export interface DeployInstance { /** "main" for flat packages. */ name: string; runtimeYamlPath: string; /** Absolute dir of the runtime.yaml — the base relative scanner paths resolve against. */ runtimeYamlDir: string; /** The `${...}` token substituted with the instance's wallet address at render. */ walletEnv: string; /** null = even split across instances. */ fundingShare: number | null; } export interface DeployPackage { id: string; version: string; dir: string; /** `defaults.decision_model_env` — the token `--decision-model` substitutes. */ decisionModelEnv: string | null; /** * The manifest's `catalog:` block verbatim ({} when absent). Read by the calculated-minimum * warning (`min-budget.ts`) for `catalog.min_budget_floor`, the authored number that raises a * package's minimum above what its sizing implies. */ catalog: Record; instances: DeployInstance[]; } /** Port of deploy.py `_sanitize_strategy_name`: whitespace→'-', keep [A-Za-z0-9_-], trim '-', cap 40. */ export declare function sanitizeStrategyName(name: string): string; /** * The package id when it is NOT already lowercase — the offending spelling, or null. * * `createWallet` stamps `skillName: pkg.id` VERBATIM at create while the backend stores what it is * given case-normalized ("WARPATH" in, "warpath" back), so a mixed-case id is written under one * spelling and read back under another. That is how `close.py ''` came to report "no OPEN * strategies to close." over a live, funded, trading wallet. Both sides now case-FOLD every stamp * compare, which is the half that finds wallets already stamped in any case; requiring one * canonical spelling is the half that keeps the next reader from having to remember to. * * Deliberately NOT a load refusal, and deliberately not part of {@link assertIdentityRoundTrips}: * a mixed-case id round-trips through `sanitizeStrategyName` perfectly — capitals are legal there. * What it breaks is the STAMP, which is minted on the deploy path, so that is where the gate goes * (`register.ts`'s `localStartRefusal` and the orchestrator's own copy, both pre-money). * `loadDeployPackage` is shared with two reads that are not creates: `senpi deploy status`'s * interrupted render — the one surface that tells an operator which wallets a crashed deploy left * FUNDED — and `senpi validate`'s target resolution. Refusing at load would blank that render for * a package that already has money in the ground. Same call the skills side made for the same * reason: the rule lives in `_pkg.validate`, which `deploy.py` runs before every money verb, and * never in `_pkg.load`, which `close.py` needs in order to tear a package down. * * `senpi validate` reports it too, at static depth (`E_VALIDATE_PACKAGE_ID_CASE`), rendering its * fix from {@link lowercasePackageIdFix} so both surfaces name the identical edit. Note the ops * ordering that creates: the redeploy loop is `validate → close → create`, so the finding fires * before anything is closed and the id is lowercased on a package that may already own funded * wallets. That is safe only because both stamp comparisons case-FOLD. */ export declare function nonLowercasePackageId(pkg: DeployPackage): string | null; /** * The fix for a mixed-case id: the edit(s) to make, and the directory the package lives in once * they are made. ONE producer of both, so a refusal's re-run command can never name a path that * the same refusal's instruction just moved. * * The rename is emitted only when the directory IS this id in another case. The skills validator * requires `id == package dir`, so lowercasing the id alone would trade this refusal for an * `id != package dir` one over there, and an author must not have to guess which of the two to * change. A directory that already differs from the id is a separate, pre-existing mismatch that * this fix neither causes nor may claim to fix. */ export declare function lowercasePackageIdFix(id: string, packageDir: string): { instruction: string; dirAfterFix: string; }; export declare function loadDeployPackage(packageDir: string): DeployPackage; /** The backend strategy name for an instance — also the reconcile key. */ export declare function walletName(pkg: DeployPackage, inst: DeployInstance): string; /** The instance's runtime.yaml exactly as authored — pre-substitution, for pre-render inspection. */ export declare function readRuntimeYaml(inst: DeployInstance): string; /** An instance whose recipe does not bind the wallet its manifest declares — see {@link unboundWalletInstances}. */ export interface WalletBindingFault { instance: DeployInstance; /** `strategy.wallet` as authored, or undefined when the recipe declares none. */ declared: string | undefined; /** What it has to be: `${}`. */ expected: string; } /** * Instances whose recipe does not bind the wallet `strategy.yaml` says it binds. * * `wallet_env` is a promise the manifest makes on behalf of the recipe, and nothing used to check * that the recipe keeps it. `renderRuntimeYaml` substitutes by string replacement, so a declared * token that never appears substitutes NOTHING — silently, because the leftover-placeholder gate * only looks for surviving `${...}` and a hardcoded address leaves none. A package declaring * `wallet_env: UNUSED` beside a recipe pinning an address it chose therefore had `senpi deploy` * create and fund a fresh wallet from the user's budget and then install the runtime — actions, * DSL exit engine and all — against the OTHER address. The install step named the recipe's wallet, * so the report read clean while the funded wallet sat with nothing watching it. * * Read from the PARSED recipe, not by searching its text: a token that appears anywhere at all * satisfies a text search, substitutes harmlessly into some field nobody reads, and leaves the * leftover gate with nothing to catch. Only `strategy.wallet` decides what the runtime binds. * * Flat packages cannot fault here and are not special-cased: `synthesizeFlatInstance` derives * `walletEnv` from this very field, so the invariant holds by construction. * * Never throws. A recipe that will not parse is a fault the YAML-syntax gate already owns and * words properly — restating it here in worse words helps nobody — and this check is called from * places (validate's static depth, deploy's preflight) where taking the whole run down over one * unreadable file would be the wrong answer. */ export declare function unboundWalletInstances(pkg: DeployPackage): WalletBindingFault[]; /** * Render an instance's runtime.yaml for install. Only the wallet token and (when * supplied) the decision-model token are substituted; anything else left as * `${...}` is a packaging bug and throws rather than reaching the runtime, where * it would install as a literal. */ export declare function renderRuntimeYaml(inst: DeployInstance, wallet: string, decisionModel?: { env: string; model: string; }): string; /** * True iff the instance ships a DSL exit block — `exit.dsl_preset`, or `exit.engine: dsl`. * * A deployed strategy without one runs every position naked: no hard stop, no trailing floor. The * skills-side `_pkg.validate` has always refused to fund such a package, and `senpi deploy` is now * the taught path, so the same gate has to live here or the hole reopens on the direct-verb route. */ export declare function instanceHasDslExit(inst: DeployInstance): boolean; /** * True iff a PARSED recipe ships a DSL exit block — `exit.dsl_preset`, or `exit.engine: dsl`. * * Split from {@link instanceHasDslExit} so `senpi validate` can ask the same question of the config * it has already parsed, rather than re-reading and re-parsing the file — and rather than growing a * second predicate that could quietly disagree with this one about, say, `dsl_preset: true`. Same * reason {@link isTruthyPreset} is exported for `runtime-descriptor.ts`. * * Takes `unknown` because its two callers hold different things: a `parseYaml` result here, and a * schema-parsed `RuntimeConfig` in validate. Both are read the same way, and neither is trusted to * have the shape — but note their INPUTS are not identical: validate resolves `${ENV}` before * parsing (`recipe.ts`), deploy does not, so `exit: { engine: "${EXIT_ENGINE}" }` reads as `dsl` * here only after substitution and as the literal token on the deploy path. Validate's reading is * the more accurate one (that is what would install); the divergence is narrow and pre-existing, * and this is the note that stops the next reader concluding the two can never disagree. */ export declare function hasDslExitBlock(parsed: unknown): boolean; /** * Python-`bool()` semantics for a YAML value: a non-empty string, map or list — and `true`, which * `bool()` also accepts. Without the boolean arm a package written `dsl_preset: true` would validate * green on the skills side and be refused here, and the two predicates must not disagree. * * Exported so `runtime-descriptor.ts` can reuse this exact predicate for `hasExit` rather than grow * a second copy that could quietly drift from this one. */ export declare function isTruthyPreset(value: unknown): boolean; /** * Instances in `pkg` that ship no DSL exit block. Empty = every instance is protected. * * The instances themselves, not their names: both refusals built from this name the re-check * command for each offender, and that command takes a directory the instance carries. A name is not * a path, and re-deriving the instance from its name at the refusal site is a lookup that can fail. * * `senpi validate` reports the same fault at static depth (`E_VALIDATE_NO_DSL_EXIT`, via * {@link hasDslExitBlock} in `validate/package-gates-stage.ts`), so the re-check command these * refusals emit agrees with them. Do not add a third reading of this fact. */ export declare function instancesWithoutDslExit(pkg: DeployPackage): DeployInstance[]; /** One scanner entry that carries an `enabled` key, whatever its value. */ export interface ScannerEnabledKey { instance: string; /** The scanner's `name` (or legacy `id`), or `scanners[]` when it declares neither. */ scanner: string; /** The value exactly as YAML parsed it — quoted back to the author so the line is findable. */ value: unknown; /** The file the line lives in, so the fix needs no search. */ runtimeYamlPath: string; } /** * Scanner entries in `pkg` that declare an `enabled` key at all. * * Scanner-level `enabled` is NOT a supported key: `scannerEntrySchema` is `.passthrough()` and does * not declare it, `buildScannerOverrides` (map-strategies-registration.ts) forwards only * `interval_seconds`/`config`, and `StrategyScannerRegistry.register` derives `enabled` from * `strategy.defaultEnabledScanners` — which nothing writes from a recipe. So the engine never reads * it: a scanner written `enabled: false` registers ENABLED and ticks. Silently accepting the key * means shipping a strategy that trades while its author believes it is switched off, which is why * every value refuses (a `true` is just as unread as a `false`, and tomorrow's reader would take * the accepted `true` as proof the key works). * * The live-state twin of this is the observe step's all-disabled branch, which reads engine rows * rather than YAML — the runtime API can genuinely disable a scanner after install. * * `senpi validate` reports the same key at static depth (`E_VALIDATE_SCANNER_ENABLED`, via * {@link scannerEnabledEntries}), one finding per offending entry, so an author never first learns * about it from the money path. */ export declare function scannersDeclaringEnabled(pkg: DeployPackage): ScannerEnabledKey[]; /** One scanner entry carrying an `enabled` key, as read from a parsed recipe. */ export interface ScannerEnabledEntry { /** The scanner's `name` (or legacy `id`), or `scanners[]` when it declares neither. */ scanner: string; /** Position in the `scanners:` list — what a finding's `yaml_path` points at. */ index: number; /** The value exactly as YAML parsed it. */ value: unknown; } /** * Scanner entries in a PARSED recipe that declare an `enabled` key at all. * * The instance-free core of {@link scannersDeclaringEnabled}, so validate can ask it of a recipe * that has no package around it yet — the mid-authoring case, where the fault is just as real and * the author is exactly the person who should hear about it. */ export declare function scannerEnabledEntries(parsed: unknown): ScannerEnabledEntry[]; /** * One offense as an instruction: the key, the scanner, the instance, the file and the exact line to * delete. Shared by both gate copies so the mechanical fix an agent reads is word-for-word the same * wherever the refusal is raised. */ export declare function describeScannerEnabledOffense(entry: ScannerEnabledKey): string; /** * The offending line as it appears in the YAML — a bare `enabled:` has no value to echo. * * Exported so validate's finding quotes the author's own line with the same rendering deploy's * refusal uses. Two surfaces telling someone to delete "the line `enabled: false`" must agree on * what that line says, or one of them is describing a line that is not in the file. */ export declare function renderScannerEnabledLine(value: unknown): string; /** True iff any action runs `decision_mode: llm` — those installs need a decision model. */ export declare function runtimeUsesLlmDecisions(runtimeYamlText: string): boolean; //# sourceMappingURL=package.d.ts.map