assertledger 1.0.0
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/CONTRIBUTING.md +31 -0
- package/LICENSE +21 -0
- package/README.fr.md +236 -0
- package/README.md +224 -0
- package/SECURITY.md +51 -0
- package/benchmarks/agentic-profile/README.md +15 -0
- package/benchmarks/agentic-profile/public/README.md +5 -0
- package/benchmarks/self-hosted-core/README.md +113 -0
- package/benchmarks/self-hosted-core/adapter.mjs +293 -0
- package/benchmarks/self-hosted-core/builder.ts +193 -0
- package/benchmarks/self-hosted-core/campaign.ts +233 -0
- package/benchmarks/self-hosted-core/existing-tests-builder.ts +217 -0
- package/benchmarks/self-hosted-core/existing-tests.ts +146 -0
- package/benchmarks/self-hosted-core/liveness.test.mjs +8 -0
- package/conformance/v1/bundle.json +104 -0
- package/conformance/v1/expected/canonical-order-a.json +4 -0
- package/conformance/v1/expected/canonical-order-b.json +4 -0
- package/conformance/v1/expected/create-benchmark-v1-measured.json +575 -0
- package/conformance/v1/expected/create-profile-v1-qualified.json +280 -0
- package/conformance/v1/expected/decide-collection-failure-non-kill.json +192 -0
- package/conformance/v1/expected/decide-compile-failure-non-kill.json +192 -0
- package/conformance/v1/expected/decide-infra-error-non-kill.json +192 -0
- package/conformance/v1/expected/decide-no-test-discovered-non-kill.json +192 -0
- package/conformance/v1/expected/decide-process-crash-non-kill.json +192 -0
- package/conformance/v1/expected/decide-timeout-non-kill.json +192 -0
- package/conformance/v1/expected/decide-verified.json +192 -0
- package/conformance/v1/expected/replay-benchmark-v1-resealed-summary-forgery.json +12 -0
- package/conformance/v1/expected/replay-evidence-raw-tamper.json +6 -0
- package/conformance/v1/expected/replay-evidence-resealed-semantic-forgery.json +6 -0
- package/conformance/v1/inputs/canonical-order-a.json +8 -0
- package/conformance/v1/inputs/canonical-order-b.json +8 -0
- package/conformance/v1/inputs/create-benchmark-v1-measured.json +459 -0
- package/conformance/v1/inputs/create-profile-v1-qualified.json +228 -0
- package/conformance/v1/inputs/decide-collection-failure-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-compile-failure-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-infra-error-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-no-test-discovered-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-process-crash-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-timeout-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-verified.json +143 -0
- package/conformance/v1/inputs/replay-benchmark-v1-resealed-summary-forgery.json +575 -0
- package/conformance/v1/inputs/replay-evidence-raw-tamper.json +201 -0
- package/conformance/v1/inputs/replay-evidence-resealed-semantic-forgery.json +192 -0
- package/conformance/v1/schemas/expected-digests.json +175 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +951 -0
- package/dist/cli.js.map +1 -0
- package/dist/contracts/diagnostics.d.ts +18 -0
- package/dist/contracts/diagnostics.d.ts.map +1 -0
- package/dist/contracts/diagnostics.js +13 -0
- package/dist/contracts/diagnostics.js.map +1 -0
- package/dist/contracts/index.d.ts +3908 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +2569 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/runtime-doctor.d.ts +107 -0
- package/dist/contracts/runtime-doctor.d.ts.map +1 -0
- package/dist/contracts/runtime-doctor.js +91 -0
- package/dist/contracts/runtime-doctor.js.map +1 -0
- package/dist/core/index.d.ts +200 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2587 -0
- package/dist/core/index.js.map +1 -0
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +252 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/engine/adapters/node-test-profile.d.ts +14 -0
- package/dist/engine/adapters/node-test-profile.d.ts.map +1 -0
- package/dist/engine/adapters/node-test-profile.js +14 -0
- package/dist/engine/adapters/node-test-profile.js.map +1 -0
- package/dist/engine/adapters/node-test-runtime.d.ts +39 -0
- package/dist/engine/adapters/node-test-runtime.d.ts.map +1 -0
- package/dist/engine/adapters/node-test-runtime.js +173 -0
- package/dist/engine/adapters/node-test-runtime.js.map +1 -0
- package/dist/engine/adapters/runtime-facts.d.ts +26 -0
- package/dist/engine/adapters/runtime-facts.d.ts.map +1 -0
- package/dist/engine/adapters/runtime-facts.js +73 -0
- package/dist/engine/adapters/runtime-facts.js.map +1 -0
- package/dist/engine/connection.d.ts +22 -0
- package/dist/engine/connection.d.ts.map +1 -0
- package/dist/engine/connection.js +343 -0
- package/dist/engine/connection.js.map +1 -0
- package/dist/engine/git-regression.d.ts +25 -0
- package/dist/engine/git-regression.d.ts.map +1 -0
- package/dist/engine/git-regression.js +803 -0
- package/dist/engine/git-regression.js.map +1 -0
- package/dist/engine/index.d.ts +55 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +2782 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/node-test-reporter.d.ts +2 -0
- package/dist/engine/node-test-reporter.d.ts.map +1 -0
- package/dist/engine/node-test-reporter.js +70 -0
- package/dist/engine/node-test-reporter.js.map +1 -0
- package/dist/engine/runtime-doctor.d.ts +16 -0
- package/dist/engine/runtime-doctor.d.ts.map +1 -0
- package/dist/engine/runtime-doctor.js +100 -0
- package/dist/engine/runtime-doctor.js.map +1 -0
- package/dist/evaluation/agentic-corpus.d.ts +161 -0
- package/dist/evaluation/agentic-corpus.d.ts.map +1 -0
- package/dist/evaluation/agentic-corpus.js +710 -0
- package/dist/evaluation/agentic-corpus.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +391 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/stdio.d.ts +3 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +13 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/sdk/index.d.ts +52 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +224 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/docs/adapter-protocol.md +196 -0
- package/docs/agentic-benchmark.md +118 -0
- package/docs/agentic-corpus-experiment-h3.md +89 -0
- package/docs/agentic-corpus-plan.md +105 -0
- package/docs/agentic-corpus-provenance.md +59 -0
- package/docs/agentic-test-profile-pilot.md +57 -0
- package/docs/agentic-test-profile-v2.md +116 -0
- package/docs/agentic-test-profile.md +274 -0
- package/docs/architecture.md +157 -0
- package/docs/ci.md +37 -0
- package/docs/client-connections.md +61 -0
- package/docs/conformance-v1.md +72 -0
- package/docs/decisions/0001-typescript-runtime.md +24 -0
- package/docs/developer-experience.md +55 -0
- package/docs/diagnostics.md +35 -0
- package/docs/distribution.md +40 -0
- package/docs/git-regression.md +39 -0
- package/docs/migration-repository-validation-order.md +35 -0
- package/docs/migration-testforge-to-assertledger.md +64 -0
- package/docs/project-intent.md +173 -0
- package/docs/proof-model.md +116 -0
- package/docs/reference.md +336 -0
- package/docs/release-1.0.md +63 -0
- package/docs/repository-audit.md +52 -0
- package/docs/repository-init.md +60 -0
- package/docs/research-basis.md +27 -0
- package/docs/roadmap.md +74 -0
- package/docs/runtime-doctor.md +65 -0
- package/docs/testexplora-calibration.md +71 -0
- package/examples/agentic-benchmark/benchmark-request.mjs +19 -0
- package/examples/agentic-benchmark/structured-phase-adapter-fixture.mjs +35 -0
- package/examples/agentic-profile/profile-benchmark.mjs +34 -0
- package/examples/agentic-profile/profile-manifest.mjs +28 -0
- package/examples/git-history/README.md +44 -0
- package/examples/git-history/create-demo.mjs +128 -0
- package/examples/git-history/escape-string-regexp/LICENSE +9 -0
- package/examples/git-history/escape-string-regexp/before.cjs.txt +11 -0
- package/examples/git-history/escape-string-regexp/fixed.cjs.txt +13 -0
- package/examples/git-history/escape-string-regexp/provenance.json +28 -0
- package/examples/node-test/repository/package.json +5 -0
- package/examples/node-test/repository/src/is-even.js +3 -0
- package/examples/node-test/repository/tests/base.test.js +6 -0
- package/examples/node-test/request.json +93 -0
- package/integrations/skill/SKILL.md +51 -0
- package/package.json +88 -0
- package/schemas/agentic-benchmark-acquisition-replay-result.v1.json +70 -0
- package/schemas/agentic-benchmark-acquisition-request.v1.json +564 -0
- package/schemas/agentic-benchmark-acquisition-result.v1.json +1409 -0
- package/schemas/agentic-benchmark-artifact.v1.json +1251 -0
- package/schemas/agentic-benchmark-replay-result.v1.json +84 -0
- package/schemas/agentic-benchmark-request.v1.json +1034 -0
- package/schemas/agentic-corpus-allocation-commitment-replay-result.v1.json +58 -0
- package/schemas/agentic-corpus-allocation-commitment.v1.json +141 -0
- package/schemas/agentic-corpus-allocation-replay-result.v1.json +34 -0
- package/schemas/agentic-corpus-allocation-request.v1.json +65 -0
- package/schemas/agentic-corpus-allocation-reveal.v1.json +66 -0
- package/schemas/agentic-corpus-allocation.v1.json +167 -0
- package/schemas/agentic-corpus-experiment-artifact.v1.json +329 -0
- package/schemas/agentic-corpus-experiment-plan-replay-result.v1.json +50 -0
- package/schemas/agentic-corpus-experiment-plan.v1.json +424 -0
- package/schemas/agentic-corpus-experiment-replay-request.v1.json +336 -0
- package/schemas/agentic-corpus-experiment-replay-result.v1.json +106 -0
- package/schemas/agentic-corpus-experiment-request.v1.json +204 -0
- package/schemas/agentic-corpus-provenance.v1.json +143 -0
- package/schemas/agentic-corpus-trust-policy.v1.json +133 -0
- package/schemas/agentic-profile-replay-result.v1.json +56 -0
- package/schemas/agentic-profile-replay-result.v2.json +63 -0
- package/schemas/agentic-profile-report.v1.json +961 -0
- package/schemas/agentic-profile-report.v2.json +1674 -0
- package/schemas/agentic-profile-request.v1.json +671 -0
- package/schemas/agentic-profile-request.v2.json +1338 -0
- package/schemas/evidence-manifest.v1.json +636 -0
- package/schemas/replay-result.v1.json +49 -0
- package/schemas/repository-analysis.v1.json +119 -0
- package/schemas/repository-audit.v1.json +811 -0
- package/schemas/repository-init-config.v1.json +183 -0
- package/schemas/repository-init-lock.v1.json +162 -0
- package/schemas/repository-init-result.v1.json +212 -0
- package/schemas/verification-request.v1.json +389 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
6
|
+
import { ContractError } from "./contracts/index.js";
|
|
7
|
+
import { renderDiagnostics } from "./diagnostics.js";
|
|
8
|
+
import { connectClient, disconnectClient } from "./engine/connection.js";
|
|
9
|
+
import { renderGitRegressionSummary } from "./engine/git-regression.js";
|
|
10
|
+
import { AgenticCorpusError, evaluateAgenticCorpusHoldout, evaluateAgenticCorpusPublic, inspectAgenticCorpus, } from "./evaluation/agentic-corpus.js";
|
|
11
|
+
import { createAssertLedgerServer } from "./mcp/index.js";
|
|
12
|
+
import { AssertLedger } from "./sdk/index.js";
|
|
13
|
+
import { ASSERTLEDGER_VERSION } from "./version.js";
|
|
14
|
+
const USAGE = `Usage: assertledger <command> [arguments] [--json]
|
|
15
|
+
(legacy alias: testforge <command> [arguments] [--json])
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
explain CODE [CODE ...] [--json] Explain reason codes and safe next actions
|
|
19
|
+
check [repository] --before REF [--after REF] --neutral REF --neutral-reason TEXT
|
|
20
|
+
--test PATH --base-test PATH [--base-test PATH ...] --out RELATIVE_DIRECTORY
|
|
21
|
+
--allow-unsafe-execution Qualify one committed node:test regression
|
|
22
|
+
doctor [repository] [--json] Inspect static repository readiness without writing
|
|
23
|
+
doctor [repository] --runtime --allow-unsafe-execution [--json]
|
|
24
|
+
Run controlled trusted-local runtime probes
|
|
25
|
+
connect [repository] --client <codex|claude-code> [--write]
|
|
26
|
+
Preview or install project-local client integration
|
|
27
|
+
connect [repository] --client mcp Emit a generic stdio descriptor as JSON
|
|
28
|
+
disconnect [repository] --client <codex|claude-code> [--write]
|
|
29
|
+
Preview or remove exact AssertLedger-owned artifacts
|
|
30
|
+
analyze [repository] Analyze a repository
|
|
31
|
+
init [repository] [--dry-run] [--adapter-config PATH] [--package-manager ID]
|
|
32
|
+
[--framework ID] [--test-command-json PATH]
|
|
33
|
+
Detect and write portable initialization files
|
|
34
|
+
audit [repository] [--verification-request PATH] [--emit-verification-request] [--no-git]
|
|
35
|
+
Produce a static audit and campaign cost projection
|
|
36
|
+
schema <verification-request|repository-analysis|repository-audit|repository-init-config|
|
|
37
|
+
repository-init-lock|repository-init-result|evidence-manifest|replay-result|
|
|
38
|
+
agentic-profile-request|agentic-profile-report|agentic-profile-replay-result|
|
|
39
|
+
agentic-profile-request-v2|agentic-profile-report-v2|agentic-profile-replay-result-v2|
|
|
40
|
+
agentic-benchmark-request|agentic-benchmark-artifact|agentic-benchmark-replay-result|
|
|
41
|
+
agentic-benchmark-acquisition-request|agentic-benchmark-acquisition-result|
|
|
42
|
+
agentic-benchmark-acquisition-replay-result|agentic-corpus-allocation-request|
|
|
43
|
+
agentic-corpus-allocation|agentic-corpus-allocation-replay-result|
|
|
44
|
+
agentic-corpus-allocation-commitment|agentic-corpus-allocation-reveal|
|
|
45
|
+
agentic-corpus-allocation-commitment-replay-result|agentic-corpus-experiment-plan|
|
|
46
|
+
agentic-corpus-experiment-plan-replay-result|
|
|
47
|
+
agentic-corpus-experiment-request|agentic-corpus-experiment-artifact|
|
|
48
|
+
agentic-corpus-experiment-replay-request|agentic-corpus-experiment-replay-result>
|
|
49
|
+
Print a JSON Schema
|
|
50
|
+
verify [request.json|-] --allow-unsafe-execution
|
|
51
|
+
Execute a trusted-local campaign
|
|
52
|
+
replay [manifest.json|-] Verify an evidence digest
|
|
53
|
+
profile [request.json|-] Derive an Agentic Test Profile
|
|
54
|
+
profile-replay [report.json|-] Replay an Agentic Test Profile
|
|
55
|
+
profile-v2 [request.json|-] Derive a benchmark-backed Agentic Test Profile v2
|
|
56
|
+
profile-v2-replay [report.json|-] Replay an Agentic Test Profile v2
|
|
57
|
+
benchmark [request.json|-] Derive an Agentic Benchmark Artifact
|
|
58
|
+
benchmark-replay [artifact.json|-] Replay an Agentic Benchmark Artifact
|
|
59
|
+
benchmark-acquire [request.json|-] --allow-unsafe-execution
|
|
60
|
+
Run fresh verification and phase-aware acquisition
|
|
61
|
+
benchmark-acquire-replay [result.json|-] Replay benchmark acquisition evidence
|
|
62
|
+
corpus-allocate [request.json|-] Create a deterministic corpus allocation
|
|
63
|
+
corpus-allocation-replay [allocation.json|-] Replay a corpus allocation
|
|
64
|
+
corpus-experiment-replay [artifact.json|-] --trust-policy PATH --trust-policy-digest DIGEST
|
|
65
|
+
--allocation-commitment PATH --allocation-commitment-digest DIGEST --allocation-reveal PATH
|
|
66
|
+
--allocation PATH --experiment-plan PATH --experiment-plan-digest DIGEST
|
|
67
|
+
--subject-evidence PATH --evidence-contents PATH
|
|
68
|
+
Replay externally anchored H3 evidence
|
|
69
|
+
corpus-status [corpus-root] Check H1-H4 corpus readiness
|
|
70
|
+
corpus-evaluate-public [corpus-root] Evaluate the public corpus with feedback
|
|
71
|
+
corpus-evaluate-holdout [corpus-root] Evaluate holdout aggregates without leakage
|
|
72
|
+
mcp [--root PATH] [--allow-unsafe-execution] Serve MCP v2 over stdio (read-only by default)
|
|
73
|
+
`;
|
|
74
|
+
const MAXIMUM_JSON_INPUT_BYTES = 16 * 1024 * 1024;
|
|
75
|
+
function writeJson(io, value) {
|
|
76
|
+
io.writeStdout(`${JSON.stringify(value)}\n`);
|
|
77
|
+
}
|
|
78
|
+
function errorMessage(error) {
|
|
79
|
+
return error instanceof Error ? error.message : String(error);
|
|
80
|
+
}
|
|
81
|
+
function isRecord(value) {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
+
}
|
|
84
|
+
function authorizeTrustedLocalExecution(request) {
|
|
85
|
+
if (!isRecord(request) || !isRecord(request.isolation))
|
|
86
|
+
return request;
|
|
87
|
+
return {
|
|
88
|
+
...request,
|
|
89
|
+
isolation: {
|
|
90
|
+
...request.isolation,
|
|
91
|
+
acknowledgedUnsafeExecution: true,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function authorizeBenchmarkAcquisition(request) {
|
|
96
|
+
if (!isRecord(request) || !isRecord(request.verificationRequest))
|
|
97
|
+
return request;
|
|
98
|
+
return {
|
|
99
|
+
...request,
|
|
100
|
+
verificationRequest: authorizeTrustedLocalExecution(request.verificationRequest),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function readJsonInput(argument, io) {
|
|
104
|
+
let text;
|
|
105
|
+
if (argument === undefined || argument === "-") {
|
|
106
|
+
text = await io.readStdin();
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const inputPath = path.resolve(io.cwd, argument);
|
|
110
|
+
if ((await stat(inputPath)).size > MAXIMUM_JSON_INPUT_BYTES) {
|
|
111
|
+
throw new TypeError("JSON_INPUT_TOO_LARGE");
|
|
112
|
+
}
|
|
113
|
+
text = await readFile(inputPath, "utf8");
|
|
114
|
+
}
|
|
115
|
+
if (Buffer.byteLength(text, "utf8") > MAXIMUM_JSON_INPUT_BYTES) {
|
|
116
|
+
throw new TypeError("JSON_INPUT_TOO_LARGE");
|
|
117
|
+
}
|
|
118
|
+
if (text.trim().length === 0)
|
|
119
|
+
throw new SyntaxError("JSON input is required");
|
|
120
|
+
return JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
function requiredFlag(argv, name) {
|
|
123
|
+
const index = argv.indexOf(name);
|
|
124
|
+
const value = index < 0 ? undefined : argv[index + 1];
|
|
125
|
+
if (value === undefined || value.startsWith("--"))
|
|
126
|
+
throw new TypeError(`MISSING_${name.slice(2).toUpperCase().replaceAll("-", "_")}`);
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
function optionalFlag(argv, name) {
|
|
130
|
+
const index = argv.indexOf(name);
|
|
131
|
+
if (index < 0)
|
|
132
|
+
return undefined;
|
|
133
|
+
const value = argv[index + 1];
|
|
134
|
+
if (value === undefined || value.startsWith("--"))
|
|
135
|
+
throw new TypeError(`MISSING_${name.slice(2).toUpperCase().replaceAll("-", "_")}`);
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function parseClientArguments(argv, cwd, operation) {
|
|
139
|
+
let client;
|
|
140
|
+
let rootArgument = ".";
|
|
141
|
+
let rootSeen = false;
|
|
142
|
+
let clientSeen = false;
|
|
143
|
+
let writeSeen = false;
|
|
144
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
145
|
+
const argument = argv[index] ?? "";
|
|
146
|
+
if (argument === "--client") {
|
|
147
|
+
if (clientSeen)
|
|
148
|
+
return undefined;
|
|
149
|
+
const value = argv[index + 1];
|
|
150
|
+
if (value === undefined || value.startsWith("-"))
|
|
151
|
+
return undefined;
|
|
152
|
+
client = value;
|
|
153
|
+
clientSeen = true;
|
|
154
|
+
index += 1;
|
|
155
|
+
}
|
|
156
|
+
else if (argument === "--write") {
|
|
157
|
+
if (writeSeen)
|
|
158
|
+
return undefined;
|
|
159
|
+
writeSeen = true;
|
|
160
|
+
}
|
|
161
|
+
else if (argument.startsWith("-") || rootSeen) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
rootArgument = argument;
|
|
166
|
+
rootSeen = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!["codex", "claude-code", "mcp"].includes(client ?? ""))
|
|
170
|
+
return undefined;
|
|
171
|
+
if (operation === "disconnect" && client === "mcp")
|
|
172
|
+
return undefined;
|
|
173
|
+
if (client === "mcp" && writeSeen)
|
|
174
|
+
return undefined;
|
|
175
|
+
return {
|
|
176
|
+
root: path.resolve(cwd, rootArgument),
|
|
177
|
+
client: client,
|
|
178
|
+
write: writeSeen,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function parseCheckArguments(argv, cwd) {
|
|
182
|
+
const values = new Map();
|
|
183
|
+
let repository = ".";
|
|
184
|
+
let repositorySeen = false;
|
|
185
|
+
const valueFlags = new Set([
|
|
186
|
+
"--before",
|
|
187
|
+
"--after",
|
|
188
|
+
"--neutral",
|
|
189
|
+
"--neutral-reason",
|
|
190
|
+
"--test",
|
|
191
|
+
"--base-test",
|
|
192
|
+
"--out",
|
|
193
|
+
]);
|
|
194
|
+
const booleanFlags = new Set(["--allow-unsafe-execution", "--json"]);
|
|
195
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
196
|
+
const argument = argv[index] ?? "";
|
|
197
|
+
if (valueFlags.has(argument)) {
|
|
198
|
+
const value = argv[index + 1];
|
|
199
|
+
if (value === undefined || value.startsWith("--"))
|
|
200
|
+
throw new TypeError(`MISSING_${argument.slice(2).toUpperCase().replaceAll("-", "_")}`);
|
|
201
|
+
const existing = values.get(argument) ?? [];
|
|
202
|
+
existing.push(value);
|
|
203
|
+
values.set(argument, existing);
|
|
204
|
+
index += 1;
|
|
205
|
+
}
|
|
206
|
+
else if (!booleanFlags.has(argument) && !argument.startsWith("--") && !repositorySeen) {
|
|
207
|
+
repository = argument;
|
|
208
|
+
repositorySeen = true;
|
|
209
|
+
}
|
|
210
|
+
else if (!booleanFlags.has(argument)) {
|
|
211
|
+
throw new TypeError("GIT_REGRESSION_ARGUMENT_INVALID");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const exactlyOne = (name) => {
|
|
215
|
+
const found = values.get(name) ?? [];
|
|
216
|
+
if (found.length !== 1)
|
|
217
|
+
throw new TypeError(found.length === 0
|
|
218
|
+
? `MISSING_${name.slice(2).toUpperCase().replaceAll("-", "_")}`
|
|
219
|
+
: "GIT_REGRESSION_ARGUMENT_INVALID");
|
|
220
|
+
return found[0];
|
|
221
|
+
};
|
|
222
|
+
const afterValues = values.get("--after") ?? [];
|
|
223
|
+
if (afterValues.length > 1)
|
|
224
|
+
throw new TypeError("GIT_REGRESSION_ARGUMENT_INVALID");
|
|
225
|
+
const baseTests = values.get("--base-test") ?? [];
|
|
226
|
+
if (baseTests.length === 0)
|
|
227
|
+
throw new TypeError("MISSING_BASE_TEST");
|
|
228
|
+
return {
|
|
229
|
+
repository: path.resolve(cwd, repository),
|
|
230
|
+
before: exactlyOne("--before"),
|
|
231
|
+
...(afterValues[0] === undefined ? {} : { after: afterValues[0] }),
|
|
232
|
+
neutral: exactlyOne("--neutral"),
|
|
233
|
+
neutralReason: exactlyOne("--neutral-reason"),
|
|
234
|
+
test: exactlyOne("--test"),
|
|
235
|
+
baseTests,
|
|
236
|
+
out: exactlyOne("--out"),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function evidenceContentMap(value) {
|
|
240
|
+
if (!Array.isArray(value))
|
|
241
|
+
throw new TypeError("EVIDENCE_CONTENTS_INVALID");
|
|
242
|
+
const entries = value.map((entry) => {
|
|
243
|
+
if (!isRecord(entry) ||
|
|
244
|
+
typeof entry.digest !== "string" ||
|
|
245
|
+
entry.encoding !== "BASE64URL" ||
|
|
246
|
+
typeof entry.content !== "string") {
|
|
247
|
+
throw new TypeError("EVIDENCE_CONTENTS_INVALID");
|
|
248
|
+
}
|
|
249
|
+
return [entry.digest, new Uint8Array(Buffer.from(entry.content, "base64url"))];
|
|
250
|
+
});
|
|
251
|
+
if (new Set(entries.map(([digest]) => digest)).size !== entries.length)
|
|
252
|
+
throw new TypeError("EVIDENCE_CONTENTS_INVALID");
|
|
253
|
+
return new Map(entries);
|
|
254
|
+
}
|
|
255
|
+
function decisionExitCode(result) {
|
|
256
|
+
if (!isRecord(result) || !isRecord(result.decision))
|
|
257
|
+
return 5;
|
|
258
|
+
switch (result.decision.status) {
|
|
259
|
+
case "VERIFIED":
|
|
260
|
+
return 0;
|
|
261
|
+
case "REJECTED":
|
|
262
|
+
return 2;
|
|
263
|
+
case "INCONCLUSIVE":
|
|
264
|
+
return 3;
|
|
265
|
+
default:
|
|
266
|
+
return 5;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function profileExitCode(result) {
|
|
270
|
+
if (!isRecord(result))
|
|
271
|
+
return 5;
|
|
272
|
+
switch (result.status) {
|
|
273
|
+
case "QUALIFIED":
|
|
274
|
+
return 0;
|
|
275
|
+
case "NOT_QUALIFIED":
|
|
276
|
+
case "BUDGET_MISSED":
|
|
277
|
+
return 2;
|
|
278
|
+
case "INSUFFICIENT_TIMING_EVIDENCE":
|
|
279
|
+
return 3;
|
|
280
|
+
case "OBSERVED_BENCHMARK_FAILURE":
|
|
281
|
+
case "COMPARISON_SCOPE_MISMATCH":
|
|
282
|
+
return 4;
|
|
283
|
+
default:
|
|
284
|
+
return 5;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function benchmarkExitCode(result) {
|
|
288
|
+
if (!isRecord(result) || !Array.isArray(result.summaries))
|
|
289
|
+
return 5;
|
|
290
|
+
const statuses = result.summaries.map((summary) => isRecord(summary) ? summary.status : undefined);
|
|
291
|
+
if (statuses.every((status) => status === "MEASURED"))
|
|
292
|
+
return 0;
|
|
293
|
+
if (statuses.some((status) => status === "OBSERVED_RUN_FAILURE"))
|
|
294
|
+
return 2;
|
|
295
|
+
if (statuses.some((status) => status === "INSUFFICIENT_SAMPLES"))
|
|
296
|
+
return 3;
|
|
297
|
+
return 5;
|
|
298
|
+
}
|
|
299
|
+
function benchmarkAcquisitionExitCode(result) {
|
|
300
|
+
if (!isRecord(result))
|
|
301
|
+
return 5;
|
|
302
|
+
switch (result.status) {
|
|
303
|
+
case "COMPLETE":
|
|
304
|
+
return 0;
|
|
305
|
+
case "SOURCE_NOT_VERIFIED":
|
|
306
|
+
case "OBSERVED_RUN_FAILURE":
|
|
307
|
+
return 2;
|
|
308
|
+
case "INSUFFICIENT_SAMPLES":
|
|
309
|
+
return 3;
|
|
310
|
+
default:
|
|
311
|
+
return 5;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function runCorpusCommand(action, root, io) {
|
|
315
|
+
try {
|
|
316
|
+
const result = action === "STATUS"
|
|
317
|
+
? await inspectAgenticCorpus(root)
|
|
318
|
+
: action === "PUBLIC"
|
|
319
|
+
? await evaluateAgenticCorpusPublic(root)
|
|
320
|
+
: await evaluateAgenticCorpusHoldout(root);
|
|
321
|
+
writeJson(io, result);
|
|
322
|
+
return result.status === "NOT_READY" ? 3 : 0;
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
writeJson(io, {
|
|
326
|
+
schemaVersion: "1.0.0",
|
|
327
|
+
status: "INVALID_CORPUS",
|
|
328
|
+
reasonCodes: [error instanceof AgenticCorpusError ? error.code : "CORPUS_IO_ERROR"],
|
|
329
|
+
});
|
|
330
|
+
return 4;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const VALIDATION_ERROR_CODES = new Set([
|
|
334
|
+
"DIAGNOSTIC_CODES_INVALID",
|
|
335
|
+
"CANDIDATE_BUDGET_EXCEEDED",
|
|
336
|
+
"CANDIDATE_BYTES_EXCEEDED",
|
|
337
|
+
"DUPLICATE_BASE_TEST_FILE",
|
|
338
|
+
"DUPLICATE_CANDIDATE_FILE_PATH",
|
|
339
|
+
"DUPLICATE_CANDIDATE_ID",
|
|
340
|
+
"DUPLICATE_CANDIDATE_ROOT",
|
|
341
|
+
"DUPLICATE_ENVIRONMENT_ALLOWLIST_ENTRY",
|
|
342
|
+
"DUPLICATE_WORLD_FILE_PATH",
|
|
343
|
+
"DUPLICATE_WORLD_ID",
|
|
344
|
+
"EXECUTION_BUDGET_EXCEEDED",
|
|
345
|
+
"FORBIDDEN_CANDIDATE_PATH",
|
|
346
|
+
"FORBIDDEN_WORLD_PATH",
|
|
347
|
+
"INVALID_ACCEPTED_TARGET_OUTCOMES",
|
|
348
|
+
"INVALID_ADAPTER",
|
|
349
|
+
"INVALID_ADAPTER_ARGUMENTS",
|
|
350
|
+
"INVALID_ADAPTER_EXECUTABLE",
|
|
351
|
+
"INVALID_BASE_TEST_FILES",
|
|
352
|
+
"INVALID_BUDGETS",
|
|
353
|
+
"INVALID_CANDIDATE",
|
|
354
|
+
"INVALID_CAMPAIGN_COLLECTIONS",
|
|
355
|
+
"INVALID_CANDIDATE_FILES",
|
|
356
|
+
"INVALID_CANDIDATE_ID",
|
|
357
|
+
"INVALID_CANDIDATE_ROOTS",
|
|
358
|
+
"INVALID_ENVIRONMENT_ALLOWLIST",
|
|
359
|
+
"INVALID_ENVIRONMENT_ALLOWLIST_ENTRY",
|
|
360
|
+
"INVALID_FILE_OVERLAY",
|
|
361
|
+
"INVALID_ISOLATION",
|
|
362
|
+
"INVALID_MAXIMUM_CANDIDATE_BYTES",
|
|
363
|
+
"INVALID_MAXIMUM_CANDIDATES",
|
|
364
|
+
"INVALID_MAXIMUM_EXECUTIONS",
|
|
365
|
+
"INVALID_MAXIMUM_OUTPUT_BYTES",
|
|
366
|
+
"INVALID_MAXIMUM_REPOSITORY_BYTES",
|
|
367
|
+
"INVALID_MAXIMUM_REPOSITORY_FILES",
|
|
368
|
+
"INVALID_MAXIMUM_SELECTED_CANDIDATES",
|
|
369
|
+
"INVALID_MAXIMUM_TOTAL_CANDIDATE_BYTES",
|
|
370
|
+
"INVALID_MAXIMUM_WORLD_OVERLAY_BYTES",
|
|
371
|
+
"INVALID_MAXIMUM_WORLDS",
|
|
372
|
+
"INVALID_MINIMUM_TARGET_WEIGHT_PERMILLE",
|
|
373
|
+
"INVALID_OVERLAY_CONTENT",
|
|
374
|
+
"INVALID_OVERLAY_PATH",
|
|
375
|
+
"INVALID_POLICY",
|
|
376
|
+
"INVALID_POLICY_VERSION",
|
|
377
|
+
"INVALID_REPOSITORY",
|
|
378
|
+
"INVALID_REPOSITORY_EXCLUDE",
|
|
379
|
+
"INVALID_REPOSITORY_ROOT",
|
|
380
|
+
"INVALID_REQUIRED_ATTEMPTS",
|
|
381
|
+
"INVALID_SCHEMA_VERSION",
|
|
382
|
+
"INVALID_TIMEOUT_MS_PER_EXECUTION",
|
|
383
|
+
"INVALID_WORLD",
|
|
384
|
+
"INVALID_WORLD_FILES",
|
|
385
|
+
"INVALID_WORLD_ID",
|
|
386
|
+
"INVALID_WORLD_KIND",
|
|
387
|
+
"INVALID_WORLD_REQUIRED",
|
|
388
|
+
"INVALID_WORLD_WEIGHT",
|
|
389
|
+
"INVALID_WORLDS",
|
|
390
|
+
"JSON_INPUT_TOO_LARGE",
|
|
391
|
+
"AGENTIC_PROFILE_SOURCE_INVALID",
|
|
392
|
+
"AGENTIC_BENCHMARK_REFERENCE_WORLD_INVALID",
|
|
393
|
+
"AGENTIC_BENCHMARK_SOURCE_INVALID",
|
|
394
|
+
"AGENTIC_BENCHMARK_SOURCE_BINDING_INVALID",
|
|
395
|
+
"AGENTIC_BENCHMARK_ACQUISITION_REQUEST_INVALID",
|
|
396
|
+
"AGENTIC_BENCHMARK_ACQUISITION_IDENTITY_INVALID",
|
|
397
|
+
"BENCHMARK_PHASE_ACQUISITION_UNSUPPORTED_NODE_TEST",
|
|
398
|
+
"AGENTIC_PROFILE_V2_BENCHMARK_INVALID",
|
|
399
|
+
"AGENTIC_PROFILE_V2_COHORT_INVALID",
|
|
400
|
+
"NODE_TEST_EXECUTABLE_PROBE_FAILED",
|
|
401
|
+
"NODE_TEST_VERSION_UNSUPPORTED",
|
|
402
|
+
"PORTABLE_PATH_COLLISION",
|
|
403
|
+
"REPOSITORY_BYTES_BUDGET_EXCEEDED",
|
|
404
|
+
"REPOSITORY_FILE_BUDGET_EXCEEDED",
|
|
405
|
+
"RESERVED_ENVIRONMENT_VARIABLE",
|
|
406
|
+
"SYMLINK_OVERLAY_PATH",
|
|
407
|
+
"TOTAL_CANDIDATE_BYTES_EXCEEDED",
|
|
408
|
+
"UNSAFE_LOCAL_EXECUTION_NOT_ACKNOWLEDGED",
|
|
409
|
+
"UNSAFE_NODE_TEST_ARGUMENT",
|
|
410
|
+
"UNSUPPORTED_ADAPTER",
|
|
411
|
+
"UNSUPPORTED_ISOLATION",
|
|
412
|
+
"UNSUPPORTED_REPOSITORY_SYMLINK",
|
|
413
|
+
"VERIFICATION_REQUEST_REPOSITORY_MISMATCH",
|
|
414
|
+
"WORLD_BUDGET_EXCEEDED",
|
|
415
|
+
"WORLD_OVERLAY_BYTES_EXCEEDED",
|
|
416
|
+
]);
|
|
417
|
+
const BOUNDARY_VALIDATION_MESSAGES = new Set([
|
|
418
|
+
"Absolute paths and Windows ADS are forbidden",
|
|
419
|
+
"Ambiguous Unicode path segment must use NFC",
|
|
420
|
+
"Ambiguous Windows path segment",
|
|
421
|
+
"At least one safe root is required",
|
|
422
|
+
"Control or Windows-forbidden filename character",
|
|
423
|
+
"Path escapes the allowed roots",
|
|
424
|
+
"Path must be a non-empty relative path",
|
|
425
|
+
"Path traversal or ambiguous segments are forbidden",
|
|
426
|
+
"Reserved Windows device name",
|
|
427
|
+
"Safe roots must be strings",
|
|
428
|
+
]);
|
|
429
|
+
function classifyError(error) {
|
|
430
|
+
if (error instanceof SyntaxError || error instanceof ContractError) {
|
|
431
|
+
return 4;
|
|
432
|
+
}
|
|
433
|
+
if (error instanceof Error &&
|
|
434
|
+
(VALIDATION_ERROR_CODES.has(error.message) ||
|
|
435
|
+
BOUNDARY_VALIDATION_MESSAGES.has(error.message) ||
|
|
436
|
+
error.message.startsWith("MISSING_") ||
|
|
437
|
+
error.message.startsWith("GIT_") ||
|
|
438
|
+
error.message === "EVIDENCE_CONTENTS_INVALID" ||
|
|
439
|
+
error.message === "SUBJECT_EVIDENCE_INVALID")) {
|
|
440
|
+
return 4;
|
|
441
|
+
}
|
|
442
|
+
return 5;
|
|
443
|
+
}
|
|
444
|
+
export async function runCli(argv, io) {
|
|
445
|
+
const ledger = new AssertLedger();
|
|
446
|
+
const positional = argv.filter((argument) => !argument.startsWith("--"));
|
|
447
|
+
const command = ["--help", "-h", "--version", "-v"].includes(argv[0] ?? "")
|
|
448
|
+
? argv[0]
|
|
449
|
+
: positional[0];
|
|
450
|
+
try {
|
|
451
|
+
switch (command) {
|
|
452
|
+
case "explain": {
|
|
453
|
+
const codes = argv.slice(1).filter((argument) => argument !== "--json");
|
|
454
|
+
if (codes.length === 0 ||
|
|
455
|
+
codes.some((code) => code.startsWith("-")) ||
|
|
456
|
+
argv.filter((argument) => argument === "--json").length > 1) {
|
|
457
|
+
io.writeStderr(USAGE);
|
|
458
|
+
return 64;
|
|
459
|
+
}
|
|
460
|
+
const report = ledger.explain(codes);
|
|
461
|
+
if (argv.includes("--json"))
|
|
462
|
+
writeJson(io, report);
|
|
463
|
+
else
|
|
464
|
+
io.writeStdout(`${renderDiagnostics(codes)}\n`);
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
467
|
+
case "help":
|
|
468
|
+
case "--help":
|
|
469
|
+
case "-h":
|
|
470
|
+
if (argv.length !== 1) {
|
|
471
|
+
io.writeStderr(USAGE);
|
|
472
|
+
return 64;
|
|
473
|
+
}
|
|
474
|
+
io.writeStdout(USAGE);
|
|
475
|
+
return 0;
|
|
476
|
+
case "version":
|
|
477
|
+
case "--version":
|
|
478
|
+
case "-v":
|
|
479
|
+
if (argv.length !== 1) {
|
|
480
|
+
io.writeStderr(USAGE);
|
|
481
|
+
return 64;
|
|
482
|
+
}
|
|
483
|
+
io.writeStdout(`assertledger ${ASSERTLEDGER_VERSION}\n`);
|
|
484
|
+
return 0;
|
|
485
|
+
case "doctor": {
|
|
486
|
+
const allowedFlags = new Set(["--json", "--runtime", "--allow-unsafe-execution"]);
|
|
487
|
+
const rootArguments = argv.slice(1).filter((argument) => !argument.startsWith("-"));
|
|
488
|
+
const runtime = argv.includes("--runtime");
|
|
489
|
+
const allowUnsafeExecution = argv.includes("--allow-unsafe-execution");
|
|
490
|
+
if (rootArguments.length > 1 ||
|
|
491
|
+
[...allowedFlags].some((flag) => argv.filter((argument) => argument === flag).length > 1) ||
|
|
492
|
+
(allowUnsafeExecution && !runtime) ||
|
|
493
|
+
argv.slice(1).some((argument) => argument.startsWith("-") && !allowedFlags.has(argument))) {
|
|
494
|
+
io.writeStderr(USAGE);
|
|
495
|
+
return 64;
|
|
496
|
+
}
|
|
497
|
+
const root = path.resolve(io.cwd, rootArguments[0] ?? ".");
|
|
498
|
+
if (runtime) {
|
|
499
|
+
const runtimeResult = await ledger.doctorRuntime(root, { allowUnsafeExecution });
|
|
500
|
+
if (argv.includes("--json")) {
|
|
501
|
+
writeJson(io, runtimeResult);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const reasons = runtimeResult.reasonCodes.length === 0
|
|
505
|
+
? "none"
|
|
506
|
+
: runtimeResult.reasonCodes.join(", ");
|
|
507
|
+
io.writeStdout([
|
|
508
|
+
`Runtime status: ${runtimeResult.status}`,
|
|
509
|
+
`Execution mode: ${runtimeResult.executionMode}`,
|
|
510
|
+
`Adapter: ${runtimeResult.adapter ?? "not checked"}`,
|
|
511
|
+
`Node.js: ${runtimeResult.nodeVersion ?? "not checked"}`,
|
|
512
|
+
`Reason codes: ${reasons}`,
|
|
513
|
+
...runtimeResult.checks.map((check) => `${check.status} ${check.id}: ${check.summary}${check.nextAction ? ` Next: ${check.nextAction}` : ""}`),
|
|
514
|
+
...runtimeResult.limitations.map((limitation) => `Limit: ${limitation}`),
|
|
515
|
+
"",
|
|
516
|
+
].join("\n"));
|
|
517
|
+
}
|
|
518
|
+
return runtimeResult.status === "READY" ? 0 : 3;
|
|
519
|
+
}
|
|
520
|
+
const result = await ledger.doctor(root);
|
|
521
|
+
if (argv.includes("--json")) {
|
|
522
|
+
writeJson(io, result);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const reasons = result.reasonCodes.length === 0 ? "none" : result.reasonCodes.join(", ");
|
|
526
|
+
io.writeStdout([
|
|
527
|
+
`Status: ${result.status}`,
|
|
528
|
+
`Reason codes: ${reasons}`,
|
|
529
|
+
`Required operator inputs: ${result.requiredOperatorInputs.join(", ")}`,
|
|
530
|
+
renderDiagnostics(result.reasonCodes),
|
|
531
|
+
`Next safe action: ${result.nextCommands[0]?.executable ?? "assertledger"} ${(result.nextCommands[0]?.arguments ?? []).join(" ")}`,
|
|
532
|
+
"Execution limit: verification remains UNSANDBOXED trusted-local and requires explicit operator authorization.",
|
|
533
|
+
"This static diagnostic does not prove campaign evidence or MCP connectivity.",
|
|
534
|
+
"",
|
|
535
|
+
].join("\n"));
|
|
536
|
+
}
|
|
537
|
+
if (result.status === "BLOCKED")
|
|
538
|
+
return 3;
|
|
539
|
+
if (result.status === "CONFLICT")
|
|
540
|
+
return 4;
|
|
541
|
+
return 0;
|
|
542
|
+
}
|
|
543
|
+
case "connect": {
|
|
544
|
+
const parsed = parseClientArguments(argv, io.cwd, "connect");
|
|
545
|
+
if (parsed === undefined) {
|
|
546
|
+
io.writeStderr(USAGE);
|
|
547
|
+
return 64;
|
|
548
|
+
}
|
|
549
|
+
const currentEntry = fileURLToPath(import.meta.url);
|
|
550
|
+
if (path.basename(currentEntry) !== "cli.js" ||
|
|
551
|
+
path.basename(path.dirname(currentEntry)) !== "dist") {
|
|
552
|
+
io.writeStderr("CONNECT_BUILD_REQUIRED: run `pnpm build` and invoke dist/cli.js.\n");
|
|
553
|
+
return 3;
|
|
554
|
+
}
|
|
555
|
+
const result = await connectClient(parsed.root, currentEntry, parsed.client, parsed.write);
|
|
556
|
+
if (parsed.client === "mcp") {
|
|
557
|
+
io.writeStdout(result.artifacts[0]?.content ?? "");
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
if (result.status === "CONFLICT") {
|
|
561
|
+
io.writeStderr(`CONFLICT: ${result.artifacts.map((artifact) => artifact.path).join(", ")} includes different operator-owned content; no files changed.\n`);
|
|
562
|
+
return 4;
|
|
563
|
+
}
|
|
564
|
+
for (const artifact of result.artifacts) {
|
|
565
|
+
io.writeStdout(`${result.status}: ${artifact.path}\n`);
|
|
566
|
+
if (result.status === "EMITTED") {
|
|
567
|
+
const label = artifact.kind.toUpperCase();
|
|
568
|
+
io.writeStdout(`--- BEGIN ASSERTLEDGER ${label} ---\n`);
|
|
569
|
+
io.writeStdout(artifact.content);
|
|
570
|
+
if (!artifact.content.endsWith("\n"))
|
|
571
|
+
io.writeStdout("\n");
|
|
572
|
+
io.writeStdout(`--- END ASSERTLEDGER ${label} ---\n`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (result.status === "EMITTED") {
|
|
576
|
+
io.writeStdout("No files changed. Re-run with --write to install these artifacts.\n");
|
|
577
|
+
}
|
|
578
|
+
return 0;
|
|
579
|
+
}
|
|
580
|
+
case "disconnect": {
|
|
581
|
+
const parsed = parseClientArguments(argv, io.cwd, "disconnect");
|
|
582
|
+
if (parsed === undefined || parsed.client === "mcp") {
|
|
583
|
+
io.writeStderr(USAGE);
|
|
584
|
+
return 64;
|
|
585
|
+
}
|
|
586
|
+
const currentEntry = fileURLToPath(import.meta.url);
|
|
587
|
+
if (path.basename(currentEntry) !== "cli.js" ||
|
|
588
|
+
path.basename(path.dirname(currentEntry)) !== "dist") {
|
|
589
|
+
io.writeStderr("CONNECT_BUILD_REQUIRED: run `pnpm build` and invoke dist/cli.js.\n");
|
|
590
|
+
return 3;
|
|
591
|
+
}
|
|
592
|
+
const result = await disconnectClient(parsed.root, currentEntry, parsed.client, parsed.write);
|
|
593
|
+
if (result.status === "CONFLICT") {
|
|
594
|
+
io.writeStderr(`CONFLICT: ${result.artifacts.map((artifact) => artifact.path).join(", ")} includes content AssertLedger does not own byte-for-byte; no files changed.\n`);
|
|
595
|
+
return 4;
|
|
596
|
+
}
|
|
597
|
+
for (const artifact of result.artifacts) {
|
|
598
|
+
io.writeStdout(`${result.status}: ${artifact.path}\n`);
|
|
599
|
+
}
|
|
600
|
+
if (result.status === "EMITTED") {
|
|
601
|
+
io.writeStdout("No files changed. Re-run with --write to remove only byte-identical AssertLedger artifacts.\n");
|
|
602
|
+
}
|
|
603
|
+
return 0;
|
|
604
|
+
}
|
|
605
|
+
case "analyze": {
|
|
606
|
+
const root = path.resolve(io.cwd, positional[1] ?? ".");
|
|
607
|
+
writeJson(io, await ledger.analyze(root));
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
case "init": {
|
|
611
|
+
const valueFlags = new Set([
|
|
612
|
+
"--adapter-config",
|
|
613
|
+
"--package-manager",
|
|
614
|
+
"--framework",
|
|
615
|
+
"--test-command-json",
|
|
616
|
+
]);
|
|
617
|
+
const booleanFlags = new Set(["--dry-run", "--json"]);
|
|
618
|
+
let rootArgument = ".";
|
|
619
|
+
let rootSeen = false;
|
|
620
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
621
|
+
const argument = argv[index] ?? "";
|
|
622
|
+
if (valueFlags.has(argument)) {
|
|
623
|
+
const value = argv[index + 1];
|
|
624
|
+
if (value === undefined || value.startsWith("--")) {
|
|
625
|
+
io.writeStderr(USAGE);
|
|
626
|
+
return 64;
|
|
627
|
+
}
|
|
628
|
+
index += 1;
|
|
629
|
+
}
|
|
630
|
+
else if (!booleanFlags.has(argument)) {
|
|
631
|
+
if (!argument.startsWith("--") && !rootSeen) {
|
|
632
|
+
rootArgument = argument;
|
|
633
|
+
rootSeen = true;
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
io.writeStderr(USAGE);
|
|
637
|
+
return 64;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const root = path.resolve(io.cwd, rootArgument);
|
|
642
|
+
const commandPath = optionalFlag(argv, "--test-command-json");
|
|
643
|
+
const adapterConfigPath = optionalFlag(argv, "--adapter-config");
|
|
644
|
+
const packageManager = optionalFlag(argv, "--package-manager");
|
|
645
|
+
const framework = optionalFlag(argv, "--framework");
|
|
646
|
+
const result = await ledger.init(root, {
|
|
647
|
+
dryRun: argv.includes("--dry-run"),
|
|
648
|
+
...(adapterConfigPath === undefined ? {} : { adapterConfigPath }),
|
|
649
|
+
...(packageManager === undefined ? {} : { packageManager }),
|
|
650
|
+
...(framework === undefined ? {} : { framework }),
|
|
651
|
+
...(commandPath === undefined
|
|
652
|
+
? {}
|
|
653
|
+
: {
|
|
654
|
+
testCommand: (await readJsonInput(commandPath, io)),
|
|
655
|
+
}),
|
|
656
|
+
});
|
|
657
|
+
writeJson(io, result);
|
|
658
|
+
if (result.status === "BLOCKED")
|
|
659
|
+
return 3;
|
|
660
|
+
if (result.status === "CONFLICT")
|
|
661
|
+
return 4;
|
|
662
|
+
return 0;
|
|
663
|
+
}
|
|
664
|
+
case "audit": {
|
|
665
|
+
const rootArgument = argv[1] && !argv[1].startsWith("--") ? argv[1] : ".";
|
|
666
|
+
const root = path.resolve(io.cwd, rootArgument);
|
|
667
|
+
const explicitRequest = optionalFlag(argv, "--verification-request");
|
|
668
|
+
let request;
|
|
669
|
+
if (explicitRequest !== undefined)
|
|
670
|
+
request = await readJsonInput(explicitRequest, io);
|
|
671
|
+
else {
|
|
672
|
+
try {
|
|
673
|
+
request = JSON.parse(await readFile(path.join(root, "assertledger.request.json"), "utf8"));
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
const code = error.code;
|
|
677
|
+
if (code !== "ENOENT")
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const result = await ledger.audit(root, {
|
|
682
|
+
noGit: argv.includes("--no-git"),
|
|
683
|
+
verificationRequest: request,
|
|
684
|
+
});
|
|
685
|
+
if (argv.includes("--emit-verification-request")) {
|
|
686
|
+
if (result.verificationRequest === null)
|
|
687
|
+
return 3;
|
|
688
|
+
writeJson(io, result.verificationRequest);
|
|
689
|
+
return 0;
|
|
690
|
+
}
|
|
691
|
+
writeJson(io, result);
|
|
692
|
+
return 0;
|
|
693
|
+
}
|
|
694
|
+
case "check": {
|
|
695
|
+
if (!argv.includes("--allow-unsafe-execution")) {
|
|
696
|
+
io.writeStderr("Refusing trusted-local execution without --allow-unsafe-execution.\n");
|
|
697
|
+
return 4;
|
|
698
|
+
}
|
|
699
|
+
const options = parseCheckArguments(argv, io.cwd);
|
|
700
|
+
const result = await ledger.checkGitRegression({
|
|
701
|
+
...options,
|
|
702
|
+
allowUnsafeExecution: true,
|
|
703
|
+
});
|
|
704
|
+
if (argv.includes("--json"))
|
|
705
|
+
writeJson(io, result);
|
|
706
|
+
else
|
|
707
|
+
io.writeStdout(renderGitRegressionSummary(result, { ...options, allowUnsafeExecution: true }));
|
|
708
|
+
return decisionExitCode(result);
|
|
709
|
+
}
|
|
710
|
+
case "schema": {
|
|
711
|
+
const name = positional[1];
|
|
712
|
+
if (name !== "verification-request" &&
|
|
713
|
+
name !== "repository-analysis" &&
|
|
714
|
+
name !== "repository-audit" &&
|
|
715
|
+
name !== "repository-init-config" &&
|
|
716
|
+
name !== "repository-init-lock" &&
|
|
717
|
+
name !== "repository-init-result" &&
|
|
718
|
+
name !== "evidence-manifest" &&
|
|
719
|
+
name !== "replay-result" &&
|
|
720
|
+
name !== "agentic-benchmark-request" &&
|
|
721
|
+
name !== "agentic-benchmark-artifact" &&
|
|
722
|
+
name !== "agentic-benchmark-replay-result" &&
|
|
723
|
+
name !== "agentic-benchmark-acquisition-request" &&
|
|
724
|
+
name !== "agentic-benchmark-acquisition-result" &&
|
|
725
|
+
name !== "agentic-benchmark-acquisition-replay-result" &&
|
|
726
|
+
name !== "agentic-profile-request" &&
|
|
727
|
+
name !== "agentic-profile-report" &&
|
|
728
|
+
name !== "agentic-profile-replay-result" &&
|
|
729
|
+
name !== "agentic-profile-request-v2" &&
|
|
730
|
+
name !== "agentic-profile-report-v2" &&
|
|
731
|
+
name !== "agentic-profile-replay-result-v2" &&
|
|
732
|
+
name !== "agentic-corpus-allocation-request" &&
|
|
733
|
+
name !== "agentic-corpus-allocation" &&
|
|
734
|
+
name !== "agentic-corpus-allocation-replay-result" &&
|
|
735
|
+
name !== "agentic-corpus-allocation-commitment" &&
|
|
736
|
+
name !== "agentic-corpus-allocation-reveal" &&
|
|
737
|
+
name !== "agentic-corpus-allocation-commitment-replay-result" &&
|
|
738
|
+
name !== "agentic-corpus-experiment-plan" &&
|
|
739
|
+
name !== "agentic-corpus-experiment-plan-replay-result" &&
|
|
740
|
+
name !== "agentic-corpus-experiment-request" &&
|
|
741
|
+
name !== "agentic-corpus-experiment-artifact" &&
|
|
742
|
+
name !== "agentic-corpus-experiment-replay-request" &&
|
|
743
|
+
name !== "agentic-corpus-experiment-replay-result") {
|
|
744
|
+
io.writeStderr(USAGE);
|
|
745
|
+
return 64;
|
|
746
|
+
}
|
|
747
|
+
writeJson(io, ledger.schema(name));
|
|
748
|
+
return 0;
|
|
749
|
+
}
|
|
750
|
+
case "verify": {
|
|
751
|
+
const request = await readJsonInput(positional[1], io);
|
|
752
|
+
if (!argv.includes("--allow-unsafe-execution")) {
|
|
753
|
+
io.writeStderr("Refusing trusted-local execution without --allow-unsafe-execution.\n");
|
|
754
|
+
return 4;
|
|
755
|
+
}
|
|
756
|
+
const result = await ledger.verify(authorizeTrustedLocalExecution(request));
|
|
757
|
+
writeJson(io, result);
|
|
758
|
+
return decisionExitCode(result);
|
|
759
|
+
}
|
|
760
|
+
case "replay": {
|
|
761
|
+
const result = ledger.replay(await readJsonInput(positional[1], io));
|
|
762
|
+
writeJson(io, result);
|
|
763
|
+
return result.valid ? 0 : 4;
|
|
764
|
+
}
|
|
765
|
+
case "profile": {
|
|
766
|
+
const result = ledger.profile(await readJsonInput(positional[1], io));
|
|
767
|
+
writeJson(io, result);
|
|
768
|
+
return profileExitCode(result);
|
|
769
|
+
}
|
|
770
|
+
case "profile-replay": {
|
|
771
|
+
const result = ledger.replayProfile(await readJsonInput(positional[1], io));
|
|
772
|
+
writeJson(io, result);
|
|
773
|
+
return result.valid ? 0 : 4;
|
|
774
|
+
}
|
|
775
|
+
case "profile-v2": {
|
|
776
|
+
const result = ledger.profileV2(await readJsonInput(positional[1], io));
|
|
777
|
+
writeJson(io, result);
|
|
778
|
+
return profileExitCode(result);
|
|
779
|
+
}
|
|
780
|
+
case "profile-v2-replay": {
|
|
781
|
+
const result = ledger.replayProfileV2(await readJsonInput(positional[1], io));
|
|
782
|
+
writeJson(io, result);
|
|
783
|
+
return result.valid ? 0 : 4;
|
|
784
|
+
}
|
|
785
|
+
case "benchmark": {
|
|
786
|
+
const result = ledger.benchmark(await readJsonInput(positional[1], io));
|
|
787
|
+
writeJson(io, result);
|
|
788
|
+
return benchmarkExitCode(result);
|
|
789
|
+
}
|
|
790
|
+
case "benchmark-replay": {
|
|
791
|
+
const result = ledger.replayBenchmark(await readJsonInput(positional[1], io));
|
|
792
|
+
writeJson(io, result);
|
|
793
|
+
return result.valid ? 0 : 4;
|
|
794
|
+
}
|
|
795
|
+
case "benchmark-acquire": {
|
|
796
|
+
const request = await readJsonInput(positional[1], io);
|
|
797
|
+
if (!argv.includes("--allow-unsafe-execution")) {
|
|
798
|
+
io.writeStderr("Refusing trusted-local execution without --allow-unsafe-execution.\n");
|
|
799
|
+
return 4;
|
|
800
|
+
}
|
|
801
|
+
const result = await ledger.acquireBenchmark(authorizeBenchmarkAcquisition(request));
|
|
802
|
+
writeJson(io, result);
|
|
803
|
+
return benchmarkAcquisitionExitCode(result);
|
|
804
|
+
}
|
|
805
|
+
case "benchmark-acquire-replay": {
|
|
806
|
+
const result = ledger.replayBenchmarkAcquisition(await readJsonInput(positional[1], io));
|
|
807
|
+
writeJson(io, result);
|
|
808
|
+
return result.valid ? 0 : 4;
|
|
809
|
+
}
|
|
810
|
+
case "corpus-allocate": {
|
|
811
|
+
const result = ledger.allocateCorpus(await readJsonInput(positional[1], io));
|
|
812
|
+
writeJson(io, result);
|
|
813
|
+
return 0;
|
|
814
|
+
}
|
|
815
|
+
case "corpus-allocation-replay": {
|
|
816
|
+
const result = ledger.replayCorpusAllocation(await readJsonInput(positional[1], io));
|
|
817
|
+
writeJson(io, result);
|
|
818
|
+
return result.valid ? 0 : 4;
|
|
819
|
+
}
|
|
820
|
+
case "corpus-experiment-replay": {
|
|
821
|
+
const artifact = await readJsonInput(positional[1], io);
|
|
822
|
+
const trustPolicy = await readJsonInput(requiredFlag(argv, "--trust-policy"), io);
|
|
823
|
+
const allocationCommitment = await readJsonInput(requiredFlag(argv, "--allocation-commitment"), io);
|
|
824
|
+
const allocationReveal = await readJsonInput(requiredFlag(argv, "--allocation-reveal"), io);
|
|
825
|
+
const allocation = await readJsonInput(requiredFlag(argv, "--allocation"), io);
|
|
826
|
+
const experimentPlan = await readJsonInput(requiredFlag(argv, "--experiment-plan"), io);
|
|
827
|
+
const subjectEvidence = await readJsonInput(requiredFlag(argv, "--subject-evidence"), io);
|
|
828
|
+
if (!Array.isArray(subjectEvidence))
|
|
829
|
+
throw new TypeError("SUBJECT_EVIDENCE_INVALID");
|
|
830
|
+
const evidenceContents = evidenceContentMap(await readJsonInput(requiredFlag(argv, "--evidence-contents"), io));
|
|
831
|
+
const result = ledger.replayCorpusExperiment(artifact, {
|
|
832
|
+
expectedTrustPolicyDigest: requiredFlag(argv, "--trust-policy-digest"),
|
|
833
|
+
expectedAllocationCommitmentDigest: requiredFlag(argv, "--allocation-commitment-digest"),
|
|
834
|
+
expectedExperimentPlanDigest: requiredFlag(argv, "--experiment-plan-digest"),
|
|
835
|
+
trustPolicy,
|
|
836
|
+
allocationCommitment,
|
|
837
|
+
allocationReveal,
|
|
838
|
+
allocation,
|
|
839
|
+
experimentPlan,
|
|
840
|
+
subjectEvidence,
|
|
841
|
+
evidenceContents,
|
|
842
|
+
});
|
|
843
|
+
writeJson(io, result);
|
|
844
|
+
if (!result.valid)
|
|
845
|
+
return 4;
|
|
846
|
+
const parsedArtifact = artifact;
|
|
847
|
+
switch (parsedArtifact.result?.status) {
|
|
848
|
+
case "SUPPORTED":
|
|
849
|
+
return 0;
|
|
850
|
+
case "NOT_SUPPORTED":
|
|
851
|
+
return 2;
|
|
852
|
+
case "INSUFFICIENT":
|
|
853
|
+
return 3;
|
|
854
|
+
default:
|
|
855
|
+
return 4;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
case "corpus-status":
|
|
859
|
+
return runCorpusCommand("STATUS", path.resolve(io.cwd, positional[1] ?? "benchmarks/agentic-profile"), io);
|
|
860
|
+
case "corpus-evaluate-public":
|
|
861
|
+
return runCorpusCommand("PUBLIC", path.resolve(io.cwd, positional[1] ?? "benchmarks/agentic-profile"), io);
|
|
862
|
+
case "corpus-evaluate-holdout":
|
|
863
|
+
return runCorpusCommand("HOLDOUT", path.resolve(io.cwd, positional[1] ?? "benchmarks/agentic-profile"), io);
|
|
864
|
+
case "mcp": {
|
|
865
|
+
const allowedFlags = new Set(["--allow-unsafe-execution", "--root"]);
|
|
866
|
+
let rootSeen = false;
|
|
867
|
+
let unsafeSeen = false;
|
|
868
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
869
|
+
const argument = argv[index] ?? "";
|
|
870
|
+
if (!allowedFlags.has(argument)) {
|
|
871
|
+
io.writeStderr(USAGE);
|
|
872
|
+
return 64;
|
|
873
|
+
}
|
|
874
|
+
if (argument === "--root") {
|
|
875
|
+
if (rootSeen) {
|
|
876
|
+
io.writeStderr(USAGE);
|
|
877
|
+
return 64;
|
|
878
|
+
}
|
|
879
|
+
const value = argv[index + 1];
|
|
880
|
+
if (value === undefined || value.startsWith("-")) {
|
|
881
|
+
io.writeStderr(USAGE);
|
|
882
|
+
return 64;
|
|
883
|
+
}
|
|
884
|
+
rootSeen = true;
|
|
885
|
+
index += 1;
|
|
886
|
+
}
|
|
887
|
+
else if (unsafeSeen) {
|
|
888
|
+
io.writeStderr(USAGE);
|
|
889
|
+
return 64;
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
unsafeSeen = true;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const requestedRoot = optionalFlag(argv, "--root");
|
|
896
|
+
const root = requestedRoot === undefined
|
|
897
|
+
? io.cwd
|
|
898
|
+
: await realpath(path.resolve(io.cwd, requestedRoot));
|
|
899
|
+
if (!(await stat(root)).isDirectory())
|
|
900
|
+
throw new Error("MCP_ROOT_NOT_DIRECTORY");
|
|
901
|
+
serveStdio(() => createAssertLedgerServer({
|
|
902
|
+
allowUnsafeExecution: argv.includes("--allow-unsafe-execution"),
|
|
903
|
+
allowedRepositoryRoots: [root],
|
|
904
|
+
}), {
|
|
905
|
+
onerror(error) {
|
|
906
|
+
io.writeStderr(`AssertLedger MCP error: ${error.message}\n`);
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
return 0;
|
|
910
|
+
}
|
|
911
|
+
default:
|
|
912
|
+
io.writeStderr(USAGE);
|
|
913
|
+
return 64;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
io.writeStderr(`${errorMessage(error)}\n`);
|
|
918
|
+
return classifyError(error);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const defaultIo = {
|
|
922
|
+
cwd: process.cwd(),
|
|
923
|
+
readStdin: async () => {
|
|
924
|
+
const chunks = [];
|
|
925
|
+
let bytes = 0;
|
|
926
|
+
for await (const chunk of process.stdin) {
|
|
927
|
+
const buffer = Buffer.from(chunk);
|
|
928
|
+
bytes += buffer.length;
|
|
929
|
+
if (bytes > MAXIMUM_JSON_INPUT_BYTES)
|
|
930
|
+
throw new TypeError("JSON_INPUT_TOO_LARGE");
|
|
931
|
+
chunks.push(buffer);
|
|
932
|
+
}
|
|
933
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
934
|
+
},
|
|
935
|
+
writeStdout: (text) => process.stdout.write(text),
|
|
936
|
+
writeStderr: (text) => process.stderr.write(text),
|
|
937
|
+
};
|
|
938
|
+
async function isDirectInvocation(moduleUrl, entryPath) {
|
|
939
|
+
if (entryPath === undefined)
|
|
940
|
+
return false;
|
|
941
|
+
try {
|
|
942
|
+
return (await realpath(fileURLToPath(moduleUrl))) === (await realpath(path.resolve(entryPath)));
|
|
943
|
+
}
|
|
944
|
+
catch {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (await isDirectInvocation(import.meta.url, process.argv[1])) {
|
|
949
|
+
process.exitCode = await runCli(process.argv.slice(2), defaultIo);
|
|
950
|
+
}
|
|
951
|
+
//# sourceMappingURL=cli.js.map
|