@senpi/runtime 3.0.59 → 3.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +73 -2
  2. package/dist/actions/decision-engine.d.ts +0 -2
  3. package/dist/actions/decision-engine.d.ts.map +1 -1
  4. package/dist/actions/decision-engine.js +1 -79
  5. package/dist/actions/decision-engine.js.map +1 -1
  6. package/dist/cli/deploy-render.d.ts +91 -0
  7. package/dist/cli/deploy-render.d.ts.map +1 -0
  8. package/dist/cli/deploy-render.js +216 -0
  9. package/dist/cli/deploy-render.js.map +1 -0
  10. package/dist/cli/guide-commands.d.ts.map +1 -1
  11. package/dist/cli/guide-commands.js +78 -6
  12. package/dist/cli/guide-commands.js.map +1 -1
  13. package/dist/cli/senpi-commands.d.ts +6 -1
  14. package/dist/cli/senpi-commands.d.ts.map +1 -1
  15. package/dist/cli/senpi-commands.js +244 -8
  16. package/dist/cli/senpi-commands.js.map +1 -1
  17. package/dist/cli/validate-command.d.ts.map +1 -1
  18. package/dist/cli/validate-command.js +32 -1
  19. package/dist/cli/validate-command.js.map +1 -1
  20. package/dist/deploy/deadlines.d.ts +47 -0
  21. package/dist/deploy/deadlines.d.ts.map +1 -0
  22. package/dist/deploy/deadlines.js +56 -0
  23. package/dist/deploy/deadlines.js.map +1 -0
  24. package/dist/deploy/error-text.d.ts +25 -0
  25. package/dist/deploy/error-text.d.ts.map +1 -0
  26. package/dist/deploy/error-text.js +42 -0
  27. package/dist/deploy/error-text.js.map +1 -0
  28. package/dist/deploy/funding.d.ts +87 -0
  29. package/dist/deploy/funding.d.ts.map +1 -0
  30. package/dist/deploy/funding.js +229 -0
  31. package/dist/deploy/funding.js.map +1 -0
  32. package/dist/deploy/install-gate-runner.d.ts +12 -0
  33. package/dist/deploy/install-gate-runner.d.ts.map +1 -0
  34. package/dist/deploy/install-gate-runner.js +17 -0
  35. package/dist/deploy/install-gate-runner.js.map +1 -0
  36. package/dist/deploy/job.d.ts +199 -0
  37. package/dist/deploy/job.d.ts.map +1 -0
  38. package/dist/deploy/job.js +463 -0
  39. package/dist/deploy/job.js.map +1 -0
  40. package/dist/deploy/min-budget.d.ts +68 -0
  41. package/dist/deploy/min-budget.d.ts.map +1 -0
  42. package/dist/deploy/min-budget.js +297 -0
  43. package/dist/deploy/min-budget.js.map +1 -0
  44. package/dist/deploy/orchestrator.d.ts +305 -0
  45. package/dist/deploy/orchestrator.d.ts.map +1 -0
  46. package/dist/deploy/orchestrator.js +2910 -0
  47. package/dist/deploy/orchestrator.js.map +1 -0
  48. package/dist/deploy/package.d.ts +130 -0
  49. package/dist/deploy/package.d.ts.map +1 -0
  50. package/dist/deploy/package.js +351 -0
  51. package/dist/deploy/package.js.map +1 -0
  52. package/dist/deploy/proof-gate.d.ts +82 -0
  53. package/dist/deploy/proof-gate.d.ts.map +1 -0
  54. package/dist/deploy/proof-gate.js +269 -0
  55. package/dist/deploy/proof-gate.js.map +1 -0
  56. package/dist/deploy/python-float.d.ts +34 -0
  57. package/dist/deploy/python-float.d.ts.map +1 -0
  58. package/dist/deploy/python-float.js +46 -0
  59. package/dist/deploy/python-float.js.map +1 -0
  60. package/dist/deploy/recheck-commands.d.ts +50 -0
  61. package/dist/deploy/recheck-commands.d.ts.map +1 -0
  62. package/dist/deploy/recheck-commands.js +59 -0
  63. package/dist/deploy/recheck-commands.js.map +1 -0
  64. package/dist/deploy/register.d.ts +99 -0
  65. package/dist/deploy/register.d.ts.map +1 -0
  66. package/dist/deploy/register.js +689 -0
  67. package/dist/deploy/register.js.map +1 -0
  68. package/dist/deploy/taxonomy.d.ts +24 -0
  69. package/dist/deploy/taxonomy.d.ts.map +1 -0
  70. package/dist/deploy/taxonomy.js +31 -0
  71. package/dist/deploy/taxonomy.js.map +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +244 -132
  74. package/dist/index.js.map +1 -1
  75. package/dist/runtime/strategy-registry.d.ts +8 -0
  76. package/dist/runtime/strategy-registry.d.ts.map +1 -1
  77. package/dist/runtime/strategy-registry.js +14 -1
  78. package/dist/runtime/strategy-registry.js.map +1 -1
  79. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  80. package/dist/runtime-api/intake/intake-mount.js +7 -4
  81. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  82. package/dist/scanners/implementations/market-regime.d.ts +1 -1
  83. package/dist/senpi/client.d.ts +58 -4
  84. package/dist/senpi/client.d.ts.map +1 -1
  85. package/dist/senpi/client.js +192 -15
  86. package/dist/senpi/client.js.map +1 -1
  87. package/dist/senpi/strategy-status.d.ts +32 -0
  88. package/dist/senpi/strategy-status.d.ts.map +1 -0
  89. package/dist/senpi/strategy-status.js +70 -0
  90. package/dist/senpi/strategy-status.js.map +1 -0
  91. package/dist/senpi/types.d.ts +47 -3
  92. package/dist/senpi/types.d.ts.map +1 -1
  93. package/dist/skills-manager/manifest.js +1 -1
  94. package/dist/skills-manager/skills-manifest.json +1 -1
  95. package/dist/telemetry/metrics.d.ts +23 -0
  96. package/dist/telemetry/metrics.d.ts.map +1 -1
  97. package/dist/telemetry/metrics.js +65 -0
  98. package/dist/telemetry/metrics.js.map +1 -1
  99. package/dist/types/strategy.d.ts +9 -1
  100. package/dist/types/strategy.d.ts.map +1 -1
  101. package/dist/universe/package-universe.d.ts +97 -0
  102. package/dist/universe/package-universe.d.ts.map +1 -0
  103. package/dist/universe/package-universe.js +275 -0
  104. package/dist/universe/package-universe.js.map +1 -0
  105. package/dist/validate/codes.d.ts +20 -0
  106. package/dist/validate/codes.d.ts.map +1 -1
  107. package/dist/validate/codes.js +36 -0
  108. package/dist/validate/codes.js.map +1 -1
  109. package/dist/validate/import-stage.js +5 -5
  110. package/dist/validate/import-stage.js.map +1 -1
  111. package/dist/validate/index.d.ts +1 -1
  112. package/dist/validate/index.d.ts.map +1 -1
  113. package/dist/validate/index.js.map +1 -1
  114. package/dist/validate/install-gate.d.ts.map +1 -1
  115. package/dist/validate/install-gate.js +10 -2
  116. package/dist/validate/install-gate.js.map +1 -1
  117. package/dist/validate/live-instruments.d.ts +44 -0
  118. package/dist/validate/live-instruments.d.ts.map +1 -0
  119. package/dist/validate/live-instruments.js +71 -0
  120. package/dist/validate/live-instruments.js.map +1 -0
  121. package/dist/validate/live-stage.js +5 -5
  122. package/dist/validate/live-stage.js.map +1 -1
  123. package/dist/validate/proof.d.ts +11 -1
  124. package/dist/validate/proof.d.ts.map +1 -1
  125. package/dist/validate/proof.js +7 -6
  126. package/dist/validate/proof.js.map +1 -1
  127. package/dist/validate/recipe-checks.d.ts.map +1 -1
  128. package/dist/validate/recipe-checks.js +6 -4
  129. package/dist/validate/recipe-checks.js.map +1 -1
  130. package/dist/validate/run.d.ts +5 -0
  131. package/dist/validate/run.d.ts.map +1 -1
  132. package/dist/validate/run.js +10 -1
  133. package/dist/validate/run.js.map +1 -1
  134. package/dist/validate/target.d.ts +41 -0
  135. package/dist/validate/target.d.ts.map +1 -1
  136. package/dist/validate/target.js +192 -16
  137. package/dist/validate/target.js.map +1 -1
  138. package/dist/validate/universe-stage.d.ts +71 -0
  139. package/dist/validate/universe-stage.d.ts.map +1 -0
  140. package/dist/validate/universe-stage.js +183 -0
  141. package/dist/validate/universe-stage.js.map +1 -0
  142. package/openclaw.plugin.json +1 -1
  143. package/package.json +1 -1
  144. package/dist/runtime/managed-wallet-guard.d.ts +0 -47
  145. package/dist/runtime/managed-wallet-guard.d.ts.map +0 -1
  146. package/dist/runtime/managed-wallet-guard.js +0 -106
  147. package/dist/runtime/managed-wallet-guard.js.map +0 -1
  148. package/dist/runtime/protection-nudge.d.ts +0 -67
  149. package/dist/runtime/protection-nudge.d.ts.map +0 -1
  150. package/dist/runtime/protection-nudge.js +0 -244
  151. package/dist/runtime/protection-nudge.js.map +0 -1
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Strategy-package model for `senpi deploy` — the TS subset of the
3
+ * `strategy.yaml` + per-instance `runtime.yaml` layout that `deploy.py`'s
4
+ * `_pkg.py` owns on the skills side. Field names deliberately mirror the YAML
5
+ * so a reader can diff the two without a translation table.
6
+ *
7
+ * This module is pure: it reads files and throws teaching errors. It never
8
+ * touches the network, the registry, or money.
9
+ */
10
+ import { readFileSync, existsSync } from "node:fs";
11
+ import { basename, join, dirname, resolve } from "node:path";
12
+ import { parse as parseYaml } from "yaml";
13
+ import { pythonFloat } from "./python-float.js";
14
+ /** Platform cap on a strategy name; also the cap on a derived wallet/runtime id. */
15
+ const WALLET_NAME_MAX = 40;
16
+ /** Matches a whole-value `${TOKEN}` wallet binding in a flat package's runtime.yaml. */
17
+ const WALLET_TOKEN_RE = /^\$\{([A-Z0-9_]+)\}$/;
18
+ /** Any surviving `${TOKEN}` after render — an unresolved placeholder must never reach the runtime. */
19
+ const LEFTOVER_TOKEN_RE = /\$\{[A-Z0-9_]+\}/;
20
+ /** Port of deploy.py `_sanitize_strategy_name`: whitespace→'-', keep [A-Za-z0-9_-], trim '-', cap 40. */
21
+ export function sanitizeStrategyName(name) {
22
+ const collapsed = name
23
+ .trim()
24
+ .replace(/\s+/g, "-")
25
+ .replace(/[^A-Za-z0-9_-]/g, "")
26
+ .replace(/^-+|-+$/g, "");
27
+ return collapsed.slice(0, WALLET_NAME_MAX);
28
+ }
29
+ /**
30
+ * The identity that has to ROUND-TRIP is the COMBINED `<id>-<instance>`: the runtime registers
31
+ * `sanitizeStrategyName("<id>-<instance>")` (the orchestrator's install id and register.ts's live
32
+ * read both derive it, for flat packages too) while `close.py --instance` looks up the raw
33
+ * `<id>-<instance>` by exact string match. Checking the instance name alone left two ways through:
34
+ * a package id sanitization would change, and a combined identity past the 40-char cap — which the
35
+ * sanitizer TRUNCATES rather than rejects, so the sleeve installs fine and can never be closed by
36
+ * instance. Load time is the one seam where both consumers' assumption is made true by
37
+ * construction; both refusals teach the edit, like the instance-name one does.
38
+ */
39
+ function assertIdentityRoundTrips(id, name, manifestPath) {
40
+ if (sanitizeStrategyName(id) !== id) {
41
+ throw new Error(`strategy.yaml id "${id}" is not usable as a strategy/runtime id — use ` +
42
+ `"${sanitizeStrategyName(id)}" instead (letters, digits, "_" and "-" only, no spaces, no ` +
43
+ `leading/trailing "-") (${manifestPath}). The runtime registers the sanitized ` +
44
+ `"<id>-<instance>" while teardown looks up the raw one, so an id that changes here cannot ` +
45
+ `be closed by instance later — and the id is shell input in every emitted command.`);
46
+ }
47
+ const combined = `${id}-${name}`;
48
+ if (sanitizeStrategyName(combined) !== combined) {
49
+ throw new Error(`strategy.yaml id "${id}" + instance "${name}" combine to "${combined}" ` +
50
+ `(${combined.length} chars), over the ${WALLET_NAME_MAX}-char strategy/runtime id cap — ` +
51
+ `shorten the package id or the instance name so "<id>-<instance>" fits in ` +
52
+ `${WALLET_NAME_MAX} (${manifestPath}). The runtime would register a TRUNCATED id while ` +
53
+ `teardown looks up the full one, so this sleeve could never be closed by instance later.`);
54
+ }
55
+ }
56
+ /**
57
+ * The package id when it is NOT already lowercase — the offending spelling, or null.
58
+ *
59
+ * `createWallet` stamps `skillName: pkg.id` VERBATIM at create while the backend stores what it is
60
+ * given case-normalized ("WARPATH" in, "warpath" back), so a mixed-case id is written under one
61
+ * spelling and read back under another. That is how `close.py '<id>'` came to report "no OPEN
62
+ * strategies to close." over a live, funded, trading wallet. Both sides now case-FOLD every stamp
63
+ * compare, which is the half that finds wallets already stamped in any case; requiring one
64
+ * canonical spelling is the half that keeps the next reader from having to remember to.
65
+ *
66
+ * Deliberately NOT a load refusal, and deliberately not part of {@link assertIdentityRoundTrips}:
67
+ * a mixed-case id round-trips through `sanitizeStrategyName` perfectly — capitals are legal there.
68
+ * What it breaks is the STAMP, which is minted on the deploy path, so that is where the gate goes
69
+ * (`register.ts`'s `localStartRefusal` and the orchestrator's own copy, both pre-money).
70
+ * `loadDeployPackage` is shared with two reads that are not creates: `senpi deploy status`'s
71
+ * interrupted render — the one surface that tells an operator which wallets a crashed deploy left
72
+ * FUNDED — and `senpi validate`'s target resolution. Refusing at load would blank that render for
73
+ * a package that already has money in the ground. Same call the skills side made for the same
74
+ * reason: the rule lives in `_pkg.validate`, which `deploy.py` runs before every money verb, and
75
+ * never in `_pkg.load`, which `close.py` needs in order to tear a package down.
76
+ */
77
+ export function nonLowercasePackageId(pkg) {
78
+ return pkg.id !== pkg.id.toLowerCase() ? pkg.id : null;
79
+ }
80
+ /**
81
+ * The fix for a mixed-case id: the edit(s) to make, and the directory the package lives in once
82
+ * they are made. ONE producer of both, so a refusal's re-run command can never name a path that
83
+ * the same refusal's instruction just moved.
84
+ *
85
+ * The rename is emitted only when the directory IS this id in another case. The skills validator
86
+ * requires `id == package dir`, so lowercasing the id alone would trade this refusal for an
87
+ * `id != package dir` one over there, and an author must not have to guess which of the two to
88
+ * change. A directory that already differs from the id is a separate, pre-existing mismatch that
89
+ * this fix neither causes nor may claim to fix.
90
+ */
91
+ export function lowercasePackageIdFix(id, packageDir) {
92
+ const lower = id.toLowerCase();
93
+ const dirName = basename(packageDir);
94
+ const renames = dirName !== lower && dirName.toLowerCase() === lower;
95
+ return {
96
+ instruction: `set \`id: ${lower}\` in ${join(packageDir, "strategy.yaml")}` +
97
+ (renames
98
+ ? ` and rename the package directory "${dirName}" to "${lower}" — the id and the ` +
99
+ `directory name have to match`
100
+ : ""),
101
+ dirAfterFix: renames ? join(dirname(packageDir), lower) : packageDir,
102
+ };
103
+ }
104
+ function readYamlMapping(path) {
105
+ const parsed = parseYaml(readFileSync(path, "utf8"));
106
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
107
+ throw new Error(`${path} did not parse to a YAML mapping`);
108
+ }
109
+ return parsed;
110
+ }
111
+ /**
112
+ * `funding_share` as a number, or null when it is absent or unreadable.
113
+ *
114
+ * A QUOTED share (`funding_share: "0.4"`) parses, because the number this field feeds is computed
115
+ * with Python's `float()` on the skills side: `gen_catalog.py` bakes the package's `min_budget`
116
+ * into the discovery card via `min_budget._f`. Reading a quoted share as undeclared here would put
117
+ * the card and the verb out of step on that figure, and would additionally make `planFunding` split
118
+ * the budget evenly when the author asked for 60/40 — a real money decision, not just a display.
119
+ *
120
+ * What the skills side does NOT do is accept it everywhere. `_pkg.py:62` reads this field RAW, so a
121
+ * quoted share reaches `_pkg.validate`'s `sum(shares)` as a `str` and raises `TypeError`; and
122
+ * `gen_catalog.derive_funding_split` filters `isinstance(x, (int, float))` and DROPS it, so the
123
+ * card's `funding_split` still falls back while its `min_budget` uses the value. Parsing it here is
124
+ * therefore the closest single behaviour to a divided reference, not agreement with all of it — a
125
+ * quoted share is a packaging bug that `deploy.py validate` catches loudly, and the verb's job is
126
+ * to not silently mis-split money in the meantime.
127
+ *
128
+ * Shared with the min-budget port so the two readers cannot drift ({@link pythonFloat}, whose two
129
+ * documented gaps from `float()` apply here and are pinned in `package.test.ts`). Anything that
130
+ * does not parse stays null — the honest "not declared", which callers treat as an even split.
131
+ * Never NaN: that would poison every downstream sum and max silently.
132
+ */
133
+ const readFundingShare = pythonFloat;
134
+ function readDeclaredInstances(rawInstances, dir, manifestPath) {
135
+ return rawInstances.map((entry) => {
136
+ const name = String(entry.name ?? "");
137
+ const rel = String(entry.runtime ?? "");
138
+ const walletEnv = String(entry.wallet_env ?? "");
139
+ if (!name || !rel || !walletEnv) {
140
+ throw new Error(`strategy.yaml instance entries need name, runtime, wallet_env (${manifestPath})`);
141
+ }
142
+ // An instance name has to survive `sanitizeStrategyName` UNCHANGED, because two different
143
+ // consumers disagree about it otherwise: the runtime registers the sanitized
144
+ // `<id>-<instance>`, while `close.py --instance <name>` looks the sleeve up under the RAW one.
145
+ // A name that changes under sanitization therefore installs fine and can never be torn down by
146
+ // instance — and, since deploy's own refusals emit that command, it would hand the reader
147
+ // something that cannot work. It is shell input there too, so spaces are doubly unwelcome.
148
+ if (sanitizeStrategyName(name) !== name) {
149
+ throw new Error(`strategy.yaml instance name "${name}" is not usable as a strategy/runtime id — use ` +
150
+ `"${sanitizeStrategyName(name)}" instead (letters, digits, "_" and "-" only, no spaces, ` +
151
+ `max ${WALLET_NAME_MAX} chars). The runtime registers the sanitized form while teardown ` +
152
+ `looks up the raw one, so a name that changes here cannot be closed by instance later.`);
153
+ }
154
+ const runtimeYamlPath = resolve(dir, rel);
155
+ if (!existsSync(runtimeYamlPath)) {
156
+ throw new Error(`Instance "${name}" runtime file not found: ${runtimeYamlPath}`);
157
+ }
158
+ return {
159
+ name,
160
+ runtimeYamlPath,
161
+ runtimeYamlDir: dirname(runtimeYamlPath),
162
+ walletEnv,
163
+ fundingShare: readFundingShare(entry.funding_share),
164
+ };
165
+ });
166
+ }
167
+ /**
168
+ * Flat package: one `runtime.yaml` at the package root and no `instances[]` —
169
+ * the layout agents naturally scaffold. The wallet env is read back out of the
170
+ * `${...}` the runtime already binds (mirror of `_pkg._flat_instance`), so
171
+ * render substitutes the token the package actually uses.
172
+ */
173
+ function synthesizeFlatInstance(dir) {
174
+ const flatPath = join(dir, "runtime.yaml");
175
+ if (!existsSync(flatPath)) {
176
+ throw new Error(`No instances[] in strategy.yaml and no root runtime.yaml (${dir})`);
177
+ }
178
+ const flat = readYamlMapping(flatPath);
179
+ const strategy = (flat.strategy ?? {});
180
+ const wallet = String(strategy.wallet ?? "");
181
+ const token = WALLET_TOKEN_RE.exec(wallet.trim());
182
+ if (!token) {
183
+ throw new Error(`Flat package runtime.yaml strategy.wallet must be a \${WALLET_ENV} placeholder, got "${wallet}"`);
184
+ }
185
+ return { name: "main", runtimeYamlPath: flatPath, runtimeYamlDir: dir, walletEnv: token[1], fundingShare: null };
186
+ }
187
+ export function loadDeployPackage(packageDir) {
188
+ const dir = resolve(packageDir);
189
+ const manifestPath = join(dir, "strategy.yaml");
190
+ if (!existsSync(manifestPath)) {
191
+ throw new Error(`No strategy.yaml in ${dir}. senpi deploy takes a strategy PACKAGE directory ` +
192
+ `(strategy.yaml + per-instance runtime.yaml). For a bare runtime.yaml, use the internal ` +
193
+ `runtime create only if you know what you are doing — packages are the supported path.`);
194
+ }
195
+ const manifest = readYamlMapping(manifestPath);
196
+ const id = String(manifest.id ?? "");
197
+ const version = String(manifest.version ?? "");
198
+ if (!id || !version)
199
+ throw new Error(`strategy.yaml must declare id and version (${manifestPath})`);
200
+ const defaults = (manifest.defaults ?? {});
201
+ const decisionModelEnv = typeof defaults.decision_model_env === "string" ? defaults.decision_model_env : null;
202
+ // A non-mapping `catalog:` is a packaging mistake, not a reason to fail the load — it reads as an
203
+ // absent block, which only costs the advisory `min_budget_floor` override.
204
+ const rawCatalog = manifest.catalog;
205
+ const catalog = typeof rawCatalog === "object" && rawCatalog !== null && !Array.isArray(rawCatalog)
206
+ ? rawCatalog
207
+ : {};
208
+ const rawInstances = Array.isArray(manifest.instances)
209
+ ? manifest.instances
210
+ : [];
211
+ const instances = rawInstances.length > 0
212
+ ? readDeclaredInstances(rawInstances, dir, manifestPath)
213
+ : [synthesizeFlatInstance(dir)];
214
+ // After the per-instance name check: the combined identity covers the id and the length, for
215
+ // declared and flat (synthesized "main") instances alike.
216
+ for (const inst of instances)
217
+ assertIdentityRoundTrips(id, inst.name, manifestPath);
218
+ return { id, version, dir, decisionModelEnv, catalog, instances };
219
+ }
220
+ /** The backend strategy name for an instance — also the reconcile key. */
221
+ export function walletName(pkg, inst) {
222
+ const base = pkg.instances.length > 1 ? `${pkg.id}-${inst.name}` : pkg.id;
223
+ return sanitizeStrategyName(base);
224
+ }
225
+ /** The instance's runtime.yaml exactly as authored — pre-substitution, for pre-render inspection. */
226
+ export function readRuntimeYaml(inst) {
227
+ return readFileSync(inst.runtimeYamlPath, "utf8");
228
+ }
229
+ /**
230
+ * Render an instance's runtime.yaml for install. Only the wallet token and (when
231
+ * supplied) the decision-model token are substituted; anything else left as
232
+ * `${...}` is a packaging bug and throws rather than reaching the runtime, where
233
+ * it would install as a literal.
234
+ */
235
+ export function renderRuntimeYaml(inst, wallet, decisionModel) {
236
+ let text = readFileSync(inst.runtimeYamlPath, "utf8");
237
+ text = text.split(`\${${inst.walletEnv}}`).join(wallet);
238
+ if (decisionModel)
239
+ text = text.split(`\${${decisionModel.env}}`).join(decisionModel.model);
240
+ const leftover = LEFTOVER_TOKEN_RE.exec(text);
241
+ if (leftover) {
242
+ throw new Error(`Unresolved placeholder ${leftover[0]} in ${inst.runtimeYamlPath}. ` +
243
+ `Only \${${inst.walletEnv}}${decisionModel ? ` and \${${decisionModel.env}}` : ""} are substituted by deploy.`);
244
+ }
245
+ return text;
246
+ }
247
+ /**
248
+ * True iff the instance ships a DSL exit block — `exit.dsl_preset`, or `exit.engine: dsl`.
249
+ *
250
+ * A deployed strategy without one runs every position naked: no hard stop, no trailing floor. The
251
+ * skills-side `_pkg.validate` has always refused to fund such a package, and `senpi deploy` is now
252
+ * the taught path, so the same gate has to live here or the hole reopens on the direct-verb route.
253
+ */
254
+ export function instanceHasDslExit(inst) {
255
+ const parsed = parseYaml(readRuntimeYaml(inst));
256
+ const exit = parsed?.exit;
257
+ if (exit === null || typeof exit !== "object" || Array.isArray(exit))
258
+ return false;
259
+ const block = exit;
260
+ const engine = typeof block.engine === "string" ? block.engine.toLowerCase() : "";
261
+ // Mirror of `_pkg.Instance.has_dsl`: a truthy `dsl_preset` (real packages use a map, not a
262
+ // string) OR `engine: dsl`. Keeping the two predicates identical is why author-green stays
263
+ // deploy-green.
264
+ return isTruthyPreset(block.dsl_preset) || engine === "dsl";
265
+ }
266
+ /**
267
+ * Python-`bool()` semantics for a YAML value: a non-empty string, map or list — and `true`, which
268
+ * `bool()` also accepts. Without the boolean arm a package written `dsl_preset: true` would validate
269
+ * green on the skills side and be refused here, and the two predicates must not disagree.
270
+ */
271
+ function isTruthyPreset(value) {
272
+ if (typeof value === "boolean")
273
+ return value;
274
+ if (typeof value === "number")
275
+ return value !== 0;
276
+ if (typeof value === "string")
277
+ return value.trim().length > 0;
278
+ if (Array.isArray(value))
279
+ return value.length > 0;
280
+ if (value !== null && typeof value === "object")
281
+ return Object.keys(value).length > 0;
282
+ return false;
283
+ }
284
+ /**
285
+ * Instances in `pkg` that ship no DSL exit block. Empty = every instance is protected.
286
+ *
287
+ * The instances themselves, not their names: both refusals built from this name the re-check
288
+ * command for each offender, and that command takes a directory the instance carries. A name is not
289
+ * a path, and re-deriving the instance from its name at the refusal site is a lookup that can fail.
290
+ */
291
+ export function instancesWithoutDslExit(pkg) {
292
+ return pkg.instances.filter((inst) => !instanceHasDslExit(inst));
293
+ }
294
+ /**
295
+ * Scanner entries in `pkg` that declare an `enabled` key at all.
296
+ *
297
+ * Scanner-level `enabled` is NOT a supported key: `scannerEntrySchema` is `.passthrough()` and does
298
+ * not declare it, `buildScannerOverrides` (map-strategies-registration.ts) forwards only
299
+ * `interval_seconds`/`config`, and `StrategyScannerRegistry.register` derives `enabled` from
300
+ * `strategy.defaultEnabledScanners` — which nothing writes from a recipe. So the engine never reads
301
+ * it: a scanner written `enabled: false` registers ENABLED and ticks. Silently accepting the key
302
+ * means shipping a strategy that trades while its author believes it is switched off, which is why
303
+ * every value refuses (a `true` is just as unread as a `false`, and tomorrow's reader would take
304
+ * the accepted `true` as proof the key works).
305
+ *
306
+ * The live-state twin of this is the observe step's all-disabled branch, which reads engine rows
307
+ * rather than YAML — the runtime API can genuinely disable a scanner after install.
308
+ */
309
+ export function scannersDeclaringEnabled(pkg) {
310
+ const found = [];
311
+ for (const inst of pkg.instances) {
312
+ readScannerEntries(inst).forEach((scanner, index) => {
313
+ // Key presence, not truthiness: a bare `enabled:` (null) is the same unsupported key.
314
+ if (scanner === null || typeof scanner !== "object" || !("enabled" in scanner))
315
+ return;
316
+ const name = typeof scanner.name === "string" ? scanner.name : scanner.id;
317
+ found.push({
318
+ instance: inst.name,
319
+ scanner: typeof name === "string" && name.length > 0 ? name : `scanners[${index}]`,
320
+ value: scanner.enabled,
321
+ runtimeYamlPath: inst.runtimeYamlPath,
322
+ });
323
+ });
324
+ }
325
+ return found;
326
+ }
327
+ /**
328
+ * One offense as an instruction: the key, the scanner, the instance, the file and the exact line to
329
+ * delete. Shared by both gate copies so the mechanical fix an agent reads is word-for-word the same
330
+ * wherever the refusal is raised.
331
+ */
332
+ export function describeScannerEnabledOffense(entry) {
333
+ return (`instance "${entry.instance}", scanner "${entry.scanner}": delete the line ` +
334
+ `\`${renderEnabledLine(entry.value)}\` from ${entry.runtimeYamlPath}`);
335
+ }
336
+ /** The offending line as it appears in the YAML — a bare `enabled:` has no value to echo. */
337
+ function renderEnabledLine(value) {
338
+ return value === null || value === undefined ? "enabled:" : `enabled: ${JSON.stringify(value)}`;
339
+ }
340
+ /** The instance's `scanners:` list, or []. */
341
+ function readScannerEntries(inst) {
342
+ const parsed = parseYaml(readRuntimeYaml(inst));
343
+ return Array.isArray(parsed?.scanners) ? parsed.scanners : [];
344
+ }
345
+ /** True iff any action runs `decision_mode: llm` — those installs need a decision model. */
346
+ export function runtimeUsesLlmDecisions(runtimeYamlText) {
347
+ const parsed = parseYaml(runtimeYamlText);
348
+ const actions = Array.isArray(parsed?.actions) ? parsed.actions : [];
349
+ return actions.some((action) => action?.decision_mode === "llm");
350
+ }
351
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.js","sourceRoot":"","sources":["../../src/deploy/package.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,oFAAoF;AACpF,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,wFAAwF;AACxF,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAE/C,sGAAsG;AACtG,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AA6B7C,yGAAyG;AACzG,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,SAAS,GAAG,IAAI;SACnB,IAAI,EAAE;SACN,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;SAC9B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,wBAAwB,CAAC,EAAU,EAAE,IAAY,EAAE,YAAoB;IAC9E,IAAI,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,qBAAqB,EAAE,iDAAiD;YACtE,IAAI,oBAAoB,CAAC,EAAE,CAAC,8DAA8D;YAC1F,0BAA0B,YAAY,yCAAyC;YAC/E,2FAA2F;YAC3F,mFAAmF,CACtF,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC;IACjC,IAAI,oBAAoB,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,qBAAqB,EAAE,iBAAiB,IAAI,iBAAiB,QAAQ,IAAI;YACvE,IAAI,QAAQ,CAAC,MAAM,qBAAqB,eAAe,kCAAkC;YACzF,2EAA2E;YAC3E,GAAG,eAAe,KAAK,YAAY,qDAAqD;YACxF,yFAAyF,CAC5F,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAkB;IACtD,OAAO,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,EAAU,EACV,UAAkB;IAElB,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC;IACrE,OAAO;QACL,WAAW,EACT,aAAa,KAAK,SAAS,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,EAAE;YAC9D,CAAC,OAAO;gBACN,CAAC,CAAC,sCAAsC,OAAO,SAAS,KAAK,qBAAqB;oBAChF,8BAA8B;gBAChC,CAAC,CAAC,EAAE,CAAC;QACT,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;IAChE,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,kCAAkC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAErC,SAAS,qBAAqB,CAC5B,YAA4C,EAC5C,GAAW,EACX,YAAoB;IAEpB,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,kEAAkE,YAAY,GAAG,CAAC,CAAC;QACrG,CAAC;QACD,0FAA0F;QAC1F,6EAA6E;QAC7E,+FAA+F;QAC/F,+FAA+F;QAC/F,0FAA0F;QAC1F,2FAA2F;QAC3F,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACb,gCAAgC,IAAI,iDAAiD;gBACnF,IAAI,oBAAoB,CAAC,IAAI,CAAC,2DAA2D;gBACzF,OAAO,eAAe,mEAAmE;gBACzF,uFAAuF,CAC1F,CAAC;QACJ,CAAC;QACD,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,6BAA6B,eAAe,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,OAAO;YACL,IAAI;YACJ,eAAe;YACf,cAAc,EAAE,OAAO,CAAC,eAAe,CAAC;YACxC,SAAS;YACT,YAAY,EAAE,gBAAgB,CAAC,KAAK,CAAC,aAAa,CAAC;SACpD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAAC,GAAW;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,6DAA6D,GAAG,GAAG,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAA4B,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,wFAAwF,MAAM,GAAG,CAClG,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AACnH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,uBAAuB,GAAG,oDAAoD;YAC5E,yFAAyF;YACzF,uFAAuF,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,YAAY,GAAG,CAAC,CAAC;IAEpG,MAAM,QAAQ,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAA4B,CAAC;IACtE,MAAM,gBAAgB,GACpB,OAAO,QAAQ,CAAC,kBAAkB,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC;IAEvF,kGAAkG;IAClG,2EAA2E;IAC3E,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC;IACpC,MAAM,OAAO,GACX,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QACjF,CAAC,CAAE,UAAsC;QACzC,CAAC,CAAC,EAAE,CAAC;IAET,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QACpD,CAAC,CAAE,QAAQ,CAAC,SAA4C;QACxD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GACb,YAAY,CAAC,MAAM,GAAG,CAAC;QACrB,CAAC,CAAC,qBAAqB,CAAC,YAAY,EAAE,GAAG,EAAE,YAAY,CAAC;QACxD,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,6FAA6F;IAC7F,0DAA0D;IAC1D,KAAK,MAAM,IAAI,IAAI,SAAS;QAAE,wBAAwB,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAEpF,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACpE,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,GAAkB,EAAE,IAAoB;IACjE,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1E,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,eAAe,CAAC,IAAoB;IAClD,OAAO,YAAY,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAoB,EACpB,MAAc,EACd,aAA8C;IAE9C,IAAI,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,aAAa;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC3F,MAAM,QAAQ,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,eAAe,IAAI;YAClE,WAAW,IAAI,CAAC,SAAS,IAAI,aAAa,CAAC,CAAC,CAAC,WAAW,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,6BAA6B,CACjH,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAoB;IACrD,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,CAAmC,CAAC;IAClF,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC;IAC1B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACnF,MAAM,KAAK,GAAG,IAA+B,CAAC;IAC9C,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,2FAA2F;IAC3F,2FAA2F;IAC3F,gBAAgB;IAChB,OAAO,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,MAAM,KAAK,KAAK,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,KAAK,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAClD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACtF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAkB;IACxD,OAAO,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAaD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAkB;IACzD,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QACjC,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;YAClD,sFAAsF;YACtF,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC;gBAAE,OAAO;YACvF,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1E,KAAK,CAAC,IAAI,CAAC;gBACT,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,OAAO,EAAE,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,GAAG;gBAClF,KAAK,EAAE,OAAO,CAAC,OAAO;gBACtB,eAAe,EAAE,IAAI,CAAC,eAAe;aACtC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,KAAwB;IACpE,OAAO,CACL,aAAa,KAAK,CAAC,QAAQ,eAAe,KAAK,CAAC,OAAO,qBAAqB;QAC5E,KAAK,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,eAAe,EAAE,CACtE,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,SAAS,iBAAiB,CAAC,KAAc;IACvC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;AAClG,CAAC;AAED,8CAA8C;AAC9C,SAAS,kBAAkB,CAAC,IAAoB;IAC9C,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,CAAmC,CAAC;IAClF,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAE,MAAM,CAAC,QAA2C,CAAC,CAAC,CAAC,EAAE,CAAC;AACpG,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,uBAAuB,CAAC,eAAuB;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,CAAmC,CAAC;IAC5E,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAE,MAAM,CAAC,OAA0C,CAAC,CAAC,CAAC,EAAE,CAAC;IACzG,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,KAAK,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { type ValidateCode, type VerifyResult } from "../validate/index.js";
2
+ import { type DeployInstance, type DeployPackage } from "./package.js";
3
+ export declare function checkPackageProof(pkg: DeployPackage): VerifyResult;
4
+ /** Instances that resolve to one directory — see {@link collidingInstanceDirs}. */
5
+ export interface InstanceDirCollision {
6
+ dir: string;
7
+ instances: DeployInstance[];
8
+ }
9
+ /**
10
+ * The one package shape a proof cannot answer for, which therefore has to be refused rather than
11
+ * verified.
12
+ *
13
+ * A proof is one file per DIRECTORY — `writeProof` writes `dirname(recipePath)/.senpi-proof.json` —
14
+ * and it records the recipe it was made for, which `verifyProofInner` re-fingerprints. Nothing in
15
+ * `loadDeployPackage` requires instances to live in different directories: a manifest naming
16
+ * `aggressive.yaml` and `calm.yaml` at the package root is legal and gives both instances the same
17
+ * `runtimeYamlDir`. Verifying that package asks the same directory twice and gets the same answer
18
+ * twice, so a proof recorded for ONE recipe passes the other — a recipe nobody watched run reaches
19
+ * funding, which is the precise harm this gate exists to prevent.
20
+ *
21
+ * Fixing it inside `verifyProof` would mean teaching the proof format about instances; the deploy
22
+ * layer is the one that HAS the manifest and can see the collision, so it refuses here. Fail-closed
23
+ * on principle and free in practice: of the 103 packages in the shipped catalog, 21 are
24
+ * multi-instance and none shares a runtime directory.
25
+ */
26
+ export declare function collidingInstanceDirs(pkg: DeployPackage): InstanceDirCollision[];
27
+ /**
28
+ * The collision as an instruction — deliberately an END STATE, not a command.
29
+ *
30
+ * The obvious rendering ("mv the recipe into its own directory") produces a package that does not
31
+ * validate: a relative `path:` resolves against the recipe's own directory, so moving the recipe
32
+ * alone re-points `./scanners` at a directory that does not exist and the very next validation
33
+ * fails on an unresolvable scanner path. Nor can the right move be computed here — two recipes that
34
+ * share a directory may well be sharing what is in it, and whether the tree moves, is copied, or is
35
+ * referenced from both is a decision about this package that this gate does not have the standing
36
+ * to make. So it names what will break (computed from each recipe, not templated), describes the
37
+ * state to reach, and emits no command whose preconditions it cannot check — the same rule
38
+ * `buildBudgetEscape` follows when a report cannot support a teardown.
39
+ *
40
+ * It also emits no post-move `senpi validate` path. Those directories do not exist yet, and a
41
+ * non-colliding sleeve's directory is not its manifest name — the exact trap this module exists to
42
+ * prevent, which is easy to walk back into while writing helpful-looking text. Re-running the
43
+ * deploy is the loop: it names any instance still unproven and carries the exact command for it.
44
+ */
45
+ export declare function buildInstanceDirCollisionRefusal(collisions: InstanceDirCollision[], pkg: DeployPackage): string;
46
+ /**
47
+ * The refusal's four parts, before rendering.
48
+ *
49
+ * Split from the renderer so the step row can take the `code` from the same producer that renders
50
+ * the `detail` — the two cannot disagree about which refusal this is. Only the code rides
51
+ * `evidence`: every sibling gate keeps `evidence` a small machine-readable bag and leaves the prose
52
+ * in `detail`, and duplicating multi-line teaching text into every journal event and instance row
53
+ * is weight without a reader.
54
+ */
55
+ export interface ProofRefusalParts {
56
+ code: ValidateCode;
57
+ what: string;
58
+ why: string;
59
+ fix: string;
60
+ }
61
+ /**
62
+ * The three reasons, each with its own fix.
63
+ *
64
+ * A single generic "run senpi validate" would be a template hint rather than a computed one: the
65
+ * three states call for different reading. `no_proof` says nothing has ever been observed to run
66
+ * here; `content_changed` says the bytes moved and names which; `runtime_version_changed` says the
67
+ * package is unchanged and only the engine under it moved — the one case where editing the package
68
+ * would be exactly the wrong response.
69
+ */
70
+ export declare function proofRefusalParts(result: VerifyResult, pkg: DeployPackage): ProofRefusalParts;
71
+ /**
72
+ * The refusal as the step row's `detail`.
73
+ *
74
+ * The bracketed code leads, and is not decoration: `taxonomy.ts` extracts it and the CLI uses it to
75
+ * exit 2 (obey this) rather than 1 (state unknown).
76
+ *
77
+ * It emits NO re-run command of its own. The report's `next` already ends a refused report with
78
+ * `openclaw senpi deploy -p '<dir>' --budget <the real figure>`; a copy here would carry a `<usd>` placeholder
79
+ * instead, which is a second, worse command competing with the authoritative one.
80
+ */
81
+ export declare function buildProofRefusal(result: VerifyResult, pkg: DeployPackage): string;
82
+ //# sourceMappingURL=proof-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof-gate.d.ts","sourceRoot":"","sources":["../../src/deploy/proof-gate.ts"],"names":[],"mappings":"AA6BA,OAAO,EAA8B,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAExG,OAAO,EAAmB,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGxF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,YAAY,CAGlE;AAED,mFAAmF;AACnF,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,GAAG,oBAAoB,EAAE,CAQhF;AA6BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,oBAAoB,EAAE,EAClC,GAAG,EAAE,aAAa,GACjB,MAAM,CAqCR;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAgED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,GAAG,iBAAiB,CAmD7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAalF"}