Conversation
Since the receipt shim refactor (4a23e31, #116), the trust pins live in scripts/receipt_pins.py, but the gate surface still listed only the files that held them before. receipt's append gate calls a proposal gate-changing only when it touches gate_surface, so a resolver-shaped append that also edited the pins, or bumped receipt in pyproject.toml and uv.lock, was judged as plain data: accepted, and never named. Reproduced with the base gate invoked as the trusted CI job invokes it: 3dd95a0's gate accepted the release-20 append plus a receipt_pins.py edit as "+5 appended vs base, release 20", and #299's gate accepted #297's seven rows plus a pins edit as "+7 appended vs base, release 21". The surface now covers: - scripts/**: the judge runs a script there, so scripts/ is sys.path[0], and both pull request jobs also put it on PYTHONPATH. A new module there shadows imports (the standard library's included), and a sitecustomize.py runs at startup. Both were observed. - pyproject.toml and uv.lock, which pick the receipt version the judge runs, and uv.toml, .python-version and .venv/**, which uv also reads from the project it provisions the judge from. Each was observed to change what `uv sync --locked --no-dev --project <dir>` and `uv run` do. The same candidates are now refused as mixed, naming the decider, while both pure appends still pass and a pins-only change is reported as gate-only. The resolver's proposals change only ledger/** and releases/manifests/**, so they stay data proposals. Tests: every decider is refused as mixed beside a witnessed append and named when changed alone; a pure append beside unchanged deciders passes; an audit-hook run shows every file the gate opens or imports from its own checkout is on the surface; and the shim's surface is pinned as a deliberate superset of the originals'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Arch checks is red for a reason that predates this PR: |
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.
Summary
The append gate's
gate_surfaceno longer matched the files that decide its verdict. Since the receipt shim refactor (4a23e31, #116, 2026-07-25), the trust pins live inscripts/receipt_pins.py. The surface still listed only the scripts that held them before, and never listedpyproject.tomloruv.lock, which pick the receipt version the judge runs.receipt 0.6.2 treats a proposal as gate-changing only when it touches
gate_surface(append_gate.py_classify_surfaces/check_surface_separation). A mixed data/gate proposal is refused, and unclassified changes are dropped on the data path. So an append that also edited the pins passed as plain data and was never named. The independent review of #299 found this by reading the code (verdict.md, "Outside this PR's scope").Invariant (trust boundary): every file in the judging (base) checkout that can decide a gate verdict is on
gate_surface.Reproduction (before the fix)
These runs used the base gate exactly as the "Trusted base append gate" job runs it:
PYTHONPATH=$J/scripts PYTHONNOUSERSITE=1 uv run --locked --no-dev --project $J python $J/scripts/check_thesis_facts_append.py --root <clone> --commit <c> --base-ref <b>, from another cwd.receipt_pins.pyedit, vs d77afe2OK: 221 rows, immutable prefix 128, +5 appended vs base, release 20receipt_pins.py, vs 3dd95a0OK: 228 rows, immutable prefix 128, +7 appended vs base, release 21OK ... +7 appended vs base, release 21lineThe mixed proposal and the pure one got the same verdict line.
What else decides the verdict (each observed on a scratch uv project)
scripts/**. The judge runsscripts/check_thesis_facts_append.py, soscripts/issys.path[0]. Both PR jobs also put it onPYTHONPATH.scripts/sitecustomize.pyran at interpreter startup.scripts/tempfile.pyshadowed the standard library, even with noPYTHONPATH(the push job's spelling).uv.tomland.python-version. Run from another cwd,uv sync --locked --no-dev --project <dir>failed on a project-diruv.tomlwithrequired-version = "==0.0.1". A project-dir.python-versionof3.12switcheduv runto Python 3.12.14..venv/**. A committed.venv/.../site-packages/*.pthsurviveduv sync --lockedand executed onuv run. This ran on one machine; on CI the committed venv's interpreter path would also have to exist on the runner.The four explicit
scripts/...entries are subsumed byscripts/**.receipt_pins.pynow documents why each entry is there.After the fix
The same candidates, judged by this branch's gate:
thesis-facts append check failed: mixed data/gate proposal is forbidden: DATA_SURFACE changes=[...]; GATE_SURFACE changes=['scripts/receipt_pins.py']; split them into separate pull requestsOK: 221 rows ... +5 appended vs base, release 20OK: 228 rows ... +7 appended vs base, release 21OK: gate-only proposal; DATA_SURFACE unchanged; GATE_SURFACE changes=['scripts/receipt_pins.py']Ordering and who is affected
This PR is judged by the current base gate, which does not yet see
receipt_pins.py. It will pass as data (+0 appended) and will not name the change. The new surface applies to PRs whose base includes this merge.Resolver proposals stay pure data. ThesisInstitute/thesis
scripts/resolve_pending.py(propose_ledger_append→_prepare_release_files) commits only:ledger/official_observations.jsonlledger/series_catalog.jsonledger/series_uuid_registry.jsonl(when minted)releases/manifests/<release>.*filesIt reads
scripts/build_series_catalog.pyandscripts/receipt_pins.pyfrom the base but never changes them.Resolve catalog supersede links against every row's effective assertion id #306 touches
pyproject.toml,uv.lockandscripts/build_series_catalog.py, with no ledger change. It becomes a named gate-only proposal, which still passes.Future receipt bumps (like Pin receipt 0.6.2 and DigiCert's 2026 timestamp responder #299) become named gate-only proposals.
releases/README.mdsays a data PR "cannot change the verifier, cutter, canonicalizer, append workflow, or anchors". That held for the verifier code, but not for the pins or the receipt version, from Repin integrity shims: vidimus==0.1.2 → receipt==0.2.0 (upstream rename) #116 until this PR. The file is immutable after genesis, so the accurate statement goes in the rootREADME.md.Unchanged limits. Branch protection still requires only status checks, so a gate-only PR still needs no review. This PR adds the refusal and the naming, not a review requirement.
Tests
tests/test_thesis_append_shim_isolation.py(in the CI "Ledger observation invariants" list) covers seven deciders:scripts/receipt_pins.py,pyproject.toml,uv.lock,uv.toml,.python-version,scripts/sitecustomize.pyand a.venv.pth..venv.PYTHONPATH="$base_gate/scripts"anduv sync --project "$base_gate"spellings are pinned, and the surface coversscripts/**and the uv inputs.tests/test_receipt_shim_transparency.pypins the shim's surface as a deliberate superset of the originals' (AST read of the hash-authenticated fixture). No existing differential case touches a decider, so byte-identity is unaffected. This is the only place the pair is meant to classify differently.Mutation check: with the old
gate_surface, 16 of the 17 new isolation tests fail. The pure-append control passes either way.Verification run locally:
174 passed, 5 xfailed(the xfails are the adversarial file's documented strict-xfail boundaries); the new transparency test separately: passedscripts/check_thesis_facts_append.py --commit HEAD(push path):OK: 229 rows, immutable prefix 128, release 21scripts/verify_release_chain.py --full:release chain OK: 22 releases, HEAD=0021-09d13cdddbedc992.jsonruff checkclean on the changed files.pytest testswas not run locally: the host is RAM-constrained. Instead I ran every test file that imports the pins or the gate; the change is one frozenset read only throughAPPEND_GATE_SPEC.🤖 Generated with Claude Code