67%

Registration Lint + negative fixture (north-star mechanical gate)

Goal

  • Turn the registry into an enforced boundary: a new consumer that skips registration, or re-derives meaning, is mechanically caught.
  • The negative fixture is the falsifiable core — a synthetic unregistered consumer MUST be rejected.

Implementation Sketch

  • Lint failure modes: (a) a crate depends on ori_ir::canon with no CANON_CONSUMERS entry; (b) a registry entry with no matching dependency edge (stale); (c) a declared pool-subset mismatch where detectable.
  • Negative fixture: a synthetic unregistered-consumer scenario the lint rejects (harness fixture, not a real workspace crate).
  • Wire the lint into test-all.sh / CI; emit an actionable diagnostic per diagnostic-ux.md (cause = unregistered consumer; fix = add an entry to ori_ir::canon::consumers).
  • 500-line cap applies (wrapper scripts/); no compiler-rigor matrix (tooling artifact).

Spec References

.claude/rules/diagnostic-ux.md §2 (actionable message); .claude/rules/compiler-ops.md (test-all wiring); impl-hygiene.md PHASE-54.

Work Items

  • Implement the three lint failure modes over registry x cargo metadata.
  • Correct the plan’s own plan-wide success criterion sc-5a808987f4: its probe_ref currently invokes ‘python -m scripts.consumer_ssot_lint —self-test’, a wrapper-root Python module that does not exist anywhere in the repo (ModuleNotFoundError on import; zero files matching consumer_ssot_lint under scripts/). Re-point probe_ref to the real, already-shipped compiler_repo/scripts/crate-dag-lint.py —self-test invocation (or a thin stable wrapper if the plan-corpus API needs a cross-plan-stable path), via the plan.json write API (scripts/plan_corpus/write.py), never a hand-edit. This is a STRUCTURE:phantom-primitive-ref-adjacent gap the section cannot honestly claim its north-star gate is proven without fixing — the walking-skeleton sibling section already surfaced an identical unresolved gap against a differently-numbered criterion, confirming this is a real, still-open plan-wide defect, not stale dossier content.
  • Extend the EXISTING compiler_repo/scripts/crate-dag-lint.py (never author a new scripts/consumer_ssot_lint.py or other second script) with a new check_unregistered_consumers(root, registry, ir_module=“ori_ir::canon”) function implementing ONLY failure mode (a): scan workspace source (a grep-rl-style walk over ‘ori_ir::canon’ usage, or cargo metadata’s package file lists) for crates importing ori_ir::canon, diff against CANON_CONSUMERS, and report every unregistered dependent crate by name — mirroring check_registered_consumers’s existing violation-dict shape and rule: citation style, with ori_canon itself explicitly exempted (it holds the edge as the PRODUCER, consuming no meaning). Do NOT re-implement failure mode (b) (already shipped) — compose main()‘s check_graph(…) + check_registered_consumers(…) + check_unregistered_consumers(…) additively. For failure mode (c), explicitly record the decision that a mechanical pool-subset-mismatch check is not achievable via cargo-metadata/source-grep beyond the existing s-e731d59b unit-test assertions, rather than silently omitting it — budget the file’s 500-line cap (already at 499/500) by planning a sibling-module split (e.g. a crate-dag-lint/ package or a new consumer_registration_check.py imported by the existing CLI) as part of this same work item, before writing the extension.
  • Add the synthetic unregistered-consumer negative fixture the lint MUST reject.
  • Wire into test-all.sh/CI with an actionable diagnostic naming the exact cure.
  • Wire crate-dag-lint.py (both the FORBIDDEN_EDGES check and the newly-extended check_unregistered_consumers) into test-all.sh AND lefthook, emitting an actionable diagnostic per diagnostic-ux.md Sec2 (cause = unregistered consumer crate; fix = add a ConsumerEntry to ori_ir::canon::consumers::CANON_CONSUMERS). BEFORE adding a lefthook entry, check compiler_repo/lefthook.yml’s current state for an existing crate-dag-lint: block — compiler-spec-system#s-83426e62’s work item w-49d8d8f6 independently plans the identical lefthook-wiring deliverable and neither plan declares a blocking edge on the other. Whichever section lands the entry first wins; the other extends/verifies only, never re-adds a duplicate block. Follow the existing version-naming-lint / operation-name-lint staged —warn-only convention (compiler_repo/lefthook.yml:61-84) unless a deliberate decision is made to gate immediately (crate-dag-lint.py’s main() already returns a hard exit code 0/1/2, unlike those two warn-only lints) — record whichever choice is made explicitly, do not silently default.

Fresh intel (regenerated)

This section’s own falsifiable gate is unproven twice over. First, its probe_ref (sc-5a808987f4: “python -m scripts.consumer_ssot_lint —self-test”) names a wrapper-root Python module that does not exist anywhere in the repoModuleNotFoundError on import, zero files matching consumer_ssot_lint under scripts/. Second, the section’s own Implementation Sketch says the lint script lives in wrapper scripts/, but the REAL, ALREADY-SHIPPED lint machinery — check_registered_consumers + _read_canon_consumers + the CANON_CONSUMERS/ConsumerEntry registry it validates — lives in compiler_repo/scripts/crate-dag-lint.py (499 of a 500-line cap), a file the sibling walking-skeleton section already built and closed. Executing this section by writing a brand-new scripts/consumer_ssot_lint.py would be LEAK:algorithmic-duplication against an existing, tested cargo-metadata reader.

Three decisions this package forces before any code is written:

  1. Extend crate-dag-lint.py, never author a second script. The three failure modes in the section’s Goal map onto the SAME file: (a) unregistered consumer — genuinely unimplemented today (see §2/§3.1 — the shipped check_registered_consumers only checks the OPPOSITE direction, registered-crate-must-depend, never depends-on-canon-must-be-registered); (b) stale registry entry — already shipped as the missing-ir-dep/unknown-consumer-crate violation kinds; (c) pool-subset mismatch — genuinely hard to detect mechanically (the section’s own “where detectable” hedge is honest: cargo metadata sees crate-level edges, not which of the three CanonPool variants a crate’s source actually walks).
  2. Fix the plan’s own success criterion, don’t work around it. sc-5a808987f4’s probe_ref must be corrected to invoke compiler_repo/scripts/crate-dag-lint.py (mirroring the walking-skeleton dossier’s identical, still-unresolved finding on a differently-numbered criterion) before the section can honestly claim its north-star gate is met.
  3. Coordinate with compiler-spec-system#s-83426e62 (not-started) before wiring CI. That section’s work item w-49d8d8f6 says verbatim: “Wire crate-dag-lint.py into lefthook (close the second governance gap)” — the SAME deliverable this section’s w-687b3dbf names (“Wire into test-all.sh/CI with an actionable diagnostic”). Whichever section lands the lefthook entry first, the other must NOT re-wire it; the second section extends/verifies, never duplicates. Neither plan currently declares a depends_on:/blocked-by: edge to the other — that gap is real (see §6).

The 500-line file cap is a live constraint, not a formality: crate-dag-lint.py is 499 lines today. Adding failure-mode-(a) source-scanning logic (§3.1) plus the negative fixture plus CI wiring will very likely need WASTE-12-conscious extraction into a sibling module before it lands.

[]

DIG DEEPER

scripts/intel-query.sh plan-status canonical-consumer-boundary
scripts/intel-query.sh dag-ascii canonical-consumer-boundary
python -m scripts.plan_corpus.read plans/canonical-consumer-boundary --criteria --section s-e26ee405

(full dossier: consumer-registration-lint—s-e26ee405.intel.md)