100%

section-02: author and finalize the 8 rules

Goal

Write the 8 grounded rules into .claude/rules/impl-hygiene.md (exact catalog format) and the matching subcats into .claude/rules/finding-categories.md, as one atomic id-assignment pass, then verify catalog integrity.

Implementation Sketch

Re-verify next-available ids at write time — SPEC-66 / COVER-32 were the max as of this plan’s authoring; re-check before writing, since impl-hygiene.md may have grown.

Assign all 8 ids (COVER-33, COVER-34, SPEC-67..SPEC-72) in a SINGLE atomic pass per the Step 8B consensus finding — never interleave with an unrelated edit that could shift the max between assignment and write.

Each rule text follows the exact - PREFIX-NN (was CATEGORY:subcat): directive (Severity); cure; pin via ... format of its sibling rules (SPEC-45, SPEC-62..66, COVER-26).

Rule bodies (final text, corrected per both /tp-help consensus rounds):

  • COVER-33 (was COMPILER:ablation-toggle-untested): every ORI_DISABLE_*/ ORI_FORCE_* toggle needs a negative-pin test proving it reproduces its documented bisected behavior. States explicitly it SPECIALIZES SPEC-63 (env-var doc+test) for the ablation-toggle subset — not a duplicate. Cites BUG-07-005 (resolved: orphan env vars read in source but not registered in debug_flags.rs) as the concrete Ori precedent for what an unverified toggle costs.
  • COVER-34 (was COMPILER:dead-instrumentation-dependency): a crate declaring tracing/log needs call-site volume proportional to its pipeline role. Zero call sites (ori_patterns) = Critical; disproportionate near-zero (ori_parse) = Major. Cure is BIDIRECTIONAL per the Step 8B correction: default is REMOVE the dead dependency (NAME-11), OR add proportional instrumentation — decided per-crate by whether the crate’s pipeline role genuinely warrants observability, recorded as a decisions/NN-*.md ADR when it is a genuine tradeoff call. BUG-07-009 (resolved: the tracing-tree TRANSITIVE dependency always compiled into oric regardless of ORI_LOG_TREE usage) is the same failure shape one level up — noted as adjacent precedent; COVER-34’s wording targets in-crate call-site volume, not transitive deps, so BUG-07-009’s exact shape stays out of this rule’s literal scope.
  • SPEC-67 (was COMPILER:eager-trace-side-effect): bans a side-effecting or non-O(1) expression as a direct trace!/debug!/etc macro argument — forward-looking only (gates new code). Cure cites rustc’s real EnteredTraceSpan::or_if_tracing_disabled marker-trait dual-dispatch pattern (rustc_const_eval/src/interpret/util.rs:56-75) and the ?field/%field lazy-capture idiom, contrasted against the allowed ori_parse/src/grammar/expr/mod.rs trace!(pos=.., kind=..) cheap-field-access shape.
  • SPEC-68 (was COMPILER:nondeterministic-dump-output): a dump/trace/remark serializer iterating a HashMap/HashSet without canonicalizing order breaks diff-based bisection. Concrete pin: dump_orchestrator.rs’s unified DumpPhase surface (the golden-baseline CONTRACT in dump_orchestrator_baseline.rs’s module doc, ORI_BLESS=1 to regenerate) + rc-stats.sh’s diff-driven contract. Cites the CONTRACT, not the current pass/fail state — BUG-04-252 (open) has one baseline leg (ori_dump_after_llvm_pins_llvm_ir_driver_block) presently red; the rule governs the mechanism’s shape, and BUG-04-252 is the mechanism catching its own regression exactly as designed, not evidence against it.
  • SPEC-69 (was COMPILER:trace-target-naming-drift): bans an explicit tracing target: override diverging from the owning crate::module path. Grounded against oric/src/tracing_setup.rs’s real tracing_subscriber filter setup.
  • SPEC-70 (was COMPILER:cycle-recovery-untraced): the ONE cycle_recovery_strategy closure SPEC-62 reserves for a genuinely-fixpoint case must trace-log the recovered query chain, distinguishing it from SPEC-62’s banned silent-masking case at incident time. States explicitly (per Step 8B correction) that the trace-log is OBSERVATIONAL, not a memoized Salsa input — purity-exempt from SPEC-34/SPEC-35 — and is a strict LAYER on SPEC-62’s carve-out, never a second path reopening the banned case. Grounding is entirely FORWARD-LOOKING (no live cycle_recovery_strategy usage anywhere in compiler/ today — confirmed by grep) — rests on SPEC-62’s existing text plus rust#119321 (“query cycles could be handled more like ICEs”), which sits at the intel graph’s 100th-percentile pain+controversy score, the sharpest available cross-repo corroboration that silent cycle recovery is a chronic, high-friction failure mode worth preventing before Ori grows its own instance.
  • SPEC-71 (was COMPILER:trace-target-central-registry): every crate-level tracing target/span-name string must be declared in ONE central enumeration artifact, verified by a parse-check script — mirroring the PATTERN (not the path) of the REAL debug_flags.rs + check-debug-flags.sh pairing (an enumeration file plus an external verifier), corrected per Step 8B from an earlier “macro-enforced registry” framing that mischaracterized debug_flags.rs (the flags! macro only emits name-mirrored consts; check-debug-flags.sh is the actual enforcement) and would have demanded unbuildable runtime-registration infrastructure. Worded pattern-first, never path-first — check-debug-flags.sh is claimed restructuring territory of the not-started diagnostics-split plan; the rule must survive that relocation. The registry MUST enumerate every independent reader of a shared env var, not just the first one found — ORI_DUMP_AFTER_ARC has TWO independent readers (dump_orchestrator::dump_arc at the driver level, plus ori_llvm::codegen::function_compiler::define_phase::dump_arc_if_requested inside codegen itself) and both must appear in the enumeration. Second cross-repo precedent beyond Lean4’s registerTraceClass: rustc’s rustc_data_structures::profiling::EVENT_FILTERS_BY_NAME named per-event filter table. Distinct failure mode from SPEC-69: an un-enumerated-but-correctly-named target vs an incorrectly-named one. Even Lean4’s own reference registry has had real registration-completeness incidents (lean4#4151 “fix: add missing registerTraceClass”, lean4#4369 “chore: missing registerTraceClass”, found via /intel-package’s section-02 pass) — corroborating evidence that “referenced but not registered” is a recurring failure mode, not a hypothetical one.
  • SPEC-72 (was COMPILER:ablation-verdict-tracing): a fired ORI_DISABLE_*/ ORI_FORCE_* toggle must emit a structured trace event (naming the toggle + what it changed) at the point it takes effect — mirrors TypeScript’s real tracing.instant(Phase, eventName, {fields}) use-site-event pattern (reference_repos/lang_repos/typescript/src/compiler/checker.ts:22892). Distinct from COVER-33 (CI-time proof) — this is runtime visibility for whoever is actively bisecting with the flag set.

Matching finding-categories.md COMPILER subcats (one bullet per rule, Detection column per the category’s existing convention) land in the SAME commit as the rule text.

Spec References

N/A.

Work Items

  • Re-verify next-available SPEC-NN/COVER-NN ids against .claude/rules/impl-hygiene.md HEAD; author all 8 rules (exact text above) into the catalog in one atomic pass.
  • Append the 8 matching COMPILER:* subcats to .claude/rules/finding-categories.md’s COMPILER category block, AND update that block’s trailing citation line (“See impl-hygiene.md §SPEC (SPEC-62/SPEC-63) + §COVER (COVER-26).”) to include the 8 new rule ids, and its Dflt:/severity line to reflect COVER-34’s zero-call-site Critical carve-out (per /intel-package §3H — appending only the bullets without updating the citation line leaves it stale).
  • Verify catalog integrity: a mechanical assertion the final catalog holds 8 net-new uniquely-numbered entries (no intra-batch id collision); run python -m scripts.plan_corpus check, python3 scripts/prose-lint.py .claude/rules/impl-hygiene.md .claude/rules/finding-categories.md, THEN python -m scripts.finding_categories freeze (regenerates scripts/finding_categories/tokens.jsonfinding-categories.md’s SECOND frozen SSOT projection that category_tokens() reads instead of live-parsing the .md; /intel-package found nothing wires this automatically for either target file, and a desync here is the exact BUG-07-229 failure class) followed by python3 -m pytest scripts/finding_categories/tests/ -q, THEN ./test-all.sh (regression canary) — all clean before close; commit the regenerated tokens.json in the SAME commit as the two .md edits.

Fresh intel (regenerated)

This section’s real terrain is NOT the compiler code the 14-pattern synthesis discusses - it is TWO rule-catalog files and the machinery that keeps them internally consistent. The sibling dossier (research-grounding--s-156cd240.intel.md) already did the exhaustive code-graph recon on debug_flags.rs/dump_orchestrator.rs/tracing_setup.rs and independently re-verified every citation section-02 will use - this dossier does NOT re-run that recon (cite it, per methodology §7.1 “interrogate, never duplicate”). What this dossier found NEW, specific to the write-target itself:

  1. The JSON-SSOT/generator path for this exact catalog was already tried and reverted. Commit e19204709 (2026-06-23, --bypass) deleted .claude/rules/impl-hygiene-rules.json + .schema.json, made impl-hygiene.md “the hand-authored SSOT” again, and extracted finding-categories.md as its own file in the SAME commit. plans/completed/impl-hygiene-rules-as-data/ is the full record of the tried-and-abandoned generator architecture. Section-02’s plan (hand-edit both files directly) is the CORRECT, current-architecture path - do not propose a generator/JSON-SSOT alternative, it is a confirmed dead end for this exact file pair. §2/§4/§6.
  2. .claude/rules/finding-categories.md has a SECOND, independently-frozen SSOT projection - scripts/finding_categories/tokens.json - and NOTHING mechanically re-syncs it for this section’s edit. category_tokens() (the live API /tpr-review’s reviewer_history.py, verify_tpr_runtime, and a commit_push parity test all consume) reads the FROZEN tokens.json, not a live parse of the .md. python -m scripts.finding_categories freeze --check currently passes (259 tokens, in sync) but is wired into NEITHER lefthook.yml NOR scripts/pre_push_test_scope.py’s path-to-test mapping for either target file - confirmed empty output for both paths. Appending the 8 COMPILER:* subcats without running python -m scripts.finding_categories freeze afterward silently desyncs tokens.json, degrading /tpr-review’s reviewer category-awareness exactly the way BUG-07-229 (resolved) already did once for a different desync cause. §3D/§4/§9/§10 - the plan’s own w-b2bed01c verification list (plan_corpus check + prose-lint.py + test-all.sh) does NOT cover this; it needs one more command.
  3. The current catalog max ids match the plan’s own claim exactly - live grep confirms SPEC-66 / COVER-32 are still the maximum-numbered entries in impl-hygiene.md and the finding-categories.md COMPILER block still carries exactly 3 subcats (salsa-dependency-cycle, env-var-undocumented, config-flag-hygiene) matching SPEC-62/SPEC-63/COVER-26. The re-verify-before-write instruction in the section body is satisfied as of this dossier’s generation - re-run the grep at actual write time regardless (§2/§10), since other sessions can land rules concurrently.
  4. No plan or bug currently collides with this section’s write target. plan-status tracing-observability-hygiene shows 0 blockers, 0 open bugs; symbol-plans impl_hygiene/finding_categories show every OTHER referencing plan/bug already completed/resolved (none in-progress against these two files right now). §6.

DIG DEEPER

python -m scripts.plan_corpus.read plans/tracing-observability-hygiene
git -C /home/eric/projects/ori_lang log --oneline -1 e19204709
python -m scripts.finding_categories freeze --check

(full dossier: author-and-finalize—s-5ecbb585.intel.md)