@spacefast/common 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/agents/connect-targets.d.ts +4 -4
  2. package/dist/agents/connect-targets.js +3 -3
  3. package/dist/agents/private-key-oauth.d.ts +37 -0
  4. package/dist/agents/private-key-oauth.js +158 -0
  5. package/dist/config/domains.js +1 -1
  6. package/dist/config/index.d.ts +1 -0
  7. package/dist/config/index.js +1 -0
  8. package/dist/config/postgres-budget.d.ts +126 -0
  9. package/dist/config/postgres-budget.js +135 -0
  10. package/dist/config/postgres-budget.json +50 -0
  11. package/dist/contracts/access.d.ts +42 -24
  12. package/dist/contracts/access.js +73 -34
  13. package/dist/contracts/activity.d.ts +1 -1
  14. package/dist/contracts/activity.js +5 -5
  15. package/dist/contracts/api-keys.d.ts +53 -45
  16. package/dist/contracts/api-keys.js +1 -0
  17. package/dist/contracts/archives.d.ts +8 -8
  18. package/dist/contracts/builds.d.ts +8 -8
  19. package/dist/contracts/builds.js +28 -48
  20. package/dist/contracts/channels.js +1 -2
  21. package/dist/contracts/comments.d.ts +55 -19
  22. package/dist/contracts/comments.js +56 -17
  23. package/dist/contracts/common.d.ts +4 -3
  24. package/dist/contracts/common.js +2 -0
  25. package/dist/contracts/deployments.d.ts +3 -3
  26. package/dist/contracts/device-auth.d.ts +14 -12
  27. package/dist/contracts/docs.d.ts +5 -5
  28. package/dist/contracts/domains.d.ts +261 -1330
  29. package/dist/contracts/domains.js +39 -301
  30. package/dist/contracts/email-preferences.d.ts +50 -0
  31. package/dist/contracts/email-preferences.js +44 -0
  32. package/dist/contracts/enums.d.ts +1 -1
  33. package/dist/contracts/error-code-meta.d.ts +84 -15
  34. package/dist/contracts/error-code-meta.js +28 -4
  35. package/dist/contracts/error-codes.d.ts +1 -1
  36. package/dist/contracts/error-codes.js +24 -6
  37. package/dist/contracts/events.d.ts +13 -13
  38. package/dist/contracts/feature-lifecycle.d.ts +644 -2
  39. package/dist/contracts/feature-lifecycle.js +685 -2
  40. package/dist/contracts/features.d.ts +236 -24
  41. package/dist/contracts/features.js +3 -272
  42. package/dist/contracts/ids.d.ts +1 -2
  43. package/dist/contracts/ids.js +1 -2
  44. package/dist/contracts/internal.d.ts +3 -3
  45. package/dist/contracts/mcp.d.ts +6 -6
  46. package/dist/contracts/me.js +1 -1
  47. package/dist/contracts/mounts.d.ts +142 -0
  48. package/dist/contracts/mounts.js +56 -0
  49. package/dist/contracts/oauth-resources.d.ts +5 -5
  50. package/dist/contracts/oauth-resources.js +1 -0
  51. package/dist/contracts/operations.d.ts +2 -2
  52. package/dist/contracts/pages.d.ts +2 -2
  53. package/dist/contracts/pages.js +8 -3
  54. package/dist/contracts/plan-policy.d.ts +1 -1
  55. package/dist/contracts/platform.d.ts +3 -3
  56. package/dist/contracts/resources.d.ts +12 -20
  57. package/dist/contracts/resources.js +13 -37
  58. package/dist/contracts/runtime-api.d.ts +330 -16
  59. package/dist/contracts/runtime-api.js +58 -14
  60. package/dist/contracts/sf-config-v1.js +1 -1
  61. package/dist/contracts/sites.d.ts +12 -12
  62. package/dist/contracts/sites.js +18 -11
  63. package/dist/contracts/space-config.d.ts +2 -3
  64. package/dist/contracts/space-config.js +3 -1
  65. package/dist/contracts/spaces.d.ts +143 -102
  66. package/dist/contracts/spaces.js +109 -43
  67. package/dist/contracts/superadmin-emails.d.ts +25 -1
  68. package/dist/contracts/superadmin-emails.js +10 -15
  69. package/dist/contracts/superadmin-runtime.d.ts +2 -2
  70. package/dist/contracts/superadmin-spaces.d.ts +6 -14
  71. package/dist/contracts/tags.d.ts +43 -13
  72. package/dist/contracts/tags.js +195 -11
  73. package/dist/contracts/transfers.d.ts +1 -1
  74. package/dist/contracts/variables.d.ts +4 -4
  75. package/dist/contracts/variables.js +4 -3
  76. package/dist/contracts/webhooks.d.ts +2 -2
  77. package/dist/contracts/zero.d.ts +329 -7
  78. package/dist/contracts/zero.js +121 -2
  79. package/dist/dashboard-paths/index.d.ts +1 -1
  80. package/dist/docs/agent-setup.d.ts +4 -2
  81. package/dist/docs/agent-setup.js +15 -7
  82. package/dist/docs/agent-solutions.js +1 -1
  83. package/dist/docs/catalog.d.ts +8 -10
  84. package/dist/docs/catalog.js +8 -10
  85. package/dist/docs/error-docs.js +86 -18
  86. package/dist/utils/access-grant.d.ts +14 -0
  87. package/dist/utils/access-grant.js +88 -0
  88. package/dist/utils/access-match.d.ts +7 -0
  89. package/dist/utils/access-match.fixtures.json +513 -0
  90. package/dist/utils/access-match.js +32 -16
  91. package/dist/utils/access-verdict.fixtures.json +421 -0
  92. package/dist/utils/credential-policy.d.ts +1 -1
  93. package/dist/utils/dns-instructions.d.ts +29 -0
  94. package/dist/utils/dns-instructions.js +78 -0
  95. package/dist/utils/domain-diagnostics.d.ts +29 -0
  96. package/dist/utils/domain-diagnostics.js +63 -0
  97. package/dist/utils/egress-policy.fixtures.json +51 -0
  98. package/dist/utils/id-hints.d.ts +1 -0
  99. package/dist/utils/id-hints.js +55 -13
  100. package/dist/utils/page-colors.d.ts +50 -0
  101. package/dist/utils/page-colors.js +167 -0
  102. package/dist/utils/page-fonts.d.ts +2 -0
  103. package/dist/utils/page-fonts.js +4 -0
  104. package/dist/utils/pages.d.ts +8 -1
  105. package/dist/utils/pages.js +123 -37
  106. package/dist/utils/production-runtime.d.ts +18 -0
  107. package/dist/utils/production-runtime.js +28 -0
  108. package/dist/utils/query-keys.d.ts +6 -7
  109. package/dist/utils/query-keys.js +11 -12
  110. package/dist/utils/sf-config-v1.fixtures.json +9 -0
  111. package/dist/utils/sf-config-v1.js +28 -19
  112. package/dist/utils/source-archive-policy.d.ts +2 -0
  113. package/dist/utils/source-archive-policy.js +2 -0
  114. package/dist/utils/static-runtime-policy.fixtures.json +57 -0
  115. package/dist/utils/static-runtime-policy.js +1 -1
  116. package/package.json +5 -4
  117. package/dist/contracts/annotations.d.ts +0 -327
  118. package/dist/contracts/annotations.js +0 -235
  119. package/dist/contracts/generated-feature-launch-entries.d.ts +0 -2
  120. package/dist/contracts/generated-feature-launch-entries.js +0 -518
  121. package/dist/contracts/publishes.d.ts +0 -135
  122. package/dist/contracts/publishes.js +0 -162
@@ -0,0 +1,513 @@
1
+ {
2
+ "namespaceFilter": [
3
+ {
4
+ "name": "platform namespaces keep platform grants",
5
+ "grants": ["user:usr_1", "email:carol@acme.com", "team:team_1:member"],
6
+ "namespaces": [
7
+ "team:",
8
+ "sub-team:",
9
+ "user:",
10
+ "email:",
11
+ "space:",
12
+ "link:",
13
+ "invite:",
14
+ "svc:",
15
+ "sso:"
16
+ ],
17
+ "expected": ["user:usr_1", "email:carol@acme.com", "team:team_1:member"]
18
+ },
19
+ {
20
+ "name": "external key drops everything outside its ext binding",
21
+ "grants": ["ext:acn_1:wc-subscriber", "team:team_1:owner", "user:usr_1", "email:x@y.z"],
22
+ "namespaces": ["ext:acn_1:"],
23
+ "expected": ["ext:acn_1:wc-subscriber"]
24
+ },
25
+ {
26
+ "name": "an external token minting team grants is fully dropped",
27
+ "grants": ["team:team_1:owner", "team:team_1:admin"],
28
+ "namespaces": ["ext:acn_1:"],
29
+ "expected": []
30
+ },
31
+ {
32
+ "name": "local pw key signs only pw grants",
33
+ "grants": ["pw:space-password", "team:team_1:viewer", "email:a@b.c"],
34
+ "namespaces": ["pw:"],
35
+ "expected": ["pw:space-password"]
36
+ },
37
+ {
38
+ "name": "prefix match is a plain startsWith, not a segment match",
39
+ "grants": ["sub-team:sub_1:member", "team:team_1:member"],
40
+ "namespaces": ["team:"],
41
+ "expected": ["team:team_1:member"]
42
+ },
43
+ {
44
+ "name": "empty namespace entries never match",
45
+ "grants": ["user:usr_1"],
46
+ "namespaces": [""],
47
+ "expected": []
48
+ },
49
+ {
50
+ "name": "email-domain namespace is dead: nothing carries it, nothing keeps it",
51
+ "grants": ["email-domain:acme.com"],
52
+ "namespaces": [
53
+ "team:",
54
+ "sub-team:",
55
+ "user:",
56
+ "email:",
57
+ "space:",
58
+ "link:",
59
+ "invite:",
60
+ "svc:",
61
+ "sso:"
62
+ ],
63
+ "expected": []
64
+ },
65
+ {
66
+ "name": "empty grant strings are dropped",
67
+ "grants": ["", "user:usr_1"],
68
+ "namespaces": ["user:"],
69
+ "expected": ["user:usr_1"]
70
+ }
71
+ ],
72
+ "glob": [
73
+ {
74
+ "pattern": "email:carol@acme.com",
75
+ "grant": "email:carol@acme.com",
76
+ "expected": true
77
+ },
78
+ {
79
+ "pattern": "email:carol@acme.com",
80
+ "grant": "email:mallory@acme.com",
81
+ "expected": false
82
+ },
83
+ {
84
+ "pattern": "email:*@acme.com",
85
+ "grant": "email:carol@acme.com",
86
+ "expected": true
87
+ },
88
+ {
89
+ "pattern": "email:*@acme.com",
90
+ "grant": "email:carol@evil.com",
91
+ "expected": false
92
+ },
93
+ {
94
+ "pattern": "email:*@acme.com",
95
+ "grant": "email:carol@sub.acme.com",
96
+ "expected": false
97
+ },
98
+ {
99
+ "pattern": "email:*acme.com",
100
+ "grant": "email:carol@sub.acme.com",
101
+ "expected": true
102
+ },
103
+ {
104
+ "pattern": "email:*@acme.com",
105
+ "grant": "email:@acme.com",
106
+ "expected": true
107
+ },
108
+ {
109
+ "pattern": "team:*",
110
+ "grant": "team:team_1:member",
111
+ "expected": true
112
+ },
113
+ {
114
+ "pattern": "team:team_1:*",
115
+ "grant": "team:team_1:viewer",
116
+ "expected": true
117
+ },
118
+ {
119
+ "pattern": "team:team_1:*",
120
+ "grant": "team:team_2:viewer",
121
+ "expected": false
122
+ },
123
+ {
124
+ "pattern": "user:*",
125
+ "grant": "user:usr_1",
126
+ "expected": true
127
+ },
128
+ {
129
+ "pattern": "user:*",
130
+ "grant": "email:x@y.z",
131
+ "expected": false
132
+ },
133
+ {
134
+ "pattern": "space:spc_1:*",
135
+ "grant": "space:spc_1:commenter",
136
+ "expected": true
137
+ },
138
+ {
139
+ "pattern": "*",
140
+ "grant": "anything:at:all",
141
+ "expected": true
142
+ },
143
+ {
144
+ "pattern": "pw:space-password",
145
+ "grant": "pw:space-password",
146
+ "expected": true
147
+ },
148
+ {
149
+ "pattern": "pw:space-password",
150
+ "grant": "pw:other-rule",
151
+ "expected": false
152
+ },
153
+ {
154
+ "pattern": "email:*@a*.com",
155
+ "grant": "email:x@acme.com",
156
+ "expected": true
157
+ },
158
+ {
159
+ "pattern": "email:*@a*.com",
160
+ "grant": "email:x@bcme.com",
161
+ "expected": false
162
+ },
163
+ {
164
+ "pattern": "email:a.b@acme.com",
165
+ "grant": "email:aXb@acme.com",
166
+ "expected": false
167
+ },
168
+ {
169
+ "pattern": "svc:stk_1",
170
+ "grant": "svc:stk_1",
171
+ "expected": true
172
+ },
173
+ {
174
+ "pattern": "link:lnk_*",
175
+ "grant": "link:lnk_abc",
176
+ "expected": true
177
+ },
178
+ {
179
+ "pattern": "",
180
+ "grant": "user:usr_1",
181
+ "expected": false
182
+ }
183
+ ],
184
+ "intersect": [
185
+ {
186
+ "name": "single exact hit",
187
+ "grants": ["user:usr_1", "email:carol@acme.com"],
188
+ "requiredGrants": ["email:carol@acme.com"],
189
+ "expected": true
190
+ },
191
+ {
192
+ "name": "no overlap",
193
+ "grants": ["user:usr_1", "team:team_1:member"],
194
+ "requiredGrants": ["email:*@acme.com", "link:lnk_1"],
195
+ "expected": false
196
+ },
197
+ {
198
+ "name": "glob run crosses separators",
199
+ "grants": ["team:team_1:member"],
200
+ "requiredGrants": ["team:*:member"],
201
+ "expected": true
202
+ },
203
+ {
204
+ "name": "any-of over requiredGrants",
205
+ "grants": ["invite:sin_1"],
206
+ "requiredGrants": ["email:x@y.z", "invite:sin_1", "team:team_1:viewer"],
207
+ "expected": true
208
+ },
209
+ {
210
+ "name": "empty grants never pass",
211
+ "grants": [],
212
+ "requiredGrants": ["user:*"],
213
+ "expected": false
214
+ },
215
+ {
216
+ "name": "empty requiredGrants never pass",
217
+ "grants": ["user:usr_1"],
218
+ "requiredGrants": [],
219
+ "expected": false
220
+ },
221
+ {
222
+ "name": "empty-string entries are ignored on both sides",
223
+ "grants": [""],
224
+ "requiredGrants": [""],
225
+ "expected": false
226
+ },
227
+ {
228
+ "name": "concrete grant does not glob against a concrete requirement",
229
+ "grants": ["email:carol@acme.com"],
230
+ "requiredGrants": ["email:carol@acme.co"],
231
+ "expected": false
232
+ }
233
+ ],
234
+ "satisfy": [
235
+ {
236
+ "name": "platform token passes an email-domain wall",
237
+ "grants": ["user:usr_1", "email:carol@acme.com", "team:team_1:member"],
238
+ "grantNamespaces": [
239
+ "team:",
240
+ "sub-team:",
241
+ "user:",
242
+ "email:",
243
+ "space:",
244
+ "link:",
245
+ "invite:",
246
+ "svc:",
247
+ "sso:"
248
+ ],
249
+ "requiredGrants": ["email:*@acme.com"],
250
+ "expected": true
251
+ },
252
+ {
253
+ "name": "external token cannot satisfy a team wall (namespace drop first)",
254
+ "grants": ["team:team_1:member"],
255
+ "grantNamespaces": ["ext:acn_1:"],
256
+ "requiredGrants": ["team:team_1:member"],
257
+ "expected": false
258
+ },
259
+ {
260
+ "name": "local pw token satisfies exactly its wall",
261
+ "grants": ["pw:space-password"],
262
+ "grantNamespaces": ["pw:"],
263
+ "requiredGrants": ["pw:space-password"],
264
+ "expected": true
265
+ },
266
+ {
267
+ "name": "pw token cannot satisfy an identity wall",
268
+ "grants": ["pw:space-password"],
269
+ "grantNamespaces": ["pw:"],
270
+ "requiredGrants": ["email:*@acme.com", "team:team_1:viewer"],
271
+ "expected": false
272
+ },
273
+ {
274
+ "name": "external token satisfies its own owner-defined tag",
275
+ "grants": ["ext:acn_1:wc-subscriber"],
276
+ "grantNamespaces": ["ext:acn_1:"],
277
+ "requiredGrants": ["ext:acn_1:wc-subscriber"],
278
+ "expected": true
279
+ },
280
+ {
281
+ "name": "external token minting team:* wildcard grants fails the namespace filter",
282
+ "grants": [
283
+ "ext:acn_1:wc-subscriber",
284
+ "team:*",
285
+ "team:team_1:owner",
286
+ "user:usr_1",
287
+ "email:carol@acme.com"
288
+ ],
289
+ "grantNamespaces": ["ext:acn_1:"],
290
+ "requiredGrants": ["team:*"],
291
+ "expected": false
292
+ },
293
+ {
294
+ "name": "external token cannot cross into another connection's ext namespace",
295
+ "grants": ["ext:acn_2:wc-subscriber"],
296
+ "grantNamespaces": ["ext:acn_1:"],
297
+ "requiredGrants": ["ext:acn_2:wc-subscriber"],
298
+ "expected": false
299
+ },
300
+ {
301
+ "name": "sso token passes the connection audience and its verified email glob",
302
+ "grants": ["sso:acn_9", "email:carol@acme.com"],
303
+ "grantNamespaces": [
304
+ "team:",
305
+ "sub-team:",
306
+ "user:",
307
+ "email:",
308
+ "space:",
309
+ "link:",
310
+ "invite:",
311
+ "svc:",
312
+ "sso:"
313
+ ],
314
+ "requiredGrants": ["email:*@acme.com"],
315
+ "expected": true
316
+ },
317
+ {
318
+ "name": "same-space grant cannot satisfy an identity-bound reviewer rule",
319
+ "grants": ["space:spc_review:viewer"],
320
+ "grantNamespaces": ["email:", "space:"],
321
+ "requiredGrants": ["email:reviewer.a@example.com"],
322
+ "expected": false
323
+ },
324
+ {
325
+ "name": "the exact reviewer identity satisfies its identity-bound rule",
326
+ "grants": ["email:reviewer.a@example.com", "space:spc_review:viewer"],
327
+ "grantNamespaces": ["email:", "space:"],
328
+ "requiredGrants": ["email:reviewer.a@example.com"],
329
+ "expected": true
330
+ },
331
+ {
332
+ "name": "a different reviewer remains denied despite a same-space grant",
333
+ "grants": ["email:reviewer.b@example.com", "space:spc_review:viewer"],
334
+ "grantNamespaces": ["email:", "space:"],
335
+ "requiredGrants": ["email:reviewer.a@example.com"],
336
+ "expected": false
337
+ }
338
+ ],
339
+ "pathGlob": [
340
+ {
341
+ "pattern": "",
342
+ "path": "/anything",
343
+ "expected": true
344
+ },
345
+ {
346
+ "pattern": "/x.html",
347
+ "path": "/x.html",
348
+ "expected": true
349
+ },
350
+ {
351
+ "pattern": "/x.html",
352
+ "path": "/x.htm",
353
+ "expected": false
354
+ },
355
+ {
356
+ "pattern": "/docs/**",
357
+ "path": "/docs/",
358
+ "expected": true
359
+ },
360
+ {
361
+ "pattern": "/docs/**",
362
+ "path": "/docs/a/b/c.html",
363
+ "expected": true
364
+ },
365
+ {
366
+ "pattern": "/docs/**",
367
+ "path": "/docs",
368
+ "expected": false
369
+ },
370
+ {
371
+ "pattern": "/docs/*",
372
+ "path": "/docs/a.html",
373
+ "expected": true
374
+ },
375
+ {
376
+ "pattern": "/docs/*",
377
+ "path": "/docs/a/b.html",
378
+ "expected": false
379
+ },
380
+ {
381
+ "pattern": "/docs/*",
382
+ "path": "/docs/",
383
+ "expected": true
384
+ },
385
+ {
386
+ "pattern": "/report/**",
387
+ "path": "/report/q2/deck.pdf",
388
+ "expected": true
389
+ },
390
+ {
391
+ "pattern": "/report/**",
392
+ "path": "/reports/q2.pdf",
393
+ "expected": false
394
+ },
395
+ {
396
+ "pattern": "/*.html",
397
+ "path": "/index.html",
398
+ "expected": true
399
+ },
400
+ {
401
+ "pattern": "/*.html",
402
+ "path": "/a/index.html",
403
+ "expected": false
404
+ },
405
+ {
406
+ "pattern": "/**",
407
+ "path": "/",
408
+ "expected": true
409
+ },
410
+ {
411
+ "pattern": "/**",
412
+ "path": "/deep/path/file.txt",
413
+ "expected": true
414
+ },
415
+ {
416
+ "pattern": "/a+b/**",
417
+ "path": "/a+b/x",
418
+ "expected": true
419
+ },
420
+ {
421
+ "pattern": "/a.b/**",
422
+ "path": "/axb/x",
423
+ "expected": false
424
+ }
425
+ ],
426
+ "cidr": [
427
+ {
428
+ "ip": "1.2.3.4",
429
+ "cidr": "1.2.3.0/24",
430
+ "expected": true
431
+ },
432
+ {
433
+ "ip": "1.2.4.4",
434
+ "cidr": "1.2.3.0/24",
435
+ "expected": false
436
+ },
437
+ {
438
+ "ip": "1.2.3.4",
439
+ "cidr": "1.2.3.4",
440
+ "expected": true
441
+ },
442
+ {
443
+ "ip": "1.2.3.4",
444
+ "cidr": "1.2.3.5",
445
+ "expected": false
446
+ },
447
+ {
448
+ "ip": "10.0.0.1",
449
+ "cidr": "10.0.0.0/8",
450
+ "expected": true
451
+ },
452
+ {
453
+ "ip": "10.128.0.1",
454
+ "cidr": "10.0.0.0/9",
455
+ "expected": false
456
+ },
457
+ {
458
+ "ip": "10.127.255.255",
459
+ "cidr": "10.0.0.0/9",
460
+ "expected": true
461
+ },
462
+ {
463
+ "ip": "1.2.3.4",
464
+ "cidr": "0.0.0.0/0",
465
+ "expected": true
466
+ },
467
+ {
468
+ "ip": "1.2.3.4",
469
+ "cidr": "1.2.3.0/33",
470
+ "expected": false
471
+ },
472
+ {
473
+ "ip": "1.2.3.4",
474
+ "cidr": "",
475
+ "expected": false
476
+ },
477
+ {
478
+ "ip": "not-an-ip",
479
+ "cidr": "1.2.3.0/24",
480
+ "expected": false
481
+ },
482
+ {
483
+ "ip": "2001:db8::1",
484
+ "cidr": "2001:db8::/32",
485
+ "expected": true
486
+ },
487
+ {
488
+ "ip": "2001:db9::1",
489
+ "cidr": "2001:db8::/32",
490
+ "expected": false
491
+ },
492
+ {
493
+ "ip": "2001:db8::1",
494
+ "cidr": "1.2.3.0/24",
495
+ "expected": false
496
+ },
497
+ {
498
+ "ip": "1.2.3.4",
499
+ "cidr": "2001:db8::/32",
500
+ "expected": false
501
+ },
502
+ {
503
+ "ip": "::ffff:1.2.3.4",
504
+ "cidr": "::ffff:1.2.3.0/120",
505
+ "expected": true
506
+ },
507
+ {
508
+ "ip": "fe80::aaaa:bbbb",
509
+ "cidr": "fe80::/10",
510
+ "expected": true
511
+ }
512
+ ]
513
+ }
@@ -60,6 +60,18 @@ export function grantsIntersect(grants, requiredGrants) {
60
60
  }
