@rpamis/comet 0.4.0-beta.7 → 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/LICENSE +21 -21
- package/README.md +3 -3
- 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 +118 -6
- package/assets/skills/comet/scripts/comet-hook-router.mjs +244 -26
- package/assets/skills/comet/scripts/comet-runtime.mjs +221 -104
- 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 +1136 -471
- 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/bundle/bundle-platform.d.ts.map +1 -1
- package/dist/domains/bundle/bundle-platform.js +1 -1
- package/dist/domains/bundle/bundle-platform.js.map +1 -1
- package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-guard.js +3 -2
- package/dist/domains/comet-classic/classic-guard.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-archive-transaction.d.ts.map +1 -1
- package/dist/domains/comet-native/native-archive-transaction.js +5 -6
- package/dist/domains/comet-native/native-archive-transaction.js.map +1 -1
- package/dist/domains/comet-native/native-atomic-file.d.ts.map +1 -1
- package/dist/domains/comet-native/native-atomic-file.js +11 -7
- package/dist/domains/comet-native/native-atomic-file.js.map +1 -1
- package/dist/domains/comet-native/native-bounded-file.d.ts.map +1 -1
- package/dist/domains/comet-native/native-bounded-file.js +11 -7
- package/dist/domains/comet-native/native-bounded-file.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-storage.d.ts.map +1 -1
- package/dist/domains/comet-native/native-check-receipt-storage.js +11 -7
- package/dist/domains/comet-native/native-check-receipt-storage.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 +16 -11
- 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-evidence-retention.d.ts.map +1 -1
- package/dist/domains/comet-native/native-evidence-retention.js +5 -3
- package/dist/domains/comet-native/native-evidence-retention.js.map +1 -1
- package/dist/domains/comet-native/native-evidence-storage.d.ts.map +1 -1
- package/dist/domains/comet-native/native-evidence-storage.js +11 -7
- package/dist/domains/comet-native/native-evidence-storage.js.map +1 -1
- package/dist/domains/comet-native/native-file-identity.d.ts +3 -0
- package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -0
- package/dist/domains/comet-native/native-file-identity.js +2 -0
- package/dist/domains/comet-native/native-file-identity.js.map +1 -0
- 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 +30 -35
- package/dist/domains/comet-native/native-lock.js.map +1 -1
- package/dist/domains/comet-native/native-protected-file.d.ts.map +1 -1
- package/dist/domains/comet-native/native-protected-file.js +9 -8
- package/dist/domains/comet-native/native-protected-file.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-run-store.d.ts.map +1 -1
- package/dist/domains/comet-native/native-run-store.js +9 -8
- package/dist/domains/comet-native/native-run-store.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 +225 -5
- 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/dashboard/web/index.html +14 -14
- 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/pyproject.toml +4 -4
- 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 +8 -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
|
|
|
@@ -7616,25 +7734,30 @@ function positiveLimit(value) {
|
|
|
7616
7734
|
return value;
|
|
7617
7735
|
}
|
|
7618
7736
|
function sameDirectoryIdentity(identity, stat) {
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7737
|
+
return sameFileObject(
|
|
7738
|
+
{ ...identity, birthtime: identity.birthtimeMs },
|
|
7739
|
+
{
|
|
7740
|
+
...stat,
|
|
7741
|
+
birthtime: stat.birthtimeMs
|
|
7742
|
+
}
|
|
7743
|
+
);
|
|
7623
7744
|
}
|
|
7624
7745
|
function sameFileIdentity(left, right) {
|
|
7625
|
-
|
|
7626
|
-
|
|
7746
|
+
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
7747
|
+
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
7748
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
7749
|
+
return sameFileObject(leftObject, rightObject);
|
|
7627
7750
|
}
|
|
7628
|
-
return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
7751
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
7629
7752
|
}
|
|
7630
7753
|
async function directoryIdentity(directory) {
|
|
7631
|
-
const stat = await
|
|
7754
|
+
const stat = await fs2.lstat(directory);
|
|
7632
7755
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
7633
7756
|
throw new Error(`Native artifact parent must be a real directory: ${directory}`);
|
|
7634
7757
|
}
|
|
7635
7758
|
return {
|
|
7636
7759
|
path: directory,
|
|
7637
|
-
realPath: await
|
|
7760
|
+
realPath: await fs2.realpath(directory),
|
|
7638
7761
|
dev: stat.dev,
|
|
7639
7762
|
ino: stat.ino,
|
|
7640
7763
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -7660,8 +7783,8 @@ async function captureDirectoryChain(root, directory) {
|
|
|
7660
7783
|
}
|
|
7661
7784
|
async function verifyDirectoryChain(chain) {
|
|
7662
7785
|
for (const identity of chain) {
|
|
7663
|
-
const stat = await
|
|
7664
|
-
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) {
|
|
7665
7788
|
throw new Error(`Native artifact parent changed while reading: ${identity.path}`);
|
|
7666
7789
|
}
|
|
7667
7790
|
}
|
|
@@ -7672,21 +7795,21 @@ async function readNativeBoundedTextFile(options) {
|
|
|
7672
7795
|
const file = path2.resolve(options.root, ...ref.split("/"));
|
|
7673
7796
|
const chain = await captureDirectoryChain(options.root, path2.dirname(file));
|
|
7674
7797
|
await options.hooks?.afterParentChainCaptured?.();
|
|
7675
|
-
const before = await
|
|
7798
|
+
const before = await fs2.lstat(file);
|
|
7676
7799
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7677
7800
|
throw new Error(`Native artifact must be a regular file: ${ref}`);
|
|
7678
7801
|
}
|
|
7679
7802
|
if (before.size > maxBytes) throw new Error(`Native artifact exceeds ${maxBytes} bytes: ${ref}`);
|
|
7680
|
-
const realPath = await
|
|
7803
|
+
const realPath = await fs2.realpath(file);
|
|
7681
7804
|
if (!isInside(chain[0].realPath, realPath)) {
|
|
7682
7805
|
throw new Error(`Native artifact resolves outside its root: ${ref}`);
|
|
7683
7806
|
}
|
|
7684
|
-
const handle = await
|
|
7807
|
+
const handle = await fs2.open(file, "r");
|
|
7685
7808
|
try {
|
|
7686
7809
|
const [opened, afterOpenPath, afterOpenRealPath] = await Promise.all([
|
|
7687
7810
|
handle.stat(),
|
|
7688
|
-
|
|
7689
|
-
|
|
7811
|
+
fs2.lstat(file),
|
|
7812
|
+
fs2.realpath(file)
|
|
7690
7813
|
]);
|
|
7691
7814
|
await verifyDirectoryChain(chain);
|
|
7692
7815
|
if (!opened.isFile() || !afterOpenPath.isFile() || afterOpenPath.isSymbolicLink() || afterOpenRealPath !== realPath || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, afterOpenPath)) {
|
|
@@ -7707,8 +7830,8 @@ async function readNativeBoundedTextFile(options) {
|
|
|
7707
7830
|
await options.hooks?.beforeFinalCheck?.();
|
|
7708
7831
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
7709
7832
|
handle.stat(),
|
|
7710
|
-
|
|
7711
|
-
|
|
7833
|
+
fs2.lstat(file),
|
|
7834
|
+
fs2.realpath(file)
|
|
7712
7835
|
]);
|
|
7713
7836
|
await verifyDirectoryChain(chain);
|
|
7714
7837
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity(opened, afterHandle) || !sameFileIdentity(opened, afterPath)) {
|
|
@@ -7734,32 +7857,37 @@ async function readNativeBoundedTextFile(options) {
|
|
|
7734
7857
|
|
|
7735
7858
|
// domains/comet-native/native-atomic-file.ts
|
|
7736
7859
|
import { randomUUID } from "crypto";
|
|
7737
|
-
import { promises as
|
|
7860
|
+
import { promises as fs3 } from "fs";
|
|
7738
7861
|
import path3 from "path";
|
|
7739
7862
|
function isInside2(parent, target) {
|
|
7740
7863
|
const relative = path3.relative(parent, target);
|
|
7741
7864
|
return relative === "" || !path3.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path3.sep}`);
|
|
7742
7865
|
}
|
|
7743
7866
|
function sameDirectoryIdentity2(identity, stat) {
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7867
|
+
return sameFileObject(
|
|
7868
|
+
{ ...identity, birthtime: identity.birthtimeMs },
|
|
7869
|
+
{
|
|
7870
|
+
...stat,
|
|
7871
|
+
birthtime: stat.birthtimeMs
|
|
7872
|
+
}
|
|
7873
|
+
);
|
|
7748
7874
|
}
|
|
7749
7875
|
function sameFileIdentity2(left, right) {
|
|
7750
|
-
|
|
7751
|
-
|
|
7876
|
+
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
7877
|
+
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
7878
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
7879
|
+
return sameFileObject(leftObject, rightObject);
|
|
7752
7880
|
}
|
|
7753
|
-
return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
7881
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
7754
7882
|
}
|
|
7755
7883
|
async function captureDirectoryIdentity(directory) {
|
|
7756
|
-
const stat = await
|
|
7884
|
+
const stat = await fs3.lstat(directory);
|
|
7757
7885
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
7758
7886
|
throw new Error(`Native atomic write parent must be a real directory: ${directory}`);
|
|
7759
7887
|
}
|
|
7760
7888
|
return {
|
|
7761
7889
|
path: directory,
|
|
7762
|
-
realPath: await
|
|
7890
|
+
realPath: await fs3.realpath(directory),
|
|
7763
7891
|
dev: stat.dev,
|
|
7764
7892
|
ino: stat.ino,
|
|
7765
7893
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -7767,8 +7895,8 @@ async function captureDirectoryIdentity(directory) {
|
|
|
7767
7895
|
}
|
|
7768
7896
|
async function verifyDirectoryChain2(chain) {
|
|
7769
7897
|
for (const identity of chain) {
|
|
7770
|
-
const stat = await
|
|
7771
|
-
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) {
|
|
7772
7900
|
throw new Error(`Native atomic write parent changed before commit: ${identity.path}`);
|
|
7773
7901
|
}
|
|
7774
7902
|
}
|
|
@@ -7786,7 +7914,7 @@ async function prepareContainedDirectoryChain(root, directory) {
|
|
|
7786
7914
|
await verifyDirectoryChain2(chain);
|
|
7787
7915
|
cursor = path3.join(cursor, segment);
|
|
7788
7916
|
try {
|
|
7789
|
-
await
|
|
7917
|
+
await fs3.mkdir(cursor);
|
|
7790
7918
|
} catch (error) {
|
|
7791
7919
|
if (error.code !== "EEXIST") throw error;
|
|
7792
7920
|
}
|
|
@@ -7802,7 +7930,7 @@ async function prepareContainedDirectoryChain(root, directory) {
|
|
|
7802
7930
|
async function syncDirectory(directory) {
|
|
7803
7931
|
let handle;
|
|
7804
7932
|
try {
|
|
7805
|
-
handle = await
|
|
7933
|
+
handle = await fs3.open(directory, "r");
|
|
7806
7934
|
await handle.sync();
|
|
7807
7935
|
} catch (error) {
|
|
7808
7936
|
const code = error.code;
|
|
@@ -7816,18 +7944,18 @@ async function syncDirectory(directory) {
|
|
|
7816
7944
|
async function atomicWrite(file, content, options = {}) {
|
|
7817
7945
|
const directory = path3.dirname(file);
|
|
7818
7946
|
const directoryChain = options.containedRoot ? await prepareContainedDirectoryChain(options.containedRoot, directory) : null;
|
|
7819
|
-
if (!directoryChain) await
|
|
7947
|
+
if (!directoryChain) await fs3.mkdir(directory, { recursive: true });
|
|
7820
7948
|
const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
|
|
7821
7949
|
let handle;
|
|
7822
7950
|
let temporaryIdentity;
|
|
7823
7951
|
try {
|
|
7824
7952
|
await options.beforeTemporaryOpen?.();
|
|
7825
|
-
handle = await
|
|
7953
|
+
handle = await fs3.open(temporary, "wx");
|
|
7826
7954
|
temporaryIdentity = await handle.stat();
|
|
7827
7955
|
if (directoryChain) {
|
|
7828
7956
|
const [temporaryPathStat, temporaryRealPath] = await Promise.all([
|
|
7829
|
-
|
|
7830
|
-
|
|
7957
|
+
fs3.lstat(temporary),
|
|
7958
|
+
fs3.realpath(temporary)
|
|
7831
7959
|
]);
|
|
7832
7960
|
await verifyDirectoryChain2(directoryChain);
|
|
7833
7961
|
if (!temporaryPathStat.isFile() || temporaryPathStat.isSymbolicLink() || !sameFileIdentity2(temporaryIdentity, temporaryPathStat) || !isInside2(directoryChain[0].realPath, temporaryRealPath)) {
|
|
@@ -7845,26 +7973,26 @@ async function atomicWrite(file, content, options = {}) {
|
|
|
7845
7973
|
await options.beforeCommit?.();
|
|
7846
7974
|
if (directoryChain) {
|
|
7847
7975
|
await verifyDirectoryChain2(directoryChain);
|
|
7848
|
-
const temporaryStat = await
|
|
7976
|
+
const temporaryStat = await fs3.lstat(temporary);
|
|
7849
7977
|
if (!temporaryStat.isFile() || temporaryStat.isSymbolicLink() || !temporaryIdentity || !sameFileIdentity2(temporaryStat, temporaryIdentity)) {
|
|
7850
7978
|
throw new Error("Native atomic write temporary file changed before commit");
|
|
7851
7979
|
}
|
|
7852
7980
|
}
|
|
7853
7981
|
if (options.exclusive) {
|
|
7854
|
-
await
|
|
7855
|
-
await
|
|
7982
|
+
await fs3.link(temporary, file);
|
|
7983
|
+
await fs3.unlink(temporary);
|
|
7856
7984
|
} else {
|
|
7857
|
-
await
|
|
7985
|
+
await fs3.rename(temporary, file);
|
|
7858
7986
|
}
|
|
7859
7987
|
await syncDirectory(directory);
|
|
7860
7988
|
} catch (error) {
|
|
7861
7989
|
await handle?.close();
|
|
7862
7990
|
if (!directoryChain) {
|
|
7863
|
-
await
|
|
7991
|
+
await fs3.rm(temporary, { force: true });
|
|
7864
7992
|
} else {
|
|
7865
7993
|
try {
|
|
7866
7994
|
await verifyDirectoryChain2(directoryChain);
|
|
7867
|
-
await
|
|
7995
|
+
await fs3.rm(temporary, { force: true });
|
|
7868
7996
|
} catch {
|
|
7869
7997
|
}
|
|
7870
7998
|
}
|
|
@@ -7883,7 +8011,7 @@ async function atomicWriteJson(file, value, options = {}) {
|
|
|
7883
8011
|
|
|
7884
8012
|
// domains/comet-native/native-config.ts
|
|
7885
8013
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
7886
|
-
import { promises as
|
|
8014
|
+
import { promises as fs6 } from "fs";
|
|
7887
8015
|
import path6 from "path";
|
|
7888
8016
|
|
|
7889
8017
|
// domains/workflow-contract/project-config.ts
|
|
@@ -7898,6 +8026,12 @@ var COMMENTS = {
|
|
|
7898
8026
|
"native.artifact_root": "# Root directory where Native stores Comet specs, changes, and runtime data.",
|
|
7899
8027
|
"native.language": "# Artifact language used by Native workflow documents.\n# language: en | zh-CN",
|
|
7900
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.",
|
|
7901
8035
|
classic: "# Classic workflow settings. They do not change Native state or behavior.",
|
|
7902
8036
|
"classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN",
|
|
7903
8037
|
"classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta",
|
|
@@ -7913,6 +8047,12 @@ var COMMENTS = {
|
|
|
7913
8047
|
"native.artifact_root": "# Native 产物的存放根目录,包括规格、change 和运行时数据。",
|
|
7914
8048
|
"native.language": "# Native 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
|
|
7915
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": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。",
|
|
7916
8056
|
classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。",
|
|
7917
8057
|
"classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
|
|
7918
8058
|
"classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta",
|
|
@@ -7923,7 +8063,7 @@ var COMMENTS = {
|
|
|
7923
8063
|
function projectConfigComment(key, language) {
|
|
7924
8064
|
return COMMENTS[language][key];
|
|
7925
8065
|
}
|
|
7926
|
-
function commentKey(line, block) {
|
|
8066
|
+
function commentKey(line, block, nativeNested) {
|
|
7927
8067
|
const match = /^(\s*)([a-z_]+):/u.exec(line);
|
|
7928
8068
|
if (!match) return null;
|
|
7929
8069
|
const indent = match[1].length;
|
|
@@ -7933,13 +8073,18 @@ function commentKey(line, block) {
|
|
|
7933
8073
|
const blockKey = `${block}.${key}`;
|
|
7934
8074
|
if (blockKey in COMMENTS.en) return blockKey;
|
|
7935
8075
|
}
|
|
8076
|
+
if (indent === 4 && block === "native" && nativeNested === "snapshot") {
|
|
8077
|
+
const nestedKey = `native.snapshot.${key}`;
|
|
8078
|
+
if (nestedKey in COMMENTS.en) return nestedKey;
|
|
8079
|
+
}
|
|
7936
8080
|
return null;
|
|
7937
8081
|
}
|
|
7938
8082
|
function renderStructuredProjectConfig(value, language) {
|
|
7939
8083
|
const output = [];
|
|
7940
8084
|
let block = null;
|
|
8085
|
+
let nativeNested = null;
|
|
7941
8086
|
for (const line of (0, import_yaml.stringify)(value).trimEnd().split("\n")) {
|
|
7942
|
-
const key = commentKey(line, block);
|
|
8087
|
+
const key = commentKey(line, block, nativeNested);
|
|
7943
8088
|
if (key) {
|
|
7944
8089
|
const indent = line.match(/^\s*/u)?.[0] ?? "";
|
|
7945
8090
|
for (const comment of projectConfigComment(key, language).split("\n")) {
|
|
@@ -7951,6 +8096,9 @@ function renderStructuredProjectConfig(value, language) {
|
|
|
7951
8096
|
if (line.startsWith("native:")) block = "native";
|
|
7952
8097
|
else if (line.startsWith("classic:")) block = "classic";
|
|
7953
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;
|
|
7954
8102
|
}
|
|
7955
8103
|
}
|
|
7956
8104
|
output.push("");
|
|
@@ -7959,7 +8107,7 @@ function renderStructuredProjectConfig(value, language) {
|
|
|
7959
8107
|
|
|
7960
8108
|
// domains/comet-native/native-protected-file.ts
|
|
7961
8109
|
import { createHash as createHash3 } from "node:crypto";
|
|
7962
|
-
import { constants as
|
|
8110
|
+
import { constants as fsConstants2, promises as fs4 } from "node:fs";
|
|
7963
8111
|
import path4 from "node:path";
|
|
7964
8112
|
import { TextDecoder as TextDecoder2 } from "node:util";
|
|
7965
8113
|
function isInside3(parent, target) {
|
|
@@ -7973,10 +8121,13 @@ function positiveLimit2(value) {
|
|
|
7973
8121
|
return value;
|
|
7974
8122
|
}
|
|
7975
8123
|
function sameDirectoryIdentity3(expected, actual) {
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
8124
|
+
return sameFileObject(
|
|
8125
|
+
{ ...expected, birthtime: expected.birthtimeMs },
|
|
8126
|
+
{
|
|
8127
|
+
...actual,
|
|
8128
|
+
birthtime: actual.birthtimeMs
|
|
8129
|
+
}
|
|
8130
|
+
);
|
|
7980
8131
|
}
|
|
7981
8132
|
function asFileIdentity(stat) {
|
|
7982
8133
|
return {
|
|
@@ -7989,17 +8140,22 @@ function asFileIdentity(stat) {
|
|
|
7989
8140
|
};
|
|
7990
8141
|
}
|
|
7991
8142
|
function sameFileIdentity3(expected, actual) {
|
|
7992
|
-
|
|
7993
|
-
|
|
8143
|
+
return sameFileObject(
|
|
8144
|
+
{ ...expected, birthtime: expected.birthtimeMs },
|
|
8145
|
+
{
|
|
8146
|
+
...actual,
|
|
8147
|
+
birthtime: actual.birthtimeMs
|
|
8148
|
+
}
|
|
8149
|
+
) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
|
|
7994
8150
|
}
|
|
7995
8151
|
async function captureDirectoryIdentity2(directory, label) {
|
|
7996
|
-
const stat = await
|
|
8152
|
+
const stat = await fs4.lstat(directory);
|
|
7997
8153
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
7998
8154
|
throw new Error(`${label} parent must be a real directory: ${directory}`);
|
|
7999
8155
|
}
|
|
8000
8156
|
return {
|
|
8001
8157
|
path: directory,
|
|
8002
|
-
realPath: await
|
|
8158
|
+
realPath: await fs4.realpath(directory),
|
|
8003
8159
|
dev: stat.dev,
|
|
8004
8160
|
ino: stat.ino,
|
|
8005
8161
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -8027,8 +8183,8 @@ async function captureDirectoryChain2(root, directory, label) {
|
|
|
8027
8183
|
}
|
|
8028
8184
|
async function verifyDirectoryChain3(chain, label) {
|
|
8029
8185
|
for (const identity of chain) {
|
|
8030
|
-
const stat = await
|
|
8031
|
-
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) {
|
|
8032
8188
|
throw new Error(`${label} parent changed during I/O: ${identity.path}`);
|
|
8033
8189
|
}
|
|
8034
8190
|
}
|
|
@@ -8058,27 +8214,27 @@ async function readNativeProtectedFile(options) {
|
|
|
8058
8214
|
);
|
|
8059
8215
|
await options.hooks?.afterParentChainCaptured?.();
|
|
8060
8216
|
await verifyDirectoryChain3(chain, options.label);
|
|
8061
|
-
const before = await
|
|
8217
|
+
const before = await fs4.lstat(file);
|
|
8062
8218
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
8063
8219
|
throw new Error(`${options.label} must be a regular file`);
|
|
8064
8220
|
}
|
|
8065
8221
|
if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`);
|
|
8066
8222
|
const beforeIdentity = asFileIdentity(before);
|
|
8067
|
-
const beforeRealPath = await
|
|
8223
|
+
const beforeRealPath = await fs4.realpath(file);
|
|
8068
8224
|
if (!isInside3(chain[0].realPath, beforeRealPath)) {
|
|
8069
8225
|
throw new Error(`${options.label} resolves outside its managed root`);
|
|
8070
8226
|
}
|
|
8071
8227
|
if (forbidden.some((identity) => isInside3(identity.realPath, beforeRealPath))) {
|
|
8072
8228
|
throw new Error(`${options.label} resolves inside an excluded root`);
|
|
8073
8229
|
}
|
|
8074
|
-
const flags = process.platform === "win32" ?
|
|
8075
|
-
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);
|
|
8076
8232
|
try {
|
|
8077
8233
|
const opened = await handle.stat();
|
|
8078
8234
|
await options.hooks?.afterOpen?.();
|
|
8079
8235
|
const [pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
8080
|
-
|
|
8081
|
-
|
|
8236
|
+
fs4.lstat(file),
|
|
8237
|
+
fs4.realpath(file)
|
|
8082
8238
|
]);
|
|
8083
8239
|
await verifyDirectoryChain3(chain, options.label);
|
|
8084
8240
|
await verifyDirectoryChain3(forbidden, options.label);
|
|
@@ -8090,8 +8246,8 @@ async function readNativeProtectedFile(options) {
|
|
|
8090
8246
|
await options.hooks?.beforeFinalCheck?.();
|
|
8091
8247
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
8092
8248
|
handle.stat(),
|
|
8093
|
-
|
|
8094
|
-
|
|
8249
|
+
fs4.lstat(file),
|
|
8250
|
+
fs4.realpath(file)
|
|
8095
8251
|
]);
|
|
8096
8252
|
await verifyDirectoryChain3(chain, options.label);
|
|
8097
8253
|
await verifyDirectoryChain3(forbidden, options.label);
|
|
@@ -8121,11 +8277,11 @@ async function readNativeProtectedDirectory(options) {
|
|
|
8121
8277
|
const chain = await captureDirectoryChain2(options.root, options.directory, options.label);
|
|
8122
8278
|
let entries;
|
|
8123
8279
|
if (options.maxEntries === void 0) {
|
|
8124
|
-
entries = await
|
|
8280
|
+
entries = await fs4.readdir(options.directory, { withFileTypes: true });
|
|
8125
8281
|
} else {
|
|
8126
8282
|
const maxEntries = positiveLimit2(options.maxEntries);
|
|
8127
8283
|
entries = [];
|
|
8128
|
-
const directory = await
|
|
8284
|
+
const directory = await fs4.opendir(options.directory);
|
|
8129
8285
|
try {
|
|
8130
8286
|
for await (const entry2 of directory) {
|
|
8131
8287
|
entries.push(entry2);
|
|
@@ -8166,25 +8322,25 @@ async function quarantineNativeProtectedDirectoryInternal(options) {
|
|
|
8166
8322
|
}
|
|
8167
8323
|
await options.beforeQuarantine?.();
|
|
8168
8324
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8169
|
-
const current = await
|
|
8170
|
-
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) {
|
|
8171
8327
|
throw new Error(`${options.label} changed before quarantine`);
|
|
8172
8328
|
}
|
|
8173
8329
|
try {
|
|
8174
|
-
await
|
|
8330
|
+
await fs4.lstat(quarantine);
|
|
8175
8331
|
throw new Error(`${options.label} quarantine path is occupied`);
|
|
8176
8332
|
} catch (error) {
|
|
8177
8333
|
if (error.code !== "ENOENT") throw error;
|
|
8178
8334
|
}
|
|
8179
|
-
await
|
|
8335
|
+
await fs4.rename(directory, quarantine);
|
|
8180
8336
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8181
|
-
const quarantined = await
|
|
8337
|
+
const quarantined = await fs4.lstat(quarantine);
|
|
8182
8338
|
if (!quarantined.isDirectory() || quarantined.isSymbolicLink() || !sameDirectoryIdentity3(identity, quarantined)) {
|
|
8183
8339
|
throw new Error(`${options.label} changed while quarantining`);
|
|
8184
8340
|
}
|
|
8185
8341
|
await options.afterQuarantine?.(quarantine);
|
|
8186
8342
|
try {
|
|
8187
|
-
await
|
|
8343
|
+
await fs4.lstat(directory);
|
|
8188
8344
|
throw new Error(`${options.label} path was recreated while quarantining`);
|
|
8189
8345
|
} catch (error) {
|
|
8190
8346
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -8197,12 +8353,12 @@ async function quarantineNativeProtectedDirectory(options) {
|
|
|
8197
8353
|
async function removeNativeProtectedFile(options) {
|
|
8198
8354
|
const file = path4.resolve(options.file);
|
|
8199
8355
|
const parentChain = await captureDirectoryChain2(options.root, path4.dirname(file), options.label);
|
|
8200
|
-
const before = await
|
|
8356
|
+
const before = await fs4.lstat(file);
|
|
8201
8357
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
8202
8358
|
throw new Error(`${options.label} must be a regular file`);
|
|
8203
8359
|
}
|
|
8204
8360
|
const identity = asFileIdentity(before);
|
|
8205
|
-
const realPath = await
|
|
8361
|
+
const realPath = await fs4.realpath(file);
|
|
8206
8362
|
if (!isInside3(parentChain[0].realPath, realPath)) {
|
|
8207
8363
|
throw new Error(`${options.label} resolves outside its managed root`);
|
|
8208
8364
|
}
|
|
@@ -8215,17 +8371,17 @@ async function removeNativeProtectedFile(options) {
|
|
|
8215
8371
|
if (snapshot2.hash !== options.expectedHash || snapshot2.size !== options.expectedSize) {
|
|
8216
8372
|
throw new Error(`${options.label} changed before removal`);
|
|
8217
8373
|
}
|
|
8218
|
-
const [afterRead, afterReadRealPath] = await Promise.all([
|
|
8374
|
+
const [afterRead, afterReadRealPath] = await Promise.all([fs4.lstat(file), fs4.realpath(file)]);
|
|
8219
8375
|
if (!afterRead.isFile() || afterRead.isSymbolicLink() || !sameFileIdentity3(identity, afterRead) || afterReadRealPath !== realPath) {
|
|
8220
8376
|
throw new Error(`${options.label} changed while verifying removal`);
|
|
8221
8377
|
}
|
|
8222
8378
|
await options.beforeRemove?.();
|
|
8223
8379
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8224
|
-
const [current, currentRealPath] = await Promise.all([
|
|
8380
|
+
const [current, currentRealPath] = await Promise.all([fs4.lstat(file), fs4.realpath(file)]);
|
|
8225
8381
|
if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentity3(identity, current) || currentRealPath !== realPath) {
|
|
8226
8382
|
throw new Error(`${options.label} changed before removal`);
|
|
8227
8383
|
}
|
|
8228
|
-
await
|
|
8384
|
+
await fs4.rm(file);
|
|
8229
8385
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8230
8386
|
}
|
|
8231
8387
|
async function removeNativeProtectedEmptyDirectory(options) {
|
|
@@ -8241,11 +8397,11 @@ async function removeNativeProtectedEmptyDirectory(options) {
|
|
|
8241
8397
|
}
|
|
8242
8398
|
await options.beforeRemove?.();
|
|
8243
8399
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8244
|
-
const current = await
|
|
8245
|
-
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) {
|
|
8246
8402
|
throw new Error(`${options.label} changed before removal`);
|
|
8247
8403
|
}
|
|
8248
|
-
await
|
|
8404
|
+
await fs4.rmdir(directory);
|
|
8249
8405
|
await verifyDirectoryChain3(parentChain, options.label);
|
|
8250
8406
|
}
|
|
8251
8407
|
async function ensureNativeProtectedDirectory(options) {
|
|
@@ -8260,7 +8416,7 @@ async function ensureNativeProtectedDirectory(options) {
|
|
|
8260
8416
|
await verifyDirectoryChain3(chain, options.label);
|
|
8261
8417
|
cursor = path4.join(cursor, segment);
|
|
8262
8418
|
try {
|
|
8263
|
-
await
|
|
8419
|
+
await fs4.mkdir(cursor);
|
|
8264
8420
|
} catch (error) {
|
|
8265
8421
|
if (error.code !== "EEXIST") throw error;
|
|
8266
8422
|
}
|
|
@@ -8291,7 +8447,7 @@ async function moveNativeProtectedDirectory(options) {
|
|
|
8291
8447
|
throw new Error(`${options.label} source resolves outside its managed root`);
|
|
8292
8448
|
}
|
|
8293
8449
|
try {
|
|
8294
|
-
await
|
|
8450
|
+
await fs4.lstat(target);
|
|
8295
8451
|
throw new Error(`${options.label} target already exists`);
|
|
8296
8452
|
} catch (error) {
|
|
8297
8453
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -8301,21 +8457,21 @@ async function moveNativeProtectedDirectory(options) {
|
|
|
8301
8457
|
verifyDirectoryChain3(sourceParentChain, options.label),
|
|
8302
8458
|
verifyDirectoryChain3(targetParentChain, options.label)
|
|
8303
8459
|
]);
|
|
8304
|
-
const current = await
|
|
8305
|
-
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) {
|
|
8306
8462
|
throw new Error(`${options.label} source changed before move`);
|
|
8307
8463
|
}
|
|
8308
|
-
await
|
|
8464
|
+
await fs4.rename(source, target);
|
|
8309
8465
|
await Promise.all([
|
|
8310
8466
|
verifyDirectoryChain3(sourceParentChain, options.label),
|
|
8311
8467
|
verifyDirectoryChain3(targetParentChain, options.label)
|
|
8312
8468
|
]);
|
|
8313
|
-
const moved = await
|
|
8314
|
-
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))) {
|
|
8315
8471
|
throw new Error(`${options.label} source identity changed while moving`);
|
|
8316
8472
|
}
|
|
8317
8473
|
try {
|
|
8318
|
-
await
|
|
8474
|
+
await fs4.lstat(source);
|
|
8319
8475
|
throw new Error(`${options.label} source was recreated while moving`);
|
|
8320
8476
|
} catch (error) {
|
|
8321
8477
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -8341,7 +8497,7 @@ async function copyNativeProtectedFile(options) {
|
|
|
8341
8497
|
if (options.expectedTargetHash === void 0) return;
|
|
8342
8498
|
if (options.expectedTargetHash === null) {
|
|
8343
8499
|
try {
|
|
8344
|
-
await
|
|
8500
|
+
await fs4.lstat(options.target);
|
|
8345
8501
|
} catch (error) {
|
|
8346
8502
|
if (error.code === "ENOENT") return;
|
|
8347
8503
|
throw error;
|
|
@@ -8372,13 +8528,13 @@ async function copyNativeProtectedFile(options) {
|
|
|
8372
8528
|
}
|
|
8373
8529
|
|
|
8374
8530
|
// domains/comet-native/native-paths.ts
|
|
8375
|
-
import { promises as
|
|
8531
|
+
import { promises as fs5 } from "fs";
|
|
8376
8532
|
import path5 from "path";
|
|
8377
8533
|
import os from "os";
|
|
8378
8534
|
var PROJECT_CONFIG_FILE = ".comet/config.yaml";
|
|
8379
8535
|
async function isFileOrDirectory(target) {
|
|
8380
8536
|
try {
|
|
8381
|
-
await
|
|
8537
|
+
await fs5.lstat(target);
|
|
8382
8538
|
return true;
|
|
8383
8539
|
} catch (error) {
|
|
8384
8540
|
if (error.code === "ENOENT") return false;
|
|
@@ -8387,7 +8543,7 @@ async function isFileOrDirectory(target) {
|
|
|
8387
8543
|
}
|
|
8388
8544
|
async function declaresNativeProjectConfig(target) {
|
|
8389
8545
|
try {
|
|
8390
|
-
const source = await
|
|
8546
|
+
const source = await fs5.readFile(target, "utf8");
|
|
8391
8547
|
return /^schema:\s*comet\.project\.v1\s*$/mu.test(source);
|
|
8392
8548
|
} catch (error) {
|
|
8393
8549
|
if (error.code === "ENOENT") return false;
|
|
@@ -8407,12 +8563,12 @@ async function physicalPath(target) {
|
|
|
8407
8563
|
missing.push(path5.basename(cursor));
|
|
8408
8564
|
cursor = parent;
|
|
8409
8565
|
}
|
|
8410
|
-
const existing = await
|
|
8566
|
+
const existing = await fs5.realpath(cursor);
|
|
8411
8567
|
return path5.resolve(existing, ...missing.reverse());
|
|
8412
8568
|
}
|
|
8413
8569
|
async function isSymbolicLink(target) {
|
|
8414
8570
|
try {
|
|
8415
|
-
return (await
|
|
8571
|
+
return (await fs5.lstat(target)).isSymbolicLink();
|
|
8416
8572
|
} catch (error) {
|
|
8417
8573
|
if (error.code === "ENOENT") return false;
|
|
8418
8574
|
throw error;
|
|
@@ -8421,7 +8577,7 @@ async function isSymbolicLink(target) {
|
|
|
8421
8577
|
async function discoverNativeProject(startPath) {
|
|
8422
8578
|
let cursor = path5.resolve(startPath);
|
|
8423
8579
|
try {
|
|
8424
|
-
if (!(await
|
|
8580
|
+
if (!(await fs5.stat(cursor)).isDirectory()) cursor = path5.dirname(cursor);
|
|
8425
8581
|
} catch (error) {
|
|
8426
8582
|
if (error.code !== "ENOENT") throw error;
|
|
8427
8583
|
}
|
|
@@ -8460,7 +8616,7 @@ function normalizeArtifactRootRef(value) {
|
|
|
8460
8616
|
async function resolveArtifactRoot(projectRoot, value) {
|
|
8461
8617
|
const normalized = normalizeArtifactRootRef(value);
|
|
8462
8618
|
const lexical = path5.resolve(projectRoot, ...normalized.split("/"));
|
|
8463
|
-
const physicalProject = await
|
|
8619
|
+
const physicalProject = await fs5.realpath(projectRoot);
|
|
8464
8620
|
const physicalTarget = await physicalPath(lexical);
|
|
8465
8621
|
if (!inside(physicalProject, physicalTarget)) {
|
|
8466
8622
|
throw new Error("native.artifact_root resolves outside the project root");
|
|
@@ -8506,7 +8662,7 @@ async function ensureNativeDirectories(paths) {
|
|
|
8506
8662
|
await Promise.all(
|
|
8507
8663
|
directories.map(async (directory) => {
|
|
8508
8664
|
await resolveContainedNativePath(paths.nativeRoot, directory);
|
|
8509
|
-
await
|
|
8665
|
+
await fs5.mkdir(directory, { recursive: true });
|
|
8510
8666
|
})
|
|
8511
8667
|
);
|
|
8512
8668
|
}
|
|
@@ -8537,11 +8693,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
8537
8693
|
"artifact_root",
|
|
8538
8694
|
"language",
|
|
8539
8695
|
"clarification_mode",
|
|
8696
|
+
"snapshot",
|
|
8540
8697
|
"pending_root_move"
|
|
8541
8698
|
]);
|
|
8699
|
+
var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
|
|
8700
|
+
"include",
|
|
8701
|
+
"exclude",
|
|
8702
|
+
"max_files",
|
|
8703
|
+
"max_total_bytes",
|
|
8704
|
+
"max_duration_ms"
|
|
8705
|
+
]);
|
|
8542
8706
|
var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
|
|
8543
8707
|
var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
|
|
8544
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
|
+
}
|
|
8545
8790
|
function record(value, label) {
|
|
8546
8791
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8547
8792
|
throw new Error(`${label} must be a mapping`);
|
|
@@ -8627,6 +8872,7 @@ function parseConfig(value) {
|
|
|
8627
8872
|
throw new Error("native.clarification_mode must be sequential or batch");
|
|
8628
8873
|
}
|
|
8629
8874
|
const pending = parsePending(native.pending_root_move);
|
|
8875
|
+
const snapshot2 = parseSnapshot(native.snapshot);
|
|
8630
8876
|
return {
|
|
8631
8877
|
schema: "comet.project.v1",
|
|
8632
8878
|
default_workflow: root.default_workflow,
|
|
@@ -8636,6 +8882,7 @@ function parseConfig(value) {
|
|
|
8636
8882
|
artifact_root: normalizeArtifactRootRef(native.artifact_root),
|
|
8637
8883
|
language,
|
|
8638
8884
|
clarification_mode: clarificationMode,
|
|
8885
|
+
snapshot: snapshot2,
|
|
8639
8886
|
...pending ? { pending_root_move: pending } : {}
|
|
8640
8887
|
}
|
|
8641
8888
|
};
|
|
@@ -8648,7 +8895,8 @@ function defaultProjectConfig(artifactRoot = "docs", language = "en") {
|
|
|
8648
8895
|
native: {
|
|
8649
8896
|
artifact_root: normalizeArtifactRootRef(artifactRoot),
|
|
8650
8897
|
language,
|
|
8651
|
-
clarification_mode: "sequential"
|
|
8898
|
+
clarification_mode: "sequential",
|
|
8899
|
+
snapshot: { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] }
|
|
8652
8900
|
}
|
|
8653
8901
|
};
|
|
8654
8902
|
}
|
|
@@ -8656,7 +8904,7 @@ async function readProjectConfig(projectRoot) {
|
|
|
8656
8904
|
const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
|
|
8657
8905
|
const file = canonical;
|
|
8658
8906
|
try {
|
|
8659
|
-
await
|
|
8907
|
+
await fs6.lstat(file);
|
|
8660
8908
|
} catch (error) {
|
|
8661
8909
|
if (error.code === "ENOENT") return null;
|
|
8662
8910
|
throw error;
|
|
@@ -8717,6 +8965,7 @@ async function writeProjectConfig(projectRoot, config) {
|
|
|
8717
8965
|
artifact_root: config.native.artifact_root,
|
|
8718
8966
|
language: config.native.language,
|
|
8719
8967
|
clarification_mode: config.native.clarification_mode,
|
|
8968
|
+
snapshot: config.native.snapshot,
|
|
8720
8969
|
...config.native.pending_root_move ? {
|
|
8721
8970
|
pending_root_move: {
|
|
8722
8971
|
id: config.native.pending_root_move.id,
|
|
@@ -8744,6 +8993,7 @@ async function writeProjectConfig(projectRoot, config) {
|
|
|
8744
8993
|
artifact_root: validated.native.artifact_root,
|
|
8745
8994
|
language: validated.native.language,
|
|
8746
8995
|
clarification_mode: validated.native.clarification_mode,
|
|
8996
|
+
snapshot: validated.native.snapshot,
|
|
8747
8997
|
...validated.native.pending_root_move ? {
|
|
8748
8998
|
pending_root_move: {
|
|
8749
8999
|
id: validated.native.pending_root_move.id,
|
|
@@ -8762,7 +9012,7 @@ async function writeProjectConfig(projectRoot, config) {
|
|
|
8762
9012
|
}
|
|
8763
9013
|
};
|
|
8764
9014
|
const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
|
|
8765
|
-
await
|
|
9015
|
+
await fs6.mkdir(path6.dirname(canonical), { recursive: true });
|
|
8766
9016
|
await atomicWriteText(
|
|
8767
9017
|
canonical,
|
|
8768
9018
|
renderStructuredProjectConfig(document, validated.native.language === "zh-CN" ? "zh-CN" : "en")
|
|
@@ -8791,13 +9041,13 @@ async function resolveNativeProject(options) {
|
|
|
8791
9041
|
}
|
|
8792
9042
|
|
|
8793
9043
|
// domains/comet-native/native-mutation-lock.ts
|
|
8794
|
-
import { promises as
|
|
9044
|
+
import { promises as fs9 } from "fs";
|
|
8795
9045
|
import path9 from "path";
|
|
8796
9046
|
|
|
8797
9047
|
// domains/comet-native/native-lock.ts
|
|
8798
9048
|
import { AsyncLocalStorage } from "async_hooks";
|
|
8799
9049
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8800
|
-
import { promises as
|
|
9050
|
+
import { promises as fs7 } from "fs";
|
|
8801
9051
|
import os2 from "os";
|
|
8802
9052
|
import path7 from "path";
|
|
8803
9053
|
var NATIVE_LOCK_MAX_BYTES = 16 * 1024;
|
|
@@ -8830,10 +9080,12 @@ function nativeLockFileIdentity(stat) {
|
|
|
8830
9080
|
};
|
|
8831
9081
|
}
|
|
8832
9082
|
function sameNativeLockObject(left, right) {
|
|
8833
|
-
|
|
8834
|
-
|
|
9083
|
+
const leftObject = { dev: left.device, ino: left.inode, birthtime: left.birthtimeNs };
|
|
9084
|
+
const rightObject = { dev: right.device, ino: right.inode, birthtime: right.birthtimeNs };
|
|
9085
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
9086
|
+
return sameFileObject(leftObject, rightObject);
|
|
8835
9087
|
}
|
|
8836
|
-
return
|
|
9088
|
+
return sameFileObject(leftObject, rightObject) && left.size === right.size;
|
|
8837
9089
|
}
|
|
8838
9090
|
function sameNativeLockVersion(left, right) {
|
|
8839
9091
|
return sameNativeLockObject(left, right) && left.size === right.size && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs;
|
|
@@ -8846,41 +9098,35 @@ function sameNativeLockDiagnosis(left, right) {
|
|
|
8846
9098
|
return left.owner.id === right.owner.id && sameNativeLockVersion(left.identity, right.identity);
|
|
8847
9099
|
}
|
|
8848
9100
|
async function readNativeLockSnapshot(file) {
|
|
8849
|
-
let
|
|
9101
|
+
let bytes;
|
|
9102
|
+
let stat;
|
|
8850
9103
|
try {
|
|
8851
|
-
|
|
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;
|
|
8852
9110
|
} catch (error) {
|
|
8853
9111
|
if (error.code === "ENOENT") return null;
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
const source = await handle.readFile({ encoding: "utf8" });
|
|
8865
|
-
const pathStat = await fs6.lstat(file, { bigint: true });
|
|
8866
|
-
if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
|
|
8867
|
-
throw new Error(`Native lock must be a regular file: ${file}`);
|
|
8868
|
-
}
|
|
8869
|
-
const identity = nativeLockFileIdentity(stat);
|
|
8870
|
-
if (!sameNativeLockObject(identity, nativeLockFileIdentity(pathStat))) {
|
|
8871
|
-
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 });
|
|
8872
9122
|
}
|
|
8873
|
-
return {
|
|
8874
|
-
file,
|
|
8875
|
-
owner: parseNativeLockOwner(JSON.parse(source), file),
|
|
8876
|
-
identity
|
|
8877
|
-
};
|
|
8878
|
-
} catch (error) {
|
|
8879
|
-
if (error.code === "ENOENT") return null;
|
|
8880
9123
|
throw error;
|
|
8881
|
-
} finally {
|
|
8882
|
-
await handle.close();
|
|
8883
9124
|
}
|
|
9125
|
+
return {
|
|
9126
|
+
file,
|
|
9127
|
+
owner: parseNativeLockOwner(JSON.parse(bytes.toString("utf8")), file),
|
|
9128
|
+
identity: nativeLockFileIdentity(stat)
|
|
9129
|
+
};
|
|
8884
9130
|
}
|
|
8885
9131
|
async function readNativeLock(file) {
|
|
8886
9132
|
return (await readNativeLockSnapshot(file))?.owner ?? null;
|
|
@@ -8899,13 +9145,13 @@ function diagnosisFromSnapshot(snapshot2) {
|
|
|
8899
9145
|
}
|
|
8900
9146
|
async function restoreQuarantinedNativeLock(quarantine, file) {
|
|
8901
9147
|
try {
|
|
8902
|
-
await
|
|
9148
|
+
await fs7.lstat(file);
|
|
8903
9149
|
return;
|
|
8904
9150
|
} catch (error) {
|
|
8905
9151
|
if (error.code !== "ENOENT") throw error;
|
|
8906
9152
|
}
|
|
8907
9153
|
try {
|
|
8908
|
-
await
|
|
9154
|
+
await fs7.rename(quarantine, file);
|
|
8909
9155
|
} catch (error) {
|
|
8910
9156
|
if (error.code !== "ENOENT") throw error;
|
|
8911
9157
|
}
|
|
@@ -8919,13 +9165,13 @@ async function removeBoundNativeLock(expected, quarantineDir) {
|
|
|
8919
9165
|
if (!sameNativeLockVersion(current.identity, expected.identity)) {
|
|
8920
9166
|
throw new Error(`Native lock identity changed: ${expected.file}`);
|
|
8921
9167
|
}
|
|
8922
|
-
await
|
|
9168
|
+
await fs7.mkdir(quarantineDir, { recursive: true });
|
|
8923
9169
|
const quarantine = path7.join(
|
|
8924
9170
|
quarantineDir,
|
|
8925
9171
|
`${path7.basename(expected.file)}.${expected.owner.id}.${randomUUID2()}.removed`
|
|
8926
9172
|
);
|
|
8927
9173
|
try {
|
|
8928
|
-
await
|
|
9174
|
+
await fs7.rename(expected.file, quarantine);
|
|
8929
9175
|
} catch (error) {
|
|
8930
9176
|
if (error.code === "ENOENT") return "missing";
|
|
8931
9177
|
throw error;
|
|
@@ -8935,7 +9181,7 @@ async function removeBoundNativeLock(expected, quarantineDir) {
|
|
|
8935
9181
|
await restoreQuarantinedNativeLock(quarantine, expected.file);
|
|
8936
9182
|
throw new Error(`Native lock changed before quarantine: ${expected.file}`);
|
|
8937
9183
|
}
|
|
8938
|
-
await
|
|
9184
|
+
await fs7.rm(quarantine, { force: true });
|
|
8939
9185
|
return "removed";
|
|
8940
9186
|
}
|
|
8941
9187
|
function newNativeLockOwner(operation) {
|
|
@@ -8950,7 +9196,7 @@ function newNativeLockOwner(operation) {
|
|
|
8950
9196
|
async function writeNativeLockFile(file, owner) {
|
|
8951
9197
|
let handle;
|
|
8952
9198
|
try {
|
|
8953
|
-
handle = await
|
|
9199
|
+
handle = await fs7.open(file, "wx");
|
|
8954
9200
|
} catch (error) {
|
|
8955
9201
|
if (error.code === "EEXIST") {
|
|
8956
9202
|
const existing = await readNativeLock(file);
|
|
@@ -8971,32 +9217,32 @@ async function writeNativeLockFile(file, owner) {
|
|
|
8971
9217
|
}
|
|
8972
9218
|
async function publishNativeCoordinatorClaim(paths, operation) {
|
|
8973
9219
|
const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
|
|
8974
|
-
await
|
|
9220
|
+
await fs7.mkdir(locksDir, { recursive: true });
|
|
8975
9221
|
const coordinatorDir = await resolveContainedNativePath(
|
|
8976
9222
|
paths.nativeRoot,
|
|
8977
9223
|
path7.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR)
|
|
8978
9224
|
);
|
|
8979
|
-
await
|
|
9225
|
+
await fs7.mkdir(coordinatorDir, { recursive: true });
|
|
8980
9226
|
const owner = newNativeLockOwner(operation);
|
|
8981
9227
|
const temporary = path7.join(coordinatorDir, `.${owner.id}.tmp`);
|
|
8982
9228
|
const file = path7.join(coordinatorDir, `${owner.id}.claim`);
|
|
8983
9229
|
try {
|
|
8984
9230
|
const identity = await writeNativeLockFile(temporary, owner);
|
|
8985
|
-
await
|
|
9231
|
+
await fs7.rename(temporary, file);
|
|
8986
9232
|
const published = await readNativeLockSnapshot(file);
|
|
8987
9233
|
if (!published || !sameNativeLockObject(identity, published.identity)) {
|
|
8988
9234
|
throw new Error(`Native lock coordinator claim changed while publishing: ${file}`);
|
|
8989
9235
|
}
|
|
8990
9236
|
return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity: published.identity };
|
|
8991
9237
|
} finally {
|
|
8992
|
-
await
|
|
9238
|
+
await fs7.rm(temporary, { force: true });
|
|
8993
9239
|
}
|
|
8994
9240
|
}
|
|
8995
9241
|
async function hasNativeCoordinatorPredecessor(claim) {
|
|
8996
9242
|
const coordinatorDir = path7.dirname(claim.file);
|
|
8997
9243
|
let predecessor = false;
|
|
8998
9244
|
const claimName = path7.basename(claim.file);
|
|
8999
|
-
for (const entry2 of await
|
|
9245
|
+
for (const entry2 of await fs7.readdir(coordinatorDir, { withFileTypes: true })) {
|
|
9000
9246
|
if (!entry2.isFile() || entry2.isSymbolicLink() || !entry2.name.endsWith(".claim")) continue;
|
|
9001
9247
|
const file = path7.join(coordinatorDir, entry2.name);
|
|
9002
9248
|
if (path7.resolve(file) === path7.resolve(claim.file)) continue;
|
|
@@ -9082,7 +9328,7 @@ async function withNativeLockRecovery(pathEntries, operation, work) {
|
|
|
9082
9328
|
async function acquireNativeLock(paths, name, operation) {
|
|
9083
9329
|
return withNativeLockCoordinator(paths, `acquire ${name}`, async () => {
|
|
9084
9330
|
const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
|
|
9085
|
-
await
|
|
9331
|
+
await fs7.mkdir(locksDir, { recursive: true });
|
|
9086
9332
|
const file = await resolveContainedNativePath(
|
|
9087
9333
|
paths.nativeRoot,
|
|
9088
9334
|
path7.join(locksDir, lockName(name))
|
|
@@ -9144,7 +9390,7 @@ async function takeOverNativeStaleLock(paths, file, expected) {
|
|
|
9144
9390
|
}
|
|
9145
9391
|
|
|
9146
9392
|
// domains/comet-native/native-transaction.ts
|
|
9147
|
-
import { promises as
|
|
9393
|
+
import { promises as fs8 } from "fs";
|
|
9148
9394
|
import path8 from "path";
|
|
9149
9395
|
import { TextDecoder as TextDecoder3 } from "util";
|
|
9150
9396
|
var JOURNAL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9606,7 +9852,7 @@ async function resolveRef(paths, ref) {
|
|
|
9606
9852
|
}
|
|
9607
9853
|
async function exists(file) {
|
|
9608
9854
|
try {
|
|
9609
|
-
await
|
|
9855
|
+
await fs8.access(file);
|
|
9610
9856
|
return true;
|
|
9611
9857
|
} catch (error) {
|
|
9612
9858
|
if (error.code === "ENOENT") return false;
|
|
@@ -9616,7 +9862,7 @@ async function exists(file) {
|
|
|
9616
9862
|
async function readEventLogSnapshot(paths, id, options = {}) {
|
|
9617
9863
|
const tx = await resolveNativeTransactionPaths(paths, id);
|
|
9618
9864
|
try {
|
|
9619
|
-
await
|
|
9865
|
+
await fs8.lstat(tx.events);
|
|
9620
9866
|
} catch (error) {
|
|
9621
9867
|
if (error.code === "ENOENT") {
|
|
9622
9868
|
return {
|
|
@@ -9696,7 +9942,7 @@ async function appendNativeTransactionEvent(paths, id, type, operationId) {
|
|
|
9696
9942
|
...operationId ? { operationId } : {}
|
|
9697
9943
|
};
|
|
9698
9944
|
parseEvent(event, event.sequence);
|
|
9699
|
-
await
|
|
9945
|
+
await fs8.mkdir(tx.directory, { recursive: true });
|
|
9700
9946
|
await atomicWriteText(tx.events, canonicalEventLogSource([...snapshot2.events, event]), {
|
|
9701
9947
|
containedRoot: paths.nativeRoot,
|
|
9702
9948
|
beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2)
|
|
@@ -9709,8 +9955,8 @@ async function createNativeTransaction(paths, journal) {
|
|
|
9709
9955
|
}
|
|
9710
9956
|
journal = parseJournal(journal);
|
|
9711
9957
|
const tx = await resolveNativeTransactionPaths(paths, journal.id);
|
|
9712
|
-
await
|
|
9713
|
-
await
|
|
9958
|
+
await fs8.mkdir(tx.staged, { recursive: true });
|
|
9959
|
+
await fs8.mkdir(tx.backups, { recursive: true });
|
|
9714
9960
|
await atomicWriteJson(tx.journal, journal);
|
|
9715
9961
|
await appendNativeTransactionEvent(paths, journal.id, "prepared");
|
|
9716
9962
|
}
|
|
@@ -9934,7 +10180,7 @@ function nativeRootRef(paths, target) {
|
|
|
9934
10180
|
async function hasUnfinishedTransaction(paths, allowedTransactionId) {
|
|
9935
10181
|
let entries;
|
|
9936
10182
|
try {
|
|
9937
|
-
entries = await
|
|
10183
|
+
entries = await fs9.readdir(paths.transactionsDir, { withFileTypes: true });
|
|
9938
10184
|
} catch (error) {
|
|
9939
10185
|
if (error.code === "ENOENT") return false;
|
|
9940
10186
|
throw error;
|
|
@@ -10010,7 +10256,7 @@ async function compareAndSwapNativeRevision(options) {
|
|
|
10010
10256
|
// domains/comet-native/native-snapshot.ts
|
|
10011
10257
|
import { createHash as createHash5 } from "crypto";
|
|
10012
10258
|
import { spawn } from "node:child_process";
|
|
10013
|
-
import { promises as
|
|
10259
|
+
import { promises as fs10 } from "fs";
|
|
10014
10260
|
import path10 from "path";
|
|
10015
10261
|
|
|
10016
10262
|
// domains/comet-native/native-hash.ts
|
|
@@ -10045,9 +10291,17 @@ var MANIFEST_KEYS = /* @__PURE__ */ new Set([
|
|
|
10045
10291
|
"entries",
|
|
10046
10292
|
"omitted",
|
|
10047
10293
|
"omittedCount",
|
|
10048
|
-
"omissionOverflow"
|
|
10294
|
+
"omissionOverflow",
|
|
10295
|
+
"policy"
|
|
10296
|
+
]);
|
|
10297
|
+
var LIMIT_KEYS = /* @__PURE__ */ new Set([
|
|
10298
|
+
"maxFiles",
|
|
10299
|
+
"maxFileBytes",
|
|
10300
|
+
"maxTotalBytes",
|
|
10301
|
+
"maxManifestBytes",
|
|
10302
|
+
"maxDurationMs"
|
|
10049
10303
|
]);
|
|
10050
|
-
var
|
|
10304
|
+
var POLICY_KEYS = /* @__PURE__ */ new Set(["schema", "include", "exclude", "hash"]);
|
|
10051
10305
|
var CAPTURE_KEYS = /* @__PURE__ */ new Set(["provider", "gitSelection", "physicalSelection", "projection"]);
|
|
10052
10306
|
var GIT_PROJECTION_KEYS = /* @__PURE__ */ new Set(["provider", "selection"]);
|
|
10053
10307
|
var GIT_SELECTION_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -10471,7 +10725,7 @@ async function hasGitMetadataBoundary(projectRoot) {
|
|
|
10471
10725
|
let cursor = path10.resolve(projectRoot);
|
|
10472
10726
|
while (true) {
|
|
10473
10727
|
try {
|
|
10474
|
-
await
|
|
10728
|
+
await fs10.lstat(path10.join(cursor, ".git"));
|
|
10475
10729
|
return true;
|
|
10476
10730
|
} catch (error) {
|
|
10477
10731
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -10700,7 +10954,7 @@ async function nativePhysicalSnapshotSelection(options) {
|
|
|
10700
10954
|
if (!hasExecutionBudget()) return;
|
|
10701
10955
|
let handle;
|
|
10702
10956
|
try {
|
|
10703
|
-
handle = await
|
|
10957
|
+
handle = await fs10.opendir(directory);
|
|
10704
10958
|
} catch (error) {
|
|
10705
10959
|
if (!hasExecutionBudget()) return;
|
|
10706
10960
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -10750,7 +11004,7 @@ async function nativePhysicalSnapshotSelection(options) {
|
|
|
10750
11004
|
if (!hasExecutionBudget()) break;
|
|
10751
11005
|
let stat;
|
|
10752
11006
|
try {
|
|
10753
|
-
stat = await
|
|
11007
|
+
stat = await fs10.lstat(target);
|
|
10754
11008
|
} catch (error) {
|
|
10755
11009
|
if (!hasExecutionBudget()) break;
|
|
10756
11010
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -10773,7 +11027,7 @@ async function nativePhysicalSnapshotSelection(options) {
|
|
|
10773
11027
|
if (!hasExecutionBudget()) break;
|
|
10774
11028
|
let realDirectory;
|
|
10775
11029
|
try {
|
|
10776
|
-
realDirectory = await
|
|
11030
|
+
realDirectory = await fs10.realpath(target);
|
|
10777
11031
|
} catch (error) {
|
|
10778
11032
|
if (!hasExecutionBudget()) break;
|
|
10779
11033
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -10964,6 +11218,133 @@ function isChangedDuringReadError(error) {
|
|
|
10964
11218
|
function serializedManifestBytes(manifest) {
|
|
10965
11219
|
return Buffer.byteLength(JSON.stringify(manifest, null, 2) + "\n");
|
|
10966
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
|
+
}
|
|
10967
11348
|
function foldSnapshotOverflowHash(previous, kind, value) {
|
|
10968
11349
|
const payload = JSON.stringify(value);
|
|
10969
11350
|
return sha256Text(
|
|
@@ -10985,16 +11366,18 @@ function takeLastCompactableOmission(omissions) {
|
|
|
10985
11366
|
return null;
|
|
10986
11367
|
}
|
|
10987
11368
|
function sameFileIdentity4(left, right) {
|
|
10988
|
-
|
|
10989
|
-
|
|
11369
|
+
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
11370
|
+
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
11371
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
11372
|
+
return sameFileObject(leftObject, rightObject);
|
|
10990
11373
|
}
|
|
10991
|
-
return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
11374
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
10992
11375
|
}
|
|
10993
11376
|
async function sha256FileBounded(file, maxBytes, expected, execution) {
|
|
10994
11377
|
if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
|
|
10995
11378
|
let handle;
|
|
10996
11379
|
try {
|
|
10997
|
-
handle = await
|
|
11380
|
+
handle = await fs10.open(file, "r");
|
|
10998
11381
|
} catch (error) {
|
|
10999
11382
|
if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
|
|
11000
11383
|
throw error;
|
|
@@ -11329,8 +11712,28 @@ function parseNativeContentSnapshotManifest(value) {
|
|
|
11329
11712
|
maxManifestBytes: positiveInteger(
|
|
11330
11713
|
limitValue.maxManifestBytes,
|
|
11331
11714
|
"Native snapshot maxManifestBytes"
|
|
11332
|
-
)
|
|
11715
|
+
),
|
|
11716
|
+
...limitValue.maxDurationMs === void 0 ? {} : {
|
|
11717
|
+
maxDurationMs: positiveInteger(limitValue.maxDurationMs, "Native snapshot maxDurationMs")
|
|
11718
|
+
}
|
|
11333
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
|
+
}
|
|
11334
11737
|
if (!Array.isArray(manifest.entries) || !Array.isArray(manifest.omitted)) {
|
|
11335
11738
|
throw new Error("Native content snapshot entries and omissions must be arrays");
|
|
11336
11739
|
}
|
|
@@ -11413,6 +11816,7 @@ function parseNativeContentSnapshotManifest(value) {
|
|
|
11413
11816
|
createdAt: manifest.createdAt,
|
|
11414
11817
|
complete: manifest.complete,
|
|
11415
11818
|
limits,
|
|
11819
|
+
...policy ? { policy } : {},
|
|
11416
11820
|
entries,
|
|
11417
11821
|
omitted,
|
|
11418
11822
|
omittedCount,
|
|
@@ -11553,13 +11957,18 @@ function nativeBaselineManifestFile(paths, name) {
|
|
|
11553
11957
|
return path10.join(changeDir, "runtime", "baseline-manifest.json");
|
|
11554
11958
|
}
|
|
11555
11959
|
async function createNativeContentSnapshot(paths, options = {}) {
|
|
11556
|
-
const execution = createNativeSnapshotExecution(options);
|
|
11557
11960
|
const limits = {
|
|
11558
11961
|
maxFiles: options.limits?.maxFiles ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFiles,
|
|
11559
11962
|
maxFileBytes: options.limits?.maxFileBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFileBytes,
|
|
11560
11963
|
maxTotalBytes: options.limits?.maxTotalBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxTotalBytes,
|
|
11561
|
-
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 }
|
|
11562
11966
|
};
|
|
11967
|
+
const policy = resolveSnapshotPolicy(options.policy);
|
|
11968
|
+
const execution = createNativeSnapshotExecution({
|
|
11969
|
+
...options,
|
|
11970
|
+
deadlineMs: options.deadlineMs ?? limits.maxDurationMs
|
|
11971
|
+
});
|
|
11563
11972
|
const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits);
|
|
11564
11973
|
const physicalSelectionLimits = resolveNativePhysicalSelectionLimits(
|
|
11565
11974
|
options.physicalSelectionLimits
|
|
@@ -11568,9 +11977,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11568
11977
|
throw new Error("Native snapshot limits must be positive");
|
|
11569
11978
|
}
|
|
11570
11979
|
const projectRoot = path10.resolve(paths.projectRoot);
|
|
11571
|
-
const physicalProjectRoot = await
|
|
11980
|
+
const physicalProjectRoot = await fs10.realpath(projectRoot);
|
|
11572
11981
|
const nativeRoot = path10.resolve(paths.nativeRoot);
|
|
11573
|
-
const physicalNativeRoot = await
|
|
11982
|
+
const physicalNativeRoot = await fs10.realpath(nativeRoot);
|
|
11574
11983
|
const configFile = path10.resolve(paths.configFile);
|
|
11575
11984
|
const selectionFile = path10.join(projectRoot, ".comet", "current-change.json");
|
|
11576
11985
|
const denylist = normalizedDenylist(projectRoot, options.denylist ?? []);
|
|
@@ -11642,7 +12051,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11642
12051
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11643
12052
|
let realTarget;
|
|
11644
12053
|
try {
|
|
11645
|
-
realTarget = await
|
|
12054
|
+
realTarget = await fs10.realpath(target);
|
|
11646
12055
|
} catch (error) {
|
|
11647
12056
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11648
12057
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11683,9 +12092,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11683
12092
|
return;
|
|
11684
12093
|
}
|
|
11685
12094
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11686
|
-
afterRealTarget = await
|
|
12095
|
+
afterRealTarget = await fs10.realpath(target);
|
|
11687
12096
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11688
|
-
after = await
|
|
12097
|
+
after = await fs10.lstat(target);
|
|
11689
12098
|
} catch (error) {
|
|
11690
12099
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11691
12100
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11719,11 +12128,11 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11719
12128
|
let secondTarget;
|
|
11720
12129
|
let after;
|
|
11721
12130
|
try {
|
|
11722
|
-
firstTarget = await
|
|
12131
|
+
firstTarget = await fs10.readlink(target, { encoding: "buffer" });
|
|
11723
12132
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11724
|
-
after = await
|
|
12133
|
+
after = await fs10.lstat(target);
|
|
11725
12134
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11726
|
-
secondTarget = await
|
|
12135
|
+
secondTarget = await fs10.readlink(target, { encoding: "buffer" });
|
|
11727
12136
|
} catch (error) {
|
|
11728
12137
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11729
12138
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11767,9 +12176,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11767
12176
|
let realTarget2;
|
|
11768
12177
|
let stat2;
|
|
11769
12178
|
try {
|
|
11770
|
-
realTarget2 = await
|
|
12179
|
+
realTarget2 = await fs10.realpath(validation2.target);
|
|
11771
12180
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11772
|
-
stat2 = await
|
|
12181
|
+
stat2 = await fs10.lstat(validation2.target);
|
|
11773
12182
|
} catch (error) {
|
|
11774
12183
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11775
12184
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11796,9 +12205,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11796
12205
|
let stat2;
|
|
11797
12206
|
let rawTarget;
|
|
11798
12207
|
try {
|
|
11799
|
-
stat2 = await
|
|
12208
|
+
stat2 = await fs10.lstat(validation2.target);
|
|
11800
12209
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11801
|
-
rawTarget = await
|
|
12210
|
+
rawTarget = await fs10.readlink(validation2.target, { encoding: "buffer" });
|
|
11802
12211
|
} catch (error) {
|
|
11803
12212
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11804
12213
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11824,9 +12233,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11824
12233
|
let realTarget;
|
|
11825
12234
|
let stat;
|
|
11826
12235
|
try {
|
|
11827
|
-
realTarget = await
|
|
12236
|
+
realTarget = await fs10.realpath(validation2.target);
|
|
11828
12237
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11829
|
-
stat = await
|
|
12238
|
+
stat = await fs10.lstat(validation2.target);
|
|
11830
12239
|
} catch {
|
|
11831
12240
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11832
12241
|
invalidateCapturedEntry(relative, {
|
|
@@ -11882,7 +12291,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11882
12291
|
for (const [relative, target] of capturedTrackedAbsences) {
|
|
11883
12292
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11884
12293
|
try {
|
|
11885
|
-
const stat = await
|
|
12294
|
+
const stat = await fs10.lstat(target);
|
|
11886
12295
|
if (!nativeSnapshotExecutionHasBudget(execution)) return;
|
|
11887
12296
|
omit({
|
|
11888
12297
|
path: relative,
|
|
@@ -11918,10 +12327,12 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11918
12327
|
for (const record8 of before.records) {
|
|
11919
12328
|
if (record8.type !== "file" && record8.type !== "symlink") continue;
|
|
11920
12329
|
if (remainingNativeSnapshotTime(execution) < 1) break;
|
|
12330
|
+
if (!snapshotPolicyIncludes(policy, record8.path, execution)) continue;
|
|
12331
|
+
if (remainingNativeSnapshotTime(execution) < 1) break;
|
|
11921
12332
|
const target = path10.resolve(projectRoot, ...record8.path.split("/"));
|
|
11922
12333
|
let stat;
|
|
11923
12334
|
try {
|
|
11924
|
-
stat = await
|
|
12335
|
+
stat = await fs10.lstat(target);
|
|
11925
12336
|
} catch (error) {
|
|
11926
12337
|
if (!nativeSnapshotExecutionHasBudget(execution)) break;
|
|
11927
12338
|
if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
|
|
@@ -11974,6 +12385,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11974
12385
|
await options.gitSelectionHooks?.afterInitialSelection?.();
|
|
11975
12386
|
for (const relative of selectionPaths(gitSelection)) {
|
|
11976
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;
|
|
11977
12391
|
const target = path10.resolve(projectRoot, ...relative.split("/"));
|
|
11978
12392
|
if (target === configFile || target === selectionFile || denylist.some((denied) => sameOrInside(denied, target))) {
|
|
11979
12393
|
continue;
|
|
@@ -11986,7 +12400,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
11986
12400
|
let realGitlink;
|
|
11987
12401
|
let gitlinkStat;
|
|
11988
12402
|
try {
|
|
11989
|
-
[realGitlink, gitlinkStat] = await Promise.all([
|
|
12403
|
+
[realGitlink, gitlinkStat] = await Promise.all([fs10.realpath(target), fs10.lstat(target)]);
|
|
11990
12404
|
} catch {
|
|
11991
12405
|
omit({
|
|
11992
12406
|
path: relative,
|
|
@@ -12044,7 +12458,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12044
12458
|
}
|
|
12045
12459
|
let before;
|
|
12046
12460
|
try {
|
|
12047
|
-
before = await
|
|
12461
|
+
before = await fs10.lstat(target);
|
|
12048
12462
|
} catch (error) {
|
|
12049
12463
|
if (isChangedDuringReadError(error) && gitSelection.tracked.has(relative)) {
|
|
12050
12464
|
capturedTrackedAbsences.set(relative, target);
|
|
@@ -12098,6 +12512,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12098
12512
|
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
12099
12513
|
complete: omittedCount === 0,
|
|
12100
12514
|
limits,
|
|
12515
|
+
...policy ? { policy: policy.manifest } : {},
|
|
12101
12516
|
entries,
|
|
12102
12517
|
omitted,
|
|
12103
12518
|
omittedCount,
|
|
@@ -12134,6 +12549,21 @@ async function createNativeContentSnapshot(paths, options = {}) {
|
|
|
12134
12549
|
}
|
|
12135
12550
|
return manifest;
|
|
12136
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
|
+
}
|
|
12137
12567
|
async function writeNativeBaselineManifest(paths, name, manifest) {
|
|
12138
12568
|
const file = nativeBaselineManifestFile(paths, name);
|
|
12139
12569
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
@@ -12182,7 +12612,7 @@ function assertRunStorageLayout(storage) {
|
|
|
12182
12612
|
|
|
12183
12613
|
// domains/comet-native/native-run-store.ts
|
|
12184
12614
|
import { createHash as createHash6 } from "node:crypto";
|
|
12185
|
-
import { constants as
|
|
12615
|
+
import { constants as fsConstants3, promises as fs11 } from "node:fs";
|
|
12186
12616
|
import path13 from "node:path";
|
|
12187
12617
|
import { TextDecoder as TextDecoder4 } from "node:util";
|
|
12188
12618
|
|
|
@@ -12371,14 +12801,22 @@ function asIdentity(stat) {
|
|
|
12371
12801
|
};
|
|
12372
12802
|
}
|
|
12373
12803
|
function sameFileIdentity5(expected, actual) {
|
|
12374
|
-
|
|
12375
|
-
|
|
12804
|
+
return sameFileObject(
|
|
12805
|
+
{ ...expected, birthtime: expected.birthtimeMs },
|
|
12806
|
+
{
|
|
12807
|
+
...actual,
|
|
12808
|
+
birthtime: actual.birthtimeMs
|
|
12809
|
+
}
|
|
12810
|
+
) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
|
|
12376
12811
|
}
|
|
12377
12812
|
function sameDirectoryIdentity4(expected, actual) {
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12813
|
+
return sameFileObject(
|
|
12814
|
+
{ ...expected, birthtime: expected.birthtimeMs },
|
|
12815
|
+
{
|
|
12816
|
+
...actual,
|
|
12817
|
+
birthtime: actual.birthtimeMs
|
|
12818
|
+
}
|
|
12819
|
+
);
|
|
12382
12820
|
}
|
|
12383
12821
|
function runFile(changeDir, kind, relativePath) {
|
|
12384
12822
|
const expected = NATIVE_RUN_STORAGE[kind];
|
|
@@ -12391,13 +12829,13 @@ function runFile(changeDir, kind, relativePath) {
|
|
|
12391
12829
|
return target;
|
|
12392
12830
|
}
|
|
12393
12831
|
async function directoryIdentity2(directory) {
|
|
12394
|
-
const stat = await
|
|
12832
|
+
const stat = await fs11.lstat(directory);
|
|
12395
12833
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
12396
12834
|
throw new Error(`Native Run parent must be a real directory: ${directory}`);
|
|
12397
12835
|
}
|
|
12398
12836
|
return {
|
|
12399
12837
|
path: directory,
|
|
12400
|
-
realPath: await
|
|
12838
|
+
realPath: await fs11.realpath(directory),
|
|
12401
12839
|
dev: stat.dev,
|
|
12402
12840
|
ino: stat.ino,
|
|
12403
12841
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -12429,8 +12867,8 @@ async function captureDirectoryChain3(root, directory) {
|
|
|
12429
12867
|
}
|
|
12430
12868
|
async function verifyDirectoryChain4(chain) {
|
|
12431
12869
|
for (const identity of chain) {
|
|
12432
|
-
const stat = await
|
|
12433
|
-
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) {
|
|
12434
12872
|
throw new Error(`Native Run parent changed during I/O: ${identity.path}`);
|
|
12435
12873
|
}
|
|
12436
12874
|
}
|
|
@@ -12455,7 +12893,7 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
|
|
|
12455
12893
|
await hooks?.afterParentChainCaptured?.();
|
|
12456
12894
|
let before;
|
|
12457
12895
|
try {
|
|
12458
|
-
before = await
|
|
12896
|
+
before = await fs11.lstat(file);
|
|
12459
12897
|
} catch (error) {
|
|
12460
12898
|
if (error.code === "ENOENT") return null;
|
|
12461
12899
|
throw error;
|
|
@@ -12465,22 +12903,22 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
|
|
|
12465
12903
|
}
|
|
12466
12904
|
if (before.size > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`);
|
|
12467
12905
|
const beforeIdentity = asIdentity(before);
|
|
12468
|
-
const beforeRealPath = await
|
|
12906
|
+
const beforeRealPath = await fs11.realpath(file);
|
|
12469
12907
|
if (!isInside4(chain[0].realPath, beforeRealPath)) {
|
|
12470
12908
|
throw new Error(`${label} resolves outside its change`);
|
|
12471
12909
|
}
|
|
12472
|
-
const flags = process.platform === "win32" ?
|
|
12910
|
+
const flags = process.platform === "win32" ? fsConstants3.O_RDONLY : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK;
|
|
12473
12911
|
let handle;
|
|
12474
12912
|
try {
|
|
12475
|
-
handle = await
|
|
12913
|
+
handle = await fs11.open(file, flags);
|
|
12476
12914
|
} catch (error) {
|
|
12477
12915
|
throw new Error(`${label} changed while opening`, { cause: error });
|
|
12478
12916
|
}
|
|
12479
12917
|
try {
|
|
12480
12918
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
12481
12919
|
handle.stat(),
|
|
12482
|
-
|
|
12483
|
-
|
|
12920
|
+
fs11.lstat(file),
|
|
12921
|
+
fs11.realpath(file)
|
|
12484
12922
|
]);
|
|
12485
12923
|
await verifyDirectoryChain4(chain);
|
|
12486
12924
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity5(beforeIdentity, opened) || !sameFileIdentity5(beforeIdentity, pathAfterOpen)) {
|
|
@@ -12491,8 +12929,8 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
|
|
|
12491
12929
|
await hooks?.beforeFinalCheck?.();
|
|
12492
12930
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
12493
12931
|
handle.stat(),
|
|
12494
|
-
|
|
12495
|
-
|
|
12932
|
+
fs11.lstat(file),
|
|
12933
|
+
fs11.realpath(file)
|
|
12496
12934
|
]);
|
|
12497
12935
|
await verifyDirectoryChain4(chain);
|
|
12498
12936
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity5(beforeIdentity, afterHandle) || !sameFileIdentity5(beforeIdentity, afterPath)) {
|
|
@@ -12517,13 +12955,13 @@ async function captureTarget(changeDir, file, label) {
|
|
|
12517
12955
|
if (!chain) return { exists: false };
|
|
12518
12956
|
let stat;
|
|
12519
12957
|
try {
|
|
12520
|
-
stat = await
|
|
12958
|
+
stat = await fs11.lstat(file);
|
|
12521
12959
|
} catch (error) {
|
|
12522
12960
|
if (error.code === "ENOENT") return { exists: false };
|
|
12523
12961
|
throw error;
|
|
12524
12962
|
}
|
|
12525
12963
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
|
|
12526
|
-
const realPath = await
|
|
12964
|
+
const realPath = await fs11.realpath(file);
|
|
12527
12965
|
if (!isInside4(chain[0].realPath, realPath))
|
|
12528
12966
|
throw new Error(`${label} resolves outside its change`);
|
|
12529
12967
|
await verifyDirectoryChain4(chain);
|
|
@@ -12537,12 +12975,12 @@ async function verifyTarget(changeDir, file, expected, label) {
|
|
|
12537
12975
|
}
|
|
12538
12976
|
let stat;
|
|
12539
12977
|
try {
|
|
12540
|
-
stat = await
|
|
12978
|
+
stat = await fs11.lstat(file);
|
|
12541
12979
|
} catch (error) {
|
|
12542
12980
|
if (error.code === "ENOENT" && !expected.exists) return;
|
|
12543
12981
|
throw new Error(`${label} changed before commit`, { cause: error });
|
|
12544
12982
|
}
|
|
12545
|
-
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) {
|
|
12546
12984
|
throw new Error(`${label} changed before commit`);
|
|
12547
12985
|
}
|
|
12548
12986
|
await verifyDirectoryChain4(chain);
|
|
@@ -12851,7 +13289,7 @@ async function repairNativeTrajectoryTail(paths, name, hooks) {
|
|
|
12851
13289
|
|
|
12852
13290
|
// domains/comet-native/native-workspace.ts
|
|
12853
13291
|
import { createHash as createHash8 } from "node:crypto";
|
|
12854
|
-
import { promises as
|
|
13292
|
+
import { promises as fs12 } from "node:fs";
|
|
12855
13293
|
import path15 from "node:path";
|
|
12856
13294
|
var HASH_PATTERN2 = /^[a-f0-9]{64}$/u;
|
|
12857
13295
|
var MAX_WORKSPACE_IDENTITY_BYTES = 16 * 1024;
|
|
@@ -12890,8 +13328,8 @@ function identityHash(tag, value) {
|
|
|
12890
13328
|
${value}`).digest("hex");
|
|
12891
13329
|
}
|
|
12892
13330
|
async function physicalDirectoryIdentity(tag, value) {
|
|
12893
|
-
const realPath = await
|
|
12894
|
-
const stat = await
|
|
13331
|
+
const realPath = await fs12.realpath(value);
|
|
13332
|
+
const stat = await fs12.lstat(realPath);
|
|
12895
13333
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
12896
13334
|
throw new Error("Native workspace identity requires a real directory");
|
|
12897
13335
|
}
|
|
@@ -12902,8 +13340,8 @@ ${stat.ino}
|
|
|
12902
13340
|
${stat.birthtimeMs}`);
|
|
12903
13341
|
}
|
|
12904
13342
|
async function directoryPathIdentity(tag, value) {
|
|
12905
|
-
const realPath = await
|
|
12906
|
-
const stat = await
|
|
13343
|
+
const realPath = await fs12.realpath(value);
|
|
13344
|
+
const stat = await fs12.lstat(realPath);
|
|
12907
13345
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
12908
13346
|
throw new Error("Native workspace identity requires a real directory");
|
|
12909
13347
|
}
|
|
@@ -13170,15 +13608,17 @@ var NativeChangeRevisionConflictError = class extends Error {
|
|
|
13170
13608
|
code = "native-change-revision-conflict";
|
|
13171
13609
|
};
|
|
13172
13610
|
var NativeBaselineIncompleteError = class extends Error {
|
|
13173
|
-
constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated) {
|
|
13611
|
+
constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated, effectiveLimits = null, policyHash = null) {
|
|
13174
13612
|
super(
|
|
13175
|
-
`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.`
|
|
13176
13614
|
);
|
|
13177
13615
|
this.change = change;
|
|
13178
13616
|
this.omittedCount = omittedCount;
|
|
13179
13617
|
this.omittedByReason = omittedByReason;
|
|
13180
13618
|
this.samplePaths = samplePaths;
|
|
13181
13619
|
this.sampleTruncated = sampleTruncated;
|
|
13620
|
+
this.effectiveLimits = effectiveLimits;
|
|
13621
|
+
this.policyHash = policyHash;
|
|
13182
13622
|
this.name = "NativeBaselineIncompleteError";
|
|
13183
13623
|
}
|
|
13184
13624
|
change;
|
|
@@ -13186,6 +13626,8 @@ var NativeBaselineIncompleteError = class extends Error {
|
|
|
13186
13626
|
omittedByReason;
|
|
13187
13627
|
samplePaths;
|
|
13188
13628
|
sampleTruncated;
|
|
13629
|
+
effectiveLimits;
|
|
13630
|
+
policyHash;
|
|
13189
13631
|
code = "native-baseline-incomplete";
|
|
13190
13632
|
};
|
|
13191
13633
|
var NATIVE_BRIEF_TEMPLATE = [
|
|
@@ -13542,7 +13984,7 @@ async function hasPendingNativeSchemaMigration(paths, name) {
|
|
|
13542
13984
|
const file = path16.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json");
|
|
13543
13985
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
13544
13986
|
try {
|
|
13545
|
-
await
|
|
13987
|
+
await fs13.lstat(file);
|
|
13546
13988
|
return true;
|
|
13547
13989
|
} catch (error) {
|
|
13548
13990
|
if (error.code === "ENOENT") return false;
|
|
@@ -13553,7 +13995,7 @@ async function hasPendingNativeCheckpointRecovery(paths, name) {
|
|
|
13553
13995
|
const file = path16.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json");
|
|
13554
13996
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
13555
13997
|
try {
|
|
13556
|
-
await
|
|
13998
|
+
await fs13.lstat(file);
|
|
13557
13999
|
return true;
|
|
13558
14000
|
} catch (error) {
|
|
13559
14001
|
if (error.code === "ENOENT") return false;
|
|
@@ -13574,13 +14016,13 @@ async function createNativeChangeLocked(options) {
|
|
|
13574
14016
|
let createdChangeDir = false;
|
|
13575
14017
|
try {
|
|
13576
14018
|
try {
|
|
13577
|
-
await
|
|
14019
|
+
await fs13.mkdir(changeDir, { recursive: false });
|
|
13578
14020
|
createdChangeDir = true;
|
|
13579
14021
|
} catch (error) {
|
|
13580
14022
|
if (error.code === "ENOENT") {
|
|
13581
|
-
await
|
|
14023
|
+
await fs13.mkdir(options.paths.changesDir, { recursive: true });
|
|
13582
14024
|
try {
|
|
13583
|
-
await
|
|
14025
|
+
await fs13.mkdir(changeDir, { recursive: false });
|
|
13584
14026
|
createdChangeDir = true;
|
|
13585
14027
|
} catch (retryError) {
|
|
13586
14028
|
if (retryError.code === "EEXIST") {
|
|
@@ -13617,13 +14059,23 @@ async function createNativeChangeLocked(options) {
|
|
|
13617
14059
|
run_id: null
|
|
13618
14060
|
};
|
|
13619
14061
|
await Promise.all([
|
|
13620
|
-
|
|
13621
|
-
|
|
14062
|
+
fs13.mkdir(path16.join(changeDir, "specs"), { recursive: true }),
|
|
14063
|
+
fs13.mkdir(path16.join(changeDir, "runtime", "checkpoints"), { recursive: true }),
|
|
13622
14064
|
atomicWriteText(path16.join(changeDir, "brief.md"), NATIVE_BRIEF_TEMPLATE)
|
|
13623
14065
|
]);
|
|
14066
|
+
const projectConfig = await readProjectConfig(options.paths.projectRoot);
|
|
14067
|
+
const snapshot2 = projectConfig?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG;
|
|
13624
14068
|
const baseline = await createNativeContentSnapshot(options.paths, {
|
|
13625
14069
|
now: options.now,
|
|
13626
|
-
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
|
|
13627
14079
|
});
|
|
13628
14080
|
if (!baseline.complete) {
|
|
13629
14081
|
const health = inspectNativeContentSnapshotHealth(baseline);
|
|
@@ -13638,7 +14090,9 @@ async function createNativeChangeLocked(options) {
|
|
|
13638
14090
|
baseline.omittedCount,
|
|
13639
14091
|
omittedByReason,
|
|
13640
14092
|
health.samplePaths,
|
|
13641
|
-
health.sampleTruncated
|
|
14093
|
+
health.sampleTruncated,
|
|
14094
|
+
baseline.limits,
|
|
14095
|
+
baseline.policy?.hash ?? null
|
|
13642
14096
|
);
|
|
13643
14097
|
}
|
|
13644
14098
|
await writeNativeBaselineManifest(options.paths, state.name, baseline);
|
|
@@ -13651,7 +14105,7 @@ async function createNativeChangeLocked(options) {
|
|
|
13651
14105
|
});
|
|
13652
14106
|
return state;
|
|
13653
14107
|
} catch (error) {
|
|
13654
|
-
if (createdChangeDir) await
|
|
14108
|
+
if (createdChangeDir) await fs13.rm(changeDir, { recursive: true, force: true });
|
|
13655
14109
|
throw error;
|
|
13656
14110
|
}
|
|
13657
14111
|
}
|
|
@@ -13701,7 +14155,7 @@ async function createNativeChangeFile(paths, state) {
|
|
|
13701
14155
|
const file = path16.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE);
|
|
13702
14156
|
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
13703
14157
|
try {
|
|
13704
|
-
await
|
|
14158
|
+
await fs13.access(file);
|
|
13705
14159
|
throw new Error(`Native change state already exists: ${state.name}`);
|
|
13706
14160
|
} catch (error) {
|
|
13707
14161
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -13799,12 +14253,12 @@ async function readContainedFile(root, relativeRef) {
|
|
|
13799
14253
|
const target = path17.resolve(root, ...relativeRef.split(/[\\/]/u));
|
|
13800
14254
|
if (!isInsidePath(root, target))
|
|
13801
14255
|
throw new Error(`Artifact escapes Native change: ${relativeRef}`);
|
|
13802
|
-
const realRoot = await
|
|
13803
|
-
const realTarget = await
|
|
14256
|
+
const realRoot = await fs14.realpath(root);
|
|
14257
|
+
const realTarget = await fs14.realpath(target);
|
|
13804
14258
|
if (!isInsidePath(realRoot, realTarget)) {
|
|
13805
14259
|
throw new Error(`Artifact symlink escapes Native change: ${relativeRef}`);
|
|
13806
14260
|
}
|
|
13807
|
-
if (!(await
|
|
14261
|
+
if (!(await fs14.stat(realTarget)).isFile())
|
|
13808
14262
|
throw new Error(`Artifact is not a file: ${relativeRef}`);
|
|
13809
14263
|
return realTarget;
|
|
13810
14264
|
}
|
|
@@ -13946,7 +14400,7 @@ async function resolveNativeArtifactFile(changeDir, relativeRef) {
|
|
|
13946
14400
|
}
|
|
13947
14401
|
|
|
13948
14402
|
// domains/comet-native/native-archive-inspection.ts
|
|
13949
|
-
import { promises as
|
|
14403
|
+
import { promises as fs18 } from "node:fs";
|
|
13950
14404
|
import path29 from "node:path";
|
|
13951
14405
|
|
|
13952
14406
|
// domains/comet-native/native-archive-preflight.ts
|
|
@@ -14536,7 +14990,7 @@ function buildNativeConflictRadar(input) {
|
|
|
14536
14990
|
|
|
14537
14991
|
// domains/comet-native/native-evidence-storage.ts
|
|
14538
14992
|
import { createHash as createHash12 } from "node:crypto";
|
|
14539
|
-
import { promises as
|
|
14993
|
+
import { promises as fs15 } from "node:fs";
|
|
14540
14994
|
import path22 from "node:path";
|
|
14541
14995
|
|
|
14542
14996
|
// domains/comet-native/native-verification-scope.ts
|
|
@@ -14725,8 +15179,10 @@ function snapshotProjection(manifest) {
|
|
|
14725
15179
|
maxFiles: parsed.limits.maxFiles,
|
|
14726
15180
|
maxFileBytes: parsed.limits.maxFileBytes,
|
|
14727
15181
|
maxTotalBytes: parsed.limits.maxTotalBytes,
|
|
14728
|
-
maxManifestBytes: parsed.limits.maxManifestBytes
|
|
15182
|
+
maxManifestBytes: parsed.limits.maxManifestBytes,
|
|
15183
|
+
...parsed.limits.maxDurationMs === void 0 ? {} : { maxDurationMs: parsed.limits.maxDurationMs }
|
|
14729
15184
|
},
|
|
15185
|
+
...parsed.policy ? { policy: parsed.policy } : {},
|
|
14730
15186
|
entries,
|
|
14731
15187
|
omitted,
|
|
14732
15188
|
omittedCount: parsed.omittedCount,
|
|
@@ -15204,7 +15660,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
|
|
|
15204
15660
|
exactScopeKeys(
|
|
15205
15661
|
root,
|
|
15206
15662
|
["schema", "origin", "complete", "limits", "entries", "omitted", "omittedCount"],
|
|
15207
|
-
["capture", "omissionOverflow"],
|
|
15663
|
+
["capture", "omissionOverflow", "policy"],
|
|
15208
15664
|
"Native snapshot projection"
|
|
15209
15665
|
);
|
|
15210
15666
|
if (root.schema !== NATIVE_SNAPSHOT_PROJECTION_SCHEMA) {
|
|
@@ -15217,6 +15673,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
|
|
|
15217
15673
|
createdAt: "1970-01-01T00:00:00.000Z",
|
|
15218
15674
|
complete: root.complete,
|
|
15219
15675
|
limits: root.limits,
|
|
15676
|
+
...root.policy === void 0 ? {} : { policy: root.policy },
|
|
15220
15677
|
entries: root.entries,
|
|
15221
15678
|
omitted: root.omitted,
|
|
15222
15679
|
omittedCount: root.omittedCount,
|
|
@@ -16040,25 +16497,30 @@ function isInside5(parent, target) {
|
|
|
16040
16497
|
return relative === "" || !path22.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path22.sep}`);
|
|
16041
16498
|
}
|
|
16042
16499
|
function sameDirectoryIdentity5(identity, stat) {
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16500
|
+
return sameFileObject(
|
|
16501
|
+
{ ...identity, birthtime: identity.birthtimeMs },
|
|
16502
|
+
{
|
|
16503
|
+
...stat,
|
|
16504
|
+
birthtime: stat.birthtimeMs
|
|
16505
|
+
}
|
|
16506
|
+
);
|
|
16047
16507
|
}
|
|
16048
16508
|
function sameFileIdentity6(left, right) {
|
|
16049
|
-
|
|
16050
|
-
|
|
16509
|
+
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
16510
|
+
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
16511
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
16512
|
+
return sameFileObject(leftObject, rightObject);
|
|
16051
16513
|
}
|
|
16052
|
-
return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
16514
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
16053
16515
|
}
|
|
16054
16516
|
async function captureDirectoryIdentity3(directory) {
|
|
16055
|
-
const stat = await
|
|
16517
|
+
const stat = await fs15.lstat(directory);
|
|
16056
16518
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
16057
16519
|
throw new Error(`Native evidence parent must be a real directory: ${directory}`);
|
|
16058
16520
|
}
|
|
16059
16521
|
return {
|
|
16060
16522
|
path: directory,
|
|
16061
|
-
realPath: await
|
|
16523
|
+
realPath: await fs15.realpath(directory),
|
|
16062
16524
|
dev: stat.dev,
|
|
16063
16525
|
ino: stat.ino,
|
|
16064
16526
|
birthtimeMs: stat.birthtimeMs
|
|
@@ -16084,8 +16546,8 @@ async function captureDirectoryChain4(root, directory) {
|
|
|
16084
16546
|
}
|
|
16085
16547
|
async function verifyDirectoryChain5(chain) {
|
|
16086
16548
|
for (const identity of chain) {
|
|
16087
|
-
const stat = await
|
|
16088
|
-
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) {
|
|
16089
16551
|
throw new Error(`Native evidence parent changed while reading: ${identity.path}`);
|
|
16090
16552
|
}
|
|
16091
16553
|
}
|
|
@@ -16093,20 +16555,20 @@ async function verifyDirectoryChain5(chain) {
|
|
|
16093
16555
|
async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
|
|
16094
16556
|
const chain = await captureDirectoryChain4(changeRoot, path22.dirname(file));
|
|
16095
16557
|
await hooks.afterParentChainCaptured?.();
|
|
16096
|
-
const lexical = await
|
|
16558
|
+
const lexical = await fs15.lstat(file);
|
|
16097
16559
|
if (!lexical.isFile() || lexical.isSymbolicLink()) {
|
|
16098
16560
|
throw new Error("Native evidence document must be a regular file");
|
|
16099
16561
|
}
|
|
16100
|
-
const realPath = await
|
|
16562
|
+
const realPath = await fs15.realpath(file);
|
|
16101
16563
|
if (!isInside5(chain[0].realPath, realPath)) {
|
|
16102
16564
|
throw new Error("Native evidence document resolves outside its change");
|
|
16103
16565
|
}
|
|
16104
|
-
const handle = await
|
|
16566
|
+
const handle = await fs15.open(file, "r");
|
|
16105
16567
|
try {
|
|
16106
16568
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
16107
16569
|
handle.stat(),
|
|
16108
|
-
|
|
16109
|
-
|
|
16570
|
+
fs15.lstat(file),
|
|
16571
|
+
fs15.realpath(file)
|
|
16110
16572
|
]);
|
|
16111
16573
|
await verifyDirectoryChain5(chain);
|
|
16112
16574
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== realPath || !sameFileIdentity6(opened, lexical) || !sameFileIdentity6(opened, pathAfterOpen)) {
|
|
@@ -16136,8 +16598,8 @@ async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
|
|
|
16136
16598
|
await hooks.beforeFinalCheck?.();
|
|
16137
16599
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
16138
16600
|
handle.stat(),
|
|
16139
|
-
|
|
16140
|
-
|
|
16601
|
+
fs15.lstat(file),
|
|
16602
|
+
fs15.realpath(file)
|
|
16141
16603
|
]);
|
|
16142
16604
|
await verifyDirectoryChain5(chain);
|
|
16143
16605
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity6(opened, afterHandle) || !sameFileIdentity6(opened, afterPath)) {
|
|
@@ -16229,7 +16691,7 @@ function assertEvidenceDocumentBudget(value) {
|
|
|
16229
16691
|
function serializedEvidenceBytes2(value) {
|
|
16230
16692
|
return Buffer.byteLength(JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
16231
16693
|
}
|
|
16232
|
-
function
|
|
16694
|
+
function parseSnapshot2(value, expectedHash) {
|
|
16233
16695
|
return parseNativeSnapshotProjection(value, expectedHash);
|
|
16234
16696
|
}
|
|
16235
16697
|
function parseScope(value, expectedHash) {
|
|
@@ -16305,10 +16767,10 @@ async function readNativeImplementationScopeBundle(paths, name, ref, hooks) {
|
|
|
16305
16767
|
const currentHash = parseEvidenceRef(scope.currentProjectionRef, "snapshots");
|
|
16306
16768
|
const [baseline, current] = await Promise.all([
|
|
16307
16769
|
readEvidenceDocument({ paths, name, kind: "snapshots", hash: baselineHash }).then(
|
|
16308
|
-
(value) =>
|
|
16770
|
+
(value) => parseSnapshot2(value, baselineHash)
|
|
16309
16771
|
),
|
|
16310
16772
|
readEvidenceDocument({ paths, name, kind: "snapshots", hash: currentHash }).then(
|
|
16311
|
-
(value) =>
|
|
16773
|
+
(value) => parseSnapshot2(value, currentHash)
|
|
16312
16774
|
)
|
|
16313
16775
|
]);
|
|
16314
16776
|
return rebuildNativeImplementationScopeBundle({ baseline, current, scope });
|
|
@@ -16433,7 +16895,7 @@ async function inspectNativeChangeConflicts(paths, name) {
|
|
|
16433
16895
|
|
|
16434
16896
|
// domains/comet-native/native-transition-journal.ts
|
|
16435
16897
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
16436
|
-
import { promises as
|
|
16898
|
+
import { promises as fs16 } from "fs";
|
|
16437
16899
|
import path23 from "path";
|
|
16438
16900
|
import { isDeepStrictEqual } from "util";
|
|
16439
16901
|
|
|
@@ -17891,7 +18353,7 @@ async function continueNativeTransitionLocked(paths, name, hooks) {
|
|
|
17891
18353
|
`Native transition journal changed while continuing ${journal.change}; it was not removed`
|
|
17892
18354
|
);
|
|
17893
18355
|
}
|
|
17894
|
-
await
|
|
18356
|
+
await fs16.rm(nativeTransitionJournalFile(paths, name), { force: true });
|
|
17895
18357
|
return journal.nextState;
|
|
17896
18358
|
}
|
|
17897
18359
|
async function continueNativeTransition(paths, name, hooks) {
|
|
@@ -18355,9 +18817,16 @@ function canonicalEvidencePayload(entries) {
|
|
|
18355
18817
|
const validated = validateEvidenceEntries([...entries]).map((entry2) => ({ ...entry2, evidence_refs: [...entry2.evidence_refs].sort() })).sort((left, right) => left.acceptance_id.localeCompare(right.acceptance_id));
|
|
18356
18818
|
return JSON.stringify(validated, null, 2);
|
|
18357
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
|
+
}
|
|
18358
18827
|
|
|
18359
18828
|
// domains/comet-native/native-check-receipt-storage.ts
|
|
18360
|
-
import { constants as
|
|
18829
|
+
import { constants as fsConstants4, promises as fs17 } from "node:fs";
|
|
18361
18830
|
import path26 from "node:path";
|
|
18362
18831
|
|
|
18363
18832
|
// domains/comet-native/native-check-receipt-model.ts
|
|
@@ -18706,16 +19175,21 @@ var HASH_PATTERN11 = /^[a-f0-9]{64}$/u;
|
|
|
18706
19175
|
var RECEIPT_REF_PATTERN = /^runtime\/evidence\/check-receipts\/([a-f0-9]{64})\.json$/u;
|
|
18707
19176
|
var MAX_NATIVE_CHECK_RECEIPT_BYTES = 512 * 1024;
|
|
18708
19177
|
function sameDirectoryIdentity6(identity, stat) {
|
|
18709
|
-
|
|
18710
|
-
|
|
18711
|
-
|
|
18712
|
-
|
|
19178
|
+
return sameFileObject(
|
|
19179
|
+
{ ...identity, birthtime: identity.birthtimeMs },
|
|
19180
|
+
{
|
|
19181
|
+
...stat,
|
|
19182
|
+
birthtime: stat.birthtimeMs
|
|
19183
|
+
}
|
|
19184
|
+
);
|
|
18713
19185
|
}
|
|
18714
19186
|
function sameFileIdentity7(left, right) {
|
|
18715
|
-
|
|
18716
|
-
|
|
19187
|
+
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
19188
|
+
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
19189
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
19190
|
+
return sameFileObject(leftObject, rightObject);
|
|
18717
19191
|
}
|
|
18718
|
-
return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
19192
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
18719
19193
|
}
|
|
18720
19194
|
async function captureDirectoryChain5(root, directory) {
|
|
18721
19195
|
const lexicalRoot = path26.resolve(root);
|
|
@@ -18730,11 +19204,11 @@ async function captureDirectoryChain5(root, directory) {
|
|
|
18730
19204
|
...path26.relative(lexicalRoot, lexicalDirectory).split(path26.sep).filter(Boolean)
|
|
18731
19205
|
]) {
|
|
18732
19206
|
if (segment) cursor = path26.join(cursor, segment);
|
|
18733
|
-
const stat = await
|
|
19207
|
+
const stat = await fs17.lstat(cursor);
|
|
18734
19208
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
18735
19209
|
throw new Error(`Native check receipt parent must be a real directory: ${cursor}`);
|
|
18736
19210
|
}
|
|
18737
|
-
const realPath = await
|
|
19211
|
+
const realPath = await fs17.realpath(cursor);
|
|
18738
19212
|
if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
|
|
18739
19213
|
throw new Error("Native check receipt parent resolves outside the Native root");
|
|
18740
19214
|
}
|
|
@@ -18750,8 +19224,8 @@ async function captureDirectoryChain5(root, directory) {
|
|
|
18750
19224
|
}
|
|
18751
19225
|
async function verifyDirectoryChain6(chain) {
|
|
18752
19226
|
for (const identity of chain) {
|
|
18753
|
-
const stat = await
|
|
18754
|
-
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) {
|
|
18755
19229
|
throw new Error(`Native check receipt parent changed while reading: ${identity.path}`);
|
|
18756
19230
|
}
|
|
18757
19231
|
}
|
|
@@ -18770,7 +19244,7 @@ function receiptFile(paths, name, hash6) {
|
|
|
18770
19244
|
}
|
|
18771
19245
|
async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
18772
19246
|
const chain = await captureDirectoryChain5(nativeRoot, path26.dirname(file));
|
|
18773
|
-
const before = await
|
|
19247
|
+
const before = await fs17.lstat(file);
|
|
18774
19248
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
18775
19249
|
throw new Error("Native check receipt must be a regular file");
|
|
18776
19250
|
}
|
|
@@ -18778,14 +19252,14 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
|
18778
19252
|
throw new Error(`Native check receipt exceeds ${MAX_NATIVE_CHECK_RECEIPT_BYTES} bytes`);
|
|
18779
19253
|
}
|
|
18780
19254
|
const [realChangeRoot, beforeRealPath] = await Promise.all([
|
|
18781
|
-
|
|
18782
|
-
|
|
19255
|
+
fs17.realpath(changeRoot),
|
|
19256
|
+
fs17.realpath(file)
|
|
18783
19257
|
]);
|
|
18784
19258
|
if (!isInsidePath(realChangeRoot, beforeRealPath)) {
|
|
18785
19259
|
throw new Error("Native check receipt resolves outside its change");
|
|
18786
19260
|
}
|
|
18787
|
-
const openFlags = process.platform === "win32" ? "r" :
|
|
18788
|
-
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) => {
|
|
18789
19263
|
if (error.code === "ELOOP" || error.code === "ENXIO") {
|
|
18790
19264
|
throw new Error("Native check receipt became unsafe while opening");
|
|
18791
19265
|
}
|
|
@@ -18794,8 +19268,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
|
18794
19268
|
try {
|
|
18795
19269
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
18796
19270
|
handle.stat(),
|
|
18797
|
-
|
|
18798
|
-
|
|
19271
|
+
fs17.lstat(file),
|
|
19272
|
+
fs17.realpath(file)
|
|
18799
19273
|
]);
|
|
18800
19274
|
await verifyDirectoryChain6(chain);
|
|
18801
19275
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity7(before, opened) || !sameFileIdentity7(opened, pathAfterOpen)) {
|
|
@@ -18816,8 +19290,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
|
|
|
18816
19290
|
}
|
|
18817
19291
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
18818
19292
|
handle.stat(),
|
|
18819
|
-
|
|
18820
|
-
|
|
19293
|
+
fs17.lstat(file),
|
|
19294
|
+
fs17.realpath(file)
|
|
18821
19295
|
]);
|
|
18822
19296
|
await verifyDirectoryChain6(chain);
|
|
18823
19297
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !isInsidePath(realChangeRoot, afterRealPath) || !sameFileIdentity7(opened, afterHandle) || !sameFileIdentity7(opened, afterPath)) {
|
|
@@ -19067,6 +19541,7 @@ function projectionManifest(projection) {
|
|
|
19067
19541
|
createdAt: "1970-01-01T00:00:00.000Z",
|
|
19068
19542
|
complete: projection.complete,
|
|
19069
19543
|
limits: projection.limits,
|
|
19544
|
+
...projection.policy ? { policy: projection.policy } : {},
|
|
19070
19545
|
entries: projection.entries,
|
|
19071
19546
|
omitted: projection.omitted,
|
|
19072
19547
|
omittedCount: projection.omittedCount,
|
|
@@ -19081,12 +19556,13 @@ function nativeRootRef2(paths) {
|
|
|
19081
19556
|
return value;
|
|
19082
19557
|
}
|
|
19083
19558
|
async function currentProjectionHash(options) {
|
|
19084
|
-
const
|
|
19559
|
+
const baseline = projectionManifest(options.bundle.baseline);
|
|
19560
|
+
const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
|
|
19085
19561
|
origin: "explicit",
|
|
19086
19562
|
now: options.now
|
|
19087
19563
|
});
|
|
19088
19564
|
return buildNativeImplementationScopeBundle({
|
|
19089
|
-
baseline
|
|
19565
|
+
baseline,
|
|
19090
19566
|
current,
|
|
19091
19567
|
contractHash: options.bundle.scope.contractHash,
|
|
19092
19568
|
declaredArtifacts: options.bundle.scope.declaredArtifacts,
|
|
@@ -19350,7 +19826,7 @@ function archiveTargetRef(name, now) {
|
|
|
19350
19826
|
}
|
|
19351
19827
|
async function exists2(target) {
|
|
19352
19828
|
try {
|
|
19353
|
-
await
|
|
19829
|
+
await fs18.lstat(target);
|
|
19354
19830
|
return true;
|
|
19355
19831
|
} catch (error) {
|
|
19356
19832
|
if (error.code === "ENOENT") return false;
|
|
@@ -19432,7 +19908,7 @@ async function inspectNativeArchivePreflight(options) {
|
|
|
19432
19908
|
}
|
|
19433
19909
|
|
|
19434
19910
|
// domains/comet-native/native-archive-content.ts
|
|
19435
|
-
import { promises as
|
|
19911
|
+
import { promises as fs19 } from "node:fs";
|
|
19436
19912
|
import path30 from "node:path";
|
|
19437
19913
|
var TREE_HASH_TAG = "comet.native.archive-tree.v1";
|
|
19438
19914
|
var NATIVE_ARCHIVE_CONTENT_LIMITS = {
|
|
@@ -19492,7 +19968,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
|
|
|
19492
19968
|
await protectedDirectory.verify();
|
|
19493
19969
|
const target = path30.join(directory, child.name);
|
|
19494
19970
|
const ref = path30.relative(root, target).replaceAll("\\", "/");
|
|
19495
|
-
const stat = await
|
|
19971
|
+
const stat = await fs19.lstat(target);
|
|
19496
19972
|
if (child.isSymbolicLink() || stat.isSymbolicLink()) {
|
|
19497
19973
|
throw new Error(`Native Archive content must not contain symlinks or junctions: ${ref}`);
|
|
19498
19974
|
}
|
|
@@ -19539,7 +20015,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
|
|
|
19539
20015
|
async function hashNativeArchiveTree(directory, requestedLimits = {}) {
|
|
19540
20016
|
const limits = normalizedLimits(requestedLimits);
|
|
19541
20017
|
directory = path30.resolve(directory);
|
|
19542
|
-
const stat = await
|
|
20018
|
+
const stat = await fs19.lstat(directory);
|
|
19543
20019
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
19544
20020
|
throw new Error(`Native Archive move source must be a real directory: ${directory}`);
|
|
19545
20021
|
}
|
|
@@ -19557,7 +20033,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
|
|
|
19557
20033
|
target = path30.resolve(target);
|
|
19558
20034
|
let stat;
|
|
19559
20035
|
try {
|
|
19560
|
-
stat = await
|
|
20036
|
+
stat = await fs19.lstat(target);
|
|
19561
20037
|
} catch (error) {
|
|
19562
20038
|
if (error.code === "ENOENT") return null;
|
|
19563
20039
|
throw error;
|
|
@@ -19581,7 +20057,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
|
|
|
19581
20057
|
}
|
|
19582
20058
|
|
|
19583
20059
|
// domains/comet-native/native-archive-transaction.ts
|
|
19584
|
-
import { promises as
|
|
20060
|
+
import { promises as fs20 } from "node:fs";
|
|
19585
20061
|
import path31 from "node:path";
|
|
19586
20062
|
var NATIVE_ARCHIVE_COPY_MAX_BYTES = 16 * 1024 * 1024;
|
|
19587
20063
|
var NATIVE_ARCHIVE_JOURNAL_MAX_BYTES = 256 * 1024;
|
|
@@ -19615,8 +20091,8 @@ async function assertContent(options) {
|
|
|
19615
20091
|
async function createNativeArchiveTransactionV2(paths, journal) {
|
|
19616
20092
|
const validated = parseNativeArchiveTransactionJournalV2(journal);
|
|
19617
20093
|
const tx = await resolveNativeTransactionPaths(paths, validated.id);
|
|
19618
|
-
await
|
|
19619
|
-
await
|
|
20094
|
+
await fs20.mkdir(tx.staged, { recursive: true });
|
|
20095
|
+
await fs20.mkdir(tx.backups, { recursive: true });
|
|
19620
20096
|
await atomicWriteJson(tx.journal, validated, { containedRoot: paths.nativeRoot });
|
|
19621
20097
|
await appendNativeTransactionEvent(paths, validated.id, "prepared");
|
|
19622
20098
|
}
|
|
@@ -19655,18 +20131,21 @@ function fileVersion(stat) {
|
|
|
19655
20131
|
size: stat.size
|
|
19656
20132
|
};
|
|
19657
20133
|
}
|
|
19658
|
-
function
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
|
|
20134
|
+
function sameFileObject2(expected, actual) {
|
|
20135
|
+
return sameFileObject(
|
|
20136
|
+
{ ...expected, birthtime: expected.birthtimeMs },
|
|
20137
|
+
{
|
|
20138
|
+
...actual,
|
|
20139
|
+
birthtime: actual.birthtimeMs
|
|
20140
|
+
}
|
|
20141
|
+
) && expected.birthtimeMs === actual.birthtimeMs;
|
|
19663
20142
|
}
|
|
19664
20143
|
function sameFileVersion(expected, actual) {
|
|
19665
|
-
return
|
|
20144
|
+
return sameFileObject2(expected, actual) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
|
|
19666
20145
|
}
|
|
19667
20146
|
async function pathExists(target) {
|
|
19668
20147
|
try {
|
|
19669
|
-
await
|
|
20148
|
+
await fs20.lstat(target);
|
|
19670
20149
|
return true;
|
|
19671
20150
|
} catch (error) {
|
|
19672
20151
|
if (error.code === "ENOENT") return false;
|
|
@@ -19674,7 +20153,7 @@ async function pathExists(target) {
|
|
|
19674
20153
|
}
|
|
19675
20154
|
}
|
|
19676
20155
|
async function captureStableArchiveFile(options) {
|
|
19677
|
-
const before = await
|
|
20156
|
+
const before = await fs20.lstat(options.file);
|
|
19678
20157
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
19679
20158
|
throw new Error(`${options.label} must be a regular file`);
|
|
19680
20159
|
}
|
|
@@ -19685,7 +20164,7 @@ async function captureStableArchiveFile(options) {
|
|
|
19685
20164
|
maxBytes: NATIVE_ARCHIVE_COPY_MAX_BYTES,
|
|
19686
20165
|
label: options.label
|
|
19687
20166
|
});
|
|
19688
|
-
const after = await
|
|
20167
|
+
const after = await fs20.lstat(options.file);
|
|
19689
20168
|
if (!after.isFile() || after.isSymbolicLink() || !sameFileVersion(beforeVersion, fileVersion(after)) || snapshot2.hash !== options.expectedHash) {
|
|
19690
20169
|
throw new Error(`${options.label} content changed or object identity changed while binding`);
|
|
19691
20170
|
}
|
|
@@ -19763,7 +20242,7 @@ async function readCasRecord(options) {
|
|
|
19763
20242
|
async function persistCasRecord(options) {
|
|
19764
20243
|
const existing = await readCasRecord(options);
|
|
19765
20244
|
if (existing) {
|
|
19766
|
-
if (!
|
|
20245
|
+
if (!sameFileObject2(existing.identity, options.identity)) {
|
|
19767
20246
|
throw new Error(
|
|
19768
20247
|
`Archive CAS ${options.operation.id} ${options.role} object identity changed`
|
|
19769
20248
|
);
|
|
@@ -19791,7 +20270,7 @@ async function persistCasRecord(options) {
|
|
|
19791
20270
|
if (error.code !== "EEXIST") throw error;
|
|
19792
20271
|
}
|
|
19793
20272
|
const persisted = await readCasRecord(options);
|
|
19794
|
-
if (!persisted || !
|
|
20273
|
+
if (!persisted || !sameFileObject2(persisted.identity, options.identity)) {
|
|
19795
20274
|
throw new Error(`Archive CAS ${options.operation.id} ${options.role} record changed`);
|
|
19796
20275
|
}
|
|
19797
20276
|
return persisted;
|
|
@@ -19803,7 +20282,7 @@ async function validateFileAgainstCasRecord(options) {
|
|
|
19803
20282
|
expectedHash: options.record.hash,
|
|
19804
20283
|
label: options.label
|
|
19805
20284
|
});
|
|
19806
|
-
if (!
|
|
20285
|
+
if (!sameFileObject2(options.record.identity, current.identity)) {
|
|
19807
20286
|
throw new Error(`${options.label} object identity changed`);
|
|
19808
20287
|
}
|
|
19809
20288
|
}
|
|
@@ -19840,20 +20319,20 @@ async function restoreUnexpectedQuarantine(options) {
|
|
|
19840
20319
|
});
|
|
19841
20320
|
await guard.verify();
|
|
19842
20321
|
try {
|
|
19843
|
-
await
|
|
20322
|
+
await fs20.link(options.quarantine, options.target);
|
|
19844
20323
|
} catch (error) {
|
|
19845
20324
|
if (error.code === "EEXIST") return;
|
|
19846
20325
|
throw error;
|
|
19847
20326
|
}
|
|
19848
20327
|
await guard.verify();
|
|
19849
20328
|
const [quarantine, target] = await Promise.all([
|
|
19850
|
-
|
|
19851
|
-
|
|
20329
|
+
fs20.lstat(options.quarantine),
|
|
20330
|
+
fs20.lstat(options.target)
|
|
19852
20331
|
]);
|
|
19853
|
-
if (!
|
|
20332
|
+
if (!sameFileObject2(fileObjectIdentity(quarantine), fileObjectIdentity(target))) {
|
|
19854
20333
|
throw new Error(`Archive CAS ${options.operation.id} could not restore quarantined content`);
|
|
19855
20334
|
}
|
|
19856
|
-
await
|
|
20335
|
+
await fs20.unlink(options.quarantine);
|
|
19857
20336
|
await guard.verify();
|
|
19858
20337
|
}
|
|
19859
20338
|
async function quarantineBoundTarget(options) {
|
|
@@ -19889,7 +20368,7 @@ async function quarantineBoundTarget(options) {
|
|
|
19889
20368
|
label: `Archive ${options.phase} target ${options.operation.target}`
|
|
19890
20369
|
});
|
|
19891
20370
|
try {
|
|
19892
|
-
await
|
|
20371
|
+
await fs20.rename(options.target, options.quarantine);
|
|
19893
20372
|
await guard.verify();
|
|
19894
20373
|
await validateFileAgainstCasRecord({
|
|
19895
20374
|
paths: options.paths,
|
|
@@ -19921,7 +20400,7 @@ async function removeExactCasFile(options) {
|
|
|
19921
20400
|
});
|
|
19922
20401
|
await guard.verify();
|
|
19923
20402
|
await validateFileAgainstCasRecord(options);
|
|
19924
|
-
await
|
|
20403
|
+
await fs20.unlink(options.file);
|
|
19925
20404
|
await guard.verify();
|
|
19926
20405
|
}
|
|
19927
20406
|
async function ensureBackup(paths, operation, hooks) {
|
|
@@ -20114,7 +20593,7 @@ async function ensureWriteInstalled(options) {
|
|
|
20114
20593
|
expectedHash,
|
|
20115
20594
|
label: `Archive write target ${options.operation.target}`
|
|
20116
20595
|
});
|
|
20117
|
-
if (!
|
|
20596
|
+
if (!sameFileObject2(candidateIdentity2.identity, targetIdentity2.identity)) {
|
|
20118
20597
|
throw new Error(
|
|
20119
20598
|
`Archive write target ${options.operation.target} is occupied by an external object`
|
|
20120
20599
|
);
|
|
@@ -20169,7 +20648,7 @@ async function ensureWriteInstalled(options) {
|
|
|
20169
20648
|
label: `Archive write candidate ${options.operation.target}`
|
|
20170
20649
|
});
|
|
20171
20650
|
try {
|
|
20172
|
-
await
|
|
20651
|
+
await fs20.link(candidate, options.target);
|
|
20173
20652
|
} catch (error) {
|
|
20174
20653
|
if (error.code === "EEXIST") {
|
|
20175
20654
|
throw new Error(
|
|
@@ -20187,7 +20666,7 @@ async function ensureWriteInstalled(options) {
|
|
|
20187
20666
|
expectedHash,
|
|
20188
20667
|
label: `Archive write target ${options.operation.target}`
|
|
20189
20668
|
});
|
|
20190
|
-
if (!
|
|
20669
|
+
if (!sameFileObject2(candidateIdentity.identity, targetIdentity.identity)) {
|
|
20191
20670
|
throw new Error(`Archive write target ${options.operation.target} changed during install`);
|
|
20192
20671
|
}
|
|
20193
20672
|
record8 = await persistCasRecord({
|
|
@@ -20273,8 +20752,8 @@ async function applyMove(paths, operation) {
|
|
|
20273
20752
|
`Archive transaction move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
|
|
20274
20753
|
);
|
|
20275
20754
|
}
|
|
20276
|
-
await
|
|
20277
|
-
await
|
|
20755
|
+
await fs20.mkdir(path31.dirname(target), { recursive: true });
|
|
20756
|
+
await fs20.rename(source, target);
|
|
20278
20757
|
await assertContent({
|
|
20279
20758
|
target,
|
|
20280
20759
|
expectedHash: operation.expectedSourceHash,
|
|
@@ -20476,7 +20955,7 @@ async function installOriginalTarget(options) {
|
|
|
20476
20955
|
label: `Archive rollback original quarantine ${options.operation.target}`
|
|
20477
20956
|
});
|
|
20478
20957
|
try {
|
|
20479
|
-
await
|
|
20958
|
+
await fs20.link(options.quarantine, options.target);
|
|
20480
20959
|
} catch (error) {
|
|
20481
20960
|
if (error.code === "EEXIST") {
|
|
20482
20961
|
throw new Error(
|
|
@@ -20556,7 +21035,7 @@ async function rollbackWriteOrRemove(paths, journal, operation, hooks) {
|
|
|
20556
21035
|
expectedHash: operation.stagedHash,
|
|
20557
21036
|
label: `Archive rollback target ${operation.target}`
|
|
20558
21037
|
});
|
|
20559
|
-
if (
|
|
21038
|
+
if (sameFileObject2(candidate.identity, current.identity)) {
|
|
20560
21039
|
postRecord = await persistCasRecord({
|
|
20561
21040
|
paths,
|
|
20562
21041
|
file: cas.postRecord,
|
|
@@ -20670,8 +21149,8 @@ async function rollbackMove(paths, operation) {
|
|
|
20670
21149
|
`Archive rollback move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
|
|
20671
21150
|
);
|
|
20672
21151
|
}
|
|
20673
|
-
await
|
|
20674
|
-
await
|
|
21152
|
+
await fs20.mkdir(path31.dirname(source), { recursive: true });
|
|
21153
|
+
await fs20.rename(target, source);
|
|
20675
21154
|
}
|
|
20676
21155
|
async function rollbackNativeArchiveTransactionV2(paths, journal, hooks) {
|
|
20677
21156
|
const events = await readNativeTransactionEvents(paths, journal.id);
|
|
@@ -20769,7 +21248,7 @@ async function finalizeNativeArchiveTransactionV2(paths, journal, event) {
|
|
|
20769
21248
|
|
|
20770
21249
|
// domains/comet-native/native-checkpoint-journal.ts
|
|
20771
21250
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
20772
|
-
import { promises as
|
|
21251
|
+
import { promises as fs21 } from "fs";
|
|
20773
21252
|
|
|
20774
21253
|
// domains/comet-native/native-checkpoint-storage.ts
|
|
20775
21254
|
import path32 from "path";
|
|
@@ -21305,7 +21784,7 @@ async function continueNativeCheckpointLocked(paths, name, hooks) {
|
|
|
21305
21784
|
await hooks?.afterStateWritten?.(journal);
|
|
21306
21785
|
await writeNativeProgressCheckpoint(paths, journal.checkpoint);
|
|
21307
21786
|
await hooks?.afterProgressWritten?.(journal);
|
|
21308
|
-
await
|
|
21787
|
+
await fs21.rm(nativeCheckpointJournalFile(paths, name), { force: true });
|
|
21309
21788
|
return journal;
|
|
21310
21789
|
}
|
|
21311
21790
|
async function continueNativeCheckpoint(paths, name, hooks) {
|
|
@@ -21327,7 +21806,7 @@ async function settleNativeChangeJournalsLocked(paths, name) {
|
|
|
21327
21806
|
|
|
21328
21807
|
// domains/comet-entry/current-selection.ts
|
|
21329
21808
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
21330
|
-
import { promises as
|
|
21809
|
+
import { promises as fs22 } from "fs";
|
|
21331
21810
|
import path33 from "path";
|
|
21332
21811
|
var COMET_CURRENT_SELECTION_SCHEMA = "comet.selection.v2";
|
|
21333
21812
|
var COMET_CURRENT_SELECTION_MAX_BYTES = 16 * 1024;
|
|
@@ -21390,16 +21869,11 @@ function parseSelection(source) {
|
|
|
21390
21869
|
async function readCometCurrentSelection(projectRoot) {
|
|
21391
21870
|
let source;
|
|
21392
21871
|
try {
|
|
21393
|
-
const
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
}
|
|
21397
|
-
|
|
21398
|
-
throw new Error(
|
|
21399
|
-
`current change selection exceeds ${COMET_CURRENT_SELECTION_MAX_BYTES} bytes`
|
|
21400
|
-
);
|
|
21401
|
-
}
|
|
21402
|
-
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");
|
|
21403
21877
|
} catch (error) {
|
|
21404
21878
|
if (error.code === "ENOENT") return { status: "missing" };
|
|
21405
21879
|
throw new Error(
|
|
@@ -21415,18 +21889,18 @@ async function writeCometCurrentSelection(projectRoot, selection) {
|
|
|
21415
21889
|
if (parsed.legacy) throw new Error("cannot write a legacy current change selection");
|
|
21416
21890
|
const file = cometCurrentSelectionFile(projectRoot);
|
|
21417
21891
|
const temporary = `${file}.${randomUUID5()}.tmp`;
|
|
21418
|
-
await
|
|
21892
|
+
await fs22.mkdir(path33.dirname(file), { recursive: true });
|
|
21419
21893
|
try {
|
|
21420
|
-
await
|
|
21894
|
+
await fs22.writeFile(temporary, `${JSON.stringify(parsed.selection, null, 2)}
|
|
21421
21895
|
`, "utf8");
|
|
21422
|
-
await
|
|
21896
|
+
await fs22.rename(temporary, file);
|
|
21423
21897
|
} catch (error) {
|
|
21424
|
-
await
|
|
21898
|
+
await fs22.rm(temporary, { force: true });
|
|
21425
21899
|
throw error;
|
|
21426
21900
|
}
|
|
21427
21901
|
}
|
|
21428
21902
|
async function clearCometCurrentSelection(projectRoot) {
|
|
21429
|
-
await
|
|
21903
|
+
await fs22.rm(cometCurrentSelectionFile(projectRoot), { force: true });
|
|
21430
21904
|
}
|
|
21431
21905
|
async function clearCometCurrentSelectionIf(projectRoot, workflow, change) {
|
|
21432
21906
|
const current = await readCometCurrentSelection(projectRoot);
|
|
@@ -21540,7 +22014,7 @@ function archiveTarget(paths, name, now) {
|
|
|
21540
22014
|
}
|
|
21541
22015
|
async function pathExists2(target) {
|
|
21542
22016
|
try {
|
|
21543
|
-
await
|
|
22017
|
+
await fs23.access(target);
|
|
21544
22018
|
return true;
|
|
21545
22019
|
} catch (error) {
|
|
21546
22020
|
if (error.code === "ENOENT") return false;
|
|
@@ -21851,7 +22325,7 @@ async function recoverArchiveTransaction(options) {
|
|
|
21851
22325
|
}
|
|
21852
22326
|
|
|
21853
22327
|
// domains/comet-native/native-diagnostics.ts
|
|
21854
|
-
import { promises as
|
|
22328
|
+
import { promises as fs24 } from "fs";
|
|
21855
22329
|
|
|
21856
22330
|
// domains/comet-native/native-run-consistency.ts
|
|
21857
22331
|
import path35 from "path";
|
|
@@ -21977,8 +22451,12 @@ async function inspectNativeRunConsistency(paths, state) {
|
|
|
21977
22451
|
// domains/comet-native/native-continuation.ts
|
|
21978
22452
|
var REPAIR_CODES = /^(?:run-|trajectory-|checkpoint-(?:missing|mismatch|invalid|progress-invalid)|transition-(?:incomplete|invalid))/u;
|
|
21979
22453
|
function requiredPhaseInputs(state) {
|
|
21980
|
-
if (state.phase === "shape")
|
|
21981
|
-
|
|
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
|
+
}
|
|
21982
22460
|
if (state.phase === "verify") return ["summary", "verification-result", "verification-report"];
|
|
21983
22461
|
return [];
|
|
21984
22462
|
}
|
|
@@ -22109,6 +22587,7 @@ function nativeContinuation(options) {
|
|
|
22109
22587
|
requiredInputs: options.archiveReady ? [] : ["archive-readiness"]
|
|
22110
22588
|
};
|
|
22111
22589
|
}
|
|
22590
|
+
const confirmationSuffix = options.state.phase === "shape" || options.state.phase === "build" && options.state.approval !== "confirmed" ? " --confirmed" : "";
|
|
22112
22591
|
return {
|
|
22113
22592
|
schema: "comet.native.continuation.v1",
|
|
22114
22593
|
skill: "comet-native",
|
|
@@ -22117,7 +22596,7 @@ function nativeContinuation(options) {
|
|
|
22117
22596
|
revision: options.state.revision,
|
|
22118
22597
|
disposition: "continue",
|
|
22119
22598
|
action: "advance-phase",
|
|
22120
|
-
command: `comet native next ${options.state.name} --summary "<summary>"`,
|
|
22599
|
+
command: `comet native next ${options.state.name} --summary "<summary>"${confirmationSuffix}`,
|
|
22121
22600
|
requiresUserDecision: false,
|
|
22122
22601
|
requiredInputs: requiredPhaseInputs(options.state)
|
|
22123
22602
|
};
|
|
@@ -22133,6 +22612,18 @@ var EXACT_METADATA = {
|
|
|
22133
22612
|
retry: "next",
|
|
22134
22613
|
repair: "none"
|
|
22135
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
|
+
},
|
|
22136
22627
|
"transition-incomplete": {
|
|
22137
22628
|
severity: "error",
|
|
22138
22629
|
requiredAction: "recover-transition",
|
|
@@ -22304,7 +22795,7 @@ function projectRelativePath3(paths, state, finding) {
|
|
|
22304
22795
|
}
|
|
22305
22796
|
function retryCommand(retry, state, code) {
|
|
22306
22797
|
if (retry === "next") {
|
|
22307
|
-
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" : ""}`;
|
|
22308
22799
|
}
|
|
22309
22800
|
if (retry === "status") return `comet native status ${state.name} --details`;
|
|
22310
22801
|
return null;
|
|
@@ -22322,7 +22813,7 @@ function structureNativeFindings(options) {
|
|
|
22322
22813
|
repairCommand: metadata.repair === "doctor" ? `comet native doctor ${options.state.name} --repair${finding.code.startsWith("transition-") ? " --strategy continue" : ""}` : null,
|
|
22323
22814
|
// This is intentionally code-based, not severity-based. Model-actionable
|
|
22324
22815
|
// missing data must never be presented as a user decision.
|
|
22325
|
-
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"
|
|
22326
22817
|
};
|
|
22327
22818
|
}).sort((left, right) => {
|
|
22328
22819
|
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
@@ -22636,11 +23127,11 @@ async function selectedName(paths) {
|
|
|
22636
23127
|
return null;
|
|
22637
23128
|
}
|
|
22638
23129
|
}
|
|
22639
|
-
function nativeNextCommand(state, archiveReady, evidenceRetreat = false) {
|
|
23130
|
+
function nativeNextCommand(state, archiveReady, evidenceRetreat = false, _clarificationMode) {
|
|
22640
23131
|
if (state.phase === "archive") {
|
|
22641
23132
|
return archiveReady ? `comet native archive ${state.name} --dry-run` : evidenceRetreat ? `comet native next ${state.name} --summary "<summary>"` : null;
|
|
22642
23133
|
}
|
|
22643
|
-
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" : ""}`;
|
|
22644
23135
|
}
|
|
22645
23136
|
async function statusFindings(paths, state) {
|
|
22646
23137
|
const changeDir = nativeChangeDir(paths, state.name);
|
|
@@ -22971,13 +23462,19 @@ async function inspectNativeStatus(paths, name, options) {
|
|
|
22971
23462
|
verificationResult: state.verification_result,
|
|
22972
23463
|
specChanges: state.spec_changes.length,
|
|
22973
23464
|
selected,
|
|
22974
|
-
nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat),
|
|
23465
|
+
nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat, options?.clarificationMode),
|
|
22975
23466
|
archiveReady,
|
|
22976
23467
|
inspection: resume.inspection,
|
|
22977
23468
|
findingSummary: summarizeNativeFindings(findings),
|
|
22978
23469
|
detailsCommand: `comet native status ${state.name} --details`,
|
|
22979
23470
|
checkpoint: resume.checkpoint,
|
|
22980
|
-
continuation: nativeContinuation({
|
|
23471
|
+
continuation: nativeContinuation({
|
|
23472
|
+
state,
|
|
23473
|
+
findings,
|
|
23474
|
+
archiveReady,
|
|
23475
|
+
evidenceRetreat,
|
|
23476
|
+
clarificationMode: options?.clarificationMode
|
|
23477
|
+
}),
|
|
22981
23478
|
repair,
|
|
22982
23479
|
...options?.details ? {
|
|
22983
23480
|
...acceptancePage ? { acceptancePage } : {},
|
|
@@ -23011,7 +23508,7 @@ async function boundedNativeChangeNames(paths) {
|
|
|
23011
23508
|
throw error;
|
|
23012
23509
|
}
|
|
23013
23510
|
const names = [];
|
|
23014
|
-
const directory = await
|
|
23511
|
+
const directory = await fs24.opendir(paths.changesDir);
|
|
23015
23512
|
try {
|
|
23016
23513
|
for await (const entry2 of directory) {
|
|
23017
23514
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
@@ -23060,7 +23557,11 @@ async function listNativeStatusPage(paths, options) {
|
|
|
23060
23557
|
cursor: options?.cursor
|
|
23061
23558
|
});
|
|
23062
23559
|
const candidates = await Promise.all(
|
|
23063
|
-
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
|
+
)
|
|
23064
23565
|
);
|
|
23065
23566
|
const items = [];
|
|
23066
23567
|
for (const candidate of candidates) {
|
|
@@ -23092,23 +23593,25 @@ async function listNativeStatusPage(paths, options) {
|
|
|
23092
23593
|
limits: { ...NATIVE_STATUS_PAGE_LIMITS }
|
|
23093
23594
|
};
|
|
23094
23595
|
}
|
|
23095
|
-
async function listNativeStatus(paths) {
|
|
23096
|
-
return (await listNativeStatusPage(paths
|
|
23596
|
+
async function listNativeStatus(paths, options) {
|
|
23597
|
+
return (await listNativeStatusPage(paths, {
|
|
23598
|
+
clarificationMode: options?.clarificationMode
|
|
23599
|
+
})).items;
|
|
23097
23600
|
}
|
|
23098
23601
|
|
|
23099
23602
|
// domains/comet-native/native-doctor.ts
|
|
23100
|
-
import { promises as
|
|
23603
|
+
import { promises as fs28 } from "fs";
|
|
23101
23604
|
import path41 from "path";
|
|
23102
23605
|
|
|
23103
23606
|
// domains/comet-native/native-evidence-retention.ts
|
|
23104
23607
|
import { createHash as createHash13, randomUUID as randomUUID8 } from "node:crypto";
|
|
23105
|
-
import { constants as
|
|
23608
|
+
import { constants as fsConstants5, promises as fs26 } from "node:fs";
|
|
23106
23609
|
import path39 from "node:path";
|
|
23107
23610
|
|
|
23108
23611
|
// domains/comet-native/native-schema-migration.ts
|
|
23109
23612
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
23110
23613
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
23111
|
-
import { promises as
|
|
23614
|
+
import { promises as fs25 } from "fs";
|
|
23112
23615
|
import path38 from "path";
|
|
23113
23616
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
23114
23617
|
var HASH_PATTERN14 = /^[a-f0-9]{64}$/u;
|
|
@@ -23591,7 +24094,7 @@ async function continueTransitionSupersede(paths, journal, hooks) {
|
|
|
23591
24094
|
if (transitionHash !== supersede.sourceHash) {
|
|
23592
24095
|
throw new Error(`Native superseded transition changed before removal for ${journal.change}`);
|
|
23593
24096
|
}
|
|
23594
|
-
await
|
|
24097
|
+
await fs25.rm(transitionFile);
|
|
23595
24098
|
await hooks?.afterTransitionSuperseded?.(journal);
|
|
23596
24099
|
}
|
|
23597
24100
|
}
|
|
@@ -23673,7 +24176,7 @@ async function continueNativeSchemaMigrationLocked(paths, name, hooks) {
|
|
|
23673
24176
|
}
|
|
23674
24177
|
await continueTransitionSupersede(paths, journal, hooks);
|
|
23675
24178
|
await continueRunRetreat(paths, journal, hooks);
|
|
23676
|
-
await
|
|
24179
|
+
await fs25.rm(nativeSchemaMigrationJournalFile(paths, name), { force: true });
|
|
23677
24180
|
return journal.nextState;
|
|
23678
24181
|
}
|
|
23679
24182
|
async function stableEvidenceRetreat(options) {
|
|
@@ -23979,7 +24482,13 @@ var MANAGED_KINDS = [
|
|
|
23979
24482
|
"check-receipts"
|
|
23980
24483
|
];
|
|
23981
24484
|
function sameObjectIdentity(left, right) {
|
|
23982
|
-
return
|
|
24485
|
+
return sameFileObject(
|
|
24486
|
+
{ ...left, birthtime: left.birthtimeMs },
|
|
24487
|
+
{
|
|
24488
|
+
...right,
|
|
24489
|
+
birthtime: right.birthtimeMs
|
|
24490
|
+
}
|
|
24491
|
+
);
|
|
23983
24492
|
}
|
|
23984
24493
|
function sameIdentity(left, right) {
|
|
23985
24494
|
return sameObjectIdentity(left, right) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.mtimeMs === right.mtimeMs && left.size === right.size;
|
|
@@ -24024,11 +24533,11 @@ async function captureDirectoryChain6(managedRoot, directory) {
|
|
|
24024
24533
|
let cursor = root;
|
|
24025
24534
|
for (const segment of ["", ...segments]) {
|
|
24026
24535
|
if (segment) cursor = path39.join(cursor, segment);
|
|
24027
|
-
const stat = await
|
|
24536
|
+
const stat = await fs26.lstat(cursor);
|
|
24028
24537
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
24029
24538
|
throw new Error(`Native evidence retention parent must be a real directory: ${cursor}`);
|
|
24030
24539
|
}
|
|
24031
|
-
const realPath = await
|
|
24540
|
+
const realPath = await fs26.realpath(cursor);
|
|
24032
24541
|
if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
|
|
24033
24542
|
throw new Error(`Native evidence retention parent resolves outside its change: ${cursor}`);
|
|
24034
24543
|
}
|
|
@@ -24038,8 +24547,8 @@ async function captureDirectoryChain6(managedRoot, directory) {
|
|
|
24038
24547
|
}
|
|
24039
24548
|
async function verifyDirectoryChain7(chain) {
|
|
24040
24549
|
for (const identity of chain) {
|
|
24041
|
-
const stat = await
|
|
24042
|
-
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) {
|
|
24043
24552
|
throw new Error(`Native evidence retention parent changed: ${identity.path}`);
|
|
24044
24553
|
}
|
|
24045
24554
|
}
|
|
@@ -24111,7 +24620,7 @@ function parseDocument4(kind, hash6, value, expectedChange) {
|
|
|
24111
24620
|
return { canonical: receipt, dependencies: [] };
|
|
24112
24621
|
}
|
|
24113
24622
|
async function readCanonicalDocument(options) {
|
|
24114
|
-
const before = await
|
|
24623
|
+
const before = await fs26.lstat(options.file);
|
|
24115
24624
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
24116
24625
|
throw new Error(`Native evidence entry is not a regular file: ${options.ref}`);
|
|
24117
24626
|
}
|
|
@@ -24119,12 +24628,12 @@ async function readCanonicalDocument(options) {
|
|
|
24119
24628
|
if (before.size > maximumBytes) {
|
|
24120
24629
|
throw new Error(`Native evidence entry exceeds its byte budget: ${options.ref}`);
|
|
24121
24630
|
}
|
|
24122
|
-
const beforeRealPath = await
|
|
24631
|
+
const beforeRealPath = await fs26.realpath(options.file);
|
|
24123
24632
|
if (!isInsidePath(options.directoryChain[0].realPath, beforeRealPath)) {
|
|
24124
24633
|
throw new Error(`Native evidence entry resolves outside its change: ${options.ref}`);
|
|
24125
24634
|
}
|
|
24126
|
-
const openFlags = process.platform === "win32" ? "r" :
|
|
24127
|
-
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) => {
|
|
24128
24637
|
if (error.code === "ELOOP" || error.code === "ENXIO") {
|
|
24129
24638
|
throw new Error(`Native evidence entry became unsafe while opening: ${options.ref}`);
|
|
24130
24639
|
}
|
|
@@ -24133,8 +24642,8 @@ async function readCanonicalDocument(options) {
|
|
|
24133
24642
|
try {
|
|
24134
24643
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
24135
24644
|
handle.stat(),
|
|
24136
|
-
|
|
24137
|
-
|
|
24645
|
+
fs26.lstat(options.file),
|
|
24646
|
+
fs26.realpath(options.file)
|
|
24138
24647
|
]);
|
|
24139
24648
|
await verifyDirectoryChain7(options.directoryChain);
|
|
24140
24649
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameIdentity(fileIdentity2(before), opened) || !sameIdentity(fileIdentity2(opened), pathAfterOpen)) {
|
|
@@ -24155,8 +24664,8 @@ async function readCanonicalDocument(options) {
|
|
|
24155
24664
|
}
|
|
24156
24665
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
24157
24666
|
handle.stat(),
|
|
24158
|
-
|
|
24159
|
-
|
|
24667
|
+
fs26.lstat(options.file),
|
|
24668
|
+
fs26.realpath(options.file)
|
|
24160
24669
|
]);
|
|
24161
24670
|
await verifyDirectoryChain7(options.directoryChain);
|
|
24162
24671
|
if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameIdentity(fileIdentity2(opened), afterHandle) || !sameIdentity(fileIdentity2(opened), afterPath)) {
|
|
@@ -24193,7 +24702,7 @@ async function readCanonicalDocument(options) {
|
|
|
24193
24702
|
}
|
|
24194
24703
|
async function pathIsMissing(file) {
|
|
24195
24704
|
try {
|
|
24196
|
-
await
|
|
24705
|
+
await fs26.lstat(file);
|
|
24197
24706
|
return false;
|
|
24198
24707
|
} catch (error) {
|
|
24199
24708
|
if (error.code === "ENOENT") return true;
|
|
@@ -24207,7 +24716,7 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24207
24716
|
let rootEntries;
|
|
24208
24717
|
try {
|
|
24209
24718
|
const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
|
|
24210
|
-
rootEntries = await
|
|
24719
|
+
rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
|
|
24211
24720
|
await verifyDirectoryChain7(rootChain);
|
|
24212
24721
|
} catch (error) {
|
|
24213
24722
|
if (error.code === "ENOENT") {
|
|
@@ -24220,7 +24729,7 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24220
24729
|
if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
|
|
24221
24730
|
const directory = path39.join(evidenceRoot, kind);
|
|
24222
24731
|
const directoryChain = await captureDirectoryChain6(changeRoot, directory);
|
|
24223
|
-
const entries = (await
|
|
24732
|
+
const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
|
|
24224
24733
|
(left, right) => compareText8(left.name, right.name)
|
|
24225
24734
|
);
|
|
24226
24735
|
const byOriginal = /* @__PURE__ */ new Map();
|
|
@@ -24323,20 +24832,20 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24323
24832
|
`Native evidence cleanup original or quarantine changed during recovery: ${recovery.ref}`
|
|
24324
24833
|
);
|
|
24325
24834
|
}
|
|
24326
|
-
await
|
|
24835
|
+
await fs26.rm(recovery.quarantine);
|
|
24327
24836
|
await verifyDirectoryChain7(recovery.document.directoryChain);
|
|
24328
24837
|
continue;
|
|
24329
24838
|
}
|
|
24330
24839
|
if (!await pathIsMissing(recovery.original)) {
|
|
24331
24840
|
throw new Error(`Native evidence cleanup original appeared during recovery: ${recovery.ref}`);
|
|
24332
24841
|
}
|
|
24333
|
-
const quarantineStat = await
|
|
24842
|
+
const quarantineStat = await fs26.lstat(recovery.quarantine);
|
|
24334
24843
|
if (!quarantineStat.isFile() || quarantineStat.isSymbolicLink() || !sameIdentity(recovery.document.identity, quarantineStat)) {
|
|
24335
24844
|
throw new Error(
|
|
24336
24845
|
`Native evidence cleanup quarantine changed during recovery: ${recovery.ref}`
|
|
24337
24846
|
);
|
|
24338
24847
|
}
|
|
24339
|
-
await
|
|
24848
|
+
await fs26.link(recovery.quarantine, recovery.original);
|
|
24340
24849
|
await options.hooks.afterRecoveryLink?.({
|
|
24341
24850
|
ref: recovery.ref,
|
|
24342
24851
|
original: recovery.original,
|
|
@@ -24363,7 +24872,7 @@ async function inspectCleanupQuarantines(options) {
|
|
|
24363
24872
|
if (!sameContentVersionAfterLinkOrRename(recovery.document.identity, restored.identity) || !sameIdentity(restored.identity, linkedQuarantine.identity)) {
|
|
24364
24873
|
throw new Error(`Native evidence cleanup recovery identity changed: ${recovery.ref}`);
|
|
24365
24874
|
}
|
|
24366
|
-
await
|
|
24875
|
+
await fs26.rm(recovery.quarantine);
|
|
24367
24876
|
await verifyDirectoryChain7(recovery.document.directoryChain);
|
|
24368
24877
|
}
|
|
24369
24878
|
return {
|
|
@@ -24387,7 +24896,7 @@ async function scanEvidenceStore(paths, name) {
|
|
|
24387
24896
|
let rootEntries;
|
|
24388
24897
|
try {
|
|
24389
24898
|
const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
|
|
24390
|
-
rootEntries = await
|
|
24899
|
+
rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
|
|
24391
24900
|
await verifyDirectoryChain7(rootChain);
|
|
24392
24901
|
} catch (error) {
|
|
24393
24902
|
if (error.code === "ENOENT") return [];
|
|
@@ -24407,7 +24916,7 @@ async function scanEvidenceStore(paths, name) {
|
|
|
24407
24916
|
if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
|
|
24408
24917
|
const directory = path39.join(evidenceRoot, kind);
|
|
24409
24918
|
const directoryChain = await captureDirectoryChain6(changeRoot, directory);
|
|
24410
|
-
const entries = (await
|
|
24919
|
+
const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
|
|
24411
24920
|
(left, right) => compareText8(left.name, right.name)
|
|
24412
24921
|
);
|
|
24413
24922
|
await verifyDirectoryChain7(directoryChain);
|
|
@@ -24551,13 +25060,13 @@ function summaryFinding(name, plan, repaired, evidenceRoot) {
|
|
|
24551
25060
|
}
|
|
24552
25061
|
async function restoreQuarantineIfSafe(quarantine, original) {
|
|
24553
25062
|
try {
|
|
24554
|
-
await
|
|
25063
|
+
await fs26.lstat(original);
|
|
24555
25064
|
return;
|
|
24556
25065
|
} catch (error) {
|
|
24557
25066
|
if (error.code !== "ENOENT") return;
|
|
24558
25067
|
}
|
|
24559
25068
|
try {
|
|
24560
|
-
await
|
|
25069
|
+
await fs26.rename(quarantine, original);
|
|
24561
25070
|
} catch {
|
|
24562
25071
|
}
|
|
24563
25072
|
}
|
|
@@ -24573,8 +25082,8 @@ async function deleteCandidate(candidate, hooks) {
|
|
|
24573
25082
|
name: candidate.change,
|
|
24574
25083
|
directoryChain: candidate.directoryChain
|
|
24575
25084
|
});
|
|
24576
|
-
const before = await
|
|
24577
|
-
const beforeRealPath = await
|
|
25085
|
+
const before = await fs26.lstat(candidate.file);
|
|
25086
|
+
const beforeRealPath = await fs26.realpath(candidate.file);
|
|
24578
25087
|
if (!before.isFile() || before.isSymbolicLink() || !sameIdentity(candidate.identity, before) || !sameIdentity(candidate.identity, beforeDocument.identity) || !isInsidePath(candidate.directoryChain[0].realPath, beforeRealPath)) {
|
|
24579
25088
|
throw new Error(`Native evidence candidate changed before cleanup: ${candidate.ref}`);
|
|
24580
25089
|
}
|
|
@@ -24582,18 +25091,18 @@ async function deleteCandidate(candidate, hooks) {
|
|
|
24582
25091
|
path39.dirname(candidate.file),
|
|
24583
25092
|
`.${path39.basename(candidate.file)}.${randomUUID8()}.gc`
|
|
24584
25093
|
);
|
|
24585
|
-
await
|
|
25094
|
+
await fs26.rename(candidate.file, quarantine);
|
|
24586
25095
|
try {
|
|
24587
25096
|
const [moved, movedRealPath] = await Promise.all([
|
|
24588
|
-
|
|
24589
|
-
|
|
25097
|
+
fs26.lstat(quarantine),
|
|
25098
|
+
fs26.realpath(quarantine)
|
|
24590
25099
|
]);
|
|
24591
25100
|
await verifyDirectoryChain7(candidate.directoryChain);
|
|
24592
25101
|
if (!moved.isFile() || moved.isSymbolicLink() || !sameContentVersionAfterLinkOrRename(candidate.identity, moved) || !isInsidePath(candidate.directoryChain[0].realPath, movedRealPath)) {
|
|
24593
25102
|
await restoreQuarantineIfSafe(quarantine, candidate.file);
|
|
24594
25103
|
throw new Error(`Native evidence candidate changed during cleanup: ${candidate.ref}`);
|
|
24595
25104
|
}
|
|
24596
|
-
await
|
|
25105
|
+
await fs26.rm(quarantine);
|
|
24597
25106
|
await verifyDirectoryChain7(candidate.directoryChain);
|
|
24598
25107
|
} catch (error) {
|
|
24599
25108
|
await restoreQuarantineIfSafe(quarantine, candidate.file);
|
|
@@ -24605,7 +25114,7 @@ async function hasPendingRelocationOrArchive(paths) {
|
|
|
24605
25114
|
if (config?.native.pending_root_move) return true;
|
|
24606
25115
|
let entries;
|
|
24607
25116
|
try {
|
|
24608
|
-
entries = await
|
|
25117
|
+
entries = await fs26.readdir(paths.transactionsDir, { withFileTypes: true });
|
|
24609
25118
|
} catch (error) {
|
|
24610
25119
|
if (error.code === "ENOENT") return false;
|
|
24611
25120
|
throw error;
|
|
@@ -24625,7 +25134,7 @@ async function changeNames(paths, requested) {
|
|
|
24625
25134
|
if (requested) return [requested];
|
|
24626
25135
|
let entries;
|
|
24627
25136
|
try {
|
|
24628
|
-
entries = await
|
|
25137
|
+
entries = await fs26.readdir(paths.changesDir, { withFileTypes: true });
|
|
24629
25138
|
} catch (error) {
|
|
24630
25139
|
if (error.code === "ENOENT") return [];
|
|
24631
25140
|
throw error;
|
|
@@ -24755,14 +25264,14 @@ async function inspectNativeEvidenceRetention(options) {
|
|
|
24755
25264
|
|
|
24756
25265
|
// domains/comet-native/native-root-move.ts
|
|
24757
25266
|
import { createHash as createHash14, randomUUID as randomUUID9 } from "crypto";
|
|
24758
|
-
import { promises as
|
|
25267
|
+
import { promises as fs27 } from "fs";
|
|
24759
25268
|
import path40 from "path";
|
|
24760
25269
|
var NATIVE_ROOT_MOVE_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
24761
25270
|
var NATIVE_ROOT_MOVE_MAX_JOURNAL_BYTES = 256 * 1024;
|
|
24762
25271
|
var NATIVE_ROOT_MOVE_MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
|
|
24763
25272
|
async function exists3(target) {
|
|
24764
25273
|
try {
|
|
24765
|
-
await
|
|
25274
|
+
await fs27.access(target);
|
|
24766
25275
|
return true;
|
|
24767
25276
|
} catch (error) {
|
|
24768
25277
|
if (error.code === "ENOENT") return false;
|
|
@@ -24772,7 +25281,7 @@ async function exists3(target) {
|
|
|
24772
25281
|
async function assertNoUnfinishedTransactions(paths) {
|
|
24773
25282
|
let entries;
|
|
24774
25283
|
try {
|
|
24775
|
-
entries = await
|
|
25284
|
+
entries = await fs27.readdir(paths.transactionsDir, { withFileTypes: true });
|
|
24776
25285
|
} catch (error) {
|
|
24777
25286
|
if (error.code === "ENOENT") return;
|
|
24778
25287
|
throw error;
|
|
@@ -24797,7 +25306,7 @@ async function assertNoUnfinishedTransactions(paths) {
|
|
|
24797
25306
|
}
|
|
24798
25307
|
}
|
|
24799
25308
|
async function assertNoOtherLocks(paths, ownedLock) {
|
|
24800
|
-
for (const entry2 of await
|
|
25309
|
+
for (const entry2 of await fs27.readdir(paths.locksDir, { withFileTypes: true })) {
|
|
24801
25310
|
const file = path40.join(paths.locksDir, entry2.name);
|
|
24802
25311
|
if (path40.resolve(file) === path40.resolve(ownedLock)) continue;
|
|
24803
25312
|
if (entry2.isFile() || entry2.isSymbolicLink()) {
|
|
@@ -24808,7 +25317,7 @@ async function assertNoOtherLocks(paths, ownedLock) {
|
|
|
24808
25317
|
async function refreshNativeWorkspaceIdentities(paths) {
|
|
24809
25318
|
let entries;
|
|
24810
25319
|
try {
|
|
24811
|
-
entries = await
|
|
25320
|
+
entries = await fs27.readdir(paths.changesDir, { withFileTypes: true });
|
|
24812
25321
|
} catch (error) {
|
|
24813
25322
|
if (error.code === "ENOENT") return;
|
|
24814
25323
|
throw error;
|
|
@@ -24848,7 +25357,7 @@ async function walkTree(root, options) {
|
|
|
24848
25357
|
await protectedDirectory.verify();
|
|
24849
25358
|
continue;
|
|
24850
25359
|
}
|
|
24851
|
-
const stat = await
|
|
25360
|
+
const stat = await fs27.lstat(target);
|
|
24852
25361
|
if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
|
|
24853
25362
|
if (options.rejectSymlinks) throw new Error(`Native root contains a symlink: ${target}`);
|
|
24854
25363
|
await protectedDirectory.verify();
|
|
@@ -24902,7 +25411,7 @@ async function copyTree(source, target, excludedFile, targetRoot) {
|
|
|
24902
25411
|
await protectedDirectory.verify();
|
|
24903
25412
|
continue;
|
|
24904
25413
|
}
|
|
24905
|
-
const stat = await
|
|
25414
|
+
const stat = await fs27.lstat(sourceEntry);
|
|
24906
25415
|
if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
|
|
24907
25416
|
throw new Error(`Native root contains a symlink: ${sourceEntry}`);
|
|
24908
25417
|
}
|
|
@@ -25349,7 +25858,7 @@ async function finishForwardMove(options) {
|
|
|
25349
25858
|
options.staging,
|
|
25350
25859
|
options.lockFile
|
|
25351
25860
|
);
|
|
25352
|
-
await
|
|
25861
|
+
await fs27.rename(options.staging, options.destinationPaths.nativeRoot);
|
|
25353
25862
|
}
|
|
25354
25863
|
config = await setPendingStage({
|
|
25355
25864
|
projectRoot: options.projectRoot,
|
|
@@ -25395,7 +25904,8 @@ async function finishForwardMove(options) {
|
|
|
25395
25904
|
const stableNative = {
|
|
25396
25905
|
artifact_root: config.native.artifact_root,
|
|
25397
25906
|
language: config.native.language,
|
|
25398
|
-
clarification_mode: config.native.clarification_mode
|
|
25907
|
+
clarification_mode: config.native.clarification_mode,
|
|
25908
|
+
snapshot: config.native.snapshot
|
|
25399
25909
|
};
|
|
25400
25910
|
const committed = {
|
|
25401
25911
|
...config,
|
|
@@ -25538,7 +26048,8 @@ async function recoverNativeRootMove(options) {
|
|
|
25538
26048
|
const stableNative = {
|
|
25539
26049
|
artifact_root: config.native.artifact_root,
|
|
25540
26050
|
language: config.native.language,
|
|
25541
|
-
clarification_mode: config.native.clarification_mode
|
|
26051
|
+
clarification_mode: config.native.clarification_mode,
|
|
26052
|
+
snapshot: config.native.snapshot
|
|
25542
26053
|
};
|
|
25543
26054
|
const restored = {
|
|
25544
26055
|
...config,
|
|
@@ -25669,7 +26180,7 @@ async function inspectSelection(paths, repair) {
|
|
|
25669
26180
|
}
|
|
25670
26181
|
}
|
|
25671
26182
|
if (repair) {
|
|
25672
|
-
await
|
|
26183
|
+
await fs28.rm(file, { force: true });
|
|
25673
26184
|
return [
|
|
25674
26185
|
{
|
|
25675
26186
|
severity: "info",
|
|
@@ -26256,7 +26767,7 @@ async function doctorNativeProject(options) {
|
|
|
26256
26767
|
|
|
26257
26768
|
// domains/comet-native/native-check-receipt.ts
|
|
26258
26769
|
import { createHash as createHash15 } from "node:crypto";
|
|
26259
|
-
import { constants as
|
|
26770
|
+
import { constants as fsConstants6, promises as fs29 } from "node:fs";
|
|
26260
26771
|
import path42 from "node:path";
|
|
26261
26772
|
import { TextDecoder as TextDecoder5 } from "node:util";
|
|
26262
26773
|
var ISSUE_KIND_RANK = {
|
|
@@ -26282,6 +26793,7 @@ function projectionManifest2(projection) {
|
|
|
26282
26793
|
createdAt: "1970-01-01T00:00:00.000Z",
|
|
26283
26794
|
complete: projection.complete,
|
|
26284
26795
|
limits: projection.limits,
|
|
26796
|
+
...projection.policy ? { policy: projection.policy } : {},
|
|
26285
26797
|
entries: projection.entries,
|
|
26286
26798
|
omitted: projection.omitted,
|
|
26287
26799
|
omittedCount: projection.omittedCount,
|
|
@@ -26289,16 +26801,17 @@ function projectionManifest2(projection) {
|
|
|
26289
26801
|
};
|
|
26290
26802
|
}
|
|
26291
26803
|
async function collectBoundFacts(options) {
|
|
26804
|
+
const baseline = projectionManifest2(options.scope.baseline);
|
|
26292
26805
|
const [contract, snapshot2] = await Promise.all([
|
|
26293
26806
|
collectNativeContractFiles({
|
|
26294
26807
|
changeDir: nativeChangeDir(options.paths, options.state.name),
|
|
26295
26808
|
briefRef: options.state.brief,
|
|
26296
26809
|
specChanges: options.state.spec_changes
|
|
26297
26810
|
}),
|
|
26298
|
-
|
|
26811
|
+
createNativeCurrentContentSnapshot(options.paths, baseline, { origin: "explicit" })
|
|
26299
26812
|
]);
|
|
26300
26813
|
const currentBundle = buildNativeImplementationScopeBundle({
|
|
26301
|
-
baseline
|
|
26814
|
+
baseline,
|
|
26302
26815
|
current: snapshot2,
|
|
26303
26816
|
contractHash: options.scope.authority.contractHash,
|
|
26304
26817
|
declaredArtifacts: options.scope.authority.declaredArtifacts,
|
|
@@ -26343,17 +26856,23 @@ function staleReasons(before, after, scope) {
|
|
|
26343
26856
|
}
|
|
26344
26857
|
function sameDirectoryIdentity7(identity, stat) {
|
|
26345
26858
|
const stableMetadata = identity.birthtimeMs === stat.birthtimeMs && identity.ctimeMs === stat.ctimeMs;
|
|
26346
|
-
|
|
26347
|
-
|
|
26348
|
-
|
|
26349
|
-
|
|
26859
|
+
return stableMetadata && sameFileObject(
|
|
26860
|
+
{ ...identity, birthtime: identity.birthtimeMs },
|
|
26861
|
+
{
|
|
26862
|
+
...stat,
|
|
26863
|
+
birthtime: stat.birthtimeMs
|
|
26864
|
+
}
|
|
26865
|
+
);
|
|
26350
26866
|
}
|
|
26351
26867
|
function sameFileIdentity8(left, right) {
|
|
26352
26868
|
const stableMetadata = left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.mtimeMs === right.mtimeMs && left.size === right.size;
|
|
26353
|
-
|
|
26354
|
-
|
|
26355
|
-
|
|
26356
|
-
|
|
26869
|
+
return stableMetadata && sameFileObject(
|
|
26870
|
+
{ ...left, birthtime: left.birthtimeMs },
|
|
26871
|
+
{
|
|
26872
|
+
...right,
|
|
26873
|
+
birthtime: right.birthtimeMs
|
|
26874
|
+
}
|
|
26875
|
+
);
|
|
26357
26876
|
}
|
|
26358
26877
|
async function captureProjectDirectoryChain(projectRoot, directory) {
|
|
26359
26878
|
const lexicalRoot = path42.resolve(projectRoot);
|
|
@@ -26370,7 +26889,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
|
|
|
26370
26889
|
if (segment) cursor = path42.join(cursor, segment);
|
|
26371
26890
|
let stat;
|
|
26372
26891
|
try {
|
|
26373
|
-
stat = await
|
|
26892
|
+
stat = await fs29.lstat(cursor);
|
|
26374
26893
|
} catch (error) {
|
|
26375
26894
|
if (error.code === "ENOENT") {
|
|
26376
26895
|
throw new ScopedFileError("scope-mismatch", "Scoped file parent no longer exists");
|
|
@@ -26380,7 +26899,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
|
|
|
26380
26899
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
26381
26900
|
throw new ScopedFileError("unsafe-file", "Scoped file parent is not a real directory");
|
|
26382
26901
|
}
|
|
26383
|
-
const realPath = await
|
|
26902
|
+
const realPath = await fs29.realpath(cursor);
|
|
26384
26903
|
if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
|
|
26385
26904
|
throw new ScopedFileError("unsafe-file", "Scoped file parent resolves outside the project");
|
|
26386
26905
|
}
|
|
@@ -26399,14 +26918,14 @@ async function verifyProjectDirectoryChain(chain) {
|
|
|
26399
26918
|
for (const identity of chain) {
|
|
26400
26919
|
let stat;
|
|
26401
26920
|
try {
|
|
26402
|
-
stat = await
|
|
26921
|
+
stat = await fs29.lstat(identity.path);
|
|
26403
26922
|
} catch (error) {
|
|
26404
26923
|
if (error.code === "ENOENT") {
|
|
26405
26924
|
throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
|
|
26406
26925
|
}
|
|
26407
26926
|
throw error;
|
|
26408
26927
|
}
|
|
26409
|
-
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) {
|
|
26410
26929
|
throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
|
|
26411
26930
|
}
|
|
26412
26931
|
}
|
|
@@ -26422,7 +26941,7 @@ async function readScopedFile(options) {
|
|
|
26422
26941
|
);
|
|
26423
26942
|
let before;
|
|
26424
26943
|
try {
|
|
26425
|
-
before = await
|
|
26944
|
+
before = await fs29.lstat(lexicalFile);
|
|
26426
26945
|
} catch (error) {
|
|
26427
26946
|
if (error.code === "ENOENT") {
|
|
26428
26947
|
throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
|
|
@@ -26438,12 +26957,12 @@ async function readScopedFile(options) {
|
|
|
26438
26957
|
"Scoped file size no longer matches its projection"
|
|
26439
26958
|
);
|
|
26440
26959
|
}
|
|
26441
|
-
const beforeRealPath = await
|
|
26960
|
+
const beforeRealPath = await fs29.realpath(lexicalFile);
|
|
26442
26961
|
if (!isInsidePath(physicalRoot, beforeRealPath)) {
|
|
26443
26962
|
throw new ScopedFileError("unsafe-file", "Scoped file resolves outside the project root");
|
|
26444
26963
|
}
|
|
26445
|
-
const openFlags = process.platform === "win32" ? "r" :
|
|
26446
|
-
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) => {
|
|
26447
26966
|
if (error.code === "ENOENT") {
|
|
26448
26967
|
throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
|
|
26449
26968
|
}
|
|
@@ -26455,8 +26974,8 @@ async function readScopedFile(options) {
|
|
|
26455
26974
|
try {
|
|
26456
26975
|
const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
|
|
26457
26976
|
handle.stat(),
|
|
26458
|
-
|
|
26459
|
-
|
|
26977
|
+
fs29.lstat(lexicalFile),
|
|
26978
|
+
fs29.realpath(lexicalFile)
|
|
26460
26979
|
]);
|
|
26461
26980
|
await verifyProjectDirectoryChain(chain);
|
|
26462
26981
|
if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity8(before, opened) || !sameFileIdentity8(opened, pathAfterOpen)) {
|
|
@@ -26483,8 +27002,8 @@ async function readScopedFile(options) {
|
|
|
26483
27002
|
}
|
|
26484
27003
|
const [afterHandle, afterPath, afterRealPath] = await Promise.all([
|
|
26485
27004
|
handle.stat(),
|
|
26486
|
-
|
|
26487
|
-
|
|
27005
|
+
fs29.lstat(lexicalFile),
|
|
27006
|
+
fs29.realpath(lexicalFile)
|
|
26488
27007
|
]);
|
|
26489
27008
|
await verifyProjectDirectoryChain(chain);
|
|
26490
27009
|
if (total !== options.file.expected.size || digest.digest("hex") !== options.file.expected.hash) {
|
|
@@ -26798,7 +27317,7 @@ async function checkpointNativeChange(options) {
|
|
|
26798
27317
|
}
|
|
26799
27318
|
|
|
26800
27319
|
// domains/comet-native/native-specs.ts
|
|
26801
|
-
import { promises as
|
|
27320
|
+
import { promises as fs30 } from "fs";
|
|
26802
27321
|
import path43 from "path";
|
|
26803
27322
|
var MAX_NATIVE_PROPOSED_SPEC_DIRECTORY_ENTRIES = NATIVE_CONTRACT_FILE_LIMITS.maxSpecs * 4;
|
|
26804
27323
|
async function optionalHash3(file) {
|
|
@@ -26824,7 +27343,7 @@ async function proposedCapabilities(paths, name) {
|
|
|
26824
27343
|
}
|
|
26825
27344
|
const capabilities = [];
|
|
26826
27345
|
let entryCount = 0;
|
|
26827
|
-
const directory = await
|
|
27346
|
+
const directory = await fs30.opendir(specsDir);
|
|
26828
27347
|
try {
|
|
26829
27348
|
for await (const entry2 of directory) {
|
|
26830
27349
|
entryCount += 1;
|
|
@@ -26842,7 +27361,7 @@ async function proposedCapabilities(paths, name) {
|
|
|
26842
27361
|
await resolveContainedNativePath(paths.nativeRoot, source);
|
|
26843
27362
|
let stat;
|
|
26844
27363
|
try {
|
|
26845
|
-
stat = await
|
|
27364
|
+
stat = await fs30.lstat(source);
|
|
26846
27365
|
} catch (error) {
|
|
26847
27366
|
if (error.code === "ENOENT") continue;
|
|
26848
27367
|
throw error;
|
|
@@ -27062,7 +27581,7 @@ async function readNativeProposedSpecs(paths, name) {
|
|
|
27062
27581
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
27063
27582
|
|
|
27064
27583
|
// domains/comet-native/native-guards.ts
|
|
27065
|
-
import { promises as
|
|
27584
|
+
import { promises as fs31 } from "fs";
|
|
27066
27585
|
import path44 from "path";
|
|
27067
27586
|
function validation(findings) {
|
|
27068
27587
|
return { valid: findings.length === 0, findings };
|
|
@@ -27095,7 +27614,7 @@ async function validateBuildArtifacts(paths, evidence) {
|
|
|
27095
27614
|
continue;
|
|
27096
27615
|
}
|
|
27097
27616
|
try {
|
|
27098
|
-
await
|
|
27617
|
+
await fs31.access(target);
|
|
27099
27618
|
} catch {
|
|
27100
27619
|
findings.push({
|
|
27101
27620
|
code: "build-artifact-missing",
|
|
@@ -27146,12 +27665,24 @@ async function inspectNativeGuard(options) {
|
|
|
27146
27665
|
const brief = await validateNativeBrief(changeDir, options.state.brief);
|
|
27147
27666
|
const specs = await validateNativeSpecChanges(options.paths, options.state);
|
|
27148
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
|
+
}
|
|
27149
27674
|
} else if (options.state.phase === "build") {
|
|
27150
27675
|
findings.push(
|
|
27151
27676
|
...(await validateNativeBrief(changeDir, options.state.brief)).findings,
|
|
27152
27677
|
...(await validateNativeSpecChanges(options.paths, options.state)).findings
|
|
27153
27678
|
);
|
|
27154
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
|
+
}
|
|
27155
27686
|
} else if (options.state.phase === "verify") {
|
|
27156
27687
|
const report = options.evidence.verificationReport ?? options.state.verification_report;
|
|
27157
27688
|
if (!report) {
|
|
@@ -27179,7 +27710,7 @@ async function inspectNativeGuard(options) {
|
|
|
27179
27710
|
}
|
|
27180
27711
|
|
|
27181
27712
|
// domains/comet-native/native-build-evidence.ts
|
|
27182
|
-
import { promises as
|
|
27713
|
+
import { promises as fs32 } from "node:fs";
|
|
27183
27714
|
import path45 from "node:path";
|
|
27184
27715
|
var NATIVE_BUILD_EVIDENCE_LIMITS = {
|
|
27185
27716
|
maxDeclaredArtifacts: 128,
|
|
@@ -27255,12 +27786,12 @@ async function inspectDeclaredArtifact(paths, baseline, rawRef) {
|
|
|
27255
27786
|
}
|
|
27256
27787
|
await resolveContainedNativePath(paths.projectRoot, target);
|
|
27257
27788
|
try {
|
|
27258
|
-
const stat = await
|
|
27789
|
+
const stat = await fs32.lstat(target);
|
|
27259
27790
|
if (stat.isSymbolicLink()) {
|
|
27260
27791
|
throw new Error(`Native build artifact must not be a symlink or junction: ${artifactRef}`);
|
|
27261
27792
|
}
|
|
27262
|
-
const realTarget = await
|
|
27263
|
-
const realProjectRoot = await
|
|
27793
|
+
const realTarget = await fs32.realpath(target);
|
|
27794
|
+
const realProjectRoot = await fs32.realpath(paths.projectRoot);
|
|
27264
27795
|
if (!isInsidePath(realProjectRoot, realTarget)) {
|
|
27265
27796
|
throw new Error(`Native build artifact resolves outside the project: ${artifactRef}`);
|
|
27266
27797
|
}
|
|
@@ -27330,7 +27861,7 @@ async function inspectNativeBuildEvidence(options) {
|
|
|
27330
27861
|
baseline,
|
|
27331
27862
|
refs: options.artifactRefs
|
|
27332
27863
|
});
|
|
27333
|
-
const current = await
|
|
27864
|
+
const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
|
|
27334
27865
|
origin: "explicit",
|
|
27335
27866
|
now: options.now
|
|
27336
27867
|
});
|
|
@@ -27528,7 +28059,8 @@ async function retreatStaleNativeEvidence(options) {
|
|
|
27528
28059
|
findings,
|
|
27529
28060
|
continuation: nativeContinuation({
|
|
27530
28061
|
state: options.state,
|
|
27531
|
-
archiveReady: previousPhase === "archive"
|
|
28062
|
+
archiveReady: previousPhase === "archive",
|
|
28063
|
+
clarificationMode: options.transition.clarificationMode
|
|
27532
28064
|
})
|
|
27533
28065
|
};
|
|
27534
28066
|
}
|
|
@@ -27583,7 +28115,10 @@ async function retreatStaleNativeEvidence(options) {
|
|
|
27583
28115
|
next: "auto",
|
|
27584
28116
|
nextCommand: null,
|
|
27585
28117
|
findings: [],
|
|
27586
|
-
continuation: nativeContinuation({
|
|
28118
|
+
continuation: nativeContinuation({
|
|
28119
|
+
state: persisted,
|
|
28120
|
+
clarificationMode: options.transition.clarificationMode
|
|
28121
|
+
})
|
|
27587
28122
|
};
|
|
27588
28123
|
}
|
|
27589
28124
|
async function advanceNativeChange(options) {
|
|
@@ -27630,7 +28165,8 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27630
28165
|
continuation: nativeContinuation({
|
|
27631
28166
|
state,
|
|
27632
28167
|
findings: repairFindings2,
|
|
27633
|
-
archiveReady: state.phase === "archive" && state.verification_result === "pass"
|
|
28168
|
+
archiveReady: state.phase === "archive" && state.verification_result === "pass",
|
|
28169
|
+
clarificationMode: options.clarificationMode
|
|
27634
28170
|
}),
|
|
27635
28171
|
...repair ? { repair } : {}
|
|
27636
28172
|
};
|
|
@@ -27669,7 +28205,8 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27669
28205
|
const guard = await inspectNativeGuard({
|
|
27670
28206
|
paths: options.paths,
|
|
27671
28207
|
state: candidate,
|
|
27672
|
-
evidence: options.evidence
|
|
28208
|
+
evidence: options.evidence,
|
|
28209
|
+
clarificationMode: options.clarificationMode
|
|
27673
28210
|
});
|
|
27674
28211
|
if (!guard.valid) {
|
|
27675
28212
|
const findings = structureNativeFindings({
|
|
@@ -27683,7 +28220,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27683
28220
|
next: "manual",
|
|
27684
28221
|
nextCommand: null,
|
|
27685
28222
|
findings,
|
|
27686
|
-
continuation: nativeContinuation({
|
|
28223
|
+
continuation: nativeContinuation({
|
|
28224
|
+
state,
|
|
28225
|
+
findings,
|
|
28226
|
+
clarificationMode: options.clarificationMode
|
|
28227
|
+
})
|
|
27687
28228
|
};
|
|
27688
28229
|
}
|
|
27689
28230
|
const shapeContract = state.phase === "shape" ? await collectNativeContractFiles({
|
|
@@ -27722,7 +28263,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27722
28263
|
next: "manual",
|
|
27723
28264
|
nextCommand: null,
|
|
27724
28265
|
findings,
|
|
27725
|
-
continuation: nativeContinuation({
|
|
28266
|
+
continuation: nativeContinuation({
|
|
28267
|
+
state,
|
|
28268
|
+
findings,
|
|
28269
|
+
clarificationMode: options.clarificationMode
|
|
28270
|
+
})
|
|
27726
28271
|
};
|
|
27727
28272
|
}
|
|
27728
28273
|
const preparedScope = buildEvidence ? {
|
|
@@ -27754,7 +28299,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27754
28299
|
next: "manual",
|
|
27755
28300
|
nextCommand: null,
|
|
27756
28301
|
findings,
|
|
27757
|
-
continuation: nativeContinuation({
|
|
28302
|
+
continuation: nativeContinuation({
|
|
28303
|
+
state,
|
|
28304
|
+
findings,
|
|
28305
|
+
clarificationMode: options.clarificationMode
|
|
28306
|
+
}),
|
|
27758
28307
|
preparedScope
|
|
27759
28308
|
};
|
|
27760
28309
|
}
|
|
@@ -27799,7 +28348,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27799
28348
|
next: "manual",
|
|
27800
28349
|
nextCommand: null,
|
|
27801
28350
|
findings,
|
|
27802
|
-
continuation: nativeContinuation({
|
|
28351
|
+
continuation: nativeContinuation({
|
|
28352
|
+
state,
|
|
28353
|
+
findings,
|
|
28354
|
+
clarificationMode: options.clarificationMode
|
|
28355
|
+
}),
|
|
27803
28356
|
preparedScope: preparedScope ? { ...preparedScope, partialAllowanceRef: null } : preparedScope
|
|
27804
28357
|
};
|
|
27805
28358
|
}
|
|
@@ -27828,7 +28381,11 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27828
28381
|
next: "manual",
|
|
27829
28382
|
nextCommand: null,
|
|
27830
28383
|
findings,
|
|
27831
|
-
continuation: nativeContinuation({
|
|
28384
|
+
continuation: nativeContinuation({
|
|
28385
|
+
state,
|
|
28386
|
+
findings,
|
|
28387
|
+
clarificationMode: options.clarificationMode
|
|
28388
|
+
})
|
|
27832
28389
|
};
|
|
27833
28390
|
}
|
|
27834
28391
|
let repairDecision = null;
|
|
@@ -27883,7 +28440,7 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27883
28440
|
...candidate,
|
|
27884
28441
|
revision: state.revision + 1,
|
|
27885
28442
|
phase: advanced.currentStep,
|
|
27886
|
-
approval: options.evidence.confirmed ? "confirmed" : state.
|
|
28443
|
+
approval: options.evidence.confirmed ? "confirmed" : state.approval,
|
|
27887
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,
|
|
27888
28445
|
run_id: run.runId,
|
|
27889
28446
|
...state.phase === "build" ? {
|
|
@@ -27960,7 +28517,8 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27960
28517
|
continuation: nativeContinuation({
|
|
27961
28518
|
state: persisted,
|
|
27962
28519
|
findings: repairFindings,
|
|
27963
|
-
archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass"
|
|
28520
|
+
archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass",
|
|
28521
|
+
clarificationMode: options.clarificationMode
|
|
27964
28522
|
}),
|
|
27965
28523
|
...preparedScope ? { preparedScope } : {},
|
|
27966
28524
|
...repairDecision ? { repair: repairDecision } : {}
|
|
@@ -27968,7 +28526,7 @@ async function advanceNativeChangeLocked(options) {
|
|
|
27968
28526
|
}
|
|
27969
28527
|
|
|
27970
28528
|
// domains/comet-native/native-hook-guard.ts
|
|
27971
|
-
import { promises as
|
|
28529
|
+
import { promises as fs33 } from "fs";
|
|
27972
28530
|
import path46 from "path";
|
|
27973
28531
|
|
|
27974
28532
|
// domains/comet-entry/hook-adapter.ts
|
|
@@ -28133,7 +28691,7 @@ async function activeNativeContext(projectRoot) {
|
|
|
28133
28691
|
const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root);
|
|
28134
28692
|
let entries;
|
|
28135
28693
|
try {
|
|
28136
|
-
entries = await
|
|
28694
|
+
entries = await fs33.readdir(paths.changesDir, { withFileTypes: true });
|
|
28137
28695
|
} catch (error) {
|
|
28138
28696
|
if (error.code === "ENOENT") return { paths, changes: [] };
|
|
28139
28697
|
throw error;
|
|
@@ -28254,6 +28812,7 @@ Commands:
|
|
|
28254
28812
|
select <change-name>
|
|
28255
28813
|
checkpoint <change-name> --summary <text> --next-action <text> [--artifact <project-relative>] [--expect-revision <n>]
|
|
28256
28814
|
check <change-name>
|
|
28815
|
+
evidence format [--entries <path>]
|
|
28257
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>]
|
|
28258
28817
|
archive <change-name> --dry-run
|
|
28259
28818
|
archive <change-name> --expect-preflight <sha256>
|
|
@@ -28334,6 +28893,44 @@ async function doctorPaths(projectRoot) {
|
|
|
28334
28893
|
function success(command, data, text) {
|
|
28335
28894
|
return { command, exitCode: 0, data, text: text ?? JSON.stringify(data, null, 2) + "\n" };
|
|
28336
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
|
+
}
|
|
28337
28934
|
async function dispatch(rawArgs, explicitProjectRoot) {
|
|
28338
28935
|
if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "help") {
|
|
28339
28936
|
return { command: rawArgs[0] ?? null, exitCode: 0, data: { usage: USAGE }, text: USAGE };
|
|
@@ -28439,7 +29036,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28439
29036
|
await ensureNativeDirectories(paths);
|
|
28440
29037
|
const state = await createNativeChange({ paths, name, language });
|
|
28441
29038
|
await selectNativeChange(paths, state.name);
|
|
28442
|
-
const status = await inspectNativeStatus(paths, state.name
|
|
29039
|
+
const status = await inspectNativeStatus(paths, state.name, {
|
|
29040
|
+
clarificationMode: config.native.clarification_mode
|
|
29041
|
+
});
|
|
28443
29042
|
return success(
|
|
28444
29043
|
"new",
|
|
28445
29044
|
{ ...state, continuation: status.continuation },
|
|
@@ -28453,9 +29052,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28453
29052
|
const name = requiredPositional(rawArgs, "change name");
|
|
28454
29053
|
const capability = requiredPositional(rawArgs, "capability");
|
|
28455
29054
|
assertNoArguments(rawArgs);
|
|
28456
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29055
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28457
29056
|
const state = await markNativeSpecRemoval(paths, name, capability);
|
|
28458
|
-
const status = await inspectNativeStatus(paths, state.name
|
|
29057
|
+
const status = await inspectNativeStatus(paths, state.name, {
|
|
29058
|
+
clarificationMode: config.native.clarification_mode
|
|
29059
|
+
});
|
|
28459
29060
|
return success(
|
|
28460
29061
|
"spec remove",
|
|
28461
29062
|
{ ...state, continuation: status.continuation },
|
|
@@ -28468,9 +29069,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28468
29069
|
const summary = takeOption(rawArgs, "--summary");
|
|
28469
29070
|
if (!summary) throw new NativeUsageError("--summary is required");
|
|
28470
29071
|
assertNoArguments(rawArgs);
|
|
28471
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29072
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28472
29073
|
const state = await rebaseNativeSpecChanges({ paths, name, summary });
|
|
28473
|
-
const status = await inspectNativeStatus(paths, state.name
|
|
29074
|
+
const status = await inspectNativeStatus(paths, state.name, {
|
|
29075
|
+
clarificationMode: config.native.clarification_mode
|
|
29076
|
+
});
|
|
28474
29077
|
return success(
|
|
28475
29078
|
"spec rebase",
|
|
28476
29079
|
{ ...state, continuation: status.continuation },
|
|
@@ -28483,8 +29086,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28483
29086
|
if (command === "list") {
|
|
28484
29087
|
const cursor = takeOption(rawArgs, "--cursor");
|
|
28485
29088
|
assertNoArguments(rawArgs);
|
|
28486
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
28487
|
-
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
|
+
});
|
|
28488
29094
|
return success("list", page);
|
|
28489
29095
|
}
|
|
28490
29096
|
if (command === "show") {
|
|
@@ -28540,19 +29146,25 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28540
29146
|
throw new NativeUsageError("--acceptance-cursor requires a change name");
|
|
28541
29147
|
}
|
|
28542
29148
|
assertNoArguments(rawArgs);
|
|
28543
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29149
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28544
29150
|
const data = name ? await inspectNativeStatus(paths, name, {
|
|
28545
29151
|
details,
|
|
28546
|
-
...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {}
|
|
28547
|
-
|
|
29152
|
+
...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {},
|
|
29153
|
+
clarificationMode: config.native.clarification_mode
|
|
29154
|
+
}) : await listNativeStatusPage(paths, {
|
|
29155
|
+
...cursor ? { cursor } : {},
|
|
29156
|
+
clarificationMode: config.native.clarification_mode
|
|
29157
|
+
});
|
|
28548
29158
|
return success("status", data);
|
|
28549
29159
|
}
|
|
28550
29160
|
if (command === "select") {
|
|
28551
29161
|
const name = requiredPositional(rawArgs, "change name");
|
|
28552
29162
|
assertNoArguments(rawArgs);
|
|
28553
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29163
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28554
29164
|
await selectNativeChange(paths, name);
|
|
28555
|
-
const status = await inspectNativeStatus(paths, name
|
|
29165
|
+
const status = await inspectNativeStatus(paths, name, {
|
|
29166
|
+
clarificationMode: config.native.clarification_mode
|
|
29167
|
+
});
|
|
28556
29168
|
return success(
|
|
28557
29169
|
"select",
|
|
28558
29170
|
{ selected: name, continuation: status.continuation },
|
|
@@ -28569,7 +29181,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28569
29181
|
const artifacts = takeMany(rawArgs, "--artifact");
|
|
28570
29182
|
const expectedRevision = revisionOption(rawArgs);
|
|
28571
29183
|
assertNoArguments(rawArgs);
|
|
28572
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29184
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28573
29185
|
const result2 = await checkpointNativeChange({
|
|
28574
29186
|
paths,
|
|
28575
29187
|
name,
|
|
@@ -28578,7 +29190,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28578
29190
|
artifacts,
|
|
28579
29191
|
expectedRevision
|
|
28580
29192
|
});
|
|
28581
|
-
const status = await inspectNativeStatus(paths, name
|
|
29193
|
+
const status = await inspectNativeStatus(paths, name, {
|
|
29194
|
+
clarificationMode: config.native.clarification_mode
|
|
29195
|
+
});
|
|
28582
29196
|
const manifestRef = path47.relative(
|
|
28583
29197
|
paths.projectRoot,
|
|
28584
29198
|
path47.join(nativeChangeDir(paths, name), ...result2.checkpoint.manifestRef.split("/"))
|
|
@@ -28617,6 +29231,43 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28617
29231
|
`
|
|
28618
29232
|
};
|
|
28619
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
|
+
}
|
|
28620
29271
|
if (command === "next") {
|
|
28621
29272
|
const name = requiredPositional(rawArgs, "change name");
|
|
28622
29273
|
const summary = takeOption(rawArgs, "--summary");
|
|
@@ -28665,7 +29316,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28665
29316
|
throw new NativeUsageError("--override-repair cannot be combined with --result");
|
|
28666
29317
|
}
|
|
28667
29318
|
assertNoArguments(rawArgs);
|
|
28668
|
-
const { paths } = await configuredPaths(projectRoot);
|
|
29319
|
+
const { config, paths } = await configuredPaths(projectRoot);
|
|
28669
29320
|
const evidence = {
|
|
28670
29321
|
summary,
|
|
28671
29322
|
...confirmed ? { confirmed: true } : {},
|
|
@@ -28681,7 +29332,12 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28681
29332
|
...repairOverrideSignature ? { repairOverrideSignature } : {},
|
|
28682
29333
|
...repairOverrideSummary ? { repairOverrideSummary } : {}
|
|
28683
29334
|
};
|
|
28684
|
-
const result2 = await advanceNativeChange({
|
|
29335
|
+
const result2 = await advanceNativeChange({
|
|
29336
|
+
paths,
|
|
29337
|
+
name,
|
|
29338
|
+
evidence,
|
|
29339
|
+
clarificationMode: config.native.clarification_mode
|
|
29340
|
+
});
|
|
28685
29341
|
if (result2.next === "manual") {
|
|
28686
29342
|
const repairBlocked = result2.repair?.disposition === "manual-stop" || result2.repair?.disposition === "hard-stop" || result2.findings.some(
|
|
28687
29343
|
(finding) => [
|
|
@@ -28700,7 +29356,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
|
|
|
28700
29356
|
}
|
|
28701
29357
|
};
|
|
28702
29358
|
}
|
|
28703
|
-
const status = await inspectNativeStatus(paths, name
|
|
29359
|
+
const status = await inspectNativeStatus(paths, name, {
|
|
29360
|
+
clarificationMode: config.native.clarification_mode
|
|
29361
|
+
});
|
|
28704
29362
|
return success("next", { ...result2, continuation: status.continuation });
|
|
28705
29363
|
}
|
|
28706
29364
|
if (command === "archive") {
|
|
@@ -28817,6 +29475,13 @@ function errorResult(command, error) {
|
|
|
28817
29475
|
omittedByReason: error.omittedByReason,
|
|
28818
29476
|
samplePaths: error.samplePaths,
|
|
28819
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
|
+
],
|
|
28820
29485
|
requiredAction: "resolve-native-baseline"
|
|
28821
29486
|
},
|
|
28822
29487
|
error: { code: "baseline-incomplete", message: error.message }
|