Skip to content

feat(buzz-db): durable workflow agent delivery state machine (Node C) - #6832

Draft
loganj wants to merge 2 commits into
darryl/delivery-protocol-vocabularyfrom
larry/workflow-delivery-db-v2
Draft

feat(buzz-db): durable workflow agent delivery state machine (Node C)#6832
loganj wants to merge 2 commits into
darryl/delivery-protocol-vocabularyfrom
larry/workflow-delivery-db-v2

Conversation

@loganj

@loganj loganj commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

🤖 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 WorkflowDeliveryBinding vocabulary. Dormant by contract — no producer, runtime, API, or ACP reachability.

Stacked on B #6818 (darryl/delivery-protocol-vocabulary @ 46aae72db5d29be3545b5a9025568d7441117773), which descends from corrected A 3f18adb23610df29b61118362431f8f044becca7. #2737 / #6697 / #6818 unchanged.

What it adds

Migration 0035 + schema.sql create workflow_agent_deliveries:

  • One canonical binding per (community, run, step, target).
  • pending → claimed → finished | failed status enum.
  • Monotonic lease_generation fence.
  • Decomposed WorkflowDeliveryCause identity columns guarded by a CHECK that makes an ambiguous/absent cause unrepresentable.
  • Cascading FKs to workflows / workflow_runs / events, plus the community write fence.

buzz-db module persists B's WorkflowDeliveryBinding verbatim (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 via ON CONFLICT DO NOTHING.
  • claim_workflow_agent_deliveryFOR 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 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.

deletion.rs registers workflow_agent_deliveries in both EXPECTED_SCOPED_TABLES and PURGE_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
  • unit: 111 passed / 0 failed
  • workflow #[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.
  • deletion-catalog drift test abort_preserves_audit_and_allows_fresh_request on a fresh db.migrate() DB: 1 passed / 0 failed (proves the catalog fix).

Note on deletion #[ignore] tests: deletion::postgres_tests and workflow::tests share one buzz DB via a fixed URL and each call db.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 --ignored matrix does not run either suite.

Co-authored-by: Mongo


🤖 Review fix (Mongo) — lease-boundary fencing, head a4423214

Larry'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:

  1. Reconciliation dropped the generation fence. finish correctly fenced its terminal UPDATE by lease_generation, but the uncertain-completion fallback SELECT read (community_id, id) only. gen-1 expires → reaper bumps → gen-2 claims and finishes → gen-1 finish: the UPDATE missed, but the unfenced SELECT read gen-2's terminal and returned AlreadyTerminal, 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 is LeaseLost.
  2. renew/finish never enforced lease_until >= NOW(). Both gated only on status='claimed' + generation. Between the deadline and the reaper winning, an expired holder could renew (resurrect authority) or finish (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.
  3. claim never 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. claim now 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 finish interleaving (same + conflicting outcomes → LeaseLost, not AlreadyTerminal); expired renew/finish before 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 to a4423214, blob e2eecfbb): 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

@loganj
loganj force-pushed the larry/workflow-delivery-db-v2 branch from a442321 to 4a4378f Compare August 25, 2026 22:09
@loganj
loganj force-pushed the darryl/delivery-protocol-vocabulary branch from 46aae72 to 092f9fb Compare August 26, 2026 02:49
@loganj
loganj force-pushed the larry/workflow-delivery-db-v2 branch from 4a4378f to 3949325 Compare August 26, 2026 02:49
@loganj
loganj force-pushed the darryl/delivery-protocol-vocabulary branch from 092f9fb to 9c87908 Compare August 26, 2026 16:30
@loganj
loganj force-pushed the larry/workflow-delivery-db-v2 branch from 3949325 to 6892c50 Compare August 26, 2026 16:30
loganj and others added 2 commits August 26, 2026 16:33
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
loganj force-pushed the darryl/delivery-protocol-vocabulary branch from 9c87908 to 92a4800 Compare August 26, 2026 20:41
@loganj
loganj force-pushed the larry/workflow-delivery-db-v2 branch from 6892c50 to 6202549 Compare August 26, 2026 20:41
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