@rpamis/comet 0.4.0-beta.8 → 0.4.0-beta.9
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 +1 -1
- package/assets/manifest.json +1 -1
- package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
- package/assets/skills/comet/scripts/comet-entry-runtime.mjs +95 -4
- package/assets/skills/comet/scripts/comet-hook-router.mjs +224 -35
- package/assets/skills/comet/scripts/comet-runtime.mjs +218 -102
- package/assets/skills/comet-archive/SKILL.md +29 -20
- package/assets/skills/comet-native/SKILL.md +23 -15
- package/assets/skills/comet-native/reference/artifacts.md +19 -4
- package/assets/skills/comet-native/reference/commands.md +8 -2
- package/assets/skills/comet-native/reference/recovery.md +1 -1
- package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1052 -458
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
- package/assets/skills-zh/comet-archive/SKILL.md +29 -20
- package/assets/skills-zh/comet-native/SKILL.md +23 -15
- package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
- package/assets/skills-zh/comet-native/reference/commands.md +8 -2
- package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
- package/dist/app/commands/init.d.ts.map +1 -1
- package/dist/app/commands/init.js +3 -6
- package/dist/app/commands/init.js.map +1 -1
- package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
- package/dist/domains/comet-entry/current-selection.js +6 -8
- package/dist/domains/comet-entry/current-selection.js.map +1 -1
- package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
- package/dist/domains/comet-entry/project-status.js +5 -1
- package/dist/domains/comet-entry/project-status.js.map +1 -1
- package/dist/domains/comet-native/native-build-evidence.js +2 -2
- package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
- package/dist/domains/comet-native/native-change.d.ts +4 -2
- package/dist/domains/comet-native/native-change.d.ts.map +1 -1
- package/dist/domains/comet-native/native-change.js +18 -4
- package/dist/domains/comet-native/native-change.js.map +1 -1
- package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
- package/dist/domains/comet-native/native-check-receipt.js +5 -3
- package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
- package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
- package/dist/domains/comet-native/native-cli.js +120 -16
- package/dist/domains/comet-native/native-cli.js.map +1 -1
- package/dist/domains/comet-native/native-config.d.ts +5 -1
- package/dist/domains/comet-native/native-config.d.ts.map +1 -1
- package/dist/domains/comet-native/native-config.js +78 -0
- package/dist/domains/comet-native/native-config.js.map +1 -1
- package/dist/domains/comet-native/native-continuation.d.ts +2 -1
- package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
- package/dist/domains/comet-native/native-continuation.js +13 -5
- package/dist/domains/comet-native/native-continuation.js.map +1 -1
- package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
- package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
- package/dist/domains/comet-native/native-diagnostics.js +19 -9
- package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
- package/dist/domains/comet-native/native-file-identity.d.ts +2 -8
- package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -1
- package/dist/domains/comet-native/native-file-identity.js +1 -20
- package/dist/domains/comet-native/native-file-identity.js.map +1 -1
- package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
- package/dist/domains/comet-native/native-findings.js +19 -1
- package/dist/domains/comet-native/native-findings.js.map +1 -1
- package/dist/domains/comet-native/native-guards.d.ts +2 -1
- package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
- package/dist/domains/comet-native/native-guards.js +14 -0
- package/dist/domains/comet-native/native-guards.js.map +1 -1
- package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
- package/dist/domains/comet-native/native-lock.js +24 -32
- package/dist/domains/comet-native/native-lock.js.map +1 -1
- package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
- package/dist/domains/comet-native/native-root-move.js +2 -0
- package/dist/domains/comet-native/native-root-move.js.map +1 -1
- package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
- package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
- package/dist/domains/comet-native/native-snapshot.js +218 -2
- package/dist/domains/comet-native/native-snapshot.js.map +1 -1
- package/dist/domains/comet-native/native-transitions.d.ts +2 -1
- package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
- package/dist/domains/comet-native/native-transitions.js +34 -11
- package/dist/domains/comet-native/native-transitions.js.map +1 -1
- package/dist/domains/comet-native/native-types.d.ts +16 -0
- package/dist/domains/comet-native/native-types.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-runtime.js +5 -3
- package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
- package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
- package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-scope.js +6 -1
- package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
- package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
- package/dist/domains/dashboard/native-collector.js +4 -1
- package/dist/domains/dashboard/native-collector.js.map +1 -1
- package/dist/domains/skill/platform-install.d.ts.map +1 -1
- package/dist/domains/skill/platform-install.js +9 -0
- package/dist/domains/skill/platform-install.js.map +1 -1
- package/dist/domains/skill/project-instructions.d.ts.map +1 -1
- package/dist/domains/skill/project-instructions.js +4 -2
- package/dist/domains/skill/project-instructions.js.map +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
- package/dist/domains/workflow-contract/project-config.js +24 -2
- package/dist/domains/workflow-contract/project-config.js.map +1 -1
- package/dist/platform/fs/file-identity.d.ts +9 -0
- package/dist/platform/fs/file-identity.d.ts.map +1 -0
- package/dist/platform/fs/file-identity.js +21 -0
- package/dist/platform/fs/file-identity.js.map +1 -0
- package/dist/platform/fs/file-system.d.ts.map +1 -1
- package/dist/platform/fs/file-system.js +13 -9
- package/dist/platform/fs/file-system.js.map +1 -1
- package/dist/platform/fs/race-safe-read.d.ts +47 -0
- package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
- package/dist/platform/fs/race-safe-read.js +132 -0
- package/dist/platform/fs/race-safe-read.js.map +1 -0
- package/eval/README.md +1 -1
- package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
- package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
- package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
- package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
- package/eval/local/tasks/index.yaml +6 -0
- package/eval/local/tests/conftest.py +146 -2
- package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
- package/eval/local/tests/scaffold/test_tasks.py +440 -8
- package/eval/local/tests/scaffold/test_utils.py +114 -1
- package/eval/local/tests/tasks/test_tasks.py +2 -0
- package/eval/scaffold/python/aligned_comparison.py +1 -0
- package/eval/scaffold/python/tasks.py +2 -0
- package/eval/scaffold/shell/docker.sh +1 -1
- package/eval/scaffold/shell/run-claude-loop.sh +25 -2
- package/package.json +1 -1
|
@@ -6915,14 +6915,14 @@ var require_parser = __commonJS({
|
|
|
6915
6915
|
case "scalar":
|
|
6916
6916
|
case "single-quoted-scalar":
|
|
6917
6917
|
case "double-quoted-scalar": {
|
|
6918
|
-
const
|
|
6918
|
+
const fs34 = this.flowScalar(this.type);
|
|
6919
6919
|
if (atNextItem || it.value) {
|
|
6920
|
-
map.items.push({ start, key:
|
|
6920
|
+
map.items.push({ start, key: fs34, sep: [] });
|
|
6921
6921
|
this.onKeyLine = true;
|
|
6922
6922
|
} else if (it.sep) {
|
|
6923
|
-
this.stack.push(
|
|
6923
|
+
this.stack.push(fs34);
|
|
6924
6924
|
} else {
|
|
6925
|
-
Object.assign(it, { key:
|
|
6925
|
+
Object.assign(it, { key: fs34, sep: [] });
|
|
6926
6926
|
this.onKeyLine = true;
|
|
6927
6927
|
}
|
|
6928
6928
|
return;
|
|
@@ -7050,13 +7050,13 @@ var require_parser = __commonJS({
|
|
|
7050
7050
|
case "scalar":
|
|
7051
7051
|
case "single-quoted-scalar":
|
|
7052
7052
|
case "double-quoted-scalar": {
|
|
7053
|
-
const
|
|
7053
|
+
const fs34 = this.flowScalar(this.type);
|
|
7054
7054
|
if (!it || it.value)
|
|
7055
|
-
fc.items.push({ start: [], key:
|
|
7055
|
+
fc.items.push({ start: [], key: fs34, sep: [] });
|
|
7056
7056
|
else if (it.sep)
|
|
7057
|
-
this.stack.push(
|
|
7057
|
+
this.stack.push(fs34);
|
|
7058
7058
|
else
|
|
7059
|
-
Object.assign(it, { key:
|
|
7059
|
+
Object.assign(it, { key: fs34, sep: [] });
|
|
7060
7060
|
return;
|
|
7061
7061
|
}
|
|
7062
7062
|
case "flow-map-end":
|
|
@@ -7370,9 +7370,127 @@ import { pathToFileURL } from "url";
|
|
|
7370
7370
|
// domains/comet-native/native-cli.ts
|
|
7371
7371
|
import path47 from "path";
|
|
7372
7372
|
|
|
7373
|
+
// platform/fs/race-safe-read.ts
|
|
7374
|
+
import { constants as fsConstants, promises as fs } from "fs";
|
|
7375
|
+
|
|
7376
|
+
// platform/fs/file-identity.ts
|
|
7377
|
+
function hasPlatformIdentity(value) {
|
|
7378
|
+
return value !== 0 && value !== 0n && value !== "0";
|
|
7379
|
+
}
|
|
7380
|
+
function hasComparableFileObject(left, right) {
|
|
7381
|
+
return hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev) && hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
7382
|
+
}
|
|
7383
|
+
function sameFileObject(left, right) {
|
|
7384
|
+
const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
|
|
7385
|
+
if (comparableDevice && left.dev !== right.dev) return false;
|
|
7386
|
+
const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
7387
|
+
if (comparableInode && left.ino !== right.ino) return false;
|
|
7388
|
+
if (comparableDevice && comparableInode) return true;
|
|
7389
|
+
return left.birthtime === right.birthtime;
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
// platform/fs/race-safe-read.ts
|
|
7393
|
+
var RaceSafeReadError = class extends Error {
|
|
7394
|
+
reason;
|
|
7395
|
+
constructor(reason, message, options) {
|
|
7396
|
+
super(message, options);
|
|
7397
|
+
this.name = "RaceSafeReadError";
|
|
7398
|
+
this.reason = reason;
|
|
7399
|
+
}
|
|
7400
|
+
};
|
|
7401
|
+
function birthtimeOf(stat) {
|
|
7402
|
+
return "birthtimeNs" in stat && typeof stat.birthtimeNs === "bigint" ? stat.birthtimeNs : stat.birthtimeMs;
|
|
7403
|
+
}
|
|
7404
|
+
function ctimeOf(stat) {
|
|
7405
|
+
return "ctimeNs" in stat && typeof stat.ctimeNs === "bigint" ? stat.ctimeNs : stat.ctimeMs;
|
|
7406
|
+
}
|
|
7407
|
+
function identityOf(stat) {
|
|
7408
|
+
return { dev: stat.dev, ino: stat.ino, birthtime: birthtimeOf(stat) };
|
|
7409
|
+
}
|
|
7410
|
+
function sameStatIdentity(left, right) {
|
|
7411
|
+
const leftObject = identityOf(left);
|
|
7412
|
+
const rightObject = identityOf(right);
|
|
7413
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
7414
|
+
return sameFileObject(leftObject, rightObject);
|
|
7415
|
+
}
|
|
7416
|
+
return sameFileObject(leftObject, rightObject) && birthtimeOf(left) === birthtimeOf(right) && ctimeOf(left) === ctimeOf(right) && left.size === right.size;
|
|
7417
|
+
}
|
|
7418
|
+
async function readFileRaceSafe(file, maxBytes, options = {}) {
|
|
7419
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
|
7420
|
+
throw new Error("race-safe read byte limit must be a positive integer");
|
|
7421
|
+
}
|
|
7422
|
+
const label = options.label ?? "file";
|
|
7423
|
+
const bigint = options.bigint === true;
|
|
7424
|
+
const before = await fs.lstat(file, { bigint });
|
|
7425
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7426
|
+
throw new RaceSafeReadError("not-regular-file", `${label} must be a regular file`);
|
|
7427
|
+
}
|
|
7428
|
+
if (BigInt(before.size) > BigInt(maxBytes)) {
|
|
7429
|
+
throw new RaceSafeReadError("too-large", `${label} exceeds ${maxBytes} bytes`);
|
|
7430
|
+
}
|
|
7431
|
+
const beforeRealPath = await fs.realpath(file);
|
|
7432
|
+
await options.verify?.("pre-open", { realPath: beforeRealPath, identity: identityOf(before) });
|
|
7433
|
+
const flags = process.platform === "win32" ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
|
|
7434
|
+
let handle;
|
|
7435
|
+
try {
|
|
7436
|
+
handle = await fs.open(file, flags);
|
|
7437
|
+
} catch (error) {
|
|
7438
|
+
if (error.code === "ELOOP") {
|
|
7439
|
+
throw new RaceSafeReadError("not-regular-file", `${label} must be a regular file`, {
|
|
7440
|
+
cause: error
|
|
7441
|
+
});
|
|
7442
|
+
}
|
|
7443
|
+
throw error;
|
|
7444
|
+
}
|
|
7445
|
+
try {
|
|
7446
|
+
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
7447
|
+
handle.stat({ bigint }),
|
|
7448
|
+
fs.lstat(file, { bigint }),
|
|
7449
|
+
fs.realpath(file)
|
|
7450
|
+
]);
|
|
7451
|
+
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameStatIdentity(before, opened) || !sameStatIdentity(before, pathAfterOpen)) {
|
|
7452
|
+
throw new RaceSafeReadError("changed", `${label} changed while opening`);
|
|
7453
|
+
}
|
|
7454
|
+
await options.verify?.("post-open", {
|
|
7455
|
+
realPath: realPathAfterOpen,
|
|
7456
|
+
identity: identityOf(opened)
|
|
7457
|
+
});
|
|
7458
|
+
await options.hooks?.afterOpen?.();
|
|
7459
|
+
const chunks = [];
|
|
7460
|
+
let total = 0;
|
|
7461
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
|
|
7462
|
+
for (; ; ) {
|
|
7463
|
+
const remaining = maxBytes + 1 - total;
|
|
7464
|
+
const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, remaining), null);
|
|
7465
|
+
if (bytesRead === 0) break;
|
|
7466
|
+
total += bytesRead;
|
|
7467
|
+
if (total > maxBytes) {
|
|
7468
|
+
throw new RaceSafeReadError("too-large", `${label} exceeds ${maxBytes} bytes`);
|
|
7469
|
+
}
|
|
7470
|
+
chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
|
|
7471
|
+
}
|
|
7472
|
+
await options.hooks?.beforeFinalCheck?.();
|
|
7473
|
+
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
7474
|
+
handle.stat({ bigint }),
|
|
7475
|
+
fs.lstat(file, { bigint }),
|
|
7476
|
+
fs.realpath(file)
|
|
7477
|
+
]);
|
|
7478
|
+
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameStatIdentity(before, afterHandle) || !sameStatIdentity(before, afterPath)) {
|
|
7479
|
+
throw new RaceSafeReadError("changed", `${label} changed while reading`);
|
|
7480
|
+
}
|
|
7481
|
+
await options.verify?.("post-read", {
|
|
7482
|
+
realPath: afterRealPath,
|
|
7483
|
+
identity: identityOf(afterHandle)
|
|
7484
|
+
});
|
|
7485
|
+
return { bytes: Buffer.concat(chunks, total), stat: afterHandle, realPath: afterRealPath };
|
|
7486
|
+
} finally {
|
|
7487
|
+
await handle.close();
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
|
|
7373
7491
|
// domains/comet-native/native-archive.ts
|
|
7374
7492
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
7375
|
-
import { promises as
|
|
7493
|
+
import { promises as fs23 } from "fs";
|
|
7376
7494
|
import path34 from "path";
|
|
7377
7495
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
7378
7496
|
|
|
@@ -7510,17 +7628,17 @@ function recordOutcomeWithResolver(pkg, state, outcome, resolver, context) {
|
|
|
7510
7628
|
}
|
|
7511
7629
|
|
|
7512
7630
|
// domains/comet-native/native-artifacts.ts
|
|
7513
|
-
import { promises as
|
|
7631
|
+
import { promises as fs14 } from "fs";
|
|
7514
7632
|
import path17 from "path";
|
|
7515
7633
|
|
|
7516
7634
|
// domains/comet-native/native-change.ts
|
|
7517
7635
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
7518
|
-
import { promises as
|
|
7636
|
+
import { promises as fs13 } from "fs";
|
|
7519
7637
|
import path16 from "path";
|
|
7520
7638
|
|
|
7521
7639
|
// domains/comet-native/native-bounded-file.ts
|
|
7522
7640
|
import { createHash as createHash2 } from "node:crypto";
|
|
7523
|
-
import { promises as
|
|
7641
|
+
import { promises as fs2 } from "node:fs";
|
|
7524
7642
|
import path2 from "node:path";
|
|
7525
7643
|
import { TextDecoder } from "node:util";
|
|
7526
7644
|
|
|
@@ -7586,22 +7704,6 @@ function nativeSensitiveArtifactReason(paths, relativeRef) {
|
|
|
7586
7704
|
return null;
|
|
7587
7705
|
}
|
|
7588
7706
|
|
|
7589
|
-
// domains/comet-native/native-file-identity.ts
|
|
7590
|
-
function hasPlatformIdentity(value) {
|
|
7591
|
-
return value !== 0 && value !== 0n && value !== "0";
|
|
7592
|
-
}
|
|
7593
|
-
function hasComparableNativeFileObject(left, right) {
|
|
7594
|
-
return hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev) && hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
7595
|
-
}
|
|
7596
|
-
function sameNativeFileObject(left, right) {
|
|
7597
|
-
const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
|
|
7598
|
-
if (comparableDevice && left.dev !== right.dev) return false;
|
|
7599
|
-
const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
7600
|
-
if (comparableInode && left.ino !== right.ino) return false;
|
|
7601
|
-
if (comparableDevice && comparableInode) return true;
|
|
7602
|
-
return left.birthtime === right.birthtime;
|
|
7603
|
-
}
|
|
7604
|
-
|
|
7605
7707
|
// domains/comet-native/native-bounded-file.ts
|
|
7606
7708
|
var DEFAULT_NATIVE_ARTIFACT_MAX_BYTES = 1024 * 1024;
|
|
7607
7709
|
function isInside(parent, target) {
|
|
@@ -7632,7 +7734,7 @@ function positiveLimit(value) {
|
|
|
7632
7734
|
return value;
|
|
7633
7735
|
}
|
|
7634
7736
|
function sameDirectoryIdentity(identity, stat) {
|
|
7635
|
-
return
|
|
7737
|
+
return sameFileObject(
|
|
7636
7738
|
{ ...identity, birthtime: identity.birthtimeMs },
|
|
7637
7739
|
{
|
|
7638
7740
|
...stat,
|
|
@@ -7643,19 +7745,19 @@ function sameDirectoryIdentity(identity, stat) {
|
|
|
7643
7745
|
function sameFileIdentity(left, right) {
|
|
7644
7746
|
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
7645
7747
|
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
7646
|
-
if (
|
|
7647
|
-
return
|
|
7748
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
7749
|
+
return sameFileObject(leftObject, rightObject);
|
|
7648
7750
|
}
|
|
7649
|
-
return
|
|
7751
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
7650
7752
|
}
|
|
7651
7753
|
async function directoryIdentity(directory) {
|
|
7652
|
-
const stat = await
|
|
7754
|
+
const stat = await fs2.lstat(directory);
|
|
7653
7755
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
7654
7756
|
throw new Error(`Native artifact parent must be a real directory: ${directory}`);
|
|
7655
7757
|
}
|
|
7656
7758
|
return {
|
|
7657
7759
|
path: directory,
|
|
7658
|
-
realPath: await
|
|
7760
|
+
realPath: await fs2.realpath(directory),
|
|
7659
7761
|
dev: stat.dev,
|
|
7660
7762
|
ino: stat.ino,
|
|
7661
7763
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -7681,8 +7783,8 @@ async function captureDirectoryChain(root, directory) {
|
|
|
7681
7783
|
}
|
|
7682
7784
|
async function verifyDirectoryChain(chain) {
|
|
7683
7785
|
for (const identity of chain) {
|
|
7684
|
-
const stat = await
|
|
7685
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity(identity, stat) || await
|
|
7786
|
+
const stat = await fs2.lstat(identity.path);
|
|
7787
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity(identity, stat) || await fs2.realpath(identity.path) !== identity.realPath) {
|
|
7686
7788
|
throw new Error(`Native artifact parent changed while reading: ${identity.path}`);
|
|
7687
7789
|
}
|
|
7688
7790
|
}
|
|
@@ -7693,21 +7795,21 @@ async function readNativeBoundedTextFile(options) {
|
|
|
7693
7795
|
const file = path2.resolve(options.root, ...ref.split("/"));
|
|
7694
7796
|
const chain = await captureDirectoryChain(options.root, path2.dirname(file));
|
|
7695
7797
|
await options.hooks?.afterParentChainCaptured?.();
|
|
7696
|
-
const before = await
|
|
7798
|
+
const before = await fs2.lstat(file);
|
|
7697
7799
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7698
7800
|
throw new Error(`Native artifact must be a regular file: ${ref}`);
|
|
7699
7801
|
}
|
|
7700
7802
|
if (before.size > maxBytes) throw new Error(`Native artifact exceeds ${maxBytes} bytes: ${ref}`);
|
|
7701
|
-
const realPath = await
|
|
7803
|
+
const realPath = await fs2.realpath(file);
|
|
7702
7804
|
if (!isInside(chain[0].realPath, realPath)) {
|
|
7703
7805
|
throw new Error(`Native artifact resolves outside its root: ${ref}`);
|
|
7704
7806
|
}
|
|
7705
|
-
const handle = await
|
|
7807
|
+
const handle = await fs2.open(file, "r");
|
|
7706
7808
|
try {
|
|
7707
7809
|
const [opened, afterOpenPath, afterOpenRealPath] = await Promise.all([
|
|
7708
7810
|
handle.stat(),
|
|
7709
|
-
|
|
7710
|
-
|
|
7811
|
+
fs2.lstat(file),
|
|
7812
|
+
fs2.realpath(file)
|
|
7711
7813
|
]);
|
|
7712
7814
|
await verifyDirectoryChain(chain);
|
|
7713
7815
|
if (!opened.isFile() || !afterOpenPath.isFile() || afterOpenPath.isSymbolicLink() || afterOpenRealPath !== realPath || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, afterOpenPath)) {
|
|
@@ -7728,8 +7830,8 @@ async function readNativeBoundedTextFile(options) {
|
|
|
7728
7830
|
await options.hooks?.beforeFinalCheck?.();
|
|
7729
7831
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
7730
7832
|
handle.stat(),
|
|
7731
|
-
|
|
7732
|
-
|
|
7833
|
+
fs2.lstat(file),
|
|
7834
|
+
fs2.realpath(file)
|
|
7733
7835
|
]);
|
|
7734
7836
|
await verifyDirectoryChain(chain);
|
|
7735
7837
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity(opened, afterHandle) || !sameFileIdentity(opened, afterPath)) {
|
|
@@ -7755,14 +7857,14 @@ async function readNativeBoundedTextFile(options) {
|
|
|
7755
7857
|
|
|
7756
7858
|
// domains/comet-native/native-atomic-file.ts
|
|
7757
7859
|
import { randomUUID } from "crypto";
|
|
7758
|
-
import { promises as
|
|
7860
|
+
import { promises as fs3 } from "fs";
|
|
7759
7861
|
import path3 from "path";
|
|
7760
7862
|
function isInside2(parent, target) {
|
|
7761
7863
|
const relative = path3.relative(parent, target);
|
|
7762
7864
|
return relative === "" || !path3.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path3.sep}`);
|
|
7763
7865
|
}
|
|
7764
7866
|
function sameDirectoryIdentity2(identity, stat) {
|
|
7765
|
-
return
|
|
7867
|
+
return sameFileObject(
|
|
7766
7868
|
{ ...identity, birthtime: identity.birthtimeMs },
|
|
7767
7869
|
{
|
|
7768
7870
|
...stat,
|
|
@@ -7773,19 +7875,19 @@ function sameDirectoryIdentity2(identity, stat) {
|
|
|
7773
7875
|
function sameFileIdentity2(left, right) {
|
|
7774
7876
|
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
7775
7877
|
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
7776
|
-
if (
|
|
7777
|
-
return
|
|
7878
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
7879
|
+
return sameFileObject(leftObject, rightObject);
|
|
7778
7880
|
}
|
|
7779
|
-
return
|
|
7881
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
7780
7882
|
}
|
|
7781
7883
|
async function captureDirectoryIdentity(directory) {
|
|
7782
|
-
const stat = await
|
|
7884
|
+
const stat = await fs3.lstat(directory);
|
|
7783
7885
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
7784
7886
|
throw new Error(`Native atomic write parent must be a real directory: ${directory}`);
|
|
7785
7887
|
}
|
|
7786
7888
|
return {
|
|
7787
7889
|
path: directory,
|
|
7788
|
-
realPath: await
|
|
7890
|
+
realPath: await fs3.realpath(directory),
|
|
7789
7891
|
dev: stat.dev,
|
|
7790
7892
|
ino: stat.ino,
|
|
7791
7893
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -7793,8 +7895,8 @@ async function captureDirectoryIdentity(directory) {
|
|
|
7793
7895
|
}
|
|
7794
7896
|
async function verifyDirectoryChain2(chain) {
|
|
7795
7897
|
for (const identity of chain) {
|
|
7796
|
-
const stat = await
|
|
7797
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity2(identity, stat) || await
|
|
7898
|
+
const stat = await fs3.lstat(identity.path);
|
|
7899
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity2(identity, stat) || await fs3.realpath(identity.path) !== identity.realPath) {
|
|
7798
7900
|
throw new Error(`Native atomic write parent changed before commit: ${identity.path}`);
|
|
7799
7901
|
}
|
|
7800
7902
|
}
|
|
@@ -7812,7 +7914,7 @@ async function prepareContainedDirectoryChain(root, directory) {
|
|
|
7812
7914
|
await verifyDirectoryChain2(chain);
|
|
7813
7915
|
cursor = path3.join(cursor, segment);
|
|
7814
7916
|
try {
|
|
7815
|
-
await
|
|
7917
|
+
await fs3.mkdir(cursor);
|
|
7816
7918
|
} catch (error) {
|
|
7817
7919
|
if (error.code !== "EEXIST") throw error;
|
|
7818
7920
|
}
|
|
@@ -7828,7 +7930,7 @@ async function prepareContainedDirectoryChain(root, directory) {
|
|
|
7828
7930
|
async function syncDirectory(directory) {
|
|
7829
7931
|
let handle;
|
|
7830
7932
|
try {
|
|
7831
|
-
handle = await
|
|
7933
|
+
handle = await fs3.open(directory, "r");
|
|
7832
7934
|
await handle.sync();
|
|
7833
7935
|
} catch (error) {
|
|
7834
7936
|
const code = error.code;
|
|
@@ -7842,18 +7944,18 @@ async function syncDirectory(directory) {
|
|
|
7842
7944
|
async function atomicWrite(file, content, options = {}) {
|
|
7843
7945
|
const directory = path3.dirname(file);
|
|
7844
7946
|
const directoryChain = options.containedRoot ? await prepareContainedDirectoryChain(options.containedRoot, directory) : null;
|
|
7845
|
-
if (!directoryChain) await
|
|
7947
|
+
if (!directoryChain) await fs3.mkdir(directory, { recursive: true });
|
|
7846
7948
|
const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
|
|
7847
7949
|
let handle;
|
|
7848
7950
|
let temporaryIdentity;
|
|
7849
7951
|
try {
|
|
7850
7952
|
await options.beforeTemporaryOpen?.();
|
|
7851
|
-
handle = await
|
|
7953
|
+
handle = await fs3.open(temporary, "wx");
|
|
7852
7954
|
temporaryIdentity = await handle.stat();
|
|
7853
7955
|
if (directoryChain) {
|
|
7854
7956
|
const [temporaryPathStat, temporaryRealPath] = await Promise.all([
|
|
7855
|
-
|
|
7856
|
-
|
|
7957
|
+
fs3.lstat(temporary),
|
|
7958
|
+
fs3.realpath(temporary)
|
|
7857
7959
|
]);
|
|
7858
7960
|
await verifyDirectoryChain2(directoryChain);
|
|
7859
7961
|
if (!temporaryPathStat.isFile() || temporaryPathStat.isSymbolicLink() || !sameFileIdentity2(temporaryIdentity, temporaryPathStat) || !isInside2(directoryChain[0].realPath, temporaryRealPath)) {
|
|
@@ -7871,26 +7973,26 @@ async function atomicWrite(file, content, options = {}) {
|
|
|
7871
7973
|
await options.beforeCommit?.();
|
|
7872
7974
|
if (directoryChain) {
|
|
7873
7975
|
await verifyDirectoryChain2(directoryChain);
|
|
7874
|
-
const temporaryStat = await
|
|
7976
|
+
const temporaryStat = await fs3.lstat(temporary);
|
|
7875
7977
|
if (!temporaryStat.isFile() || temporaryStat.isSymbolicLink() || !temporaryIdentity || !sameFileIdentity2(temporaryStat, temporaryIdentity)) {
|
|
7876
7978
|
throw new Error("Native atomic write temporary file changed before commit");
|
|
7877
7979
|
}
|
|
7878
7980
|
}
|
|
7879
7981
|
if (options.exclusive) {
|
|
7880
|
-
await
|
|
7881
|
-
await
|
|
7982
|
+
await fs3.link(temporary, file);
|
|
7983
|
+
await fs3.unlink(temporary);
|
|
7882
7984
|
} else {
|
|
7883
|
-
await
|
|
7985
|
+
await fs3.rename(temporary, file);
|
|
7884
7986
|
}
|
|
7885
7987
|
await syncDirectory(directory);
|
|
7886
7988
|
} catch (error) {
|
|
7887
7989
|
await handle?.close();
|
|
7888
7990
|
if (!directoryChain) {
|
|
7889
|
-
await
|
|
7991
|
+
await fs3.rm(temporary, { force: true });
|
|
7890
7992
|
} else {
|
|
7891
7993
|
try {
|
|
7892
7994
|
await verifyDirectoryChain2(directoryChain);
|
|
7893
|
-
await
|
|
7995
|
+
await fs3.rm(temporary, { force: true });
|
|
7894
7996
|
} catch {
|
|
7895
7997
|
}
|
|
7896
7998
|
}
|
|
@@ -7909,7 +8011,7 @@ async function atomicWriteJson(file, value, options = {}) {
|
|
|
7909
8011
|
|
|
7910
8012
|
// domains/comet-native/native-config.ts
|
|
7911
8013
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
7912
|
-
import { promises as
|
|
8014
|
+
import { promises as fs6 } from "fs";
|
|
7913
8015
|
import path6 from "path";
|
|
7914
8016
|
|
|
7915
8017
|
// domains/workflow-contract/project-config.ts
|
|
@@ -7924,6 +8026,12 @@ var COMMENTS = {
|
|
|
7924
8026
|
"native.artifact_root": "# Root directory where Native stores Comet specs, changes, and runtime data.",
|
|
7925
8027
|
"native.language": "# Artifact language used by Native workflow documents.\n# language: en | zh-CN",
|
|
7926
8028
|
"native.clarification_mode": "# Controls whether Native asks one clarification at a time or every currently answerable question in a round.\n# clarification_mode: sequential | batch",
|
|
8029
|
+
"native.snapshot": "# Controls the auditable project scope and bounded work used by Native content snapshots.",
|
|
8030
|
+
"native.snapshot.include": "# Selects the project-relative paths included in Native snapshots. Patterns use / and support *, **, and ?.",
|
|
8031
|
+
"native.snapshot.exclude": "# Removes paths from the included scope. Exclusions are bound into each new change baseline.",
|
|
8032
|
+
"native.snapshot.max_files": "# Bounds the number of files captured by one snapshot. Increase it for large monorepos.",
|
|
8033
|
+
"native.snapshot.max_total_bytes": "# Bounds the total file content hashed by one snapshot. Content is streamed and does not depend on Git hashes.",
|
|
8034
|
+
"native.snapshot.max_duration_ms": "# Bounds snapshot capture time in milliseconds. Increase it together with the byte budget on slower or larger repositories.",
|
|
7927
8035
|
classic: "# Classic workflow settings. They do not change Native state or behavior.",
|
|
7928
8036
|
"classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN",
|
|
7929
8037
|
"classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta",
|
|
@@ -7939,6 +8047,12 @@ var COMMENTS = {
|
|
|
7939
8047
|
"native.artifact_root": "# Native 产物的存放根目录,包括规格、change 和运行时数据。",
|
|
7940
8048
|
"native.language": "# Native 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
|
|
7941
8049
|
"native.clarification_mode": "# Native 每轮询问一个问题,或一次提出当前所有可回答的问题。\n# 可选值:sequential | batch",
|
|
8050
|
+
"native.snapshot": "# Native 内容快照使用的可审计项目范围与有界工作预算。",
|
|
8051
|
+
"native.snapshot.include": "# Native 快照纳入的项目相对路径;模式使用 /,支持 *、** 和 ?。",
|
|
8052
|
+
"native.snapshot.exclude": "# 从纳入范围中排除路径;新 change 会把排除策略绑定到 baseline。",
|
|
8053
|
+
"native.snapshot.max_files": "# 单次快照最多捕获的文件数;大型 monorepo 可按需提高。",
|
|
8054
|
+
"native.snapshot.max_total_bytes": "# 单次快照最多哈希的文件内容总字节数;内容采用流式读取,不依赖 Git hash。",
|
|
8055
|
+
"native.snapshot.max_duration_ms": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。",
|
|
7942
8056
|
classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。",
|
|
7943
8057
|
"classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
|
|
7944
8058
|
"classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta",
|
|
@@ -7949,7 +8063,7 @@ var COMMENTS = {
|
|
|
7949
8063
|
function projectConfigComment(key, language) {
|
|
7950
8064
|
return COMMENTS[language][key];
|
|
7951
8065
|
}
|
|
7952
|
-
function commentKey(line, block) {
|
|
8066
|
+
function commentKey(line, block, nativeNested) {
|
|
7953
8067
|
const match = /^(\s*)([a-z_]+):/u.exec(line);
|
|
7954
8068
|
if (!match) return null;
|
|
7955
8069
|
const indent = match[1].length;
|
|
@@ -7959,13 +8073,18 @@ function commentKey(line, block) {
|
|
|
7959
8073
|
const blockKey = `${block}.${key}`;
|
|
7960
8074
|
if (blockKey in COMMENTS.en) return blockKey;
|
|
7961
8075
|
}
|
|
8076
|
+
if (indent === 4 && block === "native" && nativeNested === "snapshot") {
|
|
8077
|
+
const nestedKey = `native.snapshot.${key}`;
|
|
8078
|
+
if (nestedKey in COMMENTS.en) return nestedKey;
|
|
8079
|
+
}
|
|
7962
8080
|
return null;
|
|
7963
8081
|
}
|
|
7964
8082
|
function renderStructuredProjectConfig(value, language) {
|
|
7965
8083
|
const output = [];
|
|
7966
8084
|
let block = null;
|
|
8085
|
+
let nativeNested = null;
|
|
7967
8086
|
for (const line of (0, import_yaml.stringify)(value).trimEnd().split("\n")) {
|
|
7968
|
-
const key = commentKey(line, block);
|
|
8087
|
+
const key = commentKey(line, block, nativeNested);
|
|
7969
8088
|
if (key) {
|
|
7970
8089
|
const indent = line.match(/^\s*/u)?.[0] ?? "";
|
|
7971
8090
|
for (const comment of projectConfigComment(key, language).split("\n")) {
|
|
@@ -7977,6 +8096,9 @@ function renderStructuredProjectConfig(value, language) {
|
|
|
7977
8096
|
if (line.startsWith("native:")) block = "native";
|
|
7978
8097
|
else if (line.startsWith("classic:")) block = "classic";
|
|
7979
8098
|
else block = null;
|
|
8099
|
+
nativeNested = null;
|
|
8100
|
+
} else if (/^ {2}[a-z_]+:/u.test(line) && block === "native") {
|
|
8101
|
+
nativeNested = line.startsWith(" snapshot:") ? "snapshot" : null;
|
|
7980
8102
|
}
|
|
7981
8103
|
}
|
|
7982
8104
|
output.push("");
|
|
@@ -7985,7 +8107,7 @@ function renderStructuredProjectConfig(value, language) {
|
|
|
7985
8107
|
|
|
7986
8108
|
// domains/comet-native/native-protected-file.ts
|
|
7987
8109
|
import { createHash as createHash3 } from "node:crypto";
|
|
7988
|
-
import { constants as
|
|
8110
|
+
import { constants as fsConstants2, promises as fs4 } from "node:fs";
|
|
7989
8111
|
import path4 from "node:path";
|
|
7990
8112
|
import { TextDecoder as TextDecoder2 } from "node:util";
|
|
7991
8113
|
function isInside3(parent, target) {
|
|
@@ -7999,7 +8121,7 @@ function positiveLimit2(value) {
|
|
|
7999
8121
|
return value;
|
|
8000
8122
|
}
|
|
8001
8123
|
function sameDirectoryIdentity3(expected, actual) {
|
|
8002
|
-
return
|
|
8124
|
+
return sameFileObject(
|
|
8003
8125
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
8004
8126
|
{
|
|
8005
8127
|
...actual,
|
|
@@ -8018,7 +8140,7 @@ function asFileIdentity(stat) {
|
|
|
8018
8140
|
};
|
|
8019
8141
|
}
|
|
8020
8142
|
function sameFileIdentity3(expected, actual) {
|
|
8021
|
-
return
|
|
8143
|
+
return sameFileObject(
|
|
8022
8144
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
8023
8145
|
{
|
|
8024
8146
|
...actual,
|
|
@@ -8027,13 +8149,13 @@ function sameFileIdentity3(expected, actual) {
|
|
|
8027
8149
|
) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
|
|
8028
8150
|
}
|
|
8029
8151
|
async function captureDirectoryIdentity2(directory, label) {
|
|
8030
|
-
const stat = await
|
|
8152
|
+
const stat = await fs4.lstat(directory);
|
|
8031
8153
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
8032
8154
|
throw new Error(`${label} parent must be a real directory: ${directory}`);
|
|
8033
8155
|
}
|
|
8034
8156
|
return {
|
|
8035
8157
|
path: directory,
|
|
8036
|
-
realPath: await
|
|
8158
|
+
realPath: await fs4.realpath(directory),
|
|
8037
8159
|
dev: stat.dev,
|
|
8038
8160
|
ino: stat.ino,
|
|
8039
8161
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -8061,8 +8183,8 @@ async function captureDirectoryChain2(root, directory, label) {
|
|
|
8061
8183
|
}
|
|
8062
8184
|
async function verifyDirectoryChain3(chain, label) {
|
|
8063
8185
|
for (const identity of chain) {
|
|
8064
|
-
const stat = await
|
|
8065
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await
|
|
8186
|
+
const stat = await fs4.lstat(identity.path);
|
|
8187
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs4.realpath(identity.path) !== identity.realPath) {
|
|
8066
8188
|
throw new Error(`${label} parent changed during I/O: ${identity.path}`);
|
|
8067
8189
|
}
|
|
8068
8190
|
}
|
|
@@ -8092,27 +8214,27 @@ async function readNativeProtectedFile(options) {
|
|
|
8092
8214
|
);
|
|
8093
8215
|
await options.hooks?.afterParentChainCaptured?.();
|
|
8094
8216
|
await verifyDirectoryChain3(chain, options.label);
|
|
8095
|
-
const before = await
|
|
8217
|
+
const before = await fs4.lstat(file);
|
|
8096
8218
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
8097
8219
|
throw new Error(`${options.label} must be a regular file`);
|
|
8098
8220
|
}
|
|
8099
8221
|
if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`);
|
|
8100
8222
|
const beforeIdentity = asFileIdentity(before);
|
|
8101
|
-
const beforeRealPath = await
|
|
8223
|
+
const beforeRealPath = await fs4.realpath(file);
|
|
8102
8224
|
if (!isInside3(chain[0].realPath, beforeRealPath)) {
|
|
8103
8225
|
throw new Error(`${options.label} resolves outside its managed root`);
|
|
8104
8226
|
}
|
|
8105
8227
|
if (forbidden.some((identity) => isInside3(identity.realPath, beforeRealPath))) {
|
|
8106
8228
|
throw new Error(`${options.label} resolves inside an excluded root`);
|
|
8107
8229
|
}
|
|
8108
|
-
const flags = process.platform === "win32" ?
|
|
8109
|
-
const handle = await
|
|
8230
|
+
const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK;
|
|
8231
|
+
const handle = await fs4.open(file, flags);
|
|
8110
8232
|
try {
|
|
8111
8233
|
const opened = await handle.stat();
|
|
8112
8234
|
await options.hooks?.afterOpen?.();
|
|
8113
8235
|
const [pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
8114
|
-
|
|
8115
|
-
|
|
8236
|
+
fs4.lstat(file),
|
|
8237
|
+
fs4.realpath(file)
|
|
8116
8238
|
]);
|
|
8117
8239
|
await verifyDirectoryChain3(chain, options.label);
|
|
8118
8240
|
await verifyDirectoryChain3(forbidden, options.label);
|
|
@@ -8124,8 +8246,8 @@ async function readNativeProtectedFile(options) {
|
|
|
8124
8246
|
await options.hooks?.beforeFinalCheck?.();
|
|
8125
8247
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
8126
8248
|
handle.stat(),
|
|
8127
|
-
|
|
8128
|
-
|
|
8249
|
+
fs4.lstat(file),
|
|
8250
|
+
fs4.realpath(file)
|
|
8129
8251
|
]);
|
|
8130
8252
|
await verifyDirectoryChain3(chain, options.label);
|
|
8131
8253
|
await verifyDirectoryChain3(forbidden, options.label);
|
|
@@ -8155,11 +8277,11 @@ async function readNativeProtectedDirectory(options) {
|
|
|
8155
8277
|
const chain = await captureDirectoryChain2(options.root, options.directory, options.label);
|
|
8156
8278
|
let entries;
|
|
8157
8279
|
if (options.maxEntries === void 0) {
|
|
8158
|
-
entries = await
|
|
8280
|
+
entries = await fs4.readdir(options.directory, { withFileTypes: true });
|
|
8159
8281
|
} else {
|
|
8160
8282
|
const maxEntries = positiveLimit2(options.maxEntries);
|
|
8161
8283
|
entries = [];
|
|
8162
|
-
const directory = await
|
|
8284
|
+
const directory = await fs4.opendir(options.directory);
|
|
8163
8285
|
try {
|
|
8164
8286
|
for await (const entry2 of directory) {
|
|
8165
8287
|
entries.push(entry2);
|
|
@@ -8200,25 +8322,25 @@ async function quarantineNativeProtectedDirectoryInternal(options) {
|
|
|
8200
8322
|
}
|
|
8201
8323
|
await options.beforeQuarantine?.();
|
|
8202
8324
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8203
|
-
const current = await
|
|
8204
|
-
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await
|
|
8325
|
+
const current = await fs4.lstat(directory);
|
|
8326
|
+
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs4.realpath(directory) !== identity.realPath) {
|
|
8205
8327
|
throw new Error(`${options.label} changed before quarantine`);
|
|
8206
8328
|
}
|
|
8207
8329
|
try {
|
|
8208
|
-
await
|
|
8330
|
+
await fs4.lstat(quarantine);
|
|
8209
8331
|
throw new Error(`${options.label} quarantine path is occupied`);
|
|
8210
8332
|
} catch (error) {
|
|
8211
8333
|
if (error.code !== "ENOENT") throw error;
|
|
8212
8334
|
}
|
|
8213
|
-
await
|
|
8335
|
+
await fs4.rename(directory, quarantine);
|
|
8214
8336
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8215
|
-
const quarantined = await
|
|
8337
|
+
const quarantined = await fs4.lstat(quarantine);
|
|
8216
8338
|
if (!quarantined.isDirectory() || quarantined.isSymbolicLink() || !sameDirectoryIdentity3(identity, quarantined)) {
|
|
8217
8339
|
throw new Error(`${options.label} changed while quarantining`);
|
|
8218
8340
|
}
|
|
8219
8341
|
await options.afterQuarantine?.(quarantine);
|
|
8220
8342
|
try {
|
|
8221
|
-
await
|
|
8343
|
+
await fs4.lstat(directory);
|
|
8222
8344
|
throw new Error(`${options.label} path was recreated while quarantining`);
|
|
8223
8345
|
} catch (error) {
|
|
8224
8346
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -8231,12 +8353,12 @@ async function quarantineNativeProtectedDirectory(options) {
|
|
|
8231
8353
|
async function removeNativeProtectedFile(options) {
|
|
8232
8354
|
const file = path4.resolve(options.file);
|
|
8233
8355
|
const parentChain = await captureDirectoryChain2(options.root, path4.dirname(file), options.label);
|
|
8234
|
-
const before = await
|
|
8356
|
+
const before = await fs4.lstat(file);
|
|
8235
8357
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
8236
8358
|
throw new Error(`${options.label} must be a regular file`);
|
|
8237
8359
|
}
|
|
8238
8360
|
const identity = asFileIdentity(before);
|
|
8239
|
-
const realPath = await
|
|
8361
|
+
const realPath = await fs4.realpath(file);
|
|
8240
8362
|
if (!isInside3(parentChain[0].realPath, realPath)) {
|
|
8241
8363
|
throw new Error(`${options.label} resolves outside its managed root`);
|
|
8242
8364
|
}
|
|
@@ -8249,17 +8371,17 @@ async function removeNativeProtectedFile(options) {
|
|
|
8249
8371
|
if (snapshot2.hash !== options.expectedHash || snapshot2.size !== options.expectedSize) {
|
|
8250
8372
|
throw new Error(`${options.label} changed before removal`);
|
|
8251
8373
|
}
|
|
8252
|
-
const [afterRead, afterReadRealPath] = await Promise.all([
|
|
8374
|
+
const [afterRead, afterReadRealPath] = await Promise.all([fs4.lstat(file), fs4.realpath(file)]);
|
|
8253
8375
|
if (!afterRead.isFile() || afterRead.isSymbolicLink() || !sameFileIdentity3(identity, afterRead) || afterReadRealPath !== realPath) {
|
|
8254
8376
|
throw new Error(`${options.label} changed while verifying removal`);
|
|
8255
8377
|
}
|
|
8256
8378
|
await options.beforeRemove?.();
|
|
8257
8379
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8258
|
-
const [current, currentRealPath] = await Promise.all([
|
|
8380
|
+
const [current, currentRealPath] = await Promise.all([fs4.lstat(file), fs4.realpath(file)]);
|
|
8259
8381
|
if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentity3(identity, current) || currentRealPath !== realPath) {
|
|
8260
8382
|
throw new Error(`${options.label} changed before removal`);
|
|
8261
8383
|
}
|
|
8262
|
-
await
|
|
8384
|
+
await fs4.rm(file);
|
|
8263
8385
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8264
8386
|
}
|
|
8265
8387
|
async function removeNativeProtectedEmptyDirectory(options) {
|
|
@@ -8275,11 +8397,11 @@ async function removeNativeProtectedEmptyDirectory(options) {
|
|
|
8275
8397
|
}
|
|
8276
8398
|
await options.beforeRemove?.();
|
|
8277
8399
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8278
|
-
const current = await
|
|
8279
|
-
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await
|
|
8400
|
+
const current = await fs4.lstat(directory);
|
|
8401
|
+
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs4.realpath(directory) !== identity.realPath) {
|
|
8280
8402
|
throw new Error(`${options.label} changed before removal`);
|
|
8281
8403
|
}
|
|
8282
|
-
await
|
|
8404
|
+
await fs4.rmdir(directory);
|
|
8283
8405
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8284
8406
|
}
|
|
8285
8407
|
async function ensureNativeProtectedDirectory(options) {
|
|
@@ -8294,7 +8416,7 @@ async function ensureNativeProtectedDirectory(options) {
|
|
|
8294
8416
|
await verifyDirectoryChain3(chain, options.label);
|
|
8295
8417
|
cursor = path4.join(cursor, segment);
|
|
8296
8418
|
try {
|
|
8297
|
-
await
|
|
8419
|
+
await fs4.mkdir(cursor);
|
|
8298
8420
|
} catch (error) {
|
|
8299
8421
|
if (error.code !== "EEXIST") throw error;
|
|
8300
8422
|
}
|
|
@@ -8325,7 +8447,7 @@ async function moveNativeProtectedDirectory(options) {
|
|
|
8325
8447
|
throw new Error(`${options.label} source resolves outside its managed root`);
|
|
8326
8448
|
}
|
|
8327
8449
|
try {
|
|
8328
|
-
await
|
|
8450
|
+
await fs4.lstat(target);
|
|
8329
8451
|
throw new Error(`${options.label} target already exists`);
|
|
8330
8452
|
} catch (error) {
|
|
8331
8453
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -8335,21 +8457,21 @@ async function moveNativeProtectedDirectory(options) {
|
|
|
8335
8457
|
verifyDirectoryChain3(sourceParentChain, options.label),
|
|
8336
8458
|
verifyDirectoryChain3(targetParentChain, options.label)
|
|
8337
8459
|
]);
|
|
8338
|
-
const current = await
|
|
8339
|
-
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, current) || await
|
|
8460
|
+
const current = await fs4.lstat(source);
|
|
8461
|
+
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, current) || await fs4.realpath(source) !== sourceIdentity.realPath) {
|
|
8340
8462
|
throw new Error(`${options.label} source changed before move`);
|
|
8341
8463
|
}
|
|
8342
|
-
await
|
|
8464
|
+
await fs4.rename(source, target);
|
|
8343
8465
|
await Promise.all([
|
|
8344
8466
|
verifyDirectoryChain3(sourceParentChain, options.label),
|
|
8345
8467
|
verifyDirectoryChain3(targetParentChain, options.label)
|
|
8346
8468
|
]);
|
|
8347
|
-
const moved = await
|
|
8348
|
-
if (!moved.isDirectory() || moved.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, moved) || !isInside3(sourceParentChain[0].realPath, await
|
|
8469
|
+
const moved = await fs4.lstat(target);
|
|
8470
|
+
if (!moved.isDirectory() || moved.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, moved) || !isInside3(sourceParentChain[0].realPath, await fs4.realpath(target))) {
|
|
8349
8471
|
throw new Error(`${options.label} source identity changed while moving`);
|
|
8350
8472
|
}
|
|
8351
8473
|
try {
|
|
8352
|
-
await
|
|
8474
|
+
await fs4.lstat(source);
|
|
8353
8475
|
throw new Error(`${options.label} source was recreated while moving`);
|
|
8354
8476
|
} catch (error) {
|
|
8355
8477
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -8375,7 +8497,7 @@ async function copyNativeProtectedFile(options) {
|
|
|
8375
8497
|
if (options.expectedTargetHash === void 0) return;
|
|
8376
8498
|
if (options.expectedTargetHash === null) {
|
|
8377
8499
|
try {
|
|
8378
|
-
await
|
|
8500
|
+
await fs4.lstat(options.target);
|
|
8379
8501
|
} catch (error) {
|
|
8380
8502
|
if (error.code === "ENOENT") return;
|
|
8381
8503
|
throw error;
|
|
@@ -8406,13 +8528,13 @@ async function copyNativeProtectedFile(options) {
|
|
|
8406
8528
|
}
|
|
8407
8529
|
|
|
8408
8530
|
// domains/comet-native/native-paths.ts
|
|
8409
|
-
import { promises as
|
|
8531
|
+
import { promises as fs5 } from "fs";
|
|
8410
8532
|
import path5 from "path";
|
|
8411
8533
|
import os from "os";
|
|
8412
8534
|
var PROJECT_CONFIG_FILE = ".comet/config.yaml";
|
|
8413
8535
|
async function isFileOrDirectory(target) {
|
|
8414
8536
|
try {
|
|
8415
|
-
await
|
|
8537
|
+
await fs5.lstat(target);
|
|
8416
8538
|
return true;
|
|
8417
8539
|
} catch (error) {
|
|
8418
8540
|
if (error.code === "ENOENT") return false;
|
|
@@ -8421,7 +8543,7 @@ async function isFileOrDirectory(target) {
|
|
|
8421
8543
|
}
|
|
8422
8544
|
async function declaresNativeProjectConfig(target) {
|
|
8423
8545
|
try {
|
|
8424
|
-
const source = await
|
|
8546
|
+
const source = await fs5.readFile(target, "utf8");
|
|
8425
8547
|
return /^schema:\s*comet\.project\.v1\s*$/mu.test(source);
|
|
8426
8548
|
} catch (error) {
|
|
8427
8549
|
if (error.code === "ENOENT") return false;
|
|
@@ -8441,12 +8563,12 @@ async function physicalPath(target) {
|
|
|
8441
8563
|
missing.push(path5.basename(cursor));
|
|
8442
8564
|
cursor = parent;
|
|
8443
8565
|
}
|
|
8444
|
-
const existing = await
|
|
8566
|
+
const existing = await fs5.realpath(cursor);
|
|
8445
8567
|
return path5.resolve(existing, ...missing.reverse());
|
|
8446
8568
|
}
|
|
8447
8569
|
async function isSymbolicLink(target) {
|
|
8448
8570
|
try {
|
|
8449
|
-
return (await
|
|
8571
|
+
return (await fs5.lstat(target)).isSymbolicLink();
|
|
8450
8572
|
} catch (error) {
|
|
8451
8573
|
if (error.code === "ENOENT") return false;
|
|
8452
8574
|
throw error;
|
|
@@ -8455,7 +8577,7 @@ async function isSymbolicLink(target) {
|
|
|
8455
8577
|
async function discoverNativeProject(startPath) {
|
|
8456
8578
|
let cursor = path5.resolve(startPath);
|
|
8457
8579
|
try {
|
|
8458
|
-
if (!(await
|
|
8580
|
+
if (!(await fs5.stat(cursor)).isDirectory()) cursor = path5.dirname(cursor);
|
|
8459
8581
|
} catch (error) {
|
|
8460
8582
|
if (error.code !== "ENOENT") throw error;
|
|
8461
8583
|
}
|
|
@@ -8494,7 +8616,7 @@ function normalizeArtifactRootRef(value) {
|
|
|
8494
8616
|
async function resolveArtifactRoot(projectRoot, value) {
|
|
8495
8617
|
const normalized = normalizeArtifactRootRef(value);
|
|
8496
8618
|
const lexical = path5.resolve(projectRoot, ...normalized.split("/"));
|
|
8497
|
-
const physicalProject = await
|
|
8619
|
+
const physicalProject = await fs5.realpath(projectRoot);
|
|
8498
8620
|
const physicalTarget = await physicalPath(lexical);
|
|
8499
8621
|
if (!inside(physicalProject, physicalTarget)) {
|
|
8500
8622
|
throw new Error("native.artifact_root resolves outside the project root");
|
|
@@ -8540,7 +8662,7 @@ async function ensureNativeDirectories(paths) {
|
|
|
8540
8662
|
await Promise.all(
|
|
8541
8663
|
directories.map(async (directory) => {
|
|
8542
8664
|
await resolveContainedNativePath(paths.nativeRoot, directory);
|
|
8543
|
-
await
|
|
8665
|
+
await fs5.mkdir(directory, { recursive: true });
|
|
8544
8666
|
})
|
|
8545
8667
|
);
|
|
8546
8668
|
}
|
|
@@ -8571,11 +8693,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
8571
8693
|
"artifact_root",
|
|
8572
8694
|
"language",
|
|
8573
8695
|
"clarification_mode",
|
|
8696
|
+
"snapshot",
|
|
8574
8697
|
"pending_root_move"
|
|
8575
8698
|
]);
|
|
8699
|
+
var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
|
|
8700
|
+
"include",
|
|
8701
|
+
"exclude",
|
|
8702
|
+
"max_files",
|
|
8703
|
+
"max_total_bytes",
|
|
8704
|
+
"max_duration_ms"
|
|
8705
|
+
]);
|
|
8576
8706
|
var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
|
|
8577
8707
|
var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
|
|
8578
8708
|
var CLEANUP_KEYS = /* @__PURE__ */ new Set(["kind", "state", "manifest_hash"]);
|
|
8709
|
+
var MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH = 1024;
|
|
8710
|
+
var MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS = 64;
|
|
8711
|
+
var DEFAULT_NATIVE_SNAPSHOT_CONFIG = {
|
|
8712
|
+
include: ["**/*"],
|
|
8713
|
+
exclude: [],
|
|
8714
|
+
max_files: 1e4,
|
|
8715
|
+
max_total_bytes: 256 * 1024 * 1024,
|
|
8716
|
+
max_duration_ms: 6e4
|
|
8717
|
+
};
|
|
8718
|
+
function normalizeNativeSnapshotPattern(value, label) {
|
|
8719
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").includes("..")) {
|
|
8720
|
+
throw new Error(`${label} contains an unsafe pattern`);
|
|
8721
|
+
}
|
|
8722
|
+
if (value.length > MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH) {
|
|
8723
|
+
throw new Error(`${label} exceeds ${MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH} characters`);
|
|
8724
|
+
}
|
|
8725
|
+
let wildcardTokens = 0;
|
|
8726
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
8727
|
+
if (value[index] === "?") {
|
|
8728
|
+
wildcardTokens += 1;
|
|
8729
|
+
} else if (value[index] === "*") {
|
|
8730
|
+
wildcardTokens += 1;
|
|
8731
|
+
if (value[index + 1] === "*") index += 1;
|
|
8732
|
+
}
|
|
8733
|
+
}
|
|
8734
|
+
if (wildcardTokens > MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS) {
|
|
8735
|
+
throw new Error(
|
|
8736
|
+
`${label} contains more than ${MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS} wildcard tokens`
|
|
8737
|
+
);
|
|
8738
|
+
}
|
|
8739
|
+
return value;
|
|
8740
|
+
}
|
|
8741
|
+
function snapshotPatterns(value, label, fallback) {
|
|
8742
|
+
if (value === void 0) return [...fallback];
|
|
8743
|
+
if (!Array.isArray(value)) {
|
|
8744
|
+
throw new Error(`${label} contains an unsafe pattern`);
|
|
8745
|
+
}
|
|
8746
|
+
return [...new Set(value.map((pattern) => normalizeNativeSnapshotPattern(pattern, label)))].sort(
|
|
8747
|
+
(left, right) => left.localeCompare(right, "en")
|
|
8748
|
+
);
|
|
8749
|
+
}
|
|
8750
|
+
function positiveSnapshotInteger(value, fallback, label) {
|
|
8751
|
+
const resolved = value ?? fallback;
|
|
8752
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1) {
|
|
8753
|
+
throw new Error(`${label} must be a positive integer`);
|
|
8754
|
+
}
|
|
8755
|
+
return resolved;
|
|
8756
|
+
}
|
|
8757
|
+
function parseSnapshot(value) {
|
|
8758
|
+
if (value === void 0)
|
|
8759
|
+
return { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] };
|
|
8760
|
+
const snapshot2 = record(value, "native.snapshot");
|
|
8761
|
+
rejectUnknown(snapshot2, SNAPSHOT_KEYS, "native.snapshot");
|
|
8762
|
+
return {
|
|
8763
|
+
include: snapshotPatterns(
|
|
8764
|
+
snapshot2.include,
|
|
8765
|
+
"native.snapshot.include",
|
|
8766
|
+
DEFAULT_NATIVE_SNAPSHOT_CONFIG.include
|
|
8767
|
+
),
|
|
8768
|
+
exclude: snapshotPatterns(
|
|
8769
|
+
snapshot2.exclude,
|
|
8770
|
+
"native.snapshot.exclude",
|
|
8771
|
+
DEFAULT_NATIVE_SNAPSHOT_CONFIG.exclude
|
|
8772
|
+
),
|
|
8773
|
+
max_files: positiveSnapshotInteger(
|
|
8774
|
+
snapshot2.max_files,
|
|
8775
|
+
DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_files,
|
|
8776
|
+
"native.snapshot.max_files"
|
|
8777
|
+
),
|
|
8778
|
+
max_total_bytes: positiveSnapshotInteger(
|
|
8779
|
+
snapshot2.max_total_bytes,
|
|
8780
|
+
DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_total_bytes,
|
|
8781
|
+
"native.snapshot.max_total_bytes"
|
|
8782
|
+
),
|
|
8783
|
+
max_duration_ms: positiveSnapshotInteger(
|
|
8784
|
+
snapshot2.max_duration_ms,
|
|
8785
|
+
DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_duration_ms,
|
|
8786
|
+
"native.snapshot.max_duration_ms"
|
|
8787
|
+
)
|
|
8788
|
+
};
|
|
8789
|
+
}
|
|
8579
8790
|
function record(value, label) {
|
|
8580
8791
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8581
8792
|
throw new Error(`${label} must be a mapping`);
|
|
@@ -8661,6 +8872,7 @@ function parseConfig(value) {
|
|
|
8661
8872
|
throw new Error("native.clarification_mode must be sequential or batch");
|
|
8662
8873
|
}
|
|
8663
8874
|
const pending = parsePending(native.pending_root_move);
|
|
8875
|
+
const snapshot2 = parseSnapshot(native.snapshot);
|
|
8664
8876
|
return {
|
|
8665
8877
|
schema: "comet.project.v1",
|
|
8666
8878
|
default_workflow: root.default_workflow,
|
|
@@ -8670,6 +8882,7 @@ function parseConfig(value) {
|
|
|
8670
8882
|
artifact_root: normalizeArtifactRootRef(native.artifact_root),
|
|
8671
8883
|
language,
|
|
8672
8884
|
clarification_mode: clarificationMode,
|
|
8885
|
+
snapshot: snapshot2,
|
|
8673
8886
|
...pending ? { pending_root_move: pending } : {}
|
|
8674
8887
|
}
|
|
8675
8888
|
};
|
|
@@ -8682,7 +8895,8 @@ function defaultProjectConfig(artifactRoot = "docs", language = "en") {
|
|
|
8682
8895
|
native: {
|
|
8683
8896
|
artifact_root: normalizeArtifactRootRef(artifactRoot),
|
|
8684
8897
|
language,
|
|
8685
|
-
clarification_mode: "sequential"
|
|
8898
|
+
clarification_mode: "sequential",
|
|
8899
|
+
snapshot: { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] }
|
|
8686
8900
|
}
|
|
8687
8901
|
};
|
|
8688
8902
|
}
|
|
@@ -8690,7 +8904,7 @@ async function readProjectConfig(projectRoot) {
|
|
|
8690
8904
|
const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
|
|
8691
8905
|
const file = canonical;
|
|
8692
8906
|
try {
|
|
8693
|
-
await
|
|
8907
|
+
await fs6.lstat(file);
|
|
8694
8908
|
} catch (error) {
|
|
8695
8909
|
if (error.code === "ENOENT") return null;
|
|
8696
8910
|
throw error;
|
|
@@ -8751,6 +8965,7 @@ async function writeProjectConfig(projectRoot, config) {
|
|
|
8751
8965
|
artifact_root: config.native.artifact_root,
|
|
8752
8966
|
language: config.native.language,
|
|
8753
8967
|
clarification_mode: config.native.clarification_mode,
|
|
8968
|
+
snapshot: config.native.snapshot,
|
|
8754
8969
|
...config.native.pending_root_move ? {
|
|
8755
8970
|
pending_root_move: {
|
|
8756
8971
|
id: config.native.pending_root_move.id,
|
|
@@ -8778,6 +8993,7 @@ async function writeProjectConfig(projectRoot, config) {
|
|
|
8778
8993
|
artifact_root: validated.native.artifact_root,
|
|
8779
8994
|
language: validated.native.language,
|
|
8780
8995
|
clarification_mode: validated.native.clarification_mode,
|
|
8996
|
+
snapshot: validated.native.snapshot,
|
|
8781
8997
|
...validated.native.pending_root_move ? {
|
|
8782
8998
|
pending_root_move: {
|
|
8783
8999
|
id: validated.native.pending_root_move.id,
|
|
@@ -8796,7 +9012,7 @@ async function writeProjectConfig(projectRoot, config) {
|
|
|
8796
9012
|
}
|
|
8797
9013
|
};
|
|
8798
9014
|
const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
|
|
8799
|
-
await
|
|
9015
|
+
await fs6.mkdir(path6.dirname(canonical), { recursive: true });
|
|
8800
9016
|
await atomicWriteText(
|
|
8801
9017
|
canonical,
|
|
8802
9018
|
renderStructuredProjectConfig(document, validated.native.language === "zh-CN" ? "zh-CN" : "en")
|
|
@@ -8825,13 +9041,13 @@ async function resolveNativeProject(options) {
|
|
|
8825
9041
|
}
|
|
8826
9042
|
|
|
8827
9043
|
// domains/comet-native/native-mutation-lock.ts
|
|
8828
|
-
import { promises as
|
|
9044
|
+
import { promises as fs9 } from "fs";
|
|
8829
9045
|
import path9 from "path";
|
|
8830
9046
|
|
|
8831
9047
|
// domains/comet-native/native-lock.ts
|
|
8832
9048
|
import { AsyncLocalStorage } from "async_hooks";
|
|
8833
9049
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8834
|
-
import { promises as
|
|
9050
|
+
import { promises as fs7 } from "fs";
|
|
8835
9051
|
import os2 from "os";
|
|
8836
9052
|
import path7 from "path";
|
|
8837
9053
|
var NATIVE_LOCK_MAX_BYTES = 16 * 1024;
|
|
@@ -8866,10 +9082,10 @@ function nativeLockFileIdentity(stat) {
|
|
|
8866
9082
|
function sameNativeLockObject(left, right) {
|
|
8867
9083
|
const leftObject = { dev: left.device, ino: left.inode, birthtime: left.birthtimeNs };
|
|
8868
9084
|
const rightObject = { dev: right.device, ino: right.inode, birthtime: right.birthtimeNs };
|
|
8869
|
-
if (
|
|
8870
|
-
return
|
|
9085
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
9086
|
+
return sameFileObject(leftObject, rightObject);
|
|
8871
9087
|
}
|
|
8872
|
-
return
|
|
9088
|
+
return sameFileObject(leftObject, rightObject) && left.size === right.size;
|
|
8873
9089
|
}
|
|
8874
9090
|
function sameNativeLockVersion(left, right) {
|
|
8875
9091
|
return sameNativeLockObject(left, right) && left.size === right.size && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs;
|
|
@@ -8882,41 +9098,35 @@ function sameNativeLockDiagnosis(left, right) {
|
|
|
8882
9098
|
return left.owner.id === right.owner.id && sameNativeLockVersion(left.identity, right.identity);
|
|
8883
9099
|
}
|
|
8884
9100
|
async function readNativeLockSnapshot(file) {
|
|
8885
|
-
let
|
|
9101
|
+
let bytes;
|
|
9102
|
+
let stat;
|
|
8886
9103
|
try {
|
|
8887
|
-
|
|
9104
|
+
const result2 = await readFileRaceSafe(file, NATIVE_LOCK_MAX_BYTES, {
|
|
9105
|
+
bigint: true,
|
|
9106
|
+
label: "Native lock"
|
|
9107
|
+
});
|
|
9108
|
+
bytes = result2.bytes;
|
|
9109
|
+
stat = result2.stat;
|
|
8888
9110
|
} catch (error) {
|
|
8889
9111
|
if (error.code === "ENOENT") return null;
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
const source = await handle.readFile({ encoding: "utf8" });
|
|
8901
|
-
const pathStat = await fs6.lstat(file, { bigint: true });
|
|
8902
|
-
if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
|
|
8903
|
-
throw new Error(`Native lock must be a regular file: ${file}`);
|
|
8904
|
-
}
|
|
8905
|
-
const identity = nativeLockFileIdentity(stat);
|
|
8906
|
-
if (!sameNativeLockObject(identity, nativeLockFileIdentity(pathStat))) {
|
|
8907
|
-
throw new Error(`Native lock changed while reading: ${file}`);
|
|
9112
|
+
if (error instanceof RaceSafeReadError) {
|
|
9113
|
+
if (error.reason === "too-large") {
|
|
9114
|
+
throw new Error(`Native lock metadata exceeds ${NATIVE_LOCK_MAX_BYTES} bytes: ${file}`, {
|
|
9115
|
+
cause: error
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
if (error.reason === "not-regular-file") {
|
|
9119
|
+
throw new Error(`Native lock must be a regular file: ${file}`, { cause: error });
|
|
9120
|
+
}
|
|
9121
|
+
throw new Error(`Native lock changed while reading: ${file}`, { cause: error });
|
|
8908
9122
|
}
|
|
8909
|
-
return {
|
|
8910
|
-
file,
|
|
8911
|
-
owner: parseNativeLockOwner(JSON.parse(source), file),
|
|
8912
|
-
identity
|
|
8913
|
-
};
|
|
8914
|
-
} catch (error) {
|
|
8915
|
-
if (error.code === "ENOENT") return null;
|
|
8916
9123
|
throw error;
|
|
8917
|
-
} finally {
|
|
8918
|
-
await handle.close();
|
|
8919
9124
|
}
|
|
9125
|
+
return {
|
|
9126
|
+
file,
|
|
9127
|
+
owner: parseNativeLockOwner(JSON.parse(bytes.toString("utf8")), file),
|
|
9128
|
+
identity: nativeLockFileIdentity(stat)
|
|
9129
|
+
};
|
|
8920
9130
|
}
|
|
8921
9131
|
async function readNativeLock(file) {
|
|
8922
9132
|
return (await readNativeLockSnapshot(file))?.owner ?? null;
|
|
@@ -8935,13 +9145,13 @@ function diagnosisFromSnapshot(snapshot2) {
|
|
|
8935
9145
|
}
|
|
8936
9146
|
async function restoreQuarantinedNativeLock(quarantine, file) {
|
|
8937
9147
|
try {
|
|
8938
|
-
await
|
|
9148
|
+
await fs7.lstat(file);
|
|
8939
9149
|
return;
|
|
8940
9150
|
} catch (error) {
|
|
8941
9151
|
if (error.code !== "ENOENT") throw error;
|
|
8942
9152
|
}
|
|
8943
9153
|
try {
|
|
8944
|
-
await
|
|
9154
|
+
await fs7.rename(quarantine, file);
|
|
8945
9155
|
} catch (error) {
|
|
8946
9156
|
if (error.code !== "ENOENT") throw error;
|
|
8947
9157
|
}
|
|
@@ -8955,13 +9165,13 @@ async function removeBoundNativeLock(expected, quarantineDir) {
|
|
|
8955
9165
|
if (!sameNativeLockVersion(current.identity, expected.identity)) {
|
|
8956
9166
|
throw new Error(`Native lock identity changed: ${expected.file}`);
|
|
8957
9167
|
}
|
|
8958
|
-
await
|
|
9168
|
+
await fs7.mkdir(quarantineDir, { recursive: true });
|
|
8959
9169
|
const quarantine = path7.join(
|
|
8960
9170
|
quarantineDir,
|
|
8961
9171
|
`${path7.basename(expected.file)}.${expected.owner.id}.${randomUUID2()}.removed`
|
|
8962
9172
|
);
|
|
8963
9173
|
try {
|
|
8964
|
-
await
|
|
9174
|
+
await fs7.rename(expected.file, quarantine);
|
|
8965
9175
|
} catch (error) {
|
|
8966
9176
|
if (error.code === "ENOENT") return "missing";
|
|
8967
9177
|
throw error;
|
|
@@ -8971,7 +9181,7 @@ async function removeBoundNativeLock(expected, quarantineDir) {
|
|
|
8971
9181
|
await restoreQuarantinedNativeLock(quarantine, expected.file);
|
|
8972
9182
|
throw new Error(`Native lock changed before quarantine: ${expected.file}`);
|
|
8973
9183
|
}
|
|
8974
|
-
await
|
|
9184
|
+
await fs7.rm(quarantine, { force: true });
|
|
8975
9185
|
return "removed";
|
|
8976
9186
|
}
|
|
8977
9187
|
function newNativeLockOwner(operation) {
|
|
@@ -8986,7 +9196,7 @@ function newNativeLockOwner(operation) {
|
|
|
8986
9196
|
async function writeNativeLockFile(file, owner) {
|
|
8987
9197
|
let handle;
|
|
8988
9198
|
try {
|
|
8989
|
-
handle = await
|
|
9199
|
+
handle = await fs7.open(file, "wx");
|
|
8990
9200
|
} catch (error) {
|
|
8991
9201
|
if (error.code === "EEXIST") {
|
|
8992
9202
|
const existing = await readNativeLock(file);
|
|
@@ -9007,32 +9217,32 @@ async function writeNativeLockFile(file, owner) {
|
|
|
9007
9217
|
}
|
|
9008
9218
|
async function publishNativeCoordinatorClaim(paths, operation) {
|
|
9009
9219
|
const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
|
|
9010
|
-
await
|
|
9220
|
+
await fs7.mkdir(locksDir, { recursive: true });
|
|
9011
9221
|
const coordinatorDir = await resolveContainedNativePath(
|
|
9012
9222
|
paths.nativeRoot,
|
|
9013
9223
|
path7.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR)
|
|
9014
9224
|
);
|
|
9015
|
-
await
|
|
9225
|
+
await fs7.mkdir(coordinatorDir, { recursive: true });
|
|
9016
9226
|
const owner = newNativeLockOwner(operation);
|
|
9017
9227
|
const temporary = path7.join(coordinatorDir, `.${owner.id}.tmp`);
|
|
9018
9228
|
const file = path7.join(coordinatorDir, `${owner.id}.claim`);
|
|
9019
9229
|
try {
|
|
9020
9230
|
const identity = await writeNativeLockFile(temporary, owner);
|
|
9021
|
-
await
|
|
9231
|
+
await fs7.rename(temporary, file);
|
|
9022
9232
|
const published = await readNativeLockSnapshot(file);
|
|
9023
9233
|
if (!published || !sameNativeLockObject(identity, published.identity)) {
|
|
9024
9234
|
throw new Error(`Native lock coordinator claim changed while publishing: ${file}`);
|
|
9025
9235
|
}
|
|
9026
9236
|
return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity: published.identity };
|
|
9027
9237
|
} finally {
|
|
9028
|
-
await
|
|
9238
|
+
await fs7.rm(temporary, { force: true });
|
|
9029
9239
|
}
|
|
9030
9240
|
}
|
|
9031
9241
|
async function hasNativeCoordinatorPredecessor(claim) {
|
|
9032
9242
|
const coordinatorDir = path7.dirname(claim.file);
|
|
9033
9243
|
let predecessor = false;
|
|
9034
9244
|
const claimName = path7.basename(claim.file);
|
|
9035
|
-
for (const entry2 of await
|
|
9245
|
+
for (const entry2 of await fs7.readdir(coordinatorDir, { withFileTypes: true })) {
|
|
9036
9246
|
if (!entry2.isFile() || entry2.isSymbolicLink() || !entry2.name.endsWith(".claim")) continue;
|
|
9037
9247
|
const file = path7.join(coordinatorDir, entry2.name);
|
|
9038
9248
|
if (path7.resolve(file) === path7.resolve(claim.file)) continue;
|
|
@@ -9118,7 +9328,7 @@ async function withNativeLockRecovery(pathEntries, operation, work) {
|
|
|
9118
9328
|
async function acquireNativeLock(paths, name, operation) {
|
|
9119
9329
|
return withNativeLockCoordinator(paths, `acquire ${name}`, async () => {
|
|
9120
9330
|
const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
|
|
9121
|
-
await
|
|
9331
|
+
await fs7.mkdir(locksDir, { recursive: true });
|
|
9122
9332
|
const file = await resolveContainedNativePath(
|
|
9123
9333
|
paths.nativeRoot,
|
|
9124
9334
|
path7.join(locksDir, lockName(name))
|
|
@@ -9180,7 +9390,7 @@ async function takeOverNativeStaleLock(paths, file, expected) {
|
|
|
9180
9390
|
}
|
|
9181
9391
|
|
|
9182
9392
|
// domains/comet-native/native-transaction.ts
|
|
9183
|
-
import { promises as
|
|
9393
|
+
import { promises as fs8 } from "fs";
|
|
9184
9394
|
import path8 from "path";
|
|
9185
9395
|
import { TextDecoder as TextDecoder3 } from "util";
|
|
9186
9396
|
var JOURNAL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9642,7 +9852,7 @@ async function resolveRef(paths, ref) {
|
|
|
9642
9852
|
}
|
|
9643
9853
|
async function exists(file) {
|
|
9644
9854
|
try {
|
|
9645
|
-
await
|
|
9855
|
+
await fs8.access(file);
|
|
9646
9856
|
return true;
|
|
9647
9857
|
} catch (error) {
|
|
9648
9858
|
if (error.code === "ENOENT") return false;
|
|
@@ -9652,7 +9862,7 @@ async function exists(file) {
|
|
|
9652
9862
|
async function readEventLogSnapshot(paths, id, options = {}) {
|
|
9653
9863
|
const tx = await resolveNativeTransactionPaths(paths, id);
|
|
9654
9864
|
try {
|
|
9655
|
-
await
|
|
9865
|
+
await fs8.lstat(tx.events);
|
|
9656
9866
|
} catch (error) {
|
|
9657
9867
|
if (error.code === "ENOENT") {
|
|
9658
9868
|
return {
|
|
@@ -9732,7 +9942,7 @@ async function appendNativeTransactionEvent(paths, id, type, operationId) {
|
|
|
9732
9942
|
...operationId ? { operationId } : {}
|
|
9733
9943
|
};
|
|
9734
9944
|
parseEvent(event, event.sequence);
|
|
9735
|
-
await
|
|
9945
|
+
await fs8.mkdir(tx.directory, { recursive: true });
|
|
9736
9946
|
await atomicWriteText(tx.events, canonicalEventLogSource([...snapshot2.events, event]), {
|
|
9737
9947
|
containedRoot: paths.nativeRoot,
|
|
9738
9948
|
beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2)
|
|
@@ -9745,8 +9955,8 @@ async function createNativeTransaction(paths, journal) {
|
|
|
9745
9955
|
}
|
|
9746
9956
|
journal = parseJournal(journal);
|
|
9747
9957
|
const tx = await resolveNativeTransactionPaths(paths, journal.id);
|
|
9748
|
-
await
|
|
9749
|
-
await
|
|
9958
|
+
await fs8.mkdir(tx.staged, { recursive: true });
|
|
9959
|
+
await fs8.mkdir(tx.backups, { recursive: true });
|
|
9750
9960
|
await atomicWriteJson(tx.journal, journal);
|
|
9751
9961
|
await appendNativeTransactionEvent(paths, journal.id, "prepared");
|
|
9752
9962
|
}
|
|
@@ -9970,7 +10180,7 @@ function nativeRootRef(paths, target) {
|
|
|
9970
10180
|
async function hasUnfinishedTransaction(paths, allowedTransactionId) {
|
|
9971
10181
|
let entries;
|
|
9972
10182
|
try {
|
|
9973
|
-
entries = await
|
|
10183
|
+
entries = await fs9.readdir(paths.transactionsDir, { withFileTypes: true });
|
|
9974
10184
|
} catch (error) {
|
|
9975
10185
|
if (error.code === "ENOENT") return false;
|
|
9976
10186
|
throw error;
|
|
@@ -10046,7 +10256,7 @@ async function compareAndSwapNativeRevision(options) {
|
|
|
10046
10256
|
// domains/comet-native/native-snapshot.ts
|
|
10047
10257
|
import { createHash as createHash5 } from "crypto";
|
|
10048
10258
|
import { spawn } from "node:child_process";
|
|
10049
|
-
import { promises as
|
|
10259
|
+
import { promises as fs10 } from "fs";
|
|
10050
10260
|
import path10 from "path";
|
|
10051
10261
|
|
|
10052
10262
|
// domains/comet-native/native-hash.ts
|
|
@@ -10081,9 +10291,17 @@ var MANIFEST_KEYS = /* @__PURE__ */ new Set([
|
|
|
10081
10291
|
"entries",
|
|
10082
10292
|
"omitted",
|
|
10083
10293
|
"omittedCount",
|
|
10084
|
-
"omissionOverflow"
|
|
10294
|
+
"omissionOverflow",
|
|
10295
|
+
"policy"
|
|
10296
|
+
]);
|
|
10297
|
+
var LIMIT_KEYS = /* @__PURE__ */ new Set([
|
|
10298
|
+
"maxFiles",
|
|
10299
|
+
"maxFileBytes",
|
|
10300
|
+
"maxTotalBytes",
|
|
10301
|
+
"maxManifestBytes",
|
|
10302
|
+
"maxDurationMs"
|
|
10085
10303
|
]);
|
|
10086
|
-
var
|
|
10304
|
+
var POLICY_KEYS = /* @__PURE__ */ new Set(["schema", "include", "exclude", "hash"]);
|
|
10087
10305
|
var CAPTURE_KEYS = /* @__PURE__ */ new Set(["provider", "gitSelection", "physicalSelection", "projection"]);
|
|
10088
10306
|
var GIT_PROJECTION_KEYS = /* @__PURE__ */ new Set(["provider", "selection"]);
|
|
10089
10307
|
var GIT_SELECTION_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -10507,7 +10725,7 @@ async function hasGitMetadataBoundary(projectRoot) {
|
|
|
10507
10725
|
let cursor = path10.resolve(projectRoot);
|
|
10508
10726
|
while (true) {
|
|
10509
10727
|
try {
|
|
10510
|
-
await
|
|
10728
|
+
await fs10.lstat(path10.join(cursor, ".git"));
|
|
10511
10729
|
return true;
|
|
10512
10730
|
} catch (error) {
|
|
10513
10731
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -10736,7 +10954,7 @@ async function nativePhysicalSnapshotSelection(options) {
|
|
|
10736
10954
|
if (!hasExecutionBudget()) return;
|
|
10737
10955
|
let handle;
|
|
10738
10956
|
try {
|
|
10739
|
-
handle = await
|
|
10957
|
+
handle = await fs10.opendir(directory);
|
|
10740
10958
|
} catch (error) {
|
|
10741
10959
|
if (!hasExecutionBudget()) return;
|
|
10742
10960
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -10786,7 +11004,7 @@ async function nativePhysicalSnapshotSelection(options) {
|
|
|
10786
11004
|
if (!hasExecutionBudget()) break;
|
|
10787
11005
|
let stat;
|
|
10788
11006
|
try {
|
|
10789
|
-
stat = await
|
|
11007
|
+
stat = await fs10.lstat(target);
|
|
10790
11008
|
} catch (error) {
|
|
10791
11009
|
if (!hasExecutionBudget()) break;
|
|
10792
11010
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -10809,7 +11027,7 @@ async function nativePhysicalSnapshotSelection(options) {
|
|
|
10809
11027
|
if (!hasExecutionBudget()) break;
|
|
10810
11028
|
let realDirectory;
|
|
10811
11029
|
try {
|
|
10812
|
-
realDirectory = await
|
|
11030
|
+
realDirectory = await fs10.realpath(target);
|
|
10813
11031
|
} catch (error) {
|
|
10814
11032
|
if (!hasExecutionBudget()) break;
|
|
10815
11033
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11000,6 +11218,133 @@ function isChangedDuringReadError(error) {
|
|
|
11000
11218
|
function serializedManifestBytes(manifest) {
|
|
11001
11219
|
return Buffer.byteLength(JSON.stringify(manifest, null, 2) + "\n");
|
|
11002
11220
|
}
|
|
11221
|
+
function snapshotPolicyHash(include, exclude) {
|
|
11222
|
+
return sha256Text(
|
|
11223
|
+
`comet.native.snapshot-policy.v1
|
|
11224
|
+
${JSON.stringify({ include, exclude, hash: "sha256" })}`
|
|
11225
|
+
);
|
|
11226
|
+
}
|
|
11227
|
+
function epsilonClosure(tokens, positions, checkpoint) {
|
|
11228
|
+
const closure = new Set(positions);
|
|
11229
|
+
const pending = [...positions];
|
|
11230
|
+
while (pending.length > 0) {
|
|
11231
|
+
if (checkpoint && !checkpoint()) return null;
|
|
11232
|
+
const position = pending.pop();
|
|
11233
|
+
const token = tokens[position];
|
|
11234
|
+
if (token && (token.kind === "star" || token.kind === "globstar" || token.kind === "globstar-slash") && !closure.has(position + 1)) {
|
|
11235
|
+
closure.add(position + 1);
|
|
11236
|
+
pending.push(position + 1);
|
|
11237
|
+
}
|
|
11238
|
+
}
|
|
11239
|
+
return closure;
|
|
11240
|
+
}
|
|
11241
|
+
function cooperativePatternCheckpoint(hasBudget) {
|
|
11242
|
+
let operationsUntilCheck = 0;
|
|
11243
|
+
return () => {
|
|
11244
|
+
if (operationsUntilCheck > 0) {
|
|
11245
|
+
operationsUntilCheck -= 1;
|
|
11246
|
+
return true;
|
|
11247
|
+
}
|
|
11248
|
+
if (!hasBudget()) return false;
|
|
11249
|
+
operationsUntilCheck = 63;
|
|
11250
|
+
return true;
|
|
11251
|
+
};
|
|
11252
|
+
}
|
|
11253
|
+
function compileNativeSnapshotPattern(pattern) {
|
|
11254
|
+
const normalized = normalizeNativeSnapshotPattern(pattern, "Native snapshot pattern");
|
|
11255
|
+
const tokens = [];
|
|
11256
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
11257
|
+
const character = normalized[index];
|
|
11258
|
+
if (character === "*" && normalized[index + 1] === "*") {
|
|
11259
|
+
index += 1;
|
|
11260
|
+
if (normalized[index + 1] === "/") {
|
|
11261
|
+
index += 1;
|
|
11262
|
+
tokens.push({ kind: "globstar-slash" });
|
|
11263
|
+
} else {
|
|
11264
|
+
tokens.push({ kind: "globstar" });
|
|
11265
|
+
}
|
|
11266
|
+
} else if (character === "*") {
|
|
11267
|
+
tokens.push({ kind: "star" });
|
|
11268
|
+
} else if (character === "?") {
|
|
11269
|
+
tokens.push({ kind: "question" });
|
|
11270
|
+
} else {
|
|
11271
|
+
tokens.push({ kind: "literal", value: character });
|
|
11272
|
+
}
|
|
11273
|
+
}
|
|
11274
|
+
return (relative, hasBudget) => {
|
|
11275
|
+
const checkpoint = hasBudget ? cooperativePatternCheckpoint(hasBudget) : void 0;
|
|
11276
|
+
if (checkpoint && !checkpoint()) return false;
|
|
11277
|
+
let positions = epsilonClosure(tokens, /* @__PURE__ */ new Set([0]), checkpoint);
|
|
11278
|
+
if (positions === null) return false;
|
|
11279
|
+
for (const character of relative) {
|
|
11280
|
+
if (checkpoint && !checkpoint()) return false;
|
|
11281
|
+
const next = /* @__PURE__ */ new Set();
|
|
11282
|
+
for (const position of positions) {
|
|
11283
|
+
if (checkpoint && !checkpoint()) return false;
|
|
11284
|
+
const token = tokens[position];
|
|
11285
|
+
if (!token) continue;
|
|
11286
|
+
if (token.kind === "literal" && token.value === character) {
|
|
11287
|
+
next.add(position + 1);
|
|
11288
|
+
} else if (token.kind === "question" && character !== "/") {
|
|
11289
|
+
next.add(position + 1);
|
|
11290
|
+
} else if (token.kind === "star" && character !== "/") {
|
|
11291
|
+
next.add(position);
|
|
11292
|
+
} else if (token.kind === "globstar") {
|
|
11293
|
+
next.add(position);
|
|
11294
|
+
} else if (token.kind === "globstar-slash") {
|
|
11295
|
+
next.add(position);
|
|
11296
|
+
if (character === "/") next.add(position + 1);
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
11299
|
+
positions = epsilonClosure(tokens, next, checkpoint);
|
|
11300
|
+
if (positions === null) return false;
|
|
11301
|
+
if (positions.size === 0) return false;
|
|
11302
|
+
}
|
|
11303
|
+
return epsilonClosure(tokens, positions, checkpoint)?.has(tokens.length) ?? false;
|
|
11304
|
+
};
|
|
11305
|
+
}
|
|
11306
|
+
function resolveSnapshotPolicy(value) {
|
|
11307
|
+
if (value === void 0) return void 0;
|
|
11308
|
+
const include = [
|
|
11309
|
+
...new Set(value.include.map((item) => normalizeNativeSnapshotPattern(item, "include")))
|
|
11310
|
+
].sort((left, right) => left.localeCompare(right, "en"));
|
|
11311
|
+
const exclude = [
|
|
11312
|
+
...new Set(value.exclude.map((item) => normalizeNativeSnapshotPattern(item, "exclude")))
|
|
11313
|
+
].sort((left, right) => left.localeCompare(right, "en"));
|
|
11314
|
+
if (include.length === 0) throw new Error("Native snapshot policy include must not be empty");
|
|
11315
|
+
const hash6 = snapshotPolicyHash(include, exclude);
|
|
11316
|
+
if ("hash" in value && value.hash !== hash6) {
|
|
11317
|
+
throw new Error("Native snapshot policy hash is invalid");
|
|
11318
|
+
}
|
|
11319
|
+
return {
|
|
11320
|
+
manifest: {
|
|
11321
|
+
schema: "comet.native.snapshot-policy.v1",
|
|
11322
|
+
include,
|
|
11323
|
+
exclude,
|
|
11324
|
+
hash: hash6
|
|
11325
|
+
},
|
|
11326
|
+
includeMatchers: include.map(compileNativeSnapshotPattern),
|
|
11327
|
+
excludeMatchers: exclude.map(compileNativeSnapshotPattern)
|
|
11328
|
+
};
|
|
11329
|
+
}
|
|
11330
|
+
function snapshotPolicyIncludes(policy, relative, execution) {
|
|
11331
|
+
if (!policy) return true;
|
|
11332
|
+
const hasBudget = () => nativeSnapshotExecutionHasBudget(execution);
|
|
11333
|
+
let included = false;
|
|
11334
|
+
for (const matcher of policy.includeMatchers) {
|
|
11335
|
+
if (!hasBudget()) return false;
|
|
11336
|
+
if (matcher(relative, hasBudget)) {
|
|
11337
|
+
included = true;
|
|
11338
|
+
break;
|
|
11339
|
+
}
|
|
11340
|
+
}
|
|
11341
|
+
if (!included) return false;
|
|
11342
|
+
for (const matcher of policy.excludeMatchers) {
|
|
11343
|
+
if (!hasBudget()) return false;
|
|
11344
|
+
if (matcher(relative, hasBudget)) return false;
|
|
11345
|
+
}
|
|
11346
|
+
return true;
|
|
11347
|
+
}
|
|
11003
11348
|
function foldSnapshotOverflowHash(previous, kind, value) {
|
|
11004
11349
|
const payload = JSON.stringify(value);
|
|
11005
11350
|
return sha256Text(
|
|
@@ -11023,16 +11368,16 @@ function takeLastCompactableOmission(omissions) {
|
|
|
11023
11368
|
function sameFileIdentity4(left, right) {
|
|
11024
11369
|
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
11025
11370
|
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
11026
|
-
if (
|
|
11027
|
-
return
|
|
11371
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
11372
|
+
return sameFileObject(leftObject, rightObject);
|
|
11028
11373
|
}
|
|
11029
|
-
return
|
|
11374
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
11030
11375
|
}
|
|
11031
11376
|
async function sha256FileBounded(file, maxBytes, expected, execution) {
|
|
11032
11377
|
if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
|
|
11033
11378
|
let handle;
|
|
11034
11379
|
try {
|
|
11035
|
-
handle = await
|
|
11380
|
+
handle = await fs10.open(file, "r");
|
|
11036
11381
|
} catch (error) {
|
|
11037
11382
|
if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
|
|
11038
11383
|
throw error;
|
|
@@ -11367,8 +11712,28 @@ function parseNativeContentSnapshotManifest(value) {
|
|
|
11367
11712
|
maxManifestBytes: positiveInteger(
|
|
11368
11713
|
limitValue.maxManifestBytes,
|
|
11369
11714
|
"Native snapshot maxManifestBytes"
|
|
11370
|
-
)
|
|
11715
|
+
),
|
|
11716
|
+
...limitValue.maxDurationMs === void 0 ? {} : {
|
|
11717
|
+
maxDurationMs: positiveInteger(limitValue.maxDurationMs, "Native snapshot maxDurationMs")
|
|
11718
|
+
}
|
|
11371
11719
|
};
|
|
11720
|
+
let policy;
|
|
11721
|
+
if (manifest.policy !== void 0) {
|
|
11722
|
+
const policyValue = record3(manifest.policy, "Native snapshot policy");
|
|
11723
|
+
rejectUnknown3(policyValue, POLICY_KEYS, "Native snapshot policy");
|
|
11724
|
+
if (policyValue.schema !== "comet.native.snapshot-policy.v1") {
|
|
11725
|
+
throw new Error("Native snapshot policy schema is invalid");
|
|
11726
|
+
}
|
|
11727
|
+
if (!Array.isArray(policyValue.include) || !Array.isArray(policyValue.exclude)) {
|
|
11728
|
+
throw new Error("Native snapshot policy patterns must be arrays");
|
|
11729
|
+
}
|
|
11730
|
+
policy = resolveSnapshotPolicy({
|
|
11731
|
+
include: policyValue.include,
|
|
11732
|
+
exclude: policyValue.exclude,
|
|
11733
|
+
hash: policyValue.hash,
|
|
11734
|
+
schema: "comet.native.snapshot-policy.v1"
|
|
11735
|
+
}).manifest;
|
|
11736
|
+
}
|
|
11372
11737
|
if (!Array.isArray(manifest.entries) || !Array.isArray(manifest.omitted)) {
|
|
11373
11738
|
throw new Error("Native content snapshot entries and omissions must be arrays");
|
|
11374
11739
|
}
|
|
@@ -11451,6 +11816,7 @@ function parseNativeContentSnapshotManifest(value) {
|
|
|
11451
11816
|
createdAt: manifest.createdAt,
|
|
11452
11817
|
complete: manifest.complete,
|
|
11453
11818
|
limits,
|
|
11819
|
+
...policy ? { policy } : {},
|
|
11454
11820
|
entries,
|
|
11455
11821
|
omitted,
|
|
11456
11822
|
omittedCount,
|
|
@@ -11591,13 +11957,18 @@ function nativeBaselineManifestFile(paths, name) {
|
|
|
11591
11957
|
return path10.join(changeDir, "runtime", "baseline-manifest.json");
|
|
11592
11958
|
}
|
|
11593
11959
|
async function createNativeContentSnapshot(paths, options = {}) {
|
|
11594
|
-
const execution = createNativeSnapshotExecution(options);
|
|
11595
11960
|
const limits = {
|
|
11596
11961
|
maxFiles: options.limits?.maxFiles ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFiles,
|
|
11597
11962
|
maxFileBytes: options.limits?.maxFileBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFileBytes,
|
|
11598
11963
|
maxTotalBytes: options.limits?.maxTotalBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxTotalBytes,
|
|
11599
|
-
maxManifestBytes: options.limits?.maxManifestBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxManifestBytes
|
|
11964
|
+
maxManifestBytes: options.limits?.maxManifestBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxManifestBytes,
|
|
11965
|
+
...options.limits?.maxDurationMs === void 0 ? {} : { maxDurationMs: options.limits.maxDurationMs }
|
|
11600
11966
|
};
|
|
11967
|
+
const policy = resolveSnapshotPolicy(options.policy);
|
|
11968
|
+
const execution = createNativeSnapshotExecution({
|
|
11969
|
+
...options,
|
|
11970
|
+
deadlineMs: options.deadlineMs ?? limits.maxDurationMs
|
|
11971
|
+
});
|
|
11601
11972
|
const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits);
|
|
11602
11973
|
const physicalSelectionLimits = resolveNativePhysicalSelectionLimits(
|
|
11603
11974
|
options.physicalSelectionLimits
|
|
@@ -11606,9 +11977,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11606
11977
|
throw new Error("Native snapshot limits must be positive");
|
|
11607
11978
|
}
|
|
11608
11979
|
const projectRoot = path10.resolve(paths.projectRoot);
|
|
11609
|
-
const physicalProjectRoot = await
|
|
11980
|
+
const physicalProjectRoot = await fs10.realpath(projectRoot);
|
|
11610
11981
|
const nativeRoot = path10.resolve(paths.nativeRoot);
|
|
11611
|
-
const physicalNativeRoot = await
|
|
11982
|
+
const physicalNativeRoot = await fs10.realpath(nativeRoot);
|
|
11612
11983
|
const configFile = path10.resolve(paths.configFile);
|
|
11613
11984
|
const selectionFile = path10.join(projectRoot, ".comet", "current-change.json");
|
|
11614
11985
|
const denylist = normalizedDenylist(projectRoot, options.denylist ?? []);
|
|
@@ -11680,7 +12051,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11680
12051
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11681
12052
|
let realTarget;
|
|
11682
12053
|
try {
|
|
11683
|
-
realTarget = await
|
|
12054
|
+
realTarget = await fs10.realpath(target);
|
|
11684
12055
|
} catch (error) {
|
|
11685
12056
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11686
12057
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11721,9 +12092,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11721
12092
|
return;
|
|
11722
12093
|
}
|
|
11723
12094
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11724
|
-
afterRealTarget = await
|
|
12095
|
+
afterRealTarget = await fs10.realpath(target);
|
|
11725
12096
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11726
|
-
after = await
|
|
12097
|
+
after = await fs10.lstat(target);
|
|
11727
12098
|
} catch (error) {
|
|
11728
12099
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11729
12100
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11757,11 +12128,11 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11757
12128
|
let secondTarget;
|
|
11758
12129
|
let after;
|
|
11759
12130
|
try {
|
|
11760
|
-
firstTarget = await
|
|
12131
|
+
firstTarget = await fs10.readlink(target, { encoding: "buffer" });
|
|
11761
12132
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11762
|
-
after = await
|
|
12133
|
+
after = await fs10.lstat(target);
|
|
11763
12134
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11764
|
-
secondTarget = await
|
|
12135
|
+
secondTarget = await fs10.readlink(target, { encoding: "buffer" });
|
|
11765
12136
|
} catch (error) {
|
|
11766
12137
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11767
12138
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11805,9 +12176,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11805
12176
|
let realTarget2;
|
|
11806
12177
|
let stat2;
|
|
11807
12178
|
try {
|
|
11808
|
-
realTarget2 = await
|
|
12179
|
+
realTarget2 = await fs10.realpath(validation2.target);
|
|
11809
12180
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11810
|
-
stat2 = await
|
|
12181
|
+
stat2 = await fs10.lstat(validation2.target);
|
|
11811
12182
|
} catch (error) {
|
|
11812
12183
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11813
12184
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11834,9 +12205,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11834
12205
|
let stat2;
|
|
11835
12206
|
let rawTarget;
|
|
11836
12207
|
try {
|
|
11837
|
-
stat2 = await
|
|
12208
|
+
stat2 = await fs10.lstat(validation2.target);
|
|
11838
12209
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11839
|
-
rawTarget = await
|
|
12210
|
+
rawTarget = await fs10.readlink(validation2.target, { encoding: "buffer" });
|
|
11840
12211
|
} catch (error) {
|
|
11841
12212
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11842
12213
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11862,9 +12233,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11862
12233
|
let realTarget;
|
|
11863
12234
|
let stat;
|
|
11864
12235
|
try {
|
|
11865
|
-
realTarget = await
|
|
12236
|
+
realTarget = await fs10.realpath(validation2.target);
|
|
11866
12237
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11867
|
-
stat = await
|
|
12238
|
+
stat = await fs10.lstat(validation2.target);
|
|
11868
12239
|
} catch {
|
|
11869
12240
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11870
12241
|
invalidateCapturedEntry(relative, {
|
|
@@ -11920,7 +12291,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11920
12291
|
for (const [relative, target] of capturedTrackedAbsences) {
|
|
11921
12292
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11922
12293
|
try {
|
|
11923
|
-
const stat = await
|
|
12294
|
+
const stat = await fs10.lstat(target);
|
|
11924
12295
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11925
12296
|
omit({
|
|
11926
12297
|
path: relative,
|
|
@@ -11956,10 +12327,12 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11956
12327
|
for (const record8 of before.records) {
|
|
11957
12328
|
if (record8.type !== "file" && record8.type !== "symlink") continue;
|
|
11958
12329
|
if (remainingNativeSnapshotTime(execution) < 1) break;
|
|
12330
|
+
if (!snapshotPolicyIncludes(policy, record8.path, execution)) continue;
|
|
12331
|
+
if (remainingNativeSnapshotTime(execution) < 1) break;
|
|
11959
12332
|
const target = path10.resolve(projectRoot, ...record8.path.split("/"));
|
|
11960
12333
|
let stat;
|
|
11961
12334
|
try {
|
|
11962
|
-
stat = await
|
|
12335
|
+
stat = await fs10.lstat(target);
|
|
11963
12336
|
} catch (error) {
|
|
11964
12337
|
if (!nativeSnapshotExecutionHasBudget(execution)) break;
|
|
11965
12338
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -12012,6 +12385,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12012
12385
|
await options.gitSelectionHooks?.afterInitialSelection?.();
|
|
12013
12386
|
for (const relative of selectionPaths(gitSelection)) {
|
|
12014
12387
|
if (!isSnapshotProjectRef(paths, relative)) continue;
|
|
12388
|
+
if (remainingNativeSnapshotTime(execution) < 1) break;
|
|
12389
|
+
if (!snapshotPolicyIncludes(policy, relative, execution)) continue;
|
|
12390
|
+
if (remainingNativeSnapshotTime(execution) < 1) break;
|
|
12015
12391
|
const target = path10.resolve(projectRoot, ...relative.split("/"));
|
|
12016
12392
|
if (target === configFile || target === selectionFile || denylist.some((denied) => sameOrInside(denied, target))) {
|
|
12017
12393
|
continue;
|
|
@@ -12024,7 +12400,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12024
12400
|
let realGitlink;
|
|
12025
12401
|
let gitlinkStat;
|
|
12026
12402
|
try {
|
|
12027
|
-
[realGitlink, gitlinkStat] = await Promise.all([
|
|
12403
|
+
[realGitlink, gitlinkStat] = await Promise.all([fs10.realpath(target), fs10.lstat(target)]);
|
|
12028
12404
|
} catch {
|
|
12029
12405
|
omit({
|
|
12030
12406
|
path: relative,
|
|
@@ -12082,7 +12458,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12082
12458
|
}
|
|
12083
12459
|
let before;
|
|
12084
12460
|
try {
|
|
12085
|
-
before = await
|
|
12461
|
+
before = await fs10.lstat(target);
|
|
12086
12462
|
} catch (error) {
|
|
12087
12463
|
if (isChangedDuringReadError(error) && gitSelection.tracked.has(relative)) {
|
|
12088
12464
|
capturedTrackedAbsences.set(relative, target);
|
|
@@ -12136,6 +12512,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12136
12512
|
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
12137
12513
|
complete: omittedCount === 0,
|
|
12138
12514
|
limits,
|
|
12515
|
+
...policy ? { policy: policy.manifest } : {},
|
|
12139
12516
|
entries,
|
|
12140
12517
|
omitted,
|
|
12141
12518
|
omittedCount,
|
|
@@ -12172,6 +12549,21 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12172
12549
|
}
|
|
12173
12550
|
return manifest;
|
|
12174
12551
|
}
|
|
12552
|
+
async function createNativeCurrentContentSnapshot(paths, baseline, options = {}) {
|
|
12553
|
+
const config = await readProjectConfig(paths.projectRoot);
|
|
12554
|
+
const settings = config?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG;
|
|
12555
|
+
return createNativeContentSnapshot(paths, {
|
|
12556
|
+
...options,
|
|
12557
|
+
policy: baseline.policy,
|
|
12558
|
+
limits: {
|
|
12559
|
+
maxFiles: settings.max_files,
|
|
12560
|
+
maxFileBytes: settings.max_total_bytes,
|
|
12561
|
+
maxTotalBytes: settings.max_total_bytes,
|
|
12562
|
+
maxDurationMs: settings.max_duration_ms
|
|
12563
|
+
},
|
|
12564
|
+
deadlineMs: settings.max_duration_ms
|
|
12565
|
+
});
|
|
12566
|
+
}
|
|
12175
12567
|
async function writeNativeBaselineManifest(paths, name, manifest) {
|
|
12176
12568
|
const file = nativeBaselineManifestFile(paths, name);
|
|
12177
12569
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
@@ -12220,7 +12612,7 @@ function assertRunStorageLayout(storage) {
|
|
|
12220
12612
|
|
|
12221
12613
|
// domains/comet-native/native-run-store.ts
|
|
12222
12614
|
import { createHash as createHash6 } from "node:crypto";
|
|
12223
|
-
import { constants as
|
|
12615
|
+
import { constants as fsConstants3, promises as fs11 } from "node:fs";
|
|
12224
12616
|
import path13 from "node:path";
|
|
12225
12617
|
import { TextDecoder as TextDecoder4 } from "node:util";
|
|
12226
12618
|
|
|
@@ -12409,7 +12801,7 @@ function asIdentity(stat) {
|
|
|
12409
12801
|
};
|
|
12410
12802
|
}
|
|
12411
12803
|
function sameFileIdentity5(expected, actual) {
|
|
12412
|
-
return
|
|
12804
|
+
return sameFileObject(
|
|
12413
12805
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
12414
12806
|
{
|
|
12415
12807
|
...actual,
|
|
@@ -12418,7 +12810,7 @@ function sameFileIdentity5(expected, actual) {
|
|
|
12418
12810
|
) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
|
|
12419
12811
|
}
|
|
12420
12812
|
function sameDirectoryIdentity4(expected, actual) {
|
|
12421
|
-
return
|
|
12813
|
+
return sameFileObject(
|
|
12422
12814
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
12423
12815
|
{
|
|
12424
12816
|
...actual,
|
|
@@ -12437,13 +12829,13 @@ function runFile(changeDir, kind, relativePath) {
|
|
|
12437
12829
|
return target;
|
|
12438
12830
|
}
|
|
12439
12831
|
async function directoryIdentity2(directory) {
|
|
12440
|
-
const stat = await
|
|
12832
|
+
const stat = await fs11.lstat(directory);
|
|
12441
12833
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
12442
12834
|
throw new Error(`Native Run parent must be a real directory: ${directory}`);
|
|
12443
12835
|
}
|
|
12444
12836
|
return {
|
|
12445
12837
|
path: directory,
|
|
12446
|
-
realPath: await
|
|
12838
|
+
realPath: await fs11.realpath(directory),
|
|
12447
12839
|
dev: stat.dev,
|
|
12448
12840
|
ino: stat.ino,
|
|
12449
12841
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -12475,8 +12867,8 @@ async function captureDirectoryChain3(root, directory) {
|
|
|
12475
12867
|
}
|
|
12476
12868
|
async function verifyDirectoryChain4(chain) {
|
|
12477
12869
|
for (const identity of chain) {
|
|
12478
|
-
const stat = await
|
|
12479
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity4(identity, stat) || await
|
|
12870
|
+
const stat = await fs11.lstat(identity.path);
|
|
12871
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity4(identity, stat) || await fs11.realpath(identity.path) !== identity.realPath) {
|
|
12480
12872
|
throw new Error(`Native Run parent changed during I/O: ${identity.path}`);
|
|
12481
12873
|
}
|
|
12482
12874
|
}
|
|
@@ -12501,7 +12893,7 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
|
|
|
12501
12893
|
await hooks?.afterParentChainCaptured?.();
|
|
12502
12894
|
let before;
|
|
12503
12895
|
try {
|
|
12504
|
-
before = await
|
|
12896
|
+
before = await fs11.lstat(file);
|
|
12505
12897
|
} catch (error) {
|
|
12506
12898
|
if (error.code === "ENOENT") return null;
|
|
12507
12899
|
throw error;
|
|
@@ -12511,22 +12903,22 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
|
|
|
12511
12903
|
}
|
|
12512
12904
|
if (before.size > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`);
|
|
12513
12905
|
const beforeIdentity = asIdentity(before);
|
|
12514
|
-
const beforeRealPath = await
|
|
12906
|
+
const beforeRealPath = await fs11.realpath(file);
|
|
12515
12907
|
if (!isInside4(chain[0].realPath, beforeRealPath)) {
|
|
12516
12908
|
throw new Error(`${label} resolves outside its change`);
|
|
12517
12909
|
}
|
|
12518
|
-
const flags = process.platform === "win32" ?
|
|
12910
|
+
const flags = process.platform === "win32" ? fsConstants3.O_RDONLY : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK;
|
|
12519
12911
|
let handle;
|
|
12520
12912
|
try {
|
|
12521
|
-
handle = await
|
|
12913
|
+
handle = await fs11.open(file, flags);
|
|
12522
12914
|
} catch (error) {
|
|
12523
12915
|
throw new Error(`${label} changed while opening`, { cause: error });
|
|
12524
12916
|
}
|
|
12525
12917
|
try {
|
|
12526
12918
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
12527
12919
|
handle.stat(),
|
|
12528
|
-
|
|
12529
|
-
|
|
12920
|
+
fs11.lstat(file),
|
|
12921
|
+
fs11.realpath(file)
|
|
12530
12922
|
]);
|
|
12531
12923
|
await verifyDirectoryChain4(chain);
|
|
12532
12924
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity5(beforeIdentity, opened) || !sameFileIdentity5(beforeIdentity, pathAfterOpen)) {
|
|
@@ -12537,8 +12929,8 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
|
|
|
12537
12929
|
await hooks?.beforeFinalCheck?.();
|
|
12538
12930
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
12539
12931
|
handle.stat(),
|
|
12540
|
-
|
|
12541
|
-
|
|
12932
|
+
fs11.lstat(file),
|
|
12933
|
+
fs11.realpath(file)
|
|
12542
12934
|
]);
|
|
12543
12935
|
await verifyDirectoryChain4(chain);
|
|
12544
12936
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity5(beforeIdentity, afterHandle) || !sameFileIdentity5(beforeIdentity, afterPath)) {
|
|
@@ -12563,13 +12955,13 @@ async function captureTarget(changeDir, file, label) {
|
|
|
12563
12955
|
if (!chain) return { exists: false };
|
|
12564
12956
|
let stat;
|
|
12565
12957
|
try {
|
|
12566
|
-
stat = await
|
|
12958
|
+
stat = await fs11.lstat(file);
|
|
12567
12959
|
} catch (error) {
|
|
12568
12960
|
if (error.code === "ENOENT") return { exists: false };
|
|
12569
12961
|
throw error;
|
|
12570
12962
|
}
|
|
12571
12963
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
|
|
12572
|
-
const realPath = await
|
|
12964
|
+
const realPath = await fs11.realpath(file);
|
|
12573
12965
|
if (!isInside4(chain[0].realPath, realPath))
|
|
12574
12966
|
throw new Error(`${label} resolves outside its change`);
|
|
12575
12967
|
await verifyDirectoryChain4(chain);
|
|
@@ -12583,12 +12975,12 @@ async function verifyTarget(changeDir, file, expected, label) {
|
|
|
12583
12975
|
}
|
|
12584
12976
|
let stat;
|
|
12585
12977
|
try {
|
|
12586
|
-
stat = await
|
|
12978
|
+
stat = await fs11.lstat(file);
|
|
12587
12979
|
} catch (error) {
|
|
12588
12980
|
if (error.code === "ENOENT" && !expected.exists) return;
|
|
12589
12981
|
throw new Error(`${label} changed before commit`, { cause: error });
|
|
12590
12982
|
}
|
|
12591
|
-
if (!expected.exists || !stat.isFile() || stat.isSymbolicLink() || !sameFileIdentity5(expected.identity, stat) || await
|
|
12983
|
+
if (!expected.exists || !stat.isFile() || stat.isSymbolicLink() || !sameFileIdentity5(expected.identity, stat) || await fs11.realpath(file) !== expected.realPath) {
|
|
12592
12984
|
throw new Error(`${label} changed before commit`);
|
|
12593
12985
|
}
|
|
12594
12986
|
await verifyDirectoryChain4(chain);
|
|
@@ -12897,7 +13289,7 @@ async function repairNativeTrajectoryTail(paths, name, hooks) {
|
|
|
12897
13289
|
|
|
12898
13290
|
// domains/comet-native/native-workspace.ts
|
|
12899
13291
|
import { createHash as createHash8 } from "node:crypto";
|
|
12900
|
-
import { promises as
|
|
13292
|
+
import { promises as fs12 } from "node:fs";
|
|
12901
13293
|
import path15 from "node:path";
|
|
12902
13294
|
var HASH_PATTERN2 = /^[a-f0-9]{64}$/u;
|
|
12903
13295
|
var MAX_WORKSPACE_IDENTITY_BYTES = 16 * 1024;
|
|
@@ -12936,8 +13328,8 @@ function identityHash(tag, value) {
|
|
|
12936
13328
|
${value}`).digest("hex");
|
|
12937
13329
|
}
|
|
12938
13330
|
async function physicalDirectoryIdentity(tag, value) {
|
|
12939
|
-
const realPath = await
|
|
12940
|
-
const stat = await
|
|
13331
|
+
const realPath = await fs12.realpath(value);
|
|
13332
|
+
const stat = await fs12.lstat(realPath);
|
|
12941
13333
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
12942
13334
|
throw new Error("Native workspace identity requires a real directory");
|
|
12943
13335
|
}
|
|
@@ -12948,8 +13340,8 @@ ${stat.ino}
|
|
|
12948
13340
|
${stat.birthtimeMs}`);
|
|
12949
13341
|
}
|
|
12950
13342
|
async function directoryPathIdentity(tag, value) {
|
|
12951
|
-
const realPath = await
|
|
12952
|
-
const stat = await
|
|
13343
|
+
const realPath = await fs12.realpath(value);
|
|
13344
|
+
const stat = await fs12.lstat(realPath);
|
|
12953
13345
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
12954
13346
|
throw new Error("Native workspace identity requires a real directory");
|
|
12955
13347
|
}
|
|
@@ -13216,15 +13608,17 @@ var NativeChangeRevisionConflictError = class extends Error {
|
|
|
13216
13608
|
code = "native-change-revision-conflict";
|
|
13217
13609
|
};
|
|
13218
13610
|
var NativeBaselineIncompleteError = class extends Error {
|
|
13219
|
-
constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated) {
|
|
13611
|
+
constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated, effectiveLimits = null, policyHash = null) {
|
|
13220
13612
|
super(
|
|
13221
|
-
`Native change ${change} baseline is incomplete (${omittedCount} omitted entr${omittedCount === 1 ? "y" : "ies"})
|
|
13613
|
+
`Native change ${change} baseline is incomplete (${omittedCount} omitted entr${omittedCount === 1 ? "y" : "ies"}). Adjust native.snapshot scope or resource budgets in .comet/config.yaml, then retry.`
|
|
13222
13614
|
);
|
|
13223
13615
|
this.change = change;
|
|
13224
13616
|
this.omittedCount = omittedCount;
|
|
13225
13617
|
this.omittedByReason = omittedByReason;
|
|
13226
13618
|
this.samplePaths = samplePaths;
|
|
13227
13619
|
this.sampleTruncated = sampleTruncated;
|
|
13620
|
+
this.effectiveLimits = effectiveLimits;
|
|
13621
|
+
this.policyHash = policyHash;
|
|
13228
13622
|
this.name = "NativeBaselineIncompleteError";
|
|
13229
13623
|
}
|
|
13230
13624
|
change;
|
|
@@ -13232,6 +13626,8 @@ var NativeBaselineIncompleteError = class extends Error {
|
|
|
13232
13626
|
omittedByReason;
|
|
13233
13627
|
samplePaths;
|
|
13234
13628
|
sampleTruncated;
|
|
13629
|
+
effectiveLimits;
|
|
13630
|
+
policyHash;
|
|
13235
13631
|
code = "native-baseline-incomplete";
|
|
13236
13632
|
};
|
|
13237
13633
|
var NATIVE_BRIEF_TEMPLATE = [
|
|
@@ -13588,7 +13984,7 @@ async function hasPendingNativeSchemaMigration(paths, name) {
|
|
|
13588
13984
|
const file = path16.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json");
|
|
13589
13985
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
13590
13986
|
try {
|
|
13591
|
-
await
|
|
13987
|
+
await fs13.lstat(file);
|
|
13592
13988
|
return true;
|
|
13593
13989
|
} catch (error) {
|
|
13594
13990
|
if (error.code === "ENOENT") return false;
|
|
@@ -13599,7 +13995,7 @@ async function hasPendingNativeCheckpointRecovery(paths, name) {
|
|
|
13599
13995
|
const file = path16.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json");
|
|
13600
13996
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
13601
13997
|
try {
|
|
13602
|
-
await
|
|
13998
|
+
await fs13.lstat(file);
|
|
13603
13999
|
return true;
|
|
13604
14000
|
} catch (error) {
|
|
13605
14001
|
if (error.code === "ENOENT") return false;
|
|
@@ -13620,13 +14016,13 @@ async function createNativeChangeLocked(options) {
|
|
|
13620
14016
|
let createdChangeDir = false;
|
|
13621
14017
|
try {
|
|
13622
14018
|
try {
|
|
13623
|
-
await
|
|
14019
|
+
await fs13.mkdir(changeDir, { recursive: false });
|
|
13624
14020
|
createdChangeDir = true;
|
|
13625
14021
|
} catch (error) {
|
|
13626
14022
|
if (error.code === "ENOENT") {
|
|
13627
|
-
await
|
|
14023
|
+
await fs13.mkdir(options.paths.changesDir, { recursive: true });
|
|
13628
14024
|
try {
|
|
13629
|
-
await
|
|
14025
|
+
await fs13.mkdir(changeDir, { recursive: false });
|
|
13630
14026
|
createdChangeDir = true;
|
|
13631
14027
|
} catch (retryError) {
|
|
13632
14028
|
if (retryError.code === "EEXIST") {
|
|
@@ -13663,13 +14059,23 @@ async function createNativeChangeLocked(options) {
|
|
|
13663
14059
|
run_id: null
|
|
13664
14060
|
};
|
|
13665
14061
|
await Promise.all([
|
|
13666
|
-
|
|
13667
|
-
|
|
14062
|
+
fs13.mkdir(path16.join(changeDir, "specs"), { recursive: true }),
|
|
14063
|
+
fs13.mkdir(path16.join(changeDir, "runtime", "checkpoints"), { recursive: true }),
|
|
13668
14064
|
atomicWriteText(path16.join(changeDir, "brief.md"), NATIVE_BRIEF_TEMPLATE)
|
|
13669
14065
|
]);
|
|
14066
|
+
const projectConfig = await readProjectConfig(options.paths.projectRoot);
|
|
14067
|
+
const snapshot2 = projectConfig?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG;
|
|
13670
14068
|
const baseline = await createNativeContentSnapshot(options.paths, {
|
|
13671
14069
|
now: options.now,
|
|
13672
|
-
origin: "change-created"
|
|
14070
|
+
origin: "change-created",
|
|
14071
|
+
policy: snapshot2,
|
|
14072
|
+
limits: {
|
|
14073
|
+
maxFiles: snapshot2.max_files,
|
|
14074
|
+
maxFileBytes: snapshot2.max_total_bytes,
|
|
14075
|
+
maxTotalBytes: snapshot2.max_total_bytes,
|
|
14076
|
+
maxDurationMs: snapshot2.max_duration_ms
|
|
14077
|
+
},
|
|
14078
|
+
deadlineMs: snapshot2.max_duration_ms
|
|
13673
14079
|
});
|
|
13674
14080
|
if (!baseline.complete) {
|
|
13675
14081
|
const health = inspectNativeContentSnapshotHealth(baseline);
|
|
@@ -13684,7 +14090,9 @@ async function createNativeChangeLocked(options) {
|
|
|
13684
14090
|
baseline.omittedCount,
|
|
13685
14091
|
omittedByReason,
|
|
13686
14092
|
health.samplePaths,
|
|
13687
|
-
health.sampleTruncated
|
|
14093
|
+
health.sampleTruncated,
|
|
14094
|
+
baseline.limits,
|
|
14095
|
+
baseline.policy?.hash ?? null
|
|
13688
14096
|
);
|
|
13689
14097
|
}
|
|
13690
14098
|
await writeNativeBaselineManifest(options.paths, state.name, baseline);
|
|
@@ -13697,7 +14105,7 @@ async function createNativeChangeLocked(options) {
|
|
|
13697
14105
|
});
|
|
13698
14106
|
return state;
|
|
13699
14107
|
} catch (error) {
|
|
13700
|
-
if (createdChangeDir) await
|
|
14108
|
+
if (createdChangeDir) await fs13.rm(changeDir, { recursive: true, force: true });
|
|
13701
14109
|
throw error;
|
|
13702
14110
|
}
|
|
13703
14111
|
}
|
|
@@ -13747,7 +14155,7 @@ async function createNativeChangeFile(paths, state) {
|
|
|
13747
14155
|
const file = path16.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE);
|
|
13748
14156
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
13749
14157
|
try {
|
|
13750
|
-
await
|
|
14158
|
+
await fs13.access(file);
|
|
13751
14159
|
throw new Error(`Native change state already exists: ${state.name}`);
|
|
13752
14160
|
} catch (error) {
|
|
13753
14161
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -13845,12 +14253,12 @@ async function readContainedFile(root, relativeRef) {
|
|
|
13845
14253
|
const target = path17.resolve(root, ...relativeRef.split(/[\\/]/u));
|
|
13846
14254
|
if (!isInsidePath(root, target))
|
|
13847
14255
|
throw new Error(`Artifact escapes Native change: ${relativeRef}`);
|
|
13848
|
-
const realRoot = await
|
|
13849
|
-
const realTarget = await
|
|
14256
|
+
const realRoot = await fs14.realpath(root);
|
|
14257
|
+
const realTarget = await fs14.realpath(target);
|
|
13850
14258
|
if (!isInsidePath(realRoot, realTarget)) {
|
|
13851
14259
|
throw new Error(`Artifact symlink escapes Native change: ${relativeRef}`);
|
|
13852
14260
|
}
|
|
13853
|
-
if (!(await
|
|
14261
|
+
if (!(await fs14.stat(realTarget)).isFile())
|
|
13854
14262
|
throw new Error(`Artifact is not a file: ${relativeRef}`);
|
|
13855
14263
|
return realTarget;
|
|
13856
14264
|
}
|
|
@@ -13992,7 +14400,7 @@ async function resolveNativeArtifactFile(changeDir, relativeRef) {
|
|
|
13992
14400
|
}
|
|
13993
14401
|
|
|
13994
14402
|
// domains/comet-native/native-archive-inspection.ts
|
|
13995
|
-
import { promises as
|
|
14403
|
+
import { promises as fs18 } from "node:fs";
|
|
13996
14404
|
import path29 from "node:path";
|
|
13997
14405
|
|
|
13998
14406
|
// domains/comet-native/native-archive-preflight.ts
|
|
@@ -14582,7 +14990,7 @@ function buildNativeConflictRadar(input) {
|
|
|
14582
14990
|
|
|
14583
14991
|
// domains/comet-native/native-evidence-storage.ts
|
|
14584
14992
|
import { createHash as createHash12 } from "node:crypto";
|
|
14585
|
-
import { promises as
|
|
14993
|
+
import { promises as fs15 } from "node:fs";
|
|
14586
14994
|
import path22 from "node:path";
|
|
14587
14995
|
|
|
14588
14996
|
// domains/comet-native/native-verification-scope.ts
|
|
@@ -14771,8 +15179,10 @@ function snapshotProjection(manifest) {
|
|
|
14771
15179
|
maxFiles: parsed.limits.maxFiles,
|
|
14772
15180
|
maxFileBytes: parsed.limits.maxFileBytes,
|
|
14773
15181
|
maxTotalBytes: parsed.limits.maxTotalBytes,
|
|
14774
|
-
maxManifestBytes: parsed.limits.maxManifestBytes
|
|
15182
|
+
maxManifestBytes: parsed.limits.maxManifestBytes,
|
|
15183
|
+
...parsed.limits.maxDurationMs === void 0 ? {} : { maxDurationMs: parsed.limits.maxDurationMs }
|
|
14775
15184
|
},
|
|
15185
|
+
...parsed.policy ? { policy: parsed.policy } : {},
|
|
14776
15186
|
entries,
|
|
14777
15187
|
omitted,
|
|
14778
15188
|
omittedCount: parsed.omittedCount,
|
|
@@ -15250,7 +15660,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
|
|
|
15250
15660
|
exactScopeKeys(
|
|
15251
15661
|
root,
|
|
15252
15662
|
["schema", "origin", "complete", "limits", "entries", "omitted", "omittedCount"],
|
|
15253
|
-
["capture", "omissionOverflow"],
|
|
15663
|
+
["capture", "omissionOverflow", "policy"],
|
|
15254
15664
|
"Native snapshot projection"
|
|
15255
15665
|
);
|
|
15256
15666
|
if (root.schema !== NATIVE_SNAPSHOT_PROJECTION_SCHEMA) {
|
|
@@ -15263,6 +15673,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
|
|
|
15263
15673
|
createdAt: "1970-01-01T00:00:00.000Z",
|
|
15264
15674
|
complete: root.complete,
|
|
15265
15675
|
limits: root.limits,
|
|
15676
|
+
...root.policy === void 0 ? {} : { policy: root.policy },
|
|
15266
15677
|
entries: root.entries,
|
|
15267
15678
|
omitted: root.omitted,
|
|
15268
15679
|
omittedCount: root.omittedCount,
|
|
@@ -16086,7 +16497,7 @@ function isInside5(parent, target) {
|
|
|
16086
16497
|
return relative === "" || !path22.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path22.sep}`);
|
|
16087
16498
|
}
|
|
16088
16499
|
function sameDirectoryIdentity5(identity, stat) {
|
|
16089
|
-
return
|
|
16500
|
+
return sameFileObject(
|
|
16090
16501
|
{ ...identity, birthtime: identity.birthtimeMs },
|
|
16091
16502
|
{
|
|
16092
16503
|
...stat,
|
|
@@ -16097,19 +16508,19 @@ function sameDirectoryIdentity5(identity, stat) {
|
|
|
16097
16508
|
function sameFileIdentity6(left, right) {
|
|
16098
16509
|
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
16099
16510
|
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
16100
|
-
if (
|
|
16101
|
-
return
|
|
16511
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
16512
|
+
return sameFileObject(leftObject, rightObject);
|
|
16102
16513
|
}
|
|
16103
|
-
return
|
|
16514
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
16104
16515
|
}
|
|
16105
16516
|
async function captureDirectoryIdentity3(directory) {
|
|
16106
|
-
const stat = await
|
|
16517
|
+
const stat = await fs15.lstat(directory);
|
|
16107
16518
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
16108
16519
|
throw new Error(`Native evidence parent must be a real directory: ${directory}`);
|
|
16109
16520
|
}
|
|
16110
16521
|
return {
|
|
16111
16522
|
path: directory,
|
|
16112
|
-
realPath: await
|
|
16523
|
+
realPath: await fs15.realpath(directory),
|
|
16113
16524
|
dev: stat.dev,
|
|
16114
16525
|
ino: stat.ino,
|
|
16115
16526
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -16135,8 +16546,8 @@ async function captureDirectoryChain4(root, directory) {
|
|
|
16135
16546
|
}
|
|
16136
16547
|
async function verifyDirectoryChain5(chain) {
|
|
16137
16548
|
for (const identity of chain) {
|
|
16138
|
-
const stat = await
|
|
16139
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity5(identity, stat) || await
|
|
16549
|
+
const stat = await fs15.lstat(identity.path);
|
|
16550
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity5(identity, stat) || await fs15.realpath(identity.path) !== identity.realPath) {
|
|
16140
16551
|
throw new Error(`Native evidence parent changed while reading: ${identity.path}`);
|
|
16141
16552
|
}
|
|
16142
16553
|
}
|
|
@@ -16144,20 +16555,20 @@ async function verifyDirectoryChain5(chain) {
|
|
|
16144
16555
|
async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
|
|
16145
16556
|
const chain = await captureDirectoryChain4(changeRoot, path22.dirname(file));
|
|
16146
16557
|
await hooks.afterParentChainCaptured?.();
|
|
16147
|
-
const lexical = await
|
|
16558
|
+
const lexical = await fs15.lstat(file);
|
|
16148
16559
|
if (!lexical.isFile() || lexical.isSymbolicLink()) {
|
|
16149
16560
|
throw new Error("Native evidence document must be a regular file");
|
|
16150
16561
|
}
|
|
16151
|
-
const realPath = await
|
|
16562
|
+
const realPath = await fs15.realpath(file);
|
|
16152
16563
|
if (!isInside5(chain[0].realPath, realPath)) {
|
|
16153
16564
|
throw new Error("Native evidence document resolves outside its change");
|
|
16154
16565
|
}
|
|
16155
|
-
const handle = await
|
|
16566
|
+
const handle = await fs15.open(file, "r");
|
|
16156
16567
|
try {
|
|
16157
16568
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
16158
16569
|
handle.stat(),
|
|
16159
|
-
|
|
16160
|
-
|
|
16570
|
+
fs15.lstat(file),
|
|
16571
|
+
fs15.realpath(file)
|
|
16161
16572
|
]);
|
|
16162
16573
|
await verifyDirectoryChain5(chain);
|
|
16163
16574
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== realPath || !sameFileIdentity6(opened, lexical) || !sameFileIdentity6(opened, pathAfterOpen)) {
|
|
@@ -16187,8 +16598,8 @@ async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
|
|
|
16187
16598
|
await hooks.beforeFinalCheck?.();
|
|
16188
16599
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
16189
16600
|
handle.stat(),
|
|
16190
|
-
|
|
16191
|
-
|
|
16601
|
+
fs15.lstat(file),
|
|
16602
|
+
fs15.realpath(file)
|
|
16192
16603
|
]);
|
|
16193
16604
|
await verifyDirectoryChain5(chain);
|
|
16194
16605
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity6(opened, afterHandle) || !sameFileIdentity6(opened, afterPath)) {
|
|
@@ -16280,7 +16691,7 @@ function assertEvidenceDocumentBudget(value) {
|
|
|
16280
16691
|
function serializedEvidenceBytes2(value) {
|
|
16281
16692
|
return Buffer.byteLength(JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
16282
16693
|
}
|
|
16283
|
-
function
|
|
16694
|
+
function parseSnapshot2(value, expectedHash) {
|
|
16284
16695
|
return parseNativeSnapshotProjection(value, expectedHash);
|
|
16285
16696
|
}
|
|
16286
16697
|
function parseScope(value, expectedHash) {
|
|
@@ -16356,10 +16767,10 @@ async function readNativeImplementationScopeBundle(paths, name, ref, hooks) {
|
|
|
16356
16767
|
const currentHash = parseEvidenceRef(scope.currentProjectionRef, "snapshots");
|
|
16357
16768
|
const [baseline, current] = await Promise.all([
|
|
16358
16769
|
readEvidenceDocument({ paths, name, kind: "snapshots", hash: baselineHash }).then(
|
|
16359
|
-
(value) =>
|
|
16770
|
+
(value) => parseSnapshot2(value, baselineHash)
|
|
16360
16771
|
),
|
|
16361
16772
|
readEvidenceDocument({ paths, name, kind: "snapshots", hash: currentHash }).then(
|
|
16362
|
-
(value) =>
|
|
16773
|
+
(value) => parseSnapshot2(value, currentHash)
|
|
16363
16774
|
)
|
|
16364
16775
|
]);
|
|
16365
16776
|
return rebuildNativeImplementationScopeBundle({ baseline, current, scope });
|
|
@@ -16484,7 +16895,7 @@ async function inspectNativeChangeConflicts(paths, name) {
|
|
|
16484
16895
|
|
|
16485
16896
|
// domains/comet-native/native-transition-journal.ts
|
|
16486
16897
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
16487
|
-
import { promises as
|
|
16898
|
+
import { promises as fs16 } from "fs";
|
|
16488
16899
|
import path23 from "path";
|
|
16489
16900
|
import { isDeepStrictEqual } from "util";
|
|
16490
16901
|
|
|
@@ -17942,7 +18353,7 @@ async function continueNativeTransitionLocked(paths, name, hooks) {
|
|
|
17942
18353
|
`Native transition journal changed while continuing ${journal.change}; it was not removed`
|
|
17943
18354
|
);
|
|
17944
18355
|
}
|
|
17945
|
-
await
|
|
18356
|
+
await fs16.rm(nativeTransitionJournalFile(paths, name), { force: true });
|
|
17946
18357
|
return journal.nextState;
|
|
17947
18358
|
}
|
|
17948
18359
|
async function continueNativeTransition(paths, name, hooks) {
|
|
@@ -18406,9 +18817,16 @@ function canonicalEvidencePayload(entries) {
|
|
|
18406
18817
|
const validated = validateEvidenceEntries([...entries]).map((entry2) => ({ ...entry2, evidence_refs: [...entry2.evidence_refs].sort() })).sort((left, right) => left.acceptance_id.localeCompare(right.acceptance_id));
|
|
18407
18818
|
return JSON.stringify(validated, null, 2);
|
|
18408
18819
|
}
|
|
18820
|
+
function serializeNativeVerificationMachineBlock(entries) {
|
|
18821
|
+
return [
|
|
18822
|
+
NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER,
|
|
18823
|
+
canonicalEvidencePayload(entries),
|
|
18824
|
+
NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER
|
|
18825
|
+
].join("\n");
|
|
18826
|
+
}
|
|
18409
18827
|
|
|
18410
18828
|
// domains/comet-native/native-check-receipt-storage.ts
|
|
18411
|
-
import { constants as
|
|
18829
|
+
import { constants as fsConstants4, promises as fs17 } from "node:fs";
|
|
18412
18830
|
import path26 from "node:path";
|
|
18413
18831
|
|
|
18414
18832
|
// domains/comet-native/native-check-receipt-model.ts
|
|
@@ -18757,7 +19175,7 @@ var HASH_PATTERN11 = /^[a-f0-9]{64}$/u;
|
|
|
18757
19175
|
var RECEIPT_REF_PATTERN = /^runtime\/evidence\/check-receipts\/([a-f0-9]{64})\.json$/u;
|
|
18758
19176
|
var MAX_NATIVE_CHECK_RECEIPT_BYTES = 512 * 1024;
|
|
18759
19177
|
function sameDirectoryIdentity6(identity, stat) {
|
|
18760
|
-
return
|
|
19178
|
+
return sameFileObject(
|
|
18761
19179
|
{ ...identity, birthtime: identity.birthtimeMs },
|
|
18762
19180
|
{
|
|
18763
19181
|
...stat,
|
|
@@ -18768,10 +19186,10 @@ function sameDirectoryIdentity6(identity, stat) {
|
|
|
18768
19186
|
function sameFileIdentity7(left, right) {
|
|
18769
19187
|
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
18770
19188
|
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
18771
|
-
if (
|
|
18772
|
-
return
|
|
19189
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
19190
|
+
return sameFileObject(leftObject, rightObject);
|
|
18773
19191
|
}
|
|
18774
|
-
return
|
|
19192
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
18775
19193
|
}
|
|
18776
19194
|
async function captureDirectoryChain5(root, directory) {
|
|
18777
19195
|
const lexicalRoot = path26.resolve(root);
|
|
@@ -18786,11 +19204,11 @@ async function captureDirectoryChain5(root, directory) {
|
|
|
18786
19204
|
...path26.relative(lexicalRoot, lexicalDirectory).split(path26.sep).filter(Boolean)
|
|
18787
19205
|
]) {
|
|
18788
19206
|
if (segment) cursor = path26.join(cursor, segment);
|
|
18789
|
-
const stat = await
|
|
19207
|
+
const stat = await fs17.lstat(cursor);
|
|
18790
19208
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
18791
19209
|
throw new Error(`Native check receipt parent must be a real directory: ${cursor}`);
|
|
18792
19210
|
}
|
|
18793
|
-
const realPath = await
|
|
19211
|
+
const realPath = await fs17.realpath(cursor);
|
|
18794
19212
|
if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
|
|
18795
19213
|
throw new Error("Native check receipt parent resolves outside the Native root");
|
|
18796
19214
|
}
|
|
@@ -18806,8 +19224,8 @@ async function captureDirectoryChain5(root, directory) {
|
|
|
18806
19224
|
}
|
|
18807
19225
|
async function verifyDirectoryChain6(chain) {
|
|
18808
19226
|
for (const identity of chain) {
|
|
18809
|
-
const stat = await
|
|
18810
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity6(identity, stat) || await
|
|
19227
|
+
const stat = await fs17.lstat(identity.path);
|
|
19228
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity6(identity, stat) || await fs17.realpath(identity.path) !== identity.realPath) {
|
|
18811
19229
|
throw new Error(`Native check receipt parent changed while reading: ${identity.path}`);
|
|
18812
19230
|
}
|
|
18813
19231
|
}
|
|
@@ -18826,7 +19244,7 @@ function receiptFile(paths, name, hash6) {
|
|
|
18826
19244
|
}
|
|
18827
19245
|
async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
18828
19246
|
const chain = await captureDirectoryChain5(nativeRoot, path26.dirname(file));
|
|
18829
|
-
const before = await
|
|
19247
|
+
const before = await fs17.lstat(file);
|
|
18830
19248
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
18831
19249
|
throw new Error("Native check receipt must be a regular file");
|
|
18832
19250
|
}
|
|
@@ -18834,14 +19252,14 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
|
18834
19252
|
throw new Error(`Native check receipt exceeds ${MAX_NATIVE_CHECK_RECEIPT_BYTES} bytes`);
|
|
18835
19253
|
}
|
|
18836
19254
|
const [realChangeRoot, beforeRealPath] = await Promise.all([
|
|
18837
|
-
|
|
18838
|
-
|
|
19255
|
+
fs17.realpath(changeRoot),
|
|
19256
|
+
fs17.realpath(file)
|
|
18839
19257
|
]);
|
|
18840
19258
|
if (!isInsidePath(realChangeRoot, beforeRealPath)) {
|
|
18841
19259
|
throw new Error("Native check receipt resolves outside its change");
|
|
18842
19260
|
}
|
|
18843
|
-
const openFlags = process.platform === "win32" ? "r" :
|
|
18844
|
-
const handle = await
|
|
19261
|
+
const openFlags = process.platform === "win32" ? "r" : fsConstants4.O_RDONLY | fsConstants4.O_NOFOLLOW | fsConstants4.O_NONBLOCK;
|
|
19262
|
+
const handle = await fs17.open(file, openFlags).catch((error) => {
|
|
18845
19263
|
if (error.code === "ELOOP" || error.code === "ENXIO") {
|
|
18846
19264
|
throw new Error("Native check receipt became unsafe while opening");
|
|
18847
19265
|
}
|
|
@@ -18850,8 +19268,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
|
18850
19268
|
try {
|
|
18851
19269
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
18852
19270
|
handle.stat(),
|
|
18853
|
-
|
|
18854
|
-
|
|
19271
|
+
fs17.lstat(file),
|
|
19272
|
+
fs17.realpath(file)
|
|
18855
19273
|
]);
|
|
18856
19274
|
await verifyDirectoryChain6(chain);
|
|
18857
19275
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity7(before, opened) || !sameFileIdentity7(opened, pathAfterOpen)) {
|
|
@@ -18872,8 +19290,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
|
18872
19290
|
}
|
|
18873
19291
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
18874
19292
|
handle.stat(),
|
|
18875
|
-
|
|
18876
|
-
|
|
19293
|
+
fs17.lstat(file),
|
|
19294
|
+
fs17.realpath(file)
|
|
18877
19295
|
]);
|
|
18878
19296
|
await verifyDirectoryChain6(chain);
|
|
18879
19297
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !isInsidePath(realChangeRoot, afterRealPath) || !sameFileIdentity7(opened, afterHandle) || !sameFileIdentity7(opened, afterPath)) {
|
|
@@ -19123,6 +19541,7 @@ function projectionManifest(projection) {
|
|
|
19123
19541
|
createdAt: "1970-01-01T00:00:00.000Z",
|
|
19124
19542
|
complete: projection.complete,
|
|
19125
19543
|
limits: projection.limits,
|
|
19544
|
+
...projection.policy ? { policy: projection.policy } : {},
|
|
19126
19545
|
entries: projection.entries,
|
|
19127
19546
|
omitted: projection.omitted,
|
|
19128
19547
|
omittedCount: projection.omittedCount,
|
|
@@ -19137,12 +19556,13 @@ function nativeRootRef2(paths) {
|
|
|
19137
19556
|
return value;
|
|
19138
19557
|
}
|
|
19139
19558
|
async function currentProjectionHash(options) {
|
|
19140
|
-
const
|
|
19559
|
+
const baseline = projectionManifest(options.bundle.baseline);
|
|
19560
|
+
const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
|
|
19141
19561
|
origin: "explicit",
|
|
19142
19562
|
now: options.now
|
|
19143
19563
|
});
|
|
19144
19564
|
return buildNativeImplementationScopeBundle({
|
|
19145
|
-
baseline
|
|
19565
|
+
baseline,
|
|
19146
19566
|
current,
|
|
19147
19567
|
contractHash: options.bundle.scope.contractHash,
|
|
19148
19568
|
declaredArtifacts: options.bundle.scope.declaredArtifacts,
|
|
@@ -19406,7 +19826,7 @@ function archiveTargetRef(name, now) {
|
|
|
19406
19826
|
}
|
|
19407
19827
|
async function exists2(target) {
|
|
19408
19828
|
try {
|
|
19409
|
-
await
|
|
19829
|
+
await fs18.lstat(target);
|
|
19410
19830
|
return true;
|
|
19411
19831
|
} catch (error) {
|
|
19412
19832
|
if (error.code === "ENOENT") return false;
|
|
@@ -19488,7 +19908,7 @@ async function inspectNativeArchivePreflight(options) {
|
|
|
19488
19908
|
}
|
|
19489
19909
|
|
|
19490
19910
|
// domains/comet-native/native-archive-content.ts
|
|
19491
|
-
import { promises as
|
|
19911
|
+
import { promises as fs19 } from "node:fs";
|
|
19492
19912
|
import path30 from "node:path";
|
|
19493
19913
|
var TREE_HASH_TAG = "comet.native.archive-tree.v1";
|
|
19494
19914
|
var NATIVE_ARCHIVE_CONTENT_LIMITS = {
|
|
@@ -19548,7 +19968,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
|
|
|
19548
19968
|
await protectedDirectory.verify();
|
|
19549
19969
|
const target = path30.join(directory, child.name);
|
|
19550
19970
|
const ref = path30.relative(root, target).replaceAll("\\", "/");
|
|
19551
|
-
const stat = await
|
|
19971
|
+
const stat = await fs19.lstat(target);
|
|
19552
19972
|
if (child.isSymbolicLink() || stat.isSymbolicLink()) {
|
|
19553
19973
|
throw new Error(`Native Archive content must not contain symlinks or junctions: ${ref}`);
|
|
19554
19974
|
}
|
|
@@ -19595,7 +20015,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
|
|
|
19595
20015
|
async function hashNativeArchiveTree(directory, requestedLimits = {}) {
|
|
19596
20016
|
const limits = normalizedLimits(requestedLimits);
|
|
19597
20017
|
directory = path30.resolve(directory);
|
|
19598
|
-
const stat = await
|
|
20018
|
+
const stat = await fs19.lstat(directory);
|
|
19599
20019
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
19600
20020
|
throw new Error(`Native Archive move source must be a real directory: ${directory}`);
|
|
19601
20021
|
}
|
|
@@ -19613,7 +20033,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
|
|
|
19613
20033
|
target = path30.resolve(target);
|
|
19614
20034
|
let stat;
|
|
19615
20035
|
try {
|
|
19616
|
-
stat = await
|
|
20036
|
+
stat = await fs19.lstat(target);
|
|
19617
20037
|
} catch (error) {
|
|
19618
20038
|
if (error.code === "ENOENT") return null;
|
|
19619
20039
|
throw error;
|
|
@@ -19637,7 +20057,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
|
|
|
19637
20057
|
}
|
|
19638
20058
|
|
|
19639
20059
|
// domains/comet-native/native-archive-transaction.ts
|
|
19640
|
-
import { promises as
|
|
20060
|
+
import { promises as fs20 } from "node:fs";
|
|
19641
20061
|
import path31 from "node:path";
|
|
19642
20062
|
var NATIVE_ARCHIVE_COPY_MAX_BYTES = 16 * 1024 * 1024;
|
|
19643
20063
|
var NATIVE_ARCHIVE_JOURNAL_MAX_BYTES = 256 * 1024;
|
|
@@ -19671,8 +20091,8 @@ async function assertContent(options) {
|
|
|
19671
20091
|
async function createNativeArchiveTransactionV2(paths, journal) {
|
|
19672
20092
|
const validated = parseNativeArchiveTransactionJournalV2(journal);
|
|
19673
20093
|
const tx = await resolveNativeTransactionPaths(paths, validated.id);
|
|
19674
|
-
await
|
|
19675
|
-
await
|
|
20094
|
+
await fs20.mkdir(tx.staged, { recursive: true });
|
|
20095
|
+
await fs20.mkdir(tx.backups, { recursive: true });
|
|
19676
20096
|
await atomicWriteJson(tx.journal, validated, { containedRoot: paths.nativeRoot });
|
|
19677
20097
|
await appendNativeTransactionEvent(paths, validated.id, "prepared");
|
|
19678
20098
|
}
|
|
@@ -19711,8 +20131,8 @@ function fileVersion(stat) {
|
|
|
19711
20131
|
size: stat.size
|
|
19712
20132
|
};
|
|
19713
20133
|
}
|
|
19714
|
-
function
|
|
19715
|
-
return
|
|
20134
|
+
function sameFileObject2(expected, actual) {
|
|
20135
|
+
return sameFileObject(
|
|
19716
20136
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
19717
20137
|
{
|
|
19718
20138
|
...actual,
|
|
@@ -19721,11 +20141,11 @@ function sameFileObject(expected, actual) {
|
|
|
19721
20141
|
) && expected.birthtimeMs === actual.birthtimeMs;
|
|
19722
20142
|
}
|
|
19723
20143
|
function sameFileVersion(expected, actual) {
|
|
19724
|
-
return
|
|
20144
|
+
return sameFileObject2(expected, actual) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
|
|
19725
20145
|
}
|
|
19726
20146
|
async function pathExists(target) {
|
|
19727
20147
|
try {
|
|
19728
|
-
await
|
|
20148
|
+
await fs20.lstat(target);
|
|
19729
20149
|
return true;
|
|
19730
20150
|
} catch (error) {
|
|
19731
20151
|
if (error.code === "ENOENT") return false;
|
|
@@ -19733,7 +20153,7 @@ async function pathExists(target) {
|
|
|
19733
20153
|
}
|
|
19734
20154
|
}
|
|
19735
20155
|
async function captureStableArchiveFile(options) {
|
|
19736
|
-
const before = await
|
|
20156
|
+
const before = await fs20.lstat(options.file);
|
|
19737
20157
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
19738
20158
|
throw new Error(`${options.label} must be a regular file`);
|
|
19739
20159
|
}
|
|
@@ -19744,7 +20164,7 @@ async function captureStableArchiveFile(options) {
|
|
|
19744
20164
|
maxBytes: NATIVE_ARCHIVE_COPY_MAX_BYTES,
|
|
19745
20165
|
label: options.label
|
|
19746
20166
|
});
|
|
19747
|
-
const after = await
|
|
20167
|
+
const after = await fs20.lstat(options.file);
|
|
19748
20168
|
if (!after.isFile() || after.isSymbolicLink() || !sameFileVersion(beforeVersion, fileVersion(after)) || snapshot2.hash !== options.expectedHash) {
|
|
19749
20169
|
throw new Error(`${options.label} content changed or object identity changed while binding`);
|
|
19750
20170
|
}
|
|
@@ -19822,7 +20242,7 @@ async function readCasRecord(options) {
|
|
|
19822
20242
|
async function persistCasRecord(options) {
|
|
19823
20243
|
const existing = await readCasRecord(options);
|
|
19824
20244
|
if (existing) {
|
|
19825
|
-
if (!
|
|
20245
|
+
if (!sameFileObject2(existing.identity, options.identity)) {
|
|
19826
20246
|
throw new Error(
|
|
19827
20247
|
`Archive CAS ${options.operation.id} ${options.role} object identity changed`
|
|
19828
20248
|
);
|
|
@@ -19850,7 +20270,7 @@ async function persistCasRecord(options) {
|
|
|
19850
20270
|
if (error.code !== "EEXIST") throw error;
|
|
19851
20271
|
}
|
|
19852
20272
|
const persisted = await readCasRecord(options);
|
|
19853
|
-
if (!persisted || !
|
|
20273
|
+
if (!persisted || !sameFileObject2(persisted.identity, options.identity)) {
|
|
19854
20274
|
throw new Error(`Archive CAS ${options.operation.id} ${options.role} record changed`);
|
|
19855
20275
|
}
|
|
19856
20276
|
return persisted;
|
|
@@ -19862,7 +20282,7 @@ async function validateFileAgainstCasRecord(options) {
|
|
|
19862
20282
|
expectedHash: options.record.hash,
|
|
19863
20283
|
label: options.label
|
|
19864
20284
|
});
|
|
19865
|
-
if (!
|
|
20285
|
+
if (!sameFileObject2(options.record.identity, current.identity)) {
|
|
19866
20286
|
throw new Error(`${options.label} object identity changed`);
|
|
19867
20287
|
}
|
|
19868
20288
|
}
|
|
@@ -19899,20 +20319,20 @@ async function restoreUnexpectedQuarantine(options) {
|
|
|
19899
20319
|
});
|
|
19900
20320
|
await guard.verify();
|
|
19901
20321
|
try {
|
|
19902
|
-
await
|
|
20322
|
+
await fs20.link(options.quarantine, options.target);
|
|
19903
20323
|
} catch (error) {
|
|
19904
20324
|
if (error.code === "EEXIST") return;
|
|
19905
20325
|
throw error;
|
|
19906
20326
|
}
|
|
19907
20327
|
await guard.verify();
|
|
19908
20328
|
const [quarantine, target] = await Promise.all([
|
|
19909
|
-
|
|
19910
|
-
|
|
20329
|
+
fs20.lstat(options.quarantine),
|
|
20330
|
+
fs20.lstat(options.target)
|
|
19911
20331
|
]);
|
|
19912
|
-
if (!
|
|
20332
|
+
if (!sameFileObject2(fileObjectIdentity(quarantine), fileObjectIdentity(target))) {
|
|
19913
20333
|
throw new Error(`Archive CAS ${options.operation.id} could not restore quarantined content`);
|
|
19914
20334
|
}
|
|
19915
|
-
await
|
|
20335
|
+
await fs20.unlink(options.quarantine);
|
|
19916
20336
|
await guard.verify();
|
|
19917
20337
|
}
|
|
19918
20338
|
async function quarantineBoundTarget(options) {
|
|
@@ -19948,7 +20368,7 @@ async function quarantineBoundTarget(options) {
|
|
|
19948
20368
|
label: `Archive ${options.phase} target ${options.operation.target}`
|
|
19949
20369
|
});
|
|
19950
20370
|
try {
|
|
19951
|
-
await
|
|
20371
|
+
await fs20.rename(options.target, options.quarantine);
|
|
19952
20372
|
await guard.verify();
|
|
19953
20373
|
await validateFileAgainstCasRecord({
|
|
19954
20374
|
paths: options.paths,
|
|
@@ -19980,7 +20400,7 @@ async function removeExactCasFile(options) {
|
|
|
19980
20400
|
});
|
|
19981
20401
|
await guard.verify();
|
|
19982
20402
|
await validateFileAgainstCasRecord(options);
|
|
19983
|
-
await
|
|
20403
|
+
await fs20.unlink(options.file);
|
|
19984
20404
|
await guard.verify();
|
|
19985
20405
|
}
|
|
19986
20406
|
async function ensureBackup(paths, operation, hooks) {
|
|
@@ -20173,7 +20593,7 @@ async function ensureWriteInstalled(options) {
|
|
|
20173
20593
|
expectedHash,
|
|
20174
20594
|
label: `Archive write target ${options.operation.target}`
|
|
20175
20595
|
});
|
|
20176
|
-
if (!
|
|
20596
|
+
if (!sameFileObject2(candidateIdentity2.identity, targetIdentity2.identity)) {
|
|
20177
20597
|
throw new Error(
|
|
20178
20598
|
`Archive write target ${options.operation.target} is occupied by an external object`
|
|
20179
20599
|
);
|
|
@@ -20228,7 +20648,7 @@ async function ensureWriteInstalled(options) {
|
|
|
20228
20648
|
label: `Archive write candidate ${options.operation.target}`
|
|
20229
20649
|
});
|
|
20230
20650
|
try {
|
|
20231
|
-
await
|
|
20651
|
+
await fs20.link(candidate, options.target);
|
|
20232
20652
|
} catch (error) {
|
|
20233
20653
|
if (error.code === "EEXIST") {
|
|
20234
20654
|
throw new Error(
|
|
@@ -20246,7 +20666,7 @@ async function ensureWriteInstalled(options) {
|
|
|
20246
20666
|
expectedHash,
|
|
20247
20667
|
label: `Archive write target ${options.operation.target}`
|
|
20248
20668
|
});
|
|
20249
|
-
if (!
|
|
20669
|
+
if (!sameFileObject2(candidateIdentity.identity, targetIdentity.identity)) {
|
|
20250
20670
|
throw new Error(`Archive write target ${options.operation.target} changed during install`);
|
|
20251
20671
|
}
|
|
20252
20672
|
record8 = await persistCasRecord({
|
|
@@ -20332,8 +20752,8 @@ async function applyMove(paths, operation) {
|
|
|
20332
20752
|
`Archive transaction move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
|
|
20333
20753
|
);
|
|
20334
20754
|
}
|
|
20335
|
-
await
|
|
20336
|
-
await
|
|
20755
|
+
await fs20.mkdir(path31.dirname(target), { recursive: true });
|
|
20756
|
+
await fs20.rename(source, target);
|
|
20337
20757
|
await assertContent({
|
|
20338
20758
|
target,
|
|
20339
20759
|
expectedHash: operation.expectedSourceHash,
|
|
@@ -20535,7 +20955,7 @@ async function installOriginalTarget(options) {
|
|
|
20535
20955
|
label: `Archive rollback original quarantine ${options.operation.target}`
|
|
20536
20956
|
});
|
|
20537
20957
|
try {
|
|
20538
|
-
await
|
|
20958
|
+
await fs20.link(options.quarantine, options.target);
|
|
20539
20959
|
} catch (error) {
|
|
20540
20960
|
if (error.code === "EEXIST") {
|
|
20541
20961
|
throw new Error(
|
|
@@ -20615,7 +21035,7 @@ async function rollbackWriteOrRemove(paths, journal, operation, hooks) {
|
|
|
20615
21035
|
expectedHash: operation.stagedHash,
|
|
20616
21036
|
label: `Archive rollback target ${operation.target}`
|
|
20617
21037
|
});
|
|
20618
|
-
if (
|
|
21038
|
+
if (sameFileObject2(candidate.identity, current.identity)) {
|
|
20619
21039
|
postRecord = await persistCasRecord({
|
|
20620
21040
|
paths,
|
|
20621
21041
|
file: cas.postRecord,
|
|
@@ -20729,8 +21149,8 @@ async function rollbackMove(paths, operation) {
|
|
|
20729
21149
|
`Archive rollback move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
|
|
20730
21150
|
);
|
|
20731
21151
|
}
|
|
20732
|
-
await
|
|
20733
|
-
await
|
|
21152
|
+
await fs20.mkdir(path31.dirname(source), { recursive: true });
|
|
21153
|
+
await fs20.rename(target, source);
|
|
20734
21154
|
}
|
|
20735
21155
|
async function rollbackNativeArchiveTransactionV2(paths, journal, hooks) {
|
|
20736
21156
|
const events = await readNativeTransactionEvents(paths, journal.id);
|
|
@@ -20828,7 +21248,7 @@ async function finalizeNativeArchiveTransactionV2(paths, journal, event) {
|
|
|
20828
21248
|
|
|
20829
21249
|
// domains/comet-native/native-checkpoint-journal.ts
|
|
20830
21250
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
20831
|
-
import { promises as
|
|
21251
|
+
import { promises as fs21 } from "fs";
|
|
20832
21252
|
|
|
20833
21253
|
// domains/comet-native/native-checkpoint-storage.ts
|
|
20834
21254
|
import path32 from "path";
|
|
@@ -21364,7 +21784,7 @@ async function continueNativeCheckpointLocked(paths, name, hooks) {
|
|
|
21364
21784
|
await hooks?.afterStateWritten?.(journal);
|
|
21365
21785
|
await writeNativeProgressCheckpoint(paths, journal.checkpoint);
|
|
21366
21786
|
await hooks?.afterProgressWritten?.(journal);
|
|
21367
|
-
await
|
|
21787
|
+
await fs21.rm(nativeCheckpointJournalFile(paths, name), { force: true });
|
|
21368
21788
|
return journal;
|
|
21369
21789
|
}
|
|
21370
21790
|
async function continueNativeCheckpoint(paths, name, hooks) {
|
|
@@ -21386,7 +21806,7 @@ async function settleNativeChangeJournalsLocked(paths, name) {
|
|
|
21386
21806
|
|
|
21387
21807
|
// domains/comet-entry/current-selection.ts
|
|
21388
21808
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
21389
|
-
import { promises as
|
|
21809
|
+
import { promises as fs22 } from "fs";
|
|
21390
21810
|
import path33 from "path";
|
|
21391
21811
|
var COMET_CURRENT_SELECTION_SCHEMA = "comet.selection.v2";
|
|
21392
21812
|
var COMET_CURRENT_SELECTION_MAX_BYTES = 16 * 1024;
|
|
@@ -21449,16 +21869,11 @@ function parseSelection(source) {
|
|
|
21449
21869
|
async function readCometCurrentSelection(projectRoot) {
|
|
21450
21870
|
let source;
|
|
21451
21871
|
try {
|
|
21452
|
-
const
|
|
21453
|
-
|
|
21454
|
-
|
|
21455
|
-
}
|
|
21456
|
-
|
|
21457
|
-
throw new Error(
|
|
21458
|
-
`current change selection exceeds ${COMET_CURRENT_SELECTION_MAX_BYTES} bytes`
|
|
21459
|
-
);
|
|
21460
|
-
}
|
|
21461
|
-
source = await fs21.readFile(cometCurrentSelectionFile(projectRoot), "utf8");
|
|
21872
|
+
const file = cometCurrentSelectionFile(projectRoot);
|
|
21873
|
+
const { bytes } = await readFileRaceSafe(file, COMET_CURRENT_SELECTION_MAX_BYTES, {
|
|
21874
|
+
label: "current change selection"
|
|
21875
|
+
});
|
|
21876
|
+
source = bytes.toString("utf8");
|
|
21462
21877
|
} catch (error) {
|
|
21463
21878
|
if (error.code === "ENOENT") return { status: "missing" };
|
|
21464
21879
|
throw new Error(
|
|
@@ -21474,18 +21889,18 @@ async function writeCometCurrentSelection(projectRoot, selection) {
|
|
|
21474
21889
|
if (parsed.legacy) throw new Error("cannot write a legacy current change selection");
|
|
21475
21890
|
const file = cometCurrentSelectionFile(projectRoot);
|
|
21476
21891
|
const temporary = `${file}.${randomUUID5()}.tmp`;
|
|
21477
|
-
await
|
|
21892
|
+
await fs22.mkdir(path33.dirname(file), { recursive: true });
|
|
21478
21893
|
try {
|
|
21479
|
-
await
|
|
21894
|
+
await fs22.writeFile(temporary, `${JSON.stringify(parsed.selection, null, 2)}
|
|
21480
21895
|
`, "utf8");
|
|
21481
|
-
await
|
|
21896
|
+
await fs22.rename(temporary, file);
|
|
21482
21897
|
} catch (error) {
|
|
21483
|
-
await
|
|
21898
|
+
await fs22.rm(temporary, { force: true });
|
|
21484
21899
|
throw error;
|
|
21485
21900
|
}
|
|
21486
21901
|
}
|
|
21487
21902
|
async function clearCometCurrentSelection(projectRoot) {
|
|
21488
|
-
await
|
|
21903
|
+
await fs22.rm(cometCurrentSelectionFile(projectRoot), { force: true });
|
|
21489
21904
|
}
|
|
21490
21905
|
async function clearCometCurrentSelectionIf(projectRoot, workflow, change) {
|
|
21491
21906
|
const current = await readCometCurrentSelection(projectRoot);
|
|
@@ -21599,7 +22014,7 @@ function archiveTarget(paths, name, now) {
|
|
|
21599
22014
|
}
|
|
21600
22015
|
async function pathExists2(target) {
|
|
21601
22016
|
try {
|
|
21602
|
-
await
|
|
22017
|
+
await fs23.access(target);
|
|
21603
22018
|
return true;
|
|
21604
22019
|
} catch (error) {
|
|
21605
22020
|
if (error.code === "ENOENT") return false;
|
|
@@ -21910,7 +22325,7 @@ async function recoverArchiveTransaction(options) {
|
|
|
21910
22325
|
}
|
|
21911
22326
|
|
|
21912
22327
|
// domains/comet-native/native-diagnostics.ts
|
|
21913
|
-
import { promises as
|
|
22328
|
+
import { promises as fs24 } from "fs";
|
|
21914
22329
|
|
|
21915
22330
|
// domains/comet-native/native-run-consistency.ts
|
|
21916
22331
|
import path35 from "path";
|
|
@@ -22036,8 +22451,12 @@ async function inspectNativeRunConsistency(paths, state) {
|
|
|
22036
22451
|
// domains/comet-native/native-continuation.ts
|
|
22037
22452
|
var REPAIR_CODES = /^(?:run-|trajectory-|checkpoint-(?:missing|mismatch|invalid|progress-invalid)|transition-(?:incomplete|invalid))/u;
|
|
22038
22453
|
function requiredPhaseInputs(state) {
|
|
22039
|
-
if (state.phase === "shape")
|
|
22040
|
-
|
|
22454
|
+
if (state.phase === "shape") {
|
|
22455
|
+
return ["summary", "shared-understanding-confirmation"];
|
|
22456
|
+
}
|
|
22457
|
+
if (state.phase === "build") {
|
|
22458
|
+
return state.approval === "confirmed" ? ["summary", "artifact-or-no-code-reason"] : ["summary", "artifact-or-no-code-reason", "shared-understanding-confirmation"];
|
|
22459
|
+
}
|
|
22041
22460
|
if (state.phase === "verify") return ["summary", "verification-result", "verification-report"];
|
|
22042
22461
|
return [];
|
|
22043
22462
|
}
|
|
@@ -22168,6 +22587,7 @@ function nativeContinuation(options) {
|
|
|
22168
22587
|
requiredInputs: options.archiveReady ? [] : ["archive-readiness"]
|
|
22169
22588
|
};
|
|
22170
22589
|
}
|
|
22590
|
+
const confirmationSuffix = options.state.phase === "shape" || options.state.phase === "build" && options.state.approval !== "confirmed" ? " --confirmed" : "";
|
|
22171
22591
|
return {
|
|
22172
22592
|
schema: "comet.native.continuation.v1",
|
|
22173
22593
|
skill: "comet-native",
|
|
@@ -22176,7 +22596,7 @@ function nativeContinuation(options) {
|
|
|
22176
22596
|
revision: options.state.revision,
|
|
22177
22597
|
disposition: "continue",
|
|
22178
22598
|
action: "advance-phase",
|
|
22179
|
-
command: `comet native next ${options.state.name} --summary "<summary>"`,
|
|
22599
|
+
command: `comet native next ${options.state.name} --summary "<summary>"${confirmationSuffix}`,
|
|
22180
22600
|
requiresUserDecision: false,
|
|
22181
22601
|
requiredInputs: requiredPhaseInputs(options.state)
|
|
22182
22602
|
};
|
|
@@ -22192,6 +22612,18 @@ var EXACT_METADATA = {
|
|
|
22192
22612
|
retry: "next",
|
|
22193
22613
|
repair: "none"
|
|
22194
22614
|
},
|
|
22615
|
+
"shape-confirmation-required": {
|
|
22616
|
+
severity: "error",
|
|
22617
|
+
requiredAction: "confirm-shared-understanding",
|
|
22618
|
+
retry: "next",
|
|
22619
|
+
repair: "none"
|
|
22620
|
+
},
|
|
22621
|
+
"approval-confirmation-required": {
|
|
22622
|
+
severity: "error",
|
|
22623
|
+
requiredAction: "confirm-shared-understanding",
|
|
22624
|
+
retry: "next",
|
|
22625
|
+
repair: "none"
|
|
22626
|
+
},
|
|
22195
22627
|
"transition-incomplete": {
|
|
22196
22628
|
severity: "error",
|
|
22197
22629
|
requiredAction: "recover-transition",
|
|
@@ -22363,7 +22795,7 @@ function projectRelativePath3(paths, state, finding) {
|
|
|
22363
22795
|
}
|
|
22364
22796
|
function retryCommand(retry, state, code) {
|
|
22365
22797
|
if (retry === "next") {
|
|
22366
|
-
return `comet native next ${state.name} --summary "<summary>"${code === "contract-changed-after-approval" ? " --confirmed" : ""}`;
|
|
22798
|
+
return `comet native next ${state.name} --summary "<summary>"${code === "contract-changed-after-approval" || code === "shape-confirmation-required" || code === "approval-confirmation-required" ? " --confirmed" : ""}`;
|
|
22367
22799
|
}
|
|
22368
22800
|
if (retry === "status") return `comet native status ${state.name} --details`;
|
|
22369
22801
|
return null;
|
|
@@ -22381,7 +22813,7 @@ function structureNativeFindings(options) {
|
|
|
22381
22813
|
repairCommand: metadata.repair === "doctor" ? `comet native doctor ${options.state.name} --repair${finding.code.startsWith("transition-") ? " --strategy continue" : ""}` : null,
|
|
22382
22814
|
// This is intentionally code-based, not severity-based. Model-actionable
|
|
22383
22815
|
// missing data must never be presented as a user decision.
|
|
22384
|
-
requiresUserDecision: finding.code === "brief-blocking-question" || finding.code === "contract-changed-after-approval" || finding.code === "verification-scope-partial" || finding.code === "repair-iteration-limit" || finding.code === "repair-override-exhausted"
|
|
22816
|
+
requiresUserDecision: finding.code === "brief-blocking-question" || finding.code === "shape-confirmation-required" || finding.code === "approval-confirmation-required" || finding.code === "contract-changed-after-approval" || finding.code === "verification-scope-partial" || finding.code === "repair-iteration-limit" || finding.code === "repair-override-exhausted"
|
|
22385
22817
|
};
|
|
22386
22818
|
}).sort((left, right) => {
|
|
22387
22819
|
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
@@ -22695,11 +23127,11 @@ async function selectedName(paths) {
|
|
|
22695
23127
|
return null;
|
|
22696
23128
|
}
|
|
22697
23129
|
}
|
|
22698
|
-
function nativeNextCommand(state, archiveReady, evidenceRetreat = false) {
|
|
23130
|
+
function nativeNextCommand(state, archiveReady, evidenceRetreat = false, _clarificationMode) {
|
|
22699
23131
|
if (state.phase === "archive") {
|
|
22700
23132
|
return archiveReady ? `comet native archive ${state.name} --dry-run` : evidenceRetreat ? `comet native next ${state.name} --summary "<summary>"` : null;
|
|
22701
23133
|
}
|
|
22702
|
-
return `comet native next ${state.name} --summary "<summary>"`;
|
|
23134
|
+
return `comet native next ${state.name} --summary "<summary>"${state.phase === "shape" || state.phase === "build" && state.approval !== "confirmed" ? " --confirmed" : ""}`;
|
|
22703
23135
|
}
|
|
22704
23136
|
async function statusFindings(paths, state) {
|
|
22705
23137
|
const changeDir = nativeChangeDir(paths, state.name);
|
|
@@ -23030,13 +23462,19 @@ async function inspectNativeStatus(paths, name, options) {
|
|
|
23030
23462
|
verificationResult: state.verification_result,
|
|
23031
23463
|
specChanges: state.spec_changes.length,
|
|
23032
23464
|
selected,
|
|
23033
|
-
nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat),
|
|
23465
|
+
nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat, options?.clarificationMode),
|
|
23034
23466
|
archiveReady,
|
|
23035
23467
|
inspection: resume.inspection,
|
|
23036
23468
|
findingSummary: summarizeNativeFindings(findings),
|
|
23037
23469
|
detailsCommand: `comet native status ${state.name} --details`,
|
|
23038
23470
|
checkpoint: resume.checkpoint,
|
|
23039
|
-
continuation: nativeContinuation({
|
|
23471
|
+
continuation: nativeContinuation({
|
|
23472
|
+
state,
|
|
23473
|
+
findings,
|
|
23474
|
+
archiveReady,
|
|
23475
|
+
evidenceRetreat,
|
|
23476
|
+
clarificationMode: options?.clarificationMode
|
|
23477
|
+
}),
|
|
23040
23478
|
repair,
|
|
23041
23479
|
...options?.details ? {
|
|
23042
23480
|
...acceptancePage ? { acceptancePage } : {},
|
|
@@ -23070,7 +23508,7 @@ async function boundedNativeChangeNames(paths) {
|
|
|
23070
23508
|
throw error;
|
|
23071
23509
|
}
|
|
23072
23510
|
const names = [];
|
|
23073
|
-
const directory = await
|
|
23511
|
+
const directory = await fs24.opendir(paths.changesDir);
|
|
23074
23512
|
try {
|
|
23075
23513
|
for await (const entry2 of directory) {
|
|
23076
23514
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
@@ -23119,7 +23557,11 @@ async function listNativeStatusPage(paths, options) {
|
|
|
23119
23557
|
cursor: options?.cursor
|
|
23120
23558
|
});
|
|
23121
23559
|
const candidates = await Promise.all(
|
|
23122
|
-
names.slice(offset, offset + NATIVE_STATUS_PAGE_LIMITS.maxItems).map(
|
|
23560
|
+
names.slice(offset, offset + NATIVE_STATUS_PAGE_LIMITS.maxItems).map(
|
|
23561
|
+
(name) => inspectNativeStatus(paths, name, {
|
|
23562
|
+
clarificationMode: options?.clarificationMode
|
|
23563
|
+
})
|
|
23564
|
+
)
|
|
23123
23565
|
);
|
|
23124
23566
|
const items = [];
|
|
23125
23567
|
for (const candidate of candidates) {
|
|
@@ -23151,23 +23593,25 @@ async function listNativeStatusPage(paths, options) {
|
|
|
23151
23593
|
limits: { ...NATIVE_STATUS_PAGE_LIMITS }
|
|
23152
23594
|
};
|
|
23153
23595
|
}
|
|
23154
|
-
async function listNativeStatus(paths) {
|
|
23155
|
-
return (await listNativeStatusPage(paths
|
|
23596
|
+
async function listNativeStatus(paths, options) {
|
|
23597
|
+
return (await listNativeStatusPage(paths, {
|
|
23598
|
+
clarificationMode: options?.clarificationMode
|
|
23599
|
+
})).items;
|
|
23156
23600
|
}
|
|
23157
23601
|
|
|
23158
23602
|
// domains/comet-native/native-doctor.ts
|
|
23159
|
-
import { promises as
|
|
23603
|
+
import { promises as fs28 } from "fs";
|
|
23160
23604
|
import path41 from "path";
|
|
23161
23605
|
|
|
23162
23606
|
// domains/comet-native/native-evidence-retention.ts
|
|
23163
23607
|
import { createHash as createHash13, randomUUID as randomUUID8 } from "node:crypto";
|
|
23164
|
-
import { constants as
|
|
23608
|
+
import { constants as fsConstants5, promises as fs26 } from "node:fs";
|
|
23165
23609
|
import path39 from "node:path";
|
|
23166
23610
|
|
|
23167
23611
|
// domains/comet-native/native-schema-migration.ts
|
|
23168
23612
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
23169
23613
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
23170
|
-
import { promises as
|
|
23614
|
+
import { promises as fs25 } from "fs";
|
|
23171
23615
|
import path38 from "path";
|
|
23172
23616
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
23173
23617
|
var HASH_PATTERN14 = /^[a-f0-9]{64}$/u;
|
|
@@ -23650,7 +24094,7 @@ async function continueTransitionSupersede(paths, journal, hooks) {
|
|
|
23650
24094
|
if (transitionHash !== supersede.sourceHash) {
|
|
23651
24095
|
throw new Error(`Native superseded transition changed before removal for ${journal.change}`);
|
|
23652
24096
|
}
|
|
23653
|
-
await
|
|
24097
|
+
await fs25.rm(transitionFile);
|
|
23654
24098
|
await hooks?.afterTransitionSuperseded?.(journal);
|
|
23655
24099
|
}
|
|
23656
24100
|
}
|
|
@@ -23732,7 +24176,7 @@ async function continueNativeSchemaMigrationLocked(paths, name, hooks) {
|
|
|
23732
24176
|
}
|
|
23733
24177
|
await continueTransitionSupersede(paths, journal, hooks);
|
|
23734
24178
|
await continueRunRetreat(paths, journal, hooks);
|
|
23735
|
-
await
|
|
24179
|
+
await fs25.rm(nativeSchemaMigrationJournalFile(paths, name), { force: true });
|
|
23736
24180
|
return journal.nextState;
|
|
23737
24181
|
}
|
|
23738
24182
|
async function stableEvidenceRetreat(options) {
|
|
@@ -24038,7 +24482,7 @@ var MANAGED_KINDS = [
|
|
|
24038
24482
|
"check-receipts"
|
|
24039
24483
|
];
|
|
24040
24484
|
function sameObjectIdentity(left, right) {
|
|
24041
|
-
return
|
|
24485
|
+
return sameFileObject(
|
|
24042
24486
|
{ ...left, birthtime: left.birthtimeMs },
|
|
24043
24487
|
{
|
|
24044
24488
|
...right,
|
|
@@ -24089,11 +24533,11 @@ async function captureDirectoryChain6(managedRoot, directory) {
|
|
|
24089
24533
|
let cursor = root;
|
|
24090
24534
|
for (const segment of ["", ...segments]) {
|
|
24091
24535
|
if (segment) cursor = path39.join(cursor, segment);
|
|
24092
|
-
const stat = await
|
|
24536
|
+
const stat = await fs26.lstat(cursor);
|
|
24093
24537
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
24094
24538
|
throw new Error(`Native evidence retention parent must be a real directory: ${cursor}`);
|
|
24095
24539
|
}
|
|
24096
|
-
const realPath = await
|
|
24540
|
+
const realPath = await fs26.realpath(cursor);
|
|
24097
24541
|
if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
|
|
24098
24542
|
throw new Error(`Native evidence retention parent resolves outside its change: ${cursor}`);
|
|
24099
24543
|
}
|
|
@@ -24103,8 +24547,8 @@ async function captureDirectoryChain6(managedRoot, directory) {
|
|
|
24103
24547
|
}
|
|
24104
24548
|
async function verifyDirectoryChain7(chain) {
|
|
24105
24549
|
for (const identity of chain) {
|
|
24106
|
-
const stat = await
|
|
24107
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameObjectIdentity(identity, stat) || await
|
|
24550
|
+
const stat = await fs26.lstat(identity.path);
|
|
24551
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameObjectIdentity(identity, stat) || await fs26.realpath(identity.path) !== identity.realPath) {
|
|
24108
24552
|
throw new Error(`Native evidence retention parent changed: ${identity.path}`);
|
|
24109
24553
|
}
|
|
24110
24554
|
}
|
|
@@ -24176,7 +24620,7 @@ function parseDocument4(kind, hash6, value, expectedChange) {
|
|
|
24176
24620
|
return { canonical: receipt, dependencies: [] };
|
|
24177
24621
|
}
|
|
24178
24622
|
async function readCanonicalDocument(options) {
|
|
24179
|
-
const before = await
|
|
24623
|
+
const before = await fs26.lstat(options.file);
|
|
24180
24624
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
24181
24625
|
throw new Error(`Native evidence entry is not a regular file: ${options.ref}`);
|
|
24182
24626
|
}
|
|
@@ -24184,12 +24628,12 @@ async function readCanonicalDocument(options) {
|
|
|
24184
24628
|
if (before.size > maximumBytes) {
|
|
24185
24629
|
throw new Error(`Native evidence entry exceeds its byte budget: ${options.ref}`);
|
|
24186
24630
|
}
|
|
24187
|
-
const beforeRealPath = await
|
|
24631
|
+
const beforeRealPath = await fs26.realpath(options.file);
|
|
24188
24632
|
if (!isInsidePath(options.directoryChain[0].realPath, beforeRealPath)) {
|
|
24189
24633
|
throw new Error(`Native evidence entry resolves outside its change: ${options.ref}`);
|
|
24190
24634
|
}
|
|
24191
|
-
const openFlags = process.platform === "win32" ? "r" :
|
|
24192
|
-
const handle = await
|
|
24635
|
+
const openFlags = process.platform === "win32" ? "r" : fsConstants5.O_RDONLY | fsConstants5.O_NOFOLLOW | fsConstants5.O_NONBLOCK;
|
|
24636
|
+
const handle = await fs26.open(options.file, openFlags).catch((error) => {
|
|
24193
24637
|
if (error.code === "ELOOP" || error.code === "ENXIO") {
|
|
24194
24638
|
throw new Error(`Native evidence entry became unsafe while opening: ${options.ref}`);
|
|
24195
24639
|
}
|
|
@@ -24198,8 +24642,8 @@ async function readCanonicalDocument(options) {
|
|
|
24198
24642
|
try {
|
|
24199
24643
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
24200
24644
|
handle.stat(),
|
|
24201
|
-
|
|
24202
|
-
|
|
24645
|
+
fs26.lstat(options.file),
|
|
24646
|
+
fs26.realpath(options.file)
|
|
24203
24647
|
]);
|
|
24204
24648
|
await verifyDirectoryChain7(options.directoryChain);
|
|
24205
24649
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameIdentity(fileIdentity2(before), opened) || !sameIdentity(fileIdentity2(opened), pathAfterOpen)) {
|
|
@@ -24220,8 +24664,8 @@ async function readCanonicalDocument(options) {
|
|
|
24220
24664
|
}
|
|
24221
24665
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
24222
24666
|
handle.stat(),
|
|
24223
|
-
|
|
24224
|
-
|
|
24667
|
+
fs26.lstat(options.file),
|
|
24668
|
+
fs26.realpath(options.file)
|
|
24225
24669
|
]);
|
|
24226
24670
|
await verifyDirectoryChain7(options.directoryChain);
|
|
24227
24671
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameIdentity(fileIdentity2(opened), afterHandle) || !sameIdentity(fileIdentity2(opened), afterPath)) {
|
|
@@ -24258,7 +24702,7 @@ async function readCanonicalDocument(options) {
|
|
|
24258
24702
|
}
|
|
24259
24703
|
async function pathIsMissing(file) {
|
|
24260
24704
|
try {
|
|
24261
|
-
await
|
|
24705
|
+
await fs26.lstat(file);
|
|
24262
24706
|
return false;
|
|
24263
24707
|
} catch (error) {
|
|
24264
24708
|
if (error.code === "ENOENT") return true;
|
|
@@ -24272,7 +24716,7 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24272
24716
|
let rootEntries;
|
|
24273
24717
|
try {
|
|
24274
24718
|
const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
|
|
24275
|
-
rootEntries = await
|
|
24719
|
+
rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
|
|
24276
24720
|
await verifyDirectoryChain7(rootChain);
|
|
24277
24721
|
} catch (error) {
|
|
24278
24722
|
if (error.code === "ENOENT") {
|
|
@@ -24285,7 +24729,7 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24285
24729
|
if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
|
|
24286
24730
|
const directory = path39.join(evidenceRoot, kind);
|
|
24287
24731
|
const directoryChain = await captureDirectoryChain6(changeRoot, directory);
|
|
24288
|
-
const entries = (await
|
|
24732
|
+
const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
|
|
24289
24733
|
(left, right) => compareText8(left.name, right.name)
|
|
24290
24734
|
);
|
|
24291
24735
|
const byOriginal = /* @__PURE__ */ new Map();
|
|
@@ -24388,20 +24832,20 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24388
24832
|
`Native evidence cleanup original or quarantine changed during recovery: ${recovery.ref}`
|
|
24389
24833
|
);
|
|
24390
24834
|
}
|
|
24391
|
-
await
|
|
24835
|
+
await fs26.rm(recovery.quarantine);
|
|
24392
24836
|
await verifyDirectoryChain7(recovery.document.directoryChain);
|
|
24393
24837
|
continue;
|
|
24394
24838
|
}
|
|
24395
24839
|
if (!await pathIsMissing(recovery.original)) {
|
|
24396
24840
|
throw new Error(`Native evidence cleanup original appeared during recovery: ${recovery.ref}`);
|
|
24397
24841
|
}
|
|
24398
|
-
const quarantineStat = await
|
|
24842
|
+
const quarantineStat = await fs26.lstat(recovery.quarantine);
|
|
24399
24843
|
if (!quarantineStat.isFile() || quarantineStat.isSymbolicLink() || !sameIdentity(recovery.document.identity, quarantineStat)) {
|
|
24400
24844
|
throw new Error(
|
|
24401
24845
|
`Native evidence cleanup quarantine changed during recovery: ${recovery.ref}`
|
|
24402
24846
|
);
|
|
24403
24847
|
}
|
|
24404
|
-
await
|
|
24848
|
+
await fs26.link(recovery.quarantine, recovery.original);
|
|
24405
24849
|
await options.hooks.afterRecoveryLink?.({
|
|
24406
24850
|
ref: recovery.ref,
|
|
24407
24851
|
original: recovery.original,
|
|
@@ -24428,7 +24872,7 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24428
24872
|
if (!sameContentVersionAfterLinkOrRename(recovery.document.identity, restored.identity) || !sameIdentity(restored.identity, linkedQuarantine.identity)) {
|
|
24429
24873
|
throw new Error(`Native evidence cleanup recovery identity changed: ${recovery.ref}`);
|
|
24430
24874
|
}
|
|
24431
|
-
await
|
|
24875
|
+
await fs26.rm(recovery.quarantine);
|
|
24432
24876
|
await verifyDirectoryChain7(recovery.document.directoryChain);
|
|
24433
24877
|
}
|
|
24434
24878
|
return {
|
|
@@ -24452,7 +24896,7 @@ async function scanEvidenceStore(paths, name) {
|
|
|
24452
24896
|
let rootEntries;
|
|
24453
24897
|
try {
|
|
24454
24898
|
const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
|
|
24455
|
-
rootEntries = await
|
|
24899
|
+
rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
|
|
24456
24900
|
await verifyDirectoryChain7(rootChain);
|
|
24457
24901
|
} catch (error) {
|
|
24458
24902
|
if (error.code === "ENOENT") return [];
|
|
@@ -24472,7 +24916,7 @@ async function scanEvidenceStore(paths, name) {
|
|
|
24472
24916
|
if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
|
|
24473
24917
|
const directory = path39.join(evidenceRoot, kind);
|
|
24474
24918
|
const directoryChain = await captureDirectoryChain6(changeRoot, directory);
|
|
24475
|
-
const entries = (await
|
|
24919
|
+
const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
|
|
24476
24920
|
(left, right) => compareText8(left.name, right.name)
|
|
24477
24921
|
);
|
|
24478
24922
|
await verifyDirectoryChain7(directoryChain);
|
|
@@ -24616,13 +25060,13 @@ function summaryFinding(name, plan, repaired, evidenceRoot) {
|
|
|
24616
25060
|
}
|
|
24617
25061
|
async function restoreQuarantineIfSafe(quarantine, original) {
|
|
24618
25062
|
try {
|
|
24619
|
-
await
|
|
25063
|
+
await fs26.lstat(original);
|
|
24620
25064
|
return;
|
|
24621
25065
|
} catch (error) {
|
|
24622
25066
|
if (error.code !== "ENOENT") return;
|
|
24623
25067
|
}
|
|
24624
25068
|
try {
|
|
24625
|
-
await
|
|
25069
|
+
await fs26.rename(quarantine, original);
|
|
24626
25070
|
} catch {
|
|
24627
25071
|
}
|
|
24628
25072
|
}
|
|
@@ -24638,8 +25082,8 @@ async function deleteCandidate(candidate, hooks) {
|
|
|
24638
25082
|
name: candidate.change,
|
|
24639
25083
|
directoryChain: candidate.directoryChain
|
|
24640
25084
|
});
|
|
24641
|
-
const before = await
|
|
24642
|
-
const beforeRealPath = await
|
|
25085
|
+
const before = await fs26.lstat(candidate.file);
|
|
25086
|
+
const beforeRealPath = await fs26.realpath(candidate.file);
|
|
24643
25087
|
if (!before.isFile() || before.isSymbolicLink() || !sameIdentity(candidate.identity, before) || !sameIdentity(candidate.identity, beforeDocument.identity) || !isInsidePath(candidate.directoryChain[0].realPath, beforeRealPath)) {
|
|
24644
25088
|
throw new Error(`Native evidence candidate changed before cleanup: ${candidate.ref}`);
|
|
24645
25089
|
}
|
|
@@ -24647,18 +25091,18 @@ async function deleteCandidate(candidate, hooks) {
|
|
|
24647
25091
|
path39.dirname(candidate.file),
|
|
24648
25092
|
`.${path39.basename(candidate.file)}.${randomUUID8()}.gc`
|
|
24649
25093
|
);
|
|
24650
|
-
await
|
|
25094
|
+
await fs26.rename(candidate.file, quarantine);
|
|
24651
25095
|
try {
|
|
24652
25096
|
const [moved, movedRealPath] = await Promise.all([
|
|
24653
|
-
|
|
24654
|
-
|
|
25097
|
+
fs26.lstat(quarantine),
|
|
25098
|
+
fs26.realpath(quarantine)
|
|
24655
25099
|
]);
|
|
24656
25100
|
await verifyDirectoryChain7(candidate.directoryChain);
|
|
24657
25101
|
if (!moved.isFile() || moved.isSymbolicLink() || !sameContentVersionAfterLinkOrRename(candidate.identity, moved) || !isInsidePath(candidate.directoryChain[0].realPath, movedRealPath)) {
|
|
24658
25102
|
await restoreQuarantineIfSafe(quarantine, candidate.file);
|
|
24659
25103
|
throw new Error(`Native evidence candidate changed during cleanup: ${candidate.ref}`);
|
|
24660
25104
|
}
|
|
24661
|
-
await
|
|
25105
|
+
await fs26.rm(quarantine);
|
|
24662
25106
|
await verifyDirectoryChain7(candidate.directoryChain);
|
|
24663
25107
|
} catch (error) {
|
|
24664
25108
|
await restoreQuarantineIfSafe(quarantine, candidate.file);
|
|
@@ -24670,7 +25114,7 @@ async function hasPendingRelocationOrArchive(paths) {
|
|
|
24670
25114
|
if (config?.native.pending_root_move) return true;
|
|
24671
25115
|
let entries;
|
|
24672
25116
|
try {
|
|
24673
|
-
entries = await
|
|
25117
|
+
entries = await fs26.readdir(paths.transactionsDir, { withFileTypes: true });
|
|
24674
25118
|
} catch (error) {
|
|
24675
25119
|
if (error.code === "ENOENT") return false;
|
|
24676
25120
|
throw error;
|
|
@@ -24690,7 +25134,7 @@ async function changeNames(paths, requested) {
|
|
|
24690
25134
|
if (requested) return [requested];
|
|
24691
25135
|
let entries;
|
|
24692
25136
|
try {
|
|
24693
|
-
entries = await
|
|
25137
|
+
entries = await fs26.readdir(paths.changesDir, { withFileTypes: true });
|
|
24694
25138
|
} catch (error) {
|
|
24695
25139
|
if (error.code === "ENOENT") return [];
|
|
24696
25140
|
throw error;
|
|
@@ -24820,14 +25264,14 @@ async function inspectNativeEvidenceRetention(options) {
|
|
|
24820
25264
|
|
|
24821
25265
|
// domains/comet-native/native-root-move.ts
|
|
24822
25266
|
import { createHash as createHash14, randomUUID as randomUUID9 } from "crypto";
|
|
24823
|
-
import { promises as
|
|
25267
|
+
import { promises as fs27 } from "fs";
|
|
24824
25268
|
import path40 from "path";
|
|
24825
25269
|
var NATIVE_ROOT_MOVE_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
24826
25270
|
var NATIVE_ROOT_MOVE_MAX_JOURNAL_BYTES = 256 * 1024;
|
|
24827
25271
|
var NATIVE_ROOT_MOVE_MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
|
|
24828
25272
|
async function exists3(target) {
|
|
24829
25273
|
try {
|
|
24830
|
-
await
|
|
25274
|
+
await fs27.access(target);
|
|
24831
25275
|
return true;
|
|
24832
25276
|
} catch (error) {
|
|
24833
25277
|
if (error.code === "ENOENT") return false;
|
|
@@ -24837,7 +25281,7 @@ async function exists3(target) {
|
|
|
24837
25281
|
async function assertNoUnfinishedTransactions(paths) {
|
|
24838
25282
|
let entries;
|
|
24839
25283
|
try {
|
|
24840
|
-
entries = await
|
|
25284
|
+
entries = await fs27.readdir(paths.transactionsDir, { withFileTypes: true });
|
|
24841
25285
|
} catch (error) {
|
|
24842
25286
|
if (error.code === "ENOENT") return;
|
|
24843
25287
|
throw error;
|
|
@@ -24862,7 +25306,7 @@ async function assertNoUnfinishedTransactions(paths) {
|
|
|
24862
25306
|
}
|
|
24863
25307
|
}
|
|
24864
25308
|
async function assertNoOtherLocks(paths, ownedLock) {
|
|
24865
|
-
for (const entry2 of await
|
|
25309
|
+
for (const entry2 of await fs27.readdir(paths.locksDir, { withFileTypes: true })) {
|
|
24866
25310
|
const file = path40.join(paths.locksDir, entry2.name);
|
|
24867
25311
|
if (path40.resolve(file) === path40.resolve(ownedLock)) continue;
|
|
24868
25312
|
if (entry2.isFile() || entry2.isSymbolicLink()) {
|
|
@@ -24873,7 +25317,7 @@ async function assertNoOtherLocks(paths, ownedLock) {
|
|
|
24873
25317
|
async function refreshNativeWorkspaceIdentities(paths) {
|
|
24874
25318
|
let entries;
|
|
24875
25319
|
try {
|
|
24876
|
-
entries = await
|
|
25320
|
+
entries = await fs27.readdir(paths.changesDir, { withFileTypes: true });
|
|
24877
25321
|
} catch (error) {
|
|
24878
25322
|
if (error.code === "ENOENT") return;
|
|
24879
25323
|
throw error;
|
|
@@ -24913,7 +25357,7 @@ async function walkTree(root, options) {
|
|
|
24913
25357
|
await protectedDirectory.verify();
|
|
24914
25358
|
continue;
|
|
24915
25359
|
}
|
|
24916
|
-
const stat = await
|
|
25360
|
+
const stat = await fs27.lstat(target);
|
|
24917
25361
|
if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
|
|
24918
25362
|
if (options.rejectSymlinks) throw new Error(`Native root contains a symlink: ${target}`);
|
|
24919
25363
|
await protectedDirectory.verify();
|
|
@@ -24967,7 +25411,7 @@ async function copyTree(source, target, excludedFile, targetRoot) {
|
|
|
24967
25411
|
await protectedDirectory.verify();
|
|
24968
25412
|
continue;
|
|
24969
25413
|
}
|
|
24970
|
-
const stat = await
|
|
25414
|
+
const stat = await fs27.lstat(sourceEntry);
|
|
24971
25415
|
if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
|
|
24972
25416
|
throw new Error(`Native root contains a symlink: ${sourceEntry}`);
|
|
24973
25417
|
}
|
|
@@ -25414,7 +25858,7 @@ async function finishForwardMove(options) {
|
|
|
25414
25858
|
options.staging,
|
|
25415
25859
|
options.lockFile
|
|
25416
25860
|
);
|
|
25417
|
-
await
|
|
25861
|
+
await fs27.rename(options.staging, options.destinationPaths.nativeRoot);
|
|
25418
25862
|
}
|
|
25419
25863
|
config = await setPendingStage({
|
|
25420
25864
|
projectRoot: options.projectRoot,
|
|
@@ -25460,7 +25904,8 @@ async function finishForwardMove(options) {
|
|
|
25460
25904
|
const stableNative = {
|
|
25461
25905
|
artifact_root: config.native.artifact_root,
|
|
25462
25906
|
language: config.native.language,
|
|
25463
|
-
clarification_mode: config.native.clarification_mode
|
|
25907
|
+
clarification_mode: config.native.clarification_mode,
|
|
25908
|
+
snapshot: config.native.snapshot
|
|
25464
25909
|
};
|
|
25465
25910
|
const committed = {
|
|
25466
25911
|
...config,
|
|
@@ -25603,7 +26048,8 @@ async function recoverNativeRootMove(options) {
|
|
|
25603
26048
|
const stableNative = {
|
|
25604
26049
|
artifact_root: config.native.artifact_root,
|
|
25605
26050
|
language: config.native.language,
|
|
25606
|
-
clarification_mode: config.native.clarification_mode
|
|
26051
|
+
clarification_mode: config.native.clarification_mode,
|
|
26052
|
+
snapshot: config.native.snapshot
|
|
25607
26053
|
};
|
|
25608
26054
|
const restored = {
|
|
25609
26055
|
...config,
|
|
@@ -25734,7 +26180,7 @@ async function inspectSelection(paths, repair) {
|
|
|
25734
26180
|
}
|
|
25735
26181
|
}
|
|
25736
26182
|
if (repair) {
|
|
25737
|
-
await
|
|
26183
|
+
await fs28.rm(file, { force: true });
|
|
25738
26184
|
return [
|
|
25739
26185
|
{
|
|
25740
26186
|
severity: "info",
|
|
@@ -26321,7 +26767,7 @@ async function doctorNativeProject(options) {
|
|
|
26321
26767
|
|
|
26322
26768
|
// domains/comet-native/native-check-receipt.ts
|
|
26323
26769
|
import { createHash as createHash15 } from "node:crypto";
|
|
26324
|
-
import { constants as
|
|
26770
|
+
import { constants as fsConstants6, promises as fs29 } from "node:fs";
|
|
26325
26771
|
import path42 from "node:path";
|
|
26326
26772
|
import { TextDecoder as TextDecoder5 } from "node:util";
|
|
26327
26773
|
var ISSUE_KIND_RANK = {
|
|
@@ -26347,6 +26793,7 @@ function projectionManifest2(projection) {
|
|
|
26347
26793
|
createdAt: "1970-01-01T00:00:00.000Z",
|
|
26348
26794
|
complete: projection.complete,
|
|
26349
26795
|
limits: projection.limits,
|
|
26796
|
+
...projection.policy ? { policy: projection.policy } : {},
|
|
26350
26797
|
entries: projection.entries,
|
|
26351
26798
|
omitted: projection.omitted,
|
|
26352
26799
|
omittedCount: projection.omittedCount,
|
|
@@ -26354,16 +26801,17 @@ function projectionManifest2(projection) {
|
|
|
26354
26801
|
};
|
|
26355
26802
|
}
|
|
26356
26803
|
async function collectBoundFacts(options) {
|
|
26804
|
+
const baseline = projectionManifest2(options.scope.baseline);
|
|
26357
26805
|
const [contract, snapshot2] = await Promise.all([
|
|
26358
26806
|
collectNativeContractFiles({
|
|
26359
26807
|
changeDir: nativeChangeDir(options.paths, options.state.name),
|
|
26360
26808
|
briefRef: options.state.brief,
|
|
26361
26809
|
specChanges: options.state.spec_changes
|
|
26362
26810
|
}),
|
|
26363
|
-
|
|
26811
|
+
createNativeCurrentContentSnapshot(options.paths, baseline, { origin: "explicit" })
|
|
26364
26812
|
]);
|
|
26365
26813
|
const currentBundle = buildNativeImplementationScopeBundle({
|
|
26366
|
-
baseline
|
|
26814
|
+
baseline,
|
|
26367
26815
|
current: snapshot2,
|
|
26368
26816
|
contractHash: options.scope.authority.contractHash,
|
|
26369
26817
|
declaredArtifacts: options.scope.authority.declaredArtifacts,
|
|
@@ -26408,7 +26856,7 @@ function staleReasons(before, after, scope) {
|
|
|
26408
26856
|
}
|
|
26409
26857
|
function sameDirectoryIdentity7(identity, stat) {
|
|
26410
26858
|
const stableMetadata = identity.birthtimeMs === stat.birthtimeMs && identity.ctimeMs === stat.ctimeMs;
|
|
26411
|
-
return stableMetadata &&
|
|
26859
|
+
return stableMetadata && sameFileObject(
|
|
26412
26860
|
{ ...identity, birthtime: identity.birthtimeMs },
|
|
26413
26861
|
{
|
|
26414
26862
|
...stat,
|
|
@@ -26418,7 +26866,7 @@ function sameDirectoryIdentity7(identity, stat) {
|
|
|
26418
26866
|
}
|
|
26419
26867
|
function sameFileIdentity8(left, right) {
|
|
26420
26868
|
const stableMetadata = left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.mtimeMs === right.mtimeMs && left.size === right.size;
|
|
26421
|
-
return stableMetadata &&
|
|
26869
|
+
return stableMetadata && sameFileObject(
|
|
26422
26870
|
{ ...left, birthtime: left.birthtimeMs },
|
|
26423
26871
|
{
|
|
26424
26872
|
...right,
|
|
@@ -26441,7 +26889,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
|
|
|
26441
26889
|
if (segment) cursor = path42.join(cursor, segment);
|
|
26442
26890
|
let stat;
|
|
26443
26891
|
try {
|
|
26444
|
-
stat = await
|
|
26892
|
+
stat = await fs29.lstat(cursor);
|
|
26445
26893
|
} catch (error) {
|
|
26446
26894
|
if (error.code === "ENOENT") {
|
|
26447
26895
|
throw new ScopedFileError("scope-mismatch", "Scoped file parent no longer exists");
|
|
@@ -26451,7 +26899,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
|
|
|
26451
26899
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
26452
26900
|
throw new ScopedFileError("unsafe-file", "Scoped file parent is not a real directory");
|
|
26453
26901
|
}
|
|
26454
|
-
const realPath = await
|
|
26902
|
+
const realPath = await fs29.realpath(cursor);
|
|
26455
26903
|
if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
|
|
26456
26904
|
throw new ScopedFileError("unsafe-file", "Scoped file parent resolves outside the project");
|
|
26457
26905
|
}
|
|
@@ -26470,14 +26918,14 @@ async function verifyProjectDirectoryChain(chain) {
|
|
|
26470
26918
|
for (const identity of chain) {
|
|
26471
26919
|
let stat;
|
|
26472
26920
|
try {
|
|
26473
|
-
stat = await
|
|
26921
|
+
stat = await fs29.lstat(identity.path);
|
|
26474
26922
|
} catch (error) {
|
|
26475
26923
|
if (error.code === "ENOENT") {
|
|
26476
26924
|
throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
|
|
26477
26925
|
}
|
|
26478
26926
|
throw error;
|
|
26479
26927
|
}
|
|
26480
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity7(identity, stat) || await
|
|
26928
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity7(identity, stat) || await fs29.realpath(identity.path) !== identity.realPath) {
|
|
26481
26929
|
throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
|
|
26482
26930
|
}
|
|
26483
26931
|
}
|
|
@@ -26493,7 +26941,7 @@ async function readScopedFile(options) {
|
|
|
26493
26941
|
);
|
|
26494
26942
|
let before;
|
|
26495
26943
|
try {
|
|
26496
|
-
before = await
|
|
26944
|
+
before = await fs29.lstat(lexicalFile);
|
|
26497
26945
|
} catch (error) {
|
|
26498
26946
|
if (error.code === "ENOENT") {
|
|
26499
26947
|
throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
|
|
@@ -26509,12 +26957,12 @@ async function readScopedFile(options) {
|
|
|
26509
26957
|
"Scoped file size no longer matches its projection"
|
|
26510
26958
|
);
|
|
26511
26959
|
}
|
|
26512
|
-
const beforeRealPath = await
|
|
26960
|
+
const beforeRealPath = await fs29.realpath(lexicalFile);
|
|
26513
26961
|
if (!isInsidePath(physicalRoot, beforeRealPath)) {
|
|
26514
26962
|
throw new ScopedFileError("unsafe-file", "Scoped file resolves outside the project root");
|
|
26515
26963
|
}
|
|
26516
|
-
const openFlags = process.platform === "win32" ? "r" :
|
|
26517
|
-
const handle = await
|
|
26964
|
+
const openFlags = process.platform === "win32" ? "r" : fsConstants6.O_RDONLY | fsConstants6.O_NOFOLLOW | fsConstants6.O_NONBLOCK;
|
|
26965
|
+
const handle = await fs29.open(lexicalFile, openFlags).catch((error) => {
|
|
26518
26966
|
if (error.code === "ENOENT") {
|
|
26519
26967
|
throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
|
|
26520
26968
|
}
|
|
@@ -26526,8 +26974,8 @@ async function readScopedFile(options) {
|
|
|
26526
26974
|
try {
|
|
26527
26975
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
26528
26976
|
handle.stat(),
|
|
26529
|
-
|
|
26530
|
-
|
|
26977
|
+
fs29.lstat(lexicalFile),
|
|
26978
|
+
fs29.realpath(lexicalFile)
|
|
26531
26979
|
]);
|
|
26532
26980
|
await verifyProjectDirectoryChain(chain);
|
|
26533
26981
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity8(before, opened) || !sameFileIdentity8(opened, pathAfterOpen)) {
|
|
@@ -26554,8 +27002,8 @@ async function readScopedFile(options) {
|
|
|
26554
27002
|
}
|
|
26555
27003
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
26556
27004
|
handle.stat(),
|
|
26557
|
-
|
|
26558
|
-
|
|
27005
|
+
fs29.lstat(lexicalFile),
|
|
27006
|
+
fs29.realpath(lexicalFile)
|
|
26559
27007
|
]);
|
|
26560
27008
|
await verifyProjectDirectoryChain(chain);
|
|
26561
27009
|
if (total !== options.file.expected.size || digest.digest("hex") !== options.file.expected.hash) {
|
|
@@ -26869,7 +27317,7 @@ async function checkpointNativeChange(options) {
|
|
|
26869
27317
|
}
|
|
26870
27318
|
|
|
26871
27319
|
// domains/comet-native/native-specs.ts
|
|
26872
|
-
import { promises as
|
|
27320
|
+
import { promises as fs30 } from "fs";
|
|
26873
27321
|
import path43 from "path";
|
|
26874
27322
|
var MAX_NATIVE_PROPOSED_SPEC_DIRECTORY_ENTRIES = NATIVE_CONTRACT_FILE_LIMITS.maxSpecs * 4;
|
|
26875
27323
|
async function optionalHash3(file) {
|
|
@@ -26895,7 +27343,7 @@ async function proposedCapabilities(paths, name) {
|
|
|
26895
27343
|
}
|
|
26896
27344
|
const capabilities = [];
|
|
26897
27345
|
let entryCount = 0;
|
|
26898
|
-
const directory = await
|
|
27346
|
+
const directory = await fs30.opendir(specsDir);
|
|
26899
27347
|
try {
|
|
26900
27348
|
for await (const entry2 of directory) {
|
|
26901
27349
|
entryCount += 1;
|
|
@@ -26913,7 +27361,7 @@ async function proposedCapabilities(paths, name) {
|
|
|
26913
27361
|
await resolveContainedNativePath(paths.nativeRoot, source);
|
|
26914
27362
|
let stat;
|
|
26915
27363
|
try {
|
|
26916
|
-
stat = await
|
|
27364
|
+
stat = await fs30.lstat(source);
|
|
26917
27365
|
} catch (error) {
|
|
26918
27366
|
if (error.code === "ENOENT") continue;
|
|
26919
27367
|
throw error;
|
|
@@ -27133,7 +27581,7 @@ async function readNativeProposedSpecs(paths, name) {
|
|
|
27133
27581
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
27134
27582
|
|
|
27135
27583
|
// domains/comet-native/native-guards.ts
|
|
27136
|
-
import { promises as
|
|
27584
|
+
import { promises as fs31 } from "fs";
|
|
27137
27585
|
import path44 from "path";
|
|
27138
27586
|
function validation(findings) {
|
|
27139
27587
|
return { valid: findings.length === 0, findings };
|
|
@@ -27166,7 +27614,7 @@ async function validateBuildArtifacts(paths, evidence) {
|
|
|
27166
27614
|
continue;
|
|
27167
27615
|
}
|
|
27168
27616
|
try {
|
|
27169
|
-
await
|
|
27617
|
+
await fs31.access(target);
|
|
27170
27618
|
} catch {
|
|
27171
27619
|
findings.push({
|
|
27172
27620
|
code: "build-artifact-missing",
|
|
@@ -27217,12 +27665,24 @@ async function inspectNativeGuard(options) {
|
|
|
27217
27665
|
const brief = await validateNativeBrief(changeDir, options.state.brief);
|
|
27218
27666
|
const specs = await validateNativeSpecChanges(options.paths, options.state);
|
|
27219
27667
|
findings.push(...brief.findings, ...specs.findings);
|
|
27668
|
+
if (findings.length === 0 && !options.evidence.confirmed) {
|
|
27669
|
+
findings.push({
|
|
27670
|
+
code: "shape-confirmation-required",
|
|
27671
|
+
message: "Native clarification requires explicit user confirmation of the shared understanding before Build"
|
|
27672
|
+
});
|
|
27673
|
+
}
|
|
27220
27674
|
} else if (options.state.phase === "build") {
|
|
27221
27675
|
findings.push(
|
|
27222
27676
|
...(await validateNativeBrief(changeDir, options.state.brief)).findings,
|
|
27223
27677
|
...(await validateNativeSpecChanges(options.paths, options.state)).findings
|
|
27224
27678
|
);
|
|
27225
27679
|
findings.push(...await validateBuildArtifacts(options.paths, options.evidence));
|
|
27680
|
+
if (findings.length === 0 && options.state.approval !== "confirmed" && !options.evidence.confirmed) {
|
|
27681
|
+
findings.push({
|
|
27682
|
+
code: "approval-confirmation-required",
|
|
27683
|
+
message: "Native approval is implicit; confirm the current shared understanding before leaving Build"
|
|
27684
|
+
});
|
|
27685
|
+
}
|
|
27226
27686
|
} else if (options.state.phase === "verify") {
|
|
27227
27687
|
const report = options.evidence.verificationReport ?? options.state.verification_report;
|
|
27228
27688
|
if (!report) {
|
|
@@ -27250,7 +27710,7 @@ async function inspectNativeGuard(options) {
|
|
|
27250
27710
|
}
|
|
27251
27711
|
|
|
27252
27712
|
// domains/comet-native/native-build-evidence.ts
|
|
27253
|
-
import { promises as
|
|
27713
|
+
import { promises as fs32 } from "node:fs";
|
|
27254
27714
|
import path45 from "node:path";
|
|
27255
27715
|
var NATIVE_BUILD_EVIDENCE_LIMITS = {
|
|
27256
27716
|
maxDeclaredArtifacts: 128,
|
|
@@ -27326,12 +27786,12 @@ async function inspectDeclaredArtifact(paths, baseline, rawRef) {
|
|
|
27326
27786
|
}
|
|
27327
27787
|
await resolveContainedNativePath(paths.projectRoot, target);
|
|
27328
27788
|
try {
|
|
27329
|
-
const stat = await
|
|
27789
|
+
const stat = await fs32.lstat(target);
|
|
27330
27790
|
if (stat.isSymbolicLink()) {
|
|
27331
27791
|
throw new Error(`Native build artifact must not be a symlink or junction: ${artifactRef}`);
|
|
27332
27792
|
}
|
|
27333
|
-
const realTarget = await
|
|
27334
|
-
const realProjectRoot = await
|
|
27793
|
+
const realTarget = await fs32.realpath(target);
|
|
27794
|
+
const realProjectRoot = await fs32.realpath(paths.projectRoot);
|
|
27335
27795
|
if (!isInsidePath(realProjectRoot, realTarget)) {
|
|
27336
27796
|
throw new Error(`Native build artifact resolves outside the project: ${artifactRef}`);
|
|
27337
27797
|
}
|
|
@@ -27401,7 +27861,7 @@ async function inspectNativeBuildEvidence(options) {
|
|
|
27401
27861
|
baseline,
|
|
27402
27862
|
refs: options.artifactRefs
|
|
27403
27863
|
});
|
|
27404
|
-
const current = await
|
|
27864
|
+
const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
|
|
27405
27865
|
origin: "explicit",
|
|
27406
27866
|
now: options.now
|
|
27407
27867
|
});
|
|
@@ -27599,7 +28059,8 @@ async function retreatStaleNativeEvidence(options) {
|
|
|
27599
28059
|
findings,
|
|
27600
28060
|
continuation: nativeContinuation({
|
|
27601
28061
|
state: options.state,
|
|
27602
|
-
archiveReady: previousPhase === "archive"
|
|
28062
|
+
archiveReady: previousPhase === "archive",
|
|
28063
|
+
clarificationMode: options.transition.clarificationMode
|
|
27603
28064
|
})
|
|
27604
28065
|
};
|
|
27605
28066
|
}
|
|
@@ -27654,7 +28115,10 @@ async function retreatStaleNativeEvidence(options) {
|
|
|
27654
28115
|
next: "auto",
|
|
27655
28116
|
nextCommand: null,
|
|
27656
28117
|
findings: [],
|
|
27657
|
-
continuation: nativeContinuation({
|
|
28118
|
+
continuation: nativeContinuation({
|
|
28119
|
+
state: persisted,
|
|
28120
|
+
clarificationMode: options.transition.clarificationMode
|
|
28121
|
+
})
|
|
27658
28122
|
};
|
|
27659
28123
|
}
|
|
27660
28124
|
async function advanceNativeChange(options) {
|
|
@@ -27701,7 +28165,8 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27701
28165
|
continuation: nativeContinuation({
|
|
27702
28166
|
state,
|
|
27703
28167
|
findings: repairFindings2,
|
|
27704
|
-
archiveReady: state.phase === "archive" && state.verification_result === "pass"
|
|
28168
|
+
archiveReady: state.phase === "archive" && state.verification_result === "pass",
|
|
28169
|
+
clarificationMode: options.clarificationMode
|
|
27705
28170
|
}),
|
|
27706
28171
|
...repair ? { repair } : {}
|
|
27707
28172
|
};
|
|
@@ -27740,7 +28205,8 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27740
28205
|
const guard = await inspectNativeGuard({
|
|
27741
28206
|
paths: options.paths,
|
|
27742
28207
|
state: candidate,
|
|
27743
|
-
evidence: options.evidence
|
|
28208
|
+
evidence: options.evidence,
|
|
28209
|
+
clarificationMode: options.clarificationMode
|
|
27744
28210
|
});
|
|
27745
28211
|
if (!guard.valid) {
|
|
27746
28212
|
const findings = structureNativeFindings({
|
|
@@ -27754,7 +28220,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27754
28220
|
next: "manual",
|
|
27755
28221
|
nextCommand: null,
|
|
27756
28222
|
findings,
|
|
27757
|
-
continuation: nativeContinuation({
|
|
28223
|
+
continuation: nativeContinuation({
|
|
28224
|
+
state,
|
|
28225
|
+
findings,
|
|
28226
|
+
clarificationMode: options.clarificationMode
|
|
28227
|
+
})
|
|
27758
28228
|
};
|
|
27759
28229
|
}
|
|
27760
28230
|
const shapeContract = state.phase === "shape" ? await collectNativeContractFiles({
|
|
@@ -27793,7 +28263,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27793
28263
|
next: "manual",
|
|
27794
28264
|
nextCommand: null,
|
|
27795
28265
|
findings,
|
|
27796
|
-
continuation: nativeContinuation({
|
|
28266
|
+
continuation: nativeContinuation({
|
|
28267
|
+
state,
|
|
28268
|
+
findings,
|
|
28269
|
+
clarificationMode: options.clarificationMode
|
|
28270
|
+
})
|
|
27797
28271
|
};
|
|
27798
28272
|
}
|
|
27799
28273
|
const preparedScope = buildEvidence ? {
|
|
@@ -27825,7 +28299,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27825
28299
|
next: "manual",
|
|
27826
28300
|
nextCommand: null,
|
|
27827
28301
|
findings,
|
|
27828
|
-
continuation: nativeContinuation({
|
|
28302
|
+
continuation: nativeContinuation({
|
|
28303
|
+
state,
|
|
28304
|
+
findings,
|
|
28305
|
+
clarificationMode: options.clarificationMode
|
|
28306
|
+
}),
|
|
27829
28307
|
preparedScope
|
|
27830
28308
|
};
|
|
27831
28309
|
}
|
|
@@ -27870,7 +28348,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27870
28348
|
next: "manual",
|
|
27871
28349
|
nextCommand: null,
|
|
27872
28350
|
findings,
|
|
27873
|
-
continuation: nativeContinuation({
|
|
28351
|
+
continuation: nativeContinuation({
|
|
28352
|
+
state,
|
|
28353
|
+
findings,
|
|
28354
|
+
clarificationMode: options.clarificationMode
|
|
28355
|
+
}),
|
|
27874
28356
|
preparedScope: preparedScope ? { ...preparedScope, partialAllowanceRef: null } : preparedScope
|
|
27875
28357
|
};
|
|
27876
28358
|
}
|
|
@@ -27899,7 +28381,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27899
28381
|
next: "manual",
|
|
27900
28382
|
nextCommand: null,
|
|
27901
28383
|
findings,
|
|
27902
|
-
continuation: nativeContinuation({
|
|
28384
|
+
continuation: nativeContinuation({
|
|
28385
|
+
state,
|
|
28386
|
+
findings,
|
|
28387
|
+
clarificationMode: options.clarificationMode
|
|
28388
|
+
})
|
|
27903
28389
|
};
|
|
27904
28390
|
}
|
|
27905
28391
|
let repairDecision = null;
|
|
@@ -27954,7 +28440,7 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27954
28440
|
...candidate,
|
|
27955
28441
|
revision: state.revision + 1,
|
|
27956
28442
|
phase: advanced.currentStep,
|
|
27957
|
-
approval: options.evidence.confirmed ? "confirmed" : state.
|
|
28443
|
+
approval: options.evidence.confirmed ? "confirmed" : state.approval,
|
|
27958
28444
|
approved_contract_hash: state.phase === "shape" ? shapeContract.contract.contractHash : state.phase === "build" && options.evidence.confirmed ? buildEvidence.contract.contract.contractHash : state.approved_contract_hash ?? null,
|
|
27959
28445
|
run_id: run.runId,
|
|
27960
28446
|
...state.phase === "build" ? {
|
|
@@ -28031,7 +28517,8 @@ async function advanceNativeChangeLocked(options) {
|
|
|
28031
28517
|
continuation: nativeContinuation({
|
|
28032
28518
|
state: persisted,
|
|
28033
28519
|
findings: repairFindings,
|
|
28034
|
-
archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass"
|
|
28520
|
+
archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass",
|
|
28521
|
+
clarificationMode: options.clarificationMode
|
|
28035
28522
|
}),
|
|
28036
28523
|
...preparedScope ? { preparedScope } : {},
|
|
28037
28524
|
...repairDecision ? { repair: repairDecision } : {}
|
|
@@ -28039,7 +28526,7 @@ async function advanceNativeChangeLocked(options) {
|
|
|
28039
28526
|
}
|
|
28040
28527
|
|
|
28041
28528
|
// domains/comet-native/native-hook-guard.ts
|
|
28042
|
-
import { promises as
|
|
28529
|
+
import { promises as fs33 } from "fs";
|
|
28043
28530
|
import path46 from "path";
|
|
28044
28531
|
|
|
28045
28532
|
// domains/comet-entry/hook-adapter.ts
|
|
@@ -28204,7 +28691,7 @@ async function activeNativeContext(projectRoot) {
|
|
|
28204
28691
|
const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root);
|
|
28205
28692
|
let entries;
|
|
28206
28693
|
try {
|
|
28207
|
-
entries = await
|
|
28694
|
+
entries = await fs33.readdir(paths.changesDir, { withFileTypes: true });
|
|
28208
28695
|
} catch (error) {
|
|
28209
28696
|
if (error.code === "ENOENT") return { paths, changes: [] };
|
|
28210
28697
|
throw error;
|
|
@@ -28325,6 +28812,7 @@ Commands:
|
|
|
28325
28812
|
select <change-name>
|
|
28326
28813
|
checkpoint <change-name> --summary <text> --next-action <text> [--artifact <project-relative>] [--expect-revision <n>]
|
|
28327
28814
|
check <change-name>
|
|
28815
|
+
evidence format [--entries <path>]
|
|
28328
28816
|
next <change-name> --summary <text> [--confirmed] [--artifact <path>] [--no-code-reason <text>] [--allow-partial-scope <sha256> --partial-reason <text>] [--result pass|fail] [--report <path>] [--receipt <change-relative-ref>] [--failure-category <token>] [--failed-check <token>] [--override-repair <sha256> --override-summary <text>]
|
|
28329
28817
|
archive <change-name> --dry-run
|
|
28330
28818
|
archive <change-name> --expect-preflight <sha256>
|
|
@@ -28405,6 +28893,44 @@ async function doctorPaths(projectRoot) {
|
|
|
28405
28893
|
function success(command, data, text) {
|
|
28406
28894
|
return { command, exitCode: 0, data, text: text ?? JSON.stringify(data, null, 2) + "\n" };
|
|
28407
28895
|
}
|
|
28896
|
+
async function readBoundedEvidenceFile(filePath, maxBytes) {
|
|
28897
|
+
try {
|
|
28898
|
+
const { bytes } = await readFileRaceSafe(filePath, maxBytes, {
|
|
28899
|
+
label: "Acceptance evidence entries file"
|
|
28900
|
+
});
|
|
28901
|
+
return bytes.toString("utf8");
|
|
28902
|
+
} catch (error) {
|
|
28903
|
+
if (error instanceof RaceSafeReadError) {
|
|
28904
|
+
if (error.reason === "not-regular-file") {
|
|
28905
|
+
throw new Error(`Acceptance evidence entries path is not a regular file: ${filePath}`, {
|
|
28906
|
+
cause: error
|
|
28907
|
+
});
|
|
28908
|
+
}
|
|
28909
|
+
if (error.reason === "too-large") {
|
|
28910
|
+
throw new Error(`Acceptance evidence entries file exceeds ${maxBytes} bytes: ${filePath}`, {
|
|
28911
|
+
cause: error
|
|
28912
|
+
});
|
|
28913
|
+
}
|
|
28914
|
+
throw new Error(`Acceptance evidence entries file changed while reading: ${filePath}`, {
|
|
28915
|
+
cause: error
|
|
28916
|
+
});
|
|
28917
|
+
}
|
|
28918
|
+
throw error;
|
|
28919
|
+
}
|
|
28920
|
+
}
|
|
28921
|
+
async function readBoundedEvidenceStdin(maxBytes) {
|
|
28922
|
+
const chunks = [];
|
|
28923
|
+
let total = 0;
|
|
28924
|
+
for await (const chunk of process.stdin) {
|
|
28925
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
28926
|
+
total += buffer.byteLength;
|
|
28927
|
+
if (total > maxBytes) {
|
|
28928
|
+
throw new Error(`Acceptance evidence entries on stdin exceed ${maxBytes} bytes`);
|
|
28929
|
+
}
|
|
28930
|
+
chunks.push(buffer);
|
|
28931
|
+
}
|
|
28932
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
28933
|
+
}
|
|
28408
28934
|
async function dispatch(rawArgs, explicitProjectRoot) {
|
|
28409
28935
|
if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "help") {
|
|
28410
28936
|
return { command: rawArgs[0] ?? null, exitCode: 0, data: { usage: USAGE }, text: USAGE };
|
|
@@ -28510,7 +29036,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28510
29036
|
await ensureNativeDirectories(paths);
|
|
28511
29037
|
const state = await createNativeChange({ paths, name, language });
|
|
28512
29038
|
await selectNativeChange(paths, state.name);
|
|
28513
|
-
const status = await inspectNativeStatus(paths, state.name
|
|
29039
|
+
const status = await inspectNativeStatus(paths, state.name, {
|
|
29040
|
+
clarificationMode: config.native.clarification_mode
|
|
29041
|
+
});
|
|
28514
29042
|
return success(
|
|
28515
29043
|
"new",
|
|
28516
29044
|
{ ...state, continuation: status.continuation },
|
|
@@ -28524,9 +29052,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28524
29052
|
const name = requiredPositional(rawArgs, "change name");
|
|
28525
29053
|
const capability = requiredPositional(rawArgs, "capability");
|
|
28526
29054
|
assertNoArguments(rawArgs);
|
|
28527
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29055
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28528
29056
|
const state = await markNativeSpecRemoval(paths, name, capability);
|
|
28529
|
-
const status = await inspectNativeStatus(paths, state.name
|
|
29057
|
+
const status = await inspectNativeStatus(paths, state.name, {
|
|
29058
|
+
clarificationMode: config.native.clarification_mode
|
|
29059
|
+
});
|
|
28530
29060
|
return success(
|
|
28531
29061
|
"spec remove",
|
|
28532
29062
|
{ ...state, continuation: status.continuation },
|
|
@@ -28539,9 +29069,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28539
29069
|
const summary = takeOption(rawArgs, "--summary");
|
|
28540
29070
|
if (!summary) throw new NativeUsageError("--summary is required");
|
|
28541
29071
|
assertNoArguments(rawArgs);
|
|
28542
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29072
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28543
29073
|
const state = await rebaseNativeSpecChanges({ paths, name, summary });
|
|
28544
|
-
const status = await inspectNativeStatus(paths, state.name
|
|
29074
|
+
const status = await inspectNativeStatus(paths, state.name, {
|
|
29075
|
+
clarificationMode: config.native.clarification_mode
|
|
29076
|
+
});
|
|
28545
29077
|
return success(
|
|
28546
29078
|
"spec rebase",
|
|
28547
29079
|
{ ...state, continuation: status.continuation },
|
|
@@ -28554,8 +29086,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28554
29086
|
if (command === "list") {
|
|
28555
29087
|
const cursor = takeOption(rawArgs, "--cursor");
|
|
28556
29088
|
assertNoArguments(rawArgs);
|
|
28557
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
28558
|
-
const page = await listNativeStatusPage(paths,
|
|
29089
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
29090
|
+
const page = await listNativeStatusPage(paths, {
|
|
29091
|
+
...cursor ? { cursor } : {},
|
|
29092
|
+
clarificationMode: config.native.clarification_mode
|
|
29093
|
+
});
|
|
28559
29094
|
return success("list", page);
|
|
28560
29095
|
}
|
|
28561
29096
|
if (command === "show") {
|
|
@@ -28611,19 +29146,25 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28611
29146
|
throw new NativeUsageError("--acceptance-cursor requires a change name");
|
|
28612
29147
|
}
|
|
28613
29148
|
assertNoArguments(rawArgs);
|
|
28614
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29149
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28615
29150
|
const data = name ? await inspectNativeStatus(paths, name, {
|
|
28616
29151
|
details,
|
|
28617
|
-
...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {}
|
|
28618
|
-
|
|
29152
|
+
...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {},
|
|
29153
|
+
clarificationMode: config.native.clarification_mode
|
|
29154
|
+
}) : await listNativeStatusPage(paths, {
|
|
29155
|
+
...cursor ? { cursor } : {},
|
|
29156
|
+
clarificationMode: config.native.clarification_mode
|
|
29157
|
+
});
|
|
28619
29158
|
return success("status", data);
|
|
28620
29159
|
}
|
|
28621
29160
|
if (command === "select") {
|
|
28622
29161
|
const name = requiredPositional(rawArgs, "change name");
|
|
28623
29162
|
assertNoArguments(rawArgs);
|
|
28624
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29163
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28625
29164
|
await selectNativeChange(paths, name);
|
|
28626
|
-
const status = await inspectNativeStatus(paths, name
|
|
29165
|
+
const status = await inspectNativeStatus(paths, name, {
|
|
29166
|
+
clarificationMode: config.native.clarification_mode
|
|
29167
|
+
});
|
|
28627
29168
|
return success(
|
|
28628
29169
|
"select",
|
|
28629
29170
|
{ selected: name, continuation: status.continuation },
|
|
@@ -28640,7 +29181,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28640
29181
|
const artifacts = takeMany(rawArgs, "--artifact");
|
|
28641
29182
|
const expectedRevision = revisionOption(rawArgs);
|
|
28642
29183
|
assertNoArguments(rawArgs);
|
|
28643
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29184
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28644
29185
|
const result2 = await checkpointNativeChange({
|
|
28645
29186
|
paths,
|
|
28646
29187
|
name,
|
|
@@ -28649,7 +29190,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28649
29190
|
artifacts,
|
|
28650
29191
|
expectedRevision
|
|
28651
29192
|
});
|
|
28652
|
-
const status = await inspectNativeStatus(paths, name
|
|
29193
|
+
const status = await inspectNativeStatus(paths, name, {
|
|
29194
|
+
clarificationMode: config.native.clarification_mode
|
|
29195
|
+
});
|
|
28653
29196
|
const manifestRef = path47.relative(
|
|
28654
29197
|
paths.projectRoot,
|
|
28655
29198
|
path47.join(nativeChangeDir(paths, name), ...result2.checkpoint.manifestRef.split("/"))
|
|
@@ -28688,6 +29231,43 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28688
29231
|
`
|
|
28689
29232
|
};
|
|
28690
29233
|
}
|
|
29234
|
+
if (command === "evidence") {
|
|
29235
|
+
const subcommand = requiredPositional(rawArgs, "evidence subcommand");
|
|
29236
|
+
if (subcommand === "format") {
|
|
29237
|
+
const entriesPath = takeOption(rawArgs, "--entries");
|
|
29238
|
+
assertNoArguments(rawArgs);
|
|
29239
|
+
let raw;
|
|
29240
|
+
if (entriesPath) {
|
|
29241
|
+
raw = await readBoundedEvidenceFile(
|
|
29242
|
+
path47.resolve(entriesPath),
|
|
29243
|
+
MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES
|
|
29244
|
+
);
|
|
29245
|
+
} else {
|
|
29246
|
+
if (process.stdin.isTTY) {
|
|
29247
|
+
throw new NativeUsageError(
|
|
29248
|
+
"evidence format requires acceptance evidence entries JSON on stdin, or --entries <path>"
|
|
29249
|
+
);
|
|
29250
|
+
}
|
|
29251
|
+
raw = await readBoundedEvidenceStdin(MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES);
|
|
29252
|
+
}
|
|
29253
|
+
let entries;
|
|
29254
|
+
try {
|
|
29255
|
+
entries = JSON.parse(raw);
|
|
29256
|
+
} catch (error) {
|
|
29257
|
+
throw new Error(
|
|
29258
|
+
`Acceptance evidence entries must be valid JSON: ${error.message}`,
|
|
29259
|
+
{ cause: error }
|
|
29260
|
+
);
|
|
29261
|
+
}
|
|
29262
|
+
if (!Array.isArray(entries)) {
|
|
29263
|
+
throw new Error("Acceptance evidence entries must be a JSON array");
|
|
29264
|
+
}
|
|
29265
|
+
const block = serializeNativeVerificationMachineBlock(entries);
|
|
29266
|
+
return success("evidence format", { block }, `${block}
|
|
29267
|
+
`);
|
|
29268
|
+
}
|
|
29269
|
+
throw new NativeUsageError(`Unknown evidence command: ${subcommand}`);
|
|
29270
|
+
}
|
|
28691
29271
|
if (command === "next") {
|
|
28692
29272
|
const name = requiredPositional(rawArgs, "change name");
|
|
28693
29273
|
const summary = takeOption(rawArgs, "--summary");
|
|
@@ -28736,7 +29316,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28736
29316
|
throw new NativeUsageError("--override-repair cannot be combined with --result");
|
|
28737
29317
|
}
|
|
28738
29318
|
assertNoArguments(rawArgs);
|
|
28739
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29319
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28740
29320
|
const evidence = {
|
|
28741
29321
|
summary,
|
|
28742
29322
|
...confirmed ? { confirmed: true } : {},
|
|
@@ -28752,7 +29332,12 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28752
29332
|
...repairOverrideSignature ? { repairOverrideSignature } : {},
|
|
28753
29333
|
...repairOverrideSummary ? { repairOverrideSummary } : {}
|
|
28754
29334
|
};
|
|
28755
|
-
const result2 = await advanceNativeChange({
|
|
29335
|
+
const result2 = await advanceNativeChange({
|
|
29336
|
+
paths,
|
|
29337
|
+
name,
|
|
29338
|
+
evidence,
|
|
29339
|
+
clarificationMode: config.native.clarification_mode
|
|
29340
|
+
});
|
|
28756
29341
|
if (result2.next === "manual") {
|
|
28757
29342
|
const repairBlocked = result2.repair?.disposition === "manual-stop" || result2.repair?.disposition === "hard-stop" || result2.findings.some(
|
|
28758
29343
|
(finding) => [
|
|
@@ -28771,7 +29356,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28771
29356
|
}
|
|
28772
29357
|
};
|
|
28773
29358
|
}
|
|
28774
|
-
const status = await inspectNativeStatus(paths, name
|
|
29359
|
+
const status = await inspectNativeStatus(paths, name, {
|
|
29360
|
+
clarificationMode: config.native.clarification_mode
|
|
29361
|
+
});
|
|
28775
29362
|
return success("next", { ...result2, continuation: status.continuation });
|
|
28776
29363
|
}
|
|
28777
29364
|
if (command === "archive") {
|
|
@@ -28888,6 +29475,13 @@ function errorResult(command, error) {
|
|
|
28888
29475
|
omittedByReason: error.omittedByReason,
|
|
28889
29476
|
samplePaths: error.samplePaths,
|
|
28890
29477
|
sampleTruncated: error.sampleTruncated,
|
|
29478
|
+
effectiveLimits: error.effectiveLimits,
|
|
29479
|
+
policyHash: error.policyHash,
|
|
29480
|
+
configPath: ".comet/config.yaml",
|
|
29481
|
+
supportedFixes: [
|
|
29482
|
+
"increase native.snapshot.max_total_bytes or native.snapshot.max_duration_ms",
|
|
29483
|
+
"add an explicit native.snapshot.exclude pattern for data outside implementation scope"
|
|
29484
|
+
],
|
|
28891
29485
|
requiredAction: "resolve-native-baseline"
|
|
28892
29486
|
},
|
|
28893
29487
|
error: { code: "baseline-incomplete", message: error.message }
|