cool-workflow 0.1.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/mcp.json +10 -0
  3. package/.codex-plugin/plugin.json +38 -0
  4. package/.mcp.json +10 -0
  5. package/LICENSE +24 -0
  6. package/README.md +638 -0
  7. package/apps/architecture-review/app.json +51 -0
  8. package/apps/architecture-review/workflow.js +116 -0
  9. package/apps/end-to-end-golden-path/app.json +30 -0
  10. package/apps/end-to-end-golden-path/workflow.js +33 -0
  11. package/apps/pr-review-fix-ci/app.json +59 -0
  12. package/apps/pr-review-fix-ci/workflow.js +90 -0
  13. package/apps/release-cut/app.json +54 -0
  14. package/apps/release-cut/workflow.js +82 -0
  15. package/apps/research-synthesis/app.json +50 -0
  16. package/apps/research-synthesis/workflow.js +76 -0
  17. package/apps/workflow-app-framework-demo/app.json +29 -0
  18. package/apps/workflow-app-framework-demo/workflow.js +44 -0
  19. package/dist/agent-config.js +223 -0
  20. package/dist/candidate-scoring.js +715 -0
  21. package/dist/capability-core.js +630 -0
  22. package/dist/capability-dispatcher.js +86 -0
  23. package/dist/capability-registry.js +523 -0
  24. package/dist/cli.js +1276 -0
  25. package/dist/collaboration.js +727 -0
  26. package/dist/commit.js +570 -0
  27. package/dist/contract-migration.js +234 -0
  28. package/dist/coordinator.js +1163 -0
  29. package/dist/daemon.js +44 -0
  30. package/dist/dispatch.js +201 -0
  31. package/dist/drive.js +503 -0
  32. package/dist/error-feedback.js +415 -0
  33. package/dist/evidence-grounding.js +179 -0
  34. package/dist/evidence-reasoning.js +733 -0
  35. package/dist/execution-backend.js +1279 -0
  36. package/dist/harness.js +61 -0
  37. package/dist/mcp-server.js +1615 -0
  38. package/dist/multi-agent-eval.js +857 -0
  39. package/dist/multi-agent-host.js +764 -0
  40. package/dist/multi-agent-operator-ux.js +537 -0
  41. package/dist/multi-agent-trust.js +366 -0
  42. package/dist/multi-agent.js +1173 -0
  43. package/dist/node-snapshot.js +270 -0
  44. package/dist/observability.js +922 -0
  45. package/dist/operator-ux.js +971 -0
  46. package/dist/orchestrator/audit-operations.js +182 -0
  47. package/dist/orchestrator/candidate-operations.js +117 -0
  48. package/dist/orchestrator/cli-options.js +288 -0
  49. package/dist/orchestrator/collaboration-operations.js +86 -0
  50. package/dist/orchestrator/feedback-operations.js +81 -0
  51. package/dist/orchestrator/host-operations.js +78 -0
  52. package/dist/orchestrator/lifecycle-operations.js +462 -0
  53. package/dist/orchestrator/migration-operations.js +44 -0
  54. package/dist/orchestrator/multi-agent-operations.js +362 -0
  55. package/dist/orchestrator/report.js +369 -0
  56. package/dist/orchestrator/topology-operations.js +84 -0
  57. package/dist/orchestrator.js +874 -0
  58. package/dist/pipeline-contract.js +92 -0
  59. package/dist/pipeline-runner.js +285 -0
  60. package/dist/reclamation.js +882 -0
  61. package/dist/result-normalize.js +194 -0
  62. package/dist/run-export.js +64 -0
  63. package/dist/run-registry.js +1347 -0
  64. package/dist/run-state-schema.js +67 -0
  65. package/dist/sandbox-profile.js +471 -0
  66. package/dist/scheduler.js +266 -0
  67. package/dist/scheduling.js +184 -0
  68. package/dist/schema-validate.js +98 -0
  69. package/dist/state-explosion.js +1213 -0
  70. package/dist/state-migrations.js +463 -0
  71. package/dist/state-node.js +301 -0
  72. package/dist/state.js +308 -0
  73. package/dist/telemetry-attestation.js +156 -0
  74. package/dist/telemetry-ledger.js +145 -0
  75. package/dist/topology.js +527 -0
  76. package/dist/triggers.js +159 -0
  77. package/dist/trust-audit.js +475 -0
  78. package/dist/types/blackboard.js +2 -0
  79. package/dist/types/boundary.js +29 -0
  80. package/dist/types/candidate.js +2 -0
  81. package/dist/types/collaboration.js +2 -0
  82. package/dist/types/core.js +2 -0
  83. package/dist/types/drive.js +10 -0
  84. package/dist/types/error-feedback.js +2 -0
  85. package/dist/types/evidence-reasoning.js +2 -0
  86. package/dist/types/execution-backend.js +2 -0
  87. package/dist/types/multi-agent.js +2 -0
  88. package/dist/types/observability.js +2 -0
  89. package/dist/types/pipeline.js +2 -0
  90. package/dist/types/reclamation.js +8 -0
  91. package/dist/types/result.js +2 -0
  92. package/dist/types/run-registry.js +2 -0
  93. package/dist/types/run.js +2 -0
  94. package/dist/types/sandbox.js +2 -0
  95. package/dist/types/schedule.js +2 -0
  96. package/dist/types/state-node.js +2 -0
  97. package/dist/types/topology.js +2 -0
  98. package/dist/types/trust.js +2 -0
  99. package/dist/types/workbench.js +2 -0
  100. package/dist/types/worker.js +2 -0
  101. package/dist/types/workflow-app.js +2 -0
  102. package/dist/types.js +43 -0
  103. package/dist/verifier-registry.js +46 -0
  104. package/dist/verifier.js +78 -0
  105. package/dist/version.js +8 -0
  106. package/dist/workbench-host.js +172 -0
  107. package/dist/workbench.js +190 -0
  108. package/dist/worker-isolation.js +1028 -0
  109. package/dist/workflow-api.js +98 -0
  110. package/dist/workflow-app-framework.js +626 -0
  111. package/docs/agent-delegation-drive.7.md +190 -0
  112. package/docs/agent-framework.md +176 -0
  113. package/docs/candidate-scoring.7.md +106 -0
  114. package/docs/canonical-workflow-apps.7.md +137 -0
  115. package/docs/capability-topology-registry.7.md +168 -0
  116. package/docs/cli-mcp-parity.7.md +373 -0
  117. package/docs/contract-migration-tooling.7.md +123 -0
  118. package/docs/control-plane-scheduling.7.md +110 -0
  119. package/docs/coordinator-blackboard.7.md +183 -0
  120. package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
  121. package/docs/dogfood-one-real-repo.7.md +168 -0
  122. package/docs/durable-state-and-locking.7.md +107 -0
  123. package/docs/end-to-end-golden-path.7.md +117 -0
  124. package/docs/error-feedback.7.md +153 -0
  125. package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
  126. package/docs/execution-backends.7.md +300 -0
  127. package/docs/getting-started.md +99 -0
  128. package/docs/index.md +41 -0
  129. package/docs/mcp-app-surface.7.md +235 -0
  130. package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
  131. package/docs/multi-agent-eval-replay-harness.7.md +302 -0
  132. package/docs/multi-agent-operator-ux.7.md +314 -0
  133. package/docs/multi-agent-runtime-core.7.md +231 -0
  134. package/docs/multi-agent-topologies.7.md +103 -0
  135. package/docs/multi-agent-trust-policy-audit.7.md +154 -0
  136. package/docs/node-snapshot-diff-replay.7.md +135 -0
  137. package/docs/observability-cost-accounting.7.md +194 -0
  138. package/docs/operator-ux.7.md +180 -0
  139. package/docs/pipeline-runner.7.md +136 -0
  140. package/docs/project-index.md +261 -0
  141. package/docs/real-execution-backends.7.md +142 -0
  142. package/docs/release-and-migration.7.md +280 -0
  143. package/docs/release-tooling.7.md +159 -0
  144. package/docs/routines.md +48 -0
  145. package/docs/run-registry-control-plane.7.md +312 -0
  146. package/docs/run-retention-reclamation.7.md +191 -0
  147. package/docs/sandbox-profiles.7.md +137 -0
  148. package/docs/scheduled-tasks.md +80 -0
  149. package/docs/security-trust-hardening.7.md +117 -0
  150. package/docs/state-explosion-management.7.md +264 -0
  151. package/docs/state-node.7.md +96 -0
  152. package/docs/team-collaboration.7.md +207 -0
  153. package/docs/unix-principles.md +192 -0
  154. package/docs/verifier-gated-commit.7.md +140 -0
  155. package/docs/web-desktop-workbench.7.md +215 -0
  156. package/docs/worker-isolation.7.md +167 -0
  157. package/docs/workflow-app-framework.7.md +274 -0
  158. package/manifest/README.md +43 -0
  159. package/manifest/plugin.manifest.json +316 -0
  160. package/manifest/pricing.policy.json +14 -0
  161. package/package.json +79 -0
  162. package/scripts/agents/claude-p-agent.js +104 -0
  163. package/scripts/agents/claude-p-agent.sh +9 -0
  164. package/scripts/agents/cw-attest-keygen.js +55 -0
  165. package/scripts/agents/cw-attest-wrap.js +143 -0
  166. package/scripts/block-unapproved-tag.sh +39 -0
  167. package/scripts/bump-version.js +249 -0
  168. package/scripts/canonical-apps.js +171 -0
  169. package/scripts/cw.js +4 -0
  170. package/scripts/dist-drift-check.js +79 -0
  171. package/scripts/dogfood-architecture-review.js +237 -0
  172. package/scripts/dogfood-release.js +624 -0
  173. package/scripts/forward-ref-docs.js +73 -0
  174. package/scripts/gen-manifests.js +232 -0
  175. package/scripts/golden-path.js +300 -0
  176. package/scripts/mcp-server.js +4 -0
  177. package/scripts/new-feature.js +121 -0
  178. package/scripts/parity-check.js +213 -0
  179. package/scripts/release-check.js +118 -0
  180. package/scripts/release-flow.js +272 -0
  181. package/scripts/release-gate.sh +85 -0
  182. package/scripts/sync-project-index.js +387 -0
  183. package/scripts/validate-run-state-schema.js +126 -0
  184. package/scripts/verify-container-selfref.js +64 -0
  185. package/scripts/version-sync-check.js +237 -0
  186. package/skills/cool-workflow/SKILL.md +162 -0
  187. package/skills/cool-workflow/references/commands.md +282 -0
  188. package/tsconfig.json +16 -0
  189. package/ui/workbench/app.css +76 -0
  190. package/ui/workbench/app.js +159 -0
  191. package/ui/workbench/index.html +32 -0
  192. package/workflows/architecture-review.workflow.js +84 -0
  193. package/workflows/research-synthesis.workflow.js +47 -0
@@ -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
+ }