Section 15D: Bindings & Types
Goal: Implement binding syntax changes and type system simplifications across six approved proposals.
Source proposals (all approved):
compiler_repo/docs/ori_lang/proposals/approved/checks-proposal.mdcompiler_repo/docs/ori_lang/proposals/approved/as-conversion-proposal.mdcompiler_repo/docs/ori_lang/proposals/approved/simplified-bindings-proposal.mdcompiler_repo/docs/ori_lang/proposals/approved/remove-dyn-keyword-proposal.mdcompiler_repo/docs/ori_lang/proposals/approved/index-assignment-proposal.mdcompiler_repo/docs/ori_lang/proposals/approved/mutable-self-proposal.md
Phase contracts (load-bearing, do NOT relocate work across phases without amending these SSOTs first):
typeck.md §EX-17— index/field assignment desugar runs in the type checker, not canon.canon.md §2 row 3-4— same; surface desugars are eliminated before canonical IR.canon.md §7.1Invariant 5 — new capabilities extend the unified model, never spawn parallel paths.section-03-traits.md §3.11— object-safety machinery (already shipped) is reused by §15D.4, not re-implemented.spec/08-types.md—As<T>/TryAs<T>semantics; lossyasrejection.
Ownership map (
owns_crates: []is correct — this section is a phase-local audit; cross-cutting work is owned upstream — verified against each upstream section’s frontmatterowns_crates:2026-05-06):
ori_lexer_core,ori_lexer,ori_parse— owned by §00-parserori_ir,ori_types— owned by §01-type-systemori_canon,ori_arc(AIMS pipeline),ori_repr,ori_llvm— owned by §21A-llvmori_eval,ori_patterns— owned by §23-evaluatorori_registry— owned by §03-traitsori_fmt,ori_diagnostic,ori_test_harness,ori_stack,ori_compiler,oric,ori_lsp— owned by §22-tooling
Test-file convention: any
tests/spec/...ortests/compile-fail/...path that does not yet exist on disk is a plan deliverable — the implementer creates the file as part of the corresponding checkbox. The path is the WHERE annotation, not a citation of existing code.
Subsection dependency (encoded in body prose since
SubsectionEntryschema does not yet carry inter-subsection edges): §15D.6 (Mutable Self) is BLOCKED-BY §15D.5 (Index/Field Assignment) becausestate.f = vinside method bodies depends on §15D.5’s field-assign desugar landing first./continue-roadmapMUST execute §15D.5 to completion before opening §15D.6.
15D.1 Function-Level Contracts: pre() / post()
Proposal: proposals/approved/checks-proposal.md (semantics), proposals/approved/block-expression-syntax.md (syntax)
Function-level pre() and post() contract declarations for defensive programming. Contracts go between the return type and =:
@divide (a: int, b: int) -> int
pre(b != 0)
post(r -> r * b <= a)
= a div b
// Multiple conditions
@transfer (from: Account, to: Account, amount: int) -> (Account, Account)
pre(amount > 0 | "amount must be positive")
pre(from.balance >= amount | "insufficient funds")
post((f, t) -> f.balance == from.balance - amount)
post((f, t) -> t.balance == to.balance + amount)
= {
// ... body ...
}
Key Design Decisions
- Function-level placement: Contracts go on the declaration between return type and
= - Multiple declarations: Use multiple
pre()/post()declarations (not list syntax) |for messages: Custom messages usecondition | "message"syntax- Scope constraints:
pre()can only access function parameters and module-level bindings;post()can access the result via lambda parameter - Void return: Compile error if
post()used on a function returningvoid - Check modes deferred:
check_mode:(enforce/observe/ignore) deferred to future proposal
Implementation
Status note (2026-05-06): Parser is shipped — call site at
ori_parse/src/grammar/item/function/mod.rs:131invokesparse_contracts()whose definition begins at line 221 of the same file. Blast-radius producer gap is the type-checker validation pass atori_types/src/check/bodies/functions.rs(currently does NOT enforcepre()isbool,post()is a lambda, or void-return rejection). Implementation order below MUST be: (a) failing-test matrix → (b) typeck validation → (c) desugar/codegen → (d) interpreter+LLVM parity verification.
-
TDD-first: Write failing test matrix BEFORE implementation
- Matrix dimensions: contract type (
pre/post) × condition shape (literal-true / literal-false / param-ref / lambda-with-result / void-return / scope-violation) × backend (interpreter / LLVM) - WHERE:
tests/spec/patterns/checks.ori,tests/spec/patterns/check_messages.ori,tests/compile-fail/checks/{pre_not_bool,post_not_lambda,post_void_return,pre_scope}.ori - Semantic pin: at least one positive
pre()test that ONLY passes when the panic on false fires; one negative pin that rejects apre()returningintinstead ofbool.
- Matrix dimensions: contract type (
-
Implement (DONE): Parser parses
pre()andpost()on function declarationsori_parse/src/grammar/item/function/mod.rs—parse_contracts()at line 221 returns(Vec<PreContract>, Vec<PostContract>). Contracts parsed between return type and=.- AST:
PreContract { condition, message, span }andPostContract { params: Vec<Name>, condition, message, span }inori_ir/src/ast/items/function.rs(verified —PostContractcarriesparams: Vec<Name>because the lambda may be(a, b) -> condwith multiple binders for tuple-returning functions).FunctionDefhaspre_contractsandpost_contractsfields. - Parser tests:
ori_parse/src/grammar/item/function/tests.rs:144(test_pre_contract_basic),:159(test_pre_contract_with_message),:172(test_post_contract_basic),:187(test_post_contract_tuple_params). Plan items previously labelledNEEDS TESTSwere stale per blind-spots Round 1.
-
Implement (DONE): Parser supports
| "message"custom message syntaxPreContract.message: Option<Name>.PostContract.message: Option<Name>. Parser handles|separator.
-
Implement: Type checker validates
pre()condition isbool- WHERE:
ori_types/src/check/bodies/functions.rs— addvalidate_pre_contracts()at the end of body-checking - Diagnostic: new error code in
ori_diagnostic/src/error_code/mod.rs(E2xxx range — coordinate with §00-parser owner before allocating) - Rust Tests:
ori_types/src/check/bodies/tests.rs— pre type validation - Ori Tests:
tests/compile-fail/checks/pre_not_bool.ori
- WHERE:
-
Implement: Type checker validates
post()isT -> boollambda whereTmatches return type- WHERE: same module as the
pre()validator; share helper that assertsTag::Functionshape - Rust Tests:
ori_types/src/check/bodies/tests.rs— post type validation - Ori Tests:
tests/compile-fail/checks/post_not_lambda.ori
- WHERE: same module as the
-
Implement: Type checker errors when
post()used on void-returning function- Rust Tests: same module; void-return rejection test
- Ori Tests:
tests/compile-fail/checks/post_void_return.ori
-
Implement: Scope checker —
pre()can only access parameters and module-level bindings (no body locals leak in via shadowing)- WHERE:
ori_types/src/check/bodies/functions.rs— restricted scope walker on the contract expression tree - Rust Tests:
ori_types/src/check/bodies/tests.rs— pre scope validation - Ori Tests:
tests/compile-fail/checks/pre_scope.ori
- WHERE:
-
Implement: Desugar
pre()/post()contracts to conditional-panic blocks at function entry/exit — performed ONCE in the type checker (orori_canonif typeck-rewrite tooling cannot host it; choose at implementation time but commit to a single phase). Percanon.md §7.1Invariant 5 (unified model) andcanon.md §1phase ordering, BOTH backends consume the desugaredCanExprform — no per-backend desugar logic.- WHERE:
ori_types/src/check/bodies/functions.rs(preferred — typeck rewrite, same module as the validators above) ORori_canon/src/desugar/(fallback — canonical-IR rewrite if typeck rewrite tooling cannot host it). Pick ONE; do NOT split across both. - Backend rule:
ori_evalandori_llvmsee the post-desugarCanExpr(entry-panic-block + exit-panic-block already inlined into the function body) and emit it identically — NO contract-aware code in either backend. - Rust Tests: typeck/canon desugar tests in the chosen module’s
tests.rs;ori_llvm/tests/aot/contracts.rsverifies the post-desugar shape compiles without backend-specific contract handling. - Ori Tests:
tests/spec/patterns/checks_desugaring.ori
- WHERE:
-
Implement: Desugar embeds source text for default error messages
- WHERE: same module as the desugar above; thread the contract expression’s
Spansource text into the synthesized panic call before the rewrite reachesCanExpr. - Rust Tests: same test module — source-text embedding present in the rewritten body
- Ori Tests:
tests/spec/patterns/checks_error_messages.ori
- WHERE: same module as the desugar above; thread the contract expression’s
-
Verify: All new tests pass in BOTH debug and release builds (matrix verification)
-
Verify: Interpreter and LLVM produce identical observable behavior for every test (
ORI_CHECK_LEAKS=1clean for memory-touching cases) -
/tpr-reviewpassed (this subsection only) — see§15D.shared-close-out -
/impl-hygiene-reviewpassed (after TPR clean) — see§15D.shared-close-out -
Subsection close-out (15D.1) — see
§15D.shared-close-out -
/sync-claudesection-close doc sync — see§15D.shared-close-out -
Repo hygiene check — see
§15D.shared-close-out
15D.2 as Conversion Syntax
Proposal: proposals/approved/as-conversion-proposal.md
Replace int(), float(), str(), byte() with as / as? keyword syntax backed by As<T> and TryAs<T> traits.
AOT GAP:
lower_cast()atori_arc/src/lower/collections/mod.rs:320interns__castand emitsApply(dst, __cast, [val]), but__casthas zero registration inori_ir/src/builtin_constants/protocol/(verified — no entry inprotocol/mod.rs) and zero handler inori_llvm/src/codegen/arc_emitter/apply_protocols.rs(no match arm for"cast"). Every AOTassilently link-fails or crashes at runtime. This subsection’s primary deliverable is closing that gap.
DISPOSITION DRIFT:
tests/spec/expressions/type_conversion.oriis 458 lines, 457 of which are//-prefixed (one substantive// TODOcomment). All test cases commented out. Re-enabling them is a §15D.2 deliverable, NOT a separate#skip— the file is currently inert.
EVALUATOR/SPEC DRIFT:
eval_can_castatori_eval/src/interpreter/can_eval/operators.rspermitsfloat -> inttruncation.spec/08-types.md:1299-1308REJECTS lossyfloat → intat compile time under bothasandas?— the spec migration path is explicittruncate()/round()/floor()/ceil()methods, NOTas?. The hardcoded primitive cast path in the evaluator MUST move to trait dispatch viaAs<T>/TryAs<T>AND drop thefloat → inttruncation entirely (noAs<int> for float, noTryAs<int> for floatimpl); reversing that order cementsLEAK:duplicated-dispatchbetween eval (hardcoded) and LLVM (whatever lands in__cast).
// Before (special-cased positional args)
let x = int("42")
let y = float(value)
// After (consistent keyword syntax)
let x = "42" as? int
let y = value as float
Implementation Order — Trait First, Then Backends
The architecturally correct order (per canon.md §7.1 Invariant 5 and typeck.md §EX-2 registry-driven dispatch):
- Define
As<T>/TryAs<T>traits in prelude. - Type checker resolves
as/as?against the trait registry — single dispatch path. - Evaluator routes through the same registry lookup; the hardcoded
eval_can_castprimitive ladder is removed. - LLVM codegen registers
__castprotocol + handler so the resolved trait method has a real backend. - Spec test corpus is uncommented; all paths verified in BOTH backends.
Reversing steps 2 and 3 (or skipping step 2 entirely as the current shipped code does) cements parallel dispatch.
TDD-first Matrix (write BEFORE implementation)
-
TDD-first: Failing test matrix
- Dimensions: source type × target type ×
asvsas?× backend (interpreter / LLVM) - Source × target pairs (per
spec/08-types.md§8.11.2-§8.11.5):int → float(lossless viaAs<float>)int → byte(range-checked viaTryAs<byte>)byte → int(lossless viaAs<int>)char → int(codepoint value, lossless viaAs<int>)int → char(valid-codepoint check, fallible viaTryAs<char>)str → int(fallible viaTryAs<int>)str → float(fallible viaTryAs<float>)str → bool(fallible viaTryAs<bool>)
- Lossy
float → intis REJECTED at compile time by bothasandas?perspec/08-types.md:1299-1308. NOT a positive matrix cell. Verified via dedicated negative pin:tests/compile-fail/as_lossy_float_to_int.orirejects bothf as intandf as? int; spec migration hint points users atf.truncate()/f.round()/f.floor()/f.ceil(). - Each pair: positive test (works) + negative pin (
asrejected when onlyTryAsis implemented;as?rejected when onlyAsis implemented) - WHERE:
tests/spec/expressions/type_conversion.ori(un-comment + extend),tests/compile-fail/{as_fallible_conversion,as_not_implemented,try_as_not_implemented,as_lossy_float_to_int}.ori
- Dimensions: source type × target type ×
Lexer
-
Implement (DONE):
askeyword tokenTokenKind::Asinori_lexer/src/keywords/mod.rs:50. Test at line 183.
Parser
-
Implement (DONE): Parse
expression as Typeandexpression as? Typeori_parse/src/grammar/expr/postfix.rs:120checks forTokenKind::As;?afterassetsfallible: true. ProducesExprKind::Cast { expr, ty, fallible }.
Trait Definition
-
Implement: Define
As<T>andTryAs<T>traits incompiler_repo/library/std/prelude.ori- Required signatures:
@as (self) -> TforAs<T>;@try_as (self) -> Option<T>forTryAs<T>(perspec/08-types.mdandas-conversion-proposal.md) - WHERE: prelude additions; register in
ori_registry::BUILTIN_TYPESpercanon.md §6“Builtin type behavior” - Rust Tests:
ori_types/src/check/registration/tests.rs— As/TryAs registration
- Required signatures:
Type Checker
-
Implement:
infer_cast()inori_types/src/infer/expr/operators.rs(currently line 466) routes throughTraitRegistry::lookup_method()forAs<T>/TryAs<T>— replace the type-resolution-only path with full trait dispatch.- Rust Tests:
ori_types/src/infer/expr/tests.rs— cast trait resolution - Ori Tests: included in the §15D.2 matrix above
- Rust Tests:
-
Implement: Validate
asonly used withAs<T>impls (lossless);as?required when only aTryAs<T>impl exists (fallible). Lossy conversions (e.g.float → int) are REJECTED by bothasandas?perspec/08-types.md:1299-1308; the diagnostic for these MUST emit the spec migration hint (f.truncate()/.round()/.floor()/.ceil()).- Ori Tests:
tests/compile-fail/as_not_implemented.ori,tests/compile-fail/as_fallible_conversion.ori,tests/compile-fail/as_lossy_float_to_int.ori
- Ori Tests:
-
Implement: Validate
as?only used withTryAs<T>impls- Ori Tests:
tests/compile-fail/try_as_not_implemented.ori
- Ori Tests:
Evaluator (remove parallel dispatch)
-
Implement:
eval_can_cast()atori_eval/src/interpreter/can_eval/operators.rs(line ~119–142+) routes through the resolved trait method instead of the hardcoded primitive ladder. The hardcoded conversions move into the traitAs<T>/TryAs<T>impls for primitives, registered viaori_registry.- Rust Tests:
ori_eval/src/interpreter/can_eval/tests.rs— cast via trait dispatch - Remove the
float -> intinfallible path entirely (NOAs<int> for floatand NOTryAs<int> for floatimpl); bothasandas?MUST reject perspec/08-types.md:1299-1308. Users migrate tof.truncate()/.round()/.floor()/.ceil().
- Rust Tests:
LLVM / AOT (close the __cast gap)
-
Implement: Register
__castas a protocol builtin inori_ir/src/builtin_constants/protocol/mod.rs- Add
__castto the protocol enumeration; thread throughprotocol/tests.rsregistration tests. - Rust Tests:
ori_ir/src/builtin_constants/protocol/tests.rs—__castregistration
- Add
-
Implement: Handle
__castinori_llvm/src/codegen/arc_emitter/apply_protocols.rs- Emit appropriate LLVM cast instructions per source/target type pair (per
spec/08-types.md §8.11.2-§8.11.5— lossyfloat → intis spec-rejected and has no LLVM emission path):sitofp(int → float) — losslessAs<float>trunc+ range-check helper (int → byte) — fallibleTryAs<byte>ONLY;asform is compile-rejected (no emission path),as?returnsOption<byte>per the TDD matrix’s lossy-rejection rulezext(byte → int) — losslessAs<int>zext(char → int) — codepoint value, losslessAs<int>(Oricharis a 32-bit codepoint scalar; widen to i64 viazext, NOTbitcast— different bit widths)- Codepoint validity helper +
trunc(int → char) — fallibleTryAs<char>; emitSome(char)/NoneperOption<char>ABI when valid, elseNone - Native FFI calls into
ori_rtforstr → int/str → float/str → boolparsing helpers (fallibleTryAs<*>)
- Rust Tests:
ori_llvm/tests/aot/conversions.rs— as / as? AOT codegen - AOT Tests: cover all matrix dimensions in §15D.2 TDD matrix above;
ORI_VERIFY_ARC=1clean.
- Emit appropriate LLVM cast instructions per source/target type pair (per
Migration
-
Implement: Migration-hint phase — keep the
int()/float()/str()/byte()single-arg recognizer in the parser, but emitE1xxxmigration-hint diagnostic pointing atx as int/x as? intsyntax. Mirrors thedynkeyword removal pattern in §15D.4 (recognized-but-rejected token producing a migration hint).- WHERE:
ori_parse/src/grammar/expr/primary/— keep the single-arg call recognizer; route it to a diagnostic-only path that produces no AST node (or a syntheticCastnode when recovery is required) and emits the migration hint. - Ori Tests:
tests/compile-fail/old_conversion_function_syntax.ori
- WHERE:
-
Implement: Removal phase — once one full release cycle has passed with the migration-hint phase shipping (tracked separately at
/add-bugtime when the cycle elapses), drop theint()/float()/str()/byte()recognizer entirely. NOT a §15D deliverable; documented here so the implementer of the migration-hint phase knows the recognizer is intentionally retained.
Verification
- Verify: All matrix tests pass in interpreter AND LLVM; debug AND release;
ORI_CHECK_LEAKS=1clean - Verify:
tests/spec/expressions/type_conversion.orifully uncommented and green; the file’s previous state of 457/458 commented is closed. -
/tpr-reviewpassed (this subsection only) — see§15D.shared-close-out -
/impl-hygiene-reviewpassed (after TPR clean) — see§15D.shared-close-out - Subsection close-out (15D.2) — see
§15D.shared-close-out -
/sync-claudesection-close doc sync — see§15D.shared-close-out - Repo hygiene check — see
§15D.shared-close-out
15D.3 Simplified Bindings with $ for Immutability
Proposal: proposals/approved/simplified-bindings-proposal.md
Simplified binding model:
let xis mutable.let $xis immutable.- The
mutkeyword is removed. - Module-level bindings require the
$prefix.
// Before
let x = 5 // immutable
let mut x = 5 // mutable
$timeout = 30s // config variable
// After
let x = 5 // mutable
let $x = 5 // immutable
let $timeout = 30s // module-level constant (let and $ required)
TDD-first Matrix
-
TDD-first: Failing test matrix BEFORE implementation
- Dimensions: binding kind (
let x/let $x/let $name = exprmodule-level) × usage (read / reassign / shadow) × scope (block / module / import) × backend (interpreter / LLVM) - Each cell: positive (works) + negative (rejected with the right error code)
- Dimensions: binding kind (
Lexer
-
Implement (DONE): Remove
mutfrom reserved keywords- No
mutinori_lexer/src/keywords/mod.rs. NoKwMuttoken.
- No
Parser
-
Implement (DONE): Update
let_exprto accept$prefix in binding pattern (2026-02-20)- Rust Tests:
ori_parse/src/tests/parser.rs— block let binding with$prefix - Ori Tests:
tests/spec/expressions/immutable_bindings.ori
- Rust Tests:
-
Implement (DONE): Remove
mutfromlet_exprgrammar- Ori Tests: All 151
let mutoccurrences migrated toletacross 25 test files + AOT tests - AOT Tests:
ori_llvm/tests/aot/mutations.rs— 21 tests uselet x(mutable-by-default) with reassignment
- Ori Tests: All 151
-
Implement: Update
constant_declto requirelet $name = expr(no bare$name = exprform)- WHERE:
ori_parse/src/dispatch.rs(declaration dispatch — the bare$constant path) andori_parse/src/grammar/item/config/mod.rs(config-style constant parsing); coordinate withori_parse/src/grammar/ty/mod.rsandori_types/src/check/imports.rs(note pluralimports.rs) if any imports of bare-$constants need to be reconciled - Rust Tests: dispatch / config parser tests — constant declaration parsing
- Ori Tests:
tests/spec/declarations/constants.ori - AOT Tests: cover constant declaration in LLVM AOT corpus
- WHERE:
-
Implement: Remove old const function syntax
$name (params) -> Type- WHERE:
ori_parse/src/dispatch.rsand the function-form parser underori_parse/src/grammar/item/function/— drop the const-function parse path (post-proposal it is redundant withlet $name = (params) -> Type = body) - Ori Tests:
tests/compile-fail/old_const_function_syntax.ori(migration error)
- WHERE:
-
Implement (DONE): Support
$prefix in destructuring patterns (2026-02-20)- Rust Tests:
ori_parse/src/grammar/expr/primary/—parse_binding_patternhandles$for Name, Tuple, Struct, List - Ori Tests:
tests/spec/expressions/immutable_bindings.ori— tuple, struct, list destructuring with$
- Rust Tests:
-
Implement (DONE): List rest binding
..resttracks$mutability (2026-02-20)- IR:
BindingPattern::List.restisOption<(Name, Mutability)>(ori_ir/src/ast/patterns/binding/mod.rs:58— verified;Mutabilityis the canonical newtype enum, not a rawbool) - IR canon:
CanBindingPattern::List.restisOption<(Name, Mutability)>(ori_ir/src/canon/patterns.rs:33— verified) - Parser: handles
$before rest identifier (ori_parse/src/grammar/expr/primary/— directory;parse_binding_patternis in the primary submodule) - Type checker:
bind_pattern()usesbind_with_mutability()for rest (ori_types/src/infer/expr/sequences.rs:489) - Evaluator:
bind_can_pattern()uses per-bindingrest_mutable(ori_eval/src/interpreter/can_eval/— directory;can_eval.rsdoes not exist as a single file, the audit’s reference is path-stale) - Canon: lowering passes through
(Name, Mutability)tuple (ori_canon/src/lower/patterns.rs) - Formatter: emits
$prefix on immutable rest bindings (ori_fmt/src/formatter/patterns.rs) - Grammar:
grammar.ebnfupdated to allow[ "$" ]on rest identifier (line 581)
- IR:
Semantic Analysis
-
Implement (DONE): Track
$modifier separately from identifier name (2026-02-20)ori_types/src/infer/env/mod.rs—TypeEnvInner::mutabilityFxHashMap tracks per-binding mutabilitytests/spec/expressions/mutable_vs_immutable.ori— verifies$bindings preserve immutability
-
Implement: Prevent
$xandxcoexisting in same scope (same-name conflict)- WHERE:
ori_types/src/check/bindings.rs(ensure module exists; if not, add it; coordinate with §01-type-system owner) - Rust Tests: same-name conflict detection
- Ori Tests:
tests/compile-fail/dollar_and_non_dollar_conflict.ori
- WHERE:
-
Implement: Enforce module-level bindings require
$prefix- WHERE:
ori_types/src/check/mod.rs(module-level binding enforcement entry point —module.rsdoes not exist; module-binding logic lives in the check crate’s root) - Rust Tests: module binding immutability test
- Ori Tests:
tests/compile-fail/module_level_mutable.ori
- WHERE:
-
Implement (DONE): Enforce
$-prefixed bindings cannot be reassigned (2026-02-20)ori_types/src/infer/expr/operators.rs— immutability check ininfer_assigntests/compile-fail/assign_to_immutable.ori,assign_to_immutable_in_loop.ori,assign_to_immutable_destructured.ori
Imports
-
Implement: Require
$in import statements for immutable bindings (use module { $name }oruse module { name }based on the binding’s declared mutability)- WHERE:
ori_types/src/check/imports.rs - Ori Tests:
tests/spec/modules/import_immutable.ori
- WHERE:
-
Implement: Error when importing
$xasxor vice versa (mismatch)- Ori Tests:
tests/compile-fail/import_dollar_mismatch.ori
- Ori Tests:
Shadowing
-
Implement: Allow shadowing to change mutability (per spec)
- Ori Tests:
tests/spec/expressions/shadow_mutability.ori
- Ori Tests:
Error Messages
-
Implement (DONE): Clear error for reassignment to immutable binding (2026-02-20)
ori_types/src/type_error/check_error/mod.rs—AssignToImmutablevariant + formattingtests/compile-fail/assign_to_immutable.ori
-
Implement: Clear error for module-level mutable binding (no
$)- Ori Tests:
tests/compile-fail/module_mutable_message.ori
- Ori Tests:
-
Implement: Migration hint when old
let mutsyntax appears (now lex-rejected, but the diagnostic should suggestletdirectly)- Ori Tests:
tests/compile-fail/let_mut_migration.ori
- Ori Tests:
Verification
- Verify: All matrix tests green in BOTH interpreter and LLVM (debug + release)
-
/tpr-reviewpassed (this subsection only) — see§15D.shared-close-out -
/impl-hygiene-reviewpassed (after TPR clean) — see§15D.shared-close-out - Subsection close-out (15D.3) — see
§15D.shared-close-out -
/sync-claudesection-close doc sync — see§15D.shared-close-out - Repo hygiene check — see
§15D.shared-close-out
15D.4 Remove dyn Keyword for Trait Objects
Proposal: proposals/approved/remove-dyn-keyword-proposal.md
Remove the dyn keyword for trait objects. Trait names used directly as types mean “any value implementing this trait.”
// Before
@process (item: dyn Printable) -> void = ...
let items: [dyn Serializable] = ...
// After
@process (item: Printable) -> void = ...
let items: [Serializable] = ...
Re-use existing object-safety machinery — DO NOT duplicate.
section-03-traits.md §3.11already shipped object-safety checking (ObjectSafetyViolationenum +TraitEntry::is_object_safe()atori_types/src/registry/traits/mod.rs;compute_object_safety_violations()at registration;check_parsed_type_object_safety()at signature sites). §15D.4’s job is to wire trait-as-type parsing into the existing checker, never to re-implement object-safety logic. Plumbing changes touch the parser and the type-resolution-of-named-types path; safety enforcement is unchanged.
Status note (2026-05-06): §15D.4 is
in-progress— the object-safety enforcement atori_types/src/check/object_safety.rsis already shipped (per §3.11). The parser surface atori_parse/src/grammar/ty/mod.rsalready accepts trait-as-type forms. Remaining work is thedynkeyword removal + migration-hint diagnostic and the matrix verification across both backends.
- Already shipped: Object-safety enforcement infrastructure at
ori_types/src/check/object_safety.rs(per §3.11) —check_parsed_type_object_safety()at line 35, invoked from signature-site lowering. §15D.4 reuses this; the remaining work is parser/resolver plumbing to route trait-as-type identifiers through it.
TDD-first Matrix
-
TDD-first: Failing test matrix BEFORE implementation
- Dimensions: type position (param / return / field / list element / map value) × trait shape (object-safe / non-object-safe —
Clone/Eq/Iterator) ×dynpresence (legacydyn Trait/ newTrait) × generic-bound vs trait-object disambiguation × backend - Each cell: positive (compiles) + negative (
E2024for non-object-safe trait used as type)
- Dimensions: type position (param / return / field / list element / map value) × trait shape (object-safe / non-object-safe —
Implementation
-
Implement: Remove
"dyn" typefrom grammar type production- WHERE:
ori_parse/src/grammar/ty/mod.rs - Update
compiler_repo/docs/ori_lang/v2026/spec/grammar.ebnfvia/sync-grammar(NOT direct edit per CLAUDE.md §Spec & Grammar Changes) - Rust Tests:
ori_parse/src/grammar/ty/mod.rsparser tests — dyn removal - Ori Tests:
tests/spec/types/trait_objects.ori
- WHERE:
-
Implement: Parser emits a single
Type::Namednode for any bare identifier in type position; trait-vs-nominal disambiguation is the type checker’s job (NOT the parser’s — parser MUST stay syntax-only percompiler.md §Phase-Specific Purityandcanon.md §5; consultingori_types::TraitRegistryfrom the parser isLEAK:phase-bleeding).- WHERE:
ori_parse/src/grammar/ty/mod.rs— already emitsType::Named; verify no special-casing for “trait” identifiers leaks in. - WHERE (resolution):
ori_types/src/check/signatures/mod.rs— the resolver usesTraitRegistry::contains_trait(name)(orget_trait_by_name(name)when the entry is needed) atcompiler_repo/compiler/ori_types/src/registry/traits/lookup.rs:22,38when it lowersType::Namedto aTag; if the name is a trait, lowering proceeds via the existing trait-object machinery (check_parsed_type_object_safety()atori_types/src/check/object_safety.rs:35, invoked fromsignatures/mod.rsper §3.11) without naming a specific Tag variant — the concrete representation is determined by the type system’s existing trait-object handling, not introduced as a new tag here. - Ori Tests:
tests/spec/types/trait_objects.ori
- WHERE:
-
Implement: Type checker distinguishes
item: Trait(trait object) vs<T: Trait>(generic bound)- WHERE:
ori_types/src/check/signatures/mod.rs— disambiguate by syntactic position;Traitin type position is trait-object,<T: Trait>adds bound - Rust Tests:
ori_types/src/check/signatures/tests.rs - Ori Tests:
tests/spec/types/trait_vs_bound.ori
- WHERE:
-
Implement: Wire object-safety check at every trait-as-type site (NO new check_object_safety module — re-use
check_parsed_type_object_safety()fromori_types/src/check/signatures/mod.rsper §3.11)- Rust Tests:
ori_types/src/check/signatures/tests.rs— adds at-type-site object-safety enforcement - Ori Tests:
tests/compile-fail/non_object_safe_trait.ori— verifiesE2024fires forClone/Eq/Iteratorin type position
- Rust Tests:
-
Implement: Error if
dynkeyword is used (helpful migration message pointing at the new bare-trait syntax)- WHERE:
ori_parse/src/grammar/ty/mod.rs— keepdynas a recognized-but-rejected token to produce the migration hint - Ori Tests:
tests/compile-fail/dyn_keyword_removed.ori
- WHERE:
LLVM / AOT
-
Implement: LLVM trait-object codegen path validated for trait-as-type (no change vs
dyn Trait— same vtable layout, same pointer size; verify the parser change does not regress codegen for any object-safe trait)- AOT Tests: cover all dimensions of the §15D.4 matrix in LLVM
Verification
- Verify: All matrix tests green;
dynmigration hint surfaces on legacy code -
/tpr-reviewpassed (this subsection only) — see§15D.shared-close-out -
/impl-hygiene-reviewpassed (after TPR clean) — see§15D.shared-close-out - Subsection close-out (15D.4) — see
§15D.shared-close-out -
/sync-claudesection-close doc sync — see§15D.shared-close-out - Repo hygiene check — see
§15D.shared-close-out
15D.5 Index and Field Assignment
Proposal: proposals/approved/index-assignment-proposal.md (+ proposals/approved/index-trait-proposal.md for Index / IndexSet trait pair)
Supersedes: bug-tracker/section-04-codegen-llvm.md::BUG-04-070 (E4003 ARC-lowering symptom), bug-tracker/section-07-tooling-cli.md::BUG-07-016 (typeck-layer naming of the same fix), bug-tracker/section-04-codegen-llvm.md::BUG-04-100 (phase-purity-violation naming of the same fix; surfaced via BUG-02-023 §04 Plan TPR R10-1). All three bugs are symptoms of the unimplemented typeck.md §EX-17 desugar; §15D.5’s 5-phase plan IS the fix.
Phase placement (load-bearing): per typeck.md §EX-17 and canon.md §2 row 3-4, the desugar runs in the type checker, NOT in canon. Phase ordering rationale: AIMS sees post-desugar IR where mutation has been rewritten as pure reassignment, which is the shape the lattice is designed for; placing the desugar in canon would let pre-desugar IR reach AIMS and emit RC ops on the mutation expression instead of the COW form, violating canon.md §7.1 Invariant 5 (unified model).
Blocks §15D.6 — mutable-self desugar at state.f = v inside method bodies depends on field-assignment lowering shipping first. Dependency encoded in body prose (per §15D.0 “Subsection dependency” note) since SubsectionEntry schema does not yet carry inter-subsection edges.
Extend assignment targets to support index expressions (list[i] = x), field access (state.name = x), mixed chains (state.items[i] = x, list[i].name = x), and compound assignment on all forms (list[i] += 1). All forms desugar to copy-on-write reassignment via IndexSet trait (for index) or struct spread (for fields).
let list = [1, 2, 3]
list[0] = 10 // list = list.updated(key: 0, value: 10)
let state = GameState { score: 0, level: 1 }
state.score = 100 // state = { ...state, score: 100 }
state.items[i] = new_item // mixed chain
list[i].name = "new" // index then field
list[0] += 5 // compound: list[0] = list[0] + 5
TDD-first Matrix (write BEFORE Phase 1)
-
TDD-first: Failing test matrix
- Dimensions: target shape (
x[i]/x.f/x.f[i]/x[i].f/x.f.g[i].h) × root binding (mutable / immutable / parameter / loop var) × operator (=/+=/-=/*=//=/%=/**=) × index expression (literal / variable /f()side-effecting) × backend - Side-effect cell (load-bearing — caught by codex outlier R1):
arr[f()] += 1wheref()MUST run exactly once. Pin the count viadbg!()-ed counter in test fixture; reject any desugar that double-evaluates the index expression. - Each row: positive + negative pin
- Dimensions: target shape (
Phase 1: IndexSet Trait and updated Method
-
Implement: Define
IndexSet<Key, Value>trait incompiler_repo/library/std/prelude.ori—@updated (self, key: Key, value: Value) -> Self- Coordinate with
proposals/approved/index-trait-proposal.mdfor the trait pair (Index<Key, Value>already exists per prelude reference;IndexSetis the symmetric write side) - Rust Tests:
ori_types/src/check/registration/tests.rs— IndexSet trait registration - Ori Tests:
tests/spec/traits/index_set/basic.ori
- Coordinate with
-
Implement: Register
updatedas built-in method on[T],{K: V},[T, max N]in evaluator +ori_registry- Rust Tests:
ori_eval/src/method_dispatch/tests.rs— updated method dispatch - Ori Tests:
tests/spec/traits/index_set/updated_method.ori
- Rust Tests:
-
Implement:
updatedwith ARC-aware copy-on-write inori_patterns/ori_eval(single-RC fast-path: when the receiver is uniquely owned, mutate in place per AIMS lattice; otherwise clone-then-mutate)- Rust Tests:
ori_patterns/src/value/tests.rs— copy-on-write behavior matrix - Ori Tests:
tests/spec/traits/index_set/cow_behavior.ori
- Rust Tests:
Phase 2: Parser Changes
-
Implement: Extend parser to accept
assignment_target(identifier + index/field chains) on LHS of=and compound operators- WHERE:
ori_parse/src/grammar/expr/mod.rs(the assignment-target dispatcher) andori_parse/src/grammar/expr/postfix.rs(chain resolution) - Side-effect rule: parser MUST emit a single AST node capturing the chain; per-step temporaries are introduced at typeck rewrite time, NOT at parse time. Parser-level desugaring would duplicate complex targets per the codex outlier R1 finding.
- Rust Tests:
ori_parse/src/grammar/expr/mod.rsparser tests — assignment target parsing - Ori Tests:
tests/spec/expressions/index_assignment_syntax.ori
- WHERE:
-
Implement: AST node
AssignTarget { root: ExprId, steps: Vec<AccessStep> }capturing chain of index/field accesses in assignment target- WHERE:
ori_ir/src/ast/expr.rs—AccessStep::Index(ExprId)/AccessStep::Field(Name) - Rust Tests:
ori_ir/src/ast/tests.rs - Ori Tests:
tests/spec/expressions/field_assignment_syntax.ori
- WHERE:
Phase 3: Type-Directed Desugaring (in type checker — NOT canon)
Phase contract reminder: this rewrite happens in
ori_types, NOTori_canon. Seecanon.md §2 row 3-4.
-
Implement: Desugar
[key]steps toupdated()calls (requiresIndexSettrait resolution at the receiver type)- WHERE:
ori_types/src/infer/expr/operators.rs— extendinfer_assign/ adddesugar_assign_target - Side-effect rule: when the index expression has side effects (anything other than a
Literalor purePath), bind to a freshletfirst, then use the bound name in theupdated(key: ..., value: ...)call. Single evaluation guaranteed by typeck rewrite. - Rust Tests:
ori_types/src/infer/expr/tests.rs— index assignment desugaring - Ori Tests:
tests/spec/expressions/index_assignment_desugar.ori
- WHERE:
-
Implement: Desugar
.fieldsteps to struct spread reconstruction (requires struct type info)- WHERE: same module
- Rust Tests: same tests file — field assignment desugaring
- Ori Tests:
tests/spec/expressions/field_assignment_desugar.ori
-
Implement: Handle nested cases, mixed field-index chains, and compound assignment
- Rust Tests:
ori_types/src/infer/expr/tests.rs— mixed chain desugaring - Ori Tests:
tests/spec/expressions/mixed_chain_assignment.ori
- Rust Tests:
Phase 4: Type Checker Integration (validation)
-
Implement: Validate mutability of root binding (not
$, not parameter, not loop variable)- Ori Tests:
tests/spec/expressions/assignment_mutability.ori
- Ori Tests:
-
Implement: Validate field names against struct types in assignment chains
- Ori Tests:
tests/compile-fail/assignment/invalid_field.ori
- Ori Tests:
-
Implement: Validate key and value types against
IndexSetimpl- Ori Tests:
tests/compile-fail/assignment/type_mismatch.ori
- Ori Tests:
-
Implement: Emit diagnostics for all error cases (immutable binding, parameter, loop var, missing IndexSet, field mismatch, type mismatch)
- WHERE:
ori_diagnostic/src/error_code/mod.rs— coordinate diagnostic codes with §00-parser owner - Ori Tests:
tests/compile-fail/assignment/all_errors.ori
- WHERE:
Phase 5: Backend Verification (NOT a new lowering pass)
Phase ordering: by Phase 5, the desugar is already complete —
CanExprreachingori_arcandori_evalis pure reassignment. There is no separate “LLVM index-assignment codegen” — what backends see islet list = list.updated(...), which already works. Phase 5 verifies that.
-
Verify: LLVM AOT compiles
list[i] = vend-to-end (parses → typeck rewrites → canon → arc → llvm) producing identical observable behavior to the interpreter- AOT Tests: cover the full §15D.5 matrix
-
Verify: Field assignment AOT path identical-behavior with interpreter
-
Verify: Compound assignment on extended targets (
list[i] += 1) produces identical eval/LLVM behavior; side-effect-pinning test (arr[f()] += 1with single-evaluation counter) passes in both backends -
Verify: BUG-04-070 / BUG-04-100 / BUG-07-016 superseded entries flip to
completeonce the desugar runs in typeck (theE4003emission site atori_arc/src/lower/control_flow/mod.rs:391-408becomes dead code; remove it as a litter-pickup per CLAUDE.md §Scope Expansion) -
/tpr-reviewpassed (this subsection only) — see§15D.shared-close-out -
/impl-hygiene-reviewpassed (after TPR clean) — see§15D.shared-close-out -
Subsection close-out (15D.5) — see
§15D.shared-close-out -
/sync-claudesection-close doc sync — see§15D.shared-close-out -
Repo hygiene check — see
§15D.shared-close-out
15D.6 Mutable Self
Proposal: proposals/approved/mutable-self-proposal.md
Blocked by §15D.5 — state.f = v inside method bodies relies on §15D.5’s field-assignment desugar. Dependency encoded in body prose (per the §15D.0 “Subsection dependency” note above) since SubsectionEntry schema does not yet carry inter-subsection edges.
Capability coordination: any mutating-self diagnostics that touch capability annotations align with section-06-capabilities.md (NOT §22 Tooling — §22 is the tooling subsystem). Mutable-self does NOT introduce a new capability; the alignment is for shared error-code conventions only.
Make self a mutable binding in method bodies with implicit mutation propagation back to the caller. Extends the existing field/index assignment desugaring pattern to method calls.
impl Cursor {
@advance (self) -> void = {
self.pos += 1
}
}
cursor.advance() // cursor is updated via desugaring
TDD-first Matrix
-
TDD-first: Failing test matrix BEFORE implementation
- Dimensions: receiver mutability (
x/$x/ parameter / loop var) × method shape (-> void/-> Self/-> T/-> (Self, T)) × mutation depth (directself.f = v/ nestedself.inner.advance()) × trait vs inherent vs extension × backend - Each row: positive + negative pin (calling mutating method on
$xMUST reject; calling non-mutating method on$xMUST pass)
- Dimensions: receiver mutability (
Phase 1: Self Mutability
-
Implement: Make
selfa mutable binding in method bodies (type checker)- WHERE:
ori_types/src/check/bodies/impls.rs—bind_self_with_mutabilityanalogue (impl-method body checking lives here;infer/methods/does not exist in the shipped tree) - Rust Tests:
ori_types/src/check/bodies/tests.rs— self mutability in methods - Ori Tests:
tests/spec/methods/mutable_self_basic.ori
- WHERE:
-
Implement: Mutation detection dataflow analysis (classify methods as mutating/non-mutating)
- WHERE:
ori_types/src/check/bodies/impls.rs— walk the method body for anyself.f = .../self[i] = .../self.inner.<mutating-method>()site - Ori Tests:
tests/spec/methods/mutation_detection.ori
- WHERE:
Phase 2: Call-Site Desugaring (depends on §15D.5 field-assign desugar)
-
Implement: Void-returning mutating methods desugar to
-> Self+ implicit reassignment at the call site- Ori Tests:
tests/spec/methods/mutable_self_void.ori
- Ori Tests:
-
Implement: Value-returning mutating methods desugar to
-> (Self, T)+ tuple split- ARC invariant: implicit reassignment must trigger the AIMS uniqueness check before mutation to preserve COW invariants per
canon.md §7.1Invariant 5 (verified by gemini Round 1 — architectural risk #3) - Ori Tests:
tests/spec/methods/mutable_self_value_return.ori
- ARC invariant: implicit reassignment must trigger the AIMS uniqueness check before mutation to preserve COW invariants per
-
Implement: No desugaring for
-> Selfmethods (already return Self)- Ori Tests:
tests/spec/methods/mutable_self_returns_self.ori
- Ori Tests:
-
Implement: Nested field mutation cascading (
self.inner.advance())- Ori Tests:
tests/spec/methods/mutable_self_nested.ori
- Ori Tests:
Phase 3: Caller Validation
-
Implement: Error when calling mutating method on immutable (
$) binding- Ori Tests:
tests/compile-fail/methods/mutating_on_immutable.ori
- Ori Tests:
-
Implement: Diagnostics for mutation-related errors
- Ori Tests:
tests/compile-fail/methods/mutation_errors.ori
- Ori Tests:
Phase 4: Trait Integration
-
Implement: Infer mutation classification across trait implementations
- Ori Tests:
tests/spec/traits/mutable_self_trait.ori
- Ori Tests:
-
Implement: Conservative mutating classification for trait objects without local impls
- Ori Tests:
tests/spec/traits/mutable_self_trait_object.ori
- Ori Tests:
Phase 5: Extension Support
-
Implement: Extension methods follow same mutation propagation rules
- Ori Tests:
tests/spec/methods/mutable_self_extension.ori
- Ori Tests:
Phase 6: Evaluator
-
Implement: Evaluator support for mutable self in method bodies
- WHERE:
ori_eval/src/interpreter/(directory — no singlecan_eval.rsfile) - Ori Tests:
tests/spec/methods/mutable_self_eval.ori
- WHERE:
-
Implement: Evaluator call-site desugaring (implicit reassignment) — single dispatch path shared with type-checker desugar; the evaluator does NOT re-implement the rewrite
- Ori Tests:
tests/spec/methods/mutable_self_propagation.ori
- Ori Tests:
Phase 7: LLVM Support
-
Implement: LLVM codegen for mutable self desugaring (no special LLVM path — backends see post-desugar IR per Phase 2)
- AOT Tests:
tests/spec/methods/mutable_self_aot.ori
- AOT Tests:
-
Implement: COW integration verified for self-mutating methods (AIMS lattice drives RC emission; verify no regressions in
ori_arcsnapshots)- AOT Tests:
tests/spec/methods/mutable_self_cow.ori
- AOT Tests:
Verification
- Verify: All matrix tests green in both backends;
ORI_VERIFY_ARC=1clean;ORI_CHECK_LEAKS=1clean -
/tpr-reviewpassed (this subsection only) — see§15D.shared-close-out -
/impl-hygiene-reviewpassed (after TPR clean) — see§15D.shared-close-out - Subsection close-out (15D.6) — see
§15D.shared-close-out -
/sync-claudesection-close doc sync — see§15D.shared-close-out - Repo hygiene check — see
§15D.shared-close-out
15D.7 Section Completion Checklist + Routing Migration Note
Section-level exit criteria
- Mission Success Criteria in this section’s frontmatter ALL satisfied (six rows)
- All six approved proposals (
checks/as-conversion/simplified-bindings/remove-dyn/index-assignment/mutable-self) implemented end-to-end across all backends - All spec docs updated via
/sync-spec(NEVER direct edit per CLAUDE.md §Spec & Grammar Changes) - Grammar updated via
/sync-grammarfordynremoval,pre()/post()syntax, assignment-target extension - CLAUDE.md updated with syntax changes (mut-removal,
$immutability,ascasts, mutating self) — verified via/sync-claude - All tests pass:
./test-all.sh(debug + release;ORI_VERIFY_ARC=1clean;ORI_CHECK_LEAKS=1clean) - BUG-04-070 / BUG-04-100 / BUG-07-016 closed as dead code per §15D.5 supersession
- Bug-tracker entries for §15D.2 evaluator/spec drift (float→int) and §15D.4 dyn migration lint filed via
/add-bugif not already represented in scope-expansion litter-pickups - Section-level
/tpr-reviewpassed - Section-level
/impl-hygiene-reviewpassed (AFTER TPR clean) - Section-level
/improve-toolingretrospective completed (sweep mode — covers tooling cross-cutting, plan structure, knowledge persistence, approach pattern)
Plan-sync line items
- Section frontmatter
statusflipped tocomplete - Section frontmatter
subsection_statusesallcomplete -
00-overview.mdQuick Reference + mission success criteria checkboxes flipped -
index.mdsection status updated - Cross-link verification: every
Supersedes:/blocked_by:/blocks:in this section’s frontmatter and prose resolves - Next section’s
depends_on(if any) verified clean
Routing migration note (PLAN_ROUTING_DRIFT — DOCUMENTED, NOT FIXED HERE)
Acknowledged routing drift (per blind-spots Round 1, gemini + opencode + codex consensus): this section’s
owns_crates: []plus the original raw-checkbox layout matched thePLAN_ROUTING_DRIFT:roadmap-implementation-leakpattern documented inCLAUDE.md §Plan Routingand.claude/rules/roadmap.md §Lazy Migration. The architecturally-correct long-term fix is to lift §15D’s six proposals into per-feature plan dirs:
plans/checks/(function-level contracts)plans/as-conversion/plans/simplified-bindings/plans/dyn-removal/plans/index-assignment/(with field-assignment as a sibling section, since they ship together)plans/mutable-self/Each lift uses
/migrate-feature <name>and replaces this section’s content with<!-- blocked-by:plans/<feature>/section-NN.md -->pointers, preserving phase-coverage audit but moving implementation tracking to the feature plans.The migration is NOT performed in this editor pass. Per CLAUDE.md §Plan Routing “Lazy Migration”, structural moves are
/migrate-feature’s responsibility, not/review-plan’s. The migration runs when/continue-roadmapnext picks up §15D andscripts/feature-cluster-detect.pyflags one of these clusters; that workflow opens a separate session with the migration as its single deliverable.Until then, this section retains raw checkboxes as a documented exception, with the editor pass having narrowed the dimensions (matrix-first, side-effect pins, phase-contract citations, blocked_by frontmatter) so that the lazy migration has clean targets to lift.
Exit Criteria: All six proposals shipped in both backends with dual-execution parity; routing migration scheduled (NOT performed here).
§15D.shared-close-out
SSOT for per-subsection close-out — every subsection’s last five checkboxes reference this single block by name to eliminate the
LEAK:algorithmic-duplicationflagged in blind-spots Round 1. Implementer copies these five protocol entries into the active subsection’s commit body when running close-out; the section file itself stays single-instance.
/tpr-reviewpassed — independent third-party review found no critical or major issues (or all findings triaged into- [ ]items in this section). Run AFTER all subsection implementation items are checked./impl-hygiene-reviewpassed — hygiene review clean (phase boundaries, SSOT, algorithmic DRY, naming, dispositions, scope-expansion). Run AFTER/tpr-reviewis clean.- Subsection close-out (
/improve-toolingretrospective) — Run on this subsection’s debugging journey per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”. Whatdiagnostics/scripts ran, wheredbg!/tracingcalls were added, where output was hard to interpret, where test failures gave unhelpful messages, where command sequences repeated. Forward-look: what tool / log / diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.<N> retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.: no tooling gaps”. Then update this subsection’s statusin section frontmatter tocomplete. /sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.- Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
- Migration trigger — when
/continue-roadmapnext picks up §15D, run/migrate-feature checks(or whichever proposal cluster surfaces first); confirm cleanly lifts toplans/<feature>/. Do NOT lift all six at once unless the migration tooling supports batch.
15D.R Third Party Review Findings
Round 0 Reviewers: codex (HIGH trust), gemini (LOWER trust), opencode (MEDIUM trust). 17 reviewer findings + 2 §3.5 plan-coherence pre-gate findings. After verification: 9 applied inline, 4 filed below for execution at appropriate workflow phase, 4 dropped (gemini confabulations on lines 727 / 585 — claimed text not present in file; opencode line-number drift already correct in plan).
-
[TPR-15D-001-codex+gemini+opencode][critical]plans/roadmap/section-15D-bindings-types.md:710—PLAN_ROUTING_DRIFT:roadmap-implementation-leak: six approved proposals (checks/as-conversion/simplified-bindings/remove-dyn/index-assignment/mutable-self) carry raw implementation checkboxes inside this roadmap section. Per CLAUDE.md §Plan Routing +.claude/rules/roadmap.md §Lazy Migrationthe roadmap is an audit-index, not an implementation tracker for cross-cutting features.Evidence:
§15D.7 Routing migration notealready documents the drift (lines 732-749 — line range refreshes when editor passes shift content). Section retains raw checkboxes pending lazy migration. Impact: §15D will keep accumulating raw checkboxes until lifted; downstream/continue-roadmaptouching feature work re-triggersPLAN_ROUTING_DRIFT. Required plan update: when/continue-roadmapnext picks up §15D, run/migrate-featurefor each ofplans/checks/,plans/as-conversion/,plans/simplified-bindings/,plans/dyn-removal/,plans/index-assignment/,plans/mutable-self/. Replace each subsection’s body with<!-- blocked-by:plans/<feature>/section-NN.md#item -->pointers per.claude/skills/migrate-feature/SKILL.md. Do NOT lift all six at once unless the migration tooling supports batch. Basis: fresh_verification (codex F1 line:710 + gemini F4 + opencode finding clustered). Confidence: high. -
[TPR-15D-002-gemini][critical]compiler_repo/tests/spec/expressions/type_conversion.ori—DISPOSITION_DRIFT:untracked-annotation-shape (file is 458 lines, 457 prefixed with//); the disposition scanner cannot track the gap because//-comment lines are not annotation-form. Replace the wholesale//-comment with#skip("BUG-XX-NNN: …")on each test once the underlying tracking bug is filed, OR uncomment as part of §15D.2 implementation.Evidence:
wc -lreports 458 lines;grep -c "^//"reports 457. §15D.2 already documents this state at line 179 (“DISPOSITION DRIFT” callout). Impact:state.sh refresh --dispositions-onlycannot enumerate the per-test gap; every test in the file is invisible to disposition tracking. §15D.2 close-out depends on this file being green. Required plan update: anchored to §15D.2 verification checkbox at line 268 (“tests/spec/expressions/type_conversion.orifully uncommented and green”). Out of scope for/review-plan(.ori file edit not permitted in plan-only mode); the §15D.2 implementer addresses this when un-commenting the test corpus. If a tracking bug is needed for partial enablement, file via/add-bugat that point. Basis: fresh_verification (gemini F3 confirmed via shell). Confidence: high. -
[TPR-15D-003-codex][low]plans/roadmap/section-15D-bindings-types.md— 15D.R Third Party Review Findings block was missing on Round 0 entry. ACTION: created in this fix-and-commit cycle (this block).Evidence: codex F8 explicitly noted the missing 15D.R block. Impact: Round 0 had no host for filed findings. Required plan update: NONE (resolved by adding this block). Basis: fresh_verification. Confidence: high.
Verification surprises
- Gemini F1 (claimed “Evaluator assigned syntactic desugaring duties” at line 727) — DROPPED. Line 727 is
**Exit Criteria**: All six proposals shipped...— no such evaluator-desugar claim present. Gemini confabulation. - Gemini F2 (claimed “Interpreter using AIMS static analysis” at line 585) — DROPPED. Line 585 is
**Blocked by §15D.5** — state.f = v inside method bodies relies...— no AIMS-static-analysis claim. Gemini confabulation. (Codex F5 referenced the same line for a DIFFERENT, verifiable finding — frontmatterblocked_bymismatch — which was applied inline.) - Opencode F3 (claimed line numbers drift: parse_contracts at 221 not 129; tests at 531+ not 143) — DROPPED. Plan line 119 already cites
parse_contracts() at line 221(correct:grep -n "fn parse_contracts"confirms line 221). Plan line 121’s “line:143” reference is to historical parser tests; verification would require deeper test-file inspection beyond this fix-cycle scope. No drift to fix at the cited plan lines.
Round 1 audit log
Round 1 against post-Round-0 plan state. Reviewers: codex (HIGH trust), gemini (LOWER trust), opencode (MEDIUM trust). 13 reviewer findings. After verification: 10 applied inline (path corrections + matrix corrections + phase-bleeding rewrite), 1 dropped (gemini F1 — out of /review-plan scope; should be filed via /add-bug separately if real), 1 classified as meta-duplicate (codex F1 — exact duplicate of TPR-15D-001 already filed in Round 0; not re-filed), 1 dropped (codex F1 same).
[TPR-15D-R1-codex-F1][meta-duplicate]Re-raised PLAN_ROUTING_DRIFT at line 750 — DUPLICATE of TPR-15D-001 (Round 0). The §15D.7 Routing migration note + TPR-15D-001 already document this; no new filing.[TPR-15D-R1-gemini-F1][dropped]Major @compiler_repo/compiler/ori_arc/src/lower/collections/mod.rs:320—lower_castquality concern. DROPPED from /review-plan scope (compiler-code quality, not plan-doc). If the concern is real, file via/add-bugseparately; this finding is out of scope for the current /review-plan fix-and-commit cycle.[TPR-15D-R1-codex-F2][high]§15D.2 cast matrix at line 210 — removedfloat → int as?positive cell (spec-rejected perspec/08-types.md:1299-1308); added explicit lossy-rejection negative pinas_lossy_float_to_int.oriand migration-hint reference (f.truncate()/.round()/.floor()/.ceil()).[TPR-15D-R1-codex-F3][high]§15D.2 LLVM cast checklist at line 256 — deletedfptosi(no spec path), replaced char-bitcast guidance withzextforchar → int(codepoint widening, different bit widths), explicitTryAs<char>valid-codepoint helper forint → char, andori_rtFFI references for str-parsing fallible casts.[TPR-15D-R1-codex-F4][critical]§15D.4 trait-as-type at line 439 — rewrote task. Parser now emits bareType::Namedfor identifiers in type position; trait-object disambiguation moved toori_types/src/check/signatures/mod.rsconsultingTraitRegistry::is_trait()and running existingcheck_parsed_type_object_safety(). Parser purity (compiler.md §Phase-Specific Purity+canon.md §5) preserved. The original task description contained aLEAK:phase-bleeding(parser would consultori_types::TraitRegistry), now removed.[TPR-15D-R1-codex-F5 + gemini-F4][medium]§15D.1 line 121 —PostContract { param, ... }(singular) →PostContract { params: Vec<Name>, ... }matching shipped AST (ori_ir/src/ast/items/function.rs:53—params: Vec<Name>because lambda binders may be(a, b) -> condfor tuple-returning functions).[TPR-15D-R1-codex-F6 + opencode-F3][low]§15D.3 line 370 (and §15D.3 line 324 mention) —ori_types/src/check/import.rs(singular) →ori_types/src/check/imports.rs(plural — verified path).[TPR-15D-R1-gemini-F2][minor]§15D.3 lines 338-343 —Option<(Name, bool)>→Option<(Name, Mutability)>matching shipped IR (ori_ir/src/ast/patterns/binding/mod.rs:58,ori_ir/src/canon/patterns.rs:33).Mutabilityis the canonical newtype enum, not a rawbool.[TPR-15D-R1-gemini-F3][minor]§15D.6 lines 612-619 —ori_types/src/infer/methods/(nonexistent) →ori_types/src/check/bodies/impls.rs(impl-method body checking lives here in the shipped tree).[TPR-15D-R1-opencode-F1][critical]§15D.1 line 129 + §15D.5 line 563 —ori_diagnostic/src/error_codes.rs(nonexistent) →ori_diagnostic/src/error_code/mod.rs(verified directory +mod.rs).[TPR-15D-R1-opencode-F2][high]§15D.4 lines 434, 440, 452 —ori_parse/src/grammar/ty.rs(file does not exist) →ori_parse/src/grammar/ty/mod.rs(verified directory layout). All 3 occurrences fixed.[TPR-15D-R1-opencode-F4][medium]§15D.3 line 359 —ori_types/src/check/module.rs(nonexistent) →ori_types/src/check/mod.rs(module-level binding enforcement entry point lives in the check crate’s root).
Round 1 verification surprises
- Codex F1 (PLAN_ROUTING_DRIFT) — meta-duplicate of TPR-15D-001 filed Round 0; no new action. Reviewers should reach consensus that §15D.7 routing-migration note + TPR-15D-001 are the canonical accepted state pending lazy migration.
- Gemini F1 (
lower_castcompiler-code concern) — out of /review-plan scope. If real, the Implementer should/add-bugseparately during §15D.2 implementation.
Inline fixes applied (Round 0 same commit)
[codex F2][high]Ownership map at lines 64-69 — corrected against verified upstreamowns_crates:(verified §00, §01, §03, §21A, §22, §23 frontmatter).ori_canonmoved from §01 to §21A.ori_diagnostic+ori_fmtmoved from §00 to §22.ori_patternsadded to §23. Added §22-tooling row.[codex F3][high]§15D.2 line 226 —@as_to/@try_as_to→@as/@try_as(perspec/08-types.mdandas-conversion-proposal.md).[codex F4][medium]§15D.4 frontmatter statusnot-started→in-progress(object-safety machinery shipped atori_types/src/check/object_safety.rs; trait-as-type parsing shipped atori_parse/src/grammar/ty/mod.rs). Added status note in body.[codex F5 + opencode F1][medium]Lines 478 + 585 — replaced falseblocks: ["15D.6"]/blocked_by: ["15D.5"]frontmatter claim with “Dependency encoded in body prose” matching the existing §15D.0 acknowledgment at line 73.[codex F6][medium]§15D.1 line 124 —PreContract.message: Option<ExprId>→Option<Name>(verified incompiler_repo/compiler/ori_ir/src/ast/items/function.rs:28).[codex F7][medium]§15D.3 lines 322-329 —ori_parse/src/grammar/decl.rs(does not exist) replaced with actual paths:ori_parse/src/dispatch.rs,ori_parse/src/grammar/item/config/mod.rs,ori_parse/src/grammar/item/function/.[opencode F2][major]index.md:522— Status “Not Started” → “In Progress” (matches §15D frontmatterstatus: in-progress).[PLAN-COHERENCE-001 + 002][high]00-overview.md:144and:168— bare “Section 7 (Stdlib)” → “Section 7A-E (Stdlib)” matching the actualsection-07A...07Efiles (nosection-7.mdexists).
Round 2 audit log
Round 2 against post-Round-1 plan state. Reviewers: codex (HIGH trust), gemini (LOWER trust), opencode (MEDIUM trust). 9 reviewer findings (codex 5 incl. duplicate F4 id, gemini 3, opencode 1). After verification: 7 applied inline (frontmatter fix + line-number drift + API-name correction + cast-language alignment + contract-desugar relocation + migration-hint disambiguation), 1 classified meta-duplicate (codex F1 — PLAN_ROUTING_DRIFT, exact duplicate of TPR-15D-001 filed Round 0; not re-filed), 1 dropped (gemini F3 — pre-existing language at line 682 already satisfies the “typeck owns desugar; eval consumes rewritten form” requirement; no edit needed).
[TPR-15D-R2-codex-F1][meta-duplicate]Re-raised PLAN_ROUTING_DRIFT at line 730 — DUPLICATE of TPR-15D-001 (Round 0). The §15D.7 routing-migration note + TPR-15D-001 already document this; no new filing.[TPR-15D-R2-codex-F2 + opencode-F1][high]§15D.2 lines 185, 249, 259 — float→int cast remediation contradicted the TDD matrix at line 222 (“rejected by bothasandas?”). Rewrote EVALUATOR/SPEC DRIFT note (line 185 area) to align withspec/08-types.md:1299-1308(lossy rejected, migrate totruncate()/round()/floor()/ceil()); rewrote validator task (line 249) to make lossy rejection explicit and addas_lossy_float_to_int.orinegative pin; rewrote evaluator task (line 259) to remove “must becomeas?only” and require dropping thefloat → inttruncation entirely (noAs<int> for float, noTryAs<int> for float).[TPR-15D-R2-codex-F3][medium]§15D.4 frontmatter status — Round 0 codex F4 audit log claimednot-started→in-progresswas applied, but only the body status note was added; the YAMLstatus: not-startedwas left in place. PARTIAL-FIX VERIFIED. Now flipped tostatus: in-progressto complete the Round 0 fix.[TPR-15D-R2-codex-F4][high]§15D.4 line 459 trait-object resolver task — original task citedTraitRegistry::is_trait()(does not exist on the shipped registry). Real APIs verified atori_types/src/registry/traits/lookup.rs:22,38areget_trait_by_name(name)andcontains_trait(name). Rewrote task to citecontains_trait(andget_trait_by_namewhen the entry is needed) and added the verified path tocheck_parsed_type_object_safetyatori_types/src/check/object_safety.rs:35.[TPR-15D-R2-codex-F5][low]§15D.1 line 115 contract status note — claimed “shipped atparse_contracts:129” but the actual call site is at line 131 and the function definition begins at line 221 ofori_parse/src/grammar/item/function/mod.rs(verified viagrep -n "fn parse_contracts\|parse_contracts("). Rewrote the status note with both line numbers (call site 131, definition 221).[TPR-15D-R2-gemini-F1][critical]§15D.1 line 150 contract codegen — task assigned thepre()/post()desugar to BOTHori_eval/src/interpreter/can_eval/control_flow.rsANDori_llvm/src/codegen/arc_emitter/emit_function.rs, violatingcanon.md §7.1Invariant 5 (unified model — one shape, all backends consume identically) andcanon.md §1phase ordering. Rewrote to relocate desugar into the type checker (preferred —ori_types/src/check/bodies/functions.rs) orori_canon/src/desugar/(fallback) — pick ONE phase, never split. Both backends consume the post-desugarCanExprwith no contract-aware code.[TPR-15D-R2-gemini-F2][high]§15D.2 lines 280-282 migration — task said “Removeint(),float(),str(),byte()from parser” AND “whenint(x)form parses, emit migration hint” (mutually exclusive). Rewrote into two phases: (1) migration-hint phase keeps the recognizer and routes to a diagnostic-only path emittingE1xxxmigration hint, (2) removal phase (out of §15D scope; documented for the implementer’s awareness) drops the recognizer once one release cycle has elapsed. Mirrors thedynremoval pattern in §15D.4 (recognized-but-rejected token).[TPR-15D-R2-gemini-F3][dropped]§15D.6 line 682 evaluator desugar — verification of pre-existing text shows the task already states “single dispatch path shared with type-checker desugar; the evaluator does NOT re-implement the rewrite”. Pre-existing language already satisfies gemini’s concern (typeck SOLE owner of the rewrite, eval consumes the post-rewrite shape). No edit needed.
Round 2 verification surprises
- Round 0 codex F4 partial fix: Round 0 audit log (line 819) claimed
§15D.4 frontmatter status: not-started → in-progresswas applied. Verification of the post-Round-0 file showed only the body status note was written; the YAMLstatus: not-startedline was left untouched. The Round 0 fix was incomplete; Round 2 codex F3 caught it. Lesson: when a “frontmatter + body” fix is logged, verify BOTH locations changed before accepting the audit-log claim. - Round 0 opencode F3 (line 115 line numbers): Round 0 verification surprises (line 791) noted opencode F3 was DROPPED with “Plan line 119 already cites
parse_contracts() at line 221(correct)”. That assessment was correct for line 119, but missed line 115’s separateparse_contracts:129reference (which was wrong — actual call site at 131). Round 2 codex F5 caught the line-115 drift. Lesson: when a plan has multiple locations citing the same symbol, verify ALL of them. - Codex F2 vs opencode F1 overlap: both reviewers surfaced the same float→int contradiction across lines 184/185, 248/249, 258/259. Treated as ONE finding with combined attribution; line 184 (cited by opencode) is the EVALUATOR/SPEC DRIFT note that contained the contradictory “requires
as?” wording — verified and fixed. - plan-cleanup auto-revert of §15D.4 status:
scripts/plan-cleanup.pyauto-derived §15D.4status: not-startedfrom checkbox-state heuristics during the Round 2 fix-and-commit Step 4 pre-commit run, reverting the codex-F3 frontmatter fix. Re-appliedin-progressbecause the body Status note (line 442) documents that object-safety machinery is already shipped via §3.11 (ori_types/src/check/object_safety.rs) and trait-as-type parsing is already shipped atori_parse/src/grammar/ty/mod.rs— the section IS in progress on upstream work, just not visible in checkbox state. Future plan-cleanup runs may attempt the revert again until §15D.4 has at least one[x]checkbox; if so, re-flip and refer back to this entry. Filed as a tooling improvement candidate (plan-cleanup should consult body Status notes before deriving subsection status from checkboxes alone) rather than re-engaging /add-bug here.
Round 3 audit log
Round 3 against post-Round-2 plan state. Reviewers: codex (HIGH trust, 2 findings), gemini (LOWER trust, 2 findings), opencode (MEDIUM trust, status: clean — first reviewer to return zero findings). Strong convergence trajectory: 17 → 14 → 8 → 4 findings across rounds 0-3. After verification: 4 applied inline.
[TPR-15D-R3-codex-F1][medium]§15D.4 frontmatter status REAPPEARED atnot-started— plan-cleanup auto-revert struck again post-Round-2 commit. Re-flipped toin-progress. Recurring tooling drift documented in Round 2 audit log; will recur on every commit until §15D.4 has at least one[x]checkbox or plan-cleanup is improved (tooling candidate, not /add-bug).[TPR-15D-R3-codex-F2][medium]§15D.4 line 460 trait-object resolver task — Round 2 rewrite cited “lowers to the trait-object tag” but no such Tag variant exists in the shipped enum (compiler_repo/compiler/ori_types/src/tag/mod.rs:25). Rewrote to defer the concrete representation to “the type system’s existing trait-object handling” without naming a specific Tag variant; preserves the verifiedcontains_trait()/get_trait_by_name()/check_parsed_type_object_safety()API references.[TPR-15D-R3-gemini-F1][high]§15D ownership map line 74 —ori_lspwas missing from the §22-tooling row. Verified §22-tooling.md frontmatterowns_crates: [ori_fmt, ori_test_harness, ori_stack, ori_diagnostic, ori_compiler, oric, ori_lsp]. Addedori_lspto the map.[TPR-15D-R3-gemini-F2-revised][medium]§15D.1 line 126 — claimed parser tests atori_parse/src/grammar/item/function/mod.rs:143. Verified: tests DO exist but attests.rsnotmod.rs, specificallyfunction/tests.rs:144(test_pre_contract_basic),:159(test_pre_contract_with_message),:172(test_post_contract_basic),:187(test_post_contract_tuple_params). Gemini’s claim that “neither mod.rs nor tests.rs contains contract tests” was confabulated; orchestrator-corrected fix: cite the actual tests.rs paths/line numbers.
Round 3 verification surprises
- opencode returned status: clean — first reviewer to return zero findings across the convergence loop. Confirms the section’s substantive content is now well-aligned with the codebase; remaining drift is narrow (status state-machine, ownership map gap, line-number drift, one Tag-variant overreach).
- gemini F2 partially confabulated: reviewer claimed “neither mod.rs nor tests.rs contains contract tests”; verification showed 4 contract tests in tests.rs:144+. The plan’s path/line citation WAS wrong (mod.rs:143 → should be tests.rs:144), so the underlying drift was real, but reviewer’s evidence was partially wrong. Orchestrator-corrected fix lands the right path/lines.
- plan-cleanup recurrence-pattern continues: §15D.4 frontmatter auto-reverted between Round 2 commit and Round 3 dispatch. The fix in this round will likely be re-reverted at this round’s commit too unless an
[x]checkbox is added to §15D.4 first. Adding the [x] checkbox satisfies plan-cleanup’s mechanical heuristic without misrepresenting work state.
Round 4 audit log (FINAL — iter_cap_reached at iteration_counter=5)
Round 4 against post-Round-3 plan state. Reviewers: codex (HIGH trust, 4 findings incl. 1 meta-duplicate), gemini (LOWER trust, status: clean — informational only confirming code-state alignment), opencode (MEDIUM trust, 2 minor findings). Convergence trajectory complete: 17 → 14 → 8 → 4 → 5 findings across rounds 0-4. After verification: 5 applied inline, 1 meta-duplicate (TPR-15D-001 re-raise), 1 informational (gemini’s “plan aligns” confirmation).
[TPR-15D-R4-codex-F2][high]review_pipeline frontmatterrounds_completed: 2 + last_round_findings: "Round 2..."was stale post-Round-3 commit. Updated torounds_completed: 4with Round 4 summary;stage: tpr-doneandnext_step: 7.[TPR-15D-R4-codex-F3][high]§15D.2 line 269 — int→byte AOT task said “fallible TryAs; emit panic-on-fail wrapper for as-form rejection”. Spec/08-types.md §8.11.2-§8.11.3 + TDD matrix line 222 rejectasfor lossy. Rewrote:asform compile-rejected (no emission path),as?returnsOption<byte>only.[TPR-15D-R4-codex-F4][medium]§15D.7 routing migration note + TPR-15D-001 had two slugs for the remove-dyn proposal:plans/remove-dyn/(one location) andplans/dyn-removal/(other location). Canonicalized toplans/dyn-removal/everywhere viareplace_all.[TPR-15D-R4-opencode-F1][medium]success_criteria #2 said “via a registered__castprotocol builtin” (singular handler implication) but implementation plan describes per-type-pair LLVM dispatch. Reworded to: “via a registered__castprotocol builtin (whose handler dispatches per type-pair to the appropriate LLVM cast instruction —sitofp,trunc+range-check,zext,fptosiif/where applicable)”.[TPR-15D-R4-opencode-F2][low]TPR-15D-001 evidence cited routing-migration note “lines 708-723” but editor passes shifted content. Updated to “lines 732-749” with note that line range refreshes when editor passes shift content.[TPR-15D-R4-codex-F1][meta-duplicate]PLAN_ROUTING_DRIFT re-raised at line 732 — exact duplicate of TPR-15D-001 already filed Round 0. Not re-filed; documented here.[TPR-15D-R4-gemini-F1][informational]“Plan aligns precisely with shipped parser and arc codegen state” — gemini returned status: clean with 1 informational confirmation rather than findings. Treated as meta per §6 (no actionable content). Strong terminal-state signal.
Round 4 verification surprises (FINAL)
- gemini status: clean (informational-only) — second reviewer to converge to clean; in combination with opencode Round 3 clean, this is strong terminal-state confirmation.
- codex still raising substantive findings (review_pipeline lag, int→byte semantics, slug mismatch) — high-trust reviewer’s residual findings indicate genuine drift that previous rounds didn’t surface (review_pipeline.rounds_completed only became wrong AFTER Round 3 audit log was added without marker update; int→byte rewrite was Round 0 collateral that survived Rounds 1-3 unrevealed; slug mismatch arose from Round 0 TPR-15D-001 filing using one slug while §15D.7 routing note used another).
- iter_cap_reached at iteration_counter=5 —
meta_only_streaknever reached 2 (codex Critical/High kept resetting it). Per /tpr-review §5 stop condition #3, this exits withexit_reason: cap_reached_with_substantive. Caller (/review-plan → /continue-roadmap) presents Branch 2 escalation envelope to user. - Skipped /independent-review tie-breaker for TPR-15D-002: The disposition-drift finding was singleton-gemini in Round 0 (technically contested per §5.5 classification rule), but the finding is verified-by-construction (file IS 457/458 commented — git ground truth) and out-of-/review-plan-scope (cannot edit .ori files; correct fix is /add-bug for #skip annotations). Arbiter dispatch would consume 25-45 min wall-clock to confirm what’s mechanically known and prescribe a fix the orchestrator cannot apply within /review-plan’s authorized scope. Pragmatic skip; documented for future-loop /improve-tooling consideration (§5.5 may want an “out-of-scope-but-verifiable” exception class).