@@ -128,61 +128,45 @@ test("fromConfig - does not expand tilde in middle of path", () => {
128128 expect ( result ) . toEqual ( [ { permission : "external_directory" , pattern : "/some/~/path" , action : "allow" } ] )
129129} )
130130
131- // Top-level wildcard-vs-specific precedence semantics.
132- //
133- // fromConfig sorts top-level keys so wildcard permissions (containing "*")
134- // come before specific permissions. Combined with `findLast` in evaluate(),
135- // this gives the intuitive semantic "specific tool rules override the `*`
136- // fallback", regardless of the order the user wrote the keys in their JSON.
137- //
138- // Sub-pattern order inside a single permission key (e.g. `bash: { "*": "allow", "rm": "deny" }`)
139- // still depends on insertion order — only top-level keys are sorted.
140-
141- test ( "fromConfig - specific key beats wildcard regardless of JSON key order" , ( ) => {
131+ // Permission precedence follows config insertion order. `evaluate()` uses the
132+ // last matching rule, so later config entries intentionally override earlier
133+ // entries even when a wildcard appears after a specific permission.
134+
135+ test ( "fromConfig - preserves top-level config key order" , ( ) => {
142136 const wildcardFirst = Permission . fromConfig ( { "*" : "deny" , bash : "allow" } )
143137 const specificFirst = Permission . fromConfig ( { bash : "allow" , "*" : "deny" } )
144138
145- // Both orderings produce the same ruleset
146- expect ( wildcardFirst ) . toEqual ( specificFirst )
139+ expect ( wildcardFirst . map ( ( r ) => r . permission ) ) . toEqual ( [ "*" , "bash" ] )
140+ expect ( specificFirst . map ( ( r ) => r . permission ) ) . toEqual ( [ "bash" , "*" ] )
147141
148- // And both evaluate bash → allow (bash rule wins over * fallback)
149142 expect ( Permission . evaluate ( "bash" , "ls" , wildcardFirst ) . action ) . toBe ( "allow" )
150- expect ( Permission . evaluate ( "bash" , "ls" , specificFirst ) . action ) . toBe ( "allow " )
143+ expect ( Permission . evaluate ( "bash" , "ls" , specificFirst ) . action ) . toBe ( "deny " )
151144} )
152145
153- test ( "fromConfig - wildcard acts as fallback for permissions with no specific rule " , ( ) => {
154- const ruleset = Permission . fromConfig ( { bash : "allow " , "*" : "ask " } )
146+ test ( "fromConfig - wildcard acts as fallback when it appears before specifics " , ( ) => {
147+ const ruleset = Permission . fromConfig ( { "*" : "ask " , bash : "allow " } )
155148 expect ( Permission . evaluate ( "edit" , "foo.ts" , ruleset ) . action ) . toBe ( "ask" )
156149 expect ( Permission . evaluate ( "bash" , "ls" , ruleset ) . action ) . toBe ( "allow" )
157150} )
158151
159- test ( "fromConfig - top-level ordering: wildcards first, specifics after " , ( ) => {
152+ test ( "fromConfig - top-level ordering is not sorted by wildcard specificity " , ( ) => {
160153 const ruleset = Permission . fromConfig ( {
161154 bash : "allow" ,
162155 "*" : "ask" ,
163156 edit : "deny" ,
164157 "mcp_*" : "allow" ,
165158 } )
166- // wildcards (* and mcp_*) come before specifics (bash, edit)
167- const permissions = ruleset . map ( ( r ) => r . permission )
168- expect ( permissions . slice ( 0 , 2 ) . sort ( ) ) . toEqual ( [ "*" , "mcp_*" ] )
169- expect ( permissions . slice ( 2 ) ) . toEqual ( [ "bash" , "edit" ] )
159+ expect ( ruleset . map ( ( r ) => r . permission ) ) . toEqual ( [ "bash" , "*" , "edit" , "mcp_*" ] )
170160} )
171161
172- test ( "fromConfig - sub-pattern insertion order inside a tool key is preserved (only top-level sorts)" , ( ) => {
173- // Sub-patterns within a single tool key use the documented "`*` first,
174- // specific patterns after" convention (findLast picks specifics). The
175- // top-level sort must not touch sub-pattern ordering.
162+ test ( "fromConfig - sub-pattern insertion order inside a tool key is preserved" , ( ) => {
176163 const ruleset = Permission . fromConfig ( { bash : { "*" : "deny" , "git *" : "allow" } } )
177164 expect ( ruleset . map ( ( r ) => r . pattern ) ) . toEqual ( [ "*" , "git *" ] )
178- // * fallback for unknown commands
179165 expect ( Permission . evaluate ( "bash" , "rm foo" , ruleset ) . action ) . toBe ( "deny" )
180- // specific pattern wins for git commands (it's last, findLast picks it)
181166 expect ( Permission . evaluate ( "bash" , "git status" , ruleset ) . action ) . toBe ( "allow" )
182167} )
183168
184- test ( "fromConfig - canonical documented example unchanged" , ( ) => {
185- // Regression guard for the example in docs/permissions.mdx
169+ test ( "fromConfig - documented fallback-first example" , ( ) => {
186170 const ruleset = Permission . fromConfig ( { "*" : "ask" , bash : "allow" , edit : "deny" } )
187171 expect ( Permission . evaluate ( "bash" , "ls" , ruleset ) . action ) . toBe ( "allow" )
188172 expect ( Permission . evaluate ( "edit" , "foo.ts" , ruleset ) . action ) . toBe ( "deny" )
@@ -448,7 +432,7 @@ test("evaluate - wildcard permission fallback for unknown tool", () => {
448432 expect ( result . action ) . toBe ( "ask" )
449433} )
450434
451- test ( "evaluate - permission patterns sorted by length regardless of object order " , ( ) => {
435+ test ( "evaluate - later wildcard permission can override earlier specific permission " , ( ) => {
452436 const result = Permission . evaluate ( "bash" , "rm" , [
453437 { permission : "bash" , pattern : "*" , action : "allow" } ,
454438 { permission : "*" , pattern : "*" , action : "deny" } ,
0 commit comments