medOS ultra

Policy Gate Override Protocol

Break-glass override-with-reason: hard_stop gates + override audit table + extended reason-code enum.

19 min read diagramsUpdated 2026-05-26docs/architecture/policy-gate-override-protocol.md

Status: Draft (2026-05-27). Major upgrade — not yet implemented. Replaces: The unbuilt override_json column on policy_gates and the deferred Phase 2 items in policy-gates.md §9. Generalizes: The custody-handoff override design in hospital-movement-architecture.md §6 so it applies to every gate type, not just mover handoffs. Companion: Tie-in to the existing OverrideReasonCode enum used by CDS (see event-contract.ts).


0. TL;DR

Today Proposed
policy_gates.action_json.type ∈ {block, warn, require_override}; require_override never implemented Override-with-reason is a first-class outcome of every blocking gate; require_override becomes block_with_override with a structured reason capture
policy_gates.override_json column exists but is unused Carries override authorization + reason whitelist + downstream consequences
No audit table for overrides New policy_gate_overrides append-only table; surfaced to compliance review queue
Each gate is silently bypassable via direct API call (frontend-only enforcement) Override is a recorded, audited, optionally-policy-driven event that downstream consumers (billing, alerts, compliance) can react to
OverrideReasonCode enum exists for CDS only Extended + reused across policy gates, CDS rules, drug-interaction conflicts, allergy-vs-order conflicts — one canonical reason taxonomy
TransferRequest’s transfer_accept gate says “unless override” but has no override data shape Override flow is the same component / hook / table everywhere
Staff bypass gates off-the-books when clinical reality forces it Override is the in-band path; the off-book path is closed

The headline change: a hard stop is the rare exception; structured override-with-reason is the default for any gate touching clinical reality.


1. Why this exists

1.1 The clinical-reality problem

Every gate this codebase ships hits the same wall eventually:

Gate Block UX today What staff actually do when blocked
Pathology specimen collection (unpaid) “กรุณาชำระเงิน…” replaces button Walk the sample to the lab without scanning, ask cashier to backfill
Transfer accept (receiving ward full) (would block) Phone the charge nurse, agree verbally, type in the bed assignment elsewhere
Specimen biohazard transport (no BSL3-certified runner) (would block) Hand the sample to whichever runner is nearest, sign for it later
Pharmacy max-dose / interaction (CDS rule fires) Inline warning Click “Confirm anyway” with no captured reason
IPD admission (no beds available) “Ward full” toast Boarder in ER, admission flag flipped after-the-fact

In every case the clinical action is correct; the software model is wrong. A hard stop that staff cannot legitimately bypass forces the bypass to happen out-of-band — and now we have a compliance violation AND a blind spot in tracking.

The right primitive is not “block” — it’s “block-by-default, but allow a recorded override with structured reason, optional authorization escalation, and downstream consequences.”

1.2 What’s already partially there

  • policy_gates.action_json.type has the require_override enum value — it was reserved for this. Never implemented.
  • policy_gates.override_json column exists. Never populated.
  • CDS has the OverrideReasonCode enum on the alert side (event-contract.ts). Not joined to gates.
  • The custody-handoff doc (hospital-movement-architecture.md §6) sketches the policy_gate_overrides audit table — but only for handoff/mover scope.

This document promotes that sketch into a generic protocol and specifies the implementation across every surface.


2. Design principles

  1. Hard-stop is per-gate operator policy, not a code decision. Defaults to override-with-reason because over-blocking → off-book moves, and we have no way to audit those.
  2. Override is structured. Reason is a typed code from a controlled vocabulary, not free-text. OTHER allowed but requires free-text supplement.
  3. Override is audited. Append-only row in policy_gate_overrides. Joinable to billing, compliance, and downstream side-effect rails.
  4. Override is sometimes authorized. Per-gate, certain overrides require a higher-privilege user (charge nurse, attending, safety officer). The gate row carries the required role/permission.
  5. Override has consequences. Downstream consumers (billing rail, alert fan-out, runner bonus calc, compliance digest, capacity-pressure metric) read the override row and adjust behavior. The gate doesn’t decide consequences — it makes them addressable.
  6. One reason taxonomy. Same OverrideReasonCode enum used by CDS, drug-interaction, allergy-vs-order, policy gates, custody handoffs.
  7. Scope is bounded. An override applies to a single triggering action, not a session. Re-triggering the same gate re-prompts.
  8. The override is reversible until consequences settle. Within a short window (per-gate config, default 5 min), the override can be retracted, which removes the row and re-blocks the action.

3. Schema changes

3.1 Migration: policy_gates column additions

ALTER TABLE policy_gates
  -- Hard stop: if true, no override allowed. Default false (override-with-reason permitted).
  ADD COLUMN IF NOT EXISTS hard_stop BOOLEAN NOT NULL DEFAULT FALSE,

  -- Reason code whitelist: if non-null, override MUST use one of these codes.
  -- If null, any code from the OverrideReasonCode enum is permitted.
  ADD COLUMN IF NOT EXISTS override_reason_codes TEXT[] DEFAULT NULL,

  -- Authorization: who can override this gate?
  -- Schema: { "roles": ["charge_nurse", "attending"], "permissions": ["override.transfer_accept"] }
  -- If null, any authenticated user can override.
  ADD COLUMN IF NOT EXISTS override_authorization JSONB DEFAULT NULL,

  -- Reversal window in seconds. Default 300 (5 min). 0 = irreversible.
  ADD COLUMN IF NOT EXISTS override_reversal_window_seconds INT NOT NULL DEFAULT 300,

  -- Alert recipients on override (notified via acknowledgement_requests).
  -- Schema: { "roles": ["safety_officer"], "user_ids": [...], "department_ids": [...] }
  ADD COLUMN IF NOT EXISTS override_alert_recipients JSONB DEFAULT NULL,

  -- Downstream consequence hints (read by billing rail, runner-bonus calc, etc.)
  -- Schema: { "billing_flag": "retro_collection", "suppress_runner_bonus": true,
  --           "capacity_pressure_metric": "ward_overcap" }
  ADD COLUMN IF NOT EXISTS override_consequences JSONB DEFAULT NULL;

Note: the existing override_json column is retired and replaced by the four scalar/JSONB columns above. Any rows that populated override_json (currently zero in practice) get migrated by a one-shot script in the same migration file.

3.2 Migration: new table policy_gate_overrides

CREATE TABLE IF NOT EXISTS policy_gate_overrides (
  id                       UUID PRIMARY KEY DEFAULT gen_random_uuid(),

  -- Which gate fired
  gate_id                  UUID NOT NULL REFERENCES policy_gates(id),
  gate_trigger             TEXT NOT NULL,              -- denormalized for fast query
  gate_name                TEXT NOT NULL,              -- denormalized for compliance UI

  -- What was being attempted
  subject_resource         TEXT NOT NULL,              -- 'TransferRequest' | 'Task' | 'LabRequest' | 'MedicationRequest' | ...
  subject_id               TEXT NOT NULL,              -- the entity overridden against
  triggering_action        TEXT NOT NULL,              -- e.g. 'collect_specimen', 'accept_transfer'

  -- Patient context (nullable for facility-level gates)
  encounter_id             UUID,
  patient_id               UUID,

  -- Who overrode
  override_by              UUID NOT NULL,
  override_by_role         TEXT NOT NULL,              -- snapshot of role at time of override
  override_authorized_by   UUID,                       -- if escalated authorization, who approved
  override_authorized_role TEXT,

  -- Why
  override_reason_code     TEXT NOT NULL,              -- from OverrideReasonCode enum
  override_reason_text     TEXT,                       -- mandatory if code == 'OTHER'

  -- Context
  gate_eval_inputs         JSONB NOT NULL DEFAULT '{}',-- the context that was evaluated (for replay/audit)
  gate_eval_result         JSONB NOT NULL DEFAULT '{}',-- which predicate failed, severity, etc.
  override_metadata        JSONB NOT NULL DEFAULT '{}',-- gate-specific extra context

  -- Lifecycle
  occurred_at              TIMESTAMPTZ NOT NULL DEFAULT now(),
  reversed_at              TIMESTAMPTZ,                -- non-null if retracted within reversal window
  reversed_by              UUID,
  reversed_reason          TEXT,

  -- Downstream sync
  alert_dispatched_at      TIMESTAMPTZ,                -- when override_alert_recipients were notified
  compliance_reviewed_at   TIMESTAMPTZ,                -- when compliance closed the case
  compliance_reviewed_by   UUID,
  compliance_disposition   TEXT,                      -- 'acceptable' | 'flagged' | 'training_needed' | 'sanction'
  compliance_notes         TEXT
);

