Skip to content

Run flow-syndication-underwriting's bonder as the underwriter daemon - #120

Closed
heifner wants to merge 3 commits into
masterfrom
feature/batch-operator-underwriter
Closed

heifner wants to merge 3 commits into
masterfrom
feature/batch-operator-underwriter

Conversation

@heifner

@heifner heifner commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

flow-syndication-underwriting pushed every bond, crank, approval and claim itself. It now starts the batch operator plugin's underwriter role for its bonder and waits on what the daemon does, so the flow exercises the daemon against live outposts: each request is bonded only after the Solana outpost's own record of the envelope confirms it.

Related PRs:

cluster-tool

  • OperatorDaemonTool.planDaemonStart now starts an underwriter's daemon as well as a batch operator's. It dispatches on the operator's type; a role's own inputs ride a StartDaemonOptions argument, here the underwriter's exposure caps. The underwriter's arguments are the batch operator daemon's with --batch-underwriter-account and one --batch-underwriter-max-exposure per token in place of --batch-operator-account; both roles build the shared part in one place.
  • WireSyndicationTool.planAwaitRequestBonded waits until an envelope's request is fully bonded with a bond of the bonder's on it. planAwaitRequestSettled waits until the request is APPROVED and the bonder's bonds on it are paid. Neither pushes anything. Their budgets come from the daemon's poll interval and the cluster's irreversibility budget.
  • WireSyndicationTool.readIssuedEnvelope finds an envelope by its epoch and syndicated total, which outlive its release.
  • SyndicationScenario.planUnderwriterStart materializes the bonder's underwriter identity (the account's key and the imported position's outpost keys) and starts its daemon. A scenario that plans it reserves the daemon's ports with adHocCount: SyndicationScenario.AdHocDaemonCount. Without that, or under a signature provider other than KEY, planning fails at once rather than when the daemon starts.
  • SyndicationScenario.planVerifyIssuedIntake verifies an intake while the daemon runs. The daemon can bond and release an envelope before a read sees it held, so the envelope is found with readIssuedEnvelope.
  • Constants.DEV_K1_KEY_PAIR is the development key as a WIRE key pair; the bios operator and the bonder's identity both use it.

flow-syndication-underwriting

The daemon starts where the flow used to push its first bond, after the checks that need unbonded state. Its exposure cap is the first request's covered amount, so it bonds the second request only once the first bond is paid back, and the flow keeps its order: bond, release, settle, then the next request. The release checks poll for the daemon's crank, the claim check counts a stake the daemon has already placed on the next request as the bonder's, and the third intake, which arrives with the daemon running, is no longer required to be seen held.

Notes

  • The other syndication flows still bond by hand.

valthon and others added 3 commits October 9, 2026 13:14
Remove retired reward consumers and capital-action callers, use the shared gross return minimum, and align native Andon permissions and retained flows with the current SDK. Verify raw fallback for removed reward and reserve-creation wire values without blocking active attestation decoding.
…onds and settles

The batch operator plugin's underwriter role bonds sysio.synd's envelope requests itself: it checks each statement against the outpost's record of the envelope, accepts the remainder, cranks sysio.synd, and approves and claims after the challenge window. A flow can now run its bonder as that daemon instead of pushing those actions.

OperatorDaemonTool.planDaemonStart now starts an underwriter's daemon as well as a batch operator's. It dispatches on the operator's type, and a role's own inputs ride a StartDaemonOptions argument: the underwriter's exposure caps. The underwriter's arguments are the batch operator daemon's with --batch-underwriter-account and one --batch-underwriter-max-exposure per token in place of --batch-operator-account, so both roles build the shared part in one place.

WireSyndicationTool.planAwaitRequestBonded waits until an envelope's request is fully bonded with a bond of the bonder's on it, and planAwaitRequestSettled until the request is APPROVED and the bonder's bonds on it are paid. Neither pushes anything. Their budgets come from the daemon's poll interval and the cluster's irreversibility budget. readIssuedEnvelope finds an envelope by its epoch and syndicated total, which outlive its release.

SyndicationScenario.planUnderwriterStart materializes the bonder's underwriter identity (the account's key and the imported position's outpost keys) and starts its daemon. It fails when the flow is planned, instead of when the daemon starts, if the scenario reserved no ad-hoc port pair for the daemon or the cluster's signature provider is not KEY. planVerifyIssuedIntake verifies an intake while the daemon runs: the daemon can bond and release an envelope before a read sees it held.

Constants.DEV_K1_KEY_PAIR is the development key as a WIRE key pair; the bios operator and the bonder's identity both use it.
…riter daemon

The flow pushed every bond, crank, approval and claim itself. It now starts the underwriter daemon for its bonder where it used to push the first bond, after the checks that need unbonded state, and waits on what the daemon does.

