v1.3 provenance hardening - #105
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
WhatsYourWhy
force-pushed
the
feat/v1.3-provenance-hardening
branch
from
August 14, 2026 04:08
ee20204 to
395c7b5
Compare
Makes published Hardstop artifacts traceable to the exact inputs that produced them, and stops unhealthy runs from publishing. Additive only: classification, impact score, scope, correlation and suppression are computed exactly as in v1.2, and the alerts table, brief.v1 read model and export_schema_version "1" stay compatible. Exact run-to-input lineage. New run_raw_items table records which raw items each run group fetched, with a NEW/DUPLICATE/RETRY action. compute_raw_item_batch_digest now hashes those rows instead of approximating the batch from FETCH SourceRun counters, so changing one raw item's content changes the digest. Run groups without lineage rows fall back to the previous counts digest unchanged, which is what keeps pre-v1.3 databases working; the fallback keys off row count rather than table existence, because create_all materializes an empty table on any database. raw_id and fetch_action are excluded from the hash so a replay of identical content digests identically. Pre-publication gate. Readiness checks move out of cmd_run's inline doctor block into ops/readiness.py and now run after ingest and before the brief. Strict mode skips the brief when readiness is BROKEN; best-effort marks it DRAFT_ONLY through an additive publication key and a renderer banner. Alerts are still created exactly as before, including from BLOCKED sources: only publication is gated, so v1 decision semantics are untouched. Step 4 re-evaluates rather than reusing the earlier result, because generating the brief can create tables and flip the schema_drift finding. Export provenance. hardstop export now emits a RunRecord; it was the only artifact-producing command without one. Manifests gain export_content_hash over the exact bytes on disk, while export_data_hash keeps its existing timestamp-independent definition so pinned manifests stay valid. The CSV manifest now hashes the real CSV instead of a placeholder dict containing a fresh timestamp, which changed every run and certified nothing. export_brief populates artifact_hashes; export_sources deliberately does not, since sources health is derived counters rather than hashed documents. JSON exports are written with newline="" so Windows bytes match the serialized payload. Evidence verification. load_incident_evidence_summary now compares the stored artifact_hash against the payload. It previously recomputed the hash and used it only as a fallback for a missing value, so a tampered or stale hash was trusted silently. cmd_incidents_replay and the brief read path now share one implementation. Strict provenance failures. A RunRecord emission failure is fatal in strict mode with a distinct RUN_RECORD_EMISSION_FAILED marker. Escalation happens after the try/finally so a command's own exception is never masked by a provenance failure. Also fixes SystemExit from a sub-command skipping Step 4 of hardstop run. SystemExit is a BaseException and escaped the existing except Exception guards. Also re-includes tests/output/ in .gitignore. The bare output/ rule matches a directory of that name at any depth, so the new evidence verification tests were silently skipped by git add; the existing test in that directory is tracked only because it was force-added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WhatsYourWhy
force-pushed
the
feat/v1.3-provenance-hardening
branch
from
August 14, 2026 04:12
395c7b5 to
fb1cbaa
Compare
WhatsYourWhy
changed the base branch from
chore/ci-and-line-ending-normalization
to
main
August 14, 2026 04:36
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.
Why
Makes every published Hardstop artifact traceable to the exact inputs that produced it, and stops unhealthy runs from publishing.
Additive only — no change to decision semantics. Classification, impact score, scope, correlation and suppression are computed exactly as in v1.2. The
alertstable, thebrief.v1read model andexport_schema_version: "1"stay compatible, per the v1 freeze inCHANGELOG.md.What
Exact run-to-input lineage. New
run_raw_itemstable records which raw items each run group fetched (NEW/DUPLICATE/RETRY).compute_raw_item_batch_digestnow hashes those rows rather than approximating the batch from FETCHSourceRuncounters — its own docstring called that an approximation. Changing one raw item's content now changes the digest; two batches with equal counts and status no longer collide.Pre-publication gate. Readiness checks move out of
cmd_run's inline doctor block intoops/readiness.pyand now run after ingest, before the brief. Previously source health was evaluated only at Step 4 — after the brief had already printed. Strict skips the brief onBROKEN; best-effort marks itDRAFT_ONLY.Export provenance.
hardstop exportemits a RunRecord; it was the only artifact-producing command without one. Manifests gainexport_content_hashover the exact bytes on disk. The CSV manifest now hashes the real CSV instead of a placeholder dict containing a fresh timestamp — it changed every run and certified nothing about the CSV.Evidence verification.
load_incident_evidence_summarynow compares the storedartifact_hashagainst the payload. It previously recomputed the hash and used it only as a fallback for a missing value, so a tampered or stale hash was trusted silently.cmd_incidents_replayand the brief read path now share one implementation.Strict provenance failures become exit 2, with escalation placed after the
try/finallyso a command's own exception is never masked.Backward compatibility
create_allmaterializes an empty table on any databaserun_raw_itemsis deliberately not added torequired_tables; that list feedsschema_drift→ exit 2, which would make every pre-v1.3 database report BROKEN on first runpublicationandartifact_hash_verifiedare added only in the non-authoritative / mismatch cases, so normalbrief.v1payloads are byte-identicalexport_data_hashkeeps its existing timestamp-independent definition so pinned manifests stay valid;export_content_hashis additiveVerified against a simulated pre-v1.3 database: readiness reports READY with no schema drift, and the digest is unchanged.
Deliberate non-goals
runpipeline only; a standalonehardstop brief --todayis still ungatedBLOCKEDsource still fetches, ingests and produces alerts, exactly as in v1.x. Gating alert creation would change frozen v1 decision behavior and needs its own proposalexport_sourceskeeps an emptyartifact_hashes: sources health is derived counters, not hashed documents, so any entry would hash the same bytesexport_data_hashalready coversFixes found along the way
SystemExitfrom a sub-command skipped Step 4 ofhardstop run. It is aBaseExceptionand escaped the existingexcept Exceptionguards.gitignore's bareoutput/rule matches that directory name at any depth, so new files undertests/output/were silently skipped bygit add. The existing test there is tracked only because it was force-added — this PR's evidence tests were missing until it was caughtnewline=""; on Windows the bytes on disk were CRLF-translated and matched no hash of the payloadVerification
Verified in a fresh clone of this branch with
core.autocrlf=trueand its own venv, so no editable install could leak source from elsewhere: 237 passed, 0 failed (baseline before this work: 184 passing). 50 new tests, including a parity guard that readiness andrun_statusagree on every BROKEN condition, and a precedence guard that a RunRecord failure never masks a command error.🤖 Generated with Claude Code