Project evidence artifacts into the durable evidence_artifacts table (Epic 6) - #15
Conversation
The evidence_artifacts table (with an attempt_id FK into run_attempts) has existed since Epic 2 but was never written. Epic 3 populated run_attempts and scoped artifacts per attempt on disk; this closes the loop by projecting each run's hashed evidence artifacts into evidence_artifacts, linked to the producing attempt. The Artifact records (sha256 + path) accumulate in ChainContext.artifacts, which never left the executor. Surface them via an in-process carrier on ChainExecutionResult (evidence_artifacts, exclude=True so it is never in an API response — Artifact.path is an absolute host path, same leak class as the workspace_id fix), capture them onto RunRecord.artifacts in the route, and project them in the Postgres adapter (delete + reinsert, mirroring handler_results; inserted after run_attempts so the FK target exists). - Artifact gains attempt_id; ArtifactStore.write stamps it from its scope - RunRecord.artifacts embedded + round-trips via snapshot (like attempts) - evidence_artifacts rows store the path in object_uri (server-side only); used_as_evidence=TRUE (quarantined narrative never enters record.artifacts) - Docs: persistence.md, workspace_isolation.md, README updated Tests: ArtifactStore stamps attempt_id; record round-trip carries artifacts; Postgres-gated projection + idempotency; route captures artifacts with the attempt link; the carrier is excluded from execute/results responses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015R7wjqoCZDF1pqNp8AiUJ6
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93798d86d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| agent_self_certification_used=context.agent_self_certification_used, | ||
| auto_merge=False, | ||
| auto_deploy=False, | ||
| evidence_artifacts=list(context.artifacts), |
There was a problem hiding this comment.
Include ledgered LLM invocation artifacts in the projection
For runs that invoke a configured agent (the default runtime uses the fake analyst), LLMInvocationRecorder writes llm_invocation_*.json and appends it to the evidence ledger, but it writes directly to the store/evidence writer rather than through ChainContext.record_artifact, so it never enters context.artifacts. Since the durable carrier is populated only from context.artifacts here, those ledgered LLM_INVOCATION artifacts are omitted from RunRecord.artifacts and therefore never projected into evidence_artifacts, leaving the Postgres audit table incomplete for agent-backed executions.
Useful? React with 👍 / 👎.
What & why
The
evidence_artifactstable (with anattempt_idFK intorun_attempts) has existed since Epic 2 but was never written. Epic 3 populatedrun_attemptsand scoped artifacts per attempt on disk — leaving a populated parent table, real on-disk evidence, and an empty child table. This PR closes that loop: each run's hashed evidence artifacts are projected intoevidence_artifacts, linked to the producing attempt.The core obstacle
The rich
Artifactrecords (sha256 + path) accumulate inChainContext.artifacts, which never left the executor —ChainExecutionResultonly carried artifact names. So the change surfaces those records out of execution and into the record/projection, mirroring howattemptsalready works.Changes
Artifact.attempt_idadded;ArtifactStore.writestamps it from the store's per-attempt scope (Epic 3) — every artifact is self-describing about which attempt produced it.ChainExecutionResult.evidence_artifactsin-process carrier,Field(..., exclude=True)so it is never in an API response.Artifact.pathis an absolute host path — same disclosure class as theworkspace_idleak fixed earlier — so it stays server-side only.RunRecord.artifactsembedded and round-tripped via the snapshot (likeattempts); the route capturesresult.evidence_artifactsafter each attempt, so retries accumulate.evidence_artifactsper save (same idiom ashandler_results), inserted after therun_attemptsupsert so the FK target always exists. Path stored inobject_uri(operator-queryable, never API-exposed);used_as_evidence = TRUE(quarantined agent narrative is never added torecord.artifacts, so it never lands here).persistence.md,workspace_isolation.md, README "Deferred" entry.Tests (276 passed, 10 skipped — Postgres tests skip without a DSN)
ArtifactStore.writestamps / omitsattempt_idper scope.artifacts(memory + serialization).evidence_artifactsprojects with the attempt link, sha256,used_as_evidence, and is idempotent across re-saves./executeand/resultsresponses.Scope / safety
No auth/RBAC, no object-storage backend (that's slice b), no change to verifier/gate authority, no merge/deploy/bypass.
git diff --checkclean; security review clean.🤖 Generated with Claude Code
Generated by Claude Code