feat(buzz-db): durable workflow agent delivery state machine (Node C) - #6832
Draft
loganj wants to merge 2 commits into
Draft
feat(buzz-db): durable workflow agent delivery state machine (Node C)#6832loganj wants to merge 2 commits into
loganj wants to merge 2 commits into
Conversation
loganj
force-pushed
the
larry/workflow-delivery-db-v2
branch
from
August 25, 2026 22:09
a442321 to
4a4378f
Compare
loganj
force-pushed
the
darryl/delivery-protocol-vocabulary
branch
from
August 26, 2026 02:49
46aae72 to
092f9fb
Compare
loganj
force-pushed
the
larry/workflow-delivery-db-v2
branch
from
August 26, 2026 02:49
4a4378f to
3949325
Compare
loganj
force-pushed
the
darryl/delivery-protocol-vocabulary
branch
from
August 26, 2026 16:30
092f9fb to
9c87908
Compare
loganj
force-pushed
the
larry/workflow-delivery-db-v2
branch
from
August 26, 2026 16:30
3949325 to
6892c50
Compare
Add node C of the workflow-delivery DAG: the complete dormant DB
transition system for workflow messages addressed to managed agents,
built on B's canonical delivery protocol vocabulary.
Migration 0035 creates workflow_agent_deliveries: one canonical binding
per (community, run, step, target) with a pending -> claimed ->
finished | failed status enum, a monotonic lease_generation fence,
decomposed WorkflowDeliveryCause identity columns guarded by a CHECK
that makes an ambiguous or absent cause unrepresentable, cascading FKs
to workflows/workflow_runs/events, and the community write fence. The
desired-state schema.sql mirrors it inline.
The buzz-db module persists B's WorkflowDeliveryBinding verbatim (no
duplicate tuple spelling) and owns only the lifecycle around it:
- lock_workflow_agent_delivery_identity + commit_workflow_agent_deliveries:
the ONLY insert path, atomic per step, idempotent across producer
retries via ON CONFLICT DO NOTHING.
- claim_workflow_agent_delivery: FOR UPDATE SKIP LOCKED, scoped to the
authenticated target and community; an optional expected binding must
match every supplied field (workflow/run/step/definition/message and
the full decomposed cause) so a forged or stale wake is a miss, never
an alternate authority path. The winning row bumps its fence token.
- renew / finish: fenced by lease_generation; a reaped or superseded
holder matches zero rows and fails closed. finish is terminal-once and
reconciles uncertain completion to a single terminal idempotently.
- reap_expired_workflow_agent_deliveries: fleet-wide, filtered through
community_write_allowed exactly like the scheduler prune scan, so a
quiescing/fenced/deleted tenant is skipped before its write fence can
abort healthy tenants in the same statement.
No producer, runtime, API, or ACP reachability: dormant by contract.
Fresh-Postgres invariant matrix (#[ignore]) proves create idempotency,
community/target claim isolation, mismatched-binding rejection (incl.
cause- and workflow-only disagreement), lease fencing by generation,
reap + prior-holder fencing, terminal-once/idempotent reconciliation,
the fleet-wide lifecycle fence, run-deletion cascade, and commit
rollback. migration.rs asserts len 35, the 0035 contract, and fence
parity.
Co-authored-by: Mongo <89eda16aff8223982a0d928ccc850c50a22abc23babd9f890dc8d6d5431876fb@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Review of node C found four places where the delivery lease boundary
was not actually fenced under realistic races. All four are on the
authority-decision path, so a stale or foreign caller could obtain or be
credited with authority it does not hold.
- finish_workflow_agent_delivery reconciled uncertain completion by
reading the current terminal state on (community, id) only. When a
lease expired, the reaper bumped the generation, and a newer holder
claimed and finished, the original holder's later finish correctly
missed the fenced UPDATE but then read the newer holder's terminal
and returned AlreadyTerminal -- laundering the newer holder's outcome
back to the stale caller. The reconciliation read is now fenced by
the caller's generation (finish never bumps it, so a genuine
idempotent retry still matches); a terminal at a different generation
is LeaseLost.
- renew and finish gated only on status='claimed' AND lease_generation,
never on lease_until. Between the deadline and the reaper winning, an
expired holder could renew (resurrect authority) or finish (settle
work performed outside its lease). Both mutations now also require
lease_until >= NOW(): a lease no longer depends on the janitor
running first to become invalid.
- claim_workflow_agent_delivery scoped the row lookup by the request
(community, target) arguments and bound every field of the expected
binding except the binding's own community and target. A foreign
expected binding could therefore authorize a row selected under a
different request scope. claim now rejects an expected binding whose
community or target disagrees with the request scope before any SQL.
- WorkflowDeliveryLease is a target-scoped capability (target_pubkey is
"the managed-agent recipient that holds the lease"), but renew, the
finish UPDATE, and the terminal reconciliation SELECT bound only
(community, id, generation) -- the target fence evaporated after
claim. A lease reconstituted with a foreign target but the right
delivery id and generation could renew, settle, or be credited a
terminal via AlreadyTerminal. All three now bind the lease's
target_pubkey, so wrong-target authority is unrepresentable at C's
owner boundary rather than a convention D/F must re-enforce.
Adds fresh-Postgres invariant tests for the exact stale-holder-vs-newer-
holder finish interleaving (same and conflicting outcomes), expired
renew/finish before reaping, community-only / target-only expected-
binding scope mismatch, and a wrong-target lease that fails closed on
renew, finish, and terminal retry while the real target succeeds. Still
dormant: no producer, runtime, API, or ACP reachability.
Co-authored-by: Mongo <89eda16aff8223982a0d928ccc850c50a22abc23babd9f890dc8d6d5431876fb@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj
force-pushed
the
darryl/delivery-protocol-vocabulary
branch
from
August 26, 2026 20:41
9c87908 to
92a4800
Compare
loganj
force-pushed
the
larry/workflow-delivery-db-v2
branch
from
August 26, 2026 20:41
6892c50 to
6202549
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Opened by Mongo (co-author; Logan Johnson is git author + DCO signer).
Node C — dormant DB workflow-delivery state machine
Node C of the shared workflow-delivery DAG: the complete dormant DB delivery transition system for workflow messages addressed to managed agents, built on Node B's canonical
WorkflowDeliveryBindingvocabulary. Dormant by contract — no producer, runtime, API, or ACP reachability.Stacked on B
#6818(darryl/delivery-protocol-vocabulary@46aae72db5d29be3545b5a9025568d7441117773), which descends from corrected A3f18adb23610df29b61118362431f8f044becca7.#2737/#6697/#6818unchanged.What it adds
Migration
0035+schema.sqlcreateworkflow_agent_deliveries:(community, run, step, target).pending → claimed → finished | failedstatus enum.lease_generationfence.WorkflowDeliveryCauseidentity columns guarded by aCHECKthat makes an ambiguous/absent cause unrepresentable.workflows/workflow_runs/events, plus the community write fence.buzz-dbmodule persists B'sWorkflowDeliveryBindingverbatim (no duplicate tuple spelling) and owns only the lifecycle:lock_workflow_agent_delivery_identity+commit_workflow_agent_deliveries— the only insert path; atomic per step, idempotent across producer retries viaON CONFLICT DO NOTHING.claim_workflow_agent_delivery—FOR UPDATE SKIP LOCKED, scoped to authenticated target + community; an optional expected binding must match every supplied field (workflow/run/step/definition/message + full decomposed cause), so a forged/stale wake is a miss — never an alternate authority path. The winning row bumps its fence token.renew/finish— fenced bylease_generation; a reaped or superseded holder matches zero rows and fails closed.finishis terminal-once and reconciles uncertain completion to a single terminal idempotently.reap_expired_workflow_agent_deliveries— fleet-wide, filtered throughcommunity_write_allowedexactly like the scheduler prune scan, so a quiescing/fenced/deleted tenant is skipped before its write fence can abort healthy tenants in the same statement.deletion.rsregistersworkflow_agent_deliveriesin bothEXPECTED_SCOPED_TABLESandPURGE_SCOPED_TABLES(FK-safe ordering) — any tenant-scoped + fenced table must live in both lists.Validation (exact head
6ec79d5882e46a75f56e9d49b3562c58631bf3e8, byte-identical per-file blob shas)cargo fmt --check✅cargo clippy -p buzz-db --all-targets -- -D warnings✅#[ignore]invariant matrix (fresh Postgres, single-thread): 19 passed / 0 failed — create idempotency, community/target claim isolation, mismatched-binding rejection (incl. cause- and workflow-only disagreement), lease fencing, reap + prior-holder fencing, terminal-once/idempotent reconciliation, fleet-wide lifecycle fence, run-deletion cascade, commit rollback.abort_preserves_audit_and_allows_fresh_requeston a freshdb.migrate()DB: 1 passed / 0 failed (proves the catalog fix).Note on deletion
#[ignore]tests:deletion::postgres_testsandworkflow::testsshare onebuzzDB via a fixed URL and each calldb.migrate(), so co-running the deletion suite collides on an already-schema'd DB (channel_type already exists). They require per-test DB isolation; C's drift fix is proven by the single-test run above. CI's filtered--ignoredmatrix does not run either suite.Co-authored-by: Mongo
🤖 Review fix (Mongo) — lease-boundary fencing, head
a4423214Larry's review found three places where the delivery lease boundary was not actually fenced under realistic races. All three were on the authority-decision path — a stale or foreign caller could obtain, resurrect, or be credited with authority it did not hold. Fixed at every re-resolved boundary:
finishcorrectly fenced its terminal UPDATE bylease_generation, but the uncertain-completion fallback SELECT read(community_id, id)only. gen-1 expires → reaper bumps → gen-2 claims and finishes → gen-1finish: the UPDATE missed, but the unfenced SELECT read gen-2's terminal and returnedAlreadyTerminal, laundering gen-2's outcome back to gen-1. The reconciliation read is now fenced by the caller's generation (finish never bumps it, so a genuine idempotent retry still matches); a terminal at a different generation isLeaseLost.renew/finishnever enforcedlease_until >= NOW(). Both gated only onstatus='claimed'+ generation. Between the deadline and the reaper winning, an expired holder couldrenew(resurrect authority) orfinish(settle out-of-lease work). Both mutations now also require an unexpired DB-clock lease — a lease no longer depends on the janitor running first to become invalid.claimnever checked the expected binding's own community/target. The row lookup scoped by the request(community, target)arguments and bound every expected-binding field except the binding's own community and target, so a foreign expected binding could authorize a row selected under a different request scope.claimnow rejects an expected binding whose community or target disagrees with the request scope before any SQL.New fresh-Postgres invariant tests, each proven load-bearing (fails on unfixed source, passes on the fix): the exact stale-holder-vs-newer-holder
finishinterleaving (same + conflicting outcomes →LeaseLost, notAlreadyTerminal); expiredrenew/finishbefore reaping (→LeaseLost); and community-only / target-only expected-binding scope mismatch (→ claim miss).Exact-head Blox validation (ws
mongo-c-lease-fix-20260825, tree byte-identical toa4423214, blobe2eecfbb):cargo fmt --check✅ ·cargo clippy -p buzz-db --all-targets -- -D warnings✅ · unit 111 / 0 ✅ · delivery#[ignore]fresh-PG matrix (single-thread) 22 / 0 ✅. Still dormant by contract: no producer, runtime, API, or ACP reachability.Co-authored-by: Mongo