cool-workflow 0.1.97 → 0.2.0
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 +11 -2
- 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/cli/dispatch.js +236 -0
- package/dist/cli/entry.js +120 -0
- package/dist/cli/io.js +21 -4
- package/dist/cli/parseargv.js +157 -0
- package/dist/cli.js +6 -33
- package/dist/core/capability-table.js +3534 -0
- package/dist/core/format/help.js +314 -0
- package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
- package/dist/core/hash.js +137 -0
- package/dist/core/multi-agent/candidate-scoring.js +219 -0
- package/dist/core/multi-agent/collaboration.js +481 -0
- package/dist/core/multi-agent/coordinator.js +515 -0
- package/dist/core/multi-agent/eval-replay.js +306 -0
- package/dist/core/multi-agent/runtime.js +929 -0
- package/dist/core/multi-agent/topology.js +298 -0
- package/dist/core/multi-agent/trust-policy.js +197 -0
- package/dist/core/pipeline/commit-gate.js +320 -0
- package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
- package/dist/core/pipeline/dispatch.js +103 -0
- package/dist/core/pipeline/drive-decide.js +227 -0
- package/dist/core/pipeline/error-feedback.js +161 -0
- package/dist/core/pipeline/loop-expansion.js +124 -0
- package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
- package/dist/core/pipeline/runner.js +230 -0
- package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
- package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
- package/dist/core/state/node-projection.js +95 -0
- package/dist/core/state/node-snapshot.js +230 -0
- package/dist/core/state/run-paths.js +93 -0
- package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
- package/dist/core/state/schema.js +50 -0
- package/dist/core/state/state-explosion/digest.js +243 -0
- package/dist/core/state/state-explosion/graph.js +527 -0
- package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
- package/dist/core/state/state-explosion/report.js +187 -0
- package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
- package/dist/{state-node.js → core/state/state-node.js} +90 -113
- package/dist/core/state/types.js +19 -0
- package/dist/{validation.js → core/state/validation.js} +64 -131
- package/dist/core/trust/evidence-grounding.js +134 -0
- package/dist/core/trust/ledger.js +199 -0
- package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
- package/dist/core/trust/telemetry-ledger.js +127 -0
- package/dist/core/types.js +20 -0
- package/dist/core/version.js +16 -0
- package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
- package/dist/mcp/dispatch.js +104 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp-server.js +6 -84
- package/dist/{agent-config.js → shell/agent-config.js} +118 -71
- package/dist/shell/app-run-cli.js +88 -0
- package/dist/shell/audit-cli.js +259 -0
- package/dist/shell/audit-provenance.js +83 -0
- package/dist/shell/candidate-scoring-io.js +459 -0
- package/dist/shell/collaboration-io.js +264 -0
- package/dist/shell/commit-summary.js +104 -0
- package/dist/shell/commit.js +290 -0
- package/dist/shell/coordinator-io.js +476 -0
- package/dist/shell/demo-cli.js +19 -0
- package/dist/shell/dispatch.js +162 -0
- package/dist/{doctor.js → shell/doctor.js} +123 -56
- package/dist/shell/drive.js +873 -0
- package/dist/shell/error-feedback-io.js +305 -0
- package/dist/shell/eval-io.js +473 -0
- package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
- package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
- package/dist/shell/exec-backend-cli.js +88 -0
- package/dist/shell/execution-backend/agent.js +475 -0
- package/dist/shell/execution-backend/ci.js +15 -0
- package/dist/shell/execution-backend/container.js +69 -0
- package/dist/shell/execution-backend/envelopes.js +55 -0
- package/dist/shell/execution-backend/local.js +113 -0
- package/dist/shell/execution-backend/probes.js +175 -0
- package/dist/shell/execution-backend/registry.js +402 -0
- package/dist/shell/execution-backend/remote.js +128 -0
- package/dist/shell/execution-backend/types.js +11 -0
- package/dist/shell/feedback-cli.js +81 -0
- package/dist/shell/feedback-operations.js +48 -0
- package/dist/shell/fs-atomic.js +276 -0
- package/dist/shell/harness.js +98 -0
- package/dist/shell/ledger-cli.js +212 -0
- package/dist/shell/ledger-io.js +169 -0
- package/dist/shell/man-cli.js +89 -0
- package/dist/shell/metrics-cli.js +98 -0
- package/dist/shell/multi-agent-cli.js +1002 -0
- package/dist/shell/multi-agent-host.js +563 -0
- package/dist/shell/multi-agent-io.js +387 -0
- package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
- package/dist/shell/node-store.js +124 -0
- package/dist/{observability/format.js → shell/observability-format.js} +7 -1
- package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
- package/dist/{observability.js → shell/observability.js} +159 -332
- package/dist/{onramp.js → shell/onramp.js} +11 -0
- package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
- package/dist/shell/operator-ux.js +431 -0
- package/dist/shell/orchestrator.js +231 -0
- package/dist/shell/pipeline-cli.js +667 -0
- package/dist/shell/pipeline.js +217 -0
- package/dist/shell/reclamation-io.js +1366 -0
- package/dist/shell/registry-cli.js +329 -0
- package/dist/{remote-source.js → shell/remote-source.js} +12 -5
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
- package/dist/shell/scheduling-io.js +311 -0
- package/dist/shell/state-cli.js +181 -0
- package/dist/shell/state-explosion-cli.js +197 -0
- package/dist/shell/telemetry-cli.js +85 -0
- package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
- package/dist/shell/telemetry-ledger-io.js +132 -0
- package/dist/{term.js → shell/term.js} +36 -46
- package/dist/shell/topology-io.js +361 -0
- package/dist/shell/trust-audit.js +471 -0
- package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
- package/dist/shell/verifier.js +48 -0
- package/dist/shell/workbench-host.js +250 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +175 -0
- package/dist/shell/worker-cli.js +124 -0
- package/dist/shell/worker-isolation.js +852 -0
- package/dist/shell/workflow-app-loader.js +650 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +284 -211
- 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/designs/handoff-ledger.md +145 -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/handoff-setup.md +120 -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/project-index.md +143 -71
- 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/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -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/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +30 -10
- package/scripts/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +9 -3
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1186
- package/dist/capability-registry.js +0 -879
- package/dist/cli/command-surface.js +0 -490
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/drive.js +0 -827
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -294
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -434
- package/dist/mcp/tool-definitions.js +0 -1040
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -626
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -917
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -784
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -182
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/run-store.ts — loadRunFromCwd, saveCheckpoint, compactCheckpoint.
|
|
3
|
+
//
|
|
4
|
+
// MILESTONE 3. The ONLY place state.json is read or written. Byte-exact
|
|
5
|
+
// port of the old build's src/state.ts load/save/compact functions, now
|
|
6
|
+
// split so the migration DECISION logic lives in core/state/migrations.ts
|
|
7
|
+
// and this file is the thin impure shell around it (disk read, lock,
|
|
8
|
+
// durable write).
|
|
9
|
+
//
|
|
10
|
+
// Evidence: SPEC/state-core.md "src/state.ts — persistence kernel",
|
|
11
|
+
// "Write ordering and atomic rules", "compactCheckpoint on a run with no
|
|
12
|
+
// empty optional arrays writes nothing".
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.ensureRunDirs = exports.createRunPaths = void 0;
|
|
48
|
+
exports.hashArtifactFile = hashArtifactFile;
|
|
49
|
+
exports.loadRunStateFile = loadRunStateFile;
|
|
50
|
+
exports.checkRunStateFile = checkRunStateFile;
|
|
51
|
+
exports.migrateRunStateFile = migrateRunStateFile;
|
|
52
|
+
exports.loadRunFromCwd = loadRunFromCwd;
|
|
53
|
+
exports.saveCheckpoint = saveCheckpoint;
|
|
54
|
+
exports.compactCheckpoint = compactCheckpoint;
|
|
55
|
+
exports.createRun = createRun;
|
|
56
|
+
const fs = __importStar(require("node:fs"));
|
|
57
|
+
const path = __importStar(require("node:path"));
|
|
58
|
+
const fs_atomic_1 = require("./fs-atomic");
|
|
59
|
+
const run_paths_1 = require("../core/state/run-paths");
|
|
60
|
+
Object.defineProperty(exports, "createRunPaths", { enumerable: true, get: function () { return run_paths_1.createRunPaths; } });
|
|
61
|
+
Object.defineProperty(exports, "ensureRunDirs", { enumerable: true, get: function () { return run_paths_1.ensureRunDirs; } });
|
|
62
|
+
const migrations_1 = require("../core/state/migrations");
|
|
63
|
+
const hash_1 = require("../core/hash");
|
|
64
|
+
/** Read the file at artifact.path and stamp sha256 (the core `sha256:`+hex
|
|
65
|
+
* form) + sizeBytes onto the StateArtifact. A missing/unreadable file is
|
|
66
|
+
* silently skipped so hashing an absent artifact never throws. Byte-exact
|
|
67
|
+
* port of the old flat src/state.ts:hashArtifactFile. */
|
|
68
|
+
function hashArtifactFile(artifact) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(artifact.path, "utf8");
|
|
71
|
+
artifact.sha256 = (0, hash_1.sha256)(content);
|
|
72
|
+
artifact.sizeBytes = Buffer.byteLength(content, "utf8");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* file missing — silently skip */
|
|
76
|
+
}
|
|
77
|
+
return artifact;
|
|
78
|
+
}
|
|
79
|
+
/** Dry-run load + migrate a state.json at an explicit path. */
|
|
80
|
+
function loadRunStateFile(statePath, options = {}) {
|
|
81
|
+
return (0, migrations_1.migrateRunState)((0, fs_atomic_1.readJson)(statePath), {
|
|
82
|
+
statePath,
|
|
83
|
+
dryRun: options.dryRun === undefined ? true : options.dryRun,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Same as `loadRunStateFile` with `dryRun: true`. */
|
|
87
|
+
function checkRunStateFile(statePath) {
|
|
88
|
+
return loadRunStateFile(statePath, { dryRun: true });
|
|
89
|
+
}
|
|
90
|
+
/** Dry-run unless `write: true`; writes the migrated state back with
|
|
91
|
+
* `writeJson` ONLY when status is not `unsupported` AND `write` AND
|
|
92
|
+
* `report.writeRequired`. */
|
|
93
|
+
function migrateRunStateFile(statePath, options = {}) {
|
|
94
|
+
const result = loadRunStateFile(statePath, { dryRun: !options.write });
|
|
95
|
+
if (result.report.status !== "unsupported" && options.write && result.report.writeRequired) {
|
|
96
|
+
(0, fs_atomic_1.writeJson)(statePath, result.run);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
/** Refuses an empty id with `Missing run id`; loads
|
|
101
|
+
* `<cwd>/.cw/runs/<runId>/state.json` (dry-run — never writes); throws
|
|
102
|
+
* `Unsupported CW run state: <errors joined by "; ">` on an unsupported
|
|
103
|
+
* verdict; else returns the migrated WorkflowRun in memory. */
|
|
104
|
+
function loadRunFromCwd(runId, cwd = process.cwd()) {
|
|
105
|
+
if (!runId)
|
|
106
|
+
throw new Error("Missing run id");
|
|
107
|
+
(0, fs_atomic_1.assertSafeRunId)(runId);
|
|
108
|
+
const statePath = path.join(cwd, ".cw", "runs", runId, "state.json");
|
|
109
|
+
const result = loadRunStateFile(statePath, { dryRun: true });
|
|
110
|
+
if (result.report.status === "unsupported") {
|
|
111
|
+
throw new Error(`Unsupported CW run state: ${result.report.errors.join("; ")}`);
|
|
112
|
+
}
|
|
113
|
+
return result.run;
|
|
114
|
+
}
|
|
115
|
+
/** state.json is the single source of truth — set `updatedAt`, then write
|
|
116
|
+
* it DURABLY with a lock so concurrent processes never lose an update. */
|
|
117
|
+
function saveCheckpoint(run) {
|
|
118
|
+
run.updatedAt = new Date().toISOString();
|
|
119
|
+
(0, fs_atomic_1.withFileLock)(run.paths.state, () => {
|
|
120
|
+
(0, fs_atomic_1.writeJson)(run.paths.state, run, { durable: true });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const OPTIONAL_EMPTY_ARRAY_KEYS = ["nodes", "contracts", "feedback", "workers", "sandboxProfiles", "candidates", "candidateSelections"];
|
|
124
|
+
/** Strip the 7 top-level optional-array keys when each is an empty array
|
|
125
|
+
* (normalizeRunState backfills these on load, so stripping saves disk
|
|
126
|
+
* without losing information). Returns the count stripped; writes nothing
|
|
127
|
+
* (and calls `saveCheckpoint` zero times) when nothing was stripped. */
|
|
128
|
+
function compactCheckpoint(run) {
|
|
129
|
+
let stripped = 0;
|
|
130
|
+
const state = run;
|
|
131
|
+
for (const key of OPTIONAL_EMPTY_ARRAY_KEYS) {
|
|
132
|
+
if (Array.isArray(state[key]) && state[key].length === 0) {
|
|
133
|
+
delete state[key];
|
|
134
|
+
stripped++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (stripped > 0)
|
|
138
|
+
saveCheckpoint(run);
|
|
139
|
+
return stripped;
|
|
140
|
+
}
|
|
141
|
+
/** Creates a brand-new run's initial `state.json` on disk: makes the run
|
|
142
|
+
* directory tree, migrates `{}` through `normalizeRunState` (so every
|
|
143
|
+
* default lands exactly like a real legacy-run migration would), stamps
|
|
144
|
+
* `id`/`workflow.id`/`workflow.title`, and durably writes `state.json`.
|
|
145
|
+
* This is the minimal "create a run" mechanism this milestone needs to
|
|
146
|
+
* drive the state kernel end to end; the real `plan`/`quickstart`
|
|
147
|
+
* pipeline (later milestones) layers phases/tasks/inputs on top of this
|
|
148
|
+
* same primitive. */
|
|
149
|
+
function createRun(runDir, runId, workflowId, cwd) {
|
|
150
|
+
const paths = (0, run_paths_1.createRunPaths)(runDir);
|
|
151
|
+
(0, run_paths_1.ensureRunDirs)(paths);
|
|
152
|
+
const seed = {
|
|
153
|
+
id: runId,
|
|
154
|
+
cwd,
|
|
155
|
+
workflowId,
|
|
156
|
+
paths: paths,
|
|
157
|
+
};
|
|
158
|
+
const { run, report } = (0, migrations_1.migrateRunState)(seed, { statePath: paths.state, dryRun: false });
|
|
159
|
+
if (report.status === "unsupported") {
|
|
160
|
+
throw new Error(`Unsupported CW run state: ${report.errors.join("; ")}`);
|
|
161
|
+
}
|
|
162
|
+
(0, fs_atomic_1.writeJson)(paths.state, run, { durable: true });
|
|
163
|
+
return run;
|
|
164
|
+
}
|
|
@@ -1,7 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// shell/sandbox-profile.ts — the 4 bundled sandbox profiles, resolution,
|
|
3
|
+
// validation, and the runtime read/write/command/network checks.
|
|
4
|
+
//
|
|
5
|
+
// MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
|
|
6
|
+
// plugins/cool-workflow/src/sandbox-profile.ts.
|
|
7
|
+
//
|
|
8
|
+
// Evidence: SPEC/execution-backend.md "Sandbox profiles".
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
5
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
43
|
exports.SandboxProfileError = exports.DEFAULT_SANDBOX_PROFILE_ID = exports.SANDBOX_PROFILE_SCHEMA_VERSION = void 0;
|
|
7
44
|
exports.bundledSandboxProfileIds = bundledSandboxProfileIds;
|
|
@@ -18,12 +55,12 @@ exports.validateSandboxWrite = validateSandboxWrite;
|
|
|
18
55
|
exports.validateSandboxRead = validateSandboxRead;
|
|
19
56
|
exports.validateSandboxCommand = validateSandboxCommand;
|
|
20
57
|
exports.validateSandboxNetwork = validateSandboxNetwork;
|
|
21
|
-
exports.upsertRunSandboxPolicy = upsertRunSandboxPolicy;
|
|
22
|
-
exports.sandboxContextForRun = sandboxContextForRun;
|
|
23
58
|
exports.sandboxContextForValidation = sandboxContextForValidation;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
59
|
+
exports.sandboxContextForRun = sandboxContextForRun;
|
|
60
|
+
exports.persistCustomSandboxProfile = persistCustomSandboxProfile;
|
|
61
|
+
const fs = __importStar(require("node:fs"));
|
|
62
|
+
const path = __importStar(require("node:path"));
|
|
63
|
+
const fs_atomic_1 = require("./fs-atomic");
|
|
27
64
|
exports.SANDBOX_PROFILE_SCHEMA_VERSION = 1;
|
|
28
65
|
exports.DEFAULT_SANDBOX_PROFILE_ID = "default";
|
|
29
66
|
class SandboxProfileError extends Error {
|
|
@@ -53,8 +90,8 @@ const BUNDLED_PROFILE_DEFINITIONS = [
|
|
|
53
90
|
env: { inherit: false, expose: [] },
|
|
54
91
|
hostInstructions: [
|
|
55
92
|
"Run with the host's normal process policy.",
|
|
56
|
-
"Preserve CW output acceptance checks for result.md, artifacts/, and logs/."
|
|
57
|
-
]
|
|
93
|
+
"Preserve CW output acceptance checks for result.md, artifacts/, and logs/.",
|
|
94
|
+
],
|
|
58
95
|
},
|
|
59
96
|
{
|
|
60
97
|
schemaVersion: exports.SANDBOX_PROFILE_SCHEMA_VERSION,
|
|
@@ -69,8 +106,8 @@ const BUNDLED_PROFILE_DEFINITIONS = [
|
|
|
69
106
|
env: { inherit: false, expose: [] },
|
|
70
107
|
hostInstructions: [
|
|
71
108
|
"Deny network access unless the operator explicitly layers a site policy over this profile.",
|
|
72
|
-
"Mount or expose the workspace read-only when the agent host supports it."
|
|
73
|
-
]
|
|
109
|
+
"Mount or expose the workspace read-only when the agent host supports it.",
|
|
110
|
+
],
|
|
74
111
|
},
|
|
75
112
|
{
|
|
76
113
|
schemaVersion: exports.SANDBOX_PROFILE_SCHEMA_VERSION,
|
|
@@ -85,8 +122,8 @@ const BUNDLED_PROFILE_DEFINITIONS = [
|
|
|
85
122
|
env: { inherit: false, expose: [] },
|
|
86
123
|
hostInstructions: [
|
|
87
124
|
"Use only for workers that are expected to modify repository files.",
|
|
88
|
-
"Keep CW run state writes under CW control; workers should still return results through worker output."
|
|
89
|
-
]
|
|
125
|
+
"Keep CW run state writes under CW control; workers should still return results through worker output.",
|
|
126
|
+
],
|
|
90
127
|
},
|
|
91
128
|
{
|
|
92
129
|
schemaVersion: exports.SANDBOX_PROFILE_SCHEMA_VERSION,
|
|
@@ -101,9 +138,9 @@ const BUNDLED_PROFILE_DEFINITIONS = [
|
|
|
101
138
|
env: { inherit: false, expose: [] },
|
|
102
139
|
hostInstructions: [
|
|
103
140
|
"Expose only input.md and result.md to the worker when host sandboxing is available.",
|
|
104
|
-
"Do not provide shell command execution, network access, or inherited environment variables."
|
|
105
|
-
]
|
|
106
|
-
}
|
|
141
|
+
"Do not provide shell command execution, network access, or inherited environment variables.",
|
|
142
|
+
],
|
|
143
|
+
},
|
|
107
144
|
];
|
|
108
145
|
function bundledSandboxProfileIds() {
|
|
109
146
|
return BUNDLED_PROFILE_DEFINITIONS.map((profile) => profile.id).sort();
|
|
@@ -118,7 +155,7 @@ function showBundledSandboxProfile(id, context = defaultSandboxContext()) {
|
|
|
118
155
|
const profile = BUNDLED_PROFILE_DEFINITIONS.find((candidate) => candidate.id === id);
|
|
119
156
|
if (!profile) {
|
|
120
157
|
throw new SandboxProfileError("sandbox-profile-not-found", `Sandbox profile not found: ${id}`, {
|
|
121
|
-
details: { requestedProfileId: id, bundledProfileIds: BUNDLED_PROFILE_DEFINITIONS.map((candidate) => candidate.id) }
|
|
158
|
+
details: { requestedProfileId: id, bundledProfileIds: BUNDLED_PROFILE_DEFINITIONS.map((candidate) => candidate.id) },
|
|
122
159
|
});
|
|
123
160
|
}
|
|
124
161
|
return resolveSandboxProfile(profile, context);
|
|
@@ -127,34 +164,21 @@ function resolveSandboxProfileById(id, context = defaultSandboxContext()) {
|
|
|
127
164
|
const requested = id || exports.DEFAULT_SANDBOX_PROFILE_ID;
|
|
128
165
|
if (isBundledSandboxProfileId(requested))
|
|
129
166
|
return showBundledSandboxProfile(requested, context);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// This closes the gap where `sandbox validate` accepted a custom profile that
|
|
133
|
-
// dispatch/worker-isolation then refused — validated but never enforceable.
|
|
134
|
-
// A non-bundled, non-file id still fails closed via showBundledSandboxProfile.
|
|
135
|
-
const absolute = node_path_1.default.resolve(context.cwd, requested);
|
|
136
|
-
if (!absolute.startsWith(node_path_1.default.resolve(context.cwd) + node_path_1.default.sep) && absolute !== node_path_1.default.resolve(context.cwd)) {
|
|
167
|
+
const absolute = path.resolve(context.cwd, requested);
|
|
168
|
+
if (!absolute.startsWith(path.resolve(context.cwd) + path.sep) && absolute !== path.resolve(context.cwd)) {
|
|
137
169
|
throw new SandboxProfileError("sandbox-profile-path-escape", `Custom profile path traversal denied: ${requested}`, {
|
|
138
|
-
details: { requested }
|
|
170
|
+
details: { requested },
|
|
139
171
|
});
|
|
140
172
|
}
|
|
141
|
-
if (
|
|
173
|
+
if (fs.existsSync(absolute) && fs.statSync(absolute).isFile()) {
|
|
142
174
|
const result = validateSandboxProfileFile(requested, context);
|
|
143
175
|
if (!result.valid || !result.profile) {
|
|
144
176
|
throw new SandboxProfileError("sandbox-profile-invalid", `Custom sandbox profile is invalid: ${requested}`, {
|
|
145
|
-
details: { issues: result.issues }
|
|
177
|
+
details: { issues: result.issues },
|
|
146
178
|
});
|
|
147
179
|
}
|
|
148
180
|
return result.profile;
|
|
149
181
|
}
|
|
150
|
-
// H7: a custom profile loaded from a FILE at dispatch persists as a DEFINITION
|
|
151
|
-
// in run.customSandboxProfiles (threaded here as context.customProfiles). After a
|
|
152
|
-
// worker scope snapshot is lost, the boundary re-resolves by the profile's
|
|
153
|
-
// LOGICAL id (e.g. "my-custom"), and the dispatch-time file path is gone. Resolve
|
|
154
|
-
// the persisted definition against the CURRENT (worker) context so worker-specific
|
|
155
|
-
// path tokens ($workerDir etc.) bind to THIS worker — re-enforcing the same
|
|
156
|
-
// policy instead of throwing not-found. This runs only after the bundled +
|
|
157
|
-
// file-path branches, so a custom id never shadows a bundled or on-disk profile.
|
|
158
182
|
const customDefinition = context.customProfiles?.[requested];
|
|
159
183
|
if (customDefinition) {
|
|
160
184
|
return resolveSandboxProfile(customDefinition, context);
|
|
@@ -165,7 +189,7 @@ function resolveSandboxProfile(profile, context = defaultSandboxContext()) {
|
|
|
165
189
|
const issues = validateSandboxProfileDefinition(profile, context);
|
|
166
190
|
if (issues.length) {
|
|
167
191
|
throw new SandboxProfileError("sandbox-profile-invalid", `Sandbox profile ${profile.id || "(unknown)"} is invalid`, {
|
|
168
|
-
details: { issues }
|
|
192
|
+
details: { issues },
|
|
169
193
|
});
|
|
170
194
|
}
|
|
171
195
|
const workerOutput = normalizeWorkerOutput(profile.workerOutput, context);
|
|
@@ -190,44 +214,40 @@ function resolveSandboxProfile(profile, context = defaultSandboxContext()) {
|
|
|
190
214
|
"profile validation",
|
|
191
215
|
"path normalization",
|
|
192
216
|
"worker result acceptance against sandbox write policy",
|
|
193
|
-
"durable ErrorFeedback for denied worker output"
|
|
217
|
+
"durable ErrorFeedback for denied worker output",
|
|
194
218
|
],
|
|
195
219
|
hostRequired: [
|
|
196
220
|
"OS-level read isolation",
|
|
197
221
|
"OS-level write isolation before result acceptance",
|
|
198
222
|
"process execution restrictions",
|
|
199
223
|
"network restrictions",
|
|
200
|
-
"environment variable filtering"
|
|
201
|
-
]
|
|
224
|
+
"environment variable filtering",
|
|
225
|
+
],
|
|
202
226
|
},
|
|
203
227
|
hostInstructions: profile.hostInstructions || [],
|
|
204
228
|
resolvedAt: new Date().toISOString(),
|
|
205
|
-
metadata: profile.metadata
|
|
229
|
+
metadata: profile.metadata,
|
|
206
230
|
};
|
|
207
231
|
}
|
|
208
232
|
function validateSandboxProfileFile(profileFile, context = defaultSandboxContext()) {
|
|
209
|
-
const absolutePath =
|
|
233
|
+
const absolutePath = path.resolve(profileFile);
|
|
210
234
|
const issues = [];
|
|
211
235
|
if (hasTraversal(profileFile)) {
|
|
212
236
|
issues.push(issue("sandbox-profile-invalid", `Profile file path contains traversal: ${profileFile}`, profileFile));
|
|
213
237
|
return { valid: false, profileFile: absolutePath, issues };
|
|
214
238
|
}
|
|
215
|
-
if (!
|
|
239
|
+
if (!fs.existsSync(absolutePath)) {
|
|
216
240
|
issues.push(issue("sandbox-profile-invalid", `Profile file does not exist: ${absolutePath}`, absolutePath));
|
|
217
241
|
return { valid: false, profileFile: absolutePath, issues };
|
|
218
242
|
}
|
|
219
243
|
let profile;
|
|
220
244
|
try {
|
|
221
|
-
profile = JSON.parse(
|
|
245
|
+
profile = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
|
|
222
246
|
}
|
|
223
247
|
catch (error) {
|
|
224
248
|
issues.push(issue("sandbox-profile-invalid", `Profile file is not valid JSON: ${messageOf(error)}`, absolutePath));
|
|
225
249
|
return { valid: false, profileFile: absolutePath, issues };
|
|
226
250
|
}
|
|
227
|
-
// Fail closed if a CUSTOM file reuses a BUNDLED id (H7 hardening): resolution is
|
|
228
|
-
// bundled-first, so a custom "default"/"workspace-write"/... would be silently
|
|
229
|
-
// shadowed by the WIDER bundled policy on a snapshot-loss re-resolve — widening
|
|
230
|
-
// the sandbox with no error. Reserve the bundled names for bundled profiles.
|
|
231
251
|
if (profile && typeof profile.id === "string" && isBundledSandboxProfileId(profile.id)) {
|
|
232
252
|
issues.push(issue("sandbox-profile-invalid", `Custom sandbox profile id "${profile.id}" is reserved (collides with a bundled profile); choose a different id`, absolutePath));
|
|
233
253
|
return { valid: false, profileFile: absolutePath, issues };
|
|
@@ -260,7 +280,7 @@ function effectiveSandboxWritePaths(policy) {
|
|
|
260
280
|
const workerPaths = [
|
|
261
281
|
policy.workerOutput.result ? policy.metadata?.resultPath : undefined,
|
|
262
282
|
policy.workerOutput.artifacts ? policy.metadata?.artifactsDir : undefined,
|
|
263
|
-
policy.workerOutput.logs ? policy.metadata?.logsDir : undefined
|
|
283
|
+
policy.workerOutput.logs ? policy.metadata?.logsDir : undefined,
|
|
264
284
|
].filter((value) => typeof value === "string" && value.length > 0);
|
|
265
285
|
return uniqueResolvedPaths([...policy.writePaths, ...workerPaths], defaultSandboxContext());
|
|
266
286
|
}
|
|
@@ -276,8 +296,8 @@ function sandboxPolicyForWorker(profileId, context) {
|
|
|
276
296
|
inputPath: context.inputPath,
|
|
277
297
|
resultPath: context.resultPath,
|
|
278
298
|
artifactsDir: context.artifactsDir,
|
|
279
|
-
logsDir: context.logsDir
|
|
280
|
-
})
|
|
299
|
+
logsDir: context.logsDir,
|
|
300
|
+
}),
|
|
281
301
|
};
|
|
282
302
|
}
|
|
283
303
|
function validateSandboxWrite(policy, rawPath, workerId = "") {
|
|
@@ -312,36 +332,65 @@ function validateSandboxNetwork(policy, target, workerId = "") {
|
|
|
312
332
|
}
|
|
313
333
|
return null;
|
|
314
334
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
335
|
+
function sandboxContextForValidation(cwd = process.cwd()) {
|
|
336
|
+
const root = path.resolve(cwd);
|
|
337
|
+
const runDir = path.join(root, ".cw", "runs", "_sandbox-profile-validation");
|
|
338
|
+
const workerDir = path.join(runDir, "workers", "_worker");
|
|
339
|
+
return {
|
|
340
|
+
cwd: root,
|
|
341
|
+
runDir,
|
|
342
|
+
workerDir,
|
|
343
|
+
inputPath: path.join(workerDir, "input.md"),
|
|
344
|
+
resultPath: path.join(workerDir, "result.md"),
|
|
345
|
+
artifactsDir: path.join(workerDir, "artifacts"),
|
|
346
|
+
logsDir: path.join(workerDir, "logs"),
|
|
347
|
+
};
|
|
322
348
|
}
|
|
349
|
+
// H7: a run-scoped resolution context. Threads run.customSandboxProfiles as
|
|
350
|
+
// customProfiles so a worker-boundary re-resolve by logical id can find and
|
|
351
|
+
// re-resolve a CUSTOM profile after a scope snapshot is lost — re-resolving
|
|
352
|
+
// against the worker context (not a dispatch-time file path).
|
|
323
353
|
function sandboxContextForRun(run) {
|
|
324
354
|
return {
|
|
325
355
|
cwd: run.cwd,
|
|
326
356
|
runDir: run.paths.runDir,
|
|
327
|
-
|
|
328
|
-
// logical id can find + re-resolve a custom profile after snapshot loss.
|
|
329
|
-
customProfiles: run.customSandboxProfiles
|
|
357
|
+
customProfiles: run.customSandboxProfiles,
|
|
330
358
|
};
|
|
331
359
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
360
|
+
// H7: if the requested profile is a CUSTOM profile loaded from a FILE (a
|
|
361
|
+
// non-bundled id that resolves to a readable, valid profile file), persist its
|
|
362
|
+
// raw DEFINITION onto run.customSandboxProfiles keyed by the definition's logical
|
|
363
|
+
// id. We store the DEFINITION (path tokens intact), not a resolved policy, so
|
|
364
|
+
// worker-specific tokens re-bind to the correct worker context on every later
|
|
365
|
+
// re-resolve. Bundled ids and unknown ids are left untouched, so this never
|
|
366
|
+
// shadows a bundled profile or masks a fail-closed. Throws on an id collision:
|
|
367
|
+
// the same logical id mapped to a DIFFERENT profile file is a caller mistake.
|
|
368
|
+
function persistCustomSandboxProfile(run, requested) {
|
|
369
|
+
if (!requested || isBundledSandboxProfileId(requested))
|
|
370
|
+
return;
|
|
371
|
+
const absolute = path.resolve(requested);
|
|
372
|
+
if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile())
|
|
373
|
+
return;
|
|
374
|
+
const validation = validateSandboxProfileFile(requested, sandboxContextForValidation(run.cwd));
|
|
375
|
+
if (!validation.valid || !validation.profile)
|
|
376
|
+
return;
|
|
377
|
+
let definition;
|
|
378
|
+
try {
|
|
379
|
+
definition = JSON.parse(fs.readFileSync(absolute, "utf8"));
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (!definition || typeof definition !== "object" || typeof definition.id !== "string" || !definition.id)
|
|
385
|
+
return;
|
|
386
|
+
const profiles = (run.customSandboxProfiles || {});
|
|
387
|
+
const previous = profiles[definition.id];
|
|
388
|
+
if (previous && JSON.stringify(previous) !== JSON.stringify(definition)) {
|
|
389
|
+
throw new Error(`Sandbox profile id collision: "${definition.id}" is already defined by a different custom profile ` +
|
|
390
|
+
`(source: ${absolute}). Use a unique id in each custom profile file.`);
|
|
391
|
+
}
|
|
392
|
+
profiles[definition.id] = definition;
|
|
393
|
+
run.customSandboxProfiles = profiles;
|
|
345
394
|
}
|
|
346
395
|
function validateSandboxPathAccess(mode, policy, rawPath, allowedPaths, workerId) {
|
|
347
396
|
if (hasTraversal(rawPath)) {
|
|
@@ -350,23 +399,15 @@ function validateSandboxPathAccess(mode, policy, rawPath, allowedPaths, workerId
|
|
|
350
399
|
if (hasControlCharacters(rawPath)) {
|
|
351
400
|
return denied(`sandbox-${mode}-denied`, `Worker ${workerId} ${mode} path is malformed: ${rawPath}`, rawPath, allowedPaths);
|
|
352
401
|
}
|
|
353
|
-
const candidate =
|
|
354
|
-
|
|
355
|
-
// so a planted symlink whose textual path looks "inside" an allowed root but
|
|
356
|
-
// whose real target escapes it is denied, not silently accepted.
|
|
357
|
-
const insideAllowedPath = allowedPaths.some((allowed) => (0, state_1.isContainedPath)(candidate, allowed));
|
|
402
|
+
const candidate = path.resolve(rawPath);
|
|
403
|
+
const insideAllowedPath = allowedPaths.some((allowed) => (0, fs_atomic_1.isContainedPath)(candidate, allowed));
|
|
358
404
|
if (!insideAllowedPath) {
|
|
359
405
|
return denied(`sandbox-${mode}-denied`, `Worker ${workerId} ${mode} path is outside sandbox profile ${policy.id}: ${candidate}`, candidate, allowedPaths);
|
|
360
406
|
}
|
|
361
407
|
return null;
|
|
362
408
|
}
|
|
363
409
|
function denied(code, message, candidatePath, allowedPaths) {
|
|
364
|
-
return {
|
|
365
|
-
code,
|
|
366
|
-
message,
|
|
367
|
-
path: candidatePath,
|
|
368
|
-
allowedPaths
|
|
369
|
-
};
|
|
410
|
+
return { code, message, path: candidatePath, allowedPaths };
|
|
370
411
|
}
|
|
371
412
|
function defaultSandboxContext() {
|
|
372
413
|
return sandboxContextForValidation(process.cwd());
|
|
@@ -414,7 +455,7 @@ function resolveProfilePath(value, context) {
|
|
|
414
455
|
if (hasTraversal(expanded)) {
|
|
415
456
|
throw new SandboxProfileError("sandbox-profile-invalid", `Sandbox path contains traversal: ${value}`, { path: value });
|
|
416
457
|
}
|
|
417
|
-
return
|
|
458
|
+
return path.resolve(context.cwd, expanded);
|
|
418
459
|
}
|
|
419
460
|
function expandPathToken(value, context) {
|
|
420
461
|
const tokens = {
|
|
@@ -424,15 +465,13 @@ function expandPathToken(value, context) {
|
|
|
424
465
|
$inputPath: context.inputPath,
|
|
425
466
|
$resultPath: context.resultPath,
|
|
426
467
|
$artifactsDir: context.artifactsDir,
|
|
427
|
-
$logsDir: context.logsDir
|
|
468
|
+
$logsDir: context.logsDir,
|
|
428
469
|
};
|
|
429
470
|
if (!value.startsWith("$"))
|
|
430
471
|
return value;
|
|
431
472
|
const replacement = tokens[value];
|
|
432
473
|
if (!replacement) {
|
|
433
|
-
throw new SandboxProfileError("sandbox-profile-invalid", `Unknown or unavailable sandbox path token: ${value}`, {
|
|
434
|
-
path: value
|
|
435
|
-
});
|
|
474
|
+
throw new SandboxProfileError("sandbox-profile-invalid", `Unknown or unavailable sandbox path token: ${value}`, { path: value });
|
|
436
475
|
}
|
|
437
476
|
return replacement;
|
|
438
477
|
}
|
|
@@ -440,27 +479,27 @@ function normalizeWorkerOutput(policy, context) {
|
|
|
440
479
|
return {
|
|
441
480
|
result: policy?.result ?? true,
|
|
442
481
|
artifacts: context.allowArtifacts ?? policy?.artifacts ?? true,
|
|
443
|
-
logs: context.allowLogs ?? policy?.logs ?? true
|
|
482
|
+
logs: context.allowLogs ?? policy?.logs ?? true,
|
|
444
483
|
};
|
|
445
484
|
}
|
|
446
485
|
function normalizeExecute(policy) {
|
|
447
486
|
return {
|
|
448
487
|
mode: policy?.mode || "none",
|
|
449
488
|
allow: policy?.allow ? [...policy.allow] : undefined,
|
|
450
|
-
deny: policy?.deny ? [...policy.deny] : undefined
|
|
489
|
+
deny: policy?.deny ? [...policy.deny] : undefined,
|
|
451
490
|
};
|
|
452
491
|
}
|
|
453
492
|
function normalizeNetwork(policy) {
|
|
454
493
|
return {
|
|
455
494
|
mode: policy?.mode || "none",
|
|
456
|
-
allow: policy?.allow ? [...policy.allow] : undefined
|
|
495
|
+
allow: policy?.allow ? [...policy.allow] : undefined,
|
|
457
496
|
};
|
|
458
497
|
}
|
|
459
498
|
function normalizeEnv(policy) {
|
|
460
499
|
return {
|
|
461
500
|
inherit: Boolean(policy?.inherit),
|
|
462
501
|
expose: policy?.expose ? [...policy.expose] : [],
|
|
463
|
-
deny: policy?.deny ? [...policy.deny] : undefined
|
|
502
|
+
deny: policy?.deny ? [...policy.deny] : undefined,
|
|
464
503
|
};
|
|
465
504
|
}
|
|
466
505
|
function validateCommandPolicy(policy, issues) {
|
|
@@ -506,7 +545,7 @@ function hasTraversal(value) {
|
|
|
506
545
|
return value.split(/[\\/]+/).includes("..");
|
|
507
546
|
}
|
|
508
547
|
function hasControlCharacters(value) {
|
|
509
|
-
return /[\
|
|
548
|
+
return /[\x00-\x1f]/.test(value);
|
|
510
549
|
}
|
|
511
550
|
function compactMetadata(value) {
|
|
512
551
|
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
|