mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 22:58:42 -10:00
fix(perms): OPR.0.4.8.2 Codex OFF floor = explicit -s workspace-write (QA blocker qitem-0e536cbc)
The no-flags OFF/no-profile floor is broken on the installed managed runtime: an
interactive Codex launch with required --add-dir writable roots and no -s flag is
rejected ("effective permissions do not allow additional writable roots") and falls
to shell before readiness (dev-qa firsthand, Codex 0.120.0). The clean-home codex
doctor default never exercised the --add-dir writable-roots interaction.
Centralize the fix in codexPostureArg (yolo-mode.ts), applied on ALL managed paths
(fresh / resume / native-fork):
- OFF, no profile -> EXPLICIT " -s workspace-write" floor flag (was empty)
- ON (YOLO) -> " -s danger-full-access" (was --dangerously-bypass-...-sandbox)
- OFF, named profile -> " -p <profile>" unchanged
Remove all "harness default" floor wording per PM amendment 1c; the floor is the
explicit workspace-write flag. RED-first: codex OFF/ON pins flipped across yolo-mode,
codex-runtime-adapter, codex-resume, native-resume-probe, node-inventory, and
resume-metadata suites. Source-only; no live host config touched.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
0a96a8d5c1
commit
dd61b1372b
10 files changed
+62
-51
No files matched your search
@@ -338,7 +338,9 @@ export class CodexRuntimeAdapter implements RuntimeAdapter {
|
||||
return { ok: false, error: "codex fork: forkSource.value is required (parent native_id)" };
|
||||
}
|
||||
// OPR.0.4.8.2: the FORK path uses the SAME posture decision (codexPostureArg) — YOLO forces
|
||||
// full-bypass on every seat; otherwise the named profile (or the harness-default floor).
|
||||
// -s danger-full-access on every seat; otherwise the named profile, or the explicit
|
||||
// -s workspace-write floor flag (see codexPostureArg: the no-flags default does NOT grant the
|
||||
// --add-dir writable roots managed seats pass, so the floor must be the explicit flag).
|
||||
const cmd = `codex${codexPostureArg(profileArg)} fork${queueStateDirArg} ${shellQuote(parentId)}`;
|
||||
const textResult = await this.tmux.sendText(binding.tmuxSession, cmd);
|
||||
if (!textResult.ok) {
|
||||
@@ -360,9 +362,9 @@ export class CodexRuntimeAdapter implements RuntimeAdapter {
|
||||
}
|
||||
|
||||
// OPR.0.4.8.2: one posture decision (codexPostureArg) for the fresh launch too — YOLO forces
|
||||
// full-bypass (overrides even a named profile); otherwise the named profile, or the harness-
|
||||
// default floor (no forced -a/-s; Codex's no-flags default is restricted-fs + OnRequest, the
|
||||
// workspace-only floor confirmed via `codex doctor`).
|
||||
// -s danger-full-access (overrides even a named profile); otherwise the named profile, or the
|
||||
// explicit -s workspace-write floor flag (the no-flags default does NOT grant the --add-dir
|
||||
// writable roots this launch passes, so the floor must be the explicit workspace-write flag).
|
||||
const cmd = opts.resumeToken
|
||||
? buildCodexResumeCore(opts.resumeToken, profile, false, queueStateDirArg.trim() || undefined)
|
||||
: `codex${codexPostureArg(profileArg)} -C ${shellQuote(binding.cwd)}${gitDirArg}${queueStateDirArg}${modelArg}`;
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
// A simple deterministic setting that rides the STABLE launch-flag surface only (per the founder's
|
||||
// two-surface rule: launch flags may be deterministic code; config-file policy may NOT). When ON,
|
||||
// every managed seat boots at its harness's maximally-permissive LAUNCH FLAG:
|
||||
// - Claude: --dangerously-skip-permissions (permission bypass)
|
||||
// - Codex: --dangerously-bypass-approvals-and-sandbox (permission + sandbox bypass)
|
||||
// - Pi: --approve (full RESOURCE TRUST — Pi's
|
||||
// - Claude: --dangerously-skip-permissions (permission bypass)
|
||||
// - Codex: -s danger-full-access (maximally-permissive sandbox; OFF floor = the
|
||||
// explicit -s workspace-write flag, NOT a harness default)
|
||||
// - Pi: --approve (full RESOURCE TRUST — Pi's
|
||||
// --approve/--no-approve govern RESOURCE TRUST, not a permission policy)
|
||||
// When OFF (the default), seats boot with the usability floor, unchanged. The YOLO path writes ZERO
|
||||
// config files — it only selects a launch flag. Opt-in via the OPENRIG_YOLO env setting. (The
|
||||
@@ -27,12 +28,19 @@ export function claudePostureFlag(env: NodeJS.ProcessEnv = process.env): string
|
||||
}
|
||||
|
||||
/**
|
||||
* Codex launch posture segment (leading space included). YOLO forces the full-bypass flag on EVERY
|
||||
* seat, overriding even a named `-p` profile; otherwise pass the caller's profile arg (or "" for the
|
||||
* harness-default floor). `profileArg` is the already-formatted ` -p <profile>` string or "".
|
||||
* Codex launch posture segment (leading space included), applied on EVERY managed Codex path
|
||||
* (fresh / resume / native-fork). `profileArg` is the already-formatted ` -p <profile>` string or "".
|
||||
* - YOLO ON → ` -s danger-full-access` (maximally-permissive sandbox), overriding even a named profile.
|
||||
* - OFF + named profile → the profile (it governs its own sandbox).
|
||||
* - OFF + no profile → the EXPLICIT workspace-only floor ` -s workspace-write`. NOTE: emitting NO
|
||||
* sandbox flag is NOT equivalent — on the installed managed runtime the no-flags default does not
|
||||
* grant the `--add-dir` writable roots managed seats pass, so the seat fails before readiness (QA
|
||||
* firsthand). The floor must therefore be the explicit workspace-write flag (the arch-ruled value).
|
||||
* No forced approval flag on any path — the approval flow is left to Codex, unforced.
|
||||
*/
|
||||
export function codexPostureArg(profileArg: string, env: NodeJS.ProcessEnv = process.env): string {
|
||||
return yoloEnabled(env) ? " --dangerously-bypass-approvals-and-sandbox" : profileArg;
|
||||
if (yoloEnabled(env)) return " -s danger-full-access";
|
||||
return profileArg ? profileArg : " -s workspace-write";
|
||||
}
|
||||
|
||||
/** Pi RESOURCE TRUST (Pi's --approve/--no-approve govern resource trust, NOT a permission policy):
|
||||
|
||||
@@ -49,9 +49,9 @@ export function buildCodexResumeCore(
|
||||
extraArgs?: string,
|
||||
): string {
|
||||
// OPR.0.4.8.2: the RESUME path uses the SAME posture decision (codexPostureArg) as fresh/fork.
|
||||
// YOLO forces full-bypass (overriding even a named profile); otherwise a named profile governs
|
||||
// itself and the no-profile case is the harness-default floor (no forced -a/-s; Codex's no-flags
|
||||
// default is restricted-fs + OnRequest, the workspace-only floor confirmed via `codex doctor`).
|
||||
// YOLO forces -s danger-full-access (overriding even a named profile); otherwise a named profile
|
||||
// governs itself and the no-profile case is the explicit -s workspace-write floor flag (the
|
||||
// no-flags default does NOT grant the --add-dir writable roots managed seats pass).
|
||||
const profileArg = codexConfigProfile ? ` -p ${shellQuote(codexConfigProfile)}` : "";
|
||||
const profileOrPosture = codexPostureArg(profileArg);
|
||||
const middle = extraArgs ? `${extraArgs} ` : "";
|
||||
|
||||
@@ -147,8 +147,8 @@ function computeRecoveryGuidance(input: {
|
||||
|
||||
return {
|
||||
summary: resumeToken
|
||||
? "Try native Codex resume first; Codex uses its harness default (restricted fs + on-request) unless a named config profile is present."
|
||||
: "No stored Codex resume token. Try codex resume --last; Codex uses its harness default unless a named config profile is present.",
|
||||
? "Try native Codex resume first; the managed launch sets the explicit -s workspace-write floor flag unless a named config profile is present."
|
||||
: "No stored Codex resume token. Try codex -s workspace-write resume --last; the managed launch sets the explicit -s workspace-write floor flag unless a named config profile is present.",
|
||||
commands,
|
||||
notes,
|
||||
};
|
||||
|
||||
@@ -58,13 +58,13 @@ describe("CodexResumeAdapter", () => {
|
||||
|
||||
expect(sendText).toHaveBeenCalledOnce();
|
||||
expect(sendText.mock.calls[0]![0]).toBe("r99-demo1-impl");
|
||||
expect(sendText.mock.calls[0]![1]).toBe("codex resume 'uuid-123'");
|
||||
expect(sendText.mock.calls[0]![1]).toBe("codex -s workspace-write resume 'uuid-123'");
|
||||
expect(sendKeys).toHaveBeenCalledOnce();
|
||||
expect(sendKeys.mock.calls[0]![1]).toEqual(["Enter"]);
|
||||
expect(sendText.mock.invocationCallOrder[0]).toBeLessThan(sendKeys.mock.invocationCallOrder[0]!);
|
||||
});
|
||||
|
||||
it("codex_last: sendText posture-preserving codex resume --last", async () => {
|
||||
it("codex_last: sendText posture-preserving codex -s workspace-write resume --last", async () => {
|
||||
const sendText = vi.fn(async () => ({ ok: true as const }));
|
||||
const sendKeys = vi.fn(async () => ({ ok: true as const }));
|
||||
const tmux = mockTmux({ sendText, sendKeys });
|
||||
@@ -73,7 +73,7 @@ describe("CodexResumeAdapter", () => {
|
||||
await adapter.resume("r99-demo1-impl", "codex_last", null, "/repo");
|
||||
|
||||
expect(sendText).toHaveBeenCalledOnce();
|
||||
expect(sendText.mock.calls[0]![1]).toBe("codex resume --last");
|
||||
expect(sendText.mock.calls[0]![1]).toBe("codex -s workspace-write resume --last");
|
||||
expect(sendKeys).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
@@ -112,7 +112,7 @@ describe("CodexResumeAdapter", () => {
|
||||
|
||||
await adapter.resume("r99-demo1-impl", "codex_id", "uuid; rm -rf /", "/repo");
|
||||
|
||||
expect(sendText.mock.calls[0]![1]).toBe("codex resume 'uuid; rm -rf /'");
|
||||
expect(sendText.mock.calls[0]![1]).toBe("codex -s workspace-write resume 'uuid; rm -rf /'");
|
||||
});
|
||||
|
||||
it("profile-bearing resume uses -p flag after preflight passes", async () => {
|
||||
@@ -281,7 +281,7 @@ describe("CodexResumeAdapter", () => {
|
||||
// Closes the lifecycle scenario matrix slice's documented Codex deferral.
|
||||
it("probe says attention_required (codex auth-refusal) -> { ok: false, code: 'attention_required', evidence }", async () => {
|
||||
const refusalPane = [
|
||||
"$ codex resume 019d-token",
|
||||
"$ codex -s workspace-write resume 019d-token",
|
||||
"Error: Your access token could not be refreshed because you have since",
|
||||
"logged out or signed in to another account. Please sign in again.",
|
||||
].join("\n");
|
||||
|
||||
@@ -65,12 +65,12 @@ function expectedFreshLaunchCommand(options: { cwd?: string; model?: string; que
|
||||
const gitDirArg = ` --add-dir ${quote(nodePath.join(cwd, ".git"))}`;
|
||||
const queueDirArg = options.queueRoot === null ? "" : ` --add-dir ${quote(options.queueRoot ?? testQueueRoot())}`;
|
||||
const modelArg = options.model ? ` -m ${quote(options.model)}` : "";
|
||||
return `codex -C ${quote(cwd)}${gitDirArg}${queueDirArg}${modelArg}`;
|
||||
return `codex -s workspace-write -C ${quote(cwd)}${gitDirArg}${queueDirArg}${modelArg}`;
|
||||
}
|
||||
|
||||
function expectedResumeCommand(token = "sess-456", queueRoot: string | null = testQueueRoot()): string {
|
||||
const queueDirArg = queueRoot === null ? "" : `--add-dir ${quote(queueRoot)} `;
|
||||
return `codex resume ${queueDirArg}${quote(token)}`;
|
||||
return `codex -s workspace-write resume ${queueDirArg}${quote(token)}`;
|
||||
}
|
||||
|
||||
function expectedProfileFreshLaunchCommand(profile: string, options: { cwd?: string; model?: string; queueRoot?: string | null } = {}): string {
|
||||
@@ -679,8 +679,8 @@ describe("Codex runtime adapter", () => {
|
||||
fsOps: mockFs(),
|
||||
listProcesses: () => [
|
||||
{ pid: 900, ppid: 1, command: "-zsh" },
|
||||
{ pid: 901, ppid: 900, command: "node /opt/homebrew/bin/codex -C /project" },
|
||||
{ pid: 902, ppid: 901, command: "/opt/homebrew/lib/node_modules/@openai/codex/vendor/codex/codex -C /project" },
|
||||
{ pid: 901, ppid: 900, command: "node /opt/homebrew/bin/codex -s workspace-write -C /project" },
|
||||
{ pid: 902, ppid: 901, command: "/opt/homebrew/lib/node_modules/@openai/codex/vendor/codex/codex -s workspace-write -C /project" },
|
||||
],
|
||||
readThreadIdByPid: (pid) => pid === 902 ? "019d45bc-117d-78a3-a4ad-6fb186e5a86d" : undefined,
|
||||
sleep: async () => {},
|
||||
@@ -906,7 +906,7 @@ describe("Codex runtime adapter", () => {
|
||||
// commit 63ee206 alongside the legacy CodexResumeAdapter path.
|
||||
it("launchHarness returns attention_required when Codex post-logout token-refresh fails during resume", async () => {
|
||||
const refusalPane = [
|
||||
"$ codex resume sess-456",
|
||||
"$ codex -s workspace-write resume sess-456",
|
||||
"Error: Your access token could not be refreshed because you have since",
|
||||
"logged out or signed in to another account. Please sign in again.",
|
||||
].join("\n");
|
||||
@@ -933,7 +933,7 @@ describe("Codex runtime adapter", () => {
|
||||
const tmux = mockTmux({
|
||||
getPaneCommand: vi.fn(async () => "zsh"),
|
||||
capturePaneContent: vi.fn(async () => [
|
||||
"$ codex resume sess-456",
|
||||
"$ codex -s workspace-write resume sess-456",
|
||||
"Your access token could not be refreshed.",
|
||||
"Please log out and sign in again.",
|
||||
].join("\n")),
|
||||
@@ -1046,7 +1046,7 @@ describe("Codex runtime adapter", () => {
|
||||
return callCount <= 10 ? "node" : "codex";
|
||||
}),
|
||||
capturePaneContent: vi.fn(async () => {
|
||||
if (callCount <= 10) return "codex resume 019ecd3b-test-thread\nCodex v0.128.0\nloading...";
|
||||
if (callCount <= 10) return "codex -s workspace-write resume 019ecd3b-test-thread\nCodex v0.128.0\nloading...";
|
||||
return "OpenAI Codex (v0.128.0)\n gpt-5.5 · session 019ecd3b-test-thread\n› ";
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -13,9 +13,9 @@ describe("native resume probe", () => {
|
||||
).toBe("claude --resume 'abc-123' --name 'dev-impl@demo-rig'");
|
||||
});
|
||||
|
||||
it("builds a Codex no-profile resume with NO forced flags (harness default)", () => {
|
||||
it("builds a Codex no-profile resume with the explicit -s workspace-write floor flag", () => {
|
||||
expect(buildNativeResumeCommand("codex", "019d-token")).toBe(
|
||||
"codex resume '019d-token'"
|
||||
"codex -s workspace-write resume '019d-token'"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -31,9 +31,9 @@ describe("native resume probe", () => {
|
||||
});
|
||||
|
||||
describe("buildCodexResumeCore (shared builder)", () => {
|
||||
it("no-profile emits NO forced flags (harness default) matching fresh launch", () => {
|
||||
it("no-profile emits the explicit -s workspace-write floor flag matching fresh launch", () => {
|
||||
expect(buildCodexResumeCore("tok-123")).toBe(
|
||||
"codex resume 'tok-123'"
|
||||
"codex -s workspace-write resume 'tok-123'"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("native resume probe", () => {
|
||||
|
||||
it("useLast emits --last instead of token", () => {
|
||||
expect(buildCodexResumeCore("", null, true)).toBe(
|
||||
"codex resume --last"
|
||||
"codex -s workspace-write resume --last"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -524,7 +524,7 @@ describe("native resume probe", () => {
|
||||
describe("Codex auth-refusal recognition", () => {
|
||||
it("classifies Codex post-logout token-refresh failure as attention_required", () => {
|
||||
const paneContent = [
|
||||
"$ codex resume 019d-token",
|
||||
"$ codex -s workspace-write resume 019d-token",
|
||||
"Error: Your access token could not be refreshed because you have since",
|
||||
"logged out or signed in to another account. Please sign in again.",
|
||||
].join("\n");
|
||||
@@ -542,7 +542,7 @@ describe("native resume probe", () => {
|
||||
|
||||
it("classifies Codex token-refresh failure with `log out and sign in` guidance as attention_required", () => {
|
||||
const paneContent = [
|
||||
"$ codex resume 019d-token",
|
||||
"$ codex -s workspace-write resume 019d-token",
|
||||
"Your access token could not be refreshed.",
|
||||
"Please log out and sign in again.",
|
||||
].join("\n");
|
||||
|
||||
@@ -191,9 +191,9 @@ describe("Node Inventory Projection", () => {
|
||||
expect(entry?.resumeCommand).toBeNull();
|
||||
expect(entry?.recoveryGuidance).toBeNull(); // relocated to detail
|
||||
const detail = getNodeDetail(db, "rig-2", "dev.qa");
|
||||
expect(detail?.recoveryGuidance?.summary).toContain("codex resume --last");
|
||||
expect(detail?.recoveryGuidance?.summary).toContain("harness default");
|
||||
expect(detail?.recoveryGuidance?.commands).toEqual(["cd /workspace/app", "codex resume --last"]);
|
||||
expect(detail?.recoveryGuidance?.summary).toContain("codex -s workspace-write resume --last");
|
||||
expect(detail?.recoveryGuidance?.summary).toContain("explicit -s workspace-write floor flag");
|
||||
expect(detail?.recoveryGuidance?.commands).toEqual(["cd /workspace/app", "codex -s workspace-write resume --last"]);
|
||||
expect(detail?.recoveryGuidance?.notes).toContain("Use workspace and recent prompt text to identify the right conversation.");
|
||||
expect(detail?.recoveryGuidance?.notes).toContain("If the identity anchor was captured, the picker may include: dev-qa@test-rig");
|
||||
});
|
||||
|
||||
@@ -100,8 +100,8 @@ describe("ResumeMetadataRefresher", () => {
|
||||
tmuxAdapter: tmux,
|
||||
listProcesses: () => [
|
||||
{ pid: 900, ppid: 1, command: "-zsh" },
|
||||
{ pid: 901, ppid: 900, command: "node /opt/homebrew/bin/codex -C /project" },
|
||||
{ pid: 902, ppid: 901, command: "/opt/homebrew/lib/node_modules/@openai/codex/vendor/codex/codex -C /project" },
|
||||
{ pid: 901, ppid: 900, command: "node /opt/homebrew/bin/codex -s workspace-write -C /project" },
|
||||
{ pid: 902, ppid: 901, command: "/opt/homebrew/lib/node_modules/@openai/codex/vendor/codex/codex -s workspace-write -C /project" },
|
||||
],
|
||||
readCodexThreadIdByPid: (pid) => pid === 902 ? "019d45c3-e909-7152-b52e-34edab4070ed" : undefined,
|
||||
sleep: async () => {},
|
||||
|
||||
@@ -75,20 +75,20 @@ describe("OPR.0.4.8.2 YOLO mode — opt-in, default OFF, launch-flag surface onl
|
||||
expect(on).not.toContain("--permission-mode acceptEdits");
|
||||
});
|
||||
|
||||
it("Codex resume: OFF -> harness-default floor (no bypass); ON -> --dangerously-bypass-approvals-and-sandbox", () => {
|
||||
it("Codex resume: OFF -> explicit -s workspace-write floor flag; ON -> -s danger-full-access", () => {
|
||||
delete process.env.OPENRIG_YOLO;
|
||||
const off = buildCodexResumeCore("tok-1", null, false);
|
||||
expect(off).toBe("codex resume 'tok-1'");
|
||||
expect(off).toBe("codex -s workspace-write resume 'tok-1'");
|
||||
|
||||
process.env.OPENRIG_YOLO = "1";
|
||||
const on = buildCodexResumeCore("tok-1", null, false);
|
||||
expect(on).toContain("--dangerously-bypass-approvals-and-sandbox");
|
||||
expect(on).toContain("-s danger-full-access");
|
||||
});
|
||||
|
||||
it("Codex resume ON overrides even a named config profile (every seat full-bypass)", () => {
|
||||
it("Codex resume ON overrides even a named config profile (every seat -s danger-full-access)", () => {
|
||||
process.env.OPENRIG_YOLO = "1";
|
||||
const on = buildCodexResumeCore("tok-1", "my-profile", false);
|
||||
expect(on).toContain("--dangerously-bypass-approvals-and-sandbox");
|
||||
expect(on).toContain("-s danger-full-access");
|
||||
expect(on).not.toContain("-p 'my-profile'");
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ describe("OPR.0.4.8.2 YOLO mode — opt-in, default OFF, launch-flag surface onl
|
||||
expect(on).not.toContain("--permission-mode acceptEdits");
|
||||
});
|
||||
|
||||
it("Codex native FORK carries the posture: OFF no bypass / ON --dangerously-bypass-approvals-and-sandbox", async () => {
|
||||
it("Codex native FORK carries the posture: OFF -s workspace-write / ON -s danger-full-access", async () => {
|
||||
const codexFs = {
|
||||
readFile: () => { throw new Error("nf"); },
|
||||
writeFile: () => {},
|
||||
@@ -126,20 +126,21 @@ describe("OPR.0.4.8.2 YOLO mode — opt-in, default OFF, launch-flag surface onl
|
||||
await new CodexRuntimeAdapter({ tmux: tmuxOff, fsOps: codexFs }).launchHarness(makeBinding(), forkOpts);
|
||||
const off = (tmuxOff.sendText as ReturnType<typeof vi.fn>).mock.calls[0]?.[1] as string;
|
||||
expect(off).toContain("fork");
|
||||
expect(off).not.toContain("--dangerously-bypass-approvals-and-sandbox");
|
||||
expect(off).toContain("-s workspace-write");
|
||||
expect(off).not.toContain("-s danger-full-access");
|
||||
|
||||
process.env.OPENRIG_YOLO = "1";
|
||||
const tmuxOn = mockTmux();
|
||||
await new CodexRuntimeAdapter({ tmux: tmuxOn, fsOps: codexFs }).launchHarness(makeBinding(), forkOpts);
|
||||
const on = (tmuxOn.sendText as ReturnType<typeof vi.fn>).mock.calls[0]?.[1] as string;
|
||||
expect(on).toContain("--dangerously-bypass-approvals-and-sandbox");
|
||||
expect(on).toContain("-s danger-full-access");
|
||||
expect(on).toContain("fork");
|
||||
});
|
||||
|
||||
it("codexPostureArg: OFF passes the profile arg through; ON forces the bypass flag", () => {
|
||||
it("codexPostureArg: OFF no-profile -> explicit -s workspace-write floor; OFF profile passes through; ON -> -s danger-full-access", () => {
|
||||
expect(codexPostureArg(" -p 'x'", {} as NodeJS.ProcessEnv)).toBe(" -p 'x'");
|
||||
expect(codexPostureArg("", {} as NodeJS.ProcessEnv)).toBe("");
|
||||
expect(codexPostureArg(" -p 'x'", { OPENRIG_YOLO: "1" } as NodeJS.ProcessEnv)).toBe(" --dangerously-bypass-approvals-and-sandbox");
|
||||
expect(codexPostureArg("", {} as NodeJS.ProcessEnv)).toBe(" -s workspace-write");
|
||||
expect(codexPostureArg(" -p 'x'", { OPENRIG_YOLO: "1" } as NodeJS.ProcessEnv)).toBe(" -s danger-full-access");
|
||||
});
|
||||
|
||||
it("piTrust (RESOURCE TRUST, not permission policy): OFF keeps configured/no-approve; ON forces approve", () => {
|
||||
|
||||
Reference in new issue
Block a user