cool-workflow 0.1.96 → 0.1.98
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/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/candidate-scoring.js +3 -3
- package/dist/capability-core.js +9 -1
- package/dist/capability-registry.js +7 -1
- package/dist/cli/command-surface.js +4 -0
- package/dist/cli/handlers/ledger.js +169 -0
- package/dist/cli/handlers/scheduling.js +7 -1
- package/dist/drive.js +108 -61
- package/dist/execution-backend/agent.js +84 -24
- package/dist/execution-backend.js +25 -5
- package/dist/ledger.js +313 -0
- package/dist/mcp/tool-call.js +36 -0
- package/dist/mcp/tool-definitions.js +26 -0
- package/dist/mcp-server.js +4 -0
- package/dist/onramp.js +2 -0
- package/dist/orchestrator/app-operations.js +6 -0
- package/dist/orchestrator/cli-options.js +8 -2
- package/dist/orchestrator/lifecycle-operations.js +40 -13
- package/dist/orchestrator/migration-operations.js +1 -1
- package/dist/orchestrator.js +11 -3
- package/dist/remote-source.js +10 -3
- package/dist/run-export.js +45 -5
- package/dist/sandbox-profile.js +6 -1
- package/dist/triggers.js +7 -1
- package/dist/version.js +1 -1
- package/dist/workbench-host.js +18 -2
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +16 -2
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/cross-agent-ledger.7.md +217 -0
- package/docs/demo.7.md +80 -0
- package/docs/designs/handoff-ledger.md +145 -0
- package/docs/doctor.7.md +97 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/fix.7.md +44 -0
- package/docs/handoff-setup.md +120 -0
- package/docs/init.7.md +62 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/pipeline-verbs.7.md +93 -0
- package/docs/project-index.md +28 -5
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/routine.7.md +73 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/README.md +16 -10
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/agents/agent-adapter-core.js +4 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +40 -13
- package/scripts/children/http-delegate-child.js +2 -1
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/release-flow.js +31 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
3
|
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.98",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "COOLWHITE LLC"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.98",
|
|
4
4
|
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "COOLWHITE LLC"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "architecture-review",
|
|
4
4
|
"title": "Architecture Review",
|
|
5
5
|
"summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "architecture-review-fast",
|
|
4
4
|
"title": "Architecture Review Fast",
|
|
5
5
|
"summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "pr-review-fix-ci",
|
|
4
4
|
"title": "PR Review Fix CI",
|
|
5
5
|
"summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "release-cut",
|
|
4
4
|
"title": "Release Cut",
|
|
5
5
|
"summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "research-synthesis",
|
|
4
4
|
"title": "Research Synthesis",
|
|
5
5
|
"summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -115,7 +115,7 @@ function getCandidate(run, candidateId) {
|
|
|
115
115
|
// Fail-closed integrity boundary (F4/F5): validate the parsed record against
|
|
116
116
|
// its type def BEFORE upserting it as a trusted CandidateRecord. A corrupt or
|
|
117
117
|
// forged candidate.json must throw here rather than flow into the run.
|
|
118
|
-
const candidate = (0, validation_1.validateCandidateRecord)(
|
|
118
|
+
const candidate = (0, validation_1.validateCandidateRecord)((0, state_1.readJson)(file));
|
|
119
119
|
upsertCandidate(run, candidate);
|
|
120
120
|
return candidate;
|
|
121
121
|
}
|
|
@@ -537,7 +537,7 @@ function loadCandidatesFromDisk(run) {
|
|
|
537
537
|
// Fail-closed integrity boundary (F4/F5): each candidate.json is validated
|
|
538
538
|
// against CandidateRecord before it merges into the run; a corrupt record
|
|
539
539
|
// throws rather than entering the candidate set as a trusted cast.
|
|
540
|
-
.map((file) => (0, validation_1.validateCandidateRecord)(
|
|
540
|
+
.map((file) => (0, validation_1.validateCandidateRecord)((0, state_1.readJson)(file)));
|
|
541
541
|
}
|
|
542
542
|
function readScores(run, candidateId) {
|
|
543
543
|
const dir = node_path_1.default.join(candidateDir(run, candidateId), "scores");
|
|
@@ -550,7 +550,7 @@ function readScores(run, candidateId) {
|
|
|
550
550
|
// Fail-closed integrity boundary (F4/F5): a score file is validated against
|
|
551
551
|
// CandidateScore before it can feed ranking/selection. A corrupt score must
|
|
552
552
|
// throw, not silently widen the normalized/verdict surface the gate reads.
|
|
553
|
-
.map((file) => (0, validation_1.validateCandidateScore)(
|
|
553
|
+
.map((file) => (0, validation_1.validateCandidateScore)((0, state_1.readJson)(node_path_1.default.join(dir, file))));
|
|
554
554
|
}
|
|
555
555
|
function candidateArtifacts(run, candidate) {
|
|
556
556
|
return [
|
package/dist/capability-core.js
CHANGED
|
@@ -86,6 +86,7 @@ const telemetry_demo_1 = require("./telemetry-demo");
|
|
|
86
86
|
const state_1 = require("./state");
|
|
87
87
|
const run_export_1 = require("./run-export");
|
|
88
88
|
const result_normalize_1 = require("./result-normalize");
|
|
89
|
+
const cli_options_1 = require("./orchestrator/cli-options");
|
|
89
90
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
90
91
|
const node_path_1 = __importDefault(require("node:path"));
|
|
91
92
|
const scheduling_1 = require("./scheduling");
|
|
@@ -288,7 +289,12 @@ function runExportArchive(runner, runId, args) {
|
|
|
288
289
|
// offline. Default falls back to the same env the verify gate reads, so a single
|
|
289
290
|
// configured key both attests at record-time and travels with the export.
|
|
290
291
|
const trustPublicKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
|
|
291
|
-
|
|
292
|
+
const resolvedOutput = node_path_1.default.resolve(base, output);
|
|
293
|
+
const sysDirs = /^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//;
|
|
294
|
+
if (sysDirs.test(resolvedOutput)) {
|
|
295
|
+
throw new Error(`Refusing to write archive to a system directory: ${output}`);
|
|
296
|
+
}
|
|
297
|
+
return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), resolvedOutput, { trustPublicKey });
|
|
292
298
|
}
|
|
293
299
|
function runImportArchive(runner, args) {
|
|
294
300
|
const base = invocationCwd(args);
|
|
@@ -548,6 +554,7 @@ const DRIVE_RUNTIME_KEYS = [
|
|
|
548
554
|
"agent-timeout-ms",
|
|
549
555
|
"resume",
|
|
550
556
|
"incremental",
|
|
557
|
+
"concurrency",
|
|
551
558
|
// Remote-source flags (v0.1.91): materialized into a local checkout in the capability
|
|
552
559
|
// layer, never passed to plan as inputs (the resolved sourceUrl/sourceCommit ARE inputs).
|
|
553
560
|
"link",
|
|
@@ -587,6 +594,7 @@ function runDrive(runner, args) {
|
|
|
587
594
|
once: isTrue(args.once),
|
|
588
595
|
now: optionalString(args.now),
|
|
589
596
|
incremental: isTrue(args.incremental),
|
|
597
|
+
concurrency: (0, cli_options_1.numberOption)(args.concurrency),
|
|
590
598
|
args
|
|
591
599
|
});
|
|
592
600
|
}
|
|
@@ -467,7 +467,13 @@ const BUILTIN_CAPABILITIES = [
|
|
|
467
467
|
{ capability: "comment.list", summary: "List append-only comments for a run (optionally one target).", entry: "collaborationCommentList", surface: "both", cli: { path: ["comment", "list"], caseTokens: ["comment"], jsonMode: "flag" }, mcp: { tool: "cw_comment_list", requiredArgs: ["runId"] } },
|
|
468
468
|
{ capability: "handoff", summary: "Record an ownership transfer (from-actor → to-actor) of a run/task.", entry: "collaborationHandoff", surface: "both", cli: { path: ["handoff"], jsonMode: "default" }, mcp: { tool: "cw_handoff", requiredArgs: ["runId", "targetKind|kind", "targetId|target", "to|toActor"] } },
|
|
469
469
|
{ capability: "review.status", summary: "Read the derived per-target review state + collaboration timeline for a run.", entry: "reviewStatus", surface: "both", cli: { path: ["review", "status"], caseTokens: ["review"], jsonMode: "flag" }, mcp: { tool: "cw_review_status", requiredArgs: ["runId"] } },
|
|
470
|
-
{ capability: "review.policy", summary: "Set the run's review-gate policy (required approvals, authorized roles, self-approval rule).", entry: "reviewPolicy", surface: "both", cli: { path: ["review", "policy"], caseTokens: ["review"], jsonMode: "default" }, mcp: { tool: "cw_review_policy", requiredArgs: ["runId"] } }
|
|
470
|
+
{ capability: "review.policy", summary: "Set the run's review-gate policy (required approvals, authorized roles, self-approval rule).", entry: "reviewPolicy", surface: "both", cli: { path: ["review", "policy"], caseTokens: ["review"], jsonMode: "default" }, mcp: { tool: "cw_review_policy", requiredArgs: ["runId"] } },
|
|
471
|
+
// ---- Cross-agent handoff ledger (stage 2, MCP surface + git transport) --
|
|
472
|
+
{ capability: "ledger.propose", summary: "Build a verifiable cross-agent change proposal entry (printed as JSON).", entry: "buildLedgerProposal", surface: "both", cli: { path: ["ledger", "propose"], caseTokens: ["ledger", "propose"], jsonMode: "default" }, mcp: { tool: "cw_ledger_propose", requiredArgs: ["from", "to", "title", "rationale"] }, payloadIdentical: false, reason: "Mints a fresh entry each call: createdAt is the wall-clock instant and the id/digest are derived from it, so the output is inherently non-deterministic and a byte-identity probe does not apply. Both surfaces call the same buildLedgerProposal core; round-trip + fail-closed behavior is covered by ledger-verify-smoke." },
|
|
473
|
+
{ capability: "ledger.review", summary: "Build a verifiable cross-agent review verdict entry (printed as JSON).", entry: "buildLedgerReview", surface: "both", cli: { path: ["ledger", "review"], caseTokens: ["ledger", "review"], jsonMode: "default" }, mcp: { tool: "cw_ledger_review", requiredArgs: ["from", "to", "target", "verdict"] }, payloadIdentical: false, reason: "Mints a fresh timestamped/digested verdict each call — non-deterministic output, same reasoning as ledger.propose. Both surfaces call the same buildLedgerReview core." },
|
|
474
|
+
{ capability: "ledger.verify", summary: "Verify a ledger entry against its content digest (fail-closed on tampering).", entry: "verifyLedgerEntry", surface: "both", cli: { path: ["ledger", "verify"], caseTokens: ["ledger", "verify"], jsonMode: "default" }, mcp: { tool: "cw_ledger_verify", requiredArgs: ["entry"] }, payloadIdentical: false, reason: "The entry arrives by --file/stdin on the CLI and as an `entry` argument over MCP; there is no shared arg-bag the byte-identity probe can feed both. Both surfaces call the same verifyLedgerEntry core; ledger-verify-smoke proves the fail-closed contract." },
|
|
475
|
+
{ capability: "ledger.apply", summary: "Verify a proposal entry and return its suggestedDiff for `git apply` (fail-closed: no diff unless the entry verifies as a proposal).", entry: "applyLedgerProposal", surface: "both", cli: { path: ["ledger", "apply"], caseTokens: ["ledger", "apply"], jsonMode: "default" }, mcp: { tool: "cw_ledger_apply", requiredArgs: ["entry"] }, payloadIdentical: false, reason: "The entry arrives by --file/stdin on the CLI and as an `entry` argument over MCP; there is no shared arg-bag the byte-identity probe can feed both. Both surfaces call the same applyLedgerProposal core (a fail-closed wrapper over verifyLedgerEntry); ledger-apply-smoke proves the diff only escapes a verified proposal." },
|
|
476
|
+
{ capability: "ledger.list", summary: "Read + verify every entry in one or more shared ledger directories (fail-closed inbox; 2+ dirs union-verify mirrors).", entry: "listLedgerEntries", surface: "both", cli: { path: ["ledger", "list"], caseTokens: ["ledger", "list"], jsonMode: "default" }, mcp: { tool: "cw_ledger_list", requiredArgs: ["dir|dirs"] }, payloadIdentical: false, reason: "Output depends on the on-disk contents of the named ledger directory/directories, which the generic payload probe does not populate. Both surfaces call the same listLedgerEntries/unionLedgerEntries core; ledger-verify-smoke covers the fail-closed inbox and the multi-mirror union." }
|
|
471
477
|
];
|
|
472
478
|
/** The capability registry — the single source of truth, deduplicated by
|
|
473
479
|
* capability id (last declaration wins). Derived directly from the table above:
|
|
@@ -54,6 +54,7 @@ const registry_1 = require("./handlers/registry");
|
|
|
54
54
|
const multi_agent_1 = require("./handlers/multi-agent");
|
|
55
55
|
const run_1 = require("./handlers/run");
|
|
56
56
|
const collaboration_1 = require("./handlers/collaboration");
|
|
57
|
+
const ledger_1 = require("./handlers/ledger");
|
|
57
58
|
const blackboard_1 = require("./handlers/blackboard");
|
|
58
59
|
const eval_1 = require("./handlers/eval");
|
|
59
60
|
const node_1 = require("./handlers/node");
|
|
@@ -427,6 +428,9 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
427
428
|
case "review":
|
|
428
429
|
(0, collaboration_1.handleReview)(args, runner);
|
|
429
430
|
return;
|
|
431
|
+
case "ledger":
|
|
432
|
+
(0, ledger_1.handleLedger)(args, runner);
|
|
433
|
+
return;
|
|
430
434
|
case "loop": {
|
|
431
435
|
(0, io_1.printJson)(scheduler.create({ ...args.options, kind: "loop" }));
|
|
432
436
|
return;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `cw ledger propose|review|verify` — the cross-agent handoff ledger CLI surface.
|
|
3
|
+
// A proposing agent prints a proposal or a review verdict as a verifiable JSON
|
|
4
|
+
// entry; the receiving side verifies it fail-closed before acting. See
|
|
5
|
+
// docs/cross-agent-ledger.7.md and docs/designs/handoff-ledger.md.
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.handleLedger = handleLedger;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const ledger_1 = require("../../ledger");
|
|
43
|
+
const io_1 = require("../io");
|
|
44
|
+
/** Coerce a repeatable/comma-joined list option to a clean string[]. */
|
|
45
|
+
function listOption(value) {
|
|
46
|
+
const parts = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
47
|
+
return parts.map((p) => String(p).trim()).filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
function stringOption(value) {
|
|
50
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
51
|
+
}
|
|
52
|
+
function nowIso() {
|
|
53
|
+
return new Date().toISOString();
|
|
54
|
+
}
|
|
55
|
+
function handleLedger(args, _runner) {
|
|
56
|
+
const [subcommand] = args.positionals;
|
|
57
|
+
const opts = args.options;
|
|
58
|
+
switch (subcommand) {
|
|
59
|
+
case "propose": {
|
|
60
|
+
const entry = (0, ledger_1.buildLedgerProposal)({
|
|
61
|
+
from: (0, io_1.required)(stringOption(opts.from), "--from <agent/repo>"),
|
|
62
|
+
to: (0, io_1.required)(stringOption(opts.to), "--to <agent/repo>"),
|
|
63
|
+
title: (0, io_1.required)(stringOption(opts.title), "--title <text>"),
|
|
64
|
+
rationale: (0, io_1.required)(stringOption(opts.rationale), "--rationale <text>"),
|
|
65
|
+
targetFiles: listOption(opts.files),
|
|
66
|
+
// Do NOT trim the diff: it is a unified patch (payload, not a label), and
|
|
67
|
+
// trimming strips the trailing newline `git apply` requires — a trimmed
|
|
68
|
+
// diff is a corrupt patch. Presence is detected with a trimmed test, but
|
|
69
|
+
// the bytes are passed through verbatim (matching the MCP propose path).
|
|
70
|
+
suggestedDiff: typeof opts.diff === "string" && opts.diff.trim() ? opts.diff : undefined,
|
|
71
|
+
createdAt: nowIso()
|
|
72
|
+
});
|
|
73
|
+
(0, io_1.printJson)(entry);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case "review": {
|
|
77
|
+
const verdictRaw = (0, io_1.required)(stringOption(opts.verdict), "--verdict <approved|rejected>").toUpperCase();
|
|
78
|
+
if (verdictRaw !== "APPROVED" && verdictRaw !== "REJECTED") {
|
|
79
|
+
throw new Error('--verdict must be "approved" or "rejected".');
|
|
80
|
+
}
|
|
81
|
+
const entry = (0, ledger_1.buildLedgerReview)({
|
|
82
|
+
from: (0, io_1.required)(stringOption(opts.from), "--from <agent/repo>"),
|
|
83
|
+
to: (0, io_1.required)(stringOption(opts.to), "--to <agent/repo>"),
|
|
84
|
+
target: (0, io_1.required)(stringOption(opts.target), "--target <proposal-id|pr-ref>"),
|
|
85
|
+
verdict: verdictRaw,
|
|
86
|
+
findings: listOption(opts.findings),
|
|
87
|
+
createdAt: nowIso()
|
|
88
|
+
});
|
|
89
|
+
(0, io_1.printJson)(entry);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case "verify": {
|
|
93
|
+
const file = stringOption(opts.file);
|
|
94
|
+
let text;
|
|
95
|
+
try {
|
|
96
|
+
// --file <path>, else read the entry from stdin (fd 0).
|
|
97
|
+
text = fs.readFileSync(file || 0, "utf8");
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new Error(`Cannot read ledger entry${file ? ` from ${file}` : " from stdin"}: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(text);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// A non-JSON input is itself a fail-closed refusal, not a crash.
|
|
108
|
+
(0, io_1.printJson)({ ok: false, id: null, kind: null, checks: [{ name: "parse", pass: false, code: "ledger-bad-json" }], failedChecks: [{ name: "parse", code: "ledger-bad-json" }] });
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const result = (0, ledger_1.verifyLedgerEntry)(parsed);
|
|
113
|
+
(0, io_1.printJson)(result);
|
|
114
|
+
// Fail-closed: a tampered/malformed entry exits non-zero so
|
|
115
|
+
// `cw ledger verify <file> && open-pr` cannot proceed on a lie.
|
|
116
|
+
if (!result.ok)
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
case "apply": {
|
|
121
|
+
const file = stringOption(opts.file);
|
|
122
|
+
let text;
|
|
123
|
+
try {
|
|
124
|
+
// --file <path>, else read the entry from stdin (fd 0), same as verify.
|
|
125
|
+
text = fs.readFileSync(file || 0, "utf8");
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
throw new Error(`Cannot read ledger entry${file ? ` from ${file}` : " from stdin"}: ${error.message}`);
|
|
129
|
+
}
|
|
130
|
+
let parsed;
|
|
131
|
+
try {
|
|
132
|
+
parsed = JSON.parse(text);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
(0, io_1.printJson)({ ok: false, id: null, kind: null, diff: null, failedChecks: [{ name: "parse", code: "ledger-bad-json" }] });
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const result = (0, ledger_1.applyLedgerProposal)(parsed);
|
|
140
|
+
(0, io_1.printJson)(result);
|
|
141
|
+
// Fail-closed: the diff only comes out (ok:true) when the proposal verifies,
|
|
142
|
+
// so `cw ledger apply <file> | git apply` never feeds git an unverified patch.
|
|
143
|
+
if (!result.ok)
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
case "list": {
|
|
148
|
+
// `--dir` is repeatable: 2+ dirs union-verify multiple mirrors into one
|
|
149
|
+
// inbox; a single --dir keeps the original single-directory output (POLA).
|
|
150
|
+
const dirs = Array.isArray(opts.dir) ? opts.dir.map(String).filter(Boolean) : [];
|
|
151
|
+
if (dirs.length > 1) {
|
|
152
|
+
const union = (0, ledger_1.unionLedgerEntries)(dirs);
|
|
153
|
+
(0, io_1.printJson)(union);
|
|
154
|
+
if (!union.allOk)
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const dir = (0, io_1.required)(dirs[0] || stringOption(opts.dir), "--dir <ledger-directory>");
|
|
159
|
+
const result = (0, ledger_1.listLedgerEntries)(dir);
|
|
160
|
+
(0, io_1.printJson)(result);
|
|
161
|
+
// Fail-closed inbox: refuse the whole batch if any entry does not verify.
|
|
162
|
+
if (!result.allOk)
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
default:
|
|
167
|
+
throw new Error("Usage: cw ledger propose|review|verify|apply|list [options]");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -77,7 +77,13 @@ function handleRoutine(args, triggers) {
|
|
|
77
77
|
return;
|
|
78
78
|
case "fire": {
|
|
79
79
|
const kind = (0, io_1.required)(idOrKind, "trigger kind");
|
|
80
|
-
|
|
80
|
+
let payload;
|
|
81
|
+
try {
|
|
82
|
+
payload = payloadPath ? JSON.parse(node_fs_1.default.readFileSync(payloadPath, "utf8")) : args.options;
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
throw new Error(`Failed to parse payload${payloadPath ? ` file "${payloadPath}"` : ""}: ${String(e && e.message || e)}`);
|
|
86
|
+
}
|
|
81
87
|
(0, io_1.printJson)(triggers.fire(kind, payload));
|
|
82
88
|
return;
|
|
83
89
|
}
|