cool-workflow 0.1.80 → 0.1.82

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 (122) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +42 -2
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/agent-config.js +21 -7
  11. package/dist/candidate-scoring.js +62 -48
  12. package/dist/capability-core.js +152 -96
  13. package/dist/capability-registry.js +160 -174
  14. package/dist/cli.js +90 -100
  15. package/dist/collaboration.js +5 -6
  16. package/dist/commit.js +232 -209
  17. package/dist/compare.js +18 -0
  18. package/dist/coordinator/classify.js +45 -0
  19. package/dist/coordinator/paths.js +42 -0
  20. package/dist/coordinator/util.js +126 -0
  21. package/dist/coordinator.js +127 -300
  22. package/dist/dispatch.js +46 -3
  23. package/dist/drive.js +7 -7
  24. package/dist/error-feedback.js +8 -4
  25. package/dist/evidence-reasoning.js +5 -2
  26. package/dist/execution-backend/agent.js +294 -0
  27. package/dist/execution-backend/probes.js +96 -0
  28. package/dist/execution-backend/util.js +47 -0
  29. package/dist/execution-backend.js +77 -450
  30. package/dist/gates.js +48 -0
  31. package/dist/mcp-server.js +34 -173
  32. package/dist/multi-agent/graph.js +84 -0
  33. package/dist/multi-agent/helpers.js +141 -0
  34. package/dist/multi-agent/ids.js +20 -0
  35. package/dist/multi-agent/paths.js +22 -0
  36. package/dist/multi-agent-eval/format.js +194 -0
  37. package/dist/multi-agent-eval/normalize.js +51 -0
  38. package/dist/multi-agent-eval.js +63 -242
  39. package/dist/multi-agent-host.js +53 -40
  40. package/dist/multi-agent-operator-ux.js +2 -1
  41. package/dist/multi-agent-trust.js +5 -5
  42. package/dist/multi-agent.js +125 -314
  43. package/dist/node-projection.js +59 -0
  44. package/dist/node-snapshot.js +11 -21
  45. package/dist/observability/format.js +61 -0
  46. package/dist/observability/intake.js +98 -0
  47. package/dist/observability.js +14 -160
  48. package/dist/operator-ux/format.js +364 -0
  49. package/dist/operator-ux.js +22 -363
  50. package/dist/orchestrator/lifecycle-operations.js +22 -1
  51. package/dist/orchestrator/report.js +8 -0
  52. package/dist/orchestrator.js +41 -10
  53. package/dist/reclamation.js +32 -55
  54. package/dist/run-export.js +138 -14
  55. package/dist/run-registry/derive.js +172 -0
  56. package/dist/run-registry/format.js +124 -0
  57. package/dist/run-registry/gc.js +251 -0
  58. package/dist/run-registry/policy.js +16 -0
  59. package/dist/run-registry/queue.js +116 -0
  60. package/dist/run-registry.js +78 -593
  61. package/dist/run-state-schema.js +1 -0
  62. package/dist/sandbox-profile.js +43 -2
  63. package/dist/scheduler.js +34 -4
  64. package/dist/state-explosion/format.js +159 -0
  65. package/dist/state-explosion/helpers.js +82 -0
  66. package/dist/state-explosion.js +65 -283
  67. package/dist/state-node.js +19 -4
  68. package/dist/telemetry-attestation.js +55 -0
  69. package/dist/telemetry-demo.js +15 -3
  70. package/dist/telemetry-ledger.js +60 -15
  71. package/dist/topology.js +50 -12
  72. package/dist/triggers.js +33 -14
  73. package/dist/trust-audit.js +215 -71
  74. package/dist/validation.js +328 -0
  75. package/dist/version.js +1 -1
  76. package/dist/worker-isolation/helpers.js +51 -0
  77. package/dist/worker-isolation/paths.js +46 -0
  78. package/dist/worker-isolation.js +182 -173
  79. package/docs/agent-delegation-drive.7.md +14 -0
  80. package/docs/cli-mcp-parity.7.md +5 -0
  81. package/docs/contract-migration-tooling.7.md +3 -0
  82. package/docs/control-plane-scheduling.7.md +3 -0
  83. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
  84. package/docs/durable-state-and-locking.7.md +5 -0
  85. package/docs/evidence-adoption-reasoning-chain.7.md +3 -0
  86. package/docs/execution-backends.7.md +3 -0
  87. package/docs/index.md +1 -0
  88. package/docs/launch/launch-kit.md +46 -23
  89. package/docs/launch/pre-launch-checklist.md +14 -14
  90. package/docs/multi-agent-cli-mcp-surface.7.md +5 -0
  91. package/docs/multi-agent-eval-replay-harness.7.md +3 -0
  92. package/docs/multi-agent-operator-ux.7.md +3 -0
  93. package/docs/multi-agent-trust-policy-audit.7.md +27 -0
  94. package/docs/node-snapshot-diff-replay.7.md +3 -0
  95. package/docs/observability-cost-accounting.7.md +3 -0
  96. package/docs/project-index.md +23 -6
  97. package/docs/real-execution-backends.7.md +3 -0
  98. package/docs/release-and-migration.7.md +5 -0
  99. package/docs/release-tooling.7.md +35 -2
  100. package/docs/run-registry-control-plane.7.md +55 -8
  101. package/docs/run-retention-reclamation.7.md +5 -0
  102. package/docs/state-explosion-management.7.md +3 -0
  103. package/docs/team-collaboration.7.md +3 -0
  104. package/docs/trust-model.md +267 -0
  105. package/docs/vendor-manifest-loadability.7.md +43 -0
  106. package/docs/web-desktop-workbench.7.md +3 -0
  107. package/manifest/plugin.manifest.json +1 -1
  108. package/manifest/source-context-profiles.json +1 -1
  109. package/package.json +4 -2
  110. package/scripts/agents/builtin-templates.json +7 -0
  111. package/scripts/bump-version.js +5 -11
  112. package/scripts/canonical-apps-list.js +64 -0
  113. package/scripts/canonical-apps.js +19 -4
  114. package/scripts/children/batch-delegate-child.js +58 -0
  115. package/scripts/children/http-delegate-child.js +39 -0
  116. package/scripts/dogfood-release.js +1 -1
  117. package/scripts/golden-path.js +4 -4
  118. package/scripts/parity-check.js +5 -0
  119. package/scripts/release-check.js +5 -1
  120. package/scripts/release-flow.js +181 -5
  121. package/scripts/version-sync-check.js +5 -8
  122. package/dist/capability-dispatcher.js +0 -86
@@ -9,11 +9,12 @@
9
9
  // expressed here and called identically by both surfaces. A composite that lives
10
10
  // in only one surface is exactly the cross-surface drift v0.1.27 forbids.
11
11
  //
12
- // From v0.1.46: each exported entry function SHOULD self-register its capability
13
- // metadata via registerCapability() from capability-registry.ts. This replaces
14
- // the manual "add an entry to the giant array in capability-registry.ts" workflow
15
- // with automatic discovery. New capabilities just add a registerCapability() call
16
- // next to their implementation no need to touch capability-registry.ts.
12
+ // Capability metadata (which entry is on which surface, tool names, jsonMode) is
13
+ // declared in ONE place — the BUILTIN_CAPABILITIES table in capability-registry.ts,
14
+ // the single source of truth both surfaces and the parity gate read. New
15
+ // capabilities add a row there. (A v0.1.46 "self-register at load time" mechanism
16
+ // was removed: the registry snapshot was taken before those registrations ran, so
17
+ // they were silently dead duplicates of the table — see capability-registry.ts.)
17
18
  //
18
19
  // See docs/cli-mcp-parity.7.md and src/capability-registry.ts.
19
20
  var __importDefault = (this && this.__importDefault) || function (mod) {
@@ -37,6 +38,7 @@ exports.runArchive = runArchive;
37
38
  exports.runRerun = runRerun;
38
39
  exports.runExportArchive = runExportArchive;
39
40
  exports.runImportArchive = runImportArchive;
41
+ exports.runInspectArchive = runInspectArchive;
40
42
  exports.runVerifyImport = runVerifyImport;
41
43
  exports.queueAdd = queueAdd;
42
44
  exports.queueList = queueList;
@@ -65,13 +67,15 @@ exports.withoutRuntimeKeys = withoutRuntimeKeys;
65
67
  exports.optionalString = optionalString;
66
68
  exports.isRecord = isRecord;
67
69
  exports.telemetryVerify = telemetryVerify;
70
+ exports.auditVerify = auditVerify;
68
71
  exports.demoTamper = demoTamper;
69
- const capability_registry_1 = require("./capability-registry");
70
72
  const drive_1 = require("./drive");
71
73
  const agent_config_1 = require("./agent-config");
72
74
  const run_registry_1 = require("./run-registry");
73
75
  const observability_1 = require("./observability");
74
76
  const telemetry_ledger_1 = require("./telemetry-ledger");
77
+ const telemetry_attestation_1 = require("./telemetry-attestation");
78
+ const trust_audit_1 = require("./trust-audit");
75
79
  const telemetry_demo_1 = require("./telemetry-demo");
76
80
  const state_1 = require("./state");
77
81
  const run_export_1 = require("./run-export");
@@ -90,8 +94,6 @@ function planSummary(runner, workflowId, options) {
90
94
  pendingTasks: run.tasks.filter((task) => task.status === "pending").length
91
95
  };
92
96
  }
