@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
33import { CodexAppInventoryCache } from "./app-inventory-cache.js" ;
44import { CODEX_PLUGINS_MARKETPLACE_NAME } from "./config.js" ;
55import {
6+ buildCodexPluginAppsConfigPatchFromPolicyContext ,
67 buildCodexPluginThreadConfig ,
78 buildCodexPluginThreadConfigInputFingerprint ,
89 isCodexPluginThreadBindingStale ,
@@ -68,6 +69,9 @@ describe("Codex plugin thread config", () => {
6869 } ,
6970 } ,
7071 } ) ;
72+ expect ( config . configPatch ) . not . toHaveProperty ( "approvals_reviewer" ) ;
73+ const apps = config . configPatch ?. apps as Record < string , unknown > | undefined ;
74+ expect ( apps ?. [ "_default" ] ) . not . toHaveProperty ( "approvals_reviewer" ) ;
7175 expect ( config . policyContext . apps [ "google-calendar-app" ] ) . toEqual ( {
7276 configKey : "google-calendar" ,
7377 marketplaceName : CODEX_PLUGINS_MARKETPLACE_NAME ,
@@ -104,6 +108,7 @@ describe("Codex plugin thread config", () => {
104108 default_tools_approval_mode : "auto" ,
105109 } ) ;
106110 expect ( disabledApps ?. [ "google-calendar-app" ] ) . not . toHaveProperty ( "default_tools_enabled" ) ;
111+ expect ( disabledApps ?. [ "google-calendar-app" ] ) . not . toHaveProperty ( "approvals_reviewer" ) ;
107112 expect ( disabledApps ?. [ "google-calendar-app" ] ) . not . toHaveProperty ( "tools" ) ;
108113 expect (
109114 pluginOverrideDisabled . policyContext . apps [ "google-calendar-app" ] ?. allowDestructiveActions ,
@@ -135,6 +140,7 @@ describe("Codex plugin thread config", () => {
135140 open_world_enabled : true ,
136141 default_tools_approval_mode : "auto" ,
137142 } ) ;
143+ expect ( enabledApps ?. [ "google-calendar-app" ] ) . not . toHaveProperty ( "approvals_reviewer" ) ;
138144 expect (
139145 pluginOverrideEnabled . policyContext . apps [ "google-calendar-app" ] ?. allowDestructiveActions ,
140146 ) . toBe ( true ) ;
@@ -164,13 +170,14 @@ describe("Codex plugin thread config", () => {
164170 open_world_enabled : true ,
165171 default_tools_approval_mode : "auto" ,
166172 } ) ;
173+ expect ( apps ?. [ "google-calendar-app" ] ) . not . toHaveProperty ( "approvals_reviewer" ) ;
167174 expect ( config . policyContext . apps [ "google-calendar-app" ] ) . toMatchObject ( {
168175 allowDestructiveActions : true ,
169176 destructiveApprovalMode : "auto" ,
170177 } ) ;
171178 } ) ;
172179
173- it ( "exposes destructive app access while clearing only durable approval overrides for always mode" , async ( ) => {
180+ it ( "routes destructive approvals to the user while clearing durable overrides for always mode" , async ( ) => {
174181 const appCache = new CodexAppInventoryCache ( ) ;
175182 await appCache . refreshNow ( {
176183 key : "runtime" ,
@@ -258,10 +265,12 @@ describe("Codex plugin thread config", () => {
258265 const apps = config . configPatch ?. apps as Record < string , unknown > | undefined ;
259266 expect ( apps ?. [ "google-calendar-app" ] ) . toEqual ( {
260267 enabled : true ,
268+ approvals_reviewer : "user" ,
261269 destructive_enabled : true ,
262270 open_world_enabled : true ,
263271 default_tools_approval_mode : "auto" ,
264272 } ) ;
273+ expect ( config . configPatch ) . not . toHaveProperty ( "approvals_reviewer" ) ;
265274 expect ( config . policyContext . apps [ "google-calendar-app" ] ) . toMatchObject ( {
266275 allowDestructiveActions : true ,
267276 destructiveApprovalMode : "always" ,
@@ -285,6 +294,89 @@ describe("Codex plugin thread config", () => {
285294 } ) ;
286295 } ) ;
287296
297+ it . each ( [
298+ [ "auto" , "auto" , undefined ] ,
299+ [ "boolean true" , true , undefined ] ,
300+ [ "boolean false" , false , undefined ] ,
301+ [ "always" , "always" , "user" ] ,
302+ ] as const ) (
303+ "applies the resolved per-plugin %s reviewer policy over global always" ,
304+ async ( _name , pluginOverride , expectedReviewer ) => {
305+ const config = await buildReadyGoogleCalendarThreadConfig ( {
306+ codexPlugins : {
307+ enabled : true ,
308+ allow_destructive_actions : "always" ,
309+ plugins : {
310+ "google-calendar" : {
311+ marketplaceName : CODEX_PLUGINS_MARKETPLACE_NAME ,
312+ pluginName : "google-calendar" ,
313+ allow_destructive_actions : pluginOverride ,
314+ } ,
315+ } ,
316+ } ,
317+ } ) ;
318+
319+ const apps = config . configPatch ?. apps as Record < string , unknown > | undefined ;
320+ const app = apps ?. [ "google-calendar-app" ] as Record < string , unknown > | undefined ;
321+ expect ( app ?. approvals_reviewer ) . toBe ( expectedReviewer ) ;
322+ expect ( config . policyContext . apps [ "google-calendar-app" ] ?. destructiveApprovalMode ) . toBe (
323+ pluginOverride === true ? "allow" : pluginOverride === false ? "deny" : pluginOverride ,
324+ ) ;
325+ } ,
326+ ) ;
327+
328+ it ( "rebuilds persisted app policy with the same reviewer precedence" , ( ) => {
329+ const configPatch = buildCodexPluginAppsConfigPatchFromPolicyContext ( {
330+ fingerprint : "policy" ,
331+ apps : {
332+ "always-app" : {
333+ configKey : "always" ,
334+ marketplaceName : CODEX_PLUGINS_MARKETPLACE_NAME ,
335+ pluginName : "always" ,
336+ allowDestructiveActions : true ,
337+ destructiveApprovalMode : "always" ,
338+ mcpServerNames : [ "always" ] ,
339+ } ,
340+ "auto-app" : {
341+ configKey : "auto" ,
342+ marketplaceName : CODEX_PLUGINS_MARKETPLACE_NAME ,
343+ pluginName : "auto" ,
344+ allowDestructiveActions : true ,
345+ destructiveApprovalMode : "auto" ,
346+ mcpServerNames : [ "auto" ] ,
347+ } ,
348+ } ,
349+ pluginAppIds : {
350+ always : [ "always-app" ] ,
351+ auto : [ "auto-app" ] ,
352+ } ,
353+ } ) ;
354+
355+ expect ( configPatch ) . toEqual ( {
356+ apps : {
357+ _default : {
358+ enabled : false ,
359+ destructive_enabled : false ,
360+ open_world_enabled : false ,
361+ } ,
362+ "always-app" : {
363+ enabled : true ,
364+ approvals_reviewer : "user" ,
365+ destructive_enabled : true ,
366+ open_world_enabled : true ,
367+ default_tools_approval_mode : "auto" ,
368+ } ,
369+ "auto-app" : {
370+ enabled : true ,
371+ destructive_enabled : true ,
372+ open_world_enabled : true ,
373+ default_tools_approval_mode : "auto" ,
374+ } ,
375+ } ,
376+ } ) ;
377+ expect ( configPatch ) . not . toHaveProperty ( "approvals_reviewer" ) ;
378+ } ) ;
379+
288380 it ( "omits always policy apps when cwd effective approval overrides remain after cleanup" , async ( ) => {
289381 const appCache = new CodexAppInventoryCache ( ) ;
290382 await appCache . refreshNow ( {
@@ -958,6 +1050,7 @@ describe("Codex plugin thread config", () => {
9581050 request,
9591051 } ) ;
9601052
1053+ expect ( config . configPatch ) . not . toHaveProperty ( "approvals_reviewer" ) ;
9611054 expect ( config . configPatch ?. apps ) . toEqual ( {
9621055 _default : {
9631056 enabled : false ,
@@ -1049,6 +1142,7 @@ describe("Codex plugin thread config", () => {
10491142 request,
10501143 } ) ;
10511144
1145+ expect ( config . configPatch ) . not . toHaveProperty ( "approvals_reviewer" ) ;
10521146 expect ( config . configPatch ?. apps ) . toEqual ( {
10531147 _default : {
10541148 enabled : false ,
@@ -1552,6 +1646,9 @@ async function buildReadyGoogleCalendarThreadConfig(
15521646 if ( method === "plugin/read" ) {
15531647 return pluginDetail ( "google-calendar" , [ appSummary ( "google-calendar-app" ) ] ) ;
15541648 }
1649+ if ( method === "config/read" ) {
1650+ return { config : { } } ;
1651+ }
15551652 throw new Error ( `unexpected request ${ method } ` ) ;
15561653 } ,
15571654 } ) ;
0 commit comments