@@ -45,6 +45,96 @@ const echo = Tool.make({
4545} )
4646
4747describe ( "ToolRegistry" , ( ) => {
48+ it . effect ( "matches V1 whole-tool filtering, edit aliases, and ordered wildcard precedence" , ( ) =>
49+ Effect . gen ( function * ( ) {
50+ const registry = yield * ToolRegistry . Service
51+ const transform = yield * registry . transform ( )
52+ const sessionID = SessionV2 . ID . make ( "ses_registry_filter" )
53+ yield * transform ( ( editor ) => {
54+ editor . set ( "question" , { tool : echo } )
55+ editor . set ( "bash" , { tool : echo } )
56+ editor . set ( "edit" , { tool : echo } )
57+ editor . set ( "write" , { tool : echo } )
58+ editor . set ( "apply_patch" , { tool : echo } )
59+ } )
60+
61+ const names = ( rules : PermissionV2 . Ruleset ) =>
62+ registry . definitions ( rules ) . pipe ( Effect . map ( ( definitions ) => definitions . map ( ( tool ) => tool . name ) ) )
63+
64+ expect ( yield * names ( [ { action : "question" , resource : "*" , effect : "deny" } ] ) ) . toEqual ( [
65+ "bash" ,
66+ "edit" ,
67+ "write" ,
68+ "apply_patch" ,
69+ ] )
70+
71+ expect (
72+ yield * names ( [
73+ { action : "*" , resource : "*" , effect : "deny" } ,
74+ { action : "question" , resource : "private" , effect : "allow" } ,
75+ ] ) ,
76+ ) . toEqual ( [ "question" ] )
77+
78+ expect (
79+ yield * names ( [
80+ { action : "question" , resource : "private" , effect : "allow" } ,
81+ { action : "*" , resource : "*" , effect : "deny" } ,
82+ ] ) ,
83+ ) . toEqual ( [ ] )
84+
85+ expect ( yield * names ( [ { action : "question" , resource : "*" , effect : "ask" } ] ) ) . toContain ( "question" )
86+ expect ( yield * names ( [ { action : "edit" , resource : "*" , effect : "deny" } ] ) ) . toEqual ( [ "question" , "bash" ] )
87+ expect (
88+ yield * names ( [
89+ { action : "edit" , resource : "*" , effect : "deny" } ,
90+ { action : "edit" , resource : "*.md" , effect : "ask" } ,
91+ ] ) ,
92+ ) . toEqual ( [ "question" , "bash" , "edit" , "write" , "apply_patch" ] )
93+ expect (
94+ yield * names ( [
95+ { action : "edit" , resource : "*.md" , effect : "allow" } ,
96+ { action : "edit" , resource : "*" , effect : "deny" } ,
97+ ] ) ,
98+ ) . toEqual ( [ "question" , "bash" ] )
99+ } ) ,
100+ )
101+
102+ it . effect ( "settles only through concrete leaf authorization, not catalog visibility" , ( ) =>
103+ Effect . gen ( function * ( ) {
104+ const registry = yield * ToolRegistry . Service
105+ const transform = yield * registry . transform ( )
106+ const sessionID = SessionV2 . ID . make ( "ses_registry_stale" )
107+ let executed = false
108+ yield * transform ( ( editor ) =>
109+ editor . set ( "question" , {
110+ tool : echo ,
111+ permission : { action : "question" , resource : "*" } ,
112+ authorize : ( { assertPermission } ) =>
113+ assertPermission ( { action : "question" , resources : [ "actual" ] } ) . pipe (
114+ Effect . mapError ( ( ) => new ToolFailure ( { message : "Denied" } ) ) ,
115+ ) ,
116+ execute : ( ) =>
117+ Effect . sync ( ( ) => {
118+ executed = true
119+ return { text : "unexpected" }
120+ } ) ,
121+ } ) ,
122+ )
123+
124+ expect (
125+ ( yield * registry . definitions ( [ { action : "question" , resource : "*" , effect : "deny" } ] ) ) . map ( ( tool ) => tool . name ) ,
126+ ) . toEqual ( [ ] )
127+ expect (
128+ yield * registry . settle ( {
129+ sessionID,
130+ call : { type : "tool-call" , id : "call-stale" , name : "question" , input : { text : "hello" } } ,
131+ } ) ,
132+ ) . toMatchObject ( { result : { type : "json" , value : { text : "unexpected" } } } )
133+ expect ( assertions . at ( - 1 ) ) . toMatchObject ( { action : "question" , resources : [ "actual" ] } )
134+ expect ( executed ) . toBe ( true )
135+ } ) ,
136+ )
137+
48138 it . effect ( "rebuilds advertised definitions when a scoped transform closes" , ( ) =>
49139 Effect . gen ( function * ( ) {
50140 const registry = yield * ToolRegistry . Service
0 commit comments