cool-workflow 0.1.83 → 0.1.85

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 (60) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +6 -0
  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/capability-core.js +89 -2
  11. package/dist/capability-registry.js +269 -2
  12. package/dist/cli/command-surface.js +1362 -0
  13. package/dist/cli.js +2 -1318
  14. package/dist/mcp-server.js +5 -1452
  15. package/dist/mcp-surface.js +1467 -0
  16. package/dist/run-export.js +139 -1
  17. package/dist/telemetry-demo.js +119 -0
  18. package/dist/types/report-bundle.js +6 -0
  19. package/dist/types.js +1 -0
  20. package/dist/version.js +1 -1
  21. package/dist/worker-accept/acceptance.js +114 -0
  22. package/dist/worker-accept/blackboard-fanout.js +80 -0
  23. package/dist/worker-accept/blackboard-linkage.js +19 -0
  24. package/dist/worker-accept/context.js +2 -0
  25. package/dist/worker-accept/telemetry-ledger.js +116 -0
  26. package/dist/worker-accept/validation.js +77 -0
  27. package/dist/worker-accept/verifier-completion.js +73 -0
  28. package/dist/worker-isolation.js +19 -444
  29. package/docs/agent-delegation-drive.7.md +6 -0
  30. package/docs/cli-mcp-parity.7.md +13 -3
  31. package/docs/contract-migration-tooling.7.md +6 -0
  32. package/docs/control-plane-scheduling.7.md +6 -0
  33. package/docs/durable-state-and-locking.7.md +6 -0
  34. package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
  35. package/docs/execution-backends.7.md +6 -0
  36. package/docs/index.md +1 -0
  37. package/docs/multi-agent-cli-mcp-surface.7.md +6 -0
  38. package/docs/multi-agent-eval-replay-harness.7.md +6 -0
  39. package/docs/multi-agent-operator-ux.7.md +6 -0
  40. package/docs/node-snapshot-diff-replay.7.md +6 -0
  41. package/docs/observability-cost-accounting.7.md +6 -0
  42. package/docs/project-index.md +17 -6
  43. package/docs/real-execution-backends.7.md +6 -0
  44. package/docs/release-and-migration.7.md +6 -0
  45. package/docs/release-tooling.7.md +16 -1
  46. package/docs/report-verifiable-bundle.7.md +123 -0
  47. package/docs/run-registry-control-plane.7.md +6 -0
  48. package/docs/run-retention-reclamation.7.md +6 -0
  49. package/docs/state-explosion-management.7.md +6 -0
  50. package/docs/team-collaboration.7.md +6 -0
  51. package/docs/web-desktop-workbench.7.md +6 -0
  52. package/manifest/plugin.manifest.json +1 -1
  53. package/package.json +1 -1
  54. package/scripts/bump-version.js +9 -1
  55. package/scripts/canonical-apps.js +4 -4
  56. package/scripts/dogfood-release.js +1 -1
  57. package/scripts/golden-path.js +4 -4
  58. package/scripts/parity-check.js +27 -57
  59. package/scripts/release-flow.js +7 -6
  60. package/scripts/sync-project-index.js +1 -1
@@ -16,19 +16,26 @@ exports.importRun = importRun;
16
16
  exports.verifyImportedRun = verifyImportedRun;
17
17
  exports.inspectArchive = inspectArchive;
18
18
  exports.importManifestPath = importManifestPath;
19
+ exports.verifyReportBundle = verifyReportBundle;
19
20
  const node_fs_1 = __importDefault(require("node:fs"));
21
+ const node_os_1 = __importDefault(require("node:os"));
20
22
  const node_path_1 = __importDefault(require("node:path"));
21
23
  const node_crypto_1 = __importDefault(require("node:crypto"));
22
24
  const state_1 = require("./state");
23
25
  const version_1 = require("./version");
24
26
  const telemetry_ledger_1 = require("./telemetry-ledger");
27
+ const telemetry_attestation_1 = require("./telemetry-attestation");
25
28
  const trust_audit_1 = require("./trust-audit");
26
29
  const compare_1 = require("./compare");
27
30
  /** Export a run to a portable JSON archive with run-local bytes and digests. */