CREATE INDEX idx_pgo_gate_trigger   ON policy_gate_overrides (gate_trigger, occurred_at DESC);
CREATE INDEX idx_pgo_override_by    ON policy_gate_overrides (override_by, occurred_at DESC);
CREATE INDEX idx_pgo_patient        ON policy_gate_overrides (patient_id) WHERE patient_id IS NOT NULL;
CREATE INDEX idx_pgo_subject        ON policy_gate_overrides (subject_resource, subject_id);
CREATE INDEX idx_pgo_pending_review ON policy_gate_overrides (occurred_at DESC)
  WHERE compliance_reviewed_at IS NULL;
CREATE INDEX idx_pgo_unreversed     ON policy_gate_overrides (gate_id, occurred_at DESC)
  WHERE reversed_at IS NULL;

ALTER PUBLICATION supabase_realtime ADD TABLE policy_gate_overrides;

3.3 Action JSON extension

Existing action_json.type values stay the same name-wise but with sharper semantics:

type value Meaning Override behavior
block Block this action. If policy_gates.hard_stop = true, no override. Otherwise default override-with-reason. Determined by hard_stop column, not action type
warn Show warning; do not block. N/A — no override needed
require_override Block AND require explicit override even if the predicate passes. Used for “always confirm before doing X” gates (e.g. ordering controlled substance, ESI 1 actions). Always requires override

This is a small change: block + hard_stop=true is “true hard stop”; block + hard_stop=false is “override-with-reason.” require_override becomes “always-prompt-even-on-pass” rather than the unbuilt third state.

3.4 Reason code enum extension

Existing in event-contract.ts:

export type OverrideReasonCode =
  | 'CLINICALLY_JUSTIFIED'
  | 'BENEFITS_OUTWEIGH_RISKS'
  | 'MONITORING_IN_PLACE'
  | 'SENIOR_APPROVED'
  | 'OTHER';

Extended to:

export type OverrideReasonCode =
  // Existing (CDS / clinical)
  | 'CLINICALLY_JUSTIFIED'
  | 'BENEFITS_OUTWEIGH_RISKS'
  | 'MONITORING_IN_PLACE'
  | 'SENIOR_APPROVED'

  // Handoff / logistics
  | 'TIME_CRITICAL'
  | 'NO_QUALIFIED_MOVER_AVAILABLE'
  | 'PATIENT_REFUSED_WAIT'
  | 'EMERGENCY_OVERRIDE'

  // Capacity / operations
  | 'NO_ALTERNATIVE_AVAILABLE'        // ward full but patient needs admission
  | 'BOARDING_AUTHORIZED'              // ER boarder pending bed
  | 'CAPACITY_OVERRIDE_APPROVED'       // bed-manager OK
  | 'PHYSICIAN_DIRECTED'               // attending wrote the order despite the warning

  // Financial / payor
  | 'EMERGENCY_BYPASS_PAYMENT'         // ER unpaid sample collection
  | 'INSURANCE_AUTHORIZED'             // preauth verbally confirmed, paperwork pending
  | 'DEPOSIT_WAIVED'                   // VIP / staff / charity
  | 'RETRO_BILLING_ACCEPTED'           // proceed now, bill later

  // System / data
  | 'SYSTEM_ERROR_BYPASS'              // gate eval inputs unreliable
  | 'DATA_CORRECTION_PENDING'          // chart fix in progress
  | 'DUPLICATE_ENTRY_AVOIDANCE'        // override to prevent double-recording

  // Catch-all
  | 'OTHER';                            // free-text required

Reason codes are grouped by domain in the picker UI so the override modal only shows relevant ones based on policy_gates.override_reason_codes whitelist (or the gate’s category field, if added later).


4. Runtime evaluation extension

4.1 evaluateGates() return shape

Today:

{ blocked: boolean, message: string, severity: string, matchedGate: PolicyGate }

After:

{
  blocked: boolean,
  message: string,
  severity: 'error' | 'warning' | 'info',
  matchedGate: PolicyGate,

  // NEW
  hardStop: boolean,                       // matched gate's hard_stop flag
  overrideAllowed: boolean,                // !hardStop && blocked
  overrideReasonCodes: OverrideReasonCode[], // whitelist for this gate, or full enum if null
  overrideAuthorization: {                 // null if any user can override
    roles?: string[];
    permissions?: string[];
  } | null,
  alertRecipientsOnOverride: {             // for downstream consequences preview
    roles?: string[];
    user_ids?: string[];
    department_ids?: string[];
  } | null,
}

4.2 recordOverride() action

New service action in web/src/services/policy-gate.service.ts:

