docs+test(hierarchies): end-to-end review follow-ups (M5/M6/M7)
Findings from a 3-lens end-to-end review (correctness, security, tests).
Security review came back clean (cross-repo authz sound, isolation intact,
M5 gate sound modulo the documented manifest-trust boundary). No behavior
changes here — only accuracy + coverage:
- apply_service Phase B2: correct the descendant-expansion comment. The chain
is COMMITTED ancestry (ancestors() reads the pool), so a reparent in the same
apply takes effect next apply — same "one more apply" shape the in-tree path
and group-create reparenting already have. Authz stays sound: the gate and the
expansion consume the SAME chain (checked == written).
- doc §4.5: document the two known "one more apply / no data risk" limitations
— reparent-in-same-apply template resolution, and the `{env}` source split
(declared apps use --env; cross-repo descendants use apps.environment).
- approval journey: give the app real (`[vars]`) content so the editor member's
AppVarsWrite is actually exercised — the admin-gate test now proves approval
is ABOVE editor-write, not just "any non-admin is refused". (Vars cascade with
the app; scripts are ON DELETE RESTRICT and would break AppGuard teardown.)
- templates journey: assert descendant expansions are idempotent (stable row
ids on re-apply — no churn), matching the in-tree guarantee.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1615,12 +1615,20 @@ impl ApplyService {
|
||||
.iter()
|
||||
.map(|g| g.id)
|
||||
.collect();
|
||||
// The chain is the descendant's COMMITTED ancestry (`ancestors` reads
|
||||
// the pool, not this tx). A reparent performed in THIS same apply is
|
||||
// therefore not yet reflected, so a template gained/lost via that
|
||||
// reparent fans out on the NEXT apply — the same "reparent takes one
|
||||
// more apply" limitation the in-tree path has (prepare_tree also
|
||||
// computes app chains pre-tx) and that group-create reparenting
|
||||
// already documents above (§4.5 known limitation).
|
||||
//
|
||||
// Authoritative IN-TX per-recipient authz (§7.4, M7): the actor needs
|
||||
// the matching write cap on THIS descendant before any expansion is
|
||||
// written into it. Checked here (post-reparent, already locked) so the
|
||||
// checked set is by construction the written set — no pre-tx TOCTOU
|
||||
// and a reparent-into-a-templated-group can't slip the gate. Sound via
|
||||
// the hierarchy-aware effective_app_role: a group admin/editor is
|
||||
// written into it. The gate and the expansion below consume the SAME
|
||||
// `chain`, so the checked set is by construction the written set — no
|
||||
// pre-tx TOCTOU, the per-app cap can't be slipped. Sound via the
|
||||
// hierarchy-aware effective_app_role: a group admin/editor is
|
||||
// implicitly authorized on its subtree; a narrow principal is refused.
|
||||
let route_tmpls = crate::template_repo::list_for_groups_tx(&mut tx, &chain)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user