@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.
- package/README.md +73 -2
- package/dist/actions/decision-engine.d.ts +0 -2
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +1 -79
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/cli/deploy-render.d.ts +91 -0
- package/dist/cli/deploy-render.d.ts.map +1 -0
- package/dist/cli/deploy-render.js +216 -0
- package/dist/cli/deploy-render.js.map +1 -0
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +78 -6
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts +6 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +244 -8
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/cli/validate-command.d.ts.map +1 -1
- package/dist/cli/validate-command.js +32 -1
- package/dist/cli/validate-command.js.map +1 -1
- package/dist/deploy/deadlines.d.ts +47 -0
- package/dist/deploy/deadlines.d.ts.map +1 -0
- package/dist/deploy/deadlines.js +56 -0
- package/dist/deploy/deadlines.js.map +1 -0
- package/dist/deploy/error-text.d.ts +25 -0
- package/dist/deploy/error-text.d.ts.map +1 -0
- package/dist/deploy/error-text.js +42 -0
- package/dist/deploy/error-text.js.map +1 -0
- package/dist/deploy/funding.d.ts +87 -0
- package/dist/deploy/funding.d.ts.map +1 -0
- package/dist/deploy/funding.js +229 -0
- package/dist/deploy/funding.js.map +1 -0
- package/dist/deploy/install-gate-runner.d.ts +12 -0
- package/dist/deploy/install-gate-runner.d.ts.map +1 -0
- package/dist/deploy/install-gate-runner.js +17 -0
- package/dist/deploy/install-gate-runner.js.map +1 -0
- package/dist/deploy/job.d.ts +199 -0
- package/dist/deploy/job.d.ts.map +1 -0
- package/dist/deploy/job.js +463 -0
- package/dist/deploy/job.js.map +1 -0
- package/dist/deploy/min-budget.d.ts +68 -0
- package/dist/deploy/min-budget.d.ts.map +1 -0
- package/dist/deploy/min-budget.js +297 -0
- package/dist/deploy/min-budget.js.map +1 -0
- package/dist/deploy/orchestrator.d.ts +305 -0
- package/dist/deploy/orchestrator.d.ts.map +1 -0
- package/dist/deploy/orchestrator.js +2910 -0
- package/dist/deploy/orchestrator.js.map +1 -0
- package/dist/deploy/package.d.ts +130 -0
- package/dist/deploy/package.d.ts.map +1 -0
- package/dist/deploy/package.js +351 -0
- package/dist/deploy/package.js.map +1 -0
- package/dist/deploy/proof-gate.d.ts +82 -0
- package/dist/deploy/proof-gate.d.ts.map +1 -0
- package/dist/deploy/proof-gate.js +269 -0
- package/dist/deploy/proof-gate.js.map +1 -0
- package/dist/deploy/python-float.d.ts +34 -0
- package/dist/deploy/python-float.d.ts.map +1 -0
- package/dist/deploy/python-float.js +46 -0
- package/dist/deploy/python-float.js.map +1 -0
- package/dist/deploy/recheck-commands.d.ts +50 -0
- package/dist/deploy/recheck-commands.d.ts.map +1 -0
- package/dist/deploy/recheck-commands.js +59 -0
- package/dist/deploy/recheck-commands.js.map +1 -0
- package/dist/deploy/register.d.ts +99 -0
- package/dist/deploy/register.d.ts.map +1 -0
- package/dist/deploy/register.js +689 -0
- package/dist/deploy/register.js.map +1 -0
- package/dist/deploy/taxonomy.d.ts +24 -0
- package/dist/deploy/taxonomy.d.ts.map +1 -0
- package/dist/deploy/taxonomy.js +31 -0
- package/dist/deploy/taxonomy.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +244 -132
- package/dist/index.js.map +1 -1
- package/dist/runtime/strategy-registry.d.ts +8 -0
- package/dist/runtime/strategy-registry.d.ts.map +1 -1
- package/dist/runtime/strategy-registry.js +14 -1
- package/dist/runtime/strategy-registry.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +7 -4
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/scanners/implementations/market-regime.d.ts +1 -1
- package/dist/senpi/client.d.ts +58 -4
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +192 -15
- package/dist/senpi/client.js.map +1 -1
- package/dist/senpi/strategy-status.d.ts +32 -0
- package/dist/senpi/strategy-status.d.ts.map +1 -0
- package/dist/senpi/strategy-status.js +70 -0
- package/dist/senpi/strategy-status.js.map +1 -0
- package/dist/senpi/types.d.ts +47 -3
- package/dist/senpi/types.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -1
- package/dist/telemetry/metrics.d.ts +23 -0
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +65 -0
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/types/strategy.d.ts +9 -1
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/universe/package-universe.d.ts +97 -0
- package/dist/universe/package-universe.d.ts.map +1 -0
- package/dist/universe/package-universe.js +275 -0
- package/dist/universe/package-universe.js.map +1 -0
- package/dist/validate/codes.d.ts +20 -0
- package/dist/validate/codes.d.ts.map +1 -1
- package/dist/validate/codes.js +36 -0
- package/dist/validate/codes.js.map +1 -1
- package/dist/validate/import-stage.js +5 -5
- package/dist/validate/import-stage.js.map +1 -1
- package/dist/validate/index.d.ts +1 -1
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/install-gate.d.ts.map +1 -1
- package/dist/validate/install-gate.js +10 -2
- package/dist/validate/install-gate.js.map +1 -1
- package/dist/validate/live-instruments.d.ts +44 -0
- package/dist/validate/live-instruments.d.ts.map +1 -0
- package/dist/validate/live-instruments.js +71 -0
- package/dist/validate/live-instruments.js.map +1 -0
- package/dist/validate/live-stage.js +5 -5
- package/dist/validate/live-stage.js.map +1 -1
- package/dist/validate/proof.d.ts +11 -1
- package/dist/validate/proof.d.ts.map +1 -1
- package/dist/validate/proof.js +7 -6
- package/dist/validate/proof.js.map +1 -1
- package/dist/validate/recipe-checks.d.ts.map +1 -1
- package/dist/validate/recipe-checks.js +6 -4
- package/dist/validate/recipe-checks.js.map +1 -1
- package/dist/validate/run.d.ts +5 -0
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +10 -1
- package/dist/validate/run.js.map +1 -1
- package/dist/validate/target.d.ts +41 -0
- package/dist/validate/target.d.ts.map +1 -1
- package/dist/validate/target.js +192 -16
- package/dist/validate/target.js.map +1 -1
- package/dist/validate/universe-stage.d.ts +71 -0
- package/dist/validate/universe-stage.d.ts.map +1 -0
- package/dist/validate/universe-stage.js +183 -0
- package/dist/validate/universe-stage.js.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/runtime/managed-wallet-guard.d.ts +0 -47
- package/dist/runtime/managed-wallet-guard.d.ts.map +0 -1
- package/dist/runtime/managed-wallet-guard.js +0 -106
- package/dist/runtime/managed-wallet-guard.js.map +0 -1
- package/dist/runtime/protection-nudge.d.ts +0 -67
- package/dist/runtime/protection-nudge.d.ts.map +0 -1
- package/dist/runtime/protection-nudge.js +0 -244
- 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":"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"}
|
|
@@ -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"}
|