@guilz-dev/belay 0.9.4 → 0.10.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/README.md +3 -2
- package/dist/adapters/cursor/hook-dispatch-entry.d.ts +3 -0
- package/dist/adapters/cursor/hook-dispatch-entry.js +11 -0
- package/dist/adapters/cursor/hook-router.js +14 -7
- package/dist/adapters/cursor/hooks.d.ts +1 -1
- package/dist/adapters/cursor/hooks.js +16 -5
- package/dist/adapters/cursor/routing-config-trust.d.ts +1 -0
- package/dist/adapters/cursor/routing-config-trust.js +67 -0
- package/dist/adapters/cursor/runtime-entry.d.ts +2 -2
- package/dist/adapters/cursor/runtime-entry.js +35 -10
- package/dist/adapters/shared/gate-runtime.js +28 -17
- package/dist/bundle/claude-runtime.mjs +602 -438
- package/dist/bundle/codex-runtime.mjs +612 -448
- package/dist/bundle/cursor-dispatcher.mjs +100 -26
- package/dist/bundle/cursor-runtime.mjs +662 -470
- package/dist/cli.js +53 -4
- package/dist/commands/approval-token.d.ts +10 -0
- package/dist/commands/approval-token.js +26 -0
- package/dist/commands/audit.d.ts +2 -1
- package/dist/commands/audit.js +2 -2
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +28 -2
- package/dist/commands/doctor.js +10 -54
- package/dist/commands/dogfood-check.d.ts +3 -0
- package/dist/commands/dogfood-check.js +116 -0
- package/dist/commands/dogfood.d.ts +1 -0
- package/dist/commands/dogfood.js +4 -3
- package/dist/commands/judge.js +2 -2
- package/dist/commands/recovery-checkpoints.js +2 -11
- package/dist/config-io.d.ts +1 -0
- package/dist/config-io.js +8 -1
- package/dist/core/dogfood-environment.d.ts +8 -0
- package/dist/core/dogfood-environment.js +55 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.js +12 -0
- package/dist/core/egress-approval.js +0 -18
- package/dist/core/notify.d.ts +8 -2
- package/dist/core/notify.js +63 -7
- package/dist/core/recovery/operator-guidance.js +4 -4
- package/dist/core/repo-config-trust.d.ts +31 -0
- package/dist/core/repo-config-trust.js +152 -0
- package/dist/corpus/benign-probe-cores.d.ts +1 -1
- package/dist/corpus/benign-probe-cores.js +0 -1
- package/dist/defaults.js +0 -25
- package/dist/installer/scope-config.js +2 -2
- package/dist/installer.js +4 -4
- package/dist/types.d.ts +19 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
|
@@ -3510,9 +3510,122 @@ var init_config_layers = __esm({
|
|
|
3510
3510
|
}
|
|
3511
3511
|
});
|
|
3512
3512
|
|
|
3513
|
-
// src/config-
|
|
3513
|
+
// src/core/repo-config-trust.ts
|
|
3514
3514
|
import { existsSync as existsSync3 } from "node:fs";
|
|
3515
|
-
import { chmod, mkdir as mkdir2, open as open2, readFile as readFile2, rename, unlink as unlink2
|
|
3515
|
+
import { chmod, mkdir as mkdir2, open as open2, readFile as readFile2, rename, unlink as unlink2 } from "node:fs/promises";
|
|
3516
|
+
import path12 from "node:path";
|
|
3517
|
+
function isObjectRecord(value) {
|
|
3518
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3519
|
+
}
|
|
3520
|
+
function hasOnlyExpectedKeys(value) {
|
|
3521
|
+
const expected = /* @__PURE__ */ new Set([
|
|
3522
|
+
"schemaVersion",
|
|
3523
|
+
"repoRoot",
|
|
3524
|
+
"adapter",
|
|
3525
|
+
"repoConfigFingerprint",
|
|
3526
|
+
"trustedAt"
|
|
3527
|
+
]);
|
|
3528
|
+
const keys = Object.keys(value);
|
|
3529
|
+
if (keys.length !== expected.size) {
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
return keys.every((key) => expected.has(key));
|
|
3533
|
+
}
|
|
3534
|
+
function isAdapterName(value) {
|
|
3535
|
+
return value === "cursor" || value === "claude" || value === "codex";
|
|
3536
|
+
}
|
|
3537
|
+
function isIsoTimestamp(value) {
|
|
3538
|
+
return Number.isFinite(Date.parse(value));
|
|
3539
|
+
}
|
|
3540
|
+
function parseStrictTrustRecord(value) {
|
|
3541
|
+
if (!isObjectRecord(value) || !hasOnlyExpectedKeys(value)) {
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
if (value.schemaVersion !== 1) {
|
|
3545
|
+
return null;
|
|
3546
|
+
}
|
|
3547
|
+
if (typeof value.repoRoot !== "string" || value.repoRoot.trim().length === 0) {
|
|
3548
|
+
return null;
|
|
3549
|
+
}
|
|
3550
|
+
if (!isAdapterName(value.adapter)) {
|
|
3551
|
+
return null;
|
|
3552
|
+
}
|
|
3553
|
+
if (typeof value.repoConfigFingerprint !== "string" || !/^[a-f0-9]{64}$/.test(value.repoConfigFingerprint)) {
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3556
|
+
if (typeof value.trustedAt !== "string" || !isIsoTimestamp(value.trustedAt)) {
|
|
3557
|
+
return null;
|
|
3558
|
+
}
|
|
3559
|
+
return {
|
|
3560
|
+
schemaVersion: 1,
|
|
3561
|
+
repoRoot: value.repoRoot,
|
|
3562
|
+
adapter: value.adapter,
|
|
3563
|
+
repoConfigFingerprint: value.repoConfigFingerprint,
|
|
3564
|
+
trustedAt: value.trustedAt
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
function repoConfigFingerprint(rawConfig) {
|
|
3568
|
+
return hashValue(canonicalStringify(rawConfig));
|
|
3569
|
+
}
|
|
3570
|
+
function repoConfigTrustPath(repoRoot, adapter) {
|
|
3571
|
+
const canonicalRepoRoot = canonicalPath(repoRoot);
|
|
3572
|
+
const identity = hashValue(`${canonicalRepoRoot}\0${adapter}`);
|
|
3573
|
+
return path12.join(defaultControlPlaneDir(), "config-trust", `${identity}.json`);
|
|
3574
|
+
}
|
|
3575
|
+
async function inspectRepoConfigTrust(repoRoot, adapter, rawConfig) {
|
|
3576
|
+
const canonicalRepoRoot = canonicalPath(repoRoot);
|
|
3577
|
+
const recordPath = repoConfigTrustPath(repoRoot, adapter);
|
|
3578
|
+
if (!existsSync3(recordPath)) {
|
|
3579
|
+
return { trusted: false, recordPath, reason: "missing" };
|
|
3580
|
+
}
|
|
3581
|
+
let parsed;
|
|
3582
|
+
try {
|
|
3583
|
+
parsed = JSON.parse(await readFile2(recordPath, "utf8"));
|
|
3584
|
+
} catch {
|
|
3585
|
+
return { trusted: false, recordPath, reason: "malformed" };
|
|
3586
|
+
}
|
|
3587
|
+
const record = parseStrictTrustRecord(parsed);
|
|
3588
|
+
if (!record) {
|
|
3589
|
+
return { trusted: false, recordPath, reason: "malformed" };
|
|
3590
|
+
}
|
|
3591
|
+
if (record.adapter !== adapter || canonicalPath(record.repoRoot) !== canonicalRepoRoot) {
|
|
3592
|
+
return { trusted: false, recordPath, reason: "identity_mismatch" };
|
|
3593
|
+
}
|
|
3594
|
+
const fingerprint = repoConfigFingerprint(rawConfig);
|
|
3595
|
+
if (record.repoConfigFingerprint !== fingerprint) {
|
|
3596
|
+
return { trusted: false, recordPath, reason: "fingerprint_mismatch" };
|
|
3597
|
+
}
|
|
3598
|
+
return { trusted: true, recordPath, fingerprint };
|
|
3599
|
+
}
|
|
3600
|
+
async function assertRepoConfigTrusted(repoRoot, adapter, rawConfig) {
|
|
3601
|
+
const status = await inspectRepoConfigTrust(repoRoot, adapter, rawConfig);
|
|
3602
|
+
if (status.trusted) {
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
throw new RepoConfigTrustError(status);
|
|
3606
|
+
}
|
|
3607
|
+
var TRUST_MESSAGE, RepoConfigTrustError;
|
|
3608
|
+
var init_repo_config_trust = __esm({
|
|
3609
|
+
"src/core/repo-config-trust.ts"() {
|
|
3610
|
+
"use strict";
|
|
3611
|
+
init_config();
|
|
3612
|
+
init_fingerprint2();
|
|
3613
|
+
init_path_utils();
|
|
3614
|
+
TRUST_MESSAGE = "Repository config is not trusted. Review it, then run `belay config trust`.";
|
|
3615
|
+
RepoConfigTrustError = class extends Error {
|
|
3616
|
+
status;
|
|
3617
|
+
constructor(status) {
|
|
3618
|
+
super(TRUST_MESSAGE);
|
|
3619
|
+
this.name = "RepoConfigTrustError";
|
|
3620
|
+
this.status = status;
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
});
|
|
3625
|
+
|
|
3626
|
+
// src/config-io.ts
|
|
3627
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3628
|
+
import { chmod as chmod2, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename2, unlink as unlink3, writeFile } from "node:fs/promises";
|
|
3516
3629
|
function resolveAdapterName(config) {
|
|
3517
3630
|
if (config.adapter === "claude") {
|
|
3518
3631
|
return "claude";
|
|
@@ -3523,10 +3636,10 @@ function resolveAdapterName(config) {
|
|
|
3523
3636
|
return "cursor";
|
|
3524
3637
|
}
|
|
3525
3638
|
function detectAdapterName(repoRoot) {
|
|
3526
|
-
if (
|
|
3639
|
+
if (existsSync4(configPathFor(repoRoot, "claude"))) {
|
|
3527
3640
|
return "claude";
|
|
3528
3641
|
}
|
|
3529
|
-
if (
|
|
3642
|
+
if (existsSync4(configPathFor(repoRoot, "codex"))) {
|
|
3530
3643
|
return "codex";
|
|
3531
3644
|
}
|
|
3532
3645
|
return "cursor";
|
|
@@ -3541,20 +3654,20 @@ async function loadLayeredConfig(repoRoot, adapter = detectAdapterName(repoRoot)
|
|
|
3541
3654
|
const layout = getAdapterLayout(adapter);
|
|
3542
3655
|
const configPath = configPathFor(repoRoot, adapter);
|
|
3543
3656
|
let repoConfig = {};
|
|
3544
|
-
if (
|
|
3545
|
-
repoConfig = JSON.parse(await
|
|
3657
|
+
if (existsSync4(configPath)) {
|
|
3658
|
+
repoConfig = JSON.parse(await readFile3(configPath, "utf8"));
|
|
3546
3659
|
}
|
|
3547
3660
|
let teamConfig = null;
|
|
3548
3661
|
const teamPath = teamConfigPath();
|
|
3549
|
-
if (
|
|
3550
|
-
teamConfig = JSON.parse(await
|
|
3662
|
+
if (existsSync4(teamPath)) {
|
|
3663
|
+
teamConfig = JSON.parse(await readFile3(teamPath, "utf8"));
|
|
3551
3664
|
}
|
|
3552
3665
|
return resolveLayeredConfig({
|
|
3553
3666
|
repoConfig,
|
|
3554
3667
|
adapterDefaults: layout.defaultConfig(repoRoot),
|
|
3555
3668
|
teamConfig,
|
|
3556
3669
|
teamConfigPath: teamPath,
|
|
3557
|
-
repoConfigPath:
|
|
3670
|
+
repoConfigPath: existsSync4(configPath) ? configPath : void 0
|
|
3558
3671
|
});
|
|
3559
3672
|
}
|
|
3560
3673
|
async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
|
|
@@ -3569,6 +3682,7 @@ var init_config_io = __esm({
|
|
|
3569
3682
|
init_approval_state_mutation();
|
|
3570
3683
|
init_config();
|
|
3571
3684
|
init_config_layers();
|
|
3685
|
+
init_repo_config_trust();
|
|
3572
3686
|
}
|
|
3573
3687
|
});
|
|
3574
3688
|
|
|
@@ -4230,9 +4344,9 @@ init_claude();
|
|
|
4230
4344
|
init_approval();
|
|
4231
4345
|
init_approval_replay();
|
|
4232
4346
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
4233
|
-
import { existsSync as
|
|
4234
|
-
import { mkdir as
|
|
4235
|
-
import
|
|
4347
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
4348
|
+
import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
|
|
4349
|
+
import path63 from "node:path";
|
|
4236
4350
|
|
|
4237
4351
|
// src/core/approval-service.ts
|
|
4238
4352
|
init_config_io();
|
|
@@ -4242,47 +4356,35 @@ init_approval_replay();
|
|
|
4242
4356
|
// src/core/approval-token.ts
|
|
4243
4357
|
init_config();
|
|
4244
4358
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4245
|
-
import { existsSync as
|
|
4246
|
-
import { mkdir as
|
|
4247
|
-
import
|
|
4248
|
-
function base64UrlEncode(value) {
|
|
4249
|
-
return Buffer.from(value, "utf8").toString("base64url");
|
|
4250
|
-
}
|
|
4359
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4360
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
4361
|
+
import path13 from "node:path";
|
|
4251
4362
|
function base64UrlDecode(value) {
|
|
4252
4363
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
4253
4364
|
}
|
|
4254
4365
|
function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
|
|
4255
|
-
return
|
|
4366
|
+
return path13.join(controlPlaneDir, "approval-signing.key");
|
|
4256
4367
|
}
|
|
4257
4368
|
async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
|
|
4258
4369
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
4259
|
-
if (
|
|
4260
|
-
return
|
|
4370
|
+
if (existsSync5(keyPath)) {
|
|
4371
|
+
return readFile4(keyPath);
|
|
4261
4372
|
}
|
|
4262
|
-
await
|
|
4373
|
+
await mkdir4(controlPlaneDir, { recursive: true });
|
|
4263
4374
|
const key = randomBytes(32);
|
|
4264
4375
|
await writeFile2(keyPath, key, { mode: 384 });
|
|
4265
4376
|
return key;
|
|
4266
4377
|
}
|
|
4267
|
-
function signPayload(payload, key) {
|
|
4268
|
-
const body = base64UrlEncode(JSON.stringify(payload));
|
|
4269
|
-
const signature = createHmac("sha256", key).update(body).digest("base64url");
|
|
4270
|
-
return `${body}.${signature}`;
|
|
4271
|
-
}
|
|
4272
|
-
async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
|
|
4273
|
-
const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
|
|
4274
|
-
return signPayload(payload, key);
|
|
4275
|
-
}
|
|
4276
4378
|
async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
|
|
4277
4379
|
const [body, signature] = token.split(".");
|
|
4278
4380
|
if (!body || !signature) {
|
|
4279
4381
|
return null;
|
|
4280
4382
|
}
|
|
4281
4383
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
4282
|
-
if (!
|
|
4384
|
+
if (!existsSync5(keyPath)) {
|
|
4283
4385
|
return null;
|
|
4284
4386
|
}
|
|
4285
|
-
const key = await
|
|
4387
|
+
const key = await readFile4(keyPath);
|
|
4286
4388
|
const expected = createHmac("sha256", key).update(body).digest("base64url");
|
|
4287
4389
|
const actualBuffer = Buffer.from(signature);
|
|
4288
4390
|
const expectedBuffer = Buffer.from(expected);
|
|
@@ -4489,11 +4591,11 @@ init_approval_v3();
|
|
|
4489
4591
|
|
|
4490
4592
|
// src/core/capability/boundary-attestation-sign.ts
|
|
4491
4593
|
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
4492
|
-
import { access, readFile as
|
|
4594
|
+
import { access, readFile as readFile5 } from "node:fs/promises";
|
|
4493
4595
|
init_fingerprint2();
|
|
4494
4596
|
|
|
4495
4597
|
// src/core/capability/attestation.ts
|
|
4496
|
-
import
|
|
4598
|
+
import path14 from "node:path";
|
|
4497
4599
|
var BOUNDARY_ATTESTATION_VERSION = 1;
|
|
4498
4600
|
var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
|
|
4499
4601
|
var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
|
|
@@ -4561,7 +4663,7 @@ function validateContainedExecutionAttestation(value) {
|
|
|
4561
4663
|
const record = value;
|
|
4562
4664
|
const dockerSubstrate = record.dockerSubstrate;
|
|
4563
4665
|
const dockerConfiguration = record.dockerConfiguration;
|
|
4564
|
-
if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !
|
|
4666
|
+
if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !path14.isAbsolute(dockerSubstrate.binaryPath) || /[\0\n\r]/.test(dockerSubstrate.binaryPath) || typeof dockerSubstrate.binarySha256 !== "string" || !/^[a-f0-9]{64}$/.test(dockerSubstrate.binarySha256) || typeof dockerSubstrate.endpoint !== "string" || !dockerSubstrate.endpoint.startsWith("unix:///") || !path14.isAbsolute(dockerSubstrate.endpoint.slice("unix://".length)) || /[\0\n\r]/.test(dockerSubstrate.endpoint) || typeof dockerSubstrate.daemonId !== "string" || !dockerSubstrate.daemonId || /[\0\n\r]/.test(dockerSubstrate.daemonId) || !isRecord(dockerConfiguration) || typeof dockerConfiguration.executable !== "string" || !path14.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path14.isAbsolute(dockerConfiguration.host.slice("unix://".length)) || /[\0\n\r]/.test(dockerConfiguration.host) || typeof record.user !== "string" || !/^\d+:\d+$/.test(record.user) || record.entrypoint !== "/bin/sh" || record.capDropAll !== true || record.noNewPrivileges !== true || record.logDriver !== "none" || record.proxyEnvironment !== "neutralized-empty" || typeof record.probedAt !== "string" || typeof record.expiresAt !== "string") {
|
|
4565
4667
|
return false;
|
|
4566
4668
|
}
|
|
4567
4669
|
const probedAt = Date.parse(record.probedAt);
|
|
@@ -4631,7 +4733,7 @@ async function verifySignedBoundaryAttestation(params) {
|
|
|
4631
4733
|
return record.attestation;
|
|
4632
4734
|
}
|
|
4633
4735
|
async function readSignedAttestationFile(filePath) {
|
|
4634
|
-
return JSON.parse(await
|
|
4736
|
+
return JSON.parse(await readFile5(filePath, "utf8"));
|
|
4635
4737
|
}
|
|
4636
4738
|
|
|
4637
4739
|
// src/core/capability/boundary-egress.ts
|
|
@@ -4800,13 +4902,13 @@ async function runWithBoundaryRunnable(target, params) {
|
|
|
4800
4902
|
}
|
|
4801
4903
|
|
|
4802
4904
|
// src/core/capability/boundary-session.ts
|
|
4803
|
-
import
|
|
4905
|
+
import path25 from "node:path";
|
|
4804
4906
|
|
|
4805
4907
|
// src/services/egress-service.ts
|
|
4806
|
-
import { existsSync as
|
|
4807
|
-
import { mkdir as
|
|
4908
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
4909
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
|
|
4808
4910
|
import net from "node:net";
|
|
4809
|
-
import
|
|
4911
|
+
import path15 from "node:path";
|
|
4810
4912
|
init_config_io();
|
|
4811
4913
|
init_config();
|
|
4812
4914
|
|
|
@@ -4819,16 +4921,16 @@ function egressStatePaths(repoRoot, config) {
|
|
|
4819
4921
|
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
4820
4922
|
return {
|
|
4821
4923
|
stateDir,
|
|
4822
|
-
pidPath:
|
|
4823
|
-
statusPath:
|
|
4924
|
+
pidPath: path15.join(stateDir, "egress-proxy.pid"),
|
|
4925
|
+
statusPath: path15.join(stateDir, "egress-proxy.json")
|
|
4824
4926
|
};
|
|
4825
4927
|
}
|
|
4826
4928
|
async function readStatusFile(statusPath) {
|
|
4827
|
-
if (!
|
|
4929
|
+
if (!existsSync6(statusPath)) {
|
|
4828
4930
|
return null;
|
|
4829
4931
|
}
|
|
4830
4932
|
try {
|
|
4831
|
-
const raw = JSON.parse(await
|
|
4933
|
+
const raw = JSON.parse(await readFile6(statusPath, "utf8"));
|
|
4832
4934
|
if (typeof raw.pid !== "number") {
|
|
4833
4935
|
return null;
|
|
4834
4936
|
}
|
|
@@ -4846,9 +4948,9 @@ async function readStatusFile(statusPath) {
|
|
|
4846
4948
|
async function isPortOpen(host, port) {
|
|
4847
4949
|
return new Promise((resolve) => {
|
|
4848
4950
|
const socket = net.createConnection({ host, port });
|
|
4849
|
-
const finish = (
|
|
4951
|
+
const finish = (open7) => {
|
|
4850
4952
|
socket.destroy();
|
|
4851
|
-
resolve(
|
|
4953
|
+
resolve(open7);
|
|
4852
4954
|
};
|
|
4853
4955
|
socket.setTimeout(300);
|
|
4854
4956
|
socket.on("connect", () => finish(true));
|
|
@@ -4859,7 +4961,7 @@ async function isPortOpen(host, port) {
|
|
|
4859
4961
|
async function resolveLiveEgressStatus(repoRoot, config) {
|
|
4860
4962
|
const { statusPath } = egressStatePaths(repoRoot, config);
|
|
4861
4963
|
const statusCandidates = [statusPath];
|
|
4862
|
-
const controlPlaneStatus =
|
|
4964
|
+
const controlPlaneStatus = path15.join(configuredControlPlaneDir(config), "egress-proxy.json");
|
|
4863
4965
|
if (!statusCandidates.includes(controlPlaneStatus)) {
|
|
4864
4966
|
statusCandidates.push(controlPlaneStatus);
|
|
4865
4967
|
}
|
|
@@ -4885,7 +4987,7 @@ function isProcessAlive(pid) {
|
|
|
4885
4987
|
}
|
|
4886
4988
|
}
|
|
4887
4989
|
async function egressStatus(options = {}) {
|
|
4888
|
-
const repoRoot =
|
|
4990
|
+
const repoRoot = path15.resolve(options.targetDir ?? process.cwd());
|
|
4889
4991
|
const config = await loadConfigFile(repoRoot);
|
|
4890
4992
|
const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
|
|
4891
4993
|
const ownedRunning = Boolean(status);
|
|
@@ -4916,7 +5018,7 @@ init_config();
|
|
|
4916
5018
|
import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
|
|
4917
5019
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
4918
5020
|
import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
|
|
4919
|
-
import
|
|
5021
|
+
import path20 from "node:path";
|
|
4920
5022
|
init_fingerprint2();
|
|
4921
5023
|
init_path_utils();
|
|
4922
5024
|
|
|
@@ -5095,7 +5197,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
|
|
|
5095
5197
|
}
|
|
5096
5198
|
|
|
5097
5199
|
// src/core/contained-execution/docker-policy.ts
|
|
5098
|
-
import
|
|
5200
|
+
import path16 from "node:path";
|
|
5099
5201
|
|
|
5100
5202
|
// src/core/contained-execution/policy.ts
|
|
5101
5203
|
var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
|
|
@@ -5224,7 +5326,7 @@ var PROXY_ENV_NAMES = [
|
|
|
5224
5326
|
var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
5225
5327
|
var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
|
|
5226
5328
|
function assertSafeDockerPath(value, code) {
|
|
5227
|
-
if (!
|
|
5329
|
+
if (!path16.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
|
|
5228
5330
|
throw new ContainedExecutionFailureError(code);
|
|
5229
5331
|
}
|
|
5230
5332
|
}
|
|
@@ -5303,11 +5405,11 @@ init_path_utils();
|
|
|
5303
5405
|
import { createHash as createHash7 } from "node:crypto";
|
|
5304
5406
|
import { constants as fsConstants } from "node:fs";
|
|
5305
5407
|
import {
|
|
5306
|
-
chmod as
|
|
5408
|
+
chmod as chmod3,
|
|
5307
5409
|
lstat as lstat3,
|
|
5308
|
-
mkdir as
|
|
5410
|
+
mkdir as mkdir6,
|
|
5309
5411
|
mkdtemp,
|
|
5310
|
-
open as
|
|
5412
|
+
open as open4,
|
|
5311
5413
|
opendir,
|
|
5312
5414
|
readlink as readlink2,
|
|
5313
5415
|
realpath,
|
|
@@ -5315,41 +5417,41 @@ import {
|
|
|
5315
5417
|
symlink
|
|
5316
5418
|
} from "node:fs/promises";
|
|
5317
5419
|
import os from "node:os";
|
|
5318
|
-
import
|
|
5420
|
+
import path19 from "node:path";
|
|
5319
5421
|
|
|
5320
5422
|
// src/core/transactional/file-tree.ts
|
|
5321
5423
|
import { createHash as createHash6 } from "node:crypto";
|
|
5322
5424
|
import { lstat as lstat2, readdir } from "node:fs/promises";
|
|
5323
|
-
import
|
|
5425
|
+
import path18 from "node:path";
|
|
5324
5426
|
|
|
5325
5427
|
// src/core/transactional/file-tree-path.ts
|
|
5326
5428
|
init_path_utils();
|
|
5327
|
-
import
|
|
5429
|
+
import path17 from "node:path";
|
|
5328
5430
|
var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
|
|
5329
5431
|
function validateRelativePath(relativePath) {
|
|
5330
5432
|
if (!relativePath || relativePath.includes("\0")) {
|
|
5331
5433
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5332
5434
|
}
|
|
5333
|
-
if (
|
|
5435
|
+
if (path17.isAbsolute(relativePath)) {
|
|
5334
5436
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5335
5437
|
}
|
|
5336
5438
|
if (isPathOutsideRoot(relativePath)) {
|
|
5337
5439
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5338
5440
|
}
|
|
5339
|
-
const normalized =
|
|
5340
|
-
if (normalized === ".." || normalized.startsWith(`..${
|
|
5441
|
+
const normalized = path17.normalize(relativePath);
|
|
5442
|
+
if (normalized === ".." || normalized.startsWith(`..${path17.sep}`)) {
|
|
5341
5443
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5342
5444
|
}
|
|
5343
5445
|
}
|
|
5344
5446
|
function joinRelativePath(root, relativePath) {
|
|
5345
5447
|
validateRelativePath(relativePath);
|
|
5346
|
-
return
|
|
5448
|
+
return path17.join(canonicalPath(root), relativePath);
|
|
5347
5449
|
}
|
|
5348
5450
|
function isRootGitMetadataPath(relativePath) {
|
|
5349
|
-
return relativePath === ".git" || relativePath.startsWith(`.git${
|
|
5451
|
+
return relativePath === ".git" || relativePath.startsWith(`.git${path17.sep}`);
|
|
5350
5452
|
}
|
|
5351
5453
|
function isNestedGitPath(relativePath) {
|
|
5352
|
-
const segments = relativePath.split(
|
|
5454
|
+
const segments = relativePath.split(path17.sep).filter(Boolean);
|
|
5353
5455
|
if (segments.length === 0) {
|
|
5354
5456
|
return false;
|
|
5355
5457
|
}
|
|
@@ -5516,7 +5618,7 @@ async function readPresentNode(absolutePath, counters) {
|
|
|
5516
5618
|
async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
|
|
5517
5619
|
assertWithinDeadline(deadlineMs);
|
|
5518
5620
|
assertWithinQuotas(counters, quotas);
|
|
5519
|
-
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) :
|
|
5621
|
+
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path18.resolve(resourceRoot);
|
|
5520
5622
|
const dirInfo = await lstat2(absoluteDir);
|
|
5521
5623
|
if (!dirInfo.isDirectory()) {
|
|
5522
5624
|
throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
|
|
@@ -5542,7 +5644,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
5542
5644
|
const names = await readdir(absoluteDir);
|
|
5543
5645
|
for (const name of names) {
|
|
5544
5646
|
assertWithinDeadline(deadlineMs);
|
|
5545
|
-
const childRelative = relativeDir ?
|
|
5647
|
+
const childRelative = relativeDir ? path18.join(relativeDir, name) : name;
|
|
5546
5648
|
validateRelativePath(childRelative);
|
|
5547
5649
|
if (isNestedGitPath(childRelative)) {
|
|
5548
5650
|
throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
|
|
@@ -5550,7 +5652,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
5550
5652
|
if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
|
|
5551
5653
|
continue;
|
|
5552
5654
|
}
|
|
5553
|
-
const childAbsolute =
|
|
5655
|
+
const childAbsolute = path18.join(absoluteDir, name);
|
|
5554
5656
|
const childInfo = await lstat2(childAbsolute);
|
|
5555
5657
|
if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
|
|
5556
5658
|
await walkDirectory(
|
|
@@ -5661,16 +5763,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
|
|
|
5661
5763
|
);
|
|
5662
5764
|
}
|
|
5663
5765
|
var productionDependencies = {
|
|
5664
|
-
makeTempRoot: () => mkdtemp(
|
|
5766
|
+
makeTempRoot: () => mkdtemp(path19.join(os.tmpdir(), "belay-contained-mirror-")),
|
|
5665
5767
|
removeRoot: (root) => rm(root, { recursive: true, force: true }),
|
|
5666
5768
|
now: () => Date.now()
|
|
5667
5769
|
};
|
|
5668
5770
|
function isAtOrWithin(root, target) {
|
|
5669
|
-
const relative =
|
|
5670
|
-
return relative === "" || !
|
|
5771
|
+
const relative = path19.relative(root, target);
|
|
5772
|
+
return relative === "" || !path19.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path19.sep}`);
|
|
5671
5773
|
}
|
|
5672
5774
|
function isGitMetadataRelativePath(relativePath) {
|
|
5673
|
-
return relativePath.split(
|
|
5775
|
+
return relativePath.split(path19.sep).some((segment) => segment.toLowerCase() === ".git");
|
|
5674
5776
|
}
|
|
5675
5777
|
function identityFromStats(stats) {
|
|
5676
5778
|
return {
|
|
@@ -5752,7 +5854,7 @@ async function readStableFile(absolutePath, context) {
|
|
|
5752
5854
|
if (before.nlink > 1n) {
|
|
5753
5855
|
throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
|
|
5754
5856
|
}
|
|
5755
|
-
const source = await
|
|
5857
|
+
const source = await open4(absolutePath, safeReadFlags());
|
|
5756
5858
|
try {
|
|
5757
5859
|
const opened = await source.stat({ bigint: true });
|
|
5758
5860
|
assertRegularSingleLinkBigInt(opened);
|
|
@@ -5807,7 +5909,7 @@ function addMetadataRoots(context, directoryPath) {
|
|
|
5807
5909
|
}
|
|
5808
5910
|
}
|
|
5809
5911
|
function pathMatchesRoots(absolutePath, roots) {
|
|
5810
|
-
const lexical =
|
|
5912
|
+
const lexical = path19.resolve(absolutePath);
|
|
5811
5913
|
const canonical = canonicalPath(absolutePath);
|
|
5812
5914
|
for (const root of roots) {
|
|
5813
5915
|
if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
|
|
@@ -5817,7 +5919,7 @@ function pathMatchesRoots(absolutePath, roots) {
|
|
|
5817
5919
|
return false;
|
|
5818
5920
|
}
|
|
5819
5921
|
function pathLexicallyMatchesRoots(absolutePath, roots) {
|
|
5820
|
-
const lexical =
|
|
5922
|
+
const lexical = path19.resolve(absolutePath);
|
|
5821
5923
|
for (const root of roots) {
|
|
5822
5924
|
if (isAtOrWithin(root, lexical)) {
|
|
5823
5925
|
return true;
|
|
@@ -5838,11 +5940,11 @@ async function readSafeSymlink(absolutePath, context) {
|
|
|
5838
5940
|
}
|
|
5839
5941
|
const identity = identityFromStats(before);
|
|
5840
5942
|
const target = await readlink2(absolutePath);
|
|
5841
|
-
if (
|
|
5943
|
+
if (path19.isAbsolute(target)) {
|
|
5842
5944
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5843
5945
|
}
|
|
5844
|
-
const lexicalTarget =
|
|
5845
|
-
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(
|
|
5946
|
+
const lexicalTarget = path19.resolve(path19.dirname(absolutePath), target);
|
|
5947
|
+
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path19.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
|
|
5846
5948
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5847
5949
|
}
|
|
5848
5950
|
let resolvedTarget;
|
|
@@ -5878,7 +5980,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5878
5980
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5879
5981
|
}
|
|
5880
5982
|
const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
|
|
5881
|
-
const directory = await
|
|
5983
|
+
const directory = await open4(absoluteDirectory, directoryFlags);
|
|
5882
5984
|
try {
|
|
5883
5985
|
const opened = await directory.stat({ bigint: true });
|
|
5884
5986
|
if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
|
|
@@ -5887,7 +5989,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5887
5989
|
const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
|
|
5888
5990
|
for await (const directoryEntry of entriesDirectory) {
|
|
5889
5991
|
assertDeadline(context.deadlineMs, context.now);
|
|
5890
|
-
const relativePath = relativeDirectory ?
|
|
5992
|
+
const relativePath = relativeDirectory ? path19.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
|
|
5891
5993
|
const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
|
|
5892
5994
|
const info = await lstat3(absolutePath);
|
|
5893
5995
|
if (info.isSymbolicLink()) {
|
|
@@ -5971,7 +6073,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5971
6073
|
}
|
|
5972
6074
|
assertEntryIdentity(entry, identityFromStats(before));
|
|
5973
6075
|
await context.beforeCopyOpen?.(sourcePath);
|
|
5974
|
-
const source = await
|
|
6076
|
+
const source = await open4(sourcePath, safeReadFlags());
|
|
5975
6077
|
let destination;
|
|
5976
6078
|
try {
|
|
5977
6079
|
const opened = await source.stat({ bigint: true });
|
|
@@ -5981,8 +6083,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5981
6083
|
if (entry.node.kind !== "file") {
|
|
5982
6084
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5983
6085
|
}
|
|
5984
|
-
await
|
|
5985
|
-
destination = await
|
|
6086
|
+
await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
6087
|
+
destination = await open4(
|
|
5986
6088
|
destinationPath,
|
|
5987
6089
|
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
|
|
5988
6090
|
safePermissionMode(opened.mode)
|
|
@@ -6024,7 +6126,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
6024
6126
|
if (entry.node.kind !== "file") {
|
|
6025
6127
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
6026
6128
|
}
|
|
6027
|
-
await
|
|
6129
|
+
await chmod3(destinationPath, safePermissionMode(entry.node.mode));
|
|
6028
6130
|
}
|
|
6029
6131
|
async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
|
|
6030
6132
|
const directoryEntries = [];
|
|
@@ -6033,7 +6135,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
6033
6135
|
const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
|
|
6034
6136
|
const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
|
|
6035
6137
|
if (entry.node.kind === "directory") {
|
|
6036
|
-
await
|
|
6138
|
+
await mkdir6(destinationPath, { recursive: true, mode: 448 });
|
|
6037
6139
|
directoryEntries.push(entry);
|
|
6038
6140
|
continue;
|
|
6039
6141
|
}
|
|
@@ -6045,7 +6147,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
6045
6147
|
if (current !== entry.node.target) {
|
|
6046
6148
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
6047
6149
|
}
|
|
6048
|
-
await
|
|
6150
|
+
await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
6049
6151
|
await symlink(entry.node.target, destinationPath);
|
|
6050
6152
|
continue;
|
|
6051
6153
|
}
|
|
@@ -6055,12 +6157,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
6055
6157
|
if (entry.node.kind !== "directory") {
|
|
6056
6158
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
6057
6159
|
}
|
|
6058
|
-
await
|
|
6160
|
+
await chmod3(
|
|
6059
6161
|
joinRelativePath(destinationRoot, entry.relativePath),
|
|
6060
6162
|
safePermissionMode(entry.node.mode)
|
|
6061
6163
|
);
|
|
6062
6164
|
}
|
|
6063
|
-
await
|
|
6165
|
+
await chmod3(destinationRoot, 448);
|
|
6064
6166
|
}
|
|
6065
6167
|
async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
|
|
6066
6168
|
const [after, copied] = await Promise.all([
|
|
@@ -6105,7 +6207,7 @@ async function cleanupOwnedRoot(root, dependencies) {
|
|
|
6105
6207
|
}
|
|
6106
6208
|
async function prepareWithDependencies(options, dependencies) {
|
|
6107
6209
|
validateOptions(options);
|
|
6108
|
-
const guestWorkspacePath =
|
|
6210
|
+
const guestWorkspacePath = path19.resolve(options.sourceRoot);
|
|
6109
6211
|
const sourceRoot = canonicalPath(options.sourceRoot);
|
|
6110
6212
|
const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
|
|
6111
6213
|
if (pathMatchesRoots(sourceRoot, protectedRoots)) {
|
|
@@ -6113,7 +6215,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
6113
6215
|
}
|
|
6114
6216
|
const guestRoot = await dependencies.makeTempRoot();
|
|
6115
6217
|
try {
|
|
6116
|
-
await
|
|
6218
|
+
await chmod3(guestRoot, 448);
|
|
6117
6219
|
const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
|
|
6118
6220
|
const before = await buildSafeMirrorSnapshot(
|
|
6119
6221
|
sourceRoot,
|
|
@@ -6141,7 +6243,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
6141
6243
|
dependencies.now,
|
|
6142
6244
|
before
|
|
6143
6245
|
);
|
|
6144
|
-
await
|
|
6246
|
+
await chmod3(guestRoot, 448);
|
|
6145
6247
|
const lease = {
|
|
6146
6248
|
sourceRoot,
|
|
6147
6249
|
hostMirrorRoot: guestRoot,
|
|
@@ -6275,14 +6377,14 @@ async function digestFile(file) {
|
|
|
6275
6377
|
return hash.digest("hex");
|
|
6276
6378
|
}
|
|
6277
6379
|
async function resolveConfiguredDockerSubstrate(params) {
|
|
6278
|
-
if (!
|
|
6380
|
+
if (!path20.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
|
|
6279
6381
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
|
|
6280
6382
|
}
|
|
6281
6383
|
if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
|
|
6282
6384
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
6283
6385
|
}
|
|
6284
6386
|
const configuredSocket = params.host.slice("unix://".length);
|
|
6285
|
-
if (!
|
|
6387
|
+
if (!path20.isAbsolute(configuredSocket)) {
|
|
6286
6388
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
6287
6389
|
}
|
|
6288
6390
|
let binaryPath;
|
|
@@ -6543,7 +6645,7 @@ async function validatedGuestCwd(params) {
|
|
|
6543
6645
|
const requiredExclusions = [
|
|
6544
6646
|
...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
|
|
6545
6647
|
];
|
|
6546
|
-
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !==
|
|
6648
|
+
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path20.resolve(params.repoRoot) || !path20.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
|
|
6547
6649
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
6548
6650
|
const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
|
|
6549
6651
|
const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
|
|
@@ -6555,10 +6657,10 @@ async function validatedGuestCwd(params) {
|
|
|
6555
6657
|
protectedRoots: requiredExclusions
|
|
6556
6658
|
}))
|
|
6557
6659
|
throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
|
|
6558
|
-
const relative =
|
|
6660
|
+
const relative = path20.relative(params.mirror.guestWorkspacePath, path20.resolve(params.guestCwd));
|
|
6559
6661
|
let resolvedCwd;
|
|
6560
6662
|
try {
|
|
6561
|
-
resolvedCwd = await realpath2(
|
|
6663
|
+
resolvedCwd = await realpath2(path20.join(resolvedRoot, relative));
|
|
6562
6664
|
} catch {
|
|
6563
6665
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
6564
6666
|
}
|
|
@@ -6566,7 +6668,7 @@ async function validatedGuestCwd(params) {
|
|
|
6566
6668
|
if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
|
|
6567
6669
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
6568
6670
|
}
|
|
6569
|
-
return
|
|
6671
|
+
return path20.join(params.mirror.guestWorkspacePath, path20.relative(resolvedRoot, resolvedCwd));
|
|
6570
6672
|
}
|
|
6571
6673
|
function exactKeys(value, allowed) {
|
|
6572
6674
|
const names = new Set(allowed);
|
|
@@ -6793,19 +6895,19 @@ async function executeContainedDocker(params) {
|
|
|
6793
6895
|
init_path_utils();
|
|
6794
6896
|
import { spawn as spawn3 } from "node:child_process";
|
|
6795
6897
|
import os3 from "node:os";
|
|
6796
|
-
import
|
|
6898
|
+
import path22 from "node:path";
|
|
6797
6899
|
|
|
6798
6900
|
// src/core/transactional/apply-observed-changes.ts
|
|
6799
|
-
import { copyFile, lstat as lstat5, mkdir as
|
|
6901
|
+
import { copyFile, lstat as lstat5, mkdir as mkdir7, mkdtemp as mkdtemp3, readlink as readlink3, rm as rm3, rmdir, symlink as symlink2 } from "node:fs/promises";
|
|
6800
6902
|
import os2 from "node:os";
|
|
6801
|
-
import
|
|
6903
|
+
import path21 from "node:path";
|
|
6802
6904
|
var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
|
|
6803
6905
|
var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
|
|
6804
6906
|
var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
|
|
6805
6907
|
async function chmodSafe(target, mode) {
|
|
6806
6908
|
try {
|
|
6807
|
-
const { chmod:
|
|
6808
|
-
await
|
|
6909
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
6910
|
+
await chmod5(target, mode & 511);
|
|
6809
6911
|
} catch {
|
|
6810
6912
|
}
|
|
6811
6913
|
}
|
|
@@ -6839,13 +6941,13 @@ async function copyPathPreservingType(source, target) {
|
|
|
6839
6941
|
}
|
|
6840
6942
|
}
|
|
6841
6943
|
await removePathIfExists(target);
|
|
6842
|
-
await
|
|
6944
|
+
await mkdir7(path21.dirname(target), { recursive: true });
|
|
6843
6945
|
if (info.isSymbolicLink()) {
|
|
6844
6946
|
await symlink2(await readlink3(source), target);
|
|
6845
6947
|
return;
|
|
6846
6948
|
}
|
|
6847
6949
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
6848
|
-
await
|
|
6950
|
+
await mkdir7(target, { recursive: true, mode: info.mode & 511 });
|
|
6849
6951
|
return;
|
|
6850
6952
|
}
|
|
6851
6953
|
if (!info.isFile()) {
|
|
@@ -6855,12 +6957,12 @@ async function copyPathPreservingType(source, target) {
|
|
|
6855
6957
|
await chmodSafe(target, info.mode);
|
|
6856
6958
|
}
|
|
6857
6959
|
async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
|
|
6858
|
-
const segments = relativePath.split(
|
|
6960
|
+
const segments = relativePath.split(path21.sep).filter(Boolean);
|
|
6859
6961
|
if (segments.length <= 1) {
|
|
6860
6962
|
return;
|
|
6861
6963
|
}
|
|
6862
6964
|
for (let index = 1; index < segments.length; index++) {
|
|
6863
|
-
const prefix = segments.slice(0, index).join(
|
|
6965
|
+
const prefix = segments.slice(0, index).join(path21.sep);
|
|
6864
6966
|
const absolute = joinRelativePath(targetRoot, prefix);
|
|
6865
6967
|
let info;
|
|
6866
6968
|
try {
|
|
@@ -6880,8 +6982,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
|
|
|
6880
6982
|
}
|
|
6881
6983
|
}
|
|
6882
6984
|
async function restorePathFromBackup(backupPath, target, rollbackRoot) {
|
|
6883
|
-
const stagingRoot = await mkdtemp3(
|
|
6884
|
-
const staged =
|
|
6985
|
+
const stagingRoot = await mkdtemp3(path21.join(rollbackRoot, "restore-"));
|
|
6986
|
+
const staged = path21.join(stagingRoot, "node");
|
|
6885
6987
|
try {
|
|
6886
6988
|
await copyPathPreservingType(backupPath, staged);
|
|
6887
6989
|
await copyPathPreservingType(staged, target);
|
|
@@ -6965,7 +7067,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
|
|
|
6965
7067
|
if (change.before.kind !== "directory") {
|
|
6966
7068
|
await removePathIfExists(target);
|
|
6967
7069
|
}
|
|
6968
|
-
await
|
|
7070
|
+
await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
|
|
6969
7071
|
await chmodSafe(target, change.after.mode);
|
|
6970
7072
|
return;
|
|
6971
7073
|
}
|
|
@@ -6984,7 +7086,7 @@ async function applyObservedChanges(params) {
|
|
|
6984
7086
|
await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
|
|
6985
7087
|
await assertTargetMatches(targetRoot, change);
|
|
6986
7088
|
}
|
|
6987
|
-
const backupRoot = await mkdtemp3(
|
|
7089
|
+
const backupRoot = await mkdtemp3(path21.join(os2.tmpdir(), "belay-tx-rollback-"));
|
|
6988
7090
|
const rollbackActions = [];
|
|
6989
7091
|
let mutationAttempted = false;
|
|
6990
7092
|
let resourceIdentityChanged = false;
|
|
@@ -7003,12 +7105,12 @@ async function applyObservedChanges(params) {
|
|
|
7003
7105
|
targetExists = false;
|
|
7004
7106
|
}
|
|
7005
7107
|
if (targetExists) {
|
|
7006
|
-
const backupPath =
|
|
7108
|
+
const backupPath = path21.join(
|
|
7007
7109
|
backupRoot,
|
|
7008
7110
|
String(rollbackActions.length),
|
|
7009
7111
|
change.relativePath
|
|
7010
7112
|
);
|
|
7011
|
-
await
|
|
7113
|
+
await mkdir7(path21.dirname(backupPath), { recursive: true });
|
|
7012
7114
|
await copyPathPreservingType(target, backupPath);
|
|
7013
7115
|
await assertParentChainSafe(targetRoot, change.relativePath);
|
|
7014
7116
|
rollbackActions.push({
|
|
@@ -7107,7 +7209,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
|
7107
7209
|
if (ignoreRoots.length === 0) {
|
|
7108
7210
|
return false;
|
|
7109
7211
|
}
|
|
7110
|
-
const absolutePath = canonicalPath(
|
|
7212
|
+
const absolutePath = canonicalPath(path22.join(repoRoot, relativePath));
|
|
7111
7213
|
return ignoreRoots.some(
|
|
7112
7214
|
(root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
|
|
7113
7215
|
);
|
|
@@ -7145,7 +7247,7 @@ async function isDirtyWorktree(repoRoot, options) {
|
|
|
7145
7247
|
}
|
|
7146
7248
|
async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
7147
7249
|
const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
|
|
7148
|
-
const worktreePath = await mkdtemp6(
|
|
7250
|
+
const worktreePath = await mkdtemp6(path22.join(os3.tmpdir(), "belay-tx-"));
|
|
7149
7251
|
await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
|
|
7150
7252
|
return {
|
|
7151
7253
|
worktreePath,
|
|
@@ -7164,14 +7266,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
|
7164
7266
|
}
|
|
7165
7267
|
function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
|
|
7166
7268
|
const resolvedCwd = canonicalPath(cwd);
|
|
7167
|
-
const relative =
|
|
7168
|
-
if (isPathOutsideRoot(relative) ||
|
|
7269
|
+
const relative = path22.relative(canonicalPath(repoRoot), resolvedCwd);
|
|
7270
|
+
if (isPathOutsideRoot(relative) || path22.isAbsolute(relative)) {
|
|
7169
7271
|
return worktreePath;
|
|
7170
7272
|
}
|
|
7171
7273
|
if (relative === "") {
|
|
7172
7274
|
return worktreePath;
|
|
7173
7275
|
}
|
|
7174
|
-
return
|
|
7276
|
+
return path22.join(worktreePath, relative);
|
|
7175
7277
|
}
|
|
7176
7278
|
function runShellCommand(command, cwd, timeoutMs) {
|
|
7177
7279
|
return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
|
|
@@ -7236,7 +7338,7 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
7236
7338
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
7237
7339
|
|
|
7238
7340
|
// src/core/capability/boundary-grant-materialize.ts
|
|
7239
|
-
import
|
|
7341
|
+
import path23 from "node:path";
|
|
7240
7342
|
|
|
7241
7343
|
// src/core/glob.ts
|
|
7242
7344
|
init_approval();
|
|
@@ -7396,7 +7498,7 @@ function grantMatchesRequest(grant, request) {
|
|
|
7396
7498
|
var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
|
|
7397
7499
|
var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
|
|
7398
7500
|
function resolveCapabilityPath(targetPath, cwd) {
|
|
7399
|
-
const joined =
|
|
7501
|
+
const joined = path23.isAbsolute(targetPath) ? targetPath : path23.join(cwd, targetPath);
|
|
7400
7502
|
return canonicalPath(joined);
|
|
7401
7503
|
}
|
|
7402
7504
|
function isPathWithinBoundaryMount(request) {
|
|
@@ -7471,7 +7573,7 @@ function materializeContainerBoundaryGrant(request, params) {
|
|
|
7471
7573
|
|
|
7472
7574
|
// src/core/capability/boundary-workspace-mount.ts
|
|
7473
7575
|
init_path_utils();
|
|
7474
|
-
import
|
|
7576
|
+
import path24 from "node:path";
|
|
7475
7577
|
var HOST_PATH_ENV_VARS = [
|
|
7476
7578
|
"BELAY_EGRESS_REPO_ROOT",
|
|
7477
7579
|
"BELAY_JUDGE_BROKER_REPO_ROOT",
|
|
@@ -7496,23 +7598,23 @@ function validateWorkspaceMount(mount) {
|
|
|
7496
7598
|
if (mount.cwdRelative.includes("\0")) {
|
|
7497
7599
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
7498
7600
|
}
|
|
7499
|
-
const normalizedRelative =
|
|
7601
|
+
const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
7500
7602
|
if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
|
|
7501
7603
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
7502
7604
|
}
|
|
7503
|
-
if (
|
|
7605
|
+
if (path24.posix.isAbsolute(normalizedRelative)) {
|
|
7504
7606
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
7505
7607
|
}
|
|
7506
7608
|
}
|
|
7507
7609
|
function resolveGuestWorkdir(mount) {
|
|
7508
7610
|
validateWorkspaceMount(mount);
|
|
7509
7611
|
const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
|
|
7510
|
-
const normalizedRelative =
|
|
7612
|
+
const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
7511
7613
|
if (normalizedRelative === "." || normalizedRelative === "") {
|
|
7512
7614
|
return guestTargetRoot;
|
|
7513
7615
|
}
|
|
7514
7616
|
const segments = normalizedRelative.split("/").filter(Boolean);
|
|
7515
|
-
return
|
|
7617
|
+
return path24.posix.join(guestTargetRoot, ...segments);
|
|
7516
7618
|
}
|
|
7517
7619
|
function buildWorkspaceMountSpec(mount) {
|
|
7518
7620
|
validateWorkspaceMount(mount);
|
|
@@ -7803,7 +7905,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
|
|
|
7803
7905
|
}
|
|
7804
7906
|
function boundaryAttestationPath(repoRoot, config) {
|
|
7805
7907
|
const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
|
|
7806
|
-
return
|
|
7908
|
+
return path25.isAbsolute(rel) ? rel : path25.join(repoRoot, rel);
|
|
7807
7909
|
}
|
|
7808
7910
|
async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
|
|
7809
7911
|
try {
|
|
@@ -7908,7 +8010,7 @@ init_capability_request_hash();
|
|
|
7908
8010
|
|
|
7909
8011
|
// src/core/capability/gate-policy-shadow.ts
|
|
7910
8012
|
init_config();
|
|
7911
|
-
import
|
|
8013
|
+
import path30 from "node:path";
|
|
7912
8014
|
|
|
7913
8015
|
// src/core/verdict/judge-outbound.ts
|
|
7914
8016
|
init_scrub();
|
|
@@ -7918,7 +8020,7 @@ init_path_utils();
|
|
|
7918
8020
|
|
|
7919
8021
|
// src/core/verdict/containment.ts
|
|
7920
8022
|
init_git_resource_identity();
|
|
7921
|
-
import
|
|
8023
|
+
import path26 from "node:path";
|
|
7922
8024
|
init_path_utils();
|
|
7923
8025
|
|
|
7924
8026
|
// src/core/verdict/persistent-paths.ts
|
|
@@ -7938,7 +8040,7 @@ function expandHome(token) {
|
|
|
7938
8040
|
if (!home) {
|
|
7939
8041
|
return token;
|
|
7940
8042
|
}
|
|
7941
|
-
return token === "~" ? home :
|
|
8043
|
+
return token === "~" ? home : path26.join(home, token.slice(2));
|
|
7942
8044
|
}
|
|
7943
8045
|
return token;
|
|
7944
8046
|
}
|
|
@@ -7950,10 +8052,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
|
|
|
7950
8052
|
return null;
|
|
7951
8053
|
}
|
|
7952
8054
|
const expanded = expandHome(token);
|
|
7953
|
-
if (
|
|
8055
|
+
if (path26.isAbsolute(expanded)) {
|
|
7954
8056
|
return canonicalPath(expanded);
|
|
7955
8057
|
}
|
|
7956
|
-
return canonicalPath(
|
|
8058
|
+
return canonicalPath(path26.resolve(trustedCwd, expanded));
|
|
7957
8059
|
}
|
|
7958
8060
|
function isGitPath(resolvedPath, repoRoot) {
|
|
7959
8061
|
if (isGitMetadataPath(resolvedPath, repoRoot)) {
|
|
@@ -8070,20 +8172,20 @@ function parseTier1Json(raw) {
|
|
|
8070
8172
|
init_judge_runtime_config();
|
|
8071
8173
|
|
|
8072
8174
|
// src/core/verdict/judge-session-kill-switch.ts
|
|
8073
|
-
import { existsSync as
|
|
8074
|
-
import { mkdir as
|
|
8075
|
-
import
|
|
8175
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
8176
|
+
import { mkdir as mkdir8, readFile as readFile7, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
|
|
8177
|
+
import path27 from "node:path";
|
|
8076
8178
|
var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
|
|
8077
8179
|
function judgeSessionKillSwitchPath(stateDir) {
|
|
8078
|
-
return
|
|
8180
|
+
return path27.join(stateDir, JUDGE_SESSION_KILL_FILE);
|
|
8079
8181
|
}
|
|
8080
8182
|
async function readJudgeSessionKillSwitch(stateDir) {
|
|
8081
8183
|
const filePath = judgeSessionKillSwitchPath(stateDir);
|
|
8082
|
-
if (!
|
|
8184
|
+
if (!existsSync7(filePath)) {
|
|
8083
8185
|
return null;
|
|
8084
8186
|
}
|
|
8085
8187
|
try {
|
|
8086
|
-
const raw = JSON.parse(await
|
|
8188
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
8087
8189
|
return raw.triggered === true ? raw : null;
|
|
8088
8190
|
} catch {
|
|
8089
8191
|
return null;
|
|
@@ -8094,7 +8196,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
|
|
|
8094
8196
|
return record?.triggered === true;
|
|
8095
8197
|
}
|
|
8096
8198
|
async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
|
|
8097
|
-
await
|
|
8199
|
+
await mkdir8(stateDir, { recursive: true, mode: 448 });
|
|
8098
8200
|
const record = {
|
|
8099
8201
|
triggered: true,
|
|
8100
8202
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8255,10 +8357,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
|
|
|
8255
8357
|
|
|
8256
8358
|
// src/core/verdict/judge-broker-service.ts
|
|
8257
8359
|
import { spawn as spawn6 } from "node:child_process";
|
|
8258
|
-
import { existsSync as
|
|
8259
|
-
import { unlink as
|
|
8360
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
8361
|
+
import { unlink as unlink6 } from "node:fs/promises";
|
|
8260
8362
|
import net2 from "node:net";
|
|
8261
|
-
import
|
|
8363
|
+
import path28 from "node:path";
|
|
8262
8364
|
import { fileURLToPath } from "node:url";
|
|
8263
8365
|
|
|
8264
8366
|
// src/core/verdict/judge-cli.ts
|
|
@@ -8804,10 +8906,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
|
|
|
8804
8906
|
function judgeBrokerPaths(stateDir) {
|
|
8805
8907
|
return {
|
|
8806
8908
|
stateDir,
|
|
8807
|
-
socketPath:
|
|
8808
|
-
statusPath:
|
|
8809
|
-
pidPath:
|
|
8810
|
-
sessionConfigPath:
|
|
8909
|
+
socketPath: path28.join(stateDir, JUDGE_BROKER_SOCKET),
|
|
8910
|
+
statusPath: path28.join(stateDir, JUDGE_BROKER_STATUS),
|
|
8911
|
+
pidPath: path28.join(stateDir, JUDGE_BROKER_PID),
|
|
8912
|
+
sessionConfigPath: path28.join(stateDir, JUDGE_BROKER_SESSION)
|
|
8811
8913
|
};
|
|
8812
8914
|
}
|
|
8813
8915
|
function daemonScriptPath() {
|
|
@@ -8825,12 +8927,12 @@ function useInProcessBroker(env = process.env) {
|
|
|
8825
8927
|
return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
|
|
8826
8928
|
}
|
|
8827
8929
|
async function readBrokerStatus(statusPath) {
|
|
8828
|
-
if (!
|
|
8930
|
+
if (!existsSync8(statusPath)) {
|
|
8829
8931
|
return null;
|
|
8830
8932
|
}
|
|
8831
8933
|
try {
|
|
8832
|
-
const { readFile:
|
|
8833
|
-
const raw = JSON.parse(await
|
|
8934
|
+
const { readFile: readFile17 } = await import("node:fs/promises");
|
|
8935
|
+
const raw = JSON.parse(await readFile17(statusPath, "utf8"));
|
|
8834
8936
|
if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
|
|
8835
8937
|
return null;
|
|
8836
8938
|
}
|
|
@@ -8840,13 +8942,13 @@ async function readBrokerStatus(statusPath) {
|
|
|
8840
8942
|
}
|
|
8841
8943
|
}
|
|
8842
8944
|
async function readBrokerSessionConfig(sessionConfigPath) {
|
|
8843
|
-
if (!
|
|
8945
|
+
if (!existsSync8(sessionConfigPath)) {
|
|
8844
8946
|
return null;
|
|
8845
8947
|
}
|
|
8846
8948
|
try {
|
|
8847
|
-
const { readFile:
|
|
8949
|
+
const { readFile: readFile17 } = await import("node:fs/promises");
|
|
8848
8950
|
const raw = JSON.parse(
|
|
8849
|
-
await
|
|
8951
|
+
await readFile17(sessionConfigPath, "utf8")
|
|
8850
8952
|
);
|
|
8851
8953
|
return normalizeJudgeSessionConfig({ ...raw, enabled: true });
|
|
8852
8954
|
} catch {
|
|
@@ -8937,7 +9039,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8937
9039
|
const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
|
|
8938
9040
|
const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
|
|
8939
9041
|
const status = await readBrokerStatus(paths.statusPath);
|
|
8940
|
-
if (status && isProcessAlive2(status.pid) &&
|
|
9042
|
+
if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
|
|
8941
9043
|
await writeBrokerSessionConfig(paths, brokerSessionConfig);
|
|
8942
9044
|
return paths;
|
|
8943
9045
|
}
|
|
@@ -8959,7 +9061,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8959
9061
|
child.unref();
|
|
8960
9062
|
const deadline = Date.now() + sessionConfig.connectTimeoutMs;
|
|
8961
9063
|
while (Date.now() < deadline) {
|
|
8962
|
-
if (
|
|
9064
|
+
if (existsSync8(paths.socketPath)) {
|
|
8963
9065
|
const live = await readBrokerStatus(paths.statusPath);
|
|
8964
9066
|
if (live && isProcessAlive2(live.pid)) {
|
|
8965
9067
|
return paths;
|
|
@@ -8976,8 +9078,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
|
|
|
8976
9078
|
paths.pidPath,
|
|
8977
9079
|
paths.sessionConfigPath
|
|
8978
9080
|
]) {
|
|
8979
|
-
if (
|
|
8980
|
-
await
|
|
9081
|
+
if (existsSync8(artifact)) {
|
|
9082
|
+
await unlink6(artifact).catch(() => void 0);
|
|
8981
9083
|
}
|
|
8982
9084
|
}
|
|
8983
9085
|
}
|
|
@@ -9473,9 +9575,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
|
|
|
9473
9575
|
}
|
|
9474
9576
|
|
|
9475
9577
|
// src/core/capability/gate-shadow-ratchet.ts
|
|
9476
|
-
import { existsSync as
|
|
9477
|
-
import { mkdir as
|
|
9478
|
-
import
|
|
9578
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
9579
|
+
import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
|
|
9580
|
+
import path29 from "node:path";
|
|
9479
9581
|
var DEFAULT_STATE = {
|
|
9480
9582
|
version: 1,
|
|
9481
9583
|
policyJudgeComparisons: 0,
|
|
@@ -9484,15 +9586,15 @@ var DEFAULT_STATE = {
|
|
|
9484
9586
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
9485
9587
|
};
|
|
9486
9588
|
function ratchetPath(stateDir) {
|
|
9487
|
-
return
|
|
9589
|
+
return path29.join(stateDir, "gate-shadow-ratchet.json");
|
|
9488
9590
|
}
|
|
9489
9591
|
async function loadState(stateDir) {
|
|
9490
9592
|
const filePath = ratchetPath(stateDir);
|
|
9491
|
-
if (!
|
|
9593
|
+
if (!existsSync9(filePath)) {
|
|
9492
9594
|
return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9493
9595
|
}
|
|
9494
9596
|
try {
|
|
9495
|
-
const raw = JSON.parse(await
|
|
9597
|
+
const raw = JSON.parse(await readFile8(filePath, "utf8"));
|
|
9496
9598
|
return {
|
|
9497
9599
|
...DEFAULT_STATE,
|
|
9498
9600
|
...raw,
|
|
@@ -9503,7 +9605,7 @@ async function loadState(stateDir) {
|
|
|
9503
9605
|
}
|
|
9504
9606
|
}
|
|
9505
9607
|
async function saveState(stateDir, state) {
|
|
9506
|
-
await
|
|
9608
|
+
await mkdir9(stateDir, { recursive: true, mode: 448 });
|
|
9507
9609
|
await writeFile5(
|
|
9508
9610
|
ratchetPath(stateDir),
|
|
9509
9611
|
`${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
@@ -9626,7 +9728,7 @@ function scheduleGateShadowAudit(params) {
|
|
|
9626
9728
|
async function runGatePolicyShadowComparison(params) {
|
|
9627
9729
|
const judge = params.config.judge;
|
|
9628
9730
|
const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
|
|
9629
|
-
const stateDir = params.stateDir ?? belayStateDir(params.config,
|
|
9731
|
+
const stateDir = params.stateDir ?? belayStateDir(params.config, path30.join(params.repoRoot, ".belay"));
|
|
9630
9732
|
const transport = await evaluateWithJudgeTransport(
|
|
9631
9733
|
{
|
|
9632
9734
|
prompt: buildTier1Prompt(params.command),
|
|
@@ -9916,15 +10018,15 @@ async function loadClassifierAuthorization(params) {
|
|
|
9916
10018
|
}
|
|
9917
10019
|
|
|
9918
10020
|
// src/core/capability/allowlist.ts
|
|
9919
|
-
import { existsSync as
|
|
10021
|
+
import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
|
|
9920
10022
|
init_config();
|
|
9921
10023
|
init_path_utils();
|
|
9922
|
-
import
|
|
10024
|
+
import path31 from "node:path";
|
|
9923
10025
|
function fsScopeAllowlistPath(config, repoLocalStateDir) {
|
|
9924
|
-
return
|
|
10026
|
+
return path31.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
|
|
9925
10027
|
}
|
|
9926
10028
|
function loadFsScopeAllowlistSync(filePath) {
|
|
9927
|
-
if (!
|
|
10029
|
+
if (!existsSync10(filePath)) {
|
|
9928
10030
|
return { version: 1, paths: [] };
|
|
9929
10031
|
}
|
|
9930
10032
|
const raw = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
@@ -10016,7 +10118,7 @@ function checkGatedActionLimits(action) {
|
|
|
10016
10118
|
// src/core/capability/paths.ts
|
|
10017
10119
|
init_path_utils();
|
|
10018
10120
|
init_shell_tokenizer();
|
|
10019
|
-
import
|
|
10121
|
+
import path32 from "node:path";
|
|
10020
10122
|
function applyPatchTargets(patch) {
|
|
10021
10123
|
const targets = [];
|
|
10022
10124
|
for (const line of patch.split("\n")) {
|
|
@@ -10081,7 +10183,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10081
10183
|
const paths = /* @__PURE__ */ new Set();
|
|
10082
10184
|
const filePath = extractToolFilePath(payload);
|
|
10083
10185
|
if (filePath) {
|
|
10084
|
-
const resolved =
|
|
10186
|
+
const resolved = path32.isAbsolute(filePath) ? filePath : path32.resolve(cwd, filePath);
|
|
10085
10187
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10086
10188
|
paths.add(resolved);
|
|
10087
10189
|
}
|
|
@@ -10091,7 +10193,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10091
10193
|
const patch = extractToolPatch(payload);
|
|
10092
10194
|
if (patch) {
|
|
10093
10195
|
for (const target of applyPatchTargets(patch)) {
|
|
10094
|
-
const resolved =
|
|
10196
|
+
const resolved = path32.isAbsolute(target) ? target : path32.resolve(cwd, target);
|
|
10095
10197
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10096
10198
|
paths.add(resolved);
|
|
10097
10199
|
}
|
|
@@ -10145,7 +10247,7 @@ function policyReasonToLegacyReason(decision) {
|
|
|
10145
10247
|
// src/core/capability/policy-engine.ts
|
|
10146
10248
|
init_git_resource_identity();
|
|
10147
10249
|
import { createHash as createHash10 } from "node:crypto";
|
|
10148
|
-
import
|
|
10250
|
+
import path33 from "node:path";
|
|
10149
10251
|
init_path_utils();
|
|
10150
10252
|
init_shell_tokenizer();
|
|
10151
10253
|
init_grant();
|
|
@@ -10437,7 +10539,7 @@ function hashSession(value) {
|
|
|
10437
10539
|
return createHash10("sha256").update(value).digest("hex").slice(0, 16);
|
|
10438
10540
|
}
|
|
10439
10541
|
function resolveCapabilityPath2(targetPath, cwd) {
|
|
10440
|
-
const joined =
|
|
10542
|
+
const joined = path33.isAbsolute(targetPath) ? targetPath : path33.join(cwd, targetPath);
|
|
10441
10543
|
return canonicalPath(joined);
|
|
10442
10544
|
}
|
|
10443
10545
|
function actionForFileMutation(analysis) {
|
|
@@ -10516,7 +10618,7 @@ function isRepoLocalPackageExec(request) {
|
|
|
10516
10618
|
return false;
|
|
10517
10619
|
}
|
|
10518
10620
|
const commandPath = canonicalPath(request.resource.command);
|
|
10519
|
-
return
|
|
10621
|
+
return path33.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
|
|
10520
10622
|
}
|
|
10521
10623
|
function isRepoLocalRoutineWrite(request, sensitivePaths) {
|
|
10522
10624
|
if (!isRepoLocalShellLocation(request)) {
|
|
@@ -10780,15 +10882,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
|
|
|
10780
10882
|
}
|
|
10781
10883
|
|
|
10782
10884
|
// src/core/capability/trusted-workspace-roots.ts
|
|
10783
|
-
import { existsSync as
|
|
10885
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
10784
10886
|
init_config();
|
|
10785
10887
|
init_path_utils();
|
|
10786
|
-
import
|
|
10888
|
+
import path34 from "node:path";
|
|
10787
10889
|
function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
|
|
10788
|
-
return
|
|
10890
|
+
return path34.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
|
|
10789
10891
|
}
|
|
10790
10892
|
function loadTrustedWorkspaceRootsSync(filePath) {
|
|
10791
|
-
if (!
|
|
10893
|
+
if (!existsSync11(filePath)) {
|
|
10792
10894
|
return { version: 1, roots: [] };
|
|
10793
10895
|
}
|
|
10794
10896
|
const raw = JSON.parse(readFileSync4(filePath, "utf8"));
|
|
@@ -10814,7 +10916,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
|
|
|
10814
10916
|
const source = record.source === "approval" ? "approval" : void 0;
|
|
10815
10917
|
return [
|
|
10816
10918
|
{
|
|
10817
|
-
path:
|
|
10919
|
+
path: path34.resolve(record.path),
|
|
10818
10920
|
approvedAt,
|
|
10819
10921
|
approvalId,
|
|
10820
10922
|
...source ? { source } : {}
|
|
@@ -10843,7 +10945,7 @@ function isDirectoryPath(targetPath) {
|
|
|
10843
10945
|
function isBroadTrustedWorkspaceRoot(targetPath) {
|
|
10844
10946
|
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
10845
10947
|
const root = normalizeTrustedWorkspaceRootPath(targetPath);
|
|
10846
|
-
if (root === normalizeTrustedWorkspaceRootPath(
|
|
10948
|
+
if (root === normalizeTrustedWorkspaceRootPath(path34.parse(root).root)) {
|
|
10847
10949
|
return true;
|
|
10848
10950
|
}
|
|
10849
10951
|
if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
|
|
@@ -10867,7 +10969,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
|
|
|
10867
10969
|
return false;
|
|
10868
10970
|
}
|
|
10869
10971
|
return HOME_HIGH_STAKES_SEGMENTS.some(
|
|
10870
|
-
(segment) => pathWithinRoot(
|
|
10972
|
+
(segment) => pathWithinRoot(path34.join(homeRoot, segment), normalized)
|
|
10871
10973
|
);
|
|
10872
10974
|
}
|
|
10873
10975
|
function validateTrustedWorkspaceRootCandidate(params) {
|
|
@@ -10904,7 +11006,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
|
|
|
10904
11006
|
init_config_layers();
|
|
10905
11007
|
|
|
10906
11008
|
// src/core/contained-execution/eligibility.ts
|
|
10907
|
-
import
|
|
11009
|
+
import path35 from "node:path";
|
|
10908
11010
|
init_path_utils();
|
|
10909
11011
|
var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
10910
11012
|
"command_substitution",
|
|
@@ -10921,7 +11023,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
|
10921
11023
|
]);
|
|
10922
11024
|
function isContainedUnknownExecutionEligible(config, action, result) {
|
|
10923
11025
|
const contained = config.sandbox.containedExecution;
|
|
10924
|
-
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !
|
|
11026
|
+
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path35.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
|
|
10925
11027
|
return false;
|
|
10926
11028
|
}
|
|
10927
11029
|
const plan = result.effectPlan;
|
|
@@ -11152,7 +11254,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
|
|
|
11152
11254
|
}
|
|
11153
11255
|
|
|
11154
11256
|
// src/core/classify-tool.ts
|
|
11155
|
-
import
|
|
11257
|
+
import path50 from "node:path";
|
|
11156
11258
|
init_fingerprint2();
|
|
11157
11259
|
init_path_utils();
|
|
11158
11260
|
init_scrub();
|
|
@@ -11272,13 +11374,13 @@ function worstEffectDecision(decisions) {
|
|
|
11272
11374
|
|
|
11273
11375
|
// src/core/effect-ir/shell-lower.ts
|
|
11274
11376
|
init_shell_tokenizer();
|
|
11275
|
-
import
|
|
11377
|
+
import path49 from "node:path";
|
|
11276
11378
|
|
|
11277
11379
|
// src/core/verdict/docker-compose-run.ts
|
|
11278
|
-
import
|
|
11380
|
+
import path37 from "node:path";
|
|
11279
11381
|
|
|
11280
11382
|
// src/core/verdict/recursive-invocation.ts
|
|
11281
|
-
import
|
|
11383
|
+
import path36 from "node:path";
|
|
11282
11384
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
|
|
11283
11385
|
var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
|
|
11284
11386
|
var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
|
|
@@ -11340,7 +11442,7 @@ var OSASCRIPT_PROFILE = {
|
|
|
11340
11442
|
attachedValuePrefixes: []
|
|
11341
11443
|
};
|
|
11342
11444
|
function normalizeInterpreter(value) {
|
|
11343
|
-
return
|
|
11445
|
+
return path36.basename(value);
|
|
11344
11446
|
}
|
|
11345
11447
|
function scriptResult(interpreter, token) {
|
|
11346
11448
|
if (!token) {
|
|
@@ -11570,7 +11672,7 @@ function parseOptions(words, start, options) {
|
|
|
11570
11672
|
function decodeDockerComposeRun(tokens) {
|
|
11571
11673
|
if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
|
|
11572
11674
|
const words = tokens.filter((token) => token.kind === "word");
|
|
11573
|
-
const head =
|
|
11675
|
+
const head = path37.basename(words[0]?.value ?? "");
|
|
11574
11676
|
let index;
|
|
11575
11677
|
if (head === "docker-compose") {
|
|
11576
11678
|
index = 1;
|
|
@@ -11611,7 +11713,7 @@ function decodeDockerComposeRun(tokens) {
|
|
|
11611
11713
|
}
|
|
11612
11714
|
|
|
11613
11715
|
// src/core/verdict/egress-classify.ts
|
|
11614
|
-
import
|
|
11716
|
+
import path38 from "node:path";
|
|
11615
11717
|
var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
|
|
11616
11718
|
"aws",
|
|
11617
11719
|
"curl",
|
|
@@ -11660,7 +11762,7 @@ function isEgressToolHead(head) {
|
|
|
11660
11762
|
return EGRESS_TOOL_HEADS.has(head);
|
|
11661
11763
|
}
|
|
11662
11764
|
function decodeEgressEffects(params) {
|
|
11663
|
-
const head =
|
|
11765
|
+
const head = path38.basename(params.tokens[0] ?? "");
|
|
11664
11766
|
if (head !== "curl" && head !== "wget" && head !== "gh") {
|
|
11665
11767
|
return null;
|
|
11666
11768
|
}
|
|
@@ -11668,7 +11770,7 @@ function decodeEgressEffects(params) {
|
|
|
11668
11770
|
const provenance = { segment: params.segment };
|
|
11669
11771
|
const requirements = [];
|
|
11670
11772
|
for (const file of decoded.files) {
|
|
11671
|
-
const resolved =
|
|
11773
|
+
const resolved = path38.resolve(params.cwd, expandHome2(file));
|
|
11672
11774
|
requirements.push(
|
|
11673
11775
|
requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
|
|
11674
11776
|
"egress.explicit_file_read"
|
|
@@ -11690,7 +11792,7 @@ function decodeEgressEffects(params) {
|
|
|
11690
11792
|
if (file === "-") {
|
|
11691
11793
|
continue;
|
|
11692
11794
|
}
|
|
11693
|
-
const resolved =
|
|
11795
|
+
const resolved = path38.resolve(params.cwd, expandHome2(file));
|
|
11694
11796
|
if (resolved === "/dev/null") {
|
|
11695
11797
|
continue;
|
|
11696
11798
|
}
|
|
@@ -12168,13 +12270,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12168
12270
|
if (head === "wget" && !explicitOutput) {
|
|
12169
12271
|
outputFiles.push(
|
|
12170
12272
|
...endpointOutputNames.map(
|
|
12171
|
-
(name) => outputDirectory ?
|
|
12273
|
+
(name) => outputDirectory ? path38.join(outputDirectory, name) : name
|
|
12172
12274
|
)
|
|
12173
12275
|
);
|
|
12174
12276
|
} else if (head === "curl" && remoteNameOutput) {
|
|
12175
12277
|
outputFiles.push(
|
|
12176
12278
|
...endpointOutputNames.map(
|
|
12177
|
-
(name) => outputDirectory ?
|
|
12279
|
+
(name) => outputDirectory ? path38.join(outputDirectory, name) : name
|
|
12178
12280
|
)
|
|
12179
12281
|
);
|
|
12180
12282
|
}
|
|
@@ -12188,7 +12290,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12188
12290
|
outputFiles: [
|
|
12189
12291
|
...new Set(
|
|
12190
12292
|
outputFiles.map(
|
|
12191
|
-
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !
|
|
12293
|
+
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !path38.isAbsolute(file) ? path38.join(outputDirectory, file) : file
|
|
12192
12294
|
)
|
|
12193
12295
|
)
|
|
12194
12296
|
],
|
|
@@ -12203,7 +12305,7 @@ function remoteOutputName(spec) {
|
|
|
12203
12305
|
} catch {
|
|
12204
12306
|
pathname = spec.split(/[?#]/, 1)[0] ?? "";
|
|
12205
12307
|
}
|
|
12206
|
-
const name =
|
|
12308
|
+
const name = path38.posix.basename(pathname);
|
|
12207
12309
|
return name && name !== "/" ? name : "index.html";
|
|
12208
12310
|
}
|
|
12209
12311
|
function decodeGhGrammar(tokens) {
|
|
@@ -12368,7 +12470,7 @@ function expandHome2(value) {
|
|
|
12368
12470
|
return process.env.HOME ?? value;
|
|
12369
12471
|
}
|
|
12370
12472
|
if (value.startsWith("~/")) {
|
|
12371
|
-
return
|
|
12473
|
+
return path38.join(process.env.HOME ?? "~", value.slice(2));
|
|
12372
12474
|
}
|
|
12373
12475
|
return value;
|
|
12374
12476
|
}
|
|
@@ -12387,7 +12489,7 @@ function requirement(tag, action, resource, segment, signals) {
|
|
|
12387
12489
|
}
|
|
12388
12490
|
|
|
12389
12491
|
// src/core/verdict/git-classifier.ts
|
|
12390
|
-
import
|
|
12492
|
+
import path39 from "node:path";
|
|
12391
12493
|
init_shell_tokenizer();
|
|
12392
12494
|
var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
|
|
12393
12495
|
"--copy",
|
|
@@ -12483,7 +12585,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
12483
12585
|
var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
|
|
12484
12586
|
var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
|
|
12485
12587
|
function isGitExecutable(token) {
|
|
12486
|
-
return
|
|
12588
|
+
return path39.basename(token) === "git";
|
|
12487
12589
|
}
|
|
12488
12590
|
function takesValue(flag) {
|
|
12489
12591
|
return flag === "-C" || flag === "-c" || flag === "--git-dir" || flag === "--work-tree" || flag === "--exec-path" || flag === "--paginate" || flag === "--config-env" || flag.startsWith("-C") || flag.startsWith("-c") || flag.startsWith("--git-dir=") || flag.startsWith("--work-tree=");
|
|
@@ -12511,7 +12613,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12511
12613
|
if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
|
|
12512
12614
|
const value = tokens[index + 1];
|
|
12513
12615
|
if (token === "-C" && value) {
|
|
12514
|
-
effectiveCwd =
|
|
12616
|
+
effectiveCwd = path39.resolve(baseCwd, value);
|
|
12515
12617
|
} else if (token === "--work-tree" && value) {
|
|
12516
12618
|
workTree = value;
|
|
12517
12619
|
} else if (token === "--git-dir" && value) {
|
|
@@ -12521,7 +12623,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12521
12623
|
continue;
|
|
12522
12624
|
}
|
|
12523
12625
|
if (token.startsWith("-C") && token.length > 2) {
|
|
12524
|
-
effectiveCwd =
|
|
12626
|
+
effectiveCwd = path39.resolve(baseCwd, token.slice(2));
|
|
12525
12627
|
index += 1;
|
|
12526
12628
|
continue;
|
|
12527
12629
|
}
|
|
@@ -12664,7 +12766,7 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12664
12766
|
if (!looksLikeFileOperand(token)) {
|
|
12665
12767
|
return false;
|
|
12666
12768
|
}
|
|
12667
|
-
if (token.startsWith(".") ||
|
|
12769
|
+
if (token.startsWith(".") || path39.isAbsolute(token)) {
|
|
12668
12770
|
return true;
|
|
12669
12771
|
}
|
|
12670
12772
|
return token.includes(".");
|
|
@@ -12672,12 +12774,12 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12672
12774
|
function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
|
|
12673
12775
|
const resolveBase = effectiveCwd ?? baseCwd;
|
|
12674
12776
|
if (workTree) {
|
|
12675
|
-
return
|
|
12777
|
+
return path39.resolve(resolveBase, workTree);
|
|
12676
12778
|
}
|
|
12677
12779
|
if (gitDir) {
|
|
12678
|
-
const resolvedGitDir =
|
|
12679
|
-
if (
|
|
12680
|
-
return
|
|
12780
|
+
const resolvedGitDir = path39.resolve(resolveBase, gitDir);
|
|
12781
|
+
if (path39.basename(resolvedGitDir) === ".git") {
|
|
12782
|
+
return path39.dirname(resolvedGitDir);
|
|
12681
12783
|
}
|
|
12682
12784
|
}
|
|
12683
12785
|
return void 0;
|
|
@@ -12758,7 +12860,7 @@ function classifyGitCommand(tokens, baseCwd) {
|
|
|
12758
12860
|
const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
|
|
12759
12861
|
const normalizedKey = `git ${subcommand}`;
|
|
12760
12862
|
const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
|
|
12761
|
-
const effectiveGitDir = gitDir ?
|
|
12863
|
+
const effectiveGitDir = gitDir ? path39.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
|
|
12762
12864
|
const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
|
|
12763
12865
|
(target, index, targets) => Boolean(target) && targets.indexOf(target) === index
|
|
12764
12866
|
);
|
|
@@ -12910,9 +13012,9 @@ function decodeGitEffects(params) {
|
|
|
12910
13012
|
...subcommand === "push" ? ["tier0_external"] : []
|
|
12911
13013
|
];
|
|
12912
13014
|
const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
|
|
12913
|
-
const workTreeRoot = normalized.workTree ?
|
|
12914
|
-
const gitRefRoot = normalized.gitDir ?
|
|
12915
|
-
const gitControlRoot = normalized.gitDir ? gitRefRoot :
|
|
13015
|
+
const workTreeRoot = normalized.workTree ? path39.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
|
|
13016
|
+
const gitRefRoot = normalized.gitDir ? path39.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
|
|
13017
|
+
const gitControlRoot = normalized.gitDir ? gitRefRoot : path39.join(gitRefRoot, ".git");
|
|
12916
13018
|
const requirements = [];
|
|
12917
13019
|
if (subcommand === "fetch" || subcommand === "pull") {
|
|
12918
13020
|
const positionals = gitRemotePositionals(args);
|
|
@@ -13071,7 +13173,7 @@ function decodeGitEffects(params) {
|
|
|
13071
13173
|
gitRequirement(
|
|
13072
13174
|
"control_plane.write",
|
|
13073
13175
|
"control_plane.write",
|
|
13074
|
-
{ kind: "path", path:
|
|
13176
|
+
{ kind: "path", path: path39.join(gitControlRoot, "logs") },
|
|
13075
13177
|
params.segment,
|
|
13076
13178
|
[...signals, "git_history_destructive", "git.reflog.mutate"]
|
|
13077
13179
|
)
|
|
@@ -13129,7 +13231,7 @@ function decodeGitEffects(params) {
|
|
|
13129
13231
|
gitRequirement(
|
|
13130
13232
|
"fs.read",
|
|
13131
13233
|
"fs.read",
|
|
13132
|
-
{ kind: "path", path:
|
|
13234
|
+
{ kind: "path", path: path39.resolve(workTreeRoot, operand) },
|
|
13133
13235
|
params.segment,
|
|
13134
13236
|
[...signals, "git.path.read"]
|
|
13135
13237
|
)
|
|
@@ -13164,7 +13266,7 @@ function decodeGitEffects(params) {
|
|
|
13164
13266
|
gitRequirement(
|
|
13165
13267
|
"fs.write",
|
|
13166
13268
|
"fs.write",
|
|
13167
|
-
{ kind: "path", path:
|
|
13269
|
+
{ kind: "path", path: path39.resolve(workTreeRoot, operand) },
|
|
13168
13270
|
params.segment,
|
|
13169
13271
|
[...signals, "git.path.write"]
|
|
13170
13272
|
)
|
|
@@ -13348,8 +13450,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
|
|
|
13348
13450
|
}
|
|
13349
13451
|
|
|
13350
13452
|
// src/core/verdict/launcher-resolve.ts
|
|
13351
|
-
import { existsSync as
|
|
13352
|
-
import
|
|
13453
|
+
import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
|
|
13454
|
+
import path40 from "node:path";
|
|
13353
13455
|
|
|
13354
13456
|
// src/core/verdict/makefile-expand.ts
|
|
13355
13457
|
var MAX_EXPAND_DEPTH = 16;
|
|
@@ -13522,8 +13624,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
13522
13624
|
"why"
|
|
13523
13625
|
]);
|
|
13524
13626
|
function readPackageJson(dir) {
|
|
13525
|
-
const packagePath =
|
|
13526
|
-
if (!
|
|
13627
|
+
const packagePath = path40.join(dir, "package.json");
|
|
13628
|
+
if (!existsSync12(packagePath)) {
|
|
13527
13629
|
return null;
|
|
13528
13630
|
}
|
|
13529
13631
|
try {
|
|
@@ -13533,17 +13635,17 @@ function readPackageJson(dir) {
|
|
|
13533
13635
|
}
|
|
13534
13636
|
}
|
|
13535
13637
|
function findPackageJson(startDir, stopDir) {
|
|
13536
|
-
let current =
|
|
13537
|
-
const stop =
|
|
13638
|
+
let current = path40.resolve(startDir);
|
|
13639
|
+
const stop = path40.resolve(stopDir);
|
|
13538
13640
|
while (true) {
|
|
13539
|
-
const packagePath =
|
|
13540
|
-
if (
|
|
13641
|
+
const packagePath = path40.join(current, "package.json");
|
|
13642
|
+
if (existsSync12(packagePath)) {
|
|
13541
13643
|
return packagePath;
|
|
13542
13644
|
}
|
|
13543
|
-
if (current === stop || current ===
|
|
13544
|
-
return
|
|
13645
|
+
if (current === stop || current === path40.dirname(current)) {
|
|
13646
|
+
return existsSync12(packagePath) ? packagePath : null;
|
|
13545
13647
|
}
|
|
13546
|
-
const parent =
|
|
13648
|
+
const parent = path40.dirname(current);
|
|
13547
13649
|
if (!parent.startsWith(stop) && parent !== current) {
|
|
13548
13650
|
}
|
|
13549
13651
|
if (parent === current) {
|
|
@@ -13600,7 +13702,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
|
|
|
13600
13702
|
}
|
|
13601
13703
|
return { recipes: [], opaque: true, reason: "package_json_missing" };
|
|
13602
13704
|
}
|
|
13603
|
-
const pkg = readPackageJson(
|
|
13705
|
+
const pkg = readPackageJson(path40.dirname(packagePath));
|
|
13604
13706
|
const scripts = pkg?.scripts;
|
|
13605
13707
|
if (!scripts || typeof scripts !== "object") {
|
|
13606
13708
|
return { recipes: [], opaque: true, reason: "package_scripts_missing" };
|
|
@@ -13691,20 +13793,20 @@ function parseMakefileRecipeContent(content) {
|
|
|
13691
13793
|
function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
13692
13794
|
const candidates = ["Makefile", "makefile", "GNUmakefile"];
|
|
13693
13795
|
let makefilePath = null;
|
|
13694
|
-
let searchDir =
|
|
13695
|
-
const stop =
|
|
13796
|
+
let searchDir = path40.resolve(cwd);
|
|
13797
|
+
const stop = path40.resolve(repoRoot);
|
|
13696
13798
|
while (true) {
|
|
13697
13799
|
for (const name of candidates) {
|
|
13698
|
-
const candidate =
|
|
13699
|
-
if (
|
|
13800
|
+
const candidate = path40.join(searchDir, name);
|
|
13801
|
+
if (existsSync12(candidate)) {
|
|
13700
13802
|
makefilePath = candidate;
|
|
13701
13803
|
break;
|
|
13702
13804
|
}
|
|
13703
13805
|
}
|
|
13704
|
-
if (makefilePath || searchDir === stop || searchDir ===
|
|
13806
|
+
if (makefilePath || searchDir === stop || searchDir === path40.dirname(searchDir)) {
|
|
13705
13807
|
break;
|
|
13706
13808
|
}
|
|
13707
|
-
searchDir =
|
|
13809
|
+
searchDir = path40.dirname(searchDir);
|
|
13708
13810
|
}
|
|
13709
13811
|
if (!makefilePath) {
|
|
13710
13812
|
return { recipes: [], opaque: true, reason: "unknown_local_effect" };
|
|
@@ -13731,7 +13833,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
13731
13833
|
}
|
|
13732
13834
|
const entry = targets.get(name);
|
|
13733
13835
|
if (!entry) {
|
|
13734
|
-
if (!
|
|
13836
|
+
if (!existsSync12(path40.resolve(path40.dirname(makefilePath), name))) {
|
|
13735
13837
|
hasUndefinedPrerequisite = true;
|
|
13736
13838
|
}
|
|
13737
13839
|
return;
|
|
@@ -13830,7 +13932,7 @@ function resolveLauncherRecipe(params) {
|
|
|
13830
13932
|
}
|
|
13831
13933
|
|
|
13832
13934
|
// src/core/verdict/parser.ts
|
|
13833
|
-
import
|
|
13935
|
+
import path41 from "node:path";
|
|
13834
13936
|
|
|
13835
13937
|
// src/core/shell-substitution.ts
|
|
13836
13938
|
function findStructuralCommandSubstitutions(command) {
|
|
@@ -14070,7 +14172,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
14070
14172
|
".sh"
|
|
14071
14173
|
]);
|
|
14072
14174
|
function normalizeHead(token) {
|
|
14073
|
-
const base =
|
|
14175
|
+
const base = path41.basename(token);
|
|
14074
14176
|
if (base && base !== "." && base !== "..") {
|
|
14075
14177
|
return base;
|
|
14076
14178
|
}
|
|
@@ -14380,7 +14482,7 @@ function isBareInterpreter(tokens) {
|
|
|
14380
14482
|
return false;
|
|
14381
14483
|
}
|
|
14382
14484
|
const scriptArg = args.find((token) => !token.startsWith("-"));
|
|
14383
|
-
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(
|
|
14485
|
+
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path41.extname(scriptArg))) {
|
|
14384
14486
|
return false;
|
|
14385
14487
|
}
|
|
14386
14488
|
if (scriptArg) {
|
|
@@ -14620,7 +14722,7 @@ function mergeNodes(nodes) {
|
|
|
14620
14722
|
}
|
|
14621
14723
|
|
|
14622
14724
|
// src/core/effect-ir/shell-lower/argv-delegate-gate.ts
|
|
14623
|
-
import
|
|
14725
|
+
import path42 from "node:path";
|
|
14624
14726
|
var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
|
|
14625
14727
|
"sudo",
|
|
14626
14728
|
"env",
|
|
@@ -14641,7 +14743,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
|
|
|
14641
14743
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
|
|
14642
14744
|
return false;
|
|
14643
14745
|
}
|
|
14644
|
-
const innerHead =
|
|
14746
|
+
const innerHead = path42.basename(innerTokens[0] ?? "");
|
|
14645
14747
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
|
|
14646
14748
|
return false;
|
|
14647
14749
|
}
|
|
@@ -14748,7 +14850,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
|
|
|
14748
14850
|
|
|
14749
14851
|
// src/core/effect-ir/shell-lower/tokens.ts
|
|
14750
14852
|
init_shell_tokenizer();
|
|
14751
|
-
import
|
|
14853
|
+
import path43 from "node:path";
|
|
14752
14854
|
var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
14753
14855
|
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
|
|
14754
14856
|
var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
|
|
@@ -14892,9 +14994,9 @@ function resolvePathOperand(operand, cwd) {
|
|
|
14892
14994
|
return process.env.HOME ?? operand;
|
|
14893
14995
|
}
|
|
14894
14996
|
if (operand.startsWith("~/")) {
|
|
14895
|
-
return
|
|
14997
|
+
return path43.join(process.env.HOME ?? "~", operand.slice(2));
|
|
14896
14998
|
}
|
|
14897
|
-
return
|
|
14999
|
+
return path43.resolve(cwd, operand);
|
|
14898
15000
|
}
|
|
14899
15001
|
function isOptionToken(value) {
|
|
14900
15002
|
return value.startsWith("-") || value.startsWith("+");
|
|
@@ -14906,7 +15008,7 @@ function pipeToShell(command) {
|
|
|
14906
15008
|
return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
|
|
14907
15009
|
}
|
|
14908
15010
|
function executableBaseName(head) {
|
|
14909
|
-
return
|
|
15011
|
+
return path43.basename(head);
|
|
14910
15012
|
}
|
|
14911
15013
|
function isMetadataOnlyArgv(argv) {
|
|
14912
15014
|
return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
|
|
@@ -15025,15 +15127,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
|
|
|
15025
15127
|
}
|
|
15026
15128
|
|
|
15027
15129
|
// src/core/effect-ir/shell-lower/decode-process.ts
|
|
15028
|
-
import
|
|
15130
|
+
import path47 from "node:path";
|
|
15029
15131
|
|
|
15030
15132
|
// src/core/effect-ir/shell-lower/decoders/belay.ts
|
|
15031
|
-
import
|
|
15133
|
+
import path44 from "node:path";
|
|
15032
15134
|
function decodeBelay(args, repoRoot, segment) {
|
|
15033
15135
|
const [section, operation, key] = args;
|
|
15034
15136
|
const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
|
|
15035
15137
|
const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
|
|
15036
15138
|
const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
|
|
15139
|
+
const approvalAuthorityCommand = [
|
|
15140
|
+
"approval-token",
|
|
15141
|
+
"approve",
|
|
15142
|
+
"revoke",
|
|
15143
|
+
"standing-allow"
|
|
15144
|
+
].includes(section ?? "");
|
|
15145
|
+
const configTrustMutation = section === "config" && operation === "trust";
|
|
15037
15146
|
if (judgeCommand || configRead || configJudgeMutation) {
|
|
15038
15147
|
return [
|
|
15039
15148
|
processRequirement("belay", "inspect", segment, [
|
|
@@ -15042,12 +15151,23 @@ function decodeBelay(args, repoRoot, segment) {
|
|
|
15042
15151
|
])
|
|
15043
15152
|
];
|
|
15044
15153
|
}
|
|
15154
|
+
if (approvalAuthorityCommand || configTrustMutation) {
|
|
15155
|
+
return [
|
|
15156
|
+
requirement2(
|
|
15157
|
+
"control_plane.write",
|
|
15158
|
+
"control_plane.write",
|
|
15159
|
+
{ kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
|
|
15160
|
+
segment,
|
|
15161
|
+
[approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
|
|
15162
|
+
)
|
|
15163
|
+
];
|
|
15164
|
+
}
|
|
15045
15165
|
if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
|
|
15046
15166
|
return [
|
|
15047
15167
|
requirement2(
|
|
15048
15168
|
"control_plane.write",
|
|
15049
15169
|
"control_plane.write",
|
|
15050
|
-
{ kind: "path", path:
|
|
15170
|
+
{ kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
|
|
15051
15171
|
segment,
|
|
15052
15172
|
["belay.config_non_judge_mutation"]
|
|
15053
15173
|
)
|
|
@@ -15288,7 +15408,7 @@ function validDockerInfo(args) {
|
|
|
15288
15408
|
// src/core/effect-ir/shell-lower/decoders/filesystem.ts
|
|
15289
15409
|
init_git_resource_identity();
|
|
15290
15410
|
import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
|
|
15291
|
-
import
|
|
15411
|
+
import path45 from "node:path";
|
|
15292
15412
|
function decodeCopyMove(head, args, cwd, segment) {
|
|
15293
15413
|
const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
|
|
15294
15414
|
const operands = [];
|
|
@@ -15416,11 +15536,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
|
|
|
15416
15536
|
function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
|
|
15417
15537
|
try {
|
|
15418
15538
|
if (finalOperandIsSymlink) {
|
|
15419
|
-
return
|
|
15539
|
+
return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
|
|
15420
15540
|
}
|
|
15421
15541
|
return realpathSync4.native(targetPath);
|
|
15422
15542
|
} catch {
|
|
15423
|
-
return
|
|
15543
|
+
return path45.resolve(targetPath);
|
|
15424
15544
|
}
|
|
15425
15545
|
}
|
|
15426
15546
|
function isSymbolicLink(targetPath) {
|
|
@@ -15431,8 +15551,8 @@ function isSymbolicLink(targetPath) {
|
|
|
15431
15551
|
}
|
|
15432
15552
|
}
|
|
15433
15553
|
function pathContains(ancestor, candidate) {
|
|
15434
|
-
const relative =
|
|
15435
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
15554
|
+
const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
|
|
15555
|
+
return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
|
|
15436
15556
|
}
|
|
15437
15557
|
function filesystemReadOperands(head, args) {
|
|
15438
15558
|
switch (head) {
|
|
@@ -15572,7 +15692,7 @@ function decodePrisma(args, env, repoRoot, segment) {
|
|
|
15572
15692
|
|
|
15573
15693
|
// src/core/effect-ir/shell-lower/decoders/ruby.ts
|
|
15574
15694
|
init_path_utils();
|
|
15575
|
-
import
|
|
15695
|
+
import path46 from "node:path";
|
|
15576
15696
|
var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
|
|
15577
15697
|
function railsReadOnlySubcommand(args) {
|
|
15578
15698
|
const subcommand = args[0];
|
|
@@ -15582,7 +15702,7 @@ function railsReadOnlySubcommand(args) {
|
|
|
15582
15702
|
return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
|
|
15583
15703
|
}
|
|
15584
15704
|
function isRubyTestScript(scriptPath) {
|
|
15585
|
-
const base =
|
|
15705
|
+
const base = path46.basename(scriptPath);
|
|
15586
15706
|
return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
|
|
15587
15707
|
}
|
|
15588
15708
|
function parseRubyTestInvocation(args) {
|
|
@@ -15996,7 +16116,7 @@ function decodeProcessOrFilesystem(params) {
|
|
|
15996
16116
|
requirement2(
|
|
15997
16117
|
"fs.read",
|
|
15998
16118
|
"fs.read",
|
|
15999
|
-
{ kind: "path", path:
|
|
16119
|
+
{ kind: "path", path: path47.resolve(cwd, syntax) },
|
|
16000
16120
|
segment,
|
|
16001
16121
|
["shell.syntax_source_read"]
|
|
16002
16122
|
)
|
|
@@ -16202,7 +16322,7 @@ function packageExecInnerIsMetadata(peel) {
|
|
|
16202
16322
|
|
|
16203
16323
|
// src/core/effect-ir/shell-lower/segment.ts
|
|
16204
16324
|
init_shell_tokenizer();
|
|
16205
|
-
import
|
|
16325
|
+
import path48 from "node:path";
|
|
16206
16326
|
var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
|
|
16207
16327
|
var SHELL_GLOB_PATTERN = /[*?[]/;
|
|
16208
16328
|
function requiresKnownCwd(requirementValue) {
|
|
@@ -16217,11 +16337,11 @@ function joinNestedOpacity(outer, nested) {
|
|
|
16217
16337
|
}
|
|
16218
16338
|
function startsLocalPostgresService(command) {
|
|
16219
16339
|
const tokens = tokenizeShell(command);
|
|
16220
|
-
return
|
|
16340
|
+
return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
|
|
16221
16341
|
}
|
|
16222
16342
|
function resolveCdTransition(command, currentCwd) {
|
|
16223
16343
|
const tokens = tokenizeShell(command);
|
|
16224
|
-
if (
|
|
16344
|
+
if (path48.basename(tokens[0] ?? "") !== "cd") {
|
|
16225
16345
|
return null;
|
|
16226
16346
|
}
|
|
16227
16347
|
const target = tokens[1] ?? "~";
|
|
@@ -16384,7 +16504,7 @@ function lowerSegment(command, context) {
|
|
|
16384
16504
|
stripStructuredRedirects(lexed.tokens),
|
|
16385
16505
|
stripRedirects(parsedTokens)
|
|
16386
16506
|
);
|
|
16387
|
-
const head =
|
|
16507
|
+
const head = path49.basename(tokens[0] ?? parsed.head);
|
|
16388
16508
|
let opacity = segmentOpacity(command);
|
|
16389
16509
|
const signals = /* @__PURE__ */ new Set();
|
|
16390
16510
|
const requirements = [];
|
|
@@ -17236,7 +17356,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17236
17356
|
};
|
|
17237
17357
|
}
|
|
17238
17358
|
const signals = [];
|
|
17239
|
-
const resolvedPath =
|
|
17359
|
+
const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
|
|
17240
17360
|
const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
|
|
17241
17361
|
if (hitsProtectedRoot) {
|
|
17242
17362
|
signals.push("control_plane_path");
|
|
@@ -17822,7 +17942,7 @@ function hashDecisionConfig(config) {
|
|
|
17822
17942
|
init_fingerprint2();
|
|
17823
17943
|
|
|
17824
17944
|
// src/version.ts
|
|
17825
|
-
var PACKAGE_VERSION = "0.
|
|
17945
|
+
var PACKAGE_VERSION = "0.10.0";
|
|
17826
17946
|
|
|
17827
17947
|
// src/runtime-provenance.ts
|
|
17828
17948
|
function resolveRuntimeArtifactHash(artifactHash) {
|
|
@@ -17895,56 +18015,56 @@ function capabilityRequestsBlockRecovery(requests) {
|
|
|
17895
18015
|
init_fingerprint2();
|
|
17896
18016
|
init_path_utils();
|
|
17897
18017
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17898
|
-
import { existsSync as
|
|
17899
|
-
import { mkdir as
|
|
17900
|
-
import
|
|
18018
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
18019
|
+
import { mkdir as mkdir12, readdir as readdir3, readFile as readFile13, rename as rename4, rm as rm6 } from "node:fs/promises";
|
|
18020
|
+
import path55 from "node:path";
|
|
17901
18021
|
|
|
17902
18022
|
// src/core/recovery/artifact-store.ts
|
|
17903
18023
|
init_fingerprint2();
|
|
17904
18024
|
init_path_utils();
|
|
17905
18025
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
17906
|
-
import { existsSync as
|
|
17907
|
-
import { lstat as lstat7, mkdir as
|
|
17908
|
-
import
|
|
18026
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
18027
|
+
import { lstat as lstat7, mkdir as mkdir11, open as open6, readdir as readdir2, readFile as readFile10, rename as rename3, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
|
|
18028
|
+
import path52 from "node:path";
|
|
17909
18029
|
|
|
17910
18030
|
// src/core/recovery/snapshot-node.ts
|
|
17911
18031
|
init_fingerprint2();
|
|
17912
18032
|
init_path_utils();
|
|
17913
18033
|
import { createHash as createHash12 } from "node:crypto";
|
|
17914
|
-
import { existsSync as
|
|
18034
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
17915
18035
|
import {
|
|
17916
|
-
chmod as
|
|
18036
|
+
chmod as chmod4,
|
|
17917
18037
|
copyFile as copyFile2,
|
|
17918
18038
|
lstat as lstat6,
|
|
17919
|
-
mkdir as
|
|
17920
|
-
open as
|
|
17921
|
-
readFile as
|
|
18039
|
+
mkdir as mkdir10,
|
|
18040
|
+
open as open5,
|
|
18041
|
+
readFile as readFile9,
|
|
17922
18042
|
readlink as readlink4,
|
|
17923
18043
|
rm as rm4,
|
|
17924
18044
|
rmdir as rmdir2,
|
|
17925
18045
|
symlink as symlink3,
|
|
17926
18046
|
writeFile as writeFile6
|
|
17927
18047
|
} from "node:fs/promises";
|
|
17928
|
-
import
|
|
18048
|
+
import path51 from "node:path";
|
|
17929
18049
|
var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
|
|
17930
18050
|
function validRecoveryRelativePath(relativePath) {
|
|
17931
|
-
if (!relativePath || relativePath.includes("\0") ||
|
|
17932
|
-
const normalized =
|
|
17933
|
-
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${
|
|
18051
|
+
if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
|
|
18052
|
+
const normalized = path51.normalize(relativePath);
|
|
18053
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
|
|
17934
18054
|
}
|
|
17935
18055
|
async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
17936
18056
|
if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
|
|
17937
18057
|
const root = canonicalPath(resourceRoot);
|
|
17938
|
-
const target =
|
|
17939
|
-
const relative =
|
|
17940
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
18058
|
+
const target = path51.resolve(root, relativePath);
|
|
18059
|
+
const relative = path51.relative(root, target);
|
|
18060
|
+
if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
|
|
17941
18061
|
throw new Error("recovery_path_escape");
|
|
17942
18062
|
}
|
|
17943
18063
|
let current = root;
|
|
17944
|
-
const parentParts =
|
|
18064
|
+
const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
|
|
17945
18065
|
for (const part of parentParts) {
|
|
17946
|
-
current =
|
|
17947
|
-
if (!
|
|
18066
|
+
current = path51.join(current, part);
|
|
18067
|
+
if (!existsSync13(current)) break;
|
|
17948
18068
|
const info = await lstat6(current);
|
|
17949
18069
|
if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
|
|
17950
18070
|
if (!info.isDirectory()) break;
|
|
@@ -17952,7 +18072,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
|
17952
18072
|
return target;
|
|
17953
18073
|
}
|
|
17954
18074
|
async function fsyncPath(filePath) {
|
|
17955
|
-
const handle = await
|
|
18075
|
+
const handle = await open5(filePath, "r");
|
|
17956
18076
|
try {
|
|
17957
18077
|
await handle.sync();
|
|
17958
18078
|
} finally {
|
|
@@ -17994,13 +18114,13 @@ async function captureRecoverySnapshot(filePath, options) {
|
|
|
17994
18114
|
return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
|
|
17995
18115
|
}
|
|
17996
18116
|
if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
|
|
17997
|
-
const content = await
|
|
18117
|
+
const content = await readFile9(filePath);
|
|
17998
18118
|
const hash = createHash12("sha256").update(content).digest("hex");
|
|
17999
18119
|
let blob;
|
|
18000
18120
|
if (options?.blobDir) {
|
|
18001
|
-
await
|
|
18002
|
-
const blobPath =
|
|
18003
|
-
if (!
|
|
18121
|
+
await mkdir10(options.blobDir, { recursive: true, mode: 448 });
|
|
18122
|
+
const blobPath = path51.join(options.blobDir, hash);
|
|
18123
|
+
if (!existsSync13(blobPath)) {
|
|
18004
18124
|
await writeFile6(blobPath, content, { mode: 384 });
|
|
18005
18125
|
await fsyncPath(blobPath);
|
|
18006
18126
|
}
|
|
@@ -18054,7 +18174,7 @@ async function validateRecoverySnapshot(params) {
|
|
|
18054
18174
|
if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
|
|
18055
18175
|
let content;
|
|
18056
18176
|
try {
|
|
18057
|
-
content = await
|
|
18177
|
+
content = await readFile9(path51.join(params.artifactDir, record.blob));
|
|
18058
18178
|
} catch {
|
|
18059
18179
|
throw new Error(params.corruptReason);
|
|
18060
18180
|
}
|
|
@@ -18082,16 +18202,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
|
|
|
18082
18202
|
]);
|
|
18083
18203
|
var STAGING_STALE_MS = 5 * 6e4;
|
|
18084
18204
|
function checkpointsRoot(stateDir) {
|
|
18085
|
-
return
|
|
18205
|
+
return path52.join(stateDir, "recovery", "checkpoints");
|
|
18086
18206
|
}
|
|
18087
18207
|
function checkpointDir(stateDir, checkpointId) {
|
|
18088
18208
|
if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
|
|
18089
18209
|
throw new Error("invalid_recovery_checkpoint_id");
|
|
18090
18210
|
}
|
|
18091
|
-
return
|
|
18211
|
+
return path52.join(checkpointsRoot(stateDir), checkpointId);
|
|
18092
18212
|
}
|
|
18093
18213
|
async function fsyncPath2(filePath) {
|
|
18094
|
-
const handle = await
|
|
18214
|
+
const handle = await open6(filePath, "r");
|
|
18095
18215
|
try {
|
|
18096
18216
|
await handle.sync();
|
|
18097
18217
|
} finally {
|
|
@@ -18099,13 +18219,13 @@ async function fsyncPath2(filePath) {
|
|
|
18099
18219
|
}
|
|
18100
18220
|
}
|
|
18101
18221
|
async function atomicWriteJson(filePath, value) {
|
|
18102
|
-
await
|
|
18222
|
+
await mkdir11(path52.dirname(filePath), { recursive: true, mode: 448 });
|
|
18103
18223
|
const temporary = `${filePath}.tmp-${randomUUID4()}`;
|
|
18104
18224
|
await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
|
|
18105
18225
|
`, { mode: 384 });
|
|
18106
18226
|
await fsyncPath2(temporary);
|
|
18107
|
-
await
|
|
18108
|
-
await fsyncPath2(
|
|
18227
|
+
await rename3(temporary, filePath);
|
|
18228
|
+
await fsyncPath2(path52.dirname(filePath));
|
|
18109
18229
|
}
|
|
18110
18230
|
async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
18111
18231
|
const value = {
|
|
@@ -18115,13 +18235,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
|
18115
18235
|
manifestHash,
|
|
18116
18236
|
...detail ? { detail } : {}
|
|
18117
18237
|
};
|
|
18118
|
-
await atomicWriteJson(
|
|
18238
|
+
await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
|
|
18119
18239
|
}
|
|
18120
18240
|
async function directorySize(root) {
|
|
18121
|
-
if (!
|
|
18241
|
+
if (!existsSync14(root)) return 0;
|
|
18122
18242
|
let total = 0;
|
|
18123
18243
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18124
|
-
const entryPath =
|
|
18244
|
+
const entryPath = path52.join(root, entry.name);
|
|
18125
18245
|
if (entry.isDirectory()) total += await directorySize(entryPath);
|
|
18126
18246
|
else total += (await lstat7(entryPath)).size;
|
|
18127
18247
|
}
|
|
@@ -18166,9 +18286,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18166
18286
|
let rawManifest;
|
|
18167
18287
|
let state;
|
|
18168
18288
|
try {
|
|
18169
|
-
rawManifest = JSON.parse(await
|
|
18289
|
+
rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
|
|
18170
18290
|
state = JSON.parse(
|
|
18171
|
-
await
|
|
18291
|
+
await readFile10(path52.join(artifactDir, "state.json"), "utf8")
|
|
18172
18292
|
);
|
|
18173
18293
|
} catch {
|
|
18174
18294
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
@@ -18184,10 +18304,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18184
18304
|
}
|
|
18185
18305
|
const entryPaths = /* @__PURE__ */ new Set();
|
|
18186
18306
|
for (const entry of manifest.entries) {
|
|
18187
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(
|
|
18307
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path52.normalize(entry.path))) {
|
|
18188
18308
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18189
18309
|
}
|
|
18190
|
-
entryPaths.add(
|
|
18310
|
+
entryPaths.add(path52.normalize(entry.path));
|
|
18191
18311
|
for (const [side, snapshot] of [
|
|
18192
18312
|
["before", entry.before],
|
|
18193
18313
|
["after", entry.after]
|
|
@@ -18201,9 +18321,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18201
18321
|
});
|
|
18202
18322
|
}
|
|
18203
18323
|
}
|
|
18204
|
-
const receiptPath =
|
|
18324
|
+
const receiptPath = path52.join(artifactDir, "receipt.json");
|
|
18205
18325
|
let receipt;
|
|
18206
|
-
if (["applied", "restoring", "restored", "conflict"].includes(state.state) ||
|
|
18326
|
+
if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
|
|
18207
18327
|
receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18208
18328
|
}
|
|
18209
18329
|
return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
|
|
@@ -18211,7 +18331,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18211
18331
|
async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18212
18332
|
let rawReceipt;
|
|
18213
18333
|
try {
|
|
18214
|
-
rawReceipt = JSON.parse(await
|
|
18334
|
+
rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
|
|
18215
18335
|
} catch {
|
|
18216
18336
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18217
18337
|
}
|
|
@@ -18234,8 +18354,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
|
|
|
18234
18354
|
return receipt;
|
|
18235
18355
|
}
|
|
18236
18356
|
async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18237
|
-
const receiptPath =
|
|
18238
|
-
if (
|
|
18357
|
+
const receiptPath = path52.join(artifactDir, "receipt.json");
|
|
18358
|
+
if (existsSync14(receiptPath)) {
|
|
18239
18359
|
return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18240
18360
|
}
|
|
18241
18361
|
const receipt = {
|
|
@@ -18252,14 +18372,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
|
18252
18372
|
}
|
|
18253
18373
|
async function checkpointIds(stateDir) {
|
|
18254
18374
|
const root = checkpointsRoot(stateDir);
|
|
18255
|
-
if (!
|
|
18375
|
+
if (!existsSync14(root)) return [];
|
|
18256
18376
|
return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
|
|
18257
18377
|
}
|
|
18258
18378
|
async function artifactRepoRoot(stateDir, checkpointId) {
|
|
18259
18379
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18260
18380
|
try {
|
|
18261
18381
|
const manifest = JSON.parse(
|
|
18262
|
-
await
|
|
18382
|
+
await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
|
|
18263
18383
|
);
|
|
18264
18384
|
if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
|
|
18265
18385
|
return canonicalPath(manifest.repoRoot);
|
|
@@ -18267,7 +18387,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
|
|
|
18267
18387
|
} catch {
|
|
18268
18388
|
}
|
|
18269
18389
|
try {
|
|
18270
|
-
const owner = JSON.parse(await
|
|
18390
|
+
const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
|
|
18271
18391
|
return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
|
|
18272
18392
|
} catch {
|
|
18273
18393
|
return null;
|
|
@@ -18283,14 +18403,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
|
|
|
18283
18403
|
}
|
|
18284
18404
|
async function cleanupOrphanedStaging(stateDir) {
|
|
18285
18405
|
const root = checkpointsRoot(stateDir);
|
|
18286
|
-
if (!
|
|
18406
|
+
if (!existsSync14(root)) return;
|
|
18287
18407
|
const now = Date.now();
|
|
18288
18408
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18289
18409
|
if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
|
|
18290
|
-
const stagingPath =
|
|
18410
|
+
const stagingPath = path52.join(root, entry.name);
|
|
18291
18411
|
let stale = false;
|
|
18292
18412
|
try {
|
|
18293
|
-
const owner = JSON.parse(await
|
|
18413
|
+
const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
|
|
18294
18414
|
const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
|
|
18295
18415
|
const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
|
|
18296
18416
|
let alive = false;
|
|
@@ -18330,9 +18450,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
|
|
|
18330
18450
|
|
|
18331
18451
|
// src/core/recovery/reconcile.ts
|
|
18332
18452
|
init_fingerprint2();
|
|
18333
|
-
import { existsSync as
|
|
18334
|
-
import { readFile as
|
|
18335
|
-
import
|
|
18453
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
18454
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
18455
|
+
import path53 from "node:path";
|
|
18336
18456
|
async function matchRecoverySide(resourceRoot, entries, side) {
|
|
18337
18457
|
for (const entry of entries) {
|
|
18338
18458
|
const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
|
|
@@ -18346,9 +18466,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18346
18466
|
loaded = await readRecoveryArtifact(stateDir, checkpointId);
|
|
18347
18467
|
} catch {
|
|
18348
18468
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18349
|
-
if (
|
|
18350
|
-
const manifestPath =
|
|
18351
|
-
const hash =
|
|
18469
|
+
if (existsSync15(artifactDir)) {
|
|
18470
|
+
const manifestPath = path53.join(artifactDir, "manifest.json");
|
|
18471
|
+
const hash = existsSync15(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
|
|
18352
18472
|
await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
|
|
18353
18473
|
}
|
|
18354
18474
|
return "corrupt";
|
|
@@ -18383,8 +18503,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18383
18503
|
|
|
18384
18504
|
// src/core/recovery/resource-identity.ts
|
|
18385
18505
|
init_fingerprint2();
|
|
18386
|
-
import { lstat as lstat8, readFile as
|
|
18387
|
-
import
|
|
18506
|
+
import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
|
|
18507
|
+
import path54 from "node:path";
|
|
18388
18508
|
async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
18389
18509
|
const resolvedRoot = await realpath3(resourceRoot);
|
|
18390
18510
|
if (resourceKind === "directory") {
|
|
@@ -18392,13 +18512,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
|
18392
18512
|
if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
|
|
18393
18513
|
return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
|
|
18394
18514
|
}
|
|
18395
|
-
const dotGit =
|
|
18515
|
+
const dotGit = path54.join(resolvedRoot, ".git");
|
|
18396
18516
|
const gitInfo = await lstat8(dotGit);
|
|
18397
18517
|
let gitMetadataPath = dotGit;
|
|
18398
18518
|
if (gitInfo.isFile()) {
|
|
18399
|
-
const marker = (await
|
|
18519
|
+
const marker = (await readFile12(dotGit, "utf8")).trim();
|
|
18400
18520
|
if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
|
|
18401
|
-
gitMetadataPath =
|
|
18521
|
+
gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
|
|
18402
18522
|
} else if (!gitInfo.isDirectory()) {
|
|
18403
18523
|
throw new Error("recovery_repo_identity_unavailable");
|
|
18404
18524
|
}
|
|
@@ -18463,7 +18583,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
|
|
|
18463
18583
|
async function prepareRecoveryCheckpoint(params) {
|
|
18464
18584
|
const backend = params.backend ?? "git_worktree";
|
|
18465
18585
|
const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
|
|
18466
|
-
await
|
|
18586
|
+
await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
|
|
18467
18587
|
await cleanupOrphanedStaging(params.stateDir);
|
|
18468
18588
|
await garbageCollect(params.stateDir, params.config, params.repoRoot);
|
|
18469
18589
|
const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
|
|
@@ -18471,16 +18591,16 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18471
18591
|
throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18472
18592
|
}
|
|
18473
18593
|
const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
|
|
18474
|
-
const temporary =
|
|
18594
|
+
const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
|
|
18475
18595
|
const finalDir = checkpointDir(params.stateDir, checkpointId);
|
|
18476
|
-
await
|
|
18477
|
-
await atomicWriteJson(
|
|
18596
|
+
await mkdir12(temporary, { recursive: true, mode: 448 });
|
|
18597
|
+
await atomicWriteJson(path55.join(temporary, "owner.json"), {
|
|
18478
18598
|
version: 1,
|
|
18479
18599
|
pid: process.pid,
|
|
18480
18600
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18481
18601
|
repoRoot: canonicalPath(params.repoRoot)
|
|
18482
18602
|
});
|
|
18483
|
-
await
|
|
18603
|
+
await mkdir12(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
|
|
18484
18604
|
try {
|
|
18485
18605
|
const entries = [];
|
|
18486
18606
|
const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
|
|
@@ -18489,8 +18609,8 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18489
18609
|
)) {
|
|
18490
18610
|
const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
|
|
18491
18611
|
if (protectedRoots.some((root) => {
|
|
18492
|
-
const relative =
|
|
18493
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
18612
|
+
const relative = path55.relative(root, target);
|
|
18613
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
|
|
18494
18614
|
})) {
|
|
18495
18615
|
throw new Error("recovery_protected_path");
|
|
18496
18616
|
}
|
|
@@ -18502,7 +18622,7 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18502
18622
|
entries.push({
|
|
18503
18623
|
path: change.relativePath,
|
|
18504
18624
|
before: await captureRecoverySnapshot(baseline, {
|
|
18505
|
-
blobDir:
|
|
18625
|
+
blobDir: path55.join(temporary, "blobs")
|
|
18506
18626
|
}),
|
|
18507
18627
|
after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
|
|
18508
18628
|
});
|
|
@@ -18539,12 +18659,12 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18539
18659
|
entries
|
|
18540
18660
|
};
|
|
18541
18661
|
const manifestHash = hashValue(canonicalStringify(manifest));
|
|
18542
|
-
await atomicWriteJson(
|
|
18662
|
+
await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
|
|
18543
18663
|
await writeRecoveryState(temporary, "prepared", manifestHash);
|
|
18544
18664
|
await fsyncPath2(temporary);
|
|
18545
18665
|
const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
|
|
18546
18666
|
if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18547
|
-
await
|
|
18667
|
+
await rename4(temporary, finalDir);
|
|
18548
18668
|
await fsyncPath2(checkpointsRoot(params.stateDir));
|
|
18549
18669
|
return {
|
|
18550
18670
|
checkpointId,
|
|
@@ -18582,7 +18702,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18582
18702
|
} catch {
|
|
18583
18703
|
try {
|
|
18584
18704
|
const raw = JSON.parse(
|
|
18585
|
-
await
|
|
18705
|
+
await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18586
18706
|
);
|
|
18587
18707
|
rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
|
|
18588
18708
|
} catch {
|
|
@@ -18616,7 +18736,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18616
18736
|
} catch {
|
|
18617
18737
|
try {
|
|
18618
18738
|
const manifest = JSON.parse(
|
|
18619
|
-
await
|
|
18739
|
+
await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18620
18740
|
);
|
|
18621
18741
|
if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
|
|
18622
18742
|
if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
|
|
@@ -18641,12 +18761,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18641
18761
|
async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
18642
18762
|
const root = checkpointsRoot(stateDir);
|
|
18643
18763
|
if (!repoRoot) return directorySize(root);
|
|
18644
|
-
if (!
|
|
18764
|
+
if (!existsSync16(root)) return 0;
|
|
18645
18765
|
const expected = canonicalPath(repoRoot);
|
|
18646
18766
|
let total = 0;
|
|
18647
18767
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
18648
18768
|
if (!entry.isDirectory()) continue;
|
|
18649
|
-
const entryPath =
|
|
18769
|
+
const entryPath = path55.join(root, entry.name);
|
|
18650
18770
|
if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18651
18771
|
if (await artifactRepoRoot(stateDir, entry.name) === expected) {
|
|
18652
18772
|
total += await directorySize(entryPath);
|
|
@@ -18655,7 +18775,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
|
18655
18775
|
}
|
|
18656
18776
|
if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18657
18777
|
try {
|
|
18658
|
-
const owner = JSON.parse(await
|
|
18778
|
+
const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
|
|
18659
18779
|
if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
|
|
18660
18780
|
total += await directorySize(entryPath);
|
|
18661
18781
|
}
|
|
@@ -18697,16 +18817,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
|
|
|
18697
18817
|
init_scrub();
|
|
18698
18818
|
|
|
18699
18819
|
// src/core/transactional/file-checkpoint-backend.ts
|
|
18700
|
-
import { cp, lstat as lstat11, mkdir as
|
|
18820
|
+
import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
|
|
18701
18821
|
import os5 from "node:os";
|
|
18702
|
-
import
|
|
18822
|
+
import path59 from "node:path";
|
|
18703
18823
|
|
|
18704
18824
|
// src/core/transactional/file-checkpoint-git.ts
|
|
18705
18825
|
init_path_utils();
|
|
18706
18826
|
import { spawn as spawn8 } from "node:child_process";
|
|
18707
18827
|
import { createHash as createHash13 } from "node:crypto";
|
|
18708
|
-
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as
|
|
18709
|
-
import
|
|
18828
|
+
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
|
|
18829
|
+
import path56 from "node:path";
|
|
18710
18830
|
var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
|
|
18711
18831
|
var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
|
|
18712
18832
|
var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
|
|
@@ -18740,7 +18860,7 @@ function rethrowStableFileCheckpointError(error) {
|
|
|
18740
18860
|
}
|
|
18741
18861
|
async function rootGitMetadataPresent(repoRoot) {
|
|
18742
18862
|
try {
|
|
18743
|
-
await lstat9(
|
|
18863
|
+
await lstat9(path56.join(repoRoot, ".git"));
|
|
18744
18864
|
return true;
|
|
18745
18865
|
} catch {
|
|
18746
18866
|
return false;
|
|
@@ -18789,10 +18909,10 @@ function execGit2(repoRoot, args) {
|
|
|
18789
18909
|
}
|
|
18790
18910
|
async function resolveGitPath(repoRoot, gitPath) {
|
|
18791
18911
|
const trimmed = gitPath.trim();
|
|
18792
|
-
if (
|
|
18912
|
+
if (path56.isAbsolute(trimmed)) {
|
|
18793
18913
|
return trimmed;
|
|
18794
18914
|
}
|
|
18795
|
-
return
|
|
18915
|
+
return path56.join(repoRoot, trimmed);
|
|
18796
18916
|
}
|
|
18797
18917
|
async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
|
|
18798
18918
|
await execGit2(sourceRoot, [
|
|
@@ -18834,7 +18954,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18834
18954
|
destinationRoot,
|
|
18835
18955
|
await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
|
|
18836
18956
|
);
|
|
18837
|
-
const destinationShared =
|
|
18957
|
+
const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
|
|
18838
18958
|
try {
|
|
18839
18959
|
await copyFile3(sourceShared, destinationShared);
|
|
18840
18960
|
} catch (error) {
|
|
@@ -18843,14 +18963,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18843
18963
|
}
|
|
18844
18964
|
async function readGitFile(gitDir, relativePath) {
|
|
18845
18965
|
try {
|
|
18846
|
-
return await
|
|
18966
|
+
return await readFile14(path56.join(gitDir, relativePath));
|
|
18847
18967
|
} catch {
|
|
18848
18968
|
return null;
|
|
18849
18969
|
}
|
|
18850
18970
|
}
|
|
18851
18971
|
async function readAbsoluteGitFile(absolutePath) {
|
|
18852
18972
|
try {
|
|
18853
|
-
return await
|
|
18973
|
+
return await readFile14(absolutePath);
|
|
18854
18974
|
} catch {
|
|
18855
18975
|
return null;
|
|
18856
18976
|
}
|
|
@@ -18867,8 +18987,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18867
18987
|
repoRoot,
|
|
18868
18988
|
await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
|
|
18869
18989
|
);
|
|
18870
|
-
const relative =
|
|
18871
|
-
const content = typeof relative === "string" && !relative.startsWith("..") && !
|
|
18990
|
+
const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
|
|
18991
|
+
const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18872
18992
|
if (content !== null) {
|
|
18873
18993
|
hashGitFileContent(hash, gitPath, content);
|
|
18874
18994
|
}
|
|
@@ -18878,13 +18998,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18878
18998
|
async function hashGitTree(gitDir, relativeDir, hash) {
|
|
18879
18999
|
let names;
|
|
18880
19000
|
try {
|
|
18881
|
-
names = await readdir4(
|
|
19001
|
+
names = await readdir4(path56.join(gitDir, relativeDir));
|
|
18882
19002
|
} catch {
|
|
18883
19003
|
return;
|
|
18884
19004
|
}
|
|
18885
19005
|
for (const name of names.sort()) {
|
|
18886
|
-
const relativePath = relativeDir ?
|
|
18887
|
-
const absolutePath =
|
|
19006
|
+
const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
|
|
19007
|
+
const absolutePath = path56.join(gitDir, relativePath);
|
|
18888
19008
|
let childNames = null;
|
|
18889
19009
|
try {
|
|
18890
19010
|
childNames = await readdir4(absolutePath);
|
|
@@ -18906,7 +19026,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
|
|
|
18906
19026
|
}
|
|
18907
19027
|
async function computeGitMetadataFingerprint(repoRoot) {
|
|
18908
19028
|
const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
|
|
18909
|
-
const gitDir =
|
|
19029
|
+
const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
|
|
18910
19030
|
const hash = createHash13("sha256");
|
|
18911
19031
|
for (const file of [
|
|
18912
19032
|
"HEAD",
|
|
@@ -18929,8 +19049,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18929
19049
|
const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
|
|
18930
19050
|
if (sharedIndex) {
|
|
18931
19051
|
const resolved = await resolveGitPath(repoRoot, sharedIndex);
|
|
18932
|
-
const relative =
|
|
18933
|
-
const content = relative && !relative.startsWith("..") && !
|
|
19052
|
+
const relative = path56.relative(gitDir, resolved);
|
|
19053
|
+
const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18934
19054
|
if (content !== null) {
|
|
18935
19055
|
hashGitFileContent(hash, "shared-index", content);
|
|
18936
19056
|
}
|
|
@@ -18941,7 +19061,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18941
19061
|
await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
|
|
18942
19062
|
}
|
|
18943
19063
|
try {
|
|
18944
|
-
const rootGitPath =
|
|
19064
|
+
const rootGitPath = path56.join(repoRoot, ".git");
|
|
18945
19065
|
const rootGitInfo = await lstat9(rootGitPath);
|
|
18946
19066
|
if (rootGitInfo.isFile()) {
|
|
18947
19067
|
const content = await readAbsoluteGitFile(rootGitPath);
|
|
@@ -18957,14 +19077,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18957
19077
|
function resolveExecutionCwdRelative(resourceRoot, cwd) {
|
|
18958
19078
|
const resolvedCwd = canonicalPath(cwd);
|
|
18959
19079
|
const resourceCanonical = canonicalPath(resourceRoot);
|
|
18960
|
-
const relative =
|
|
19080
|
+
const relative = path56.relative(resourceCanonical, resolvedCwd);
|
|
18961
19081
|
if (relative === "" || relative === ".") {
|
|
18962
19082
|
return "";
|
|
18963
19083
|
}
|
|
18964
|
-
if (relative.startsWith("..") ||
|
|
19084
|
+
if (relative.startsWith("..") || path56.isAbsolute(relative)) {
|
|
18965
19085
|
throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
|
|
18966
19086
|
}
|
|
18967
|
-
return relative.split(
|
|
19087
|
+
return relative.split(path56.sep).join("/");
|
|
18968
19088
|
}
|
|
18969
19089
|
|
|
18970
19090
|
// src/core/transactional/file-checkpoint-isolation.ts
|
|
@@ -18985,8 +19105,8 @@ function fileCheckpointIsolationReason(context) {
|
|
|
18985
19105
|
}
|
|
18986
19106
|
|
|
18987
19107
|
// src/core/transactional/file-checkpoint-staging.ts
|
|
18988
|
-
import { readdir as readdir5, readFile as
|
|
18989
|
-
import
|
|
19108
|
+
import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
|
|
19109
|
+
import path57 from "node:path";
|
|
18990
19110
|
function isOwnerProcessAlive(pid) {
|
|
18991
19111
|
try {
|
|
18992
19112
|
process.kill(pid, 0);
|
|
@@ -18996,12 +19116,12 @@ function isOwnerProcessAlive(pid) {
|
|
|
18996
19116
|
}
|
|
18997
19117
|
}
|
|
18998
19118
|
async function writeOwnerMarker(stagingRoot, marker) {
|
|
18999
|
-
await writeFile8(
|
|
19119
|
+
await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
|
|
19000
19120
|
`, "utf8");
|
|
19001
19121
|
}
|
|
19002
19122
|
async function readOwnerMarker(stagingRoot) {
|
|
19003
19123
|
try {
|
|
19004
|
-
const raw = await
|
|
19124
|
+
const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
|
|
19005
19125
|
return JSON.parse(raw.trim());
|
|
19006
19126
|
} catch {
|
|
19007
19127
|
return null;
|
|
@@ -19019,7 +19139,7 @@ async function collectDeadOwnerStaging(parentDir) {
|
|
|
19019
19139
|
if (!name.startsWith("belay-file-checkpoint-")) {
|
|
19020
19140
|
continue;
|
|
19021
19141
|
}
|
|
19022
|
-
const stagingRoot =
|
|
19142
|
+
const stagingRoot = path57.join(parentDir, name);
|
|
19023
19143
|
const marker = await readOwnerMarker(stagingRoot);
|
|
19024
19144
|
if (!marker) {
|
|
19025
19145
|
dead.push(stagingRoot);
|
|
@@ -19042,7 +19162,7 @@ import { constants as fsConstants2 } from "node:fs";
|
|
|
19042
19162
|
import {
|
|
19043
19163
|
copyFile as copyFile4,
|
|
19044
19164
|
lstat as lstat10,
|
|
19045
|
-
mkdir as
|
|
19165
|
+
mkdir as mkdir13,
|
|
19046
19166
|
mkdtemp as mkdtemp4,
|
|
19047
19167
|
readlink as readlink5,
|
|
19048
19168
|
rm as rm8,
|
|
@@ -19051,17 +19171,17 @@ import {
|
|
|
19051
19171
|
writeFile as writeFile9
|
|
19052
19172
|
} from "node:fs/promises";
|
|
19053
19173
|
import os4 from "node:os";
|
|
19054
|
-
import
|
|
19174
|
+
import path58 from "node:path";
|
|
19055
19175
|
var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
|
|
19056
19176
|
async function chmodSafe2(target, mode) {
|
|
19057
19177
|
try {
|
|
19058
|
-
const { chmod:
|
|
19059
|
-
await
|
|
19178
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
19179
|
+
await chmod5(target, mode & 511);
|
|
19060
19180
|
} catch {
|
|
19061
19181
|
}
|
|
19062
19182
|
}
|
|
19063
19183
|
async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
|
|
19064
|
-
await
|
|
19184
|
+
await mkdir13(path58.dirname(destinationPath), { recursive: true });
|
|
19065
19185
|
if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
|
|
19066
19186
|
try {
|
|
19067
19187
|
await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
|
|
@@ -19083,11 +19203,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
|
|
|
19083
19203
|
const info = await lstat10(sourcePath);
|
|
19084
19204
|
await rm8(destinationPath, { force: true, recursive: false });
|
|
19085
19205
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
19086
|
-
await
|
|
19206
|
+
await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
|
|
19087
19207
|
return strategy;
|
|
19088
19208
|
}
|
|
19089
19209
|
if (info.isSymbolicLink()) {
|
|
19090
|
-
await
|
|
19210
|
+
await mkdir13(path58.dirname(destinationPath), { recursive: true });
|
|
19091
19211
|
await symlink4(await readlink5(sourcePath), destinationPath);
|
|
19092
19212
|
return strategy;
|
|
19093
19213
|
}
|
|
@@ -19143,9 +19263,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
|
|
|
19143
19263
|
async function probeFileCloneStrategy() {
|
|
19144
19264
|
let tempDir = null;
|
|
19145
19265
|
try {
|
|
19146
|
-
tempDir = await mkdtemp4(
|
|
19147
|
-
const source =
|
|
19148
|
-
const destination =
|
|
19266
|
+
tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
|
|
19267
|
+
const source = path58.join(tempDir, "source.txt");
|
|
19268
|
+
const destination = path58.join(tempDir, "dest.txt");
|
|
19149
19269
|
await writeFile9(source, "probe\n");
|
|
19150
19270
|
if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
|
|
19151
19271
|
try {
|
|
@@ -19298,11 +19418,11 @@ async function protectedRootState(root) {
|
|
|
19298
19418
|
return `directory:${node.hash}:${index.treeHash}`;
|
|
19299
19419
|
}
|
|
19300
19420
|
function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
|
|
19301
|
-
const relative =
|
|
19302
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
19421
|
+
const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
|
|
19422
|
+
if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
|
|
19303
19423
|
return null;
|
|
19304
19424
|
}
|
|
19305
|
-
return
|
|
19425
|
+
return path59.join(executionRoot, relative);
|
|
19306
19426
|
}
|
|
19307
19427
|
async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
|
|
19308
19428
|
const states = /* @__PURE__ */ new Map();
|
|
@@ -19327,15 +19447,15 @@ async function directoryByteSize(root, deadlineMs) {
|
|
|
19327
19447
|
}
|
|
19328
19448
|
let total = 0;
|
|
19329
19449
|
for (const name of await readdir6(root)) {
|
|
19330
|
-
total += await directoryByteSize(
|
|
19450
|
+
total += await directoryByteSize(path59.join(root, name), deadlineMs);
|
|
19331
19451
|
}
|
|
19332
19452
|
return total;
|
|
19333
19453
|
}
|
|
19334
19454
|
async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
|
|
19335
19455
|
const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
|
|
19336
|
-
const sourceGitDir =
|
|
19337
|
-
const relativeGitDir =
|
|
19338
|
-
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ?
|
|
19456
|
+
const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
|
|
19457
|
+
const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
|
|
19458
|
+
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
|
|
19339
19459
|
await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
|
|
19340
19460
|
}
|
|
19341
19461
|
async function prepareDirtyGitSnapshot(context) {
|
|
@@ -19344,7 +19464,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19344
19464
|
const quotas = context.fileCheckpoint;
|
|
19345
19465
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19346
19466
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19347
|
-
const stagingRoot = await mkdtemp5(
|
|
19467
|
+
const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19348
19468
|
await writeOwnerMarker(stagingRoot, {
|
|
19349
19469
|
version: 1,
|
|
19350
19470
|
pid: process.pid,
|
|
@@ -19352,8 +19472,8 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19352
19472
|
resourceRoot: context.repoRoot,
|
|
19353
19473
|
backend: "file_checkpoint"
|
|
19354
19474
|
});
|
|
19355
|
-
const baselineRoot =
|
|
19356
|
-
const executionRoot =
|
|
19475
|
+
const baselineRoot = path59.join(stagingRoot, "baseline");
|
|
19476
|
+
const executionRoot = path59.join(stagingRoot, "execution");
|
|
19357
19477
|
try {
|
|
19358
19478
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19359
19479
|
const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
|
|
@@ -19387,7 +19507,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19387
19507
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19388
19508
|
}
|
|
19389
19509
|
await writeFile10(
|
|
19390
|
-
|
|
19510
|
+
path59.join(stagingRoot, "baseline-index.json"),
|
|
19391
19511
|
`${JSON.stringify(baselineIndex)}
|
|
19392
19512
|
`,
|
|
19393
19513
|
"utf8"
|
|
@@ -19437,7 +19557,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19437
19557
|
const quotas = context.fileCheckpoint;
|
|
19438
19558
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19439
19559
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19440
|
-
const stagingRoot = await mkdtemp5(
|
|
19560
|
+
const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19441
19561
|
await writeOwnerMarker(stagingRoot, {
|
|
19442
19562
|
version: 1,
|
|
19443
19563
|
pid: process.pid,
|
|
@@ -19445,8 +19565,8 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19445
19565
|
resourceRoot: context.repoRoot,
|
|
19446
19566
|
backend: "file_checkpoint"
|
|
19447
19567
|
});
|
|
19448
|
-
const baselineRoot =
|
|
19449
|
-
const executionRoot =
|
|
19568
|
+
const baselineRoot = path59.join(stagingRoot, "baseline");
|
|
19569
|
+
const executionRoot = path59.join(stagingRoot, "execution");
|
|
19450
19570
|
try {
|
|
19451
19571
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19452
19572
|
const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
|
|
@@ -19455,7 +19575,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19455
19575
|
quotas,
|
|
19456
19576
|
deadlineMs
|
|
19457
19577
|
});
|
|
19458
|
-
await
|
|
19578
|
+
await mkdir14(baselineRoot, { recursive: true });
|
|
19459
19579
|
const baselineIndex = await buildFileTreeIndex({
|
|
19460
19580
|
resourceRoot: baselineRoot,
|
|
19461
19581
|
excludedRoots,
|
|
@@ -19475,7 +19595,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19475
19595
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19476
19596
|
}
|
|
19477
19597
|
await writeFile10(
|
|
19478
|
-
|
|
19598
|
+
path59.join(stagingRoot, "baseline-index.json"),
|
|
19479
19599
|
`${JSON.stringify(baselineIndex)}
|
|
19480
19600
|
`,
|
|
19481
19601
|
"utf8"
|
|
@@ -19485,7 +19605,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19485
19605
|
quotas,
|
|
19486
19606
|
deadlineMs
|
|
19487
19607
|
});
|
|
19488
|
-
await
|
|
19608
|
+
await mkdir14(executionRoot, { recursive: true });
|
|
19489
19609
|
const finalSourceIndex = await buildFileTreeIndex({
|
|
19490
19610
|
resourceRoot: context.repoRoot,
|
|
19491
19611
|
excludedRoots,
|
|
@@ -19828,10 +19948,10 @@ async function selectTransactionalBackend(context) {
|
|
|
19828
19948
|
}
|
|
19829
19949
|
|
|
19830
19950
|
// src/core/transactional/diff-evaluator.ts
|
|
19831
|
-
import
|
|
19951
|
+
import path60 from "node:path";
|
|
19832
19952
|
init_path_utils();
|
|
19833
19953
|
function categorizeChange(change, ctx) {
|
|
19834
|
-
const absolutePath = canonicalPath(
|
|
19954
|
+
const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
|
|
19835
19955
|
if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
|
|
19836
19956
|
return "repo_outside";
|
|
19837
19957
|
}
|
|
@@ -20381,17 +20501,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
|
|
|
20381
20501
|
}
|
|
20382
20502
|
|
|
20383
20503
|
// src/core/notify.ts
|
|
20504
|
+
init_path_utils();
|
|
20384
20505
|
import { execFile } from "node:child_process";
|
|
20506
|
+
import path61 from "node:path";
|
|
20385
20507
|
import { promisify } from "node:util";
|
|
20386
20508
|
var execFileAsync = promisify(execFile);
|
|
20387
|
-
|
|
20388
|
-
|
|
20389
|
-
|
|
20509
|
+
var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
20510
|
+
function webhookConfigIssue(url) {
|
|
20511
|
+
let parsed;
|
|
20512
|
+
try {
|
|
20513
|
+
parsed = new URL(url);
|
|
20514
|
+
} catch {
|
|
20515
|
+
return `notifications.webhookUrl is invalid: ${url}`;
|
|
20516
|
+
}
|
|
20517
|
+
if (parsed.protocol === "https:") {
|
|
20518
|
+
return null;
|
|
20519
|
+
}
|
|
20520
|
+
const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
20521
|
+
if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
|
|
20522
|
+
return null;
|
|
20523
|
+
}
|
|
20524
|
+
return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
|
|
20525
|
+
}
|
|
20526
|
+
function commandHookConfigIssue(commandHook, repoRoot) {
|
|
20527
|
+
if (!path61.isAbsolute(commandHook)) {
|
|
20528
|
+
return `notifications.commandHook must be an absolute path: ${commandHook}`;
|
|
20529
|
+
}
|
|
20530
|
+
if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
|
|
20531
|
+
return `notifications.commandHook must not be inside the repository: ${commandHook}`;
|
|
20532
|
+
}
|
|
20533
|
+
return null;
|
|
20534
|
+
}
|
|
20535
|
+
async function notifyDeny(config, event, deps = {
|
|
20536
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
20537
|
+
execFile: (file, args, options) => execFileAsync(file, [...args], options)
|
|
20538
|
+
}) {
|
|
20539
|
+
const payload = JSON.stringify({
|
|
20540
|
+
approvalId: event.approvalId,
|
|
20541
|
+
reason: event.reason,
|
|
20542
|
+
summary: event.summary,
|
|
20543
|
+
repoRoot: event.repoRoot,
|
|
20544
|
+
fingerprint: event.fingerprint
|
|
20545
|
+
});
|
|
20546
|
+
const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
|
|
20547
|
+
if (config.webhookUrl && !webhookIssue) {
|
|
20390
20548
|
try {
|
|
20391
20549
|
const controller = new AbortController();
|
|
20392
20550
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
20393
20551
|
try {
|
|
20394
|
-
await fetch(config.webhookUrl, {
|
|
20552
|
+
await deps.fetch(config.webhookUrl, {
|
|
20395
20553
|
method: "POST",
|
|
20396
20554
|
headers: { "content-type": "application/json" },
|
|
20397
20555
|
body: payload,
|
|
@@ -20403,17 +20561,16 @@ async function notifyDeny(config, event) {
|
|
|
20403
20561
|
} catch {
|
|
20404
20562
|
}
|
|
20405
20563
|
}
|
|
20406
|
-
|
|
20564
|
+
const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
|
|
20565
|
+
if (config.commandHook && !commandHookIssue) {
|
|
20407
20566
|
try {
|
|
20408
|
-
await
|
|
20567
|
+
await deps.execFile(config.commandHook, [], {
|
|
20409
20568
|
env: {
|
|
20410
|
-
...process.env,
|
|
20411
20569
|
BELAY_APPROVAL_ID: event.approvalId,
|
|
20412
20570
|
BELAY_REASON: event.reason,
|
|
20413
20571
|
BELAY_SUMMARY: event.summary,
|
|
20414
20572
|
BELAY_REPO_ROOT: event.repoRoot,
|
|
20415
|
-
BELAY_FINGERPRINT: event.fingerprint
|
|
20416
|
-
BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
|
|
20573
|
+
BELAY_FINGERPRINT: event.fingerprint
|
|
20417
20574
|
}
|
|
20418
20575
|
});
|
|
20419
20576
|
} catch {
|
|
@@ -20423,9 +20580,10 @@ async function notifyDeny(config, event) {
|
|
|
20423
20580
|
|
|
20424
20581
|
// src/adapters/shared/gate-runtime.ts
|
|
20425
20582
|
init_path_utils();
|
|
20583
|
+
init_repo_config_trust();
|
|
20426
20584
|
|
|
20427
20585
|
// src/adapters/layouts/protected-paths.ts
|
|
20428
|
-
import
|
|
20586
|
+
import path62 from "node:path";
|
|
20429
20587
|
function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
20430
20588
|
const roots = [
|
|
20431
20589
|
layout.configPath(repoRoot),
|
|
@@ -20437,7 +20595,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
|
20437
20595
|
if (controlPlaneDir) {
|
|
20438
20596
|
roots.push(controlPlaneDir);
|
|
20439
20597
|
}
|
|
20440
|
-
return roots.map((entry) =>
|
|
20598
|
+
return roots.map((entry) => path62.resolve(entry));
|
|
20441
20599
|
}
|
|
20442
20600
|
|
|
20443
20601
|
// src/adapters/shared/gate-runtime.ts
|
|
@@ -20478,7 +20636,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
|
|
|
20478
20636
|
}
|
|
20479
20637
|
async function loadJsonFile(filePath, fallback) {
|
|
20480
20638
|
try {
|
|
20481
|
-
const raw = await
|
|
20639
|
+
const raw = await readFile16(filePath, "utf8");
|
|
20482
20640
|
return JSON.parse(raw);
|
|
20483
20641
|
} catch {
|
|
20484
20642
|
return fallback;
|
|
@@ -20487,11 +20645,18 @@ async function loadJsonFile(filePath, fallback) {
|
|
|
20487
20645
|
function createDefaultGateRuntimeDeps() {
|
|
20488
20646
|
return {
|
|
20489
20647
|
async readConfig(configPath) {
|
|
20490
|
-
|
|
20648
|
+
try {
|
|
20649
|
+
return JSON.parse(await readFile16(configPath, "utf8"));
|
|
20650
|
+
} catch (error) {
|
|
20651
|
+
if (error.code === "ENOENT") {
|
|
20652
|
+
return {};
|
|
20653
|
+
}
|
|
20654
|
+
throw error;
|
|
20655
|
+
}
|
|
20491
20656
|
},
|
|
20492
20657
|
async appendAudit(ctx, event) {
|
|
20493
|
-
const auditPath =
|
|
20494
|
-
await
|
|
20658
|
+
const auditPath = path63.join(ctx.repoRoot, ctx.config.audit.logPath);
|
|
20659
|
+
await mkdir15(path63.dirname(auditPath), { recursive: true });
|
|
20495
20660
|
const provenance = auditProvenance(ctx.config);
|
|
20496
20661
|
const record = {
|
|
20497
20662
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -20524,7 +20689,7 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20524
20689
|
};
|
|
20525
20690
|
},
|
|
20526
20691
|
async writeApprovals(filePath, state) {
|
|
20527
|
-
await
|
|
20692
|
+
await mkdir15(path63.dirname(filePath), { recursive: true });
|
|
20528
20693
|
await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
|
|
20529
20694
|
`, "utf8");
|
|
20530
20695
|
},
|
|
@@ -20545,10 +20710,11 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20545
20710
|
}
|
|
20546
20711
|
async function resolveGateConfig(ctx, deps) {
|
|
20547
20712
|
const loaded = await deps.readConfig(ctx.configPath);
|
|
20713
|
+
await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
|
|
20548
20714
|
let teamConfig = null;
|
|
20549
20715
|
const teamPath = teamConfigPath();
|
|
20550
|
-
if (
|
|
20551
|
-
teamConfig = JSON.parse(await
|
|
20716
|
+
if (existsSync17(teamPath)) {
|
|
20717
|
+
teamConfig = JSON.parse(await readFile16(teamPath, "utf8"));
|
|
20552
20718
|
}
|
|
20553
20719
|
return resolveLayeredConfig({
|
|
20554
20720
|
repoConfig: loaded,
|
|
@@ -20686,7 +20852,7 @@ function deriveWorkspaceRootScopeHint(params) {
|
|
|
20686
20852
|
if (!targetPath) {
|
|
20687
20853
|
return void 0;
|
|
20688
20854
|
}
|
|
20689
|
-
const candidateRoot = canonicalPath(
|
|
20855
|
+
const candidateRoot = canonicalPath(path63.dirname(targetPath));
|
|
20690
20856
|
const validation = validateTrustedWorkspaceRootCandidate({
|
|
20691
20857
|
candidatePath: candidateRoot,
|
|
20692
20858
|
repoRoot: action.repoRoot,
|
|
@@ -21077,7 +21243,21 @@ async function evaluateGatedAction(ctx, deps, params) {
|
|
|
21077
21243
|
...authorization,
|
|
21078
21244
|
egressProxyActive
|
|
21079
21245
|
};
|
|
21080
|
-
|
|
21246
|
+
let predicted = await classifyGatedActionAsync(action, ctx.config, enrichedClassifierOptions);
|
|
21247
|
+
if (action.kind === "tool" && predicted.reason === "unclassified_tool" && (ctx.config.policy.codexUnmappedTool ?? "deny") === "deny") {
|
|
21248
|
+
predicted = {
|
|
21249
|
+
...predicted,
|
|
21250
|
+
verdict: "deny_pending_approval",
|
|
21251
|
+
reason: "unmapped_tool",
|
|
21252
|
+
assessment: {
|
|
21253
|
+
reversibility: "irreversible",
|
|
21254
|
+
external: false,
|
|
21255
|
+
blastRadius: "unknown tool action",
|
|
21256
|
+
confidence: 0.5,
|
|
21257
|
+
signals: ["unmapped_tool"]
|
|
21258
|
+
}
|
|
21259
|
+
};
|
|
21260
|
+
}
|
|
21081
21261
|
if (action.kind === "shell" && action.command && isContainedUnknownExecutionEligible(ctx.config, action, predicted)) {
|
|
21082
21262
|
const mediated = await mediateContainedUnknownExecution({
|
|
21083
21263
|
ctx,
|
|
@@ -21262,7 +21442,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21262
21442
|
event: auditEvent,
|
|
21263
21443
|
sourceEvent,
|
|
21264
21444
|
kind,
|
|
21265
|
-
repoLabel:
|
|
21445
|
+
repoLabel: path63.basename(ctx.repoRoot),
|
|
21266
21446
|
...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
|
|
21267
21447
|
fingerprint: result.fingerprint,
|
|
21268
21448
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
@@ -21294,21 +21474,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21294
21474
|
if (created) {
|
|
21295
21475
|
await recordGateApprovalAsk(stateDir, result.reason, false);
|
|
21296
21476
|
}
|
|
21297
|
-
let approvalToken;
|
|
21298
|
-
try {
|
|
21299
|
-
approvalToken = await issueApprovalToken(
|
|
21300
|
-
{
|
|
21301
|
-
approvalId: approval.approvalId,
|
|
21302
|
-
fingerprint: approval.fingerprint,
|
|
21303
|
-
repoRoot: approval.repoRoot,
|
|
21304
|
-
issuedAt: approval.createdAt,
|
|
21305
|
-
expiresAt: approval.expiresAt
|
|
21306
|
-
},
|
|
21307
|
-
configuredControlPlaneDir(ctx.config)
|
|
21308
|
-
);
|
|
21309
|
-
} catch {
|
|
21310
|
-
approvalToken = void 0;
|
|
21311
|
-
}
|
|
21312
21477
|
const denialReason = failure?.reason ?? result.reason;
|
|
21313
21478
|
if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
|
|
21314
21479
|
await notifyDeny(ctx.config.notifications, {
|
|
@@ -21316,8 +21481,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21316
21481
|
reason: denialReason,
|
|
21317
21482
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
21318
21483
|
repoRoot: ctx.repoRoot,
|
|
21319
|
-
fingerprint: result.fingerprint
|
|
21320
|
-
approvalToken
|
|
21484
|
+
fingerprint: result.fingerprint
|
|
21321
21485
|
});
|
|
21322
21486
|
}
|
|
21323
21487
|
const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
|
|
@@ -21728,38 +21892,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
|
|
|
21728
21892
|
}
|
|
21729
21893
|
|
|
21730
21894
|
// src/adapters/shared/repo-root.ts
|
|
21731
|
-
import { existsSync as
|
|
21732
|
-
import
|
|
21895
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
21896
|
+
import path64 from "node:path";
|
|
21733
21897
|
function belayConfigPath(current, adapterName) {
|
|
21734
21898
|
if (adapterName === "cursor") {
|
|
21735
|
-
return
|
|
21899
|
+
return path64.join(current, ".cursor", "belay.config.json");
|
|
21736
21900
|
}
|
|
21737
21901
|
if (adapterName === "claude") {
|
|
21738
|
-
return
|
|
21902
|
+
return path64.join(current, ".claude", "belay.config.json");
|
|
21739
21903
|
}
|
|
21740
|
-
return
|
|
21904
|
+
return path64.join(current, ".codex", "belay.config.json");
|
|
21741
21905
|
}
|
|
21742
21906
|
function markerMatches(current, marker, layout) {
|
|
21743
|
-
const markerPath =
|
|
21744
|
-
if (!
|
|
21907
|
+
const markerPath = path64.join(current, marker);
|
|
21908
|
+
if (!existsSync18(markerPath)) {
|
|
21745
21909
|
return false;
|
|
21746
21910
|
}
|
|
21747
21911
|
if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
|
|
21748
|
-
return
|
|
21912
|
+
return existsSync18(belayConfigPath(current, layout.name));
|
|
21749
21913
|
}
|
|
21750
21914
|
return true;
|
|
21751
21915
|
}
|
|
21752
21916
|
function findRepoRoot(startPath, layout) {
|
|
21753
|
-
let current =
|
|
21917
|
+
let current = path64.resolve(startPath);
|
|
21754
21918
|
while (true) {
|
|
21755
21919
|
for (const marker of layout.repoRootMarkers) {
|
|
21756
21920
|
if (markerMatches(current, marker, layout)) {
|
|
21757
21921
|
return current;
|
|
21758
21922
|
}
|
|
21759
21923
|
}
|
|
21760
|
-
const parent =
|
|
21924
|
+
const parent = path64.dirname(current);
|
|
21761
21925
|
if (parent === current) {
|
|
21762
|
-
return
|
|
21926
|
+
return path64.resolve(startPath);
|
|
21763
21927
|
}
|
|
21764
21928
|
current = parent;
|
|
21765
21929
|
}
|