@senpi/runtime 3.0.56 → 3.0.58
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/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +28 -2
- package/dist/cli/guide-commands.js.map +1 -1
- 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/index.d.ts.map +1 -1
- package/dist/index.js +62 -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 +30 -0
- package/dist/scanners/external/scaffold/import_check.py +310 -0
- package/dist/scanners/external/scaffold/preflight.py +545 -0
- package/dist/scanners/external/scaffold/scaffold.py +19 -6
- 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 +40 -0
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +12 -0
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/validate/codes.d.ts +165 -0
- package/dist/validate/codes.d.ts.map +1 -0
- package/dist/validate/codes.js +381 -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 +244 -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 +134 -0
- package/dist/validate/live-stage.d.ts.map +1 -0
- package/dist/validate/live-stage.js +472 -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 +269 -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 +205 -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 +80 -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 +471 -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,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live depth: run one tick against real data, and say what it established.
|
|
3
|
+
*
|
|
4
|
+
* The harness is in Python, beside the loop production uses. This side builds its launch
|
|
5
|
+
* configuration, runs it, and judges the outcome.
|
|
6
|
+
*
|
|
7
|
+
* ## The judgement that matters
|
|
8
|
+
*
|
|
9
|
+
* A tick returning nothing is ambiguous. It could be a healthy scanner with no setups, or one that
|
|
10
|
+
* stopped before touching anything, or one whose every read failed. Those look identical from the
|
|
11
|
+
* outside and mean completely different things, so the verdict keys on **successful reads** rather
|
|
12
|
+
* than on the tick completing.
|
|
13
|
+
*
|
|
14
|
+
* A read that failed is not a read. Observed in practice: an authentication failure arrives as an
|
|
15
|
+
* ordinary response carrying a false flag, raising nothing, so a scanner's own error handling never
|
|
16
|
+
* fires and it computes a confidently wrong answer from missing input. Counting that as a read
|
|
17
|
+
* would report the run clean.
|
|
18
|
+
*/
|
|
19
|
+
// Via the repo's wrapper, never the stdlib module directly: OpenClaw's install gate
|
|
20
|
+
// blocks any plugin file matching BOTH a spawn-like call AND the literal module name,
|
|
21
|
+
// and `safe-spawn` exists to keep those bytes out of a file. Importing it directly here
|
|
22
|
+
// shipped in 3.0.57, whose published dist the gate refuses outright — a hard failure,
|
|
23
|
+
// not a warning, because senpi-agent's bootstrap deliberately stopped passing
|
|
24
|
+
// --dangerously-force-unsafe-install so that exactly this regression would be loud.
|
|
25
|
+
import { spawn } from "../utils/safe-spawn.js";
|
|
26
|
+
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { tmpdir } from "node:os";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
import { VALIDATE_CODE } from "./codes.js";
|
|
30
|
+
import { scaffoldPackageDir } from "./import-stage.js";
|
|
31
|
+
import { judgeSignals } from "./signal-stage.js";
|
|
32
|
+
function liveFinding(code, fixKind, severity, parts) {
|
|
33
|
+
const actor = code === VALIDATE_CODE.noCredentials || code === VALIDATE_CODE.envMissingSdk ? "ops" : "agent";
|
|
34
|
+
return { code, severity, stage: "live", actor, fix_kind: fixKind, ...parts };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Group failed reads by the service they belong to.
|
|
38
|
+
*
|
|
39
|
+
* One failed call among many is noise; every call to one service failing means a whole input is
|
|
40
|
+
* missing and the scanner scored without it. The tool-name prefix is the service, which is enough
|
|
41
|
+
* to tell those apart without knowing anything about the catalogue.
|
|
42
|
+
*/
|
|
43
|
+
export function groupFailuresByService(reads) {
|
|
44
|
+
const grouped = new Map();
|
|
45
|
+
for (const read of reads) {
|
|
46
|
+
if (read.ok)
|
|
47
|
+
continue;
|
|
48
|
+
const service = read.tool.split("_")[0] ?? read.tool;
|
|
49
|
+
grouped.set(service, [...(grouped.get(service) ?? []), read]);
|
|
50
|
+
}
|
|
51
|
+
return grouped;
|
|
52
|
+
}
|
|
53
|
+
/** Services for which every attempted call failed — a whole input the scanner did not get. */
|
|
54
|
+
function fullyFailedServices(reads) {
|
|
55
|
+
const attempted = new Map();
|
|
56
|
+
for (const read of reads) {
|
|
57
|
+
const service = read.tool.split("_")[0] ?? read.tool;
|
|
58
|
+
const entry = attempted.get(service) ?? { ok: 0, failed: 0 };
|
|
59
|
+
if (read.ok)
|
|
60
|
+
entry.ok += 1;
|
|
61
|
+
else
|
|
62
|
+
entry.failed += 1;
|
|
63
|
+
attempted.set(service, entry);
|
|
64
|
+
}
|
|
65
|
+
return [...attempted.entries()]
|
|
66
|
+
.filter(([, counts]) => counts.failed > 0 && counts.ok === 0)
|
|
67
|
+
.map(([service]) => service)
|
|
68
|
+
.sort();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Production's tick budget for a scanner, mirroring the scaffold's own fallback.
|
|
72
|
+
*
|
|
73
|
+
* Kept beside the judgement that reports it, with the source named, because the whole point is
|
|
74
|
+
* that this number is production's and not ours.
|
|
75
|
+
*/
|
|
76
|
+
export function productionBudgetSeconds(declaredTimeout, intervalSeconds) {
|
|
77
|
+
return declaredTimeout ?? intervalSeconds;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Turn one tick into findings.
|
|
81
|
+
*
|
|
82
|
+
* Exported separately from running it so the judgement can be tested against recorded outcomes
|
|
83
|
+
* without spawning anything or needing credentials.
|
|
84
|
+
*/
|
|
85
|
+
export function judgeTick(scanner, outcome, context = {}) {
|
|
86
|
+
if (!outcome.ok) {
|
|
87
|
+
if (outcome.reason === "no_credentials") {
|
|
88
|
+
return [
|
|
89
|
+
liveFinding(VALIDATE_CODE.noCredentials, "command", "error", {
|
|
90
|
+
what: "No data-client credentials are configured in this environment",
|
|
91
|
+
why: "A live run reads real market data. Without credentials the scanner cannot be executed at all, so nothing about it can be established.",
|
|
92
|
+
fix: "Set the access token and service endpoint in this environment, or use --stage import to check the code without executing it.",
|
|
93
|
+
evidence: { detail: outcome.detail },
|
|
94
|
+
}),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
if (outcome.reason === "outer_timeout") {
|
|
98
|
+
// This kill exists for exactly what the tick's OWN budget cannot reach — a stall somewhere
|
|
99
|
+
// the in-process alarm never interrupts, a connection that never resolves. So the likely
|
|
100
|
+
// cause is the scanner, and routing it to ops told the one person who could fix it that it
|
|
101
|
+
// was not theirs.
|
|
102
|
+
return [
|
|
103
|
+
liveFinding(VALIDATE_CODE.tickUnresponsive, "locate-only", "error", {
|
|
104
|
+
what: `The tick for scanner '${scanner.name}' stopped responding and had to be killed`,
|
|
105
|
+
why: "It passed its own budget without the in-process timeout ending it, which means it stalled somewhere that timeout cannot interrupt — most often a network read with no deadline, or a blocking call outside Python's control. Production has the same blind spot, so a tick that hangs here can hang there.",
|
|
106
|
+
fix: "Give every outbound call an explicit timeout, and check for blocking work that does not yield — a socket read, a subprocess, a C extension.",
|
|
107
|
+
evidence: { detail: outcome.detail },
|
|
108
|
+
}),
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
return [
|
|
112
|
+
liveFinding(VALIDATE_CODE.envMissingSdk, "command", "error", {
|
|
113
|
+
what: `The tick could not be started for scanner '${scanner.name}': ${outcome.detail ?? outcome.reason}`,
|
|
114
|
+
why: "The harness never ran, so nothing was learned about the strategy either way. This is the environment the validator runs in.",
|
|
115
|
+
fix: "Check that the Python interpreter and its dependencies are installed as the runtime image installs them.",
|
|
116
|
+
evidence: { reason: outcome.reason, detail: outcome.detail },
|
|
117
|
+
}),
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
const findings = [];
|
|
121
|
+
const recorded = outcome.reads ?? [];
|
|
122
|
+
// A refused write is not a failed read. Counted among them it would look like an unavailable
|
|
123
|
+
// service, which points at the wrong fix entirely.
|
|
124
|
+
const blocked = recorded.filter((r) => r.blocked);
|
|
125
|
+
const reads = recorded.filter((r) => !r.blocked);
|
|
126
|
+
const readsOk = outcome.reads_ok ?? 0;
|
|
127
|
+
const readsFailed = (outcome.reads_failed ?? 0) - blocked.length;
|
|
128
|
+
const signals = outcome.signals ?? [];
|
|
129
|
+
for (const call of blocked) {
|
|
130
|
+
findings.push(liveFinding(VALIDATE_CODE.mcpPermission, "edit", "error", {
|
|
131
|
+
what: `The scanner called '${call.tool}', which changes money or account state`,
|
|
132
|
+
why: "Scanners are read-only: the call was refused before any transport was opened, here and in production alike. So this is not a validation-only restriction — it means the scanner is trying to do something it will never be able to do, and whatever it expected to follow never happens.",
|
|
133
|
+
fix: `A scanner reads and returns candidates; opening, closing, or altering a position is the runtime's job downstream of the signal. Remove the '${call.tool}' call and emit a signal instead.`,
|
|
134
|
+
evidence: { tool: call.tool },
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
// ── the tick itself ──────────────────────────────────────────────────────
|
|
138
|
+
if (outcome.status === "timeout") {
|
|
139
|
+
findings.push(liveFinding(VALIDATE_CODE.tickTimeout, "edit", "error", { ...timeoutParts(context.budget, reads.length) }));
|
|
140
|
+
return findings;
|
|
141
|
+
}
|
|
142
|
+
if (outcome.status === "error" && outcome.failure?.type === "EgressBlocked") {
|
|
143
|
+
const where = outcome.failure.where ?? undefined;
|
|
144
|
+
findings.push(liveFinding(VALIDATE_CODE.egressBlocked, "edit", "error", {
|
|
145
|
+
what: "The scanner tried to send data out of the process",
|
|
146
|
+
why: "Validation observes a tick and delivers nothing, so the attempt was refused. A scanner that posts its own results bypasses the signal path entirely, and nothing downstream would see what it produced.",
|
|
147
|
+
fix: "Return candidates from scan() and let the runtime deliver them.",
|
|
148
|
+
where: where ? { file: where.file, line: where.line } : undefined,
|
|
149
|
+
evidence: { message: outcome.failure.message },
|
|
150
|
+
}));
|
|
151
|
+
return findings;
|
|
152
|
+
}
|
|
153
|
+
if (outcome.status === "error" && outcome.failure) {
|
|
154
|
+
const where = outcome.failure.where ?? undefined;
|
|
155
|
+
findings.push(liveFinding(VALIDATE_CODE.tickException, "locate-only", "error", {
|
|
156
|
+
what: `${outcome.failure.type}: ${outcome.failure.message}`,
|
|
157
|
+
why: "The tick raised, so the runtime would record a failed tick and produce no signals. Every tick would do the same until this is fixed.",
|
|
158
|
+
fix: where
|
|
159
|
+
? `The failure is at ${where.file}:${where.line}.`
|
|
160
|
+
: "The failure is inside scan(); the captured output below has the detail.",
|
|
161
|
+
where: where ? { file: where.file, line: where.line } : undefined,
|
|
162
|
+
evidence: { stderr: outcome.stderr, stdout: outcome.stdout },
|
|
163
|
+
}));
|
|
164
|
+
return findings;
|
|
165
|
+
}
|
|
166
|
+
// ── failures with a specific cause ───────────────────────────────────────
|
|
167
|
+
// Diagnosed before the general accounting, because "the service is down" and "that name does not
|
|
168
|
+
// exist" lead to completely different work and the second is answerable from a catalogue.
|
|
169
|
+
const diagnosed = new Set();
|
|
170
|
+
for (const [tool, cause] of namedCauses(reads)) {
|
|
171
|
+
const example = reads.find((r) => r.tool === tool && !r.ok);
|
|
172
|
+
for (const read of reads)
|
|
173
|
+
if (read.tool === tool && !read.ok)
|
|
174
|
+
diagnosed.add(read);
|
|
175
|
+
findings.push(cause === "unknown-tool"
|
|
176
|
+
? liveFinding(VALIDATE_CODE.unknownMcpTool, "lookup", "error", {
|
|
177
|
+
what: `'${tool}' is not a data source this server exposes`,
|
|
178
|
+
why: "Every call to it fails, so whatever the scanner intended to read it never gets. The name is likely misremembered or from an older catalogue.",
|
|
179
|
+
fix: "Look up the available data sources and use the one that carries this data.",
|
|
180
|
+
evidence: { tool, error: example.error },
|
|
181
|
+
})
|
|
182
|
+
: liveFinding(VALIDATE_CODE.unknownTicker, "lookup", "error", {
|
|
183
|
+
what: `The instrument named in the '${tool}' call is not tradeable`,
|
|
184
|
+
why: "A hardcoded instrument that has been delisted, renamed, or never existed fails on every tick, and does so quietly enough that the scanner keeps running.",
|
|
185
|
+
fix: "Check the name against the tradeable instrument list, or select instruments from that list rather than hardcoding them.",
|
|
186
|
+
evidence: { tool, error: example.error },
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
// ── what it read ─────────────────────────────────────────────────────────
|
|
190
|
+
const undiagnosed = reads.filter((r) => !diagnosed.has(r));
|
|
191
|
+
const deadServices = fullyFailedServices(undiagnosed);
|
|
192
|
+
if (readsOk === 0 && readsFailed > 0 && undiagnosed.some((r) => !r.ok)) {
|
|
193
|
+
findings.push(liveFinding(VALIDATE_CODE.mcpToolError, "locate-only", "error", {
|
|
194
|
+
what: `Every data read failed — ${readsFailed} attempted, none succeeded`,
|
|
195
|
+
why: "The scanner completed without fetching anything, so any output it produced was computed from missing input. Nothing raised, which is why this is invisible in production.",
|
|
196
|
+
fix: `Resolve the failures below before trusting this scanner. First: ${undiagnosed.find((r) => !r.ok)?.error ?? "see the read list"}`,
|
|
197
|
+
evidence: { reads: undiagnosed },
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
else if (deadServices.length > 0) {
|
|
201
|
+
// Not fatal — other reads worked — but a whole input is missing, which is a different claim
|
|
202
|
+
// from "one call failed" and is easy to miss among a long list of successful reads.
|
|
203
|
+
for (const service of deadServices) {
|
|
204
|
+
const failed = undiagnosed.filter((r) => !r.ok && r.tool.startsWith(service));
|
|
205
|
+
findings.push(liveFinding(VALIDATE_CODE.someReadsFailed, "locate-only", "warning", {
|
|
206
|
+
what: `Every '${service}' read failed (${failed.length} of ${failed.length}), while other reads succeeded`,
|
|
207
|
+
why: `The scanner ran and produced output, but without any ${service} data. Nothing raised — the failure arrives as an ordinary response — so the scanner's own error handling does not see it and it scores as though that input were simply empty.`,
|
|
208
|
+
fix: `Establish why ${service} is unavailable: ${failed[0]?.error ?? "see the read list"}`,
|
|
209
|
+
evidence: { service, failed },
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (undiagnosed.some((r) => !r.ok)) {
|
|
214
|
+
findings.push(liveFinding(VALIDATE_CODE.someReadsFailed, "locate-only", "warning", {
|
|
215
|
+
what: `${undiagnosed.filter((r) => !r.ok).length} of ${undiagnosed.length} reads failed`,
|
|
216
|
+
why: "The scanner had partial data. That may be expected for a single unavailable asset, but it is worth knowing which.",
|
|
217
|
+
fix: `Check the failures listed in the evidence.`,
|
|
218
|
+
evidence: { failed: undiagnosed.filter((r) => !r.ok) },
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
// ── did it prove anything ────────────────────────────────────────────────
|
|
222
|
+
if (readsOk === 0 && readsFailed === 0) {
|
|
223
|
+
const exit = outcome.last_return;
|
|
224
|
+
const offRoute = context.nonMcpSources ?? [];
|
|
225
|
+
// Two facts, stated separately. The import check is import-level, not call-level, so a scanner
|
|
226
|
+
// may import one of these for something other than fetching and still have a genuine early
|
|
227
|
+
// exit. Asserting which one caused this would be a guess, and a confident wrong diagnosis costs
|
|
228
|
+
// more than an honest pair of observations.
|
|
229
|
+
findings.push(liveFinding(VALIDATE_CODE.tickUnproven, "edit", "error", {
|
|
230
|
+
what: offRoute.length > 0
|
|
231
|
+
? `The tick read nothing through ctx.senpi_mcp, and ${describeSources(offRoute)} a data route the runtime cannot observe`
|
|
232
|
+
: "The tick completed without reading any data",
|
|
233
|
+
why: "A scanner that reads nothing observable is indistinguishable from a healthy one with nothing to trade. This is how a fatal bug behind a time-of-day condition stayed hidden for eighteen hours and then lost most of a funded strategy on the first window that opened.",
|
|
234
|
+
fix: offRoute.length > 0
|
|
235
|
+
? `Fetch through ctx.senpi_mcp so reads are counted, cast, and recorded when they fail. If the data genuinely has no MCP source, say so — this cannot be proven runnable as it stands.${exit ? ` The tick returned at ${exit.file}:${exit.line}.` : ""}`
|
|
236
|
+
: exit
|
|
237
|
+
? `The tick returned at ${exit.file}:${exit.line} in ${exit.function}(). If that is a gate, let it be bypassed when ctx.dry_run is set, so validation can see a real pass.`
|
|
238
|
+
: "Ensure scan() reads through ctx.senpi_mcp before returning.",
|
|
239
|
+
where: exit ? { file: exit.file, line: exit.line } : offRoute[0],
|
|
240
|
+
evidence: {
|
|
241
|
+
signals_emitted: signals.length,
|
|
242
|
+
...(offRoute.length > 0 ? { non_mcp_sources: offRoute } : {}),
|
|
243
|
+
},
|
|
244
|
+
}));
|
|
245
|
+
if (signals.length > 0) {
|
|
246
|
+
findings.push(liveFinding(VALIDATE_CODE.signalsWithoutReads, "edit", "warning", {
|
|
247
|
+
what: `${signals.length} signal(s) were emitted having read no data`,
|
|
248
|
+
why: "Output derived from no input is either hardcoded or built from stored state, and validation runs against empty state — so nothing here reflects the market.",
|
|
249
|
+
fix: "Fetch through ctx.senpi_mcp before emitting.",
|
|
250
|
+
evidence: { signals: signals.length },
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// ── worth knowing on an otherwise clean run ──────────────────────────────
|
|
255
|
+
if (readsOk > 0 && signals.length === 0) {
|
|
256
|
+
findings.push(liveFinding(VALIDATE_CODE.noSignalsEmitted, "locate-only", "warning", {
|
|
257
|
+
what: `The tick read ${readsOk} time(s) and emitted nothing`,
|
|
258
|
+
why: "Normal for a scanner with no setup right now. Worth confirming it is the market rather than a filter that never passes, or a response key that does not exist.",
|
|
259
|
+
fix: `Compare the keys the code reads against those the responses carried: ${describeShapes(reads)}`,
|
|
260
|
+
evidence: { reads },
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
// What was emitted is a separate claim from whether the tick ran, and is judged separately.
|
|
264
|
+
findings.push(...judgeSignals(scanner, signals));
|
|
265
|
+
const unread = reads.filter((r) => r.ok && r.unread);
|
|
266
|
+
if (unread.length > 0) {
|
|
267
|
+
findings.push(liveFinding(VALIDATE_CODE.unusedMcpResponse, "edit", "warning", {
|
|
268
|
+
what: `${unread.length} response(s) came back and nothing was read out of them: ${unread.map((r) => r.tool).join(", ")}`,
|
|
269
|
+
why: "Usually the shape is not what the code expects — a renamed key, or a nesting level missed. The read succeeded and the scanner carried on, so it scored from whatever the default was.",
|
|
270
|
+
fix: `Compare the keys the code reads against what came back: ${describeShapes(unread)}`,
|
|
271
|
+
evidence: { unread },
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
const textNumerics = new Map();
|
|
275
|
+
for (const signal of signals) {
|
|
276
|
+
const data = signal.emitted?.data;
|
|
277
|
+
const fields = textNumericFields(data);
|
|
278
|
+
if (fields.length > 0)
|
|
279
|
+
textNumerics.set(fields.join(", "), fields);
|
|
280
|
+
}
|
|
281
|
+
if (textNumerics.size > 0) {
|
|
282
|
+
const fields = [...new Set([...textNumerics.values()].flat())].sort();
|
|
283
|
+
findings.push(liveFinding(VALIDATE_CODE.stringNumerics, "edit", "warning", {
|
|
284
|
+
what: `Emitted signal data carries numbers as text: ${fields.join(", ")}`,
|
|
285
|
+
why: "Text compares and sorts character by character, so a threshold holds for some values and quietly fails for others — \"9\" is above \"10\". Nothing raises either way.",
|
|
286
|
+
fix: `Convert ${fields.join(", ")} with float() before putting them in the signal's data.`,
|
|
287
|
+
evidence: { fields },
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
for (const suppressed of outcome.suppressed ?? []) {
|
|
291
|
+
findings.push(liveFinding(VALIDATE_CODE.suppressedException, "locate-only", "warning", {
|
|
292
|
+
what: `${suppressed.type} raised at ${suppressed.file}:${suppressed.line} and was caught: ${suppressed.message}`,
|
|
293
|
+
why: "The author contract asks scanners to return an empty list rather than crash, so in production this leaves no trace at all — the scanner simply produces less, or nothing, and still reports a healthy tick.",
|
|
294
|
+
fix: `Handle the cause at ${suppressed.file}:${suppressed.line}, or narrow the except so a genuine defect is not swallowed with it.`,
|
|
295
|
+
where: { file: suppressed.file, line: suppressed.line },
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
return findings;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Failures whose cause the message itself gives away.
|
|
302
|
+
*
|
|
303
|
+
* Matched on the message rather than by checking a catalogue, because the server is the catalogue
|
|
304
|
+
* and it has already answered. The patterns are deliberately narrow: a failure that does not match
|
|
305
|
+
* falls through to the general accounting, which is correct, whereas a wrong match sends someone
|
|
306
|
+
* looking up a name that was never the problem.
|
|
307
|
+
*
|
|
308
|
+
* Keyed by tool, since one bad name fails every time it is called and is one thing to fix.
|
|
309
|
+
*/
|
|
310
|
+
function namedCauses(reads) {
|
|
311
|
+
const causes = new Map();
|
|
312
|
+
for (const read of reads) {
|
|
313
|
+
if (read.ok || !read.error)
|
|
314
|
+
continue;
|
|
315
|
+
const message = read.error.toLowerCase();
|
|
316
|
+
if (/unknown tool|no such tool|tool not found|is not a (known|valid) tool/.test(message)) {
|
|
317
|
+
causes.set(read.tool, "unknown-tool");
|
|
318
|
+
}
|
|
319
|
+
else if (/unknown (asset|coin|symbol|instrument)|(asset|coin|symbol|instrument) not (found|supported)|not tradeable/.test(message)) {
|
|
320
|
+
causes.set(read.tool, "unknown-instrument");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return causes;
|
|
324
|
+
}
|
|
325
|
+
/** Values that are numbers written as text — `"1250.5"`, not `"BTC"` or `""`. */
|
|
326
|
+
function textNumericFields(data) {
|
|
327
|
+
if (typeof data !== "object" || data === null)
|
|
328
|
+
return [];
|
|
329
|
+
return Object.entries(data)
|
|
330
|
+
.filter(([, v]) => typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)))
|
|
331
|
+
.map(([k]) => k);
|
|
332
|
+
}
|
|
333
|
+
/** Where the off-route imports are, as a phrase that reads in a sentence. */
|
|
334
|
+
function describeSources(sources) {
|
|
335
|
+
const at = sources
|
|
336
|
+
.slice(0, 3)
|
|
337
|
+
.map((s) => (s.line === undefined ? s.file : `${s.file}:${s.line}`))
|
|
338
|
+
.join(", ");
|
|
339
|
+
return sources.length === 1 ? `${at} imports` : `${at} import`;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* What a timeout means, which depends on whether the two budgets agree.
|
|
343
|
+
*
|
|
344
|
+
* When the recipe declares `timeout_seconds`, validate enforced exactly what production will, and
|
|
345
|
+
* the strong statement is true. When it does not, production falls back to the cadence — often far
|
|
346
|
+
* larger — and the honest report says so rather than claiming a production failure that would not
|
|
347
|
+
* happen. It is still worth reporting: a tick taking minutes is worth a look even where nothing
|
|
348
|
+
* kills it.
|
|
349
|
+
*/
|
|
350
|
+
function timeoutParts(budget, calls) {
|
|
351
|
+
const workFix = `Reduce the work in one tick — this one made ${calls} call${calls === 1 ? "" : "s"}`;
|
|
352
|
+
if (budget === undefined || budget.declared) {
|
|
353
|
+
const seconds = budget?.validateSeconds;
|
|
354
|
+
return {
|
|
355
|
+
what: `scan() did not finish within the ${seconds !== undefined ? `${seconds}s ` : ""}budget this recipe declares`,
|
|
356
|
+
why: "The runtime kills a tick at this budget in production too, so this would time out on every tick rather than only here.",
|
|
357
|
+
fix: `${workFix} — or raise timeout_seconds if the budget is genuinely too tight.`,
|
|
358
|
+
evidence: { calls, ...(seconds !== undefined ? { budget_seconds: seconds } : {}) },
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
what: `scan() did not finish within this check's ${budget.validateSeconds}s budget`,
|
|
363
|
+
why: `The recipe declares no timeout_seconds, so production would allow ${budget.productionSeconds}s — its cadence — and this tick would probably not be killed there. This check caps at ${budget.validateSeconds}s so a long-cadence scanner cannot make it hang. So: not proof of a production timeout, but a tick this slow is worth understanding.`,
|
|
364
|
+
fix: `${workFix}. If the duration is expected, declare timeout_seconds so the budget is explicit, and pass --timeout to check against it here.`,
|
|
365
|
+
evidence: {
|
|
366
|
+
calls,
|
|
367
|
+
validate_budget_seconds: budget.validateSeconds,
|
|
368
|
+
production_budget_seconds: budget.productionSeconds,
|
|
369
|
+
timeout_declared: false,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function describeShapes(reads) {
|
|
374
|
+
const shapes = reads
|
|
375
|
+
.filter((r) => r.ok && r.response_keys)
|
|
376
|
+
.slice(0, 3)
|
|
377
|
+
.map((r) => `${r.tool} → [${r.response_keys.join(", ")}]`);
|
|
378
|
+
return shapes.length > 0 ? shapes.join("; ") : "no responses were recorded";
|
|
379
|
+
}
|
|
380
|
+
/** Run one tick for one scanner. */
|
|
381
|
+
export async function runTick(options) {
|
|
382
|
+
const dir = mkdtempSync(join(tmpdir(), "senpi-validate-"));
|
|
383
|
+
const configPath = join(dir, "launch.json");
|
|
384
|
+
// The result travels by file, not by stdout. The tick shares this process's stdout with the
|
|
385
|
+
// author's code, and a scanner writing to the descriptor directly — os.write, a subprocess, a C
|
|
386
|
+
// extension — would corrupt a document parsed off that buffer. Reported as a broken environment,
|
|
387
|
+
// which is the wrong person. A path we own makes it structural.
|
|
388
|
+
const resultPath = join(dir, "result.json");
|
|
389
|
+
writeFileSync(configPath, JSON.stringify({
|
|
390
|
+
path: options.scanner.root,
|
|
391
|
+
entrypoint: options.scanner.entrypoint,
|
|
392
|
+
interval_seconds: options.intervalSeconds,
|
|
393
|
+
timeout_seconds: options.timeoutSeconds,
|
|
394
|
+
default_signal_validity_seconds: options.defaultSignalValiditySeconds,
|
|
395
|
+
wallet: options.wallet.address,
|
|
396
|
+
scanner_name: options.scanner.name,
|
|
397
|
+
inputs: options.inputs ?? {},
|
|
398
|
+
// A throwaway directory, so a validation run cannot disturb what a deployed strategy has
|
|
399
|
+
// stored — and so the tick sees the empty history a new strategy starts with.
|
|
400
|
+
state_path: join(dir, "state.json"),
|
|
401
|
+
state_history_max_count: options.stateHistoryMaxCount ?? 0,
|
|
402
|
+
result_path: resultPath,
|
|
403
|
+
signal_data_schema: options.signalDataSchema,
|
|
404
|
+
max_ticks: 1,
|
|
405
|
+
}), "utf8");
|
|
406
|
+
const python = options.pythonBin ?? process.env.PYTHON ?? "python3";
|
|
407
|
+
const existing = process.env.PYTHONPATH;
|
|
408
|
+
const pythonPath = existing ? `${scaffoldPackageDir()}:${existing}` : scaffoldPackageDir();
|
|
409
|
+
// Generous relative to the tick's own budget: the harness enforces that budget itself, and this
|
|
410
|
+
// only catches a process that stalls somewhere the budget cannot reach — a connection that never
|
|
411
|
+
// resolves, for instance. Killing earlier would report a timeout the runtime would not have.
|
|
412
|
+
const outerMs = (options.timeoutSeconds + 30) * 1000;
|
|
413
|
+
return await new Promise((done) => {
|
|
414
|
+
const child = spawn(python, ["-m", "scaffold.preflight", configPath], {
|
|
415
|
+
env: { ...process.env, PYTHONPATH: pythonPath },
|
|
416
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
417
|
+
});
|
|
418
|
+
let stdout = "";
|
|
419
|
+
let stderr = "";
|
|
420
|
+
let settled = false;
|
|
421
|
+
const finish = (outcome) => {
|
|
422
|
+
if (settled)
|
|
423
|
+
return;
|
|
424
|
+
settled = true;
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
done(outcome);
|
|
427
|
+
};
|
|
428
|
+
const timer = setTimeout(() => {
|
|
429
|
+
child.kill("SIGKILL");
|
|
430
|
+
finish({
|
|
431
|
+
ok: false,
|
|
432
|
+
reason: "outer_timeout",
|
|
433
|
+
detail: `the tick process did not exit within ${outerMs / 1000}s`,
|
|
434
|
+
});
|
|
435
|
+
}, outerMs);
|
|
436
|
+
child.stdout.on("data", (chunk) => (stdout += String(chunk)));
|
|
437
|
+
child.stderr.on("data", (chunk) => (stderr += String(chunk)));
|
|
438
|
+
child.on("error", (err) => finish({ ok: false, reason: "spawn_failed", detail: String(err) }));
|
|
439
|
+
child.on("close", () => {
|
|
440
|
+
let document;
|
|
441
|
+
try {
|
|
442
|
+
document = readFileSync(resultPath, "utf8");
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// No result file at all: the interpreter never got far enough to write one. That is the
|
|
446
|
+
// harness or the environment, which is what this reason means — and stdout/stderr is where
|
|
447
|
+
// the reason will be.
|
|
448
|
+
finish({
|
|
449
|
+
ok: false,
|
|
450
|
+
reason: "unreadable_output",
|
|
451
|
+
detail: stderr.slice(-2000) ||
|
|
452
|
+
stdout.slice(-2000) ||
|
|
453
|
+
"the tick process wrote no result document",
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const outcome = JSON.parse(document);
|
|
459
|
+
// Author output is evidence now, never the channel — carried so a finding can quote it.
|
|
460
|
+
finish({ ...outcome, stdout: outcome.stdout ?? stdout, stderr: outcome.stderr ?? stderr });
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
finish({
|
|
464
|
+
ok: false,
|
|
465
|
+
reason: "unreadable_output",
|
|
466
|
+
detail: `the result document could not be parsed: ${String(err).slice(0, 300)}`,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
//# sourceMappingURL=live-stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-stage.js","sourceRoot":"","sources":["../../src/validate/live-stage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,oFAAoF;AACpF,sFAAsF;AACtF,wFAAwF;AACxF,sFAAsF;AACtF,8EAA8E;AAC9E,oFAAoF;AACpF,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAsB,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AAgDpE,SAAS,WAAW,CAClB,IAAY,EACZ,OAAgB,EAChB,QAA6B,EAC7B,KAA8G;IAE9G,MAAM,KAAK,GACT,IAAI,KAAK,aAAa,CAAC,aAAa,IAAI,IAAI,KAAK,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;IACjG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;AAC/E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAmB;IACxD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,EAAE;YAAE,SAAS;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8FAA8F;AAC9F,SAAS,mBAAmB,CAAC,KAAmB;IAC9C,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0C,CAAC;IACpE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;QACrD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAC7D,IAAI,IAAI,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;;YACtB,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QACvB,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;SAC5D,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC;SAC3B,IAAI,EAAE,CAAC;AACZ,CAAC;AAkCD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,eAAmC,EACnC,eAAuB;IAEvB,OAAO,eAAe,IAAI,eAAe,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,OAAsB,EACtB,OAAoB,EACpB,UAAuB,EAAE;IAEzB,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,IAAI,OAAO,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;YACxC,OAAO;gBACL,WAAW,CAAC,aAAa,CAAC,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE;oBAC3D,IAAI,EAAE,+DAA+D;oBACrE,GAAG,EAAE,uIAAuI;oBAC5I,GAAG,EAAE,8HAA8H;oBACnI,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;iBACrC,CAAC;aACH,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YACvC,2FAA2F;YAC3F,yFAAyF;YACzF,2FAA2F;YAC3F,kBAAkB;YAClB,OAAO;gBACL,WAAW,CAAC,aAAa,CAAC,gBAAgB,EAAE,aAAa,EAAE,OAAO,EAAE;oBAClE,IAAI,EAAE,yBAAyB,OAAO,CAAC,IAAI,2CAA2C;oBACtF,GAAG,EAAE,4SAA4S;oBACjT,GAAG,EAAE,6IAA6I;oBAClJ,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;iBACrC,CAAC;aACH,CAAC;QACJ,CAAC;QAED,OAAO;YACL,WAAW,CAAC,aAAa,CAAC,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE;gBAC3D,IAAI,EAAE,8CAA8C,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE;gBACxG,GAAG,EAAE,6HAA6H;gBAClI,GAAG,EAAE,0GAA0G;gBAC/G,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;aAC7D,CAAC;SACH,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAErC,6FAA6F;IAC7F,mDAAmD;IACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IACjE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE;YACxD,IAAI,EAAE,uBAAuB,IAAI,CAAC,IAAI,yCAAyC;YAC/E,GAAG,EAAE,0RAA0R;YAC/R,GAAG,EAAE,+IAA+I,IAAI,CAAC,IAAI,mCAAmC;YAChM,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;SAC9B,CAAC,CACH,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAC3G,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,IAAI,KAAK,eAAe,EAAE,CAAC;QAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;QACjD,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE;YACxD,IAAI,EAAE,mDAAmD;YACzD,GAAG,EAAE,yMAAyM;YAC9M,GAAG,EAAE,iEAAiE;YACtE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS;YACjE,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE;SAC/C,CAAC,CACH,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;QACjD,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE;YAC/D,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE;YAC3D,GAAG,EAAE,sIAAsI;YAC3I,GAAG,EAAE,KAAK;gBACR,CAAC,CAAC,qBAAqB,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,GAAG;gBAClD,CAAC,CAAC,yEAAyE;YAC7E,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS;YACjE,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;SAC7D,CAAC,CACH,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,4EAA4E;IAC5E,iGAAiG;IACjG,0FAA0F;IAC1F,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IAExC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC;QAC7D,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClF,QAAQ,CAAC,IAAI,CACX,KAAK,KAAK,cAAc;YACtB,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE;gBAC3D,IAAI,EAAE,IAAI,IAAI,4CAA4C;gBAC1D,GAAG,EAAE,8IAA8I;gBACnJ,GAAG,EAAE,4EAA4E;gBACjF,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;aACzC,CAAC;YACJ,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE;gBAC1D,IAAI,EAAE,gCAAgC,IAAI,yBAAyB;gBACnE,GAAG,EAAE,0JAA0J;gBAC/J,GAAG,EAAE,yHAAyH;gBAC9H,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;aACzC,CAAC,CACP,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAEtD,IAAI,OAAO,KAAK,CAAC,IAAI,WAAW,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACvE,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE;YAC9D,IAAI,EAAE,4BAA4B,WAAW,4BAA4B;YACzE,GAAG,EAAE,2KAA2K;YAChL,GAAG,EAAE,mEAAmE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,IAAI,mBAAmB,EAAE;YACtI,QAAQ,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;SACjC,CAAC,CACH,CAAC;IACJ,CAAC;SAAM,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,4FAA4F;QAC5F,oFAAoF;QACpF,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;YAC9E,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE;gBACnE,IAAI,EAAE,UAAU,OAAO,kBAAkB,MAAM,CAAC,MAAM,OAAO,MAAM,CAAC,MAAM,gCAAgC;gBAC1G,GAAG,EAAE,wDAAwD,OAAO,iLAAiL;gBACrP,GAAG,EAAE,iBAAiB,OAAO,oBAAoB,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,mBAAmB,EAAE;gBAC1F,QAAQ,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;aAC9B,CAAC,CACH,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC1C,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE;YACnE,IAAI,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,MAAM,eAAe;YACxF,GAAG,EAAE,mHAAmH;YACxH,GAAG,EAAE,4CAA4C;YACjD,QAAQ,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;SACvD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,IAAI,OAAO,KAAK,CAAC,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC;QACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;QAE7C,+FAA+F;QAC/F,2FAA2F;QAC3F,gGAAgG;QAChG,4CAA4C;QAC5C,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;YACvD,IAAI,EACF,QAAQ,CAAC,MAAM,GAAG,CAAC;gBACjB,CAAC,CAAC,oDAAoD,eAAe,CAAC,QAAQ,CAAC,0CAA0C;gBACzH,CAAC,CAAC,6CAA6C;YACnD,GAAG,EAAE,yQAAyQ;YAC9Q,GAAG,EACD,QAAQ,CAAC,MAAM,GAAG,CAAC;gBACjB,CAAC,CAAC,sLAAsL,IAAI,CAAC,CAAC,CAAC,yBAAyB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACxP,CAAC,CAAC,IAAI;oBACJ,CAAC,CAAC,wBAAwB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,QAAQ,uGAAuG;oBAC3K,CAAC,CAAC,6DAA6D;YACrE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAChE,QAAQ,EAAE;gBACR,eAAe,EAAE,OAAO,CAAC,MAAM;gBAC/B,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9D;SACF,CAAC,CACH,CAAC;QAEF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,mBAAmB,EAAE,MAAM,EAAE,SAAS,EAAE;gBAChE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,6CAA6C;gBACpE,GAAG,EAAE,6JAA6J;gBAClK,GAAG,EAAE,8CAA8C;gBACnD,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE;aACtC,CAAC,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE;YACpE,IAAI,EAAE,iBAAiB,OAAO,8BAA8B;YAC5D,GAAG,EAAE,gKAAgK;YACrK,GAAG,EAAE,wEAAwE,cAAc,CAAC,KAAK,CAAC,EAAE;YACpG,QAAQ,EAAE,EAAE,KAAK,EAAE;SACpB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE;YAC9D,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,4DAA4D,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACxH,GAAG,EAAE,uLAAuL;YAC5L,GAAG,EAAE,2DAA2D,cAAc,CAAC,MAAM,CAAC,EAAE;YACxF,QAAQ,EAAE,EAAE,MAAM,EAAE;SACrB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,GAAG,EAAoB,CAAC;IACjD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAI,MAAM,CAAC,OAAqC,EAAE,IAAI,CAAC;QACjE,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE;YAC3D,IAAI,EAAE,gDAAgD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACzE,GAAG,EAAE,uKAAuK;YAC5K,GAAG,EAAE,WAAW,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,yDAAyD;YAC1F,QAAQ,EAAE,EAAE,MAAM,EAAE;SACrB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;QAClD,QAAQ,CAAC,IAAI,CACX,WAAW,CAAC,aAAa,CAAC,mBAAmB,EAAE,aAAa,EAAE,SAAS,EAAE;YACvE,IAAI,EAAE,GAAG,UAAU,CAAC,IAAI,cAAc,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,oBAAoB,UAAU,CAAC,OAAO,EAAE;YAChH,GAAG,EAAE,6MAA6M;YAClN,GAAG,EAAE,uBAAuB,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,sEAAsE;YACpI,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE;SACxD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,KAAmB;IACtC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiD,CAAC;IACxE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,sEAAsE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACzF,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QACxC,CAAC;aAAM,IAAI,2GAA2G,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACrI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,SAAS,iBAAiB,CAAC,IAAa;IACtC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACzD,OAAO,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC;SACnD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;SACzF,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,6EAA6E;AAC7E,SAAS,eAAe,CAAC,OAA0B;IACjD,MAAM,EAAE,GAAG,OAAO;SACf,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SACnE,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC;AACjE,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,YAAY,CACnB,MAA8B,EAC9B,KAAa;IAEb,MAAM,OAAO,GAAG,+CAA+C,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAErG,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,EAAE,eAAe,CAAC;QACxC,OAAO;YACL,IAAI,EAAE,oCAAoC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,6BAA6B;YAClH,GAAG,EAAE,wHAAwH;YAC7H,GAAG,EAAE,GAAG,OAAO,mEAAmE;YAClF,QAAQ,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;SACnF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,6CAA6C,MAAM,CAAC,eAAe,UAAU;QACnF,GAAG,EAAE,qEAAqE,MAAM,CAAC,iBAAiB,0FAA0F,MAAM,CAAC,eAAe,sIAAsI;QACxV,GAAG,EAAE,GAAG,OAAO,gIAAgI;QAC/I,QAAQ,EAAE;YACR,KAAK;YACL,uBAAuB,EAAE,MAAM,CAAC,eAAe;YAC/C,yBAAyB,EAAE,MAAM,CAAC,iBAAiB;YACnD,gBAAgB,EAAE,KAAK;SACxB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAmB;IACzC,MAAM,MAAM,GAAG,KAAK;SACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,aAAa,CAAC;SACtC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,aAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,4BAA4B,CAAC;AAC9E,CAAC;AAED,oCAAoC;AACpC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAoB;IAChD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC5C,4FAA4F;IAC5F,gGAAgG;IAChG,iGAAiG;IACjG,gEAAgE;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAE5C,aAAa,CACX,UAAU,EACV,IAAI,CAAC,SAAS,CAAC;QACb,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI;QAC1B,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU;QACtC,gBAAgB,EAAE,OAAO,CAAC,eAAe;QACzC,eAAe,EAAE,OAAO,CAAC,cAAc;QACvC,+BAA+B,EAAE,OAAO,CAAC,4BAA4B;QACrE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO;QAC9B,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI;QAClC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;QAC5B,yFAAyF;QACzF,8EAA8E;QAC9E,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC;QACnC,uBAAuB,EAAE,OAAO,CAAC,oBAAoB,IAAI,CAAC;QAC1D,WAAW,EAAE,UAAU;QACvB,kBAAkB,EAAE,OAAO,CAAC,gBAAgB;QAC5C,SAAS,EAAE,CAAC;KACb,CAAC,EACF,MAAM,CACP,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACxC,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,kBAAkB,EAAE,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAC;IAE3F,gGAAgG;IAChG,iGAAiG;IACjG,6FAA6F;IAC7F,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,cAAc,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IAErD,OAAO,MAAM,IAAI,OAAO,CAAc,CAAC,IAAI,EAAE,EAAE;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,oBAAoB,EAAE,UAAU,CAAC,EAAE;YACpE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE;YAC/C,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,MAAM,GAAG,CAAC,OAAoB,EAAE,EAAE;YACtC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,CAAC;QAChB,CAAC,CAAC;QAEF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,MAAM,CAAC;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,eAAe;gBACvB,MAAM,EAAE,wCAAwC,OAAO,GAAG,IAAI,GAAG;aAClE,CAAC,CAAC;QACL,CAAC,EAAE,OAAO,CAAC,CAAC;QAEZ,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9D,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/F,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,IAAI,QAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,QAAQ,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,wFAAwF;gBACxF,2FAA2F;gBAC3F,sBAAsB;gBACtB,MAAM,CAAC;oBACL,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,mBAAmB;oBAC3B,MAAM,EACJ,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;wBACnB,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;wBACnB,2CAA2C;iBAC9C,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAgB,CAAC;gBACpD,wFAAwF;gBACxF,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;YAC7F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC;oBACL,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,mBAAmB;oBAC3B,MAAM,EAAE,4CAA4C,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;iBAChF,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The proof of a passing run, and the function that checks it.
|
|
3
|
+
*
|
|
4
|
+
* A proof is a claim about *content*: this exact recipe and these exact scanner files passed a
|
|
5
|
+
* full, unscoped, live validation. It is content-addressed — hash the authored bytes, nothing
|
|
6
|
+
* else — so a copied package is still proven (same content, same claim) and a one-character edit
|
|
7
|
+
* is not. There is no expiry: content that has not changed does not become less proven by sitting
|
|
8
|
+
* still, and a TTL would only train people to re-run validation ritually rather than when it
|
|
9
|
+
* means something.
|
|
10
|
+
*
|
|
11
|
+
* `verifyProof` is deliberately pure observation: it reports, it never enforces and never throws.
|
|
12
|
+
* The deploy verb owns the decision; this module owns the fact. It also never parses the recipe —
|
|
13
|
+
* the record carries the file list and the scanner roots, so verification is a re-hash and a
|
|
14
|
+
* comparison. A recipe that no longer parses necessarily hashes differently than the one that
|
|
15
|
+
* passed, which is the answer, not an error.
|
|
16
|
+
*
|
|
17
|
+
* Per-file hashes are stored so a refusal can say *what* changed. "The package differs" sends an
|
|
18
|
+
* agent diffing blindly; "scan.py changed, scoring.py is new" is an answer.
|
|
19
|
+
*/
|
|
20
|
+
import type { Finding } from "./types.js";
|
|
21
|
+
import type { ResolvedWallet } from "./wallet.js";
|
|
22
|
+
/** Lives at the package root, beside the recipe. Excluded from its own fingerprint. */
|
|
23
|
+
export declare const PROOF_FILENAME = ".senpi-proof.json";
|
|
24
|
+
export interface ScannerObservation {
|
|
25
|
+
scanner: string;
|
|
26
|
+
reads_ok: number;
|
|
27
|
+
reads_failed: number;
|
|
28
|
+
signals_emitted: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ProofRecord {
|
|
31
|
+
record_version: 1;
|
|
32
|
+
/** Hash over the sorted per-file map — the package's identity. */
|
|
33
|
+
fingerprint: string;
|
|
34
|
+
/** Relative path → sha256, so a mismatch can name the file. */
|
|
35
|
+
files: Record<string, string>;
|
|
36
|
+
/** What was hashed, so verification can re-walk without parsing the recipe. */
|
|
37
|
+
recipe: string;
|
|
38
|
+
scanner_roots: string[];
|
|
39
|
+
runtime_version: string;
|
|
40
|
+
verdict: "PASS";
|
|
41
|
+
wallet?: {
|
|
42
|
+
address: string;
|
|
43
|
+
source: string;
|
|
44
|
+
book: string;
|
|
45
|
+
};
|
|
46
|
+
observations: ScannerObservation[];
|
|
47
|
+
created_at: string;
|
|
48
|
+
}
|
|
49
|
+
export type VerifyReason = "no_proof" | "content_changed" | "runtime_version_changed";
|
|
50
|
+
export interface VerifyResult {
|
|
51
|
+
ok: boolean;
|
|
52
|
+
reason?: VerifyReason;
|
|
53
|
+
/** Relative paths that differ from the record: edited, added, or removed. */
|
|
54
|
+
changedFiles?: string[];
|
|
55
|
+
/** Present exactly when `ok` is false — ready for the deploy verb to surface unmodified. */
|
|
56
|
+
finding?: Finding;
|
|
57
|
+
record?: ProofRecord;
|
|
58
|
+
}
|
|
59
|
+
/** Hash the authored content: the recipe as written, every scanner file verbatim. */
|
|
60
|
+
export declare function fingerprintPackage(recipePath: string, scannerRoots: string[]): {
|
|
61
|
+
fingerprint: string;
|
|
62
|
+
files: Record<string, string>;
|
|
63
|
+
};
|
|
64
|
+
export interface WriteProofInputs {
|
|
65
|
+
recipePath: string;
|
|
66
|
+
scannerRoots: string[];
|
|
67
|
+
wallet?: ResolvedWallet;
|
|
68
|
+
observations: ScannerObservation[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Record a passing run. Atomic — a crash mid-write must not leave a half-proof that
|
|
72
|
+
* `verifyProof` would read as corrupt-and-therefore-absent *after* a genuine pass.
|
|
73
|
+
*
|
|
74
|
+
* Returns the finding to attach when the write fails: the run still passed — the package is no
|
|
75
|
+
* less runnable because a disk was full — but the deploy gate will not find a proof, and whoever
|
|
76
|
+
* operates the environment is the one who can do something about that. Post-verdict by design:
|
|
77
|
+
* it must not flip a pass, even under --strict.
|
|
78
|
+
*/
|
|
79
|
+
export declare function writeProof(inputs: WriteProofInputs): {
|
|
80
|
+
recorded: boolean;
|
|
81
|
+
finding?: Finding;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Which instances a multi-instance package expects to be proven.
|
|
85
|
+
*
|
|
86
|
+
* A proof lives beside the recipe it is a claim about, so a package with `swing/runtime.yaml` and
|
|
87
|
+
* `scalp/runtime.yaml` has two proofs and none at its root. The caller names the instances because
|
|
88
|
+
* only the caller knows them — they are declared in `strategy.yaml`, which is the deploy manifest
|
|
89
|
+
* and deliberately not something the runtime reads. Inferring them by looking for proofs on disk
|
|
90
|
+
* would pass a package whose third instance was never validated at all.
|
|
91
|
+
*/
|
|
92
|
+
export interface VerifyOptions {
|
|
93
|
+
/** Instance directory names, relative to `packageDir`. Omit for a flat single-recipe package. */
|
|
94
|
+
instances?: string[];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Is this package's content the content that passed?
|
|
98
|
+
*
|
|
99
|
+
* Pure observation for the deploy verb to act on. Every failure reason has the same remedy —
|
|
100
|
+
* `senpi validate <packageDir>` — stated on the finding, so the caller can surface it unmodified
|
|
101
|
+
* and an agent can close the loop without knowing anything about proofs.
|
|
102
|
+
*/
|
|
103
|
+
export declare function verifyProof(packageDir: string, options?: VerifyOptions): VerifyResult;
|
|
104
|
+
//# sourceMappingURL=proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../src/validate/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,uFAAuF;AACvF,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAUlD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,CAAC,CAAC;IAClB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,iBAAiB,GAAG,yBAAyB,CAAC;AAEtF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA0CD,qFAAqF;AACrF,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EAAE,GACrB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAcxD;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,YAAY,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAW7F;AAoED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CAQzF"}
|