@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
@@ -98,6 +98,67 @@ export declare enum OAuthTokenEndpointAuthMethod {
98
98
  * helper rather than re-deciding, so the classification never drifts.
99
99
  */
100
100
  export declare function isConfidentialTokenEndpointAuthMethod(method: OAuthTokenEndpointAuthMethod): boolean;
101
+ /**
102
+ * Safe redirect-URI policy for a registered OAuth client — the anti-open-redirect / anti-XSS gate
103
+ * on every entry of the `redirectUris` allowlist.
104
+ *
105
+ * The authorization_code flow matches a request's `redirect_uri` by EXACT string against this
106
+ * allowlist, and the authorize handler then emits the matched value as the browser's navigation
107
+ * target (`redirect_to`). A registered entry is therefore a literal, user-navigated URL, so the
108
+ * allowlist must never contain a scheme or shape that can execute script or redirect off-origin:
109
+ *
110
+ * - `https:` — allowed for any host/port (the standard for web / SPA / agent RPs).
111
+ * - `http:` — allowed ONLY for a loopback host (`localhost` / `127.0.0.1` / `::1`), per OAuth 2.1
112
+ * + RFC 8252 (native / CLI / dev clients); loopback is unreachable by a network
113
+ * attacker, so plain http there is safe.
114
+ * - a PRIVATE-USE URI scheme (`cursor:`, `com.example.app:`) — allowed, because that is how a native
115
+ * app receives the code on a platform with no loopback listener. See below.
116
+ * - everything else — `javascript:`, `data:`, `vbscript:`, `file:`, and plain `http:` to a
117
+ * NON-loopback host — is REJECTED.
118
+ * - a fragment (`#…`) is REJECTED (RFC 6749 §3.1.2: a redirect endpoint URI MUST NOT include one).
119
+ * - embedded credentials (`https://user:pass@host`) are REJECTED (a phishing / host-confusion vector).
120
+ * - leading/trailing whitespace is REJECTED — the WHATWG `URL` constructor silently trims it, so a
121
+ * padded value would parse to a different string than the one exact-matched at authorize time.
122
+ *
123
+ * ## Private-use schemes: why they were added on 2026-08-11
124
+ *
125
+ * This policy previously refused every non-http(s) scheme, and the note here said to extend it
126
+ * "deliberately" if an app ever needed one. A real client made that concrete: Cursor registered
127
+ * `cursor://anysphere.cursor-mcp/oauth/callback` and was refused, which means **no native MCP client
128
+ * using a custom-scheme callback could register with a Wildo application at all.** None of our own
129
+ * lanes could have found it — every one of them registers `http://localhost/...`.
130
+ *
131
+ * The standard sanctions the pattern: native apps "MAY use whichever redirect option suits their
132
+ * needs best, taking into account platform-specific implementation details", and private-use URI
133
+ * scheme redirection is one of the three named options.
134
+ * spec-cite: RFC 8252 § 7 and § 7.1 (Private-Use URI Scheme Redirection).
135
+ *
136
+ * The known weakness is NOT that such a URI is unsafe to navigate to — it is that the code may reach
137
+ * the wrong recipient: "A limitation of using private-use URI schemes for redirect URIs is that
138
+ * multiple apps can typically register the same scheme, which makes it indeterminate as to which app
139
+ * will receive the authorization code."
140
+ * spec-cite: RFC 8252 § 8.1 (Protecting the Authorization Code).
141
+ *
142
+ * That is an interception risk, and PKCE is the mitigation the same section prescribes. This server
143
+ * REQUIRES PKCE and advertises `S256` alone, so a code delivered to the wrong app is unusable without
144
+ * the verifier. Refusing the scheme outright bought no protection PKCE does not already provide, and
145
+ * cost the entire native-client ecosystem.
146
+ *
147
+ * What is still refused is what actually executes or reads local state — the danger is the SCHEME's
148
+ * capability, not its unfamiliarity, so the check is a denylist of dangerous schemes rather than an
149
+ * allowlist of known apps. An allowlist is impossible here by construction: the set of legitimate app
150
+ * schemes is open, and every entry would be a new client the framework has to be taught about.
151
+ *
152
+ * Rejected alternatives: `z.string().url()` (the original guard) accepts every dangerous scheme; a
153
+ * strict https-only rule is too tight for the native/CLI loopback flow; requiring a reverse-DNS
154
+ * scheme (`com.example.app:`, which RFC 8252 § 7.1 RECOMMENDS) was rejected because it would still
155
+ * refuse `cursor:` — the recommendation binds the app author, not the authorization server, and a
156
+ * server that enforces it rejects conformant clients for a style violation.
157
+ *
158
+ * assurance-control: WILDO.IDENTITY.REDIRECT_TARGET_VALIDATION — registered OAuth redirect targets are constrained to
159
+ * safe absolute URLs, closing an open-redirect / stored-XSS delegation primitive.
160
+ */
161
+ export declare const DANGEROUS_REDIRECT_URI_SCHEMES: ReadonlySet<string>;
101
162
  export declare function isSafeOAuthRedirectUri(value: string): boolean;
102
163
  /**
103
164
  * Base schema for extension (needs to be z.object for `.extend()` to work).
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-clients.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-clients.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAMlE;;;GAGG;AACH,oBAAY,iBAAiB;IAC3B,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;CACpB;AAED;;;;;GAKG;AACH;;;;;;;;;;GAUG;AACH,oBAAY,6BAA6B;IACvC;;;;OAIG;IACH,QAAQ,aAAa;IACrB;;;;;OAKG;IACH,OAAO,YAAY;CACpB;AAED,oBAAY,cAAc;IACxB,kBAAkB,uBAAuB;IACzC,kBAAkB,uBAAuB;IACzC,aAAa,kBAAkB;CAChC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,oBAAY,4BAA4B;IACtC,IAAI,SAAS;IACb,mBAAmB,wBAAwB;IAC3C,kBAAkB,uBAAuB;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,qCAAqC,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAEnG;AA8FD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAsB7D;AAMD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;iBA0JhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;iBAGxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;iBAGvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"}
1
+ {"version":3,"file":"oauth-clients.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-clients.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAMlE;;;GAGG;AACH,oBAAY,iBAAiB;IAC3B,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;CACpB;AAED;;;;;GAKG;AACH;;;;;;;;;;GAUG;AACH,oBAAY,6BAA6B;IACvC;;;;OAIG;IACH,QAAQ,aAAa;IACrB;;;;;OAKG;IACH,OAAO,YAAY;CACpB;AAED,oBAAY,cAAc;IACxB,kBAAkB,uBAAuB;IACzC,kBAAkB,uBAAuB;IACzC,aAAa,kBAAkB;CAChC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,oBAAY,4BAA4B;IACtC,IAAI,SAAS;IACb,mBAAmB,wBAAwB;IAC3C,kBAAkB,uBAAuB;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,qCAAqC,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAEnG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAaH,eAAO,MAAM,8BAA8B,EAAE,WAAW,CAAC,MAAM,CAiB7D,CAAC;AAeH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAsB7D;AAMD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;iBA0JhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;iBAGxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;iBAGvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"}
@@ -168,7 +168,19 @@ export function isConfidentialTokenEndpointAuthMethod(method) {
168
168
  * assurance-control: WILDO.IDENTITY.REDIRECT_TARGET_VALIDATION — registered OAuth redirect targets are constrained to
169
169
  * safe absolute URLs, closing an open-redirect / stored-XSS delegation primitive.
170
170
  */
171
- const DANGEROUS_REDIRECT_URI_SCHEMES = new Set([
171
+ /*
172
+ * EXPORTED as of 2026-09-02, and the export is the point rather than a convenience.
173
+ *
174
+ * This is a closed vocabulary with one named runtime source — the shape `CLAUDE.md` asks for — and it
175
+ * was reachable by nothing outside this file, so the assurance authority basis could not attest
176
+ * `WILDO.IDENTITY.REDIRECT_TARGET_VALIDATION` against the set the guard actually branches on. It would
177
+ * have had to transcribe the schemes into a second list, free to drift from this one, which is the
178
+ * failure the single-source rule exists to prevent.
179
+ *
180
+ * Nothing else may BRANCH on it: the validator below is the only enforcement site. A reader is a
181
+ * reader — the basis names the members so a programme can say which schemes are refused.
182
+ */
183
+ export const DANGEROUS_REDIRECT_URI_SCHEMES = new Set([
172
184
  // Executes script in the navigating context — the open-redirect-to-XSS primitive this guard exists
173
185
  // to close.
174
186
  'javascript:',
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-clients.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-clients.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth Client Shared Schemas\n *\n * Frontend/backend shared schemas + DTOs for the registered OAuth-client\n * credential resource (`organizationOAuthClients`, `applicationOAuthClients`).\n * Backend-specific logic (secret minting/hashing, mint pipeline) stays in\n * saas-backend-lib.\n *\n * This resource is a FAITHFUL MIRROR of the api-keys resource\n * (`api-keys.shared.schemas.ts`): the same long-lived secret + rotation-grace\n * shape, the same server-managed strip-seam contract, the same org|app scope\n * split. It exists as its OWN resource (not a variant of api-keys) because the\n * request-auth choke point routes a presented secret to a DISTINCT principal\n * by the plaintext prefix (`oc_org_`/`oc_app_` vs the api-key `sk_org_`/`sk_app_`)\n * — an OAuth client and an api-key both authenticate the SAME machine principal\n * (`ORGANIZATION_MACHINE` / `APPLICATION_MACHINE`); they are distinct credential types, an api-key acts\n * as an api-key principal.\n *\n * OIDC authorization code, interactive consent, persistent consent grants,\n * exact redirect allowlists and public/confidential client classification are\n * implemented. The resource remains the registered-client authority; issued\n * codes, tokens and user consent records have their own runtime/storage owners.\n */\n\nimport { z } from 'zod';\nimport { RolesSchema } from '../authorizations/roles.shared.schemas';\nimport { ApiKeyScope } from '../api-keys/api-keys.shared.schemas';\n\n// ===============================\n// OAUTH CLIENT ENUMS\n// ===============================\n\n/**\n * Lifecycle status of a registered OAuth client. Mirrors `ApiKeyStatus`: an\n * INACTIVE or EXPIRED client cannot mint tokens at the request-auth choke point.\n */\nexport enum OAuthClientStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n EXPIRED = 'expired',\n}\n\n/**\n * OAuth 2.0 grant types a registered client may be authorized to use. The trunk\n * resource ships CLIENT_CREDENTIALS (the machine-to-machine front); the\n * AUTHORIZATION_CODE / REFRESH_TOKEN arms are declared here so the OIDC branch can\n * enable them per-client via `allowedGrants` without a schema reshape.\n */\n/**\n * How a client got registered — a TRUST classification, not a bookkeeping detail.\n *\n * Kept separate from `ApiKeyScope` on purpose. `scope` answers \"which of this application's scopes\n * owns the credential\", and both of its members imply someone with standing inside the application\n * created it. This enum answers \"did anyone vouch for this client at all\", and the two questions\n * became independent the moment an anonymous caller could register one.\n *\n * semantic-literal-boundary: a closed Wildo vocabulary with one named runtime source. Every schema,\n * default, comparison and mapping over registration provenance derives from THIS enum.\n */\nexport enum OAuthClientRegistrationOrigin {\n /**\n * Created by a principal with standing in this application — an organization administrator\n * through the org-clients API, a platform operator, or a seed. **The default**, so nothing that\n * existed before dynamic registration changes posture.\n */\n OPERATOR = 'operator',\n /**\n * Self-registered by an anonymous caller at the RFC 7591 `/register` endpoint.\n *\n * The client asserted its own name and redirect URIs and proved nothing. It is granted no roles,\n * capped at identity scopes, and always faces the user's consent prompt.\n */\n DYNAMIC = 'dynamic',\n}\n\nexport enum OAuthGrantType {\n CLIENT_CREDENTIALS = 'client_credentials',\n AUTHORIZATION_CODE = 'authorization_code',\n REFRESH_TOKEN = 'refresh_token',\n}\n\n/**\n * How a registered OAuth client authenticates AT THIS APP'S token endpoint (OIDC\n * `token_endpoint_auth_method`) — the PROVIDER-side classification of OUR clients. Deliberately\n * DISTINCT from `OAuth2_TokenEndpoint_AuthMethod` in `external-connectors-models`, which describes how\n * Wildo-as-CLIENT authenticates to an EXTERNAL IdP's token endpoint (the opposite direction); the two\n * are separate concepts even though they share the OAuth wire vocabulary. Values are the OIDC-registered\n * method names advertised in the discovery document's `token_endpoint_auth_methods_supported`.\n *\n * - `NONE` — a PUBLIC client: no client secret at the token endpoint; the authorization_code exchange\n * relies on mandatory PKCE (S256) + exact redirect_uri (the OAuth 2.1 baseline for SPA / native /\n * agent clients). This is the default because a client is public unless its\n * server-side secret-retention and token authentication method are explicit.\n * - `CLIENT_SECRET_BASIC` / `CLIENT_SECRET_POST` — a CONFIDENTIAL client: it MUST present its\n * `client_secret` (via HTTP Basic, or the request body, respectively) at the token endpoint, as\n * defense-in-depth on top of PKCE. Both map to \"confidential\" for enforcement — the basic-vs-body\n * distinction is a per-request transport choice the token endpoint already accepts either way.\n */\nexport enum OAuthTokenEndpointAuthMethod {\n NONE = 'none',\n CLIENT_SECRET_BASIC = 'client_secret_basic',\n CLIENT_SECRET_POST = 'client_secret_post',\n}\n\n/**\n * The single semantic authority for the public-vs-confidential decision: a client is CONFIDENTIAL\n * (must present its secret at the token endpoint) iff its `tokenEndpointAuthMethod` is not `NONE`.\n * Every enforcement site (authorization_code secret check, revocation client-auth) derives from this\n * helper rather than re-deciding, so the classification never drifts.\n */\nexport function isConfidentialTokenEndpointAuthMethod(method: OAuthTokenEndpointAuthMethod): boolean {\n return method !== OAuthTokenEndpointAuthMethod.NONE;\n}\n\n/**\n * Safe redirect-URI policy for a registered OAuth client — the anti-open-redirect / anti-XSS gate\n * on every entry of the `redirectUris` allowlist.\n *\n * The authorization_code flow matches a request's `redirect_uri` by EXACT string against this\n * allowlist, and the authorize handler then emits the matched value as the browser's navigation\n * target (`redirect_to`). A registered entry is therefore a literal, user-navigated URL, so the\n * allowlist must never contain a scheme or shape that can execute script or redirect off-origin:\n *\n * - `https:` — allowed for any host/port (the standard for web / SPA / agent RPs).\n * - `http:` — allowed ONLY for a loopback host (`localhost` / `127.0.0.1` / `::1`), per OAuth 2.1\n * + RFC 8252 (native / CLI / dev clients); loopback is unreachable by a network\n * attacker, so plain http there is safe.\n * - a PRIVATE-USE URI scheme (`cursor:`, `com.example.app:`) — allowed, because that is how a native\n * app receives the code on a platform with no loopback listener. See below.\n * - everything else — `javascript:`, `data:`, `vbscript:`, `file:`, and plain `http:` to a\n * NON-loopback host — is REJECTED.\n * - a fragment (`#…`) is REJECTED (RFC 6749 §3.1.2: a redirect endpoint URI MUST NOT include one).\n * - embedded credentials (`https://user:pass@host`) are REJECTED (a phishing / host-confusion vector).\n * - leading/trailing whitespace is REJECTED — the WHATWG `URL` constructor silently trims it, so a\n * padded value would parse to a different string than the one exact-matched at authorize time.\n *\n * ## Private-use schemes: why they were added on 2026-08-11\n *\n * This policy previously refused every non-http(s) scheme, and the note here said to extend it\n * \"deliberately\" if an app ever needed one. A real client made that concrete: Cursor registered\n * `cursor://anysphere.cursor-mcp/oauth/callback` and was refused, which means **no native MCP client\n * using a custom-scheme callback could register with a Wildo application at all.** None of our own\n * lanes could have found it — every one of them registers `http://localhost/...`.\n *\n * The standard sanctions the pattern: native apps \"MAY use whichever redirect option suits their\n * needs best, taking into account platform-specific implementation details\", and private-use URI\n * scheme redirection is one of the three named options.\n * spec-cite: RFC 8252 § 7 and § 7.1 (Private-Use URI Scheme Redirection).\n *\n * The known weakness is NOT that such a URI is unsafe to navigate to — it is that the code may reach\n * the wrong recipient: \"A limitation of using private-use URI schemes for redirect URIs is that\n * multiple apps can typically register the same scheme, which makes it indeterminate as to which app\n * will receive the authorization code.\"\n * spec-cite: RFC 8252 § 8.1 (Protecting the Authorization Code).\n *\n * That is an interception risk, and PKCE is the mitigation the same section prescribes. This server\n * REQUIRES PKCE and advertises `S256` alone, so a code delivered to the wrong app is unusable without\n * the verifier. Refusing the scheme outright bought no protection PKCE does not already provide, and\n * cost the entire native-client ecosystem.\n *\n * What is still refused is what actually executes or reads local state — the danger is the SCHEME's\n * capability, not its unfamiliarity, so the check is a denylist of dangerous schemes rather than an\n * allowlist of known apps. An allowlist is impossible here by construction: the set of legitimate app\n * schemes is open, and every entry would be a new client the framework has to be taught about.\n *\n * Rejected alternatives: `z.string().url()` (the original guard) accepts every dangerous scheme; a\n * strict https-only rule is too tight for the native/CLI loopback flow; requiring a reverse-DNS\n * scheme (`com.example.app:`, which RFC 8252 § 7.1 RECOMMENDS) was rejected because it would still\n * refuse `cursor:` — the recommendation binds the app author, not the authorization server, and a\n * server that enforces it rejects conformant clients for a style violation.\n *\n * assurance-control: WILDO.IDENTITY.REDIRECT_TARGET_VALIDATION — registered OAuth redirect targets are constrained to\n * safe absolute URLs, closing an open-redirect / stored-XSS delegation primitive.\n */\nconst DANGEROUS_REDIRECT_URI_SCHEMES: ReadonlySet<string> = new Set([\n // Executes script in the navigating context — the open-redirect-to-XSS primitive this guard exists\n // to close.\n 'javascript:',\n 'vbscript:',\n // Carries its own payload, so the \"redirect target\" IS attacker-authored content.\n 'data:',\n 'blob:',\n // Reads local state rather than handing the code to an application.\n 'file:',\n 'filesystem:',\n // Browser-internal surfaces; never a native app's callback.\n 'about:',\n 'chrome:',\n 'chrome-extension:',\n 'moz-extension:',\n 'view-source:',\n]);\n\n/**\n * The WHATWG \"special\" schemes, minus the two handled explicitly above.\n *\n * These are web-platform schemes with defined network semantics — never a native app's private-use\n * callback. Refusing them is what keeps \"private-use scheme\" meaning what it says: an app-owned\n * scheme, not any string that happens to parse. `ftp:` in particular was pinned as refused before\n * private-use schemes were admitted, and it stays refused.\n */\nconst NON_CALLBACK_SPECIAL_SCHEMES: ReadonlySet<string> = new Set(['ftp:', 'ws:', 'wss:', 'file:']);\n\n/** RFC 3986 § 3.1 scheme grammar: ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" ), plus the WHATWG colon. */\nconst RFC3986_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:$/;\n\nexport function isSafeOAuthRedirectUri(value: string): boolean {\n if (typeof value !== 'string' || value.length === 0 || value !== value.trim()) return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.hash) return false;\n if (url.username || url.password) return false;\n if (url.protocol === 'https:') return true;\n if (url.protocol === 'http:') {\n const host = url.hostname.toLowerCase();\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';\n }\n // A private-use scheme for a native app. Refused only when the scheme itself confers a dangerous\n // capability; the shape is otherwise held to the same rules as an https target (no fragment, no\n // embedded credentials, both checked above), and interception is answered by mandatory PKCE.\n const scheme = url.protocol.toLowerCase();\n if (DANGEROUS_REDIRECT_URI_SCHEMES.has(scheme)) return false;\n if (NON_CALLBACK_SPECIAL_SCHEMES.has(scheme)) return false;\n return RFC3986_SCHEME_PATTERN.test(scheme);\n}\n\n// ===============================\n// CORE OAUTH CLIENT SCHEMAS\n// ===============================\n\n/**\n * Base schema for extension (needs to be z.object for `.extend()` to work).\n *\n * `scope` reuses `ApiKeyScope` (ORGANIZATION|APPLICATION) — the same primary-scope\n * split as api-keys; the org/app variants narrow it to a literal + add the FK.\n *\n * SECRET + ROTATION GRACE (`hashedSecret`/`secretPrefix`/`previous*`) mirror the\n * api-key `hashedKey`/`keyPrefix`/`previous*` fields VERBATIM (same decorators,\n * same server-managed strip-seam contract). Unlike api-keys — which declare\n * `hashedKey`/`keyPrefix` on the concrete org/app variant schemas — the OAuth\n * client keeps them on the base: every scope persists the identical secret shape,\n * so a single base declaration is the honest single source of truth.\n */\nexport const OAuthClientSchemaBase = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(), // == clientId\n name: z.string().min(1).max(100).isDBIndexed().isSummaryField(),\n description: z.string().max(500).optional(),\n scope: z.enum(ApiKeyScope).isDBIndexed().isSummaryField(),\n // `.isAuditEvidence()` — the roles an OAuth client acts with. Same reasoning as the API-key\n // authority field: roles are the single authorization axis, so their before/after IS the\n // evidence. assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n roles: z.array(RolesSchema).default([]).isAuditEvidence(),\n // OIDC IDENTITY scopes this client may request in the authorization_code flow (e.g. `openid`,\n // `profile`, `email`) — validated against the requested `scope` when minting the ID token /\n // serving `/oauth/userinfo`. This is IDENTITY only: it is NOT an authorization axis. Authorization\n // is roles-only (scope-based authorization was deleted 2026-07-21 — do NOT re-introduce it). Empty\n // for pure client_credentials machine clients (they carry no scopes; their access is roles-only).\n allowedScopes: z.array(z.string()).default([]),\n // Grant types this client is authorized to use. Defaults to the machine front.\n allowedGrants: z.array(z.enum(OAuthGrantType)).default([OAuthGrantType.CLIENT_CREDENTIALS]),\n // OIDC authorization_code: exact-match allowlist of redirect URIs. Empty for pure\n // client_credentials clients. An authorization request's `redirect_uri` MUST equal one of\n // these by EXACT string match (no wildcards / no substring) — the anti-open-redirect guard.\n // Each entry is additionally constrained to a SAFE absolute URL by `isSafeOAuthRedirectUri`\n // (https anywhere · http loopback only · no javascript:/data:/fragment/embedded-credentials) so a\n // malicious registered entry cannot become a stored-XSS / off-origin redirect navigation target.\n redirectUris: z\n .array(\n z.string().refine(isSafeOAuthRedirectUri, {\n message: 'redirect_uri must be an absolute https URL (or an http loopback URL) with no fragment or embedded credentials',\n }),\n )\n // Bounded. RFC 7591 sets no limit, and one is needed here because `/oauth/register` accepts this\n // array from an ANONYMOUS caller: without a ceiling a single registration can carry an arbitrary\n // number of URIs, and the per-IP registration limit bounds how MANY registrations happen, not how\n // large each one is. 50 is far above any real client — several environments times a few loopback\n // ports — so this constrains only abuse. Every entry is independently constrained by\n // `isSafeOAuthRedirectUri`, so this is a storage bound, not an authorization one.\n .max(50)\n .default([]),\n // OIDC RP-Initiated Logout 1.0: exact-match allowlist of POST-LOGOUT redirect URIs (revocation-C). When\n // an RP calls `/oauth/end-session` with a `post_logout_redirect_uri`, that value MUST equal one of these\n // by EXACT string match — the SAME anti-open-redirect discipline as `redirectUris` (no wildcards, no\n // substring). A non-matching value is refused and the OP lands the user on its own logged-out page rather\n // than redirecting back to the RP. Empty for clients that never drive an interactive logout (pure\n // client_credentials machines). Each entry is independently held to a SAFE absolute URL by\n // `isSafeOAuthRedirectUri` (https anywhere · http loopback only · no fragment/embedded-credentials), and\n // the array is `.max(50)`-bounded for the same anonymous-`/oauth/register` abuse reason as `redirectUris`.\n // spec-cite: OpenID Connect RP-Initiated Logout 1.0 §2 (`post_logout_redirect_uri` MUST have been\n // registered with the OP).\n postLogoutRedirectUris: z\n .array(\n z.string().refine(isSafeOAuthRedirectUri, {\n message: 'post_logout_redirect_uri must be an absolute https URL (or an http loopback URL) with no fragment or embedded credentials',\n }),\n )\n .max(50)\n .default([]),\n // OIDC INTERACTIVE CONSENT gate. When TRUE, the authorization_code flow requires EXPLICIT user\n // consent before a code is minted: `/oauth/authorize` returns `consent_required` (with a single-use\n // consent-request token + the client/resource/scope details) instead of auto-issuing the code, and\n // the user must approve at `/oauth/authorize/decision`. Defaults FALSE = a first-party / trusted\n // client whose consent is auto-granted (the pre-consent behavior, so machine + trusted clients are\n // UNAFFECTED). Set TRUE for a THIRD-PARTY client that acts on the user's behalf and must therefore\n // be explicitly authorized by the user before it can obtain a delegated token. The gate is\n // SERVER-owned (a client property, never a request parameter) so a third-party client cannot\n // suppress its own consent prompt.\n requiresConsent: z.boolean().default(false),\n // OIDC `token_endpoint_auth_method` — PUBLIC (`NONE`, the default: PKCE-only, no secret at the token\n // endpoint) vs CONFIDENTIAL (`CLIENT_SECRET_BASIC`/`POST`: the authorization_code exchange AND token\n // revocation REQUIRE the client to present its `client_secret`, defense-in-depth over PKCE). Default\n // `NONE` is the safe default: a server-side confidential client must opt into\n // a secret-bearing token-endpoint method explicitly.\n // Settable at create/update — flipping a client confidential simply starts requiring the secret it\n // already has. The public-vs-confidential decision is derived ONLY via\n // `isConfidentialTokenEndpointAuthMethod`, never by re-comparing the enum inline.\n tokenEndpointAuthMethod: z.enum(OAuthTokenEndpointAuthMethod).default(OAuthTokenEndpointAuthMethod.NONE).isSummaryField(),\n // `.isAuditEvidence()` — whether this client's delegated access is live.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n status: z.enum(OAuthClientStatus).default(OAuthClientStatus.ACTIVE).isDBIndexed().isSummaryField().isAuditEvidence(),\n expiresAt: z.date().optional().isDBIndexed().isSummaryField(),\n lastUsedAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n createdAt: z.date().default(() => new Date()).isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n\n // ── Client secret ─────────────────────────────────────────────────────────\n // `hashedSecret` is the lookup key for the client-credentials token exchange\n // (the request-auth choke point resolves a presented secret against it on every\n // exchange). Without an index this is an O(n) collection scan; the compound\n // unique guarantees only one active row per hash — cryptographically near-\n // impossible to collide by accident, but a uniqueness contract avoids two rows\n // being returned to the resolver on a hash collision and prevents shadow-client\n // duplicates during rotation. Backend-only (a secret hash, never surfaced except\n // once at creation via `isReturnedAtCreation`, which the mint postfix correlates\n // the one-time plaintext against). `.excludeFrom{Create,Update}()` — the client\n // never supplies it; the mint impl is the single server-side choke point.\n hashedSecret: z.string().min(1).isDBIndexed().isUnique().isBackendOnly({ isReturnedAtCreation: true }).excludeFromCreate().excludeFromUpdate(),\n // First 8 chars of the plaintext secret — persisted + shown in lists\n // (`.isSummaryField()`) so an admin can identify a client without revealing the\n // secret. Server-managed.\n secretPrefix: z.string().length(8).isSummaryField().excludeFromCreate().excludeFromUpdate(),\n\n // ── Rotation grace window ─────────────────────────────────────────────────\n // When a client secret is ROTATED or REGENERATEd, the PRIOR secret is retained\n // here so it keeps authenticating during an overlap window — callers can migrate\n // to the new secret without a hard cutover. The client-credentials exchange\n // resolves a presented secret against the current `hashedSecret` OR this\n // `previousHashedSecret` while the window is open (`previousSecretInvalidationDate`\n // is null or in the future) AND the client is ACTIVE. All three are server-managed\n // by the rotate/regenerate impls only — never client-settable\n // (`.excludeFromCreate().excludeFromUpdate()`).\n //\n // `previousHashedSecret` — `sha256` of the prior secret; the auth path looks a\n // presented secret up against it, so it is `.isDBIndexed()`. Backend-only (a\n // secret hash, never surfaced). Absent until the first rotation.\n previousHashedSecret: z.string().min(1).optional().isDBIndexed().isBackendOnly().excludeFromCreate().excludeFromUpdate(),\n // First 8 chars of the prior secret — retained for support/audit display while\n // the previous secret is still valid (not a secret, so surfaced). Overwritten on\n // the next rotation.\n previousSecretPrefix: z.string().length(8).optional().excludeFromCreate().excludeFromUpdate(),\n // When the previous secret stops authenticating. `null`/absent = no scheduled\n // invalidation (REGENERATE overlap — the prior secret stays valid until the next\n // rotation). A future date (ROTATE `oldSecretInvalidationDate`) = honored until\n // then, then rejected. `now`/past (ROTATE default) = immediate hard cutover.\n // Surfaced so operators can see when the old secret dies.\n previousSecretInvalidationDate: z.date().optional().excludeFromCreate().excludeFromUpdate(),\n\n // ── How this client came to exist ─────────────────────────────────────────\n // WHO registered this client, which is a different question from WHERE the row lives.\n //\n // Every other client on this collection was created by someone with standing in this application:\n // an organization administrator, or a platform operator. A DYNAMIC client was created by an\n // anonymous caller at the RFC 7591 `/register` endpoint, and proved nothing about itself.\n //\n // The field exists because the STORAGE LOCATION can no longer carry that meaning. A dynamically\n // registered client has no organization, so it must live on the APPLICATION collection — and\n // `scope: APPLICATION` otherwise means \"platform-operator first-party\", the most trusted posture\n // there is. Without this discriminator a stranger's client would inherit that trust purely from\n // where it was filed. `ResolvedAuthorizationClient` reads it and maps DYNAMIC to\n // `AuthorizationClientTenancy.UNAFFILIATED`, so the authorize path treats it like a CIMD client:\n // always consent, identity scopes only.\n //\n // `.excludeFromCreate().excludeFromUpdate()` is load-bearing, not hygiene: this is a TRUST\n // CLASSIFICATION, and a caller who could set it could file itself as OPERATOR. Only the\n // registration endpoint writes it, through the system door. Authenticated\n // administrative registration receives OPERATOR by default because that\n // administrator vouches for the client.\n // `.isAuditEvidence()` — a change here changes how much this client is trusted.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n registrationOrigin: z.enum(OAuthClientRegistrationOrigin)\n .default(OAuthClientRegistrationOrigin.OPERATOR)\n .isDBIndexed()\n .isSummaryField()\n .excludeFromCreate()\n .excludeFromUpdate()\n .isAuditEvidence(),\n\n});\n\nexport type OAuthClientBase = z.infer<typeof OAuthClientSchemaBase>;\n\nexport const OAuthClientOrganizationSchema = OAuthClientSchemaBase.extend({\n scope: z.literal(ApiKeyScope.ORGANIZATION),\n organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n});\n\nexport type OAuthClientOrganization = z.infer<typeof OAuthClientOrganizationSchema>;\n\nexport const OAuthClientApplicationSchema = OAuthClientSchemaBase.extend({\n scope: z.literal(ApiKeyScope.APPLICATION),\n applicationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n});\n\nexport type OAuthClientApplication = z.infer<typeof OAuthClientApplicationSchema>;\n"]}
1
+ {"version":3,"file":"oauth-clients.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-clients.shared.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth Client Shared Schemas\n *\n * Frontend/backend shared schemas + DTOs for the registered OAuth-client\n * credential resource (`organizationOAuthClients`, `applicationOAuthClients`).\n * Backend-specific logic (secret minting/hashing, mint pipeline) stays in\n * saas-backend-lib.\n *\n * This resource is a FAITHFUL MIRROR of the api-keys resource\n * (`api-keys.shared.schemas.ts`): the same long-lived secret + rotation-grace\n * shape, the same server-managed strip-seam contract, the same org|app scope\n * split. It exists as its OWN resource (not a variant of api-keys) because the\n * request-auth choke point routes a presented secret to a DISTINCT principal\n * by the plaintext prefix (`oc_org_`/`oc_app_` vs the api-key `sk_org_`/`sk_app_`)\n * — an OAuth client and an api-key both authenticate the SAME machine principal\n * (`ORGANIZATION_MACHINE` / `APPLICATION_MACHINE`); they are distinct credential types, an api-key acts\n * as an api-key principal.\n *\n * OIDC authorization code, interactive consent, persistent consent grants,\n * exact redirect allowlists and public/confidential client classification are\n * implemented. The resource remains the registered-client authority; issued\n * codes, tokens and user consent records have their own runtime/storage owners.\n */\n\nimport { z } from 'zod';\nimport { RolesSchema } from '../authorizations/roles.shared.schemas';\nimport { ApiKeyScope } from '../api-keys/api-keys.shared.schemas';\n\n// ===============================\n// OAUTH CLIENT ENUMS\n// ===============================\n\n/**\n * Lifecycle status of a registered OAuth client. Mirrors `ApiKeyStatus`: an\n * INACTIVE or EXPIRED client cannot mint tokens at the request-auth choke point.\n */\nexport enum OAuthClientStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n EXPIRED = 'expired',\n}\n\n/**\n * OAuth 2.0 grant types a registered client may be authorized to use. The trunk\n * resource ships CLIENT_CREDENTIALS (the machine-to-machine front); the\n * AUTHORIZATION_CODE / REFRESH_TOKEN arms are declared here so the OIDC branch can\n * enable them per-client via `allowedGrants` without a schema reshape.\n */\n/**\n * How a client got registered — a TRUST classification, not a bookkeeping detail.\n *\n * Kept separate from `ApiKeyScope` on purpose. `scope` answers \"which of this application's scopes\n * owns the credential\", and both of its members imply someone with standing inside the application\n * created it. This enum answers \"did anyone vouch for this client at all\", and the two questions\n * became independent the moment an anonymous caller could register one.\n *\n * semantic-literal-boundary: a closed Wildo vocabulary with one named runtime source. Every schema,\n * default, comparison and mapping over registration provenance derives from THIS enum.\n */\nexport enum OAuthClientRegistrationOrigin {\n /**\n * Created by a principal with standing in this application — an organization administrator\n * through the org-clients API, a platform operator, or a seed. **The default**, so nothing that\n * existed before dynamic registration changes posture.\n */\n OPERATOR = 'operator',\n /**\n * Self-registered by an anonymous caller at the RFC 7591 `/register` endpoint.\n *\n * The client asserted its own name and redirect URIs and proved nothing. It is granted no roles,\n * capped at identity scopes, and always faces the user's consent prompt.\n */\n DYNAMIC = 'dynamic',\n}\n\nexport enum OAuthGrantType {\n CLIENT_CREDENTIALS = 'client_credentials',\n AUTHORIZATION_CODE = 'authorization_code',\n REFRESH_TOKEN = 'refresh_token',\n}\n\n/**\n * How a registered OAuth client authenticates AT THIS APP'S token endpoint (OIDC\n * `token_endpoint_auth_method`) — the PROVIDER-side classification of OUR clients. Deliberately\n * DISTINCT from `OAuth2_TokenEndpoint_AuthMethod` in `external-connectors-models`, which describes how\n * Wildo-as-CLIENT authenticates to an EXTERNAL IdP's token endpoint (the opposite direction); the two\n * are separate concepts even though they share the OAuth wire vocabulary. Values are the OIDC-registered\n * method names advertised in the discovery document's `token_endpoint_auth_methods_supported`.\n *\n * - `NONE` — a PUBLIC client: no client secret at the token endpoint; the authorization_code exchange\n * relies on mandatory PKCE (S256) + exact redirect_uri (the OAuth 2.1 baseline for SPA / native /\n * agent clients). This is the default because a client is public unless its\n * server-side secret-retention and token authentication method are explicit.\n * - `CLIENT_SECRET_BASIC` / `CLIENT_SECRET_POST` — a CONFIDENTIAL client: it MUST present its\n * `client_secret` (via HTTP Basic, or the request body, respectively) at the token endpoint, as\n * defense-in-depth on top of PKCE. Both map to \"confidential\" for enforcement — the basic-vs-body\n * distinction is a per-request transport choice the token endpoint already accepts either way.\n */\nexport enum OAuthTokenEndpointAuthMethod {\n NONE = 'none',\n CLIENT_SECRET_BASIC = 'client_secret_basic',\n CLIENT_SECRET_POST = 'client_secret_post',\n}\n\n/**\n * The single semantic authority for the public-vs-confidential decision: a client is CONFIDENTIAL\n * (must present its secret at the token endpoint) iff its `tokenEndpointAuthMethod` is not `NONE`.\n * Every enforcement site (authorization_code secret check, revocation client-auth) derives from this\n * helper rather than re-deciding, so the classification never drifts.\n */\nexport function isConfidentialTokenEndpointAuthMethod(method: OAuthTokenEndpointAuthMethod): boolean {\n return method !== OAuthTokenEndpointAuthMethod.NONE;\n}\n\n/**\n * Safe redirect-URI policy for a registered OAuth client — the anti-open-redirect / anti-XSS gate\n * on every entry of the `redirectUris` allowlist.\n *\n * The authorization_code flow matches a request's `redirect_uri` by EXACT string against this\n * allowlist, and the authorize handler then emits the matched value as the browser's navigation\n * target (`redirect_to`). A registered entry is therefore a literal, user-navigated URL, so the\n * allowlist must never contain a scheme or shape that can execute script or redirect off-origin:\n *\n * - `https:` — allowed for any host/port (the standard for web / SPA / agent RPs).\n * - `http:` — allowed ONLY for a loopback host (`localhost` / `127.0.0.1` / `::1`), per OAuth 2.1\n * + RFC 8252 (native / CLI / dev clients); loopback is unreachable by a network\n * attacker, so plain http there is safe.\n * - a PRIVATE-USE URI scheme (`cursor:`, `com.example.app:`) — allowed, because that is how a native\n * app receives the code on a platform with no loopback listener. See below.\n * - everything else — `javascript:`, `data:`, `vbscript:`, `file:`, and plain `http:` to a\n * NON-loopback host — is REJECTED.\n * - a fragment (`#…`) is REJECTED (RFC 6749 §3.1.2: a redirect endpoint URI MUST NOT include one).\n * - embedded credentials (`https://user:pass@host`) are REJECTED (a phishing / host-confusion vector).\n * - leading/trailing whitespace is REJECTED — the WHATWG `URL` constructor silently trims it, so a\n * padded value would parse to a different string than the one exact-matched at authorize time.\n *\n * ## Private-use schemes: why they were added on 2026-08-11\n *\n * This policy previously refused every non-http(s) scheme, and the note here said to extend it\n * \"deliberately\" if an app ever needed one. A real client made that concrete: Cursor registered\n * `cursor://anysphere.cursor-mcp/oauth/callback` and was refused, which means **no native MCP client\n * using a custom-scheme callback could register with a Wildo application at all.** None of our own\n * lanes could have found it — every one of them registers `http://localhost/...`.\n *\n * The standard sanctions the pattern: native apps \"MAY use whichever redirect option suits their\n * needs best, taking into account platform-specific implementation details\", and private-use URI\n * scheme redirection is one of the three named options.\n * spec-cite: RFC 8252 § 7 and § 7.1 (Private-Use URI Scheme Redirection).\n *\n * The known weakness is NOT that such a URI is unsafe to navigate to — it is that the code may reach\n * the wrong recipient: \"A limitation of using private-use URI schemes for redirect URIs is that\n * multiple apps can typically register the same scheme, which makes it indeterminate as to which app\n * will receive the authorization code.\"\n * spec-cite: RFC 8252 § 8.1 (Protecting the Authorization Code).\n *\n * That is an interception risk, and PKCE is the mitigation the same section prescribes. This server\n * REQUIRES PKCE and advertises `S256` alone, so a code delivered to the wrong app is unusable without\n * the verifier. Refusing the scheme outright bought no protection PKCE does not already provide, and\n * cost the entire native-client ecosystem.\n *\n * What is still refused is what actually executes or reads local state — the danger is the SCHEME's\n * capability, not its unfamiliarity, so the check is a denylist of dangerous schemes rather than an\n * allowlist of known apps. An allowlist is impossible here by construction: the set of legitimate app\n * schemes is open, and every entry would be a new client the framework has to be taught about.\n *\n * Rejected alternatives: `z.string().url()` (the original guard) accepts every dangerous scheme; a\n * strict https-only rule is too tight for the native/CLI loopback flow; requiring a reverse-DNS\n * scheme (`com.example.app:`, which RFC 8252 § 7.1 RECOMMENDS) was rejected because it would still\n * refuse `cursor:` — the recommendation binds the app author, not the authorization server, and a\n * server that enforces it rejects conformant clients for a style violation.\n *\n * assurance-control: WILDO.IDENTITY.REDIRECT_TARGET_VALIDATION — registered OAuth redirect targets are constrained to\n * safe absolute URLs, closing an open-redirect / stored-XSS delegation primitive.\n */\n/*\n * EXPORTED as of 2026-09-02, and the export is the point rather than a convenience.\n *\n * This is a closed vocabulary with one named runtime source — the shape `CLAUDE.md` asks for — and it\n * was reachable by nothing outside this file, so the assurance authority basis could not attest\n * `WILDO.IDENTITY.REDIRECT_TARGET_VALIDATION` against the set the guard actually branches on. It would\n * have had to transcribe the schemes into a second list, free to drift from this one, which is the\n * failure the single-source rule exists to prevent.\n *\n * Nothing else may BRANCH on it: the validator below is the only enforcement site. A reader is a\n * reader — the basis names the members so a programme can say which schemes are refused.\n */\nexport const DANGEROUS_REDIRECT_URI_SCHEMES: ReadonlySet<string> = new Set([\n // Executes script in the navigating context — the open-redirect-to-XSS primitive this guard exists\n // to close.\n 'javascript:',\n 'vbscript:',\n // Carries its own payload, so the \"redirect target\" IS attacker-authored content.\n 'data:',\n 'blob:',\n // Reads local state rather than handing the code to an application.\n 'file:',\n 'filesystem:',\n // Browser-internal surfaces; never a native app's callback.\n 'about:',\n 'chrome:',\n 'chrome-extension:',\n 'moz-extension:',\n 'view-source:',\n]);\n\n/**\n * The WHATWG \"special\" schemes, minus the two handled explicitly above.\n *\n * These are web-platform schemes with defined network semantics — never a native app's private-use\n * callback. Refusing them is what keeps \"private-use scheme\" meaning what it says: an app-owned\n * scheme, not any string that happens to parse. `ftp:` in particular was pinned as refused before\n * private-use schemes were admitted, and it stays refused.\n */\nconst NON_CALLBACK_SPECIAL_SCHEMES: ReadonlySet<string> = new Set(['ftp:', 'ws:', 'wss:', 'file:']);\n\n/** RFC 3986 § 3.1 scheme grammar: ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" ), plus the WHATWG colon. */\nconst RFC3986_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:$/;\n\nexport function isSafeOAuthRedirectUri(value: string): boolean {\n if (typeof value !== 'string' || value.length === 0 || value !== value.trim()) return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.hash) return false;\n if (url.username || url.password) return false;\n if (url.protocol === 'https:') return true;\n if (url.protocol === 'http:') {\n const host = url.hostname.toLowerCase();\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';\n }\n // A private-use scheme for a native app. Refused only when the scheme itself confers a dangerous\n // capability; the shape is otherwise held to the same rules as an https target (no fragment, no\n // embedded credentials, both checked above), and interception is answered by mandatory PKCE.\n const scheme = url.protocol.toLowerCase();\n if (DANGEROUS_REDIRECT_URI_SCHEMES.has(scheme)) return false;\n if (NON_CALLBACK_SPECIAL_SCHEMES.has(scheme)) return false;\n return RFC3986_SCHEME_PATTERN.test(scheme);\n}\n\n// ===============================\n// CORE OAUTH CLIENT SCHEMAS\n// ===============================\n\n/**\n * Base schema for extension (needs to be z.object for `.extend()` to work).\n *\n * `scope` reuses `ApiKeyScope` (ORGANIZATION|APPLICATION) — the same primary-scope\n * split as api-keys; the org/app variants narrow it to a literal + add the FK.\n *\n * SECRET + ROTATION GRACE (`hashedSecret`/`secretPrefix`/`previous*`) mirror the\n * api-key `hashedKey`/`keyPrefix`/`previous*` fields VERBATIM (same decorators,\n * same server-managed strip-seam contract). Unlike api-keys — which declare\n * `hashedKey`/`keyPrefix` on the concrete org/app variant schemas — the OAuth\n * client keeps them on the base: every scope persists the identical secret shape,\n * so a single base declaration is the honest single source of truth.\n */\nexport const OAuthClientSchemaBase = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(), // == clientId\n name: z.string().min(1).max(100).isDBIndexed().isSummaryField(),\n description: z.string().max(500).optional(),\n scope: z.enum(ApiKeyScope).isDBIndexed().isSummaryField(),\n // `.isAuditEvidence()` — the roles an OAuth client acts with. Same reasoning as the API-key\n // authority field: roles are the single authorization axis, so their before/after IS the\n // evidence. assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n roles: z.array(RolesSchema).default([]).isAuditEvidence(),\n // OIDC IDENTITY scopes this client may request in the authorization_code flow (e.g. `openid`,\n // `profile`, `email`) — validated against the requested `scope` when minting the ID token /\n // serving `/oauth/userinfo`. This is IDENTITY only: it is NOT an authorization axis. Authorization\n // is roles-only (scope-based authorization was deleted 2026-07-21 — do NOT re-introduce it). Empty\n // for pure client_credentials machine clients (they carry no scopes; their access is roles-only).\n allowedScopes: z.array(z.string()).default([]),\n // Grant types this client is authorized to use. Defaults to the machine front.\n allowedGrants: z.array(z.enum(OAuthGrantType)).default([OAuthGrantType.CLIENT_CREDENTIALS]),\n // OIDC authorization_code: exact-match allowlist of redirect URIs. Empty for pure\n // client_credentials clients. An authorization request's `redirect_uri` MUST equal one of\n // these by EXACT string match (no wildcards / no substring) — the anti-open-redirect guard.\n // Each entry is additionally constrained to a SAFE absolute URL by `isSafeOAuthRedirectUri`\n // (https anywhere · http loopback only · no javascript:/data:/fragment/embedded-credentials) so a\n // malicious registered entry cannot become a stored-XSS / off-origin redirect navigation target.\n redirectUris: z\n .array(\n z.string().refine(isSafeOAuthRedirectUri, {\n message: 'redirect_uri must be an absolute https URL (or an http loopback URL) with no fragment or embedded credentials',\n }),\n )\n // Bounded. RFC 7591 sets no limit, and one is needed here because `/oauth/register` accepts this\n // array from an ANONYMOUS caller: without a ceiling a single registration can carry an arbitrary\n // number of URIs, and the per-IP registration limit bounds how MANY registrations happen, not how\n // large each one is. 50 is far above any real client — several environments times a few loopback\n // ports — so this constrains only abuse. Every entry is independently constrained by\n // `isSafeOAuthRedirectUri`, so this is a storage bound, not an authorization one.\n .max(50)\n .default([]),\n // OIDC RP-Initiated Logout 1.0: exact-match allowlist of POST-LOGOUT redirect URIs (revocation-C). When\n // an RP calls `/oauth/end-session` with a `post_logout_redirect_uri`, that value MUST equal one of these\n // by EXACT string match — the SAME anti-open-redirect discipline as `redirectUris` (no wildcards, no\n // substring). A non-matching value is refused and the OP lands the user on its own logged-out page rather\n // than redirecting back to the RP. Empty for clients that never drive an interactive logout (pure\n // client_credentials machines). Each entry is independently held to a SAFE absolute URL by\n // `isSafeOAuthRedirectUri` (https anywhere · http loopback only · no fragment/embedded-credentials), and\n // the array is `.max(50)`-bounded for the same anonymous-`/oauth/register` abuse reason as `redirectUris`.\n // spec-cite: OpenID Connect RP-Initiated Logout 1.0 §2 (`post_logout_redirect_uri` MUST have been\n // registered with the OP).\n postLogoutRedirectUris: z\n .array(\n z.string().refine(isSafeOAuthRedirectUri, {\n message: 'post_logout_redirect_uri must be an absolute https URL (or an http loopback URL) with no fragment or embedded credentials',\n }),\n )\n .max(50)\n .default([]),\n // OIDC INTERACTIVE CONSENT gate. When TRUE, the authorization_code flow requires EXPLICIT user\n // consent before a code is minted: `/oauth/authorize` returns `consent_required` (with a single-use\n // consent-request token + the client/resource/scope details) instead of auto-issuing the code, and\n // the user must approve at `/oauth/authorize/decision`. Defaults FALSE = a first-party / trusted\n // client whose consent is auto-granted (the pre-consent behavior, so machine + trusted clients are\n // UNAFFECTED). Set TRUE for a THIRD-PARTY client that acts on the user's behalf and must therefore\n // be explicitly authorized by the user before it can obtain a delegated token. The gate is\n // SERVER-owned (a client property, never a request parameter) so a third-party client cannot\n // suppress its own consent prompt.\n requiresConsent: z.boolean().default(false),\n // OIDC `token_endpoint_auth_method` — PUBLIC (`NONE`, the default: PKCE-only, no secret at the token\n // endpoint) vs CONFIDENTIAL (`CLIENT_SECRET_BASIC`/`POST`: the authorization_code exchange AND token\n // revocation REQUIRE the client to present its `client_secret`, defense-in-depth over PKCE). Default\n // `NONE` is the safe default: a server-side confidential client must opt into\n // a secret-bearing token-endpoint method explicitly.\n // Settable at create/update — flipping a client confidential simply starts requiring the secret it\n // already has. The public-vs-confidential decision is derived ONLY via\n // `isConfidentialTokenEndpointAuthMethod`, never by re-comparing the enum inline.\n tokenEndpointAuthMethod: z.enum(OAuthTokenEndpointAuthMethod).default(OAuthTokenEndpointAuthMethod.NONE).isSummaryField(),\n // `.isAuditEvidence()` — whether this client's delegated access is live.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n status: z.enum(OAuthClientStatus).default(OAuthClientStatus.ACTIVE).isDBIndexed().isSummaryField().isAuditEvidence(),\n expiresAt: z.date().optional().isDBIndexed().isSummaryField(),\n lastUsedAt: z.date().optional().isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n createdAt: z.date().default(() => new Date()).isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n\n // ── Client secret ─────────────────────────────────────────────────────────\n // `hashedSecret` is the lookup key for the client-credentials token exchange\n // (the request-auth choke point resolves a presented secret against it on every\n // exchange). Without an index this is an O(n) collection scan; the compound\n // unique guarantees only one active row per hash — cryptographically near-\n // impossible to collide by accident, but a uniqueness contract avoids two rows\n // being returned to the resolver on a hash collision and prevents shadow-client\n // duplicates during rotation. Backend-only (a secret hash, never surfaced except\n // once at creation via `isReturnedAtCreation`, which the mint postfix correlates\n // the one-time plaintext against). `.excludeFrom{Create,Update}()` — the client\n // never supplies it; the mint impl is the single server-side choke point.\n hashedSecret: z.string().min(1).isDBIndexed().isUnique().isBackendOnly({ isReturnedAtCreation: true }).excludeFromCreate().excludeFromUpdate(),\n // First 8 chars of the plaintext secret — persisted + shown in lists\n // (`.isSummaryField()`) so an admin can identify a client without revealing the\n // secret. Server-managed.\n secretPrefix: z.string().length(8).isSummaryField().excludeFromCreate().excludeFromUpdate(),\n\n // ── Rotation grace window ─────────────────────────────────────────────────\n // When a client secret is ROTATED or REGENERATEd, the PRIOR secret is retained\n // here so it keeps authenticating during an overlap window — callers can migrate\n // to the new secret without a hard cutover. The client-credentials exchange\n // resolves a presented secret against the current `hashedSecret` OR this\n // `previousHashedSecret` while the window is open (`previousSecretInvalidationDate`\n // is null or in the future) AND the client is ACTIVE. All three are server-managed\n // by the rotate/regenerate impls only — never client-settable\n // (`.excludeFromCreate().excludeFromUpdate()`).\n //\n // `previousHashedSecret` — `sha256` of the prior secret; the auth path looks a\n // presented secret up against it, so it is `.isDBIndexed()`. Backend-only (a\n // secret hash, never surfaced). Absent until the first rotation.\n previousHashedSecret: z.string().min(1).optional().isDBIndexed().isBackendOnly().excludeFromCreate().excludeFromUpdate(),\n // First 8 chars of the prior secret — retained for support/audit display while\n // the previous secret is still valid (not a secret, so surfaced). Overwritten on\n // the next rotation.\n previousSecretPrefix: z.string().length(8).optional().excludeFromCreate().excludeFromUpdate(),\n // When the previous secret stops authenticating. `null`/absent = no scheduled\n // invalidation (REGENERATE overlap — the prior secret stays valid until the next\n // rotation). A future date (ROTATE `oldSecretInvalidationDate`) = honored until\n // then, then rejected. `now`/past (ROTATE default) = immediate hard cutover.\n // Surfaced so operators can see when the old secret dies.\n previousSecretInvalidationDate: z.date().optional().excludeFromCreate().excludeFromUpdate(),\n\n // ── How this client came to exist ─────────────────────────────────────────\n // WHO registered this client, which is a different question from WHERE the row lives.\n //\n // Every other client on this collection was created by someone with standing in this application:\n // an organization administrator, or a platform operator. A DYNAMIC client was created by an\n // anonymous caller at the RFC 7591 `/register` endpoint, and proved nothing about itself.\n //\n // The field exists because the STORAGE LOCATION can no longer carry that meaning. A dynamically\n // registered client has no organization, so it must live on the APPLICATION collection — and\n // `scope: APPLICATION` otherwise means \"platform-operator first-party\", the most trusted posture\n // there is. Without this discriminator a stranger's client would inherit that trust purely from\n // where it was filed. `ResolvedAuthorizationClient` reads it and maps DYNAMIC to\n // `AuthorizationClientTenancy.UNAFFILIATED`, so the authorize path treats it like a CIMD client:\n // always consent, identity scopes only.\n //\n // `.excludeFromCreate().excludeFromUpdate()` is load-bearing, not hygiene: this is a TRUST\n // CLASSIFICATION, and a caller who could set it could file itself as OPERATOR. Only the\n // registration endpoint writes it, through the system door. Authenticated\n // administrative registration receives OPERATOR by default because that\n // administrator vouches for the client.\n // `.isAuditEvidence()` — a change here changes how much this client is trusted.\n // assurance-control: WILDO.ACCESS.PRIVILEGE_EVIDENCE\n registrationOrigin: z.enum(OAuthClientRegistrationOrigin)\n .default(OAuthClientRegistrationOrigin.OPERATOR)\n .isDBIndexed()\n .isSummaryField()\n .excludeFromCreate()\n .excludeFromUpdate()\n .isAuditEvidence(),\n\n});\n\nexport type OAuthClientBase = z.infer<typeof OAuthClientSchemaBase>;\n\nexport const OAuthClientOrganizationSchema = OAuthClientSchemaBase.extend({\n scope: z.literal(ApiKeyScope.ORGANIZATION),\n organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n});\n\nexport type OAuthClientOrganization = z.infer<typeof OAuthClientOrganizationSchema>;\n\nexport const OAuthClientApplicationSchema = OAuthClientSchemaBase.extend({\n scope: z.literal(ApiKeyScope.APPLICATION),\n applicationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n});\n\nexport type OAuthClientApplication = z.infer<typeof OAuthClientApplicationSchema>;\n"]}
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { CoreResourceOperation } from '../../resources/resources.shared.core.definitions';
19
19
  import { ResourceRelationship } from '../../resources/resources.shared.schemas';
20
- export declare const oauthConsentGrantsResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("../..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST, import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodObject<{
20
+ export declare const oauthConsentGrantsResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("../..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.UPDATE_MANY | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST, import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodObject<{
21
21
  _id: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>;
22
22
  userId: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>>>;
23
23
  clientId: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-consent-grants.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAE,qBAAqB,EAAwD,MAAM,mDAAmD,CAAC;AAChJ,OAAO,EAA4D,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAI1I,eAAO,MAAM,6DAA6D,GACxE,wBAAwB,oBAAoB,EAAE;;;;;;;;;kCA+D9C,CAAC"}
1
+ {"version":3,"file":"oauth-consent-grants.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAE,qBAAqB,EAAwD,MAAM,mDAAmD,CAAC;AAChJ,OAAO,EAA4D,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAK1I,eAAO,MAAM,6DAA6D,GACxE,wBAAwB,oBAAoB,EAAE;;;;;;;;;kCAsG9C,CAAC"}
@@ -22,6 +22,7 @@ import { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdenti
22
22
  import { ResourceOperationRiskLevel, ResourceOperationVariantType } from '../../resources/resources.shared.schemas.js';
23
23
  import { CORE_APP_ROLES } from '../authorizations/roles.shared.schemas.js';
24
24
  import { DataPortabilityProvenance } from '../../compliance/privacy/subject-export.shared.schemas.js';
25
+ import { ErasureRetentionMode, ResourceDataSubjectKind } from '../../compliance/privacy/impersonalization.shared.schemas.js';
25
26
  export const oauthConsentGrantsResourceConfiguration_InitializationFactory = (resourcesRelationships) => createResourceConfiguration_Initialization({
26
27
  mainSchema: OAuthConsentGrantSchema,
27
28
  resourceIdentifier: CoreResourceType.OAUTH_CONSENT_GRANTS,
@@ -40,14 +41,48 @@ export const oauthConsentGrantsResourceConfiguration_InitializationFactory = (re
40
41
  list: ResourceSystemAccessMode.ALLOWED,
41
42
  },
42
43
  portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.SUBJECT_PROVIDED },
44
+ /**
45
+ * Subject erasure RETAINS this row and RESETS the authorized scopes.
46
+ *
47
+ * The grant is kept deliberately: a consent decision is the evidence that access was authorized, which is
48
+ * exactly what an operator must be able to show afterwards. But `scopes` records WHAT this person let a
49
+ * third party reach, which its USAGE category correctly declares — so `RETAIN_ONLY` would be false. The
50
+ * field is a defaulted array, which only `RedactionType.RESET` can scrub; it returns to the empty default,
51
+ * so the grant remains as evidence and what it granted no longer describes the person.
52
+ */
53
+ retentionPolicy: {
54
+ mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,
55
+ },
56
+ /**
57
+ * Erasing one of these rows revokes NO session. A grant is a record ABOUT a decision, not the person, and
58
+ * revoking a third-party client's OAuth access is a separate act from ending a login session.
59
+ */
60
+ dataSubjectPolicy: { kind: ResourceDataSubjectKind.NO_PRINCIPAL },
43
61
  coreOperations: [
44
62
  CoreResourceOperation.READ,
45
63
  CoreResourceOperation.LIST,
46
64
  CoreResourceOperation.CREATE,
47
65
  CoreResourceOperation.UPDATE,
48
66
  CoreResourceOperation.DELETE,
67
+ // Route-less; `retentionPolicy` requires READ + LIST + UPDATE_MANY.
68
+ CoreResourceOperation.UPDATE_MANY,
49
69
  ],
50
70
  operationsConfiguration: {
71
+ /*
72
+ * Route-less retention plumbing — INTERNAL_CALL registers no route, so no API surface is added, and it
73
+ * must not be REPOSITORY_ONLY: the retention primitives resolve through `getServiceOperationPathDefault`,
74
+ * which matches only INTERNAL_CALL / API_CALL. LIST is LOW (a read-shaped op may not claim the
75
+ * HIGH/CRITICAL band, which carries an audit-emission obligation); UPDATE_MANY is CRITICAL — it is the
76
+ * verb the impersonalize writer uses to stamp the retention markers.
77
+ */
78
+ [CoreResourceOperation.UPDATE_MANY]: {
79
+ variants: [{
80
+ variantType: ResourceOperationVariantType.INTERNAL_CALL,
81
+ isDefault: true,
82
+ roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],
83
+ riskLevel: ResourceOperationRiskLevel.CRITICAL,
84
+ }],
85
+ },
51
86
  // Owner-facing: the USER_SELF primary-scope filter restricts results to the caller's own grants.
52
87
  [CoreResourceOperation.READ]: {
53
88
  variants: [
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-consent-grants.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OIDC Persistent Consent Grants — Resource Configuration\n *\n * USER_SELF-scoped (the scope is derived from the `USERS → OAUTH_CONSENT_GRANTS` composition\n * relationship). READ / LIST / DELETE are OWNER-facing (`API_CALL` + `APP_USER`): the framework's\n * USER_SELF primary-scope filter restricts every result to the authenticated caller's own grants, so a\n * user can list their \"authorized apps\" and revoke one, and nobody can see or delete another user's\n * grants. CREATE / UPDATE are SYSTEM-only (`INTERNAL_CALL`): the OIDC consent-decision endpoint is the\n * sole writer, via the `createAsSystem` / `updateAsSystem` primitives.\n *\n * `systemAccessPolicy` opts the write + read verbs into the `*AsSystem` door (default is fail-closed\n * FORBIDDEN): the decision endpoint create/updates a grant with no request-context FK to attach\n * (`userId` is re-attached from the schema allowlist on the system path), and the authorize endpoint\n * read/lists a user's grants to decide whether to skip the prompt. There is no `delete` system-access\n * verb (none exists); owner revoke goes through the `API_CALL` DELETE route, and the USERS\n * `onParentDelete` composition cascade deletes grants directly at the repository layer (variant-agnostic).\n */\n\nimport { OAuthConsentGrantSchema } from './oauth-consent-grants.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../../resources/resources-config.shared.schemas';\nimport { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier } from '../../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourceOperationVariantType, ResourceRelationship } from '../../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../authorizations/roles.shared.schemas';\nimport { DataPortabilityProvenance } from '../../compliance/privacy/subject-export.shared.schemas';\n\nexport const oauthConsentGrantsResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[]\n) => createResourceConfiguration_Initialization<undefined,\n CoreResourceOperation.READ | CoreResourceOperation.LIST | CoreResourceOperation.CREATE | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE,\n typeof OAuthConsentGrantSchema\n>({\n mainSchema: OAuthConsentGrantSchema,\n resourceIdentifier: CoreResourceType.OAUTH_CONSENT_GRANTS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.OAUTH_CONSENT_GRANTS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n\n // The OIDC consent-decision endpoint stores/updates grants via createAsSystem/updateAsSystem (the\n // client-shaped CREATE DTO strips the context FK `userId`, re-attached from the schema allowlist on\n // the system path); the authorize endpoint reads/lists a user's grants via readAsSystem/listAsSystem\n // to decide whether to skip the consent prompt. Default is fail-closed FORBIDDEN, so opt each in.\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.ALLOWED,\n create: ResourceSystemAccessMode.ALLOWED,\n update: ResourceSystemAccessMode.ALLOWED,\n read: ResourceSystemAccessMode.ALLOWED,\n list: ResourceSystemAccessMode.ALLOWED,\n },\n portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.SUBJECT_PROVIDED },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n ],\n operationsConfiguration: {\n // Owner-facing: the USER_SELF primary-scope filter restricts results to the caller's own grants.\n [CoreResourceOperation.READ]: {\n variants: [\n { variantType: ResourceOperationVariantType.API_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_USER], riskLevel: ResourceOperationRiskLevel.LOW },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n { variantType: ResourceOperationVariantType.API_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_USER], riskLevel: ResourceOperationRiskLevel.LOW },\n ],\n },\n // System-only: the consent-decision endpoint is the sole writer (createAsSystem / updateAsSystem).\n [CoreResourceOperation.CREATE]: {\n variants: [\n { variantType: ResourceOperationVariantType.INTERNAL_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN], riskLevel: ResourceOperationRiskLevel.MEDIUM },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n { variantType: ResourceOperationVariantType.INTERNAL_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN], riskLevel: ResourceOperationRiskLevel.MEDIUM },\n ],\n },\n // Owner-facing revoke (USER_SELF filter → own grant only). The USERS onParentDelete composition\n // cascade also deletes grants, directly at the repository layer (variant-agnostic — it only requires\n // a DELETE op to exist).\n [CoreResourceOperation.DELETE]: {\n variants: [\n { variantType: ResourceOperationVariantType.API_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_USER], riskLevel: ResourceOperationRiskLevel.MEDIUM },\n ],\n },\n },\n});\n"]}
1
+ {"version":3,"file":"oauth-consent-grants.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OIDC Persistent Consent Grants — Resource Configuration\n *\n * USER_SELF-scoped (the scope is derived from the `USERS → OAUTH_CONSENT_GRANTS` composition\n * relationship). READ / LIST / DELETE are OWNER-facing (`API_CALL` + `APP_USER`): the framework's\n * USER_SELF primary-scope filter restricts every result to the authenticated caller's own grants, so a\n * user can list their \"authorized apps\" and revoke one, and nobody can see or delete another user's\n * grants. CREATE / UPDATE are SYSTEM-only (`INTERNAL_CALL`): the OIDC consent-decision endpoint is the\n * sole writer, via the `createAsSystem` / `updateAsSystem` primitives.\n *\n * `systemAccessPolicy` opts the write + read verbs into the `*AsSystem` door (default is fail-closed\n * FORBIDDEN): the decision endpoint create/updates a grant with no request-context FK to attach\n * (`userId` is re-attached from the schema allowlist on the system path), and the authorize endpoint\n * read/lists a user's grants to decide whether to skip the prompt. There is no `delete` system-access\n * verb (none exists); owner revoke goes through the `API_CALL` DELETE route, and the USERS\n * `onParentDelete` composition cascade deletes grants directly at the repository layer (variant-agnostic).\n */\n\nimport { OAuthConsentGrantSchema } from './oauth-consent-grants.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../../resources/resources-config.shared.schemas';\nimport { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier } from '../../resources/resources.shared.core.definitions';\nimport { ResourceOperationRiskLevel, ResourceOperationVariantType, ResourceRelationship } from '../../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../authorizations/roles.shared.schemas';\nimport { DataPortabilityProvenance } from '../../compliance/privacy/subject-export.shared.schemas';\nimport { ErasureRetentionMode, ResourceDataSubjectKind } from '../../compliance/privacy/impersonalization.shared.schemas';\n\nexport const oauthConsentGrantsResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[]\n) => createResourceConfiguration_Initialization<undefined,\n // UPDATE_MANY is route-less; `retentionPolicy` requires READ + LIST + UPDATE_MANY.\n CoreResourceOperation.READ | CoreResourceOperation.LIST | CoreResourceOperation.CREATE | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE\n | CoreResourceOperation.UPDATE_MANY,\n typeof OAuthConsentGrantSchema\n>({\n mainSchema: OAuthConsentGrantSchema,\n resourceIdentifier: CoreResourceType.OAUTH_CONSENT_GRANTS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.OAUTH_CONSENT_GRANTS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n\n // The OIDC consent-decision endpoint stores/updates grants via createAsSystem/updateAsSystem (the\n // client-shaped CREATE DTO strips the context FK `userId`, re-attached from the schema allowlist on\n // the system path); the authorize endpoint reads/lists a user's grants via readAsSystem/listAsSystem\n // to decide whether to skip the consent prompt. Default is fail-closed FORBIDDEN, so opt each in.\n systemAccessPolicy: {\n exportSubject: ResourceSystemAccessMode.ALLOWED,\n create: ResourceSystemAccessMode.ALLOWED,\n update: ResourceSystemAccessMode.ALLOWED,\n read: ResourceSystemAccessMode.ALLOWED,\n list: ResourceSystemAccessMode.ALLOWED,\n },\n portabilityPolicy: { defaultProvenance: DataPortabilityProvenance.SUBJECT_PROVIDED },\n\n /**\n * Subject erasure RETAINS this row and RESETS the authorized scopes.\n *\n * The grant is kept deliberately: a consent decision is the evidence that access was authorized, which is\n * exactly what an operator must be able to show afterwards. But `scopes` records WHAT this person let a\n * third party reach, which its USAGE category correctly declares — so `RETAIN_ONLY` would be false. The\n * field is a defaulted array, which only `RedactionType.RESET` can scrub; it returns to the empty default,\n * so the grant remains as evidence and what it granted no longer describes the person.\n */\n retentionPolicy: {\n mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,\n },\n\n /**\n * Erasing one of these rows revokes NO session. A grant is a record ABOUT a decision, not the person, and\n * revoking a third-party client's OAuth access is a separate act from ending a login session.\n */\n dataSubjectPolicy: { kind: ResourceDataSubjectKind.NO_PRINCIPAL },\n\n coreOperations: [\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.CREATE,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n // Route-less; `retentionPolicy` requires READ + LIST + UPDATE_MANY.\n CoreResourceOperation.UPDATE_MANY,\n ],\n operationsConfiguration: {\n /*\n * Route-less retention plumbing — INTERNAL_CALL registers no route, so no API surface is added, and it\n * must not be REPOSITORY_ONLY: the retention primitives resolve through `getServiceOperationPathDefault`,\n * which matches only INTERNAL_CALL / API_CALL. LIST is LOW (a read-shaped op may not claim the\n * HIGH/CRITICAL band, which carries an audit-emission obligation); UPDATE_MANY is CRITICAL — it is the\n * verb the impersonalize writer uses to stamp the retention markers.\n */\n [CoreResourceOperation.UPDATE_MANY]: {\n variants: [{\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n }],\n },\n\n // Owner-facing: the USER_SELF primary-scope filter restricts results to the caller's own grants.\n [CoreResourceOperation.READ]: {\n variants: [\n { variantType: ResourceOperationVariantType.API_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_USER], riskLevel: ResourceOperationRiskLevel.LOW },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n { variantType: ResourceOperationVariantType.API_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_USER], riskLevel: ResourceOperationRiskLevel.LOW },\n ],\n },\n // System-only: the consent-decision endpoint is the sole writer (createAsSystem / updateAsSystem).\n [CoreResourceOperation.CREATE]: {\n variants: [\n { variantType: ResourceOperationVariantType.INTERNAL_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN], riskLevel: ResourceOperationRiskLevel.MEDIUM },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n { variantType: ResourceOperationVariantType.INTERNAL_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN], riskLevel: ResourceOperationRiskLevel.MEDIUM },\n ],\n },\n // Owner-facing revoke (USER_SELF filter → own grant only). The USERS onParentDelete composition\n // cascade also deletes grants, directly at the repository layer (variant-agnostic — it only requires\n // a DELETE op to exist).\n [CoreResourceOperation.DELETE]: {\n variants: [\n { variantType: ResourceOperationVariantType.API_CALL, isDefault: true, roles: [CORE_APP_ROLES.APP_USER], riskLevel: ResourceOperationRiskLevel.MEDIUM },\n ],\n },\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-consent-grants.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,uBAAuB;;;;;;;;;kBAuClC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
1
+ {"version":3,"file":"oauth-consent-grants.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,uBAAuB;;;;;;;;;kBA8ClC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -16,6 +16,8 @@
16
16
  */
17
17
  import { z } from 'zod';
18
18
  import { PersonalDataCategory } from '../../compliance/privacy/personal-data-category.shared.schemas.js';
19
+ import { RedactionType } from '../../compliance/privacy/redaction.shared.schemas.js';
20
+ import { addImpersonalizeWith } from '@wildo-ai/zod-decorators';
19
21
  export const OAuthConsentGrantSchema = z.object({
20
22
  _id: z.string().min(1).isPrimaryKey().isSummaryField(),
21
23
  /** FK → USERS — the user who granted consent (the USER_SELF scope owner). */
@@ -36,7 +38,11 @@ export const OAuthConsentGrantSchema = z.object({
36
38
  */
37
39
  // USAGE — what this person authorized a third-party client to reach on their behalf, and when.
38
40
  // A consent record is an observation of a decision they made, not an attribute of them.
39
- scopes: z.array(z.string()).default([]).dataCategories(PersonalDataCategory.USAGE),
41
+ // RESET on erasure — returns this field to its schema default. It holds which scopes this person authorized a third-party client to reach,
42
+ // which the category above correctly declares as personal data. RESET rather than REMOVE or MASK
43
+ // because the field is non-nullable WITH a `.default()`: REMOVE needs a clearable field and MASK
44
+ // needs a string. The value comes from the schema, so there is no second copy to drift.
45
+ scopes: addImpersonalizeWith(z.array(z.string()).default([]).dataCategories(PersonalDataCategory.USAGE), RedactionType.RESET),
40
46
  /**
41
47
  * Optional expiry. When set, the authorize lookup ignores a grant past this instant (fail-closed to a
42
48
  * fresh consent prompt). No operator TTL is authored in v1; the field future-proofs a policy knob
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-consent-grants.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gEAAgE,CAAC;AAEtG,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE;IAEtD,6EAA6E;IAC7E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE,CAAC,iBAAiB,EAAE;IAE3F,iGAAiG;IACjG,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE;IAE1D;;;;OAIG;IACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE;IAE9D;;;;;OAKG;IACH,+FAA+F;IAC/F,wFAAwF;IACxF,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,oBAAoB,CAAC,KAAK,CAAC;IAElF;;;;OAIG;IACH,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE;IAE5C,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;IACpH,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;CACpG,CAAC,CAAC,cAAc,CAAC;IAChB,QAAQ;IACR,UAAU;IACV,UAAU;CACX,CAAC,CAAC","sourcesContent":["/**\n * OIDC Persistent Consent Grants (\"remember this app\")\n *\n * A durable \"user X authorized OAuth client Y for identity scopes Z (at resource R)\" record. It is\n * CREATED (via the system write primitive) when a user APPROVES an interactive-consent request AND\n * opts to remember the app, and READ at `/oauth/authorize` to SKIP re-prompting when an existing grant\n * already covers the requested scopes. The user can list their grants (\"authorized apps\") and revoke\n * one.\n *\n * USER-scoped: owned by `userId` (USER_SELF primary scope, derived from the `USERS → OAUTH_CONSENT_GRANTS`\n * composition relationship). One grant per `(userId, clientId, resource)` — a re-consent UPDATES the\n * granted scopes rather than creating a duplicate. `resource` participates in the grain because two\n * authorize requests from the same client for different RFC 8707 resources (distinct A2A / MCP agent\n * endpoints) are genuinely distinct delegations; an OIDC-only login carries no `resource` and yields a\n * single resource-less grant.\n */\n\nimport { z } from 'zod';\nimport { PersonalDataCategory } from '../../compliance/privacy/personal-data-category.shared.schemas';\n\nexport const OAuthConsentGrantSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n\n /** FK → USERS — the user who granted consent (the USER_SELF scope owner). */\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n\n /** The registered OAuth client (`_id`, `oc_*`) the user authorized. Part of the business key. */\n clientId: z.string().min(1).isDBIndexed().isSummaryField(),\n\n /**\n * The RFC 8707 `resource` (the target A2A / MCP agent endpoint) this grant covers. Absent for an\n * OIDC-only login with no delegation. Part of the business key — a grant authorizes one client for\n * one resource, so different agent endpoints require separate, explicit consent.\n */\n resource: z.string().optional().isDBIndexed().isSummaryField(),\n\n /**\n * The IDENTITY scopes the user granted (e.g. `openid`, `profile`, `email`). A later authorize whose\n * requested scopes are a SUBSET of these skips the prompt; a request for a scope NOT in the grant\n * re-prompts (silent scope expansion is forbidden). Identity only — scopes carry no authorization\n * weight (authorization is roles-only).\n */\n // USAGE — what this person authorized a third-party client to reach on their behalf, and when.\n // A consent record is an observation of a decision they made, not an attribute of them.\n scopes: z.array(z.string()).default([]).dataCategories(PersonalDataCategory.USAGE),\n\n /**\n * Optional expiry. When set, the authorize lookup ignores a grant past this instant (fail-closed to a\n * fresh consent prompt). No operator TTL is authored in v1; the field future-proofs a policy knob\n * without a schema reshape.\n */\n expiresAt: z.date().optional().isDBIndexed(),\n\n createdAt: z.date().default(() => new Date()).isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n}).businessKeySet([\n 'userId',\n 'clientId',\n 'resource',\n]);\n\nexport type OAuthConsentGrant = z.infer<typeof OAuthConsentGrantSchema>;\n"]}
1
+ {"version":3,"file":"oauth-consent-grants.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/security/oauth-clients/oauth-consent-grants.shared.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gEAAgE,CAAC;AACtG,OAAO,EAAE,aAAa,EAAE,MAAM,mDAAmD,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE;IAEtD,6EAA6E;IAC7E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,CAAC,cAAc,EAAE,CAAC,iBAAiB,EAAE;IAE3F,iGAAiG;IACjG,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE;IAE1D;;;;OAIG;IACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE;IAE9D;;;;;OAKG;IACH,+FAA+F;IAC/F,wFAAwF;IACxF,2IAA2I;IAC3I,iGAAiG;IACjG,iGAAiG;IACjG,wFAAwF;IACxF,MAAM,EAAE,oBAAoB,CAC5B,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,oBAAoB,CAAC,KAAK,CAAC,EACxE,aAAa,CAAC,KAAK,CACpB;IAED;;;;OAIG;IACH,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE;IAE5C,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,cAAc,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;IACpH,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,EAAE;CACpG,CAAC,CAAC,cAAc,CAAC;IAChB,QAAQ;IACR,UAAU;IACV,UAAU;CACX,CAAC,CAAC","sourcesContent":["/**\n * OIDC Persistent Consent Grants (\"remember this app\")\n *\n * A durable \"user X authorized OAuth client Y for identity scopes Z (at resource R)\" record. It is\n * CREATED (via the system write primitive) when a user APPROVES an interactive-consent request AND\n * opts to remember the app, and READ at `/oauth/authorize` to SKIP re-prompting when an existing grant\n * already covers the requested scopes. The user can list their grants (\"authorized apps\") and revoke\n * one.\n *\n * USER-scoped: owned by `userId` (USER_SELF primary scope, derived from the `USERS → OAUTH_CONSENT_GRANTS`\n * composition relationship). One grant per `(userId, clientId, resource)` — a re-consent UPDATES the\n * granted scopes rather than creating a duplicate. `resource` participates in the grain because two\n * authorize requests from the same client for different RFC 8707 resources (distinct A2A / MCP agent\n * endpoints) are genuinely distinct delegations; an OIDC-only login carries no `resource` and yields a\n * single resource-less grant.\n */\n\nimport { z } from 'zod';\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\nexport const OAuthConsentGrantSchema = z.object({\n _id: z.string().min(1).isPrimaryKey().isSummaryField(),\n\n /** FK → USERS — the user who granted consent (the USER_SELF scope owner). */\n userId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),\n\n /** The registered OAuth client (`_id`, `oc_*`) the user authorized. Part of the business key. */\n clientId: z.string().min(1).isDBIndexed().isSummaryField(),\n\n /**\n * The RFC 8707 `resource` (the target A2A / MCP agent endpoint) this grant covers. Absent for an\n * OIDC-only login with no delegation. Part of the business key — a grant authorizes one client for\n * one resource, so different agent endpoints require separate, explicit consent.\n */\n resource: z.string().optional().isDBIndexed().isSummaryField(),\n\n /**\n * The IDENTITY scopes the user granted (e.g. `openid`, `profile`, `email`). A later authorize whose\n * requested scopes are a SUBSET of these skips the prompt; a request for a scope NOT in the grant\n * re-prompts (silent scope expansion is forbidden). Identity only — scopes carry no authorization\n * weight (authorization is roles-only).\n */\n // USAGE — what this person authorized a third-party client to reach on their behalf, and when.\n // A consent record is an observation of a decision they made, not an attribute of them.\n // RESET on erasure — returns this field to its schema default. It holds which scopes this person authorized a third-party client to reach,\n // which the category above correctly declares as personal data. RESET rather than REMOVE or MASK\n // because the field is non-nullable WITH a `.default()`: REMOVE needs a clearable field and MASK\n // needs a string. The value comes from the schema, so there is no second copy to drift.\n scopes: addImpersonalizeWith(\n z.array(z.string()).default([]).dataCategories(PersonalDataCategory.USAGE),\n RedactionType.RESET,\n ),\n\n /**\n * Optional expiry. When set, the authorize lookup ignores a grant past this instant (fail-closed to a\n * fresh consent prompt). No operator TTL is authored in v1; the field future-proofs a policy knob\n * without a schema reshape.\n */\n expiresAt: z.date().optional().isDBIndexed(),\n\n createdAt: z.date().default(() => new Date()).isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),\n updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),\n}).businessKeySet([\n 'userId',\n 'clientId',\n 'resource',\n]);\n\nexport type OAuthConsentGrant = z.infer<typeof OAuthConsentGrantSchema>;\n"]}
@@ -33,7 +33,7 @@
33
33
  */
34
34
  import { CoreResourceOperation } from '../resources/resources.shared.core.definitions';
35
35
  import { type ResourceRelationship } from '../resources/resources.shared.schemas';
36
- export declare const inboundContactsResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST | CoreResourceOperation.SEARCH, import("zod").ZodObject<{
36
+ export declare const inboundContactsResourceConfiguration_InitializationFactory: (resourcesRelationships: ResourceRelationship[]) => import("..").ResourceConfiguration<undefined, CoreResourceOperation.READ | CoreResourceOperation.UPDATE | CoreResourceOperation.UPDATE_MANY | CoreResourceOperation.DELETE | CoreResourceOperation.CREATE | CoreResourceOperation.LIST | CoreResourceOperation.SEARCH, import("zod").ZodObject<{
37
37
  _id: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodString>>;
38
38
  email: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodEmail>>;
39
39
  firstName: import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("@wildo-ai/zod-decorators").DecoratedPrimitiveZodType<import("zod").ZodOptional<import("zod").ZodString>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"inbound-contacts.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,OAAO,EACL,qBAAqB,EAGtB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,uCAAuC,CAAC;AAG/C,eAAO,MAAM,0DAA0D,GACrE,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;;;;iCAyH9C,CAAC"}
1
+ {"version":3,"file":"inbound-contacts.shared.resources-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.resources-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,EACL,qBAAqB,EAGtB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,uCAAuC,CAAC;AAG/C,eAAO,MAAM,0DAA0D,GACrE,wBAAwB,oBAAoB,EAAE;;;;;;;;;;;;;;;iCA8J9C,CAAC"}
@@ -32,6 +32,7 @@
32
32
  * joins via `anonymousUserId`.
33
33
  */
34
34
  import { InboundContactSchema } from './inbound-contacts.shared.schemas.js';
35
+ import { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas.js';
35
36
  import { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory.js';
36
37
  import { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas.js';
37
38
  import { CoreResourceOperation, CoreResourceType, coreResourceShared_FieldIdentifier, } from '../resources/resources.shared.core.definitions.js';
@@ -55,6 +56,22 @@ export const inboundContactsResourceConfiguration_InitializationFactory = (resou
55
56
  // Subject-export disclosure. Lead records the subject is the data subject of. ⚠️ Art. 15(4): if an app stores third-party notes ABOUT the subject on these rows, mark those fields `.isBackendOnly()` so the export's security floor withholds them. Secrets and backend-only values are withheld by the export's security floor regardless of this grant. On a polymorphic resource this grant applies to EVERY generated variant, which is the documented intent of the shared config.
56
57
  exportSubject: ResourceSystemAccessMode.ALLOWED, update: ResourceSystemAccessMode.ALLOWED
57
58
  },
59
+ /**
60
+ * Subject erasure scrubs-and-RETAINS the inbound contact.
61
+ *
62
+ * `RETAIN_AND_IMPERSONALIZE`: an inbound contact IS contact details — email, names, phone — and all
63
+ * are scrubbed. The row is retained because it may be the only record that a contact attempt
64
+ * happened, which an operator can legitimately need to account for, and once scrubbed it carries no
65
+ * personal datum.
66
+ */
67
+ retentionPolicy: {
68
+ mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,
69
+ },
70
+ /**
71
+ * The subject reaches this row through EITHER `userId` or `anonymousUserId` — an inbound contact can
72
+ * predate the account. `SELF_PRINCIPAL` names the person the row is about either way.
73
+ */
74
+ dataSubjectPolicy: { kind: ResourceDataSubjectKind.SELF_PRINCIPAL },
58
75
  coreOperations: [
59
76
  CoreResourceOperation.CREATE,
60
77
  CoreResourceOperation.READ,
@@ -62,8 +79,25 @@ export const inboundContactsResourceConfiguration_InitializationFactory = (resou
62
79
  CoreResourceOperation.SEARCH,
63
80
  CoreResourceOperation.UPDATE,
64
81
  CoreResourceOperation.DELETE,
82
+ // Route-less; required by `retentionPolicy` (the in-place scrub).
83
+ CoreResourceOperation.UPDATE_MANY,
65
84
  ],
66
85
  operationsConfiguration: {
86
+ /*
87
+ * Route-less retention plumbing. `retentionPolicy` requires UPDATE_MANY — the impersonalize writer
88
+ * scrubs rows in place through `updateManyAsSystem`. INTERNAL_CALL registers NO route, and must not
89
+ * be REPOSITORY_ONLY: that satisfies the boot-time guard and then fails to resolve at erasure time.
90
+ */
91
+ [CoreResourceOperation.UPDATE_MANY]: {
92
+ variants: [
93
+ {
94
+ variantType: ResourceOperationVariantType.INTERNAL_CALL,
95
+ isDefault: true,
96
+ roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],
97
+ riskLevel: ResourceOperationRiskLevel.CRITICAL,
98
+ },
99
+ ],
100
+ },
67
101
  [CoreResourceOperation.CREATE]: {
68
102
  variants: [
69
103
  /** @wildo_source:part:start example.rate-limit.resource-operation-surface facet:kind:usage-example facet:audience:app-developer */
@@ -1 +1 @@
1
- {"version":3,"file":"inbound-contacts.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * InboundContact Resource Configuration (Phase 0.5, Q-E=B path)\n *\n * Lead-capture resource for the public marketing site.\n * APPLICATION-scope (anchored via the `ANONYMOUS_USERS → INBOUND_CONTACTS`\n * relationship row in `coreSharedResourceRelationship`), NO\n * `isAnonymizable: true` — the framework's anonymizable transposition\n * pipeline gates on USER_SELF semantics, but `INBOUND_CONTACTS` is\n * conceptually an org-owned admin resource. Transposition is handled\n * by a focused manual hook in `AnonymousSessionBackendService` that\n * UPDATEs `{ anonymousUserId } → { userId, anonymousUserId: null }`\n * when the lead converts to an authenticated user.\n *\n * Operations:\n * - CREATE : public — `APP_ANONYMOUS` role, requires an active\n * anonymous session. Per-endpoint rate-limit declared\n * on the variant itself (5 req/min + 30 req/hour per IP),\n * enforced at the controller pre-flight seam by\n * `RateLimitBackendService.checkPolicyForVariant`\n * BEFORE execution-context creation.\n * - READ / LIST : `APP_ADMIN_MARKETING_EDITOR` (admin marketing dashboard).\n * - SEARCH : `APP_ADMIN_MARKETING_EDITOR`.\n * - UPDATE : INTERNAL_CALL only — used by the manual transposition\n * hook in `AnonymousSessionBackendService` to set\n * `{ userId, anonymousUserId: null }` on lead conversion.\n * Not exposed as a public API (no admin-edit surface in\n * Phase 0.5 — leads are read-only in the dashboard).\n * - DELETE : `APP_ADMIN_SUPER_ADMIN` (compliance / data-cleanup).\n *\n * Marketing attribution does NOT live on this row. First-touch\n * attribution is on `AnonymousUser.marketingMetadata` (Q-D=β); analytics\n * joins via `anonymousUserId`.\n */\n\nimport { InboundContactSchema } from './inbound-contacts.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport {\n CoreResourceOperation,\n CoreResourceType,\n coreResourceShared_FieldIdentifier,\n} from '../resources/resources.shared.core.definitions';\nimport {\n ResourceOperationRiskLevel,\n ResourceOperationVariantType,\n type ResourceRelationship,\n} from '../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../security/authorizations/roles.shared.schemas';\n\nexport const inboundContactsResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[],\n) => createResourceConfiguration_Initialization<\n undefined,\n | CoreResourceOperation.CREATE\n | CoreResourceOperation.READ\n | CoreResourceOperation.LIST\n | CoreResourceOperation.SEARCH\n | CoreResourceOperation.UPDATE\n | CoreResourceOperation.DELETE,\n typeof InboundContactSchema\n>({\n mainSchema: InboundContactSchema,\n resourceIdentifier: CoreResourceType.INBOUND_CONTACTS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.INBOUND_CONTACTS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n // Lead-conversion transposition (`AnonymousSessionBackendService`\n // .transposeInboundContactsOwnership) UPDATEs `{ anonymousUserId } →\n // { userId, anonymousUserId: null }` in-process. Both FKs are context-auto-\n // populated names the client-shaped request DTO strips, so a plain update is a\n // silent no-op; `updateAsSystem` re-attaches them after the strict parse,\n // fail-closed behind this opt-in. INBOUND_CONTACTS is APPLICATION-scoped (not\n // `isAnonymizable`), so the grant is authored explicitly here rather than\n // derived by the factory's anonymizable enrichment.\n systemAccessPolicy: {\n // Subject-export disclosure. Lead records the subject is the data subject of. ⚠️ Art. 15(4): if an app stores third-party notes ABOUT the subject on these rows, mark those fields `.isBackendOnly()` so the export's security floor withholds them. Secrets and backend-only values are withheld by the export's security floor regardless of this grant. On a polymorphic resource this grant applies to EVERY generated variant, which is the documented intent of the shared config.\n exportSubject: ResourceSystemAccessMode.ALLOWED, update: ResourceSystemAccessMode.ALLOWED },\n coreOperations: [\n CoreResourceOperation.CREATE,\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.SEARCH,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n ],\n operationsConfiguration: {\n [CoreResourceOperation.CREATE]: {\n variants: [\n /** @wildo_source:part:start example.rate-limit.resource-operation-surface facet:kind:usage-example facet:audience:app-developer */\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ANONYMOUS],\n riskLevel: ResourceOperationRiskLevel.LOW,\n // Public anonymous CREATE — declares the per-IP rate-limit on the\n // default API_CALL variant. Two windows (a short burst limit + an\n // hourly cap) are AND-ed and enforced at the controller pre-flight\n // seam; no bucket-key choice is needed here because IP is the only\n // identity available before execution-context creation. The global\n // HTTP limiter remains the safety net for any path that bypasses\n // this surface.\n rateLimit: {\n requestsPerMinute: 5,\n requestsPerHour: 30,\n },\n },\n /** @wildo_source:part:end example.rate-limit.resource-operation-surface */\n ],\n // No `userNotifications` on CREATE. A Phase-0.5 draft declared an\n // `INBOUND_CONTACT` + `EMAIL` notification, but the dispatcher has no\n // email-by-target recipient resolution for `INBOUND_CONTACT`\n // (`getUserIdsForTarget` falls through to `[]`, so the email was a silent\n // no-op) and there is no template for it. `INBOUND_CONTACT` is wired only\n // for the WEBSOCKET channel (admin in-app alerts) — declare\n // `{ target: INBOUND_CONTACT, channel: WEBSOCKET }` here if an app wants\n // that. For a visitor acknowledgement email, use the email-by-address\n // path — `{ channel: EMAIL, recipientEmailField: 'email' }` (see\n // `organization-member-invitations` CREATE). The paired resource spec\n // asserts no email delivery; keep both in sync.\n },\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_MARKETING_EDITOR],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_MARKETING_EDITOR],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.SEARCH]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_MARKETING_EDITOR],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n },\n});\n"]}
1
+ {"version":3,"file":"inbound-contacts.shared.resources-config.schemas.js","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.resources-config.schemas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * InboundContact Resource Configuration (Phase 0.5, Q-E=B path)\n *\n * Lead-capture resource for the public marketing site.\n * APPLICATION-scope (anchored via the `ANONYMOUS_USERS → INBOUND_CONTACTS`\n * relationship row in `coreSharedResourceRelationship`), NO\n * `isAnonymizable: true` — the framework's anonymizable transposition\n * pipeline gates on USER_SELF semantics, but `INBOUND_CONTACTS` is\n * conceptually an org-owned admin resource. Transposition is handled\n * by a focused manual hook in `AnonymousSessionBackendService` that\n * UPDATEs `{ anonymousUserId } → { userId, anonymousUserId: null }`\n * when the lead converts to an authenticated user.\n *\n * Operations:\n * - CREATE : public — `APP_ANONYMOUS` role, requires an active\n * anonymous session. Per-endpoint rate-limit declared\n * on the variant itself (5 req/min + 30 req/hour per IP),\n * enforced at the controller pre-flight seam by\n * `RateLimitBackendService.checkPolicyForVariant`\n * BEFORE execution-context creation.\n * - READ / LIST : `APP_ADMIN_MARKETING_EDITOR` (admin marketing dashboard).\n * - SEARCH : `APP_ADMIN_MARKETING_EDITOR`.\n * - UPDATE : INTERNAL_CALL only — used by the manual transposition\n * hook in `AnonymousSessionBackendService` to set\n * `{ userId, anonymousUserId: null }` on lead conversion.\n * Not exposed as a public API (no admin-edit surface in\n * Phase 0.5 — leads are read-only in the dashboard).\n * - DELETE : `APP_ADMIN_SUPER_ADMIN` (compliance / data-cleanup).\n *\n * Marketing attribution does NOT live on this row. First-touch\n * attribution is on `AnonymousUser.marketingMetadata` (Q-D=β); analytics\n * joins via `anonymousUserId`.\n */\n\nimport { InboundContactSchema } from './inbound-contacts.shared.schemas';\nimport { ErasureRetentionMode, ResourceDataSubjectKind } from '../compliance/privacy/impersonalization.shared.schemas';\nimport { createResourceConfiguration_Initialization } from '../resources/resources-config.shared.factory';\nimport { ResourceSystemAccessMode } from '../resources/resources-config.shared.schemas';\nimport {\n CoreResourceOperation,\n CoreResourceType,\n coreResourceShared_FieldIdentifier,\n} from '../resources/resources.shared.core.definitions';\nimport {\n ResourceOperationRiskLevel,\n ResourceOperationVariantType,\n type ResourceRelationship,\n} from '../resources/resources.shared.schemas';\nimport { CORE_APP_ROLES } from '../security/authorizations/roles.shared.schemas';\n\nexport const inboundContactsResourceConfiguration_InitializationFactory = (\n resourcesRelationships: ResourceRelationship[],\n) => createResourceConfiguration_Initialization<\n undefined,\n | CoreResourceOperation.CREATE\n | CoreResourceOperation.READ\n | CoreResourceOperation.LIST\n | CoreResourceOperation.SEARCH\n | CoreResourceOperation.UPDATE\n | CoreResourceOperation.UPDATE_MANY\n | CoreResourceOperation.DELETE,\n typeof InboundContactSchema\n>({\n mainSchema: InboundContactSchema,\n resourceIdentifier: CoreResourceType.INBOUND_CONTACTS,\n resourceFieldIdentifier: coreResourceShared_FieldIdentifier[CoreResourceType.INBOUND_CONTACTS],\n resourceRelationships: resourcesRelationships,\n isSystemResource: true,\n // Lead-conversion transposition (`AnonymousSessionBackendService`\n // .transposeInboundContactsOwnership) UPDATEs `{ anonymousUserId } →\n // { userId, anonymousUserId: null }` in-process. Both FKs are context-auto-\n // populated names the client-shaped request DTO strips, so a plain update is a\n // silent no-op; `updateAsSystem` re-attaches them after the strict parse,\n // fail-closed behind this opt-in. INBOUND_CONTACTS is APPLICATION-scoped (not\n // `isAnonymizable`), so the grant is authored explicitly here rather than\n // derived by the factory's anonymizable enrichment.\n systemAccessPolicy: {\n // Subject-export disclosure. Lead records the subject is the data subject of. ⚠️ Art. 15(4): if an app stores third-party notes ABOUT the subject on these rows, mark those fields `.isBackendOnly()` so the export's security floor withholds them. Secrets and backend-only values are withheld by the export's security floor regardless of this grant. On a polymorphic resource this grant applies to EVERY generated variant, which is the documented intent of the shared config.\n exportSubject: ResourceSystemAccessMode.ALLOWED, update: ResourceSystemAccessMode.ALLOWED },\n\n /**\n * Subject erasure scrubs-and-RETAINS the inbound contact.\n *\n * `RETAIN_AND_IMPERSONALIZE`: an inbound contact IS contact details — email, names, phone — and all\n * are scrubbed. The row is retained because it may be the only record that a contact attempt\n * happened, which an operator can legitimately need to account for, and once scrubbed it carries no\n * personal datum.\n */\n retentionPolicy: {\n mode: ErasureRetentionMode.RETAIN_AND_IMPERSONALIZE,\n },\n\n /**\n * The subject reaches this row through EITHER `userId` or `anonymousUserId` — an inbound contact can\n * predate the account. `SELF_PRINCIPAL` names the person the row is about either way.\n */\n dataSubjectPolicy: { kind: ResourceDataSubjectKind.SELF_PRINCIPAL },\n\n coreOperations: [\n CoreResourceOperation.CREATE,\n CoreResourceOperation.READ,\n CoreResourceOperation.LIST,\n CoreResourceOperation.SEARCH,\n CoreResourceOperation.UPDATE,\n CoreResourceOperation.DELETE,\n // Route-less; required by `retentionPolicy` (the in-place scrub).\n CoreResourceOperation.UPDATE_MANY,\n ],\n operationsConfiguration: {\n /*\n * Route-less retention plumbing. `retentionPolicy` requires UPDATE_MANY — the impersonalize writer\n * scrubs rows in place through `updateManyAsSystem`. INTERNAL_CALL registers NO route, and must not\n * be REPOSITORY_ONLY: that satisfies the boot-time guard and then fails to resolve at erasure time.\n */\n [CoreResourceOperation.UPDATE_MANY]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.CRITICAL,\n },\n ],\n },\n [CoreResourceOperation.CREATE]: {\n variants: [\n /** @wildo_source:part:start example.rate-limit.resource-operation-surface facet:kind:usage-example facet:audience:app-developer */\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ANONYMOUS],\n riskLevel: ResourceOperationRiskLevel.LOW,\n // Public anonymous CREATE — declares the per-IP rate-limit on the\n // default API_CALL variant. Two windows (a short burst limit + an\n // hourly cap) are AND-ed and enforced at the controller pre-flight\n // seam; no bucket-key choice is needed here because IP is the only\n // identity available before execution-context creation. The global\n // HTTP limiter remains the safety net for any path that bypasses\n // this surface.\n rateLimit: {\n requestsPerMinute: 5,\n requestsPerHour: 30,\n },\n },\n /** @wildo_source:part:end example.rate-limit.resource-operation-surface */\n ],\n // No `userNotifications` on CREATE. A Phase-0.5 draft declared an\n // `INBOUND_CONTACT` + `EMAIL` notification, but the dispatcher has no\n // email-by-target recipient resolution for `INBOUND_CONTACT`\n // (`getUserIdsForTarget` falls through to `[]`, so the email was a silent\n // no-op) and there is no template for it. `INBOUND_CONTACT` is wired only\n // for the WEBSOCKET channel (admin in-app alerts) — declare\n // `{ target: INBOUND_CONTACT, channel: WEBSOCKET }` here if an app wants\n // that. For a visitor acknowledgement email, use the email-by-address\n // path — `{ channel: EMAIL, recipientEmailField: 'email' }` (see\n // `organization-member-invitations` CREATE). The paired resource spec\n // asserts no email delivery; keep both in sync.\n },\n [CoreResourceOperation.READ]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_MARKETING_EDITOR],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.LIST]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_MARKETING_EDITOR],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.SEARCH]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_MARKETING_EDITOR],\n riskLevel: ResourceOperationRiskLevel.LOW,\n },\n ],\n },\n [CoreResourceOperation.UPDATE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.INTERNAL_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n [CoreResourceOperation.DELETE]: {\n variants: [\n {\n variantType: ResourceOperationVariantType.API_CALL,\n isDefault: true,\n roles: [CORE_APP_ROLES.APP_ADMIN_SUPER_ADMIN],\n riskLevel: ResourceOperationRiskLevel.MEDIUM,\n },\n ],\n },\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"inbound-contacts.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;GAUG;AACH,oBAAY,4BAA4B;IACtC,kEAAkE;IAClE,IAAI,SAAS;IACb,2EAA2E;IAC3E,WAAW,gBAAgB;IAC3B;;;OAGG;IACH,aAAa,kBAAkB;CAChC;AAGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;iBA+F/B,CAAC;AAGH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"inbound-contacts.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../src/users/inbound-contacts.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB;;;;;;;;;;GAUG;AACH,oBAAY,4BAA4B;IACtC,kEAAkE;IAClE,IAAI,SAAS;IACb,2EAA2E;IAC3E,WAAW,gBAAgB;IAC3B;;;OAGG;IACH,aAAa,kBAAkB;CAChC;AAGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;iBA4G/B,CAAC;AAGH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}