permdock 0.0.0 → 0.1.0

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 (290) hide show
  1. package/README.md +268 -1
  2. package/dist/a2a/index.d.ts +178 -0
  3. package/dist/a2a/index.js +271 -0
  4. package/dist/ai-sdk/index.d.ts +74 -0
  5. package/dist/ai-sdk/index.js +137 -0
  6. package/dist/approval-required-TzNB9oQA.js +6 -0
  7. package/dist/approvals/index.d.ts +3 -0
  8. package/dist/approvals/index.js +4 -0
  9. package/dist/approvals-DaxY-Bdg.js +218 -0
  10. package/dist/approvers-aH0PJkmf.js +20 -0
  11. package/dist/arazzo-CTeVuXr5.js +70 -0
  12. package/dist/arazzo-aoTkqYsJ.js +54 -0
  13. package/dist/authzen/index.d.ts +48 -0
  14. package/dist/authzen/index.js +336 -0
  15. package/dist/better-auth/index.d.ts +92 -0
  16. package/dist/better-auth/index.js +324 -0
  17. package/dist/budget-JqcRqyhK.d.ts +11 -0
  18. package/dist/budget-NbEtbJC_.js +11 -0
  19. package/dist/capability-B68OFnZx.js +171 -0
  20. package/dist/capability-BDG_nkxt.d.ts +99 -0
  21. package/dist/catalog/index.d.ts +12 -0
  22. package/dist/catalog/index.js +2 -0
  23. package/dist/catalog-B0E6bggc.js +48 -0
  24. package/dist/catalog-H-vFGwWK.js +55 -0
  25. package/dist/catalog-doc-BSo_Q_-6.js +298 -0
  26. package/dist/catalog-idsHc0vo.js +2 -0
  27. package/dist/catalog-path-CnI0XTFK.js +12 -0
  28. package/dist/claude-agent/index.d.ts +81 -0
  29. package/dist/claude-agent/index.js +83 -0
  30. package/dist/clerk/index.d.ts +56 -0
  31. package/dist/clerk/index.js +280 -0
  32. package/dist/cli/bin.d.ts +1 -0
  33. package/dist/cli/bin.js +20 -0
  34. package/dist/cli/index.d.ts +46 -0
  35. package/dist/cli/index.js +5 -0
  36. package/dist/cloud/index.d.ts +91 -0
  37. package/dist/cloud/index.js +390 -0
  38. package/dist/cloud-BNQTrmrr.js +50 -0
  39. package/dist/cloud-Cuxr6Czw.js +117 -0
  40. package/dist/collect-B7_TIfmg.js +49 -0
  41. package/dist/collect-O-tOtbnk.js +350 -0
  42. package/dist/collect-fRPAQIWb.js +2 -0
  43. package/dist/commands-BMh_Etm2.js +93 -0
  44. package/dist/compact-y6vfu2_f.js +19 -0
  45. package/dist/compare-pfyVnBjE.js +12 -0
  46. package/dist/compile-CQbvyIBr.d.ts +30 -0
  47. package/dist/compile-D22iwvuV.js +558 -0
  48. package/dist/config-BPYmjTOp.js +26 -0
  49. package/dist/connection-JvqwHODC.js +194 -0
  50. package/dist/context-CKEAabd7.js +73 -0
  51. package/dist/context-gtAwAILx.js +6 -0
  52. package/dist/convex/index.d.ts +40 -0
  53. package/dist/convex/index.js +55 -0
  54. package/dist/create-BKF3dgFk.d.ts +162 -0
  55. package/dist/create-CSkhDU9T.js +222 -0
  56. package/dist/credential-B_rkUNlY.js +326 -0
  57. package/dist/credentials-BgRX3WEg.js +268 -0
  58. package/dist/describe-Bt1YD5uS.js +99 -0
  59. package/dist/describe-DernyItf.d.ts +37 -0
  60. package/dist/diff-BUzf5Poi.js +48 -0
  61. package/dist/diff-x5Il8LVa.js +418 -0
  62. package/dist/digest-B3kc_PgQ.d.ts +19 -0
  63. package/dist/digest-BI-6aa6d.js +27 -0
  64. package/dist/doctor-BGle8Gqi.js +1314 -0
  65. package/dist/doctor-Bqo6FwUa.js +53 -0
  66. package/dist/drizzle/index.d.ts +72 -0
  67. package/dist/drizzle/index.js +160 -0
  68. package/dist/duration-xK2jGFVc.js +25 -0
  69. package/dist/elysia/index.d.ts +71 -0
  70. package/dist/elysia/index.js +103 -0
  71. package/dist/errors-DSMnAVNa.js +43 -0
  72. package/dist/errors-DbZ1YOnb.d.ts +138 -0
  73. package/dist/errors-Iy71s7Oj.js +177 -0
  74. package/dist/errors-m3u9TmDu.js +27 -0
  75. package/dist/evaluations-DsAU9ppl.js +479 -0
  76. package/dist/eve/index.d.ts +95 -0
  77. package/dist/eve/index.js +181 -0
  78. package/dist/express/index.d.ts +46 -0
  79. package/dist/express/index.js +108 -0
  80. package/dist/fastify/index.d.ts +56 -0
  81. package/dist/fastify/index.js +141 -0
  82. package/dist/files-B_wAGYmt.js +121 -0
  83. package/dist/fixtures-Cvaw0gDG.js +64 -0
  84. package/dist/freeze-y1_jQL4v.js +31 -0
  85. package/dist/from-snapshot-BMGnhmgv.js +552 -0
  86. package/dist/generate-CH7ThJ14.js +173 -0
  87. package/dist/grantee-Bv04_Vzf.js +521 -0
  88. package/dist/headers-B5RRv3Xm.js +6 -0
  89. package/dist/headers-C8SHQccD.d.ts +6 -0
  90. package/dist/helpers-DnSOrhB9.js +682 -0
  91. package/dist/hono/index.d.ts +70 -0
  92. package/dist/hono/index.js +131 -0
  93. package/dist/hosted-BwKQ1BTA.js +265 -0
  94. package/dist/http-DPuDaChb.d.ts +13 -0
  95. package/dist/http-GgVApX1k.js +111 -0
  96. package/dist/index-CltcQ4Bf.d.ts +138 -0
  97. package/dist/index-LF_hS-yn.d.ts +177 -0
  98. package/dist/index-fG6NSXlc.d.ts +9 -0
  99. package/dist/index-xIxJLwOQ.d.ts +415 -0
  100. package/dist/index.d.ts +11 -0
  101. package/dist/index.js +21 -0
  102. package/dist/jwt/index.d.ts +87 -0
  103. package/dist/jwt/index.js +571 -0
  104. package/dist/kernel-YOGCWNLf.js +211 -0
  105. package/dist/kysely/index.d.ts +77 -0
  106. package/dist/kysely/index.js +129 -0
  107. package/dist/load-BZ24CpZP.js +69 -0
  108. package/dist/load-QOOm1z70.js +2 -0
  109. package/dist/load-jose-BL6LH4bL.js +11 -0
  110. package/dist/lru-8q4WZjBV.js +28 -0
  111. package/dist/manifest-DDQbC6K0.d.ts +118 -0
  112. package/dist/map-DowSO96j.js +158 -0
  113. package/dist/map-claims-B_6I1Rn-.js +360 -0
  114. package/dist/map-error-RJ3ZsUyA.js +16 -0
  115. package/dist/markers-B0hYLvMD.js +60 -0
  116. package/dist/may-use-CnFEjVEp.js +37 -0
  117. package/dist/mcp/index.d.ts +127 -0
  118. package/dist/mcp/index.js +563 -0
  119. package/dist/nest/index.d.ts +80 -0
  120. package/dist/nest/index.js +270 -0
  121. package/dist/next/client.d.ts +24 -0
  122. package/dist/next/client.js +31 -0
  123. package/dist/next/index.d.ts +110 -0
  124. package/dist/next/index.js +195 -0
  125. package/dist/next/plugin.d.ts +14 -0
  126. package/dist/next/plugin.js +2 -0
  127. package/dist/node/index.d.ts +43 -0
  128. package/dist/node/index.js +66 -0
  129. package/dist/openai/index.d.ts +71 -0
  130. package/dist/openai/index.js +105 -0
  131. package/dist/openapi/index.d.ts +93 -0
  132. package/dist/openapi/index.js +3 -0
  133. package/dist/openapi-CwePaF7C.js +164 -0
  134. package/dist/openapi-DWD0Uc_4.js +250 -0
  135. package/dist/openapi-DcqM8nU7.js +306 -0
  136. package/dist/openapi-import-BWRUNNeW.js +5959 -0
  137. package/dist/openapi-schema-D8j_T9tO.js +6902 -0
  138. package/dist/orpc/index.d.ts +64 -0
  139. package/dist/orpc/index.js +188 -0
  140. package/dist/otel/index.d.ts +13 -0
  141. package/dist/otel/index.js +202 -0
  142. package/dist/package-root-zGkxO3oN.js +30 -0
  143. package/dist/parse-DPfIvFMU.js +135 -0
  144. package/dist/pdp/index.d.ts +22 -0
  145. package/dist/pdp/index.js +795 -0
  146. package/dist/peer-BOr_kJE1.js +31 -0
  147. package/dist/permdock-yoZ7EjxX.js +2956 -0
  148. package/dist/permissions-B4apnahQ.d.ts +185 -0
  149. package/dist/permissions-B8vCg_bI.d.ts +52 -0
  150. package/dist/permissions-CofRqesP.js +142 -0
  151. package/dist/permissions-DC7GROPA.js +600 -0
  152. package/dist/pg-4GJWDUxG.js +953 -0
  153. package/dist/pins-CqE1jZuF.js +12 -0
  154. package/dist/plugin-4gr7IxFJ.js +65 -0
  155. package/dist/policy-ANp36PnI.js +971 -0
  156. package/dist/policy-CInmNGkM.d.ts +1959 -0
  157. package/dist/policy-Cq2fB8Q5.d.ts +20 -0
  158. package/dist/prisma/index.d.ts +135 -0
  159. package/dist/prisma/index.js +430 -0
  160. package/dist/problem-Cgp9-KZA.js +226 -0
  161. package/dist/prompt-CXYhLfsS.js +32 -0
  162. package/dist/protected-B4K3BKoX.js +138 -0
  163. package/dist/protected-BuPAycJT.d.ts +26 -0
  164. package/dist/protected-wY7nXhY9.d.ts +23 -0
  165. package/dist/provider-_wx46HpB.js +48 -0
  166. package/dist/provider-wTAUda2v.d.ts +6 -0
  167. package/dist/react/index.d.ts +6 -0
  168. package/dist/react/index.js +6 -0
  169. package/dist/react/provider-client.d.ts +2 -0
  170. package/dist/react/provider-client.js +3 -0
  171. package/dist/react-native/index.d.ts +42 -0
  172. package/dist/react-native/index.js +174 -0
  173. package/dist/refs-ChLSxbST.js +90 -0
  174. package/dist/relations-r4soIFZB.js +426 -0
  175. package/dist/revocations-Cy06JNx2.d.ts +21 -0
  176. package/dist/rls-BOFU3chG.js +233 -0
  177. package/dist/rls-CJVja9Ww.js +2119 -0
  178. package/dist/rls-graph-DxQ39PJj.js +371 -0
  179. package/dist/rls-helpers-BxRuif0l.js +1346 -0
  180. package/dist/rls-import-CXV6LBlS.js +942 -0
  181. package/dist/rls-migrate-DrFPbf8p.js +366 -0
  182. package/dist/row-check-BajTgQZ-.d.ts +43 -0
  183. package/dist/run-CJ6UvS3a.js +155 -0
  184. package/dist/runtime-DP9ttFh1.js +27 -0
  185. package/dist/saas-BRrZzJkM.js +1193 -0
  186. package/dist/schema-P8yoma__.js +296 -0
  187. package/dist/scim/index.d.ts +2 -0
  188. package/dist/scim/index.js +2 -0
  189. package/dist/scim-9NFa0XVZ.js +1445 -0
  190. package/dist/scopes-cKVp_WTw.js +212 -0
  191. package/dist/server/index.d.ts +123 -0
  192. package/dist/server/index.js +8 -0
  193. package/dist/sha256-BO0swRXL.js +150 -0
  194. package/dist/sink-Bd9PSyfq.js +141 -0
  195. package/dist/sink-Cfr9-7YC.d.ts +104 -0
  196. package/dist/skills-C_lpV4Jx.js +50 -0
  197. package/dist/skills-Dx3QLnMY.js +110 -0
  198. package/dist/snapshot-DE8dE_hz.js +1051 -0
  199. package/dist/solid/index.d.ts +63 -0
  200. package/dist/solid/index.js +197 -0
  201. package/dist/source-DoBBYJi2.js +6 -0
  202. package/dist/sources-DOpW5Ila.d.ts +347 -0
  203. package/dist/sources-DVHM3Mwq.js +284 -0
  204. package/dist/sql-Bll1d7y0.js +22 -0
  205. package/dist/src-CTNT-PBW.js +430 -0
  206. package/dist/ssf/index.d.ts +3 -0
  207. package/dist/ssf/index.js +622 -0
  208. package/dist/store-Bq90395i.d.ts +27 -0
  209. package/dist/store-C6hFW6ta.js +508 -0
  210. package/dist/stores-CkG2HrSg.js +140 -0
  211. package/dist/stores-CvOnu_2T.d.ts +41 -0
  212. package/dist/stream-BhMfZOwr.d.ts +12 -0
  213. package/dist/stream-Q_xlB6Ko.js +52 -0
  214. package/dist/style-CTm1AXDT.js +22 -0
  215. package/dist/subject-C6dLxqHU.js +24 -0
  216. package/dist/subject-settings-xt7V845g.js +82 -0
  217. package/dist/supabase/index.d.ts +67 -0
  218. package/dist/supabase/index.js +4 -0
  219. package/dist/supabase/middleware.d.ts +77 -0
  220. package/dist/supabase/middleware.js +68 -0
  221. package/dist/supabase-D8UlBA7Z.js +83 -0
  222. package/dist/supabase-DTxGtYdi.js +796 -0
  223. package/dist/supabase-hook-DW-xP4hU.js +2 -0
  224. package/dist/supabase-hook-D_OqGb_V.js +1528 -0
  225. package/dist/svelte/Protected.svelte +34 -0
  226. package/dist/svelte/index.d.ts +10 -0
  227. package/dist/svelte/index.js +53 -0
  228. package/dist/svelte/runtime.d.ts +6 -0
  229. package/dist/svelte/runtime.js +3 -0
  230. package/dist/svelte/source.d.ts +10 -0
  231. package/dist/svelte/source.js +8 -0
  232. package/dist/terminal/index.d.ts +171 -0
  233. package/dist/terminal/index.js +821 -0
  234. package/dist/testing/index.d.ts +656 -0
  235. package/dist/testing/index.js +3208 -0
  236. package/dist/testing/saas/index.d.ts +194 -0
  237. package/dist/testing/saas/index.js +3 -0
  238. package/dist/testing/saas/permissions.d.ts +2 -0
  239. package/dist/testing/saas/permissions.js +2 -0
  240. package/dist/text-diff-BdkkYSbi.js +15 -0
  241. package/dist/thenable-okc2xDsj.js +10 -0
  242. package/dist/timeout-D51niuQP.js +8 -0
  243. package/dist/trpc/index.d.ts +80 -0
  244. package/dist/trpc/index.js +205 -0
  245. package/dist/types-B8w5PFtS.d.ts +141 -0
  246. package/dist/types-B9dmiiab.d.ts +66 -0
  247. package/dist/types-BBK-exvJ.d.ts +177 -0
  248. package/dist/types-BvBk1wgn.d.ts +118 -0
  249. package/dist/types-CxquUByI.d.ts +9 -0
  250. package/dist/types-D6Z6-rKJ.d.ts +312 -0
  251. package/dist/types-DbWdEfcm.d.ts +87 -0
  252. package/dist/types-bva3i4Gf.d.ts +98 -0
  253. package/dist/types-i4BAOvdb.d.ts +97 -0
  254. package/dist/unplugin/index.d.ts +7 -0
  255. package/dist/unplugin/index.js +30 -0
  256. package/dist/usage-1ozMubLY.js +42 -0
  257. package/dist/usage-CUnK6Ou9.js +530 -0
  258. package/dist/usage-PyEor6GA.js +2 -0
  259. package/dist/validity-D0uRmJKJ.js +224 -0
  260. package/dist/verifier-B90agnhl.js +461 -0
  261. package/dist/version-CnSh33Fj.js +16 -0
  262. package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
  263. package/dist/vue/index.d.ts +57 -0
  264. package/dist/vue/index.js +203 -0
  265. package/dist/webmcp/index.d.ts +75 -0
  266. package/dist/webmcp/index.js +313 -0
  267. package/package.json +423 -4
  268. package/rulesets/permdock.yaml +29 -0
  269. package/schemas/catalog-v1.json +507 -0
  270. package/schemas/openapi/oas-3.1.json +1298 -0
  271. package/schemas/openapi/oas-3.2.json +1516 -0
  272. package/schemas/openapi/overlay-1.1.json +79 -0
  273. package/schemas/supabase-claims-v1.json +139 -0
  274. package/schemas/supabase-manifest-v1.json +217 -0
  275. package/skills/permdock/SKILL.md +66 -0
  276. package/skills/permdock-agents/SKILL.md +77 -0
  277. package/skills/permdock-agents/references/runtimes.md +211 -0
  278. package/skills/permdock-approvals/SKILL.md +80 -0
  279. package/skills/permdock-approvals/references/stores-and-resume.md +83 -0
  280. package/skills/permdock-audit/SKILL.md +67 -0
  281. package/skills/permdock-credentials/SKILL.md +55 -0
  282. package/skills/permdock-credentials/references/tokens-keys-links.md +127 -0
  283. package/skills/permdock-data/SKILL.md +57 -0
  284. package/skills/permdock-data/references/queries-and-rls.md +54 -0
  285. package/skills/permdock-data/references/relationships.md +65 -0
  286. package/skills/permdock-tenancy/SKILL.md +77 -0
  287. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
  288. package/skills/permdock-wire/SKILL.md +116 -0
  289. package/skills/permdock-wire/references/adapters.md +480 -0
  290. package/skills/permdock-wire/references/policy-options.md +43 -0
