feat(missions): capture a coding phase's diff to durable storage
First half of mission delivery: the work is captured before anything is published. A coding mission has until now produced nothing durable — the checkout is deleted thirty minutes after completion and `register_artifact` had no callers at all, so the only surviving output was an LLM narrative of what the agents said they did. `capture_phase_diff` writes `diff.patch`, `diffstat.txt` and `delivery.json` under `<missions_root>/_outputs/<mission>/<phase>/` and registers a `code_diff` artifact. That directory is a *sibling* of the per-mission directories the sweeper removes, and outside every bind mount handed to a container — so teardown cannot take the record with it and agents cannot edit their own evidence. Three details that decide whether this works at all: - `git add --intent-to-add` before diffing. Untracked files are invisible to `git diff`, and a phase that only *creates* files is the likeliest shape for generated code — silently capturing an empty patch would be the worst possible failure. The index is reset afterwards so capture leaves the tree exactly as the agents left it, which the test asserts. - Build output is excluded by pathspec (`target`, `node_modules`, `.venv`, …). A phase that ran `cargo build` leaves a directory larger than the repo. - An empty diff is still an artifact, flagged `empty: true`. "This coding phase wrote no code" is currently invisible to an operator and is worth saying out loud. `RegisterArtifact` gains `metadata`, which the column has had since 0047 and nothing ever wrote; the diffstat and base sha go there. No migration needed — `kind` is unconstrained TEXT and the column already exists. Tests run against a real `git init` repo rather than a mock: every bug in this area so far came from git behaving differently than assumed, and a fake git would have agreed with the assumption. `capture_phase_diff_at` takes explicit paths so parallel tests cannot race through the process-global CLAWMATES_MISSIONS_ROOT — the first version of these tests did exactly that and two of four failed non-deterministically. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ea3d145aac
commit
716ee9a304
@@ -556,6 +556,11 @@ pub struct RegisterArtifact<'a> {
|
||||
pub title: Option<&'a str>,
|
||||
pub generated_by_run: Option<Uuid>,
|
||||
pub render_pdf: bool,
|
||||
/// Free-form facts about the artifact (diffstat, branch, gate verdict).
|
||||
/// The column has existed since 0047 and was never written — an artifact
|
||||
/// with no metadata is a path and a kind, which is not enough for a UI to
|
||||
/// say anything useful about it.
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Register an artifact discovered on disk (or produced inline).
|
||||
@@ -566,14 +571,15 @@ pub async fn register_artifact(pool: &PgPool, a: RegisterArtifact<'_>) -> Result
|
||||
let row = sqlx::query(
|
||||
"INSERT INTO mission_artifacts
|
||||
(id, mission_id, phase_id, path, kind, mime, title,
|
||||
generated_by_run, render_pdf_status)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
|
||||
generated_by_run, render_pdf_status, metadata)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,COALESCE($10, '{}'::jsonb))
|
||||
ON CONFLICT (mission_id, path) DO UPDATE
|
||||
SET kind = EXCLUDED.kind,
|
||||
mime = COALESCE(EXCLUDED.mime, mission_artifacts.mime),
|
||||
title = COALESCE(EXCLUDED.title, mission_artifacts.title),
|
||||
generated_by_run = COALESCE(EXCLUDED.generated_by_run,
|
||||
mission_artifacts.generated_by_run),
|
||||
metadata = COALESCE(EXCLUDED.metadata, mission_artifacts.metadata),
|
||||
updated_at = now()
|
||||
RETURNING id",
|
||||
)
|
||||
@@ -586,6 +592,7 @@ pub async fn register_artifact(pool: &PgPool, a: RegisterArtifact<'_>) -> Result
|
||||
.bind(a.title)
|
||||
.bind(a.generated_by_run)
|
||||
.bind(render_status)
|
||||
.bind(a.metadata.as_ref())
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
Ok(row.get("id"))
|
||||
|
||||
Reference in New Issue
Block a user