Repository navigation
Harden the verifier's input and state boundaries - #9
Merged
Merged
Conversation
`load_json` returned whatever `json.loads` produced, and every caller read the result with `.get`. Valid JSON of another type - `[1]`, `"evidence"`, `5`, `null` - therefore reached that `.get` and ended in an AttributeError traceback from `check`, `compose` and `import-ci`, where a refusal naming the file belonged. #5 fixed this class for `authority.json`; the general record and payload loader still had it. The shape is checked once, at the loader, and refused as BLOCKED with the file and the type it held. Regressions in tests/test_input_boundaries.py: four wrong shapes through `check`, `compose`, and each of `import-ci`'s two payloads, asserting exit 2, the named reason and no traceback. All 16 fail against main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016m6di8xFVdwwq949mJx9U1
`relative_inside` says the evidence directory is excluded whole, but it
built the exclusion by listing the files that directory held when the
command started. The list was fixed before the gates ran, so a file a gate
created there - an artifact, where evidence/3 tells behavioural gates to
put them - was not in it, and `run` read it as the gate moving the
repository: BLOCKED, drift on `.verification/artifacts/...`. Only a
`.gitignore` entry for `.verification/` hid this, and the exclusion exists
so that correctness does not depend on one.
The exclusion now carries the directory as a prefix entry ("/" suffix), and
`state_id`, `state_paths` and `is_dirty` test paths through one helper that
honours it. Named record paths outside the directory are still excluded
exactly, as before.
Regressions: with `.verification/` not ignored, a gate that writes
`.verification/artifacts/new.json` runs PASS with no drift - this fails
against the previous commit; and a gate that writes outside the directory
is still drift, naming the path, so the prefix cannot have switched drift
detection off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016m6di8xFVdwwq949mJx9U1
`_hash_path` refused to follow a symlink at the top level - a link's identity is where it points - but its directory branch walked with `rglob` and read every child that `is_file()`, which follows links. `git status --untracked-files=all` lists ordinary untracked files one by one, so the directory branch is reached through an untracked nested repository, which it reports as a single `dir/` entry. A symlink inside one folded the contents of a file outside the repository into the state id: editing that outside file changed it. The directory branch now applies the top-level rule inside: an explicit `os.walk(followlinks=False)` - not `rglob`, whose symlink handling changed between the interpreters CI runs - digests links by target and regular files by content. Regression: an untracked nested repository holding links to an outside file and an outside directory keeps its state id when those outside bytes change (fails against the previous commit), and still changes it when a link is retargeted or a real file inside is edited. Passes on 3.11 and 3.14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016m6di8xFVdwwq949mJx9U1
`compose` under evidence/3 reads the baseline beside the first record it is given. When that record lived anywhere else, it reported GOVERNING BASELINE MISSING with the generic advice to run `verify.py baseline` - while the baseline sat beside the other records. Found during the Cerberus qualification (#8), where it sent a correct run the wrong way. Which baseline governs is unchanged. The refusal now names the exact path it checked and that it reads beside the first record, and, when a baseline does exist beside another record given, names that one and says to pass a record from its directory first. Regression: with the first record copied outside the evidence directory, the message names both paths; with no baseline beside any given record, it names the one it checked and points nowhere. Fails against the previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016m6di8xFVdwwq949mJx9U1
Deep-Sixed
marked this pull request as ready for review
September 25, 2026 11:01
This was referenced Sep 25, 2026
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.
Work package: input and state-boundary hardening. Branched from
mainat1865ef6. It fixes three findings from the repository review of the merged #2–#5 state, and one observation from the Cerberus qualification (#8). Each was reproduced against the CLI onmainfirst. None of them let bad evidence through: each either crashed, refused good evidence, or said the wrong thing.Four commits, one per defect, each with a regression that fails against its parent:
14c00a6[1],"evidence",5,null) reached.getand crashedcheck,composeandimport-ciwith anAttributeErrortraceback. #5 fixed this forauthority.jsononly.load_jsonrefuses anything that isn't an object, as BLOCKED, naming the file and the type.f8863bb.verification/was documented as "excluded whole", but the exclusion listed the files it held before the gates ran. An artifact a gate wrote there was read as drift (BLOCKED) unless.gitignorehid it.state_id,state_pathsandis_dirtyshare one test that honours it.a1a45c7_hash_pathfollowed symlinks. Git reports an untracked nested repository as onedir/entry, so a link inside it pulled the bytes of a file outside the repository into the state ID.rglob, whose symlink behaviour changed between the Python versions CI runs.2a1e36fcomposereads the baseline beside the first record given. With that record elsewhere, it said "GOVERNING BASELINE MISSING" while the baseline sat beside the others.Regressions
tests/test_input_boundaries.py, 20 cases:check,compose, and each ofimport-ci's two payloads), each asserting exit 2, the named reason and no traceback..verification/is not drift. A control case, a gate writing outside it, is still drift, naming the path.Compatibility contract: stays
evidence-v3.2(decided)Fixes 2 and 3 change how the state ID is computed in two edge cases: files a gate creates under an un-ignored
.verification/, and symlinks inside an untracked nested repository. For every other tree the IDs are the same as before. I've keptevidence-v3.2and not moved the version. My reading is that these are identity bugs, not a change in what counts as admissible evidence, and a record from before the fix at worst goes stale and fails safe. Decision (maintainer, 2026-09-25): keepevidence-v3.2. No version bump, no installer pin move.Verification
Climbed on
2a1e36f, statesha256:f092f09e…d2318, against a baseline captured on main1865ef6:drift: false.2a1e36f): lint, tests-py311, tests-py314 PASS; imported withimport-ci.🤖 Generated with Claude Code
https://claude.ai/code/session_016m6di8xFVdwwq949mJx9U1
Generated by Claude Code