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
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ // core/multi-agent/topology.ts — OFFICIAL_TOPOLOGIES, applyTopology's
3
+ // decision half, role-width math, collector-exclusion, trust-authority.
4
+ //
5
+ // MILESTONE 9. Byte-exact port of the old build's src/topology.ts, minus
6
+ // the actual multi-agent/blackboard/coordinator record creation and audit
7
+ // writes (those are impure — see shell/topology-io.ts, which calls this
8
+ // file's pure helpers then wires createMultiAgentRun/createAgentRole/
9
+ // createBlackboardTopic/etc via shell/multi-agent-io.ts +
10
+ // shell/coordinator-io.ts, byte-identical to the old build's applyTopology
11
+ // call sequence).
12
+ //
13
+ // BYTE-COMPAT ITEM 3 [load-bearing, HIGH priority]: `unique` in THIS file
14
+ // does NOT sort (dedup only, insertion order preserved) — the opposite of
15
+ // core/multi-agent/runtime.ts's sorting `unique`. Never merge the two. See
16
+ // uniquedual-role-vs-candidate-order.case.js.
17
+ //
18
+ // Evidence: SPEC/multi-agent.md section B ("Topologies"), "Topology error
19
+ // strings and outputs"; plugins/cool-workflow/src/topology.ts (byte-exact
20
+ // source).
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.OFFICIAL_TOPOLOGIES = exports.TOPOLOGY_SCHEMA_VERSION = void 0;
23
+ exports.unique = unique;
24
+ exports.registerTopology = registerTopology;
25
+ exports.listTopologyDefinitions = listTopologyDefinitions;
26
+ exports.getTopologyDefinition = getTopologyDefinition;
27
+ exports.validateTopologyDefinition = validateTopologyDefinition;
28
+ exports.withLegacyRoleCounts = withLegacyRoleCounts;
29
+ exports.materializedRoles = materializedRoles;
30
+ exports.fanoutRoleIds = fanoutRoleIds;
31
+ exports.selectedTaskIds = selectedTaskIds;
32
+ exports.topologyRunId = topologyRunId;
33
+ exports.nextActionsFor = nextActionsFor;
34
+ exports.statusToNodeStatus = statusToNodeStatus;
35
+ exports.buildTopologyGraphFromRuns = buildTopologyGraphFromRuns;
36
+ exports.TOPOLOGY_SCHEMA_VERSION = 1;
37
+ /** Dedup, insertion-order preserved (does NOT sort) — the topology-side
38
+ * `unique` twin. See file header byte-compat note. */
39
+ function unique(items) {
40
+ return [...new Set(items.filter((item) => item !== undefined && item !== null))];
41
+ }
42
+ function roleSpec(id, title, responsibilities, expectedArtifacts, faninObligations, count) {
43
+ return { id, title, responsibilities, requiredEvidence: expectedArtifacts, expectedArtifacts, faninObligations, ...(count !== undefined ? { count } : {}) };
44
+ }
45
+ function topicSpec(id, title, description) {
46
+ return { id, title, description };
47
+ }
48
+ function phaseSpec(id, title, roleIds, fanout, fanin, requiredEvidence, coordinatorDecisionKinds) {
49
+ return { id, title, roleIds, fanout, fanin, requiredEvidence, coordinatorDecisionKinds };
50
+ }
51
+ /** The three built-in topologies, in this exact order. Byte-exact
52
+ * transcription of src/topology.ts:58-137. */
53
+ exports.OFFICIAL_TOPOLOGIES = [
54
+ {
55
+ schemaVersion: 1,
56
+ id: "map-reduce",
57
+ title: "Map-Reduce",
58
+ summary: "Fan out mapper roles, index mapper evidence on the blackboard, then reduce only after required evidence is present.",
59
+ roles: [
60
+ roleSpec("mapper", "Mapper", ["Produce an independent shard result and cite evidence."], ["mapper output artifact"], ["indexed mapper artifact"], 2),
61
+ roleSpec("reducer", "Reducer", ["Synthesize mapper outputs only after fanin is verifier-ready."], ["reducer synthesis"], ["all mapper evidence"]),
62
+ ],
63
+ groups: [{ id: "map-reduce", title: "Map-Reduce Group", roleIds: ["mapper", "reducer"] }],
64
+ blackboardTopics: [
65
+ topicSpec("mapper-outputs", "Mapper Outputs", "Indexed mapper result artifacts and evidence."),
66
+ topicSpec("reducer-synthesis", "Reducer Synthesis", "Reducer fanin readiness and synthesis provenance."),
67
+ ],
68
+ phases: [
69
+ phaseSpec("map", "Map", ["mapper"], true, false, ["mapper output artifact"], ["artifact-index"]),
70
+ phaseSpec("reduce", "Reduce", ["reducer"], false, true, ["all mapper evidence"], ["fanin-readiness", "candidate-synthesis"]),
71
+ ],
72
+ fanoutStrategy: "one membership per mapper role over selected run tasks",
73
+ faninStrategy: "required mapper roles must report result evidence and indexed blackboard artifacts",
74
+ requiredEvidence: ["mapper output artifact", "blackboard artifact ref", "reducer synthesis"],
75
+ coordinatorDecisions: ["artifact-index", "fanin-readiness", "candidate-synthesis"],
76
+ candidateExpectations: ["Reducer result becomes a candidate only with mapper provenance."],
77
+ verifierGates: ["Reducer fanin must be ready before completion or commit."],
78
+ },
79
+ {
80
+ schemaVersion: 1,
81
+ id: "debate",
82
+ title: "Debate",
83
+ summary: "Record opposing claims, rebuttal rounds, conflict context, coordinator decisions, and final synthesis on shared topics.",
84
+ roles: [
85
+ roleSpec("position-a", "Position A", ["Argue one supported position with evidence."], ["claim message"], ["round messages"]),
86
+ roleSpec("position-b", "Position B", ["Argue a contrasting position with evidence."], ["counterclaim message"], ["round messages"]),
87
+ roleSpec("synthesizer", "Synthesis", ["Resolve or preserve conflicts with citations."], ["debate synthesis"], ["coordinator decisions"]),
88
+ ],
89
+ groups: [{ id: "debate", title: "Debate Group", roleIds: ["position-a", "position-b", "synthesizer"] }],
90
+ blackboardTopics: [
91
+ topicSpec("debate-rounds", "Debate Rounds", "Claim and rebuttal messages by round."),
92
+ topicSpec("debate-conflicts", "Conflict Context", "Conflicting or unresolved claims."),
93
+ topicSpec("debate-synthesis", "Final Synthesis", "Accepted, rejected, conflicting, and unresolved claims."),
94
+ ],
95
+ phases: [
96
+ phaseSpec("opening", "Opening Claims", ["position-a", "position-b"], true, false, ["claim evidence"], ["message-moderation"]),
97
+ phaseSpec("rebuttal", "Rebuttal Rounds", ["position-a", "position-b"], true, false, ["response evidence"], ["conflict-resolution"]),
98
+ phaseSpec("synthesis", "Synthesis", ["synthesizer"], false, true, ["debate messages", "coordinator decisions"], ["candidate-synthesis"]),
99
+ ],
100
+ fanoutStrategy: "opposing roles write blackboard messages for each round",
101
+ faninStrategy: "synthesis requires debate messages and coordinator claim decisions",
102
+ requiredEvidence: ["debate message", "conflict context", "coordinator decision", "final synthesis"],
103
+ coordinatorDecisions: ["message-moderation", "conflict-resolution", "candidate-synthesis"],
104
+ candidateExpectations: ["Final synthesis cites debate messages and decisions."],
105
+ verifierGates: ["Required debate rounds and synthesis evidence must be present."],
106
+ },
107
+ {
108
+ schemaVersion: 1,
109
+ id: "judge-panel",
110
+ title: "Judge Panel",
111
+ summary: "Collect independent judge outputs, aggregate scores, and select a panel decision with linked evidence.",
112
+ roles: [
113
+ roleSpec("judge", "Judge", ["Score candidates independently and cite evidence."], ["judge score artifact"], ["judge verdict"], 3),
114
+ roleSpec("panel-chair", "Panel Chair", ["Aggregate scores and write a panel decision."], ["panel decision"], ["judge evidence"]),
115
+ ],
116
+ groups: [{ id: "judge-panel", title: "Judge Panel Group", roleIds: ["judge", "panel-chair"] }],
117
+ blackboardTopics: [
118
+ topicSpec("judge-verdicts", "Judge Verdicts", "Independent judge outputs and score evidence."),
119
+ topicSpec("panel-decision", "Panel Decision", "Aggregated verdict and candidate selection rationale."),
120
+ ],
121
+ phases: [
122
+ phaseSpec("judge", "Judge", ["judge"], true, false, ["judge score artifact"], ["artifact-index"]),
123
+ phaseSpec("panel", "Panel", ["panel-chair"], false, true, ["judge evidence", "score records"], ["candidate-synthesis"]),
124
+ ],
125
+ fanoutStrategy: "one membership per independent judge role",
126
+ faninStrategy: "panel decision requires fanin over judge evidence and score records",
127
+ requiredEvidence: ["judge output", "score record", "panel decision", "candidate selection rationale"],
128
+ coordinatorDecisions: ["artifact-index", "candidate-synthesis"],
129
+ candidateExpectations: ["No single judge is authoritative without aggregated fanin and score evidence."],
130
+ verifierGates: ["Panel decision requires multiple judge outputs unless explicitly configured otherwise."],
131
+ },
132
+ ];
133
+ /** Module-level registry (mechanism); OFFICIAL_TOPOLOGIES + registrations
134
+ * are policy. `listTopologyDefinitions()` composes them — registered
135
+ * wins on id clash, last-write-wins. */
136
+ const topologyRegistry = new Map();
137
+ function registerTopology(definition) {
138
+ topologyRegistry.set(definition.id, clone(definition));
139
+ }
140
+ function listTopologyDefinitions() {
141
+ const merged = exports.OFFICIAL_TOPOLOGIES.map((definition) => clone(definition));
142
+ for (const registered of topologyRegistry.values()) {
143
+ const idx = merged.findIndex((d) => d.id === registered.id);
144
+ if (idx >= 0)
145
+ merged[idx] = clone(registered);
146
+ else
147
+ merged.push(clone(registered));
148
+ }
149
+ return merged;
150
+ }
151
+ function getTopologyDefinition(topologyId) {
152
+ const registered = topologyRegistry.get(topologyId);
153
+ if (registered)
154
+ return clone(registered);
155
+ return exports.OFFICIAL_TOPOLOGIES.find((definition) => definition.id === topologyId);
156
+ }
157
+ function validateTopologyDefinition(topologyId) {
158
+ const definition = getTopologyDefinition(topologyId);
159
+ if (!definition)
160
+ return { valid: false, topologyId, issues: [{ code: "unknown-topology", message: `Unknown topology id: ${topologyId}` }] };
161
+ const issues = [];
162
+ if (!definition.roles.length)
163
+ issues.push({ code: "missing-roles", message: "Topology must declare at least one role.", path: "roles" });
164
+ if (!definition.groups.length)
165
+ issues.push({ code: "missing-groups", message: "Topology must declare at least one group.", path: "groups" });
166
+ if (!definition.blackboardTopics.length)
167
+ issues.push({ code: "missing-topics", message: "Topology must declare blackboard topics.", path: "blackboardTopics" });
168
+ if (!definition.requiredEvidence.length)
169
+ issues.push({ code: "missing-evidence", message: "Topology must declare required evidence.", path: "requiredEvidence" });
170
+ const roleIds = new Set(definition.roles.map((role) => role.id));
171
+ for (const phase of definition.phases) {
172
+ for (const roleId of phase.roleIds) {
173
+ if (!roleIds.has(roleId))
174
+ issues.push({ code: "unknown-phase-role", message: `Phase ${phase.id} references unknown role ${roleId}.`, path: `phases.${phase.id}` });
175
+ }
176
+ }
177
+ return { valid: issues.length === 0, topologyId, issues, definition };
178
+ }
179
+ /** Fold the legacy id-keyed mapperCount/judgeCount flags into the uniform
180
+ * roleCounts map. An explicit roleCounts entry always wins; judgeCount
181
+ * floors at 2 (a panel never collapses to one judge via the legacy
182
+ * flag), mapperCount floors at 1. */
183
+ function withLegacyRoleCounts(input) {
184
+ const legacy = {
185
+ mapper: input.mapperCount === undefined ? undefined : Math.max(1, input.mapperCount),
186
+ judge: input.judgeCount === undefined ? undefined : Math.max(2, input.judgeCount),
187
+ };
188
+ const roleCounts = { ...input.roleCounts };
189
+ for (const [roleId, value] of Object.entries(legacy)) {
190
+ if (value !== undefined && roleCounts[roleId] === undefined)
191
+ roleCounts[roleId] = value;
192
+ }
193
+ return Object.keys(roleCounts).length ? { ...input, roleCounts } : input;
194
+ }
195
+ /** Width = `max(1, roleCounts[role.id] ?? role.count ?? 1)`; when width >
196
+ * 1, instances get ids `${role.id}-${index}` and titles `${role.title}
197
+ * ${index}` for index 1..N. Data-driven — no topology-id/role-id
198
+ * branching. */
199
+ function materializedRoles(definition, input) {
200
+ const roles = [];
201
+ for (const role of definition.roles) {
202
+ const roleCount = Math.max(1, input.roleCounts?.[role.id] ?? role.count ?? 1);
203
+ if (roleCount > 1) {
204
+ for (let index = 1; index <= roleCount; index += 1) {
205
+ roles.push({ ...role, id: `${role.id}-${index}`, title: `${role.title} ${index}` });
206
+ }
207
+ }
208
+ else {
209
+ roles.push({ ...role, id: role.id, title: role.title });
210
+ }
211
+ }
212
+ return roles;
213
+ }
214
+ /** Collector roles are excluded from fanout by id suffix
215
+ * (-reducer/-synthesizer/-panel-chair) — NOT by a flag or role position.
216
+ * If the filter empties the list, ALL roles fan out. */
217
+ function fanoutRoleIds(roleIds) {
218
+ const filtered = roleIds.filter((roleId) => !roleId.endsWith("-reducer") && !roleId.endsWith("-synthesizer") && !roleId.endsWith("-panel-chair"));
219
+ return filtered.length ? filtered : roleIds;
220
+ }
221
+ function selectedTaskIds(tasks, taskIds) {
222
+ const ids = taskIds?.length ? taskIds : [tasks.find((task) => task.status === "pending")?.id || tasks[0]?.id].filter(Boolean);
223
+ for (const id of ids) {
224
+ if (!tasks.some((task) => task.id === id))
225
+ throw new Error(`Unknown task id for topology: ${id}`);
226
+ }
227
+ return ids;
228
+ }
229
+ /** Deterministic default topology-run id: `${definition.id}-${hex16}`
230
+ * where `hex16` is the first 16 hex chars of a content-hash over
231
+ * `{definitionId, roleIds sorted, taskIds sorted, runId, sequence}`.
232
+ * `sha256`/`stableStringify` are injected so this file stays free of a
233
+ * direct core/hash.ts import cycle risk (matches the old build's own
234
+ * use of the shared hash module). */
235
+ function topologyRunId(definition, taskIds, runId, sequence, hash) {
236
+ const digest = hash({
237
+ definitionId: definition.id,
238
+ roleIds: [...definition.roles.map((role) => role.id)].sort(),
239
+ taskIds: [...taskIds].sort(),
240
+ runId,
241
+ sequence,
242
+ });
243
+ return `${definition.id}-${digest.replace("sha256:", "").slice(0, 16)}`;
244
+ }
245
+ function nextActionsFor(runId, topologyRunId, fanoutId) {
246
+ return [
247
+ `node scripts/cw.js dispatch ${runId} --multi-agent-fanout ${fanoutId}`,
248
+ `node scripts/cw.js multi-agent fanin ${runId} ${topologyRunId}-fanin --fanout ${fanoutId}`,
249
+ `node scripts/cw.js topology summary ${runId}`,
250
+ ];
251
+ }
252
+ function statusToNodeStatus(status) {
253
+ if (status === "completed" || status === "ready")
254
+ return "completed";
255
+ if (status === "blocked")
256
+ return "blocked";
257
+ if (status === "failed")
258
+ return "failed";
259
+ if (status === "running")
260
+ return "running";
261
+ return "pending";
262
+ }
263
+ function clone(value) {
264
+ return JSON.parse(JSON.stringify(value));
265
+ }
266
+ function uniqueEdges(edges) {
267
+ const seen = new Set();
268
+ return edges.filter((edge) => {
269
+ const key = `${edge.from}->${edge.to}:${edge.label || ""}`;
270
+ if (seen.has(key))
271
+ return false;
272
+ seen.add(key);
273
+ return true;
274
+ });
275
+ }
276
+ function buildTopologyGraphFromRuns(runId, records, runPath) {
277
+ const nodes = [];
278
+ const edges = [];
279
+ for (const record of records) {
280
+ nodes.push({ id: `${runId}:topology:${record.id}`, kind: "topology-run", status: record.status, label: `${record.topologyId}:${record.id}`, path: runPath(record.id) });
281
+ edges.push({ from: `${runId}:run`, to: `${runId}:topology:${record.id}` });
282
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:multi-agent:${record.multiAgentRunId}`, label: "multi-agent" });
283
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:blackboard:${record.blackboardId}`, label: "blackboard" });
284
+ for (const topicId of record.topicIds)
285
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:blackboard:topic:${topicId}`, label: "topic" });
286
+ for (const roleId of record.roleIds)
287
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:multi-agent:role:${roleId}`, label: "role" });
288
+ for (const groupId of record.groupIds)
289
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:multi-agent:group:${groupId}`, label: "group" });
290
+ for (const fanoutId of record.fanoutIds)
291
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:multi-agent:fanout:${fanoutId}`, label: "fanout" });
292
+ for (const faninId of record.faninIds)
293
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:multi-agent:fanin:${faninId}`, label: "fanin" });
294
+ for (const decisionId of record.coordinatorDecisionIds)
295
+ edges.push({ from: `${runId}:topology:${record.id}`, to: `${runId}:blackboard:decision:${decisionId}`, label: "decision" });
296
+ }
297
+ return { nodes, edges: uniqueEdges(edges) };
298
+ }
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ // core/multi-agent/trust-policy.ts — policyForRole/Group/Membership,
3
+ // authorizeMultiAgentAction's PURE decision half.
4
+ //
5
+ // MILESTONE 9. Byte-exact port of the decision logic in the old build's
6
+ // src/multi-agent-trust.ts. Audit-event recording (recordTrustAuditEvent,
7
+ // which appends to disk) is the caller's job — see
8
+ // shell/multi-agent-io.ts's `authorizeMultiAgentAction` wrapper, which
9
+ // calls `evaluateMultiAgentAction` below then records the two audit
10
+ // events exactly like the old build did.
11
+ //
12
+ // Evidence: SPEC/multi-agent.md section D ("Trust policies"), "Trust
13
+ // denial reasons"; plugins/cool-workflow/src/multi-agent-trust.ts
14
+ // (byte-exact source for policyForRole/Group/Membership + evaluatePolicy/
15
+ // missingEvidence).
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.policyForRole = policyForRole;
18
+ exports.policyForGroup = policyForGroup;
19
+ exports.policyForMembership = policyForMembership;
20
+ exports.evaluateMultiAgentAction = evaluateMultiAgentAction;
21
+ exports.resolvePolicy = resolvePolicy;
22
+ exports.sourceForActor = sourceForActor;
23
+ function unique(values) {
24
+ return Array.from(new Set(values.filter(Boolean)));
25
+ }
26
+ /** Chair detection: lowercased `metadata.topologyRoleId` (or title)
27
+ * contains "chair", "reducer", or "synthesizer". Judge detection:
28
+ * contains "judge". Renaming a role changes its authority (v2/PLAN.md
29
+ * byte-compat / rebuild risk 4 — substring match, not a flag). */
30
+ function policyForRole(role) {
31
+ const topologyRole = String(role.metadata?.topologyRoleId || role.title || "").toLowerCase();
32
+ const isChair = topologyRole.includes("chair") || topologyRole.includes("reducer") || topologyRole.includes("synthesizer");
33
+ const isJudge = topologyRole.includes("judge");
34
+ return {
35
+ schemaVersion: 1,
36
+ id: `${role.id}-policy`,
37
+ policyRef: `multiAgent.roles.${role.id}.policy`,
38
+ subjectKind: "role",
39
+ subjectId: role.id,
40
+ allowedBlackboardTopicIds: unique(role.topicIds || ["*"]),
41
+ allowedWriteOperations: unique([
42
+ "message",
43
+ "context",
44
+ "artifact",
45
+ ...(isChair ? ["snapshot", "coordinator-decision"] : []),
46
+ ]),
47
+ allowedCandidateOperations: (isChair ? ["score", "select"] : ["score"]),
48
+ allowedJudgeOperations: unique([
49
+ ...(isJudge ? ["verdict", "rationale"] : []),
50
+ ...(isChair ? ["rationale", "panel-decision"] : []),
51
+ ]),
52
+ sandboxProfileHints: unique(role.sandboxProfileHints || []),
53
+ requiredEvidenceRefs: unique(role.requiredEvidence || []),
54
+ requiredEvidenceFor: {
55
+ "judge.rationale": ["judge rationale evidence"],
56
+ "judge.verdict": ["judge verdict evidence"],
57
+ "judge.panel-decision": ["judge messages", "score evidence", "coordinator decision"],
58
+ "candidate.select": ["score evidence", "judge rationale"],
59
+ },
60
+ deniedOperations: [],
61
+ metadata: { title: role.title, topologyRoleId: role.metadata?.topologyRoleId },
62
+ };
63
+ }
64
+ /** Wide-open group policy: all write/candidate/judge ops allowed, topics
65
+ * from the group or `["*"]`. */
66
+ function policyForGroup(group) {
67
+ return {
68
+ schemaVersion: 1,
69
+ id: `${group.id}-policy`,
70
+ policyRef: `multiAgent.groups.${group.id}.policy`,
71
+ subjectKind: "group",
72
+ subjectId: group.id,
73
+ allowedBlackboardTopicIds: unique(group.topicIds || ["*"]),
74
+ allowedWriteOperations: ["message", "context", "artifact", "snapshot", "coordinator-decision"],
75
+ allowedCandidateOperations: ["register", "score", "select"],
76
+ allowedJudgeOperations: ["verdict", "rationale", "panel-decision"],
77
+ sandboxProfileHints: [],
78
+ requiredEvidenceRefs: [],
79
+ deniedOperations: [],
80
+ metadata: { phase: group.phase },
81
+ };
82
+ }
83
+ /** Copies the role policy (or a minimal message/context/artifact-only
84
+ * policy if no role) and re-subjects it to the membership. */
85
+ function policyForMembership(membership, role) {
86
+ const source = role?.policy || (role ? policyForRole(role) : undefined);
87
+ return {
88
+ ...(source || {
89
+ schemaVersion: 1,
90
+ id: `${membership.id}-policy`,
91
+ policyRef: `multiAgent.memberships.${membership.id}.policy`,
92
+ subjectKind: "membership",
93
+ subjectId: membership.id,
94
+ allowedBlackboardTopicIds: unique(membership.topicIds || ["*"]),
95
+ allowedWriteOperations: ["message", "context", "artifact"],
96
+ allowedCandidateOperations: [],
97
+ allowedJudgeOperations: [],
98
+ sandboxProfileHints: [],
99
+ requiredEvidenceRefs: [],
100
+ deniedOperations: [],
101
+ }),
102
+ id: `${membership.id}-policy`,
103
+ policyRef: `multiAgent.memberships.${membership.id}.policy`,
104
+ subjectKind: "membership",
105
+ subjectId: membership.id,
106
+ };
107
+ }
108
+ /** The pure decision half of `authorizeMultiAgentAction`: resolves the
109
+ * policy (membership -> role -> group, in that priority order) and
110
+ * evaluates denied ops / topic scope / op-class membership / required
111
+ * evidence. Audit recording is NOT done here (shell/multi-agent-io.ts's
112
+ * wrapper does that, injecting the resolved policy + reason into
113
+ * recordTrustAuditEvent calls byte-identical to the old build's). */
114
+ function evaluateMultiAgentAction(policy, operation, topicId, evidenceRefs) {
115
+ const reason = evaluatePolicy(policy, operation, topicId, evidenceRefs);
116
+ const allowed = !reason;
117
+ return {
118
+ allowed,
119
+ decision: allowed ? "allowed" : "denied",
120
+ reason: reason || "allowed by explicit multi-agent policy",
121
+ policyRef: policy?.policyRef,
122
+ policy,
123
+ missingEvidenceRefs: missingEvidence(policy, operation, evidenceRefs),
124
+ };
125
+ }
126
+ /** Resolves the effective policy for an action: membership wins, else
127
+ * role, else group — same priority chain as the old build's
128
+ * `resolvePolicy`. Callers supply lookup functions so this stays pure
129
+ * (no direct WorkflowRun/state coupling beyond what's passed in). */
130
+ function resolvePolicy(input, lookups) {
131
+ const membership = input.membershipId ? lookups.membership?.(input.membershipId) : undefined;
132
+ if (membership?.policy)
133
+ return membership.policy;
134
+ const roleId = input.roleId || membership?.roleId;
135
+ const role = roleId ? lookups.role?.(roleId) : undefined;
136
+ if (role?.policy)
137
+ return role.policy;
138
+ if (role)
139
+ return policyForRole(role);
140
+ const group = input.groupId ? lookups.group?.(input.groupId) : undefined;
141
+ if (group?.policy)
142
+ return group.policy;
143
+ if (group)
144
+ return policyForGroup(group);
145
+ return undefined;
146
+ }
147
+ function evaluatePolicy(policy, operation, topicId, evidenceRefs) {
148
+ if (!policy)
149
+ return "missing role authority or policy";
150
+ const denied = policy.deniedOperations.find((entry) => entry.operation === operation);
151
+ if (denied)
152
+ return denied.reason;
153
+ if (topicId && policy.allowedBlackboardTopicIds.length && !policy.allowedBlackboardTopicIds.includes("*") && !policy.allowedBlackboardTopicIds.includes(topicId)) {
154
+ return `topic ${topicId} is outside policy ${policy.policyRef}`;
155
+ }
156
+ if (operation.startsWith("candidate.")) {
157
+ const op = operation.slice("candidate.".length);
158
+ if (!policy.allowedCandidateOperations.includes(op))
159
+ return `candidate operation ${op} is outside policy ${policy.policyRef}`;
160
+ }
161
+ else if (operation.startsWith("judge.")) {
162
+ const op = operation.slice("judge.".length);
163
+ if (!policy.allowedJudgeOperations.includes(op))
164
+ return `judge operation ${op} is outside policy ${policy.policyRef}`;
165
+ }
166
+ else if (!policy.allowedWriteOperations.includes(operation)) {
167
+ return `blackboard write operation ${operation} is outside policy ${policy.policyRef}`;
168
+ }
169
+ const missing = missingEvidence(policy, operation, evidenceRefs);
170
+ if (missing.length)
171
+ return `operation ${operation} requires evidence refs: ${missing.join(", ")}`;
172
+ return undefined;
173
+ }
174
+ function missingEvidence(policy, operation, evidenceRefs) {
175
+ if (!policy)
176
+ return [];
177
+ const required = unique([...(policy.requiredEvidenceFor?.[operation] || [])]);
178
+ if (!required.length)
179
+ return [];
180
+ if (evidenceRefs.length)
181
+ return [];
182
+ return required;
183
+ }
184
+ /** no actor -> operator-recorded; worker -> host-attested; operator ->
185
+ * operator-recorded; runtime/coordinator/verifier -> runtime-derived;
186
+ * else cw-validated. */
187
+ function sourceForActor(actor) {
188
+ if (!actor)
189
+ return "operator-recorded";
190
+ if (actor.kind === "worker")
191
+ return "host-attested";
192
+ if (actor.kind === "operator")
193
+ return "operator-recorded";
194
+ if (actor.kind === "runtime" || actor.kind === "coordinator" || actor.kind === "verifier")
195
+ return "runtime-derived";
196
+ return "cw-validated";
197
+ }