payment-kit 1.29.1 → 1.29.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 (310) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +36 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +27 -24
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/index.ts +22 -161
  12. package/api/src/integrations/app-store/client.ts +3 -4
  13. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  14. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  15. package/api/src/integrations/arcblock/token.ts +21 -7
  16. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  17. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  18. package/api/src/integrations/google-play/verify.ts +3 -2
  19. package/api/src/integrations/iap-reconcile.ts +3 -5
  20. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  21. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  22. package/api/src/libs/archive/query.ts +19 -0
  23. package/api/src/libs/audit.ts +61 -4
  24. package/api/src/libs/auth.ts +99 -38
  25. package/api/src/libs/context.ts +78 -1
  26. package/api/src/libs/currency.ts +2 -2
  27. package/api/src/libs/dayjs.ts +8 -2
  28. package/api/src/libs/drivers/auth-storage.ts +118 -0
  29. package/api/src/libs/drivers/cron.ts +264 -0
  30. package/api/src/libs/drivers/db.ts +170 -0
  31. package/api/src/libs/drivers/identity.ts +81 -0
  32. package/api/src/libs/drivers/index.ts +40 -0
  33. package/api/src/libs/drivers/locks.ts +226 -0
  34. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  35. package/api/src/libs/drivers/queue.ts +104 -0
  36. package/api/src/libs/drivers/secrets.ts +194 -0
  37. package/api/src/libs/env.ts +170 -54
  38. package/api/src/libs/exchange-rate/service.ts +7 -6
  39. package/api/src/libs/http-fetch-adapter.ts +50 -0
  40. package/api/src/libs/invoice.ts +1 -1
  41. package/api/src/libs/lock.ts +51 -47
  42. package/api/src/libs/logger.ts +48 -8
  43. package/api/src/libs/notification/index.ts +1 -1
  44. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  45. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  46. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  47. package/api/src/libs/overdraft-protection.ts +1 -1
  48. package/api/src/libs/payout.ts +1 -1
  49. package/api/src/libs/queue/index.ts +259 -52
  50. package/api/src/libs/queue/runtime.ts +175 -0
  51. package/api/src/libs/resource.ts +3 -3
  52. package/api/src/libs/secrets.ts +38 -0
  53. package/api/src/libs/session.ts +3 -2
  54. package/api/src/libs/subscription.ts +5 -5
  55. package/api/src/libs/tenant.ts +92 -0
  56. package/api/src/libs/url.ts +3 -3
  57. package/api/src/libs/util.ts +21 -13
  58. package/api/src/middlewares/hono/cdn.ts +63 -0
  59. package/api/src/middlewares/hono/context.ts +73 -0
  60. package/api/src/middlewares/hono/csrf.ts +72 -0
  61. package/api/src/middlewares/hono/fallback.ts +194 -0
  62. package/api/src/middlewares/hono/pipeline.ts +73 -0
  63. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  64. package/api/src/middlewares/hono/resource.ts +63 -0
  65. package/api/src/middlewares/hono/security.ts +214 -0
  66. package/api/src/middlewares/hono/session.ts +114 -0
  67. package/api/src/middlewares/hono/xss.ts +61 -0
  68. package/api/src/queues/auto-recharge.ts +12 -10
  69. package/api/src/queues/checkout-session.ts +17 -12
  70. package/api/src/queues/credit-consume.ts +40 -36
  71. package/api/src/queues/credit-grant.ts +25 -18
  72. package/api/src/queues/credit-reconciliation.ts +7 -5
  73. package/api/src/queues/discount-status.ts +9 -6
  74. package/api/src/queues/event.ts +12 -4
  75. package/api/src/queues/exchange-rate-health.ts +49 -30
  76. package/api/src/queues/invoice.ts +18 -15
  77. package/api/src/queues/notification.ts +14 -7
  78. package/api/src/queues/payment.ts +41 -28
  79. package/api/src/queues/payout.ts +9 -5
  80. package/api/src/queues/refund.ts +18 -12
  81. package/api/src/queues/subscription.ts +83 -53
  82. package/api/src/queues/token-transfer.ts +15 -10
  83. package/api/src/queues/usage-record.ts +8 -5
  84. package/api/src/queues/vendors/commission.ts +7 -5
  85. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  86. package/api/src/queues/vendors/fulfillment.ts +4 -2
  87. package/api/src/queues/vendors/return-processor.ts +5 -3
  88. package/api/src/queues/vendors/return-scanner.ts +5 -4
  89. package/api/src/queues/vendors/status-check.ts +10 -7
  90. package/api/src/queues/webhook.ts +60 -32
  91. package/api/src/routes/connect/shared.ts +1 -2
  92. package/api/src/routes/connect/subscribe.ts +3 -3
  93. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  94. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  95. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  96. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  97. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  98. package/api/src/routes/hono/credit-tokens.ts +43 -0
  99. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  100. package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
  101. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  102. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  103. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  104. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  105. package/api/src/routes/hono/exchange-rates.ts +77 -0
  106. package/api/src/routes/hono/index.ts +115 -0
  107. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  108. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  109. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  110. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  111. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  112. package/api/src/routes/hono/meters.ts +288 -0
  113. package/api/src/routes/hono/passports.ts +73 -0
  114. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  115. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  116. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  117. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  118. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  119. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  120. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  121. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  122. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  123. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  124. package/api/src/routes/hono/redirect.ts +24 -0
  125. package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
  126. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  127. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  128. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  129. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  130. package/api/src/routes/hono/tool.ts +69 -0
  131. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  132. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  133. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  134. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  135. package/api/src/service.ts +667 -0
  136. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  137. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  138. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  139. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  140. package/api/src/store/models/auto-recharge-config.ts +22 -10
  141. package/api/src/store/models/checkout-session.ts +15 -14
  142. package/api/src/store/models/coupon.ts +29 -20
  143. package/api/src/store/models/credit-grant.ts +38 -29
  144. package/api/src/store/models/credit-transaction.ts +32 -21
  145. package/api/src/store/models/customer.ts +19 -17
  146. package/api/src/store/models/discount.ts +11 -2
  147. package/api/src/store/models/entitlement-grant.ts +21 -9
  148. package/api/src/store/models/entitlement-product.ts +21 -9
  149. package/api/src/store/models/entitlement.ts +19 -10
  150. package/api/src/store/models/event.ts +18 -9
  151. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  152. package/api/src/store/models/invoice-item.ts +18 -9
  153. package/api/src/store/models/invoice.ts +16 -8
  154. package/api/src/store/models/meter-event.ts +27 -9
  155. package/api/src/store/models/meter.ts +31 -22
  156. package/api/src/store/models/payment-currency.ts +25 -8
  157. package/api/src/store/models/payment-intent.ts +15 -6
  158. package/api/src/store/models/payment-link.ts +15 -6
  159. package/api/src/store/models/payment-method.ts +38 -22
  160. package/api/src/store/models/payment-stat.ts +18 -9
  161. package/api/src/store/models/payout.ts +15 -6
  162. package/api/src/store/models/price-quote.ts +17 -8
  163. package/api/src/store/models/price.ts +24 -12
  164. package/api/src/store/models/pricing-table.ts +29 -20
  165. package/api/src/store/models/product-vendor.ts +20 -10
  166. package/api/src/store/models/product.ts +15 -6
  167. package/api/src/store/models/promotion-code.ts +14 -6
  168. package/api/src/store/models/refund.ts +15 -6
  169. package/api/src/store/models/revenue-snapshot.ts +21 -9
  170. package/api/src/store/models/setting.ts +18 -9
  171. package/api/src/store/models/setup-intent.ts +36 -27
  172. package/api/src/store/models/subscription-item.ts +21 -9
  173. package/api/src/store/models/subscription-schedule.ts +21 -9
  174. package/api/src/store/models/subscription.ts +21 -10
  175. package/api/src/store/models/tax-rate.ts +29 -21
  176. package/api/src/store/models/usage-record.ts +11 -2
  177. package/api/src/store/models/webhook-attempt.ts +18 -9
  178. package/api/src/store/models/webhook-endpoint.ts +18 -9
  179. package/api/src/store/scoped-core.ts +55 -0
  180. package/api/src/store/scoped.ts +247 -0
  181. package/api/src/store/sequelize.ts +66 -22
  182. package/api/src/store/sql-migrations.ts +20 -0
  183. package/api/src/store/tenant-backfill.ts +260 -0
  184. package/api/src/store/tenant-model.ts +124 -0
  185. package/api/src/store/tenant-tables.ts +50 -0
  186. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  187. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  188. package/api/tests/fixtures/core-env-violation.ts +10 -0
  189. package/api/tests/fixtures/host-read-violation.ts +19 -0
  190. package/api/tests/fixtures/tenants.ts +4 -0
  191. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  192. package/api/tests/libs/archive-query.spec.ts +26 -0
  193. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  194. package/api/tests/libs/context.spec.ts +204 -0
  195. package/api/tests/libs/core-config.spec.ts +115 -0
  196. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  197. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  198. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  199. package/api/tests/libs/scoped.spec.ts +222 -0
  200. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  201. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  202. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  203. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  204. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  205. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  206. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  207. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  208. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  209. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  210. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  211. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  212. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  213. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  214. package/api/tests/models/tenant-columns.spec.ts +161 -0
  215. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  216. package/api/tests/queues/credit-consume.spec.ts +8 -1
  217. package/api/tests/queues/event-tenant.spec.ts +236 -0
  218. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  219. package/api/tests/queues/queue-parity.spec.ts +249 -0
  220. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  221. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  222. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  223. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  224. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  225. package/api/tests/service/collapse.spec.ts +96 -0
  226. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  227. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  228. package/api/tests/store/tenant-model.spec.ts +162 -0
  229. package/api/tests/store/tenant-residual.spec.ts +196 -0
  230. package/api/third.d.ts +4 -0
  231. package/blocklet.yml +1 -1
  232. package/cloudflare/README.md +26 -6
  233. package/cloudflare/build.ts +28 -13
  234. package/cloudflare/did-connect-auth.ts +0 -217
  235. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  236. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  237. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  238. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  239. package/cloudflare/queue-runtime-mode.ts +13 -0
  240. package/cloudflare/run-build.js +10 -56
  241. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  242. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  243. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  244. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  245. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  246. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  247. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  248. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  249. package/cloudflare/shims/cron.ts +38 -158
  250. package/cloudflare/shims/events.ts +124 -0
  251. package/cloudflare/shims/fastq.ts +15 -1
  252. package/cloudflare/shims/nedb-storage.ts +16 -8
  253. package/cloudflare/shims/xss.ts +8 -0
  254. package/cloudflare/tenant-middleware.ts +36 -0
  255. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  256. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  257. package/cloudflare/worker.ts +204 -433
  258. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  259. package/jest.config.js +3 -1
  260. package/package.json +33 -38
  261. package/scripts/core-env-whitelist.json +1 -0
  262. package/scripts/e2e-12b-runtime.ts +149 -0
  263. package/scripts/e2e-core-config.ts +125 -0
  264. package/scripts/e2e-d1-tenancy.ts +116 -0
  265. package/scripts/e2e-d2-cron-queue.ts +139 -0
  266. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  267. package/scripts/e2e-hono-s2.ts +125 -0
  268. package/scripts/e2e-hono-s3e.ts +135 -0
  269. package/scripts/e2e-hono-s4.ts +114 -0
  270. package/scripts/e2e-migration-contract.ts +100 -0
  271. package/scripts/e2e-s0.ts +61 -0
  272. package/scripts/e2e-s1.ts +107 -0
  273. package/scripts/e2e-s2.ts +178 -0
  274. package/scripts/e2e-s3.ts +110 -0
  275. package/scripts/e2e-s4.ts +191 -0
  276. package/scripts/e2e-s5.ts +139 -0
  277. package/scripts/e2e-s6.ts +127 -0
  278. package/scripts/e2e-tenant-model.ts +119 -0
  279. package/scripts/e2e-tenant-worker.ts +199 -0
  280. package/scripts/gen-sql-migrations.js +46 -0
  281. package/scripts/phase8-codemod.js +219 -0
  282. package/scripts/phase9a-env-getters-codemod.js +82 -0
  283. package/scripts/scan-core-env.js +109 -0
  284. package/scripts/scan-tenant-queries.js +235 -0
  285. package/scripts/schema-drift-guard.ts +210 -0
  286. package/scripts/tenant-scan-whitelist.json +1 -0
  287. package/src/env.d.ts +13 -1
  288. package/tsconfig.json +1 -1
  289. package/api/src/libs/did-space.ts +0 -235
  290. package/api/src/libs/middleware.ts +0 -50
  291. package/api/src/libs/security.ts +0 -192
  292. package/api/src/queues/space.ts +0 -662
  293. package/api/src/routes/credit-tokens.ts +0 -38
  294. package/api/src/routes/exchange-rates.ts +0 -87
  295. package/api/src/routes/index.ts +0 -142
  296. package/api/src/routes/integrations/stripe.ts +0 -61
  297. package/api/src/routes/meters.ts +0 -274
  298. package/api/src/routes/passports.ts +0 -68
  299. package/api/src/routes/redirect.ts +0 -20
  300. package/api/src/routes/tool.ts +0 -65
  301. package/api/src/routes/webhook-endpoints.ts +0 -126
  302. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  303. package/cloudflare/shims/did-space-js.ts +0 -17
  304. package/cloudflare/shims/did-space.ts +0 -11
  305. package/cloudflare/shims/express-compat/index.ts +0 -80
  306. package/cloudflare/shims/express-compat/types.ts +0 -41
  307. package/cloudflare/shims/lock.ts +0 -115
  308. package/cloudflare/shims/queue.ts +0 -611
  309. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  310. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -0,0 +1,8 @@
