@openlup/platform 0.0.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +204 -0
  3. package/README.md +471 -1
  4. package/dist/bff/dispatch.d.ts +27 -0
  5. package/dist/bff/dispatch.js +47 -0
  6. package/dist/bff/envelope.d.ts +46 -0
  7. package/dist/bff/envelope.js +80 -0
  8. package/dist/bff/health.d.ts +24 -0
  9. package/dist/bff/health.js +19 -0
  10. package/dist/bff/index.d.ts +6 -0
  11. package/dist/bff/index.js +6 -0
  12. package/dist/bff/lazyRoute.d.ts +4 -0
  13. package/dist/bff/lazyRoute.js +13 -0
  14. package/dist/bff/requestContext.d.ts +17 -0
  15. package/dist/bff/requestContext.js +26 -0
  16. package/dist/bff/routerRewrite.d.ts +2 -0
  17. package/dist/bff/routerRewrite.js +31 -0
  18. package/dist/blob/binding.d.ts +16 -0
  19. package/dist/blob/binding.js +37 -0
  20. package/dist/blob/filesystem.d.ts +8 -0
  21. package/dist/blob/filesystem.js +71 -0
  22. package/dist/blob/index.d.ts +6 -0
  23. package/dist/blob/index.js +6 -0
  24. package/dist/blob/s3.d.ts +32 -0
  25. package/dist/blob/s3.js +56 -0
  26. package/dist/blob/s3Client.d.ts +25 -0
  27. package/dist/blob/s3Client.js +76 -0
  28. package/dist/blob/supabase.d.ts +40 -0
  29. package/dist/blob/supabase.js +59 -0
  30. package/dist/blob/vercel.d.ts +63 -0
  31. package/dist/blob/vercel.js +69 -0
  32. package/dist/composition/composeBundle.d.ts +47 -0
  33. package/dist/composition/composeBundle.js +45 -0
  34. package/dist/composition/httpBinding.d.ts +8 -0
  35. package/dist/composition/httpBinding.js +18 -0
  36. package/dist/composition/index.d.ts +3 -0
  37. package/dist/composition/index.js +3 -0
  38. package/dist/composition/nodeHost.d.ts +103 -0
  39. package/dist/composition/nodeHost.js +145 -0
  40. package/dist/data/binding.d.ts +34 -0
  41. package/dist/data/binding.js +92 -0
  42. package/dist/data/index.d.ts +5 -0
  43. package/dist/data/index.js +5 -0
  44. package/dist/data/postgresGateway.d.ts +42 -0
  45. package/dist/data/postgresGateway.js +135 -0
  46. package/dist/data/queryBuilder.d.ts +54 -0
  47. package/dist/data/queryBuilder.js +240 -0
  48. package/dist/data/queryBuilderSql.d.ts +76 -0
  49. package/dist/data/queryBuilderSql.js +145 -0
  50. package/dist/data/supabaseClientFactory.d.ts +34 -0
  51. package/dist/data/supabaseClientFactory.js +56 -0
  52. package/dist/data/supabaseGateway.d.ts +22 -0
  53. package/dist/data/supabaseGateway.js +49 -0
  54. package/dist/http/deadline.d.ts +20 -0
  55. package/dist/http/deadline.js +93 -0
  56. package/dist/http/index.d.ts +6 -0
  57. package/dist/http/index.js +5 -0
  58. package/dist/http/requestAdapter.d.ts +38 -0
  59. package/dist/http/requestAdapter.js +203 -0
  60. package/dist/http/runtime.d.ts +83 -0
  61. package/dist/http/runtime.js +199 -0
  62. package/dist/http/security.d.ts +44 -0
  63. package/dist/http/security.js +114 -0
  64. package/dist/http/staticAssets.d.ts +3 -0
  65. package/dist/http/staticAssets.js +42 -0
  66. package/dist/http/types.d.ts +23 -0
  67. package/dist/http/types.js +6 -0
  68. package/dist/identity/adminAuthBinding.d.ts +28 -0
  69. package/dist/identity/adminAuthBinding.js +66 -0
  70. package/dist/identity/adminAuthorization.d.ts +34 -0
  71. package/dist/identity/adminAuthorization.js +72 -0
  72. package/dist/identity/adminContracts.d.ts +70 -0
  73. package/dist/identity/adminContracts.js +43 -0
  74. package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
  75. package/dist/identity/adminMagicLinkHandler.js +94 -0
  76. package/dist/identity/adminMeHandler.d.ts +15 -0
  77. package/dist/identity/adminMeHandler.js +49 -0
  78. package/dist/identity/adminPreflight.d.ts +16 -0
  79. package/dist/identity/adminPreflight.js +38 -0
  80. package/dist/identity/adminUserActionPorts.d.ts +31 -0
  81. package/dist/identity/adminUserActionPorts.js +51 -0
  82. package/dist/identity/adminUserHandlers.d.ts +38 -0
  83. package/dist/identity/adminUserHandlers.js +140 -0
  84. package/dist/identity/authVerifyUrl.d.ts +6 -0
  85. package/dist/identity/authVerifyUrl.js +54 -0
  86. package/dist/identity/contracts/contracts.d.ts +8 -0
  87. package/dist/identity/contracts/contracts.js +2 -0
  88. package/dist/identity/contracts/emailTrust.d.ts +3 -0
  89. package/dist/identity/contracts/emailTrust.js +4 -0
  90. package/dist/identity/contracts/index.d.ts +8 -0
  91. package/dist/identity/contracts/index.js +8 -0
  92. package/dist/identity/contracts/oidc.d.ts +3 -0
  93. package/dist/identity/contracts/oidc.js +9 -0
  94. package/dist/identity/contracts/operatorAuth.d.ts +35 -0
  95. package/dist/identity/contracts/operatorAuth.js +53 -0
  96. package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
  97. package/dist/identity/contracts/operatorInvitations.js +1 -0
  98. package/dist/identity/contracts/ports.d.ts +22 -0
  99. package/dist/identity/contracts/ports.js +1 -0
  100. package/dist/identity/contracts/privacy.d.ts +62 -0
  101. package/dist/identity/contracts/privacy.js +99 -0
  102. package/dist/identity/contracts/providerList.d.ts +3 -0
  103. package/dist/identity/contracts/providerList.js +11 -0
  104. package/dist/identity/contracts/types.d.ts +16 -0
  105. package/dist/identity/contracts/types.js +4 -0
  106. package/dist/identity/contribution.d.ts +8 -0
  107. package/dist/identity/contribution.js +22 -0
  108. package/dist/identity/customerAuth.d.ts +8 -0
  109. package/dist/identity/customerAuth.js +1 -0
  110. package/dist/identity/customerAuthorization.d.ts +5 -0
  111. package/dist/identity/customerAuthorization.js +22 -0
  112. package/dist/identity/customerContracts.d.ts +42 -0
  113. package/dist/identity/customerContracts.js +43 -0
  114. package/dist/identity/customerIdentityBinding.d.ts +44 -0
  115. package/dist/identity/customerIdentityBinding.js +60 -0
  116. package/dist/identity/customerIdentityVerifier.d.ts +8 -0
  117. package/dist/identity/customerIdentityVerifier.js +101 -0
  118. package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
  119. package/dist/identity/customerMagicLinkHandler.js +115 -0
  120. package/dist/identity/customerMeHandler.d.ts +11 -0
  121. package/dist/identity/customerMeHandler.js +46 -0
  122. package/dist/identity/customerPorts.d.ts +4 -0
  123. package/dist/identity/customerPorts.js +1 -0
  124. package/dist/identity/customerSessionBinding.d.ts +38 -0
  125. package/dist/identity/customerSessionBinding.js +80 -0
  126. package/dist/identity/customerSessionIssuer.d.ts +10 -0
  127. package/dist/identity/customerSessionIssuer.js +57 -0
  128. package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
  129. package/dist/identity/customerVerifyOtpHandler.js +41 -0
  130. package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
  131. package/dist/identity/directOperatorInvitationStore.js +193 -0
  132. package/dist/identity/emailTrustPolicy.d.ts +14 -0
  133. package/dist/identity/emailTrustPolicy.js +16 -0
  134. package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
  135. package/dist/identity/identityPersonalDataContributor.js +46 -0
  136. package/dist/identity/identityReadiness.d.ts +10 -0
  137. package/dist/identity/identityReadiness.js +25 -0
  138. package/dist/identity/index.d.ts +67 -0
  139. package/dist/identity/index.js +66 -0
  140. package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
  141. package/dist/identity/inviteAdminUserUseCase.js +75 -0
  142. package/dist/identity/jwtLibrary.d.ts +54 -0
  143. package/dist/identity/jwtLibrary.js +1 -0
  144. package/dist/identity/machineActorWriteFence.d.ts +4 -0
  145. package/dist/identity/machineActorWriteFence.js +13 -0
  146. package/dist/identity/magicLinkPorts.d.ts +28 -0
  147. package/dist/identity/magicLinkPorts.js +1 -0
  148. package/dist/identity/managedAccountLinkStore.d.ts +2 -0
  149. package/dist/identity/managedAccountLinkStore.js +93 -0
  150. package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
  151. package/dist/identity/managedAdminAuthVerifier.js +27 -0
  152. package/dist/identity/managedAdminUserGateways.d.ts +123 -0
  153. package/dist/identity/managedAdminUserGateways.js +127 -0
  154. package/dist/identity/managedClient.d.ts +90 -0
  155. package/dist/identity/managedClient.js +10 -0
  156. package/dist/identity/managedClientAuthLink.d.ts +3 -0
  157. package/dist/identity/managedClientAuthLink.js +115 -0
  158. package/dist/identity/managedCustomerMe.d.ts +13 -0
  159. package/dist/identity/managedCustomerMe.js +30 -0
  160. package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
  161. package/dist/identity/managedEmailTrustPolicy.js +105 -0
  162. package/dist/identity/managedIdentityVerifier.d.ts +2 -0
  163. package/dist/identity/managedIdentityVerifier.js +16 -0
  164. package/dist/identity/managedPrivacy.d.ts +4 -0
  165. package/dist/identity/managedPrivacy.js +39 -0
  166. package/dist/identity/messages.d.ts +29 -0
  167. package/dist/identity/messages.js +39 -0
  168. package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
  169. package/dist/identity/oidcIdentityVerifier.js +309 -0
  170. package/dist/identity/oidcPrincipal.d.ts +2 -0
  171. package/dist/identity/oidcPrincipal.js +10 -0
  172. package/dist/identity/operatorAdminAuth.d.ts +6 -0
  173. package/dist/identity/operatorAdminAuth.js +13 -0
  174. package/dist/identity/operatorAuthHandlers.d.ts +18 -0
  175. package/dist/identity/operatorAuthHandlers.js +71 -0
  176. package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
  177. package/dist/identity/operatorIdentityVerifier.js +19 -0
  178. package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
  179. package/dist/identity/operatorInvitationLifecycle.js +15 -0
  180. package/dist/identity/operatorInvitationPorts.d.ts +31 -0
  181. package/dist/identity/operatorInvitationPorts.js +1 -0
  182. package/dist/identity/operatorPorts.d.ts +37 -0
  183. package/dist/identity/operatorPorts.js +1 -0
  184. package/dist/identity/operatorSession.d.ts +13 -0
  185. package/dist/identity/operatorSession.js +31 -0
  186. package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
  187. package/dist/identity/operatorTokenAdminAuth.js +50 -0
  188. package/dist/identity/personalDataRegistry.d.ts +4 -0
  189. package/dist/identity/personalDataRegistry.js +11 -0
  190. package/dist/identity/platformAdminAuth.d.ts +15 -0
  191. package/dist/identity/platformAdminAuth.js +1 -0
  192. package/dist/identity/ports.d.ts +127 -0
  193. package/dist/identity/ports.js +38 -0
  194. package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
  195. package/dist/identity/postgresAccountLinkStore.js +52 -0
  196. package/dist/identity/postgresCustomerMe.d.ts +3 -0
  197. package/dist/identity/postgresCustomerMe.js +21 -0
  198. package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
  199. package/dist/identity/postgresCustomerRecovery.js +57 -0
  200. package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
  201. package/dist/identity/postgresOperatorIdentity.js +137 -0
  202. package/dist/identity/postgresPrivacy.d.ts +7 -0
  203. package/dist/identity/postgresPrivacy.js +126 -0
  204. package/dist/identity/privacyHandlers.d.ts +10 -0
  205. package/dist/identity/privacyHandlers.js +35 -0
  206. package/dist/identity/privacyPorts.d.ts +72 -0
  207. package/dist/identity/privacyPorts.js +8 -0
  208. package/dist/identity/privacyUseCases.d.ts +5 -0
  209. package/dist/identity/privacyUseCases.js +55 -0
  210. package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
  211. package/dist/identity/reconcileAccountForPrincipal.js +66 -0
  212. package/dist/identity/reconcileAccountHandler.d.ts +11 -0
  213. package/dist/identity/reconcileAccountHandler.js +30 -0
  214. package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
  215. package/dist/identity/removeAdminUserUseCase.js +60 -0
  216. package/dist/identity/response.d.ts +3 -0
  217. package/dist/identity/response.js +2 -0
  218. package/dist/identity/sql.d.ts +44 -0
  219. package/dist/identity/sql.js +10 -0
  220. package/dist/job-ledger/index.d.ts +5 -0
  221. package/dist/job-ledger/index.js +3 -0
  222. package/dist/job-ledger/ports.d.ts +10 -0
  223. package/dist/job-ledger/ports.js +7 -0
  224. package/dist/job-ledger/postgres.d.ts +10 -0
  225. package/dist/job-ledger/postgres.js +101 -0
  226. package/dist/job-ledger/sql.d.ts +7 -0
  227. package/dist/job-ledger/sql.js +10 -0
  228. package/dist/job-ledger/supabase.d.ts +77 -0
  229. package/dist/job-ledger/supabase.js +202 -0
  230. package/dist/kernel/index.d.ts +1 -0
  231. package/dist/kernel/index.js +1 -0
  232. package/dist/kernel/platformKernel.d.ts +34 -0
  233. package/dist/kernel/platformKernel.js +115 -0
  234. package/dist/migrations/index.d.ts +1 -0
  235. package/dist/migrations/index.js +1 -0
  236. package/dist/migrations/postgresRunner.d.ts +50 -0
  237. package/dist/migrations/postgresRunner.js +176 -0
  238. package/dist/scheduler/advisoryLock.d.ts +44 -0
  239. package/dist/scheduler/advisoryLock.js +114 -0
  240. package/dist/scheduler/cronGuard.d.ts +10 -0
  241. package/dist/scheduler/cronGuard.js +19 -0
  242. package/dist/scheduler/cronSecret.d.ts +5 -0
  243. package/dist/scheduler/cronSecret.js +23 -0
  244. package/dist/scheduler/handlers.d.ts +9 -0
  245. package/dist/scheduler/handlers.js +10 -0
  246. package/dist/scheduler/httpEntry.d.ts +16 -0
  247. package/dist/scheduler/httpEntry.js +31 -0
  248. package/dist/scheduler/index.d.ts +9 -0
  249. package/dist/scheduler/index.js +9 -0
  250. package/dist/scheduler/jobRegistry.d.ts +21 -0
  251. package/dist/scheduler/jobRegistry.js +39 -0
  252. package/dist/scheduler/jobs.d.ts +48 -0
  253. package/dist/scheduler/jobs.js +92 -0
  254. package/dist/scheduler/nodeScheduler.d.ts +42 -0
  255. package/dist/scheduler/nodeScheduler.js +73 -0
  256. package/dist/scheduler/vercelScheduler.d.ts +29 -0
  257. package/dist/scheduler/vercelScheduler.js +51 -0
  258. package/package.json +116 -3
  259. package/release-gates.json +143 -0
  260. package/sql/20260809150000_job_run_ledger.sql +301 -0
  261. package/sql/20260809150001_job_run_ledger_v3.sql +70 -0
  262. package/sql/20260810180001_self_host_actor_auth.sql +40 -0
  263. package/sql/20260810204057_customer_identity_read.sql +31 -0
  264. package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
  265. package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
  266. package/sql/20261018120000_identity_operator_invitations.sql +42 -0
  267. package/sql/20261018180000_identity_operator_challenges.sql +56 -0
  268. package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
  269. package/src/bff/dispatch.ts +81 -0
  270. package/src/bff/envelope.ts +142 -0
  271. package/src/bff/health.ts +37 -0
  272. package/src/bff/index.ts +30 -0
  273. package/src/bff/lazyRoute.ts +28 -0
  274. package/src/bff/requestContext.ts +48 -0
  275. package/src/bff/routerRewrite.ts +38 -0
  276. package/src/blob/binding.ts +55 -0
  277. package/src/blob/filesystem.ts +86 -0
  278. package/src/blob/index.ts +17 -0
  279. package/src/blob/s3.ts +87 -0
  280. package/src/blob/s3Client.ts +91 -0
  281. package/src/blob/supabase.ts +88 -0
  282. package/src/blob/vercel.ts +127 -0
  283. package/src/composition/composeBundle.ts +109 -0
  284. package/src/composition/httpBinding.ts +30 -0
  285. package/src/composition/index.ts +17 -0
  286. package/src/composition/nodeHost.ts +289 -0
  287. package/src/data/binding.ts +157 -0
  288. package/src/data/index.ts +33 -0
  289. package/src/data/postgresGateway.ts +186 -0
  290. package/src/data/queryBuilder.ts +300 -0
  291. package/src/data/queryBuilderSql.ts +183 -0
  292. package/src/data/supabaseClientFactory.ts +92 -0
  293. package/src/data/supabaseGateway.ts +76 -0
  294. package/src/http/deadline.ts +117 -0
  295. package/src/http/index.ts +36 -0
  296. package/src/http/requestAdapter.ts +215 -0
  297. package/src/http/runtime.ts +319 -0
  298. package/src/http/security.ts +155 -0
  299. package/src/http/staticAssets.ts +42 -0
  300. package/src/http/types.ts +31 -0
  301. package/src/identity/adminAuthBinding.ts +76 -0
  302. package/src/identity/adminAuthorization.ts +106 -0
  303. package/src/identity/adminContracts.ts +52 -0
  304. package/src/identity/adminMagicLinkHandler.ts +132 -0
  305. package/src/identity/adminMeHandler.ts +64 -0
  306. package/src/identity/adminPreflight.ts +46 -0
  307. package/src/identity/adminUserActionPorts.ts +78 -0
  308. package/src/identity/adminUserHandlers.ts +174 -0
  309. package/src/identity/authVerifyUrl.ts +56 -0
  310. package/src/identity/contracts/contracts.ts +15 -0
  311. package/src/identity/contracts/emailTrust.ts +5 -0
  312. package/src/identity/contracts/index.ts +8 -0
  313. package/src/identity/contracts/oidc.ts +10 -0
  314. package/src/identity/contracts/operatorAuth.ts +61 -0
  315. package/src/identity/contracts/operatorInvitations.ts +32 -0
  316. package/src/identity/contracts/ports.ts +25 -0
  317. package/src/identity/contracts/privacy.ts +86 -0
  318. package/src/identity/contracts/providerList.ts +13 -0
  319. package/src/identity/contracts/types.ts +25 -0
  320. package/src/identity/contribution.ts +29 -0
  321. package/src/identity/customerAuth.ts +3 -0
  322. package/src/identity/customerAuthorization.ts +38 -0
  323. package/src/identity/customerContracts.ts +52 -0
  324. package/src/identity/customerIdentityBinding.ts +125 -0
  325. package/src/identity/customerIdentityVerifier.ts +141 -0
  326. package/src/identity/customerMagicLinkHandler.ts +171 -0
  327. package/src/identity/customerMeHandler.ts +64 -0
  328. package/src/identity/customerPorts.ts +2 -0
  329. package/src/identity/customerSessionBinding.ts +114 -0
  330. package/src/identity/customerSessionIssuer.ts +71 -0
  331. package/src/identity/customerVerifyOtpHandler.ts +44 -0
  332. package/src/identity/directOperatorInvitationStore.ts +178 -0
  333. package/src/identity/emailTrustPolicy.ts +16 -0
  334. package/src/identity/identityPersonalDataContributor.ts +48 -0
  335. package/src/identity/identityReadiness.ts +26 -0
  336. package/src/identity/index.ts +69 -0
  337. package/src/identity/inviteAdminUserUseCase.ts +134 -0
  338. package/src/identity/jwtLibrary.ts +24 -0
  339. package/src/identity/machineActorWriteFence.ts +19 -0
  340. package/src/identity/magicLinkPorts.ts +7 -0
  341. package/src/identity/managedAccountLinkStore.ts +105 -0
  342. package/src/identity/managedAdminAuthVerifier.ts +40 -0
  343. package/src/identity/managedAdminUserGateways.ts +164 -0
  344. package/src/identity/managedClient.ts +58 -0
  345. package/src/identity/managedClientAuthLink.ts +120 -0
  346. package/src/identity/managedCustomerMe.ts +52 -0
  347. package/src/identity/managedEmailTrustPolicy.ts +90 -0
  348. package/src/identity/managedIdentityVerifier.ts +20 -0
  349. package/src/identity/managedPrivacy.ts +37 -0
  350. package/src/identity/messages.ts +41 -0
  351. package/src/identity/oidcIdentityVerifier.ts +229 -0
  352. package/src/identity/oidcPrincipal.ts +10 -0
  353. package/src/identity/operatorAdminAuth.ts +14 -0
  354. package/src/identity/operatorAuthHandlers.ts +52 -0
  355. package/src/identity/operatorIdentityVerifier.ts +19 -0
  356. package/src/identity/operatorInvitationLifecycle.ts +14 -0
  357. package/src/identity/operatorInvitationPorts.ts +11 -0
  358. package/src/identity/operatorPorts.ts +11 -0
  359. package/src/identity/operatorSession.ts +30 -0
  360. package/src/identity/operatorTokenAdminAuth.ts +79 -0
  361. package/src/identity/personalDataRegistry.ts +8 -0
  362. package/src/identity/platformAdminAuth.ts +7 -0
  363. package/src/identity/ports.ts +158 -0
  364. package/src/identity/postgresAccountLinkStore.ts +49 -0
  365. package/src/identity/postgresCustomerMe.ts +33 -0
  366. package/src/identity/postgresCustomerRecovery.ts +72 -0
  367. package/src/identity/postgresOperatorIdentity.ts +123 -0
  368. package/src/identity/postgresPrivacy.ts +117 -0
  369. package/src/identity/privacyHandlers.ts +33 -0
  370. package/src/identity/privacyPorts.ts +52 -0
  371. package/src/identity/privacyUseCases.ts +48 -0
  372. package/src/identity/reconcileAccountForPrincipal.ts +99 -0
  373. package/src/identity/reconcileAccountHandler.ts +36 -0
  374. package/src/identity/removeAdminUserUseCase.ts +85 -0
  375. package/src/identity/response.ts +3 -0
  376. package/src/identity/sql.ts +10 -0
  377. package/src/job-ledger/index.ts +25 -0
  378. package/src/job-ledger/ports.ts +27 -0
  379. package/src/job-ledger/postgres.ts +121 -0
  380. package/src/job-ledger/sql.ts +12 -0
  381. package/src/job-ledger/supabase.ts +294 -0
  382. package/src/kernel/index.ts +17 -0
  383. package/src/kernel/platformKernel.ts +153 -0
  384. package/src/migrations/index.ts +10 -0
  385. package/src/migrations/postgresRunner.ts +255 -0
  386. package/src/scheduler/advisoryLock.ts +145 -0
  387. package/src/scheduler/cronGuard.ts +30 -0
  388. package/src/scheduler/cronSecret.ts +26 -0
  389. package/src/scheduler/handlers.ts +24 -0
  390. package/src/scheduler/httpEntry.ts +49 -0
  391. package/src/scheduler/index.ts +47 -0
  392. package/src/scheduler/jobRegistry.ts +57 -0
  393. package/src/scheduler/jobs.ts +119 -0
  394. package/src/scheduler/nodeScheduler.ts +123 -0
  395. package/src/scheduler/vercelScheduler.ts +71 -0
