# Flow Runtime V2 Execution Architecture and Migration Contract

- **Status:** Proposed architecture authority for [NexusCRM issue #4001](https://github.com/pingcap-inc/NexusCRM/issues/4001)
- **Date:** 2026-07-26
- **Scope:** L0 Flow execution, persistence, scheduling, and extension contracts
- **Change class:** Execution-kernel redesign; the Flow metadata language and
  Builder compatibility are preserved

## Readers and required action

This document is for the L0 automation, persistence, metadata, control-plane,
connector, API-host, and observability owners who will implement or review the
Flow runtime redesign.

After reading it, those owners must be able to:

1. split issue #4001 into the implementation slices defined here;
2. assign each fact and transition to exactly one owning component;
3. implement the slices in dependency order without inventing a second state
   authority;
4. identify which decisions block coding and which can be deferred;
5. demonstrate the acceptance evidence required before each cohort is enabled.

## 1. Decision

NexusCRM will retain the existing Flow metadata language and authoring
experience, but replace the current element-walking execution model with a
compiled, profile-aware execution kernel.

The kernel has three execution profiles:

- **Embedded Before-Save** for bounded, side-effect-free record transforms that
  must participate in the caller's record transaction.
- **Bounded Atomic** for short, statically bounded units that can safely commit
  as one transaction.
- **Durable Orchestration** for waits, external effects, unknown or large
  fan-out, and any work that must survive process or scheduler failure.

The active `FlowInterview` record evolves into the durable Flow-run authority.
New step, attempt, checkpoint, dependency, and compiled-plan records provide
the missing execution detail. Existing interview fields remain a compatibility
projection during migration; they do not become a second scheduler authority.

`RunAtomically` becomes a compatibility request evaluated by the compiler. It
does not authorize an unsafe transaction shape.

Connector delivery remains owned by Connector work state. External integration
effects remain owned by the integration work/effect model. Flow stores only its
own execution state and immutable lineage to those owners.

This is a redesign of execution ownership and transaction boundaries, not a
rewrite of Flow metadata, the record engine, Connector ingress, or TiDB.

## 2. Why this is the required boundary

The production deadlock class exposed a broader design problem: a single Flow
attempt can currently discover work dynamically while holding a transaction,
cross multiple resource classes, invoke nested behavior, and then replay a
large callback after lock contention. Correctness therefore depends on every
element, action, trigger, history writer, and caller discovering locks in a
compatible order.

The narrow history-sequence repair merged through
[#4002](https://github.com/pingcap-inc/NexusCRM/pull/4002) aligns the known
normal and retry paths around business-row-before-history ordering. That is
necessary containment for the observed lock cycle, but it cannot prove every
future Flow shape safe. New actions, subflows, loops, or record-triggered side
effects can still expand the transaction and lock graph.

The system feels fragile because several concerns are currently coupled:

- metadata interpretation and scheduling;
- execution progress and mutable in-memory context;
- business mutation and orchestration lifetime;
- transaction retry and application replay;
- connector delivery and Flow completion;
- external-effect submission and external-effect outcome;
- admission control and database connection acquisition.

The redesign removes those couplings by making the plan, state, ownership,
transaction, and retry contracts explicit.

## 3. Goals and non-goals

### 3.1 Goals

- Make every Flow version executable through an immutable, validated plan.
- Make transaction size and replay scope explicit before activation.
- Ensure no database transaction spans a wait, remote call, scheduler yield, or
  unbounded loop.
- Give every durable transition a single state authority and fenced compare-and-
  swap protocol.
- Resume only from committed checkpoints.
- Make crash, timeout, retry, and ambiguous-commit behavior deterministic.
- Apply admission and fairness before consuming a database connection.
- Preserve existing Flow metadata and public interview compatibility during a
  controlled migration.
- Produce evidence that separates source correctness, release inclusion,
  staging behavior, production behavior, and business completion.

### 3.2 Non-goals

- Replacing TiDB or hiding database-capacity problems.
- Moving all asynchronous work into one generic queue table.
- Making Connector work state the owner of Flow execution.
- Making Flow the owner of remote-effect outcomes.
- Providing a generic distributed transaction across committed Flow steps.
- Automatically compensating arbitrary external side effects.
- Moving product- or tenant-specific behavior into L0.
- Starting the separate L0/L1/L2 semantic-purity migration.
- Preserving undocumented timing behavior that conflicts with deterministic
  execution and safety.

## 4. Existing foundations and gaps

The current runtime already contains useful foundations:

- activation-time and runtime Flow validation;
- typed transaction-completion outcomes;
- fail-closed handling for commit or rollback ambiguity;
- explicit retry ownership for root atomic execution;
- persistent and transient interviews;
- paused-interview claiming;
- terminal-cause classification and Flow audit evidence;
- dependency publication only after confirmed rollback;
- action registration with partial atomic and preview policy;
- the history lock-order containment merged through #4002.

These foundations do not yet provide:

- an immutable compiled execution plan;
- a complete action capability contract;
- static fan-out and transaction bounds;
- durable per-step state;
- immutable resume checkpoints;
- fenced step attempts and lease-safe commit;
- a durable producer/consumer wait contract;
- a single cutover epoch between old and new schedulers;
- plan-aware admission and backpressure;
- a migration protocol that prevents two execution owners.

The current execution function is therefore both an interpreter and an
orchestrator. The target separates those roles.

## 5. Required invariants

The following invariants are normative.

### 5.1 Authority invariants

1. One fact has one authoritative owner and one normal read path.
2. `FlowInterview`/FlowRun owns Flow lifecycle state.
3. `FlowStepExecution` owns the schedulable state of one logical Flow step.
4. `FlowStepAttempt` owns one fenced attempt lifecycle and its evidence, not
   the current step state.
5. `FlowCheckpoint` owns resumable context and cursor snapshots.
6. `FlowDependency` owns the unresolved/resolved state of a Flow wait.
7. Connector work state owns connector ingress delivery.
8. The designated external-effect subsystem owns remote-effect outcome.
9. Flow audit is evidence; it is not the execution-state authority.
10. Compatibility fields are projections; they never independently drive a
    post-cutover scheduler.

### 5.2 Transaction invariants

1. Admission and claim happen before a business database connection is held.
2. A transaction contains one statically bounded mutation unit.
3. No transaction crosses a wait, network call, scheduler yield, or unknown
   fan-out.
4. A retry starts from an immutable input checkpoint.
5. Only a proven rollback permits automatic replay.
6. Commit-unknown and rollback-unknown are terminal or quarantined outcomes;
   they are never blind retries.
7. The final fenced step transition and its business mutations commit in the
   same transaction.
8. Late lock discovery can restart the entire step after proven rollback; it
   cannot run an element-local special retry.
9. Business and parent-resource locks precede history-sequence reservation.
10. Audit evidence, outbox-intent inserts, and checkpoint inserts occur after
    the required business locks have been acquired.

### 5.3 Plan invariants

1. Every activated Flow version has exactly one effective compiled plan for a
   given compiler, capability-catalog, immutable Runtime metadata view, trigger
   binding, and policy generation.
2. A run is permanently bound to its accepted plan identifier and ownership
   epoch.
3. Plan inputs include transitive action and subflow capabilities.
4. Unknown capabilities fail closed for Bounded Atomic.
5. An active plan is immutable.
6. A compiler, capability, schema, Runtime metadata view, or trigger-binding
   change creates a new plan generation; it does not mutate an in-flight run.

### 5.4 Durable-execution invariants

1. A durable step can be executed by more than one process attempt, but at most
   one attempt may commit.
2. Every commit validates the live lease token, ownership epoch, input
   checkpoint, and state version.
3. A stale worker may calculate or open a transaction, but its failed final
   fence must roll back all business changes.
4. A checkpoint is published only with the state transition that makes it
   current.
5. A completed external effect is consumed by idempotent receipt identity.
6. No bearer token, password, or credential is persisted in Flow context.

## 6. Target ownership model

```mermaid
flowchart LR
    FV["Immutable FlowVersion metadata"] --> PC["Plan compiler"]
    AR["Sealed action/subflow capability catalog"] --> PC
    RM["Immutable RuntimeMetadataView and trigger binding"] --> PC
    PP["Runtime policy generation"] --> PC
    PC --> EP["Immutable FlowExecutionPlan"]
    EP --> FR["FlowInterview / FlowRun authority"]
    FR --> FS["FlowStepExecution authority"]
    FS --> FA["FlowStepAttempt lifecycle"]
    FS --> CP["Immutable FlowCheckpoint"]
    FS --> DP["FlowDependency authority"]
    WS["FlowWakeSignal delivery authority"] --> DP
    FS --> EL["Immutable external-effect link"]
    FS --> RO["Canonical Runtime outbox/inbox authority"]
    EL -.-> RO
    RO --> EO["External-effect owner inbox/command authority"]
    CW["Connector work authority"] -->|starts or correlates| FR
    FR --> AU["FlowAudit evidence"]
```

### 6.1 Fact-to-owner table

| Fact | Sole authority | Other representations |
|---|---|---|
| Author intent | Immutable Flow version | Builder projection |
| Executable graph and profile | Flow execution plan | Compiler diagnostics |
| Flow lifecycle | FlowInterview/FlowRun | Legacy interview status, API projection, audit |
| Schedulable step state | Flow step execution | Queue metrics |
| Attempt result | Flow step attempt | Logs and traces |
| Resume context | Flow checkpoint | Redacted diagnostics |
| Wait resolution | Flow dependency | Producer-specific evidence |
| Accepted wake delivery | Flow wake signal | Dependency resolution receipt |
| Connector delivery | Connector work authority | Flow correlation link |
| Integration effect outcome | Integration work/effect authority | Flow effect link and completion receipt |
| Runtime delivery outcome | Canonical Runtime outbox/inbox authority | Flow effect link and completion receipt |
| Historical evidence | Flow audit | Logs and traces |

The phrase “WorkItem/FlowRun state” must not be interpreted as shared
authority. Connector work and Flow run state are separate, correlated facts.

## 7. Compiled execution plan

### 7.1 Compiler inputs

The compiler consumes:

- immutable Flow version metadata;
- the transitive subflow graph;
- one sealed `FlowActionCapabilityCatalog` snapshot, as defined in section 8;
- one exact immutable `RuntimeMetadataView` and its existing
  `RuntimeWorkBinding`, including `CatalogRevision`, `SchemaGeneration`,
  `EngineGeneration`, `CatalogSnapshotDigest`, and
  `RuntimeMetadataViewDigest`;
- the normalized trigger-subscription set selected from that same view and its
  `TriggerBindingDigest`;
- compiler version;
- capability-catalog generation and digest;
- runtime-policy version;
- schema/capability generation required by the plan.

The compiler must not query tenant records or depend on runtime traffic.

### 7.2 Compiler algorithm

For each Flow version, the compiler:

1. parses and normalizes the metadata;
2. resolves every element, action, and subflow reference;
3. builds a control-flow graph with explicit success, fault, wait, and terminal
   edges;
4. detects recursion and cyclic subflow references;
5. annotates every node with its execution, transaction, replay, effect, read,
   lock, fan-out, and output-durability capabilities;
6. propagates transitive capabilities through subflows and loops;
7. proves loop and fan-out bounds or marks them unknown;
8. groups nodes into candidate execution steps;
9. validates transaction and checkpoint boundaries;
10. selects an execution profile;
11. rejects an explicitly requested profile that cannot be proven safe;
12. emits stable step keys, resource-plan declarations, checkpoint schemas,
    dependency declarations, and compiler diagnostics;
13. computes and stores the immutable plan digest.

### 7.3 Plan identity

The plan digest includes at least:

- Flow version identity and metadata digest;
- compiler version;
- capability-catalog generation and digest;
- every referenced action entry digest and binding fingerprint;
- the complete `RuntimeWorkBinding` tuple and `TriggerBindingDigest`;
- runtime-policy version;
- transitive subflow plan digests;
- normalized plan payload digest.

The effective plan identity is not derived from the Flow version alone. This
prevents action-registry or compiler changes from silently changing the
meaning of an already accepted run.

### 7.4 Logical plan record

`FlowExecutionPlan` contains:

| Field | Contract |
|---|---|
| OrgID | Immutable trusted org identity from RuntimeWorkBinding |
| AppInstallationID | Immutable installation scope from RuntimeWorkBinding |
| RuntimeCapabilityID | Immutable execution-capability scope |
| RuntimeWorkBindingDigest | Canonical complete-binding digest for composite references |
| PlanId | Immutable primary identity within org |
| FlowVersionId | Source version |
| MetadataDigest | Digest of normalized source metadata |
| CompilerVersion | Compiler behavior generation |
| CatalogGenerationId | Required foreign key to the sealed capability artifact |
| CatalogDigest | Digest of the referenced capability generation |
| RuntimeWorkBinding | Exact OrgID, AppInstallationID, RuntimeCapabilityID, Catalog revision, schema/engine generations, Catalog snapshot digest, and Runtime metadata-view digest |
| TriggerBindingDigest | Digest of the normalized trigger subscriptions and dispatch selection used by this plan |
| PolicyVersion | Runtime policy generation |
| ExecutionProfile | Embedded Before-Save, Bounded Atomic, or Durable Orchestration |
| PlanDigest | Digest of the complete normalized plan |
| PlanPayload | Immutable executable graph and contracts |
| BoundSummary | Maximum declared steps, reads, writes, fan-out, and lock classes |
| DiagnosticSummary | Activation-safe human explanation |
| CreatedAt | Audit timestamp |

Required constraints:

- one immutable record per unique
  org/source/compiler/catalog/RuntimeWorkBinding/trigger/policy tuple;
- an enforced composite plan-to-catalog-generation reference over
  `(OrgID, AppInstallationID, RuntimeCapabilityID, RuntimeWorkBindingDigest,
  CatalogGenerationId, CatalogDigest)`; every component must match and a
  referenced generation cannot be deleted;
- an enforced content-addressed plan-to-`RuntimeMetadataView` artifact
  reference through `RuntimeMetadataViewDigest`; a referenced view cannot be
  deleted;
- `PlanPayload` pins each action step's entry digest and binding fingerprint;
- no update of plan payload after activation;
- only a valid compile emits an executable plan record;
- blocked-compilation diagnostics belong to the activation attempt, not a
  mutable plan state;
- a Flow version activation binding references one plan identifier and owns
  active/superseded state;
- in-flight runs retain the accepted plan and Runtime metadata-view artifact
  when a newer plan is compiled.

Flow compilation and V2 trigger dispatch read trigger subscriptions, object and
field schema, lock-relevant relationships, and action metadata only from the
same bound `RuntimeMetadataView`. The plan's activation binding is invalidated
whenever its `RuntimeWorkBinding` or `TriggerBindingDigest` changes. A metadata
publication therefore builds a new trigger dispatch selection and recompiles
affected plans before that view can serve V2 admission.

At event admission, the V2 listener selects only a plan whose complete
`RuntimeWorkBinding` and trigger digest equal the request-bound view. It never
re-resolves V2 subscriptions from live repository state or the legacy
process-global cache. A missing historical view, digest mismatch, or changed
trigger set fails admission before a business transaction; it cannot add
writes, locks, or a newly subscribed Flow to an already activated plan.
Referenced Runtime metadata-view artifacts have the same retention and
compatible-worker-lane rule as referenced action catalog generations.

The compiler implementation is owned by the neutral automation engine. Metadata
activation and control-plane verification invoke the same compiler contract;
they do not maintain separate eligibility logic.

### 7.5 Step kinds

The initial compiler recognizes:

- `EmbeddedTransform`
- `PureCompute`
- `CurrentRead`
- `VerifiedSnapshotRead`
- `BoundedMutation`
- `DependencyWait`
- `EffectSubmit`
- `EffectCompletion`
- `ExplicitFault`
- `Terminal`

`PureCompute` may use only checkpointed inputs. A value captured by
`VerifiedSnapshotRead` carries verification tokens; a later mutation must
verify those tokens or recompute the read inside its own transaction.

The compiler expands every external-effect action into one immutable
`EffectSubmit` → `EffectCompletion` pair. The submit step has exactly one
completion-step identity in the plan; the completion dependency targets that
identity, and only the completion step may consume the owner outcome receipt.
Plan validation rejects a submit step that waits on or resumes itself, a
completion step with no matching submit/link identity, or any edge that can
return from completion to submit.

## 8. Extension capability contract

The existing action registration contract is insufficient because atomic
policy and “sends email” do not describe replay, fan-out, lock planning, or
outcome ownership.

### 8.1 Sole authority

`FlowActionCapabilityCatalog` is the sole effective action registry for Flow
compilation, activation validation, preview, and runtime dispatch.

The neutral automation engine owns the catalog type, normalization, validation,
digest, and read API. The application/distribution composition root is the only
authoring entry point for an executable catalog generation. It assembles:

- generic and platform-owned runtime registrations;
- active pack action descriptors read through the Catalog authority;
- separately released neutral extension registrations;
- distribution-owned handler bindings.

The current separate global, default-app, descriptor, and per-executor
registries are migration inputs, not independent V2 authorities. Slice 2
replaces their downstream reads with one sealed catalog snapshot. No V2
compiler or dispatcher may independently re-read one of those legacy
registries.

Each effective entry binds, in one record:

- normalized action type;
- stable owner/registration identity;
- handler reference and an explicit handler implementation version;
- a stable binding fingerprint derived from owner registration, handler
  reference, handler implementation version, and capability descriptor digest;
- the complete capability descriptor below;
- entry digest and catalog generation.

The handler function itself remains runtime-internal. The neutral contract
publishes the handler reference, owner identity, handler implementation version,
binding fingerprint, and capability descriptor so separately released modules
can be validated without importing runtime code. A process address or function
pointer is never used as persisted identity. A handler implementation change
must change its explicit implementation version and therefore its binding
fingerprint.

Digest construction is non-circular and versioned:

1. normalize and digest the capability descriptor;
2. compute `BindingFingerprint` from the digest-algorithm version, owner
   registration ID, normalized handler reference, handler implementation
   version or service-release digest, and capability-descriptor digest;
3. compute `EntryDigest` from the normalized complete entry, including that
   binding fingerprint;
4. compute `CatalogDigest` from the complete `RuntimeWorkBindingDigest` plus
   the sorted entry digests.

### 8.2 Authoring, sealing, and read path

Catalog construction follows this protocol:

1. The composition root opens a catalog builder for a new generation bound to
   one complete, already verified `RuntimeWorkBinding`.
2. Every source submits a neutral descriptor and, for executable actions, one
   matching handler binding.
3. The builder normalizes action type and handler reference and computes the
   binding fingerprint from the required release-owned inputs.
4. The builder validates ownership, reserved names, capabilities, and handler
   compatibility.
5. The builder sorts entries and computes the catalog digest.
6. The generation is sealed before Flow activation or execution starts.
7. The exact sealed snapshot is injected into the compiler, authoring
   validator, preview executor, and runtime dispatcher.

Those consumers read by `(RuntimeWorkBinding, CatalogGeneration,
CatalogDigest, ActionType, EntryDigest, BindingFingerprint)`.
They do not query package globals, live pack metadata, or a second registry.
An accepted plan stores the catalog generation/digest and every referenced
entry digest and binding fingerprint. Its enforced catalog reference includes
the binding's `OrgID`, `AppInstallationID`, `RuntimeCapabilityID`, complete
binding digest, generation ID, and catalog digest. Runtime dispatch verifies
that full scope plus the exact entry and handler binding before claim admission
and again before mutation. A scope mismatch, missing generation, handler,
entry digest, or binding-fingerprint mismatch fails before the step begins; it
never falls back to a current global registry.

Pack activation, module installation, or registration changes create a new
sealed generation and recompile affected Flow versions. In-flight runs remain
bound to their accepted generation.

### 8.3 Conflict policy

- One normalized action type has exactly one effective owner and executable
  handler binding in a catalog generation.
- Re-submission of the same registration identity and byte-identical entry is
  idempotent before sealing.
- The same action type from a different owner, handler reference, handler
  binding, or capability descriptor rejects the entire generation.
- A reserved contract-only action type cannot compile as executable until its
  designated owner supplies exactly one matching handler binding.
- Product/pack metadata cannot override a platform-owned action, and a runtime
  handler cannot broaden the capability declared by its owner.
- A sealed catalog is immutable. Conflict resolution requires correcting the
  source and constructing a new generation; precedence and last-writer-wins are
  forbidden.

### 8.4 Generation persistence and runtime availability

Every sealed catalog generation is persisted as an immutable
`FlowActionCatalogGeneration` artifact before a plan can reference it.

| Field | Purpose |
|---|---|
| OrgID | Immutable trusted org identity from RuntimeWorkBinding |
| AppInstallationID | Installation whose active-pack descriptors/bindings were assembled |
| RuntimeCapabilityID | Runtime capability scope for execution |
| RuntimeWorkBinding | Complete immutable metadata and work scope |
| RuntimeWorkBindingDigest | Canonical digest used by composite references |
| CatalogGenerationId | Stable generation identity within the complete binding |
| CatalogDigest | Digest of the complete sorted entry set |
| ContractVersion | Neutral descriptor schema version |
| EntriesPayload | Immutable descriptors, handler refs/versions, entry digests, and binding fingerprints |
| CreatedAt | Audit timestamp |

The artifact identity and every entry/index/read key are scoped by
`(OrgID, AppInstallationID, RuntimeCapabilityID, RuntimeWorkBindingDigest,
CatalogGenerationId)`. The builder rejects any active-pack descriptor or
distribution handler binding whose trusted Catalog scope does not equal that
complete binding. `CatalogDigest` commits the binding digest as well as the
sorted entries, so identical entries in another org or installation cannot
alias the artifact.

Handler functions are not serialized. Within its configured
`RuntimeWorkScope`, each runtime binary publishes the exact
`(RuntimeWorkBindingDigest, CatalogGenerationId, ActionType, HandlerRef,
EntryDigest, BindingFingerprint)` tuples it can execute. On startup and before
claim admission, the runtime reconstructs the sealed read view from the
persisted scoped artifact plus its in-binary handler bindings and verifies the
trusted org/install/capability, complete work binding, catalog digest, and every
referenced binding fingerprint.

Release and retention rules:

- an active Flow run and its plan retain an immutable catalog generation
  reference whose full RuntimeWorkBinding scope must byte-match the plan;
- a catalog generation cannot be deleted while referenced by an activated plan,
  a non-terminal run, or the configured diagnostic-retention window;
- a candidate binary must prove support for every generation used by work it
  can claim;
- if a candidate cannot execute an active generation, the release keeps a
  compatible worker lane until those runs terminate;
- restart or module upgrade never rebuilds an old generation from current pack
  metadata;
- a generation, entry, or handler lookup by generation ID without the complete
  trusted work scope is forbidden;
- an unexpected missing binding, entry digest, or binding-fingerprint mismatch
  rejects claim or quarantines before mutation; it never dispatches through a
  newer handler.

Every action and executable extension must publish a neutral L0 capability
descriptor. The descriptor belongs in the neutral contract boundary when
third-party or separately released modules must implement it.

| Capability | Required meaning |
|---|---|
| ExecutionClass | Pure compute, record read, record mutation, wait, or external effect |
| TransactionPolicy | Caller transaction, owned transaction, transaction forbidden, or profile-dependent |
| ReplayPolicy | Deterministic replay, idempotent by key, at-most-once, or not replayable |
| FanoutBound | Static maximum, metadata-derived maximum, or unknown |
| ReadContract | Checkpoint-only, current transactional read, or verified snapshot |
| LockPlanContract | Static resource keys, metadata-derived keys, late-discovery allowed, or unknown |
| EffectOwner | None, integration work, runtime delivery, or another registered owner |
| IdempotencyPolicy | Required key shape and uniqueness scope |
| OutputDurability | Ephemeral, checkpointed, or owner receipt |
| SecurityPolicy | Required principal mode and reauthorization behavior |
| MinimumCapabilityVersion | Compatibility generation |

Rules:

- missing or unknown capabilities cannot enter Bounded Atomic;
- an external-effect action cannot execute its network call inside a Flow
  transaction;
- capability descriptors are versioned and included in the plan digest;
- a descriptor change recompiles dependent plans;
- runtime code asserts the plan contract but does not silently broaden it;
- compilation and dispatch use the same sealed catalog entry.

## 9. Execution profiles

### 9.1 Embedded Before-Save

Use when all of the following are true:

- execution is initiated by a record mutation;
- output is limited to the trigger record's in-transaction transform;
- there is no wait, external effect, scheduler handoff, or independent retry;
- reads, writes, fan-out, and computation are within configured bounds;
- every transitive action permits the caller transaction;
- the caller owns commit and rollback.

Contract:

- no independent durable Flow root is required on the successful hot path;
- failure evidence may be emitted by the caller's existing audit contract;
- the Flow cannot pause or resume;
- the Flow cannot open a second root transaction;
- retry is owned by the caller and must replay the whole record mutation;
- no external side effect occurs before caller commit;
- execution budget exhaustion fails the record mutation deterministically.

This profile preserves the current record-trigger performance reason for
transient interviews without making transient state a general orchestration
model.

### 9.2 Bounded Atomic

Use only when the compiler proves:

- one short owned transaction is sufficient;
- there is no wait or external I/O;
- all loops and subflows have bounded fan-out;
- the maximum read/write/lock set is within configured policy;
- all actions are replay-safe under the same root retry owner;
- resource keys are statically known or safely restartable from the same input
  checkpoint after proven rollback.

Contract:

- the Flow root is persisted and bound to its plan before mutation begins;
- one logical mutation step owns the transaction;
- the whole step is the minimum replay unit;
- the final state, checkpoint, audit/outbox evidence, and business writes commit
  together;
- commit ambiguity quarantines the step;
- the profile is rejected at activation if any capability becomes unknown.

`RunAtomically=true` requests this profile. If the proof fails, activation
fails with diagnostics; the runtime does not silently run a larger unsafe
transaction. A migration policy may explicitly map legacy atomic intent to
Durable Orchestration, but that mapping must be visible, versioned, and
approved before activation.

### 9.3 Durable Orchestration

This is the required profile for:

- waits;
- external effects;
- large or unknown fan-out;
- long-running calculations;
- work requiring scheduler fairness;
- any sequence that must survive process loss;
- any Flow that cannot satisfy Bounded Atomic proof.

Contract:

- the root, current step, and checkpoint are durable;
- every mutation step uses a short transaction;
- committed steps are not rolled back by later failure;
- fault paths and compensating actions are explicit Flow steps;
- waits are represented by durable dependencies;
- remote-effect handoff is dispatched only after the canonical Runtime outbox
  intent commits; the designated owner then durably accepts its command;
- restart resumes from the current committed checkpoint only.

V2 does not promise a generic rollback across committed steps. The public
authoring experience must describe this clearly.

## 10. Durable data model

### 10.0 Trusted org identity and key discipline

Every durable Flow operation first derives `TrustedOrgContext` from the
authenticated session, workload delegation, or operator routing defined by the
serviceification authority. `OrgID` never comes from Flow metadata, checkpoint
payload, wake evidence, or another ordinary caller field. At admission it must
byte-equal `RuntimeWorkBinding.OrgID`; the binding's `AppInstallationID` and
`RuntimeCapabilityID` are validated at the same boundary.

Every Flow durable row below carries immutable `OrgID`. A child insert or
transition must compare it with the root, parent row, accepted plan binding,
and trusted context. Physical foreign keys and logical uniqueness constraints
include `OrgID` even when the record ID is otherwise globally unique. Every
read, claim, lease, compare-and-swap, dedupe, reconciliation, retention, and
repair query is scoped by trusted `OrgID`; an ID-only repository method is
forbidden. Pooled workers partition admission by this scope and fail closed
before SQL when their trusted org does not match the row or plan.

`AppInstallationID` is also part of work/idempotency identity whenever a
different installation or handler binding changes semantics. The complete
persisted `RuntimeWorkBinding`, rather than independently reconstructed payload
fields, decides that scope.

For cross-boundary Runtime messages and durable producer wakeups,
`TrustedRuntimeWorkScope` means the exact verified
`(OrgID, AppInstallationID, RuntimeCapabilityID, RuntimeWorkBindingDigest)`
derived from that persisted binding. Every dependency/wake match, inbox,
dedupe, rejection, and receipt check uses all four components;
`TrustedOrgContext` alone is insufficient for work identity.

### 10.1 FlowInterview evolves into FlowRun

The existing interview identity remains the public Flow-run identity within
its trusted org. Its durable lookup identity is `(OrgID, FlowRunId)`; an
unscoped `FlowRunId` is never a repository or support-read key. Add canonical
runtime fields rather than create a parallel root table.

| New field | Purpose |
|---|---|
| OrgID | Immutable identity from trusted RuntimeWorkBinding |
| PlanId | Immutable accepted execution plan |
| ExecutionProfile | Accepted profile |
| ExecutionOwner | Legacy interpreter or Flow Runtime V2 |
| RuntimeState | Canonical V2 lifecycle state |
| CurrentStepExecutionId | Canonical schedulable step pointer |
| CurrentCheckpointId | Canonical resume snapshot pointer |
| StateVersion | Compare-and-swap version |
| OwnershipEpoch | Old/new scheduler exclusivity generation |
| RunAfter | Earliest runnable time |
| QueueClass | Admission lane |
| Priority | Policy-controlled priority |
| TerminalCauseCode | Stable safe terminal classification |
| DeleteRequestedAt | Durable public-delete/cancellation request time |
| StartedAt | First execution timestamp |
| CompletedAt | Terminal timestamp |

Legacy interview status, current element, paused time, and context fields remain
transactionally derived projections until consumers migrate. They are never
updated independently of the canonical V2 transition.

`FlowInterviewStateStore` is the only write boundary for this table after the
bridge fleet is drained of pre-bridge binaries and
`FlowInterviewWriteProtocolGeneration=1` is enabled. The persistence layer then
applies a table-specific, fail-closed write policy to every generic insert,
update, upsert, bulk, and delete path:

- a legacy transition must compare
  `(ExecutionOwner=LegacyInterpreter, OwnershipEpoch=0, StateVersion)` and may
  update only the legacy-owned field set;
- a V2 transition must compare
  `(ExecutionOwner=FlowRuntimeV2, OwnershipEpoch, StateVersion)` and updates the
  canonical state and compatibility projection in one transaction;
- generic record CRUD cannot insert, update, upsert, bulk mutate, or delete any
  FlowInterview row; public lifecycle commands delegate to typed state-store
  transitions;
- an internal caller without the typed transition capability and expected
  owner/epoch/version is rejected before SQL execution;
- metadata permissions also mark protected fields non-createable and
  non-updateable, but metadata permissions are defense in depth rather than the
  storage authority.

The bridge source replaces current unrestricted `CreateInterview`,
`UpdateInterview`, wait-promotion, pause-update, and `SaveFlowInterview`
call sites with the typed store. The write-protocol gate remains off during
pre-bridge coexistence. New V2 admission remains disabled until the drain gate
and an inventory test prove there is no remaining generic write path for the
table.
A module-boundary gate rejects direct FlowInterview SQL and generic table DML
outside the state store, and persistence integration tests exercise every
generic CRUD/bulk entry point against the table to prove fail-closed behavior.

The existing public `DeleteFlowInterview` contract remains source-compatible
but no longer performs an immediate physical delete:

1. the existing authorization check runs before any state change;
2. for a non-terminal run, the typed command locks root then active step by
   trusted OrgID, compares the expected root `StateVersion` and non-terminal,
   non-`CancelRequested` state, atomically records `DeleteRequestedAt`, sets
   the existing canonical `IsDeleted=true` soft-delete fact, moves the root to
   `CancelRequested`, and increments its state version;
3. in that same transaction, a `Pending`/`RetryScheduled` active step becomes
   `Cancelled`; a `Claimed`/`Running` active step becomes `CancelRequested`
   with an incremented step version and its matching `Started` attempt
   compare-and-swaps to terminal `Cancelled`; a `Waiting` step becomes
   `CancelRequested` without rewriting its already-terminal prior attempt.
   Thus the old lease, attempt, and root-version fence cannot commit;
   dependencies and pending handoffs take the cancellation disposition defined
   below;
4. for a terminal run, it records only the idempotent `IsDeleted=true`
   soft-delete fact and request time;
5. a repeated authorized delete returns the same successful outcome;
6. every normal record read path, including point reads, `Get`/`List`,
   `QueryWithSpec`, relationship queries, and Flow support summaries, consumes
   the existing shared `IsDeleted` visibility filter; only an explicitly
   authorized include-deleted/recycle-bin or retention read can reconstruct it;
7. physical deletion is an internal retention operation only after the run and
   all steps, dependencies, attempts, checkpoints, Runtime outbox messages, and
   owner receipts are terminal, their retention windows have elapsed, and no
   audit/legal hold applies.

Delete and successful step commit serialize on the same root row and expected
state version. If delete wins, the worker's final root/step/attempt CAS fails
and its entire business transaction rolls back. If the worker's atomic commit
wins first, delete re-reads the advanced root/current step and never represents
that committed business change as cancelled or rolled back. A terminal
`Cancelled` attempt proves cancellation won the root/step fence; physical
retention still waits for the old lease/connection/transaction to quiesce.

Cancellation cannot erase an accepted external command or committed business
effect. Pending handoff delivery is cancelled only after the owner returns the
durable `Rejected` fence receipt; an accepted receipt continues owner outcome
tracking and an unknown receipt quarantines. The owner outcome always remains
durable evidence.
During legacy drain, `DeleteFlowInterview` uses the typed
`LegacyInterpreter/0/StateVersion` cancellation-and-tombstone transition rather
than physical deletion. The old direct-delete repository method is removed
from the public implementation path and retained, if needed, only as a private
retention primitive with the terminal/reference guards above.

There is no second `VisibilityState` or Flow-only tombstone. `IsDeleted` is the
single public-visibility fact and the normal generic read path remains its
single consumer. `DeleteRequestedAt` is lifecycle evidence for cancellation
and retention; it never controls visibility independently.

Recommended indexes:

- org, canonical state, run-after time, queue class, and stable identity for
  claims;
- org, Flow version, creation time, and identity for history queries;
- org, ownership epoch, and canonical state for migration reconciliation.

### 10.2 FlowStepExecution

One record represents one logical step invocation.

| Field | Purpose |
|---|---|
| OrgID | Immutable trusted org identity |
| StepExecutionId | Primary identity within org |
| FlowRunId | Owning root |
| PlanStepKey | Stable compiled node/group identity |
| InvocationOrdinal | Loop or repeated invocation identity |
| StepKind | Compiled step kind |
| State | Canonical step lifecycle |
| InputCheckpointId | Immutable input |
| OutputCheckpointId | Successful output |
| StateVersion | Compare-and-swap version |
| AttemptCount | Monotonic attempt count |
| LeaseOwner | Current claimant |
| LeaseToken | Unpredictable fencing token |
| LeaseUntil | Claim expiry |
| OwnershipEpoch | Scheduler generation |
| RunAfter | Earliest eligible time |
| QueueClass | Admission lane |
| FailureClass | Stable safe failure class |
| CreatedAt/UpdatedAt | Audit timestamps |

Required constraints and indexes:

- unique logical invocation per org, run, plan step, and invocation ordinal;
- claim index by org, state, queue class, run-after time, and stable identity;
- org and lease-expiry index for recovery;
- org, run, and plan-step index for support diagnostics.

### 10.3 FlowStepAttempt

An attempt is one fenced mutable lifecycle row, not an append-only event. Its
identity and execution inputs are immutable; its outcome fields transition
once from `Started` to exactly one terminal outcome.

| Field | Purpose |
|---|---|
| OrgID | Immutable trusted org identity |
| AttemptId | Primary identity within org |
| StepExecutionId | Logical step |
| AttemptNumber | Monotonic sequence |
| LeaseTokenDigest | Safe fence correlation |
| OwnershipEpoch | Scheduler generation |
| RootStateVersionAtClaim | Immutable root cancellation/advancement fence |
| StartedAt/FinishedAt | Timing |
| TransactionOutcome | Typed completion outcome |
| FailureClass | Retryable, terminal, ambiguous, fenced, or cancelled |
| SafeErrorCode | Non-sensitive diagnostic |
| RetryDecision | None, scheduled, terminal, or quarantined |
| RetryPolicyDigest | Immutable policy selected at claim |
| RecoveryBackoffFloor | Immutable minimum lease-loss retry delay |
| AttemptStateVersion | Compare-and-swap version for the one terminal transition |
| PlanDigest | Exact executed contract |
| LockPlanDigest | Declared resource plan |
| TraceId | Observability correlation |
| MetricsSummary | Bounded attempt counters |

`AttemptId`, step/attempt identity, fence correlation, epoch,
`RootStateVersionAtClaim`, retry-policy digest, recovery backoff floor,
plan/lock digests, and start time never change. Outcome, failure, retry
decision, bounded metrics, and finish time change only through a fenced
compare-and-swap from `Started`; terminal rows are immutable. Audit records and
transaction receipts are separate append-only evidence. The attempt lifecycle
must never be used as an alternative current-step-state query. Attempt
uniqueness and every attempt transition use
`(OrgID, StepExecutionId, AttemptNumber)` or `(OrgID, AttemptId)`.

### 10.4 FlowCheckpoint

Checkpoints are immutable.

| Field | Purpose |
|---|---|
| OrgID | Immutable trusted org identity |
| CheckpointId | Primary identity within org |
| FlowRunId | Owning root |
| Sequence | Monotonic run sequence |
| PlanCursor | Next compiled step and control-flow position |
| ContextSchemaVersion | Decoder generation |
| ContextPayload | Size-bounded serialized state or durable payload reference |
| ContextDigest | Integrity and idempotency |
| ReadVerificationSet | Optional snapshot verification tokens |
| CreatedByAttemptId | Commit lineage |
| CreatedAt | Audit timestamp |

Constraints:

- unique sequence per org and run;
- no update after insert;
- current checkpoint pointer changes only in the same transaction as the
  successful step transition;
- payloads exclude credentials and apply configured retention and redaction.

### 10.5 FlowDependency

One active dependency describes why a step cannot yet continue.

| Field | Purpose |
|---|---|
| OrgID | Immutable trusted org identity |
| AppInstallationID/RuntimeCapabilityID | Immutable waiting-consumer scope |
| RuntimeWorkBindingDigest | Complete immutable binding scope |
| DependencyId | Primary identity within org |
| FlowRunId/StepExecutionId | Waiting consumer |
| ProducerKind | Registered producer class |
| ProducerKey | Stable producer identity |
| ConditionDigest | Immutable satisfaction rule |
| State | Pending, resolved, expired, quarantined, or cancelled |
| WakeVersion | Monotonic producer notification version |
| WakeToken | Idempotent notification identity |
| Deadline | Terminal time bound |
| NextProbeAt | Optional bounded reconciliation time |
| ExpiryPolicy | Fault path, terminal failure, or quarantine |
| ResolvedAt | Completion evidence |
| ResolutionReceipt | Safe producer evidence |

There is at most one active dependency per
`(TrustedRuntimeWorkScope, waiting step)`. A dependency without a registered
producer and without a bounded reconciliation strategy fails activation or is
quarantined; it is not polled forever.

### 10.6 FlowWakeSignal

`FlowWakeSignal` is the durable receipt authority that a producer notification
was accepted, including when no dependency row exists yet.

| Field | Purpose |
|---|---|
| OrgID/AppInstallationID/RuntimeCapabilityID | Immutable producer work scope |
| RuntimeWorkBindingDigest | Complete immutable binding scope |
| WakeSignalId | Primary identity within work scope |
| ProducerKind/ProducerKey | Stable matching key |
| WakeToken | Idempotent producer delivery identity |
| WakeVersion | Optional monotonic producer version |
| EvidenceDigest | Safe integrity/correlation value |
| EvidenceReference | Optional pointer to producer-owned evidence |
| ReceivedAt | Durable acceptance time |
| ExpiresAt | Retention bound |
| ResolutionState | Unmatched, consumed, or expired |
| DependencyId | Set only by the atomic consume transition |

Required constraints:

- unique trusted Runtime work scope, producer kind, producer key, and wake
  token;
- producer identity, token, version, and evidence are immutable after insert;
- matching state and dependency identity change only by fenced compare-and-swap;
- no acknowledgement to an event-only producer before signal commit;
- unmatched signals remain queryable until after the maximum eligible
  dependency deadline plus the repair window;
- consumption is idempotent and cannot transfer one signal to two
  dependencies; a broadcast producer emits distinct durable signal identities
  or exposes durable producer history for each consumer;
- dependency state remains the wait-resolution authority; the signal owns only
  durable notification acceptance.

Signal insertion derives the complete `TrustedRuntimeWorkScope` from the
authenticated producer's persisted Runtime binding, never from evidence or
event payload. Matching and consumption require the signal, dependency, step,
and root's persisted binding to share the exact `(OrgID, AppInstallationID,
RuntimeCapabilityID, RuntimeWorkBindingDigest)`. Cross-org or same-org
cross-installation/capability/binding collisions with the same producer
key/token cannot match, consume, or reveal another work scope's signal.

### 10.7 Runtime effect-handoff outbox message family

This design does not create a `FlowEffectDispatchOutbox` table, queue, claimer,
or delivery read path. Effect submission is one message family in the existing
canonical **Runtime outbox/inbox** authority defined by the serviceification
architecture. Its physical store, state machine, lease, retry, dedupe,
dead-letter, repair, and operator read path remain the shared Runtime outbox
implementation.

The Flow-specific immutable message schema is:

| Field | Purpose |
|---|---|
| OrgID/AppInstallationID/RuntimeCapabilityID | Immutable trusted Runtime work scope |
| RuntimeWorkBindingDigest | Complete immutable binding scope |
| RuntimeOutboxMessageId | Canonical Runtime outbox identity within work scope |
| MessageFamily | Versioned `flow.effect.submit` family |
| EffectHandoffKey | Deterministic canonical message/dedupe key |
| EffectOwnerKind | Trusted destination capability, never a metadata URL |
| PayloadSchemaVersion | Versioned owner-acceptance envelope |
| PayloadDigest/Reference | Frozen safe payload or content-addressed reference |
| FlowRunId/StepExecutionId | Originating Runtime work lineage |
| SubmissionAttemptId | Fenced Flow attempt that emitted the intent |
| Deadline/FailurePolicy | Bounded handoff convergence contract |

The Flow mutation calls the canonical Runtime outbox enqueue API inside its
transaction. After commit, the existing Runtime outbox dispatcher delivers the
envelope plus its `TrustedRuntimeWorkScope` idempotently to the designated
owner's inbox. `EffectHandoffKey` canonically encodes `OrgID`,
`AppInstallationID`, `RuntimeCapabilityID`, and `RuntimeWorkBindingDigest`; the
owner stores those same values as immutable validation attributes. The owner
commits inbox dedupe and `ExternalCommand` acceptance before acknowledging.
Lost acknowledgement retries the same canonical message key.

Runtime owns only this transport message's delivery state. Integration owns
`ExternalCommand`, its handoff-key-to-command mapping, and remote-effect
outcome. No Flow-specific worker may claim the canonical message, and no
Flow-specific table may duplicate its delivery state. An accepted receipt must
match the complete trusted Runtime work scope, handoff key, Runtime outbox
identity, and payload digest before the canonical Runtime outbox marks delivery
complete.

The effect-owner inbox/command authority also provides the idempotent
[`RejectIfUnaccepted` fence](issue-3725-integration-work-effect-ownership.md#61-runtime-handoff-acceptancerejection-fence).
Command acceptance and rejection serialize on the same unique owner-inbox key:

- `EffectHandoffKey` alone is unique; Runtime outbox identity, payload digest,
  and the complete `TrustedRuntimeWorkScope` are immutable validation
  attributes on that row, never alternate key parts; the key's canonical
  grammar already includes that full scope;
- acceptance atomically records `Accepted` plus the one `ExternalCommand`, or
  returns the existing accepted command;
- rejection atomically returns an existing accepted receipt, or records a
  durable `Rejected` tombstone when no command was accepted;
- a later acceptance for a `Rejected` key is deterministically refused;
- an org, App installation, Runtime capability, binding-digest, payload-digest,
  or originating-outbox mismatch locks the existing handoff row and fails
  closed/quarantines without cross-scope disclosure; it cannot insert a second
  fence, command, or WorkItem.

The owner retains accepted/rejected fence records beyond the Runtime outbox,
Flow dependency, and maximum redelivery/repair windows. A read that merely
finds no command is never proof of non-acceptance.

### 10.8 FlowEffectLink

The link is immutable lineage, not a duplicate effect-state machine.

| Field | Purpose |
|---|---|
| OrgID/AppInstallationID/RuntimeCapabilityID | Immutable trusted Runtime work scope |
| RuntimeWorkBindingDigest | Complete immutable binding scope |
| EffectLinkId | Primary identity within work scope |
| FlowRunId/StepExecutionId | Flow lineage |
| EffectOwnerKind | Designated authority |
| RuntimeOutboxMessageId | Canonical Runtime outbox message committed with the Flow transition |
| EffectHandoffKey | Cross-boundary message/deduplication key |
| SubmissionAttemptId | Flow attempt that committed the handoff intent |
| CreatedAt | Audit timestamp |

The effect owner's accepted receipt owns the mapping from `EffectHandoffKey` to
its command/work identity. Support reads may join that owner receipt by handoff
key, but Flow does not mutate the immutable link to copy an owner command ID.
Remote outcome is read from the owner or consumed through a durable,
idempotent completion receipt. Flow must not infer owner acceptance or remote
completion from the existence of the link.

## 11. State machines

### 11.1 FlowRun

```mermaid
stateDiagram-v2
    [*] --> Accepted
    Accepted --> Runnable
    Runnable --> Running
    Running --> Runnable: next step committed
    Running --> Waiting: dependency committed
    Running --> Runnable: expired attempt fenced
    Running --> Runnable: proven rollback scheduled
    Waiting --> Runnable: durable resolution or compiled fault path
    Waiting --> TerminalFailed: dependency expiry terminal policy
    Waiting --> Quarantined: expiry or acceptance ambiguity
    Running --> Succeeded
    Running --> TerminalFailed
    Running --> Quarantined: ambiguous outcome
    Accepted --> CancelRequested
    Runnable --> CancelRequested
    Running --> CancelRequested
    Waiting --> CancelRequested
    CancelRequested --> Cancelled: fenced cleanup complete
    Succeeded --> [*]
    TerminalFailed --> [*]
    Quarantined --> [*]
    Cancelled --> [*]
```

`CancelRequested` increments the root fence and the active step's cancellation
fence so an in-flight worker cannot publish through its old root version, step
version, or lease. Every worker final commit must compare the root version and
non-cancelled state as well as its step/attempt fence. Cancellation then
atomically cancels claimable steps/dependencies and applies the effect-handoff
disposition before entering `Cancelled`. It cannot claim that already committed
business or external effects were rolled back. Compensation, when supported,
is an explicit plan. Cancellation uses the canonical lifecycle-row order in
Section 12: root, affected steps, attempts, dependencies, then handoff
dispositions. It never locks a dependency or handoff row and then waits for the
root.

### 11.2 FlowStepExecution

```mermaid
stateDiagram-v2
    [*] --> Pending
    Pending --> Claimed
    RetryScheduled --> Claimed
    Claimed --> Running
    Claimed --> Pending: lease released before work
    Running --> Succeeded
    Running --> Waiting
    Running --> RetryScheduled: proven rollback
    Running --> RetryScheduled: expired lease fenced
    Running --> Failed
    Running --> Quarantined: ambiguous outcome
    Claimed --> RetryScheduled: expired lease recovered
    Pending --> Cancelled
    RetryScheduled --> Cancelled
    Claimed --> CancelRequested: root cancellation fence
    Running --> CancelRequested: root cancellation fence
    Waiting --> CancelRequested: root cancellation fence
    CancelRequested --> Cancelled: stale commit fenced and cleanup complete
    Waiting --> Pending: dependency resolved
    Waiting --> Failed: dependency expired
    Waiting --> Quarantined: producer or acceptance ambiguity
```

### 11.3 FlowStepAttempt outcome

```mermaid
stateDiagram-v2
    [*] --> Started
    Started --> Committed
    Started --> RolledBack
    Started --> CommitUnknown
    Started --> RollbackUnknown
    Started --> PreTransactionFailed
    Started --> Fenced
    Started --> Cancelled
    Started --> Abandoned
```

Only `RolledBack`, explicitly classified pre-transaction failures, or a
completed fenced-recovery transition that proves the old attempt cannot commit
may lead to automatic retry. Each path schedules durable backoff.
`CommitUnknown` and `RollbackUnknown` lead to quarantine or a dedicated
reconciliation workflow. `Fenced` alone is not retry permission: the same
recovery transaction must install `RetryScheduled` with at least the attempt's
immutable `RecoveryBackoffFloor`. `Cancelled` means the root cancellation CAS
won while the attempt was still `Started`; the same root→step→attempt
transaction made the old root/step fence uncommittable before recording this
terminal outcome. `Abandoned` is allowed only when the attempt provably never
entered a business transaction.

Every arrow is a compare-and-swap on `AttemptStateVersion`,
`LeaseTokenDigest`, and `OwnershipEpoch`. The successful business commit moves
the same row from `Started` to `Committed` inside the root/step-fenced
transaction and also compares `RootStateVersionAtClaim`. Any attempt transition
that advances, retries, or republishes work must compare the same live root
non-cancellation fence; a cancellation winner permits only terminal
`Cancelled` cleanup. A delete/cancellation transition compare-and-swaps
`Started` directly
to `Cancelled`; it never reuses `Fenced`. A proven rollback records its terminal
transition in the following bounded state transaction. Recovery may move a
stale `Started` attempt to `Fenced` only after proving that its lease/epoch
cannot commit and no root cancellation won. No terminal outcome can be
overwritten.

## 12. Claim, fencing, and commit protocol

Within the complete platform order in Section 13.1, every transaction that
mutates existing Flow lifecycle rows follows one mandatory lifecycle sub-order:

1. `FlowRun` root, keyed by trusted `(OrgID, FlowRunId)`;
2. affected `FlowStepExecution` rows in stable `StepExecutionId` order;
3. affected `FlowStepAttempt` rows in stable attempt order;
4. affected `FlowDependency` rows in stable `DependencyId` order;
5. affected wake-signal, effect-link, outbox-finalization, or handoff-disposition
   rows in their stable key order.

Candidate discovery by dependency, signal, deadline, or outbox identity is a
non-locking lookup. A transaction that has declared business mutations first
acquires its business/parent and history-sequence locks in the Section 13.1
order; it then enters the lifecycle sub-order at the root. A lifecycle-only
transition has no business/history locks and starts at the root. Both forms
revalidate every discovered identity and version while acquiring the remaining
lifecycle rows in the order above. After the root is locked, no new business,
parent, or history-sequence lock may be acquired. Claim, commit, retry,
recovery, resolution, expiry, cancellation, dead-letter finalization, and repair
may not enter the lifecycle sub-order at a step, dependency, signal, or outbox
row and later acquire the root. A path that already holds one of those
lower-order lifecycle rows must end that transaction and retry through the
ordered transition API. This is the one lifecycle lock order; step- or
dependency-specific workers do not define alternatives.

### 12.1 Claim

The scheduler:

1. selects eligible work by trusted OrgID, queue class, and fairness policy,
   joining the current root pointer and rejecting `IsDeleted`,
   `DeleteRequestedAt`, or `CancelRequested`;
2. applies admission budgets without holding a business connection;
3. locks root then step and atomically changes `Pending` or eligible
   `RetryScheduled` to `Claimed` only while the root is the expected
   `Runnable`, non-deleted version and still points to that step;
4. assigns lease owner, unpredictable lease token, lease deadline, ownership
   epoch, and incremented step state version, moves the root to `Running`, and
   increments the root state version;
5. inserts the next attempt lifecycle row in `Started` with the claim fence and
   the resulting immutable `RootStateVersionAtClaim`, selected
   `RetryPolicyDigest`, and deterministic jittered `RecoveryBackoffFloor`
   derived from policy plus attempt number and no shorter than the ordinary
   retry floor for that attempt;
6. releases the claim transaction;
7. starts execution only after the claim is confirmed.

Claim queries use stable ordering and bounded batches. They must not scan an
unbounded paused-interview set.

### 12.2 Execute

The worker:

1. loads the immutable plan, input checkpoint, root, step, and `Started`
   attempt, including `RootStateVersionAtClaim`;
2. verifies trusted OrgID, plan digest, run ownership epoch, root/current-step
   pointer, root state/version, step state/version, and lease token;
3. before executing user work, opens a short lifecycle-only transaction, locks
   root then step, and compare-and-swaps the step `Claimed -> Running` using
   the lease/epoch/step version only while the root still equals
   `RootStateVersionAtClaim`, points to that step, and is not
   cancelled/deleted; it uses the resulting step version as the final fence;
4. performs pure computation without a database transaction where allowed;
5. opens a transaction only for the bounded current-read/mutation unit;
6. verifies any snapshot-read tokens;
7. acquires declared business resource locks in canonical order;
8. reserves any required history-sequence state; late resource discovery rolls
   the transaction back before any lifecycle lock;
9. enters the lifecycle sub-order by locking the root and revalidating trusted
   OrgID, `ExecutionOwner`, ownership epoch, `RootStateVersionAtClaim`,
   current-step pointer, `RuntimeState=Running`, `IsDeleted=false`, and
   `DeleteRequestedAt IS NULL`;
10. locks and revalidates the step using trusted OrgID, lease token, ownership
    epoch, input checkpoint, state version, and `State=Running`, then locks and
    revalidates the attempt from `Started` using the same lease/epoch/root
    fence;
11. applies business writes only to the already-locked declared resource set,
    then inserts the derived audit/outbox records and immutable output
    checkpoint required by the same commit; neither operation may discover or
    acquire a new earlier-class lock;
12. applies the final fenced compare-and-swap to the already-locked root, step,
    and attempt; advances the root pointer/state and compatibility projection;
    and increments all state versions in that same transaction;
13. commits.

If any final root, step, or attempt compare-and-swap fails, every business
change, audit/outbox write, and checkpoint insert in that transaction rolls
back. A concurrent delete changes root state/version and active-step state
version, so the stale worker cannot commit after cancellation. This is the
commit fence that prevents an expired or cancelled worker from publishing
duplicate work.

The successful business transaction compare-and-swaps root, step, and mutable
attempt lifecycle together, including the attempt transition from `Started` to
`Committed`. A proven rollback is recorded afterward in a small state
transition transaction that locks root then step then attempt, compares the
same root cancellation fence, compare-and-swaps the attempt to `RolledBack`,
sets the step to `RetryScheduled` with its durable `RunAfter`, and atomically
returns the root from `Running` to `Runnable` while retaining that step as
`CurrentStepExecutionId` and publishing the same next-due time. All three state
versions advance together. The scheduled time is no earlier than transition
time plus the attempt's `RecoveryBackoffFloor` and may be later for its
classified failure. Claim admission still checks step `RunAfter`, so a
`Runnable` root does not bypass backoff. If the worker dies between rollback
and that transition, lease recovery follows the protocol below and preserves
at least that precommitted duration; if cancellation won, the attempt is
already terminal `Cancelled`, cleanup only quiesces the lease/transaction, and
the step is never rescheduled.

Retry transition time and `RecoveryObservedAt` are captured once from the
database transaction clock and persisted with `RunAfter`; a worker-local clock
cannot shorten the floor.

### 12.3 Lease behavior

- lease duration is policy-controlled and longer than the admitted step budget;
- long pure-compute steps may heartbeat in a lifecycle-only root→step
  transaction without changing the commit fence;
- mutation transactions do not depend on a heartbeat succeeding;
- a recovered lease creates a new token and attempt;
- the old worker cannot commit after the new token is active;
- repeated lease expiry becomes a terminal policy signal, not infinite churn.

### 12.4 Expired-attempt recovery

Recovery locks root then step and compare-and-swaps the expired `Claimed` or
`Running` step using trusted OrgID, its old lease token, step state version,
ownership epoch, and the attempt's `RootStateVersionAtClaim`.

- If the prior business commit already won, the step is no longer in the
  recoverable state and recovery does nothing.
- If delete/cancellation changed the root version or state, recovery never
  makes the step runnable; it preserves or records terminal `Cancelled` on the
  attempt and completes the
  `CancelRequested -> Cancelled` cleanup path.
- If recovery wins first, it installs a new fence. Any still-running old
  transaction fails its final compare-and-swap and rolls back all business
  writes.
- If the old process is gone and the step remains under the expired token, the
  absence of the atomic final state transition proves that no business commit
  from that attempt became visible.
- The old attempt is marked `Fenced`, the step becomes `RetryScheduled` with
  `RunAfter >= RecoveryObservedAt + RecoveryBackoffFloor`, and the root
  atomically becomes `Runnable` while retaining that step as
  `CurrentStepExecutionId` and publishing the same next-due time. Root, step,
  and attempt state versions increment in the same root→step→attempt recovery
  transaction. A worker death after business rollback but before the normal
  `RolledBack` transition therefore cannot erase backoff or create an immediate
  retry storm. Only a due claim can create the next attempt from the same input
  checkpoint.

This proof depends on business writes, attempt success, checkpoint publication,
step success, and root advancement sharing one transaction. Splitting those
writes invalidates the recovery protocol.

## 13. Transaction, lock, and retry protocol

### 13.1 Global resource-order authority

Persistence owns the platform-wide resource ordering contract because ordinary
record writes, Flow writes, history reservation, audit, and outbox code must
share it. The Flow compiler consumes that contract; it does not invent a
Flow-only order.

The initial total lock-acquisition rule is:

1. determine the business and parent-resource set;
2. acquire business/parent locks in canonical resource-key order;
3. reserve history-sequence state only after its business rows;
4. if the transaction mutates Flow lifecycle state, acquire Flow root, stable-
   ordered steps, attempts, dependencies, and signal/outbox disposition rows in
   the Section 12 sub-order;
5. append derived audit, outbox, and checkpoint records only after all required
   business/history and Flow lifecycle locks are established.

Lifecycle-only claim, retry, recovery, resolution, expiry, cancellation,
dead-letter, and repair transitions have no business/history resource set and
therefore begin at Flow root. A business-mutating worker begins with declared
business/history locks and then enters Flow state at the root. No code path may
acquire a new business, parent, or history-sequence lock after acquiring a Flow
root, and no path may acquire a step/dependency/outbox row then wait for the
root. Already-locked business rows may be updated after the root fence is
validated, but late resource discovery rolls the entire transaction back. This
is one total order, not two competing orders.

Any future resource-class reordering requires:

- an explicit persistence decision record;
- current-tree call-path inventory;
- mixed Flow and non-Flow concurrency tests;
- upgrade tests with old and new application binaries;
- no alternate retry path that reverses the order.

### 13.2 Resource keys

A declared resource key contains:

- resource class;
- object or namespace identity;
- stable record/parent identity;
- optional partition identity;
- ordering version.

Keys are normalized and sorted by the persistence-owned comparator. Runtime
discovery cannot use map iteration, arrival order, or metadata declaration
order as lock order.

### 13.3 Late discovery

If an allowed action discovers a required resource that was not in the admitted
lock plan:

1. record the safe resource-class mismatch;
2. roll back the entire current step;
3. verify rollback completion;
4. update only the retry attempt's expanded resource plan within the plan's
   declared maximum;
5. restart from the same immutable input checkpoint.

If the resource exceeds the plan's declared class or bound, terminate or
quarantine. Do not preserve partial in-memory element progress.

### 13.4 Retry classification

Automatic retry requires all of:

- typed failure is retryable;
- transaction outcome is proven rolled back or failure occurred before begin;
- replay policy permits the entire step;
- attempt count and elapsed retry budget remain;
- admission permits another attempt.

Backoff uses bounded exponential delay with jitter. Retry schedules are durable
and visible. Retry does not occur in a tight process-local loop that monopolizes
a database connection or worker.

## 14. Durable waits and dependency wakeup

A wait transition and its dependency record commit with the checkpoint that
defines the resumption point.

### 14.1 Producer contract

Every producer kind publishes:

- stable producer identity;
- registration and existence check;
- notification or reconciliation mechanism;
- a same-transaction signal write when producer evidence shares the database,
  or an owner-controlled durable outbox transfer when it does not;
- monotonic wake version or idempotent wake token;
- safe resolution receipt;
- expiry behavior.

### 14.2 Resolution protocol

1. The producer inserts or idempotently finds a `FlowWakeSignal` in a durable
   commit that is independent of dependency existence. When producer evidence
   shares the database, both facts commit together. Otherwise the producer's
   outbox owns delivery into `FlowWakeSignal`. An event-only producer
   acknowledges delivery only after signal commit.
2. The producer path attempts a fresh-transaction match against an existing
   dependency. If no dependency exists, the signal remains `Unmatched`; it is
   not discarded.
3. The wait-creation transaction inserts the dependency with the checkpoint and
   waiting transition. It may consume already-visible signals, but correctness
   does not depend on that in-transaction read.
4. Immediately after dependency commit, a required fresh-snapshot
   reconciliation matches signals for the producer key. A durable periodic
   reconciler repeats this match to close crashes on either side.
5. The condition is evaluated against authoritative producer evidence.
6. A satisfied-dependency transition discovers its candidate without locks,
   then locks and compares root, waiting step, dependency, and consumed signal
   in the Section 12 order. It atomically records the consumed signal, moves
   the waiting step to `Pending`, moves the Flow root to `Runnable`, and
   advances every affected state version.
7. Duplicate or stale notifications are idempotent no-ops.
8. The scheduler later claims the step through the normal claim protocol.

This closes the notification-before-dependency race: either the signal-side
match sees the committed dependency, the dependency-side fresh reconciliation
sees the committed signal, or the durable repair sweep sees both. There is no
state in which the only wake evidence existed only in process memory.

### 14.3 Expiry and terminal convergence

Dependency expiry is a root transition, not a step-only update. The expiry
worker discovers the due dependency without locking it, then locks the root,
all affected steps in stable order, and the dependency last, and compares their
state versions. It never enters through a dependency lock. The transaction then
commits exactly one of the compiled `ExpiryPolicy` outcomes:

- **fault path:** dependency becomes `Expired`, the waiting step becomes
  `Failed`, the precompiled fault-successor step becomes `Pending`, and the root
  becomes `Runnable` with that successor as its current step;
- **terminal failure:** dependency becomes `Expired`, the waiting step becomes
  `Failed`, and the root becomes `TerminalFailed` with a stable terminal cause;
- **quarantine:** dependency, waiting step, and root become `Quarantined` when
  producer or outcome ambiguity makes terminal failure unsafe;
- **cancellation:** dependency and step become `Cancelled` as part of the
  fenced root cancellation transition.

The dependency result, step/root states, current-step pointer, compatibility
projection, checkpoint/fault cursor, terminal cause, and all state-version
increments commit atomically. Resolution, expiry, cancellation, and producer
dead-letter finalization all acquire lifecycle rows in the Section 12 order and
race on the same versions, so exactly one transition wins and the losers re-read
the terminal fact. A repair scan treats any terminal dependency paired with a
`Waiting` root as invariant corruption and replays this root-first idempotent
finalizer; it never leaves the root orphaned.

### 14.4 Time and polling

- time waits use `Deadline`/`RunAfter`, not process timers;
- producer-backed waits prefer event notification;
- durable signal/dependency reconciliation is mandatory, bounded, and
  jittered;
- the policy defines maximum wait age and maximum reconciliation attempts;
- missing producers cannot create infinite polling.

## 15. External-effect protocol

Flow never performs a remote call while holding a business transaction.

### 15.1 Submission

An `EffectSubmit` step transaction:

1. computes the deterministic `EffectHandoffKey`, immutable envelope, link, and
   completion-dependency identities without acquiring an outbox or dependency
   row lock;
2. opens the transition transaction and locks Flow root, affected submit and
   completion steps in stable order, and the submit attempt; it then validates
   the Flow fence and verifies that any preceding bounded business-mutation
   step is already committed;
3. creates the dependency for the owner's business-completion receipt, bound to
   the precompiled successor `EffectCompletion` step and never to the
   `EffectSubmit` step;
4. only after those root→steps→attempt→dependency operations, calls the
   canonical Runtime outbox enqueue API in that same caller-owned transaction
   and writes the immutable Flow effect link. The API locks/deduplicates the
   outbox key last; it may not start with outbox ownership or call back into a
   root mutator;
5. atomically commits the outbox, link, dependency, checkpoint, the
   `EffectSubmit` attempt as `Committed`, the `EffectSubmit` step as
   `Succeeded`, the `EffectCompletion` step as `Waiting`, and the root as
   `Waiting` with `CurrentStepExecutionId` pointing to that completion step.

A retry may perform a non-locking lookup of the deterministic outbox identity,
but it must win the root/submit-step fence before acquiring that outbox row.
The finalizer follows the same root-before-outbox order, so submit retry and
finalization cannot form outbox→root versus root→outbox waits.

No Integration or other effect-owner call occurs inside this active Flow
mutation transaction, even when both logical services currently share a
process or database. After commit, a Runtime dispatcher sends the immutable
envelope to the designated owner's idempotent inbox/command-acceptance API. The
owner transaction serializes on the handoff key, rejects a durably fenced key,
or creates/finds exactly one command for `EffectHandoffKey`, records inbox
acceptance, and only then acknowledges.

A successful handoff response means durable owner acceptance, not external
business completion. Runtime marks only its outbox delivery state. The Flow
dependency and its `EffectCompletion` step remain waiting until a separately
versioned owner completion receipt satisfies the declared condition. The
owner's dispatcher performs the network call after owner acceptance commits.

`DELIVERED` is terminal only for Runtime transport delivery and normally
coexists with a pending business-completion dependency and `Waiting` Flow root.
It proves durable owner acceptance, not the remote business outcome. Only a
terminal non-delivery state such as `DEAD_LETTERED` or
`HANDOFF_REJECTED` is forbidden from coexisting with that unresolved Flow state;
the dead-letter convergence protocol below must first finalize or quarantine
the dependency/root transition.

This outbox/inbox boundary is required from the initial modular implementation
and remains the deployment boundary after service extraction. A same-process
adapter may optimize transport, but it cannot bypass the producer commit,
consumer inbox dedupe, or acknowledgement semantics.

### 15.2 Completion

The owner:

- owns remote-attempt state, retry policy, throttling, and ambiguous outcomes;
- owns the accepted handoff-to-command identity mapping;
- publishes a durable completion receipt keyed by command and outcome version;
- never requires Flow to infer completion from logs or HTTP acceptance.

When the owner is a separate logical service, its completion transaction also
writes the owner's producer outbox. Delivery into `FlowWakeSignal` follows
Section 14 and is acknowledged only after the Runtime signal commit. Flow
consumes the receipt idempotently and resumes through the dependency protocol:
the root-first resolution transaction changes the bound `EffectCompletion`
step from `Waiting` to `Pending` and the root from `Waiting` to `Runnable`.
Claiming that step consumes the immutable completion receipt and effect link,
validates the handoff key, owner, and outcome version, and advances through the
compiled success, fault, quarantine, or compensation path. An
`EffectCompletion` step has no operation that can enqueue
`flow.effect.submit`; retry or crash recovery of completion therefore cannot
resubmit the external effect.

### 15.3 Ambiguity

`OUTCOME_UNKNOWN` is not success and is not automatically retryable. The effect
owner reconciles through provider-specific lookup, idempotency support, or
operator quarantine. Flow remains waiting or quarantined according to the
declared policy.

The integration work/effect architecture remains authoritative for integration
commands and remote outcomes. Runtime-owned delivery remains authoritative for
its delivery outcomes.

### 15.4 Handoff dead-letter convergence

A `flow.effect.submit` message cannot enter the canonical Runtime outbox's
terminal dead-letter state while its Flow dependency remains silently pending.
Delivery-budget exhaustion first moves the canonical message to a non-terminal
acceptance-reconciliation disposition; absence from an owner lookup is not a
terminal fact. Outside any Runtime mutation transaction, the dispatcher calls
the trusted owner-side `RejectIfUnaccepted(TrustedRuntimeWorkScope,
EffectHandoffKey, payload digest, Runtime outbox identity)` fence with all four
exact values from the persisted canonical message. The owner transaction
validates the full Runtime work scope and serializes this request against every
in-flight or future command-acceptance transaction.

The durable owner receipt determines the finalizer:

- `Accepted` repairs the canonical outbox delivery receipt and continues
  waiting for the separate business-completion receipt;
- `Rejected` proves no command was accepted and that no late acceptance can
  commit. The Runtime finalizer may then apply the dependency's compiled
  `ExpiryPolicy`: activate its explicit fault successor and make the root
  `Runnable`, move step/root to `Failed`/`TerminalFailed`, or complete a
  cancellation;
- a lost response retries the idempotent fence; an unavailable fence, digest
  conflict, or exhausted reconciliation deadline is
  `AcceptanceUnknown`—never proven rejection—and quarantines dependency, step,
  and root.

After persisting the owner receipt, the message-family finalizer applies one
fenced Runtime transition. It discovers the candidate from the outbox without
holding an outbox lock, opens a fresh transaction, and locks Flow root, waiting
`EffectCompletion` step, dependency, and outbox/finalization rows in the
Section 12 order before updating the compatibility projection and
terminal-cause evidence. The generic Runtime outbox cannot finalize this
message family as dead without that state-store transition and may not call the
root mutator while holding an outbox-row lock. A durable repair sweep finds any
pre-finalization candidate and repeats the same root-first idempotent protocol.

Thus an owner acceptance racing rejection either commits first and is observed,
or loses to the durable rejection tombstone and cannot commit. Transport retry
exhaustion is never treated as proof that the external command did or did not
exist, and no terminal non-delivery outbox state can leave a Flow root
permanently `Waiting`. Transport-terminal `DELIVERED` is intentionally excluded
because the separately owned business completion may still be pending.

## 16. Admission, fairness, and backpressure

Admission happens before business connection acquisition.

### 16.1 Queue classes

The initial logical lanes are:

- embedded foreground;
- bounded atomic foreground;
- durable mutation;
- dependency wakeup;
- effect completion;
- maintenance and reconciliation.

These are scheduling classes, not separate fact authorities.

### 16.2 Budgets

Budgets are configurable by:

- environment;
- organization/tenant boundary;
- plan or Flow version;
- queue class;
- target object/resource class;
- execution profile.

Signals include:

- admitted and active mutation transactions;
- TiDB request-unit pressure;
- transaction and query latency;
- lock-wait and deadlock rate;
- retry rate;
- queue age;
- lease-expiry rate;
- connection-pool saturation.

The scheduler reduces new mutation admissions when pressure rises while
preserving a reserved share for cold lanes, dependency completions, and
recovery work. It does not solve overload by increasing polling or opening more
connections.

### 16.3 Numeric policy

This architecture does not hard-code initial concurrency, row, time, or fan-out
values. Each value must be established by benchmark evidence and stored in a
versioned runtime policy. Activation diagnostics show the applicable bound.

Changing a bound that affects plan eligibility creates a new policy generation
and recompiles dependent plans.

## 17. Identity, authorization, and sensitive context

### 17.1 Identity

The run stores:

- initiating principal identity;
- execution-principal mode;
- authorization-context digest or epoch;
- explicit system/background identity when applicable;
- no session cookie, bearer token, password, or credential.

### 17.2 Reauthorization

- Embedded Before-Save uses the caller's active authorization context.
- Each Durable Orchestration step reauthorizes according to its declared
  principal mode.
- A disabled principal or materially changed permission state follows a
  versioned policy: fault path, terminal failure, or explicit system handoff.
- Long-running execution does not reuse an expired interactive session.

### 17.3 Checkpoint data

Checkpoint schemas classify fields as:

- business state;
- control state;
- sensitive but permitted durable state;
- forbidden credential material.

Payload size, retention, redaction, encryption requirements, and support-view
projection are policy-controlled. Logs and audit store safe codes and digests,
not raw context by default.

## 18. Public and internal API behavior

### 18.1 Stable identity

The existing interview identifier remains the Flow-run identifier exposed to
clients. API consumers do not need a second root identity.

### 18.2 Compatibility projection

During migration:

- legacy `Running`, `Paused`, `Error`, and `Finished` values are derived from
  canonical V2 state;
- legacy current-element and context views are derived from the current plan
  step and checkpoint;
- writes through legacy scheduler paths are rejected for V2 ownership epochs;
- readback exposes the accepted plan/profile and a safe terminal cause.

The required projection is:

| Canonical V2 state | Legacy interview status |
|---|---|
| Accepted, Runnable, Running | Running |
| Waiting | Paused |
| Succeeded | Finished |
| TerminalFailed, Quarantined | Error |
| Cancelled | Error until a dedicated public cancellation value is versioned |

Whenever a dependency resolution changes the step from `Waiting` to `Pending`,
the same transition changes the Flow root from `Waiting` to `Runnable` and
updates the compatibility projection.

### 18.3 Synchronous invocation

- Embedded Before-Save remains part of the caller's synchronous mutation.
- Bounded Atomic may return its terminal result synchronously when it completes
  within the API budget.
- Durable Orchestration returns an accepted Flow-run identity and current state;
  callers observe or subscribe to completion.

Exact compatibility behavior for callers that currently assume synchronous
completion is a blocking decision before cohort cutover.

## 19. Observability and support contract

Every accepted run and attempt carries:

- immutable OrgID from TrustedOrgContext;
- FlowRunId;
- PlanId and plan digest;
- Flow version;
- execution profile;
- step execution and attempt identity;
- ownership epoch and safe lease correlation;
- trace identity;
- transaction-completion outcome;
- retry decision and delay;
- dependency/effect-owner correlation where applicable.

Required metrics:

- accepted, runnable, waiting, terminal, and quarantined run counts;
- queue age and oldest age by lane;
- claims, claim conflicts, lease expiries, and stale-fence rollbacks;
- step duration and transaction duration by plan step;
- lock wait, deadlock, rollback, and ambiguity rates;
- retry count and retry-budget exhaustion;
- checkpoint size and serialization failures;
- dependency age, wake latency, and missing-producer count;
- effect-submit and effect-completion latency;
- compatibility-projection divergence;
- old/new scheduler ownership conflicts, which must remain zero.

Required support read:

Given an authorized `TrustedOrgContext + FlowRunId`, return the immutable plan,
canonical run state, current step, current checkpoint metadata, attempt
history, dependency/effect correlation, and safe terminal cause without
reconstructing truth from logs. A `FlowRunId`-only support read is forbidden.

Alerts must be based on authoritative state and bounded windows, not on the
absence of a log line.

## 20. Compatibility and migration

Migration is additive, cohort-based, and fail-closed.

### 20.1 Phases

```mermaid
flowchart LR
    A["Additive schema and contracts"] --> B["Epoch-aware legacy claim bridge"]
    B --> C["Compile-only inventory"]
    C --> D["Shadow plan comparison"]
    D --> E["New runs in selected cohorts"]
    E --> F["Mixed-version soak"]
    F --> G["Default V2 ownership"]
    G --> H["Legacy scheduler retirement"]
    H --> I["Compatibility field cleanup"]
```

### 20.2 Additive schema

Create catalog-generation, plan, step, attempt, checkpoint, dependency,
wake-signal, and effect-link storage. Register the `flow.effect.submit` message
schema and finalizer in the existing canonical Runtime outbox; do not create
Flow-specific outbox storage. Add canonical fields and `DeleteRequestedAt` to
interviews; reuse existing `IsDeleted` for soft-delete visibility.

The physical mixed-version contract is explicit:

- `FlowInterview.OrgID` is nullable only during the pre-bridge additive-schema
  window because old binaries omit it; every newly introduced V2 durable table
  requires non-null `OrgID` from its first writer;
- `ExecutionOwner` is `NOT NULL DEFAULT 'LegacyInterpreter'`;
- `OwnershipEpoch` is `BIGINT NOT NULL DEFAULT 0`;
- `StateVersion` is `BIGINT NOT NULL DEFAULT 0`;
- `DeleteRequestedAt` is nullable;
- existing missing/null owner, epoch, state-version, and delete-request values
  are reconciled to those legacy-safe values before any reader depends on them;
- a repeatable migration reads the one authorized deployment org from trusted
  controlplane/Catalog routing, never from a Flow row or payload, and stamps
  null `FlowInterview.OrgID`; if exactly one authoritative org cannot be
  proven, those rows and the rollout are quarantined rather than guessed;
- an old writer that omits owner, epoch, state-version, and delete-request
  fields therefore still creates a non-deleted legacy-owned row at state
  version zero; while pre-bridge writers remain, the idempotent OrgID sweep
  also stamps any newly inserted null row before org-scoped enforcement;
- a V2 writer never relies on the defaults and explicitly inserts
  trusted `OrgID`, `FlowRuntimeV2`, and its nonzero admitted epoch in the first
  row write.

Schema rollout order is: add the nullable compatibility OrgID and columns with
legacy-safe defaults; repeatedly backfill OrgID from the trusted single-org
deployment authority; warm-backfill lifecycle values; exercise old-writer
inserts; deploy the bridge binary with V2 disabled; drain every pre-bridge
writer; run the final OrgID sweep; verify zero null/mismatched orgs and zero
invalid owner/epoch/state-version tuples; then make OrgID non-null, create the
org-scoped composite keys/indexes, and enable bridge readers and claim filters.
The final `FlowInterviewOrgBackfillReceipt` records the trusted authority
digest, exact OrgID, scanned/stamped/already-matching counts, zero-null count,
zero-mismatch count, schema generation, and completion time. A non-null row
whose OrgID differs from that authority is never overwritten; it blocks the
receipt and quarantines rollout for investigation.
The bridge also changes current explicit creation paths, including
`CreateInterview`, wait promotion, and `SaveFlowInterview`, to write
trusted `OrgID` plus `LegacyInterpreter/0/0` deliberately; defaults remain the
compatibility safety net for pre-bridge binaries during the additive-schema
phase.

Before final tightening, ID-only legacy reads/claims may exist only in the
current physically isolated single-org deployment, with V2 and pooled serving
disabled. They are not accepted as the future identity contract. Schema
apply/verify and backfill must be idempotent. Mixed-binary acceptance includes
an old application insert that omits the new columns, proves the stored
owner/epoch/state-version tuple is exactly `LegacyInterpreter/0/0` and
`IsDeleted=false`, and proves the trusted sweep stamps the exact deployment
OrgID before bridge enforcement.

### 20.3 Epoch-aware legacy claim bridge

The current legacy scheduler lists paused rows by legacy status/time and
rechecks only legacy status after locking. Therefore schema fields alone cannot
protect a V2 row from an old binary.

Migration requires a separate bridge release before any V2 admission:

1. The bridge release contains no enabled V2 admission path.
2. During rolling coexistence with pre-bridge binaries, every run remains
   legacy-owned, the new columns provide only compatibility defaults, and
   state-version CAS enforcement remains disabled. No V2 row or owner transfer
   may exist in this interval. ID-only legacy reads/claims remain confined to
   the physically isolated single-org database and pooled serving is forbidden.
3. The rollout controller drains every pre-bridge process, request, scheduler
   callback, and active FlowInterview write transaction, prevents a pre-bridge
   binary from restarting, and records authoritative zero-old-writer evidence.
4. After the drain, the controller reruns trusted OrgID backfill, fails closed
   on any unprovable/mismatched row, verifies zero nulls, tightens OrgID to
   non-null, and installs `(OrgID, identity)` foreign keys, uniqueness, claim,
   CAS, and repair indexes. This schema receipt is a prerequisite for the write
   protocol.
5. Only after both the drain and org-schema gates pass may the bridge enable
   `FlowInterviewWriteProtocolGeneration=1`. From this point the typed
   `FlowInterviewStateStore` is the sole writer, every API requires
   `TrustedOrgContext`, every mutation increments `StateVersion`, and generic
   table DML or an ID-only repository call fails closed.
6. Legacy due-list queries require trusted OrgID and filter
   `(OrgID, ExecutionOwner=LegacyInterpreter, OwnershipEpoch=0)`.
7. `ClaimPausedInterview` locks by `(OrgID, FlowRunId)` and rechecks trusted
   org, status, execution owner, ownership epoch, and state version.
8. Stale-claim recovery, direct legacy resume, public delete/cancellation,
   support reads, and every legacy interview mutator apply the same
   org/owner/epoch/state-version predicate and locked-row recheck.
9. A boundary/inventory test proves there is no ID-only FlowInterview
   read/claim/mutation/repair path before pooled serving or V2 admission.
10. The bridge release and write-protocol generation are fully rolled out and
   accepted before a later release can enable V2 cohorts.

The bridge binary may contain the typed store during coexistence, but it cannot
claim CAS protection until the drain gate proves there is no unversioned
pre-bridge writer. This separates additive-schema compatibility from
single-writer enforcement and prevents an old update from changing state
without advancing `StateVersion`.

During the later V2 rolling deployment, both the bridge binary and the V2
binary understand the owner gate, so projecting V2 `Waiting` to legacy
`Paused` is read-compatible but not claimable by the legacy interpreter.

The minimum rollback binary is the accepted bridge release. Rolling back to a
pre-bridge binary while any V2-owned row exists is prohibited and must be
rejected by the release gate.

### 20.4 Compile-only inventory

Compile every active Flow without changing execution. Produce:

- selected profile;
- rejection reason;
- unknown action/subflow capabilities;
- loop/fan-out proof;
- estimated step boundaries;
- legacy behavior relied upon;
- migration cohort.

No Flow silently falls back to the legacy interpreter after its V2 ownership
epoch is enabled.

### 20.5 Shadow comparison

For safe read-only or replayable fixtures:

- compare legacy element trace with compiled plan trace;
- compare outputs and fault routing;
- compare declared and observed resource classes;
- compare legacy interview projection with proposed canonical state;
- do not duplicate business mutation or external effect.

### 20.6 Ownership epoch

Each run is accepted under exactly one execution ownership epoch:

- legacy owner/epoch: bridge-protected interpreter/scheduler owns writes;
- V2 owner/epoch: FlowStepExecution scheduler owns writes.

The epoch is immutable per run. A deployment may operate both epochs, but no run
may switch owners in place.

No V2 cohort can be enabled until the bridge release acceptance proves every
active legacy claim/resume/stale-recovery path is owner-aware.

### 20.7 Cohort order

Recommended order:

1. metadata-only compile and diagnostics;
2. pure autolaunched flows;
3. Embedded Before-Save flows;
4. small Bounded Atomic flows;
5. time waits without external effects;
6. producer-backed waits;
7. external-effect flows;
8. large and complex subflow/loop cohorts.

Each cohort requires its own evidence pack and rollback gate.

### 20.8 Existing paused interviews

V2 uses a drain-only policy for existing runs. Every legacy accepted, running,
paused, retrying, or otherwise non-terminal interview keeps
`ExecutionOwner=LegacyInterpreter` and its original ownership epoch for its
entire lifetime.

- No existing interview is converted in place to a V2 checkpoint or step.
- No transition assigns a V2 epoch to a legacy run.
- The bridge scheduler continues to execute and resume legacy-owned runs.
- New invocations of a migrated Flow version may use V2, but they receive new
  FlowRun identities.
- A legacy wait that cannot converge follows its existing producer, expiry,
  cancellation, or operator policy; it is not repaired by changing execution
  owner.
- Legacy scheduler retirement requires authoritative readback of zero
  non-terminal legacy-owned runs.

Any future in-place conversion requires a separate architecture decision and
an atomic old-owner fencing/handoff protocol. It is not part of this design.

### 20.9 Rollback

Rollback means stopping admission of new V2 runs and returning new runs to a
compatible legacy cohort. It does not change ownership of existing V2 runs.

Before application rollback:

- preserve binaries capable of draining both epochs;
- verify schema compatibility;
- disable only new V2 admission;
- keep V2 scheduler capacity for existing V2 runs;
- reconcile ownership conflicts and quarantined outcomes.

The rollback target must be bridge-capable. A deployment controller must reject
a pre-bridge target while V2 ownership is possible.

No rollback procedure uses manual production SQL as the normal control plane.

## 21. Implementation slices

Each slice is independently reviewable. A slice cannot claim completion until
its listed exit evidence exists.

| Slice | Owner/release boundary | Deliverable | Depends on | Exit evidence |
|---|---|---|---|---|
| 0. Architecture authority | NexusCRM | This contract, open decisions, child-issue map | None | Design approval bound to document revision |
| 1. Current-tree inventory | NexusCRM | Compiler input inventory, action/subflow catalog, active-flow classifier | 0 | Complete compile-only report; unknowns named |
| 2. Neutral capability contract | Neutral L0 contract plus consumers | Versioned action capability types, sealed catalog builder, stable binding fingerprints, legacy registry adapters, ownership/conflict policy, and conformance tests | 1 | Contract release and consumer pins; compiler, preview, and dispatcher observe the same catalog/entry/binding identities; no reverse dependency |
| 3. Compiler and immutable plan | Automation and metadata | CFG compiler, profile selection, exact RuntimeWorkBinding/trigger binding, catalog-generation reference, per-step binding pins, digests, diagnostics, plan storage | 2 | Golden plans, deterministic digest, metadata/trigger/binding mismatch rejection, activation fail-closed |
| 4. Durable schema | Persistence and control plane | Additive legacy-safe interview/delete-request fields and catalog-generation/plan/step/attempt/checkpoint/dependency/wake-signal/link storage; canonical Runtime outbox message registration | 3 | Clean install, upgrade, old-writer defaults, verify, catalog/metadata-view retention, and rollback-compatible schema |
| 5. State-store protocol and legacy bridge | Automation and persistence | Pre-bridge drain gate, table-level write guard, typed lifecycle/delete CAS transitions, claims, leases, fencing, compatibility projection, and owner-aware legacy list/claim/resume/stale recovery | 4 | Zero pre-bridge writers before CAS; bridge protocol accepted before V2 admission; zero generic FlowInterview writers; TiDB concurrency and stale-worker tests |
| 6. Embedded Before-Save | Automation and records | Compiled embedded executor and strict capability/budget enforcement | 3, 5 | Output parity, no extra root transaction, latency evidence |
| 7. Bounded Atomic | Automation and persistence | One-step executor, resource plan, whole-step retry, typed outcomes | 3, 5 | Mixed-path TiDB lock suite and ambiguity fault injection |
| 8. Durable orchestration core | Automation | Multi-step checkpoint executor, explicit fault routing, resume | 3, 5 | Process-kill recovery at every transition |
| 9. Dependency waits | Automation and producer owners | Durable dependency and wake-signal authorities, deadlines, matching, and repair reconciliation | 8 | Notification-before-dependency, crash-window, duplicate/stale wake, and missing-producer tests |
| 10. External-effect bridge | Automation plus effect owners | Canonical Runtime outbox message/link/dependency commit, post-commit owner inbox acceptance/rejection fence, dead-letter convergence, and completion-receipt consumption | 8, 9 | Acceptance-vs-rejection race, producer/consumer crash-window, dead-letter, and outcome-unknown tests; no competing queue, cross-service call, or network in transaction |
| 11. Admission and observability | Automation, persistence, operations | Lanes, budgets, fairness, pressure signals, support read | 5-10 | Load test, cold-lane progress, complete lineage |
| 12. Shadow and cohort cutover | Control plane and release owner | Inventory report, ownership epochs, cohort flags, reconciliation | 6-11 | Cohort evidence pack and zero dual ownership |
| 13. Legacy retirement | Automation and API host | Stop old admissions, drain legacy-owned runs without owner conversion, and remove legacy write authority | 12 | No non-terminal legacy-owned runs; consumer read migration |

### 21.1 Slice rules

- A slice changing a public neutral contract releases that contract before
  downstream adoption.
- A source merge is not a release, deployment, or production acceptance.
- Every schema slice includes clean install, upgrade, verify, and mixed-binary
  tests.
- Every scheduler slice includes stale-worker and duplicate-delivery tests.
- No slice adds product- or tenant-specific literals to L0.
- Compatibility code has a retirement condition and observable usage counter.
- Old blocker lists are superseded when a new reviewed head or document
  revision becomes authoritative.

## 22. Verification matrix

### 22.1 Compiler and authoring

- deterministic plan from identical inputs;
- changed action capability changes plan identity;
- changed handler implementation version changes binding fingerprint, catalog
  generation, and plan identity;
- runtime binary binding-fingerprint mismatch rejects claim before mutation;
- a plan cannot outlive or silently rebind its referenced catalog generation;
- a catalog generation, entry, or handler binding from another org,
  installation, capability, or RuntimeWorkBinding cannot satisfy the plan's
  composite foreign key or runtime lookup even when generation IDs and entry
  digests collide;
- Runtime metadata-view or trigger-subscription change invalidates the activation
  binding and changes plan identity before V2 dispatch;
- V2 trigger admission uses the exact request-bound `RuntimeWorkBinding` and
  never a live repository or legacy global-cache fallback;
- recursive subflow rejection;
- bounded and unbounded loop classification;
- unsafe atomic request rejection;
- external-effect boundary insertion;
- every external-effect boundary is one validated
  `EffectSubmit` → `EffectCompletion` pair with no self-wait or return edge;
- stable step keys across non-semantic metadata formatting;
- diagnostic includes the exact transitive blocker;
- activation and setup/apply verification use the same compiler result.

### 22.2 State and fencing

- two workers claim the same candidate;
- lease expires before execution;
- lease expires during pure compute;
- lease expires during mutation transaction;
- stale worker reaches final fence after replacement worker;
- expired-lease recovery atomically fences the old attempt, returns the step to
  `RetryScheduled` with `RunAfter` no earlier than recovery time plus its
  immutable recovery backoff floor, and returns the same root/current-step
  pointer to `Runnable`; pre-due claim fails and due claim succeeds without a
  stranded `Running` root;
- worker dies after business rollback but before the normal `RolledBack`
  transition; fenced recovery preserves the precommitted run-after floor and
  cannot schedule an immediate retry;
- proven rollback atomically records `RolledBack`, schedules the step with
  `RunAfter`, and returns the same root/current-step pointer to `Runnable`;
  pre-due claim is rejected and due claim succeeds without a stranded
  `Running` root;
- duplicate scheduler delivery;
- pre-bridge old-writer inserts with null OrgID are stamped only from the
  trusted single-org deployment authority; ambiguous authority blocks rollout;
- final migration proves zero null/mismatched FlowInterview OrgID values,
  installs org-scoped keys, and rejects every ID-only legacy
  read/claim/mutation/repair entry point before V2 admission;
- process death after claim, after begin, before fence, after fence, and after
  commit response;
- two terminal updates race on one `Started` attempt and exactly one fenced
  compare-and-swap wins;
- a terminal attempt outcome cannot be overwritten;
- root and step projection never diverge;
- generic CRUD, bulk, upsert, and physical delete reject all FlowInterview
  writes;
- authorized public `DeleteFlowInterview` is idempotent, hides the run through a
  canonical `IsDeleted` soft delete, fences non-terminal cancellation, and
  retains durable lineage;
- delete racing a claimed/running step and its final business commit serializes
  on root `StateVersion`: when delete wins, root/step cancellation versions
  make the worker's root/step/attempt CAS fail and all business/audit/outbox/
  checkpoint writes roll back; when commit wins, delete observes the committed
  advancement and does not relabel it as rolled back;
- claimed/running/waiting active steps enter `CancelRequested`, stale workers
  cannot reschedule them, and cancellation cleanup alone enters `Cancelled`;
- for claimed/running work, the delete-winning root→step→attempt transaction
  moves exactly one `Started` attempt to terminal `Cancelled`; replacement
  recovery alone uses `Fenced`, and neither terminal outcome can be rewritten;
- point read, `Get`/`List`, `QueryWithSpec`, relationship query, and support
  summary all consume the same `IsDeleted` visibility fact;
- no Flow-specific visibility/tombstone field exists;
- physical retention refuses a non-terminal run or any live dependent/outbox
  reference;
- every legacy interview mutator includes owner/epoch/state-version predicates;
- old and V2 schedulers cannot both own one run;
- legacy due-list excludes a V2-owned interview projected as `Paused`;
- legacy locked claim rechecks owner/epoch and rejects a V2 row;
- legacy stale recovery and direct resume reject V2 ownership;
- V2 admission is impossible on the pre-bridge release;
- rollback to a pre-bridge binary is rejected while V2 ownership is possible.

### 22.3 Transactions and locks

- Flow and ordinary record write contend on the same business row;
- two Flow plans touch parent and child resources in different metadata orders;
- record mutation and history-sequence reservation contend;
- retry discovers an additional allowed resource;
- resource discovery exceeds declared bound;
- deadlock victim rolls back and retries from the same checkpoint;
- resolution, expiry, cancellation, dead-letter finalization, and repair all
  lock root before stable-ordered steps, attempts, dependencies, and
  signal/outbox disposition rows; dependency expiry racing cancellation has no
  reverse-order wait;
- a business-mutating worker acquires declared business/parent and history
  locks before Flow root, while lifecycle-only cancellation starts at root and
  never acquires business locks; their real-TiDB race has no root→business
  reverse edge;
- commit response is lost;
- rollback response is lost;
- connection is severed during commit;
- audit/outbox/checkpoint failure rolls back business changes.

Tests must run against real TiDB for locking, deadlock, and ambiguity behavior;
an in-memory database is insufficient evidence.

### 22.4 Durable waits

- time wait survives process and deployment restart;
- notification arrives before dependency commit;
- producer commits a wake signal and crashes before dependency matching;
- dependency commits and crashes before fresh reconciliation;
- periodic reconciliation matches a previously unmatched signal;
- identical producer kind/key/token in two same-org installations,
  capabilities, or RuntimeWorkBinding digests cannot dedupe, match, consume, or
  disclose the other work scope's signal;
- notification arrives twice;
- stale wake version arrives after a newer one;
- producer is missing;
- producer is deleted while waiting;
- deadline and notification race;
- dependency terminal-failure expiry atomically moves the step and root out of
  `Waiting`;
- dependency fault-path expiry atomically activates the compiled successor and
  makes the root `Runnable`;
- dependency quarantine expiry atomically quarantines dependency, step, and
  root;
- resolver crashes after recording resolution but before scheduler claim.

### 22.5 External effects

- canonical Runtime outbox message, link, dependency, checkpoint,
  `EffectSubmit` success/committed attempt, waiting `EffectCompletion`, and
  Flow root/current-step transition commit together;
- `EffectSubmit` retry and outbox finalization both discover without lower-row
  locks and acquire Flow root before the deterministic outbox row; their race
  has no outbox→root edge;
- no Flow-specific outbox table, claimer, state machine, or operator read path
  exists;
- no owner command exists before the Runtime transaction commits;
- Runtime dispatcher crashes before owner delivery, after owner durable
  acceptance, and before marking its outbox delivered;
- owner inbox deduplicates repeated `EffectHandoffKey` delivery to one command;
- dispatcher crashes before and after remote submission;
- provider accepts request but response is lost;
- duplicate receipt;
- receipt arrives before Flow scheduler observes wait;
- completion receipt activates only the bound `EffectCompletion` step; crash,
  duplicate wake, lease recovery, and retry of that step never execute
  `EffectSubmit` or enqueue another external-effect message;
- effect owner quarantines unknown outcome;
- proven handoff non-acceptance applies the compiled dependency failure policy;
- acceptance-unknown dead-letter atomically quarantines the dependency, step,
  and root;
- owner acceptance and `RejectIfUnaccepted` race on one durable unique key;
  exactly one accepted command or rejection tombstone wins;
- the same handoff key under a different org, App installation, Runtime
  capability, binding digest, payload digest, or outbox identity quarantines
  against the existing fence row and cannot bypass uniqueness;
- dead-letter reconciliation passes the persisted complete trusted Runtime work
  scope, handoff key, payload digest, and Runtime outbox identity; omitting or
  changing any value fails closed;
- a delivery arriving after owner rejection cannot create `ExternalCommand`;
- command absence without a durable rejection receipt quarantines rather than
  failing or cancelling the Flow;
- terminal transport `DELIVERED` may coexist with a pending completion
  dependency and `Waiting` root, while terminal non-delivery
  `DEAD_LETTERED`/`HANDOFF_REJECTED` cannot coexist with them;
- Flow cancellation after remote effect commit;
- explicit compensation path;
- no remote call occurs inside a Flow business transaction.

### 22.6 Security

- every Flow root, step, attempt, checkpoint, dependency, wake signal, Runtime
  outbox message, claim/CAS, and repair read is scoped by the immutable `OrgID`
  from `TrustedOrgContext`; dependency, wake-signal, and cross-boundary message
  identity additionally use the complete persisted `TrustedRuntimeWorkScope`;
- identical record IDs, producer keys/tokens, and handoff payloads in two orgs
  cannot cross-match, claim, dedupe, wake, repair, or disclose state;
- two installations/capabilities in the same org cannot share or accept an
  effect handoff, even when payload and owner kind are identical;
- two installations/capabilities/binding digests in the same org cannot share,
  dedupe, or consume a dependency wake signal even when producer key and token
  are identical;
- payload-supplied org identity is rejected and cannot override trusted org
  routing;
- payload-supplied App installation, Runtime capability, or binding digest
  cannot override the persisted trusted Runtime work scope;
- principal disabled while waiting;
- permission/FLS/RLS changes between steps;
- system-mode step requires explicit declared identity;
- checkpoint rejects credential material;
- support read redacts sensitive context;
- audit and logs contain safe codes rather than raw payloads.

### 22.7 Performance and capacity

- before-save latency compared with current baseline;
- bounded atomic transaction duration and lock footprint;
- durable step throughput at configured admission;
- queue fairness under one hot plan/tenant;
- database pressure reduces admissions before connection exhaustion;
- cold dependency completions continue under mutation pressure;
- checkpoint payload and index growth;
- paused/waiting population claim-query behavior;
- retry storm containment.

### 22.8 Migration

- clean install and every supported upgrade path;
- old application inserts an interview without owner/epoch/state-version
  or delete-request columns against the additive schema and reads back
  `LegacyInterpreter/0/0` with `IsDeleted=false`;
  the trusted backfill stamps its exact deployment OrgID before enforcement;
- final backfill proves zero null/ambiguous OrgID values before NOT NULL and
  org-scoped key activation;
- every bridge creation path explicitly writes trusted OrgID plus
  `LegacyInterpreter/0/0`;
- new application with legacy epoch runs;
- mixed pre-bridge/bridge binaries prove additive-schema compatibility while
  V2 and CAS enforcement remain disabled;
- authoritative fleet and active-transaction evidence proves zero pre-bridge
  writers before `FlowInterviewWriteProtocolGeneration=1`;
- a pre-bridge binary cannot restart after the write-protocol gate;
- no ID-only FlowInterview read, claim, mutation, support, or repair path
  remains at the bridge/V2 admission gate;
- public delete preserves its authorized/idempotent response and subsequent
  not-found projection through the shared `IsDeleted` read path without
  physically deleting durable lineage;
- paused legacy drain without in-place owner/epoch conversion;
- new admission disable without abandoning V2 runs;
- compatibility projection read by existing clients;
- reconciliation reports zero dual ownership.

## 23. Release and acceptance gates

### 23.1 Source gate

Evidence:

- reviewed head SHA;
- approved design revision;
- required unit, integration, real-TiDB, and fault-injection results;
- public-core and dependency-boundary checks;
- schema clean-install and upgrade proof.

### 23.2 Release gate

Evidence:

- exact released module versions;
- consumer pins;
- final release tuple or equivalent immutable assembly;
- schema/control-plane artifacts included;
- no local replacement or untracked runtime dependency.

### 23.3 Staging gate

Evidence:

- live runtime reports expected versions and plan compiler generation;
- schema verification passes;
- selected cohort has V2 ownership enabled;
- synthetic contention, wait, crash, and effect cases pass;
- support read reconstructs every synthetic run from authoritative state;
- zero dual ownership and zero unexplained projection divergence.

### 23.4 Production gate

Evidence:

- exact production runtime and ownership policy;
- bounded cohort enablement timestamp;
- queue age, lock wait, retry, ambiguity, and lease-expiry telemetry;
- business-record and Flow-run readback;
- rollback/admission-disable readiness;
- explicit distinction between “accepted,” “processed,” “committed,”
  “externally completed,” and “business reconciled.”

No PASS statement may rely only on a merged pull request, successful build,
HTTP acceptance, processed work item, or emitted log.

## 24. Blocking decisions

The following decisions must be recorded before the dependent slice starts.

| Decision | Owner | Blocks |
|---|---|---|
| Platform-wide canonical resource-class order and versioning | Persistence architecture | Bounded Atomic implementation |
| Exact numeric profile and admission budgets from benchmarks | Runtime and operations | Cohort activation |
| Synchronous API compatibility for flows reclassified as durable | API and product contract | Durable cohort cutover |
| Before-save success evidence and sampling policy | Audit and records | Embedded cohort cutover |
| Checkpoint size, retention, encryption, and sensitive-field policy | Security and persistence | Durable schema production use |
| Principal reauthorization policy for long-running flows | Auth and permissions | Wait/effect cohort cutover |
| Cancellation and explicit compensation authoring semantics | Automation product contract | External-effect cohort |
| Legacy drain completion and non-convergent-run terminal policy | Release owner | Legacy scheduler retirement |
| Compiler and plan compatibility window across rolling binaries | Automation and release architecture | Mixed-version rollout |

These are decision gates, not permission to leave execution ownership ambiguous.

## 25. Risks and mitigations

| Risk | Mitigation |
|---|---|
| Compiler and runtime semantics diverge | Execute only immutable normalized plans; shared capability contract; golden trace tests |
| Durable storage adds latency | Keep Embedded Before-Save transient; persist only required roots/steps; benchmark indexes |
| New scheduler duplicates work | Lease token plus final in-transaction fence; idempotent step identity |
| External effect is duplicated | Owner-specific idempotency and ambiguity protocol; Flow never blind-retries outcome unknown |
| Metadata change expands a compiled lock graph | Plan pins exact RuntimeWorkBinding and trigger digest; changed view invalidates activation |
| Flow-specific effect queue becomes a second authority | Register one message family in the canonical Runtime outbox; forbid another table/claimer |
| Delete races a business commit or dead-letter orphans durable work | Root/step/attempt cancellation CAS plus canonical IsDeleted and retention guards; owner rejection fence and atomic dependency/outbox terminal convergence |
| Old and new schedulers race | Immutable ownership epoch per run; dual-owner metric is a release blocker |
| Checkpoint contains sensitive data | Schema classification, payload limits, redaction, encryption policy, no credentials |
| Atomic profile becomes an escape hatch | Compiler proof and activation fail-closed; policy generation in plan digest |
| Retry storm worsens database pressure | Durable jittered retry, admission before connection, tenant/plan budgets |
| A new resource path reverses lock order | Persistence-owned order, declared resource keys, mixed-path real-TiDB tests |
| Migration never retires compatibility code | Usage metrics, cohort exit criteria, explicit retirement slice |
| “Durable” is mistaken for end-to-end business success | Separate run, connector, effect, and business reconciliation facts |

## 26. Definition of done

The Flow runtime redesign is complete only when:

- every active Flow version compiles to a version-bound immutable plan or has an
  explicit activation blocker;
- every plan and trigger admission is bound to one exact
  `RuntimeWorkBinding` and `TriggerBindingDigest`;
- every plan-to-capability-catalog reference and runtime handler lookup matches
  that same org/install/capability/binding scope;
- every transitive action/subflow has a versioned capability descriptor;
- all new runs use an accepted execution profile;
- no transaction spans a wait, remote call, scheduler yield, or unknown
  fan-out;
- Flow lifecycle, step state, attempts, checkpoints, dependencies, connector
  work, and external effects each have one authority;
- stale-worker and ambiguous-commit fault injection passes;
- mixed Flow/non-Flow real-TiDB lock tests pass;
- admission and fairness prevent one hot cohort from exhausting all execution
  capacity;
- compatibility projections have zero unexplained divergence;
- no run is writable by both legacy and V2 schedulers;
- no terminal dependency or terminal non-delivery Runtime outbox handoff
  (`DEAD_LETTERED`/`HANDOFF_REJECTED`) can coexist with a `Waiting` Flow root;
  transport-terminal `DELIVERED` may coexist while the separately owned
  business-completion dependency is pending;
- public delete is an authorized canonical-`IsDeleted`/cancellation command;
  its root-version/active-step fence prevents post-delete worker commits, and
  physical retention cannot remove live lineage;
- existing legacy runs reach terminal state under the legacy owner; no run is
  converted in place;
- release, staging, and production gates are independently satisfied;
- the source-of-truth design and linked issue identify the accepted document
  revision, reviewed head, and remaining open decisions;
- legacy scheduler write authority is removed only after its active population
  reaches zero.

## 27. Relationship to other architecture authorities

This document owns Flow execution plans, Flow-run/step/checkpoint/dependency
state, transaction boundaries, and Flow scheduler migration.

It defers to:

- the [Connector root architecture](connector-root-architecture.md) for
  connector ingress work ownership;
- the [integration work/effect ownership design](issue-3725-integration-work-effect-ownership.md)
  for integration commands, remote attempts, and ambiguous outcomes;
- the [serviceification target architecture](nexuscrm-serviceification-target-architecture.md)
  for process/service boundaries;
- persistence architecture for the platform-wide lock resource order;
- control-plane architecture for idempotent schema install, upgrade, and verify.

Where an older Flow issue or comment describes `WorkItem/FlowRun` as one shared
execution state, this document supersedes that wording: the facts are
correlated, but their owners remain separate.