1
+ // CF shim for @blocklet/sdk/lib/util/wallet.
2
+ //
3
+ // DEAD on the CF path: the only importer is the csrf middleware (node-shell only;
4
+ // see util-csrf.ts), which reads isDidWalletConnect to skip csrf for DID-wallet
5
+ // requests. Never executes on the worker — a resolving stub is enough.
6
+ export const isDidWalletConnect = (_userAgent?: string): boolean => false;
7
+
8
+ export default { isDidWalletConnect };
@@ -1,182 +1,62 @@
1
- // @abtnode/cron shim for CF Cron Triggers
1
+ // @abtnode/cron shim for CF Cron Triggers (Phase 9, W2-1b).
2
2
  //
3
- // The real @abtnode/cron exports { init } where init({ context, jobs, onError }) registers jobs.
4
- // In CF Workers, we store the jobs and execute them via the scheduled() handler.
5
- //
6
- // The shim parses 6-field cron expressions (sec min hour day month weekday)
7
- // and only runs jobs whose schedule matches the current 5-minute window.
8
-
9
- type CronJob = {
10
- name: string;
11
- time: string;
12
- fn: () => Promise<any> | any;
13
- options?: { runOnInit?: boolean };
14
- };
3
+ // The real @abtnode/cron exports { init } where init({ jobs, onError }) registers
4
+ // jobs. In CF Workers we store the jobs and execute due ones from scheduled().
5
+ // The cron-expression matcher + registry now live in the shared cron driver
6
+ // (api/src/libs/drivers/cron.ts) so embedded and worker agree on when a job is
7
+ // due; this file is the thin worker adapter that keeps the @abtnode/cron API.
8
+
9
+ import {
10
+ createCronRegistry,
11
+ setCronDriver,
12
+ getCronDriver,
13
+ matchesCron,
14
+ shouldRunInWindow,
15
+ } from '../../api/src/libs/drivers/cron';
16
+
17
+ // D2: crons/index.ts now registers through getCronDriver() instead of importing
18
+ // @abtnode/cron directly. On CF this shim is the active cron driver, so make the
19
+ // cf-cron registry the global driver at module load — BEFORE worker.ts calls
20
+ // crons.init(). That keeps crons.init()'s register() and this shim's runAll() on
21
+ // the SAME passive cf-cron registry (host drives runDue from scheduled(); no
22
+ // @abtnode/cron self-scheduling timer is ever created in the frozen isolate).
23
+ const registry = createCronRegistry('cf-cron');
24
+ setCronDriver(registry);
15
25
 
