cool-workflow 0.1.98 → 0.2.1

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 (315) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +18 -7
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  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/cli/dispatch.js +236 -0
  12. package/dist/cli/entry.js +120 -0
  13. package/dist/cli/io.js +21 -4
  14. package/dist/cli/parseargv.js +157 -0
  15. package/dist/cli.js +6 -33
  16. package/dist/core/capability-table.js +3518 -0
  17. package/dist/core/format/help.js +314 -0
  18. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  19. package/dist/core/hash.js +137 -0
  20. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  21. package/dist/core/multi-agent/collaboration.js +481 -0
  22. package/dist/core/multi-agent/coordinator.js +515 -0
  23. package/dist/core/multi-agent/eval-replay.js +306 -0
  24. package/dist/core/multi-agent/runtime.js +929 -0
  25. package/dist/core/multi-agent/topology.js +298 -0
  26. package/dist/core/multi-agent/trust-policy.js +197 -0
  27. package/dist/core/pipeline/commit-gate.js +320 -0
  28. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  29. package/dist/core/pipeline/dispatch.js +103 -0
  30. package/dist/core/pipeline/drive-decide.js +227 -0
  31. package/dist/core/pipeline/error-feedback.js +161 -0
  32. package/dist/core/pipeline/loop-expansion.js +124 -0
  33. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  34. package/dist/core/pipeline/runner.js +230 -0
  35. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  36. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  37. package/dist/core/state/node-projection.js +95 -0
  38. package/dist/core/state/node-snapshot.js +230 -0
  39. package/dist/core/state/run-paths.js +93 -0
  40. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  41. package/dist/core/state/schema.js +50 -0
  42. package/dist/core/state/state-explosion/digest.js +243 -0
  43. package/dist/core/state/state-explosion/graph.js +527 -0
  44. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  45. package/dist/core/state/state-explosion/report.js +187 -0
  46. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  47. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  48. package/dist/core/state/types.js +19 -0
  49. package/dist/{validation.js → core/state/validation.js} +64 -131
  50. package/dist/core/trust/evidence-grounding.js +134 -0
  51. package/dist/core/trust/ledger.js +199 -0
  52. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  53. package/dist/core/trust/telemetry-ledger.js +127 -0
  54. package/dist/core/types.js +20 -0
  55. package/dist/core/version.js +16 -0
  56. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  57. package/dist/mcp/dispatch.js +104 -0
  58. package/dist/mcp/server.js +144 -0
  59. package/dist/mcp-server.js +6 -84
  60. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  61. package/dist/shell/app-run-cli.js +88 -0
  62. package/dist/shell/audit-cli.js +259 -0
  63. package/dist/shell/audit-provenance.js +83 -0
  64. package/dist/shell/candidate-scoring-io.js +459 -0
  65. package/dist/shell/collaboration-io.js +264 -0
  66. package/dist/shell/commit-summary.js +104 -0
  67. package/dist/shell/commit.js +290 -0
  68. package/dist/shell/coordinator-io.js +476 -0
  69. package/dist/shell/demo-cli.js +19 -0
  70. package/dist/shell/dispatch.js +162 -0
  71. package/dist/shell/doctor.js +292 -0
  72. package/dist/shell/drive.js +885 -0
  73. package/dist/shell/error-feedback-io.js +305 -0
  74. package/dist/shell/eval-io.js +473 -0
  75. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  76. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  77. package/dist/shell/exec-backend-cli.js +88 -0
  78. package/dist/shell/execution-backend/agent.js +507 -0
  79. package/dist/shell/execution-backend/ci.js +15 -0
  80. package/dist/shell/execution-backend/container.js +69 -0
  81. package/dist/shell/execution-backend/envelopes.js +55 -0
  82. package/dist/shell/execution-backend/local.js +113 -0
  83. package/dist/shell/execution-backend/probes.js +175 -0
  84. package/dist/shell/execution-backend/registry.js +402 -0
  85. package/dist/shell/execution-backend/remote.js +128 -0
  86. package/dist/shell/execution-backend/types.js +11 -0
  87. package/dist/shell/feedback-cli.js +81 -0
  88. package/dist/shell/feedback-operations.js +48 -0
  89. package/dist/shell/fs-atomic.js +276 -0
  90. package/dist/shell/harness.js +98 -0
  91. package/dist/shell/ledger-cli.js +212 -0
  92. package/dist/shell/ledger-io.js +169 -0
  93. package/dist/shell/man-cli.js +89 -0
  94. package/dist/shell/metrics-cli.js +100 -0
  95. package/dist/shell/multi-agent-cli.js +1002 -0
  96. package/dist/shell/multi-agent-host.js +563 -0
  97. package/dist/shell/multi-agent-io.js +387 -0
  98. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  99. package/dist/shell/node-store.js +124 -0
  100. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  101. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  102. package/dist/{observability.js → shell/observability.js} +159 -332
  103. package/dist/{onramp.js → shell/onramp.js} +11 -0
  104. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  105. package/dist/shell/operator-ux.js +431 -0
  106. package/dist/shell/orchestrator.js +231 -0
  107. package/dist/shell/pipeline-cli.js +667 -0
  108. package/dist/shell/pipeline.js +217 -0
  109. package/dist/shell/reclamation-io.js +1366 -0
  110. package/dist/shell/registry-cli.js +344 -0
  111. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  112. package/dist/shell/report-cli.js +101 -0
  113. package/dist/shell/report-view-cli.js +117 -0
  114. package/dist/{orchestrator → shell}/report.js +289 -282
  115. package/dist/shell/reporter.js +62 -0
  116. package/dist/shell/run-export-cli.js +106 -0
  117. package/dist/shell/run-export.js +680 -0
  118. package/dist/shell/run-registry-io.js +1014 -0
  119. package/dist/shell/run-store.js +164 -0
  120. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  121. package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
  122. package/dist/shell/scheduling-io.js +321 -0
  123. package/dist/shell/state-cli.js +181 -0
  124. package/dist/shell/state-explosion-cli.js +197 -0
  125. package/dist/shell/telemetry-cli.js +85 -0
  126. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  127. package/dist/shell/telemetry-ledger-io.js +132 -0
  128. package/dist/{term.js → shell/term.js} +36 -46
  129. package/dist/shell/topology-io.js +361 -0
  130. package/dist/shell/trust-audit.js +472 -0
  131. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  132. package/dist/shell/verifier.js +48 -0
  133. package/dist/shell/workbench-host.js +258 -0
  134. package/dist/shell/workbench-text.js +18 -0
  135. package/dist/shell/workbench.js +170 -0
  136. package/dist/shell/worker-cli.js +124 -0
  137. package/dist/shell/worker-isolation.js +852 -0
  138. package/dist/shell/workflow-app-loader.js +650 -0
  139. package/docs/agent-delegation-drive.7.md +4 -0
  140. package/docs/cli-mcp-parity.7.md +282 -219
  141. package/docs/contract-migration-tooling.7.md +4 -0
  142. package/docs/control-plane-scheduling.7.md +4 -0
  143. package/docs/durable-state-and-locking.7.md +4 -0
  144. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  145. package/docs/execution-backends.7.md +4 -0
  146. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  147. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  148. package/docs/multi-agent-operator-ux.7.md +4 -0
  149. package/docs/node-snapshot-diff-replay.7.md +4 -0
  150. package/docs/observability-cost-accounting.7.md +6 -1
  151. package/docs/project-index.md +137 -71
  152. package/docs/real-execution-backends.7.md +4 -0
  153. package/docs/release-and-migration.7.md +4 -0
  154. package/docs/release-tooling.7.md +4 -0
  155. package/docs/remote-source-review.7.md +4 -4
  156. package/docs/report-verifiable-bundle.7.md +1 -1
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/security-trust-hardening.7.md +3 -1
  160. package/docs/state-explosion-management.7.md +4 -0
  161. package/docs/team-collaboration.7.md +4 -0
  162. package/docs/web-desktop-workbench.7.md +15 -1
  163. package/manifest/plugin.manifest.json +1 -1
  164. package/manifest/source-context-profiles.json +9 -13
  165. package/package.json +1 -1
  166. package/scripts/agents/agent-adapter-core.js +22 -2
  167. package/scripts/agents/claude-p-agent.js +8 -3
  168. package/scripts/agents/cw-attest-wrap.js +2 -2
  169. package/scripts/agents/gemini-agent.js +5 -1
  170. package/scripts/agents/opencode-agent.js +5 -1
  171. package/scripts/bump-version.js +4 -3
  172. package/scripts/canonical-apps.js +4 -4
  173. package/scripts/dogfood-architecture-review.js +2 -3
  174. package/scripts/dogfood-release.js +3 -3
  175. package/scripts/gen-parity-doc.js +15 -2
  176. package/scripts/golden-path.js +4 -4
  177. package/scripts/onramp-check.js +1 -1
  178. package/scripts/parity-check.js +38 -21
  179. package/scripts/release-flow.js +2 -2
  180. package/scripts/sync-project-index.js +51 -27
  181. package/scripts/validate-run-state-schema.js +2 -2
  182. package/scripts/version-sync-check.js +30 -30
  183. package/dist/candidate-scoring.js +0 -729
  184. package/dist/capability-core.js +0 -1189
  185. package/dist/capability-registry.js +0 -885
  186. package/dist/cli/command-surface.js +0 -494
  187. package/dist/cli/format.js +0 -56
  188. package/dist/cli/handlers/audit.js +0 -82
  189. package/dist/cli/handlers/blackboard.js +0 -81
  190. package/dist/cli/handlers/candidate.js +0 -40
  191. package/dist/cli/handlers/clones.js +0 -34
  192. package/dist/cli/handlers/collaboration.js +0 -61
  193. package/dist/cli/handlers/eval.js +0 -40
  194. package/dist/cli/handlers/ledger.js +0 -169
  195. package/dist/cli/handlers/maintenance.js +0 -107
  196. package/dist/cli/handlers/multi-agent.js +0 -165
  197. package/dist/cli/handlers/node.js +0 -41
  198. package/dist/cli/handlers/operational.js +0 -155
  199. package/dist/cli/handlers/operator.js +0 -146
  200. package/dist/cli/handlers/registry.js +0 -68
  201. package/dist/cli/handlers/run.js +0 -153
  202. package/dist/cli/handlers/scheduling.js +0 -132
  203. package/dist/cli/handlers/workbench.js +0 -41
  204. package/dist/cli/handlers/worker.js +0 -45
  205. package/dist/cli/run-summary.js +0 -45
  206. package/dist/clones.js +0 -162
  207. package/dist/collaboration.js +0 -726
  208. package/dist/commit.js +0 -592
  209. package/dist/compare.js +0 -18
  210. package/dist/coordinator/classify.js +0 -45
  211. package/dist/coordinator/paths.js +0 -42
  212. package/dist/coordinator/util.js +0 -126
  213. package/dist/coordinator.js +0 -990
  214. package/dist/daemon.js +0 -44
  215. package/dist/dispatch.js +0 -250
  216. package/dist/doctor.js +0 -212
  217. package/dist/drive.js +0 -864
  218. package/dist/error-feedback.js +0 -419
  219. package/dist/evidence-grounding.js +0 -184
  220. package/dist/execution-backend/agent.js +0 -354
  221. package/dist/execution-backend/probes.js +0 -112
  222. package/dist/execution-backend/util.js +0 -47
  223. package/dist/execution-backend.js +0 -961
  224. package/dist/gates.js +0 -48
  225. package/dist/harness.js +0 -61
  226. package/dist/ledger.js +0 -313
  227. package/dist/loop-expansion.js +0 -60
  228. package/dist/mcp/tool-call.js +0 -470
  229. package/dist/mcp/tool-definitions.js +0 -1066
  230. package/dist/mcp-surface.js +0 -30
  231. package/dist/multi-agent/graph.js +0 -84
  232. package/dist/multi-agent/helpers.js +0 -141
  233. package/dist/multi-agent/ids.js +0 -20
  234. package/dist/multi-agent/paths.js +0 -22
  235. package/dist/multi-agent-eval/normalize.js +0 -51
  236. package/dist/multi-agent-eval.js +0 -678
  237. package/dist/multi-agent-host.js +0 -777
  238. package/dist/multi-agent.js +0 -984
  239. package/dist/node-projection.js +0 -59
  240. package/dist/node-snapshot.js +0 -260
  241. package/dist/operator-ux.js +0 -631
  242. package/dist/orchestrator/app-operations.js +0 -211
  243. package/dist/orchestrator/audit-operations.js +0 -182
  244. package/dist/orchestrator/candidate-operations.js +0 -117
  245. package/dist/orchestrator/cli-options.js +0 -294
  246. package/dist/orchestrator/collaboration-operations.js +0 -86
  247. package/dist/orchestrator/feedback-operations.js +0 -81
  248. package/dist/orchestrator/host-operations.js +0 -78
  249. package/dist/orchestrator/lifecycle-operations.js +0 -650
  250. package/dist/orchestrator/migration-operations.js +0 -44
  251. package/dist/orchestrator/multi-agent-operations.js +0 -362
  252. package/dist/orchestrator/topology-operations.js +0 -84
  253. package/dist/orchestrator.js +0 -925
  254. package/dist/pipeline-runner.js +0 -285
  255. package/dist/reclamation/hash.js +0 -72
  256. package/dist/reclamation.js +0 -812
  257. package/dist/reporter.js +0 -67
  258. package/dist/run-export.js +0 -815
  259. package/dist/run-registry/derive.js +0 -175
  260. package/dist/run-registry/format.js +0 -124
  261. package/dist/run-registry/gc.js +0 -251
  262. package/dist/run-registry/policy.js +0 -16
  263. package/dist/run-registry/queue.js +0 -115
  264. package/dist/run-registry.js +0 -850
  265. package/dist/run-state-schema.js +0 -68
  266. package/dist/scheduling.js +0 -184
  267. package/dist/state-explosion.js +0 -1014
  268. package/dist/state.js +0 -367
  269. package/dist/telemetry-ledger.js +0 -196
  270. package/dist/topology.js +0 -565
  271. package/dist/triggers.js +0 -184
  272. package/dist/trust-audit.js +0 -644
  273. package/dist/types/blackboard.js +0 -2
  274. package/dist/types/candidate.js +0 -2
  275. package/dist/types/collaboration.js +0 -2
  276. package/dist/types/core.js +0 -2
  277. package/dist/types/drive.js +0 -10
  278. package/dist/types/error-feedback.js +0 -2
  279. package/dist/types/evidence-reasoning.js +0 -2
  280. package/dist/types/execution-backend.js +0 -2
  281. package/dist/types/multi-agent.js +0 -2
  282. package/dist/types/observability.js +0 -2
  283. package/dist/types/pipeline.js +0 -2
  284. package/dist/types/reclamation.js +0 -8
  285. package/dist/types/report-bundle.js +0 -6
  286. package/dist/types/result.js +0 -2
  287. package/dist/types/run-registry.js +0 -2
  288. package/dist/types/run.js +0 -2
  289. package/dist/types/sandbox.js +0 -2
  290. package/dist/types/schedule.js +0 -2
  291. package/dist/types/state-node.js +0 -2
  292. package/dist/types/topology.js +0 -2
  293. package/dist/types/trust.js +0 -2
  294. package/dist/types/workbench.js +0 -2
  295. package/dist/types/worker.js +0 -2
  296. package/dist/types/workflow-app.js +0 -2
  297. package/dist/types.js +0 -44
  298. package/dist/util/fingerprint.js +0 -19
  299. package/dist/util/fingerprint.test.js +0 -27
  300. package/dist/verifier.js +0 -78
  301. package/dist/version.js +0 -8
  302. package/dist/workbench-host.js +0 -192
  303. package/dist/workbench.js +0 -192
  304. package/dist/worker-accept/acceptance.js +0 -114
  305. package/dist/worker-accept/blackboard-fanout.js +0 -80
  306. package/dist/worker-accept/blackboard-linkage.js +0 -19
  307. package/dist/worker-accept/context.js +0 -2
  308. package/dist/worker-accept/telemetry-ledger.js +0 -126
  309. package/dist/worker-accept/validation.js +0 -77
  310. package/dist/worker-accept/verifier-completion.js +0 -73
  311. package/dist/worker-isolation/helpers.js +0 -51
  312. package/dist/worker-isolation/paths.js +0 -46
  313. package/dist/worker-isolation.js +0 -656
  314. package/dist/workflow-api.js +0 -131
  315. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -0,0 +1,852 @@
