@@ -569,4 +569,86 @@ describe("cli/run-events", () => {
569569 } ) ,
570570 ) ,
571571 )
572+
573+ // F8: when skipPermissions=true the auto-approve branch must produce symmetric
574+ // telemetry — Stats counter + JSON event — so operators running
575+ // --dangerously-skip-permissions get an audit trail of what was approved.
576+ it . live ( "increments autoApprovedPermissions and emits JSON event when skipPermissions=true" , ( ) =>
577+ provideTmpdirInstance ( ( ) =>
578+ Effect . gen ( function * ( ) {
579+ const permission = yield * Permission . Service
580+ const bus = yield * Bus . Service
581+ const rootSessionID = SessionID . make ( "ses_root_auto_approve_0000000000000" )
582+ const replies : Array < { sessionID : SessionID ; reply : string } > = [ ]
583+ const unsubscribeReply = yield * bus . subscribeCallback ( Permission . Event . Replied , ( evt ) => {
584+ replies . push ( { sessionID : evt . properties . sessionID , reply : evt . properties . reply } )
585+ } )
586+
587+ const writes : string [ ] = [ ]
588+ const originalWrite = process . stdout . write . bind ( process . stdout )
589+ process . stdout . write = ( ( chunk : string | Uint8Array ) => {
590+ writes . push ( typeof chunk === "string" ? chunk : Buffer . from ( chunk ) . toString ( "utf8" ) )
591+ return true
592+ } ) as typeof process . stdout . write
593+
594+ yield * Effect . acquireUseRelease (
595+ RunEvents . make ( {
596+ rootSessionID,
597+ skipPermissions : true ,
598+ jsonMode : true ,
599+ } ) ,
600+ ( handle ) =>
601+ Effect . gen ( function * ( ) {
602+ const exit = yield * Effect . exit (
603+ permission . ask ( {
604+ sessionID : rootSessionID ,
605+ permission : "bash" ,
606+ patterns : [ "ls" ] ,
607+ metadata : { } ,
608+ always : [ ] ,
609+ ruleset : [ { permission : "bash" , pattern : "*" , action : "ask" } ] ,
610+ } ) ,
611+ )
612+
613+ yield * pollUntil (
614+ ( ) => Effect . sync ( ( ) => ( replies . length === 1 ? Option . some ( true ) : Option . none ( ) ) ) ,
615+ { label : "permission.replied event" } ,
616+ )
617+
618+ expect ( Exit . isSuccess ( exit ) ) . toBe ( true )
619+ expect ( replies [ 0 ] ?. reply ) . toBe ( "once" )
620+ expect ( handle . stats . autoApprovedPermissions ) . toBe ( 1 )
621+ expect ( handle . stats . autoRejectedPermissions ) . toBe ( 0 )
622+ } ) ,
623+ ( handle ) =>
624+ Effect . sync ( ( ) => {
625+ unsubscribeReply ( )
626+ handle . unsubscribe ( )
627+ } ) ,
628+ ) . pipe (
629+ Effect . ensuring (
630+ Effect . sync ( ( ) => {
631+ process . stdout . write = originalWrite
632+ } ) ,
633+ ) ,
634+ )
635+
636+ const payload = JSON . parse ( ( writes [ 0 ] ?? "" ) . trim ( ) ) as {
637+ type : string
638+ timestamp : number
639+ sessionID : string
640+ kind : string
641+ autoApproveSessionID : string
642+ totalAutoApproves : number
643+ }
644+
645+ expect ( payload . type ) . toBe ( "auto-approve" )
646+ expect ( typeof payload . timestamp ) . toBe ( "number" )
647+ expect ( payload . sessionID ) . toBe ( rootSessionID )
648+ expect ( payload . kind ) . toBe ( "permission" )
649+ expect ( payload . autoApproveSessionID ) . toBe ( rootSessionID )
650+ expect ( payload . totalAutoApproves ) . toBe ( 1 )
651+ } ) ,
652+ ) ,
653+ )
572654} )
0 commit comments