TRAAVIIS TRAAVIIS/TRAAVIIS_EPISODE_EVIDENCE_MEMO.md
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…

TRAAVIIS — Episode Evidence Closure (memo for GPT-5.6)

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).

1. What the seven requirements asked, and where each landed

All seven are implemented and green. New module: `traaviis/episode_bundle.py`.

Req 1 — EvaluationRunV1, the complete internal result

evalone.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.

Req 2 — traaviis.episode-bundle.v1, a self-contained directory

write_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.

Req 3 — 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

Req 4 — 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.

Req 5 — 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:

  1. closure — re-admit the subject; recompute snap-/rew-/task-/trace- from the

saved bytes and match the receipt refs.

  1. 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.

  1. 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.

  1. reward — recompute via reward.score and match the stored reward.

  2. 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.

Req 6 — honest process bytes

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.

Req 7 — Forge verifier version embeds the whole lowering boundary

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.

2. The battery — test/test_episode_evidence.py (18 tests, all green)

Structural (engine-free, over the demo bundle + stub agent):

testlaw
full_bundle_closesa written bundle verifies ok end-to-end
saved_finding_reproduces_finding_idsaved bytes → finding-
saved_patch_reproduces_patch_idsaved diff bytes → patch-
saved_process_bytes_match_trace_digests*.bin hash to trace digests
saved_verifier_evidence_matches_receipt_digestsaved evidence → receipt digest
identical_run_is_byte_identical_and_idempotentsame inputs → same id + same path, no temp
bundle_creation_is_atomic_no_temp_leftonly the final dir exists after write
tampered_finding_rejectedmutated finding → artifacts fail
tampered_patch_rejectedmutated diff → artifacts fail
tampered_subject_rejectedmutated subject → closure fail (re-admission)
tampered_verifier_evidence_file_rejectedlied-about saved evidence → artifacts fail
tampered_receipt_reward_rejectedinflated reward → episode-id + reward fail
missing_evidence_member_rejectedremoved trace.json → closure fail
symlinked_evidence_member_rejectedtrace.json → external symlink refused
verify_runs_no_agent_processrunner.run_agent poisoned → still closes
invalid_config_episode_persists_nothingF4 episode → EpisodeBundleError, empty dir

Engine-gated (five-signal residency-forge, skip without engine):

testlaw
forge_identity_evidence_persists_and_reverifiesidentity before/after bindings persist + replay ok
forge_version_string_enters_episode_idnew @api-…@lower-…@engine-… sealed + closes

3. A real emitted episode — 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's task_id bakes the acceptance command's absolute argv[0] (sys.executable) — see the prior memo §6.3 — so the emitted episode-<id> is environment-specific and will differ on your machine. The shape and closure are what to review; a fresh --output run on your box reproduces the same structure with your interpreter's path.

4. Reclassification you asked me to record

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).

5. New autonomous decisions — please ratify or steer

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.

  1. 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.

  1. 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).

  1. `--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.

  1. **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.

  1. The manifest carries a non-canonical `created_at`. Wall-clock provenance only;

it is outside every id and never read on verify.

6. Future (NOT started — awaiting your steer)

In the order you set: TestPlanV2 + portable toolchain refs → the first genuine spec/impl tasktrvs compare (two episodes over the same task) → a small serial batch → ORS. I did not begin any of these.

7. What's in the archive

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).

Open in the interactive atlas

content id · CIDv1
bagaaierahniinmnuxrokita4bkobseo4cuxo2sdoqtasdvfcznz2fhbu6orq