`creating_a_group_requires_group_admin_on_the_parent` asserted only that the
member's apply exited non-zero — so it would pass even with the GroupAdmin check
removed, because the apply would still fail for an unrelated reason (a 404 on the
attach-point lookup, a credential error, a 500). It now asserts the stderr carries
`HTTP 403` — specifically the authz denial — matching the rest of the RBAC suite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review #1 (HIGH). M1's `authz_tree` skips the group content-write capability
check (`require_group_node_writes`) for a group node absent at API-request
time — a to-create group whose authorization is the service create-gate. But
that gate only fires when the group is STILL absent at reconcile, and the
content writer performs no authz. So a group created by a racer in the window
between the API check and the in-tx reconcile could have its scripts/vars
written by a principal with zero rights on it (the bound-plan token doesn't
save a hand-rolled request that omits `expected_token`).
Close it with an in-tx content-write re-check in `apply_tree` for every group
NOT structurally created/reparented by this apply: a group WE created is
covered by create-RBAC (`GroupAdmin(parent)` cascades) and its uncommitted row
is invisible to the pool-based authz cascade anyway, so it must not be
re-checked; a pre-existing (incl. racer-created) group is committed, so its
ancestry resolves and a missing cap denies. `require_group_node_writes` moves
from `apply_api` onto `ApplyService` (up the existing dependency edge) so the
API pre-check and the in-tx re-check share one implementation.
Pinned by `tests/group_create.rs::tree_apply_denies_group_content_write_without_the_cap`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`tests/group_create.rs`: a nested `[group]` tree with no server groups yet →
`pic apply --dir` creates both (parent from directory nesting), claims them for
the `[project]`, and re-applies as a no-op; a member with only editor (no
group-admin) is refused and leaves nothing behind. Design doc §6 records M1
shipped (parent-by-nesting, Phase-0 create-in-tx, claim, RBAC + attach ceiling)
with reparent/divergence still deferred to M2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>