cool-workflow 0.1.84 → 0.1.86
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +47 -331
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/architecture-review-fast/workflow.js +2 -0
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/capability-core.js +220 -5
- package/dist/capability-registry.js +90 -51
- package/dist/cli/command-surface.js +47 -2
- package/dist/doctor.js +37 -1
- package/dist/mcp/tool-call.js +428 -0
- package/dist/mcp/tool-definitions.js +1027 -0
- package/dist/mcp-surface.js +5 -1416
- package/dist/onramp.js +421 -0
- package/dist/orchestrator.js +20 -15
- package/dist/run-export.js +139 -1
- package/dist/telemetry-demo.js +119 -0
- package/dist/types/report-bundle.js +6 -0
- package/dist/types.js +1 -0
- package/dist/version.js +1 -1
- package/dist/workbench-host.js +1 -8
- package/docs/agent-delegation-drive.7.md +23 -2
- package/docs/cli-mcp-parity.7.md +38 -7
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/dogfood-one-real-repo.7.md +9 -1
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/getting-started.md +40 -2
- package/docs/index.md +51 -37
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +23 -7
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +5 -1
- package/docs/release-history.md +342 -0
- package/docs/release-tooling.7.md +18 -0
- package/docs/report-verifiable-bundle.7.md +123 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +1 -1
- package/package.json +2 -1
- package/scripts/architecture-review-fast.js +44 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +55 -133
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +47 -0
- package/scripts/parity-check.js +740 -2
- package/scripts/release-check.js +2 -0
- package/scripts/source-context.js +31 -5
- package/scripts/version-sync-check.js +4 -2
package/dist/capability-core.js
CHANGED
|
@@ -40,6 +40,8 @@ exports.runExportArchive = runExportArchive;
|
|
|
40
40
|
exports.runImportArchive = runImportArchive;
|
|
41
41
|
exports.runInspectArchive = runInspectArchive;
|
|
42
42
|
exports.runVerifyImport = runVerifyImport;
|
|
43
|
+
exports.reportBundle = reportBundle;
|
|
44
|
+
exports.runVerifyReportBundle = runVerifyReportBundle;
|
|
43
45
|
exports.queueAdd = queueAdd;
|
|
44
46
|
exports.queueList = queueList;
|
|
45
47
|
exports.queueDrain = queueDrain;
|
|
@@ -69,6 +71,7 @@ exports.isRecord = isRecord;
|
|
|
69
71
|
exports.telemetryVerify = telemetryVerify;
|
|
70
72
|
exports.auditVerify = auditVerify;
|
|
71
73
|
exports.demoTamper = demoTamper;
|
|
74
|
+
exports.demoBundle = demoBundle;
|
|
72
75
|
const drive_1 = require("./drive");
|
|
73
76
|
const agent_config_1 = require("./agent-config");
|
|
74
77
|
const run_registry_1 = require("./run-registry");
|
|
@@ -176,8 +179,8 @@ function compactOperatorStatus(status) {
|
|
|
176
179
|
// functions so `cw <cmd> --json` is byte-identical to `cw_<tool>`. Each accepts
|
|
177
180
|
// the raw CLI options OR the raw MCP arguments and normalizes them identically,
|
|
178
181
|
// then calls the single RunRegistry method. The registry is constructed from the
|
|
179
|
-
// same resolved cwd on both surfaces (CLI: --cwd|process.cwd(); MCP
|
|
180
|
-
//
|
|
182
|
+
// same resolved cwd on both surfaces (CLI: --cwd|process.cwd(); MCP passes a
|
|
183
|
+
// resolved cwd and scopes the runner), so repo/home roots line up.
|
|
181
184
|
function runRegistryFor(args, planner) {
|
|
182
185
|
return new run_registry_1.RunRegistry(String(args.cwd || process.cwd()), planner);
|
|
183
186
|
}
|
|
@@ -277,7 +280,11 @@ function runRerun(reg, runId, args) {
|
|
|
277
280
|
function runExportArchive(runner, runId, args) {
|
|
278
281
|
const base = invocationCwd(args);
|
|
279
282
|
const output = optionalString(args.output || args.path || args.archive) || `${runId}.cwrun.json`;
|
|
280
|
-
|
|
283
|
+
// Optionally seal in the operator's PUBLIC trust key so the bundle re-verifies
|
|
284
|
+
// offline. Default falls back to the same env the verify gate reads, so a single
|
|
285
|
+
// configured key both attests at record-time and travels with the export.
|
|
286
|
+
const trustPublicKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
|
|
287
|
+
return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), node_path_1.default.resolve(base, output), { trustPublicKey });
|
|
281
288
|
}
|
|
282
289
|
function runImportArchive(runner, args) {
|
|
283
290
|
const base = invocationCwd(args);
|
|
@@ -303,6 +310,48 @@ function runInspectArchive(_runner, args) {
|
|
|
303
310
|
function runVerifyImport(runner, runId, args) {
|
|
304
311
|
return (0, run_export_1.verifyImportedRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId));
|
|
305
312
|
}
|
|
313
|
+
// Produce-and-prove: export a run to a portable bundle sealed with the operator's
|
|
314
|
+
// trust key (defaulting to CW_AGENT_ATTEST_PUBKEY, same as `run export`), then
|
|
315
|
+
// IMMEDIATELY verify the artifact offline the way a recipient will. The producer
|
|
316
|
+
// learns now — fail-closed — whether the bundle a client will check is actually
|
|
317
|
+
// verifiable (e.g. an unconfigured attest key yields an unverifiable bundle). Pure
|
|
318
|
+
// composition of runExportArchive + verifyReportBundle; spawns nothing, writes only
|
|
319
|
+
// the archive (and, with --extract-report, the human report) that `run export` would.
|
|
320
|
+
function reportBundle(runner, runId, args) {
|
|
321
|
+
const exported = runExportArchive(runner, runId, args);
|
|
322
|
+
const base = invocationCwd(args);
|
|
323
|
+
const extractReportTo = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
|
|
324
|
+
const verification = (0, run_export_1.verifyReportBundle)(exported.path, {
|
|
325
|
+
pubkey: optionalString(args.pubkey || args.pubKey || args.publicKey),
|
|
326
|
+
extractReportTo: extractReportTo ? node_path_1.default.resolve(base, extractReportTo) : undefined,
|
|
327
|
+
strictSignatures: Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs)
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
schemaVersion: 1,
|
|
331
|
+
runId,
|
|
332
|
+
archivePath: exported.path,
|
|
333
|
+
trustKeyEmbedded: exported.trustKeyEmbedded,
|
|
334
|
+
reportExtractedTo: verification.reportExtractedTo,
|
|
335
|
+
verification,
|
|
336
|
+
ok: verification.ok
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
// Read-only: verify a portable run bundle OFFLINE and self-contained (archive bytes
|
|
340
|
+
// + telemetry chain + trust-audit chain + embedded-key signatures). The runner is
|
|
341
|
+
// unused — verification restores into its own throwaway tmpdir and writes nothing to
|
|
342
|
+
// any registry — but kept for dispatch-signature symmetry with the other run verbs.
|
|
343
|
+
function runVerifyReportBundle(_runner, args) {
|
|
344
|
+
const base = invocationCwd(args);
|
|
345
|
+
const archive = optionalString(args.archive || args.path || args.file || args.bundle);
|
|
346
|
+
if (!archive)
|
|
347
|
+
throw new Error("report verify-bundle requires a bundle path (positional, --archive, --path, --file, or --bundle)");
|
|
348
|
+
const extractReportTo = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
|
|
349
|
+
return (0, run_export_1.verifyReportBundle)(node_path_1.default.resolve(base, archive), {
|
|
350
|
+
pubkey: optionalString(args.pubkey || args.pubKey || args.publicKey),
|
|
351
|
+
extractReportTo: extractReportTo ? node_path_1.default.resolve(base, extractReportTo) : undefined,
|
|
352
|
+
strictSignatures: Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs)
|
|
353
|
+
});
|
|
354
|
+
}
|
|
306
355
|
function queueAdd(reg, args) {
|
|
307
356
|
return reg.queueAdd({
|
|
308
357
|
runId: optionalString(args.runId),
|
|
@@ -496,6 +545,8 @@ exports.QUICKSTART_DEFAULT_APP = "architecture-review";
|
|
|
496
545
|
function quickstart(runner, args) {
|
|
497
546
|
const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
|
|
498
547
|
const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
|
|
548
|
+
if (isTrue(args.check))
|
|
549
|
+
return quickstartCheck(runner, appId, args, agentConfigured);
|
|
499
550
|
// `--resume`: a discoverability flag over the existing continuation. With no
|
|
500
551
|
// `--run`, advance exactly ONE step (reuse the `--once` path) and print a
|
|
501
552
|
// copy-pasteable continue line; with `--run <id>`, continue that run to
|
|
@@ -534,6 +585,33 @@ function quickstart(runner, args) {
|
|
|
534
585
|
const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
|
|
535
586
|
const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
|
|
536
587
|
const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
|
|
588
|
+
// --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
|
|
589
|
+
// bundle so the one command yields a client-verifiable artifact. Pure composition
|
|
590
|
+
// of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
|
|
591
|
+
// completion: a partial or blocked run is NEVER sealed (you must not ship an
|
|
592
|
+
// uncommitted artifact).
|
|
593
|
+
//
|
|
594
|
+
// Run-state resolution MUST anchor to the run's OWN repo (reportTarget): the README
|
|
595
|
+
// headline runs quickstart cross-directory (caller cwd != --repo), so a caller-cwd
|
|
596
|
+
// loadRun would not find the run. But operator-supplied OUTPUT paths
|
|
597
|
+
// (--output/--extract-report) and the default archive name resolve against the
|
|
598
|
+
// CALLER's cwd — so artifacts land where the operator ran the command (and `&&
|
|
599
|
+
// send out.md` works) and never pollute the analyzed repo's working tree, matching
|
|
600
|
+
// standalone `report bundle`. Pre-resolving to absolute makes path.resolve(base, …)
|
|
601
|
+
// inside reportBundle a no-op, so the run-repo cwd cannot reclaim them.
|
|
602
|
+
let bundle;
|
|
603
|
+
const wantsBundle = flag(args.bundle) === true;
|
|
604
|
+
if (wantsBundle && result.status === "complete") {
|
|
605
|
+
const callerBase = invocationCwd(args);
|
|
606
|
+
const outArg = optionalString(args.output || args.path || args.archive);
|
|
607
|
+
const extractArg = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
|
|
608
|
+
bundle = reportBundle(runner, result.runId, {
|
|
609
|
+
...args,
|
|
610
|
+
cwd: reportTarget,
|
|
611
|
+
output: node_path_1.default.resolve(callerBase, outArg || `${result.runId}.cwrun.json`),
|
|
612
|
+
...(extractArg ? { "extract-report": node_path_1.default.resolve(callerBase, extractArg) } : {})
|
|
613
|
+
});
|
|
614
|
+
}
|
|
537
615
|
let hint;
|
|
538
616
|
if (!agentConfigured) {
|
|
539
617
|
hint =
|
|
@@ -547,9 +625,14 @@ function quickstart(runner, args) {
|
|
|
547
625
|
}
|
|
548
626
|
else if (result.status === "in-progress") {
|
|
549
627
|
hint = resume
|
|
550
|
-
? `one step advanced — continue: cw quickstart ${appId} --run ${result.runId} --resume`
|
|
628
|
+
? `one step advanced — continue: cw quickstart ${appId} --run ${result.runId} --resume${wantsBundle ? " --bundle" : ""}`
|
|
551
629
|
: `one step advanced (--once) — continue: cw quickstart ${appId} --run ${result.runId} --once`;
|
|
552
630
|
}
|
|
631
|
+
// --bundle on a run that didn't complete is a NO-OP, not silence: tell the operator
|
|
632
|
+
// why nothing was sealed (Rule of Silence permits a human-facing hint).
|
|
633
|
+
if (wantsBundle && result.status !== "complete") {
|
|
634
|
+
hint = `${hint ? `${hint} ` : ""}--bundle skipped: the run did not complete (status=${result.status}); no bundle was sealed.`;
|
|
635
|
+
}
|
|
553
636
|
return {
|
|
554
637
|
schemaVersion: 1,
|
|
555
638
|
appId,
|
|
@@ -568,9 +651,138 @@ function quickstart(runner, args) {
|
|
|
568
651
|
// Stamp resumedFrom ONLY when we continued an explicit run. Conditional spread
|
|
569
652
|
// keeps the key absent on the default/fresh path (own-property absent + omitted
|
|
570
653
|
// by JSON.stringify), so default output is byte-identical.
|
|
571
|
-
...(resumeRunId ? { resumedFrom: resumeRunId } : {})
|
|
654
|
+
...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
|
|
655
|
+
// Same conditional-spread discipline: `bundle` is present only when --bundle ran
|
|
656
|
+
// on a completed drive, so the default (no --bundle) payload is byte-identical.
|
|
657
|
+
...(bundle ? { bundle } : {})
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function quickstartCheck(runner, appId, args, agentConfigured) {
|
|
661
|
+
const base = invocationCwd(args);
|
|
662
|
+
const repoArg = optionalString(args.repo) || base;
|
|
663
|
+
const repo = node_path_1.default.resolve(base, repoArg);
|
|
664
|
+
const checks = [];
|
|
665
|
+
try {
|
|
666
|
+
runner.showApp(appId);
|
|
667
|
+
checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
checks.push({
|
|
671
|
+
name: "app",
|
|
672
|
+
status: "blocked",
|
|
673
|
+
detail: `Workflow app ${appId} is not available.`,
|
|
674
|
+
fix: "Run `cw app list` and choose one of the listed app ids."
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
let repoReadable = false;
|
|
678
|
+
let repoStateWritable = false;
|
|
679
|
+
try {
|
|
680
|
+
const stat = node_fs_1.default.statSync(repo);
|
|
681
|
+
repoReadable = stat.isDirectory();
|
|
682
|
+
if (!repoReadable)
|
|
683
|
+
throw new Error("not a directory");
|
|
684
|
+
node_fs_1.default.accessSync(repo, node_fs_1.default.constants.R_OK);
|
|
685
|
+
checks.push({ name: "repo", status: "ok", detail: `Repository path is readable (${repo}).` });
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
checks.push({
|
|
689
|
+
name: "repo",
|
|
690
|
+
status: "blocked",
|
|
691
|
+
detail: `Repository path is not readable (${repo}).`,
|
|
692
|
+
fix: "Pass --repo PATH for a readable repository directory."
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
const cwDir = node_path_1.default.join(repo, ".cw");
|
|
697
|
+
node_fs_1.default.accessSync(node_fs_1.default.existsSync(cwDir) ? cwDir : repo, node_fs_1.default.constants.W_OK);
|
|
698
|
+
repoStateWritable = repoReadable;
|
|
699
|
+
checks.push({ name: "repo-state", status: "ok", detail: "Run state location is writable." });
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
checks.push({
|
|
703
|
+
name: "repo-state",
|
|
704
|
+
status: "blocked",
|
|
705
|
+
detail: "Run state location is not writable.",
|
|
706
|
+
fix: "Use a writable repo, fix directory permissions, or pass --repo to a writable checkout."
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
if (optionalString(args.question)) {
|
|
710
|
+
checks.push({ name: "question", status: "ok", detail: "Question is set." });
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
checks.push({
|
|
714
|
+
name: "question",
|
|
715
|
+
status: "blocked",
|
|
716
|
+
detail: "Question is missing.",
|
|
717
|
+
fix: "Pass --question TEXT."
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (agentConfigured) {
|
|
721
|
+
checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
checks.push({
|
|
725
|
+
name: "agent",
|
|
726
|
+
status: "blocked",
|
|
727
|
+
detail: "No agent backend is configured.",
|
|
728
|
+
fix: "Pass --agent-command \"claude -p\", set $CW_AGENT_COMMAND, or use --agent-command builtin:claude."
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
if (flag(args.bundle) === true) {
|
|
732
|
+
const trustKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
|
|
733
|
+
if (trustKey) {
|
|
734
|
+
checks.push({ name: "bundle-trust-key", status: "ok", detail: "Bundle trust public key is configured." });
|
|
735
|
+
}
|
|
736
|
+
else if (Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs)) {
|
|
737
|
+
checks.push({
|
|
738
|
+
name: "bundle-trust-key",
|
|
739
|
+
status: "blocked",
|
|
740
|
+
detail: "Strict signature verification needs a public trust key.",
|
|
741
|
+
fix: "Pass --with-trust-key PATH or set $CW_AGENT_ATTEST_PUBKEY."
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
checks.push({
|
|
746
|
+
name: "bundle-trust-key",
|
|
747
|
+
status: "warn",
|
|
748
|
+
detail: "No public trust key is configured; unsigned or unkeyed bundles may verify with reduced signature proof.",
|
|
749
|
+
fix: "Pass --with-trust-key PATH to embed the public key."
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const ok = checks.every((check) => check.status !== "blocked") && repoStateWritable;
|
|
754
|
+
return {
|
|
755
|
+
schemaVersion: 1,
|
|
756
|
+
mode: "check",
|
|
757
|
+
ok,
|
|
758
|
+
appId,
|
|
759
|
+
repo,
|
|
760
|
+
checks,
|
|
761
|
+
nextCommand: quickstartNextCommand(appId, repo, args)
|
|
572
762
|
};
|
|
573
763
|
}
|
|
764
|
+
function quickstartNextCommand(appId, repo, args) {
|
|
765
|
+
const parts = ["cw", "quickstart", shellWord(appId), "--repo", shellWord(repo)];
|
|
766
|
+
const question = optionalString(args.question);
|
|
767
|
+
if (question)
|
|
768
|
+
parts.push("--question", shellWord(question));
|
|
769
|
+
const command = optionalString(args.agentCommand || args["agent-command"]);
|
|
770
|
+
if (command)
|
|
771
|
+
parts.push("--agent-command", shellWord(command));
|
|
772
|
+
if (flag(args.bundle) === true)
|
|
773
|
+
parts.push("--bundle");
|
|
774
|
+
const trustKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey);
|
|
775
|
+
if (trustKey)
|
|
776
|
+
parts.push("--with-trust-key", shellWord(trustKey));
|
|
777
|
+
if (Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs))
|
|
778
|
+
parts.push("--strict-signatures");
|
|
779
|
+
return parts.join(" ");
|
|
780
|
+
}
|
|
781
|
+
function shellWord(value) {
|
|
782
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value))
|
|
783
|
+
return value;
|
|
784
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
785
|
+
}
|
|
574
786
|
/** Read-only, deterministic projection of the effective agent config (secret-stripped). */
|
|
575
787
|
function backendAgentConfigShow(args) {
|
|
576
788
|
return (0, agent_config_1.agentConfigShow)(args);
|
|
@@ -763,3 +975,6 @@ function auditVerify(runner, args) {
|
|
|
763
975
|
function demoTamper(_runner, _args = {}) {
|
|
764
976
|
return (0, telemetry_demo_1.runTamperDemo)();
|
|
765
977
|
}
|
|
978
|
+
function demoBundle(_runner, _args = {}) {
|
|
979
|
+
return (0, telemetry_demo_1.runBundleDemo)();
|
|
980
|
+
}
|
|
@@ -31,11 +31,13 @@ exports.mcpCapabilityForId = mcpCapabilityForId;
|
|
|
31
31
|
exports.mcpRequiredArgsForTool = mcpRequiredArgsForTool;
|
|
32
32
|
exports.mcpToolDefinition = mcpToolDefinition;
|
|
33
33
|
exports.declaredCliTokens = declaredCliTokens;
|
|
34
|
+
exports.declaredCliHelpTokens = declaredCliHelpTokens;
|
|
34
35
|
exports.requiresReason = requiresReason;
|
|
35
36
|
exports.isPayloadProbeOptOut = isPayloadProbeOptOut;
|
|
36
37
|
exports.payloadIdenticalCapabilities = payloadIdenticalCapabilities;
|
|
37
38
|
exports.payloadProbeTargets = payloadProbeTargets;
|
|
38
39
|
exports.deferredPayloadProbeCapabilities = deferredPayloadProbeCapabilities;
|
|
40
|
+
exports.buildPayloadProbePlan = buildPayloadProbePlan;
|
|
39
41
|
exports.payloadProbePlan = payloadProbePlan;
|
|
40
42
|
exports.buildParityReport = buildParityReport;
|
|
41
43
|
// ---------------------------------------------------------------------------
|
|
@@ -345,15 +347,17 @@ const BUILTIN_CAPABILITIES = [
|
|
|
345
347
|
{ capability: "run.import", summary: "Restore a portable run archive into a target repo and verify restored file digests.", entry: "runImportArchive", surface: "both", cli: { path: ["run", "import"], jsonMode: "default" }, mcp: { tool: "cw_run_import", requiredArgs: ["archive|path|file"] } },
|
|
346
348
|
{ capability: "run.verify-import", summary: "Verify an imported run against its restore manifest and telemetry chain.", entry: "runVerifyImport", surface: "both", cli: { path: ["run", "verify-import"], jsonMode: "default" }, mcp: { tool: "cw_run_verify_import", requiredArgs: ["runId"] } },
|
|
347
349
|
{ capability: "run.inspect-archive", summary: "Read-only integrity inspection of a portable run archive without importing it.", entry: "runInspectArchive", surface: "both", cli: { path: ["run", "inspect-archive"], jsonMode: "default" }, mcp: { tool: "cw_run_inspect_archive", requiredArgs: ["archive|path|file"] } },
|
|
350
|
+
{ capability: "report.verify-bundle", summary: "Offline self-contained verify of a portable run bundle: archive bytes + telemetry chain + trust-audit chain + embedded-key signatures.", entry: "runVerifyReportBundle", surface: "both", cli: { path: ["report", "verify-bundle"], caseTokens: ["report"], jsonMode: "default" }, mcp: { tool: "cw_report_verify_bundle", requiredArgs: ["archive|path|file|bundle"] } },
|
|
351
|
+
{ capability: "report.bundle", summary: "Produce-and-prove: export a run to a portable bundle sealed with the trust key, then self-verify it offline (fail-closed) so the producer knows it is verifiable before shipping.", entry: "reportBundle", surface: "both", cli: { path: ["report", "bundle"], caseTokens: ["report"], jsonMode: "default" }, mcp: { tool: "cw_report_bundle", requiredArgs: ["runId"] } },
|
|
348
352
|
{ capability: "run.drive", summary: "Preview the next agent-delegation drive step for a run (read-only, deterministic).", entry: "runDrivePreview", surface: "both", cli: { path: ["run", "drive"], caseTokens: ["run", "drive"], jsonMode: "default" }, mcp: { tool: "cw_run_drive" } },
|
|
349
353
|
{ capability: "run.drive.step", summary: "Drive a run by delegating each worker to the agent backend (plan->dispatch->fulfill->accept->commit; --once for one step).", entry: "runDrive", surface: "both", cli: { path: ["run", "drive"], caseTokens: ["run", "drive"], jsonMode: "default" }, mcp: { tool: "cw_run_drive_step" }, payloadIdentical: false, reason: "Mutating: advances the run by spawning the external agent per worker and recording attested output — not a read probe. CLI (--drive/--step) and MCP route through the same drive() core." },
|
|
350
354
|
{
|
|
351
355
|
capability: "quickstart",
|
|
352
|
-
summary: "ONE-COMMAND quickstart: plan(app, default architecture-review) -> run --drive -> report in a single invocation (--preview for a read-only dry run).",
|
|
356
|
+
summary: "ONE-COMMAND quickstart: --check preflights without writes; otherwise plan(app, default architecture-review) -> run --drive -> report in a single invocation (--preview for a read-only dry run; --bundle [--with-trust-key K] seals a completed run into a self-verified portable bundle).",
|
|
353
357
|
entry: "quickstart",
|
|
354
358
|
surface: "cli-only",
|
|
355
359
|
cli: { path: ["quickstart"], caseTokens: ["quickstart", "audit-run"], jsonMode: "default" },
|
|
356
|
-
reason: "CLI UX convenience layer (newcomer first value in one command) over the existing run.drive.step + report verbs; it spawns nothing new and delegates worker execution to the operator's agent backend. MCP hosts compose the same outcome from cw_run_drive_step + cw_report. `audit-run` is a CLI-only alias of the same wrapper."
|
|
360
|
+
reason: "CLI UX convenience layer (newcomer first value in one command) over the existing run.drive.step + report verbs; it spawns nothing new and delegates worker execution to the operator's agent backend. MCP hosts compose the same outcome from cw_run_drive_step + cw_report (+ cw_report_bundle for --bundle). `audit-run` is a CLI-only alias of the same wrapper."
|
|
357
361
|
},
|
|
358
362
|
{ capability: "queue.add", summary: "Enqueue a pending/planned run with explicit ordering policy.", entry: "runRegistry.queueAdd", surface: "both", cli: { path: ["queue", "add"], jsonMode: "default" }, mcp: { tool: "cw_queue_add" } },
|
|
359
363
|
{ capability: "queue.list", summary: "List the durable run queue in policy order.", entry: "runRegistry.queueList", surface: "both", cli: { path: ["queue", "list"], jsonMode: "flag" }, mcp: { tool: "cw_queue_list" } },
|
|
@@ -377,6 +381,7 @@ const BUILTIN_CAPABILITIES = [
|
|
|
377
381
|
{ capability: "gc.verify", summary: "Re-prove a reclaimed run: skeleton-complete, tombstone chain untampered, artifacts reconstructable.", entry: "gcVerify", surface: "both", cli: { path: ["gc", "verify"], caseTokens: ["gc", "verify"], jsonMode: "flag" }, mcp: { tool: "cw_gc_verify", requiredArgs: ["runId"] } },
|
|
378
382
|
{ capability: "telemetry.verify", summary: "Re-prove a run's telemetry attestation ledger offline: chain linkage + independent hash recompute, and (with --pubkey / CW_AGENT_ATTEST_PUBKEY) re-verify each attested hop's ed25519 signature against the public key.", entry: "telemetryVerify", surface: "both", cli: { path: ["telemetry", "verify"], caseTokens: ["telemetry"], jsonMode: "flag" }, mcp: { tool: "cw_telemetry_verify", requiredArgs: ["runId"] } },
|
|
379
383
|
{ capability: "demo.tamper", summary: "Prove tamper-evidence: build a signed telemetry ledger, forge it, watch verification fail offline.", entry: "demoTamper", surface: "cli-only", cli: { path: ["demo", "tamper"], caseTokens: ["demo", "tamper"], jsonMode: "flag" }, reason: "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface telemetry.verify. No agent or MCP client needs to invoke a demo." },
|
|
384
|
+
{ capability: "demo.bundle", summary: "Prove portable-bundle verification: export a sealed report bundle, forge it two ways, watch report verify-bundle catch both offline with only the embedded public key.", entry: "demoBundle", surface: "cli-only", cli: { path: ["demo", "bundle"], caseTokens: ["demo", "bundle"], jsonMode: "flag" }, reason: "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface report.verify-bundle. No agent or MCP client needs to invoke a demo." },
|
|
380
385
|
{ capability: "history", summary: "Read a cross-repo unified run timeline (newest first).", entry: "runRegistry.history", surface: "both", cli: { path: ["history"], jsonMode: "flag" }, mcp: { tool: "cw_history" } },
|
|
381
386
|
// ---- web / desktop workbench (v0.1.30) ----------------------------------
|
|
382
387
|
// A THIRD FRONT DOOR — a read-only renderer, not a new brain. Both verbs route
|
|
@@ -455,34 +460,63 @@ const RUN_PAYLOAD_PROBE_CAPABILITIES = [
|
|
|
455
460
|
"gc.plan",
|
|
456
461
|
"gc.verify"
|
|
457
462
|
];
|
|
463
|
+
const SCENARIO_PAYLOAD_PROBE_CAPABILITIES = [
|
|
464
|
+
"plan",
|
|
465
|
+
"app.show",
|
|
466
|
+
"app.validate",
|
|
467
|
+
"app.package",
|
|
468
|
+
"topology.show",
|
|
469
|
+
"topology.validate",
|
|
470
|
+
"topology.apply",
|
|
471
|
+
"topology.summary",
|
|
472
|
+
"topology.graph",
|
|
473
|
+
"summary.refresh",
|
|
474
|
+
"summary.show",
|
|
475
|
+
"sandbox.show",
|
|
476
|
+
"sandbox.validate",
|
|
477
|
+
"sandbox.choose",
|
|
478
|
+
"sandbox.resolve",
|
|
479
|
+
"approve",
|
|
480
|
+
"reject",
|
|
481
|
+
"comment.add",
|
|
482
|
+
"handoff",
|
|
483
|
+
"review.policy",
|
|
484
|
+
"worker.list",
|
|
485
|
+
"worker.show",
|
|
486
|
+
"worker.manifest",
|
|
487
|
+
"worker.output",
|
|
488
|
+
"worker.fail",
|
|
489
|
+
"worker.validate",
|
|
490
|
+
"candidate.list",
|
|
491
|
+
"candidate.show",
|
|
492
|
+
"candidate.register",
|
|
493
|
+
"candidate.score",
|
|
494
|
+
"candidate.rank",
|
|
495
|
+
"candidate.select",
|
|
496
|
+
"candidate.reject",
|
|
497
|
+
"feedback.list",
|
|
498
|
+
"feedback.show",
|
|
499
|
+
"feedback.collect",
|
|
500
|
+
"feedback.task",
|
|
501
|
+
"feedback.resolve",
|
|
502
|
+
"node.show",
|
|
503
|
+
"node.snapshot",
|
|
504
|
+
"node.diff",
|
|
505
|
+
"node.replay",
|
|
506
|
+
"node.replay.verify"
|
|
507
|
+
];
|
|
458
508
|
const PAYLOAD_PROBE_DEFERRED_GROUPS = [
|
|
459
509
|
{
|
|
460
510
|
reason: "Not safe for the deterministic bootstrap parity probe yet: this capability needs extra target ids/files, mutates durable state, depends on external state, or needs a dedicated fixture beyond cwd/runId.",
|
|
461
511
|
capabilities: [
|
|
462
512
|
"init",
|
|
463
|
-
"plan",
|
|
464
513
|
"dispatch",
|
|
465
514
|
"result",
|
|
466
|
-
"app.show",
|
|
467
|
-
"app.validate",
|
|
468
515
|
"app.init",
|
|
469
|
-
"app.package",
|
|
470
516
|
"app.run",
|
|
471
|
-
"node.show",
|
|
472
|
-
"node.snapshot",
|
|
473
|
-
"node.diff",
|
|
474
|
-
"node.replay",
|
|
475
|
-
"node.replay.verify",
|
|
476
517
|
"migration.list",
|
|
477
518
|
"migration.check",
|
|
478
519
|
"migration.prove",
|
|
479
|
-
"topology.show",
|
|
480
|
-
"topology.validate",
|
|
481
|
-
"topology.apply",
|
|
482
|
-
"topology.summary",
|
|
483
|
-
"topology.graph",
|
|
484
|
-
"summary.refresh",
|
|
485
|
-
"summary.show",
|
|
486
520
|
"multi-agent.run",
|
|
487
521
|
"multi-agent.status",
|
|
488
522
|
"multi-agent.step",
|
|
@@ -539,30 +573,8 @@ const PAYLOAD_PROBE_DEFERRED_GROUPS = [
|
|
|
539
573
|
"audit.judge",
|
|
540
574
|
"audit.attest",
|
|
541
575
|
"audit.decision",
|
|
542
|
-
"sandbox.show",
|
|
543
|
-
"sandbox.validate",
|
|
544
|
-
"sandbox.choose",
|
|
545
|
-
"sandbox.resolve",
|
|
546
576
|
"backend.show",
|
|
547
577
|
"backend.probe",
|
|
548
|
-
"worker.list",
|
|
549
|
-
"worker.show",
|
|
550
|
-
"worker.manifest",
|
|
551
|
-
"worker.output",
|
|
552
|
-
"worker.fail",
|
|
553
|
-
"worker.validate",
|
|
554
|
-
"candidate.list",
|
|
555
|
-
"candidate.show",
|
|
556
|
-
"candidate.register",
|
|
557
|
-
"candidate.score",
|
|
558
|
-
"candidate.rank",
|
|
559
|
-
"candidate.select",
|
|
560
|
-
"candidate.reject",
|
|
561
|
-
"feedback.list",
|
|
562
|
-
"feedback.show",
|
|
563
|
-
"feedback.collect",
|
|
564
|
-
"feedback.task",
|
|
565
|
-
"feedback.resolve",
|
|
566
578
|
"schedule.create",
|
|
567
579
|
"schedule.list",
|
|
568
580
|
"schedule.delete",
|
|
@@ -589,6 +601,8 @@ const PAYLOAD_PROBE_DEFERRED_GROUPS = [
|
|
|
589
601
|
"run.import",
|
|
590
602
|
"run.verify-import",
|
|
591
603
|
"run.inspect-archive",
|
|
604
|
+
"report.verify-bundle",
|
|
605
|
+
"report.bundle",
|
|
592
606
|
"queue.add",
|
|
593
607
|
"queue.list",
|
|
594
608
|
"queue.drain",
|
|
@@ -602,12 +616,7 @@ const PAYLOAD_PROBE_DEFERRED_GROUPS = [
|
|
|
602
616
|
"sched.policy.show",
|
|
603
617
|
"sched.policy.set",
|
|
604
618
|
"telemetry.verify",
|
|
605
|
-
"history"
|
|
606
|
-
"approve",
|
|
607
|
-
"reject",
|
|
608
|
-
"comment.add",
|
|
609
|
-
"handoff",
|
|
610
|
-
"review.policy"
|
|
619
|
+
"history"
|
|
611
620
|
]
|
|
612
621
|
}
|
|
613
622
|
];
|
|
@@ -660,6 +669,24 @@ function declaredCliTokens() {
|
|
|
660
669
|
}
|
|
661
670
|
return [...tokens].sort();
|
|
662
671
|
}
|
|
672
|
+
/** The top-level CLI commands that should be visible in `cw help`. Subcommands
|
|
673
|
+
* are intentionally collapsed to their first token; aliases such as audit-run
|
|
674
|
+
* stay visible. */
|
|
675
|
+
function declaredCliHelpTokens() {
|
|
676
|
+
const tokens = new Set();
|
|
677
|
+
for (const cap of exports.CAPABILITY_REGISTRY) {
|
|
678
|
+
if (!cap.cli)
|
|
679
|
+
continue;
|
|
680
|
+
const subcommandTokens = new Set(cap.cli.path.slice(1));
|
|
681
|
+
tokens.add(cap.cli.path[0]);
|
|
682
|
+
for (const token of cap.cli.caseTokens || []) {
|
|
683
|
+
if (!subcommandTokens.has(token))
|
|
684
|
+
tokens.add(token);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
tokens.delete("help");
|
|
688
|
+
return [...tokens].sort();
|
|
689
|
+
}
|
|
663
690
|
/** Whether a descriptor MUST carry a reason (surface-specific or divergent). */
|
|
664
691
|
function requiresReason(cap) {
|
|
665
692
|
if (cap.surface !== "both")
|
|
@@ -694,27 +721,31 @@ function payloadIdenticalCapabilities() {
|
|
|
694
721
|
function payloadProbeTargets() {
|
|
695
722
|
return [
|
|
696
723
|
...GLOBAL_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "global" })),
|
|
697
|
-
...RUN_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "run" }))
|
|
724
|
+
...RUN_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "run" })),
|
|
725
|
+
...SCENARIO_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "scenario" }))
|
|
698
726
|
];
|
|
699
727
|
}
|
|
700
728
|
function deferredPayloadProbeCapabilities() {
|
|
701
729
|
return PAYLOAD_PROBE_DEFERRED_GROUPS.flatMap((group) => group.capabilities.map((capability) => ({ capability, reason: group.reason })));
|
|
702
730
|
}
|
|
703
|
-
function
|
|
731
|
+
function buildPayloadProbePlan(targets, deferred) {
|
|
704
732
|
const candidateIds = new Set(payloadIdenticalCapabilities().map((cap) => cap.capability));
|
|
705
733
|
const counts = new Map();
|
|
706
|
-
const classified = [...
|
|
734
|
+
const classified = [...targets.map((entry) => entry.capability), ...deferred.map((entry) => entry.capability)];
|
|
707
735
|
for (const capability of classified)
|
|
708
736
|
counts.set(capability, (counts.get(capability) || 0) + 1);
|
|
709
737
|
const classifiedIds = new Set(classified);
|
|
710
738
|
return {
|
|
711
|
-
targets
|
|
712
|
-
deferred
|
|
739
|
+
targets,
|
|
740
|
+
deferred,
|
|
713
741
|
unclassified: [...candidateIds].filter((capability) => !classifiedIds.has(capability)).sort(),
|
|
714
742
|
duplicateClassifications: [...counts.entries()].filter(([, count]) => count > 1).map(([capability]) => capability).sort(),
|
|
715
743
|
invalidClassifications: [...classifiedIds].filter((capability) => !candidateIds.has(capability)).sort()
|
|
716
744
|
};
|
|
717
745
|
}
|
|
746
|
+
function payloadProbePlan() {
|
|
747
|
+
return buildPayloadProbePlan(payloadProbeTargets(), deferredPayloadProbeCapabilities());
|
|
748
|
+
}
|
|
718
749
|
function lintRegistry() {
|
|
719
750
|
const issues = [];
|
|
720
751
|
const seenCaps = new Set();
|
|
@@ -750,10 +781,14 @@ function buildParityReport(input) {
|
|
|
750
781
|
const actualTools = new Set(input.mcpTools);
|
|
751
782
|
const declaredTokens = new Set(declaredCliTokens());
|
|
752
783
|
const actualTokens = new Set(input.cliTokens);
|
|
784
|
+
const declaredHelpTokens = new Set(declaredCliHelpTokens());
|
|
785
|
+
const actualHelpTokens = new Set(input.helpTokens || []);
|
|
753
786
|
const missingMcpTools = [...declaredTools].filter((tool) => !actualTools.has(tool)).sort();
|
|
754
787
|
const undeclaredMcpTools = [...actualTools].filter((tool) => !declaredTools.has(tool)).sort();
|
|
755
788
|
const missingCliTokens = [...declaredTokens].filter((token) => !actualTokens.has(token)).sort();
|
|
756
789
|
const undeclaredCliTokens = [...actualTokens].filter((token) => !declaredTokens.has(token)).sort();
|
|
790
|
+
const helpMissingCliTokens = input.helpTokens ? [...declaredHelpTokens].filter((token) => !actualHelpTokens.has(token)).sort() : [];
|
|
791
|
+
const helpUndeclaredCliTokens = input.helpTokens ? [...actualHelpTokens].filter((token) => !declaredHelpTokens.has(token)).sort() : [];
|
|
757
792
|
const reasonlessExceptions = exports.CAPABILITY_REGISTRY.filter((cap) => requiresReason(cap) && !(cap.reason && cap.reason.trim()))
|
|
758
793
|
.map((cap) => cap.capability)
|
|
759
794
|
.sort();
|
|
@@ -763,6 +798,8 @@ function buildParityReport(input) {
|
|
|
763
798
|
undeclaredMcpTools.length === 0 &&
|
|
764
799
|
missingCliTokens.length === 0 &&
|
|
765
800
|
undeclaredCliTokens.length === 0 &&
|
|
801
|
+
helpMissingCliTokens.length === 0 &&
|
|
802
|
+
helpUndeclaredCliTokens.length === 0 &&
|
|
766
803
|
reasonlessExceptions.length === 0 &&
|
|
767
804
|
payloadPlan.unclassified.length === 0 &&
|
|
768
805
|
payloadPlan.duplicateClassifications.length === 0 &&
|
|
@@ -775,6 +812,8 @@ function buildParityReport(input) {
|
|
|
775
812
|
undeclaredMcpTools,
|
|
776
813
|
missingCliTokens,
|
|
777
814
|
undeclaredCliTokens,
|
|
815
|
+
helpMissingCliTokens,
|
|
816
|
+
helpUndeclaredCliTokens,
|
|
778
817
|
reasonlessExceptions,
|
|
779
818
|
payloadProbeUnclassified: payloadPlan.unclassified,
|
|
780
819
|
payloadProbeDuplicateClassifications: payloadPlan.duplicateClassifications,
|
|
@@ -91,7 +91,17 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
91
91
|
// fails closed (status=blocked) when none is set. No new executor/scheduler.
|
|
92
92
|
const [appId] = args.positionals;
|
|
93
93
|
const runId = optionalArg(args.options.run) || optionalArg(args.options.runId);
|
|
94
|
-
|
|
94
|
+
const qs = (0, capability_core_1.quickstart)(runner, { ...args.options, ...(appId ? { appId } : {}), ...(runId ? { runId } : {}) });
|
|
95
|
+
printJson(qs);
|
|
96
|
+
if (qs.mode === "check" && qs.ok === false) {
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
}
|
|
99
|
+
// Fail closed: if --bundle produced an artifact that does not self-verify, exit
|
|
100
|
+
// non-zero so `cw quickstart ... --bundle && send-to-client` cannot ship a report
|
|
101
|
+
// whose bundle a client could not verify. Mirrors `report bundle`.
|
|
102
|
+
if (qs.bundle && qs.bundle.ok === false) {
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
95
105
|
return;
|
|
96
106
|
}
|
|
97
107
|
case "plan": {
|
|
@@ -151,6 +161,29 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
151
161
|
printJson(runner.commit(required(args.positionals[0], "run id"), args.options));
|
|
152
162
|
return;
|
|
153
163
|
case "report": {
|
|
164
|
+
// `report verify-bundle <path>` is the offline self-contained bundle verifier;
|
|
165
|
+
// `report bundle <run-id>` exports a sealed bundle and self-verifies it;
|
|
166
|
+
// every other `report <run-id>` form prints/inspects a local run's report.
|
|
167
|
+
if (args.positionals[0] === "verify-bundle") {
|
|
168
|
+
const result = (0, capability_core_1.runVerifyReportBundle)(runner, { ...args.options, archive: args.positionals[1] || args.options.archive || args.options.path || args.options.file || args.options.bundle });
|
|
169
|
+
printJson(result);
|
|
170
|
+
// Fail closed: a forged/edited/corrupt bundle verifies false — surface it
|
|
171
|
+
// through the exit code so `cw report verify-bundle <file> && ship` cannot
|
|
172
|
+
// pass on a lie. Mirrors run inspect-archive / telemetry verify.
|
|
173
|
+
if (!result.ok)
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (args.positionals[0] === "bundle") {
|
|
178
|
+
const result = (0, capability_core_1.reportBundle)(runner, required(args.positionals[1] || optionalArg(args.options.runId || args.options.run), "run id"), args.options);
|
|
179
|
+
printJson(result);
|
|
180
|
+
// Fail closed: never report a "bundle made" success if the artifact does not
|
|
181
|
+
// self-verify — so `cw report bundle <run> && send-to-client` cannot ship an
|
|
182
|
+
// unverifiable report (e.g. no trust key under --strict-signatures).
|
|
183
|
+
if (!result.ok)
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
154
187
|
const runId = required(args.positionals[0], "run id");
|
|
155
188
|
const report = runner.report(runId);
|
|
156
189
|
if (wantsJson(args.options)) {
|
|
@@ -1250,8 +1283,20 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
1250
1283
|
process.exitCode = 1;
|
|
1251
1284
|
return;
|
|
1252
1285
|
}
|
|
1286
|
+
case "bundle": {
|
|
1287
|
+
const result = (0, capability_core_1.demoBundle)(runner, args.options);
|
|
1288
|
+
if (wantsJson(args.options))
|
|
1289
|
+
printJson(result);
|
|
1290
|
+
else
|
|
1291
|
+
process.stdout.write(`${(0, telemetry_demo_1.formatBundleDemo)(result)}\n`);
|
|
1292
|
+
// Fail closed: a forged bundle that verified would be a regression in the
|
|
1293
|
+
// bundle guarantee — exit nonzero so the demo can never green it.
|
|
1294
|
+
if (!result.proven)
|
|
1295
|
+
process.exitCode = 1;
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1253
1298
|
default:
|
|
1254
|
-
throw new Error("Usage: cw.js demo tamper [--json]");
|
|
1299
|
+
throw new Error("Usage: cw.js demo tamper|bundle [--json]");
|
|
1255
1300
|
}
|
|
1256
1301
|
}
|
|
1257
1302
|
case "workbench": {
|