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
package/dist/mcp/dispatch.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// mcp/dispatch.ts — the generic MCP tool-call reader over
|
|
3
3
|
// core/capability-table.ts.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 2 (
|
|
5
|
+
// MILESTONE 2 (docs/rebuild/PLAN.md build order, step 2). Replaces the old build's
|
|
6
6
|
// 196-arm `callTool` switch (src/mcp/tool-call.ts) with a data-driven
|
|
7
7
|
// lookup: resolve the row by tool name, check its declared required-arg
|
|
8
8
|
// groups (SPEC/mcp.md's `McpBinding.requiredArgs`), re-base `cwd` if
|
package/dist/mcp/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// mcp/server.ts — the stdio JSON-RPC loop: initialize / tools/list /
|
|
3
3
|
// tools/call.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 2 (
|
|
5
|
+
// MILESTONE 2 (docs/rebuild/PLAN.md build order, step 2). Byte-exact port of the
|
|
6
6
|
// framing rules in SPEC/mcp.md's "JSON-RPC methods" / "Exact outputs" /
|
|
7
7
|
// "Invariants and error behavior" #9 / "Edge cases" sections:
|
|
8
8
|
// - transport: stdin/stdout, ONE JSON object per line, no
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// shell/agent-config.ts — agent delegation config: env/file/flag resolution
|
|
3
3
|
// order, CW_HOME resolution, builtin:<name> expansion, PATH auto-detect.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 5 (
|
|
5
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
6
6
|
// plugins/cool-workflow/src/agent-config.ts.
|
|
7
7
|
//
|
|
8
8
|
// Resolution order per field: flags > env > a durable
|
package/dist/shell/audit-cli.js
CHANGED
|
@@ -44,6 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.auditVerifyCli = auditVerifyCli;
|
|
47
|
+
exports.auditHeadCli = auditHeadCli;
|
|
47
48
|
exports.auditSummaryCli = auditSummaryCli;
|
|
48
49
|
exports.auditMultiAgentCli = auditMultiAgentCli;
|
|
49
50
|
exports.auditPolicyCli = auditPolicyCli;
|
|
@@ -65,12 +66,36 @@ const sandbox_profile_1 = require("./sandbox-profile");
|
|
|
65
66
|
function invocationCwd(args) {
|
|
66
67
|
return typeof args.cwd === "string" && args.cwd.trim() ? path.resolve(args.cwd) : process.cwd();
|
|
67
68
|
}
|
|
69
|
+
/** Parse the optional truncation anchor off the CLI options / MCP args
|
|
70
|
+
* (`--expect-head <hash>` / `--expect-count <n>`; MCP: expectHead /
|
|
71
|
+
* expectCount). Fail-closed on a malformed count — a flag given without
|
|
72
|
+
* a usable value must never silently weaken the check it asked for. */
|
|
73
|
+
function anchorOption(args) {
|
|
74
|
+
const headRaw = args["expect-head"] ?? args.expectHead;
|
|
75
|
+
const countRaw = args["expect-count"] ?? args.expectCount;
|
|
76
|
+
const expectHead = optionalString(headRaw);
|
|
77
|
+
if (headRaw !== undefined && headRaw !== null && expectHead === undefined) {
|
|
78
|
+
throw new Error("audit verify: --expect-head requires a hash value");
|
|
79
|
+
}
|
|
80
|
+
let expectCount;
|
|
81
|
+
if (countRaw !== undefined && countRaw !== null) {
|
|
82
|
+
const parsed = Number(countRaw);
|
|
83
|
+
if (countRaw === true || !Number.isInteger(parsed) || parsed < 0) {
|
|
84
|
+
throw new Error("audit verify: --expect-count requires a non-negative integer");
|
|
85
|
+
}
|
|
86
|
+
expectCount = parsed;
|
|
87
|
+
}
|
|
88
|
+
if (expectHead === undefined && expectCount === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
return { expectHead, expectCount };
|
|
91
|
+
}
|
|
68
92
|
function auditVerifyCli(runId, args) {
|
|
69
93
|
if (!runId)
|
|
70
94
|
throw new Error("audit verify requires a run id (cw audit verify <run-id>)");
|
|
95
|
+
const anchor = anchorOption(args);
|
|
71
96
|
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
|
|
72
|
-
const v = (0, trust_audit_1.verifyTrustAudit)(run);
|
|
73
|
-
|
|
97
|
+
const v = (0, trust_audit_1.verifyTrustAudit)(run, anchor);
|
|
98
|
+
const result = {
|
|
74
99
|
schemaVersion: 1,
|
|
75
100
|
runId: run.id,
|
|
76
101
|
present: v.present,
|
|
@@ -81,6 +106,25 @@ function auditVerifyCli(runId, args) {
|
|
|
81
106
|
corruptLines: v.corruptLines,
|
|
82
107
|
failedChecks: v.checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code })),
|
|
83
108
|
};
|
|
109
|
+
if (anchor) {
|
|
110
|
+
result.anchor = {
|
|
111
|
+
...(anchor.expectHead !== undefined ? { expectHead: anchor.expectHead } : {}),
|
|
112
|
+
...(anchor.expectCount !== undefined ? { expectCount: anchor.expectCount } : {}),
|
|
113
|
+
satisfied: !v.checks.some((c) => c.code === "trust-audit-truncated"),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/** `cw audit head <run>` — the chain head anchor (read-only projection).
|
|
119
|
+
* Capture it after a run (or before publishing/exporting); later,
|
|
120
|
+
* `cw audit verify <run> --expect-head <hash> --expect-count <n>`
|
|
121
|
+
* re-proves the log was not shortened since the capture. */
|
|
122
|
+
function auditHeadCli(runId, args) {
|
|
123
|
+
if (!runId)
|
|
124
|
+
throw new Error("audit head requires a run id (cw audit head <run-id>)");
|
|
125
|
+
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
|
|
126
|
+
const head = (0, trust_audit_1.trustAuditHead)(run);
|
|
127
|
+
return { schemaVersion: 1, runId: run.id, eventCount: head.eventCount, headHash: head.headHash };
|
|
84
128
|
}
|
|
85
129
|
/** MILESTONE 11 (reporting/observability, workbench audit panels) —
|
|
86
130
|
* `cw audit summary`/`audit multi-agent`/`audit policy`/`audit judge`.
|
|
@@ -46,6 +46,7 @@ exports.commitSummaryCli = commitSummaryCli;
|
|
|
46
46
|
exports.formatCommitSummaryText = formatCommitSummaryText;
|
|
47
47
|
const path = __importStar(require("node:path"));
|
|
48
48
|
const run_store_1 = require("./run-store");
|
|
49
|
+
const collate_1 = require("../core/util/collate");
|
|
49
50
|
function formatCommitRow(commit) {
|
|
50
51
|
return {
|
|
51
52
|
id: commit.id,
|
|
@@ -63,7 +64,7 @@ function formatCommitRow(commit) {
|
|
|
63
64
|
/** Byte-exact port of the old build's `summarizeOperatorCommits`
|
|
64
65
|
* (src/operator-ux.ts:339-349). */
|
|
65
66
|
function summarizeOperatorCommits(run) {
|
|
66
|
-
const commits = [...(run.commits || [])].sort((left, right) => left.createdAt
|
|
67
|
+
const commits = [...(run.commits || [])].sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
67
68
|
const rows = commits.map(formatCommitRow);
|
|
68
69
|
return {
|
|
69
70
|
total: rows.length,
|
package/dist/shell/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// shell/doctor.ts — `cw doctor` environment diagnostics, in the spirit of
|
|
3
3
|
// `brew doctor`.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 5 (
|
|
5
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
6
6
|
// plugins/cool-workflow/src/doctor.ts's checks this milestone needs
|
|
7
7
|
// (node/agent/agent-binary/git/home-registry/repo-state). The `--onramp`
|
|
8
8
|
// section (buildDoctorOnramp) is later-milestone (reporting) territory and
|
|
@@ -167,7 +167,20 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
-
// 3.
|
|
170
|
+
// 3. Sandbox enforcement boundary — a fixed architectural fact (see
|
|
171
|
+
// docs/sandbox-profiles.7.md), not a host-specific problem to fix, so this
|
|
172
|
+
// is "ok" (informational), never "warn": CW validates sandbox policy and
|
|
173
|
+
// gates worker-output acceptance, but OS-level read/write/execute/network/
|
|
174
|
+
// env isolation is the execution host's responsibility, which this doctor
|
|
175
|
+
// run cannot itself verify. An "ok" check carries no `fix` line by
|
|
176
|
+
// convention (see the node/git checks above), so the pointer to the docs
|
|
177
|
+
// lives in `detail` instead.
|
|
178
|
+
checks.push({
|
|
179
|
+
name: "sandbox-enforceability",
|
|
180
|
+
status: "ok",
|
|
181
|
+
detail: "CW sandbox profiles validate policy and gate worker-output acceptance; OS-level read/write/execute/network/env isolation is the execution host's responsibility (see docs/sandbox-profiles.7.md's ENFORCEMENT section) — this doctor run cannot verify that host enforcement.",
|
|
182
|
+
});
|
|
183
|
+
// 4. git — only needed for commit provenance; a warn, not a hard fail.
|
|
171
184
|
const git = (0, node_child_process_1.spawnSync)("git", ["--version"], { encoding: "utf8", timeout: 5000 });
|
|
172
185
|
checks.push(!git.error && git.status === 0
|
|
173
186
|
? { name: "git", status: "ok", detail: `${String(git.stdout || "git").trim()}.` }
|
|
@@ -177,7 +190,7 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
|
|
|
177
190
|
detail: "git is not available — commit provenance (git HEAD) is recorded as absent.",
|
|
178
191
|
fix: "Install git (e.g. `brew install git`) if you want commit provenance.",
|
|
179
192
|
});
|
|
180
|
-
//
|
|
193
|
+
// 5. Home registry — the cross-repo run index lives here; must be writable.
|
|
181
194
|
const home = env.CW_HOME && String(env.CW_HOME).trim() ? path.resolve(String(env.CW_HOME)) : path.join(os.homedir(), ".local", "state", "cool-workflow");
|
|
182
195
|
checks.push(dirWritable(home)
|
|
183
196
|
? { name: "home-registry", status: "ok", detail: `Home registry location is writable (${home}).` }
|
|
@@ -187,7 +200,7 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
|
|
|
187
200
|
detail: `Home registry location is not writable: ${home}`,
|
|
188
201
|
fix: "Set $CW_HOME to a writable directory, or fix the permissions.",
|
|
189
202
|
});
|
|
190
|
-
//
|
|
203
|
+
// 6. Working-dir state — per-repo runs land under <cwd>/.cw.
|
|
191
204
|
const cwState = path.join(path.resolve(cwd), ".cw");
|
|
192
205
|
checks.push(dirWritable(cwState)
|
|
193
206
|
? { name: "repo-state", status: "ok", detail: `Run state location is writable (${cwState}).` }
|
package/dist/shell/drive.js
CHANGED
|
@@ -3,19 +3,14 @@
|
|
|
3
3
|
// once per step and performs the spawn/commit/cache-write IO the
|
|
4
4
|
// decision names.
|
|
5
5
|
//
|
|
6
|
-
// MILESTONE 6+7 (combined; see
|
|
6
|
+
// MILESTONE 6+7 (combined; see docs/rebuild/PLAN.md Open risk 9/10 — the LARGEST
|
|
7
7
|
// milestone). Byte-exact port of the old build's src/drive.ts's
|
|
8
8
|
// imperative shell around the pure decision core now in
|
|
9
9
|
// core/pipeline/drive-decide.ts. Sub-workflow nesting and `--incremental`
|
|
10
|
-
// are ported; the concurrent-round driver (driveConcurrentRound)
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// authored as its own future conformance case per Open risk 5) — the
|
|
15
|
-
// `mode:"parallel"` architecture-review phases still complete correctly
|
|
16
|
-
// through the serial per-task loop, just without the wall-clock-parallel
|
|
17
|
-
// spawn optimization; this is flagged here rather than silently ported as
|
|
18
|
-
// if fully equivalent.
|
|
10
|
+
// are ported; the concurrent-round driver (driveConcurrentRound, below)
|
|
11
|
+
// dispatches and settles a whole round's tasks in one batch (see
|
|
12
|
+
// `--concurrency`/`roundWidth`), pinned by
|
|
13
|
+
// v2/conformance/cases/pipeline-concurrent-round.case.js.
|
|
19
14
|
//
|
|
20
15
|
// Evidence: SPEC/pipeline-run.md "Drive loop — src/drive.ts".
|
|
21
16
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -76,8 +71,8 @@ const trust_audit_1 = require("./trust-audit");
|
|
|
76
71
|
const agent_config_1 = require("./agent-config");
|
|
77
72
|
const registry_1 = require("./execution-backend/registry");
|
|
78
73
|
const agent_1 = require("./execution-backend/agent");
|
|
79
|
-
const local_1 = require("./execution-backend/local");
|
|
80
74
|
const hash_1 = require("../core/hash");
|
|
75
|
+
const collate_1 = require("../core/util/collate");
|
|
81
76
|
const pipeline_1 = require("./pipeline");
|
|
82
77
|
const reporter_1 = require("./reporter");
|
|
83
78
|
const fs_atomic_1 = require("./fs-atomic");
|
|
@@ -192,7 +187,10 @@ function previousPhaseResultsDigest(run, task) {
|
|
|
192
187
|
return undefined;
|
|
193
188
|
const previousTaskIds = new Set(run.phases.slice(0, phaseIndex).flatMap((p) => p.taskIds));
|
|
194
189
|
const records = [];
|
|
195
|
-
|
|
190
|
+
// stableCompare (not a bare localeCompare): this order feeds the sha256
|
|
191
|
+
// digest below, which feeds the incremental cache key — a host-locale-
|
|
192
|
+
// dependent order would silently move the cache key across machines.
|
|
193
|
+
for (const candidate of run.tasks.filter((t) => previousTaskIds.has(t.id)).sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
|
|
196
194
|
if (candidate.status !== "completed" || !candidate.resultPath || !fs.existsSync(candidate.resultPath)) {
|
|
197
195
|
records.push(undefined);
|
|
198
196
|
continue;
|
|
@@ -304,7 +302,24 @@ function processSelectedTask(ctx, selectedId, preparedOutcome, deferPersist = fa
|
|
|
304
302
|
emitProgress(`↺ ${selected.label || selected.id} (${selected.phase}) — accepting cached result`);
|
|
305
303
|
try {
|
|
306
304
|
fs.writeFileSync(manifest.resultPath, fs.readFileSync(cachePath, "utf8"), "utf8");
|
|
305
|
+
// Not gated by requireAttestedTelemetry here: the underlying result was
|
|
306
|
+
// already gated (attested or explicitly overridden) at its FIRST
|
|
307
|
+
// acceptance, before it was cached. Re-blocking a cache hit would only
|
|
308
|
+
// punish the operator for their own earlier, already-audited accept.
|
|
309
|
+
// Still made visible, not silent: when the operator requires attested
|
|
310
|
+
// telemetry, record that this particular accept came from the cache
|
|
311
|
+
// rather than a freshly re-verified hop.
|
|
307
312
|
(0, worker_isolation_1.recordWorkerOutput)(run, workerId, manifest.resultPath);
|
|
313
|
+
if (ctx.config.requireAttestedTelemetry) {
|
|
314
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
315
|
+
kind: "telemetry.cache-accept",
|
|
316
|
+
decision: "recorded",
|
|
317
|
+
source: "cw-validated",
|
|
318
|
+
workerId,
|
|
319
|
+
taskId: selected.id,
|
|
320
|
+
metadata: { reason: "result-cache hit; original attestation gate applied at first acceptance, not re-verified here" },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
308
323
|
// Advance the run lifecycle stage on accept, as the old build's
|
|
309
324
|
// recordWorkerOutput wrapper did (run.loopStage = "observe").
|
|
310
325
|
run.loopStage = "observe";
|
|
@@ -384,6 +399,25 @@ function processSelectedTask(ctx, selectedId, preparedOutcome, deferPersist = fa
|
|
|
384
399
|
catch (error) {
|
|
385
400
|
return handleHop(ctx, selected, workerId, `result.md rejected: ${errMessage(error)}`, deferPersist, deferPersist ? run : undefined);
|
|
386
401
|
}
|
|
402
|
+
// Trust visibility (purely additive, no execution-behavior change): when
|
|
403
|
+
// the agent backend forwarded provider-namespace env vars (CW_/ANTHROPIC_/
|
|
404
|
+
// etc.) to the delegated child, record the NAMES (never values) as their
|
|
405
|
+
// own tamper-evident audit event, mirroring the worker.sub-workflow event
|
|
406
|
+
// pattern above. Only ever populated by execution-backend/agent.ts's
|
|
407
|
+
// recordedAgentHandle() — a no-op for every other backend.
|
|
408
|
+
const forwardedEnvVars = handle?.metadata?.forwardedEnvVars || [];
|
|
409
|
+
if (forwardedEnvVars.length) {
|
|
410
|
+
(0, trust_audit_1.recordTrustAuditEvent)(run, {
|
|
411
|
+
kind: "worker.agent-env",
|
|
412
|
+
decision: "delegated",
|
|
413
|
+
source: "runtime-derived",
|
|
414
|
+
workerId,
|
|
415
|
+
taskId: selected.id,
|
|
416
|
+
nodeId: selected.resultNodeId,
|
|
417
|
+
envVars: forwardedEnvVars,
|
|
418
|
+
metadata: { reason: "provider-namespace env vars forwarded from host process env to the delegated agent child" },
|
|
419
|
+
});
|
|
420
|
+
}
|
|
387
421
|
if (cachePath && fs.existsSync(manifest.resultPath)) {
|
|
388
422
|
writeResultCache(cachePath, fs.readFileSync(manifest.resultPath, "utf8"));
|
|
389
423
|
}
|
|
@@ -664,13 +698,7 @@ function prepareConcurrentOutcomes(ctx, batch) {
|
|
|
664
698
|
if (job) {
|
|
665
699
|
const sandboxPolicy = manifest.sandboxPolicy;
|
|
666
700
|
if (sandboxPolicy) {
|
|
667
|
-
|
|
668
|
-
for (const key of Object.keys(process.env)) {
|
|
669
|
-
if (/^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i.test(key)) {
|
|
670
|
-
filteredEnv[key] = process.env[key];
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
job.env = filteredEnv;
|
|
701
|
+
job.env = (0, agent_1.buildAgentChildEnv)(sandboxPolicy).env;
|
|
674
702
|
}
|
|
675
703
|
jobs.push(job);
|
|
676
704
|
jobTaskIds.push(taskId);
|
|
@@ -29,6 +29,7 @@ const fs_atomic_1 = require("./fs-atomic");
|
|
|
29
29
|
const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
|
|
30
30
|
const trust_audit_1 = require("./trust-audit");
|
|
31
31
|
const trust_policy_1 = require("../core/multi-agent/trust-policy");
|
|
32
|
+
const collate_1 = require("../core/util/collate");
|
|
32
33
|
exports.EVIDENCE_REASONING_SCHEMA_VERSION = 1;
|
|
33
34
|
function candidatesOf(run) {
|
|
34
35
|
return (run.candidates || []);
|
|
@@ -56,7 +57,7 @@ function buildEvidenceReasoningReport(run, options = {}) {
|
|
|
56
57
|
const counterfactuals = deriveCounterfactuals(run, scores);
|
|
57
58
|
const chains = operator.evidence
|
|
58
59
|
.map((evidence) => buildChain(run, evidence, { scores, auditEvents, counterfactuals }))
|
|
59
|
-
.sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || left.id
|
|
60
|
+
.sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
60
61
|
const totals = summarizeTotals(chains);
|
|
61
62
|
const currentFingerprint = fingerprintChains(chains);
|
|
62
63
|
const persisted = options.index;
|
|
@@ -340,7 +341,7 @@ function refreshEvidenceReasoning(run) {
|
|
|
340
341
|
generatedAt: new Date().toISOString(),
|
|
341
342
|
sourceFingerprint: report.sourceFingerprint,
|
|
342
343
|
totals: report.totals,
|
|
343
|
-
entries: entries.sort((a, b) => a.id
|
|
344
|
+
entries: entries.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
|
|
344
345
|
paths: { reasoningDir: dir, indexPath, reportPath },
|
|
345
346
|
nextAction: `node scripts/cw.js multi-agent reasoning ${run.id}`,
|
|
346
347
|
};
|
|
@@ -601,5 +602,5 @@ function unique(values) {
|
|
|
601
602
|
return Array.from(new Set(values.filter(Boolean))).sort();
|
|
602
603
|
}
|
|
603
604
|
function byRef(a, b) {
|
|
604
|
-
return a.ref
|
|
605
|
+
return (0, collate_1.stableCompare)(a.ref, b.ref);
|
|
605
606
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// shell/execution-backend/agent.ts — agent-delegation pure helpers +
|
|
3
3
|
// concurrent batch fulfillment.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 5 (
|
|
5
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
6
6
|
// plugins/cool-workflow/src/execution-backend/agent.ts. This module holds
|
|
7
7
|
// the PURE data-transform helpers (invocation resolution, arg substitution,
|
|
8
8
|
// secret redaction, report parsing) plus the batch delegate-child spawn.
|
|
@@ -49,6 +49,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
49
49
|
};
|
|
50
50
|
})();
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.buildAgentChildEnv = buildAgentChildEnv;
|
|
52
53
|
exports.resolveAgentInvocation = resolveAgentInvocation;
|
|
53
54
|
exports.stripSecretArgs = stripSecretArgs;
|
|
54
55
|
exports.parseAgentReport = parseAgentReport;
|
|
@@ -70,6 +71,35 @@ const envelopes_1 = require("./envelopes");
|
|
|
70
71
|
function messageOf(error) {
|
|
71
72
|
return error instanceof Error ? error.message : String(error);
|
|
72
73
|
}
|
|
74
|
+
const AGENT_PROVIDER_KEY_ENV_RE = /^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i;
|
|
75
|
+
/** Build the env for a REAL spawned agent child under a sandbox policy: the
|
|
76
|
+
* policy's own PATH+HOME+expose/deny base (buildChildEnv), plus provider-key
|
|
77
|
+
* vars (the CW_, ANTHROPIC_, etc. prefixes), plus USER. USER is not a provider key, but a
|
|
78
|
+
* real vendor CLI (claude, at least) reads it to resolve its own OS-level
|
|
79
|
+
* login/keychain credential in headless mode -- buildChildEnv alone keeps
|
|
80
|
+
* only PATH+HOME under the readonly policy, so a spawned agent that IS
|
|
81
|
+
* logged in interactively reported "Not logged in" here. Found live: PATH+
|
|
82
|
+
* HOME alone reproduces it, adding USER back fixes it, LOGNAME alone does
|
|
83
|
+
* not. This is the ONE place that builds this env -- the single-request
|
|
84
|
+
* (runAgentProcess) and concurrent-batch (shell/drive.ts) spawn paths both
|
|
85
|
+
* call it, so the allowlist cannot drift into a second silent copy again.
|
|
86
|
+
* Returns the forwarded var NAMES too (never values) for the
|
|
87
|
+
* worker.agent-env trust-audit event. */
|
|
88
|
+
function buildAgentChildEnv(policy, baseEnv = process.env) {
|
|
89
|
+
const env = (0, local_1.buildChildEnv)(policy, baseEnv);
|
|
90
|
+
const forwarded = [];
|
|
91
|
+
for (const key of Object.keys(baseEnv)) {
|
|
92
|
+
if (AGENT_PROVIDER_KEY_ENV_RE.test(key)) {
|
|
93
|
+
env[key] = baseEnv[key];
|
|
94
|
+
forwarded.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (baseEnv.USER !== undefined && env.USER === undefined) {
|
|
98
|
+
env.USER = baseEnv.USER;
|
|
99
|
+
forwarded.push("USER");
|
|
100
|
+
}
|
|
101
|
+
return { env, forwarded };
|
|
102
|
+
}
|
|
73
103
|
/** Resolve the agent invocation from the request delegation > env.
|
|
74
104
|
* Vendor-neutral; the durable file config is folded in by the drive layer
|
|
75
105
|
* before this point (see shell/agent-config.ts's resolveAgentConfig). */
|
|
@@ -203,7 +233,7 @@ function substituteAgentArg(arg, subst) {
|
|
|
203
233
|
/** Build the recorded process handle for the envelope — secret-stripped +
|
|
204
234
|
* the agent-reported model. Same SHAPE that lands in provenance, never in
|
|
205
235
|
* evidence. */
|
|
206
|
-
function recordedAgentHandle(binary, endpoint, recordedArgs, model, reportedModel, reportedUsage, usageSignature) {
|
|
236
|
+
function recordedAgentHandle(binary, endpoint, recordedArgs, model, reportedModel, reportedUsage, usageSignature, forwardedEnvVars) {
|
|
207
237
|
const ref = binary ? [binary, ...recordedArgs].join(" ") : endpoint || "";
|
|
208
238
|
return {
|
|
209
239
|
kind: "process",
|
|
@@ -217,6 +247,7 @@ function recordedAgentHandle(binary, endpoint, recordedArgs, model, reportedMode
|
|
|
217
247
|
reportedModel,
|
|
218
248
|
...(reportedUsage ? { reportedUsage } : {}),
|
|
219
249
|
...(usageSignature ? { usageSignature } : {}),
|
|
250
|
+
...(forwardedEnvVars && forwardedEnvVars.length ? { forwardedEnvVars } : {}),
|
|
220
251
|
},
|
|
221
252
|
};
|
|
222
253
|
}
|
|
@@ -365,17 +396,18 @@ function runAgentProcess(descriptor, policy, request, label, attestation) {
|
|
|
365
396
|
const realArgs = resolved.rawArgs.map((arg) => substituteAgentArg(arg, subst));
|
|
366
397
|
const recordedArgs = stripSecretArgs(realArgs);
|
|
367
398
|
let outcome;
|
|
399
|
+
// Names only, never values — for the worker.agent-env trust-audit event
|
|
400
|
+
// below. Stays empty for a preparedAgentOutcome (a batch-delegated child
|
|
401
|
+
// that already ran elsewhere; this code path forwards nothing itself).
|
|
402
|
+
let forwardedEnvVars = [];
|
|
368
403
|
if (request.preparedAgentOutcome) {
|
|
369
404
|
outcome = request.preparedAgentOutcome;
|
|
370
405
|
}
|
|
371
406
|
else {
|
|
372
407
|
const streamStderr = shouldStreamAgentStderr(process.env, Boolean(process.stderr.isTTY));
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
childEnv[key] = process.env[key];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
408
|
+
const built = buildAgentChildEnv(policy);
|
|
409
|
+
const childEnv = built.env;
|
|
410
|
+
forwardedEnvVars = built.forwarded;
|
|
379
411
|
const child = (0, node_child_process_1.spawnSync)(resolved.binary, realArgs, {
|
|
380
412
|
cwd: request.cwd,
|
|
381
413
|
env: childEnv,
|
|
@@ -392,7 +424,7 @@ function runAgentProcess(descriptor, policy, request, label, attestation) {
|
|
|
392
424
|
};
|
|
393
425
|
}
|
|
394
426
|
if (outcome.spawnError) {
|
|
395
|
-
const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, "unreported");
|
|
427
|
+
const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, "unreported", undefined, undefined, forwardedEnvVars);
|
|
396
428
|
return (0, envelopes_1.refusedEnvelope)(descriptor, policy, label, "delegation-failed", `agent process failed to spawn: ${outcome.spawnError}`, {
|
|
397
429
|
...attestation,
|
|
398
430
|
handle: handleOut,
|
|
@@ -402,7 +434,7 @@ function runAgentProcess(descriptor, policy, request, label, attestation) {
|
|
|
402
434
|
const stdout = outcome.stdout;
|
|
403
435
|
const report = parseAgentReport(stdout);
|
|
404
436
|
const reportedModel = report.model && report.model.trim() ? report.model.trim() : "unreported";
|
|
405
|
-
const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, reportedModel, report.usage, report.usageSignature);
|
|
437
|
+
const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, reportedModel, report.usage, report.usageSignature, forwardedEnvVars);
|
|
406
438
|
if (exitCode === null) {
|
|
407
439
|
return (0, envelopes_1.refusedEnvelope)(descriptor, policy, label, "delegation-failed", `agent process returned no exit code (timed out or killed)`, { ...attestation, handle: handleOut });
|
|
408
440
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// shell/execution-backend/ci.ts — the ci delegating driver.
|
|
3
3
|
//
|
|
4
|
-
// MILESTONE 5 (
|
|
4
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). The ci driver shares its
|
|
5
5
|
// entire HTTP-delegation body with the remote driver (byte-exact port of
|
|
6
6
|
// plugins/cool-workflow/src/execution-backend.ts, which also shares
|
|
7
7
|
// `runHttpDelegation` between the two); this file just re-exports the ci
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// shell/execution-backend/container.ts — the container delegating driver.
|
|
3
3
|
//
|
|
4
|
-
// MILESTONE 5 (
|
|
4
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
5
5
|
// plugins/cool-workflow/src/execution-backend.ts's container handle builder
|
|
6
6
|
// (:1068-1075) and `runContainer` (:720-792).
|
|
7
7
|
//
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// every delegating driver (container/remote/ci/agent) plus the registry's
|
|
4
4
|
// own refusal path.
|
|
5
5
|
//
|
|
6
|
-
// MILESTONE 5 (
|
|
6
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the
|
|
7
7
|
// `delegatedEnvelope` (src/execution-backend.ts:680-713) and
|
|
8
8
|
// `refusedEnvelope` (:1095-1126) helpers, factored into one shared module
|
|
9
9
|
// since every delegating driver builds the identical shape (byte-stable
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// shell/execution-backend/local.ts — the local-execution driver body
|
|
3
3
|
// (executeLocal) shared by the node/bun/shell built-in drivers.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 5 (
|
|
5
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
6
6
|
// plugins/cool-workflow/src/execution-backend.ts's `executeLocal` (lines
|
|
7
7
|
// 547-626) plus the leaf helpers it needs (sha256/hasExecutable/messageOf).
|
|
8
8
|
//
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// shell/execution-backend/probes.ts — per-backend readiness probe bodies.
|
|
3
3
|
//
|
|
4
|
-
// MILESTONE 5 (
|
|
4
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
5
5
|
// plugins/cool-workflow/src/execution-backend/probes.ts. Each probe is a
|
|
6
6
|
// pure free function of the host (PATH + configured env); the registry's
|
|
7
7
|
// `probeBackend` wraps these with the descriptor-derived envelope.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// registerBackend, resolveBackendSelection, attestSandbox, probeBackend,
|
|
4
4
|
// runBackend, and the CLI-facing inspection payloads.
|
|
5
5
|
//
|
|
6
|
-
// MILESTONE 5 (
|
|
6
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
7
7
|
// plugins/cool-workflow/src/execution-backend.ts (the parts not carved into
|
|
8
8
|
// local.ts/container.ts/remote.ts/ci.ts/agent.ts/probes.ts).
|
|
9
9
|
//
|
|
@@ -33,6 +33,7 @@ const container_1 = require("./container");
|
|
|
33
33
|
const remote_1 = require("./remote");
|
|
34
34
|
const ci_1 = require("./ci");
|
|
35
35
|
const agent_1 = require("./agent");
|
|
36
|
+
const collate_1 = require("../../core/util/collate");
|
|
36
37
|
exports.EXECUTION_BACKEND_SCHEMA_VERSION = 1;
|
|
37
38
|
exports.DEFAULT_BACKEND_ID = "node";
|
|
38
39
|
exports.SANDBOX_DIMENSIONS = ["read", "write", "command", "network", "env"];
|
|
@@ -159,7 +160,7 @@ function registeredDrivers() {
|
|
|
159
160
|
function listBackendDescriptors() {
|
|
160
161
|
return registeredDrivers()
|
|
161
162
|
.map((driver) => specDescriptor(driver.spec))
|
|
162
|
-
.sort((left, right) => left.id
|
|
163
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
|
|
163
164
|
}
|
|
164
165
|
function backendIds() {
|
|
165
166
|
return registeredDrivers()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// shell/execution-backend/remote.ts — remote/ci delegating drivers (shared
|
|
3
3
|
// HTTP-delegation body) + handle builders.
|
|
4
4
|
//
|
|
5
|
-
// MILESTONE 5 (
|
|
5
|
+
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
|
|
6
6
|
// plugins/cool-workflow/src/execution-backend.ts's `runHttpDelegation`
|
|
7
7
|
// (:821-876), `remoteHandle` (:1077-1084), `ciHandle` (:1086-1093), and
|
|
8
8
|
// `delegateChildScript` (:801-811).
|
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// shell/execution-backend/types.ts — plain data shapes for the driver layer.
|
|
3
|
-
//
|
|
4
|
-
// MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of the shapes
|
|
5
|
-
// in the old build's src/types/execution-backend.ts and the sandbox slice of
|
|
6
|
-
// src/types/sandbox.ts that this subsystem needs. Types only — no logic — so
|
|
7
|
-
// this file is safe to import from both shell/ (impure) and any future core/
|
|
8
|
-
// caller without violating the core/shell purity split.
|
|
9
|
-
//
|
|
10
|
-
// Evidence: SPEC/execution-backend.md.
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/shell/fs-atomic.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// `writeJson` here, so the write bytes stay exactly one place.
|
|
6
6
|
//
|
|
7
7
|
// Evidence: SPEC/state-core.md "Write ordering and atomic rules", "Lock
|
|
8
|
-
// protocol";
|
|
8
|
+
// protocol"; docs/rebuild/PLAN.md byte-compat items 1 and 6.
|
|
9
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
11
11
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -198,14 +198,25 @@ function durableAppendFileSync(file, data) {
|
|
|
198
198
|
// only when still owned by this pid.
|
|
199
199
|
// ---------------------------------------------------------------------------
|
|
200
200
|
const FILE_LOCK_STALE_MS = 30_000;
|
|
201
|
+
// Lock paths this process holds right now. A nested withFileLock on the
|
|
202
|
+
// SAME target runs its fn directly (re-entrant) instead of waiting on its
|
|
203
|
+
// own lock file until the 240 tries run out — that lets a whole
|
|
204
|
+
// load -> change -> save cycle hold one lock while the save path inside
|
|
205
|
+
// it keeps its own withFileLock call unchanged.
|
|
206
|
+
const HELD_LOCKS = new Set();
|
|
201
207
|
function sleepSync(ms) {
|
|
202
208
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
203
209
|
}
|
|
204
210
|
/** Run `fn` while holding an advisory lock for `targetPath`; always released
|
|
205
211
|
* (unless the lock was stolen mid-operation, in which case releasing would
|
|
206
|
-
* corrupt the thief's critical section, so it is deliberately NOT released).
|
|
212
|
+
* corrupt the thief's critical section, so it is deliberately NOT released).
|
|
213
|
+
* Re-entrant inside one process: a nested call on the same target runs
|
|
214
|
+
* `fn` under the already-held lock. */
|
|
207
215
|
function withFileLock(targetPath, fn) {
|
|
208
216
|
const lock = `${targetPath}.lock`;
|
|
217
|
+
const heldKey = path.resolve(lock);
|
|
218
|
+
if (HELD_LOCKS.has(heldKey))
|
|
219
|
+
return fn();
|
|
209
220
|
fs.mkdirSync(path.dirname(lock), { recursive: true });
|
|
210
221
|
const pid = String(process.pid);
|
|
211
222
|
let acquired = false;
|
|
@@ -234,6 +245,7 @@ function withFileLock(targetPath, fn) {
|
|
|
234
245
|
}
|
|
235
246
|
if (!acquired)
|
|
236
247
|
throw new Error(`could not acquire file lock for ${targetPath}`);
|
|
248
|
+
HELD_LOCKS.add(heldKey);
|
|
237
249
|
// Refresh mtime right before the critical section.
|
|
238
250
|
try {
|
|
239
251
|
fs.utimesSync(lock, new Date(), new Date());
|
|
@@ -263,6 +275,7 @@ function withFileLock(targetPath, fn) {
|
|
|
263
275
|
return result;
|
|
264
276
|
}
|
|
265
277
|
finally {
|
|
278
|
+
HELD_LOCKS.delete(heldKey);
|
|
266
279
|
try {
|
|
267
280
|
// Only release if we still own the lock.
|
|
268
281
|
const current = fs.readFileSync(lock, "utf8");
|
package/dist/shell/ledger-io.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// MILESTONE 8. Byte-exact port of the old build's src/ledger.ts stage-2
|
|
7
7
|
// git-transport functions (listLedgerEntries, unionLedgerEntries), split
|
|
8
|
-
// out per
|
|
8
|
+
// out per docs/rebuild/PLAN.md's core/shell rule since these two functions are the
|
|
9
9
|
// ONLY impure ones in the old ledger.ts module.
|
|
10
10
|
//
|
|
11
11
|
// Evidence: SPEC/ledger-trust.md "Handoff ledger entry" (listLedgerEntries/
|
|
@@ -72,12 +72,14 @@ function metricsShowCli(runId, args) {
|
|
|
72
72
|
const policy = (0, observability_1.loadCostPolicy)(args, pluginRoot());
|
|
73
73
|
return (0, observability_1.showMetricsReport)(run, { now: nowOf(args), policy });
|
|
74
74
|
}
|
|
75
|
-
/** `cw metrics summary [--scope repo|home] [--pricing ...] [--now ISO]`.
|
|
75
|
+
/** `cw metrics summary [--scope repo|home] [--pricing ...] [--now ISO] [--limit N]`.
|
|
76
|
+
* `--limit` defaults to RunRegistry.list()'s own 50-record floor when omitted. */
|
|
76
77
|
function metricsSummaryCli(args) {
|
|
77
78
|
const cwd = invocationCwd(args);
|
|
78
79
|
const scope = args.scope === "home" ? "home" : "repo";
|
|
79
80
|
const registry = new run_registry_io_1.RunRegistry(cwd);
|
|
80
|
-
const
|
|
81
|
+
const limit = args.limit === undefined ? undefined : Number(args.limit);
|
|
82
|
+
const listing = registry.list({ scope, includeArchived: true, limit });
|
|
81
83
|
const inputs = [];
|
|
82
84
|
let unreadableRuns = 0;
|
|
83
85
|
for (const record of listing.records) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// deterministic step order), hostBlackboard's action dispatch. The
|
|
10
10
|
// `summaries.workers/candidates/feedback/commits/operator` blocks are
|
|
11
11
|
// simplified real projections (not the full operator-ux/multi-agent-
|
|
12
|
-
// operator-ux module — out of this milestone's scope per
|
|
12
|
+
// operator-ux module — out of this milestone's scope per docs/rebuild/PLAN.md's
|
|
13
13
|
// build-order list) — every FIELD the conformance cases actually assert
|
|
14
14
|
// on (`summaries.topologies.officialTopologies`, the envelope's fixed key
|
|
15
15
|
// set, `ids.*`) is still byte-exact; the parts no case reaches are
|