cool-workflow 0.1.98 → 0.2.1

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 (315) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +18 -7
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/cli/dispatch.js +236 -0
  12. package/dist/cli/entry.js +120 -0
  13. package/dist/cli/io.js +21 -4
  14. package/dist/cli/parseargv.js +157 -0
  15. package/dist/cli.js +6 -33
  16. package/dist/core/capability-table.js +3518 -0
  17. package/dist/core/format/help.js +314 -0
  18. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  19. package/dist/core/hash.js +137 -0
  20. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  21. package/dist/core/multi-agent/collaboration.js +481 -0
  22. package/dist/core/multi-agent/coordinator.js +515 -0
  23. package/dist/core/multi-agent/eval-replay.js +306 -0
  24. package/dist/core/multi-agent/runtime.js +929 -0
  25. package/dist/core/multi-agent/topology.js +298 -0
  26. package/dist/core/multi-agent/trust-policy.js +197 -0
  27. package/dist/core/pipeline/commit-gate.js +320 -0
  28. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  29. package/dist/core/pipeline/dispatch.js +103 -0
  30. package/dist/core/pipeline/drive-decide.js +227 -0
  31. package/dist/core/pipeline/error-feedback.js +161 -0
  32. package/dist/core/pipeline/loop-expansion.js +124 -0
  33. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  34. package/dist/core/pipeline/runner.js +230 -0
  35. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  36. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  37. package/dist/core/state/node-projection.js +95 -0
  38. package/dist/core/state/node-snapshot.js +230 -0
  39. package/dist/core/state/run-paths.js +93 -0
  40. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  41. package/dist/core/state/schema.js +50 -0
  42. package/dist/core/state/state-explosion/digest.js +243 -0
  43. package/dist/core/state/state-explosion/graph.js +527 -0
  44. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  45. package/dist/core/state/state-explosion/report.js +187 -0
  46. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  47. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  48. package/dist/core/state/types.js +19 -0
  49. package/dist/{validation.js → core/state/validation.js} +64 -131
  50. package/dist/core/trust/evidence-grounding.js +134 -0
  51. package/dist/core/trust/ledger.js +199 -0
  52. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  53. package/dist/core/trust/telemetry-ledger.js +127 -0
  54. package/dist/core/types.js +20 -0
  55. package/dist/core/version.js +16 -0
  56. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  57. package/dist/mcp/dispatch.js +104 -0
  58. package/dist/mcp/server.js +144 -0
  59. package/dist/mcp-server.js +6 -84
  60. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  61. package/dist/shell/app-run-cli.js +88 -0
  62. package/dist/shell/audit-cli.js +259 -0
  63. package/dist/shell/audit-provenance.js +83 -0
  64. package/dist/shell/candidate-scoring-io.js +459 -0
  65. package/dist/shell/collaboration-io.js +264 -0
  66. package/dist/shell/commit-summary.js +104 -0
  67. package/dist/shell/commit.js +290 -0
  68. package/dist/shell/coordinator-io.js +476 -0
  69. package/dist/shell/demo-cli.js +19 -0
  70. package/dist/shell/dispatch.js +162 -0
  71. package/dist/shell/doctor.js +292 -0
  72. package/dist/shell/drive.js +885 -0
  73. package/dist/shell/error-feedback-io.js +305 -0
  74. package/dist/shell/eval-io.js +473 -0
  75. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  76. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  77. package/dist/shell/exec-backend-cli.js +88 -0
  78. package/dist/shell/execution-backend/agent.js +507 -0
  79. package/dist/shell/execution-backend/ci.js +15 -0
  80. package/dist/shell/execution-backend/container.js +69 -0
  81. package/dist/shell/execution-backend/envelopes.js +55 -0
  82. package/dist/shell/execution-backend/local.js +113 -0
  83. package/dist/shell/execution-backend/probes.js +175 -0
  84. package/dist/shell/execution-backend/registry.js +402 -0
  85. package/dist/shell/execution-backend/remote.js +128 -0
  86. package/dist/shell/execution-backend/types.js +11 -0
  87. package/dist/shell/feedback-cli.js +81 -0
  88. package/dist/shell/feedback-operations.js +48 -0
  89. package/dist/shell/fs-atomic.js +276 -0
  90. package/dist/shell/harness.js +98 -0
  91. package/dist/shell/ledger-cli.js +212 -0
  92. package/dist/shell/ledger-io.js +169 -0
  93. package/dist/shell/man-cli.js +89 -0
  94. package/dist/shell/metrics-cli.js +100 -0
  95. package/dist/shell/multi-agent-cli.js +1002 -0
  96. package/dist/shell/multi-agent-host.js +563 -0
  97. package/dist/shell/multi-agent-io.js +387 -0
  98. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  99. package/dist/shell/node-store.js +124 -0
  100. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  101. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  102. package/dist/{observability.js → shell/observability.js} +159 -332
  103. package/dist/{onramp.js → shell/onramp.js} +11 -0
  104. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  105. package/dist/shell/operator-ux.js +431 -0
  106. package/dist/shell/orchestrator.js +231 -0
  107. package/dist/shell/pipeline-cli.js +667 -0
  108. package/dist/shell/pipeline.js +217 -0
  109. package/dist/shell/reclamation-io.js +1366 -0
  110. package/dist/shell/registry-cli.js +344 -0
  111. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  112. package/dist/shell/report-cli.js +101 -0
  113. package/dist/shell/report-view-cli.js +117 -0
  114. package/dist/{orchestrator → shell}/report.js +289 -282
  115. package/dist/shell/reporter.js +62 -0
  116. package/dist/shell/run-export-cli.js +106 -0
  117. package/dist/shell/run-export.js +680 -0
  118. package/dist/shell/run-registry-io.js +1014 -0
  119. package/dist/shell/run-store.js +164 -0
  120. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  121. package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
  122. package/dist/shell/scheduling-io.js +321 -0
  123. package/dist/shell/state-cli.js +181 -0
  124. package/dist/shell/state-explosion-cli.js +197 -0
  125. package/dist/shell/telemetry-cli.js +85 -0
  126. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  127. package/dist/shell/telemetry-ledger-io.js +132 -0
  128. package/dist/{term.js → shell/term.js} +36 -46
  129. package/dist/shell/topology-io.js +361 -0
  130. package/dist/shell/trust-audit.js +472 -0
  131. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  132. package/dist/shell/verifier.js +48 -0
  133. package/dist/shell/workbench-host.js +258 -0
  134. package/dist/shell/workbench-text.js +18 -0
  135. package/dist/shell/workbench.js +170 -0
  136. package/dist/shell/worker-cli.js +124 -0
  137. package/dist/shell/worker-isolation.js +852 -0
  138. package/dist/shell/workflow-app-loader.js +650 -0
  139. package/docs/agent-delegation-drive.7.md +4 -0
  140. package/docs/cli-mcp-parity.7.md +282 -219
  141. package/docs/contract-migration-tooling.7.md +4 -0
  142. package/docs/control-plane-scheduling.7.md +4 -0
  143. package/docs/durable-state-and-locking.7.md +4 -0
  144. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  145. package/docs/execution-backends.7.md +4 -0
  146. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  147. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  148. package/docs/multi-agent-operator-ux.7.md +4 -0
  149. package/docs/node-snapshot-diff-replay.7.md +4 -0
  150. package/docs/observability-cost-accounting.7.md +6 -1
  151. package/docs/project-index.md +137 -71
  152. package/docs/real-execution-backends.7.md +4 -0
  153. package/docs/release-and-migration.7.md +4 -0
  154. package/docs/release-tooling.7.md +4 -0
  155. package/docs/remote-source-review.7.md +4 -4
  156. package/docs/report-verifiable-bundle.7.md +1 -1
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/security-trust-hardening.7.md +3 -1
  160. package/docs/state-explosion-management.7.md +4 -0
  161. package/docs/team-collaboration.7.md +4 -0
  162. package/docs/web-desktop-workbench.7.md +15 -1
  163. package/manifest/plugin.manifest.json +1 -1
  164. package/manifest/source-context-profiles.json +9 -13
  165. package/package.json +1 -1
  166. package/scripts/agents/agent-adapter-core.js +22 -2
  167. package/scripts/agents/claude-p-agent.js +8 -3
  168. package/scripts/agents/cw-attest-wrap.js +2 -2
  169. package/scripts/agents/gemini-agent.js +5 -1
  170. package/scripts/agents/opencode-agent.js +5 -1
  171. package/scripts/bump-version.js +4 -3
  172. package/scripts/canonical-apps.js +4 -4
  173. package/scripts/dogfood-architecture-review.js +2 -3
  174. package/scripts/dogfood-release.js +3 -3
  175. package/scripts/gen-parity-doc.js +15 -2
  176. package/scripts/golden-path.js +4 -4
  177. package/scripts/onramp-check.js +1 -1
  178. package/scripts/parity-check.js +38 -21
  179. package/scripts/release-flow.js +2 -2
  180. package/scripts/sync-project-index.js +51 -27
  181. package/scripts/validate-run-state-schema.js +2 -2
  182. package/scripts/version-sync-check.js +30 -30
  183. package/dist/candidate-scoring.js +0 -729
  184. package/dist/capability-core.js +0 -1189
  185. package/dist/capability-registry.js +0 -885
  186. package/dist/cli/command-surface.js +0 -494
  187. package/dist/cli/format.js +0 -56
  188. package/dist/cli/handlers/audit.js +0 -82
  189. package/dist/cli/handlers/blackboard.js +0 -81
  190. package/dist/cli/handlers/candidate.js +0 -40
  191. package/dist/cli/handlers/clones.js +0 -34
  192. package/dist/cli/handlers/collaboration.js +0 -61
  193. package/dist/cli/handlers/eval.js +0 -40
  194. package/dist/cli/handlers/ledger.js +0 -169
  195. package/dist/cli/handlers/maintenance.js +0 -107
  196. package/dist/cli/handlers/multi-agent.js +0 -165
  197. package/dist/cli/handlers/node.js +0 -41
  198. package/dist/cli/handlers/operational.js +0 -155
  199. package/dist/cli/handlers/operator.js +0 -146
  200. package/dist/cli/handlers/registry.js +0 -68
  201. package/dist/cli/handlers/run.js +0 -153
  202. package/dist/cli/handlers/scheduling.js +0 -132
  203. package/dist/cli/handlers/workbench.js +0 -41
  204. package/dist/cli/handlers/worker.js +0 -45
  205. package/dist/cli/run-summary.js +0 -45
  206. package/dist/clones.js +0 -162
  207. package/dist/collaboration.js +0 -726
  208. package/dist/commit.js +0 -592
  209. package/dist/compare.js +0 -18
  210. package/dist/coordinator/classify.js +0 -45
  211. package/dist/coordinator/paths.js +0 -42
  212. package/dist/coordinator/util.js +0 -126
  213. package/dist/coordinator.js +0 -990
  214. package/dist/daemon.js +0 -44
  215. package/dist/dispatch.js +0 -250
  216. package/dist/doctor.js +0 -212
  217. package/dist/drive.js +0 -864
  218. package/dist/error-feedback.js +0 -419
  219. package/dist/evidence-grounding.js +0 -184
  220. package/dist/execution-backend/agent.js +0 -354
  221. package/dist/execution-backend/probes.js +0 -112
  222. package/dist/execution-backend/util.js +0 -47
  223. package/dist/execution-backend.js +0 -961
  224. package/dist/gates.js +0 -48
  225. package/dist/harness.js +0 -61
  226. package/dist/ledger.js +0 -313
  227. package/dist/loop-expansion.js +0 -60
  228. package/dist/mcp/tool-call.js +0 -470
  229. package/dist/mcp/tool-definitions.js +0 -1066
  230. package/dist/mcp-surface.js +0 -30
  231. package/dist/multi-agent/graph.js +0 -84
  232. package/dist/multi-agent/helpers.js +0 -141
  233. package/dist/multi-agent/ids.js +0 -20
  234. package/dist/multi-agent/paths.js +0 -22
  235. package/dist/multi-agent-eval/normalize.js +0 -51
  236. package/dist/multi-agent-eval.js +0 -678
  237. package/dist/multi-agent-host.js +0 -777
  238. package/dist/multi-agent.js +0 -984
  239. package/dist/node-projection.js +0 -59
  240. package/dist/node-snapshot.js +0 -260
  241. package/dist/operator-ux.js +0 -631
  242. package/dist/orchestrator/app-operations.js +0 -211
  243. package/dist/orchestrator/audit-operations.js +0 -182
  244. package/dist/orchestrator/candidate-operations.js +0 -117
  245. package/dist/orchestrator/cli-options.js +0 -294
  246. package/dist/orchestrator/collaboration-operations.js +0 -86
  247. package/dist/orchestrator/feedback-operations.js +0 -81
  248. package/dist/orchestrator/host-operations.js +0 -78
  249. package/dist/orchestrator/lifecycle-operations.js +0 -650
  250. package/dist/orchestrator/migration-operations.js +0 -44
  251. package/dist/orchestrator/multi-agent-operations.js +0 -362
  252. package/dist/orchestrator/topology-operations.js +0 -84
  253. package/dist/orchestrator.js +0 -925
  254. package/dist/pipeline-runner.js +0 -285
  255. package/dist/reclamation/hash.js +0 -72
  256. package/dist/reclamation.js +0 -812
  257. package/dist/reporter.js +0 -67
  258. package/dist/run-export.js +0 -815
  259. package/dist/run-registry/derive.js +0 -175
  260. package/dist/run-registry/format.js +0 -124
  261. package/dist/run-registry/gc.js +0 -251
  262. package/dist/run-registry/policy.js +0 -16
  263. package/dist/run-registry/queue.js +0 -115
  264. package/dist/run-registry.js +0 -850
  265. package/dist/run-state-schema.js +0 -68
  266. package/dist/scheduling.js +0 -184
  267. package/dist/state-explosion.js +0 -1014
  268. package/dist/state.js +0 -367
  269. package/dist/telemetry-ledger.js +0 -196
  270. package/dist/topology.js +0 -565
  271. package/dist/triggers.js +0 -184
  272. package/dist/trust-audit.js +0 -644
  273. package/dist/types/blackboard.js +0 -2
  274. package/dist/types/candidate.js +0 -2
  275. package/dist/types/collaboration.js +0 -2
  276. package/dist/types/core.js +0 -2
  277. package/dist/types/drive.js +0 -10
  278. package/dist/types/error-feedback.js +0 -2
  279. package/dist/types/evidence-reasoning.js +0 -2
  280. package/dist/types/execution-backend.js +0 -2
  281. package/dist/types/multi-agent.js +0 -2
  282. package/dist/types/observability.js +0 -2
  283. package/dist/types/pipeline.js +0 -2
  284. package/dist/types/reclamation.js +0 -8
  285. package/dist/types/report-bundle.js +0 -6
  286. package/dist/types/result.js +0 -2
  287. package/dist/types/run-registry.js +0 -2
  288. package/dist/types/run.js +0 -2
  289. package/dist/types/sandbox.js +0 -2
  290. package/dist/types/schedule.js +0 -2
  291. package/dist/types/state-node.js +0 -2
  292. package/dist/types/topology.js +0 -2
  293. package/dist/types/trust.js +0 -2
  294. package/dist/types/workbench.js +0 -2
  295. package/dist/types/worker.js +0 -2
  296. package/dist/types/workflow-app.js +0 -2
  297. package/dist/types.js +0 -44
  298. package/dist/util/fingerprint.js +0 -19
  299. package/dist/util/fingerprint.test.js +0 -27
  300. package/dist/verifier.js +0 -78
  301. package/dist/version.js +0 -8
  302. package/dist/workbench-host.js +0 -192
  303. package/dist/workbench.js +0 -192
  304. package/dist/worker-accept/acceptance.js +0 -114
  305. package/dist/worker-accept/blackboard-fanout.js +0 -80
  306. package/dist/worker-accept/blackboard-linkage.js +0 -19
  307. package/dist/worker-accept/context.js +0 -2
  308. package/dist/worker-accept/telemetry-ledger.js +0 -126
  309. package/dist/worker-accept/validation.js +0 -77
  310. package/dist/worker-accept/verifier-completion.js +0 -73
  311. package/dist/worker-isolation/helpers.js +0 -51
  312. package/dist/worker-isolation/paths.js +0 -46
  313. package/dist/worker-isolation.js +0 -656
  314. package/dist/workflow-api.js +0 -131
  315. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -1,42 +1,64 @@
1
1
  "use strict";
2
- // Tamper-evidence demo (the one-command proof) make CW's central claim VISIBLE:
3
- // an audit record proves its own integrity, and ANYONE can re-verify it offline
4
- // with only the public key. No competitor's pipeline telemetry can do this.
2
+ // shell/telemetry-demo.ts`cw demo tamper|bundle`: the hermetic,
3
+ // offline trust proofs. Impure (fs, ephemeral keypair generation,
4
+ // tmpdir), so this lives in shell/ not core/.
5
5
  //
6
- // Fully hermetic + deterministic: generates an EPHEMERAL ed25519 keypair, builds
7
- // a REAL telemetry ledger through the production append API (appendTelemetryAttestation
8
- // + signTelemetry byte-identical to what a live attested run writes), then
9
- // demonstrates all THREE tamper-evidence layers catching a forgery:
10
- // A) LEDGER layer — flip a recorded verdict on disk (unattested -> attested, the
11
- // canonical "forge a green record" attack) -> verifyTelemetryLedger recomputes
12
- // every hash independently, so the edited record's hash mismatches AND every
13
- // record after it breaks the chain (cascade).
14
- // B) SIGNATURE layer — inflate the reported tokens but keep the original ed25519
15
- // signature -> verifyTelemetryAttestation rejects it ("signature does not match").
16
- // C) RESULT layer — edit the agent's SIGNED FINDING after signing. The executor
17
- // binds sha256(result.md) into the ed25519 payload; CW re-derives the digest
18
- // from the result file at verify time, so an edited finding no longer joins the
19
- // signature. This is the threat a usage-only signature never covered.
6
+ // MILESTONE 8. Byte-exact port of the old build's src/telemetry-demo.ts.
7
+ // Fully hermetic + deterministic: an EPHEMERAL ed25519 keypair, fixed
8
+ // hops/usages, a pinned `now`, no network, no model. Only the keypair
9
+ // itself varies per run and never leaves this module.
20
10
  //
21
- // No model, no network, no API key, no second repo — runs in a private tmpdir.
22
- var __importDefault = (this && this.__importDefault) || function (mod) {
23
- return (mod && mod.__esModule) ? mod : { "default": mod };
24
- };
11
+ // Evidence: SPEC/ledger-trust.md "`cw demo tamper`", "`cw demo bundle`";
12
+ // plugins/cool-workflow/src/telemetry-demo.ts:1-424 (byte-exact source).
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
25
46
  Object.defineProperty(exports, "__esModule", { value: true });
26
47
  exports.formatTelemetryVerify = formatTelemetryVerify;
27
48
  exports.formatTamperDemo = formatTamperDemo;
28
49
  exports.runTamperDemo = runTamperDemo;
29
50
  exports.runBundleDemo = runBundleDemo;
30
51
  exports.formatBundleDemo = formatBundleDemo;
31
- const node_crypto_1 = __importDefault(require("node:crypto"));
32
- const node_fs_1 = __importDefault(require("node:fs"));
33
- const node_os_1 = __importDefault(require("node:os"));
34
- const node_path_1 = __importDefault(require("node:path"));
35
- const telemetry_ledger_1 = require("./telemetry-ledger");
36
- const telemetry_attestation_1 = require("./telemetry-attestation");
52
+ const crypto = __importStar(require("node:crypto"));
53
+ const fs = __importStar(require("node:fs"));
54
+ const os = __importStar(require("node:os"));
55
+ const path = __importStar(require("node:path"));
56
+ const hash_1 = require("../core/hash");
57
+ const telemetry_attestation_1 = require("../core/trust/telemetry-attestation");
58
+ const telemetry_ledger_io_1 = require("./telemetry-ledger-io");
59
+ const run_paths_1 = require("../core/state/run-paths");
60
+ const run_store_1 = require("./run-store");
37
61
  const run_export_1 = require("./run-export");
38
- const state_1 = require("./state");
39
- const execution_backend_1 = require("./execution-backend");
40
62
  /** Human-facing render of `telemetry verify <run>`. */
41
63
  function formatTelemetryVerify(r) {
42
64
  const keyUnreadable = r.failedChecks.some((c) => c.code === "telemetry-pubkey-unreadable");
@@ -58,7 +80,7 @@ function formatTelemetryVerify(r) {
58
80
  const fails = r.failedChecks.length ? "\n" + r.failedChecks.map((c) => ` ✗ ${c.name} ${c.code || ""}`).join("\n") : "";
59
81
  return `telemetry verify ${r.runId}\n${head}\n${tally}${sig}${fails}`;
60
82
  }
61
- /** Human-facing render of `demo tamper` — the visible tamper-evidence proof. */
83
+ /** Human-facing render of `demo tamper`. */
62
84
  function formatTamperDemo(r) {
63
85
  const lines = [];
64
86
  lines.push(`cw demo tamper — tamper-evidence proof (hermetic, ${r.trustKey} key)`);
@@ -78,214 +100,222 @@ function formatTamperDemo(r) {
78
100
  : "VERDICT: PROOF FAILED ✗ — a tamper went undetected. This is a regression in the integrity guarantee.");
79
101
  return lines.join("\n");
80
102
  }
81
- // Three hops with a deliberate mix: two signed/attested, one unattested — so the
82
- // ledger-layer tamper can forge the unattested verdict into "attested" (the exact
83
- // threat the ledger exists to catch).
103
+ // Three hops with a deliberate mix: two signed/attested, one unattested —
104
+ // so the ledger-layer tamper can forge the unattested verdict into
105
+ // "attested" (the exact threat the ledger exists to catch).
84
106
  const HOPS = [
85
- { workerId: "w-map", taskId: "map:server-api", promptDigest: (0, execution_backend_1.sha256)("map:server-api"), usage: { input_tokens: 2117, output_tokens: 1911 }, attestation: "attested" },
86
- { workerId: "w-assess", taskId: "assess:security", promptDigest: (0, execution_backend_1.sha256)("assess:security"), usage: { input_tokens: 1840, output_tokens: 1502 }, attestation: "unattested" },
87
- { workerId: "w-verdict", taskId: "verdict:synthesis", promptDigest: (0, execution_backend_1.sha256)("verdict:synthesis"), usage: { input_tokens: 980, output_tokens: 770 }, attestation: "attested" }
107
+ { workerId: "w-map", taskId: "map:server-api", promptDigest: (0, hash_1.sha256)("map:server-api"), usage: { input_tokens: 2117, output_tokens: 1911 }, attestation: "attested" },
108
+ { workerId: "w-assess", taskId: "assess:security", promptDigest: (0, hash_1.sha256)("assess:security"), usage: { input_tokens: 1840, output_tokens: 1502 }, attestation: "unattested" },
109
+ { workerId: "w-verdict", taskId: "verdict:synthesis", promptDigest: (0, hash_1.sha256)("verdict:synthesis"), usage: { input_tokens: 980, output_tokens: 770 }, attestation: "attested" },
88
110
  ];
89
111
  const DEMO_NOW = "2026-01-01T00:00:00.000Z";
90
112
  function failingChecks(checks) {
91
113
  return checks.filter((c) => !c.pass).map((c) => `${c.name}: ${c.code}`);
92
114
  }
93
- /** Run the full tamper-evidence demonstration in a private tmpdir (cleaned up
94
- * unless `keepDir` is set). Pure of clock/network; the only nondeterminism is
95
- * the ephemeral keypair, which never leaves this function. */
115
+ /** Run the full tamper-evidence demonstration in a private tmpdir
116
+ * (cleaned up unless `keepDir` is set). Deterministic except for the
117
+ * ephemeral keypair, which never leaves this function. */
96
118
  function runTamperDemo(options = {}) {
97
- const runDir = options.dir || node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "cw-tamper-demo-"));
98
- node_fs_1.default.mkdirSync(runDir, { recursive: true });
119
+ const runDir = options.dir || fs.mkdtempSync(path.join(os.tmpdir(), "cw-tamper-demo-"));
120
+ fs.mkdirSync(runDir, { recursive: true });
99
121
  const runId = "demo-tamper-run";
100
122
  // Minimal run shape: the ledger API uses only id + paths.runDir.
101
123
  const run = { id: runId, paths: { runDir } };
102
- const { publicKey, privateKey } = node_crypto_1.default.generateKeyPairSync("ed25519");
124
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
103
125
  const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
104
126
  const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
105
- // 1. Build a REAL ledger through the production append API, signing each
106
- // attested hop's usage with the ephemeral key.
127
+ // 1. Build a REAL ledger through the production append API, signing
128
+ // each attested hop's usage with the ephemeral key.
107
129
  const signed = [];
108
130
  for (const hop of HOPS) {
109
131
  const ctx = { runId, taskId: hop.taskId, promptDigest: hop.promptDigest };
110
132
  const signature = hop.attestation === "attested" ? (0, telemetry_attestation_1.signTelemetry)(hop.usage, privateKeyPem, ctx) : undefined;
111
- (0, telemetry_ledger_1.appendTelemetryAttestation)(run, {
133
+ (0, telemetry_ledger_io_1.appendTelemetryAttestation)(run, {
112
134
  workerId: hop.workerId,
113
135
  taskId: hop.taskId,
114
136
  promptDigest: hop.promptDigest,
115
137
  reportedUsage: hop.usage,
116
138
  usageSignature: signature,
117
139
  attestation: hop.attestation,
118
- now: DEMO_NOW
140
+ now: DEMO_NOW,
119
141
  });
120
142
  signed.push({ hop, signature });
121
143
  }
122
- // 2. Baseline: the clean ledger verifies, and every signed hop's signature is valid.
123
- const clean = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
124
- const signaturesValid = signed.filter((s) => s.signature && (0, telemetry_attestation_1.verifyTelemetryAttestation)(s.hop.usage, s.signature, publicKeyPem, { runId, taskId: s.hop.taskId, promptDigest: s.hop.promptDigest }).status === "attested").length;
144
+ // 2. Baseline: the clean ledger verifies, and every signed hop's
145
+ // signature is valid.
146
+ const clean = (0, telemetry_ledger_io_1.verifyTelemetryLedger)(run);
147
+ const signaturesValid = signed.filter((s) => s.signature &&
148
+ (0, telemetry_attestation_1.verifyTelemetryAttestation)(s.hop.usage, s.signature, publicKeyPem, { runId, taskId: s.hop.taskId, promptDigest: s.hop.promptDigest }).status === "attested").length;
125
149
  const baseline = { ledgerVerified: clean.verified, signaturesValid, records: clean.records.length };
126
150
  const layers = [];
127
- // 3a. LEDGER layer — the SOPHISTICATED forgery: flip record[1]'s verdict
128
- // "unattested" -> "attested" AND recompute its recordHash to cover the edit,
129
- // so the per-record digest check passes. The chain still catches it: record[2]
130
- // was linked to the ORIGINAL record[1] hash, so chain-link[2] now breaks. This
131
- // is the point of the chain over a flat per-record hash — fixing one record's
132
- // hash cannot be hidden without rewriting every record after it too.
133
- const ledgerFile = (0, telemetry_ledger_1.telemetryLedgerPath)(run);
134
- const ledgerJson = JSON.parse(node_fs_1.default.readFileSync(ledgerFile, "utf8"));
151
+ // 3a. LEDGER layer — flip record[1]'s verdict "unattested" -> "attested"
152
+ // AND recompute its recordHash to cover the edit, so the per-record
153
+ // digest check passes. The chain still catches it: record[2] was
154
+ // linked to the ORIGINAL record[1] hash, so chain-link[2] breaks.
155
+ const ledgerFile = (0, telemetry_ledger_io_1.telemetryLedgerPath)(run);
156
+ const ledgerJson = JSON.parse(fs.readFileSync(ledgerFile, "utf8"));
135
157
  ledgerJson.records[1].attestation = "attested";
136
158
  const { recordHash: _stale, ...rest1 } = ledgerJson.records[1];
137
- ledgerJson.records[1].recordHash = (0, telemetry_ledger_1.computeRecordHash)(rest1); // attacker re-seals the local hash
138
- node_fs_1.default.writeFileSync(ledgerFile, JSON.stringify(ledgerJson, null, 2));
139
- const afterLedger = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
159
+ ledgerJson.records[1].recordHash = (0, telemetry_ledger_io_1.computeRecordHash)(rest1); // attacker re-seals the local hash
160
+ fs.writeFileSync(ledgerFile, JSON.stringify(ledgerJson, null, 2));
161
+ const afterLedger = (0, telemetry_ledger_io_1.verifyTelemetryLedger)(run);
140
162
  layers.push({
141
163
  layer: "ledger",
142
164
  tamper: `forged record[1] verdict "unattested" -> "attested" AND recomputed its recordHash to cover the edit`,
143
165
  before: { verified: clean.verified, detail: `${clean.records.length} records: chain intact, all hashes recompute` },
144
166
  after: { verified: afterLedger.verified, detail: `the hash chain caught it: ${failingChecks(afterLedger.checks).join(", ")}` },
145
- failures: failingChecks(afterLedger.checks)
167
+ failures: failingChecks(afterLedger.checks),
146
168
  });
147
- // 3b. SIGNATURE layer — inflate hop-0's reported output tokens, keep the original
148
- // signature. The ed25519 verify binds the exact usage bytes, so it rejects.
169
+ // 3b. SIGNATURE layer — inflate hop-0's reported output tokens, keep
170
+ // the original signature.
149
171
  const target = signed[0];
150
172
  const inflated = { ...target.hop.usage, output_tokens: target.hop.usage.output_tokens * 10 };
151
173
  const sigCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(inflated, target.signature, publicKeyPem, {
152
174
  runId,
153
175
  taskId: target.hop.taskId,
154
- promptDigest: target.hop.promptDigest
176
+ promptDigest: target.hop.promptDigest,
155
177
  });
156
178
  const sigCleanCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, target.signature, publicKeyPem, {
157
179
  runId,
158
180
  taskId: target.hop.taskId,
159
- promptDigest: target.hop.promptDigest
181
+ promptDigest: target.hop.promptDigest,
160
182
  });
161
183
  layers.push({
162
184
  layer: "signature",
163
185
  tamper: `inflated record[0] reported output_tokens ${target.hop.usage.output_tokens} -> ${inflated.output_tokens}, reused the original ed25519 signature`,
164
186
  before: { verified: sigCleanCheck.status === "attested", detail: `signature verifies against the reported usage (${sigCleanCheck.algorithm || "ed25519"})` },
165
187
  after: { verified: sigCheck.status === "attested", detail: sigCheck.reason || sigCheck.status },
166
- failures: sigCheck.status === "attested" ? [] : [`signature: ${sigCheck.reason}`]
188
+ failures: sigCheck.status === "attested" ? [] : [`signature: ${sigCheck.reason}`],
167
189
  });
168
- // 3c. RESULT layer — the REAL threat the result-bound signature closes: the agent's
169
- // signed FINDING is edited after signing. The executor binds sha256(result.md)
170
- // into the ed25519 payload (a 5-field, result-COVERING signature); CW re-derives
171
- // the digest from the result file at verify time. An edited finding hashes to a
172
- // different digest, so the signature joins neither the 5-field nor the 4-field
173
- // payload and the verify rejects it. A usage-only signature never covered this.
190
+ // 3c. RESULT layer — the agent's signed FINDING is edited after
191
+ // signing. CW re-derives the digest from the result text at verify
192
+ // time, so the edited finding no longer joins the signature.
174
193
  const findingSigned = "Finding: auth bypass in login() — severity HIGH";
175
194
  const findingEdited = findingSigned.replace("HIGH", "LOW");
176
195
  const resultCtx = { runId, taskId: target.hop.taskId, promptDigest: target.hop.promptDigest };
177
- const resultSignature = (0, telemetry_attestation_1.signTelemetry)(target.hop.usage, privateKeyPem, { ...resultCtx, resultDigest: (0, execution_backend_1.sha256)(findingSigned) });
178
- // Baseline: the signed finding verifies, and the signature actually COVERED the result.
179
- const resultClean = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, execution_backend_1.sha256)(findingSigned) });
180
- // Tamper: CW re-derives the digest from the EDITED finding -> no longer the signed digest.
181
- const resultCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, execution_backend_1.sha256)(findingEdited) });
196
+ const resultSignature = (0, telemetry_attestation_1.signTelemetry)(target.hop.usage, privateKeyPem, { ...resultCtx, resultDigest: (0, hash_1.sha256)(findingSigned) });
197
+ const resultClean = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, hash_1.sha256)(findingSigned) });
198
+ const resultCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, hash_1.sha256)(findingEdited) });
182
199
  layers.push({
183
200
  layer: "result",
184
201
  tamper: `edited the agent's signed finding "severity HIGH" -> "severity LOW" after it was signed`,
185
202
  before: {
186
203
  verified: resultClean.status === "attested" && resultClean.coversResult === true,
187
- detail: `the signed finding verifies — ed25519 binds usage + sha256(result), result-covering`
204
+ detail: `the signed finding verifies — ed25519 binds usage + sha256(result), result-covering`,
188
205
  },
189
206
  after: { verified: resultCheck.status === "attested", detail: resultCheck.reason || resultCheck.status },
190
- failures: resultCheck.status === "attested" ? [] : [`result: ${resultCheck.reason}`]
207
+ failures: resultCheck.status === "attested" ? [] : [`result: ${resultCheck.reason}`],
191
208
  });
192
209
  if (!options.keepDir && !options.dir)
193
- node_fs_1.default.rmSync(runDir, { recursive: true, force: true });
210
+ fs.rmSync(runDir, { recursive: true, force: true });
194
211
  const proven = baseline.ledgerVerified &&
195
212
  baseline.signaturesValid === signed.filter((s) => s.signature).length &&
196
213
  layers.every((l) => l.before.verified && !l.after.verified && l.failures.length > 0);
197
214
  return { schemaVersion: 1, runId, workers: HOPS.length, trustKey: "ephemeral-ed25519", baseline, layers, proven };
198
215
  }
199
216
  function runBundleDemo(options = {}) {
200
- const workdir = options.dir || node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "cw-bundle-demo-"));
201
- node_fs_1.default.mkdirSync(workdir, { recursive: true });
217
+ const workdir = options.dir || fs.mkdtempSync(path.join(os.tmpdir(), "cw-bundle-demo-"));
218
+ fs.mkdirSync(workdir, { recursive: true });
202
219
  const runId = "demo-bundle-run";
203
- const runDir = node_path_1.default.join(workdir, ".cw", "runs", runId);
204
- const paths = (0, state_1.createRunPaths)(runDir);
205
- (0, state_1.ensureRunDirs)(paths);
206
- const { publicKey, privateKey } = node_crypto_1.default.generateKeyPairSync("ed25519");
220
+ const runDir = path.join(workdir, ".cw", "runs", runId);
221
+ const paths = (0, run_paths_1.createRunPaths)(runDir);
222
+ (0, run_paths_1.ensureRunDirs)(paths);
223
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
207
224
  const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
208
225
  const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
209
- // Build a real signed ledger + a cited report, the way an attested run would.
226
+ // Build a real signed ledger + a cited report, the way an attested run
227
+ // would.
210
228
  const ledgerRun = { id: runId, paths };
211
229
  for (const hop of HOPS) {
212
230
  const ctx = { runId, taskId: hop.taskId, promptDigest: hop.promptDigest };
213
- (0, telemetry_ledger_1.appendTelemetryAttestation)(ledgerRun, {
231
+ (0, telemetry_ledger_io_1.appendTelemetryAttestation)(ledgerRun, {
214
232
  workerId: hop.workerId,
215
233
  taskId: hop.taskId,
216
234
  promptDigest: hop.promptDigest,
217
235
  reportedUsage: hop.usage,
218
236
  usageSignature: hop.attestation === "attested" ? (0, telemetry_attestation_1.signTelemetry)(hop.usage, privateKeyPem, ctx) : undefined,
219
237
  attestation: hop.attestation,
220
- now: DEMO_NOW
238
+ now: DEMO_NOW,
221
239
  });
222
240
  }
223
- node_fs_1.default.writeFileSync(node_path_1.default.join(runDir, "report.md"), "# Architecture review\n\nRisk: src/server.js:18 — unauthenticated route.\n", "utf8");
241
+ fs.writeFileSync(path.join(runDir, "report.md"), "# Architecture review\n\nRisk: src/server.js:18 — unauthenticated route.\n", "utf8");
224
242
  const attestedCount = HOPS.filter((h) => h.attestation === "attested").length;
225
243
  const fullRun = {
226
- schemaVersion: 1, id: runId, createdAt: DEMO_NOW, updatedAt: DEMO_NOW, cwd: workdir,
244
+ schemaVersion: 1,
245
+ id: runId,
246
+ createdAt: DEMO_NOW,
247
+ updatedAt: DEMO_NOW,
248
+ cwd: workdir,
227
249
  workflow: { id: "demo", title: "Demo", summary: "", limits: { maxAgents: 1, maxConcurrentAgents: 1 } },
228
- inputs: { question: "what are the risks?" }, loopStage: "interpret",
229
- phases: [], tasks: [], dispatches: [], commits: [], paths, nodes: [], contracts: []
250
+ inputs: { question: "what are the risks?" },
251
+ loopStage: "interpret",
252
+ phases: [],
253
+ tasks: [],
254
+ dispatches: [],
255
+ commits: [],
256
+ paths,
257
+ nodes: [],
258
+ contracts: [],
259
+ };
260
+ (0, run_store_1.saveCheckpoint)(fullRun);
261
+ const ledgerFile = (0, telemetry_ledger_io_1.telemetryLedgerPath)(ledgerRun);
262
+ const cleanLedger = fs.readFileSync(ledgerFile, "utf8");
263
+ const exportSealed = (out) => {
264
+ (0, run_export_1.exportRun)(fullRun, out, { trustPublicKey: publicKeyPem });
230
265
  };
231
- (0, state_1.saveCheckpoint)(fullRun);
232
- const ledgerFile = (0, telemetry_ledger_1.telemetryLedgerPath)(ledgerRun);
233
- const cleanLedger = node_fs_1.default.readFileSync(ledgerFile, "utf8");
234
- const exportSealed = (out) => { (0, run_export_1.exportRun)(fullRun, out, { trustPublicKey: publicKeyPem }); };
235
- // Baseline: a clean sealed bundle verifies offline; the embedded key reverifies
236
- // every signed hop.
237
- const cleanBundle = node_path_1.default.join(workdir, "clean.cwrun.json");
266
+ // Baseline: a clean sealed bundle verifies offline; the embedded key
267
+ // reverifies every signed hop.
268
+ const cleanBundle = path.join(workdir, "clean.cwrun.json");
238
269
  exportSealed(cleanBundle);
239
270
  const clean = (0, run_export_1.verifyReportBundle)(cleanBundle);
240
271
  const baseline = { ok: clean.ok, telemetryVerified: clean.telemetryVerified, signaturesReverified: clean.signaturesReverified };
241
272
  const layers = [];
242
- // CHAIN forgery: flip record[1]'s verdict and reseal its recordHash; record[2]'s
243
- // prevHash still points at the original hash, so the chain breaks — even though
244
- // every archive file digest (computed at export over the tampered bytes) is valid.
245
- // This is exactly what inspect-archive alone cannot catch.
273
+ // CHAIN forgery: flip record[1]'s verdict and reseal its recordHash;
274
+ // record[2]'s prevHash still points at the original hash, so the chain
275
+ // breaks — even though every archive file digest is valid.
246
276
  {
247
277
  const j = JSON.parse(cleanLedger);
248
278
  j.records[1].attestation = "attested";
249
279
  const { recordHash: _drop, ...rest } = j.records[1];
250
- j.records[1].recordHash = (0, telemetry_ledger_1.computeRecordHash)(rest);
251
- node_fs_1.default.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
252
- const forged = node_path_1.default.join(workdir, "forged-chain.cwrun.json");
280
+ j.records[1].recordHash = (0, telemetry_ledger_io_1.computeRecordHash)(rest);
281
+ fs.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
282
+ const forged = path.join(workdir, "forged-chain.cwrun.json");
253
283
  exportSealed(forged);
254
284
  const after = (0, run_export_1.verifyReportBundle)(forged);
255
- node_fs_1.default.writeFileSync(ledgerFile, cleanLedger);
285
+ fs.writeFileSync(ledgerFile, cleanLedger);
256
286
  layers.push({
257
287
  layer: "chain",
258
288
  tamper: `forged record[1] verdict "unattested" -> "attested" and resealed its recordHash; the archive's own file digests stay valid`,
259
289
  before: { ok: clean.ok, detail: `${clean.signaturesReverified} signed hop(s) reverify; chain intact` },
260
290
  after: { ok: after.ok, detail: after.telemetryVerified ? "telemetry chain still verified (UNDETECTED!)" : "the embedded hash chain broke at the next record" },
261
- failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`)
291
+ failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`),
262
292
  });
263
293
  }
264
- // SIGNATURE forgery: inflate the last attested hop's reported tokens and reseal its
265
- // usage digest + recordHash so the chain AND archive digests still verify; only the
266
- // ed25519 signature (over the original usage) no longer matches the inflated number.
294
+ // SIGNATURE forgery: inflate the last attested hop's reported tokens
295
+ // and reseal its usage digest + recordHash so the chain AND archive
296
+ // digests still verify; only the ed25519 signature no longer matches.
267
297
  {
268
298
  const j = JSON.parse(cleanLedger);
269
299
  const idx = j.records.length - 1;
270
300
  j.records[idx].reportedUsage = { ...j.records[idx].reportedUsage, output_tokens: j.records[idx].reportedUsage.output_tokens * 10 };
271
- j.records[idx].reportedUsageDigest = (0, telemetry_ledger_1.reportedUsageDigest)(j.records[idx].reportedUsage);
301
+ j.records[idx].reportedUsageDigest = (0, telemetry_ledger_io_1.reportedUsageDigest)(j.records[idx].reportedUsage);
272
302
  const { recordHash: _drop, ...rest } = j.records[idx];
273
- j.records[idx].recordHash = (0, telemetry_ledger_1.computeRecordHash)(rest);
274
- node_fs_1.default.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
275
- const forged = node_path_1.default.join(workdir, "forged-sig.cwrun.json");
303
+ j.records[idx].recordHash = (0, telemetry_ledger_io_1.computeRecordHash)(rest);
304
+ fs.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
305
+ const forged = path.join(workdir, "forged-sig.cwrun.json");
276
306
  exportSealed(forged);
277
307
  const after = (0, run_export_1.verifyReportBundle)(forged);
278
- node_fs_1.default.writeFileSync(ledgerFile, cleanLedger);
308
+ fs.writeFileSync(ledgerFile, cleanLedger);
279
309
  layers.push({
280
310
  layer: "signature",
281
311
  tamper: `inflated the last attested hop's output_tokens 10x and resealed its digest + recordHash; the chain stays valid`,
282
312
  before: { ok: clean.ok, detail: `the embedded public key reverifies the original signature` },
283
313
  after: { ok: after.ok, detail: after.signaturesFailed > 0 ? `${after.signaturesFailed} signature(s) failed ed25519 reverify` : "signature still verified (UNDETECTED!)" },
284
- failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`)
314
+ failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`),
285
315
  });
286
316
  }
287
317
  if (!options.keepDir && !options.dir)
288
- node_fs_1.default.rmSync(workdir, { recursive: true, force: true });
318
+ fs.rmSync(workdir, { recursive: true, force: true });
289
319
  const proven = baseline.ok &&
290
320
  baseline.telemetryVerified &&
291
321
  baseline.signaturesReverified === attestedCount &&
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ // shell/telemetry-ledger-io.ts — telemetry ledger disk IO: path
3
+ // resolution, absent-vs-corrupt load, durable append. The pure verify
4
+ // math lives in core/trust/telemetry-ledger.ts.
5
+ //
6
+ // MILESTONE 8. Byte-exact port of the old build's src/telemetry-ledger.ts
7
+ // IO-touching half. CRITICAL invariant (docs/rebuild/PLAN.md byte-compat item 12):
8
+ // an ABSENT telemetry.json is an empty, clean-verifying chain
9
+ // (`present:false`); a PRESENT but unparseable one is CORRUPT — reads
10
+ // report `telemetry-ledger-corrupt`, and append THROWS
11
+ // `TelemetryLedgerCorruptError`. It must never re-genesis over a
12
+ // poisoned file.
13
+ //
14
+ // Evidence: SPEC/ledger-trust.md "Absent vs corrupt telemetry ledger",
15
+ // invariant 3; plugins/cool-workflow/src/telemetry-ledger.ts:25-72,133-161.
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.reportedUsageDigest = exports.computeRecordHash = exports.TELEMETRY_LEDGER_SCHEMA_VERSION = exports.TelemetryLedgerCorruptError = void 0;
51
+ exports.telemetryLedgerPath = telemetryLedgerPath;
52
+ exports.loadTelemetryLedger = loadTelemetryLedger;
53
+ exports.appendTelemetryAttestation = appendTelemetryAttestation;
54
+ exports.verifyTelemetryLedger = verifyTelemetryLedger;
55
+ const fs = __importStar(require("node:fs"));
56
+ const path = __importStar(require("node:path"));
57
+ const fs_atomic_1 = require("./fs-atomic");
58
+ const telemetry_ledger_1 = require("../core/trust/telemetry-ledger");
59
+ Object.defineProperty(exports, "computeRecordHash", { enumerable: true, get: function () { return telemetry_ledger_1.computeRecordHash; } });
60
+ Object.defineProperty(exports, "reportedUsageDigest", { enumerable: true, get: function () { return telemetry_ledger_1.reportedUsageDigest; } });
61
+ Object.defineProperty(exports, "TelemetryLedgerCorruptError", { enumerable: true, get: function () { return telemetry_ledger_1.TelemetryLedgerCorruptError; } });
62
+ Object.defineProperty(exports, "TELEMETRY_LEDGER_SCHEMA_VERSION", { enumerable: true, get: function () { return telemetry_ledger_1.TELEMETRY_LEDGER_SCHEMA_VERSION; } });
63
+ function telemetryLedgerPath(run) {
64
+ return path.join(run.paths.runDir, "telemetry.json");
65
+ }
66
+ /** Read the ledger, DISTINGUISHING absent (never written -> empty chain,
67
+ * fine) from corrupt (exists but unparseable/wrong shape -> fail
68
+ * closed). */
69
+ function readTelemetryLedgerState(run) {
70
+ const file = telemetryLedgerPath(run);
71
+ if (!fs.existsSync(file))
72
+ return { status: "absent", ledger: { schemaVersion: 1, runId: run.id, records: [] } };
73
+ let parsed;
74
+ try {
75
+ parsed = JSON.parse(fs.readFileSync(file, "utf8"));
76
+ }
77
+ catch {
78
+ return { status: "corrupt", file };
79
+ }
80
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.records)) {
81
+ return { status: "corrupt", file };
82
+ }
83
+ return { status: "ok", ledger: { schemaVersion: 1, runId: run.id, records: parsed.records } };
84
+ }
85
+ /** Load the ledger for read/append. Absent -> empty chain. Corrupt ->
86
+ * THROWS, so an append can never silently re-genesis on a poisoned/
87
+ * edited file. */
88
+ function loadTelemetryLedger(run) {
89
+ const state = readTelemetryLedgerState(run);
90
+ if (state.status === "corrupt")
91
+ throw new telemetry_ledger_1.TelemetryLedgerCorruptError(state.file);
92
+ return state.ledger;
93
+ }
94
+ /** Append one attestation record DURABLY to the append-only chain,
95
+ * linking it to the prior record (or genesis). Returns the committed
96
+ * record. Throws `TelemetryLedgerCorruptError` if the file on disk is
97
+ * present but unparseable — never silently re-genesis. */
98
+ function appendTelemetryAttestation(run, input) {
99
+ const ledger = loadTelemetryLedger(run);
100
+ const now = input.now || new Date().toISOString();
101
+ const prevHash = ledger.records.length ? ledger.records[ledger.records.length - 1].recordHash : (0, telemetry_ledger_1.genesisPrevHash)(run.id);
102
+ const base = {
103
+ schemaVersion: 1,
104
+ runId: run.id,
105
+ recordId: (0, telemetry_ledger_1.recordId)(ledger.records.length + 1),
106
+ recordedAt: now,
107
+ workerId: input.workerId,
108
+ taskId: input.taskId,
109
+ promptDigest: input.promptDigest,
110
+ reportedUsageDigest: (0, telemetry_ledger_1.reportedUsageDigest)(input.reportedUsage),
111
+ ...(input.reportedUsage ? { reportedUsage: input.reportedUsage } : {}),
112
+ usageSignature: input.usageSignature,
113
+ ...(input.resultDigest ? { resultDigest: input.resultDigest } : {}),
114
+ attestation: input.attestation,
115
+ attestationReason: input.attestationReason,
116
+ prevHash,
117
+ };
118
+ const record = { ...base, recordHash: (0, telemetry_ledger_1.computeRecordHash)(base) };
119
+ ledger.records.push(record);
120
+ (0, fs_atomic_1.writeJson)(telemetryLedgerPath(run), ledger, { durable: true });
121
+ return record;
122
+ }
123
+ /** Re-prove the whole telemetry chain for a run, reading from disk.
124
+ * Absent -> present:false/verified:true (nothing to prove). Corrupt ->
125
+ * present:true/verified:false with the `telemetry-ledger-corrupt`
126
+ * check, never thrown. */
127
+ function verifyTelemetryLedger(run) {
128
+ const state = readTelemetryLedgerState(run);
129
+ if (state.status === "corrupt")
130
+ return (0, telemetry_ledger_1.corruptTelemetryLedgerVerification)();
131
+ return (0, telemetry_ledger_1.verifyTelemetryLedgerRecords)(run.id, state.ledger.records);
132
+ }