Skip to content

Give the top-level workflow run its own durable identity - #130

Open
alexanderguy wants to merge 9 commits into
mainfrom
intr-358-dissolve-the-workflow_deployment-cache-and-re-anchor-onto
Open

Give the top-level workflow run its own durable identity#130
alexanderguy wants to merge 9 commits into
mainfrom
intr-358-dissolve-the-workflow_deployment-cache-and-re-anchor-onto

Conversation

@alexanderguy

Copy link
Copy Markdown
Member

Summary

  • The top-level workflow run carries its own hub-minted run_<hex> id, threaded from the hub through the mail-inbound frame and the claim-check envelope and read by the supervisor. The run id no longer derives from the deployment mail address, and the address-as-run-id derivation is removed.
  • The deployment anchor holds its own deployed status, distinct from the run lifecycle running. Reconnect key-lookup, signal correlation, and the reservation, trigger, and approval gates treat deployed as live through one shared run-state classifier.
  • The workflow_run_dispatch row carries the run id as an explicit column. The sidecar restore record — its type, file, and functions — names the run it serves, while keeping the persisted deployment.json filename so records written by an earlier sidecar still restore. The deploy-list route is /workflows/deployments.
  • A durable claim-check inbox entry persisted before the run id was a required field decodes by deriving the run id from the address, so an in-place upgrade recovers legacy inbox mail rather than wedging.

Verification

  • The full pipeline passes: make all (build, lint, and both test suites) exits 0.
  • An in-place hub-and-sidecar upgrade over live deployments is handled: existing on-disk restore records keep their filename and restore, and claim-check inbox entries written before the run id existed decode via the address.
  • Migrations 0079, 0080, and 0081 backfill the dispatch row's run id, the is_top_level marker, and the deployed anchor status, keyed on the legacy invariant that a top-level run's id equalled its address.
  • Whole-branch review is clean.

Closes INTR-358
Closes INTR-359

The old post-reconnect block fired a second distinct trigger and waited
for RunCompleted under a runId hard-coded equal to the first. That
re-observed the first run's own terminal and passed without exercising
the real behavior: the deployment's one run is already terminal, so the
second trigger is rejected with a workflow_run_terminal receipt and no
new run starts. Assert that receipt and a single RunStarted/RunCompleted
instead, read through the supervisor-minted run id rather than the mail
address.
The sidecar's on-disk restore record persists a single workflow run's
sources, session id, and hub key, but carried "deployment" naming left
over from the dropped workflow_deployment table. Rename the type, file,
and functions to name the run they serve.

The persisted record keeps its deployment.json filename under
workflow-runs/<deploymentId>, so a record written by an earlier sidecar
is still found at boot. Only the in-memory naming moves; the on-disk
format and the behavior are unchanged.
The top-level run id is re-derived independently in each consumer from
the deployment mail address. Carry it as an explicit column on the
workflow_run_dispatch row instead, so the reconcile path reads one
authoritative value rather than re-deriving it. The value is unchanged
(still the mail address), so delivery is byte-for-byte identical; this
only removes the independent re-derivation.
The top-level run borrowed the deployment mail address as its id, so
the run and the deployment were one identifier. Give the run its own
run_ id: thread the id from the hub through the mail.inbound frame and
the claim-check envelope, with the supervisor reading it instead of
re-deriving; and mint it once per deployment under the anchor-run lock
(read-or-mint, keyed by a new is_top_level marker whose partial unique
index fails a double-mint loud). Remove deriveWorkflowRunId, the
address-as-run-id function, so no code path can re-derive the old id.
The deployment keeps its own id, address, public key, and running gate,
so reconnect is unchanged.
The deployment anchor row borrowed the run status "running", so
"running" meant both "this deployment is live and reconnectable" and
the run-lifecycle "running" -- two concepts in one value. Split them:
the anchor is born "deployed" (a deployment-phase status), while the
top-level run and its children keep running and the terminal states.
The liveness gates -- the reconnect key lookup, the anchor FOR UPDATE
lock, the trigger and approval paths -- now treat "deployed" as live,
funnelled through the shared run-state classifier so the constraint
lives in one layer. A backfill flips existing live anchors.
The route lived at /workflows/instances but deploys and lists workflow
deployments, not instances -- a leftover of the retired agent-instance
vocabulary. Move it to /workflows/deployments so the path names what it
returns. The only consumers are the in-repo hub-client and admin-UI, so
there is no external contract to keep and no compatibility alias.
The anchor-locked read-or-mint of a deployment's top-level run was
copy-pasted across the two mail-route reservation transactions and the
sidecar mail-trigger materializer. A change to how the addressable run
is resolved -- the is_top_level discriminator or the lock ordering --
had to be applied in all three or behavior would fork between them.
Resolve it in one place so the three paths cannot drift.
The durable claim-check inbox is read across a hub upgrade, so the
decode boundary can meet an entry written before the run id became a
required envelope field. A strict decode rejects it and the in-flight
trigger wedges. Derive the missing run id from the entry's address,
the value a deployment's run was addressed by before it had its own
id, so a legacy entry recovers instead of wedging.
@alexanderguy

Copy link
Copy Markdown
Member Author

Self-Review (code-review skill)

Whole-branch review is clean — all findings dispositioned, no unfixed defects.

Two design decisions are intentional and reviewed:

  • The exclusive (durable-enqueue) and non-exclusive (synchronous-route) mail-route reservation transactions stay separate rather than collapsing behind a flag — they are distinct delivery contracts, not drifted duplication.
  • The HTTP trigger stages grants ahead of the anchor lock and relies on commitRunGrants dedup; a raced first-trigger discards the staged snapshot, never producing wrong output.

@linear

linear Bot commented Aug 11, 2026

Copy link
Copy Markdown

INTR-358

INTR-359

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant