@stigmer/server 3.40.0 → 3.41.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 (528) hide show
  1. package/dist/authorization/authorizer.d.ts +16 -10
  2. package/dist/authorization/authorizer.d.ts.map +1 -1
  3. package/dist/authorization/authorizer.js +17 -9
  4. package/dist/authorization/authorizer.js.map +1 -1
  5. package/dist/authorization/derived-tuples.d.ts +7 -3
  6. package/dist/authorization/derived-tuples.d.ts.map +1 -1
  7. package/dist/authorization/derived-tuples.js +62 -6
  8. package/dist/authorization/derived-tuples.js.map +1 -1
  9. package/dist/authorization/evaluator.d.ts +11 -7
  10. package/dist/authorization/evaluator.d.ts.map +1 -1
  11. package/dist/authorization/evaluator.js +16 -10
  12. package/dist/authorization/evaluator.js.map +1 -1
  13. package/dist/authorization/list-read-scope.d.ts +4 -0
  14. package/dist/authorization/list-read-scope.d.ts.map +1 -1
  15. package/dist/authorization/list-read-scope.js +1 -0
  16. package/dist/authorization/list-read-scope.js.map +1 -1
  17. package/dist/authorization/model/data/authorization-model.json +830 -67
  18. package/dist/authorization/model/execution-viewer.d.ts.map +1 -1
  19. package/dist/authorization/model/execution-viewer.js +26 -24
  20. package/dist/authorization/model/execution-viewer.js.map +1 -1
  21. package/dist/authorization/model/index.d.ts +4 -1
  22. package/dist/authorization/model/index.d.ts.map +1 -1
  23. package/dist/authorization/model/index.js +4 -1
  24. package/dist/authorization/model/index.js.map +1 -1
  25. package/dist/authorization/model/rewrite.d.ts +5 -3
  26. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  27. package/dist/authorization/organization-directory.d.ts +44 -0
  28. package/dist/authorization/organization-directory.d.ts.map +1 -1
  29. package/dist/authorization/organization-directory.js +1 -0
  30. package/dist/authorization/organization-directory.js.map +1 -1
  31. package/dist/authorization/person.d.ts +14 -1
  32. package/dist/authorization/person.d.ts.map +1 -1
  33. package/dist/authorization/person.js +16 -0
  34. package/dist/authorization/person.js.map +1 -1
  35. package/dist/authorization/policy-check.d.ts +52 -0
  36. package/dist/authorization/policy-check.d.ts.map +1 -0
  37. package/dist/authorization/policy-check.js +35 -0
  38. package/dist/authorization/policy-check.js.map +1 -0
  39. package/dist/authorization/posture.d.ts +30 -4
  40. package/dist/authorization/posture.d.ts.map +1 -1
  41. package/dist/authorization/posture.js +58 -0
  42. package/dist/authorization/posture.js.map +1 -1
  43. package/dist/authorization/resource-creators.d.ts +4 -0
  44. package/dist/authorization/resource-creators.d.ts.map +1 -0
  45. package/dist/authorization/resource-creators.js +44 -0
  46. package/dist/authorization/resource-creators.js.map +1 -0
  47. package/dist/authorization/trusted-local-authorizer.d.ts +9 -0
  48. package/dist/authorization/trusted-local-authorizer.d.ts.map +1 -0
  49. package/dist/authorization/trusted-local-authorizer.js +70 -0
  50. package/dist/authorization/trusted-local-authorizer.js.map +1 -0
  51. package/dist/authorization/tuples.d.ts +12 -0
  52. package/dist/authorization/tuples.d.ts.map +1 -1
  53. package/dist/authorization/tuples.js +3 -0
  54. package/dist/authorization/tuples.js.map +1 -1
  55. package/dist/boot/compose.d.ts.map +1 -1
  56. package/dist/boot/compose.js +133 -43
  57. package/dist/boot/compose.js.map +1 -1
  58. package/dist/boot/config.d.ts +22 -7
  59. package/dist/boot/config.d.ts.map +1 -1
  60. package/dist/boot/config.js +2 -1
  61. package/dist/boot/config.js.map +1 -1
  62. package/dist/boot/inprocess.d.ts.map +1 -1
  63. package/dist/boot/inprocess.js +10 -4
  64. package/dist/boot/inprocess.js.map +1 -1
  65. package/dist/boot/list-indexes.d.ts.map +1 -1
  66. package/dist/boot/list-indexes.js +6 -1
  67. package/dist/boot/list-indexes.js.map +1 -1
  68. package/dist/boot/logger.d.ts +3 -1
  69. package/dist/boot/logger.d.ts.map +1 -1
  70. package/dist/boot/logger.js +3 -1
  71. package/dist/boot/logger.js.map +1 -1
  72. package/dist/boot/oauth-redirect-uri.d.ts +33 -12
  73. package/dist/boot/oauth-redirect-uri.d.ts.map +1 -1
  74. package/dist/boot/oauth-redirect-uri.js +43 -14
  75. package/dist/boot/oauth-redirect-uri.js.map +1 -1
  76. package/dist/boot/skill-transfer-origin.d.ts +46 -14
  77. package/dist/boot/skill-transfer-origin.d.ts.map +1 -1
  78. package/dist/boot/skill-transfer-origin.js +53 -13
  79. package/dist/boot/skill-transfer-origin.js.map +1 -1
  80. package/dist/domain/agentchannel/steps.d.ts +8 -6
  81. package/dist/domain/agentchannel/steps.d.ts.map +1 -1
  82. package/dist/domain/agentchannel/steps.js +9 -7
  83. package/dist/domain/agentchannel/steps.js.map +1 -1
  84. package/dist/domain/agentexecution/controller.d.ts +7 -0
  85. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  86. package/dist/domain/agentexecution/controller.js +1 -1
  87. package/dist/domain/agentexecution/controller.js.map +1 -1
  88. package/dist/domain/agentexecution/create-steps.d.ts +26 -12
  89. package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
  90. package/dist/domain/agentexecution/create-steps.js +61 -43
  91. package/dist/domain/agentexecution/create-steps.js.map +1 -1
  92. package/dist/domain/agentexecution/submit-approval.d.ts.map +1 -1
  93. package/dist/domain/agentexecution/submit-approval.js +9 -6
  94. package/dist/domain/agentexecution/submit-approval.js.map +1 -1
  95. package/dist/domain/agentexecution/submit-file-decision.d.ts.map +1 -1
  96. package/dist/domain/agentexecution/submit-file-decision.js +5 -4
  97. package/dist/domain/agentexecution/submit-file-decision.js.map +1 -1
  98. package/dist/domain/agentshare/steps.d.ts +5 -3
  99. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  100. package/dist/domain/agentshare/steps.js +6 -4
  101. package/dist/domain/agentshare/steps.js.map +1 -1
  102. package/dist/domain/apikey/verifier.d.ts +7 -0
  103. package/dist/domain/apikey/verifier.d.ts.map +1 -1
  104. package/dist/domain/apikey/verifier.js +34 -1
  105. package/dist/domain/apikey/verifier.js.map +1 -1
  106. package/dist/domain/channelapp/controller.d.ts +9 -4
  107. package/dist/domain/channelapp/controller.d.ts.map +1 -1
  108. package/dist/domain/channelapp/controller.js +19 -9
  109. package/dist/domain/channelapp/controller.js.map +1 -1
  110. package/dist/domain/executioncontext/steps.d.ts +2 -1
  111. package/dist/domain/executioncontext/steps.d.ts.map +1 -1
  112. package/dist/domain/executioncontext/steps.js +2 -1
  113. package/dist/domain/executioncontext/steps.js.map +1 -1
  114. package/dist/domain/iampolicy/access-lists.d.ts.map +1 -1
  115. package/dist/domain/iampolicy/access-lists.js +57 -2
  116. package/dist/domain/iampolicy/access-lists.js.map +1 -1
  117. package/dist/domain/iampolicy/change.d.ts +53 -0
  118. package/dist/domain/iampolicy/change.d.ts.map +1 -0
  119. package/dist/domain/iampolicy/change.js +44 -0
  120. package/dist/domain/iampolicy/change.js.map +1 -0
  121. package/dist/domain/iampolicy/constants.d.ts +6 -0
  122. package/dist/domain/iampolicy/constants.d.ts.map +1 -1
  123. package/dist/domain/iampolicy/constants.js +6 -0
  124. package/dist/domain/iampolicy/constants.js.map +1 -1
  125. package/dist/domain/iampolicy/controller.d.ts.map +1 -1
  126. package/dist/domain/iampolicy/controller.js +9 -5
  127. package/dist/domain/iampolicy/controller.js.map +1 -1
  128. package/dist/domain/iampolicy/grant-path.d.ts +31 -13
  129. package/dist/domain/iampolicy/grant-path.d.ts.map +1 -1
  130. package/dist/domain/iampolicy/grant-path.js +216 -19
  131. package/dist/domain/iampolicy/grant-path.js.map +1 -1
  132. package/dist/domain/iampolicy/membership.d.ts +11 -3
  133. package/dist/domain/iampolicy/membership.d.ts.map +1 -1
  134. package/dist/domain/iampolicy/membership.js +18 -13
  135. package/dist/domain/iampolicy/membership.js.map +1 -1
  136. package/dist/domain/iampolicy/resource-store.d.ts.map +1 -1
  137. package/dist/domain/iampolicy/resource-store.js +4 -0
  138. package/dist/domain/iampolicy/resource-store.js.map +1 -1
  139. package/dist/domain/iampolicy/role-lifecycle.d.ts.map +1 -1
  140. package/dist/domain/iampolicy/role-lifecycle.js +2 -2
  141. package/dist/domain/iampolicy/role-lifecycle.js.map +1 -1
  142. package/dist/domain/iampolicy/roles.d.ts.map +1 -1
  143. package/dist/domain/iampolicy/roles.js +7 -0
  144. package/dist/domain/iampolicy/roles.js.map +1 -1
  145. package/dist/domain/iampolicy/steps.d.ts +4 -3
  146. package/dist/domain/iampolicy/steps.d.ts.map +1 -1
  147. package/dist/domain/iampolicy/steps.js +7 -7
  148. package/dist/domain/iampolicy/steps.js.map +1 -1
  149. package/dist/domain/iampolicy/store-contract.d.ts.map +1 -1
  150. package/dist/domain/iampolicy/store-contract.js +22 -1
  151. package/dist/domain/iampolicy/store-contract.js.map +1 -1
  152. package/dist/domain/iampolicy/store.d.ts +18 -5
  153. package/dist/domain/iampolicy/store.d.ts.map +1 -1
  154. package/dist/domain/iampolicy/store.js.map +1 -1
  155. package/dist/domain/identityaccount/constants.d.ts +15 -0
  156. package/dist/domain/identityaccount/constants.d.ts.map +1 -1
  157. package/dist/domain/identityaccount/constants.js +17 -0
  158. package/dist/domain/identityaccount/constants.js.map +1 -1
  159. package/dist/domain/identityaccount/controller.d.ts.map +1 -1
  160. package/dist/domain/identityaccount/controller.js +17 -6
  161. package/dist/domain/identityaccount/controller.js.map +1 -1
  162. package/dist/domain/identityaccount/resolve.d.ts +47 -0
  163. package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
  164. package/dist/domain/identityaccount/resolve.js +38 -1
  165. package/dist/domain/identityaccount/resolve.js.map +1 -1
  166. package/dist/domain/identityaccount/resource-store.d.ts.map +1 -1
  167. package/dist/domain/identityaccount/resource-store.js +11 -1
  168. package/dist/domain/identityaccount/resource-store.js.map +1 -1
  169. package/dist/domain/identityaccount/steps.d.ts +2 -1
  170. package/dist/domain/identityaccount/steps.d.ts.map +1 -1
  171. package/dist/domain/identityaccount/steps.js +4 -3
  172. package/dist/domain/identityaccount/steps.js.map +1 -1
  173. package/dist/domain/identityaccount/store-contract.js +2 -2
  174. package/dist/domain/identityaccount/store-contract.js.map +1 -1
  175. package/dist/domain/identityaccount/store.d.ts +6 -1
  176. package/dist/domain/identityaccount/store.d.ts.map +1 -1
  177. package/dist/domain/mcpserver/connect-sandbox.d.ts +67 -0
  178. package/dist/domain/mcpserver/connect-sandbox.d.ts.map +1 -0
  179. package/dist/domain/mcpserver/connect-sandbox.js +64 -0
  180. package/dist/domain/mcpserver/connect-sandbox.js.map +1 -0
  181. package/dist/domain/mcpserver/connect.d.ts +42 -10
  182. package/dist/domain/mcpserver/connect.d.ts.map +1 -1
  183. package/dist/domain/mcpserver/connect.js +147 -40
  184. package/dist/domain/mcpserver/connect.js.map +1 -1
  185. package/dist/domain/mcpserver/controller.js +4 -2
  186. package/dist/domain/mcpserver/controller.js.map +1 -1
  187. package/dist/domain/mcpserver/engine.d.ts +26 -7
  188. package/dist/domain/mcpserver/engine.d.ts.map +1 -1
  189. package/dist/domain/mcpserver/engine.js.map +1 -1
  190. package/dist/domain/mcpserver/start-connect.d.ts +5 -0
  191. package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
  192. package/dist/domain/mcpserver/start-connect.js +38 -15
  193. package/dist/domain/mcpserver/start-connect.js.map +1 -1
  194. package/dist/domain/memory/controller.js +2 -1
  195. package/dist/domain/memory/controller.js.map +1 -1
  196. package/dist/domain/memory/list-index.d.ts +2 -0
  197. package/dist/domain/memory/list-index.d.ts.map +1 -0
  198. package/dist/domain/memory/list-index.js +31 -0
  199. package/dist/domain/memory/list-index.js.map +1 -0
  200. package/dist/domain/memory/queries.d.ts +10 -0
  201. package/dist/domain/memory/queries.d.ts.map +1 -0
  202. package/dist/domain/memory/queries.js +54 -0
  203. package/dist/domain/memory/queries.js.map +1 -0
  204. package/dist/domain/memory/steps.d.ts +13 -11
  205. package/dist/domain/memory/steps.d.ts.map +1 -1
  206. package/dist/domain/memory/steps.js +26 -57
  207. package/dist/domain/memory/steps.js.map +1 -1
  208. package/dist/domain/organization/steps.d.ts.map +1 -1
  209. package/dist/domain/organization/steps.js +1 -1
  210. package/dist/domain/organization/steps.js.map +1 -1
  211. package/dist/domain/platformclient/mint.d.ts.map +1 -1
  212. package/dist/domain/platformclient/mint.js +2 -2
  213. package/dist/domain/platformclient/mint.js.map +1 -1
  214. package/dist/domain/platformclient/verifier.d.ts.map +1 -1
  215. package/dist/domain/platformclient/verifier.js +6 -3
  216. package/dist/domain/platformclient/verifier.js.map +1 -1
  217. package/dist/domain/plugin/push.d.ts.map +1 -1
  218. package/dist/domain/plugin/push.js +1 -0
  219. package/dist/domain/plugin/push.js.map +1 -1
  220. package/dist/domain/schedule/steps.d.ts +3 -1
  221. package/dist/domain/schedule/steps.d.ts.map +1 -1
  222. package/dist/domain/schedule/steps.js +3 -1
  223. package/dist/domain/schedule/steps.js.map +1 -1
  224. package/dist/domain/skill/push.d.ts.map +1 -1
  225. package/dist/domain/skill/push.js +1 -0
  226. package/dist/domain/skill/push.js.map +1 -1
  227. package/dist/domain/workflow/registry/data/task-kind-registry.json +27 -1
  228. package/dist/domain/workflowexecution/submit-approval.d.ts +5 -2
  229. package/dist/domain/workflowexecution/submit-approval.d.ts.map +1 -1
  230. package/dist/domain/workflowexecution/submit-approval.js +1 -1
  231. package/dist/domain/workflowexecution/submit-approval.js.map +1 -1
  232. package/dist/domain/workflowexecution/submit-file-decision.d.ts +5 -2
  233. package/dist/domain/workflowexecution/submit-file-decision.d.ts.map +1 -1
  234. package/dist/domain/workflowexecution/submit-file-decision.js +1 -1
  235. package/dist/domain/workflowexecution/submit-file-decision.js.map +1 -1
  236. package/dist/domain/workflowexecution/submit-workflow-task-approval.d.ts.map +1 -1
  237. package/dist/domain/workflowexecution/submit-workflow-task-approval.js +12 -1
  238. package/dist/domain/workflowexecution/submit-workflow-task-approval.js.map +1 -1
  239. package/dist/domain/workflowinstance/controller.d.ts +6 -3
  240. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  241. package/dist/domain/workflowinstance/controller.js +12 -7
  242. package/dist/domain/workflowinstance/controller.js.map +1 -1
  243. package/dist/domain/workflowinstance/steps.d.ts +31 -0
  244. package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
  245. package/dist/domain/workflowinstance/steps.js +88 -1
  246. package/dist/domain/workflowinstance/steps.js.map +1 -1
  247. package/dist/extensions/composed-services.d.ts +56 -0
  248. package/dist/extensions/composed-services.d.ts.map +1 -0
  249. package/dist/extensions/composed-services.js +2 -0
  250. package/dist/extensions/composed-services.js.map +1 -0
  251. package/dist/extensions/drivers.d.ts +32 -1
  252. package/dist/extensions/drivers.d.ts.map +1 -1
  253. package/dist/extensions/identity.d.ts +4 -1
  254. package/dist/extensions/identity.d.ts.map +1 -1
  255. package/dist/extensions/identity.js +4 -1
  256. package/dist/extensions/identity.js.map +1 -1
  257. package/dist/extensions/registry.d.ts +37 -0
  258. package/dist/extensions/registry.d.ts.map +1 -1
  259. package/dist/extensions/registry.js +50 -0
  260. package/dist/extensions/registry.js.map +1 -1
  261. package/dist/extensions/resource-authorization.d.ts +78 -0
  262. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  263. package/dist/extensions/resource-row-reader.d.ts +43 -0
  264. package/dist/extensions/resource-row-reader.d.ts.map +1 -0
  265. package/dist/extensions/resource-row-reader.js +2 -0
  266. package/dist/extensions/resource-row-reader.js.map +1 -0
  267. package/dist/extensions/visitor-classifier.d.ts +41 -0
  268. package/dist/extensions/visitor-classifier.d.ts.map +1 -0
  269. package/dist/extensions/visitor-classifier.js +2 -0
  270. package/dist/extensions/visitor-classifier.js.map +1 -0
  271. package/dist/identity/oidc-userinfo.d.ts +6 -1
  272. package/dist/identity/oidc-userinfo.d.ts.map +1 -1
  273. package/dist/identity/oidc-userinfo.js.map +1 -1
  274. package/dist/identity/oidc-verifier.d.ts.map +1 -1
  275. package/dist/identity/oidc-verifier.js +37 -8
  276. package/dist/identity/oidc-verifier.js.map +1 -1
  277. package/dist/index.d.ts +14 -6
  278. package/dist/index.d.ts.map +1 -1
  279. package/dist/index.js +25 -9
  280. package/dist/index.js.map +1 -1
  281. package/dist/pipeline/errors.d.ts +8 -0
  282. package/dist/pipeline/errors.d.ts.map +1 -1
  283. package/dist/pipeline/errors.js +25 -16
  284. package/dist/pipeline/errors.js.map +1 -1
  285. package/dist/pipeline/interceptors/auth.d.ts +17 -9
  286. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  287. package/dist/pipeline/interceptors/auth.js +17 -9
  288. package/dist/pipeline/interceptors/auth.js.map +1 -1
  289. package/dist/pipeline/steps/authorization-tuples.d.ts +23 -1
  290. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  291. package/dist/pipeline/steps/authorization-tuples.js +42 -0
  292. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  293. package/dist/pipeline/steps/authorize.d.ts +6 -5
  294. package/dist/pipeline/steps/authorize.d.ts.map +1 -1
  295. package/dist/pipeline/steps/authorize.js +6 -5
  296. package/dist/pipeline/steps/authorize.js.map +1 -1
  297. package/dist/pipeline/steps/defaults.d.ts +12 -0
  298. package/dist/pipeline/steps/defaults.d.ts.map +1 -1
  299. package/dist/pipeline/steps/defaults.js +42 -7
  300. package/dist/pipeline/steps/defaults.js.map +1 -1
  301. package/dist/pipeline/steps/guard-memory-capture.d.ts +13 -15
  302. package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
  303. package/dist/pipeline/steps/guard-memory-capture.js +2 -3
  304. package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
  305. package/dist/runnerauth/built-in-runner-credential-provider.d.ts +0 -2
  306. package/dist/runnerauth/built-in-runner-credential-provider.d.ts.map +1 -1
  307. package/dist/runnerauth/built-in-runner-credential-provider.js +8 -17
  308. package/dist/runnerauth/built-in-runner-credential-provider.js.map +1 -1
  309. package/dist/runnerauth/runner-credential-provider.d.ts +17 -7
  310. package/dist/runnerauth/runner-credential-provider.d.ts.map +1 -1
  311. package/dist/runnerauth/runner-credential-provider.js.map +1 -1
  312. package/dist/sandbox/docker.d.ts.map +1 -1
  313. package/dist/sandbox/docker.js +3 -0
  314. package/dist/sandbox/docker.js.map +1 -1
  315. package/dist/sandbox/kubernetes.d.ts.map +1 -1
  316. package/dist/sandbox/kubernetes.js +3 -0
  317. package/dist/sandbox/kubernetes.js.map +1 -1
  318. package/dist/sandbox/local-process.d.ts.map +1 -1
  319. package/dist/sandbox/local-process.js +4 -0
  320. package/dist/sandbox/local-process.js.map +1 -1
  321. package/dist/sandbox/provisioner.d.ts +18 -6
  322. package/dist/sandbox/provisioner.d.ts.map +1 -1
  323. package/dist/sandbox/provisioner.js.map +1 -1
  324. package/dist/temporal/manager.d.ts +5 -0
  325. package/dist/temporal/manager.d.ts.map +1 -1
  326. package/dist/temporal/manager.js +9 -0
  327. package/dist/temporal/manager.js.map +1 -1
  328. package/dist/temporal/mcpserver/engine-client.d.ts +2 -2
  329. package/dist/temporal/mcpserver/engine-client.d.ts.map +1 -1
  330. package/dist/temporal/mcpserver/engine-client.js +6 -4
  331. package/dist/temporal/mcpserver/engine-client.js.map +1 -1
  332. package/dist/temporal/mcpserver/names.d.ts +13 -2
  333. package/dist/temporal/mcpserver/names.d.ts.map +1 -1
  334. package/dist/temporal/mcpserver/names.js +15 -2
  335. package/dist/temporal/mcpserver/names.js.map +1 -1
  336. package/dist/temporal/sdk-logger.d.ts +74 -0
  337. package/dist/temporal/sdk-logger.d.ts.map +1 -0
  338. package/dist/temporal/sdk-logger.js +143 -0
  339. package/dist/temporal/sdk-logger.js.map +1 -0
  340. package/package.json +6 -6
  341. package/src/authorization/README.md +18 -6
  342. package/src/authorization/__tests__/authorizer.test.ts +5 -1
  343. package/src/authorization/__tests__/derived-tuples.test.ts +26 -0
  344. package/src/authorization/__tests__/evaluator.test.ts +4 -0
  345. package/src/authorization/__tests__/list-read-scope.test.ts +11 -5
  346. package/src/authorization/__tests__/store-tests.test.ts +3 -1
  347. package/src/authorization/__tests__/trusted-local-authorizer.test.ts +155 -0
  348. package/src/authorization/__tests__/unit-owned-kinds.test.ts +420 -0
  349. package/src/authorization/__tests__/wire-permissions.test.ts +31 -2
  350. package/src/authorization/authorizer.ts +21 -10
  351. package/src/authorization/derived-tuples.ts +92 -18
  352. package/src/authorization/evaluator.ts +30 -15
  353. package/src/authorization/list-read-scope.ts +4 -0
  354. package/src/authorization/model/__tests__/registry.test.ts +23 -0
  355. package/src/authorization/model/data/authorization-model.json +830 -67
  356. package/src/authorization/model/execution-viewer.ts +32 -27
  357. package/src/authorization/model/index.ts +4 -1
  358. package/src/authorization/model/rewrite.ts +5 -3
  359. package/src/authorization/organization-directory.ts +4 -0
  360. package/src/authorization/person.ts +32 -1
  361. package/src/authorization/policy-check.ts +105 -0
  362. package/src/authorization/posture.ts +45 -4
  363. package/src/authorization/resource-creators.ts +48 -0
  364. package/src/authorization/trusted-local-authorizer.ts +91 -0
  365. package/src/authorization/tuples.ts +17 -0
  366. package/src/boot/__tests__/compose.test.ts +120 -4
  367. package/src/boot/__tests__/list-indexes.test.ts +4 -0
  368. package/src/boot/__tests__/oauth-redirect-uri.test.ts +121 -10
  369. package/src/boot/__tests__/skill-transfer-origin.test.ts +96 -25
  370. package/src/boot/compose.ts +154 -61
  371. package/src/boot/config.ts +28 -12
  372. package/src/boot/inprocess.ts +18 -4
  373. package/src/boot/list-indexes.ts +6 -1
  374. package/src/boot/logger.ts +3 -1
  375. package/src/boot/oauth-redirect-uri.ts +62 -17
  376. package/src/boot/skill-transfer-origin.ts +71 -16
  377. package/src/domain/agent/__tests__/agent.test.ts +42 -1
  378. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +9 -0
  379. package/src/domain/agentchannel/__tests__/channel-runtime.test.ts +2 -0
  380. package/src/domain/agentchannel/__tests__/create-authorization.test.ts +7 -6
  381. package/src/domain/agentchannel/steps.ts +9 -7
  382. package/src/domain/agentexecution/__tests__/agentexecution.test.ts +148 -56
  383. package/src/domain/agentexecution/__tests__/create-steps.test.ts +175 -12
  384. package/src/domain/agentexecution/__tests__/file-review-seed.ts +91 -0
  385. package/src/domain/agentexecution/controller.ts +8 -0
  386. package/src/domain/agentexecution/create-steps.ts +74 -44
  387. package/src/domain/agentexecution/submit-approval.ts +9 -6
  388. package/src/domain/agentexecution/submit-file-decision.ts +5 -4
  389. package/src/domain/agentinstance/__tests__/agentinstance.test.ts +2 -0
  390. package/src/domain/agentshare/__tests__/agentshare.test.ts +17 -0
  391. package/src/domain/agentshare/__tests__/create-authorization.test.ts +5 -4
  392. package/src/domain/agentshare/steps.ts +6 -4
  393. package/src/domain/apikey/__tests__/verifier.test.ts +73 -1
  394. package/src/domain/apikey/verifier.ts +45 -1
  395. package/src/domain/artifact/__tests__/artifact.test.ts +13 -24
  396. package/src/domain/channelapp/__tests__/channelapp.test.ts +7 -0
  397. package/src/domain/channelapp/controller.ts +35 -12
  398. package/src/domain/environment/__tests__/environment.test.ts +13 -0
  399. package/src/domain/executioncontext/__tests__/executioncontext.test.ts +2 -0
  400. package/src/domain/executioncontext/steps.ts +2 -1
  401. package/src/domain/iampolicy/__tests__/access-lists.test.ts +79 -0
  402. package/src/domain/iampolicy/__tests__/change.test.ts +63 -0
  403. package/src/domain/iampolicy/__tests__/controller.test.ts +39 -3
  404. package/src/domain/iampolicy/__tests__/grant-path.test.ts +595 -27
  405. package/src/domain/iampolicy/__tests__/iampolicy.test.ts +33 -2
  406. package/src/domain/iampolicy/__tests__/membership.test.ts +47 -5
  407. package/src/domain/iampolicy/__tests__/resource-store.test.ts +1 -0
  408. package/src/domain/iampolicy/__tests__/role-lifecycle.test.ts +41 -8
  409. package/src/domain/iampolicy/__tests__/roles.test.ts +3 -2
  410. package/src/domain/iampolicy/__tests__/steps.test.ts +75 -6
  411. package/src/domain/iampolicy/__tests__/support.ts +68 -4
  412. package/src/domain/iampolicy/access-lists.ts +70 -2
  413. package/src/domain/iampolicy/change.ts +109 -0
  414. package/src/domain/iampolicy/constants.ts +8 -0
  415. package/src/domain/iampolicy/controller.ts +17 -3
  416. package/src/domain/iampolicy/grant-path.ts +349 -31
  417. package/src/domain/iampolicy/membership.ts +32 -12
  418. package/src/domain/iampolicy/resource-store.ts +4 -0
  419. package/src/domain/iampolicy/role-lifecycle.ts +2 -0
  420. package/src/domain/iampolicy/roles.ts +8 -0
  421. package/src/domain/iampolicy/steps.ts +15 -5
  422. package/src/domain/iampolicy/store-contract.ts +37 -1
  423. package/src/domain/iampolicy/store.ts +19 -5
  424. package/src/domain/identityaccount/__tests__/resolve.test.ts +246 -2
  425. package/src/domain/identityaccount/__tests__/resource-store.test.ts +1 -1
  426. package/src/domain/identityaccount/constants.ts +20 -0
  427. package/src/domain/identityaccount/controller.ts +20 -5
  428. package/src/domain/identityaccount/resolve.ts +80 -1
  429. package/src/domain/identityaccount/resource-store.ts +14 -1
  430. package/src/domain/identityaccount/steps.ts +4 -3
  431. package/src/domain/identityaccount/store-contract.ts +4 -4
  432. package/src/domain/identityaccount/store.ts +6 -1
  433. package/src/domain/mcpserver/__tests__/connect-authorization.test.ts +1 -0
  434. package/src/domain/mcpserver/__tests__/connect.test.ts +358 -15
  435. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +2 -0
  436. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +2 -0
  437. package/src/domain/mcpserver/__tests__/store-faults.test.ts +2 -0
  438. package/src/domain/mcpserver/connect-sandbox.ts +135 -0
  439. package/src/domain/mcpserver/connect.ts +226 -56
  440. package/src/domain/mcpserver/controller.ts +8 -2
  441. package/src/domain/mcpserver/engine.ts +24 -6
  442. package/src/domain/mcpserver/start-connect.ts +49 -10
  443. package/src/domain/memory/__tests__/memory.test.ts +92 -22
  444. package/src/domain/memory/__tests__/queries.test.ts +173 -0
  445. package/src/domain/memory/controller.ts +2 -1
  446. package/src/domain/memory/list-index.ts +32 -0
  447. package/src/domain/memory/queries.ts +72 -0
  448. package/src/domain/memory/steps.ts +26 -63
  449. package/src/domain/oauthapp/__tests__/oauthapp.test.ts +14 -0
  450. package/src/domain/organization/__tests__/support.ts +41 -0
  451. package/src/domain/organization/steps.ts +1 -0
  452. package/src/domain/platformclient/mint.ts +6 -2
  453. package/src/domain/platformclient/verifier.ts +6 -3
  454. package/src/domain/plugin/__tests__/plugin.test.ts +11 -24
  455. package/src/domain/plugin/push.ts +1 -0
  456. package/src/domain/schedule/__tests__/schedule.composed.test.ts +2 -0
  457. package/src/domain/schedule/steps.ts +3 -1
  458. package/src/domain/session/__tests__/session.test.ts +2 -0
  459. package/src/domain/skill/__tests__/skill.test.ts +7 -25
  460. package/src/domain/skill/push.ts +1 -0
  461. package/src/domain/workflow/__tests__/workflow.test.ts +2 -0
  462. package/src/domain/workflow/registry/data/task-kind-registry.json +27 -1
  463. package/src/domain/workflowexecution/__tests__/forward-attribution-composed.test.ts +270 -0
  464. package/src/domain/workflowexecution/__tests__/submit-workflow-task-approval.test.ts +196 -0
  465. package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +3 -57
  466. package/src/domain/workflowexecution/submit-approval.ts +9 -2
  467. package/src/domain/workflowexecution/submit-file-decision.ts +6 -1
  468. package/src/domain/workflowexecution/submit-workflow-task-approval.ts +23 -5
  469. package/src/domain/workflowinstance/controller.ts +27 -9
  470. package/src/domain/workflowinstance/steps.ts +123 -1
  471. package/src/extensions/__tests__/apikey-before-sign-in-composed.test.ts +172 -0
  472. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +80 -1
  473. package/src/extensions/__tests__/composed-services-composed.test.ts +236 -0
  474. package/src/extensions/__tests__/extension-composition.test.ts +229 -16
  475. package/src/extensions/__tests__/identity-account-composed.test.ts +84 -4
  476. package/src/extensions/__tests__/list-read-scope-composed.test.ts +41 -1
  477. package/src/extensions/__tests__/registry.test.ts +147 -1
  478. package/src/extensions/__tests__/secret-cleanup-composed.test.ts +2 -0
  479. package/src/extensions/__tests__/visitor-classifier-points.test.ts +42 -0
  480. package/src/extensions/composed-services.ts +61 -0
  481. package/src/extensions/drivers.ts +33 -1
  482. package/src/extensions/identity.ts +4 -1
  483. package/src/extensions/registry.ts +105 -0
  484. package/src/extensions/resource-authorization.ts +87 -0
  485. package/src/extensions/resource-row-reader.ts +43 -0
  486. package/src/extensions/visitor-classifier.ts +41 -0
  487. package/src/identity/__tests__/oidc-verifier.test.ts +38 -12
  488. package/src/identity/oidc-userinfo.ts +6 -1
  489. package/src/identity/oidc-verifier.ts +42 -8
  490. package/src/index.ts +61 -6
  491. package/src/pipeline/__tests__/steps.test.ts +31 -4
  492. package/src/pipeline/errors.ts +30 -16
  493. package/src/pipeline/interceptors/auth.ts +17 -9
  494. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +20 -0
  495. package/src/pipeline/steps/__tests__/guard-memory-capture.test.ts +72 -15
  496. package/src/pipeline/steps/authorization-tuples.ts +54 -0
  497. package/src/pipeline/steps/authorize.ts +6 -5
  498. package/src/pipeline/steps/defaults.ts +52 -7
  499. package/src/pipeline/steps/guard-memory-capture.ts +15 -20
  500. package/src/runnerauth/__tests__/built-in-runner-credential-provider.test.ts +1 -1
  501. package/src/runnerauth/__tests__/runner-subject-composed.test.ts +13 -0
  502. package/src/runnerauth/bound-execution.ts +2 -2
  503. package/src/runnerauth/built-in-runner-credential-provider.ts +8 -21
  504. package/src/runnerauth/runner-credential-provider.ts +17 -7
  505. package/src/sandbox/__tests__/docker.integration.test.ts +4 -0
  506. package/src/sandbox/__tests__/kubernetes.test.ts +18 -1
  507. package/src/sandbox/__tests__/local-process.integration.test.ts +4 -0
  508. package/src/sandbox/__tests__/provisioner.test.ts +1 -0
  509. package/src/sandbox/docker.ts +3 -0
  510. package/src/sandbox/kubernetes.ts +3 -0
  511. package/src/sandbox/local-process.ts +4 -0
  512. package/src/sandbox/provisioner.ts +18 -6
  513. package/src/store/postgres/__tests__/support.ts +11 -2
  514. package/src/temporal/README.md +1 -0
  515. package/src/temporal/__tests__/manager.test.ts +56 -4
  516. package/src/temporal/__tests__/sdk-log-workflows.ts +26 -0
  517. package/src/temporal/__tests__/sdk-logger.e2e.test.ts +150 -0
  518. package/src/temporal/__tests__/sdk-logger.test.ts +238 -0
  519. package/src/temporal/manager.ts +9 -0
  520. package/src/temporal/mcpserver/engine-client.ts +8 -3
  521. package/src/temporal/mcpserver/names.ts +16 -2
  522. package/src/temporal/sdk-logger.ts +173 -0
  523. package/dist/authorization/lane-admission.d.ts +0 -50
  524. package/dist/authorization/lane-admission.d.ts.map +0 -1
  525. package/dist/authorization/lane-admission.js +0 -13
  526. package/dist/authorization/lane-admission.js.map +0 -1
  527. package/src/authorization/__tests__/lane-admission.test.ts +0 -115
  528. package/src/authorization/lane-admission.ts +0 -73
