Retire work_products whose files the run deleted

Fix #16 kept deletions from registering, but rows created by earlier
runs stayed `status = "active"` forever even after the agent `rm`'d
the file. The DocumentCenter kept showing zombies and
`openWorkProduct` resolved to an empty path.

`ChangeSet` now carries both `changed` and `deleted` paths, populated
from the two detectors:

  - git porcelain: the `D` status entries that were previously
    dropped are now captured into `deleted` instead;
  - baseline diff: paths present in the baseline but no longer on
    disk at diff time are `deleted`.

A new `retireDeletedArtifacts` pass runs alongside
`projectChangedPaths` and flips the matching
`work_products.status` to `archived`, bumping `last_touched_by_run_id`
to the retiring run so the provenance timeline records the deletion.
`upsertFilesystemArtifact` sets `status = "active"` on update so a
subsequent run that recreates the path brings the row back into the
visible set. A new `ProjectionOutcome` variant — `retired` — lets the
hook and reconciliation logs report both sides of the fan-out.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 16:56:22 -04:00
1 parent 090a656f91
commit 0813e691cd
3 files changed
+116 -40

No files matched your search

+3 -2
View File
@@ -133,9 +133,10 @@ export class RuntimeBridge {
workspaceBaseline: ctx.workspaceBaseline,
});
const inserted = outcomes.filter((o) => o.kind === "inserted").length;
if (inserted > 0) {
const retired = outcomes.filter((o) => o.kind === "retired").length;
if (inserted > 0 || retired > 0) {
this.deps.logger.info(
`[runtime-bridge] reconciliation registered ${inserted} new artifact(s) for run ${ctx.runId}`,
`[runtime-bridge] reconciliation for run ${ctx.runId}: ${inserted} registered, ${retired} retired`,
);
}
} catch (error) {