16
26
  type InitOptions = {
17
27
  context?: any;
18
- jobs: CronJob[];
28
+ jobs: Array<{ name: string; time: string; fn: () => Promise<any> | any; options?: { runOnInit?: boolean } }>;
19
29
  onError?: (error: Error, name: string) => void;
20
30
  };
21
31
 
22
- // Singleton storage for registered cron jobs
23
- const registeredJobs: CronJob[] = [];
24
- let onErrorHandler: ((error: Error, name: string) => void) | undefined;
25
-
26
- // --- Cron expression matcher ---
27
- // Supports 6-field format: second minute hour dayOfMonth month dayOfWeek
28
- // Supports: numbers, *, */N, ranges (1-5), lists (1,3,5)
29
-
30
- function parseField(field: string, min: number, max: number): number[] | null {
31
- // null means "match all"
32
- if (field === '*') return null;
33
-
34
- const values = new Set<number>();
35
-
36
- for (const part of field.split(',')) {
37
- // */N — every N
38
- const stepMatch = part.match(/^\*\/(\d+)$/);
39
- if (stepMatch) {
40
- const step = parseInt(stepMatch[1], 10);
41
- for (let i = min; i <= max; i += step) {
42
- values.add(i);
43
- }
44
- continue;
45
- }
46
-
47
- // N-M — range
48
- const rangeMatch = part.match(/^(\d+)-(\d+)$/);
49
- if (rangeMatch) {
50
- const from = parseInt(rangeMatch[1], 10);
51
- const to = parseInt(rangeMatch[2], 10);
52
- for (let i = from; i <= to; i++) {
53
- values.add(i);
54
- }
55
- continue;
56
- }
57
-
58
- // N — single value
59
- const num = parseInt(part, 10);
60
- if (!isNaN(num)) {
61
- values.add(num);
62
- }
63
- }
64
-
65
- return values.size > 0 ? Array.from(values) : null;
66
- }
67
-
68
- /**
69
- * Check if a date matches a 6-field cron expression.
70
- * Returns true if the date's minute/hour/day/month/weekday match.
71
- * Seconds field is ignored (CF triggers are minute-level).
72
- */
73
- function matchesCron(cronExpr: string, date: Date): boolean {
74
- const fields = cronExpr.trim().split(/\s+/);
75
- if (fields.length < 5) return true; // Can't parse — run it
76
-
77
- // 6-field: sec min hour dom month dow
78
- // 5-field: min hour dom month dow
79
- const offset = fields.length >= 6 ? 1 : 0;
80
-
81
- const minuteField = parseField(fields[offset], 0, 59);
82
- const hourField = parseField(fields[offset + 1], 0, 23);
83
- const domField = parseField(fields[offset + 2], 1, 31);
84
- const monthField = parseField(fields[offset + 3], 0, 11); // cron months are 1-12, JS is 0-11
85
- const dowField = parseField(fields[offset + 4], 0, 6);
86
-
87
- const m = date.getUTCMinutes();
88
- const h = date.getUTCHours();
89
- const dom = date.getUTCDate();
90
- const month = date.getUTCMonth() + 1; // JS 0-based → cron 1-based
91
- const dow = date.getUTCDay(); // 0=Sunday
92
-
93
- if (minuteField && !minuteField.includes(m)) return false;
94
- if (hourField && !hourField.includes(h)) return false;
95
- if (domField && !domField.includes(dom)) return false;
96
- if (monthField && !monthField.includes(month)) return false;
97
- if (dowField && !dowField.includes(dow)) return false;
98
-
99
- return true;
100
- }
101
-
102
- // Check if a cron expression should fire at the given date (minute-level match).
103
- //
104
- // History: this helper previously used a 5-minute look-ahead window, under the
105
- // assumption that CF Cron Triggers fire every 5 minutes. Once the deploy config
106
- // switched to every-minute cron, that window made every stepped expression fire
107
- // 5x more often than intended, driving CF Queues past the free-tier daily cap
108
- // (2026-04-17 incident). With CF Scheduled firing every minute, we only check
109
- // the current minute — each cron expression triggers at its designed frequency.
110
- // See docs/cf-queues-ops-alert-analysis.md § 改动 B.
111
- function shouldRunInWindow(cronExpr: string, date: Date): boolean {
112
- return matchesCron(cronExpr, date);
113
- }
114
-
115
- // --- Cron shim API ---
116
-
117
32
  function init(options: InitOptions) {
118
- registeredJobs.length = 0;
119
- onErrorHandler = options.onError;
120
-
121
- for (const job of options.jobs || []) {
122
- if (job.name && job.time && typeof job.fn === 'function') {
123
- registeredJobs.push(job);
124
- }
125
- }
126
-
127
- // Skip runOnInit jobs in CF Workers — they'll run on the next matching trigger
128
- // (running them at module init time would block the request and may exceed CPU limits)
129
-
33
+ registry.register(options.jobs || [], options.onError);
34
+ // runOnInit jobs are skipped in CF Workers — they run on the next matching
35
+ // trigger (running them at module init would block the request / risk CPU limits)
130
36
  return {
131
37
  addJob(name: string, time: string, fn: Function, opts?: any) {
132
- registeredJobs.push({ name, time, fn: fn as any, options: opts });
38
+ registry.addJob(name, time, fn as any, opts);
39
+ },
40
+ start() {
41
+ /* no-op — CF scheduled() drives runAll */
133
42
  },
134
- start() { /* no-op */ },
135
43
  };
136
44
  }
137
45
 
138
- // Called by worker.ts scheduled handler only runs jobs matching the trigger time.
139
- // Accepts an optional `now` so the caller can pass `event.scheduledTime` (the
140
- // intended trigger minute) instead of relying on wall-clock at execution time.
141
- // CF may deliver scheduled events with a small delay that crosses a minute
142
- // boundary; matching on `scheduledTime` keeps exact-minute cron reliable.
46
+ // Called by worker.ts scheduled handler. Accepts an optional `now` so the caller
47
+ // can pass `event.scheduledTime` (the intended trigger minute).
143
48
  async function runAll(now: Date = new Date()) {
144
- const matched: string[] = [];
145
- const skipped: string[] = [];
146
-
147
- for (const job of registeredJobs) {
148
- if (shouldRunInWindow(job.time, now)) {
149
- matched.push(job.name);
150
- try {
151
- await job.fn();
152
- } catch (err: any) {
153
- console.error(`[Cron] ${job.name} failed:`, err?.message || err);
154
- onErrorHandler?.(err, job.name);
155
- }
156
- } else {
157
- skipped.push(job.name);
158
- }
159
- }
160
-
161
- console.log(`[Cron] Ran ${matched.length} jobs: [${matched.join(', ')}]. Skipped ${skipped.length}.`);
49
+ const { ran, skipped } = await getCronDriver().runDue(now);
50
+ // eslint-disable-next-line no-console
51
+ console.log(`[Cron] Ran ${ran.length} jobs: [${ran.join(', ')}]. Skipped ${skipped.length}.`);
162
52
  }