1
+ "use strict";
2
+ // shell/worker-isolation.ts — worker scope allocation, recordWorkerOutput's
3
+ // accept pipeline.
4
+ //
5
+ // MILESTONE 6+7 (combined). Byte-exact port of the old build's
6
+ // src/worker-isolation.ts's real-execution-path (allocateWorkerScope,
7
+ // writeWorkerManifest, recordWorkerOutput, recordWorkerFailure,
8
+ // recordWorkerRetryAttempt, the worker index) — the multi-agent/
9
+ // blackboard cross-linking (worker-accept/blackboard-*.ts) is milestone
10
+ // 9's scope and is a no-op here (no case in this milestone's gate
11
+ // exercises multi-agent linkage). The accept-path ORDER matches the old
12
+ // build: validate -> attest delegation -> accept -> verify -> completion.
13
+ //
14
+ // Evidence: SPEC/pipeline-run.md's worker-isolation references;
15
+ // exechard-evidence-triple-hygiene.case.js, exechard-model-attestation-
16
+ // unreported.case.js, exec-agent-secret-redaction.case.js pin the exact
17
+ // shapes here.
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.WORKER_ISOLATION_SCHEMA_VERSION = void 0;
53
+ exports.getWorkerScope = getWorkerScope;
54
+ exports.writeWorkerManifest = writeWorkerManifest;
55
+ exports.allocateWorkerScope = allocateWorkerScope;
56
+ exports.validateWorkerBoundary = validateWorkerBoundary;
57
+ exports.recordWorkerOutput = recordWorkerOutput;
58
+ exports.recordWorkerFailure = recordWorkerFailure;
59
+ exports.recordWorkerRetryAttempt = recordWorkerRetryAttempt;
60
+ exports.showWorkerManifest = showWorkerManifest;
61
+ exports.listWorkerScopes = listWorkerScopes;
62
+ exports.summarizeWorkers = summarizeWorkers;
63
+ exports.formatWorkerSummaryText = formatWorkerSummaryText;
64
+ const fs = __importStar(require("node:fs"));
65
+ const path = __importStar(require("node:path"));
66
+ const fs_atomic_1 = require("./fs-atomic");
67
+ const node_store_1 = require("./node-store");
68
+ const state_node_1 = require("../core/state/state-node");
69
+ const validation_1 = require("../core/state/validation");
70
+ const contract_1 = require("../core/pipeline/contract");
71
+ const result_normalize_1 = require("../core/pipeline/result-normalize");
72
+ const evidence_grounding_1 = require("../core/trust/evidence-grounding");
73
+ const trust_audit_1 = require("./trust-audit");
74
+ const sandbox_profile_1 = require("./sandbox-profile");
75
+ const registry_1 = require("./execution-backend/registry");
76
+ const error_feedback_io_1 = require("./error-feedback-io");
77
+ const run_store_1 = require("./run-store");
78
+ const multi_agent_io_1 = require("./multi-agent-io");
79
+ const runtime_1 = require("../core/multi-agent/runtime");
80
+ /** The blackboard coordination block on a worker manifest, derived from the
81
+ * worker's AgentMembership linkage (undefined for a non-blackboard worker).
82
+ * Byte-behavior port of the old build's blackboardManifest. */
83
+ function workerBlackboardManifest(run, task) {
84
+ const membershipId = task?.multiAgent?.membershipId;
85
+ const membership = membershipId ? (0, runtime_1.getAgentMembership)(run, membershipId) : undefined;
86
+ const blackboardId = membership?.blackboardId;
87
+ if (!blackboardId)
88
+ return undefined;
89
+ const root = run.paths.blackboardDir || path.join(run.paths.runDir, "blackboard");
90
+ return {
91
+ id: blackboardId,
92
+ topicIds: membership?.topicIds || [],
93
+ indexPath: path.join(root, "index.json"),
94
+ messagesPath: path.join(root, "messages.jsonl"),
95
+ topicsDir: path.join(root, "topics"),
96
+ contextsDir: path.join(root, "contexts"),
97
+ artifactsDir: path.join(root, "artifacts"),
98
+ instructions: [
99
+ "Use the blackboard as shared coordination context.",
100
+ "Read index.json and the relevant topic/context/artifact files before synthesizing.",
101
+ "Cite blackboard artifact refs or message refs in result evidence when relevant.",
102
+ "Do not edit blackboard files directly; CW records accepted worker output into the blackboard.",
103
+ ],
104
+ };
105
+ }
106
+ const verifier_1 = require("./verifier");
107
+ const runner_1 = require("../core/pipeline/runner");
108
+ const hash_1 = require("../core/hash");
109
+ const telemetry_attestation_1 = require("../core/trust/telemetry-attestation");
110
+ const telemetry_ledger_io_1 = require("./telemetry-ledger-io");
111
+ exports.WORKER_ISOLATION_SCHEMA_VERSION = 1;
112
+ function workerRoot(run) {
113
+ return run.paths.workersDir || path.join(run.paths.runDir, "workers");
114
+ }
115
+ /** Record a resolved sandbox policy into run.sandboxProfiles (upsert by id) so
116
+ * the run state carries every profile a worker ran under — reports and
117
+ * operators read it. Byte-exact to the old sandbox-profile.ts helper v2
118
+ * dropped. */
119
+ function upsertRunSandboxProfile(run, policy) {
120
+ const profiles = run.sandboxProfiles || [];
121
+ const index = profiles.findIndex((candidate) => candidate.id === policy.id);
122
+ run.sandboxProfiles = (index >= 0 ? profiles.map((candidate) => (candidate.id === policy.id ? policy : candidate)) : [...profiles, policy]);
123
+ }
124
+ function ensureWorkerState(run) {
125
+ run.paths.workersDir = run.paths.workersDir || path.join(run.paths.runDir, "workers");
126
+ fs.mkdirSync(run.paths.workersDir, { recursive: true });
127
+ run.workers = run.workers || [];
128
+ }
129
+ function manifestPath(scope) {
130
+ return path.join(scope.workerDir, "manifest.json");
131
+ }
132
+ function scopePath(scope) {
133
+ return path.join(scope.workerDir, "worker.json");
134
+ }
135
+ /** Deterministic worker id: the task plus a PER-TASK sequence (count of
136
+ * worker scopes already allocated for THIS task + 1) — byte-exact port
137
+ * of the old build's src/worker-isolation/paths.ts:38-42. Re-running the
138
+ * same workflow yields byte-identical worker ids while retries of the
139
+ * SAME task still get a fresh, unique id (workerId is excluded from the
140
+ * snapshot source fingerprint, so this does not change replay digests). */
141
+ function createWorkerId(run, taskId) {
142
+ const prefix = `worker-${(0, fs_atomic_1.safeFileName)(taskId)}-`;
143
+ const seq = (run.workers || []).filter((scope) => scope.id.startsWith(prefix)).length + 1;
144
+ return `${prefix}${String(seq).padStart(4, "0")}`;
145
+ }
146
+ /** Full fail-closed shape guard for a worker.json overlay: delegates to the
147
+ * core WorkerScope guard (schemaVersion, every required string field, the
148
+ * status enum, allowedPaths/feedbackIds/errors shapes) and casts the
149
+ * result to this module's richer WorkerScope, a structural superset of the
150
+ * core WorkerScopeShape. A syntactically-invalid file throws from
151
+ * JSON.parse before this runs; a wrong-shape (but parseable) file throws a
152
+ * RecordValidationError naming the exact broken field. */
153
+ function validateWorkerScope(value) {
154
+ return (0, validation_1.validateWorkerScope)(value);
155
+ }
156
+ function getWorkerScope(run, workerId) {
157
+ ensureWorkerState(run);
158
+ const existing = (run.workers || []).find((s) => s.id === workerId);
159
+ if (existing)
160
+ return existing;
161
+ const file = path.join(workerRoot(run), (0, fs_atomic_1.safeFileName)(workerId), "worker.json");
162
+ if (!fs.existsSync(file))
163
+ return undefined;
164
+ let scope;
165
+ try {
166
+ scope = validateWorkerScope(JSON.parse(fs.readFileSync(file, "utf8")));
167
+ }
168
+ catch (error) {
169
+ // A present-but-corrupt scope fails closed with context, not a raw
170
+ // SyntaxError/validation throw bubbling up from deep in the call stack.
171
+ throw new Error(`Corrupt worker scope ${file}: ${error instanceof Error ? error.message : String(error)}`);
172
+ }
173
+ upsertWorkerScope(run, scope);
174
+ return scope;
175
+ }
176
+ /** Load every worker.json under the run's workers dir, skipping (with a
177
+ * stderr diagnostic) any one that is corrupt/partially-written so a single
178
+ * bad file cannot blank the whole listing. Byte-exact to the old build's
179
+ * loadWorkerScopesFromDisk. */
180
+ function loadWorkerScopesFromDisk(run) {
181
+ const root = workerRoot(run);
182
+ if (!fs.existsSync(root))
183
+ return [];
184
+ return fs
185
+ .readdirSync(root, { withFileTypes: true })
186
+ .filter((entry) => entry.isDirectory())
187
+ .map((entry) => path.join(root, entry.name, "worker.json"))
188
+ .filter((file) => fs.existsSync(file))
189
+ .map((file) => {
190
+ try {
191
+ return validateWorkerScope(JSON.parse(fs.readFileSync(file, "utf8")));
192
+ }
193
+ catch (error) {
194
+ process.stderr.write(`cw: skipping unreadable worker scope ${file}: ${error instanceof Error ? error.message : String(error)}\n`);
195
+ return undefined;
196
+ }
197
+ })
198
+ .filter((scope) => scope !== undefined);
199
+ }
200
+ /** Overlay disk-loaded scopes onto the in-memory list, keyed by id (disk
201
+ * wins), preserving first-seen order. */
202
+ function mergeScopes(existing, loaded) {
203
+ const byId = new Map();
204
+ for (const scope of existing)
205
+ byId.set(scope.id, scope);
206
+ for (const scope of loaded)
207
+ byId.set(scope.id, scope);
208
+ return [...byId.values()];
209
+ }
210
+ function upsertWorkerScope(run, scope) {
211
+ ensureWorkerState(run);
212
+ const scopes = run.workers || [];
213
+ const index = scopes.findIndex((s) => s.id === scope.id);
214
+ run.workers = (index >= 0 ? scopes.map((s) => (s.id === scope.id ? scope : s)) : [...scopes, scope]);
215
+ (0, fs_atomic_1.writeJson)(scopePath(scope), scope);
216
+ return scope;
217
+ }
218
+ function writeWorkerIndex(run) {
219
+ ensureWorkerState(run);
220
+ (0, fs_atomic_1.writeJson)(path.join(workerRoot(run), "index.json"), {
221
+ schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
222
+ runId: run.id,
223
+ workers: (run.workers || []).map((scope) => ({
224
+ id: scope.id,
225
+ taskId: scope.taskId,
226
+ dispatchId: scope.dispatchId,
227
+ status: scope.status,
228
+ workerDir: scope.workerDir,
229
+ manifestPath: manifestPath(scope),
230
+ resultPath: scope.resultPath,
231
+ sandboxProfileId: scope.sandboxProfileId,
232
+ backendId: scope.backendId,
233
+ feedbackIds: scope.feedbackIds,
234
+ })),
235
+ });
236
+ }
237
+ function writeWorkerManifest(run, scope) {
238
+ const task = run.tasks.find((t) => t.id === scope.taskId);
239
+ const sandboxProfileId = scope.sandboxProfileId;
240
+ const manifest = {
241
+ schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
242
+ id: scope.id,
243
+ runId: scope.runId,
244
+ taskId: scope.taskId,
245
+ dispatchId: scope.dispatchId,
246
+ createdAt: scope.createdAt,
247
+ updatedAt: scope.updatedAt,
248
+ status: scope.status,
249
+ workerDir: scope.workerDir,
250
+ scopePath: scopePath(scope),
251
+ manifestPath: manifestPath(scope),
252
+ inputPath: scope.inputPath,
253
+ resultPath: scope.resultPath,
254
+ artifactsDir: scope.artifactsDir,
255
+ logsDir: scope.logsDir,
256
+ allowedPaths: scope.allowedPaths,
257
+ sandboxProfileId,
258
+ sandboxPolicy: scope.sandboxPolicy,
259
+ sandbox: scope.sandboxPolicy
260
+ ? { profileId: scope.sandboxPolicy.id, policy: scope.sandboxPolicy, enforcedByCW: scope.sandboxPolicy.enforcement.enforcedByCW, hostRequired: scope.sandboxPolicy.enforcement.hostRequired }
261
+ : undefined,
262
+ backendId: scope.backendId,
263
+ backendAttestation: scope.backendAttestation,
264
+ multiAgent: task?.multiAgent,
265
+ blackboard: workerBlackboardManifest(run, task),
266
+ backend: scope.backendId && scope.backendAttestation
267
+ ? {
268
+ id: scope.backendId,
269
+ locality: scope.backendAttestation.locality,
270
+ kind: scope.backendAttestation.kind,
271
+ enforces: scope.backendAttestation.enforced,
272
+ attests: scope.backendAttestation.attested,
273
+ attestation: scope.backendAttestation,
274
+ }
275
+ : undefined,
276
+ retryCount: scope.retryCount,
277
+ instructions: [
278
+ "Read input.md before doing work.",
279
+ "Write the final Markdown result to result.md.",
280
+ "Write worker-local artifacts under artifacts/ and logs under logs/.",
281
+ `Sandbox profile: ${sandboxProfileId}.`,
282
+ "CW enforces profile validation and worker result acceptance only.",
283
+ "The agent host must enforce OS file access, process execution, network access, and environment filtering.",
284
+ "Do not edit shared run state files directly; CW records accepted results.",
285
+ ],
286
+ taskPath: task?.taskPath,
287
+ prompt: task?.prompt,
288
+ stateNodeId: scope.stateNodeId,
289
+ resultNodeId: scope.resultNodeId,
290
+ feedbackIds: scope.feedbackIds,
291
+ errors: scope.errors,
292
+ output: scope.output,
293
+ metadata: scope.metadata,
294
+ };
295
+ (0, fs_atomic_1.writeJson)(manifestPath(scope), manifest);
296
+ return manifest;
297
+ }
298
+ function writeWorkerInput(run, task, scope) {
299
+ const lines = [
300
+ `# Worker ${scope.id}`,
301
+ "",
302
+ `- Run: ${run.id}`,
303
+ `- Task: ${task.id}`,
304
+ `- Dispatch: ${scope.dispatchId || ""}`,
305
+ `- Result: ${scope.resultPath}`,
306
+ `- Artifacts: ${scope.artifactsDir}`,
307
+ `- Logs: ${scope.logsDir}`,
308
+ `- Sandbox Profile: ${scope.sandboxProfileId || sandbox_profile_1.DEFAULT_SANDBOX_PROFILE_ID}`,
309
+ "",
310
+ "## Task",
311
+ "",
312
+ task.prompt,
313
+ "",
314
+ "## Boundary",
315
+ "",
316
+ "- Write the final Markdown result to result.md.",
317
+ "- Keep extra files under artifacts/ or logs/.",
318
+ `- Read paths: ${(scope.sandboxPolicy?.readPaths || []).join(", ") || "none"}.`,
319
+ `- Write paths: ${(0, sandbox_profile_1.effectiveSandboxWritePaths)(scope.sandboxPolicy).join(", ") || "none"}.`,
320
+ "- CW enforces result acceptance. The host is responsible for OS/process/network/environment sandbox enforcement.",
321
+ "- Do not mutate state.json, nodes/, feedback/, dispatches/, or commits/ directly.",
322
+ "",
323
+ ];
324
+ fs.writeFileSync(scope.inputPath, lines.join("\n"), "utf8");
325
+ }
326
+ function allocateWorkerScope(run, task, options = {}) {
327
+ ensureWorkerState(run);
328
+ const existing = task.workerId ? getWorkerScope(run, String(task.workerId)) : undefined;
329
+ if (existing) {
330
+ if (existing.status === "failed" || existing.status === "orphaned") {
331
+ existing.retryCount = (existing.retryCount || 0) + 1;
332
+ existing.updatedAt = new Date().toISOString();
333
+ existing.status = options.status || "allocated";
334
+ existing.errors = [];
335
+ upsertWorkerScope(run, existing);
336
+ writeWorkerIndex(run);
337
+ }
338
+ return existing;
339
+ }
340
+ const now = new Date().toISOString();
341
+ const workerId = options.workerId || createWorkerId(run, task.id);
342
+ const workerDir = path.join(workerRoot(run), (0, fs_atomic_1.safeFileName)(workerId));
343
+ const inputPath = path.join(workerDir, "input.md");
344
+ const resultPath = path.join(workerDir, "result.md");
345
+ const artifactsDir = path.join(workerDir, "artifacts");
346
+ const logsDir = path.join(workerDir, "logs");
347
+ const sandboxProfileId = options.sandboxProfileId || sandbox_profile_1.DEFAULT_SANDBOX_PROFILE_ID;
348
+ const sandboxPolicy = (0, sandbox_profile_1.sandboxPolicyForWorker)(sandboxProfileId, {
349
+ cwd: run.cwd,
350
+ runDir: run.paths.runDir,
351
+ workerDir,
352
+ inputPath,
353
+ resultPath,
354
+ artifactsDir,
355
+ logsDir,
356
+ customProfiles: run.customSandboxProfiles,
357
+ });
358
+ const allowedPaths = (0, sandbox_profile_1.effectiveSandboxWritePaths)(sandboxPolicy);
359
+ upsertRunSandboxProfile(run, sandboxPolicy);
360
+ // Execution-backend selection (mechanism vs policy): when a backend was
361
+ // explicitly selected, record its sandbox attestation. The dispatch path is a
362
+ // delegate-host execution (the host runs the worker), so the backend enforces
363
+ // only CW's own worker-output acceptance and attests the rest.
364
+ const backendAttestation = options.backendId
365
+ ? (0, registry_1.attestSandbox)((0, registry_1.getBackendDescriptor)(options.backendId), sandboxPolicy, { mode: "delegate-host" })
366
+ : undefined;
367
+ fs.mkdirSync(artifactsDir, { recursive: true });
368
+ fs.mkdirSync(logsDir, { recursive: true });
369
+ const scope = {
370
+ schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
371
+ id: workerId,
372
+ runId: run.id,
373
+ taskId: task.id,
374
+ dispatchId: options.dispatchId || task.dispatchId,
375
+ createdAt: now,
376
+ updatedAt: now,
377
+ status: options.status || "allocated",
378
+ workerDir,
379
+ inputPath,
380
+ resultPath,
381
+ artifactsDir,
382
+ logsDir,
383
+ allowedPaths,
384
+ sandboxProfileId: sandboxPolicy.id,
385
+ sandboxPolicy,
386
+ backendId: options.backendId,
387
+ backendAttestation,
388
+ stateNodeId: task.stateNodeId,
389
+ feedbackIds: [],
390
+ errors: [],
391
+ metadata: options.metadata,
392
+ };
393
+ writeWorkerInput(run, task, scope);
394
+ writeWorkerManifest(run, scope);
395
+ upsertWorkerScope(run, scope);
396
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
397
+ kind: "worker.sandbox-profile",
398
+ decision: "recorded",
399
+ source: "runtime-derived",
400
+ workerId: scope.id,
401
+ taskId: task.id,
402
+ sandboxProfileId: sandboxPolicy.id,
403
+ policySnapshot: sandboxPolicy,
404
+ metadata: { dispatchId: scope.dispatchId, workerDir: scope.workerDir, allowedPaths },
405
+ });
406
+ if (options.backendId && backendAttestation) {
407
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
408
+ kind: "worker.backend",
409
+ decision: backendAttestation.status === "refused" ? "denied" : "recorded",
410
+ source: "runtime-derived",
411
+ workerId: scope.id,
412
+ taskId: task.id,
413
+ sandboxProfileId: sandboxPolicy.id,
414
+ policySnapshot: sandboxPolicy,
415
+ metadata: {
416
+ backendId: options.backendId,
417
+ attestationStatus: backendAttestation.status,
418
+ enforced: backendAttestation.enforced,
419
+ attested: backendAttestation.attested,
420
+ unenforceable: backendAttestation.unenforceable,
421
+ dispatchId: scope.dispatchId,
422
+ },
423
+ });
424
+ }
425
+ task.workerId = scope.id;
426
+ task.workerManifestPath = manifestPath(scope);
427
+ task.sandboxProfileId = sandboxPolicy.id;
428
+ task.sandboxPolicy = sandboxPolicy;
429
+ task.backendId = options.backendId;
430
+ task.backendAttestation = backendAttestation;
431
+ writeWorkerIndex(run);
432
+ return scope;
433
+ }
434
+ function requireWorkerScope(run, workerId) {
435
+ const scope = getWorkerScope(run, workerId);
436
+ if (!scope)
437
+ throw new Error(`Unknown worker for run ${run.id}: ${workerId}`);
438
+ return scope;
439
+ }
440
+ function requireWorkerTask(run, scope) {
441
+ const task = run.tasks.find((t) => t.id === scope.taskId);
442
+ if (!task)
443
+ throw new Error(`Unknown task for worker ${scope.id}: ${scope.taskId}`);
444
+ return task;
445
+ }
446
+ /** recordWorkerOutput — the accept-path orchestrator. Order: validate ->
447
+ * attest delegation -> accept -> verify -> completion (byte-exact to the
448
+ * old build; multi-agent fan-out is a no-op here). */
449
+ /** Record the worker.sandbox-boundary trust-audit event: a successful write-path
450
+ * check documents transparently what CW enforced (write paths) vs what is
451
+ * delegated to the host (execute/network/env). Byte-exact to the old build's
452
+ * event emitted inside validateWorkerBoundary. */
453
+ function recordSandboxBoundaryEvent(run, scope) {
454
+ const policy = scope.sandboxPolicy;
455
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
456
+ kind: "worker.sandbox-boundary",
457
+ decision: "allowed",
458
+ source: "cw-validated",
459
+ workerId: scope.id,
460
+ taskId: scope.taskId,
461
+ sandboxProfileId: policy.id,
462
+ policyRef: `execute=${policy.execute.mode} network=${policy.network.mode} env.inherit=${policy.env.inherit}`,
463
+ command: policy.execute.mode,
464
+ networkTarget: policy.network.mode,
465
+ policySnapshot: policy,
466
+ metadata: {
467
+ enforced_by_cw: ["write-paths"],
468
+ delegated_to_host: ["execute", "network", "env"],
469
+ env_inherit: policy.env.inherit,
470
+ },
471
+ });
472
+ }
473
+ /** `cw worker validate <run-id> <worker-id> [target-file]` — re-run the
474
+ * write-path boundary check for a worker (default target = its result file).
475
+ * Returns the violation, or null when the write path is allowed (also
476
+ * recording the sandbox-boundary transparency event on success). */
477
+ function validateWorkerBoundary(run, workerId, options = {}) {
478
+ const scope = requireWorkerScope(run, workerId);
479
+ const rawPath = path.resolve(String(options.path || scope.resultPath));
480
+ const violation = (0, sandbox_profile_1.validateSandboxWrite)(scope.sandboxPolicy, rawPath, workerId);
481
+ if (!violation)
482
+ recordSandboxBoundaryEvent(run, scope);
483
+ return violation;
484
+ }
485
+ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
486
+ const scope = requireWorkerScope(run, workerId);
487
+ const task = requireWorkerTask(run, scope);
488
+ const absoluteResultPath = path.resolve(resultPath);
489
+ // Step 1: sandbox boundary + result-file existence + envelope contract.
490
+ const violation = (0, sandbox_profile_1.validateSandboxWrite)(scope.sandboxPolicy, absoluteResultPath, workerId);
491
+ if (violation) {
492
+ (0, trust_audit_1.recordSandboxPathDecision)(run, { workerId, taskId: task.id, sandboxProfileId: scope.sandboxProfileId, policySnapshot: scope.sandboxPolicy, target: absoluteResultPath, decision: "denied", metadata: { code: violation.code } });
493
+ recordWorkerFailure(run, workerId, violation.message, { code: violation.code, retryable: false });
494
+ throw new Error(violation.message);
495
+ }
496
+ // Write path enforced by CW; record the enforced-vs-delegated policy split.
497
+ recordSandboxBoundaryEvent(run, scope);
498
+ if (!fs.existsSync(absoluteResultPath)) {
499
+ recordWorkerFailure(run, workerId, `Worker result file does not exist: ${absoluteResultPath}`, { code: "worker-result-missing", retryable: true });
500
+ throw new Error(`Worker result file does not exist: ${absoluteResultPath}`);
501
+ }
502
+ const rawResult = fs.readFileSync(absoluteResultPath, "utf8");
503
+ let parsedResult;
504
+ try {
505
+ parsedResult = (0, result_normalize_1.normalizeResultEnvelope)(rawResult);
506
+ }
507
+ catch (error) {
508
+ const message = error instanceof Error ? error.message : String(error);
509
+ recordWorkerFailure(run, workerId, message, { code: "result-parse-error", retryable: false });
510
+ throw error;
511
+ }
512
+ if ((0, verifier_1.taskRequiresEvidence)(task) && !parsedResult.evidence.some((e) => (0, evidence_grounding_1.isGroundedEvidence)(e))) {
513
+ const message = `Task ${task.id} requires grounded cw:result evidence (a path-like locator, URL, or namespace:value token — not free text)`;
514
+ recordWorkerFailure(run, workerId, message, { code: "missing-required-evidence", retryable: false });
515
+ throw new Error(message);
516
+ }
517
+ // Step 2: attest delegation (the agent-hop provenance). Track 1: verify
518
+ // the agent's signed telemetry BEFORE recording it — CW holds only the
519
+ // operator's PUBLIC key, so this verifies attribution, never measures
520
+ // usage. resultDigest binds the agent's findings into the signature:
521
+ // CW recomputes the digest from the ACCEPTED result bytes so a result
522
+ // edited after signing fails verification; a signer that did not
523
+ // cover the result still verifies (4-field back-compat).
524
+ const delegation = options.agentDelegation;
525
+ const telemetry = delegation
526
+ ? (0, telemetry_attestation_1.verifyTelemetryAttestation)(delegation.reportedUsage, delegation.usageSignature, (0, telemetry_attestation_1.resolveTrustPublicKey)(delegation.usageTrustPublicKey), {
527
+ runId: run.id,
528
+ taskId: task.id,
529
+ promptDigest: delegation.promptDigest,
530
+ resultDigest: (0, hash_1.sha256)(rawResult),
531
+ })
532
+ : undefined;
533
+ // Opt-in fail-closed gate (default off): when the operator requires
534
+ // attested telemetry, a delegated hop whose verdict is not `attested`
535
+ // is REJECTED here — BEFORE any accept-side state mutation — so the
536
+ // drive parks it instead of recording unverifiable usage.
537
+ if (options.requireAttestedTelemetry && telemetry && telemetry.status !== "attested") {
538
+ const message = `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`;
539
+ recordWorkerFailure(run, workerId, message, { code: "telemetry-unattested-blocked", path: absoluteResultPath, retryable: false });
540
+ throw new Error(message);
541
+ }
542
+ const agentDelegationMeta = delegation
543
+ ? {
544
+ schemaVersion: 1,
545
+ backendId: "agent",
546
+ handle: delegation.handle,
547
+ model: delegation.model,
548
+ promptDigest: delegation.promptDigest,
549
+ resultDigest: (0, hash_1.sha256)(rawResult),
550
+ command: delegation.command,
551
+ args: delegation.args,
552
+ exitCode: delegation.exitCode,
553
+ ...(delegation.reportedUsage ? { reportedUsage: delegation.reportedUsage } : {}),
554
+ ...(delegation.usageSignature ? { usageSignature: delegation.usageSignature } : {}),
555
+ ...(telemetry ? { usageAttestation: telemetry.status, usageAttestationReason: telemetry.reason } : {}),
556
+ }
557
+ : undefined;
558
+ // Step 3: accept — the irreversible mutation.
559
+ const pathAudit = (0, trust_audit_1.recordSandboxPathDecision)(run, { workerId, taskId: task.id, sandboxProfileId: scope.sandboxProfileId, policySnapshot: scope.sandboxPolicy, target: absoluteResultPath, decision: "allowed", metadata: { operation: "worker-output-acceptance" } });
560
+ const destination = path.join(run.paths.resultsDir, `${(0, fs_atomic_1.safeFileName)(task.id)}.md`);
561
+ fs.mkdirSync(run.paths.resultsDir, { recursive: true });
562
+ fs.copyFileSync(absoluteResultPath, destination);
563
+ task.status = "completed";
564
+ task.completedAt = new Date().toISOString();
565
+ task.resultPath = destination;
566
+ task.loopStage = "observe";
567
+ task.result = parsedResult;
568
+ const evidence = (0, trust_audit_1.normalizeEvidence)(run, parsedResult.evidence.map((entry, index) => ({ id: `result:${index + 1}`, source: "cw:result", locator: entry, summary: entry })), { source: "cw-validated", workerId, taskId: task.id, auditEventIds: [pathAudit.id] });
569
+ let resultNode = (0, state_node_1.createStateNode)({
570
+ id: `${run.id}:result:${task.id}`,
571
+ kind: "result",
572
+ status: "completed",
573
+ loopStage: "observe",
574
+ inputs: { taskId: task.id, dispatchId: task.dispatchId, workerId },
575
+ outputs: parsedResult,
576
+ artifacts: [
577
+ { id: "result", kind: "markdown", path: destination },
578
+ { id: "worker-result", kind: "markdown", path: absoluteResultPath },
579
+ ],
580
+ evidence,
581
+ parents: task.dispatchId ? [`${run.id}:dispatch:${task.dispatchId}`] : [String(task.stateNodeId || `${run.id}:task:${task.id}`)],
582
+ contractId: contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
583
+ metadata: {
584
+ taskId: task.id,
585
+ workerId,
586
+ workerDir: scope.workerDir,
587
+ sandboxProfileId: scope.sandboxProfileId,
588
+ auditEventIds: [pathAudit.id],
589
+ ...((0, result_normalize_1.isEmptyCapture)(parsedResult) ? { captureWarning: "no findings or evidence captured from result.md" } : {}),
590
+ ...(agentDelegationMeta ? { agentDelegation: agentDelegationMeta } : {}),
591
+ },
592
+ });
593
+ const acceptedAudit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
594
+ kind: "worker.output",
595
+ decision: "accepted",
596
+ source: "cw-validated",
597
+ workerId,
598
+ taskId: task.id,
599
+ nodeId: resultNode.id,
600
+ sandboxProfileId: scope.sandboxProfileId,
601
+ policySnapshot: scope.sandboxPolicy,
602
+ normalizedPath: absoluteResultPath,
603
+ evidence,
604
+ parentEventIds: [pathAudit.id],
605
+ metadata: { destination },
606
+ });
607
+ resultNode.evidence = (0, trust_audit_1.normalizeEvidence)(run, resultNode.evidence, { source: "cw-validated", workerId, taskId: task.id, resultNodeId: resultNode.id, auditEventIds: [pathAudit.id, acceptedAudit.id] });
608
+ resultNode = (0, node_store_1.appendRunNode)(run, resultNode);
609
+ task.resultNodeId = resultNode.id;
610
+ // Multi-agent: if this task belongs to an AgentMembership, sync the membership
611
+ // to "reported" with this result's evidence so a fanin can see it as complete
612
+ // (isMembershipReported). No-op for a non-multi-agent task (no membership
613
+ // matches workerId/taskId). Byte-behavior port of the old accept path.
614
+ if (task.multiAgent) {
615
+ (0, multi_agent_io_1.recordMultiAgentWorkerOutput)(run, { workerId, taskId: task.id, resultNodeId: resultNode.id, evidence: resultNode.evidence });
616
+ }
617
+ if ((0, result_normalize_1.isEmptyCapture)(parsedResult)) {
618
+ (0, trust_audit_1.recordTrustAuditEvent)(run, { kind: "worker.capture-warning", decision: "recorded", source: "cw-validated", workerId, taskId: task.id, nodeId: resultNode.id, parentEventIds: [acceptedAudit.id], metadata: { reason: "no findings or evidence captured from result.md", resultPath: destination } });
619
+ }
620
+ if (delegation && agentDelegationMeta) {
621
+ // Track 1 (tamper-evidence): bind this verdict into the append-only,
622
+ // hash-chained telemetry ledger BEFORE the audit event, so the event
623
+ // can cross-link the record hash. Editing the recorded verdict/usage
624
+ // later breaks the chain (verifyTelemetryLedger). Only when a
625
+ // verdict was computed (every agent hop gets one, even "absent").
626
+ const ledgerRecord = agentDelegationMeta.usageAttestation
627
+ ? (0, telemetry_ledger_io_1.appendTelemetryAttestation)(run, {
628
+ workerId,
629
+ taskId: task.id,
630
+ promptDigest: agentDelegationMeta.promptDigest,
631
+ reportedUsage: agentDelegationMeta.reportedUsage,
632
+ usageSignature: agentDelegationMeta.usageSignature,
633
+ // Store the signed result digest ONLY when the signature
634
+ // actually covered it, so the offline re-verifier can
635
+ // reconstruct the 5-field payload.
636
+ resultDigest: telemetry?.coversResult ? agentDelegationMeta.resultDigest : undefined,
637
+ attestation: agentDelegationMeta.usageAttestation,
638
+ attestationReason: agentDelegationMeta.usageAttestationReason,
639
+ })
640
+ : undefined;
641
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
642
+ kind: "worker.agent-delegation",
643
+ decision: "recorded",
644
+ source: "host-attested",
645
+ workerId,
646
+ taskId: task.id,
647
+ nodeId: resultNode.id,
648
+ sandboxProfileId: scope.sandboxProfileId,
649
+ policySnapshot: scope.sandboxPolicy,
650
+ parentEventIds: [acceptedAudit.id],
651
+ metadata: {
652
+ backendId: "agent",
653
+ handleKind: delegation.handle.kind,
654
+ handleRef: delegation.handle.ref,
655
+ model: delegation.model,
656
+ promptDigest: delegation.promptDigest,
657
+ resultDigest: agentDelegationMeta.resultDigest,
658
+ command: delegation.command,
659
+ args: delegation.args,
660
+ exitCode: delegation.exitCode,
661
+ ...(agentDelegationMeta.usageAttestation
662
+ ? {
663
+ telemetryAttestation: agentDelegationMeta.usageAttestation,
664
+ ...(agentDelegationMeta.usageAttestationReason ? { telemetryAttestationReason: agentDelegationMeta.usageAttestationReason } : {}),
665
+ ...(agentDelegationMeta.reportedUsage ? { reportedUsage: agentDelegationMeta.reportedUsage } : {}),
666
+ ...(ledgerRecord ? { telemetryRecordId: ledgerRecord.recordId, telemetryRecordHash: ledgerRecord.recordHash, telemetryPrevHash: ledgerRecord.prevHash } : {}),
667
+ }
668
+ : {}),
669
+ },
670
+ });
671
+ }
672
+ // Step 4: verify — drive the pipeline's "verify" stage off the accepted result.
673
+ const verifierResult = (0, runner_1.runPipelineStage)(run, "verify", resultNode.id, {
674
+ outputNodeId: `${run.id}:verifier:${task.id}`,
675
+ outputStatus: "verified",
676
+ loopStage: "adjust",
677
+ outputs: { accepted: true, workerId },
678
+ artifacts: [{ id: "result", kind: "markdown", path: destination }],
679
+ evidence: resultNode.evidence.length ? resultNode.evidence : [{ id: "result:summary", source: "summary", summary: parsedResult.summary }],
680
+ metadata: { taskId: task.id, workerId, resultNodeId: resultNode.id, sandboxProfileId: scope.sandboxProfileId },
681
+ }, { persist: false, persistNode: node_store_1.appendRunNode });
682
+ task.verifierNodeId = verifierResult.outputNodeId;
683
+ // Step 5: completion — persist the worker scope with the verify-derived status.
684
+ const output = { workerId, taskId: task.id, resultPath: absoluteResultPath, recordedAt: new Date().toISOString(), stateNodeId: resultNode.id, verifierNodeId: task.verifierNodeId, auditEventIds: [pathAudit.id, acceptedAudit.id] };
685
+ const reportedModel = agentDelegationMeta && agentDelegationMeta.model && agentDelegationMeta.model !== "unreported" ? agentDelegationMeta.model : undefined;
686
+ // Host-attested usage rides on the worker record. Recorded when the agent
687
+ // REPORTED a model OR token usage — `unreported`/absent stays ABSENT (never
688
+ // backfilled from the operator-chosen CW_AGENT_MODEL, never made up).
689
+ // Track 1: the attestation verdict (`attested`/`unattested`/`absent`) and its
690
+ // reason ride along, and the token buckets come from normalizeReportedUsage
691
+ // (tolerates snake_case/camelCase) — CW still never measures usage, it only
692
+ // records + labels what the agent self-reported. Byte-exact to the old
693
+ // build's src/worker-accept/verifier-completion.ts:58-68.
694
+ const usageRecord = agentDelegationMeta && (reportedModel || agentDelegationMeta.reportedUsage)
695
+ ? {
696
+ schemaVersion: 1,
697
+ source: "host-attested",
698
+ ...(reportedModel ? { model: reportedModel } : {}),
699
+ ...(0, telemetry_attestation_1.normalizeReportedUsage)(agentDelegationMeta.reportedUsage),
700
+ attestedAt: new Date().toISOString(),
701
+ ...(telemetry ? { attestation: telemetry.status, ...(telemetry.reason ? { attestationReason: telemetry.reason } : {}) } : {}),
702
+ note: "agent-delegation host-attested usage",
703
+ }
704
+ : undefined;
705
+ const updatedScope = {
706
+ ...scope,
707
+ updatedAt: new Date().toISOString(),
708
+ status: verifierResult.status === "advanced" ? "verified" : "completed",
709
+ resultNodeId: resultNode.id,
710
+ output,
711
+ outputDigest: sha256Local(rawResult),
712
+ outputSizeBytes: Buffer.byteLength(rawResult, "utf8"),
713
+ ...(usageRecord ? { usage: usageRecord } : {}),
714
+ };
715
+ upsertWorkerScope(run, updatedScope);
716
+ writeWorkerManifest(run, updatedScope);
717
+ writeWorkerIndex(run);
718
+ return output;
719
+ }
720
+ function sha256Local(value) {
721
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
722
+ const { createHash } = require("node:crypto");
723
+ return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
724
+ }
725
+ function recordWorkerFailure(run, workerId, error, options = {}) {
726
+ const scope = requireWorkerScope(run, workerId);
727
+ const task = requireWorkerTask(run, scope);
728
+ const message = error instanceof Error ? error.message : String(error);
729
+ const structured = { code: options.code || "worker-runtime-error", message, at: new Date().toISOString(), path: options.path, retryable: options.retryable ?? false };
730
+ const failureNodeId = `${run.id}:worker:${(0, fs_atomic_1.safeFileName)(workerId)}:failure:${scope.errors.length + 1}`;
731
+ let failureNode = (0, state_node_1.recordNodeError)((0, state_node_1.createStateNode)({ id: failureNodeId, kind: "error", status: "pending", loopStage: "adjust", inputs: { workerId, taskId: task.id, dispatchId: scope.dispatchId }, parents: task.stateNodeId ? [String(task.stateNodeId)] : [], contractId: contract_1.DEFAULT_PIPELINE_CONTRACT_ID, metadata: { workerId, taskId: task.id, dispatchId: scope.dispatchId, workerDir: scope.workerDir, sandboxProfileId: scope.sandboxProfileId } }), structured);
732
+ if (task.stateNodeId) {
733
+ const parent = (run.nodes || []).find((n) => n.id === task.stateNodeId);
734
+ if (parent) {
735
+ const [linkedParent, linkedChild] = (0, state_node_1.linkStateNodes)(parent, failureNode);
736
+ (0, node_store_1.appendRunNode)(run, linkedParent);
737
+ failureNode = linkedChild;
738
+ }
739
+ }
740
+ failureNode = (0, node_store_1.appendRunNode)(run, failureNode);
741
+ task.status = "failed";
742
+ task.loopStage = "adjust";
743
+ // Record the failure as append-only operator feedback so worker.feedbackIds
744
+ // and run.feedback carry it (its absence cascaded: failed workers left no
745
+ // feedback trail). Byte-exact to the old build.
746
+ const feedback = (0, error_feedback_io_1.recordFeedback)(run, {
747
+ source: "pipeline-runner",
748
+ error: structured,
749
+ nodeId: failureNode.id,
750
+ taskId: task.id,
751
+ path: structured.path,
752
+ retryable: structured.retryable,
753
+ artifacts: failureNode.artifacts,
754
+ metadata: { workerId, dispatchId: scope.dispatchId, workerDir: scope.workerDir, sandboxProfileId: scope.sandboxProfileId, sandboxPolicy: scope.sandboxPolicy, allowedPaths: scope.allowedPaths, details: structured.details },
755
+ }, { persist: false });
756
+ (0, trust_audit_1.recordTrustAuditEvent)(run, { kind: "worker.failure", decision: structured.code === "worker-boundary-violation" || structured.code.startsWith("sandbox-") ? "denied" : "failed", source: structured.code.startsWith("sandbox-") || structured.code === "worker-boundary-violation" ? "cw-validated" : "runtime-derived", workerId, taskId: task.id, nodeId: failureNode.id, feedbackIds: [feedback.id], sandboxProfileId: scope.sandboxProfileId, policySnapshot: scope.sandboxPolicy, normalizedPath: structured.path, metadata: { code: structured.code, dispatchId: scope.dispatchId } });
757
+ const updated = upsertWorkerScope(run, {
758
+ ...scope,
759
+ updatedAt: new Date().toISOString(),
760
+ status: structured.code === "worker-boundary-violation" || structured.code.startsWith("sandbox-") ? "rejected" : "failed",
761
+ retryCount: typeof options.retryCount === "number" ? options.retryCount : scope.retryCount,
762
+ feedbackIds: [...new Set([...(scope.feedbackIds || []), feedback.id])],
763
+ errors: [...(scope.errors || []), structured],
764
+ });
765
+ // Byte-exact to the old build's updateWorkerScope: worker.json (scope)
766
+ // AND manifest.json must both reflect the terminal park state — a bare
767
+ // upsertWorkerScope only rewrites worker.json, leaving manifest.json
768
+ // (what `cw worker manifest`/`cw worker show` and operators read)
769
+ // stale at whatever retryCount/status it had at dispatch time.
770
+ writeWorkerManifest(run, updated);
771
+ writeWorkerIndex(run);
772
+ if (options.persist !== false)
773
+ (0, run_store_1.saveCheckpoint)(run);
774
+ return updated;
775
+ }
776
+ function recordWorkerRetryAttempt(run, workerId, attempts, reason) {
777
+ const scope = requireWorkerScope(run, workerId);
778
+ const updated = upsertWorkerScope(run, {
779
+ ...scope,
780
+ updatedAt: new Date().toISOString(),
781
+ retryCount: attempts,
782
+ metadata: { ...(scope.metadata || {}), agentDelegationAttempts: attempts, agentDelegationLastFailure: reason },
783
+ });
784
+ writeWorkerManifest(run, updated);
785
+ return updated;
786
+ }
787
+ function showWorkerManifest(run, workerId) {
788
+ const scope = requireWorkerScope(run, workerId);
789
+ const task = run.tasks.find((t) => t.id === scope.taskId);
790
+ return { resultPath: scope.resultPath, inputPath: scope.inputPath, manifestPath: manifestPath(scope), workerDir: scope.workerDir, prompt: task?.prompt, sandboxPolicy: scope.sandboxPolicy };
791
+ }
792
+ /** MILESTONE 11 (reporting/observability) — `cw worker list [--status]`. */
793
+ function listWorkerScopes(run, options = {}) {
794
+ ensureWorkerState(run);
795
+ // Reload from disk and merge so a listing reflects the durable truth (and a
796
+ // single corrupt worker.json is skipped, not fatal) — an in-memory-only slice
797
+ // silently drops workers whenever run.workers was reset.
798
+ const merged = mergeScopes(run.workers || [], loadWorkerScopesFromDisk(run));
799
+ run.workers = merged;
800
+ const workers = merged.slice().sort((a, b) => a.id.localeCompare(b.id));
801
+ return options.status ? workers.filter((w) => w.status === options.status) : workers;
802
+ }
803
+ function countByStatus(workers) {
804
+ const counts = {};
805
+ for (const w of workers)
806
+ counts[w.status] = (counts[w.status] || 0) + 1;
807
+ return counts;
808
+ }
809
+ /** `cw worker summary <run-id>` — the workbench `worker.summary` panel and
810
+ * report.ts's own worker rollup share this one function. */
811
+ function summarizeWorkers(run) {
812
+ const workers = listWorkerScopes(run);
813
+ return {
814
+ total: workers.length,
815
+ byStatus: countByStatus(workers),
816
+ manifestPaths: workers.map((w) => manifestPath(w)),
817
+ failed: workers.filter((w) => w.status === "failed" || w.status === "rejected").map((w) => ({ id: w.id, status: w.status, feedbackIds: w.feedbackIds || [] })),
818
+ };
819
+ }
820
+ function countBucket(values) {
821
+ const counts = {};
822
+ for (const value of values)
823
+ counts[value] = (counts[value] || 0) + 1;
824
+ return counts;
825
+ }
826
+ function formatCountBucket(counts) {
827
+ const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
828
+ if (!entries.length)
829
+ return "none";
830
+ return entries.map(([k, v]) => `${k}=${v}`).join(", ");
831
+ }
832
+ /** `cw worker summary <run-id>` human text — port of the old build's
833
+ * formatWorkerPanel (operator-ux/format.ts): a `Workers` rollup with
834
+ * status/sandbox/backend counts and one line per worker naming its
835
+ * sandbox profile and manifest path. */
836
+ function formatWorkerSummaryText(run) {
837
+ const workers = listWorkerScopes(run);
838
+ const lines = [
839
+ "Workers",
840
+ ` total=${workers.length}; status=${formatCountBucket(countBucket(workers.map((w) => w.status)))}; sandbox=${formatCountBucket(countBucket(workers.map((w) => w.sandboxProfileId || "none")))}; backend=${formatCountBucket(countBucket(workers.map((w) => w.backendId || "none")))}`,
841
+ ];
842
+ for (const worker of workers.slice(0, 8)) {
843
+ lines.push(` ${worker.id}: ${worker.status}, task=${worker.taskId}, sandbox=${worker.sandboxProfileId || "none"}, backend=${worker.backendId || "none"}`);
844
+ lines.push(` manifest=${manifestPath(worker)}`);
845
+ lines.push(` result=${worker.resultPath}`);
846
+ if ((worker.feedbackIds || []).length)
847
+ lines.push(` feedback=${worker.feedbackIds.join(", ")}`);
848
+ }
849
+ if (workers.length > 8)
850
+ lines.push(` ... ${workers.length - 8} more worker(s)`);
851
+ return lines.join("\n");
852
+ }