mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
812 lines
28 KiB
TypeScript
812 lines
28 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import type * as NodeOs from 'node:os'
|
|
import { join } from 'node:path'
|
|
|
|
const { getPathMock, homedirMock } = vi.hoisted(() => ({
|
|
getPathMock: vi.fn<(name: string) => string>(),
|
|
homedirMock: vi.fn<() => string>()
|
|
}))
|
|
|
|
vi.mock('electron', () => ({
|
|
app: {
|
|
getPath: getPathMock
|
|
}
|
|
}))
|
|
|
|
vi.mock('node:os', async () => {
|
|
const actual = await vi.importActual<typeof NodeOs>('node:os')
|
|
return {
|
|
...actual,
|
|
homedir: homedirMock
|
|
}
|
|
})
|
|
|
|
import {
|
|
prepareSystemConfigForFreshRuntimeMirror,
|
|
resolveCodexConfigMirrorSourceDirectory,
|
|
syncSystemConfigIntoLegacySharedCodexHome,
|
|
syncSystemConfigIntoManagedCodexHome
|
|
} from './codex-config-mirror'
|
|
|
|
let fakeHomeDir: string
|
|
let userDataDir: string
|
|
let previousUserDataPath: string | undefined
|
|
|
|
function getSystemCodexHomePath(): string {
|
|
return join(fakeHomeDir, '.codex')
|
|
}
|
|
|
|
function getSystemConfigPath(): string {
|
|
return join(getSystemCodexHomePath(), 'config.toml')
|
|
}
|
|
|
|
function getRuntimeConfigPath(): string {
|
|
return join(userDataDir, 'codex-runtime-home', 'home', 'config.toml')
|
|
}
|
|
|
|
beforeEach(() => {
|
|
fakeHomeDir = mkdtempSync(join(tmpdir(), 'orca-codex-config-home-'))
|
|
userDataDir = mkdtempSync(join(tmpdir(), 'orca-codex-config-user-data-'))
|
|
previousUserDataPath = process.env.ORCA_USER_DATA_PATH
|
|
process.env.ORCA_USER_DATA_PATH = userDataDir
|
|
homedirMock.mockReturnValue(fakeHomeDir)
|
|
getPathMock.mockImplementation((name: string) => {
|
|
if (name === 'userData') {
|
|
return userDataDir
|
|
}
|
|
throw new Error(`unexpected app.getPath(${name})`)
|
|
})
|
|
mkdirSync(getSystemCodexHomePath(), { recursive: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
rmSync(fakeHomeDir, { recursive: true, force: true })
|
|
rmSync(userDataDir, { recursive: true, force: true })
|
|
if (previousUserDataPath === undefined) {
|
|
delete process.env.ORCA_USER_DATA_PATH
|
|
} else {
|
|
process.env.ORCA_USER_DATA_PATH = previousUserDataPath
|
|
}
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('syncSystemConfigIntoManagedCodexHome', () => {
|
|
it('seeds a missing runtime config without copying system hook trust', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'model = "system-model"',
|
|
'',
|
|
'[hooks.state."system-hooks:stop:0:0"]',
|
|
'enabled = true',
|
|
'trusted_hash = "sha256:system"',
|
|
'',
|
|
'[projects."/repo"]',
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('model = "system-model"')
|
|
expect(runtimeConfig).toContain('[projects."/repo"]')
|
|
expect(runtimeConfig).not.toContain('[hooks.state."system-hooks:stop:0:0"]')
|
|
})
|
|
|
|
it('normalizes deprecated codex_hooks feature flag only in runtime config', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['model = "system-model"', '', '[features]', 'codex_hooks = true', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('[features]\nhooks = true')
|
|
expect(runtimeConfig).not.toContain('codex_hooks')
|
|
expect(readFileSync(getSystemConfigPath(), 'utf-8')).toContain('codex_hooks = true')
|
|
})
|
|
|
|
it('preserves system-home relative path references in the runtime config copy', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'model_instructions_file = "instructions.md"',
|
|
"model_catalog_json = 'catalogs/models.json'",
|
|
'experimental_compact_prompt_file = "prompts/compact.md"',
|
|
'experimental_instructions_file = "legacy-instructions.md"',
|
|
'log_dir = "logs"',
|
|
'sqlite_home = "state"',
|
|
'',
|
|
'[agents.reviewer]',
|
|
'config_file = "agents/reviewer.toml"',
|
|
'',
|
|
'[model_providers.qwen.auth]',
|
|
'cwd = "auth"',
|
|
'',
|
|
'[[skills.config]]',
|
|
'path = "skills/local"',
|
|
''
|
|
].join('\r\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain(
|
|
`model_instructions_file = '${join(getSystemCodexHomePath(), 'instructions.md')}'`
|
|
)
|
|
expect(runtimeConfig).toContain(
|
|
`model_catalog_json = '${join(getSystemCodexHomePath(), 'catalogs', 'models.json')}'`
|
|
)
|
|
expect(runtimeConfig).toContain(
|
|
`experimental_compact_prompt_file = '${join(
|
|
getSystemCodexHomePath(),
|
|
'prompts',
|
|
'compact.md'
|
|
)}'`
|
|
)
|
|
expect(runtimeConfig).toContain(
|
|
`experimental_instructions_file = '${join(
|
|
getSystemCodexHomePath(),
|
|
'legacy-instructions.md'
|
|
)}'`
|
|
)
|
|
expect(runtimeConfig).toContain(`log_dir = '${join(getSystemCodexHomePath(), 'logs')}'`)
|
|
expect(runtimeConfig).toContain(`sqlite_home = '${join(getSystemCodexHomePath(), 'state')}'`)
|
|
expect(runtimeConfig).toContain(
|
|
`config_file = '${join(getSystemCodexHomePath(), 'agents', 'reviewer.toml')}'`
|
|
)
|
|
expect(runtimeConfig).toContain(`cwd = '${join(getSystemCodexHomePath(), 'auth')}'`)
|
|
expect(runtimeConfig).toContain(`path = '${join(getSystemCodexHomePath(), 'skills', 'local')}'`)
|
|
})
|
|
|
|
it('rewrites profile and debug lockfile path references', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'[profiles.fast]',
|
|
'model_catalog_json = "catalogs/fast.json"',
|
|
'',
|
|
'[debug.config_lockfile]',
|
|
'load_path = "locks/config.lock.toml"',
|
|
'export_dir = "locks"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain(
|
|
`model_catalog_json = '${join(getSystemCodexHomePath(), 'catalogs', 'fast.json')}'`
|
|
)
|
|
expect(runtimeConfig).toContain(
|
|
`load_path = '${join(getSystemCodexHomePath(), 'locks', 'config.lock.toml')}'`
|
|
)
|
|
expect(runtimeConfig).toContain(`export_dir = '${join(getSystemCodexHomePath(), 'locks')}'`)
|
|
})
|
|
|
|
it('does not treat lines inside multiline arrays as headers or path keys', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['notify = [', ' ["custom", 1]', ']', 'log_dir = "logs"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain(' ["custom", 1]')
|
|
expect(runtimeConfig).toContain(`log_dir = '${join(getSystemCodexHomePath(), 'logs')}'`)
|
|
})
|
|
|
|
it('escapes control characters instead of emitting them raw in rewritten paths', () => {
|
|
writeFileSync(getSystemConfigPath(), 'log_dir = "logs\\bdir"\n', 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('log_dir = "')
|
|
expect(runtimeConfig).toContain('\\u0008')
|
|
expect(runtimeConfig).not.toContain('\b')
|
|
})
|
|
|
|
it('leaves values with lone-surrogate unicode escapes untouched', () => {
|
|
writeFileSync(getSystemConfigPath(), 'log_dir = "logs\\uD800dir"\n', 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('log_dir = "logs\\uD800dir"')
|
|
})
|
|
|
|
it('leaves absolute, home-prefixed, env-shaped, and URL path references untouched', () => {
|
|
const passthroughLines = [
|
|
'model_instructions_file = "~/notes/instructions.md"',
|
|
'model_catalog_json = "$CODEX_ASSETS/models.json"',
|
|
'experimental_instructions_file = "%USERPROFILE%\\\\instructions.md"',
|
|
'log_dir = "/var/log/codex"',
|
|
"sqlite_home = 'C:\\Users\\example\\state'",
|
|
'experimental_compact_prompt_file = "file://server/prompts/compact.md"'
|
|
]
|
|
writeFileSync(getSystemConfigPath(), `${passthroughLines.join('\n')}\n`, 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
for (const line of passthroughLines) {
|
|
expect(runtimeConfig).toContain(line)
|
|
}
|
|
})
|
|
|
|
it('drops deprecated codex_hooks when the new hooks flag already exists', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['[features]', 'hooks = true', 'codex_hooks = true', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('[features]\nhooks = true')
|
|
expect(runtimeConfig).not.toContain('codex_hooks')
|
|
})
|
|
|
|
it('preserves an existing runtime config when the system config is missing', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
const runtimeConfig = [
|
|
'model = "runtime-model"',
|
|
'',
|
|
'[features]',
|
|
'hooks = true',
|
|
'',
|
|
'[projects."/repo"]',
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\n')
|
|
writeFileSync(getRuntimeConfigPath(), runtimeConfig, 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
expect(readFileSync(getRuntimeConfigPath(), 'utf-8')).toBe(runtimeConfig)
|
|
expect(existsSync(getSystemConfigPath())).toBe(false)
|
|
})
|
|
|
|
it('preserves an existing runtime config when the system config is blank', () => {
|
|
// Why: a 0-byte config.toml is what a half-written or unhydrated
|
|
// cloud-synced home shows, not a deliberate "erase all my settings".
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
const runtimeConfig = ['model = "runtime-model"', '', '[features]', 'hooks = true', ''].join(
|
|
'\n'
|
|
)
|
|
writeFileSync(getRuntimeConfigPath(), runtimeConfig, 'utf-8')
|
|
writeFileSync(getSystemConfigPath(), '', 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
expect(readFileSync(getRuntimeConfigPath(), 'utf-8')).toBe(runtimeConfig)
|
|
})
|
|
|
|
it('mirrors system config updates while preserving runtime-owned trust sections', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[
|
|
'model = "runtime-model"',
|
|
'',
|
|
'[hooks.state]',
|
|
'# runtime-owned parent',
|
|
'',
|
|
'[hooks.state."runtime-hooks:stop:0:0"]',
|
|
'enabled = false',
|
|
'trusted_hash = "sha256:runtime"',
|
|
'',
|
|
'[projects."/repo"]',
|
|
'trust_level = "trusted"',
|
|
'',
|
|
'[projects."/runtime-only"]',
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'model = "system-model"',
|
|
'',
|
|
'[hooks.state]',
|
|
'# system-owned parent',
|
|
'',
|
|
'[projects."/repo"] # explicit revocation',
|
|
'trust_level = "untrusted"',
|
|
'',
|
|
'[projects."/system-only"]',
|
|
'trust_level = "trusted"',
|
|
'',
|
|
'[hooks.state."system-hooks:stop:0:0"]',
|
|
'enabled = true',
|
|
'trusted_hash = "sha256:system"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('model = "system-model"')
|
|
expect(runtimeConfig).not.toContain('model = "runtime-model"')
|
|
expect(runtimeConfig).toContain('[projects."/repo"]')
|
|
expect(runtimeConfig).toContain('[projects."/runtime-only"]')
|
|
expect(runtimeConfig).toContain('[projects."/system-only"]')
|
|
expect(runtimeConfig).toContain('[hooks.state."runtime-hooks:stop:0:0"]')
|
|
expect(runtimeConfig).not.toContain('[hooks.state."system-hooks:stop:0:0"]')
|
|
expect(runtimeConfig).toContain('# runtime-owned parent')
|
|
expect(runtimeConfig).not.toContain('# system-owned parent')
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig.match(/\[projects\."\/repo"\]/g)?.length).toBe(1)
|
|
})
|
|
|
|
it('deduplicates basic and literal project headers by decoded Windows path', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
["[projects.'c:\\gemini_etl']", 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['[projects."c:\\\\gemini_etl"]', 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain("[projects.'c:\\gemini_etl']")
|
|
})
|
|
|
|
it('deduplicates a CRLF system project header against an LF runtime header', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
const projectHeader = '[projects."C:/Users/jinwo/orca/workspaces/orca/repo"]'
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[projectHeader, 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[projectHeader, 'trust_level = "trusted"', ''].join('\r\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain(`${projectHeader}\ntrust_level = "trusted"`)
|
|
})
|
|
|
|
it('self-heals duplicate project tables in a CRLF runtime config', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
const projectHeader = '[projects."C:/Users/jinwo/orca/workspaces/orca/repo"]'
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[
|
|
projectHeader,
|
|
'trust_level = "trusted"',
|
|
'',
|
|
projectHeader,
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\r\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(getSystemConfigPath(), 'model = "gpt-5"\n', 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it('applies a CRLF system revocation to an LF runtime project', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
["[projects.'c:\\repo']", 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['[projects."C:/repo"]', 'trust_level = "untrusted"', ''].join('\r\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig).not.toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it('lets a semantically matching system revocation replace runtime trust', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
["[projects.'c:\\gemini_etl']", 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['[projects."C:/gemini_etl"]', 'trust_level = "untrusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain('[projects."C:/gemini_etl"]')
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig).not.toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it('applies a case-drifted WSL system revocation to the runtime trusted block', () => {
|
|
// Why: configs written before WSL tails compared case-sensitively can hold
|
|
// the revocation under drifted casing; err toward revoked, not trusted.
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
["[projects.'\\\\wsl$\\Ubuntu\\home\\u\\Repo']", 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
["[projects.'\\\\wsl$\\Ubuntu\\home\\u\\repo']", 'trust_level = "untrusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig).not.toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it('keeps runtime trust when the system config re-trusts the exact-cased WSL project', () => {
|
|
// Why: after a user re-grants trust, markCodexProjectTrusted appends an
|
|
// exact-cased trusted block beside a legacy drifted-case revocation; the
|
|
// loose revocation match must not revert that grant on every mirror pass.
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
["[projects.'\\\\wsl$\\Ubuntu\\home\\u\\Repo']", 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
"[projects.'\\\\wsl$\\Ubuntu\\home\\u\\repo']",
|
|
'trust_level = "untrusted"',
|
|
'',
|
|
"[projects.'\\\\wsl$\\Ubuntu\\home\\u\\Repo']",
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain("[projects.'\\\\wsl$\\Ubuntu\\home\\u\\Repo']")
|
|
expect(runtimeConfig).toContain('trust_level = "trusted"')
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
})
|
|
|
|
it('does not let a case-distinct POSIX system revocation clobber runtime trust', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
['[projects."/home/u/Repo"]', 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
['[projects."/home/u/repo"]', 'trust_level = "untrusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(2)
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig).toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it.each([
|
|
{
|
|
name: 'drive-letter casing and separators',
|
|
runtimePath: 'c:\\work\\repo',
|
|
systemPath: 'C:/work/repo'
|
|
},
|
|
{
|
|
name: 'WSL UNC path',
|
|
runtimePath: '\\\\wsl$\\Ubuntu\\home\\u\\proj',
|
|
systemPath: '//WSL$/ubuntu/home/u/proj'
|
|
},
|
|
{
|
|
name: 'server UNC path',
|
|
runtimePath: '\\\\server\\share\\proj',
|
|
systemPath: '//SERVER/share/proj'
|
|
}
|
|
])('deduplicates $name across mirror inputs', ({ runtimePath, systemPath }) => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[`[projects.'${runtimePath}']`, 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[`[projects."${systemPath}"]`, 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
expect(readFileSync(getRuntimeConfigPath(), 'utf-8').match(/\[projects\./g)).toHaveLength(1)
|
|
})
|
|
|
|
it('self-heals duplicate project tables already present in the runtime config', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[
|
|
'[projects."c:\\\\gemini_etl"]',
|
|
'trust_level = "trusted"',
|
|
'',
|
|
"[projects.'c:\\gemini_etl']",
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(getSystemConfigPath(), 'model = "gpt-5"\n', 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain('[projects."c:\\\\gemini_etl"]')
|
|
})
|
|
|
|
it('keeps untrusted when self-healing duplicate project tables', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[
|
|
'[projects."c:\\\\gemini_etl"]',
|
|
'trust_level = "trusted"',
|
|
'',
|
|
"[projects.'C:\\gemini_etl']",
|
|
'trust_level = "untrusted"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(getSystemConfigPath(), 'model = "gpt-5"\n', 'utf-8')
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig).not.toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it('keeps untrusted when self-healing duplicate system project tables', () => {
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'[projects."c:\\\\gemini_etl"]',
|
|
'trust_level = "trusted"',
|
|
'',
|
|
"[projects.'C:\\gemini_etl']",
|
|
'trust_level = "untrusted"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
|
|
expect(runtimeConfig).toContain('trust_level = "untrusted"')
|
|
expect(runtimeConfig).not.toContain('trust_level = "trusted"')
|
|
})
|
|
|
|
it('deduplicates a POSIX project path containing a quote and backslash', () => {
|
|
const projectPath = '/tmp/with"quote\\and-backslash'
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[`[projects.'${projectPath}']`, 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[`[projects."/tmp/with\\"quote\\\\and-backslash"]`, 'trust_level = "trusted"', ''].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
expect(readFileSync(getRuntimeConfigPath(), 'utf-8').match(/\[projects\./g)).toHaveLength(1)
|
|
})
|
|
|
|
it('does not treat TOML table headers inside multiline strings as sections', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[
|
|
'[hooks.state."runtime-hooks:stop:0:0"]',
|
|
'enabled = true',
|
|
'trusted_hash = "sha256:runtime"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'instructions = """',
|
|
'[hooks.state."inside-basic-string"]',
|
|
'trusted_hash = "not-a-section"',
|
|
'"""',
|
|
'',
|
|
"literal_instructions = '''",
|
|
'[hooks.state."inside-literal-string"]',
|
|
"'''",
|
|
'',
|
|
'[model_providers.openai]',
|
|
'name = "OpenAI"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('[hooks.state."inside-basic-string"]')
|
|
expect(runtimeConfig).toContain('[hooks.state."inside-literal-string"]')
|
|
expect(runtimeConfig).toContain('[model_providers.openai]')
|
|
expect(runtimeConfig).toContain('[hooks.state."runtime-hooks:stop:0:0"]')
|
|
})
|
|
|
|
it('does not let triple quotes in comments affect runtime-owned section mirroring', () => {
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(
|
|
getRuntimeConfigPath(),
|
|
[
|
|
'# example: """ in a comment',
|
|
"# example: ''' in a comment",
|
|
'model = "runtime-model"',
|
|
'',
|
|
'[hooks.state."runtime-hooks:stop:0:0"]',
|
|
'enabled = true',
|
|
'trusted_hash = "sha256:runtime"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
writeFileSync(
|
|
getSystemConfigPath(),
|
|
[
|
|
'# system example: """ in a comment',
|
|
"# system example: ''' in a comment",
|
|
'model = "system-model"',
|
|
'',
|
|
'[hooks.state."system-hooks:stop:0:0"]',
|
|
'enabled = true',
|
|
'trusted_hash = "sha256:system"',
|
|
''
|
|
].join('\n'),
|
|
'utf-8'
|
|
)
|
|
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
|
|
expect(runtimeConfig).toContain('model = "system-model"')
|
|
expect(runtimeConfig).toContain('[hooks.state."runtime-hooks:stop:0:0"]')
|
|
expect(runtimeConfig).toContain('trusted_hash = "sha256:runtime"')
|
|
expect(runtimeConfig).not.toContain('[hooks.state."system-hooks:stop:0:0"]')
|
|
expect(runtimeConfig).not.toContain('trusted_hash = "sha256:system"')
|
|
})
|
|
|
|
it('does not create a runtime config when neither system nor runtime config exists', () => {
|
|
syncSystemConfigIntoManagedCodexHome()
|
|
|
|
expect(existsSync(getRuntimeConfigPath())).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('syncSystemConfigIntoLegacySharedCodexHome', () => {
|
|
it('recovers an interrupted runtime config when the system source is missing', () => {
|
|
const runtimeConfigPath = getRuntimeConfigPath()
|
|
const heldConfigPath = `${runtimeConfigPath}.orca-guarded`
|
|
mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
|
|
writeFileSync(heldConfigPath, 'model = "retained"\n', 'utf-8')
|
|
|
|
syncSystemConfigIntoLegacySharedCodexHome({
|
|
runtimeHomePath: join(userDataDir, 'codex-runtime-home', 'home'),
|
|
systemHomePath: getSystemCodexHomePath()
|
|
})
|
|
|
|
expect(readFileSync(runtimeConfigPath, 'utf-8')).toBe('model = "retained"\n')
|
|
expect(existsSync(heldConfigPath)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('prepareSystemConfigForFreshRuntimeMirror', () => {
|
|
it('uses the Linux-side directory for WSL UNC source homes', () => {
|
|
const sourceDir = resolveCodexConfigMirrorSourceDirectory(
|
|
'\\\\wsl.localhost\\Ubuntu\\home\\alice\\.codex'
|
|
)
|
|
|
|
expect(sourceDir).toBe('/home/alice/.codex')
|
|
expect(
|
|
prepareSystemConfigForFreshRuntimeMirror(
|
|
'model_instructions_file = "instructions.md"\n',
|
|
sourceDir
|
|
)
|
|
).toContain("model_instructions_file = '/home/alice/.codex/instructions.md'")
|
|
})
|
|
|
|
it('rewrites relative paths against a Linux-side home and strips hook trust', () => {
|
|
const prepared = prepareSystemConfigForFreshRuntimeMirror(
|
|
[
|
|
'model_instructions_file = "instructions.md"',
|
|
'',
|
|
'[features]',
|
|
'codex_hooks = true',
|
|
'',
|
|
'[hooks.state."system-hooks:stop:0:0"]',
|
|
'enabled = true',
|
|
'',
|
|
'[projects."/home/alice/repo"]',
|
|
'trust_level = "trusted"',
|
|
''
|
|
].join('\r\n'),
|
|
'/home/alice/.codex'
|
|
)
|
|
|
|
// Why: WSL configs are consumed inside the distro, so rewrites must use
|
|
// posix join semantics regardless of the host platform.
|
|
expect(prepared).toContain("model_instructions_file = '/home/alice/.codex/instructions.md'")
|
|
expect(prepared).toContain('hooks = true')
|
|
expect(prepared).not.toContain('codex_hooks')
|
|
expect(prepared).toContain('[projects."/home/alice/repo"]')
|
|
expect(prepared).not.toContain('[hooks.state."system-hooks:stop:0:0"]')
|
|
})
|
|
})
|