@@ -22,6 +22,15 @@
22
22
  * - revokeOrgAccess revokes the account's direct rows on the
23
23
  * organization; the inert org-column arm the cloud carried is gone
24
24
  * (Q-OR-9).
25
+ * - Leaving an organization: once an account holds no row on an
26
+ * organization, the rows it holds on that organization's resources
27
+ * go too (shares, team memberships, an owner someone else granted),
28
+ * found through the resources' scope links, each through the revoke
29
+ * order; its authorship (`owner`/`creator` naming the resource's
30
+ * recorded creator) stays, as open source's derived tuple does, for
31
+ * the model's organization bound to govern. revokeOrgAccess always
32
+ * sweeps, so a retry converges; a revoke by spec sweeps only when it
33
+ * removed the last role; cleanupResource never sweeps.
25
34
  * - With no lifecycle composed (the OSS default before the built-in
26
35
  * posture, and any unit that implements only the three required
27
36
  * methods), the path writes rows and notifies nothing.
@@ -36,6 +45,15 @@
36
45
  * revoke, so a composition never receives a garbage spec to delete a
37
46
  * bare tuple from.
38
47
  *
48
+ * - Who and why: every access row the path writes or deletes reaches the
49
+ * store with its change record (the caller's id and class, the door,
50
+ * the row's organization), and nothing else does: no record for a
51
+ * structural link, no write at all on a duplicate grant or a revoke of
52
+ * a row that is not held. The organization is read before any row of
53
+ * the operation goes, so a cleanup that deletes a resource's scope link
54
+ * before its owner still names the owner row's organization. The log
55
+ * lines carry the same facts.
56
+ *
39
57
  * The row the path builds is pinned too: the proto's apiVersion const, the
40
58
  * derived id, the caller's audit stamp — the cloud's `buildNewPolicy`
41
59
  * stamped neither org nor creator, and this path stamps the creator.
@@ -53,6 +71,7 @@ import type { IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/
53
71
  import type { CallerIdentity } from "../../../extensions/identity.js";
54
72
  import type { ResourceAuthorizationLifecycle } from "../../../extensions/resource-authorization.js";
55
73
  import {
74
+ AFFILIATION_NOT_GRANTABLE_MESSAGE,
56
75
  IAM_POLICY_API_VERSION,
57
76
  IAM_POLICY_KIND,
58
77
  malformedTripleMessage,
@@ -60,15 +79,19 @@ import {
60
79
  unknownPrincipalKindMessage,
61
80
  unknownResourceKindMessage,
62
81
  } from "../constants.js";
82
+ import type { PolicyActor } from "../change.js";
63
83
  import { newIamPolicyGrantPath } from "../grant-path.js";
84
+ import type { ResourceCreators } from "../grant-path.js";
64
85
  import { DuplicatePolicyError } from "../store.js";
65
86
  import {
87
+ NO_RECORDED_CREATORS,
66
88
  fakeIamPolicyStore,
67
89
  orgRole,
90
+ recordedCreators,
68
91
  recordingLifecycle,
69
92
  triple,
70
93
  } from "./support.js";
71
- import type { RecordedEvent } from "./support.js";
94
+ import type { FakeIamPolicyStore, RecordedEvent } from "./support.js";
72
95
 
73
96
  const ALICE = "ida_wtr3jcf281yfk9xx61kj59fsme";
74
97
  const BOB = "ida_0byc5k14t1e7b7kdxft7hwz1f7";
@@ -114,9 +137,11 @@ const silentLogger = { debug() {}, info() {}, warn() {}, error() {} };
114
137
  function pathOver(
115
138
  recorded: RecordedEvent[],
116
139
  lifecycle: ResourceAuthorizationLifecycle | undefined,
140
+ creators: ResourceCreators = NO_RECORDED_CREATORS,
117
141
  ) {
118
142
  const policies = fakeIamPolicyStore(recorded);
119
143
  const path = newIamPolicyGrantPath({
144
+ creators,
120
145
  policies,
121
146
  lifecycle,
122
147
  logger: silentLogger,
@@ -130,7 +155,7 @@ describe("grant: row before onPolicyGranted", () => {
130
155
  const { policies, path } = pathOver(recorded, recordingLifecycle(recorded));
131
156
  const spec = orgRole(ALICE, "admin", "acme");
132
157
 
133
- const result = await path.grant(spec, alice);
158
+ const result = await path.grant(spec, alice, "grant");
134
159
 
135
160
  const id = policyIdFor(spec);
136
161
  expect(result.duplicate).toBe(false);
@@ -150,10 +175,10 @@ describe("grant: row before onPolicyGranted", () => {
150
175
  const recorded: RecordedEvent[] = [];
151
176
  const { path } = pathOver(recorded, recordingLifecycle(recorded));
152
177
  const spec = orgRole(ALICE, "admin", "acme");
153
- const first = await path.grant(spec, alice);
178
+ const first = await path.grant(spec, alice, "grant");
154
179
  recorded.length = 0;
155
180
 
156
- const second = await path.grant(spec, alice);
181
+ const second = await path.grant(spec, alice, "grant");
157
182
 
158
183
  expect(second.duplicate).toBe(true);
159
184
  expect(second.policy).toEqual(first.policy);
@@ -170,7 +195,9 @@ describe("grant: row before onPolicyGranted", () => {
170
195
  );
171
196
  const spec = orgRole(ALICE, "admin", "acme");
172
197
 
173
- await expect(path.grant(spec, alice)).rejects.toThrow("fga is down");
198
+ await expect(path.grant(spec, alice, "grant")).rejects.toThrow(
199
+ "fga is down",
200
+ );
174
201
 
175
202
  expect(policies.rows.has(policyIdFor(spec))).toBe(true);
176
203
  expect(recorded).toEqual([{ kind: "row-save", id: policyIdFor(spec) }]);
@@ -179,7 +206,7 @@ describe("grant: row before onPolicyGranted", () => {
179
206
  it("with no lifecycle composed, writes the row and notifies nothing", async () => {
180
207
  const recorded: RecordedEvent[] = [];
181
208
  const { path } = pathOver(recorded, undefined);
182
- await path.grant(orgRole(ALICE, "member", "acme"), alice);
209
+ await path.grant(orgRole(ALICE, "member", "acme"), alice, "grant");
183
210
  expect(recorded.map((event) => event.kind)).toEqual(["row-save"]);
184
211
  });
185
212
 
@@ -191,8 +218,8 @@ describe("grant: row before onPolicyGranted", () => {
191
218
  async onVisibilityChanged() {},
192
219
  };
193
220
  const { path } = pathOver(recorded, threeMethods);
194
- await path.grant(orgRole(ALICE, "member", "acme"), alice);
195
- await path.revokeBySpec(orgRole(ALICE, "member", "acme"));
221
+ await path.grant(orgRole(ALICE, "member", "acme"), alice, "grant");
222
+ await path.revokeBySpec(orgRole(ALICE, "member", "acme"), alice);
196
223
  expect(recorded.map((event) => event.kind)).toEqual([
197
224
  "row-save",
198
225
  "row-delete",
@@ -207,7 +234,7 @@ describe("the row for a triple is found by triple, so legacy ids converge", () =
207
234
  const spec = orgRole(ALICE, "admin", "acme");
208
235
  policies.rows.set(LEGACY_ID, legacyRow(spec));
209
236
 
210
- const result = await path.grant(spec, alice);
237
+ const result = await path.grant(spec, alice, "grant");
211
238
 
212
239
  expect(result.duplicate).toBe(true);
213
240
  expect(result.policy.metadata?.id).toBe(LEGACY_ID);
@@ -223,7 +250,7 @@ describe("the row for a triple is found by triple, so legacy ids converge", () =
223
250
  const spec = orgRole(ALICE, "admin", "acme");
224
251
  policies.rows.set(LEGACY_ID, legacyRow(spec));
225
252
 
226
- const revoked = await path.revokeBySpec(spec);
253
+ const revoked = await path.revokeBySpec(spec, alice);
227
254
 
228
255
  expect(revoked?.metadata?.id).toBe(LEGACY_ID);
229
256
  expect(policies.rows.size).toBe(0);
@@ -253,12 +280,13 @@ describe("the row for a triple is found by triple, so legacy ids converge", () =
253
280
  await racingSave(policy);
254
281
  };
255
282
  const path = newIamPolicyGrantPath({
283
+ creators: NO_RECORDED_CREATORS,
256
284
  policies,
257
285
  lifecycle: recordingLifecycle(recorded),
258
286
  logger: silentLogger,
259
287
  });
260
288
 
261
- const result = await path.grant(spec, bob);
289
+ const result = await path.grant(spec, bob, "grant");
262
290
 
263
291
  expect(result.duplicate).toBe(true);
264
292
  expect(result.policy.status).toBeUndefined();
@@ -298,7 +326,9 @@ describe("the one writer is the one gate: nothing is read, written or notified f
298
326
  [bothUnknown, unknownResourceKindMessage("organisation")],
299
327
  ];
300
328
  for (const [spec, message] of refusals) {
301
- const error = await invalidArgument(() => path.grant(spec, alice));
329
+ const error = await invalidArgument(() =>
330
+ path.grant(spec, alice, "grant"),
331
+ );
302
332
  expect(error.rawMessage, message).toBe(message);
303
333
  }
304
334
  expect(policies.rows.size).toBe(0);
@@ -309,7 +339,7 @@ describe("the one writer is the one gate: nothing is read, written or notified f
309
339
  const recorded: RecordedEvent[] = [];
310
340
  const { path } = pathOver(recorded, recordingLifecycle(recorded));
311
341
  for (const spec of [unknownResource, unknownPrincipal, malformed]) {
312
- await invalidArgument(() => path.revokeBySpec(spec));
342
+ await invalidArgument(() => path.revokeBySpec(spec, alice));
313
343
  }
314
344
  expect(recorded).toEqual([]);
315
345
  });
@@ -324,6 +354,7 @@ describe("the one writer is the one gate: nothing is read, written or notified f
324
354
  id: "acme",
325
355
  }),
326
356
  alice,
357
+ "grant",
327
358
  ),
328
359
  );
329
360
  expect(error.rawMessage).toBe(
@@ -337,10 +368,10 @@ describe("revoke: onPolicyRevoked before the row delete", () => {
337
368
  const recorded: RecordedEvent[] = [];
338
369
  const { policies, path } = pathOver(recorded, recordingLifecycle(recorded));
339
370
  const spec = orgRole(ALICE, "admin", "acme");
340
- await path.grant(spec, alice);
371
+ await path.grant(spec, alice, "grant");
341
372
  recorded.length = 0;
342
373
 
343
- const revoked = await path.revokeBySpec(spec);
374
+ const revoked = await path.revokeBySpec(spec, alice);
344
375
 
345
376
  const id = policyIdFor(spec);
346
377
  expect(revoked?.metadata?.id).toBe(id);
@@ -358,10 +389,10 @@ describe("revoke: onPolicyRevoked before the row delete", () => {
358
389
  recordingLifecycle(recorded, { revoked: new Error("fga is down") }),
359
390
  );
360
391
  const spec = orgRole(ALICE, "admin", "acme");
361
- await path.grant(spec, alice);
392
+ await path.grant(spec, alice, "grant");
362
393
  recorded.length = 0;
363
394
 
364
- await expect(path.revokeBySpec(spec)).rejects.toThrow("fga is down");
395
+ await expect(path.revokeBySpec(spec, alice)).rejects.toThrow("fga is down");
365
396
 
366
397
  expect(policies.rows.has(policyIdFor(spec))).toBe(true);
367
398
  expect(recorded).toEqual([]);
@@ -371,7 +402,10 @@ describe("revoke: onPolicyRevoked before the row delete", () => {
371
402
  const recorded: RecordedEvent[] = [];
372
403
  const { path } = pathOver(recorded, recordingLifecycle(recorded));
373
404
 
374
- const revoked = await path.revokeBySpec(orgRole(BOB, "viewer", "acme"));
405
+ const revoked = await path.revokeBySpec(
406
+ orgRole(BOB, "viewer", "acme"),
407
+ alice,
408
+ );
375
409
 
376
410
  expect(revoked).toBeUndefined();
377
411
  expect(recorded).toEqual([
@@ -394,12 +428,13 @@ describe("cleanupResource: bidirectional, deduplicated, each row through the rev
394
428
  const unrelated = orgRole(ALICE, "member", "globex");
395
429
  // Granted owner first, so the order below is the cleanup's, not the store's.
396
430
  for (const spec of [owner, member, asPrincipal, unrelated]) {
397
- await path.grant(spec, alice);
431
+ await path.grant(spec, alice, "grant");
398
432
  }
399
433
  recorded.length = 0;
400
434
 
401
435
  await path.cleanupResource(
402
436
  create(ApiResourceRefSchema, { kind: "organization", id: "acme" }),
437
+ alice,
403
438
  );
404
439
 
405
440
  expect([...policies.rows.keys()]).toEqual([policyIdFor(unrelated)]);
@@ -430,12 +465,13 @@ describe("cleanupResource: bidirectional, deduplicated, each row through the rev
430
465
  const owner = orgRole(ALICE, "owner", "acme");
431
466
  const member = orgRole(BOB, "member", "acme");
432
467
  for (const spec of [owner, member]) {
433
- await path.grant(spec, alice);
468
+ await path.grant(spec, alice, "grant");
434
469
  }
435
470
 
436
471
  await expect(
437
472
  path.cleanupResource(
438
473
  create(ApiResourceRefSchema, { kind: "organization", id: "acme" }),
474
+ alice,
439
475
  ),
440
476
  ).rejects.toThrow("tuple delete refused");
441
477
 
@@ -450,11 +486,12 @@ describe("cleanupResource: bidirectional, deduplicated, each row through the rev
450
486
  kind: "identity_account",
451
487
  id: ALICE,
452
488
  });
453
- await path.grant(selfOwner, alice);
489
+ await path.grant(selfOwner, alice, "grant");
454
490
  recorded.length = 0;
455
491
 
456
492
  await path.cleanupResource(
457
493
  create(ApiResourceRefSchema, { kind: "identity_account", id: ALICE }),
494
+ alice,
458
495
  );
459
496
 
460
497
  expect(
@@ -467,6 +504,7 @@ describe("cleanupResource: bidirectional, deduplicated, each row through the rev
467
504
  const { path } = pathOver(recorded, recordingLifecycle(recorded));
468
505
  await path.cleanupResource(
469
506
  create(ApiResourceRefSchema, { kind: "agent", id: AGENT }),
507
+ alice,
470
508
  );
471
509
  expect(recorded).toEqual([]);
472
510
  });
@@ -476,13 +514,13 @@ describe("revokeOrgAccess: the account's direct rows on the organization", () =>
476
514
  it("revokes every relation the account holds on that organization and nothing on another", async () => {
477
515
  const recorded: RecordedEvent[] = [];
478
516
  const { policies, path } = pathOver(recorded, recordingLifecycle(recorded));
479
- await path.grant(orgRole(ALICE, "admin", "acme"), alice);
480
- await path.grant(orgRole(ALICE, "viewer", "acme"), alice);
481
- await path.grant(orgRole(ALICE, "member", "globex"), alice);
482
- await path.grant(orgRole(BOB, "member", "acme"), alice);
517
+ await path.grant(orgRole(ALICE, "admin", "acme"), alice, "grant");
518
+ await path.grant(orgRole(ALICE, "viewer", "acme"), alice, "grant");
519
+ await path.grant(orgRole(ALICE, "member", "globex"), alice, "grant");
520
+ await path.grant(orgRole(BOB, "member", "acme"), alice, "grant");
483
521
  recorded.length = 0;
484
522
 
485
- await path.revokeOrgAccess(ALICE, "acme");
523
+ await path.revokeOrgAccess(ALICE, "acme", alice);
486
524
 
487
525
  expect([...policies.rows.keys()].sort()).toEqual(
488
526
  [
@@ -501,7 +539,537 @@ describe("revokeOrgAccess: the account's direct rows on the organization", () =>
501
539
  it("an account with no rows on the organization is a no-op", async () => {
502
540
  const recorded: RecordedEvent[] = [];
503
541
  const { path } = pathOver(recorded, recordingLifecycle(recorded));
504
- await path.revokeOrgAccess(BOB, "acme");
542
+ await path.revokeOrgAccess(BOB, "acme", alice);
543
+ expect(recorded).toEqual([]);
544
+ });
545
+ });
546
+
547
+ describe("leaving an organization: the account's rows on its resources", () => {
548
+ const SHARED = "agt_shared0000000000000000000";
549
+ const AUTHORED = "agt_authored000000000000000000";
550
+ const ELSEWHERE = "agt_elsewhere0000000000000000";
551
+ const TEAM = "tm_sre000000000000000000000000";
552
+ const SESSION = "ses_alice0000000000000000000000";
553
+ const RUN = "aex_alice0000000000000000000000";
554
+
555
+ /** The scope link a resource's creation writes: `organization:<org>` holds `organization` on it. */
556
+ function scopeLink(kind: string, id: string, org: string): IamPolicySpec {
557
+ return triple({ kind: "organization", id: org }, "organization", {
558
+ kind,
559
+ id,
560
+ });
561
+ }
562
+
563
+ const bob: CallerIdentity = {
564
+ ...alice,
565
+ identityId: BOB,
566
+ email: "bob@example.com",
567
+ };
568
+
569
+ /**
570
+ * alice holds acme's admin role, a viewer share on bob's agent, the
571
+ * owner row of the agent she created, an owner row bob granted her on
572
+ * his agent, a team membership, and her session's owner row with a run
573
+ * inside it; bob holds an agent in globex that alice may view.
574
+ */
575
+ async function seeded() {
576
+ const recorded: RecordedEvent[] = [];
577
+ const creators = recordedCreators({
578
+ [`agent:${SHARED}`]: BOB,
579
+ [`agent:${AUTHORED}`]: ALICE,
580
+ [`agent:${ELSEWHERE}`]: BOB,
581
+ [`session:${SESSION}`]: ALICE,
582
+ });
583
+ const { policies, path } = pathOver(
584
+ recorded,
585
+ recordingLifecycle(recorded),
586
+ creators,
587
+ );
588
+ const agent = (id: string) => ({ kind: "agent", id });
589
+ for (const spec of [
590
+ orgRole(ALICE, "admin", "acme"),
591
+ orgRole(ALICE, "member", "globex"),
592
+ scopeLink("agent", SHARED, "acme"),
593
+ scopeLink("agent", AUTHORED, "acme"),
594
+ scopeLink("agent", ELSEWHERE, "globex"),
595
+ scopeLink("team", TEAM, "acme"),
596
+ scopeLink("session", SESSION, "acme"),
597
+ triple({ kind: "session", id: SESSION }, "session", {
598
+ kind: "agent_execution",
599
+ id: RUN,
600
+ }),
601
+ triple({ kind: "identity_account", id: ALICE }, "viewer", agent(SHARED)),
602
+ triple({ kind: "identity_account", id: ALICE }, "owner", agent(AUTHORED)),
603
+ triple({ kind: "identity_account", id: ALICE }, "owner", agent(SHARED)),
604
+ triple(
605
+ { kind: "identity_account", id: ALICE },
606
+ "viewer",
607
+ agent(ELSEWHERE),
608
+ ),
609
+ triple({ kind: "identity_account", id: ALICE }, "member", {
610
+ kind: "team",
611
+ id: TEAM,
612
+ }),
613
+ triple({ kind: "identity_account", id: ALICE }, "owner", {
614
+ kind: "session",
615
+ id: SESSION,
616
+ }),
617
+ triple({ kind: "identity_account", id: ALICE }, "viewer", {
618
+ kind: "agent_execution",
619
+ id: RUN,
620
+ }),
621
+ ]) {
622
+ await path.grant(spec, bob, "grant");
623
+ }
624
+ recorded.length = 0;
625
+ return { recorded, policies, path };
626
+ }
627
+
628
+ function aliceHolds(policies: FakeIamPolicyStore): ReadonlyArray<string> {
629
+ return [...policies.rows.values()]
630
+ .filter((policy) => policy.spec?.principal?.id === ALICE)
631
+ .map(
632
+ (policy) =>
633
+ `${policy.spec?.relation} ${policy.spec?.resource?.kind}:${policy.spec?.resource?.id}`,
634
+ )
635
+ .sort();
636
+ }
637
+
638
+ it("revokeOrgAccess removes the shares and memberships the account held in the organization and keeps its authorship", async () => {
639
+ const { recorded, policies, path } = await seeded();
640
+
641
+ await path.revokeOrgAccess(ALICE, "acme", alice);
642
+
643
+ expect(aliceHolds(policies)).toEqual(
644
+ [
645
+ "member organization:globex",
646
+ `owner agent:${AUTHORED}`,
647
+ `owner session:${SESSION}`,
648
+ `viewer agent:${ELSEWHERE}`,
649
+ ].sort(),
650
+ );
651
+ // Every row went through the revoke order: the hook, then the delete.
652
+ const kinds = recorded.map((event) => event.kind);
653
+ expect(kinds.filter((kind) => kind === "revoked")).toHaveLength(5);
654
+ expect(kinds.filter((kind) => kind === "row-delete")).toHaveLength(5);
655
+ for (let at = 0; at < kinds.length; at += 2) {
656
+ expect(kinds.slice(at, at + 2)).toEqual(["revoked", "row-delete"]);
657
+ }
658
+ });
659
+
660
+ it("a resource below another is placed by its parent's scope link", async () => {
661
+ const { policies, path } = await seeded();
662
+
663
+ await path.revokeOrgAccess(ALICE, "acme", alice);
664
+
665
+ expect(aliceHolds(policies)).not.toContain(`viewer agent_execution:${RUN}`);
666
+ });
667
+
668
+ it("a retried revokeOrgAccess still sweeps when the organization rows are already gone", async () => {
669
+ const { policies, path } = await seeded();
670
+ for (const id of [policyIdFor(orgRole(ALICE, "admin", "acme"))]) {
671
+ policies.rows.delete(id);
672
+ }
673
+
674
+ await path.revokeOrgAccess(ALICE, "acme", alice);
675
+
676
+ expect(aliceHolds(policies)).not.toContain(`viewer agent:${SHARED}`);
677
+ expect(aliceHolds(policies)).not.toContain(`member team:${TEAM}`);
678
+ });
679
+
680
+ it("revoking the last role by spec sweeps the organization", async () => {
681
+ const { policies, path } = await seeded();
682
+
683
+ await path.revokeBySpec(orgRole(ALICE, "admin", "acme"), alice);
684
+
685
+ expect(aliceHolds(policies)).not.toContain(`viewer agent:${SHARED}`);
686
+ expect(aliceHolds(policies)).toContain(`owner agent:${AUTHORED}`);
687
+ });
688
+
689
+ it("revoking one role by spec while another remains sweeps nothing", async () => {
690
+ const { policies, path } = await seeded();
691
+ await path.grant(orgRole(ALICE, "viewer", "acme"), bob, "grant");
692
+
693
+ await path.revokeBySpec(orgRole(ALICE, "admin", "acme"), alice);
694
+
695
+ expect(aliceHolds(policies)).toContain(`viewer agent:${SHARED}`);
696
+ expect(aliceHolds(policies)).toContain(`member team:${TEAM}`);
697
+ });
698
+
699
+ it("revoking a row that is not an organization role sweeps nothing", async () => {
700
+ const { policies, path } = await seeded();
701
+
702
+ await path.revokeBySpec(
703
+ triple({ kind: "identity_account", id: ALICE }, "member", {
704
+ kind: "team",
705
+ id: TEAM,
706
+ }),
707
+ alice,
708
+ );
709
+
710
+ expect(aliceHolds(policies)).toContain(`viewer agent:${SHARED}`);
711
+ });
712
+
713
+ it("an organization's delete (cleanupResource) revokes its own rows and sweeps nothing else", async () => {
714
+ const { policies, path } = await seeded();
715
+
716
+ await path.cleanupResource(
717
+ create(ApiResourceRefSchema, { kind: "organization", id: "acme" }),
718
+ alice,
719
+ );
720
+
721
+ expect(aliceHolds(policies)).toContain(`viewer agent:${SHARED}`);
722
+ expect(aliceHolds(policies)).not.toContain("admin organization:acme");
723
+ });
724
+
725
+ it("an owner row whose principal is not the recorded creator is a grant, and goes", async () => {
726
+ const { policies, path } = await seeded();
727
+
728
+ await path.revokeOrgAccess(ALICE, "acme", alice);
729
+
730
+ expect(aliceHolds(policies)).not.toContain(`owner agent:${SHARED}`);
731
+ });
732
+ });
733
+
734
+ describe("affiliation: every change of an account's organization rows is announced", () => {
735
+ const TEAM = "tm_core000000000000000000000";
736
+
737
+ function announcing(faults: { changing?: Error } = {}) {
738
+ const recorded: RecordedEvent[] = [];
739
+ const { policies, path } = pathOver(
740
+ recorded,
741
+ recordingLifecycle(recorded, faults, { affiliation: true }),
742
+ );
743
+ return { recorded, policies, path };
744
+ }
745
+
746
+ it("a role granted announces changed after the row's grant hook, on the duplicate arm too", async () => {
747
+ const { recorded, path } = announcing();
748
+ await path.grant(orgRole(ALICE, "member", "acme"), alice, "grant");
749
+ await path.grant(orgRole(ALICE, "member", "acme"), alice, "grant");
750
+ expect(recorded.map((event) => event.kind)).toEqual([
751
+ "row-save",
752
+ "granted",
753
+ "affiliation-changed",
754
+ "granted",
755
+ "affiliation-changed",
756
+ ]);
757
+ expect(recorded.at(-1)).toEqual({
758
+ kind: "affiliation-changed",
759
+ pair: `${ALICE}@acme`,
760
+ });
761
+ });
762
+
763
+ it("a role revoked announces changing before its revoke hook and changed after the delete", async () => {
764
+ const { recorded, path } = announcing();
765
+ await path.grant(orgRole(ALICE, "admin", "acme"), alice, "grant");
766
+ await path.grant(orgRole(ALICE, "viewer", "acme"), alice, "grant");
767
+ recorded.length = 0;
768
+
769
+ await path.revokeBySpec(orgRole(ALICE, "admin", "acme"), alice);
770
+
771
+ expect(recorded.map((event) => event.kind)).toEqual([
772
+ "affiliation-changing",
773
+ "revoked",
774
+ "row-delete",
775
+ "affiliation-changed",
776
+ ]);
777
+ });
778
+
779
+ it("a revoke that finds no row still announces changed, so a tuple that outlived its rows heals", async () => {
780
+ const { recorded, path } = announcing();
781
+ await path.revokeBySpec(orgRole(ALICE, "member", "acme"), alice);
782
+ expect(recorded.map((event) => event.kind)).toEqual([
783
+ "revoked",
784
+ "affiliation-changed",
785
+ ]);
786
+ });
787
+
788
+ it("every path that deletes a role announces it: revokeOrgAccess and an organization's delete", async () => {
789
+ const { recorded, path } = announcing();
790
+ await path.grant(orgRole(ALICE, "admin", "acme"), alice, "grant");
791
+ await path.grant(orgRole(BOB, "member", "acme"), alice, "grant");
792
+ recorded.length = 0;
793
+
794
+ await path.revokeOrgAccess(ALICE, "acme", alice);
795
+ expect(
796
+ recorded.filter((event) => event.kind === "affiliation-changing"),
797
+ ).toEqual([{ kind: "affiliation-changing", pair: `${ALICE}@acme` }]);
798
+
799
+ recorded.length = 0;
800
+ await path.cleanupResource(
801
+ create(ApiResourceRefSchema, { kind: "organization", id: "acme" }),
802
+ alice,
803
+ );
804
+ expect(
805
+ recorded.filter((event) => event.kind === "affiliation-changing"),
806
+ ).toEqual([{ kind: "affiliation-changing", pair: `${BOB}@acme` }]);
807
+ });
808
+
809
+ it("a row of any other shape announces nothing: a share, a team membership, a userset on an organization", async () => {
810
+ const { recorded, path } = announcing();
811
+ await path.grant(
812
+ triple({ kind: "identity_account", id: ALICE }, "viewer", {
813
+ kind: "agent",
814
+ id: AGENT,
815
+ }),
816
+ alice,
817
+ "grant",
818
+ );
819
+ await path.grant(
820
+ triple({ kind: "identity_account", id: ALICE }, "member", {
821
+ kind: "team",
822
+ id: TEAM,
823
+ }),
824
+ alice,
825
+ "grant",
826
+ );
827
+ await path.grant(
828
+ triple({ kind: "team", id: TEAM, relation: "member" }, "viewer", {
829
+ kind: "organization",
830
+ id: "acme",
831
+ }),
832
+ alice,
833
+ "grant",
834
+ );
835
+ expect(
836
+ recorded.filter((event) => event.kind.startsWith("affiliation")),
837
+ ).toEqual([]);
838
+ });
839
+
840
+ it("a fault in changing stops the revoke with the row in place", async () => {
841
+ const fault = new Error("tuple store down");
842
+ const { policies, path } = announcing({ changing: fault });
843
+ await path.grant(orgRole(ALICE, "member", "acme"), alice, "grant");
844
+
845
+ await expect(
846
+ path.revokeBySpec(orgRole(ALICE, "member", "acme"), alice),
847
+ ).rejects.toBe(fault);
848
+
849
+ expect(
850
+ policies.rows.has(policyIdFor(orgRole(ALICE, "member", "acme"))),
851
+ ).toBe(true);
852
+ });
853
+
854
+ it("affiliated is refused as a grant before any read, write or hook", async () => {
855
+ const { recorded, path } = announcing();
856
+ const refusal = await path
857
+ .grant(orgRole(ALICE, "affiliated", "acme"), alice, "grant")
858
+ .then(
859
+ () => undefined,
860
+ (error: unknown) => error,
861
+ );
862
+ expect(refusal).toBeInstanceOf(ConnectError);
863
+ expect((refusal as ConnectError).code).toBe(Code.InvalidArgument);
864
+ expect((refusal as ConnectError).rawMessage).toBe(
865
+ AFFILIATION_NOT_GRANTABLE_MESSAGE,
866
+ );
505
867
  expect(recorded).toEqual([]);
506
868
  });
507
869
  });
870
+
871
+ describe("who and why: every access row reaches the store with its change record", () => {
872
+ const TEAM = "tm_audit0000000000000000000";
873
+ const SHARED = "agt_audited00000000000000000";
874
+ const bob: CallerIdentity = {
875
+ ...alice,
876
+ identityId: BOB,
877
+ email: "bob@example.com",
878
+ };
879
+ const aliceActor: PolicyActor = { id: ALICE, callerClass: "user" };
880
+ const bobActor: PolicyActor = { id: BOB, callerClass: "user" };
881
+
882
+ function scopeLink(kind: string, id: string, org: string): IamPolicySpec {
883
+ return triple({ kind: "organization", id: org }, "organization", {
884
+ kind,
885
+ id,
886
+ });
887
+ }
888
+
889
+ function share(principal: string, relation: string): IamPolicySpec {
890
+ return triple({ kind: "identity_account", id: principal }, relation, {
891
+ kind: "agent",
892
+ id: SHARED,
893
+ });
894
+ }
895
+
896
+ it("a fresh grant hands the store its actor, its door and its organization; a duplicate hands nothing", async () => {
897
+ const { policies, path } = pathOver([], undefined);
898
+ const spec = orgRole(BOB, "member", "acme");
899
+
900
+ await path.grant(spec, alice, "grant");
901
+ await path.grant(spec, alice, "grant");
902
+
903
+ expect(policies.changes).toEqual([
904
+ {
905
+ op: "save",
906
+ id: policyIdFor(spec),
907
+ record: { actor: aliceActor, cause: "grant", organizationId: "acme" },
908
+ },
909
+ ]);
910
+ });
911
+
912
+ it("the actor is the caller's id and class, never its email or token", async () => {
913
+ const { policies, path } = pathOver([], undefined);
914
+ await path.grant(orgRole(BOB, "member", "acme"), alice, "first_sign_in");
915
+ expect(policies.changes[0]?.record?.actor).toStrictEqual(aliceActor);
916
+ });
917
+
918
+ it("a structural link carries no record; a share below the organization finds it through the link, and a team's audience is an access row", async () => {
919
+ const { policies, path } = pathOver([], undefined);
920
+ const link = scopeLink("agent", SHARED, "acme");
921
+ const toTeam = triple(
922
+ { kind: "team", id: TEAM, relation: "member" },
923
+ "viewer",
924
+ { kind: "agent", id: SHARED },
925
+ );
926
+
927
+ await path.grant(link, alice, "structural");
928
+ await path.grant(share(BOB, "viewer"), alice, "grant");
929
+ await path.grant(toTeam, alice, "grant");
930
+
931
+ expect(policies.changes.map((change) => change.record)).toEqual([
932
+ undefined,
933
+ { actor: aliceActor, cause: "grant", organizationId: "acme" },
934
+ { actor: aliceActor, cause: "grant", organizationId: "acme" },
935
+ ]);
936
+ });
937
+
938
+ it("a resource whose organization cannot be found is recorded with none, never refused", async () => {
939
+ const { policies, path } = pathOver([], undefined);
940
+ await path.grant(share(BOB, "viewer"), alice, "grant");
941
+ expect(policies.changes[0]?.record?.organizationId).toBe("");
942
+ });
943
+
944
+ it("a revoke names its revoker; revoking a row that is not held records nothing", async () => {
945
+ const { policies, path } = pathOver([], undefined);
946
+ const spec = orgRole(BOB, "member", "acme");
947
+ await path.grant(orgRole(BOB, "admin", "acme"), alice, "grant");
948
+ await path.grant(spec, alice, "grant");
949
+ policies.changes.length = 0;
950
+
951
+ await path.revokeBySpec(spec, alice);
952
+ await path.revokeBySpec(orgRole(BOB, "viewer", "acme"), alice);
953
+
954
+ expect(policies.changes).toEqual([
955
+ {
956
+ op: "delete",
957
+ id: policyIdFor(spec),
958
+ record: { actor: aliceActor, cause: "revoke", organizationId: "acme" },
959
+ },
960
+ ]);
961
+ });
962
+
963
+ it("removing a person records the removal on their roles and left_organization on what the sweep takes, both as the remover", async () => {
964
+ const { policies, path } = pathOver([], undefined);
965
+ await path.grant(scopeLink("agent", SHARED, "acme"), alice, "structural");
966
+ await path.grant(orgRole(BOB, "member", "acme"), alice, "grant");
967
+ await path.grant(share(BOB, "viewer"), alice, "grant");
968
+ policies.changes.length = 0;
969
+
970
+ await path.revokeOrgAccess(BOB, "acme", alice);
971
+
972
+ expect(policies.changes).toEqual([
973
+ {
974
+ op: "delete",
975
+ id: policyIdFor(orgRole(BOB, "member", "acme")),
976
+ record: {
977
+ actor: aliceActor,
978
+ cause: "organization_access_revoked",
979
+ organizationId: "acme",
980
+ },
981
+ },
982
+ {
983
+ op: "delete",
984
+ id: policyIdFor(share(BOB, "viewer")),
985
+ record: {
986
+ actor: aliceActor,
987
+ cause: "left_organization",
988
+ organizationId: "acme",
989
+ },
990
+ },
991
+ ]);
992
+ });
993
+
994
+ it("the last role's revoke sweeps as the revoker", async () => {
995
+ const { policies, path } = pathOver([], undefined);
996
+ await path.grant(scopeLink("agent", SHARED, "acme"), alice, "structural");
997
+ await path.grant(orgRole(BOB, "member", "acme"), alice, "grant");
998
+ await path.grant(share(BOB, "viewer"), alice, "grant");
999
+ policies.changes.length = 0;
1000
+
1001
+ await path.revokeBySpec(orgRole(BOB, "member", "acme"), alice);
1002
+
1003
+ expect(policies.changes.map((change) => change.record?.cause)).toEqual([
1004
+ "revoke",
1005
+ "left_organization",
1006
+ ]);
1007
+ });
1008
+
1009
+ it("a resource's cleanup reads its organization before its scope link goes, and names whoever deleted it", async () => {
1010
+ const { policies, path } = pathOver([], undefined);
1011
+ const link = scopeLink("agent", SHARED, "acme");
1012
+ await path.grant(link, alice, "structural");
1013
+ await path.grant(share(ALICE, "viewer"), alice, "grant");
1014
+ await path.grant(share(BOB, "owner"), bob, "structural");
1015
+ policies.changes.length = 0;
1016
+
1017
+ await path.cleanupResource(
1018
+ create(ApiResourceRefSchema, { kind: "agent", id: SHARED }),
1019
+ bob,
1020
+ );
1021
+
1022
+ const deleted = {
1023
+ actor: bobActor,
1024
+ cause: "resource_deleted",
1025
+ organizationId: "acme",
1026
+ };
1027
+ expect(policies.changes).toEqual([
1028
+ { op: "delete", id: policyIdFor(link), record: undefined },
1029
+ {
1030
+ op: "delete",
1031
+ id: policyIdFor(share(ALICE, "viewer")),
1032
+ record: deleted,
1033
+ },
1034
+ { op: "delete", id: policyIdFor(share(BOB, "owner")), record: deleted },
1035
+ ]);
1036
+ });
1037
+
1038
+ it("the grant and revoke log lines carry who, why and where", async () => {
1039
+ const lines: Array<{ message: string; fields: Record<string, unknown> }> =
1040
+ [];
1041
+ const capture = (message: string, fields?: Record<string, unknown>) => {
1042
+ lines.push({ message, fields: fields ?? {} });
1043
+ };
1044
+ const path = newIamPolicyGrantPath({
1045
+ creators: NO_RECORDED_CREATORS,
1046
+ policies: fakeIamPolicyStore(),
1047
+ lifecycle: undefined,
1048
+ logger: { ...silentLogger, info: capture },
1049
+ });
1050
+ const spec = orgRole(BOB, "member", "acme");
1051
+
1052
+ await path.grant(spec, alice, "grant");
1053
+ await path.revokeBySpec(spec, alice);
1054
+
1055
+ expect(lines.map(({ message, fields }) => [message, fields])).toEqual([
1056
+ [
1057
+ "iam policy granted",
1058
+ expect.objectContaining({
1059
+ actorId: ALICE,
1060
+ actorClass: "user",
1061
+ cause: "grant",
1062
+ organizationId: "acme",
1063
+ }),
1064
+ ],
1065
+ [
1066
+ "iam policy revoked",
1067
+ expect.objectContaining({
1068
+ actorId: ALICE,
1069
+ cause: "revoke",
1070
+ organizationId: "acme",
1071
+ }),
1072
+ ],
1073
+ ]);
1074
+ });
1075
+ });