28
- function exportRun(run, outputPath) {
31
+ function exportRun(run, outputPath, options = {}) {
29
32
  const exportedAt = new Date().toISOString();
30
33
  const files = collectArchiveFiles(run);
31
34
  const manifestSha256 = digestManifest(files);
35
+ // Embed ONLY a public key. resolveTrustPublicKey normalizes an inline PEM or a
36
+ // file path down to inline PEM bytes so the archive is self-contained; a value
37
+ // that resolves to nothing (bad path) yields no trust block rather than a throw.
38
+ const trustPublicKeyPem = (0, telemetry_attestation_1.resolveTrustPublicKey)(options.trustPublicKey);
32
39
  const exported = {
33
40
  schemaVersion: 1,
34
41
  exportedAt,
@@ -39,6 +46,7 @@ function exportRun(run, outputPath) {
39
46
  fileCount: files.length,
40
47
  manifestSha256
41
48
  },
49
+ ...(trustPublicKeyPem ? { trust: { publicKeyPem: trustPublicKeyPem, algorithm: "ed25519" } } : {}),
42
50
  // Legacy field retained so old readers still find an artifact-ish list.
43
51
  artifacts: files
44
52
  .filter((file) => file.role === "artifact")
@@ -62,6 +70,7 @@ function exportRun(run, outputPath) {
62
70
  artifactCount: files.filter((file) => file.role === "artifact").length,
63
71
  auditFileCount: files.filter((file) => file.role === "audit").length,
64
72
  telemetryIncluded: files.some((file) => file.role === "telemetry"),
73
+ trustKeyEmbedded: Boolean(trustPublicKeyPem),
65
74
  manifestSha256,
66
75
  archiveSha256
67
76
  };
@@ -276,6 +285,135 @@ function inspectArchive(archivePath) {
276
285
  function importManifestPath(run) {
277
286
  return node_path_1.default.join(run.paths.runDir, "import-manifest.json");
278
287
  }
288
+ /** Verify a portable run bundle OFFLINE and SELF-CONTAINED: prove the archive bytes,
289
+ * the telemetry hash chain, the trust-audit chain, and (with the bundle's embedded
290
+ * public key) the ed25519 signatures — WITHOUT a source repo, a pre-existing .cw
291
+ * tree, or an out-of-band key. Reuses the existing import + restore-verify path: it
292
+ * restores into an auto-cleaned tmpdir so a stranger's machine is left untouched.
293
+ * Never throws — every failure is a structured check and a false `ok` (exit-1 worthy).
294
+ *
295
+ * Key precedence is bundle > argument > environment, so the artifact verifies the
296
+ * same on any machine; only when the bundle omits a key do the override/env apply. */
297
+ function verifyReportBundle(archivePath, options = {}) {
298
+ const inspect = inspectArchive(archivePath);
299
+ const failedChecks = inspect.checks
300
+ .filter((check) => !check.pass)
301
+ .map((check) => ({ name: check.name, code: check.code }));
302
+ const base = {
303
+ schemaVersion: 1,
304
+ archivePath,
305
+ runId: inspect.runId,
306
+ ok: false,
307
+ archiveOk: inspect.ok,
308
+ telemetryVerified: false,
309
+ trustAuditVerified: false,
310
+ trustKeySource: "none",
311
+ signatureKeyProvided: false,
312
+ signaturesChecked: 0,
313
+ signaturesReverified: 0,
314
+ signaturesFailed: 0,
315
+ failedChecks
316
+ };
317
+ // A bundle that is not even a supported archive cannot be restored — report the
318
+ // inspection failure and stop (fail-closed, no tmpdir, nothing written).
319
+ if (!inspect.schemaSupported)
320
+ return base;
321
+ // Read the embedded trust key (and, if requested, the report bytes) straight from
322
+ // the archive JSON. inspectArchive already proved the bytes parse + digest-match.
323
+ let bundleKey;
324
+ let reportContent;
325
+ try {
326
+ const raw = JSON.parse(node_fs_1.default.readFileSync(archivePath, "utf8"));
327
+ bundleKey = raw.trust?.publicKeyPem;
328
+ if (options.extractReportTo) {
329
+ const reportFile = (raw.files || []).find((file) => file.relativePath === "report.md");
330
+ if (reportFile)
331
+ reportContent = Buffer.from(reportFile.contentBase64, "base64").toString("utf8");
332
+ }
333
+ }
334
+ catch {
335
+ /* inspect already recorded the parse failure; treat key as absent */
336
+ }
337
+ const trustKeySource = bundleKey
338
+ ? "bundle"
339
+ : options.pubkey
340
+ ? "argument"
341
+ : process.env.CW_AGENT_ATTEST_PUBKEY
342
+ ? "environment"
343
+ : "none";
344
+ const trustKey = (0, telemetry_attestation_1.resolveTrustPublicKey)(bundleKey || options.pubkey || process.env.CW_AGENT_ATTEST_PUBKEY);
345
+ const tmpDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "cw-verify-bundle-"));
346
+ let telemetryVerified = false;
347
+ let trustAuditVerified = false;
348
+ let signaturesChecked = 0;
349
+ let signaturesReverified = 0;
350
+ let signaturesFailed = 0;
351
+ let reportExtractedTo;
352
+ try {
353
+ // Restore into the throwaway tree. importRun digest-checks every file and throws
354
+ // on the first mismatch (or on a stripped integrity block under
355
+ // CW_REQUIRE_ARCHIVE_INTEGRITY=1) — caught below as a failed "restore" check.
356
+ const imported = importRun(archivePath, tmpDir);
357
+ for (const check of imported.verification.checks) {
358
+ if (check.name === "telemetry-ledger")
359
+ telemetryVerified = check.pass;
360
+ if (check.name === "trust-audit")
361
+ trustAuditVerified = check.pass;
362
+ if (!check.pass)
363
+ failedChecks.push({ name: check.name, code: check.code });
364
+ }
365
+ // Independent ed25519 re-verification over the restored ledger using the bundle's
366
+ // own key (or override/env). With no key, attested records degrade to
367
+ // informational (signaturesFailed stays 0); the chain check above still gates ok.
368
+ const ledger = (0, telemetry_ledger_1.verifyTelemetryLedger)(imported.run);
369
+ const sig = (0, telemetry_attestation_1.verifyTelemetrySignatures)(ledger.records, trustKey);
370
+ signaturesChecked = sig.checked;
371
+ signaturesReverified = sig.reverified;
372
+ signaturesFailed = sig.failed;
373
+ for (const check of sig.checks)
374
+ if (!check.pass)
375
+ failedChecks.push({ name: check.name, code: check.code });
376
+ if (options.extractReportTo && reportContent !== undefined) {
377
+ reportExtractedTo = node_path_1.default.resolve(options.extractReportTo);
378
+ node_fs_1.default.writeFileSync(reportExtractedTo, reportContent);
379
+ }
380
+ }
381
+ catch (error) {
382
+ failedChecks.push({ name: "restore", code: messageOf(error) });
383
+ }
384
+ finally {
385
+ node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
386
+ }
387
+ // Strict mode: a bundle that claims attested telemetry but offers no key to check
388
+ // it is unverifiable — refuse rather than green it.
389
+ const strictShortfall = Boolean(options.strictSignatures) && signaturesChecked > 0 && !trustKey;
390
+ if (strictShortfall)
391
+ failedChecks.push({ name: "signatures", code: "signature-key-required" });
392
+ // Extraction was requested but could not be fulfilled (no report.md in the bundle,
393
+ // or the write failed): fail closed rather than silently green a missing artifact —
394
+ // otherwise `report bundle <run> --extract-report r.md && send r.md` would ship
395
+ // nothing (or a stale file) with exit 0. A requested-but-absent output is a failure,
396
+ // not a no-op (distinct from extraction never being requested).
397
+ const extractShortfall = Boolean(options.extractReportTo) && !reportExtractedTo;
398
+ if (extractShortfall)
399
+ failedChecks.push({ name: "extract-report", code: "report-md-unavailable" });
400
+ return {
401
+ schemaVersion: 1,
402
+ archivePath,
403
+ runId: inspect.runId,
404
+ ok: inspect.ok && telemetryVerified && trustAuditVerified && signaturesFailed === 0 && !strictShortfall && !extractShortfall,
405
+ archiveOk: inspect.ok,
406
+ telemetryVerified,
407
+ trustAuditVerified,
408
+ trustKeySource,
409
+ signatureKeyProvided: Boolean(trustKey),
410
+ signaturesChecked,
411
+ signaturesReverified,
412
+ signaturesFailed,
413
+ reportExtractedTo,
414
+ failedChecks
415
+ };
416
+ }
279
417
  function collectArchiveFiles(run) {
280
418
  const entries = new Map();
281
419
  for (const file of walkFiles(run.paths.runDir)) {
@@ -22,12 +22,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  exports.formatTelemetryVerify = formatTelemetryVerify;
23
23
  exports.formatTamperDemo = formatTamperDemo;
24
24
  exports.runTamperDemo = runTamperDemo;
25
+ exports.runBundleDemo = runBundleDemo;
26
+ exports.formatBundleDemo = formatBundleDemo;
25
27
  const node_crypto_1 = __importDefault(require("node:crypto"));
26
28
  const node_fs_1 = __importDefault(require("node:fs"));
27
29
  const node_os_1 = __importDefault(require("node:os"));
28
30
  const node_path_1 = __importDefault(require("node:path"));
29
31
  const telemetry_ledger_1 = require("./telemetry-ledger");
30
32
  const telemetry_attestation_1 = require("./telemetry-attestation");
33
+ const run_export_1 = require("./run-export");
34
+ const state_1 = require("./state");
31
35
  const execution_backend_1 = require("./execution-backend");
32
36
  /** Human-facing render of `telemetry verify <run>`. */
33
37
  function formatTelemetryVerify(r) {
@@ -164,3 +168,118 @@ function runTamperDemo(options = {}) {
164
168
  layers.every((l) => l.before.verified && !l.after.verified && l.failures.length > 0);
165
169
  return { schemaVersion: 1, runId, workers: HOPS.length, trustKey: "ephemeral-ed25519", baseline, layers, proven };
166
170
  }
171
+ function runBundleDemo(options = {}) {
172
+ const workdir = options.dir || node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "cw-bundle-demo-"));
173
+ node_fs_1.default.mkdirSync(workdir, { recursive: true });
174
+ const runId = "demo-bundle-run";
175
+ const runDir = node_path_1.default.join(workdir, ".cw", "runs", runId);
176
+ const paths = (0, state_1.createRunPaths)(runDir);
177
+ (0, state_1.ensureRunDirs)(paths);
178
+ const { publicKey, privateKey } = node_crypto_1.default.generateKeyPairSync("ed25519");
179
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
180
+ const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
181
+ // Build a real signed ledger + a cited report, the way an attested run would.
182
+ const ledgerRun = { id: runId, paths };
183
+ for (const hop of HOPS) {
184
+ const ctx = { runId, taskId: hop.taskId, promptDigest: hop.promptDigest };
185
+ (0, telemetry_ledger_1.appendTelemetryAttestation)(ledgerRun, {
186
+ workerId: hop.workerId,
187
+ taskId: hop.taskId,
188
+ promptDigest: hop.promptDigest,
189
+ reportedUsage: hop.usage,
190
+ usageSignature: hop.attestation === "attested" ? (0, telemetry_attestation_1.signTelemetry)(hop.usage, privateKeyPem, ctx) : undefined,
191
+ attestation: hop.attestation,
192
+ now: DEMO_NOW
193
+ });
194
+ }
195
+ node_fs_1.default.writeFileSync(node_path_1.default.join(runDir, "report.md"), "# Architecture review\n\nRisk: src/server.js:18 — unauthenticated route.\n", "utf8");
196
+ const attestedCount = HOPS.filter((h) => h.attestation === "attested").length;
197
+ const fullRun = {
198
+ schemaVersion: 1, id: runId, createdAt: DEMO_NOW, updatedAt: DEMO_NOW, cwd: workdir,
199
+ workflow: { id: "demo", title: "Demo", summary: "", limits: { maxAgents: 1, maxConcurrentAgents: 1 } },
200
+ inputs: { question: "what are the risks?" }, loopStage: "interpret",
201
+ phases: [], tasks: [], dispatches: [], commits: [], paths, nodes: [], contracts: []
202
+ };
203
+ (0, state_1.saveCheckpoint)(fullRun);
204
+ const ledgerFile = (0, telemetry_ledger_1.telemetryLedgerPath)(ledgerRun);
205
+ const cleanLedger = node_fs_1.default.readFileSync(ledgerFile, "utf8");
206
+ const exportSealed = (out) => { (0, run_export_1.exportRun)(fullRun, out, { trustPublicKey: publicKeyPem }); };
207
+ // Baseline: a clean sealed bundle verifies offline; the embedded key reverifies
208
+ // every signed hop.
209
+ const cleanBundle = node_path_1.default.join(workdir, "clean.cwrun.json");
210
+ exportSealed(cleanBundle);
211
+ const clean = (0, run_export_1.verifyReportBundle)(cleanBundle);
212
+ const baseline = { ok: clean.ok, telemetryVerified: clean.telemetryVerified, signaturesReverified: clean.signaturesReverified };
213
+ const layers = [];
214
+ // CHAIN forgery: flip record[1]'s verdict and reseal its recordHash; record[2]'s
215
+ // prevHash still points at the original hash, so the chain breaks — even though
216
+ // every archive file digest (computed at export over the tampered bytes) is valid.
217
+ // This is exactly what inspect-archive alone cannot catch.
218
+ {
219
+ const j = JSON.parse(cleanLedger);
220
+ j.records[1].attestation = "attested";
221
+ const { recordHash: _drop, ...rest } = j.records[1];
222
+ j.records[1].recordHash = (0, telemetry_ledger_1.computeRecordHash)(rest);
223
+ node_fs_1.default.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
224
+ const forged = node_path_1.default.join(workdir, "forged-chain.cwrun.json");
225
+ exportSealed(forged);
226
+ const after = (0, run_export_1.verifyReportBundle)(forged);
227
+ node_fs_1.default.writeFileSync(ledgerFile, cleanLedger);
228
+ layers.push({
229
+ layer: "chain",
230
+ tamper: `forged record[1] verdict "unattested" -> "attested" and resealed its recordHash; the archive's own file digests stay valid`,
231
+ before: { ok: clean.ok, detail: `${clean.signaturesReverified} signed hop(s) reverify; chain intact` },
232
+ after: { ok: after.ok, detail: after.telemetryVerified ? "telemetry chain still verified (UNDETECTED!)" : "the embedded hash chain broke at the next record" },
233
+ failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`)
234
+ });
235
+ }
236
+ // SIGNATURE forgery: inflate the last attested hop's reported tokens and reseal its
237
+ // usage digest + recordHash so the chain AND archive digests still verify; only the
238
+ // ed25519 signature (over the original usage) no longer matches the inflated number.
239
+ {
240
+ const j = JSON.parse(cleanLedger);
241
+ const idx = j.records.length - 1;
242
+ j.records[idx].reportedUsage = { ...j.records[idx].reportedUsage, output_tokens: j.records[idx].reportedUsage.output_tokens * 10 };
243
+ j.records[idx].reportedUsageDigest = (0, telemetry_ledger_1.reportedUsageDigest)(j.records[idx].reportedUsage);
244
+ const { recordHash: _drop, ...rest } = j.records[idx];
245
+ j.records[idx].recordHash = (0, telemetry_ledger_1.computeRecordHash)(rest);
246
+ node_fs_1.default.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
247
+ const forged = node_path_1.default.join(workdir, "forged-sig.cwrun.json");
248
+ exportSealed(forged);
249
+ const after = (0, run_export_1.verifyReportBundle)(forged);
250
+ node_fs_1.default.writeFileSync(ledgerFile, cleanLedger);
251
+ layers.push({
252
+ layer: "signature",
253
+ tamper: `inflated the last attested hop's output_tokens 10x and resealed its digest + recordHash; the chain stays valid`,
254
+ before: { ok: clean.ok, detail: `the embedded public key reverifies the original signature` },
255
+ after: { ok: after.ok, detail: after.signaturesFailed > 0 ? `${after.signaturesFailed} signature(s) failed ed25519 reverify` : "signature still verified (UNDETECTED!)" },
256
+ failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`)
257
+ });
258
+ }
259
+ if (!options.keepDir && !options.dir)
260
+ node_fs_1.default.rmSync(workdir, { recursive: true, force: true });
261
+ const proven = baseline.ok &&
262
+ baseline.telemetryVerified &&
263
+ baseline.signaturesReverified === attestedCount &&
264
+ layers.every((l) => l.before.ok && !l.after.ok && l.failures.length > 0);
265
+ return { schemaVersion: 1, runId, workers: HOPS.length, trustKey: "ephemeral-ed25519", baseline, layers, proven };
266
+ }
267
+ function formatBundleDemo(r) {
268
+ const lines = [];
269
+ lines.push(`cw demo bundle — portable-bundle verification proof (hermetic, ${r.trustKey} key)`);
270
+ lines.push("");
271
+ lines.push(`▶ Exported a sealed report bundle: ${r.workers} hops, public key embedded`);
272
+ lines.push(` ${r.baseline.ok ? "✓" : "✗"} bundle verifies offline ${r.baseline.signaturesReverified} signed hop(s) reverify with only the embedded public key`);
273
+ for (const l of r.layers) {
274
+ lines.push("");
275
+ lines.push(`▶ ${l.layer.toUpperCase()} forgery`);
276
+ lines.push(` edit: ${l.tamper}`);
277
+ lines.push(` before: ${l.before.ok ? "✓ verifies" : "✗"} — ${l.before.detail}`);
278
+ lines.push(` after: ${l.after.ok ? "✓ (UNDETECTED!)" : "✗ DETECTED"} — ${l.after.detail}`);
279
+ }
280
+ lines.push("");
281
+ lines.push(r.proven
282
+ ? "VERDICT: bundle verification holds ✓ — every forgery caught offline with only the bundle's embedded public key. No repo, no server, no key handed over."
283
+ : "VERDICT: PROOF FAILED ✗ — a forged bundle verified. This is a regression in the bundle guarantee.");
284
+ return lines.join("\n");
285
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ // Verifiable report bundle — result shapes shared across the run-export verifier,
3
+ // the capability-core producer (reportBundle), and the quickstart auto-bundle field
4
+ // on QuickstartResult. Kept in types/ (a leaf) so types/drive.ts can reference
5
+ // ReportBundleResult without a types->core import cycle.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/types.js CHANGED
@@ -28,6 +28,7 @@ __exportStar(require("./types/sandbox"), exports);
28
28
  __exportStar(require("./types/execution-backend"), exports);
29
29
  __exportStar(require("./types/boundary"), exports);
30
30
  __exportStar(require("./types/drive"), exports);
31
+ __exportStar(require("./types/report-bundle"), exports);
31
32
  __exportStar(require("./types/multi-agent"), exports);
32
33
  __exportStar(require("./types/topology"), exports);
33
34
  __exportStar(require("./types/blackboard"), exports);
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.83";
4
+ exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.85";
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;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.acceptWorkerResult = acceptWorkerResult;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const state_1 = require("../state");
10
+ const pipeline_contract_1 = require("../pipeline-contract");
11
+ const state_node_1 = require("../state-node");
12
+ const result_normalize_1 = require("../result-normalize");
13
+ const trust_audit_1 = require("../trust-audit");
14
+ /** Step 3 — recordStateNode/audit: the irreversible accept. Records the allowed
15
+ * path decision, copies the result into the run results dir, completes the task,
16
+ * builds + appends the result node, emits the accepted audit event, re-normalizes
17
+ * node evidence against both audit ids, and surfaces the empty-capture warning.
18
+ * Writes destination/pathAuditId/acceptedAuditId/resultNode back into `accept`. */
19
+ function acceptWorkerResult(accept, delegation) {
20
+ const { run, workerId, scope, task, absoluteResultPath, parsedResult } = accept;
21
+ const { agentDelegation } = delegation;
22
+ const pathAudit = (0, trust_audit_1.recordSandboxPathDecision)(run, {
23
+ workerId,
24
+ taskId: task.id,
25
+ sandboxProfileId: scope.sandboxProfileId,
26
+ policySnapshot: scope.sandboxPolicy,
27
+ target: absoluteResultPath,
28
+ decision: "allowed",
29
+ metadata: { operation: "worker-output-acceptance" }
30
+ });
31
+ const destination = node_path_1.default.join(run.paths.resultsDir, `${(0, state_1.safeFileName)(task.id)}.md`);
32
+ node_fs_1.default.mkdirSync(run.paths.resultsDir, { recursive: true });
33
+ node_fs_1.default.copyFileSync(absoluteResultPath, destination);
34
+ task.status = "completed";
35
+ task.completedAt = new Date().toISOString();
36
+ task.resultPath = destination;
37
+ task.loopStage = "observe";
38
+ task.result = parsedResult;
39
+ const evidence = (0, trust_audit_1.normalizeEvidence)(run, parsedResult.evidence.map((entry, index) => ({
40
+ id: `result:${index + 1}`,
41
+ source: "cw:result",
42
+ locator: entry,
43
+ summary: entry
44
+ })), { source: "cw-validated", workerId, taskId: task.id, auditEventIds: [pathAudit.id] });
45
+ const resultNode = (0, state_node_1.appendRunNode)(run, (0, state_node_1.createStateNode)({
46
+ id: `${run.id}:result:${task.id}`,
47
+ kind: "result",
48
+ status: "completed",
49
+ loopStage: "observe",
50
+ inputs: { taskId: task.id, dispatchId: task.dispatchId, workerId },
51
+ outputs: parsedResult,
52
+ artifacts: [
53
+ { id: "result", kind: "markdown", path: destination },
54
+ { id: "worker-result", kind: "markdown", path: absoluteResultPath }
55
+ ],
56
+ evidence,
57
+ parents: task.dispatchId ? [`${run.id}:dispatch:${task.dispatchId}`] : [task.stateNodeId || `${run.id}:task:${task.id}`],
58
+ contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
59
+ metadata: {
60
+ taskId: task.id,
61
+ workerId,
62
+ workerDir: scope.workerDir,
63
+ sandboxProfileId: scope.sandboxProfileId,
64
+ auditEventIds: [pathAudit.id],
65
+ // Empty-capture warning (v0.1.42): even after robust normalization the result
66
+ // yielded NO findings and NO evidence — surfaced, never silently passed.
67
+ ...((0, result_normalize_1.isEmptyCapture)(parsedResult) ? { captureWarning: "no findings or evidence captured from result.md" } : {}),
68
+ // Folded into the snapshotted node body so v0.1.35 replay re-verifies the
69
+ // prompt/result/model digests WITHOUT re-spawning the agent. NOT evidence.
70
+ ...(agentDelegation ? { agentDelegation } : {})
71
+ }
72
+ }));
73
+ const acceptedAudit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
74
+ kind: "worker.output",
75
+ decision: "accepted",
76
+ source: "cw-validated",
77
+ workerId,
78
+ taskId: task.id,
79
+ nodeId: resultNode.id,
80
+ sandboxProfileId: scope.sandboxProfileId,
81
+ policySnapshot: scope.sandboxPolicy,
82
+ normalizedPath: absoluteResultPath,
83
+ evidence,
84
+ parentEventIds: [pathAudit.id],
85
+ metadata: { destination }
86
+ });
87
+ resultNode.evidence = (0, trust_audit_1.normalizeEvidence)(run, resultNode.evidence, {
88
+ source: "cw-validated",
89
+ workerId,
90
+ taskId: task.id,
91
+ resultNodeId: resultNode.id,
92
+ auditEventIds: [pathAudit.id, acceptedAudit.id]
93
+ });
94
+ (0, state_node_1.appendRunNode)(run, resultNode);
95
+ task.resultNodeId = resultNode.id;
96
+ // Warn (don't silently pass) when a worker's result captured no structured signal
97
+ // at all — the v0.1.41 self-audit's "accepted with evidenceCount:0" failure mode.
98
+ if ((0, result_normalize_1.isEmptyCapture)(parsedResult)) {
99
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
100
+ kind: "worker.capture-warning",
101
+ decision: "recorded",
102
+ source: "cw-validated",
103
+ workerId,
104
+ taskId: task.id,
105
+ nodeId: resultNode.id,
106
+ parentEventIds: [acceptedAudit.id],
107
+ metadata: { reason: "no findings or evidence captured from result.md", resultPath: destination }
108
+ });
109
+ }
110
+ accept.destination = destination;
111
+ accept.pathAuditId = pathAudit.id;
112
+ accept.acceptedAuditId = acceptedAudit.id;
113
+ accept.resultNode = resultNode;
114
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fanOutWorkerOutput = fanOutWorkerOutput;
4
+ const coordinator_1 = require("../coordinator");
5
+ const multi_agent_1 = require("../multi-agent");
6
+ const blackboard_linkage_1 = require("./blackboard-linkage");
7
+ /** Step 7 — fanOut: publish the accepted output to the blackboard and record the
8
+ * multi-agent worker output (linking the blackboard message/artifact refs). */
9
+ function fanOutWorkerOutput(accept) {
10
+ const { run, workerId, scope, task, absoluteResultPath, parsedResult, destination, resultNode, verifierNodeId, acceptedAuditId } = accept;
11
+ const blackboardLinks = publishWorkerOutputToBlackboard(run, scope, task, parsedResult.summary, destination, absoluteResultPath, resultNode.evidence, acceptedAuditId);
12
+ (0, multi_agent_1.recordMultiAgentWorkerOutput)(run, {
13
+ workerId,
14
+ taskId: task.id,
15
+ resultNodeId: resultNode.id,
16
+ verifierNodeId,
17
+ evidence: resultNode.evidence,
18
+ artifactPaths: [destination, absoluteResultPath],
19
+ blackboardMessageIds: blackboardLinks.messageIds,
20
+ blackboardArtifactRefIds: blackboardLinks.artifactRefIds
21
+ });
22
+ }
23
+ function publishWorkerOutputToBlackboard(run, scope, task, summary, destination, workerResultPath, evidence, acceptedAuditId) {
24
+ const linkage = (0, blackboard_linkage_1.blackboardLinkage)(run, scope);
25
+ if (!linkage.blackboardId || !linkage.topicIds.length)
26
+ return { messageIds: [], artifactRefIds: [] };
27
+ const topicId = linkage.topicIds[0];
28
+ const artifactRefs = [
29
+ (0, coordinator_1.addBlackboardArtifact)(run, {
30
+ topicId,
31
+ blackboardId: linkage.blackboardId,
32
+ kind: "worker-result",
33
+ path: destination,
34
+ owner: { kind: "worker", id: scope.id },
35
+ author: { kind: "runtime", id: "cw" },
36
+ source: "cw-validated-worker-output",
37
+ provenance: {
38
+ workerId: scope.id,
39
+ taskId: task.id,
40
+ multiAgentRunId: scope.multiAgent?.runId,
41
+ agentGroupId: scope.multiAgent?.groupId,
42
+ agentRoleId: scope.multiAgent?.roleId,
43
+ agentMembershipId: scope.multiAgent?.membershipId,
44
+ auditEventIds: [acceptedAuditId]
45
+ },
46
+ evidenceRefs: evidence.map((entry) => entry.locator || entry.path || entry.summary || entry.id).filter(Boolean),
47
+ auditEventIds: [acceptedAuditId],
48
+ metadata: { workerResultPath }
49
+ })
50
+ ];
51
+ const message = (0, coordinator_1.postBlackboardMessage)(run, {
52
+ topicId,
53
+ blackboardId: linkage.blackboardId,
54
+ body: summary,
55
+ author: { kind: "worker", id: scope.id },
56
+ scope: { kind: "worker", id: scope.id },
57
+ artifactRefIds: artifactRefs.map((artifact) => artifact.id),
58
+ evidenceRefs: evidence.map((entry) => entry.locator || entry.path || entry.summary || entry.id).filter(Boolean),
59
+ auditEventIds: [acceptedAuditId],
60
+ links: {
61
+ multiAgentRunId: scope.multiAgent?.runId,
62
+ agentGroupId: scope.multiAgent?.groupId,
63
+ agentRoleId: scope.multiAgent?.roleId,
64
+ agentMembershipId: scope.multiAgent?.membershipId,
65
+ agentFanoutId: scope.multiAgent?.fanoutId,
66
+ taskId: task.id,
67
+ workerId: scope.id,
68
+ auditEventIds: [acceptedAuditId]
69
+ },
70
+ metadata: {
71
+ taskId: task.id,
72
+ resultPath: destination,
73
+ multiAgent: scope.multiAgent
74
+ }
75
+ });
76
+ return {
77
+ messageIds: [message.id],
78
+ artifactRefIds: artifactRefs.map((artifact) => artifact.id)
79
+ };
80
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blackboardLinkage = blackboardLinkage;
4
+ const helpers_1 = require("../worker-isolation/helpers");
5
+ function blackboardLinkage(run, scope) {
6
+ const membershipId = scope.multiAgent?.membershipId;
7
+ const membership = membershipId ? run.multiAgent?.memberships.find((entry) => entry.id === membershipId) : undefined;
8
+ const group = scope.multiAgent?.groupId ? run.multiAgent?.groups.find((entry) => entry.id === scope.multiAgent?.groupId) : undefined;
9
+ const role = scope.multiAgent?.roleId ? run.multiAgent?.roles.find((entry) => entry.id === scope.multiAgent?.roleId) : undefined;
10
+ const multiAgentRun = scope.multiAgent?.runId ? run.multiAgent?.runs.find((entry) => entry.id === scope.multiAgent?.runId) : undefined;
11
+ const blackboardId = membership?.blackboardId || group?.blackboardId || role?.blackboardId || multiAgentRun?.blackboardId;
12
+ const topicIds = (0, helpers_1.unique)([
13
+ ...(membership?.topicIds || []),
14
+ ...(group?.topicIds || []),
15
+ ...(role?.topicIds || []),
16
+ ...(multiAgentRun?.topicIds || [])
17
+ ]);
18
+ return { blackboardId, topicIds };
19
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });