cool-workflow 0.1.81 → 0.1.83
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.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +4 -4
- package/README.md +125 -121
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/candidate-scoring.js +20 -26
- package/dist/capability-core.js +80 -93
- package/dist/capability-registry.js +30 -3
- package/dist/cli.js +12 -1
- package/dist/commit.js +217 -204
- package/dist/coordinator/util.js +6 -9
- package/dist/dispatch.js +11 -3
- package/dist/doctor.js +153 -0
- package/dist/evidence-reasoning.js +4 -1
- package/dist/execution-backend/agent.js +11 -48
- package/dist/execution-backend.js +11 -31
- package/dist/gates.js +48 -0
- package/dist/mcp-server.js +11 -0
- package/dist/multi-agent/helpers.js +6 -10
- package/dist/multi-agent/ids.js +20 -0
- package/dist/multi-agent-eval.js +27 -1
- package/dist/multi-agent-host.js +53 -21
- package/dist/multi-agent-operator-ux.js +2 -1
- package/dist/multi-agent-trust.js +5 -5
- package/dist/node-projection.js +59 -0
- package/dist/node-snapshot.js +8 -18
- package/dist/orchestrator/lifecycle-operations.js +22 -1
- package/dist/orchestrator.js +29 -2
- package/dist/reclamation/hash.js +72 -0
- package/dist/reclamation.js +31 -112
- package/dist/run-registry/queue.js +6 -7
- package/dist/run-registry.js +35 -24
- package/dist/scheduler.js +112 -57
- package/dist/topology.js +25 -4
- package/dist/trust-audit.js +70 -38
- package/dist/validation.js +328 -0
- package/dist/version.js +1 -1
- package/dist/worker-isolation.js +163 -58
- package/docs/agent-delegation-drive.7.md +90 -85
- package/docs/agent-framework.md +33 -32
- package/docs/candidate-scoring.7.md +26 -24
- package/docs/canonical-workflow-apps.7.md +40 -40
- package/docs/capability-topology-registry.7.md +24 -24
- package/docs/cli-mcp-parity.7.md +227 -154
- package/docs/contract-migration-tooling.7.md +49 -41
- package/docs/control-plane-scheduling.7.md +45 -40
- package/docs/coordinator-blackboard.7.md +30 -30
- package/docs/dogfood-one-real-repo.7.md +44 -44
- package/docs/durable-state-and-locking.7.md +35 -30
- package/docs/end-to-end-golden-path.7.md +29 -29
- package/docs/error-feedback.7.md +27 -27
- package/docs/evidence-adoption-reasoning-chain.7.md +63 -58
- package/docs/execution-backends.7.md +84 -79
- package/docs/getting-started.md +35 -18
- package/docs/index.md +3 -3
- package/docs/mcp-app-surface.7.md +64 -64
- package/docs/multi-agent-cli-mcp-surface.7.md +83 -77
- package/docs/multi-agent-eval-replay-harness.7.md +59 -54
- package/docs/multi-agent-operator-ux.7.md +70 -65
- package/docs/multi-agent-runtime-core.7.md +39 -39
- package/docs/multi-agent-topologies.7.md +24 -24
- package/docs/multi-agent-trust-policy-audit.7.md +38 -38
- package/docs/node-snapshot-diff-replay.7.md +26 -21
- package/docs/observability-cost-accounting.7.md +50 -45
- package/docs/operator-ux.7.md +30 -30
- package/docs/pipeline-runner.7.md +31 -31
- package/docs/project-index.md +15 -6
- package/docs/real-execution-backends.7.md +47 -42
- package/docs/release-and-migration.7.md +43 -38
- package/docs/release-tooling.7.md +74 -39
- package/docs/routines.md +16 -16
- package/docs/run-registry-control-plane.7.md +120 -115
- package/docs/run-retention-reclamation.7.md +46 -41
- package/docs/sandbox-profiles.7.md +32 -32
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +29 -29
- package/docs/source-context-profiles.7.md +28 -28
- package/docs/state-explosion-management.7.md +64 -59
- package/docs/state-node.7.md +8 -8
- package/docs/team-collaboration.7.md +63 -58
- package/docs/trust-model.md +126 -126
- package/docs/unix-principles.md +80 -80
- package/docs/vendor-manifest-loadability.7.md +20 -20
- package/docs/verifier-gated-commit.7.md +16 -16
- package/docs/web-desktop-workbench.7.md +70 -65
- package/docs/worker-isolation.7.md +34 -37
- package/docs/workflow-app-framework.7.md +38 -38
- package/manifest/plugin.manifest.json +4 -4
- package/manifest/source-context-profiles.json +1 -1
- package/package.json +3 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +58 -0
- package/scripts/children/http-delegate-child.js +39 -0
- package/scripts/dogfood-release.js +1 -1
- package/scripts/gen-parity-doc.js +106 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/release-flow.js +181 -5
- package/dist/verifier-registry.js +0 -46
package/dist/commit.js
CHANGED
|
@@ -18,6 +18,7 @@ const collaboration_1 = require("./collaboration");
|
|
|
18
18
|
const evidence_grounding_1 = require("./evidence-grounding");
|
|
19
19
|
const verifier_1 = require("./verifier");
|
|
20
20
|
const compare_1 = require("./compare");
|
|
21
|
+
const gates_1 = require("./gates");
|
|
21
22
|
class CommitGateError extends Error {
|
|
22
23
|
structured;
|
|
23
24
|
feedbackId;
|
|
@@ -119,26 +120,6 @@ function verifierNodeRequiresEvidence(run, verifierNode) {
|
|
|
119
120
|
return (0, verifier_1.taskRequiresEvidence)(task);
|
|
120
121
|
return true; // candidate/selection verifier with no 1:1 task — enforce grounding.
|
|
121
122
|
}
|
|
122
|
-
/** The HARD no-false-green gate (DIRECTION.md "ambiguity is a visible state").
|
|
123
|
-
* A verifier node is built FROM a result node; when that result captured no
|
|
124
|
-
* structured signal at all the result node carries an `metadata.captureWarning`
|
|
125
|
-
* marker (set in worker-isolation / lifecycle ingest via isEmptyCapture). The
|
|
126
|
-
* worker output is still ACCEPTED (a recorded warning, never a silent pass), but
|
|
127
|
-
* a verifier-GATED commit must NOT be able to present that zero-evidence result
|
|
128
|
-
* as clean/green. We detect it here, reading ONLY persisted state (the source
|
|
129
|
-
* result node's metadata) — purely functional, no clock/ordering — so snapshot
|
|
130
|
-
* replay reaches the same gate decision. Returns the marker string, or undefined.
|
|
131
|
-
*
|
|
132
|
-
* Resolution trail: verifier node -> its input/parent result node. We look at
|
|
133
|
-
* `inputs.inputNodeId` (set by runPipelineStage) first, then fall back to the
|
|
134
|
-
* first parent, so it works regardless of which ingest path produced the node. */
|
|
135
|
-
function emptyCaptureWarning(run, verifierNode) {
|
|
136
|
-
const resultNodeId = (typeof verifierNode.inputs?.inputNodeId === "string" ? verifierNode.inputs.inputNodeId : undefined) ||
|
|
137
|
-
verifierNode.parents[0];
|
|
138
|
-
const resultNode = resultNodeId ? findNode(run, resultNodeId) : undefined;
|
|
139
|
-
const warning = resultNode?.metadata?.captureWarning;
|
|
140
|
-
return typeof warning === "string" && warning ? warning : undefined;
|
|
141
|
-
}
|
|
142
123
|
function evidenceLocatorString(entry) {
|
|
143
124
|
const ref = entry.locator || entry.path || entry.summary || entry.id;
|
|
144
125
|
return ref ? String(ref) : undefined;
|
|
@@ -165,10 +146,6 @@ function resolveCommitGate(run, options) {
|
|
|
165
146
|
const taskVerifierNodeId = taskVerifierFromReason(run, options.reason);
|
|
166
147
|
const explicitGate = Boolean(options.verifierNodeId || options.candidateId || options.selectionId || options.verifierGated);
|
|
167
148
|
const verifierGated = explicitGate || Boolean(taskVerifierNodeId);
|
|
168
|
-
let verifierNodeId = options.verifierNodeId || taskVerifierNodeId;
|
|
169
|
-
let candidateId = options.candidateId;
|
|
170
|
-
let selectionId = options.selectionId;
|
|
171
|
-
let selectionNodeId;
|
|
172
149
|
if (!verifierGated) {
|
|
173
150
|
return {
|
|
174
151
|
verifierGated: false,
|
|
@@ -179,177 +156,234 @@ function resolveCommitGate(run, options) {
|
|
|
179
156
|
}
|
|
180
157
|
metadata.verifierGated = true;
|
|
181
158
|
metadata.checkpoint = false;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (!selectionNode) {
|
|
193
|
-
errors.push(error("commit-selection-node-missing", `Selection ${selection.id} has no state node`, {
|
|
194
|
-
details: { selectionId: selection.id, candidateId: selection.candidateId }
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
|
-
else if (selectionNode.kind !== "candidate" || selectionNode.status !== "verified") {
|
|
198
|
-
errors.push(error("commit-selection-not-verified", `Selection ${selection.id} is not a verified candidate selection`, {
|
|
199
|
-
nodeId: selectionNode.id,
|
|
200
|
-
details: { selectionId: selection.id, status: selectionNode.status, kind: selectionNode.kind }
|
|
201
|
-
}));
|
|
202
|
-
}
|
|
203
|
-
if (!selection.scoreId) {
|
|
204
|
-
errors.push(error("commit-candidate-unscored", `Selection ${selection.id} has no score evidence`, {
|
|
205
|
-
details: { selectionId: selection.id, candidateId: selection.candidateId }
|
|
206
|
-
}));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (candidateId) {
|
|
211
|
-
const candidate = findCandidate(run, candidateId);
|
|
212
|
-
if (!candidate) {
|
|
213
|
-
errors.push(error("commit-candidate-not-found", `Commit candidate not found: ${candidateId}`, { details: { candidateId } }));
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
if (candidate.status === "rejected" || candidate.status === "failed") {
|
|
217
|
-
errors.push(error("commit-candidate-not-selectable", `Candidate ${candidateId} is ${candidate.status}`, {
|
|
218
|
-
details: { candidateId, status: candidate.status }
|
|
219
|
-
}));
|
|
220
|
-
}
|
|
221
|
-
if (!candidate.scores.length) {
|
|
222
|
-
errors.push(error("commit-candidate-unscored", `Candidate ${candidateId} has no score evidence`, {
|
|
223
|
-
details: { candidateId }
|
|
224
|
-
}));
|
|
225
|
-
}
|
|
226
|
-
if (candidate.status !== "verified") {
|
|
227
|
-
errors.push(error("commit-candidate-not-verified", `Candidate ${candidateId} is not verifier-gated`, {
|
|
228
|
-
details: { candidateId, status: candidate.status }
|
|
229
|
-
}));
|
|
230
|
-
}
|
|
231
|
-
const selection = selectionId ? findSelection(run, selectionId) : latestSelectionForCandidate(run, candidateId);
|
|
232
|
-
if (!selection) {
|
|
233
|
-
errors.push(error("commit-candidate-selection-missing", `Candidate ${candidateId} has no verified selection`, {
|
|
234
|
-
details: { candidateId }
|
|
235
|
-
}));
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
selectionId = selection.id;
|
|
239
|
-
verifierNodeId = resolveLinkedVerifier(verifierNodeId, selection.verifierNodeId || candidate.verifierNodeId, errors, "candidate", candidateId);
|
|
240
|
-
const selectionNode = findSelectionNode(run, selection.id);
|
|
241
|
-
selectionNodeId = selectionNode?.id;
|
|
242
|
-
if (!selectionNode || selectionNode.status !== "verified") {
|
|
243
|
-
errors.push(error("commit-selection-not-verified", `Candidate ${candidateId} selection ${selection.id} is not verified`, {
|
|
244
|
-
nodeId: selectionNode?.id,
|
|
245
|
-
details: { candidateId, selectionId: selection.id, status: selectionNode?.status || "missing" }
|
|
246
|
-
}));
|
|
247
|
-
}
|
|
248
|
-
if (!selection.scoreId) {
|
|
249
|
-
errors.push(error("commit-candidate-unscored", `Candidate ${candidateId} selection ${selection.id} has no score evidence`, {
|
|
250
|
-
details: { candidateId, selectionId: selection.id }
|
|
251
|
-
}));
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (!verifierNodeId) {
|
|
159
|
+
const resolution = {
|
|
160
|
+
verifierNodeId: options.verifierNodeId || taskVerifierNodeId,
|
|
161
|
+
candidateId: options.candidateId,
|
|
162
|
+
selectionId: options.selectionId,
|
|
163
|
+
selectionNodeId: undefined,
|
|
164
|
+
errors
|
|
165
|
+
};
|
|
166
|
+
resolveSelectionForCommit(run, resolution);
|
|
167
|
+
resolveCandidateForCommit(run, resolution);
|
|
168
|
+
if (!resolution.verifierNodeId) {
|
|
257
169
|
errors.push(error("commit-verifier-required", "Verifier-gated commit requires --verifier, --candidate, or --selection", {
|
|
258
170
|
details: {
|
|
259
171
|
hint: "Use --allow-unverified-checkpoint to write a non-gated checkpoint."
|
|
260
172
|
}
|
|
261
173
|
}));
|
|
262
174
|
}
|
|
263
|
-
const verifierNode = verifierNodeId ? findNode(run, verifierNodeId) : undefined;
|
|
264
|
-
if (verifierNodeId && !verifierNode) {
|
|
265
|
-
errors.push(error("commit-verifier-not-found", `Verifier node not found: ${verifierNodeId}`, {
|
|
175
|
+
const verifierNode = resolution.verifierNodeId ? findNode(run, resolution.verifierNodeId) : undefined;
|
|
176
|
+
if (resolution.verifierNodeId && !verifierNode) {
|
|
177
|
+
errors.push(error("commit-verifier-not-found", `Verifier node not found: ${resolution.verifierNodeId}`, {
|
|
178
|
+
details: { verifierNodeId: resolution.verifierNodeId }
|
|
179
|
+
}));
|
|
266
180
|
}
|
|
267
181
|
if (verifierNode) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (captureWarning) {
|
|
289
|
-
errors.push(error("commit-rationale-empty-capture", `Verifier node ${verifierNode.id} cannot back a commit: ${captureWarning}`, {
|
|
290
|
-
nodeId: verifierNode.id,
|
|
291
|
-
details: { verifierNodeId: verifierNode.id, reason: captureWarning }
|
|
292
|
-
}));
|
|
182
|
+
groundVerifierEvidence(run, verifierNode, errors);
|
|
183
|
+
}
|
|
184
|
+
const rationale = buildCommitRationale(run, resolution, verifierNode);
|
|
185
|
+
const review = layerCommitReviewGate(run, resolution, errors);
|
|
186
|
+
return {
|
|
187
|
+
verifierGated: true,
|
|
188
|
+
verifierNodeId: resolution.verifierNodeId,
|
|
189
|
+
candidateId: resolution.candidateId,
|
|
190
|
+
selectionId: resolution.selectionId,
|
|
191
|
+
selectionNodeId: resolution.selectionNodeId,
|
|
192
|
+
evidence: verifierNode?.evidence || [],
|
|
193
|
+
errors,
|
|
194
|
+
rationale,
|
|
195
|
+
review,
|
|
196
|
+
metadata: {
|
|
197
|
+
...metadata,
|
|
198
|
+
verifierNodeId: resolution.verifierNodeId,
|
|
199
|
+
candidateId: resolution.candidateId,
|
|
200
|
+
selectionId: resolution.selectionId,
|
|
201
|
+
selectionNodeId: resolution.selectionNodeId
|
|
293
202
|
}
|
|
294
|
-
|
|
295
|
-
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Selection pass: when a selectionId is supplied, resolve the candidate it
|
|
206
|
+
* carries, the linked verifier, and its state node — pushing the SAME errors as
|
|
207
|
+
* the inline block did, in the same order. No-op when no selectionId is set. */
|
|
208
|
+
function resolveSelectionForCommit(run, resolution) {
|
|
209
|
+
const { selectionId, errors } = resolution;
|
|
210
|
+
if (!selectionId)
|
|
211
|
+
return;
|
|
212
|
+
const selection = findSelection(run, selectionId);
|
|
213
|
+
if (!selection) {
|
|
214
|
+
errors.push(error("commit-selection-not-found", `Commit selection not found: ${selectionId}`, { details: { selectionId } }));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
resolution.candidateId = resolution.candidateId || selection.candidateId;
|
|
218
|
+
resolution.verifierNodeId = resolveLinkedVerifier(resolution.verifierNodeId, selection.verifierNodeId, errors, "selection", selection.id);
|
|
219
|
+
const selectionNode = findSelectionNode(run, selection.id);
|
|
220
|
+
resolution.selectionNodeId = selectionNode?.id;
|
|
221
|
+
if (!selectionNode) {
|
|
222
|
+
errors.push(error("commit-selection-node-missing", `Selection ${selection.id} has no state node`, {
|
|
223
|
+
details: { selectionId: selection.id, candidateId: selection.candidateId }
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
else if (selectionNode.kind !== "candidate" || selectionNode.status !== "verified") {
|
|
227
|
+
errors.push(error("commit-selection-not-verified", `Selection ${selection.id} is not a verified candidate selection`, {
|
|
228
|
+
nodeId: selectionNode.id,
|
|
229
|
+
details: { selectionId: selection.id, status: selectionNode.status, kind: selectionNode.kind }
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
if (!selection.scoreId) {
|
|
233
|
+
errors.push(error("commit-candidate-unscored", `Selection ${selection.id} has no score evidence`, {
|
|
234
|
+
details: { selectionId: selection.id, candidateId: selection.candidateId }
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/** Candidate pass: when a candidateId is resolved, enforce its status/score bar
|
|
239
|
+
* and bind it to a verified selection (the supplied one, else the latest). Same
|
|
240
|
+
* errors, same order, same mutations as the inline block. No-op without one. */
|
|
241
|
+
function resolveCandidateForCommit(run, resolution) {
|
|
242
|
+
const { candidateId, errors } = resolution;
|
|
243
|
+
if (!candidateId)
|
|
244
|
+
return;
|
|
245
|
+
const candidate = findCandidate(run, candidateId);
|
|
246
|
+
if (!candidate) {
|
|
247
|
+
errors.push(error("commit-candidate-not-found", `Commit candidate not found: ${candidateId}`, { details: { candidateId } }));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (candidate.status === "rejected" || candidate.status === "failed") {
|
|
251
|
+
errors.push(error("commit-candidate-not-selectable", `Candidate ${candidateId} is ${candidate.status}`, {
|
|
252
|
+
details: { candidateId, status: candidate.status }
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
if (!candidate.scores.length) {
|
|
256
|
+
errors.push(error("commit-candidate-unscored", `Candidate ${candidateId} has no score evidence`, {
|
|
257
|
+
details: { candidateId }
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
if (candidate.status !== "verified") {
|
|
261
|
+
errors.push(error("commit-candidate-not-verified", `Candidate ${candidateId} is not verifier-gated`, {
|
|
262
|
+
details: { candidateId, status: candidate.status }
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
const selection = resolution.selectionId ? findSelection(run, resolution.selectionId) : latestSelectionForCandidate(run, candidateId);
|
|
266
|
+
if (!selection) {
|
|
267
|
+
errors.push(error("commit-candidate-selection-missing", `Candidate ${candidateId} has no verified selection`, {
|
|
268
|
+
details: { candidateId }
|
|
269
|
+
}));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
resolution.selectionId = selection.id;
|
|
273
|
+
resolution.verifierNodeId = resolveLinkedVerifier(resolution.verifierNodeId, selection.verifierNodeId || candidate.verifierNodeId, errors, "candidate", candidateId);
|
|
274
|
+
const selectionNode = findSelectionNode(run, selection.id);
|
|
275
|
+
resolution.selectionNodeId = selectionNode?.id;
|
|
276
|
+
if (!selectionNode || selectionNode.status !== "verified") {
|
|
277
|
+
errors.push(error("commit-selection-not-verified", `Candidate ${candidateId} selection ${selection.id} is not verified`, {
|
|
278
|
+
nodeId: selectionNode?.id,
|
|
279
|
+
details: { candidateId, selectionId: selection.id, status: selectionNode?.status || "missing" }
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
if (!selection.scoreId) {
|
|
283
|
+
errors.push(error("commit-candidate-unscored", `Candidate ${candidateId} selection ${selection.id} has no score evidence`, {
|
|
284
|
+
details: { candidateId, selectionId: selection.id }
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** Verifier-node grounding pass: kind/status checks plus the HARD no-false-green
|
|
289
|
+
* gate (empty-capture) and the grounded-evidence gate. These remain EXACTLY as
|
|
290
|
+
* strict — same predicates, same error codes, same order — only lifted out of
|
|
291
|
+
* the monolith. */
|
|
292
|
+
function groundVerifierEvidence(run, verifierNode, errors) {
|
|
293
|
+
if (verifierNode.kind !== "verifier") {
|
|
294
|
+
errors.push(error("commit-verifier-wrong-kind", `Node ${verifierNode.id} is not a verifier node`, {
|
|
295
|
+
nodeId: verifierNode.id,
|
|
296
|
+
details: { verifierNodeId: verifierNode.id, kind: verifierNode.kind }
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
if (verifierNode.status !== "verified") {
|
|
300
|
+
errors.push(error("commit-verifier-not-verified", `Verifier node ${verifierNode.id} is ${verifierNode.status}`, {
|
|
301
|
+
nodeId: verifierNode.id,
|
|
302
|
+
details: { verifierNodeId: verifierNode.id, status: verifierNode.status }
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
// HARD no-false-green gate (v0.1.43): if the backing result was an
|
|
306
|
+
// empty-capture (no findings AND no evidence even after robust
|
|
307
|
+
// normalization), the verifier node only carries a non-grounded summary
|
|
308
|
+
// fallback. That can otherwise pass the length-only path for
|
|
309
|
+
// optional-evidence tasks and present a 0-real-evidence review as
|
|
310
|
+
// clean/green. Block it BEFORE the rationale is built so the commit fails
|
|
311
|
+
// visibly (commit-gate-failed node + feedback) instead of silently passing.
|
|
312
|
+
const captureWarning = (0, gates_1.emptyCaptureWarning)(run, verifierNode);
|
|
313
|
+
if (captureWarning) {
|
|
314
|
+
errors.push(error("commit-rationale-empty-capture", `Verifier node ${verifierNode.id} cannot back a commit: ${captureWarning}`, {
|
|
315
|
+
nodeId: verifierNode.id,
|
|
316
|
+
details: { verifierNodeId: verifierNode.id, reason: captureWarning }
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
if (!verifierNode.evidence.length) {
|
|
320
|
+
errors.push(error("commit-verifier-missing-evidence", `Verifier node ${verifierNode.id} has no evidence`, {
|
|
321
|
+
nodeId: verifierNode.id,
|
|
322
|
+
details: { verifierNodeId: verifierNode.id }
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
else if (verifierNodeRequiresEvidence(run, verifierNode)) {
|
|
326
|
+
// Evidence grounding (v0.1.40 self-audit P1/P2): for verifier nodes whose
|
|
327
|
+
// task REQUIRES evidence (verify/verdict/requiresEvidence, and explicit
|
|
328
|
+
// candidate/selection commits), the gate must not accept unverifiable free
|
|
329
|
+
// text. Require at least one GROUNDED locator (path-like / URL /
|
|
330
|
+
// namespace:value), and — when the operator opts in via
|
|
331
|
+
// CW_REQUIRE_RESOLVABLE_EVIDENCE — that file-style locators actually resolve
|
|
332
|
+
// on disk. Closes the "presence != existence" gap. Optional-evidence tasks
|
|
333
|
+
// (e.g. map/assess) keep the length-only check so the gate is never stricter
|
|
334
|
+
// than result acceptance was.
|
|
335
|
+
const locators = verifierNode.evidence.map(evidenceLocatorString).filter(Boolean);
|
|
336
|
+
if (!(0, evidence_grounding_1.hasGroundedEvidence)(locators)) {
|
|
337
|
+
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)`, {
|
|
296
338
|
nodeId: verifierNode.id,
|
|
297
|
-
details: { verifierNodeId: verifierNode.id }
|
|
339
|
+
details: { verifierNodeId: verifierNode.id, evidence: locators }
|
|
298
340
|
}));
|
|
299
341
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
// text. Require at least one GROUNDED locator (path-like / URL /
|
|
305
|
-
// namespace:value), and — when the operator opts in via
|
|
306
|
-
// CW_REQUIRE_RESOLVABLE_EVIDENCE — that file-style locators actually resolve
|
|
307
|
-
// on disk. Closes the "presence != existence" gap. Optional-evidence tasks
|
|
308
|
-
// (e.g. map/assess) keep the length-only check so the gate is never stricter
|
|
309
|
-
// than result acceptance was.
|
|
310
|
-
const locators = verifierNode.evidence.map(evidenceLocatorString).filter(Boolean);
|
|
311
|
-
if (!(0, evidence_grounding_1.hasGroundedEvidence)(locators)) {
|
|
312
|
-
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)`, {
|
|
342
|
+
if ((0, evidence_grounding_1.requireResolvableEvidence)()) {
|
|
343
|
+
const unresolved = (0, evidence_grounding_1.unresolvedFileEvidence)(locators, commitEvidenceBaseDirs(run));
|
|
344
|
+
if (unresolved.length) {
|
|
345
|
+
errors.push(error("commit-verifier-evidence-unresolvable", `Verifier node ${verifierNode.id} cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, {
|
|
313
346
|
nodeId: verifierNode.id,
|
|
314
|
-
details: { verifierNodeId: verifierNode.id,
|
|
347
|
+
details: { verifierNodeId: verifierNode.id, unresolved }
|
|
315
348
|
}));
|
|
316
349
|
}
|
|
317
|
-
if ((0, evidence_grounding_1.requireResolvableEvidence)()) {
|
|
318
|
-
const unresolved = (0, evidence_grounding_1.unresolvedFileEvidence)(locators, commitEvidenceBaseDirs(run));
|
|
319
|
-
if (unresolved.length) {
|
|
320
|
-
errors.push(error("commit-verifier-evidence-unresolvable", `Verifier node ${verifierNode.id} cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, {
|
|
321
|
-
nodeId: verifierNode.id,
|
|
322
|
-
details: { verifierNodeId: verifierNode.id, unresolved }
|
|
323
|
-
}));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
350
|
}
|
|
327
351
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
352
|
+
}
|
|
353
|
+
/** Rationale pass: when candidate + selection are both resolved, reuse the
|
|
354
|
+
* selection's acceptance rationale or rebuild it, then push completeness errors.
|
|
355
|
+
* Returns the rationale (or undefined). Identical to the inline block. */
|
|
356
|
+
function buildCommitRationale(run, resolution, verifierNode) {
|
|
357
|
+
const { candidateId, selectionId, verifierNodeId, errors } = resolution;
|
|
358
|
+
if (!candidateId || !selectionId)
|
|
359
|
+
return undefined;
|
|
360
|
+
const candidate = findCandidate(run, candidateId);
|
|
361
|
+
const selection = findSelection(run, selectionId);
|
|
362
|
+
const score = selection?.scoreId ? findScore(run, candidateId, selection.scoreId) : undefined;
|
|
363
|
+
const rationale = selection?.acceptanceRationale || (0, trust_audit_1.buildAcceptanceRationale)({
|
|
364
|
+
selectedCandidateId: candidateId,
|
|
365
|
+
scoreId: selection?.scoreId,
|
|
366
|
+
scoreCriteria: score?.criteria,
|
|
367
|
+
verifierNodeId,
|
|
368
|
+
evidenceCount: verifierNode?.evidence.length || 0,
|
|
369
|
+
sandboxProfileId: (0, gates_1.sandboxProfileForCandidate)(run, candidate),
|
|
370
|
+
workerId: candidate?.workerId,
|
|
371
|
+
commitGateResult: "passed"
|
|
372
|
+
});
|
|
373
|
+
for (const failure of (0, trust_audit_1.validateAcceptanceRationale)(rationale)) {
|
|
374
|
+
errors.push(error("commit-rationale-incomplete", `Verifier-gated commit cannot explain acceptance: ${failure}`, {
|
|
375
|
+
details: { candidateId, selectionId, verifierNodeId }
|
|
376
|
+
}));
|
|
348
377
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
378
|
+
return rationale;
|
|
379
|
+
}
|
|
380
|
+
/** Review-gate pass — POLICY layered ON TOP of the verifier MECHANISM. These
|
|
381
|
+
* errors can only ADD constraints (required approvals from authorized roles);
|
|
382
|
+
* they never relax verifier acceptance. Empty unless a policy applies to
|
|
383
|
+
* commits. Fail closed: a commit lacking its required approvals is BLOCKED here,
|
|
384
|
+
* and provenance is only emitted when NO errors remain. */
|
|
385
|
+
function layerCommitReviewGate(run, resolution, errors) {
|
|
386
|
+
const { candidateId, selectionId } = resolution;
|
|
353
387
|
const reviewErrors = (0, collaboration_1.reviewGateErrors)(run, {
|
|
354
388
|
targetKind: "commit",
|
|
355
389
|
candidateId,
|
|
@@ -357,7 +391,7 @@ function resolveCommitGate(run, options) {
|
|
|
357
391
|
selfActorIds: (0, collaboration_1.selfActorIdsForCandidate)(run, candidateId, selectionId)
|
|
358
392
|
});
|
|
359
393
|
errors.push(...reviewErrors);
|
|
360
|
-
|
|
394
|
+
return errors.length
|
|
361
395
|
? undefined
|
|
362
396
|
: (0, collaboration_1.commitReviewProvenance)(run, {
|
|
363
397
|
targetKind: "commit",
|
|
@@ -365,24 +399,6 @@ function resolveCommitGate(run, options) {
|
|
|
365
399
|
selectionId,
|
|
366
400
|
selfActorIds: (0, collaboration_1.selfActorIdsForCandidate)(run, candidateId, selectionId)
|
|
367
401
|
});
|
|
368
|
-
return {
|
|
369
|
-
verifierGated: true,
|
|
370
|
-
verifierNodeId,
|
|
371
|
-
candidateId,
|
|
372
|
-
selectionId,
|
|
373
|
-
selectionNodeId,
|
|
374
|
-
evidence: verifierNode?.evidence || [],
|
|
375
|
-
errors,
|
|
376
|
-
rationale,
|
|
377
|
-
review,
|
|
378
|
-
metadata: {
|
|
379
|
-
...metadata,
|
|
380
|
-
verifierNodeId,
|
|
381
|
-
candidateId,
|
|
382
|
-
selectionId,
|
|
383
|
-
selectionNodeId
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
402
|
}
|
|
387
403
|
function recordCommitNode(run, commit, options, gate) {
|
|
388
404
|
const contract = (0, state_node_1.upsertRunContract)(run, (0, pipeline_contract_1.createDefaultPipelineContract)());
|
|
@@ -534,13 +550,6 @@ function findScore(run, candidateId, scoreId) {
|
|
|
534
550
|
return undefined;
|
|
535
551
|
}
|
|
536
552
|
}
|
|
537
|
-
function sandboxProfileForCandidate(run, candidate) {
|
|
538
|
-
const worker = candidate?.workerId ? (run.workers || []).find((entry) => entry.id === candidate.workerId) : undefined;
|
|
539
|
-
if (worker?.sandboxProfileId)
|
|
540
|
-
return worker.sandboxProfileId;
|
|
541
|
-
const task = candidate?.taskId ? (run.tasks || []).find((entry) => entry.id === candidate.taskId) : undefined;
|
|
542
|
-
return task?.sandboxProfileId;
|
|
543
|
-
}
|
|
544
553
|
function findNode(run, nodeId) {
|
|
545
554
|
return (run.nodes || []).find((node) => node.id === nodeId);
|
|
546
555
|
}
|
|
@@ -575,7 +584,11 @@ function readGitHead(cwd) {
|
|
|
575
584
|
return (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
576
585
|
cwd,
|
|
577
586
|
encoding: "utf8",
|
|
578
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
587
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
588
|
+
// Bound the call: a hung filesystem, a held .git/index.lock, or a
|
|
589
|
+
// credential-helper prompt must not block the commit path forever. A
|
|
590
|
+
// timeout throws, which the catch below maps to "no git head".
|
|
591
|
+
timeout: 5000
|
|
579
592
|
}).trim();
|
|
580
593
|
}
|
|
581
594
|
catch {
|
package/dist/coordinator/util.js
CHANGED
|
@@ -3,13 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createId = void 0;
|
|
6
7
|
exports.checksumFile = checksumFile;
|
|
7
8
|
exports.assertUnique = assertUnique;
|
|
8
9
|
exports.assertNoRecordPathCollisions = assertNoRecordPathCollisions;
|
|
9
10
|
exports.indexRow = indexRow;
|
|
10
11
|
exports.compareRecords = compareRecords;
|
|
11
12
|
exports.uniqueEdges = uniqueEdges;
|
|
12
|
-
exports.createId = createId;
|
|
13
13
|
exports.touch = touch;
|
|
14
14
|
exports.timestamp = timestamp;
|
|
15
15
|
exports.unique = unique;
|
|
@@ -68,14 +68,11 @@ function uniqueEdges(edges) {
|
|
|
68
68
|
}
|
|
69
69
|
return result;
|
|
70
70
|
}
|
|
71
|
-
// Deterministic record id
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function createId(prefix, seq) {
|
|
77
|
-
return `${prefix}-${String(seq).padStart(4, "0")}`;
|
|
78
|
-
}
|
|
71
|
+
// Deterministic record id — single source of truth in ../multi-agent/ids.
|
|
72
|
+
// Re-exported here so coordinator.ts importers stay byte-unchanged (F10 dedup:
|
|
73
|
+
// the multi-agent kernel shares the exact same helper).
|
|
74
|
+
var ids_1 = require("../multi-agent/ids");
|
|
75
|
+
Object.defineProperty(exports, "createId", { enumerable: true, get: function () { return ids_1.createId; } });
|
|
79
76
|
function touch(record) {
|
|
80
77
|
record.updatedAt = timestamp();
|
|
81
78
|
return record;
|
package/dist/dispatch.js
CHANGED
|
@@ -55,7 +55,7 @@ function createDispatchManifest(run, limit, options = {}) {
|
|
|
55
55
|
backendSelection
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
const dispatchId = createDispatchId();
|
|
58
|
+
const dispatchId = createDispatchId(run);
|
|
59
59
|
const manifestPath = node_path_1.default.join(run.paths.dispatchesDir, `${dispatchId}.json`);
|
|
60
60
|
node_fs_1.default.mkdirSync(run.paths.dispatchesDir, { recursive: true });
|
|
61
61
|
const taskIds = new Set(tasks.map((task) => task.id));
|
|
@@ -201,9 +201,17 @@ function formatDispatchTask(task) {
|
|
|
201
201
|
multiAgent: task.multiAgent
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
|
-
|
|
204
|
+
// Deterministic dispatch id (replay-determinism self-audit): the wall-clock stamp
|
|
205
|
+
// is an edge timestamp (stripped on replay), but the former Math.random() suffix
|
|
206
|
+
// made every dispatch mint a non-reproducible id. The suffix is now a per-run
|
|
207
|
+
// sequence — the count of dispatches already allocated on this run — so re-running
|
|
208
|
+
// the same workflow yields byte-identical dispatch ids while each dispatch within a
|
|
209
|
+
// run still gets a distinct, monotonically increasing id. Mirrors the de-clock done
|
|
210
|
+
// for worker ids in src/worker-isolation/paths.ts.
|
|
211
|
+
function createDispatchId(run) {
|
|
205
212
|
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
|
|
206
|
-
|
|
213
|
+
const seq = (run.dispatches?.length || 0) + 1;
|
|
214
|
+
return `dispatch-${stamp}-${String(seq).padStart(4, "0")}`;
|
|
207
215
|
}
|
|
208
216
|
// H7: persist a CUSTOM sandbox profile DEFINITION (loaded from a FILE at dispatch)
|
|
209
217
|
// onto run.customSandboxProfiles, keyed by the definition's logical id. Only fires
|