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:
@@ -261,6 +261,14 @@ struct ApplyArgs {
|
||||
/// top of the base manifest before applying.
|
||||
#[arg(long)]
|
||||
env: Option<String>,
|
||||
/// §6 M2: reparent a group whose server parent diverges from the
|
||||
/// manifest's directory nesting (default: refuse on divergence).
|
||||
#[arg(long, conflicts_with = "adopt_server_structure")]
|
||||
force_local_structure: bool,
|
||||
/// §6 M2: accept the server's group placement on a structural
|
||||
/// divergence and reconcile content in place (no reparent).
|
||||
#[arg(long)]
|
||||
adopt_server_structure: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
@@ -1433,12 +1441,20 @@ async fn main() -> ExitCode {
|
||||
Cmd::Whoami => cmds::whoami::run(mode).await,
|
||||
Cmd::Apply(args) => match &args.dir {
|
||||
Some(dir) => {
|
||||
let structure_mode = if args.force_local_structure {
|
||||
"force-local"
|
||||
} else if args.adopt_server_structure {
|
||||
"adopt-server"
|
||||
} else {
|
||||
"refuse"
|
||||
};
|
||||
cmds::apply::run_tree(
|
||||
dir,
|
||||
args.prune,
|
||||
args.yes,
|
||||
args.force,
|
||||
args.takeover,
|
||||
structure_mode,
|
||||
args.env.as_deref(),
|
||||
mode,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user