async recordOverride(input: {
  gateId: string;
  gateTrigger: string;
  subjectResource: string;
  subjectId: string;
  triggeringAction: string;
  encounterId?: string;
  patientId?: string;
  reasonCode: OverrideReasonCode;
  reasonText?: string;
  evalInputs: object;
  evalResult: object;
  metadata?: object;
}): Promise<PolicyGateOverride> {
  // 1. Authorization check (caller must hold the required role/permission)
  // 2. Whitelist check (reasonCode must be in override_reason_codes if set)
  // 3. If reasonCode === 'OTHER', reasonText is required
  // 4. INSERT policy_gate_overrides row
  // 5. Fire acknowledgement_requests to alert recipients if configured
  // 6. Return the row (UI shows confirmation, attaches row id to the subject)
}

This action is the only path to recording an override. The frontend never bypasses gates silently.

4.3 Reversal

async reverseOverride(overrideId: string, reason: string): Promise<void> {
  // Within reversal_window_seconds from occurred_at: mark reversed_at, reversed_by, reversed_reason.
  // Past the window: requires admin permission + audit trail.
  // Reversing an override should also undo downstream side effects where possible
  // (cancel the dispatched alert, retract the billing flag, etc.) — handled by
  // a reversal-event consumer per gate's override_consequences config.
}

5. Frontend UX

5.1 The override modal — one component, all gates

New shared component: web/src/common/components/policy-gate/OverrideReasonModal.tsx

<OverrideReasonModal
  gate={matchedGate}
  evalResult={policyResult}
  subjectResource="TransferRequest"
  subjectId={req.id}
  triggeringAction="accept_transfer"
  onConfirm={(override) => { /* row recorded, proceed with action */ }}
  onCancel={() => { /* close, action stays blocked */ }}
/>

Internal flow:

  1. Shows the gate name + matched predicate explanation (“Receiving ward at capacity: ICU-2 has 0 beds free”)
  2. Reason code dropdown — filtered by gate.override_reason_codes if set
  3. If OTHER selected → required free-text field
  4. If gate.override_authorization is set → either:
    • Current user has the role/permission → proceed
    • Current user does not → escalation flow (“Request charge nurse approval”)
  5. Preview block: “This will alert: Safety Officer, ICU charge nurse. The override will appear on the daily compliance digest.”
  6. Confirm button calls recordOverride() then closes; UI proceeds with the action

5.2 Hook wrapper

Existing usePolicyGate(ctx) returns the new result shape. New companion:

const { result, gates, recordOverride } = usePolicyGate(ctx);

const handleConfirm = async () => {
  if (result.blocked && result.overrideAllowed) {
    setShowOverrideModal(true);
    return;
  }
  if (result.blocked) {
    // hard stop — UI shows the block message, no override path
    return;
  }
  await proceed();
};

Every existing dialog that consumes usePolicyGate keeps working without changes; the override path is opt-in per dialog.

5.3 Override badge on the subject

After an override fires, the subject (TransferRequest, LabRequest, etc.) carries a visible “Overridden” badge in the worklist + detail view. Click → opens the override row with full audit (who, when, why, downstream alerts dispatched).

5.4 Compliance review surface

New admin page: /admin/policy-gate-overrides

  • List of policy_gate_overrides with compliance_reviewed_at IS NULL
  • Filter by gate, date range, override_by user, reason code
  • Row actions: Mark Reviewed → acceptable | flagged | training_needed | sanction + notes
  • Daily/shift digest export for compliance officer
  • Realtime updates so multi-officer review doesn’t double-handle

6. Application surfaces — where this gets wired

6.1 TransferRequest

Current gates in transfer-request-system.md §10:

Gate Current spec After upgrade
transfer_request_create Blocks low-priv users from STAT transfers hard_stop=false, codes: CLINICALLY_JUSTIFIED, EMERGENCY_OVERRIDE. Authorization: charge_nurse.
transfer_accept Blocks if receiving ward at capacity (unless override) hard_stop=false, codes: NO_ALTERNATIVE_AVAILABLE, CAPACITY_OVERRIDE_APPROVED, BOARDING_AUTHORIZED. Alert recipients: bed_manager, receiving charge nurse. Consequences: capacity_pressure_metric: ward_overcap.
transfer_late_cancel Requires admin override + reason hard_stop=false, codes: DATA_CORRECTION_PENDING, PHYSICIAN_DIRECTED, OTHER. Authorization: admin.

6.2 Custody handoff (per hospital-movement §6)

Already designed there. This doc absorbs that design into the general protocol — no new spec needed beyond the schema unification.

