@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,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static depth: read a recipe and report what is wrong with it.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the loading path the runtime itself uses — same parser, same schema, same hint table, same
|
|
5
|
+
* semantic asserts — but collects findings instead of throwing at the first one. The throwing loader
|
|
6
|
+
* is untouched and remains the runtime's entry point; this is a second reading of the same rules for
|
|
7
|
+
* a different audience.
|
|
8
|
+
*
|
|
9
|
+
* ## Ordering: fail fast at dependency boundaries, report together within one
|
|
10
|
+
*
|
|
11
|
+
* A config that will not parse cannot be schema-checked, and one that fails the schema cannot be
|
|
12
|
+
* semantically checked — running those anyway produces findings derived from a shape that does not
|
|
13
|
+
* exist. So each boundary stops the pass.
|
|
14
|
+
*
|
|
15
|
+
* Within a boundary the opposite applies: schema issues are independent of one another, as are the
|
|
16
|
+
* semantic checks, so all of them are reported at once. An agent fixing one thing per round trip is
|
|
17
|
+
* an agent making N round trips for information we already had.
|
|
18
|
+
*/
|
|
19
|
+
import { readFile } from "node:fs/promises";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
22
|
+
import { parse } from "yaml";
|
|
23
|
+
import { DslConfigValidationError, parseDslPluginConfig } from "../dsl/config/index.js";
|
|
24
|
+
import { clarifyIssue, findDuplicateNames } from "../runtime/load-runtime-config.js";
|
|
25
|
+
import { resolveEnvInObject } from "../runtime/env-resolve.js";
|
|
26
|
+
import { mapRuntimeExitToDslConfig } from "../runtime/map-exit-config.js";
|
|
27
|
+
import { runtimeConfigSchema } from "../runtime/runtime-schema.js";
|
|
28
|
+
import { assertActionPromptPlaceholdersResolvable } from "../runtime/validate-action-prompts.js";
|
|
29
|
+
import { VALIDATE_CODE } from "./codes.js";
|
|
30
|
+
import { findLlmActionsWithoutModel, findMarginPctFraction, findMissingEntrypoints, findUngatedStrategy, findUnsizedStrategy, } from "./recipe-checks.js";
|
|
31
|
+
function staticFinding(code, fixKind, parts) {
|
|
32
|
+
return { code, severity: "error", stage: "static", actor: "agent", fix_kind: fixKind, ...parts };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Convert a thrown validation error into a finding.
|
|
36
|
+
*
|
|
37
|
+
* Used for the two checks that only exist in throwing form. Reusing them as-is keeps one
|
|
38
|
+
* implementation of each rule; re-deriving them here to get a structured result would create a
|
|
39
|
+
* second copy to keep in step, which is the failure this whole module is trying to avoid.
|
|
40
|
+
*/
|
|
41
|
+
function fromThrown(code, why, fix, where, run) {
|
|
42
|
+
try {
|
|
43
|
+
run();
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return [
|
|
48
|
+
staticFinding(code, "edit", {
|
|
49
|
+
what: err instanceof Error ? err.message : String(err),
|
|
50
|
+
why,
|
|
51
|
+
fix,
|
|
52
|
+
where,
|
|
53
|
+
...(err instanceof DslConfigValidationError ? { evidence: { issues: err.issues } } : {}),
|
|
54
|
+
}),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Findings for every schema violation, sharing the runtime's own hint table. */
|
|
59
|
+
function schemaFindings(issues) {
|
|
60
|
+
return issues.map((issue) => {
|
|
61
|
+
const path = issue.path;
|
|
62
|
+
const pathStr = path.length > 0 ? path.join(".") : "root";
|
|
63
|
+
const { message, hint } = clarifyIssue({
|
|
64
|
+
code: issue.code,
|
|
65
|
+
path: path,
|
|
66
|
+
message: issue.message,
|
|
67
|
+
});
|
|
68
|
+
return staticFinding(VALIDATE_CODE.schema, "edit", {
|
|
69
|
+
what: `${pathStr}: ${message}`,
|
|
70
|
+
// The hint table explains what the field is for; without one, the message alone is the reason.
|
|
71
|
+
why: hint ?? "The recipe does not match the shape the runtime requires, so it cannot be loaded.",
|
|
72
|
+
fix: hint ?? `Correct \`${pathStr}\` to match the schema.`,
|
|
73
|
+
where: { yaml_path: pathStr },
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function duplicateFindings(code, kind, items) {
|
|
78
|
+
const duplicates = findDuplicateNames(items);
|
|
79
|
+
return [...duplicates.entries()].map(([name, indices]) => staticFinding(code, "edit", {
|
|
80
|
+
what: `${kind === "scanner" ? "Scanner" : "Action"} name '${name}' is used ${indices.length} times, at ${indices.map((i) => `${kind}s.${i}`).join(" and ")}`,
|
|
81
|
+
why: `${kind === "scanner" ? "Scanner" : "Action"} names key per-${kind} state across the runtime, so duplicates silently share state rather than failing.`,
|
|
82
|
+
fix: `Rename all but one of them. The name is what other parts of the recipe reference, so pick the one already referred to elsewhere and change the others.`,
|
|
83
|
+
where: { yaml_path: `${kind}s.${indices[indices.length - 1]}` },
|
|
84
|
+
evidence: { name, indices },
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* `${VAR}` references with no value, and no default, in this environment.
|
|
89
|
+
*
|
|
90
|
+
* Matches the pattern the runtime's own resolver uses, so the two agree on what a placeholder is.
|
|
91
|
+
*/
|
|
92
|
+
const UNSET_PLACEHOLDER = /\$\{([^}:]+)(?::-([^}]*))?\}/g;
|
|
93
|
+
/** A value that is obviously not real, but is a non-empty string so shape checks still apply. */
|
|
94
|
+
const VALIDATION_PLACEHOLDER = "__unset_at_validation__";
|
|
95
|
+
/**
|
|
96
|
+
* Give unset placeholders a stand-in value before the schema sees them.
|
|
97
|
+
*
|
|
98
|
+
* A recipe is a template. `wallet: "${SPIDER_SWING_WALLET}"` is the correct authoring state — the
|
|
99
|
+
* value arrives at deployment. But the runtime's resolver substitutes an unset variable with the
|
|
100
|
+
* empty string, which is right at boot (an empty wallet should fail loudly) and wrong here: it turns
|
|
101
|
+
* every unfinished recipe into a schema error about a field the author was never meant to have
|
|
102
|
+
* filled in yet. Validation would reject exactly the recipes it exists to help.
|
|
103
|
+
*
|
|
104
|
+
* The deploy preflight already solved this the same way, rendering with a stand-in wallet and model
|
|
105
|
+
* before it validates. This is that idea generalised: any placeholder with no value becomes a
|
|
106
|
+
* visibly-fake non-empty string, so the recipe is checked for shape without pretending a value
|
|
107
|
+
* exists. A placeholder that *does* resolve is left to the normal resolver, and a genuinely empty
|
|
108
|
+
* literal in the recipe still fails as it should.
|
|
109
|
+
*/
|
|
110
|
+
function fillUnsetPlaceholders(value) {
|
|
111
|
+
if (typeof value === "string") {
|
|
112
|
+
return value.replace(UNSET_PLACEHOLDER, (whole, name, fallback) => {
|
|
113
|
+
if (fallback !== undefined)
|
|
114
|
+
return whole; // has a default; the resolver will use it
|
|
115
|
+
const actual = process.env[name];
|
|
116
|
+
return actual !== undefined && actual !== "" ? whole : VALIDATION_PLACEHOLDER;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(value))
|
|
120
|
+
return value.map(fillUnsetPlaceholders);
|
|
121
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
122
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, fillUnsetPlaceholders(v)]));
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
/** Resolve a scanner's declared path against the recipe's directory, or say why it cannot be. */
|
|
127
|
+
function scannerRootResolver(baseDir) {
|
|
128
|
+
return (scannerPath) => {
|
|
129
|
+
if (isAbsolute(scannerPath))
|
|
130
|
+
return { ok: true, root: scannerPath };
|
|
131
|
+
if (baseDir === undefined) {
|
|
132
|
+
return { ok: false, reason: "The recipe was supplied without a directory, so a relative path has nothing to resolve against." };
|
|
133
|
+
}
|
|
134
|
+
return { ok: true, root: resolve(baseDir, scannerPath) };
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Read a recipe and report every problem the static depth can see.
|
|
139
|
+
*
|
|
140
|
+
* Returns the parsed config alongside the findings when it got that far, so a caller running deeper
|
|
141
|
+
* depths does not parse a second time.
|
|
142
|
+
*/
|
|
143
|
+
export async function collectRecipeFindings(source) {
|
|
144
|
+
// ── parse ────────────────────────────────────────────────────────────────
|
|
145
|
+
let raw;
|
|
146
|
+
try {
|
|
147
|
+
const text = source.content ?? (await readFile(source.path, "utf-8"));
|
|
148
|
+
raw = parse(text);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
return {
|
|
152
|
+
findings: [
|
|
153
|
+
staticFinding(VALIDATE_CODE.yamlSyntax, "edit", {
|
|
154
|
+
what: err instanceof Error ? err.message : String(err),
|
|
155
|
+
why: "Nothing further can be checked until the recipe parses.",
|
|
156
|
+
fix: "Check indentation, colons and quoting at the position named above.",
|
|
157
|
+
where: source.path ? { file: source.path } : undefined,
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (raw === null || raw === undefined || typeof raw !== "object") {
|
|
163
|
+
return {
|
|
164
|
+
findings: [
|
|
165
|
+
staticFinding(VALIDATE_CODE.yamlSyntax, "edit", {
|
|
166
|
+
what: "The recipe does not define an object",
|
|
167
|
+
why: "A recipe is a mapping of top-level keys such as `name` and `strategy`.",
|
|
168
|
+
fix: "Define at least `name` and a `strategy` with a `wallet`.",
|
|
169
|
+
where: source.path ? { file: source.path } : undefined,
|
|
170
|
+
}),
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const resolved = resolveEnvInObject(fillUnsetPlaceholders(raw));
|
|
175
|
+
if ("strategies" in resolved) {
|
|
176
|
+
return {
|
|
177
|
+
findings: [
|
|
178
|
+
staticFinding(VALIDATE_CODE.schema, "edit", {
|
|
179
|
+
what: "The recipe uses `strategies` (plural), which is no longer supported",
|
|
180
|
+
why: "A runtime drives exactly one strategy, so the key is `strategy` with a single wallet.",
|
|
181
|
+
fix: "Rename `strategies` to `strategy` and keep a single entry with its `wallet`.",
|
|
182
|
+
where: { yaml_path: "strategies" },
|
|
183
|
+
}),
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// ── schema ───────────────────────────────────────────────────────────────
|
|
188
|
+
const parsed = runtimeConfigSchema.safeParse(resolved);
|
|
189
|
+
if (!parsed.success) {
|
|
190
|
+
return { findings: schemaFindings(parsed.error.issues) };
|
|
191
|
+
}
|
|
192
|
+
const config = parsed.data;
|
|
193
|
+
// ── semantics ────────────────────────────────────────────────────────────
|
|
194
|
+
// Past this point every check reads a config that is known well-formed, so they are independent
|
|
195
|
+
// of one another and all run.
|
|
196
|
+
const findings = [
|
|
197
|
+
...duplicateFindings(VALIDATE_CODE.dupScannerName, "scanner", config.scanners ?? []),
|
|
198
|
+
...duplicateFindings(VALIDATE_CODE.dupActionName, "action", config.actions ?? []),
|
|
199
|
+
...fromThrown(VALIDATE_CODE.promptPlaceholder, "A placeholder with nothing to fill it renders literally into the prompt, so the decision step reasons over the placeholder text instead of the value.", "Reference a context entry the action declares, or remove the placeholder.", undefined, () => assertActionPromptPlaceholdersResolvable(config)),
|
|
200
|
+
...findUnsizedStrategy(config),
|
|
201
|
+
...findUngatedStrategy(config),
|
|
202
|
+
...findMarginPctFraction(config),
|
|
203
|
+
...findLlmActionsWithoutModel(config),
|
|
204
|
+
...findMissingEntrypoints(config, scannerRootResolver(source.baseDir), existsSync, join),
|
|
205
|
+
];
|
|
206
|
+
const exit = config.exit;
|
|
207
|
+
const preset = exit && typeof exit === "object" ? exit.dsl_preset : undefined;
|
|
208
|
+
if (preset !== null && preset !== undefined && typeof preset === "object" && !Array.isArray(preset)) {
|
|
209
|
+
findings.push(...fromThrown(VALIDATE_CODE.exitPreset, "The exit block is what closes a position. An invalid preset means the runtime cannot build the exit engine, so positions would open with nothing managing them.", "Copy a known-good preset and change one field at a time.", { yaml_path: "exit.dsl_preset" }, () => {
|
|
210
|
+
parseDslPluginConfig(mapRuntimeExitToDslConfig(exit));
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
return { findings, config };
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe.js","sourceRoot":"","sources":["../../src/validate/recipe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering a result, for the two audiences that read it.
|
|
3
|
+
*
|
|
4
|
+
* ## Why the streams are split so strictly
|
|
5
|
+
*
|
|
6
|
+
* Under `--json`, standard output carries **exactly one JSON document and nothing else**. Everything
|
|
7
|
+
* intended for a human — progress, findings, the verdict — goes to standard error.
|
|
8
|
+
*
|
|
9
|
+
* This is not tidiness. The review that produced this work found a case where an agent retried a
|
|
10
|
+
* failing operation eight times because every error it captured was three hundred characters of
|
|
11
|
+
* unrelated banner; the real failure is still unknown. A verdict an agent cannot parse is not a
|
|
12
|
+
* verdict. So the machine channel carries one parseable thing, and anything that might contaminate
|
|
13
|
+
* it goes elsewhere by construction rather than by care.
|
|
14
|
+
*/
|
|
15
|
+
import type { Finding } from "./types.js";
|
|
16
|
+
import type { ValidateResult } from "./run.js";
|
|
17
|
+
/** The machine channel: one document, nothing else. */
|
|
18
|
+
export declare function renderJson(result: ValidateResult): string;
|
|
19
|
+
/**
|
|
20
|
+
* One finding, for a human.
|
|
21
|
+
*
|
|
22
|
+
* Order is what / why / fix, because that is the order a reader needs them: what happened, whether
|
|
23
|
+
* it matters, what to do. Evidence is deliberately absent — it is often large and is carried on the
|
|
24
|
+
* JSON for whoever is debugging rather than skimming.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderFinding(finding: Finding): string;
|
|
27
|
+
/**
|
|
28
|
+
* The human channel.
|
|
29
|
+
*
|
|
30
|
+
* A passing run states what it proved **and what it did not** — a green line that lets a reader
|
|
31
|
+
* infer more than was checked recreates, in a new place, the reporting problem this feature exists
|
|
32
|
+
* to fix.
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderHuman(result: ValidateResult): string;
|
|
35
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/validate/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,uDAAuD;AACvD,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEzD;AAWD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAYtD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAiC1D"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering a result, for the two audiences that read it.
|
|
3
|
+
*
|
|
4
|
+
* ## Why the streams are split so strictly
|
|
5
|
+
*
|
|
6
|
+
* Under `--json`, standard output carries **exactly one JSON document and nothing else**. Everything
|
|
7
|
+
* intended for a human — progress, findings, the verdict — goes to standard error.
|
|
8
|
+
*
|
|
9
|
+
* This is not tidiness. The review that produced this work found a case where an agent retried a
|
|
10
|
+
* failing operation eight times because every error it captured was three hundred characters of
|
|
11
|
+
* unrelated banner; the real failure is still unknown. A verdict an agent cannot parse is not a
|
|
12
|
+
* verdict. So the machine channel carries one parseable thing, and anything that might contaminate
|
|
13
|
+
* it goes elsewhere by construction rather than by care.
|
|
14
|
+
*/
|
|
15
|
+
/** The machine channel: one document, nothing else. */
|
|
16
|
+
export function renderJson(result) {
|
|
17
|
+
return JSON.stringify(result, null, 2);
|
|
18
|
+
}
|
|
19
|
+
const SEVERITY_MARK = { error: "✗", warning: "⚠" };
|
|
20
|
+
function indent(text, prefix) {
|
|
21
|
+
return text
|
|
22
|
+
.split("\n")
|
|
23
|
+
.map((line) => `${prefix}${line}`)
|
|
24
|
+
.join("\n");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One finding, for a human.
|
|
28
|
+
*
|
|
29
|
+
* Order is what / why / fix, because that is the order a reader needs them: what happened, whether
|
|
30
|
+
* it matters, what to do. Evidence is deliberately absent — it is often large and is carried on the
|
|
31
|
+
* JSON for whoever is debugging rather than skimming.
|
|
32
|
+
*/
|
|
33
|
+
export function renderFinding(finding) {
|
|
34
|
+
const mark = SEVERITY_MARK[finding.severity] ?? "•";
|
|
35
|
+
const where = finding.where?.file
|
|
36
|
+
? `${finding.where.file}${finding.where.line !== undefined ? `:${finding.where.line}` : ""}`
|
|
37
|
+
: finding.where?.yaml_path;
|
|
38
|
+
const lines = [` ${mark} ${finding.code}${where ? ` ${where}` : ""}`, ""];
|
|
39
|
+
lines.push(indent(finding.what, " "));
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push(indent(`why: ${finding.why}`, " "));
|
|
42
|
+
lines.push(indent(`fix: ${finding.fix}`, " "));
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The human channel.
|
|
47
|
+
*
|
|
48
|
+
* A passing run states what it proved **and what it did not** — a green line that lets a reader
|
|
49
|
+
* infer more than was checked recreates, in a new place, the reporting problem this feature exists
|
|
50
|
+
* to fix.
|
|
51
|
+
*/
|
|
52
|
+
export function renderHuman(result) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
for (const stage of result.stages_run) {
|
|
55
|
+
const stageFindings = result.findings.filter((f) => f.stage === stage);
|
|
56
|
+
const failed = stageFindings.some((f) => f.severity === "error");
|
|
57
|
+
lines.push(` ${failed ? "✗" : "✓"} ${stage}`);
|
|
58
|
+
}
|
|
59
|
+
if (result.findings.length > 0) {
|
|
60
|
+
lines.push("");
|
|
61
|
+
for (const finding of result.findings) {
|
|
62
|
+
lines.push(renderFinding(finding), "");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const verdictLine = result.verdict === "PASS"
|
|
66
|
+
// proof_eligible says a proof was WARRANTED; proof_recorded says one LANDED. Keying on the
|
|
67
|
+
// first prints "proof recorded" directly above the finding reporting that it was not.
|
|
68
|
+
? ` PASS${result.proof_recorded ? " · proof recorded" : ""}`
|
|
69
|
+
: ` ${result.verdict} (exit ${result.exit_code})`;
|
|
70
|
+
lines.push(verdictLine);
|
|
71
|
+
const basis = result.verdict_basis;
|
|
72
|
+
if (result.verdict === "PASS" && basis.proves) {
|
|
73
|
+
lines.push(` proves: ${basis.proves}`);
|
|
74
|
+
for (const limit of basis.does_not_prove ?? []) {
|
|
75
|
+
lines.push(` does not prove: ${limit}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/validate/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
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 { type Depth, type Finding } from "./types.js";
|
|
9
|
+
export type Verdict = "PASS" | "FAIL" | "UNPROVEN";
|
|
10
|
+
/** 0 pass · 1 something is wrong · 2 ran but proved nothing · 3 the request itself was malformed. */
|
|
11
|
+
export type ExitCode = 0 | 1 | 2 | 3;
|
|
12
|
+
export interface ValidateOptions {
|
|
13
|
+
/** Positional target. Omitted means the working directory. */
|
|
14
|
+
target?: string;
|
|
15
|
+
/** Recipe text supplied instead of a path. */
|
|
16
|
+
content?: string;
|
|
17
|
+
/** Directory relative scanner paths resolve against, when content is supplied. */
|
|
18
|
+
dir?: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
depth: Depth;
|
|
21
|
+
/** Restrict execution and reporting to one scanner. Never produces a proof. */
|
|
22
|
+
scanner?: string;
|
|
23
|
+
/** Account identity for the live depth. */
|
|
24
|
+
wallet?: string;
|
|
25
|
+
/** Per-scanner tick budget, seconds. */
|
|
26
|
+
timeoutSeconds?: number;
|
|
27
|
+
/** Treat warnings as failures. */
|
|
28
|
+
strict?: boolean;
|
|
29
|
+
/** Suppress proof recording even on a complete passing run. */
|
|
30
|
+
noAttest?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface VerdictBasis {
|
|
33
|
+
/** The rule that decided it, rendered from the table rather than written per outcome. */
|
|
34
|
+
rule: string;
|
|
35
|
+
observed: Record<string, unknown>;
|
|
36
|
+
/** What a passing run at this depth does and does not establish. */
|
|
37
|
+
proves?: string;
|
|
38
|
+
does_not_prove?: string[];
|
|
39
|
+
why_not_pass?: string;
|
|
40
|
+
what_would_change_it?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ValidateResult {
|
|
43
|
+
verdict: Verdict;
|
|
44
|
+
exit_code: ExitCode;
|
|
45
|
+
target: {
|
|
46
|
+
kind?: string;
|
|
47
|
+
recipe?: string;
|
|
48
|
+
scanner_roots?: string[];
|
|
49
|
+
};
|
|
50
|
+
stages_run: Depth[];
|
|
51
|
+
verdict_basis: VerdictBasis;
|
|
52
|
+
findings: Finding[];
|
|
53
|
+
/** True only when this run was complete enough to be worth recording as a proof. */
|
|
54
|
+
proof_eligible: boolean;
|
|
55
|
+
/** Present only on eligible runs: whether the record actually landed on disk. */
|
|
56
|
+
proof_recorded?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export declare function isImplemented(depth: Depth): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Validate, and report what it means.
|
|
61
|
+
*
|
|
62
|
+
* Stages short-circuit: a depth that produced an error stops the ones after it, because they would
|
|
63
|
+
* be reading a shape that was never established.
|
|
64
|
+
*/
|
|
65
|
+
export declare function runValidation(options: ValidateOptions): Promise<ValidateResult>;
|
|
66
|
+
/**
|
|
67
|
+
* The verdict, from the findings alone.
|
|
68
|
+
*
|
|
69
|
+
* Three outcomes rather than two. A run that executed cleanly and established nothing is neither a
|
|
70
|
+
* pass nor a failure: called a failure it sends someone hunting a defect that may not exist, called
|
|
71
|
+
* a pass it is precisely the hole this command exists to close. It gets its own verdict and its own
|
|
72
|
+
* exit code so a caller can tell the difference without parsing anything.
|
|
73
|
+
*
|
|
74
|
+
* Pure, and exported, because this is the rule the whole command reduces to.
|
|
75
|
+
*/
|
|
76
|
+
export declare function decideVerdict(findings: Finding[], options?: {
|
|
77
|
+
strict?: boolean;
|
|
78
|
+
}): {
|
|
79
|
+
verdict: Verdict;
|
|
80
|
+
exit_code: ExitCode;
|
|
81
|
+
errors: number;
|
|
82
|
+
warnings: number;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* How long one tick may take.
|
|
86
|
+
*
|
|
87
|
+
* Never derived from the cadence. A strategy that ticks every six hours is not a strategy whose
|
|
88
|
+
* tick may take six hours — the cadence is how long the runtime waits between ticks, and inheriting
|
|
89
|
+
* it would make `senpi validate` hang for a working scanner while looking like it was doing
|
|
90
|
+
* something. What the recipe declares wins; failing that, a bound that keeps the command
|
|
91
|
+
* answerable.
|
|
92
|
+
*/
|
|
93
|
+
export declare function tickBudgetSeconds(declared: Record<string, unknown> | undefined, override?: number): number;
|
|
94
|
+
/**
|
|
95
|
+
* What the environment is missing before a tick could run at all.
|
|
96
|
+
*
|
|
97
|
+
* Named rather than counted: "credentials are missing" leaves someone guessing which, and this is
|
|
98
|
+
* the one finding whose fix belongs to whoever set the environment up rather than to the author.
|
|
99
|
+
*/
|
|
100
|
+
export declare function missingCredentials(env?: NodeJS.ProcessEnv): string[];
|
|
101
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/validate/run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,OAAO,EAAkC,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEnD,qGAAqG;AACrG,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAErC,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,QAAQ,CAAC;IACpB,MAAM,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF,UAAU,EAAE,KAAK,EAAE,CAAC;IACpB,aAAa,EAAE,YAAY,CAAC;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,oFAAoF;IACpF,cAAc,EAAE,OAAO,CAAC;IACxB,iFAAiF;IACjF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAwDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAEnD;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAOrF;AA8RD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAuB7E;AAiGD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC7C,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,EAAE,CAQjF"}
|