cool-workflow 0.1.83 → 0.1.85

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.
Files changed (60) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +6 -0
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/capability-core.js +89 -2
  11. package/dist/capability-registry.js +269 -2
  12. package/dist/cli/command-surface.js +1362 -0
  13. package/dist/cli.js +2 -1318
  14. package/dist/mcp-server.js +5 -1452
  15. package/dist/mcp-surface.js +1467 -0
  16. package/dist/run-export.js +139 -1
  17. package/dist/telemetry-demo.js +119 -0
  18. package/dist/types/report-bundle.js +6 -0
  19. package/dist/types.js +1 -0
  20. package/dist/version.js +1 -1
  21. package/dist/worker-accept/acceptance.js +114 -0
  22. package/dist/worker-accept/blackboard-fanout.js +80 -0
  23. package/dist/worker-accept/blackboard-linkage.js +19 -0
  24. package/dist/worker-accept/context.js +2 -0
  25. package/dist/worker-accept/telemetry-ledger.js +116 -0
  26. package/dist/worker-accept/validation.js +77 -0
  27. package/dist/worker-accept/verifier-completion.js +73 -0
  28. package/dist/worker-isolation.js +19 -444
  29. package/docs/agent-delegation-drive.7.md +6 -0
  30. package/docs/cli-mcp-parity.7.md +13 -3
  31. package/docs/contract-migration-tooling.7.md +6 -0
  32. package/docs/control-plane-scheduling.7.md +6 -0
  33. package/docs/durable-state-and-locking.7.md +6 -0
  34. package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
  35. package/docs/execution-backends.7.md +6 -0
  36. package/docs/index.md +1 -0
  37. package/docs/multi-agent-cli-mcp-surface.7.md +6 -0
  38. package/docs/multi-agent-eval-replay-harness.7.md +6 -0
  39. package/docs/multi-agent-operator-ux.7.md +6 -0
  40. package/docs/node-snapshot-diff-replay.7.md +6 -0
  41. package/docs/observability-cost-accounting.7.md +6 -0
  42. package/docs/project-index.md +17 -6
  43. package/docs/real-execution-backends.7.md +6 -0
  44. package/docs/release-and-migration.7.md +6 -0
  45. package/docs/release-tooling.7.md +16 -1
  46. package/docs/report-verifiable-bundle.7.md +123 -0
  47. package/docs/run-registry-control-plane.7.md +6 -0
  48. package/docs/run-retention-reclamation.7.md +6 -0
  49. package/docs/state-explosion-management.7.md +6 -0
  50. package/docs/team-collaboration.7.md +6 -0
  51. package/docs/web-desktop-workbench.7.md +6 -0
  52. package/manifest/plugin.manifest.json +1 -1
  53. package/package.json +1 -1
  54. package/scripts/bump-version.js +9 -1
  55. package/scripts/canonical-apps.js +4 -4
  56. package/scripts/dogfood-release.js +1 -1
  57. package/scripts/golden-path.js +4 -4
  58. package/scripts/parity-check.js +27 -57
  59. package/scripts/release-flow.js +7 -6
  60. package/scripts/sync-project-index.js +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
3
  "description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
4
- "version": "0.1.83",
4
+ "version": "0.1.85",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.1.83",
3
+ "version": "0.1.85",
4
4
  "description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
package/README.md CHANGED
@@ -706,3 +706,9 @@ _This documentation tracks Cool Workflow v0.1.82. See [CHANGELOG](../../CHANGELO
706
706
  ## Hardening and Onboarding (v0.1.83)
707
707
 
708
708
  Loaders fail closed on corrupt state; store writes are made safe under more than one writer; a new cw doctor checks your setup; help lists every command; and the docs are put into Basic English.
709
+
710
+ ## Privacy Release (v0.1.84)
711
+
712
+ This release removes local user path text from saved release review input and adds a scan that keeps those words out of tracked files.
713
+
714
+ v0.1.85
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review",
4
4
  "title": "Architecture Review",
5
5
  "summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
6
- "version": "0.1.83",
6
+ "version": "0.1.85",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review-fast",
4
4
  "title": "Architecture Review Fast",
5
5
  "summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
6
- "version": "0.1.83",
6
+ "version": "0.1.85",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "end-to-end-golden-path",
4
4
  "title": "End-to-End Golden Path",
5
5
  "summary": "Deterministic one-worker workflow app for proving the CW integration chain.",
6
- "version": "0.1.83",
6
+ "version": "0.1.85",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "pr-review-fix-ci",
4
4
  "title": "PR Review Fix CI",
5
5
  "summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
6
- "version": "0.1.83",
6
+ "version": "0.1.85",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "release-cut",
4
4
  "title": "Release Cut",
5
5
  "summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
6
- "version": "0.1.83",
6
+ "version": "0.1.85",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "research-synthesis",
4
4
  "title": "Research Synthesis",
5
5
  "summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
6
- "version": "0.1.83",
6
+ "version": "0.1.85",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -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");
@@ -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
- return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), node_path_1.default.resolve(base, output));
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),
@@ -534,6 +583,33 @@ function quickstart(runner, args) {
534
583
  const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
535
584
  const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
536
585
  const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
586
+ // --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
587
+ // bundle so the one command yields a client-verifiable artifact. Pure composition
588
+ // of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
589
+ // completion: a partial or blocked run is NEVER sealed (you must not ship an
590
+ // uncommitted artifact).
591
+ //
592
+ // Run-state resolution MUST anchor to the run's OWN repo (reportTarget): the README
593
+ // headline runs quickstart cross-directory (caller cwd != --repo), so a caller-cwd
594
+ // loadRun would not find the run. But operator-supplied OUTPUT paths
595
+ // (--output/--extract-report) and the default archive name resolve against the
596
+ // CALLER's cwd — so artifacts land where the operator ran the command (and `&&
597
+ // send out.md` works) and never pollute the analyzed repo's working tree, matching
598
+ // standalone `report bundle`. Pre-resolving to absolute makes path.resolve(base, …)
599
+ // inside reportBundle a no-op, so the run-repo cwd cannot reclaim them.
600
+ let bundle;
601
+ const wantsBundle = flag(args.bundle) === true;
602
+ if (wantsBundle && result.status === "complete") {
603
+ const callerBase = invocationCwd(args);
604
+ const outArg = optionalString(args.output || args.path || args.archive);
605
+ const extractArg = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
606
+ bundle = reportBundle(runner, result.runId, {
607
+ ...args,
608
+ cwd: reportTarget,
609
+ output: node_path_1.default.resolve(callerBase, outArg || `${result.runId}.cwrun.json`),
610
+ ...(extractArg ? { "extract-report": node_path_1.default.resolve(callerBase, extractArg) } : {})
611
+ });
612
+ }
537
613
  let hint;
538
614
  if (!agentConfigured) {
539
615
  hint =
@@ -550,6 +626,11 @@ function quickstart(runner, args) {
550
626
  ? `one step advanced — continue: cw quickstart ${appId} --run ${result.runId} --resume`
551
627
  : `one step advanced (--once) — continue: cw quickstart ${appId} --run ${result.runId} --once`;
552
628
  }
629
+ // --bundle on a run that didn't complete is a NO-OP, not silence: tell the operator
630
+ // why nothing was sealed (Rule of Silence permits a human-facing hint).
631
+ if (wantsBundle && result.status !== "complete") {
632
+ hint = `${hint ? `${hint} ` : ""}--bundle skipped: the run did not complete (status=${result.status}); no bundle was sealed.`;
633
+ }
553
634
  return {
554
635
  schemaVersion: 1,
555
636
  appId,
@@ -568,7 +649,10 @@ function quickstart(runner, args) {
568
649
  // Stamp resumedFrom ONLY when we continued an explicit run. Conditional spread
569
650
  // keeps the key absent on the default/fresh path (own-property absent + omitted
570
651
  // by JSON.stringify), so default output is byte-identical.
571
- ...(resumeRunId ? { resumedFrom: resumeRunId } : {})
652
+ ...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
653
+ // Same conditional-spread discipline: `bundle` is present only when --bundle ran
654
+ // on a completed drive, so the default (no --bundle) payload is byte-identical.
655
+ ...(bundle ? { bundle } : {})
572
656
  };
573
657
  }
574
658
  /** Read-only, deterministic projection of the effective agent config (secret-stripped). */
@@ -763,3 +847,6 @@ function auditVerify(runner, args) {
763
847
  function demoTamper(_runner, _args = {}) {
764
848
  return (0, telemetry_demo_1.runTamperDemo)();
765
849
  }
850
+ function demoBundle(_runner, _args = {}) {
851
+ return (0, telemetry_demo_1.runBundleDemo)();
852
+ }
@@ -26,10 +26,17 @@
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.CAPABILITY_REGISTRY = void 0;
28
28
  exports.declaredMcpTools = declaredMcpTools;
29
+ exports.mcpCapabilityForTool = mcpCapabilityForTool;
30
+ exports.mcpCapabilityForId = mcpCapabilityForId;
31
+ exports.mcpRequiredArgsForTool = mcpRequiredArgsForTool;
32
+ exports.mcpToolDefinition = mcpToolDefinition;
29
33
  exports.declaredCliTokens = declaredCliTokens;
30
34
  exports.requiresReason = requiresReason;
31
35
  exports.isPayloadProbeOptOut = isPayloadProbeOptOut;
32
36
  exports.payloadIdenticalCapabilities = payloadIdenticalCapabilities;
37
+ exports.payloadProbeTargets = payloadProbeTargets;
38
+ exports.deferredPayloadProbeCapabilities = deferredPayloadProbeCapabilities;
39
+ exports.payloadProbePlan = payloadProbePlan;
33
40
  exports.buildParityReport = buildParityReport;
34
41
  // ---------------------------------------------------------------------------
35
42
  // Builtin entries. Grouped to mirror the CLI dispatch and the MCP tool list.
@@ -338,15 +345,17 @@ const BUILTIN_CAPABILITIES = [
338
345
  { 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"] } },
339
346
  { 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"] } },
340
347
  { 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"] } },
348
+ { 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"] } },
349
+ { 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"] } },
341
350
  { 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" } },
342
351
  { 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." },
343
352
  {
344
353
  capability: "quickstart",
345
- summary: "ONE-COMMAND quickstart: plan(app, default architecture-review) -> run --drive -> report in a single invocation (--preview for a read-only dry run).",
354
+ summary: "ONE-COMMAND quickstart: 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).",
346
355
  entry: "quickstart",
347
356
  surface: "cli-only",
348
357
  cli: { path: ["quickstart"], caseTokens: ["quickstart", "audit-run"], jsonMode: "default" },
349
- 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."
358
+ 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."
350
359
  },
351
360
  { 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" } },
352
361
  { 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" } },
@@ -370,6 +379,7 @@ const BUILTIN_CAPABILITIES = [
370
379
  { 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"] } },
371
380
  { 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"] } },
372
381
  { 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." },
382
+ { 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." },
373
383
  { 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" } },
374
384
  // ---- web / desktop workbench (v0.1.30) ----------------------------------
375
385
  // A THIRD FRONT DOOR — a read-only renderer, not a new brain. Both verbs route
@@ -412,12 +422,238 @@ const BUILTIN_CAPABILITIES = [
412
422
  * silently-dead duplicate the way the old snapshot-then-register design allowed. */
413
423
  exports.CAPABILITY_REGISTRY = Array.from(new Map(BUILTIN_CAPABILITIES.map((cap) => [cap.capability, cap])).values());
414
424
  // ---------------------------------------------------------------------------
425
+ // Payload probe classification.
426
+ // ---------------------------------------------------------------------------
427
+ const GLOBAL_PAYLOAD_PROBE_CAPABILITIES = [
428
+ "list",
429
+ "app.list",
430
+ "topology.list",
431
+ "sandbox.list",
432
+ "backend.list",
433
+ "backend.agent.config.show",
434
+ "metrics.summary"
435
+ ];
436
+ const RUN_PAYLOAD_PROBE_CAPABILITIES = [
437
+ "status",
438
+ "operator.status",
439
+ "operator.report",
440
+ "graph",
441
+ "report",
442
+ "next",
443
+ "state.check",
444
+ "contract.show",
445
+ "node.list",
446
+ "node.graph",
447
+ "worker.summary",
448
+ "candidate.summary",
449
+ "feedback.summary",
450
+ "commit.summary",
451
+ "audit.summary",
452
+ "multi-agent.summary",
453
+ "workbench.view",
454
+ "metrics.show",
455
+ "review.status",
456
+ "comment.list",
457
+ "run.drive",
458
+ "gc.plan",
459
+ "gc.verify"
460
+ ];
461
+ const PAYLOAD_PROBE_DEFERRED_GROUPS = [
462
+ {
463
+ 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.",
464
+ capabilities: [
465
+ "init",
466
+ "plan",
467
+ "dispatch",
468
+ "result",
469
+ "app.show",
470
+ "app.validate",
471
+ "app.init",
472
+ "app.package",
473
+ "app.run",
474
+ "node.show",
475
+ "node.snapshot",
476
+ "node.diff",
477
+ "node.replay",
478
+ "node.replay.verify",
479
+ "migration.list",
480
+ "migration.check",
481
+ "migration.prove",
482
+ "topology.show",
483
+ "topology.validate",
484
+ "topology.apply",
485
+ "topology.summary",
486
+ "topology.graph",
487
+ "summary.refresh",
488
+ "summary.show",
489
+ "multi-agent.run",
490
+ "multi-agent.status",
491
+ "multi-agent.step",
492
+ "multi-agent.blackboard",
493
+ "multi-agent.score",
494
+ "multi-agent.select",
495
+ "multi-agent.summarize",
496
+ "multi-agent.graph",
497
+ "multi-agent.graph.compact",
498
+ "multi-agent.dependencies",
499
+ "multi-agent.failures",
500
+ "multi-agent.evidence",
501
+ "multi-agent.reasoning",
502
+ "multi-agent.reasoning.refresh",
503
+ "multi-agent.run.create",
504
+ "multi-agent.run.transition",
505
+ "multi-agent.run.show",
506
+ "multi-agent.role.create",
507
+ "multi-agent.role.show",
508
+ "multi-agent.group.create",
509
+ "multi-agent.group.show",
510
+ "multi-agent.membership.create",
511
+ "multi-agent.membership.show",
512
+ "multi-agent.fanout.create",
513
+ "multi-agent.fanout.show",
514
+ "multi-agent.fanin.collect",
515
+ "multi-agent.fanin.show",
516
+ "eval.snapshot",
517
+ "eval.replay",
518
+ "eval.compare",
519
+ "eval.score",
520
+ "eval.gate",
521
+ "eval.report",
522
+ "blackboard.summary",
523
+ "blackboard.summarize",
524
+ "blackboard.graph",
525
+ "blackboard.resolve",
526
+ "blackboard.topic.create",
527
+ "blackboard.message.post",
528
+ "blackboard.message.list",
529
+ "blackboard.context.put",
530
+ "blackboard.artifact.add",
531
+ "blackboard.artifact.list",
532
+ "blackboard.snapshot",
533
+ "coordinator.summary",
534
+ "coordinator.decision",
535
+ "audit.verify",
536
+ "audit.worker",
537
+ "audit.provenance",
538
+ "audit.multi-agent",
539
+ "audit.policy",
540
+ "audit.role",
541
+ "audit.blackboard",
542
+ "audit.judge",
543
+ "audit.attest",
544
+ "audit.decision",
545
+ "sandbox.show",
546
+ "sandbox.validate",
547
+ "sandbox.choose",
548
+ "sandbox.resolve",
549
+ "backend.show",
550
+ "backend.probe",
551
+ "worker.list",
552
+ "worker.show",
553
+ "worker.manifest",
554
+ "worker.output",
555
+ "worker.fail",
556
+ "worker.validate",
557
+ "candidate.list",
558
+ "candidate.show",
559
+ "candidate.register",
560
+ "candidate.score",
561
+ "candidate.rank",
562
+ "candidate.select",
563
+ "candidate.reject",
564
+ "feedback.list",
565
+ "feedback.show",
566
+ "feedback.collect",
567
+ "feedback.task",
568
+ "feedback.resolve",
569
+ "schedule.create",
570
+ "schedule.list",
571
+ "schedule.delete",
572
+ "schedule.due",
573
+ "schedule.complete",
574
+ "schedule.pause",
575
+ "schedule.resume",
576
+ "schedule.run-now",
577
+ "schedule.history",
578
+ "routine.create",
579
+ "routine.list",
580
+ "routine.delete",
581
+ "routine.fire",
582
+ "routine.events",
583
+ "registry.refresh",
584
+ "registry.show",
585
+ "run.search",
586
+ "run.list",
587
+ "run.show",
588
+ "run.resume",
589
+ "run.archive",
590
+ "run.rerun",
591
+ "run.export",
592
+ "run.import",
593
+ "run.verify-import",
594
+ "run.inspect-archive",
595
+ "report.verify-bundle",
596
+ "report.bundle",
597
+ "queue.add",
598
+ "queue.list",
599
+ "queue.drain",
600
+ "queue.show",
601
+ "sched.plan",
602
+ "sched.lease",
603
+ "sched.release",
604
+ "sched.complete",
605
+ "sched.reclaim",
606
+ "sched.reset",
607
+ "sched.policy.show",
608
+ "sched.policy.set",
609
+ "telemetry.verify",
610
+ "history",
611
+ "approve",
612
+ "reject",
613
+ "comment.add",
614
+ "handoff",
615
+ "review.policy"
616
+ ]
617
+ }
618
+ ];
619
+ // ---------------------------------------------------------------------------
415
620
  // Derivations + the fail-closed parity report builder.
416
621
  // ---------------------------------------------------------------------------
417
622
  /** The MCP tool names this registry declares. */
418
623
  function declaredMcpTools() {
419
624
  return exports.CAPABILITY_REGISTRY.filter((cap) => cap.mcp).map((cap) => cap.mcp.tool);
420
625
  }
626
+ /** The descriptor for a registry-declared MCP tool name. */
627
+ function mcpCapabilityForTool(tool) {
628
+ return exports.CAPABILITY_REGISTRY.find((capability) => capability.mcp?.tool === tool);
629
+ }
630
+ /** The descriptor for a registry-declared capability id. */
631
+ function mcpCapabilityForId(capabilityId) {
632
+ const descriptor = exports.CAPABILITY_REGISTRY.find((capability) => capability.capability === capabilityId);
633
+ return descriptor?.mcp ? descriptor : undefined;
634
+ }
635
+ /** Required MCP argument groups for a registry-declared tool. */
636
+ function mcpRequiredArgsForTool(tool) {
637
+ return mcpCapabilityForTool(tool)?.mcp?.requiredArgs ?? [];
638
+ }
639
+ function mcpToolDefinition(capabilityId, descriptionOrProperties, maybeProperties) {
640
+ const descriptor = mcpCapabilityForId(capabilityId);
641
+ if (!descriptor?.mcp)
642
+ throw new Error(`MCP capability not declared: ${capabilityId}`);
643
+ const description = typeof descriptionOrProperties === "string" ? descriptionOrProperties : descriptor.summary;
644
+ const properties = typeof descriptionOrProperties === "string" ? maybeProperties : descriptionOrProperties;
645
+ if (!properties)
646
+ throw new Error(`MCP capability ${capabilityId} missing input schema properties.`);
647
+ return {
648
+ name: descriptor.mcp.tool,
649
+ description,
650
+ inputSchema: {
651
+ type: "object",
652
+ properties,
653
+ additionalProperties: true
654
+ }
655
+ };
656
+ }
421
657
  /** The CLI `case` tokens this registry declares (deduped). */
422
658
  function declaredCliTokens() {
423
659
  const tokens = new Set();
@@ -460,6 +696,30 @@ function isPayloadProbeOptOut(cap) {
460
696
  function payloadIdenticalCapabilities() {
461
697
  return exports.CAPABILITY_REGISTRY.filter((cap) => cap.surface === "both" && cap.cli && cap.mcp && !isPayloadProbeOptOut(cap));
462
698
  }
699
+ function payloadProbeTargets() {
700
+ return [
701
+ ...GLOBAL_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "global" })),
702
+ ...RUN_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "run" }))
703
+ ];
704
+ }
705
+ function deferredPayloadProbeCapabilities() {
706
+ return PAYLOAD_PROBE_DEFERRED_GROUPS.flatMap((group) => group.capabilities.map((capability) => ({ capability, reason: group.reason })));
707
+ }
708
+ function payloadProbePlan() {
709
+ const candidateIds = new Set(payloadIdenticalCapabilities().map((cap) => cap.capability));
710
+ const counts = new Map();
711
+ const classified = [...payloadProbeTargets().map((entry) => entry.capability), ...deferredPayloadProbeCapabilities().map((entry) => entry.capability)];
712
+ for (const capability of classified)
713
+ counts.set(capability, (counts.get(capability) || 0) + 1);
714
+ const classifiedIds = new Set(classified);
715
+ return {
716
+ targets: payloadProbeTargets(),
717
+ deferred: deferredPayloadProbeCapabilities(),
718
+ unclassified: [...candidateIds].filter((capability) => !classifiedIds.has(capability)).sort(),
719
+ duplicateClassifications: [...counts.entries()].filter(([, count]) => count > 1).map(([capability]) => capability).sort(),
720
+ invalidClassifications: [...classifiedIds].filter((capability) => !candidateIds.has(capability)).sort()
721
+ };
722
+ }
463
723
  function lintRegistry() {
464
724
  const issues = [];
465
725
  const seenCaps = new Set();
@@ -502,12 +762,16 @@ function buildParityReport(input) {
502
762
  const reasonlessExceptions = exports.CAPABILITY_REGISTRY.filter((cap) => requiresReason(cap) && !(cap.reason && cap.reason.trim()))
503
763
  .map((cap) => cap.capability)
504
764
  .sort();
765
+ const payloadPlan = payloadProbePlan();
505
766
  const registryLint = lintRegistry();
506
767
  const ok = missingMcpTools.length === 0 &&
507
768
  undeclaredMcpTools.length === 0 &&
508
769
  missingCliTokens.length === 0 &&
509
770
  undeclaredCliTokens.length === 0 &&
510
771
  reasonlessExceptions.length === 0 &&
772
+ payloadPlan.unclassified.length === 0 &&
773
+ payloadPlan.duplicateClassifications.length === 0 &&
774
+ payloadPlan.invalidClassifications.length === 0 &&
511
775
  registryLint.length === 0;
512
776
  return {
513
777
  ok,
@@ -517,6 +781,9 @@ function buildParityReport(input) {
517
781
  missingCliTokens,
518
782
  undeclaredCliTokens,
519
783
  reasonlessExceptions,
784
+ payloadProbeUnclassified: payloadPlan.unclassified,
785
+ payloadProbeDuplicateClassifications: payloadPlan.duplicateClassifications,
786
+ payloadProbeInvalidClassifications: payloadPlan.invalidClassifications,
520
787
  registryLint
521
788
  };
522
789
  }