61
61
  return false;
62
62
  }
63
+ // Managed reviewer rules are identity bindings. Policies compiled before the
64
+ // identity-only cutover may still contain a generic `space:` grant beside the
65
+ // reviewer's email. Treating that legacy entry as another ANY-OF alternative
66
+ // recreates the cross-reviewer bypass, so every evaluator derives the effective
67
+ // requirement from the trusted managed-block owner before intersecting.
68
+ export function requiredGrantsForRule(rule) {
69
+ const requiredGrants = rule.auth?.requiredGrants ?? [];
70
+ if (rule.managedBy !== "cast_reviewer") {
71
+ return requiredGrants;
72
+ }
73
+ return requiredGrants.filter((grant) => grant.startsWith("email:"));
74
+ }
63
75
  // The composed satisfaction test the authorize pre-flight runs: namespace-drop
64
76
  // the visitor's grants by the signing key, then intersect with the rule's
65
77
  // requiredGrants — exactly what the runtime does after verifying a signature.
@@ -146,6 +158,19 @@ export function agentMatches(needle, agent) {
146
158
  }
147
159
  return agent.toLowerCase().includes(needle.toLowerCase());
148
160
  }
161
+ function parseIpv6Groups(raw) {
162
+ if (raw === "") {
163
+ return [];
164
+ }
165
+ const groups = [];
166
+ for (const part of raw.split(":")) {
167
+ if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {
168
+ return null;
169
+ }
170
+ groups.push(Number.parseInt(part, 16));
171
+ }
172
+ return groups;
173
+ }
149
174
  // inet_pton equivalent: dotted-quad IPv4 or RFC 4291 IPv6 (including "::"
150
175
  // compression and an embedded IPv4 tail) → bytes, or null when unparseable.
151
176
  function ipToBytes(ip) {
@@ -175,30 +200,17 @@ function ipToBytes(ip) {
175
200
  if (doubleColonSplits.length > 2) {
176
201
  return null;
177
202
  }
178
- const parseGroups = (raw) => {
179
- if (raw === "") {
180
- return [];
181
- }
182
- const groups = [];
183
- for (const part of raw.split(":")) {
184
- if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {
185
- return null;
186
- }
187
- groups.push(Number.parseInt(part, 16));
188
- }
189
- return groups;
190
- };
191
203
  let groups;
192
204
  if (doubleColonSplits.length === 2) {
193
- const left = parseGroups(doubleColonSplits[0] ?? "");
194
- const right = parseGroups(doubleColonSplits[1] ?? "");
205
+ const left = parseIpv6Groups(doubleColonSplits[0] ?? "");
206
+ const right = parseIpv6Groups(doubleColonSplits[1] ?? "");
195
207
  if (left === null || right === null || left.length + right.length > 8) {
196
208
  return null;
197
209
  }
198
210
  groups = [...left, ...Array(8 - left.length - right.length).fill(0), ...right];
199
211
  }
200
212
  else {
201
- groups = parseGroups(head);
213
+ groups = parseIpv6Groups(head);
202
214
  if (groups === null || groups.length !== 8) {
203
215
  return null;
204
216
  }
@@ -285,6 +297,10 @@ export function ruleMatchApplies(match, context) {
285
297
  if (match.country !== undefined && match.country.toUpperCase() !== (context.country ?? "")) {
286
298
  return false;
287
299
  }
300
+ if (match.countryNotIn !== undefined &&
301
+ match.countryNotIn.some((country) => country.toUpperCase() === (context.country ?? "").toUpperCase())) {
302
+ return false;
303
+ }
288
304
  if (match.header !== undefined) {
289
305
  const name = match.header.name.toLowerCase();
290
306
  const actual = Object.entries(context.headers ?? {}).find(([key]) => key.toLowerCase() === name)?.[1];