Repository navigation
Let a collector close a stream, and report a cut stream as cut (E30) - #53
Merged
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes, and why
Closes the verifier half of EVASION.md E30 and builds the producer half, so E30 moves from
workingtohalf_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 literalfinalis 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.INTEGRITY_STREAM_NOT_CLOSEDis coverage and degrades CH06 like a missing ledger, because a live tail is open by definition.INTEGRITY_RECORDS_AFTER_CLOSEis inadmissible, and also fires across runs through the seen-stream ledger, which now remembersclosed.INTEGRITY_STREAM_CLOSE_UNVERIFIEDreports afinalclaim nothing trusted vouched for. New--require-closed-streamsturns a missing terminator intoINTEGRITY_STREAM_END_MISSING, inadmissible. It is refused without a trust store and enterstrust_config_digestonly when on.cohaera.emit):StreamSigner.sign(record, final=True)closes the stream and refuses to reopen it;state()recordsclosed.attest=Truesigns a batch boundary without closing.python -m cohaera.emit sign --close.tools/collector_sign.sign_stream(close=True).truncatedis nowmanipulated/inadmissibleon both Cohaera and the independent baseline (which gained the samefinalhandling). The local lab and the eval corpus sign open streams, so their signed sessions now carryINTEGRITY_STREAM_NOT_CLOSED.Evidence
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). Newtests/test_stream_close.py(25 tests) covers the wire format, the four codes, the flag, the ledger across runs, and the signer and CLI.finalfrom the signing input fails 4 tests. Removing therequire_closedbranch fails 7 tests acrosstest_stream_close.py,test_evasion.pyand the CH06 lab.python -m pytest tests/ -qpasses: 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 demopasses.mypypasses.python tools/readme_facts.py --checkpasses.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.pyre-run and the card regenerated.If this changes an evasion
EVASION.mdE30 row and section updated tohalf_closed, the self-test rewritten to pin both halves, counts regenerated.Anything a reviewer should push back on
INTEGRITY_STREAM_NOT_CLOSEDis 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.finalfrom the closing record breaks its signature. Addingfinalto 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 setsclosed_at.lab/local/README.mdquoted a stderr block that was already partly stale before this change (CH04 had moved tonot_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