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
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WORKER_ISOLATION_SCHEMA_VERSION = void 0;
7
- exports.createWorkerIsolation = createWorkerIsolation;
8
7
  exports.allocateWorkerScope = allocateWorkerScope;
9
8
  exports.writeWorkerManifest = writeWorkerManifest;
10
9
  exports.syncWorkerScopeFromTask = syncWorkerScopeFromTask;
@@ -33,21 +32,10 @@ const multi_agent_1 = require("./multi-agent");
33
32
  const telemetry_attestation_1 = require("./telemetry-attestation");
34
33
  const telemetry_ledger_1 = require("./telemetry-ledger");
35
34
  const coordinator_1 = require("./coordinator");
35
+ const helpers_1 = require("./worker-isolation/helpers");
36
+ const paths_1 = require("./worker-isolation/paths");
37
+ const validation_1 = require("./validation");
36
38
  exports.WORKER_ISOLATION_SCHEMA_VERSION = 1;
37
- const WORKER_SCOPE_FILE = "worker.json";
38
- const WORKER_MANIFEST_FILE = "manifest.json";
39
- function createWorkerIsolation(options = {}) {
40
- return {
41
- allocateWorkerScope: (run, task, allocateOptions) => allocateWorkerScope(run, task, { ...options, ...allocateOptions }),
42
- writeWorkerManifest,
43
- listWorkerScopes: (run, listOptions) => listWorkerScopes(run, listOptions),
44
- getWorkerScope,
45
- recordWorkerOutput,
46
- recordWorkerFailure,
47
- validateWorkerBoundary,
48
- summarizeWorkers
49
- };
50
- }
51
39
  function allocateWorkerScope(run, task, options = {}) {
52
40
  ensureWorkerState(run);
53
41
  const existing = task.workerId ? getWorkerScope(run, task.workerId) : undefined;
@@ -64,7 +52,7 @@ function allocateWorkerScope(run, task, options = {}) {
64
52
  return existing;
65
53
  }
66
54
  const now = new Date().toISOString();
67
- const workerId = options.workerId || createWorkerId(run, task.id);
55
+ const workerId = options.workerId || (0, paths_1.createWorkerId)(run, task.id);
68
56
  const workerDir = node_path_1.default.join(workerRoot(run), (0, state_1.safeFileName)(workerId));
69
57
  const inputPath = node_path_1.default.join(workerDir, "input.md");
70
58
  const resultPath = node_path_1.default.join(workerDir, "result.md");
@@ -82,7 +70,10 @@ function allocateWorkerScope(run, task, options = {}) {
82
70
  extraReadPaths: options.policy?.readPaths || [],
83
71
  extraWritePaths: [...(options.policy?.writePaths || []), ...(options.policy?.allowedPaths || [])],
84
72
  allowArtifacts: options.policy?.allowArtifacts,
85
- allowLogs: options.policy?.allowLogs
73
+ allowLogs: options.policy?.allowLogs,
74
+ // H7: persisted custom profile definitions so a custom logical id resolves
75
+ // against THIS worker's context (worker-specific path tokens bind correctly).
76
+ customProfiles: run.customSandboxProfiles
86
77
  });
87
78
  const allowedPaths = (0, sandbox_profile_1.effectiveSandboxWritePaths)(sandboxPolicy);
88
79
  (0, sandbox_profile_1.upsertRunSandboxPolicy)(run, sandboxPolicy);
@@ -123,7 +114,7 @@ function allocateWorkerScope(run, task, options = {}) {
123
114
  feedbackIds: [],
124
115
  errors: [],
125
116
  multiAgent: options.multiAgent,
126
- metadata: compactMetadata({
117
+ metadata: (0, helpers_1.compactMetadata)({
127
118
  ...options.metadata,
128
119
  multiAgent: options.multiAgent,
129
120
  phase: task.phase,
@@ -165,7 +156,7 @@ function allocateWorkerScope(run, task, options = {}) {
165
156
  });
166
157
  }
167
158
  task.workerId = scope.id;
168
- task.workerManifestPath = manifestPath(scope);
159
+ task.workerManifestPath = (0, paths_1.manifestPath)(scope);
169
160
  task.sandboxProfileId = sandboxPolicy.id;
170
161
  task.sandboxPolicy = sandboxPolicy;
171
162
  task.backendId = backendId;
@@ -180,8 +171,8 @@ function writeWorkerManifest(run, scope) {
180
171
  const task = run.tasks.find((candidate) => candidate.id === scope.taskId);
181
172
  const sandboxPolicy = scope.sandboxPolicy || sandboxPolicyForBoundary(run, scope);
182
173
  const sandboxProfileId = scope.sandboxProfileId || sandboxPolicy.id;
183
- const scopePath = workerScopePath(scope);
184
- const workerManifestPath = manifestPath(scope);
174
+ const scopePath = (0, paths_1.workerScopePath)(scope);
175
+ const workerManifestPath = (0, paths_1.manifestPath)(scope);
185
176
  const manifest = {
186
177
  schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
187
178
  id: scope.id,
@@ -257,7 +248,7 @@ function syncWorkerScopeFromTask(run, workerId) {
257
248
  ...scope,
258
249
  updatedAt: new Date().toISOString(),
259
250
  multiAgent: task.multiAgent,
260
- metadata: compactMetadata({
251
+ metadata: (0, helpers_1.compactMetadata)({
261
252
  ...(scope.metadata || {}),
262
253
  multiAgent: task.multiAgent
263
254
  })
@@ -267,7 +258,7 @@ function syncWorkerScopeFromTask(run, workerId) {
267
258
  function listWorkerScopes(run, options = {}) {
268
259
  ensureWorkerState(run);
269
260
  const scopes = loadWorkerScopesFromDisk(run);
270
- run.workers = mergeScopes(run.workers || [], scopes);
261
+ run.workers = (0, helpers_1.mergeScopes)(run.workers || [], scopes);
271
262
  const listed = run.workers || [];
272
263
  return options.status ? listed.filter((scope) => scope.status === options.status) : listed;
273
264
  }
@@ -276,14 +267,34 @@ function getWorkerScope(run, workerId) {
276
267
  const existing = (run.workers || []).find((scope) => scope.id === workerId);
277
268
  if (existing)
278
269
  return existing;
279
- const file = node_path_1.default.join(workerRoot(run), (0, state_1.safeFileName)(workerId), WORKER_SCOPE_FILE);
270
+ const file = node_path_1.default.join(workerRoot(run), (0, state_1.safeFileName)(workerId), paths_1.WORKER_SCOPE_FILE);
280
271
  if (!node_fs_1.default.existsSync(file))
281
272
  return undefined;
282
- const scope = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
273
+ const scope = (0, validation_1.validateWorkerScope)(JSON.parse(node_fs_1.default.readFileSync(file, "utf8")));
283
274
  upsertWorkerScope(run, scope);
284
275
  return scope;
285
276
  }
286
277
  function recordWorkerOutput(run, workerId, resultPath, options = {}) {
278
+ // Accept-path orchestrator. The recorded order + side effects of these ordered
279
+ // steps are load-bearing (replay determinism + the hash-chained audit/telemetry
280
+ // ledgers cross-link by parent event ids), so each helper runs exactly where it
281
+ // did before and mutates the shared `accept` context in place. Do NOT reorder.
282
+ const accept = validateWorkerResult(run, workerId, resultPath, options);
283
+ const delegation = attestWorkerDelegation(accept);
284
+ acceptWorkerResult(accept, delegation);
285
+ recordWorkerDelegationLedger(accept, delegation);
286
+ runWorkerVerify(accept);
287
+ recordWorkerCompletion(accept, delegation);
288
+ fanOutWorkerOutput(accept);
289
+ if (options.persist !== false)
290
+ (0, state_1.saveCheckpoint)(run);
291
+ return accept.output;
292
+ }
293
+ /** Step 1 — validateResult: resolve scope/task, enforce the sandbox boundary, the
294
+ * result-file existence, the envelope contract, and (opt-in) resolvable evidence.
295
+ * Fail-closed: any guard records a worker failure and throws BEFORE accept-side
296
+ * state mutation. Returns the partially-filled accept context on success. */
297
+ function validateWorkerResult(run, workerId, resultPath, options) {
287
298
  const scope = requireWorkerScope(run, workerId);
288
299
  const task = requireWorkerTask(run, scope);
289
300
  const absoluteResultPath = node_path_1.default.resolve(resultPath);
@@ -302,7 +313,7 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
302
313
  throw new Error(violation.message);
303
314
  }
304
315
  if (!node_fs_1.default.existsSync(absoluteResultPath)) {
305
- const error = structuredError("worker-result-missing", `Worker result file does not exist: ${absoluteResultPath}`, {
316
+ const error = (0, helpers_1.structuredError)("worker-result-missing", `Worker result file does not exist: ${absoluteResultPath}`, {
306
317
  path: absoluteResultPath,
307
318
  retryable: true
308
319
  });
@@ -321,11 +332,35 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
321
332
  const baseDirs = Array.from(new Set([run.cwd, process.cwd(), scope.workerDir, run.paths.runDir].filter(Boolean)));
322
333
  const unresolved = (0, evidence_grounding_1.unresolvedFileEvidence)(parsedResult.evidence, baseDirs);
323
334
  if (unresolved.length) {
324
- const error = structuredError("worker-evidence-unresolvable", `Worker ${workerId} result cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, { path: absoluteResultPath, retryable: false });
335
+ const error = (0, helpers_1.structuredError)("worker-evidence-unresolvable", `Worker ${workerId} result cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, { path: absoluteResultPath, retryable: false });
325
336
  recordWorkerFailure(run, workerId, error, { ...options, persist: options.persist });
326
337
  throw new Error(error.message);
327
338
  }
328
339
  }
340
+ return {
341
+ run,
342
+ workerId,
343
+ options,
344
+ scope,
345
+ task,
346
+ absoluteResultPath,
347
+ rawResult,
348
+ parsedResult,
349
+ destination: "",
350
+ pathAuditId: "",
351
+ acceptedAuditId: "",
352
+ resultNode: undefined,
353
+ verifierNodeId: "",
354
+ verifierStatus: "",
355
+ output: undefined
356
+ };
357
+ }
358
+ /** Step 2 — attestSandbox/attestDelegation: verify the agent's signed telemetry
359
+ * BEFORE recording it, enforce the opt-in require-attested-telemetry gate (still
360
+ * fail-closed, pre-mutation), and build the agent-hop provenance. Non-agent hops
361
+ * return an empty delegation. */
362
+ function attestWorkerDelegation(accept) {
363
+ const { run, workerId, options, task, absoluteResultPath, rawResult } = accept;
329
364
  // Agent Delegation Drive (v0.1.38): if this worker's result.md was produced by an
330
365
  // EXTERNAL agent, record the agent-hop attestation AS PROVENANCE — the agent
331
366
  // (kind:process) handle, the agent-REPORTED model (never CW_AGENT_MODEL), the
@@ -345,7 +380,7 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
345
380
  // it instead of recording unverifiable usage. Default behavior is unchanged
346
381
  // (flag-and-surface). Non-agent hops carry no verdict and are never blocked.
347
382
  if (options.requireAttestedTelemetry && telemetry && telemetry.status !== "attested") {
348
- const error = structuredError("telemetry-unattested-blocked", `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`, { path: absoluteResultPath, retryable: false });
383
+ const error = (0, helpers_1.structuredError)("telemetry-unattested-blocked", `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`, { path: absoluteResultPath, retryable: false });
349
384
  recordWorkerFailure(run, workerId, error, { ...options, persist: options.persist });
350
385
  throw new Error(error.message);
351
386
  }
@@ -365,6 +400,16 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
365
400
  ...(telemetry ? { usageAttestation: telemetry.status, usageAttestationReason: telemetry.reason } : {})
366
401
  }
367
402
  : undefined;
403
+ return { agentDelegation, telemetry };
404
+ }
405
+ /** Step 3 — recordStateNode/audit: the irreversible accept. Records the allowed
406
+ * path decision, copies the result into the run results dir, completes the task,
407
+ * builds + appends the result node, emits the accepted audit event, re-normalizes
408
+ * node evidence against both audit ids, and surfaces the empty-capture warning.
409
+ * Writes destination/pathAuditId/acceptedAuditId/resultNode back into `accept`. */
410
+ function acceptWorkerResult(accept, delegation) {
411
+ const { run, workerId, scope, task, absoluteResultPath, parsedResult } = accept;
412
+ const { agentDelegation } = delegation;
368
413
  const pathAudit = (0, trust_audit_1.recordSandboxPathDecision)(run, {
369
414
  workerId,
370
415
  taskId: task.id,
@@ -453,58 +498,75 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
453
498
  metadata: { reason: "no findings or evidence captured from result.md", resultPath: destination }
454
499
  });
455
500
  }
501
+ accept.destination = destination;
502
+ accept.pathAuditId = pathAudit.id;
503
+ accept.acceptedAuditId = acceptedAudit.id;
504
+ accept.resultNode = resultNode;
505
+ }
506
+ /** Step 4 — recordTelemetryLedger: the agent-hop attestation. Binds the telemetry
507
+ * verdict into the append-only hash-chained ledger BEFORE the audit event (so the
508
+ * event can cross-link the record hash), then emits the worker.agent-delegation
509
+ * audit event. No-op for non-agent hops. */
510
+ function recordWorkerDelegationLedger(accept, delegation) {
511
+ const { agentDelegation } = delegation;
456
512
  // The agent-hop attestation event — hung off worker.output, alongside
457
513
  // worker.backend. Recorded in trust-audit/provenance, NEVER in node evidence.
458
- if (agentDelegation) {
459
- // Track 1 (tamper-evidence): bind this verdict into the append-only,
460
- // hash-chained telemetry ledger BEFORE the audit event, so the event can
461
- // cross-link the record hash. Editing the recorded verdict/usage later breaks
462
- // the chain (verifyTelemetryLedger). Only when a verdict was computed.
463
- const ledgerRecord = agentDelegation.usageAttestation
464
- ? (0, telemetry_ledger_1.appendTelemetryAttestation)(run, {
465
- workerId,
466
- taskId: task.id,
467
- promptDigest: agentDelegation.promptDigest,
468
- reportedUsage: agentDelegation.reportedUsage,
469
- usageSignature: agentDelegation.usageSignature,
470
- attestation: agentDelegation.usageAttestation,
471
- attestationReason: agentDelegation.usageAttestationReason
472
- })
473
- : undefined;
474
- (0, trust_audit_1.recordTrustAuditEvent)(run, {
475
- kind: "worker.agent-delegation",
476
- decision: "recorded",
477
- source: "host-attested",
514
+ if (!agentDelegation)
515
+ return;
516
+ const { run, workerId, scope, task, resultNode, acceptedAuditId } = accept;
517
+ // Track 1 (tamper-evidence): bind this verdict into the append-only,
518
+ // hash-chained telemetry ledger BEFORE the audit event, so the event can
519
+ // cross-link the record hash. Editing the recorded verdict/usage later breaks
520
+ // the chain (verifyTelemetryLedger). Only when a verdict was computed.
521
+ const ledgerRecord = agentDelegation.usageAttestation
522
+ ? (0, telemetry_ledger_1.appendTelemetryAttestation)(run, {
478
523
  workerId,
479
524
  taskId: task.id,
480
- nodeId: resultNode.id,
481
- sandboxProfileId: scope.sandboxProfileId,
482
- policySnapshot: scope.sandboxPolicy,
483
- parentEventIds: [acceptedAudit.id],
484
- metadata: {
485
- backendId: agentDelegation.backendId,
486
- handleKind: agentDelegation.handle.kind,
487
- handleRef: agentDelegation.handle.ref,
488
- model: agentDelegation.model,
489
- promptDigest: agentDelegation.promptDigest,
490
- resultDigest: agentDelegation.resultDigest,
491
- command: agentDelegation.command,
492
- args: agentDelegation.args,
493
- exitCode: agentDelegation.exitCode,
494
- // Track 1: the telemetry verdict travels with the agent-hop event so the
495
- // audit report can surface `unattested` usage loudly. Absent ⇒ no usage.
496
- ...(agentDelegation.usageAttestation
497
- ? {
498
- telemetryAttestation: agentDelegation.usageAttestation,
499
- ...(agentDelegation.usageAttestationReason ? { telemetryAttestationReason: agentDelegation.usageAttestationReason } : {}),
500
- ...(agentDelegation.reportedUsage ? { reportedUsage: agentDelegation.reportedUsage } : {}),
501
- // Cross-link to the hash-chained ledger entry (tamper-evidence).
502
- ...(ledgerRecord ? { telemetryRecordId: ledgerRecord.recordId, telemetryRecordHash: ledgerRecord.recordHash, telemetryPrevHash: ledgerRecord.prevHash } : {})
503
- }
504
- : {})
505
- }
506
- });
507
- }
525
+ promptDigest: agentDelegation.promptDigest,
526
+ reportedUsage: agentDelegation.reportedUsage,
527
+ usageSignature: agentDelegation.usageSignature,
528
+ attestation: agentDelegation.usageAttestation,
529
+ attestationReason: agentDelegation.usageAttestationReason
530
+ })
531
+ : undefined;
532
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
533
+ kind: "worker.agent-delegation",
534
+ decision: "recorded",
535
+ source: "host-attested",
536
+ workerId,
537
+ taskId: task.id,
538
+ nodeId: resultNode.id,
539
+ sandboxProfileId: scope.sandboxProfileId,
540
+ policySnapshot: scope.sandboxPolicy,
541
+ parentEventIds: [acceptedAuditId],
542
+ metadata: {
543
+ backendId: agentDelegation.backendId,
544
+ handleKind: agentDelegation.handle.kind,
545
+ handleRef: agentDelegation.handle.ref,
546
+ model: agentDelegation.model,
547
+ promptDigest: agentDelegation.promptDigest,
548
+ resultDigest: agentDelegation.resultDigest,
549
+ command: agentDelegation.command,
550
+ args: agentDelegation.args,
551
+ exitCode: agentDelegation.exitCode,
552
+ // Track 1: the telemetry verdict travels with the agent-hop event so the
553
+ // audit report can surface `unattested` usage loudly. Absent ⇒ no usage.
554
+ ...(agentDelegation.usageAttestation
555
+ ? {
556
+ telemetryAttestation: agentDelegation.usageAttestation,
557
+ ...(agentDelegation.usageAttestationReason ? { telemetryAttestationReason: agentDelegation.usageAttestationReason } : {}),
558
+ ...(agentDelegation.reportedUsage ? { reportedUsage: agentDelegation.reportedUsage } : {}),
559
+ // Cross-link to the hash-chained ledger entry (tamper-evidence).
560
+ ...(ledgerRecord ? { telemetryRecordId: ledgerRecord.recordId, telemetryRecordHash: ledgerRecord.recordHash, telemetryPrevHash: ledgerRecord.prevHash } : {})
561
+ }
562
+ : {})
563
+ }
564
+ });
565
+ }
566
+ /** Step 5 — runVerify: drive the verify pipeline stage off the accepted result node
567
+ * and record the verifier node id on the task + accept context. */
568
+ function runWorkerVerify(accept) {
569
+ const { run, workerId, scope, task, parsedResult, destination, resultNode } = accept;
508
570
  const verifierResult = (0, pipeline_runner_1.createPipelineRunner)({ persist: false }).runPipelineStage(run, "verify", resultNode.id, {
509
571
  outputNodeId: `${run.id}:verifier:${task.id}`,
510
572
  outputStatus: "verified",
@@ -517,14 +579,24 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
517
579
  metadata: { taskId: task.id, workerId, resultNodeId: resultNode.id, sandboxProfileId: scope.sandboxProfileId }
518
580
  });
519
581
  task.verifierNodeId = verifierResult.outputNodeId;
582
+ accept.verifierNodeId = verifierResult.outputNodeId;
583
+ // Carry the verify verdict for the scope-status transition in recordWorkerCompletion.
584
+ accept.verifierStatus = verifierResult.status;
585
+ }
586
+ /** Step 6 — recordStateNode (worker record): assemble the worker output record +
587
+ * host-attested usage record, then persist the worker scope with the verify-derived
588
+ * status, output digest/size, and (when present) usage. */
589
+ function recordWorkerCompletion(accept, delegation) {
590
+ const { run, workerId, scope, task, absoluteResultPath, rawResult, resultNode, verifierNodeId, verifierStatus, pathAuditId, acceptedAuditId } = accept;
591
+ const { agentDelegation, telemetry } = delegation;
520
592
  const output = {
521
593
  workerId,
522
594
  taskId: task.id,
523
595
  resultPath: absoluteResultPath,
524
596
  recordedAt: new Date().toISOString(),
525
597
  stateNodeId: resultNode.id,
526
- verifierNodeId: verifierResult.outputNodeId,
527
- auditEventIds: [pathAudit.id, acceptedAudit.id]
598
+ verifierNodeId,
599
+ auditEventIds: [pathAuditId, acceptedAuditId]
528
600
  };
529
601
  // Host-attested usage rides on the worker record. Recorded when the agent
530
602
  // REPORTED a model OR token usage — `unreported`/absent stays ABSENT (never
@@ -547,7 +619,7 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
547
619
  updateWorkerScope(run, {
548
620
  ...scope,
549
621
  updatedAt: new Date().toISOString(),
550
- status: verifierResult.status === "advanced" ? "verified" : "completed",
622
+ status: verifierStatus === "advanced" ? "verified" : "completed",
551
623
  resultNodeId: resultNode.id,
552
624
  output,
553
625
  // Output integrity (v0.1.63): SHA256 digest + file size
@@ -555,20 +627,23 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
555
627
  outputSizeBytes: Buffer.byteLength(rawResult, "utf8"),
556
628
  ...(usageRecord ? { usage: usageRecord } : {})
557
629
  });
558
- const blackboardLinks = publishWorkerOutputToBlackboard(run, scope, task, parsedResult.summary, destination, absoluteResultPath, resultNode.evidence, acceptedAudit.id);
630
+ accept.output = output;
631
+ }
632
+ /** Step 7 — fanOut: publish the accepted output to the blackboard and record the
633
+ * multi-agent worker output (linking the blackboard message/artifact refs). */
634
+ function fanOutWorkerOutput(accept) {
635
+ const { run, workerId, scope, task, absoluteResultPath, parsedResult, destination, resultNode, verifierNodeId, acceptedAuditId } = accept;
636
+ const blackboardLinks = publishWorkerOutputToBlackboard(run, scope, task, parsedResult.summary, destination, absoluteResultPath, resultNode.evidence, acceptedAuditId);
559
637
  (0, multi_agent_1.recordMultiAgentWorkerOutput)(run, {
560
638
  workerId,
561
639
  taskId: task.id,
562
640
  resultNodeId: resultNode.id,
563
- verifierNodeId: verifierResult.outputNodeId,
641
+ verifierNodeId,
564
642
  evidence: resultNode.evidence,
565
643
  artifactPaths: [destination, absoluteResultPath],
566
644
  blackboardMessageIds: blackboardLinks.messageIds,
567
645
  blackboardArtifactRefIds: blackboardLinks.artifactRefIds
568
646
  });
569
- if (options.persist !== false)
570
- (0, state_1.saveCheckpoint)(run);
571
- return output;
572
647
  }
573
648
  function recordWorkerFailure(run, workerId, error, options = {}) {
574
649
  const scope = requireWorkerScope(run, workerId);
@@ -581,7 +656,7 @@ function recordWorkerFailure(run, workerId, error, options = {}) {
581
656
  status: "pending",
582
657
  loopStage: "adjust",
583
658
  inputs: { workerId, taskId: task.id, dispatchId: scope.dispatchId },
584
- artifacts: workerArtifacts(scope),
659
+ artifacts: (0, paths_1.workerArtifacts)(scope),
585
660
  parents: task.stateNodeId ? [task.stateNodeId] : [],
586
661
  contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
587
662
  metadata: { workerId, taskId: task.id, dispatchId: scope.dispatchId, workerDir: scope.workerDir, sandboxProfileId: scope.sandboxProfileId }
@@ -636,7 +711,7 @@ function recordWorkerFailure(run, workerId, error, options = {}) {
636
711
  updatedAt: new Date().toISOString(),
637
712
  status: structured.code === "worker-boundary-violation" || structured.code.startsWith("sandbox-") ? "rejected" : "failed",
638
713
  retryCount: typeof options.retryCount === "number" ? options.retryCount : scope.retryCount,
639
- feedbackIds: unique([...(scope.feedbackIds || []), feedback.id]),
714
+ feedbackIds: (0, helpers_1.unique)([...(scope.feedbackIds || []), feedback.id]),
640
715
  errors: [...(scope.errors || []), structured]
641
716
  });
642
717
  if (options.persist !== false)
@@ -649,7 +724,7 @@ function recordWorkerRetryAttempt(run, workerId, attempts, reason, options = {})
649
724
  ...scope,
650
725
  updatedAt: new Date().toISOString(),
651
726
  retryCount: attempts,
652
- metadata: compactMetadata({
727
+ metadata: (0, helpers_1.compactMetadata)({
653
728
  ...scope.metadata,
654
729
  agentDelegationAttempts: attempts,
655
730
  agentDelegationLastFailure: reason
@@ -668,8 +743,8 @@ function summarizeWorkers(run) {
668
743
  const workers = listWorkerScopes(run);
669
744
  return {
670
745
  total: workers.length,
671
- byStatus: countBy(workers, (scope) => scope.status),
672
- manifestPaths: workers.map(manifestPath),
746
+ byStatus: (0, helpers_1.countBy)(workers, (scope) => scope.status),
747
+ manifestPaths: workers.map(paths_1.manifestPath),
673
748
  failed: workers
674
749
  .filter((scope) => scope.status === "failed" || scope.status === "rejected")
675
750
  .map((scope) => ({ id: scope.id, status: scope.status, feedbackIds: scope.feedbackIds || [] }))
@@ -705,15 +780,9 @@ function reclaimOrphans(run, now) {
705
780
  }
706
781
  if (orphans.length) {
707
782
  writeWorkerIndex(run);
708
- saveWorkerCheckpoint(run);
709
783
  }
710
784
  return { runId: run.id, reclaimed: orphans.length, orphans };
711
785
  }
712
- function saveWorkerCheckpoint(run) {
713
- // Durable write via atomic temp+rename (same contract as saveCheckpoint)
714
- // For worker index, the atomic write in writeWorkerIndex already handles it.
715
- // This is a no-op wrapper that signals the checkpoint boundary.
716
- }
717
786
  function ensureWorkerState(run) {
718
787
  run.paths.workersDir = run.paths.workersDir || node_path_1.default.join(run.paths.runDir, "workers");
719
788
  node_fs_1.default.mkdirSync(run.paths.workersDir, { recursive: true });
@@ -771,7 +840,7 @@ function updateWorkerScope(run, scope) {
771
840
  return updated;
772
841
  }
773
842
  function writeWorkerScope(scope) {
774
- (0, state_1.writeJson)(workerScopePath(scope), scope);
843
+ (0, state_1.writeJson)((0, paths_1.workerScopePath)(scope), scope);
775
844
  }
776
845
  function writeWorkerIndex(run) {
777
846
  ensureWorkerState(run);
@@ -784,7 +853,7 @@ function writeWorkerIndex(run) {
784
853
  dispatchId: scope.dispatchId,
785
854
  status: scope.status,
786
855
  workerDir: scope.workerDir,
787
- manifestPath: manifestPath(scope),
856
+ manifestPath: (0, paths_1.manifestPath)(scope),
788
857
  resultPath: scope.resultPath,
789
858
  sandboxProfileId: scope.sandboxProfileId,
790
859
  backendId: scope.backendId,
@@ -800,9 +869,9 @@ function loadWorkerScopesFromDisk(run) {
800
869
  return node_fs_1.default
801
870
  .readdirSync(workerRoot(run), { withFileTypes: true })
802
871
  .filter((entry) => entry.isDirectory())
803
- .map((entry) => node_path_1.default.join(workerRoot(run), entry.name, WORKER_SCOPE_FILE))
872
+ .map((entry) => node_path_1.default.join(workerRoot(run), entry.name, paths_1.WORKER_SCOPE_FILE))
804
873
  .filter((file) => node_fs_1.default.existsSync(file))
805
- .map((file) => JSON.parse(node_fs_1.default.readFileSync(file, "utf8")));
874
+ .map((file) => (0, validation_1.validateWorkerScope)(JSON.parse(node_fs_1.default.readFileSync(file, "utf8"))));
806
875
  }
807
876
  function requireWorkerScope(run, workerId) {
808
877
  const scope = getWorkerScope(run, workerId);
@@ -823,6 +892,12 @@ function sandboxPolicyForBoundary(run, scope, options = {}) {
823
892
  if (scope.sandboxPolicy && !options.policy && !options.sandboxProfileId)
824
893
  return scope.sandboxPolicy;
825
894
  const profileId = options.sandboxProfileId || options.policy?.sandboxProfileId || scope.sandboxProfileId || sandbox_profile_1.DEFAULT_SANDBOX_PROFILE_ID;
895
+ // H7: when the scope.sandboxPolicy snapshot is LOST, this re-resolves the policy
896
+ // by its logical profileId against the WORKER's paths (scope.workerDir etc.). For
897
+ // a CUSTOM profile the bundled lookup would throw not-found; threading
898
+ // run.customSandboxProfiles lets resolveSandboxProfileById re-resolve the persisted
899
+ // DEFINITION here — re-enforcing the same policy with worker-correct path tokens
900
+ // (NOT the dispatch-time paths), so a legitimate worker write is not falsely denied.
826
901
  return (0, sandbox_profile_1.sandboxPolicyForWorker)(profileId, {
827
902
  cwd: run.cwd,
828
903
  runDir: run.paths.runDir,
@@ -838,7 +913,8 @@ function sandboxPolicyForBoundary(run, scope, options = {}) {
838
913
  ...(!scope.sandboxPolicy ? scope.allowedPaths || [] : [])
839
914
  ],
840
915
  allowArtifacts: options.policy?.allowArtifacts,
841
- allowLogs: options.policy?.allowLogs
916
+ allowLogs: options.policy?.allowLogs,
917
+ customProfiles: run.customSandboxProfiles
842
918
  });
843
919
  }
844
920
  function blackboardManifest(run, scope) {
@@ -927,7 +1003,7 @@ function blackboardLinkage(run, scope) {
927
1003
  const role = scope.multiAgent?.roleId ? run.multiAgent?.roles.find((entry) => entry.id === scope.multiAgent?.roleId) : undefined;
928
1004
  const multiAgentRun = scope.multiAgent?.runId ? run.multiAgent?.runs.find((entry) => entry.id === scope.multiAgent?.runId) : undefined;
929
1005
  const blackboardId = membership?.blackboardId || group?.blackboardId || role?.blackboardId || multiAgentRun?.blackboardId;
930
- const topicIds = unique([
1006
+ const topicIds = (0, helpers_1.unique)([
931
1007
  ...(membership?.topicIds || []),
932
1008
  ...(group?.topicIds || []),
933
1009
  ...(role?.topicIds || []),
@@ -935,94 +1011,27 @@ function blackboardLinkage(run, scope) {
935
1011
  ]);
936
1012
  return { blackboardId, topicIds };
937
1013
  }
938
- function manifestPath(scope) {
939
- return node_path_1.default.join(scope.workerDir, WORKER_MANIFEST_FILE);
940
- }
941
- function workerScopePath(scope) {
942
- return node_path_1.default.join(scope.workerDir, WORKER_SCOPE_FILE);
943
- }
944
- // Deterministic worker id (v0.1.40 self-audit P2): a wall-clock stamp + Math.random()
945
- // made every dispatch mint a different id, so audit references were not reproducible
946
- // across re-runs of the same inputs. The id is now derived from the task plus a
947
- // per-task sequence (count of worker scopes already allocated for that task + 1),
948
- // so re-running the same workflow yields byte-identical worker ids while retries of
949
- // the SAME task still get a fresh, unique id. (workerId is excluded from the
950
- // snapshot source fingerprint, so this does not change replay digests.)
951
- function createWorkerId(run, taskId) {
952
- const prefix = `worker-${(0, state_1.safeFileName)(taskId)}-`;
953
- const seq = (run.workers || []).filter((scope) => scope.id.startsWith(prefix)).length + 1;
954
- return `${prefix}${String(seq).padStart(4, "0")}`;
955
- }
956
- function workerArtifacts(scope) {
957
- return [
958
- { id: "worker", kind: "json", path: workerScopePath(scope) },
959
- { id: "worker-manifest", kind: "json", path: manifestPath(scope) },
960
- { id: "worker-input", kind: "markdown", path: scope.inputPath }
961
- ];
962
- }
963
1014
  function normalizeWorkerError(error, scope, options) {
964
- if (isBoundaryViolation(error)) {
965
- return structuredError(error.code, error.message, {
1015
+ if ((0, helpers_1.isBoundaryViolation)(error)) {
1016
+ return (0, helpers_1.structuredError)(error.code, error.message, {
966
1017
  path: error.path,
967
1018
  retryable: false,
968
1019
  details: { allowedPaths: error.allowedPaths, workerId: scope.id, taskId: scope.taskId, sandboxProfileId: scope.sandboxProfileId }
969
1020
  });
970
1021
  }
971
- if (isStateNodeError(error)) {
1022
+ if ((0, helpers_1.isStateNodeError)(error)) {
972
1023
  return {
973
1024
  ...error,
974
1025
  at: error.at || new Date().toISOString(),
975
1026
  path: options.path || error.path,
976
1027
  retryable: options.retryable ?? error.retryable ?? false,
977
- details: compactMetadata({ ...(error.details || {}), workerId: scope.id, taskId: scope.taskId })
1028
+ details: (0, helpers_1.compactMetadata)({ ...(error.details || {}), workerId: scope.id, taskId: scope.taskId })
978
1029
  };
979
1030
  }
980
1031
  const message = error instanceof Error ? error.message : String(error);
981
- return structuredError(options.code || "worker-runtime-error", message, {
1032
+ return (0, helpers_1.structuredError)(options.code || "worker-runtime-error", message, {
982
1033
  path: options.path,
983
1034
  retryable: options.retryable ?? false,
984
1035
  details: { workerId: scope.id, taskId: scope.taskId }
985
1036
  });
986
1037
  }
987
- function structuredError(code, message, options = {}) {
988
- return {
989
- code,
990
- message,
991
- at: new Date().toISOString(),
992
- path: options.path,
993
- retryable: options.retryable,
994
- details: options.details
995
- };
996
- }
997
- function isBoundaryViolation(value) {
998
- return Boolean(value && typeof value === "object" && "allowedPaths" in value && "message" in value);
999
- }
1000
- function isStateNodeError(value) {
1001
- return Boolean(value && typeof value === "object" && "code" in value && "message" in value);
1002
- }
1003
- function mergeScopes(left, right) {
1004
- const merged = [...left];
1005
- for (const scope of right) {
1006
- const index = merged.findIndex((candidate) => candidate.id === scope.id);
1007
- if (index >= 0)
1008
- merged[index] = scope;
1009
- else
1010
- merged.push(scope);
1011
- }
1012
- return merged;
1013
- }
1014
- function unique(values) {
1015
- return Array.from(new Set(values.filter(Boolean)));
1016
- }
1017
- function compactMetadata(value) {
1018
- const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
1019
- return entries.length ? Object.fromEntries(entries) : undefined;
1020
- }
1021
- function countBy(items, key) {
1022
- const counts = {};
1023
- for (const item of items) {
1024
- const value = key(item);
1025
- counts[value] = (counts[value] || 0) + 1;
1026
- }
1027
- return counts;
1028
- }
@@ -131,8 +131,17 @@ node dist/cli.js backend probe agent --json # ready iff configured, else un
131
131
  node dist/cli.js run architecture-review --drive --repo /path/to/repo --question "Is the design sound?"
132
132
  node dist/cli.js run architecture-review --drive --once --repo /path/to/repo --question "..." # one step
133
133
  node dist/cli.js run drive <run-id> --json # read-only preview of the next step
134
+
135
+ # quickstart --resume: a guided stop-then-resume a newcomer can WITNESS in <5 min
136
+ node dist/cli.js quickstart --resume --repo /path/to/repo --question "..." # advances ONE step, prints a continue line
137
+ node dist/cli.js quickstart --run <run-id> --resume # continues that run to completion
134
138
  ```
135
139
 
140
+ `quickstart --resume` with no `--run` drives a single step and prints a
141
+ copy-pasteable `cw quickstart --run <id> --resume` continue line; rerun it with the
142
+ `--run <id>` to finish. The continuing invocation echoes `resumedFrom: <id>`. Bare
143
+ `quickstart` (no `--resume`) is unchanged — it drives straight to completion.
144
+
136
145
  For faster first results, use the opt-in fast app instead of changing the full
137
146
  review contract:
138
147
 
@@ -248,3 +257,8 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
248
257
  ## Fast Architecture Review (v0.1.80)
249
258
 
250
259
  Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
260
+
261
+ ## Resumable Drive & Resume Routing (v0.1.81)
262
+
263
+ Adds `run resume <id> --drive/--once` alongside `quickstart --resume`: a stopped pipeline resumes in-place, advancing to completion (`--drive`) or one deterministic step (`--once`) over the same plan->dispatch->agent-fulfill->accept->commit lifecycle, echoing `resumedFrom: <id>`. Fixes the `run resume --drive` CLI routing so the drive flag reaches the resumed run instead of being read as an app name. Replay determinism and the agent evidence triple are unchanged.
264
+ _No behavioral change in v0.1.82 (drive/quickstart resolve the run repo via an explicit base directory rather than process.chdir; delegation behavior is unchanged)._
@@ -385,3 +385,8 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
385
385
  ## Fast Architecture Review (v0.1.80)
386
386
 
387
387
  Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
388
+
389
+ ## Re-Prove Verbs on Both Surfaces (v0.1.81)
390
+
391
+ v0.1.81 grows the parity surface with two new both-surface, fail-closed verbs declared once in the capability registry: `cw audit verify` / `cw_audit_verify` re-proves the trust-audit chain and exits non-zero on any unverified or corrupt chain, and `cw run inspect-archive` / `cw_run_inspect_archive` is a read-only archive integrity check. Each `cw <cmd> --json` is schema-identical to its `cw_<tool>` and validated by the same parity gate.
392
+ _No changes in v0.1.82._
@@ -127,3 +127,6 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
127
127
  ## Fast Architecture Review (v0.1.80)
128
128
 
129
129
  Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
130
+
131
+ _No changes to the contract-migration subsystem in v0.1.81._
132
+ _No changes in v0.1.82._