Skip to Content
EngineeringDecisions (ADRs)ADR 0003 Program scope and the permission catalog

ADR 0003: Program as the tenant scope and the 72-permission catalog

Status
Accepted
Date
Deciders
Founder, product team

Context

The reference profile scopes every record to an organisation and a generic “space”. Trovensa’s brief describes teams working inside one development program at a time, with milestones, dependencies and approvals per program. Calling the scope “space” would put two words on one idea, and calling the fourth product “Programs” while the scope is a program would collide. The scaffolded gate web/scripts/check-permissions-sync.mjs parsed _org( / _space( helpers and the export SPACE_PERMISSIONS (lines 27 and 35 before the patch); api/tests/route_matrix.py carried SPACE_PREFIX = "/spaces/{space_id}".

Decision

  • The tenant scope is Organization -> Program. Scope kinds are org and program; program routes live under /api/v1/programs/{program_id}/...; the web tree is /p/[slug]/...; hooks are useProgram().can().
  • The fourth product is named Progress (ADR 0002).
  • The permission catalog is pinned at 72 strings: org 13, program 13, evidence 13, plans 14, review 10, progress 9 (PRODUCT-CONTRACT.md section 3 lists them in order). Verbs come from the closed set read create update delete manage approve link sync use read_own manage_owners.
  • Ten personas: org_owner, org_admin, org_member, org_auditor, program_owner, program_lead, scientist, quality_reviewer, manufacturing_specialist, viewer, expanded from patterns at import time and seeded by migration 0001.
  • The workspace copy of the permissions sync gate is patched to _(org|program)( and PROGRAM_PERMISSIONS; route_matrix.py uses PROGRAM_PREFIX. The plugin templates are not changed.

Consequences

  • Every scoped table carries organization_id, program_id and stage_id; every scoped query filters by them.
  • The web mirror, the docs permissions page and the API catalog must agree on 72 keys in the same order; check-permissions-sync fails otherwise.
  • Adding a permission is a contract change: bump the count in the API, the web mirror and the docs page in one commit.
  • Approval routes are separate from transition routes so each route has exactly one permission (.manage for draft/review/archived transitions, .approve for approvals).

Alternatives considered

  • Keep “space” and put program records inside a space: gate-compatible without a patch, but two words for one idea in the UI; rejected.
  • Scope = program and keep product “Programs”: acceptable collision but confusing navigation (“Programs” inside a program); rejected.
  • Fewer permissions (one manage per product): simpler catalog but no way to separate approvers from authors, which the brief requires; rejected.

Follow-ups

  • Add scripts/check-permissions-sync.test.mjs in the web repo covering the patched parser (web writer).
  • Record the measured route count (117 designed) in the claims register on publish day.