Date: 2026-07-24. Author: Claude (autonomous, under the standing "don't stop unless you need GPT-5.6" instruction). This memo + the accompanying archive are everything needed to review the Episode Evidence Closure slice you ruled — making "keep the proof" literally true. It follows the Eval-One CLI + Admission Closure memo (TRAAVIIS_EVALONE_CLI_MEMO.md), which stays valid; this is additive.
Full battery green. Run any standalone: python3 test/test_*.py (pytest is not installed; each file self-runs).
**On this machine (real TRVM engine reachable via TRVS_FORGE_DIR): 218 passed,
0 skipped, 0 failed** across 17 modules (was 200/16 — +18 in the new test_episode_evidence.py, +0 net elsewhere).
The new battery: **16 engine-free structural cases always run + 2 engine-gated
five-signal identity cases** (skip when the engine is unlocatable).
All seven are implemented and green. New module: `traaviis/episode_bundle.py`.
EvaluationRunV1, the complete internal resultevalone.evaluate(...) (new) returns {evaluation_run_version, receipt, artifacts}:
receipt — the episode-… receipt (unchanged shape + the new verification_evidence).
artifacts — {trace, finding, patch, verifier_evidence, process}, i.e. the concrete
evidence the receipt's ids/digests attest. artifacts is None for an invalid-config (F4) episode that never ran the agent.
evalone.eval_one(...) is now a thin wrapper returning evaluate(...)["receipt"]
(backward-compatible for every existing caller/test). The CLI consumes the complete EvaluationRunV1.
traaviis.episode-bundle.v1, a self-contained directorywrite_episode_bundle lays out exactly:
episode-<id>/
episode-bundle.json # manifest (members map + non-canonical created_at)
receipt.json task.json reward.json snapshot.json
subject/… # the materialized sealed subject tree
evidence/
trace.json # canonical TraceV1
finding.json # omitted when the agent produced none
candidate.patch # raw unified diff (omitted when there was none)
verifiers/<signal>.json # one canonical VerifierEvidence per scored signal
process/stdout.bin process/stderr.bin # exact captured (capped) agent bytes
It is a transport/closure format, not a new identity domain — it carries the existing content-addressed artifacts plus the raw process bytes.
verification_evidence alongside verification, entering episode-The receipt now carries verification_evidence[sig] = {format, digest} where digest is sha256: over the canonical bytes of the per-signal evidence object ({evidence_version, signal, state, detail}). "verification_evidence" was added to identity._EPISODE_IDENTITY_KEYS, so the digest map enters `episode-`: the saved evidence/verifiers/<sig>.json cannot drift without moving the episode id.
tests evidence detail = baseline + patched `[{command_id, exit_code,
stdout_digest, stderr_digest}]. command_id is host-independent (cmd-<sha256(normalized argv + cwd)>`, argv basenamed like the canonical trace).
identity evidence detail = {bindings:{<label>:{path, before_id, after_id}}}
(the existing moved key is preserved on a fail, so no prior test broke).
Live five-signal receipt maps (from the emitted episode, §3):
verifier_versions.identity.implementation:
"forge.identity.v1@[email protected]@engine-0.7.0-alpha.5"
identity evidence.detail.bindings.world:
{ path: "world/frozen.wrl",
before_id: "sem-67e954cf…60ae", after_id: "sem-67e954cf…60ae" } # held → equal
trvs eval-one BUNDLE --output DIR -- AGENT…Evaluate in memory → build the tree in a temp sibling (.tmp-episode-* under DIR) → os.rename atomically into DIR/episode-<id>/ → immediately re-verify closure before returning. A reader never observes a half-written bundle. Content-addressed + idempotent: a second identical run returns the existing path (no temp left). --json still prints the receipt (the bundle path goes to stderr so stdout stays a pure receipt). An invalid-config episode persists nothing (there is no evidence to keep) and warns.
trvs verify-episode DIR, pure replay (no agent rerun)verify_episode_bundle reopens every member under the same containment discipline as the eval-bundle loader (safe_relposix + realpath equality + commonpath; a symlink or .. is refused before the path is opened), then:
closure — re-admit the subject; recompute snap-/rew-/task-/trace- from the
saved bytes and match the receipt refs.
artifacts — recompute finding-/patch- from saved bytes; re-attest the
process bytes against the trace digests (Req 6); check each saved verifiers/<sig>.json hashes to the receipt digest.
signals — re-run the declared verifiers against the saved subject + candidate
(pure citations/patch/finding_completeness live; tests/identity injected by the CLI); each replayed state must equal the receipt state and its recomputed evidence digest must match.
reward — recompute via reward.score and match the stored reward.
episode-id — the stored receipt must be self-consistent
(identity.episode_id(receipt) == episode_id == manifest.episode_id).
Output lines: episode / closure / artifacts / <signals> / reward / episode-id / verified. Exit 0 closed, 1 a check failed, 2 the bundle cannot be opened.
The exact captured (capped) stdout/stderr are written to process/*.bin and must hash to the trace's stdout_digest/stderr_digest. verify_episode_bundle reconstructs the substrate-run-failure override from the saved trace exit_code (null ⇒ timeout) + the manifest truncation flags, so a timeout/bad-exit/truncated episode replays to the same error verdicts the live run sealed.
real_adapter().version moved from forge.identity.v1@trvm-<engine> to:
forge.identity.v1@api-<ENGINE_API_VERSION>@lower-<LOWER_RESULT_VERSION>@engine-<engine>
= forge.identity.v1@[email protected]@engine-0.7.0-alpha.5
A drift in the engine API, the LowerResultV1 contract, or the engine build now moves verifier_versions.identity, hence episode-. test_forge_adapter.py + test_real_residency.py updated to the new format.
test/test_episode_evidence.py (18 tests, all green)Structural (engine-free, over the demo bundle + stub agent):
| test | law |
|---|---|
| full_bundle_closes | a written bundle verifies ok end-to-end |
| saved_finding_reproduces_finding_id | saved bytes → finding- |
| saved_patch_reproduces_patch_id | saved diff bytes → patch- |
| saved_process_bytes_match_trace_digests | *.bin hash to trace digests |
| saved_verifier_evidence_matches_receipt_digest | saved evidence → receipt digest |
| identical_run_is_byte_identical_and_idempotent | same inputs → same id + same path, no temp |
| bundle_creation_is_atomic_no_temp_left | only the final dir exists after write |
| tampered_finding_rejected | mutated finding → artifacts fail |
| tampered_patch_rejected | mutated diff → artifacts fail |
| tampered_subject_rejected | mutated subject → closure fail (re-admission) |
| tampered_verifier_evidence_file_rejected | lied-about saved evidence → artifacts fail |
| tampered_receipt_reward_rejected | inflated reward → episode-id + reward fail |
| missing_evidence_member_rejected | removed trace.json → closure fail |
| symlinked_evidence_member_rejected | trace.json → external symlink refused |
| verify_runs_no_agent_process | runner.run_agent poisoned → still closes |
| invalid_config_episode_persists_nothing | F4 episode → EpisodeBundleError, empty dir |
Engine-gated (five-signal residency-forge, skip without engine):
| test | law |
|---|---|
| forge_identity_evidence_persists_and_reverifies | identity before/after bindings persist + replay ok |
| forge_version_string_enters_episode_id | new @api-…@lower-…@engine-… sealed + closes |
examples/eval-one/episodes/episode-<id>/A committed, five-signal 1.0 episode you can trvs verify-episode directly. Emitted by:
$ trvs eval-one examples/eval-one/residency-forge --output examples/eval-one/episodes \
--platform linux-x86_64 -- python3 $PWD/test/fixtures/residency_agent.py
status ✓ ok · reward 1 · bundle …/episode-e125d77b…f415
$ trvs verify-episode examples/eval-one/episodes/episode-e125d77b…f415
closure ✓ · artifacts ✓ · citations/patch/tests/identity/finding_completeness ✓
reward ✓ (1.0) · episode-id ✓ · verified ✓ closed
Note on the id:
residency-forge'stask_idbakes the acceptance command's absoluteargv[0](sys.executable) — see the prior memo §6.3 — so the emittedepisode-<id>is environment-specific and will differ on your machine. The shape and closure are what to review; a fresh--outputrun on your box reproduces the same structure with your interpreter's path.
The residency-forge bundle is a five-signal conformance fixture — it exercises the full citations/patch/tests/identity contract deterministically via the stub agent. It is not a genuine Evidence Residency finding about real code: the "spec" and "src" are minimal fixtures chosen to make each signal's pass/fail path reachable and byte-reproducible. Treat every reward/verdict in the examples + batteries as a contract-conformance demonstration, not a substantive spec-vs-impl result. The first genuine spec/impl task (real repo, real finding) remains future work, gated behind TestPlanV2 + portable toolchain refs (below).
None move a content-addressed identity beyond Req 3 (verification_evidence into the allowlist) and Req 7 (the version-string widening) — both of which your ruling directed.
Saved evidence files are re-checked against the receipt digest on verify. The
ruling's replay check compares the re-run verifier's digest to the receipt; I also added a check that the persisted verifiers/<sig>.json bytes hash to the receipt digest, so tampering the kept file is caught even if the replay happens to reproduce the same state. (This is what tampered_verifier_evidence_file_rejected pins.) Say the word if you'd rather the saved file be treated as purely decorative.
Runtime tamper (`policy_violations`) is not recomputed on replay. A write-escape
is a live-run observation not recoverable from a digest; replay scores the un-tampered path, and a saved receipt that lies about a tampered reward is caught by the episode-id self-consistency check (tampered_receipt_reward_rejected).
`--json` prints the receipt to stdout; the bundle path goes to stderr. Keeps the
stdout contract (a pure episode receipt) byte-identical whether or not --output is set.
**verify-episode wires tests unconditionally and identity when the engine is
locatable.** An engine-less box that verifies a bundle which scored identity gets an honest signal mismatch (never a false pass), plus a stderr warn.
The manifest carries a non-canonical `created_at`. Wall-clock provenance only;
it is outside every id and never read on verify.
In the order you set: TestPlanV2 + portable toolchain refs → the first genuine spec/impl task → trvs compare (two episodes over the same task) → a small serial batch → ORS. I did not begin any of these.
The whole TRAAVIIS working tree (traaviis/, test/, examples/ incl. the emitted episodes/episode-<id>/, cli.py, engine.py, pyproject.toml, both memos, the ruling request), minus __pycache__ / dist / .git. A read-only copy of the TRVM forge_api.py boundary is included at the archive root as TRVM_forge_api.py (unchanged this slice).