163
53
 
164
54
  async function runJob(name: string) {
165
- const job = registeredJobs.find((j) => j.name === name);
166
- if (job) {
167
- try {
168
- await job.fn();
169
- } catch (err: any) {
170
- console.error(`[Cron] ${name} failed:`, err?.message || err);
171
- onErrorHandler?.(err, name);
172
- }
173
- } else {
174
- console.warn(`[Cron] Job ${name} not found`);
175
- }
55
+ await getCronDriver().runJob(name);
176
56
  }
177
57
 
178
58
  function getJobNames(): string[] {
179
- return registeredJobs.map((j) => `${j.name} (${j.time})`);
59
+ return getCronDriver().getJobNames();
180
60
  }
181
61
 
182
62
  // Export matching @abtnode/cron API: default export is { init }
@@ -0,0 +1,124 @@
1
+ // EventEmitter shim for CF Workers.
2
+ //
3
+ // workerd's nodejs_compat provides `node:events` for ESM `import from 'events'`,
4
+ // but the esbuild banner's globalThis.require polyfill (build.ts) does NOT list
5
+ // `events`, so CJS `const { EventEmitter } = require('events')` resolves to an
6
+ // empty object and `class X extends EventEmitter` throws at global init
7
+ // ("Class extends value undefined"). Two core drivers hit this:
8
+ // - api/src/libs/drivers/locks.ts (MemoryLock)
9
+ // - api/src/libs/drivers/auth-storage.ts (DbAuthStorage extends EventEmitter)
10
+ //
11
+ // Aliasing `events` to this shim (build.ts) makes BOTH the CJS-require and
12
+ // ESM-import forms resolve to one deterministic implementation, removing the
13
+ // fragile split between nodejs_compat (ESM) and the banner (CJS). Only the core
14
+ // queue/lock/auth-storage event buses use it — emit / on / once / removeListener
15
+ // are the methods exercised (verified by grep), so a compact but correct
16
+ // implementation suffices.
17
+
18
+ type Listener = (...args: any[]) => void;
19
+
20
+ export class EventEmitter {
21
+ private _events: Map<string | symbol, Listener[]> = new Map();
22
+
23
+ private _maxListeners = 10;
24
+
25
+ addListener(event: string | symbol, listener: Listener): this {
26
+ return this.on(event, listener);
27
+ }
28
+
29
+ on(event: string | symbol, listener: Listener): this {
30
+ const list = this._events.get(event);
31
+ if (list) {
32
+ list.push(listener);
33
+ } else {
34
+ this._events.set(event, [listener]);
35
+ }
36
+ return this;
37
+ }
38
+
39
+ prependListener(event: string | symbol, listener: Listener): this {
40
+ const list = this._events.get(event);
41
+ if (list) {
42
+ list.unshift(listener);
43
+ } else {
44
+ this._events.set(event, [listener]);
45
+ }
46
+ return this;
47
+ }
48
+
49
+ once(event: string | symbol, listener: Listener): this {
50
+ const wrapper = (...args: any[]) => {
51
+ this.removeListener(event, wrapper);
52
+ listener.apply(this, args);
53
+ };
54
+ // keep a handle to the original so removeListener(event, listener) still works
55
+ (wrapper as any).listener = listener;
56
+ return this.on(event, wrapper);
57
+ }
58
+
59
+ prependOnceListener(event: string | symbol, listener: Listener): this {
60
+ const wrapper = (...args: any[]) => {
61
+ this.removeListener(event, wrapper);
62
+ listener.apply(this, args);
63
+ };
64
+ (wrapper as any).listener = listener;
65
+ return this.prependListener(event, wrapper);
66
+ }
67
+
68
+ removeListener(event: string | symbol, listener: Listener): this {
69
+ const list = this._events.get(event);
70
+ if (!list) return this;
71
+ const idx = list.findIndex((l) => l === listener || (l as any).listener === listener);
72
+ if (idx >= 0) {
73
+ list.splice(idx, 1);
74
+ if (list.length === 0) this._events.delete(event);
75
+ }
76
+ return this;
77
+ }
78
+
79
+ off(event: string | symbol, listener: Listener): this {
80
+ return this.removeListener(event, listener);
81
+ }
82
+
83
+ removeAllListeners(event?: string | symbol): this {
84
+ if (event === undefined) {
85
+ this._events.clear();
86
+ } else {
87
+ this._events.delete(event);
88
+ }
89
+ return this;
90
+ }
91
+
92
+ emit(event: string | symbol, ...args: any[]): boolean {
93
+ const list = this._events.get(event);
94
+ if (!list || list.length === 0) return false;
95
+ // copy so once()-removals during iteration don't skip listeners
96
+ for (const listener of [...list]) {
97
+ listener.apply(this, args);
98
+ }
99
+ return true;
100
+ }
101
+
102
+ listeners(event: string | symbol): Listener[] {
103
+ return [...(this._events.get(event) || [])];
104
+ }
105
+
106
+ listenerCount(event: string | symbol): number {
107
+ return this._events.get(event)?.length || 0;
108
+ }
109
+
110
+ eventNames(): (string | symbol)[] {
111
+ return [...this._events.keys()];
112
+ }
113
+
114
+ setMaxListeners(n: number): this {
115
+ this._maxListeners = n;
116
+ return this;
117
+ }
118
+
119
+ getMaxListeners(): number {
120
+ return this._maxListeners;
121
+ }
122
+ }
123
+
124
+ export default EventEmitter;
@@ -12,7 +12,21 @@
12
12
 
