cool-workflow 0.1.78 → 0.1.79

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 (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +21 -3
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/end-to-end-golden-path/app.json +1 -1
  6. package/apps/pr-review-fix-ci/app.json +1 -1
  7. package/apps/release-cut/app.json +1 -1
  8. package/apps/research-synthesis/app.json +1 -1
  9. package/dist/capability-core.js +33 -0
  10. package/dist/capability-registry.js +2 -0
  11. package/dist/cli.js +39 -0
  12. package/dist/mcp-server.js +8 -0
  13. package/dist/telemetry-demo.js +154 -0
  14. package/dist/version.js +1 -1
  15. package/docs/agent-delegation-drive.7.md +2 -0
  16. package/docs/cli-mcp-parity.7.md +2 -0
  17. package/docs/contract-migration-tooling.7.md +2 -0
  18. package/docs/control-plane-scheduling.7.md +2 -0
  19. package/docs/durable-state-and-locking.7.md +2 -0
  20. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  21. package/docs/execution-backends.7.md +2 -0
  22. package/docs/launch/launch-kit.md +116 -0
  23. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  24. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  25. package/docs/multi-agent-operator-ux.7.md +2 -0
  26. package/docs/node-snapshot-diff-replay.7.md +2 -0
  27. package/docs/observability-cost-accounting.7.md +2 -0
  28. package/docs/project-index.md +5 -3
  29. package/docs/real-execution-backends.7.md +2 -0
  30. package/docs/release-and-migration.7.md +2 -0
  31. package/docs/release-tooling.7.md +2 -0
  32. package/docs/run-registry-control-plane.7.md +2 -0
  33. package/docs/run-retention-reclamation.7.md +2 -0
  34. package/docs/state-explosion-management.7.md +2 -0
  35. package/docs/team-collaboration.7.md +2 -0
  36. package/docs/web-desktop-workbench.7.md +2 -0
  37. package/manifest/plugin.manifest.json +1 -1
  38. package/package.json +1 -1
  39. package/scripts/canonical-apps.js +4 -4
  40. package/scripts/dogfood-release.js +1 -1
  41. package/scripts/golden-path.js +4 -4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
3
  "description": "Auditable workflow control-plane and orchestration runtime: TypeScript dispatch, evidence-gated verification, state commits, scheduling, routines, multi-agent coordination, and MCP. Delegates execution to external agents — never runs models.",
4
- "version": "0.1.78",
4
+ "version": "0.1.79",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.1.78",
3
+ "version": "0.1.79",
4
4
  "description": "Auditable workflow control-plane and orchestration runtime: TypeScript dispatch, evidence-gated verification, state commits, scheduling, routines, multi-agent coordination, and MCP. Delegates execution to external agents — never runs models.",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
package/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ```
9
9
 
10
10
  [![CI](https://img.shields.io/github/actions/workflow/status/coo1white/cool-workflow/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/coo1white/cool-workflow/actions/workflows/ci.yml)
11
+ [![npm](https://img.shields.io/npm/v/cool-workflow?style=flat-square&label=npm&color=cb3837)](https://www.npmjs.com/package/cool-workflow)
12
+ [![downloads](https://img.shields.io/npm/dm/cool-workflow?style=flat-square&label=downloads)](https://www.npmjs.com/package/cool-workflow)
11
13
  [![release](https://img.shields.io/github/v/tag/coo1white/cool-workflow?style=flat-square&label=release&color=brightgreen&sort=semver)](https://github.com/coo1white/cool-workflow/tags)
12
14
  [![license](https://img.shields.io/badge/license-BSD--2--Clause-blue?style=flat-square)](../../LICENSE)
13
15
  ![MCP](https://img.shields.io/badge/MCP-native-8A2BE2?style=flat-square)
@@ -631,8 +633,24 @@ CHANGELOG.md and RELEASE.md are content surfaces checked by the dogfood-release
631
633
 
632
634
  Auto-compaction hook moved from `saveCheckpoint()` to explicit `maybeCompactRun()` calls after major lifecycle mutations. Fixes test fixture fingerprint instability. Also fixes the dogfood-release version-sync pipeline: always use `npm run bump:version`, never hand-edit version.ts alone.
633
635
 
634
- v0.1.76
636
+ ## Control-plane naming (v0.1.76)
635
637
 
636
- v0.1.77
638
+ Positioning consistency: every self-describing surface names CW an auditable workflow control-plane / Workflow App framework, not an "SDK" (which survives only in the red-line disclaimer "embeds no model SDK").
637
639
 
638
- v0.1.78
640
+ ## Workflow orchestration: Tracks 1–3 (v0.1.77)
641
+
642
+ The orchestration vision landed in one release, all reviewer-gated:
643
+
644
+ - **Track 1 — telemetry attestation**: each agent's reported token usage is verified against an operator ed25519 trust key (`attested`/`unattested`/`absent`, surfaced loudly), recorded in a tamper-evident hash-chained ledger; opt-in `require-attested-telemetry` fails closed on unverifiable usage.
645
+ - **Track 2 — concurrent failure semantics**: a `parallel()` phase runs its agents concurrently with declared collapse rules — **collect-all** (a failing hop never aborts siblings) and **kill-on-timeout** (a hung agent is killed at its deadline and counted as one failure). 16 agents with a forced hang + crash + dirty-return complete with no deadlock and a replay-complete record.
646
+ - **Track 3 — boundary contract**: per-task output `schema` validation (dependency-free, parks on mismatch), `limits.tokenBudget` enforced against recorded usage, and the one-way executor boundary welded into the type layer (a callable crossing it fails `npm run build`).
647
+
648
+ ## Working onboarding + npm distribution (v0.1.78)
649
+
650
+ `--agent-command builtin:claude` resolves to a bundled read-only claude wrapper that completes workers with a real agent; the cross-directory quickstart crash is fixed; missing optional inputs no longer leak `{{name}}` into prompts. Published to npm (`cool-workflow`, bins `cw`/`cool-workflow`) with LICENSE and metadata. Live dogfood proof committed under `docs/dogfood/`.
651
+
652
+ ## Tamper-evidence demo (on main, ships next)
653
+
654
+ `cw demo tamper` — a hermetic, one-command proof that a recorded telemetry verdict cannot be forged undetected: it builds a real ed25519-signed ledger, forges it at the ledger layer (verdict flip + recomputed local hash → the chain still breaks) and the signature layer (inflated tokens, reused signature → ed25519 rejects), all verified offline with only the public key. `cw telemetry verify <run>` is the operator-facing half (`cw_telemetry_verify` on MCP).
655
+
656
+ v0.1.79
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review",
4
4
  "title": "Architecture Review",
5
5
  "summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
6
- "version": "0.1.78",
6
+ "version": "0.1.79",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "end-to-end-golden-path",
4
4
  "title": "End-to-End Golden Path",
5
5
  "summary": "Deterministic one-worker workflow app for proving the CW integration chain.",
6
- "version": "0.1.78",
6
+ "version": "0.1.79",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "pr-review-fix-ci",
4
4
  "title": "PR Review Fix CI",
5
5
  "summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
6
- "version": "0.1.78",
6
+ "version": "0.1.79",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "release-cut",
4
4
  "title": "Release Cut",
5
5
  "summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
6
- "version": "0.1.78",
6
+ "version": "0.1.79",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "research-synthesis",
4
4
  "title": "Research Synthesis",
5
5
  "summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
6
- "version": "0.1.78",
6
+ "version": "0.1.79",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -61,11 +61,15 @@ exports.sandboxProfileIdFrom = sandboxProfileIdFrom;
61
61
  exports.withoutRuntimeKeys = withoutRuntimeKeys;
62
62
  exports.optionalString = optionalString;
63
63
  exports.isRecord = isRecord;
64
+ exports.telemetryVerify = telemetryVerify;
65
+ exports.demoTamper = demoTamper;
64
66
  const capability_registry_1 = require("./capability-registry");
65
67
  const drive_1 = require("./drive");
66
68
  const agent_config_1 = require("./agent-config");
67
69
  const run_registry_1 = require("./run-registry");
68
70
  const observability_1 = require("./observability");
71
+ const telemetry_ledger_1 = require("./telemetry-ledger");
72
+ const telemetry_demo_1 = require("./telemetry-demo");
69
73
  const state_1 = require("./state");
70
74
  const node_fs_1 = __importDefault(require("node:fs"));
71
75
  const node_path_1 = __importDefault(require("node:path"));
@@ -628,3 +632,32 @@ function optionalString(value) {
628
632
  function isRecord(value) {
629
633
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
630
634
  }
635
+ // ---- telemetry attestation: read-only ledger verification (Track 1) --------
636
+ // Re-prove a run's telemetry chain offline: prevHash linkage + independent per-
637
+ // record hash recompute (never trusts the stored hash). The auditable claim made
638
+ // inspectable on demand — anyone can run this; a forged/edited record fails it.
639
+ function telemetryVerify(runner, args) {
640
+ const runId = optionalString(args.runId || args.run);
641
+ if (!runId)
642
+ throw new Error("telemetry verify requires a run id (cw telemetry verify <run-id>)");
643
+ const run = runner.loadRun(runId);
644
+ const v = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
645
+ return {
646
+ schemaVersion: 1,
647
+ runId: run.id,
648
+ present: v.present,
649
+ verified: v.verified,
650
+ records: v.records.length,
651
+ attested: v.attested,
652
+ unattested: v.unattested,
653
+ absent: v.absent,
654
+ failedChecks: v.checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code }))
655
+ };
656
+ }
657
+ // ---- demo: tamper-evidence (the one-command proof) -------------------------
658
+ // Hermetic, deterministic-shape: builds a real ed25519-signed telemetry ledger,
659
+ // then forges it two ways and shows both tamper-evidence layers catch it. CLI-only
660
+ // (a human-facing demonstration; the underlying verify is the telemetry.verify verb).
661
+ function demoTamper(_runner, _args = {}) {
662
+ return (0, telemetry_demo_1.runTamperDemo)();
663
+ }
@@ -374,6 +374,8 @@ const BUILTIN_CAPABILITIES = [
374
374
  { capability: "gc.plan", summary: "Dry-run plan of run reclamation (per-kind bytes + capability downgrade); frees nothing.", entry: "gcPlan", surface: "both", cli: { path: ["gc", "plan"], caseTokens: ["gc", "plan"], jsonMode: "flag" }, mcp: { tool: "cw_gc_plan" } },
375
375
  { capability: "gc.run", summary: "Execute the write-ahead reclamation transaction (skeleton -> tombstone -> fsync -> free).", entry: "gcRun", surface: "both", cli: { path: ["gc", "run"], caseTokens: ["gc", "run"], jsonMode: "flag" }, mcp: { tool: "cw_gc_run" }, payloadIdentical: false, reason: "Mutating: frees disk and appends a tombstone; both surfaces perform the identical transaction but the payload reports now-derived bytesFreed/tombstone." },
376
376
  { capability: "gc.verify", summary: "Re-prove a reclaimed run: skeleton-complete, tombstone chain untampered, artifacts reconstructable.", entry: "gcVerify", surface: "both", cli: { path: ["gc", "verify"], caseTokens: ["gc", "verify"], jsonMode: "flag" }, mcp: { tool: "cw_gc_verify" } },
377
+ { capability: "telemetry.verify", summary: "Re-prove a run's telemetry attestation ledger offline (chain linkage + independent hash recompute).", entry: "telemetryVerify", surface: "both", cli: { path: ["telemetry", "verify"], caseTokens: ["telemetry"], jsonMode: "flag" }, mcp: { tool: "cw_telemetry_verify" } },
378
+ { capability: "demo.tamper", summary: "Prove tamper-evidence: build a signed telemetry ledger, forge it, watch verification fail offline.", entry: "demoTamper", surface: "cli-only", cli: { path: ["demo", "tamper"], caseTokens: ["demo", "tamper"], jsonMode: "flag" }, reason: "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface telemetry.verify. No agent or MCP client needs to invoke a demo." },
377
379
  { capability: "history", summary: "Read a cross-repo unified run timeline (newest first).", entry: "runRegistry.history", surface: "both", cli: { path: ["history"], jsonMode: "flag" }, mcp: { tool: "cw_history" } },
378
380
  // ---- web / desktop workbench (v0.1.30) ----------------------------------
379
381
  // A THIRD FRONT DOOR — a read-only renderer, not a new brain. Both verbs route
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ const orchestrator_1 = require("./orchestrator");
10
10
  const capability_registry_1 = require("./capability-registry");
11
11
  const capability_core_1 = require("./capability-core");
12
12
  const observability_1 = require("./observability");
13
+ const telemetry_demo_1 = require("./telemetry-demo");
13
14
  const run_registry_1 = require("./run-registry");
14
15
  const daemon_1 = require("./daemon");
15
16
  const scheduler_1 = require("./scheduler");
@@ -1179,6 +1180,44 @@ async function main() {
1179
1180
  process.stdout.write(`${(0, run_registry_1.formatHistory)(result)}\n`);
1180
1181
  return;
1181
1182
  }
1183
+ case "telemetry": {
1184
+ const [subcommand, id] = args.positionals;
1185
+ switch (subcommand) {
1186
+ case "verify": {
1187
+ const result = (0, capability_core_1.telemetryVerify)(runner, { ...args.options, runId: id || args.options.runId || args.options.run });
1188
+ if (wantsJson(args.options))
1189
+ printJson(result);
1190
+ else
1191
+ process.stdout.write(`${(0, telemetry_demo_1.formatTelemetryVerify)(result)}\n`);
1192
+ return;
1193
+ }
1194
+ default:
1195
+ if (await tryDispatchCli(args, runner))
1196
+ return;
1197
+ throw new Error("Usage: cw.js telemetry verify <run-id> [--json]");
1198
+ }
1199
+ }
1200
+ case "demo": {
1201
+ const [subcommand] = args.positionals;
1202
+ switch (subcommand) {
1203
+ case "tamper": {
1204
+ const result = (0, capability_core_1.demoTamper)(runner, args.options);
1205
+ if (wantsJson(args.options))
1206
+ printJson(result);
1207
+ else
1208
+ process.stdout.write(`${(0, telemetry_demo_1.formatTamperDemo)(result)}\n`);
1209
+ // Fail closed: if the proof did not hold (a tamper went undetected),
1210
+ // exit nonzero so the demo can never green a broken guarantee.
1211
+ if (!result.proven)
1212
+ process.exitCode = 1;
1213
+ return;
1214
+ }
1215
+ default:
1216
+ if (await tryDispatchCli(args, runner))
1217
+ return;
1218
+ throw new Error("Usage: cw.js demo tamper [--json]");
1219
+ }
1220
+ }
1182
1221
  case "workbench": {
1183
1222
  const [subcommand, runId] = args.positionals;
1184
1223
  switch (subcommand) {
@@ -425,6 +425,8 @@ function callTool(name, args) {
425
425
  return (0, capability_core_1.gcRun)((0, capability_core_1.runRegistryFor)(args, runner), (0, capability_core_1.optionalString)(args.runId), args);
426
426
  case "cw_gc_verify":
427
427
  return (0, capability_core_1.gcVerify)((0, capability_core_1.runRegistryFor)(args, runner), String(args.runId || ""), args);
428
+ case "cw_telemetry_verify":
429
+ return (0, capability_core_1.telemetryVerify)(runner, args);
428
430
  case "cw_history":
429
431
  return (0, capability_core_1.runHistory)((0, capability_core_1.runRegistryFor)(args, runner), args);
430
432
  case "cw_workbench_view":
@@ -516,6 +518,8 @@ function requiredArgsForTool(name) {
516
518
  return ["runId|olderThanDays"];
517
519
  if (name === "cw_gc_verify")
518
520
  return ["runId"];
521
+ if (name === "cw_telemetry_verify")
522
+ return ["runId"];
519
523
  if (name === "cw_queue_show")
520
524
  return ["id"];
521
525
  if (name.endsWith("_show")) {
@@ -1531,6 +1535,10 @@ function toolDefinitions() {
1531
1535
  scope: stringSchema("home (default, cross-repo) or repo"),
1532
1536
  runId: stringSchema("Run id to verify")
1533
1537
  }),
1538
+ tool("cw_telemetry_verify", "Re-prove a run's telemetry attestation ledger offline: prevHash chain linkage + independent per-record hash recompute (never trusts the stored hash). A forged or edited record fails it. Peer of `cw telemetry verify`.", {
1539
+ cwd: stringSchema("Repo workspace"),
1540
+ runId: stringSchema("Run id to verify")
1541
+ }),
1534
1542
  tool("cw_history", "Read a cross-repo unified run timeline (newest first), deterministic and paginated, with provenance links.", {
1535
1543
  cwd: stringSchema("Repo workspace"),
1536
1544
  scope: stringSchema("home (default, cross-repo) or repo"),
@@ -0,0 +1,154 @@
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.
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 BOTH 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
+ //
17
+ // No model, no network, no API key, no second repo — runs in a private tmpdir.
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.formatTelemetryVerify = formatTelemetryVerify;
23
+ exports.formatTamperDemo = formatTamperDemo;
24
+ exports.runTamperDemo = runTamperDemo;
25
+ const node_crypto_1 = __importDefault(require("node:crypto"));
26
+ const node_fs_1 = __importDefault(require("node:fs"));
27
+ const node_os_1 = __importDefault(require("node:os"));
28
+ const node_path_1 = __importDefault(require("node:path"));
29
+ const telemetry_ledger_1 = require("./telemetry-ledger");
30
+ const telemetry_attestation_1 = require("./telemetry-attestation");
31
+ const execution_backend_1 = require("./execution-backend");
32
+ /** Human-facing render of `telemetry verify <run>`. */
33
+ function formatTelemetryVerify(r) {
34
+ if (!r.present)
35
+ return `telemetry: run ${r.runId} has no attestation ledger (nothing to verify)`;
36
+ const head = r.verified ? `✓ VERIFIED — ${r.records} record(s), chain intact, every hash recomputed independently` : `✗ TAMPERING DETECTED — ${r.failedChecks.length} check(s) failed`;
37
+ const tally = ` attested ${r.attested} · unattested ${r.unattested} · absent ${r.absent}`;
38
+ const fails = r.failedChecks.length ? "\n" + r.failedChecks.map((c) => ` ✗ ${c.name} ${c.code || ""}`).join("\n") : "";
39
+ return `telemetry verify ${r.runId}\n${head}\n${tally}${fails}`;
40
+ }
41
+ /** Human-facing render of `demo tamper` — the visible tamper-evidence proof. */
42
+ function formatTamperDemo(r) {
43
+ const lines = [];
44
+ lines.push(`cw demo tamper — tamper-evidence proof (hermetic, ${r.trustKey} key)`);
45
+ lines.push("");
46
+ lines.push(`▶ Built an attested telemetry ledger: ${r.workers} hops, ${r.baseline.records} records`);
47
+ lines.push(` ${r.baseline.ledgerVerified ? "✓" : "✗"} ledger verifies ${r.baseline.signaturesValid} signed hop(s) verify against the public key`);
48
+ for (const l of r.layers) {
49
+ lines.push("");
50
+ lines.push(`▶ ${l.layer.toUpperCase()} tamper`);
51
+ lines.push(` edit: ${l.tamper}`);
52
+ lines.push(` before: ${l.before.verified ? "✓ verified" : "✗"} — ${l.before.detail}`);
53
+ lines.push(` after: ${l.after.verified ? "✓ (UNDETECTED!)" : "✗ DETECTED"} — ${l.after.detail}`);
54
+ }
55
+ lines.push("");
56
+ lines.push(r.proven
57
+ ? "VERDICT: tamper-evidence holds ✓ — every forgery was caught offline, with only the public key. No server was trusted."
58
+ : "VERDICT: PROOF FAILED ✗ — a tamper went undetected. This is a regression in the integrity guarantee.");
59
+ return lines.join("\n");
60
+ }
61
+ // Three hops with a deliberate mix: two signed/attested, one unattested — so the
62
+ // ledger-layer tamper can forge the unattested verdict into "attested" (the exact
63
+ // threat the ledger exists to catch).
64
+ const HOPS = [
65
+ { 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" },
66
+ { workerId: "w-assess", taskId: "assess:security", promptDigest: (0, execution_backend_1.sha256)("assess:security"), usage: { input_tokens: 1840, output_tokens: 1502 }, attestation: "unattested" },
67
+ { workerId: "w-verdict", taskId: "verdict:synthesis", promptDigest: (0, execution_backend_1.sha256)("verdict:synthesis"), usage: { input_tokens: 980, output_tokens: 770 }, attestation: "attested" }
68
+ ];
69
+ const DEMO_NOW = "2026-01-01T00:00:00.000Z";
70
+ function failingChecks(checks) {
71
+ return checks.filter((c) => !c.pass).map((c) => `${c.name}: ${c.code}`);
72
+ }
73
+ /** Run the full tamper-evidence demonstration in a private tmpdir (cleaned up
74
+ * unless `keepDir` is set). Pure of clock/network; the only nondeterminism is
75
+ * the ephemeral keypair, which never leaves this function. */
76
+ function runTamperDemo(options = {}) {
77
+ const runDir = options.dir || node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "cw-tamper-demo-"));
78
+ node_fs_1.default.mkdirSync(runDir, { recursive: true });
79
+ const runId = "demo-tamper-run";
80
+ // Minimal run shape: the ledger API uses only id + paths.runDir.
81
+ const run = { id: runId, paths: { runDir } };
82
+ const { publicKey, privateKey } = node_crypto_1.default.generateKeyPairSync("ed25519");
83
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
84
+ const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
85
+ // 1. Build a REAL ledger through the production append API, signing each
86
+ // attested hop's usage with the ephemeral key.
87
+ const signed = [];
88
+ for (const hop of HOPS) {
89
+ const ctx = { runId, taskId: hop.taskId, promptDigest: hop.promptDigest };
90
+ const signature = hop.attestation === "attested" ? (0, telemetry_attestation_1.signTelemetry)(hop.usage, privateKeyPem, ctx) : undefined;
91
+ (0, telemetry_ledger_1.appendTelemetryAttestation)(run, {
92
+ workerId: hop.workerId,
93
+ taskId: hop.taskId,
94
+ promptDigest: hop.promptDigest,
95
+ reportedUsage: hop.usage,
96
+ usageSignature: signature,
97
+ attestation: hop.attestation,
98
+ now: DEMO_NOW
99
+ });
100
+ signed.push({ hop, signature });
101
+ }
102
+ // 2. Baseline: the clean ledger verifies, and every signed hop's signature is valid.
103
+ const clean = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
104
+ 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;
105
+ const baseline = { ledgerVerified: clean.verified, signaturesValid, records: clean.records.length };
106
+ const layers = [];
107
+ // 3a. LEDGER layer — the SOPHISTICATED forgery: flip record[1]'s verdict
108
+ // "unattested" -> "attested" AND recompute its recordHash to cover the edit,
109
+ // so the per-record digest check passes. The chain still catches it: record[2]
110
+ // was linked to the ORIGINAL record[1] hash, so chain-link[2] now breaks. This
111
+ // is the point of the chain over a flat per-record hash — fixing one record's
112
+ // hash cannot be hidden without rewriting every record after it too.
113
+ const ledgerFile = (0, telemetry_ledger_1.telemetryLedgerPath)(run);
114
+ const ledgerJson = JSON.parse(node_fs_1.default.readFileSync(ledgerFile, "utf8"));
115
+ ledgerJson.records[1].attestation = "attested";
116
+ const { recordHash: _stale, ...rest1 } = ledgerJson.records[1];
117
+ ledgerJson.records[1].recordHash = (0, telemetry_ledger_1.computeRecordHash)(rest1); // attacker re-seals the local hash
118
+ node_fs_1.default.writeFileSync(ledgerFile, JSON.stringify(ledgerJson, null, 2));
119
+ const afterLedger = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
120
+ layers.push({
121
+ layer: "ledger",
122
+ tamper: `forged record[1] verdict "unattested" -> "attested" AND recomputed its recordHash to cover the edit`,
123
+ before: { verified: clean.verified, detail: `${clean.records.length} records: chain intact, all hashes recompute` },
124
+ after: { verified: afterLedger.verified, detail: `the hash chain caught it: ${failingChecks(afterLedger.checks).join(", ")}` },
125
+ failures: failingChecks(afterLedger.checks)
126
+ });
127
+ // 3b. SIGNATURE layer — inflate hop-0's reported output tokens, keep the original
128
+ // signature. The ed25519 verify binds the exact usage bytes, so it rejects.
129
+ const target = signed[0];
130
+ const inflated = { ...target.hop.usage, output_tokens: target.hop.usage.output_tokens * 10 };
131
+ const sigCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(inflated, target.signature, publicKeyPem, {
132
+ runId,
133
+ taskId: target.hop.taskId,
134
+ promptDigest: target.hop.promptDigest
135
+ });
136
+ const sigCleanCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, target.signature, publicKeyPem, {
137
+ runId,
138
+ taskId: target.hop.taskId,
139
+ promptDigest: target.hop.promptDigest
140
+ });
141
+ layers.push({
142
+ layer: "signature",
143
+ tamper: `inflated record[0] reported output_tokens ${target.hop.usage.output_tokens} -> ${inflated.output_tokens}, reused the original ed25519 signature`,
144
+ before: { verified: sigCleanCheck.status === "attested", detail: `signature verifies against the reported usage (${sigCleanCheck.algorithm || "ed25519"})` },
145
+ after: { verified: sigCheck.status === "attested", detail: sigCheck.reason || sigCheck.status },
146
+ failures: sigCheck.status === "attested" ? [] : [`signature: ${sigCheck.reason}`]
147
+ });
148
+ if (!options.keepDir && !options.dir)
149
+ node_fs_1.default.rmSync(runDir, { recursive: true, force: true });
150
+ const proven = baseline.ledgerVerified &&
151
+ baseline.signaturesValid === signed.filter((s) => s.signature).length &&
152
+ layers.every((l) => l.before.verified && !l.after.verified && l.failures.length > 0);
153
+ return { schemaVersion: 1, runId, workers: HOPS.length, trustKey: "ephemeral-ed25519", baseline, layers, proven };
154
+ }
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA_VERSION = exports.CURRENT_RUN_STATE_SCHEMA_VERSION = exports.WORKFLOW_APP_SCHEMA_VERSION = exports.CURRENT_COOL_WORKFLOW_VERSION = void 0;
4
- exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.78";
4
+ exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.79";
5
5
  exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
6
6
  exports.CURRENT_RUN_STATE_SCHEMA_VERSION = 1;
7
7
  exports.LEGACY_RUN_STATE_SCHEMA_VERSION = 0;
@@ -188,3 +188,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
188
188
  0.1.77
189
189
 
190
190
  0.1.78
191
+
192
+ 0.1.79
@@ -371,3 +371,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
371
371
  0.1.77
372
372
 
373
373
  0.1.78
374
+
375
+ 0.1.79
@@ -121,3 +121,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
121
121
  0.1.77
122
122
 
123
123
  0.1.78
124
+
125
+ 0.1.79
@@ -108,3 +108,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
108
108
  0.1.77
109
109
 
110
110
  0.1.78
111
+
112
+ 0.1.79
@@ -105,3 +105,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
105
105
  0.1.77
106
106
 
107
107
  0.1.78
108
+
109
+ 0.1.79
@@ -268,3 +268,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
268
268
  0.1.77
269
269
 
270
270
  0.1.78
271
+
272
+ 0.1.79
@@ -298,3 +298,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
298
298
  0.1.77
299
299
 
300
300
  0.1.78
301
+
302
+ 0.1.79
@@ -0,0 +1,116 @@
1
+ # Launch Kit — Cool Workflow
2
+
3
+ Copy for announcing CW. The through-line is the one thing no other agent-pipeline
4
+ tool ships: **you can prove the telemetry, offline, with only a public key.**
5
+ Everything leads with the 30-second `npx cool-workflow demo tamper` proof.
6
+
7
+ ---
8
+
9
+ ## One-liner
10
+
11
+ > Cool Workflow is an auditable control-plane for multi-agent workflows. It
12
+ > *delegates* model execution — never embeds it — and makes every recorded agent
13
+ > telemetry verdict tamper-evident: anyone can re-verify a run offline with only a
14
+ > public key.
15
+
16
+ ## Elevator (2 sentences)
17
+
18
+ > Most agent-pipeline tools log what the model reported and trust it. CW signs and
19
+ > hash-chains every telemetry verdict, so a forged or edited record fails
20
+ > verification — provably, offline — which is what "auditable" has to mean before
21
+ > you let agents touch production work.
22
+
23
+ ---
24
+
25
+ ## Show HN
26
+
27
+ **Title:**
28
+ `Show HN: Cool Workflow – tamper-evident telemetry for agent pipelines (npx demo)`
29
+
30
+ **Body:**
31
+
32
+ > I kept seeing agent-orchestration tools treat the model's self-reported token
33
+ > usage and results as ground truth. For anything auditable that's backwards — a
34
+ > control-plane that trusts unverified self-reports audits *claims*, not facts, and
35
+ > a forged "green" run looks identical to a real one.
36
+ >
37
+ > Cool Workflow is a small, zero-dependency CLI + MCP runtime that takes the
38
+ > opposite stance. It **delegates** model execution to whatever agent you configure
39
+ > (`claude -p`, `codex exec`, an HTTP endpoint) and never embeds a model SDK or
40
+ > holds an API key. What it *does* own is the audit trail: each agent hop's reported
41
+ > usage is signed (ed25519) and appended to a hash-chained ledger, so editing any
42
+ > record — or even recomputing its local hash to cover the edit — breaks the chain
43
+ > downstream. You can re-verify a finished run with only the public key, no network,
44
+ > no trusted server.
45
+ >
46
+ > The 30-second proof, no install:
47
+ >
48
+ > ```
49
+ > npx cool-workflow demo tamper
50
+ > ```
51
+ >
52
+ > It builds a real signed ledger, forges it two ways (flip a verdict + re-seal its
53
+ > hash; inflate reported tokens + reuse the signature), and shows both forgeries
54
+ > caught offline. On a real run, `cw telemetry verify <run>` does the same against
55
+ > what's on disk.
56
+ >
57
+ > Other things it does: concurrent `parallel()` phases with declared collapse
58
+ > semantics (collect-all + kill-on-timeout — 16 agents with a forced hang/crash/
59
+ > dirty-return finish without deadlock and replay "who passed/who failed"), per-task
60
+ > output-schema gates, token budgets enforced against attested usage, and a one-way
61
+ > executor boundary welded into the type system (a callable that could reach a model
62
+ > API fails `npm run build`).
63
+ >
64
+ > Runs anywhere Node runs; `dist/` is committed; BSD-2. It's early (v0.1.79) and I'd
65
+ > genuinely like to hear where the "delegate, prove, replay" model breaks down for
66
+ > your workflows.
67
+ >
68
+ > Repo: https://github.com/coo1white/cool-workflow
69
+ > npm: https://www.npmjs.com/package/cool-workflow
70
+
71
+ ---
72
+
73
+ ## Short post / tweet thread
74
+
75
+ 1/ Your agent pipeline trusts what the model *says* it did. Cool Workflow proves
76
+ it instead. `npx cool-workflow demo tamper` — 30s, no install:
77
+
78
+ 2/ It builds a real ed25519-signed telemetry ledger, forges it two ways, and
79
+ catches both offline with only the public key. A control-plane that delegates
80
+ model execution but can still prove the bill is real.
81
+
82
+ 3/ Also: concurrent batches that don't deadlock when an agent hangs, schema-gated
83
+ outputs, token budgets vs *attested* usage, and a red line (never call a model
84
+ API) enforced at compile time. Zero deps, BSD-2.
85
+ → https://github.com/coo1white/cool-workflow
86
+
87
+ ---
88
+
89
+ ## Why this matters (the wedge, for a longer post)
90
+
91
+ - **Separation of duties.** CW never runs the model, yet can verify the executor's
92
+ reported usage. The thing that *spends the money* is not the thing that *keeps
93
+ the books* — the property auditors require everywhere except, so far, agent
94
+ infra.
95
+ - **Offline, public-key verification.** No telemetry service to trust or breach.
96
+ The record proves its own integrity; the verifier needs only the public key.
97
+ - **Replayable, not just logged.** CW breaks at dispatch and writes to disk, so a
98
+ run replays deterministically — "who passed / who failed" is reconstructable, not
99
+ a scrollback of a fused process.
100
+ - **Fail-closed by default where it counts.** Schema mismatch parks the hop;
101
+ unverifiable usage can be refused (opt-in); an empty-capture result can't be
102
+ presented as a clean commit.
103
+
104
+ ## Assets to capture before posting
105
+
106
+ - [ ] A terminal GIF of `npx cool-workflow demo tamper` (the ✗ DETECTED lines are
107
+ the hook) for the README top and the HN/tweet.
108
+ - [ ] Confirm `npx cool-workflow demo tamper` works from a clean machine (no clone).
109
+ - [ ] Pin the npm version badge / release in the first comment.
110
+
111
+ ## Channels
112
+
113
+ Hacker News (Show HN), the MCP / agent-tooling communities, r/LocalLLaMA &
114
+ r/MachineLearning (the offline-verification angle), and the npm listing itself
115
+ (keywords already set). Lead every one with the demo command, not the feature
116
+ list.
@@ -263,3 +263,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
263
263
  0.1.77
264
264
 
265
265
  0.1.78
266
+
267
+ 0.1.79
@@ -300,3 +300,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
300
300
  0.1.77
301
301
 
302
302
  0.1.78
303
+
304
+ 0.1.79
@@ -312,3 +312,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
312
312
  0.1.77
313
313
 
314
314
  0.1.78
315
+
316
+ 0.1.79
@@ -133,3 +133,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
133
133
  0.1.77
134
134
 
135
135
  0.1.78
136
+
137
+ 0.1.79
@@ -192,3 +192,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
192
192
  0.1.77
193
193
 
194
194
  0.1.78
195
+
196
+ 0.1.79
@@ -5,11 +5,11 @@ Generated from the current repository code on 2026-06-11 by `npm run sync:projec
5
5
  ## Snapshot
6
6
 
7
7
  - Package: `cool-workflow`
8
- - Version: `0.1.78`
9
- - Source modules: `57`
8
+ - Version: `0.1.79`
9
+ - Source modules: `58`
10
10
  - Workflow apps: `6`
11
11
  - Docs: `46`
12
- - Smoke tests: `68`
12
+ - Smoke tests: `69`
13
13
  - Repository: https://github.com/coo1white/cool-workflow
14
14
 
15
15
  ## Architecture
@@ -105,6 +105,7 @@ multi-agent host -> topology -> blackboard/coordinator
105
105
  - [state-explosion.ts](../src/state-explosion.ts)
106
106
  - [state-migrations.ts](../src/state-migrations.ts)
107
107
  - [telemetry-attestation.ts](../src/telemetry-attestation.ts)
108
+ - [telemetry-demo.ts](../src/telemetry-demo.ts)
108
109
  - [telemetry-ledger.ts](../src/telemetry-ledger.ts)
109
110
  - [verifier-registry.ts](../src/verifier-registry.ts)
110
111
  - [workbench-host.ts](../src/workbench-host.ts)
@@ -230,6 +231,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
230
231
  - [self-audit-hardening-smoke.js](../test/self-audit-hardening-smoke.js)
231
232
  - [state-explosion-management-smoke.js](../test/state-explosion-management-smoke.js)
232
233
  - [state-node-smoke.js](../test/state-node-smoke.js)
234
+ - [tamper-evidence-demo-smoke.js](../test/tamper-evidence-demo-smoke.js)
233
235
  - [team-collaboration-smoke.js](../test/team-collaboration-smoke.js)
234
236
  - [telemetry-attest-wrap-smoke.js](../test/telemetry-attest-wrap-smoke.js)
235
237
  - [telemetry-attestation-smoke.js](../test/telemetry-attestation-smoke.js)
@@ -140,3 +140,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
140
140
  0.1.77
141
141
 
142
142
  0.1.78
143
+
144
+ 0.1.79
@@ -278,3 +278,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
278
278
  0.1.77
279
279
 
280
280
  0.1.78
281
+
282
+ 0.1.79
@@ -157,3 +157,5 @@ also get generated MCP manifests (`.gemini-plugin/`, `.opencode-plugin/`) so the
157
157
  0.1.76
158
158
 
159
159
  0.1.78
160
+
161
+ 0.1.79
@@ -310,3 +310,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
310
310
  0.1.77
311
311
 
312
312
  0.1.78
313
+
314
+ 0.1.79
@@ -189,3 +189,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
189
189
  0.1.77
190
190
 
191
191
  0.1.78
192
+
193
+ 0.1.79
@@ -262,3 +262,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
262
262
  0.1.77
263
263
 
264
264
  0.1.78
265
+
266
+ 0.1.79
@@ -205,3 +205,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
205
205
  0.1.77
206
206
 
207
207
  0.1.78
208
+
209
+ 0.1.79
@@ -213,3 +213,5 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
213
213
  0.1.77
214
214
 
215
215
  0.1.78
216
+
217
+ 0.1.79
@@ -2,7 +2,7 @@
2
2
  "_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
3
3
  "identity": {
4
4
  "name": "cool-workflow",
5
- "version": "0.1.78",
5
+ "version": "0.1.79",
6
6
  "license": "BSD-2-Clause",
7
7
  "homepage": "https://github.com/coo1white/cool-workflow",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.1.78",
3
+ "version": "0.1.79",
4
4
  "bin": {
5
5
  "cool-workflow": "scripts/cw.js",
6
6
  "cw": "scripts/cw.js"
@@ -65,7 +65,7 @@ const canonicalApps = [
65
65
  "--source",
66
66
  "plugins/cool-workflow/docs/workflow-app-framework.7.md",
67
67
  "--scope",
68
- "Cool Workflow v0.1.78",
68
+ "Cool Workflow v0.1.79",
69
69
  "--freshness",
70
70
  "as of release preparation"
71
71
  ]
@@ -85,14 +85,14 @@ function main() {
85
85
  assert.ok(summary, `${app.id} must appear in app list`);
86
86
  assert.equal(summary.sourceKind, "app-directory");
87
87
  assert.equal(summary.legacy, false);
88
- assert.equal(summary.version, "0.1.78");
88
+ assert.equal(summary.version, "0.1.79");
89
89
 
90
90
  const validation = runJson(["app", "validate", manifestPath]);
91
91
  assert.equal(validation.valid, true, `${app.id} manifest must validate`);
92
92
 
93
93
  const shown = runJson(["app", "show", app.id]);
94
94
  assert.equal(shown.app.id, app.id);
95
- assert.equal(shown.app.version, "0.1.78");
95
+ assert.equal(shown.app.version, "0.1.79");
96
96
  assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
97
97
  assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
98
98
  assertTaskIdsUnique(shown);
@@ -103,7 +103,7 @@ function main() {
103
103
  const plan = runJson(["plan", app.id, ...app.args(workspace)]);
104
104
  const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
105
105
  assert.equal(state.workflow.app.id, app.id);
106
- assert.equal(state.workflow.app.version, "0.1.78");
106
+ assert.equal(state.workflow.app.version, "0.1.79");
107
107
  assert.equal(state.workflow.app.metadata.canonical, true);
108
108
  assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
109
109
  assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
@@ -5,7 +5,7 @@ const { spawnSync } = require("node:child_process");
5
5
  const fs = require("node:fs");
6
6
  const path = require("node:path");
7
7
 
8
- const TARGET_VERSION = "0.1.78";
8
+ const TARGET_VERSION = "0.1.79";
9
9
  const PREVIOUS_VERSION = "0.1.31";
10
10
  const pluginRoot = path.resolve(__dirname, "..");
11
11
  const repoRoot = path.resolve(pluginRoot, "..", "..");
@@ -33,7 +33,7 @@ function main() {
33
33
  const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
34
34
  assert.equal(appValidation.valid, true);
35
35
  assert.equal(appValidation.summary.id, "end-to-end-golden-path");
36
- assert.equal(appValidation.summary.version, "0.1.78");
36
+ assert.equal(appValidation.summary.version, "0.1.79");
37
37
 
38
38
  const plan = runJson(
39
39
  [
@@ -42,7 +42,7 @@ function main() {
42
42
  "--repo",
43
43
  tmp,
44
44
  "--question",
45
- "Prove the deterministic v0.1.78 end-to-end golden path."
45
+ "Prove the deterministic v0.1.79 end-to-end golden path."
46
46
  ],
47
47
  pluginRoot
48
48
  );
@@ -52,7 +52,7 @@ function main() {
52
52
 
53
53
  let state = readJson(plan.statePath);
54
54
  assert.equal(state.workflow.app.id, "end-to-end-golden-path");
55
- assert.equal(state.workflow.app.version, "0.1.78");
55
+ assert.equal(state.workflow.app.version, "0.1.79");
56
56
  assert.equal(state.loopStage, "interpret");
57
57
 
58
58
  const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
@@ -195,7 +195,7 @@ function main() {
195
195
  assert.equal(reportPath, plan.reportPath);
196
196
  assert.ok(fs.existsSync(reportPath));
197
197
  const report = fs.readFileSync(reportPath, "utf8");
198
- assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.78/);
198
+ assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.79/);
199
199
  assert.match(report, /## Candidates/);
200
200
  assert.match(report, /## Trust Audit/);
201
201
  assert.match(report, /## Acceptance Rationale/);