@@ -0,0 +1,48 @@
1
+ import { privacyExportResponseSchema, erasureReceiptSchema, privacySubjectSchema, privacyEraseRequestSchema } from "./contracts/privacy.js";
2
+ import { PrivacyRefusal, type PrivacyUseCaseDependencies, type AuthorizedHumanAdministrator, type PrivacyOperations } from "./privacyPorts.js";
3
+ import type { PrivacySubjectRef, PrivacyEraseRequest, PrivacyExport, ErasureReceipt } from "./contracts/privacy.js";
4
+
5
+ export async function exportPersonalData<C>(deps: PrivacyUseCaseDependencies<C>, actor: AuthorizedHumanAdministrator, input: PrivacySubjectRef): Promise<PrivacyExport> {
6
+ const subject = privacySubjectSchema.parse(input);
7
+ return deps.unitOfWork.run(async context => {
8
+ const resolved = await deps.store.resolve(context, subject);
9
+ if (!resolved) throw new PrivacyRefusal("PRIVACY_SUBJECT_NOT_FOUND");
10
+ const documents = [];
11
+ for (const contributor of deps.registry.contributors) {
12
+ const document = await contributor.export(context, resolved);
13
+ if (document.contributorId !== contributor.id || document.version !== contributor.version) throw new PrivacyRefusal("PRIVACY_OWNERSHIP_UNDECLARED");
14
+ documents.push(document);
15
+ }
16
+ return privacyExportResponseSchema.parse({ format: "personal-data.v1", generatedAt: deps.now().toISOString(), subject, documents, missingContributors: [...deps.registry.missingContributors], erasure: await deps.store.findReceipt(context, subject) });
17
+ });
18
+ }
19
+ export async function erasePersonalData<C>(deps: PrivacyUseCaseDependencies<C>, actor: AuthorizedHumanAdministrator, request: PrivacyEraseRequest): Promise<ErasureReceipt> {
20
+ const input = privacyEraseRequestSchema.parse(request);
21
+ // Capability refusal precedes even opening a managed transaction or provider request.
22
+ if (!deps.store.erasureSupported) throw new PrivacyRefusal("MANAGED_PRIVACY_ERASURE_UNAVAILABLE");
23
+ if (deps.registry.missingContributors.length || !deps.registry.contributors.some(c => c.id === "identity")) throw new PrivacyRefusal("PRIVACY_CONTRIBUTOR_MISSING");
24
+ return deps.unitOfWork.run(async context => {
25
+ await deps.store.prepareErasure(context);
26
+ // Domain relation locks precede subject/member row locks and remain held to commit.
27
+ for (const contributor of deps.registry.contributors) await contributor.prepareErasure(context);
28
+ const resolved = await deps.store.resolve(context, input.subject, actor);
29
+ await deps.store.authorizeErasure(context, actor);
30
+ const previous = await deps.store.findReceipt(context, input.subject, input.idempotencyKey);
31
+ if (previous) return erasureReceiptSchema.parse({ ...previous, outcome: "ALREADY_ERASED" });
32
+ if (!resolved) throw new PrivacyRefusal("PRIVACY_SUBJECT_NOT_FOUND");
33
+ const assessments = [];
34
+ for (const contributor of deps.registry.contributors) {
35
+ const assessment = await contributor.assessErasure(context, resolved);
36
+ if (assessment.obligations.some(o => o.disposition === "unresolved")) throw new PrivacyRefusal("PRIVACY_OWNERSHIP_UNDECLARED");
37
+ assessments.push(assessment);
38
+ }
39
+ const dispositions = [];
40
+ for (let i = 0; i < deps.registry.contributors.length; i++) dispositions.push(await deps.registry.contributors[i]!.eraseOrRetain(context, resolved, assessments[i]!, actor));
41
+ const receipt = erasureReceiptSchema.parse({ version: "identity.erasure.v1", erasureId: deps.randomId(), completedAt: deps.now().toISOString(), outcome: "ERASED", obligations: dispositions.flatMap(d => d.obligations) });
42
+ await deps.store.saveReceipt(context, resolved, actor, input.idempotencyKey, receipt, dispositions);
43
+ return receipt;
44
+ });
45
+ }
46
+ export function createPrivacyOperations<C>(deps: PrivacyUseCaseDependencies<C>): PrivacyOperations {
47
+ return { export: (actor, subject) => exportPersonalData(deps, actor, subject), erase: (actor, input) => erasePersonalData(deps, actor, input) };
48
+ }
@@ -0,0 +1,99 @@
1
+ import { assessEmailTrust, type EmailTrustPolicy } from "./emailTrustPolicy.js";
2
+ import type { EmailTrustFailureCode } from "./contracts/emailTrust.js";
3
+ import type { AccountLinkStore } from "./ports.js";
4
+
5
+
6
+
7
+ export interface ReconcileAccountInput {
8
+ principalId: string;
9
+ email: string | null;
10
+ emailVerified: boolean;
11
+ }
12
+
13
+ export type ReconcileAccountErrorCode =
14
+ | EmailTrustFailureCode
15
+ | "NO_VERIFIED_EMAIL"
16
+ | "RESERVED_PRINCIPAL_REFUSED"
17
+ | "PRINCIPAL_EMAIL_MISMATCH"
18
+ | "ACCOUNT_LINK_CONFLICT"
19
+ | "EMAIL_OWNED_BY_OTHER_PRINCIPAL";
20
+
21
+ export type ReconcileAccountResult =
22
+ | { ok: true; accountId: string; created: boolean; linked: boolean }
23
+ | { ok: false; code: ReconcileAccountErrorCode; existingPrincipalId?: string | null };
24
+
25
+ export async function reconcileAccountForPrincipal(
26
+ store: AccountLinkStore,
27
+ input: ReconcileAccountInput,
28
+ emailTrustPolicy: EmailTrustPolicy,
29
+ ): Promise<ReconcileAccountResult> {
30
+ const email = normalizeEmail(input.email);
31
+ if (!email || !input.emailVerified) {
32
+ return { ok: false, code: "NO_VERIFIED_EMAIL" };
33
+ }
34
+ if (await store.isReservedPrincipal(input.principalId)) {
35
+ return { ok: false, code: "RESERVED_PRINCIPAL_REFUSED" };
36
+ }
37
+
38
+ const byPrincipal = await store.findAccountByPrincipalId(input.principalId);
39
+ if (byPrincipal) {
40
+ const accountEmail = normalizeEmail(byPrincipal.email);
41
+ if (accountEmail && accountEmail !== email) {
42
+ return { ok: false, code: "PRINCIPAL_EMAIL_MISMATCH", existingPrincipalId: input.principalId };
43
+ }
44
+ return { ok: true, accountId: byPrincipal.id, created: false, linked: false };
45
+ }
46
+
47
+ const trust = await assessEmailTrust(emailTrustPolicy);
48
+ if (!trust.trusted) return { ok: false, code: trust.code };
49
+
50
+ const viaEmail = await resolveByEmail(store, email, input.principalId);
51
+ if (viaEmail) return viaEmail;
52
+ try {
53
+ const created = await store.createAccount({
54
+ email,
55
+ principalId: input.principalId,
56
+ acquisitionSource: "social_signup",
57
+ lifecycleStage: "lead",
58
+ });
59
+ return { ok: true, accountId: created.id, created: true, linked: true };
60
+ } catch (err) {
61
+ const retry = await resolveByEmail(store, email, input.principalId);
62
+ if (retry) return retry;
63
+ throw err;
64
+ }
65
+ }
66
+
67
+ function normalizeEmail(email: string | null | undefined): string {
68
+ return (email ?? "").trim().toLowerCase();
69
+ }
70
+
71
+ async function resolveByEmail(
72
+ store: AccountLinkStore,
73
+ email: string,
74
+ principalId: string,
75
+ ): Promise<ReconcileAccountResult | null> {
76
+ const account = await store.findAccountByEmail(email);
77
+ if (!account) return null;
78
+
79
+ if (account.principalId === principalId) {
80
+ return { ok: true, accountId: account.id, created: false, linked: false };
81
+ }
82
+ if (account.principalId !== null) {
83
+ return {
84
+ ok: false,
85
+ code: "EMAIL_OWNED_BY_OTHER_PRINCIPAL",
86
+ existingPrincipalId: account.principalId,
87
+ };
88
+ }
89
+
90
+ const link = await store.linkPrincipalToAccount(account.id, principalId);
91
+ if (link.ok === true) {
92
+ return { ok: true, accountId: account.id, created: false, linked: true };
93
+ }
94
+ return {
95
+ ok: false,
96
+ code: "ACCOUNT_LINK_CONFLICT",
97
+ existingPrincipalId: link.existingPrincipalId,
98
+ };
99
+ }
@@ -0,0 +1,36 @@
1
+ import type { EmailTrustPolicy } from "./emailTrustPolicy.js";
2
+ import { createIdentityResponder, type IdentityResponseOptions } from "./response.js";
3
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
4
+ import type { AccountLinkStore, IdentityVerifierPort } from "./ports.js";
5
+ import { reconcileAccountForPrincipal, type ReconcileAccountErrorCode } from "./reconcileAccountForPrincipal.js";
6
+ import { readBearerToken } from "./adminAuthorization.js";
7
+
8
+ export function createReconcileAccountHandler(options: IdentityResponseOptions & {
9
+ verifier: IdentityVerifierPort;
10
+ store: AccountLinkStore;
11
+ emailTrustPolicy: EmailTrustPolicy;
12
+ message?: (code: ReconcileAccountErrorCode) => string;
13
+ }) {
14
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createIdentityResponder(options);
15
+ return async (req: HttpRequest, res: HttpResponse): Promise<void> => {
16
+ if (req.method !== "POST") return sendMethodNotAllowed(res, ["POST"]);
17
+ const token = readBearerToken(req);
18
+ if (!token) return sendBffError(res, "UNAUTHORIZED", "Customer session required");
19
+ try {
20
+ const principal = await options.verifier.verifyAccessToken(token);
21
+ if (!principal) return sendBffError(res, "UNAUTHORIZED", "Customer session required");
22
+ const result = await reconcileAccountForPrincipal(options.store, principal, options.emailTrustPolicy);
23
+ if (!result.ok) {
24
+ const code = result.code === "NO_VERIFIED_EMAIL" ? "BAD_REQUEST"
25
+ : result.code === "EMAIL_TRUST_SETTINGS_UNAVAILABLE" ? "UPSTREAM_UNAVAILABLE"
26
+ : result.code === "RESERVED_PRINCIPAL_REFUSED" || result.code === "EMAIL_TRUST_ENVELOPE_REFUSED" ? "FORBIDDEN" : "CONFLICT";
27
+ sendBffError(res, code,
28
+ options.message?.(result.code) ?? result.code, { details: { code: result.code } });
29
+ return;
30
+ }
31
+ sendBffSuccess(res, { accountId: result.accountId, created: result.created, linked: result.linked });
32
+ } catch {
33
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Account reconciliation failed");
34
+ }
35
+ };
36
+ }
@@ -0,0 +1,85 @@
1
+ import type { OperatorInvitationStore } from "./operatorInvitationPorts.js";
2
+ import { identityMessages, type IdentityMessages } from "./messages.js";
3
+ export type RemoveAdminUserResponse = {
4
+ revoked?: true;
5
+ error?: string;
6
+ code?: string;
7
+ };
8
+
9
+ export interface RemoveAdminUserGateway {
10
+ getUser: (accessToken: string) => Promise<{
11
+ user: { id: string } | null;
12
+ error: unknown;
13
+ }>;
14
+ findAdminUser: (userId: string) => Promise<{ id: string; role: string } | null>;
15
+ revokeAdminUser: (userId: string) => Promise<{ error: { message: string } | null }>;
16
+ }
17
+
18
+ export async function runRemoveAdminUserUseCase(
19
+ request: { accessToken: string | null; userId: string },
20
+ gateway: RemoveAdminUserGateway,
21
+ messages: IdentityMessages = identityMessages.en,
22
+ invitationStore?: Pick<OperatorInvitationStore, "remove">,
23
+ ): Promise<{ status: number; body: RemoveAdminUserResponse }> {
24
+ try {
25
+ if (!request.accessToken) {
26
+ return { status: 401, body: { code: "UNAUTHORIZED", error: messages.unauthorized } };
27
+ }
28
+
29
+ const { user, error: authError } = await gateway.getUser(request.accessToken);
30
+ if (authError || !user) {
31
+ return { status: 401, body: { code: "INVALID_TOKEN", error: messages.invalidToken } };
32
+ }
33
+
34
+ const callerAdmin = await gateway.findAdminUser(user.id);
35
+ if (!callerAdmin || callerAdmin.role !== "admin") {
36
+ return { status: 403, body: { code: "REMOVE_FORBIDDEN", error: messages.removeForbidden } };
37
+ }
38
+
39
+ if (!request.userId) {
40
+ return { status: 400, body: { code: "USER_ID_REQUIRED", error: messages.userIdRequired } };
41
+ }
42
+
43
+ if (request.userId === user.id) {
44
+ return { status: 403, body: { code: "SELF_REVOKE_FORBIDDEN", error: messages.selfRemoveForbidden } };
45
+ }
46
+
47
+ if (invitationStore) {
48
+ try {
49
+ await invitationStore.remove({ actorPrincipalId: user.id, targetPrincipalId: request.userId });
50
+ return { status: 200, body: { revoked: true } };
51
+ } catch (error) {
52
+ const failure = error as { identityCode?: string; bffCode?: string; message?: string };
53
+ if (failure.identityCode) return { status: failure.bffCode === "FORBIDDEN" ? 403 : failure.bffCode === "BAD_REQUEST" ? 400 : 409, body: { code: failure.identityCode, error: failure.identityCode } };
54
+ return mapRevokeError(failure.message ?? "revoke_failed", messages);
55
+ }
56
+ }
57
+ const { error: revokeError } = await gateway.revokeAdminUser(request.userId);
58
+ if (revokeError) {
59
+ return mapRevokeError(revokeError.message, messages);
60
+ }
61
+
62
+ return { status: 200, body: { revoked: true } };
63
+ } catch (err) {
64
+ return { status: 500, body: { code: "IDENTITY_ACTION_FAILED", error: String(err) } };
65
+ }
66
+ }
67
+
68
+ function mapRevokeError(message: string, messages: IdentityMessages): { status: number; body: RemoveAdminUserResponse } {
69
+ if (message.includes("last_active_human_admin") || message.includes("last_admin_lockout")) {
70
+ return { status: 409, body: { code: "LAST_ACTIVE_HUMAN_ADMIN", error: messages.lastAdmin } };
71
+ }
72
+ if (message.includes("self_revoke_forbidden")) {
73
+ return { status: 403, body: { code: "SELF_REVOKE_FORBIDDEN", error: messages.selfRevoke } };
74
+ }
75
+ if (message.includes("machine_actor_revoke_forbidden")) {
76
+ return { status: 403, body: { code: "MACHINE_ACTOR_REVOKE_FORBIDDEN", error: messages.machineRevoke } };
77
+ }
78
+ if (message.includes("target_not_found") || message.includes("target_not_active")) {
79
+ return { status: 400, body: { code: "ADMIN_NOT_FOUND", error: messages.adminNotFound } };
80
+ }
81
+ if (message.includes("forbidden")) {
82
+ return { status: 403, body: { code: "ADMIN_REQUIRED", error: messages.adminRequired } };
83
+ }
84
+ return { status: 500, body: { code: "REVOKE_FAILED", error: messages.revokeFailed(message) } };
85
+ }
@@ -0,0 +1,3 @@
1
+ import { createBffResponder, type BffResponderOptions } from "../bff/index.js";
2
+ export type IdentityResponseOptions = BffResponderOptions;
3
+ export const createIdentityResponder = createBffResponder;
@@ -0,0 +1,10 @@
1
+ /** A projection of canonical migration units, never a second migration chain. */
2
+ export const IDENTITY_MIGRATIONS = [
3
+ { id: "20260810180001_self_host_actor_auth", catalogPath: "db/platform/migrations/20260810180001_self_host_actor_auth.sql", position: 11, sha256: "84c80d328785151f1fdb5fc4b69220469ffb9c8579054c473ead910a273dfe7b", url: new URL("../../sql/20260810180001_self_host_actor_auth.sql", import.meta.url) },
4
+ { id: "20260810204057_customer_identity_read", catalogPath: "db/platform/migrations/20260810204057_customer_identity_read.sql", position: 12, sha256: "1aa626c0187a4ed9d08f152540e0d17c68cd678d2caf3e896ac444b5a38aafe3", url: new URL("../../sql/20260810204057_customer_identity_read.sql", import.meta.url) },
5
+ { id: "20260814090000_customer_selfservice_recovery", catalogPath: "db/platform/migrations/20260814090000_customer_selfservice_recovery.sql", position: 29, sha256: "ca7f58994533727eb58da148d549ff986fe8f55bb2ffc9eaf61e7d239675ba2e", url: new URL("../../sql/20260814090000_customer_selfservice_recovery.sql", import.meta.url) },
6
+ { id: "20260901143000_admin_membership_authority_foundation", catalogPath: "db/platform/migrations/20260901143000_admin_membership_authority_foundation.sql", position: 60, sha256: "bb4703b669d17e5a0eb4d9a4e8889d12e5286d4b5a813226a77ef2b71df8281f", url: new URL("../../sql/20260901143000_admin_membership_authority_foundation.sql", import.meta.url) },
7
+ { id: "20261018120000_identity_operator_invitations", catalogPath: "db/platform/migrations/20261018120000_identity_operator_invitations.sql", position: 67, sha256: "b73034908bd5e27819bcd421d17037a0f00a24aa3df1be1f8f9ca411bf984349", url: new URL("../../sql/20261018120000_identity_operator_invitations.sql", import.meta.url) },
8
+ { id: "20261018180000_identity_operator_challenges", catalogPath: "db/platform/migrations/20261018180000_identity_operator_challenges.sql", position: 68, sha256: "191cf0c151a5882ad20d6043624da50afad799df739fff326d67560d3e00a031", url: new URL("../../sql/20261018180000_identity_operator_challenges.sql", import.meta.url) },
9
+ { id: "20261018190000_identity_privacy_erasures", catalogPath: "db/platform/migrations/20261018190000_identity_privacy_erasures.sql", position: 69, sha256: "f39b691cb14c0e9ec1a7460d77dc46e16bca4d2448838e4882f78885a6d92351", url: new URL("../../sql/20261018190000_identity_privacy_erasures.sql", import.meta.url) },
10
+ ] as const;
@@ -0,0 +1,25 @@
1
+ export type {
2
+ PlatformJobBackstopState,
3
+ PlatformJobClaim,
4
+ PlatformJobFinishStatus,
5
+ PlatformJobFinishSummary,
6
+ PlatformJobInvocation,
7
+ PlatformJobRunLedgerPort,
8
+ PlatformJobTriggerKind,
9
+ } from "./ports.js";
10
+ export type { PgQueryExecutor } from "../data/index.js";
11
+ export {
12
+ createPostgresPlatformJobRunLedger,
13
+ type PostgresPlatformJobRunLedgerOptions,
14
+ } from "./postgres.js";
15
+ export { JOB_RUN_LEDGER_MIGRATIONS, JOB_RUN_LEDGER_SQL_URL, JOB_RUN_LEDGER_V3_SQL_URL } from "./sql.js";
16
+ export {
17
+ createManagedJobRunCalls,
18
+ createSupabasePlatformJobRunLedger,
19
+ platformJobInvocationForDriver,
20
+ type ManagedJobRunOptions,
21
+ type PlatformJobClient,
22
+ type PlatformJobDriver,
23
+ type PlatformJobSupabaseClient,
24
+ type SupabasePlatformJobRunLedgerOptions,
25
+ } from "./supabase.js";
@@ -0,0 +1,27 @@
1
+ // Persistence-neutral scheduled-job lease and attempt ledger port.
2
+ //
3
+ // The claim and finish contract is core's `JobLeasePort`. A job-run ledger adds the two reads a
4
+ // scheduled job uses for its backstop and checkpoint. Invocation is deployment evidence supplied by
5
+ // the composition root; a job never sees a database client, SDK query, RPC name, vendor driver or
6
+ // environment configuration.
7
+
8
+ import type { JobLeasePort } from "@openlup/core/platform-runtime";
9
+
10
+ export type {
11
+ PlatformJobClaim,
12
+ PlatformJobFinishStatus,
13
+ PlatformJobFinishSummary,
14
+ PlatformJobInvocation,
15
+ PlatformJobTriggerKind,
16
+ } from "@openlup/core/platform-runtime";
17
+
18
+ export type PlatformJobBackstopState = {
19
+ lastStatus: string | null;
20
+ lastSuccessAt: string | null;
21
+ };
22
+
23
+ export interface PlatformJobRunLedgerPort extends JobLeasePort {
24
+ readJobBackstopState(jobName: string): Promise<PlatformJobBackstopState | null>;
25
+
26
+ readLatestTerminalRunMetadata(jobName: string): Promise<Record<string, unknown> | null>;
27
+ }
@@ -0,0 +1,121 @@
1
+ // Direct PostgreSQL adapter for the public job-run ledger (claim, finish, backstop, checkpoint).
2
+ //
3
+ // The RPCs and tables come from the package's job-run ledger SQL. Every claim and finish row
4
+ // carries the invocation and the deployment's release correlation in its metadata.
5
+
6
+ import type { PgQueryExecutor } from "../data/index.js";
7
+ import type { PlatformJobRunLedgerPort } from "./ports.js";
8
+
9
+ const CLAIM_SQL = `
10
+ SELECT acquired, run_id, reason
11
+ FROM public.platform_claim_job_run_v3($1, $2, $3, $4, $5::jsonb)`;
12
+
13
+ const FINISH_SQL = `
14
+ SELECT public.platform_finish_job_run_v3(
15
+ $1, $2::uuid, $3, $4, $5, $6, $7, $8::jsonb
16
+ ) AS released`;
17
+
18
+ const BACKSTOP_SQL = `
19
+ SELECT last_status, last_success_at
20
+ FROM public.platform_job_controls
21
+ WHERE job_name = $1`;
22
+
23
+ const LATEST_TERMINAL_METADATA_SQL = `
24
+ SELECT metadata
25
+ FROM public.platform_job_runs
26
+ WHERE job_name = $1
27
+ AND status IN ('success', 'failed')
28
+ AND finished_at IS NOT NULL
29
+ ORDER BY started_at DESC
30
+ LIMIT 1`;
31
+
32
+ export interface PostgresPlatformJobRunLedgerOptions {
33
+ /** Release and deployment identity merged into every claim and finish row's metadata. */
34
+ releaseCorrelation: () => Record<string, string | null>;
35
+ /** Failure reason recorded when a failed run's summary carries none. */
36
+ defaultFailureReason: string;
37
+ }
38
+
39
+ /** Direct PostgreSQL adapter for the public neutral-vocabulary job-control rail. */
40
+ export function createPostgresPlatformJobRunLedger(
41
+ executor: PgQueryExecutor,
42
+ options: PostgresPlatformJobRunLedgerOptions,
43
+ ): PlatformJobRunLedgerPort {
44
+ return {
45
+ async claimJobRun(jobName, invocation, leaseSeconds = 900) {
46
+ const invocationSource = requiredInvocationSource(invocation.invocationSource);
47
+ const result = await executor.query(CLAIM_SQL, [
48
+ jobName,
49
+ invocation.triggerKind,
50
+ invocationSource,
51
+ leaseSeconds,
52
+ {
53
+ triggerKind: invocation.triggerKind,
54
+ invocationSource,
55
+ ...options.releaseCorrelation(),
56
+ },
57
+ ]);
58
+ const row = plainRecord(result.rows[0]);
59
+ return {
60
+ acquired: row?.acquired === true,
61
+ runId: typeof row?.run_id === "string" ? row.run_id : null,
62
+ reason: typeof row?.reason === "string" ? row.reason : "unknown",
63
+ };
64
+ },
65
+
66
+ async finishJobRun(jobName, runId, invocation, status, summary, extraMetadata) {
67
+ const invocationSource = requiredInvocationSource(invocation.invocationSource);
68
+ const result = await executor.query(FINISH_SQL, [
69
+ jobName,
70
+ runId,
71
+ status,
72
+ summary.checked,
73
+ summary.updated,
74
+ status === "failed" ? summary.reason ?? options.defaultFailureReason : null,
75
+ null,
76
+ {
77
+ failures: summary.failures,
78
+ skipped: summary.skipped,
79
+ reason: summary.reason ?? null,
80
+ ...options.releaseCorrelation(),
81
+ ...(extraMetadata ?? {}),
82
+ triggerKind: invocation.triggerKind,
83
+ invocationSource,
84
+ driver: invocationSource,
85
+ },
86
+ ]);
87
+ return plainRecord(result.rows[0])?.released === true;
88
+ },
89
+
90
+ async readJobBackstopState(jobName) {
91
+ const result = await executor.query(BACKSTOP_SQL, [jobName]);
92
+ const row = plainRecord(result.rows[0]);
93
+ return row ? {
94
+ lastStatus: typeof row.last_status === "string" ? row.last_status : null,
95
+ lastSuccessAt: timestampString(row.last_success_at),
96
+ } : null;
97
+ },
98
+
99
+ async readLatestTerminalRunMetadata(jobName) {
100
+ const result = await executor.query(LATEST_TERMINAL_METADATA_SQL, [jobName]);
101
+ return plainRecord(plainRecord(result.rows[0])?.metadata);
102
+ },
103
+ };
104
+ }
105
+
106
+ function plainRecord(value: unknown): Record<string, unknown> | null {
107
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
108
+ return { ...(value as Record<string, unknown>) };
109
+ }
110
+
111
+ function timestampString(value: unknown): string | null {
112
+ if (typeof value === "string") return value;
113
+ if (value instanceof Date) return value.toISOString();
114
+ return null;
115
+ }
116
+
117
+ function requiredInvocationSource(value: string): string {
118
+ const source = value.trim();
119
+ if (!source) throw new Error("platform_job_invocation_source_required");
120
+ return source;
121
+ }
@@ -0,0 +1,12 @@
1
+ // The job-run ledger SQL a fresh installation applies through its own migration chain, in order.
2
+ // The first file's bytes and migration identity equal the reference application's platform
3
+ // migration of the same name; the second adds the v3 claim and finish entry points.
4
+ export const JOB_RUN_LEDGER_SQL_URL = new URL("../../sql/20260809150000_job_run_ledger.sql", import.meta.url);
5
+
6
+ export const JOB_RUN_LEDGER_V3_SQL_URL = new URL("../../sql/20260809150001_job_run_ledger_v3.sql", import.meta.url);
7
+
8
+ /** Every job-run ledger migration, ordered by migration identity. */
9
+ export const JOB_RUN_LEDGER_MIGRATIONS: ReadonlyArray<{ readonly id: string; readonly url: URL }> = [
10
+ { id: "20260809150000_job_run_ledger", url: JOB_RUN_LEDGER_SQL_URL },
11
+ { id: "20260809150001_job_run_ledger_v3", url: JOB_RUN_LEDGER_V3_SQL_URL },
12
+ ];