Skip to content

ADR-0056 D6: activate declarative roles + sharingRules at runtime (enforce-or-remove) — unblocks role_and_subordinates showcase demo #2077

Description

@os-zhuang

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 bootStack yields sys_role count = 0 and sys_sharing_rule count = 0, even though the showcase declares 3 roles + 2 sharing rules.
  • SharingServicePlugin.start() binds rule hooks from ruleService.listRules(), which reads the sys_sharing_rule table — 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/sharingRules into sys_role/sys_sharing_rule (only the runtime createRule API at sharing-rule-service.ts:138 writes 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 rolessys_role and sharingRulessys_sharing_rule at boot, so the existing sharing-rule evaluator (rule-hooks.ts afterInsert/afterUpdate) activates them.

  • Likely homes: SecurityPlugin for roles (sys_role), SharingServicePlugin for sharing rules (sys_sharing_rule), reading from metadataService.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_rule at boot (verify harness + CLI).
  • Full dogfood suite green across all example apps; any behavior changes intentional + documented.
  • Showcase D6 demo: add a role_and_subordinates sharing 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions