Skip to content

Gate the catalog actions with requiredPermissions — duly_catalog_apply is currently ungated #30

Description

@os-warren

Sub-issue of #8, which is where the permission sets these need to name get created.

Found while implementing #5. Not fixed there: naming a capability that no permission set grants would either make the action uninvokable or leave a gate that grants nothing — and src/security/index.ts is #8's file surface, not #5's.

What is ungated

Both actions added in #5 declare no requiredPermissions:

  • duly_catalog_apply — creates a duly_duty for any sys_user id the caller passes, in bulk. A 26-item catalog against 40 people is 1,040 rows in one call.
  • duly_catalog_sync — rewrites cadence (frequency, due_anchor, due_offset_days, lead_days, grace_days) on every catalog-sourced duty in the org when position_code is omitted.

requiredPermissions is the real gate (ADR-0066 D4, enforced with a 403 on the platform action route and mirrored on the MCP bridge). visible/disabled are UI predicates and do not stop a request; neither action declares those either, and they would not help if it did.

Note this is not mitigated by object-level permissions: ctx.engine is the trusted facade — context-less and RLS/FLS-bypassing by design (#2849). The boundary for an action handler is the invoke-time capability gate, so with none declared there is nothing between a caller and a bulk write.

What to add

#8 already defines duly_admin as "catalog owners and rollout admins" — the right holder for both. Once that permission set exists:

// src/actions/catalog.actions.ts
requiredPermissions: ['<the duly_admin capability from #8>'],

on both CatalogApplyAction and CatalogSyncAction.

Worth considering as part of the same pass: whether duly_catalog_sync should require a narrower capability than apply. Applying a catalog to a new hire is routine onboarding; syncing org-wide rewrites authored cadence on duties people are already working to, and #5's PR notes it is deliberately reportable-after-the-fact for that reason.

Acceptance

  • both actions declare requiredPermissions naming a capability duly_admin actually grants
  • a test asserts the declaration (the same shape as the wiring assertions in test/catalog-instantiate.test.ts)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions