cool-workflow 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- package/workflows/research-synthesis.workflow.js +47 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Node Snapshot / Diff / Replay (v0.1.35) — per-node granularity over the eval
|
|
3
|
+
// harness. Snapshot one StateNode into a DERIVED, sha256-fingerprinted projection,
|
|
4
|
+
// diff two snapshots structurally, and deterministically replay one node in
|
|
5
|
+
// isolation.
|
|
6
|
+
//
|
|
7
|
+
// BSD discipline:
|
|
8
|
+
// - MECHANISM, not policy: the caller names the node id; nothing decides which
|
|
9
|
+
// node "matters".
|
|
10
|
+
// - FAIL CLOSED ON SOURCE DRIFT [load-bearing]: a snapshot carries a
|
|
11
|
+
// sourceFingerprint over the RAW node (id:status:updatedAt + artifact/evidence
|
|
12
|
+
// ids+paths). loadNodeSnapshot recomputes it from the current source; on
|
|
13
|
+
// divergence (`stale`) or a missing node/artifact (`absent`) diff/replay REFUSE
|
|
14
|
+
// with a structured error — never a silent stale replay.
|
|
15
|
+
// - REUSE, don't fork: operates on the real StateNode (getRunNode) and reuses the
|
|
16
|
+
// eval harness's normalizeValue/stableStringify and state-explosion's
|
|
17
|
+
// fingerprintStrings. No parallel node type, normalizer, or replay engine.
|
|
18
|
+
// - DETERMINISTIC: `now` is injected; the deterministic payload (normalized body
|
|
19
|
+
// + outputFingerprint) carries zero wall-clock, so two replays are byte-identical.
|
|
20
|
+
//
|
|
21
|
+
// Additive: StateNode and STATE_NODE_SCHEMA_VERSION are unchanged. See
|
|
22
|
+
// docs/node-snapshot-diff-replay.7.md.
|
|
23
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.NodeSnapshotError = exports.NODE_SNAPSHOT_SCHEMA_VERSION = void 0;
|
|
28
|
+
exports.readNodeSnapshot = readNodeSnapshot;
|
|
29
|
+
exports.readNodeReplay = readNodeReplay;
|
|
30
|
+
exports.snapshotNode = snapshotNode;
|
|
31
|
+
exports.loadNodeSnapshot = loadNodeSnapshot;
|
|
32
|
+
exports.diffNodeSnapshots = diffNodeSnapshots;
|
|
33
|
+
exports.replayNodeSnapshot = replayNodeSnapshot;
|
|
34
|
+
exports.verifyNodeReplay = verifyNodeReplay;
|
|
35
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
36
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
37
|
+
const pipeline_runner_1 = require("./pipeline-runner");
|
|
38
|
+
const state_1 = require("./state");
|
|
39
|
+
const multi_agent_eval_1 = require("./multi-agent-eval");
|
|
40
|
+
const state_explosion_1 = require("./state-explosion");
|
|
41
|
+
exports.NODE_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
42
|
+
/** Structured fail-closed error (mirrors the PipelineContractError shape). */
|
|
43
|
+
class NodeSnapshotError extends Error {
|
|
44
|
+
code;
|
|
45
|
+
freshness;
|
|
46
|
+
details;
|
|
47
|
+
constructor(code, message, options = {}) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "NodeSnapshotError";
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.freshness = options.freshness;
|
|
52
|
+
this.details = options.details;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.NodeSnapshotError = NodeSnapshotError;
|
|
56
|
+
const SNAPSHOT_SECTIONS = [
|
|
57
|
+
"status",
|
|
58
|
+
"inputs",
|
|
59
|
+
"outputs",
|
|
60
|
+
"artifacts",
|
|
61
|
+
"evidence",
|
|
62
|
+
"errors",
|
|
63
|
+
"links",
|
|
64
|
+
"metadata"
|
|
65
|
+
];
|
|
66
|
+
/** The normalized projection of a node — timestamps/paths stripped by the eval
|
|
67
|
+
* normalizer, so it is byte-stable across captures of the same logical state. */
|
|
68
|
+
function snapshotBody(node) {
|
|
69
|
+
return (0, multi_agent_eval_1.normalizeValue)({
|
|
70
|
+
id: node.id,
|
|
71
|
+
kind: node.kind,
|
|
72
|
+
status: node.status,
|
|
73
|
+
loopStage: node.loopStage,
|
|
74
|
+
inputs: node.inputs,
|
|
75
|
+
outputs: node.outputs,
|
|
76
|
+
artifacts: node.artifacts,
|
|
77
|
+
evidence: node.evidence,
|
|
78
|
+
errors: node.errors,
|
|
79
|
+
parents: node.parents,
|
|
80
|
+
children: node.children,
|
|
81
|
+
contractId: node.contractId,
|
|
82
|
+
metadata: node.metadata
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** RAW fingerprint (NOT normalized): any transition (updatedAt/status) or
|
|
86
|
+
* artifact/evidence change flips it, which is how drift is detected. */
|
|
87
|
+
function sourceFingerprint(node) {
|
|
88
|
+
return (0, state_explosion_1.fingerprintStrings)([
|
|
89
|
+
`node:${node.id}:${node.status}:${node.updatedAt}`,
|
|
90
|
+
...node.artifacts.map((artifact) => `artifact:${artifact.id}:${artifact.path}`),
|
|
91
|
+
...node.evidence.map((evidence) => `evidence:${evidence.id}:${evidence.path || ""}`)
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
function tryGetNode(run, nodeId) {
|
|
95
|
+
try {
|
|
96
|
+
return (0, pipeline_runner_1.getRunNode)(run, nodeId);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function snapshotsRoot(run) {
|
|
103
|
+
const base = run.paths.stateNodesDir || node_path_1.default.join(run.paths.runDir, "nodes");
|
|
104
|
+
return node_path_1.default.join(base, "snapshots");
|
|
105
|
+
}
|
|
106
|
+
function snapshotDir(run, nodeId) {
|
|
107
|
+
return node_path_1.default.join(snapshotsRoot(run), (0, state_1.safeFileName)(nodeId));
|
|
108
|
+
}
|
|
109
|
+
/** Load a persisted snapshot by id (scans the per-node snapshot dirs). */
|
|
110
|
+
function readNodeSnapshot(run, snapshotId) {
|
|
111
|
+
const root = snapshotsRoot(run);
|
|
112
|
+
if (node_fs_1.default.existsSync(root)) {
|
|
113
|
+
for (const nodeDir of node_fs_1.default.readdirSync(root)) {
|
|
114
|
+
const file = node_path_1.default.join(root, nodeDir, `${snapshotId}.json`);
|
|
115
|
+
if (node_fs_1.default.existsSync(file))
|
|
116
|
+
return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new NodeSnapshotError("snapshot-not-found", `Node snapshot ${snapshotId} not found in run ${run.id}`, { freshness: "absent" });
|
|
120
|
+
}
|
|
121
|
+
/** Load a persisted replay run by id. */
|
|
122
|
+
function readNodeReplay(run, replayId) {
|
|
123
|
+
const root = snapshotsRoot(run);
|
|
124
|
+
if (node_fs_1.default.existsSync(root)) {
|
|
125
|
+
for (const nodeDir of node_fs_1.default.readdirSync(root)) {
|
|
126
|
+
const file = node_path_1.default.join(root, nodeDir, "replays", `${replayId}.json`);
|
|
127
|
+
if (node_fs_1.default.existsSync(file))
|
|
128
|
+
return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw new NodeSnapshotError("replay-not-found", `Node replay ${replayId} not found in run ${run.id}`, { freshness: "absent" });
|
|
132
|
+
}
|
|
133
|
+
/** Snapshot one StateNode by id. Throws (fail closed) if the node is absent. */
|
|
134
|
+
function snapshotNode(run, nodeId, options = {}) {
|
|
135
|
+
const node = tryGetNode(run, nodeId);
|
|
136
|
+
if (!node) {
|
|
137
|
+
throw new NodeSnapshotError("node-absent", `Cannot snapshot: node ${nodeId} not found in run ${run.id}`, { freshness: "absent" });
|
|
138
|
+
}
|
|
139
|
+
const fingerprint = sourceFingerprint(node);
|
|
140
|
+
const snapshot = {
|
|
141
|
+
schemaVersion: 1,
|
|
142
|
+
snapshotId: `snap-${(0, state_1.safeFileName)(nodeId)}-${fingerprint.replace("sha256:", "").slice(0, 12)}`,
|
|
143
|
+
runId: run.id,
|
|
144
|
+
nodeId,
|
|
145
|
+
capturedAt: options.now || new Date().toISOString(),
|
|
146
|
+
sourceFingerprint: fingerprint,
|
|
147
|
+
body: snapshotBody(node)
|
|
148
|
+
};
|
|
149
|
+
if (options.persist !== false) {
|
|
150
|
+
(0, state_1.writeJson)(node_path_1.default.join(snapshotDir(run, nodeId), `${snapshot.snapshotId}.json`), snapshot);
|
|
151
|
+
}
|
|
152
|
+
return snapshot;
|
|
153
|
+
}
|
|
154
|
+
/** Recompute freshness from current source. valid | stale | absent. */
|
|
155
|
+
function loadNodeSnapshot(run, snapshot) {
|
|
156
|
+
const node = tryGetNode(run, snapshot.nodeId);
|
|
157
|
+
if (!node) {
|
|
158
|
+
return { snapshot, freshness: "absent", reason: `source node ${snapshot.nodeId} is gone from run ${run.id}` };
|
|
159
|
+
}
|
|
160
|
+
const missingArtifact = node.artifacts.find((artifact) => artifact.path && !node_fs_1.default.existsSync(artifact.path));
|
|
161
|
+
if (missingArtifact) {
|
|
162
|
+
return { snapshot, freshness: "absent", reason: `referenced artifact path is unreadable: ${missingArtifact.id}` };
|
|
163
|
+
}
|
|
164
|
+
if (sourceFingerprint(node) !== snapshot.sourceFingerprint) {
|
|
165
|
+
return { snapshot, freshness: "stale", reason: `source node ${snapshot.nodeId} changed since capture` };
|
|
166
|
+
}
|
|
167
|
+
return { snapshot, freshness: "valid" };
|
|
168
|
+
}
|
|
169
|
+
function sectionValue(body, section) {
|
|
170
|
+
if (section === "links")
|
|
171
|
+
return { parents: body.parents, children: body.children };
|
|
172
|
+
return body[section];
|
|
173
|
+
}
|
|
174
|
+
/** Stable, structural diff of two snapshots (same node id or two explicit ids). */
|
|
175
|
+
function diffNodeSnapshots(baseline, candidate) {
|
|
176
|
+
const sections = SNAPSHOT_SECTIONS.map((section) => {
|
|
177
|
+
const baselineValue = sectionValue(baseline.body, section);
|
|
178
|
+
const candidateValue = sectionValue(candidate.body, section);
|
|
179
|
+
const sameBytes = (0, multi_agent_eval_1.stableStringify)(baselineValue) === (0, multi_agent_eval_1.stableStringify)(candidateValue);
|
|
180
|
+
let change;
|
|
181
|
+
if (sameBytes)
|
|
182
|
+
change = "same";
|
|
183
|
+
else if (baselineValue === undefined)
|
|
184
|
+
change = "added";
|
|
185
|
+
else if (candidateValue === undefined)
|
|
186
|
+
change = "removed";
|
|
187
|
+
else
|
|
188
|
+
change = "changed";
|
|
189
|
+
const entry = { section, change };
|
|
190
|
+
if (change !== "same") {
|
|
191
|
+
entry.baseline = baselineValue;
|
|
192
|
+
entry.candidate = candidateValue;
|
|
193
|
+
}
|
|
194
|
+
return entry;
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
schemaVersion: 1,
|
|
198
|
+
runId: baseline.runId,
|
|
199
|
+
baselineSnapshotId: baseline.snapshotId,
|
|
200
|
+
candidateSnapshotId: candidate.snapshotId,
|
|
201
|
+
baselineNodeId: baseline.nodeId,
|
|
202
|
+
candidateNodeId: candidate.nodeId,
|
|
203
|
+
changed: sections.some((entry) => entry.change !== "same"),
|
|
204
|
+
sections
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Deterministically replay one node from its snapshot, fail-closed on drift.
|
|
208
|
+
* `now` is injected; the deterministic payload (body + outputFingerprint) has
|
|
209
|
+
* zero wall-clock, so two replays are byte-identical. */
|
|
210
|
+
function replayNodeSnapshot(run, snapshot, options = {}) {
|
|
211
|
+
const { freshness, reason } = loadNodeSnapshot(run, snapshot);
|
|
212
|
+
if (freshness !== "valid") {
|
|
213
|
+
throw new NodeSnapshotError(freshness === "stale" ? "snapshot-stale" : "snapshot-absent", reason || `cannot replay a ${freshness} snapshot of node ${snapshot.nodeId}`, { freshness, details: { runId: run.id, nodeId: snapshot.nodeId } });
|
|
214
|
+
}
|
|
215
|
+
const body = (0, multi_agent_eval_1.normalizeValue)(snapshot.body);
|
|
216
|
+
const outputFingerprint = (0, state_explosion_1.fingerprintStrings)([(0, multi_agent_eval_1.stableStringify)(body)]);
|
|
217
|
+
const replay = {
|
|
218
|
+
schemaVersion: 1,
|
|
219
|
+
replayId: `replay-${snapshot.snapshotId}-${outputFingerprint.replace("sha256:", "").slice(0, 8)}`,
|
|
220
|
+
runId: run.id,
|
|
221
|
+
nodeId: snapshot.nodeId,
|
|
222
|
+
snapshotId: snapshot.snapshotId,
|
|
223
|
+
replayedAt: options.now || new Date().toISOString(),
|
|
224
|
+
freshness: "valid",
|
|
225
|
+
contractValidated: Boolean(snapshot.body.contractId),
|
|
226
|
+
outputFingerprint,
|
|
227
|
+
body
|
|
228
|
+
};
|
|
229
|
+
if (options.persist !== false) {
|
|
230
|
+
(0, state_1.writeJson)(node_path_1.default.join(snapshotDir(run, snapshot.nodeId), "replays", `${replay.replayId}.json`), replay);
|
|
231
|
+
}
|
|
232
|
+
return replay;
|
|
233
|
+
}
|
|
234
|
+
/** Compare a replay to a fresh snapshot of the source node; pass = byte-identical
|
|
235
|
+
* normalized body. Findings reuse the eval harness severity/category shape. */
|
|
236
|
+
function verifyNodeReplay(run, replay, options = {}) {
|
|
237
|
+
const fresh = tryGetNode(run, replay.nodeId);
|
|
238
|
+
if (!fresh) {
|
|
239
|
+
return {
|
|
240
|
+
schemaVersion: 1,
|
|
241
|
+
runId: run.id,
|
|
242
|
+
nodeId: replay.nodeId,
|
|
243
|
+
replayId: replay.replayId,
|
|
244
|
+
pass: false,
|
|
245
|
+
freshness: "absent",
|
|
246
|
+
findings: [{ id: "source-absent", severity: "error", category: "source", reason: `source node ${replay.nodeId} is gone` }]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const freshSnapshot = snapshotNode(run, replay.nodeId, { now: options.now, persist: false });
|
|
250
|
+
const diff = diffNodeSnapshots(freshSnapshot, { ...freshSnapshot, body: replay.body, snapshotId: replay.snapshotId });
|
|
251
|
+
const findings = diff.sections
|
|
252
|
+
.filter((section) => section.change !== "same")
|
|
253
|
+
.map((section) => ({
|
|
254
|
+
id: `drift:${section.section}`,
|
|
255
|
+
severity: "error",
|
|
256
|
+
category: section.section,
|
|
257
|
+
reason: `replay diverged from source in ${section.section}`,
|
|
258
|
+
baselineRef: replay.snapshotId,
|
|
259
|
+
replayRef: replay.replayId
|
|
260
|
+
}));
|
|
261
|
+
return {
|
|
262
|
+
schemaVersion: 1,
|
|
263
|
+
runId: run.id,
|
|
264
|
+
nodeId: replay.nodeId,
|
|
265
|
+
replayId: replay.replayId,
|
|
266
|
+
pass: findings.length === 0,
|
|
267
|
+
freshness: "valid",
|
|
268
|
+
findings
|
|
269
|
+
};
|
|
270
|
+
}
|