Skip to content

Let a collector close a stream, and report a cut stream as cut (E30) - #53

Merged
404SecNotFound merged 1 commit into
mainfrom
claude/compassionate-meitner-nzp6tq
Oct 7, 2026
Merged

404SecNotFound merged 1 commit into
mainfrom
claude/compassionate-meitner-nzp6tq

Conversation

@404SecNotFound

Copy link
Copy Markdown
Owner

What this changes, and why

Closes the verifier half of EVASION.md E30 and builds the producer half, so E30 moves from working to half_closed. The chain proves nothing is missing in between and the signatures prove the collector wrote what remains; neither said how long a stream was meant to be, so cutting records off the end of a signed stream left a contiguous, fully verified prefix and no code of any kind.

The last record of a stream may now carry integrity.final: true, and the literal final is appended to that record's signing input, so "nothing follows this" is the collector's statement and cannot be added or stripped without the signature failing. Non-final records sign exactly what they did, so every existing signature still verifies.

  • Verifier: four codes. INTEGRITY_STREAM_NOT_CLOSED is coverage and degrades CH06 like a missing ledger, because a live tail is open by definition. INTEGRITY_RECORDS_AFTER_CLOSE is inadmissible, and also fires across runs through the seen-stream ledger, which now remembers closed. INTEGRITY_STREAM_CLOSE_UNVERIFIED reports a final claim nothing trusted vouched for. New --require-closed-streams turns a missing terminator into INTEGRITY_STREAM_END_MISSING, inadmissible. It is refused without a trust store and enters trust_config_digest only when on.
  • Producer (cohaera.emit): StreamSigner.sign(record, final=True) closes the stream and refuses to reopen it; state() records closed. attest=True signs a batch boundary without closing. python -m cohaera.emit sign --close. tools/collector_sign.sign_stream(close=True).
  • Labs: the CH06 conformance lab signs its canonical stream closed and scores keyed cases under the flag, so truncated is now manipulated / inadmissible on both Cohaera and the independent baseline (which gained the same final handling). The local lab and the eval corpus sign open streams, so their signed sessions now carry INTEGRITY_STREAM_NOT_CLOSED.

Evidence

  • The defect was reproduced first, and the test that reproduces it is in this change. test_evasion_30_... previously pinned the cut as passing; it now pins both halves (still passes on an open stream, inadmissible on a closed one under the flag). New tests/test_stream_close.py (25 tests) covers the wire format, the four codes, the flag, the ledger across runs, and the signer and CLI.
  • I mutated the fix and the test failed. Removing final from the signing input fails 4 tests. Removing the require_closed branch fails 7 tests across test_stream_close.py, test_evasion.py and the CH06 lab.
  • python -m pytest tests/ -q passes: 1549 passed, 18 skipped by design.
  • python tests/test_evasion.py: 29 constructed evasions, 14 working, 13 half closed, 2 closed, plus 6 remedies.
  • ruff check src tests eval tools lab demo passes. mypy passes.
  • python tools/readme_facts.py --check passes.

Also run: both lab --checks (regenerated and committed; every changed line is explained above), python tools/release_gate.py, python tests/fuzz_smoke.py 4000 (0 exception classes).

If this changes detection

  • python eval/run_eval.py re-run and the card regenerated.
  • Recall and false-positive rate, before → after: unchanged in every cell (manifest 100.0% / 42.0%, name_only 53.3% / 15.3%).
  • Which way the FPR moved, and why: it did not. The only card movement is self-reported coverage, down 0.01 in each cell (for example 0.65 → 0.64), because the corpus signs open streams and CH06 now multiplies its confidence by 0.9 for an unattested end. That is the honest reading of an unclosed stream, not a regression.

If this changes an evasion

  • EVASION.md E30 row and section updated to half_closed, the self-test rewritten to pin both halves, counts regenerated.

Anything a reviewer should push back on

  • INTEGRITY_STREAM_NOT_CLOSED is coverage, not inadmissible, by default. Making it inadmissible would refuse every batched deployment. The flag is the operator's statement that their collectors close streams.
  • The lowest verified close wins; a second close later in the same stream is itself after-close.
  • Stripping final from the closing record breaks its signature. Adding final to an ordinary record breaks that record's signature and does not get to charge the genuine records after it as after-close, because only a verified close sets closed_at.
  • The heartbeat half of the remedy (a periodic signed count bounding what a cut between heartbeats can lose) is not built; that is why E30 is half closed rather than closed.
  • lab/local/README.md quoted a stderr block that was already partly stale before this change (CH04 had moved to not_evaluated: NO_POLICY_EVIDENCE); it now quotes the documented command's actual output.

🤖 Generated with Claude Code

https://claude.ai/code/session_01As5wcUhETPg1K3ceCVrWew


Generated by Claude Code

The chain proves nothing is missing in between and the signatures prove
the collector wrote what remains; neither said how long a stream was meant
to be, so cutting records off the end of a signed stream left a verified
prefix and no code of any kind.

The last record may now carry integrity.final, and the literal "final" is
part of that record's signing input, so the closing statement is the
collector's and cannot be added or stripped without the signature failing.
Non-final records sign exactly what they did, so every existing signature
still verifies.

Verifier: INTEGRITY_STREAM_NOT_CLOSED (coverage), INTEGRITY_RECORDS_AFTER_CLOSE
(inadmissible, also across runs through the ledger, which now remembers a
close), INTEGRITY_STREAM_CLOSE_UNVERIFIED, and under the new
--require-closed-streams flag INTEGRITY_STREAM_END_MISSING (inadmissible).
The flag is refused without a trust store and enters the run identity only
when on.

Producer: StreamSigner.sign(final=True) closes and refuses to reopen, state
records the close, attest=True signs a batch boundary without closing,
`python -m cohaera.emit sign --close`, collector_sign.sign_stream(close=True).

The CH06 lab signs its stream closed and scores keyed cases under the flag,
so `truncated` is now manipulated/inadmissible on both the product and the
independent baseline. Card: no recall, FPR or precision moved; self-reported
coverage drops by 0.01 because signed corpus streams are open. E30 is half
closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01As5wcUhETPg1K3ceCVrWew
@404SecNotFound
404SecNotFound merged commit 258d3c1 into main Oct 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants