cool-workflow 0.1.98 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +11 -2
  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/cli/dispatch.js +236 -0
  11. package/dist/cli/entry.js +120 -0
  12. package/dist/cli/io.js +21 -4
  13. package/dist/cli/parseargv.js +157 -0
  14. package/dist/cli.js +6 -33
  15. package/dist/core/capability-table.js +3534 -0
  16. package/dist/core/format/help.js +314 -0
  17. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  18. package/dist/core/hash.js +137 -0
  19. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  20. package/dist/core/multi-agent/collaboration.js +481 -0
  21. package/dist/core/multi-agent/coordinator.js +515 -0
  22. package/dist/core/multi-agent/eval-replay.js +306 -0
  23. package/dist/core/multi-agent/runtime.js +929 -0
  24. package/dist/core/multi-agent/topology.js +298 -0
  25. package/dist/core/multi-agent/trust-policy.js +197 -0
  26. package/dist/core/pipeline/commit-gate.js +320 -0
  27. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  28. package/dist/core/pipeline/dispatch.js +103 -0
  29. package/dist/core/pipeline/drive-decide.js +227 -0
  30. package/dist/core/pipeline/error-feedback.js +161 -0
  31. package/dist/core/pipeline/loop-expansion.js +124 -0
  32. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  33. package/dist/core/pipeline/runner.js +230 -0
  34. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  35. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  36. package/dist/core/state/node-projection.js +95 -0
  37. package/dist/core/state/node-snapshot.js +230 -0
  38. package/dist/core/state/run-paths.js +93 -0
  39. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  40. package/dist/core/state/schema.js +50 -0
  41. package/dist/core/state/state-explosion/digest.js +243 -0
  42. package/dist/core/state/state-explosion/graph.js +527 -0
  43. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  44. package/dist/core/state/state-explosion/report.js +187 -0
  45. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  46. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  47. package/dist/core/state/types.js +19 -0
  48. package/dist/{validation.js → core/state/validation.js} +64 -131
  49. package/dist/core/trust/evidence-grounding.js +134 -0
  50. package/dist/core/trust/ledger.js +199 -0
  51. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  52. package/dist/core/trust/telemetry-ledger.js +127 -0
  53. package/dist/core/types.js +20 -0
  54. package/dist/core/version.js +16 -0
  55. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  56. package/dist/mcp/dispatch.js +104 -0
  57. package/dist/mcp/server.js +144 -0
  58. package/dist/mcp-server.js +6 -84
  59. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  60. package/dist/shell/app-run-cli.js +88 -0
  61. package/dist/shell/audit-cli.js +259 -0
  62. package/dist/shell/audit-provenance.js +83 -0
  63. package/dist/shell/candidate-scoring-io.js +459 -0
  64. package/dist/shell/collaboration-io.js +264 -0
  65. package/dist/shell/commit-summary.js +104 -0
  66. package/dist/shell/commit.js +290 -0
  67. package/dist/shell/coordinator-io.js +476 -0
  68. package/dist/shell/demo-cli.js +19 -0
  69. package/dist/shell/dispatch.js +162 -0
  70. package/dist/{doctor.js → shell/doctor.js} +123 -56
  71. package/dist/shell/drive.js +873 -0
  72. package/dist/shell/error-feedback-io.js +305 -0
  73. package/dist/shell/eval-io.js +473 -0
  74. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  75. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  76. package/dist/shell/exec-backend-cli.js +88 -0
  77. package/dist/shell/execution-backend/agent.js +475 -0
  78. package/dist/shell/execution-backend/ci.js +15 -0
  79. package/dist/shell/execution-backend/container.js +69 -0
  80. package/dist/shell/execution-backend/envelopes.js +55 -0
  81. package/dist/shell/execution-backend/local.js +113 -0
  82. package/dist/shell/execution-backend/probes.js +175 -0
  83. package/dist/shell/execution-backend/registry.js +402 -0
  84. package/dist/shell/execution-backend/remote.js +128 -0
  85. package/dist/shell/execution-backend/types.js +11 -0
  86. package/dist/shell/feedback-cli.js +81 -0
  87. package/dist/shell/feedback-operations.js +48 -0
  88. package/dist/shell/fs-atomic.js +276 -0
  89. package/dist/shell/harness.js +98 -0
  90. package/dist/shell/ledger-cli.js +212 -0
  91. package/dist/shell/ledger-io.js +169 -0
  92. package/dist/shell/man-cli.js +89 -0
  93. package/dist/shell/metrics-cli.js +98 -0
  94. package/dist/shell/multi-agent-cli.js +1002 -0
  95. package/dist/shell/multi-agent-host.js +563 -0
  96. package/dist/shell/multi-agent-io.js +387 -0
  97. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  98. package/dist/shell/node-store.js +124 -0
  99. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  100. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  101. package/dist/{observability.js → shell/observability.js} +159 -332
  102. package/dist/{onramp.js → shell/onramp.js} +11 -0
  103. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  104. package/dist/shell/operator-ux.js +431 -0
  105. package/dist/shell/orchestrator.js +231 -0
  106. package/dist/shell/pipeline-cli.js +667 -0
  107. package/dist/shell/pipeline.js +217 -0
  108. package/dist/shell/reclamation-io.js +1366 -0
  109. package/dist/shell/registry-cli.js +329 -0
  110. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  111. package/dist/shell/report-cli.js +101 -0
  112. package/dist/shell/report-view-cli.js +117 -0
  113. package/dist/{orchestrator → shell}/report.js +289 -282
  114. package/dist/shell/reporter.js +62 -0
  115. package/dist/shell/run-export-cli.js +106 -0
  116. package/dist/shell/run-export.js +680 -0
  117. package/dist/shell/run-registry-io.js +1014 -0
  118. package/dist/shell/run-store.js +164 -0
  119. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  120. package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
  121. package/dist/shell/scheduling-io.js +311 -0
  122. package/dist/shell/state-cli.js +181 -0
  123. package/dist/shell/state-explosion-cli.js +197 -0
  124. package/dist/shell/telemetry-cli.js +85 -0
  125. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  126. package/dist/shell/telemetry-ledger-io.js +132 -0
  127. package/dist/{term.js → shell/term.js} +36 -46
  128. package/dist/shell/topology-io.js +361 -0
  129. package/dist/shell/trust-audit.js +471 -0
  130. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  131. package/dist/shell/verifier.js +48 -0
  132. package/dist/shell/workbench-host.js +250 -0
  133. package/dist/shell/workbench-text.js +18 -0
  134. package/dist/shell/workbench.js +175 -0
  135. package/dist/shell/worker-cli.js +124 -0
  136. package/dist/shell/worker-isolation.js +852 -0
  137. package/dist/shell/workflow-app-loader.js +650 -0
  138. package/docs/agent-delegation-drive.7.md +2 -0
  139. package/docs/cli-mcp-parity.7.md +280 -219
  140. package/docs/contract-migration-tooling.7.md +2 -0
  141. package/docs/control-plane-scheduling.7.md +2 -0
  142. package/docs/durable-state-and-locking.7.md +2 -0
  143. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  144. package/docs/execution-backends.7.md +2 -0
  145. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  146. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  147. package/docs/multi-agent-operator-ux.7.md +2 -0
  148. package/docs/node-snapshot-diff-replay.7.md +2 -0
  149. package/docs/observability-cost-accounting.7.md +2 -0
  150. package/docs/project-index.md +132 -71
  151. package/docs/real-execution-backends.7.md +2 -0
  152. package/docs/release-and-migration.7.md +2 -0
  153. package/docs/release-tooling.7.md +2 -0
  154. package/docs/run-registry-control-plane.7.md +2 -0
  155. package/docs/run-retention-reclamation.7.md +23 -0
  156. package/docs/state-explosion-management.7.md +2 -0
  157. package/docs/team-collaboration.7.md +2 -0
  158. package/docs/web-desktop-workbench.7.md +2 -0
  159. package/manifest/plugin.manifest.json +1 -1
  160. package/manifest/source-context-profiles.json +9 -13
  161. package/package.json +1 -1
  162. package/scripts/agents/cw-attest-wrap.js +2 -2
  163. package/scripts/bump-version.js +4 -3
  164. package/scripts/canonical-apps.js +4 -4
  165. package/scripts/dogfood-architecture-review.js +2 -3
  166. package/scripts/dogfood-release.js +3 -3
  167. package/scripts/gen-parity-doc.js +15 -2
  168. package/scripts/golden-path.js +4 -4
  169. package/scripts/onramp-check.js +1 -1
  170. package/scripts/parity-check.js +38 -21
  171. package/scripts/release-flow.js +2 -2
  172. package/scripts/sync-project-index.js +51 -27
  173. package/scripts/validate-run-state-schema.js +2 -2
  174. package/scripts/version-sync-check.js +30 -30
  175. package/dist/candidate-scoring.js +0 -729
  176. package/dist/capability-core.js +0 -1189
  177. package/dist/capability-registry.js +0 -885
  178. package/dist/cli/command-surface.js +0 -494
  179. package/dist/cli/format.js +0 -56
  180. package/dist/cli/handlers/audit.js +0 -82
  181. package/dist/cli/handlers/blackboard.js +0 -81
  182. package/dist/cli/handlers/candidate.js +0 -40
  183. package/dist/cli/handlers/clones.js +0 -34
  184. package/dist/cli/handlers/collaboration.js +0 -61
  185. package/dist/cli/handlers/eval.js +0 -40
  186. package/dist/cli/handlers/ledger.js +0 -169
  187. package/dist/cli/handlers/maintenance.js +0 -107
  188. package/dist/cli/handlers/multi-agent.js +0 -165
  189. package/dist/cli/handlers/node.js +0 -41
  190. package/dist/cli/handlers/operational.js +0 -155
  191. package/dist/cli/handlers/operator.js +0 -146
  192. package/dist/cli/handlers/registry.js +0 -68
  193. package/dist/cli/handlers/run.js +0 -153
  194. package/dist/cli/handlers/scheduling.js +0 -132
  195. package/dist/cli/handlers/workbench.js +0 -41
  196. package/dist/cli/handlers/worker.js +0 -45
  197. package/dist/cli/run-summary.js +0 -45
  198. package/dist/clones.js +0 -162
  199. package/dist/collaboration.js +0 -726
  200. package/dist/commit.js +0 -592
  201. package/dist/compare.js +0 -18
  202. package/dist/coordinator/classify.js +0 -45
  203. package/dist/coordinator/paths.js +0 -42
  204. package/dist/coordinator/util.js +0 -126
  205. package/dist/coordinator.js +0 -990
  206. package/dist/daemon.js +0 -44
  207. package/dist/dispatch.js +0 -250
  208. package/dist/drive.js +0 -864
  209. package/dist/error-feedback.js +0 -419
  210. package/dist/evidence-grounding.js +0 -184
  211. package/dist/execution-backend/agent.js +0 -354
  212. package/dist/execution-backend/probes.js +0 -112
  213. package/dist/execution-backend/util.js +0 -47
  214. package/dist/execution-backend.js +0 -961
  215. package/dist/gates.js +0 -48
  216. package/dist/harness.js +0 -61
  217. package/dist/ledger.js +0 -313
  218. package/dist/loop-expansion.js +0 -60
  219. package/dist/mcp/tool-call.js +0 -470
  220. package/dist/mcp/tool-definitions.js +0 -1066
  221. package/dist/mcp-surface.js +0 -30
  222. package/dist/multi-agent/graph.js +0 -84
  223. package/dist/multi-agent/helpers.js +0 -141
  224. package/dist/multi-agent/ids.js +0 -20
  225. package/dist/multi-agent/paths.js +0 -22
  226. package/dist/multi-agent-eval/normalize.js +0 -51
  227. package/dist/multi-agent-eval.js +0 -678
  228. package/dist/multi-agent-host.js +0 -777
  229. package/dist/multi-agent.js +0 -984
  230. package/dist/node-projection.js +0 -59
  231. package/dist/node-snapshot.js +0 -260
  232. package/dist/operator-ux.js +0 -631
  233. package/dist/orchestrator/app-operations.js +0 -211
  234. package/dist/orchestrator/audit-operations.js +0 -182
  235. package/dist/orchestrator/candidate-operations.js +0 -117
  236. package/dist/orchestrator/cli-options.js +0 -294
  237. package/dist/orchestrator/collaboration-operations.js +0 -86
  238. package/dist/orchestrator/feedback-operations.js +0 -81
  239. package/dist/orchestrator/host-operations.js +0 -78
  240. package/dist/orchestrator/lifecycle-operations.js +0 -650
  241. package/dist/orchestrator/migration-operations.js +0 -44
  242. package/dist/orchestrator/multi-agent-operations.js +0 -362
  243. package/dist/orchestrator/topology-operations.js +0 -84
  244. package/dist/orchestrator.js +0 -925
  245. package/dist/pipeline-runner.js +0 -285
  246. package/dist/reclamation/hash.js +0 -72
  247. package/dist/reclamation.js +0 -812
  248. package/dist/reporter.js +0 -67
  249. package/dist/run-export.js +0 -815
  250. package/dist/run-registry/derive.js +0 -175
  251. package/dist/run-registry/format.js +0 -124
  252. package/dist/run-registry/gc.js +0 -251
  253. package/dist/run-registry/policy.js +0 -16
  254. package/dist/run-registry/queue.js +0 -115
  255. package/dist/run-registry.js +0 -850
  256. package/dist/run-state-schema.js +0 -68
  257. package/dist/scheduling.js +0 -184
  258. package/dist/state-explosion.js +0 -1014
  259. package/dist/state.js +0 -367
  260. package/dist/telemetry-ledger.js +0 -196
  261. package/dist/topology.js +0 -565
  262. package/dist/triggers.js +0 -184
  263. package/dist/trust-audit.js +0 -644
  264. package/dist/types/blackboard.js +0 -2
  265. package/dist/types/candidate.js +0 -2
  266. package/dist/types/collaboration.js +0 -2
  267. package/dist/types/core.js +0 -2
  268. package/dist/types/drive.js +0 -10
  269. package/dist/types/error-feedback.js +0 -2
  270. package/dist/types/evidence-reasoning.js +0 -2
  271. package/dist/types/execution-backend.js +0 -2
  272. package/dist/types/multi-agent.js +0 -2
  273. package/dist/types/observability.js +0 -2
  274. package/dist/types/pipeline.js +0 -2
  275. package/dist/types/reclamation.js +0 -8
  276. package/dist/types/report-bundle.js +0 -6
  277. package/dist/types/result.js +0 -2
  278. package/dist/types/run-registry.js +0 -2
  279. package/dist/types/run.js +0 -2
  280. package/dist/types/sandbox.js +0 -2
  281. package/dist/types/schedule.js +0 -2
  282. package/dist/types/state-node.js +0 -2
  283. package/dist/types/topology.js +0 -2
  284. package/dist/types/trust.js +0 -2
  285. package/dist/types/workbench.js +0 -2
  286. package/dist/types/worker.js +0 -2
  287. package/dist/types/workflow-app.js +0 -2
  288. package/dist/types.js +0 -44
  289. package/dist/util/fingerprint.js +0 -19
  290. package/dist/util/fingerprint.test.js +0 -27
  291. package/dist/verifier.js +0 -78
  292. package/dist/version.js +0 -8
  293. package/dist/workbench-host.js +0 -192
  294. package/dist/workbench.js +0 -192
  295. package/dist/worker-accept/acceptance.js +0 -114
  296. package/dist/worker-accept/blackboard-fanout.js +0 -80
  297. package/dist/worker-accept/blackboard-linkage.js +0 -19
  298. package/dist/worker-accept/context.js +0 -2
  299. package/dist/worker-accept/telemetry-ledger.js +0 -126
  300. package/dist/worker-accept/validation.js +0 -77
  301. package/dist/worker-accept/verifier-completion.js +0 -73
  302. package/dist/worker-isolation/helpers.js +0 -51
  303. package/dist/worker-isolation/paths.js +0 -46
  304. package/dist/worker-isolation.js +0 -656
  305. package/dist/workflow-api.js +0 -131
  306. /package/dist/{types → core/types}/boundary.js +0 -0
package/dist/state.js DELETED
@@ -1,367 +0,0 @@
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.CURRENT_RUN_STATE_SCHEMA_VERSION = void 0;
7
- exports.createRunPaths = createRunPaths;
8
- exports.ensureRunDirs = ensureRunDirs;
9
- exports.loadRunFromCwd = loadRunFromCwd;
10
- exports.loadRunStateFile = loadRunStateFile;
11
- exports.checkRunStateFile = checkRunStateFile;
12
- exports.migrateRunStateFile = migrateRunStateFile;
13
- exports.saveCheckpoint = saveCheckpoint;
14
- exports.compactCheckpoint = compactCheckpoint;
15
- exports.readJson = readJson;
16
- exports.writeJson = writeJson;
17
- exports.durableAppendFileSync = durableAppendFileSync;
18
- exports.realResolve = realResolve;
19
- exports.isContainedPath = isContainedPath;
20
- exports.withFileLock = withFileLock;
21
- exports.safeFileName = safeFileName;
22
- exports.assertSafeRunId = assertSafeRunId;
23
- exports.hashArtifactFile = hashArtifactFile;
24
- const node_fs_1 = __importDefault(require("node:fs"));
25
- const node_path_1 = __importDefault(require("node:path"));
26
- const state_migrations_1 = require("./state-migrations");
27
- const version_1 = require("./version");
28
- Object.defineProperty(exports, "CURRENT_RUN_STATE_SCHEMA_VERSION", { enumerable: true, get: function () { return version_1.CURRENT_RUN_STATE_SCHEMA_VERSION; } });
29
- const execution_backend_1 = require("./execution-backend");
30
- function createRunPaths(runDir) {
31
- return {
32
- runDir,
33
- state: node_path_1.default.join(runDir, "state.json"),
34
- report: node_path_1.default.join(runDir, "report.md"),
35
- tasksDir: node_path_1.default.join(runDir, "tasks"),
36
- resultsDir: node_path_1.default.join(runDir, "results"),
37
- dispatchesDir: node_path_1.default.join(runDir, "dispatches"),
38
- artifactsDir: node_path_1.default.join(runDir, "artifacts"),
39
- commitsDir: node_path_1.default.join(runDir, "commits"),
40
- stateNodesDir: node_path_1.default.join(runDir, "nodes"),
41
- feedbackDir: node_path_1.default.join(runDir, "feedback"),
42
- auditDir: node_path_1.default.join(runDir, "audit"),
43
- workersDir: node_path_1.default.join(runDir, "workers"),
44
- candidatesDir: node_path_1.default.join(runDir, "candidates"),
45
- multiAgentDir: node_path_1.default.join(runDir, "multi-agent"),
46
- blackboardDir: node_path_1.default.join(runDir, "blackboard"),
47
- topologiesDir: node_path_1.default.join(runDir, "topologies")
48
- };
49
- }
50
- function ensureRunDirs(paths) {
51
- for (const dir of [
52
- paths.runDir,
53
- paths.tasksDir,
54
- paths.resultsDir,
55
- paths.dispatchesDir,
56
- paths.artifactsDir,
57
- paths.commitsDir,
58
- paths.stateNodesDir,
59
- paths.feedbackDir,
60
- paths.auditDir || node_path_1.default.join(paths.runDir, "audit"),
61
- paths.workersDir || node_path_1.default.join(paths.runDir, "workers"),
62
- paths.candidatesDir || node_path_1.default.join(paths.runDir, "candidates"),
63
- paths.multiAgentDir || node_path_1.default.join(paths.runDir, "multi-agent"),
64
- paths.blackboardDir || node_path_1.default.join(paths.runDir, "blackboard"),
65
- paths.topologiesDir || node_path_1.default.join(paths.runDir, "topologies")
66
- ]) {
67
- node_fs_1.default.mkdirSync(dir, { recursive: true });
68
- }
69
- }
70
- function loadRunFromCwd(runId, cwd = process.cwd()) {
71
- if (!runId)
72
- throw new Error("Missing run id");
73
- assertSafeRunId(runId);
74
- const statePath = node_path_1.default.join(cwd, ".cw", "runs", runId, "state.json");
75
- const result = loadRunStateFile(statePath, { dryRun: true });
76
- if (result.report.status === "unsupported") {
77
- throw new Error(`Unsupported CW run state: ${result.report.errors.join("; ")}`);
78
- }
79
- return result.run;
80
- }
81
- function loadRunStateFile(statePath, options = {}) {
82
- const result = (0, state_migrations_1.migrateRunState)(readJson(statePath), {
83
- statePath,
84
- dryRun: options.dryRun === undefined ? true : options.dryRun
85
- });
86
- if (result.report.status === "unsupported")
87
- return result;
88
- return result;
89
- }
90
- function checkRunStateFile(statePath) {
91
- return loadRunStateFile(statePath, { dryRun: true });
92
- }
93
- function migrateRunStateFile(statePath, options = {}) {
94
- const result = loadRunStateFile(statePath, { dryRun: !options.write });
95
- if (result.report.status !== "unsupported" && options.write && result.report.writeRequired) {
96
- writeJson(statePath, result.run);
97
- }
98
- return result;
99
- }
100
- function saveCheckpoint(run) {
101
- run.updatedAt = new Date().toISOString();
102
- // state.json is the single source of truth — write it DURABLY with a lock
103
- // so concurrent processes never lose an update (v0.1.95).
104
- withFileLock(run.paths.state, () => {
105
- writeJson(run.paths.state, run, { durable: true });
106
- });
107
- }
108
- /** Compact a run checkpoint by stripping empty optional arrays and null values
109
- * that don't carry semantic meaning (v0.1.60). The normalization layer
110
- * (normalizeRunState) backfills these on load, so stripping them saves disk
111
- * without losing information. Returns the number of keys stripped. */
112
- function compactCheckpoint(run) {
113
- const optionalArrays = [
114
- "nodes", "contracts", "feedback", "workers", "sandboxProfiles",
115
- "candidates", "candidateSelections"
116
- ];
117
- let stripped = 0;
118
- const state = run;
119
- for (const key of optionalArrays) {
120
- if (Array.isArray(state[key]) && state[key].length === 0) {
121
- delete state[key];
122
- stripped++;
123
- }
124
- }
125
- if (stripped > 0)
126
- saveCheckpoint(run);
127
- return stripped;
128
- }
129
- function readJson(file) {
130
- if (!node_fs_1.default.existsSync(file))
131
- throw new Error(`File not found: ${file}`);
132
- try {
133
- return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
134
- }
135
- catch (error) {
136
- const message = error instanceof Error ? error.message : String(error);
137
- throw new Error(`Invalid JSON in ${file}: ${message}`);
138
- }
139
- }
140
- // ---------------------------------------------------------------------------
141
- // Atomic, optionally-durable JSON write (v0.1.40, closes the prior P1).
142
- //
143
- // ORDER IS THE SAFETY PROPERTY: write to a unique temp file, then rename over the
144
- // target. `rename(2)` is atomic on POSIX, so a crash/`ENOSPC` mid-write can never
145
- // leave a truncated `state.json` that throws `Invalid JSON` on reload — a reader
146
- // always sees EITHER the old bytes OR the new bytes, never a torn file. With
147
- // `{ durable: true }` we additionally fsync the file (and best-effort the dir)
148
- // before/after the rename so the bytes survive power loss — used for AUTHORITATIVE
149
- // state (state.json, registry overlays, the scheduler store, reclaimed.json). The
150
- // fsync is skipped for high-frequency derived/rebuildable writes so the atomic
151
- // rename (the actual torn-write fix) stays cheap everywhere.
152
- // ---------------------------------------------------------------------------
153
- let atomicWriteCounter = 0;
154
- function writeJson(file, value, options = {}) {
155
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
156
- const tmp = `${file}.tmp.${process.pid}.${atomicWriteCounter++}`;
157
- const fd = node_fs_1.default.openSync(tmp, "w");
158
- try {
159
- node_fs_1.default.writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf8");
160
- if (options.durable)
161
- node_fs_1.default.fsyncSync(fd);
162
- }
163
- finally {
164
- node_fs_1.default.closeSync(fd);
165
- }
166
- try {
167
- node_fs_1.default.renameSync(tmp, file);
168
- }
169
- catch (error) {
170
- try {
171
- node_fs_1.default.rmSync(tmp, { force: true });
172
- }
173
- catch {
174
- /* best-effort temp cleanup */
175
- }
176
- throw error;
177
- }
178
- if (options.durable) {
179
- try {
180
- const dirFd = node_fs_1.default.openSync(node_path_1.default.dirname(file), "r");
181
- try {
182
- node_fs_1.default.fsyncSync(dirFd);
183
- }
184
- finally {
185
- node_fs_1.default.closeSync(dirFd);
186
- }
187
- }
188
- catch {
189
- /* directory fsync is best-effort (not supported on every platform) */
190
- }
191
- }
192
- }
193
- // ---------------------------------------------------------------------------
194
- // Durable append (v0.1.40 self-audit P1) — append a line and fsync it before
195
- // returning. The trust-audit event log is the ONE artifact whose loss breaks
196
- // audit-completeness/non-repudiation, so unlike high-frequency derived writes it
197
- // must survive power loss. `appendFileSync` alone does NOT fsync, so a crash
198
- // after it returns could drop the most recent event while durable state.json
199
- // advanced past it. We open O_APPEND, write, fsync the fd, then close.
200
- // ---------------------------------------------------------------------------
201
- function durableAppendFileSync(file, data) {
202
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
203
- const fd = node_fs_1.default.openSync(file, "a");
204
- try {
205
- node_fs_1.default.writeFileSync(fd, data, "utf8");
206
- node_fs_1.default.fsyncSync(fd);
207
- }
208
- finally {
209
- node_fs_1.default.closeSync(fd);
210
- }
211
- }
212
- // ---------------------------------------------------------------------------
213
- // Symlink-hardened path containment (v0.1.40 self-audit P1) — `path.resolve()`
214
- // only normalizes `.`/`..` textually; it does NOT follow symlinks, so a planted
215
- // symlink whose textual path sits "inside" an allowed root but whose real target
216
- // escapes it would pass a `startsWith` containment check. `realResolve` resolves
217
- // the deepest EXISTING ancestor with `realpathSync` (which follows symlinks) and
218
- // re-joins the not-yet-created remainder, so a not-yet-created file is still
219
- // pinned to its real parent. `isContainedPath` realpaths BOTH sides so the
220
- // comparison stays consistent on platforms where the temp root itself is a
221
- // symlink (e.g. macOS /tmp -> /private/tmp).
222
- // ---------------------------------------------------------------------------
223
- function realResolve(target) {
224
- let current = node_path_1.default.resolve(target);
225
- const tail = [];
226
- // Walk up to the deepest existing ancestor, realpath it, then re-append the tail.
227
- for (;;) {
228
- try {
229
- const real = node_fs_1.default.realpathSync.native ? node_fs_1.default.realpathSync.native(current) : node_fs_1.default.realpathSync(current);
230
- return tail.length ? node_path_1.default.join(real, ...tail.reverse()) : real;
231
- }
232
- catch {
233
- const parent = node_path_1.default.dirname(current);
234
- if (parent === current)
235
- return node_path_1.default.resolve(target); // reached root; nothing existed
236
- tail.push(node_path_1.default.basename(current));
237
- current = parent;
238
- }
239
- }
240
- }
241
- function isContainedPath(candidate, allowed) {
242
- const realCandidate = realResolve(candidate);
243
- const realAllowed = realResolve(allowed);
244
- return realCandidate === realAllowed || realCandidate.startsWith(realAllowed + node_path_1.default.sep);
245
- }
246
- // ---------------------------------------------------------------------------
247
- // Portable advisory file lock (v0.1.40) — serialize cross-process read-modify-
248
- // write on shared stores (home queue, scheduler store, archive overlay, the
249
- // per-run reclamation chain) so a concurrent writer can never lose a record.
250
- // O_EXCL (`wx`) is portable (no native flock); a stale holder is stolen so a
251
- // crashed process can never wedge the store forever.
252
- //
253
- // v0.1.95: the lock mtime is refreshed immediately before fn() runs, and
254
- // verified AFTER fn() returns — if another process stole the lock mid-operation
255
- // (the stale check fired and it overwrote our lock), the holder refuses to
256
- // release and throws. This guards long-running RMW operations (e.g. GC over a
257
- // large run) against mid-operation theft.
258
- // ---------------------------------------------------------------------------
259
- const FILE_LOCK_STALE_MS = 30_000;
260
- function sleepSync(ms) {
261
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
262
- }
263
- /** Run `fn` while holding an advisory lock for `targetPath`; always released. */
264
- function withFileLock(targetPath, fn) {
265
- const lock = `${targetPath}.lock`;
266
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(lock), { recursive: true });
267
- const pid = String(process.pid);
268
- let acquired = false;
269
- for (let attempt = 0; attempt < 240 && !acquired; attempt++) {
270
- try {
271
- const fd = node_fs_1.default.openSync(lock, "wx");
272
- node_fs_1.default.writeFileSync(fd, `${pid}@${new Date().toISOString()}\n`, "utf8");
273
- node_fs_1.default.closeSync(fd);
274
- acquired = true;
275
- }
276
- catch (error) {
277
- if (!(error && typeof error === "object" && error.code === "EEXIST"))
278
- throw error;
279
- try {
280
- if (Date.now() - node_fs_1.default.statSync(lock).mtimeMs > FILE_LOCK_STALE_MS) {
281
- node_fs_1.default.rmSync(lock, { force: true });
282
- continue;
283
- }
284
- }
285
- catch {
286
- continue;
287
- }
288
- sleepSync(25);
289
- }
290
- }
291
- if (!acquired)
292
- throw new Error(`could not acquire file lock for ${targetPath}`);
293
- // Refresh mtime right before the critical section
294
- try {
295
- node_fs_1.default.utimesSync(lock, new Date(), new Date());
296
- }
297
- catch { /* best-effort */ }
298
- try {
299
- const result = fn();
300
- // Verify lock was not stolen during fn(). The lock content may have changed
301
- // if another process opened it with wx after stealing it. If our PID is not
302
- // in the lock file, the lock was stolen — do NOT release the stolen lock
303
- // (the thief owns it now, and releasing would corrupt its operation).
304
- try {
305
- const current = node_fs_1.default.readFileSync(lock, "utf8");
306
- if (!current.startsWith(pid + "@")) {
307
- throw new Error(`File lock for ${targetPath} was stolen during the critical section ` +
308
- `(lock now owned by another process). The operation may have lost ` +
309
- `cross-process isolation — increase FILE_LOCK_STALE_MS or split the work.`);
310
- }
311
- }
312
- catch (checkError) {
313
- if (checkError instanceof Error && checkError.message.includes("stolen"))
314
- throw checkError;
315
- /* lock vanished — another process already released it, nothing to clean up */
316
- }
317
- return result;
318
- }
319
- finally {
320
- try {
321
- // Only release if we still own the lock
322
- const current = node_fs_1.default.readFileSync(lock, "utf8");
323
- if (current.startsWith(pid + "@"))
324
- node_fs_1.default.rmSync(lock, { force: true });
325
- }
326
- catch {
327
- /* releasing a missing lock is fine */
328
- }
329
- }
330
- }
331
- function safeFileName(value) {
332
- return String(value).replace(/[^a-zA-Z0-9_.:-]+/g, "_");
333
- }
334
- /** Refuse a run id that is not a single safe path segment, so an id taken from
335
- * an untrusted source (an imported run archive / bundle) can never escape the
336
- * runs directory via a separator or a `..`/`.` component (path traversal). A
337
- * real run id is `${workflowId}-${stamp}-${suffix}` (createRunId), and a
338
- * workflow id is alnum-bounded [a-z0-9.-] (validateWorkflowId) — all within
339
- * [A-Za-z0-9._:-]. Sub-workflow ids also include `:` from the task id prefix.
340
- * Because the charset already forbids any separator, the whole
341
- * id is ONE path component, so the only values that could traverse are the
342
- * components `.` and `..` themselves; an embedded `..` (e.g. a workflow id like
343
- * `v1..2`) is a safe directory name and is allowed. A separator, an absolute
344
- * path, or an empty value is refused, fail-closed. Returns the id on success so
345
- * callers can use it inline. */
346
- function assertSafeRunId(value, context = "run id") {
347
- if (typeof value !== "string" || value.length === 0) {
348
- throw new Error(`Invalid ${context}: expected a non-empty string`);
349
- }
350
- if (!/^[A-Za-z0-9._:-]+$/.test(value) || value === "." || value === "..") {
351
- throw new Error(`Unsafe ${context}: ${JSON.stringify(value)} must be a single path segment ([A-Za-z0-9._:-], not '.' or '..')`);
352
- }
353
- return value;
354
- }
355
- /** Compute and set SHA256 + sizeBytes on a StateArtifact from its file path
356
- * (v0.1.73). Fails silently when the file doesn't exist — does not throw. */
357
- function hashArtifactFile(artifact) {
358
- try {
359
- const content = node_fs_1.default.readFileSync(artifact.path, "utf8");
360
- artifact.sha256 = (0, execution_backend_1.sha256)(content);
361
- artifact.sizeBytes = Buffer.byteLength(content, "utf8");
362
- }
363
- catch {
364
- /* file missing — silently skip */
365
- }
366
- return artifact;
367
- }
@@ -1,196 +0,0 @@
1
- "use strict";
2
- // Telemetry attestation ledger (Track 1) — make the RECORDED attestation itself
3
- // tamper-evident, not just the in-flight signature.
4
- //
5
- // The executor's signature (telemetry-attestation.ts) proves the agent SAID a
6
- // given usage. This ledger proves CW RECORDED exactly that and nobody edited the
7
- // record afterward: an append-only, hash-chained overlay (`telemetry.json`, a
8
- // runDir peer of reclaimed.json), one entry per agent hop. Each entry chains to
9
- // the prior via prevHash; recordHash = sha256(canonical entry sans recordHash).
10
- // Flip a recorded verdict (`unattested`→`attested`), or edit a recorded usage
11
- // digest, and the chain no longer recomputes — `verifyTelemetryLedger` catches it.
12
- //
13
- // Same discipline as reclamation.ts's tombstone chain: APPEND-ONLY (never rewrite
14
- // a prior entry), DURABLE (temp→fsync→rename via writeJson), and verify recomputes
15
- // every hash independently — it never trusts the stored value.
16
- var __importDefault = (this && this.__importDefault) || function (mod) {
17
- return (mod && mod.__esModule) ? mod : { "default": mod };
18
- };
19
- Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.TelemetryLedgerCorruptError = exports.TELEMETRY_LEDGER_SCHEMA_VERSION = void 0;
21
- exports.telemetryLedgerPath = telemetryLedgerPath;
22
- exports.loadTelemetryLedger = loadTelemetryLedger;
23
- exports.genesisPrevHash = genesisPrevHash;
24
- exports.computeRecordHash = computeRecordHash;
25
- exports.reportedUsageDigest = reportedUsageDigest;
26
- exports.appendTelemetryAttestation = appendTelemetryAttestation;
27
- exports.verifyTelemetryLedger = verifyTelemetryLedger;
28
- const node_fs_1 = __importDefault(require("node:fs"));
29
- const node_path_1 = __importDefault(require("node:path"));
30
- const state_1 = require("./state");
31
- const execution_backend_1 = require("./execution-backend");
32
- const telemetry_attestation_1 = require("./telemetry-attestation");
33
- exports.TELEMETRY_LEDGER_SCHEMA_VERSION = 1;
34
- function telemetryLedgerPath(run) {
35
- return node_path_1.default.join(run.paths.runDir, "telemetry.json");
36
- }
37
- /** A telemetry ledger that EXISTS on disk but cannot be parsed (or whose shape is
38
- * not a record array). This is exactly the corruption/truncation case the hash
39
- * chain exists to catch — it must fail closed, never be silently treated as the
40
- * "empty/absent" chain (which verifies as clean). */
41
- class TelemetryLedgerCorruptError extends Error {
42
- file;
43
- constructor(file) {
44
- super(`Telemetry ledger exists but is corrupt (unparseable): ${file}`);
45
- this.name = "TelemetryLedgerCorruptError";
46
- this.file = file;
47
- }
48
- }
49
- exports.TelemetryLedgerCorruptError = TelemetryLedgerCorruptError;
50
- /** Read the ledger, DISTINGUISHING absent (never written -> empty chain, fine)
51
- * from corrupt (exists but unparseable/wrong shape -> fail closed). Conflating
52
- * the two was the bug that let a corrupt overlay verify green and let an append
53
- * silently re-genesis on top of it, discarding history. */
54
- function readTelemetryLedgerState(run) {
55
- const file = telemetryLedgerPath(run);
56
- if (!node_fs_1.default.existsSync(file))
57
- return { status: "absent", ledger: { schemaVersion: 1, runId: run.id, records: [] } };
58
- let parsed;
59
- try {
60
- parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
61
- }
62
- catch {
63
- return { status: "corrupt", file };
64
- }
65
- if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.records)) {
66
- return { status: "corrupt", file };
67
- }
68
- return { status: "ok", ledger: { schemaVersion: 1, runId: run.id, records: parsed.records } };
69
- }
70
- /** Load the ledger for read/append. Absent -> empty chain. Corrupt -> THROWS, so
71
- * an append can never silently re-genesis on a poisoned/edited file, and a read
72
- * surfaces the corruption rather than swallowing it. */
73
- function loadTelemetryLedger(run) {
74
- const state = readTelemetryLedgerState(run);
75
- if (state.status === "corrupt")
76
- throw new TelemetryLedgerCorruptError(state.file);
77
- return state.ledger;
78
- }
79
- /** genesis prevHash for a run's chain (no prior record). */
80
- function genesisPrevHash(runId) {
81
- return (0, execution_backend_1.sha256)(`cw-telemetry-ledger:${runId}`);
82
- }
83
- /** The canonical bytes a recordHash binds — every field except recordHash itself.
84
- * Recomputed independently by verifyTelemetryLedger. */
85
- function recordHashInput(record) {
86
- return (0, telemetry_attestation_1.stableStringify)({
87
- schemaVersion: record.schemaVersion,
88
- runId: record.runId,
89
- recordId: record.recordId,
90
- recordedAt: record.recordedAt,
91
- workerId: record.workerId,
92
- taskId: record.taskId,
93
- promptDigest: record.promptDigest,
94
- reportedUsageDigest: record.reportedUsageDigest,
95
- ...(record.reportedUsage !== undefined ? { reportedUsage: record.reportedUsage } : {}),
96
- usageSignature: record.usageSignature || null,
97
- // Chain-bind resultDigest only when present, so a usage-only record's hash is
98
- // byte-identical to a pre-result-coverage one (back-compat with old ledgers).
99
- ...(record.resultDigest !== undefined ? { resultDigest: record.resultDigest } : {}),
100
- attestation: record.attestation,
101
- attestationReason: record.attestationReason || null,
102
- prevHash: record.prevHash
103
- });
104
- }
105
- function computeRecordHash(record) {
106
- return (0, execution_backend_1.sha256)(recordHashInput(record));
107
- }
108
- /** sha256 of the canonical reported usage (compact, chainable). Absent usage gets
109
- * the digest of `null`, so "the agent reported nothing" is itself bound. */
110
- function reportedUsageDigest(usage) {
111
- return (0, execution_backend_1.sha256)((0, telemetry_attestation_1.stableStringify)(usage ?? null));
112
- }
113
- function recordId(seq) {
114
- // Deterministic (FreeBSD-audit L13): the chain POSITION, not a process-global
115
- // counter or wall-clock stamp — recordId is bound into the recordHash chain.
116
- return `tel-${String(seq).padStart(3, "0")}`;
117
- }
118
- /** Append one attestation record DURABLY to the append-only chain, linking it to
119
- * the prior record (or genesis). Returns the committed record. */
120
- function appendTelemetryAttestation(run, input) {
121
- const ledger = loadTelemetryLedger(run);
122
- const now = input.now || new Date().toISOString();
123
- const prevHash = ledger.records.length ? ledger.records[ledger.records.length - 1].recordHash : genesisPrevHash(run.id);
124
- const base = {
125
- schemaVersion: 1,
126
- runId: run.id,
127
- recordId: recordId(ledger.records.length + 1),
128
- recordedAt: now,
129
- workerId: input.workerId,
130
- taskId: input.taskId,
131
- promptDigest: input.promptDigest,
132
- reportedUsageDigest: reportedUsageDigest(input.reportedUsage),
133
- // Store the raw usage verbatim, digest-bound, and hash-chained so the
134
- // signature can be independently re-verified offline at `telemetry verify`.
135
- ...(input.reportedUsage ? { reportedUsage: input.reportedUsage } : {}),
136
- usageSignature: input.usageSignature,
137
- // Present only for a result-bound signature, so usage-only records are
138
- // byte-identical (and their recordHash unchanged) — back-compat.
139
- ...(input.resultDigest ? { resultDigest: input.resultDigest } : {}),
140
- attestation: input.attestation,
141
- attestationReason: input.attestationReason,
142
- prevHash
143
- };
144
- const record = { ...base, recordHash: computeRecordHash(base) };
145
- ledger.records.push(record);
146
- (0, state_1.writeJson)(telemetryLedgerPath(run), ledger, { durable: true });
147
- return record;
148
- }
149
- /** Re-prove the whole telemetry chain for a run: prevHash linkage + per-record
150
- * hash recompute. Recomputes every hash independently — never trusts the stored
151
- * value — so an edited record/verdict is detected. An empty ledger verifies as
152
- * present:false (nothing to prove), NOT a failure. */
153
- function verifyTelemetryLedger(run) {
154
- const state = readTelemetryLedgerState(run);
155
- if (state.status === "corrupt") {
156
- // Fail closed: a ledger that exists but cannot be parsed is indistinguishable
157
- // from a truncated/forged one — report it, never green it.
158
- return {
159
- present: true,
160
- verified: false,
161
- records: [],
162
- checks: [{ name: "ledger-load", pass: false, code: "telemetry-ledger-corrupt" }],
163
- attested: 0,
164
- unattested: 0,
165
- absent: 0
166
- };
167
- }
168
- const records = state.ledger.records;
169
- const checks = [];
170
- const tally = { attested: 0, unattested: 0, absent: 0 };
171
- for (const record of records)
172
- tally[record.attestation] += 1;
173
- if (!records.length) {
174
- return { present: false, verified: true, records, checks, ...tally };
175
- }
176
- // (a) chain linkage: genesis = sha256("cw-telemetry-ledger:"+runId).
177
- let chainOk = true;
178
- for (let i = 0; i < records.length; i++) {
179
- const expectedPrev = i === 0 ? genesisPrevHash(run.id) : records[i - 1].recordHash;
180
- const pass = records[i].prevHash === expectedPrev;
181
- if (!pass)
182
- chainOk = false;
183
- checks.push({ name: `chain-link[${i}]`, pass, code: pass ? undefined : "telemetry-chain-broken" });
184
- }
185
- // (b) per-record independent hash recompute (digest integrity).
186
- let digestsOk = true;
187
- for (let i = 0; i < records.length; i++) {
188
- const { recordHash, ...rest } = records[i];
189
- const recomputed = computeRecordHash(rest);
190
- const pass = recomputed === recordHash;
191
- if (!pass)
192
- digestsOk = false;
193
- checks.push({ name: `record-hash[${i}]`, pass, code: pass ? undefined : "telemetry-digest-mismatch" });
194
- }
195
- return { present: true, verified: chainOk && digestsOk, records, checks, ...tally };
196
- }