UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

835 lines (834 loc) • 40.4 kB
import { w as root } from "./fs-safe-BUXBSN-4.mjs"; import { i as formatInvalidConfigDetails, r as createInvalidConfigError } from "./io.invalid-config-BzeD3bi5.mjs"; import { r as resolveConfigEnvVars } from "./env-substitution-mBnQ8cYd.mjs"; import { r as isMissingPathError } from "./errno-FR4CRyya.mjs"; import { l as resolveConfigIncludeWritePath, n as ConfigIncludeError, o as hashConfigIncludeRaw, p as isPathInside, s as isInternalIncludeWriteTarget } from "./includes-BkzfmxST.mjs"; import { t as parseJsonWithJson5Fallback } from "./parse-json-compat-BBtWoq5_.mjs"; import { m as resolveConfigPath } from "./paths-DGIwWbpu.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { t as _usingCtx } from "./usingCtx-DwOBYOJf.mjs"; import { m as withConfigReadEnvChanges, o as createConfigRuntimeEnvBase, r as cloneEnvWithPlatformSemantics, s as getPublishedConfigRuntimeEnvState, t as applyConfigEnvVars } from "./config-env-vars-DjPHKieF.mjs"; import { L as projectIncludeModelPolicyWrite } from "./agent-scope-config-CDYpzA0r.mjs"; import { i as hashConfigRaw, n as containsConfigIncludeDirective, p as resolveConfigSnapshotHash } from "./io.read-helpers-Bvdjf9tk.mjs"; import { m as withDeferredPluginMigrationsCurrent, o as readConfigWritePendingMigrations, p as withDeferredPluginConfigRollback } from "./deferred-plugin-migrations-by1ypkxx.mjs"; import { s as resolveManagedRuntimeEnvBaseline, t as createConfigIO } from "./io.factory-0ZgSoWaa.mjs"; import { S as hasWebhookMigrationProgress, _ as captureConfigWriteLockGuard, b as withConfigWriteLock, h as rollbackConfigFileWriteIfUnchanged, l as createConfigFileWriteGuard, o as captureConfigFileWritePathProof, r as warnIfJSON5CommentsWillBeStripped, v as markActiveConfigMutationPath } from "./io.warnings-BL_WO_iY.mjs"; import { t as getConfigValueAtPath } from "./config-paths-Bb7WEbv8.mjs"; import { t as ConfigMutationConflictError } from "./mutation-conflict-Be0wSyDG.mjs"; import { n as createConfigWriteAuthorityGuard } from "./write-authority-BBYsD_pp.mjs"; import { h as recordUpdateDoctorConfigFileWrite, o as assertUpdateDoctorConfigInputHash } from "./update-doctor-result-b1ffujJO.mjs"; import { r as assertConfigWriteAllowedInCurrentMode } from "./config-write-guard-6uPuICvJ.mjs"; import { c as resolveManagedUnsetPathsForWrite, i as preserveDeferredPluginMigrationConfig, o as setDeferredPluginMigrationConfigFacts, s as applyUnsetPathsForWrite } from "./deferred-plugin-migration-config-Db8uALnk.mjs"; import { n as getLegacyTopLevelIncludeBoundary, r as resolveIncludeWriteBoundary, t as collectChangedConfigPaths } from "./include-write-boundary-Cv161pwQ.mjs"; import { S as resolveConfigWriteFollowUp, c as getRuntimeConfigSnapshotRefreshHandler, g as preflightRuntimeSnapshotWrite, h as preflightManagedRuntimeConfigWrite, i as finalizeRuntimeSnapshotWrite, l as getRuntimeConfigSourceSnapshot, o as getRuntimeConfigSnapshot, u as hasManagedRuntimeConfigWriteOwner, x as resolveConfigWriteAfterWrite } from "./runtime-snapshot-DYptB0Cf.mjs"; import { r as projectLegacyRuntimeConfigWrite } from "./runtime-source-projection-rvQFgz1Z.mjs"; import { r as validateConfigObjectWithPlugins } from "./validation-CiziKiqX.mjs"; import { t as GATEWAY_CONFIG_SELECTION_ENV_KEYS } from "./gateway-env-selection-B-LO0cRL.mjs"; import { i as resolveWriteEnvSnapshotForPath, n as configWriteCommittedSnapshot } from "./io.types-E3Nszhk8.mjs"; import { o as recoverConfigWriteFailure } from "./io.write-errors-DuSgsySH.mjs"; import { D as publishRuntimeConfigWrite, S as writeConfigFile, u as readConfigFileSnapshotForWrite, w as copyRuntimeConfigWriteApplication } from "./io.runtime-BsUXSsD4.mjs"; import "./io-BYWYXJXl.mjs"; import { i as projectConfigWriteSource, n as prepareConfigWriteValues, t as injectExplicitlySetPaths } from "./io.write-prepare-DWaupA4w.mjs"; import { n as prepareConfigFileWrite } from "./backup-rotation-C9pxGn3m.mjs"; import { t as rejectConfigNonFiniteNumbers } from "./value-tree-3oLf9xok.mjs"; import fs from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; import { isDeepStrictEqual } from "node:util"; //#region src/config/mutate.include-io.ts function formatJsonFileValue(value) { rejectConfigNonFiniteNumbers(value); return `${JSON.stringify(value, null, 2)}\n`; } async function resolveRootBoundIncludeFile(params) { const absolutePath = resolveConfigIncludeWritePath(params); const candidateRoots = [path.dirname(params.configPath), ...params.allowedRoots]; for (const candidateRoot of candidateRoots) { const rootReal = await fs$1.realpath(candidateRoot).catch(() => null); if (!rootReal || !isPathInside(rootReal, absolutePath)) continue; const relativePath = path.relative(rootReal, absolutePath); if (!relativePath || path.isAbsolute(relativePath) || relativePath.split(path.sep)[0] === "..") continue; return { absolutePath, relativePath, root: await root(rootReal, { hardlinks: "reject", mkdir: true, mode: 384, symlinks: "reject" }) }; } throw new Error(`Config include write path has no approved existing root: ${absolutePath}`); } async function resolveExpectedRootBoundIncludeFile(params) { let target; try { target = await resolveRootBoundIncludeFile(params); } catch (error) { if (error instanceof ConfigIncludeError || error instanceof Error && error.message.startsWith("Config include write path has no approved existing root:")) throw new ConfigMutationConflictError("included config target changed since last load"); throw error; } if (path.normalize(target.absolutePath) !== path.normalize(params.expectedAbsolutePath)) throw new ConfigMutationConflictError("included config target changed since last load"); return target; } async function readRootBoundFileRawIfExists(target) { try { return await target.root.readText(target.relativePath); } catch (error) { if (isMissingPathError(error)) return null; throw error; } } async function assertRootConfigStillMatchesSnapshot(snapshot) { let currentRaw = null; try { currentRaw = await fs$1.readFile(snapshot.path, "utf-8"); } catch (error) { if (!isMissingPathError(error)) throw error; } if (hashConfigIncludeRaw(currentRaw) !== hashConfigIncludeRaw(snapshot.exists ? snapshot.raw ?? null : null)) throw new ConfigMutationConflictError("config changed while preparing include write"); } async function assertIncludeGraphStillMatchesSnapshot(params) { await assertRootConfigStillMatchesSnapshot(params.snapshot); for (const [includePath, capturedHash] of Object.entries(params.writeOptions?.includeFileHashesForWrite ?? {})) { const expectedTarget = params.writeOptions?.includeFileTargetsForWrite?.[includePath]; if (!expectedTarget) throw new ConfigMutationConflictError("included config target changed since last load"); const target = await resolveExpectedRootBoundIncludeFile({ configPath: params.snapshot.path, includePath, allowedRoots: [path.dirname(expectedTarget)], expectedAbsolutePath: expectedTarget }); const expectedHash = includePath === params.includePath ? params.includeHash : capturedHash; if (hashConfigIncludeRaw(await readRootBoundFileRawIfExists(target)) !== expectedHash) throw new ConfigMutationConflictError("included config changed while preparing write"); } } async function rollbackJsonFileWriteIfUnchanged(params) { const restored = await rollbackConfigFileWriteIfUnchanged({ configPath: params.target.absolutePath, previousSnapshot: { path: params.target.absolutePath, exists: params.previousRaw !== null, raw: params.previousRaw }, committedHash: hashConfigRaw(params.committedRaw), fsModule: fs, ...params.pathProof.captureRollbackProof(), preserveDirectoryMode: true, durable: true, destinationHardlinks: "reject", withPublication: params.pathProof.withPublication }); if (restored) recordUpdateDoctorConfigFileWrite(params.target.absolutePath, hashConfigRaw(params.committedRaw), hashConfigRaw(params.previousRaw)); return restored; } async function writeRootBoundJsonFile(params) { params.assertConfigPathForWrite(); await params.preCommitRuntimePreflight?.(); params.assertConfigPathForWrite(); const targetAtCommit = await resolveExpectedRootBoundIncludeFile({ configPath: params.configPath, includePath: params.includePath, allowedRoots: params.allowedRoots, expectedAbsolutePath: params.expectedTargetPath }); params.assertConfigPathForWrite(); await params.assertIncludeGraphForWrite(); params.assertConfigPathForWrite(); const currentRaw = await readRootBoundFileRawIfExists(targetAtCommit); params.assertConfigPathForWrite(); if (hashConfigIncludeRaw(currentRaw) !== hashConfigIncludeRaw(params.expectedRaw)) throw new ConfigMutationConflictError("included config changed while preparing write"); const pathProof = captureConfigFileWritePathProof(params.includePath, targetAtCommit.absolutePath, fs); const assertCurrent = createConfigWriteAuthorityGuard(() => { params.assertConfigPathForWrite(); pathProof.assertCurrent(); }); const content = formatJsonFileValue(params.value); params.assertConfigPathForWrite(); warnIfJSON5CommentsWillBeStripped({ raw: currentRaw, filePath: targetAtCommit.absolutePath, skipOutputLogs: params.skipOutputLogs }); const publication = { phase: "unpublished" }; const writeGuard = createConfigFileWriteGuard(targetAtCommit.absolutePath, fs, assertCurrent, { snapshot: { path: targetAtCommit.absolutePath, exists: currentRaw !== null, raw: currentRaw }, includeGraph: params.includeGraph, targetPathProof: pathProof, preserveDirectoryMode: true, onRootRemoved: () => { publication.phase = "removed"; }, onRootPublished: () => { publication.phase = "published"; } }); const publicationProof = { ...pathProof, assertCurrent: () => { params.assertOwnerForRollback(); pathProof.assertCurrent(); writeGuard.assertPublishedIdentity(); }, captureRollbackProof: () => writeGuard.captureRollbackProof(params.assertOwnerForRollback), withPublication: (publish, didMutate) => withDeferredPluginConfigRollback({ configPath: params.configPath, env: params.env, assertCurrent: params.assertOwnerForRollback }, publish, didMutate) }; try { try { var _usingCtx$1 = _usingCtx(); const preparedFile = _usingCtx$1.a(await prepareConfigFileWrite({ configPath: targetAtCommit.absolutePath, previousRaw: currentRaw, content, fsModule: writeGuard.fileSystem, assertCurrent: writeGuard.assertCurrent, assertBeforeMutation: writeGuard.assertBeforeMutation, onDestinationState: writeGuard.onDestinationState, destinationHardlinks: "reject", durable: true })); await params.beforeCommit?.(); writeGuard.assertCurrent(); withDeferredPluginMigrationsCurrent({ env: params.env, configPath: params.configPath, expectedPending: params.deferredPluginMigrations }, () => { preparedFile.publish(); publication.phase = "published"; recordUpdateDoctorConfigFileWrite(targetAtCommit.absolutePath, hashConfigRaw(currentRaw), hashConfigRaw(content)); }); await params.assertIncludeGraphForWrite(hashConfigIncludeRaw(content)); writeGuard.assertCurrent(); writeGuard.assertPublishedIdentity(); } catch (_) { _usingCtx$1.e = _; } finally { await _usingCtx$1.d(); } } catch (error) { if (publication.phase === "unpublished") throw error; return await recoverConfigWriteFailure({ configPath: targetAtCommit.absolutePath, publication: publication.phase === "removed" ? "partial" : "complete", cause: error, restoreFile: () => rollbackJsonFileWriteIfUnchanged({ target: targetAtCommit, previousRaw: currentRaw, committedRaw: publication.phase === "published" ? content : null, pathProof: publicationProof }) }); } return publicationProof; } //#endregion //#region src/config/mutate.ts const DEFAULT_CONFIG_MUTATION_RETRY_ATTEMPTS = 5; function assertManagedRuntimeEnvGeneration(generation) { if (getPublishedConfigRuntimeEnvState().generation !== generation) throw new ConfigMutationConflictError("active config environment changed while preparing write"); } function assertBaseHashMatches(snapshot, expectedHash) { const currentHash = resolveConfigSnapshotHash(snapshot) ?? null; if (expectedHash !== void 0 && expectedHash !== currentHash) throw new ConfigMutationConflictError("config changed since last load"); return currentHash; } function assertExpectedConfigPathMatches(snapshot, expectedConfigPath) { if (expectedConfigPath !== void 0 && expectedConfigPath !== snapshot.path) throw new ConfigMutationConflictError("config path changed since last load", { retryable: false }); } /** Serialize config writers without requiring a schema-valid snapshot. */ async function withConfigMutationLock(params, fn) { const assertCurrent = params.assertCurrent; assertCurrent?.(); return params.io ? await fn() : await withConfigWriteLock(params.lockPath ?? resolveConfigPath(), fn, void 0, assertCurrent); } async function readConfigSnapshotForMutation(params) { const options = { ...params.writeOptions?.skipPluginValidation ? { skipPluginValidation: true } : {}, ...params.writeOptions?.observe === false ? { observe: false } : {} }; if (params.io) return await params.io.readConfigFileSnapshotForWrite(options); if (params.ownedConfigPathForWrite) { const ioOptions = { configPath: params.ownedConfigPathForWrite, ...params.writeOptions?.skipPluginValidation ? { pluginValidation: "skip" } : {}, ...params.writeOptions?.observe === false ? { observe: false } : {}, ...params.writeOptions?.preservedLegacyRootKeys ? { preservedLegacyRootKeys: params.writeOptions.preservedLegacyRootKeys } : {} }; return await (hasManagedRuntimeConfigWriteOwner(params.ownedConfigPathForWrite) ? createConfigIO({ ...ioOptions, env: createConfigRuntimeEnvBase(resolveManagedRuntimeEnvBaseline().sourceConfig, process.env, { preservedKeys: GATEWAY_CONFIG_SELECTION_ENV_KEYS }) }) : createConfigIO(ioOptions)).readConfigFileSnapshotForWrite(); } return await readConfigFileSnapshotForWrite(options); } function mergeConfigMutationWriteOptions(prepared, caller) { const merged = copyRuntimeConfigWriteApplication(caller, { ...prepared, ...caller }); const capturedGuard = prepared.assertConfigPathForWrite; const callerGuard = caller?.assertConfigPathForWrite; if (capturedGuard && callerGuard && capturedGuard !== callerGuard) merged.assertConfigPathForWrite = () => { capturedGuard(); callerGuard(); }; else if (capturedGuard) merged.assertConfigPathForWrite = capturedGuard; return merged; } function createConfigMutationOwnership(prepared, writeOptions) { const mergedWriteOptions = mergeConfigMutationWriteOptions(prepared.writeOptions, writeOptions); return { expectedConfigPath: mergedWriteOptions.expectedConfigPath ?? prepared.snapshot.path, ownedConfigPathForWrite: mergedWriteOptions.ownedConfigPathForWrite, assertConfigPathForWrite: mergedWriteOptions.assertConfigPathForWrite }; } async function withConfigMutationSnapshotLock(writeOptions, fn) { let lockPath = path.resolve(writeOptions?.ownedConfigPathForWrite ?? resolveConfigPath()); for (let attempt = 0; attempt < 3; attempt += 1) { writeOptions?.assertConfigPathForWrite?.(); const outcome = await withConfigMutationLock({ lockPath, assertCurrent: writeOptions?.assertCurrent }, async () => { const prepared = await readConfigSnapshotForMutation({ ownedConfigPathForWrite: writeOptions?.ownedConfigPathForWrite, writeOptions }); captureConfigWriteLockGuard(lockPath)?.(); writeOptions?.assertConfigPathForWrite?.(); const preparedPath = path.resolve(prepared.snapshot.path); if (preparedPath !== lockPath) return { done: false, lockPath: preparedPath }; return { done: true, value: await fn(prepared) }; }); if (outcome.done) return outcome.value; lockPath = outcome.lockPath; } throw new ConfigMutationConflictError("config path changed repeatedly while acquiring lock", { retryable: false }); } /** * Run a multi-phase operation under the canonical cross-process write lock. * Nested mutation helpers are reentrant through activeConfigMutationLocks. */ async function withConfigMutationExclusive(fn) { return await withConfigMutationSnapshotLock(void 0, (prepared) => fn(prepared.snapshot.sourceConfig)); } /** * One include-owned write decision for the guarded writer and Doctor's * eligibility check; a split decision lets Doctor advertise an include write * that the writer then declines. Null means the write belongs to the root writer. */ function resolveIncludeOwnedWriteCandidate(params) { if (params.writeOptions?.persistCanonicalAgentRoster === true || hasWebhookMigrationProgress(params.snapshot.sourceConfig, params.nextConfig)) return null; const projection = { inputBasis: params.writeOptions?.inputBase === "source" ? { kind: "source", config: params.snapshot.sourceConfig } : void 0, runtimeConfig: params.snapshot.runtimeConfig, sourceConfig: params.snapshot.sourceConfig, nextConfig: !params.io && params.writeOptions?.inputBase === void 0 ? projectLegacyRuntimeConfigWrite(params.nextConfig) : params.nextConfig, unsetPaths: resolveManagedUnsetPathsForWrite(params.writeOptions?.unsetPaths), explicitSetPaths: params.writeOptions?.explicitSetPaths, explicitSetValueSource: params.writeOptions?.explicitSetValueSource ?? params.nextConfig }; const requestedConfig = applyUnsetPathsForWrite(injectExplicitlySetPaths({ ...projection, valueSource: projection.explicitSetValueSource, persistedCandidate: projectConfigWriteSource(projection) }), projection.unsetPaths); const values = prepareConfigWriteValues({ snapshot: params.snapshot, nextConfig: requestedConfig, writeOptions: params.writeOptions, env: params.io?.env ?? process.env, explicitSetPaths: params.writeOptions?.explicitSetPaths }); const nextConfig = projectIncludeModelPolicyWrite({ config: values.authoredConfig, previousConfig: params.snapshot.sourceConfig, explicitSetPaths: params.writeOptions?.explicitSetPaths }); let changed = collectChangedConfigPaths(values.authoredSourceConfig, nextConfig); if (changed.paths.length === 0 && !changed.rootChanged && nextConfig !== values.authoredConfig) changed = collectChangedConfigPaths(values.authoredSourceConfig, values.authoredConfig); if (changed.rootChanged || changed.paths.length === 0) return null; const boundary = params.snapshot.includeProvenance === void 0 ? getLegacyTopLevelIncludeBoundary({ snapshot: params.snapshot, changed }) : resolveIncludeWriteBoundary({ provenance: params.snapshot.includeProvenance, changed }); if (!boundary || getConfigValueAtPath(nextConfig, [...boundary.boundaryPath]) === void 0) return null; if (params.writeOptions?.allowIncludeAncestorExplicitSetPaths && params.writeOptions.explicitSetPaths?.some((segments) => segments.length > boundary.boundaryPath.length && boundary.boundaryPath.every((part, index) => part === segments[index]))) return null; return { nextConfig, ...boundary, includePath: path.normalize(boundary.includePath) }; } /** * Resolve the authored $include file that solely owns every changed path of this * write. Callers that add root-level metadata before persisting (Doctor wizard * state) must consult this first: an extra root key would push the change set * outside the boundary and force the guarded root writer to reject the write. */ function resolveConfigIncludeWriteBoundary(params) { const includeWrite = resolveIncludeOwnedWriteCandidate({ snapshot: params.snapshot, nextConfig: params.nextConfig, writeOptions: { inputBase: "source", persistCanonicalAgentRoster: params.persistCanonicalAgentRoster, explicitSetPaths: params.explicitSetPaths } }); return includeWrite && isInternalIncludeWriteTarget({ configPath: params.snapshot.path, includePath: includeWrite.includePath }) ? { boundaryPath: includeWrite.boundaryPath, includePath: includeWrite.includePath } : null; } function snapshotProvesBrokenInclude(snapshot, includePath) { return !snapshot.valid && snapshot.issues.some((issue) => /Failed to (?:read|parse) include file:/.test(issue.message) && issue.message.includes(includePath)); } async function tryWriteIncludeOwnedConfigMutation(params) { const includeWrite = resolveIncludeOwnedWriteCandidate({ snapshot: params.snapshot, nextConfig: params.nextConfig, writeOptions: params.writeOptions, io: params.io }); if (!includeWrite) return null; const { nextConfig, boundaryPath, includePath } = includeWrite; const rootGuard = captureConfigWriteLockGuard(params.snapshot.path); const assertOwner = createConfigWriteAuthorityGuard(params.writeOptions?.assertCurrent, rootGuard); assertOwner(); const writeEnv = params.io?.env ?? process.env; const allowedRoots = []; const expectedIncludeTarget = params.writeOptions?.includeFileTargetsForWrite?.[includePath]; if (!expectedIncludeTarget) throw new ConfigMutationConflictError("included config target changed since last load"); const assertConfigPathForWrite = params.writeOptions?.assertConfigPathForWrite; if (!assertConfigPathForWrite) return null; assertConfigPathForWrite(); const configRoot = await fs$1.realpath(path.dirname(params.snapshot.path)); assertOwner(); if (!isPathInside(configRoot, expectedIncludeTarget)) throw new Error(`Config mutation cannot update external $include target ${includePath}; edit the included file directly or move it under the config directory.`); return await withConfigWriteLock(params.snapshot.path, () => withConfigWriteLock(expectedIncludeTarget, async () => { const includeGuard = captureConfigWriteLockGuard(expectedIncludeTarget); const assertScopedOwner = createConfigWriteAuthorityGuard(assertOwner, includeGuard); assertScopedOwner(); const includeTarget = await resolveExpectedRootBoundIncludeFile({ configPath: params.snapshot.path, includePath, allowedRoots, expectedAbsolutePath: expectedIncludeTarget }); assertScopedOwner(); const previousIncludeRaw = await readRootBoundFileRawIfExists(includeTarget); assertScopedOwner(); const previousIncludeHash = hashConfigIncludeRaw(previousIncludeRaw); const expectedIncludeHash = params.writeOptions?.includeFileHashesForWrite?.[includePath]; if (expectedIncludeHash !== void 0 && expectedIncludeHash !== previousIncludeHash) throw new ConfigMutationConflictError("included config changed since last load"); const envForRestore = resolveWriteEnvSnapshotForPath({ actualConfigPath: params.snapshot.path, expectedConfigPath: params.writeOptions?.expectedConfigPath, envSnapshotForRestore: params.writeOptions?.envSnapshotForRestore }) ?? params.io?.env ?? process.env; const snapshotHasBrokenInclude = snapshotProvesBrokenInclude(params.snapshot, includePath); if (previousIncludeRaw === null && (!snapshotHasBrokenInclude || expectedIncludeHash === void 0)) throw new ConfigMutationConflictError("included config changed since last load"); const includedValueToWrite = getConfigValueAtPath(nextConfig, [...boundaryPath]); if (previousIncludeRaw !== null) { let authoredIncludeValue; let parsedInclude = false; try { authoredIncludeValue = parseJsonWithJson5Fallback(previousIncludeRaw); parsedInclude = true; } catch { if (!snapshotHasBrokenInclude || expectedIncludeHash === void 0) throw new ConfigMutationConflictError("included config changed since last load"); } if (parsedInclude) { if (containsConfigIncludeDirective(authoredIncludeValue)) return null; const currentIncludedValue = resolveConfigEnvVars(authoredIncludeValue, envForRestore, { onMissing: () => {} }); const authoredSnapshotSource = params.snapshot.sourceConfigBeforeMigrations ?? params.snapshot.sourceConfig; const snapshotIncludedValue = getConfigValueAtPath(authoredSnapshotSource, [...boundaryPath]); if (!isDeepStrictEqual(currentIncludedValue, snapshotIncludedValue)) throw new ConfigMutationConflictError("included config changed since last load"); } } const deferRuntimeActivation = hasManagedRuntimeConfigWriteOwner(params.snapshot.path); const runtimeEnvBaseline = deferRuntimeActivation ? resolveManagedRuntimeEnvBaseline() : void 0; const runtimeCandidateEnv = runtimeEnvBaseline ? createConfigRuntimeEnvBase(runtimeEnvBaseline.sourceConfig, process.env, { preservedKeys: GATEWAY_CONFIG_SELECTION_ENV_KEYS }) : cloneEnvWithPlatformSemantics(writeEnv); applyConfigEnvVars(nextConfig, runtimeCandidateEnv); const runtimeConfigToWrite = resolveConfigEnvVars(nextConfig, runtimeCandidateEnv, { onMissing: () => {} }); const validated = validateConfigObjectWithPlugins(runtimeConfigToWrite, { ...params.writeOptions?.skipPluginValidation ? { pluginValidation: "skip" } : {}, deferredPluginMigrations: params.deferredPluginMigrations }); if (!validated.ok) throw createInvalidConfigError(params.snapshot.path, formatInvalidConfigDetails(validated.issues)); const runtimeConfigSnapshot = getRuntimeConfigSnapshot(); const runtimeConfigSourceSnapshot = getRuntimeConfigSourceSnapshot(); const hadRuntimeSnapshot = Boolean(runtimeConfigSnapshot); const hadBothSnapshots = Boolean(runtimeConfigSnapshot && runtimeConfigSourceSnapshot); let managedPreparedCandidates = /* @__PURE__ */ new Map(); let runtimePreflightResult; if (runtimeEnvBaseline) { managedPreparedCandidates = await preflightManagedRuntimeConfigWrite(params.snapshot.path, runtimeConfigToWrite, params.writeOptions?.runtimeRefresh); assertManagedRuntimeEnvGeneration(runtimeEnvBaseline.generation); } else runtimePreflightResult = await preflightRuntimeSnapshotWrite({ nextSourceConfig: runtimeConfigToWrite, refreshOptions: params.writeOptions?.runtimeRefresh, formatRefreshError: (error) => formatErrorMessage(error), createRefreshError: (detail, cause) => new Error(`Config write blocked before committing ${includePath}: active SecretRef resolution failed: ${detail}`, { cause }) }); assertScopedOwner(); const committedIncludeRaw = formatJsonFileValue(includedValueToWrite); const committedIncludeHash = hashConfigIncludeRaw(committedIncludeRaw); const callerPreCommit = params.writeOptions?.preCommitRuntimePreflight; const assertIncludeGraphForWrite = (includeHash = previousIncludeHash) => assertIncludeGraphStillMatchesSnapshot({ snapshot: params.snapshot, writeOptions: params.writeOptions, includePath, includeHash }); const pathProof = await writeRootBoundJsonFile({ env: writeEnv, deferredPluginMigrations: params.deferredPluginMigrations, configPath: params.snapshot.path, includePath, allowedRoots, expectedTargetPath: expectedIncludeTarget, value: includedValueToWrite, expectedRaw: previousIncludeRaw, includeGraph: { hashes: { ...params.writeOptions?.includeFileHashesForWrite, [params.snapshot.path]: hashConfigIncludeRaw(params.snapshot.raw) }, targets: { ...params.writeOptions?.includeFileTargetsForWrite, [params.snapshot.path]: fs.realpathSync(params.snapshot.path) } }, assertIncludeGraphForWrite, assertConfigPathForWrite: () => { assertScopedOwner(); assertConfigPathForWrite(); if (runtimeEnvBaseline) assertManagedRuntimeEnvGeneration(runtimeEnvBaseline.generation); }, assertOwnerForRollback: assertScopedOwner, beforeCommit: params.writeOptions?.beforeCommit, skipOutputLogs: params.writeOptions?.skipOutputLogs, preCommitRuntimePreflight: async () => { await callerPreCommit?.(runtimeConfigToWrite); } }); const assertPostCommitCurrent = () => { pathProof.assertCurrent(); assertConfigPathForWrite(); }; let restorePostWriteEnv; try { assertPostCommitCurrent(); if (runtimeEnvBaseline) assertManagedRuntimeEnvGeneration(runtimeEnvBaseline.generation); if (params.writeOptions?.skipRuntimeSnapshotRefresh && !hadRuntimeSnapshot && !getRuntimeConfigSnapshotRefreshHandler()) return { persistedHash: null, persistedConfig: runtimeConfigToWrite, persistedSourceConfig: runtimeConfigToWrite }; const refreshed = await withConfigReadEnvChanges(writeEnv, (restore) => { restorePostWriteEnv = restore; return withConfigWriteLock(params.snapshot.path, () => readConfigSnapshotForMutation({ ownedConfigPathForWrite: params.snapshot.path, io: params.io, writeOptions: { ...params.writeOptions, observe: false } }), writeEnv, assertPostCommitCurrent); }); assertPostCommitCurrent(); const refreshedSnapshot = refreshed.snapshot; await assertIncludeGraphForWrite(committedIncludeHash); assertPostCommitCurrent(); assertExpectedConfigPathMatches(refreshedSnapshot, params.snapshot.path); const persistedHash = resolveConfigSnapshotHash(refreshedSnapshot); if (!refreshedSnapshot.valid) throw createInvalidConfigError(params.snapshot.path, formatInvalidConfigDetails(refreshedSnapshot.issues)); if (!persistedHash) throw new Error(`No persisted config hash was available after rereading ${params.snapshot.path}.`); const notifyCommittedWrite = () => { publishRuntimeConfigWrite({ configPath: params.snapshot.path, snapshot: refreshedSnapshot, sourceConfig: refreshedSnapshot.sourceConfig, runtimeConfig: refreshedSnapshot.runtimeConfig, persistedHash, deferRuntimeActivation, preparedCandidates: managedPreparedCandidates, writeOptions: params.writeOptions, afterWrite: params.afterWrite }); }; if (runtimeEnvBaseline) assertManagedRuntimeEnvGeneration(runtimeEnvBaseline.generation); await finalizeRuntimeSnapshotWrite({ assertCurrent: assertPostCommitCurrent, nextSourceConfig: refreshedSnapshot.sourceConfig, refreshOptions: params.writeOptions?.runtimeRefresh, hadBothSnapshots, freshConfig: refreshedSnapshot.runtimeConfig, notifyCommittedWrite, preflightResult: runtimePreflightResult, deferRuntimeActivation, formatRefreshError: (error) => formatErrorMessage(error), createRefreshError: (detail, cause) => new Error(`runtime snapshot refresh failed: ${detail}`, { cause }) }); assertPostCommitCurrent(); return { persistedHash, persistedConfig: refreshedSnapshot.sourceConfig, persistedSourceConfig: runtimeConfigToWrite }; } catch (error) { return await recoverConfigWriteFailure({ configPath: includeTarget.absolutePath, cause: error, restoreFile: () => rollbackJsonFileWriteIfUnchanged({ target: includeTarget, previousRaw: previousIncludeRaw, committedRaw: committedIncludeRaw, pathProof }), restoreEffects: () => { assertScopedOwner(); restorePostWriteEnv?.(); } }); } }, writeEnv, assertOwner), writeEnv, assertOwner); } async function replaceConfigFile(params) { params.writeOptions?.assertConfigPathForWrite?.(); if (!params.snapshot && !params.io) return await withConfigMutationSnapshotLock(params.writeOptions, async (prepared) => await replaceConfigFileUnlocked({ ...params, snapshot: prepared.snapshot, writeOptions: mergeConfigMutationWriteOptions(prepared.writeOptions, params.writeOptions) })); return await withConfigMutationLock({ io: params.io, lockPath: params.snapshot?.path, assertCurrent: params.writeOptions?.assertCurrent }, async () => await replaceConfigFileUnlocked(params)); } async function replaceConfigFileUnlocked(params) { const { snapshot, writeOptions } = params.snapshot ? { snapshot: params.snapshot, writeOptions: params.writeOptions ?? {} } : await readConfigSnapshotForMutation({ io: params.io, writeOptions: params.writeOptions }); const deferredPluginMigrations = readConfigWritePendingMigrations(snapshot.path, params.io?.env); const mergedWriteOptions = mergeConfigMutationWriteOptions(writeOptions, params.writeOptions); const nextConfig = preserveDeferredPluginMigrationConfig({ sourceConfig: snapshot.sourceConfig, nextConfig: params.sourceConfig ?? params.nextConfig, pending: deferredPluginMigrations, writeOptions: mergedWriteOptions }); mergedWriteOptions.inputBase = params.sourceConfig ? "source" : mergedWriteOptions.inputBase; mergedWriteOptions.assertConfigPathForWrite?.(); assertExpectedConfigPathMatches(snapshot, mergedWriteOptions.expectedConfigPath); assertConfigWriteAllowedInCurrentMode({ configPath: snapshot.path }); markActiveConfigMutationPath(snapshot.path); assertUpdateDoctorConfigInputHash(snapshot.path, hashConfigRaw(snapshot.raw)); const previousHash = assertBaseHashMatches(snapshot, params.baseHash); const afterWrite = resolveConfigWriteAfterWrite(params.afterWrite ?? params.writeOptions?.afterWrite); let writeResult = await tryWriteIncludeOwnedConfigMutation({ snapshot, nextConfig, deferredPluginMigrations, afterWrite, writeOptions: mergedWriteOptions, io: params.io }); if (writeResult) setDeferredPluginMigrationConfigFacts(writeResult.persistedConfig, deferredPluginMigrations); else { const fallbackWriteOptions = copyRuntimeConfigWriteApplication(mergedWriteOptions, { baseSnapshot: snapshot, ...mergedWriteOptions, afterWrite }); const ioPreCommitRuntimePreflight = params.io ? fallbackWriteOptions.preCommitRuntimePreflight : void 0; if (params.io) fallbackWriteOptions.preCommitRuntimePreflight = async (sourceConfig) => { await preflightRuntimeSnapshotWrite({ nextSourceConfig: sourceConfig, refreshOptions: fallbackWriteOptions.runtimeRefresh, formatRefreshError: (error) => formatErrorMessage(error), createRefreshError: (detail, cause) => new Error(`Config write blocked before committing ${snapshot.path}: active SecretRef resolution failed: ${detail}`, { cause }) }); await ioPreCommitRuntimePreflight?.(sourceConfig); }; const written = await (params.io?.writeConfigFile ?? writeConfigFile)(nextConfig, fallbackWriteOptions); const committed = written?.[configWriteCommittedSnapshot]; writeResult = { persistedHash: committed?.hash ?? (written && !containsConfigIncludeDirective(written.persistedConfig) ? written.persistedHash : null), persistedConfig: committed?.sourceConfig ?? written?.persistedConfig ?? nextConfig, persistedSourceConfig: written?.persistedSourceConfig }; } return { path: snapshot.path, previousHash, snapshot, nextConfig: writeResult.persistedConfig, persistedHash: writeResult.persistedHash, persistedSourceConfig: writeResult.persistedSourceConfig, afterWrite, followUp: resolveConfigWriteFollowUp(afterWrite) }; } async function commitPreparedConfigMutation(params) { const result = await replaceConfigFileUnlocked({ nextConfig: params.nextConfig, snapshot: params.snapshot, baseHash: params.baseHash, writeOptions: copyRuntimeConfigWriteApplication(params.writeOptions, { ...params.writeOptions, afterWrite: params.afterWrite }), io: params.io }); return { config: result.nextConfig, persistedHash: result.persistedHash, persistedSourceConfig: result.persistedSourceConfig, afterWrite: result.afterWrite }; } async function transformConfigFileAttempt(params, attempt, ownership, prepared) { ownership?.assertConfigPathForWrite?.(); const { snapshot, writeOptions } = prepared ?? await readConfigSnapshotForMutation({ ...ownership?.ownedConfigPathForWrite ? { ownedConfigPathForWrite: ownership.ownedConfigPathForWrite } : {}, io: params.io, writeOptions: params.writeOptions }); let mergedWriteOptions = mergeConfigMutationWriteOptions(writeOptions, params.writeOptions); if (ownership) { if (ownership.expectedConfigPath === void 0) { ownership.expectedConfigPath = mergedWriteOptions.expectedConfigPath ?? snapshot.path; ownership.ownedConfigPathForWrite = mergedWriteOptions.ownedConfigPathForWrite; ownership.assertConfigPathForWrite = mergedWriteOptions.assertConfigPathForWrite; } mergedWriteOptions = copyRuntimeConfigWriteApplication(mergedWriteOptions, { ...mergedWriteOptions, expectedConfigPath: ownership.expectedConfigPath, ...ownership.ownedConfigPathForWrite ? { ownedConfigPathForWrite: ownership.ownedConfigPathForWrite } : {}, ...ownership.assertConfigPathForWrite ? { assertConfigPathForWrite: ownership.assertConfigPathForWrite } : {} }); } mergedWriteOptions.assertConfigPathForWrite?.(); assertExpectedConfigPathMatches(snapshot, mergedWriteOptions.expectedConfigPath); assertConfigWriteAllowedInCurrentMode({ configPath: snapshot.path }); markActiveConfigMutationPath(snapshot.path); const previousHash = assertBaseHashMatches(snapshot, params.baseHash); const baseConfig = params.base === "runtime" ? snapshot.runtimeConfig : snapshot.sourceConfig; mergedWriteOptions.inputBase = params.base ?? "source"; const afterWrite = resolveConfigWriteAfterWrite(params.afterWrite ?? params.writeOptions?.afterWrite); const transformed = await params.transform(baseConfig, { snapshot, previousHash, attempt }, { envSnapshotForRestore: writeOptions.envSnapshotForRestore }); const committed = await (params.commit ?? commitPreparedConfigMutation)({ nextConfig: transformed.nextConfig, snapshot, ...previousHash !== null ? { baseHash: previousHash } : {}, writeOptions: mergedWriteOptions, afterWrite, io: params.io }); const committedAfterWrite = committed.afterWrite ?? afterWrite; return { path: snapshot.path, previousHash, snapshot, nextConfig: committed.config, persistedHash: committed.persistedHash, persistedSourceConfig: committed.persistedSourceConfig, result: transformed.result, attempts: attempt + 1, afterWrite: committedAfterWrite, followUp: resolveConfigWriteFollowUp(committedAfterWrite) }; } async function transformConfigFile(params) { params.writeOptions?.assertConfigPathForWrite?.(); if (!params.io) return await withConfigMutationSnapshotLock(params.writeOptions, async (prepared) => await transformConfigFileAttempt(params, 0, createConfigMutationOwnership(prepared, params.writeOptions), prepared)); return await withConfigMutationLock({ io: params.io, assertCurrent: params.writeOptions?.assertCurrent }, async () => await transformConfigFileAttempt(params, 0)); } async function transformConfigFileWithRetry(params) { params.writeOptions?.assertConfigPathForWrite?.(); const maxAttempts = params.maxAttempts ?? DEFAULT_CONFIG_MUTATION_RETRY_ATTEMPTS; if (!Number.isInteger(maxAttempts) || maxAttempts < 1) throw new Error("Config mutation maxAttempts must be a positive integer."); const runWithPrepared = async (prepared) => { const ownership = prepared ? createConfigMutationOwnership(prepared, params.writeOptions) : {}; for (let attempt = 0; attempt < maxAttempts; attempt += 1) try { return await transformConfigFileAttempt(params, attempt, ownership, attempt === 0 ? prepared : void 0); } catch (err) { if (err instanceof ConfigMutationConflictError && err.retryable && attempt < maxAttempts - 1) continue; throw err; } throw new Error("Config mutation retry loop exhausted unexpectedly."); }; if (!params.io) return await withConfigMutationSnapshotLock(params.writeOptions, runWithPrepared); return await withConfigMutationLock({ io: params.io, assertCurrent: params.writeOptions?.assertCurrent }, async () => await runWithPrepared()); } function configMutationTransform(params) { return { base: params.base, baseHash: params.baseHash, afterWrite: params.afterWrite, writeOptions: params.writeOptions, io: params.io, transform: async (currentConfig, context) => { const draft = structuredClone(currentConfig); return { nextConfig: draft, result: await params.mutate(draft, context) }; } }; } async function mutateConfigFile(params) { return await transformConfigFile(configMutationTransform(params)); } async function mutateConfigFileWithRetry(params) { return await transformConfigFileWithRetry({ ...configMutationTransform(params), maxAttempts: params.maxAttempts }); } //#endregion export { transformConfigFile as a, withConfigMutationLock as c, resolveConfigIncludeWriteBoundary as i, mutateConfigFileWithRetry as n, transformConfigFileWithRetry as o, replaceConfigFile as r, withConfigMutationExclusive as s, mutateConfigFile as t };