Gate (example) Notes
Mover certification mismatch (BSL3, radioactive) hard_stop=false, codes: TIME_CRITICAL, NO_QUALIFIED_MOVER_AVAILABLE. Alert: safety_officer. Consequence: suppress_runner_bonus: true.
Mortuary handoff without dual identity verification hard_stop=true per operator policy in some jurisdictions

6.3 CDS rules bridge

CDS rules already use OverrideReasonCode. Currently they fire acknowledgement_requests and accept dismissal with a reason — but the reason is not joined to any audit table that compliance reviews systematically.

The upgrade: when a CDS rule dismissal includes a reason code, also write a policy_gate_overrides row with:

  • gate_id = synthetic CDS gate id (one per CDS rule, materialized in policy_gates)
  • gate_trigger = 'cds_alert_dismiss'
  • subject_resource = the resource the CDS was evaluating (MedicationRequest, LabRequest, etc.)
  • override_reason_code = the dismissal reason

This unifies CDS dismissals into the same compliance review surface as policy-gate overrides. CDS rule schema doesn’t change — only the dismissal handler gains a write-through.

6.4 Specimen biohazard (future)

When the specimen biohazard gate ships (currently TODO per specimen-transport-bounded-context.md), it slots into this protocol directly. No additional design needed.

6.5 Payment gates (existing pathology)

The five wired dialogs in policy-gates.md §5 get an upgrade: when payment-gate fires and the encounter is ER, the override modal appears with the EMERGENCY_BYPASS_PAYMENT code preselected. Cashier supervisor reviews the daily digest, escalates if abuse.

6.6 Pharmacy max-dose / interaction

Today these go through CDS (no separate policy gate). After the §6.3 bridge ships, dismissals are audited identically.

6.7 IPD admission / bed assignment

Today: no gate. After this upgrade, a ward_capacity gate can be configured with hard_stop=false + BOARDING_AUTHORIZED reason code, giving bed managers visibility into how often capacity is being overridden.


7. Downstream consequences — the powerful part

policy_gates.override_consequences is the lever that lets an override change downstream behavior without each consumer hardcoding gate-specific logic.

Example consequences:

Consequence field Consumer Effect
billing_flag: 'retro_collection' Financial service Encounter flagged in cashier’s “outstanding to backfill” queue
suppress_runner_bonus: true Custody-handoff bonus calc No bonus paid on this handoff
capacity_pressure_metric: 'ward_overcap' Operations dashboard Increment ward-overcap counter for shift report
alert_safety_officer: true Already handled by override_alert_recipients, but consequence flag lets billing rail also react (e.g., insurance non-coverage for off-protocol care)
requires_24h_followup_review: true Compliance scheduler Auto-creates a follow-up review item 24h later
escalate_to_qa: true Quality assurance Override appears in monthly QA case review

Consequences are declared on the gate, not in the consumer. Adding a new consequence means:

  1. Add the key to the consequence schema
  2. Add a consumer that reads it from the override row
  3. Configure the gate to emit it

No gate-specific code in the consumer.


8. Phase plan

Phase Scope Effort
P1: Schema + reason enum Migration: policy_gates column additions + policy_gate_overrides table + realtime + RLS; OverrideReasonCode enum extension in event-contract.ts + platform-api-schema ~250 LOC SQL/TS
P2: Service layer evaluateGates() return shape extension; recordOverride() + reverseOverride() service actions; React Query mutations ~300 LOC
P3: Shared override modal + hook OverrideReasonModal.tsx + usePolicyGate() updated return; preview block, authorization escalation UX ~400 LOC
P4: Wire TransferRequest gates All three transfer gates configured + override flow on accept/cancel dialogs ~200 LOC
P5: Wire pathology payment gates Update the five wired dialogs to surface override modal in ER/VIP context ~250 LOC
P6: CDS bridge Synthetic CDS gates materialized into policy_gates; dismissal-handler write-through to policy_gate_overrides ~300 LOC
P7: Compliance review surface /admin/policy-gate-overrides page with filter/review/disposition workflow + daily digest export ~500 LOC
P8: Downstream consequences wiring Billing-flag reader, runner-bonus suppressor, capacity-pressure metric, QA escalation hooks ~400 LOC
P9: Custody-handoff gates (depends on hospital-movement P1) Mover-cert gates configured into this protocol; handoff dispatch UI integrates override modal ~300 LOC
P10: Cross-region seeds Per-market-pack override reason codes (some codes are region-specific — e.g. KAIGO_AUTHORIZED for Japan); per-region hard_stop defaults ~150 LOC SQL

Total estimated: ~3,050 LOC across ~40 files in 12 directories.

Dependency graph:

P1 ──► P2 ──► P3 ──┬──► P4 ──► P5
                   ├──► P6 ──► P7
                   ├──► P8
                   └──► P9 (also depends on hospital-movement protocol §6 land)
P1 ──────────────────────► P10

P3 can ship before any wiring — just the modal + hook ready for reuse. P4 + P5 are the visible demo surface. P6 is the highest-leverage compliance unification.


9. Invariants

  1. Hard-stop is the exception, not the default. Gate authors must explicitly opt into hard_stop=true with a documented reason.
  2. No silent bypass. Frontend dialogs that consume usePolicyGate MUST route through the override modal when blocked && overrideAllowed. Direct API calls that skip the gate are eventually blocked by backend duplicate enforcement (Phase 2 of original policy-gates.md).
  3. Reason code is mandatory and structured. Free-text alone is never accepted.
  4. Override is auditable forever. policy_gate_overrides is append-only. Reversal sets a column, doesn’t delete.
  5. Override authorization is checked at record time. Frontend MAY hide the override path from unauthorized users, but the backend MUST re-check.
  6. Consequences are declared on the gate, consumed by domain services. No consumer hardcodes gate-id checks.
  7. CDS dismissals and policy-gate overrides share one taxonomy. The reason code enum is single-source.
  8. Reversal closes the loop. If an override is reversed within window, downstream consequences must be undoable (or the gate must declare them irreversible and disallow reversal).

10. Open questions

  1. Per-gate vs per-tenant hard-stop policy? Today the spec is per-gate row. A tenant might want to globally promote all medication.* gates to hard-stop. Add a policy_gate_groups table for batch policy, or rely on per-gate config? Recommend per-gate until tenants ask.
  2. Override expiry? If a charge nurse overrides the ward-capacity gate at 8am, does the gate re-fire at 10am for the next admission attempt? Default: yes (each triggering action gets its own evaluation). Some shops may want session-scoped overrides — defer until requested.
  3. CDS bridge — gate materialization on every rule edit? When admins edit a CDS rule, do we synthetically maintain a matching policy_gates row? Or query CDS rules virtually at override-record time? Materialization is simpler; virtual is more consistent. Recommend materialization with a sync trigger.
  4. Authorization escalation — synchronous or async? Today’s spec assumes the higher-privilege user is physically present (“ask charge nurse to scan their badge”). For escalation that requires reaching someone remotely, we need an async approval flow — that’s a bigger feature, probably out of scope for the first cut.
  5. Compliance review SLA? What’s the expected turnaround on compliance_reviewed_at? 24h for most, 1h for EMERGENCY_OVERRIDE-coded entries? Configurable per gate?
  6. Cross-region reason codes. Japan’s kaigo flows may need codes that don’t exist in Thailand. How are region-specific codes added without forking the enum? Recommend a tier 2 enum (OverrideReasonCodeExtended) that market packs can extend, with the base enum frozen at the codes in §3.4.
  7. Overrides on behalf-of-machine. If an HL7v2 inbound feed creates a transfer that would have tripped a gate, who’s the override actor? A system service account, or do we hold the message in a triage queue for a human to override? Defer until HL7v2 ADT^A02 ingest is built.
  8. What happens to existing CDS overrides on day 1? Backfill policy_gate_overrides from historical CDS dismissals, or start fresh? Recommend start fresh; historical CDS dismissals stay in their original log.

11. Migration sequencing notes

  1. P1 ships standalone. Schema additions are backward compatible — no existing code breaks.
  2. P2 + P3 ship together. The hook + modal + service action form one usable unit.
  3. P4-P9 can ship in any order depending on which gate domain has highest demand.
  4. P7 (compliance UI) can ship at any point after P1 — it just queries the override table; doesn’t need any of the other phases.
  5. P10 (cross-region) is non-blocking — happens per market-pack adoption.

The frontend-only enforcement caveat from policy-gates.md §9 still applies: a direct API call to backend services bypasses the gate UI entirely. Backend enforcement is Phase 2 of original policy-gates.md and is separate from this protocol. When it ships, the override flow will need to be re-implementable backend-side (the recordOverride() action and reason taxonomy already live in shared schema, so this is mostly a matter of adding the Moleculer/NestJS service-layer hook).


12. References

Ask Anything