Repository navigation
ADR-0056 D6: activate declarative roles + sharingRules at runtime (enforce-or-remove) — unblocks role_and_subordinates showcase demo #2077
Description
Background
ADR-0056 D6 added the role_and_subordinates sharing-rule recipient (configurable role-hierarchy widening), implemented in @objectstack/plugin-sharing (role-graph.ts RoleGraphService + sharing-rule-service.ts expandRecipient) and covered by the plugin's own unit tests.
While trying to demonstrate D6 in the showcase (as part of #2076), I found a deeper, pre-existing architectural gap that blocks it from being shown end-to-end.
The gap
Stack-declared roles and sharingRules are never seeded into sys_role / sys_sharing_rule at runtime — neither by the verify harness (bootStack) nor by the real pnpm dev CLI (packages/cli/src/commands/serve.ts).
Evidence:
- Booting the showcase via
bootStackyieldssys_rolecount = 0 andsys_sharing_rulecount = 0, even though the showcase declares 3 roles + 2 sharing rules. SharingServicePlugin.start()binds rule hooks fromruleService.listRules(), which reads thesys_sharing_ruletable — but nothing populates that table from app metadata. Boot logs show[sharing-rule] hooks bound {"objects":[],"ruleCount":0}.- A repo-wide search finds no code path that inserts stack-declared
roles/sharingRulesintosys_role/sys_sharing_rule(only the runtimecreateRuleAPI atsharing-rule-service.ts:138writes the table).
Consequence: the showcase's existing declarative roles + sharing rules (and any app's, e.g. examples/app-crm) are decorative — Studio/authoring metadata that never enforces at runtime. This is a classic enforce-or-remove (ADR-0049) situation.
Note: this is distinct from permission sets, which do resolve at runtime via
metadataService.list('permission')/sys_permission_set(permission-evaluator.ts resolvePermissionSets). D7's app-declared default profile was wired through the CLI in #2076 precisely because permission sets resolve; roles + sharing rules do not.
Proposed work
Seed (idempotent upsert) stack-declared roles → sys_role and sharingRules → sys_sharing_rule at boot, so the existing sharing-rule evaluator (rule-hooks.ts afterInsert/afterUpdate) activates them.
- Likely homes:
SecurityPluginfor roles (sys_role),SharingServicePluginfor sharing rules (sys_sharing_rule), reading frommetadataService.list('role' / 'sharingRule'). - Must be idempotent (re-seed on each boot without duplicating), and reconcile updates/removals.
- Decide org-scoping behavior (per-tenant seeding under
plugin-org-scoping).
⚠️ Blast radius — needs its own ADR + careful review
This changes enforcement behavior for every app that declares roles/sharing rules (examples/app-showcase and examples/app-crm today). Activating previously-inert rules can change who-can-see-what. Requires:
- A full dogfood-suite run across all example apps (currently green precisely because rules are inert).
- An ADR documenting the enforce-or-remove decision and migration/rollout posture.
Acceptance criteria
- ADR for "activate declarative roles + sharing rules at runtime".
- Stack-declared roles + sharing rules seeded idempotently into
sys_role/sys_sharing_ruleat boot (verify harness + CLI). - Full dogfood suite green across all example apps; any behavior changes intentional + documented.
- Showcase D6 demo: add a
role_and_subordinatessharing rule + a conventional role hierarchy (exec → manager → contributor), with an end-to-end dogfood proof that a manager + their subordinates gain access via the widening.
References
- ADR-0056 (permission model landing verification); D6 section.
- Merged in feat(app-showcase): ADR-0056 Option A public form + D7 default-profile CLI wiring #2076: Option A public form + D7 default-profile CLI wiring (D6 explicitly deferred here).
- Code:
packages/plugins/plugin-sharing/src/{role-graph,sharing-rule-service,rule-hooks,sharing-plugin}.ts,packages/cli/src/commands/serve.ts,packages/plugins/plugin-security/src/security-plugin.ts.
Activity
chore(changeset): ADR-0057 ERP authorization core (#2077)