---
id: WF-PFM-OPS-010
title: Make or withdraw a staff licence override
product_area: platform
module: operations
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
inference: []
trigger: Authorized staff create or withdraw an override from the HQ workspace licensing panel.
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-PFM-OPS-011
  - CAP-PFM-OPS-013
  - CAP-PFM-OPS-015
moves:
  - STATE-PFM-OPS-004
  - STATE-PFM-OPS-006
crosses:
  - SVC-PFM-OPS-licensing
idempotency: >-
  Each creation makes a separate decision with no request deduplication. Withdrawal updates only an unrevoked
  decision; later repeats cause conflict and no refresh.
concurrency: >-
  Simultaneous creates retain independent decisions. Two grants can both see a missing operational record
  before preparation, so row uniqueness does not prevent duplicate participant work.
hops:
  - 'n': 1
    actor_or_service: Rhythm HQ licensing section
    action: >-
      Collect effect, the console’s twenty-character reason and required customer-grant end; collect an
      additional withdrawal reason that the route ignores.
    anchor: 'react-console:components/console/hq/tenant-licensing-card.tsx'
    seam: sync
    on_failure: Keep the dialog open while required input is invalid.
    left_inconsistent: No server decision has been made by form validation.
    observer: Dialog is the observation surface.
    provenance: test
    source: 'react-console:e2e/mock/application-access.mock.spec.ts#asks for a reason long enough to be one'
  - 'n': 2
    actor_or_service: Licensing API
    action: >-
      Require staff licensing management, validate the target and period, and commit the decision or
      withdrawal.
    anchor: 'services-platform:modules/licensing/services/persistence/licence-overrides.ts:createLicenceOverride'
    seam: sync
    on_failure: Refuse invalid input or state without a committed decision write.
    left_inconsistent: No partial decision commit results from validation refusal.
    observer: Response is the observation surface.
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/e2e/licensing.e2e.spec.js#leaves the tenant with no override
      after that refusal
  - 'n': 3
    actor_or_service: Licensing service
    action: Attempt the security-history entry with original decision reason and widening direction.
    anchor: 'services-platform:modules/licensing/services/enablement/grant-licence-override.ts:grantLicenceOverride'
    seam: sync
    on_failure: Return an error if history persistence fails after the decision commit.
    left_inconsistent: 'The decision can remain unaudited, with preparation and refresh skipped.'
    observer: Request error and committed decision visible to staff is the observation surface.
    provenance: observed
    source: 'services-platform:modules/licensing/services/enablement/grant-licence-override.ts:grantLicenceOverride'
  - 'n': 4
    actor_or_service: Licensing service
    action: 'For a grant with no operational row, run preparation and record its outcome in security history.'
    anchor: 'services-platform:modules/licensing/services/enablement/grant-licence-override.ts:grantLicenceOverride'
    seam: sync
    on_failure: Retain participant failure as failed preparation; propagate persistence or history errors.
    left_inconsistent: 'The grant can stand while preparation is failed, stranded or missing its history entry.'
    observer: Staff panel’s operation state is the observation surface.
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/integration/services/enablement/provision-tenant-application.integration.spec.js#leaves
      the operational row exactly as it was, because the licence does not decide the state
  - 'n': 5
    actor_or_service: Licensing service
    action: Request Security’s cache refresh after the preceding follow-up work succeeds.
    anchor: >-
      services-platform:modules/security/services/authorizer-flush/submit-flush-job.ts:submitAuthorizerFlushJob
    seam: state_machine
    on_failure: Contain refresh submission errors and rely on cache expiry.
    left_inconsistent: Cached access can reflect the earlier decision until expiry.
    observer: Refresh job’s record; the log is the observation surface.
    provenance: test
    source: >-
      services-platform:modules/licensing/tests/e2e/licensing.e2e.spec.js#asks for none when the second
      revocation of the same override is refused, because nothing changed
branches:
  - condition: Withholding or withdrawing
    path: Withholding or withdrawal preserves the workspace’s operational records and application data.
waits:
  - what: Access refresh
    how_long: 'until the refresh job runs, or at most the authorizer cache lifetime plus the signed context’s life'
evidence:
  - >-
    services-platform at ad3f6a6: modules/licensing (aws/api/licensing.routes.ts,
    services/enablement/grant-licence-override.ts, services/persistence/licence-overrides.ts);
    modules/security/services/authorizer-flush/submit-flush-job.ts; docs/licensing.md §3, §5, §9.
  - >-
    react-console at baf6dae: components/console/hq/tenant-licensing-card.tsx, licensing-dialogs.tsx;
    lib/hq/licensing.ts.
  - >-
    codex examination 2026-10-23: checked the pinned source anchors, planning references and enforcement
    boundaries; unresolved limits are recorded in module findings.
controls: [TC-AVL-001, TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CHG-005, TC-CHG-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-DAT-005, TC-DAT-006, TC-IAM-001, TC-IAM-002, TC-IAM-003, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-003, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Make or withdraw a staff licence override

## What It Does

Staff exceptions supplement or withhold contract-based access. A new grant can also prepare an application the workspace has never held. The committed exception is distinct from its later preparation, security-history insertion and refresh request.

## The Flow

```mermaid
flowchart TD
  H0["Collect effect, the console’s twenty-character reason and required customer-grant end; collect an additional withdrawal reason that the route ignores."]
  H1["Require staff licensing management, validate the target and period, and commit the decision or withdrawal."]
  H0 --> H1
  H2["Attempt the security-history entry with original decision reason and widening direction."]
  H1 --> H2
  H3["For a grant with no operational row, run preparation and record its outcome in security history."]
  H2 --> H3
  H4["Request Security’s cache refresh after the preceding follow-up work succeeds."]
  H3 --> H4
```

## When Something Fails

History failure reports an error after the override is committed and prevents subsequent preparation and refresh. Participant failure records failed preparation while preserving the grant, whereas database and history exceptions can interrupt the remaining path. Cache expiry bounds an omitted refresh. The console’s longer reason requirement and discarded withdrawal reason remain integration differences.

## Open Design Questions

The existing question asks whether decisions and security-history insertion should share a transaction or gain durable follow-up recovery.
