/** * Every refusal, warning and detail string the deploy verb emits — one producer per taxonomy code. * * Split out of `orchestrator.ts` verbatim: this module holds the rendering, `orchestrator.ts` holds * the control flow that decides which of these to call. Nothing here reads the job's run state, which * is why it could move at all — the handful of builders that DO take an `InstanceRun` stay beside it. */ import type { StrategySummary } from "../senpi/types.js"; import { type WalletBindingFault, type DeployPackage, type DeployInstance, type ScannerEnabledKey } from "./package.js"; import { type ShareOverrun } from "./funding.js"; import type { MinBudgetResult } from "./min-budget.js"; import type { InstallGateResult } from "../validate/index.js"; /** * How reconcile found a candidate. `name` is the ordinary path; `created-key` is the C2 carve-out — * a strategyId this box journaled at create time, re-read from the backend before it counts. */ export type MatchedBy = "name" | "created-key"; /** The provenance clause every reconcile message carries, so a report never hides how it matched. */ export declare function matchedByClause(matchedBy: MatchedBy): string; /** * The other live strategies that answered to the matched name and were dropped as another * package's. A bind that ignored one is still a bind on a CONTESTED name — the collision outlives * this deploy and surfaces again at the next `close.py`, rename, or status read — so the report * says so rather than rendering a clean "matched by name". It is a note, not a warning: nothing * here is this package's to act on, which is the same reason those rows were dropped. */ export declare function foreignCollisionClause(ignoredForeign: StrategySummary[]): string; export declare function buildAmbiguousRefusal(count: number, matchedAs: string, instance: string): string; /** * A single live name-match whose wallet address cannot be read. deploy.py refused this case rather * than guessing, and the taxonomy's `E_STATE_AMBIGUOUS_WALLETS` row names it explicitly: the strategy * may be funded and live, so neither adopting a blank address nor creating a second wallet is safe. */ export declare function buildUnreadableWalletRefusal(strategyName: string, instance: string, strategyId: string): string; /** * The `create: skipped` detail for an adopted wallet. * * When a budget was requested, this is the only place the report can say that it was **not** * applied — deploy never adds funds to a wallet that already exists, and a silent skip would let an * agent summarise "deployed spider with $500" over a wallet holding $100. */ /** * How a backend `totalFunded` read renders in prose. * * An unreadable figure is stated as unreadable, never as `$0.00`. Every sentence that quotes this * number sits beside an instruction about money, and the two cases call for opposite next steps: a * genuine zero says "there is nothing on this wallet", an unread one says "nobody knows yet". */ export declare function fundedClause(amount: number | undefined): string; /** `its $60.00` / `its balance` — the possessive form of the same rule. */ export declare function itsFunded(amount: number | undefined): string; /** * The rollback voice of {@link fundedClause}: `funded $250.00` / the unread form. * * Both sentences it feeds order money moves on the wallet they name — "closing it and returning the * funds", "Reclaim the funds from THIS wallet" — so a `?? 0` here told the reader to reclaim from a * wallet the same sentence called empty, which reads as "nothing to reclaim, skip it" over money * that is genuinely out there. UNKNOWN, never $0.00. */ export declare function rollbackFundedClause(amount: number | undefined): string; /** * A live name-match in a status that is neither ACTIVE nor on its way there — PAUSED, mid-teardown, * or something this verb does not recognise. * * Polling these is the bug this builder exists to prevent: a PAUSED strategy never becomes ACTIVE on * its own, so waiting on it burns the whole `--max-wait` and then reports the bounded-wait outcome * — "re-run to resume, reconciliation waits for it to reach ACTIVE" — which is unactionable on a * status that will never get there. The next step is computed from the status actually read, * and the non-destructive option always leads. */ export declare function buildNotDeployableRefusal(matched: StrategySummary, instance: string, pkg: DeployPackage, budget: number | undefined): string; export declare function buildBudgetRequiredRefusal(wallets: number, pkg: DeployPackage): string; /** * `--budget 0` is not "no budget": it parses, and without this gate `planFunding` floors every * wallet to MIN_WALLET — funding money the user never asked to spend. `--budget ≤ $0` is also the * taxonomy's rule-4 field case: its only known source in the field is a buggy computed hint, so the * refusal teaches where the real fix lives instead of letting that loop run. The same gate exists * at gateway intake (register.ts), keeping every caller of `runDeploy` covered. */ export declare function buildBudgetNotPositiveRefusal(budget: number, wallets: number, pkg: DeployPackage): string; /** * A wallet THIS deploy is funding for less than its own sizing needs. * * The soft tier's claim is per-wallet, not whole-budget-vs-whole-package, because those two are not * the same question on a partially-adopted deploy: `planFunding` splits the budget across the * instances still NEEDING a wallet, while the calculated minimum is computed over every instance in * the package. Comparing the budget to the package minimum once said "$20.00 is below $30.00" about * a re-run that was handing a single $13.50 sleeve the whole $20 — a shortfall that did not exist, * attached to an escape that would have closed the adopted, live, funded sleeve beside it. */ export interface UnderfundedWallet { wallet: string; /** What `planFunding` allocates this wallet. */ funded: number; /** Its own `perWalletMin` — what its smallest slot needs to open. */ needs: number; } /** * The close-then-redeploy escape — the ONLY sequence that actually raises a deployed wallet's size. * * A bare re-run at a bigger `--budget` is a guaranteed no-op: reconcile matches the strategy by * name, ADOPTS it, skips create, and answers with `buildAdoptedWalletDetail`'s "the requested … was * NOT added". The agent then has to reverse-engineer the real step from a message it did not * expect, and the two likeliest readings are "the deploy failed" and "close it to start clean". * * SCOPED to the underfunded sleeves. `close.py ` tears down the WHOLE package, so on a * partially-adopted deploy it would close the adopted, live, funded sleeve that this warn is not * even about — the "close-to-replace teardown" class the taxonomy's rule 5 exists to prevent. * `--instance` narrows it, and is only omitted when every sleeve in the package really is affected. * * `existing` is the subset of `under` whose wallets ACTUALLY EXIST on the finished report. An empty * one yields no escape at all: on a deploy that failed before create there is no "it" to close, and * offering a teardown for a wallet that was never made is a false premise plus a pointless step. */ export declare function buildBudgetEscape(pkg: DeployPackage, min: MinBudgetResult, state: SoftEscapeState): string; /** * What the finished report can actually support behind an escape. Every field is read from terminal * state, never from the plan — the whole point is that the emitted command's preconditions hold. */ export interface SoftEscapeState { /** * Short sleeves with a live runtime — the only ones `close.py --instance` can resolve — MINUS any * declaring `funding_share: 0`, which no close-and-redeploy can help (see `zeroShareSentence`). */ closable: string[]; /** Short sleeves holding a funded wallet with NO runtime: strandable, not closable by instance. */ strandedNames: string[]; /** Instances a re-run would fund (no live wallet), which is what sizes the redeploy hint. */ walletless: string[]; /** Short sleeves declaring `funding_share: 0` — money cannot fix them; the manifest can. */ zeroShare: string[]; /** `[E_ROLLBACK_INCOMPLETE]` is on this report and owns the cleanup instruction. */ rollbackIncomplete: boolean; } /** * Which of the short sleeves the terminal report actually shows funded / carrying a runtime. * The two claims a note makes ride different facts — "this deploy funds X" is about the WALLET, * "X will run" is about the RUNTIME — and each is judged per row, so a mixed report (some sleeves * installed, some stranded or never created) can no longer assert one tense about all of them. */ interface SoftTense { funded: Set; running: Set; } /** Forward-looking tense for the preflight narration: everything is about to be funded and run. */ export declare function forwardTense(under: UnderfundedWallet[]): SoftTense; /** * The soft-tier note, assembled from the facts plus whatever escape the caller's state supports. * ONE producer for both codes' strings — the preflight narration and the final report differ only * in `escape`, so they can never drift into two different accounts of the same finding. */ export declare function buildSoftBudgetNote(pkg: DeployPackage, min: MinBudgetResult, under: UnderfundedWallet[], escape: string, tense: SoftTense): string | null; /** * What both OUTCOME-tier rows carry: which sleeve, which wallet, what was asked of the backend for * it — and who did the asking. * * Every other budget check on this report judges the PLAN — what `planFunding` decided to ask for, * against the package's own sizing, before a wallet exists. The two rows below are the only ones * that judge the OUTCOME: the post-create strategy read against the ask that produced it. * * The shape is shared so the two notes cannot drift on the same fact. They are read together, on * one report, about one package: one saying a wallet was created and the other saying the same * wallet was adopted is a report contradicting itself. */ interface OutcomeFundedWallet { /** Sleeve name — how the package, the escape commands and the step table all name it. */ instance: string; /** The address, because the non-destructive fix (a top-up) is performed against it. */ wallet: string; /** What the create call asked the backend for — this run's ask, or a prior run's (see below). */ requested: number; /** * Whether THIS run created the wallet, so the note can say which of the two it is looking at * instead of assuming a create. * * It is `run.needsWallet`, not the create step's status: an ADOPTED wallet that was still * initializing reaches `ok` through the very same ACTIVE poll a freshly created one does, so the * status alone cannot tell them apart. Only `needsWallet` marks the run that actually called * create — it is the same flag the rollback and the unwired-install detail use for "created by * this job". * * The ask follows from it: a created wallet's is this run's own `fundAmount`, an adopted one's is * the amount a PRIOR run journaled ({@link DeployParams.priorRequested}), which is exactly the * path that puts an adoption in front of these notes. * * It is a per-wallet fact and stays one. Neither note may generalise it over the deploy: each row * set is FILTERED (one holds the short wallets, the other the unreadable ones), so nothing about * the wallets outside it can be read out of it. */ created: boolean; } /** A wallet the backend funded for materially less than was asked for it. */ export interface PartialFundedWallet extends OutcomeFundedWallet { /** What the post-create `strategy_list` read reports the wallet actually holds. */ funded: number; } /** * A wallet whose funded amount the backend did not report on the post-create read. * * Split from {@link PartialFundedWallet} rather than folded into it as a `funded: 0`, because the * two demand OPPOSITE next steps: a real zero is a shortfall with a computable top-up, an unread * figure is a wallet that may already hold the full amount. Only the ask is known here. */ export type UnreadableFundedWallet = OutcomeFundedWallet; /** * The OUTCOME tier's warn: the backend funded a wallet for materially less than the create call * asked for (a partial bridge, a rejected top-up leg — the deploy sees only the result). The * successor to the skills-side verify gate's `_budget_verdict`, which reported the whole strategy * "not live" over the same finding. * * It is a `W_`, and the difference from that predecessor is the point: the strategy IS live, its * wallet holds real money and its runtime is ticking. Reporting `not-live` (or letting this change * `overall`) would send an agent to close a funded, running strategy over a shortfall a top-up * fixes. So it warns, the exit code stays whatever the deploy earned, and the remediation leads * with the NON-destructive route. * * It deliberately emits NO teardown command. `close.py`'s correct scope depends on state this note * does not own — adopted siblings, wallets stranded with no runtime, an `[E_ROLLBACK_INCOMPLETE]` * line already claiming the cleanup — and the report already has exactly one producer of close * commands ({@link buildBudgetEscape}). A second, differently scoped teardown beside it is the * self-contradiction the taxonomy's rule 5 exists to prevent, and an agent reconciling two of them * runs whichever executes. The destructive option is named as an option and routed through * read-only triage, which is the only command here and cannot destroy anything. * * Two properties of the note are load-bearing and easy to lose: * - `reassure` — the "nothing is broken, the strategy is LIVE" sentence is a CLAIM about the rest * of the report, so it is only made when the report PROVES it: `overall === "live"` and nothing * less (see the gate at the call site). Asserted unconditionally it rode `failed` and `pending` * reports saying the opposite two lines above; gated only loosely it rode * `installed-unobserved` ones, where no tick was ever seen. * - the figures are DATED and the top-up is routed through a fresh read. This note persists on the * job snapshot and re-renders on every later `deploy status`, and unlike every other budget note * it asserts a MUTABLE backend quantity whose fix is exactly the action that invalidates it — so * an undated "short $440.00" invites a second $440 top-up after the first one landed. * - PROVENANCE, exactly as its unreadable sibling carries it. This note is reachable on the * ADOPTION path too — the ask is looked up from a prior run's journal for the strategy this run * adopted — where "SHORT of what this deploy requested … funded $60.00 of requested $500.00" * named a request this run never made about a wallet it never funded ($500 was the earlier run's * ask; this one asked for nothing and created nothing). The lead attributes the ask to no one and * each wallet states its own origin, so a reader can correct the figure from the note itself. */ export declare function buildPartialFundNote(pkg: DeployPackage, rows: PartialFundedWallet[], reassure: boolean): string; /** * The unreadable-read sibling of {@link buildPartialFundNote}: the ask is known, the landed amount * is not, so this note makes NO percentage claim, NO dollar shortfall, and asks for no money move. * * The failure it replaces: the client coerced a missing/unparseable `totalFunded` to `0`, which * turned one bad read into `funded $0.00 of requested $500.00 (0%)` plus an instruction to add * $500 — against a wallet that may already hold the full amount, so following it doubled the * deployment. An unread figure is not a small figure; the only honest next step is for the USER to * establish what is actually on the wallet. * * `reassure` is the same earned-claim gate its sibling carries, and for the same reason: "the * create succeeded and the strategy is running" is a claim about the REST of the report, and this * note is reachable on `failed` and `pending` reports through the adoption path — asserted * unconditionally it rode a verdict saying the opposite two lines above. Without it the note keeps * every fact it owns (the amount is unreadable, verify before moving money) and makes no claim * about what the deploy left running. * * PROVENANCE is the other claim it may not invent, and it is claimed PER WALLET only. The note is * reachable on the ADOPTION path — a journaled ask is looked up for exactly the strategy a prior * run created — so "wallet(s) this deploy created" described a create that never happened on a * crash-resume that adopted, created nothing and funded nothing, handing the reader a second wallet * that does not exist. Each wallet therefore states its own origin from * {@link UnreadableFundedWallet.created}. * * No sentence here generalises that over the deploy. `rows` is a FILTERED set — the wallets whose * funded figure came back unreadable — so nothing about the OTHER wallets can be read out of it, * and a `rows.every(...)` looks exactly like a deploy-level fact while being one about the subset: * a deploy that created `main` (readable $300) and adopted `scalp` (unreadable) has `rows = * [scalp]`, and "this deploy created no wallet" is then flatly false about the $300 it just moved. * The only deploy-level claim allowed is the one `reassure` proves — the report says `live`, so the * strategy is running — and it names no create and no adoption. */ export declare function buildFundedUnreadableNote(pkg: DeployPackage, rows: UnreadableFundedWallet[], reassure: boolean): string; export declare function buildDecisionModelRefusal(instance: string, pkg: DeployPackage, budget?: number): string; export declare function buildDecisionModelEnvRefusal(instance: string, pkg: DeployPackage): string; /** What the blind-runtime teardown actually achieved — the detail is computed from it, not assumed. */ export type RuntimeRemoval = { ok: true; } | { ok: false; error: string; }; /** * An install that came up with NO live entry scanners (`payload.unwired`). The runtime came UP and * is BLIND, and every one of the three phases is deterministic on this box — so this is an install * failure, not a tick that has not landed yet. * * Says what is true (it will never tick), what was NOT done (no scanner ever mounted), and — the * part that has to track reality — what is LEFT on the box. The two teardown outcomes leave * different states, so they never share a sentence: naming `senpi runtime delete` after a * successful delete points at a runtime that no longer exists, and omitting it after a failed one * hides a runtime still running against a wallet this job is about to close. * * The NEXT STEP is per-phase for the same reason. One ladder ("delete, fix, re-run") cannot serve * all three: only `install_wire` is fixable in the package, and on `no_intake` re-running is a loop * that cannot terminate — create → fund → install → unwired → close, moving real money out and * back on every iteration — because the cause is box-level and the intake context is built once at * plugin register. No emitted command may require a precondition the state lacks. */ export declare function buildUnwiredInstallDetail(params: { phase: string; runtimeId: string; wallet: string; /** false = the wallet predates this deploy, so no rollback follows and it stays live. */ createdByThisJob: boolean; removal: RuntimeRemoval; }): string; /** * The install step's detail when this deploy stopped waiting on an install it could not cancel. * * Every clause here is load-bearing. The install is IN-PROCESS work that takes no signal, so this * deploy giving up says nothing about what the install did — and a detail that guessed either way * would send a reader to close a wallet a runtime may have just come up on. So the claim is the * only one both reads support: the outcome is unknown. The read that resolves it is free and * non-destructive, so it leads; the money-moving step is conditional on its answer, and carries the * same live-sleeve caveat the rollback path uses, because the same package may hold other wallets. * * The detail forks on TWO facts, because one was not enough to make either instruction true. * * `createdByThisJob` (the same flag {@link buildUnwiredInstallDetail} branches on) decides whether a * close is even on the table. A wallet this job created was never closed BECAUSE the install might * still bind it — the causal claim, and the reclaim instruction, both hold, and the money is this * job's own to reclaim. A wallet this job ADOPTED predates the deploy: D-6 never closes an adopted * wallet regardless of how the install lands, so "would have closed it but for the race" is false on * that wallet, and `funded` is the caller's pre-existing balance — not a close target this deploy * has any standing to name. * * `stillInFlight` is the wallet's install FENCE, read as this detail is written, and it decides * whether ANY instruction may be given yet. `installRuntimeCore` writes its registry row and takes * its MCP wallet-ownership check BEFORE `run()` — the modal wedge — so at this instant the install * can be sitting anywhere in that sequence with the fence still held. Two things follow, and both * used to be wrong here: * - a registry ROW is not proof the install landed. The row is written first; `senpi.listRuntimes` * renders exactly that state `status: "stopped"`. The landed claim is therefore keyed on the * runtime showing RUNNING, never on a row existing. * - while the fence is held, no close may be named at all. An install wedged at the ownership * check has written NO row, so "if NO row names that wallet, reclaim the funds" pointed straight * at a `strategy_close` that races the install into the one state this whole carve-out exists to * prevent — a live runtime trading a closed strategy. * An UNREADABLE fence (`undefined`) takes the in-flight arm: it cannot be proven clear, and the * only unsafe mistake here is naming a close over an install that is still running. */ export declare function buildInstallIndeterminateDetail(args: { wallet: string; funded: number | undefined; runtimeId: string; deadlineMs: number; createdByThisJob: boolean; /** * Is an install still in flight on this wallet right now (`deps.installPendingOnWallet`)? * `undefined` = the fence could not be read, which is treated as "assume it is". */ stillInFlight: boolean | undefined; resume: string; caveat: string; }): string; /** * The install step's detail when this deploy found an install already running on the wallet. * * The refusal `installRuntimeCore` renders for the same condition is the enforcement copy; this one * exists because deploy reaches the condition one step earlier — before the dead-row branch that * would delete the in-flight install's registry row — and so can say what deploy did (nothing) as * well as what it found. Both name the same reads and neither names a delete, and this one now * carries the same install-bound and wedge-vs-restart facts the enforcement copy does — deploy is * the primary path, so it is the one most readers see, and a weaker message here would invert which * of the two is the source of truth. The one fact it deliberately does NOT reach parity on is elapsed * time: `installPendingOnWallet` reports a boolean, not when the install started, and the job's own * timing is already what `openclaw senpi deploy status` (named below) shows. */ export declare function buildInstallInFlightDetail(args: { wallet: string; runtimeId: string; }): string; /** * A strategy the ACTIVE poll watched go DEAD. Terminal, so the poll stops on it — and the claim it * replaces is the point: `buildCreatePendingDetail` would have said "waits for it to reach ACTIVE, * never creating a second wallet", and on this input neither half holds. It never reaches ACTIVE, * and reconcile filters dead statuses on BOTH match routes, so a re-run creates and funds a fresh * wallet beside it. The status only ever survived in the step evidence; here it leads the claim. */ export declare function buildCreateDeadDetail(pkg: DeployPackage, budget: number | undefined, live: StrategySummary, createdByThisJob: boolean): string; /** * A strategy the ACTIVE poll watched settle on a status that is neither ACTIVE nor on the way * there — PAUSED, mid-teardown, or one this build does not recognise. * * Only {@link INITIALIZING_STATUSES} resolve on their own, so only they are worth waiting on; * everything else burned the full `--max-wait` and was then reported with the bounded-wait detail's * "re-run; reconciliation waits for it to reach ACTIVE" promise, which none of them will honour. * * The REMEDIATION branches, and must: the create step gets no D-6 rollback (only an install failure * rolls a wallet back), so a wallet this job created and funded seconds ago is still live when this * is read. One shared "reclaim it FIRST with `strategy_close`" told an agent to tear down a wallet * on a status this build cannot interpret — the exact opposite of what * {@link buildNotDeployableRefusal} says for the same input at the reconcile seam ("will not guess * … NEVER close or recreate to 'start clean'"). The two seams now agree arm for arm: a teardown is * offered only where the status PROVES the strategy is not coming back. */ export declare function buildCreateNotDeployableDetail(pkg: DeployPackage, budget: number | undefined, live: StrategySummary, createdByThisJob: boolean, /** * The scoped start-over clause — see {@link buildPackageCloseOffer}. Used by the PAUSED arm * only. Completes the sentence "If you meant to start over instead, …" and carries its own * terminal period; the arm appends nothing to it. */ closeOffer: string): string; /** * Is the NAME route even open for this row — i.e. does it carry the name a re-run will look up? * * Reconcile has two match routes: `strategyName` (case-folded, non-empty on both sides) and a * create key journaled by THIS box. This is the name comparison out of `resolveInstanceBinding`, * and it is a NECESSARY condition for that route, never a sufficient one. Reconcile applies three * further filters this function cannot see, all of them about rows other than the one held here: * it drops `DEAD_STATUSES` before comparing, it refuses when the surviving list carries a foreign * `skillName` stamp (`E_WALLET_OWNED_BY_OTHER_PACKAGE`), and it refuses when more than one * candidate answers to the name (`E_STATE_AMBIGUOUS_WALLETS`). All three are executed and all three * create nothing — so a `false` here means "the name route is closed", while a `true` means only * "it is not closed by the name", which is why the sentence it feeds states its outcome as * conditional rather than as a fact. * * A `false` is not hypothetical: the unnamed create retry sends no `strategyName` at all (it is * `.optional()` in the MCP schema), so the backend names that wallet itself and the row comes back * under a name this package will never derive. */ export declare function reconcileCanMatchByName(wantedName: string, live: StrategySummary): boolean; /** * The bounded wait ran out. What this renders is what it READ, plus the one thing it can promise. * * The poll's success test is ACTIVE **and** a readable wallet address; everything else that is not * ACTIVE is intercepted before the deadline (dead → `failed`, neither-ACTIVE-nor-initializing → * `failed`), and reconcile refuses those statuses one screen earlier. So what lands here is either * a row still on its way to ACTIVE, or a row that already READS ACTIVE whose address could not be * read — plus the degenerate reads (no row at all, unparseable `createdAt`), which keep the * initializing branch's wording because they establish nothing to say instead. The two payloads * differ in what is MISSING, and that is the one place they diverge here: ACTIVE for the first, the * wallet address for the second. * * It states no outcome for a re-run beyond the money invariant, and the reason is structural. This * builder holds ONE row, read by id. How many live rows answer to the name a re-run looks up, what * stamps THOSE rows carry, whether a create key journaled on this box still resolves, and whether * the address becomes readable are facts about things it did not read and must not read here — a * detail that can throw is a detail that sometimes does not render. Four rounds enumerated those * outcomes anyway: the enumeration grew to 16 predicted branches across the four arms and was still * false on six executed payloads, twice along axes its stated conditions never named. Predicting * from a read that cannot see the deciding facts does not converge, so this renders none of it. * * What survives is {@link noSecondWalletWhileNamedLive} / {@link secondWalletRiskWhenNameRouteClosed} * — one class statement about money per arm, scoped to this strategy and executed at its boundary * rather than reasoned — then {@link FACTS_THAT_DECIDE}, which names facts WITHOUT attaching * outcomes to them or claiming to have listed them all, and {@link triageThenNeverClose}, which * carries the only next step that is correct whatever the next read finds, the shape of the failure * this is all about ({@link oneWalletPerStrategy}), and the one act that must never follow. Every * arm ends on those two, because the tail is what a reader in a hurry acts on. */ export declare function buildCreatePendingDetail(pkg: DeployPackage, budget: number | undefined, waitedSec: number, live: StrategySummary | undefined, nowMs: number, wantedName: string): string; /** * Live wallets carry this package's stamp, and none of them answered for this instance. * * Rendered from the reconcile read alone — no additional network calls, because a gate that can * fail to render is a gate that sometimes does not fire. Every column is quoted from that read and * says so when it is unreadable: an unread funded amount is the one number a reader would otherwise * take for an empty wallet. The name column is there because a missing/differing name is WHY the * match failed, and it is the fact that tells a reader whether this is old-tooling residue or a * backend that renamed what deploy created. * * The destructive route is named LAST and behind the read-only one, with its real blast radius on * the same line: a refusal that names only a teardown gets followed. * * The consequence sentence is hedged for the same reason the columns are: this read is metadata, * so "left funded, in position and supervised by nothing" would assert two things nothing here * looked at (no position read, no `registryFindById`/`runtimeIsLive` call) — and the second can be * plainly false, since install rebinds only the registry row for `-` and a wallet * carrying a different runtime id keeps its runtime. What is unread is named as unread; that is * the argument for stopping, not a weaker version of it. */ export declare function buildBindingUnknownRefusal(pkg: DeployPackage, instance: string, wantedName: string, candidates: StrategySummary[], budget: number | undefined): string; /** * Every live strategy under the name this instance answers to is stamped for a DIFFERENT package. * * The counterpart to {@link buildBindingUnknownRefusal} on the other match route, and the harder of * the two to word: nothing here is the operator's to close. The wallets named belong to another * package — its positions, its balance, possibly its running runtime — so the only routes offered * are edits to THIS package and reads. Re-running is explicitly not one of them: the name is * derived, so it comes out identical every time, which is exactly why the collision is structural * rather than transient. * * Neither is "set `id:` back to the stamp and re-run", which this refusal used to offer. The name * is derived FROM the id ({@link walletName}: `sanitizeStrategyName(id)`, or `-` for * a multi-sleeve package), and a loadable id is sanitize-stable by construction * (`assertIdentityRoundTrips`), so changing the id to the stamp changes the derived name by exactly * as much — the run after the edit looks for a name the matched wallet does not carry, finds * nothing, and refuses again from the gate instead, whose remedy is the whole-package `close.py` * this refusal's last line forbids. Only the id AND the sleeve layout restored together re-derive * the matched name, which is a restructure of this package, not a field edit — so the route ends at * the read and hands the decision to the user. */ export declare function buildForeignPackageRefusal(pkg: DeployPackage, instance: string, wantedName: string, candidates: StrategySummary[], budget: number | undefined): string; /** * Refusal for a package whose instances ship no DSL exit block. Rendered BEFORE any wallet exists: * funding a strategy that cannot stop itself out is the "funded-but-no-DSL" hole, and the skills-side * validator has always refused it. */ export declare function buildNoDslExitRefusal(instances: DeployInstance[], pkg: DeployPackage): string; /** * A recipe that does not bind the wallet its manifest promises, refused before a wallet exists. * * Names the two addresses side by side because the failure is precisely that they are two: the * money this run would move, and the account the strategy would trade. It states the edit rather * than a command, in the {@link buildInstanceDirCollisionRefusal} spirit — the fix is one line in a * file the reader already has open, and no command can make it for them. */ export declare function buildUnboundWalletRefusal(faults: WalletBindingFault[], pkg: DeployPackage): string; /** * A package id carrying capitals, refused before any wallet exists to carry the wrong stamp. * * Deploy's copy of `register.ts`'s gate — same rule, same fix (built from the one producer in * `package.ts`, so the edit an author reads is word-for-word identical), different standing * sentence: nothing was CREATED here, where the gateway's copy could only say nothing was started. * * The re-run command names the directory the fix LEAVES BEHIND, not the one that failed — this is * the only refusal in the verb whose own instruction can move the package. */ export declare function buildNonLowercaseIdRefusal(pkg: DeployPackage, budget?: number): string; /** * The refusal for a manifest whose funding shares allocate more than the whole budget. * * States the arithmetic and nothing about the outcome: the sum, every sleeve's effective share, and * the amount the plan would have moved against the amount authorized. A reader can check each * figure against the file named, which is the point — the defect is a typo in one line of * `strategy.yaml`, and the fastest fix is seeing which share is wrong. * * The fix is the author's, and only they can make it: which sleeve should be smaller is a decision * about the strategy, not one this gate can compute. So it names the constraint (the shares are a * split of ONE budget) and the file, and offers no edit of its own — a hint naming a particular * share would be a guess presented as an instruction. Deliberately no teardown route either: * nothing was created, so there is nothing to tear down. */ export declare function buildShareOverrunRefusal(overrun: ShareOverrun, pkg: DeployPackage, budget: number): string; /** * The install gate's refusal, rendered for deploy rather than for the gateway. * * Same findings, different standing sentence: the gateway's copy says a recipe cannot be installed, * which is all that is true there. Here the run had money to move and did not move it, and that is * the fact a reader needs first — so it is stated, and the re-check names `senpi validate` on the * instance directory rather than "run validate on the package" in the abstract. * * Deploy's refusal shape: bracketed code first (taxonomy.ts:16 reads it), then what/why/fix. */ export declare function buildInstallGateRefusal(gate: InstallGateResult, inst: DeployInstance, pkg: DeployPackage, budget?: number): string; /** * The gate itself could not run. NOT a verdict on the recipe — so it carries no taxonomy code and * names no offending field, exactly like the universe check's `unavailable` branch. */ export declare function buildInstallGateUnavailableDetail(inst: DeployInstance, reason: string): string; /** * D-2 (revised 2026-08-05): a scanner-level `enabled` key is refused outright — the engine never * reads it (see {@link scannersDeclaringEnabled}), so accepting `enabled: false` would ship a * strategy that ticks and trades while its author believes it is switched off. Package-derivable, * so it is decided synchronously here rather than discovered later; observe keeps its own * all-disabled branch for the live-state case, which the runtime API can genuinely produce. * * Every offense is enumerated in ONE refusal, each as a delete-this-line instruction naming the * key, the scanner, the instance and the file: a reader (agent or human) fixes the whole package in * one pass instead of re-running to discover the next one. */ export declare function buildScannerEnabledUnsupportedRefusal(offenders: ScannerEnabledKey[], pkg: DeployPackage): string; /** * A create this job stopped waiting on at its per-call deadline. Deliberately NOT phrased as * "creation failed": the deadline bounds the WAIT, not the backend's work, so the wallet may come * into existence after this line is written. Naming both reconcile routes — and the one case * neither can see — is what lets the operator check rather than guess. */ export declare function buildCreateAbandonedDetail(args: { instance: string; strategyName: string; /** True when the abandoned call was the UNNAMED retry after a name rejection — see below. */ nameFallbackFired: boolean; fundAmount: number; message: string | undefined; pkg: DeployPackage; budget?: number; }): string; /** * The name-rejection retry that was NOT issued because the deadline stop landed between the two * creates. * * Not a variant of {@link buildCreateAbandonedDetail}: nothing was abandoned mid-call here. One * create was issued and the backend answered it; the second one — the money-moving call this run * would otherwise have made with no step boundary in front of it — never went out. So the line says * what was attempted, what was not, and leaves the rejected create's own residue to the same check * every rejection carries: reconcile re-reads the live list before it creates anything. */ export declare function buildCreateNameRetryNotIssuedDetail(args: { instance: string; strategyName: string; fundAmount: number; message: string | undefined; pkg: DeployPackage; budget?: number; }): string; export declare function buildUnobservedDetail(waitSec: number, runtimeId: string): string; export {}; //# sourceMappingURL=messages.d.ts.map