Skip to content

Derive the source census from tracked Git paths - #18

Open
kreneskyp wants to merge 5 commits into
mainfrom
issue/17-round-three-residuals
Open

Derive the source census from tracked Git paths#18
kreneskyp wants to merge 5 commits into
mainfrom
issue/17-round-three-residuals

Conversation

@kreneskyp

@kreneskyp kreneskyp commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Closes #17

Summary

  • derive an exact reviewed live-source partition from every non-archival tracked path, independent of directory, filename, or extension
  • scan non-ignored untracked live paths separately and prove the actual consumer reads them, while ignored generated artifacts remain excluded
  • detect exact path substitutions plus per-area changes and reject higher-precedence GNU Make input names
  • separate deleted-machinery absence (FR-006-AC-6/TC-026) from source-partition integrity (FR-006-AC-7/TC-034)
  • correct every retired NFR-002-AC-4 clause without blanket succession and attribute the originating external findings

Independent review response

Verification

  • full local make ci CARGO_TARGET_DIR=target/cargo-review passed at exact head 5861efce28c65d9fb72207f638317cba49878196 outside the documented Node child-process sandbox
  • 58/58 specification documents grammar-clean; all 25 matrix cases and 28/28 Rust symbols backed
  • 7/7 shared-assurance tests, MSRV, corpus, supply-chain, rustdoc, four mutation probes, and the complete Quoin chain passed
  • hosted CI was not dispatched (manual-only policy)

SUITE-008 declares the existing local Rust test command and Git premise, but is explicitly not a structured producer or Quoin attestation. This change adds no local runner, evidence collector, Make parser, evidence envelope, identity registry, or retention layer.

@kreneskyp

Copy link
Copy Markdown
Contributor Author

Reviewer: please run the full code-review and gap-analysis cycle at exact head 99dde2023fdcf838a93b89528e11c5b72314eef6.

This closes the five nonblocking residuals recorded from PR #14: Git-derived tracked population, a separate nonignored-untracked scan, an ignored proptest-regression control, explicit root and per-area populations, self-locating census failures, and corrected successor/scenario-label prose. Please specifically scrutinize whether the Git ceiling makes both the scratch-repository positive control and true non-repository refusal falsifiable, whether ignored generated files are excluded without hiding ordinary untracked sources, and whether the per-area counts reject compensating cross-area swaps.

Full local make ci CARGO_TARGET_DIR=target/cargo-review passed at this exact head, including 57/57 spec documents, 6/6 shared-assurance tests, 4 mutation probes, and the complete Quoin chain. Hosted CI was not dispatched. No runner, collector, evidence envelope, Make parser, retention layer, or duplicate shared contract was added.

@kreneskyp

Copy link
Copy Markdown
Contributor Author

Independent review — PR #18, "Derive the source census from tracked Git paths"