@@ -0,0 +1,1051 @@
1
+ import { n as isReadonlyArray, r as sole, t as compact } from "./compact-y6vfu2_f.js";
2
+ import { n as freezeDeep } from "./freeze-y1_jQL4v.js";
3
+ import { C as ownGet, S as isForbiddenKey, i as findPermission, o as formerScopes } from "./permissions-DC7GROPA.js";
4
+ import { c as isConditionRef, i as resolveConditionRef, s as isConditionDate, u as parentHop } from "./refs-ChLSxbST.js";
5
+ import { a as resolveScope, c as scopeChain, d as subjectMemberships, f as tenantOf, l as scopeIdOf, r as findScope, t as activeFor, u as scopeList } from "./scopes-cKVp_WTw.js";
6
+ import { C as listPlans, h as resourceRoleCondition, l as matchGrantee, s as flattenGrantee, w as listRoles } from "./grantee-Bv04_Vzf.js";
7
+ import { f as tenantsOf, i as isMembershipExpired, o as membershipField, r as inTeam, t as isActive } from "./validity-D0uRmJKJ.js";
8
+ import { n as sha256, t as bytesToBase64Url } from "./sha256-BO0swRXL.js";
9
+ //#region src/conditions/evaluate.ts
10
+ function isExpired(membership, now) {
11
+ return membership.expiresAt !== void 0 && membership.expiresAt <= now;
12
+ }
13
+ function resolveRef(ref, subject) {
14
+ return resolveConditionRef(ref, subject);
15
+ }
16
+ function unwrap(value, subject) {
17
+ if (isConditionRef(value)) return resolveRef(value.ref, subject);
18
+ if (isConditionDate(value)) return new Date(isoInstant(value.date) ?? NaN);
19
+ if (isReadonlyArray(value)) return value.map((item) => unwrap(item, subject));
20
+ return value;
21
+ }
22
+ const ISO_INSTANT = /^(\d{4}-\d{2}-\d{2})(?:[T ](\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?)(Z|[+-]\d{2}(?::?\d{2})?)?)?$/u;
23
+ function isoInstant(value) {
24
+ const match = ISO_INSTANT.exec(value);
25
+ if (match === null) return;
26
+ const [, day, time, zone] = match;
27
+ let offset = zone ?? (time === void 0 ? "" : "Z");
28
+ if (/^[+-]\d{2}$/u.test(offset)) offset = `${offset}:00`;
29
+ else if (/^[+-]\d{4}$/u.test(offset)) offset = `${offset.slice(0, 3)}:${offset.slice(3)}`;
30
+ const parsed = Date.parse(time === void 0 ? `${day}` : `${day}T${time}${offset}`);
31
+ return Number.isNaN(parsed) ? void 0 : parsed;
32
+ }
33
+ function instantOf(value) {
34
+ if (value instanceof Date) {
35
+ const time = value.getTime();
36
+ return Number.isNaN(time) ? void 0 : time;
37
+ }
38
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
39
+ if (typeof value === "string") return isoInstant(value);
40
+ }
41
+ function comparablePair(left, right) {
42
+ if (left instanceof Date || right instanceof Date) {
43
+ const a = instantOf(left);
44
+ const b = instantOf(right);
45
+ return a === void 0 || b === void 0 ? void 0 : [a, b];
46
+ }
47
+ const kind = typeof left;
48
+ if (kind === typeof right && (kind === "string" || kind === "number" || kind === "boolean")) return [left, right];
49
+ }
50
+ function compare(op, left, right) {
51
+ if (left === void 0 || left === null || right === void 0 || right === null) return false;
52
+ const comparable = comparablePair(left, right);
53
+ if (comparable === void 0) return false;
54
+ const [a, b] = comparable;
55
+ switch (op) {
56
+ case "eq": return a === b;
57
+ case "ne": return a !== b;
58
+ case "gt": return a > b;
59
+ case "gte": return a >= b;
60
+ case "lt": return a < b;
61
+ case "lte": return a <= b;
62
+ default:
63
+ /* v8 ignore next */
64
+ return false;
65
+ }
66
+ }
67
+ function contains(left, right) {
68
+ if (left === void 0 || left === null || right === void 0 || right === null) return false;
69
+ if (typeof left === "string" && typeof right === "string") return left.includes(right);
70
+ if (Array.isArray(left)) return left.includes(right);
71
+ return false;
72
+ }
73
+ function inList(left, right) {
74
+ if (left === void 0 || left === null || !Array.isArray(right)) return false;
75
+ return right.includes(left);
76
+ }
77
+ function matchesParentHop(data, parents, on) {
78
+ for (const parent of parents ?? []) {
79
+ const hop = parentHop(parent);
80
+ if (hop.resource !== void 0 && hop.resource !== on.resource) continue;
81
+ if (ownGet(data, hop.field) === on.id) return true;
82
+ }
83
+ return false;
84
+ }
85
+ function evaluateMemberOf(condition, data, subject, now, scopes) {
86
+ if (data === null || typeof data !== "object") return false;
87
+ const rowValue = ownGet(data, condition.field);
88
+ if (rowValue === void 0 || rowValue === null) return false;
89
+ const memberships = subjectMemberships(subject.principal?.memberships, scopes);
90
+ const wanted = new Set(condition.roles);
91
+ const active = subject.principal?.tenant === "" ? void 0 : subject.principal?.tenant;
92
+ const scope = condition.scope === "resource" ? void 0 : resolveScope(scopes, condition.scope);
93
+ if (condition.scope !== "resource" && scope === void 0) return false;
94
+ for (const membership of memberships) {
95
+ if (isExpired(membership, now)) continue;
96
+ if (wanted.size > 0 && !membership.roles.some((role) => wanted.has(role))) continue;
97
+ if (scope !== void 0) {
98
+ if (membership.scope !== scope || membership.id !== rowValue) continue;
99
+ const tenant = tenantOf(membership, scopes);
100
+ if (active !== void 0 && tenant !== void 0 && tenant !== active) continue;
101
+ return true;
102
+ }
103
+ if (membership.on === void 0) continue;
104
+ if (condition.resource !== void 0 && membership.on.resource !== condition.resource) {
105
+ if (matchesParentHop(data, condition.parents, membership.on)) return true;
106
+ continue;
107
+ }
108
+ if (membership.on.id === rowValue) return true;
109
+ if (matchesParentHop(data, condition.parents, membership.on)) return true;
110
+ }
111
+ return false;
112
+ }
113
+ function evaluateCondition(condition, data, subject, now = Date.now() / 1e3, scopes = scopeList(void 0), related, onOpaque) {
114
+ switch (condition.op) {
115
+ case "and": return condition.conditions.every((child) => evaluateCondition(child, data, subject, now, scopes, related, onOpaque));
116
+ case "or": return condition.conditions.some((child) => evaluateCondition(child, data, subject, now, scopes, related, onOpaque));
117
+ case "not": return !evaluateCondition(condition.condition, data, subject, now, scopes, related, onOpaque);
118
+ case "isNull": {
119
+ if (data === null || typeof data !== "object") return false;
120
+ const value = ownGet(data, condition.field);
121
+ const isNull = value === null || value === void 0;
122
+ return condition.value ? isNull : !isNull;
123
+ }
124
+ case "in":
125
+ case "notIn": {
126
+ if (data === null || typeof data !== "object") return false;
127
+ const left = ownGet(data, condition.field);
128
+ const matched = inList(left, unwrap(condition.value, subject));
129
+ return condition.op === "in" ? matched : !matched && left !== void 0 && left !== null;
130
+ }
131
+ case "contains":
132
+ if (data === null || typeof data !== "object") return false;
133
+ return contains(ownGet(data, condition.field), unwrap(condition.value, subject));
134
+ case "eq":
135
+ case "ne":
136
+ case "gt":
137
+ case "gte":
138
+ case "lt":
139
+ case "lte":
140
+ if (condition.field === "_" && condition.op === "eq") return condition.value === true;
141
+ if (data === null || typeof data !== "object") return false;
142
+ return compare(condition.op, ownGet(data, condition.field), unwrap(condition.value, subject));
143
+ case "memberOf": return evaluateMemberOf(condition, data, subject, now, scopes);
144
+ case "related": return related?.(condition, data) === true;
145
+ case "sqlFunction": return evaluateCondition(condition.twin, data, subject, now, scopes, related, onOpaque);
146
+ case "opaque":
147
+ onOpaque?.();
148
+ return false;
149
+ default:
150
+ /* v8 ignore next */
151
+ return condition;
152
+ }
153
+ }
154
+ //#endregion
155
+ //#region src/core/arazzo.ts
156
+ const VERSION = /^1\.[01]\.\d+(?:-.+)?$/u;
157
+ const STEP_TARGETS = [
158
+ "operationId",
159
+ "operationPath",
160
+ "workflowId",
161
+ "channelPath"
162
+ ];
163
+ const denied = (reason) => freezeDeep({
164
+ outcome: "denied",
165
+ denials: [{
166
+ role: null,
167
+ reason
168
+ }],
169
+ alternatives: []
170
+ });
171
+ function isRecord$1(value) {
172
+ return value !== null && typeof value === "object" && !Array.isArray(value);
173
+ }
174
+ function asString(value) {
175
+ return typeof value === "string" && value.length > 0 ? value : void 0;
176
+ }
177
+ function parseParameters(raw, components, where) {
178
+ if (raw === void 0) return [];
179
+ if (!Array.isArray(raw)) return `${where}: parameters must be an array`;
180
+ const out = [];
181
+ for (const item of raw) {
182
+ if (!isRecord$1(item)) return `${where}: a parameter is not an object`;
183
+ const reference = asString(item["reference"]);
184
+ if (reference !== void 0) {
185
+ const match = /^\$components\.parameters\.(.+)$/u.exec(reference);
186
+ const target = match?.[1] === void 0 ? void 0 : components[match[1]];
187
+ const name = isRecord$1(target) ? asString(target["name"]) : void 0;
188
+ if (!isRecord$1(target) || name === void 0 || !("value" in target)) return `${where}: unresolved parameter reference ${reference}`;
189
+ out.push({
190
+ name,
191
+ value: "value" in item ? item["value"] : target["value"]
192
+ });
193
+ continue;
194
+ }
195
+ const name = asString(item["name"]);
196
+ if (name === void 0 || !("value" in item)) return `${where}: a parameter needs a name and a value`;
197
+ out.push({
198
+ name,
199
+ value: item["value"]
200
+ });
201
+ }
202
+ return out;
203
+ }
204
+ function parseDocument(arazzo) {
205
+ if (!isRecord$1(arazzo)) return "Arazzo document is not an object";
206
+ const version = arazzo["arazzo"];
207
+ if (typeof version !== "string" || !VERSION.test(version)) return "arazzo must be a 1.0.x or 1.1.x version";
208
+ if (!isRecord$1(arazzo["info"])) return "info is required";
209
+ const rawSources = arazzo["sourceDescriptions"];
210
+ if (!Array.isArray(rawSources) || rawSources.length === 0) return "sourceDescriptions needs at least one entry";
211
+ const sources = [];
212
+ for (const source of rawSources) {
213
+ const name = isRecord$1(source) ? asString(source["name"]) : void 0;
214
+ if (!isRecord$1(source) || name === void 0 || asString(source["url"]) === void 0) return "a sourceDescription needs a name and a url";
215
+ if (sources.some((item) => item.name === name)) return `duplicate sourceDescription ${name}`;
216
+ sources.push({
217
+ name,
218
+ type: asString(source["type"]) ?? "openapi"
219
+ });
220
+ }
221
+ const components = isRecord$1(arazzo["components"]) && isRecord$1(arazzo["components"]["parameters"]) ? arazzo["components"]["parameters"] : {};
222
+ const rawWorkflows = arazzo["workflows"];
223
+ if (!Array.isArray(rawWorkflows) || rawWorkflows.length === 0) return "workflows needs at least one entry";
224
+ const workflows = [];
225
+ for (const item of rawWorkflows) {
226
+ const workflowId = isRecord$1(item) ? asString(item["workflowId"]) : void 0;
227
+ if (!isRecord$1(item) || workflowId === void 0) return "a workflow needs a workflowId";
228
+ if (workflows.some((workflow) => workflow.workflowId === workflowId)) return `duplicate workflowId ${workflowId}`;
229
+ const parameters = parseParameters(item["parameters"], components, workflowId);
230
+ if (typeof parameters === "string") return parameters;
231
+ const rawSteps = item["steps"];
232
+ if (!Array.isArray(rawSteps) || rawSteps.length === 0) return `${workflowId}: steps needs at least one entry`;
233
+ const steps = [];
234
+ for (const step of rawSteps) {
235
+ const stepId = isRecord$1(step) ? asString(step["stepId"]) : void 0;
236
+ if (!isRecord$1(step) || stepId === void 0) return `${workflowId}: a step needs a stepId`;
237
+ if (steps.some((other) => other.stepId === stepId)) return `${workflowId}: duplicate stepId ${stepId}`;
238
+ const targets = STEP_TARGETS.filter((field) => asString(step[field]) !== void 0);
239
+ if (targets.length !== 1 && !(targets.length === 2 && targets.includes("operationId") && targets.includes("channelPath"))) return `${workflowId}.${stepId}: a step needs exactly one of operationId, operationPath or workflowId`;
240
+ const stepParameters = parseParameters(step["parameters"], components, `${workflowId}.${stepId}`);
241
+ if (typeof stepParameters === "string") return stepParameters;
242
+ steps.push(compact({
243
+ stepId,
244
+ operationId: asString(step["operationId"]),
245
+ operationPath: asString(step["operationPath"]),
246
+ workflowId: asString(step["workflowId"]),
247
+ channelPath: asString(step["channelPath"]),
248
+ parameters: stepParameters
249
+ }));
250
+ }
251
+ workflows.push({
252
+ workflowId,
253
+ parameters,
254
+ steps
255
+ });
256
+ }
257
+ return {
258
+ sources,
259
+ workflows
260
+ };
261
+ }
262
+ function unescapePointer(token) {
263
+ return token.replaceAll("~1", "/").replaceAll("~0", "~");
264
+ }
265
+ function readPointer(doc, pointer) {
266
+ if (!pointer.startsWith("#/")) return;
267
+ let current = doc;
268
+ for (const raw of pointer.slice(2).split("/")) {
269
+ if (!isRecord$1(current)) return;
270
+ let token;
271
+ try {
272
+ token = unescapePointer(decodeURIComponent(raw));
273
+ } catch {
274
+ return;
275
+ }
276
+ if (!Object.hasOwn(current, token)) return;
277
+ current = current[token];
278
+ }
279
+ return current;
280
+ }
281
+ function findOperation(openapi, operationId) {
282
+ if (!isRecord$1(openapi) || !isRecord$1(openapi["paths"])) return;
283
+ for (const pathItem of Object.values(openapi["paths"])) {
284
+ if (!isRecord$1(pathItem)) continue;
285
+ for (const [method, operation] of Object.entries(pathItem)) if (!method.startsWith("x-") && isRecord$1(operation) && operation["operationId"] === operationId) return operation;
286
+ }
287
+ }
288
+ /** One description, or a map of descriptions keyed by source `name`. */
289
+ function descriptionFor(openapi, name) {
290
+ if (isRecord$1(openapi) && !("openapi" in openapi) && !("paths" in openapi) && isRecord$1(openapi[name])) return openapi[name];
291
+ return openapi;
292
+ }
293
+ function permissionKeysOf(node) {
294
+ if (!isRecord$1(node)) return [];
295
+ const raw = node["x-permdock-permissions"];
296
+ if (!Array.isArray(raw)) return [];
297
+ return raw.filter((item) => typeof item === "string");
298
+ }
299
+ /** `$inputs.a.b` against the frame's inputs; anything else that is a runtime expression is unknown. */
300
+ function valueOf(value, inputs) {
301
+ if (typeof value !== "string" || !value.includes("$")) return {
302
+ known: true,
303
+ value
304
+ };
305
+ const match = /^\$inputs\.([^#{}]+)$/u.exec(value);
306
+ if (match?.[1] === void 0) return value.startsWith("$") || value.includes("{$") ? { known: false } : {
307
+ known: true,
308
+ value
309
+ };
310
+ let current = inputs;
311
+ for (const part of match[1].split(".")) {
312
+ if (!isRecord$1(current) || !Object.hasOwn(current, part)) return { known: false };
313
+ current = current[part];
314
+ }
315
+ return {
316
+ known: true,
317
+ value: current
318
+ };
319
+ }
320
+ function bind(parameters, inputs) {
321
+ const record = {};
322
+ let provisional = false;
323
+ for (const parameter of parameters) {
324
+ const bound = valueOf(parameter.value, inputs);
325
+ if (!bound.known) {
326
+ provisional = true;
327
+ continue;
328
+ }
329
+ Object.defineProperty(record, parameter.name, {
330
+ value: bound.value,
331
+ enumerable: true,
332
+ writable: true,
333
+ configurable: true
334
+ });
335
+ }
336
+ return {
337
+ record,
338
+ provisional
339
+ };
340
+ }
341
+ function flatten(doc, workflowId, inputs, seen, out) {
342
+ if (seen.has(workflowId)) return "workflowId cycle";
343
+ const workflow = doc.workflows.find((item) => item.workflowId === workflowId);
344
+ if (workflow === void 0) return `unknown workflowId ${workflowId}`;
345
+ seen.add(workflowId);
346
+ for (const step of workflow.steps) {
347
+ if (step.workflowId === void 0 || step.workflowId.startsWith("$sourceDescriptions.")) {
348
+ out.push({
349
+ step,
350
+ workflow,
351
+ inputs
352
+ });
353
+ continue;
354
+ }
355
+ const nested = flatten(doc, step.workflowId, bind(step.parameters, inputs).record, seen, out);
356
+ if (nested !== void 0) return nested;
357
+ }
358
+ seen.delete(workflowId);
359
+ }
360
+ function locate(doc, openapi, step) {
361
+ if (step.workflowId !== void 0) return {
362
+ reason: "unsupported",
363
+ detail: "workflows in another Arazzo document are not resolved"
364
+ };
365
+ let sourceName;
366
+ let operationId = step.operationId;
367
+ let pointer;
368
+ if (step.operationPath !== void 0) {
369
+ const match = /^\{?\$sourceDescriptions\.([^.}#]+)(?:\.url)?\}?(#.*)?$/u.exec(step.operationPath);
370
+ if (match?.[1] === void 0) return {
371
+ reason: "undocumented",
372
+ detail: "operationPath is not a $sourceDescriptions reference"
373
+ };
374
+ sourceName = match[1];
375
+ pointer = match[2] ?? "";
376
+ } else if (operationId !== void 0) {
377
+ const match = /^\$sourceDescriptions\.([^.]+)\.(.+)$/u.exec(operationId);
378
+ if (match?.[1] !== void 0 && match[2] !== void 0) {
379
+ sourceName = match[1];
380
+ operationId = match[2];
381
+ }
382
+ }
383
+ const candidates = sourceName === void 0 ? doc.sources.filter((source) => source.type !== "arazzo") : doc.sources.filter((source) => source.name === sourceName);
384
+ if (candidates.length === 0) return {
385
+ reason: "undocumented",
386
+ detail: sourceName === void 0 ? "no API sourceDescription" : `unknown sourceDescription ${sourceName}`
387
+ };
388
+ if (step.channelPath !== void 0) return {
389
+ reason: "unsupported",
390
+ detail: "AsyncAPI steps are not evaluated"
391
+ };
392
+ const found = [];
393
+ for (const source of candidates) {
394
+ const description = descriptionFor(openapi, source.name);
395
+ const node = pointer === void 0 ? operationId === void 0 ? void 0 : findOperation(description, operationId) : readPointer(description, pointer);
396
+ if (node !== void 0 || candidates.length === 1) found.push({
397
+ source,
398
+ node
399
+ });
400
+ }
401
+ if (found.length > 1) return {
402
+ reason: "undocumented",
403
+ detail: "operationId matches several sources; qualify it with $sourceDescriptions"
404
+ };
405
+ const [hit] = found;
406
+ if (hit?.source.type === "asyncapi") return {
407
+ reason: "unsupported",
408
+ detail: "AsyncAPI steps are not evaluated"
409
+ };
410
+ if (hit === void 0 || hit.source.type === "arazzo" || !isRecord$1(hit.node)) return {
411
+ reason: "undocumented",
412
+ detail: "operation not found"
413
+ };
414
+ return compact({
415
+ node: hit.node,
416
+ operationId: asString(hit.node["operationId"]) ?? operationId
417
+ });
418
+ }
419
+ function resolve(doc, openapi, tree, flat) {
420
+ const { step, workflow, inputs } = flat;
421
+ const base = {
422
+ stepId: step.stepId,
423
+ workflowId: workflow.workflowId
424
+ };
425
+ const located = locate(doc, openapi, step);
426
+ const { record, provisional } = bind([...workflow.parameters.filter((parameter) => !step.parameters.some((own) => own.name === parameter.name)), ...step.parameters], inputs);
427
+ const data = Object.keys(inputs).length + Object.keys(record).length === 0 ? void 0 : {
428
+ ...inputs,
429
+ ...record
430
+ };
431
+ if ("reason" in located) return {
432
+ ...base,
433
+ ...compact({ operationId: step.operationId }),
434
+ keys: [],
435
+ finding: located,
436
+ data,
437
+ provisional
438
+ };
439
+ const keys = permissionKeysOf(located.node);
440
+ const unknown = tree === void 0 ? void 0 : keys.find((key) => findPermission(tree, key) === void 0);
441
+ return {
442
+ ...base,
443
+ ...compact({ operationId: located.operationId }),
444
+ keys,
445
+ ...keys.length === 0 ? { finding: {
446
+ reason: "undocumented",
447
+ detail: "operation has no x-permdock-permissions"
448
+ } } : unknown === void 0 ? {} : { finding: {
449
+ reason: "unknown-key",
450
+ detail: unknown
451
+ } },
452
+ data,
453
+ provisional
454
+ };
455
+ }
456
+ function resolveAll(input, tree) {
457
+ const doc = parseDocument(input.arazzo);
458
+ if (typeof doc === "string") return {
459
+ workflowId: input.workflowId ?? "",
460
+ error: doc
461
+ };
462
+ const workflowId = input.workflowId ?? doc.workflows[0]?.workflowId ?? "";
463
+ const flat = [];
464
+ const error = flatten(doc, workflowId, input.inputs ?? {}, /* @__PURE__ */ new Set(), flat);
465
+ if (error !== void 0) return {
466
+ workflowId,
467
+ error
468
+ };
469
+ return {
470
+ workflowId,
471
+ steps: flat.map((item) => resolve(doc, input.openapi, tree, item))
472
+ };
473
+ }
474
+ function arazzoFindings(input, tree) {
475
+ const resolved = resolveAll(input, tree);
476
+ if ("error" in resolved) return [{
477
+ stepId: "document",
478
+ workflowId: resolved.workflowId,
479
+ reason: "validation",
480
+ detail: resolved.error
481
+ }];
482
+ return resolved.steps.flatMap((step) => step.finding === void 0 ? [] : [{
483
+ stepId: step.stepId,
484
+ workflowId: step.workflowId,
485
+ reason: step.finding.reason,
486
+ detail: step.finding.detail
487
+ }]);
488
+ }
489
+ function worst(outcomes) {
490
+ if (outcomes.includes("denied")) return "denied";
491
+ if (outcomes.includes("approval-required")) return "approval-required";
492
+ return "granted";
493
+ }
494
+ function combine(decisions) {
495
+ const outcome = worst(decisions.map((item) => item.outcome));
496
+ return decisions.find((item) => item.outcome === outcome) ?? denied("undocumented");
497
+ }
498
+ function simulateArazzo(input, tree, decide) {
499
+ const resolved = resolveAll(input, tree);
500
+ if ("error" in resolved) return freezeDeep({
501
+ workflowId: resolved.workflowId === "" ? "unknown" : resolved.workflowId,
502
+ outcome: "denied",
503
+ steps: [{
504
+ stepId: "document",
505
+ permissions: [],
506
+ decision: denied("validation")
507
+ }]
508
+ });
509
+ const steps = resolved.steps.map((step) => {
510
+ const owner = step.workflowId === resolved.workflowId ? void 0 : step.workflowId;
511
+ if (step.finding !== void 0) return compact({
512
+ stepId: step.stepId,
513
+ workflowId: owner,
514
+ operationId: step.operationId,
515
+ permissions: [],
516
+ decision: denied(step.finding.reason === "unsupported" ? "unsupported" : "undocumented")
517
+ });
518
+ const permissions = step.keys.flatMap((key) => {
519
+ const leaf = tree === void 0 ? void 0 : findPermission(tree, key);
520
+ return leaf === void 0 ? [] : [leaf];
521
+ });
522
+ return compact({
523
+ stepId: step.stepId,
524
+ workflowId: owner,
525
+ operationId: step.operationId,
526
+ permissions,
527
+ decision: combine(permissions.map((permission) => decide(permission, step.data))),
528
+ provisional: step.provisional ? true : void 0
529
+ });
530
+ });
531
+ return freezeDeep({
532
+ workflowId: resolved.workflowId,
533
+ outcome: worst(steps.map((step) => step.decision.outcome)),
534
+ steps
535
+ });
536
+ }
537
+ function isArazzoSimulateInput(value) {
538
+ return isRecord$1(value) && "arazzo" in value;
539
+ }
540
+ //#endregion
541
+ //#region src/core/delegation.ts
542
+ /** The permission's scope and any scope it was renamed from: tokens issued before a rename keep working. */
543
+ function scopeNames(permission) {
544
+ return [permission.scope, ...formerScopes(permission)];
545
+ }
546
+ /**
547
+ * Whether a delegated caller may use a permission its principal holds, through
548
+ * OAuth `scopes`, RFC 9396 `authorizationDetails` or GNAP `access`.
549
+ * `undefined` means covered; otherwise the denial reason `decide` would add.
550
+ */
551
+ function coveredByDelegation(permission, delegation, resourceId, hasActor = false) {
552
+ const unscoped = hasActor ? "no-delegation" : void 0;
553
+ if (delegation === void 0) return unscoped;
554
+ const hasScopes = delegation.scopes !== void 0;
555
+ const hasDetails = delegation.authorizationDetails !== void 0;
556
+ const hasAccess = delegation.access !== void 0;
557
+ if (!hasScopes && !hasDetails && !hasAccess) return unscoped;
558
+ const emptyScopes = hasScopes && (delegation.scopes?.length ?? 0) === 0;
559
+ const emptyAccess = hasAccess && (delegation.access?.length ?? 0) === 0;
560
+ if (emptyScopes && !hasDetails && !hasAccess) return "no-delegation";
561
+ if (emptyAccess && !hasScopes && !hasDetails) return "no-delegation";
562
+ if (emptyScopes && emptyAccess && !hasDetails) return "no-delegation";
563
+ const scopes = scopeNames(permission);
564
+ const scopeOk = delegation.scopes?.some((scope) => scopes.includes(scope)) ?? false;
565
+ const detailOk = delegation.authorizationDetails?.some((detail) => {
566
+ if (detail.type !== permission.resource) return false;
567
+ const identifier = detail["identifier"];
568
+ if (identifier !== void 0 && (typeof identifier !== "string" || identifier !== resourceId)) return false;
569
+ const actions = detail.actions;
570
+ if (actions === void 0) return true;
571
+ return Array.isArray(actions) && actions.includes(permission.action);
572
+ }) ?? false;
573
+ const accessOk = accessCovers(permission, delegation.access, resourceId);
574
+ if (scopeOk || detailOk || accessOk) return;
575
+ return "not-delegated";
576
+ }
577
+ function accessCovers(permission, access, resourceId) {
578
+ if (access === void 0) return false;
579
+ return access.some((entry) => {
580
+ if (typeof entry === "string") return scopeNames(permission).includes(entry);
581
+ if (entry === null || typeof entry !== "object") return false;
582
+ const type = entry["type"];
583
+ if (typeof type !== "string" || !typeMatches(type, permission.resource)) return false;
584
+ const actions = entry["actions"];
585
+ if (actions !== void 0 && (!Array.isArray(actions) || !actions.includes(permission.action))) return false;
586
+ const identifier = entry["identifier"];
587
+ if (identifier !== void 0 && (typeof identifier !== "string" || identifier !== resourceId)) return false;
588
+ return true;
589
+ });
590
+ }
591
+ function typeMatches(type, resource) {
592
+ if (type === resource) return true;
593
+ return type.endsWith(`/${resource}`);
594
+ }
595
+ function resourceIdOf(data) {
596
+ if (data === null || typeof data !== "object" || !("id" in data)) return;
597
+ const id = data.id;
598
+ return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
599
+ }
600
+ /**
601
+ * Whether the subject's principal is a `from` of the delegation: every item
602
+ * matches, with roles checked against the roles the principal holds here
603
+ * (`matchGrantee` leaves role holding to the caller) and nothing that needs a
604
+ * row.
605
+ */
606
+ function handsOver(delegation, subject, heldRoles, now) {
607
+ const items = flattenGrantee(delegation.from);
608
+ if (items.length === 0 || subject.principal === null) return false;
609
+ return items.every((item) => {
610
+ if (item.kind === "role") return heldRoles.has(item.role);
611
+ if (item.kind === "relation") return false;
612
+ const match = matchGrantee(item, subject, now, void 0);
613
+ return match.matched && match.where === void 0;
614
+ });
615
+ }
616
+ /**
617
+ * The permission keys the policy's delegations let `subject.actor` use for
618
+ * `subject.principal` right now: the union over every active delegation whose
619
+ * `to` matches the actor and whose `from` the principal holds. `undefined`
620
+ * when none applies, so the call falls back to the token delegation alone.
621
+ * An actor with `readOnly: true` gets only each delegation's read-only keys.
622
+ * Attenuation only: a key here still needs a matching allow and no deny.
623
+ */
624
+ function delegatedPermissions(delegations, subject, heldRoles, now) {
625
+ const actor = subject.actor;
626
+ if (actor === void 0 || delegations === void 0) return;
627
+ let keys;
628
+ for (const delegation of delegations) {
629
+ if (delegation.to.kind !== actor.kind || delegation.to.id !== void 0 && delegation.to.id !== actor.id || !isActive(delegation.validity, now) || !handsOver(delegation, subject, heldRoles, now)) continue;
630
+ keys ??= /* @__PURE__ */ new Set();
631
+ for (const key of actor.readOnly === true ? delegation.readOnly : delegation.permissions) keys.add(key);
632
+ }
633
+ return keys;
634
+ }
635
+ //#endregion
636
+ //#region src/core/canonical-json.ts
637
+ function isRecord(value) {
638
+ return value !== null && typeof value === "object" && !Array.isArray(value);
639
+ }
640
+ /**
641
+ * JSON with sorted keys and `undefined` members dropped, so equal values hash
642
+ * equally. Dates become ISO strings, bigints `"<n>n"`, and a repeated object
643
+ * on the current path `"[Circular]"`, so any validated row serialises.
644
+ */
645
+ function canonicalJson(value, path = /* @__PURE__ */ new Set()) {
646
+ if (typeof value === "bigint") return JSON.stringify(`${value}n`);
647
+ if (value instanceof Date) return JSON.stringify(Number.isNaN(value.getTime()) ? null : value.toISOString());
648
+ if (value !== null && typeof value === "object") {
649
+ if (path.has(value)) return "\"[Circular]\"";
650
+ path.add(value);
651
+ try {
652
+ if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item, path)).join(",")}]`;
653
+ if (isRecord(value)) return `{${Object.keys(value).toSorted().filter((key) => value[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key], path)}`).join(",")}}`;
654
+ } finally {
655
+ path.delete(value);
656
+ }
657
+ }
658
+ return JSON.stringify(value) ?? "null";
659
+ }
660
+ //#endregion
661
+ //#region src/core/token.ts
662
+ /**
663
+ * Only stable identity goes into the token, so refreshing a session (new
664
+ * roles, assurance or claims) does not orphan an outstanding approval.
665
+ */
666
+ function principalIdentity(principal) {
667
+ if (principal === null) return null;
668
+ return {
669
+ id: principal.id,
670
+ tenant: principal.tenant ?? null,
671
+ issuer: principal.issuer ?? null
672
+ };
673
+ }
674
+ /** A row's `version` field as token input; `null` when it is missing or not a scalar. */
675
+ function versionOf(row, field) {
676
+ if (row === null || typeof row !== "object" || !Object.hasOwn(row, field)) return null;
677
+ const value = row[field];
678
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
679
+ return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" ? String(value) : null;
680
+ }
681
+ /** The digest that binds a token to the call's data when no row id does. */
682
+ function payloadDigest(data) {
683
+ return bytesToBase64Url(sha256(canonicalJson(data)));
684
+ }
685
+ /**
686
+ * `version` is set only for an approval that goes stale on a resource
687
+ * change; without it the payload, and so every other token, is unchanged.
688
+ * `payload` is set when `resourceId` is `*` (a collection action or a row
689
+ * without an id), so an approval covers the arguments it was given and no
690
+ * others.
691
+ */
692
+ function decisionToken(input) {
693
+ const payload = JSON.stringify({
694
+ key: input.key,
695
+ resourceId: input.resourceId,
696
+ principal: principalIdentity(input.principal),
697
+ actor: input.actor === void 0 ? null : {
698
+ id: input.actor.id,
699
+ kind: input.actor.kind
700
+ },
701
+ fingerprint: input.fingerprint,
702
+ ...input.version === void 0 ? {} : { version: input.version },
703
+ ...input.payload === void 0 ? {} : { payload: input.payload }
704
+ });
705
+ return `pd1.${bytesToBase64Url(sha256(payload))}`;
706
+ }
707
+ //#endregion
708
+ //#region src/conditions/bind.ts
709
+ function isPrimitive(value) {
710
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
711
+ }
712
+ function bindValue(value, subject, only) {
713
+ if (isConditionRef(value) && only !== void 0 && !only(value.ref)) return value;
714
+ if (isConditionRef(value)) {
715
+ const resolved = resolveConditionRef(value.ref, subject);
716
+ if (Array.isArray(resolved)) return resolved.filter((item) => isPrimitive(item));
717
+ return isPrimitive(resolved) ? resolved : null;
718
+ }
719
+ if (Array.isArray(value)) return value.map((item) => bindValue(item, subject, only));
720
+ return value;
721
+ }
722
+ function bindArg(arg, subject, only) {
723
+ return arg !== null && typeof arg === "object" && "field" in arg && !isConditionRef(arg) ? arg : bindValue(arg, subject, only);
724
+ }
725
+ /**
726
+ * Replaces every `principal.*` / `context.*` ref with the subject's value, so
727
+ * a `where()` result compiles the same without the subject. A ref the subject
728
+ * does not hold binds to `null` (or `[]` in a list), which never matches, as
729
+ * in `evaluateCondition`. With `only`, refs it rejects stay refs.
730
+ */
731
+ function bindConditionRefs(condition, subject, only) {
732
+ switch (condition.op) {
733
+ case "and":
734
+ case "or": return {
735
+ op: condition.op,
736
+ conditions: condition.conditions.map((child) => bindConditionRefs(child, subject, only))
737
+ };
738
+ case "not": return {
739
+ op: "not",
740
+ condition: bindConditionRefs(condition.condition, subject, only)
741
+ };
742
+ case "in":
743
+ case "notIn": {
744
+ const value = condition.value;
745
+ if (isConditionRef(value) && only !== void 0 && !only(value.ref)) return condition;
746
+ const bound = bindValue(value, subject, only);
747
+ return {
748
+ ...condition,
749
+ value: Array.isArray(bound) ? bound : []
750
+ };
751
+ }
752
+ case "eq":
753
+ case "ne":
754
+ case "gt":
755
+ case "gte":
756
+ case "lt":
757
+ case "lte":
758
+ case "contains": return {
759
+ ...condition,
760
+ value: bindValue(condition.value, subject, only)
761
+ };
762
+ case "sqlFunction": return {
763
+ ...condition,
764
+ args: condition.args.map((arg) => bindArg(arg, subject, only)),
765
+ twin: bindConditionRefs(condition.twin, subject, only)
766
+ };
767
+ case "isNull":
768
+ case "memberOf":
769
+ case "related":
770
+ case "opaque": return condition;
771
+ default: return condition;
772
+ }
773
+ }
774
+ //#endregion
775
+ //#region src/core/where-scope.ts
776
+ const ALWAYS = {
777
+ op: "eq",
778
+ field: "_",
779
+ value: true
780
+ };
781
+ function eq(field, value) {
782
+ return value === void 0 ? {
783
+ op: "or",
784
+ conditions: []
785
+ } : {
786
+ op: "eq",
787
+ field,
788
+ value
789
+ };
790
+ }
791
+ /** One equality per partitioning scope key on the membership's chain, outermost first. */
792
+ function namedFilters(membership, scope) {
793
+ if (findScope(scope.scopes, membership.scope ?? "") === void 0 || !inTeam(membership, scope.scopes, scope.team) || !activeFor(membership, scope.scopes, scope.tenant) || membership.scope === void 0) return null;
794
+ const filters = [];
795
+ for (const name of scopeChain(scope.scopes, membership.scope).toReversed()) {
796
+ const key = findScope(scope.scopes, name)?.key;
797
+ if (key !== void 0 && scope.partitioned(name, key)) filters.push(eq(key, scopeIdOf(membership, name)));
798
+ }
799
+ return filters;
800
+ }
801
+ /**
802
+ * The row filters a grant's membership implies, or `null` when the grant
803
+ * reaches no row in this scope (another tenant, no active tenant, expired).
804
+ */
805
+ function scopeFilters(grant, scope) {
806
+ if (grant.scope === void 0) return [];
807
+ const membership = grant.membership;
808
+ if (membership === void 0 || isMembershipExpired(membership, scope.now)) return null;
809
+ if (typeof grant.scope === "string") return membership.scope === grant.scope ? namedFilters(membership, scope) : null;
810
+ const on = membership.on;
811
+ if (on === void 0) return null;
812
+ if (scope.resources === void 0) return on.resource === grant.scope.resource && on.resource === scope.resource ? [eq("id", on.id)] : null;
813
+ const roleResource = scope.resources.get(grant.scope.resource);
814
+ if (on.resource !== grant.scope.resource && membershipField(roleResource, on.resource, scope.resources) === void 0) return null;
815
+ const walked = scope.graph === true ? resourceRoleCondition(scope.resources.get(scope.resource), on.resource, [on.id], scope.resources) : void 0;
816
+ if (walked !== void 0) return [walked];
817
+ const field = membershipField(scope.resources.get(scope.resource), on.resource, scope.resources);
818
+ return field === void 0 ? null : [eq(field, on.id)];
819
+ }
820
+ function all(conditions) {
821
+ if (conditions.length === 0) return ALWAYS;
822
+ return sole(conditions) ?? {
823
+ op: "and",
824
+ conditions: [...conditions]
825
+ };
826
+ }
827
+ function scopedCondition(grant, scope) {
828
+ const filters = scopeFilters(grant, scope);
829
+ if (filters === null) return null;
830
+ const parts = grant.where === void 0 ? filters : [...filters, grant.where];
831
+ return parts.length === 0 ? "always" : all(parts);
832
+ }
833
+ /** Whether an unconditional `deny` removes every row `allow` could reach. */
834
+ function covers(deny, allow, scopes) {
835
+ if (deny.where !== void 0) return false;
836
+ const denied = deny.membership;
837
+ if (deny.scope === void 0 || denied === void 0) return deny.scope === void 0;
838
+ const allowed = allow.membership;
839
+ if (allowed === void 0) return false;
840
+ if (typeof deny.scope === "object") return typeof allow.scope === "object" && denied.on?.resource === allowed.on?.resource && denied.on?.id === allowed.on?.id;
841
+ if (typeof allow.scope !== "string" || allowed.scope === void 0) return false;
842
+ const chain = scopeChain(scopes, deny.scope);
843
+ return scopeChain(scopes, allowed.scope).includes(deny.scope) && chain.every((name) => scopeIdOf(allowed, name) === scopeIdOf(denied, name));
844
+ }
845
+ /**
846
+ * The portable filter for one permission's grants: allows OR together, each
847
+ * narrowed to its membership's tenant, team or resource; any applicable
848
+ * portable deny is subtracted, and an unconditional one empties the result.
849
+ */
850
+ function collect(grants, scope) {
851
+ const none = {
852
+ op: "or",
853
+ conditions: []
854
+ };
855
+ const partial = grants.some((grant) => grant.portable === false);
856
+ const denies = [];
857
+ for (const grant of grants) {
858
+ if (grant.effect !== "deny" || grant.portable === false || !isActive(grant.validity, scope.now)) continue;
859
+ const condition = scopedCondition(grant, scope);
860
+ if (condition === "always") return {
861
+ condition: none,
862
+ partial
863
+ };
864
+ if (condition !== null) denies.push({
865
+ grant,
866
+ condition
867
+ });
868
+ }
869
+ const parts = [];
870
+ for (const grant of grants) {
871
+ if (grant.effect !== "allow" || grant.portable === false || !isActive(grant.validity, scope.now)) continue;
872
+ const condition = scopedCondition(grant, scope);
873
+ if (condition === null || denies.some((deny) => covers(deny.grant, grant, scope.scopes))) continue;
874
+ parts.push(all([...condition === "always" ? [] : [condition], ...denies.map((deny) => ({
875
+ op: "not",
876
+ condition: deny.condition
877
+ }))]));
878
+ }
879
+ if (parts.length === 0) return {
880
+ condition: none,
881
+ partial
882
+ };
883
+ return {
884
+ condition: sole(parts) ?? {
885
+ op: "or",
886
+ conditions: parts
887
+ },
888
+ partial
889
+ };
890
+ }
891
+ /**
892
+ * `collect`, with every subject ref bound to `scope.subject` and the subject
893
+ * attached (not enumerable) for `memberOf`, which needs its memberships.
894
+ */
895
+ function whereFromGrants(grants, scope) {
896
+ const { condition, partial } = collect(grants, scope);
897
+ const result = {
898
+ condition: bindConditionRefs(condition, scope.subject),
899
+ partial
900
+ };
901
+ Object.defineProperty(result, "subject", {
902
+ value: scope.subject,
903
+ enumerable: false
904
+ });
905
+ Object.defineProperty(result, "scopes", {
906
+ value: scope.scopes,
907
+ enumerable: false
908
+ });
909
+ if (scope.resources !== void 0) Object.defineProperty(result, "resources", {
910
+ value: scope.resources,
911
+ enumerable: false
912
+ });
913
+ return freezeDeep(result);
914
+ }
915
+ //#endregion
916
+ //#region src/core/snapshot.ts
917
+ function snapshotGrant(grant, membership) {
918
+ const scope = grant.scope === "global" ? void 0 : typeof grant.scope === "string" ? grant.scope : { resource: grant.scope.resource };
919
+ const entry = compact({
920
+ permission: grant.permission.key,
921
+ effect: grant.effect,
922
+ role: grant.role,
923
+ to: grant.to,
924
+ where: grant.portable ? grant.where : void 0,
925
+ check: grant.portable ? grant.check : void 0,
926
+ approval: grant.approval,
927
+ scope,
928
+ membership,
929
+ portable: grant.portable ? void 0 : false,
930
+ fields: grant.fields,
931
+ validity: grant.validity
932
+ });
933
+ return freezeDeep(entry);
934
+ }
935
+ /** Claim refs: the snapshot principal does not carry `claims`, so they are bound to the subject's values. */
936
+ function isClaimRef(ref) {
937
+ return ref.startsWith("principal.claim.") || ref.startsWith("principal.claims.");
938
+ }
939
+ function bindClaims(grant, subject) {
940
+ if (grant.where === void 0 && grant.check === void 0) return grant;
941
+ return freezeDeep(compact({
942
+ ...grant,
943
+ where: grant.where === void 0 ? void 0 : bindConditionRefs(grant.where, subject, isClaimRef),
944
+ check: grant.check === void 0 ? void 0 : bindConditionRefs(grant.check, subject, isClaimRef)
945
+ }));
946
+ }
947
+ function notEntitledOf(items) {
948
+ const seen = /* @__PURE__ */ new Map();
949
+ for (const { grant } of items) {
950
+ const key = `${grant.permission.key}\u0000${grant.role ?? ""}`;
951
+ if (!seen.has(key)) seen.set(key, {
952
+ permission: grant.permission.key,
953
+ role: grant.role,
954
+ to: grant.to
955
+ });
956
+ }
957
+ return seen.size === 0 ? void 0 : [...seen.values()];
958
+ }
959
+ function buildSnapshot(input) {
960
+ const now = input.now ?? Math.floor(Date.now() / 1e3);
961
+ const principal = input.subject.principal;
962
+ const allTenants = tenantsOf(principal, scopeList(input.scopes));
963
+ const tenants = input.tenants === "all" ? allTenants : principal?.tenant === void 0 ? [] : [principal.tenant];
964
+ const include = input.include;
965
+ const included = (permission) => include === void 0 || include.length === 0 || include.some((prefix) => permission.key === prefix || permission.key.startsWith(`${prefix}.`) || permission.resource === prefix);
966
+ const grants = input.grants.filter((item) => included(item.grant.permission)).map((item) => bindClaims(snapshotGrant(item.grant, item.membership), input.subject));
967
+ const assignableFor = input.assignable;
968
+ const assignable = assignableFor === void 0 ? [] : tenants.map((tenant) => {
969
+ const entry = assignableFor(tenant);
970
+ return {
971
+ ...entry,
972
+ permissions: entry.permissions.filter(included)
973
+ };
974
+ });
975
+ return freezeDeep(compact({
976
+ v: 1,
977
+ issuedAt: now,
978
+ subject: compact({
979
+ principal: principal === null ? null : compact({
980
+ id: principal.id,
981
+ roles: principal.roles ?? [],
982
+ plans: principal.plans,
983
+ tenant: principal.tenant,
984
+ memberships: principal.memberships
985
+ }),
986
+ delegation: snapshotDelegation(input.subject, input.delegated !== void 0),
987
+ context: input.subject.context
988
+ }),
989
+ roles: input.roles,
990
+ audiences: input.audiences,
991
+ grants,
992
+ tenants,
993
+ include,
994
+ simulated: input.simulated === true ? true : void 0,
995
+ expiresAt: input.subject.expiresAt,
996
+ scopes: input.scopes,
997
+ assignable: assignable.length > 0 ? assignable : void 0,
998
+ delegated: input.delegated === void 0 ? void 0 : [...input.delegated].toSorted(),
999
+ notEntitled: notEntitledOf((input.notEntitled ?? []).filter((item) => included(item.grant.permission))),
1000
+ vocabulary: input.vocabulary === void 0 ? void 0 : compact({
1001
+ roles: listRoles(input.vocabulary.roles).length === 0 ? void 0 : Object.fromEntries(listRoles(input.vocabulary.roles).map((leaf) => [leaf.key, leaf])),
1002
+ plans: listPlans(input.vocabulary.plans).length === 0 ? void 0 : Object.fromEntries(listPlans(input.vocabulary.plans).map((leaf) => [leaf.key, leaf]))
1003
+ })
1004
+ }));
1005
+ }
1006
+ async function signSnapshot(snapshot, signer, audience) {
1007
+ const payload = { snapshot };
1008
+ if (snapshot.subject.principal !== null) payload["sub"] = snapshot.subject.principal.id;
1009
+ return await signer.sign(payload, compact({
1010
+ typ: "permdock-snapshot+jwt",
1011
+ audience,
1012
+ expiresAt: snapshot.expiresAt
1013
+ }));
1014
+ }
1015
+ /**
1016
+ * The token delegation as the client sees it. An actor with no token
1017
+ * delegation gets an empty `scopes` list so the client ceiling agrees with
1018
+ * `no-delegation`, unless a policy delegation applies (`delegated`), which
1019
+ * then is the ceiling.
1020
+ */
1021
+ function snapshotDelegation(subject, delegated) {
1022
+ const delegation = subject.delegation;
1023
+ if (subject.actor === void 0 || delegated) return delegation;
1024
+ if (delegation?.scopes === void 0 && delegation?.authorizationDetails === void 0 && delegation?.access === void 0) return {
1025
+ ...delegation,
1026
+ scopes: []
1027
+ };
1028
+ return delegation;
1029
+ }
1030
+ function rejectUnsafe(value, path) {
1031
+ if (value === null || typeof value !== "object") return;
1032
+ if (Array.isArray(value)) {
1033
+ for (const [index, item] of value.entries()) rejectUnsafe(item, `${path}[${index}]`);
1034
+ return;
1035
+ }
1036
+ for (const key of Object.keys(value)) {
1037
+ if (isForbiddenKey(key)) throw new Error(`PermDock: unsafe snapshot key '${key}' at ${path}`);
1038
+ rejectUnsafe(value[key], `${path}.${key}`);
1039
+ }
1040
+ }
1041
+ function parseSnapshot(json) {
1042
+ const input = typeof json === "string" ? JSON.parse(json) : json;
1043
+ if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("PermDock: snapshot must be an object");
1044
+ const value = typeof json === "string" || Object.isFrozen(input) ? input : JSON.parse(JSON.stringify(input));
1045
+ rejectUnsafe(value, "$");
1046
+ const version = value["v"];
1047
+ if (version !== 1) throw new Error(`PermDock: unsupported snapshot version '${String(version)}'`);
1048
+ return freezeDeep(value);
1049
+ }
1050
+ //#endregion
1051
+ export { whereFromGrants as a, versionOf as c, delegatedPermissions as d, resourceIdOf as f, evaluateCondition as g, simulateArazzo as h, snapshotGrant as i, canonicalJson as l, isArazzoSimulateInput as m, parseSnapshot as n, decisionToken as o, arazzoFindings as p, signSnapshot as r, payloadDigest as s, buildSnapshot as t, coveredByDelegation as u };