The daemon's exposure cap is the first request's covered amount, so it bonds the second request only once the first bond is paid back and the flow keeps its order: bond, release, settle, then the next request. The release checks poll for the daemon's crank, the claim check counts a stake the daemon has already placed on the next request as the bonder's, and the third intake no longer needs to be seen held.
@valthon
valthon force-pushed the fix/sysio-670-consumers branch from f258cc5 to e3d76da Compare October 10, 2026 22:44
Base automatically changed from fix/sysio-670-consumers to master October 12, 2026 18:31
valthon added a commit that referenced this pull request Oct 14, 2026
The flow proves the batch_operator_plugin underwriter role enforces its
exposure cap and recovers once a bond is paid back. Every batch operator
runs the role, but batchOperatorUnderwriting funds only batchop.a, with
a cap of exactly the first request's coverage. It brings the
cap-exhaustion case of the standalone-daemon change to
flow-syndication-underwriting (wire-tools-ts #120) onto the operators'
own nodes. flow-syndication-plugin-underwriting keeps the case of three
funded operators racing.

Setup sets the pair rules with a 240 s challenge window. After that the
flow writes only what users do (syndications and the unlinked
recipient's createlink); a unit test pins the list. Its Plan, pair rules
and shared tail phases come from BatchOperatorUnderwritingSteps
(createPlan, configOf, planUnlinkedIntake, planParkAndLink, planSettle).
Its first-released check requires the held second envelope to have
released nothing. It adds two checks of its own:
- second-held-by-cap, last in ExhaustCap: polls the first bond until it
  reads paid, for at most the window plus two plugin passes. On every
  reading before that, taken between two unpaid readings of the first
  bond, the second request must be OPEN with nothing bonded and no bond
  row. The first bond must equal the cap, at least one held reading must
  exist, and the first bond row must not vanish before it reads paid.
  The Report row records the held-reading count, its wall-clock span and
  the first request's resolved_at at the paid reading.
- second-bonded-after-recovery: the first request is APPROVED and its
  bond paid, and the funded operator's liquid LIQSOL plus its unpaid
  stake on the second request equals its funded balance from setup
  (bounty 0), the approved-and-returned check of #120. As a secondary
  check, the second request's bonded_at is no earlier than the first
  request's resolved_at.

The shared Steps also assert:
- every bond is the funded operator's, within the cap;
- unfunded operators hold nothing throughout;
- exact fees and exact custody;
- approvals no earlier than the window, and every bond claimed;
- the funded operator's credit returns, and every operator stays ACTIVE.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PPcjf9SpvBzUMjiuGoKZt6
valthon added a commit that referenced this pull request Oct 15, 2026
Prove the batch_operator_plugin underwriter role enforces its exposure
cap and recovers once a bond is paid back. Every batch operator runs
the role, but batchOperatorUnderwriting funds only batchop.a, with a
cap of exactly the first request's coverage. This brings the cap case
of the standalone-daemon change in wire-tools-ts #120 onto operators'
own nodes; the companion flow covers three funded operators racing.

The first request has a 589 s challenge window, covering submission,
one busy hop, governance finality, two active plugin passes and one
observation gap. Later requests use 60 s windows. Timeout scaling
raises ceilings without lengthening the on-chain windows. After setup,
the flow configures later request governance and performs user writes;
it never bonds, cranks, approves or claims on an underwriter's behalf.

Start the second-intake observer before submitting the syndication,
retaining its OPEN, zero-bonded snapshot through the first payment.
Bracket ordinary observations with unpaid first-bond readings. When
payment races discovery or reads, require request creation strictly
before the first approval to establish the earlier held state, even if
atomic accepts advance the second bond before its rows are read.
The first bond must equal the cap and cannot disappear before payment.
Record the held-reading count, span and first approval in the Report.
The shared initial evidence lives in orchestration/outputs.

After recovery, require the first request approved and its bond paid.
The funded operator's liquid balance plus unpaid second-request stake
must equal its original funding. Accept an already-paid second bond
and reread liquid balance once if its claim races the snapshot. Require
the second bond timestamp no earlier than the first approval. Custody
persistence can lag releases, so first-release verification accounts
for either no second release or its full release, with exact fees.

Shared Steps check funded ownership, exposure caps, unfunded operators'
empty holdings, exact custody, challenge-window approvals and claims,
returned credit, ACTIVE operators and the parked recipient's one-time
delivery. Controlled-clock tests cover delayed intake, payment races,
missing ordering evidence and claim races. The Plan, pair rules and
shared tail phases come from BatchOperatorUnderwritingSteps.

Claude-Session: https://claude.ai/code/session_01PPcjf9SpvBzUMjiuGoKZt6
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019LgGtRheR6Hac9WHX739y3
@valthon

valthon commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

This PR is superseded by #127

@valthon valthon closed this Oct 15, 2026
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.

2 participants