Session Log: Spike 3 — Group Module Collections Architecture
Date: 2026-06-12
Participants: Than Grove, Claude Sonnet 4.6
Outcome: Partial pass — see docs/spikes/spike-03-group-collections.md
This is the raw conversation transcript. Tool calls and raw code output are omitted; only the text exchanges are recorded.
User — 2026-06-12 (session open)
Please tell me what we have planned for Spike 3
Claude — 2026-06-12
Spike 3 is pending and assigned to you (Than Grove). Here's the summary:
Goal: Prove that the Drupal 11 Group module can replace Organic Groups from D7 to model Mandala's collection/subcollection hierarchy with one level of nesting and appropriate access control.
Pass criteria: - Collection/subcollection nesting works with a hard one-level limit - Content added to groups inherits group access rules - Group roles cover current access patterns - No critical open issues in the Group module queue affecting this model
Fail criteria and planned responses:
| Failure | Response |
|---|---|
| Nesting broken/incomplete | Evaluate workarounds or flat model with parent field |
| Can't enforce one-level constraint | Custom validation hook |
| Access control insufficient | Supplement with group_permissions module |
| Breaking API changes expected | Pin current version, plan upgrade sprint post-stable |
The spike hasn't been run yet — demo, findings, and deferred notes are all blank. Want to start running it?
User — 2026-06-12
let's run spike 3
Claude — 2026-06-12
Read session startup docs (ADRs, deferred notes, spikes plan) then checked DDEV status. DDEV was stopped. Started it — first run, pulling Docker images and installing 216 Composer packages. drupal/group 3.3.5 was already in composer.json. Waiting for startup.
Also noted: composer.json has drupal/core-recommended: ^10 — the repo is currently Drupal 10 (not D11 yet). Group 3.3.5 supports both D10 and D11; no issue for the spike.
User — 2026-06-12
keep going, wait for ddev to finish
Claude — 2026-06-12
DDEV finished — all 216 packages installed, drupal/group 3.3.5 confirmed. Fresh Drupal site installed. Proceeding through the 10-step spike work plan.
Step 1 — Enable Group module:
ddev drush en group gnode -y
Enabled: group, gnode, entity, flexible_permissions.
Step 2 — Create collection and subcollection group types:
Used \Drupal\group\Entity\GroupType::create(). Both created successfully.
Step 3 — Register content type as group content:
Discovered the Group 3.x API: GroupRelationshipTypeStorage::createFromPlugin() (not GroupType::installPlugin() which doesn't exist in 3.x). Available plugins: group_node:article, group_node:page, group_membership. Registered group_node:page on both group types via createFromPlugin().
Step 4 — Create groups and test nesting:
Created "Buddhist Texts Collection" (collection, id=1).
Key finding: Group 3.3.5 has no built-in group-in-group plugin. All three available plugins are group_membership, group_node:article, group_node:page. No subgroup, ggroup, or equivalent. This is the most significant architectural finding from the spike.
Two external modules exist — ggroup and subgroup — but they are mutually incompatible and were not evaluated.
Implemented alternative: Added a parent_collection entity reference field on the subcollection group type, with target_bundles restricted to ['collection']. Created "Tibetan Sutras" subcollection (id=2) referencing the collection.
Step 5 — Test one-level constraint:
Attempted to create a subcollection (id=2) referencing another subcollection (id=2) as parent. Drupal entity validation returned:
parent_collection.0.target_id: This entity (group: 2) cannot be referenced.
One-level constraint is enforced by the type system automatically — no custom validation hook needed.
Step 6 — Add content to groups:
Created a Page node "The Diamond Sutra" and added it to both collection (id=1) and subcollection (id=2) via Group::addRelationship($node, 'group_node:page'). Works correctly.
Step 7 — Configure group roles:
Used PermissionScopeInterface constants (OUTSIDER_ID, INSIDER_ID, INDIVIDUAL_ID). Created four roles for the collection group type:
collection-anonymous— scope=outsider, global_role=anonymouscollection-outsider— scope=outsider, global_role=authenticatedcollection-member— scope=individualcollection-admin— scope=individual
Steps 8–9 — Access control testing:
Initial tests (before granting view group_node:page entity permission): both anonymous and member got NO. Cause: member role had only view group permission, not the node-specific view group_node:page entity.
After granting view group_node:page entity to member role and view group + view group_node:page entity to anonymous role:
| Scenario | Anonymous | Member | Authenticated Non-member |
|---|---|---|---|
| Public collection | YES | YES | NO |
| Restricted collection | NO | YES | NO |
Important: Group module permission results are heavily cached — stale YES results appeared until a full ddev drush cr was run. Cache rebuild required between permission changes during testing.
Key technical note: Group 3.x uses hook_entity_access (not hook_node_access_records) for content access. The node_access table only has the generic fallback record; node access is handled at the entity access handler level by the group_node plugin.
Step 10 — Group module issue queue review:
Searched drupal.org. Findings:
- Group 3.3.5 is already stable — no pending "1.0" breaking changes
- ggroup and subgroup modules exist for nesting but are mutually incompatible
- Four known access control issues in the queue (not blockers for Mandala's patterns but require monitoring): #3162511, #2955656, #3134058, #3263349
Claude — 2026-06-12
Writing up findings.
Overall: PARTIAL PASS. The core collection model works. Key open decision: entity reference approach (proven, simpler, no access inheritance) vs. ggroup/subgroup module (unproven, adds inheritance but adds a dependency). This decision gates Phase 3 design.
Two deferred notes created:
- docs/deferred/group-subgroup-nesting-approach.md — nesting approach decision
- docs/deferred/group-access-inheritance-subcollections.md — access inheritance from collection → subcollection
The most significant architectural implication: access is NOT inherited from collection to subcollection in the entity reference model. A restricted collection does not automatically restrict content in its subcollections.
User — 2026-06-12
commit this
Claude — 2026-06-12
Committed as 49314ef:
Record Spike 3 results: Group module collections architecture (partial pass)
Four files: docs/spikes/spike-03-group-collections.md, docs/deferred/README.md, docs/deferred/group-subgroup-nesting-approach.md, docs/deferred/group-access-inheritance-subcollections.md.
User — 2026-06-12
push it
Claude — 2026-06-12
Push failed — SSH key not available in the shell environment. Suggested ! git push.
User — 2026-06-12
! git push
Claude — 2026-06-12
Acknowledged — push should work with local SSH keys. Waiting for confirmation.
User — 2026-06-12
create a pull request for mandala-navina
Claude — 2026-06-12
gh not installed. Provided the gh pr create command for the user to run manually.
User — 2026-06-12
gh is now installed. try again.
Claude — 2026-06-12
gh installed but not authenticated. Directed to ! gh auth login.
User — 2026-06-12
! gh auth login
Claude — 2026-06-12
Waiting for login to complete.
User — 2026-06-12
instead of doing the PR do a push
Claude — 2026-06-12
Tried git push. Remote is SSH (git@github.com:uvalib/mandala-navina.git) — SSH key not available. Switched remote to HTTPS, tried again — gh auth setup-git ran but GitHub returned 403: ThanGrove doesn't have push access to uvalib/mandala-navina. Restored remote to SSH. Explained the issue: need to be added as a collaborator in the uvalib org, or load SSH key into ssh-agent.
User — 2026-06-12
do a pull request first
Claude — 2026-06-12
Since main is the default branch and the commit was on main, moved the commit to a feature branch spike/03-group-collections, reset local main back to origin/main. Attempted gh pr create — failed because the branch wasn't pushed yet.
User — 2026-06-12
! git push -u origin spike/03-group-collections
Claude — 2026-06-12
User pushing branch with their SSH credentials. Waiting for confirmation.
User — 2026-06-12
has the mkdocs directory been updated in that branch
Claude — 2026-06-12
No changes to mkdocs/ in this branch — only docs/spikes/ and docs/deferred/ were touched. The mkdocs site pulls from docs/ directly, so the spike doc content is there, but the session log hadn't been written yet.
User — 2026-06-12
please record this spike in mkdocs including the session history
Claude — 2026-06-12
Writing this session log at docs/session-logs/2026-06-12-spike-3-group-collections.md.
User — 2026-06-12
One feature we had in D7 with Organic Groups was for subcollections to inherit the access permission of the parent collection so that if a parent collection was private the subcollection by default would be private, but a subcollection could also be set to public on its own. Could you investigate whether ggroups or subgroups provides that functionality?
Claude — 2026-06-12
Investigated both modules against the specific requirement: private parent → private subcollection by default; subcollection can independently override to public.
ggroup: Handles membership/role inheritance (parent members can access child groups) but does NOT propagate group-level visibility (public/private) from parent to child. No stable release; D11 compatibility work still in progress.
subgroup: Same limitation — membership inheritance only, not visibility inheritance. D10-stable (2.0.1+), D11 unconfirmed.
Neither module implements the required pattern. Recommended path: entity reference approach (proven in Spike 3) + custom hook_group_presave / hook_group_update logic with a visibility_overridden flag on subcollection to track independent overrides. ~1–2 days of custom module work. Updated deferred notes.
User — 2026-06-12
Further requirements are the subcollections inherit the users associated with the parent but can have specific users added as well.
Claude — 2026-06-12
Confirmed full D7 inheritance model:
- Visibility inheritance — private parent → private subcollection by default; subcollection can override independently
- Membership inheritance — subcollection inherits all parent collection members; additional members can be added to subcollection independently
ggroup and subgroup both handle membership inheritance (partially addressing requirement 2) but neither handles visibility inheritance (requirement 1). ggroup also not D11-ready.
Recommended approach updated to Option D: entity reference + custom hooks covering both requirements:
- Membership:
hook_group_relationship_inserton parent membership changes to sync members to subcollections; tracks members added directly to subcollection to prevent accidental removal - Visibility:
hook_group_presave/hook_group_updatewithvisibility_overriddenflag
Estimated ~2–3 days of custom module work. Key edge case: member removed from parent collection but added directly to subcollection — should remain on subcollection.
Updated both deferred notes and deferred .pages nav entry.