diff --git a/docs/development/current/main/design/control-tree.md b/docs/development/current/main/design/control-tree.md index b33c7e23..da58197a 100644 --- a/docs/development/current/main/design/control-tree.md +++ b/docs/development/current/main/design/control-tree.md @@ -122,3 +122,87 @@ StepTree は capability を“宣言”し、未対応は **Fail-Fast(dev-only - 既定では出さない(既定挙動不変)。 - `NYASH_JOINIR_DEV=1` のときのみ StepTree をダンプする(prefix は `[trace:dev] control_tree/step_tree`)。 - StepTree は routing の入力にしない(当面は parity/観測のみ。routing SSOT は feature extractor + analyzer)。 + +## Phase 121: StepTree→Normalized Shadow Lowering + +**目的**: StepTree(構造SSOT)から Normalized 形式への最小ルートを確立し、if-only パターンで VM/LLVM との parity を検証する。 + +**スコープ**: if-only(loop無し)のみ。loop は capability guard で拒否。 + +### 設計ルール + +**入力SSOT**: +- `StepTree` + `StepTreeContract`(facts 再解析禁止) +- contract に含まれる情報のみで lowering 判断 + +**出力**: +- `JoinModule`(Normalized 方言) +- または "Normalized 相当の中間" を JoinIR 既存型で表現 + +**実行条件**: +- `joinir_dev_enabled()` のときのみ shadow 変換を実行(dev-only) +- `joinir_strict_enabled()` のときのみ mismatch を Fail-Fast + +**禁止事項**: +- fallback 禁止: shadow 変換失敗時は "disabled 扱い" ではなく dev-only で理由ログ、strict で Fail-Fast +- env 直読み禁止(`src/config/env/*` 経由必須) +- ハードコード禁止(fixture 名や変数名で分岐しない) + +### 責務分離(Box化) + +**`StepTreeNormalizedShadowLowererBox`**: +- 責務: StepTree→JoinModule 変換(if-only限定) +- 入力: `&StepTree` +- 出力: `Result, String>` + - `Ok(None)` = if-only 対象外(例: loop 含む) + - `Ok(Some(...))` = shadow 生成成功 + - `Err(...)` = 生成できるはずなのに壊れている + +**`normalized_shadow/contracts.rs`**: +- 責務: "if-only に限定" チェック、capability 拒否理由の SSOT +- Unsupported capability の明示的列挙(Loop / Break / Continue 等) + +**`normalized_shadow/parity.rs`**: +- 責務: router/既存経路との比較(dev ログ / strict fail-fast) +- 比較対象: 出口契約(`exits`)と writes の一致(最小で壊れにくい) +- strict mode では `freeze_with_hint` でエラー(hint 必須) + +### Parity検証(最小セット) + +**比較対象**(値の一致まではやらない): +- `StepTreeContract.exits` / `writes` +- 既存ルータ・既存抽出から得られる "exit/writes" 相当 + +**不一致時の挙動**: +- dev mode: 1行ログ `[trace:dev] phase121/shadow/parity_mismatch: ...` +- strict mode: `error_tags::freeze_with_hint("phase121/shadow/parity_mismatch", msg, hint)` + +### デバッグ出力(dev-only) + +**1行ログ形式**: +``` +[trace:dev] phase121/shadow: step_tree_sig=... shadow_lowered=true/false reason=... exits=... writes=... +``` + +**strict fail-fast**: +- "if-only なのに shadow が作れない" は即座に `freeze_with_hint` +- hint 空禁止(必ず具体的な理由を1行で記述) + +### 配線場所 + +**`src/mir/builder/calls/lowering.rs`**: +- `lower_function_body` 内の StepTree capability guard 近辺 +- `joinir_dev_enabled()` のときのみ shadow lowerer を呼ぶ +- 既存の本番経路はそのまま実行(結果は一切変えない) + +### テスト戦略 + +**Smoke tests**: +- 既存 fixture を流用(`phase103_if_only_merge_min.hako` 等) +- VM ラインと LLVM ライン両方で実行 +- `HAKO_JOINIR_STRICT=1` で strict mode 検証 +- `NYASH_JOINIR_DEV=1` で dev mode ログ確認 + +**期待値**: +- 既存と同じ数値出力(output_validator.sh で比較) +- strict mode で落ちない(= shadow parity mismatch が無い)