Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@
"compatibility-provider-equivalence.test.ts": "routing",
"compatibility-version.test.ts": "ci-workflows",
"config-load-degrade.test.ts": "config",
"config-initialize-if-missing.test.ts": "config",
"config-mutation-lock.test.ts": "config",
"config-ownership-uninstall.test.ts": "config",
"config-rebase-provenance-writers.test.ts": "config",
Expand Down
221 changes: 220 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { chmodSync, constants as fsConstants, copyFileSync, existsSync, linkSync, mkdirSync, readFileSync, truncateSync, unlinkSync, writeFileSync } from "node:fs";
import { chmodSync, closeSync, constants as fsConstants, copyFileSync, existsSync, fchmodSync, fstatSync, ftruncateSync, lstatSync, linkSync, mkdirSync, openSync, readFileSync, truncateSync, unlinkSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { Database } from "bun:sqlite";
import * as z from "zod/v4";
Expand Down Expand Up @@ -106,9 +106,12 @@ import {
} from "./lib/app-owned-memory";
import { isHostedToolUnsupportedForModel } from "./responses/hosted-tool-policy";
import {
AtomicWriteResidualTempError,
AtomicWriteSecretResidualError,
atomicWriteFile,
isMissingPathError,
nextAtomicTempSequence,
resolveWriteTarget,
} from "./config/atomic-write";
export {
AtomicWriteResidualTempError,
Expand Down Expand Up @@ -3011,6 +3014,222 @@ export function observeConfigGeneration(): ConfigGenerationObservation {
return observeConfigGenerationAtPath(join(getConfigDir(), CONFIG_MUTATION_DB_FILENAME));
}

export type PersistedConfigInitializationOutcome = "created" | "exists" | "invalid";

export class PersistedConfigInitializationCleanupError extends Error {
constructor(options?: ErrorOptions) {
super("Initial config publication cleanup failed after rollback", options);
this.name = "PersistedConfigInitializationCleanupError";
}
}

export class PersistedConfigInitializationRollbackError extends Error {
constructor(options?: ErrorOptions) {
super("Initial config publication rollback failed", options);
this.name = "PersistedConfigInitializationRollbackError";
}
}

export class PersistedConfigInitializationHardLinkUnavailableError extends Error {
readonly code = "CONFIG_INITIALIZATION_HARDLINK_UNAVAILABLE";
constructor(options?: ErrorOptions) {
super("Initial config publication requires hard-link support; no-replace semantics unavailable", options);
this.name = "PersistedConfigInitializationHardLinkUnavailableError";
}
}

export interface PersistedConfigInitializationIO {
createExclusive(path: string): void;
write(path: string, bytes: string): void;
harden(path: string): void;
publishNoReplace(temp: string, target: string): void;
truncate(path: string): void;
unlink(path: string): void;
close?(): void;
}

let persistedConfigInitializationBeforePublishForTests: (() => void) | null = null;
let persistedConfigInitializationAfterPublishForTests: (() => void) | null = null;

export function setPersistedConfigInitializationBeforePublishForTests(hook: (() => void) | null): void {
persistedConfigInitializationBeforePublishForTests = hook;
}
export function setPersistedConfigInitializationAfterPublishForTests(hook: (() => void) | null): void {
persistedConfigInitializationAfterPublishForTests = hook;
}

function publishInitialConfigNoReplace(config: OcxConfig, io: PersistedConfigInitializationIO): boolean {
const configPath = getConfigPath();
const target = resolveWriteTarget(configPath);
assertNotRealHomeUnderTest(dirname(target));
const persisted = projectConfigRebaseProvenance(config);
const bytes = JSON.stringify(persisted, null, 2) + "\n";
const temp = `${target}.ocx.${process.pid}.${nextAtomicTempSequence()}.tmp`;
let staged = false;
let hardened = false;
let published = false;
let cleanupAttempted = false;

const scrubUnpublishedTemp = (cause?: unknown): void => {
cleanupAttempted = true;
let scrubbed = false;
try { io.truncate(temp); scrubbed = true; }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
catch (error) {
if (isMissingPathError(error)) scrubbed = true;
else { try { io.write(temp, ""); scrubbed = true; } catch { /* removal may still succeed */ } }
}
io.close?.();
let removed = false;
try { io.unlink(temp); removed = true; }
catch (error) {
if (isMissingPathError(error)) removed = true;
else { try { io.unlink(temp); removed = true; } catch (retryError) { if (isMissingPathError(retryError)) removed = true; } }
}
if (removed) forgetEphemeralSecretPath(temp);
if (!removed && !scrubbed) throw new AtomicWriteSecretResidualError(temp, { cause });
if (!removed) throw new AtomicWriteResidualTempError(temp, hardened, { cause });
};

try {
io.createExclusive(temp); staged = true;
io.write(temp, bytes); io.harden(temp); hardened = true;
const hook = persistedConfigInitializationBeforePublishForTests;
persistedConfigInitializationBeforePublishForTests = null;
hook?.();
try { io.publishNoReplace(temp, target); }
catch (cause) {
const code = cause && typeof cause === "object" && "code" in cause
? String((cause as { code?: unknown }).code) : "";
if (code === "EOPNOTSUPP" || code === "EXDEV" || code === "EPERM") {
throw new PersistedConfigInitializationHardLinkUnavailableError({ cause });
}
if (!isAlreadyExistsError(cause)) throw cause;
scrubUnpublishedTemp(cause);
return false;
}
published = true;
try { io.unlink(temp); io.close?.(); forgetEphemeralSecretPath(temp); }
catch (firstError) {
if (isMissingPathError(firstError)) { io.close?.(); forgetEphemeralSecretPath(temp); }
else {
try { io.unlink(temp); io.close?.(); forgetEphemeralSecretPath(temp); }
catch (secondError) {
if (isMissingPathError(secondError)) { io.close?.(); forgetEphemeralSecretPath(temp); }
else {
let samePublishedInode = false;
try {
const stagedStat = lstatSync(temp);
const targetStat = lstatSync(target);
samePublishedInode = stagedStat.dev === targetStat.dev && stagedStat.ino === targetStat.ino;
} catch { /* leave target untouched when identity cannot be proven */ }
if (samePublishedInode) {
cleanupAttempted = true;
try { io.unlink(target); }
catch (cause) {
io.close?.();
throw new PersistedConfigInitializationRollbackError({ cause });
}
published = false;
scrubUnpublishedTemp(secondError);
} else {
// Publication succeeded, but the temporary pathname no longer
// identifies its inode. Do not follow it for scrubbing.
cleanupAttempted = true;
io.close?.();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
throw new PersistedConfigInitializationCleanupError({ cause: secondError });
}
}
}
}
// Claim the config path only after no-replace publication and its identity
// checks have completed successfully. A losing initializer must not leave
// ownership metadata claiming a winner's file after an EEXIST collision.
recordOwnedConfigPath(getConfigDir(), configPath);
refreshUserCostOverlays(persisted);
return true;
} catch (cause) {
if (staged && !published && !cleanupAttempted) scrubUnpublishedTemp(cause);
throw cause;
}
}

function defaultPersistedConfigInitializationIO(configPath: string): PersistedConfigInitializationIO {
let descriptor: number | undefined;
return {
createExclusive: target => { descriptor = openSync(target, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 0o600); },
write: (target, bytes) => {
if (descriptor === undefined) writeFileSync(target, bytes);
else writeFileSync(descriptor, bytes, { encoding: "utf-8" });
},
harden: target => {
try {
if (descriptor === undefined) chmodSync(target, 0o600);
else if (process.platform !== "win32") fchmodSync(descriptor, 0o600);
} catch { /* platform may ignore chmod */ }
if (process.platform === "win32") hardenSecretPath(target, { required: true, timeoutMemoKey: configPath });
},
publishNoReplace: (temp, target) => {
if (descriptor !== undefined) {
const opened = fstatSync(descriptor);
const linked = lstatSync(temp);
if (opened.dev !== linked.dev || opened.ino !== linked.ino) {
throw new Error("atomic initialization temporary file identity changed before publication");
}
}
try {
linkSync(temp, target);
const hook = persistedConfigInitializationAfterPublishForTests;
persistedConfigInitializationAfterPublishForTests = null;
hook?.();
if (descriptor !== undefined) {
const published = lstatSync(target);
const opened = fstatSync(descriptor);
if (opened.dev !== published.dev || opened.ino !== published.ino) {
throw new Error("atomic initialization published target identity changed");
}
}
} catch (cause) {
const code = cause && typeof cause === "object" && "code" in cause
? String((cause as { code?: unknown }).code) : "";
if (code === "EOPNOTSUPP" || code === "EXDEV" || code === "EPERM") {
throw new PersistedConfigInitializationHardLinkUnavailableError({ cause });
}
throw cause;
}
},
truncate: target => {
if (descriptor !== undefined) ftruncateSync(descriptor, 0);
else truncateSync(target, 0);
},
unlink: unlinkSync,
close: () => { if (descriptor !== undefined) { closeSync(descriptor); descriptor = undefined; } },
};
}

export function initializePersistedConfigIfMissing(
config: OcxConfig,
io = defaultPersistedConfigInitializationIO(getConfigPath()),
): PersistedConfigInitializationOutcome {
assertNotRealHomeUnderTest(getConfigDir());
return withConfigMutationLockSync(() => {
const snapshot = readConfigFileSnapshot();
if (snapshot.diagnostics.source === "file") return "exists";
if (snapshot.diagnostics.source !== "default") return "invalid";
const projected = projectCustomModelCatalogMigration(readRawConfigJson(), projectConfigRebaseProvenance(config));
if (!publishInitialConfigNoReplace(projected, io)) {
const winner = readConfigFileSnapshot();
return winner.diagnostics.source === "file" ? "exists" : "invalid";
}
bumpGenerationForCooperatingConfigWrite();
adoptCustomModelCatalogMigration(config, projected);
if (projected.configRebaseProvenance === undefined) delete config.configRebaseProvenance;
else config.configRebaseProvenance = structuredClone(projected.configRebaseProvenance);
clearPendingConfigTopLevelDeletions(config);
return "created";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
}

/**
* Read the generation from the transaction that is open RIGHT NOW.
*
Expand Down
Loading
Loading