Loom

Type to search across the guides, specification and decision records.

↑↓ navigate ↵ open esc close Pagefind

Decision records

ADR 0023 — K11: flow-code versioning for in-flight executions

  • Status: Accepted (implemented 2026-07-10 — K11.1 flowContentHash recorded as additive flowHash on EXECUTION_STARTED (schema 1.4.0, folds to variables._flowHash) with refusal via FlowCodeMismatchError on resume/recovery/restore, legacy logs unchecked (G2); K11.2 version-aware registry (idempotent same-content re-register, hard error on same id+version with different content, pinned resumption — an in-flight execution resumes on whichever registered version’s hash matches its recorded one); K11.3 resumeAs(executionId, flowId, event, { strategy: "replay-check" | "operator-ack" }) with the durable EXECUTION_MIGRATED fact (schema 1.5.0) that re-points _flowHash / _migratedTo, replay-check = deploy-sim’s step-orphan rule (FlowMigrationCheckError); K11.4 deploy-check census — flow-code-drift breaking issue for LIVE executions whose recorded hash would change, plus the K9 idempotency advisory (idempotencyAdvisory, runs when the config module exports its EffectRuntime). Compiler chunk-hash enrichment of the fingerprint remains a future option. Amendments below applied.)
  • Date: 2026-07-10
  • Requirements: K11 in the kernel-hardening requirements (loom/docs/requirements/2026-07-10-kernel-hardening-k7-k12-t1-t2.md) — priority P1

Context

K5 pins the event schema and @loom/deploy-sim (ADR 0008) detects divergence, but nothing remediates it: an execution suspended for three days is stranded when new flow code deploys — resuming it against changed code silently folds a log the new code never wrote. Long-lived executions are Loom’s raison d’être, so this is a kernel obligation, not an application concern. Long-lived executions plus continuous deploy collide immediately in production.

Decision

Make version skew between a suspended execution and deployed flow code detectable, pinnable, and — where compatible — migratable, never silent:

  • EXECUTION_STARTED (the requirement’s “RUN_STARTED” does not exist) records the flow’s content hash — which must be built: the compiler manifest has only version + per-step chunk hashes today. Resume and crash recovery compare recorded vs. registered hash and never silently proceed across a mismatch (K11.1 as amended).
  • Pinned resumption: the registry may hold multiple versions of a flow (flow$({ id, version }) already exists); an in-flight execution resumes on the version it started with (K11.2).
  • Where pinning is impossible, an explicit migration verdict API: resumeAs(executionId, newFlowId, { strategy: "replay-check" | "operator-ack" }), where replay-check proves the new code folds the existing log identically (reusing the deploy-sim engine) before resuming; any migration is a durable EXECUTION_MIGRATED fact (K11.3).
  • loom deploy check (ADR 0014) enumerates live suspended executions whose flow hash would change, making “drain or pin before deploy” a CI-enforceable gate (K11.4).

Acceptance criteria

Deploying changed flow code with a suspended execution in flight: (a) resume without pinning fails loudly; (b) pinned resume completes on old code; (c) replay-check migration succeeds when the change is fold-compatible and refuses when not.

Consequences

  • The kernel’s core promise — executions that outlive processes — extends to outliving deploys, the case production hits first.
  • New durable fact EXECUTION_MIGRATED enters the K5-governed schema (additive → MINOR); the fold reference must be updated.
  • The flow registry becomes version-aware; deploy tooling gains a live-execution census. Unwired (no version pinning, no migration API use), resume behavior is byte-identical for matching hashes (G2) — the only new behavior is refusing a mismatch that today proceeds silently, which is the point.

Amendments (code-grounded evaluation, 2026-07-10)

Adopted per the K11 evaluation (loom/docs/requirements/2026-07-10-kernel-hardening-evaluation.md) (verdict: Accept with amendments):

  • A1 — event + hash corrected (applied inline above): target is EXECUTION_STARTED (payload extension under the K5 payload-extension rule; folds treat absence as legacy — logs without a hash resume exactly as today, G2). The hash is computed over compiler-normalized manifest content (step graph + step chunk hashes), never raw bundler output — non-reproducible builds would flap the hash every rebuild, refuse every resume, teach operators to override, and reduce the check to theater. The mismatch error presents the three exits (pin / resumeAs / drain).
  • A2 — recovery path, pulled forward: the check also guards crash-recovery / rehydration, not just operator resume() — today a restart-after-deploy folds an old log with new code silently, with no operator action. This slice (record hash at start; compare on recovery + resume) is the first K11 work and P0-adjacent in risk.
  • A3 — registry semantics: the registry is today a Map<flowId, …> that silently overwrites on re-registration. It becomes version-aware (flowId → version → flow); re-registering the same id+version+hash stays silent (idempotent — dev watch-mode must not spam); same id+version with a different hash is an error.

Source: docs/adr/0023-k11-flow-code-versioning-in-flight-executions.md in the loom repository.