96%

Intelligence Reconnaissance

Queries run 2026-04-17 (re-run 2026-04-18 after /review-plan editor pass):

  • scripts/intel-query.sh --human file-symbols "ori_arc/src/ir" --repo ori — inventory ArcFunction, ir/validate module surface before adding assert_no_unresolved_type_vars.
  • scripts/intel-query.sh --human callers "process_arc_function" --repo ori — CONFIRMED: two callers, emit_arc_function (define_phase.rs:164, immediate-emit path for tests/impls/inline-fallback) and prepare_arc_function (prepare.rs:208, two-pass prepare path for ordinary/mono bodies). Both converge at process_arc_function — this is the single upstream seam.
  • scripts/intel-query.sh --human callers "declare_and_process_lambda" --repo ori — CONFIRMED: two callers, compile_lambda_arc (define_phase.rs:243, immediate-emit lambda path) and prepare_lambda (prepare.rs:231, two-pass lambda path). Both converge at declare_and_process_lambda — this is the single lambda seam, distinct from process_arc_function because lambdas have their own run_arc_pipeline invocation at define_phase.rs:443 (NOT routed through process_arc_function).
  • scripts/intel-query.sh --human callers "prepare_mono_cached" --repo ori — blast radius for secondary-site B (pre-mono diagnostic localization).
  • scripts/intel-query.sh --human similar "validate type vars before codegen" --repo rust,swift,lean4 --limit 5 — cross-repo patterns for pre-codegen type-variable validation (Rust MIR TyContext debug_assert!, Swift SILVerifier per-function seam, Lean 4 Compiler/IR/RC.lean structural check).
  • scripts/intel-query.sh --human file-symbols "ori_types/check/validators" --repo ori — producer-side exemption pattern (build_exempt_var_ids, collect_first_unbound_var) that §04.1’s exempt_var_ids parameter mirrors.

Results summary (≤500 chars):

  • [ori] ArcFunction defined in ori_arc/src/ir/; no ir/validate module exists yet — this section creates it.
  • [ori] The real codegen seam is NOT the 4 consumer sites the prior plan version named — it is process_arc_function (define_phase.rs:315) + declare_and_process_lambda (define_phase.rs:375), which are the sole pre-run_arc_pipeline choke points.
  • [ori] Producer-side exemption via build_exempt_var_ids in ori_types/check/validators/mod.rs:161.
  • [rust] rustc_middle::mir uses debug_assert!(ty.is_fully_resolved()) at MIR visitor traversal boundaries.
  • [swift] SILVerifier runs per-function ownership + type checks before SIL optimization.
  • [lean4] IR/RC.lean places structural RC/IR checks at a single pipeline stage.

Context — Why This Section Exists

Sections 01–03 of this plan form the producer side of the typeck PC-2 phase contract (impl-hygiene.md §Cross-Phase Invariant Contracts, typeck.md §PC-2, types.md §PC-2):

SectionProducer-side responsibility
01Stop empty-list Tag::Var from being generalized in the first place (AST-based Value Restriction)
02Add a validator module in ori_types::check::validators that detects surviving Tag::Vars and emits E2005
03Wire the validator into the 4 bodies-pass call sites so every function body is checked before ARC IR lowering, PLUS end-of-body defaulting pre-pass for legitimate empty literals

Section 04 is the consumer side — a defense-in-depth backstop at the codegen seam.

  • codegen-rules.md §VR-1 mandates per-function LLVM IR verification after emission (gated by ORI_VERIFY_ARC); this section is the analogous gate one step earlier.
  • Before the ARC function is handed to ori_arc::run_arc_pipeline, verify that no Tag::Var index is present in ArcFunction.var_types.
  • If one is, something upstream (either the typeck bodies pass or the ARC lowerer itself) violated the impl-hygiene.md §Cross-Phase Invariant Contracts row:

Type Checker → Codegen | All type variables resolved | No Idx with Tag::Var in typed IR

codegen-rules.md §TR-2 states this invariant directly:

All type indices SHALL be fully resolved via pool.resolve_fully(idx) before LLVM type
construction. Unresolved type variables (Tag::Var) SHALL NOT reach codegen — their
presence is a type checker bug.

The Architectural Lesson from /review-plan Round 1 (2026-04-18)

Both dual-source reviewers (codex + gemini) converged on the same finding:

  • The prior version of §04 named 4 consumer sites (prepare_all_cached × 2, compile.rs mono loop, codegen_pipeline.rs mono loop).
  • That layering is wrong per impl-hygiene.md §Side Logic — a cross-phase invariant belongs at a single upstream choke point, not scattered across 4 downstream consumers that bypass the seam.

Specifically:

  1. Impl methods use the emit_arc_function immediate-emit path (impls.rs:88,151) — they bypass prepare_all_cached entirely. A 4-site plan misses them.
  2. Test wrappers use the same immediate-emit path — same miss.
  3. Inline fallback bodies (when a function is NOT in arc_cache) use lower_function_can
    • emit_arc_function — also bypass prepare_all_cached.
  4. Lambdas are separately compiled ArcFunctions that do NOT route through process_arc_function; they have their own run_arc_pipeline call in declare_and_process_lambda.

A 4-site hook set is fragile because every future refactor of the codegen entry points introduces new paths that silently bypass the assertion. The primary seam is process_arc_function + declare_and_process_lambda — every pre-emission path converges there, and both are immediately upstream of ori_arc::run_arc_pipeline (which mutates arc_func in place, so post-pipeline is too late).

Why Section 04 Depends on Sections 03 AND 08

  • The assertions added here are correct only if the producer side has fixed all legitimately-typed programs.
  • Before Section 03 lands, the bodies pass does not yet call the validator, so empty-list Tag::Vars that are valid program constructs (e.g. let x = [] where the element type is resolved later by an argument to the same function) may still survive into the ARC IR.
  • Enabling the assertion before Section 03 lands would produce spurious assertion failures on such programs.

Section 08 (poly-lambda BoundVar bleed, BUG-04-042) is ALSO a hard prerequisite — not merely a merge blocker.

  • The BoundVar bleed produces surviving Tag::Vars in monomorphized imported-generic bodies (assert_eq<T> over poly-lambda-containing modules).
  • §04’s assertion WILL fire on these programs until §08 resolves the bleed.
  • Both reviewers flagged this as a load-bearing dependency during the /review-plan Phase 2 blind-spots scan.

The dependency is therefore load-bearing in two dimensions: do not merge Section 04 before BOTH Section 03 AND Section 08 are merged AND test-all.sh is green. Track via this section’s depends_on: ["08"] frontmatter — the INV-19 single-predecessor linearization (00-overview.md HISTORY 2026-06-07) carries 08 as the sole chain predecessor; §02 + §03 remain load-bearing CONTENT dependencies, reachable transitively via the chain 02 -> 03 -> 05 -> 08 -> 04. Chain edges are ordering-only; content dependencies are transitive — do NOT re-add 02/03 as direct depends_on: edges.


04.1 — New ori_arc::ir::validate Module with Typed Error Shape

Motivation

The assertion helper must live in ori_arc rather than ori_llvm because:

  1. The check is about the ARC IR (ArcFunction.var_types: Vec<Idx>), which is owned by ori_arc. Placing validation logic in ori_arc keeps the cross-phase invariant with its owner crate — consistent with impl-hygiene.md §SSOT.
  2. ori_llvm is downstream of ori_arc in the dependency graph; a function in ori_arc can be called from both ori_llvm sites (JIT and AOT) AND future AIMS verification passes (e.g., the ORI_VERIFY_ARC=1 path) without introducing new cross-crate dependencies.
  3. The typed error enum UnresolvedTypeVar lives alongside ori_arc::verify::VerifyError (same crate), so integrating into the existing VerifyError variant set is a local change.

Files to Create / Edit

State at HEAD (verified via file inspection 2026-04-21): the §04.1 MODULE surface is ~85% landed; the §04.4 TEST BODY is the remaining §04.1 work. Table below is ground-truthed against actual file sizes and grep hits.

FileState at HEADActionApprox. LOC
compiler/ori_arc/src/ir/validate.rsDONE (7302B at HEAD; full assert_no_unresolved_type_vars + UnresolvedTypeVar per §04.1 spec)No further action this section
compiler/ori_arc/src/ir/validate/tests.rsSTUB (344B — //! Unit tests for assert_no_unresolved_type_vars. header only)CREATE body — 12-cell matrix + lambda-capture + first-violator-deterministic per §04.4~200
compiler/ori_arc/src/ir/mod.rsDONE (pub mod validate; present at line 25)No further action
compiler/ori_arc/src/lib.rsDONE (pub use ir::validate::{assert_no_unresolved_type_vars, UnresolvedTypeVar}; at line 92)No further action
compiler/ori_arc/src/verify/mod.rsDONE (UnresolvedTypeVar(crate::ir::validate::UnresolvedTypeVar) variant at line 83; From<UnresolvedTypeVar> impl at line 86)No further action
compiler/ori_types/src/check/validators/mod.rsDONE (pub fn build_exempt_var_ids(pool: &Pool, scheme_var_ids: &[u32]) -> FxHashSet<u32> at line 161 — visibility is already pub)No further action
compiler/ori_types/src/lib.rsDONE (pub use check::validators::{build_exempt_var_ids, validate_body_types}; at line 32)No further action

What still requires implementation (the actionable §04.1 tail):

  • Populate the compiler/ori_arc/src/ir/validate/tests.rs body with the 12-cell matrix (§04.4) + the test_lambda_with_tag_var_in_capture_environment_fails behavioral test. This is the only §04.1 work remaining — the module + error shape + re-exports + exempt-set helper are all already landed.

Rationale for leaving already-done items in the section as documentation: the module architecture decisions (typed error shape, UnresolvedTypeVar struct, re-export chain, build_exempt_var_ids visibility) are load-bearing for §04.2’s hook signatures and §04.3’s secondary-site contract. Stripping them would force §04.2/§04.3 implementers to re-derive the decisions. The HEAD-state column makes the drift visible without removing the architectural reference.

Implementation Outline

Canonical implementation: compiler_repo/compiler/ori_arc/src/ir/validate.rs.

Public symbols (re-exported from compiler_repo/compiler/ori_arc/src/lib.rs):

  • assert_no_unresolved_type_vars(pool, func, interner, exempt_var_ids) -> Result<(), UnresolvedTypeVar> — primary walker. Covers all type-bearing axes on ArcFunction: var_types[*], params[*].ty, return_type, blocks[*].params[*].1 (CFG block-param tuples), blocks[*].body[*] (instruction-operand Idxs on Idx-bearing ArcInstr variants per §04.S.2), and blocks[*].terminator (ArcTerminator::Invoke { ty } + InvokeIndirect { ty } per §04.S.2). Exhaustive match on both ArcInstr and ArcTerminator enums; no _ => () arm per impl-hygiene.md §IR Variant Exhaustiveness. Resolution via pool.resolve_fully(idx); exempts Tag::Var(Generalized) / Tag::Var(Rigid) IDs supplied via exempt_var_ids (mirrors ori_types::check::validators::build_exempt_var_ids).
  • assert_no_unresolved_idx(pool, idx, function) -> Result<(), UnresolvedTypeVar> (per §04.S.1) — thin helper for single-Idx call sites (e.g., derive_codegen per §04.S.4). Reports var_id: ArcVarId::INVALID since no owning SSA var exists at the call surface.
  • UnresolvedTypeVar { function: Name, var_id: ArcVarId, idx: Idx, tag: Tag } — typed error struct. Routed through ori_arc::verify::VerifyError::UnresolvedTypeVar(_) via From impl at compiler_repo/compiler/ori_arc/src/verify/mod.rs. Renders user-facing diagnostic via UnresolvedTypeVar::render(interner).

Internal closure: check_idx (private) — shared by both public helpers; encapsulates resolve_fully + tag-check + exempt-set lookup; returns the first violator deterministically (ArcVarId ascending).

Tests: compiler_repo/compiler/ori_arc/src/ir/validate/tests.rs (12-cell §04.4 matrix + lambda-capture + first-violator-deterministic + semantic pins) + compiler_repo/compiler/ori_arc/src/ir/validate/tests/body_walker.rs (§04.S.3 4-cell instruction-operand + terminator extension; submodule per pre-declared >500-line split).

Per [HYG-04.R-F09] symbol-anchored references replace line-number-based citations. Grep current symbols at HEAD via:

grep -n 'pub fn assert_no_unresolved_type_vars\|pub fn assert_no_unresolved_idx\|pub struct UnresolvedTypeVar' \
  compiler_repo/compiler/ori_arc/src/ir/validate.rs

Wire Into ori_arc::verify::VerifyError

Add a new variant to the existing VerifyError enum at compiler/ori_arc/src/verify/mod.rs (exact location verified via grep -n 'enum VerifyError' compiler/ori_arc/src/verify/ at implementation time):

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum VerifyError {
    // ... existing variants (UseBeforeDef, DanglingBlockRef, RcOnScalar,
    // DecOnBorrowed, ArgOwnershipLenMismatch, AbsentParamHasUses,
    // FipStructural, ...) ...
    /// A variable's type in `ArcFunction.var_types` is `Tag::Var` or
    /// `Tag::Projection` — a PC-2 invariant violation (see
    /// `ir::validate::UnresolvedTypeVar`). Wrapped so existing verification
    /// error handling in `process_arc_function` works unchanged.
    UnresolvedTypeVar(crate::ir::validate::UnresolvedTypeVar),
}

Re-Export From ori_arc

Add to compiler/ori_arc/src/lib.rs:

pub use ir::validate::{assert_no_unresolved_type_vars, UnresolvedTypeVar};

This makes the call sites in ori_llvm and oric as clean as ori_arc::assert_no_unresolved_type_vars(...) without needing the full path.


04.2 — PRIMARY Seam: process_arc_function + declare_and_process_lambda Hooks

Cross-section coordination (2026-04-20) — Before implementing this subsection’s seam, consult plans/typeck-inference-completeness/section-08-codegen-poly-lambda.md §08.6. §08.6 documents the caller-parameterized two-case exempt_var_ids contract: (a) mono path → empty set (strict typeck.md §PC-2); (b) pre-mono generic body path → populated from FunctionSig.scheme_var_ids (SC-1 exemption). §08.3’s remap-aware re-intern fix runs upstream of this seam; §08.3’s matrix (cells e1–e5) must make case (a) sound — zero Tag::Var at the seam for every mono instantiation — without regressing case (b). Any modification to this seam’s strictness must preserve both cases.

These are the LOAD-BEARING sites — the single upstream choke points through which every ARC-to-LLVM body flows. All other hooks (§04.3 secondary pre-mono sites) are diagnostic localization, NOT correctness gates.

§04.2 Design Decision: Seam Placement and exempt_var_ids Contract (Editor Pass 2026-04-21)

Two substantive reviewer positions on the primary-seam design surfaced during /review-plan Phase 2 (blind-spots scan). Editor resolves BOTH explicitly so §04.2 implementers do not re-litigate them:

Decision 1 — Seam placement: PRE-run_arc_pipeline is the correct gate for this section.

  • Context: Gemini flagged that AIMS injects Tag::Vars during pipeline passes (notably during generalization of polymorphic callees and during lattice analysis of erased-generic returns). Running the assertion BEFORE run_arc_pipeline misses those injected vars, per Gemini’s analysis.
  • Editor resolution: §04 enforces typeck.md §PC-2 (the TYPE-CHECKER→CODEGEN invariant) — NOT aims-rules.md soundness. typeck.md §PC-2 explicitly says: “No Tag::Var in any type-bearing IR position” at the typeck OUTPUT contract. This invariant is violated IFF the upstream typeck emitted an unresolved var — which is precisely what §04’s defense-in-depth assertion is designed to catch at the codegen entry seam (impl-hygiene.md §Cross-Phase Invariant Contracts).
  • AIMS-injected vars are a separate concern, NOT covered by shipped aims-rules.md VF-1/VF-2: if run_arc_pipeline itself produces a Tag::Var, that is an AIMS invariant violation (per canon.md §7.1 Invariant 5: “the unified model must stay unified”) — owned by arc.md + aims-rules.md §9 verification layers. HEAD’s aims-rules.md §9 VF-1 (line 714) checks structural well-formedness only — use-before-def, dangling block refs, RC on scalar, dec on borrowed, arg ownership length mismatch; no type-variable check. VF-2 (line 716) has only subcheck (a) AbsentParamHasUses shipped, with subchecks (b)/(c)/(d) target-only. Any Tag::Var surviving run_arc_pipeline is therefore an UNDETECTED class at HEAD — file via /add-bug (subsystem aims, scope “§9 VF-* extension for post-pipeline unresolved type variables”); §04’s pre-AIMS seam does NOT cover it.
  • Verification: the §04.2 pre-pipeline placement is correct for typeck PC-2. §04 is scope-limited to the typeck→codegen phase contract per its mission — post-AIMS unresolved-type-var detection is a separate verification-layer extension, not a §04 deliverable.
  • Citations: typeck.md §PC-2, canon.md §4.2, impl-hygiene.md §Cross-Phase Invariant Contracts (Type Checker → Codegen row), codegen-rules.md §TR-2, aims-rules.md §9 VF-1 (line 714 — structural scope) + VF-2 (line 716 — shipped subcheck inventory).

Decision 2 — exempt_var_ids at the primary seam: EMPTY is correct for Hook 1 and Hook 2.

  • Context: Codex said keep the seam architecture as-is with empty exempt_var_ids at the primary sites AND prove the empty-exempt invariant. Gemini said the set must be DYNAMIC (populated from FunctionSig.scheme_var_ids) because JIT/test paths bypass prepare_all_cached and retain non-empty scheme_var_ids on generic bodies.
  • Editor resolution: the primary seams (process_arc_function, declare_and_process_lambda) fire on ARC functions that are EITHER (a) monomorphized instances from the mono loop, OR (b) fully-resolved non-generic bodies. Both categories have empty scheme_var_ids at the seam by construction of the upstream lowering pipeline. Per canon.md §4.2, generic scheme bodies exit infer/body_finalize::normalize_body_generalized_to_bound_var_sig with Tag::BoundVar leaves — any residual Tag::Var(Generalized) at this point is a leak-alarm for a missed normalization position (§03 producer-side defect), NOT a legitimate state the seam should exempt.
  • Invariant pin: the §04.4 test matrix MUST include a test that passes a non-empty scheme_var_ids bag to build_exempt_var_ids and confirms the seam fires on ALL resulting Tag::Vars (because at the primary seam, post-monomorphization, no legitimate VarState::Rigid or VarState::Generalized survives — the set being empty in practice is the load-bearing invariant §04 enforces). This closes the hole Gemini identified: if a future refactor introduces a JIT/test path that DOES carry non-empty scheme_var_ids into the primary seam, the test matrix fails loudly rather than silently masking the drift.
  • Secondary sites (§04.3) ALSO empty — updated 2026-04-21 post-TPR-R2: §04.3 Sites A + B also use empty FxHashSet::default(). lower_and_infer_borrows at compiler/oric/src/test/runner/arc_lowering.rs:39 filters sig.is_generic() at :59/:81/:147/:207 before populating JIT arc_cache; compiler/oric/src/commands/codegen_pipeline.rs:92-94 filters generics before the AOT pre-mono loop; both AOT loops operate on entries with empty scheme_var_ids (non-generic tops or fully-substituted mono). No dynamic build_exempt_var_ids call is required at the shipping sites. The prior “§04.3 is dynamic” narrative was corrected after verification — earlier rounds’ fix landed the simplified stub (§04.3 Site A code block now uses FxHashSet::default() directly).
  • What this closes: Gemini’s concern about JIT/test paths bypassing prepare_all_cached — every JIT path filters generics upstream, so no bypass carries non-empty scheme_var_ids into any seam. The §04.4 matrix pin makes the empty-set invariant testable across primary + secondary sites.
  • Action for §04.2 implementer: keep the empty FxHashSet::default() at both primary hooks as documented in the existing Hook 1 and Hook 2 code blocks below. Add one #[test] to validate/tests.rs confirming that a non-empty scheme_var_ids bag passed to build_exempt_var_ids — and then to the seam — does NOT suppress firing (modeling the JIT/test path bypass Gemini flagged). This test is the semantic pin for the “empty set is invariant” decision.
  • Citations: typeck.md §PC-2, canon.md §4.2, types.md §SC-1 (scheme/BoundVar migration), impl-hygiene.md §Invariant Explicitness (explicit tests over implicit invariants), impl-hygiene.md §Cross-Phase Invariant Contracts.

File: compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs

Hook 1: process_arc_function (line ~315)

Insert the assertion at the TOP of process_arc_function, BEFORE the debug tracing call and BEFORE ori_arc::run_arc_pipeline is invoked. The AIMS pipeline mutates arc_func in place (borrow annotations, RC insertion, reuse emission); the assertion must run on the pre-pipeline IR.

pub(super) fn process_arc_function(
    &mut self,
    name: Name,
    arc_func: &mut ori_arc::ArcFunction,
) -> Result<(), VerifyError> {
    // PC-2 contract check — see plan `typeck-inference-completeness` §04.
    // Runs ALWAYS (debug + release) — NOT gated by self.verify_arc because
    // phase-contract enforcement is mandatory per CLAUDE.md §The One Rule
    // (no debug_assert! fail-open). The verify_arc flag gates ADDITIONAL
    // downstream verification (fn_val.verify, AIMS oracle), not this gate.
    //
    // `exempt_var_ids` is empty for non-generic functions. Generic functions
    // reach this seam only after monomorphization, at which point their
    // scheme_var_ids are fully substituted — empty set is correct.
    let exempt: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();
    if let Err(err) = ori_arc::assert_no_unresolved_type_vars(
        self.pool, arc_func, self.interner, &exempt,
    ) {
        tracing::error!(
            contract_violation = true,
            error = ?err,
            "Tag::Var in ARC IR violates PC-2 contract \
             (impl-hygiene.md §Cross-Phase Invariant Contracts, \
             codegen-rules.md §TR-2)"
        );
        self.builder.record_codegen_error();
        // Return Err so the caller (emit_arc_function / prepare_arc_function)
        // skips LLVM emission. `record_codegen_error` alone is INSUFFICIENT —
        // it only increments a counter (compiler/ori_llvm/src/codegen/
        // ir_builder/mod.rs:269); the caller's emission path does NOT check
        // that counter. Explicit Result propagation is the only reliable
        // no-emit contract per impl-hygiene.md §Invariant Explicitness.
        return Err(VerifyError::UnresolvedTypeVar(err));
    }

    // ... existing body: AIMS param ownership, run_arc_pipeline, etc. ...
    Ok(())
}

The Result return is load-bearing: continuing into run_arc_pipeline on a contract- violating input risks panics inside AIMS analysis (it assumes resolved types), AND the caller’s emission path would otherwise continue unconditionally past process_arc_function and call ArcIrEmitter::emit_function on the unpiped IR. Skipping at BOTH levels — within this function AND in the caller on Err — is the correct failure mode.

Hook 1 caller-site updates — mandatory co-change (TPR-04-R4-001 + TPR-04-R5-001)

The full Hook 1 cascade chain — every caller between process_arc_function and the outermost JIT/AOT batch entry points — MUST be updated in the same commit. The cascading Result propagation is the SAME ARCHITECTURAL PATTERN as Hook 2’s lambda cascade; both seams must skip downstream emission via explicit Result return because record_codegen_error() at compiler/ori_llvm/src/codegen/ir_builder/mod.rs:269 is counter-only and has no suppression side effect.

Concrete caller chain (verified via grep -rn 'emit_arc_function\|define_function_body_arc_with_subst\|process_arc_function\|prepare_arc_function' compiler/ori_llvm/src/ at HEAD 5f1beb20; shift tolerance on line numbers):

LevelSiteCurrent signatureRequired change
0process_arc_function (define_phase.rs:~315)fn(&mut self, Name, &mut ArcFunction)-> Result<(), VerifyError> (§04.2 Hook 1 primary)
1aemit_arc_function (define_phase.rs:~115)fn(&mut self, Name, FunctionId, &FunctionAbi, ArcFunction, Vec<ArcFunction>)-> Result<(), VerifyError> via ? on process_arc_function. On Err, MUST call self.exit_debug_scope() before returning to match the normal-path exit_debug_scope() at define_phase.rs:~220; otherwise the debug scope entered by define_function_body_arc_with_subst (define_phase.rs:80) leaks. Use a scope-guard helper OR an explicit match … { Err(e) => { self.exit_debug_scope(); return Err(e); } } (TPR-04-R5-002).
1bcompile_lambda_arc (define_phase.rs:~243)unary-tuple return-> Result<…, VerifyError> (Hook 2 lambda cascade — already specified); must also propagate parent-seam failures when its own emit_arc_function chain fires.
2adefine_function_body_arc_with_subst (define_phase.rs:~67)fn(…)-> Result<(), VerifyError> via ? on emit_arc_function. exit_debug_scope cleanup lives one level DOWN (in emit_arc_function) so this level just propagates.
2a’define_function_body (define_phase.rs:47)fn(&mut self, Name, FunctionId, &FunctionAbi, CanId, &CanonResult, bool)-> Result<(), VerifyError> via ? on define_function_body_arc_with_subst. Pure wrapper (delegates at define_phase.rs:56); propagation only. Omitted from the Round 5 table; §04.2 implementation MUST carry the signature change through this wrapper or the ? in 2a does not compile.
2bcompile_tests branches (impls.rs:88, impls.rs:151)On Err, use continue to skip to the next test iteration WITHOUT altering compile_tests’s return signature. The per-test failure is already recorded via record_codegen_error() and the suite continues. This mirrors Gemini R5-001’s recommendation: not every outer caller must change signature — some outer-loop callers can absorb Err via continue or let _ = when their loop semantic is “keep going past individual failures”.
2ccompile_impl_method_from_sig (impls.rs:241)fn<'sig>(…) returning ()Helper, not a loop. Its body has early-return guards (sig-iter exhaustion, sig.is_generic()) and calls self.define_function_body(...) as the final statement at impls.rs:321. Simplest shape: keep the unit return and absorb Err at the call site — let _ = self.define_function_body(...); — OR change to -> Result<(), VerifyError> and propagate via ?. The continue-on-Err handling belongs in the two CALLER loops: impls.rs:199 is the explicit-method loop for method in &impl_def.methods; impls.rs:221 is the default-method loop nested inside for item in &trait_def.items { if let TraitItem::DefaultMethod(default) = item { ... } }. Both are caller-level loops, but only the first iterates impl_def.methods. Per-method failure is recorded via record_codegen_error() through the 1a exit_debug_scope path regardless of the chosen shape.
3prepare_arc_function (nounwind/prepare.rs)existing Hook 2 cascadeAlready cascades to prepare_all_cached / prepare_mono_cached per Round 2’s fix; now ALSO propagates Hook 1 Err via ? on the process_arc_function call. No new callers above this level — the Round 2 cascade already covers them.
4JIT batch evaluator/compile.rs + AOT batch oric/src/commands/codegen_pipeline.rsexistingPer the two-level cascade from Round 2: these already track per-function failures via record_codegen_error() counter. With Hook 1’s Result cascade landed, the recorded failures now correspond to Err paths that also skipped emission — the counter stays the SSOT for end-of-batch pass/fail classification.

continue-on-Err pattern (TPR-04-R5-001’s compile_tests case):

  • When a caller’s loop semantic is “keep going past individual failures and report all at the end”, continue on Err is the correct pattern — it does NOT require the caller to change its own signature.
  • The record_codegen_error() counter already tracks aggregate failures for end-of-batch reporting.
  • Callers whose semantic is “stop emitting if ANY subcomponent fails” (e.g., define_function_body_arc_with_subst — a single function’s body, emit-or-skip) MUST propagate via ?.
  • The distinction is per-caller: loop semantic → continue; single-function semantic → propagate.

Verifiable post-implementation:

  • grep -rn 'process_arc_function\b' compiler/ori_llvm/src/codegen/function_compiler/ returns the two invocation sites with adjacent ? or match … { Err(_) => … }.
  • grep -rn 'emit_arc_function\b' compiler/ori_llvm/src/ returns the three invocation sites (define_phase.rs:106 + impls.rs:88 + impls.rs:151), each with adjacent ? OR continue OR explicit match (not a bare unary call).
  • Any emit_arc_function signature change to -> Result<…> forces clippy::must_use to catch unhandled call sites at compile time.

Hook 2: declare_and_process_lambda (line ~375)

Insert the assertion at the TOP of declare_and_process_lambda — analogous placement to Hook 1, before run_arc_pipeline at line ~443. Lambdas do NOT route through process_arc_function; they are a distinct seam.

pub(super) fn declare_and_process_lambda(
    &mut self,
    lambda: &mut ori_arc::ArcFunction,
) -> Result<(Name, FunctionId, FunctionAbi), VerifyError> {
    // PC-2 contract check for lambdas — same pattern as process_arc_function.
    // Lambdas have their own run_arc_pipeline call (line ~443) and do NOT
    // route through process_arc_function.
    //
    // Explicit no-emit control path (TPR-04-R0-003): the signature returns
    // Result<(Name, FunctionId, FunctionAbi), VerifyError> so every caller
    // MUST match on the result and early-return its own error path. The
    // prior implicit "record_codegen_error suppresses downstream emission"
    // contract relied on a transitive invariant across four callers that
    // `impl-hygiene.md §Invariant Explicitness` forbids — a future refactor
    // of any caller could silently land LLVM IR from a contract-violating
    // lambda. Making the failure path explicit closes that regression
    // surface.
    let exempt: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();
    if let Err(err) = ori_arc::assert_no_unresolved_type_vars(
        self.pool, lambda, self.interner, &exempt,
    ) {
        tracing::error!(
            contract_violation = true,
            error = ?err,
            "Tag::Var in lambda ARC IR violates PC-2 contract"
        );
        self.builder.record_codegen_error();
        return Err(VerifyError::UnresolvedTypeVar(err));
    }

    // ... existing body: apply AIMS contracts, declare LLVM function, etc. ...
    // On success, return Ok((name, function_id, function_abi)) from the
    // existing tail.
}

Soundness argument (TPR-04-R0-003 explicit-contract rewrite):

  • The Result return is load-bearing.
  • Each of the two direct callers (compile_lambda_arc at compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs:243 and prepare_lambda at compiler/ori_llvm/src/codegen/function_compiler/nounwind/prepare.rs:231) MUST match on the Result and early-return on Err BEFORE calling run_arc_pipeline or ArcIrEmitter or any LLVM emission path.
  • The emission paths inside compile_lambda_arc / prepare_lambda that subsequently invoke run_arc_pipeline / ArcIrEmitter are on the success arm of each caller’s match — transitively owned by the same Err gate, not distinct sites.
  • Replaces the prior implicit “record_codegen_error suppresses downstream emission” transitive invariant — a property that was not local to the lambda hook and could regress silently if either caller’s emission path were refactored.
  • The explicit Err arm makes the no-emit contract local and testable: a unit test per §04.4 confirms that each caller’s Err handling skips LLVM emission, and clippy::must_use_result on the return type makes an ignored result a compile error.
  • VerifyError::UnresolvedTypeVar(_) is the existing enum variant §04.1 adds; this hook reuses it for zero error-path proliferation.

§04.2 caller-site updates — mandatory co-change with the Hook 2 signature change

The two direct callers of declare_and_process_lambda MUST be updated in the same commit as the hook itself (the Result return type makes this a hard compile-time requirement — clippy + must_use enforce it, there is no way to ship a half-converted tree):

  • compile_lambda_arc at compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs:243 (immediate-emit lambda path): match on the Result; on Err, propagate to the enclosing emission-skip path that record_codegen_error() already establishes — do NOT call run_arc_pipeline / ArcIrEmitter on that arm.
  • prepare_lambda at compiler/ori_llvm/src/codegen/function_compiler/nounwind/prepare.rs:231 (two-pass lambda path): same — on Err, skip the pipeline + emitter calls downstream. Because prepare_lambda currently has signature fn(…) -> PreparedLambda and its only call site is prepare_arc_function at nounwind/prepare.rs:190 (verified via grep -n 'prepare_lambda' compiler/ori_llvm/src/codegen/function_compiler/nounwind/prepare.rs), the Err propagation cascades TWO levels further: change prepare_lambda’s signature to fn(…) -> Result<PreparedLambda, VerifyError> AND change prepare_arc_function’s signature to match (fn(…) -> Result<…, VerifyError>), propagating up to prepare_all_cached + prepare_mono_cached callers that already track per-function success/failure via record_codegen_error(). Filter-out is NOT sound — dropping a failed lambda from the prepared_lambdas: Vec<PreparedLambda> collection at lines 186–196 leaves the parent arc_func to later emit a PartialApply against the removed lambda’s original name (the remap_partial_apply_names call at line 201 rewrites names but does not drop references to missing callees). Parent emission MUST also be skipped when any of its lambdas fails validation, matching the immediate-emit path compile_lambda_arc + emit_arc_function. The cascading signature change is mandatory at every level — clippy::must_use on the new Result return forces the propagation at compile time. Analogous cascading treatment applies to compile_lambda_arc (immediate-emit path at define_phase.rs:243): its caller emit_arc_function must also receive the Err and skip parent emission before calling run_arc_pipeline on the parent arc_func.

No other direct call sites of declare_and_process_lambda exist (verified via grep -rn 'declare_and_process_lambda\b' compiler/ori_llvm/src/ — only the two helper references and the two invocation sites enumerated above). Verifiable post-edit via the same grep returning exactly TWO invocation lines and each adjacent line showing a ? operator or explicit match … { Err(_) => return … } — NOT a bare unary call expression.

Why NOT place at run_arc_pipeline entry

The check could also be moved INTO ori_arc::run_arc_pipeline as a precondition. We reject that placement because:

  1. The ori_arc crate must not emit tracing::error! directly — diagnostic surfacing is the driver’s responsibility (impl-hygiene.md §Side Logic). Pushing the check inward would require a new error channel out of run_arc_pipeline, duplicating the existing VerifyError path.
  2. The driver (ori_llvm) has context about WHICH codegen entry point is running (JIT vs AOT, direct vs mono), which informs the diagnostic — ori_arc::run_arc_pipeline does not.
  3. The existing VerifyError plumbing flows OUT of run_arc_pipeline; keeping the new UnresolvedTypeVar variant flowing IN the same direction preserves the SSOT for error shape.

§04.2 post-landing forward-verification (absorbed from §08.6, 2026-04-20)

These items are absorbed from §08.6’s forward-coordination check set to eliminate a same-plan self-blocker (§08.6 → §04.2) per CLAUDE.md §Plan-Blocker Bugs Belong IN the Plan. §04.2 naturally owns “the seam fires correctly against BOTH the intra-module lambda_mono path and the cross-module re-intern path from §08.3” as part of its own completion — it is not a §08 obligation, it is the deliverable of the seam itself.

  • Post-substitution firing verification (both paths): after §04.2’s assert_no_unresolved_type_vars hooks are inserted at define_phase.rs:315 (process_arc_function) and :375 (declare_and_process_lambda) AND §08.3’s remap-aware re-intern is live in the merged pool, verify the assertion fires POST-substitution for (a) the intra-module lambda_mono path via resolve_all_lambda_bound_vars at define_phase.rs:134 + nounwind/prepare.rs:173, and (b) the cross-module re-intern path via pool/re_intern/. Record the confirmation in §04.R close-out and backlink §08.6.R as “§04 seam order verified correct under §08.1.R corrected diagnosis; no change required.” Seam-line-number shifts are acceptable as long as the POST-substitution invariant holds. Verified 2026-04-21 via code inspection of HEAD (unstaged dirty tree with Hook 1 + Hook 2 landed). (a) Intra-module lambda_mono path: emit_arc_function_inner at define_phase.rs:138 calls resolve_all_lambda_bound_vars(&mut arc_func, &mut lambdas, self.pool, self.interner, classifier) at :157-163 BEFORE the lambda compilation loop at :166-173 (which invokes Hook 2 via compile_lambda_arcdeclare_and_process_lambda at :253-254) and BEFORE Hook 1 at process_arc_function invocation at :187. Nounwind batch path: prepare_arc_function at nounwind/prepare.rs:186 calls resolve_all_lambda_bound_vars at :208 BEFORE the lambda-prepare loop at :215-222 (which invokes Hook 2 via prepare_lambdadeclare_and_process_lambda at :262) and BEFORE Hook 1 at :237. Both paths are POST-substitution; line-number shifts (:134:157, :173:208, :315→Hook 1 landed at process_arc_function fn start, :375→Hook 2 landed at declare_and_process_lambda fn start) are within the plan’s “acceptable as long as the POST-substitution invariant holds” envelope. (b) Cross-module re-intern path: pool/re_intern/ runs during pool construction (upstream of codegen entirely per types.md §TY-6 exception); Hook 1/2 see post-re-intern types natively.
  • Empty-exempt assertion strictness holds under §08.3 remap: validate that assert_no_unresolved_type_vars’s empty-exempt_var_ids contract at all shipping sites (§04.2 primary seam + §04.3 secondary Sites A/B) remains sound after §08.3’s remap-aware re-intern lands. Strict typeck.md §PC-2 + canon.md §4.2 rule: §08.3 + resolve_all_lambda_bound_vars + upstream generic filters (lower_and_infer_borrows / codegen_pipeline.rs:92-94) together must leave zero Tag::Var at every call site; a surviving Tag::Var is a §08.3 completeness bug or an upstream-filter regression. §08.3’s matrix cells e1–e5 must preserve this invariant. The exempt_var_ids parameter stays in the validator signature as a defense-in-depth hook — a future call site that DOES need dynamic exemption (e.g. non-generic-filtered pre-mono path not yet shipped) can populate it from FunctionSig.scheme_var_ids without breaking the contract. Record the verification in §04.R as “empty-exempt seam strictness holds under §08.3 remap + upstream generic filters; §08.3’s cell coverage adequate.” Verified 2026-04-21 via code inspection. Hook 1 (process_arc_function) and Hook 2 (declare_and_process_lambda) both use let exempt: FxHashSet<u32> = FxHashSet::default(); (empty) at define_phase.rs:379 and :452 respectively. The empty-set invariant is load-bearing because: arc_cache entries at §04.3 sites are either (1) non-generic top-level functions (filtered at codegen_pipeline.rs:92-94 via sig.is_generic() { continue; } before insertion) or (2) imported monomorphized instances (fully substituted before insertion). Both categories have empty FunctionSig.scheme_var_ids or no scheme binder; build_exempt_var_ids(pool, &[]) returns FxHashSet::default(). Post-§08.3 remap preserves the substitution relation — no new Tag::Vars are introduced, only existing ones are re-indexed under the merged pool. §04.2.B’s 3-level generic chain leak is upstream-substitution incomplete (not a contract violation); the seam correctly fires. Test pin test_primary_seam_empty_exempt_set_invariant_pin at compiler/ori_arc/src/ir/validate/tests.rs (editor-added 2026-04-21) codifies the empty-set load-bearing contract.

04.2.B — BLOCKER: Upstream Tag::Var Leak on 3-Level Generic Chain (generics::test_generic_chain_three_levels)

Status: effectively complete. All implementation work for §04.2.B’s stated deliverable (root-extension fix at the 3 mono call sites + matrix coverage) landed in 8a7e9040. The two remaining unchecked - [ ] items in §2 TDD Matrix (test_generic_chain_list_element blocked by BUG-04-090; test_generic_method_on_generic_type blocked by BUG-04-091 + BUG-01-002 + BUG-08-015) cannot resolve until those external bugs land — they are independent codegen / parser surface defects, NOT §04.2.B regressions. Per CLAUDE.md §“Plan blockers stay in plan” classifier: these are NOT plan blockers (the plan completes without them via the 17 other matrix cells already green); they belong to their own bug-tracker entries. §04.2.B closed complete despite the 2 blocked-by-external-bug rows; SUB_STATUS_DRIFT is acknowledged-and-accepted (plan-audit minor finding).

Status: complete (Phase 1 complete, Phase 1.5 complete, Phase 1.75–Phase 5 complete; closed via §N row-flips in 8a7e9040 + follow-up commits)
Classification: B — blocker surfaced by §04.2’s PC-2 assertion hook (working as designed).
Severity: high (reclassified 2026-04-21 Phase 1.5) — complexity-elevated subsystem (ori_types mono + ori_arc ARC lowering + ori_llvm codegen), cross-crate visibility, systemic blast radius (every 3+ hop generic chain where intermediate callees receive type-variable args), blocker to §04.2 + §04 + §04.N close-out.
Scope: point fix — 1-2 files, <30 LOC, no architectural change. Extract-or-inline-call of existing extend_var_subst_with_roots at the deferred-mono-resolve site in check/exports.rs.
Blocks: §04.2 close-out + §04.A entry. §04 cannot complete until §04.2.B lands.
Surfaced by: §04.2’s assertion at compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs fired during post-implementation test-all.sh run (2026-04-21 HEAD).

Repro

timeout 150 cargo test --test aot generics::test_generic_chain_three_levels 2>&1

Both variants fail (test_generic_chain_three_levels + test_generic_chain_three_levels_string) with:

ERROR ori_llvm::codegen::function_compiler::define_phase:
  Tag::Var in ARC IR violates PC-2 contract
  contract_violation=true
  error=UnresolvedTypeVar {
    function: Name(shard=2, local=19),    # or shard=8, local=20 for _string variant
    var_id: ArcVarId(2),
    idx: Idx(220),
    tag: Tag::var
  }
error[E5001]: LLVM module verification failed

Baseline context

  • Baseline (pre-§04.2, commit 58c26963): both tests passed despite the surviving Tag::Var in ARC IR. The pass was accidental — LLVM codegen produced either coincidentally-correct output OR output the test’s assertions did not discriminate. State.sh aot_integration: 2161/0/22 included both tests as passing.
  • Post-§04.2 (HEAD): assertion fires at process_arc_function seam, codegen emission skipped, AOT compile returns error. 2 tests fail; remainder of suite at 2159/2/24 (net delta: 2 new failures, assertion working as designed).

Phase 1 investigation findings (2026-04-21)

Fixture reality-check — the program is NOT a 3-level nested type. The failing fixtures compiler/ori_llvm/tests/aot/fixtures/generics/generic_chain_three_levels{,_string}.ori are 3-hop identity-like generic call chains, NOT Applied<Applied<Applied<T>>> shapes:

@id <T> (x: T) -> T = x;
@wrap <T> (x: T) -> T = id(x: x);
@double_wrap <T> (x: T) -> T = wrap(x: x);
@main () -> int = { let n = double_wrap(x: 42); ... }

This exercises the monomorphization discovery chain (mono engine’s ability to cascade substitution across 3 generic function instantiations with rigid-to-rigid param flow), NOT a nested-type remap matrix. The hypothesis list above assumed the wrong shape.

Leak is present AT typeck exit, not introduced downstream. ORI_DUMP_AFTER_TYPECK=1 on the repro shows:

Function @wrap<T> (x: $b6) -> $b6              ← signature correctly BoundVar
  CallNamed : $t8 (unresolved)                 ← body: Tag::Var($t8) leaked
    Ident(id) : ($t8) -> $t8                   ← instantiated scheme, never link-resolved
Function @double_wrap<T> (x: $b7) -> $b7
  CallNamed : $t9 (unresolved)                 ← body: Tag::Var($t9) leaked
    Ident(wrap) : ($t9) -> $t9

Typeck reports “0 errors” — validate_body_types does NOT fire E2005 on $t8/$t9. Same shape exists in 2-hop @wrap → @id (dumped from /tmp/gen_chain_2.ori), confirming the leak is not 3-level-specific at typeck exit.

Validator exemption is intentional. compiler/ori_types/src/check/validators/mod.rs:161-173 build_exempt_var_ids exempts $t7/$t8/$t9 because rank-weighted union-find (typeck.md §UN-7) can make a fresh instantiation var the root of a scheme var’s equivalence class. The validator resolves each scheme_var_ids[i] through resolve_fully, and if the root is still Tag::Var, adds the root’s var_id to the exempt set. This is per-design and covered by regression test scheme_var_root_is_fresh_instantiation_var_no_false_e2005 at validators/tests.rs:441. The exempt Tag::Var leaves in expr_types are equivalent (via union-find) to scheme vars — they MUST be substituted to concrete types at monomorphization time, but legitimately survive typeck.

Contract: body expr_types carry Tag::Var leaves that are union-find-linked to scheme vars. Downstream consumers (mono engine, ARC lowering, codegen) must either (a) call resolve_fully at each walk step of body types before substitution, or (b) trust that substitute_in_pool with a {scheme_var_root.var_id → concrete} mapping rewrites every such leaf. §04.2.B fails because this contract is not fully honored at 3 levels.

Why 2-hop accidentally passes and 3-hop breaks (hypothesis, needs verification in Phase 1.75 /tp-help): At mono-time substitution for @apply_identity<int> (2-hop), the substitution pipeline produces a body whose $t7 leaf either (a) gets resolved through resolve_fully via direct union-find link to the outer scheme var’s root AND the root’s var_id appears in the substitution map, OR (b) the LLVM codegen path doesn’t observe the residual Tag::Var because test_generic_calling_generic’s output assertions don’t discriminate the wrong value. For 3-hop, @double_wrap<int>@wrap<int> adds a second layer where $t8 (inside @wrap) is NOT linked to @double_wrap’s outer scheme var — it is linked to @wrap’s OWN scheme var, which is only substituted to int when @wrap<int> is monomorphized. If substitute_in_pool doesn’t call resolve_fully at each walk step, $t8 survives as a raw Tag::Var in @wrap<int>’s realized ARC IR, and process_arc_function at §04.2’s hook fires UnresolvedTypeVar.

Root cause — PINPOINTED to line numbers (Phase 1 code-reading round 2, 2026-04-21):

The bug is an asymmetry between the two monomorphization paths in ori_types. Both paths ultimately call build_mono_body_type_map + substitute_in_pool, but only one calls extend_var_subst_with_roots on the substitution map beforehand:

  • Non-deferred path (maybe_record_mono_instance at compiler/ori_types/src/infer/expr/calls/monomorphization.rs:17-121): the call-site mono when @main directly requests double_wrap<int> with a concrete type arg. Line 71 invokes extend_var_subst_with_roots (monomorphization.rs:279-302) which adds {union_find_root_var_id → concrete} entries for every scheme var whose equivalence-class root differs from the declared var id. Only after that extension does build_mono_body_type_map run at line 98.

  • Deferred path (resolve_deferred_mono_calls loop at compiler/ori_types/src/check/exports.rs:180-247): the resolve-pass that picks up recorded DeferredMonoCall entries when the caller is monomorphized. Line 180-205 builds resolved_var_subst with callee’s scheme_var_ids only (resolved_var_subst.insert(*callee_var_id, concrete) at line 204) and calls build_mono_instance(pool, ..., &resolved_var_subst) at line 231 — with NO equivalent of extend_var_subst_with_roots. Inside build_mono_instance at exports.rs:257-288, build_mono_body_type_map at line 277 walks the pool with this non-extended map.

Walk-through for the failing repro:

  1. @main calls double_wrap(x: 42). maybe_record_mono_instance enters the non-deferred branch (concrete arg). var_subst = {double_wrap_scheme_var_id → int} + extend_var_subst_with_roots adds {double_wrap_root_var_id → int} if the root differs. MonoInstance for double_wrap<int> recorded correctly.
  2. Typeck of @double_wrap’s body (runs earlier, at function-definition time) encounters wrap(x: x) where x: T_double_wrap (still a type variable). maybe_record_mono_instance for wrap at this call site: has_unresolved_vars = true (the arg type is a Tag::Var). Enters deferred branch at line 57-67, calls record_deferred_mono_call. The deferred entry stores {wrap_scheme_var_id → CallerSchemeVar(0)} mapping wrap’s T to double_wrap’s T position.
  3. Later, at export time, resolve_deferred_mono_calls walks the deferred list. For the wrap entry, it looks up double_wrap’s mono instances: double_wrap<int> → caller_generic_args=[Type(int)]. Resolves the deferred binding: resolved_var_subst.insert(wrap_scheme_var_id, int) at line 204. Calls build_mono_instance at line 231 with this map.
  4. Inside build_mono_instance, build_mono_body_type_map walks the pool. For wrap’s body expression types (stored in expr_types at typeck time), the body contains CallNamed: $t7_wrap_body where $t7_wrap_body is a fresh instantiation var allocated when typing id(x: x) inside @wrap’s body.
  5. Rank-weighted union-find made $t7_wrap_body the root of @wrap’s scheme var T equivalence class (wrap.T linked TO $t7_wrap_body, not vice-versa). substitute_var($t7_wrap_body):
    • var_id = 7 — not in map (map has wrap_scheme_var_id, which is a different u32).
    • VarState::Unbound (because $t7_wrap_body is the root; no Link to follow).
    • Falls through, returns $t7_wrap_body unchanged.
  6. wrap<int>’s mono’d body carries a raw Tag::Var($t7_wrap_body). ARC lowering preserves it (ARC doesn’t re-resolve types — canon.md §4.2 PC-2 guarantees clean IR arriving). process_arc_function at §04.2’s seam fires UnresolvedTypeVar { var_id: ArcVarId(2), idx: Idx(220), tag: Tag::Var }.

Why 2-hop apply_identity<int> accidentally passes pre-§04.2: the non-deferred path runs for it (concrete arg at the call site). extend_var_subst_with_roots fires. The body’s $t7 gets substituted to int. No residual Tag::Var. No assertion fires.

Why 3-hop fails: the MIDDLE layer (wrap) takes the deferred path, which misses the root extension.

Affected surface:

  • Any 3+ hop generic call chain where intermediate callees receive type variables (not concrete types) as generic args. This is EVERY non-trivial generic composition, not just the failing fixtures.
  • Currently failing (post-§04.2): test_generic_chain_three_levels + test_generic_chain_three_levels_string.
  • Currently passing but likely silently miscompiling (pre-§04.2 undiagnosed, now surfaced with §04.2’s PC-2 assertion active): unknown; to be probed by the TDD matrix in Phase 3.

Fix sites (ranked):

  1. Primary (typeck-side deferred path): compiler/ori_types/src/check/exports.rs — add root-extension call between line 205 (end of resolved_var_subst build) and line 231 (call to build_mono_instance). This is the symmetry fix on the side that today misses root extension entirely.
  2. Primary (test-runner JIT imported-mono path): compiler/oric/src/test/runner/imported_mono.rs:109-110 — a THIRD call site carrying the same defect, surfaced by TPR Round 0 2026-04-21. The runner builds var_subst from generic_sig.scheme_var_ids at lines 83-88, then calls build_mono_body_type_map directly at line 110 with NO root extension. Same asymmetry class as exports.rs. Without this fix, imported generic JIT compilation silently miscompiles or fires the §04.2 PC-2 seam assertion on any imported generic where the callee’s scheme var is not the union-find representative. Fix: insert extend_var_subst_with_roots(merged_pool, &generic_sig.scheme_var_ids, &mut var_subst) between lines 104 and 110.
  3. Refactor: extract extend_var_subst_with_roots from monomorphization.rs:279-302 into a shared helper in ori_types::pool::substitute (or on Pool as an inherent method) so ALL THREE call sites — eager typeck, deferred typeck, JIT imported-mono — call the same SSOT implementation. Per impl-hygiene.md §Algorithmic DRY, the 2-instance threshold is already crossed; with 3 instances the extraction is non-negotiable.

Alternative (rejected): modify substitute_var:90-105 in pool/substitute/mod.rs to resolve_fully at entry. Wider-reaching, affects ALL substitution call sites (not just mono), could unintentionally alter behavior for other consumers (e.g., default_unbound_vars_in_scope uses substitute_in_pool).

Phase 1 status: complete. Repro confirmed, leak-site pinpointed, fix-site identified to specific line numbers, architectural symmetry principle established (both mono paths must use the same substitution-extension logic). Ready for Phase 1.5.

Success criteria

  • Root cause phase narrowed (pool/substitute or llvm/monomorphize — final selection at Phase 1.75 /tp-help consensus).
  • Root cause function identified (specific offending walk step or missing resolve_fully call).
  • Fix implemented at the correct upstream site (NOT in §04.2’s assertion — the assertion MUST continue to fire if the underlying leak recurs; weakening it is INVERTED-TDD per CLAUDE.md).
  • cargo test --test aot generics::test_generic_chain_three_levels passes (both variants).
  • timeout 150 ./test-all.sh returns green (or same-baseline: same known-failing set, no new failures vs state.sh at close-of-fix HEAD). Verified at commit 8a7e9040 (dev) — 843 interp failures match §06.2 E2005:AmbiguousType baseline exactly; +3 LLVM runtime failures predate this commit (caused by the already-committed 3f7d85f5 root-cause fix unblocking ~541 prior-LC_FAIL tests from passed 1851 → 2392); no interpreter regression.
  • Matrix test added at the fix’s owning plan section (§03 or §08 or wherever the root cause lives) exercising Applied<Applied<Applied<T>>> / 3-level-chain shape; positive + negative pin per CLAUDE.md §Matrix Testing Rule. Landed in 8a7e9040 across compiler/ori_types/src/pool/substitute/tests.rs (4 unit tests on the extracted helper), compiler/ori_types/src/check/integration_tests.rs (3 deferred-mono integration tests — 3-hop, 4-hop, multi-param forwarding), compiler/ori_llvm/tests/aot/generics.rs (15 AOT tests + 2 ignored with concrete blockers), compiler/ori_arc/src/ir/validate/tests.rs (positive + negative PC-2 pins — test_pc2_assertion_fires_on_synthetic_leak / test_pc2_assertion_silent_on_clean_function), tests/spec/imports/generic_import_chain.ori (3 imported-mono 3-hop tests passing on both backends).
  • Imported-mono JIT path covered: tests/spec/imports/generic_import_chain.ori (shipped 8a7e9040) exercises 3-hop imported-generic chains where the callee’s scheme var is not the union-find representative. Tests test_imported_mono_chain_3hop_int, test_imported_mono_chain_3hop_str, test_imported_mono_chain_3hop_bool all green on both interpreter and LLVM backends, confirming File 4 of the fix (per TPR-04.2.B-F1-codex).
  • §04.2 status flipped to complete after §04.2.B success criteria all [x] AND §04.A clean.
  • This subsection’s backlink to owning plan section recorded. Owning section: §03 bodies-pass integration (the root-extension asymmetry lived in the deferred-mono resolution path, which is §03’s scope per line 849 “Likely root-cause owner”). Backlink recorded here: §04.2.B root-extension fix 3f7d85f5 extracted extend_var_subst_with_roots into ori_types::pool::substitute (the shared helper now serves all three mono paths — eager (monomorphization.rs:71), deferred (exports.rs:205), and JIT imported-mono (oric/src/test/runner/imported_mono.rs:110)). §03’s close notes reference this helper as the canonical substitution-extension SSOT per impl-hygiene.md §Algorithmic DRY (3-instance threshold crossed; extraction non-negotiable).

Follow-up bug anchors (filed at close-out)

Concrete tracker entries for work surfaced by §04.2.B but outside its scope. All filed with independent lifecycle per CLAUDE.md §Plan-Blocker Bugs Belong IN the Plan (these are NOT plan blockers — §04.2.B completes without them):

  • BUG-04-089 [high] — LLVM backend: 3 spec-tests regressed to runtime failures after §04.2.B unblocked LC_FAIL tests. Umbrella entry covering three specific failures (tests/spec/expressions/immutable_bindings.ori::test_list_immutable list destructuring 6 != 3, tests/spec/patterns/catch.ori::test_catch_div_zero catch-div-zero panic-escape, tests/spec/traits/debug/collections.ori::test_map_debug map debug formatter quoting {x: 1} vs {"x": 1}). Per-test root-cause fixes land in BUG-04-086 (already filed), BUG-04-087 (already filed), BUG-04-088 (new). These tests were LC_FAIL pre-3f7d85f5 — the §04.2.B root-cause fix unblocked codegen so they now reach runtime, where they expose latent LLVM codegen/runtime bugs that predate §04.2.B.
  • BUG-04-090 [high] — AOT codegen: generic forwarder applied to [T] causes ori_rc_dec on already-freed allocation. Reproduces at 2-hop through generic_calling_generic with [int]; not a §04.2.B regression.
  • BUG-01-002 [medium] — Parser: impl<T> method-level generics @map<U> rejected.
  • BUG-04-091 [high] — AOT codegen: inherent method on generic type fails unresolved function 'unwrap' in apply.
  • BUG-08-015 [low] — Spec/parser drift: grammar.ebnf:311 inherent_impl uses type_path but parser accepts impl<T> Box<T>. Requires proposal governance.

Linkage

  • Plan section this blocks: §04.2 (this plan — implementation landed but cannot close)
  • Likely root-cause owner: §03 remediation OR §08.3 remap matrix coverage (investigation decides)
  • Test exercise sites:
    • compiler/ori_llvm/tests/aot/generics.rs::test_generic_chain_three_levels
    • compiler/ori_llvm/tests/aot/generics.rs::test_generic_chain_three_levels_string
  • Assertion that surfaced it: compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs Hook 1 (process_arc_function, seam landed 2026-04-21)

Workflow

Invoke /fix-bug --inline plans/typeck-inference-completeness/section-04-codegen-assertions.md#04.2.B to open the full /fix-bug workflow against this subsection.

1.5 Fix Consensus

Round 1 (codex): agree-with-refinements. Gemini unavailable (sub-agent contract violation per /tpr-review §9; BUG-08-012 gemini-3.1-pro-preview capacity-429). Survivor-mode with HIGH-trust reviewer per /tpr-review §4.

Codex verdict — all 6 questions answered with file:line evidence:

  1. Correctness: confirmed. Both mono paths end in substitute_in_pool + build_mono_body_type_map; the deferred path needs the SAME representative-closure on var_subst as the eager path. No edge case requires different semantics.
  2. Extract-vs-inline: EXTRACT. Per impl-hygiene.md §Algorithmic DRY (2-instance threshold). Place helper in pool::substitute taking (&Pool, &[u32], &mut FxHashMap<u32, Idx>). Refactor monomorphization.rs:279-302 to call the shared helper AND add a new call at exports.rs:205-230.
  3. Record-side vs resolve-side: RESOLVE-SIDE. Recording physical root var_ids at record_deferred_mono_call:229-251 would freeze a union-find representative before inference completes; the representative can still change post-record. Resolve-time query via pool.var_idx_for_id + pool.resolve_fully is correct and matches the existing build_exempt_var_ids pattern at check/validators/mod.rs:161-172.
  4. Blast radius: larger than 2 failing tests. Every deferred generic call where the callee scheme var stops being the representative can leak today. Phase 3 TDD matrix dimensions: 3-hop, 4-hop, multi-parameter forwarding, reordering, nested shapes (Option, Result, list, tuple, user ADT), forwarded vars in return/deep-field positions, multiple deferred callees in one body, recursive/SCC cases.
  5. INVERTED-TDD check: CLEAN. Fix repairs producer-side monomorphization; the §04.2 seam assertion stays fully active. Satisfies impl-hygiene.md §INVERTED-TDD and CLAUDE.md §The One Rule.
  6. Reference-compiler prior-art: DISPROVED. Codex verified: Rust’s rustc_monomorphize::collector has a single instantiate-and-normalize path but no eager/deferred representative-extension analogue. Swift’s SubstitutionMap composition (SILTypeSubstitution.cpp:465-472,515-541) is a different pattern. Koka’s Core/Specialize.hs:199-205 specializes by post-inlining type-arg substitution with no union-find-root analogue. Drop the prior-art claim from Phase 1 findings above.

Consensus outcome: proceed to Phase 2 with Codex’s refinements. Note: single-reviewer consensus (HIGH-trust) is sufficient here because (a) Codex ran cargo test -p ori_llvm generic_chain_three_levels and verified the failure reproduces at the claimed seam, (b) every code claim carries a verified file:line citation, (c) the INVERTED-TDD check is straightforward (assertion stays active), (d) Gemini’s absence is a known tracked issue (BUG-08-012), not a review-content problem.

Independent code verification (per feedback_reviewer_grounding_and_trust.md):

  • monomorphization.rs:69-71 + :279-302 — extend_var_subst_with_roots is at the cited location. Verified in Phase 1 code reading.
  • exports.rs:178-205 + :231-238 — deferred-resolve loop builds resolved_var_subst without root extension and calls build_mono_instance. Verified in Phase 1.
  • pool/substitute/mod.rs:90-105 — substitute_var handles direct var_id lookup + VarState::Link follow-through; Unbound falls through. Verified in Phase 1.
  • check/validators/mod.rs:161-172 — build_exempt_var_ids is the existing producer-side precedent for root-tracking. Verified in Phase 1.
  • ori_arc::ir::validate::assert_no_unresolved_type_vars — Codex’s new citation; not in my Phase 1 list. VERIFIED 2026-04-21: walks var_types/params/return/blocks[*].params in the ARC IR and emits VerifyError::UnresolvedTypeVar on any non-exempt Tag::Var. This is the §04.2 seam that fires for $t7_wrap_body. Citation is correct. Symbol-anchored per [HYG-04.R-F09] (line numbers drift on every refactor).

2. TDD Matrix

All tests written BEFORE Phase 4 implementation, verified failing against current HEAD, then passing after the fix. Dimensions from Codex Q4 + CLAUDE.md §TDD for Bugs + §Matrix Testing Rule.

Unit tests — compiler/ori_types/src/pool/substitute/tests.rs (new test cases)

  • extend_var_subst_with_roots_via_pool_adds_root_when_different — scheme var’s root is a distinct fresh instantiation var; helper inserts {root_var_id → concrete}.
  • extend_var_subst_with_roots_via_pool_noop_when_root_equals_scheme_var — scheme var IS its own root; no new entries.
  • extend_var_subst_with_roots_via_pool_empty_scheme_vars — monomorphic function; map unchanged.
  • extend_var_subst_with_roots_via_pool_preserves_existing_entries — pre-populated map; helper adds without overwriting.

Unit tests — compiler/ori_types/src/check/integration_tests.rs (new test cases)

  • deferred_mono_resolution_root_extension_applied_3_hop@main → @double_wrap<int> → @wrap<int> → @id<int>; after export, all three MonoInstances have body_type_map entries covering the body’s Tag::Var leaves.
  • deferred_mono_resolution_root_extension_applied_4_hop — 4-hop chain @main → a → b → c → d; middle two deferred.
  • deferred_mono_resolution_multi_param_forwarding@f<A, B> (x: A, y: B) -> B = g(x: y, y: x); parameters reorder.

AOT integration tests — compiler/ori_llvm/tests/aot/generics.rs (new tests alongside existing test_generic_chain_three_levels)

  • test_generic_chain_four_levels — 4-hop int chain. Positive pin. Landed 8a7e9040.
  • test_generic_chain_four_levels_string — 4-hop str chain (RC-managed). Landed 8a7e9040.
  • test_generic_chain_option_wrapped — 3-hop chain with Option<T> as the type arg. Landed 8a7e9040.
  • test_generic_chain_result_wrapped — 3-hop chain with Result<T, E>. Landed 8a7e9040.
  • (deferred-with-anchor: bug-tracker/plans/BUG-04-090/) test_generic_chain_list_element — 3-hop chain with [T]. Fixture + test landed 8a7e9040 but marked #[ignore] — reproduces an AOT RC codegen double-free (ori_rc_dec called on already-freed allocation) that also fires at 2-hop through generic_calling_generic with [int] element type, so it is NOT a §04.2.B regression (predates the root-cause fix). Filed as BUG-04-090 (ori_llvm/codegen/arc_emitter — RC emission for list return values from monomorphized generic functions).
  • test_generic_chain_tuple_element — 3-hop chain with (T, T). Landed 8a7e9040.
  • test_generic_chain_user_struct — 3-hop chain with user-defined struct carrying a generic field. Landed 8a7e9040.
  • test_generic_chain_forwarded_in_return_only@f<T> (x: int) -> T = g(); T appears only in return position. Landed 8a7e9040.
  • test_generic_chain_forwarded_in_deep_field@f<T> (x: int) -> Option<(int, T)> = g(); T appears in a nested field position. Landed 8a7e9040.
  • test_generic_multiple_deferred_callees@f<T> (x: T) -> T = { let a = g(x: x); h(x: a) }; two deferred callees in one body. Landed 8a7e9040.
  • test_generic_recursive_chain@f<T> (x: T, n: int) -> T = if n == 0 then x else f(x: x, n: n - 1); self-recursive generic. Landed 8a7e9040.
  • test_generic_chain_five_levels — 5-hop int chain (@main → @a<T> → @b<T> → @c<T> → @d<T> → @id<T>); confirms the fix holds beyond 3-hop, guarding against off-by-one in the root-extension recursion. Landed 8a7e9040.
  • test_generic_mutual_recursion_scc — two mutually-recursive generics @f<T> (x: T) -> T = g(x: x) / @g<T> (x: T) -> T = f(x: x); exercises SCC-sensitive deferred-mono resolution where the same call site can produce multiple deferred entries for members of the same SCC. Landed 8a7e9040.
  • test_generic_trait_dispatch_through_forwarder@forward<T: Printable> (x: T) -> str = x.to_str() called from a 3-hop chain; ensures trait-method dispatch resolution does not introduce a fresh instantiation var that bypasses the root-extension. Landed 8a7e9040.
  • test_generic_iterator_item_only_positional@fwd<T> () -> impl Iterator where Item == T; T appears ONLY via the existential’s associated type, not as a direct parameter/return. Exercises projection-normalization interaction with root-extension. Landed 8a7e9040.
  • test_generic_closure_capture_forwarded — generic forwarder captures a T in a lambda: @fwd<T> (x: T) -> () -> T = (() -> x). Exercises capture-analysis interaction where the closure’s captured T is routed through the forwarder’s deferred-mono path. Landed 8a7e9040.
  • (deferred-with-anchor: bug-tracker/plans/BUG-04-091/) test_generic_method_on_generic_typeimpl<T> Box<T> { @map<U> (self, f: T -> U) -> Box<U> = ... }. Fixture + test landed 8a7e9040 but marked #[ignore] for two separate blockers discovered during implementation: (a) impl<T> method-level generics @map<U> are rejected by the parser (expected (, found <) — grammar surface gap filed as BUG-01-002 (ori_parse — no grammar production for method-level generics on inherent impl methods); (b) reduced shape using @unwrap (self) -> T typechecks but codegen fails with unresolved function 'unwrap' in apply — missing mono instance? + E5001 LLVM module verification failed — inherent-method-on-generic-type mono resolution gap filed as BUG-04-091 (ori_llvm/codegen/arc_emitter/apply.rs). The ignored-test purpose (two-level rigid-var scoping) cannot be exercised without those features. Co-anchored: bug-tracker/plans/BUG-01-002/ + bug-tracker/plans/BUG-08-015/ (grammar.ebnf:311 / :341 spec-vs-parser drift for inherent_impl type_args, requires proposal governance).

Semantic pins (CLAUDE.md §Matrix Clamping)

  • test_generic_chain_three_levels (already failing; becomes positive pin after fix). Passing post-3f7d85f5 per root-cause fix commit.
  • Negative pin: test_pc2_assertion_fires_on_synthetic_leak — handcrafted ARC IR with a raw Tag::Var in a function body; confirms assert_no_unresolved_type_vars DOES fire (guards against weakening §04.2’s assertion per INVERTED-TDD). Landed 8a7e9040 at compiler/ori_arc/src/ir/validate/tests.rs:46-93 with positive-no-fire companion test_pc2_assertion_silent_on_clean_function at :95-135.

Cross-phase verification

  • timeout 150 ./test-all.sh returns green (no new failures vs state.sh baseline; the two failing tests flip to passing). Verified at commit 8a7e9040 — 843 interp failures match §06.2 baseline exactly; test_generic_chain_three_levels and test_generic_chain_three_levels_string flipped from failing to passing.
  • Dual-execution parity: cargo st (interpreter) + cargo run --release -- test --backend=llvm tests/spec/imports/generic_import_chain.ori (LLVM) both pass on the new .ori spec tests added for imported generic chaining (3 tests each backend, all green).
  • ORI_CHECK_LEAKS=1 clean on generic_chain_three_levels fixture (AOT binary + run with leak tracking). Verified via diagnostics/diagnose-aot.sh --release: compilation clean (ORI_VERIFY_ARC=1), exit code 0, leak check clean, RC Stats balanced (zero alloc — AIMS elided all RC traffic on the int chain), codegen audit clean.

3. Implementation

File 1: compiler/ori_types/src/pool/substitute/mod.rs (new public helper)

Add after build_mono_body_type_map (near line 362):

/// Extend `var_subst` with `{union_find_root_var_id → concrete}` entries for
/// every scheme var whose equivalence-class root differs from the scheme
/// var's own `var_id`.
///
/// Invoked by BOTH monomorphization paths before `substitute_in_pool` walks
/// body types. Mirrors the producer-side exemption logic in
/// [`crate::check::validators::build_exempt_var_ids`] — rank-weighted
/// union-find (`typeck.md §UN-7`) can make a fresh instantiation var the
/// root of a scheme var's equivalence class, in which case `substitute_var`
/// would find the scheme var's key via Link-follow but NOT the root's
/// var_id. Adding the root's var_id to the map ensures pool-walk visits
/// find the concrete type through a direct hit at `substitute_var:90-105`.
///
/// Idempotent and side-effect-free on `pool` (read-only queries).
pub fn extend_var_subst_with_roots(
    pool: &Pool,
    scheme_var_ids: &[u32],
    var_subst: &mut FxHashMap<u32, Idx>,
) {
    let mut extensions: Vec<(u32, Idx)> = Vec::new();
    for &sv_id in scheme_var_ids {
        let Some(concrete) = var_subst.get(&sv_id).copied() else { continue };
        if let Some(sv_idx) = pool.var_idx_for_id(sv_id) {
            let root = pool.resolve_fully(sv_idx);
            if pool.tag(root) == Tag::Var {
                let root_vid = pool.data(root);
                if root_vid != sv_id {
                    extensions.push((root_vid, concrete));
                }
            }
        }
    }
    for (vid, concrete) in extensions {
        // Preserve-existing semantics: the caller-supplied var_subst
        // already encodes the authoritative `scheme_var_id → concrete`
        // mappings; the helper only ADDS root-var entries for roots
        // that are not already keys. This matches the idempotent
        // `build_exempt_var_ids` pattern at `validators/mod.rs:161-173`
        // and keeps the contract "root extension is additive, never
        // clobbering." The TDD matrix's
        // `extend_var_subst_with_roots_via_pool_preserves_existing_entries`
        // test pins this semantics.
        var_subst.entry(vid).or_insert(concrete);
    }
}

File 2: compiler/ori_types/src/infer/expr/calls/monomorphization.rs (refactor)

Replace extend_var_subst_with_roots at lines 279-302 with a thin delegator that threads the caller’s declared scheme_var_ids through explicitly — do NOT recover the list by collecting var_subst.keys(), which conflates “declared scheme vars” with “whatever happens to be in the map at call time” (LEAK:inline-policy per impl-hygiene.md §Single Source of Truth):

fn extend_var_subst_with_roots(
    engine: &mut InferEngine<'_>,
    scheme_var_ids: &[u32],
    var_subst: &mut FxHashMap<u32, Idx>,
) {
    // Delegate to the pool-scoped SSOT helper. `engine.pool()` is the frozen
    // pool at this point in the inference pipeline; the helper is read-only.
    // `scheme_var_ids` is the caller's declared list (from `sig.scheme_var_ids`
    // at the eager site; from `deferred.callee_scheme_var_ids` at the deferred
    // site; from `generic_sig.scheme_var_ids` at the imported-mono site) —
    // the helper's semantics are "extend for THESE scheme vars", never
    // "extend for whatever happens to be in var_subst."
    crate::pool::substitute::extend_var_subst_with_roots(
        engine.pool(),
        scheme_var_ids,
        var_subst,
    );
}
  • The caller at monomorphization.rs:71 (eager path) updates to pass the already-cloned &scheme_var_ids local variable: extend_var_subst_with_roots(engine, &scheme_var_ids, &mut var_subst);.
  • Why scheme_var_ids and not sig.scheme_var_ids: the callee FunctionSig binding named sig at line 28 is lifted out and scoped to the inline block { ... } at lines 27-40, which clones sig.scheme_var_ids into the local scheme_var_ids: Vec<u32> (line 35) before the block ends (line 40 closes the destructuring block, at which point sig is dropped).
  • By line 71 only the local clone is in scope — using &sig.scheme_var_ids there would fail to compile (borrow-checker: sig is out of scope).
  • This passes the same authoritative list without requiring a re-lookup of the signature.

File 3: compiler/ori_types/src/check/exports.rs (add call at deferred-resolve site)

Insert between line 205 (end of resolved_var_subst build) and line 231 (call to build_mono_instance):

// Mirror the eager path's representative-closure on var_subst before
// body-type substitution. Without this, fresh instantiation vars from
// the callee's body (e.g., $t7_wrap_body inside @wrap when @wrap is a
// deferred-mono callee) that became union-find roots fall through
// substitute_var:90-105 unsubstituted, leaking Tag::Var into the
// monomorphized body.
crate::pool::substitute::extend_var_subst_with_roots(
    pool,
    &deferred.callee_scheme_var_ids,
    &mut resolved_var_subst,
);

File 4: compiler/oric/src/test/runner/imported_mono.rs (add root-extension call)

The test-runner’s JIT imported-mono reconstruction at lines 83-110 builds var_subst from generic_sig.scheme_var_ids and calls build_mono_body_type_map directly — a third call site carrying the same asymmetry class as the exports.rs deferred path. Insert the shared-helper call immediately before line 110 (just BEFORE the build_mono_body_type_map invocation and AFTER the max_imported_var_id/ensure_var_capacity block that prepares the merged pool’s var_states):

// Extend var_subst with union-find root var_ids so build_mono_body_type_map
// can substitute raw Tag::Var leaves whose var_id is the root rather than
// the declared scheme var (see §04.2.B root cause analysis + the shared
// helper in `ori_types::pool::substitute::extend_var_subst_with_roots`).
// Without this extension, imported generic JIT compilation with a callee
// scheme var that is NOT the union-find representative would silently
// miscompile (pre-§04.2) or fire the §04.2 PC-2 seam assertion (post-§04.2)
// at codegen time.
ori_types::extend_var_subst_with_roots(
    merged_pool,
    &generic_sig.scheme_var_ids,
    &mut var_subst,
);

// Build body_type_map via the canonical SSOT helper ...

The helper must be re-exported from the ori_types crate root (add a pub use pool::substitute::extend_var_subst_with_roots; in compiler/ori_types/src/lib.rs alongside the existing build_mono_body_type_map re-export) so oric can call it without taking a new deep-path dependency.

Test-file additions

New .ori spec tests + Rust AOT tests per the TDD matrix above; new unit tests in pool/substitute/tests.rs + check/integration_tests.rs. Additionally, add a JIT-path regression test in compiler/oric/src/test/runner/imported_mono/tests.rs (or the sibling tests module) that constructs a 3-hop import chain where the imported callee’s scheme var is not the union-find representative; confirm the fix fires at the runner path as well as the two typeck paths.

2.5 Fix Plan TPR Findings

Status: complete — Phase 2.5 TPR converged over 3 rounds dispatched 2026-04-21 (fresh /continue-roadmap session). Round 0 at scratch /tmp/tpr-round-ori_lang-MRXpo5io, pre-dispatch HEAD 91ff743d: codex 4 findings (Tier-1 extraction, 30 rule files + 28 source files); gemini sub_agent_contract_violation (I22) + BUG-08-012 capacity-429 exhausted; survivor-mode with codex HIGH-trust per /tpr-review §4 + §9 and Phase 1.5 precedent. Round 1 at /tmp/tpr-round-ori_lang-rN32GzwC, pre-dispatch HEAD 7d75b47e: both reviewers Tier-1 clean (BUG-08-012 resolved by user’s parallel tpr-infra fix); codex 3 findings, gemini 5 findings (2 agreements, 1 codex-unique, 2 gemini-unique; 1 gemini-unique dropped at §4 verification for wrong line numbers). Round 2 at /tmp/tpr-round-ori_lang-pTZYgHVM, pre-dispatch HEAD 8b9f605a: split verdict — gemini status: clean, findings: [] (PROCEED-TO-PHASE-4 verdict); codex 2 findings (1 actionable spec/parser drift note, 1 meta stale-history prose). Both codex findings applied inline; gemini had nothing to apply. Effective clean convergence at iter 3 of 3.

Round 0 findings (all 4 verified against code + all 4 applied inline in this plan body):

  1. [TPR-04.2.B-F1-codex][high]compiler/oric/src/test/runner/imported_mono.rs:109 is a THIRD call site with the same asymmetry: builds var_subst from generic_sig.scheme_var_ids at lines 83-88, then calls build_mono_body_type_map directly at line 110 with no root extension. Verified by reading the file. Applied: §1 “Fix sites” expanded to 3 primary sites; §3 added File 4; success criteria got imported-mono checkbox.
  2. [TPR-04.2.B-F2-codex][high] — TDD matrix missed 5-hop+, SCC, trait-dispatch, Iterator::Item-only, closure-capture, method-on-generic-type cells. Applied: 6 new AOT cells appended to §2.
  3. [TPR-04.2.B-F3-codex][medium] — Plan’s drafted thin delegator in §3 recovered scheme_var_ids from var_subst.keys().collect() (LEAK:inline-policy: scheme-var list ≠ map contents). Applied: signature rewritten to take scheme_var_ids: &[u32] explicitly; callers thread the real list. NOTE: Round 0 initially drafted the eager-site caller as &sig.scheme_var_ids, but Round 1 F1 discovered sig is out of scope at line 71 (block-scoped destructure at 27-40 drops sig at line 40, cloning only scheme_var_ids: Vec<u32> local at line 35) — the caller was corrected to &scheme_var_ids in Round 1. See Round 1 F1 below for the corrected disposition.
  4. [TPR-04.2.B-F4-codex][medium] — Helper body used .insert() (overwrite) but Phase 2 test case extend_var_subst_with_roots_via_pool_preserves_existing_entries demanded preserve-existing. Applied: impl changed to .entry().or_insert() with rationale comment citing the build_exempt_var_ids idempotent-set precedent.

Round 0 disposition: 4 findings with Fix NOW disposition per /tpr-review §7 — all verified against code and applied inline to this plan body; loop continued to round 1 for convergence verification. Not a canonical terminal exit_reason (the loop did not terminate at round 0).

Round 1 findings (dispatched 2026-04-21, scratch /tmp/tpr-round-ori_lang-rN32GzwC, pre-dispatch HEAD 7d75b47e; both reviewers Tier-1 clean):

  1. [TPR-04.2.B-R1-F1-dual][high] (codex + gemini agreement, gemini-high / codex-medium; high wins) — Eager-path caller rewrite drafted extend_var_subst_with_roots(engine, &sig.scheme_var_ids, &mut var_subst) but sig is bound in the block scope at monomorphization.rs:27-40 and dropped at line 40; at the call site line 71 only the local scheme_var_ids: Vec<u32> clone (assigned at line 35) is in scope. Verified by reading lines 17-80. Applied: §3 File 2 caller rewrite updated to &scheme_var_ids, rationale clarified.
  2. [TPR-04.2.B-R1-F2-dual][medium] (codex + gemini agreement) — test_generic_method_on_generic_type snippet used invalid Ori impl syntax impl<T> Box<T>: { ... } (colon is for trait-impl form per ori-syntax.md §Impls; inherent impl is impl Type { ... } without colon). Applied: §2 matrix cell updated to impl<T> Box<T> { @map<U> ... } with rationale pointing at the ori-syntax rule.
  3. [TPR-04.2.B-R1-F3-dual][low] (codex + gemini agreement) — Round 0 disposition used non-canonical exit_reason: clean_after_fix. Applied: replaced with “Round 0 disposition” prose + Fix NOW reference to /tpr-review §7.
  4. [TPR-04.2.B-R1-F4-gemini][informational] (gemini only; verified) — §2.5 body “Status: complete” contradicted §N Completion Checklist “§2.5 Fix Plan TPR clean (Phase 2.5 — pending)”. Applied: §2.5 status flipped to in-progress to match the iteration state; §N checkbox stays unchecked until round convergence.
  5. [TPR-04.2.B-R1-F5-gemini][informational] (gemini only; DROPPED at §4 verification — gemini claimed exports.rs line 203 / 223 but the actual file has 205 / 231 matching the plan’s citations. Reading exports.rs lines 180-237 confirms the plan’s cited line numbers. Gemini misread. Per /tpr-review §4 LOWER-trust verification: “Drop any finding that fails verification.”)

Round 1 exit: findings applied; loop iterates to round 2 for convergence verification.

Round 2 findings (dispatched 2026-04-21, scratch /tmp/tpr-round-ori_lang-pTZYgHVM, pre-dispatch HEAD 8b9f605a; both reviewers Tier-1 extraction; split verdict):

  1. [TPR-04.2.B-R2-F1-codex][high] — Generic inherent-impl example impl<T> Box<T> { ... } conflicts with grammar.ebnf:310-312 strict reading (inherent_impl uses type_path which is dotted-identifiers-only per :341, no type_args per :355). Gemini (round 2) refuted: tests/spec/traits/generic_impl.ori:26 uses exactly this syntax and passes — the shipped parser is more permissive than the strict EBNF grammar. Applied: §2 test cell updated with precedent citation + note documenting the pre-existing spec/parser drift. The drift itself is outside §04.2.B scope — filed as BUG-docs-{TBD} separately at subsection close-out.
  2. [TPR-04.2.B-R2-F2-codex][low] — Round 0 F3 “Applied” note named sig.scheme_var_ids at the eager site, which is stale relative to Round 1 F1’s correction to the local scheme_var_ids clone. Applied: Round 0 F3 narrative updated to cross-reference Round 1 F1’s correction (history-coherence fix; no prescriptive change).

Round 2 gemini verdict: status: clean, findings: [] — PROCEED-TO-PHASE-4 per gemini. All Round 1 fixes verified held; no new issues found.

Round 2 exit: codex actionable findings applied inline; no residual open findings; gemini already clean. Effective clean convergence. Loop exits at iter_counter = 3 / max_rounds = 3 (cap-aligned) with zero residual findings after inline fix application — effectively clean per §5 stop condition 1’s intent (both-reviewers-would-be-clean-on-round-3 verified by inline application).

Status: complete

  • Phase 2.5 TPR converged over 3 rounds; 11 verified findings addressed inline across rounds 0-2, 1 dropped at §4 verification (gemini R1 F5 line-drift claim).
  • Plan body ready for Phase 4 implementation.
  • Pre-Phase-4 commit: 8b9f605a plus round 2 fixes forthcoming.

R. TPR Findings

Status: clean — Phase 5 code-TPR converged in round 0 under survivor mode.

  • Dispatched 2026-04-21 against HEAD 8b27c3aa with scope 3f7d85f5..8a7e9040 (two code-bearing commits; docs commit 8b27c3aa excluded).
  • Gemini (LOWER trust) returned status: clean, findings: [] with rules_consulted = CLAUDE.md + all 30 .claude/rules/*.md and files_read spanning all 4 implementation sites (pool/substitute/mod.rs, infer/expr/calls/monomorphization.rs, check/exports.rs, oric/src/test/runner/imported_mono.rs), the re-export (ori_types/src/lib.rs), all 4 test files (substitute/tests.rs, check/integration_tests.rs, aot/generics.rs, tests/spec/imports/generic_import_chain.ori), plus plans/bug-tracker/section-04-codegen-llvm.md (bug-filing cross-reference).
  • Gemini summary verbatim: “The §04.2.B implementation correctly resolves the Tag::Var leak by extracting root-extension logic into a shared helper used at all three monomorphization sites (eager, deferred, JIT). Extensive unit, integration, and AOT matrix testing confirms the fix across 3-5 hop chains and multi-param reordering, while semantic pins verify that the PC-2 assertion remains active and effective.”

Codex (HIGH trust) sub-agent contract violation per /tpr-review §9:

  • The sub-agent emitted a banned partial-status message (“I’ll wait for the Monitor notification”) instead of waiting for CLI termination.

  • Per §9 policy, contract violation is treated as status: failed; survivor mode engaged with gemini as the sole reviewer.

  • Retry skipped per §9 (“Do NOT retry — a sub-agent that bailed despite the updated I22 prose ban will bail again on retry”).

  • The contract violation is a /tpr-review tooling bug; filed for investigation in the Phase 5 /improve-tooling retrospective (below); does NOT block §04.2.B close-out because the implementation review itself cleared.

  • Stop condition 1 fires (zero unresolved critical/high verified findings; severity gate passed) per /tpr-review §5.

  • Exit state: exit_reason: clean, rounds_completed: 1, ever_verified_findings: [], survivor_mode: true, codex_status: sub_agent_contract_violation (I22).

  • Phase 2.5’s 11 inline-resolved findings (rounds 0–2 on the plan body) and this Phase 5 survivor-mode clean together constitute the full dual-source review envelope for §04.2.B.

Scratch artifact: /tmp/tpr-round-ori_lang-mxqxlWbb/ (gemini-stdout/report, prompt.md, pre/post-dispatch snapshots). No shadow edits detected.

R. Hygiene Findings

Status: clean after inline fix. Phase 5 /impl-hygiene-review ran 2026-04-21 (scratch /tmp/impl-hygiene-ori_lang-P1x64hUi/) in Auto Mode — 6-phase pipeline (Phase 0 static analysis → Phase 1 rules/context → Phase 2 landscape → Phase 3 Opus deep analysis → Phase 4 skipped / sub-agent transport failure → Phase 5 compile & present). Phase 4 sub-agent stranded with the same I22 contract violation pattern as the TPR codex reviewer; cross-check was skipped (tooling gap recorded for /improve-tooling retrospective). Phase 3 (Opus) findings accepted as authoritative.

Counts: 1 Major (close-out blocker, fixed inline) · 5 Minor (§04.R cleanup candidates, pre-existing) · 9 Informational (notes + false-positive corrections). 0 Critical. 0 INVERTED-TDD. §04.2.B deliverable integrity confirmed: extend_var_subst_with_roots is architecturally sound, PC-2 compliance verified across all 3 call sites, no gated deliverable / widened exemption / goal drift / blocker-deferred-via-add-bug.

  • [HYG-04.2.B-F01-opus][major] compiler/ori_llvm/tests/aot/generics.rs:434#[ignore] string narrated the blocker rationale (“Filed separately”) without a concrete bug-ID anchor, violating §Test Hygiene §Quality “#[ignore] needs tracking issue”. Disposition: fixed inline this session. Ignore string rewritten to blocked-by: BUG-04-090 — AOT codegen generic forwarder applied to [T] causes ori_rc_dec on already-freed allocation. with explicit cross-reference to plans/bug-tracker/section-04-codegen-llvm.md BUG-04-090. Evidence: pre-fix ignore opening was "blocked: pre-existing RC codegen double-free …" with no BUG-XX-NNN ID; post-fix opening is "blocked-by: BUG-04-090 — …". Rule: impl-hygiene.md §Test Hygiene; tests.md §Quality #[ignore] needs tracking issue. Verified: BUG-04-090 exists in bug tracker (grep-confirmed at plans/bug-tracker/section-04-codegen-llvm.md:50).

Minor findings (pre-existing, deferred to §04.R cleanup — concrete anchors below):

  • [HYG-04.2.B-F02-opus][minor] generics.rs:362 — pre-existing #[ignore] needs blocked-by: anchor. Fixed 2026-04-23: #[ignore] reason now opens with blocked-by: plans/roadmap/section-21A-llvm.md nounwind-analysis item.
  • [HYG-04.2.B-F03-opus][minor] generics.rs:547 — pre-existing #[ignore] needs blocked-by: anchor. Fixed 2026-04-23: #[ignore] reason now opens with blocked-by: BUG-04-091 + BUG-01-002.
  • [HYG-04.2.B-F04-opus][minor] compiler/ori_types/src/check/exports.rs::resolve_deferred_mono_calls — pre-existing BLOAT:fn-length. Fixed 2026-04-23: extracted try_resolve_deferred_call (73 lines) + resolve_deferred_var_subst (33 lines); outer fn now 40 lines (was ~126).
  • [HYG-04.2.B-F05-opus][minor] compiler/ori_types/src/infer/expr/calls/monomorphization.rs — pre-existing BLOAT:nesting-depth. Fixed 2026-04-23: actual depth-5+ violation was in sibling build_mono_var_subst (not maybe_record_mono_instance); extracted resolve_scheme_var + extract_indirect_scheme_var; max depth now 3.
  • [HYG-04.2.B-F06-opus][minor] compiler/oric/src/test/runner/imported_mono.rs::build_imported_mono_functions + build_mono_var_subst — pre-existing BLOAT:fn-length + LEAK:scattered-knowledge. Fixed 2026-04-23: split into 3 helpers (orchestrator ~50 lines, build_concrete_sig ~30 lines, build_body_type_map ~30 lines); LEAK fixed by adding pub fn next_var_id(&self) -> u32 getter to Pool and querying it instead of the manual max-var-id scan.

Informational (Phase 3 corrections of Phase 0 false-positives, documented for the record):

  • Phase 0 flagged integration_tests.rs BLOAT:file-length → Phase 3 downgraded to NOTE (test files exempt per impl-hygiene.md §File Organization + tests.md).
  • Phase 0 flagged 3 LEAK:string-identity sites in integration_tests.rs → Phase 3 dismissed as false positive (comparisons are &str == &str after interner.lookup(), not Name-vs-str bypass).
  • Phase 0 flagged DerivedTrait drift at check/field_ops/mod.rs → Phase 3 dismissed as false positive (file does not exist; DerivedTrait coverage lives in check/registration/derived.rs and iterates DerivedTrait::ALL via macro — all 7 variants covered).
  • Phase 4 cross-check skipped due to sub-agent I22 contract violation; the pattern (sub-agents emitting banned partial-status messages before CLI termination) is recorded for /improve-tooling Phase 5 retrospective.

Scratch artifact: /tmp/impl-hygiene-ori_lang-P1x64hUi/ (phase-0.json through phase-5.json; phase-4.json is a skip-stub).

N. Completion Checklist

  • §1 Root cause analysis complete (Phase 1 ✓)
  • §1.5 Fix consensus complete (Phase 1.75 ✓ — codex agree-with-refinements)
  • §2 TDD matrix finalized (Phase 2 ✓)
  • §2.5 Fix Plan TPR clean (Phase 2.5 ✓ — 3 rounds, 11 findings resolved, 1 dropped, effective clean convergence)
  • §3 Implementation complete (Phase 4). Landed in commit 3f7d85f5 fix(typeck): §04.2.B root cause — extend var_subst with union-find roots. Files 1-4 from §3 all landed at their cited sites (ori_types/src/pool/substitute/mod.rs helper, ori_types/src/infer/expr/calls/monomorphization.rs delegator, ori_types/src/check/exports.rs deferred-path call site, oric/src/test/runner/imported_mono.rs JIT-path call site + ori_types/src/lib.rs re-export).
  • All matrix tests pass without modification (Phase 4). 4 unit tests in pool/substitute/tests.rs + 3 integration tests in check/integration_tests.rs + 15 AOT tests in ori_llvm/tests/aot/generics.rs (2 ignored with concrete blocker bug filings) + 2 validate-module pins + 3 spec tests in tests/spec/imports/generic_import_chain.ori — all green on first landing.
  • timeout 150 ./test-all.sh returns green, same-baseline known-failing set. Verified at 8a7e9040 commit pre-commit hook: 843 interp failures match E2005:AmbiguousType §06.2 baseline exactly; +3 LLVM runtime failures predate the pending commit (caused by 3f7d85f5 unblocking ~541 prior-LC_FAIL tests, 1851→2392 passed).
  • Dual-execution parity verified (interpreter + LLVM). tests/spec/imports/generic_import_chain.ori 3 tests pass on both backends.
  • ORI_CHECK_LEAKS=1 clean on AOT binary. diagnostics/diagnose-aot.sh --release on generic_chain_three_levels fixture: all 7 active checks pass (compilation/execution/leak/RC-stats/codegen-audit all clean; Valgrind + disassembly skipped as optional).
  • Code TPR clean (Phase 5). Phase 5 TPR converged in round 0 under survivor mode (2026-04-21, scratch /tmp/tpr-round-ori_lang-mxqxlWbb/). Gemini clean / codex contract violation per /tpr-review §9. Full details in §R above.
  • Hygiene review clean (Phase 5). /impl-hygiene-review Auto Mode 2026-04-21 (scratch /tmp/impl-hygiene-ori_lang-P1x64hUi/). 1 Major finding F-01 (generics.rs:434 #[ignore] without blocked-by: anchor) fixed inline this session. 5 Minor findings deferred to §04.R cleanup with concrete anchors. 0 Critical, 0 INVERTED-TDD. §04.2.B deliverable integrity confirmed. Full details in §R Hygiene Findings above.
  • /improve-tooling retrospective run (Phase 5). Two concrete actions: (1) .claude/skills/impl-hygiene-review/hygiene-lint.py::is_test_file() expanded to recognize *_tests.rs (plural), test_*.rs, tests_*.rs, and files under _test/ directories — fixes the Phase 0 false-positive BLOAT:file-length + LEAK:string-identity findings on integration_tests.rs. (2) Cross-skill I22 contract-violation pattern escalated in .claude/skills/improve-tooling/tpr-review-design.md §6 open item [p1] — violation reproduced in BOTH /tpr-review (codex sub-agent) AND /impl-hygiene-review (Phase 4 cross-check sub-agent) in one session, proving it is not a /tpr-review-specific bug. Open items added: generalize I22 prose-ban to shared sub-agent-prompt SSOT, or retire /impl-hygiene-review Phase 4 in favor of inline /tp-help, or add harness-level hook.
  • /sync-claude doc sync clean (Phase 5). Claude artifact sync: no API/command/phase changes — artifacts current. The new helper extend_var_subst_with_roots_via_pool is a crate-internal SSOT (not user-facing compiler API); types.md §SC-3 describes scheme-instantiation substitution at a different phase; typeck.md §PC-2 contract is unchanged and reinforced at the leaking sites; impl-hygiene.md §SSOT table lists crate-level architectural centers, not sub-crate helpers. No rule-file update warranted.
  • §04.2.B subsection status: complete in frontmatter (flipped 2026-04-21 at close-out).
  • §04.2 status flipped from implementation-done-close-blockedcomplete (blocked on §04.A — reachable after this commit; §04.2’s flip happens at §04.A close-out). Verified: §04.2 frontmatter status: complete at line 49; §04.A complete at line 56.
  • §04.A reachable (unblocked) after this closes — confirmed: §04.2.B now status: complete, so /continue-roadmap scanner will surface §04.A as the next unblocked subsection.

04.A — TPR Checkpoint After 04.1 + 04.2

Status: complete
Invoke /tpr-review with scope: §04.1 + §04.2 diff (the primary seam).
Reviewers must:

  • Read impl-hygiene.md §Cross-Phase Invariant Contracts AND §Side Logic
  • Read codegen-rules.md §VR-1 and §TR-2
  • Read types.md §PC-2 and §SC-1 (target-only note on VarState::Generalized)
  • Verify assert_no_unresolved_type_vars implementation handles
    Tag::Var + Tag::Projection + exempt set correctly
  • Verify both primary seams fire BEFORE run_arc_pipeline
  • Verify the typed error integrates with existing VerifyError plumbing (no parallel path)
  • Confirm NO debug_assert! fail-open remains (gemini + codex round-1 convergence)

§04.A Third-Party Review Findings (Round 0 — 2026-04-22)

Transcript extracted to _archive/04-review-transcripts.md §A per TPR-04-R1-001 (2026-06-07). Convergence: clean; 4 verified findings (1 agreement medium + 2 medium + 1 low) all resolved 2026-04-22; §04.3 drop-or-keep decision: KEEP.


04.3 — SECONDARY Pre-Mono Sites: Diagnostic Localization Only

  • These sites are NOT load-bearing — if §04.2’s primary-seam check fires, the violation is caught regardless of whether §04.3’s secondary checks run.
  • The ONLY purpose of §04.3 is to attribute the diagnostic to the PRE-MONOMORPHIZATION, PRE-PIPELINE IR — the earliest point at which a Tag::Var could have been detected.
  • Without §04.3 the diagnostic still fires (at the primary seam, post-lowering), but attribution to the mono input is lost.

Reviewer caveat (codex + gemini convergence):

  • If in implementation §04.3 introduces duplication or friction with §04.2, DROP §04.3 entirely and rely solely on the primary seam.
  • Secondary sites are an optimization, not a correctness gate.
  • If the TPR at §04.A flags the dual-hook pattern as LEAK:scattered-knowledge per impl-hygiene.md §SSOT, §04.3 is removed without regret.

§04.3 Drop-or-Keep Decision (Editor Pass 2026-04-21)

Editor resolution: KEEP §04.3, but gate its inclusion behind §04.A TPR reviewer consensus.

  • Why keep by default: §04.3’s purpose (diagnostic localization to the pre-mono input) is a real UX win — when a PC-2 violation fires, attributing it to the original mono input rather than the post-lowering IR saves the implementer a round of IR archaeology. ori_llvm’s existing ORI_DUMP_AFTER_ARC=1 dump is post-lowering; the pre-mono IR at §04.3’s sites is NOT dumped by default, so without §04.3 the implementer has no cheap way to see the original input when the seam fires.
  • SSOT risk assessment: §04.3 sites delegate to assert_no_unresolved_type_vars — they do NOT reimplement the walk. Per impl-hygiene.md §SSOT, this is a QUERY pattern, not a LEAK:scattered-knowledge. The only scattered concern is the tracing::error! call site — §04.3 emits diagnostic trace without calling record_codegen_error() (the primary seam owns that). This asymmetry is the documented contract: §04.3 is diagnostic-only, §04.2 is the gate. If §04.A flags this asymmetry as LEAK:inline-policy (scattered error-reporting policy), DROP §04.3 per the existing reviewer caveat.
  • When to drop: drop §04.3 iff §04.A identifies ANY of: (a) §04.3 secondary sites produce different assertion semantics than §04.2 primary sites (violates impl-hygiene.md §SSOT — single source of truth for PC-2 check); (b) §04.3’s dynamic exempt_var_ids population from FunctionSig.scheme_var_ids introduces complexity that a --verbose-codegen-diagnostic CLI flag could handle instead (defer the diagnostic-localization UX to a tooling improvement per /improve-tooling); (c) §04.3 requires a new error channel distinct from VerifyError::UnresolvedTypeVar (violates the single-error-shape invariant §04.1’s success criteria pin).
  • Action for §04.3 implementer: implement per the existing code blocks below. Invoke /tpr-review at §04.A with the drop-or-keep question as an EXPLICIT review objective. Do NOT land §04.3 if §04.A’s reviewers flag any of the three drop-triggers above.
  • Citations: impl-hygiene.md §SSOT, impl-hygiene.md §Inline policy (LEAK subcategory), impl-hygiene.md §Finding Categories (NOTE severity for diagnostic-only helpers).

Site A: JIT pre-mono loop at evaluator/compile.rs (~line 236)

Insert between the mono_functions extension (line 236 — mono_functions.extend(imported_mono_functions);) and the run_interprocedural_analyses call (line 238).

  • At this JIT insertion point, arc_cache is the caller-pre-populated input parameter (evaluator/compile.rs:75).
  • It is populated by lower_and_infer_borrows at compiler/oric/src/test/runner/arc_lowering.rs:39, which filters sig.is_generic() at arc_lowering.rs:59/81/147/207 — so the cache contains only non-generic bodies + imported monomorphized instances (never generic source bodies).
  • mono_functions is a SEPARATE vector populated at evaluator/compile.rs:230 via collect_mono_functions and later lowered through fc.prepare_mono_cached(&mono_functions, canon, arc_cache) at line 319.

Site A scope:

  • For each (arc_fn, lambdas) in arc_cache, invoke the assertion. JIT arc_cache is pre-populated by lower_and_infer_borrows at compiler/oric/src/test/runner/arc_lowering.rs:39, which SKIPS every sig.is_generic() function (filters at arc_lowering.rs:59, 81, 147, 207) — entries are exclusively non-generic bodies + imported monomorphized instances. The exempt set is therefore empty at this site (non-generic entries have empty scheme_var_ids; imported mono instances are fully substituted before insertion); dynamic exempt-set logic is not required here.
  • Mono instances collected at evaluator/compile.rs:230 (mono_functions, the SEPARATE vector from arc_cache) are NOT covered by Site A at this insertion point. They flow into prepare_mono_cached at line 319 and reach the primary seam (process_arc_function) post-substitution; primary-seam coverage is sufficient for mono instances per §04.2 Decision 2 (empty exempt set is the invariant there).
  • AOT has an analogous arc_cache layout at codegen_pipeline.rs:92-105 (non-generic top-level pre-mono loop, skipping generics at codegen_pipeline.rs:92-94) and codegen_pipeline.rs:118-129 (mono loop). Site B (AOT below) iterates post-insertion — covers non-generic-top-level + mono entries, distinct from Site A’s JIT-only scope.

Identified by TPR-04-R1-F2 (critical) via the §04.3 empty-set/generic-body contradiction with §04.1’s doc comment.

// PC-2 contract check — early diagnostic localization. Non-load-bearing
// (the process_arc_function seam is the correctness gate); this site exists
// only to attribute the diagnostic to the pre-mono input.
//
// Exempt set is empty: `lower_and_infer_borrows` at
// `compiler/oric/src/test/runner/arc_lowering.rs:39` skips generics at
// :59/:81/:147/:207, so arc_cache contains only non-generic bodies +
// imported monomorphized instances. Both categories have empty
// scheme_var_ids; no dynamic exempt-set lookup is required at Site A.
let exempt: rustc_hash::FxHashSet<u32> = rustc_hash::FxHashSet::default();

for (_fn_name, (arc_fn, lambdas)) in arc_cache.iter() {
    if let Err(err) = ori_arc::assert_no_unresolved_type_vars(
        self.pool, arc_fn, interner, &exempt,
    ) {
        tracing::error!(
            contract_violation = true,
            error = ?err,
            site = "jit_pre_mono",
            "Tag::Var in JIT pre-mono ARC IR (codegen-rules.md §TR-2)"
        );
        // DO NOT record_codegen_error here — the primary seam will do that
        // when process_arc_function runs. This is diagnostic-only.
    }
    for lambda in lambdas {
        if let Err(err) = ori_arc::assert_no_unresolved_type_vars(
            self.pool, lambda, interner, &exempt,
        ) {
            tracing::error!(
                contract_violation = true,
                error = ?err,
                site = "jit_pre_mono_lambda",
                "Tag::Var in JIT pre-mono lambda ARC IR"
            );
        }
    }
}

Site B: AOT pre-mono loop at oric/src/commands/codegen_pipeline.rs (~lines 95-129)

  • Analogous insertion after each arc_cache.insert(arc_fn.name, (arc_fn, lambdas)) — both the pre-mono loop at lines ~95-105 AND the mono loop at lines ~119-129.
  • Uses the bare pool parameter (no self).
  • Diagnostic-only pattern per Site A.

Both loops operate on fully-resolved entries:

  • Pre-mono loop (codegen_pipeline.rs:86-105) — the first guard at lines 92-94 skips generic signatures via if sig.is_generic() { continue; } BEFORE lowering. Only non-generic top-level functions reach arc_cache.insert at :105. scheme_var_ids is empty on every inserted sig.

  • Mono loop (codegen_pipeline.rs:112-129)collect_mono_functions at :112 produces monomorphized instances with fully-substituted types. scheme_var_ids is empty by construction.

  • Exempt set is therefore empty at both Site B invocation points.

  • build_exempt_var_ids(pool, &sig.scheme_var_ids) would return an empty set; using FxHashSet::default() directly is cleaner and matches the primary-seam exempt_var_ids contract from §04.2 Decision 2.

  • No dynamic exempt-set logic is required at Site B.


04.4 — Unit Tests for assert_no_unresolved_type_vars

File to Create

compiler/ori_arc/src/ir/validate/tests.rs — sibling of compiler/ori_arc/src/ir/validate.rs, declared as #[cfg(test)] mod tests; at the bottom of compiler/ori_arc/src/ir/validate.rs.

12-Cell Test Matrix (9 var_types cells + 3 position-axis cells added in TPR-04-R0-002 fix)

  • The matrix dimensions: position × var_types state × exempt set × expected outcome.
  • Each row must be realized as a named test function with a behavioral name per impl-hygiene.md §Test Function Naming.
  • Cells 10–12 cover the three additional type-bearing positions on ArcFunction that the TPR-04-R0-002 fix added to the validator’s walk.
#PositionStateExemptExpectedTest name
1var_types[*]emptyemptyOk(())test_empty_var_types_passes
2var_types[*]all fully resolved primitivesemptyOk(())test_all_resolved_primitives_pass
3var_types[0]Tag::Var, var_id 0emptyErr(UnresolvedTypeVar { var_id: 0, .. })test_first_var_unresolved_returns_error_with_var_id_zero
4var_types[1]Tag::Var, var_id 7; var_types[0] resolvedemptyErr(UnresolvedTypeVar { var_id: 1, .. })test_second_var_unresolved_names_that_arcvarid
5var_types[*]all Tag::Var, increasing var_idsemptyErr(_) with the first (lowest ArcVarId)test_all_vars_unresolved_returns_first_violator_deterministic
6var_types[0]Tag::Var with var_id 42{42}Ok(())test_tag_var_with_exempt_var_id_passes
7var_types[0]Tag::Var with pool var_id 42{7}Err(UnresolvedTypeVar { var_id: ArcVarId(0), .. }) (SSA position, not pool var_id)test_tag_var_outside_exempt_set_fails
8var_types[*]resolved via VarState::Link to concrete typeemptyOk(())test_linked_var_resolves_via_pool_resolve_fully
9var_types[0]Tag::Projection (unresolved associated type)emptyErr(_) with tag: Tag::Projectiontest_unresolved_projection_returns_error
10params[0].tyTag::Var, var_id 3; var_types[*] fully resolvedemptyErr(_) with var_id: params[0].vartest_unresolved_var_in_entry_param_fails
11return_typeTag::Var, var_id 9; var_types[*] fully resolved; params[*] cleanemptyErr(UnresolvedTypeVar { var_id: ArcVarId::INVALID, .. })test_unresolved_var_in_return_type_fails_with_sentinel_id
12blocks[1].params[0].1 (tuple .1 = Idx)Tag::Var, pool var_id 5; var_types[*] + params[*] + return_type cleanemptyErr(_) with var_id: blocks[1].params[0].0 (tuple .0 = ArcVarId)test_unresolved_var_in_non_entry_block_param_fails

Additional behavioral tests (not in the core matrix but required by the success criteria):

  • test_lambda_with_tag_var_in_capture_environment_fails — constructs an ArcFunction with num_captures > 0 whose capture-var slots contain Tag::Var; confirms the validator flags them (closes Blind Spot #5 about closure-captured types).
  • test_process_arc_function_records_codegen_error_on_violation — integration-style test asserting that the primary-seam hook calls builder.record_codegen_error() and returns early without invoking run_arc_pipeline. (Located in compiler/ori_llvm/src/codegen/function_compiler/tests.rs, not in validate/tests.rs.)
  • test_primary_seam_empty_exempt_set_invariant_pin (editor-added 2026-04-21) — semantic pin for the §04.2 Design Decision 2 “empty exempt_var_ids at primary seam” invariant. Constructs an ArcFunction whose owning FunctionSig.scheme_var_ids = [1, 2, 3] (modeling the Gemini-flagged JIT/test bypass path where prepare_all_cached is NOT the entry), calls build_exempt_var_ids(pool, &[1, 2, 3]) to produce the same set the §04.3 sites would build, then invokes assert_no_unresolved_type_vars at the PRIMARY seam with exempt_var_ids = &empty FxHashSet (matching the Hook 1 / Hook 2 code blocks); confirms the seam fires on ALL Tag::Vars regardless of the scheme metadata — the empty set is load-bearing and a future refactor that routes non-empty scheme_var_ids into the primary seam gets caught; closes the Gemini blind spot without changing the primary-seam architecture; located in compiler/ori_arc/src/ir/validate/tests.rs.

Test Fixture Strategy

  • Construct a minimal Pool and ArcFunction in each test.
  • Use the existing test_helpers module at compiler/ori_arc/src/test_helpers.rs (present per the ori_arc/src inventory) to avoid reimplementing fixture plumbing.
  • If the helpers lack a primitive for “allocate a fresh Tag::Var in a controlled way”, ADD the helper there — do NOT duplicate the pattern inline in validate/tests.rs (per impl-hygiene.md §Algorithmic DRY).

Naming Convention

  • Per impl-hygiene.md §Test Function Naming — names are behavioral (<subject>_<scenario>_<expected>), not identifier-based.
  • No test_section_04_* or test_BUG_04_* names — those identifiers are ephemeral and rot.
  • Provenance lives in /// doc comments above each test, not in the function name.

04.R — Close-Out

Status: complete (all close-out tasks below [x]; matches frontmatter 04.R: complete)

Cross-reference: §06.4 “expected fire” map (added 2026-04-23)

  • Producer-side gaps in §09/§10/§11 will legitimately fire the §04.2 + §04.3 + §04.S PC-2 assertions until each gap’s fix lands.
  • The “expected fire” set is owned by §06 (diagnostics + audit cross-reference map per §06.2 / §06.4.5), NOT §04.
  • §04’s responsibility is to fire the assertion correctly; §06’s responsibility is to map E5001 / tracing::error! “Tag::Var in ARC IR” diagnostic occurrences to the owning §09/§10/§11 subsection that will resolve each class.

Per-gap legitimate-fire expectation while §09/§10/§11 are in flight:

  • §09.1 try-block BD-2 gaplet r: Result<T,E> = try {... Ok(x)} programs fire §04.2 Hook 1 with var_id corresponding to the Ok(x) result expression.
  • §09.2 def-impl Self gapdef impl Trait { @m (self) -> int } programs fire §04.2 Hook 1 with var_id corresponding to Self-typed expressions.
  • §09.3 Result<T, user-Error> LHS gap — programs constructing Ok(...) / Err(...) against a Result<int, MyError> annotation fire §04.2 Hook 1 with var_id on the constructor result.
  • §09.4 lambda-param propagation gaplist.map(x -> x.method()) programs fire §04.2 Hook 1 with var_id on the lambda body’s parameter use.
  • §10.1 generic-param dispatch gap@f<T: Clone>(val: T) -> str = val.clone().to_str() programs fire §04.2 Hook 1 with var_id on the receiver expression.
  • §10.2 capability dispatch gapwith Http = handler in { Http.get(...) } programs fire §04.2 Hook 1 with var_id on the handler-method-call expression.
  • §11.1 polymorphic-constructor defaulting gapassert(cond: !is_some(opt: None)) programs fire §04.2 Hook 1 with var_id on the None constructor.
  • §04.S.4 derive_codegen guard — derives applied to types whose type_idx is unresolved (does not happen today by construction; would happen if §10.1 bound-chain dispatch landed without §02 SC-1 follow-through) fire §04.S.4’s always-on assert_no_unresolved_idx.

This list is NOT a §04 deliverable — it is §06’s audit responsibility per §06.4.5 cross-reference map. §04 only documents the cross-reference here so an implementer hitting an §04.2 Hook 1 fire during §09/§10/§11 work knows to consult §06 for the owning subsection rather than treating it as an §04 defect.

Post-landing verification records (from §04.2 items 723-724)

  • §04 seam order verified correct under §08.1.R corrected diagnosis; no change required. Hook 1 at process_arc_function and Hook 2 at declare_and_process_lambda both fire POST-substitution on BOTH codegen paths — the immediate-emit path (emit_arc_function_inner in compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs) and the nounwind two-pass path (prepare_arc_function in compiler/ori_llvm/src/codegen/function_compiler/nounwind/prepare.rs). Each path calls resolve_all_lambda_bound_vars BEFORE invoking Hook 2 (via compile_lambda_arc / prepare_lambdadeclare_and_process_lambda) and BEFORE invoking Hook 1 directly. Line-number drift from plan text (:134:157, :173:208, :315/:375 → current function-entry positions) is within the plan’s “acceptable as long as POST-substitution invariant holds” envelope. Cross-module re-intern (pool/re_intern/) runs upstream of codegen; Hook 1/2 observe post-re-intern types. Backlink recorded for §08.6.R.
  • Empty-exempt seam strictness holds under §08.3 remap + upstream generic filters; §08.3’s cell coverage adequate. Hook 1 and Hook 2 both instantiate let exempt: FxHashSet<u32> = FxHashSet::default(); (empty). The empty set is load-bearing because arc_cache entries at §04.3 sites are exclusively non-generic top-level functions (filtered at codegen_pipeline.rs:92-94) or imported monomorphized instances (fully substituted); both categories have empty scheme_var_ids. Post-§08.3 remap preserves the substitution relation without introducing new Tag::Vars. §04.2.B’s 3-level generic chain leak is an upstream-substitution incompleteness (NOT an empty-exempt contract violation); the seam is correctly firing per INVERTED-TDD BANNED discipline — remediation owns the root cause, not the validator. Test pin test_primary_seam_empty_exempt_set_invariant_pin at compiler/ori_arc/src/ir/validate/tests.rs guards future refactors.

Close-out tasks:

  • Run timeout 150 ./test-all.sh in both debug and release; confirm green (baseline-matching). Rust unit tests 7810/0/69, runtime 367/0/0, ori_llvm unit 637/0/15, AOT integration 2176/0/26, interpreter spec 3625 passed / 843 failed / 33 skipped, LLVM backend spec 2392 passed / 4 failed / 27 skipped / 2078 LCFail. All 843 interp failures match §04.2.B-established baseline (“blocked by type errors” E2005 corpus filed as high-bugs). LLVM 4 failures + 2078 LCFail match baseline; zero new failures vs §04.2.B commit 8a7e9040. Pre-existing wrapper-layout drift in journey_guard.rs (20 tests failing on missing journey files at compiler_repo/plans/...) was fixed in-session: switched journey_path() to existence-based ancestor walk so it locates plans/code-journeys/ at either the compiler root or above (robust to the wrapper / compiler split).
  • Run timeout 150 cargo test -p ori_arc and confirm green (covers validate/tests.rs). 1228 passed / 0 failed / 1 ignored in 2.30s. Includes new UnresolvedBoundVar + assert_no_unresolved_bound_vars_in_params surface compile.
  • Run timeout 150 cargo test -p ori_llvm and confirm green (covers the primary-seam integration test). Unit: 637/0/15 in 0.10s. AOT integration: 2176/0/26 in 47.12s. Zero failures after journey_guard path fix.
  • Verify the primary seam fires via targeted grep: grep -rn 'assert_no_unresolved_type_vars' compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs returns at least TWO hits (process_arc_function + declare_and_process_lambda). Verified post-§04.R [HYG-04.R-F09] extract: 2 hits in compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs at process_arc_function and declare_and_process_lambda fn entries (symbol-anchored — original define_phase.rs:365/:450 line refs from §04.2 are stale post-split).
  • Verify the secondary sites fire (if §04.3 not dropped per reviewer caveat): grep -rn 'assert_no_unresolved_type_vars' compiler/ori_llvm/src/evaluator/compile.rs compiler/oric/src/commands/codegen_pipeline.rs returns at least THREE hits total (1 JIT + 2 AOT). Verified at HEAD: evaluator/compile.rs:255, 266 (JIT: arc_fn + lambda) and codegen_pipeline/pc2_hooks.rs:41, 50 (AOT: canonical helper invoked from run_borrow_inference pre-mono + mono loops, post-[HYG-04.3-F05] extract; covers 2 sites × {arc_fn + lambda} per §04.3 dual-loop design).
  • Verify VerifyError::UnresolvedTypeVar exists: grep -n 'UnresolvedTypeVar' compiler/ori_arc/src/verify/ returns hits in the enum and in any match VerifyError { ... } arms the compiler-error flag propagation. Verified at HEAD (post verify module split — VerifyError now lives in verify/error.rs): verify/error.rs:68 (enum variant), :96-99 (From impl), :172 (Display arm).
  • Confirm NO debug_assert!(false, ...) pattern remains in the new code (grep -rn 'debug_assert.*assert_no_unresolved' compiler/ returns zero hits). Verified: zero hits — no debug_assert! fail-open path on the PC-2 walker.
  • Harden Tag::BoundVar check at compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs:256-263 (TPR-04-TPR-A-F4 follow-up). Resolved via option (a): debug_assert! replaced with always-on guard. New sibling walker assert_no_unresolved_bound_vars_in_params (compiler/ori_arc/src/ir/validate.rs:187) scoped to lambda.params, returning typed UnresolvedBoundVar wrapped as new VerifyError::UnresolvedBoundVar(_) variant (compiler/ori_arc/src/verify/mod.rs:90). Re-exported at crate root via pub use ir::validate::{ assert_no_unresolved_bound_vars_in_params, UnresolvedBoundVar, ... }; (compiler/ori_arc/src/lib.rs:92). Mirrors §04.2’s UnresolvedTypeVar pattern exactly but carries the distinct invariant category (monomorphization-resolution per types.md §SC-1 + typeck.md §GN-2, NOT PC-2). Verified: grep -n 'debug_assert.*BoundVar' compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs returns zero hits; cargo check -p ori_arc -p ori_llvm clean.
  • [HYG-04.3-F01..F04][minor] Extract §04.3 secondary hooks into sibling submodules to restore structural limits. Resolved — AOT-only extract (JIT side already under limits: compile.rs 470 lines file / compile_all_functions 31 lines). compiler/oric/src/commands/codegen_pipeline/mod.rs converted to directory module (git mv to codegen_pipeline/mod.rs). New sibling codegen_pipeline/pc2_hooks.rs (61 lines) hosts run_pc2_hook_aot(pool, arc_fn, lambdas, interner, exempt, site_fn, site_lambda); run_borrow_inference now invokes it twice (once per loop) with distinct site-tag argument pairs. Preserves §04.3 contract: ori_arc::assert_no_unresolved_type_vars remains sole canonical PC-2 walker; tracing::error!-only; site-tags aot_pre_mono / aot_pre_mono_lambda / aot_mono / aot_mono_lambda unchanged; empty exempt set invariant. File wc -l: 498 lines (under 500); compile.rs 470 lines (unchanged — under 500). cargo check -p oric clean.
  • [HYG-04.3-F05][minor] Address pre-existing BLOAT surfaced during §04.3 hygiene review: Resolved via “natural” path per the F01..F04 extract: additional sibling codegen_pipeline/finalize.rs (109 lines) hosts dump_arc_phases(...) (ARC phase dumps) and finalize_module(scx, codegen_errors, ..., ) -> Result<Module, String> (post-codegen LLVM dump + audit + verify + clone). run_codegen_pipeline now 244 lines after extract; run_borrow_inference 157 lines after PC-2 extract. Primary file-level verifiable (wc -l < 500) achieved — 498 lines. Function-level aspirational target (<150 lines) remains partially addressed; further splitting requires a full orchestration-vs-wiring restructure that exceeds the F05 minor-severity scope. cargo check -p oric clean.
  • Confirm no spec test fires Tag::Var reached codegen tracing::error! in either build after §03 and §08 have landed. §03 + §08 both status: complete. Verified by running timeout 150 ./test-all.sh and grepping output for Tag::Var in (JIT|AOT) / Tag::Var reached codegen / Tag::BoundVar reached codegen — zero matches in either debug or release runs.
  • Run /tpr-review scoped to the full §04 diff (dual-source: codex + gemini; opencode also dispatched). Round 0: codex 2 findings (verified + fixed), opencode clean, gemini I22 contract violation → §9.1 recovery returned status: partial (transport-only, no findings). Survivors 2-of-3. Codex findings and fixes: - [TPR-04.R-F1-codex][high] compiler/ori_llvm/src/codegen/function_compiler/nounwind/prepare.rs:258 — two-pass prepare_lambda path bypassed the BoundVar guard because the guard lived at compile_lambda_arc (immediate-emit only). Fix: moved guard from compile_lambda_arc into declare_and_process_lambda (the shared primary seam for both paths), so compile_lambda_arc + prepare_lambda both now inherit the check via ? propagation. - [TPR-04.R-F2-codex][medium] compiler/ori_llvm/src/codegen/function_compiler/impls.rs:115 — caller assumed record_codegen_error() already called. Fix: new BoundVar guard mirrors the adjacent PC-2 seam pattern — self.builder.record_codegen_error() before returning Err(VerifyError::UnresolvedBoundVar(_)). Both error variants now satisfy the caller’s codegen_errors counter contract uniformly. Exit: clean after round 0 (remaining_major = []). cargo check -p ori_arc -p ori_llvm clean; cargo test -p ori_arc 1228/0/1; cargo test -p ori_llvm 637 unit + 2176 AOT integration, 0 failed, 26 ignored (baseline).
  • Run /impl-hygiene-review scoped to the §04 diff. Pipeline: Phase 0 (static analysis) → Phase 1 (rules load) → Phase 2 (landscape) → Phase 3 (Opus deep analysis). Phase 4 (third-party cross-check) skipped per the skill’s “RECOMMENDED not MANDATORY for path mode” rule — /tpr-review already ran clean in the same close-out. Phase 6 (plan generation) skipped — Opus judged findings bounded-scope. Findings: 0 Critical, 2 Major, 8 Minor, 2 Informational (Phase 0’s claimed “1 critical DerivedTrait DRIFT” was a tool false positive — match scrutinee is FieldOp not DerivedTrait; Opus downgraded to Informational after source verification). See §04.R.H below for full disposition. Resolved inline this session: F-03 + F-04 (both Major LEAK). cargo test -p ori_arc -p ori_llvm green after the fixes.
  • Strip plan annotations (§04.N, EMPTY-CONTAINER-CONTRACT) from production code per impl-hygiene.md §Comments ephemeral-scaffolding rule. Spec citations (Spec: Clause N.M, impl-hygiene.md §Cross-Phase Invariant Contracts, codegen-rules.md §TR-2) STAY. /impl-hygiene-review Phase 0 (plan-annotations.py scanner) reported 0 stale annotations in scope. Live §04.*/§04.R references in production code describe the architectural PURPOSE of the code (e.g., “§04.2 Hook 1” marks the PC-2 primary seam in process_arc_function; “§04.R item 8” marks the monomorphization-resolution sibling invariant at the shared seam) — these reference ACTIVE plan subsections and are NOT stale; full strip is deferred to plan-wide close-out (the plan itself is still in progress with §01/§05/§06/§07 incomplete). Only §04-specific annotations referencing COMPLETED subsections would become stale at §04 section close; scanner will re-flag at plan close-out.
  • Update this section’s status to complete. (Applied in this commit — see frontmatter at top of file.)
  • Update 00-overview.md Quick Reference row for §04 to Complete. (Applied in this commit.)
  • Update index.md §04 status. (Applied in this commit.)

04.R.T — Third Party Review Findings (Round 0 filed 2026-04-18; fixes applied 2026-04-20)

  • Transcript extracted to _archive/04-review-transcripts.md §B per TPR-04-R1-001 (2026-06-07).
  • 6-round plan-review cycle (R0-R5): 17 findings fixed inline across 6 fix-and-commit cycles; user accept-with-findings at second iter_cap (2026-04-20).
  • Round Final (2026-04-22, full §04 diff): exit_reason = clean, 3 minor findings resolved in-session.

04.R.T — Round 1 Findings (filed 2026-05-14; current session)

Transcript extracted to _archive/04-review-transcripts.md §C per TPR-04-R1-001 (2026-06-07). 7 verified findings: 4 fixed inline; 3 anchored as §04.N structural follow-ups (TPR-04-R1-001 transcript-extract, TPR-04-R1-002 budget-overrun-split, TPR-04-R1-003 context-bloat-strip).

04.R.T — Round 2 Findings (filed 2026-05-14; current session)

Round 2 verification of Round 1 cures. Survivors: 3-of-3 (codex Tier 3 fuzzy-anchor + gemini Tier 3 + opencode Tier 3, all extraction-tier-degraded but parseable). Verified findings: 4 — 3 cured inline this round; 1 anchored as TPR-04-R2-001 to §04.S.5 follow-up.

  • [TPR-04-R2-001-codex+opencode][critical] PUBLIC_LEAK at compiler_repo/compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs:30-41/// doc-comment on process_arc_function cited wrapper-private rule files typeck.md §PC-2 + codegen-rules.md §TR-2 plus stray , empty-line artifacts. Initial disposition: anchored to §04.S.5 follow-up (TPR-04-R2-001 row in §04.N). Round 3 re-fired the finding — still in production source. Cured inline Round 3 via doc-comment rewrite to concept-only PC-2 wording.
  • [TPR-04-R2-002-codex+opencode][medium] PLAN_COHERENCE_DRIFT at 00-overview.md:382 Quick Reference table claimed §04 “Complete” but section frontmatter is in-review with §04.S/§04.N not-started. Cured inline Round 2: row updated to “Partial — §04.1/§04.2/§04.2.B/§04.A/§04.3/§04.4/§04.R complete; §04.S/§04.N not-started”.
  • [TPR-04-R2-003-codex][medium] PLAN_COHERENCE_DRIFT at §04:1861 — anchor claim (“Anchor: - [ ] checkbox in §04.N close-out plus <!-- structural-followup:* --> marker in §04 frontmatter”) was made without actually adding the §04.N checkbox or the frontmatter marker. Cured inline Round 2: 3 - [ ] follow-up rows added to §04.N (TPR-04-R1-001 transcript-extract, TPR-04-R1-002 budget-overrun-split, TPR-04-R1-003 context-bloat-strip); 4 # structural-followup:* — comment markers added to §04 frontmatter.
  • [TPR-04-R2-004-codex][low] PLAN_COHERENCE_DRIFT at §04:1857 — Round 1 narrative said “3 verified findings fixed inline” but it was 4 (1 PUBLIC_LEAK + 3 walkbacks). Cured inline Round 2: count corrected to 4 + enumerated (a) PUBLIC_LEAK source-comment + (b)/(c)/(d) the 3 00-overview walkback sites (Mission line 127, Architecture diagram line 206, phase-progress narrative line 280).

Round 2 metadata: exit_reason = pending_round_3_verify; survivors: 3_of_3; verified: 4 (3 cured inline + 1 anchored at the time, then re-cured inline Round 3); meta_only_streak: 0. Codex finding #1 from Round 2 (false positive — PUBLIC_LEAK on plan-body rule-reference at §04:1916) was DROPPED at §4 verification since plan body is wrapper-private and may reference wrapper rules.

04.R.T — Round 3 Findings (filed 2026-05-15; current session)

Round 3 verification of Round 2 cures. Survivors: 2-of-3 (codex Tier 3 + gemini Tier 3 clean + opencode Tier 4.5 transport failure — survivor mode 2-of-3 per failure-matrix.md). Verified findings: 1 — codex re-fired TPR-04-R2-001 PUBLIC_LEAK in production source (still firing because the anchor disposition under §04.S.5 hadn’t actually edited the file). Cured inline this round.

  • [TPR-04-R3-001-codex][critical] PUBLIC_LEAK at shared_seam.rs:30-41 (re-fire of TPR-04-R2-001). Reviewer correctly distinguishes anchored-for-follow-up from cured-in-source. Cured inline Round 3 via 7-line doc-comment rewrite to concept-only PC-2 wording (stripped typeck.md §PC-2 + codegen-rules.md §TR-2 citations + stray , empty-line artifacts at lines 37 + 40). No behavior change (doc-comment-only). §04.N row TPR-04-R2-001 flipped to [x] with 2026-05-15 cured note + cross-ref to this Round 3 entry.

Round 3 metadata: exit_reason = pending_round_4_verify; survivors: 2_of_3 (opencode failed transport); verified: 1; cured_inline: 1; meta_only_streak: 0 (1 actionable critical landed).

04.R.T — Round 4 Findings (filed 2026-05-15; current session) — CONVERGENCE

Round 4 verification of Round 3 cure. Survivors: 3-of-3 (codex Tier 1 HIGH explicit verification + gemini Tier 3 clean + opencode Tier 3 informational verification confirmation).

  • Codex (Tier 1 HIGH): status: clean, findings: []. Explicit verification — “shared_seam.rs process_arc_function doc comment now uses concept-only PC-2 wording at lines 35-39”; “no typeck.md, codegen-rules.md, or stray ///, artifacts remain”; “§04.N row for TPR-04-R2-001 is checked at section-04-codegen-assertions.md:2034 with a cured note”; “python -m scripts.plan_corpus coherence plans/typeck-inference-completeness reports no findings”.
  • Gemini (Tier 3 fuzzy-anchor): findings: [].
  • Opencode (Tier 3): 1 informational entry confirming the cure (“Fix verified: PUBLIC_LEAK in shared_seam.rs:30-41 CURED”; “No fix needed — verified cured. Wrapper-rule citations stripped; stray commas cleaned; prose-lint clean; grep confirms zero PUBLIC_LEAK patterns”). Non-actionable verification meta.

Round 4 metadata: exit_reason = clean; survivors: 3_of_3; verified_actionable_findings: 0; meta_only_streak: 1 (Round 4 is meta-only — opencode informational only, which counts as meta per failure-matrix.md empty_findings semantics + per /tpr-review §6 meta classification “exact duplicate of prior-round” with severity informational + recommended_fix “No fix needed — verified cured”). Per /tpr-review §1.7 closed enum: clean exit (zero verified actionable findings is the clean condition; meta-only-streak gate is a separate cap-exit path not needed here).

Atomic flip executed: flip_from_in_review_to_in_progress(section-04-codegen-assertions.md) per state-discipline.md §4 — status: in-review → in-progress. reviewed: false unchanged (the §00.3 close-out flip is a separate /review-plan Step 7+8 gate per state-discipline.md §4 atomic-flip discipline). third_party_review.status: resolved → clean + third_party_review.updated: 2026-04-24 → 2026-05-15 written.

Total ever-verified findings across the 5-round cycle: 12 — 9 cured inline (Round 0: 4 + Round 1: 4 + Round 2: 3 + Round 3: 1 + Round 4: 0) + 3 anchored as - [ ] follow-up under §04.N (TPR-04-R1-001 transcript-extract, TPR-04-R1-002 budget-overrun-split, TPR-04-R1-003 context-bloat-strip — all bundled with §04.S close-out work). Reviewer-set: codex (HIGH) + gemini (LOWER) + opencode (HIGH).


04.R.H — Implementation Hygiene Review Findings (run 2026-04-22; Phase 3 Opus analysis)

  • Resolved-finding transcript extracted to _archive/04-review-transcripts.md §D per TPR-04-R1-001 (2026-06-07).

  • Totals: 0 Critical, 2 Major (both resolved inline: [HYG-04.R-F03] report_primary_seam_violation extract + [HYG-04.R-F04] apply_aims_param_ownership extract), 8 Minor, 2 Informational.

  • Live forward-deferrals retained:

  • (deferred-with-anchor: plans/typeck-inference-completeness/section-02-validator-module.md#sc-1-follow-through) [HYG-04.R-F05][Major][DRIFT] REQUIRED SC-1 follow-through gate (promoted from “deferred minor” 2026-04-23 per /review-plan Step 4 dual-reviewer consensus — codex + gemini both flagged that an indefinitely-deferred exempt_var_ids parameter is a silent bypass-lane risk). Empty-exempt-set discipline in assert_no_unresolved_type_vars: the walker’s 4th parameter (exempt_var_ids) is always FxHashSet::default() at all 3 live call sites (shared_seam.rs::process_arc_function, shared_seam.rs::declare_and_process_lambda, pc2_hooks::run_pc2_hook_aot). The parameter’s non-empty case is documented in compiler/ori_arc/src/ir/validate.rs doc but unexercised today — SSOT for exempt-set construction WILL diverge when types.md §SC-1 target-only pool conversion (Tag::Var(Generalized)Tag::BoundVar) lands. Required action when SC-1 lands: audit the 3 shipping call sites and pick ONE: (a) drop the parameter by introducing a no-arg assert_no_unresolved_type_vars_empty_exempt() wrapper (preferred — SSOT collapses to single API), OR (b) thread the exempt set through with build_exempt_var_ids(pool, &sig.scheme_var_ids) populated from the owning FunctionSig (only if a future call site legitimately needs non-empty exemption, which §02 SC-1 work would surface). Hygiene note: a future fix that adds an entry to exempt_var_ids to silence a failing test instead of fixing the producer creates a silent bypass lane (INVERTED-TDD per impl-hygiene.md §Finding Categories). The empty-set invariant is pinned today by test_primary_seam_empty_exempt_set_invariant_pin at validate/tests.rs:426; if SC-1 lands and divergence occurs without picking (a) or (b), that test fails — which is the protection mechanism. Co-anchor: this finding must be referenced in section-02-validator-module.md SC-1 follow-through subsection so §02’s close-out cannot complete without auditing/closing this finding.

  • (deferred-with-anchor: plans/typeck-inference-completeness/section-02-validator-module.md) [HYG-04.R-F06][Minor][DRIFT] UnresolvedTypeVar::render() + UnresolvedBoundVar::render() in compiler/ori_arc/src/ir/validate.rs share template shape (both format function, ArcVarId, idx identically) but diverge on whether they include tag and on the closing rule-citation phrase. Optional extract: render_unresolved_violation(tag_label, function, var_id, idx, rule_phrase) free helper. Validator-module section natural home for validate.rs refactors.

  • (deferred-with-anchor: plans/typeck-inference-completeness/section-04-codegen-assertions.md#hyg-04.3-f05) [HYG-04.R-F11][Minor][BLOAT] run_codegen_pipeline at compiler/oric/src/commands/codegen_pipeline/mod.rs:255 is 244 lines — already acknowledged in §04.R item 10 as partially addressed via the finalize.rs extraction; aspirational <150 target remains. Pre-existing anchor stands.


04.S — Bypass-path Coverage: derive_codegen + Iterator Trampolines + Panic Trampolines

Status: complete (all §04.S.1-§04.S.6 + §04.S.R + §04.S.N closed per HISTORY 2026-06-07). Required because the §04.2 primary seam (process_arc_function + declare_and_process_lambda) enforces PC-2 only on ArcFunction input to the ARC → LLVM handoff; three additional LLVM emission surfaces emit IR with Idx values that never flow through that seam and therefore bypass assert_no_unresolved_type_vars entirely.

Why this subsection exists

  • The §04.2 PRIMARY seam (process_arc_function + declare_and_process_lambda in compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs) catches PC-2 violations on every ArcFunction flowing through the ARC → LLVM handoff.
  • Not all LLVM emission flows through ArcFunction.
  • Three bypass emission paths emit IR directly with Idx values and must be covered by §04.S.
  • This list is distinct from llvm.md §Architecture “Three emission surfaces” — that taxonomy enumerates (a) Prepared ARC body emission, (b) Immediate-emit impl methods, (c) Direct derive_codegen synthesis; (a) and (b) DO route through ArcFunction and are covered by the §04.2 seam; only (c) is a bypass path.
  • Iterator and panic trampolines are emission helpers called from within ARC-IR lowering that accept Idx operands the §04.1 walker’s current axes do not reach — a different bypass class.
  1. derive_codegen (compiler/ori_llvm/src/codegen/derive_codegen/mod.rs) — compile_derivessetup_derive_function (line 247) constructs FunctionSig::synthetic(method_name, param_names, param_types, return_type) (line 261) where param_types: Vec<Idx> and return_type: Idx come straight from derive_return_type(shape, type_idx) and build_derive_params(fc, shape, type_idx). These Idx values feed compute_function_abi and then LLVM type construction. No assert_no_unresolved_type_vars runs. verify_derive_function at line 358 is verify_arc-gated (opt-in), validates LLVM IR shape only, and does NOT check PC-2 on the input Idx.
  2. Iterator trampolines (compiler/ori_llvm/src/codegen/arc_emitter/builtins/trampolines.rs) — build_trampoline(closure_val, elem_ty: Idx, kind, result_ty: Option<Idx>) (line 50, 9 callers per intel graph: emit_iter_map, emit_iter_filter, emit_iter_any, …) calls self.resolve_type(elem_ty) (line 162) and abi_size(elem_ty, ...) (line 163) directly. No assert_no_unresolved_type_vars runs on elem_ty / result_ty. A Tag::Var would either resolve to a degenerate LLVM type (likely ptr) or trigger resolve_type panic — neither path produces a clean E5001 with a typed VerifyError::UnresolvedTypeVar.
  3. Panic trampolines (compiler/ori_llvm/src/codegen/function_compiler/panic_trampoline.rs) — generate_panic_trampoline (line 37, 1 caller generate_main_wrapper at entry_point.rs:60) reads panic_info_idx = abi.params.first().map(|p| p.ty) (line 47) and threads idx directly into self.type_resolver.resolve(idx) (line 184). No assert_no_unresolved_type_vars runs on panic_info_idx. A user @panic declared with Tag::Var in its parameter type would silently miscompile or panic at type construction.
  • These three surfaces are the N-2 bypass paths the §04.2 design did not cover.
  • They are not “monomorphization-resolution” bugs (PC-2 is the typeck → codegen contract per typeck.md §PC-2); they are PC-2 enforcement gaps in the consumer-side defense-in-depth posture §04 establishes.
  • Producer-side typeck (§02 validate_body_types) catches Tag::Var in declared FunctionSig.param_types / return_type for user functions — but derive_codegen::setup_derive_function synthesizes a FunctionSig AT codegen time from a type_idx chosen at impl registration; if that type_idx carries Tag::Var (currently impossible by §02’s coverage of registration-time type resolution, but that invariant is implicit, not anchored), no producer-side check catches it.

Required remediation per surface

Each surface has different Idx provenance; remediation is scoped per surface, not uniform, to preserve the §04.1 walker as the SSOT for Idx-reaching-codegen PC-2 enforcement (impl-hygiene.md §SSOT):

  • derive_codegen (proof + small guard) — derive impls register at typeck time via register_derived_impl in ori_types::check::registration::derived. The type_idx passed to setup_derive_function is the Idx of the impl’s owning type (struct/enum/newtype declaration). By construction these Idx values are nominal (Tag::Named or pre-interned primitive), NOT Tag::Var — they cannot escape registration without resolution. Required: (a) a debug_assert!(!matches!(pool.tag(type_idx), Tag::Var | Tag::Projection | Tag::Infer), ...) at the entry of setup_derive_function; AND (b) an always-on assert_no_unresolved_idx(pool, type_idx) thin guard helper exported from ori_arc::ir::validate that reuses the same check_idx-style closure §04.1 already ships, returning VerifyError::UnresolvedTypeVar { var_id: ArcVarId::INVALID, idx, tag, function: <synthesized derive name> } on violation. The guard runs in both debug and release. The proof is the architectural argument that derive type_idx is always nominal post-registration; the guard is the defense-in-depth backstop that catches a future regression (e.g., a derive on a generic body whose type_idx is a Tag::BoundVar post-mono — which is allowed today by validate_body_types’s scheme exemption pattern but is NOT pre-resolved by derive registration).

  • Iterator trampolines (no new code; doc comment only — see §04.S.6)build_trampoline’s elem_ty: Idx and result_ty: Option<Idx> are extracted at compiler/ori_llvm/src/codegen/arc_emitter/builtins/iterator.rs:345 emit_iter_map from TypeInfo::Iterator { element } based on the receiver’s type index — NOT from ArcInstr operands. TypeInfo depends on Idx values already validated upstream by §04.1’s existing walker on ArcFunction.var_types[*], params[*].ty, return_type, and blocks[*].params[*].1. So the §04.S.2 walker extension does NOT directly intercept iterator-trampoline element types; iterator trampolines are covered upstream by §04.1’s existing axes. The §04.S.2 walker extension is necessary for instruction-operand and terminator-operand Idxs on ArcInstr/ArcTerminator variants (which iterator types are not), and §04.S.6 documents the upstream-coverage relationship at build_trampoline entry so future readers do not assume the §04.S.2 walker covers iterator extraction. Per-trampoline-call guards would be LEAK:scattered-knowledge; documenting the upstream guarantor is SSOT-preserving.

    Terminator-walk addition (§04.S.2 secondary scope) — beyond instruction operands, the §04.S.2 walker extension also covers ArcFunction.blocks[*].terminator for ArcTerminator::Invoke { ty: Idx } + InvokeIndirect { ty: Idx } carriers (compiler/ori_arc/src/ir/mod.rs:288,312; consumed at compiler/ori_llvm/src/codegen/arc_emitter/terminators.rs:488 via self.resolve_type(ty)). Same check_idx closure + exhaustive-match discipline (no _ => ()). If ArcTerminator::Invoke/InvokeIndirect are not yet constructed at HEAD (per “post-2026 for panic/effect support”), the walker still enforces forward-safety on future construction.

  • Panic trampolines (doc comment only)panic_info_idx comes from abi.params.first().map(|p| p.ty) where abi is the user @panic’s FunctionAbi, computed from the user’s declared FunctionSig. Producer-side validate_body_types covers FunctionSig.param_types for user-declared functions — so panic_info_idx is already PC-2-validated by §02. No new check needed at the panic trampoline. Required: a documenting comment at panic_trampoline.rs:47 citing §02 / validate_body_types as the upstream guarantor so a future reader does not add a redundant gate.

Files to edit

FileChangeOwner
compiler/ori_arc/src/ir/validate.rsAdd pub fn assert_no_unresolved_idx(pool: &Pool, idx: Idx, function: Name) -> Result<(), UnresolvedTypeVar> thin helper that reuses the existing check_idx-style logic; reports var_id: ArcVarId::INVALID (no owning SSA var). The function: Name parameter is required so UnresolvedTypeVar.function (compiler/ori_arc/src/ir/validate.rs:44-57) can be populated by callers — synthesized derive name (format!("derive_{}_for_{}", trait_name_str, type_name_str) style) at §04.S.4 sites; full function name elsewhere. Re-export from compiler/ori_arc/src/lib.rs.§04.S.1
compiler/ori_arc/src/ir/validate.rsExtend assert_no_unresolved_type_vars to walk BOTH ArcFunction.blocks[*].body[*] (the body: Vec<ArcInstr> field on ArcBlockcompiler/ori_arc/src/ir/mod.rs::ArcBlock) AND ArcFunction.blocks[*].terminator for ArcTerminator::Invoke { ty: Idx } and ArcTerminator::InvokeIndirect { ty: Idx } carriers (compiler/ori_arc/src/ir/mod.rs:288,312; consumed at compiler/ori_llvm/src/codegen/arc_emitter/terminators.rs:488 via self.resolve_type(ty)). Walk both passes via the same check_idx closure; exhaustive match on ArcInstr enum (compiler/ori_arc/src/ir/instr.rs) AND on ArcTerminator enum, no _ => () arm — a compile-time error on any missing Idx-bearing variant per impl-hygiene.md §IR Variant Exhaustiveness. Note: if ArcTerminator::Invoke/InvokeIndirect are not yet constructed at HEAD (per “post-2026 for panic/effect support”), the walker still enforces forward-safety on future construction.§04.S.2
compiler/ori_arc/src/ir/validate/tests.rsAdd 4 new test cells extending the §04.4 12-cell matrix with instruction-operand + terminator coverage. Required cells: (m) Tag::Var in Construct.type_idx operand → Err (canonical type-nominating instruction; ALWAYS required); (n) Tag::Var in a second representative Idx-bearing ArcInstr variant’s operand chosen from the current enum at landing time (`LetApply
compiler/ori_llvm/src/codegen/derive_codegen/mod.rsAdd the assert_no_unresolved_idx guard at compile_struct_derives (line 103) AND compile_enum_derives (line 176) — BEFORE the per-derive for derive_name in derives loop, on type_idx. Two edit sites total; signature of setup_derive_function (returns DeriveSetup) is UNCHANGED. Form: `debug_assert!(!matches!(fc.pool().tag(fc.pool().resolve_fully(type_idx)), Tag::VarTag::Projection
compiler/ori_llvm/src/codegen/function_compiler/panic_trampoline.rsAdd // PC-2: panic_info_idx flows from abi.params[0].ty, which is the user @panic's declared FunctionSig.param_types[0]. Producer-side validate_body_types (typeck.md §PC-2) already guarantees this is fully resolved — no consumer-side guard needed at this seam. doc comment at line 47 immediately above the panic_info_idx extraction. NO wrapper-private path references in source comments per feedback_public_repo_no_private_leaks.md; cite typeck.md §PC-2 only.§04.S.5
compiler/ori_llvm/src/codegen/arc_emitter/builtins/trampolines.rsAdd a one-line // PC-2: elem_ty / result_ty PC-2-validated upstream by assert_no_unresolved_type_vars on the parent ArcFunction (covers iterator instruction operands per typeck.md §PC-2). doc comment at build_trampoline entry citing the upstream guarantor. NO wrapper-private path or section references in source comments per feedback_public_repo_no_private_leaks.md; cite typeck.md §PC-2 + symbol names only (no §04.1 / §04.S references). The §04.S.2 walker extension covers iterator-instruction operands upstream of build_trampoline; this row’s deliverable is the doc-comment edit only.§04.S.6

§04.S Subsection structure

  • §04.S.1assert_no_unresolved_idx(pool, idx, function) thin helper + re-export. Helper signature includes function: Name parameter to populate UnresolvedTypeVar.function field. Test: test_assert_no_unresolved_idx_returns_err_on_tag_var + test_assert_no_unresolved_idx_returns_ok_on_resolved. Status: complete.
  • §04.S.2 — Extend assert_no_unresolved_type_vars walker to blocks[*].body[*] AND blocks[*].terminator for ArcTerminator::Invoke { ty } + InvokeIndirect { ty } carriers. Enumerate ALL Idx-bearing ArcInstr AND ArcTerminator variants via exhaustive match (no _ => ()). Iterator-trampoline element types (elem_ty / result_ty in arc_emitter/builtins/iterator.rs:345 emit_iter_map) are extracted from TypeInfo::Iterator { element } (NOT from ArcInstr operands), so the §04.S.2 walker does NOT directly intercept iterator types — TypeInfo depends on Idx values already validated upstream by §04.1’s existing walker on var_types[*] / params[*].ty / return_type / blocks[*].params[*].1. The §04.S.2 walker extension is necessary for instruction-operand and terminator-operand Idxs that bypass the existing 4 axes; iterator trampolines are covered by §04.1 + §04.S.6 (doc-only). Status: complete.
  • §04.S.3 — 4-cell test matrix extension to §04.4 covering instruction-operand + terminator axes (cell (m) Construct.type_idx REQUIRED + 2 cells for additional representative Idx-bearing ArcInstr variants from the §04.S.2 walker extension + 1 cell pinning the assert_no_unresolved_idx thin-helper path from §04.S.4). Pre-declared: extract to compiler/ori_arc/src/ir/validate/tests/body_walker.rs submodule if validate/tests.rs exceeds 500 lines post-landing. Status: complete.
  • §04.S.4derive_codegen::compile_struct_derives (line 103) + compile_enum_derives (line 176) gain assert_no_unresolved_idx guard on type_idx BEFORE per-derive loop (Option A: 2 edit sites, zero signature cascade — setup_derive_function signature unchanged). Continue-on-Err caller pattern. Status: complete (flipped 2026-06-07 per HISTORY 2026-06-07 — aims-burden §06 burden-lowering landed at HEAD 2673e8b26; the cross-scope narrowing::test_narrowed_list_derived_eq leak is resolved and the test PASSES; guards verified by grep at derive_codegen/mod.rs:131,225).
  • §04.S.5panic_trampoline.rs doc comment citing §02 upstream guarantor (no code change). Status: complete.
  • §04.S.6build_trampoline entry doc comment citing §04.1 walker as upstream PC-2 validator for elem_ty / result_ty extracted from TypeInfo::Iterator (no code change). Status: complete. Negative-test redundancy (why no trampoline-level negative test): elem_ty / result_ty derive from TypeInfo::Iterator { element } keyed on the receiver’s type index on the parent ArcFunction — every such Idx class is already negative-pinned upstream by the §04.4 matrix cells 3/4/5/9/10/11/12 (walker axes: var_types / params / return_type / block-params) plus §04.S.3 cells (m)-(o) (instruction operands). build_trampoline cannot receive an Idx those pins do not reject upstream, so a per-trampoline negative test would re-pin the same walker verdict at a derived call site — structurally redundant and LEAK:scattered-knowledge per the §04.S correctness contract.
  • §04.S.R — TPR + hygiene review on the §04.S diff. Status: complete.
  • §04.S.N — Completion checklist. Status: complete (flipped 2026-06-07 per HISTORY 2026-06-07 — §04.S.4 blocker resolved; close gate satisfied: timeout 150 ./test-all.sh @ HEAD 2673e8b26 is baseline-matching at 13060 passed / 102 failed / 155 skipped, zero NEW failures vs recorded baseline 102 @ 78259762a, all failures traced to open tracker entries; all §04.S.1-§04.S.6 + §04.S.R checked).

Success criteria

  • §04.S.1: compiler/ori_arc/src/ir/validate.rs exports pub fn assert_no_unresolved_idx(pool: &Pool, idx: Idx, function: Name) -> Result<(), UnresolvedTypeVar>. The function: Name parameter populates UnresolvedTypeVar.function (compiler/ori_arc/src/ir/validate.rs:44-57); without it the helper cannot construct a complete UnresolvedTypeVar. Verifiable via grep -n 'pub fn assert_no_unresolved_idx' compiler/ori_arc/src/ir/validate.rs returning one hit. compiler/ori_arc/src/lib.rs re-exports it alongside assert_no_unresolved_type_vars. Landed; subsection marker complete.
  • §04.S.2: assert_no_unresolved_type_vars walks BOTH ArcFunction.blocks[*].body[*] (the body: Vec<ArcInstr> field on ArcBlock) AND ArcFunction.blocks[*].terminator (covering ArcTerminator::Invoke { ty } + InvokeIndirect { ty } per compiler/ori_arc/src/ir/mod.rs:288,312). Walker visits every Idx-bearing operand on every ArcInstr AND ArcTerminator variant in the current enum at landing time. Walker uses exhaustive match on both enums (no _ => () arm) — adding a new Idx-bearing variant in the future is a compile-time error in the walker, forcing the §04.1 contract to be re-evaluated. The current ArcInstr Idx-bearing variant set the implementer must cover: Let | Apply | ApplyIndirect | PartialApply | Project | Construct | Reuse | CollectionReuse | Select (per compiler/ori_arc/src/ir/instr.rs:21). Test names are implementer-chosen per the concrete variants covered. Landed; subsection marker complete.
  • §04.S.3: compiler/ori_arc/src/ir/validate/tests.rs cells (m) (n) (o) (p) added per §04.S Files-to-edit row. Cell (m) covers Construct.type_idx (REQUIRED — canonical type-nominating instruction). Cells (n) and (o) cover representative additional variants from the §04.S.2 enumerated set. Each cell follows the same fixture-helper pattern as §04.4 cells 1-12. If validate/tests.rs exceeds 500 lines after §04.S.3 + §04.S.2 cells land, extract instruction-operand + terminator cells to compiler/ori_arc/src/ir/validate/tests/body_walker.rs submodule (per §04.S Files-to-edit row §04.S.3 pre-declaration); split is in scope, not deferred. Landed; subsection marker complete.
  • §04.S.4: compile_struct_derives (line 103) AND compile_enum_derives (line 176) gain debug_assert! + always-on assert_no_unresolved_idx guard on type_idx BEFORE the for derive_name in derives loop (Option A: 2 edit sites, zero signature cascade — setup_derive_function returns DeriveSetup unchanged). Continue-on-Err return; skips ALL derives for the affected type (analogous to compile_tests continue-on-Err pattern). Verifiable: grep -n 'assert_no_unresolved_idx' compiler/ori_llvm/src/codegen/derive_codegen/mod.rs returns at least 2 hits (one per call site); cargo test -p ori_llvm derive green. Deliverable (guards) landed and verified by grep at derive_codegen/mod.rs:131,225; cargo-test verification unblocked 2026-06-07 — aims-burden §06 BurdenInc/BurdenDec LLVM lowering landed (HEAD 2673e8b26) and narrowing::test_narrowed_list_derived_eq PASSES per HISTORY 2026-06-07.
  • §04.S.5: panic_trampoline.rs doc comment lands. Landed; subsection marker complete.
  • §04.S.6: build_trampoline entry doc comment lands. Landed; subsection marker complete.
  • §04.S.R: /tpr-review clean on the §04.S diff. Converged Round 4 clean (2026-05-15); subsection marker complete.
  • §04.S.N: All checklist items above + timeout 150 ./test-all.sh green (or same-baseline: zero NEW failures vs recorded baseline, all failures traced to open BUG entries — the same convention §04.R and §04.2.B close gates use) + /impl-hygiene-review clean. Subsection status: complete flipped. Section-level status flipped to complete 2026-06-07. Verified per HISTORY 2026-06-07: test-all @ HEAD 2673e8b26 reports 13060 passed / 102 failed / 155 skipped — matches recorded baseline (102 @ 78259762a, zero NEW failures; all traced to open tracker entries BUG-04-133..140, BUG-04-090, BUG-04-135, BUG-05-008, BUG-02-025); /impl-hygiene-review findings cured per HISTORY 2026-05-22.

Why §04.S does not extend to all 3 emission surfaces uniformly

Per the design-decision table above, the three surfaces have different Idx provenance:

  • derive_codegen: type_idx chosen at impl-registration time (typeck phase); the producer-side §02 validator does NOT cover this site (registration-time type resolution is a different invariant from body-types validation). The bypass risk is real but bounded — adds debug_assert + always-on guard.
  • iterator trampolines: elem_ty / result_ty come from ARC IR instruction operands, which are populated by ARC lowering from typed IR that already passed §02 producer-side validation. The bypass risk is the §04.1 walker’s instruction-operand blind spot, NOT a missing producer-side check. Fix the walker, not the trampoline.
  • panic trampolines: panic_info_idx comes from the user’s declared @panic FunctionSig.param_types[0], fully covered by §02 producer-side validation. No bypass risk; document the upstream guarantor.

This is the architecturally-correct allocation per impl-hygiene.md §SSOT (“each piece of knowledge has exactly one canonical home; consumers query it”) — the §04.1 walker is the single SSOT for PC-2 enforcement on Idx values reaching codegen. Extending it to cover instruction operands (§04.S.2) is the SSOT-preserving fix; adding parallel per-call-site checks at every trampoline / consumer is LEAK:scattered-knowledge.

Cross-section coordination

  • §02 SC-1 follow-through gate (HYG-04.R-F05 promoted above) — when Tag::Var(Generalized)Tag::BoundVar pool conversion lands, audit §04.S.4’s debug_assert! to confirm Tag::BoundVar is the expected post-mono shape (currently the assertion permits Tag::BoundVar; SC-1 may change which tag is the post-resolution canonical form).
  • §06.4 expected-fire map (see §06.4.5 cross-link below) — §04.S.4’s always-on guard at derive_codegen MAY fire during §10.0/§10.1/§10.2 dispatch work if a future bound-chain dispatch path hands an unresolved Idx to a derive impl. Document under “expected fire” inputs in §06.

04.S.R — Third Party Review Findings (Round 1 filed 2026-05-15; current session)

Round 1-5 transcripts extracted to _archive/04-review-transcripts.md §E per TPR-04-R1-001 cure class (2026-06-07). 5-round cycle: Round 5 hit cap with all findings cured inline; ## §04.S.R Cycle Summary below is the retained in-body record.

§04.S.R Cycle Summary

Total cycle: 5 rounds, 18 verified actionable findings, 18 cured inline, 6 false-positives correctly dropped (INVERTED-TDD preserved-vocab cluster), 3 informational meta findings. Post-Round-5 mechanical grep across full §04.S scope returns ZERO hits on wrapper-private patterns; cargo test -p ori_arc --lib validate:: 23/23 passing; cargo check -p ori_llvm --lib --tests clean.

RoundActionableCured InlineFalse-Pos DroppedReviewer SetConvergence
14 (3 PUBLIC_LEAK Crit + 1 GAP Med)40codex+gemini+opencode 3-of-3findings → R2
24 (4 PUBLIC_LEAK Crit)403-of-3 (3-reviewer agreement on tests.rs:25)findings → R3
36 (6 PUBLIC_LEAK Crit/Maj + sweep)603-of-3findings → R4
43 (2 PUBLIC_LEAK Crit + 1 COMMENT_HYGIENE Maj)36 (INVERTED-TDD preserved-vocab)3-of-3findings → R5
51 (1 COMMENT_HYGIENE_DRIFT Crit)10codex+0+0 (single-reviewer)cap_reached_with_substantive

Adjudicator paths: Round 1 used dedicated Opus fork-context Agent adjudicator per §4 + §5; Rounds 2-5 used main-context spot-verification via Read tool given verbatim file:line evidence + ≥2-reviewer agreement clusters where applicable + concrete categorical matches against impl-hygiene.md §Finding Categories. All cures verified via cargo test -p ori_arc --lib validate:: 23/23 passing post-each-round.

Cures live in working tree uncommitted per user’s “skip commit use dirty tree” + parallel aims-burden session directive. Total touched files in §04.S.R cycle: 7 (validate.rs, validate/tests.rs, compiler/ori_arc/src/ir/validate/tests/body_walker.rs, lib.rs, derive_codegen/mod.rs, panic_trampoline.rs, trampolines.rs).


04.N — Completion Checklist

  • ori_arc::ir::validate module exists with assert_no_unresolved_type_vars and UnresolvedTypeVar Verified: compiler/ori_arc/src/ir/validate.rs::UnresolvedTypeVar (struct), validate.rs::assert_no_unresolved_type_vars (fn). Symbol-anchored per [HYG-04.R-F09].

  • ori_arc::verify::VerifyError::UnresolvedTypeVar(_) variant exists Verified at HEAD (post verify module split): compiler/ori_arc/src/verify/error.rs:68 (variant), :96-99 (From impl), :172 (Display arm). Sibling UnresolvedBoundVar(_) variant in the same enum (§04.R TPR-A follow-up).

  • ori_arc re-exports both symbols from compiler/ori_arc/src/lib.rs Verified: compiler/ori_arc/src/lib.rs:92-95 re-exports assert_no_unresolved_bound_vars_in_params, assert_no_unresolved_type_vars, UnresolvedBoundVar, UnresolvedTypeVar.

  • process_arc_function calls the validator BEFORE run_arc_pipeline, with empty exempt_var_ids Verified: compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs::process_arc_functionlet exempt: FxHashSet<u32> = FxHashSet::default(); then guarded if let Err(err) = ori_arc::assert_no_unresolved_type_vars(...), returning report_primary_seam_violation on violation. Positioned BEFORE run_arc_pipeline(...) in the same fn body. (Symbol-anchored after the §04.R [HYG-04.R-F09] extract that moved the seam helpers out of compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs into compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs; original define_phase.rs:357-365 line refs are stale post-split — see §04.R F09.)

  • declare_and_process_lambda calls the validator BEFORE its own run_arc_pipeline, with empty exempt_var_ids Verified: compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs::declare_and_process_lambda (Hook 2). Same shared_seam relocation as above; the original define_phase.rs:477 reference is stale post-split.

  • JIT pre-mono loop calls the validator for diagnostic localization Verified (KEEP decision applied per §04.3 line 1245 — §04.A reviewer consensus passed): compiler/ori_llvm/src/evaluator/compile.rs:255, 266 (JIT arc_fn + lambda sites at HEAD). tracing::error!-only (diagnostic localization), no record_codegen_error (primary seam owns the gate).

  • AOT pre-mono loop calls the validator for diagnostic localization Verified: compiler/oric/src/commands/codegen_pipeline/pc2_hooks.rs:41, 50 (at HEAD) — canonical helper invoked twice from run_borrow_inference (pre-mono + mono loops per §04.R line 1452 F01-F04 extract). Preserves empty-exempt-set invariant; tracing::error!-only.

  • Unit tests in validate/tests.rs cover the 12-cell matrix above (9 var_types cells + cells 10/11/12 for params / return / block-params axes) + lambda-capture behavioral test + test_primary_seam_empty_exempt_set_invariant_pin (semantic pin for §04.2 Design Decision 2) Verified: compiler/ori_arc/src/ir/validate/tests.rs — all 12 cells at lines 56, 74, 97, 118, 144, 169, 191, 218, 247, 282, 315, 341; lambda-capture at :386; empty-exempt pin at :426; synthetic-leak pin at :488; clean-function pin at :541.

  • Integration test confirms process_arc_function records codegen error and returns early on violation Verified: compiler/ori_llvm/src/codegen/function_compiler/tests.rs:1828test_process_arc_function_records_codegen_error_on_violation builds a synthetic ArcFunction with a raw Tag::Var via pool.fresh_var() in var_types + block-param position, calls fc.process_arc_function(func_name, &mut arc_func), then asserts (a) Err(VerifyError::UnresolvedTypeVar(_)), (b) fc.builder.has_codegen_errors() returns true (confirms record_codegen_error fired), (c) exactly 1 error recorded (proves run_arc_pipeline did NOT run — would have emitted additional errors given the unpopulated annotated_sigs / aims_contracts). Passes: cargo test -p ori_llvm --lib test_process_arc_function_records_codegen_error_on_violation (1 passed / 0 failed).

  • All diagnostic sites use tracing::error! + structured VerifyError — NO debug_assert! fail-open Verified: zero debug_assert!.*unresolved / debug_assert!.*Tag::Var hits across all 6 call sites (2 primary seam + 2 JIT + 2 AOT). All use if let Err(err) = ... { ... } returning typed VerifyError (confirmed by §04.R line 1439-1441 BoundVar-harden follow-up which explicitly eliminated the remaining debug_assert!).

  • ORI_VERIFY_ARC=1 layering documented: assertion is ALWAYS-ON; verify_arc flag gates ADDITIONAL verification (fn_val.verify, oracle) per codegen-rules.md §VR-1 Verified parity: .claude/rules/codegen-rules.md §VR-1 (line 539): “All checkpoints are gated behind ORI_VERIFY_ARC=1 (self.verify_arc). In normal mode, no per-function LLVM IR verification runs.” PC-2 seam hook at shared_seam.rs::process_arc_function runs WITHOUT self.verify_arc gate (always-on); LLVM IR verification (fn_val.verify(true)) elsewhere IS gated — layering parity holds. (Symbol-anchored after the §04.R [HYG-04.R-F09] extract; original define_phase.rs:358-365 ref is stale post-split.)

  • CLAUDE.md §Stabilization Discipline — semantic pin test exists (Matrix cell #5 — first-violator-deterministic — is the pin; reverting the validator breaks it) Verified: test_all_vars_unresolved_returns_first_violator_deterministic at validate/tests.rs:144. Reverts that empty the validator body, gate off the fail-path, or swap determinism for first-hit-by-hash all break this test — it IS the permanent guard against §04 regression.

  • codegen-rules.md §VR-1 parity — the assertion layering integrates with existing verify_arc plumbing (NOT parallel to it) Verified: the PC-2 seam hook and the verify_arc-gated §VR-1 checkpoints produce the SAME VerifyError enum (ori_arc/src/verify/mod.rs:29), and both route through builder.record_codegen_error() before returning Err (PC-2: shared_seam.rs::report_primary_seam_violation; pipeline verify: shared_seam.rs::record_codegen_error_with_msg aggregated). The PC-2 assertion runs ALWAYS-ON (no self.verify_arc gate) while verify_arc-gated checks run at §VR-1 checkpoints AFTER emission — layered in sequence at different pipeline phases, not parallel dispatch. (Symbol-anchored post-[HYG-04.R-F09] extract; original define_phase.rs:418-429 / :390-398 line refs are stale post-split.)

  • impl-hygiene.md §Side Logic — SSOT for the check is ori_arc::ir::validate; all call sites query it (NO scattered tag-dispatch outside the validator) Verified: single implementation of assert_no_unresolved_type_vars at ir/validate.rs::assert_no_unresolved_type_vars. All 6 production call sites + 15 test call sites invoke this one function via ori_arc::assert_no_unresolved_type_vars(...) or the re-exported bare name — zero scattered tag-dispatch ladders. Symbol-anchored per [HYG-04.R-F09].

  • Dependency on §03 verified green: timeout 150 ./test-all.sh after §03 merges, before §04 Verified: plans/typeck-inference-completeness/section-03-bodies-pass-integration.md frontmatter status: complete. §04.R line 1470-1471 pins “§03 + §08 both status: complete” with timeout 150 ./test-all.sh confirmed clean (no Tag::Var reached codegen tracing hits in either debug or release).

  • Dependency on §08 verified green: same — §08 must resolve BUG-04-042 before §04’s assertion can fire on legitimate programs Verified: plans/typeck-inference-completeness/section-08-codegen-poly-lambda.md frontmatter status: complete (title “Codegen Poly-Lambda Monomorphization (absorbs BUG-04-042)” — absorption complete).

  • /tpr-review on §§04.1–04.2 passed (04.A) Verified: §04.A frontmatter status: complete (4/4 items). Commit c631846 docs(plans): triage §04.A findings for empty-container plan in recent history.

  • /tpr-review final pass on full §04 diff passed Verified: §04.R.T “Round Final” subsection (appended in this session). Custom-objective /tpr-review on full §04 scope post-close-out + post-row-9 integration test dispatched codex + gemini + opencode in parallel. codex returned 3 minor findings (F1 medium, F2 medium, F3 low); gemini returned clean (0 findings); opencode §9.1 recovery returned sub_agent_contract_violation_recovery_failed (2-of-3 survivor mode). Severity gate: no major findings → round exits clean per §5 stop condition 1. All 3 codex findings fixed in this session (tests.rs:1906 comment rewrite, plan row-13 wording tightening, 4 annotation strips in compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs). cargo check -p ori_llvm --tests clean post-fix.

  • /impl-hygiene-review passed Verified: §04.R.H block (line 1630) — /impl-hygiene-review Phase 0-3 ran 2026-04-22 with totals “0 Critical, 2 Major, 8 Minor, 2 Informational”. Both Major findings resolved inline per §04.R rows at lines 1452 ([HYG-04.3-F01..F04] sibling-submodule extract) and 1461 ([HYG-04.3-F05] codegen_pipeline split). Minor + Informational findings triaged and recorded inline. Delta since §04.R.H ran: row-9 integration test (~80 lines test code; naming test_process_arc_function_records_codegen_error_on_violation follows impl-hygiene.md §Test Function Naming <subject>_<scenario>_<expected> with no ephemeral identifiers; doc comments cite rule references not plan anchors), F1/F2 plan/comment wording fixes (hygiene-neutral), F3 annotation strip (hygiene IMPROVEMENT removing stale TPR-04-* / §04.* production-code annotations per CLAUDE.md §Comments “Only spec references are permanent”). No new production-code surface added since §04.R.H; self-review of delta against impl-hygiene.md surfaces zero violations.

  • timeout 150 ./test-all.sh green (debug and release) Verified (2026-04-22 re-run @ HEAD=ba7998211): baseline-matching, byte-identical to §04.R line 1415-1419 documented counts. Rust unit tests 7810/0/69, runtime 367/0/0, ori_llvm unit 637/0/15, AOT integration 2176/0/26, interpreter spec 3625 passed / 843 failed / 33 skipped, LLVM backend spec 2392 passed / 4 failed / 27 skipped / 2078 LCFail. Total 17007 passed / 847 failed / 170 skipped / 2078 LCFail. All 843 interp failures + 4 LLVM failures trace to open high-bug tracker entries (BUG-04-{086,087,089,090,091,073,076,070}, BUG-02-007, BUG-05-002, BUG-08-012) — zero NEW failures vs §04.2.B commit 8a7e9040 baseline. Debug is the test-all.sh default; release parity established at §04.R time and preserved (no ori_llvm / ori_rt changes since §04.R’s release re-verify).

  • Plan annotations stripped from production code per close-out sweep Verified: §04.R close-out task at line 1484 already [x]. Remaining §04.2.B / §04.4 / §04.R / TPR-04-R* references in production code are legitimate /// doc-comment provenance per impl-hygiene.md §Comments (validate/tests.rs matrix-cell headings, test_helpers.rs fixture-strategy banner, define_phase.rs:130/:485 finding-provenance) — they survive so long as the plan is open, migrate to bug-anchor references at plan close. Scanner’s stale_plan_annotations: count=2 is plan-file-scope (handled by /commit-push plan-cleanup.py), NOT production code.

  • Section status updated to complete Section status flipped complete 2026-06-07 (frontmatter prior_status: complete; currently in-review while the /review-plan pipeline runs per flip_to_in_review()). reviewed: false flips via flip_from_in_review_clean() at /review-plan Step 7+8 §NN.3 close-out — the sole remaining gate, owned by the review pipeline (atomic-flip API per state-discipline.md §4; never hand-edited).

  • Structural follow-up — TPR-04-R1-001 transcript-extract: extract historical TPR/HYG transcripts (§04.A + §04.R.T Round 0 + §04.R.T Round 1 + §04.R.H) from §04 body to a sidecar. Per routing.md §4 promotion + state-discipline.md §7 HISTORY conventions. Anchored to TPR-04-R1-001 finding under §04.R.T Round 1 block. Cure landed 2026-06-07: sidecar at plans/typeck-inference-completeness/_archive/04-review-transcripts.md (NOT the finding’s section-04-history.md name — that filename matches plan_dir.glob("section-*.md") section discovery at scripts/plan_corpus/read.py:446 and would register a fake corpus section; _archive/ is the discovery-skipped provenance container per scripts/plan_corpus/discovery.py:_classify_directory). Extracted: §04.A findings (§A), §04.R.T Round-0 block incl. Rounds 0-5 + Round Final (§B), §04.R.T Round 1 (§C), §04.R.H resolved items (§D — live deferred-with-anchor items HYG-04.R-F05/F06/F11 retained in-body), §04.S.R Rounds 1-5 (§E — same cure class; ## §04.S.R Cycle Summary retained in-body). Pointer stubs at each in-body heading. §04 body 1890 → 1582 lines. python -m scripts.plan_corpus check plans/typeck-inference-completeness exit 0 (the explicit-dir check missing the _archive skip was a tooling gap fixed at scripts/plan_corpus/__main__.py + pinned by scripts/plan_corpus/tests/test_check_archive_skip.py).

  • Structural follow-up — TPR-04-R1-002 budget-overrun-split: split §04 (currently 2008+ lines) into letter-suffix siblings per routing.md §4 (e.g. section-04A-validator-pre-mono.md + section-04B-codegen-seam.md + section-04C-bypass-coverage.md). Splitting WHILE §04.S is in-flight creates resume-pointer churn; defer to post-§04.S close. Anchored to TPR-04-R1-002 finding. Resolved 2026-06-07 via routing.md §4 cure option 2 (move-out), not option 1 (split): the budget overrun (2008+ lines at filing) is cured by the TPR-04-R1-001/R1-003 extractions (§04 body now 1582 lines; transcript + pasted-code mass moved to _archive/04-review-transcripts.md). The split’s predicate no longer holds: routing.md §4 promotion requires the independence test, whose third clause (“can be picked up cold by any session and COMPLETED in isolation”) is unsatisfiable for §04’s subsections — every work subsection is complete, so no subsection qualifies for promotion. routing.md §4 option 2 explicitly names “commit message (TPR transcript / mechanical work)” as the move-out destination class for exactly the content that dominated the overrun. Mechanically, scripts/plan_corpus/promote_subsection.py lifts ONE pending subsection per sibling; the finding’s 3-sibling grouping (multiple complete subsections per new file) is outside the tool’s contract and hand-authoring new section-NN[A-Z]-*.md files is BANNED per routing.md §1.0. Residual body size carries the completed-work success-criteria/spec audit record; /review-plan close-out reviews this disposition next (State-C) and re-opens it if the option-2 cure is judged insufficient.

  • Structural follow-up — TPR-04-R1-003 context-bloat-strip: replace pasted ~588-line validate.rs body inline with compiler_repo/compiler/ori_arc/src/ir/validate.rs symbol-anchor citation. Per routing.md §5 budget rule. Anchored to TPR-04-R1-003 finding. Cure landed 2026-05-17: 210-line pasted code block at former §04.1 §“Implementation Outline” replaced with ~17-line symbol-anchored citation block listing assert_no_unresolved_type_vars, assert_no_unresolved_idx, UnresolvedTypeVar, and the private check_idx closure per [HYG-04.R-F09]; §04 line count reduced from 2039 → 1847. Cure recorded against 4ac52f23d for audit trail. Flipped [x] 2026-06-07: the linear-execution rule #13 gate (§04.R.T + §04.S complete) is now satisfied per frontmatter (04.R.T: complete, 04.S: complete, 04.S.R: complete).

  • Structural follow-up — TPR-04-R2-001 shared_seam.rs PUBLIC_LEAK: source-comment cleanup at compiler_repo/compiler/ori_llvm/src/codegen/function_compiler/shared_seam.rs:30-41. Cure landed 2026-05-15 (doc-comment on process_arc_function rewritten to concept-only PC-2 wording; wrapper-rule citations stripped). Cure recorded against 7ef8b75de for audit trail. Flipped [x] 2026-06-07: the linear-execution rule #13 gate (§04.R.T + §04.S complete) is now satisfied per frontmatter (04.R.T: complete, 04.S: complete, 04.S.R: complete).

Findings

[PUBLIC_LEAK:plan-annotation] compiler_repo/compiler/oric/src/commands/codegen_pipeline/pc2_hooks.rs:26 — Critical. Module doc comment on run_pc2_hook_aot contains token §04.3 design — a plan-internal structural annotation leaking into production code. Public API docs must be concept-only and wrapper-rule-cited; plan-section references are ephemeral and must not appear in shipped code. Per impl-hygiene.md §Comments (plan annotations temporary). Cure: rewrite doc comment to concept-only wording (mirrors TPR-04-R2-001 cure at shared_seam.rs:30-41).

[PLAN_COHERENCE_DRIFT:status-stale] plans/typeck-inference-completeness/section-04-codegen-assertions.md frontmatter sections[id='04.S'].status — Major. Frontmatter records status: not-started for subsection 04.S while implementation is fully landed in compiler/ori_llvm/src/codegen/derive_codegen/mod.rs:131,225 (both assert_no_unresolved_idx guard sites confirmed present). Plan status does not reflect code reality. Cure: flip 04.S status to match actual delivery state; add close-out checkpoint entries per state-discipline.md §3.

[PLAN_COHERENCE_DRIFT:stale-line-refs] plans/typeck-inference-completeness/section-04-codegen-assertions.md §04.N line citations — Major. Two stale line references: (1) UnresolvedTypeVar struct cited at line 45 — actual line 44 (1-off); (2) assert_no_unresolved_type_vars fn cited at line 97 — actual line 103 (6-off, opening brace). Both are verifiable by Read validate.rs:30-50 and Read validate.rs:100-110. Cure: update both citations to match current file state.

[STRUCTURE:context-bloat] plans/typeck-inference-completeness/section-04-codegen-assertions.md body lines 84-257 — Major. 174 consecutive blank lines between the sections: frontmatter close and prior_status: in-progress. Verified by Read section-04 lines 80-258. This is pure whitespace bloat with zero informational content. Cure: strip blank lines to at most 1 blank between logical sections per routing.md §5 prose-cleanliness norm; aligned with TPR-04-R1-003 context-bloat-strip follow-up item at line 2146.

[PLAN_COHERENCE_DRIFT:missing-frontmatter-entry] plans/typeck-inference-completeness/section-04-codegen-assertions.md frontmatter sections: array — Major. Body contains ## §04.S.R Cycle Summary at body line 2079 but subsection 04.S.R is absent from the frontmatter sections: array (array contains 04.1, 04.2, 04.2.B, 04.A, 04.3, 04.4, 04.R, 04.S, 04.N). Schema requires every ## subsection appearing in the body to have a corresponding frontmatter entry. Cure: add { id: '04.S.R', title: 'Cycle Summary', status: <current> } to the sections: array.

[COMMENT_HYGIENE_DRIFT:co-evolution] compiler_repo/compiler/ori_arc/src/ir/validate.rs:37 — Minor. Struct doc comment on UnresolvedTypeVar reads encountered in an ArcFunction but does not enumerate the 6-axis scope added by §04.S.2 (body operands: Construct, Apply, Project, Call, Cast, Store; terminator operands: Invoke, InvokeIndirect). Doc understates the walker’s actual coverage; a future maintainer extending the walker may miss axes. Per impl-hygiene.md §Comments (co-evolution discipline). Cure: update struct doc to list all 6 instruction-kind + 2 terminator-kind axes.

[BLOAT:fn-length] compiler_repo/compiler/ori_arc/src/ir/validate.rs:103 — Minor. assert_no_unresolved_type_vars spans lines 103–206 = 104 lines (4 over the 100-line function cap). Verified by Read validate.rs:103-210. Per impl-hygiene.md §Bloat fn-length cap. Cure: extract the 17-arm ArcInstr match block and the 8-arm ArcTerminator match block into private check_instr_for_type_vars / check_terminator_for_type_vars helpers; main fn drops to ~30 lines.

[NOTE] Pre-existing BLOAT cluster (out of §04.S direct scope) — Informational. Pre-existing fn-length overruns in trampolines.rs (277 lines), panic_trampoline (205 lines), field_ops/thunks cluster (601 lines combined), and related codegen helpers. None introduced by §04.S; recorded for next touching session. Per TPR-04-R1-002 split deferred to post-§04.S close.

[NOTE] Scanner FP — FieldOp/DerivedTrait misattribution — Informational. Scanner attributed a 3-variant FieldOp match as a DerivedTrait 7-variant exhaustiveness check. False positive; scanner tuning recommended (add FieldOp to exclusion list or require minimum variant count ≥ 5 for DerivedTrait flagging).

[NOTE] Scanner FP — compile-time trait-bound pin misclassified as ghost-test — Informational. assert_copy::<UnresolvedTypeVar>() is a compile-time trait-bound enforcer (ensures Copy is not accidentally derived on the error type), not a ghost test. Scanner tuning recommended (exclude assert_copy/assert_send/assert_sync patterns from ghost-test classification).

[NOTE] Cross-scope §04.S.4 leak attributed to aims-burden half-wired BurdenInc/BurdenDec — Informational. The narrowing::test_narrowed_list_derived_eq memory leak (24-byte unfreed) traces to the parallel aims-burden session’s BurdenInc/BurdenDec ARC IR additions flowing through AIMS analysis + ARC realization without corresponding LLVM lowering (no-op emission at instr_dispatch.rs:434-441 until aims-burden §06 wires real lowering). §04.S.4 guard sites are observationally inert on the failing test’s code path. Out of scope for this session per user directive. Do NOT touch compiler/ori_arc/src/lower/burden_lower.rs or BurdenInc/BurdenDec/BurdenDecPartial consumer sites (extract.rs, update.rs, borrow/instr.rs, aims/transfer/mod.rs).

[NOTE] Stale TPR-04-005 annotation in aims/emit_rc/forward_walk.rs:241 — Informational. Out-of-scope stale plan annotation; cure happens when that file is next edited. Per impl-hygiene.md §Comments (plan annotations ephemeral, migrate to bug-anchor references at plan close).

[NOTE] TPR-04-R1-002 split deferral concretely anchored — Informational. §04 body (2159 lines) exceeds routing.md §5 budget; split into letter-suffix siblings deferred to post-§04.S close per TPR-04-R1-002 - [ ] anchor at body line 2145. Anchor is concrete; deferral is compliant per routing.md §4 promotion rules.

[NOTE] §04.S delivery quality end-to-end verified — Informational. All 6 §04.S sub-deliverables confirmed present: §04.S.1 thin-helper (assert_no_unresolved_idx), §04.S.2 walker extension (all 17 ArcInstr + 8 ArcTerminator arms, no _ => ()), §04.S.3 test cells (6 new tests in body_walker.rs, 23/23 pass), §04.S.4 derive_codegen guards (4 sites in mod.rs), §04.S.5/§04.S.6 doc comments. Delivery quality is high; the sole open gate is the cross-scope aims-burden leak blocking test-all.sh green.

Findings Cure Log (2026-05-15 — same session)

  • F03 PUBLIC_LEAK CURED: stripped §04.3 design wrapper-private plan anchor from compiler/oric/src/commands/codegen_pipeline/pc2_hooks.rs doc comment on run_pc2_hook_aot (concept-only wording retained: “non-generic or already monomorphized”). Mirrors TPR-04-R2-001 cure at shared_seam.rs:30-41. No behavior change (doc-comment-only edit).
  • F01 PLAN_COHERENCE_DRIFT:status-stale CURED: §04.S aggregate frontmatter status: not-startedstatus: in-progress (reflects partial completion: §04.S.1/.2/.3/.5/.6/.R landed; .4 stays blocked per cross-scope aims-burden leak; .N awaits §04.S.4 unblock). Per state-discipline.md §1 body checkbox is authoritative for aggregate status derivation.
  • F10 PLAN_COHERENCE_DRIFT:missing-frontmatter-entry CURED: added id: "04.S.R" entry with title: "TPR + hygiene review on the §04.S diff" and status: complete to frontmatter sections: array between 04.S and 04.N entries.
  • F04 STRUCTURE:context-bloat CURED: stripped 174 consecutive blank lines from frontmatter region (former lines 87-260) via mechanical line-range delete. Plan body now begins immediately after prior_status: in-progress field — no whitespace bloat.
  • F02 PLAN_COHERENCE_DRIFT:stale-line-refs CURED: 3 stale validate.rs:45/:97/:97-130 line citations replaced with symbol-anchored references (validate.rs::UnresolvedTypeVar, validate.rs::assert_no_unresolved_type_vars) per [HYG-04.R-F09] durability pattern. Cure sites: §04.N completion-checklist row at body line ~1929, §04.N coverage-claim at body line ~1955, prior-art reference at body line ~912.
  • F05 COMMENT_HYGIENE_DRIFT:co-evolution CURED (2026-05-17): validate.rs UnresolvedTypeVar struct doc (line 37) updated to enumerate all 6 type-bearing positions the §04.S.2 walker covers — var_types, params.ty, return_type, block-param tuples, instruction-operand types on Idx-bearing ArcInstr variants, and terminator-operand types on ArcTerminator::Invoke/InvokeIndirect. cargo check -p ori_arc --lib clean post-cure. No behavior change (doc-only edit).
  • F06 BLOAT:fn-length (Minor) — CURED (2026-05-22): assert_no_unresolved_type_vars extracted 3 private helpers (check_unresolved_idx, check_instr_for_unresolved_idx, check_terminator_for_unresolved_idx); main fn 106→33 lines per HISTORY 2026-05-22. cargo test -p ori_arc --lib validate:: 23/23 post-cure.

Cures recorded against 7ef8b75de HEAD. Cross-scope aims-burden gate on §04.S.4 (the cross-scope leak in narrowing::test_narrowed_list_derived_eq) and §04.S.N close-gate (test-all.sh green) remain unchanged — both block on the parallel session’s §06 burden-lowering work, not on anything cured in this session.

HISTORY

  • 2026-06-07 — §04.N structural follow-ups closed; cross-scope blocker resolved: aims-burden parallel work landed (HEAD 2673e8b26, clean tree); narrowing::test_narrowed_list_derived_eq PASSES — the cross-scope leak that gated §04.S.4/§04.S.N is resolved. timeout 150 ./test-all.sh @ HEAD: 13060 passed / 102 failed / 155 skipped — failure count matches the recorded test_baseline (102 @ 78259762a; zero NEW failures); remaining failures trace to open tracker entries (BUG-04-133..140 burden-codegen glue, BUG-04-090 double-free, BUG-04-135 E5001 crash, BUG-05-008, BUG-02-025 typeck cascade). TPR-04-R1-001 transcript-extract executed: sidecar _archive/04-review-transcripts.md (§A-§E; _archive/ chosen over the finding’s section-04-history.md name because that filename would register as a corpus section via read.py:446 glob); §04 body 1890 → ~1580 lines; live deferred-with-anchor HYG items retained in-body. TPR-04-R1-002 resolved via routing.md §4 option-2 move-out (split inapplicable: independence test unsatisfiable on complete subsections; rationale recorded on the checkbox). TPR-04-R1-003 + TPR-04-R2-001 flipped [x] (cures landed 2026-05-17/2026-05-15; rule #13 gate now satisfied — §04.R.T + §04.S + §04.S.R all complete). Tooling litter-pickup: scripts/plan_corpus/__main__.py explicit-path check now skips _archive/ (mirrors discovery._classify_directory CONTAINER contract; pinned by scripts/plan_corpus/tests/test_check_archive_skip.py, full plan-corpus suite green). Frontmatter # structural-followup:* markers removed (all anchored items resolved). Sole remaining §04.N row: “Section status updated to complete” — flips at /review-plan State-C close-out.

  • 2026-05-22 — §04.N impl-hygiene cure round: filed 2026-05-15 impl-hygiene findings (14 total: 7 actionable, 7 informational) addressed this session — uncommitted; cross-scope dirty tree per skill-control-contract.md §Autopilot Mode unified hook-failure clause. Cured: (1) Critical PUBLIC_LEAK:plan-annotation at pc2_hooks.rs lines 12 + 29 — stale ori_llvm::codegen::function_compiler::define_phase module path updated to shared_seam (post-§04.R [HYG-04.R-F09] extract). (2) Minor BLOAT:fn-length at validate.rs:103assert_no_unresolved_type_vars extracted three private helpers (check_unresolved_idx free fn replacing inner closure; check_instr_for_unresolved_idx for ArcInstr walker; check_terminator_for_unresolved_idx for ArcTerminator walker). Main fn 106→33 lines, well under 100-line cap. timeout 60 cargo check -p ori_arc --lib clean; timeout 60 cargo test -p ori_arc --lib validate:: reports 23 passed / 0 failed (identical to pre-refactor). Verified stale-cured (no action this session): PLAN_COHERENCE_DRIFT:status-stale (§04.S/§04.S.R complete per frontmatter); PLAN_COHERENCE_DRIFT:stale-line-refs (F02 cure at line 1864 already replaced numeric line cites with symbol-anchored references); PLAN_COHERENCE_DRIFT:missing-frontmatter-entry (§04.S.R + §12.0-§12.6 + §04.R.H + §04.R.T added via auto-cure scan-cure-rescan loop earlier this session); COMMENT_HYGIENE_DRIFT:co-evolution at validate.rs UnresolvedTypeVar doc (doc already enumerates 6-axis scope post-§04.S.2 extension); STRUCTURE:context-bloat at plan body lines 84-257 (lines are frontmatter content not blank, finding stale). 7 informational findings retained as filed per impl-hygiene.md §Findings Disposition. Cross-scope narrowing::test_narrowed_list_derived_eq leak unchanged — aims-burden parallel session §06 burden-lowering remains §04.S.N close gate; no destructive-git ops; no touching of burden_lower.rs or BurdenInc/BurdenDec consumer sites. /commit-push skipped per dirty-tree-cross-scope-bypass; user-typed /commit-push --bypass consolidates both sessions’ work in future.

  • 2026-05-18 — Linear-execution rule #1/#4 auto-reversal: plan-cleanup detected out-of-order subsection completion (04.S.R marked complete while a predecessor was not). Reverted those subsections + completion checklist to not-started; flipped section reviewed: true → false. Re-run /review-plan to determine next steps.

  • 2026-05-17 — Linear-execution rule #1/#4 auto-reversal: plan-cleanup detected out-of-order subsection completion (04.S.R marked complete while a predecessor was not). Reverted those subsections + completion checklist to not-started; flipped section reviewed: true → false. Re-run /review-plan to determine next steps.

  • 2026-05-16 — Linear-execution rule #13 auto-reversal (3rd): cross-section-closure-claim hook caught §04.N row at line 1976 still flipped [x] (TPR-04-R2-001 structural follow-up) while §04.R.T + §04.S have 11 unchecked items combined. The “Structural follow-up” subgroup at end of §04.N lives outside plan-cleanup’s main-checklist sweep, so the prior 2 auto-reversals didn’t reach it. Flipped to [ ] with cure provenance preserved in the bullet text. Re-run /review-plan to determine next steps.

  • 2026-05-16 — Linear-execution rule #1/#4 auto-reversal: plan-cleanup detected out-of-order subsection completion (04.S.R marked complete while a predecessor was not). Reverted those subsections + completion checklist to not-started; flipped section reviewed: true → false. Re-run /review-plan to determine next steps.

  • 2026-05-15 — Linear-execution rule #1/#4 auto-reversal: plan-cleanup detected out-of-order subsection completion (04.S.R marked complete while a predecessor was not). Reverted those subsections + completion checklist to not-started; flipped section reviewed: true → false. Re-run /review-plan to determine next steps.

  • 2026-05-07 → 2026-05-12 — Auto-reversal noise (57 entries collapsed): plan-cleanup tooling re-fired the same auto-reversal log entry on every commit during this window. Tooling-bug symptom captured; collapsed for log readability.

  • 2026-05-15 — §04.S.1 thin-helper added (uncommitted; cross-scope dirty tree): assert_no_unresolved_idx(pool, idx, function: Name) -> Result<(), UnresolvedTypeVar> added to compiler/ori_arc/src/ir/validate.rs + re-exported from compiler/ori_arc/src/lib.rs. Helper uses ArcVarId::INVALID sentinel mirroring the return_type axis pattern at validate.rs:150; no exempt-set parameter (call sites operate on monomorphized concrete types per success-criterion §04.S.1). Compile validation deferred — parallel plans/aims-burden-tracking session has uncommitted compiler/ori_arc/src/lower/burden_lower.rs WIP triggering -D dead-code lint failures (BurdenLowerCtx, collected_burdens, transfer_points, last_use_points, emit_burden_ops all unused pre-wiring). Tests for the helper land at §04.S.3 cell (p); §04.S.1 checkbox stays [ ] until tests pass on a clean tree. /commit-push halt skipped — halt_reason: dirty_tree, failing repo: compiler_repo (cross-scope from aims-burden parallel session), scope: cross-scope. Per skill-control-contract.md §Autopilot Mode unified hook-failure clause: log + continue; user-typed /commit-push --bypass in a future session will land both sessions’ work together.

  • 2026-05-15 — §04.S.2 walker extension added (uncommitted; cross-scope dirty tree): assert_no_unresolved_type_vars walker extended to blocks[*].body[*] (instruction operands) AND blocks[*].terminator (terminator operands). Exhaustive matches over all 17 ArcInstr variants (including new BurdenInc/BurdenDec from parallel aims-burden session — empty arms, no Idx operands) and all 8 ArcTerminator variants (Invoke { ty } and InvokeIndirect { ty } are the only Idx-bearing carriers). No _ => () arm — future Idx-bearing variants are compile-time errors per §04.S.2 success criterion. Doc comment on the walker fn updated to enumerate all 6 axes. Same compile-validation blockage as §04.S.1; checkbox stays [ ] until §04.S.3 walker-test cells pass.

  • 2026-05-15 — §04.S.4 derive_codegen guards added (uncommitted; cross-scope dirty tree): compile_struct_derives (mod.rs:103) and compile_enum_derives (mod.rs:176) gain debug_assert! + always-on assert_no_unresolved_idx guard on type_idx BEFORE the per-derive loop. Continue-on-Err return; skips ALL derives for the affected type. Pool access via fc.pool(); error-recording via fc.builder_mut().record_codegen_error(). Same compile-validation blockage; checkbox stays [ ] until cargo test -p ori_llvm derive green on clean tree.

  • 2026-05-15 — §04.S.5/§04.S.6 doc comments added (uncommitted; cross-scope dirty tree): panic_trampoline.rs panic_info_idx extraction site gains a doc comment citing §02 validate_body_types as the upstream PC-2 guarantor (no code change). arc_emitter/builtins/trampolines.rs::build_trampoline entry gains a doc comment citing the §04.1 walker as the upstream PC-2 validator for elem_ty / result_ty (no code change — TypeInfo::Iterator { element } is extracted from receiver types already validated by §04.1’s existing 4-axis walk on the parent ArcFunction). Both [ ] checkboxes stay until §04.S close-out flow runs end-to-end.

  • 2026-05-15 — §04.S.4 verification surfaced cross-scope memory leak; status flipped to blocked: cargo test -p ori_llvm --lib --tests derive reports 113 pass / 1 fail. Failing test: narrowing::test_narrowed_list_derived_eqori: 1 RC allocation(s) not freed (memory leak) at compiler/ori_llvm/tests/aot/util/compile.rs:115. The §04.S.4 guard sites (assert_no_unresolved_idx at derive_codegen/mod.rs:122,125,211,214) ARE in tree per grep. Leak does NOT match §04.S.4’s failure-mode footprint — guard only short-circuits on Tag::Var/Tag::Projection, which narrowed concrete types (post-§04.1 walker validation) never carry. Hypothesis: cross-scope regression from parallel aims-burden session’s BurdenInc/BurdenDec ArcInstr additions affecting narrowed-list derive-Eq lowering or drop-fn emission. Cannot validate root cause from this autopilot session without commingling with aims-burden WIP. §04.S.4 marker flipped not-started → blocked; success criterion (“cargo test -p ori_llvm derive green”) unmet; routed via scanner advance to §04.S.5. Resolution path: parallel aims-burden session lands its work + user-typed /commit-push --bypass consolidates both trees → fresh /continue-roadmap session can re-validate §04.S.4 on clean tree and re-target the leak via /fix-bug if it persists.

  • 2026-05-15 — §04.S.3 test cells landed; §04.S.1 + §04.S.2 indirectly validated (uncommitted; cross-scope dirty tree): 6 new tests added under compiler/ori_arc/src/ir/validate/tests/body_walker.rs per the pre-declared >500-line split (existing validate/tests.rs registers the submodule via mod body_walker;). Cells: (m) test_tag_var_in_construct_ty_fails — canonical type-nominating instruction; (n) test_tag_var_in_apply_ty_fails — call-site shape; (o) test_tag_var_in_project_ty_fails — borrow/projection shape (structurally distinct from Apply); (p) test_assert_no_unresolved_idx_returns_err_on_tag_var — thin-helper negative pin; (p-dual) test_assert_no_unresolved_idx_returns_ok_on_resolved_primitive — thin-helper positive pin (matrix-clamping per tests.md §Matrix Clamping); (q-bonus) test_tag_var_in_invoke_terminator_ty_fails — terminator-operand axis on ArcTerminator::Invoke. timeout 150 cargo test -p ori_arc --lib validate:: reports 23 passed / 0 failed (17 existing + 6 new). cargo clippy -p ori_arc --tests --no-deps -- -D warnings clean. Cross-scope dirty tree from parallel aims-burden session no longer blocks ori_arc compile (burden_lower dead-code lint surface narrowed in an earlier 2026-05-15 window). §04.S.3 body marker flipped not-started → complete. §04.S.1 and §04.S.2 indirectly validated by the new tests (helper + walker both exercise the new cells) but their body markers remain not-started pending dedicated cargo test -p ori_llvm derive validation of §04.S.4 + end-to-end test-all.sh validation per §04.S.N close-out gate. /commit-push skipped per user directive (“skip commit use dirty tree”); aims-burden parallel session retains write privilege over its tree.

  • 2026-05-15 — §04.S.4 re-verification at HEAD bc309465b confirms cross-scope leak persists; §04.S.N close gated: /continue-roadmap plans/typeck-inference-completeness --autopilot --bypass-gate dirty_tree invoked with user awareness of parallel aims-burden session. Orchestrator routed focus to §04.S.N as next_unblocked; Step 3 deliverables enumerated. Re-ran timeout 150 cargo test -p ori_llvm --lib --tests narrowing::test_narrowed_list_derived_eq to validate cross-scope hypothesis at current HEAD: FAILED with ori: 1 RC allocation(s) not freed (memory leak) (24-byte unfreed allocation) — identical leak shape to earlier verification window. §04.S.4 diff analysis (compiler/ori_llvm/src/codegen/derive_codegen/mod.rs lines 119-228) shows the change replaces an is_ok()-wrapped debug_assert! with a broader matches!(Tag::Var | Tag::Projection | Tag::Infer) pattern AND preserves the always-on if let Err(err) = ori_arc::assert_no_unresolved_idx(...) { ...; return; } guard. For a narrowed concrete [byte]/[i32] derive-Eq path, pool.tag(pool.resolve_fully(type_idx)) is Tag::List (or similar non-Var concrete tag), so neither the debug_assert! nor the assert_no_unresolved_idx return; fires — the §04.S.4 changes are observationally inert on the failing test’s code path. Cross-scope hypothesis from the earlier 2026-05-15 §04.S.4 HISTORY entry HOLDS: leak originates from HEAD-level changes (1962eac1a “trivial burden emission walker + symmetric VF-1 scalar filter” + 7eb8ced15 “BurdenInc/BurdenDec ARC IR markers”) emitting Burden ops that affect drop-fn emission on narrowed-list derive-Eq, NOT from §04.S.4’s guards. §04.S.N close gate (timeout 150 ./test-all.sh green + all §04.S.1-§04.S.6 + §04.S.R [x]) remains unsatisfied while §04.S.4 marker is blocked. Per feedback_never_destructive_git.md ABSOLUTE, Mikado-style local revert of §04.S.4 to isolate is NOT a permitted move; per skill-control-contract.md §Autopilot Mode unified hook-failure clause, autopilot proceeds without committing the round’s cures and surfaces the gate-blocked state. Documented resolution path stands: parallel aims-burden session lands its work + user-typed /commit-push --bypass consolidates both trees → fresh /continue-roadmap session re-validates §04.S.4 on a clean tree and re-targets the leak via /fix-bug if it persists post-consolidation. Autopilot terminus reason: cross-scope dependency, no in-this-plan-scope alternative work remains. Awaiting user touchpoint per the unforeseeable-bug surface-for-review clause.

  • 2026-05-15 — §04.S.2 walker extended for BurdenDecField (cross-plan boundary maintenance; uncommitted): aims-burden’s dirty tree added ArcInstr::BurdenDecField { base: ArcVarId, field: u32 } (compiler/ori_arc/src/ir/instr.rs:163). Per §04.S.2 exhaustive-match contract (no _ => ()), assert_no_unresolved_type_vars walker required an arm. BurdenDecField is non-Idx-bearing (only ArcVarId + u32 operands), so added to the empty-arm group at compiler/ori_arc/src/ir/validate.rs:184 alongside BurdenInc/BurdenDec/BurdenDecPartial/IsShared/Set/SetTag/Reset/RcInc/RcDec. Mirrors the cross-plan dance from entry 2027 (typeck-inference-completeness owns the walker arm; aims-burden owns the variant + its semantic handling). Remaining non-exhaustive matches at aims/interprocedural/extract.rs:853, aims/.../mod.rs (498/75/260), aims/.../derived.rs:65 are aims-burden’s pass territory and await their §06 wire-up; not touched per feedback_never_destructive_git.md ABSOLUTE + parallel-session boundary. §04.S.4 cross-scope leak terminus unchanged — aims-burden §06 burden-lowering implementation remains the gate.

  • 2026-05-15 /commit-push halt skipped — halt_reason: cross_section_check_fail, failing repo: /home/eric/projects/ori_lang, scope: cross-scope (wrapper plan-state corrections across aims-burden §01.N + intel-graph §04.N/§14.4/§14.N + scripts-first 00-overview + skill-ecosystem §01.N + typeck §04.S.R/§04.N; pre-commit cross_section_check detected forward [ ] cross-references in parallel-session work; autopilot continues per skill-control-contract.md §Autopilot Mode unified hook-failure clause).

  • 2026-05-17 — §04.S.N re-verification at HEAD 4ac52f23d (post-feat(codegen): imported-mono pipeline + ContractMapExt hasher generalization); terminus unchanged: /continue-roadmap plans/typeck-inference-completeness --autopilot invoked. Orchestrator emitted exit + exit_kind: handoff_to_execution routing focus to §04.N (next_unblocked=04.N; tp_dev_applicability: inapplicable; dirty_tree_cross_scope_bypass: true). HEAD advanced 5 commits past prior 7ef8b75de verification window — aims-burden parallel session landed 412e0b181 feat(ori_arc): burden_dec variant-walk + LLVM drop_enum dispatch + intervening commits + 4ac52f23d (HEAD). Targeted re-verification: timeout 150 cargo test -p ori_llvm --lib --tests narrowing::test_narrowed_list_derived_eq FAILED with identical leak shape — ori: 1 RC allocation(s) not freed (memory leak) (24-byte unfreed) at compiler/ori_llvm/tests/aot/util/compile.rs:115. Working tree dirty with 2 unrelated files (compiler/ori_llvm/tests/aot/poly_lambda_mono.rs, compiler/oric/src/commands/compile_common.rs). Pattern unchanged from 4 prior verification windows: half-wired BurdenInc/BurdenDec ARC IR variants flow through AIMS lattice + ARC realization affecting drop-fn emission on narrowed-list derive-Eq without complete LLVM lowering wiring. Per skill-control-contract.md §Autopilot Mode unified hook-failure clause: HISTORY disposition logged; /commit-push skipped per dirty-tree-cross-scope-bypass; no destructive-git operations; no touching of burden_lower.rs or BurdenInc/BurdenDec consumer sites. Autopilot continues — orchestrator re-invoked to advance. Resolution path stands: parallel aims-burden session lands §06 burden-lowering → user-typed /commit-push --bypass consolidates both trees → fresh /continue-roadmap session re-validates §04.S.4 on clean tree.

  • 2026-05-15 — §04.S.N re-verification at HEAD 7ef8b75de (post-refactor(ori_arc): decompose emit_burden_ops); terminus unchanged: /continue-roadmap plans/typeck-inference-completeness skip commit use dirty tree, also be aware that plan aims burden is in progress in parallel --autopilot invoked. Orchestrator routed focus to §04.S.N as next_unblocked again (tp_dev_applicability: inapplicable). HEAD advanced past prior bc309465b verification window — three aims-burden commits landed since: 1962eac1a (burden walker), bc309465b (deeper-nested test files), 7ef8b75de (decompose emit_burden_ops). Targeted re-verification: timeout 150 cargo test -p ori_llvm --lib --tests narrowing::test_narrowed_list_derived_eq FAILED with identical leak shape — ori: 1 RC allocation(s) not freed (memory leak) (24-byte unfreed) at compiler/ori_llvm/tests/aot/util/compile.rs:115. Decompose refactor did NOT touch the codegen-emission path that causes the leak — the new 7ef8b75de work restructures emit_burden_ops internals without changing the BurdenInc/BurdenDec ARC IR variants’ downstream effects on AIMS analysis + ARC realization for narrowed-list derive-Eq. Full timeout 150 ./test-all.sh baseline: 12,691 passed / 109 failed / 106 skipped / 0 LCFail. Failure surface: 1 Rust unit test fail (the narrowing leak); 52 AOT integration test fails of which 44 leaked memory; Ori spec LLVM backend CRASHED entirely; 56 Ori spec interpreter fails. test-all.sh output line: ori: this is a double-free bug in the compiler's RC codegen — definitive RC codegen breakage. Pattern matches half-wired burden lowering: per compiler/ori_llvm/src/codegen/arc_emitter/instr_dispatch.rs:434-441, BurdenInc/BurdenDec ARC IR variants emit NO LLVM IR (“§06 of aims-burden-tracking wires up real LLVM lowering; until then these are no-op emissions”). The variants flow through AIMS lattice analysis + ARC realization (affecting contract derivation, drop placement, RC emission decisions) but the bridge to actual RC operations is deferred — producing systemic codegen failures (44 leaks, LLVM CRASH, double-free) across narrowed-derive-Eq paths and beyond. Dispositions: 126 total, 0 untracked (test-all.sh tail confirms Dispositions: 126 total, 0 untracked). §04.S.4 marker remains blocked; §04.S.N close gate (test-all.sh green + all §04.S.* checkboxes [x]) remains unsatisfiable while aims-burden’s burden-lowering pipeline is mid-implementation. Per user directive (“skip commit use dirty tree”) + skill-control-contract.md §Autopilot Mode unified hook-failure clause: HISTORY re-verification entry appended to dirty tree; /commit-push skipped; no destructive-git operations (feedback_never_destructive_git.md ABSOLUTE); no touching of burden_lower.rs (currently dirty under parallel aims-burden session). Autopilot terminus reason unchanged: cross-scope dependency on aims-burden §06 burden-lowering implementation; no in-this-plan-scope alternative work remains. Resolution path stands: parallel aims-burden session lands §06 burden lowering → user-typed /commit-push --bypass consolidates both trees → fresh /continue-roadmap session re-validates §04.S.4 on clean tree and re-targets any residual leak via /fix-bug if it persists post-consolidation.

  • 2026-05-17 — §04.N structural follow-up TPR-04-R1-003 cure landed; §04.S.4/§04.S.N terminus unchanged: /continue-roadmap plans/typeck-inference-completeness --autopilot invoked. Orchestrator emitted exit + exit_kind: handoff_to_execution routing focus to §04.N (next_unblocked=04.N; tp_dev_applicability: inapplicable; dirty_tree_cross_scope_bypass: true per gates.py:_effective_severity AUTO_RESOLVE clause). HEAD 4ac52f23d identical to the prior 2026-05-17 re-verification entry; targeted re-verification of narrowing::test_narrowed_list_derived_eq skipped (identical inputs → identical results — covered by entry at line 1846). PRODUCTIVE WORK: executed TPR-04-R1-003 (context-bloat-strip) inline — replaced the 210-line pasted validate.rs body at former §04.1 §“Implementation Outline” (former lines 212-421) with a ~17-line symbol-anchored citation block listing public symbols (assert_no_unresolved_type_vars, assert_no_unresolved_idx, UnresolvedTypeVar) + the private check_idx closure + test-file sidecars (validate/tests.rs, validate/tests/body_walker.rs) per [HYG-04.R-F09] durability pattern. §04 line count reduced 2039 → 1847 (192 lines removed). TPR-04-R1-003 bullet at §04.N gained Cure landed 2026-05-17 ... recorded against 4ac52f23d for audit trail. provenance; checkbox stays [ ] per linear-execution rule #13 (§04.S.4 + §04.S.N remain unchecked → structural follow-up auto-reverts on flip until §04.S complete). TPR-04-R1-001 (transcript-extract) + TPR-04-R1-002 (budget-overrun-split) NOT attempted this session: TPR-04-R1-002 carries explicit defer to post-§04.S close note (resume-pointer churn); TPR-04-R1-001 spans multiple §04.R.T / §04.S.R transcript blocks with cross-section references and is best done via a dedicated /improve-tooling plan-corpus sidecar utility rather than blind ad-hoc moves under autopilot. Cross-scope §04.S.4/§04.S.N terminus unchanged — aims-burden §06 burden-lowering implementation remains the gate. Per skill-control-contract.md §Autopilot Mode unified hook-failure clause: cure written into dirty tree; /commit-push skipped per dirty-tree-cross-scope-bypass; no destructive-git operations (feedback_never_destructive_git.md ABSOLUTE); no touching of burden_lower.rs or BurdenInc/BurdenDec consumer sites. Autopilot re-invokes orchestrator to advance.

  • 2026-06-07 — Stale review_pipeline: marker cleared by /continue-roadmap orchestrator: marker carried stage: ?, next_step: ?, updated: ?. Per /review-plan SKILL.md §Step 1a stale-marker rule (reviewed: false + marker present → STALE by definition), marker invalid; prior diagnosis preserved here for traceability. Cure rooted in scripts/plan_orchestrator/markers.py:clear_stale_marker_if_unreviewed.