feat(apply): cross-repo blast-radius in the plan preview
§7 M3 (part 2): planning a change to a GROUP node now reports the descendant apps owned by OTHER projects that the change fans out to — the cross-repo signal an operator needs before touching shared config. - OwnershipPreview gains `blast_radius: Vec<ProjectImpact>`; `group_blast_radius` walks the group's subtree (recursive CTE) and groups descendant apps by their nearest-claimed-ancestor project, excluding the planning project. Ancestor resolution is memoized per group (one walk per distinct descendant group, not per app). - `pic plan` renders `blast_radius` rows (mode-agnostic). - the apply_ownership journey gains a plan-preview case pinning both the ownership annotation (claim / conflict) and the blast radius (two other projects' apps surfaced) end to end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1548,12 +1548,21 @@ pub struct ChangeDto {
|
||||
}
|
||||
|
||||
/// §7 M3 plan preview: `action` ∈ claim/owned/conflict/unclaimed; `owner` is
|
||||
/// the current owner's slug (for owned/conflict).
|
||||
/// the current owner's slug (for owned/conflict); `blast_radius` (M3.2) lists
|
||||
/// other projects' apps a group change fans out to.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct OwnershipPreviewDto {
|
||||
pub action: String,
|
||||
#[serde(default)]
|
||||
pub owner: Option<String>,
|
||||
#[serde(default)]
|
||||
pub blast_radius: Vec<ProjectImpactDto>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProjectImpactDto {
|
||||
pub project: String,
|
||||
pub apps: u32,
|
||||
}
|
||||
|
||||
/// Response of `POST .../tree/plan` (Phase 5): a per-node diff + one combined
|
||||
|
||||
@@ -74,6 +74,15 @@ fn render_tree(plan: &TreePlanDto, mode: OutputMode) {
|
||||
o.owner.clone().unwrap_or_default(),
|
||||
String::new(),
|
||||
]);
|
||||
for b in &o.blast_radius {
|
||||
table.row([
|
||||
node.clone(),
|
||||
"blast_radius".to_string(),
|
||||
b.project.clone(),
|
||||
format!("{} app(s)", b.apps),
|
||||
String::new(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
let groups: [(&str, &Vec<ChangeDto>); 8] = [
|
||||
("script", &n.scripts),
|
||||
@@ -203,6 +212,14 @@ fn render(plan: &PlanDto, mode: OutputMode) {
|
||||
o.owner.clone().unwrap_or_default(),
|
||||
String::new(),
|
||||
]);
|
||||
for b in &o.blast_radius {
|
||||
table.row([
|
||||
"blast_radius".to_string(),
|
||||
b.project.clone(),
|
||||
format!("{} app(s)", b.apps),
|
||||
String::new(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
let groups: [(&str, &Vec<ChangeDto>); 8] = [
|
||||
("script", &plan.scripts),
|
||||
|
||||
Reference in New Issue
Block a user