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,156 @@
1
+ "use strict";
2
+ // Telemetry attestation (Track 1) — make "auditable" a FACT, not a claim.
3
+ //
4
+ // The agent self-reports its token usage on stdout (parseAgentReport). A control
5
+ // plane that records that number verbatim is recording a CLAIM: a tampered or
6
+ // fabricated usage survives untouched. This module is the verify gate that turns
7
+ // the claim into an attestation:
8
+ //
9
+ // - The EXECUTOR signs a canonical payload binding {usage, runId, taskId,
10
+ // promptDigest} with its private key (ed25519). The binding context is what
11
+ // stops one task's signature being replayed onto another.
12
+ // - CW VERIFIES that signature against an operator-provisioned PUBLIC key.
13
+ // CW holds ONLY the public key — it can verify, but can neither forge a
14
+ // signature nor (the red line) call a model to measure usage itself. A
15
+ // third-party auditor with the same public key can re-verify independently.
16
+ //
17
+ // HONEST CEILING [load-bearing]: a signature proves the usage came from the
18
+ // keyholder and was not tampered in transit — NON-REPUDIABLE ATTRIBUTION, not
19
+ // ground-truth measurement. A dishonest keyholder can still sign a lie, but the
20
+ // lie is now cryptographically bound to its signer. That is strictly stronger
21
+ // than self-signed sha256 (which any party can recompute) and is the most a
22
+ // delegating control-plane can claim without measuring (which it must not).
23
+ //
24
+ // Default is honest: no signature ⇒ `unattested`, no usage ⇒ `absent`. Usage is
25
+ // NEVER silently recorded as trusted.
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.stableStringify = stableStringify;
31
+ exports.canonicalTelemetryPayload = canonicalTelemetryPayload;
32
+ exports.normalizeReportedUsage = normalizeReportedUsage;
33
+ exports.verifyTelemetryAttestation = verifyTelemetryAttestation;
34
+ exports.resolveTrustPublicKey = resolveTrustPublicKey;
35
+ exports.signTelemetry = signTelemetry;
36
+ const node_crypto_1 = __importDefault(require("node:crypto"));
37
+ /** Deterministic, key-sorted JSON so signer and verifier hash byte-identical
38
+ * input regardless of object key order. */
39
+ function stableStringify(value) {
40
+ if (value === null || typeof value !== "object")
41
+ return JSON.stringify(value) ?? "null";
42
+ if (Array.isArray(value))
43
+ return `[${value.map(stableStringify).join(",")}]`;
44
+ const entries = Object.keys(value)
45
+ .sort()
46
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
47
+ return `{${entries.join(",")}}`;
48
+ }
49
+ /** The exact bytes the executor signs and CW verifies. */
50
+ function canonicalTelemetryPayload(usage, ctx) {
51
+ return stableStringify({
52
+ usage: usage ?? null,
53
+ runId: ctx.runId,
54
+ taskId: ctx.taskId,
55
+ promptDigest: ctx.promptDigest
56
+ });
57
+ }
58
+ function messageOf(error) {
59
+ return error instanceof Error ? error.message : String(error);
60
+ }
61
+ function numberAt(usage, ...keys) {
62
+ for (const key of keys) {
63
+ const value = usage[key];
64
+ const num = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
65
+ if (Number.isFinite(num))
66
+ return num;
67
+ }
68
+ return undefined;
69
+ }
70
+ /** Map the agent's free-form usage report onto UsageRecord token buckets, tolerating
71
+ * snake_case / camelCase variants (input_tokens vs inputTokens, etc.). CW never
72
+ * invents a number — an unreported bucket stays undefined, never zero. */
73
+ function normalizeReportedUsage(usage) {
74
+ if (!usage)
75
+ return {};
76
+ return {
77
+ inputTokens: numberAt(usage, "inputTokens", "input_tokens", "promptTokens", "prompt_tokens"),
78
+ outputTokens: numberAt(usage, "outputTokens", "output_tokens", "completionTokens", "completion_tokens"),
79
+ cacheReadTokens: numberAt(usage, "cacheReadTokens", "cache_read_tokens", "cacheReadInputTokens", "cache_read_input_tokens"),
80
+ cacheWriteTokens: numberAt(usage, "cacheWriteTokens", "cache_write_tokens", "cacheCreationInputTokens", "cache_creation_input_tokens"),
81
+ totalTokens: numberAt(usage, "totalTokens", "total_tokens")
82
+ };
83
+ }
84
+ /** Verify the agent's signed usage against the operator-provisioned public key.
85
+ * Pure + deterministic (no clock, no env). Returns a status, never throws. */
86
+ function verifyTelemetryAttestation(usage, signatureB64, trustPublicKeyPem, ctx) {
87
+ if (!usage || Object.keys(usage).length === 0) {
88
+ return { status: "absent", reason: "agent reported no usage" };
89
+ }
90
+ if (!signatureB64) {
91
+ return { status: "unattested", reason: "reported usage carries no signature" };
92
+ }
93
+ if (!trustPublicKeyPem) {
94
+ return { status: "unattested", reason: "no trust key configured (set agent attestPublicKey / CW_AGENT_ATTEST_PUBKEY)" };
95
+ }
96
+ let publicKey;
97
+ try {
98
+ publicKey = node_crypto_1.default.createPublicKey(trustPublicKeyPem);
99
+ }
100
+ catch (error) {
101
+ return { status: "unattested", reason: `trust key unreadable: ${messageOf(error)}` };
102
+ }
103
+ let signature;
104
+ try {
105
+ signature = Buffer.from(signatureB64, "base64");
106
+ if (signature.length === 0)
107
+ return { status: "unattested", reason: "signature is empty" };
108
+ }
109
+ catch {
110
+ return { status: "unattested", reason: "signature is not valid base64" };
111
+ }
112
+ const payload = Buffer.from(canonicalTelemetryPayload(usage, ctx), "utf8");
113
+ let ok = false;
114
+ try {
115
+ ok = node_crypto_1.default.verify(null, payload, publicKey, signature);
116
+ }
117
+ catch (error) {
118
+ return { status: "unattested", reason: `verification error: ${messageOf(error)}` };
119
+ }
120
+ return ok
121
+ ? { status: "attested", algorithm: "ed25519" }
122
+ : { status: "unattested", reason: "signature does not match reported usage (tampered, replayed, or wrong key)" };
123
+ }
124
+ /** Resolve a trust key from a config value that is EITHER an inline PEM or a path
125
+ * to a `.pem` file. Returns undefined when absent/unreadable (⇒ `unattested`,
126
+ * never a hard throw). CW only ever loads a PUBLIC key here. */
127
+ function resolveTrustPublicKey(value) {
128
+ if (!value)
129
+ return undefined;
130
+ const trimmed = value.trim();
131
+ if (!trimmed)
132
+ return undefined;
133
+ if (trimmed.includes("BEGIN") && trimmed.includes("KEY"))
134
+ return trimmed;
135
+ try {
136
+ // Lazy require so a bundle that never resolves a key path pays no fs cost.
137
+ const fs = require("node:fs");
138
+ if (fs.existsSync(trimmed))
139
+ return fs.readFileSync(trimmed, "utf8");
140
+ }
141
+ catch {
142
+ /* fall through to undefined */
143
+ }
144
+ return undefined;
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // EXECUTOR-SIDE HELPER — the CW RUNTIME NEVER CALLS THIS.
148
+ // Provided for signing wrappers around `claude -p`/endpoints and for tests. It
149
+ // touches a private key, never a model, so it does not cross the red line; it is
150
+ // kept here only so signer and verifier share one canonicalization.
151
+ // ---------------------------------------------------------------------------
152
+ function signTelemetry(usage, privateKeyPem, ctx) {
153
+ const payload = Buffer.from(canonicalTelemetryPayload(usage, ctx), "utf8");
154
+ const key = node_crypto_1.default.createPrivateKey(privateKeyPem);
155
+ return node_crypto_1.default.sign(null, payload, key).toString("base64");
156
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ // Telemetry attestation ledger (Track 1) — make the RECORDED attestation itself
3
+ // tamper-evident, not just the in-flight signature.
4
+ //
5
+ // The executor's signature (telemetry-attestation.ts) proves the agent SAID a
6
+ // given usage. This ledger proves CW RECORDED exactly that and nobody edited the
7
+ // record afterward: an append-only, hash-chained overlay (`telemetry.json`, a
8
+ // runDir peer of reclaimed.json), one entry per agent hop. Each entry chains to
9
+ // the prior via prevHash; recordHash = sha256(canonical entry sans recordHash).
10
+ // Flip a recorded verdict (`unattested`→`attested`), or edit a recorded usage
11
+ // digest, and the chain no longer recomputes — `verifyTelemetryLedger` catches it.
12
+ //
13
+ // Same discipline as reclamation.ts's tombstone chain: APPEND-ONLY (never rewrite
14
+ // a prior entry), DURABLE (temp→fsync→rename via writeJson), and verify recomputes
15
+ // every hash independently — it never trusts the stored value.
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.TELEMETRY_LEDGER_SCHEMA_VERSION = void 0;
21
+ exports.telemetryLedgerPath = telemetryLedgerPath;
22
+ exports.loadTelemetryLedger = loadTelemetryLedger;
23
+ exports.genesisPrevHash = genesisPrevHash;
24
+ exports.computeRecordHash = computeRecordHash;
25
+ exports.reportedUsageDigest = reportedUsageDigest;
26
+ exports.appendTelemetryAttestation = appendTelemetryAttestation;
27
+ exports.verifyTelemetryLedger = verifyTelemetryLedger;
28
+ const node_fs_1 = __importDefault(require("node:fs"));
29
+ const node_path_1 = __importDefault(require("node:path"));
30
+ const state_1 = require("./state");
31
+ const execution_backend_1 = require("./execution-backend");
32
+ const telemetry_attestation_1 = require("./telemetry-attestation");
33
+ exports.TELEMETRY_LEDGER_SCHEMA_VERSION = 1;
34
+ function telemetryLedgerPath(run) {
35
+ return node_path_1.default.join(run.paths.runDir, "telemetry.json");
36
+ }
37
+ /** Load the ledger; fail closed to an empty chain on a malformed overlay (a
38
+ * corrupt file must never brick the run — and an empty chain verifies as such). */
39
+ function loadTelemetryLedger(run) {
40
+ const file = telemetryLedgerPath(run);
41
+ if (!node_fs_1.default.existsSync(file))
42
+ return { schemaVersion: 1, runId: run.id, records: [] };
43
+ try {
44
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
45
+ return { schemaVersion: 1, runId: run.id, records: Array.isArray(parsed.records) ? parsed.records : [] };
46
+ }
47
+ catch {
48
+ return { schemaVersion: 1, runId: run.id, records: [] };
49
+ }
50
+ }
51
+ /** genesis prevHash for a run's chain (no prior record). */
52
+ function genesisPrevHash(runId) {
53
+ return (0, execution_backend_1.sha256)(`cw-telemetry-ledger:${runId}`);
54
+ }
55
+ /** The canonical bytes a recordHash binds — every field except recordHash itself.
56
+ * Recomputed independently by verifyTelemetryLedger. */
57
+ function recordHashInput(record) {
58
+ return (0, telemetry_attestation_1.stableStringify)({
59
+ schemaVersion: record.schemaVersion,
60
+ runId: record.runId,
61
+ recordId: record.recordId,
62
+ recordedAt: record.recordedAt,
63
+ workerId: record.workerId,
64
+ taskId: record.taskId,
65
+ promptDigest: record.promptDigest,
66
+ reportedUsageDigest: record.reportedUsageDigest,
67
+ usageSignature: record.usageSignature || null,
68
+ attestation: record.attestation,
69
+ attestationReason: record.attestationReason || null,
70
+ prevHash: record.prevHash
71
+ });
72
+ }
73
+ function computeRecordHash(record) {
74
+ return (0, execution_backend_1.sha256)(recordHashInput(record));
75
+ }
76
+ /** sha256 of the canonical reported usage (compact, chainable). Absent usage gets
77
+ * the digest of `null`, so "the agent reported nothing" is itself bound. */
78
+ function reportedUsageDigest(usage) {
79
+ return (0, execution_backend_1.sha256)((0, telemetry_attestation_1.stableStringify)(usage ?? null));
80
+ }
81
+ let recordCounter = 0;
82
+ function recordId(now) {
83
+ recordCounter += 1;
84
+ const stamp = now.replace(/[-:.TZ]/g, "").slice(0, 14);
85
+ return `tel-${stamp}-${String(recordCounter).padStart(3, "0")}`;
86
+ }
87
+ /** Append one attestation record DURABLY to the append-only chain, linking it to
88
+ * the prior record (or genesis). Returns the committed record. */
89
+ function appendTelemetryAttestation(run, input) {
90
+ const ledger = loadTelemetryLedger(run);
91
+ const now = input.now || new Date().toISOString();
92
+ const prevHash = ledger.records.length ? ledger.records[ledger.records.length - 1].recordHash : genesisPrevHash(run.id);
93
+ const base = {
94
+ schemaVersion: 1,
95
+ runId: run.id,
96
+ recordId: recordId(now),
97
+ recordedAt: now,
98
+ workerId: input.workerId,
99
+ taskId: input.taskId,
100
+ promptDigest: input.promptDigest,
101
+ reportedUsageDigest: reportedUsageDigest(input.reportedUsage),
102
+ usageSignature: input.usageSignature,
103
+ attestation: input.attestation,
104
+ attestationReason: input.attestationReason,
105
+ prevHash
106
+ };
107
+ const record = { ...base, recordHash: computeRecordHash(base) };
108
+ ledger.records.push(record);
109
+ (0, state_1.writeJson)(telemetryLedgerPath(run), ledger, { durable: true });
110
+ return record;
111
+ }
112
+ /** Re-prove the whole telemetry chain for a run: prevHash linkage + per-record
113
+ * hash recompute. Recomputes every hash independently — never trusts the stored
114
+ * value — so an edited record/verdict is detected. An empty ledger verifies as
115
+ * present:false (nothing to prove), NOT a failure. */
116
+ function verifyTelemetryLedger(run) {
117
+ const records = loadTelemetryLedger(run).records;
118
+ const checks = [];
119
+ const tally = { attested: 0, unattested: 0, absent: 0 };
120
+ for (const record of records)
121
+ tally[record.attestation] += 1;
122
+ if (!records.length) {
123
+ return { present: false, verified: true, records, checks, ...tally };
124
+ }
125
+ // (a) chain linkage: genesis = sha256("cw-telemetry-ledger:"+runId).
126
+ let chainOk = true;
127
+ for (let i = 0; i < records.length; i++) {
128
+ const expectedPrev = i === 0 ? genesisPrevHash(run.id) : records[i - 1].recordHash;
129
+ const pass = records[i].prevHash === expectedPrev;
130
+ if (!pass)
131
+ chainOk = false;
132
+ checks.push({ name: `chain-link[${i}]`, pass, code: pass ? undefined : "telemetry-chain-broken" });
133
+ }
134
+ // (b) per-record independent hash recompute (digest integrity).
135
+ let digestsOk = true;
136
+ for (let i = 0; i < records.length; i++) {
137
+ const { recordHash, ...rest } = records[i];
138
+ const recomputed = computeRecordHash(rest);
139
+ const pass = recomputed === recordHash;
140
+ if (!pass)
141
+ digestsOk = false;
142
+ checks.push({ name: `record-hash[${i}]`, pass, code: pass ? undefined : "telemetry-digest-mismatch" });
143
+ }
144
+ return { present: true, verified: chainOk && digestsOk, records, checks, ...tally };
145
+ }