Repository navigation
harness: syndication underwriting, emergency stop and live custody totals - #112
Merged
Merged
Conversation
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.
valthon
marked this pull request as ready for review
October 1, 2026 14:32
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.
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
approved these changes
Oct 8, 2026
jglanz
left a comment
Contributor
There was a problem hiding this comment.
This is a repo rewrite, no real way to review it for undefined behavior, so simply approving
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.
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
sysio.liq, the depot-localsysio.swapAMM,sysio.bond,sysio.synd, andsysio.andon; configure outpost custody, transfer limits, and emergency controls.Depot-native operator collateral
sysio.opreg, with exact repayment and eligibility checks, replacing external-chain collateral paths.Complete settlement and recovery coverage
sysiogovernance 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.Retired functionality and harness reliability
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, andflow-yield-distribution.Validation and current limitations
6126502d1ef4e0c69cd85af911737cf7d532cc37. Hosted CI run 37607138468 fails duringInstall 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.updateauth/linkauthpermissions and RAM funding, but this branch'sClusterBuildDefaults/AndonContractStepsstill call the removedsetpanic/addpulleractions. Update that runtime path before using the current native-permission contract from wire-sysio #662. The documentation change does not implement that runtime migration.95a66e2drecords a successful build, 248 suites / 2,642 tests, retained flow-unit and runner checks, and a live LIQ syndication run through final wallet settlement.ed871bd7records 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.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.