diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md
index 67f19cbe..685913de 100644
--- a/CURRENT_TASK.md
+++ b/CURRENT_TASK.md
@@ -18,9 +18,12 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu
**CorePlan migration 道筋 SSOT**
`docs/development/current/main/design/coreplan-migration-roadmap-ssot.md` が移行タスクの Done 判定の入口。
-**Next implementation (Phase 29ao P30)**
-- 目的: Facts→CorePlan の入口を `plan/composer` に集約し、Normalizer の責務を DomainPlan→CorePlan に縮退(挙動不変)
-- 指示書: `docs/development/current/main/phases/phase-29ao/P30-MOVE-SHADOW-ADOPT-COMPOSER-SSOT-INSTRUCTIONS.md`
+**Next implementation (Phase 29ao P31)**
+- 目的: TBD
+- 指示書: TBD
+
+**2025-12-30: Phase 29ao P30 COMPLETE (Shadow adopt composer SSOT)**
+Facts→CorePlan の入口を `plan/composer` に集約し、Normalizer の責務を DomainPlan→CorePlan に縮退した(挙動不変)。
**2025-12-30: Phase 29ao P25 COMPLETE (Pattern5 strict/dev adopt from facts)**
Pattern5(Infinite Early-Exit)を strict/dev で Facts→CorePlan に寄せ、DomainPlan 経路との差分を Fail-Fast で検知できるようにした。
diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md
index f8514ec8..585761c7 100644
--- a/docs/development/current/main/10-Now.md
+++ b/docs/development/current/main/10-Now.md
@@ -2,12 +2,17 @@
## Current Focus: Phase 29ao(CorePlan composition)
-Next: Phase 29ao P30(Shadow adopt composer SSOT)
-指示書: `docs/development/current/main/phases/phase-29ao/P30-MOVE-SHADOW-ADOPT-COMPOSER-SSOT-INSTRUCTIONS.md`
+Next: Phase 29ao P31(TBD)
+指示書: TBD
運用ルール: integration filter で phase143_* は回さない(JoinIR 回帰は phase29ae pack のみ)
運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う
移行道筋 SSOT: `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md`
+**2025-12-30: Phase 29ao P30 完了** ✅
+- 目的: Facts→CorePlan の入口を `plan/composer` に集約し、Normalizer の責務を DomainPlan→CorePlan に縮退(挙動不変)
+- 変更: `src/mir/builder/control_flow/plan/composer/shadow_adopt.rs` / `src/mir/builder/control_flow/plan/composer/mod.rs` / `src/mir/builder/control_flow/joinir/patterns/router.rs` / `src/mir/builder/control_flow/plan/normalizer/pattern2_break.rs` / `src/mir/builder/control_flow/plan/normalizer/pattern3_if_phi.rs` / `src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs` / `src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs` / `src/mir/builder/control_flow/plan/normalizer/pattern_split_scan.rs` / `docs/development/current/main/phases/phase-29ao/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md` / `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md`
+- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh`
+
**2025-12-30: Phase 29ao P29 完了** ✅
- 目的: regression gate 全パターンで shadow adopt タグを必須化し、strict/dev 実踏みを SSOT 化(仕様不変)
- 変更: `src/mir/builder/control_flow/joinir/patterns/router.rs` / `tools/smokes/v2/profiles/integration/joinir/phase29ao_pattern1_strict_shadow_vm.sh` / `tools/smokes/v2/profiles/integration/joinir/phase29ao_pattern5_strict_shadow_vm.sh` / `tools/smokes/v2/profiles/integration/apps/phase29ai_pattern2_break_plan_subset_ok_min_vm.sh` / `tools/smokes/v2/profiles/integration/apps/phase118_pattern3_if_sum_vm.sh` / `tools/smokes/v2/lib/test_runner.sh` / `docs/development/current/main/phases/phase-29ae/README.md` / `docs/development/current/main/phases/phase-29ao/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md` / `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md`
diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md
index e2d8e0db..136ea590 100644
--- a/docs/development/current/main/30-Backlog.md
+++ b/docs/development/current/main/30-Backlog.md
@@ -15,8 +15,8 @@ Related:
- **Phase 29ao(active): CorePlan composition from Skeleton/Feature**
- 入口: `docs/development/current/main/phases/phase-29ao/README.md`
- - 状況: P0–P29 ✅ 完了 / Next: P30(Shadow adopt composer SSOT)
- - Next 指示書: `docs/development/current/main/phases/phase-29ao/P30-MOVE-SHADOW-ADOPT-COMPOSER-SSOT-INSTRUCTIONS.md`
+ - 状況: P0–P30 ✅ 完了 / Next: P31(TBD)
+ - Next 指示書: TBD
- **Phase 29af(✅ COMPLETE): Boundary hygiene / regression entrypoint / carrier layout SSOT**
- 入口: `docs/development/current/main/phases/phase-29af/README.md`
diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md
index 4c49d879..4dc20b04 100644
--- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md
+++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md
@@ -34,7 +34,7 @@ Related:
## 1.1 Current (active)
- Active phase: `docs/development/current/main/phases/phase-29ao/README.md`
-- Next step: `docs/development/current/main/phases/phase-29ao/P30-MOVE-SHADOW-ADOPT-COMPOSER-SSOT-INSTRUCTIONS.md`
+- Next step: Phase 29ao P31(TBD)
## 2. すでに固めた SSOT(再発防止の土台)
diff --git a/docs/development/current/main/phases/phase-29ao/README.md b/docs/development/current/main/phases/phase-29ao/README.md
index 7577cd32..fdb65d97 100644
--- a/docs/development/current/main/phases/phase-29ao/README.md
+++ b/docs/development/current/main/phases/phase-29ao/README.md
@@ -172,13 +172,12 @@ Gate(SSOT):
- 指示書: `docs/development/current/main/phases/phase-29ao/P29-SHADOW-ADOPT-TAGS-COVERAGE-ALL-GATE-PATTERNS-INSTRUCTIONS.md`
- ねらい: regression gate に含まれる全パターンで “shadow adopt を踏んだ” をタグ必須として固定(仕様不変)
-## P30: Shadow adopt composer SSOT(Facts→CorePlan入口を集約)
+## P30: Shadow adopt composer SSOT(Facts→CorePlan入口を集約)✅
- 指示書: `docs/development/current/main/phases/phase-29ao/P30-MOVE-SHADOW-ADOPT-COMPOSER-SSOT-INSTRUCTIONS.md`
- ねらい: Facts→CorePlan の入口を `plan/composer` に集約し、Normalizer の責務を DomainPlan→CorePlan に縮退(挙動不変)
## Next(planned)
-- Next: P30(実装)
- - 指示書: `docs/development/current/main/phases/phase-29ao/P30-MOVE-SHADOW-ADOPT-COMPOSER-SSOT-INSTRUCTIONS.md`
-- After P30: P31(TBD)
+- Next: P31(TBD)
+- After P31: TBD
diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs
index ac7ab7c9..7e5fd7e0 100644
--- a/src/mir/builder/control_flow/joinir/patterns/router.rs
+++ b/src/mir/builder/control_flow/joinir/patterns/router.rs
@@ -28,6 +28,7 @@ use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind};
use crate::mir::builder::control_flow::plan::lowerer::PlanLowerer;
use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer;
use crate::mir::builder::control_flow::plan::verifier::PlanVerifier;
+use crate::mir::builder::control_flow::plan::composer;
use crate::mir::builder::control_flow::plan::single_planner;
/// AST Feature Extractor (declared in mod.rs as pub module, import from parent)
@@ -349,7 +350,7 @@ pub(crate) fn route_loop_pattern(
return Err("pattern1 strict/dev adopt failed: facts mismatch".to_string());
}
let core_plan =
- PlanNormalizer::normalize_loop_skeleton_from_facts(builder, facts, ctx)?
+ composer::compose_coreplan_for_pattern1_simplewhile(builder, facts, ctx)?
.ok_or_else(|| {
"pattern1 strict/dev adopt failed: skeleton compose rejected".to_string()
})?;
@@ -372,7 +373,7 @@ pub(crate) fn route_loop_pattern(
return Err("pattern3 strict/dev adopt failed: facts mismatch".to_string());
}
let core_plan =
- PlanNormalizer::normalize_pattern3_if_phi_from_facts(builder, facts, ctx)?
+ composer::compose_coreplan_for_pattern3_ifphi(builder, facts, ctx)?
.ok_or_else(|| {
"pattern3 strict/dev adopt failed: compose rejected".to_string()
})?;
@@ -398,7 +399,7 @@ pub(crate) fn route_loop_pattern(
if facts.facts.scan_with_init.is_none() {
return Err("pattern6 strict/dev adopt failed: facts mismatch".to_string());
}
- let core_plan = PlanNormalizer::normalize_scan_with_init_from_facts(builder, facts, ctx)?
+ let core_plan = composer::compose_coreplan_for_pattern6_scan_with_init(builder, facts, ctx)?
.ok_or_else(|| "pattern6 strict/dev adopt failed: compose rejected".to_string())?;
PlanVerifier::verify(&core_plan)?;
eprintln!("[coreplan/shadow_adopt:pattern6_scan_with_init]");
@@ -418,7 +419,7 @@ pub(crate) fn route_loop_pattern(
if facts.facts.split_scan.is_none() {
return Err("pattern7 strict/dev adopt failed: facts mismatch".to_string());
}
- let core_plan = PlanNormalizer::normalize_split_scan_from_facts(builder, facts, ctx)?
+ let core_plan = composer::compose_coreplan_for_pattern7_split_scan(builder, facts, ctx)?
.ok_or_else(|| "pattern7 strict/dev adopt failed: compose rejected".to_string())?;
PlanVerifier::verify(&core_plan)?;
eprintln!("[coreplan/shadow_adopt:pattern7_split_scan]");
@@ -439,7 +440,7 @@ pub(crate) fn route_loop_pattern(
return Err("pattern5 strict/dev adopt failed: facts mismatch".to_string());
}
let core_plan =
- PlanNormalizer::normalize_pattern5_infinite_early_exit_from_facts(builder, facts, ctx)?
+ composer::compose_coreplan_for_pattern5_infinite_early_exit(builder, facts, ctx)?
.ok_or_else(|| {
"pattern5 strict/dev adopt failed: compose rejected".to_string()
})?;
@@ -465,7 +466,7 @@ pub(crate) fn route_loop_pattern(
if facts.facts.pattern2_break.is_none() {
return Err("pattern2 strict/dev adopt failed: facts mismatch".to_string());
}
- let core_plan = PlanNormalizer::normalize_pattern2_break_from_facts(builder, facts, ctx)?
+ let core_plan = composer::compose_coreplan_for_pattern2_break_subset(builder, facts, ctx)?
.ok_or_else(|| "pattern2 strict/dev adopt failed: compose rejected".to_string())?;
PlanVerifier::verify(&core_plan)?;
eprintln!("[coreplan/shadow_adopt:pattern2_break_subset]");
diff --git a/src/mir/builder/control_flow/plan/composer/mod.rs b/src/mir/builder/control_flow/plan/composer/mod.rs
index 85348447..0bd06e98 100644
--- a/src/mir/builder/control_flow/plan/composer/mod.rs
+++ b/src/mir/builder/control_flow/plan/composer/mod.rs
@@ -1,5 +1,7 @@
//! Phase 29ao P0: CorePlan composer scaffold (CanonicalLoopFacts -> CorePlan)
+mod shadow_adopt;
+
use super::{normalizer::PlanNormalizer, CorePlan, DomainPlan, Pattern1SimpleWhilePlan};
use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind;
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
@@ -7,6 +9,15 @@ use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts;
use crate::mir::builder::control_flow::plan::planner::Freeze;
use crate::mir::builder::MirBuilder;
+pub(in crate::mir::builder) use shadow_adopt::{
+ compose_coreplan_for_pattern1_simplewhile,
+ compose_coreplan_for_pattern2_break_subset,
+ compose_coreplan_for_pattern3_ifphi,
+ compose_coreplan_for_pattern5_infinite_early_exit,
+ compose_coreplan_for_pattern6_scan_with_init,
+ compose_coreplan_for_pattern7_split_scan,
+};
+
#[allow(dead_code)]
pub(in crate::mir::builder) fn try_compose_domain_plan_from_canonical_facts(
facts: &CanonicalLoopFacts,
diff --git a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs
new file mode 100644
index 00000000..4f290416
--- /dev/null
+++ b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs
@@ -0,0 +1,133 @@
+//! Phase 29ao P30: shadow adopt composer entrypoints (Facts -> CorePlan).
+
+use super::PlanNormalizer;
+use crate::ast::{ASTNode, Span};
+use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
+use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts;
+use crate::mir::builder::control_flow::plan::{
+ CorePlan, DomainPlan, Pattern2BreakPlan, Pattern2PromotionHint,
+ Pattern3IfPhiPlan, Pattern5InfiniteEarlyExitPlan, ScanDirection, ScanWithInitPlan,
+ SplitScanPlan,
+};
+use crate::mir::builder::MirBuilder;
+
+pub(in crate::mir::builder) fn compose_coreplan_for_pattern1_simplewhile(
+ builder: &mut MirBuilder,
+ facts: &CanonicalLoopFacts,
+ ctx: &LoopPatternContext,
+) -> Result