13
13
  type Task = { data: any; cb?: Function };
14
14
 
15
- export default function fastq(_context: any, worker: Function, _concurrency: number) {
15
+ // Phase 9 (W2-1b): faithful fastq executor driver for the worker.
16
+ //
17
+ // Match the real fastq calling convention. The Node queue engine
18
+ // (api/src/libs/queue) calls `fastq(workerFn, concurrency)` (2-arg form);
19
+ // real fastq detects a function first arg and shifts (context => null). The
20
+ // previous shim assumed the 3-arg `fastq(context, worker, concurrency)` form,
21
+ // so the 2-arg call landed `worker` on `context` and every job execution threw
22
+ // "worker is not a function". This shift makes the shim a drop-in executor so
23
+ // the SAME engine runs identically on Node (real fastq) and the worker (shim).
24
+ export default function fastq(context: any, worker: Function, concurrency: number) {
25
+ if (typeof context === 'function') {
26
+ concurrency = worker as unknown as number;
27
+ worker = context;
28
+ context = null;
29
+ }
16
30
  const pending: Task[] = [];
17
31
  let running = false;
18
32
  let drainFn: (() => void) | null = null;
@@ -1,9 +1,17 @@
1
- // NeDB → D1 storage shim for DID Connect sessions
2
- export default class AuthStorage {
3
- constructor(_opts?: any) {}
4
- create(_id: string, _data: any) { return Promise.resolve(); }
5
- read(_id: string) { return Promise.resolve(null); }
6
- update(_id: string, _data: any) { return Promise.resolve(); }
7
- delete(_id: string) { return Promise.resolve(); }
8
- on(_event: string, _fn: Function) {}
1
+ // NeDB → D1 storage shim for DID Connect sessions (Phase 8, W2-1a).
2
+ //
3
+ // Was a no-op stub (DID Connect state silently dropped in the worker). Now a
4
+ // thin worker adapter: it builds a D1 db driver from the bound D1 database and
5
+ // delegates to the real, contract-tested DbAuthStorage. The `{ dbPath }` option
6
+ // is ignored — persistence is the D1 binding, not a disk file.
7
+
8
+ import { createD1DbDriver, DbAuthStorage } from '../../api/src/libs/drivers';
9
+
10
+ import { getDB } from './sequelize-d1/model';
11
+
12
+ export default class AuthStorage extends DbAuthStorage {
13
+ constructor(_opts?: any) {
14
+ // lazy getter — the D1 binding is set per request, not at module import
15
+ super(createD1DbDriver(() => getDB()));
16
+ }
9
17
  }
@@ -1,3 +1,11 @@
1
1
  export function xss(_opts?: any) {
2
2
  return (_req: any, _res: any, next: any) => next();
3
3
  }
4
+
5
+ // Phase 4 (express→hono): the hono xss middleware (LITE app-shell on CF) calls
6
+ // initSanitize(...)(body) to produce sanitizedBody. The old express-compat worker
7
+ // path set req.body WITHOUT xss sanitization, so an identity sanitizer preserves
8
+ // the worker's exact behavior (the node host uses the real @blocklet/xss).
9
+ export function initSanitize(_opts?: any) {
10
+ return <T>(body: T): T => body;
11
+ }
@@ -0,0 +1,36 @@
1
+ // Phase 7 (W1′): worker tenant-context wiring.
2
+ //
3
+ // The CF worker's `*` middleware only set up DB/env — it never established a
4
+ // tenant context, so every TenantModel query in the worker fell back to the
5
+ // single-mode default. This Hono middleware mirrors the Express
6
+ // `contextMiddleware`: it resolves the request's raw Host to a tenant via the
7
+ // SINGLE-POINT resolver (`resolveTenantForHost`) and wraps the downstream chain
8
+ // in `context.withTenant` so the resolved tenant flows through AsyncLocalStorage
9
+ // into every handler/query.
10
+ //
11
+ // single mode: resolves to the deployment app DID (standalone worker unchanged).
12
+ // multi mode : unknown/missing Host -> 400 fail-closed, no default-tenant
13
+ // fallback, and the handler never runs.
14
+
15
+ import type { Context, Next } from 'hono';
16
+
17
+ import { context } from '../api/src/libs/context';
18
+ import { TenantError, TENANT_HOST_UNRESOLVED } from '../api/src/libs/tenant';
19
+ import { resolveTenantForHost } from '../api/src/libs/drivers/identity';
20
+
21
+ export function tenantMiddleware() {
22
+ return async (c: Context, next: Next) => {
23
+ let instanceDid: string;
24
+ try {
25
+ // raw Host header only — never a proxy header (X-Forwarded-Host); the CF
26
+ // route is responsible for ensuring it cannot be forged by the client.
27
+ instanceDid = await resolveTenantForHost(c.req.header('host'));
28
+ } catch (err) {
29
+ if (err instanceof TenantError && err.code === TENANT_HOST_UNRESOLVED) {
30
+ return c.json({ error: { code: err.code, message: err.message } }, 400);
31
+ }
32
+ throw err;
33
+ }
34
+ return context.withTenant(instanceDid, () => next());
35
+ };
36
+ }
@@ -0,0 +1,160 @@
1
+ // Phase 7 (W1′): worker `withTenant` wiring. The CF worker had no tenant
2
+ // context — its `*` middleware only `setDB`. This drives the real Hono tenant
3
+ // middleware (the same factory mounted in worker.ts) over a tiny Hono app so
4
+ // the actual Host -> instanceDid resolution, multi-mode fail-closed 4xx, and
5
+ // `context.withTenant` ALS wrapping are exercised end to end.
6
+
7
+ import { Hono } from 'hono';
8
+
9
+ import { tenantMiddleware } from '../tenant-middleware';
10
+ import { getInstanceDid, context } from '../../api/src/libs/context';
11
+ import { getDefaultInstanceDid } from '../../api/src/libs/tenant';
12
+ import { setIdentityDriver, createDefaultIdentityDriver, type IdentityDriver } from '../../api/src/libs/drivers/identity';
13
+
14
+ const TENANT_A = 'did:abt:zHOSTA';
15
+ const TENANT_B = 'did:abt:zHOSTB';
16
+
17
+ // Build a Hono app wired exactly like worker.ts: tenant middleware on /api/*,
18
+ // then a handler that reports the tenant resolved from the ALS context.
19
+ function buildApp() {
20
+ const app = new Hono();
21
+ app.use('/api/*', tenantMiddleware());
22
+ app.get('/api/whoami', (c) => c.json({ tenant: getInstanceDid() }));
23
+ // /health is OUTSIDE /api/* — must never require a tenant (infra health check)
24
+ app.get('/health', (c) => c.json({ status: 'ok' }));
25
+ return app;
26
+ }
27
+
28
+ async function get(app: Hono, path: string, headers: Record<string, string> = {}) {
29
+ const res = await app.request(path, { headers });
30
+ let body: any;
31
+ try {
32
+ body = await res.json();
33
+ } catch {
34
+ body = await res.text();
35
+ }
36
+ return { status: res.status, body };
37
+ }
38
+
39
+ const ORIGINAL_MODE = process.env.PAYMENT_TENANT_MODE;
40
+
41
+ afterEach(() => {
42
+ if (ORIGINAL_MODE === undefined) delete process.env.PAYMENT_TENANT_MODE;
43
+ else process.env.PAYMENT_TENANT_MODE = ORIGINAL_MODE;
44
+ setIdentityDriver(createDefaultIdentityDriver());
45
+ });
46
+
47
+ describe('single mode (standalone worker legacy) — unchanged', () => {
48
+ beforeEach(() => {
49
+ process.env.PAYMENT_TENANT_MODE = 'single';
50
+ });
51
+
52
+ it('any host resolves to the deployment app DID', async () => {
53
+ const expected = getDefaultInstanceDid();
54
+ const app = buildApp();
55
+ const r1 = await get(app, '/api/whoami', { Host: 'anything.example.com' });
56
+ expect(r1.status).toBe(200);
57
+ expect(r1.body.tenant).toBe(expected);
58
+ const r2 = await get(app, '/api/whoami', { Host: 'other.example.com' });
59
+ expect(r2.body.tenant).toBe(expected); // default regardless of host
60
+ });
61
+
62
+ it('a request with no Host still resolves (single-mode default, no crash)', async () => {
63
+ const expected = getDefaultInstanceDid();
64
+ const app = buildApp();
65
+ const r = await get(app, '/api/whoami');
66
+ expect(r.status).toBe(200);
67
+ expect(r.body.tenant).toBe(expected);
68
+ });
69
+ });
70
+
71
+ describe('multi mode — Host maps to tenant, fail-closed on unknown', () => {
72
+ const identity: IdentityDriver = {
73
+ resolveInstanceDidForHost(host) {
74
+ if (host === 'a.example.com') return TENANT_A;
75
+ if (host === 'b.example.com') return TENANT_B;
76
+ return null;
77
+ },
78
+ };
79
+
80
+ beforeEach(() => {
81
+ process.env.PAYMENT_TENANT_MODE = 'multi';
82
+ setIdentityDriver(identity);
83
+ });
84
+
85
+ // Happy path — two hosts see two tenants (data isolation)
86
+ it('two hosts resolve to two tenants', async () => {
87
+ const app = buildApp();
88
+ const a = await get(app, '/api/whoami', { Host: 'a.example.com' });
89
+ const b = await get(app, '/api/whoami', { Host: 'b.example.com' });
90
+ expect(a.body.tenant).toBe(TENANT_A);
91
+ expect(b.body.tenant).toBe(TENANT_B);
92
+ expect(a.body.tenant).not.toBe(b.body.tenant);
93
+ });
94
+
95
+ // Bad input — missing Host fails closed (no default-tenant fallback)
96
+ it('missing Host fails closed with 4xx (no APP_PID fallback)', async () => {
97
+ const app = buildApp();
98
+ const r = await get(app, '/api/whoami');
99
+ expect(r.status).toBe(400);
100
+ expect(r.body.error.code).toBe('TENANT_HOST_UNRESOLVED');
101
+ expect(r.body.tenant).toBeUndefined();
102
+ });
103
+
104
+ // Data leak — unknown (unregistered) host must never fall into a tenant
105
+ it('unknown host fails closed with 4xx + error code', async () => {
106
+ const app = buildApp();
107
+ const r = await get(app, '/api/whoami', { Host: 'unknown.example.com' });
108
+ expect(r.status).toBe(400);
109
+ expect(r.body.error.code).toBe('TENANT_HOST_UNRESOLVED');
110
+ expect(r.body.tenant).toBeUndefined();
111
+ });
112
+
113
+ // Security — a forged X-Forwarded-Host must not change resolution (raw Host only)
114
+ it('SECURITY: X-Forwarded-Host cannot override the raw Host', async () => {
115
+ const app = buildApp();
116
+ const r = await get(app, '/api/whoami', { Host: 'a.example.com', 'X-Forwarded-Host': 'b.example.com' });
117
+ expect(r.body.tenant).toBe(TENANT_A);
118
+ });
119
+
120
+ it('SECURITY: X-Forwarded-Host pointing at a known tenant cannot rescue an unknown raw Host', async () => {
121
+ const app = buildApp();
122
+ const r = await get(app, '/api/whoami', { Host: 'unknown.example.com', 'X-Forwarded-Host': 'a.example.com' });
123
+ expect(r.status).toBe(400);
124
+ });
125
+
126
+ // Data loss — a 4xx-rejected request must never reach the route handler
127
+ it('a 4xx-rejected request never runs the handler (no write)', async () => {
128
+ const app = new Hono();
129
+ app.use('/api/*', tenantMiddleware());
130
+ let handlerRuns = 0;
131
+ app.get('/api/whoami', (c) => {
132
+ handlerRuns += 1;
133
+ return c.json({ tenant: getInstanceDid() });
134
+ });
135
+ const r = await get(app, '/api/whoami', { Host: 'unknown.example.com' });
136
+ expect(r.status).toBe(400);
137
+ expect(handlerRuns).toBe(0);
138
+ });
139
+
140
+ // ALS propagation — the handler runs INSIDE the withTenant context
141
+ it('the handler observes the resolved tenant via context ALS', async () => {
142
+ const app = new Hono();
143
+ app.use('/api/*', tenantMiddleware());
144
+ let seen: string | undefined;
145
+ app.get('/api/whoami', (c) => {
146
+ seen = context.getInstanceDid();
147
+ return c.json({ ok: true });
148
+ });
149
+ await get(app, '/api/whoami', { Host: 'b.example.com' });
150
+ expect(seen).toBe(TENANT_B);
151
+ });
152
+
153
+ // /health is outside /api/* — tenant middleware must not gate it
154
+ it('/health is reachable without a Host even in multi mode', async () => {
155
+ const app = buildApp();
156
+ const r = await get(app, '/health');
157
+ expect(r.status).toBe(200);
158
+ expect(r.body.status).toBe('ok');
159
+ });
160
+ });
@@ -0,0 +1,44 @@
1
+ // Phase 12c HARD GATE (scan): the CF worker is a host adapter. It must mount the
2
+ // embedded service's resource-route surface (svc.http.resourceRoutes) and NEVER
3
+ // access svc.handler — whose lazy getter builds the node-only Express app shell
4
+ // that does not belong under workerd. It also must not import the deleted legacy
5
+ // shims/queue.ts duplicate engine. This statically scans the worker source so a
6
+ // regression fails the suite (the runtime Proxy in ensurePaymentService is the
7
+ // second line of defense).
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+
11
+ const workerSrc = fs.readFileSync(path.join(__dirname, '../worker.ts'), 'utf8');
12
+
13
+ describe('Phase 12c — worker host-adapter hard gate', () => {
14
+ it('mounts the resource-route surface (svc.http.resourceRoutes)', () => {
15
+ expect(workerSrc).toMatch(/service\.http\.resourceRoutes/);
16
+ });
17
+
18
+ it('never reads .handler on the payment service', () => {
19
+ const offending = workerSrc
20
+ .split('\n')
21
+ .map((line, n) => ({ line: line.trim(), n: n + 1 }))
22
+ // ignore comment lines — only actual code may not read svc.handler
23
+ .filter(({ line }) => !line.startsWith('//') && !line.startsWith('*') && !line.startsWith('/*'))
24
+ .filter(({ line }) => /\b(service|paymentService|svc)\.handler\b/.test(line))
25
+ // the hard-gate trap string + the Proxy guard line are the gate itself
26
+ .filter(({ line }) => !/forbidden in the CF worker/.test(line) && !/prop === 'handler'/.test(line));
27
+ expect(offending).toEqual([]);
28
+ });
29
+
30
+ it('installs the runtime hard-gate Proxy that throws on handler access', () => {
31
+ expect(workerSrc).toMatch(/new Proxy\(svc,/);
32
+ expect(workerSrc).toMatch(/svc\.handler is forbidden in the CF worker/);
33
+ });
34
+
35
+ it('does not import the deleted legacy shims/queue engine', () => {
36
+ expect(workerSrc).not.toMatch(/from '\.\/shims\/queue'/);
37
+ });
38
+
39
+ it('drives the core queue runtime surface instead', () => {
40
+ expect(workerSrc).toMatch(/from '\.\.\/api\/src\/libs\/queue\/runtime'/);
41
+ expect(workerSrc).toMatch(/dispatchDueJobs\(\)/);
42
+ expect(workerSrc).toMatch(/flushQueueWork\(\)/);
43
+ });
44
+ });