feat(apply): §6 M2 — structural-divergence detection + declarative reparent
With the declared parent on the wire (M1), `apply_tree` now compares each EXISTING group node's server parent to the manifest's (directory-nesting) parent and resolves a divergence per a request `StructureMode` (default `Refuse` — a pre-M2 CLI never reshapes the tree): - `Refuse` → `ApplyError::StructuralDivergence` (422), naming the flags. - `ForceLocal` → reparent to the declared parent IN the apply tx via the extracted `group_repo::reparent_group_tx` (cycle guard + structure_version bump), §5.6-gated (`GroupAdmin` on node + source + destination) and attach-ceilinged. - `AdoptServer` → keep the server shape, reconcile content in place. M1's `create_missing_groups_tx` generalized to `reconcile_group_structure_tx` (create + reparent share the coarse-lock / attach-ceiling / RBAC path; both are recorded `structurally_changed` so the pool-based attach re-check skips their uncommitted rows). `pic plan` previews the drift (`divergence_preview` → a `structure` row naming server + manifest parents). CLI: `--force-local-structure` / `--adopt-server-structure` (mutually exclusive) → the `structure_mode` wire field; the 422 surfaces verbatim. A concurrent `pic groups reparent` between plan and apply still trips `StateMoved` (the tree token already folds structure_version). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,21 @@ fn render_tree(plan: &TreePlanDto, mode: OutputMode) {
|
||||
let mut table = Table::new(["node", "kind", "op", "resource", "detail"]);
|
||||
for n in &plan.nodes {
|
||||
let node = format!("{}:{}", n.kind, n.slug);
|
||||
if let Some(s) = &n.structure {
|
||||
table.row([
|
||||
node.clone(),
|
||||
"structure".to_string(),
|
||||
s.status.clone(),
|
||||
format!(
|
||||
"server={}",
|
||||
s.server_parent.clone().unwrap_or_else(|| "<root>".into())
|
||||
),
|
||||
format!(
|
||||
"manifest={}",
|
||||
s.manifest_parent.clone().unwrap_or_else(|| "<root>".into())
|
||||
),
|
||||
]);
|
||||
}
|
||||
if let Some(o) = &n.ownership {
|
||||
table.row([
|
||||
node.clone(),
|
||||
|
||||
Reference in New Issue
Block a user