permdock 0.0.0 → 0.2.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 (344) hide show
  1. package/README.md +327 -1
  2. package/dist/a2a/index.d.ts +169 -0
  3. package/dist/a2a/index.js +232 -0
  4. package/dist/adapter-context-mlWsVGGg.js +23 -0
  5. package/dist/ai-sdk/index.d.ts +95 -0
  6. package/dist/ai-sdk/index.js +154 -0
  7. package/dist/approval-required-TzNB9oQA.js +6 -0
  8. package/dist/approvals/index.d.ts +3 -0
  9. package/dist/approvals/index.js +5 -0
  10. package/dist/approvals-DbYAuNkG.js +224 -0
  11. package/dist/approvers-DT4AVyIg.js +52 -0
  12. package/dist/arazzo-BAgw9ap6.js +70 -0
  13. package/dist/arazzo-cmvX2jDe.js +54 -0
  14. package/dist/authzen/index.d.ts +39 -0
  15. package/dist/authzen/index.js +302 -0
  16. package/dist/better-auth/index.d.ts +109 -0
  17. package/dist/better-auth/index.js +344 -0
  18. package/dist/better-supabase/index.d.ts +206 -0
  19. package/dist/better-supabase/index.js +620 -0
  20. package/dist/bind-CVy3Espv.d.ts +25 -0
  21. package/dist/bind-VnYn2HKq.js +97 -0
  22. package/dist/boundary-BM8a_P6E.js +13 -0
  23. package/dist/boundary-BmHxVH1J.d.ts +15 -0
  24. package/dist/budget-JqcRqyhK.d.ts +11 -0
  25. package/dist/budget-NbEtbJC_.js +11 -0
  26. package/dist/canonical-json-DemiK4Vp.js +26 -0
  27. package/dist/capability-4qPdO9gk.js +173 -0
  28. package/dist/capability-Dq43tT1u.d.ts +99 -0
  29. package/dist/catalog/index.d.ts +12 -0
  30. package/dist/catalog/index.js +2 -0
  31. package/dist/catalog-BjZSovXQ.js +2 -0
  32. package/dist/catalog-doc-Dsc9y7sd.js +309 -0
  33. package/dist/catalog-h2tlKFkq.js +48 -0
  34. package/dist/catalog-path-CnI0XTFK.js +12 -0
  35. package/dist/catalog-z8q6bi_r.js +55 -0
  36. package/dist/claude-agent/index.d.ts +72 -0
  37. package/dist/claude-agent/index.js +77 -0
  38. package/dist/clerk/index.d.ts +65 -0
  39. package/dist/clerk/index.js +301 -0
  40. package/dist/cli/bin.d.ts +1 -0
  41. package/dist/cli/bin.js +20 -0
  42. package/dist/cli/index.d.ts +46 -0
  43. package/dist/cli/index.js +5 -0
  44. package/dist/clients-C7zLFuWT.d.ts +12 -0
  45. package/dist/clients-DjU5ABMy.js +23 -0
  46. package/dist/cloud/index.d.ts +90 -0
  47. package/dist/cloud/index.js +404 -0
  48. package/dist/cloud-BGhHS-lY.js +50 -0
  49. package/dist/cloud-Bo3e9Ak3.js +118 -0
  50. package/dist/collect-DDDv_ESs.js +502 -0
  51. package/dist/collect-DLlujdHC.js +83 -0
  52. package/dist/collect-rG2IQ3t0.js +2 -0
  53. package/dist/commands-DWOrSQic.js +97 -0
  54. package/dist/compact-y6vfu2_f.js +19 -0
  55. package/dist/compare-pfyVnBjE.js +12 -0
  56. package/dist/compile/index.d.ts +3 -0
  57. package/dist/compile/index.js +2 -0
  58. package/dist/compile-Ba4blROR.js +613 -0
  59. package/dist/compile-CNnl6uIt.d.ts +94 -0
  60. package/dist/config-BJiM-xAm.js +157 -0
  61. package/dist/config-Bn00Z3ks.js +36 -0
  62. package/dist/config-report-Dw_JcUqT.js +44 -0
  63. package/dist/connection-CU8MQxRi.js +194 -0
  64. package/dist/context-Ttp0ekMT.js +82 -0
  65. package/dist/convex/index.d.ts +45 -0
  66. package/dist/convex/index.js +65 -0
  67. package/dist/create-BWyx3jua.d.ts +245 -0
  68. package/dist/create-CPhDL6qx.js +253 -0
  69. package/dist/credential-8P81ff4u.js +306 -0
  70. package/dist/delegation-D8pUEuce.js +103 -0
  71. package/dist/describe-B_gVZEVi.js +94 -0
  72. package/dist/describe-PdoqESvg.d.ts +36 -0
  73. package/dist/diff-B8Tunc3o.js +451 -0
  74. package/dist/diff-CzqLxZ77.js +48 -0
  75. package/dist/digest-B3kc_PgQ.d.ts +19 -0
  76. package/dist/digest-BI-6aa6d.js +27 -0
  77. package/dist/doctor-WpvG6p4k.js +1194 -0
  78. package/dist/doctor-collect-ByfYlNv1.js +664 -0
  79. package/dist/doctor-rOr-4ZfS.js +53 -0
  80. package/dist/drizzle/index.d.ts +73 -0
  81. package/dist/drizzle/index.js +160 -0
  82. package/dist/duration-xK2jGFVc.js +25 -0
  83. package/dist/elysia/index.d.ts +49 -0
  84. package/dist/elysia/index.js +59 -0
  85. package/dist/errors-CWfoNg9N.js +47 -0
  86. package/dist/errors-Cz5zn0mW.js +123 -0
  87. package/dist/errors-DAqevpNk.d.ts +125 -0
  88. package/dist/errors-DRnGZHzF.js +30 -0
  89. package/dist/evaluation-items-CfCCejQd.js +40 -0
  90. package/dist/evaluations-tz6D9A2G.js +461 -0
  91. package/dist/eve/index.d.ts +86 -0
  92. package/dist/eve/index.js +176 -0
  93. package/dist/expiry-BoBJ8Tvc.js +7 -0
  94. package/dist/express/index.d.ts +23 -0
  95. package/dist/express/index.js +78 -0
  96. package/dist/fastify/index.d.ts +33 -0
  97. package/dist/fastify/index.js +92 -0
  98. package/dist/fields-BMl1RxoX.js +26 -0
  99. package/dist/files-DWUN9PzI.js +125 -0
  100. package/dist/fixtures-ht28Cplr.js +67 -0
  101. package/dist/freeze-CrU8nQeX.js +60 -0
  102. package/dist/from-snapshot-C3sHpZNS.js +569 -0
  103. package/dist/generate-CH7ThJ14.js +173 -0
  104. package/dist/grantee-Cv5Sogfv.js +602 -0
  105. package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
  106. package/dist/headers-BZSDieNT.js +6 -0
  107. package/dist/headers-Cf-zWvsO.d.ts +6 -0
  108. package/dist/helpers-H_KARDVC.js +421 -0
  109. package/dist/hono/index.d.ts +48 -0
  110. package/dist/hono/index.js +99 -0
  111. package/dist/hosted-DJQw46hn.js +266 -0
  112. package/dist/http-BIhmLfCc.js +95 -0
  113. package/dist/http-DPuDaChb.d.ts +13 -0
  114. package/dist/http-SO7qGttf.js +44 -0
  115. package/dist/ids-DC4V4CFu.js +18 -0
  116. package/dist/index-A0YNL8jZ.d.ts +177 -0
  117. package/dist/index-CtE07CBF.d.ts +6 -0
  118. package/dist/index-D3vbkoSI.d.ts +483 -0
  119. package/dist/index-D6_cSa1z.d.ts +201 -0
  120. package/dist/index.d.ts +16 -0
  121. package/dist/index.js +26 -0
  122. package/dist/instance-options-BK_rNOSC.d.ts +11 -0
  123. package/dist/instance-options-Dhhcnm3y.js +22 -0
  124. package/dist/json-schema-BsAflUGW.js +106 -0
  125. package/dist/jwt/index.d.ts +91 -0
  126. package/dist/jwt/index.js +590 -0
  127. package/dist/kernel-GSQnMz3O.js +208 -0
  128. package/dist/kysely/index.d.ts +78 -0
  129. package/dist/kysely/index.js +129 -0
  130. package/dist/live-options-H2eG8dfJ.js +60 -0
  131. package/dist/load-DhWEB9P1.js +75 -0
  132. package/dist/load-DwJUafYN.js +2 -0
  133. package/dist/load-jose-BL6LH4bL.js +11 -0
  134. package/dist/lru-8q4WZjBV.js +28 -0
  135. package/dist/manifest-iaoBXHKc.js +783 -0
  136. package/dist/manifest-uDS9y5de.d.ts +206 -0
  137. package/dist/map-AcxK9o_D.js +147 -0
  138. package/dist/map-claims-Z4QZOiFF.js +369 -0
  139. package/dist/map-error-pgUWNNtG.js +23 -0
  140. package/dist/markers-B0hYLvMD.js +60 -0
  141. package/dist/may-use-DndQ5tQ-.js +77 -0
  142. package/dist/mcp/index.d.ts +159 -0
  143. package/dist/mcp/index.js +603 -0
  144. package/dist/nest/index.d.ts +75 -0
  145. package/dist/nest/index.js +261 -0
  146. package/dist/next/client.d.ts +23 -0
  147. package/dist/next/client.js +33 -0
  148. package/dist/next/index.d.ts +93 -0
  149. package/dist/next/index.js +180 -0
  150. package/dist/next/plugin.d.ts +14 -0
  151. package/dist/next/plugin.js +33 -0
  152. package/dist/node/index.d.ts +24 -0
  153. package/dist/node/index.js +34 -0
  154. package/dist/openai/index.d.ts +62 -0
  155. package/dist/openai/index.js +92 -0
  156. package/dist/openapi/index.d.ts +176 -0
  157. package/dist/openapi/index.js +3 -0
  158. package/dist/openapi-Bbg4wGVL.js +164 -0
  159. package/dist/openapi-BqRBvAng.js +538 -0
  160. package/dist/openapi-CqqEbHsL.js +250 -0
  161. package/dist/openapi-import-cmwetbzw.js +5959 -0
  162. package/dist/openapi-schema--ARjyfQr.js +6902 -0
  163. package/dist/orpc/index.d.ts +51 -0
  164. package/dist/orpc/index.js +145 -0
  165. package/dist/otel/index.d.ts +13 -0
  166. package/dist/otel/index.js +202 -0
  167. package/dist/package-root-zGkxO3oN.js +30 -0
  168. package/dist/parse-BuyjxhZV.js +53 -0
  169. package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
  170. package/dist/parse-local-manifest-BrMozJCo.js +36 -0
  171. package/dist/pdp/index.d.ts +22 -0
  172. package/dist/pdp/index.js +682 -0
  173. package/dist/peer-BOr_kJE1.js +31 -0
  174. package/dist/permdock-CzqXjCpU.js +3471 -0
  175. package/dist/permissions-CDgvKG5L.d.ts +52 -0
  176. package/dist/permissions-CpZrivYv.js +142 -0
  177. package/dist/permissions-DVoF6dMt.js +662 -0
  178. package/dist/permissions-DttnqqxC.d.ts +214 -0
  179. package/dist/pins-8ILTftC9.js +12 -0
  180. package/dist/policy-CFZAr_Al.d.ts +2252 -0
  181. package/dist/policy-D8UFyERw.d.ts +20 -0
  182. package/dist/policy-xyWg7QdR.js +1122 -0
  183. package/dist/powersync-A8x_Yyvh.js +2 -0
  184. package/dist/powersync-C7kLglqX.js +540 -0
  185. package/dist/powersync-yW1UUFzp.js +62 -0
  186. package/dist/prisma/index.d.ts +136 -0
  187. package/dist/prisma/index.js +455 -0
  188. package/dist/problem-B43ztKIm.js +191 -0
  189. package/dist/problem-details-CkL50EEf.d.ts +7 -0
  190. package/dist/problem-details-_AMOYz-w.js +144 -0
  191. package/dist/prompt-CXYhLfsS.js +32 -0
  192. package/dist/protected-CNsMzrVd.d.ts +27 -0
  193. package/dist/protected-Cs6kTaHo.js +100 -0
  194. package/dist/protected-bZs6rbT1.d.ts +40 -0
  195. package/dist/provider-Cjr62YMV.d.ts +6 -0
  196. package/dist/provider-DLzpb2Xz.js +52 -0
  197. package/dist/provider-store-DNyC1y48.js +43 -0
  198. package/dist/react/index.d.ts +8 -0
  199. package/dist/react/index.js +7 -0
  200. package/dist/react/provider-client.d.ts +2 -0
  201. package/dist/react/provider-client.js +3 -0
  202. package/dist/react-native/index.d.ts +198 -0
  203. package/dist/react-native/index.js +696 -0
  204. package/dist/refs-KYb4ntaz.js +91 -0
  205. package/dist/relations-cjOL01hZ.js +505 -0
  206. package/dist/replay-BsOyTrdV.js +28 -0
  207. package/dist/replay-sqBGMnNs.d.ts +5 -0
  208. package/dist/resource-ref-CaS9yVPi.js +20 -0
  209. package/dist/revocations-Bhr0aMIC.js +27 -0
  210. package/dist/revocations-Cy06JNx2.d.ts +21 -0
  211. package/dist/rls-BMNydpHk.js +2521 -0
  212. package/dist/rls-CEvs64Kj.js +238 -0
  213. package/dist/rls-fields-BMasREhR.js +3502 -0
  214. package/dist/rls-graph-pc9fuXYm.js +435 -0
  215. package/dist/rls-import-D8F7j35Q.js +943 -0
  216. package/dist/rls-migrate-7wikfrVI.js +367 -0
  217. package/dist/rls-ownership-B5Lkq62W.js +843 -0
  218. package/dist/rls-shared-aOQ4hGTO.js +493 -0
  219. package/dist/rls-verify-D8B7dOTh.js +745 -0
  220. package/dist/row-check-Dt35czLy.d.ts +40 -0
  221. package/dist/rpc-DOK54_CS.js +89 -0
  222. package/dist/run-YXCJDNYk.js +174 -0
  223. package/dist/runtime-CB8gQFvS.js +11 -0
  224. package/dist/saas-Bj_FFkjN.js +1203 -0
  225. package/dist/schema-D3MnDMxa.js +300 -0
  226. package/dist/scim/index.d.ts +2 -0
  227. package/dist/scim/index.js +2 -0
  228. package/dist/scim-BFGBBAqR.js +1445 -0
  229. package/dist/scopes-CVdPrWN-.js +239 -0
  230. package/dist/server/index.d.ts +134 -0
  231. package/dist/server/index.js +11 -0
  232. package/dist/server-DlZ28Oz5.js +268 -0
  233. package/dist/sha256-BO0swRXL.js +150 -0
  234. package/dist/shared-DRu8VFVM.js +123 -0
  235. package/dist/sink-Bd9PSyfq.js +141 -0
  236. package/dist/sink-Gs6TTVa-.d.ts +104 -0
  237. package/dist/skills-Biq5uup3.js +192 -0
  238. package/dist/skills-JaymNCdK.js +56 -0
  239. package/dist/snapshot-Enxd0n-s.js +975 -0
  240. package/dist/snapshot-cache-D6jLelln.js +44 -0
  241. package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
  242. package/dist/solid/index.d.ts +93 -0
  243. package/dist/solid/index.js +188 -0
  244. package/dist/sources-B0UvrJ21.js +534 -0
  245. package/dist/sources-BcJgw0lt.d.ts +192 -0
  246. package/dist/sql-xdg5f4Fi.js +27 -0
  247. package/dist/src-CSbUiozF.js +568 -0
  248. package/dist/ssf/index.d.ts +4 -0
  249. package/dist/ssf/index.js +596 -0
  250. package/dist/store-DMAWwAJV.js +371 -0
  251. package/dist/store-D_pjn91-.d.ts +36 -0
  252. package/dist/store-options-yO0V6gRn.js +716 -0
  253. package/dist/stores-4_sqiOA1.js +87 -0
  254. package/dist/stores-DVR5NHLE.d.ts +52 -0
  255. package/dist/stream-DDpvynSB.js +66 -0
  256. package/dist/stream-DlvG2hs-.d.ts +12 -0
  257. package/dist/style-CTm1AXDT.js +22 -0
  258. package/dist/subject-C6dLxqHU.js +24 -0
  259. package/dist/subject-settings-BwBmCdTR.js +128 -0
  260. package/dist/supabase/index.d.ts +183 -0
  261. package/dist/supabase/index.js +5 -0
  262. package/dist/supabase/middleware.d.ts +82 -0
  263. package/dist/supabase/middleware.js +102 -0
  264. package/dist/supabase-CwafwxxF.js +83 -0
  265. package/dist/supabase-DTBaC20O.js +774 -0
  266. package/dist/supabase-config-Bs44dare.js +472 -0
  267. package/dist/supabase-hook-BuxLQLpU.js +1814 -0
  268. package/dist/supabase-hook-D5qVd_D8.js +2 -0
  269. package/dist/supautils-DYZnzRWV.js +91 -0
  270. package/dist/svelte/PermissionBoundary.svelte +37 -0
  271. package/dist/svelte/Protected.svelte +40 -0
  272. package/dist/svelte/index.d.ts +13 -0
  273. package/dist/svelte/index.js +107 -0
  274. package/dist/svelte/runtime.d.ts +7 -0
  275. package/dist/svelte/runtime.js +4 -0
  276. package/dist/svelte/source.d.ts +13 -0
  277. package/dist/svelte/source.js +11 -0
  278. package/dist/terminal/index.d.ts +163 -0
  279. package/dist/terminal/index.js +833 -0
  280. package/dist/testing/index.d.ts +745 -0
  281. package/dist/testing/index.js +3672 -0
  282. package/dist/testing/saas/index.d.ts +194 -0
  283. package/dist/testing/saas/index.js +3 -0
  284. package/dist/testing/saas/permissions.d.ts +2 -0
  285. package/dist/testing/saas/permissions.js +2 -0
  286. package/dist/text-diff-BdkkYSbi.js +15 -0
  287. package/dist/thenable-okc2xDsj.js +10 -0
  288. package/dist/timeout-D51niuQP.js +8 -0
  289. package/dist/token-CIWxJOCh.js +50 -0
  290. package/dist/trpc/index.d.ts +61 -0
  291. package/dist/trpc/index.js +122 -0
  292. package/dist/types-9AWO9EGt.d.ts +197 -0
  293. package/dist/types-BPxgszlT.d.ts +550 -0
  294. package/dist/types-BVyImy5I.d.ts +120 -0
  295. package/dist/types-Bvc4oWT6.d.ts +9 -0
  296. package/dist/types-C3s9xVJG.d.ts +48 -0
  297. package/dist/types-Dnemwd77.d.ts +183 -0
  298. package/dist/types-kxznc7cn.d.ts +52 -0
  299. package/dist/types-vKyAMtcX.d.ts +157 -0
  300. package/dist/types-vNSDF7Dq.d.ts +66 -0
  301. package/dist/types-yF5LmQVD.d.ts +306 -0
  302. package/dist/unplugin/index.d.ts +7 -0
  303. package/dist/unplugin/index.js +31 -0
  304. package/dist/usage-D07N9QJ3.js +42 -0
  305. package/dist/usage-D82l1CwK.js +2 -0
  306. package/dist/usage-DG1oeCca.js +693 -0
  307. package/dist/validation-error-CnjZ4t92.js +33 -0
  308. package/dist/validation-error-CtF23SrC.d.ts +26 -0
  309. package/dist/validity-uOp8UpYn.js +245 -0
  310. package/dist/verifier-BxBo6I44.js +466 -0
  311. package/dist/version-BvacVTZR.js +17 -0
  312. package/dist/views-Soe7bGSC.js +95 -0
  313. package/dist/vocabulary-DA8a8zif.d.ts +56 -0
  314. package/dist/vue/index.d.ts +79 -0
  315. package/dist/vue/index.js +196 -0
  316. package/dist/watch-Etoa_Mvr.js +2 -0
  317. package/dist/watch-UihESpsJ.js +128 -0
  318. package/dist/webmcp/index.d.ts +75 -0
  319. package/dist/webmcp/index.js +307 -0
  320. package/package.json +450 -5
  321. package/rulesets/permdock.yaml +29 -0
  322. package/schemas/approval-request-v1.json +175 -0
  323. package/schemas/catalog-v1.json +535 -0
  324. package/schemas/openapi/oas-3.1.json +1298 -0
  325. package/schemas/openapi/oas-3.2.json +1516 -0
  326. package/schemas/openapi/overlay-1.1.json +79 -0
  327. package/schemas/supabase-claims-v1.json +144 -0
  328. package/schemas/supabase-manifest-v1.json +361 -0
  329. package/skills/permdock/SKILL.md +66 -0
  330. package/skills/permdock-agents/SKILL.md +77 -0
  331. package/skills/permdock-agents/references/runtimes.md +213 -0
  332. package/skills/permdock-approvals/SKILL.md +80 -0
  333. package/skills/permdock-approvals/references/stores-and-resume.md +94 -0
  334. package/skills/permdock-audit/SKILL.md +67 -0
  335. package/skills/permdock-credentials/SKILL.md +55 -0
  336. package/skills/permdock-credentials/references/tokens-keys-links.md +132 -0
  337. package/skills/permdock-data/SKILL.md +57 -0
  338. package/skills/permdock-data/references/queries-and-rls.md +62 -0
  339. package/skills/permdock-data/references/relationships.md +67 -0
  340. package/skills/permdock-tenancy/SKILL.md +80 -0
  341. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +89 -0
  342. package/skills/permdock-wire/SKILL.md +116 -0
  343. package/skills/permdock-wire/references/adapters.md +492 -0
  344. package/skills/permdock-wire/references/policy-options.md +45 -0
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: permdock-agents
3
+ description: Guards AI agent tool calls with PermDock subjects, actors and delegation. Use when wiring tool maps in permdock/ai-sdk, permdock/claude-agent, permdock/openai, permdock/eve or permdock/mcp, separating the user (principal) from the agent (actor), declaring a delegation option or policy delegations, publishing an A2A Agent Card with permdock/a2a, registering WebMCP tools with permdock/webmcp, verifying Web Bot Auth signers as actors, or when agent calls are denied with no-delegation or not-delegated.
4
+ license: MIT
5
+ metadata:
6
+ author: ScaleDockHQ
7
+ homepage: https://permdock.com/docs/security/delegation
8
+ repository: https://github.com/ScaleDockHQ/permdock
9
+ ---
10
+
11
+ # PermDock agents
12
+
13
+ An agent call has a principal (the user whose grants are evaluated), an actor (the agent making the call) and a delegation (what the user handed over). The decision is the principal's grants intersected with the delegation ([delegation](https://permdock.com/docs/security/delegation)). Set up `permissions.ts`, `policy.ts` and the factory first with the `permdock-wire` skill (`npx skills add ScaleDockHQ/PermDock --skill permdock-wire`).
14
+
15
+ ## Inputs (find out, or ask before starting)
16
+
17
+ - Runtimes in use: AI SDK, Claude Agent SDK, OpenAI Agents SDK, Eve, an MCP server, an A2A agent, WebMCP tools in a web page, signed bot traffic over HTTP.
18
+ - Where the principal comes from: the host session, a verified token, the runtime's session (Eve).
19
+ - Where the actor id comes from: runtime context, `authInfo.clientId`, an Agent Card, a request signature.
20
+ - What the user delegates, per call (scopes, `authorizationDetails`, GNAP `access`) or as standing policy ("members let the Eve agent read and update posts").
21
+ - Which tools are destructive or move money.
22
+
23
+ ## Invariants
24
+
25
+ 1. Subject and actor come from the host session or verified transport auth, never from tool arguments, a task body, a header the client sets, or model output.
26
+ 2. An actor with no delegation is denied every check with `no-delegation`. Delegate explicit scopes, never the principal's whole role.
27
+ 3. Delegation only narrows. A policy delegation is a ceiling, a token delegation on the same call must also cover the permission, and a `deny` or an `approval` still applies.
28
+ 4. One permission per tool. An instance action loads its row through `data` from parsed arguments; a collection permission (`list`, `create`) on a handler that reads `args.id` is a bug.
29
+ 5. Tool lists are hints. `capabilityMiddleware`, `guardTools`, MCP list filtering, the A2A extended card, `mayUse` and WebMCP registration hide tools; the server adapter still decides each call.
30
+ 6. A failed Web Bot Auth signature throws `InvalidSignatureError`; it never becomes an anonymous actor.
31
+ 7. Protocol rules come from the spec skills (`npx skills add ScaleDockHQ/scaledock-skills --skill <name>`): `mcp-authorization` for MCP auth, `a2a` for Agent Cards, `webmcp` for page tools, `web-bot-auth` for signatures, `owasp-agentic` for the threat checklist.
32
+
33
+ ## Workflow
34
+
35
+ 1. **Name the three parts per surface.** Write down the principal source, the actor source and the delegation source for each runtime from the Inputs. The adapter table on [delegation](https://permdock.com/docs/security/delegation#how-adapters-fill-actor-and-delegation) shows what each adapter fills itself (MCP and A2A read token scopes; in-process runtimes take a `delegation` option).
36
+ ✓ Every surface has all three, and none reads from model-controlled input.
37
+ 2. **Map the tools.** Use the runtime's `createPermDock` with `subject`, `actor` and a `tools` map, and pass the returned hook to the runtime. -> [references/runtimes.md](references/runtimes.md)
38
+ ✓ Every tool the agent can call has a `permission`, and every instance permission has a `data` loader that parses its arguments.
39
+ 3. **Delegate.** Per call, return `{ scopes: [permissions.post.update.scope] }` (or `authorizationDetails`) from the `delegation` option. As standing policy, declare it once on the policy:
40
+
41
+ ```ts
42
+ definePolicy(permissions, {
43
+ roles: [member],
44
+ delegations: [
45
+ {
46
+ from: roles.member,
47
+ to: actor("eve"),
48
+ permissions: [permissions.post.read, permissions.post.update],
49
+ },
50
+ ],
51
+ subject,
52
+ });
53
+ ```
54
+
55
+ `from` takes a role, `authenticated()`, a plan or `assurance()`. `to` is an actor kind, or `{ kind, id }` for one agent. A permission outside the ceiling is denied with `not-delegated`.
56
+ ✓ The agent is granted a delegated tool, and denied with `not-delegated` or `no-delegation` for anything else.
57
+
58
+ 4. **Gate destructive tools.** Put `approval: { by }` on each destructive or money-moving grant and pass a `store` to the adapter. Follow the `permdock-approvals` skill (`npx skills add ScaleDockHQ/PermDock --skill permdock-approvals`).
59
+ ✓ A destructive tool returns the runtime's approval outcome instead of running.
60
+ 5. **Wire the agent-facing surfaces.** A2A: serve `agentCard()` publicly, `extendedAgentCard(auth)` behind auth, and `protectSkill` on the task endpoint. WebMCP: `registerTools` from a snapshot, with the server re-checking. Web Bot Auth: `webBotAuth: (request) => verifyWebBotAuth(request, { verify: true, keys })` on the HTTP adapter. -> [references/runtimes.md](references/runtimes.md)
61
+ ✓ Each surface passes the Verify list of its spec skill.
62
+ 6. **Test.** Add scenario tests with `permdock/testing` for one delegated grant, one `not-delegated` denial and one approval per destructive tool ([scenario testing](https://permdock.com/docs/guides/scenario-testing)). Then review with the `permdock-audit` skill, which reports ASI02 and ASI03 per adapter.
63
+ ✓ The tests pass and the audit has no blocker.
64
+
65
+ ## Verify before done
66
+
67
+ - [ ] No `subject`, `actor`, tenant or approval token is read from tool arguments, a task body or model output.
68
+ - [ ] Every in-process agent adapter sets `delegation`, or a policy `delegations` entry covers its actor kind.
69
+ - [ ] Every instance tool has a `data` loader; no collection permission guards a handler that reads one row.
70
+ - [ ] MCP servers set `resource` to their own URL; Claude Agent `mcp__` tools come only from `mcpSources`.
71
+ - [ ] WebMCP and capability filtering are backed by a server-side check on the same permission.
72
+ - [ ] Every destructive tool returns `approval-required`, and the `permdock-approvals` Verify list passes.
73
+
74
+ ## Reference index
75
+
76
+ - [references/runtimes.md](references/runtimes.md): factory shapes and outcome mapping for AI SDK, Claude Agent SDK, Eve, OpenAI Agents SDK, MCP, A2A, WebMCP, Web Bot Auth and `permdock/terminal`.
77
+ - Docs: [delegation](https://permdock.com/docs/security/delegation), [subject](https://permdock.com/docs/concepts/subject), [OWASP Agentic mapping](https://permdock.com/docs/security/owasp-agentic), adapter pages under `https://permdock.com/docs/adapters/<name>`.
@@ -0,0 +1,213 @@
1
+ # Agent runtimes
2
+
3
+ Every agent adapter exports `createPermDock` from `permdock/<runtime>` and takes `subject`, `actor`, `delegation`, `tools` and `store`. One permission per tool. Instance actions load the row through `data`; tool input arrives as `unknown`, so parse it before loading. The current adapter page is `https://permdock.com/docs/adapters/<name>`.
4
+
5
+ ## AI SDK — `permdock/ai-sdk`
6
+
7
+ ```ts
8
+ import { createPermDock } from "permdock/ai-sdk";
9
+ import { z } from "zod";
10
+
11
+ const PostArgs = z.object({ id: z.string() });
12
+
13
+ export const { toolApproval, capabilityMiddleware, needsApproval } =
14
+ createPermDock(policy, {
15
+ subject: ({ runtimeContext }) => runtimeContext.user,
16
+ actor: ({ runtimeContext }) => ({
17
+ id: runtimeContext.agentId,
18
+ kind: "ai-sdk",
19
+ }),
20
+ delegation: () => ({ scopes: [permissions.post.delete.scope] }),
21
+ tools: {
22
+ delete_post: {
23
+ permission: permissions.post.delete,
24
+ data: (args) => loadPost(PostArgs.parse(args).id),
25
+ },
26
+ },
27
+ });
28
+ ```
29
+
30
+ Pass `toolApproval` into `generateText` / `ToolLoopAgent`. Wrap the model with `wrapLanguageModel({ model, middleware: capabilityMiddleware({ user }) })` per caller. Use `needsApproval(permissions.post.delete)` only on `WorkflowAgent`. `denied` maps to `'denied'`, `approval-required` to `'user-approval'`, and `'not-applicable'` is never returned ([AI SDK adapter](https://permdock.com/docs/adapters/ai-sdk)). Tools without a permission are hidden and denied unless `unmapped: 'allow'`; to combine PermDock with the app's own confirmation, pass `composeToolApproval(appApproval)` as `toolApproval` instead of wrapping the adapter: the app's function is asked only for calls PermDock grants, and its answer passes through (`undefined` stays not applicable; an unreadable answer denies).
31
+
32
+ ## Claude Agent SDK — `permdock/claude-agent`
33
+
34
+ ```ts
35
+ import { createPermDock } from "permdock/claude-agent";
36
+
37
+ export const { canUseTool, permissionRequestHook } = createPermDock(policy, {
38
+ subject: () => user,
39
+ actor: () => ({ id: "claude", kind: "claude-agent" }),
40
+ delegation: () => ({ scopes: [permissions.post.delete.scope] }),
41
+ tools: {
42
+ delete_post: {
43
+ permission: permissions.post.delete,
44
+ data: (args) => loadPost(args),
45
+ },
46
+ },
47
+ });
48
+ ```
49
+
50
+ `canUseTool` returns `{ behavior: 'allow', updatedInput }` or `{ behavior: 'deny', message }`; approval-required is a deny whose message carries the pending token. Pass a `store`: once a reviewer approves, the retried call is allowed exactly once. `mcp__` tools are trusted only from `mcpSources` (default `['sdk']`). Pass `permissionRequestHook` under `hooks.PermissionRequest` ([Claude Agent adapter](https://permdock.com/docs/adapters/claude-agent)).
51
+
52
+ ## Eve — `permdock/eve`
53
+
54
+ ```ts
55
+ import { createPermDock } from "permdock/eve";
56
+
57
+ export const { approval, approvalFor, permdock } = createPermDock(policy, {
58
+ delegation: () => ({ scopes: [permissions.post.delete.scope] }),
59
+ tools: {
60
+ delete_post: {
61
+ permission: permissions.post.delete,
62
+ data: (args) => loadPost(args),
63
+ },
64
+ },
65
+ });
66
+ ```
67
+
68
+ Pass `approval` as the tool's `approval`. Default subject and actor read `session.auth.initiator` / `current`. `approval.request(ctx)` maps granted to Eve's `not-applicable` (continue), approval-required to `user-approval`, denied to `{ type: 'denied', reason }`; Eve's re-check of a call nobody approved is denied. `approval.response(ctx)` maps the responder through the same `subject`. Use a durable `store` when replicas share sessions ([Eve adapter](https://permdock.com/docs/adapters/eve)).
69
+
70
+ ## OpenAI Agents SDK — `permdock/openai`
71
+
72
+ ```ts
73
+ import { createPermDock } from "permdock/openai";
74
+
75
+ export const { needsApproval, guardTools, resolveInterruptions, permdock } =
76
+ createPermDock(policy, {
77
+ subject: (ctx) => ctx.user,
78
+ actor: (ctx) => ({ id: ctx.agentId, kind: "openai" }),
79
+ delegation: (ctx) => ({ scopes: ctx.scopes }),
80
+ tools: {
81
+ delete_post: {
82
+ permission: permissions.post.delete,
83
+ data: (args) => loadPost(args),
84
+ },
85
+ },
86
+ });
87
+ ```
88
+
89
+ `needsApproval(permission)` is the tool's `needsApproval`; it reads `runContext.context` and is true unless granted. `guardTools` drops tools with no grant. `resolveInterruptions(state, interruptions, { context })` approves or rejects each pause and returns the still-pending `ApprovalRequest`s. On resume, rebuild the context from the session and use `RunState.fromStringWithContext` ([OpenAI adapter](https://permdock.com/docs/adapters/openai)).
90
+
91
+ ## MCP — `permdock/mcp`
92
+
93
+ ```ts
94
+ import { createPermDock } from "permdock/mcp";
95
+
96
+ export const { protectServer } = createPermDock(policy, {
97
+ subject: (authInfo) => userFrom(authInfo), // or subjectFromMcp
98
+ requireAuthInfo: true, // HTTP behind bearer auth
99
+ store,
100
+ });
101
+
102
+ const server = protectServer(
103
+ new McpServer({ name: "posts", version: "1.0.0" }),
104
+ );
105
+ server.registerTool(
106
+ "delete_post",
107
+ {
108
+ permission: permissions.post.delete,
109
+ inputSchema: z.object({ id: z.string() }),
110
+ data: ({ id }) => loadPost(id),
111
+ },
112
+ handler,
113
+ );
114
+ ```
115
+
116
+ `actor.kind` is `'mcp-client'` unless `actorKind` sets another (`'oauth-client'` makes the token the same actor kind `permdock/supabase` and `permdock/jwt` give it, so one delegation covers both), and `delegation` comes from the token's scopes and `authorization_details`. Every `registerTool` / `registerResource` / `registerPrompt` needs a `permission`. Lists are filtered per caller. A missing scope is an `insufficient_scope` step-up (HTTP `403`) naming only the scope the call needs. When the authorization server issues coarse scopes (`mcp:read`), declare them in `definePolicy({ oauthScopes })` instead of expanding scopes yourself: they are expanded into the delegation and named in challenges. Set `resource` to the server's URL so tokens for another audience are refused. Denied calls return `isError: true` with Decision `structuredContent`. `approval-required` parks the call in `store` and returns `isError: true` with the token, or an `input_required` URL request when `approval.at` is set and the client declares URL elicitation; the retried call runs once after approval (token optional under `_meta["dev.permdock/approval"]`, never from tool arguments) ([MCP adapter](https://permdock.com/docs/adapters/mcp)).
117
+
118
+ An MCP server that is not an SDK `McpServer` cannot be wrapped: narrow the tool's `meta` with `isPermission`, filter its tool list with `mayUse(permdock, permission)` from `permdock`, and decide each call with `permdock.decide(permission, args)` in its `authorize` hook. `mayUse` is a listing hint, never a decision.
119
+
120
+ When a tool's handler calls an oRPC procedure that runs `protect`, use `protectServer(server, { enforce: 'procedure', permissionFor: (name) => permissionOf(procedures.get(name)) })` with `permissionOf` from `permdock/orpc`. `tools/list` and annotations follow the permission; the call is not guarded, so each call decides once, in the procedure. `data` and `longRunning` throw in this mode, and the mode covers the whole server. When the same operations are REST routes, declare each permission once with `operationPermissions({ 'GET /customers/{id}': { permission, operationId } })` from `permdock/openapi` and pass `permissionFor: operations.forOperation`; the HTTP gate reads `operations.forRequest(method, path)` from the same object. When two operations share a permission but need different coarse scopes (reading an export with `mcp:read`, creating one with `mcp:write`), list the permission under both in `oauthScopes`, then narrow each operation with `oauthScopes: ['mcp:read']` on its entry (or on the tool config): pass `oauthScopesFor: operations.oauthScopesForOperation` to `protectServer`, and `operations` to `permdock/server` (or `protect`'s `oauthScopes`) for the REST routes, which then answer `insufficient_scope` naming that scope. A route that checks no permission but should still require a scope (a chat endpoint, setting the active organization) uses `protect(null, undefined, { oauthScopes: ['api:chat'] })` or an operation entry with only `oauthScopes`; never write an app-side rule that maps HTTP methods to scopes.
121
+
122
+ ## A2A — `permdock/a2a`
123
+
124
+ ```ts
125
+ import { createPermDock } from "permdock/a2a";
126
+
127
+ export const { agentCard, extendedAgentCard, protectSkill } = createPermDock(
128
+ policy,
129
+ {
130
+ subject: (auth) => userFrom(auth),
131
+ card: {
132
+ name: "Posts agent",
133
+ url: "https://agent.example.com/a2a",
134
+ version: "1.0.0",
135
+ },
136
+ securitySchemes: { oauth: { type: "oauth2" } },
137
+ skills: {
138
+ summarise: { permission: permissions.post.read },
139
+ publish: {
140
+ permission: permissions.post.publish,
141
+ data: (task) => loadPost(task.postId),
142
+ },
143
+ },
144
+ },
145
+ );
146
+
147
+ app.get("/.well-known/agent-card.json", (c) => c.json(agentCard()));
148
+ app.get("/a2a/extended-card", (c) => c.json(extendedAgentCard(c.get("auth"))));
149
+ app.post(
150
+ "/a2a/tasks",
151
+ protectSkill((task) => task.skillId),
152
+ handleTask,
153
+ );
154
+ ```
155
+
156
+ Identity comes from transport auth, never the task body. `actor.kind` is `'a2a'`, with `delegation` from the caller's token. Each skill's `securityRequirements` name the permission's `scope`. The public card is never filtered; `extendedAgentCard(auth)` keeps only skills the caller has a grant for. An instance-level skill without `data` throws at startup ([A2A adapter](https://permdock.com/docs/adapters/a2a)).
157
+
158
+ ## WebMCP — `permdock/webmcp`
159
+
160
+ Client entry. No factory. Register snapshot-allowed tools on `document.modelContext`:
161
+
162
+ ```ts
163
+ import { registerTools } from "permdock/webmcp";
164
+ import { approvalHeaders, usePermDock } from "permdock/react";
165
+
166
+ const permdock = usePermDock();
167
+ const controller = new AbortController();
168
+ registerTools(document.modelContext, permissions.post, {
169
+ permdock,
170
+ signal: controller.signal,
171
+ handlers: {
172
+ update: async ({ input, token }) =>
173
+ api.posts.update(input, { headers: approvalHeaders(token) }),
174
+ },
175
+ });
176
+ ```
177
+
178
+ Never import a policy into this entry. A missing `document.modelContext` is a no-op. Tools re-register when the snapshot changes. The client check is a filter: the server adapter behind `api.posts` makes the decision. `onApprovalRequired` opens the page's own confirmation dialog ([WebMCP adapter](https://permdock.com/docs/adapters/webmcp)).
179
+
180
+ ## Web Bot Auth on HTTP adapters
181
+
182
+ ```ts
183
+ import {
184
+ createPermDock,
185
+ discoverViaSignatureAgent,
186
+ verifyWebBotAuth,
187
+ } from "permdock/hono";
188
+
189
+ const keys = discoverViaSignatureAgent({ allow: ["agents.example.com"] });
190
+
191
+ export const { permdock, protect } = createPermDock(policy, {
192
+ subject: (c) => c.get("user"),
193
+ webBotAuth: (request) => verifyWebBotAuth(request, { verify: true, keys }),
194
+ });
195
+ ```
196
+
197
+ A verified RFC 9421 signer becomes the actor with `kind: 'web-bot-auth'`; `delegation` comes from a bearer token on the same request, otherwise it is empty. A failed signature throws `InvalidSignatureError`, never an anonymous actor ([Web Bot Auth](https://permdock.com/docs/standards/web-bot-auth)).
198
+
199
+ ## Terminal — `permdock/terminal`
200
+
201
+ ```ts
202
+ import { createPermDock } from "permdock/terminal";
203
+
204
+ export const { permdock, protect, filterCommands, format, exitCode } =
205
+ createPermDock(policy, {
206
+ subject: async ({ token }) => {
207
+ const jwt = await token(["env", "keychain", "ci-oidc", "device"]);
208
+ return jwt ? subjectFromJwt(jwt, { issuer, audience: "acme-cli" }) : null;
209
+ },
210
+ });
211
+ ```
212
+
213
+ For the app's own CLI; not the `permdock` binary. Never accept `--user` or `--actor` as identity. In CI, verify the job token with `subjectFromCiOidc(jwt, { provider: 'github', audience })` from `permdock/jwt`, which returns a `workload` principal, never a user. A `destructive` permission asks for the resource id to be typed, and exits `64` without a terminal unless `--yes` is passed; `--yes` never approves an `approval-required` call. `--dry-run` decides and exits with the outcome's code without running the action ([terminal adapter](https://permdock.com/docs/adapters/terminal)).
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: permdock-approvals
3
+ description: Requires human approval on PermDock grants and resumes the approved call. Use when adding approval to a destructive or money-moving action (delete, publish, pay, refund, approve, transfer), choosing approvers with approval by, user(), relation(), distinct, quorum, ttl, escalation or staleOn, ordering approvers with mode and stages, loading approval rules as data with an ApprovalPolicySource, separating preparer and approver with exclusiveWith, mounting approvalsHandler with an ApprovalStore, resuming with the PermDock-Approval header, mapping approval-required to an agent runtime, MCP elicitation or AG-UI interrupt, or fixing doctor PD017, PD018 or PD024.
4
+ license: MIT
5
+ metadata:
6
+ author: ScaleDockHQ
7
+ homepage: https://permdock.com/docs/security/approvals
8
+ repository: https://github.com/ScaleDockHQ/permdock
9
+ ---
10
+
11
+ # PermDock approvals
12
+
13
+ `approval-required` is the third outcome: the subject may do it, but a person other than the requester confirms each call. An approval never grants something the subject could not do, and a `deny` still wins ([approval security](https://permdock.com/docs/security/approvals)). Set up the policy and factory first with the `permdock-wire` skill (`npx skills add ScaleDockHQ/PermDock --skill permdock-wire`).
14
+
15
+ ## Inputs (find out, or ask before starting)
16
+
17
+ - Which actions need a person: deletions, publishing, payments, refunds, access changes, anything an agent can repeat.
18
+ - Who may approve each one (a role, a plan, `assurance()`, one named user, or a relation on the row such as the expense's manager), how many approvers (quorum), whether several groups sign in order, how long an ask may wait, and who steps in when nobody answers.
19
+ - Whether organisations configure their own approval rules at runtime ("payments over 1000 need finance").
20
+ - Whether the approver judges the row's content (an amount, a text), so a changed row needs a new approval.
21
+ - Whether a user confirming their own agent's call is the intent anywhere.
22
+ - Where pending requests live: one process (`memoryApprovalStore`), the app's database, or PermDock Cloud.
23
+ - Which runtimes ask: HTTP, AI SDK, Claude Agent SDK, Eve, OpenAI Agents SDK, MCP, A2A, WebMCP, a chat UI.
24
+
25
+ ## Invariants
26
+
27
+ 1. The requester never approves by default. The actor is always refused. The principal is refused unless the grant sets `distinct: false`, and `requireDistinctApprover` on the handler refuses it even then.
28
+ 2. The approver comes from authentication on the approval surface, never from the request body, a tool argument or a link.
29
+ 3. One approval resumes one call. The token binds permission, resource, subject and actor, the resume re-runs `decide`, and `consume` spends the approval once.
30
+ 4. `can` returns `false` for `approval-required`. Only `decide`, `assert` and the adapters see the third outcome.
31
+ 5. Quorum, ttl and escalation live on the grant, not in the store. A grant's `ttl` can shorten the store's window, never extend it.
32
+ 6. A `relation()` approver is matched only through facts the approvals handler reads from the app's `RelationSource` at verdict time, never from a request body.
33
+ 7. Approval rules kept as data only add stages. A source that fails to load denies every call an allow would grant.
34
+
35
+ ## Workflow
36
+
37
+ 1. **Mark the grants.** Add `approval: 'human'` (anyone authenticated but the requester) or `approval: { by }` with the same selectors as `to`:
38
+
39
+ ```ts
40
+ allow(permissions.payout.release, {
41
+ approval: {
42
+ by: roles.finance,
43
+ quorum: 2,
44
+ ttl: "30m",
45
+ escalation: { after: "4h", to: roles.owner },
46
+ },
47
+ });
48
+ ```
49
+
50
+ For sign-off in order, use `mode: 'sequential'` with `stages: [{ by, quorum? }]` (or `mode: 'all'` for any order); one person signs one stage. `user('u_cfo')` names one person, and `relation(permissions.expense, 'manager')` names whoever holds the relation on the row.
51
+ Add `distinct: false` only where the user confirms their own agent's call and no money, deletion or access change sits behind it.
52
+ ✓ `permdock doctor` reports no PD017 (sensitive verb without approval), and every PD024 (`distinct: false`) is deliberate.
53
+
54
+ 2. **Bind content when it matters.** Declare `version: 'updatedAt'` (or a revision counter) on the `resource()` and add `staleOn: 'resource-change'` to the approval, so a changed row resumes as `stale-approval` and asks again.
55
+ ✓ `definePolicy` accepts the grant (it throws when the resource has no `version` or the action is a collection action).
56
+ 3. **Split workflow verbs.** Model `prepare`, `approve`, `pay`, `settle` and `submit` as separate leaves. Put the approval on `approve` / `pay`, and use `exclusiveWith` on `role()` so a preparer cannot also hold the approver role.
57
+ ✓ `permdock doctor` reports no PD018 (exclusive roles held together).
58
+ 4. **Store and handler.** Create one `ApprovalStore`, pass it as `store` to every adapter, and mount `approvalsHandler(store, { subject })` for approvers; add `relations` and `permissions` when a grant uses `relation()` approvers. -> [references/stores-and-resume.md](references/stores-and-resume.md)
59
+ ✓ A second authenticated user can approve; the actor and the requester get `403`.
60
+ 5. **Rules as data (optional).** Pass `approvalPolicies` (an `ApprovalPolicySource`, `memoryApprovalPolicies` in process) to `createPermDock` for approval rules organisations configure. -> [references/stores-and-resume.md](references/stores-and-resume.md#approval-policies-as-data)
61
+ ✓ `testApprovalPolicySource` from `permdock/testing` passes on the source.
62
+ 6. **Surface and resume.** Map the outcome to each runtime's approval hook and resume with the token: the `PermDock-Approval` header over HTTP, the runtime's own resume for agents. -> [references/stores-and-resume.md](references/stores-and-resume.md#surfaces)
63
+ ✓ The approved call runs once; a second resume is denied with `approval-consumed`.
64
+ 7. **Test.** Add a scenario test per approval grant: the ask, an approval by an eligible user, a refused self-approval, and the single resume ([scenario testing](https://permdock.com/docs/guides/scenario-testing)). Run `testApprovalStore` on a custom store.
65
+ ✓ The tests pass.
66
+
67
+ ## Verify before done
68
+
69
+ - [ ] Every destructive or money-moving action an agent or a user can trigger has `approval`, or a `deny` on the same leaf.
70
+ - [ ] Each `distinct: false` (PD024) is a deliberate self-confirmation with nothing irreversible behind it.
71
+ - [ ] Roles that must not be held together set `exclusiveWith`, and PD018 is clean.
72
+ - [ ] The store survives the deployment: no `memoryApprovalStore` on serverless or across replicas.
73
+ - [ ] No resume token is read from model output or tool arguments, and each resume re-runs `decide`.
74
+ - [ ] A custom `ApprovalStore` passes `testApprovalStore` from `permdock/testing`.
75
+ - [ ] Grants with `relation()` approvers have a handler with `relations`, and nothing fills `verdict.relations` from client input.
76
+
77
+ ## Reference index
78
+
79
+ - [references/stores-and-resume.md](references/stores-and-resume.md): `approvalsHandler` routes, custom stores, relation approvers, approval policies as data, the token, resume details and denial causes, the runtime surfaces table, delivery to chat and email.
80
+ - Docs: [approval security](https://permdock.com/docs/security/approvals), [approvals adapter](https://permdock.com/docs/adapters/approvals), [wire formats](https://permdock.com/docs/concepts/wire-formats).
@@ -0,0 +1,94 @@
1
+ # Approval stores and the resume flow
2
+
3
+ The API and the store contract are on the [approvals adapter](https://permdock.com/docs/adapters/approvals); the token and the runtime table are on [approval security](https://permdock.com/docs/security/approvals).
4
+
5
+ ## Store and handler
6
+
7
+ ```ts
8
+ import { approvalsHandler, memoryApprovalStore } from "permdock/approvals";
9
+
10
+ export const store = memoryApprovalStore({ ttl: 60 * 60 * 1000 }); // default: one hour
11
+
12
+ // Every agent and HTTP adapter takes the same option
13
+ export const { protect } = createPermDock(policy, { subject, store });
14
+
15
+ // Routes for approvers: GET /pending, GET /mine, GET /:token, POST /:token/approve, POST /:token/reject
16
+ const handler = approvalsHandler(store, {
17
+ subject: (request) => approverFromSession(request), // the approver, from real authentication
18
+ requireDistinctApprover: false, // true refuses the principal even where a grant sets distinct: false
19
+ relations, // RelationSource: needed when a grant uses relation() approvers
20
+ permissions, // the permission tree, for relation approvers reached through links
21
+ });
22
+ app.all("/permdock/approvals/*", (c) => handler(c.req.raw));
23
+ ```
24
+
25
+ The store's `ttl` is the window a request stays open when the call sets none; `resumeDecision({ ..., ttl })` sets it per call (milliseconds). A grant's `approval.ttl` only shortens either.
26
+
27
+ - `memoryApprovalStore` is per process. On serverless or with several replicas, implement `ApprovalStore` over the app's database; the Drizzle recipe on the adapter page is the template, and `cloud().approvals` from `permdock/cloud` is the hosted implementation of the same interface. On Postgres or Supabase, set `rls.approvals: true` and use `supabaseApprovalStore(admin, { onOpen })` from `permdock/supabase` over the generated table and functions; grant the functions to the server's role, and put notifications and superseding older requests in `onOpen`.
28
+ - A custom store calls `assertApprover(request, by, requireDistinct, now, verdict.relations)` in `resolve` and computes the next record with `applyApprovalVerdict`, which records each signature's `stage`. `verdict.relations` holds `approverRelationKey` values that the handler, or `resolveApproval(store, token, verdict, { relations, permissions })`, computed with `approverRelations`; a store never derives them. `consume` must be atomic: two concurrent calls never both succeed.
29
+ - When the app's own rules decide who approves (a manager chain, delegates, a quorum it counts), record their verdict with `vouchApproval(store, token, { status, by, rule })` from `permdock/approvals` instead of mirroring those rules in `approvers` or copying the verdict into the request by hand. It resolves the request at once and records `vouched: rule`; the actor, requester and repeat checks still apply. Never take `vouched` from a request body.
30
+ - Run `testApprovalStore(store, { reopen })` from `permdock/testing` on every custom store.
31
+ - `cancelApprovals` rejects matching pending requests (for example when a session ends).
32
+
33
+ ## Approval policies as data
34
+
35
+ ```ts
36
+ import { memoryApprovalPolicies } from "permdock";
37
+
38
+ const permdock = await createPermDock(policy, user, {
39
+ approvalPolicies: memoryApprovalPolicies([
40
+ {
41
+ permission: "expense.pay",
42
+ tenant: "o_acme", // absent: every tenant
43
+ actors: ["agent"], // absent: every call
44
+ where: { op: "gt", field: "amount", value: 1000 },
45
+ approval: { by: "finance" }, // an escalation here widens only this entry's stages
46
+ },
47
+ ]),
48
+ });
49
+ ```
50
+
51
+ - Every adapter's `createPermDock` takes `approvalPolicies` (HTTP, MCP, A2A, AuthZEN, terminal and the agent adapters), so an entry also turns an agent call into `user-approval` or an MCP call into `approval-required`.
52
+ - Entries are read once per instance and combine with the grant's own approval as stages (`sequential` if any part is, else `all`); the shortest `ttl` wins.
53
+ - A throw or an invalid entry denies with detail `approval-policy-unavailable`. An unknown permission applies to nothing.
54
+ - `where` reads the row, so it works only on instance permissions; on a collection permission the entry does not load. Use `check` (the proposed row) there. Validate entries where they are saved with `validateApprovalPolicy(policy, entry)` (`{ ok: false, problem }` names `unknown-permission`, `invalid`, `where-on-collection` or `stale-on-without-version`).
55
+ - Client snapshots do not see the entries; the server's `decide` adds the approval.
56
+
57
+ ## Resume
58
+
59
+ 1. `decide` returns `approval-required` with a deterministic `token`. The adapter calls `store.create`; `create` is idempotent per token.
60
+ 2. The runtime surfaces the ask (table below). A person answers through `approvalsHandler`, the runtime's own UI or the Cloud inbox.
61
+ 3. The original call is retried with the token. The adapter re-runs `decide`, recomputes the token, compares it, and calls `store.consume`. Only then does the tool or route run.
62
+
63
+ The token binds the permission key, the resource id (or a digest of the data when there is no id), the principal, the tenant, the actor and the matched grant's conditions. Under `staleOn: 'resource-change'` it also binds the row's `version`.
64
+
65
+ A gate in your own code resumes the same way with `resumeDecision` from `permdock/approvals`, passing `token: carried ?? (await storedApprovalToken(store, decision))`: `storedApprovalToken` finds an approved or rejected request for the recomputed token, so the caller need not carry it. Do not reimplement it.
66
+
67
+ Over HTTP, the client retries the same request with the `PermDock-Approval: <token>` header. In React, `approvalHeaders(token)` builds that header and `useApproval(decision)` requests and polls an approval from the UI.
68
+
69
+ A bad resume is denied with detail `approval-not-found`, `approval-pending`, `approval-rejected`, `approval-expired`, `approval-consumed` or `approval-mismatch`; a changed row is `stale-approval`. `approvalsHandler` answers `403` for an ineligible approver (`approver-not-eligible`, the actor, or the principal without `distinct: false`) and `409` for a repeated one (`approver-repeated`).
70
+
71
+ ## Surfaces
72
+
73
+ | Runtime | Ask | Resume |
74
+ | --------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
75
+ | AI SDK `generateText` / `ToolLoopAgent` | `toolApproval` returns `'user-approval'` | The SDK's re-check of the same call is denied unless the store holds an approved record |
76
+ | AI SDK `WorkflowAgent` | `needsApproval(permission)` suspends the workflow | The workflow resumes; the token is recomputed |
77
+ | Claude Agent SDK | `canUseTool` denies with the token and records the request | The retry consumes the approved record once |
78
+ | Eve | `approval.request` returns `user-approval` | `approval.response` checks the responder |
79
+ | OpenAI Agents SDK | `needsApproval` pauses the run with `interruptions` | `resolveInterruptions` applies the store's verdict |
80
+ | MCP | `input_required` URL elicitation to `approval.at`, otherwise an `isError` result with the token | The retried call re-runs `decide` and consumes |
81
+ | HTTP adapters | `403` Problem Details, type `.../approval-required`, with `token` | Retry with `PermDock-Approval` |
82
+ | A2A | The task enters input-required with the token | The caller continues the task |
83
+ | WebMCP | The handler is not run; `onApprovalRequired` opens the page's dialog | The page calls the guarded route with the token |
84
+ | Chat UI over AG-UI | The backend emits the request as an AG-UI human-in-the-loop or custom event | The answer returns on the stream; the backend recomputes the token |
85
+
86
+ For AG-UI event shapes, defer to the `ag-ui` spec skill (`npx skills add ScaleDockHQ/scaledock-skills --skill ag-ui`).
87
+
88
+ ## Delivery
89
+
90
+ Notifying an approver is a listener on the `approval` event (`permdock.on('approval', …)`), never a package. A message or email carries only the token and links to a page where the approver signs in; the verdict goes through `approvalsHandler` with that session's subject. A link never approves on its own ([approvals adapter, Delivery](https://permdock.com/docs/adapters/approvals#delivery)).
91
+
92
+ ## Approvers
93
+
94
+ `by` takes roles, `user(id)`, `relation()`, `holder(permission)` and `anyOf(...)`. A list is all-of; use `anyOf(user(id), holder(permissions.x.approve))` for "this person or anyone who holds the permission". `holder()` needs `approvalsHandler(store, { permdockFor: (approver, request) => createPermDock(policy, approver, { customRoles }) })`, so custom roles count. Stages may carry their own `escalation`.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: permdock-audit
3
+ description: Reviews an existing PermDock setup or a pull request that changes it. Use when auditing permissions, reviewing a PR that touches permissions.ts, policy.ts or an adapter guard, finding ungranted or unused leaves, missing human approvals on agent tools, closures that should be portable, validate never, untested grants, or mapping an app to OWASP ASI02 and ASI03. Use whenever the user asks to audit, review or harden PermDock.
4
+ license: MIT
5
+ metadata:
6
+ author: ScaleDockHQ
7
+ homepage: https://permdock.com/docs/for-ai-agents
8
+ repository: https://github.com/ScaleDockHQ/permdock
9
+ ---
10
+
11
+ # Audit PermDock
12
+
13
+ Read the app's `permissions.ts`, `policy.ts`, factory file and every adapter guard, then write the findings as a Markdown checklist the user can turn into pull requests. Every finding names the file and the permission or role.
14
+
15
+ ## Inputs (find out, or ask before starting)
16
+
17
+ - The scope: the whole app, or one pull request against its base branch.
18
+ - Whether the `permdock` binary runs here (`permdock usage --json`, `permdock doctor --json`); otherwise walk the files and the catalog by hand.
19
+ - Which topic areas the app uses: agent runtimes, approvals, named scopes and custom roles, ORM queries or RLS, tokens, API keys or share links.
20
+
21
+ ## Invariants
22
+
23
+ 1. Report, never fix silently. Each finding is one line with a file and a permission or role.
24
+ 2. Anything that widens access or fails open leads the report.
25
+ 3. Expected outcomes in tests are written by hand, never computed by calling `decide` or `can`.
26
+ 4. A topic skill's Verify list is the checklist for its area; this skill does not repeat it.
27
+
28
+ ## Workflow
29
+
30
+ 1. **Inventory.** List every leaf from `definePermissions`, every role from `defineRoles` / `role()`, every plan from `definePlans`, and every grant from `definePolicy` (`grants`, role bindings, `delegations`). Note which entries import the policy (server, agent) and which import only the snapshot (client).
31
+ ✓ Every leaf, role, plan and grant is in the report, including leaves with zero grants.
32
+ 2. **Ungranted and unused.** A leaf no `allow` mentions is ungranted: grant it or delete it. A granted leaf never passed to `can`, `decide`, `assert`, `protect`, `tools` or `<Protected>` is unused; confirm with `usage --json` or a repo-wide search for the reference.
33
+ ✓ Every leaf is tagged granted and used, granted and unused, or ungranted.
34
+ 3. **Validation and identity.**
35
+ - `validate: 'never'` on a policy that sees HTTP bodies, MCP arguments or model output is a finding.
36
+ - Client entries (`permdock/react` and the client half of a framework adapter) import permissions and snapshots only.
37
+ - `subject` and `actor` come from the host session or `subjectFrom*`, never from tool arguments or a model-supplied id.
38
+ - A `limit` grant without `limits` on `createPermDock` always denies with `limit-unavailable`, and a mutation that only calls `can` never spends a unit.
39
+ - On Supabase, every `ctx.supabaseAdmin`, `withPostgresAdminClient` or `service_role` client sits behind `permdock.assert(...)` (or `withPermDock({ protect })`), and `subjectFromSupabase` receives `getClaims()` output or `ctx.jwtClaims`, never `getSession().access_token` or `user_metadata`.
40
+ ✓ Each check has a pass or a file-level finding.
41
+ 4. **Topic checks.** For each area the app uses, run the Verify list of its skill (`npx skills add ScaleDockHQ/PermDock --skill <name>`): `permdock-agents`, `permdock-approvals`, `permdock-tenancy`, `permdock-data`, `permdock-credentials`.
42
+ ✓ Every item of every applicable Verify list has a pass or a finding.
43
+ 5. **Tests.** No `describePolicy` suite, or one with `exhaustive: false`, is a finding: a new permission can ship untested. A custom `ApprovalStore`, `DecisionSink`, `MembershipSource`, `RoleSource`, `SnapshotSource`, `LimitStore`, `RelationSource` or `DirectoryStore` without its `test<Interface>` runner from `permdock/testing` is a finding.
44
+ ✓ Every policy and custom store has its runner, or a finding.
45
+ 6. **ASI02 and ASI03.** Map the inventory onto [OWASP Agentic](https://permdock.com/docs/security/owasp-agentic); for the threat list itself, defer to the `owasp-agentic` spec skill (`npx skills add ScaleDockHQ/scaledock-skills --skill owasp-agentic`).
46
+ - **ASI02** (tool misuse): unmapped tools, a collection permission on a row handler, a destructive tool without `approval`, capability lists that include denied tools.
47
+ - **ASI03** (identity and privilege abuse): a model-supplied subject or actor, an actor used as approver, `distinct: false` on a sensitive action, a resume token not bound to permission, resource, subject and actor, API keys stored in plain text or created outside `decideCredential`, keys without expiry (PD029), and a `memberships` source applied to service-key subjects.
48
+ ✓ Each agent adapter in the app has an ASI02 line and an ASI03 line.
49
+ 7. **Pull request review**, when the scope is one PR. Review only what the diff changes:
50
+ - Run `permdock collect --check` and `permdock usage --json` on both sides and compare. New leaves with no grant, grants for leaves nobody checks, and new `undeclared` or `outsideInclude` findings are comments.
51
+ - A new or widened `allow` (a role gains a leaf, a `where` is dropped or loosened, a `to` becomes `anyone()` or `authenticated()`), a new policy `delegations` entry, or a removed `deny` needs a matching `describePolicy` row in the same PR.
52
+ - A new model-reachable tool, or a new destructive action on an existing one, goes through the `permdock-agents` and `permdock-approvals` Verify lists.
53
+ - A change to the `rls` config or generated RLS comes with `permdock rls generate --check` passing and an `rlsParity` or `rls verify` run.
54
+ - `permdock doctor --json` on the head branch reports no new error.
55
+ ✓ Every changed grant, leaf and tool has a comment on the changed line or an explicit pass.
56
+
57
+ ## Verify before done
58
+
59
+ - [ ] The report leads with blockers: fail-open paths, a trusted model subject, a destructive tool without approval.
60
+ - [ ] Then ungranted and unused leaves, then closures that could be portable, then test gaps.
61
+ - [ ] Every finding names a file and a permission or role.
62
+ - [ ] The report ends with suggested PRs, one finding per PR when the fixes are independent.
63
+
64
+ ## Reference index
65
+
66
+ - Topic Verify lists: the `permdock-agents`, `permdock-approvals`, `permdock-tenancy`, `permdock-data` and `permdock-credentials` skills.
67
+ - Docs: [OWASP Agentic mapping](https://permdock.com/docs/security/owasp-agentic), [threat model](https://permdock.com/docs/security/threat-model), [`permdock doctor`](https://permdock.com/docs/cli/doctor), [`permdock usage`](https://permdock.com/docs/cli/usage), [testing](https://permdock.com/docs/adapters/testing).
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: permdock-credentials
3
+ description: Resolves PermDock subjects from access tokens, API keys and share links. Use when verifying OAuth or OIDC access tokens with subjectFromJwt or an introspection response with subjectFromIntrospection, issuing personal access tokens, API keys or service accounts with decideCredential and resolving them with subjectFromApiKey, setting tenant key rules (maxTtl, kinds, allowNoExpiry) in a SettingsSource, minting guest or share links with signCapability and resolving them with subjectFromCapability, serving a link through RLS with exchangeCapability, or fixing doctor PD029 and on('auth') causes such as invalid-chain.
4
+ license: MIT
5
+ metadata:
6
+ author: ScaleDockHQ
7
+ homepage: https://permdock.com/docs/concepts/authentication
8
+ repository: https://github.com/ScaleDockHQ/permdock
9
+ ---
10
+
11
+ # PermDock credentials
12
+
13
+ Authentication is upstream: core never verifies a token. A `subjectFrom*` resolver verifies the material and hands core a `Subject`, and anything it cannot verify becomes the anonymous subject, never a throw ([authentication](https://permdock.com/docs/concepts/authentication)). Set up the policy and factory first with the `permdock-wire` skill (`npx skills add ScaleDockHQ/PermDock --skill permdock-wire`).
14
+
15
+ ## Inputs (find out, or ask before starting)
16
+
17
+ - Which credentials reach the app: browser sessions, OAuth or OIDC access tokens, introspected tokens, API keys, CI job tokens, share links.
18
+ - For tokens: the issuer, the resource identifier (audience) and the signing algorithms.
19
+ - For keys: personal (`user`) keys, service accounts (`service`), or both; where the hashes are stored; the tenant rules on lifetime and kinds.
20
+ - For links: what a link opens, who may redeem it, how long it lives, and whether Postgres RLS must serve the guest page.
21
+
22
+ ## Invariants
23
+
24
+ 1. Core never verifies a token or a session. Only `permdock/jwt`, `permdock/server` key helpers and the provider `subjectFrom*` helpers do.
25
+ 2. A resolver never throws. An unverifiable token, key or link is the anonymous subject, and the cause goes to `on('auth')`.
26
+ 3. Never build grants from user-editable claims (`user_metadata`), and never take a subject from a request body, an unsigned header, a CLI flag or a model argument.
27
+ 4. Store only `hashApiKey(key)` and show the key once. Every key lists its permissions and expires unless the tenant opts in with `allowNoExpiry`.
28
+ 5. A share link is a signed, expiring capability on one resource. It never holds a scope or global role, and RLS sees it only through `exchangeCapability`, never `service_role`.
29
+ 6. Token rules come from the spec skills (`npx skills add ScaleDockHQ/scaledock-skills --skill <name>`): `jwt` for JOSE, `oauth` for access tokens and introspection, `openid-connect` for ID tokens and Discovery.
30
+
31
+ ## Workflow
32
+
33
+ 1. **Verify access tokens.** Resolve the subject with `subjectFromJwt(token, { discovery, audience })` from `permdock/jwt`, or `subjectFromIntrospection` when the app already called introspection. -> [references/tokens-keys-links.md](references/tokens-keys-links.md)
34
+ ✓ An ID token, a token for another audience and an expired token each resolve to the anonymous subject.
35
+ 2. **Issue API keys, when the product has them.** Guard the creation endpoint with its own permission, call `decideCredential(permdock, request, { settings })`, store the hash with the returned `Credential`, and show the key once. -> [references/tokens-keys-links.md](references/tokens-keys-links.md#api-keys-and-service-accounts)
36
+ ✓ A service key wider than its creator is denied with `exceeds-creator`.
37
+ 3. **Resolve API keys.** Build `subjectFromApiKey({ verifier: apiKeyVerifier({ find }), permissions, owner, revoked, settings, sink })` and call it as `resolveKey(key, { tenant })`.
38
+ ✓ A revoked or expired key is anonymous, and a user key never exceeds its owner's current rights.
39
+ 4. **Share links, when the product has them.** Declare the link's roles on the resource, mint with `signCapability` behind a guard of its own, and resolve with `subjectFromCapability` and the tenant's `linkPolicy`. With Supabase RLS, exchange the link with `exchangeCapability` and generate with `--capabilities`. -> [references/tokens-keys-links.md](references/tokens-keys-links.md#share-links)
40
+ ✓ An expired, revoked or replayed (`once`) link resolves to the anonymous subject.
41
+ 5. **Test.** Add scenario tests for each credential kind: a valid one, a forged or expired one, and a key or link used past its permissions ([scenario testing](https://permdock.com/docs/guides/scenario-testing)). Run `permdock doctor`.
42
+ ✓ The tests pass and PD029 is clean.
43
+
44
+ ## Verify before done
45
+
46
+ - [ ] `subjectFromJwt` uses Discovery or an explicit `jwks` plus `issuer`, sets `audience`, and allows only `Ed25519`, `ES256` or `PS256`.
47
+ - [ ] No subject, tenant or membership comes from `user_metadata`, a request body or an unsigned header.
48
+ - [ ] API keys are stored as hashes, created only through `decideCredential` behind their own guard, and expire (PD029).
49
+ - [ ] No `memberships` source is passed to `createPermDock` for service-key subjects.
50
+ - [ ] Share links expire, are minted behind their own guard, and reach RLS only through `exchangeCapability`.
51
+
52
+ ## Reference index
53
+
54
+ - [references/tokens-keys-links.md](references/tokens-keys-links.md): `subjectFromJwt` options, introspection, CI OIDC, `decideCredential`, key storage and resolution, tenant settings, `signCapability`, `subjectFromCapability`, `exchangeCapability`.
55
+ - Docs: [authentication](https://permdock.com/docs/concepts/authentication), [subject](https://permdock.com/docs/concepts/subject), [JWT adapter](https://permdock.com/docs/adapters/jwt), [API keys](https://permdock.com/docs/concepts/credentials), [link capabilities](https://permdock.com/docs/concepts/capabilities), [JOSE](https://permdock.com/docs/standards/jose).