Phase 121: StepTree→Normalized Shadow Lowering (if-only, dev-only)
Status: ✅ COMPLETE Date: 2025-12-18 Scope: if-only patterns (no loops)
Overview
Phase 121 establishes a minimal shadow lowering route from StepTree (structure SSOT) to Normalized JoinIR form, with parity verification against the existing router for if-only patterns.
Objectives
- Create dev-only shadow lowering path: StepTree → JoinModule (Normalized)
- Verify parity with existing router (exit contracts + writes)
- Maintain default behavior (dev-only, no production impact)
- Establish foundation for future Normalized migration
Implementation
P0: Design Documentation ✅
Commit: 8d930d2dc - "docs: Phase 121 StepTree→Normalized shadow plan"
Added comprehensive design section to docs/development/current/main/design/control-tree.md:
- Input SSOT: StepTree + StepTreeContract
- Output: JoinModule (Normalized dialect)
- Execution conditions: dev-only, strict fail-fast
- Prohibitions: fallback, env direct reads, hardcoding
P1: Module Structure ✅
Commit: 1e5432f61 - "feat(control_tree): add StepTree→Normalized shadow lowerer (if-only, dev-only)"
Created modular structure in src/mir/control_tree/normalized_shadow/:
mod.rs: Module interface and exports
contracts.rs: Capability checking
UnsupportedCapabilityenum (Loop/Break/Continue/Other)CapabilityCheckResulttypecheck_if_only()function
builder.rs: Shadow lowering
StepTreeNormalizedShadowLowererBox(Box-First principle)try_lower_if_only()- ReturnsResult<Option<(JoinModule, JoinFragmentMeta)>, String>Ok(None): Out of scopeOk(Some(...)): SuccessErr(...): Internal error
get_status_string()- Dev logging
parity.rs: Comparison & verification
MismatchKindenum (ExitMismatch/WritesMismatch/UnsupportedKind)ShadowParityResulttypecompare_exit_contracts(),compare_writes_contracts(),check_full_parity()
Tests: 12 unit tests (all passing)
P2: Dev-Only Wiring ✅
Commit: 89b868703 - "feat(joinir/dev): wire Phase 121 StepTree shadow lowering (strict fail-fast)"
Integrated into src/mir/builder/calls/lowering.rs:
- Wired into existing
lower_function_body()after capability guard - Only runs when
joinir_dev_enabled()returns true - Strict mode fail-fast with
freeze_with_hint(hint required) - Dev logging:
[trace:dev] phase121/shadow: ...
Behavior:
- Default: No impact (dev-only gate)
- Dev mode: Shadow lowering attempted, status logged
- Strict mode: Fail-fast on if-only mismatch
P3: Parity SSOT ✅
Included in P1 (parity.rs):
- Minimal comparison: exits + writes contracts
- No value comparison (too fragile)
- BTreeSet deterministic ordering
- Clear mismatch classification
P4: Smoke Tests ✅
Commit: 0892df6df - "test(joinir): Phase 121 shadow parity smokes (VM + LLVM EXE)"
Created smoke tests in tools/smokes/v2/profiles/integration/apps/:
phase121_shadow_if_only_vm.sh✅ (3/3 tests PASS)phase121_shadow_if_only_llvm_exe.sh(created, LLVM harness config needed)
Test fixtures (existing):
phase103_if_only_merge_min.hako- Basic if merge (output: 2)phase114_if_only_return_then_post_min.hako- Return + post (output: 7\n2)phase117_if_only_nested_if_call_merge_min.hako- Nested if (output: 2\n3\n4)
Test conditions:
HAKO_JOINIR_STRICT=1(strict mode)NYASH_JOINIR_DEV=1(dev mode)NYASH_DISABLE_PLUGINS=1(VM required)
P5: Documentation ✅
This document + updates to:
docs/development/current/main/design/control-tree.md(Phase 121 design)docs/development/current/main/10-Now.md(updated)docs/development/current/main/01-JoinIR-Selfhost-INDEX.md(updated)docs/development/current/main/30-Backlog.md(updated)
Verification
Build ✅
cargo build --lib
cargo test --lib normalized_shadow
# 12 tests passed
Smoke Tests ✅
bash tools/smokes/v2/profiles/integration/apps/phase121_shadow_if_only_vm.sh
# [PASS] phase121_shadow_if_only_vm: All tests passed
Manual Verification ✅
NYASH_JOINIR_DEV=1 ./target/release/hakorune apps/tests/phase103_if_only_merge_min.hako 2>&1 \
| grep "phase121/shadow"
# [trace:dev] phase121/shadow: shadow_lowered=true ...
Design Highlights
Box-First Principle
- Single responsibility modules (contracts/builder/parity)
- Clear boundaries (capability check → lowering → parity)
- Testable units (12 unit tests)
Fail-Fast Enforcement
- No fallback on error (dev log or strict freeze)
- Explicit unsupported reasons (Loop/Break/Continue)
- Mandatory hint on freeze (
freeze_with_hintrequires non-empty hint)
SSOT Discipline
- No AST re-analysis (contract-only decisions)
- No env direct reads (all through
config::env/*) - No hardcoding (no fixture name branching)
Next Steps (Future Phases)
Phase 121 is complete. Future work:
- Actual lowering - Currently returns empty JoinModule, implement real conversion
- Loop support - Extend beyond if-only scope
- Value parity - Compare generated values (post-stabilization)
- LLVM harness - Complete LLVM smoke test configuration
- Production migration - Gradually enable for production paths
Known Limitations
- Stub implementation: Returns empty JoinModule (contract check only)
- If-only scope: Loops/breaks/continues rejected
- No value comparison: Only contracts compared (exits/writes)
- LLVM test incomplete: Harness configuration needed (VM tests validate core)
Files Modified
New files (6):
src/mir/control_tree/normalized_shadow/mod.rssrc/mir/control_tree/normalized_shadow/contracts.rssrc/mir/control_tree/normalized_shadow/builder.rssrc/mir/control_tree/normalized_shadow/parity.rstools/smokes/v2/profiles/integration/apps/phase121_shadow_if_only_vm.shtools/smokes/v2/profiles/integration/apps/phase121_shadow_if_only_llvm_exe.sh
Modified files (2):
src/mir/control_tree/mod.rs(normalized_shadow module export)src/mir/builder/calls/lowering.rs(dev-only wiring)docs/development/current/main/design/control-tree.md(Phase 121 design)
Commits
8d930d2dc- docs: Phase 121 StepTree→Normalized shadow plan1e5432f61- feat(control_tree): add StepTree→Normalized shadow lowerer (if-only, dev-only)89b868703- feat(joinir/dev): wire Phase 121 StepTree shadow lowering (strict fail-fast)0892df6df- test(joinir): Phase 121 shadow parity smokes (VM + LLVM EXE)
Total: +793 lines, 4 commits, 8 files