test/docs(ownership): attach-ceiling journey + M2 status

The apply_ownership journey gains an attach-ceiling case: a group node below
the attach point applies; the attach point itself and a sibling subtree are
both refused (422, message names the attach point). Design doc §7 + CLAUDE.md
record M2 shipped and re-point 'Next' at M3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-06 20:57:57 +02:00
parent 5bb1ccad5e
commit 5a820d7262
3 changed files with 88 additions and 5 deletions

View File

@@ -214,3 +214,80 @@ fn claim_conflict_takeover_and_app_inheritance() {
"a refused takeover must not change ownership"
);
}
/// §6/§7 M2 — `[project] parent_group` is the ceiling: applies are refused for
/// any node not strictly within the attach point's subtree.
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn attach_point_ceiling_bounds_the_subtree() {
let Some(fx) = common::fixture_or_skip() else {
return;
};
let env = common::admin_env(fx);
// acme (root) → team (child); plus a sibling `outsider` root.
let acme = common::unique_slug("acme");
let team = common::unique_slug("team");
let outsider = common::unique_slug("outsider");
let _a = GroupGuard::new(&env.url, &env.token, &acme);
let _t = GroupGuard::new(&env.url, &env.token, &team);
let _o = GroupGuard::new(&env.url, &env.token, &outsider);
common::pic_as(&env)
.args(["groups", "create", &acme])
.assert()
.success();
common::pic_as(&env)
.args(["groups", "create", &team, "--parent", &acme])
.assert()
.success();
common::pic_as(&env)
.args(["groups", "create", &outsider])
.assert()
.success();
let proj = common::unique_slug("attach-p");
let dir = manifest_dir();
let apply = |m: &str| -> std::process::Output {
fs::write(dir.path().join("picloud.toml"), m).unwrap();
common::pic_as(&env)
.args(["apply", "--file"])
.arg(dir.path().join("picloud.toml"))
.output()
.expect("apply")
};
// A group node strictly BELOW the attach point → ok.
let out = apply(&format!(
"[project]\nslug = \"{proj}\"\nparent_group = \"{acme}\"\n\n\
[group]\nslug = \"{team}\"\nname = \"Team\"\n"
));
assert!(
out.status.success(),
"a node below the attach point applies: {}",
String::from_utf8_lossy(&out.stderr)
);
// The attach point ITSELF → refused (you can't apply above your local root).
let out = apply(&format!(
"[project]\nslug = \"{proj}\"\nparent_group = \"{acme}\"\n\n\
[group]\nslug = \"{acme}\"\nname = \"Acme\"\n"
));
assert!(
!out.status.success(),
"applying the attach point itself must be refused"
);
let err = String::from_utf8_lossy(&out.stderr).to_lowercase();
assert!(
err.contains("attach point"),
"the refusal must mention the attach point:\n{err}"
);
// A SIBLING subtree (not under acme) → refused.
let out = apply(&format!(
"[project]\nslug = \"{proj}\"\nparent_group = \"{acme}\"\n\n\
[group]\nslug = \"{outsider}\"\nname = \"Out\"\n"
));
assert!(
!out.status.success(),
"a sibling subtree is outside the attach point"
);
}