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
package/dist/commit.js ADDED
@@ -0,0 +1,570 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CommitGateError = void 0;
7
+ exports.commitState = commitState;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_child_process_1 = require("node:child_process");
11
+ const state_1 = require("./state");
12
+ const pipeline_contract_1 = require("./pipeline-contract");
13
+ const state_node_1 = require("./state-node");
14
+ const pipeline_runner_1 = require("./pipeline-runner");
15
+ const error_feedback_1 = require("./error-feedback");
16
+ const trust_audit_1 = require("./trust-audit");
17
+ const collaboration_1 = require("./collaboration");
18
+ const evidence_grounding_1 = require("./evidence-grounding");
19
+ const verifier_1 = require("./verifier");
20
+ class CommitGateError extends Error {
21
+ structured;
22
+ feedbackId;
23
+ stateNodeId;
24
+ constructor(error, options = {}) {
25
+ super(error.message);
26
+ this.name = "CommitGateError";
27
+ this.structured = error;
28
+ this.feedbackId = options.feedbackId;
29
+ this.stateNodeId = options.stateNodeId;
30
+ }
31
+ }
32
+ exports.CommitGateError = CommitGateError;
33
+ function commitState(run, input) {
34
+ const options = normalizeCommitOptions(input);
35
+ const gate = resolveCommitGate(run, options);
36
+ if (gate.errors.length) {
37
+ throw recordCommitGateFailure(run, options, gate);
38
+ }
39
+ node_fs_1.default.mkdirSync(run.paths.commitsDir, { recursive: true });
40
+ const id = createCommitId();
41
+ const snapshotPath = node_path_1.default.join(run.paths.commitsDir, `${id}.json`);
42
+ const audit = gate.verifierGated
43
+ ? (0, trust_audit_1.recordTrustAuditEvent)(run, {
44
+ kind: "commit.gate",
45
+ decision: "accepted",
46
+ source: "cw-validated",
47
+ workerId: gate.rationale?.workerId,
48
+ nodeId: gate.verifierNodeId,
49
+ candidateId: gate.candidateId,
50
+ selectionId: gate.selectionId,
51
+ commitId: id,
52
+ sandboxProfileId: gate.rationale?.sandboxProfileId,
53
+ evidence: gate.evidence,
54
+ metadata: gate.rationale
55
+ })
56
+ : undefined;
57
+ const evidence = (0, trust_audit_1.normalizeEvidence)(run, gate.evidence, {
58
+ source: gate.verifierGated ? "cw-validated" : "runtime-derived",
59
+ workerId: gate.rationale?.workerId,
60
+ verifierNodeId: gate.verifierNodeId,
61
+ candidateId: gate.candidateId,
62
+ selectionId: gate.selectionId,
63
+ commitId: id,
64
+ auditEventIds: audit ? [audit.id] : []
65
+ });
66
+ const commit = {
67
+ id,
68
+ createdAt: new Date().toISOString(),
69
+ reason: options.reason,
70
+ loopStage: run.loopStage,
71
+ statePath: run.paths.state,
72
+ reportPath: run.paths.report,
73
+ snapshotPath,
74
+ gitHead: readGitHead(run.cwd),
75
+ verifierGated: gate.verifierGated,
76
+ checkpoint: !gate.verifierGated,
77
+ verifierNodeId: gate.verifierNodeId,
78
+ candidateId: gate.candidateId,
79
+ selectionId: gate.selectionId,
80
+ evidence,
81
+ // Partial commit (v0.1.59): operator commits only specified tasks.
82
+ // Failed/pending tasks remain active for later retry. The verifier gate
83
+ // still applies per-task; partial is about scope, not about skipping gates.
84
+ partial: Array.isArray(options.partialTaskIds) && options.partialTaskIds.length > 0 || undefined,
85
+ partialTaskIds: options.partialTaskIds?.length ? options.partialTaskIds : undefined,
86
+ acceptanceRationale: gate.rationale
87
+ ? {
88
+ ...gate.rationale,
89
+ commitGateResult: gate.verifierGated ? "passed" : "checkpoint",
90
+ auditEventIds: audit ? [...(gate.rationale.auditEventIds || []), audit.id] : gate.rationale.auditEventIds
91
+ }
92
+ : undefined,
93
+ review: gate.review,
94
+ metadata: {
95
+ ...(options.metadata || {}),
96
+ ...gate.metadata
97
+ }
98
+ };
99
+ const commitNodeId = recordCommitNode(run, commit, options, gate);
100
+ if (commitNodeId)
101
+ commit.stateNodeId = commitNodeId;
102
+ (0, state_1.writeJson)(snapshotPath, {
103
+ commit,
104
+ run
105
+ });
106
+ run.commits.push(commit);
107
+ return commit;
108
+ }
109
+ /** A verifier node is held to the grounded-evidence bar when its backing task
110
+ * requires evidence, or when it backs an explicit candidate/selection commit
111
+ * (no 1:1 task — these are the higher-stakes commits the gate exists for). */
112
+ function verifierNodeRequiresEvidence(run, verifierNode) {
113
+ const marker = ":verifier:";
114
+ const idx = verifierNode.id.indexOf(marker);
115
+ const taskId = idx >= 0 ? verifierNode.id.slice(idx + marker.length) : undefined;
116
+ const task = taskId ? run.tasks.find((candidate) => candidate.id === taskId) : undefined;
117
+ if (task)
118
+ return (0, verifier_1.taskRequiresEvidence)(task);
119
+ return true; // candidate/selection verifier with no 1:1 task — enforce grounding.
120
+ }
121
+ /** The HARD no-false-green gate (DIRECTION.md "ambiguity is a visible state").
122
+ * A verifier node is built FROM a result node; when that result captured no
123
+ * structured signal at all the result node carries an `metadata.captureWarning`
124
+ * marker (set in worker-isolation / lifecycle ingest via isEmptyCapture). The
125
+ * worker output is still ACCEPTED (a recorded warning, never a silent pass), but
126
+ * a verifier-GATED commit must NOT be able to present that zero-evidence result
127
+ * as clean/green. We detect it here, reading ONLY persisted state (the source
128
+ * result node's metadata) — purely functional, no clock/ordering — so snapshot
129
+ * replay reaches the same gate decision. Returns the marker string, or undefined.
130
+ *
131
+ * Resolution trail: verifier node -> its input/parent result node. We look at
132
+ * `inputs.inputNodeId` (set by runPipelineStage) first, then fall back to the
133
+ * first parent, so it works regardless of which ingest path produced the node. */
134
+ function emptyCaptureWarning(run, verifierNode) {
135
+ const resultNodeId = (typeof verifierNode.inputs?.inputNodeId === "string" ? verifierNode.inputs.inputNodeId : undefined) ||
136
+ verifierNode.parents[0];
137
+ const resultNode = resultNodeId ? findNode(run, resultNodeId) : undefined;
138
+ const warning = resultNode?.metadata?.captureWarning;
139
+ return typeof warning === "string" && warning ? warning : undefined;
140
+ }
141
+ function evidenceLocatorString(entry) {
142
+ const ref = entry.locator || entry.path || entry.summary || entry.id;
143
+ return ref ? String(ref) : undefined;
144
+ }
145
+ /** Base dirs used to resolve file-style evidence locators in strict mode. */
146
+ function commitEvidenceBaseDirs(run) {
147
+ return Array.from(new Set([run.cwd, process.cwd(), run.paths.runDir].filter(Boolean)));
148
+ }
149
+ function normalizeCommitOptions(input) {
150
+ if (typeof input === "string")
151
+ return { reason: input || "manual", source: "runtime" };
152
+ return {
153
+ ...input,
154
+ reason: input.reason || "manual",
155
+ source: input.source || "runtime"
156
+ };
157
+ }
158
+ function resolveCommitGate(run, options) {
159
+ const metadata = {
160
+ verifierGated: false,
161
+ checkpoint: true
162
+ };
163
+ const errors = [];
164
+ const taskVerifierNodeId = taskVerifierFromReason(run, options.reason);
165
+ const explicitGate = Boolean(options.verifierNodeId || options.candidateId || options.selectionId || options.verifierGated);
166
+ const verifierGated = explicitGate || Boolean(taskVerifierNodeId);
167
+ let verifierNodeId = options.verifierNodeId || taskVerifierNodeId;
168
+ let candidateId = options.candidateId;
169
+ let selectionId = options.selectionId;
170
+ let selectionNodeId;
171
+ if (!verifierGated) {
172
+ return {
173
+ verifierGated: false,
174
+ evidence: [],
175
+ errors,
176
+ metadata
177
+ };
178
+ }
179
+ metadata.verifierGated = true;
180
+ metadata.checkpoint = false;
181
+ if (selectionId) {
182
+ const selection = findSelection(run, selectionId);
183
+ if (!selection) {
184
+ errors.push(error("commit-selection-not-found", `Commit selection not found: ${selectionId}`, { details: { selectionId } }));
185
+ }
186
+ else {
187
+ candidateId = candidateId || selection.candidateId;
188
+ verifierNodeId = resolveLinkedVerifier(verifierNodeId, selection.verifierNodeId, errors, "selection", selection.id);
189
+ const selectionNode = findSelectionNode(run, selection.id);
190
+ selectionNodeId = selectionNode?.id;
191
+ if (!selectionNode) {
192
+ errors.push(error("commit-selection-node-missing", `Selection ${selection.id} has no state node`, {
193
+ details: { selectionId: selection.id, candidateId: selection.candidateId }
194
+ }));
195
+ }
196
+ else if (selectionNode.kind !== "candidate" || selectionNode.status !== "verified") {
197
+ errors.push(error("commit-selection-not-verified", `Selection ${selection.id} is not a verified candidate selection`, {
198
+ nodeId: selectionNode.id,
199
+ details: { selectionId: selection.id, status: selectionNode.status, kind: selectionNode.kind }
200
+ }));
201
+ }
202
+ if (!selection.scoreId) {
203
+ errors.push(error("commit-candidate-unscored", `Selection ${selection.id} has no score evidence`, {
204
+ details: { selectionId: selection.id, candidateId: selection.candidateId }
205
+ }));
206
+ }
207
+ }
208
+ }
209
+ if (candidateId) {
210
+ const candidate = findCandidate(run, candidateId);
211
+ if (!candidate) {
212
+ errors.push(error("commit-candidate-not-found", `Commit candidate not found: ${candidateId}`, { details: { candidateId } }));
213
+ }
214
+ else {
215
+ if (candidate.status === "rejected" || candidate.status === "failed") {
216
+ errors.push(error("commit-candidate-not-selectable", `Candidate ${candidateId} is ${candidate.status}`, {
217
+ details: { candidateId, status: candidate.status }
218
+ }));
219
+ }
220
+ if (!candidate.scores.length) {
221
+ errors.push(error("commit-candidate-unscored", `Candidate ${candidateId} has no score evidence`, {
222
+ details: { candidateId }
223
+ }));
224
+ }
225
+ if (candidate.status !== "verified") {
226
+ errors.push(error("commit-candidate-not-verified", `Candidate ${candidateId} is not verifier-gated`, {
227
+ details: { candidateId, status: candidate.status }
228
+ }));
229
+ }
230
+ const selection = selectionId ? findSelection(run, selectionId) : latestSelectionForCandidate(run, candidateId);
231
+ if (!selection) {
232
+ errors.push(error("commit-candidate-selection-missing", `Candidate ${candidateId} has no verified selection`, {
233
+ details: { candidateId }
234
+ }));
235
+ }
236
+ else {
237
+ selectionId = selection.id;
238
+ verifierNodeId = resolveLinkedVerifier(verifierNodeId, selection.verifierNodeId || candidate.verifierNodeId, errors, "candidate", candidateId);
239
+ const selectionNode = findSelectionNode(run, selection.id);
240
+ selectionNodeId = selectionNode?.id;
241
+ if (!selectionNode || selectionNode.status !== "verified") {
242
+ errors.push(error("commit-selection-not-verified", `Candidate ${candidateId} selection ${selection.id} is not verified`, {
243
+ nodeId: selectionNode?.id,
244
+ details: { candidateId, selectionId: selection.id, status: selectionNode?.status || "missing" }
245
+ }));
246
+ }
247
+ if (!selection.scoreId) {
248
+ errors.push(error("commit-candidate-unscored", `Candidate ${candidateId} selection ${selection.id} has no score evidence`, {
249
+ details: { candidateId, selectionId: selection.id }
250
+ }));
251
+ }
252
+ }
253
+ }
254
+ }
255
+ if (!verifierNodeId) {
256
+ errors.push(error("commit-verifier-required", "Verifier-gated commit requires --verifier, --candidate, or --selection", {
257
+ details: {
258
+ hint: "Use --allow-unverified-checkpoint to write a non-gated checkpoint."
259
+ }
260
+ }));
261
+ }
262
+ const verifierNode = verifierNodeId ? findNode(run, verifierNodeId) : undefined;
263
+ if (verifierNodeId && !verifierNode) {
264
+ errors.push(error("commit-verifier-not-found", `Verifier node not found: ${verifierNodeId}`, { details: { verifierNodeId } }));
265
+ }
266
+ if (verifierNode) {
267
+ if (verifierNode.kind !== "verifier") {
268
+ errors.push(error("commit-verifier-wrong-kind", `Node ${verifierNode.id} is not a verifier node`, {
269
+ nodeId: verifierNode.id,
270
+ details: { verifierNodeId: verifierNode.id, kind: verifierNode.kind }
271
+ }));
272
+ }
273
+ if (verifierNode.status !== "verified") {
274
+ errors.push(error("commit-verifier-not-verified", `Verifier node ${verifierNode.id} is ${verifierNode.status}`, {
275
+ nodeId: verifierNode.id,
276
+ details: { verifierNodeId: verifierNode.id, status: verifierNode.status }
277
+ }));
278
+ }
279
+ // HARD no-false-green gate (v0.1.43): if the backing result was an
280
+ // empty-capture (no findings AND no evidence even after robust
281
+ // normalization), the verifier node only carries a non-grounded summary
282
+ // fallback. That can otherwise pass the length-only path for
283
+ // optional-evidence tasks and present a 0-real-evidence review as
284
+ // clean/green. Block it BEFORE the rationale is built so the commit fails
285
+ // visibly (commit-gate-failed node + feedback) instead of silently passing.
286
+ const captureWarning = emptyCaptureWarning(run, verifierNode);
287
+ if (captureWarning) {
288
+ errors.push(error("commit-rationale-empty-capture", `Verifier node ${verifierNode.id} cannot back a commit: ${captureWarning}`, {
289
+ nodeId: verifierNode.id,
290
+ details: { verifierNodeId: verifierNode.id, reason: captureWarning }
291
+ }));
292
+ }
293
+ if (!verifierNode.evidence.length) {
294
+ errors.push(error("commit-verifier-missing-evidence", `Verifier node ${verifierNode.id} has no evidence`, {
295
+ nodeId: verifierNode.id,
296
+ details: { verifierNodeId: verifierNode.id }
297
+ }));
298
+ }
299
+ else if (verifierNodeRequiresEvidence(run, verifierNode)) {
300
+ // Evidence grounding (v0.1.40 self-audit P1/P2): for verifier nodes whose
301
+ // task REQUIRES evidence (verify/verdict/requiresEvidence, and explicit
302
+ // candidate/selection commits), the gate must not accept unverifiable free
303
+ // text. Require at least one GROUNDED locator (path-like / URL /
304
+ // namespace:value), and — when the operator opts in via
305
+ // CW_REQUIRE_RESOLVABLE_EVIDENCE — that file-style locators actually resolve
306
+ // on disk. Closes the "presence != existence" gap. Optional-evidence tasks
307
+ // (e.g. map/assess) keep the length-only check so the gate is never stricter
308
+ // than result acceptance was.
309
+ const locators = verifierNode.evidence.map(evidenceLocatorString).filter(Boolean);
310
+ if (!(0, evidence_grounding_1.hasGroundedEvidence)(locators)) {
311
+ errors.push(error("commit-verifier-evidence-ungrounded", `Verifier node ${verifierNode.id} evidence is not grounded (needs a path-like locator, URL, or namespace:value token)`, {
312
+ nodeId: verifierNode.id,
313
+ details: { verifierNodeId: verifierNode.id, evidence: locators }
314
+ }));
315
+ }
316
+ if ((0, evidence_grounding_1.requireResolvableEvidence)()) {
317
+ const unresolved = (0, evidence_grounding_1.unresolvedFileEvidence)(locators, commitEvidenceBaseDirs(run));
318
+ if (unresolved.length) {
319
+ errors.push(error("commit-verifier-evidence-unresolvable", `Verifier node ${verifierNode.id} cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, {
320
+ nodeId: verifierNode.id,
321
+ details: { verifierNodeId: verifierNode.id, unresolved }
322
+ }));
323
+ }
324
+ }
325
+ }
326
+ }
327
+ let rationale;
328
+ if (verifierGated && candidateId && selectionId) {
329
+ const candidate = findCandidate(run, candidateId);
330
+ const selection = findSelection(run, selectionId);
331
+ const score = selection?.scoreId ? findScore(run, candidateId, selection.scoreId) : undefined;
332
+ rationale = selection?.acceptanceRationale || (0, trust_audit_1.buildAcceptanceRationale)({
333
+ selectedCandidateId: candidateId,
334
+ scoreId: selection?.scoreId,
335
+ scoreCriteria: score?.criteria,
336
+ verifierNodeId,
337
+ evidenceCount: verifierNode?.evidence.length || 0,
338
+ sandboxProfileId: sandboxProfileForCandidate(run, candidate),
339
+ workerId: candidate?.workerId,
340
+ commitGateResult: "passed"
341
+ });
342
+ for (const failure of (0, trust_audit_1.validateAcceptanceRationale)(rationale)) {
343
+ errors.push(error("commit-rationale-incomplete", `Verifier-gated commit cannot explain acceptance: ${failure}`, {
344
+ details: { candidateId, selectionId, verifierNodeId }
345
+ }));
346
+ }
347
+ }
348
+ // REVIEW GATE — POLICY layered ON TOP of the verifier MECHANISM. These errors
349
+ // can only ADD constraints (required approvals from authorized roles); they
350
+ // never relax verifier acceptance. Empty unless a policy applies to commits.
351
+ // Fail closed: a commit lacking its required approvals is BLOCKED here.
352
+ const reviewErrors = (0, collaboration_1.reviewGateErrors)(run, {
353
+ targetKind: "commit",
354
+ candidateId,
355
+ selectionId,
356
+ selfActorIds: (0, collaboration_1.selfActorIdsForCandidate)(run, candidateId, selectionId)
357
+ });
358
+ errors.push(...reviewErrors);
359
+ const review = errors.length
360
+ ? undefined
361
+ : (0, collaboration_1.commitReviewProvenance)(run, {
362
+ targetKind: "commit",
363
+ candidateId,
364
+ selectionId,
365
+ selfActorIds: (0, collaboration_1.selfActorIdsForCandidate)(run, candidateId, selectionId)
366
+ });
367
+ return {
368
+ verifierGated: true,
369
+ verifierNodeId,
370
+ candidateId,
371
+ selectionId,
372
+ selectionNodeId,
373
+ evidence: verifierNode?.evidence || [],
374
+ errors,
375
+ rationale,
376
+ review,
377
+ metadata: {
378
+ ...metadata,
379
+ verifierNodeId,
380
+ candidateId,
381
+ selectionId,
382
+ selectionNodeId
383
+ }
384
+ };
385
+ }
386
+ function recordCommitNode(run, commit, options, gate) {
387
+ const contract = (0, state_node_1.upsertRunContract)(run, (0, pipeline_contract_1.createDefaultPipelineContract)());
388
+ const verifierNode = gate.verifierNodeId ? findNode(run, gate.verifierNodeId) : undefined;
389
+ if (commit.verifierGated && verifierNode) {
390
+ const commitResult = (0, pipeline_runner_1.createPipelineRunner)({ contractId: contract.id, persist: false }).runPipelineStage(run, "commit", verifierNode.id, {
391
+ outputNodeId: `${run.id}:commit:${commit.id}`,
392
+ outputStatus: "committed",
393
+ loopStage: "checkpoint",
394
+ outputs: {
395
+ snapshotPath: commit.snapshotPath,
396
+ gitHead: commit.gitHead,
397
+ verifierGated: true,
398
+ verifierNodeId: verifierNode.id,
399
+ candidateId: gate.candidateId,
400
+ selectionId: gate.selectionId
401
+ },
402
+ artifacts: [{ id: "snapshot", kind: "json", path: commit.snapshotPath }],
403
+ evidence: commit.evidence || verifierNode.evidence,
404
+ metadata: {
405
+ ...(options.metadata || {}),
406
+ reason: options.reason,
407
+ commitId: commit.id,
408
+ verifierGated: true,
409
+ checkpoint: false,
410
+ verifierNodeId: verifierNode.id,
411
+ candidateId: gate.candidateId,
412
+ selectionId: gate.selectionId,
413
+ selectionNodeId: gate.selectionNodeId,
414
+ acceptanceRationale: commit.acceptanceRationale
415
+ }
416
+ });
417
+ if (gate.selectionNodeId && commitResult.outputNodeId) {
418
+ linkAdditionalParent(run, gate.selectionNodeId, commitResult.outputNodeId);
419
+ }
420
+ return commitResult.outputNodeId;
421
+ }
422
+ const checkpointNode = (0, state_node_1.createStateNode)({
423
+ id: `${run.id}:checkpoint:${commit.id}`,
424
+ kind: "commit",
425
+ status: "completed",
426
+ loopStage: "checkpoint",
427
+ inputs: { reason: options.reason, commitId: commit.id },
428
+ outputs: { snapshotPath: commit.snapshotPath, gitHead: commit.gitHead, verifierGated: false, checkpoint: true },
429
+ artifacts: [{ id: "snapshot", kind: "json", path: commit.snapshotPath }],
430
+ contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
431
+ metadata: {
432
+ ...(options.metadata || {}),
433
+ verifierGated: false,
434
+ checkpoint: true
435
+ }
436
+ });
437
+ (0, state_node_1.appendRunNode)(run, checkpointNode);
438
+ return checkpointNode.id;
439
+ }
440
+ function recordCommitGateFailure(run, options, gate) {
441
+ const first = gate.errors[0] || error("commit-gate-blocked", "Verifier-gated commit blocked");
442
+ const node = (0, state_node_1.recordNodeError)((0, state_node_1.createStateNode)({
443
+ id: `${run.id}:commit-gate-failed:${createCommitId()}`,
444
+ kind: "error",
445
+ status: "pending",
446
+ loopStage: "checkpoint",
447
+ inputs: {
448
+ reason: options.reason,
449
+ verifierNodeId: gate.verifierNodeId,
450
+ candidateId: gate.candidateId,
451
+ selectionId: gate.selectionId
452
+ },
453
+ evidence: gate.evidence,
454
+ contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
455
+ metadata: {
456
+ ...(options.metadata || {}),
457
+ verifierGated: true,
458
+ checkpoint: false,
459
+ failureCount: gate.errors.length,
460
+ failures: gate.errors.map((entry) => ({ code: entry.code, message: entry.message, nodeId: entry.nodeId })),
461
+ gate: gate.metadata
462
+ }
463
+ }), first);
464
+ const persisted = (0, state_node_1.appendRunNode)(run, node);
465
+ for (const parentId of [gate.selectionNodeId, gate.verifierNodeId].filter(Boolean)) {
466
+ linkAdditionalParent(run, parentId, persisted.id);
467
+ }
468
+ const feedback = (0, error_feedback_1.recordFeedback)(run, {
469
+ source: options.source === "cli" ? "cli" : "verifier",
470
+ error: first,
471
+ nodeId: persisted.id,
472
+ stageId: "commit",
473
+ contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
474
+ retryable: false,
475
+ evidence: gate.evidence,
476
+ artifacts: [],
477
+ metadata: {
478
+ reason: options.reason,
479
+ verifierNodeId: gate.verifierNodeId,
480
+ candidateId: gate.candidateId,
481
+ selectionId: gate.selectionId,
482
+ failures: gate.errors.map((entry) => ({ code: entry.code, message: entry.message, nodeId: entry.nodeId }))
483
+ }
484
+ }, { persist: false });
485
+ return new CommitGateError(first, { feedbackId: feedback.id, stateNodeId: persisted.id });
486
+ }
487
+ function linkAdditionalParent(run, parentId, childId) {
488
+ const parent = findNode(run, parentId);
489
+ const child = findNode(run, childId);
490
+ if (!parent || !child)
491
+ return;
492
+ const [linkedParent, linkedChild] = (0, state_node_1.linkStateNodes)(parent, child);
493
+ (0, state_node_1.appendRunNode)(run, linkedParent);
494
+ (0, state_node_1.appendRunNode)(run, linkedChild);
495
+ }
496
+ function taskVerifierFromReason(run, reason) {
497
+ const taskId = reason.startsWith("result:") ? reason.slice("result:".length) : "";
498
+ if (!taskId)
499
+ return undefined;
500
+ return run.tasks.find((task) => task.id === taskId)?.verifierNodeId;
501
+ }
502
+ function resolveLinkedVerifier(requested, linked, errors, ownerKind, ownerId) {
503
+ if (requested && linked && requested !== linked) {
504
+ errors.push(error("commit-verifier-linkage-mismatch", `Requested verifier ${requested} is not linked to ${ownerKind} ${ownerId}`, {
505
+ details: { requestedVerifierNodeId: requested, linkedVerifierNodeId: linked, ownerKind, ownerId }
506
+ }));
507
+ return requested;
508
+ }
509
+ return requested || linked;
510
+ }
511
+ function latestSelectionForCandidate(run, candidateId) {
512
+ return [...(run.candidateSelections || [])]
513
+ .filter((selection) => selection.candidateId === candidateId)
514
+ .sort((left, right) => right.selectedAt.localeCompare(left.selectedAt))[0];
515
+ }
516
+ function findSelection(run, selectionId) {
517
+ return (run.candidateSelections || []).find((selection) => selection.id === selectionId);
518
+ }
519
+ function findSelectionNode(run, selectionId) {
520
+ return (run.nodes || []).find((node) => node.kind === "candidate" && node.metadata?.selectionId === selectionId);
521
+ }
522
+ function findCandidate(run, candidateId) {
523
+ return (run.candidates || []).find((candidate) => candidate.id === candidateId);
524
+ }
525
+ function findScore(run, candidateId, scoreId) {
526
+ const file = node_path_1.default.join(run.paths.candidatesDir || node_path_1.default.join(run.paths.runDir, "candidates"), (0, state_1.safeFileName)(candidateId), "scores", `${(0, state_1.safeFileName)(scoreId)}.json`);
527
+ if (!node_fs_1.default.existsSync(file))
528
+ return undefined;
529
+ try {
530
+ return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
531
+ }
532
+ catch {
533
+ return undefined;
534
+ }
535
+ }
536
+ function sandboxProfileForCandidate(run, candidate) {
537
+ const worker = candidate?.workerId ? (run.workers || []).find((entry) => entry.id === candidate.workerId) : undefined;
538
+ if (worker?.sandboxProfileId)
539
+ return worker.sandboxProfileId;
540
+ const task = candidate?.taskId ? (run.tasks || []).find((entry) => entry.id === candidate.taskId) : undefined;
541
+ return task?.sandboxProfileId;
542
+ }
543
+ function findNode(run, nodeId) {
544
+ return (run.nodes || []).find((node) => node.id === nodeId);
545
+ }
546
+ function error(code, message, options = {}) {
547
+ return {
548
+ code,
549
+ message,
550
+ at: new Date().toISOString(),
551
+ retryable: false,
552
+ ...options
553
+ };
554
+ }
555
+ function createCommitId() {
556
+ const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
557
+ return `state-${stamp}-${(0, state_1.safeFileName)(Math.random().toString(36).slice(2, 8))}`;
558
+ }
559
+ function readGitHead(cwd) {
560
+ try {
561
+ return (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
562
+ cwd,
563
+ encoding: "utf8",
564
+ stdio: ["ignore", "pipe", "ignore"]
565
+ }).trim();
566
+ }
567
+ catch {
568
+ return undefined;
569
+ }
570
+ }