cool-workflow 0.1.84 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +2 -0
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/capability-core.js +89 -2
- package/dist/capability-registry.js +7 -2
- package/dist/cli/command-surface.js +44 -2
- package/dist/mcp-surface.js +27 -1
- package/dist/run-export.js +139 -1
- package/dist/telemetry-demo.js +119 -0
- package/dist/types/report-bundle.js +6 -0
- package/dist/types.js +1 -0
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +2 -0
- package/docs/cli-mcp-parity.7.md +9 -3
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/index.md +1 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +9 -4
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/report-verifiable-bundle.7.md +123 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
package/dist/telemetry-demo.js
CHANGED
|
@@ -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.
|
|
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;
|
package/docs/cli-mcp-parity.7.md
CHANGED
|
@@ -72,7 +72,7 @@ relationship. `identical` means `cw <cmd> --json` is equal to the `cw_<tool>`
|
|
|
72
72
|
payload; `projected` means a declared divergence with a reason; `cli-only` marks
|
|
73
73
|
a surface-specific capability with a recorded reason. The matrix is
|
|
74
74
|
<!-- gen:parity:count -->
|
|
75
|
-
machine-complete by design:
|
|
75
|
+
machine-complete by design: 193 capabilities, 186 MCP tools.
|
|
76
76
|
<!-- /gen:parity:count -->
|
|
77
77
|
|
|
78
78
|
<!-- gen:parity:table -->
|
|
@@ -236,6 +236,8 @@ machine-complete by design: 190 capabilities, 184 MCP tools.
|
|
|
236
236
|
| `run.import` | `cw run import` | `cw_run_import` | `runImportArchive` | both | identical |
|
|
237
237
|
| `run.verify-import` | `cw run verify-import` | `cw_run_verify_import` | `runVerifyImport` | both | identical |
|
|
238
238
|
| `run.inspect-archive` | `cw run inspect-archive` | `cw_run_inspect_archive` | `runInspectArchive` | both | identical |
|
|
239
|
+
| `report.verify-bundle` | `cw report verify-bundle` | `cw_report_verify_bundle` | `runVerifyReportBundle` | both | identical |
|
|
240
|
+
| `report.bundle` | `cw report bundle` | `cw_report_bundle` | `reportBundle` | both | identical |
|
|
239
241
|
| `run.drive` | `cw run drive` | `cw_run_drive` | `runDrivePreview` | both | identical |
|
|
240
242
|
| `run.drive.step` | `cw run drive` | `cw_run_drive_step` | `runDrive` | both | projected |
|
|
241
243
|
| `quickstart` | `cw quickstart` | `—` | `quickstart` | cli-only | cli-only |
|
|
@@ -256,6 +258,7 @@ machine-complete by design: 190 capabilities, 184 MCP tools.
|
|
|
256
258
|
| `gc.verify` | `cw gc verify` | `cw_gc_verify` | `gcVerify` | both | identical |
|
|
257
259
|
| `telemetry.verify` | `cw telemetry verify` | `cw_telemetry_verify` | `telemetryVerify` | both | identical |
|
|
258
260
|
| `demo.tamper` | `cw demo tamper` | `—` | `demoTamper` | cli-only | cli-only |
|
|
261
|
+
| `demo.bundle` | `cw demo bundle` | `—` | `demoBundle` | cli-only | cli-only |
|
|
259
262
|
| `history` | `cw history` | `cw_history` | `runRegistry.history` | both | identical |
|
|
260
263
|
| `workbench.view` | `cw workbench view` | `cw_workbench_view` | `buildWorkbenchRunView` | both | identical |
|
|
261
264
|
| `workbench.serve` | `cw workbench serve` | `cw_workbench_serve` | `buildWorkbenchServeDescriptor` | both | projected |
|
|
@@ -282,14 +285,15 @@ A capability may be on one surface only, but never without word of it — it mus
|
|
|
282
285
|
carry a recorded reason in the registry.
|
|
283
286
|
|
|
284
287
|
<!-- gen:parity:cliOnly -->
|
|
285
|
-
|
|
288
|
+
Seven capabilities are CLI-only:
|
|
286
289
|
|
|
287
290
|
- `help` — Human help text. MCP hosts enumerate capabilities via tools/list, not a help command.
|
|
288
291
|
- `doctor` — Environment diagnostics are inherently local to the CLI host — Node version, $PATH, $CW_HOME/cwd writability. An MCP client diagnosing the server process's environment is not meaningful; agents already receive the same readiness facts in their typed results (e.g. status: blocked, agentConfigured). Inspired by `brew doctor`.
|
|
289
292
|
- `loop` — Convenience alias of `schedule create` with kind=loop. MCP hosts use cw_schedule_create with kind=loop.
|
|
290
293
|
- `schedule daemon` — Long-running desktop daemon process, not a request/response tool. MCP hosts drive ticks via cw_schedule_due + cw_schedule_run_now.
|
|
291
|
-
- `quickstart` — CLI UX convenience layer (newcomer first value in one command) over the existing run.drive.step + report verbs; it spawns nothing new and delegates worker execution to the operator's agent backend. MCP hosts compose the same outcome from cw_run_drive_step + cw_report. `audit-run` is a CLI-only alias of the same wrapper.
|
|
294
|
+
- `quickstart` — CLI UX convenience layer (newcomer first value in one command) over the existing run.drive.step + report verbs; it spawns nothing new and delegates worker execution to the operator's agent backend. MCP hosts compose the same outcome from cw_run_drive_step + cw_report (+ cw_report_bundle for --bundle). `audit-run` is a CLI-only alias of the same wrapper.
|
|
292
295
|
- `demo tamper` — Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface telemetry.verify. No agent or MCP client needs to invoke a demo.
|
|
296
|
+
- `demo bundle` — Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface report.verify-bundle. No agent or MCP client needs to invoke a demo.
|
|
293
297
|
<!-- /gen:parity:cliOnly -->
|
|
294
298
|
|
|
295
299
|
<!-- gen:parity:projected -->
|
|
@@ -466,3 +470,5 @@ Loaders fail closed on corrupt state; store writes are made safe under more than
|
|
|
466
470
|
## Privacy Release (v0.1.84)
|
|
467
471
|
|
|
468
472
|
No other change to this page in v0.1.84.
|
|
473
|
+
|
|
474
|
+
0.1.85
|
package/docs/index.md
CHANGED
|
@@ -37,6 +37,7 @@ Read these in order when you are new to CW:
|
|
|
37
37
|
32. [Run Retention & Provable Reclamation](run-retention-reclamation.7.md) - tiered, append-only, cryptographically-verifiable disk reclamation over the v0.1.28 archive overlay: seal the audit skeleton, free the reconstructable/scratch bulk, and prove it via a hash-chained tombstone; `gc plan|run|verify`, write-ahead + fail-closed, explicit capability downgrade.
|
|
38
38
|
33. [Durable State & Locking](durable-state-and-locking.7.md) - atomic (temp→rename) writes for every authoritative store with fsync-durability for the audit-essential ones, plus a portable stale-stealing file lock serializing the cross-process read-modify-write stores (home queue, archive overlay, reclamation chain); closes the prior verdict's non-atomic/unlocked P1.
|
|
39
39
|
34. [Source Context Profiles](source-context-profiles.7.md) - opt-in JSONL source exports for AI context slimming, with profile policy in manifest data and manifest records proving every included or omitted tracked file.
|
|
40
|
+
35. [Verifiable Report Bundle](report-verifiable-bundle.7.md) - embed the operator's ed25519 public key into a portable run archive so anyone can `report verify-bundle` it OFFLINE and self-contained (archive bytes + telemetry chain + trust-audit chain + signatures); `report bundle` produces-and-proves in one command. Fail-closed, no repo/key/install beyond npx.
|
|
40
41
|
|
|
41
42
|
CW is the base system. Workflow apps are userland. Release and migration rules
|
|
42
43
|
must keep that line clear: stable contracts, clear compatibility checks, and
|
package/docs/project-index.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Cool Workflow Project Index
|
|
2
2
|
|
|
3
|
-
Generated from the current repository code on 2026-06-
|
|
3
|
+
Generated from the current repository code on 2026-06-18 by `npm run sync:project-index`.
|
|
4
4
|
|
|
5
5
|
## Snapshot
|
|
6
6
|
|
|
7
7
|
- Package: `cool-workflow`
|
|
8
|
-
- Version: `0.1.
|
|
8
|
+
- Version: `0.1.85`
|
|
9
9
|
- Source modules: `62`
|
|
10
10
|
- Workflow apps: `7`
|
|
11
|
-
- Docs: `
|
|
12
|
-
- Smoke tests: `
|
|
11
|
+
- Docs: `50`
|
|
12
|
+
- Smoke tests: `101`
|
|
13
13
|
- Repository: https://github.com/coo1white/cool-workflow
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
@@ -161,6 +161,7 @@ multi-agent host -> topology -> blackboard/coordinator
|
|
|
161
161
|
- [Real Execution Backend Integrations](real-execution-backends.7.md)
|
|
162
162
|
- [Release And Migration Discipline](release-and-migration.7.md)
|
|
163
163
|
- [Release Tooling](release-tooling.7.md)
|
|
164
|
+
- [Verifiable Report Bundle](report-verifiable-bundle.7.md)
|
|
164
165
|
- [Routines](routines.md)
|
|
165
166
|
- [Run Registry / Control Plane](run-registry-control-plane.7.md)
|
|
166
167
|
- [Run Retention & Provable Reclamation](run-retention-reclamation.7.md)
|
|
@@ -202,6 +203,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
|
|
|
202
203
|
- [contract-migration-tooling-smoke.js](../test/contract-migration-tooling-smoke.js)
|
|
203
204
|
- [control-plane-scheduling-smoke.js](../test/control-plane-scheduling-smoke.js)
|
|
204
205
|
- [coordinator-blackboard-smoke.js](../test/coordinator-blackboard-smoke.js)
|
|
206
|
+
- [demo-bundle-smoke.js](../test/demo-bundle-smoke.js)
|
|
205
207
|
- [det-ids-b-smoke.js](../test/det-ids-b-smoke.js)
|
|
206
208
|
- [doctor-smoke.js](../test/doctor-smoke.js)
|
|
207
209
|
- [dogfood-release-smoke.js](../test/dogfood-release-smoke.js)
|
|
@@ -236,12 +238,15 @@ Smoke tests mirror the public contracts. The high-signal suites are:
|
|
|
236
238
|
- [pipeline-auto-advance-smoke.js](../test/pipeline-auto-advance-smoke.js)
|
|
237
239
|
- [pipeline-runner-smoke.js](../test/pipeline-runner-smoke.js)
|
|
238
240
|
- [project-index-sync-smoke.js](../test/project-index-sync-smoke.js)
|
|
241
|
+
- [quickstart-bundle-smoke.js](../test/quickstart-bundle-smoke.js)
|
|
239
242
|
- [quickstart-smoke.js](../test/quickstart-smoke.js)
|
|
240
243
|
- [real-execution-backends-smoke.js](../test/real-execution-backends-smoke.js)
|
|
241
244
|
- [registry-corrupt-fail-closed-smoke.js](../test/registry-corrupt-fail-closed-smoke.js)
|
|
242
245
|
- [release-flow-smoke.js](../test/release-flow-smoke.js)
|
|
243
246
|
- [release-gate-smoke.js](../test/release-gate-smoke.js)
|
|
244
247
|
- [release-tooling-smoke.js](../test/release-tooling-smoke.js)
|
|
248
|
+
- [report-bundle-smoke.js](../test/report-bundle-smoke.js)
|
|
249
|
+
- [report-verify-bundle-smoke.js](../test/report-verify-bundle-smoke.js)
|
|
245
250
|
- [result-normalize-smoke.js](../test/result-normalize-smoke.js)
|
|
246
251
|
- [robustness-failclosed-smoke.js](../test/robustness-failclosed-smoke.js)
|
|
247
252
|
- [robustness-hardening-smoke.js](../test/robustness-hardening-smoke.js)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Verifiable Report Bundle
|
|
2
|
+
|
|
3
|
+
CW v0.1.85 adds the Verifiable Report Bundle: a way to hand someone a run's report
|
|
4
|
+
that they can check themselves, OFFLINE, with one command — no source repo, no
|
|
5
|
+
pre-existing `.cw` tree, and no key passed on the side.
|
|
6
|
+
|
|
7
|
+
Before this, a run could be exported to a portable `<id>.cwrun.json` archive
|
|
8
|
+
([Run Registry / Control Plane](run-registry-control-plane.7.md)) and its bytes
|
|
9
|
+
re-proven with `cw run inspect-archive`. But two things stopped a stranger from
|
|
10
|
+
fully verifying it:
|
|
11
|
+
|
|
12
|
+
- The ed25519 PUBLIC key that re-checks the signed telemetry came only from
|
|
13
|
+
`--pubkey` or the `CW_AGENT_ATTEST_PUBKEY` environment variable — it did not
|
|
14
|
+
travel with the archive.
|
|
15
|
+
- `inspect-archive` proves the archive's own file digests, not the telemetry hash
|
|
16
|
+
chain or the signatures. Re-proving those meant `cw run import` into a real
|
|
17
|
+
`.cw` tree and then `cw run verify-import` — too much to ask of a recipient.
|
|
18
|
+
|
|
19
|
+
The Verifiable Report Bundle closes both gaps without a new archive format.
|
|
20
|
+
|
|
21
|
+
## Mechanism vs Policy
|
|
22
|
+
|
|
23
|
+
The MECHANISM is three small additions to the existing export/verify path:
|
|
24
|
+
|
|
25
|
+
1. `cw run export <run> --with-trust-key <pem-or-path>` embeds the operator's
|
|
26
|
+
ed25519 PUBLIC key into the archive under a new optional `trust` block
|
|
27
|
+
(`{ publicKeyPem, algorithm: "ed25519" }`). Only a public key is ever embedded;
|
|
28
|
+
CW never exports a private key. The flag defaults to `CW_AGENT_ATTEST_PUBKEY`,
|
|
29
|
+
so one configured key both attests at record-time and travels with the export.
|
|
30
|
+
A raw inline PEM begins with `-----`, which the CLI parses as a flag — so the
|
|
31
|
+
CLI form takes a key FILE PATH; an inline PEM is accepted programmatically and
|
|
32
|
+
via the environment variable.
|
|
33
|
+
|
|
34
|
+
2. `cw report verify-bundle <bundle>` verifies the bundle SELF-CONTAINED and
|
|
35
|
+
OFFLINE. It reuses the existing verifiers end to end: `inspectArchive` for the
|
|
36
|
+
archive bytes, then a restore into a throwaway temp dir (auto-removed) so
|
|
37
|
+
`verifyImportedRun` re-proves the telemetry hash chain and the trust-audit
|
|
38
|
+
chain, then `verifyTelemetrySignatures` re-runs ed25519 over each attested hop
|
|
39
|
+
using the key the bundle carries. It writes nothing to any registry.
|
|
40
|
+
|
|
41
|
+
3. `cw report bundle <run>` is the PRODUCER counterpart: it exports a sealed
|
|
42
|
+
bundle (step 1) and then immediately self-verifies it (step 2), returning the
|
|
43
|
+
archive path and the verification verdict together. It fails closed — a solo
|
|
44
|
+
operator never hands off a report whose bundle does not verify (for example, no
|
|
45
|
+
trust key configured under `--strict-signatures`). `--extract-report` also
|
|
46
|
+
writes the human-readable `report.md` next to the bundle so the shippable pair
|
|
47
|
+
is produced in one command. It is pure composition; it spawns nothing.
|
|
48
|
+
|
|
49
|
+
`cw quickstart <app> --bundle [--with-trust-key K]` folds step 3 into the
|
|
50
|
+
one-command entry: after the drive reaches `status: complete`, it seals the run
|
|
51
|
+
with `reportBundle` (anchored to the run's own repo) and returns the verdict as
|
|
52
|
+
`result.bundle`. It is gated on completion — a blocked/partial run is **never**
|
|
53
|
+
sealed (the operator gets a `hint`, not a half-shipped artifact) — and the CLI
|
|
54
|
+
exits non-zero when `result.bundle.ok` is false. `quickstart` stays `cli-only`;
|
|
55
|
+
MCP hosts compose `cw_run_drive_step` + `cw_report_bundle` for the same outcome.
|
|
56
|
+
|
|
57
|
+
Because the README headline runs quickstart cross-directory (your shell cwd is not
|
|
58
|
+
the `--repo`), the run is resolved from its own repo, but the bundle's OUTPUT —
|
|
59
|
+
the `.cwrun.json` and any `--extract-report` — lands in **your cwd**, not the
|
|
60
|
+
analyzed repo (so `cw quickstart … --bundle --extract-report out.md && send out.md`
|
|
61
|
+
works and the repo's working tree is never polluted). `result.bundle.archivePath`
|
|
62
|
+
and `result.bundle.reportExtractedTo` report the absolute paths.
|
|
63
|
+
|
|
64
|
+
The POLICY is fail-closed and self-describing:
|
|
65
|
+
|
|
66
|
+
- Key precedence is **bundle > `--pubkey` > `CW_AGENT_ATTEST_PUBKEY`**, so a bundle
|
|
67
|
+
with an embedded key verifies the same on any machine; the override/env only
|
|
68
|
+
apply when the bundle omits a key.
|
|
69
|
+
- `ok` is true only when the archive bytes, the telemetry chain, and the
|
|
70
|
+
trust-audit chain all verify AND no attested signature failed re-verification.
|
|
71
|
+
- A bundle with attested telemetry but no available key DEGRADES by default
|
|
72
|
+
(`signatureKeyProvided: false`, the intact chain still decides `ok`).
|
|
73
|
+
`--strict-signatures` refuses such a bundle instead.
|
|
74
|
+
- `--extract-report <path>` writes the bundle's `report.md` out for a human to
|
|
75
|
+
read alongside the machine verdict. If extraction is requested but the bundle
|
|
76
|
+
has no `report.md` (or the write fails), that is a failure, not a silent no-op:
|
|
77
|
+
a `extract-report` / `report-md-unavailable` check is recorded and `ok` is
|
|
78
|
+
false — so a producer never ships a green verdict with no report attached.
|
|
79
|
+
|
|
80
|
+
## Fail closed
|
|
81
|
+
|
|
82
|
+
`cw report verify-bundle` exits non-zero whenever `ok` is false, so
|
|
83
|
+
`cw report verify-bundle <file> && ship` cannot pass on a forgery. This holds even
|
|
84
|
+
for a telemetry chain forged so that every archive file digest still matches: the
|
|
85
|
+
file-digest layer (`inspect-archive`) would wave it through, but the embedded hash
|
|
86
|
+
chain breaks at the next record and the signature re-verification rejects inflated
|
|
87
|
+
usage. A missing, unreadable, or schema-unsupported bundle is also `ok: false`.
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
# See it in 30 seconds, hermetic — no agent, no API key, no repo:
|
|
93
|
+
npx cool-workflow demo bundle
|
|
94
|
+
# -> builds a sealed bundle, forges it two ways, and shows verify-bundle
|
|
95
|
+
# catching both offline with only the embedded public key.
|
|
96
|
+
|
|
97
|
+
# Run the review AND get a shippable, client-verifiable bundle from ONE command:
|
|
98
|
+
cw quickstart architecture-review --repo . --question "What are the risks?" \
|
|
99
|
+
--agent-command "claude -p" --bundle --with-trust-key ./trust-pub.pem
|
|
100
|
+
# -> after the drive COMPLETES, the run is sealed into a self-verified bundle and
|
|
101
|
+
# the verdict is folded into the quickstart JSON (result.bundle). Exits non-zero
|
|
102
|
+
# if that bundle would not verify. A run that did not complete is never sealed.
|
|
103
|
+
|
|
104
|
+
# Produce-and-prove from an existing run: export sealed + self-verify + emit the report.
|
|
105
|
+
cw report bundle <run-id> --with-trust-key ./trust-pub.pem \
|
|
106
|
+
--output report.cwrun.json --extract-report report.md
|
|
107
|
+
# -> exits non-zero if the produced bundle would not verify (don't ship it).
|
|
108
|
+
|
|
109
|
+
# Or the two steps separately:
|
|
110
|
+
cw run export <run-id> --with-trust-key ./trust-pub.pem --output report.cwrun.json
|
|
111
|
+
|
|
112
|
+
# Anyone, anywhere, offline — no repo, no key handed over, no install beyond npx:
|
|
113
|
+
npx cool-workflow report verify-bundle report.cwrun.json
|
|
114
|
+
npx cool-workflow report verify-bundle report.cwrun.json --extract-report report.md
|
|
115
|
+
npx cool-workflow report verify-bundle report.cwrun.json --strict-signatures --json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The same capability is on the MCP surface as `cw_report_verify_bundle`, so an agent
|
|
119
|
+
host re-verifies a bundle through the identical core entry.
|
|
120
|
+
|
|
121
|
+
See also [Security / Trust Hardening](security-trust-hardening.7.md) and the trust
|
|
122
|
+
model's honest ceiling: a signature proves non-repudiable attribution to the key
|
|
123
|
+
holder, not ground-truth measurement.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
|
|
3
3
|
"identity": {
|
|
4
4
|
"name": "cool-workflow",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.85",
|
|
6
6
|
"license": "BSD-2-Clause",
|
|
7
7
|
"homepage": "https://github.com/coo1white/cool-workflow",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -83,7 +83,7 @@ const canonicalApps = [
|
|
|
83
83
|
"--source",
|
|
84
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
85
85
|
"--scope",
|
|
86
|
-
"Cool Workflow v0.1.
|
|
86
|
+
"Cool Workflow v0.1.85",
|
|
87
87
|
"--freshness",
|
|
88
88
|
"as of release preparation"
|
|
89
89
|
]
|
|
@@ -117,14 +117,14 @@ function main() {
|
|
|
117
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
118
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
119
119
|
assert.equal(summary.legacy, false);
|
|
120
|
-
assert.equal(summary.version, "0.1.
|
|
120
|
+
assert.equal(summary.version, "0.1.85");
|
|
121
121
|
|
|
122
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
123
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
124
124
|
|
|
125
125
|
const shown = runJson(["app", "show", app.id]);
|
|
126
126
|
assert.equal(shown.app.id, app.id);
|
|
127
|
-
assert.equal(shown.app.version, "0.1.
|
|
127
|
+
assert.equal(shown.app.version, "0.1.85");
|
|
128
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
129
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
130
130
|
assertTaskIdsUnique(shown);
|
|
@@ -135,7 +135,7 @@ function main() {
|
|
|
135
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
136
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
137
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
138
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.1.85");
|
|
139
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
140
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
141
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
@@ -5,7 +5,7 @@ const { spawnSync } = require("node:child_process");
|
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
|
|
8
|
-
const TARGET_VERSION = "0.1.
|
|
8
|
+
const TARGET_VERSION = "0.1.85";
|
|
9
9
|
const PREVIOUS_VERSION = "0.1.31";
|
|
10
10
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
11
11
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
package/scripts/golden-path.js
CHANGED
|
@@ -33,7 +33,7 @@ function main() {
|
|
|
33
33
|
const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
|
|
34
34
|
assert.equal(appValidation.valid, true);
|
|
35
35
|
assert.equal(appValidation.summary.id, "end-to-end-golden-path");
|
|
36
|
-
assert.equal(appValidation.summary.version, "0.1.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.1.85");
|
|
37
37
|
|
|
38
38
|
const plan = runJson(
|
|
39
39
|
[
|
|
@@ -42,7 +42,7 @@ function main() {
|
|
|
42
42
|
"--repo",
|
|
43
43
|
tmp,
|
|
44
44
|
"--question",
|
|
45
|
-
"Prove the deterministic v0.1.
|
|
45
|
+
"Prove the deterministic v0.1.85 end-to-end golden path."
|
|
46
46
|
],
|
|
47
47
|
pluginRoot
|
|
48
48
|
);
|
|
@@ -52,7 +52,7 @@ function main() {
|
|
|
52
52
|
|
|
53
53
|
let state = readJson(plan.statePath);
|
|
54
54
|
assert.equal(state.workflow.app.id, "end-to-end-golden-path");
|
|
55
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.1.85");
|
|
56
56
|
assert.equal(state.loopStage, "interpret");
|
|
57
57
|
|
|
58
58
|
const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
|
|
@@ -195,7 +195,7 @@ function main() {
|
|
|
195
195
|
assert.equal(reportPath, plan.reportPath);
|
|
196
196
|
assert.ok(fs.existsSync(reportPath));
|
|
197
197
|
const report = fs.readFileSync(reportPath, "utf8");
|
|
198
|
-
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.85/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|