# CEM CRM Cutover Interlock

Phase 9C0.1 provides local mutual exclusion for controlled per-action migration. Phase 9C2.1 adds the runtime health proof required before legacy suppression. The legacy listener remains authoritative unless a valid, unexpired snapshot explicitly transfers an action scope with the interlock enabled and a fresh matching ready health lease exists. Related architecture: MPBX-002, MPBX-004, MPBX-006 and MPBX-CONF-001.

## Shared snapshot

The local JSON state contains:

- schema version;
- random generation identity;
- generation and expiry timestamps;
- global safe default;
- reviewed tenant/environment runtime scope used when the legacy config has no such fields;
- cutover interlock state;
- tenant/environment/action rules with optional queue and extension scope;
- SHA-256 integrity checksum.

It contains no caller identity, AMI identifier or payload, CRM record, credential, token or recording path. The writer uses a restrictive temporary file, flushes and fsyncs where available, then atomically renames it over the destination.

Generate it only from reviewed local configuration:

```text
php tools/cem_crm_consumer.php --config <local-config> --export-authority-state <runtime-path>
```

Export validates the complete policy, rejects a global CEM default, rejects malformed or conflicting rules, and does not change the configured interlock. The sample defaults to `LEGACY`, an empty ruleset, a false interlock and a five-minute validity period.

## Asymmetric failure behavior

For missing, unreadable, malformed, unsupported, expired, checksum-failed or conflicting state:

- legacy resolves ownership as `LEGACY` and preserves its established effect;
- CEM resolves authority as `BLOCKED` and executes nothing.

This asymmetry prevents a control-file failure from removing the current production behavior or enabling a new duplicate effect. The file is re-read at each authority decision, so a valid atomic replacement or removal takes effect without listener restart.

`SHADOW` retains legacy ownership while CEM only plans. `BLOCKED` explicitly suppresses both owners and does not require a health lease. A `CEM` nomination without the snapshot interlock, or without a matching ready lease, retains legacy ownership.

## Generation-bound health proof

The health lease is a separate atomically replaced local artifact. Its default validity is 15 seconds and the follow consumer refreshes it every five seconds. Its generation, tenant and environment must match the authority snapshot exactly. Every readiness flag must be true; `checkpoint_ready` also means the committed checkpoint has reached the last complete journal record. Missing, stale, malformed, checksum-invalid, permission-unsafe or mismatched health state causes the listener to retain the effect. CEM continues to enforce its own independent execution gates; the lease is not an execution permit.

The consumer writes no lease during dry-run, CRM preflight or authority export. On controlled shutdown it publishes a non-ready lease. After an abrupt stop, the last lease expires and legacy automatically resumes for new actions. Authority generation replacement invalidates the old lease for new actions immediately.

## Atomic action generation

Phase 9C2.3 makes generation selection action-bound. For `POPUP_RING`, both runtimes derive the same safe action-pin key from the shared raw in-memory Linkedid, action type and extension. Atomic create-if-absent selects exactly one generation; subsequent decisions for that action load the immutable generation snapshot referenced by the pin. The raw Linkedid is never persisted—only a SHA-256 hash is stored.

The pin is short-lived (30 seconds by default), generation-bound, scope-bound, checksummed and restrictive. It captures whether the selected generation had a matching ready health lease. A LEGACY→CEM or CEM→LEGACY rotation therefore leaves an already pinned ring on its prior owner and moves only the next ring to the new generation. Missing or unsafe pin state, missing archived generation, mismatched scope or unavailable shared identity restores legacy and blocks CEM.

This removes the mixed-generation ownership race. It does not replace transport idempotency, resolve uncertain `EXECUTING` effects, or prove the dedicated-extension queue constraint.

## Execution and legacy suppression scopes

Phase 9C2.2 keeps queue-aware execution in CEM while leaving the listener queue-unaware. A CEM rule must explicitly serialize both scopes. The execution scope may name a queue and extension; its linked legacy suppression scope may omit only the queue. Legacy suppression still requires the interlock and a healthy lease for the same authority generation.

An execution-only `POPUP_RING` CEM rule is fail-closed: CEM execution is not permitted and legacy remains owner. Suppression is never inferred from a missing queue field.

An extension-only suppression scope is accepted only with reviewed evidence that the extension is dedicated to the execution queue for the entire pilot window. This is an operational constraint, not inferred PBX state. If exclusivity cannot be established, the rule must not be exported. Removing the suppression scope preserves legacy ownership; changing or removing the authority snapshot also invalidates suppression through generation binding.

## Listener guard points

- `ring` WebSocket delivery maps to `POPUP_RING` using the target extension.
- `answered` and `taken` WebSocket delivery map to `POPUP_ANSWERED`; `taken` uses its target extension.
- completed PBXManager creation maps to `ACTIVITY_COMPLETED`.
- missed PBXManager creation maps to `ACTIVITY_MISSED`.
- the current listener has no recording-link side effect, so `RECORDING_LINK` remains untouched.

Guards do not change call detection, answer resolution, missed timers, contact lookup, AMI handling or existing LEGACY SQL transaction behavior. When an activity effect is not legacy-owned, call finalization and the existing end notification still complete without creating the PBXManager record.

Queue scope is used only when a reliable queue identifier is available. The current listener guard does not infer a queue, so queue-only transfer rules do not suppress listener effects lacking that evidence.

## Controlled DEV plan and rollback

1. Export a default-LEGACY snapshot and verify existing behavior.
2. In reviewed DEV configuration, assign only `POPUP_RING` for one extension to `CEM` and enable the interlock. If the CEM adapter is disabled, no ready lease exists and legacy deliberately continues; disabling the adapter must never create a no-owner test state.
3. Restore the rule to `LEGACY` and export again; the legacy popup resumes.

Phase 9C1 may replace step 2's disabled adapter with the reviewed local TCP popup adapter only during a controlled DEV test. The authority rule must remain limited to one tenant, DEV environment, `POPUP_RING`, and one extension. Answered popup, activity, missed activity and recording scopes remain `LEGACY`.

Rollback is atomic for new actions: export a default-LEGACY snapshot, set the interlock false, or remove the active snapshot. An already pinned ring completes under its pinned generation; subsequent rings use LEGACY. No database or call-state migration is required.
