cool-workflow 0.1.78

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 (193) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/mcp.json +10 -0
  3. package/.codex-plugin/plugin.json +38 -0
  4. package/.mcp.json +10 -0
  5. package/LICENSE +24 -0
  6. package/README.md +638 -0
  7. package/apps/architecture-review/app.json +51 -0
  8. package/apps/architecture-review/workflow.js +116 -0
  9. package/apps/end-to-end-golden-path/app.json +30 -0
  10. package/apps/end-to-end-golden-path/workflow.js +33 -0
  11. package/apps/pr-review-fix-ci/app.json +59 -0
  12. package/apps/pr-review-fix-ci/workflow.js +90 -0
  13. package/apps/release-cut/app.json +54 -0
  14. package/apps/release-cut/workflow.js +82 -0
  15. package/apps/research-synthesis/app.json +50 -0
  16. package/apps/research-synthesis/workflow.js +76 -0
  17. package/apps/workflow-app-framework-demo/app.json +29 -0
  18. package/apps/workflow-app-framework-demo/workflow.js +44 -0
  19. package/dist/agent-config.js +223 -0
  20. package/dist/candidate-scoring.js +715 -0
  21. package/dist/capability-core.js +630 -0
  22. package/dist/capability-dispatcher.js +86 -0
  23. package/dist/capability-registry.js +523 -0
  24. package/dist/cli.js +1276 -0
  25. package/dist/collaboration.js +727 -0
  26. package/dist/commit.js +570 -0
  27. package/dist/contract-migration.js +234 -0
  28. package/dist/coordinator.js +1163 -0
  29. package/dist/daemon.js +44 -0
  30. package/dist/dispatch.js +201 -0
  31. package/dist/drive.js +503 -0
  32. package/dist/error-feedback.js +415 -0
  33. package/dist/evidence-grounding.js +179 -0
  34. package/dist/evidence-reasoning.js +733 -0
  35. package/dist/execution-backend.js +1279 -0
  36. package/dist/harness.js +61 -0
  37. package/dist/mcp-server.js +1615 -0
  38. package/dist/multi-agent-eval.js +857 -0
  39. package/dist/multi-agent-host.js +764 -0
  40. package/dist/multi-agent-operator-ux.js +537 -0
  41. package/dist/multi-agent-trust.js +366 -0
  42. package/dist/multi-agent.js +1173 -0
  43. package/dist/node-snapshot.js +270 -0
  44. package/dist/observability.js +922 -0
  45. package/dist/operator-ux.js +971 -0
  46. package/dist/orchestrator/audit-operations.js +182 -0
  47. package/dist/orchestrator/candidate-operations.js +117 -0
  48. package/dist/orchestrator/cli-options.js +288 -0
  49. package/dist/orchestrator/collaboration-operations.js +86 -0
  50. package/dist/orchestrator/feedback-operations.js +81 -0
  51. package/dist/orchestrator/host-operations.js +78 -0
  52. package/dist/orchestrator/lifecycle-operations.js +462 -0
  53. package/dist/orchestrator/migration-operations.js +44 -0
  54. package/dist/orchestrator/multi-agent-operations.js +362 -0
  55. package/dist/orchestrator/report.js +369 -0
  56. package/dist/orchestrator/topology-operations.js +84 -0
  57. package/dist/orchestrator.js +874 -0
  58. package/dist/pipeline-contract.js +92 -0
  59. package/dist/pipeline-runner.js +285 -0
  60. package/dist/reclamation.js +882 -0
  61. package/dist/result-normalize.js +194 -0
  62. package/dist/run-export.js +64 -0
  63. package/dist/run-registry.js +1347 -0
  64. package/dist/run-state-schema.js +67 -0
  65. package/dist/sandbox-profile.js +471 -0
  66. package/dist/scheduler.js +266 -0
  67. package/dist/scheduling.js +184 -0
  68. package/dist/schema-validate.js +98 -0
  69. package/dist/state-explosion.js +1213 -0
  70. package/dist/state-migrations.js +463 -0
  71. package/dist/state-node.js +301 -0
  72. package/dist/state.js +308 -0
  73. package/dist/telemetry-attestation.js +156 -0
  74. package/dist/telemetry-ledger.js +145 -0
  75. package/dist/topology.js +527 -0
  76. package/dist/triggers.js +159 -0
  77. package/dist/trust-audit.js +475 -0
  78. package/dist/types/blackboard.js +2 -0
  79. package/dist/types/boundary.js +29 -0
  80. package/dist/types/candidate.js +2 -0
  81. package/dist/types/collaboration.js +2 -0
  82. package/dist/types/core.js +2 -0
  83. package/dist/types/drive.js +10 -0
  84. package/dist/types/error-feedback.js +2 -0
  85. package/dist/types/evidence-reasoning.js +2 -0
  86. package/dist/types/execution-backend.js +2 -0
  87. package/dist/types/multi-agent.js +2 -0
  88. package/dist/types/observability.js +2 -0
  89. package/dist/types/pipeline.js +2 -0
  90. package/dist/types/reclamation.js +8 -0
  91. package/dist/types/result.js +2 -0
  92. package/dist/types/run-registry.js +2 -0
  93. package/dist/types/run.js +2 -0
  94. package/dist/types/sandbox.js +2 -0
  95. package/dist/types/schedule.js +2 -0
  96. package/dist/types/state-node.js +2 -0
  97. package/dist/types/topology.js +2 -0
  98. package/dist/types/trust.js +2 -0
  99. package/dist/types/workbench.js +2 -0
  100. package/dist/types/worker.js +2 -0
  101. package/dist/types/workflow-app.js +2 -0
  102. package/dist/types.js +43 -0
  103. package/dist/verifier-registry.js +46 -0
  104. package/dist/verifier.js +78 -0
  105. package/dist/version.js +8 -0
  106. package/dist/workbench-host.js +172 -0
  107. package/dist/workbench.js +190 -0
  108. package/dist/worker-isolation.js +1028 -0
  109. package/dist/workflow-api.js +98 -0
  110. package/dist/workflow-app-framework.js +626 -0
  111. package/docs/agent-delegation-drive.7.md +190 -0
  112. package/docs/agent-framework.md +176 -0
  113. package/docs/candidate-scoring.7.md +106 -0
  114. package/docs/canonical-workflow-apps.7.md +137 -0
  115. package/docs/capability-topology-registry.7.md +168 -0
  116. package/docs/cli-mcp-parity.7.md +373 -0
  117. package/docs/contract-migration-tooling.7.md +123 -0
  118. package/docs/control-plane-scheduling.7.md +110 -0
  119. package/docs/coordinator-blackboard.7.md +183 -0
  120. package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
  121. package/docs/dogfood-one-real-repo.7.md +168 -0
  122. package/docs/durable-state-and-locking.7.md +107 -0
  123. package/docs/end-to-end-golden-path.7.md +117 -0
  124. package/docs/error-feedback.7.md +153 -0
  125. package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
  126. package/docs/execution-backends.7.md +300 -0
  127. package/docs/getting-started.md +99 -0
  128. package/docs/index.md +41 -0
  129. package/docs/mcp-app-surface.7.md +235 -0
  130. package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
  131. package/docs/multi-agent-eval-replay-harness.7.md +302 -0
  132. package/docs/multi-agent-operator-ux.7.md +314 -0
  133. package/docs/multi-agent-runtime-core.7.md +231 -0
  134. package/docs/multi-agent-topologies.7.md +103 -0
  135. package/docs/multi-agent-trust-policy-audit.7.md +154 -0
  136. package/docs/node-snapshot-diff-replay.7.md +135 -0
  137. package/docs/observability-cost-accounting.7.md +194 -0
  138. package/docs/operator-ux.7.md +180 -0
  139. package/docs/pipeline-runner.7.md +136 -0
  140. package/docs/project-index.md +261 -0
  141. package/docs/real-execution-backends.7.md +142 -0
  142. package/docs/release-and-migration.7.md +280 -0
  143. package/docs/release-tooling.7.md +159 -0
  144. package/docs/routines.md +48 -0
  145. package/docs/run-registry-control-plane.7.md +312 -0
  146. package/docs/run-retention-reclamation.7.md +191 -0
  147. package/docs/sandbox-profiles.7.md +137 -0
  148. package/docs/scheduled-tasks.md +80 -0
  149. package/docs/security-trust-hardening.7.md +117 -0
  150. package/docs/state-explosion-management.7.md +264 -0
  151. package/docs/state-node.7.md +96 -0
  152. package/docs/team-collaboration.7.md +207 -0
  153. package/docs/unix-principles.md +192 -0
  154. package/docs/verifier-gated-commit.7.md +140 -0
  155. package/docs/web-desktop-workbench.7.md +215 -0
  156. package/docs/worker-isolation.7.md +167 -0
  157. package/docs/workflow-app-framework.7.md +274 -0
  158. package/manifest/README.md +43 -0
  159. package/manifest/plugin.manifest.json +316 -0
  160. package/manifest/pricing.policy.json +14 -0
  161. package/package.json +79 -0
  162. package/scripts/agents/claude-p-agent.js +104 -0
  163. package/scripts/agents/claude-p-agent.sh +9 -0
  164. package/scripts/agents/cw-attest-keygen.js +55 -0
  165. package/scripts/agents/cw-attest-wrap.js +143 -0
  166. package/scripts/block-unapproved-tag.sh +39 -0
  167. package/scripts/bump-version.js +249 -0
  168. package/scripts/canonical-apps.js +171 -0
  169. package/scripts/cw.js +4 -0
  170. package/scripts/dist-drift-check.js +79 -0
  171. package/scripts/dogfood-architecture-review.js +237 -0
  172. package/scripts/dogfood-release.js +624 -0
  173. package/scripts/forward-ref-docs.js +73 -0
  174. package/scripts/gen-manifests.js +232 -0
  175. package/scripts/golden-path.js +300 -0
  176. package/scripts/mcp-server.js +4 -0
  177. package/scripts/new-feature.js +121 -0
  178. package/scripts/parity-check.js +213 -0
  179. package/scripts/release-check.js +118 -0
  180. package/scripts/release-flow.js +272 -0
  181. package/scripts/release-gate.sh +85 -0
  182. package/scripts/sync-project-index.js +387 -0
  183. package/scripts/validate-run-state-schema.js +126 -0
  184. package/scripts/verify-container-selfref.js +64 -0
  185. package/scripts/version-sync-check.js +237 -0
  186. package/skills/cool-workflow/SKILL.md +162 -0
  187. package/skills/cool-workflow/references/commands.md +282 -0
  188. package/tsconfig.json +16 -0
  189. package/ui/workbench/app.css +76 -0
  190. package/ui/workbench/app.js +159 -0
  191. package/ui/workbench/index.html +32 -0
  192. package/workflows/architecture-review.workflow.js +84 -0
  193. package/workflows/research-synthesis.workflow.js +47 -0
@@ -0,0 +1,727 @@
1
+ "use strict";
2
+ // Team Collaboration core (v0.1.32) — the human-decision layer.
3
+ //
4
+ // BSD discipline applied here:
5
+ // - IDENTITY IS ATTESTED, NOT AUTHENTICATED. `normalizeActor` records WHO acted
6
+ // from host/operator provenance; an absent identity becomes the explicit
7
+ // `unattributed` actor, never a fabricated one. CW is not an auth server.
8
+ // - REVIEW GATES STACK ON THE VERIFIER GATE. `reviewGateErrors` returns extra
9
+ // StateNodeErrors for `resolveCommitGate`/`selectCandidate` to APPEND — policy
10
+ // layered on top of the verifier mechanism, never replacing it.
11
+ // - APPEND-ONLY LOG; NEVER MUTATE THE PAST. record* only push; a correction is a
12
+ // NEW record carrying `supersedes`. The approved artifact is never edited; the
13
+ // review link is provenance, not a field overwrite.
14
+ // - FAIL CLOSED ON AUTHORITY AND QUORUM. `deriveReviewState` counts only
15
+ // distinct, attested, authorized, non-self approvals; anything short is
16
+ // pending/blocked/rejected/unattributed — never auto-passed.
17
+ // - POLICY AS DATA. The ReviewGatePolicy lives on the run (or is injected),
18
+ // out of the kernel; default (absent / requiredApprovals 0) keeps pre-v0.1.32
19
+ // behavior unchanged.
20
+ // - COLLABORATION IS STATE, NOT CHAT. Every record attaches to a durable target
21
+ // and is derived deterministically — no hidden dashboard.
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.UNATTRIBUTED_ACTOR = exports.COLLABORATION_SCHEMA_VERSION = void 0;
24
+ exports.ensureCollaborationState = ensureCollaborationState;
25
+ exports.normalizeActor = normalizeActor;
26
+ exports.recordApproval = recordApproval;
27
+ exports.recordComment = recordComment;
28
+ exports.recordHandoff = recordHandoff;
29
+ exports.setReviewPolicy = setReviewPolicy;
30
+ exports.resolveReviewPolicy = resolveReviewPolicy;
31
+ exports.deriveReviewState = deriveReviewState;
32
+ exports.reviewGateErrors = reviewGateErrors;
33
+ exports.commitReviewProvenance = commitReviewProvenance;
34
+ exports.selfActorIdsForCandidate = selfActorIdsForCandidate;
35
+ exports.buildReviewStatusReport = buildReviewStatusReport;
36
+ exports.listComments = listComments;
37
+ exports.deriveOwner = deriveOwner;
38
+ exports.formatReviewStatus = formatReviewStatus;
39
+ exports.formatCommentList = formatCommentList;
40
+ const trust_audit_1 = require("./trust-audit");
41
+ const state_1 = require("./state");
42
+ exports.COLLABORATION_SCHEMA_VERSION = 1;
43
+ /** The single, honest stand-in for an absent identity. */
44
+ exports.UNATTRIBUTED_ACTOR = {
45
+ kind: "unattributed",
46
+ id: "unattributed",
47
+ attestation: "unattributed",
48
+ attested: false,
49
+ source: "runtime-derived"
50
+ };
51
+ // ---------------------------------------------------------------------------
52
+ // State + actor normalization
53
+ // ---------------------------------------------------------------------------
54
+ function ensureCollaborationState(run) {
55
+ if (!run.collaboration) {
56
+ run.collaboration = { schemaVersion: exports.COLLABORATION_SCHEMA_VERSION, approvals: [], comments: [], handoffs: [] };
57
+ }
58
+ const state = run.collaboration;
59
+ if (!Array.isArray(state.approvals))
60
+ state.approvals = [];
61
+ if (!Array.isArray(state.comments))
62
+ state.comments = [];
63
+ if (!Array.isArray(state.handoffs))
64
+ state.handoffs = [];
65
+ return state;
66
+ }
67
+ const ACTOR_KINDS = ["operator", "worker", "role", "membership", "group", "host", "service", "unattributed"];
68
+ /** Build a host-attested (never authenticated) actor. Absent id => unattributed. */
69
+ function normalizeActor(input) {
70
+ const id = trimmed(input?.actor);
71
+ if (!id)
72
+ return { ...exports.UNATTRIBUTED_ACTOR };
73
+ const roleId = trimmed(input?.roleId) || trimmed(input?.role);
74
+ const kind = normalizeActorKind(input?.actorKind, roleId);
75
+ const attestation = input?.attestation
76
+ ? input.attestation
77
+ : input?.attested
78
+ ? "host-attested"
79
+ : "operator-recorded";
80
+ const attested = attestation === "host-attested";
81
+ return {
82
+ kind,
83
+ id,
84
+ displayName: trimmed(input?.displayName) || undefined,
85
+ attestation,
86
+ attested,
87
+ roleId: roleId || undefined,
88
+ source: sourceForAttestation(attestation)
89
+ };
90
+ }
91
+ function normalizeActorKind(raw, roleId) {
92
+ const value = trimmed(raw);
93
+ if (value && ACTOR_KINDS.includes(value))
94
+ return value;
95
+ if (roleId)
96
+ return "role";
97
+ return "operator";
98
+ }
99
+ function sourceForAttestation(attestation) {
100
+ if (attestation === "host-attested")
101
+ return "host-attested";
102
+ if (attestation === "operator-recorded")
103
+ return "operator-recorded";
104
+ return "runtime-derived";
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Append-only record writers
108
+ // ---------------------------------------------------------------------------
109
+ function recordApproval(run, input, options = {}) {
110
+ const state = ensureCollaborationState(run);
111
+ const actor = normalizeActor(input);
112
+ const target = normalizeTarget(input.target);
113
+ const decision = input.decision === "reject" ? "reject" : "approve";
114
+ const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
115
+ kind: decision === "approve" ? "collaboration.approval" : "collaboration.rejection",
116
+ decision: decision === "approve" ? "accepted" : "rejected",
117
+ source: actor.source,
118
+ actor: actor.id,
119
+ ...auditTargetFields(target),
120
+ agentRoleId: actor.roleId,
121
+ metadata: compact({
122
+ decision,
123
+ rationale: input.rationale,
124
+ roleId: actor.roleId,
125
+ attestation: actor.attestation,
126
+ targetKind: target.kind,
127
+ supersedes: input.supersedes
128
+ })
129
+ });
130
+ const record = compact({
131
+ schemaVersion: exports.COLLABORATION_SCHEMA_VERSION,
132
+ id: createCollabId(run, decision === "approve" ? "approval" : "rejection", state.approvals.length),
133
+ runId: run.id,
134
+ createdAt: new Date().toISOString(),
135
+ actor,
136
+ decision,
137
+ target,
138
+ rationale: trimmed(input.rationale) || undefined,
139
+ roleId: actor.roleId,
140
+ supersedes: trimmed(input.supersedes) || undefined,
141
+ auditEventIds: [audit.id],
142
+ metadata: undefined
143
+ });
144
+ state.approvals.push(record);
145
+ persist(run, options);
146
+ return record;
147
+ }
148
+ function recordComment(run, input, options = {}) {
149
+ const state = ensureCollaborationState(run);
150
+ const actor = normalizeActor(input);
151
+ const target = normalizeTarget(input.target);
152
+ const body = trimmed(input.body);
153
+ if (!body)
154
+ throw new Error("Comment body is required");
155
+ const threadId = trimmed(input.threadId) || `${target.kind}:${target.id}`;
156
+ const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
157
+ kind: "collaboration.comment",
158
+ decision: "recorded",
159
+ source: actor.source,
160
+ actor: actor.id,
161
+ ...auditTargetFields(target),
162
+ agentRoleId: actor.roleId,
163
+ metadata: compact({ threadId, parentId: input.parentId, targetKind: target.kind })
164
+ });
165
+ const record = compact({
166
+ schemaVersion: exports.COLLABORATION_SCHEMA_VERSION,
167
+ id: createCollabId(run, "comment", state.comments.length),
168
+ runId: run.id,
169
+ createdAt: new Date().toISOString(),
170
+ actor,
171
+ target,
172
+ body,
173
+ threadId,
174
+ parentId: trimmed(input.parentId) || undefined,
175
+ auditEventIds: [audit.id]
176
+ });
177
+ state.comments.push(record);
178
+ persist(run, options);
179
+ return record;
180
+ }
181
+ function recordHandoff(run, input, options = {}) {
182
+ const state = ensureCollaborationState(run);
183
+ const recorder = normalizeActor(input);
184
+ const fromActor = input.fromActor
185
+ ? normalizeActor({ actor: input.fromActor, actorKind: input.fromActorKind, role: input.fromRole, attested: input.attested })
186
+ : recorder;
187
+ const toActor = normalizeActor({
188
+ actor: input.toActor,
189
+ actorKind: input.toActorKind,
190
+ role: input.toRole,
191
+ displayName: input.toDisplayName,
192
+ attested: input.toAttested
193
+ });
194
+ if (toActor.kind === "unattributed")
195
+ throw new Error("Handoff requires a to-actor (--to)");
196
+ const target = normalizeTarget(input.target);
197
+ const reason = trimmed(input.reason) || "handoff";
198
+ const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
199
+ kind: "collaboration.handoff",
200
+ decision: "recorded",
201
+ source: recorder.source,
202
+ actor: recorder.id,
203
+ ...auditTargetFields(target),
204
+ metadata: compact({ from: fromActor.id, to: toActor.id, reason, targetKind: target.kind })
205
+ });
206
+ const record = compact({
207
+ schemaVersion: exports.COLLABORATION_SCHEMA_VERSION,
208
+ id: createCollabId(run, "handoff", state.handoffs.length),
209
+ runId: run.id,
210
+ createdAt: new Date().toISOString(),
211
+ actor: recorder,
212
+ fromActor,
213
+ toActor,
214
+ target,
215
+ reason,
216
+ auditEventIds: [audit.id]
217
+ });
218
+ state.handoffs.push(record);
219
+ persist(run, options);
220
+ return record;
221
+ }
222
+ function setReviewPolicy(run, input, options = {}) {
223
+ const state = ensureCollaborationState(run);
224
+ const policy = {
225
+ schemaVersion: exports.COLLABORATION_SCHEMA_VERSION,
226
+ id: state.policy?.id || createCollabId(run, "policy", 0),
227
+ requiredApprovals: Math.max(0, Math.floor(toNumber(input.requiredApprovals, state.policy?.requiredApprovals ?? 0))),
228
+ authorizedRoles: toStringList(input.authorizedRoles, state.policy?.authorizedRoles ?? ["*"]),
229
+ allowSelfApproval: input.allowSelfApproval ?? state.policy?.allowSelfApproval ?? false,
230
+ requireAttestedActor: input.requireAttestedActor ?? state.policy?.requireAttestedActor ?? false,
231
+ appliesTo: toTargetKindList(input.appliesTo, state.policy?.appliesTo ?? ["commit"]),
232
+ updatedAt: new Date().toISOString()
233
+ };
234
+ state.policy = policy;
235
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
236
+ kind: "collaboration.review-policy",
237
+ decision: "recorded",
238
+ source: "operator-recorded",
239
+ metadata: compact({
240
+ policyId: policy.id,
241
+ requiredApprovals: policy.requiredApprovals,
242
+ authorizedRoles: policy.authorizedRoles,
243
+ allowSelfApproval: policy.allowSelfApproval,
244
+ requireAttestedActor: policy.requireAttestedActor,
245
+ appliesTo: policy.appliesTo
246
+ })
247
+ });
248
+ persist(run, options);
249
+ return policy;
250
+ }
251
+ // ---------------------------------------------------------------------------
252
+ // Deterministic review-state derivation (the fail-closed heart)
253
+ // ---------------------------------------------------------------------------
254
+ function resolveReviewPolicy(run, policy) {
255
+ return policy || run.collaboration?.policy || undefined;
256
+ }
257
+ /** Pure projection: derive a target's review state from append-only records +
258
+ * policy. Deterministic over a fixed run snapshot (no wall-clock). */
259
+ function deriveReviewState(run, target, options = {}) {
260
+ const normalized = normalizeTarget(target);
261
+ const policy = resolveReviewPolicy(run, options.policy);
262
+ const related = (options.relatedTargets && options.relatedTargets.length ? options.relatedTargets : [normalized]).map(normalizeTarget);
263
+ const selfIds = new Set((options.selfActorIds || []).filter(Boolean));
264
+ const approvals = (run.collaboration?.approvals || []).filter((record) => matchesAnyTarget(record.target, related));
265
+ // git-style supersession: a record named by any `supersedes` is retired.
266
+ const supersededIds = new Set(approvals.map((record) => record.supersedes).filter((id) => Boolean(id)));
267
+ const gated = Boolean(policy && policy.requiredApprovals > 0 && policy.appliesTo.includes(normalized.kind));
268
+ const required = gated ? policy.requiredApprovals : 0;
269
+ const counted = [];
270
+ const countedActorIds = new Set();
271
+ const rejections = [];
272
+ const disqualified = [];
273
+ for (const record of [...approvals].sort(compareByCreated)) {
274
+ if (supersededIds.has(record.id)) {
275
+ disqualified.push({ approvalId: record.id, actorId: record.actor.id, reason: "superseded" });
276
+ continue;
277
+ }
278
+ const reason = disqualify(record, policy, selfIds);
279
+ if (record.decision === "reject") {
280
+ // A reject from an authorized, attested actor is a blocking veto.
281
+ if (!reason || reason === "self-approval")
282
+ rejections.push(record);
283
+ else
284
+ disqualified.push({ approvalId: record.id, actorId: record.actor.id, reason });
285
+ continue;
286
+ }
287
+ if (reason) {
288
+ disqualified.push({ approvalId: record.id, actorId: record.actor.id, reason });
289
+ continue;
290
+ }
291
+ if (!countedActorIds.has(record.actor.id)) {
292
+ countedActorIds.add(record.actor.id);
293
+ counted.push(record);
294
+ }
295
+ }
296
+ const recordedApprovals = countedActorIds.size;
297
+ const status = deriveStatus(gated, required, recordedApprovals, rejections.length, disqualified);
298
+ const approvers = [...countedActorIds].sort();
299
+ const missing = buildMissing(status, gated, required, recordedApprovals, policy, rejections, disqualified);
300
+ return {
301
+ schemaVersion: exports.COLLABORATION_SCHEMA_VERSION,
302
+ runId: run.id,
303
+ target: normalized,
304
+ status,
305
+ gated,
306
+ policyId: policy?.id,
307
+ requiredApprovals: required,
308
+ recordedApprovals,
309
+ approvers,
310
+ approvals: counted,
311
+ rejections,
312
+ disqualified,
313
+ missing
314
+ };
315
+ }
316
+ function disqualify(record, policy, selfIds) {
317
+ const actor = record.actor;
318
+ if (actor.kind === "unattributed")
319
+ return "unattributed";
320
+ if (policy?.requireAttestedActor && !actor.attested)
321
+ return "unattributed";
322
+ if (policy && !roleAuthorized(actor.roleId, policy.authorizedRoles))
323
+ return "unauthorized-role";
324
+ if (policy && !policy.allowSelfApproval && selfIds.has(actor.id))
325
+ return "self-approval";
326
+ return undefined;
327
+ }
328
+ function roleAuthorized(roleId, authorizedRoles) {
329
+ if (authorizedRoles.includes("*"))
330
+ return true;
331
+ if (!roleId)
332
+ return false;
333
+ return authorizedRoles.includes(roleId);
334
+ }
335
+ function deriveStatus(gated, required, recorded, rejectionCount, disqualified) {
336
+ if (!gated)
337
+ return "approved";
338
+ if (rejectionCount > 0)
339
+ return "rejected";
340
+ if (recorded >= required)
341
+ return "approved";
342
+ if (recorded === 0 && disqualified.length > 0) {
343
+ const blocking = disqualified.filter((entry) => entry.reason !== "superseded");
344
+ if (blocking.length > 0 && blocking.every((entry) => entry.reason === "unattributed"))
345
+ return "unattributed";
346
+ if (blocking.length > 0)
347
+ return "blocked";
348
+ }
349
+ return "pending";
350
+ }
351
+ function buildMissing(status, gated, required, recorded, policy, rejections, disqualified) {
352
+ if (!gated || status === "approved")
353
+ return [];
354
+ const missing = [];
355
+ if (status === "rejected") {
356
+ for (const record of rejections)
357
+ missing.push(`rejected by ${record.actor.id}${record.rationale ? ` (${record.rationale})` : ""}`);
358
+ return missing;
359
+ }
360
+ const roles = policy?.authorizedRoles?.length ? policy.authorizedRoles.join(", ") : "*";
361
+ missing.push(`${required - recorded} more approval(s) from authorized role(s) [${roles}] required (have ${recorded}/${required})`);
362
+ const selfCount = disqualified.filter((entry) => entry.reason === "self-approval").length;
363
+ const unattributedCount = disqualified.filter((entry) => entry.reason === "unattributed").length;
364
+ const unauthorizedCount = disqualified.filter((entry) => entry.reason === "unauthorized-role").length;
365
+ if (selfCount)
366
+ missing.push(`${selfCount} self-approval(s) ignored (policy forbids self-approval)`);
367
+ if (unattributedCount)
368
+ missing.push(`${unattributedCount} unattributed approval(s) ignored`);
369
+ if (unauthorizedCount)
370
+ missing.push(`${unauthorizedCount} approval(s) from unauthorized role(s) ignored`);
371
+ return missing;
372
+ }
373
+ /** The StateNodeErrors a review gate contributes. Empty when the target is not
374
+ * gated or the gate is satisfied — so it can only ADD constraints, never remove
375
+ * the verifier's. */
376
+ function reviewGateErrors(run, input) {
377
+ const policy = resolveReviewPolicy(run, input.policy);
378
+ if (!policy || policy.requiredApprovals <= 0 || !policy.appliesTo.includes(input.targetKind))
379
+ return [];
380
+ const target = gateTarget(input);
381
+ const related = gateRelatedTargets(input);
382
+ const state = deriveReviewState(run, target, { policy, relatedTargets: related, selfActorIds: input.selfActorIds });
383
+ if (state.status === "approved")
384
+ return [];
385
+ return [
386
+ {
387
+ code: "review-gate-missing-approvals",
388
+ message: `Review gate blocked (${state.status}): ${state.missing.join("; ")}`,
389
+ at: new Date().toISOString(),
390
+ retryable: false,
391
+ details: {
392
+ reviewStatus: state.status,
393
+ requiredApprovals: state.requiredApprovals,
394
+ recordedApprovals: state.recordedApprovals,
395
+ approvers: state.approvers,
396
+ missing: state.missing,
397
+ policyId: state.policyId,
398
+ targetKind: input.targetKind
399
+ }
400
+ }
401
+ ];
402
+ }
403
+ /** When a gated commit passes, stamp who approved the very artifact that shipped. */
404
+ function commitReviewProvenance(run, input) {
405
+ const policy = resolveReviewPolicy(run, input.policy);
406
+ if (!policy || policy.requiredApprovals <= 0 || !policy.appliesTo.includes(input.targetKind))
407
+ return undefined;
408
+ const target = gateTarget(input);
409
+ const state = deriveReviewState(run, target, {
410
+ policy,
411
+ relatedTargets: gateRelatedTargets(input),
412
+ selfActorIds: input.selfActorIds
413
+ });
414
+ if (state.status !== "approved")
415
+ return undefined;
416
+ return {
417
+ policyId: policy.id,
418
+ requiredApprovals: state.requiredApprovals,
419
+ recordedApprovals: state.recordedApprovals,
420
+ approvers: state.approvers,
421
+ approvalIds: state.approvals.map((record) => record.id).sort(),
422
+ target
423
+ };
424
+ }
425
+ function gateTarget(input) {
426
+ if (input.targetKind === "commit")
427
+ return { kind: "commit", id: input.commitId || "(pending)" };
428
+ if (input.targetKind === "selection")
429
+ return { kind: "selection", id: input.selectionId || "(pending)" };
430
+ if (input.targetKind === "candidate")
431
+ return { kind: "candidate", id: input.candidateId || "(pending)" };
432
+ if (input.targetKind === "node")
433
+ return { kind: "node", id: input.nodeId || "(pending)" };
434
+ if (input.targetKind === "task")
435
+ return { kind: "task", id: input.taskId || "(pending)" };
436
+ return { kind: "run", id: run_id_or_pending(input) };
437
+ }
438
+ function run_id_or_pending(input) {
439
+ return input.commitId || input.candidateId || input.selectionId || "(pending)";
440
+ }
441
+ /** A commit/selection counts approvals on ITSELF and its underlying
442
+ * candidate/selection — you approve the candidate, the commit honors it. */
443
+ function gateRelatedTargets(input) {
444
+ const related = [];
445
+ if (input.commitId)
446
+ related.push({ kind: "commit", id: input.commitId });
447
+ if (input.selectionId)
448
+ related.push({ kind: "selection", id: input.selectionId });
449
+ if (input.candidateId)
450
+ related.push({ kind: "candidate", id: input.candidateId });
451
+ if (input.nodeId)
452
+ related.push({ kind: "node", id: input.nodeId });
453
+ if (input.taskId)
454
+ related.push({ kind: "task", id: input.taskId });
455
+ if (!related.length)
456
+ related.push(gateTarget(input));
457
+ return related;
458
+ }
459
+ /** Self ids for a candidate/selection target: its producing worker + selector. */
460
+ function selfActorIdsForCandidate(run, candidateId, selectionId) {
461
+ const ids = new Set();
462
+ const candidate = candidateId ? (run.candidates || []).find((entry) => entry.id === candidateId) : undefined;
463
+ if (candidate?.workerId)
464
+ ids.add(candidate.workerId);
465
+ const selections = (run.candidateSelections || []).filter((selection) => (selectionId && selection.id === selectionId) || (candidateId && selection.candidateId === candidateId));
466
+ for (const selection of selections)
467
+ if (selection.selectedBy)
468
+ ids.add(selection.selectedBy);
469
+ return [...ids];
470
+ }
471
+ function buildReviewStatusReport(run, options) {
472
+ ensureCollaborationState(run);
473
+ const policy = run.collaboration?.policy;
474
+ const approvals = run.collaboration?.approvals || [];
475
+ const comments = run.collaboration?.comments || [];
476
+ const handoffs = run.collaboration?.handoffs || [];
477
+ const targets = options.target ? [normalizeTarget(options.target)] : distinctTargets(run);
478
+ const reviewStates = targets.map((target) => deriveReviewState(run, target, {
479
+ policy,
480
+ relatedTargets: relatedTargetsFor(run, target),
481
+ selfActorIds: selfActorIdsForTarget(run, target)
482
+ }));
483
+ const owner = deriveOwner(run);
484
+ const timeline = buildTimeline(run);
485
+ return {
486
+ schemaVersion: exports.COLLABORATION_SCHEMA_VERSION,
487
+ surface: "collaboration",
488
+ runId: run.id,
489
+ generatedAt: options.now,
490
+ policy,
491
+ owner,
492
+ targets: reviewStates,
493
+ counts: {
494
+ approvals: approvals.filter((record) => record.decision === "approve").length,
495
+ rejections: approvals.filter((record) => record.decision === "reject").length,
496
+ comments: comments.length,
497
+ handoffs: handoffs.length
498
+ },
499
+ timeline,
500
+ nextActions: buildNextActions(run, reviewStates, policy)
501
+ };
502
+ }
503
+ function listComments(run, target) {
504
+ const comments = run.collaboration?.comments || [];
505
+ const filtered = target ? comments.filter((record) => sameTarget(record.target, normalizeTarget(target))) : comments;
506
+ return [...filtered].sort(compareByCreated);
507
+ }
508
+ function deriveOwner(run) {
509
+ const handoffs = [...(run.collaboration?.handoffs || [])]
510
+ .filter((record) => record.target.kind === "run" || record.target.kind === "task")
511
+ .sort(compareByCreated);
512
+ return handoffs.length ? handoffs[handoffs.length - 1].toActor : undefined;
513
+ }
514
+ function buildTimeline(run) {
515
+ const entries = [];
516
+ for (const record of run.collaboration?.approvals || []) {
517
+ entries.push({
518
+ kind: "approval",
519
+ id: record.id,
520
+ createdAt: record.createdAt,
521
+ actor: record.actor,
522
+ target: record.target,
523
+ summary: `${record.decision === "approve" ? "approved" : "rejected"} ${record.target.kind} ${record.target.id}${record.rationale ? ` — ${record.rationale}` : ""}`
524
+ });
525
+ }
526
+ for (const record of run.collaboration?.comments || []) {
527
+ entries.push({
528
+ kind: "comment",
529
+ id: record.id,
530
+ createdAt: record.createdAt,
531
+ actor: record.actor,
532
+ target: record.target,
533
+ summary: `commented on ${record.target.kind} ${record.target.id}: ${truncate(record.body, 80)}`
534
+ });
535
+ }
536
+ for (const record of run.collaboration?.handoffs || []) {
537
+ entries.push({
538
+ kind: "handoff",
539
+ id: record.id,
540
+ createdAt: record.createdAt,
541
+ actor: record.actor,
542
+ target: record.target,
543
+ summary: `handed off ${record.target.kind} ${record.target.id}: ${record.fromActor.id} → ${record.toActor.id} (${record.reason})`
544
+ });
545
+ }
546
+ if (run.collaboration?.policy) {
547
+ const policy = run.collaboration.policy;
548
+ entries.push({
549
+ kind: "policy",
550
+ id: policy.id,
551
+ createdAt: policy.updatedAt,
552
+ actor: { ...exports.UNATTRIBUTED_ACTOR, kind: "operator", id: "operator", attestation: "operator-recorded", source: "operator-recorded" },
553
+ summary: `review policy: ${policy.requiredApprovals} approval(s) from [${policy.authorizedRoles.join(", ")}] for [${policy.appliesTo.join(", ")}]`
554
+ });
555
+ }
556
+ return entries.sort(compareTimeline);
557
+ }
558
+ function buildNextActions(run, states, policy) {
559
+ const actions = [];
560
+ if (!policy) {
561
+ actions.push(`node scripts/cw.js review policy ${run.id} --requiredApprovals 1 --authorizedRoles reviewer --appliesTo commit`);
562
+ return actions;
563
+ }
564
+ for (const state of states) {
565
+ if (state.status === "pending" || state.status === "blocked" || state.status === "unattributed") {
566
+ actions.push(`node scripts/cw.js approve ${state.target.kind} ${run.id} ${state.target.id} --role <authorized-role> --actor <id> --attested`);
567
+ }
568
+ }
569
+ if (!actions.length)
570
+ actions.push(`node scripts/cw.js review status ${run.id} --json`);
571
+ return actions;
572
+ }
573
+ // ---------------------------------------------------------------------------
574
+ // Human formatters
575
+ // ---------------------------------------------------------------------------
576
+ function formatReviewStatus(report) {
577
+ const lines = [];
578
+ const policy = report.policy;
579
+ lines.push(`review ${report.runId} policy=${policy ? `${policy.requiredApprovals} from [${policy.authorizedRoles.join(",")}] on [${policy.appliesTo.join(",")}]` : "none"}`);
580
+ if (report.owner)
581
+ lines.push(` owner: ${report.owner.id} (${report.owner.attestation})`);
582
+ lines.push(` counts: approvals=${report.counts.approvals} rejections=${report.counts.rejections} comments=${report.counts.comments} handoffs=${report.counts.handoffs}`);
583
+ for (const state of report.targets) {
584
+ lines.push(` ${state.target.kind} ${state.target.id}: ${state.status}` +
585
+ (state.gated ? ` (${state.recordedApprovals}/${state.requiredApprovals}${state.approvers.length ? ` by ${state.approvers.join(",")}` : ""})` : " (not gated)"));
586
+ for (const note of state.missing)
587
+ lines.push(` - ${note}`);
588
+ }
589
+ if (report.timeline.length) {
590
+ lines.push(" timeline:");
591
+ for (const entry of report.timeline)
592
+ lines.push(` ${entry.createdAt} ${entry.actor.id} ${entry.summary}`);
593
+ }
594
+ return lines.join("\n");
595
+ }
596
+ function formatCommentList(comments) {
597
+ if (!comments.length)
598
+ return "no comments";
599
+ return comments
600
+ .map((record) => `${record.createdAt} ${record.actor.id} (${record.actor.attestation}) [${record.target.kind} ${record.target.id}] ${record.body}`)
601
+ .join("\n");
602
+ }
603
+ // ---------------------------------------------------------------------------
604
+ // internals
605
+ // ---------------------------------------------------------------------------
606
+ function distinctTargets(run) {
607
+ const seen = new Map();
608
+ for (const record of run.collaboration?.approvals || [])
609
+ seen.set(targetKey(record.target), record.target);
610
+ for (const record of run.collaboration?.comments || [])
611
+ seen.set(targetKey(record.target), record.target);
612
+ for (const record of run.collaboration?.handoffs || [])
613
+ seen.set(targetKey(record.target), record.target);
614
+ return [...seen.values()].sort((left, right) => targetKey(left).localeCompare(targetKey(right)));
615
+ }
616
+ /** For a commit target, also count its candidate/selection approvals. */
617
+ function relatedTargetsFor(run, target) {
618
+ if (target.kind !== "commit")
619
+ return [target];
620
+ const commit = (run.commits || []).find((entry) => entry.id === target.id);
621
+ const related = [target];
622
+ if (commit?.selectionId)
623
+ related.push({ kind: "selection", id: commit.selectionId });
624
+ if (commit?.candidateId)
625
+ related.push({ kind: "candidate", id: commit.candidateId });
626
+ return related;
627
+ }
628
+ function selfActorIdsForTarget(run, target) {
629
+ if (target.kind === "candidate")
630
+ return selfActorIdsForCandidate(run, target.id);
631
+ if (target.kind === "selection") {
632
+ const selection = (run.candidateSelections || []).find((entry) => entry.id === target.id);
633
+ return selfActorIdsForCandidate(run, selection?.candidateId, target.id);
634
+ }
635
+ if (target.kind === "commit") {
636
+ const commit = (run.commits || []).find((entry) => entry.id === target.id);
637
+ return selfActorIdsForCandidate(run, commit?.candidateId, commit?.selectionId);
638
+ }
639
+ return [];
640
+ }
641
+ function normalizeTarget(target) {
642
+ const kind = target?.kind;
643
+ const id = trimmed(target?.id);
644
+ if (!kind || !id)
645
+ throw new Error("Collaboration target requires a kind and id");
646
+ if (!["run", "task", "candidate", "selection", "commit", "node"].includes(kind)) {
647
+ throw new Error(`Unknown collaboration target kind: ${kind}`);
648
+ }
649
+ return { kind, id };
650
+ }
651
+ function auditTargetFields(target) {
652
+ switch (target.kind) {
653
+ case "candidate":
654
+ return { candidateId: target.id };
655
+ case "selection":
656
+ return { selectionId: target.id };
657
+ case "commit":
658
+ return { commitId: target.id };
659
+ case "node":
660
+ return { nodeId: target.id };
661
+ case "task":
662
+ return { taskId: target.id };
663
+ default:
664
+ return {};
665
+ }
666
+ }
667
+ function matchesAnyTarget(target, related) {
668
+ return related.some((entry) => sameTarget(target, entry));
669
+ }
670
+ function sameTarget(left, right) {
671
+ return left.kind === right.kind && left.id === right.id;
672
+ }
673
+ function targetKey(target) {
674
+ return `${target.kind}:${target.id}`;
675
+ }
676
+ function createCollabId(run, kind, count) {
677
+ const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
678
+ return `collab-${(0, state_1.safeFileName)(kind)}-${stamp}-${String(count + 1).padStart(4, "0")}`;
679
+ }
680
+ function persist(run, options) {
681
+ if (options.persist === false)
682
+ return;
683
+ (0, state_1.saveCheckpoint)(run);
684
+ }
685
+ function compareByCreated(left, right) {
686
+ return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
687
+ }
688
+ function compareTimeline(left, right) {
689
+ return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
690
+ }
691
+ function compact(value) {
692
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
693
+ }
694
+ function trimmed(value) {
695
+ if (value === undefined || value === null)
696
+ return "";
697
+ return String(value).trim();
698
+ }
699
+ function truncate(value, max) {
700
+ return value.length > max ? `${value.slice(0, max - 1)}…` : value;
701
+ }
702
+ function toNumber(value, fallback) {
703
+ if (value === undefined || value === null || value === "" || value === true)
704
+ return fallback;
705
+ const parsed = Number(value);
706
+ return Number.isFinite(parsed) ? parsed : fallback;
707
+ }
708
+ function toStringList(value, fallback) {
709
+ if (value === undefined)
710
+ return fallback;
711
+ const list = Array.isArray(value) ? value : String(value).split(",");
712
+ const cleaned = list.map((entry) => String(entry).trim()).filter(Boolean);
713
+ return cleaned.length ? unique(cleaned) : fallback;
714
+ }
715
+ function toTargetKindList(value, fallback) {
716
+ if (value === undefined)
717
+ return fallback;
718
+ const list = Array.isArray(value) ? value : String(value).split(",");
719
+ const valid = ["run", "task", "candidate", "selection", "commit", "node"];
720
+ const cleaned = list
721
+ .map((entry) => String(entry).trim())
722
+ .filter((entry) => valid.includes(entry));
723
+ return cleaned.length ? unique(cleaned) : fallback;
724
+ }
725
+ function unique(values) {
726
+ return Array.from(new Set(values));
727
+ }