cool-workflow 0.1.80 → 0.1.82
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +42 -2
- 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/agent-config.js +21 -7
- package/dist/candidate-scoring.js +62 -48
- package/dist/capability-core.js +152 -96
- package/dist/capability-registry.js +160 -174
- package/dist/cli.js +90 -100
- package/dist/collaboration.js +5 -6
- package/dist/commit.js +232 -209
- package/dist/compare.js +18 -0
- package/dist/coordinator/classify.js +45 -0
- package/dist/coordinator/paths.js +42 -0
- package/dist/coordinator/util.js +126 -0
- package/dist/coordinator.js +127 -300
- package/dist/dispatch.js +46 -3
- package/dist/drive.js +7 -7
- package/dist/error-feedback.js +8 -4
- package/dist/evidence-reasoning.js +5 -2
- package/dist/execution-backend/agent.js +294 -0
- package/dist/execution-backend/probes.js +96 -0
- package/dist/execution-backend/util.js +47 -0
- package/dist/execution-backend.js +77 -450
- package/dist/gates.js +48 -0
- package/dist/mcp-server.js +34 -173
- package/dist/multi-agent/graph.js +84 -0
- package/dist/multi-agent/helpers.js +141 -0
- package/dist/multi-agent/ids.js +20 -0
- package/dist/multi-agent/paths.js +22 -0
- package/dist/multi-agent-eval/format.js +194 -0
- package/dist/multi-agent-eval/normalize.js +51 -0
- package/dist/multi-agent-eval.js +63 -242
- package/dist/multi-agent-host.js +53 -40
- package/dist/multi-agent-operator-ux.js +2 -1
- package/dist/multi-agent-trust.js +5 -5
- package/dist/multi-agent.js +125 -314
- package/dist/node-projection.js +59 -0
- package/dist/node-snapshot.js +11 -21
- package/dist/observability/format.js +61 -0
- package/dist/observability/intake.js +98 -0
- package/dist/observability.js +14 -160
- package/dist/operator-ux/format.js +364 -0
- package/dist/operator-ux.js +22 -363
- package/dist/orchestrator/lifecycle-operations.js +22 -1
- package/dist/orchestrator/report.js +8 -0
- package/dist/orchestrator.js +41 -10
- package/dist/reclamation.js +32 -55
- package/dist/run-export.js +138 -14
- package/dist/run-registry/derive.js +172 -0
- package/dist/run-registry/format.js +124 -0
- package/dist/run-registry/gc.js +251 -0
- package/dist/run-registry/policy.js +16 -0
- package/dist/run-registry/queue.js +116 -0
- package/dist/run-registry.js +78 -593
- package/dist/run-state-schema.js +1 -0
- package/dist/sandbox-profile.js +43 -2
- package/dist/scheduler.js +34 -4
- package/dist/state-explosion/format.js +159 -0
- package/dist/state-explosion/helpers.js +82 -0
- package/dist/state-explosion.js +65 -283
- package/dist/state-node.js +19 -4
- package/dist/telemetry-attestation.js +55 -0
- package/dist/telemetry-demo.js +15 -3
- package/dist/telemetry-ledger.js +60 -15
- package/dist/topology.js +50 -12
- package/dist/triggers.js +33 -14
- package/dist/trust-audit.js +215 -71
- package/dist/validation.js +328 -0
- package/dist/version.js +1 -1
- package/dist/worker-isolation/helpers.js +51 -0
- package/dist/worker-isolation/paths.js +46 -0
- package/dist/worker-isolation.js +182 -173
- package/docs/agent-delegation-drive.7.md +14 -0
- package/docs/cli-mcp-parity.7.md +5 -0
- package/docs/contract-migration-tooling.7.md +3 -0
- package/docs/control-plane-scheduling.7.md +3 -0
- package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
- package/docs/durable-state-and-locking.7.md +5 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +3 -0
- package/docs/execution-backends.7.md +3 -0
- package/docs/index.md +1 -0
- package/docs/launch/launch-kit.md +46 -23
- package/docs/launch/pre-launch-checklist.md +14 -14
- package/docs/multi-agent-cli-mcp-surface.7.md +5 -0
- package/docs/multi-agent-eval-replay-harness.7.md +3 -0
- package/docs/multi-agent-operator-ux.7.md +3 -0
- package/docs/multi-agent-trust-policy-audit.7.md +27 -0
- package/docs/node-snapshot-diff-replay.7.md +3 -0
- package/docs/observability-cost-accounting.7.md +3 -0
- package/docs/project-index.md +23 -6
- package/docs/real-execution-backends.7.md +3 -0
- package/docs/release-and-migration.7.md +5 -0
- package/docs/release-tooling.7.md +35 -2
- package/docs/run-registry-control-plane.7.md +55 -8
- package/docs/run-retention-reclamation.7.md +5 -0
- package/docs/state-explosion-management.7.md +3 -0
- package/docs/team-collaboration.7.md +3 -0
- package/docs/trust-model.md +267 -0
- package/docs/vendor-manifest-loadability.7.md +43 -0
- package/docs/web-desktop-workbench.7.md +3 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +1 -1
- package/package.json +4 -2
- package/scripts/agents/builtin-templates.json +7 -0
- package/scripts/bump-version.js +5 -11
- package/scripts/canonical-apps-list.js +64 -0
- package/scripts/canonical-apps.js +19 -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/golden-path.js +4 -4
- package/scripts/parity-check.js +5 -0
- package/scripts/release-check.js +5 -1
- package/scripts/release-flow.js +181 -5
- package/scripts/version-sync-check.js +5 -8
- package/dist/capability-dispatcher.js +0 -86
package/dist/commit.js
CHANGED
|
@@ -17,6 +17,8 @@ const trust_audit_1 = require("./trust-audit");
|
|
|
17
17
|
const collaboration_1 = require("./collaboration");
|
|
18
18
|
const evidence_grounding_1 = require("./evidence-grounding");
|
|
19
19
|
const verifier_1 = require("./verifier");
|
|
20
|
+
const compare_1 = require("./compare");
|
|
21
|
+
const gates_1 = require("./gates");
|
|
20
22
|
class CommitGateError extends Error {
|
|
21
23
|
structured;
|
|
22
24
|
feedbackId;
|
|
@@ -37,7 +39,7 @@ function commitState(run, input) {
|
|
|
37
39
|
throw recordCommitGateFailure(run, options, gate);
|
|
38
40
|
}
|
|
39
41
|
node_fs_1.default.mkdirSync(run.paths.commitsDir, { recursive: true });
|
|
40
|
-
const id = createCommitId();
|
|
42
|
+
const id = createCommitId(run);
|
|
41
43
|
const snapshotPath = node_path_1.default.join(run.paths.commitsDir, `${id}.json`);
|
|
42
44
|
const audit = gate.verifierGated
|
|
43
45
|
? (0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
@@ -118,26 +120,6 @@ function verifierNodeRequiresEvidence(run, verifierNode) {
|
|
|
118
120
|
return (0, verifier_1.taskRequiresEvidence)(task);
|
|
119
121
|
return true; // candidate/selection verifier with no 1:1 task — enforce grounding.
|
|
120
122
|
}
|
|
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
123
|
function evidenceLocatorString(entry) {
|
|
142
124
|
const ref = entry.locator || entry.path || entry.summary || entry.id;
|
|
143
125
|
return ref ? String(ref) : undefined;
|
|
@@ -164,10 +146,6 @@ function resolveCommitGate(run, options) {
|
|
|
164
146
|
const taskVerifierNodeId = taskVerifierFromReason(run, options.reason);
|
|
165
147
|
const explicitGate = Boolean(options.verifierNodeId || options.candidateId || options.selectionId || options.verifierGated);
|
|
166
148
|
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
149
|
if (!verifierGated) {
|
|
172
150
|
return {
|
|
173
151
|
verifierGated: false,
|
|
@@ -178,177 +156,234 @@ function resolveCommitGate(run, options) {
|
|
|
178
156
|
}
|
|
179
157
|
metadata.verifierGated = true;
|
|
180
158
|
metadata.checkpoint = false;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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) {
|
|
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) {
|
|
256
169
|
errors.push(error("commit-verifier-required", "Verifier-gated commit requires --verifier, --candidate, or --selection", {
|
|
257
170
|
details: {
|
|
258
171
|
hint: "Use --allow-unverified-checkpoint to write a non-gated checkpoint."
|
|
259
172
|
}
|
|
260
173
|
}));
|
|
261
174
|
}
|
|
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}`, {
|
|
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
|
+
}));
|
|
265
180
|
}
|
|
266
181
|
if (verifierNode) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
}));
|
|
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
|
|
292
202
|
}
|
|
293
|
-
|
|
294
|
-
|
|
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)`, {
|
|
295
338
|
nodeId: verifierNode.id,
|
|
296
|
-
details: { verifierNodeId: verifierNode.id }
|
|
339
|
+
details: { verifierNodeId: verifierNode.id, evidence: locators }
|
|
297
340
|
}));
|
|
298
341
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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)`, {
|
|
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(", ")}`, {
|
|
312
346
|
nodeId: verifierNode.id,
|
|
313
|
-
details: { verifierNodeId: verifierNode.id,
|
|
347
|
+
details: { verifierNodeId: verifierNode.id, unresolved }
|
|
314
348
|
}));
|
|
315
349
|
}
|
|
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
350
|
}
|
|
326
351
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
}));
|
|
347
377
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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;
|
|
352
387
|
const reviewErrors = (0, collaboration_1.reviewGateErrors)(run, {
|
|
353
388
|
targetKind: "commit",
|
|
354
389
|
candidateId,
|
|
@@ -356,7 +391,7 @@ function resolveCommitGate(run, options) {
|
|
|
356
391
|
selfActorIds: (0, collaboration_1.selfActorIdsForCandidate)(run, candidateId, selectionId)
|
|
357
392
|
});
|
|
358
393
|
errors.push(...reviewErrors);
|
|
359
|
-
|
|
394
|
+
return errors.length
|
|
360
395
|
? undefined
|
|
361
396
|
: (0, collaboration_1.commitReviewProvenance)(run, {
|
|
362
397
|
targetKind: "commit",
|
|
@@ -364,24 +399,6 @@ function resolveCommitGate(run, options) {
|
|
|
364
399
|
selectionId,
|
|
365
400
|
selfActorIds: (0, collaboration_1.selfActorIdsForCandidate)(run, candidateId, selectionId)
|
|
366
401
|
});
|
|
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
402
|
}
|
|
386
403
|
function recordCommitNode(run, commit, options, gate) {
|
|
387
404
|
const contract = (0, state_node_1.upsertRunContract)(run, (0, pipeline_contract_1.createDefaultPipelineContract)());
|
|
@@ -440,7 +457,7 @@ function recordCommitNode(run, commit, options, gate) {
|
|
|
440
457
|
function recordCommitGateFailure(run, options, gate) {
|
|
441
458
|
const first = gate.errors[0] || error("commit-gate-blocked", "Verifier-gated commit blocked");
|
|
442
459
|
const node = (0, state_node_1.recordNodeError)((0, state_node_1.createStateNode)({
|
|
443
|
-
id: `${run.id}:commit-gate-failed:${
|
|
460
|
+
id: `${run.id}:commit-gate-failed:${gateFailureSeq(run)}`,
|
|
444
461
|
kind: "error",
|
|
445
462
|
status: "pending",
|
|
446
463
|
loopStage: "checkpoint",
|
|
@@ -511,7 +528,7 @@ function resolveLinkedVerifier(requested, linked, errors, ownerKind, ownerId) {
|
|
|
511
528
|
function latestSelectionForCandidate(run, candidateId) {
|
|
512
529
|
return [...(run.candidateSelections || [])]
|
|
513
530
|
.filter((selection) => selection.candidateId === candidateId)
|
|
514
|
-
.sort((left, right) => right.selectedAt
|
|
531
|
+
.sort((left, right) => (0, compare_1.compareBytes)(right.selectedAt, left.selectedAt))[0];
|
|
515
532
|
}
|
|
516
533
|
function findSelection(run, selectionId) {
|
|
517
534
|
return (run.candidateSelections || []).find((selection) => selection.id === selectionId);
|
|
@@ -533,13 +550,6 @@ function findScore(run, candidateId, scoreId) {
|
|
|
533
550
|
return undefined;
|
|
534
551
|
}
|
|
535
552
|
}
|
|
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
553
|
function findNode(run, nodeId) {
|
|
544
554
|
return (run.nodes || []).find((node) => node.id === nodeId);
|
|
545
555
|
}
|
|
@@ -552,9 +562,22 @@ function error(code, message, options = {}) {
|
|
|
552
562
|
...options
|
|
553
563
|
};
|
|
554
564
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
565
|
+
// Deterministic commit id (FreeBSD-audit L12/L13): the commit's POSITION in the
|
|
566
|
+
// run's append-only commit log, not a wall-clock stamp + PRNG suffix. Re-running
|
|
567
|
+
// the same workflow mints byte-identical commit ids, so snapshot/replay digests
|
|
568
|
+
// match. The sequence is unique within a run (commits only ever append), and the
|
|
569
|
+
// commitState caller writes the snapshot under this id before pushing the commit.
|
|
570
|
+
function createCommitId(run) {
|
|
571
|
+
const seq = (run.commits || []).length + 1;
|
|
572
|
+
return `state-${String(seq).padStart(4, "0")}`;
|
|
573
|
+
}
|
|
574
|
+
// Deterministic suffix for a blocked-commit node id. Counts the commit-gate-failed
|
|
575
|
+
// nodes already recorded on the run and returns the next position, so repeated
|
|
576
|
+
// gate failures in one run stay collision-free and replay-stable.
|
|
577
|
+
function gateFailureSeq(run) {
|
|
578
|
+
const marker = ":commit-gate-failed:";
|
|
579
|
+
const seq = (run.nodes || []).filter((node) => node.id.includes(marker)).length + 1;
|
|
580
|
+
return String(seq).padStart(4, "0");
|
|
558
581
|
}
|
|
559
582
|
function readGitHead(cwd) {
|
|
560
583
|
try {
|
package/dist/compare.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compareBytes = compareBytes;
|
|
4
|
+
// Locale-INDEPENDENT total order for strings (FreeBSD-audit L12 determinism).
|
|
5
|
+
//
|
|
6
|
+
// `String.localeCompare` is host/locale-sensitive: two hosts can order the same
|
|
7
|
+
// byte-identical strings differently. That is fine for human-facing display, but
|
|
8
|
+
// FATAL when the sorted order feeds a sha256 digest, a tombstone/hash chain, or a
|
|
9
|
+
// stable-persisted projection (index.json, messages.jsonl, an export manifest):
|
|
10
|
+
// the same content would then serialize/hash differently across hosts, breaking
|
|
11
|
+
// CW's cross-host reproducibility and replay-determinism guarantees.
|
|
12
|
+
//
|
|
13
|
+
// Use compareBytes() for any ordering that flows into a hash, an export, a
|
|
14
|
+
// persisted projection, or a commit/replay-bearing decision. Pure display sorts
|
|
15
|
+
// may keep localeCompare.
|
|
16
|
+
function compareBytes(a, b) {
|
|
17
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statusToNodeStatus = statusToNodeStatus;
|
|
4
|
+
exports.decisionStatus = decisionStatus;
|
|
5
|
+
exports.auditDecision = auditDecision;
|
|
6
|
+
exports.sourceForAuthor = sourceForAuthor;
|
|
7
|
+
function statusToNodeStatus(status) {
|
|
8
|
+
switch (status) {
|
|
9
|
+
case "active":
|
|
10
|
+
case "open":
|
|
11
|
+
return "running";
|
|
12
|
+
case "resolved":
|
|
13
|
+
case "superseded":
|
|
14
|
+
return "completed";
|
|
15
|
+
case "conflicting":
|
|
16
|
+
return "blocked";
|
|
17
|
+
case "rejected":
|
|
18
|
+
return "rejected";
|
|
19
|
+
default:
|
|
20
|
+
return "completed";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function decisionStatus(outcome) {
|
|
24
|
+
if (outcome === "conflicting" || outcome === "blocked")
|
|
25
|
+
return "conflicting";
|
|
26
|
+
if (outcome === "rejected")
|
|
27
|
+
return "rejected";
|
|
28
|
+
if (outcome === "superseded")
|
|
29
|
+
return "superseded";
|
|
30
|
+
return "active";
|
|
31
|
+
}
|
|
32
|
+
function auditDecision(outcome) {
|
|
33
|
+
if (outcome === "rejected")
|
|
34
|
+
return "rejected";
|
|
35
|
+
if (outcome === "blocked" || outcome === "conflicting")
|
|
36
|
+
return "failed";
|
|
37
|
+
return "accepted";
|
|
38
|
+
}
|
|
39
|
+
function sourceForAuthor(author) {
|
|
40
|
+
if (author.kind === "runtime" || author.kind === "coordinator")
|
|
41
|
+
return "runtime-derived";
|
|
42
|
+
if (author.kind === "worker" || author.kind === "verifier")
|
|
43
|
+
return "cw-validated";
|
|
44
|
+
return "operator-recorded";
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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.boardPaths = boardPaths;
|
|
7
|
+
exports.blackboardRoot = blackboardRoot;
|
|
8
|
+
exports.messagesPath = messagesPath;
|
|
9
|
+
exports.recordPath = recordPath;
|
|
10
|
+
// Filesystem path derivation for the coordinator/blackboard layer
|
|
11
|
+
// (FreeBSD-audit R-carve). Carved out of coordinator.ts so the module no longer
|
|
12
|
+
// bundles the per-run path computation alongside the stateful blackboard
|
|
13
|
+
// operations. Re-exported from coordinator.ts to keep the public surface
|
|
14
|
+
// byte-identical.
|
|
15
|
+
//
|
|
16
|
+
// BEHAVIOR-PRESERVING — pure code movement, zero logic change. Each function is a
|
|
17
|
+
// function of a WorkflowRun's paths only: it reads run.paths and joins names; it
|
|
18
|
+
// never mutates run, never touches the blackboard state, never writes the disk.
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const state_1 = require("../state");
|
|
21
|
+
function boardPaths(run) {
|
|
22
|
+
const root = blackboardRoot(run);
|
|
23
|
+
return {
|
|
24
|
+
root,
|
|
25
|
+
index: node_path_1.default.join(root, "index.json"),
|
|
26
|
+
messages: messagesPath(run),
|
|
27
|
+
topicsDir: node_path_1.default.join(root, "topics"),
|
|
28
|
+
contextsDir: node_path_1.default.join(root, "contexts"),
|
|
29
|
+
artifactsDir: node_path_1.default.join(root, "artifacts"),
|
|
30
|
+
snapshotsDir: node_path_1.default.join(root, "snapshots"),
|
|
31
|
+
decisionsDir: node_path_1.default.join(root, "decisions")
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function blackboardRoot(run) {
|
|
35
|
+
return run.paths.blackboardDir || node_path_1.default.join(run.paths.runDir, "blackboard");
|
|
36
|
+
}
|
|
37
|
+
function messagesPath(run) {
|
|
38
|
+
return node_path_1.default.join(blackboardRoot(run), "messages.jsonl");
|
|
39
|
+
}
|
|
40
|
+
function recordPath(run, kind, id) {
|
|
41
|
+
return node_path_1.default.join(blackboardRoot(run), kind, `${(0, state_1.safeFileName)(id)}.json`);
|
|
42
|
+
}
|