cool-workflow 0.1.79 → 0.1.81

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 (131) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +51 -3
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +64 -0
  6. package/apps/architecture-review-fast/workflow.js +153 -0
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/agent-config.js +21 -7
  12. package/dist/candidate-scoring.js +42 -22
  13. package/dist/capability-core.js +132 -17
  14. package/dist/capability-registry.js +138 -168
  15. package/dist/cli.js +97 -98
  16. package/dist/collaboration.js +5 -6
  17. package/dist/commit.js +20 -6
  18. package/dist/compare.js +18 -0
  19. package/dist/coordinator/classify.js +45 -0
  20. package/dist/coordinator/paths.js +42 -0
  21. package/dist/coordinator/util.js +129 -0
  22. package/dist/coordinator.js +127 -300
  23. package/dist/dispatch.js +35 -0
  24. package/dist/drive.js +79 -6
  25. package/dist/error-feedback.js +8 -4
  26. package/dist/evidence-reasoning.js +3 -3
  27. package/dist/execution-backend/agent.js +331 -0
  28. package/dist/execution-backend/probes.js +96 -0
  29. package/dist/execution-backend/util.js +47 -0
  30. package/dist/execution-backend.js +73 -421
  31. package/dist/mcp-server.js +79 -183
  32. package/dist/multi-agent/graph.js +84 -0
  33. package/dist/multi-agent/helpers.js +145 -0
  34. package/dist/multi-agent/paths.js +22 -0
  35. package/dist/multi-agent-eval/format.js +194 -0
  36. package/dist/multi-agent-eval/normalize.js +51 -0
  37. package/dist/multi-agent-eval.js +39 -244
  38. package/dist/multi-agent-host.js +0 -19
  39. package/dist/multi-agent.js +125 -314
  40. package/dist/node-snapshot.js +3 -3
  41. package/dist/observability/format.js +61 -0
  42. package/dist/observability/intake.js +98 -0
  43. package/dist/observability.js +14 -160
  44. package/dist/operator-ux/format.js +364 -0
  45. package/dist/operator-ux.js +22 -363
  46. package/dist/orchestrator/lifecycle-operations.js +2 -1
  47. package/dist/orchestrator/report.js +8 -0
  48. package/dist/orchestrator.js +26 -9
  49. package/dist/reclamation.js +26 -21
  50. package/dist/run-export.js +494 -25
  51. package/dist/run-registry/derive.js +172 -0
  52. package/dist/run-registry/format.js +124 -0
  53. package/dist/run-registry/gc.js +251 -0
  54. package/dist/run-registry/policy.js +16 -0
  55. package/dist/run-registry/queue.js +116 -0
  56. package/dist/run-registry.js +89 -597
  57. package/dist/run-state-schema.js +1 -0
  58. package/dist/sandbox-profile.js +43 -2
  59. package/dist/state-explosion/format.js +159 -0
  60. package/dist/state-explosion/helpers.js +82 -0
  61. package/dist/state-explosion.js +165 -304
  62. package/dist/state-node.js +19 -4
  63. package/dist/telemetry-attestation.js +55 -0
  64. package/dist/telemetry-demo.js +15 -3
  65. package/dist/telemetry-ledger.js +60 -15
  66. package/dist/topology.js +25 -8
  67. package/dist/triggers.js +33 -14
  68. package/dist/trust-audit.js +145 -33
  69. package/dist/version.js +1 -1
  70. package/dist/worker-isolation/helpers.js +51 -0
  71. package/dist/worker-isolation/paths.js +46 -0
  72. package/dist/worker-isolation.js +39 -115
  73. package/docs/agent-delegation-drive.7.md +71 -0
  74. package/docs/canonical-workflow-apps.7.md +37 -0
  75. package/docs/cli-mcp-parity.7.md +16 -0
  76. package/docs/contract-migration-tooling.7.md +6 -0
  77. package/docs/control-plane-scheduling.7.md +6 -0
  78. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
  79. package/docs/durable-state-and-locking.7.md +8 -0
  80. package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
  81. package/docs/execution-backends.7.md +6 -0
  82. package/docs/index.md +2 -0
  83. package/docs/launch/demo.tape +28 -0
  84. package/docs/launch/launch-kit.md +96 -17
  85. package/docs/launch/pre-launch-checklist.md +53 -0
  86. package/docs/multi-agent-cli-mcp-surface.7.md +8 -0
  87. package/docs/multi-agent-eval-replay-harness.7.md +6 -0
  88. package/docs/multi-agent-operator-ux.7.md +6 -0
  89. package/docs/multi-agent-trust-policy-audit.7.md +27 -0
  90. package/docs/node-snapshot-diff-replay.7.md +6 -0
  91. package/docs/observability-cost-accounting.7.md +6 -0
  92. package/docs/project-index.md +27 -6
  93. package/docs/real-execution-backends.7.md +6 -0
  94. package/docs/release-and-migration.7.md +8 -0
  95. package/docs/release-tooling.7.md +6 -0
  96. package/docs/routines.md +23 -0
  97. package/docs/run-registry-control-plane.7.md +89 -2
  98. package/docs/run-retention-reclamation.7.md +8 -0
  99. package/docs/source-context-profiles.7.md +119 -0
  100. package/docs/state-explosion-management.7.md +13 -0
  101. package/docs/team-collaboration.7.md +6 -0
  102. package/docs/trust-model.md +267 -0
  103. package/docs/unix-principles.md +49 -1
  104. package/docs/vendor-manifest-loadability.7.md +43 -0
  105. package/docs/web-desktop-workbench.7.md +6 -0
  106. package/manifest/plugin.manifest.json +1 -1
  107. package/manifest/source-context-profiles.json +142 -0
  108. package/package.json +4 -1
  109. package/scripts/agents/builtin-templates.json +7 -0
  110. package/scripts/agents/claude-p-agent.js +129 -43
  111. package/scripts/architecture-review-fast.js +362 -0
  112. package/scripts/bump-version.js +5 -10
  113. package/scripts/canonical-apps-list.js +64 -0
  114. package/scripts/canonical-apps.js +36 -4
  115. package/scripts/coverage-gate.js +211 -0
  116. package/scripts/dogfood-release.js +1 -1
  117. package/scripts/golden-path.js +4 -4
  118. package/scripts/parity-check.js +5 -0
  119. package/scripts/release-check.js +5 -1
  120. package/scripts/source-context.js +291 -0
  121. package/scripts/version-sync-check.js +5 -7
  122. package/skills/ci-triage/SKILL.md +50 -0
  123. package/skills/ci-triage/agents/openai.yaml +4 -0
  124. package/skills/cool-workflow/SKILL.md +4 -1
  125. package/skills/deploy-check/SKILL.md +55 -0
  126. package/skills/deploy-check/agents/openai.yaml +4 -0
  127. package/skills/design-qa/SKILL.md +49 -0
  128. package/skills/design-qa/agents/openai.yaml +4 -0
  129. package/skills/pr-review/SKILL.md +45 -0
  130. package/skills/pr-review/agents/openai.yaml +4 -0
  131. package/dist/capability-dispatcher.js +0 -86
package/dist/cli.js CHANGED
@@ -7,7 +7,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const orchestrator_1 = require("./orchestrator");
10
- const capability_registry_1 = require("./capability-registry");
11
10
  const capability_core_1 = require("./capability-core");
12
11
  const observability_1 = require("./observability");
13
12
  const telemetry_demo_1 = require("./telemetry-demo");
@@ -70,8 +69,6 @@ async function main() {
70
69
  printJson((0, capability_core_1.appRun)(runner, { ...args.options, appId: required(appIdOrPath, "app id") }));
71
70
  return;
72
71
  default:
73
- if (await tryDispatchCli(args, runner))
74
- return;
75
72
  throw new Error("Usage: cw.js app list|show|validate|init|package|run [app-id|path]");
76
73
  }
77
74
  }
@@ -128,8 +125,6 @@ async function main() {
128
125
  return;
129
126
  }
130
127
  default:
131
- if (await tryDispatchCli(args, runner))
132
- return;
133
128
  throw new Error("Usage: cw.js state check <run-id> [--state PATH] [--write]");
134
129
  }
135
130
  }
@@ -175,8 +170,6 @@ async function main() {
175
170
  process.stdout.write(`${(0, operator_ux_1.formatOperatorReport)(runner.operatorReport(required(runId, "run id")))}\n`);
176
171
  return;
177
172
  default:
178
- if (await tryDispatchCli(args, runner))
179
- return;
180
173
  throw new Error("Usage: cw.js operator status|report <run-id> [--json]");
181
174
  }
182
175
  }
@@ -227,8 +220,6 @@ async function main() {
227
220
  return;
228
221
  }
229
222
  default:
230
- if (await tryDispatchCli(args, runner))
231
- return;
232
223
  throw new Error("Usage: cw.js topology list|show <topology-id>|show <run-id> <topology-run-id>|validate <topology-id>|apply <run-id> <topology-id>|summary <run-id>|graph <run-id>");
233
224
  }
234
225
  }
@@ -252,8 +243,6 @@ async function main() {
252
243
  return;
253
244
  }
254
245
  default:
255
- if (await tryDispatchCli(args, runner))
256
- return;
257
246
  throw new Error("Usage: cw.js summary refresh|show <run-id> [--json]");
258
247
  }
259
248
  }
@@ -410,8 +399,6 @@ async function main() {
410
399
  }
411
400
  return;
412
401
  default:
413
- if (await tryDispatchCli(args, runner))
414
- return;
415
402
  throw new Error("Usage: cw.js multi-agent run|status|step|blackboard|score|select|summary|summarize|graph|dependencies|failures|evidence|reasoning|show|role|group|membership|fanout|fanin <run-id> [id]");
416
403
  }
417
404
  }
@@ -440,8 +427,6 @@ async function main() {
440
427
  result = runner.evalReport(required(first, "replay id or path"));
441
428
  break;
442
429
  default:
443
- if (await tryDispatchCli(args, runner))
444
- return;
445
430
  throw new Error("Usage: cw.js eval snapshot <run-id> --id <snapshot-id> | replay <snapshot-id-or-path> | compare <baseline-id-or-path> <replay-id-or-path> | score <replay-id-or-path> | gate <suite-id-or-path> | report <replay-id-or-path>");
446
431
  }
447
432
  if (wantsJson(args.options))
@@ -510,8 +495,6 @@ async function main() {
510
495
  default:
511
496
  break;
512
497
  }
513
- if (await tryDispatchCli(args, runner))
514
- return;
515
498
  throw new Error("Usage: cw.js blackboard summary|summarize|graph|resolve <run-id> | topic create <run-id> | message post|list <run-id> | context put <run-id> | artifact add|list <run-id> | snapshot <run-id>");
516
499
  }
517
500
  case "coordinator": {
@@ -524,8 +507,6 @@ async function main() {
524
507
  printJson(runner.recordCoordinatorDecision(required(runId, "run id"), args.options));
525
508
  return;
526
509
  default:
527
- if (await tryDispatchCli(args, runner))
528
- return;
529
510
  throw new Error("Usage: cw.js coordinator summary <run-id> | coordinator decision <run-id> --kind <kind> --outcome <outcome> --reason TEXT");
530
511
  }
531
512
  }
@@ -538,16 +519,18 @@ async function main() {
538
519
  case "show":
539
520
  printJson(runner.showSandboxProfile(required(profileIdOrFile, "profile id"), args.options));
540
521
  return;
541
- case "validate":
542
- printJson(runner.validateSandboxProfile(required(profileIdOrFile, "profile file"), args.options));
522
+ case "validate": {
523
+ const result = runner.validateSandboxProfile(required(profileIdOrFile, "profile file"), args.options);
524
+ printJson(result);
525
+ if (!result.valid)
526
+ process.exitCode = 1;
543
527
  return;
528
+ }
544
529
  case "choose":
545
530
  case "resolve":
546
531
  printJson((0, capability_core_1.sandboxChoose)(runner, { ...args.options, profileId: profileIdOrFile || args.options.profileId }));
547
532
  return;
548
533
  default:
549
- if (await tryDispatchCli(args, runner))
550
- return;
551
534
  throw new Error("Usage: cw.js sandbox list|show|validate|choose|resolve [profile-id|profile-file]");
552
535
  }
553
536
  }
@@ -574,8 +557,6 @@ async function main() {
574
557
  return;
575
558
  }
576
559
  default:
577
- if (await tryDispatchCli(args, runner))
578
- return;
579
560
  throw new Error("Usage: cw.js backend list|show|probe [backend-id] | cw.js backend agent config [show|set] [--agent-command ... --agent-endpoint ... --agent-model ...]");
580
561
  }
581
562
  }
@@ -586,8 +567,6 @@ async function main() {
586
567
  printJson(runner.showContract(required(runId, "run id"), contractId));
587
568
  return;
588
569
  default:
589
- if (await tryDispatchCli(args, runner))
590
- return;
591
570
  throw new Error("Usage: cw.js contract show <run-id> [contract-id]");
592
571
  }
593
572
  }
@@ -615,12 +594,14 @@ async function main() {
615
594
  case "replay":
616
595
  printJson(runner.nodeReplay(required(runId, "run id"), required(nodeId, "snapshot id")));
617
596
  return;
618
- case "verify":
619
- printJson(runner.nodeReplayVerify(required(runId, "run id"), required(nodeId, "replay id")));
597
+ case "verify": {
598
+ const verdict = runner.nodeReplayVerify(required(runId, "run id"), required(nodeId, "replay id"));
599
+ printJson(verdict);
600
+ if (!verdict.pass)
601
+ process.exitCode = 1;
620
602
  return;
603
+ }
621
604
  default:
622
- if (await tryDispatchCli(args, runner))
623
- return;
624
605
  throw new Error("Usage: cw.js node list|show|graph|snapshot|diff|replay|verify <run-id> [node-id|snapshot-id|replay-id]");
625
606
  }
626
607
  }
@@ -630,15 +611,21 @@ async function main() {
630
611
  case "list":
631
612
  printJson(runner.migrationList());
632
613
  return;
633
- case "check":
634
- printJson(runner.migrationCheck(required(target, "target (run-id or state/app file)"), args.options));
614
+ case "check": {
615
+ const report = runner.migrationCheck(required(target, "target (run-id or state/app file)"), args.options);
616
+ printJson(report);
617
+ if (report.status === "unsupported")
618
+ process.exitCode = 1;
635
619
  return;
636
- case "prove":
637
- printJson(runner.migrationProve(required(target, "target (run-id or state/app file)"), args.options));
620
+ }
621
+ case "prove": {
622
+ const proof = runner.migrationProve(required(target, "target (run-id or state/app file)"), args.options);
623
+ printJson(proof);
624
+ if (!proof.pass)
625
+ process.exitCode = 1;
638
626
  return;
627
+ }
639
628
  default:
640
- if (await tryDispatchCli(args, runner))
641
- return;
642
629
  throw new Error("Usage: cw.js migration list|check|prove [target] [--contract run-state|workflow-app]");
643
630
  }
644
631
  }
@@ -669,8 +656,6 @@ async function main() {
669
656
  printJson(runner.resolveFeedback(required(runId, "run id"), required(feedbackId, "feedback id"), args.options));
670
657
  return;
671
658
  default:
672
- if (await tryDispatchCli(args, runner))
673
- return;
674
659
  throw new Error("Usage: cw.js feedback list|show|summary|collect|task|resolve <run-id> [feedback-id]");
675
660
  }
676
661
  }
@@ -698,14 +683,18 @@ async function main() {
698
683
  printJson(runner.recordWorkerOutput(required(runId, "run id"), required(workerId, "worker id"), required(resultPath, "result file"), args.options));
699
684
  return;
700
685
  case "fail":
701
- printJson(runner.recordWorkerFailure(required(runId, "run id"), required(workerId, "worker id"), String(args.options.message || args.options.m || required(resultPath, "failure message")), args.options));
686
+ printJson(runner.recordWorkerFailure(required(runId, "run id"), required(workerId, "worker id"), String(args.options.message || required(resultPath, "failure message")), args.options));
702
687
  return;
703
- case "validate":
704
- printJson(runner.validateWorker(required(runId, "run id"), required(workerId, "worker id"), resultPath));
688
+ case "validate": {
689
+ // Non-null = a boundary violation: a validate verb must report an invalid
690
+ // verdict through its exit code, not just print it and exit 0.
691
+ const violation = runner.validateWorker(required(runId, "run id"), required(workerId, "worker id"), resultPath);
692
+ printJson(violation);
693
+ if (violation)
694
+ process.exitCode = 1;
705
695
  return;
696
+ }
706
697
  default:
707
- if (await tryDispatchCli(args, runner))
708
- return;
709
698
  throw new Error("Usage: cw.js worker list|summary|show|manifest|output|fail|validate <run-id> [worker-id] [result-file]");
710
699
  }
711
700
  }
@@ -715,6 +704,19 @@ async function main() {
715
704
  case "summary":
716
705
  printJson(runner.auditSummary(required(runId, "run id")));
717
706
  return;
707
+ case "verify": {
708
+ const result = (0, capability_core_1.auditVerify)(runner, { ...args.options, runId: required(runId, "run id") });
709
+ printJson(result);
710
+ // Fail-closed: any unverified chain exits non-zero so `cw audit verify
711
+ // <run> && deploy` stops — mirrors the telemetry-verify guard. verifyTrustAudit
712
+ // returns verified:true for a truly absent/empty chain (nothing to prove),
713
+ // so this stays exit 0 there; a FULLY-corrupt log reports present:false but
714
+ // verified:false (corruptLines>0) and must NOT be conflated with absent — the
715
+ // earlier `present && ...` guard let that severe tamper escape (exit 0).
716
+ if (!result.verified)
717
+ process.exitCode = 1;
718
+ return;
719
+ }
718
720
  case "worker":
719
721
  printJson(runner.workerAudit(required(runId, "run id"), required(id, "worker id")));
720
722
  return;
@@ -768,8 +770,6 @@ async function main() {
768
770
  printJson(runner.recordAuditDecision(required(runId, "run id"), required(id, "worker id"), args.options));
769
771
  return;
770
772
  default:
771
- if (await tryDispatchCli(args, runner))
772
- return;
773
773
  throw new Error("Usage: cw.js audit summary|worker|provenance|multi-agent|policy|role|blackboard|judge|attest|decision <run-id> [worker-id|role-id]");
774
774
  }
775
775
  }
@@ -804,8 +804,6 @@ async function main() {
804
804
  process.stdout.write(`${(0, operator_ux_1.formatCandidateSummary)(runner.summarizeCandidateOperatorRecords(required(runId, "run id")))}\n`);
805
805
  return;
806
806
  default:
807
- if (await tryDispatchCli(args, runner))
808
- return;
809
807
  throw new Error("Usage: cw.js candidate list|show|register|score|rank|select|reject|summary <run-id> [candidate-id]");
810
808
  }
811
809
  }
@@ -835,8 +833,6 @@ async function main() {
835
833
  process.stdout.write(`${runner.formatCommentList(result.comments)}\n`);
836
834
  return;
837
835
  }
838
- if (await tryDispatchCli(args, runner))
839
- return;
840
836
  throw new Error("Usage: cw.js comment add <kind> <run-id> <target-id> --body <text> | comment list <run-id> [--json]");
841
837
  }
842
838
  case "handoff": {
@@ -910,8 +906,6 @@ async function main() {
910
906
  return;
911
907
  }
912
908
  default:
913
- if (await tryDispatchCli(args, runner))
914
- return;
915
909
  throw new Error("Usage: cw.js schedule create|list|delete|due|complete|pause|resume|run-now|history|daemon");
916
910
  }
917
911
  }
@@ -937,8 +931,6 @@ async function main() {
937
931
  printJson(triggers.events(idOrKind));
938
932
  return;
939
933
  default:
940
- if (await tryDispatchCli(args, runner))
941
- return;
942
934
  throw new Error("Usage: cw.js routine create|list|delete|fire|events");
943
935
  }
944
936
  }
@@ -963,8 +955,6 @@ async function main() {
963
955
  return;
964
956
  }
965
957
  default:
966
- if (await tryDispatchCli(args, runner))
967
- return;
968
958
  throw new Error("Usage: cw.js registry refresh|show [--scope repo|home] [--json]");
969
959
  }
970
960
  }
@@ -996,7 +986,15 @@ async function main() {
996
986
  // run end-to-end by delegating each worker to the agent backend. Distinct from
997
987
  // the run-REGISTRY verbs below. `--preview` (or the `run drive <run-id>` form)
998
988
  // is the read-only, deterministic next-step preview.
999
- if (args.options.drive) {
989
+ //
990
+ // A run-REGISTRY subcommand keyword (resume/show/...) must NOT be intercepted
991
+ // here just because it carries a --drive flag of its own — e.g.
992
+ // `run resume <id> --drive` is the resume verb's opt-in continuation, not
993
+ // `run <app=resume> --drive`. Fall through to the switch for those keywords.
994
+ const runRegistrySubcommand = new Set([
995
+ "drive", "search", "list", "show", "resume", "archive", "rerun", "export", "import", "verify-import", "inspect-archive"
996
+ ]);
997
+ if (args.options.drive && !runRegistrySubcommand.has(String(args.positionals[0] || ""))) {
1000
998
  const target = args.positionals[0];
1001
999
  const runId = optionalArg(args.options.run) || optionalArg(args.options.runId);
1002
1000
  if (args.options.preview) {
@@ -1051,7 +1049,7 @@ async function main() {
1051
1049
  return;
1052
1050
  }
1053
1051
  case "resume": {
1054
- const result = (0, capability_core_1.runResume)(registry, required(id, "run id"), args.options);
1052
+ const result = (0, capability_core_1.runResume)(registry, runner, required(id, "run id"), args.options);
1055
1053
  if (wantsJson(args.options))
1056
1054
  printJson(result);
1057
1055
  else
@@ -1064,10 +1062,33 @@ async function main() {
1064
1062
  case "rerun":
1065
1063
  printJson((0, capability_core_1.runRerun)(registry, required(id, "run id"), args.options));
1066
1064
  return;
1065
+ case "export":
1066
+ printJson((0, capability_core_1.runExportArchive)(runner, required(id || optionalArg(args.options.runId || args.options.run), "run id"), args.options));
1067
+ return;
1068
+ case "import":
1069
+ printJson((0, capability_core_1.runImportArchive)(runner, { ...args.options, archive: id || args.options.archive || args.options.path }));
1070
+ return;
1071
+ case "verify-import": {
1072
+ const result = (0, capability_core_1.runVerifyImport)(runner, required(id || optionalArg(args.options.runId || args.options.run), "run id"), args.options);
1073
+ printJson(result);
1074
+ // Fail-closed ONLY behind --strict, so the default exit stays 0
1075
+ // (byte-identical). With --strict, any failed restore check — including
1076
+ // the new trust-audit row — exits 1 for `verify-import && restore`.
1077
+ if (Boolean(args.options.strict) && !result.ok)
1078
+ process.exitCode = 1;
1079
+ return;
1080
+ }
1081
+ case "inspect-archive": {
1082
+ const result = (0, capability_core_1.runInspectArchive)(runner, { ...args.options, archive: id || args.options.archive || args.options.path });
1083
+ printJson(result);
1084
+ // Read-only diagnostic: exit 1 when the archive fails any integrity check,
1085
+ // so `cw run inspect-archive <path> && restore` stops on a bad archive.
1086
+ if (!result.ok)
1087
+ process.exitCode = 1;
1088
+ return;
1089
+ }
1067
1090
  default:
1068
- if (await tryDispatchCli(args, runner))
1069
- return;
1070
- throw new Error("Usage: cw.js run search|list|show|resume|archive|rerun|drive [run-id] [--scope repo|home] [--json] | cw.js run <app> --drive [--once] [--repo R --question Q]");
1091
+ throw new Error("Usage: cw.js run search|list|show|resume|archive|rerun|drive|export|import|verify-import|inspect-archive [run-id|archive] [--scope repo|home] [--json] | cw.js run <app> --drive [--once] [--repo R --question Q]");
1071
1092
  }
1072
1093
  }
1073
1094
  case "queue": {
@@ -1092,8 +1113,6 @@ async function main() {
1092
1113
  printJson((0, capability_core_1.queueShow)(registry, required(id, "queue id")));
1093
1114
  return;
1094
1115
  default:
1095
- if (await tryDispatchCli(args, runner))
1096
- return;
1097
1116
  throw new Error("Usage: cw.js queue add|list|drain|show [queue-id] [--repo PATH] [--priority N]");
1098
1117
  }
1099
1118
  }
@@ -1129,8 +1148,6 @@ async function main() {
1129
1148
  return;
1130
1149
  }
1131
1150
  default:
1132
- if (await tryDispatchCli(args, runner))
1133
- return;
1134
1151
  throw new Error("Usage: cw.js sched plan|lease|release|complete|reclaim|reset|policy [show|set] [id] [--maxConcurrent N --maxAttempts N ...]");
1135
1152
  }
1136
1153
  }
@@ -1163,11 +1180,18 @@ async function main() {
1163
1180
  printJson(result);
1164
1181
  else
1165
1182
  process.stdout.write(`${(0, run_registry_1.formatGcVerify)(result)}\n`);
1183
+ // Fail closed ONLY on a real integrity failure: a run that WAS reclaimed
1184
+ // but no longer re-proves. A not-reclaimed run has nothing to verify
1185
+ // (reclaimed:false/verified:false) and must not be treated as a failure.
1186
+ // LIMIT (honest): a DELETED reclaimed.json reads as reclaimed:false, so
1187
+ // proof-deletion is indistinguishable from never-reclaimed here without
1188
+ // an independent witness (e.g. a trust-audit reclamation event) — a
1189
+ // follow-up. This guard is still strictly better than the prior exit-0.
1190
+ if (result.reclaimed && !result.verified)
1191
+ process.exitCode = 1;
1166
1192
  return;
1167
1193
  }
1168
1194
  default:
1169
- if (await tryDispatchCli(args, runner))
1170
- return;
1171
1195
  throw new Error("Usage: cw.js gc plan|run|verify [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--limit N] [--json]");
1172
1196
  }
1173
1197
  }
@@ -1189,12 +1213,15 @@ async function main() {
1189
1213
  printJson(result);
1190
1214
  else
1191
1215
  process.stdout.write(`${(0, telemetry_demo_1.formatTelemetryVerify)(result)}\n`);
1216
+ // Fail closed: a forged/edited/corrupt ledger verifies false — report it
1217
+ // through the exit code so `cw telemetry verify <run> && deploy` cannot
1218
+ // pass on a lie. (Absent ledger = present:false/verified:true -> exit 0.)
1219
+ if (!result.verified)
1220
+ process.exitCode = 1;
1192
1221
  return;
1193
1222
  }
1194
1223
  default:
1195
- if (await tryDispatchCli(args, runner))
1196
- return;
1197
- throw new Error("Usage: cw.js telemetry verify <run-id> [--json]");
1224
+ throw new Error("Usage: cw.js telemetry verify <run-id> [--pubkey <pem-or-path>] [--json]");
1198
1225
  }
1199
1226
  }
1200
1227
  case "demo": {
@@ -1213,8 +1240,6 @@ async function main() {
1213
1240
  return;
1214
1241
  }
1215
1242
  default:
1216
- if (await tryDispatchCli(args, runner))
1217
- return;
1218
1243
  throw new Error("Usage: cw.js demo tamper [--json]");
1219
1244
  }
1220
1245
  }
@@ -1247,39 +1272,13 @@ async function main() {
1247
1272
  return;
1248
1273
  }
1249
1274
  default:
1250
- if (await tryDispatchCli(args, runner))
1251
- return;
1252
1275
  throw new Error("Usage: cw.js workbench serve [--port N] [--once] | view <run-id> [--json]");
1253
1276
  }
1254
1277
  }
1255
1278
  default:
1256
- if (await tryDispatchCli(args, runner))
1257
- return;
1258
1279
  throw new Error(`Unknown command: ${args.command}`);
1259
1280
  }
1260
1281
  }
1261
- /** Try to dispatch a command through the dynamic capability registry.
1262
- * Mechanism: reconstructs the full CLI path, resolves a handler, invokes it.
1263
- * Policy: unknown commands fall through to the legacy switch; this is a thin pipe.
1264
- * Returns true when the command was dispatched. */
1265
- async function tryDispatchCli(args, runner) {
1266
- const cliPath = [args.command, ...args.positionals].filter((token) => typeof token === "string");
1267
- if (!cliPath.length)
1268
- return false;
1269
- const capabilityId = (0, capability_registry_1.resolveCliPath)(cliPath);
1270
- if (!capabilityId)
1271
- return false;
1272
- const handler = (0, capability_registry_1.getCapabilityHandler)(capabilityId);
1273
- if (!handler)
1274
- return false;
1275
- const result = await (0, capability_registry_1.dispatchCapability)(capabilityId, args.options, {
1276
- runner,
1277
- cwd: String(args.options.cwd || process.cwd())
1278
- });
1279
- if (wantsJson(args.options) || handler.descriptor.cli?.jsonMode === "default")
1280
- printJson(result);
1281
- return true;
1282
- }
1283
1282
  function required(value, label) {
1284
1283
  if (!value)
1285
1284
  throw new Error(`Missing ${label}`);
@@ -553,7 +553,7 @@ function buildTimeline(run) {
553
553
  summary: `review policy: ${policy.requiredApprovals} approval(s) from [${policy.authorizedRoles.join(", ")}] for [${policy.appliesTo.join(", ")}]`
554
554
  });
555
555
  }
556
- return entries.sort(compareTimeline);
556
+ return entries.sort(compareByCreated);
557
557
  }
558
558
  function buildNextActions(run, states, policy) {
559
559
  const actions = [];
@@ -674,8 +674,10 @@ function targetKey(target) {
674
674
  return `${target.kind}:${target.id}`;
675
675
  }
676
676
  function createCollabId(run, kind, count) {
677
- const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
678
- return `collab-${(0, state_1.safeFileName)(kind)}-${stamp}-${String(count + 1).padStart(4, "0")}`;
677
+ // Deterministic (FreeBSD-audit L12/L13): caller-supplied count (approvals/comments/
678
+ // handoffs length), no wall-clock stamp. The collab id is bound into the trust-audit
679
+ // chain via linkedAuditEventIds, so a stable id keeps that reproducible.
680
+ return `collab-${(0, state_1.safeFileName)(kind)}-${String(count + 1).padStart(4, "0")}`;
679
681
  }
680
682
  function persist(run, options) {
681
683
  if (options.persist === false)
@@ -685,9 +687,6 @@ function persist(run, options) {
685
687
  function compareByCreated(left, right) {
686
688
  return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
687
689
  }
688
- function compareTimeline(left, right) {
689
- return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
690
- }
691
690
  function compact(value) {
692
691
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
693
692
  }
package/dist/commit.js CHANGED
@@ -17,6 +17,7 @@ const trust_audit_1 = require("./trust-audit");
17
17
  const collaboration_1 = require("./collaboration");
18
18
  const evidence_grounding_1 = require("./evidence-grounding");
19
19
  const verifier_1 = require("./verifier");
20
+ const compare_1 = require("./compare");
20
21
  class CommitGateError extends Error {
21
22
  structured;
22
23
  feedbackId;
@@ -37,7 +38,7 @@ function commitState(run, input) {
37
38
  throw recordCommitGateFailure(run, options, gate);
38
39
  }
39
40
  node_fs_1.default.mkdirSync(run.paths.commitsDir, { recursive: true });
40
- const id = createCommitId();
41
+ const id = createCommitId(run);
41
42
  const snapshotPath = node_path_1.default.join(run.paths.commitsDir, `${id}.json`);
42
43
  const audit = gate.verifierGated
43
44
  ? (0, trust_audit_1.recordTrustAuditEvent)(run, {
@@ -440,7 +441,7 @@ function recordCommitNode(run, commit, options, gate) {
440
441
  function recordCommitGateFailure(run, options, gate) {
441
442
  const first = gate.errors[0] || error("commit-gate-blocked", "Verifier-gated commit blocked");
442
443
  const node = (0, state_node_1.recordNodeError)((0, state_node_1.createStateNode)({
443
- id: `${run.id}:commit-gate-failed:${createCommitId()}`,
444
+ id: `${run.id}:commit-gate-failed:${gateFailureSeq(run)}`,
444
445
  kind: "error",
445
446
  status: "pending",
446
447
  loopStage: "checkpoint",
@@ -511,7 +512,7 @@ function resolveLinkedVerifier(requested, linked, errors, ownerKind, ownerId) {
511
512
  function latestSelectionForCandidate(run, candidateId) {
512
513
  return [...(run.candidateSelections || [])]
513
514
  .filter((selection) => selection.candidateId === candidateId)
514
- .sort((left, right) => right.selectedAt.localeCompare(left.selectedAt))[0];
515
+ .sort((left, right) => (0, compare_1.compareBytes)(right.selectedAt, left.selectedAt))[0];
515
516
  }
516
517
  function findSelection(run, selectionId) {
517
518
  return (run.candidateSelections || []).find((selection) => selection.id === selectionId);
@@ -552,9 +553,22 @@ function error(code, message, options = {}) {
552
553
  ...options
553
554
  };
554
555
  }
555
- function createCommitId() {
556
- const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
557
- return `state-${stamp}-${(0, state_1.safeFileName)(Math.random().toString(36).slice(2, 8))}`;
556
+ // Deterministic commit id (FreeBSD-audit L12/L13): the commit's POSITION in the
557
+ // run's append-only commit log, not a wall-clock stamp + PRNG suffix. Re-running
558
+ // the same workflow mints byte-identical commit ids, so snapshot/replay digests
559
+ // match. The sequence is unique within a run (commits only ever append), and the
560
+ // commitState caller writes the snapshot under this id before pushing the commit.
561
+ function createCommitId(run) {
562
+ const seq = (run.commits || []).length + 1;
563
+ return `state-${String(seq).padStart(4, "0")}`;
564
+ }
565
+ // Deterministic suffix for a blocked-commit node id. Counts the commit-gate-failed
566
+ // nodes already recorded on the run and returns the next position, so repeated
567
+ // gate failures in one run stay collision-free and replay-stable.
568
+ function gateFailureSeq(run) {
569
+ const marker = ":commit-gate-failed:";
570
+ const seq = (run.nodes || []).filter((node) => node.id.includes(marker)).length + 1;
571
+ return String(seq).padStart(4, "0");
558
572
  }
559
573
  function readGitHead(cwd) {
560
574
  try {
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compareBytes = compareBytes;
4
+ // Locale-INDEPENDENT total order for strings (FreeBSD-audit L12 determinism).
5
+ //
6
+ // `String.localeCompare` is host/locale-sensitive: two hosts can order the same
7
+ // byte-identical strings differently. That is fine for human-facing display, but
8
+ // FATAL when the sorted order feeds a sha256 digest, a tombstone/hash chain, or a
9
+ // stable-persisted projection (index.json, messages.jsonl, an export manifest):
10
+ // the same content would then serialize/hash differently across hosts, breaking
11
+ // CW's cross-host reproducibility and replay-determinism guarantees.
12
+ //
13
+ // Use compareBytes() for any ordering that flows into a hash, an export, a
14
+ // persisted projection, or a commit/replay-bearing decision. Pure display sorts
15
+ // may keep localeCompare.
16
+ function compareBytes(a, b) {
17
+ return a < b ? -1 : a > b ? 1 : 0;
18
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.statusToNodeStatus = statusToNodeStatus;
4
+ exports.decisionStatus = decisionStatus;
5
+ exports.auditDecision = auditDecision;
6
+ exports.sourceForAuthor = sourceForAuthor;
7
+ function statusToNodeStatus(status) {
8
+ switch (status) {
9
+ case "active":
10
+ case "open":
11
+ return "running";
12
+ case "resolved":
13
+ case "superseded":
14
+ return "completed";
15
+ case "conflicting":
16
+ return "blocked";
17
+ case "rejected":
18
+ return "rejected";
19
+ default:
20
+ return "completed";
21
+ }
22
+ }
23
+ function decisionStatus(outcome) {
24
+ if (outcome === "conflicting" || outcome === "blocked")
25
+ return "conflicting";
26
+ if (outcome === "rejected")
27
+ return "rejected";
28
+ if (outcome === "superseded")
29
+ return "superseded";
30
+ return "active";
31
+ }
32
+ function auditDecision(outcome) {
33
+ if (outcome === "rejected")
34
+ return "rejected";
35
+ if (outcome === "blocked" || outcome === "conflicting")
36
+ return "failed";
37
+ return "accepted";
38
+ }
39
+ function sourceForAuthor(author) {
40
+ if (author.kind === "runtime" || author.kind === "coordinator")
41
+ return "runtime-derived";
42
+ if (author.kind === "worker" || author.kind === "verifier")
43
+ return "cw-validated";
44
+ return "operator-recorded";
45
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.boardPaths = boardPaths;
7
+ exports.blackboardRoot = blackboardRoot;
8
+ exports.messagesPath = messagesPath;
9
+ exports.recordPath = recordPath;
10
+ // Filesystem path derivation for the coordinator/blackboard layer
11
+ // (FreeBSD-audit R-carve). Carved out of coordinator.ts so the module no longer
12
+ // bundles the per-run path computation alongside the stateful blackboard
13
+ // operations. Re-exported from coordinator.ts to keep the public surface
14
+ // byte-identical.
15
+ //
16
+ // BEHAVIOR-PRESERVING — pure code movement, zero logic change. Each function is a
17
+ // function of a WorkflowRun's paths only: it reads run.paths and joins names; it
18
+ // never mutates run, never touches the blackboard state, never writes the disk.
19
+ const node_path_1 = __importDefault(require("node:path"));
20
+ const state_1 = require("../state");
21
+ function boardPaths(run) {
22
+ const root = blackboardRoot(run);
23
+ return {
24
+ root,
25
+ index: node_path_1.default.join(root, "index.json"),
26
+ messages: messagesPath(run),
27
+ topicsDir: node_path_1.default.join(root, "topics"),
28
+ contextsDir: node_path_1.default.join(root, "contexts"),
29
+ artifactsDir: node_path_1.default.join(root, "artifacts"),
30
+ snapshotsDir: node_path_1.default.join(root, "snapshots"),
31
+ decisionsDir: node_path_1.default.join(root, "decisions")
32
+ };
33
+ }
34
+ function blackboardRoot(run) {
35
+ return run.paths.blackboardDir || node_path_1.default.join(run.paths.runDir, "blackboard");
36
+ }
37
+ function messagesPath(run) {
38
+ return node_path_1.default.join(blackboardRoot(run), "messages.jsonl");
39
+ }
40
+ function recordPath(run, kind, id) {
41
+ return node_path_1.default.join(blackboardRoot(run), kind, `${(0, state_1.safeFileName)(id)}.json`);
42
+ }