witnora 0.8.1 → 0.9.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/README.md +275 -12
- package/dist/artifact-manifest.js +37 -0
- package/dist/artifact-validation.js +102 -0
- package/dist/badge.js +31 -0
- package/dist/browser-adapter.js +156 -0
- package/dist/bundle.js +100 -0
- package/dist/cli.js +971 -0
- package/dist/command-help.js +71 -0
- package/dist/companion-artifacts.js +170 -0
- package/dist/conformance.js +115 -0
- package/dist/control-plane.js +409 -0
- package/dist/corpus-governance.js +126 -0
- package/dist/corpus-store.js +239 -0
- package/dist/corpus.js +438 -0
- package/dist/credentials.js +127 -0
- package/dist/evidence-signing.js +85 -0
- package/dist/failure-review.js +188 -0
- package/dist/guided-setup.js +101 -0
- package/dist/index.js +16 -0
- package/dist/lab.js +323 -0
- package/dist/local-server.js +491 -0
- package/dist/monitor.js +100 -0
- package/dist/normalizers.js +265 -0
- package/dist/onboarding-templates.js +158 -0
- package/dist/release-gate.js +457 -0
- package/dist/report.js +165 -0
- package/dist/runner.js +349 -0
- package/dist/sandbox.js +424 -0
- package/dist/schema-validator.js +503 -0
- package/dist/try.js +135 -0
- package/dist/types.js +3 -0
- package/dist/vendor/onegent-runtime/browser-adapter-kit.d.ts +107 -0
- package/dist/vendor/onegent-runtime/browser-adapter-kit.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/browser-adapter-kit.js +187 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts +183 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.js +344 -0
- package/dist/vendor/onegent-runtime/controlled-adapter.d.ts +6 -0
- package/dist/vendor/onegent-runtime/controlled-adapter.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/controlled-adapter.js +40 -0
- package/dist/vendor/onegent-runtime/mock-procurement.d.ts +6 -0
- package/dist/vendor/onegent-runtime/mock-procurement.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/mock-procurement.js +33 -0
- package/dist/vendor/onegent-runtime/policies.d.ts +6 -0
- package/dist/vendor/onegent-runtime/policies.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/policies.js +109 -0
- package/dist/vendor/onegent-runtime/risk.d.ts +3 -0
- package/dist/vendor/onegent-runtime/risk.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/risk.js +54 -0
- package/dist/vendor/onegent-runtime/sandbox-adapter-kit.d.ts +45 -0
- package/dist/vendor/onegent-runtime/sandbox-adapter-kit.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/sandbox-adapter-kit.js +148 -0
- package/dist/vendor/onegent-runtime/sandbox-harness.d.ts +167 -0
- package/dist/vendor/onegent-runtime/sandbox-harness.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/sandbox-harness.js +729 -0
- package/dist/vendor/onegent-runtime/sandbox-hosted.d.ts +77 -0
- package/dist/vendor/onegent-runtime/sandbox-hosted.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/sandbox-hosted.js +152 -0
- package/dist/vendor/onegent-runtime/sdk.d.ts +29 -0
- package/dist/vendor/onegent-runtime/sdk.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/sdk.js +140 -0
- package/dist/vendor/onegent-runtime/service.d.ts +27 -0
- package/dist/vendor/onegent-runtime/service.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/service.js +423 -0
- package/dist/vendor/onegent-runtime/store.d.ts +16 -0
- package/dist/vendor/onegent-runtime/store.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/store.js +29 -0
- package/dist/vendor/onegent-runtime/stripe-test-readonly.d.ts +79 -0
- package/dist/vendor/onegent-runtime/stripe-test-readonly.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/stripe-test-readonly.js +163 -0
- package/dist/vendor/onegent-runtime/trust-crypto.d.ts +11 -0
- package/dist/vendor/onegent-runtime/trust-crypto.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/trust-crypto.js +70 -0
- package/dist/vendor/onegent-runtime/trust-types.d.ts +163 -0
- package/dist/vendor/onegent-runtime/trust-types.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/trust-types.js +7 -0
- package/dist/vendor/onegent-runtime/trusted-recorder.d.ts +49 -0
- package/dist/vendor/onegent-runtime/trusted-recorder.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/trusted-recorder.js +347 -0
- package/dist/vendor/onegent-runtime/types.d.ts +287 -0
- package/dist/vendor/onegent-runtime/types.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/types.js +1 -0
- package/dist/vendor/onegent-runtime/vendor-sandbox-egress.d.ts +49 -0
- package/dist/vendor/onegent-runtime/vendor-sandbox-egress.d.ts.map +1 -0
- package/dist/vendor/onegent-runtime/vendor-sandbox-egress.js +152 -0
- package/package.json +33 -13
- package/bin/witnora.js +0 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { validateEvidenceArtifacts } from "./artifact-validation.js";
|
|
6
|
+
import { renderAgentCertBadge } from "./badge.js";
|
|
7
|
+
import { buildEvidenceBundle } from "./bundle.js";
|
|
8
|
+
import { collectCompanionArtifacts, MAX_REPORTED_COMPANION_ARTIFACT_SKIPS } from "./companion-artifacts.js";
|
|
9
|
+
import { recordsFromAgentCertResult, evaluateFailureClassifier, renderCorpusSummary, summarizeCorpus, writeReviewedFailureDataset, } from "./corpus.js";
|
|
10
|
+
import { openCorpusStore, parseCorpusStoreKind } from "./corpus-store.js";
|
|
11
|
+
import { deleteCorpusRecords, exportGovernedCorpus, governCorpusRecords, parseCorpusConsent } from "./corpus-governance.js";
|
|
12
|
+
import { pushEvidenceToControlPlane, requireContinuousAssuranceCurrent, verifyControlPlaneConnection } from "./control-plane.js";
|
|
13
|
+
import { runEvidenceConformance } from "./conformance.js";
|
|
14
|
+
import { DEFAULT_WITNORA_SERVER, resolveConnection, saveConnection } from "./credentials.js";
|
|
15
|
+
import { applyFailureReviews, appendFailureReview, createFailureReview, findFailurePattern, parseFailureReviewStatus, parseFailureType, parseReviewConfidence, readFailureReviews, } from "./failure-review.js";
|
|
16
|
+
import { buildMonitorSnapshot, writeMonitorSnapshot } from "./monitor.js";
|
|
17
|
+
import { normalizeMcpBenchResult, normalizeOnegentAuditPacket, normalizeTripwireResult } from "./normalizers.js";
|
|
18
|
+
import { renderHtmlReport, renderMarkdownReport } from "./report.js";
|
|
19
|
+
import { generateEvidenceSigningKeyPair, signEvidenceFile, verifyEvidenceFile, } from "./evidence-signing.js";
|
|
20
|
+
import { buildReleaseGateReport, renderReleaseGateSummary, writeReleaseGateArtifacts, } from "./release-gate.js";
|
|
21
|
+
import { serveAgentCertMonitor } from "./local-server.js";
|
|
22
|
+
import { parseSchemaId, validateAgentCertSchema } from "./schema-validator.js";
|
|
23
|
+
import { applyRunOverrides, loadRunProfile, profileFromArtifactFlags, renderRunSummary, runAgentCertProfile, } from "./runner.js";
|
|
24
|
+
import { buildRobustnessLabSnapshot, readRobustnessLabConfig, renderRobustnessLabSummary, writeRobustnessLabSnapshot } from "./lab.js";
|
|
25
|
+
import { renderCommandHelp } from "./command-help.js";
|
|
26
|
+
import { runSandboxCommand } from "./sandbox.js";
|
|
27
|
+
import { runBrowserAdapterCommand } from "./browser-adapter.js";
|
|
28
|
+
import { parseAgentTemplate, starterAdapter, starterGitHubActionWorkflow, starterInstructions, starterProfile, starterTripwireConfig, } from "./onboarding-templates.js";
|
|
29
|
+
import { renderTrySummary, writeTryEvidence } from "./try.js";
|
|
30
|
+
import { applyGuidedSetup } from "./guided-setup.js";
|
|
31
|
+
process.on("uncaughtException", reportFatalError);
|
|
32
|
+
process.on("unhandledRejection", reportFatalError);
|
|
33
|
+
const command = process.argv[2] ?? "help";
|
|
34
|
+
const commandHelp = process.argv.some((argument) => argument === "--help" || argument === "-h")
|
|
35
|
+
? renderCommandHelp(command)
|
|
36
|
+
: undefined;
|
|
37
|
+
if (commandHelp) {
|
|
38
|
+
process.stdout.write(commandHelp);
|
|
39
|
+
}
|
|
40
|
+
else if (command === "try") {
|
|
41
|
+
const template = parseAgentTemplate(readFlag("--template") ?? "workflow");
|
|
42
|
+
const subject = readFlag("--subject") ?? `witnora-sample-${template}`;
|
|
43
|
+
const outcome = await writeTryEvidence({
|
|
44
|
+
template,
|
|
45
|
+
subject,
|
|
46
|
+
outDir: readFlag("--out") ?? ".witnora/try",
|
|
47
|
+
});
|
|
48
|
+
process.stdout.write(renderTrySummary(outcome));
|
|
49
|
+
if (readBoolFlag("--push")) {
|
|
50
|
+
await pushHostedEvidence(outcome.bundle, Buffer.from(`${JSON.stringify(outcome.bundle, null, 2)}\n`), "agentcert-evidence.json");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (command === "init") {
|
|
54
|
+
const outPath = resolve(readFlag("--out") ?? "witnora.config.json");
|
|
55
|
+
const tripwireConfigPath = resolve(readFlag("--tripwire-config") ?? "tripwire.yml");
|
|
56
|
+
const githubWorkflowPath = resolve(readFlag("--github-action-out") ?? ".github/workflows/witnora-tripwire.yml");
|
|
57
|
+
const adapterPath = resolve(readFlag("--adapter-out") ?? "witnora.adapter.mjs");
|
|
58
|
+
const template = parseAgentTemplate(readFlag("--template"));
|
|
59
|
+
const subject = readFlag("--subject") ?? `my-${template}-agent`;
|
|
60
|
+
const force = readBoolFlag("--force");
|
|
61
|
+
const writeGitHubAction = (readBoolFlag("--github-action") || process.argv.includes("--github-action-out")) && !readBoolFlag("--skip-github-action");
|
|
62
|
+
const config = starterProfile(template, subject);
|
|
63
|
+
await writeStarterFile(outPath, `${JSON.stringify(config, null, 2)}\n`, force);
|
|
64
|
+
process.stdout.write(`Wrote ${outPath}\n`);
|
|
65
|
+
if (template === "browser" && !readBoolFlag("--skip-tripwire")) {
|
|
66
|
+
await writeStarterFile(tripwireConfigPath, starterTripwireConfig(subject), force);
|
|
67
|
+
process.stdout.write(`Wrote ${tripwireConfigPath}\n`);
|
|
68
|
+
}
|
|
69
|
+
if (template === "browser" && writeGitHubAction) {
|
|
70
|
+
await writeStarterFile(githubWorkflowPath, starterGitHubActionWorkflow(subject), force);
|
|
71
|
+
process.stdout.write(`Wrote ${githubWorkflowPath}\n`);
|
|
72
|
+
}
|
|
73
|
+
if (template === "coding" || template === "workflow" || template === "data") {
|
|
74
|
+
await writeStarterFile(adapterPath, starterAdapter(template, subject), force);
|
|
75
|
+
process.stdout.write(`Wrote ${adapterPath}\n`);
|
|
76
|
+
}
|
|
77
|
+
process.stdout.write(starterInstructions(template, subject));
|
|
78
|
+
}
|
|
79
|
+
else if (command === "connect") {
|
|
80
|
+
if (readBoolFlag("--help")) {
|
|
81
|
+
process.stdout.write(`Usage:
|
|
82
|
+
witnora connect --server https://witnora.com --project <project-id>
|
|
83
|
+
witnora connect --name staging --server https://staging.example.com --project <project-id>
|
|
84
|
+
|
|
85
|
+
The API key is read from WITNORA_API_KEY (or legacy AGENTCERT_API_KEY) or requested with hidden input.
|
|
86
|
+
Saved connections are reused by witnora push and witnora run --push.
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const name = readFlag("--name") ?? "default";
|
|
91
|
+
const server = readFlag("--server") ?? brandedEnvironment("BASE_URL") ?? DEFAULT_WITNORA_SERVER;
|
|
92
|
+
const projectId = readFlag("--project") ?? brandedEnvironment("PROJECT_ID") ?? await promptValue("Project ID: ");
|
|
93
|
+
const apiKey = readFlag("--api-key") ?? brandedEnvironment("API_KEY") ?? await promptSecret("Project API key: ");
|
|
94
|
+
const connection = await resolveConnection({ server, projectId, apiKey, env: {} });
|
|
95
|
+
const verified = await verifyControlPlaneConnection({
|
|
96
|
+
baseUrl: connection.server,
|
|
97
|
+
projectId: connection.projectId,
|
|
98
|
+
apiKey: connection.apiKey,
|
|
99
|
+
});
|
|
100
|
+
const path = await saveConnection(name, connection);
|
|
101
|
+
process.stdout.write(`Connected ${JSON.stringify(name)} to ${connection.server}\n`);
|
|
102
|
+
process.stdout.write(`Project: ${verified.projectId} (${verified.runs} runs, ${verified.evidence} evidence objects)\n`);
|
|
103
|
+
process.stdout.write(`Credentials: ${path}\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (command === "setup") {
|
|
107
|
+
const action = process.argv[3] ?? "help";
|
|
108
|
+
if (action !== "apply")
|
|
109
|
+
throw new Error("Use witnora setup apply --plan <setup-plan.json> --repo <directory>.");
|
|
110
|
+
const planPath = readFlag("--plan");
|
|
111
|
+
if (!planPath)
|
|
112
|
+
throw new Error("--plan is required.");
|
|
113
|
+
const result = await applyGuidedSetup({
|
|
114
|
+
planPath,
|
|
115
|
+
repository: readFlag("--repo") ?? process.cwd(),
|
|
116
|
+
force: readBoolFlag("--force"),
|
|
117
|
+
githubPullRequest: readBoolFlag("--github-pr"),
|
|
118
|
+
configureCi: readBoolFlag("--configure-ci"),
|
|
119
|
+
});
|
|
120
|
+
process.stdout.write(`Applied ${result.template} setup plan ${result.planId}.\n`);
|
|
121
|
+
for (const file of result.files)
|
|
122
|
+
process.stdout.write(`Wrote ${file}\n`);
|
|
123
|
+
if (result.pullRequestUrl)
|
|
124
|
+
process.stdout.write(`Pull request: ${result.pullRequestUrl}\n`);
|
|
125
|
+
if (result.ciConfigured)
|
|
126
|
+
process.stdout.write("Configured WITNORA_PROJECT_ID and WITNORA_API_KEY for GitHub Actions.\n");
|
|
127
|
+
else
|
|
128
|
+
process.stdout.write("Review the generated files, store WITNORA_API_KEY in your secret manager, then run the generated workflow. Legacy AGENTCERT_* names remain supported.\n");
|
|
129
|
+
}
|
|
130
|
+
else if (command === "sandbox") {
|
|
131
|
+
const result = await runSandboxCommand(process.argv.slice(3));
|
|
132
|
+
process.exitCode = result.exitCode;
|
|
133
|
+
}
|
|
134
|
+
else if (command === "browser-adapter") {
|
|
135
|
+
const result = await runBrowserAdapterCommand(process.argv.slice(3));
|
|
136
|
+
process.exitCode = result.exitCode;
|
|
137
|
+
}
|
|
138
|
+
else if (command === "report") {
|
|
139
|
+
const config = await loadConfig(readFlag("--config"));
|
|
140
|
+
const subject = readFlag("--subject") ?? config?.subject.name ?? "agentcert-subject";
|
|
141
|
+
const subjectType = readFlag("--subject-type") ?? config?.subject.type ?? "agent";
|
|
142
|
+
const outDir = resolve(readFlag("--out") ?? config?.outputDir ?? ".witnora/latest");
|
|
143
|
+
const loaded = await loadArtifactResults(config);
|
|
144
|
+
const results = loaded.map((artifact) => artifact.result);
|
|
145
|
+
if (results.length === 0) {
|
|
146
|
+
throw new Error("No input artifacts were provided. Use --mcpbench, --tripwire, --onegent, or --config.");
|
|
147
|
+
}
|
|
148
|
+
const bundle = buildEvidenceBundle(results, subject, subjectType);
|
|
149
|
+
await mkdir(outDir, { recursive: true });
|
|
150
|
+
await writeFile(`${outDir}/agentcert-evidence.json`, `${JSON.stringify(bundle, null, 2)}\n`);
|
|
151
|
+
await writeFile(`${outDir}/agentcert-report.md`, renderMarkdownReport(bundle));
|
|
152
|
+
await writeFile(`${outDir}/agentcert-report.html`, renderHtmlReport(bundle));
|
|
153
|
+
await writeFile(`${outDir}/badge.svg`, renderAgentCertBadge(bundle));
|
|
154
|
+
process.stdout.write(`Wrote ${outDir}\\agentcert-evidence.json\n`);
|
|
155
|
+
process.stdout.write(`Wrote ${outDir}\\agentcert-report.md\n`);
|
|
156
|
+
process.stdout.write(`Wrote ${outDir}\\agentcert-report.html\n`);
|
|
157
|
+
process.stdout.write(`Wrote ${outDir}\\badge.svg\n`);
|
|
158
|
+
process.exitCode = bundle.verdict.passed ? 0 : 1;
|
|
159
|
+
}
|
|
160
|
+
else if (command === "corpus") {
|
|
161
|
+
const action = process.argv[3] ?? "help";
|
|
162
|
+
if (action === "ingest") {
|
|
163
|
+
const config = await loadConfig(readFlag("--config"));
|
|
164
|
+
const subject = readFlag("--subject") ?? config?.subject.name ?? "agentcert-subject";
|
|
165
|
+
const storeOptions = readCorpusStoreOptions(".witnora/corpus/corpus.jsonl", readFlag("--out"));
|
|
166
|
+
const replace = readBoolFlag("--replace");
|
|
167
|
+
const loaded = await loadArtifactResults(config);
|
|
168
|
+
if (loaded.length === 0) {
|
|
169
|
+
throw new Error("No input artifacts were provided. Use --mcpbench, --tripwire, --onegent, or --config.");
|
|
170
|
+
}
|
|
171
|
+
const reviews = await readFailureReviews(readReviewsPath());
|
|
172
|
+
const records = governCorpusRecords(applyFailureReviews(loaded.flatMap(({ result, path, raw }) => recordsFromAgentCertResult(result, path, subject, raw)), reviews), { consent: parseCorpusConsent(readFlag("--consent")), consentSource: readFlag("--consent-source") ?? "local-default" });
|
|
173
|
+
const store = await openCorpusStore(storeOptions);
|
|
174
|
+
try {
|
|
175
|
+
await store.append(records, { replace });
|
|
176
|
+
process.stdout.write(`Wrote ${records.length} corpus records to ${store.description}\n`);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
await store.close();
|
|
180
|
+
}
|
|
181
|
+
const summary = summarizeCorpus(records);
|
|
182
|
+
process.stdout.write(renderCorpusSummary(summary));
|
|
183
|
+
}
|
|
184
|
+
else if (action === "summary") {
|
|
185
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
186
|
+
try {
|
|
187
|
+
const records = applyFailureReviews(await store.readAll(), await readFailureReviews(readReviewsPath()));
|
|
188
|
+
process.stdout.write(renderCorpusSummary(summarizeCorpus(records)));
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await store.close();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (action === "metrics") {
|
|
195
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
196
|
+
try {
|
|
197
|
+
const records = applyFailureReviews(await store.readAll(), await readFailureReviews(readReviewsPath()));
|
|
198
|
+
const summary = summarizeCorpus(records);
|
|
199
|
+
process.stdout.write(`${JSON.stringify(summary.taxonomy, null, 2)}\n`);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
await store.close();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (action === "export-reviewed") {
|
|
206
|
+
const outPath = readFlag("--out") ?? ".witnora/latest/reviewed-failure-dataset.jsonl";
|
|
207
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
208
|
+
try {
|
|
209
|
+
const records = applyFailureReviews(await store.readAll(), await readFailureReviews(readReviewsPath()));
|
|
210
|
+
const rows = await writeReviewedFailureDataset(outPath, records);
|
|
211
|
+
process.stdout.write(`Wrote ${rows.length} reviewed failure rows to ${resolve(outPath)}\n`);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
await store.close();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (action === "export") {
|
|
218
|
+
const outPath = readFlag("--out") ?? ".witnora/latest/governed-corpus.jsonl";
|
|
219
|
+
const allowed = (readFlag("--consent") ?? "public,anonymous").split(",").map((item) => parseCorpusConsent(item.trim()));
|
|
220
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
221
|
+
try {
|
|
222
|
+
const records = exportGovernedCorpus(await store.readAll(), allowed);
|
|
223
|
+
await mkdir(dirname(resolve(outPath)), { recursive: true });
|
|
224
|
+
await writeFile(outPath, records.map((record) => JSON.stringify(record)).join("\n") + (records.length ? "\n" : ""));
|
|
225
|
+
process.stdout.write(`Exported ${records.length} consented corpus records to ${resolve(outPath)}\n`);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
await store.close();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else if (action === "delete") {
|
|
232
|
+
const ids = requiredFlag("--record-id").split(",").map((item) => item.trim()).filter(Boolean);
|
|
233
|
+
const reason = requiredFlag("--reason");
|
|
234
|
+
const journalPath = readFlag("--journal") ?? ".witnora/corpus/deletion-journal.jsonl";
|
|
235
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
236
|
+
try {
|
|
237
|
+
const result = deleteCorpusRecords(await store.readAll(), ids, reason);
|
|
238
|
+
if (!result.tombstones.length)
|
|
239
|
+
throw new Error("No corpus records matched --record-id; nothing was deleted.");
|
|
240
|
+
await store.append(result.retained, { replace: true });
|
|
241
|
+
await mkdir(dirname(resolve(journalPath)), { recursive: true });
|
|
242
|
+
await appendFile(journalPath, result.tombstones.map((item) => JSON.stringify(item)).join("\n") + "\n");
|
|
243
|
+
process.stdout.write(`Deleted ${result.tombstones.length} corpus records and wrote non-identifying tombstones to ${resolve(journalPath)}\n`);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
await store.close();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (action === "classifier-eval") {
|
|
250
|
+
const outPath = readFlag("--out");
|
|
251
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
252
|
+
try {
|
|
253
|
+
const records = applyFailureReviews(await store.readAll(), await readFailureReviews(readReviewsPath()));
|
|
254
|
+
const evaluation = `${JSON.stringify(evaluateFailureClassifier(records), null, 2)}\n`;
|
|
255
|
+
if (outPath) {
|
|
256
|
+
await mkdir(dirname(resolve(outPath)), { recursive: true });
|
|
257
|
+
await writeFile(outPath, evaluation);
|
|
258
|
+
process.stdout.write(`Wrote classifier evaluation to ${resolve(outPath)}\n`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
process.stdout.write(evaluation);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
await store.close();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (action === "review") {
|
|
269
|
+
const storeOptions = readCorpusStoreOptions(".witnora/corpus/corpus.jsonl");
|
|
270
|
+
const reviewPath = readReviewsPath() ?? ".witnora/corpus/failure-reviews.jsonl";
|
|
271
|
+
const store = await openCorpusStore(storeOptions);
|
|
272
|
+
try {
|
|
273
|
+
const records = await store.readAll();
|
|
274
|
+
const target = {
|
|
275
|
+
patternKey: requiredFlag("--pattern-key"),
|
|
276
|
+
recordId: readFlag("--record-id"),
|
|
277
|
+
runId: readFlag("--run-id"),
|
|
278
|
+
product: readFlag("--product"),
|
|
279
|
+
scenarioName: readFlag("--scenario"),
|
|
280
|
+
faultName: readFlag("--fault"),
|
|
281
|
+
};
|
|
282
|
+
const matched = findFailurePattern(records, target);
|
|
283
|
+
const type = parseFailureType(readFlag("--type"));
|
|
284
|
+
const suggestedType = matched?.pattern.suggestedType ?? matched?.pattern.type;
|
|
285
|
+
const status = parseFailureReviewStatus(readFlag("--status"), type, suggestedType);
|
|
286
|
+
const review = createFailureReview({
|
|
287
|
+
target,
|
|
288
|
+
type,
|
|
289
|
+
status,
|
|
290
|
+
suggestedType,
|
|
291
|
+
reviewer: readFlag("--reviewer"),
|
|
292
|
+
note: readFlag("--note"),
|
|
293
|
+
confidence: parseReviewConfidence(readFlag("--confidence")),
|
|
294
|
+
evidenceContext: readReviewEvidenceContextFromFlags(),
|
|
295
|
+
taxonomyRationale: readReviewTaxonomyRationaleFromFlags(),
|
|
296
|
+
});
|
|
297
|
+
await appendFailureReview(reviewPath, review);
|
|
298
|
+
const updated = applyFailureReviews(records, await readFailureReviews(reviewPath));
|
|
299
|
+
await store.append(updated, { replace: true });
|
|
300
|
+
process.stdout.write(`Wrote failure review ${review.id} to ${resolve(reviewPath)}\n`);
|
|
301
|
+
process.stdout.write(`Updated ${updated.length} corpus records in ${store.description}\n`);
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
await store.close();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
process.stdout.write(`Usage:
|
|
309
|
+
witnora corpus ingest --tripwire .tripwire/latest/tripwire-result.json --out .witnora/corpus/corpus.jsonl --subject my-agent
|
|
310
|
+
witnora corpus ingest --store sqlite --sqlite .witnora/corpus/agentcert.sqlite --tripwire .tripwire/latest/tripwire-result.json --subject my-agent
|
|
311
|
+
witnora corpus ingest --tripwire result.json --consent anonymous --consent-source "pilot agreement 2026-07"
|
|
312
|
+
witnora corpus review --corpus .witnora/corpus/corpus.jsonl --reviews .witnora/corpus/failure-reviews.jsonl --pattern-key tripwire:ui_drift:modal-overlay:url_contains --type ui_drift --status confirmed --reviewer you@example.com --confidence 0.8 --why "Visible evidence supports the ui_drift label."
|
|
313
|
+
witnora corpus metrics --corpus .witnora/corpus/corpus.jsonl
|
|
314
|
+
witnora corpus export-reviewed --corpus .witnora/corpus/corpus.jsonl --out .witnora/latest/reviewed-failure-dataset.jsonl
|
|
315
|
+
witnora corpus export --corpus .witnora/corpus/corpus.jsonl --consent public,anonymous --out .witnora/latest/governed-corpus.jsonl
|
|
316
|
+
witnora corpus delete --corpus .witnora/corpus/corpus.jsonl --record-id <id> --reason "participant withdrawal"
|
|
317
|
+
witnora corpus classifier-eval --corpus .witnora/corpus/corpus.jsonl --out .witnora/latest/failure-classifier-evaluation.json
|
|
318
|
+
witnora corpus summary --corpus .witnora/corpus/corpus.jsonl
|
|
319
|
+
witnora corpus summary --store postgres --database-url "$DATABASE_URL"
|
|
320
|
+
`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else if (command === "monitor") {
|
|
324
|
+
const action = process.argv[3] ?? "help";
|
|
325
|
+
if (action === "build") {
|
|
326
|
+
const outPath = readFlag("--out") ?? ".witnora/latest/monitor.json";
|
|
327
|
+
const subject = readFlag("--subject") ?? "agentcert-subject";
|
|
328
|
+
const detailUrl = readFlag("--detail-url");
|
|
329
|
+
const store = await openCorpusStore(readCorpusStoreOptions(".witnora/corpus/corpus.jsonl"));
|
|
330
|
+
try {
|
|
331
|
+
const records = applyFailureReviews(await store.readAll(), await readFailureReviews(readReviewsPath()));
|
|
332
|
+
const snapshot = buildMonitorSnapshot(records, { subject, detailUrl });
|
|
333
|
+
await writeMonitorSnapshot(outPath, snapshot);
|
|
334
|
+
process.stdout.write(`Wrote ${resolve(outPath)}\n`);
|
|
335
|
+
process.stdout.write(`Monitor snapshot: ${snapshot.summary.totalRecords} records, ${(snapshot.summary.passRate * 100).toFixed(1)}% pass rate\n`);
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
await store.close();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
process.stdout.write(`Usage:
|
|
343
|
+
witnora monitor build --corpus .witnora/corpus/corpus.jsonl --out packages/agentcert-dashboard/public/data/monitor.json --subject my-agent
|
|
344
|
+
witnora monitor build --store sqlite --sqlite .witnora/corpus/agentcert.sqlite --out packages/agentcert-dashboard/public/data/monitor.json --subject my-agent
|
|
345
|
+
`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (command === "run") {
|
|
349
|
+
const overrides = readRunOverrides();
|
|
350
|
+
const profileName = readFlag("--profile");
|
|
351
|
+
const configPath = readFlag("--config");
|
|
352
|
+
const hasArtifactFlags = Boolean(overrides.mcpbench || overrides.tripwire || overrides.onegent);
|
|
353
|
+
const profile = hasArtifactFlags && !profileName && !configPath
|
|
354
|
+
? profileFromArtifactFlags(overrides)
|
|
355
|
+
: await loadRunProfile(profileName, configPath);
|
|
356
|
+
const outcome = await runAgentCertProfile(profile, {
|
|
357
|
+
runCommands: !readBoolFlag("--skip-commands"),
|
|
358
|
+
overrides,
|
|
359
|
+
});
|
|
360
|
+
process.stdout.write(renderRunSummary(outcome));
|
|
361
|
+
if (readBoolFlag("--push")) {
|
|
362
|
+
await pushHostedEvidence(outcome.bundle, Buffer.from(`${JSON.stringify(outcome.bundle, null, 2)}\n`), "agentcert-evidence.json");
|
|
363
|
+
}
|
|
364
|
+
process.exitCode = outcome.exitCode;
|
|
365
|
+
}
|
|
366
|
+
else if (command === "push") {
|
|
367
|
+
const evidencePath = resolve(readFlag("--evidence") ?? ".witnora/latest/agentcert-evidence.json");
|
|
368
|
+
const evidenceBytes = await readFile(evidencePath);
|
|
369
|
+
let bundle;
|
|
370
|
+
try {
|
|
371
|
+
bundle = JSON.parse(evidenceBytes.toString("utf8"));
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
throw new Error(`Evidence file is not valid JSON: ${evidencePath}`);
|
|
375
|
+
}
|
|
376
|
+
const validation = validateAgentCertSchema("evidence-bundle", bundle);
|
|
377
|
+
if (!validation.valid) {
|
|
378
|
+
throw new Error(`Evidence bundle is invalid:\n${validation.errors.map((error) => `- ${error}`).join("\n")}`);
|
|
379
|
+
}
|
|
380
|
+
await pushHostedEvidence(bundle, evidenceBytes, basename(evidencePath));
|
|
381
|
+
}
|
|
382
|
+
else if (command === "release-gate") {
|
|
383
|
+
const overrides = readRunOverrides();
|
|
384
|
+
const profileName = readFlag("--profile");
|
|
385
|
+
const configPath = readFlag("--config");
|
|
386
|
+
const evidenceInput = readFlag("--evidence");
|
|
387
|
+
let profile;
|
|
388
|
+
let bundle;
|
|
389
|
+
let evidenceBundlePath;
|
|
390
|
+
let sourceArtifacts;
|
|
391
|
+
if (evidenceInput) {
|
|
392
|
+
profile = configPath ? applyRunOverrides(await loadRunProfile(profileName, configPath), overrides) : undefined;
|
|
393
|
+
bundle = (await readJson(evidenceInput));
|
|
394
|
+
evidenceBundlePath = resolve(evidenceInput);
|
|
395
|
+
sourceArtifacts = bundle.artifacts;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const hasArtifactFlags = Boolean(overrides.mcpbench || overrides.tripwire || overrides.onegent);
|
|
399
|
+
const loadedProfile = hasArtifactFlags && !profileName && !configPath
|
|
400
|
+
? profileFromArtifactFlags(overrides)
|
|
401
|
+
: await loadRunProfile(profileName, configPath);
|
|
402
|
+
profile = applyRunOverrides(loadedProfile, overrides);
|
|
403
|
+
const outcome = await runAgentCertProfile(profile, {
|
|
404
|
+
runCommands: !readBoolFlag("--skip-commands"),
|
|
405
|
+
});
|
|
406
|
+
bundle = outcome.bundle;
|
|
407
|
+
evidenceBundlePath = resolve(outcome.manifest.outputs.evidenceBundle ?? join(profile.outputDir, "agentcert-evidence.json"));
|
|
408
|
+
sourceArtifacts = profile.artifacts;
|
|
409
|
+
if (!outcome.manifest.outputs.evidenceBundle) {
|
|
410
|
+
await mkdir(dirname(evidenceBundlePath), { recursive: true });
|
|
411
|
+
await writeFile(evidenceBundlePath, `${JSON.stringify(outcome.bundle, null, 2)}\n`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const outDir = resolve(readFlag("--out") ?? profile?.run?.gate?.outDir ?? profile?.outputDir ?? dirname(evidenceBundlePath));
|
|
415
|
+
await mkdir(outDir, { recursive: true });
|
|
416
|
+
const baselinePath = readFlag("--baseline") ?? profile?.run?.gate?.baseline;
|
|
417
|
+
const baseline = baselinePath ? (await readJson(baselinePath)) : undefined;
|
|
418
|
+
const report = await buildReleaseGateReport(bundle, {
|
|
419
|
+
strict: readBoolFlag("--strict") || profile?.run?.gate?.strict,
|
|
420
|
+
requireBaseline: readBoolFlag("--require-baseline") || profile?.run?.gate?.requireBaseline,
|
|
421
|
+
maxScoreDrop: parseOptionalNonNegativeNumber(readFlag("--max-score-drop"), "--max-score-drop") ?? profile?.run?.gate?.maxScoreDrop,
|
|
422
|
+
baseline,
|
|
423
|
+
attestations: profile?.run?.gate?.controls,
|
|
424
|
+
sourceArtifacts,
|
|
425
|
+
evidenceBundlePath,
|
|
426
|
+
});
|
|
427
|
+
const paths = await writeReleaseGateArtifacts(outDir, report);
|
|
428
|
+
const saveBaselinePath = readFlag("--save-baseline");
|
|
429
|
+
if (saveBaselinePath) {
|
|
430
|
+
if (report.verdict.passed) {
|
|
431
|
+
await mkdir(dirname(resolve(saveBaselinePath)), { recursive: true });
|
|
432
|
+
await writeFile(resolve(saveBaselinePath), `${JSON.stringify(bundle, null, 2)}\n`);
|
|
433
|
+
process.stdout.write(`Saved passing baseline ${resolve(saveBaselinePath)}\n`);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
process.stdout.write(`Baseline not saved because the release gate failed: ${resolve(saveBaselinePath)}\n`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const privateKey = readFlag("--sign-private-key") ?? profile?.run?.gate?.signing?.privateKey;
|
|
440
|
+
if (privateKey) {
|
|
441
|
+
await signEvidenceFile(evidenceBundlePath, privateKey);
|
|
442
|
+
await signEvidenceFile(paths.json, privateKey);
|
|
443
|
+
process.stdout.write(`Signed ${evidenceBundlePath}\nSigned ${paths.json}\n`);
|
|
444
|
+
}
|
|
445
|
+
process.stdout.write(renderReleaseGateSummary(report));
|
|
446
|
+
process.stdout.write(`Release gate JSON: ${paths.json}\nRelease gate HTML: ${paths.html}\nRelease gate JUnit: ${paths.junit}\n`);
|
|
447
|
+
process.exitCode = report.verdict.passed ? 0 : 1;
|
|
448
|
+
}
|
|
449
|
+
else if (command === "evidence") {
|
|
450
|
+
const action = process.argv[3] ?? "help";
|
|
451
|
+
if (action === "keygen") {
|
|
452
|
+
const privateKeyPath = readFlag("--private-key") ?? ".witnora/keys/evidence-private.pem";
|
|
453
|
+
const publicKeyPath = readFlag("--public-key") ?? ".witnora/keys/evidence-public.pem";
|
|
454
|
+
await generateEvidenceSigningKeyPair(privateKeyPath, publicKeyPath);
|
|
455
|
+
process.stdout.write(`Wrote private key ${resolve(privateKeyPath)}\nWrote public key ${resolve(publicKeyPath)}\n`);
|
|
456
|
+
}
|
|
457
|
+
else if (action === "sign") {
|
|
458
|
+
const file = readFlag("--file") ?? readPositional(4);
|
|
459
|
+
if (!file)
|
|
460
|
+
throw new Error("Missing evidence file. Usage: agentcert evidence sign <file> --private-key <path>.");
|
|
461
|
+
const privateKeyPath = requiredFlag("--private-key");
|
|
462
|
+
const signaturePath = readFlag("--out") ?? `${file}.sig.json`;
|
|
463
|
+
const signature = await signEvidenceFile(file, privateKeyPath, signaturePath);
|
|
464
|
+
process.stdout.write(`Signed ${resolve(file)}\nSignature: ${resolve(signaturePath)}\nKey id: ${signature.keyId}\n`);
|
|
465
|
+
}
|
|
466
|
+
else if (action === "verify") {
|
|
467
|
+
const file = readFlag("--file") ?? readPositional(4);
|
|
468
|
+
if (!file)
|
|
469
|
+
throw new Error("Missing evidence file. Usage: agentcert evidence verify <file> --signature <path> --public-key <path>.");
|
|
470
|
+
const result = await verifyEvidenceFile(file, requiredFlag("--signature"), requiredFlag("--public-key"));
|
|
471
|
+
process.stdout.write(`${result.valid ? "Valid" : "Invalid"} evidence signature: ${resolve(file)}\nKey id: ${result.keyId}\nSHA-256: ${result.artifactSha256}\n`);
|
|
472
|
+
process.exitCode = result.valid ? 0 : 1;
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
process.stdout.write(`Usage:
|
|
476
|
+
witnora evidence keygen --private-key .witnora/keys/evidence-private.pem --public-key .witnora/keys/evidence-public.pem
|
|
477
|
+
witnora evidence sign .witnora/latest/agentcert-evidence.json --private-key .witnora/keys/evidence-private.pem
|
|
478
|
+
witnora evidence verify .witnora/latest/agentcert-evidence.json --signature .witnora/latest/agentcert-evidence.json.sig.json --public-key .witnora/keys/evidence-public.pem
|
|
479
|
+
`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else if (command === "lab") {
|
|
483
|
+
const action = process.argv[3] ?? "help";
|
|
484
|
+
if (action === "build") {
|
|
485
|
+
const configPath = readFlag("--config") ?? "examples/real-agents/robustness-lab/lab.config.json";
|
|
486
|
+
const outPath = readFlag("--out") ?? "public-demo/real-agent-robustness/evidence/lab-snapshot.json";
|
|
487
|
+
const config = await readRobustnessLabConfig(configPath);
|
|
488
|
+
const snapshot = await buildRobustnessLabSnapshot(config);
|
|
489
|
+
await writeRobustnessLabSnapshot(outPath, snapshot);
|
|
490
|
+
process.stdout.write(`Wrote ${resolve(outPath)}\n`);
|
|
491
|
+
process.stdout.write(renderRobustnessLabSummary(snapshot));
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
process.stdout.write(`Usage:
|
|
495
|
+
witnora lab build --config examples/real-agents/robustness-lab/lab.config.json --out public-demo/real-agent-robustness/evidence/lab-snapshot.json
|
|
496
|
+
`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else if (command === "serve") {
|
|
500
|
+
await serveAgentCertMonitor({
|
|
501
|
+
host: readFlag("--host") ?? "127.0.0.1",
|
|
502
|
+
port: Number(readFlag("--port") ?? process.env.PORT ?? 8765),
|
|
503
|
+
subject: readFlag("--subject") ?? "agentcert-local",
|
|
504
|
+
detailUrl: readFlag("--detail-url") ?? "../browser-agent-robustness/",
|
|
505
|
+
staticDir: readFlag("--static") ?? "public-demo/agentcert-monitor",
|
|
506
|
+
artifactRoot: readFlag("--artifact-root") ?? "public-demo/browser-agent-robustness/evidence/tripwire-public-demo",
|
|
507
|
+
store: readCorpusStoreOptions("public-demo/browser-agent-robustness/evidence/agentcert-corpus.jsonl"),
|
|
508
|
+
reviewsPath: readReviewsPath() ?? "public-demo/browser-agent-robustness/evidence/failure-reviews.jsonl",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
else if (command === "schema") {
|
|
512
|
+
const action = process.argv[3] ?? "help";
|
|
513
|
+
if (action === "validate") {
|
|
514
|
+
const schema = parseSchemaId(readFlag("--schema"));
|
|
515
|
+
const file = requiredFlag("--file");
|
|
516
|
+
const result = validateAgentCertSchema(schema, await readJson(file));
|
|
517
|
+
if (result.valid) {
|
|
518
|
+
process.stdout.write(`Valid ${schema}: ${resolve(file)}\n`);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
process.stdout.write(`Invalid ${schema}: ${resolve(file)}\n`);
|
|
522
|
+
for (const error of result.errors) {
|
|
523
|
+
process.stdout.write(`- ${error}\n`);
|
|
524
|
+
}
|
|
525
|
+
process.exitCode = 1;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
process.stdout.write(`Usage:
|
|
530
|
+
witnora schema validate --schema evidence-bundle --file .witnora/latest/agentcert-evidence.json
|
|
531
|
+
witnora schema validate --schema monitor-snapshot --file .witnora/latest/monitor.json
|
|
532
|
+
witnora schema validate --schema corpus-record --file examples/agentcert/corpus-record.example.json
|
|
533
|
+
witnora schema validate --schema classifier-eval --file examples/agentcert/classifier-eval.example.json
|
|
534
|
+
witnora schema validate --schema release-gate --file .witnora/latest/agentcert-release-gate.json
|
|
535
|
+
witnora schema validate --schema assurance-report --file assurance-report.json
|
|
536
|
+
witnora schema validate --schema assurance-delivery --file assurance-delivery.json
|
|
537
|
+
witnora schema validate --schema evidence-signature --file .witnora/latest/agentcert-evidence.json.sig.json
|
|
538
|
+
`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else if (command === "validate") {
|
|
542
|
+
const schema = parseSchemaId(readFlag("--schema") ?? "evidence-bundle");
|
|
543
|
+
const file = readFlag("--file") ?? readFirstPositionalAfterCommand();
|
|
544
|
+
if (!file) {
|
|
545
|
+
throw new Error("Missing evidence file. Usage: agentcert validate <file> [--schema evidence-bundle].");
|
|
546
|
+
}
|
|
547
|
+
const input = await readJson(file);
|
|
548
|
+
const result = validateAgentCertSchema(schema, input);
|
|
549
|
+
if (result.valid) {
|
|
550
|
+
process.stdout.write(`Valid ${schema}: ${resolve(file)}\n`);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
process.stdout.write(`Invalid ${schema}: ${resolve(file)}\n`);
|
|
554
|
+
for (const error of result.errors) {
|
|
555
|
+
process.stdout.write(`- ${error}\n`);
|
|
556
|
+
}
|
|
557
|
+
process.exitCode = 1;
|
|
558
|
+
}
|
|
559
|
+
if (result.valid && schema === "evidence-bundle" && readBoolFlag("--check-artifacts")) {
|
|
560
|
+
const artifactResult = await validateEvidenceArtifacts(input, resolve(readFlag("--artifact-root") ?? process.cwd()));
|
|
561
|
+
process.stdout.write(`Artifact paths checked: ${artifactResult.checked}\n`);
|
|
562
|
+
if (artifactResult.missing.length > 0) {
|
|
563
|
+
process.stdout.write("Missing artifact paths:\n");
|
|
564
|
+
for (const missing of artifactResult.missing) {
|
|
565
|
+
process.stdout.write(`- ${missing}\n`);
|
|
566
|
+
}
|
|
567
|
+
process.exitCode = 1;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
else if (command === "conformance") {
|
|
572
|
+
const file = readFlag("--file") ?? readFirstPositionalAfterCommand();
|
|
573
|
+
if (!file)
|
|
574
|
+
throw new Error("Missing evidence file. Usage: agentcert conformance <file> --artifact-root <directory>.");
|
|
575
|
+
const artifactRoot = resolve(readFlag("--artifact-root") ?? dirname(resolve(file)));
|
|
576
|
+
const report = await runEvidenceConformance(await readJson(file), {
|
|
577
|
+
evidenceFile: file,
|
|
578
|
+
artifactRoot,
|
|
579
|
+
implementation: readFlag("--implementation"),
|
|
580
|
+
});
|
|
581
|
+
const output = `${JSON.stringify(report, null, 2)}\n`;
|
|
582
|
+
const outPath = readFlag("--out");
|
|
583
|
+
if (outPath) {
|
|
584
|
+
await mkdir(dirname(resolve(outPath)), { recursive: true });
|
|
585
|
+
await writeFile(outPath, output);
|
|
586
|
+
process.stdout.write(`Wrote conformance report: ${resolve(outPath)}\n`);
|
|
587
|
+
}
|
|
588
|
+
process.stdout.write(`${report.valid ? "Conformant" : "Non-conformant"} evidence implementation: ${report.implementation}\n`);
|
|
589
|
+
for (const item of report.checks) {
|
|
590
|
+
process.stdout.write(`- ${item.status === "passed" ? "PASS" : "FAIL"} ${item.id}: ${item.message}\n`);
|
|
591
|
+
for (const error of item.errors)
|
|
592
|
+
process.stdout.write(` - ${error}\n`);
|
|
593
|
+
}
|
|
594
|
+
if (!report.valid)
|
|
595
|
+
process.exitCode = 1;
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
process.stdout.write(`Usage:
|
|
599
|
+
witnora try --template workflow [--push]
|
|
600
|
+
witnora init --subject my-browser-agent
|
|
601
|
+
witnora connect --server https://witnora.com --project <project-id>
|
|
602
|
+
witnora sandbox init
|
|
603
|
+
witnora sandbox certify --adapter ./witnora.sandbox.mjs
|
|
604
|
+
witnora sandbox push --adapter ./witnora.sandbox.mjs
|
|
605
|
+
witnora browser-adapter init
|
|
606
|
+
witnora browser-adapter certify --adapter ./witnora.browser-adapter.mjs
|
|
607
|
+
witnora init --out witnora.config.json --tripwire-config tripwire.yml --force
|
|
608
|
+
witnora init --subject my-browser-agent --github-action
|
|
609
|
+
witnora report --mcpbench .mcpbench/latest/results.json --tripwire .tripwire/latest/tripwire-result.json --onegent .onegent/procurement/audit-packet.json --out .witnora/latest --subject my-agent
|
|
610
|
+
witnora corpus ingest --tripwire .tripwire/latest/tripwire-result.json --out .witnora/corpus/corpus.jsonl --subject my-agent
|
|
611
|
+
witnora corpus review --corpus .witnora/corpus/corpus.jsonl --reviews .witnora/corpus/failure-reviews.jsonl --pattern-key <failure-key> --type wrong_click --status corrected
|
|
612
|
+
witnora corpus summary --corpus .witnora/corpus/corpus.jsonl
|
|
613
|
+
witnora corpus classifier-eval --corpus .witnora/corpus/corpus.jsonl --out .witnora/latest/failure-classifier-evaluation.json
|
|
614
|
+
witnora monitor build --corpus .witnora/corpus/corpus.jsonl --out .witnora/latest/monitor.json --subject my-agent
|
|
615
|
+
witnora monitor build --store sqlite --sqlite .witnora/corpus/agentcert.sqlite --out .witnora/latest/monitor.json --subject my-agent
|
|
616
|
+
witnora run --profile public-demo
|
|
617
|
+
witnora push --evidence .witnora/latest/agentcert-evidence.json --server https://witnora.example.com --project <project-id> [--artifact-root .] [--no-artifacts]
|
|
618
|
+
witnora run --tripwire .tripwire/latest/tripwire-result.json --push
|
|
619
|
+
witnora run --mcpbench .mcpbench/latest/results.json --tripwire .tripwire/latest/tripwire-result.json --onegent .onegent/procurement/audit-packet.json --out .witnora/latest --corpus .witnora/corpus/corpus.jsonl --monitor-out .witnora/latest/monitor.json --reviewed-dataset-out .witnora/latest/reviewed-failure-dataset.jsonl
|
|
620
|
+
witnora release-gate --config witnora.config.json --strict
|
|
621
|
+
witnora release-gate --evidence .witnora/latest/agentcert-evidence.json --baseline .witnora/baselines/main.json
|
|
622
|
+
witnora release-gate --evidence .witnora/latest/agentcert-evidence.json --save-baseline .witnora/baselines/main.json
|
|
623
|
+
witnora release-gate --tripwire .tripwire/latest/tripwire-result.json --baseline .witnora/baselines/main.json
|
|
624
|
+
witnora evidence keygen --private-key .witnora/keys/evidence-private.pem --public-key .witnora/keys/evidence-public.pem
|
|
625
|
+
witnora evidence sign .witnora/latest/agentcert-evidence.json --private-key .witnora/keys/evidence-private.pem
|
|
626
|
+
witnora evidence verify .witnora/latest/agentcert-evidence.json --signature .witnora/latest/agentcert-evidence.json.sig.json --public-key .witnora/keys/evidence-public.pem
|
|
627
|
+
witnora lab build --config examples/real-agents/robustness-lab/lab.config.json --out public-demo/real-agent-robustness/evidence/lab-snapshot.json
|
|
628
|
+
witnora serve --corpus .witnora/corpus/corpus.jsonl --static public-demo/agentcert-monitor --artifact-root public-demo/browser-agent-robustness/evidence/tripwire-public-demo
|
|
629
|
+
witnora validate .witnora/latest/agentcert-evidence.json
|
|
630
|
+
witnora validate .witnora/latest/agentcert-evidence.json --check-artifacts
|
|
631
|
+
witnora conformance .witnora/latest/agentcert-evidence.json --artifact-root . --implementation my-agent
|
|
632
|
+
witnora schema validate --schema evidence-bundle --file .witnora/latest/agentcert-evidence.json
|
|
633
|
+
`);
|
|
634
|
+
}
|
|
635
|
+
async function loadConfig(path) {
|
|
636
|
+
if (!path) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
return (await readJson(path));
|
|
640
|
+
}
|
|
641
|
+
async function pushHostedEvidence(bundle, bytes, fileName) {
|
|
642
|
+
const connection = await resolveConnection({
|
|
643
|
+
name: readFlag("--connection"),
|
|
644
|
+
server: readFlag("--server"),
|
|
645
|
+
projectId: readFlag("--project"),
|
|
646
|
+
apiKey: readFlag("--api-key"),
|
|
647
|
+
});
|
|
648
|
+
const companions = readBoolFlag("--no-artifacts")
|
|
649
|
+
? undefined
|
|
650
|
+
: await collectCompanionArtifacts(bundle, resolve(readFlag("--artifact-root") ?? process.cwd()));
|
|
651
|
+
const assurance = await readContinuousAssuranceBinding();
|
|
652
|
+
const healthOut = readFlag("--continuous-health-out");
|
|
653
|
+
const requireCurrent = readBoolFlag("--require-current");
|
|
654
|
+
if (requireCurrent && !assurance)
|
|
655
|
+
throw new Error("--require-current needs --assurance-case and --assurance-scope.");
|
|
656
|
+
const result = await pushEvidenceToControlPlane({
|
|
657
|
+
baseUrl: connection.server,
|
|
658
|
+
projectId: connection.projectId,
|
|
659
|
+
apiKey: connection.apiKey,
|
|
660
|
+
bundle,
|
|
661
|
+
evidenceBytes: bytes,
|
|
662
|
+
fileName,
|
|
663
|
+
externalId: readFlag("--external-id"),
|
|
664
|
+
companionArtifacts: companions?.artifacts,
|
|
665
|
+
skippedCompanionArtifacts: companions?.skipped,
|
|
666
|
+
assurance,
|
|
667
|
+
verifyContinuousAssurance: Boolean(assurance && (healthOut || requireCurrent)),
|
|
668
|
+
});
|
|
669
|
+
process.stdout.write(`Hosted run: ${result.runId}\nHosted evidence: ${result.evidenceId}\n`);
|
|
670
|
+
if (companions) {
|
|
671
|
+
process.stdout.write(`Hosted companion artifacts: ${result.artifactsUploaded} uploaded, ${result.artifactsSkipped} skipped, ${result.artifactBytesUploaded} bytes.\n`);
|
|
672
|
+
for (const skipped of companions.skipped.slice(0, MAX_REPORTED_COMPANION_ARTIFACT_SKIPS)) {
|
|
673
|
+
process.stderr.write(`Skipped companion artifact ${skipped.sourcePath}: ${skipped.reason}${skipped.detail ? ` (${skipped.detail})` : ""}.\n`);
|
|
674
|
+
}
|
|
675
|
+
if (companions.skipped.length > MAX_REPORTED_COMPANION_ARTIFACT_SKIPS) {
|
|
676
|
+
process.stderr.write(`${companions.skipped.length - MAX_REPORTED_COMPANION_ARTIFACT_SKIPS} additional skipped artifacts omitted.\n`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (result.continuousAssuranceHealth) {
|
|
680
|
+
process.stdout.write(`Hosted assurance: ${result.continuousAssuranceHealth.status} (${result.continuousAssuranceHealth.healthy ? "healthy" : "attention required"})\n`);
|
|
681
|
+
if (healthOut) {
|
|
682
|
+
const outputPath = resolve(healthOut);
|
|
683
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
684
|
+
await writeFile(outputPath, `${JSON.stringify(result.continuousAssuranceHealth, null, 2)}\n`, "utf8");
|
|
685
|
+
process.stdout.write(`Continuous assurance health: ${outputPath}\n`);
|
|
686
|
+
}
|
|
687
|
+
if (requireCurrent)
|
|
688
|
+
requireContinuousAssuranceCurrent(result.continuousAssuranceHealth);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async function readContinuousAssuranceBinding() {
|
|
692
|
+
const caseId = readFlag("--assurance-case");
|
|
693
|
+
const scopePath = readFlag("--assurance-scope");
|
|
694
|
+
if (!caseId && !scopePath)
|
|
695
|
+
return undefined;
|
|
696
|
+
if (!caseId || !scopePath)
|
|
697
|
+
throw new Error("--assurance-case and --assurance-scope must be provided together.");
|
|
698
|
+
const scope = await readJson(scopePath);
|
|
699
|
+
const validation = validateAgentCertSchema("assurance-scope", scope);
|
|
700
|
+
if (!validation.valid)
|
|
701
|
+
throw new Error(`Assurance scope is invalid:\n${validation.errors.map((error) => `- ${error}`).join("\n")}`);
|
|
702
|
+
return { caseId, trigger: assuranceTriggerFromEnvironment(readFlag("--assurance-trigger") ?? "auto"), scope: scope };
|
|
703
|
+
}
|
|
704
|
+
function assuranceTriggerFromEnvironment(value) {
|
|
705
|
+
if (value === "pull_request" || value === "release" || value === "nightly")
|
|
706
|
+
return value;
|
|
707
|
+
if (value !== "auto")
|
|
708
|
+
throw new Error("--assurance-trigger must be auto, pull_request, release, or nightly.");
|
|
709
|
+
const event = process.env.GITHUB_EVENT_NAME;
|
|
710
|
+
if (event === "pull_request" || event === "pull_request_target")
|
|
711
|
+
return "pull_request";
|
|
712
|
+
if (event === "schedule")
|
|
713
|
+
return "nightly";
|
|
714
|
+
return "release";
|
|
715
|
+
}
|
|
716
|
+
async function promptValue(label) {
|
|
717
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
718
|
+
throw new Error(`Missing ${label.trim().replace(/:$/, "").toLowerCase()}. Pass it as a flag or environment variable.`);
|
|
719
|
+
}
|
|
720
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
721
|
+
try {
|
|
722
|
+
return (await prompt.question(label)).trim();
|
|
723
|
+
}
|
|
724
|
+
finally {
|
|
725
|
+
prompt.close();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async function promptSecret(label) {
|
|
729
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
|
|
730
|
+
throw new Error("Missing project API key. Set WITNORA_API_KEY (or legacy AGENTCERT_API_KEY) or pass --api-key in a trusted environment.");
|
|
731
|
+
}
|
|
732
|
+
process.stdout.write(label);
|
|
733
|
+
process.stdin.setRawMode(true);
|
|
734
|
+
process.stdin.resume();
|
|
735
|
+
process.stdin.setEncoding("utf8");
|
|
736
|
+
return new Promise((resolveSecret, rejectSecret) => {
|
|
737
|
+
let value = "";
|
|
738
|
+
const cleanup = () => {
|
|
739
|
+
process.stdin.off("data", onData);
|
|
740
|
+
process.stdin.setRawMode?.(false);
|
|
741
|
+
process.stdin.pause();
|
|
742
|
+
process.stdout.write("\n");
|
|
743
|
+
};
|
|
744
|
+
const onData = (chunk) => {
|
|
745
|
+
for (const character of String(chunk)) {
|
|
746
|
+
if (character === "\u0003") {
|
|
747
|
+
cleanup();
|
|
748
|
+
rejectSecret(new Error("Witnora connection cancelled."));
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (character === "\r" || character === "\n") {
|
|
752
|
+
cleanup();
|
|
753
|
+
resolveSecret(value.trim());
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (character === "\u007f" || character === "\b") {
|
|
757
|
+
if (value) {
|
|
758
|
+
value = value.slice(0, -1);
|
|
759
|
+
process.stdout.write("\b \b");
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
value += character;
|
|
764
|
+
process.stdout.write("*");
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
process.stdin.on("data", onData);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
async function readJson(path) {
|
|
771
|
+
const raw = await readFile(resolve(path), "utf8");
|
|
772
|
+
return JSON.parse(raw);
|
|
773
|
+
}
|
|
774
|
+
async function loadArtifactResults(config) {
|
|
775
|
+
const loaded = [];
|
|
776
|
+
const mcpbenchPath = readFlag("--mcpbench") ?? config?.artifacts.mcpbench;
|
|
777
|
+
const tripwirePath = readFlag("--tripwire") ?? config?.artifacts.tripwire;
|
|
778
|
+
const onegentPath = readFlag("--onegent") ?? config?.artifacts.onegent;
|
|
779
|
+
if (mcpbenchPath) {
|
|
780
|
+
const raw = await readJson(mcpbenchPath);
|
|
781
|
+
loaded.push({ result: normalizeMcpBenchResult(raw, mcpbenchPath), path: mcpbenchPath, raw });
|
|
782
|
+
}
|
|
783
|
+
if (tripwirePath) {
|
|
784
|
+
const raw = await readJson(tripwirePath);
|
|
785
|
+
loaded.push({ result: normalizeTripwireResult(raw, tripwirePath), path: tripwirePath, raw });
|
|
786
|
+
}
|
|
787
|
+
if (onegentPath) {
|
|
788
|
+
const raw = await readJson(onegentPath);
|
|
789
|
+
loaded.push({ result: normalizeOnegentAuditPacket(raw, onegentPath), path: onegentPath, raw });
|
|
790
|
+
}
|
|
791
|
+
return loaded;
|
|
792
|
+
}
|
|
793
|
+
function readFlag(name) {
|
|
794
|
+
const index = process.argv.indexOf(name);
|
|
795
|
+
return index >= 0 ? process.argv[index + 1] : undefined;
|
|
796
|
+
}
|
|
797
|
+
function brandedEnvironment(name) {
|
|
798
|
+
return process.env[`WITNORA_${name}`] ?? process.env[`AGENTCERT_${name}`];
|
|
799
|
+
}
|
|
800
|
+
function readFirstPositionalAfterCommand() {
|
|
801
|
+
for (let index = 3; index < process.argv.length; index += 1) {
|
|
802
|
+
const value = process.argv[index];
|
|
803
|
+
if (!value)
|
|
804
|
+
continue;
|
|
805
|
+
if (value.startsWith("--")) {
|
|
806
|
+
index += 1;
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
return value;
|
|
810
|
+
}
|
|
811
|
+
return undefined;
|
|
812
|
+
}
|
|
813
|
+
function readPositional(startIndex) {
|
|
814
|
+
for (let index = startIndex; index < process.argv.length; index += 1) {
|
|
815
|
+
const value = process.argv[index];
|
|
816
|
+
if (!value)
|
|
817
|
+
continue;
|
|
818
|
+
if (value.startsWith("--")) {
|
|
819
|
+
index += 1;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
return value;
|
|
823
|
+
}
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
function readBoolFlag(name) {
|
|
827
|
+
return process.argv.includes(name);
|
|
828
|
+
}
|
|
829
|
+
function readRepeatedFlag(name) {
|
|
830
|
+
const values = [];
|
|
831
|
+
for (let index = 0; index < process.argv.length; index += 1) {
|
|
832
|
+
if (process.argv[index] === name && process.argv[index + 1]) {
|
|
833
|
+
values.push(process.argv[index + 1]);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return values;
|
|
837
|
+
}
|
|
838
|
+
function readReviewEvidenceContextFromFlags() {
|
|
839
|
+
const firstDivergenceSnippet = readFlag("--first-divergence") ?? readFlag("--first-divergence-snippet");
|
|
840
|
+
const screenshotPath = readFlag("--screenshot") ?? readFlag("--screenshot-path");
|
|
841
|
+
const screenshotUrl = readFlag("--screenshot-url");
|
|
842
|
+
const tracePath = readFlag("--trace") ?? readFlag("--trace-path");
|
|
843
|
+
const stepIndex = parseOptionalNonNegativeInteger(readFlag("--step-index"), "--step-index");
|
|
844
|
+
if (!firstDivergenceSnippet && !screenshotPath && !screenshotUrl && !tracePath && stepIndex === undefined) {
|
|
845
|
+
return undefined;
|
|
846
|
+
}
|
|
847
|
+
return {
|
|
848
|
+
firstDivergenceSnippet,
|
|
849
|
+
screenshotPath,
|
|
850
|
+
screenshotUrl,
|
|
851
|
+
tracePath,
|
|
852
|
+
stepIndex,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function readReviewTaxonomyRationaleFromFlags() {
|
|
856
|
+
const primaryReason = readFlag("--why") ?? readFlag("--taxonomy-reason");
|
|
857
|
+
const supportingSignals = readRepeatedFlag("--signal");
|
|
858
|
+
const contradictingSignals = readRepeatedFlag("--contradiction");
|
|
859
|
+
const classifierLimitation = readFlag("--classifier-limitation");
|
|
860
|
+
if (!primaryReason) {
|
|
861
|
+
return undefined;
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
primaryReason,
|
|
865
|
+
supportingSignals: supportingSignals.length > 0 ? supportingSignals : undefined,
|
|
866
|
+
contradictingSignals: contradictingSignals.length > 0 ? contradictingSignals : undefined,
|
|
867
|
+
classifierLimitation,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function readRunOverrides() {
|
|
871
|
+
return {
|
|
872
|
+
subject: readFlag("--subject"),
|
|
873
|
+
mcpbench: readFlag("--mcpbench"),
|
|
874
|
+
tripwire: readFlag("--tripwire"),
|
|
875
|
+
onegent: readFlag("--onegent"),
|
|
876
|
+
outDir: readFlag("--out"),
|
|
877
|
+
corpusPath: readFlag("--corpus"),
|
|
878
|
+
monitorOut: readFlag("--monitor-out"),
|
|
879
|
+
reviewedDatasetOut: readFlag("--reviewed-dataset-out"),
|
|
880
|
+
reviewsPath: readReviewsPath(),
|
|
881
|
+
replaceCorpus: readBoolFlag("--replace") ? true : undefined,
|
|
882
|
+
failOnVerdict: readBoolFlag("--fail-on-verdict") ? true : undefined,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function requiredFlag(name) {
|
|
886
|
+
const value = readFlag(name);
|
|
887
|
+
if (!value) {
|
|
888
|
+
throw new Error(`Missing required flag ${name}.`);
|
|
889
|
+
}
|
|
890
|
+
return value;
|
|
891
|
+
}
|
|
892
|
+
function parseOptionalNonNegativeInteger(input, flagName) {
|
|
893
|
+
if (input === undefined)
|
|
894
|
+
return undefined;
|
|
895
|
+
const value = Number(input);
|
|
896
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
897
|
+
throw new Error(`${flagName} must be a non-negative integer.`);
|
|
898
|
+
}
|
|
899
|
+
return value;
|
|
900
|
+
}
|
|
901
|
+
function parseOptionalNonNegativeNumber(input, flagName) {
|
|
902
|
+
if (input === undefined)
|
|
903
|
+
return undefined;
|
|
904
|
+
const value = Number(input);
|
|
905
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
906
|
+
throw new Error(`${flagName} must be a non-negative number.`);
|
|
907
|
+
}
|
|
908
|
+
return value;
|
|
909
|
+
}
|
|
910
|
+
function readReviewsPath() {
|
|
911
|
+
return readFlag("--reviews") ?? brandedEnvironment("FAILURE_REVIEWS");
|
|
912
|
+
}
|
|
913
|
+
function readCorpusStoreOptions(defaultJsonlPath, outPath) {
|
|
914
|
+
return {
|
|
915
|
+
kind: parseCorpusStoreKind(readFlag("--store") ?? brandedEnvironment("CORPUS_STORE")),
|
|
916
|
+
jsonlPath: readFlag("--corpus") ?? outPath ?? defaultJsonlPath,
|
|
917
|
+
sqlitePath: readFlag("--sqlite") ?? brandedEnvironment("SQLITE_PATH") ?? ".witnora/corpus/witnora.sqlite",
|
|
918
|
+
databaseUrl: readFlag("--database-url") ?? brandedEnvironment("DATABASE_URL") ?? process.env.DATABASE_URL,
|
|
919
|
+
tableName: readFlag("--table") ?? brandedEnvironment("CORPUS_TABLE"),
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
async function writeStarterFile(path, content, force) {
|
|
923
|
+
await mkdir(dirname(path), { recursive: true });
|
|
924
|
+
try {
|
|
925
|
+
await writeFile(path, content, { flag: force ? "w" : "wx" });
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
|
929
|
+
throw new Error(`${path} already exists. Re-run with --force to overwrite it.`);
|
|
930
|
+
}
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function reportFatalError(error) {
|
|
935
|
+
process.stderr.write(formatCliError(error));
|
|
936
|
+
process.exit(1);
|
|
937
|
+
}
|
|
938
|
+
function formatCliError(error) {
|
|
939
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
940
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
|
|
941
|
+
const detail = [`Witnora error: ${message}`];
|
|
942
|
+
const hint = errorHint(message, code);
|
|
943
|
+
if (hint) {
|
|
944
|
+
detail.push("", hint);
|
|
945
|
+
}
|
|
946
|
+
return `${detail.join("\n")}\n`;
|
|
947
|
+
}
|
|
948
|
+
function errorHint(message, code) {
|
|
949
|
+
if (code === "ENOENT") {
|
|
950
|
+
return "Hint: check the artifact path. Run `witnora init` for starter config, then run Tripwire before `witnora run`.";
|
|
951
|
+
}
|
|
952
|
+
if (message.includes("witnora run requires") || message.includes("agentcert run requires")) {
|
|
953
|
+
return "Hint: run `witnora init`, pass `--tripwire .tripwire/latest/tripwire-result.json`, or use `--config witnora.config.json`.";
|
|
954
|
+
}
|
|
955
|
+
if (message.includes("No AgentCert artifacts were loaded") || message.includes("No input artifacts were provided")) {
|
|
956
|
+
return "Hint: provide at least one artifact with `--tripwire`, `--mcpbench`, `--onegent`, or configure `witnora.config.json`.";
|
|
957
|
+
}
|
|
958
|
+
if (message.includes("Unexpected token") || message.includes("JSON")) {
|
|
959
|
+
return "Hint: Witnora expected JSON. Check that the artifact file exists and was not replaced by logs or HTML.";
|
|
960
|
+
}
|
|
961
|
+
if (message.includes("already exists")) {
|
|
962
|
+
return "Hint: re-run with `--force` to overwrite starter files, or choose a different `--out` path.";
|
|
963
|
+
}
|
|
964
|
+
if (message.includes("Hosted push requires")) {
|
|
965
|
+
return "Hint: create a project API key in Witnora Setup, then set WITNORA_BASE_URL, WITNORA_PROJECT_ID, and WITNORA_API_KEY. Legacy AGENTCERT_* names remain supported.";
|
|
966
|
+
}
|
|
967
|
+
if (message.includes("Authentication required") || message.includes("API key")) {
|
|
968
|
+
return "Hint: check WITNORA_API_KEY and confirm the key belongs to WITNORA_PROJECT_ID. Legacy AGENTCERT_* names remain supported.";
|
|
969
|
+
}
|
|
970
|
+
return undefined;
|
|
971
|
+
}
|