FIX7 Recheck-9 Produce Fail-Closed + Negative Tests — R9-B2 (2026-06-10)
FIX7 Recheck-9 — Production --produce Fail-Closed + Negative Tests (R9-B2)
- Date: 2026-06-10 · Authority: provisional-non-authority, evidence-only. Codex consulted: NO · Production mutation: NO (SSOT patch P-EXT-2 = authorized KB doc write in this lane).
- Subject: FIX7-CANON-V1 canonicalizer SSOT fence (blueprint path, KB rev3) ==
evidence/fix7_canon_v1_ssot_extended.py(one canonical identity, sha256d9caa9fe…f3e5).
1. The V1 defect (Codex evidence, reproduced)
--produce with docs/05-rollback-blueprint.md removed printed EXTRACT_ERROR=LOCAL_FILE_MISSING yet reported membership_frozen_ok: True and exited 0 (reproduced: REPRO_B2_MISSING05_PRODUCE_EXIT=0). Root causes: membership hashed the frozen ID constants (not the actual corpus); per-member errors never gated digests; --produce unconditionally sys.exit(0).
2. The P-EXT-2 repair (in the SSOT fence itself)
validate_corpus_listing(actual_names)— pure fail-closed listing check vs the frozen membership: detects missing, extra, duplicate (exact or case-variant) members.extract()rejects empty active content (ACTIVE_CONTENT_EMPTY).- membership recomputed over PRESENT+VALID members — a missing/invalid member can never reproduce the frozen pin
f2bda8…fe251. gate_and_suppress()— single enforcement point: ANY listing problem / extract error / incomplete extraction ⇒corpus_ok=false,membership_frozen_ok=false, and EVERY candidate digest (membership, corpus, registry, boundary, guard set, canonicalizer candidate, N7, N8) replaced bySUPPRESSED_CORPUS_NOT_OK.- CLI: exit 0 ONLY iff
corpus_okANDmembership_frozen_ok; exit 4 otherwise, printingALL CANDIDATE DIGESTS SUPPRESSED (fail-closed: corpus not OK)plus the problem lists. - Statuses documented in the SSOT prose:
LOCAL_FILE_MISSING,DOCS_DIR_MISSING,GUARD_SET_SOURCE_MISSING,SUPPRESSED_CORPUS_NOT_OK,ACTIVE_CONTENT_EMPTY. - Selftest 36 → 45 checks: +9 R9-B2 fixtures (exact/missing/extra/duplicate listings; gate truth table; suppression wiring over a synthetic problem corpus; empty-content rejection).
3. Executable negative tests (every one EXECUTED; results recomputed inside every --verify)
| test | construction | expected | observed |
|---|---|---|---|
| missing member (Codex's probe) | docs copy minus 05-rollback-blueprint.md |
exit 4; all digests suppressed; membership_frozen_ok: False |
exit 4 ✓ suppression ✓ (suite T2a; verify negative_tests.produce_missing_member) |
| extra member | docs copy + 99-extra-doc.md |
exit 4; suppression | exit 4 ✓ |
| invalid member | doc 03 replaced by unbalanced-fence content | exit 4; suppression (error FENCE_UNBALANCED) |
exit 4 ✓ |
| docs dir absent | nonexistent path | exit 4; DOCS_DIR_MISSING |
exit 4 ✓ |
| duplicate member | listing with case-variant 05-Rollback-Blueprint.md (pure check — a case-insensitive FS cannot host the file pair) |
listing not ok, 1 duplicate | ✓ (selftest fixture + verify negative_tests.listing_duplicate_detected) |
| wrong membership | 9-doc digest | ≠ frozen pin | ✓ |
| doc tamper | doc 03 + appended bytes | hash diverges (caught by manifest/HASH_MANIFEST) | ✓ |
| absent .py (recheck-8 class) | open nonexistent file | FileNotFoundError fail-closed | ✓ |
Nominal control: full valid corpus → exit 0, corpus_ok: True, membership_frozen_ok: True, and every digest value byte-identical to V1 (encoder untouched: N1×10 unchanged, N3 bb9ca0…, N4 9b111c…, N5 1144b7…, N6 rehearsal d777e8…, membership f2bda8…fe251).
4. Verdict
R9-B2 CLOSED. Detection now IS enforcement: any corpus problem suppresses all candidate digests, marks membership/corpus not OK, and exits nonzero — proven by executable negative tests that re-run inside every manifest verification and inside the adversarial suite.