Skip to content

harness: syndication underwriting, emergency stop and live custody totals - #112

Merged
valthon merged 27 commits into
masterfrom
feature/syndication-underwriting
Oct 8, 2026
Merged

valthon merged 27 commits into
masterfrom
feature/syndication-underwriting

Conversation

@valthon

@valthon valthon commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Bring the cluster harness and live flows onto WIRE-side syndication underwriting: external outposts retain original-asset custody, while the depot handles shadow LIQ release, underwriting, collateral, and recovery policy. Exercise complete destination settlement, including external wallet delivery after desyndication.

The current branch includes the retired-feature cleanup from #113 and documentation updates from #114. The eight previously disabled reserve/swap/underwriter flow packages are now deleted, together with their dedicated tooling and bootstrap support; 13 enabled flow packages remain.

What changes

Cluster bootstrap and chain tools

  • Load the Solana outpost and liqsol program set at genesis, initialize the real liqsol surface, and drive syndication/yield through actual on-chain instructions rather than injected attestations.
  • Deploy and configure sysio.liq, the depot-local sysio.swap AMM, sysio.bond, sysio.synd, and sysio.andon; configure outpost custody, transfer limits, and emergency controls.
  • Add typed tools for bond requests, syndication envelopes, fees, budgets, custody reconciliation, freezing, and deferred payouts on Ethereum and Solana.
  • Keep mock LIQ pools and custody-backed shadow imports opt-in. Seed the corresponding external backing and Ethereum principal so imported backing is not mistaken for yield.
  • Support current Ethereum artifact aliases and generated OPP custody totals.

Depot-native operator collateral

  • Bond, withdraw, and claim collateral through sysio.opreg, with exact repayment and eligibility checks, replacing external-chain collateral paths.
  • Require 2.0 LIQSOL AND 2.0 LIQETH for the producer-registration scenario. Both are depot-native requirements under the WIRE chain code, each 2,000,000,000 atomic units.
  • Prove that one bond alone is insufficient, both allow admission, and withdrawing either independently removes eligibility. Retain production, missed-round demotion, recovery, and final remit-claim checks.

Complete settlement and recovery coverage

  • Exercise provider-backed underwriting, release fees, invalid challenges and forfeiture, intake/redemption rate limits, emergency freezes, custody shortfalls, and exactly-once deferred payouts.
  • Do not finish successful syndication/yield paths at a held envelope or desyndication at a queued message: assert exact destination balances and wait for actual external payout.
  • Use explicit sysio governance resolution for selected OPEN, unbonded envelopes to complete slow flows without provisioning extra providers. The helper refuses to override provider bonds or challenge state; provider-path coverage remains in the underwriting scenarios.
  • Align assertions with per-envelope obligations and outstanding returns; exercise custody reconciliation separately from clearing the Andon stop. Add regressions for wrong return identities and short settlement.
  • Keep canonical runner/heartbeat supervision, narrowly scoped allowances for deliberate freeze errors, and package unit tests in the root gate.

Retired functionality and harness reliability

  • Delete eight retired reserve/swap/underwriter flow packages, their dedicated clients and helpers, reserve seeding options, and underwriter daemon wiring. Default the retired daemon count to zero and reject nonzero values.
  • Preserve active LIQ syndication/yield, node-owner registration, depot collateral, operator lifecycle, emissions, and the depot-local AMM.
  • Include the bind-registry and file-lock repairs: registry-scoped external locks, owning-call failures on compromised locks, network-family collision checks, faster wildcard probing, and coverage for registry deletion and late completion.
  • Add the supervised commit gate/watchdog and refresh setup, bootstrap, and architecture documentation for the retained feature set.

Enabled live flows

flow-batch-operator-slashing, flow-batch-operator-termination, flow-emergency-stop, flow-emissions-soak, flow-liq-syndication, flow-liq-yield, flow-node-owner-nft, flow-operator-collateral-deposit, flow-producer-registration, flow-syndication-challenge, flow-syndication-rate-limit, flow-syndication-underwriting, and flow-yield-distribution.

Validation and current limitations

  • Current head: 6126502d1ef4e0c69cd85af911737cf7d532cc37. Hosted CI run 37607138468 fails during Install deps: package preparation compiles against published SDK/OPP packages missing required syndication contract types, messages, and enum members. The log includes @wireio/[email protected]. This is not a passing build/test gate; matching companion packages are required.
  • Andon runtime alignment remains outstanding. The production-bootstrap document now describes native updateauth/linkauth permissions and RAM funding, but this branch's ClusterBuildDefaults/AndonContractSteps still call the removed setpanic/addpuller actions. Update that runtime path before using the current native-permission contract from wire-sysio #662. The documentation change does not implement that runtime migration.
  • Recorded local validation, not rerun at this merge head: cleanup commit 95a66e2d records a successful build, 248 suites / 2,642 tests, retained flow-unit and runner checks, and a live LIQ syndication run through final wallet settlement.
  • Operational-state commit ed871bd7 records a successful build, 53 shared-tool and 43 focused-flow tests, and successful underwriting/emergency live runs. Its rate-limit run passed depot checks but stalled on Solana compute exhaustion before external settlement was verified; it must not be counted as a successful complete flow.
  • The earlier producer-collateral update recorded eight focused tests and a fresh live run of 75 phases / 300 steps, including both mandatory bonds and exact repayments. Earlier report-backed full-flow results apply to their recorded revisions, not automatically to the current branch.

Coordinated dependencies

Land compatible depot contracts and generated OPP models from wire-sysio #662, matching outpost changes from wire-solana #562 and wire-ethereum #217, and generated SDK types from wire-libraries-ts #92 before treating this harness as independently buildable. Publish the corresponding model/SDK packages for standalone CI and resolve the Andon runtime mismatch above. wire-platform-manifest #6 carries the coordinated platform guidance.

This branch also includes the earlier Solana/liquidity foundations from #98 and #104, the lock/hook integration from #100, and the merged cleanup/documentation work from #113 and #114.

valthon and others added 17 commits August 20, 2026 14:54
Concurrent clusters on one host race the cross-process bind registry.
The advisory port lock now keys on the NORMALIZED registry path — two
spellings of one directory hash to one lock — and lives in the OS temp
dir rather than inside the registry: a sandboxing caller owns and
deletes that directory, while proper-lockfile keeps an mtime-refresh
timer alive for a held lock and its onCompromised hook THROWS when the
directory vanishes underneath it (`ENOENT ... wire-cluster-ports.lock.lock`).
Hashing the path keeps the scoping while putting the file somewhere no
test owns.

Real runs are unchanged: with the env var unset every process resolves
the same default registry, hence the same lock, and the host-global
cross-process guarantee holds exactly as before. A caller that sandboxes
the registry (every jest suite, via tests/jest.setup.ts) gets its OWN
lock, which is correct — disjoint registries cannot collide on a port,
so serializing them protects nothing.

The stale-lock steal window is raised 10s -> 30s so it sits ABOVE the
~15s loaded-host port-probing critical section; at 10s a live holder was
stolen mid-section, which is the failure mode that let two clusters draw
the same port and UDP-double-bind.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…timer throw

Mechanism, verified against [email protected] `lib/lockfile.js`:
`updateLock` refreshes a held lock's mtime on an `unref`'d timer; when that
`stat`/`utimes` ENOENTs it calls `setLockAsCompromised` with
`Object.assign(err, { code: 'ECOMPROMISED' })` — the error is rewritten IN
PLACE, so its `ENOENT … stat '…/<target>.lock'` text survives. The DEFAULT
`onCompromised` is `(err) => { throw err }`, i.e. an uncaught exception raised
from that timer callback, with no call-stack relationship to the code holding
the lock: under jest it fails whatever test happens to be running, while the
caller still inside the critical section is never told it lost exclusivity.
`setLockAsCompromised` also marks the holder released before invoking the
handler, which is why `release()` then rejects `ERELEASED` (already handled).

The fix: `withFileLock` owns `onCompromised`. It logs, then rejects a
`Deferred` the call races against the section, so the compromise fails THAT
call with a `NestedError` carrying proper-lockfile's error as the cause and
`{ lockPath }` as context. A compromise landing after the section already
settled is swallowed; a section that fails after a compromise decided the
outcome is logged at warn instead of vanishing. The section itself cannot be
cancelled — the public JSDoc now says so, since its side effects still land.

Deliberately NOT changed: `stale`, `update` and `retries` stay exactly as
master had them. Across four full cluster-tool runs the compromise signature
appeared 18–68 times while `Unable to update lock within the stale threshold`
— the signature a stale STEAL produces — appeared ZERO times: every observed
compromise was the lock directory being deleted under a live holder, so the
handler, not the threshold, is what fixes the reported failure. Raising
`stale` would only lengthen how long a crashed holder wedges every contender.

Gate on an idle host: `pnpm build` clean, `pnpm run lint` clean,
`pnpm --filter @wireio/cluster-tool test` 138/138 suites, 1505/1505 tests,
with no `.lock.lock` signature anywhere in the run. The new compromise test
fails against the unpatched handler (uncaught `ENOENT … .lock` from the
refresh timer; the call never settles), and the root `jest.config.ts` note
about that hook throwing is updated to what happens now.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa
…e liqsol surface

The Solana outpost bootstrap only ever loaded `liqsol_core`, so the liqsol
staking + syndication surface it hosts had no accounts behind it: the liqSOL
mint, its Token-2022 transfer hook, the distribution / stake / withdraw state,
the leaderboard, the wire `GlobalState` and the reserve pool simply did not
exist on a flow cluster. Anything that drives a REAL `synd` or
`report_liq_yield` was therefore unreachable.

Load all four programs at genesis and run wire-solana's own `init-*` scripts:

- `SolanaOutpostProgramTool` generalizes over the program's CRATE name
  (`.keys/<name>-keypair.json`, `target/deploy/<name>.so`,
  `target/idl/<name>.json`) and gains `assertIdlProgramId`, so the id a script
  resolves from the IDL can be compared to the id the validator loaded.
  `liqsol_core` stays the default, so every existing caller is unchanged.
- `SolanaValidatorProcessSteps.resolvePrograms` returns all four programs
  (`GenesisAnchorPrograms`), each upgradeable under the ONE per-cluster
  deployer — the same identity `initialize_global_config` proves against
  `ProgramData`.
- `SolanaAnchorScriptTool` runs ONE `Anchor.toml` `[scripts]` entry per Step
  (`anchor run <script> --provider.cluster … --provider.wallet …`), the Solana
  analogue of the hardhat shell-out in `EthereumOutpostBootstrapper`. It
  prepends wire-solana's own `node_modules/.bin` to the subprocess `PATH`:
  every `[scripts]` entry is a bare `ts-node`, which anchor-cli hands to
  `bash -c` WITHOUT adding the workspace bin dir, so on a machine with no global
  `ts-node` — a CI runner — every script would die `command not found`.
  Prepending it also means the interpreter is the one that repo's lockfile pins
  rather than whatever a global shim happens to be.
- `SolanaLiqsolSurfaceSteps.planLiqsolSurface` composes the phase: assert the
  toolchain and the program ids, airdrop the deployer, run the sixteen init
  scripts in `bash-scripts/reset-local-cluster.sh`'s order — with harness Steps
  spliced in at the three scripts that need them — then top the rent treasury up
  to its floor.

  `init-distro` and `init-controller` are NOT among the scripts. Both write
  unconditionally and both end in `main().catch(console.error)`, so they exit 0
  on a failed write, and `runScript` sees only the exit code: their Report rows
  could not go red, and a failed init would surface at whichever later step
  first read `distribution_state` or `controllerState`, under the wrong name.
  The three writes they carry are three harness Steps instead (`initialize`,
  `initialize_stake_controller_state`, `initialize_vault`), each reading its
  target account first, at the position the scripts ran — which is not one
  position: `init-distro` runs immediately BEFORE `init-wire-config`, whose
  instruction reads the `distribution_state` it creates, and `init-controller`
  immediately before `init-global-config`. The Steps are keyed to those script
  NAMES rather than to an index, so inserting an init script cannot move them.

  `init-pretoken-purchase-history` gets a gate and a read-back, and this one is
  not hygiene either. The instruction stores `starting_epoch = current_epoch -
  1`, and `PretokenPurchaseHistory::is_initialized()` IS `starting_epoch != 0`.
  In Solana epoch 0 that underflows and the script exits 1 — every local-mode
  bootstrap dies. In epoch 1 it SUCCEEDS and stores 0, the program's own
  "never initialized" sentinel, after which every pre-launch syndication,
  pretoken and refund path on that cluster fails `InvalidWireState` for the rest
  of its life. Measured on this branch's own runs the step landed 1.4 s into
  epoch 1 — the sentinel, every time, invisible only because PostLaunch `synd`
  never reads the history. So the phase waits for epoch
  `MinimumPretokenHistoryEpoch` (2, wire-solana's own `MIN_EPOCH` in
  `bash-scripts/wait-for-validators.sh`, for exactly this reason) immediately
  before the script, and reads the account back immediately after so a
  regression in the gate cannot pass silently.

  The wait's budget comes from `getEpochInfo()` — the slot time actually
  REMAINING (`slotsInEpoch - slotIndex`, plus whole epochs after it) times a
  slack factor — not from a whole number of epochs counted at the step. By the
  time this runs the scripts ahead of it have already spent most of the wait, so
  a step-relative budget would be mostly slack in the normal case and still too
  tight for a slow validator in the bad one. The read-back polls for a few slots
  before decoding, because the script confirms its `.rpc()` at `processed` while
  the harness reads at `confirmed`. This is also what makes the
  100-slot epoch schedule load-bearing rather than cosmetic: at agave's default
  the wait would be days.

  The toolchain assertion is not hygiene. Since anchor-cli 0.30, `Anchor.toml`'s
  `[toolchain]` is applied on EVERY `anchor` invocation, so a `solana` or
  `anchor` that differs from the pins makes each script Step run
  `agave-install init <pinned>` first — a network download that repoints
  `~/.local/share/solana/install/active_release` while THIS cluster's
  `solana-test-validator` is running out of it. Refusing up front costs one
  `--version` call per binary; discovering it the other way is a validator that
  misbehaves mid-bootstrap, blamed on whichever step was unlucky. `reset-local-cluster.sh` itself is NOT used — its
  `anchor deploy` would fight the genesis load with a different upgrade
  authority. The treasury is NOT one of the scripts: `fund-treasury` transfers
  a fixed amount every time it runs, so on a re-run it would keep paying. A
  script Step is only sound when the script is idempotent, so the treasury gets
  a harness Step instead — `planFundTreasury` reads the PDA's balance and
  transfers only the shortfall, the shape `planKeypairAirdrop` already uses.
  `init-tranche-state` is left out for a harder reason: outside
  `--features development`, `initialize_tranche_state` pins its `chainlink_feed`
  and `chainlink_program` accounts to the real MAINNET Chainlink SOL/USD feed
  and program by address, and a test validator hosts neither — so running it
  would make a development build a prerequisite of the bootstrap. Nothing on the
  syndication path reads the tranche state, and neither does any later script
  here, so the surface is complete without it.
- `SolanaAnchorScriptTool` records each script's stdout/stderr TAILS into the
  step's Report `extra`. A script Step's evidence is its output, and the tails
  are logged at `debug` (twenty scripts x 4 000 chars would flood the aggregate
  log the heartbeat greps); without the Report entry a PASSING step would leave
  nothing behind but the argv that launched it, so `init-wire-config` standing up
  the wire `GlobalState` and the liqSOL pool's accounting would be unverifiable
  after the run.
- `SolanaFundingTool` gains handle-addressed per-cluster keypairs
  (`sol-<name>-keypair.json`, the deployer being one of them) and
  `planKeypairAirdrop`, the operator-free counterpart of `planAirdrop`: the
  deployer and flow-owned wallets have no `ctx.keyStore` operator identity.
