@gajae-code/coding-agent 0.15.6 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +13 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +10 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +77 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/session-runtime.d.ts +52 -0
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +5 -0
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +6 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +77 -11
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +1 -5
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +23 -0
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +8 -1
- package/src/config/settings.ts +5 -0
- package/src/coordinator-mcp/projection-scan.ts +21 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +94 -20
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +79 -6
- package/src/gjc-runtime/session-state-sidecar.ts +929 -23
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +57 -2
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +605 -156
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/input-controller.ts +28 -0
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/prompts/system/master-mode.md +28 -0
- package/src/sdk/broker/broker.ts +1636 -26
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +186 -41
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +35 -4
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +264 -5
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +99 -38
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/session-runtime.test.ts +210 -6
- package/src/sdk/host/session-runtime.ts +291 -49
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +13 -3
- package/src/sdk/router/session-router.ts +67 -59
- package/src/sdk/session.ts +357 -155
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/agent-session.ts +640 -103
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +337 -24
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/bash.ts +256 -42
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +4 -0
|
@@ -6,8 +6,13 @@ import { exactUnlink } from "@gajae-code/natives";
|
|
|
6
6
|
import { isEnoent, logger, postmortem } from "@gajae-code/utils";
|
|
7
7
|
|
|
8
8
|
const BLOB_PREFIX = "blob:sha256:";
|
|
9
|
+
const CANONICAL_BLOB_NAME = /^[0-9a-f]{64}$/;
|
|
9
10
|
const TAKE_BLOB_BUFFER_OWNERSHIP = Symbol("takeBlobBufferOwnership");
|
|
10
11
|
|
|
12
|
+
function isCanonicalBlobHash(hash: string): boolean {
|
|
13
|
+
return hash.length === 64 && CANONICAL_BLOB_NAME.test(hash);
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
/**
|
|
12
17
|
* Owner-only permissions for on-disk blob directories and files.
|
|
13
18
|
*
|
|
@@ -1126,6 +1131,7 @@ export class BlobStore {
|
|
|
1126
1131
|
|
|
1127
1132
|
/** Read blob by hash, returns Buffer or null if not found. */
|
|
1128
1133
|
async get(hash: string): Promise<Buffer | null> {
|
|
1134
|
+
if (!isCanonicalBlobHash(hash)) return null;
|
|
1129
1135
|
const blobPath = path.join(this.dir, hash);
|
|
1130
1136
|
try {
|
|
1131
1137
|
const file = Bun.file(blobPath);
|
|
@@ -1139,6 +1145,7 @@ export class BlobStore {
|
|
|
1139
1145
|
|
|
1140
1146
|
/** Synchronously read blob by hash, returns Buffer or null if not found. */
|
|
1141
1147
|
getSync(hash: string): Buffer | null {
|
|
1148
|
+
if (!isCanonicalBlobHash(hash)) return null;
|
|
1142
1149
|
const blobPath = path.join(this.dir, hash);
|
|
1143
1150
|
try {
|
|
1144
1151
|
return fs.readFileSync(blobPath);
|
|
@@ -1155,6 +1162,7 @@ export class BlobStore {
|
|
|
1155
1162
|
|
|
1156
1163
|
/** Synchronously read blob by hash and verify its content hash; returns null if not found. */
|
|
1157
1164
|
getCheckedSync(hash: string): Buffer | null {
|
|
1165
|
+
if (!isCanonicalBlobHash(hash)) return null;
|
|
1158
1166
|
const blobPath = path.join(this.dir, hash);
|
|
1159
1167
|
try {
|
|
1160
1168
|
return verifyBlobFileSync(hash, blobPath);
|
|
@@ -1166,6 +1174,7 @@ export class BlobStore {
|
|
|
1166
1174
|
|
|
1167
1175
|
/** Check if a blob exists. */
|
|
1168
1176
|
async has(hash: string): Promise<boolean> {
|
|
1177
|
+
if (!isCanonicalBlobHash(hash)) return false;
|
|
1169
1178
|
try {
|
|
1170
1179
|
await fsp.access(path.join(this.dir, hash));
|
|
1171
1180
|
return true;
|
|
@@ -1321,6 +1330,7 @@ export class EphemeralBlobStore extends BlobStore {
|
|
|
1321
1330
|
}
|
|
1322
1331
|
|
|
1323
1332
|
getSync(hash: string): Buffer | null {
|
|
1333
|
+
if (!isCanonicalBlobHash(hash)) return null;
|
|
1324
1334
|
const cached = this.#bufferCache.get(hash);
|
|
1325
1335
|
if (cached) {
|
|
1326
1336
|
const blobPath = path.join(this.dir, hash);
|
|
@@ -1374,7 +1384,7 @@ export class EphemeralBlobStore extends BlobStore {
|
|
|
1374
1384
|
const blobPath = path.join(this.dir, entry);
|
|
1375
1385
|
const stat = fs.lstatSync(blobPath);
|
|
1376
1386
|
if (
|
|
1377
|
-
|
|
1387
|
+
!isCanonicalBlobHash(entry) ||
|
|
1378
1388
|
!stat.isFile() ||
|
|
1379
1389
|
stat.isSymbolicLink() ||
|
|
1380
1390
|
stat.uid !== uid ||
|
|
@@ -1534,13 +1544,14 @@ export class ResidentBlobMissingError extends Error {
|
|
|
1534
1544
|
|
|
1535
1545
|
/** Check if a data string is a blob reference. */
|
|
1536
1546
|
export function isBlobRef(data: string): boolean {
|
|
1537
|
-
return data
|
|
1547
|
+
return parseBlobRef(data) !== null;
|
|
1538
1548
|
}
|
|
1539
1549
|
|
|
1540
1550
|
/** Extract the SHA-256 hash from a blob reference string. */
|
|
1541
1551
|
export function parseBlobRef(data: string): string | null {
|
|
1542
1552
|
if (!data.startsWith(BLOB_PREFIX)) return null;
|
|
1543
|
-
|
|
1553
|
+
const hash = data.slice(BLOB_PREFIX.length);
|
|
1554
|
+
return isCanonicalBlobHash(hash) ? hash : null;
|
|
1544
1555
|
}
|
|
1545
1556
|
|
|
1546
1557
|
/** Identify provider transport image data URLs so persistence can externalize and restore them losslessly. */
|
|
@@ -1731,7 +1742,6 @@ export interface CanonicalBlobEntry {
|
|
|
1731
1742
|
readonly nlink: bigint;
|
|
1732
1743
|
}
|
|
1733
1744
|
|
|
1734
|
-
const CANONICAL_BLOB_NAME = /^[0-9a-f]{64}$/;
|
|
1735
1745
|
const BLOB_REFERENCE = /blob:sha256:([0-9a-f]{64})/g;
|
|
1736
1746
|
|
|
1737
1747
|
/** Longest possible `blob:sha256:<hash>` reference, used to bound chunked scans. */
|
|
@@ -1758,7 +1768,7 @@ export async function listCanonicalBlobs(dir: string): Promise<CanonicalBlobEntr
|
|
|
1758
1768
|
|
|
1759
1769
|
const entries: CanonicalBlobEntry[] = [];
|
|
1760
1770
|
for (const name of names) {
|
|
1761
|
-
if (!
|
|
1771
|
+
if (!isCanonicalBlobHash(name)) continue;
|
|
1762
1772
|
const blobPath = path.join(dir, name);
|
|
1763
1773
|
let stat: fs.BigIntStats;
|
|
1764
1774
|
try {
|
|
@@ -290,8 +290,8 @@ export interface ManagedBoundedAppendExpectation {
|
|
|
290
290
|
|
|
291
291
|
function managedFileIdentityFromNative(identity: RecoveryFsIdentity): ManagedFileIdentity {
|
|
292
292
|
return {
|
|
293
|
-
dev: BigInt(identity.dev),
|
|
294
|
-
ino: BigInt(identity.ino),
|
|
293
|
+
dev: canonicalFileId(BigInt(identity.dev)),
|
|
294
|
+
ino: canonicalFileId(BigInt(identity.ino)),
|
|
295
295
|
nlink: BigInt(identity.nlink),
|
|
296
296
|
size: Number(identity.size),
|
|
297
297
|
mtimeNs: BigInt(identity.mtimeNs),
|
|
@@ -346,6 +346,12 @@ type LegacyReplacementCleanupIdentity = {
|
|
|
346
346
|
};
|
|
347
347
|
|
|
348
348
|
const U64_MAX = 18_446_744_073_709_551_615n;
|
|
349
|
+
const I64_ABS_MIN = 9_223_372_036_854_775_808n;
|
|
350
|
+
|
|
351
|
+
function canonicalFileId(value: bigint): bigint {
|
|
352
|
+
if (value < -I64_ABS_MIN || value > U64_MAX) throw new Error("file_identity_out_of_range");
|
|
353
|
+
return BigInt.asUintN(64, value);
|
|
354
|
+
}
|
|
349
355
|
|
|
350
356
|
function parseCanonicalU64(value: unknown): bigint | undefined {
|
|
351
357
|
if (typeof value !== "string" || !/^(0|[1-9][0-9]*)$/.test(value) || value.length > 20) return undefined;
|
|
@@ -353,10 +359,19 @@ function parseCanonicalU64(value: unknown): bigint | undefined {
|
|
|
353
359
|
return parsed <= U64_MAX ? parsed : undefined;
|
|
354
360
|
}
|
|
355
361
|
|
|
356
|
-
function
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
function parseFileId(value: unknown): bigint | undefined {
|
|
363
|
+
const canonical = parseCanonicalU64(value);
|
|
364
|
+
if (canonical !== undefined) return canonical;
|
|
365
|
+
if (typeof value !== "string" || !/^-[1-9][0-9]*$/.test(value) || value.length > 20) return undefined;
|
|
366
|
+
const parsed = BigInt(value);
|
|
367
|
+
return parsed >= -I64_ABS_MIN ? canonicalFileId(parsed) : undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function parseFileIdHex(value: string): bigint | undefined {
|
|
371
|
+
if (/^(0|[1-9a-f][0-9a-f]*)$/.test(value) && value.length <= 16) return BigInt(`0x${value}`);
|
|
372
|
+
if (!/^-[1-9a-f][0-9a-f]*$/.test(value) || value.length > 17) return undefined;
|
|
373
|
+
const magnitude = BigInt(`0x${value.slice(1)}`);
|
|
374
|
+
return magnitude <= I64_ABS_MIN ? canonicalFileId(-magnitude) : undefined;
|
|
360
375
|
}
|
|
361
376
|
|
|
362
377
|
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
@@ -365,19 +380,21 @@ function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
365
380
|
: undefined;
|
|
366
381
|
}
|
|
367
382
|
|
|
368
|
-
function legacyReplacementCleanupReceiptBinding(
|
|
369
|
-
|
|
383
|
+
function legacyReplacementCleanupReceiptBinding(
|
|
384
|
+
name: string,
|
|
385
|
+
): { dev: bigint; ino: bigint; encodedDev: string; encodedIno: string } | undefined {
|
|
386
|
+
const match = /^\.gjc-replace-cleanup-(-?(?:0|[1-9a-f][0-9a-f]*))-(-?(?:0|[1-9a-f][0-9a-f]*))\.json$/.exec(name);
|
|
370
387
|
if (!match?.[1] || !match[2]) return undefined;
|
|
371
|
-
const dev =
|
|
372
|
-
const ino =
|
|
373
|
-
return dev !== undefined && ino !== undefined ? { dev, ino } : undefined;
|
|
388
|
+
const dev = parseFileIdHex(match[1]);
|
|
389
|
+
const ino = parseFileIdHex(match[2]);
|
|
390
|
+
return dev !== undefined && ino !== undefined ? { dev, ino, encodedDev: match[1], encodedIno: match[2] } : undefined;
|
|
374
391
|
}
|
|
375
392
|
|
|
376
393
|
function parseLegacyReplacementCleanupIdentity(value: unknown): LegacyReplacementCleanupIdentity | undefined {
|
|
377
394
|
const identity = asRecord(value);
|
|
378
395
|
if (!identity) return undefined;
|
|
379
|
-
const dev =
|
|
380
|
-
const ino =
|
|
396
|
+
const dev = parseFileId(identity.dev);
|
|
397
|
+
const ino = parseFileId(identity.ino);
|
|
381
398
|
const nlink = parseCanonicalU64(identity.nlink);
|
|
382
399
|
const size = parseCanonicalU64(identity.size);
|
|
383
400
|
const mtimeNs = parseCanonicalU64(identity.mtimeNs);
|
|
@@ -417,28 +434,50 @@ function replacementCleanupReceiptBinding(
|
|
|
417
434
|
name: string,
|
|
418
435
|
): { predecessor: { dev: bigint; ino: bigint }; receipt: { dev: bigint; ino: bigint } } | undefined {
|
|
419
436
|
const match =
|
|
420
|
-
/^\.gjc-replace-cleanup-(0|[1-9a-f][0-9a-f]*)-(0|[1-9a-f][0-9a-f]*)-receipt-(0|[1-9a-f][0-9a-f]*)-(0|[1-9a-f][0-9a-f]*)\.json$/.exec(
|
|
437
|
+
/^\.gjc-replace-cleanup-(-?(?:0|[1-9a-f][0-9a-f]*))-(-?(?:0|[1-9a-f][0-9a-f]*))-receipt-(-?(?:0|[1-9a-f][0-9a-f]*))-(-?(?:0|[1-9a-f][0-9a-f]*))\.json$/.exec(
|
|
421
438
|
name,
|
|
422
439
|
);
|
|
423
440
|
if (!match?.[1] || !match[2] || !match[3] || !match[4]) return undefined;
|
|
424
|
-
const predecessor = { dev:
|
|
425
|
-
const receipt = { dev:
|
|
426
|
-
return predecessor.dev
|
|
427
|
-
|
|
441
|
+
const predecessor = { dev: parseFileIdHex(match[1]), ino: parseFileIdHex(match[2]) };
|
|
442
|
+
const receipt = { dev: parseFileIdHex(match[3]), ino: parseFileIdHex(match[4]) };
|
|
443
|
+
return predecessor.dev !== undefined &&
|
|
444
|
+
predecessor.ino !== undefined &&
|
|
445
|
+
receipt.dev !== undefined &&
|
|
446
|
+
receipt.ino !== undefined
|
|
447
|
+
? { predecessor: { dev: predecessor.dev, ino: predecessor.ino }, receipt: { dev: receipt.dev, ino: receipt.ino } }
|
|
428
448
|
: undefined;
|
|
429
449
|
}
|
|
430
450
|
|
|
431
451
|
function parseReplacementIdentity(value: unknown): ManagedFileSnapshot["identity"] | undefined {
|
|
432
|
-
const
|
|
433
|
-
if (!
|
|
452
|
+
const identity = asRecord(value);
|
|
453
|
+
if (!identity) return undefined;
|
|
454
|
+
const dev = parseFileId(identity.dev);
|
|
455
|
+
const ino = parseFileId(identity.ino);
|
|
456
|
+
const nlink = parseCanonicalU64(identity.nlink);
|
|
457
|
+
const size = parseCanonicalU64(identity.size);
|
|
458
|
+
const mtimeNs = parseCanonicalU64(identity.mtimeNs);
|
|
459
|
+
const ctimeNs = parseCanonicalU64(identity.ctimeNs);
|
|
460
|
+
const sha256 = identity.sha256;
|
|
461
|
+
if (
|
|
462
|
+
dev === undefined ||
|
|
463
|
+
ino === undefined ||
|
|
464
|
+
nlink === undefined ||
|
|
465
|
+
size === undefined ||
|
|
466
|
+
size > BigInt(Number.MAX_SAFE_INTEGER) ||
|
|
467
|
+
mtimeNs === undefined ||
|
|
468
|
+
ctimeNs === undefined ||
|
|
469
|
+
typeof sha256 !== "string" ||
|
|
470
|
+
!/^[0-9a-f]{64}$/.test(sha256)
|
|
471
|
+
)
|
|
472
|
+
return undefined;
|
|
434
473
|
return {
|
|
435
|
-
dev
|
|
436
|
-
ino
|
|
437
|
-
nlink
|
|
438
|
-
size: Number(
|
|
439
|
-
mtimeNs
|
|
440
|
-
ctimeNs
|
|
441
|
-
sha256
|
|
474
|
+
dev,
|
|
475
|
+
ino,
|
|
476
|
+
nlink,
|
|
477
|
+
size: Number(size),
|
|
478
|
+
mtimeNs,
|
|
479
|
+
ctimeNs,
|
|
480
|
+
sha256,
|
|
442
481
|
};
|
|
443
482
|
}
|
|
444
483
|
|
|
@@ -463,8 +502,8 @@ function parseReplacementCleanupReceipt(bytes: Uint8Array): ReplacementCleanupRe
|
|
|
463
502
|
|
|
464
503
|
function serializeReplacementIdentity(identity: ManagedFileSnapshot["identity"]): SerializedReplacementIdentity {
|
|
465
504
|
return {
|
|
466
|
-
dev: identity.dev.toString(),
|
|
467
|
-
ino: identity.ino.toString(),
|
|
505
|
+
dev: canonicalFileId(identity.dev).toString(),
|
|
506
|
+
ino: canonicalFileId(identity.ino).toString(),
|
|
468
507
|
nlink: identity.nlink.toString(),
|
|
469
508
|
size: String(identity.size),
|
|
470
509
|
mtimeNs: identity.mtimeNs.toString(),
|
|
@@ -480,7 +519,7 @@ function replacementReceiptPath(
|
|
|
480
519
|
): string {
|
|
481
520
|
return path.join(
|
|
482
521
|
baseDir,
|
|
483
|
-
`.gjc-replace-cleanup-${predecessor.dev.toString(16)}-${predecessor.ino.toString(16)}-receipt-${receipt.dev.toString(16)}-${receipt.ino.toString(16)}.json`,
|
|
522
|
+
`.gjc-replace-cleanup-${canonicalFileId(predecessor.dev).toString(16)}-${canonicalFileId(predecessor.ino).toString(16)}-receipt-${canonicalFileId(receipt.dev).toString(16)}-${canonicalFileId(receipt.ino).toString(16)}.json`,
|
|
484
523
|
);
|
|
485
524
|
}
|
|
486
525
|
|
|
@@ -488,7 +527,7 @@ function replacementReceiptRetirementName(
|
|
|
488
527
|
receipt: { dev: bigint; ino: bigint },
|
|
489
528
|
predecessor: { dev: bigint; ino: bigint },
|
|
490
529
|
): string {
|
|
491
|
-
return `.gjc-receipt-remove-${receipt.dev.toString(16)}-${receipt.ino.toString(16)}-${predecessor.dev.toString(16)}-${predecessor.ino.toString(16)}`;
|
|
530
|
+
return `.gjc-receipt-remove-${canonicalFileId(receipt.dev).toString(16)}-${canonicalFileId(receipt.ino).toString(16)}-${canonicalFileId(predecessor.dev).toString(16)}-${canonicalFileId(predecessor.ino).toString(16)}`;
|
|
492
531
|
}
|
|
493
532
|
|
|
494
533
|
function replacementReceiptPlaceholderRetirementName(
|
|
@@ -496,7 +535,7 @@ function replacementReceiptPlaceholderRetirementName(
|
|
|
496
535
|
predecessor: { dev: bigint; ino: bigint },
|
|
497
536
|
receipt: { dev: bigint; ino: bigint },
|
|
498
537
|
): string {
|
|
499
|
-
return `.gjc-receipt-placeholder-remove-${placeholder.dev.toString(16)}-${placeholder.ino.toString(16)}-${predecessor.dev.toString(16)}-${predecessor.ino.toString(16)}-${receipt.dev.toString(16)}-${receipt.ino.toString(16)}`;
|
|
538
|
+
return `.gjc-receipt-placeholder-remove-${canonicalFileId(placeholder.dev).toString(16)}-${canonicalFileId(placeholder.ino).toString(16)}-${canonicalFileId(predecessor.dev).toString(16)}-${canonicalFileId(predecessor.ino).toString(16)}-${canonicalFileId(receipt.dev).toString(16)}-${canonicalFileId(receipt.ino).toString(16)}`;
|
|
500
539
|
}
|
|
501
540
|
|
|
502
541
|
/**
|
|
@@ -805,7 +844,7 @@ export function managedDirectoryRoot(configuredRoot: string): ManagedDirectoryRo
|
|
|
805
844
|
const canonicalPath = fs.realpathSync.native(configuredRoot);
|
|
806
845
|
const stat = fs.lstatSync(canonicalPath, { bigint: true });
|
|
807
846
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error(`Unsafe managed root: ${configuredRoot}`);
|
|
808
|
-
return Object.freeze({ canonicalPath, dev: stat.dev, ino: stat.ino });
|
|
847
|
+
return Object.freeze({ canonicalPath, dev: canonicalFileId(stat.dev), ino: canonicalFileId(stat.ino) });
|
|
809
848
|
}
|
|
810
849
|
|
|
811
850
|
export function retainManagedDirectoryAuthority(
|
|
@@ -819,7 +858,7 @@ export function retainManagedDirectoryAuthority(
|
|
|
819
858
|
if (process.platform !== "linux") return undefined;
|
|
820
859
|
const named = fs.lstatSync(resolved, { bigint: true });
|
|
821
860
|
if (!named.isDirectory() || named.isSymbolicLink()) throw new Error("Managed directory authority is unavailable");
|
|
822
|
-
if (expected && (named.dev !== expected.dev || named.ino !== expected.ino))
|
|
861
|
+
if (expected && (canonicalFileId(named.dev) !== expected.dev || canonicalFileId(named.ino) !== expected.ino))
|
|
823
862
|
throw new Error("Managed directory identity changed before retention");
|
|
824
863
|
const rootAuthority = nativeSessionStorage().openRecoveryFsRoot(root.canonicalPath);
|
|
825
864
|
try {
|
|
@@ -832,7 +871,11 @@ export function retainManagedDirectoryAuthority(
|
|
|
832
871
|
)
|
|
833
872
|
throw new Error("Managed root authority changed");
|
|
834
873
|
const relative = path.relative(root.canonicalPath, resolved).split(path.sep).join("/");
|
|
835
|
-
return rootAuthority.retainManagedDirectory(
|
|
874
|
+
return rootAuthority.retainManagedDirectory(
|
|
875
|
+
relative,
|
|
876
|
+
canonicalFileId(named.dev).toString(),
|
|
877
|
+
canonicalFileId(named.ino).toString(),
|
|
878
|
+
);
|
|
836
879
|
} finally {
|
|
837
880
|
rootAuthority.close();
|
|
838
881
|
}
|
|
@@ -852,7 +895,12 @@ function ensureDirectoryComponent(pathname: string): boolean {
|
|
|
852
895
|
|
|
853
896
|
export function assertManagedDirectoryRoot(root: ManagedDirectoryRoot): void {
|
|
854
897
|
const named = fs.lstatSync(root.canonicalPath, { bigint: true });
|
|
855
|
-
if (
|
|
898
|
+
if (
|
|
899
|
+
!named.isDirectory() ||
|
|
900
|
+
named.isSymbolicLink() ||
|
|
901
|
+
canonicalFileId(named.dev) !== root.dev ||
|
|
902
|
+
canonicalFileId(named.ino) !== root.ino
|
|
903
|
+
) {
|
|
856
904
|
throw new Error(`Managed root authority changed: ${root.canonicalPath}`);
|
|
857
905
|
}
|
|
858
906
|
}
|
|
@@ -932,7 +980,12 @@ function verifyExistingManagedPathSecurity(
|
|
|
932
980
|
expected: fs.BigIntStats,
|
|
933
981
|
): void {
|
|
934
982
|
const verified = validateNativeSecurityResult(
|
|
935
|
-
nativeSessionStorage().verifyOwnerOnlyPathSecurityExpected(
|
|
983
|
+
nativeSessionStorage().verifyOwnerOnlyPathSecurityExpected(
|
|
984
|
+
pathname,
|
|
985
|
+
kind,
|
|
986
|
+
canonicalFileId(expected.dev),
|
|
987
|
+
canonicalFileId(expected.ino),
|
|
988
|
+
),
|
|
936
989
|
"verify",
|
|
937
990
|
kind,
|
|
938
991
|
);
|
|
@@ -945,7 +998,12 @@ function secureExistingManagedDirectory(pathname: string, kind: "directory" | "f
|
|
|
945
998
|
const safeKind = kind === "directory" ? named.isDirectory() : named.isFile();
|
|
946
999
|
if (!safeKind || named.isSymbolicLink()) throw new Error(`Unsafe managed ${kind}: ${pathname}`);
|
|
947
1000
|
const verified = validateNativeSecurityResult(
|
|
948
|
-
nativeSessionStorage().verifyOwnerOnlyPathSecurityExpected(
|
|
1001
|
+
nativeSessionStorage().verifyOwnerOnlyPathSecurityExpected(
|
|
1002
|
+
pathname,
|
|
1003
|
+
kind,
|
|
1004
|
+
canonicalFileId(named.dev),
|
|
1005
|
+
canonicalFileId(named.ino),
|
|
1006
|
+
),
|
|
949
1007
|
"verify",
|
|
950
1008
|
kind,
|
|
951
1009
|
);
|
|
@@ -953,7 +1011,12 @@ function secureExistingManagedDirectory(pathname: string, kind: "directory" | "f
|
|
|
953
1011
|
if (verified.ok) return;
|
|
954
1012
|
if (verified.code !== "acl_verify_failed") throw securityError(pathname, verified);
|
|
955
1013
|
const repaired = validateNativeSecurityResult(
|
|
956
|
-
nativeSessionStorage().repairOwnerOnlyPathSecurityExpected(
|
|
1014
|
+
nativeSessionStorage().repairOwnerOnlyPathSecurityExpected(
|
|
1015
|
+
pathname,
|
|
1016
|
+
kind,
|
|
1017
|
+
canonicalFileId(named.dev),
|
|
1018
|
+
canonicalFileId(named.ino),
|
|
1019
|
+
),
|
|
957
1020
|
"verify",
|
|
958
1021
|
kind,
|
|
959
1022
|
);
|
|
@@ -1015,8 +1078,8 @@ export class ManagedSessionDescendantStore {
|
|
|
1015
1078
|
throw new Error(rootIdentity.code ?? "Managed subtree identity unavailable");
|
|
1016
1079
|
this.#subtreeRoot = Object.freeze({
|
|
1017
1080
|
canonicalPath: this.#baseDir,
|
|
1018
|
-
dev: BigInt(rootIdentity.identity.dev),
|
|
1019
|
-
ino: BigInt(rootIdentity.identity.ino),
|
|
1081
|
+
dev: canonicalFileId(BigInt(rootIdentity.identity.dev)),
|
|
1082
|
+
ino: canonicalFileId(BigInt(rootIdentity.identity.ino)),
|
|
1020
1083
|
});
|
|
1021
1084
|
} else {
|
|
1022
1085
|
const captured = retained.authority.snapshotManagedTree(relative);
|
|
@@ -1024,8 +1087,8 @@ export class ManagedSessionDescendantStore {
|
|
|
1024
1087
|
throw new Error(captured.code ?? "Managed subtree identity unavailable");
|
|
1025
1088
|
this.#subtreeRoot = Object.freeze({
|
|
1026
1089
|
canonicalPath: this.#baseDir,
|
|
1027
|
-
dev: BigInt(captured.snapshot.rootDev),
|
|
1028
|
-
ino: BigInt(captured.snapshot.rootIno),
|
|
1090
|
+
dev: canonicalFileId(BigInt(captured.snapshot.rootDev)),
|
|
1091
|
+
ino: canonicalFileId(BigInt(captured.snapshot.rootIno)),
|
|
1029
1092
|
});
|
|
1030
1093
|
}
|
|
1031
1094
|
this.#authority = retained.authority;
|
|
@@ -1036,7 +1099,11 @@ export class ManagedSessionDescendantStore {
|
|
|
1036
1099
|
assertManagedDirectoryRoot(root);
|
|
1037
1100
|
ensureManagedDirectory(this.#baseDir, root, this.#policy);
|
|
1038
1101
|
const subtreeStat = fs.lstatSync(this.#baseDir, { bigint: true });
|
|
1039
|
-
this.#subtreeRoot = Object.freeze({
|
|
1102
|
+
this.#subtreeRoot = Object.freeze({
|
|
1103
|
+
canonicalPath: this.#baseDir,
|
|
1104
|
+
dev: canonicalFileId(subtreeStat.dev),
|
|
1105
|
+
ino: canonicalFileId(subtreeStat.ino),
|
|
1106
|
+
});
|
|
1040
1107
|
if (process.platform === "linux") {
|
|
1041
1108
|
const before = fs.lstatSync(this.#baseDir, { bigint: true });
|
|
1042
1109
|
const authority = nativeSessionStorage().openRecoveryFsRoot(this.#baseDir);
|
|
@@ -1044,8 +1111,8 @@ export class ManagedSessionDescendantStore {
|
|
|
1044
1111
|
if (
|
|
1045
1112
|
!retained.ok ||
|
|
1046
1113
|
!retained.identity ||
|
|
1047
|
-
retained.identity.dev !== before.dev.toString() ||
|
|
1048
|
-
retained.identity.ino !== before.ino.toString()
|
|
1114
|
+
retained.identity.dev !== canonicalFileId(before.dev).toString() ||
|
|
1115
|
+
retained.identity.ino !== canonicalFileId(before.ino).toString()
|
|
1049
1116
|
) {
|
|
1050
1117
|
authority.close();
|
|
1051
1118
|
throw new Error("Managed descendant root identity changed");
|
|
@@ -1150,7 +1217,11 @@ export class ManagedSessionDescendantStore {
|
|
|
1150
1217
|
#assertBound(): void {
|
|
1151
1218
|
if (!this.#authority) {
|
|
1152
1219
|
const named = fs.statSync(this.#baseDir, { bigint: true });
|
|
1153
|
-
if (
|
|
1220
|
+
if (
|
|
1221
|
+
!named.isDirectory() ||
|
|
1222
|
+
canonicalFileId(named.dev) !== this.#subtreeRoot.dev ||
|
|
1223
|
+
canonicalFileId(named.ino) !== this.#subtreeRoot.ino
|
|
1224
|
+
)
|
|
1154
1225
|
throw new Error("Managed descendant root binding changed");
|
|
1155
1226
|
return;
|
|
1156
1227
|
}
|
|
@@ -1170,10 +1241,10 @@ export class ManagedSessionDescendantStore {
|
|
|
1170
1241
|
!retained.identity ||
|
|
1171
1242
|
!named.isDirectory() ||
|
|
1172
1243
|
named.isSymbolicLink() ||
|
|
1173
|
-
retained.identity.dev !== named.dev.toString() ||
|
|
1244
|
+
retained.identity.dev !== canonicalFileId(named.dev).toString() ||
|
|
1174
1245
|
retained.identity.dev !== this.#subtreeRoot.dev.toString() ||
|
|
1175
1246
|
retained.identity.ino !== this.#subtreeRoot.ino.toString() ||
|
|
1176
|
-
retained.identity.ino !== named.ino.toString()
|
|
1247
|
+
retained.identity.ino !== canonicalFileId(named.ino).toString()
|
|
1177
1248
|
) {
|
|
1178
1249
|
throw new Error("Managed descendant root binding changed");
|
|
1179
1250
|
}
|
|
@@ -1283,7 +1354,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1283
1354
|
const parsed = parseLegacyReplacementCleanupReceipt(receipt.bytes);
|
|
1284
1355
|
const expectedPredecessor = path.join(
|
|
1285
1356
|
this.#baseDir,
|
|
1286
|
-
`.gjc-exact-replace-destination-${binding.
|
|
1357
|
+
`.gjc-exact-replace-destination-${binding.encodedDev}-${binding.encodedIno}`,
|
|
1287
1358
|
);
|
|
1288
1359
|
if (
|
|
1289
1360
|
!parsed ||
|
|
@@ -1488,13 +1559,17 @@ export class ManagedSessionDescendantStore {
|
|
|
1488
1559
|
this.#assertBound();
|
|
1489
1560
|
return Object.freeze({
|
|
1490
1561
|
canonicalPath: this.#resolve(relativePath),
|
|
1491
|
-
dev: BigInt(ensured.identity.dev),
|
|
1492
|
-
ino: BigInt(ensured.identity.ino),
|
|
1562
|
+
dev: canonicalFileId(BigInt(ensured.identity.dev)),
|
|
1563
|
+
ino: canonicalFileId(BigInt(ensured.identity.ino)),
|
|
1493
1564
|
});
|
|
1494
1565
|
}
|
|
1495
1566
|
ensureManagedDirectory(this.#resolve(relativePath), this.#root, this.#policy);
|
|
1496
1567
|
const named = fs.lstatSync(this.#resolve(relativePath), { bigint: true });
|
|
1497
|
-
return Object.freeze({
|
|
1568
|
+
return Object.freeze({
|
|
1569
|
+
canonicalPath: this.#resolve(relativePath),
|
|
1570
|
+
dev: canonicalFileId(named.dev),
|
|
1571
|
+
ino: canonicalFileId(named.ino),
|
|
1572
|
+
});
|
|
1498
1573
|
}
|
|
1499
1574
|
|
|
1500
1575
|
async publishNoReplace(relativePath: string, bytes: Uint8Array): Promise<void> {
|
|
@@ -1878,7 +1953,7 @@ export class ManagedSessionDescendantStore {
|
|
|
1878
1953
|
for (const component of components.slice(0, -1)) {
|
|
1879
1954
|
current = path.join(current, component);
|
|
1880
1955
|
const stat = fs.lstatSync(current, { bigint: true });
|
|
1881
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.dev !== this.#subtreeRoot.dev)
|
|
1956
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || canonicalFileId(stat.dev) !== this.#subtreeRoot.dev)
|
|
1882
1957
|
throw new Error("Managed descendant path escapes retained store");
|
|
1883
1958
|
}
|
|
1884
1959
|
}
|
|
@@ -1999,8 +2074,8 @@ export class ManagedSessionDescendantStore {
|
|
|
1999
2074
|
if (
|
|
2000
2075
|
!rootBefore.isDirectory() ||
|
|
2001
2076
|
rootBefore.isSymbolicLink() ||
|
|
2002
|
-
rootBefore.dev !== this.#subtreeRoot.dev ||
|
|
2003
|
-
rootBefore.ino !== this.#subtreeRoot.ino
|
|
2077
|
+
canonicalFileId(rootBefore.dev) !== this.#subtreeRoot.dev ||
|
|
2078
|
+
canonicalFileId(rootBefore.ino) !== this.#subtreeRoot.ino
|
|
2004
2079
|
)
|
|
2005
2080
|
throw new Error("Managed descendant root binding changed");
|
|
2006
2081
|
let fd: number | undefined;
|
|
@@ -2031,8 +2106,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2031
2106
|
throw new Error("Managed descendant root binding changed");
|
|
2032
2107
|
this.#assertBound();
|
|
2033
2108
|
return managedAppendReceiptFromIdentity({
|
|
2034
|
-
dev: opened.dev,
|
|
2035
|
-
ino: opened.ino,
|
|
2109
|
+
dev: canonicalFileId(opened.dev),
|
|
2110
|
+
ino: canonicalFileId(opened.ino),
|
|
2036
2111
|
nlink: opened.nlink,
|
|
2037
2112
|
size: Number(opened.size),
|
|
2038
2113
|
mtimeNs: opened.mtimeNs,
|
|
@@ -2076,8 +2151,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2076
2151
|
if (
|
|
2077
2152
|
!rootBefore.isDirectory() ||
|
|
2078
2153
|
rootBefore.isSymbolicLink() ||
|
|
2079
|
-
rootBefore.dev !== this.#subtreeRoot.dev ||
|
|
2080
|
-
rootBefore.ino !== this.#subtreeRoot.ino
|
|
2154
|
+
canonicalFileId(rootBefore.dev) !== this.#subtreeRoot.dev ||
|
|
2155
|
+
canonicalFileId(rootBefore.ino) !== this.#subtreeRoot.ino
|
|
2081
2156
|
)
|
|
2082
2157
|
throw new Error("Managed descendant root binding changed");
|
|
2083
2158
|
const resolved = this.#resolve(relativePath);
|
|
@@ -2099,8 +2174,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2099
2174
|
if (retained?.identity) {
|
|
2100
2175
|
const expected = retained.identity;
|
|
2101
2176
|
if (
|
|
2102
|
-
before.dev.toString() !== expected.dev ||
|
|
2103
|
-
before.ino.toString() !== expected.ino ||
|
|
2177
|
+
canonicalFileId(before.dev).toString() !== expected.dev ||
|
|
2178
|
+
canonicalFileId(before.ino).toString() !== expected.ino ||
|
|
2104
2179
|
before.nlink.toString() !== expected.nlink ||
|
|
2105
2180
|
before.size.toString() !== expected.size ||
|
|
2106
2181
|
before.mtimeNs.toString() !== expected.mtimeNs ||
|
|
@@ -2110,8 +2185,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2110
2185
|
}
|
|
2111
2186
|
if (
|
|
2112
2187
|
expectedDescriptor &&
|
|
2113
|
-
(before.dev !== expectedDescriptor.dev ||
|
|
2114
|
-
before.ino !== expectedDescriptor.ino ||
|
|
2188
|
+
(canonicalFileId(before.dev) !== expectedDescriptor.dev ||
|
|
2189
|
+
canonicalFileId(before.ino) !== expectedDescriptor.ino ||
|
|
2115
2190
|
before.nlink !== (expectedDescriptor.nlink ?? before.nlink) ||
|
|
2116
2191
|
Number(before.size) !== expectedDescriptor.size ||
|
|
2117
2192
|
before.mtimeNs !== expectedDescriptor.mtimeNs ||
|
|
@@ -2154,8 +2229,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2154
2229
|
return {
|
|
2155
2230
|
bytes,
|
|
2156
2231
|
stat: {
|
|
2157
|
-
dev: after.dev,
|
|
2158
|
-
ino: after.ino,
|
|
2232
|
+
dev: canonicalFileId(after.dev),
|
|
2233
|
+
ino: canonicalFileId(after.ino),
|
|
2159
2234
|
nlink: after.nlink,
|
|
2160
2235
|
size: Number(after.size),
|
|
2161
2236
|
mtimeNs: after.mtimeNs,
|
|
@@ -2194,8 +2269,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2194
2269
|
return {
|
|
2195
2270
|
bytes: Buffer.from(read.data),
|
|
2196
2271
|
identity: {
|
|
2197
|
-
dev: BigInt(read.identity.dev),
|
|
2198
|
-
ino: BigInt(read.identity.ino),
|
|
2272
|
+
dev: canonicalFileId(BigInt(read.identity.dev)),
|
|
2273
|
+
ino: canonicalFileId(BigInt(read.identity.ino)),
|
|
2199
2274
|
nlink: BigInt(read.identity.nlink),
|
|
2200
2275
|
size: Number(read.identity.size),
|
|
2201
2276
|
mtimeNs: BigInt(read.identity.mtimeNs),
|
|
@@ -2217,8 +2292,8 @@ export class ManagedSessionDescendantStore {
|
|
|
2217
2292
|
size: BigInt(expected.identity.size),
|
|
2218
2293
|
mtimeNs: expected.identity.mtimeNs,
|
|
2219
2294
|
sha256: expected.identity.sha256,
|
|
2220
|
-
parentDev: parent.dev,
|
|
2221
|
-
parentIno: parent.ino,
|
|
2295
|
+
parentDev: canonicalFileId(parent.dev),
|
|
2296
|
+
parentIno: canonicalFileId(parent.ino),
|
|
2222
2297
|
quarantineName: `.gjc-remove-${process.pid}-${randomUUID()}`,
|
|
2223
2298
|
});
|
|
2224
2299
|
if (
|
|
@@ -2647,8 +2722,8 @@ function bootId(): string | undefined {
|
|
|
2647
2722
|
|
|
2648
2723
|
function identity(stat: fs.BigIntStats, sha256 = ""): ManagedFileSnapshot["identity"] {
|
|
2649
2724
|
return {
|
|
2650
|
-
dev: stat.dev,
|
|
2651
|
-
ino: stat.ino,
|
|
2725
|
+
dev: canonicalFileId(stat.dev),
|
|
2726
|
+
ino: canonicalFileId(stat.ino),
|
|
2652
2727
|
nlink: stat.nlink,
|
|
2653
2728
|
size: Number(stat.size),
|
|
2654
2729
|
mtimeNs: stat.mtimeNs,
|
|
@@ -3163,8 +3238,8 @@ function replaceManagedFileGeneratedSync(
|
|
|
3163
3238
|
fsyncDirectory(parent);
|
|
3164
3239
|
receiptCleanup = {
|
|
3165
3240
|
path: receiptPath,
|
|
3166
|
-
parentDev: parentIdentity.dev,
|
|
3167
|
-
parentIno: parentIdentity.ino,
|
|
3241
|
+
parentDev: canonicalFileId(parentIdentity.dev),
|
|
3242
|
+
parentIno: canonicalFileId(parentIdentity.ino),
|
|
3168
3243
|
identity: publishedReceiptIdentity,
|
|
3169
3244
|
predecessor: expectedDestination,
|
|
3170
3245
|
};
|
|
@@ -3184,8 +3259,8 @@ function replaceManagedFileGeneratedSync(
|
|
|
3184
3259
|
dev: successor.dev,
|
|
3185
3260
|
ino: successor.ino,
|
|
3186
3261
|
nlink: successor.nlink,
|
|
3187
|
-
parentDev: parentIdentity.dev,
|
|
3188
|
-
parentIno: parentIdentity.ino,
|
|
3262
|
+
parentDev: canonicalFileId(parentIdentity.dev),
|
|
3263
|
+
parentIno: canonicalFileId(parentIdentity.ino),
|
|
3189
3264
|
size: BigInt(successor.size),
|
|
3190
3265
|
mtimeNs: successor.mtimeNs,
|
|
3191
3266
|
sha256: successor.sha256,
|
|
@@ -3194,8 +3269,8 @@ function replaceManagedFileGeneratedSync(
|
|
|
3194
3269
|
dev: expectedDestination.dev,
|
|
3195
3270
|
ino: expectedDestination.ino,
|
|
3196
3271
|
nlink: expectedDestination.nlink,
|
|
3197
|
-
parentDev: parentIdentity.dev,
|
|
3198
|
-
parentIno: parentIdentity.ino,
|
|
3272
|
+
parentDev: canonicalFileId(parentIdentity.dev),
|
|
3273
|
+
parentIno: canonicalFileId(parentIdentity.ino),
|
|
3199
3274
|
size: BigInt(expectedDestination.size),
|
|
3200
3275
|
mtimeNs: expectedDestination.mtimeNs,
|
|
3201
3276
|
sha256: expectedDestination.sha256,
|