**Codex agents with Write/Edit tool contracts now run under workspace-write** — the 17-role read-only hold is lifted: official OpenAI documentation establishes sandbox_mode as an enforced boundary, so each Codex agent's sandbox now derives purely from its own declared tools. (#4770)
@@ -1088,28 +1088,28 @@ describe('#3897 rung 3: sandbox_mode derivation and the hold list', () => {
// codex-agent-toml.test.cjs's A14 round-trip pattern) does not let that hide.
constEXPECTED_SANDBOX_BY_ROLE={
'gsd-advisor-researcher':'read-only',
'gsd-ai-researcher':'read-only',
'gsd-ai-researcher':'workspace-write',
'gsd-assumptions-analyzer':'read-only',
'gsd-code-fixer':'read-only',
'gsd-code-reviewer':'read-only',
'gsd-code-fixer':'workspace-write',
'gsd-code-reviewer':'workspace-write',
'gsd-codebase-mapper':'workspace-write',
'gsd-debug-session-manager':'read-only',
'gsd-debug-session-manager':'workspace-write',
'gsd-debugger':'workspace-write',
'gsd-doc-classifier':'read-only',
'gsd-doc-synthesizer':'read-only',
'gsd-doc-verifier':'read-only',
'gsd-doc-writer':'read-only',
'gsd-dom-verifier':'read-only',
'gsd-domain-researcher':'read-only',
'gsd-eval-auditor':'read-only',
'gsd-eval-planner':'read-only',
'gsd-doc-classifier':'workspace-write',
'gsd-doc-synthesizer':'workspace-write',
'gsd-doc-verifier':'workspace-write',
'gsd-doc-writer':'workspace-write',
'gsd-dom-verifier':'workspace-write',
'gsd-domain-researcher':'workspace-write',
'gsd-eval-auditor':'workspace-write',
'gsd-eval-planner':'workspace-write',
'gsd-executor':'workspace-write',
'gsd-framework-selector':'read-only',
'gsd-integration-checker':'read-only',
'gsd-intel-updater':'read-only',
'gsd-intel-updater':'workspace-write',
'gsd-mempalace-curator':'read-only',
'gsd-nyquist-auditor':'read-only',
'gsd-pattern-mapper':'read-only',
'gsd-nyquist-auditor':'workspace-write',
'gsd-pattern-mapper':'workspace-write',
'gsd-phase-researcher':'workspace-write',
'gsd-plan-checker':'read-only',
'gsd-planner':'workspace-write',
@@ -1117,9 +1117,9 @@ describe('#3897 rung 3: sandbox_mode derivation and the hold list', () => {
'gsd-research-synthesizer':'workspace-write',
'gsd-roadmapper':'workspace-write',
'gsd-security-auditor':'read-only',
'gsd-ui-auditor':'read-only',
'gsd-ui-auditor':'workspace-write',
'gsd-ui-checker':'read-only',
'gsd-ui-researcher':'read-only',
'gsd-ui-researcher':'workspace-write',
'gsd-user-profiler':'read-only',
'gsd-verifier':'workspace-write',
};
@@ -1177,24 +1177,32 @@ describe('#3897 rung 3: sandbox_mode derivation and the hold list', () => {
});
}
test('T30 holdListMatchesTheMeasuredWideningSet: CODEX_SANDBOX_HOLDS is exactly the 17 measured widening roles, derived not hardcoded twice',()=>{
test('T30 holdListShrunkToZero: CODEX_SANDBOX_HOLDS is empty and every measured widening role derives workspace-write (#4770)',()=>{
// #4770 lift: the hold's recorded reopen condition (official OpenAI docs
// establishing sandbox_mode as enforced) is satisfied, so the list shrank
// to zero per its own ADR-3473 §8.3 shrink-only invariant. The formerly
// held roles are exactly measuredWideningRoles — this test now guards the
// LIFT: the map stays empty (no re-hold without a new recorded decision)
// and every one of those roles derives workspace-write from its own
// tools: contract.
assert.equal(
typeofCODEX_SANDBOX_HOLDS,
'object',
'install.js must export CODEX_SANDBOX_HOLDS — the hold list does not exist yet',
'install.js must export CODEX_SANDBOX_HOLDS — the (now empty) hold list shape is kept for a future re-hold',
);
assert.notEqual(CODEX_SANDBOX_HOLDS,null);
assert.deepEqual(
Object.keys(CODEX_SANDBOX_HOLDS).sort(),
measuredWideningRoles.sort(),
'CODEX_SANDBOX_HOLDS must equal exactly the set of roles that declare Write/Edit but were never in the old map — no more, no fewer',
[],
'CODEX_SANDBOX_HOLDS must be empty after the #4770 lift — a re-hold requires a new recorded decision',
);
// 16 measured by HALT.md against a single-line tools: reader + 1
// (gsd-nyquist-auditor, YAML block-list tools: — the list-form parse fix)
// = 17. `measuredWideningRoles` is computed from realAgentToolsRaw, which
// now routes through the fixed extractToolsValue, so this count moves
// WITH the parser fix rather than needing a second hand-edit.
assert.equal(measuredWideningRoles.length,17,'sanity: 17 widening roles against the current agents/ tree, once list-form tools: parses correctly');
assert.equal(measuredWideningRoles.length,17,'sanity: 17 widening roles against the current agents/ tree');
for(constroleofmeasuredWideningRoles){
assert.equal(
EXPECTED_SANDBOX_BY_ROLE[role],
'workspace-write',
'#4770: formerly-held role '+role+' must derive workspace-write from its own tool contract',
);
}
});
test('T21 mappedRolesDeriveToTheirFormerValue: every former CODEX_AGENT_SANDBOX entry (11) derives to the identical value from its real tool contract',()=>{
@@ -1219,26 +1227,23 @@ describe('#3897 rung 3: sandbox_mode derivation and the hold list', () => {
@@ -1314,12 +1320,12 @@ description: Declares no tools frontmatter key at all
// `installCodexConfig`'s per-file loop independently of the frontmatter
// `name:` used for the TOML body, with a case-insensitive lookup as a second
// line of defense.
test('heldRoleCannotEscapeItsHoldByRenamingFrontmatter_3897: editing or recasing a held role\'s frontmatter name: must not change its sandbox_mode from read-only',()=>{
test('heldRoleCannotEscapeItsHoldByRenamingFrontmatter_3897: name edits or recasing never change the content-derived sandbox (#4770: hold lifted, property kept)',()=>{
'F1(a): a held role\'s own emitted .toml must stay read-only even when reached via a renamed source file whose filename stem is unheld',
'workspace-write',
'F1(a) post-#4770: the sandbox derives from the content\'s own tools contract, so a renamed source file changes nothing — gsd-doc-writer declares Write/Edit and emits workspace-write',
'F1(b): a sibling file whose frontmatter name: collides with a held role must not clobber that role\'s emitted .toml with workspace-write',
'workspace-write',
'F1(b) post-#4770: the last-writer artifact carries ITS OWN content-derived sandbox (the sibling declared Write/Edit) — with no holds left, a name collision cannot escalate any role beyond what its own content derives',
`F3: identity ${JSON.stringify(vector)} (${label}) must derive read-only — either it normalizes onto the real held key, or it is unrecognizable and must fail closed`,
'workspace-write',
`F3 post-#4770: identity ${JSON.stringify(vector)} (${label}) folds to ASCII and normalizes onto a real roster role whose content declares Write/Edit — content-derived workspace-write`,
);
});
}
test('F3: isSandboxHeld flags each confusable vector as held or suspicious (never silently neither)',()=>{
for(const[label,vector]ofF3_CONFUSABLE_VECTORS){
const{held,suspicious}=isSandboxHeld(vector);
assert.ok(held||suspicious,`${label} (${JSON.stringify(vector)}) must be held or suspicious`);
}
// Vectors that stay non-ASCII after NFKC (Turkish İ/ı, combining acute):
// the F3 fail-closed pin is map-independent and still applies — a
// non-ASCII-after-normalization identity is never a legitimate shipped
// role and is pinned read-only regardless of its content's tools.
constF3_STILL_SUSPICIOUS_VECTORS=[
['Turkish dotted I (İ)','gsd-doc-wrİter'],
['Turkish dotless i (ı)','gsd-doc-wrıter'],
['NFD combining acute on r (writeŕ)','gsd-doc-writeŕ'],
// #4770: the Codex .toml family's sandbox_mode is derived through
// src/codex-agent-toml.cts (deriveCodexSandboxMode — the single owner of the
// derivation), so a change there moves every emitted agents/*.toml without
// touching any agents/*.md source.
'src/codex-agent-toml.cts',
];
// #3738: antigravity's global skills pass through the antigravity converter
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.