feat(apply): [project] parent_group attach-point ceiling
§6/§7 M2: a project's attach point is its ceiling — applies are refused for any node not strictly within the declared subtree, so a repo can't reach (or claim) above its local root even with the RBAC to do so. - `[project] parent_group = "<slug>"` (ManifestProject + ProjectDecl); absent = instance root = no ceiling (the default, backward-compatible). - `check_within_attach`: the attach group must be a PROPER ancestor of a group node (you can't apply the attach point itself) or an ancestor (inclusive) of an app node's group; resolved via `groups.ancestors`. Enforced read-only in apply_owner (prologue) + apply_tree (per node), before the claim. - `ApplyError::OutsideAttachPoint` → 422 (a scope error, like Invalid). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -278,6 +278,11 @@ pub struct ManifestProject {
|
||||
pub slug: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
/// `parent_group` (§7/§6) — the pre-existing group this repo binds UNDER
|
||||
/// (its attach point). Applies are refused for any node not strictly within
|
||||
/// that subtree. Absent = instance root = no ceiling (the default).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub parent_group: Option<String>,
|
||||
}
|
||||
|
||||
/// The store kind of a shared collection (§11.6).
|
||||
@@ -973,6 +978,14 @@ mod tests {
|
||||
// Unknown key inside [project] is rejected (deny_unknown_fields).
|
||||
Manifest::parse("[project]\nslug=\"p\"\nbogus=1\n[app]\nslug=\"w\"\nname=\"W\"\n")
|
||||
.expect_err("unknown [project] key rejected");
|
||||
|
||||
// §6/§7 M2: the optional attach-point `parent_group`.
|
||||
let m = Manifest::parse(
|
||||
"[project]\nslug = \"p\"\nparent_group = \"acme\"\n\n\
|
||||
[group]\nslug = \"team\"\nname = \"T\"\n",
|
||||
)
|
||||
.expect("parent_group parses");
|
||||
assert_eq!(m.project.unwrap().parent_group.as_deref(), Some("acme"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user