cool-workflow 0.2.0 → 0.2.2
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 +7 -5
- package/apps/architecture-review/app.json +2 -2
- package/apps/architecture-review/workflow.js +12 -12
- 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/cli/dispatch.js +20 -88
- package/dist/cli/parseargv.js +6 -3
- package/dist/core/capability-data.js +270 -0
- package/dist/core/capability-table.js +15 -3519
- package/dist/core/format/help.js +30 -7
- package/dist/core/hash.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +1 -1
- package/dist/core/multi-agent/collaboration.js +3 -2
- package/dist/core/multi-agent/coordinator.js +4 -3
- package/dist/core/multi-agent/eval-replay.js +1 -1
- package/dist/core/multi-agent/runtime.js +2 -2
- package/dist/core/multi-agent/trust-policy.js +1 -1
- package/dist/core/pipeline/commit-gate.js +2 -1
- package/dist/core/pipeline/contract.js +1 -1
- package/dist/core/pipeline/drive-decide.js +1 -1
- package/dist/core/state/contract-migration.js +1 -1
- package/dist/core/state/migrations.js +2 -2
- package/dist/core/state/node-snapshot.js +1 -1
- package/dist/core/state/state-explosion/digest.js +3 -2
- package/dist/core/state/state-explosion/graph.js +13 -12
- package/dist/core/state/state-explosion/helpers.js +5 -4
- package/dist/core/state/state-explosion/report.js +1 -1
- package/dist/core/state/state-explosion/size.js +1 -1
- package/dist/core/state/state-node.js +2 -2
- package/dist/core/state/types.js +1 -1
- package/dist/core/trust/ledger.js +3 -2
- package/dist/core/trust/telemetry-attestation.js +3 -3
- package/dist/core/trust/telemetry-ledger.js +2 -2
- package/dist/core/types/execution-backend.js +18 -0
- package/dist/core/types/observability.js +7 -0
- package/dist/core/util/collate.js +23 -0
- package/dist/core/version.js +1 -1
- package/dist/core/workflow-apps/app-schema.js +1 -1
- package/dist/mcp/dispatch.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/shell/agent-config.js +1 -1
- package/dist/shell/audit-cli.js +46 -2
- package/dist/shell/commit-summary.js +2 -1
- package/dist/shell/doctor.js +17 -4
- package/dist/shell/drive.js +47 -19
- package/dist/shell/evidence-reasoning.js +4 -3
- package/dist/shell/execution-backend/agent.js +42 -10
- package/dist/shell/execution-backend/ci.js +1 -1
- package/dist/shell/execution-backend/container.js +1 -1
- package/dist/shell/execution-backend/envelopes.js +1 -1
- package/dist/shell/execution-backend/local.js +1 -1
- package/dist/shell/execution-backend/probes.js +1 -1
- package/dist/shell/execution-backend/registry.js +3 -2
- package/dist/shell/execution-backend/remote.js +1 -1
- package/dist/shell/execution-backend/types.js +0 -9
- package/dist/shell/fs-atomic.js +15 -2
- package/dist/shell/ledger-io.js +1 -1
- package/dist/shell/metrics-cli.js +4 -2
- package/dist/shell/multi-agent-host.js +1 -1
- package/dist/shell/multi-agent-operator-ux.js +4 -3
- package/dist/shell/observability.js +12 -11
- package/dist/shell/onramp.js +17 -1
- package/dist/shell/operator-ux-text.js +2 -1
- package/dist/shell/operator-ux.js +7 -6
- package/dist/shell/pipeline-cli.js +82 -72
- package/dist/shell/reclamation-io.js +3 -2
- package/dist/shell/registry-cli.js +15 -0
- package/dist/shell/report.js +2 -1
- package/dist/shell/run-registry-io.js +1 -1
- package/dist/shell/run-store.js +17 -0
- package/dist/shell/sandbox-profile.js +1 -1
- package/dist/shell/scheduler-io.js +46 -37
- package/dist/shell/scheduling-io.js +32 -22
- package/dist/shell/state-explosion-cli.js +2 -1
- package/dist/shell/telemetry-ledger-io.js +1 -1
- package/dist/shell/term.js +1 -1
- package/dist/shell/topology-io.js +2 -1
- package/dist/shell/trust-audit.js +45 -5
- package/dist/shell/workbench-host.js +9 -1
- package/dist/shell/workbench.js +6 -11
- package/dist/shell/worker-cli.js +9 -1
- package/dist/shell/worker-isolation.js +34 -9
- package/dist/shell/workflow-app-loader.js +3 -2
- package/dist/wiring/capability-table/basics.js +66 -0
- package/dist/wiring/capability-table/exec-backend.js +162 -0
- package/dist/wiring/capability-table/index.js +42 -0
- package/dist/wiring/capability-table/multi-agent.js +623 -0
- package/dist/wiring/capability-table/parity.js +466 -0
- package/dist/wiring/capability-table/pipeline.js +280 -0
- package/dist/wiring/capability-table/registry-core.js +189 -0
- package/dist/wiring/capability-table/reporting.js +394 -0
- package/dist/wiring/capability-table/scheduling-registry.js +558 -0
- package/dist/wiring/capability-table/state.js +173 -0
- package/dist/wiring/capability-table/trust-ledger.js +134 -0
- package/dist/wiring/capability-table/workflow-apps.js +366 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +9 -2
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/durable-state-and-locking.7.md +24 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/index.md +1 -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 +6 -1
- package/docs/project-index.md +30 -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/remote-source-review.7.md +4 -4
- package/docs/report-verifiable-bundle.7.md +1 -1
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/security-trust-hardening.7.md +33 -1
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +69 -0
- package/docs/web-desktop-workbench.7.md +15 -1
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +3 -1
- package/scripts/agents/agent-adapter-core.js +22 -2
- package/scripts/agents/claude-p-agent.js +8 -3
- package/scripts/agents/gemini-agent.js +5 -1
- package/scripts/agents/opencode-agent.js +5 -1
- package/scripts/bump-version.js +24 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +68 -0
- package/scripts/purity-gate.js +239 -0
- package/scripts/release-check.js +8 -1
- package/scripts/version-sync-check.js +33 -12
- package/workflows/README.md +19 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// lease-plan mechanism over the run-registry-io.ts queue store, plus the
|
|
4
4
|
// policy file IO.
|
|
5
5
|
//
|
|
6
|
-
// MILESTONE 10 (
|
|
6
|
+
// MILESTONE 10 (docs/rebuild/PLAN.md build order, step 10). Byte-exact port of the
|
|
7
7
|
// old build's src/scheduling.ts + the sched-specific slice of
|
|
8
8
|
// src/capability-core.ts. `sched` is a DIFFERENT system from `schedule`
|
|
9
9
|
// (see scheduler-io.ts's file header) — this operates on
|
|
@@ -251,43 +251,53 @@ function schedLeaseCli(options = {}) {
|
|
|
251
251
|
const now = nowIso(options);
|
|
252
252
|
const policy = loadSchedulingPolicy(registry).policy;
|
|
253
253
|
const limit = options.limit === undefined ? undefined : Number(options.limit);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
|
|
255
|
+
const { entries, leases } = applyLease(registry.loadQueueEntries(), policy, now, limit);
|
|
256
|
+
registry.saveQueueEntries(entries);
|
|
257
|
+
return { schemaVersion: 1, now, granted: leases.length, leases };
|
|
258
|
+
});
|
|
257
259
|
}
|
|
258
260
|
function schedReleaseCli(leaseId, options = {}) {
|
|
259
261
|
const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
|
|
260
262
|
const now = nowIso(options);
|
|
261
263
|
const failed = isTrue(options.failed);
|
|
262
264
|
const reason = typeof options.reason === "string" ? options.reason : undefined;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
|
|
266
|
+
const { entries, matched } = leaseRelease(registry.loadQueueEntries(), leaseId, loadSchedulingPolicy(registry).policy, now, { failed, reason });
|
|
267
|
+
if (!matched)
|
|
268
|
+
throw new Error(`No active lease to release: ${leaseId}`);
|
|
269
|
+
registry.saveQueueEntries(entries);
|
|
270
|
+
return { schemaVersion: 1, released: leaseId, failed };
|
|
271
|
+
});
|
|
268
272
|
}
|
|
269
273
|
function schedCompleteCli(leaseId, options = {}) {
|
|
270
274
|
const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
|
|
276
|
+
const { entries, matched } = leaseComplete(registry.loadQueueEntries(), leaseId, nowIso(options));
|
|
277
|
+
if (!matched)
|
|
278
|
+
throw new Error(`No active lease to complete: ${leaseId}`);
|
|
279
|
+
registry.saveQueueEntries(entries);
|
|
280
|
+
return { schemaVersion: 1, completed: leaseId };
|
|
281
|
+
});
|
|
276
282
|
}
|
|
277
283
|
function schedReclaimCli(options = {}) {
|
|
278
284
|
const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
|
|
279
285
|
const now = nowIso(options);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
286
|
+
return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
|
|
287
|
+
const { entries, reclaimed } = reclaimExpired(registry.loadQueueEntries(), loadSchedulingPolicy(registry).policy, now);
|
|
288
|
+
registry.saveQueueEntries(entries);
|
|
289
|
+
return { schemaVersion: 1, now, reclaimed };
|
|
290
|
+
});
|
|
283
291
|
}
|
|
284
292
|
function schedResetCli(id, options = {}) {
|
|
285
293
|
const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
294
|
+
return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
|
|
295
|
+
const { entries, matched } = resetEntry(registry.loadQueueEntries(), id);
|
|
296
|
+
if (!matched)
|
|
297
|
+
throw new Error(`No parked entry to reset: ${id}`);
|
|
298
|
+
registry.saveQueueEntries(entries);
|
|
299
|
+
return { schemaVersion: 1, reset: id };
|
|
300
|
+
});
|
|
291
301
|
}
|
|
292
302
|
function schedPolicyShowCli(options = {}) {
|
|
293
303
|
const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
|
|
@@ -66,6 +66,7 @@ const report_1 = require("../core/state/state-explosion/report");
|
|
|
66
66
|
Object.defineProperty(exports, "buildStateExplosionReport", { enumerable: true, get: function () { return report_1.buildStateExplosionReport; } });
|
|
67
67
|
const size_2 = require("../core/state/state-explosion/size");
|
|
68
68
|
const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
|
|
69
|
+
const collate_1 = require("../core/util/collate");
|
|
69
70
|
function summariesDir(run) {
|
|
70
71
|
return path.join(run.paths.runDir, "summaries");
|
|
71
72
|
}
|
|
@@ -134,7 +135,7 @@ function refreshStateExplosionSummaries(run, options = {}) {
|
|
|
134
135
|
status: "valid",
|
|
135
136
|
deterministic: true,
|
|
136
137
|
nextAction: `node scripts/cw.js summary show ${run.id}`,
|
|
137
|
-
entries: entries.sort((a, b) => a.id
|
|
138
|
+
entries: entries.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
|
|
138
139
|
views,
|
|
139
140
|
paths: { summariesDir: dir, indexPath: path.join(dir, "index.json"), reportPath },
|
|
140
141
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// math lives in core/trust/telemetry-ledger.ts.
|
|
5
5
|
//
|
|
6
6
|
// MILESTONE 8. Byte-exact port of the old build's src/telemetry-ledger.ts
|
|
7
|
-
// IO-touching half. CRITICAL invariant (
|
|
7
|
+
// IO-touching half. CRITICAL invariant (docs/rebuild/PLAN.md byte-compat item 12):
|
|
8
8
|
// an ABSENT telemetry.json is an empty, clean-verifying chain
|
|
9
9
|
// (`present:false`); a PRESENT but unparseable one is CORRUPT — reads
|
|
10
10
|
// report `telemetry-ledger-corrupt`, and append THROWS
|
package/dist/shell/term.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// shell/term.ts — zero-dependency terminal styling.
|
|
3
3
|
//
|
|
4
|
-
// MILESTONE 5 (
|
|
4
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5, doctor/fix): TTY-gated
|
|
5
5
|
// ANSI formatting, so a piped run (every conformance case pipes with
|
|
6
6
|
// NO_COLOR=1) prints plain text. EXTENDED at MILESTONE 11 (reporting/
|
|
7
7
|
// observability) with the rest of the old build's src/term.ts: indent,
|
|
@@ -63,6 +63,7 @@ const hash_2 = require("../core/hash");
|
|
|
63
63
|
const topo = __importStar(require("../core/multi-agent/topology"));
|
|
64
64
|
const multi_agent_io_1 = require("./multi-agent-io");
|
|
65
65
|
const coordinator_io_1 = require("./coordinator-io");
|
|
66
|
+
const collate_1 = require("../core/util/collate");
|
|
66
67
|
function topologyRoot(run) {
|
|
67
68
|
return run.paths.topologiesDir || path.join(run.paths.runDir, "topologies");
|
|
68
69
|
}
|
|
@@ -307,7 +308,7 @@ function buildTopologyGraph(run) {
|
|
|
307
308
|
return topo.buildTopologyGraphFromRuns(run.id, state.runs, (id) => topologyRunPath(run, id));
|
|
308
309
|
}
|
|
309
310
|
function formatTopologyCounts(counts) {
|
|
310
|
-
const entries = Object.entries(counts).sort(([a], [b]) =>
|
|
311
|
+
const entries = Object.entries(counts).sort(([a], [b]) => (0, collate_1.stableCompare)(a, b));
|
|
311
312
|
if (!entries.length)
|
|
312
313
|
return "none";
|
|
313
314
|
return entries.map(([k, v]) => `${k}=${v}`).join(", ");
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// the era-rule check + trustAuditGenesis on top, and switches
|
|
14
14
|
// computeEventHash to use core/hash.ts's named `eventHashInput` export
|
|
15
15
|
// (byte-identical behavior to the pre-existing inline JSON-round-trip,
|
|
16
|
-
// now shared with the rest of the hash-dedup story per
|
|
16
|
+
// now shared with the rest of the hash-dedup story per docs/rebuild/PLAN.md byte-
|
|
17
17
|
// compat item 2).
|
|
18
18
|
//
|
|
19
19
|
// Evidence: SPEC/ledger-trust.md "Trust-audit chain", invariant 10 (era
|
|
@@ -58,6 +58,7 @@ exports.TRUST_AUDIT_SCHEMA_VERSION = void 0;
|
|
|
58
58
|
exports.ensureTrustAudit = ensureTrustAudit;
|
|
59
59
|
exports.trustAuditGenesis = trustAuditGenesis;
|
|
60
60
|
exports.listTrustAuditEvents = listTrustAuditEvents;
|
|
61
|
+
exports.trustAuditHead = trustAuditHead;
|
|
61
62
|
exports.verifyTrustAudit = verifyTrustAudit;
|
|
62
63
|
exports.recordTrustAuditEvent = recordTrustAuditEvent;
|
|
63
64
|
exports.recordSandboxPathDecision = recordSandboxPathDecision;
|
|
@@ -97,7 +98,7 @@ function trustAuditGenesis(runId) {
|
|
|
97
98
|
* itself, via core/hash.ts's `eventHashInput` (the JSON round-trip
|
|
98
99
|
* pre-pass that drops nested `undefined`-valued keys BEFORE the
|
|
99
100
|
* sort-and-stringify step — not a formatting flag on the same shape,
|
|
100
|
-
* see
|
|
101
|
+
* see docs/rebuild/PLAN.md byte-compat item 2). Hashing the PERSISTED form this
|
|
101
102
|
* way makes record-time hashes equal verify-time (parsed-from-disk)
|
|
102
103
|
* hashes. */
|
|
103
104
|
function computeEventHash(event) {
|
|
@@ -136,19 +137,40 @@ function readEventsRaw(eventLogPath) {
|
|
|
136
137
|
function listTrustAuditEvents(run) {
|
|
137
138
|
return readEventsRaw(ensureTrustAudit(run).eventLogPath);
|
|
138
139
|
}
|
|
140
|
+
/** The current head of a run's trust-audit chain: the hash the NEXT
|
|
141
|
+
* appended event will link from (genesis when the log is empty), plus
|
|
142
|
+
* the event count. Read-only projection over existing data. Capture it
|
|
143
|
+
* (e.g. right after a run, or at export time) and later hand it to
|
|
144
|
+
* `verifyTrustAudit`'s anchor / `cw audit verify --expect-head` to
|
|
145
|
+
* re-prove the log was not shortened since the capture. */
|
|
146
|
+
function trustAuditHead(run) {
|
|
147
|
+
const audit = ensureTrustAudit(run);
|
|
148
|
+
const events = readEventsRaw(audit.eventLogPath);
|
|
149
|
+
let head = trustAuditGenesis(run.id);
|
|
150
|
+
for (const event of events) {
|
|
151
|
+
head = event.eventHash !== undefined ? event.eventHash : computeEventHash(event);
|
|
152
|
+
}
|
|
153
|
+
return { eventCount: events.length, headHash: head };
|
|
154
|
+
}
|
|
139
155
|
/** Re-prove the run's trust-audit chain: prevEventHash linkage (append
|
|
140
156
|
* order) + per-event hash recompute. A corrupt line, an edited event,
|
|
141
157
|
* or a removed event flips verified=false. Legacy events without a
|
|
142
158
|
* hash are reported as `unchained` (skipped), NOT treated as a forgery
|
|
143
159
|
* — they predate the chain.
|
|
144
160
|
*
|
|
145
|
-
* ERA RULE (
|
|
161
|
+
* ERA RULE (docs/rebuild/PLAN.md, SPEC/ledger-trust.md invariant 10): a single
|
|
146
162
|
* run is written by one code version, so a log is all-chained (chain
|
|
147
163
|
* era) or all-legacy (pre-chain). An unchained (eventHash-less) line
|
|
148
164
|
* mixed into an otherwise-chained log is a forgery attempt — dropping
|
|
149
165
|
* the hash to be waved through as "legacy" — so it fails with
|
|
150
|
-
* `trust-audit-unchained-event`, never silently accepted.
|
|
151
|
-
|
|
166
|
+
* `trust-audit-unchained-event`, never silently accepted.
|
|
167
|
+
*
|
|
168
|
+
* ANCHOR (optional): the walk alone cannot see tail truncation — see
|
|
169
|
+
* TrustAuditAnchor. With an anchor, the head-hash trail (genesis plus
|
|
170
|
+
* the hash after each event) must contain `expectHead`, and the log
|
|
171
|
+
* must reach `expectCount` events; a shortfall fails closed with
|
|
172
|
+
* `trust-audit-truncated`. Without an anchor, behavior is unchanged. */
|
|
173
|
+
function verifyTrustAudit(run, anchor) {
|
|
152
174
|
const audit = ensureTrustAudit(run);
|
|
153
175
|
const { events, corruptLines } = readEventsRawCounted(audit.eventLogPath);
|
|
154
176
|
const checks = [];
|
|
@@ -158,12 +180,14 @@ function verifyTrustAudit(run) {
|
|
|
158
180
|
let chained = 0;
|
|
159
181
|
let unchained = 0;
|
|
160
182
|
let expectedPrev = trustAuditGenesis(run.id);
|
|
183
|
+
const headTrail = new Set([expectedPrev]);
|
|
161
184
|
for (let i = 0; i < events.length; i++) {
|
|
162
185
|
const event = events[i];
|
|
163
186
|
const recomputed = computeEventHash(event);
|
|
164
187
|
if (event.eventHash === undefined) {
|
|
165
188
|
unchained += 1;
|
|
166
189
|
expectedPrev = recomputed; // advance the chain over legacy events
|
|
190
|
+
headTrail.add(expectedPrev);
|
|
167
191
|
continue;
|
|
168
192
|
}
|
|
169
193
|
chained += 1;
|
|
@@ -176,12 +200,27 @@ function verifyTrustAudit(run) {
|
|
|
176
200
|
checks.push({ name: `chain-link[${i}]`, pass: false, code: "trust-audit-chain-broken" });
|
|
177
201
|
}
|
|
178
202
|
expectedPrev = event.eventHash;
|
|
203
|
+
headTrail.add(expectedPrev);
|
|
179
204
|
}
|
|
180
205
|
// Era rule: a log with ANY chained event must have EVERY event chained.
|
|
181
206
|
if (chained > 0 && unchained > 0) {
|
|
182
207
|
verified = false;
|
|
183
208
|
checks.push({ name: "unchained-events", pass: false, code: "trust-audit-unchained-event" });
|
|
184
209
|
}
|
|
210
|
+
// Anchor rule: the captured head must still be ON the chain, and the log
|
|
211
|
+
// must be at least as long as it was at capture time. A truncated-then-
|
|
212
|
+
// appended log fails the head check (new events link from an earlier
|
|
213
|
+
// point, so the old head is no longer in the trail).
|
|
214
|
+
if (anchor) {
|
|
215
|
+
if (anchor.expectCount !== undefined && events.length < anchor.expectCount) {
|
|
216
|
+
verified = false;
|
|
217
|
+
checks.push({ name: "anchor-count", pass: false, code: "trust-audit-truncated" });
|
|
218
|
+
}
|
|
219
|
+
if (anchor.expectHead !== undefined && !headTrail.has(anchor.expectHead)) {
|
|
220
|
+
verified = false;
|
|
221
|
+
checks.push({ name: "anchor-head", pass: false, code: "trust-audit-truncated" });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
185
224
|
return { present: events.length > 0, verified, eventCount: events.length, chained, unchained, corruptLines, checks };
|
|
186
225
|
}
|
|
187
226
|
function unique(values) {
|
|
@@ -277,6 +316,7 @@ function recordTrustAuditEvent(run, input) {
|
|
|
277
316
|
normalizedPath: input.normalizedPath ? path.resolve(input.normalizedPath) : undefined,
|
|
278
317
|
command: input.command,
|
|
279
318
|
networkTarget: input.networkTarget,
|
|
319
|
+
envVars: input.envVars?.filter(Boolean).sort(),
|
|
280
320
|
evidence: normalizeEvidence(run, input.evidence || [], { source: input.source, workerId: input.workerId, taskId: input.taskId, resultNodeId: input.nodeId }),
|
|
281
321
|
evidenceRefs: unique(input.evidenceRefs || []).sort(),
|
|
282
322
|
parentEventIds: unique(input.parentEventIds || []).sort(),
|
|
@@ -233,8 +233,16 @@ class WorkbenchHost {
|
|
|
233
233
|
/** `cw workbench serve` entry point: `--once`/`--json` prints ONLY the
|
|
234
234
|
* descriptor (compact JSON) and starts nothing; the default binds and
|
|
235
235
|
* blocks, printing one compact line (`{...descriptor, boundPort}`)
|
|
236
|
-
* once listening.
|
|
236
|
+
* once listening. `--require-token` is a strict opt-in: default behavior
|
|
237
|
+
* (unauthenticated local reads when no token is configured) is
|
|
238
|
+
* unchanged unless the caller explicitly asks to fail closed. */
|
|
237
239
|
async run() {
|
|
240
|
+
const requireToken = Boolean(this.args.requireToken || this.args["require-token"]);
|
|
241
|
+
if (requireToken && !this.token) {
|
|
242
|
+
process.stderr.write("workbench serve --require-token: CW_WORKBENCH_TOKEN is not set; refusing to start.\n");
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
238
246
|
const once = Boolean(this.args.once || this.args.json);
|
|
239
247
|
if (once) {
|
|
240
248
|
process.stdout.write(`${JSON.stringify(this.descriptor(true))}\n`);
|
package/dist/shell/workbench.js
CHANGED
|
@@ -116,18 +116,13 @@ function buildWorkbenchRunView(runId, args = {}) {
|
|
|
116
116
|
}
|
|
117
117
|
return { schemaVersion: 1, surface: "workbench", runId, resolved, ...(resolveError ? { error: resolveError } : {}), panels };
|
|
118
118
|
}
|
|
119
|
+
/** Package-relative resolution only — never falls back to the invocation
|
|
120
|
+
* cwd. `ui/` ships as a sibling of `dist/` in the published package
|
|
121
|
+
* (package.json's `files`), so from `dist/shell/workbench.js` the fixed
|
|
122
|
+
* path is two levels up. Matches the existing precedent in
|
|
123
|
+
* execution-backend/agent.ts's BATCH_DELEGATE_CHILD_SCRIPT resolution. */
|
|
119
124
|
function workbenchUiRoot() {
|
|
120
|
-
|
|
121
|
-
for (let i = 0; i < 8; i++) {
|
|
122
|
-
const candidate = path.join(dir, "plugins", "cool-workflow", exports.WORKBENCH_UI_RELATIVE);
|
|
123
|
-
if (require("node:fs").existsSync(candidate))
|
|
124
|
-
return candidate;
|
|
125
|
-
const parent = path.dirname(dir);
|
|
126
|
-
if (parent === dir)
|
|
127
|
-
break;
|
|
128
|
-
dir = parent;
|
|
129
|
-
}
|
|
130
|
-
return path.join(process.cwd(), exports.WORKBENCH_UI_RELATIVE);
|
|
125
|
+
return path.resolve(__dirname, "..", "..", exports.WORKBENCH_UI_RELATIVE);
|
|
131
126
|
}
|
|
132
127
|
const WORKBENCH_ROUTES = [
|
|
133
128
|
{ method: "GET", path: "/", description: "Index page (or the UI's index.html, when installed)." },
|
package/dist/shell/worker-cli.js
CHANGED
|
@@ -51,6 +51,7 @@ const drive_1 = require("./drive");
|
|
|
51
51
|
const commit_1 = require("./commit");
|
|
52
52
|
const report_1 = require("./report");
|
|
53
53
|
const operator_ux_1 = require("./operator-ux");
|
|
54
|
+
const agent_config_1 = require("./agent-config");
|
|
54
55
|
function cwdFor(args) {
|
|
55
56
|
return typeof args.cwd === "string" && args.cwd.trim() ? path.resolve(args.cwd) : process.cwd();
|
|
56
57
|
}
|
|
@@ -60,6 +61,10 @@ function req(value, label) {
|
|
|
60
61
|
throw new Error(`Missing ${label}`);
|
|
61
62
|
return s;
|
|
62
63
|
}
|
|
64
|
+
/** `--allow-unattested` (CLI: dashed key; MCP: allowUnattested). */
|
|
65
|
+
function allowUnattestedOption(args) {
|
|
66
|
+
return Boolean(args.allowUnattested ?? args["allow-unattested"]);
|
|
67
|
+
}
|
|
63
68
|
function workerListCli(args) {
|
|
64
69
|
const run = (0, run_store_1.loadRunFromCwd)(req(args.runId, "run id"), cwdFor(args));
|
|
65
70
|
return (0, worker_isolation_1.listWorkerScopes)(run, { status: typeof args.status === "string" ? args.status : undefined });
|
|
@@ -92,7 +97,10 @@ function workerManifestCli(args) {
|
|
|
92
97
|
* same steps itself around the bare accept, so it never routes through here. */
|
|
93
98
|
function workerOutputCli(args) {
|
|
94
99
|
const run = (0, run_store_1.loadRunFromCwd)(req(args.runId, "run id"), cwdFor(args));
|
|
95
|
-
(0, worker_isolation_1.recordWorkerOutput)(run, req(args.workerId, "worker id"), req(args.resultPath, "result file"), {
|
|
100
|
+
(0, worker_isolation_1.recordWorkerOutput)(run, req(args.workerId, "worker id"), req(args.resultPath, "result file"), {
|
|
101
|
+
requireAttestedTelemetry: (0, agent_config_1.resolveAgentConfig)(args).requireAttestedTelemetry,
|
|
102
|
+
allowUnattested: allowUnattestedOption(args),
|
|
103
|
+
});
|
|
96
104
|
run.loopStage = "observe";
|
|
97
105
|
(0, dispatch_1.updatePhaseStatuses)(run);
|
|
98
106
|
(0, drive_1.maybeExpandLoop)(run);
|
|
@@ -106,6 +106,7 @@ function workerBlackboardManifest(run, task) {
|
|
|
106
106
|
const verifier_1 = require("./verifier");
|
|
107
107
|
const runner_1 = require("../core/pipeline/runner");
|
|
108
108
|
const hash_1 = require("../core/hash");
|
|
109
|
+
const collate_1 = require("../core/util/collate");
|
|
109
110
|
const telemetry_attestation_1 = require("../core/trust/telemetry-attestation");
|
|
110
111
|
const telemetry_ledger_io_1 = require("./telemetry-ledger-io");
|
|
111
112
|
exports.WORKER_ISOLATION_SCHEMA_VERSION = 1;
|
|
@@ -531,13 +532,37 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
|
|
|
531
532
|
})
|
|
532
533
|
: undefined;
|
|
533
534
|
// Opt-in fail-closed gate (default off): when the operator requires
|
|
534
|
-
// attested telemetry,
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
535
|
+
// attested telemetry, an accept whose usage cannot be verified is
|
|
536
|
+
// REJECTED here — BEFORE any accept-side state mutation — so the drive
|
|
537
|
+
// parks it instead of recording unverifiable usage. This fires on BOTH
|
|
538
|
+
// shapes: a delegation present but not attested (telemetry.status !==
|
|
539
|
+
// "attested"), and NO delegation metadata at all. The second shape is
|
|
540
|
+
// the gap a manual `cw worker output` / `cw result` accept used to slip
|
|
541
|
+
// through silently: options.agentDelegation was simply absent, so
|
|
542
|
+
// `telemetry` was undefined and the old `telemetry &&` condition
|
|
543
|
+
// short-circuited false — an unattested result could be laundered
|
|
544
|
+
// through the manual accept path even with the require flag on.
|
|
545
|
+
// --allow-unattested is the operator's explicit way past this: it never
|
|
546
|
+
// skips the gate silently, it records a telemetry.gate-override event.
|
|
547
|
+
if (options.requireAttestedTelemetry && (!telemetry || telemetry.status !== "attested")) {
|
|
548
|
+
if (options.allowUnattested) {
|
|
549
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
550
|
+
kind: "telemetry.gate-override",
|
|
551
|
+
decision: "allowed",
|
|
552
|
+
source: "operator",
|
|
553
|
+
workerId,
|
|
554
|
+
taskId: task.id,
|
|
555
|
+
metadata: { reason: "--allow-unattested", telemetryStatus: telemetry ? telemetry.status : "absent" },
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const code = telemetry ? "telemetry-unattested-blocked" : "telemetry-missing-blocked";
|
|
560
|
+
const message = telemetry
|
|
561
|
+
? `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`
|
|
562
|
+
: `Worker ${workerId} carries no agent-delegation telemetry at all and require-attested-telemetry is enabled — refusing to accept an unattested manual result (pass --allow-unattested to record an audited override)`;
|
|
563
|
+
recordWorkerFailure(run, workerId, message, { code, path: absoluteResultPath, retryable: false });
|
|
564
|
+
throw new Error(message);
|
|
565
|
+
}
|
|
541
566
|
}
|
|
542
567
|
const agentDelegationMeta = delegation
|
|
543
568
|
? {
|
|
@@ -797,7 +822,7 @@ function listWorkerScopes(run, options = {}) {
|
|
|
797
822
|
// silently drops workers whenever run.workers was reset.
|
|
798
823
|
const merged = mergeScopes(run.workers || [], loadWorkerScopesFromDisk(run));
|
|
799
824
|
run.workers = merged;
|
|
800
|
-
const workers = merged.slice().sort((a, b) => a.id
|
|
825
|
+
const workers = merged.slice().sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
|
|
801
826
|
return options.status ? workers.filter((w) => w.status === options.status) : workers;
|
|
802
827
|
}
|
|
803
828
|
function countByStatus(workers) {
|
|
@@ -824,7 +849,7 @@ function countBucket(values) {
|
|
|
824
849
|
return counts;
|
|
825
850
|
}
|
|
826
851
|
function formatCountBucket(counts) {
|
|
827
|
-
const entries = Object.entries(counts).sort(([a], [b]) =>
|
|
852
|
+
const entries = Object.entries(counts).sort(([a], [b]) => (0, collate_1.stableCompare)(a, b));
|
|
828
853
|
if (!entries.length)
|
|
829
854
|
return "none";
|
|
830
855
|
return entries.map(([k, v]) => `${k}=${v}`).join(", ");
|
|
@@ -75,6 +75,7 @@ const path = __importStar(require("node:path"));
|
|
|
75
75
|
const app_schema_1 = require("../core/workflow-apps/app-schema");
|
|
76
76
|
const sandbox_profile_1 = require("./sandbox-profile");
|
|
77
77
|
const version_1 = require("../core/version");
|
|
78
|
+
const collate_1 = require("../core/util/collate");
|
|
78
79
|
class WorkflowAppNotFoundError extends Error {
|
|
79
80
|
constructor(appId) {
|
|
80
81
|
super(`Workflow app not found: ${appId}`);
|
|
@@ -421,10 +422,10 @@ function listWorkflowAppRecords() {
|
|
|
421
422
|
...loadWorkflowFiles(workflowsDir).map((file) => loadWorkflowAppFromEntrypoint(file)),
|
|
422
423
|
...loadAppManifestFiles(appsDir).map((file) => loadWorkflowAppFromManifest(file)),
|
|
423
424
|
].sort((left, right) => {
|
|
424
|
-
const byId = left.app.id
|
|
425
|
+
const byId = (0, collate_1.stableCompare)(left.app.id, right.app.id);
|
|
425
426
|
if (byId)
|
|
426
427
|
return byId;
|
|
427
|
-
return sourcePathOf(left)
|
|
428
|
+
return (0, collate_1.stableCompare)(sourcePathOf(left), sourcePathOf(right));
|
|
428
429
|
});
|
|
429
430
|
const seen = new Map();
|
|
430
431
|
for (const record of records) {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// wiring/capability-table/basics.ts — MILESTONE 2's CLI bindings (version,
|
|
3
|
+
// list, status, sandbox.list). Split out of core/capability-table.ts,
|
|
4
|
+
// byte-for-byte (extracted with sed, not retyped).
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const registry_core_1 = require("./registry-core");
|
|
7
|
+
// ---------------------------------------------------------------------
|
|
8
|
+
// CLI bindings wired at THIS milestone (version, list, status,
|
|
9
|
+
// sandbox.list). `version` is cli-only per SPEC/mcp.md's declared
|
|
10
|
+
// one-surface list (`help` is handled directly by cli/entry.ts's
|
|
11
|
+
// top-level flag redirect, same as milestone 1 — it is not itself a
|
|
12
|
+
// dispatchable command row); `list`/`status`/`sandbox.list` reuse the mcp
|
|
13
|
+
// row's capability id and get a cli binding layered on top. Every handler
|
|
14
|
+
// below returns a `CliHandlerResult`; core/ never touches process.stdout
|
|
15
|
+
// or process.exitCode directly (see docs/rebuild/PLAN.md's core/shell split) —
|
|
16
|
+
// cli/dispatch.ts's generic executor performs the actual write.
|
|
17
|
+
// ---------------------------------------------------------------------
|
|
18
|
+
const version_1 = require("../../core/version");
|
|
19
|
+
const workflow_app_loader_1 = require("../../shell/workflow-app-loader");
|
|
20
|
+
const help_1 = require("../../core/format/help");
|
|
21
|
+
(0, registry_core_1.addCliOnlyCapability)("version", "Print the current cool-workflow version.", {
|
|
22
|
+
path: ["version"],
|
|
23
|
+
jsonMode: "default",
|
|
24
|
+
handler: () => ({ text: `${version_1.CURRENT_COOL_WORKFLOW_VERSION}\n` }),
|
|
25
|
+
}, "version is a local, no-run-state print; the old build never gave it an MCP peer.");
|
|
26
|
+
/** `cw search <keyword>` — filters the SAME real app discovery `cw list`
|
|
27
|
+
* shows, by id/title/summary (byte-behavior port of cli/dispatch.ts's
|
|
28
|
+
* milestone-1 carry-over `search` arm, moved here so the dispatchLegacy
|
|
29
|
+
* switch shrinks per its file header's rule). `hiddenFromHelp` keeps it
|
|
30
|
+
* out of the per-verb help listing exactly as before (it never had one —
|
|
31
|
+
* `search` only ever appeared in formatHelp's hard-coded "More commands"
|
|
32
|
+
* index line, which this row does not touch), so `cw help search` keeps
|
|
33
|
+
* its existing "Unknown command: search" text. */
|
|
34
|
+
(0, registry_core_1.addCliOnlyCapability)("search", "Search bundled workflows by id/title/summary keyword.", {
|
|
35
|
+
path: ["search"],
|
|
36
|
+
jsonMode: "flag",
|
|
37
|
+
hiddenFromHelp: true,
|
|
38
|
+
handler: (args) => {
|
|
39
|
+
const keyword = args.positionals.join(" ");
|
|
40
|
+
if (!keyword.trim()) {
|
|
41
|
+
throw new Error('Missing search keyword.\n Tip: cw search architecture to find workflows about architecture.');
|
|
42
|
+
}
|
|
43
|
+
const lower = keyword.toLowerCase();
|
|
44
|
+
const results = (0, workflow_app_loader_1.listWorkflowApps)()
|
|
45
|
+
.filter((a) => String(a.title).toLowerCase().includes(lower) ||
|
|
46
|
+
String(a.summary).toLowerCase().includes(lower) ||
|
|
47
|
+
String(a.id).toLowerCase().includes(lower))
|
|
48
|
+
.map((a) => ({ id: String(a.id), title: String(a.title), summary: String(a.summary) }));
|
|
49
|
+
return { json: results, text: (0, help_1.formatSearchResults)(keyword, results) };
|
|
50
|
+
},
|
|
51
|
+
}, "CLI-only discovery helper over the same real app data cw list shows; no MCP client needs a free-text search tool alongside cw_list's structured output.");
|
|
52
|
+
(0, registry_core_1.attachCliBinding)("list", {
|
|
53
|
+
path: ["list"],
|
|
54
|
+
jsonMode: "default",
|
|
55
|
+
handler: () => ({ json: (0, registry_core_1.listBundledWorkflows)() }),
|
|
56
|
+
});
|
|
57
|
+
(0, registry_core_1.attachCliBinding)("status", {
|
|
58
|
+
path: ["status"],
|
|
59
|
+
jsonMode: "flag",
|
|
60
|
+
handler: (args) => ({ json: (0, registry_core_1.statusPayload)(args.positionals[0]) }),
|
|
61
|
+
});
|
|
62
|
+
(0, registry_core_1.attachCliBinding)("sandbox.list", {
|
|
63
|
+
path: ["sandbox", "list"],
|
|
64
|
+
jsonMode: "default",
|
|
65
|
+
handler: () => ({ json: (0, registry_core_1.listBundledSandboxProfiles)() }),
|
|
66
|
+
});
|