@wildo-ai/saas-models 1.1.0 → 1.1.2

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 (292) hide show
  1. package/dist/esm/applications/applications-context.schemas.d.ts +2 -0
  2. package/dist/esm/applications/applications-context.schemas.d.ts.map +1 -1
  3. package/dist/esm/billing/billing-account.shared.resources-config.schemas.d.ts +1 -1
  4. package/dist/esm/billing/billing-account.shared.resources-config.schemas.d.ts.map +1 -1
  5. package/dist/esm/billing/billing-account.shared.resources-config.schemas.js +36 -0
  6. package/dist/esm/billing/billing-account.shared.resources-config.schemas.js.map +1 -1
  7. package/dist/esm/billing/billing-account.shared.schemas.d.ts.map +1 -1
  8. package/dist/esm/billing/billing-account.shared.schemas.js +8 -1
  9. package/dist/esm/billing/billing-account.shared.schemas.js.map +1 -1
  10. package/dist/esm/billing/billing-types.shared.schemas.d.ts +2 -2
  11. package/dist/esm/billing/credit-pool.shared.resources-config.schemas.d.ts +1 -1
  12. package/dist/esm/billing/credit-pool.shared.resources-config.schemas.d.ts.map +1 -1
  13. package/dist/esm/billing/credit-pool.shared.resources-config.schemas.js +39 -0
  14. package/dist/esm/billing/credit-pool.shared.resources-config.schemas.js.map +1 -1
  15. package/dist/esm/billing/credit-pool.shared.schemas.d.ts +6 -6
  16. package/dist/esm/billing/credit-pool.shared.schemas.d.ts.map +1 -1
  17. package/dist/esm/billing/credit-pool.shared.schemas.js +8 -4
  18. package/dist/esm/billing/credit-pool.shared.schemas.js.map +1 -1
  19. package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.d.ts +1 -1
  20. package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.d.ts.map +1 -1
  21. package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.js +35 -0
  22. package/dist/esm/billing/invoice-ref.shared.resources-config.schemas.js.map +1 -1
  23. package/dist/esm/billing/invoice-ref.shared.schemas.d.ts.map +1 -1
  24. package/dist/esm/billing/invoice-ref.shared.schemas.js +8 -3
  25. package/dist/esm/billing/invoice-ref.shared.schemas.js.map +1 -1
  26. package/dist/esm/billing/subscription.shared.resources-config.schemas.d.ts +1 -1
  27. package/dist/esm/billing/subscription.shared.resources-config.schemas.d.ts.map +1 -1
  28. package/dist/esm/billing/subscription.shared.resources-config.schemas.js +39 -0
  29. package/dist/esm/billing/subscription.shared.resources-config.schemas.js.map +1 -1
  30. package/dist/esm/billing/subscription.shared.schemas.d.ts +4 -4
  31. package/dist/esm/billing/subscription.shared.schemas.d.ts.map +1 -1
  32. package/dist/esm/billing/subscription.shared.schemas.js +6 -2
  33. package/dist/esm/billing/subscription.shared.schemas.js.map +1 -1
  34. package/dist/esm/billing/usage-record.shared.resources-config.schemas.d.ts +1 -1
  35. package/dist/esm/billing/usage-record.shared.resources-config.schemas.d.ts.map +1 -1
  36. package/dist/esm/billing/usage-record.shared.resources-config.schemas.js +39 -0
  37. package/dist/esm/billing/usage-record.shared.resources-config.schemas.js.map +1 -1
  38. package/dist/esm/billing/usage-record.shared.schemas.d.ts +4 -4
  39. package/dist/esm/billing/usage-record.shared.schemas.d.ts.map +1 -1
  40. package/dist/esm/billing/usage-record.shared.schemas.js +7 -3
  41. package/dist/esm/billing/usage-record.shared.schemas.js.map +1 -1
  42. package/dist/esm/compliance/audit-trails/audit-logs.shared.resources-config.schemas.d.ts.map +1 -1
  43. package/dist/esm/compliance/audit-trails/audit-logs.shared.resources-config.schemas.js +35 -2
  44. package/dist/esm/compliance/audit-trails/audit-logs.shared.resources-config.schemas.js.map +1 -1
  45. package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.d.ts +61 -0
  46. package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.d.ts.map +1 -1
  47. package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.js +58 -0
  48. package/dist/esm/compliance/audit-trails/auditable-events.shared.schema.js.map +1 -1
  49. package/dist/esm/compliance/audit-trails/security-audit-event-envelope.shared.schema.d.ts.map +1 -1
  50. package/dist/esm/compliance/audit-trails/security-audit-event-envelope.shared.schema.js +14 -0
  51. package/dist/esm/compliance/audit-trails/security-audit-event-envelope.shared.schema.js.map +1 -1
  52. package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.d.ts +49 -0
  53. package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.d.ts.map +1 -1
  54. package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.js +124 -1
  55. package/dist/esm/compliance/privacy/impersonalization-scrub.shared.utils.js.map +1 -1
  56. package/dist/esm/compliance/privacy/impersonalization.shared.schemas.d.ts +11 -3
  57. package/dist/esm/compliance/privacy/impersonalization.shared.schemas.d.ts.map +1 -1
  58. package/dist/esm/compliance/privacy/impersonalization.shared.schemas.js +11 -3
  59. package/dist/esm/compliance/privacy/impersonalization.shared.schemas.js.map +1 -1
  60. package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.d.ts +93 -0
  61. package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.d.ts.map +1 -0
  62. package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.js +116 -0
  63. package/dist/esm/compliance/privacy/operating-jurisdictions.shared.schemas.js.map +1 -0
  64. package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.d.ts +67 -12
  65. package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.d.ts.map +1 -1
  66. package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.js +95 -8
  67. package/dist/esm/compliance/privacy/operator-compliance-identity.shared.schemas.js.map +1 -1
  68. package/dist/esm/compliance/privacy/redaction.shared.schemas.d.ts +21 -1
  69. package/dist/esm/compliance/privacy/redaction.shared.schemas.d.ts.map +1 -1
  70. package/dist/esm/compliance/privacy/redaction.shared.schemas.js +20 -0
  71. package/dist/esm/compliance/privacy/redaction.shared.schemas.js.map +1 -1
  72. package/dist/esm/config/app-configuration-shared.shared.schemas.d.ts +2 -0
  73. package/dist/esm/config/app-configuration-shared.shared.schemas.d.ts.map +1 -1
  74. package/dist/esm/config/app-configuration-shared.shared.schemas.js +29 -0
  75. package/dist/esm/config/app-configuration-shared.shared.schemas.js.map +1 -1
  76. package/dist/esm/config/frontend/navigation.shared.schemas.d.ts +33 -0
  77. package/dist/esm/config/frontend/navigation.shared.schemas.d.ts.map +1 -1
  78. package/dist/esm/config/frontend/navigation.shared.schemas.js +32 -0
  79. package/dist/esm/config/frontend/navigation.shared.schemas.js.map +1 -1
  80. package/dist/esm/errors/errors.custom-message-ref.shared.definitions.d.ts +12 -1
  81. package/dist/esm/errors/errors.custom-message-ref.shared.definitions.d.ts.map +1 -1
  82. package/dist/esm/errors/errors.custom-message-ref.shared.definitions.js +12 -1
  83. package/dist/esm/errors/errors.custom-message-ref.shared.definitions.js.map +1 -1
  84. package/dist/esm/external-data/external-data-pipeline.shared.schemas.d.ts +13 -0
  85. package/dist/esm/external-data/external-data-pipeline.shared.schemas.d.ts.map +1 -1
  86. package/dist/esm/external-data/external-data-pipeline.shared.schemas.js.map +1 -1
  87. package/dist/esm/external-providers/engine-capabilities.shared.schemas.d.ts +6 -6
  88. package/dist/esm/external-providers/engine-capabilities.shared.schemas.d.ts.map +1 -1
  89. package/dist/esm/external-providers/engine-capabilities.shared.schemas.js +26 -6
  90. package/dist/esm/external-providers/engine-capabilities.shared.schemas.js.map +1 -1
  91. package/dist/esm/external-providers/engine-capability-resolution-mode.shared.d.ts.map +1 -1
  92. package/dist/esm/external-providers/engine-capability-resolution-mode.shared.js +0 -2
  93. package/dist/esm/external-providers/engine-capability-resolution-mode.shared.js.map +1 -1
  94. package/dist/esm/external-providers/frontend-bootstrap.shared.schemas.d.ts +1 -0
  95. package/dist/esm/external-providers/frontend-bootstrap.shared.schemas.d.ts.map +1 -1
  96. package/dist/esm/external-providers/provider-capability.shared.schemas.d.ts +4 -3
  97. package/dist/esm/external-providers/provider-capability.shared.schemas.d.ts.map +1 -1
  98. package/dist/esm/external-providers/provider-capability.shared.schemas.js +4 -3
  99. package/dist/esm/external-providers/provider-capability.shared.schemas.js.map +1 -1
  100. package/dist/esm/features/user-features.shared.resources-config.schemas.d.ts +1 -1
  101. package/dist/esm/features/user-features.shared.resources-config.schemas.d.ts.map +1 -1
  102. package/dist/esm/features/user-features.shared.resources-config.schemas.js +48 -0
  103. package/dist/esm/features/user-features.shared.resources-config.schemas.js.map +1 -1
  104. package/dist/esm/files/file-upload-grant.shared.schemas.d.ts +144 -0
  105. package/dist/esm/files/file-upload-grant.shared.schemas.d.ts.map +1 -0
  106. package/dist/esm/files/file-upload-grant.shared.schemas.js +136 -0
  107. package/dist/esm/files/file-upload-grant.shared.schemas.js.map +1 -0
  108. package/dist/esm/files/files.shared.schemas.d.ts +45 -4
  109. package/dist/esm/files/files.shared.schemas.d.ts.map +1 -1
  110. package/dist/esm/files/files.shared.schemas.js +48 -4
  111. package/dist/esm/files/files.shared.schemas.js.map +1 -1
  112. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.d.ts +1 -1
  113. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.d.ts.map +1 -1
  114. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.js +33 -0
  115. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.resources-config.schemas.js.map +1 -1
  116. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.schemas.d.ts.map +1 -1
  117. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.schemas.js +6 -2
  118. package/dist/esm/flows-actors/a2a-push-delivery-log.shared.schemas.js.map +1 -1
  119. package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.d.ts +1 -1
  120. package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.d.ts.map +1 -1
  121. package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.js +53 -0
  122. package/dist/esm/flows-actors/a2a-token-budget-state.shared.resources-config.schemas.js.map +1 -1
  123. package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.d.ts +1 -1
  124. package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.d.ts.map +1 -1
  125. package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.js +43 -0
  126. package/dist/esm/flows-actors/flows-actors-execution.shared.resources-config.schemas.js.map +1 -1
  127. package/dist/esm/flows-actors/flows-actors-execution.shared.schemas.d.ts.map +1 -1
  128. package/dist/esm/flows-actors/flows-actors-execution.shared.schemas.js +8 -3
  129. package/dist/esm/flows-actors/flows-actors-execution.shared.schemas.js.map +1 -1
  130. package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.d.ts +1 -1
  131. package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.d.ts.map +1 -1
  132. package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.js +41 -0
  133. package/dist/esm/flows-actors/flows-actors-task.shared.resources-config.schemas.js.map +1 -1
  134. package/dist/esm/flows-actors/flows-actors-task.shared.schemas.d.ts +0 -7
  135. package/dist/esm/flows-actors/flows-actors-task.shared.schemas.d.ts.map +1 -1
  136. package/dist/esm/flows-actors/flows-actors-task.shared.schemas.js +23 -13
  137. package/dist/esm/flows-actors/flows-actors-task.shared.schemas.js.map +1 -1
  138. package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.d.ts +1 -1
  139. package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.d.ts.map +1 -1
  140. package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.js +51 -0
  141. package/dist/esm/guidance/guidance-state.shared.resources-config.schemas.js.map +1 -1
  142. package/dist/esm/guidance/guidance-state.shared.schemas.d.ts.map +1 -1
  143. package/dist/esm/guidance/guidance-state.shared.schemas.js +8 -2
  144. package/dist/esm/guidance/guidance-state.shared.schemas.js.map +1 -1
  145. package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.d.ts +1 -1
  146. package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.d.ts.map +1 -1
  147. package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.js +51 -0
  148. package/dist/esm/guidance/lifecycle-state.shared.resources-config.schemas.js.map +1 -1
  149. package/dist/esm/guidance/lifecycle-state.shared.schemas.d.ts.map +1 -1
  150. package/dist/esm/guidance/lifecycle-state.shared.schemas.js +28 -3
  151. package/dist/esm/guidance/lifecycle-state.shared.schemas.js.map +1 -1
  152. package/dist/esm/guidance/progression-state.shared.resources-config.schemas.d.ts +1 -1
  153. package/dist/esm/guidance/progression-state.shared.resources-config.schemas.d.ts.map +1 -1
  154. package/dist/esm/guidance/progression-state.shared.resources-config.schemas.js +51 -0
  155. package/dist/esm/guidance/progression-state.shared.resources-config.schemas.js.map +1 -1
  156. package/dist/esm/guidance/progression-state.shared.schemas.d.ts.map +1 -1
  157. package/dist/esm/guidance/progression-state.shared.schemas.js +18 -4
  158. package/dist/esm/guidance/progression-state.shared.schemas.js.map +1 -1
  159. package/dist/esm/http-api-binding/http-api-binding.shared.schemas.d.ts +37 -3
  160. package/dist/esm/http-api-binding/http-api-binding.shared.schemas.d.ts.map +1 -1
  161. package/dist/esm/http-api-binding/http-api-binding.shared.schemas.js +29 -3
  162. package/dist/esm/http-api-binding/http-api-binding.shared.schemas.js.map +1 -1
  163. package/dist/esm/index.d.ts +6 -0
  164. package/dist/esm/index.d.ts.map +1 -1
  165. package/dist/esm/index.js +6 -0
  166. package/dist/esm/index.js.map +1 -1
  167. package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.d.ts +70 -0
  168. package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.d.ts.map +1 -0
  169. package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.js +71 -0
  170. package/dist/esm/notifications/m2m/m2m-webhook-delivery-contract.shared.js.map +1 -0
  171. package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.d.ts +1 -1
  172. package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.d.ts.map +1 -1
  173. package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.js +36 -0
  174. package/dist/esm/notifications/notification-badges.shared.resources-config.schemas.js.map +1 -1
  175. package/dist/esm/organizations/organization-members.shared.resources-config.schemas.d.ts +1 -1
  176. package/dist/esm/organizations/organization-members.shared.resources-config.schemas.d.ts.map +1 -1
  177. package/dist/esm/organizations/organization-members.shared.resources-config.schemas.js +44 -0
  178. package/dist/esm/organizations/organization-members.shared.resources-config.schemas.js.map +1 -1
  179. package/dist/esm/organizations/organization-members.shared.schemas.d.ts.map +1 -1
  180. package/dist/esm/organizations/organization-members.shared.schemas.js +15 -4
  181. package/dist/esm/organizations/organization-members.shared.schemas.js.map +1 -1
  182. package/dist/esm/organizations/organizations.shared.resources-config.schemas.d.ts.map +1 -1
  183. package/dist/esm/organizations/organizations.shared.resources-config.schemas.js +18 -10
  184. package/dist/esm/organizations/organizations.shared.resources-config.schemas.js.map +1 -1
  185. package/dist/esm/queue/jobs.shared.resources-config.schemas.d.ts.map +1 -1
  186. package/dist/esm/queue/jobs.shared.resources-config.schemas.js +17 -0
  187. package/dist/esm/queue/jobs.shared.resources-config.schemas.js.map +1 -1
  188. package/dist/esm/requests/websocket.shared.schemas.d.ts +59 -0
  189. package/dist/esm/requests/websocket.shared.schemas.d.ts.map +1 -1
  190. package/dist/esm/requests/websocket.shared.schemas.js +103 -0
  191. package/dist/esm/requests/websocket.shared.schemas.js.map +1 -1
  192. package/dist/esm/resources/collection-query-parameters.shared.d.ts +22 -0
  193. package/dist/esm/resources/collection-query-parameters.shared.d.ts.map +1 -0
  194. package/dist/esm/resources/collection-query-parameters.shared.js +22 -0
  195. package/dist/esm/resources/collection-query-parameters.shared.js.map +1 -0
  196. package/dist/esm/resources/resources-config.shared.factory.d.ts.map +1 -1
  197. package/dist/esm/resources/resources-config.shared.factory.js +86 -13
  198. package/dist/esm/resources/resources-config.shared.factory.js.map +1 -1
  199. package/dist/esm/resources/resources-config.shared.schemas.d.ts +162 -41
  200. package/dist/esm/resources/resources-config.shared.schemas.d.ts.map +1 -1
  201. package/dist/esm/resources/resources-config.shared.schemas.js +109 -20
  202. package/dist/esm/resources/resources-config.shared.schemas.js.map +1 -1
  203. package/dist/esm/resources/utils/resources-config-operations.dtos-builder.utils.d.ts.map +1 -1
  204. package/dist/esm/resources/utils/resources-config-operations.dtos-builder.utils.js +21 -6
  205. package/dist/esm/resources/utils/resources-config-operations.dtos-builder.utils.js.map +1 -1
  206. package/dist/esm/security/authentications/authentication.shared.schemas.d.ts +57 -0
  207. package/dist/esm/security/authentications/authentication.shared.schemas.d.ts.map +1 -1
  208. package/dist/esm/security/authentications/authentication.shared.schemas.js +64 -0
  209. package/dist/esm/security/authentications/authentication.shared.schemas.js.map +1 -1
  210. package/dist/esm/security/authentications/consumable-token.shared.schemas.d.ts +17 -1
  211. package/dist/esm/security/authentications/consumable-token.shared.schemas.d.ts.map +1 -1
  212. package/dist/esm/security/authentications/consumable-token.shared.schemas.js +16 -0
  213. package/dist/esm/security/authentications/consumable-token.shared.schemas.js.map +1 -1
  214. package/dist/esm/security/authentications/mfa-backup-code-contract.shared.d.ts +28 -0
  215. package/dist/esm/security/authentications/mfa-backup-code-contract.shared.d.ts.map +1 -0
  216. package/dist/esm/security/authentications/mfa-backup-code-contract.shared.js +28 -0
  217. package/dist/esm/security/authentications/mfa-backup-code-contract.shared.js.map +1 -0
  218. package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.d.ts +21 -0
  219. package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.d.ts.map +1 -0
  220. package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.js +21 -0
  221. package/dist/esm/security/authentications/siem/siem-delivery-reliability-contract.shared.js.map +1 -0
  222. package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.d.ts +1 -1
  223. package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.d.ts.map +1 -1
  224. package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.js +43 -0
  225. package/dist/esm/security/authorizations/platform-access-grants.shared.resources-config.schemas.js.map +1 -1
  226. package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.d.ts +0 -8
  227. package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.d.ts.map +1 -1
  228. package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.js +13 -4
  229. package/dist/esm/security/authorizations/platform-access-grants.shared.schemas.js.map +1 -1
  230. package/dist/esm/security/authorizations/roles.shared.schemas.d.ts +16 -1
  231. package/dist/esm/security/authorizations/roles.shared.schemas.d.ts.map +1 -1
  232. package/dist/esm/security/authorizations/roles.shared.schemas.js +16 -1
  233. package/dist/esm/security/authorizations/roles.shared.schemas.js.map +1 -1
  234. package/dist/esm/security/authorizations/roles.shared.utils.d.ts +54 -0
  235. package/dist/esm/security/authorizations/roles.shared.utils.d.ts.map +1 -1
  236. package/dist/esm/security/authorizations/roles.shared.utils.js +59 -0
  237. package/dist/esm/security/authorizations/roles.shared.utils.js.map +1 -1
  238. package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.d.ts +61 -0
  239. package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.d.ts.map +1 -1
  240. package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.js +13 -1
  241. package/dist/esm/security/oauth-clients/oauth-clients.shared.schemas.js.map +1 -1
  242. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.d.ts +1 -1
  243. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.d.ts.map +1 -1
  244. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.js +35 -0
  245. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.js.map +1 -1
  246. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.schemas.d.ts.map +1 -1
  247. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.schemas.js +7 -1
  248. package/dist/esm/security/oauth-clients/oauth-consent-grants.shared.schemas.js.map +1 -1
  249. package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.d.ts +1 -1
  250. package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.d.ts.map +1 -1
  251. package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.js +34 -0
  252. package/dist/esm/users/inbound-contacts.shared.resources-config.schemas.js.map +1 -1
  253. package/dist/esm/users/inbound-contacts.shared.schemas.d.ts.map +1 -1
  254. package/dist/esm/users/inbound-contacts.shared.schemas.js +7 -4
  255. package/dist/esm/users/inbound-contacts.shared.schemas.js.map +1 -1
  256. package/dist/esm/users/user-credentials.shared.resources-config.schemas.d.ts +1 -1
  257. package/dist/esm/users/user-credentials.shared.resources-config.schemas.d.ts.map +1 -1
  258. package/dist/esm/users/user-credentials.shared.resources-config.schemas.js +37 -0
  259. package/dist/esm/users/user-credentials.shared.resources-config.schemas.js.map +1 -1
  260. package/dist/esm/users/user-credentials.shared.schemas.d.ts.map +1 -1
  261. package/dist/esm/users/user-credentials.shared.schemas.js +17 -2
  262. package/dist/esm/users/user-credentials.shared.schemas.js.map +1 -1
  263. package/dist/esm/users/user-identity-links.shared.resources-config.schemas.d.ts +1 -1
  264. package/dist/esm/users/user-identity-links.shared.resources-config.schemas.d.ts.map +1 -1
  265. package/dist/esm/users/user-identity-links.shared.resources-config.schemas.js +36 -0
  266. package/dist/esm/users/user-identity-links.shared.resources-config.schemas.js.map +1 -1
  267. package/dist/esm/users/user-identity-links.shared.schemas.d.ts.map +1 -1
  268. package/dist/esm/users/user-identity-links.shared.schemas.js +15 -5
  269. package/dist/esm/users/user-identity-links.shared.schemas.js.map +1 -1
  270. package/dist/esm/users/user-preferences.shared.resources-config.schemas.d.ts +1 -1
  271. package/dist/esm/users/user-preferences.shared.resources-config.schemas.d.ts.map +1 -1
  272. package/dist/esm/users/user-preferences.shared.resources-config.schemas.js +53 -0
  273. package/dist/esm/users/user-preferences.shared.resources-config.schemas.js.map +1 -1
  274. package/dist/esm/users/user-profiles.shared.resources-config.schemas.d.ts +1 -1
  275. package/dist/esm/users/user-profiles.shared.resources-config.schemas.d.ts.map +1 -1
  276. package/dist/esm/users/user-profiles.shared.resources-config.schemas.js +62 -0
  277. package/dist/esm/users/user-profiles.shared.resources-config.schemas.js.map +1 -1
  278. package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.d.ts +1 -1
  279. package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.d.ts.map +1 -1
  280. package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.js +53 -0
  281. package/dist/esm/users/user-self-preferences.shared.resources-config.schemas.js.map +1 -1
  282. package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.d.ts +1 -1
  283. package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.d.ts.map +1 -1
  284. package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.js +62 -0
  285. package/dist/esm/users/user-self-profiles.shared.resources-config.schemas.js.map +1 -1
  286. package/dist/esm/users/users.shared.schemas.d.ts +19 -0
  287. package/dist/esm/users/users.shared.schemas.d.ts.map +1 -1
  288. package/dist/esm/users/users.shared.schemas.js +41 -16
  289. package/dist/esm/users/users.shared.schemas.js.map +1 -1
  290. package/dist/tsconfig.build.tsbuildinfo +1 -1
  291. package/package.json +5 -6
  292. package/dist/esm/.builder.pid +0 -9
@@ -17,6 +17,7 @@ import { CoreConsumableTokenTypes, ConsumableToken_ConsumptionMode, } from '../s
17
17
  import { DurationUnit } from '@wildo-ai/zod-decorators';
18
18
  import { CORE_APP_ROLES, CORE_ORG_ROLES } from '../security/authorizations/roles.shared.schemas.js';
19
19
  import { DataPortabilityProvenance } from '../compliance/privacy/subject-export.shared.schemas.js';
20
+ import { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas.js';
20
21
  // Custom operations enum for organization members.
21
22
  // NOTE: cross-organization member transfer is intentionally NOT a framework
22
23
  // capability — moving a member between organizations is a tenant-boundary
@@ -111,6 +112,26 @@ export const organizationMembersResourceConfiguration_InitializationFactory = (r
111
112
  * Subject-export disclosure. The subject's OWN membership rows (org, roles, title) — not other members' data.
112
113
  */
113
114
  portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.OBSERVED },
115
+ /**
116
+ * Subject erasure RETAINS this row and scrubs the person's contact and professional details.
117
+ *
118
+ * The membership survives because it is the tenant's record of who belonged to it and under what
119
+ * authority; what does not survive is `userEmail` and `userTitle`, the denormalized copies of the
120
+ * person held here so a row can be read without resolving the user.
121
+ *
122
+ * `roles` is deliberately NOT scrubbed. It is this application's authorization vocabulary rather than
123
+ * personal data — the field's own comment says so — and emptying it could leave a tenant with no
124
+ * administrator, which the administrative-continuity floors would refuse, making an erasure fail closed
125
+ * on a tenant. Keeping it means that interaction never arises.
126
+ */
127
+ retentionPolicy: {
128
+ mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,
129
+ },
130
+ /**
131
+ * Erasing one of these rows revokes NO session. A membership is a record ABOUT a person's place in a
132
+ * tenant, not the person — their session ends through the `USERS` row, which is `SELF_PRINCIPAL`.
133
+ */
134
+ dataSubjectPolicy: { kind: ResourceDataSubjectKind.NO_PRINCIPAL },
114
135
  coreOperations: [
115
136
  CoreResourceOperation.READ,
116
137
  CoreResourceOperation.LIST,
@@ -119,9 +140,32 @@ export const organizationMembersResourceConfiguration_InitializationFactory = (r
119
140
  CoreResourceOperation.UPDATE,
120
141
  CoreResourceOperation.DELETE,
121
142
  CoreResourceOperation.COUNT,
143
+ // Route-less; `retentionPolicy` requires READ + LIST + UPDATE_MANY.
144
+ CoreResourceOperation.UPDATE_MANY,
122
145
  ],
123
146
  customOperation: OrganizationMembers_Operations,
124
147
  operationsConfiguration: {
148
+ /*
149
+
150
+ * Route-less retention plumbing — INTERNAL_CALL registers no route, so no API surface is added, and it
151
+
152
+ * must not be REPOSITORY_ONLY: the retention primitives resolve through `getServiceOperationPathDefault`,
153
+
154
+ * which matches only INTERNAL_CALL / API_CALL. LIST is LOW (a read-shaped op may not claim the
155
+
156
+ * HIGH/CRITICAL band, which carries an audit-emission obligation); UPDATE_MANY is CRITICAL — it is the
157
+
158
+ * verb the impersonalize writer uses to stamp the retention markers.
159
+
160
+ */
161
+ [CoreResourceOperation.UPDATE_MANY]: {
162
+ variants: [{
163
+ variantType: ResourceOperationVariantType.INTERNAL_CALL,
164
+ isDefault: true,
165
+ roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],
166
+ riskLevel: ResourceOperationRiskLevel.CRITICAL,
167
+ }],
168
+ },
125
169
  [CoreResourceOperation.COUNT]: {
126
170
  variants: [
127
171
  {
@@ -1 +1 @@
1
- {"version":3,"file":"organization-members.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/organizations/organization-members.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Organization Members Shared Resource Configuration\n *\n * Resource configuration for organization member management.\n * Comprehensive member lifecycle and role management.\n */\n\nimport { z } from 'zod';\nimport {\n OrganizationMemberSchema,\n OrganizationMemberStatus,\n} from './organization-members.shared.schemas';\nimport { CoreResourceOperation } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourcePrimaryScope, ResourceRelationship, DefaultDeleteResponseSchema } from '../resources/resources.shared.schemas';\nimport { CoreResourceType, coreResourceShared_FieldIdentifier } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationVariantType } from '../resources/resources.shared.schemas';\nimport { CoreUserNotificationTarget, CoreUserNotificationChannel } from '../notifications/notifications.shared.schemas';\n\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport {\n CoreConsumableTokenTypes,\n ConsumableToken_ConsumptionMode,\n} from '../security/authentications/consumable-token.shared.schemas';\nimport type {\n TokenRevocationConfig,\n} from '../security/authentications/consumable-token.shared.schemas';\nimport { DurationUnit } from '@wildo-ai/zod-decorators';\n\nimport { CORE_APP_ROLES, CORE_ORG_ROLES } from '../security/authorizations/roles.shared.schemas';\nimport { DataPortabilityProvenance } from '../compliance/privacy/subject-export.shared.schemas';\n\n// Custom operations enum for organization members.\n// NOTE: cross-organization member transfer is intentionally NOT a framework\n// capability — moving a member between organizations is a tenant-boundary\n// crossing the framework does not sanction. Re-invite the identity in the\n// target organization instead.\n//\n// A membership row IS the invitation (there is no separate invitation resource):\n// - CREATE = invite (creates an `INVITED` row + mints the accept token).\n// - REVOKE = inviter/admin cancels a still-`INVITED` membership.\n// - RESEND = re-mint + re-send the accept token for an `INVITED` membership.\n// - SUSPEND/REACTIVATE = lifecycle of an ACTIVE/SUSPENDED member.\n// Invitee-side accept/reject is the tokenized `/auth/acceptInvitation` flow\n// (auth controller + `Auth_Invitation` screen), NOT a resource operation.\n//\n// ROLE CHANGES ARE `UPDATE`, NOT A DEDICATED VERB. `PROMOTE` / `DEMOTE` were declared here\n// as `ORG_ADMIN`-gated `riskLevel: HIGH` operations taking `{ newRole, reason }`, and were\n// removed on 2026-08-01: they were ROUTED but never implemented, so the request fell through\n// to the generic core UPDATE, which persists only fields the resource schema declares.\n// `newRole` is not one, so every call answered **HTTP 200 having changed nothing** — and the\n// dangerous direction is DEMOTE: an admin demotes a compromised account, is told 200 OK, and\n// the account keeps its privileges. Re-declaring an explicit role-change verb is legitimate,\n// but only WITH a custom implementation that writes `roles` and calls\n// `assertRequestedRolesWithinCallerCeiling` — a custom implementation is addressed by an EXACT\n// operation path, so it does NOT inherit the UPDATE variant's ceiling gate. Follow the\n// organization-units MOVE/ARCHIVE pattern (a `prefixCoreOperations` hook returning the field\n// patch the core UPDATE persists). See\n// `.claude/plans/routed-unimplemented-operations-fail-closed.md`.\n//\n// GRANT_OWNERSHIP is the ONE exception to \"role changes are UPDATE\", and it exists for a reason\n// UPDATE structurally cannot serve: it is the only operation on this resource a caller who is NOT a\n// member of the organization may perform. See its config entry below.\nexport enum OrganizationMembers_Operations {\n SUSPEND = 'suspend',\n REACTIVATE = 'reactivate',\n REVOKE = 'revoke',\n RESEND = 'resend',\n GRANT_OWNERSHIP = 'grantOwnership',\n}\n\n/**\n * One semantic definition for invalidating an organization invitation.\n *\n * REVOKE attaches it declaratively, so HTTP and trusted service callers enter\n * the same transactional revocation pipeline. Ordinary DELETE is\n * intentionally excluded: it removes a non-INVITED membership, which has no\n * invitation token to revoke.\n */\nexport const ORGANIZATION_MEMBER_INVITATION_TOKEN_REVOCATION = {\n tokenTypes: [\n CoreConsumableTokenTypes.ORGANIZATION_MEMBER_INVITATION,\n ],\n relatedIdField: '_id',\n} satisfies TokenRevocationConfig;\n\n/**\n * One predicate owns both invitation-token generation and invitation-email\n * dispatch. Keeping the two declarative side effects on the same named gate\n * prevents the polymorphic CREATE operation from drifting: an ACTIVE direct\n * add must produce neither an accept token nor an invitation email.\n */\nconst isPendingOrganizationMemberInvitation = ({\n currentObject,\n}: {\n currentObject: Record<string, unknown>;\n}): boolean =>\n currentObject.status === OrganizationMemberStatus.INVITED;\n\nconst ORGANIZATION_MEMBERS_SORT_FIELDS = ['status', 'role', 'joinedAt'];\n\nconst ORGANIZATION_MEMBERS_FILTER_FIELDS = {\n status: z.enum(OrganizationMemberStatus),\n role: z.string(),\n};\n\ntype OrganizationMembersCoreOperations =\n | CoreResourceOperation.READ\n | CoreResourceOperation.LIST\n | CoreResourceOperation.SEARCH\n | CoreResourceOperation.CREATE\n | CoreResourceOperation.UPDATE\n | CoreResourceOperation.DELETE\n | CoreResourceOperation.COUNT;\n\n/*\nOrganization Members Resource Configuration Factory\n*/\nexport const organizationMembersResourceConfiguration_InitializationFactory = (resourcesRelationships: ResourceRelationship[]) => createResourceConfiguration_Initialization<\n typeof OrganizationMembers_Operations,\n OrganizationMembersCoreOperations,\n typeof OrganizationMemberSchema\n>({\n mainSchema: OrganizationMemberSchema,\n resourceIdentifier: CoreResourceType.ORGANIZATION_MEMBERS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.ORGANIZATION_MEMBERS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n\n // create: invitation-accept was the historical opt-in; now the org-admin INVITE, SSO/SCIM\n // JIT, and org-auto-create-on-registration all create members in-process with explicit\n // `userId` + `organizationId` FKs on a request-context-less EC (stripped by the\n // client-shaped CREATE DTO, re-attached by `createAsSystem`).\n // read/list: the invitee's own pending-invitation inbox reads INVITED rows cross-org\n // BEFORE the invitee is a scoped member — authorized by `userId`-ownership at the\n // entry point (`listMyPendingInvitations`/`loadOwnedPendingInvitation`), not org role.\n // update: invitation ACCEPTANCE flips INVITED → ACTIVE in-process; the invitee is not an\n // org admin, so the status flip goes through the trusted system path.\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.ALLOWED,\n create: ResourceSystemAccessMode.ALLOWED,\n read: ResourceSystemAccessMode.ALLOWED,\n list: ResourceSystemAccessMode.ALLOWED,\n update: ResourceSystemAccessMode.ALLOWED,\n },\n\n /**\n * Subject-export disclosure. The subject's OWN membership rows (org, roles, title) — not other members' data.\n */\n portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.OBSERVED },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.SEARCH,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n CoreResourceOperation.COUNT,\n ],\n\n customOperation: OrganizationMembers_Operations,\n\n operationsConfiguration: {\n [CoreResourceOperation.COUNT]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER],\n riskLevel: ResourceOperationRiskLevel.LOW,\n }\n ]\n },\n\n // CREATE is POLYMORPHIC — two intents on one op:\n // • INVITE (BRANCH B): the org admin supplies `userEmail` (the strict identity\n // anchor); the CREATE prefix find-or-creates the `User` by it (`PENDING_INVITATION`\n // if new) and FORCES `status: INVITED`. A single-use accept token is minted AFTER the\n // row is created (relatedId = the membership `_id`, STABLE across `INVITED → ACTIVE`\n // so the emailed link never orphans); the pipeline injects it into the invite email and\n // the dispatcher builds the accept link (`emailLinkPath → /invitation?token=…`, consumed\n // by `/auth/acceptInvitation`). No `grantedRoles`: the accept handler reads the\n // membership's own `roles` when it flips the row to ACTIVE.\n // • DIRECT-ADD (BRANCH A): the caller supplies `userId` (founder org-owner grant,\n // SSO-JIT, SCIM) and the row persists `ACTIVE` — there is nothing to accept.\n // Token generation and notification dispatch share one named predicate.\n // The INVITE branch mints one accept token and sends one post-commit email;\n // the ACTIVE direct-add branch does neither.\n [CoreResourceOperation.CREATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can invite members\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n // requestDto auto-derived from decorators (userEmail + roles +\n // organizationUnitsMembership + userTitle; userId/status/invitedByUserId/\n // invitedAt are server-injected via `.excludeFromCreate()`).\n tokenGeneration: {\n tokenType: CoreConsumableTokenTypes.ORGANIZATION_MEMBER_INVITATION,\n consumptionMode: ConsumableToken_ConsumptionMode.SINGLE_USE,\n expiresIn: { value: 7, unit: DurationUnit.DAYS },\n relatedIdField: '_id',\n emailLinkPath: '/invitation',\n // Mint the accept token for real invites only — never for an ACTIVE direct-add.\n condition: isPendingOrganizationMemberInvitation,\n },\n }\n ],\n // Deliver the invite email to the INVITEE (`userEmail`) via the\n // email-by-address path — the invitee may be a brand-new `PENDING_INVITATION`\n // user with no session. Carries the `acceptUrl` (`/invitation?token=…`) built\n // from the token minted above. `target: USER_SELF` is retained only so the\n // derived template ref stays `…create.user-self` and the accept link routes to\n // the main (non-admin) app.\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n recipientEmailField: 'userEmail',\n condition: isPendingOrganizationMemberInvitation,\n }\n ]\n },\n\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER], // Org members can read member info\n riskLevel: ResourceOperationRiskLevel.LOW,\n }\n ]\n },\n\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER], // Org members can list organization members\n riskLevel: ResourceOperationRiskLevel.LOW,\n }\n ]\n },\n\n [CoreResourceOperation.SEARCH]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER],\n riskLevel: ResourceOperationRiskLevel.LOW,\n isSearchable: true,\n // `userEmail` is a denormalized, immutable own-field (mirror of the linked\n // user's email, captured at membership creation). First/last name are NOT\n // denormalized onto the member row — they live on `USER_PROFILES` and are\n // mutable there — so they are searched RELATIONALLY: the framework runs a\n // secondary match on `USER_PROFILES` (its own searchable fields) and folds\n // the matching `userId`s into an `$in` on this row's `userId`. The member\n // query stays org-scoped, so this is tenant-safe; names are never stale\n // because they are read live from the profile at search time. See\n // SearchableRelationDescriptor in resources-config.shared.schemas.ts.\n searchableFields: [\n 'userEmail',\n {\n localField: 'userId',\n relatedResource: CoreResourceType.USER_PROFILES,\n relatedJoinField: 'userId',\n fields: ['firstName', 'lastName'],\n },\n ],\n searchableOptions: { caseSensitive: false, fullMatchOnly: false },\n filterFields: ORGANIZATION_MEMBERS_FILTER_FIELDS,\n sortFields: ORGANIZATION_MEMBERS_SORT_FIELDS,\n maxPaginatedResultPerPageLimit: 50,\n }\n ],\n },\n\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can update member details\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n // requestDto auto-derived from decorators\n //\n // Audited despite being below the HIGH/CRITICAL floor: since PROMOTE / DEMOTE were removed\n // (2026-08-01), **this operation IS the only org-member role-change path**, and a role\n // change is an access-control decision whatever the verb's risk level says. MEDIUM is the\n // right answer for the UI axis riskLevel primarily serves — updating a member's details\n // does not deserve a danger-zone affordance or a typed confirmation — but it is the wrong\n // answer for the evidence axis, and this flag is exactly that divergence.\n //\n // This also closes the follow-up left open by the routed-unimplemented-operations sweep:\n // `CoreAuditableEventType.ORGANIZATION_MEMBER_ROLE_CHANGED` is declared and classified but\n // emitted by nothing, so org-member role changes produced no trail at all. The generic\n // mechanism covers it — with `roles` marked as audit evidence, the row carries the before\n // and after sets — rather than adding a sixth hand-rolled emission.\n //\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE — evidence of privilege grant/revocation.\n auditableEvent: { auditBelowRiskFloor: true },\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can remove members\n riskLevel: ResourceOperationRiskLevel.HIGH,\n // Accepted/non-pending members use ordinary DELETE. A pending invite\n // is cancelled through REVOKE, which atomically revokes its accept\n // token. Keeping the vocabularies disjoint avoids making every\n // active-member removal token-coupled.\n enabledCondition: ({ currentObject }) =>\n currentObject.status !== OrganizationMemberStatus.INVITED,\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n /**\n * The higher-tier repair door for the organization-owner floor — the second half of the\n * industry pattern described in `.claude/rules/administrative-continuity-floors.md`.\n *\n * ## Why this cannot be UPDATE\n *\n * The floor refuses to let the last `ORG_OWNER` be removed, demoted or deactivated. That is the\n * correct tenant-tier answer, but on its own it converts every mistake that DID strand a tenant\n * — a direct database edit, a restore from a mid-erasure backup, an acknowledged erasure under\n * GDPR Art. 17, a seed that authored no owner — into a permanent lockout, because\n * `validateParentScopeAccess` denies a non-member on ORGANIZATIONS scope and there is no\n * `APP_ADMIN_SUPER_ADMIN` bypass. Both denials are pinned in\n * `cross-tenant-super-admin-reach.mongo.integration.test.ts`. So no tenant member can grant the\n * role (they would need the authority they just lost) and no platform administrator can reach\n * the row at all. GitHub solves this with an Enterprise owner one tier up; this is that tier.\n *\n * ## Why it is a distinct verb rather than a flag on UPDATE\n *\n * `admitsCrossTenantPlatformAdministration` relaxes the SCOPE check for the variant that carries\n * it. Putting it on generic UPDATE would open every field of every membership row in every\n * tenant to a cross-tenant caller. The whole safety argument is that the opened surface is\n * exactly one field with one direction, so it is authored as its own operation whose custom\n * implementation ignores the payload for anything but the justification.\n *\n * ## Deliberate narrowness\n *\n * - **Promotes an EXISTING member** (Microsoft Loop's rule). It cannot mint a membership, so it\n * never creates an identity inside a tenant nobody owns.\n * - **Additive only.** It adds the owner-conferring role; it cannot remove roles, so it can\n * never be used to demote and is not a second path through the floor.\n * - **`justification` is required**, not optional. This is a break-glass action against a tenant\n * the operator is not a member of; the audit row is worth nothing without the why. `AC-2(7)`\n * asks for privileged assignment to be monitored — the trail IS the control here, since by\n * construction no authorization check downstream can second-guess the caller.\n *\n * assurance-control: WILDO.ACCESS.ADMINISTRATIVE_CONTINUITY — restore administrability of a scope\n * that has no usable administrator.\n * assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE — evidence of privilege grant.\n */\n [OrganizationMembers_Operations.GRANT_OWNERSHIP]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n // Platform tier only. This is the role set the operation-roles gate checks INDEPENDENTLY\n // of the scope admission below: a caller holding this key at ORGANIZATIONS scope resolves\n // it against the organization role table, which does not contain it, and is denied there.\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n // THE cross-tenant admission. Without it the operation is unreachable by the only\n // principal it exists for, because a platform administrator is by definition not a member\n // of the stranded tenant.\n admitsCrossTenantPlatformAdministration: true,\n requestDto: z.object({\n justification: z.string().min(1).max(1000),\n }),\n // No `auditableEvent` declaration: CRITICAL is inside `AUTOMATIC_AUDIT_RISK_FLOOR`, so the\n // event emits on the risk level alone and `auditBelowRiskFloor` would be inert here. The\n // before/after role sets ride along because `roles` carries `.isAuditEvidence()` — which is\n // what makes the trail answer the question that matters about this door: WHO was made an\n // owner of a tenant, by a caller who was not in it.\n },\n ],\n userNotifications: [\n {\n // The tenant learns that its ownership changed from outside. An operator repairing a\n // stranded tenant is legitimate; an operator quietly granting themselves an ally inside a\n // customer's tenant is not, and the difference is only visible to the tenant if it is told.\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n },\n ],\n },\n\n [OrganizationMembers_Operations.SUSPEND]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can suspend members\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n requestDto: z.object({\n suspensionReason: z.string().min(1).max(500),\n suspensionEndDate: z.date().optional()\n }),\n // Implemented by the shared status-transition factory (ACTIVE → SUSPENDED, carrying\n // `suspensionEndDate` onto the row's `scheduledReactivationAt`). SUSPEND is legal only\n // from ACTIVE: an INVITED row has not accepted yet — cancel it with REVOKE, which also\n // revokes the accept token — and an INACTIVE one is already out.\n enabledCondition: ({ currentObject }) =>\n currentObject.status === OrganizationMemberStatus.ACTIVE,\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n },\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n [OrganizationMembers_Operations.REACTIVATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can reactivate members\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n requestDto: z.object({\n reason: z.string().max(500).optional()\n }),\n // Implemented by the shared status-transition factory (SUSPENDED → ACTIVE, clearing any\n // pending `scheduledReactivationAt`). Legal only from SUSPENDED: reactivating an INVITED\n // row would skip acceptance and grant a membership nobody agreed to.\n enabledCondition: ({ currentObject }) =>\n currentObject.status === OrganizationMemberStatus.SUSPENDED,\n // MEDIUM sits below `AUTOMATIC_AUDIT_RISK_FLOOR`, so without this the RESTORE half of the\n // pair would go untrailed while SUSPEND (CRITICAL) is recorded — a trail showing every\n // suspension and none of the lifts, which reads as a member still suspended a year after\n // they were reinstated. The risk level answers \"how alarming is the affordance\"; this\n // answers \"is it evidence an auditor needs\", and for the inverse of a CRITICAL verb it is.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n auditableEvent: { auditBelowRiskFloor: true },\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n },\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n // REVOKE — inviter/admin cancels a still-`INVITED` membership. The exact\n // custom implementation DELETES the row and atomically revokes its accept\n // token. The paired PENDING_INVITATION user is retained here and reaped by\n // deferred hygiene only after it is old and still has no memberships:\n // point-operation cleanup cannot predicate-lock \"zero memberships\" against\n // a concurrent invitation insert under Mongo snapshot isolation. `reason`\n // is captured in the audit payload, not persisted on the deleted row.\n [OrganizationMembers_Operations.REVOKE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n resourceOperationLike: CoreResourceOperation.DELETE,\n requestDto: z.object({\n reason: z.string().max(500).optional()\n }),\n tokenRevocation:\n ORGANIZATION_MEMBER_INVITATION_TOKEN_REVOCATION,\n // REVOKE has an exact API custom registration; `resourceOperationLike`\n // selects DELETE persistence/DTO semantics but does not inherit the\n // core DELETE custom implementation. Its real return is the delete\n // envelope, declared explicitly so output validation matches runtime.\n customResponseDto: DefaultDeleteResponseSchema,\n // Only a still-pending invite can be revoked — an ACTIVE member is removed\n // via DELETE, not \"revoked\". Gates the roster action (paired with the preset's\n // `WHEN_AVAILABLE`, the button shows only on INVITED rows) AND fail-closes the\n // backend (`services-registry-handler` throws SERVICE_RESOURCE_OPERATION_DISABLED\n // → 403 if invoked on a non-INVITED row).\n enabledCondition: ({ currentObject }) => currentObject.status === OrganizationMemberStatus.INVITED,\n },\n ],\n // Two emails (distinct templates, disambiguated by `customNotificationRef`):\n // • the INVITEE (`recipientEmailField: 'userEmail'`) — \"your invitation was revoked\";\n // • the ADMIN/initiator (`USER_SELF`) — confirmation of the action.\n // Both USER_SELF+EMAIL, so each MUST carry a unique `customNotificationRef`.\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n recipientEmailField: 'userEmail',\n customNotificationRef: 'invitee',\n },\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n customNotificationRef: 'admin',\n }\n ]\n },\n\n // RESEND — re-mint the accept token (revoking any prior one for this membership)\n // and re-send the invite email with the fresh accept link. Only valid while the\n // membership is `INVITED`.\n [OrganizationMembers_Operations.RESEND]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n requestDto: z.object({}),\n tokenGeneration: {\n tokenType: CoreConsumableTokenTypes.ORGANIZATION_MEMBER_INVITATION,\n consumptionMode: ConsumableToken_ConsumptionMode.SINGLE_USE,\n expiresIn: { value: 7, unit: DurationUnit.DAYS },\n relatedIdField: '_id',\n revokeExistingForSameRelated: true,\n emailLinkPath: '/invitation',\n condition: isPendingOrganizationMemberInvitation,\n },\n // Resend is meaningful only while the invite is pending. Same dual enforcement\n // as REVOKE: hides the roster action on non-INVITED rows and 403s server-side.\n enabledCondition: ({ currentObject }) => currentObject.status === OrganizationMemberStatus.INVITED,\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n recipientEmailField: 'userEmail',\n condition: isPendingOrganizationMemberInvitation,\n }\n ]\n }\n },\n});\n"]}
1
+ {"version":3,"file":"organization-members.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/organizations/organization-members.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Organization Members Shared Resource Configuration\n *\n * Resource configuration for organization member management.\n * Comprehensive member lifecycle and role management.\n */\n\nimport { z } from 'zod';\nimport {\n OrganizationMemberSchema,\n OrganizationMemberStatus,\n} from './organization-members.shared.schemas';\nimport { CoreResourceOperation } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourcePrimaryScope, ResourceRelationship, DefaultDeleteResponseSchema } from '../resources/resources.shared.schemas';\nimport { CoreResourceType, coreResourceShared_FieldIdentifier } from '../resources/resources.shared.core.definitions';\nimport { ResourceOperationVariantType } from '../resources/resources.shared.schemas';\nimport { CoreUserNotificationTarget, CoreUserNotificationChannel } from '../notifications/notifications.shared.schemas';\n\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport {\n CoreConsumableTokenTypes,\n ConsumableToken_ConsumptionMode,\n} from '../security/authentications/consumable-token.shared.schemas';\nimport type {\n TokenRevocationConfig,\n} from '../security/authentications/consumable-token.shared.schemas';\nimport { DurationUnit } from '@wildo-ai/zod-decorators';\n\nimport { CORE_APP_ROLES, CORE_ORG_ROLES } from '../security/authorizations/roles.shared.schemas';\nimport { DataPortabilityProvenance } from '../compliance/privacy/subject-export.shared.schemas';\nimport { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas';\n\n// Custom operations enum for organization members.\n// NOTE: cross-organization member transfer is intentionally NOT a framework\n// capability — moving a member between organizations is a tenant-boundary\n// crossing the framework does not sanction. Re-invite the identity in the\n// target organization instead.\n//\n// A membership row IS the invitation (there is no separate invitation resource):\n// - CREATE = invite (creates an `INVITED` row + mints the accept token).\n// - REVOKE = inviter/admin cancels a still-`INVITED` membership.\n// - RESEND = re-mint + re-send the accept token for an `INVITED` membership.\n// - SUSPEND/REACTIVATE = lifecycle of an ACTIVE/SUSPENDED member.\n// Invitee-side accept/reject is the tokenized `/auth/acceptInvitation` flow\n// (auth controller + `Auth_Invitation` screen), NOT a resource operation.\n//\n// ROLE CHANGES ARE `UPDATE`, NOT A DEDICATED VERB. `PROMOTE` / `DEMOTE` were declared here\n// as `ORG_ADMIN`-gated `riskLevel: HIGH` operations taking `{ newRole, reason }`, and were\n// removed on 2026-08-01: they were ROUTED but never implemented, so the request fell through\n// to the generic core UPDATE, which persists only fields the resource schema declares.\n// `newRole` is not one, so every call answered **HTTP 200 having changed nothing** — and the\n// dangerous direction is DEMOTE: an admin demotes a compromised account, is told 200 OK, and\n// the account keeps its privileges. Re-declaring an explicit role-change verb is legitimate,\n// but only WITH a custom implementation that writes `roles` and calls\n// `assertRequestedRolesWithinCallerCeiling` — a custom implementation is addressed by an EXACT\n// operation path, so it does NOT inherit the UPDATE variant's ceiling gate. Follow the\n// organization-units MOVE/ARCHIVE pattern (a `prefixCoreOperations` hook returning the field\n// patch the core UPDATE persists). See\n// `.claude/plans/routed-unimplemented-operations-fail-closed.md`.\n//\n// GRANT_OWNERSHIP is the ONE exception to \"role changes are UPDATE\", and it exists for a reason\n// UPDATE structurally cannot serve: it is the only operation on this resource a caller who is NOT a\n// member of the organization may perform. See its config entry below.\nexport enum OrganizationMembers_Operations {\n SUSPEND = 'suspend',\n REACTIVATE = 'reactivate',\n REVOKE = 'revoke',\n RESEND = 'resend',\n GRANT_OWNERSHIP = 'grantOwnership',\n}\n\n/**\n * One semantic definition for invalidating an organization invitation.\n *\n * REVOKE attaches it declaratively, so HTTP and trusted service callers enter\n * the same transactional revocation pipeline. Ordinary DELETE is\n * intentionally excluded: it removes a non-INVITED membership, which has no\n * invitation token to revoke.\n */\nexport const ORGANIZATION_MEMBER_INVITATION_TOKEN_REVOCATION = {\n tokenTypes: [\n CoreConsumableTokenTypes.ORGANIZATION_MEMBER_INVITATION,\n ],\n relatedIdField: '_id',\n} satisfies TokenRevocationConfig;\n\n/**\n * One predicate owns both invitation-token generation and invitation-email\n * dispatch. Keeping the two declarative side effects on the same named gate\n * prevents the polymorphic CREATE operation from drifting: an ACTIVE direct\n * add must produce neither an accept token nor an invitation email.\n */\nconst isPendingOrganizationMemberInvitation = ({\n currentObject,\n}: {\n currentObject: Record<string, unknown>;\n}): boolean =>\n currentObject.status === OrganizationMemberStatus.INVITED;\n\nconst ORGANIZATION_MEMBERS_SORT_FIELDS = ['status', 'role', 'joinedAt'];\n\nconst ORGANIZATION_MEMBERS_FILTER_FIELDS = {\n status: z.enum(OrganizationMemberStatus),\n role: z.string(),\n};\n\ntype OrganizationMembersCoreOperations =\n | CoreResourceOperation.READ\n | CoreResourceOperation.LIST\n | CoreResourceOperation.SEARCH\n | CoreResourceOperation.CREATE\n | CoreResourceOperation.UPDATE\n | CoreResourceOperation.DELETE\n | CoreResourceOperation.COUNT\n // Route-less; `retentionPolicy` requires READ + LIST + UPDATE_MANY.\n | CoreResourceOperation.UPDATE_MANY;\n\n/*\nOrganization Members Resource Configuration Factory\n*/\nexport const organizationMembersResourceConfiguration_InitializationFactory = (resourcesRelationships: ResourceRelationship[]) => createResourceConfiguration_Initialization<\n typeof OrganizationMembers_Operations,\n OrganizationMembersCoreOperations,\n typeof OrganizationMemberSchema\n>({\n mainSchema: OrganizationMemberSchema,\n resourceIdentifier: CoreResourceType.ORGANIZATION_MEMBERS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.ORGANIZATION_MEMBERS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n\n // create: invitation-accept was the historical opt-in; now the org-admin INVITE, SSO/SCIM\n // JIT, and org-auto-create-on-registration all create members in-process with explicit\n // `userId` + `organizationId` FKs on a request-context-less EC (stripped by the\n // client-shaped CREATE DTO, re-attached by `createAsSystem`).\n // read/list: the invitee's own pending-invitation inbox reads INVITED rows cross-org\n // BEFORE the invitee is a scoped member — authorized by `userId`-ownership at the\n // entry point (`listMyPendingInvitations`/`loadOwnedPendingInvitation`), not org role.\n // update: invitation ACCEPTANCE flips INVITED → ACTIVE in-process; the invitee is not an\n // org admin, so the status flip goes through the trusted system path.\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.ALLOWED,\n create: ResourceSystemAccessMode.ALLOWED,\n read: ResourceSystemAccessMode.ALLOWED,\n list: ResourceSystemAccessMode.ALLOWED,\n update: ResourceSystemAccessMode.ALLOWED,\n },\n\n /**\n * Subject-export disclosure. The subject's OWN membership rows (org, roles, title) — not other members' data.\n */\n portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.OBSERVED },\n\n /**\n * Subject erasure RETAINS this row and scrubs the person's contact and professional details.\n *\n * The membership survives because it is the tenant's record of who belonged to it and under what\n * authority; what does not survive is `userEmail` and `userTitle`, the denormalized copies of the\n * person held here so a row can be read without resolving the user.\n *\n * `roles` is deliberately NOT scrubbed. It is this application's authorization vocabulary rather than\n * personal data — the field's own comment says so — and emptying it could leave a tenant with no\n * administrator, which the administrative-continuity floors would refuse, making an erasure fail closed\n * on a tenant. Keeping it means that interaction never arises.\n */\n retentionPolicy: {\n mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,\n },\n\n /**\n * Erasing one of these rows revokes NO session. A membership is a record ABOUT a person's place in a\n * tenant, not the person — their session ends through the `USERS` row, which is `SELF_PRINCIPAL`.\n */\n dataSubjectPolicy: { kind: ResourceDataSubjectKind.NO_PRINCIPAL },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.SEARCH,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n CoreResourceOperation.COUNT,\n // Route-less; `retentionPolicy` requires READ + LIST + UPDATE_MANY.\n CoreResourceOperation.UPDATE_MANY,\n ],\n\n customOperation: OrganizationMembers_Operations,\n\n operationsConfiguration: {\n\n /*\n\n * Route-less retention plumbing — INTERNAL_CALL registers no route, so no API surface is added, and it\n\n * must not be REPOSITORY_ONLY: the retention primitives resolve through `getServiceOperationPathDefault`,\n\n * which matches only INTERNAL_CALL / API_CALL. LIST is LOW (a read-shaped op may not claim the\n\n * HIGH/CRITICAL band, which carries an audit-emission obligation); UPDATE_MANY is CRITICAL — it is the\n\n * verb the impersonalize writer uses to stamp the retention markers.\n\n */\n\n [CoreResourceOperation.UPDATE_MANY]: {\n\n variants: [{\n\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n\n isDefault: true,\n\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n\n }],\n\n },\n\n [CoreResourceOperation.COUNT]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER],\n riskLevel: ResourceOperationRiskLevel.LOW,\n }\n ]\n },\n\n // CREATE is POLYMORPHIC — two intents on one op:\n // • INVITE (BRANCH B): the org admin supplies `userEmail` (the strict identity\n // anchor); the CREATE prefix find-or-creates the `User` by it (`PENDING_INVITATION`\n // if new) and FORCES `status: INVITED`. A single-use accept token is minted AFTER the\n // row is created (relatedId = the membership `_id`, STABLE across `INVITED → ACTIVE`\n // so the emailed link never orphans); the pipeline injects it into the invite email and\n // the dispatcher builds the accept link (`emailLinkPath → /invitation?token=…`, consumed\n // by `/auth/acceptInvitation`). No `grantedRoles`: the accept handler reads the\n // membership's own `roles` when it flips the row to ACTIVE.\n // • DIRECT-ADD (BRANCH A): the caller supplies `userId` (founder org-owner grant,\n // SSO-JIT, SCIM) and the row persists `ACTIVE` — there is nothing to accept.\n // Token generation and notification dispatch share one named predicate.\n // The INVITE branch mints one accept token and sends one post-commit email;\n // the ACTIVE direct-add branch does neither.\n [CoreResourceOperation.CREATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can invite members\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n // requestDto auto-derived from decorators (userEmail + roles +\n // organizationUnitsMembership + userTitle; userId/status/invitedByUserId/\n // invitedAt are server-injected via `.excludeFromCreate()`).\n tokenGeneration: {\n tokenType: CoreConsumableTokenTypes.ORGANIZATION_MEMBER_INVITATION,\n consumptionMode: ConsumableToken_ConsumptionMode.SINGLE_USE,\n expiresIn: { value: 7, unit: DurationUnit.DAYS },\n relatedIdField: '_id',\n emailLinkPath: '/invitation',\n // Mint the accept token for real invites only — never for an ACTIVE direct-add.\n condition: isPendingOrganizationMemberInvitation,\n },\n }\n ],\n // Deliver the invite email to the INVITEE (`userEmail`) via the\n // email-by-address path — the invitee may be a brand-new `PENDING_INVITATION`\n // user with no session. Carries the `acceptUrl` (`/invitation?token=…`) built\n // from the token minted above. `target: USER_SELF` is retained only so the\n // derived template ref stays `…create.user-self` and the accept link routes to\n // the main (non-admin) app.\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n recipientEmailField: 'userEmail',\n condition: isPendingOrganizationMemberInvitation,\n }\n ]\n },\n\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER], // Org members can read member info\n riskLevel: ResourceOperationRiskLevel.LOW,\n }\n ]\n },\n\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER], // Org members can list organization members\n riskLevel: ResourceOperationRiskLevel.LOW,\n }\n ]\n },\n\n [CoreResourceOperation.SEARCH]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_MEMBER],\n riskLevel: ResourceOperationRiskLevel.LOW,\n isSearchable: true,\n // `userEmail` is a denormalized, immutable own-field (mirror of the linked\n // user's email, captured at membership creation). First/last name are NOT\n // denormalized onto the member row — they live on `USER_PROFILES` and are\n // mutable there — so they are searched RELATIONALLY: the framework runs a\n // secondary match on `USER_PROFILES` (its own searchable fields) and folds\n // the matching `userId`s into an `$in` on this row's `userId`. The member\n // query stays org-scoped, so this is tenant-safe; names are never stale\n // because they are read live from the profile at search time. See\n // SearchableRelationDescriptor in resources-config.shared.schemas.ts.\n searchableFields: [\n 'userEmail',\n {\n localField: 'userId',\n relatedResource: CoreResourceType.USER_PROFILES,\n relatedJoinField: 'userId',\n fields: ['firstName', 'lastName'],\n },\n ],\n searchableOptions: { caseSensitive: false, fullMatchOnly: false },\n filterFields: ORGANIZATION_MEMBERS_FILTER_FIELDS,\n sortFields: ORGANIZATION_MEMBERS_SORT_FIELDS,\n maxPaginatedResultPerPageLimit: 50,\n }\n ],\n },\n\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can update member details\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n // requestDto auto-derived from decorators\n //\n // Audited despite being below the HIGH/CRITICAL floor: since PROMOTE / DEMOTE were removed\n // (2026-08-01), **this operation IS the only org-member role-change path**, and a role\n // change is an access-control decision whatever the verb's risk level says. MEDIUM is the\n // right answer for the UI axis riskLevel primarily serves — updating a member's details\n // does not deserve a danger-zone affordance or a typed confirmation — but it is the wrong\n // answer for the evidence axis, and this flag is exactly that divergence.\n //\n // This also closes the follow-up left open by the routed-unimplemented-operations sweep:\n // `CoreAuditableEventType.ORGANIZATION_MEMBER_ROLE_CHANGED` is declared and classified but\n // emitted by nothing, so org-member role changes produced no trail at all. The generic\n // mechanism covers it — with `roles` marked as audit evidence, the row carries the before\n // and after sets — rather than adding a sixth hand-rolled emission.\n //\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE — evidence of privilege grant/revocation.\n auditableEvent: { auditBelowRiskFloor: true },\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can remove members\n riskLevel: ResourceOperationRiskLevel.HIGH,\n // Accepted/non-pending members use ordinary DELETE. A pending invite\n // is cancelled through REVOKE, which atomically revokes its accept\n // token. Keeping the vocabularies disjoint avoids making every\n // active-member removal token-coupled.\n enabledCondition: ({ currentObject }) =>\n currentObject.status !== OrganizationMemberStatus.INVITED,\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n /**\n * The higher-tier repair door for the organization-owner floor — the second half of the\n * industry pattern described in `.claude/rules/administrative-continuity-floors.md`.\n *\n * ## Why this cannot be UPDATE\n *\n * The floor refuses to let the last `ORG_OWNER` be removed, demoted or deactivated. That is the\n * correct tenant-tier answer, but on its own it converts every mistake that DID strand a tenant\n * — a direct database edit, a restore from a mid-erasure backup, an acknowledged erasure under\n * GDPR Art. 17, a seed that authored no owner — into a permanent lockout, because\n * `validateParentScopeAccess` denies a non-member on ORGANIZATIONS scope and there is no\n * `APP_ADMIN_SUPER_ADMIN` bypass. Both denials are pinned in\n * `cross-tenant-super-admin-reach.mongo.integration.test.ts`. So no tenant member can grant the\n * role (they would need the authority they just lost) and no platform administrator can reach\n * the row at all. GitHub solves this with an Enterprise owner one tier up; this is that tier.\n *\n * ## Why it is a distinct verb rather than a flag on UPDATE\n *\n * `admitsCrossTenantPlatformAdministration` relaxes the SCOPE check for the variant that carries\n * it. Putting it on generic UPDATE would open every field of every membership row in every\n * tenant to a cross-tenant caller. The whole safety argument is that the opened surface is\n * exactly one field with one direction, so it is authored as its own operation whose custom\n * implementation ignores the payload for anything but the justification.\n *\n * ## Deliberate narrowness\n *\n * - **Promotes an EXISTING member** (Microsoft Loop's rule). It cannot mint a membership, so it\n * never creates an identity inside a tenant nobody owns.\n * - **Additive only.** It adds the owner-conferring role; it cannot remove roles, so it can\n * never be used to demote and is not a second path through the floor.\n * - **`justification` is required**, not optional. This is a break-glass action against a tenant\n * the operator is not a member of; the audit row is worth nothing without the why. `AC-2(7)`\n * asks for privileged assignment to be monitored — the trail IS the control here, since by\n * construction no authorization check downstream can second-guess the caller.\n *\n * assurance-control: WILDO.ACCESS.ADMINISTRATIVE_CONTINUITY — restore administrability of a scope\n * that has no usable administrator.\n * assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE — evidence of privilege grant.\n */\n [OrganizationMembers_Operations.GRANT_OWNERSHIP]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n // Platform tier only. This is the role set the operation-roles gate checks INDEPENDENTLY\n // of the scope admission below: a caller holding this key at ORGANIZATIONS scope resolves\n // it against the organization role table, which does not contain it, and is denied there.\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n // THE cross-tenant admission. Without it the operation is unreachable by the only\n // principal it exists for, because a platform administrator is by definition not a member\n // of the stranded tenant.\n admitsCrossTenantPlatformAdministration: true,\n requestDto: z.object({\n justification: z.string().min(1).max(1000),\n }),\n // No `auditableEvent` declaration: CRITICAL is inside `AUTOMATIC_AUDIT_RISK_FLOOR`, so the\n // event emits on the risk level alone and `auditBelowRiskFloor` would be inert here. The\n // before/after role sets ride along because `roles` carries `.isAuditEvidence()` — which is\n // what makes the trail answer the question that matters about this door: WHO was made an\n // owner of a tenant, by a caller who was not in it.\n },\n ],\n userNotifications: [\n {\n // The tenant learns that its ownership changed from outside. An operator repairing a\n // stranded tenant is legitimate; an operator quietly granting themselves an ally inside a\n // customer's tenant is not, and the difference is only visible to the tenant if it is told.\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n },\n ],\n },\n\n [OrganizationMembers_Operations.SUSPEND]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can suspend members\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n requestDto: z.object({\n suspensionReason: z.string().min(1).max(500),\n suspensionEndDate: z.date().optional()\n }),\n // Implemented by the shared status-transition factory (ACTIVE → SUSPENDED, carrying\n // `suspensionEndDate` onto the row's `scheduledReactivationAt`). SUSPEND is legal only\n // from ACTIVE: an INVITED row has not accepted yet — cancel it with REVOKE, which also\n // revokes the accept token — and an INACTIVE one is already out.\n enabledCondition: ({ currentObject }) =>\n currentObject.status === OrganizationMemberStatus.ACTIVE,\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n },\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n [OrganizationMembers_Operations.REACTIVATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN], // Org admins can reactivate members\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n requestDto: z.object({\n reason: z.string().max(500).optional()\n }),\n // Implemented by the shared status-transition factory (SUSPENDED → ACTIVE, clearing any\n // pending `scheduledReactivationAt`). Legal only from SUSPENDED: reactivating an INVITED\n // row would skip acceptance and grant a membership nobody agreed to.\n enabledCondition: ({ currentObject }) =>\n currentObject.status === OrganizationMemberStatus.SUSPENDED,\n // MEDIUM sits below `AUTOMATIC_AUDIT_RISK_FLOOR`, so without this the RESTORE half of the\n // pair would go untrailed while SUSPEND (CRITICAL) is recorded — a trail showing every\n // suspension and none of the lifts, which reads as a member still suspended a year after\n // they were reinstated. The risk level answers \"how alarming is the affordance\"; this\n // answers \"is it evidence an auditor needs\", and for the inverse of a CRITICAL verb it is.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n auditableEvent: { auditBelowRiskFloor: true },\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n },\n {\n target: CoreUserNotificationTarget.ORGANIZATION_USERS,\n channel: CoreUserNotificationChannel.EMAIL,\n }\n ]\n },\n\n // REVOKE — inviter/admin cancels a still-`INVITED` membership. The exact\n // custom implementation DELETES the row and atomically revokes its accept\n // token. The paired PENDING_INVITATION user is retained here and reaped by\n // deferred hygiene only after it is old and still has no memberships:\n // point-operation cleanup cannot predicate-lock \"zero memberships\" against\n // a concurrent invitation insert under Mongo snapshot isolation. `reason`\n // is captured in the audit payload, not persisted on the deleted row.\n [OrganizationMembers_Operations.REVOKE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n resourceOperationLike: CoreResourceOperation.DELETE,\n requestDto: z.object({\n reason: z.string().max(500).optional()\n }),\n tokenRevocation:\n ORGANIZATION_MEMBER_INVITATION_TOKEN_REVOCATION,\n // REVOKE has an exact API custom registration; `resourceOperationLike`\n // selects DELETE persistence/DTO semantics but does not inherit the\n // core DELETE custom implementation. Its real return is the delete\n // envelope, declared explicitly so output validation matches runtime.\n customResponseDto: DefaultDeleteResponseSchema,\n // Only a still-pending invite can be revoked — an ACTIVE member is removed\n // via DELETE, not \"revoked\". Gates the roster action (paired with the preset's\n // `WHEN_AVAILABLE`, the button shows only on INVITED rows) AND fail-closes the\n // backend (`services-registry-handler` throws SERVICE_RESOURCE_OPERATION_DISABLED\n // → 403 if invoked on a non-INVITED row).\n enabledCondition: ({ currentObject }) => currentObject.status === OrganizationMemberStatus.INVITED,\n },\n ],\n // Two emails (distinct templates, disambiguated by `customNotificationRef`):\n // • the INVITEE (`recipientEmailField: 'userEmail'`) — \"your invitation was revoked\";\n // • the ADMIN/initiator (`USER_SELF`) — confirmation of the action.\n // Both USER_SELF+EMAIL, so each MUST carry a unique `customNotificationRef`.\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n recipientEmailField: 'userEmail',\n customNotificationRef: 'invitee',\n },\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n customNotificationRef: 'admin',\n }\n ]\n },\n\n // RESEND — re-mint the accept token (revoking any prior one for this membership)\n // and re-send the invite email with the fresh accept link. Only valid while the\n // membership is `INVITED`.\n [OrganizationMembers_Operations.RESEND]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_ORG_ROLES.ORG_ADMIN],\n riskLevel: ResourceOperationRiskLevel.LOW,\n resourceOperationLike: CoreResourceOperation.UPDATE,\n requestDto: z.object({}),\n tokenGeneration: {\n tokenType: CoreConsumableTokenTypes.ORGANIZATION_MEMBER_INVITATION,\n consumptionMode: ConsumableToken_ConsumptionMode.SINGLE_USE,\n expiresIn: { value: 7, unit: DurationUnit.DAYS },\n relatedIdField: '_id',\n revokeExistingForSameRelated: true,\n emailLinkPath: '/invitation',\n condition: isPendingOrganizationMemberInvitation,\n },\n // Resend is meaningful only while the invite is pending. Same dual enforcement\n // as REVOKE: hides the roster action on non-INVITED rows and 403s server-side.\n enabledCondition: ({ currentObject }) => currentObject.status === OrganizationMemberStatus.INVITED,\n }\n ],\n userNotifications: [\n {\n target: CoreUserNotificationTarget.USER_SELF,\n channel: CoreUserNotificationChannel.EMAIL,\n recipientEmailField: 'userEmail',\n condition: isPendingOrganizationMemberInvitation,\n }\n ]\n }\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"organization-members.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/organizations/organization-members.shared.schemas.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAkB,MAAM,iDAAiD,CAAC;AAMjG;;;;;;;;;;;;;;;;;;GAkBG;AACH,oBAAY,wBAAwB;IAClC,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAED,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;kBAkKnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"organization-members.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/organizations/organization-members.shared.schemas.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAkB,MAAM,iDAAiD,CAAC;AAQjG;;;;;;;;;;;;;;;;;;GAkBG;AACH,oBAAY,wBAAwB;IAClC,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAED,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;kBAiLnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -8,6 +8,8 @@ import { CORE_ORG_ROLES, OrgRolesSchema } from '../security/authorizations/roles
8
8
  import { forwardedLabel } from '../resources/resources.shared.schemas.js';
9
9
  import { CoreResourceType } from '../resources/resources.shared.core.definitions.js';
10
10
  import { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas.js';
11
+ import { RedactionType } from '../compliance/privacy/redaction.shared.schemas.js';
12
+ import { addImpersonalizeWith } from '@wildo-ai/zod-decorators';
11
13
  /**
12
14
  * Organization member lifecycle status.
13
15
  *
@@ -72,8 +74,15 @@ export const OrganizationMemberSchema = z.object({
72
74
  * auto-rendered display cards/columns (the user-facing email is surfaced by
73
75
  * resolving the `userId` reference) — it still renders on the CREATE form.
74
76
  */
75
- userEmail: z.email().optional().isDBIndexed().isSummaryField().isHidden({ display: true }).excludeFromUpdate()
76
- .dataCategories(PersonalDataCategory.CONTACT_DETAILS),
77
+ // REMOVE on erasure. A denormalized copy of the person's email, held here so a membership row can be
78
+ // read without resolving the user — which is exactly what makes it personal data this row HOLDS rather
79
+ // than a link it carries. Clearable (`.optional()`), so REMOVE resolves to a removed key.
80
+ //
81
+ // `roles` beside it is deliberately NOT scrubbed: it is this application's authorization vocabulary,
82
+ // not personal data, and emptying it could strand a tenant with no administrator — the interaction the
83
+ // administrative-continuity floors exist to refuse. Keeping it means that interaction never arises.
84
+ userEmail: addImpersonalizeWith(z.email().optional().isDBIndexed().isSummaryField().isHidden({ display: true }).excludeFromUpdate()
85
+ .dataCategories(PersonalDataCategory.CONTACT_DETAILS), RedactionType.REMOVE),
77
86
  organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),
78
87
  /*
79
88
  * `organizationUnitsMembership` (an embedded `[{ organizationUnitId, role }]` array) was REMOVED
@@ -149,8 +158,10 @@ export const OrganizationMemberSchema = z.object({
149
158
  scheduledReactivationAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),
150
159
  // The person's role in the customer's organization — their working life, not this application's
151
160
  // authorization model (`roles` is that, and it is a closed vocabulary rather than personal data).
152
- userTitle: z.string().max(100).optional().isDBIndexed().isSummaryField()
153
- .dataCategories(PersonalDataCategory.PROFESSIONAL),
161
+ // REMOVE on erasure — the person's role in the customer's organization is a fact about their working
162
+ // life, which is why it carries PROFESSIONAL. Clearable (`.optional()`).
163
+ userTitle: addImpersonalizeWith(z.string().max(100).optional().isDBIndexed().isSummaryField()
164
+ .dataCategories(PersonalDataCategory.PROFESSIONAL), RedactionType.REMOVE),
154
165
  /**
155
166
  * The `User._id` of the org admin who issued the invite. Server-set by the invite
156
167
  * CREATE prefix from the execution-context actor; absent on direct/system
@@ -1 +1 @@
1
- {"version":3,"file":"organization-members.shared.schemas.js","sourceRoot":"","sources":["../../../../src/organizations/organization-members.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["\n/**\n * Organization Member Shared Schemas\n * \n * Junction entity between users and organizations with roles and permissions\n */\n\nimport { z } from 'zod';\n\nimport { CORE_ORG_ROLES, OrgRolesSchema } from '../security/authorizations/roles.shared.schemas';\nimport { forwardedLabel } from '../resources/resources.shared.schemas';\nimport { CoreResourceType } from '../resources/resources.shared.core.definitions';\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\n\n\n/**\n * Organization member lifecycle status.\n *\n * A membership row IS the invitation — there is no separate invitation\n * collection. `INVITED` is the pending-membership state; acceptance flips it to\n * `ACTIVE` on the SAME row (stable `_id`, so the emailed accept-link token never\n * orphans). SSO JIT / SCIM provisioning find-or-activate an existing `INVITED`\n * row rather than blind-creating a second `ACTIVE` one.\n *\n * - `INVITED` — membership extended, invitee has not yet accepted. Paired with a\n * `User` in `PENDING_INVITATION` status when the invitee has no account yet.\n * - `ACTIVE` — accepted / directly provisioned membership.\n * - `INACTIVE` — deactivated (e.g. SCIM deprovision).\n * - `SUSPENDED` — temporarily suspended (optionally time-boxed).\n *\n * Invite OUTCOMES (declined / revoked / expired) are NOT statuses — the row is\n * deleted and the event recorded in the audit trail (keeps this enum about\n * membership, not invitation bookkeeping).\n */\nexport enum OrganizationMemberStatus {\n INVITED = 'INVITED',\n ACTIVE = 'ACTIVE',\n INACTIVE = 'INACTIVE',\n SUSPENDED = 'SUSPENDED'\n}\n\nexport const OrganizationMemberSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n /**\n * FK to the member `User`. ALWAYS present on a persisted row (a membership with\n * no member is meaningless) — even an `INVITED` invitee has an eagerly-created\n * `User` in `PENDING_INVITATION` status, so this FK is never null.\n *\n * SERVER-INJECTED on create (`.excludeFromCreate()`), via one of two paths:\n * - Invite (org-admin CREATE): the admin supplies {@link userEmail} instead;\n * the CREATE prefix find-or-creates the user by normalized email and injects\n * the resolved `_id` here (email is the strict unique identity anchor — see\n * `User.email.isUnique()`).\n * - System provisioning (invite-acceptance / SSO JIT / SCIM): passes `userId`\n * directly through `createAsSystem`, which re-attaches this context FK past\n * the strict-DTO strip.\n * `.excludeFromUpdate()` keeps the member↔user binding immutable.\n */\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n /**\n * The linked user's email — DUAL ROLE:\n *\n * 1. INVITE CREATE INPUT. On the org-admin invite CREATE the admin supplies\n * an email (not a `userId`); the CREATE prefix normalizes it, find-or-creates\n * the `User` by it (unique identity anchor), and derives {@link userId}. This\n * is why the field is client-writable on CREATE (no `.excludeFromCreate()`).\n * 2. DENORMALIZED IMMUTABLE COPY. After creation it is the immutable mirror of\n * `USERS.email` (which is `.excludeFromUpdate()`, so this copy never drifts).\n * It feeds member SEARCH (`searchableFields: ['userEmail']` — no async hop)\n * and the {@link instanceLabel} fallback when the related profile/name is\n * unavailable (e.g. a `PENDING_INVITATION` invitee has no `USER_PROFILE` yet).\n *\n * For system creates (SSO/SCIM/acceptance) that pass `userId` instead of an\n * email, the CREATE prefix DERIVES this value from the authoritative user record,\n * so it is `.optional()` at the input boundary. `.excludeFromUpdate()` keeps it\n * immutable post-creation. `.isHidden({ display: true })` keeps it out of\n * auto-rendered display cards/columns (the user-facing email is surfaced by\n * resolving the `userId` reference) — it still renders on the CREATE form.\n */\n userEmail: z.email().optional().isDBIndexed().isSummaryField().isHidden({ display: true }).excludeFromUpdate()\n .dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n /*\n * `organizationUnitsMembership` (an embedded `[{ organizationUnitId, role }]` array) was REMOVED\n * here on 2026-07-31 and promoted to the `ORGANIZATION_UNIT_MEMBERS` junction resource\n * (plan decision D-B5). The array could not be queried portably — a dot-path filter into an\n * array field resolves to NULL on PostgreSQL and the LIST summary projection strips the field —\n * so every \"who is assigned to this unit?\" question failed OPEN, it could not carry a registered\n * relationship (no cascade), and it admitted two conflicting roles for the same unit. Read unit\n * assignments through `ORGANIZATION_UNIT_MEMBERS` filtered by `organizationMemberId`.\n */\n // `.isAuditEvidence()` — this member's authority INSIDE the organization. Roles are the\n // framework's single authorization axis, so a change here is an access-control decision and the\n // before/after sets ARE the evidence: a trail that records only \"roles changed\" proves nothing\n // about what authority resulted. Safe to record in full — a role name is vocabulary, not personal\n // data, so it carries no erasure obligation into the immutable audit ledger.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n roles: z.array(OrgRolesSchema).default([CORE_ORG_ROLES.ORG_MEMBER]).isDBIndexed().isSummaryField().isAuditEvidence(),\n /**\n * Server-owned lifecycle state. Default `ACTIVE` covers direct/system\n * provisioning (SSO JIT / SCIM / self-registration org-owner grant), while\n * the org-admin invite CREATE prefix forces `INVITED`.\n *\n * Neither CREATE nor generic UPDATE may accept this field from a client:\n * lifecycle transitions carry security side effects that a field update\n * cannot reproduce. In particular, invitation acceptance must validate and\n * consume the invitation token, grant the derived user type, and serialize\n * against revocation; suspension/reactivation likewise have dedicated\n * operations. Keeping status out of both DTOs makes those trusted transition\n * operations the only way to change it.\n */\n status: z.enum(OrganizationMemberStatus)\n .default(OrganizationMemberStatus.ACTIVE)\n .isDBIndexed()\n .isSummaryField()\n .excludeFromCreate()\n .excludeFromUpdate()\n // Lifecycle state gates access as surely as roles do — a SUSPENDED member is denied, an ACTIVE\n // one is not — so a suspension or reactivation is an authorization event and its before/after\n // values are the evidence. The enum's values are closed vocabulary, never personal data, so\n // recording them carries no GDPR Art. 17 obligation into the immutable ledger — the same\n // reasoning `UserSchema.status` records. Without this the framework's\n // `RESOURCE_OPERATION_PERFORMED` event notes only that `status` was among the changed fields,\n // never what it changed FROM or TO, which is the half an access review actually reads.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n .isAuditEvidence(),\n /**\n * When an automated sweep must return this membership to `ACTIVE`.\n *\n * Set by SUSPEND when the caller time-boxes the suspension (`suspensionEndDate` in that verb's\n * request contract), cleared by REACTIVATE. Absent means the suspension is indefinite and only a\n * deliberate REACTIVATE lifts it — which is the default, since an unbounded suspension is the\n * safer reading of \"suspend\".\n *\n * NAMED FOR THE EFFECT, NOT THE CAUSE. `scheduledReactivationAt` says what the system will do;\n * a `suspensionEndsAt` would describe only the suspension branch while the identical field on\n * `USERS` also serves self-service deactivation. One concept, one name across both resources,\n * so the reactivation sweep reads the same field everywhere. The request contracts keep their\n * own caller-facing vocabulary and the transition hook translates — the same split\n * `ORGANIZATION_UNITS` MOVE uses (`newParentUnitId` in, `parentUnitId` + `path` persisted).\n *\n * Server-owned on both sides (`.excludeFromCreate().excludeFromUpdate()`): only the transition\n * operations write it, exactly as for {@link status} above. Indexed because the sweep's whole\n * query is \"rows whose scheduled reactivation has passed\".\n *\n * ⚠️ A value here is a PROMISE the framework keeps: the reactivation batch is what makes it\n * true. Persisting the date without that sweep would be the same defect as a verb that answers\n * success and writes nothing — an authored intention nothing honors.\n */\n // Deliberately NOT `.isAuditEvidence()`. The rationale that briefly sat here — \"lifecycle state\n // gates access as surely as roles do\" — is true of {@link status}, which is where it now lives.\n // This field decides nothing about what the principal may do; it is a scheduling detail, and the\n // transition's audit row already records the resulting status.\n scheduledReactivationAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n // The person's role in the customer's organization — their working life, not this application's\n // authorization model (`roles` is that, and it is a closed vocabulary rather than personal data).\n userTitle: z.string().max(100).optional().isDBIndexed().isSummaryField()\n .dataCategories(PersonalDataCategory.PROFESSIONAL),\n /**\n * The `User._id` of the org admin who issued the invite. Server-set by the invite\n * CREATE prefix from the execution-context actor; absent on direct/system\n * provisioning. Immutable. Feeds the audit trail and the user-facing \"invited by\"\n * cue in the pending-invitations inbox.\n *\n * PROVENANCE STAMP, NOT a navigable FK — deliberately a plain indexed string (same\n * idiom as `scim-tokens.createdByUserId`), NOT `.isForeignKey()`. The primary\n * member↔user link is `userId` (registered M2M relationship); adding `.isForeignKey()`\n * here would demand a SECOND `ORGANIZATION_MEMBERS → USERS` relationship (else the\n * registry warns \"cannot be resolved to a resource type\") and would graft nested-route /\n * parent-scope machinery onto a field that only records who clicked \"invite\". Resolve\n * the inviter's display name with a bounded lookup at render time if the cue needs it.\n */\n invitedByUserId: z.string().min(1).optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n /**\n * When the invite was issued. Server-set by the invite CREATE prefix; absent on\n * direct/system provisioning. Immutable. Used by the inbox ordering and the\n * expiry-reap CRON batch.\n */\n invitedAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n /**\n * Computed instance label — the linked user's name, forwarded live from\n * `USER_PROFILES`. A junction row has no inherent name; its identity is the\n * related party. {@link forwardedLabel} resolves it SERVER-SIDE at read time\n * (joining `userId` → `USER_PROFILES.userId`, rendering `firstName lastName`)\n * and projects it onto the summary/read DTO via the virtual-field pipeline, so\n * the frontend label is a plain field ref (`getFrontendResourceLabel:\n * ['instanceLabel']`). Never stale (read live), never denormalized, tenant-safe\n * (a bounded system read over the userIds already on the org-scoped page). NOT\n * persisted (virtual). When the profile/name is unavailable it falls back to the\n * immutable {@link userEmail} (`fallbackField`) rather than a raw id.\n */\n instanceLabel: forwardedLabel({\n localField: 'userId',\n relatedResource: CoreResourceType.USER_PROFILES,\n relatedJoinField: 'userId',\n fields: ['firstName', 'lastName'],\n fallbackField: 'userEmail',\n }),\n createdAt: z.date().default(() => new Date()).optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n}).businessKeySet([\n 'userId',\n 'organizationId',\n]);\nexport type OrganizationMember = z.infer<typeof OrganizationMemberSchema>;\n"]}
1
+ {"version":3,"file":"organization-members.shared.schemas.js","sourceRoot":"","sources":["../../../../src/organizations/organization-members.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["\n/**\n * Organization Member Shared Schemas\n * \n * Junction entity between users and organizations with roles and permissions\n */\n\nimport { z } from 'zod';\n\nimport { CORE_ORG_ROLES, OrgRolesSchema } from '../security/authorizations/roles.shared.schemas';\nimport { forwardedLabel } from '../resources/resources.shared.schemas';\nimport { CoreResourceType } from '../resources/resources.shared.core.definitions';\nimport { PersonalDataCategory } from '../compliance/privacy/personal-data-category.shared.schemas';\nimport { RedactionType } from '../compliance/privacy/redaction.shared.schemas';\nimport { addImpersonalizeWith } from '@wildo-ai/zod-decorators';\n\n\n/**\n * Organization member lifecycle status.\n *\n * A membership row IS the invitation — there is no separate invitation\n * collection. `INVITED` is the pending-membership state; acceptance flips it to\n * `ACTIVE` on the SAME row (stable `_id`, so the emailed accept-link token never\n * orphans). SSO JIT / SCIM provisioning find-or-activate an existing `INVITED`\n * row rather than blind-creating a second `ACTIVE` one.\n *\n * - `INVITED` — membership extended, invitee has not yet accepted. Paired with a\n * `User` in `PENDING_INVITATION` status when the invitee has no account yet.\n * - `ACTIVE` — accepted / directly provisioned membership.\n * - `INACTIVE` — deactivated (e.g. SCIM deprovision).\n * - `SUSPENDED` — temporarily suspended (optionally time-boxed).\n *\n * Invite OUTCOMES (declined / revoked / expired) are NOT statuses — the row is\n * deleted and the event recorded in the audit trail (keeps this enum about\n * membership, not invitation bookkeeping).\n */\nexport enum OrganizationMemberStatus {\n INVITED = 'INVITED',\n ACTIVE = 'ACTIVE',\n INACTIVE = 'INACTIVE',\n SUSPENDED = 'SUSPENDED'\n}\n\nexport const OrganizationMemberSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n /**\n * FK to the member `User`. ALWAYS present on a persisted row (a membership with\n * no member is meaningless) — even an `INVITED` invitee has an eagerly-created\n * `User` in `PENDING_INVITATION` status, so this FK is never null.\n *\n * SERVER-INJECTED on create (`.excludeFromCreate()`), via one of two paths:\n * - Invite (org-admin CREATE): the admin supplies {@link userEmail} instead;\n * the CREATE prefix find-or-creates the user by normalized email and injects\n * the resolved `_id` here (email is the strict unique identity anchor — see\n * `User.email.isUnique()`).\n * - System provisioning (invite-acceptance / SSO JIT / SCIM): passes `userId`\n * directly through `createAsSystem`, which re-attaches this context FK past\n * the strict-DTO strip.\n * `.excludeFromUpdate()` keeps the member↔user binding immutable.\n */\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n /**\n * The linked user's email — DUAL ROLE:\n *\n * 1. INVITE CREATE INPUT. On the org-admin invite CREATE the admin supplies\n * an email (not a `userId`); the CREATE prefix normalizes it, find-or-creates\n * the `User` by it (unique identity anchor), and derives {@link userId}. This\n * is why the field is client-writable on CREATE (no `.excludeFromCreate()`).\n * 2. DENORMALIZED IMMUTABLE COPY. After creation it is the immutable mirror of\n * `USERS.email` (which is `.excludeFromUpdate()`, so this copy never drifts).\n * It feeds member SEARCH (`searchableFields: ['userEmail']` — no async hop)\n * and the {@link instanceLabel} fallback when the related profile/name is\n * unavailable (e.g. a `PENDING_INVITATION` invitee has no `USER_PROFILE` yet).\n *\n * For system creates (SSO/SCIM/acceptance) that pass `userId` instead of an\n * email, the CREATE prefix DERIVES this value from the authoritative user record,\n * so it is `.optional()` at the input boundary. `.excludeFromUpdate()` keeps it\n * immutable post-creation. `.isHidden({ display: true })` keeps it out of\n * auto-rendered display cards/columns (the user-facing email is surfaced by\n * resolving the `userId` reference) — it still renders on the CREATE form.\n */\n // REMOVE on erasure. A denormalized copy of the person's email, held here so a membership row can be\n // read without resolving the user — which is exactly what makes it personal data this row HOLDS rather\n // than a link it carries. Clearable (`.optional()`), so REMOVE resolves to a removed key.\n //\n // `roles` beside it is deliberately NOT scrubbed: it is this application's authorization vocabulary,\n // not personal data, and emptying it could strand a tenant with no administrator — the interaction the\n // administrative-continuity floors exist to refuse. Keeping it means that interaction never arises.\n userEmail: addImpersonalizeWith(\n z.email().optional().isDBIndexed().isSummaryField().isHidden({ display: true }).excludeFromUpdate()\n .dataCategories(PersonalDataCategory.CONTACT_DETAILS),\n RedactionType.REMOVE,\n ),\n organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n /*\n * `organizationUnitsMembership` (an embedded `[{ organizationUnitId, role }]` array) was REMOVED\n * here on 2026-07-31 and promoted to the `ORGANIZATION_UNIT_MEMBERS` junction resource\n * (plan decision D-B5). The array could not be queried portably — a dot-path filter into an\n * array field resolves to NULL on PostgreSQL and the LIST summary projection strips the field —\n * so every \"who is assigned to this unit?\" question failed OPEN, it could not carry a registered\n * relationship (no cascade), and it admitted two conflicting roles for the same unit. Read unit\n * assignments through `ORGANIZATION_UNIT_MEMBERS` filtered by `organizationMemberId`.\n */\n // `.isAuditEvidence()` — this member's authority INSIDE the organization. Roles are the\n // framework's single authorization axis, so a change here is an access-control decision and the\n // before/after sets ARE the evidence: a trail that records only \"roles changed\" proves nothing\n // about what authority resulted. Safe to record in full — a role name is vocabulary, not personal\n // data, so it carries no erasure obligation into the immutable audit ledger.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n roles: z.array(OrgRolesSchema).default([CORE_ORG_ROLES.ORG_MEMBER]).isDBIndexed().isSummaryField().isAuditEvidence(),\n /**\n * Server-owned lifecycle state. Default `ACTIVE` covers direct/system\n * provisioning (SSO JIT / SCIM / self-registration org-owner grant), while\n * the org-admin invite CREATE prefix forces `INVITED`.\n *\n * Neither CREATE nor generic UPDATE may accept this field from a client:\n * lifecycle transitions carry security side effects that a field update\n * cannot reproduce. In particular, invitation acceptance must validate and\n * consume the invitation token, grant the derived user type, and serialize\n * against revocation; suspension/reactivation likewise have dedicated\n * operations. Keeping status out of both DTOs makes those trusted transition\n * operations the only way to change it.\n */\n status: z.enum(OrganizationMemberStatus)\n .default(OrganizationMemberStatus.ACTIVE)\n .isDBIndexed()\n .isSummaryField()\n .excludeFromCreate()\n .excludeFromUpdate()\n // Lifecycle state gates access as surely as roles do — a SUSPENDED member is denied, an ACTIVE\n // one is not — so a suspension or reactivation is an authorization event and its before/after\n // values are the evidence. The enum's values are closed vocabulary, never personal data, so\n // recording them carries no GDPR Art. 17 obligation into the immutable ledger — the same\n // reasoning `UserSchema.status` records. Without this the framework's\n // `RESOURCE_OPERATION_PERFORMED` event notes only that `status` was among the changed fields,\n // never what it changed FROM or TO, which is the half an access review actually reads.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n .isAuditEvidence(),\n /**\n * When an automated sweep must return this membership to `ACTIVE`.\n *\n * Set by SUSPEND when the caller time-boxes the suspension (`suspensionEndDate` in that verb's\n * request contract), cleared by REACTIVATE. Absent means the suspension is indefinite and only a\n * deliberate REACTIVATE lifts it — which is the default, since an unbounded suspension is the\n * safer reading of \"suspend\".\n *\n * NAMED FOR THE EFFECT, NOT THE CAUSE. `scheduledReactivationAt` says what the system will do;\n * a `suspensionEndsAt` would describe only the suspension branch while the identical field on\n * `USERS` also serves self-service deactivation. One concept, one name across both resources,\n * so the reactivation sweep reads the same field everywhere. The request contracts keep their\n * own caller-facing vocabulary and the transition hook translates — the same split\n * `ORGANIZATION_UNITS` MOVE uses (`newParentUnitId` in, `parentUnitId` + `path` persisted).\n *\n * Server-owned on both sides (`.excludeFromCreate().excludeFromUpdate()`): only the transition\n * operations write it, exactly as for {@link status} above. Indexed because the sweep's whole\n * query is \"rows whose scheduled reactivation has passed\".\n *\n * ⚠️ A value here is a PROMISE the framework keeps: the reactivation batch is what makes it\n * true. Persisting the date without that sweep would be the same defect as a verb that answers\n * success and writes nothing — an authored intention nothing honors.\n */\n // Deliberately NOT `.isAuditEvidence()`. The rationale that briefly sat here — \"lifecycle state\n // gates access as surely as roles do\" — is true of {@link status}, which is where it now lives.\n // This field decides nothing about what the principal may do; it is a scheduling detail, and the\n // transition's audit row already records the resulting status.\n scheduledReactivationAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n // The person's role in the customer's organization — their working life, not this application's\n // authorization model (`roles` is that, and it is a closed vocabulary rather than personal data).\n // REMOVE on erasure — the person's role in the customer's organization is a fact about their working\n // life, which is why it carries PROFESSIONAL. Clearable (`.optional()`).\n userTitle: addImpersonalizeWith(\n z.string().max(100).optional().isDBIndexed().isSummaryField()\n .dataCategories(PersonalDataCategory.PROFESSIONAL),\n RedactionType.REMOVE,\n ),\n /**\n * The `User._id` of the org admin who issued the invite. Server-set by the invite\n * CREATE prefix from the execution-context actor; absent on direct/system\n * provisioning. Immutable. Feeds the audit trail and the user-facing \"invited by\"\n * cue in the pending-invitations inbox.\n *\n * PROVENANCE STAMP, NOT a navigable FK — deliberately a plain indexed string (same\n * idiom as `scim-tokens.createdByUserId`), NOT `.isForeignKey()`. The primary\n * member↔user link is `userId` (registered M2M relationship); adding `.isForeignKey()`\n * here would demand a SECOND `ORGANIZATION_MEMBERS → USERS` relationship (else the\n * registry warns \"cannot be resolved to a resource type\") and would graft nested-route /\n * parent-scope machinery onto a field that only records who clicked \"invite\". Resolve\n * the inviter's display name with a bounded lookup at render time if the cue needs it.\n */\n invitedByUserId: z.string().min(1).optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n /**\n * When the invite was issued. Server-set by the invite CREATE prefix; absent on\n * direct/system provisioning. Immutable. Used by the inbox ordering and the\n * expiry-reap CRON batch.\n */\n invitedAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n /**\n * Computed instance label — the linked user's name, forwarded live from\n * `USER_PROFILES`. A junction row has no inherent name; its identity is the\n * related party. {@link forwardedLabel} resolves it SERVER-SIDE at read time\n * (joining `userId` → `USER_PROFILES.userId`, rendering `firstName lastName`)\n * and projects it onto the summary/read DTO via the virtual-field pipeline, so\n * the frontend label is a plain field ref (`getFrontendResourceLabel:\n * ['instanceLabel']`). Never stale (read live), never denormalized, tenant-safe\n * (a bounded system read over the userIds already on the org-scoped page). NOT\n * persisted (virtual). When the profile/name is unavailable it falls back to the\n * immutable {@link userEmail} (`fallbackField`) rather than a raw id.\n */\n instanceLabel: forwardedLabel({\n localField: 'userId',\n relatedResource: CoreResourceType.USER_PROFILES,\n relatedJoinField: 'userId',\n fields: ['firstName', 'lastName'],\n fallbackField: 'userEmail',\n }),\n createdAt: z.date().default(() => new Date()).optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n}).businessKeySet([\n 'userId',\n 'organizationId',\n]);\nexport type OrganizationMember = z.infer<typeof OrganizationMemberSchema>;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"organizations.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/organizations/organizations.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAEL,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AACvF,OAAO,EAAoD,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAW/H;;;;;;;;;;;;;;GAcG;AACH,oBAAY,iCAAiC;IAC3C,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB;;;;;;;OAOG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;OAMG;IACH,OAAO,YAAY;CACpB;AAQD,KAAK,0BAA0B,GAC3B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,CAAC;AAKjC,eAAO,MAAM,uDAAuD,GAAI,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;kBAwoBpH,CAAC"}
1
+ {"version":3,"file":"organizations.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/organizations/organizations.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAEL,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AACvF,OAAO,EAAoD,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAW/H;;;;;;;;;;;;;;GAcG;AACH,oBAAY,iCAAiC;IAC3C,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB;;;;;;;OAOG;IACH,gBAAgB,qBAAqB;IACrC;;;;;;OAMG;IACH,OAAO,YAAY;CACpB;AAQD,KAAK,0BAA0B,GAC3B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,IAAI,GAC1B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,GAC5B,qBAAqB,CAAC,MAAM,CAAC;AAKjC,eAAO,MAAM,uDAAuD,GAAI,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;kBAgpBpH,CAAC"}
@@ -506,16 +506,24 @@ export const organizationResourceConfiguration_InitializationFactory = (resource
506
506
  * way — they are a denormalized grant on the user row — so they are reconciled here, and
507
507
  * `restore` re-grants them.
508
508
  *
509
- * ## Audience limitation inherited from the DELETE door this replaces
510
- *
511
- * ⚠️ KNOWN, accepted deliberately. Re-authentication proves identity by PASSWORD only today,
512
- * and the framework DESTROYS `passwordHash` + passkeys for directory-managed (SCIM) users as a
513
- * compliance control (`_reconcileLocalCredentialsForDirectoryManaged`). So an ORG_ADMIN of a
514
- * directory-managed organization cannot satisfy the step-up gate and is refused. That is
515
- * fail-closed on the most destructive action available to a tenant, and the refusal is
516
- * diagnosable — the step-up round-trip ends in an explicit "No password set for this account".
517
- * Extending re-authentication to TOTP (which IS preserved for directory-managed users) removes
518
- * the limitation; until then such a tenant is marked through the `admin` variant below.
509
+ * ## Audience limitation inherited from the DELETE door this replaces — now CLOSED
510
+ *
511
+ * This used to read: re-authentication proves identity by PASSWORD only, the framework DESTROYS
512
+ * `passwordHash` + passkeys for directory-managed (SCIM) users as a compliance control
513
+ * (`_reconcileLocalCredentialsForDirectoryManaged`), so an ORG_ADMIN of a directory-managed
514
+ * organization could not satisfy the step-up gate and was refused and it named the fix as
515
+ * future work, routing such a tenant through the `admin` variant below.
516
+ *
517
+ * That fix SHIPPED. `AuthMethodManagementBackendService.reAuthenticate` accepts PASSWORD, TOTP
518
+ * and PASSKEY. `AuthMethod.TOTP` means "an enrolled second factor" a TOTP code or a backup
519
+ * code — verified through `AuthMfaBackendService.verifyEnrolledSecondFactor`, the same single
520
+ * authority the login second-factor step uses, anti-replay included. It was chosen precisely
521
+ * because the SCIM reconciliation PRESERVES it: a second factor sitting on top of the IdP first
522
+ * factor survives deprovisioning by design.
523
+ *
524
+ * So a directory-managed ORG_ADMIN can satisfy this gate itself, and does NOT need the `admin`
525
+ * variant to start a teardown. That variant remains for platform-operator use, not as the
526
+ * workaround for a limitation that no longer exists.
519
527
  *
520
528
  * assurance-control: WILDO.IDENTITY.STEP_UP_AUTHENTICATION — re-authentication before a high-impact
521
529
  * action; the tenant loses its access rights at the moment of the decision, not at the moment