Head reviewed: 99dde2023fdcf838a93b89528e11c5b72314eef6
Base = merge base = origin/main: a6d58aa4df3ade8964b3e1223666983aa5e89910 (PR #14 merged earlier today, so head-vs-base and head-vs-main are the same diff)
Commits: 130c521 spec, 1a00573 test, 99dde20 docs. First review of this PR — finding IDs TS18-nn (code and test lane) and TS18-Rnn (specification, records and gap lane).

This was a read-only review. No builds, no make, no cargo, no test runs, no mutation probes, no hosted-CI dispatch. Enumerations below are reads of the Git index at this exact head, not executions of the project. Where an earlier round established something by execution it is labelled prior evidence.


❌ BLOCKED — one high

TS18-01: the census reaches 42 of 107 tracked paths, while tests/shared_assurance.rs:557-559 and the FR-006-AC-6 text this PR rewrote both assert it covers everything live. 25 of the 65 exclusions are unargued and live, including the whole corpus/ gate tree and every gate-configuration file. Detail in the code lane below.

Everything else is clearing work. 20 findings: 1 high, 8 medium, 11 low. The four residuals other than TS14R3-02 are closed, and the central mechanism — the thing this PR exists to change — is correct.

What I verified myself before accepting either lane

I reimplemented source_in_scope (tests/shared_assurance.rs:364-385) independently against the Git index at this head and recomputed the census from scratch. Every number in the code is exactly right:

Control Code Independently measured
Population (:688-693) assert_eq!(tracked.len(), 42) 42 of 107 tracked
expected_areas (:670-681) <root> 7, .github 1, assurance 3, examples 1, scripts 7, spec 18, src 3, tests 2 identical, all eight

This is the first head in this family where I have not had to correct a census figure. Across four rounds and three repositories the stated population was wrong in tl-rewrite twice, in tl-mltl once and in tl-syntax once; here the total, the per-area breakdown and the exclusion set all reconcile against the index.

Two structural points deserve credit, both confirmed by that recomputation:

  • The population no longer moves when reviews land. :368-372 filters spec/reviews/ and spec/plans/, so this PR's own three SpecReviews and its eight-file PLAN-004 bundle do not touch the count. That is the self-invalidating-population defect — the one that ran through four rounds and needed a re-derivation every time — closed at the root rather than re-typed.
  • Per-area counts, not names. expected_areas is a BTreeMap<String, usize>, so a compensating cross-area swap is rejected. The siblings tl-rewrite and tl-mltl still compare area names only and would pass that case.

I also confirmed the two claims the lanes turn on. corpus/ is genuinely a gate tree: Makefile:123-124 runs sha256sum --check corpus/SHA256SUMS and scripts/validate_corpus.py, and check-corpus is a ci prerequisite at Makefile:229. And NFR-002 is genuinely digest-bound, at assurance/change-assurance.json:16 and :46, which is what makes the self-contradiction in the records lane a sealed one.


Code and test lane

Scope: tests/shared_assurance.rs in full at 99dde2023fdcf838a93b89528e11c5b72314eef6, read against the tracked tree. All references below are file:line at that head. Nothing was built, run, or dispatched; the only executions were read-only git ls-files / git log reads of the index, plus two git ls-files probes outside the repository used to establish the ceiling behaviour reported under TS18-02.

Findings

ID Severity Summary Refs
TS18-01 high The census drops 65 of the 107 tracked paths and asserts nothing about the complement; 25 of the dropped paths are live, including the whole corpus/ gate tree, while the comment claims everything live is in scope tests/shared_assurance.rs:345-385, :550-559, Makefile:121-125, Makefile:229, spec/requirements/FR-006-shared-assurance-intake.md:71
TS18-02 medium GIT_CEILING_DIRECTORIES has no falsifying control — deleting it leaves both controls green; on the real census it is a no-op, and a latent fail-closed hazard if the crate root is ever not the repository top level tests/shared_assurance.rs:325-329, :611, :620-642
TS18-03 medium The scanned set's read site is unconstrained: nothing fails if the deleted-reference scan iterates tracked instead, and no retained number reports the tracked/scanned delta tests/shared_assurance.rs:387-400, :650, :688-694, :696-708
TS18-04 medium Extension/name allowlist slips: extensionless and .yaml paths are invisible, and a root GNUmakefile — which GNU Make prefers over Makefile — is outside REQUIRED_ROOT_FILES, bypassing the TC-026 Makefile-name pin without moving any count tests/shared_assurance.rs:354-362, :379-384, :709-725
TS18-05 low The population assertion is unreachable as a tree check — it can fire only when the two literals disagree — and its message then misattributes the cause to a tree change tests/shared_assurance.rs:670-694
TS18-06 low The required-root-file control is a single assert! over a conjunction whose message cannot distinguish "missing from disk" from "present but untracked" tests/shared_assurance.rs:657-661
TS18-07 low The non-repository control pins "git exited non-zero", not "this is not a repository"; any git failure satisfies it tests/shared_assurance.rs:331-336, :639-642
TS18-08 low Control directories leak on the failure path (the two clear calls are downstream of four asserts), and the fixture path is not process-scoped tests/shared_assurance.rs:572-578, :612-618, :630-644
TS18-09 low Within-area swaps are undetected by design and this bound is not disclosed next to the area map tests/shared_assurance.rs:664-687

TS18-01 (high). source_in_scope (:364-385) admits a path only if it is one of seven named root files or sits under one of seven named directories and carries one of eight extensions. git ls-files at this head returns 107 paths; 42 survive. Of the 65 dropped, 40 are the deliberate spec/reviews/ + spec/plans/ records and one is this test file — all three exclusions are argued at :555-559. The remaining 24 are not argued anywhere: corpus/ (14 tracked files, including corpus/manifest.json, corpus/SHA256SUMS and corpus/schema/*.schema.json), .agent/rules/writing_rust.md, .github/CODEOWNERS (extensionless, so .github counts 1 of its 2 tracked files), and eight root files — Cargo.lock, clippy.toml, deny.toml, rustfmt.toml, rust-toolchain.toml, .gitignore, LICENSE-APACHE, LICENSE-MIT. corpus/ is not inert: Makefile:121-125 runs sha256sum --check corpus/SHA256SUMS, scripts/validate_corpus.py and scripts/test_corpus_gate.py under check-corpus, which Makefile:229 puts in ci. So the comment at :557-559 — "Everything that is live -- code, gates, workflows, requirements, the matrix, the suite registry, the assurance argument -- is in scope" — is false as written, and FR-006-AC-6 (spec/requirements/FR-006-shared-assurance-intake.md:71) states the same coverage claim as the acceptance criterion TC-026 discharges. I grepped all 107 tracked paths for the five FORBIDDEN names and the only hits are the excluded records and this test itself, so there is no live escape at this head, and the pre-PR fs::read_dir walk had the same reach — this is a claim-and-completeness defect, not a regression. What makes it worth a high in this program is that the fix is now nearly free and was not taken: the helper already holds the full 107-path list, so the census can assert a partition — every tracked path falls into exactly one declared class (in-scope, spec/reviews/, spec/plans/, this file, or an explicitly enumerated out-of-scope class with its own count) — which is what would make a new top-level directory, a new root file, or a new extension fail closed instead of silently changing nothing. What would make TS18-01 fail today: nothing. Add tools/legacy_evidence_view.py, or a corpus/schema/tl-syntax-evidence-input-v1.schema.json, and the population stays 42, every area count is unchanged, the forbidden-reference scan never opens the file, and TC-026 reports pass.

TS18-02 (medium). git_files sets GIT_CEILING_DIRECTORIES to root.parent() on every call (:325-329) with a comment claiming it stops a negative-control directory inheriting a repository from an ancestor. Nothing verifies that property. Trace both call sites. The fixture (:611) has its own .git created at :599-604, so discovery succeeds at depth zero and the ceiling is never consulted. The non-repository control (:620-642) sits directly under std::env::temp_dir(), and neither /tmp nor / is a repository — I confirmed read-only that git ls-files in /tmp exits 128 with "not a git repository" both with and without GIT_CEILING_DIRECTORIES set. Deleting lines :325-329 outright therefore leaves every assertion in this file green. Applying technique 1: the env var has a write site and no read site that any test constrains, which makes it decorative and its comment an unverified claim. A control that would exercise it must place the non-repository directory inside a repository — e.g. root.join("target/source-census-non-repository"), where without the ceiling git would ascend, find the real .git, exit 0, and turn expect_err at :633 red. Separately, on the real census the ceiling is also a no-op — I confirmed git ls-files returns 107 at the worktree root with the ceiling set to its parent, and note this worktree is a linked worktree whose .git is a file, which the ceiling does not disturb because it only blocks ascent, not discovery at cwd. The latent hazard is directional and fail-closed: if CARGO_MANIFEST_DIR were ever below the repository top level (crate vendored into a workspace subdirectory), the ceiling would turn the real census into a refusal rather than a wrong answer. That is the right direction, but it means the helper cannot be reused for a subdirectory root and nothing says so.

TS18-03 (medium). source_sets (:387-400) returns (tracked, scanned). tracked feeds the root-file control (:659), the area map (:665) and the population (:689). scanned has exactly one read site — the forbidden-reference loop at :696-708 — and no assertion anywhere constrains it on the live tree. Apply technique 4 to that read site: change :696 from &scanned to &tracked and every assertion in the file still passes, because the fixture control at :611-618 pins the output of source_sets, not the loop that consumes it. The untracked half of the two-set design is thus unenforced at its point of use, which is the half the PR summary sells ("keep nonignored untracked paths as a separate scan"). The second half of the finding is a records point under the review lens: the retained numbers are 42 and the eight area counts, all derived from tracked only. If two untracked in-scope sources are present when the suite runs, 44 files are opened and grepped while the record says 42, and nothing reports the difference. A scanned.len() - tracked.len() line in the failure/diagnostic path, or an assertion that names the delta, would make the set that was actually examined recoverable from the record.

TS18-04 (medium). The scope predicate accepts a root-level path only by exact name (:365-366, :354-362) and any other path only via the eight-extension matches! at :381-384. Construct the strings that slip through: .github/workflows/ci.yaml (the allowlist has yml, not yaml) — a second workflow added under the .yaml spelling changes no count and is never grepped; .github/CODEOWNERS and any extensionless script (scripts/bootstrap, scripts/run.bash, scripts/deploy.ps1) likewise. The sharpest instance is the Make name. GNU Make §3.2 searches GNUmakefile, then makefile, then Makefile, so a GNUmakefile at the root takes precedence over Makefile. source_in_scope("GNUmakefile") returns false — it is not in REQUIRED_ROOT_FILES, and its first path component is the filename itself, which is not in SOURCE_DIRECTORIES — so it would be tracked, invisible to the population, invisible to the area map, and never read by the forbidden scan. Meanwhile the TC-026 name pin at :711-725 reads root.join("Makefile") only. So the assertions that the Makefile no longer carries check-failure-propagation, check-tool-identities, compat-view and COMPAT_RESULT can be satisfied while a GNUmakefile that GNU Make would actually use carries all of them. I am not re-raising the Make execution-control class here — tl-syntax has 0 of 18 guards, that is tracked on tl-syntax#11, and I confirmed the absence pin at :712-725 is unchanged by this diff and still disclosed. This is a distinct point about the name the census and the pin resolve, and it is newly cheap to close because Git now supplies the root listing.

The author's three scrutiny requests

1. "whether the Git ceiling makes both the scratch-repository positive control and true non-repository refusal falsifiable" — No; the ceiling makes neither falsifiable, though both controls are falsifiable with respect to the refusal itself. See TS18-02 for the mechanism and the two read-only probes. To separate the two claims cleanly: the refusal is properly pinned — replace the assert!(output.status.success(), …) at :331-336 with an early return Vec::new() and the negative control turns red at :633; keep the assert but drop the phrase "source census cannot enumerate" and it turns red at :639. The ceiling is not pinned at all — remove :325-329 and nothing changes colour. The scratch fixture never consults the ceiling because it owns a .git at depth zero. Two further notes on refusal fidelity. When the git binary is absent, command.output() returns Err and .expect("git ls-files failed") at :330 panics: in the real census that is a red test (correct — a refusal, not a pass), and in the negative control the panic string does not contain "source census cannot enumerate", so :639 fires and the control is also red. Both directions fail closed; good. The non-UTF-8 path at :337-338 is likewise a panic, and -z is used on both invocations so git emits raw bytes rather than quoted paths, making that check meaningful rather than dead. catch_unwind is sound here (&PathBuf is RefUnwindSafe) and suppresses nothing beyond the one call; under cargo test the default hook's stderr is captured, so a passing run leaves no misleading panic text in the record.

2. "whether ignored generated files are excluded without hiding ordinary untracked sources" — Yes. This is the strongest part of the change and the companion control the residual asked for is present. The fixture at :572-618 is a real scratch repository: git init --quiet at :599-604 with its success asserted, git add .gitignore src/tracked.rs at :605-610 likewise, and it then calls the production source_sets(&fixture) at :611 rather than asserting on a stand-in helper. The falsifiability test that matters comes out clean: source_in_scope("tests/proptest-regressions/integration.txt") returns true — first component tests is in SOURCE_DIRECTORIES, extension txt is in the allowlist — so the file is kept out of scanned only by --exclude-standard at :393. Remove that flag and :613-618 goes red. Remove --others and tests/untracked.rs vanishes from fixture_scanned and :613-618 goes red the other way. The companion control proving an ordinary untracked non-ignored source is picked up is tests/untracked.rs (:589-593, expected at :615), so the exclusion cannot silently hide real untracked sources at the source_sets boundary — with the caveat that it can still be hidden at the consumption boundary (TS18-03). Two residual caveats: --exclude-standard also honours .git/info/exclude and the developer's core.excludesFile, both machine-local and unpinned, so an unlucky global ignore rule can shrink scanned on one machine and not another with no trace in the record; and the control proves the observable behaviour rather than the specific flag, so a paired change (adding a proptest-regressions filter to source_in_scope and dropping --exclude-standard) would slip past — that is acceptable, single-change falsifiability is the right bar. I also confirmed the fixture cannot perturb the live census two ways over: .gitignore:1 ignores /target, and even if it did not, target/… fails SOURCE_DIRECTORIES. Placing it there was the right call.

3. "whether the per-area counts reject compensating cross-area swaps" — Yes for cross-area; no for within-area, and no for anything involving the 65 out-of-scope tracked paths. observed_areas (:664-669) is built from tracked and compared to the literal at :683-687. A file moved out of tests/ and into spec/ yields {spec: 19, tests: 1} against {spec: 18, tests: 2} and fires, while tracked.len() stays 42 — exactly the case the old total-only equality missed, and the residual is genuinely closed for that class. Two bounds should be stated in the code next to the map. Within an area it remains a pure cardinality check: delete spec/requirements/FR-001-*.md and add spec/requirements/FR-099-*.md and every number is unchanged (TS18-09). And any swap that moves a file between the in-scope set and the 24 unargued out-of-scope live paths is not compensating at all — it is a plain one-sided change that the map still cannot see in one direction (TS18-01). On the redundancy question you asked about: assert_eq!(observed_areas, expected_areas) at :683 already implies tracked.len() == 7+1+3+1+7+18+3+2 == 42, so the population assertion at :688-694 is unreachable as a check on the tree — after :683 passes it can only fire if the literal map and the literal 42 disagree with each other. That is not worthless: it is precisely what stops a maintainer silencing a real change by editing one constant, since bumping ("spec", 18) to 19 leaves :688 firing until 42 is also changed. So the answer to "can editing one constant alone silence a real change" is no — but the two-constant lock is a property of the pair of literals, not of Git, and I checked: tl-syntax makes no equivalent of the tl-rewrite comment claiming "neither can be silenced by editing one list, because the other side comes from Git", so there is no false claim to correct here (TS18-05 is only the diagnostic).

TS14R3-04, self-locating failure

Partly closed. The area assertion at :683-687 is an assert_eq! over two BTreeMaps, so the default left/right rendering prints all eight counts and a maintainer can read the changed area straight off the failure — no recount is required, which is the substance of the residual. It locates to the area, not the file: spec moving 18 → 19 still leaves eighteen candidates. The helper already holds tracked, so naming the symmetric difference against a retained path list would locate it exactly; that is an improvement, not a defect. On the specific ordering question: :691-692 reads "expected and observed areas: {expected_areas:?} / {observed_areas:?}" — the interpolation order matches the order named in the sentence. No finding there. The defect that remains is TS18-05: under the only condition that message can fire, the tree has not changed at all, so "the tracked source-census population changed from 42 to 43" actively misdirects; both maps are printed alongside, so a careful maintainer recovers, which is why it is low rather than medium.

What holds

  • The self-invalidating population bound is structurally closed, and cannot rot the same way. The population is now trackedgit ls-files -z filtered by source_in_scope (:388-391) — so no file that is not deliberately git add-ed can move it. The original defect required a build artifact appearing in the walked tree; that path no longer exists, and crucially the closure does not depend on .gitignore:3 still containing proptest-regressions/: even if that line were deleted, a proptest seed would enter scanned and be grepped but never enter tracked, so 42 holds. I traced this rather than taking the PR summary's word for it. The remaining rot vector is a deliberate tracked addition, which is the review gate the constant exists to be.
  • The self-invalidating review record problem is closed too. spec/reviews/ and spec/plans/ are excluded by path prefix at :369-370 using starts_with on the relative string, so this PR's own three new SpecReviews and the eight-file PLAN-004 bundle do not move the population — the four-round defect that ran through three sibling repos. The prefix form is exact (spec/reviewsX/f.md does not match), so the exclusion does not over-reach by accident.
  • Fail-closed on every enumeration failure. :331-336 on non-zero git exit, :330 on a missing or unspawnable git, :337-338 on non-UTF-8 paths, :651-655 on a declared source directory that is absent, :698-700 on an unreadable selected source. Under the review lens, there is no path in this helper that returns a clean empty set when it was unable to enumerate — which is what the old collect_sources risked and what the change was for.
  • The two mandatory controls are real controls, not helper tests. Both call source_sets/git_files — the same code the live census calls — against a real scratch repository (:611) and a real non-repository (:632), and both are falsifiable against single-change edits to the production helper, as set out under scrutiny request 1 and 2. That is meaningfully better than the fixture-asserts-a-pure-helper pattern this program keeps finding.
  • source_in_scope tightened the self-exclusion. The old code skipped any file whose basename was shared_assurance.rs anywhere in the tree; :368 now excludes exactly the path tests/shared_assurance.rs, so a second copy elsewhere would be censused. Small, and in the right direction.
  • Numbers verified. I independently re-derived the tracked layout from the index at this head: 107 tracked paths; per top-level area .agent 1, .github 2, root 15, assurance 3, corpus 14, examples 1, scripts 7, spec 58, src 3, tests 3 — summing to 107 and consistent with the in-scope 42 and the eight expected area counts at :670-681. I also grepped all 107 tracked files for the five FORBIDDEN names: the only hits are tests/shared_assurance.rs and six files under the deliberately excluded spec/reviews/ and spec/plans/. No live reference to the dropped tree survives at this head.
  • TC-026 unchanged and still disclosed. The trace comment at :511 is untouched (TC-026, FR-006-AC-6), and the Makefile absence pin at :712-725 is byte-identical to base — the Make execution-control gap remains 0 of 18 and remains tracked on tl-syntax#11. I am not re-raising it; TS18-04 is about the file name that pin resolves, not the pin's content.

Specification, records and gap lane

Reviewed read-only at head 99dde202, base a6d58aa4, over everything in the diff except
tests/shared_assurance.rs (read for prose-vs-code checks only). Nothing was built, run or
dispatched. Where a claim could only be settled by executing a gate, it is reported as
unverifiable rather than accepted.

Residual adjudication

Residual Verdict (records half) Evidence
TS14R3-01 derive population from tracked Git paths FIXED (records half; code half = code lane) spec/requirements/FR-006-shared-assurance-intake.md:71, spec/test-matrix.md:64, spec/plans/PLAN-004-tracked-source-census/plan.md:45-50 and assurance/change-assurance.json:109-111 all describe the Git-derived mechanism. The stale recursive-walk description survives only in closed reviews spec/reviews/SR-009-…:108, SR-010-…:82, SR-011-…:90,148, which is the correct outcome under the repository's own no-rewriting-closed-records policy stated at spec/reviews/SR-011-drop-legacy-evidence-code-review.md:92. The premise of the fix is real: proptest is a dev-dependency (Cargo.toml:25), proptest-regressions/ is ignored (.gitignore:3), and a tests/proptest-regressions/*.txt seed would have been in scope under the base filter.
TS14R3-02 name only live FR-006 criteria; separate the two subjects PARTIAL Both named halves are genuinely fixed: NFR-002-…:87-88 enumerates exactly AC-1, AC-2, AC-3, AC-5, AC-6, which is exactly the live set in FR-006-…:67-71 with AC-4 retired at FR-006-…:75; and NFR-002-…:89-93 separates the deleted per-record-validator subject from the still-unowned Make execution-control clause. What is not fixed is NFR-002-…:102-103, which still reads "Its retired evidence obligations are carried by [FR-006]" — the blanket succession claim that lines 88-89 now explicitly deny, in the same digest-bound file. See TS18-R02.
TS14R3-03 fail closed per named root; decide on per-area cardinality FIXED (records half; code half = code lane) The decision is recorded — plan.md:60 exit criterion 3, tasks/Task-003-records.md:22-27, SR-017-…:30,43 — and it matches the code: tests/shared_assurance.rs:657-662 requires each of the seven REQUIRED_ROOT_FILES to be both present and tracked, and :664-686 asserts a BTreeMap<String, usize> of per-area counts. I re-derived the population independently from git ls-files at head with the same filter and got exactly 42 with the same eight-entry area map (<root> 7, .github 1, assurance 3, examples 1, scripts 7, spec 18, src 3, tests 2). The residual same-area substitution is honestly declared and accepted at SR-018-…:41,47.
TS14R3-04 self-locating census failure FIXED (records half; code half = code lane) Claimed at SR-017-…:32,43 and tasks/Task-003-records.md:27; true of the code — the directory, root-file and read failures each name the path (tests/shared_assurance.rs:659,661,691-693), and the area assert prints both maps through assert_eq! (:684-686), with the total assert naming them explicitly (:689-694).
TS14R3-05 label vs. Quoin-read predicate FIXED spec/reviews/SR-013-…:93-96 is exactly true of the code. scripts/assurance_chain.py:747 shows scenario(name, state, matched, detail) where the second argument is a caller-supplied literal; :1010-1013 passes the literal "inconclusive" while the predicate reads audited["unknowns"] and audited["reasons"], and audited is the parsed stdout of quoin change-assurance receipt --json (:893, via :537-566). I also confirmed the corollary the sentence relies on: disposition (assurance/change-assurance.json, three unknowns entries) has no read site in scripts/, tests/ or the Makefile.

Findings

ID Severity Summary Refs
TS18-R01 medium No retained record evidences a gate run at the merge head; the record says head verification "remains" while the PR note asserts it passed spec/plans/PLAN-004-tracked-source-census/log.md:21-26, .../tasks/Task-004-verification.md:5, PR #18 body
TS18-R02 medium NFR-002 Dependencies still asserts blanket succession by FR-006, contradicting the correction made three sections above it, inside a digest-bound file; three of the five retired AC-4 clauses still get no named successor and no unowned-tracking spec/requirements/NFR-002-determinism-and-integrity.md:75-78,87-93,102-103
TS18-R03 medium FR-006-AC-6 now specifies its own test's implementation mechanism and bundles six distinct obligations into one criterion discharged by one test case spec/requirements/FR-006-shared-assurance-intake.md:71, spec/test-matrix.md:21,64
TS18-R04 medium The new PRESERVE-git-source-census constraint is sealed, but the control it asserts produces no attestation, belongs to no declared evidence suite, and nothing in the chain observes whether TC-026 ran assurance/change-assurance.json:109-112,114, spec/evidence/suites.md:13-18, scripts/assurance_chain.py:418-420
TS18-R05 medium SR-017 renumbers the independent reviewer's residuals as its own findings and marks them FIXED in the same commit that made the edits, with no attribution and no TS14R3→FND traceability spec/reviews/SR-017-…:29-35,40-45 (commit 130c521)
TS18-R06 low SR-017 carries no reviewed-revision marker; SR-018/SR-019 correctly mark 1a00573, which is not the merge head — no review covers 99dde202 SR-017-…:6, SR-018-…:6, SR-019-…:6
TS18-R07 low SR-013 (which carries this PR's corrected TS14R3-05 statement) and AA-001 (which carries the release owner's acceptance) are still in no sources entry and no proof_obligations[].configuration — repeat of a prior-round finding, untouched here assurance/change-assurance.json:13-22, spec/assurance/AA-001.md:112-118
TS18-R08 low Git is now the authority for the reviewed population but is declared as a verification-path tool nowhere; TC-026 becomes unrunnable outside a Git checkout and no record says so tests/shared_assurance.rs:322-343, assurance/pins.json, spec/evidence/suites.md:13-18, SR-017-…:62-65
TS18-R09 low The declaration's definition.requirements[].statement text differs from every corresponding spec AC row and nothing compares them; this PR edited AC-6 in both places and left them divergent assurance/change-assurance.json:83, spec/requirements/FR-006-shared-assurance-intake.md:71
TS18-R10 low FR-006-AC-6's "no live tracked or non-ignored untracked source … or specification row" is wider than the scan, which excludes 31 of 57 spec documents plus corpus/ and every extensionless/unlisted-extension tracked file spec/requirements/FR-006-shared-assurance-intake.md:71, tests/shared_assurance.rs:345-383
TS18-R11 low On merged main the review series jumps SR-013 → SR-017 and the plan series 002 → 004 with no note; the reason lives only on an unmerged branch spec/reviews/, spec/plans/, origin/issue/15-typed-signal-context

TS18-R01 (medium). spec/plans/PLAN-004-tracked-source-census/log.md:21-26 records the full
local gate at "exact implementation candidate 1a00573" and closes with "Final
documentation-head verification and independent review remain".
tasks/Task-004-verification.md:5 is status: in_progress. The PR body asserts that
make ci CARGO_TARGET_DIR=target/cargo-review passed at 99dde2023fdcf838a93b89528e11c5b72314eef6
and that 57/57 specification documents were grammar-clean. The two disagree about the same head.
The 57 is at least arithmetically consistent with the tree — I counted 57 tracked spec/**/*.md
at 99dde202 versus 55 at 1a00573 and 47 at a6d58aa, so SR-018's "55/55 … at exact committed
candidate 1a00573" (SR-018-…:55) is honest and the PR's 57 is the right head number. But I
cannot verify either run, and the retained record does not carry the head one. Under this
program's own rule that the record is the artifact, the head gate is currently unevidenced, and
the last thing the record says about it is that it has not happened. The structural cause is that
log.md is written in the final commit and so can never self-record its own head; the siblings
have the same shape (/home/peter/dev/tl-rewrite/spec/plans/PLAN-002-…/log.md,
/home/peter/dev/tl-mltl/spec/plans/PLAN-002-…/log.md — both also status: in_progress), so this
is a program-wide pattern rather than a lapse by this author. It still means the merge decision
rests on a PR-body assertion with no retained counterpart. The cheapest honest fix is a log entry
naming the head and the gate result, committed as the post-review head, or an explicit statement
in log.md that the head gate result lives in the PR record and not in the bundle.

TS18-R02 (medium). NFR-002-…:75-78 lists the five clauses retired with NFR-002-AC-4:
source-locked launcher and toolchain identities, a compiled test census, rejecting in-file Make
execution controls, behaviourally verifying per-record evidence validators, and requiring an
active qualified record. The new text at :87-93 correctly names the five live FR-006 criteria,
correctly denies blanket succession, and correctly disposes of two of the five clauses — the
validator clause (retired with its subject and FR-006-AC-4) and the Make execution-control
clause (unowned, tracked as challenge-make-execution-control and #11). Both halves of the
reviewer's TS14R3-02 are therefore met, and I verified the enumeration is arithmetically right:
FR-006-…:67-71 has exactly AC-1, AC-2, AC-3, AC-5 and AC-6 live, AC-4 retired at :75. The
defect is that NFR-002-…:102-103 was not touched and still says "Its retired evidence obligations
are carried by FR-006" — the unqualified blanket claim the
new paragraph exists to deny. A reader who reaches Dependencies first gets the old, false answer.
NFR-002 is bound by digest through assurance/change-assurance.json:13-22 (sources.NFR-002
source_connections, digested at scripts/assurance_chain.py:424-430), so the contradiction is
inside the sealed set: it is not merely wrong, it is wrong under seal. Separately, the remaining
three AC-4 clauses — launcher/toolchain identities, compiled test census, active qualified record —
get neither a named successor nor an explicit "unowned and tracked as X". They may well be fully
subsumed by FR-006-AC-1 and the shared contract, but the new paragraph's whole point is that
succession must be stated per clause rather than assumed, and it stops after two of five.

TS18-R03 (medium). FR-006-AC-6 at spec/requirements/FR-006-shared-assurance-intake.md:71 now
reads, in its second sentence, "The census population comes from Git-tracked paths, requires every
declared root, and binds the exact total and per-area populations…". That is a description of how
tests/shared_assurance.rs is built, promoted into the acceptance criterion. Two consequences.
First, the criterion can no longer be satisfied by any other correct mechanism, and any structural
change to the test falsifies the requirement rather than merely changing its evidence — the
requirement has become a mirror of the test it is supposed to constrain. Second, AC-6 now carries
six separable obligations (no local generic machinery in the execution path; no live source naming
the deleted machinery; Git-derived population; required roots; exact total; per-area cardinality),
all traced to the single row TC-026 (spec/test-matrix.md:21,64). A failure of any one is
indistinguishable at the criterion level, and a criterion that can fail six ways cannot report
which. The matrix row itself I checked and it is not wider than the criterion — spec/test-matrix.md:64
maps cleanly onto AC-6's clauses, and its changed portion ("scan the Git-derived live source set;
and bind required roots plus exact total and per-area populations") is accurate to
tests/shared_assurance.rs:601-694. The scope problem is in the criterion, not the row.

TS18-R04 (medium). The new PRESERVE-git-source-census entry at
assurance/change-assurance.json:109-112 sits in record.definition.preservation_constraints, so
it is inside the sealed body: scripts/assurance_chain.py:418-420 projects
self.declaration["record"] wholesale, and the new key is under record, not a top-level sibling.
That is the right side of the boundary and I want to give credit for it — the known
record_body() trap was avoided. The problem is what happens next. Nothing reads
preservation_constraints anywhere in scripts/, tests/ or the Makefile (I grepped for both
preservation_constraints and source_ids; neither appears outside the JSON). The only local
verification of the property it asserts is TC-026, and TC-026's outcome enters the sealed chain
nowhere: the five proof obligations are PROOF-corpus-conformance, PROOF-corpus-oracle,
PROOF-feature-boundary, PROOF-quire-static-export and PROOF-msrv, none of which is the
cargo test harness, and spec/evidence/suites.md:13-18 registers six suites, none of which is
that harness either. So the sealed record now carries a positive statement that the census fails
closed, with no attestation that the census ran, no suite naming the tool that runs it, and no
digest binding it to the test. Delete TC-026 tomorrow and the sealed statement stays true-looking
and unfalsifiable — frozen testimony in the precise sense. Note that this is a pre-existing hole
that this PR widens: it is the first preservation constraint to make a claim about a control that
lives only in the untracked-by-the-chain test binary.

TS18-R05 (medium). SR-017 restates the independent reviewer's five residuals as
FND-1701FND-1706 (SR-017-…:29-35) and dispositions all six (:40-45). Four are marked
FIXED IN SPEC, which is a defensible scoping for a spec review authored before the code existed.
Two are marked plain FIXED: FND-1705 (which is TS14R3-02) and FND-1706 (which is TS14R3-05).
Both were authored in commit 130c521, the same commit that made the NFR-002 and SR-013 edits
they clear. That is the author granting closure on an external reviewer's findings in the act of
writing them. The substance happens to be right in both cases — I verified both against the code
above — but the disposition is a self-grant, not a clearance, and the document does not say so.
Compounding it, nothing anywhere in the tree maps TS14R3-01TS14R3-05 to FND-1701FND-1706.
Issue #17 lists the reviewer's IDs; SR-017 uses new local IDs; SR-019's scope says only
"retained PR #14 findings" (SR-019-…:6). A later reader cannot establish that the six FND rows
are the reviewer's five residuals, cannot see that they originated outside the author, and cannot
detect if one had been quietly dropped in the renumbering. In fairness, the renumbering did not
drop anything — I checked all five residuals map onto the six FND rows with TS14R3-03 splitting
into FND-1702 and FND-1703 — and both closing reviews correctly refuse to grant merge authority
(SR-018-…:63 "External reviewer clearance remains required before merge"; SR-019-…:59-61).
That refusal is a real improvement over the tl-rewrite precedent and should be credited.

What holds

  • The Git-derived census records are accurate, and I re-derived the numbers. Applying
    source_in_scope (tests/shared_assurance.rs:363-383) to git ls-files at head yields exactly
    42 files and exactly the eight-entry area map asserted at :670-682. Every figure SR-018 and
    SR-019 state about the tree is true at this head: seven source directories (:345), seven
    required root files (:354), five forbidden names (:560), the 42 total, the eight areas, and —
    the one I most expected to be decorative — GIT_CEILING_DIRECTORIES really is set
    (:326-328), so the non-repository negative control genuinely cannot inherit an ancestor
    repository. SR-018-…:55's "55/55 documents" matches the 55 tracked spec/**/*.md at 1a00573.
  • TS14R3-05 is correct at the level of the read site. SR-013-…:93-96 distinguishes a
    caller-supplied literal from a Quoin-computed predicate, and both halves check out in
    scripts/assurance_chain.py:747 and :1010-1013. The supporting negative — that disposition
    has no local read site — also holds.
  • The declaration edit landed inside the seal. PRESERVE-git-source-census is under
    record.definition, not a top-level sibling, so it is covered by the record digest. The known
    record_body() boundary defect was not repeated.
  • The plan bundle is honest about its own state. plan.md:5 is status: in_progress and
    tasks/Task-004-verification.md:5 is status: in_progress while Tasks 001–003 are done
    matching the sibling practice and correctly refusing to declare completion before independent
    review. The four task files decompose real work and each traces to PLAN-004 and (for 001/002)
    FR-006.
  • ID allocation was done properly across branches. SR-014, SR-015, SR-016 and PLAN-003
    are all reserved on the unmerged origin/issue/15-typed-signal-context; this PR correctly took
    SR-017SR-019 and PLAN-004 rather than colliding. SR-019-…:53-56 also anticipates the
    merge-order consequence — that the next feature branch must update the exact population from Git
    and must not restore the walk — which is exactly the right thing for a hard-coded 42 to say.
  • No dangling identifiers. Sweeping spec/ for (FR|NFR|StR)-NNN-AC-N and TC-NNN against
    the live tables leaves only FR-006-AC-4, NFR-002-AC-4, TC-018 and TC-024 — all four
    explicitly documented as retired-and-not-reused (FR-006-…:75-88, NFR-002-…:75-96). No
    reference to SR-015/SR-016 exists in the tree.
  • The PR note's structural claim is true. "adds no local runner, evidence collector, Make
    parser, or duplicate evidence contract" is verifiable from the diff shape alone: the change
    touches only assurance/change-assurance.json, spec/, and one existing test file. No file was
    added under scripts/, no Makefile target, no .github/ workflow, no Cargo.toml change.

Claims I could not verify. "full local make ci passed at 99dde202", "6/6 shared-assurance
tests … passed", "all 4 mutation probes passed", SR-018-…:55-57's "53/59 rows were backed" and
"27/27 Rust trace symbols reconciled". These require executing gates, which is outside this lane.
Structurally they are consistent: tests/shared_assurance.rs contains exactly 6 #[test]
functions, and scripts/assurance_chain.py:1173-1175 describes exactly four adapter-refusal
mutation probes (the other four entries in adapter_probes are not mutation probes), so "4"
is the right denominator and does not contradict SR-011-…:141's "8 adapter probes".

Gap analysis

  • No NFR-003. All three siblings carry spec/requirements/NFR-003-qualification-integrity.md
    (/home/peter/dev/tl-rewrite, /home/peter/dev/tl-mltl, /home/peter/dev/tl-parse); tl-syntax
    has FR-001…FR-006, NFR-001, NFR-002, StR-001, StR-002 and no NFR-003. In the siblings that
    requirement is precisely the one that owns the shared-assurance intake path — the pins file,
    the change-assurance declaration, assurance_chain.py, check_shared_pins.py and the tests that
    exercise them — and it is where the Make execution-control reduction is stated as a scoped,
    owned deviation (/home/peter/dev/tl-rewrite/spec/requirements/NFR-003-qualification-integrity.md:11-45).
    This is the structural root of TS14R3-02: in tl-syntax that material is scattered between
    NFR-002's Scope prose and FR-006, which is why the Make clause ends up "unowned by an
    acceptance criterion" and why AC-6 has had to absorb six obligations. Adopting the sibling
    NFR-003 shape would give the Make clause and the census a home and would let AC-6 shrink back
    to one obligation. This is the single highest-value gap in the lane and it is out of scope for
    this PR — it should become an issue.
  • No evidence suite for the harness that discharges FR-006-AC-6. spec/evidence/suites.md:13-18
    names a tool and command for each of SUITE-001…SUITE-006; cargo test --test shared_assurance
    is not among them, so the six integration test cases TC-021…TC-026 have no registered suite,
    no declared tool version, and no declared command. suites.md is digest-bound (SUR-001), so
    this omission is sealed too.
  • Sealed subject scope disagrees with the census scope. assurance/change-assurance.json's
    record.subject.scope is ["src","corpus","examples","scripts","spec","tests","assurance", "CLAUDE.md","Makefile"]. The census covers .github, README.md, AGENTS.md,
    CONTRIBUTING.md, Cargo.toml and requirements-assurance.txt, which the subject scope omits;
    and the subject scope names corpus, which the census never reads. SR-018-…:41,47
    (FND-1802) and SR-019-…:36,44 (FND-1901) acknowledge the corpus/gate-config direction
    and defer it to #16; neither notices the other direction. Nine tracked live files sit outside
    both — .agent/rules/writing_rust.md, .github/CODEOWNERS, .gitignore, Cargo.lock,
    clippy.toml, deny.toml, rustfmt.toml, rust-toolchain.toml, LICENSE-* — of which
    rust-toolchain.toml is itself a proof-obligation configuration.
  • A control declared in the record with no owning requirement. PRESERVE-git-source-census
    cites source_ids: ["FR-006","NFR-002","TM-001"], but nothing local validates source_ids
    against sources or the requirement set — the string appears nowhere in scripts/. It is
    sealed and therefore not decorative, but it is unchecked locally, and a typo in a source id would
    be sealed exactly as faithfully as a correct one.
  • Acceptance-criteria clauses dropped in a rewrite. Three of NFR-002-AC-4's five clauses
    (launcher/toolchain identity, compiled test census, active qualified record) still have no named
    successor and no unowned-and-tracked statement — see TS18-R02.
  • No spec/usecase/ and no US-* artifacts. Known absent program-wide; noted once, not
    re-derived. spec/ holds only assurance, evidence, plans, requirements, reviews,
    spec.md, test-matrix.md.
  • Cross-repo structure otherwise consistent. Makefile, scripts/, assurance/,
    spec/reviews/ placement, corpus layout, workflow_dispatch-only CI and the absence of a local
    evidence/ tree all match the three siblings. tl-rewrite and tl-mltl already use the same
    git ls-files -z / --others --exclude-standard split in their own
    tests/shared_assurance.rs, so SR-018-…:20-22's claim to reuse the sibling pattern is true —
    and tl-syntax goes further than either, since both siblings assert only a BTreeSet of area
    names while this change asserts a BTreeMap of area counts. tl-parse has no such census.
    Worth noting for the program: the fix landed here is stronger than the two siblings that already
    had the mechanism, so tl-rewrite and tl-mltl now carry the compensating-swap gap this PR
    closed.
  • Correction to a premise. PLAN-004 is not the first plan in this repo with a tasks/
    directory. PLAN-001-v0.1 has seven task files and PLAN-002-shared-assurance-migration has
    three, both on main; PLAN-003-typed-signal-context on origin/issue/15-typed-signal-context
    has five. There is no task-decomposition gap here to report.

@kreneskyp

Copy link
Copy Markdown
Contributor Author

Reviewer: please re-run the full code-review and gap-analysis cycle at exact head 5861efce28c65d9fb72207f638317cba49878196.

SR-020 maps all 20 findings from the review at 99dde202. The one high and all eight medium findings are author-remediated; nine lows are also remediated, while TS18-R07/R09's generic authoritative-source/statement validation remains explicitly deferred to #16. The newly identified missing qualification-integrity owner is tracked as #19.

Key changes to scrutinize:

  • TC-034 now compares the exact 66-path non-archival tracked set, with no directory/name/extension allowlist; corpus, gate configuration, extensionless files, locks, licenses, CODEOWNERS, and .agent are included. Exact paths catch within-area substitutions and the independent ten-area map catches cross-area changes.
  • The untracked fixture now forces the production forbidden-reference consumer to reject its exact path/name; the live untracked delta must be empty and is printed. Scratch directories are PID-scoped and RAII-cleaned.
  • The non-repository control lives under the real repo so removing GIT_CEILING_DIRECTORIES makes it inherit the repo and fail; it also requires Git's specific not a git repository diagnosis.
  • GNUmakefile and makefile are explicitly refused as higher-precedence inputs.
  • FR-006 separates deleted-machinery absence (AC-6/TC-026) from implementation-independent source-partition integrity (AC-7/TC-034). NFR-002 now accounts for all five retired clauses and removes its blanket succession claim.
  • The unattested PRESERVE-git-source-census assertion is removed. SUITE-008 declares the local test command and Git premise while stating that Quoin does not attest this suite.
  • SR-017 now attributes every TS14R3 finding and reserves closure for an independent reviewer.

Full local make ci CARGO_TARGET_DIR=target/cargo-review passed at this exact head outside the documented Node child-process sandbox: 58/58 spec documents, all 25 matrix cases and 28/28 Rust symbols, 7/7 shared-assurance tests, MSRV/corpus/supply-chain/rustdoc, four mutation probes, and the complete Quoin chain. Hosted CI was not dispatched. No local runner, collector, Make parser, evidence envelope, identity registry, or retention layer was added.

@kreneskyp

Copy link
Copy Markdown
Contributor Author

Independent re-review — PR #18, "Derive the source census from tracked Git paths"

Head reviewed: 5861efce28c65d9fb72207f638317cba49878196
Previously reviewed head: 99dde2023fdcf838a93b89528e11c5b72314eef6
Base = merge base = origin/main: a6d58aa4df3ade8964b3e1223666983aa5e89910
Delta: 400bba5 "fix: close source census review findings", 5861efc "docs: adjudicate PR 18 review" — 13 files, tests/shared_assurance.rs +425/−238.

This was a read-only review. No builds, no make, no cargo, no test runs, no mutation probes, no hosted-CI dispatch. Enumerations below are reads of the Git index at this exact head. Prior-round execution results are carried forward labelled as prior evidence.


✅ MERGEABLE

The round-1 high is closed. All 20 findings from the review at 99dde202 are adjudicated below: 17 FIXED, 3 deferred onto open issues, 0 open. New findings this round: 0 high, 4 medium, 12 low. Nothing blocking survives.

What I verified myself before accepting either lane

The round-1 high, TS18-01, was that the census reached 42 of 107 tracked paths behind a directory-and-extension allowlist while FR-006-AC-6 — rewritten in that same PR — claimed it covered everything live. 25 exclusions were unargued and live, including the whole corpus/ gate tree and every gate-configuration file.

The allowlist is gone. is_archival_record (tests/shared_assurance.rs:422-427) now excludes exactly four things — this test file, spec/.gitkeep, spec/reviews/ and spec/plans/ — and everything else tracked is in scope. I extracted all 66 string literals of EXPECTED_LIVE_TRACKED (:345-412) and diffed them against git ls-files minus those four exclusions:

git ls-files 108
archival (spec/reviews 17 + spec/plans 23 + 2) 42
live tracked 66 — matching [&str; 66]
declared set vs. real set byte-identical, diff reports nothing in either direction

Every one of the 25 paths I named is now in scope: all 14 corpus/ files, deny.toml, clippy.toml, rustfmt.toml, rust-toolchain.toml, Cargo.lock, .gitignore, both licences, .github/CODEOWNERS, .agent/rules/writing_rust.md. And the scan genuinely runs over the Git-derived set rather than the literal, so the scope-outruns-scan defect is closed rather than restated.

I also confirmed the two records claims the lanes turn on. NFR-002:111-115 no longer says "Its retired evidence obligations are carried by FR-006"; it now says the surviving relationships and unclaimed clauses "are enumerated above; no blanket succession by FR-006 is implied" — an explicit denial replacing the contradiction, with all five retired AC-4 clauses disposed of individually at :86-104. And PRESERVE-git-source-census was removed rather than defended — zero grep hits tree-wide — with SUITE-008 stating in terms that Quoin does not attest that suite. Both are the honest outcome rather than a softened one.

The two things I would still fix

Neither blocks, and both are the same shape: a control that cannot fail, described as one that can.

  1. The area map has no tree input. :700-703 pins tracked to the 66-entry literal; :704-708 then builds observed_areas from tracked. So :725-728 is assert_eq!(f(literal_a), literal_b) — it can only catch the two literals being authored inconsistently, never a repository change. The message says "the live tracked area populations changed" and SR-020:63 calls the map "independent". Both are false.
  2. TC-026 passes identically whether it scanned 66 files or none. assert_no_forbidden_references (:451-465) is a bare for loop, vacuous on an empty set, and TC-026 (:739, traced to FR-006-AC-6) calls it at :782 with no assertion about the population it received — every census-integrity assertion now lives in TC-034. Pre-PR these were one test. As split, the test discharging AC-6 cannot distinguish "scanned everything and found nothing" from "scanned nothing", which is the review lens applied to this PR's own headline control.

Code and test lane

Scope: tests/shared_assurance.rs read in full at 5861efce28c65d9fb72207f638317cba49878196, plus git diff 99dde20..5861efc hand-read hunk by hunk for silently dropped assertions. All file:line references are at this head unless a 99dde20: prefix says otherwise. Nothing was built, run, or dispatched. The only executions were index reads (git ls-files, git log, git diff), a file(1) type census over the tracked paths, and one read-only git ls-files probe under GIT_CEILING_DIRECTORIES used to confirm the diagnostic string reported under TS18-07/TS18R2-06.

Round-1 code findings adjudication

ID Verdict Evidence
TS18-01 (high) FIXED The directory+extension allowlist is deleted; source_sets now filters only is_archival_record (tests/shared_assurance.rs:429-441, :422-427) and TC-034 pins the result to an exact 66-path literal (:345-412, :695-703). corpus/ (all 14), deny.toml, clippy.toml, rustfmt.toml, rust-toolchain.toml, Cargo.lock, .gitignore, both licences, .github/CODEOWNERS, .agent/rules/writing_rust.md are all in the declared set. The set is genuinely consumed — see "What holds". Residual: TS18R2-03, TS18R2-05.
TS18-02 (medium) FIXED The non-repository control moved from std::env::temp_dir() to root.join("target/source-census-non-repository-{pid}") (:673-675), i.e. inside the real repository, so GIT_CEILING_DIRECTORIES (:325-329) is now load-bearing. Trace with the env lines deleted: Git ascends from target/… to the repo root, finds .git, and git ls-files -z exits 0 listing nothing (cwd holds no index entries) → no panic → catch_unwind(...).expect_err(...) at :677-678 fires. Red, as required.
TS18-03 (medium) PARTIAL The helper consumer is now pinned: the untracked fixture carries a forbidden literal (:615-619) and assert_no_forbidden_references must panic naming both the path and the name (:655-668). The production call site is still unfalsifiable: :782 passes &scanned, and because :731-736 requires the untracked delta to be empty, scanned == tracked on every green run — substituting &tracked at :782 still breaks nothing. The "no retained number" half is addressed differently but soundly: the delta is pinned at zero and printed on failure (:732-736).
TS18-04 (medium) FIXED No name/extension allowlist survives (:429-441); .yaml, extensionless and lockfile paths are admitted by construction. GNUmakefile and makefile are refused as higher-precedence GNU Make inputs (:786-791). The TC-026 Makefile-content pin still reads root.join("Makefile") and is byte-identical to base (:792-806). Residual: TS18R2-06.
TS18-05 (low) PARTIAL — transposed The tracked.len() == 42 assertion and its misattributing message are gone (99dde20:688-694 deleted). The identical structure now sits in the area map: after :700-703 pins tracked to the literal, observed_areas (:705-710) is a pure function of that literal, so :726-729 can fire only when two literals disagree — and its message still says "the live tracked area populations changed". See TS18R2-02.
TS18-06 (low) PARTIAL The is_file() && tracked.contains(...) conjunction is gone; the exact-set failure at :700-703 names the offending path. It still does not distinguish "missing from disk" from "present but untracked": both produce the same assert_eq! failure, and the untracked evidence at :731-736 is unreachable because :700 panics first. The disk half is now covered only indirectly, by fs::read_to_string failing in TC-026 (:454-456), in a different test. SR-020's disposition for FND-2006 ("the set comparison distinguishes…") is true of which path, not of what happened to it.
TS18-07 (low) FIXED :684-688 now requires both "source census cannot enumerate" and Git's "not a git repository". Verified read-only that a ceiling-blocked ascent still emits fatal: not a git repository (or any of the parent directories): .git, exit 128 (git 2.34.1), so the added conjunct is satisfiable by the real mechanism. Caveat: TS18R2-06.
TS18-08 (low) FIXED ScratchDirectory (:467-490) with a Drop that removes the tree, both controls PID-scoped (:598, :604, :674), guards held in scope so unwind cleans them. Cargo.toml sets no panic = "abort", so unwind (and catch_unwind at :655/:677) is real.
TS18-09 (low) FIXED Exact path-set equality (:700-703) detects within-area substitution, and the bound is disclosed in the comment at :690-694.

New findings

ID Severity Summary Refs
TS18R2-01 medium TC-026 — the test traced to FR-006-AC-6 and the only place the forbidden-reference scan actually runs on the live tree — has no population floor of its own; its scan is vacuous over an empty or truncated set and it cannot distinguish "scanned 66 live sources" from "scanned nothing". Every census-integrity assertion moved to a different test. tests/shared_assurance.rs:451-465, :781-782, :695-736, 99dde20:688-708
TS18R2-02 medium The ten-area map is derived from the path set that was pinned exactly two statements earlier, so it is unreachable as a check on the tree and its failure message misattributes the cause. The author's claim that it is "independent" is false, and is recorded in the repository. tests/shared_assurance.rs:695-703, :705-729, spec/reviews/SR-020-pr18-review-response.md:63
TS18R2-03 low spec/reviews/ and spec/plans/ are unbounded prefix exclusions: any live file placed under them is removed from tracked and scanned, and nothing constrains what kind of file may live there. tests/shared_assurance.rs:422-427, :430-440
TS18R2-04 low The exclusion boundary carries no rationale at its definition, and the round-1 rationale for not scanning records was deleted rather than carried forward; the self-exclusion of this test file is nowhere argued. tests/shared_assurance.rs:422-427, :690-694, 99dde20:555-559
TS18R2-05 low The scan now reads every tracked path with fs::read_to_string, so the census silently constrains the repository to UTF-8, non-directory, readable tracked paths (a tracked binary, submodule gitlink or broken symlink panics). Fail-closed, but undisclosed and misdiagnosed. tests/shared_assurance.rs:451-465, :429-441
TS18R2-06 low The GNUmakefile/makefile guard fails spuriously on case-insensitive filesystems, has no positive control showing it can fire, and only one of its two conjuncts adds reach. Separately, the non-repository control pins an untranslated English Git string. tests/shared_assurance.rs:786-791, :684-688, .github/workflows/ci.yml:9,27,70
TS18R2-07 low Nothing asserts EXPECTED_LIVE_TRACKED holds 66 distinct entries, and the constant carries no note stating the maintenance protocol — the "update the expected map only after reviewing the named area delta" instruction was deleted with the old message and not replaced. tests/shared_assurance.rs:345-412, :696-703, :726-729, 99dde20:684-686

TS18R2-01 (medium). The forbidden-reference scan runs on the live tree in exactly one place, :782 inside TC-026. assert_no_forbidden_references (:451-465) is a for loop over the set it is handed: over an empty or truncated set it performs no assertion and returns normally — the empty-set vacuity pattern, and under the review lens a gate that returns pass when it examined nothing. Everything that constrains the size and content of that set — the 66-path equality (:700-703), the area map (:726-729), the untracked delta (:731-736) — now lives in TC-034, a different #[test] with a different trace tag. Before this PR they were one test (99dde20:688-708: the population assertion and the scan sat in the same function). So a change that shrinks source_sets — broadening is_archival_record, adding a pathspec to git ls-files, or a machine-local core.excludesFile narrowing the --others half — leaves TC-026 green while it scans less, and TC-026 is the test whose name and trace claim "nothing still reads the dropped tree" (:739-741). What would make it fail today: nothing; TC-034 is present and passing, so the suite as a whole is still fail-closed. What makes it a finding rather than a nit is the qualification consequence: TC-026's own verdict carries no evidence of the population it covered, and the two tests are coupled only by a comment (:778-780). One line inside TC-026 — assert_eq!(scanned.len(), EXPECTED_LIVE_TRACKED.len()), or reusing the set equality before the scan — restores the property the pre-PR code had.

TS18R2-02 (medium). At :700-703 the test asserts tracked == expected, where expected is built from the 66-entry literal. assert_eq! panics on mismatch, so :705 onwards is reachable only when tracked is the literal. observed_areas is then computed from tracked (:705-710) — a pure function of a constant — and compared against a second constant, expected_areas (:711-725). Their sum is 15+1+2+3+14+1+7+18+3+2 = 66, so the two literals agree today and the assertion at :726-729 has no tree input whatsoever: it is assert_eq!(f(literal_a), literal_b), statically true or statically false, and no repository change can alter its outcome. This is the round-1 TS18-05 structure with the operands swapped: previously the count was implied by the map, now the map is implied by the set. Its message — "the live tracked area populations changed; inspect the exact path-set delta above" — misattributes under the only condition it can fire (the tree has not changed, and the "delta above" was empty), and it points the maintainer at an assertion that passed. Cross-checking the prose (technique 5): SR-020 disposition FND-2009 (spec/reviews/SR-020-pr18-review-response.md:63) states "Exact path-set equality detects same-area substitutions; the independent area map still identifies cross-area changes", and the PR comment repeats "the independent ten-area map catches cross-area changes". The map is not independent — it is derived from the very set the preceding assertion pins, and it catches nothing the exact set has not already caught. The residual value is real but different: it is a two-literal lock that stops a maintainer silencing a genuine change by editing one constant. That is what the comment and the record should say. What would make the area assertion fail: only editing one of the two literals — never a change to the repository.

What holds

  • The enumeration is genuinely consumed, not merely compared. I grepped every read site of tracked, scanned and EXPECTED_LIVE_TRACKED (:695-696, :706, :731, :781-782, :788). The byte scan at :782 runs over scanned, which is produced by git ls-files (:430-440), not by the literal — so the scan's reach is the real tree, and the literal's job is to make "which 66 paths" recoverable. The set equality and the byte scan therefore run over the same collection modulo the archival filter; the round-1 defect of a scope claim outrunning the scan is closed. The newly admitted files are read with fs::read_to_string, which errors rather than skips on non-UTF-8 (TS18R2-05); all 108 tracked paths are text today, verified with file(1).
  • The untracked-consumption control is tight, and its wrong-reason guard is real. :655-668 requires the panic to contain both tests/untracked.rs and legacy_evidence_view, so a read failure naming the same path (which would contain only the first) cannot satisfy it. The fixture pins all three Git classes through the production helper (:640-654), and the ignored proptest-regressions seed is kept out only by --exclude-standard.
  • The ceiling is now load-bearing and the refusal is specifically diagnosed. Verified by trace (TS18-02 above) and by a read-only probe that a ceiling-blocked ascent still yields not a git repository at exit 128.
  • The literal list is diffable. I extracted the 66 string literals from :346-411: 66 entries, 66 unique after sort -u, and LC_ALL=C sort -c accepts the file, so the array is in byte order — the same order git ls-files prints, which is what makes a future delta a one-line diff. The declared length [&str; 66] is compile-checked in Rust (a literal with a different element count is a type error), so the count cannot drift from the contents; only duplication is unchecked (TS18R2-07), and duplication cannot hide a new file, only mask exactly one deleted one.
  • Nothing was silently dropped in the 238 deleted lines. Hand-read hunk by hunk: the removals are the SOURCE_DIRECTORIES / REQUIRED_ROOT_FILES constants and source_in_scope (superseded by the exact set), the is_dir() directory-presence loop and the is_file() && tracked root-file loop (superseded, with the disk half now surfacing as a read failure in TC-026 — see TS18-06), the tracked.len() == 42 assertion (superseded), the non_repository.parent() == temp_dir guard (moot now the control is inside the repo), and the inline clear closures (replaced by RAII). The only substantive losses are prose, not assertions: the assurance/ in-scope rationale, the "a record is not edited to stop naming what it examined" rationale (TS18R2-04) and the update-authorisation instruction (TS18R2-07). TC-025 and everything above :341 are untouched.
  • Make execution-control class: unchanged and still disclosed. Makefile:10-36 still states the 0-of-18 position, the SR-013 reproduction and the tl-syntax#11 / engineering-assurance#11 ownership; the TC-026 content pin (:792-806) is byte-identical to base. Not re-raised.
  • The untracked-delta trade-off is the right direction and is explained. :731-736 chooses "any non-ignored untracked live file turns TC-034 red" over "an untracked file is scanned but unrecorded", and the message names the offenders. I checked the operational cost: .gitignore:1-3 ignores /target and proptest-regressions/, ASSURANCE_DIR := target/assurance (Makefile:49-55) and the Quoin store (scripts/assurance_chain.py:46) both live under target/, so make ci leaves no non-ignored untracked file behind. The cost falls only on a developer running the suite before git add, which is the disclosed intent. The residual unpinned input is machine-local ignore configuration (.git/info/exclude, core.excludesFile), which can shrink scanned and, now, also silence the delta assertion derived from it — carried forward from round 1 as a residual, not re-raised as a finding.

Specification, records and gap lane

Head reviewed: 5861efce28c65d9fb72207f638317cba49878196 · previously reviewed: 99dde202 · base: a6d58aa4
Delta in this lane: 400bba5 "fix: close source census review findings", 5861efc "docs: adjudicate PR 18 review" — 12 files, +214/−66 outside tests/shared_assurance.rs.

This was a read-only review. No builds, no make, no cargo, no test runs, no mutation probes, no hosted-CI dispatch. Every enumeration below is a read of the Git index or of file bytes at this exact head. Execution results from earlier rounds are labelled prior evidence and are never restated as freshly measured. Second-round finding IDs are TS18R2-Rnn.

Round-1 records findings adjudication

ID Verdict Evidence
TS18-R01 FIXED (structurally; residual is now disclosed, see TS18R2-R02) spec/plans/PLAN-004-tracked-source-census/log.md:27-37 adds a dated entry recording the review, the remediation, and a full local gate at 400bba5, and states that "the next exact-head gate result and independent clearance are retained in the immutable PR conversation rather than claimed by this bundle" (log.md:34-37). That is the second of the two honest fixes offered in round 1. plan.md:5 and tasks/Task-004-verification.md:5 correctly remain in_progress.
TS18-R02 FIXED Both halves. NFR-002-…:113-115 replaces "Its retired evidence obligations are carried by FR-006" with "no blanket succession by FR-006 is implied". NFR-002-…:86-104 now disposes of all five retired AC-4 clauses named at :75-78: launcher/toolchain identity → :90-93 (AC-1 carries classification only; broader obligation outside the pre-stable claim, EA#11); compiled test census → :94-96 (AC-3 carries the Quire export; the local census is "not retained or claimed as an equivalent control"); Make execution control → :97-99 (unowned, #11); per-record validator → :100-101 (retired with its subject); active qualified record → :102-104 (not claimed, re-applies at first stable RC, EA#11). I checked the enumeration against the retired-criteria sentence clause by clause; it is complete and each disposition is a narrowing, not a succession. The file no longer contradicts itself under seal.
TS18-R03 FIXED (residual TS18R2-R03) FR-006-…:71 (AC-6) is reduced to deleted-machinery absence and names no CLI, helper or mechanism; the census obligation moves to a new behavioural FR-006-…:72 (AC-7). spec/test-matrix.md:21 splits the FR-006 row and :64-65 gives TC-026 and TC-034 separate, scoped descriptions. I checked both matrix rows against tests/shared_assurance.rs:596-737 (TC-034) and :741-807 (TC-026): neither row is wider than its test.
TS18-R04 FIXED — and correctly PRESERVE-git-source-census is gone: assurance/change-assurance.json:92-112 now carries four preservation constraints and grep -rn PRESERVE-git-source-census over the tree returns nothing. The replacement is honest: spec/evidence/suites.md:19 registers SUITE-008 with a real command and names Git as the inventory source, and :48-53 states in terms that "the Quoin record does not claim those tests ran". That statement is inside the sealed set — suites.md is SUR-001 at change-assurance.json:19 with a source_connections entry at :49, whose digest is read from the declared path's bytes (:7). So the sealed record no longer asserts an unattested property, and the non-attestation is itself sealed. Residuals in TS18R2-R04.
TS18-R05 FIXED SR-017-…:29-34 now prefixes every finding with its external identifier (TS14R3-01TS14R3-05, with TS14R3-03 explicitly split into a "root half" and a "cardinality half"); :40-45 replaces the self-granted FIXED with SPECIFIED; EXTERNAL CLEARANCE REQUIRED and AUTHOR REMEDIATED; EXTERNAL CLEARANCE REQUIRED; :19-22 says the review "did not grant closure to those external findings; only a later independent exact-head review may do that"; :74-76 repeats it in the Conclusion. This is the correct shape.
TS18-R06 FIXED SR-017-…:6 now names the reviewed candidate 130c521; SR-020-…:6 names remediation candidate 400bba5 and the review it answers (99dde202). Every author review in the bundle now carries a revision marker — the tl-rewrite head-marker defect is not repeated.
TS18-R07 DEFERRED-TRACKED (adequate) Still true in the tree: assurance/change-assurance.json:13-22 names FR-005, FR-006, NFR-001, NFR-002, TM-001, SUR-001, corpus/manifest.json, assurance/pins.json and nothing else; spec/assurance/AA-001.md and spec/reviews/SR-013-… appear in no sources entry and no proof_obligations[].configuration. Issue #16 is OPEN and its comment of 2026-09-04T20:50:29Z names this case explicitly ("bind AA-001 and SR-013 when their claims are authoritative"). The ticket genuinely describes the finding.
TS18-R08 FIXED, partial residual spec/evidence/suites.md:19 names Git in the Tool column and :51-53 records the repository-boundary refusal premise, in a digest-bound file. Residual: the row declares no tool version where SUITE-002/003/006 pin theirs, and assurance/pins.json is untouched — see TS18R2-R04.
TS18-R09 PARTIAL / DEFERRED-TRACKED The two statements this PR edited are now byte-identical between declaration and specification (change-assurance.json:83 vs FR-006-…:71; :88 vs :72). The other four still diverge (:63,68,73,78 vs FR-006-…:67-70) and nothing compares them. Generic enforcement is described in #16's 2026-09-04 comment ("validate that declaration requirement/source-id statements correspond to their authoritative specification identities"). Tracking is honest and the deferral is stated in SR-020-…:72.
TS18-R10 FIXED, residual AC-6 no longer claims "no live tracked or non-ignored untracked source … or specification row"; the population is now the exact non-archival tracked set with no extension or directory allowlist (parent-verified: 66 literals byte-identical to git ls-files minus the four is_archival_record exclusions at tests/shared_assurance.rs:422-427). Residual over-claim in the surviving verb "implements" — TS18R2-R03.
TS18-R11 FIXED for SR/PLAN, not for TC SR-017-…:68-70 records that "SR-014 through SR-016 and PLAN-003 were already allocated by the concurrent issue #15 branch". I verified this on origin/issue/15-typed-signal-context: spec/reviews/SR-014-…, SR-015-…, SR-016-… and spec/plans/PLAN-003-typed-signal-context/ all exist there, so the sentence is true. The same PR opens a TC-027…TC-033 gap that no record explains — TS18R2-R06.

Round-1 records lane: 0 high, 5 medium, 6 low → 0 open, 8 fixed, 2 deferred-tracked, 1 partial/deferred-tracked. The two flagged as most important (TS14R3-02 / NFR-002, and TS18-R04 / the sealed unattested constraint) are both genuinely and correctly closed.

New findings

ID Severity Summary Refs
TS18R2-R01 medium The bundle's retained closing reviews (SR-018, SR-019) describe a design the head no longer has and conclude "no high or medium finding remains open" for a candidate a later independent review found had one high and eight medium; nothing marks them superseded or points to SR-020 spec/reviews/SR-018-…:19-23,32,40,47,60-63, SR-019-…:19-22,28,30,31,33,57-61, plan.md:67-68
TS18R2-R02 medium The head gate result now carries an execution-environment caveat ("outside the documented Node child-process sandbox") that exists nowhere before the commit that asserts it, has no owner, no ticket, and no environment premise in the suite registry; the sandboxed run of the same gate failed and only the pass is retained spec/reviews/SR-020-…:80-85,93-95, spec/plans/…/log.md:32-33, PR #18 note, spec/evidence/suites.md:11-19
TS18R2-R03 low FR-006-AC-6's "No live repository source implements … the deleted generic evidence envelope, manifest, retention store, tool-identity lock, anchor file, or aggregate verdict" is wider than TC-026, which checks 20 named deleted paths for absence and 5 forbidden strings for non-occurrence spec/requirements/FR-006-…:71, tests/shared_assurance.rs:414-419,750-776,782
TS18R2-R04 low SUITE-008's declared command omits the producer prerequisite that its own tests need, declares no tool version, and its note names a retired test case in a range spec/evidence/suites.md:19,49, Makefile:98-103,179-187, spec/requirements/FR-006-…:88
TS18R2-R05 low FR-006-AC-7 has no owning clause in FR-006's Behavior list; source-partition integrity is not an assurance-intake behaviour spec/requirements/FR-006-…:36-61,72
TS18R2-R06 low TC-027…TC-033 are skipped with no note, in the same PR whose SR/PLAN gap note was added to close TS18-R11 spec/test-matrix.md:64-65, SR-017-…:68-70, origin/issue/15-typed-signal-context:spec/test-matrix.md
TS18R2-R07 low The bundle now carries three unreconciled coverage denominators for the same gate and never dispositions the six rows that were unbacked at 1a00573 spec/plans/…/log.md:22, SR-018-…:55, SR-020-…:78-80, PR #18 note
TS18R2-R08 low The sealed record.subject.scope is now narrower than the reviewed live population it is supposed to bound: it names nine entries while the census spans ten areas including .agent, .github and thirteen root files the scope does not name assurance/change-assurance.json:31-41, tests/shared_assurance.rs:711-725
TS18R2-R09 low Two SR-020 rows restate the reviewer's finding more weakly than it was written, in the document that exists to map them: :42 renders TS18-R04 as "claimed a local test had run", which is not what the sealed constraint said (it asserted the census property, with nothing observing whether the test ran — the reason it was frozen testimony), and :44 renders TS18-R06 as reviews that were not "fully explicit", where SR-017 in fact carried no revision marker at all spec/reviews/SR-020-…:42,44, round-1 comment 5546157654

TS18R2-R01 (medium). SR-018 and SR-019 are the only code-review and gap-analysis records in the PLAN-004 bundle, and plan.md:67-68 makes them exit criterion 5. Both are scoped to candidate 1a00573 in frontmatter, which is the right practice and is why this is a medium and not a high. But their bodies are written in the present tense about a design that 400bba5 replaced, and after merge they are what the bundle retains. SR-019-…:28 cites an "exact 42-file assertion" (now 66); :30 cites "seven directory checks plus seven tracked root-file checks" (the separate root/directory controls no longer exist — the set comparison subsumes them, as SR-020-…:60 itself says); :31 cites an "independent eight-entry area-cardinality map" (now ten entries, tests/shared_assurance.rs:711-725); :33 cites "five live criteria" (now six). SR-018-…:32 carries the same 42/eight/seven claims, and SR-018-…:40,47 records FND-1801 — same-area substitution — as ACCEPTED on the reasoning that "FR-006-AC-6 requires exact total and per-area populations, not a manifest of every path", which is false of FR-006-…:71-72 at this head and was in any case remediated as SR-020's FND-2009. Most consequentially, SR-019-…:59 concludes "The candidate has no unresolved high or medium gap" about the exact revision an independent review then found carried one high and eight medium findings. Nothing in the tree connects these documents to SR-020: grep -rn 'SR-018|SR-019' spec assurance outside their own files returns only log.md:24 and two SR-020 rows about revision markers. This is the tl-rewrite defect class one notch better — the head marker is present, so a careful reader can date the claims — but a reader who opens the bundle's gap analysis still gets a live-reading "no gap" verdict on superseded code, and an accepted disposition that no longer matches the specification it cites. The cheap fix is one superseding line in SR-020 naming SR-018/SR-019 and the four cells the remediation invalidated, or a superseded_by note in each.

TS18R2-R02 (medium). The PR note asserts the head gate passed "outside the documented Node child-process sandbox", and log.md:32-33 calls it "the known Node child-process sandbox". Neither adjective is earned: grep -rn 'sandbox|execFileSync' across the whole tree returns exactly four hits, all in spec/reviews/SR-020-…:80-95 and log.md:32-33, both authored in the commit under review. There is no prior record, no issue, no definition of which environment is sandboxed, and no environment premise anywhere in spec/evidence/suites.md:11-19 — the suite registry declares commands and tools but no execution environment, so nothing states which of the two runs is the qualifying one. What is retained is asymmetric: SR-020-…:93-95 discloses that "the initial sandboxed full gate failed only because Quoin's Node child could not spawn the installed Quire CLI", and the conclusion retained everywhere else is a pass. I want to credit the disclosure — the author volunteered a failed run and stated that "no project assertion was weakened to hide it", which is exactly the right instinct and is more than the record required. The defect is that a tool-unavailable condition in one environment and a pass in another are being reconciled by prose in a self-authored document, with no owner and no re-evaluation trigger, in a program whose central rule is that unavailable must stay distinguishable from pass. I could not verify either run; I am reporting the record's shape, not the result. This belongs on #19 (which already owns "verification-suite identity" and "tool/version premises") or on a ticket of its own.

What holds

  • The NFR-002 self-contradiction is genuinely gone, and the five-clause accounting is complete. I read NFR-002-…:75-78 clause by clause against :86-104 and against FR-006-…:67-72: five retired clauses, five explicit dispositions, no clause silently absorbed, and each unclaimed one carries its tracker (#11, EA#11) or its reason. :113-115 no longer asserts blanket succession. The live-criteria enumeration is arithmetically right (AC-1, AC-2, AC-3, AC-5, AC-6, AC-7 live; AC-4 retired at FR-006-…:76-89).
  • PRESERVE-git-source-census was removed rather than defended, and its replacement declares its own non-attestation inside the seal. This is the single best piece of work in the lane. The author had the easier option of leaving the sealed assertion and pointing at the test; instead the unattestable claim came out of the record (change-assurance.json:92-112) and what went in — suites.md:19,48-53 — says explicitly that Quoin does not claim those tests ran. I verified the seal boundary myself: SUR-001 is in sources (:19) and in source_connections (:49), and derived_fields (:7) states the digest is read from the declared path's bytes.
  • SR-017 reserves closure correctly, and SR-020 does too. SR-017-…:19-22,40-45,74-76 and SR-020-…:23-24,99-101 both state that the author response grants no closure and no merge authority, and SR-020's dispositions are AUTHOR REMEDIATED / DEFERRED — not FIXED. No row in either document marks an independent reviewer's finding closed. This is the correct discipline and it is a clear improvement on the tl-rewrite precedent.
  • SR-020 carries a reviewed-head marker and its sampled evidence cells are true at this head. :6 names both the remediation candidate (400bba5) and the review it answers (99dde202). I sampled six cells: :55 (66 non-archival paths, four exclusions) matches tests/shared_assurance.rs:422-427 and the parent's byte-identical set diff; :56 matches :670-688 (the negative directory is under root.join("target/…"), inside the repository, and the assertion requires both the census refusal and not a git repository); :57 matches :603-668 (the untracked fixture is tests/untracked.rs containing legacy_evidence_view, and the panic must name both) and :731-736 (live untracked delta must be empty and is printed); :58 matches :786-791 (GNUmakefile, makefile); :62 matches :486-490 (Drop); :63 matches :696-729. :72's claim that the changed AC-6/AC-7 declaration text matches the specification exactly is true byte for byte.
  • Counts I could check are right. 58 tracked spec/**.md at this head (47 at base, 55 at 1a00573, 57 at 400bba5) — so the PR note's "58/58" and SR-020-…:78's "57/57 … at the implementation candidate" are each the right number for the revision they name. spec/test-matrix.md has exactly 25 TC- rows, matching "all 25 matrix cases". tests/shared_assurance.rs has exactly 7 #[test] functions, matching "7/7 shared-assurance tests" and consistent with the move from 27 to 28 Rust trace symbols. Both new test functions carry // Trace: tags (:594, :739), so the matrix's ✅ implemented for TC-034 is backed by a real tracking tag.
  • The deferrals are real deferrals. gh issue view 16OPEN, and its 2026-09-04T20:50:29Z comment names both TS18-R07 (bind AA-001 and SR-013) and TS18-R09 (validate declaration statements against their authoritative specification identities) in the reviewer's own terms. gh issue view 19OPEN, titled "Own qualification-integrity and shared-assurance controls explicitly", and its body describes the NFR-003 gap, names the three siblings, and attributes it to the PR Derive the source census from tracked Git paths #18 review's gap analysis. Neither is a ticket that fails to describe its finding.
  • No dangling identifiers. Sweeping spec/ for (FR|NFR|StR)-NNN-AC-N, TC-NNN, SUITE-NNN and SR-NNN against the live tables leaves FR-006-AC-4, NFR-002-AC-4, TC-018, TC-024 and SUITE-007, all five explicitly retired-and-not-reused (FR-006-…:76-94, NFR-002-…:75-107, suites.md:38-46), plus SR-014/SR-016, which appear only in the sentence that says they are reserved on another branch — and I confirmed all three of SR-014/SR-015/SR-016 exist on origin/issue/15-typed-signal-context, so that sentence is true.
  • No stale census description in any live document. grep for PRESERVE-git-source-census returns nothing; the 42-path/eight-area/required-root description survives only in SR-011-…:148, SR-018-…:32 and SR-019-…:28-31, all revision-marked reviews (see TS18R2-R01 for why two of those still matter), and in log.md:29, where it is correctly written as history ("expands the census from a 42-path allowlist to the exact 66-path non-archival set").

Claims I could not verify. "Full local make ci passed at this exact head", "all 25 matrix cases and 28/28 Rust symbols" backed, "7/7 shared-assurance tests", "MSRV/corpus/supply-chain/rustdoc", "four mutation probes", "the complete Quoin chain", and the entire "Verification observed" block at SR-020-…:78-85. These require executing gates, which this lane may not do. Structural consistency is as noted above; the sandbox qualifier is reported as TS18R2-R02 rather than accepted.

Gap analysis

  • No NFR-003-qualification-integrity.md, now tracked — and the tracking is adequate. tl-rewrite, tl-mltl and tl-parse all carry one; tl-syntax has FR-001…FR-006, NFR-001, NFR-002, StR-001, StR-002. Issue Own qualification-integrity and shared-assurance controls explicitly #19 is OPEN and its "Required work" enumerates exactly the five orphaned obligations (shared component compatibility, verification-suite identity, source-set integrity, Make execution-control exposure, stable-release qualified record) and forbids a bespoke local framework. That is a better statement of the gap than my round-1 paragraph. It is adequate tracking; the gap itself remains open in the tree and is the root cause behind TS18R2-R05 and TS18R2-R04.
  • AC-7 has no owning behaviour clause (TS18R2-R05). FR-006-…:36-61 lists sixteen shall statements, all about the intake path; AC-6 anchors to :58-60, AC-7 anchors to nothing. An acceptance criterion whose requirement never states the behaviour is a symptom of the missing NFR-003, not a drafting slip.
  • The sealed boundary, stated exactly. Digest-bound at this head: FR-005, FR-006, NFR-001, NFR-002, spec/test-matrix.md (TM-001), spec/evidence/suites.md (SUR-001), corpus/manifest.json, assurance/pins.json (change-assurance.json:13-22,43-52), plus the four proof configurations corpus/manifest.json, Cargo.toml, spec/test-matrix.md, rust-toolchain.toml (:125,137,149,161,173) and everything under record (:23-201). Not bound: all five spec/assurance/ documents — AA-001 (the release owner's acceptance), AP-001, MP-001, AD-001, CAC-001 — every spec/reviews/ document including SR-013, and the declaration's own top-level purpose, derived_fields and sources map. NFR-002 is bound, so this round's correction to it is sealed; SR-013's corrected statement is not. Net movement this round: FR-006-AC-7 in, PRESERVE-git-source-census out, no change to which files are bound.
  • Subject scope versus census scope, now inverted (TS18R2-R08). record.subject.scope (:31-41) still lists nine entries. The reviewed population now spans ten areas and includes .agent/rules/writing_rust.md, .github/, and thirteen root files the scope does not name (AGENTS.md, CONTRIBUTING.md, README.md, Cargo.toml, Cargo.lock, LICENSE-APACHE, LICENSE-MIT, clippy.toml, deny.toml, rustfmt.toml, rust-toolchain.toml, requirements-assurance.txt, .gitignore). corpus/ is no longer the mismatch it was — it is now in both. Bind change-assurance source paths and validate declared scope #16 owns scope validation generically; the specific direction (sealed scope narrower than the repository's own census) is named in neither Bind change-assurance source paths and validate declared scope #16 nor Own qualification-integrity and shared-assurance controls explicitly #19.
  • Nothing reads the suite registry. SUITE-008 appears only in suites.md and SR-020; scripts/assurance_chain.py hardcodes SUITE-001 (:1084,1109,1141,1153) and Makefile:217 passes --suite SUITE-001. No document maps a TC to a suite except the suites.md:48-53 prose. So if the test file were renamed, SUITE-008's command would become false and no gate would notice. The mitigation is that SUITE-008 claims nothing a gate must confirm and says so; the residual is that MP-001, the measurement plan, references no suite at all.
  • Plans and tasks trace correctly. All four PLAN-004 task files carry part_of PLAN-004; index.md:11-16 lists all six bundle documents; statuses are Task-001/002/003 done, Task-004 in_progress, plan.md:5 in_progress — honest, and matching the sibling practice. The task files were updated in step with the AC-6/AC-7 split (Task-001:25-27, Task-002:25-31, Task-003:17-29) and Task-003:26-29 correctly states the 66-path/ten-area numbers. Task-002:30-31's "repository-contained non-repository directory exercises both the Git ceiling and the specific unable-to-enumerate refusal" is true of tests/shared_assurance.rs:670-688.
  • No spec/usecase/ and no US-* artifacts. Known absent program-wide; noted once, not re-derived.
  • Cross-repo structure otherwise consistent. Makefile, scripts/, assurance/, spec/ layout, workflow_dispatch-only CI and the absence of a local evidence/ tree all still match /home/peter/dev/tl-rewrite, /home/peter/dev/tl-mltl and /home/peter/dev/tl-parse. Carried forward from round 1 for the program: this repository's census is now materially stronger than the two siblings that already had the Git mechanism — exact path-set equality plus a ten-entry area map, against their area-name sets — so tl-rewrite and tl-mltl now carry both the compensating-swap gap and the allowlist gap that this PR closed. That is worth an issue in each.

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.

Close PR #14 round-three nonblocking review residuals

1 participant