cool-workflow 0.2.1 → 0.2.2
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/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/cli/dispatch.js +18 -86
- package/dist/cli/parseargv.js +5 -2
- package/dist/core/capability-data.js +270 -0
- package/dist/core/capability-table.js +16 -3504
- package/dist/core/format/help.js +28 -5
- package/dist/core/multi-agent/collaboration.js +3 -2
- package/dist/core/multi-agent/coordinator.js +4 -3
- package/dist/core/pipeline/commit-gate.js +2 -1
- package/dist/core/state/state-explosion/digest.js +3 -2
- package/dist/core/state/state-explosion/graph.js +9 -8
- package/dist/core/state/state-explosion/helpers.js +2 -1
- package/dist/core/trust/ledger.js +2 -1
- package/dist/core/types/execution-backend.js +18 -0
- package/dist/core/types/observability.js +7 -0
- package/dist/core/util/collate.js +23 -0
- package/dist/core/version.js +1 -1
- package/dist/shell/audit-cli.js +46 -2
- package/dist/shell/commit-summary.js +2 -1
- package/dist/shell/drive.js +26 -10
- package/dist/shell/evidence-reasoning.js +4 -3
- package/dist/shell/execution-backend/registry.js +2 -1
- package/dist/shell/execution-backend/types.js +0 -9
- package/dist/shell/fs-atomic.js +14 -1
- package/dist/shell/multi-agent-operator-ux.js +4 -3
- package/dist/shell/observability.js +12 -11
- package/dist/shell/onramp.js +17 -1
- package/dist/shell/operator-ux-text.js +2 -1
- package/dist/shell/operator-ux.js +7 -6
- package/dist/shell/pipeline-cli.js +82 -72
- package/dist/shell/reclamation-io.js +2 -1
- package/dist/shell/report.js +2 -1
- package/dist/shell/run-store.js +17 -0
- package/dist/shell/state-explosion-cli.js +2 -1
- package/dist/shell/topology-io.js +2 -1
- package/dist/shell/trust-audit.js +41 -2
- package/dist/shell/worker-cli.js +9 -1
- package/dist/shell/worker-isolation.js +34 -9
- package/dist/shell/workflow-app-loader.js +3 -2
- package/dist/wiring/capability-table/basics.js +66 -0
- package/dist/wiring/capability-table/exec-backend.js +162 -0
- package/dist/wiring/capability-table/index.js +42 -0
- package/dist/wiring/capability-table/multi-agent.js +623 -0
- package/dist/wiring/capability-table/parity.js +466 -0
- package/dist/wiring/capability-table/pipeline.js +280 -0
- package/dist/wiring/capability-table/registry-core.js +189 -0
- package/dist/wiring/capability-table/reporting.js +394 -0
- package/dist/wiring/capability-table/scheduling-registry.js +558 -0
- package/dist/wiring/capability-table/state.js +173 -0
- package/dist/wiring/capability-table/trust-ledger.js +134 -0
- package/dist/wiring/capability-table/workflow-apps.js +366 -0
- package/docs/agent-delegation-drive.7.md +2 -0
- package/docs/cli-mcp-parity.7.md +7 -2
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +22 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/index.md +1 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +25 -5
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/security-trust-hardening.7.md +30 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/trust-audit-anchor.7.md +69 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +3 -1
- package/scripts/bump-version.js +24 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +68 -0
- package/scripts/purity-gate.js +239 -0
- package/scripts/release-check.js +8 -1
- package/scripts/version-sync-check.js +33 -12
- package/workflows/README.md +19 -0
package/dist/core/format/help.js
CHANGED
|
@@ -28,9 +28,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
28
28
|
exports.formatHelp = formatHelp;
|
|
29
29
|
exports.formatCommandHelp = formatCommandHelp;
|
|
30
30
|
exports.formatInfo = formatInfo;
|
|
31
|
+
exports.formatSearchResults = formatSearchResults;
|
|
31
32
|
const capability_table_1 = require("../capability-table");
|
|
32
|
-
/** src/orchestrator.ts:934-951 — the
|
|
33
|
-
*
|
|
33
|
+
/** src/orchestrator.ts:934-951 — the "More commands" token set, in the old
|
|
34
|
+
* build's order (space-joined in the source, pipe-joined for display).
|
|
35
|
+
* One change from the old capture: `update` is gone. The verb had no code
|
|
36
|
+
* behind it in this build (`cw update` said "Unknown command"), so the
|
|
37
|
+
* help must not offer it. See parseargv.ts KNOWN_COMMANDS. */
|
|
34
38
|
const MORE_COMMANDS_TOKENS = [
|
|
35
39
|
"list", "search", "info", "init", "plan", "status", "next", "dispatch",
|
|
36
40
|
"result", "state", "commit", "report", "app", "sandbox", "backend",
|
|
@@ -40,7 +44,7 @@ const MORE_COMMANDS_TOKENS = [
|
|
|
40
44
|
"summary", "blackboard", "coordinator", "metrics", "operator", "sched",
|
|
41
45
|
"gc", "telemetry", "migration", "demo", "workbench", "approve", "reject",
|
|
42
46
|
"comment", "handoff", "ledger", "graph", "eval", "man", "version",
|
|
43
|
-
"
|
|
47
|
+
"fix",
|
|
44
48
|
];
|
|
45
49
|
const MORE_COMMANDS_WRAP_WIDTH = 76;
|
|
46
50
|
/** src/orchestrator.ts:940-951 — greedily pack pipe-joined tokens into
|
|
@@ -205,7 +209,9 @@ const COMMAND_HELP_ROWS = {
|
|
|
205
209
|
/** src/orchestrator.ts:899-933 — the top-level `cw help` text. Color is
|
|
206
210
|
* intentionally NOT applied here (this milestone's conformance runs pipe
|
|
207
211
|
* stdout, so NO_COLOR/non-TTY always wins); byte content matches the
|
|
208
|
-
* plain-text capture at SPEC/cli-help/_root.txt
|
|
212
|
+
* plain-text capture at SPEC/cli-help/_root.txt, but for the dead
|
|
213
|
+
* `update` lines, which were taken out on purpose (no code was behind
|
|
214
|
+
* the verb — see MORE_COMMANDS_TOKENS note above). */
|
|
209
215
|
function formatHelp() {
|
|
210
216
|
const moreCommandsLines = wrapPipeJoined(MORE_COMMANDS_TOKENS, MORE_COMMANDS_WRAP_WIDTH);
|
|
211
217
|
const lines = [
|
|
@@ -214,7 +220,6 @@ function formatHelp() {
|
|
|
214
220
|
' -q "question" [-claude|-codex|-gemini|-deepseek] Ask a question, get a report',
|
|
215
221
|
' -q "question" --link <url> Review a remote repo by URL',
|
|
216
222
|
" version Show version",
|
|
217
|
-
" update Update to latest release",
|
|
218
223
|
" doctor Check setup",
|
|
219
224
|
" fix Show fix commands for setup issues",
|
|
220
225
|
"",
|
|
@@ -312,3 +317,21 @@ function formatInfo(appId, data) {
|
|
|
312
317
|
lines.push(` Run: cw quickstart ${appId} --repo . --question "..."`);
|
|
313
318
|
return lines.join("\n");
|
|
314
319
|
}
|
|
320
|
+
/** `cw search <keyword>`'s human text — byte-exact to the milestone-1
|
|
321
|
+
* carry-over's own formatSearchResults (moved here from cli/dispatch.ts
|
|
322
|
+
* so the search capability-table row, which lives in core/, can render
|
|
323
|
+
* its own text without core importing from cli/). */
|
|
324
|
+
function formatSearchResults(keyword, results) {
|
|
325
|
+
if (results.length === 0) {
|
|
326
|
+
return `No workflows matched "${keyword}".\n Tip: cw list for all available workflows.`;
|
|
327
|
+
}
|
|
328
|
+
const lines = [`${results.length} workflow${results.length === 1 ? "" : "s"} matching "${keyword}"`];
|
|
329
|
+
for (const r of results) {
|
|
330
|
+
lines.push(` ${r.id} — ${r.title}`);
|
|
331
|
+
const cut = r.summary.length > 120 ? `${r.summary.slice(0, 119)}…` : r.summary;
|
|
332
|
+
lines.push(` ${cut}`);
|
|
333
|
+
}
|
|
334
|
+
lines.push("");
|
|
335
|
+
lines.push("Use cw info <id> for full details.");
|
|
336
|
+
return lines.join("\n");
|
|
337
|
+
}
|
|
@@ -36,6 +36,7 @@ exports.listComments = listComments;
|
|
|
36
36
|
exports.distinctTargets = distinctTargets;
|
|
37
37
|
exports.formatReviewStatus = formatReviewStatus;
|
|
38
38
|
exports.formatCommentList = formatCommentList;
|
|
39
|
+
const collate_1 = require("../util/collate");
|
|
39
40
|
exports.COLLABORATION_SCHEMA_VERSION = 1;
|
|
40
41
|
/** The single, honest stand-in for an absent identity. */
|
|
41
42
|
exports.UNATTRIBUTED_ACTOR = {
|
|
@@ -213,7 +214,7 @@ function matchesAnyTarget(target, related) {
|
|
|
213
214
|
return related.some((entry) => sameTarget(target, entry));
|
|
214
215
|
}
|
|
215
216
|
function compareByCreated(left, right) {
|
|
216
|
-
return left.createdAt
|
|
217
|
+
return (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id);
|
|
217
218
|
}
|
|
218
219
|
function disqualify(record, policy, selfIds) {
|
|
219
220
|
const actor = record.actor;
|
|
@@ -453,7 +454,7 @@ function distinctTargets(state) {
|
|
|
453
454
|
seen.set(targetKey(record.target), record.target);
|
|
454
455
|
for (const record of state.handoffs)
|
|
455
456
|
seen.set(targetKey(record.target), record.target);
|
|
456
|
-
return [...seen.values()].sort((left, right) => targetKey(left)
|
|
457
|
+
return [...seen.values()].sort((left, right) => (0, collate_1.stableCompare)(targetKey(left), targetKey(right)));
|
|
457
458
|
}
|
|
458
459
|
function formatReviewStatus(report) {
|
|
459
460
|
const lines = [];
|
|
@@ -47,6 +47,7 @@ exports.summarizeBlackboard = summarizeBlackboard;
|
|
|
47
47
|
exports.listBlackboardMessages = listBlackboardMessages;
|
|
48
48
|
exports.listBlackboardArtifacts = listBlackboardArtifacts;
|
|
49
49
|
exports.buildBlackboardGraph = buildBlackboardGraph;
|
|
50
|
+
const collate_1 = require("../util/collate");
|
|
50
51
|
exports.BLACKBOARD_SCHEMA_VERSION = 1;
|
|
51
52
|
/** Dedup, SORTS. Coordinator-side sorting `unique` — byte-identical
|
|
52
53
|
* behavior to core/multi-agent/runtime.ts's own copy, kept as a
|
|
@@ -420,7 +421,7 @@ function summarizeBlackboard(runId, state, blackboardId, defaultIndexPath) {
|
|
|
420
421
|
].sort();
|
|
421
422
|
const readyForFanin = Boolean(board && !openQuestions.length && !conflicts.length && artifacts.length > 0 && missingEvidence.length === 0);
|
|
422
423
|
const latestSnapshot = scoped(state.snapshots)
|
|
423
|
-
.sort((left, right) => left.createdAt
|
|
424
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt))
|
|
424
425
|
.at(-1);
|
|
425
426
|
return {
|
|
426
427
|
runId,
|
|
@@ -454,12 +455,12 @@ function nextAction(runId, board, openQuestions, conflicts, artifacts) {
|
|
|
454
455
|
function listBlackboardMessages(state, options = {}) {
|
|
455
456
|
return state.messages
|
|
456
457
|
.filter((message) => (!options.blackboardId || message.blackboardId === options.blackboardId) && (!options.topicId || message.topicId === options.topicId))
|
|
457
|
-
.sort((left, right) => left.createdAt
|
|
458
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
458
459
|
}
|
|
459
460
|
function listBlackboardArtifacts(state, options = {}) {
|
|
460
461
|
return state.artifacts
|
|
461
462
|
.filter((artifact) => (!options.blackboardId || artifact.blackboardId === options.blackboardId) && (!options.topicId || artifact.topicId === options.topicId))
|
|
462
|
-
.sort((left, right) => left.id
|
|
463
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
|
|
463
464
|
}
|
|
464
465
|
function buildBlackboardGraph(runId, state, recordPath, messagesPath) {
|
|
465
466
|
const nodes = [];
|
|
@@ -20,6 +20,7 @@ exports.formatCommitId = formatCommitId;
|
|
|
20
20
|
exports.gateFailureSeq = gateFailureSeq;
|
|
21
21
|
const evidence_grounding_1 = require("../trust/evidence-grounding");
|
|
22
22
|
const result_normalize_1 = require("./result-normalize");
|
|
23
|
+
const collate_1 = require("../util/collate");
|
|
23
24
|
/** The sandbox profile that accepted a candidate's worker output — the
|
|
24
25
|
* worker's own profile when present, else the backing task's. Pure port of
|
|
25
26
|
* the old build's src/gates.ts sandboxProfileForCandidate. */
|
|
@@ -97,7 +98,7 @@ function findSelectionNode(run, selectionId) {
|
|
|
97
98
|
function latestSelectionForCandidate(run, candidateId) {
|
|
98
99
|
return [...(run.candidateSelections || [])]
|
|
99
100
|
.filter((s) => s.candidateId === candidateId)
|
|
100
|
-
.sort((a, b) => (b.selectedAt || ""
|
|
101
|
+
.sort((a, b) => (0, collate_1.stableCompare)(b.selectedAt || "", a.selectedAt || ""))[0];
|
|
101
102
|
}
|
|
102
103
|
function evidenceLocatorString(entry) {
|
|
103
104
|
const ref = entry.locator || entry.path || entry.summary || entry.id;
|
|
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.summarizeBlackboardDigest = summarizeBlackboardDigest;
|
|
30
30
|
const hash_1 = require("../../hash");
|
|
31
31
|
const helpers_1 = require("./helpers");
|
|
32
|
+
const collate_1 = require("../../util/collate");
|
|
32
33
|
/** Deterministic structural summary of one (or the default) blackboard.
|
|
33
34
|
* Every list is sorted by id (`byId`); `recentChanges` is the last 10 by
|
|
34
35
|
* `updatedAt` desc, THEN re-sorted by id for the final list (matching the
|
|
@@ -63,7 +64,7 @@ function summarizeBlackboardDigest(run, blackboardId, now) {
|
|
|
63
64
|
.map((topic) => {
|
|
64
65
|
const topicMessages = messages
|
|
65
66
|
.filter((m) => m.topicId === topic.id)
|
|
66
|
-
.sort((a, b) => a.createdAt
|
|
67
|
+
.sort((a, b) => (0, collate_1.stableCompare)(a.createdAt, b.createdAt) || (0, collate_1.stableCompare)(a.id, b.id));
|
|
67
68
|
const last = topicMessages[topicMessages.length - 1];
|
|
68
69
|
return {
|
|
69
70
|
id: `thread:${topic.id}`,
|
|
@@ -181,7 +182,7 @@ function summarizeBlackboardDigest(run, blackboardId, now) {
|
|
|
181
182
|
updatedAt: record.updatedAt,
|
|
182
183
|
status: record.status,
|
|
183
184
|
}))
|
|
184
|
-
.sort((a, b) => b.updatedAt
|
|
185
|
+
.sort((a, b) => (0, collate_1.stableCompare)(b.updatedAt, a.updatedAt) || (0, collate_1.stableCompare)(a.id, b.id))
|
|
185
186
|
.slice(0, 10)
|
|
186
187
|
.map((record) => ({
|
|
187
188
|
id: `recent:${record.id}`,
|
|
@@ -37,6 +37,7 @@ exports.buildCompactGraphFromView = buildCompactGraphFromView;
|
|
|
37
37
|
const size_1 = require("./size");
|
|
38
38
|
const helpers_1 = require("./helpers");
|
|
39
39
|
Object.defineProperty(exports, "byId", { enumerable: true, get: function () { return helpers_1.byId; } });
|
|
40
|
+
const collate_1 = require("../../util/collate");
|
|
40
41
|
const runtime_1 = require("../../multi-agent/runtime");
|
|
41
42
|
const coordinator_1 = require("../../multi-agent/coordinator");
|
|
42
43
|
const topology_1 = require("../../multi-agent/topology");
|
|
@@ -144,8 +145,8 @@ function runToGraphView(run) {
|
|
|
144
145
|
return true;
|
|
145
146
|
});
|
|
146
147
|
return {
|
|
147
|
-
nodes: [...nodes.values()].sort((a, b) => a.kind
|
|
148
|
-
edges: dedupedEdges.sort((a, b) => a.from
|
|
148
|
+
nodes: [...nodes.values()].sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id)),
|
|
149
|
+
edges: dedupedEdges.sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || "")),
|
|
149
150
|
};
|
|
150
151
|
}
|
|
151
152
|
/** `buildCompactGraph(run, view, options)` — builds the graph view via
|
|
@@ -192,8 +193,8 @@ function runToGraphViewFromWorkflowRun(run) {
|
|
|
192
193
|
edges.push(edge);
|
|
193
194
|
}
|
|
194
195
|
return {
|
|
195
|
-
nodes: [...nodes.values()].sort((a, b) => a.kind
|
|
196
|
-
edges: edges.sort((a, b) => a.from
|
|
196
|
+
nodes: [...nodes.values()].sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id)),
|
|
197
|
+
edges: edges.sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || "")),
|
|
197
198
|
};
|
|
198
199
|
}
|
|
199
200
|
function collapseRuleFor() {
|
|
@@ -470,7 +471,7 @@ function buildCompactGraphFromView(runId, full, view = "compact", options = {})
|
|
|
470
471
|
// critical-path — docs/rebuild/PLAN.md byte-compat item 9).
|
|
471
472
|
const synthetic = [];
|
|
472
473
|
const collapsedNodeIds = new Map(); // sourceNodeId -> syntheticId
|
|
473
|
-
for (const [bucketKey, ids] of [...buckets.entries()].sort((a, b) => a[0]
|
|
474
|
+
for (const [bucketKey, ids] of [...buckets.entries()].sort((a, b) => (0, collate_1.stableCompare)(a[0], b[0]))) {
|
|
474
475
|
if (view !== "critical-path" && ids.length < thresholds.collapseBucket) {
|
|
475
476
|
for (const id of ids)
|
|
476
477
|
keep.add(id);
|
|
@@ -519,9 +520,9 @@ function buildCompactGraphFromView(runId, full, view = "compact", options = {})
|
|
|
519
520
|
edges.push({ from, to, label: edge.label });
|
|
520
521
|
}
|
|
521
522
|
return finalizeGraphRecord(runId, view, options, full, {
|
|
522
|
-
nodes: nodes.sort((a, b) => a.kind
|
|
523
|
-
edges: edges.sort((a, b) => a.from
|
|
524
|
-
syntheticNodes: synthetic.sort((a, b) => a.id
|
|
523
|
+
nodes: nodes.sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id)),
|
|
524
|
+
edges: edges.sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || "")),
|
|
525
|
+
syntheticNodes: synthetic.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
|
|
525
526
|
critical,
|
|
526
527
|
});
|
|
527
528
|
}
|
|
@@ -33,6 +33,7 @@ exports.slug = slug;
|
|
|
33
33
|
const hash_1 = require("../../hash");
|
|
34
34
|
Object.defineProperty(exports, "fingerprintRecords", { enumerable: true, get: function () { return hash_1.fingerprintRecords; } });
|
|
35
35
|
Object.defineProperty(exports, "fingerprintStrings", { enumerable: true, get: function () { return hash_1.fingerprintStrings; } });
|
|
36
|
+
const collate_1 = require("../../util/collate");
|
|
36
37
|
/** True for `failed`, `blocked`, `rejected`, `conflicting` — the never-
|
|
37
38
|
* collapse status set (docs/rebuild/PLAN.md byte-compat item 9). */
|
|
38
39
|
function isProtectedStatus(status) {
|
|
@@ -83,7 +84,7 @@ function unique(values) {
|
|
|
83
84
|
return Array.from(new Set(values.filter(Boolean))).sort();
|
|
84
85
|
}
|
|
85
86
|
function byId(a, b) {
|
|
86
|
-
return a.id
|
|
87
|
+
return (0, collate_1.stableCompare)(a.id, b.id);
|
|
87
88
|
}
|
|
88
89
|
/** Whitespace-collapsed; over 80 chars becomes the first 77 chars + `...`. */
|
|
89
90
|
function truncate(value) {
|
|
@@ -22,6 +22,7 @@ exports.verifyLedgerEntry = verifyLedgerEntry;
|
|
|
22
22
|
exports.applyLedgerProposal = applyLedgerProposal;
|
|
23
23
|
exports.resolveLedgerInbox = resolveLedgerInbox;
|
|
24
24
|
const hash_1 = require("../hash");
|
|
25
|
+
const collate_1 = require("../util/collate");
|
|
25
26
|
/** sha256 over the canonical content (every field except `id` and
|
|
26
27
|
* `digest`, which are derived FROM it). Returns the full `sha256:<hex>`
|
|
27
28
|
* form — the same prefixed spelling every other hash chain uses. */
|
|
@@ -187,7 +188,7 @@ function resolveLedgerInbox(entries) {
|
|
|
187
188
|
reviews: answering.map((r) => r.id).sort(),
|
|
188
189
|
};
|
|
189
190
|
})
|
|
190
|
-
.sort((a, b) => a.id
|
|
191
|
+
.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
|
|
191
192
|
const tally = (s) => proposals.filter((p) => p.resolution === s).length;
|
|
192
193
|
return {
|
|
193
194
|
proposals,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// core/types/execution-backend.ts — plain data shapes for the driver layer.
|
|
3
|
+
//
|
|
4
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the shapes
|
|
5
|
+
// in the old build's src/types/execution-backend.ts and the sandbox slice of
|
|
6
|
+
// src/types/sandbox.ts that this subsystem needs. Types only — no logic —
|
|
7
|
+
// so this file lives in core/ (moved here from shell/execution-backend/
|
|
8
|
+
// types.ts, which now re-exports it for its 7 existing importers): the
|
|
9
|
+
// executor-boundary welds in core/types/boundary.ts need ResultEnvelope
|
|
10
|
+
// and ExecutionResultEnvelope, and neither can be cherry-picked out of
|
|
11
|
+
// this file alone without also carrying their whole dependency graph
|
|
12
|
+
// (ExecutionProvenance, SandboxAttestation, BackendLocality/BackendKind,
|
|
13
|
+
// BackendExecutionHandle, ...) — so the file moves as one piece, matching
|
|
14
|
+
// its own original header's claim that it was "safe to import from both
|
|
15
|
+
// shell/ (impure) and any future core/ caller."
|
|
16
|
+
//
|
|
17
|
+
// Evidence: SPEC/execution-backend.md.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// core/types/observability.ts — the one type shell/observability.ts's
|
|
3
|
+
// executor-boundary weld (core/types/boundary.ts) needs, moved here since
|
|
4
|
+
// it is plain data with no dependency on that (impure) file's logic.
|
|
5
|
+
// shell/observability.ts re-exports it so its own existing exports stay
|
|
6
|
+
// unchanged.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stableCompare = stableCompare;
|
|
4
|
+
// core/util/collate.ts — THE one string-ordering comparator for anything
|
|
5
|
+
// whose order feeds a hash, a cache key, or a byte-pinned output.
|
|
6
|
+
//
|
|
7
|
+
// Pure. No fs, no child_process, no net, no process.env, no Date.now(), no
|
|
8
|
+
// Math.random().
|
|
9
|
+
//
|
|
10
|
+
// A bare `a.localeCompare(b)` reads the HOST's default locale (LANG/LC_ALL),
|
|
11
|
+
// which is not part of CW's replay-determinism story: two machines with
|
|
12
|
+
// different locales sorting the SAME string set can walk it in a different
|
|
13
|
+
// order, so anything that hashes that order (a cache key) drifts silently
|
|
14
|
+
// across hosts, and anything that hashes CONTENT built from that order (an
|
|
15
|
+
// eval snapshot) can misreport a determinism regression that is really just
|
|
16
|
+
// a locale difference. `stableCompare` pins the locale explicitly to "en" —
|
|
17
|
+
// this is the SAME bytes Node's full-ICU build already produces under the
|
|
18
|
+
// ICU root locale (which is what a locale-stripped environment, e.g. this
|
|
19
|
+
// repo's own conformance harness, always runs under), so switching a bare
|
|
20
|
+
// `localeCompare` call to this one changes NO existing output.
|
|
21
|
+
function stableCompare(a, b) {
|
|
22
|
+
return a.localeCompare(b, "en");
|
|
23
|
+
}
|
package/dist/core/version.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA
|
|
|
7
7
|
// `cw version` output does not change bytes just because the code under it
|
|
8
8
|
// was rebuilt. See SPEC/cli-surface.md "Exact outputs > Version" and
|
|
9
9
|
// conformance/cases/version-basic.case.js (regex `/^\d+\.\d+\.\d+\n$/`).
|
|
10
|
-
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.
|
|
10
|
+
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.2";
|
|
11
11
|
// State-kernel schema version constants (SPEC/state-core.md "Version
|
|
12
12
|
// constants"). Pinned to the old build's src/version.ts byte-for-byte.
|
|
13
13
|
exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
|
package/dist/shell/audit-cli.js
CHANGED
|
@@ -44,6 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.auditVerifyCli = auditVerifyCli;
|
|
47
|
+
exports.auditHeadCli = auditHeadCli;
|
|
47
48
|
exports.auditSummaryCli = auditSummaryCli;
|
|
48
49
|
exports.auditMultiAgentCli = auditMultiAgentCli;
|
|
49
50
|
exports.auditPolicyCli = auditPolicyCli;
|
|
@@ -65,12 +66,36 @@ const sandbox_profile_1 = require("./sandbox-profile");
|
|
|
65
66
|
function invocationCwd(args) {
|
|
66
67
|
return typeof args.cwd === "string" && args.cwd.trim() ? path.resolve(args.cwd) : process.cwd();
|
|
67
68
|
}
|
|
69
|
+
/** Parse the optional truncation anchor off the CLI options / MCP args
|
|
70
|
+
* (`--expect-head <hash>` / `--expect-count <n>`; MCP: expectHead /
|
|
71
|
+
* expectCount). Fail-closed on a malformed count — a flag given without
|
|
72
|
+
* a usable value must never silently weaken the check it asked for. */
|
|
73
|
+
function anchorOption(args) {
|
|
74
|
+
const headRaw = args["expect-head"] ?? args.expectHead;
|
|
75
|
+
const countRaw = args["expect-count"] ?? args.expectCount;
|
|
76
|
+
const expectHead = optionalString(headRaw);
|
|
77
|
+
if (headRaw !== undefined && headRaw !== null && expectHead === undefined) {
|
|
78
|
+
throw new Error("audit verify: --expect-head requires a hash value");
|
|
79
|
+
}
|
|
80
|
+
let expectCount;
|
|
81
|
+
if (countRaw !== undefined && countRaw !== null) {
|
|
82
|
+
const parsed = Number(countRaw);
|
|
83
|
+
if (countRaw === true || !Number.isInteger(parsed) || parsed < 0) {
|
|
84
|
+
throw new Error("audit verify: --expect-count requires a non-negative integer");
|
|
85
|
+
}
|
|
86
|
+
expectCount = parsed;
|
|
87
|
+
}
|
|
88
|
+
if (expectHead === undefined && expectCount === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
return { expectHead, expectCount };
|
|
91
|
+
}
|
|
68
92
|
function auditVerifyCli(runId, args) {
|
|
69
93
|
if (!runId)
|
|
70
94
|
throw new Error("audit verify requires a run id (cw audit verify <run-id>)");
|
|
95
|
+
const anchor = anchorOption(args);
|
|
71
96
|
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
|
|
72
|
-
const v = (0, trust_audit_1.verifyTrustAudit)(run);
|
|
73
|
-
|
|
97
|
+
const v = (0, trust_audit_1.verifyTrustAudit)(run, anchor);
|
|
98
|
+
const result = {
|
|
74
99
|
schemaVersion: 1,
|
|
75
100
|
runId: run.id,
|
|
76
101
|
present: v.present,
|
|
@@ -81,6 +106,25 @@ function auditVerifyCli(runId, args) {
|
|
|
81
106
|
corruptLines: v.corruptLines,
|
|
82
107
|
failedChecks: v.checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code })),
|
|
83
108
|
};
|
|
109
|
+
if (anchor) {
|
|
110
|
+
result.anchor = {
|
|
111
|
+
...(anchor.expectHead !== undefined ? { expectHead: anchor.expectHead } : {}),
|
|
112
|
+
...(anchor.expectCount !== undefined ? { expectCount: anchor.expectCount } : {}),
|
|
113
|
+
satisfied: !v.checks.some((c) => c.code === "trust-audit-truncated"),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/** `cw audit head <run>` — the chain head anchor (read-only projection).
|
|
119
|
+
* Capture it after a run (or before publishing/exporting); later,
|
|
120
|
+
* `cw audit verify <run> --expect-head <hash> --expect-count <n>`
|
|
121
|
+
* re-proves the log was not shortened since the capture. */
|
|
122
|
+
function auditHeadCli(runId, args) {
|
|
123
|
+
if (!runId)
|
|
124
|
+
throw new Error("audit head requires a run id (cw audit head <run-id>)");
|
|
125
|
+
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
|
|
126
|
+
const head = (0, trust_audit_1.trustAuditHead)(run);
|
|
127
|
+
return { schemaVersion: 1, runId: run.id, eventCount: head.eventCount, headHash: head.headHash };
|
|
84
128
|
}
|
|
85
129
|
/** MILESTONE 11 (reporting/observability, workbench audit panels) —
|
|
86
130
|
* `cw audit summary`/`audit multi-agent`/`audit policy`/`audit judge`.
|
|
@@ -46,6 +46,7 @@ exports.commitSummaryCli = commitSummaryCli;
|
|
|
46
46
|
exports.formatCommitSummaryText = formatCommitSummaryText;
|
|
47
47
|
const path = __importStar(require("node:path"));
|
|
48
48
|
const run_store_1 = require("./run-store");
|
|
49
|
+
const collate_1 = require("../core/util/collate");
|
|
49
50
|
function formatCommitRow(commit) {
|
|
50
51
|
return {
|
|
51
52
|
id: commit.id,
|
|
@@ -63,7 +64,7 @@ function formatCommitRow(commit) {
|
|
|
63
64
|
/** Byte-exact port of the old build's `summarizeOperatorCommits`
|
|
64
65
|
* (src/operator-ux.ts:339-349). */
|
|
65
66
|
function summarizeOperatorCommits(run) {
|
|
66
|
-
const commits = [...(run.commits || [])].sort((left, right) => left.createdAt
|
|
67
|
+
const commits = [...(run.commits || [])].sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
67
68
|
const rows = commits.map(formatCommitRow);
|
|
68
69
|
return {
|
|
69
70
|
total: rows.length,
|
package/dist/shell/drive.js
CHANGED
|
@@ -7,15 +7,10 @@
|
|
|
7
7
|
// milestone). Byte-exact port of the old build's src/drive.ts's
|
|
8
8
|
// imperative shell around the pure decision core now in
|
|
9
9
|
// core/pipeline/drive-decide.ts. Sub-workflow nesting and `--incremental`
|
|
10
|
-
// are ported; the concurrent-round driver (driveConcurrentRound)
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// authored as its own future conformance case per Open risk 5) — the
|
|
15
|
-
// `mode:"parallel"` architecture-review phases still complete correctly
|
|
16
|
-
// through the serial per-task loop, just without the wall-clock-parallel
|
|
17
|
-
// spawn optimization; this is flagged here rather than silently ported as
|
|
18
|
-
// if fully equivalent.
|
|
10
|
+
// are ported; the concurrent-round driver (driveConcurrentRound, below)
|
|
11
|
+
// dispatches and settles a whole round's tasks in one batch (see
|
|
12
|
+
// `--concurrency`/`roundWidth`), pinned by
|
|
13
|
+
// v2/conformance/cases/pipeline-concurrent-round.case.js.
|
|
19
14
|
//
|
|
20
15
|
// Evidence: SPEC/pipeline-run.md "Drive loop — src/drive.ts".
|
|
21
16
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -77,6 +72,7 @@ const agent_config_1 = require("./agent-config");
|
|
|
77
72
|
const registry_1 = require("./execution-backend/registry");
|
|
78
73
|
const agent_1 = require("./execution-backend/agent");
|
|
79
74
|
const hash_1 = require("../core/hash");
|
|
75
|
+
const collate_1 = require("../core/util/collate");
|
|
80
76
|
const pipeline_1 = require("./pipeline");
|
|
81
77
|
const reporter_1 = require("./reporter");
|
|
82
78
|
const fs_atomic_1 = require("./fs-atomic");
|
|
@@ -191,7 +187,10 @@ function previousPhaseResultsDigest(run, task) {
|
|
|
191
187
|
return undefined;
|
|
192
188
|
const previousTaskIds = new Set(run.phases.slice(0, phaseIndex).flatMap((p) => p.taskIds));
|
|
193
189
|
const records = [];
|
|
194
|
-
|
|
190
|
+
// stableCompare (not a bare localeCompare): this order feeds the sha256
|
|
191
|
+
// digest below, which feeds the incremental cache key — a host-locale-
|
|
192
|
+
// dependent order would silently move the cache key across machines.
|
|
193
|
+
for (const candidate of run.tasks.filter((t) => previousTaskIds.has(t.id)).sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
|
|
195
194
|
if (candidate.status !== "completed" || !candidate.resultPath || !fs.existsSync(candidate.resultPath)) {
|
|
196
195
|
records.push(undefined);
|
|
197
196
|
continue;
|
|
@@ -303,7 +302,24 @@ function processSelectedTask(ctx, selectedId, preparedOutcome, deferPersist = fa
|
|
|
303
302
|
emitProgress(`↺ ${selected.label || selected.id} (${selected.phase}) — accepting cached result`);
|
|
304
303
|
try {
|
|
305
304
|
fs.writeFileSync(manifest.resultPath, fs.readFileSync(cachePath, "utf8"), "utf8");
|
|
305
|
+
// Not gated by requireAttestedTelemetry here: the underlying result was
|
|
306
|
+
// already gated (attested or explicitly overridden) at its FIRST
|
|
307
|
+
// acceptance, before it was cached. Re-blocking a cache hit would only
|
|
308
|
+
// punish the operator for their own earlier, already-audited accept.
|
|
309
|
+
// Still made visible, not silent: when the operator requires attested
|
|
310
|
+
// telemetry, record that this particular accept came from the cache
|
|
311
|
+
// rather than a freshly re-verified hop.
|
|
306
312
|
(0, worker_isolation_1.recordWorkerOutput)(run, workerId, manifest.resultPath);
|
|
313
|
+
if (ctx.config.requireAttestedTelemetry) {
|
|
314
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
315
|
+
kind: "telemetry.cache-accept",
|
|
316
|
+
decision: "recorded",
|
|
317
|
+
source: "cw-validated",
|
|
318
|
+
workerId,
|
|
319
|
+
taskId: selected.id,
|
|
320
|
+
metadata: { reason: "result-cache hit; original attestation gate applied at first acceptance, not re-verified here" },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
307
323
|
// Advance the run lifecycle stage on accept, as the old build's
|
|
308
324
|
// recordWorkerOutput wrapper did (run.loopStage = "observe").
|
|
309
325
|
run.loopStage = "observe";
|
|
@@ -29,6 +29,7 @@ const fs_atomic_1 = require("./fs-atomic");
|
|
|
29
29
|
const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
|
|
30
30
|
const trust_audit_1 = require("./trust-audit");
|
|
31
31
|
const trust_policy_1 = require("../core/multi-agent/trust-policy");
|
|
32
|
+
const collate_1 = require("../core/util/collate");
|
|
32
33
|
exports.EVIDENCE_REASONING_SCHEMA_VERSION = 1;
|
|
33
34
|
function candidatesOf(run) {
|
|
34
35
|
return (run.candidates || []);
|
|
@@ -56,7 +57,7 @@ function buildEvidenceReasoningReport(run, options = {}) {
|
|
|
56
57
|
const counterfactuals = deriveCounterfactuals(run, scores);
|
|
57
58
|
const chains = operator.evidence
|
|
58
59
|
.map((evidence) => buildChain(run, evidence, { scores, auditEvents, counterfactuals }))
|
|
59
|
-
.sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || left.id
|
|
60
|
+
.sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
60
61
|
const totals = summarizeTotals(chains);
|
|
61
62
|
const currentFingerprint = fingerprintChains(chains);
|
|
62
63
|
const persisted = options.index;
|
|
@@ -340,7 +341,7 @@ function refreshEvidenceReasoning(run) {
|
|
|
340
341
|
generatedAt: new Date().toISOString(),
|
|
341
342
|
sourceFingerprint: report.sourceFingerprint,
|
|
342
343
|
totals: report.totals,
|
|
343
|
-
entries: entries.sort((a, b) => a.id
|
|
344
|
+
entries: entries.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
|
|
344
345
|
paths: { reasoningDir: dir, indexPath, reportPath },
|
|
345
346
|
nextAction: `node scripts/cw.js multi-agent reasoning ${run.id}`,
|
|
346
347
|
};
|
|
@@ -601,5 +602,5 @@ function unique(values) {
|
|
|
601
602
|
return Array.from(new Set(values.filter(Boolean))).sort();
|
|
602
603
|
}
|
|
603
604
|
function byRef(a, b) {
|
|
604
|
-
return a.ref
|
|
605
|
+
return (0, collate_1.stableCompare)(a.ref, b.ref);
|
|
605
606
|
}
|
|
@@ -33,6 +33,7 @@ const container_1 = require("./container");
|
|
|
33
33
|
const remote_1 = require("./remote");
|
|
34
34
|
const ci_1 = require("./ci");
|
|
35
35
|
const agent_1 = require("./agent");
|
|
36
|
+
const collate_1 = require("../../core/util/collate");
|
|
36
37
|
exports.EXECUTION_BACKEND_SCHEMA_VERSION = 1;
|
|
37
38
|
exports.DEFAULT_BACKEND_ID = "node";
|
|
38
39
|
exports.SANDBOX_DIMENSIONS = ["read", "write", "command", "network", "env"];
|
|
@@ -159,7 +160,7 @@ function registeredDrivers() {
|
|
|
159
160
|
function listBackendDescriptors() {
|
|
160
161
|
return registeredDrivers()
|
|
161
162
|
.map((driver) => specDescriptor(driver.spec))
|
|
162
|
-
.sort((left, right) => left.id
|
|
163
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
|
|
163
164
|
}
|
|
164
165
|
function backendIds() {
|
|
165
166
|
return registeredDrivers()
|
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// shell/execution-backend/types.ts — plain data shapes for the driver layer.
|
|
3
|
-
//
|
|
4
|
-
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the shapes
|
|
5
|
-
// in the old build's src/types/execution-backend.ts and the sandbox slice of
|
|
6
|
-
// src/types/sandbox.ts that this subsystem needs. Types only — no logic — so
|
|
7
|
-
// this file is safe to import from both shell/ (impure) and any future core/
|
|
8
|
-
// caller without violating the core/shell purity split.
|
|
9
|
-
//
|
|
10
|
-
// Evidence: SPEC/execution-backend.md.
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/shell/fs-atomic.js
CHANGED
|
@@ -198,14 +198,25 @@ function durableAppendFileSync(file, data) {
|
|
|
198
198
|
// only when still owned by this pid.
|
|
199
199
|
// ---------------------------------------------------------------------------
|
|
200
200
|
const FILE_LOCK_STALE_MS = 30_000;
|
|
201
|
+
// Lock paths this process holds right now. A nested withFileLock on the
|
|
202
|
+
// SAME target runs its fn directly (re-entrant) instead of waiting on its
|
|
203
|
+
// own lock file until the 240 tries run out — that lets a whole
|
|
204
|
+
// load -> change -> save cycle hold one lock while the save path inside
|
|
205
|
+
// it keeps its own withFileLock call unchanged.
|
|
206
|
+
const HELD_LOCKS = new Set();
|
|
201
207
|
function sleepSync(ms) {
|
|
202
208
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
203
209
|
}
|
|
204
210
|
/** Run `fn` while holding an advisory lock for `targetPath`; always released
|
|
205
211
|
* (unless the lock was stolen mid-operation, in which case releasing would
|
|
206
|
-
* corrupt the thief's critical section, so it is deliberately NOT released).
|
|
212
|
+
* corrupt the thief's critical section, so it is deliberately NOT released).
|
|
213
|
+
* Re-entrant inside one process: a nested call on the same target runs
|
|
214
|
+
* `fn` under the already-held lock. */
|
|
207
215
|
function withFileLock(targetPath, fn) {
|
|
208
216
|
const lock = `${targetPath}.lock`;
|
|
217
|
+
const heldKey = path.resolve(lock);
|
|
218
|
+
if (HELD_LOCKS.has(heldKey))
|
|
219
|
+
return fn();
|
|
209
220
|
fs.mkdirSync(path.dirname(lock), { recursive: true });
|
|
210
221
|
const pid = String(process.pid);
|
|
211
222
|
let acquired = false;
|
|
@@ -234,6 +245,7 @@ function withFileLock(targetPath, fn) {
|
|
|
234
245
|
}
|
|
235
246
|
if (!acquired)
|
|
236
247
|
throw new Error(`could not acquire file lock for ${targetPath}`);
|
|
248
|
+
HELD_LOCKS.add(heldKey);
|
|
237
249
|
// Refresh mtime right before the critical section.
|
|
238
250
|
try {
|
|
239
251
|
fs.utimesSync(lock, new Date(), new Date());
|
|
@@ -263,6 +275,7 @@ function withFileLock(targetPath, fn) {
|
|
|
263
275
|
return result;
|
|
264
276
|
}
|
|
265
277
|
finally {
|
|
278
|
+
HELD_LOCKS.delete(heldKey);
|
|
266
279
|
try {
|
|
267
280
|
// Only release if we still own the lock.
|
|
268
281
|
const current = fs.readFileSync(lock, "utf8");
|
|
@@ -62,6 +62,7 @@ const coordinator_io_1 = require("./coordinator-io");
|
|
|
62
62
|
const multi_agent_io_1 = require("./multi-agent-io");
|
|
63
63
|
const topology_io_1 = require("./topology-io");
|
|
64
64
|
const trust_audit_1 = require("./trust-audit");
|
|
65
|
+
const collate_1 = require("../core/util/collate");
|
|
65
66
|
function maOf(run) {
|
|
66
67
|
return run.multiAgent || {};
|
|
67
68
|
}
|
|
@@ -275,7 +276,7 @@ function deriveDependencies(run) {
|
|
|
275
276
|
for (const commit of commitsOf(run)) {
|
|
276
277
|
add(commit.selectionId ? `${run.id}:selection:${commit.selectionId}` : undefined, String(commit.stateNodeId || `${run.id}:commit:${commit.id}`), "commits", commit.verifierGated ? "committed" : "checkpoint");
|
|
277
278
|
}
|
|
278
|
-
return rows.filter(uniqueById).sort((left, right) => left.from
|
|
279
|
+
return rows.filter(uniqueById).sort((left, right) => (0, collate_1.stableCompare)(left.from, right.from) || (0, collate_1.stableCompare)(left.to, right.to));
|
|
279
280
|
}
|
|
280
281
|
function deriveFailures(run, dependencies) {
|
|
281
282
|
const rows = [];
|
|
@@ -337,7 +338,7 @@ function deriveFailures(run, dependencies) {
|
|
|
337
338
|
const readySelection = selectionsOf(run).find((selection) => !commitsOf(run).some((commit) => commit.selectionId === selection.id && commit.verifierGated));
|
|
338
339
|
if (readySelection)
|
|
339
340
|
add(String(readySelection.id), "commit-gate", "not-ready", `selection ${readySelection.id} has no verifier-gated commit`, `node scripts/cw.js commit ${run.id} --selection ${readySelection.id} --reason "<verified rationale>"`, readySelection.candidateId);
|
|
340
|
-
return rows.filter(uniqueByFailure).sort((left, right) => left.kind
|
|
341
|
+
return rows.filter(uniqueByFailure).sort((left, right) => (0, collate_1.stableCompare)(left.kind, right.kind) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
341
342
|
}
|
|
342
343
|
function deriveEvidence(run) {
|
|
343
344
|
const rows = new Map();
|
|
@@ -442,7 +443,7 @@ function deriveEvidence(run) {
|
|
|
442
443
|
return [...rows.values()]
|
|
443
444
|
.map(normalizeEvidenceStatus)
|
|
444
445
|
.map(withDisposition)
|
|
445
|
-
.sort((left, right) => statusRank(left.status) - statusRank(right.status) || left.id
|
|
446
|
+
.sort((left, right) => statusRank(left.status) - statusRank(right.status) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
446
447
|
}
|
|
447
448
|
function formatDependencies(rows) {
|
|
448
449
|
const lines = ["Dependencies"];
|