@@ -23,7 +23,6 @@ import type { AuthorizeVerdict, GateVerdict, PermissionGate } from "./gate.js";
2323import type { PermissionRequest } from "./types.js" ;
2424import {
2525 createDeniedCallEnvelope ,
26- fingerprintDeniedCall ,
2726 formatWorkerDenyWithGrantId ,
2827 getProcessWorkerGrantStore ,
2928 type WorkerGrantStore ,
@@ -164,17 +163,6 @@ function denyWorkerCallWithEnvelope(
164163 } ;
165164}
166165
167- /** Mutex key covering the envelope match: concurrent identical worker calls
168- * serialize through precheck-to-consume so one envelope allows exactly once. */
169- function workerGrantKey (
170- sessionId : string ,
171- call : ToolCallType ,
172- cwd : string ,
173- ) : string {
174- const identity = workerCallIdentity ( sessionId , call , cwd ) ;
175- return `${ sessionId } \n${ fingerprintDeniedCall ( identity . canonicalTool , identity . args , identity . cwd ) } ` ;
176- }
177-
178166async function authorizeWorkerCall (
179167 gate : PermissionGate ,
180168 call : ToolCallType ,
@@ -187,11 +175,13 @@ async function authorizeWorkerCall(
187175 if ( sessionId === undefined )
188176 return authorizeWorkerCallInner ( gate , call , grantOptions , workerCwd ) ;
189177 const store = grantOptions ?. store ?? getProcessWorkerGrantStore ( ) ;
190- return store . runExclusive ( workerGrantKey ( sessionId , call , workerCwd ) , ( ) =>
191- authorizeWorkerCallInner ( gate , call , grantOptions , workerCwd , {
192- sessionId,
193- store,
194- } ) ,
178+ return store . runExclusiveIdentity (
179+ workerCallIdentity ( sessionId , call , workerCwd ) ,
180+ ( ) =>
181+ authorizeWorkerCallInner ( gate , call , grantOptions , workerCwd , {
182+ sessionId,
183+ store,
184+ } ) ,
195185 ) ;
196186}
197187
@@ -203,10 +193,10 @@ async function authorizeWorkerCallInner(
203193 grant ?: { sessionId : string ; store : WorkerGrantStore } ,
204194) : Promise < { effect : "allow" } | { effect : "deny" ; reason : string } > {
205195 if ( grant !== undefined ) {
206- const precheck = grant . store . precheck (
207- workerCallIdentity ( grant . sessionId , call , workerCwd ) ,
208- ) ;
196+ const identity = workerCallIdentity ( grant . sessionId , call , workerCwd ) ;
197+ const precheck = grant . store . precheck ( identity ) ;
209198 if ( ! precheck . ok ) return { effect : "deny" , reason : precheck . blocker } ;
199+ if ( grant . store . hasAuthorizedRetry ( identity ) ) return { effect : "allow" } ;
210200 }
211201 const verdict = await gate . authorizeCall ( call ) ;
212202 // Authorize never consumes: it only permits the call to proceed to the
@@ -248,11 +238,13 @@ async function executionVerdictWorkerCall(
248238 if ( sessionId === undefined )
249239 return executionVerdictWorkerCallInner ( gate , call , grantOptions , workerCwd ) ;
250240 const store = grantOptions ?. store ?? getProcessWorkerGrantStore ( ) ;
251- return store . runExclusive ( workerGrantKey ( sessionId , call , workerCwd ) , ( ) =>
252- executionVerdictWorkerCallInner ( gate , call , grantOptions , workerCwd , {
253- sessionId,
254- store,
255- } ) ,
241+ return store . runExclusiveIdentity (
242+ workerCallIdentity ( sessionId , call , workerCwd ) ,
243+ ( ) =>
244+ executionVerdictWorkerCallInner ( gate , call , grantOptions , workerCwd , {
245+ sessionId,
246+ store,
247+ } ) ,
256248 ) ;
257249}
258250
@@ -264,10 +256,11 @@ async function executionVerdictWorkerCallInner(
264256 grant ?: { sessionId : string ; store : WorkerGrantStore } ,
265257) : Promise < AuthorizeVerdict > {
266258 if ( grant !== undefined ) {
267- const precheck = grant . store . precheck (
268- workerCallIdentity ( grant . sessionId , call , workerCwd ) ,
269- ) ;
259+ const identity = workerCallIdentity ( grant . sessionId , call , workerCwd ) ;
260+ const precheck = grant . store . precheck ( identity ) ;
270261 if ( ! precheck . ok ) return { effect : "deny" , reason : precheck . blocker } ;
262+ if ( grant . store . consumeAuthorizedRetry ( identity ) !== undefined )
263+ return { effect : "allow" } ;
271264 }
272265 const verdict = await gate . executionVerdict ( call ) ;
273266 // Sole consumption point: the execution backstop spends the envelope when
0 commit comments