ADR 011: Group collections inheritance — entity-reference nesting + custom hooks
Status: Accepted Date: 2026-06-18 Deciders: Than Grove (Spike 3 lead, decision owner), Yuji Shinozaki (Lead Architect), Xiaoming Wang Relates to: Spike 3 (Partial → resolved by this ADR), ADR 009 (migration sequencing), Sprint 1 (Step 1b consumes this), Collections Inheritance Decision Brief (closed by this ADR) Supersedes: the "Recommended" standing of group-subgroup-nesting-approach.md and group-access-inheritance-subcollections.md
Context
The D11 rebuild replaces D7's Organic Groups (OG) + og_subgroups with the
Group 3.x module. Spike 3 proved the structural half on D11 — collection and
subcollection group bundles, one-level nesting via a parent_collection
entity-reference field, content membership, and basic access control — but
explicitly did not resolve the inheritance model.
Two requirements were confirmed (2026-06-12) as the minimum to match D7
og_subgroups behaviour, and are settled inputs to this decision:
- Visibility inheritance — a private parent collection ⇒ its subcollections are private by default; a subcollection may independently override to public.
- Membership inheritance — a subcollection inherits all members of its parent collection; additional subcollection-specific members may be added.
Production scale (verified against the 2026-06-11 D7 dump): 55 collections, 116 subcollections, one level deep — so the model only has to handle a single layer of nesting. Deeper nesting is explicitly deferred (separate concern, not gating MVP).
The Group 3.x contrib landscape, investigated 2026-06-12:
| Option | Membership inheritance | Visibility inheritance | D11 status |
|---|---|---|---|
| A — entity-reference field alone | ✗ | ✗ | Proven (Spike 3) |
B — ggroup |
✓ | ✗ (needs custom) | Not D11-ready (no stable release) |
C — subgroup |
✓ | ✗ (needs custom) | D10-stable; D11 unconfirmed |
| D — entity-reference + custom hooks | ✓ | ✓ | No dependency risk |
E — ggroup/subgroup + custom visibility hook |
✓ | ✓ | Dependency-blocked (per B/C) |
The decisive observation: no Group 3.x contrib option provides visibility
inheritance out of the box — visibility always requires custom logic.
That removes the strongest argument for taking on ggroup/subgroup as a
dependency, since their unique value (membership inheritance) doesn't
eliminate the custom-code requirement; it only shrinks it.
Decision
Adopt Option D: entity-reference nesting + a custom module providing both
inheritance behaviours. Structural nesting uses the Spike 3 pattern
(parent_collection entity-reference field on subcollection, restricted to
collection targets). A custom module implements visibility and membership
inheritance via Group's existing event hooks.
Structural shape (unchanged from Spike 3)
collectionandsubcollectionare sibling Group bundles.- A
subcollectionreferences its parent via a single-cardinalityparent_collectionentity-reference field, target-bundle restricted tocollection. This is what enforces "one level only" at the schema layer. - Content membership uses the standard
group_node:shanti_imagerelation plugin (and analogous relations for other content types in later sprints).
Custom inheritance hooks
Membership inheritance (matching D7 og_subgroups member cascade):
hook_group_relationship_insert— when asubcollectionis created with aparent_collection, enumerate the parent's members and add them to the subcollection.hook_group_relationship_inserton collection membership — when a member is added to acollection, also add them to everysubcollectionof that collection.hook_group_relationship_deleteon collection membership — when a member is removed from acollection, remove them from its subcollections unless they were also added directly to the subcollection (the sub-only retention edge case below).
Visibility inheritance (matching D7 private-parent cascade):
hook_group_presave— when asubcollectionis created, copy the parent collection's outsider/anonymous permissions to the subcollection as defaults.hook_group_update— when acollection's visibility changes, propagate to subcollections except those whosevisibility_overriddenflag is set.
The visibility_overridden flag
A boolean field on the subcollection bundle records whether an editor has
intentionally diverged from the parent's visibility. The flag is the integrity
guarantee that parent updates do not silently stomp deliberate per-subcollection
choices.
- Set automatically when a user changes a subcollection's visibility away from the parent's value.
- Cleared automatically when a user resets the subcollection's visibility to match the parent's current value.
- The
hook_group_updatepropagation reads this flag and skips overridden subcollections.
Sub-only member retention (membership edge case)
When a member is removed from a parent collection, the cascade-delete must
not remove that user from a subcollection if they were added to the
subcollection directly (independent of parent inheritance). The hook
distinguishes these cases by tracking whether a subcollection membership was
created by the cascade or by direct assignment — implementation may use a
provenance field, a relationship origin marker, or a lookup against the parent's
historical members; the choice is left to the module implementation but the
behavioural requirement is fixed by this ADR.
Sequencing — closes Q2 and Q3 of the decision brief
- Q2 (sequencing). All Group collections work — both structural CMI config
and the inheritance module — is held for Step 1b. No
collection/subcollectiongroup config lands in the Sprint 1 Step 1a baseline. The Images content-model audit explicitly puts OG-related fields out of scope for Step 1a, and Sprint 1 task 1a.7 (the pattern-setting image migration) does not touch OG/Group fields — they are deferred to 1b.2. - Q3 (phase ownership). The inheritance hooks land with 1b.2 in Sprint 1 Step 1b, not deferred to Phase 3. Rationale: Sprint 1's security acceptance criterion ("a restricted Images item is non-retrievable by an unauthorized user via the D11 search path") cannot honestly pass for content in a subcollection of a private collection without visibility inheritance in place. The "before Phase 3" wording in the superseded deferred notes was defensive against contrib-readiness risk, which Option D eliminates.
Consequences
Implementation work this adds to Sprint 1 Step 1b
Folded into task 1b.2 (currently "OG → Group migration"):
- Structural CMI config:
collectionandsubcollectiongroup bundles, theparent_collectionreference field, thevisibility_overriddenflag field, permission grids for both bundles, and thegroup_node:shanti_imagerelation plugin. - A new custom module (working name
mandala_group_inheritanceor similar) implementing the five hooks above. Approximate size: ~2–3 days of module work plus tests. - Migration of D7
field_og_collection_ref→ Group membership, and of D7group_content_access→collection/subcollectionvisibility — bridged by the inheritance module so the migrated state reproduces D7 behaviour.
Sprint 1 acceptance gates that now have a real basis:
- 1b.3 (Solr-proxy visibility coherence) can be exercised end-to-end against nested-collection content.
- 1b.4 (paragraph access inheritance) composes correctly because the parent image's access is now correct under nesting.
- Sprint 1's overall security acceptance criterion is meetable for the full collection / subcollection topology, not only flat-collection content.
Architectural consequences
- No new contrib dependency. The MVP avoids
ggroup(not D11-ready) andsubgroup(D11 unconfirmed). The Group 3.x contrib bet remains revisitable later (Option E retains design value) without blocking MVP. - Inheritance logic is explicit, in this repo, and testable. All behavioural divergence from off-the-shelf Group access lives in one module rather than being implicit in a contrib module's plugin internals.
- One-level-only is enforced structurally by the entity-reference target bundle restriction. Deeper nesting is not blocked by this ADR but is out of scope; revisiting it would require schema changes and a successor ADR.
What this ADR does not decide
- The custom module's machine name, internal API surface, or test framework choice — those are implementation details for the 1b.2 work.
- The visibility-flag UI affordance (whether to expose
visibility_overriddenas an editable toggle in the subcollection form or to manage it automatically only) — recommended automatic management, but a UI affordance may be added without superseding this ADR. - Performance characteristics under large parent-membership cascades. With production at 55/116 and small membership counts, this is not a near-term concern; revisit if it becomes one.
Documentation effects
- Closes the Collections Inheritance Decision Brief.
- Supersedes the "Recommended" standing of the two deferred notes
(
group-subgroup-nesting-approach.md,group-access-inheritance-subcollections.md); they remain as historical context for the investigation but the open recommendation is now this ADR. - Spike 3 transitions from ◐ Partial to ● Proven for MVP purposes — its open question (the inheritance approach) is now resolved here.