- `SolanaValidatorProcess` now passes `--slots-per-epoch`, resolved from the
  PERSISTED `ClusterConfig` so `create`, `run` and `start.sh` cannot drift from
  one another. The field is REQUIRED and carries no schema default:
  `ClusterConfigProvider.resolve` always writes it (from
  `--solana-slots-per-epoch`, defaulting to 100), so a config without it is
  malformed rather than old.
  agave's default keeps a fresh validator at Solana epoch 0 for ~2 days of slot
  time, and `initialize_pretoken_purchase_history_handler` seeds its history
  from `current_epoch.checked_sub(1)` — which underflows (`Underflow`, 7418)
  for as long as the epoch is 0, so the liqsol surface could not be
  initialized at all. This is the same reason wire-solana's own
  `run-wire-postlaunch-local.sh` passes the flag. No harness or flow code
  reads the Solana epoch (the OPP consensus boundary is the DEPOT's epoch), so
  shortening it changes no behavior under test.

The phase is sequenced BEFORE the OPP outpost deploy because that is where
`init-global-config` creates the `global_config` every OPP admin op is gated
on; `SolanaOutpostBootstrapper.ensureGlobalConfig` then finds it already there.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa
…ol_core paths

`SolanaLiqSyndicationTool` is the harness's Step palette for the `liqsol_core`
instructions a user, an admin and a permissionless cranker actually call —
`sol_to_liqsol`, `set_wire_state`, `set_token_address`, `synd`,
`inject_bonus_synd_yield` and `report_liq_yield`. Every one is its own Step, so
the Report records each write; `SYNDICATE_LIQ` and `LIQ_YIELD` are queued by the
program itself, never by the harness.

- PDA derivation is a pure value helper (`deriveBasePdas` / `deriveUserPdas`)
  over `LiqsolPdaSeed` — the seed registry the bootstrap phase that CREATES
  these accounts already derives from, aliased here as `PdaSeed` so every call
  site reads the same. The seeds are cross-checked against
  `liqsol-core`, `liqsol-token` (the mint + its authority) and `transfer-hook`
  (the `ExtraAccountMetaList`). The mint's token accounts are Token-2022 ATAs.
- `readLiqYieldState` and `readWireState` decode `GlobalState` through the
  program's OWN Anchor coder, reached via a connection-only
  `SolanaOutpostProgramTool.loadReadOnlyProgram` — a read needs the IDL's
  layouts and an RPC, never a wallet or a fabricated keypair. It has to be a
  `Program` and not a bare `anchor.BorshCoder`: `Program` is what camelCases the
  IDL, so its coder keys `globalState` and `liqYieldReported` where a raw-IDL
  coder keys `GlobalState` and `liq_yield_reported`, and Anchor exports no
  converter to bridge them. A committed `GlobalState` fixture pins that, because
  the wrong spelling costs a four-minute cluster to discover. The crank's
  reported amount IS the delta between its watermark before and after.
- `set_token_address` is exposed as a Step because it is a hard precondition,
  not a convenience: `synd` and `report_liq_yield` resolve their attestation's
  depot token code via `OutpostConfig::token_code_for_mint` and refuse with
  `LiqTokenNotMapped` when the liqSOL mint is unmapped.
- `SolanaAnchorEnumTool` gains the `WireState` identity enum, its variant tag
  and the program's OWN transition table, alongside the existing proto-enum
  variant helpers. `runSetWireState` READS `GlobalState.wireState` and refuses
  before it submits — `set_wire_state` is a one-way ratchet in `liqsol_core`,
  so a re-run or a wrong target would otherwise spend a transaction to earn an
  opaque on-chain revert.
- Every instruction's account map is an exported pure builder, and one test per
  instruction feeds the runner's OWN map to `program.methods.<ix>().accounts()`
  against a trimmed, committed copy of the real `liqsol_core` IDL, asserting
  the resolved keys, their order and their writable/signer flags. wire-solana
  sets `resolution = false`, so Anchor fills in NOTHING: a renamed key would
  otherwise surface only as a run-time failure deep inside a flow, and an extra
  key would be silently dropped. The same comparison runs at BOOTSTRAP, against
  the IDL actually on disk (`verify-instruction-accounts`), because the
  committed fixture can only prove the maps were right when it was generated —
  and it names the instruction and the drifted keys rather than reverting later.

The deposit Step stands off Solana's epoch-rewards window, and retries once if
it lands in it anyway.
The runtime distributes an epoch's staking rewards over the first blocks of the
next one (SIMD-118) and refuses every stake instruction while it does;
`liqsol_core` reads the same `EpochRewards` sysvar and fails `deposit_to_reserve`
with `EpochRewardsActive`. At the harness's 100-slot epochs that window recurs
every ~40 seconds, so `runDepositForLiqsol` polls the sysvar and waits it out —
otherwise the flow is a coin flip, which is exactly how it was observed failing.

Polling narrows the window to the gap between the last read and the transaction
landing; it cannot close it. So the submission runs through
`submitWithEpochRewardsRetry`, which on that one error waits the window out and
re-sends EXACTLY once — safe in both shapes it arrives in, because a preflight
refusal sent nothing and an on-chain failure is atomic. All three renderings are
matched, derived from the single `EpochRewardsActiveErrorCode`: the AnchorError
name, `custom program error: 0x1dc5` from a refused preflight, and
`{"InstructionError":[1,{"Custom":7621}]}` from a transaction that PASSED
preflight and executed inside the window — the last being the one that actually
happens to the ~1 % of deposits the poll cannot protect. The retry takes its
`send` as a parameter so the branch is exercised without a validator.

`oppEnvelopeScan.readEnvelopeAttestations` returns the raw attestation payloads
of one type on one edge, so a scenario can assert on an attestation's CONTENT
instead of only its type tag. It delegates to `@wireio/debugging-shared`'s
`readEnvelopeRecordsFromDir` rather than re-walking the directory and re-parsing
the envelopes: that reader already owns the `.data`/`.metadata` pairing, the
filename grammar and the torn-read handling. Decoding the payload stays at the
call site, with the generated message class, so this module re-declares no proto
shape.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa
… injectors

`flow-liq-syndication` now drives the outpost's own instructions instead of
enqueueing attestations behind the program's back. A verify step reads
`GlobalState.wireState` and asserts the outpost is still PreLaunch — FIRST,
because every step after it writes — then a user is funded and deposits SOL for
liqSOL, the admin flips the outpost Launching -> PostLaunch, the user
syndicates — and `synd` queues `SYNDICATE_LIQ` itself. A permissionless donation
then credits the syndicated pool and a NON-ADMIN crank of `report_liq_yield`
queues `LIQ_YIELD`. The circulated `LIQ_YIELD` is DECODED and must carry exactly
the delta the crank advanced its watermark by, on exactly the sequence it drew,
under the outpost's chain code and the depot's liqSOL token code.

Because `set_wire_state` is a one-way ratchet, the scenario is single-shot per
cluster — the flow's README says so, and a second run against the same cluster
fails at that verify step with the state it found rather than at an opaque
on-chain revert several steps later. The step leads the phase so a refused run
spends nothing: behind it sits a real 5 SOL `sol_to_liqsol`, which would
otherwise land before the refusal.

The scenario binds the REAL liqSOL mint to the depot's liqSOL token code as its
first step rather than in the shared bootstrap: the bootstrap binds that code to
the mock SPL mint the swap flows hold reserves in, and only this flow's cluster
should see the change (`wire-outpost-repos-…-belong-in-before-all.md`).

`flow-yield-distribution` loses its Solana leg. The SOL staking surface is a
separate developer track and `liqsol_core` emits no `STAKING_REWARD`, so the leg
could only ever have been an injection; the flow is Ethereum-only until the
outpost produces the attestation itself.

With both consumers gone, the injection surface goes with them:
`SolanaYieldEmitterTool` (and, from the superseded revision of this branch,
`SolanaAddAttestationTool` / `SolanaSyndicationTool` and the flow's own emit
steps) are deleted. Nothing in the harness writes to a program's outbound
message buffer any more.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa
…he new flow

The root README's outpost summary and `--solana-path` row said the harness
bootstraps `opp-outpost`; it now loads all four wire-solana Anchor programs at
genesis and runs their `init-*` scripts before the OPP deploy. CLAUDE.md's
orchestration map names `SolanaLiqsolSurfaceSteps` and why it is sequenced ahead
of the outpost bootstrap, and the flow table rows say what the two touched flows
actually assert.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa
sdk-core and shared to 1.0.92, the wire-libraries-ts release carrying
the regenerated SysioContractTypes for sysio.liq and sysio.swap
(wire-libraries-ts#84). Produced by scripts/update-wireio-deps.mjs.

Change-Id: I2ccd90c14dd35080a9961f615041c70ff86a0e52
…ve the LIQ reward flow end to end

The bootstrap configures the swap, opens one shadow symbol per registered liq
token and sets the kicker; the mock yield pools are opt-in
(--enable-mock-liq-pools), seeded inside the epoch-0 window like the mock
reserves. Steps.contracts.sysio.{liq,swap} are the per-action factories;
Steps.user.planProvisionWire is the shared WIRE-user provisioning
flow-swap-from-wire carried locally.

flow-liq-yield: synd -> parked -> createlink sweeps it -> report_liq_yield ->
liqpending -> the batch operators' queueyield and tickyield -> claim pays
exactly owed(row, index) -> desyndicate -> DESYNDICATE_LIQ pays the liqSOL ATA.

Two harness fixes the run surfaced: confirmSignature checked a tx's err only
after returning on a confirmed status, so an on-chain failure passed as
success; WireClient.symbolCodeKeyRange reads a KV row keyed by one symbol_code
exactly (a bare code as a bound is a nodeop parse error).

.pnpmfile.cjs takes WIRE_LIBRARIES_TS_PATH / WIRE_SYSIO_PATH so a worktree links
its sibling worktrees. Pairs with wire-sysio#634 and wire-libraries-ts#84.

Change-Id: I67ceaa5927bb8deb4a71ed70426e223e41a44b22
sdk-core and shared to 1.0.92, the wire-libraries-ts release carrying
the regenerated SysioContractTypes for sysio.liq and sysio.swap
(wire-libraries-ts#84). Produced by scripts/update-wireio-deps.mjs.

Change-Id: I803eca9e735ebf7082843418b8945adfb537fac0
The jest ceiling is a local-operation ceiling, sized to the loaded-host
worst case. For a port-resolving test that worst case is set by bind(): a
resolve is about 800 binds (about 1500 once its process has already claimed
the first dynamic-range window), and on a host that serializes bind() the
cost of each one grows with the number of processes binding at once.

Measured on WSL2 with mirrored networking: one bind costs 10.2 ms with 1
process binding, 135.5 ms with 13 and 324.1 ms with 31. A full run makes
53-56k binds, and its 13 port-resolving suites start together, so every
resolve takes 120-135 s. Against the 120 s ceiling that fails 9-10 suites
with hook and test timeouts, and each timeout then produces the
`ENOENT ... wire-cluster-ports.lock.lock` follow-on the comment already
describes.

Raise `testTimeout` to 360 s in the root config, which multi-project mode
honors, and in the cluster-tool config kept in sync with it. The worker
count bounds how many resolves run at once, which puts the worst case near
31 resolves at about 10 s each. Wall time is unchanged: the run is bound by
bind throughput and a passing test returns the moment it finishes (704 s
failing at 120 s, 708 s passing at 360 s).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WvJpbenHM8A16MLiMteYtj
Operator collateral at launch is bonded on the depot. Add typed
sysio.opreg deposit, withdraw and claimremit steps and compose them in
WireCollateralTool with WIRE funding and exact balance verification.
Resolve operator accounts at run time, require a new withdrawal queue
entry, reject truncated reads and budget claim polling by epoch length.

Move collateral lifecycle, producer registration and batch termination
flows to WIRE bonds, including eligibility transitions and exact claim
payouts. Remove the unused Ethereum withdrawal helpers and write budget.
Underwriter plans now fund and bond native WIRE; shadow tokens still
lack a funding path and emit a warning. Keep action and tool tests with
these behaviors and update the consuming flows' descriptions.
The launch Solana program rejects reserve initialization and mock SPL
reserve provisioning with OperationDisabled. Default bootstrap must
skip those calls while retaining the inbound outpost path.

Carry enableLaunchWithheldOperations from the CLI and scenario defaults
through persisted configuration into the bootstrapper. Keep it false
for both new and legacy configurations. Reserve and swap scenarios opt
in explicitly and require a program build without launch restrictions;
depot collateral flows need no opt-in.

Keep registry seeding compatible with an absent sol-mock-mints.json.
Include CLI, schema, bootstrap and registry coverage, update fixtures,
and document the option and its limits for flow authors.
…n-underwriting

PR #104 is the only branch that deploys sysio.liq and sysio.swap in the
harness; the syndication-underwriting work builds on it.

Textual conflicts, resolved by keeping both sides' opt-in flags
(enableLaunchWithheldOperations from this branch, enableMockLiqPools from
#104): CLAUDE.md, README.md, ClusterConfig.ts and its test,
ClusterBuildOptionsArgs.ts and its test, ClusterBuildOptions.ts,
ClusterConfigProvider.ts, clusterConfigFixture.ts, fixtureCluster.ts and
ClusterSlice.test.ts.

Semantic conflicts:
- RegistrySteps.ts and the RegistrySteps / LiqContractSteps tests: the
  depot-native-collateral ABIs spell chain_code and token_code as slug
  codenames, so the shadow and liq-pool rows carry the codename strings.
- Constants.ts: ProtocolTiming.OutpostWriteBudgetMs, which this branch
  dropped as unused, is restored with its tests; #104's two liq flows
  size their outpost write Steps with it.
- oppEnvelopeScan.test.ts: the regenerated OPP bundle's LIQYield carries
  total_syndicated; the fixture sets it and the decode test reads it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WvJpbenHM8A16MLiMteYtj
Liquidity intake now needs a bonded envelope before the depot can
release shadow or yield. Bootstrap sysio.bond, sysio.synd and
sysio.andon together, arming panic accounts and configuring each outpost
before traffic starts.

Add typed depot and outpost tools for bonding, custody, emergency
controls and deferred payments. Keep mock position imports opt-in, back
all seeded shadow in custody, and initialize Ethereum principal to avoid
treating that backing as yield.

Migrate the existing liquidity flows to held-envelope bonding and the
sysio.synd actions. Share their user steps and include bootstrap, tool
and configuration coverage alongside the updated flow documentation.
Add fresh-cluster scenarios for bonded release and fees, invalid
challenges with forfeiture, and rate-limited intake and redemption.
Assert exact balances, supply, custody and queue transitions, including
same-epoch crank behavior and restoration of governance configuration.

Share provisioning and state readers in SyndicationScenario, with helper
coverage, and register all three packages in the TypeScript build. Keep
each scenario's run instructions and typed verification steps with it.
Add a live scenario that freezes the Solana and Ethereum outposts, pulls
the depot cord, and verifies that deferred redemptions pay exactly once
after recovery. Exercise automatic custody-shortfall stops and preserve
the mismatch evidence while proving repaired messages are admitted.

Teach the heartbeat monitor to recognize deliberate freeze refusals only
when explicitly requested, while retaining unrelated failure and
liveness checks. Base the initial growth deadline on epoch chain time.

Include monitor and recovery-helper coverage, run package test:unit
scripts through the root test gate, and document the paired live runner
and monitor commands.
The launch Solana program withholds the reserve and swap instructions
needed by eight flows. Rename their live scripts to test:disabled and
discover only packages with a test script, so exact names, regex matches
and the picker all select runnable flows consistently with CI.

Keep disabled packages in the build and document the manifest change
needed to enable them after audit. Include discovery coverage and update
the examples to use an enabled collateral flow.
Complete held principal and yield releases in syndication, challenge, and emergency-stop flows using explicit governance resolution for unbonded envelopes. Wait for the actual external destination balance after desyndication. Retain provider-path coverage and add exact-balance regressions and the enabled-flow audit.
…syndication-underwriting

* commit 'abf6d1ebeb6a9be223ba14c3caac66d93f49419a':
  Scope the bind-registry port lock to the registry it guards
Combine owning-call compromise failures with PR #76's registry-scoped
external mutex and 30-second short-lock stale threshold. Preserve the
long-lock policy and document that lock loss cannot cancel ongoing work.

Retain faster wildcard port probing with IPv6 checks, early range rejection,
two Jest workers, and a supervised full commit gate. Isolate the validator
environment test and keep all test ports in the bind registry.

Cover real lock refresh and mutual exclusion after registry deletion,
late completion after compromise, network-family collisions, and watchdog
termination of descendants.
Replace lifetime reporting assertions with per-envelope obligations,
outstanding returns, and actual destination wallet settlement. Exercise
explicit custody reconciliation while preserving the separate Andon
clear step, and describe independently claimable WIRE rewards.

Add regressions for incorrect return identities and short settlement.

Validation: build passed; 53 shared tool and 43 focused flow tests passed.
Underwriting and emergency live runs passed. The rate-limit live run
passed depot checks but stalled on Solana compute exhaustion before
external wallet completion could be verified.
Delete eight disabled reserve, swap and underwriter flow packages and
their dedicated clients, seeding flags and daemon wiring. Default the
retired daemon count to zero and reject nonzero values. Preserve all
13 enabled flows and their final destination settlement assertions.

Validation: build; 248 suites and 2,642 tests; retained flow-unit tests;
runner checks; live LIQ syndication through final wallet settlement.
jglanz and others added 3 commits October 6, 2026 13:00
Docs now describe the current cross-chain feature set: liq syndication (SYNDICATE_LIQ / LIQ_YIELD / DESYNDICATE_LIQ) with sysio.bond underwriting, node-owner registration, depot-native operator collateral and the depot-local sysio.swap AMM. Outpost swaps, reserves, underwriter commits and outpost collateral are described only as removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Replace removed panic and puller registration actions with native permission authorities and action links. Include the RAM-only policy, signing permissions, and privileged syndication path, and flag the older harness dependency.
docs: align with the syndication feature set and current cluster tooling
Remove retired flows and bootstrap support

@jglanz jglanz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a repo rewrite, no real way to review it for undefined behavior, so simply approving

@valthon
valthon merged commit fd8352d into master Oct 8, 2026
2 of 4 checks passed
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.

3 participants