Reviewed coordination promotion and recovery

September 22, 2026 ยท View on GitHub

A promotion moves a Goal's Todo/lease coordination authority from the legacy source to its selected canonical provider. Preview, writer fencing, provider commit, and acknowledgement are distinct steps. A successful preview is neither a grant nor evidence that cutover has happened.

The operator can now save the exact preview, execute that plan, and recover its original transaction without reconstructing intent from a later Markdown view. The TypeScript coordination boundary owns plan validation, qualification, fencing and receipt proof; Python only loads the file and transports the request.

Preview and execute

Use an explicitly enabled, bootstrapped and qualified runtime shadow. Its qualification must cover real mutations and required event classes; an empty shadow or a saved JSON file cannot substitute for that evidence. Existing v0 promotion still requires hard_lease. Provider selection and migration approval remain separate from these commands.

loopx --format json coordination-shadow promote \
  --goal-id example-goal \
  --minimum-operations 3 \
  --require-event-kind todo_update > reviewed-promotion.json

loopx --format json coordination-shadow promote \
  --goal-id example-goal --reviewed-plan reviewed-promotion.json

loopx --format json coordination-shadow promote \
  --goal-id example-goal --reviewed-plan reviewed-promotion.json --execute

Inspect ok, promotion.status, the plan's target provider, source revision, projection digest and qualification policy before execution. The saved file may be the entire successful CLI preview or its promotion.plan.reviewed_plan envelope. Keep it in operator-owned local storage: it carries a runtime path and Goal identity, so it is not a public collaboration artifact.

--reviewed-plan owns the operation id and qualification policy. Combining it with --minimum-operations or --require-event-kind is an error. A normal promote command without a saved plan retains its existing defaults.

Execution captures and qualifies the source again under the existing locks. If the computed plan digest differs, it returns local_authority_reviewed_plan_changed before engaging a writer fence. Review a new preview after legitimate source changes; do not edit the old digest to force acceptance. The digest detects changed intent; the durable fence and provider state establish whether that intent may proceed.

Recover the original cutover

loopx --format json coordination-shadow recover-promotion \
  --goal-id example-goal --reviewed-plan reviewed-promotion.json

loopx --format json coordination-shadow recover-promotion \
  --goal-id example-goal --reviewed-plan reviewed-promotion.json --execute

Recovery resolves the registered Goal and runtime but does not read legacy Markdown or require the transient shadow opt-in. It requires the exact existing writer fence. It never creates a missing fence, selects another provider, or falls back to a legacy source.

Durable statePreviewWith --execute
No matching writer fenceRejectReject
Matching fence, no canonical commit, exact qualified shadow retainedrecovery_readyCommit and read back
Original promotion committed, including a later canonical headreplayedreplayed; no business write
Different canonical initialization or inconsistent receipt lineageRejectReject
Provider unavailableReport provider failureReport provider failure

For an uncommitted recovery, the original shadow revision, projection, capture binding, complete transaction lineage, outbox settlement, operation count and event coverage must still qualify. Recovery validates these durable facts under the same maintenance guard used by canonical writers. It does not pretend to observe fresh source parity after the source has ceased to be authority.

A thrown commit acknowledgement can mean that the provider already committed. Both promotion paths therefore share one commit/readback implementation. It attempts the business commit once, then checks the persisted receipt and first transaction. The receipt body, operation id, cursor, provider revision and initial projection must agree. A matching proof reports success/recovery even if later work has advanced the head. A missing or conflicting proof remains a failure; an unavailable proof read is not silently treated as absence.

The returned promotion revision and cursor identify the original cutover, not the current head. executed=false on a replay means this invocation performed no business write. Inspect legacy_writer_fenced and reconciliation evidence when an execution fails; do not infer that a failure left legacy writers usable.

Bounded capture proof transport

A long Goal can exceed the existing 2 MiB RPC response budget before promotion: sequence recovery used to return a full head and full projections for retained transactions. The outbox_read proof read model now keeps full lineage validation inside TypeScript, while returning progress, receipts, projection digests and partition markers. Sequence allocation requests no transaction rows; drain uses the compact rows. Existing full diagnostic reads retain their default contract. No transport limit, stored population or transaction validation is weakened. A pending outbox still blocks promotion; use the existing bounded authority-shadow drain --goal-id example-goal --budget-seconds 60 operation and inspect its result before retrying preview.

Product and rollout boundary

This is an operator CLI administration journey. It adds no dashboard, Lark or managed-Turn automatic migration trigger, settings editor, capability grant or new provider selector. Those surfaces continue to consume canonical data through the existing routing/projection contracts after a separately authorized cutover.

File and SQLite use their existing local stores. PostgreSQL follows the same transaction/readback contract through its service-owned factory; a local CLI selector alone does not provide a PostgreSQL connection or tenant authority.

The claim-preserving migration work in PR #4870 is a complementary prerequisite for Goals that need explicit preserve or a claim-preserving hard_lease transition. The two changes overlap the promotion orchestration and must be integrated and tested together; this saved-plan feature alone does not enable that policy conversion on a v0-only checkout.

Default-on promotion, SQLite long-duration qualification, post-promotion export or rollback, and retirement of remaining Python callers retain their RFC gates. Recovery is a forward completion/readback operation, not rollback. Do not remove a live fence, reset canonical storage, or replace the source to make recovery pass. Before execution, abandoning a saved preview needs no runtime mutation.

Validation contract

Durable tests cover real File/SQLite CLI preview, saved-plan execution, source drift, policy override rejection, later canonical writes and recovery after legacy deletion. The provider conformance suite uses the shared production-scale fixture in legacy and native record shapes, preserving the complete Todo/lease population through File, SQLite and a real isolated PostgreSQL server.

Negative receipt tests independently corrupt the receipt index and first transaction. Interrupted-commit tests distinguish failure before commit from a lost acknowledgement after commit. These are synthetic fault injections, not a claim of arbitrary process-death or elapsed-soak coverage. Real local rehearsals must use read-only captured sources and disposable copies; never promote an active Goal merely to validate this refactor.