93
- // Auto-register with the capability registry (v0.1.46 — no need to edit capability-registry.ts)
94
- (0, capability_registry_1.registerCapability)({ capability: "plan", summary: "Plan a workflow run on a repo + app.", entry: "planSummary", surface: "both", cli: { path: ["plan"], jsonMode: "default" }, mcp: { tool: "cw_plan" } });
95
97
  // ---- canonical app-run payload --------------------------------------------
96
98
  // Both `cw app run` and `cw_app_run` resolve to this exact object. Structured
97
99
  // app inputs + optional sandbox resolution, then a compact operator status.
@@ -117,7 +119,6 @@ function appRun(runner, args) {
117
119
  sandboxProfile: resolvedSandbox
118
120
  };
119
121
  }
120
- (0, capability_registry_1.registerCapability)({ capability: "app.run", summary: "Plan a run on a named app with structured inputs.", entry: "appRun", surface: "both", cli: { path: ["app", "run"], caseTokens: ["app"], jsonMode: "default" }, mcp: { tool: "cw_app_run" } });
121
122
  // ---- canonical sandbox choice payload -------------------------------------
122
123
  // Both `cw sandbox choose|resolve` and `cw_sandbox_choose|cw_sandbox_resolve`
123
124
  // resolve to this exact object.
@@ -154,7 +155,6 @@ function commitEnvelope(runner, runId, args) {
154
155
  commit
155
156
  };
156
157
  }
157
- (0, capability_registry_1.registerCapability)({ capability: "commit", summary: "Create a verifier-gated state commit with evidence.", entry: "commitEnvelope", surface: "both", cli: { path: ["commit"], jsonMode: "default" }, mcp: { tool: "cw_commit" }, payloadIdentical: false, reason: "CLI renders a human summary with path hints; MCP returns the structured commit payload alone." });
158
158
  function compactOperatorStatus(status) {
159
159
  return {
160
160
  runId: status.runId,
@@ -198,18 +198,16 @@ function flag(value) {
198
198
  return false;
199
199
  return Boolean(value);
200
200
  }
201
- function withInvocationCwd(args, fn) {
202
- const cwd = optionalString(args.cwd);
203
- if (!cwd)
204
- return fn();
205
- const previous = process.cwd();
206
- process.chdir(cwd);
207
- try {
208
- return fn();
209
- }
210
- finally {
211
- process.chdir(previous);
212
- }
201
+ // F7: explicit invocation cwd — no more process.chdir bracket.
202
+ // The runner resolves a run from process.cwd() by default; to operate WITH a run's
203
+ // repo as base (run --drive --repo X from anywhere; cross-directory quickstart; run
204
+ // import/export against a target dir) we now pass that base EXPLICITLY —
205
+ // runner.withBaseDir(dir).loadRun(...) for run resolution (see
206
+ // CoolWorkflowRunner.withBaseDir), and invocationCwd(args) to anchor relative path
207
+ // args. Nothing mutates the global process.cwd(), so concurrent in-process callers
208
+ // can no longer corrupt each other's working directory (the former reentrancy hazard).
209
+ function invocationCwd(args) {
210
+ return node_path_1.default.resolve(optionalString(args.cwd) || process.cwd());
213
211
  }
214
212
  function runRegistryRefresh(reg, args) {
215
213
  return reg.refresh({ scope: scopeOf(args, "repo") });
@@ -242,11 +240,19 @@ function runList(reg, args) {
242
240
  function runShow(reg, runId, args) {
243
241
  return reg.showRun(runId, { scope: scopeOf(args, "home") });
244
242
  }
245
- function runResume(reg, runId, args) {
246
- return reg.resume(runId, {
243
+ function runResume(reg, runner, runId, args) {
244
+ const base = reg.resume(runId, {
247
245
  scope: scopeOf(args, "home"),
248
246
  limit: args.limit === undefined ? undefined : Number(args.limit)
249
247
  });
248
+ // Default (no --drive/--once): read-only, byte-identical to before.
249
+ if (!isTrue(args.drive) && !isTrue(args.once))
250
+ return base;
251
+ // Opt-in continuation: hand the resolved run to the EXISTING agent-delegation
252
+ // drive loop (re-plans nothing; picks up pending/running tasks from durable
253
+ // state). An unconfigured agent surfaces drive.status="blocked" (fail-closed).
254
+ const drive = runDrive(runner, { ...args, runId: base.runId, repo: base.repo, once: isTrue(args.once) });
255
+ return { ...base, drive };
250
256
  }
251
257
  function runArchive(reg, runId, args) {
252
258
  if (runId) {
@@ -269,25 +275,33 @@ function runRerun(reg, runId, args) {
269
275
  return reg.rerun(runId, { scope: scopeOf(args, "home"), reason: optionalString(args.reason) });
270
276
  }
271
277
  function runExportArchive(runner, runId, args) {
272
- return withInvocationCwd(args, () => {
273
- const output = optionalString(args.output || args.path || args.archive) || `${runId}.cwrun.json`;
274
- return (0, run_export_1.exportRun)(runner.loadRun(runId), node_path_1.default.resolve(output));
275
- });
278
+ const base = invocationCwd(args);
279
+ const output = optionalString(args.output || args.path || args.archive) || `${runId}.cwrun.json`;
280
+ return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), node_path_1.default.resolve(base, output));
276
281
  }
277
282
  function runImportArchive(runner, args) {
278
- return withInvocationCwd(args, () => {
279
- const archive = optionalString(args.archive || args.path || args.file);
280
- if (!archive)
281
- throw new Error("run import requires an archive path (positional, --archive, --path, or --file)");
282
- const target = optionalString(args.target || args.repo || args.cwd) || process.cwd();
283
- const imported = (0, run_export_1.importRun)(node_path_1.default.resolve(archive), node_path_1.default.resolve(target));
284
- const registry = new run_registry_1.RunRegistry(node_path_1.default.resolve(target), runner);
285
- const registryReport = registry.refresh({ scope: "repo" });
286
- return { ...imported, registry: registryReport };
287
- });
283
+ const base = invocationCwd(args);
284
+ const archive = optionalString(args.archive || args.path || args.file);
285
+ if (!archive)
286
+ throw new Error("run import requires an archive path (positional, --archive, --path, or --file)");
287
+ const target = node_path_1.default.resolve(base, optionalString(args.target || args.repo || args.cwd) || base);
288
+ const imported = (0, run_export_1.importRun)(node_path_1.default.resolve(base, archive), target);
289
+ const registry = new run_registry_1.RunRegistry(target, runner.withBaseDir(target));
290
+ const registryReport = registry.refresh({ scope: "repo" });
291
+ return { ...imported, registry: registryReport };
292
+ }
293
+ // Read-only: inspect a portable archive's integrity WITHOUT importing it. Routes
294
+ // both surfaces through one shared core entry. The runner is unused (no registry
295
+ // touch — inspection writes nothing) but kept for dispatch-signature symmetry.
296
+ function runInspectArchive(_runner, args) {
297
+ const base = invocationCwd(args);
298
+ const archive = optionalString(args.archive || args.path || args.file);
299
+ if (!archive)
300
+ throw new Error("run inspect-archive requires an archive path (positional, --archive, --path, or --file)");
301
+ return (0, run_export_1.inspectArchive)(node_path_1.default.resolve(base, archive));
288
302
  }
289
303
  function runVerifyImport(runner, runId, args) {
290
- return withInvocationCwd(args, () => (0, run_export_1.verifyImportedRun)(runner.loadRun(runId)));
304
+ return (0, run_export_1.verifyImportedRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId));
291
305
  }
292
306
  function queueAdd(reg, args) {
293
307
  return reg.queueAdd({
@@ -422,7 +436,8 @@ const DRIVE_RUNTIME_KEYS = [
422
436
  "agentModel",
423
437
  "agent-model",
424
438
  "agentTimeoutMs",
425
- "agent-timeout-ms"
439
+ "agent-timeout-ms",
440
+ "resume"
426
441
  ];
427
442
  function planInputsFor(args) {
428
443
  const copy = withoutRuntimeKeys(args);
@@ -438,26 +453,25 @@ function planInputsFor(args) {
438
453
  * the golden path runs each verb from the repo dir) — then restores cwd. This lets
439
454
  * `cw run <app> --drive --repo X` work when invoked from anywhere. */
440
455
  function runDrive(runner, args) {
441
- const cwd0 = process.cwd();
442
- try {
443
- let runId = optionalString(args.runId || args.run);
444
- let repoCwd = optionalString(args.repo);
445
- if (!runId) {
446
- const appId = String(args.appId || args.workflowId || args.app || "");
447
- if (!appId)
448
- throw new Error("run --drive requires an app id (or --run <run-id> to continue)");
449
- const run = runner.plan(appId, planInputsFor(args));
450
- runId = run.id;
451
- repoCwd = run.cwd;
452
- }
453
- if (repoCwd && repoCwd !== process.cwd() && node_fs_1.default.existsSync(repoCwd))
454
- process.chdir(repoCwd);
455
- return (0, drive_1.drive)(runner, runId, { once: isTrue(args.once), now: optionalString(args.now), args });
456
- }
457
- finally {
458
- if (process.cwd() !== cwd0)
459
- process.chdir(cwd0);
456
+ let runId = optionalString(args.runId || args.run);
457
+ let repoCwd = optionalString(args.repo);
458
+ if (!runId) {
459
+ const appId = String(args.appId || args.workflowId || args.app || "");
460
+ if (!appId)
461
+ throw new Error("run --drive requires an app id (or --run <run-id> to continue)");
462
+ const run = runner.plan(appId, planInputsFor(args));
463
+ runId = run.id;
464
+ repoCwd = run.cwd;
460
465
  }
466
+ // The runner resolves the run from its baseDir, so the drive must run WITH the
467
+ // run's repo as base. Pass it explicitly via withBaseDir (F7 — no process.chdir).
468
+ const target = repoCwd && node_fs_1.default.existsSync(repoCwd) ? repoCwd : undefined;
469
+ const driveRunId = runId;
470
+ return (0, drive_1.drive)(runner.withBaseDir(target), driveRunId, {
471
+ once: isTrue(args.once),
472
+ now: optionalString(args.now),
473
+ args
474
+ });
461
475
  }
462
476
  /** The app the one-command quickstart plans when none is named. */
463
477
  exports.QUICKSTART_DEFAULT_APP = "architecture-review";
@@ -474,50 +488,44 @@ exports.QUICKSTART_DEFAULT_APP = "architecture-review";
474
488
  function quickstart(runner, args) {
475
489
  const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
476
490
  const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
491
+ // `--resume`: a discoverability flag over the existing continuation. With no
492
+ // `--run`, advance exactly ONE step (reuse the `--once` path) and print a
493
+ // copy-pasteable continue line; with `--run <id>`, continue that run to
494
+ // completion (the default drive). It adds no new execution path.
495
+ const resume = isTrue(args.resume);
496
+ const resumeRunId = resume ? optionalString(args.runId || args.run) : undefined;
477
497
  // `--preview`: read-only, deterministic next-step projection (no spawn, no commit).
478
498
  // Plan a fresh run (the read-only first verb) then project the next drive step.
479
499
  if (isTrue(args.preview)) {
480
- const cwd0 = process.cwd();
481
- try {
482
- let runId = optionalString(args.runId || args.run);
483
- let repoCwd = optionalString(args.repo);
484
- if (!runId) {
485
- const run = runner.plan(appId, planInputsFor(args));
486
- runId = run.id;
487
- repoCwd = run.cwd;
488
- }
489
- if (repoCwd && repoCwd !== process.cwd() && node_fs_1.default.existsSync(repoCwd))
490
- process.chdir(repoCwd);
491
- return (0, drive_1.drivePreview)(runner, runId, args);
492
- }
493
- finally {
494
- if (process.cwd() !== cwd0)
495
- process.chdir(cwd0);
500
+ let runId = optionalString(args.runId || args.run);
501
+ let repoCwd = optionalString(args.repo);
502
+ if (!runId) {
503
+ const run = runner.plan(appId, planInputsFor(args));
504
+ runId = run.id;
505
+ repoCwd = run.cwd;
496
506
  }
507
+ const previewRunId = runId;
508
+ const target = repoCwd && node_fs_1.default.existsSync(repoCwd) ? repoCwd : undefined;
509
+ return (0, drive_1.drivePreview)(runner.withBaseDir(target), previewRunId, args);
497
510
  }
498
511
  // Drive end-to-end (or one `--once` step). runDrive plans the run, delegates each
499
512
  // worker to the agent backend, and commits — we add only the report write + a
500
513
  // single assembled payload. No orchestration is duplicated here.
501
- const result = runDrive(runner, { ...args, appId });
514
+ // `--resume` with no run id advances a single step so a newcomer WITNESSES the
515
+ // stop-then-resume; with a run id it continues to completion. Non-resume paths
516
+ // are untouched (byte-identical default).
517
+ const result = runDrive(runner, { ...args, appId, ...(resume && !resumeRunId ? { once: true } : {}) });
502
518
  // Always (re)write the report so the one command yields a report.md on disk, even
503
519
  // when the drive blocked/parked (a partial report is still useful triage).
504
- const cwd0 = process.cwd();
505
- let reportPath = result.reportPath;
506
- try {
507
- // runDrive restored cwd, so the runs root would resolve against the CALLER's
508
- // cwd here orphaning the run when quickstart is invoked cross-directory
509
- // (cwd = plugin dir, --repo elsewhere: the README's headline command). The
510
- // run's statePath (<repo>/.cw/runs/<id>/state.json) is authoritative however
511
- // the run was planned or continued; chdir to ITS repo BEFORE any run read.
512
- const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
513
- if (runRepoCwd !== process.cwd() && node_fs_1.default.existsSync(runRepoCwd))
514
- process.chdir(runRepoCwd);
515
- reportPath = runner.report(result.runId).path;
516
- }
517
- finally {
518
- if (process.cwd() !== cwd0)
519
- process.chdir(cwd0);
520
- }
520
+ //
521
+ // runDrive restored cwd, so the runs root would resolve against the CALLER's cwd
522
+ // here — orphaning the run when quickstart is invoked cross-directory (cwd =
523
+ // plugin dir, --repo elsewhere: the README's headline command). The run's
524
+ // statePath (<repo>/.cw/runs/<id>/state.json) is authoritative however the run
525
+ // was planned or continued; resolve to ITS repo BEFORE any run read, reentrant-safe.
526
+ const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
527
+ const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
528
+ const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
521
529
  let hint;
522
530
  if (!agentConfigured) {
523
531
  hint =
@@ -530,7 +538,9 @@ function quickstart(runner, args) {
530
538
  hint = `the drive is blocked — inspect: cw run drive ${result.runId}`;
531
539
  }
532
540
  else if (result.status === "in-progress") {
533
- hint = `one step advanced (--once) — continue: cw quickstart ${appId} --run ${result.runId} --once`;
541
+ hint = resume
542
+ ? `one step advanced — continue: cw quickstart ${appId} --run ${result.runId} --resume`
543
+ : `one step advanced (--once) — continue: cw quickstart ${appId} --run ${result.runId} --once`;
534
544
  }
535
545
  return {
536
546
  schemaVersion: 1,
@@ -546,7 +556,11 @@ function quickstart(runner, args) {
546
556
  statePath: result.statePath,
547
557
  agentConfigured,
548
558
  steps: result.steps,
549
- hint
559
+ hint,
560
+ // Stamp resumedFrom ONLY when we continued an explicit run. Conditional spread
561
+ // keeps the key absent on the default/fresh path (own-property absent + omitted
562
+ // by JSON.stringify), so default output is byte-identical.
563
+ ...(resumeRunId ? { resumedFrom: resumeRunId } : {})
550
564
  };
551
565
  }
552
566
  /** Read-only, deterministic projection of the effective agent config (secret-stripped). */
@@ -680,15 +694,57 @@ function telemetryVerify(runner, args) {
680
694
  throw new Error("telemetry verify requires a run id (cw telemetry verify <run-id>)");
681
695
  const run = runner.loadRun(runId);
682
696
  const v = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
697
+ // Opt-in independent signature re-verification. verifyTelemetryLedger re-proves
698
+ // the chain (so the stored attestation verdicts were not edited); supplying the
699
+ // trust public key (--pubkey / CW_AGENT_ATTEST_PUBKEY) additionally RE-RUNS the
700
+ // ed25519 check over each `attested` record's stored raw usage rather than
701
+ // trusting that verdict, so a forged signature can no longer ride a green chain.
702
+ const trustPublicKeyInput = optionalString(args.pubkey || args.pubKey || args.publicKey) || process.env.CW_AGENT_ATTEST_PUBKEY;
703
+ const trustPublicKey = (0, telemetry_attestation_1.resolveTrustPublicKey)(trustPublicKeyInput);
704
+ const keyChecks = trustPublicKeyInput && !trustPublicKey
705
+ ? [{ name: "signature-key", pass: false, code: "telemetry-pubkey-unreadable" }]
706
+ : [];
707
+ const sig = (0, telemetry_attestation_1.verifyTelemetrySignatures)(v.records, trustPublicKey);
708
+ const failedChecks = [...v.checks.filter((c) => !c.pass), ...keyChecks, ...sig.checks.filter((c) => !c.pass)];
683
709
  return {
684
710
  schemaVersion: 1,
685
711
  runId: run.id,
686
712
  present: v.present,
687
- verified: v.verified,
713
+ // Chain integrity AND (when a key was supplied) every attested signature must
714
+ // re-verify. With no key, sig.failed is 0 → unchanged chain-only behavior.
715
+ verified: v.verified && keyChecks.length === 0 && sig.failed === 0,
688
716
  records: v.records.length,
689
717
  attested: v.attested,
690
718
  unattested: v.unattested,
691
719
  absent: v.absent,
720
+ signatureKeyProvided: sig.keyProvided,
721
+ signaturesChecked: sig.checked,
722
+ signaturesReverified: sig.reverified,
723
+ signaturesFailed: sig.failed,
724
+ failedChecks: failedChecks.map((c) => ({ name: c.name, code: c.code }))
725
+ };
726
+ }
727
+ // audit.verify — fail-closed re-prove of a run's trust-audit hash chain. The peer
728
+ // of telemetry.verify for the sandbox/policy/commit-gate decision log: recomputes
729
+ // every event hash from genesis, checks chain linkage, and catches the
730
+ // unchained-event forgery. Exposed as a verb (not just embedded in `audit summary`,
731
+ // which always exits 0) so `cw audit verify <run> && deploy` can gate on the exit
732
+ // code. POLA: a run with no audit log is present:false / verified:true / exit 0.
733
+ function auditVerify(runner, args) {
734
+ const runId = optionalString(args.runId || args.run);
735
+ if (!runId)
736
+ throw new Error("audit verify requires a run id (cw audit verify <run-id>)");
737
+ const run = runner.loadRun(runId);
738
+ const v = (0, trust_audit_1.verifyTrustAudit)(run);
739
+ return {
740
+ schemaVersion: 1,
741
+ runId: run.id,
742
+ present: v.present,
743
+ verified: v.verified,
744
+ eventCount: v.eventCount,
745
+ chained: v.chained,
746
+ unchained: v.unchained,
747
+ corruptLines: v.corruptLines,
692
748
  failedChecks: v.checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code }))
693
749
  };
694
750
  }