diff --git a/docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-6.md b/docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-6.md index 28f601b..99d89d7 100644 --- a/docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-6.md +++ b/docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-6.md @@ -3,8 +3,18 @@ **Raised:** 2026-07-30, after Codex round 4 (`gpt-5.6-sol` @ `xhigh`, session `019fb495`) **Raised by:** Claude (optimizer), on the reviewer's adjudication of a conflict I had escalated rather than resolved -**State: PROPOSED** — needs owner ratification. **Nothing is applied.** F30 stays open and out of -`findings_closed` until this is accepted and implemented. +**State: RATIFIED** — owner ratification 2026-08-03; implemented the same day. The F30 guard +in `tests/layer1-repo.sh` now enforces both halves: semantic absence (the sweep covers every +tracked `.md` with NO allowlist, and SPLIT-3's resolver independently fails any shipping +reference that cannot resolve) and the occurrence-addressed ledger +(`tests/fixtures/f30-provenance-ledger.tsv`, 39 entries across 10 files at ratification). +One implementation decision within the proposal's frame: occurrence address = file + +sha256 of the exact line, NOT a file hash — a file-level hash would freeze every record +containing one mention, punishing the truthful-rewording practice CR-4 established. +All six acceptance probes executed and caught/passed as specified, including the one the +allowlist could never catch (a stale instruction added inside this plan's own directory) +and both controls. F30 accordingly moves into `findings_closed` and PHV5-053 into +`tickets_complete`. **Replaces:** Wave 5 acceptance row 3, verbatim: > - [ ] F30 stale-reference sweep — no `/deepgrade:doc` or `commands/doc.md` string survives anywhere [G] diff --git a/docs/plans/2026-07-20-plugin-hardening-v5/research/layer7-runtime-evidence.md b/docs/plans/2026-07-20-plugin-hardening-v5/research/layer7-runtime-evidence.md index e3be4c1..13ed38c 100644 --- a/docs/plans/2026-07-20-plugin-hardening-v5/research/layer7-runtime-evidence.md +++ b/docs/plans/2026-07-20-plugin-hardening-v5/research/layer7-runtime-evidence.md @@ -82,18 +82,29 @@ its message claims. ### Prerequisite before running A–I +> **Procedure refreshed 2026-08-03 (post-split, catalog at v7.0.0).** The plugin split (7.0.0) +> moved the hooks under test: the five safety handlers (both PreToolUse, both PostToolUse, Stop) +> now ship in `deepgrade-guard`; `deepgrade` retains SessionStart, SubagentStop and PreCompact. +> Checks B–E and G below therefore exercise `deepgrade-guard`'s handlers; A and F exercise +> `deepgrade`'s. The slots below remain EMPTY — nothing about the observations themselves is +> carried over or inferred. `tests/layer7-runtime-proof.sh --manual` prints this same checklist +> and was retargeted with the split. + ``` /plugin marketplace update deepgrade-marketplace /plugin update deepgrade +/plugin update deepgrade-guard /reload-plugins -/plugin details deepgrade -> must report Hooks (6) incl. SubagentStop +/plugin details deepgrade -> must report Hooks (3) incl. SubagentStop +/plugin details deepgrade-guard -> must report Hooks (5) ``` -> **Changed as of `df8ac58`.** The catalog now pins a GitHub source object at `9f16277`, so these -> commands install **from the pushed SHA, not from this working copy**. That is currently harmless — -> hook code is byte-identical between `9f16277` and `df8ac58` — but it stops being harmless the moment -> you edit anything under `hooks/` or `scripts/` and re-run this checklist. Push first, or the copy -> under test is not the copy you changed. +> The catalog pins a GitHub source object (v7.0.0 @ `55dbdeb`), so these commands install +> **from the pinned SHA, not from this working copy**. That is harmless only while hook code is +> byte-identical between the pin and HEAD — it stops being harmless the moment you edit anything +> under `plugins/deepgrade/scripts/`, `plugins/deepgrade-guard/scripts/` or either `hooks/` and +> re-run this checklist. Release (or test via `--plugin-dir` against the working copy) first, or +> the copy under test is not the copy you changed. ### A. SessionStart (F26; settles part of U4) diff --git a/docs/plans/2026-07-20-plugin-hardening-v5/status.json b/docs/plans/2026-07-20-plugin-hardening-v5/status.json index dc080d9..74f5976 100644 --- a/docs/plans/2026-07-20-plugin-hardening-v5/status.json +++ b/docs/plans/2026-07-20-plugin-hardening-v5/status.json @@ -208,7 +208,8 @@ "PHV5-041", "PHV5-042", "PHV5-043", - "PHV5-051" + "PHV5-051", + "PHV5-053" ], "findings_closed": [ "F01", @@ -229,7 +230,8 @@ "F32", "F11", "F13", - "F14" + "F14", + "F30" ], "wave_1_complete": { "date": "2026-07-29", diff --git a/interop.md b/interop.md index 0922d79..5f0c401 100644 --- a/interop.md +++ b/interop.md @@ -65,10 +65,12 @@ reproduced the documented shape instead of the actual one and hid the vocabulary drift above; provenance from a live artifact is what makes it a known-positive. One observed key was deliberately NOT ratified: a top-level `weight_set` appeared in the live artifact but in no template or schema and -has no consumer; it stays out of the contract. Observed but unresolved: live -elements also omit the `confidence` field some templates request — a -semantic question (the scoring skill defines confidence levels) left open -rather than silently ratified. +has no consumer; it stays out of the contract. The per-check `confidence` field the templates +originally requested was RESOLVED the same way (2026-08-03): recon showed it +had no consumer anywhere — the report generator never reads it, and the +methodology's "confidence levels" are a different, module-level concept the +orchestrator derives from gate results, not from a checks[] field. The dead +field is removed from the templates; module-level confidence is untouched. ## Deliberate non-edges diff --git a/plugins/deepgrade-readiness/agents/budget-scanner.md b/plugins/deepgrade-readiness/agents/budget-scanner.md index caf77fd..51c2e91 100644 --- a/plugins/deepgrade-readiness/agents/budget-scanner.md +++ b/plugins/deepgrade-readiness/agents/budget-scanner.md @@ -429,7 +429,7 @@ Write results as JSON to docs/audit/readability/context-budget-scan.json followi the standard scanner output schema with all 8 checks. Each check must include: -- id, name, status, points, max, confidence, evidence, details, remediation +- id, name, status, points, max, evidence, details, remediation **Remediation guidance per check:** - 8.1 high tokens: "Move detailed instructions to .claude/rules/ with paths: or globs: frontmatter. Use progressive disclosure: CLAUDE.md points to docs, Claude reads on-demand." diff --git a/plugins/deepgrade-readiness/agents/context-scanner.md b/plugins/deepgrade-readiness/agents/context-scanner.md index d7a02d0..8b308bf 100644 --- a/plugins/deepgrade-readiness/agents/context-scanner.md +++ b/plugins/deepgrade-readiness/agents/context-scanner.md @@ -267,7 +267,7 @@ Write results as JSON to docs/audit/readability/context-scan.json following the standard scanner output schema with all 10 checks. Each check must include: -- id, name, status, points, max, confidence, evidence, details, remediation +- id, name, status, points, max, evidence, details, remediation **Constraints:** - Read-only. Do not modify any source files. diff --git a/plugins/deepgrade-readiness/agents/database-scanner.md b/plugins/deepgrade-readiness/agents/database-scanner.md index 5309407..b7ea404 100644 --- a/plugins/deepgrade-readiness/agents/database-scanner.md +++ b/plugins/deepgrade-readiness/agents/database-scanner.md @@ -590,7 +590,6 @@ standard scanner output schema. "status": "pass|partial|fail|skipped", "points": 0, "max": 3, - "confidence": "high|medium|low", "evidence": "...", "details": "...", "remediation": "..." @@ -599,7 +598,7 @@ standard scanner output schema. } ``` -Each check must include: id, name, priority, status, points, max, confidence, +Each check must include: id, name, priority, status, points, max, evidence, details, remediation. Status mapping: diff --git a/plugins/deepgrade-readiness/agents/manifest-scanner.md b/plugins/deepgrade-readiness/agents/manifest-scanner.md index 5547352..d9194c0 100644 --- a/plugins/deepgrade-readiness/agents/manifest-scanner.md +++ b/plugins/deepgrade-readiness/agents/manifest-scanner.md @@ -78,7 +78,6 @@ Do NOT change any "id" or "name" field. The check names are a contract. "status": "pass|fail", "points": <0 or 3>, "max": 3, - "confidence": "high|medium|low", "evidence": "", "details": { "manifest_path": "", "all_manifests_found": [""] }, "remediation": null @@ -89,7 +88,6 @@ Do NOT change any "id" or "name" field. The check names are a contract. "status": "pass|warn|fail", "points": <0-2>, "max": 2, - "confidence": "high|medium|low", "evidence": "", "details": { "has_name": , "has_deps": , "has_scripts": }, "remediation": "" @@ -100,7 +98,6 @@ Do NOT change any "id" or "name" field. The check names are a contract. "status": "pass|warn|fail", "points": <0-2>, "max": 2, - "confidence": "high|medium|low", "evidence": "", "details": { "language": "", "framework": "", "method": "" }, "remediation": null @@ -111,7 +108,6 @@ Do NOT change any "id" or "name" field. The check names are a contract. "status": "pass|warn|fail", "points": <0-2>, "max": 2, - "confidence": "high|medium|low", "evidence": "", "details": { "readme_path": "", "has_description": , "has_setup": , "has_run": }, "remediation": "" diff --git a/tests/fixtures/f30-provenance-ledger.tsv b/tests/fixtures/f30-provenance-ledger.tsv new file mode 100644 index 0000000..fb3c6f3 --- /dev/null +++ b/tests/fixtures/f30-provenance-ledger.tsv @@ -0,0 +1,47 @@ +# F30 provenance ledger (CR-6) — every intentional historical mention of the +# deleted doc command, addressed by file + sha256 of the exact line content +# (\r-stripped). The F30 guard fails on any occurrence NOT listed here, and on +# any entry whose file no longer contains a matching line. Line-content hashes, +# not file hashes: records that contain one mention stay editable elsewhere, +# which is what the CR-4 truthful-rewording practice requires. +# Registering a NEW entry is a provenance claim — it belongs in a change +# record or a plan's own history, never in the same commit as a feature. +docs/plans/2026-07-20-plugin-hardening-v5/acceptance-matrix.md e6853e0a8d0feeca0cac0dd058a05c92fcf06fbd7e60b7f47f421603c229227d +docs/plans/2026-07-20-plugin-hardening-v5/approach.md 77f3ff950477dc077571b5e95536b1fda50416f6fc7474ef862f85d88b4786a2 +docs/plans/2026-07-20-plugin-hardening-v5/approach.md 61e980dcea61b78c03aa968bfc09ab4aed853a09fc8bb06e1d3590daf5903369 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 3d301f87992072978cfb5d3e9d68651987267fd128b82dd214b597ae6d830414 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 7dee6b9416e47f84d3ba8070f83a6cf2d1a5ffc0d018399128166009b94fbda5 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 7323964a02cae50a620f54fcdf9570dc873bc05958abf7bdc7df5eab6c5d406a +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 982d1d4ab8eb247102b5b7ee463cc5824e6c3b9482de45e8c1002e496ebd05df +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md c67934f2b5f2863c44b00a176afa60a1607c884140cb573237155e25c5318dba +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 36a2a4338f77c9fc33d2e284037c18ee0ecb221b385219af901f9a34f10d362c +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md a51f1525ab49dc4d3939b116a8ac849e7019f8a2524de34a4dab2d1820fe9452 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 335603b911112447452b031a177fc7044f31fa7d5ee7a0c26ab6a6964a97f972 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 506dcbe880722d979fa1c76ae920ee6adef19aa49f4cd65d7a138219df40f58d +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md f6a77c6959030a42fabb03e048ef101088e49757207be7fb0902fa2b9ea835fd +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md ff9dd134d04abe138a7ba5adbc8af9bb7fea18e2c846834275566854b8155c44 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 77684a6230f46967fc2ee3eadc49f00c8be62228869b099f687363d98a9e81fc +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-4.md 5b1eceac26a57f385fb6cc708d3f3cb3fe1a56ce9205b586badc8dae30118dbb +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-6.md 7dee6b9416e47f84d3ba8070f83a6cf2d1a5ffc0d018399128166009b94fbda5 +docs/plans/2026-07-20-plugin-hardening-v5/changes/CR-6.md 65aac0f3799ea409530500b62874bcf3dada028be76af327a3d963ef8c2f5a4c +docs/plans/2026-07-20-plugin-hardening-v5/codex-wave5-audit-prompt.md c505fd57059eb3b2ec6608920a38a809948839c65e0c8cfd17179309e1181cd4 +docs/plans/2026-07-20-plugin-hardening-v5/codex-wave5-round4-prompt.md ba7d59ca6b4fc046714190e26d3f3bc5fa0cff4cd2bad1b1f2910656feb341fe +docs/plans/2026-07-20-plugin-hardening-v5/codex-wave5-round4-prompt.md 7c299a3ab04f0ec102e4d624568f4ab12310f852d23778b8d0e9e04c0acb3e33 +docs/plans/2026-07-20-plugin-hardening-v5/confidence.md 9f9ff55063c411826ebef8eedc8d4d3c829441edf2b36f6bd02b5c2f7e63b35a +docs/plans/2026-07-20-plugin-hardening-v5/research/best-practices.md 2400e6c61184dfacfd205352d8d4d2f674f63fff22722a7eb697440a598f3676 +docs/plans/2026-07-20-plugin-hardening-v5/research/best-practices.md 4a99c57fcd0892a68e4762771ebb0af8e841da7a348fb19fb874fcae95235209 +docs/plans/2026-07-20-plugin-hardening-v5/research/best-practices.md 540f775e8ea452f49cd8bea0dce0991afc896421ca496f6f05671498ca342b34 +docs/plans/2026-07-20-plugin-hardening-v5/research/best-practices.md 200fe30a67acda51178ff5df09376407b942cb8aadfae5cdf85f34c40b0652db +docs/plans/2026-07-20-plugin-hardening-v5/research/best-practices.md a86a422185766e80a4869b43d323f6732dc13c12d70f19ee7c28200adc2a2669 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md 3a8f182183ad6de5ed815f923e9ecb827ff8afc783767fadceffd4e4e3ce4b6d +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md a4178ef6ae8d411a9e07d8d7a36ceeeb255e76bedcbc8e0f9cea12baeb5342b4 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md 7cd93c83ef7781dea6503d536f076414a69613fac41e7c12893a9b565ea974c9 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md 0b23e4b82af736f653ce0fa9dcd044f6bc0cc34d3cc1ed0c552a30a7bc04dbb0 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md 21231c01ac6b12797054f2965e8c0e2143e76945e1d26adbeaa5aa1a94afaff6 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md 3c1fa4458ad8b0faa72f3c3b1ccca720e29cabc8a3f17c1bfc270c43a62b1b12 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md f004b555362aa375778b0de0e4e5dfbe06b9d64926554f9379b4c0329577f43d +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md f31645295d9a3dad0849718992fe514153701565970512808c3a12868a5bf908 +docs/plans/2026-07-20-plugin-hardening-v5/research/codebase-scan.md 7cc69bb6b4c464839ce50571f644adc47c301708c0478fcd263cf093b831c6e0 +docs/specs/plugin-hardening-v5.md 2e16326a4c37f4a673a76eaef4859b0508848af028073ceec974dc5505e16a2e +docs/specs/plugin-hardening-v5.md 6d2df358afd5a639de92e59ad7efc6d2a2ee35593b78456f07ab22a8a2c6eb79 +docs/specs/plugin-hardening-v5.md cc81f0c810473f344b305faf57faa928de64363476367c08c667ee488eb5628a diff --git a/tests/layer1-repo.sh b/tests/layer1-repo.sh index 1e4a788..94ed699 100644 --- a/tests/layer1-repo.sh +++ b/tests/layer1-repo.sh @@ -143,60 +143,86 @@ for f30_cmds in commands plugins/*/commands; do [ -e "$f30_cmds/doc.md" ] && { fail "F30: $f30_cmds/doc.md still exists"; f30_bad=1; } done -# Subject set: every tracked .md except an EXPLICIT two-path allowlist. +# CR-6 (ratified 2026-08-03): occurrence-addressed provenance ledger, no allowlist. # -# The previous version skipped all of `docs/plans/*`, which I described in the pass -# message and the commit as "enforced literally, no exemptions". It was not: three stale -# references were living in an UNRELATED plan (2026-04-03-mcp-research-integration) and -# the sweep reported clean (Codex N1). I had replaced two narrow disclosed exemptions -# with one broad undisclosed one. Those three are now reworded, so the allowlist is: +# The two-path allowlist this replaces was the THIRD construction. The first skipped +# all of docs/plans/* while claiming "no exemptions" (Codex N1 — three stale +# references survived in an unrelated plan). The second was CR-4's exemption +# machinery, withdrawn. A directory allowlist of any width is a hiding place: a new +# stale INSTRUCTION added under an allowlisted path passes. An occurrence ledger is +# an inventory: every intentional historical mention is enumerated in +# tests/fixtures/f30-provenance-ledger.tsv as `pathsha256(line)`, and the sweep +# fails BOTH ways — an occurrence not in the ledger (new stale reference, wherever +# it lives), and an entry whose file no longer carries a matching line (the ledger +# cannot go stale and keep passing). # -# 1. THIS plan's directory — its records ARE the evidence of the deletion -# 2. THIS plan's spec — it states the row, so it must quote the strings -# -# Nothing else, including other plans. Anything added to this list is a scope change and -# belongs in a change record. -f30_count=0 -f30_skipped=0 -# NUL-delimited: `for f in $subjects` word-splits, so a tracked path containing a space -# would be counted toward the floor and then silently skipped during inspection -# (Codex F2). -z/IFS= is immune to that. -while IFS= read -r -d '' f; do - case "$f" in - docs/plans/2026-07-20-plugin-hardening-v5/*|docs/specs/plugin-hardening-v5.md) - f30_skipped=$((f30_skipped + 1)); continue ;; - esac - [ -f "$f" ] || continue - f30_count=$((f30_count + 1)) - if grep -qE '/deepgrade:doc\b|commands/doc\.md' "$f" 2>/dev/null; then - fail "F30: $f references a deleted command — $(grep -nE '/deepgrade:doc\b|commands/doc\.md' "$f" | head -1 | cut -c1-70)" +# Hash granularity is the LINE, not the file: a file-level hash would freeze every +# record that contains one mention, which punishes exactly the truthful-rewording +# practice CR-4 established. Editing the ledgered line itself changes its hash and +# fails both directions at once. The ledger stores hashes, never the tokens, so it +# is not an occurrence; this script is not swept (subjects are tracked .md). +F30_LEDGER="tests/fixtures/f30-provenance-ledger.tsv" +f30_re='/deepgrade:doc\b|commands/doc\.md' +f30_hash() { printf '%s' "$1" | tr -d '\r' | sha256sum | cut -d' ' -f1; } + +# Instruments proven before their silence is trusted: the regex must fire on the +# real token and stay quiet on the near-miss; the hasher must distinguish lines. +printf '%s\n' 'see /deepgrade:doc for usage' | grep -qE "$f30_re" \ + || { fail "F30: token regex fails its known-positive — the sweep would be vacuous"; f30_bad=1; } +printf '%s\n' 'see /deepgrade:documentation for usage' | grep -qE "$f30_re" \ + && { fail "F30: token regex matches the LIVE documentation skill name — it would flag legitimate references"; f30_bad=1; } +[ "$(f30_hash 'line A')" != "$(f30_hash 'line B')" ] \ + || { fail "F30: line hasher cannot distinguish lines — ledger lookups would be vacuous"; f30_bad=1; } + +if [ ! -f "$F30_LEDGER" ]; then + fail "F30: provenance ledger $F30_LEDGER is missing — every historical mention is unregistered without it" + f30_bad=1 +else + # Direction 1: every occurrence in every tracked .md must be registered. + # NUL-delimited: `for f in $subjects` word-splits, so a tracked path containing a + # space would be counted toward the floor and silently skipped (Codex F2). + f30_count=0 + f30_occurrences=0 + while IFS= read -r -d '' f; do + [ -f "$f" ] || continue + f30_count=$((f30_count + 1)) + while IFS= read -r f30_line; do + [ -n "$f30_line" ] || continue + f30_occurrences=$((f30_occurrences + 1)) + f30_h=$(f30_hash "$f30_line") + grep -qF "$f $f30_h" "$F30_LEDGER" \ + || { fail "F30: unregistered occurrence in $f — $(printf '%s' "$f30_line" | cut -c1-60)"; f30_bad=1; } + done < <(grep -E "$f30_re" "$f" 2>/dev/null) + done < <(git ls-files -z '*.md' 2>/dev/null) + + # Direction 2: every ledger entry must still be backed by a matching line. + f30_entries=0 + while IFS=$'\t' read -r f30_lf f30_lh; do + case "$f30_lf" in \#*|'') continue ;; esac + f30_entries=$((f30_entries + 1)) + f30_found=0 + if [ -f "$f30_lf" ]; then + while IFS= read -r f30_line; do + [ "$(f30_hash "$f30_line")" = "$f30_lh" ] && { f30_found=1; break; } + done < <(grep -E "$f30_re" "$f30_lf" 2>/dev/null) + fi + [ "$f30_found" -eq 1 ] \ + || { fail "F30: stale ledger entry — $f30_lf no longer carries the registered line ($(printf '%s' "$f30_lh" | cut -c1-12)...)"; f30_bad=1; } + done < "$F30_LEDGER" + + # Floors, per the recurring vacuous-pass lesson: a subject set or a ledger that + # collapses to nothing would sweep clean and prove nothing. + if [ "$f30_count" -lt 10 ]; then + fail "F30: subject set is only $f30_count files — the derivation collapsed, so a pass here would be vacuous" + f30_bad=1 + fi + if [ "$f30_entries" -lt 10 ]; then + fail "F30: ledger holds only $f30_entries entries (39 registered at ratification) — the ledger read collapsed, so the stale-entry direction proved nothing" f30_bad=1 fi -done < <(git ls-files -z '*.md' 2>/dev/null) - -# Detect N1's ACTUAL failure mode rather than counting skips. N1 was not "too many files -# skipped" — this plan legitimately has ~46 — it was OTHER plans falling inside the -# exclusion. So: assert that files under docs/plans/ belonging to other plans are being -# INSPECTED. A count threshold cannot see that and my first attempt at one just fired on -# the legitimate size of this plan. -f30_other_plans=$(git ls-files 'docs/plans/*.md' 2>/dev/null \ - | grep -v '^docs/plans/2026-07-20-plugin-hardening-v5/' | grep -c . || true) -f30_other_dirs=$(git ls-files 'docs/plans/*' 2>/dev/null \ - | grep -v '^docs/plans/2026-07-20-plugin-hardening-v5/' \ - | sed 's|^docs/plans/\([^/]*\)/.*|\1|' | sort -u | grep -c . || true) -if [ "$f30_other_dirs" -gt 0 ] && [ "$f30_other_plans" -eq 0 ]; then - fail "F30: $f30_other_dirs other plan director(ies) exist but contributed no inspected .md files — the exclusion is broader than this plan, which is how three stale references survived" - f30_bad=1 -fi -# Floor, per the recurring vacuous-pass lesson: a derivation that collapses to nothing -# would otherwise sweep clean and prove nothing. -if [ "$f30_count" -lt 10 ]; then - fail "F30: subject set is only $f30_count files — the derivation collapsed, so a pass here would be vacuous" - f30_bad=1 + [ "$f30_bad" -eq 0 ] && pass "F30: all $f30_occurrences occurrences across $f30_count tracked files are ledger-registered, and all $f30_entries ledger entries are live" fi - -[ "$f30_bad" -eq 0 ] && pass "F30: neither stale string survives in any of $f30_count tracked files ($f30_skipped skipped: this plan's own records and spec)" # =========================================================================== # PH5-001 / acceptance row A1: lint-registry.md is the ONLY file that states # LINT rule text.