@senpi/runtime 3.0.55 → 3.0.57
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/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +2 -0
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/cli/validate-command.d.ts +29 -0
- package/dist/cli/validate-command.d.ts.map +1 -0
- package/dist/cli/validate-command.js +89 -0
- package/dist/cli/validate-command.js.map +1 -0
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +5 -1
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/load-runtime-config.d.ts +38 -0
- package/dist/runtime/load-runtime-config.d.ts.map +1 -1
- package/dist/runtime/load-runtime-config.js +33 -25
- package/dist/runtime/load-runtime-config.js.map +1 -1
- package/dist/runtime/runtime-schema.d.ts +100 -100
- package/dist/scanners/external/scaffold/__main__.py +8 -0
- package/dist/scanners/external/scaffold/ctx.py +15 -1
- package/dist/scanners/external/scaffold/http_post.py +22 -0
- package/dist/scanners/external/scaffold/import_check.py +305 -0
- package/dist/scanners/external/scaffold/preflight.py +541 -0
- package/dist/scanners/external/scaffold/scaffold.py +11 -2
- package/dist/scanners/implementations/market-regime.d.ts +1 -1
- package/dist/telemetry/metrics.d.ts +6 -0
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +45 -0
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/utils/event-catalog.d.ts +22 -0
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +6 -0
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/validate/codes.d.ts +163 -0
- package/dist/validate/codes.d.ts.map +1 -0
- package/dist/validate/codes.js +375 -0
- package/dist/validate/codes.js.map +1 -0
- package/dist/validate/coverage.d.ts +34 -0
- package/dist/validate/coverage.d.ts.map +1 -0
- package/dist/validate/coverage.js +34 -0
- package/dist/validate/coverage.js.map +1 -0
- package/dist/validate/import-stage.d.ts +56 -0
- package/dist/validate/import-stage.d.ts.map +1 -0
- package/dist/validate/import-stage.js +238 -0
- package/dist/validate/import-stage.js.map +1 -0
- package/dist/validate/index.d.ts +28 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +27 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate/install-gate.d.ts +44 -0
- package/dist/validate/install-gate.d.ts.map +1 -0
- package/dist/validate/install-gate.js +66 -0
- package/dist/validate/install-gate.js.map +1 -0
- package/dist/validate/live-stage.d.ts +108 -0
- package/dist/validate/live-stage.d.ts.map +1 -0
- package/dist/validate/live-stage.js +416 -0
- package/dist/validate/live-stage.js.map +1 -0
- package/dist/validate/proof.d.ts +104 -0
- package/dist/validate/proof.d.ts.map +1 -0
- package/dist/validate/proof.js +256 -0
- package/dist/validate/proof.js.map +1 -0
- package/dist/validate/recipe-checks.d.ts +79 -0
- package/dist/validate/recipe-checks.d.ts.map +1 -0
- package/dist/validate/recipe-checks.js +202 -0
- package/dist/validate/recipe-checks.js.map +1 -0
- package/dist/validate/recipe.d.ts +41 -0
- package/dist/validate/recipe.d.ts.map +1 -0
- package/dist/validate/recipe.js +215 -0
- package/dist/validate/recipe.js.map +1 -0
- package/dist/validate/report.d.ts +35 -0
- package/dist/validate/report.d.ts.map +1 -0
- package/dist/validate/report.js +78 -0
- package/dist/validate/report.js.map +1 -0
- package/dist/validate/run.d.ts +101 -0
- package/dist/validate/run.d.ts.map +1 -0
- package/dist/validate/run.js +444 -0
- package/dist/validate/run.js.map +1 -0
- package/dist/validate/signal-stage.d.ts +40 -0
- package/dist/validate/signal-stage.d.ts.map +1 -0
- package/dist/validate/signal-stage.js +116 -0
- package/dist/validate/signal-stage.js.map +1 -0
- package/dist/validate/target.d.ts +70 -0
- package/dist/validate/target.d.ts.map +1 -0
- package/dist/validate/target.js +231 -0
- package/dist/validate/target.js.map +1 -0
- package/dist/validate/telemetry.d.ts +24 -0
- package/dist/validate/telemetry.d.ts.map +1 -0
- package/dist/validate/telemetry.js +60 -0
- package/dist/validate/telemetry.js.map +1 -0
- package/dist/validate/types.d.ts +115 -0
- package/dist/validate/types.d.ts.map +1 -0
- package/dist/validate/types.js +31 -0
- package/dist/validate/types.js.map +1 -0
- package/dist/validate/wallet.d.ts +63 -0
- package/dist/validate/wallet.d.ts.map +1 -0
- package/dist/validate/wallet.js +66 -0
- package/dist/validate/wallet.js.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running a validation and deciding what it means.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately free of any command-line concern: it takes resolved options and returns a result
|
|
5
|
+
* object. The command layer is a thin translation on either side, so the parts worth testing —
|
|
6
|
+
* ordering, verdict, exit code, the basis — are testable without spawning anything.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { dirname, relative, resolve } from "node:path";
|
|
10
|
+
import { VALIDATE_CODE } from "./codes.js";
|
|
11
|
+
import { checkScanner, resolveRoot } from "./import-stage.js";
|
|
12
|
+
import { judgeTick, runTick } from "./live-stage.js";
|
|
13
|
+
import { recordValidateRun } from "../telemetry/metrics.js";
|
|
14
|
+
import { writeProof } from "./proof.js";
|
|
15
|
+
import { resolveWallet, walletClaim } from "./wallet.js";
|
|
16
|
+
import { collectRecipeFindings } from "./recipe.js";
|
|
17
|
+
import { resolveTarget } from "./target.js";
|
|
18
|
+
import { depthsUpTo, severityOfFindings } from "./types.js";
|
|
19
|
+
/** Applied when a recipe declares no per-tick budget. Never derived from the cadence. */
|
|
20
|
+
const DEFAULT_TICK_BUDGET_SECONDS = 120;
|
|
21
|
+
/** Used only when a bare scanner directory is validated with no recipe to declare an entrypoint. */
|
|
22
|
+
const CONVENTIONAL_ENTRYPOINT = "scan.py";
|
|
23
|
+
/** An address as the chain writes them. Shape only — existence is not this layer's business. */
|
|
24
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
25
|
+
function invocationFailure(finding) {
|
|
26
|
+
return {
|
|
27
|
+
verdict: "FAIL",
|
|
28
|
+
exit_code: 3,
|
|
29
|
+
target: {},
|
|
30
|
+
stages_run: [],
|
|
31
|
+
verdict_basis: {
|
|
32
|
+
rule: "the request must name something that can be validated",
|
|
33
|
+
observed: { resolved: false },
|
|
34
|
+
why_not_pass: finding.what,
|
|
35
|
+
what_would_change_it: finding.fix,
|
|
36
|
+
},
|
|
37
|
+
findings: [finding],
|
|
38
|
+
proof_eligible: false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What a passing run establishes at each depth, and what it does not.
|
|
43
|
+
*
|
|
44
|
+
* Carried on the result so a caller cannot over-claim on the user's behalf. A green result that
|
|
45
|
+
* silently implies more than it checked is the same failure as a health surface that reports fine
|
|
46
|
+
* while dead — the thing this whole feature exists to correct.
|
|
47
|
+
*/
|
|
48
|
+
const DEPTH_CLAIMS = {
|
|
49
|
+
static: {
|
|
50
|
+
proves: "the recipe is well formed and internally coherent",
|
|
51
|
+
doesNotProve: ["that the scanner code loads", "that anything executes"],
|
|
52
|
+
},
|
|
53
|
+
import: {
|
|
54
|
+
proves: "the recipe is coherent and every scanner file compiles and imports",
|
|
55
|
+
doesNotProve: ["that a tick executes", "that emitted signals would be accepted"],
|
|
56
|
+
},
|
|
57
|
+
live: {
|
|
58
|
+
proves: "runnable from cold start — imported, ticked, and emitted intake-valid signals",
|
|
59
|
+
doesNotProve: [
|
|
60
|
+
"position-dependent logic, since the account had no open positions",
|
|
61
|
+
"branches this tick did not take",
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
/** The depths that have an implementation. Grows as each layer lands. */
|
|
66
|
+
const IMPLEMENTED = ["static", "import", "live"];
|
|
67
|
+
export function isImplemented(depth) {
|
|
68
|
+
return IMPLEMENTED.includes(depth);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate, and report what it means.
|
|
72
|
+
*
|
|
73
|
+
* Stages short-circuit: a depth that produced an error stops the ones after it, because they would
|
|
74
|
+
* be reading a shape that was never established.
|
|
75
|
+
*/
|
|
76
|
+
export async function runValidation(options) {
|
|
77
|
+
const startedAt = Date.now();
|
|
78
|
+
const result = await runValidationInner(options);
|
|
79
|
+
// Class and shape only — the attribute builders clamp everything to closed sets, so nothing
|
|
80
|
+
// from the package (paths, config, addresses, messages) can reach a label.
|
|
81
|
+
recordValidateRun(result, Date.now() - startedAt);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
async function runValidationInner(options) {
|
|
85
|
+
if (options.wallet !== undefined && !ADDRESS.test(options.wallet)) {
|
|
86
|
+
return invocationFailure({
|
|
87
|
+
code: VALIDATE_CODE.badWallet,
|
|
88
|
+
severity: "error",
|
|
89
|
+
stage: "invocation",
|
|
90
|
+
actor: "agent",
|
|
91
|
+
fix_kind: "command",
|
|
92
|
+
what: `'${options.wallet}' is not a well-formed account address`,
|
|
93
|
+
why: "The address is passed to the exchange reads a scanner makes, which will not accept it.",
|
|
94
|
+
fix: "Pass a 42-character hex address beginning 0x, or omit --wallet to validate against an empty account.",
|
|
95
|
+
evidence: { supplied: options.wallet },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const resolution = options.content !== undefined
|
|
99
|
+
? resolveTarget({ kind: "content", content: options.content, dir: options.dir, cwd: options.cwd }, options.depth)
|
|
100
|
+
: resolveTarget({ kind: "path", path: options.target, cwd: options.cwd }, options.depth);
|
|
101
|
+
if (!resolution.ok)
|
|
102
|
+
return invocationFailure(resolution.finding);
|
|
103
|
+
const target = resolution.target;
|
|
104
|
+
const requested = depthsUpTo(options.depth);
|
|
105
|
+
const stagesRun = [];
|
|
106
|
+
const findings = [];
|
|
107
|
+
// Carried between depths so the import stage knows which scanners the recipe declared, rather
|
|
108
|
+
// than reading and parsing it a second time.
|
|
109
|
+
let config;
|
|
110
|
+
let wallet;
|
|
111
|
+
let liveExecuted;
|
|
112
|
+
let observations = [];
|
|
113
|
+
for (const depth of requested) {
|
|
114
|
+
if (!isImplemented(depth))
|
|
115
|
+
break;
|
|
116
|
+
const produced = await runDepth(depth, target, options, config, findings);
|
|
117
|
+
if (produced.config !== undefined)
|
|
118
|
+
config = produced.config;
|
|
119
|
+
if (produced.wallet !== undefined)
|
|
120
|
+
wallet = produced.wallet;
|
|
121
|
+
if (produced.liveExecuted !== undefined)
|
|
122
|
+
liveExecuted = produced.liveExecuted;
|
|
123
|
+
if (produced.observations !== undefined)
|
|
124
|
+
observations = produced.observations;
|
|
125
|
+
stagesRun.push(depth);
|
|
126
|
+
findings.push(...produced.findings);
|
|
127
|
+
// A depth that failed invalidates everything after it: the next reads a shape this one could
|
|
128
|
+
// not establish.
|
|
129
|
+
if (produced.findings.some((f) => f.severity === "error"))
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
const result = assemble(target, stagesRun, findings, options, wallet, liveExecuted);
|
|
133
|
+
// The record is the pass made durable — it is what the deploy gate reads. Written after the
|
|
134
|
+
// verdict so a write failure can never flip a pass: the package is no less runnable because a
|
|
135
|
+
// disk was full, but whoever operates the environment needs to hear about it.
|
|
136
|
+
if (result.proof_eligible && target.recipePath !== undefined) {
|
|
137
|
+
const scannerRoots = scannersToCheck(target, config, undefined).targets.map((t) => relative(dirname(target.recipePath), t.root));
|
|
138
|
+
const written = writeProof({
|
|
139
|
+
recipePath: target.recipePath,
|
|
140
|
+
scannerRoots,
|
|
141
|
+
wallet,
|
|
142
|
+
observations,
|
|
143
|
+
});
|
|
144
|
+
result.proof_recorded = written.recorded;
|
|
145
|
+
if (written.finding)
|
|
146
|
+
result.findings.push(written.finding);
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The scanners the import depth should check.
|
|
152
|
+
*
|
|
153
|
+
* From the recipe when there is one, so the declared entrypoint is honoured; from the target itself
|
|
154
|
+
* when validating a bare directory or file, which is the mid-authoring case where the recipe does
|
|
155
|
+
* not exist yet.
|
|
156
|
+
*/
|
|
157
|
+
function scannersToCheck(target, config, only) {
|
|
158
|
+
if (config === undefined) {
|
|
159
|
+
if (target.scannerRoot === undefined)
|
|
160
|
+
return { targets: [], findings: [] };
|
|
161
|
+
// With no recipe there is no declared entrypoint, so fall back to the conventional name when
|
|
162
|
+
// it is there. This is the only place a filename is assumed, and it is a fallback rather than a
|
|
163
|
+
// rule: without it the contract checks simply do not run, and the compile and shape checks
|
|
164
|
+
// still do — which is worth having on its own while a recipe is being written.
|
|
165
|
+
const entrypoint = target.entrypoint ??
|
|
166
|
+
(existsSync(resolve(target.scannerRoot, CONVENTIONAL_ENTRYPOINT))
|
|
167
|
+
? CONVENTIONAL_ENTRYPOINT
|
|
168
|
+
: undefined);
|
|
169
|
+
return {
|
|
170
|
+
targets: [{ name: "scanner", root: target.scannerRoot, entrypoint }],
|
|
171
|
+
findings: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const targets = [];
|
|
175
|
+
const findings = [];
|
|
176
|
+
for (const scanner of config.scanners ?? []) {
|
|
177
|
+
if (scanner.type !== "external_scanner")
|
|
178
|
+
continue;
|
|
179
|
+
if (only !== undefined && scanner.name !== only)
|
|
180
|
+
continue;
|
|
181
|
+
const declared = scanner.path;
|
|
182
|
+
const entrypoint = scanner.entrypoint;
|
|
183
|
+
if (declared === undefined || entrypoint === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
const root = resolveRoot(declared, target.baseDir);
|
|
186
|
+
// An unresolvable path is already reported by the recipe checks; skipping here avoids saying
|
|
187
|
+
// the same thing twice in different words.
|
|
188
|
+
if (root === undefined)
|
|
189
|
+
continue;
|
|
190
|
+
targets.push({ name: scanner.name, root, entrypoint });
|
|
191
|
+
}
|
|
192
|
+
return { targets, findings };
|
|
193
|
+
}
|
|
194
|
+
async function runDepth(depth, target, options, config,
|
|
195
|
+
/** What the depths before this one found. Read, never appended to. */
|
|
196
|
+
prior) {
|
|
197
|
+
if (depth === "import") {
|
|
198
|
+
const { targets, findings } = scannersToCheck(target, config, options.scanner);
|
|
199
|
+
// Sequentially: each check spawns an interpreter, and running them in order keeps output
|
|
200
|
+
// deterministic for a cost that is irrelevant at this scale.
|
|
201
|
+
for (const scanner of targets) {
|
|
202
|
+
findings.push(...(await checkScanner(scanner)));
|
|
203
|
+
}
|
|
204
|
+
return { findings };
|
|
205
|
+
}
|
|
206
|
+
if (depth === "live") {
|
|
207
|
+
const { targets } = scannersToCheck(target, config, options.scanner);
|
|
208
|
+
if (targets.length === 0) {
|
|
209
|
+
return {
|
|
210
|
+
findings: [
|
|
211
|
+
{
|
|
212
|
+
code: VALIDATE_CODE.noExternalScanners,
|
|
213
|
+
severity: "warning",
|
|
214
|
+
stage: "live",
|
|
215
|
+
actor: "agent",
|
|
216
|
+
fix_kind: "locate-only",
|
|
217
|
+
what: "This recipe declares no author-written scanner",
|
|
218
|
+
why: "Built-in scanners are part of the runtime and are covered by its own tests, so there is no author code here whose runnability is in question.",
|
|
219
|
+
fix: "Nothing to do — a recipe using only built-in scanners has nothing for this depth to execute.",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
liveExecuted: 0,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const wallet = resolveWallet({
|
|
226
|
+
explicit: options.wallet,
|
|
227
|
+
fromRecipe: config?.strategy?.wallet,
|
|
228
|
+
fromEnvironment: process.env.SENPI_VALIDATE_WALLET,
|
|
229
|
+
});
|
|
230
|
+
// Asked once, before anything is spawned. A package with thirty scanners would otherwise start
|
|
231
|
+
// thirty processes to learn the same thing thirty times and report it thirty times over.
|
|
232
|
+
const missing = missingCredentials();
|
|
233
|
+
if (missing.length > 0) {
|
|
234
|
+
return {
|
|
235
|
+
findings: [
|
|
236
|
+
{
|
|
237
|
+
code: VALIDATE_CODE.noCredentials,
|
|
238
|
+
severity: "error",
|
|
239
|
+
stage: "live",
|
|
240
|
+
actor: "ops",
|
|
241
|
+
fix_kind: "command",
|
|
242
|
+
what: `This environment has no data-client credentials: ${missing.join(" and ")} ${missing.length === 1 ? "is" : "are"} not set`,
|
|
243
|
+
why: "A live run reads real market data. Without credentials no scanner can be executed, so nothing about this strategy can be established.",
|
|
244
|
+
fix: `Set ${missing.join(" and ")} in this environment, or run --stage import to check the code without executing it.`,
|
|
245
|
+
evidence: { missing },
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
wallet,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// A warning from the import depth that changes how a zero-read tick should be read: the
|
|
252
|
+
// scanner may have fetched plenty, just not by a route anything here can see.
|
|
253
|
+
const nonMcpSources = prior
|
|
254
|
+
.filter((f) => f.code === VALIDATE_CODE.nonMcpSource && f.where !== undefined)
|
|
255
|
+
.map((f) => f.where);
|
|
256
|
+
const findings = [];
|
|
257
|
+
const observations = [];
|
|
258
|
+
for (const scanner of targets) {
|
|
259
|
+
const declared = (config?.scanners ?? []).find((s) => s.name === scanner.name);
|
|
260
|
+
const outcome = await runTick({
|
|
261
|
+
scanner,
|
|
262
|
+
wallet,
|
|
263
|
+
timeoutSeconds: tickBudgetSeconds(declared, options.timeoutSeconds),
|
|
264
|
+
intervalSeconds: declared?.interval_seconds ?? 60,
|
|
265
|
+
defaultSignalValiditySeconds: declared?.default_signal_validity_seconds ?? 600,
|
|
266
|
+
inputs: declared?.inputs,
|
|
267
|
+
signalDataSchema: declared?.signal_data_schema,
|
|
268
|
+
stateHistoryMaxCount: declared?.state_history_max_count,
|
|
269
|
+
});
|
|
270
|
+
findings.push(...judgeTick(scanner, outcome, { nonMcpSources }));
|
|
271
|
+
observations.push({
|
|
272
|
+
scanner: scanner.name,
|
|
273
|
+
reads_ok: outcome.reads_ok ?? 0,
|
|
274
|
+
reads_failed: outcome.reads_failed ?? 0,
|
|
275
|
+
signals_emitted: outcome.signals?.length ?? 0,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return { findings, wallet, liveExecuted: targets.length, observations };
|
|
279
|
+
}
|
|
280
|
+
if (depth !== "static")
|
|
281
|
+
return { findings: [] };
|
|
282
|
+
if (target.recipePath === undefined && target.recipeContent === undefined) {
|
|
283
|
+
// A code-only target has no recipe to check; the depth is a no-op rather than a failure.
|
|
284
|
+
return { findings: [] };
|
|
285
|
+
}
|
|
286
|
+
const result = await collectRecipeFindings({
|
|
287
|
+
path: target.recipePath,
|
|
288
|
+
content: target.recipeContent,
|
|
289
|
+
baseDir: target.baseDir,
|
|
290
|
+
});
|
|
291
|
+
const findings = [...result.findings];
|
|
292
|
+
// `--scanner` can only be checked once the recipe names its scanners.
|
|
293
|
+
if (options.scanner !== undefined && result.config !== undefined) {
|
|
294
|
+
const declared = (result.config.scanners ?? []).map((s) => s.name);
|
|
295
|
+
if (!declared.includes(options.scanner)) {
|
|
296
|
+
findings.push({
|
|
297
|
+
code: VALIDATE_CODE.unknownScanner,
|
|
298
|
+
severity: "error",
|
|
299
|
+
stage: "invocation",
|
|
300
|
+
actor: "agent",
|
|
301
|
+
fix_kind: "choice",
|
|
302
|
+
what: `This recipe declares no scanner named '${options.scanner}'`,
|
|
303
|
+
why: "Nothing would be checked, so the run would report a pass having examined nothing.",
|
|
304
|
+
fix: declared.length > 0
|
|
305
|
+
? `Pick one of the scanners it does declare:\n${declared.map((n) => ` --scanner ${n}`).join("\n")}`
|
|
306
|
+
: "This recipe declares no scanners at all, so drop --scanner.",
|
|
307
|
+
evidence: { requested: options.scanner, declared },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { findings, config: result.config };
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The verdict, from the findings alone.
|
|
315
|
+
*
|
|
316
|
+
* Three outcomes rather than two. A run that executed cleanly and established nothing is neither a
|
|
317
|
+
* pass nor a failure: called a failure it sends someone hunting a defect that may not exist, called
|
|
318
|
+
* a pass it is precisely the hole this command exists to close. It gets its own verdict and its own
|
|
319
|
+
* exit code so a caller can tell the difference without parsing anything.
|
|
320
|
+
*
|
|
321
|
+
* Pure, and exported, because this is the rule the whole command reduces to.
|
|
322
|
+
*/
|
|
323
|
+
export function decideVerdict(findings, options = {}) {
|
|
324
|
+
const { errors, warnings } = severityOfFindings(findings);
|
|
325
|
+
const strict = options.strict === true;
|
|
326
|
+
const failing = errors > 0 || (strict && warnings > 0);
|
|
327
|
+
// A malformed request is not a failed package: a different exit code, because the remedy is a
|
|
328
|
+
// different command rather than an edit. Checked first — nothing downstream of it ran.
|
|
329
|
+
const invocationError = findings.some((f) => f.stage === "invocation" && f.severity === "error");
|
|
330
|
+
if (invocationError)
|
|
331
|
+
return { verdict: "FAIL", exit_code: 3, errors, warnings };
|
|
332
|
+
// Only the verdict when nothing else is wrong. A genuine error alongside it wins, because there
|
|
333
|
+
// is something concrete to fix first — and under --strict a warning is a genuine error.
|
|
334
|
+
const unprovenOnly = errors > 0 &&
|
|
335
|
+
!(strict && warnings > 0) &&
|
|
336
|
+
findings
|
|
337
|
+
.filter((f) => f.severity === "error")
|
|
338
|
+
.every((f) => f.code === VALIDATE_CODE.tickUnproven);
|
|
339
|
+
if (unprovenOnly)
|
|
340
|
+
return { verdict: "UNPROVEN", exit_code: 2, errors, warnings };
|
|
341
|
+
return failing
|
|
342
|
+
? { verdict: "FAIL", exit_code: 1, errors, warnings }
|
|
343
|
+
: { verdict: "PASS", exit_code: 0, errors, warnings };
|
|
344
|
+
}
|
|
345
|
+
function assemble(target, stagesRun, findings, options, wallet, liveExecuted) {
|
|
346
|
+
const strict = options.strict === true;
|
|
347
|
+
const { verdict, exit_code, errors, warnings } = decideVerdict(findings, { strict });
|
|
348
|
+
const deepest = stagesRun[stagesRun.length - 1];
|
|
349
|
+
// At the live depth what a pass establishes depends on what actually ran. A recipe with no
|
|
350
|
+
// author-written scanner reaches this depth and executes nothing — claiming it "ticked and
|
|
351
|
+
// emitted" would be a green result asserting things it never checked, which is the precise
|
|
352
|
+
// failure this command exists to correct. The claim states the absence instead.
|
|
353
|
+
const claims = deepest === "live" && liveExecuted === 0
|
|
354
|
+
? {
|
|
355
|
+
proves: "the recipe is well formed and every scanner it declares is runtime-provided — there is no author-written code in this package to execute",
|
|
356
|
+
doesNotProve: [
|
|
357
|
+
"anything about author code, since none exists here",
|
|
358
|
+
"runtime-provided scanners, which are covered by the runtime's own tests rather than by this run",
|
|
359
|
+
],
|
|
360
|
+
}
|
|
361
|
+
: deepest === "live" && wallet !== undefined
|
|
362
|
+
? walletClaim(wallet)
|
|
363
|
+
: deepest
|
|
364
|
+
? DEPTH_CLAIMS[deepest]
|
|
365
|
+
: undefined;
|
|
366
|
+
const basis = {
|
|
367
|
+
rule: strict
|
|
368
|
+
? "a pass requires no findings of any severity"
|
|
369
|
+
: "a pass requires no error-severity findings",
|
|
370
|
+
observed: {
|
|
371
|
+
errors,
|
|
372
|
+
warnings,
|
|
373
|
+
stages_run: stagesRun,
|
|
374
|
+
strict,
|
|
375
|
+
...(wallet ? { wallet: wallet.address, wallet_source: wallet.source, book: wallet.book } : {}),
|
|
376
|
+
},
|
|
377
|
+
...(verdict === "PASS" && claims
|
|
378
|
+
? { proves: claims.proves, does_not_prove: claims.doesNotProve }
|
|
379
|
+
: {}),
|
|
380
|
+
...(verdict === "UNPROVEN"
|
|
381
|
+
? {
|
|
382
|
+
why_not_pass: "the tick completed but read no data, so nothing about the scanner was established",
|
|
383
|
+
what_would_change_it: "one successful read through ctx.senpi_mcp during the tick",
|
|
384
|
+
}
|
|
385
|
+
: verdict !== "PASS"
|
|
386
|
+
? {
|
|
387
|
+
why_not_pass: `${errors} error${errors === 1 ? "" : "s"}${strict && warnings > 0 ? ` and ${warnings} warning${warnings === 1 ? "" : "s"} under --strict` : ""}`,
|
|
388
|
+
what_would_change_it: "Apply the fix on each finding below and run again.",
|
|
389
|
+
}
|
|
390
|
+
: {}),
|
|
391
|
+
};
|
|
392
|
+
// A run proves the whole package only when it went all the way, unscoped, and passed.
|
|
393
|
+
const proof_eligible = verdict === "PASS" &&
|
|
394
|
+
options.depth === "live" &&
|
|
395
|
+
options.scanner === undefined &&
|
|
396
|
+
options.noAttest !== true &&
|
|
397
|
+
stagesRun.includes("live") &&
|
|
398
|
+
// Content mode validates text with no durable home; a proof must live beside the package it
|
|
399
|
+
// is a claim about, so there is nothing coherent to record.
|
|
400
|
+
target.recipePath !== undefined;
|
|
401
|
+
return {
|
|
402
|
+
verdict,
|
|
403
|
+
exit_code,
|
|
404
|
+
target: {
|
|
405
|
+
kind: target.kind,
|
|
406
|
+
...(target.recipePath ? { recipe: target.recipePath } : {}),
|
|
407
|
+
...(target.scannerRoot ? { scanner_roots: [target.scannerRoot] } : {}),
|
|
408
|
+
},
|
|
409
|
+
stages_run: stagesRun,
|
|
410
|
+
verdict_basis: basis,
|
|
411
|
+
findings,
|
|
412
|
+
proof_eligible,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* How long one tick may take.
|
|
417
|
+
*
|
|
418
|
+
* Never derived from the cadence. A strategy that ticks every six hours is not a strategy whose
|
|
419
|
+
* tick may take six hours — the cadence is how long the runtime waits between ticks, and inheriting
|
|
420
|
+
* it would make `senpi validate` hang for a working scanner while looking like it was doing
|
|
421
|
+
* something. What the recipe declares wins; failing that, a bound that keeps the command
|
|
422
|
+
* answerable.
|
|
423
|
+
*/
|
|
424
|
+
export function tickBudgetSeconds(declared, override) {
|
|
425
|
+
return override ?? declared?.timeout_seconds ?? DEFAULT_TICK_BUDGET_SECONDS;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* What the environment is missing before a tick could run at all.
|
|
429
|
+
*
|
|
430
|
+
* Named rather than counted: "credentials are missing" leaves someone guessing which, and this is
|
|
431
|
+
* the one finding whose fix belongs to whoever set the environment up rather than to the author.
|
|
432
|
+
*/
|
|
433
|
+
export function missingCredentials(env = process.env) {
|
|
434
|
+
const missing = [];
|
|
435
|
+
// Named as a pair: either satisfies the check, and the agent box's normal credential is
|
|
436
|
+
// SENPI_AUTH_TOKEN — telling someone to set SENPI_API_KEY when they already have the other one
|
|
437
|
+
// sends them looking for a second credential that does not exist.
|
|
438
|
+
if (!env.SENPI_API_KEY && !env.SENPI_AUTH_TOKEN)
|
|
439
|
+
missing.push("SENPI_AUTH_TOKEN (or SENPI_API_KEY)");
|
|
440
|
+
if (!env.SENPI_MCP_URL)
|
|
441
|
+
missing.push("SENPI_MCP_URL");
|
|
442
|
+
return missing;
|
|
443
|
+
}
|
|
444
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/validate/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Would the signals this tick produced actually be accepted?
|
|
3
|
+
*
|
|
4
|
+
* A tick can read successfully, compute correctly, and emit something intake rejects at the wire —
|
|
5
|
+
* and from the scanner's side that is indistinguishable from success. It returned its candidates
|
|
6
|
+
* and the loop reported a healthy tick; the 400 happens somewhere else, to someone else. That gap
|
|
7
|
+
* is the last place a strategy can look runnable while producing nothing.
|
|
8
|
+
*
|
|
9
|
+
* ## Judged against intake's own schema
|
|
10
|
+
*
|
|
11
|
+
* `SignalsBodySchema` is imported from the route rather than described here, and checked with the
|
|
12
|
+
* same `Value.Check` the route calls. A second description of the contract would be a second thing
|
|
13
|
+
* to keep in step, and the failure mode of getting it wrong is the one this feature exists to
|
|
14
|
+
* prevent: a green result that means less than it claims.
|
|
15
|
+
*
|
|
16
|
+
* The whole body is checked, not the signal alone, because the body is what intake receives —
|
|
17
|
+
* `additionalProperties: false` at that level is part of the contract too.
|
|
18
|
+
*/
|
|
19
|
+
import type { ScannerTarget } from "./import-stage.js";
|
|
20
|
+
import type { Finding } from "./types.js";
|
|
21
|
+
/** One candidate as the harness reports it, before and after the envelope was built. */
|
|
22
|
+
export interface SignalRecord {
|
|
23
|
+
/** What scan() returned. */
|
|
24
|
+
emitted: unknown;
|
|
25
|
+
/** The envelope, or null when the scaffold refused to build one. */
|
|
26
|
+
wire: unknown;
|
|
27
|
+
/** Why the scaffold refused, when it did. */
|
|
28
|
+
rejected_by_scaffold: {
|
|
29
|
+
reason?: string;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Judge the signals one tick produced.
|
|
34
|
+
*
|
|
35
|
+
* Separate from judging the tick because these are separate claims — a tick can be perfectly
|
|
36
|
+
* healthy and emit nothing acceptable — and because this needs no harness at all, so the cases that
|
|
37
|
+
* matter are testable directly.
|
|
38
|
+
*/
|
|
39
|
+
export declare function judgeSignals(scanner: ScannerTarget, signals: SignalRecord[]): Finding[];
|
|
40
|
+
//# sourceMappingURL=signal-stage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-stage.d.ts","sourceRoot":"","sources":["../../src/validate/signal-stage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAC;IACd,6CAA6C;IAC7C,oBAAoB,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7C;AAoDD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CAsDvF"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Would the signals this tick produced actually be accepted?
|
|
3
|
+
*
|
|
4
|
+
* A tick can read successfully, compute correctly, and emit something intake rejects at the wire —
|
|
5
|
+
* and from the scanner's side that is indistinguishable from success. It returned its candidates
|
|
6
|
+
* and the loop reported a healthy tick; the 400 happens somewhere else, to someone else. That gap
|
|
7
|
+
* is the last place a strategy can look runnable while producing nothing.
|
|
8
|
+
*
|
|
9
|
+
* ## Judged against intake's own schema
|
|
10
|
+
*
|
|
11
|
+
* `SignalsBodySchema` is imported from the route rather than described here, and checked with the
|
|
12
|
+
* same `Value.Check` the route calls. A second description of the contract would be a second thing
|
|
13
|
+
* to keep in step, and the failure mode of getting it wrong is the one this feature exists to
|
|
14
|
+
* prevent: a green result that means less than it claims.
|
|
15
|
+
*
|
|
16
|
+
* The whole body is checked, not the signal alone, because the body is what intake receives —
|
|
17
|
+
* `additionalProperties: false` at that level is part of the contract too.
|
|
18
|
+
*/
|
|
19
|
+
import { Value } from "@sinclair/typebox/value";
|
|
20
|
+
import { SignalsBodySchema } from "../runtime-api/routes/signals.schema.js";
|
|
21
|
+
import { VALIDATE_CODE } from "./codes.js";
|
|
22
|
+
/**
|
|
23
|
+
* A percentage below one.
|
|
24
|
+
*
|
|
25
|
+
* `marginPct` is a percent of withdrawable, so `0.1` means a tenth of one percent — a position
|
|
26
|
+
* roughly a thousand times smaller than the `10` the author almost certainly meant. Intake accepts
|
|
27
|
+
* it, the exchange accepts it, and the strategy trades sizes too small to matter while looking
|
|
28
|
+
* entirely healthy.
|
|
29
|
+
*/
|
|
30
|
+
const SUSPICIOUS_MARGIN_PCT = 1;
|
|
31
|
+
function signalFinding(code, severity, parts) {
|
|
32
|
+
return {
|
|
33
|
+
code,
|
|
34
|
+
severity,
|
|
35
|
+
stage: "live",
|
|
36
|
+
actor: "agent",
|
|
37
|
+
fix_kind: "edit",
|
|
38
|
+
...parts,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Describe an emitted candidate briefly enough to recognise, without dumping it whole. */
|
|
42
|
+
function identify(record, index) {
|
|
43
|
+
const emitted = record.emitted;
|
|
44
|
+
const asset = typeof emitted?.asset === "string" ? emitted.asset : undefined;
|
|
45
|
+
const direction = typeof emitted?.direction === "string" ? emitted.direction : undefined;
|
|
46
|
+
if (asset && direction)
|
|
47
|
+
return `${direction} ${asset}`;
|
|
48
|
+
if (asset)
|
|
49
|
+
return asset;
|
|
50
|
+
return `signal ${index + 1}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* What intake would say about this body, in its own words.
|
|
54
|
+
*
|
|
55
|
+
* Reported per field: "the body was rejected" is not a fix, and the offending path plus the value
|
|
56
|
+
* that was there is the difference between an edit and an investigation.
|
|
57
|
+
*/
|
|
58
|
+
function schemaComplaints(body) {
|
|
59
|
+
return [...Value.Errors(SignalsBodySchema, body)]
|
|
60
|
+
.slice(0, 10)
|
|
61
|
+
.map((e) => {
|
|
62
|
+
const path = e.path === "" ? "the body" : e.path.replace(/^\/signals\/\d+\//, "");
|
|
63
|
+
return `${path}: ${e.message} (got ${JSON.stringify(e.value)?.slice(0, 80)})`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Judge the signals one tick produced.
|
|
68
|
+
*
|
|
69
|
+
* Separate from judging the tick because these are separate claims — a tick can be perfectly
|
|
70
|
+
* healthy and emit nothing acceptable — and because this needs no harness at all, so the cases that
|
|
71
|
+
* matter are testable directly.
|
|
72
|
+
*/
|
|
73
|
+
export function judgeSignals(scanner, signals) {
|
|
74
|
+
const findings = [];
|
|
75
|
+
for (const [index, record] of signals.entries()) {
|
|
76
|
+
const label = identify(record, index);
|
|
77
|
+
// The scaffold refused to build an envelope, so this never reaches the wire at all. In
|
|
78
|
+
// production the loop logs it and carries on: the scanner reports a healthy tick, and the
|
|
79
|
+
// candidate simply evaporates.
|
|
80
|
+
if (record.wire === null || record.wire === undefined) {
|
|
81
|
+
const reasons = record.rejected_by_scaffold.map((r) => r.reason).filter(Boolean);
|
|
82
|
+
findings.push(signalFinding(VALIDATE_CODE.signalRejected, "error", {
|
|
83
|
+
what: `${label} was discarded before it reached the runtime: ${reasons.join("; ") || "the envelope could not be built"}`,
|
|
84
|
+
why: "The tick still reports success — a candidate the scaffold refuses is dropped quietly, so the scanner looks healthy while producing nothing that can be traded.",
|
|
85
|
+
fix: `Correct what scan() returns for ${label} so the envelope can be built.`,
|
|
86
|
+
evidence: { emitted: record.emitted, rejected_by_scaffold: record.rejected_by_scaffold },
|
|
87
|
+
}));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// The exact body intake receives, checked the way intake checks it.
|
|
91
|
+
const body = { scanner_id: scanner.name, signals: [record.wire] };
|
|
92
|
+
if (!Value.Check(SignalsBodySchema, body)) {
|
|
93
|
+
const complaints = schemaComplaints(body);
|
|
94
|
+
findings.push(signalFinding(VALIDATE_CODE.signalRejected, "error", {
|
|
95
|
+
what: `${label} would be rejected by intake: ${complaints[0] ?? "the body does not match the accepted shape"}`,
|
|
96
|
+
why: "Intake answers 400 and the signal is never processed. The scanner does not see that reply — it returned its candidates and its tick was recorded as healthy — so this is silent from the strategy's side.",
|
|
97
|
+
fix: complaints.length > 1
|
|
98
|
+
? `Correct each of: ${complaints.join(" · ")}`
|
|
99
|
+
: `Correct ${complaints[0] ?? "the emitted shape"}.`,
|
|
100
|
+
evidence: { wire: record.wire, rejections: complaints },
|
|
101
|
+
}));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const marginPct = record.wire.marginPct;
|
|
105
|
+
if (typeof marginPct === "number" && marginPct > 0 && marginPct < SUSPICIOUS_MARGIN_PCT) {
|
|
106
|
+
findings.push(signalFinding(VALIDATE_CODE.marginPctSuspicious, "warning", {
|
|
107
|
+
what: `${label} sizes at marginPct ${marginPct}`,
|
|
108
|
+
why: `This field is a percent of withdrawable, so ${marginPct} means ${marginPct} percent — not ${marginPct * 100}. Nothing rejects it: the position is simply opened about a hundred times smaller than intended, and the strategy trades on happily.`,
|
|
109
|
+
fix: `If ${marginPct * 100} percent was intended, emit ${marginPct * 100}. If ${marginPct} percent really was, this warning is the only way to say so — it needs no change.`,
|
|
110
|
+
evidence: { marginPct },
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return findings;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=signal-stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-stage.js","sourceRoot":"","sources":["../../src/validate/signal-stage.ts"],"names":[],"mappings":"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"}
|