Skip to content

0004 — Forms Permissions Architecture

  • Status: Accepted
  • Date: 2026-05-09
  • Decision owners: Gina Lyn Cox · Operations Lead
  • Related: Form O · guildcommerce-core-plugin · ADR-005 (Community Platform)

Context

The Ganjier Guild ecosystem uses 22 operational forms (Forms A–V) spanning onboarding, registries, governance, communications, capital, and cross-entity service. The Operational Manual already defines a role taxonomy (Section 28A) and a role × form permissions matrix (Form O). Until now, that taxonomy and matrix existed only as prose. We needed:

  1. A machine-readable representation of the permissions that the guildcommerce-core-plugin can consume.
  2. A way to gate visibility, fill rights, and approval rights on each form by the participant's role inside the member hub (rendered by guildcommerce-theme).
  3. A platform-neutral schema so the model survives a future migration from BuddyPress to BuddyBoss Platform (see ADR-005).
  4. PDF and Markdown renderings of every form that visually communicate the audience tier (Public / Internal / Restricted) so reviewers, board members, and counsel can read the same artifact and know who sees it.

Decision

Adopt a frontmatter + manifest model for form permissions.

  1. YAML frontmatter on every forms/*.md captures: form_letter, title, category, audience (public | internal | restricted), status, visible_to, fillable_by, approver_roles, filer_role, retention_years, related_forms.
  2. .github/pdf/build-manifest.py runs in the GitHub Actions PDF build and emits dist/forms-manifest.json — a single, versioned, machine-readable manifest of every form and its permission rules.
  3. guildcommerce-core-plugin / forms sub-module fetches the manifest from a release artifact (or a checked-in copy) and uses it to gate render in the member hub.
  4. Visual audience tier is rendered both in the markdown/PDF (<span class="form-audience {public|internal|restricted}">) and in the hub UI as a colored chip mapped to the same brand palette (Honey · Mint · Lavender).
  5. Form O is the canonical role × form matrix. Frontmatter values must agree with Form O. Drift is detected in CI by a future check (check-form-o-consistency.py, deferred).

Schema (v1.0)

---
form_letter: A
title: "Ecosystem Access & Participation Form"
category: Onboarding              # Onboarding | Registries | Communications | Operations | Governance | Capital & Cross-Entity
audience: public                  # public | internal | restricted
status: final                     # final | superseded
visible_to: [guest, applicant, member, ...]
fillable_by: [guest, applicant]
approver_roles: [officer, sysadmin]
filer_role: sysadmin
retention_years: 7
related_forms: [E, O]
---

Audience tiers

Tier Visual Who sees it Examples
Public Honey badge Participants and external counterparties per role Forms A, B, D, E, I, P, Q, R, S, T, V
Internal Mint badge Officers, SysAdmin, operational leads Forms C, F, G, H, J, L, M, N, O, U
Restricted Lavender badge Officers and SysAdmin only Form K (emergency credentials)

Role taxonomy (frontmatter values)

Sourced from Section 28A and Form O. Stable slugs that map 1:1 to BuddyBoss member types:

guest, applicant, member, ganjier, navigator, operator, voyage-captain, voyage-crew, coop-participant, contractor, employee, sponsor, board, officer, sysadmin, counsel.

Plugin integration contract

The guildcommerce-core-plugin / forms sub-module:

  1. Reads forms-manifest.json at activation and on a daily cron.
  2. Registers a custom post type gc_form and writes one post per manifest entry.
  3. On every render of a gc_form post, calls bp_get_member_type( $user_id ) and filters by permissions.visible_to.
  4. Exposes a shortcode [guild-form letter="A"] and a member-profile My Forms tab.
  5. Exposes a REST endpoint GET /wp-json/guildcommerce/v1/forms that returns the same manifest filtered to the current user's permissions.
  6. PDFs are served via signed URL; the markdown is rendered inline by the guildcommerce-theme template.

Consequences

  • Single source of truth. Form O is canonical; frontmatter is the executable copy; manifest is the integration surface.
  • No third-party plugin coupling. The plugin lives inside guildcommerce-core-plugin, deployed via the existing phase3-deploy.sh flow.
  • Migration cost to BuddyBoss is small. The manifest is platform-neutral; the plugin's bp_get_member_type() call works on both BuddyPress and BuddyBoss Platform (BB ships a compatibility layer).
  • CI must keep frontmatter and Form O in sync. A future ADR-update or a check-form-o-consistency.py script will catch drift.
  • GitHub repo posture is unchanged. The blueprint repo can stay public (transparency for trade-association mission) because the hub itself enforces gates. Restricted form (K) markdown is already structural — it does not contain credentials. Credentials live only in the live record stored in Privileged-Confidential/.

Alternatives considered

  • Members + Restrict Content Pro. Works, but adds third-party plugin licensing and admin surface area. Rejected.
  • Two-repo split (public + private). Adds maintenance overhead for marginal security benefit, since restricted content (credentials) was never going to live in markdown anyway. Rejected.
  • Native BuddyPress xprofile-only gating. Insufficient — BuddyPress does not have first-class member types. Drove ADR-005 (BuddyBoss migration).

Follow-ups

  • ADR-005 — Community Platform Selection (BuddyBoss Platform).
  • Implement guildcommerce-core-plugin / forms sub-module (separate sprint).
  • Add CI consistency check (check-form-o-consistency.py).
  • Phase 2: e-signature + completion tracking on participant-facing forms.
  • Phase 3: cross-entity workflow on Form V (dual-officer signature gate).