* fix(#1151): drive codex sandbox_mode emission from runtime descriptor sandboxTier axis The sandboxTier runtime-capability axis was cosmetic: declared and validated on all 16 descriptors but read by nothing. The codex per-agent sandbox_mode line was emitted unconditionally from the hardcoded CODEX_AGENT_SANDBOX map, so the descriptor field drove no behaviour (ADR-857 audit finding F10; the "rides along in 5e/5g" promise in ADR-1016 §8 never landed). Make the axis load-bearing: - resolveInstallPlan projects sandboxTier as a 7th InstallPlan axis and fails loud (throws) on a missing/invalid value rather than coercing to 'none'. - installCodexConfig / generateCodexAgentToml gate sandbox_mode emission on sandboxTier !== 'none'. - The per-agent CODEX_AGENT_SANDBOX map is kept: it is GSD agent policy, not a runtime-descriptor property (different layer). Full removal of that map is tracked under #1138 (phase-6 descriptor-residue removal). For codex (sandboxTier === 'codex-agent-sandbox') the emitted TOML is byte-identical to before; for 'none' runtimes sandbox_mode is omitted. Adds leaf, projection, and installCodexConfig threading-seam regression tests; updates the enh-1082 InstallPlan golden master with sandboxTier for all 16 runtimes. Confirmed hypothesis: schema-first vocabulary closure outran consumer wiring, with no conformance gate to catch the orphaned axis. Closes #1151 Refs #857, #1138 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#1151): stamp changeset with PR number 1152 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/1151-sandbox-tier-load-bearing.md
Normal file
5
.changeset/1151-sandbox-tier-load-bearing.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1152
|
||||
---
|
||||
**`sandbox_mode` emission in Codex TOML is now gated on the runtime descriptor's `sandboxTier` axis** — previously `installCodexConfig` emitted `sandbox_mode` unconditionally from a hardcoded policy map regardless of whether the runtime descriptor declared a sandbox tier, making the descriptor field cosmetic. `resolveInstallPlan` now projects `sandboxTier` from the capability registry, and `generateCodexAgentToml` / `installCodexConfig` gate emission on `sandboxTier !== 'none'`. The per-agent mode table `CODEX_AGENT_SANDBOX` remains GSD agent policy (not a runtime-descriptor property). For codex (`sandboxTier === 'codex-agent-sandbox'`) output is byte-identical to before; for all other runtimes (`sandboxTier === 'none'`) `sandbox_mode` is correctly omitted. `resolveInstallPlan` now fails loud (throws `TypeError`) on a missing or invalid `sandboxTier` descriptor axis rather than silently coercing garbage to `'none'`, preventing a corrupt/stale registry from silently dropping sandbox enforcement. Full removal of the per-agent registration-tax map remains tracked under #1138. (#1151)
|
||||
@@ -179,7 +179,7 @@ A Capability whose integration shape brings its own external process, service, o
|
||||
Standalone hook-surface writer module extracted from `bin/install.js` as ADR-857 phase 5f-1 (behavior-preserving relocation, no logic change). Owns: Cline rules-body/agents-md/pre-tool-use hook generation (`buildClineRulesBody`, `buildClineAgentsMdBody`, `buildClinePreToolUseHook`, `mergeGsdAgentsMd`, `writeClineArtifacts`); Cursor `hooks.json` lifecycle (`buildCursorHookEntry`, `isManagedCursorHookEntry`, `reconcileCursorHooksJson`, `writeCursorHooksJson`, `removeCursorHooksJson`); Copilot session-hook config (`buildCopilotHookConfig`, `writeCopilotHookConfig`); Codex hook-block and event management (`buildCodexHookBlock`, `rewriteLegacyCodexHookBlock`, `reconcileCodexHooksJsonEvent`, `reconcileCodexHooksJsonSessionStart`, `ensureCodexHooksJsonSessionStart`, `ensureCodexHooksJsonEvent`, `removeCodexHooksJsonEvent`, `removeCodexHooksJsonSessionStart`, `buildCodexHookWindowsShimIR`); and shared hook command helpers (`buildHookCommand`, `rewriteLegacyManagedNodeHookCommands`, `normalizeNodePath`, `resolveNodeRunner`). `bin/install.js` delegates to this module via thin wrappers and re-exports its functions unchanged so existing tests require no modification. Source: `src/runtime-hooks-surface.cts`. Built output: `gsd-core/bin/lib/runtime-hooks-surface.cjs`.
|
||||
|
||||
### Runtime Config Adapter Registry
|
||||
Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60.
|
||||
Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`, `sandboxTier`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. `sandboxTier` (`none` | `codex-agent-sandbox`) gates per-agent `sandbox_mode` emission in the codex TOML path and fails loud on a missing/invalid value (#1151). The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60.
|
||||
|
||||
### Claude Code Plugin Manifest Module
|
||||
Module owning the projection of gsd-core's artifact surfaces (`commands`, `agents`, hooks) onto the Claude Code plugin contract (`.claude-plugin/plugin.json` + `hooks/hooks.json`) — the plugin-contract sibling of the Runtime Artifact Layout Module (which projects the same surfaces onto filesystem placements). Defined mapping: `name`=`binName` (drives the `/gsd-core:` command namespace), `repository`/`homepage`=`repoUrl` (Package Identity Module), `version`/`description`/`license` from `package.json` (`version` is required for `claude plugin validate --strict`), `commands`=`./commands/gsd/`, agents via Claude Code's default `agents/` discovery (the explicit string form is schema-rejected), `hooks`=`./hooks/hooks.json`. The hook projection carries ONLY the always-on subset of the Installer Module's Claude `settings.json` wiring (check-update, context-monitor, prompt-guard, read-guard, worktree-path-guard, read-injection-scanner) via `${CLAUDE_PLUGIN_ROOT}`; config-gated opt-in hooks are excluded because a static manifest cannot honor per-project config gates, and plugin-shipped agents cannot carry hook frontmatter (so all plugin-path hook wiring lives in hooks.json). `hooks.json` covers all seven Claude Code lifecycle events: SessionStart, PreToolUse, PostToolUse, SubagentStop, Stop, PreCompact (all wired to context-monitor for context-headroom awareness), and FileChanged (matcher: `config.json` → config-reload, injects `additionalContext` when `.planning/config.json` changes mid-session). Additive — the file-copy path (Runtime Artifact Layout / Install Policy / Installer Modules) is unchanged. Conformance is validated by `claude plugin validate --strict` plus the in-repo drift-guard `tests/issue-766-plugin-manifest.test.cjs`. _Avoid_: "the plugin API", "the plugin file" (when you mean the seam). See ADR-766 and Runtime Artifact Layout Module.
|
||||
|
||||
@@ -3166,7 +3166,7 @@ purpose: ${toSingleLine(description)}
|
||||
* @param {object|null} runtimeResolver — runtime-aware tier resolver from readGsdRuntimeProfileResolver
|
||||
* @param {object|null} effortCfg — #443: merged effort config from readGsdEffectiveEffortConfig
|
||||
*/
|
||||
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null, runtimeResolver = null, effortCfg = null) {
|
||||
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null, runtimeResolver = null, effortCfg = null, sandboxTier = 'codex-agent-sandbox') {
|
||||
const sandboxMode = CODEX_AGENT_SANDBOX[agentName] || 'read-only';
|
||||
const { frontmatter, body } = extractFrontmatterAndBody(agentContent);
|
||||
const frontmatterText = frontmatter || '';
|
||||
@@ -3179,8 +3179,10 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
||||
const lines = [
|
||||
`name = ${JSON.stringify(resolvedName)}`,
|
||||
`description = ${JSON.stringify(resolvedDescription)}`,
|
||||
`sandbox_mode = "${sandboxMode}"`,
|
||||
];
|
||||
if (sandboxTier != null && sandboxTier !== 'none') {
|
||||
lines.push(`sandbox_mode = "${sandboxMode}"`);
|
||||
}
|
||||
|
||||
// Embed model override when configured in ~/.gsd/defaults.json so that
|
||||
// model_overrides is respected on Codex (which uses static TOML, not inline
|
||||
@@ -5745,7 +5747,7 @@ function writeCopilotHookConfig(targetDir) {
|
||||
* Generate config.toml and per-agent .toml files for Codex.
|
||||
* Reads agent .md files from source, extracts metadata, writes .toml configs.
|
||||
*/
|
||||
function installCodexConfig(targetDir, agentsSrc) {
|
||||
function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-sandbox') {
|
||||
const configPath = path.join(targetDir, 'config.toml');
|
||||
const agentsTomlDir = path.join(targetDir, 'agents');
|
||||
fs.mkdirSync(agentsTomlDir, { recursive: true });
|
||||
@@ -5791,7 +5793,7 @@ function installCodexConfig(targetDir, agentsSrc) {
|
||||
// #443 — pass unified effort config so model_reasoning_effort in the .toml
|
||||
// follows the same config-driven precedence as the Claude .md effort key.
|
||||
const effortCfg = readGsdEffectiveEffortConfig(targetDir);
|
||||
const tomlContent = generateCodexAgentToml(name, content, modelOverrides, runtimeResolver, effortCfg);
|
||||
const tomlContent = generateCodexAgentToml(name, content, modelOverrides, runtimeResolver, effortCfg, sandboxTier);
|
||||
fs.writeFileSync(path.join(agentsTomlDir, `${name}.toml`), tomlContent);
|
||||
}
|
||||
|
||||
@@ -10717,7 +10719,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
if (!isMinimalMode(_effectiveInstallMode)) {
|
||||
try {
|
||||
// Generate Codex config.toml and per-agent .toml files.
|
||||
agentCount = installCodexConfig(targetDir, agentsSrc);
|
||||
agentCount = installCodexConfig(targetDir, agentsSrc, plan.sandboxTier);
|
||||
} catch (e) {
|
||||
restoreCodexSnapshot();
|
||||
throw e;
|
||||
|
||||
@@ -150,7 +150,7 @@ The migration is staged the way phases 3–4 were (registry-only → consume inc
|
||||
6. **5f — extract `hooksSurface` into its own module, then drive it** ← descriptor. `hooks-surface` is the one axis still scattered across `install.js`; its module extraction is a prerequisite, exactly as ADR-3660 was for `artifactLayout`. **5f-completion (done):** `installSurface` drives the config-writing adapter in `runtime-config-adapter-registry` (#1055, retiring the hand-kept `REGISTRY` table); `extendedHookEvents` drives per-event hook registration in `applySettingsJsonHooks` (#1076, retiring the three hardcoded per-event guards); `hooksSurface === 'none'` is now load-bearing for the hook-skip path (replacing `isOpencode/isKilo` flags). `writesSharedSettings` and `permissionWriter` complete the adapter-selection and permissions-writer parameterisation (all four axes added to `validateRuntimeBody`).
|
||||
7. **5g — materialize the `InstallPlan`** (ADR-58) — **DONE**: the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`) are collected into one typed `InstallPlan` value by the exported `resolveInstallPlan(runtime)` in `runtime-config-adapter-registry`. `install()` and `finishInstall()` in `bin/install.js` now route through it. The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules (`runtime-homes` / `runtime-artifact-layout` / `runtime-slash`) as the execution adapters — consistent with the "adapters execute" pattern. Phase 5 is fully materialized.
|
||||
|
||||
Each rung is its own approved-enhancement + PR + equivalence proof (`sandbox-tier`, codex-only, is tiny and rides along in 5e/5g). **Irreducible first-party code, named not hidden:** the artifact converter *functions* remain first-party code, selected by the descriptor's closed `ConverterName` — the descriptor never embeds them.
|
||||
Each rung is its own approved-enhancement + PR + equivalence proof. (`sandbox-tier`, codex-only, was scoped to ride along in 5e/5g, but neither rung delivered the drive — the InstallPlan capstone shipped with six axes and `sandboxTier` was not among them. It is now wired directly: `resolveInstallPlan` projects `sandboxTier`, and `installCodexConfig` gates per-agent `sandbox_mode` emission on `sandboxTier !== 'none'`. The per-agent mode table `CODEX_AGENT_SANDBOX` remains GSD agent policy — not a runtime-descriptor property — so it is intentionally NOT retired here; full removal of that registration-tax map is tracked under #1138 (phase-6 descriptor-residue removal). The gate is behaviourally a no-op while codex is the only runtime with a non-`none` tier.) **Irreducible first-party code, named not hidden:** the artifact converter *functions* remain first-party code, selected by the descriptor's closed `ConverterName` — the descriptor never embeds them.
|
||||
|
||||
### 9. Two axes: the descriptor dogfoods the *runtime* interface only
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { runtimes } = require('./capability-registry.cjs') as { runtimes: Record<string, { runtime: Record<string, unknown> | undefined }> };
|
||||
|
||||
/** Valid sandboxTier enum values — mirrors the gen-capability-registry validator vocabulary. */
|
||||
const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -71,6 +74,8 @@ interface InstallPlan extends RuntimeConfigIntent {
|
||||
extendedHookEvents: string[];
|
||||
/** Which surface owns the hook registration for this runtime. */
|
||||
hooksSurface: HooksSurface;
|
||||
/** Runtime sandbox tier ('none' | 'codex-agent-sandbox'); gates per-agent sandbox_mode emission. */
|
||||
sandboxTier: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -130,6 +135,10 @@ function resolveInstallPlan(runtime: string): InstallPlan {
|
||||
const desc = runtimes[runtime]?.runtime;
|
||||
if (!desc) throw new TypeError(`Unknown runtime for install plan: ${runtime}`);
|
||||
const configIntent = resolveRuntimeConfigIntent(runtime);
|
||||
const sandboxTier = desc['sandboxTier'];
|
||||
if (typeof sandboxTier !== 'string' || !VALID_SANDBOX_TIERS.has(sandboxTier)) {
|
||||
throw new TypeError(`Runtime '${runtime}' has a missing or invalid sandboxTier descriptor axis: ${JSON.stringify(sandboxTier)}`);
|
||||
}
|
||||
return {
|
||||
runtime,
|
||||
installSurface: configIntent.installSurface,
|
||||
@@ -140,6 +149,7 @@ function resolveInstallPlan(runtime: string): InstallPlan {
|
||||
hooksSurface: desc['hooksSurface'] != null
|
||||
? desc['hooksSurface'] as HooksSurface
|
||||
: ((runtime === 'opencode' || runtime === 'kilo') ? 'none' : 'settings-json'),
|
||||
sandboxTier,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ const {
|
||||
resolveNodeRunner,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
||||
|
||||
function runCodexInstall(codexHome, cwd = path.join(__dirname, '..')) {
|
||||
const previousCodeHome = process.env.CODEX_HOME;
|
||||
const previousCwd = process.cwd();
|
||||
@@ -579,6 +581,109 @@ description: Maps the codebase
|
||||
});
|
||||
});
|
||||
|
||||
// ─── sandboxTier gate on generateCodexAgentToml ────────────────────────────────
|
||||
|
||||
describe('generateCodexAgentToml sandboxTier gate', () => {
|
||||
const sampleAgent = `---
|
||||
name: gsd-executor
|
||||
description: Executes plans
|
||||
tools: Read, Write, Edit
|
||||
color: yellow
|
||||
---
|
||||
|
||||
<role>You are an executor.</role>`;
|
||||
|
||||
test('sandboxTier=none: does NOT emit sandbox_mode', () => {
|
||||
const result = generateCodexAgentToml('gsd-executor', sampleAgent, null, null, null, 'none');
|
||||
assert.ok(!result.includes('sandbox_mode'), 'sandbox_mode must be absent when sandboxTier is none');
|
||||
});
|
||||
|
||||
test('sandboxTier=codex-agent-sandbox: emits sandbox_mode = "workspace-write"', () => {
|
||||
const result = generateCodexAgentToml('gsd-executor', sampleAgent, null, null, null, 'codex-agent-sandbox');
|
||||
assert.ok(result.includes('sandbox_mode = "workspace-write"'), 'must emit workspace-write for codex-agent-sandbox tier');
|
||||
});
|
||||
|
||||
test('default (no sandboxTier arg): still emits sandbox_mode = "workspace-write" (no-op for codex)', () => {
|
||||
const result = generateCodexAgentToml('gsd-executor', sampleAgent);
|
||||
assert.ok(result.includes('sandbox_mode = "workspace-write"'), 'default preserves codex behavior');
|
||||
});
|
||||
|
||||
test('resolveInstallPlan projection: codex.sandboxTier === "codex-agent-sandbox"', () => {
|
||||
const plan = resolveInstallPlan('codex');
|
||||
assert.strictEqual(plan.sandboxTier, 'codex-agent-sandbox', 'codex must project sandboxTier=codex-agent-sandbox');
|
||||
});
|
||||
|
||||
test('resolveInstallPlan projection: claude.sandboxTier === "none"', () => {
|
||||
const plan = resolveInstallPlan('claude');
|
||||
assert.strictEqual(plan.sandboxTier, 'none', 'claude must project sandboxTier=none');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── installCodexConfig threading-seam: sandboxTier → per-agent TOML ─────────
|
||||
|
||||
describe('installCodexConfig sandboxTier threading seam', () => {
|
||||
const { installCodexConfig } = require('../bin/install.js');
|
||||
|
||||
let tmpDir;
|
||||
let agentsSrc;
|
||||
let targetDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-sandboxtier-seam-'));
|
||||
agentsSrc = path.join(tmpDir, 'agents');
|
||||
targetDir = path.join(tmpDir, 'codex');
|
||||
fs.mkdirSync(agentsSrc, { recursive: true });
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
// Write a minimal gsd-executor agent fixture
|
||||
fs.writeFileSync(path.join(agentsSrc, 'gsd-executor.md'), [
|
||||
'---',
|
||||
'name: gsd-executor',
|
||||
'description: Executes plans',
|
||||
'tools: Read, Write, Edit',
|
||||
'---',
|
||||
'',
|
||||
'<role>You are an executor.</role>',
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('sandboxTier=none: written per-agent .toml does NOT contain sandbox_mode', () => {
|
||||
installCodexConfig(targetDir, agentsSrc, 'none');
|
||||
const tomlPath = path.join(targetDir, 'agents', 'gsd-executor.toml');
|
||||
assert.ok(fs.existsSync(tomlPath), 'per-agent TOML must be written');
|
||||
const toml = fs.readFileSync(tomlPath, 'utf8');
|
||||
assert.ok(!toml.includes('sandbox_mode'), 'sandbox_mode must be absent when sandboxTier=none');
|
||||
});
|
||||
|
||||
test('sandboxTier=codex-agent-sandbox: written per-agent .toml contains sandbox_mode', () => {
|
||||
installCodexConfig(targetDir, agentsSrc, 'codex-agent-sandbox');
|
||||
const tomlPath = path.join(targetDir, 'agents', 'gsd-executor.toml');
|
||||
assert.ok(fs.existsSync(tomlPath), 'per-agent TOML must be written');
|
||||
const toml = fs.readFileSync(tomlPath, 'utf8');
|
||||
assert.ok(toml.includes('sandbox_mode'), 'sandbox_mode must be present when sandboxTier=codex-agent-sandbox');
|
||||
});
|
||||
|
||||
test('default 2-arg form (no sandboxTier): written per-agent .toml contains sandbox_mode (codex default)', () => {
|
||||
installCodexConfig(targetDir, agentsSrc);
|
||||
const tomlPath = path.join(targetDir, 'agents', 'gsd-executor.toml');
|
||||
assert.ok(fs.existsSync(tomlPath), 'per-agent TOML must be written');
|
||||
const toml = fs.readFileSync(tomlPath, 'utf8');
|
||||
assert.ok(toml.includes('sandbox_mode'), 'sandbox_mode must be present in default 2-arg form (codex-agent-sandbox default)');
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE: A test for the new fail-loud throw on missing/invalid sandboxTier in
|
||||
// resolveInstallPlan is omitted here. Constructing a descriptor without the
|
||||
// field would require mocking the capability-registry module which is a
|
||||
// singleton require(); patching it invasively would corrupt other tests in the
|
||||
// same process. The throw path is verified at the type level (tsc) and by the
|
||||
// build passing, and the happy-path coverage (claude.sandboxTier === 'none' and
|
||||
// codex.sandboxTier === 'codex-agent-sandbox') confirms the real registry has
|
||||
// valid values for all 16 runtimes.
|
||||
|
||||
// ─── CODEX_AGENT_SANDBOX mapping ────────────────────────────────────────────────
|
||||
|
||||
describe('CODEX_AGENT_SANDBOX', () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged'],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
codex: {
|
||||
runtime: 'codex',
|
||||
@@ -35,6 +36,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'codex-hooks-json',
|
||||
sandboxTier: 'codex-agent-sandbox',
|
||||
},
|
||||
antigravity: {
|
||||
runtime: 'antigravity',
|
||||
@@ -44,6 +46,7 @@ const EXPECTED = {
|
||||
hookEvents: 'gemini',
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
gemini: {
|
||||
runtime: 'gemini',
|
||||
@@ -53,6 +56,7 @@ const EXPECTED = {
|
||||
hookEvents: 'gemini',
|
||||
extendedHookEvents: ['BeforeAgent', 'AfterAgent', 'BeforeModel'],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
cursor: {
|
||||
runtime: 'cursor',
|
||||
@@ -62,6 +66,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'cursor-hooks-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
opencode: {
|
||||
runtime: 'opencode',
|
||||
@@ -71,6 +76,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'none',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
kilo: {
|
||||
runtime: 'kilo',
|
||||
@@ -80,6 +86,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'none',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
copilot: {
|
||||
runtime: 'copilot',
|
||||
@@ -89,6 +96,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'copilot-inline',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
augment: {
|
||||
runtime: 'augment',
|
||||
@@ -98,6 +106,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
trae: {
|
||||
runtime: 'trae',
|
||||
@@ -107,6 +116,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'none',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
qwen: {
|
||||
runtime: 'qwen',
|
||||
@@ -116,6 +126,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact'],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
hermes: {
|
||||
runtime: 'hermes',
|
||||
@@ -125,6 +136,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
codebuddy: {
|
||||
runtime: 'codebuddy',
|
||||
@@ -134,6 +146,7 @@ const EXPECTED = {
|
||||
hookEvents: 'claude',
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'settings-json',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
cline: {
|
||||
runtime: 'cline',
|
||||
@@ -143,6 +156,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'cline-rules',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
kimi: {
|
||||
runtime: 'kimi',
|
||||
@@ -152,6 +166,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'none',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
windsurf: {
|
||||
runtime: 'windsurf',
|
||||
@@ -161,6 +176,7 @@ const EXPECTED = {
|
||||
hookEvents: undefined,
|
||||
extendedHookEvents: [],
|
||||
hooksSurface: 'none',
|
||||
sandboxTier: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user