@omega.js/backend 0.1.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 (993) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +1053 -0
  3. package/bin/omega +2 -0
  4. package/bin/omega-backend +2 -0
  5. package/bin/omg +2 -0
  6. package/cli.js +3 -0
  7. package/dist/cli/command-table.js +268 -0
  8. package/dist/cli/commands/auth.js +259 -0
  9. package/dist/cli/commands/base-command.js +451 -0
  10. package/dist/cli/commands/build.js +27 -0
  11. package/dist/cli/commands/clean.js +13 -0
  12. package/dist/cli/commands/clear.js +11 -0
  13. package/dist/cli/commands/cwd.js +9 -0
  14. package/dist/cli/commands/deploy.js +205 -0
  15. package/dist/cli/commands/emulator-orphans.js +440 -0
  16. package/dist/cli/commands/emulator.js +1829 -0
  17. package/dist/cli/commands/firebase-init.js +101 -0
  18. package/dist/cli/commands/firestore.js +261 -0
  19. package/dist/cli/commands/indexes.js +51 -0
  20. package/dist/cli/commands/install.js +151 -0
  21. package/dist/cli/commands/logs.js +358 -0
  22. package/dist/cli/commands/mcp.js +41 -0
  23. package/dist/cli/commands/migrate-markers.js +176 -0
  24. package/dist/cli/commands/migrate-rules.js +90 -0
  25. package/dist/cli/commands/migrate.js +59 -0
  26. package/dist/cli/commands/serve.js +208 -0
  27. package/dist/cli/commands/setup-tests/base-test.js +174 -0
  28. package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
  29. package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
  30. package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
  31. package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
  32. package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
  33. package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
  34. package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
  35. package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
  36. package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
  37. package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
  38. package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
  39. package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
  40. package/dist/cli/commands/setup-tests/functions-package.js +44 -0
  41. package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
  42. package/dist/cli/commands/setup-tests/gitignore.js +122 -0
  43. package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
  44. package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
  45. package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
  46. package/dist/cli/commands/setup-tests/helpers.js +72 -0
  47. package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
  48. package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
  49. package/dist/cli/commands/setup-tests/index.js +111 -0
  50. package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
  51. package/dist/cli/commands/setup-tests/java-installed.js +27 -0
  52. package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
  53. package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
  54. package/dist/cli/commands/setup-tests/node-version.js +60 -0
  55. package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
  56. package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
  57. package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
  58. package/dist/cli/commands/setup-tests/omega-config.js +84 -0
  59. package/dist/cli/commands/setup-tests/project-directories.js +34 -0
  60. package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
  61. package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
  62. package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
  63. package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
  64. package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
  65. package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
  66. package/dist/cli/commands/setup-tests/service-account.js +86 -0
  67. package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
  68. package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
  69. package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
  70. package/dist/cli/commands/stripe.js +14 -0
  71. package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
  72. package/dist/cli/commands/test.js +976 -0
  73. package/dist/cli/commands/update.js +27 -0
  74. package/dist/cli/commands/version.js +10 -0
  75. package/dist/cli/commands/watch.js +196 -0
  76. package/dist/cli/flags.js +13 -0
  77. package/dist/cli/index.js +173 -0
  78. package/dist/cli/run.js +33 -0
  79. package/dist/cli/utils/attach-log-file.js +4 -0
  80. package/dist/cli/utils/compile-rules.js +1155 -0
  81. package/dist/cli/utils/ensure-target.js +314 -0
  82. package/dist/cli/utils/project-type.js +140 -0
  83. package/dist/cli/utils/public-files.js +49 -0
  84. package/dist/cli/utils/safe-install.js +4 -0
  85. package/dist/cli/utils/spawn-shell.js +94 -0
  86. package/dist/cli/utils/stage-functions.js +338 -0
  87. package/dist/cli/utils/stage-local-packages.js +210 -0
  88. package/dist/cli/utils/target-checks.js +161 -0
  89. package/dist/cli/utils/target.js +24 -0
  90. package/dist/cli/utils/ui.js +292 -0
  91. package/dist/defaults/AGENTS.md +125 -0
  92. package/dist/defaults/CHANGELOG.md +15 -0
  93. package/dist/defaults/CLAUDE.md +1 -0
  94. package/dist/defaults/_.gitignore +65 -0
  95. package/dist/defaults/docs/README.md +17 -0
  96. package/dist/defaults/test/README.md +65 -0
  97. package/dist/defaults/test/_init.js +14 -0
  98. package/dist/defaults/test/helpers/connect-trap.js +72 -0
  99. package/dist/defaults/test/unit/registration.test.js +143 -0
  100. package/dist/defaults/test/unit/rules-posture.test.js +101 -0
  101. package/dist/defaults/test/unit/socket-free.test.js +43 -0
  102. package/dist/manager/events/auth/before-create.js +89 -0
  103. package/dist/manager/events/auth/before-signin.js +76 -0
  104. package/dist/manager/events/auth/on-create.js +112 -0
  105. package/dist/manager/events/auth/on-delete.js +141 -0
  106. package/dist/manager/events/auth/utils.js +93 -0
  107. package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
  108. package/dist/manager/events/cron/daily/data-requests.js +57 -0
  109. package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
  110. package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
  111. package/dist/manager/events/cron/daily/reset-usage.js +188 -0
  112. package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
  113. package/dist/manager/events/cron/daily.js +3 -0
  114. package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
  115. package/dist/manager/events/cron/frequent/email-queue.js +71 -0
  116. package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
  117. package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
  118. package/dist/manager/events/cron/frequent.js +3 -0
  119. package/dist/manager/events/cron/runner.js +64 -0
  120. package/dist/manager/events/firestore/notifications/on-write.js +123 -0
  121. package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
  122. package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
  123. package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
  124. package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
  125. package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
  126. package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
  127. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
  128. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
  129. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
  130. package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
  131. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
  132. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
  133. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
  134. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
  135. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
  136. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
  137. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
  138. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
  139. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
  140. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
  141. package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
  142. package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
  143. package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
  144. package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
  145. package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
  146. package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
  147. package/dist/manager/functions/_legacy/admin/query.js +198 -0
  148. package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
  149. package/dist/manager/functions/_legacy/template.js +33 -0
  150. package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
  151. package/dist/manager/functions/_legacy/test/webhook.js +37 -0
  152. package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
  153. package/dist/manager/helpers/analytics.js +498 -0
  154. package/dist/manager/helpers/api-manager.js +313 -0
  155. package/dist/manager/helpers/backend-router.js +37 -0
  156. package/dist/manager/helpers/context/authenticate.js +215 -0
  157. package/dist/manager/helpers/context/client-info.js +91 -0
  158. package/dist/manager/helpers/context/index.js +228 -0
  159. package/dist/manager/helpers/context/logging.js +150 -0
  160. package/dist/manager/helpers/context/parse.js +160 -0
  161. package/dist/manager/helpers/context/respond.js +247 -0
  162. package/dist/manager/helpers/event-middleware.js +109 -0
  163. package/dist/manager/helpers/metadata.js +32 -0
  164. package/dist/manager/helpers/middleware.js +541 -0
  165. package/dist/manager/helpers/redact-secret.js +25 -0
  166. package/dist/manager/helpers/resolved-config.js +34 -0
  167. package/dist/manager/helpers/roles.js +69 -0
  168. package/dist/manager/helpers/safe-compare.js +31 -0
  169. package/dist/manager/helpers/schema-engine.js +314 -0
  170. package/dist/manager/helpers/schema-zod.js +203 -0
  171. package/dist/manager/helpers/settings.js +215 -0
  172. package/dist/manager/helpers/usage.js +541 -0
  173. package/dist/manager/helpers/user.js +57 -0
  174. package/dist/manager/helpers/utilities.js +565 -0
  175. package/dist/manager/index.js +1404 -0
  176. package/dist/manager/libraries/abandoned-cart-config.js +12 -0
  177. package/dist/manager/libraries/ai/index.js +321 -0
  178. package/dist/manager/libraries/ai/prompt.js +85 -0
  179. package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
  180. package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
  181. package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
  182. package/dist/manager/libraries/ai/providers/openai.js +1272 -0
  183. package/dist/manager/libraries/ai/providers/test.js +257 -0
  184. package/dist/manager/libraries/ai/tokens.js +54 -0
  185. package/dist/manager/libraries/analytics/conversions.js +478 -0
  186. package/dist/manager/libraries/analytics/match-data.js +573 -0
  187. package/dist/manager/libraries/analytics/signup.js +120 -0
  188. package/dist/manager/libraries/auth-user.js +46 -0
  189. package/dist/manager/libraries/content/feed-parser.js +198 -0
  190. package/dist/manager/libraries/content/ghostii.js +191 -0
  191. package/dist/manager/libraries/content/source-resolver.js +629 -0
  192. package/dist/manager/libraries/email/constants.js +532 -0
  193. package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
  194. package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
  195. package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
  196. package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
  197. package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
  198. package/dist/manager/libraries/email/data/typo-domains.js +83 -0
  199. package/dist/manager/libraries/email/disposable-domains.js +92 -0
  200. package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
  201. package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
  202. package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
  203. package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
  204. package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
  205. package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
  206. package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
  207. package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
  208. package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
  209. package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
  210. package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
  211. package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
  212. package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
  213. package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
  214. package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
  215. package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
  216. package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
  217. package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
  218. package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
  219. package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
  220. package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
  221. package/dist/manager/libraries/email/generators/newsletter.js +899 -0
  222. package/dist/manager/libraries/email/index.js +146 -0
  223. package/dist/manager/libraries/email/marketing/index.js +754 -0
  224. package/dist/manager/libraries/email/prepare.js +365 -0
  225. package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
  226. package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
  227. package/dist/manager/libraries/email/transactional/index.js +510 -0
  228. package/dist/manager/libraries/email/utm.js +86 -0
  229. package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
  230. package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
  231. package/dist/manager/libraries/email/validation.js +255 -0
  232. package/dist/manager/libraries/env.js +198 -0
  233. package/dist/manager/libraries/infer-contact.js +101 -0
  234. package/dist/manager/libraries/load-provider.js +65 -0
  235. package/dist/manager/libraries/notification.js +256 -0
  236. package/dist/manager/libraries/openai.js +13 -0
  237. package/dist/manager/libraries/payment/discount-codes.js +204 -0
  238. package/dist/manager/libraries/payment/fetch-failure.js +56 -0
  239. package/dist/manager/libraries/payment/license.js +44 -0
  240. package/dist/manager/libraries/payment/order-id.js +57 -0
  241. package/dist/manager/libraries/payment/provider-errors.js +71 -0
  242. package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
  243. package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
  244. package/dist/manager/libraries/payment/providers/paypal.js +987 -0
  245. package/dist/manager/libraries/payment/providers/stripe.js +755 -0
  246. package/dist/manager/libraries/payment/providers/test.js +250 -0
  247. package/dist/manager/libraries/payment/refund-linkage.js +78 -0
  248. package/dist/manager/libraries/payment/refund-policy.js +113 -0
  249. package/dist/manager/libraries/payment/winback.js +55 -0
  250. package/dist/manager/libraries/prompts/infer-contact.md +78 -0
  251. package/dist/manager/libraries/rate-limits.js +17 -0
  252. package/dist/manager/libraries/recaptcha.js +58 -0
  253. package/dist/manager/libraries/user-doc.js +234 -0
  254. package/dist/manager/routes/admin/backup/post.js +106 -0
  255. package/dist/manager/routes/admin/cron/post.js +37 -0
  256. package/dist/manager/routes/admin/database/get.js +35 -0
  257. package/dist/manager/routes/admin/database/post.js +34 -0
  258. package/dist/manager/routes/admin/email/post.js +74 -0
  259. package/dist/manager/routes/admin/firestore/get.js +35 -0
  260. package/dist/manager/routes/admin/firestore/post.js +56 -0
  261. package/dist/manager/routes/admin/firestore/query/post.js +128 -0
  262. package/dist/manager/routes/admin/hook/post.js +104 -0
  263. package/dist/manager/routes/admin/infer-contact/post.js +35 -0
  264. package/dist/manager/routes/admin/notification/post.js +37 -0
  265. package/dist/manager/routes/admin/payment/post.js +56 -0
  266. package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
  267. package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
  268. package/dist/manager/routes/admin/post/post.js +487 -0
  269. package/dist/manager/routes/admin/post/put.js +146 -0
  270. package/dist/manager/routes/admin/post/templates/post.html +14 -0
  271. package/dist/manager/routes/admin/repo/content/post.js +106 -0
  272. package/dist/manager/routes/admin/stats/get.js +222 -0
  273. package/dist/manager/routes/admin/users/disable/post.js +50 -0
  274. package/dist/manager/routes/admin/users/list/get.js +110 -0
  275. package/dist/manager/routes/admin/users/sync/post.js +119 -0
  276. package/dist/manager/routes/brand/get.js +26 -0
  277. package/dist/manager/routes/content/post/get.js +110 -0
  278. package/dist/manager/routes/general/email/post.js +89 -0
  279. package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
  280. package/dist/manager/routes/general/uuid/post.js +31 -0
  281. package/dist/manager/routes/handler/post/post.js +142 -0
  282. package/dist/manager/routes/health/get.js +43 -0
  283. package/dist/manager/routes/index.js +11 -0
  284. package/dist/manager/routes/marketing/campaign/delete.js +45 -0
  285. package/dist/manager/routes/marketing/campaign/get.js +69 -0
  286. package/dist/manager/routes/marketing/campaign/post.js +124 -0
  287. package/dist/manager/routes/marketing/campaign/put.js +86 -0
  288. package/dist/manager/routes/marketing/campaign/utils.js +59 -0
  289. package/dist/manager/routes/marketing/contact/delete.js +100 -0
  290. package/dist/manager/routes/marketing/contact/post.js +149 -0
  291. package/dist/manager/routes/marketing/contact/put.js +37 -0
  292. package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
  293. package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
  294. package/dist/manager/routes/marketing/webhook/post.js +123 -0
  295. package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
  296. package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
  297. package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
  298. package/dist/manager/routes/payments/cancel/post.js +149 -0
  299. package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
  300. package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
  301. package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
  302. package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
  303. package/dist/manager/routes/payments/discount/get.js +25 -0
  304. package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
  305. package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
  306. package/dist/manager/routes/payments/intent/post.js +341 -0
  307. package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
  308. package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
  309. package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
  310. package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
  311. package/dist/manager/routes/payments/intent/providers/test.js +341 -0
  312. package/dist/manager/routes/payments/plan/post.js +147 -0
  313. package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
  314. package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
  315. package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
  316. package/dist/manager/routes/payments/plan/providers/test.js +113 -0
  317. package/dist/manager/routes/payments/portal/post.js +113 -0
  318. package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
  319. package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
  320. package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
  321. package/dist/manager/routes/payments/portal/providers/test.js +18 -0
  322. package/dist/manager/routes/payments/refund/post.js +207 -0
  323. package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
  324. package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
  325. package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
  326. package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
  327. package/dist/manager/routes/payments/refund/providers/test.js +204 -0
  328. package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
  329. package/dist/manager/routes/payments/uncancel/post.js +106 -0
  330. package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
  331. package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
  332. package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
  333. package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
  334. package/dist/manager/routes/payments/webhook/post.js +157 -0
  335. package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
  336. package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
  337. package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
  338. package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
  339. package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
  340. package/dist/manager/routes/payments/winback/post.js +276 -0
  341. package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
  342. package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
  343. package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
  344. package/dist/manager/routes/payments/winback/providers/test.js +42 -0
  345. package/dist/manager/routes/restart/index.js +37 -0
  346. package/dist/manager/routes/special/electron-client/post.js +59 -0
  347. package/dist/manager/routes/test/authenticate/get.js +11 -0
  348. package/dist/manager/routes/test/health/get.js +8 -0
  349. package/dist/manager/routes/test/index.js +16 -0
  350. package/dist/manager/routes/test/lab/post.js +17 -0
  351. package/dist/manager/routes/test/redirect/get.js +27 -0
  352. package/dist/manager/routes/test/reset-account/post.js +79 -0
  353. package/dist/manager/routes/test/roster/get.js +40 -0
  354. package/dist/manager/routes/test/schema/post.js +20 -0
  355. package/dist/manager/routes/test/usage/post.js +44 -0
  356. package/dist/manager/routes/test/webhook/post.js +21 -0
  357. package/dist/manager/routes/user/api-keys/post.js +51 -0
  358. package/dist/manager/routes/user/connections/_context.js +194 -0
  359. package/dist/manager/routes/user/connections/_grant.js +247 -0
  360. package/dist/manager/routes/user/connections/_lease.js +184 -0
  361. package/dist/manager/routes/user/connections/_providers.js +191 -0
  362. package/dist/manager/routes/user/connections/_state.js +117 -0
  363. package/dist/manager/routes/user/connections/delete.js +55 -0
  364. package/dist/manager/routes/user/connections/get.js +167 -0
  365. package/dist/manager/routes/user/connections/post.js +370 -0
  366. package/dist/manager/routes/user/connections/providers/discord.js +41 -0
  367. package/dist/manager/routes/user/connections/providers/google.js +55 -0
  368. package/dist/manager/routes/user/connections/providers/kick.js +68 -0
  369. package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
  370. package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
  371. package/dist/manager/routes/user/data-request/delete.js +84 -0
  372. package/dist/manager/routes/user/data-request/get.js +229 -0
  373. package/dist/manager/routes/user/data-request/post.js +136 -0
  374. package/dist/manager/routes/user/delete.js +149 -0
  375. package/dist/manager/routes/user/feedback/post.js +81 -0
  376. package/dist/manager/routes/user/get.js +14 -0
  377. package/dist/manager/routes/user/orders/get.js +95 -0
  378. package/dist/manager/routes/user/sessions/delete.js +95 -0
  379. package/dist/manager/routes/user/sessions/get.js +40 -0
  380. package/dist/manager/routes/user/settings/validate/post.js +86 -0
  381. package/dist/manager/routes/user/signup/post.js +662 -0
  382. package/dist/manager/routes/user/subscription/get.js +77 -0
  383. package/dist/manager/routes/user/token/post.js +30 -0
  384. package/dist/manager/routes/verts/delete.js +43 -0
  385. package/dist/manager/routes/verts/get.js +49 -0
  386. package/dist/manager/routes/verts/post.js +61 -0
  387. package/dist/manager/routes/verts/put.js +75 -0
  388. package/dist/manager/routes/verts/redirect/get.js +36 -0
  389. package/dist/manager/routes/verts/serve/get.js +67 -0
  390. package/dist/manager/routes/verts/utils.js +436 -0
  391. package/dist/manager/schemas/admin/backup/post.js +8 -0
  392. package/dist/manager/schemas/admin/cron/post.js +8 -0
  393. package/dist/manager/schemas/admin/database/get.js +5 -0
  394. package/dist/manager/schemas/admin/database/post.js +6 -0
  395. package/dist/manager/schemas/admin/email/post.js +31 -0
  396. package/dist/manager/schemas/admin/firestore/get.js +5 -0
  397. package/dist/manager/schemas/admin/firestore/post.js +8 -0
  398. package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
  399. package/dist/manager/schemas/admin/hook/post.js +8 -0
  400. package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
  401. package/dist/manager/schemas/admin/notification/post.js +23 -0
  402. package/dist/manager/schemas/admin/payment/post.js +8 -0
  403. package/dist/manager/schemas/admin/post/post.js +23 -0
  404. package/dist/manager/schemas/admin/post/put.js +13 -0
  405. package/dist/manager/schemas/admin/repo/content/post.js +10 -0
  406. package/dist/manager/schemas/admin/stats/get.js +5 -0
  407. package/dist/manager/schemas/admin/users/disable/post.js +9 -0
  408. package/dist/manager/schemas/admin/users/list/get.js +10 -0
  409. package/dist/manager/schemas/admin/users/sync/post.js +7 -0
  410. package/dist/manager/schemas/brand/get.js +3 -0
  411. package/dist/manager/schemas/content/post/get.js +8 -0
  412. package/dist/manager/schemas/general/email/post.js +10 -0
  413. package/dist/manager/schemas/general/uuid/post.js +9 -0
  414. package/dist/manager/schemas/handler/post/post.js +13 -0
  415. package/dist/manager/schemas/health/get.js +3 -0
  416. package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
  417. package/dist/manager/schemas/marketing/campaign/get.js +13 -0
  418. package/dist/manager/schemas/marketing/campaign/post.js +43 -0
  419. package/dist/manager/schemas/marketing/campaign/put.js +37 -0
  420. package/dist/manager/schemas/marketing/contact/delete.js +8 -0
  421. package/dist/manager/schemas/marketing/contact/post.js +14 -0
  422. package/dist/manager/schemas/marketing/contact/put.js +8 -0
  423. package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
  424. package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
  425. package/dist/manager/schemas/marketing/webhook/post.js +7 -0
  426. package/dist/manager/schemas/payments/cancel/post.js +15 -0
  427. package/dist/manager/schemas/payments/discount/get.js +8 -0
  428. package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
  429. package/dist/manager/schemas/payments/intent/post.js +28 -0
  430. package/dist/manager/schemas/payments/plan/post.js +11 -0
  431. package/dist/manager/schemas/payments/portal/post.js +12 -0
  432. package/dist/manager/schemas/payments/refund/post.js +14 -0
  433. package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
  434. package/dist/manager/schemas/payments/uncancel/post.js +9 -0
  435. package/dist/manager/schemas/payments/webhook/post.js +6 -0
  436. package/dist/manager/schemas/payments/winback/post.js +12 -0
  437. package/dist/manager/schemas/restart/index.js +5 -0
  438. package/dist/manager/schemas/special/electron-client/post.js +11 -0
  439. package/dist/manager/schemas/test/authenticate/get.js +3 -0
  440. package/dist/manager/schemas/test/health/get.js +3 -0
  441. package/dist/manager/schemas/test/index.js +5 -0
  442. package/dist/manager/schemas/test/lab/post.js +3 -0
  443. package/dist/manager/schemas/test/redirect/get.js +7 -0
  444. package/dist/manager/schemas/test/reset-account/post.js +3 -0
  445. package/dist/manager/schemas/test/roster/get.js +3 -0
  446. package/dist/manager/schemas/test/schema/post.js +70 -0
  447. package/dist/manager/schemas/test/usage/post.js +8 -0
  448. package/dist/manager/schemas/test/webhook/post.js +7 -0
  449. package/dist/manager/schemas/user/api-keys/post.js +6 -0
  450. package/dist/manager/schemas/user/connections/delete.js +8 -0
  451. package/dist/manager/schemas/user/connections/get.js +21 -0
  452. package/dist/manager/schemas/user/connections/post.js +12 -0
  453. package/dist/manager/schemas/user/data-request/delete.js +3 -0
  454. package/dist/manager/schemas/user/data-request/get.js +5 -0
  455. package/dist/manager/schemas/user/data-request/post.js +6 -0
  456. package/dist/manager/schemas/user/delete.js +6 -0
  457. package/dist/manager/schemas/user/feedback/post.js +8 -0
  458. package/dist/manager/schemas/user/get.js +3 -0
  459. package/dist/manager/schemas/user/orders/get.js +6 -0
  460. package/dist/manager/schemas/user/sessions/delete.js +6 -0
  461. package/dist/manager/schemas/user/sessions/get.js +6 -0
  462. package/dist/manager/schemas/user/settings/validate/post.js +8 -0
  463. package/dist/manager/schemas/user/signup/post.js +27 -0
  464. package/dist/manager/schemas/user/subscription/get.js +5 -0
  465. package/dist/manager/schemas/user/token/post.js +5 -0
  466. package/dist/manager/schemas/verts/delete.js +8 -0
  467. package/dist/manager/schemas/verts/get.js +9 -0
  468. package/dist/manager/schemas/verts/post.js +23 -0
  469. package/dist/manager/schemas/verts/put.js +20 -0
  470. package/dist/manager/schemas/verts/redirect/get.js +10 -0
  471. package/dist/manager/schemas/verts/serve/get.js +14 -0
  472. package/dist/manager/server-manager.js +69 -0
  473. package/dist/mcp/client.js +98 -0
  474. package/dist/mcp/handler.js +521 -0
  475. package/dist/mcp/index.js +157 -0
  476. package/dist/mcp/tools.js +555 -0
  477. package/dist/mcp/utils.js +108 -0
  478. package/dist/omega-bin.js +6 -0
  479. package/dist/require.js +3 -0
  480. package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
  481. package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
  482. package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
  483. package/dist/test/fixtures/firebase-project/firebase.json +49 -0
  484. package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
  485. package/dist/test/fixtures/firebase-project/package.json +14 -0
  486. package/dist/test/fixtures/firebase-project/src/index.js +11 -0
  487. package/dist/test/fixtures/firebase-project/storage.rules +8 -0
  488. package/dist/test/parse-audit.js +25 -0
  489. package/dist/test/run-tests.js +90 -0
  490. package/dist/test/runner.js +982 -0
  491. package/dist/test/seed.js +242 -0
  492. package/dist/test/test-accounts.js +2660 -0
  493. package/dist/test/utils/assertions.js +189 -0
  494. package/dist/test/utils/email-capture.js +252 -0
  495. package/dist/test/utils/extended-mode-warning.js +11 -0
  496. package/dist/test/utils/firestore-rules-client.js +277 -0
  497. package/dist/test/utils/http-client.js +228 -0
  498. package/dist/test/utils/test-mode-file.js +192 -0
  499. package/dist/utils/merge-line-files.js +6 -0
  500. package/dist/utils/scaffold-defaults.js +71 -0
  501. package/dist/utils/test-lanes.js +62 -0
  502. package/dist/vendor/account/engine.js +182 -0
  503. package/dist/vendor/account/features.js +220 -0
  504. package/dist/vendor/account/index.js +53 -0
  505. package/dist/vendor/account/schema.js +272 -0
  506. package/dist/vendor/account/subscription.js +38 -0
  507. package/dist/vendor/analytics/adapters/ga4.js +26 -0
  508. package/dist/vendor/analytics/adapters/meta.js +26 -0
  509. package/dist/vendor/analytics/adapters/resolve.js +130 -0
  510. package/dist/vendor/analytics/adapters/tiktok.js +27 -0
  511. package/dist/vendor/analytics/catalog.js +908 -0
  512. package/dist/vendor/analytics/identity.js +136 -0
  513. package/dist/vendor/config/company.js +31 -0
  514. package/dist/vendor/config/defaults.js +173 -0
  515. package/dist/vendor/config/demo.js +18 -0
  516. package/dist/vendor/config/desktop-artifacts.js +110 -0
  517. package/dist/vendor/config/edit.js +769 -0
  518. package/dist/vendor/config/env-delivery.js +145 -0
  519. package/dist/vendor/config/env-rules.js +93 -0
  520. package/dist/vendor/config/env-schema.js +1078 -0
  521. package/dist/vendor/config/env.js +445 -0
  522. package/dist/vendor/config/hooks.js +97 -0
  523. package/dist/vendor/config/index.js +237 -0
  524. package/dist/vendor/config/instances.js +208 -0
  525. package/dist/vendor/config/load.js +490 -0
  526. package/dist/vendor/config/merge.js +68 -0
  527. package/dist/vendor/config/order.js +139 -0
  528. package/dist/vendor/config/ports.js +374 -0
  529. package/dist/vendor/config/providers.js +32 -0
  530. package/dist/vendor/config/repo.js +142 -0
  531. package/dist/vendor/config/retired-keys.js +430 -0
  532. package/dist/vendor/config/schema.js +1610 -0
  533. package/dist/vendor/config/secrets.js +50 -0
  534. package/dist/vendor/config/seed.js +34 -0
  535. package/dist/vendor/config/site-global.js +205 -0
  536. package/dist/vendor/config/validate.js +554 -0
  537. package/dist/vendor/config/winback.js +61 -0
  538. package/dist/vendor/devkit/attach-log-file.js +262 -0
  539. package/dist/vendor/devkit/bare-requires.js +153 -0
  540. package/dist/vendor/devkit/command-path.js +46 -0
  541. package/dist/vendor/devkit/defaults-engine.js +372 -0
  542. package/dist/vendor/devkit/deploy-record.js +180 -0
  543. package/dist/vendor/devkit/deploy.js +286 -0
  544. package/dist/vendor/devkit/license.js +155 -0
  545. package/dist/vendor/devkit/local-https.js +360 -0
  546. package/dist/vendor/devkit/local.js +1905 -0
  547. package/dist/vendor/devkit/logger.js +128 -0
  548. package/dist/vendor/devkit/merge-line-files.js +296 -0
  549. package/dist/vendor/devkit/npm-registry.js +52 -0
  550. package/dist/vendor/devkit/omega-bin.js +345 -0
  551. package/dist/vendor/devkit/parse-audit.js +74 -0
  552. package/dist/vendor/devkit/safe-install.js +18 -0
  553. package/dist/vendor/devkit/scaffold-guard.js +96 -0
  554. package/dist/vendor/devkit/stop-signals.js +28 -0
  555. package/dist/vendor/devkit/test/assert.js +120 -0
  556. package/dist/vendor/devkit/test/define-cases.js +104 -0
  557. package/dist/vendor/devkit/test/runner-core.js +554 -0
  558. package/dist/vendor/devkit/test/scope.js +162 -0
  559. package/dist/vendor/devkit/update.js +569 -0
  560. package/dist/vendor/monitoring/core.js +180 -0
  561. package/dist/vendor/monitoring/env.js +49 -0
  562. package/dist/vendor/monitoring/logger.js +39 -0
  563. package/dist/vendor/monitoring/node.js +72 -0
  564. package/docs/admin-post-route.md +57 -0
  565. package/docs/ai-library.md +171 -0
  566. package/docs/architecture.md +62 -0
  567. package/docs/audit.md +70 -0
  568. package/docs/auth-hooks.md +74 -0
  569. package/docs/build-system.md +65 -0
  570. package/docs/cdp-debugging.md +29 -0
  571. package/docs/cli-firestore-auth.md +85 -0
  572. package/docs/cli-logs.md +67 -0
  573. package/docs/cli-output.md +146 -0
  574. package/docs/code-patterns.md +77 -0
  575. package/docs/common-mistakes.md +12 -0
  576. package/docs/common-operations.md +60 -0
  577. package/docs/connections.md +212 -0
  578. package/docs/consent.md +362 -0
  579. package/docs/directory-structure.md +150 -0
  580. package/docs/email-system.md +459 -0
  581. package/docs/environment-detection.md +93 -0
  582. package/docs/file-naming.md +10 -0
  583. package/docs/firestore.md +134 -0
  584. package/docs/ghostii.md +240 -0
  585. package/docs/index.md +299 -0
  586. package/docs/key-files.md +37 -0
  587. package/docs/logging.md +60 -0
  588. package/docs/marketing-campaigns.md +407 -0
  589. package/docs/marketing-fields.md +25 -0
  590. package/docs/mcp.md +222 -0
  591. package/docs/migration.md +129 -0
  592. package/docs/payment-system.md +856 -0
  593. package/docs/paypal-sandbox-qa.md +105 -0
  594. package/docs/response-headers.md +9 -0
  595. package/docs/routes.md +211 -0
  596. package/docs/sanitization.md +71 -0
  597. package/docs/schemas.md +163 -0
  598. package/docs/shared/agent-docs.md +89 -0
  599. package/docs/shared/analytics.md +612 -0
  600. package/docs/shared/brands.md +51 -0
  601. package/docs/shared/breaking-changes.md +497 -0
  602. package/docs/shared/config.md +1387 -0
  603. package/docs/shared/deploys.md +215 -0
  604. package/docs/shared/icons.md +201 -0
  605. package/docs/shared/local-dev.md +147 -0
  606. package/docs/shared/logging.md +202 -0
  607. package/docs/shared/monitoring.md +153 -0
  608. package/docs/shared/publishing.md +183 -0
  609. package/docs/shared/rulings.md +34 -0
  610. package/docs/shared/testing.md +147 -0
  611. package/docs/shared/theming.md +604 -0
  612. package/docs/shared/translation.md +291 -0
  613. package/docs/shared/updates.md +61 -0
  614. package/docs/stripe-webhook-forwarding.md +20 -0
  615. package/docs/test-boot-layer.md +67 -0
  616. package/docs/test-framework.md +583 -0
  617. package/docs/usage-rate-limiting.md +121 -0
  618. package/docs/verts.md +29 -0
  619. package/package.json +143 -0
  620. package/templates/config/omega.json5 +322 -0
  621. package/templates/database.rules.json +82 -0
  622. package/templates/firebase.json +67 -0
  623. package/templates/firestore.framework.rules +191 -0
  624. package/templates/firestore.indexes.json +4 -0
  625. package/templates/firestore.rules +59 -0
  626. package/templates/index.js +12 -0
  627. package/templates/public/404.html +26 -0
  628. package/templates/public/index.html +24 -0
  629. package/templates/remoteconfig.template.json +1 -0
  630. package/templates/storage-lifecycle-config-1-day.json +9 -0
  631. package/templates/storage-lifecycle-config-30-days.json +9 -0
  632. package/templates/storage.rules +11 -0
  633. package/test/_init/accounts-validation.js +58 -0
  634. package/test/ai/tools-live.test.js +171 -0
  635. package/test/analytics/conversion-delivery.test.js +1013 -0
  636. package/test/analytics/match-normalization.test.js +238 -0
  637. package/test/analytics/signup-conversion.test.js +135 -0
  638. package/test/boot/cli-dispatch.test.js +142 -0
  639. package/test/boot/defaults-scaffold.test.js +248 -0
  640. package/test/boot/deploy-staging.test.js +446 -0
  641. package/test/boot/disposable-domains.test.js +154 -0
  642. package/test/boot/emulator-boots.test.js +39 -0
  643. package/test/boot/emulator-port-preflight.test.js +126 -0
  644. package/test/boot/emulator-ready-timeout.test.js +46 -0
  645. package/test/boot/project-id-refresh.test.js +73 -0
  646. package/test/boot/suite-portability.test.js +278 -0
  647. package/test/boot/update-command.test.js +63 -0
  648. package/test/cli/cross-platform.test.js +168 -0
  649. package/test/cli/custom-project-type.test.js +426 -0
  650. package/test/cli/deploy-license-env.test.js +100 -0
  651. package/test/cli/emulator-adoption.test.js +254 -0
  652. package/test/cli/emulator-orphans.test.js +658 -0
  653. package/test/cli/emulator-ownership.test.js +177 -0
  654. package/test/cli/emulator-port-retry.test.js +395 -0
  655. package/test/cli/emulator-shutdown.test.js +788 -0
  656. package/test/cli/emulator-stale-reap.test.js +574 -0
  657. package/test/cli/ensure-target.test.js +325 -0
  658. package/test/cli/flags.test.js +64 -0
  659. package/test/cli/https-trust.test.js +38 -0
  660. package/test/cli/install-update-dispatch.test.js +47 -0
  661. package/test/cli/lane-environments.test.js +159 -0
  662. package/test/cli/marketing-campaigns-seeded.test.js +194 -0
  663. package/test/cli/migrate-bare-requires.test.js +128 -0
  664. package/test/cli/migrate-markers.test.js +530 -0
  665. package/test/cli/required-indexes.test.js +64 -0
  666. package/test/cli/rules-compile.test.js +622 -0
  667. package/test/cli/rules-migration-deferral.test.js +259 -0
  668. package/test/cli/rules-version.test.js +104 -0
  669. package/test/cli/setup-load-files.test.js +66 -0
  670. package/test/cli/setup-offline-mode.test.js +276 -0
  671. package/test/cli/setup-retired.test.js +64 -0
  672. package/test/cli/setup-shared-project-indexes.test.js +262 -0
  673. package/test/cli/setup-tests-requires.test.js +45 -0
  674. package/test/cli/stage-env-compose.test.js +316 -0
  675. package/test/cli/stripe-live-lane.test.js +303 -0
  676. package/test/cli/target.test.js +182 -0
  677. package/test/cli/templates.test.js +66 -0
  678. package/test/cli/test-runner-env.test.js +115 -0
  679. package/test/cli/test-stack-shutdown.test.js +275 -0
  680. package/test/cli/test-target-no-match.test.js +128 -0
  681. package/test/cli/verb-logs.test.js +118 -0
  682. package/test/cli/version-dispatch.test.js +39 -0
  683. package/test/content/blog-generate.test.js +164 -0
  684. package/test/email/campaign-config-fault.test.js +376 -0
  685. package/test/email/campaign-cron-pipeline.test.js +536 -0
  686. package/test/email/campaign-send.test.js +47 -0
  687. package/test/email/consent-lifecycle.test.js +258 -0
  688. package/test/email/content-html-policy.test.js +270 -0
  689. package/test/email/feedback-and-plain-send.test.js +54 -0
  690. package/test/email/fixtures/clean.json +30 -0
  691. package/test/email/fixtures/editorial.json +30 -0
  692. package/test/email/fixtures/field-report.json +53 -0
  693. package/test/email/identity.test.js +321 -0
  694. package/test/email/marketing/consent-gate.test.js +265 -0
  695. package/test/email/marketing/custom-fields-catalog.test.js +259 -0
  696. package/test/email/marketing/prune-per-provider.test.js +762 -0
  697. package/test/email/marketing/remove-log-privacy.test.js +225 -0
  698. package/test/email/marketing-lifecycle.test.js +139 -0
  699. package/test/email/newsletter-generate.test.js +853 -0
  700. package/test/email/newsletter-svg-tokens.test.js +51 -0
  701. package/test/email/newsletter-templates.test.js +492 -0
  702. package/test/email/order-one-time-cta.test.js +97 -0
  703. package/test/email/render-content.test.js +248 -0
  704. package/test/email/safe-url.test.js +0 -0
  705. package/test/email/sanitize-images.test.js +66 -0
  706. package/test/email/send-log-privacy.test.js +107 -0
  707. package/test/email/templates.test.js +288 -0
  708. package/test/email/testing-capture.test.js +260 -0
  709. package/test/email/transactional-send.test.js +36 -0
  710. package/test/email/transactional.test.js +562 -0
  711. package/test/email/unsubscribe-groups.test.js +155 -0
  712. package/test/email/unsubscribe-key.test.js +91 -0
  713. package/test/email/validation-cases.test.js +152 -0
  714. package/test/email/validation.test.js +746 -0
  715. package/test/events/auth-delete-conversion.test.js +138 -0
  716. package/test/events/auth-delete-race.test.js +211 -0
  717. package/test/events/auth-on-create-log.test.js +164 -0
  718. package/test/events/auth-signup-conversion.test.js +141 -0
  719. package/test/events/auth-signup-limit.test.js +142 -0
  720. package/test/events/auth-trigger-log-privacy.test.js +256 -0
  721. package/test/events/cron-job-doc-shape.test.js +101 -0
  722. package/test/events/cron-reset-usage.test.js +116 -0
  723. package/test/events/notification-conversion.test.js +182 -0
  724. package/test/events/payments/_webhook-harness.js +307 -0
  725. package/test/events/payments/abandoned-cart-activity.test.js +135 -0
  726. package/test/events/payments/analytics-payment-events.test.js +945 -0
  727. package/test/events/payments/dispute-email-status.test.js +55 -0
  728. package/test/events/payments/journey-payments-abandoned.test.js +126 -0
  729. package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
  730. package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
  731. package/test/events/payments/journey-payments-cancel.test.js +184 -0
  732. package/test/events/payments/journey-payments-decline.test.js +225 -0
  733. package/test/events/payments/journey-payments-discount.test.js +91 -0
  734. package/test/events/payments/journey-payments-dispute.test.js +190 -0
  735. package/test/events/payments/journey-payments-failure.test.js +148 -0
  736. package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
  737. package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
  738. package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
  739. package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
  740. package/test/events/payments/journey-payments-one-time.test.js +175 -0
  741. package/test/events/payments/journey-payments-plan-change.test.js +146 -0
  742. package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
  743. package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
  744. package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
  745. package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
  746. package/test/events/payments/journey-payments-suspend.test.js +183 -0
  747. package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
  748. package/test/events/payments/journey-payments-trial.test.js +173 -0
  749. package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
  750. package/test/events/payments/journey-payments-uncancel.test.js +157 -0
  751. package/test/events/payments/journey-payments-upgrade.test.js +137 -0
  752. package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
  753. package/test/events/payments/journey-payments-winback.test.js +195 -0
  754. package/test/events/payments/paypal-expiry-cron.test.js +166 -0
  755. package/test/events/payments/purchase-failed-handler.test.js +80 -0
  756. package/test/events/payments/purchase-refunded-handler.test.js +108 -0
  757. package/test/events/payments/test-processor-doc-shape.test.js +162 -0
  758. package/test/events/payments/transition-order-emails.test.js +185 -0
  759. package/test/events/payments/transition-promo-lines.test.js +192 -0
  760. package/test/events/payments/transitions-detect.test.js +767 -0
  761. package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
  762. package/test/events/payments/trial-lapse-sweep.test.js +359 -0
  763. package/test/events/payments/webhook-atomic-writes.test.js +69 -0
  764. package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
  765. package/test/events/payments/webhook-discount-clear.test.js +189 -0
  766. package/test/events/payments/webhook-failure-intent.test.js +93 -0
  767. package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
  768. package/test/events/payments/webhook-ordering.test.js +326 -0
  769. package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
  770. package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
  771. package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
  772. package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
  773. package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
  774. package/test/events/payments/webhook-refund-without-order.test.js +170 -0
  775. package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
  776. package/test/events/payments/webhook-refused-intent.test.js +142 -0
  777. package/test/events/payments/webhook-retry-sweep.test.js +252 -0
  778. package/test/events/payments/webhook-transition-claim.test.js +389 -0
  779. package/test/events/payments/webhook-uid-trust.test.js +203 -0
  780. package/test/events/payments/webhook-user-without-auth.test.js +161 -0
  781. package/test/fixtures/chargebee/invoice-one-time.json +27 -0
  782. package/test/fixtures/chargebee/subscription-active.json +44 -0
  783. package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
  784. package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
  785. package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
  786. package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
  787. package/test/fixtures/chargebee/subscription-paused.json +42 -0
  788. package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
  789. package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
  790. package/test/fixtures/coinbase/charge-confirmed.json +50 -0
  791. package/test/fixtures/coinbase/charge-failed.json +34 -0
  792. package/test/fixtures/coinbase/charge-pending.json +34 -0
  793. package/test/fixtures/migrate/ported-route.js.txt +11 -0
  794. package/test/fixtures/paypal/capture-completed.json +51 -0
  795. package/test/fixtures/paypal/capture-refunded.json +43 -0
  796. package/test/fixtures/paypal/order-approved.json +62 -0
  797. package/test/fixtures/paypal/order-completed.json +110 -0
  798. package/test/fixtures/paypal/sale-refunded.json +38 -0
  799. package/test/fixtures/paypal/subscription-active.json +76 -0
  800. package/test/fixtures/paypal/subscription-cancelled.json +50 -0
  801. package/test/fixtures/paypal/subscription-suspended.json +65 -0
  802. package/test/fixtures/stripe/checkout-session-completed.json +130 -0
  803. package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
  804. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
  805. package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
  806. package/test/fixtures/stripe/subscription-active.json +161 -0
  807. package/test/fixtures/stripe/subscription-canceled.json +161 -0
  808. package/test/fixtures/stripe/subscription-trialing.json +161 -0
  809. package/test/helpers/_shared-config.js +15 -0
  810. package/test/helpers/ai-request-payload.test.js +619 -0
  811. package/test/helpers/ai-schema-resolve.test.js +125 -0
  812. package/test/helpers/ai-test-provider.test.js +227 -0
  813. package/test/helpers/ai-token-accounting.test.js +150 -0
  814. package/test/helpers/ai-tools-format.test.js +384 -0
  815. package/test/helpers/analytics-no-id-notice.test.js +146 -0
  816. package/test/helpers/analytics-user-data.test.js +194 -0
  817. package/test/helpers/api-manager.test.js +311 -0
  818. package/test/helpers/backend-router.test.js +136 -0
  819. package/test/helpers/content/blog-auto-publisher.test.js +482 -0
  820. package/test/helpers/content/feed-parser.test.js +529 -0
  821. package/test/helpers/content/ghostii-blocks.test.js +135 -0
  822. package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
  823. package/test/helpers/content/ghostii-write-article.test.js +244 -0
  824. package/test/helpers/dev-only-routes.test.js +225 -0
  825. package/test/helpers/env-reader.test.js +362 -0
  826. package/test/helpers/environment.test.js +263 -0
  827. package/test/helpers/event-middleware.test.js +407 -0
  828. package/test/helpers/infer-contact.test.js +157 -0
  829. package/test/helpers/lane-url.test.js +90 -0
  830. package/test/helpers/merge-line-files.test.js +272 -0
  831. package/test/helpers/metadata.test.js +122 -0
  832. package/test/helpers/middleware-request-log.test.js +134 -0
  833. package/test/helpers/middleware-user-log.test.js +124 -0
  834. package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
  835. package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
  836. package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
  837. package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
  838. package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
  839. package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
  840. package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
  841. package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
  842. package/test/helpers/payment/discount-codes.test.js +141 -0
  843. package/test/helpers/payment/extract-resource.test.js +113 -0
  844. package/test/helpers/payment/fetch-failure.test.js +215 -0
  845. package/test/helpers/payment/license-gate.test.js +108 -0
  846. package/test/helpers/payment/order-id.test.js +99 -0
  847. package/test/helpers/payment/paypal/create-intent.test.js +382 -0
  848. package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
  849. package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
  850. package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
  851. package/test/helpers/payment/paypal/refund-details.test.js +202 -0
  852. package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
  853. package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
  854. package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
  855. package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
  856. package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
  857. package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
  858. package/test/helpers/payment/stripe/refund-details.test.js +147 -0
  859. package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
  860. package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
  861. package/test/helpers/persona-domain.test.js +87 -0
  862. package/test/helpers/recaptcha.test.js +166 -0
  863. package/test/helpers/resolved-config.test.js +106 -0
  864. package/test/helpers/response-log-redaction.test.js +262 -0
  865. package/test/helpers/roles.test.js +155 -0
  866. package/test/helpers/route-context-debug-gate.test.js +118 -0
  867. package/test/helpers/route-context-logging.test.js +214 -0
  868. package/test/helpers/route-context.test.js +129 -0
  869. package/test/helpers/safe-compare.test.js +114 -0
  870. package/test/helpers/sanitize.test.js +223 -0
  871. package/test/helpers/schema-engine.test.js +537 -0
  872. package/test/helpers/schema-zod.test.js +554 -0
  873. package/test/helpers/seed-accounts-load.test.js +163 -0
  874. package/test/helpers/seed-google-personas.test.js +114 -0
  875. package/test/helpers/seeded-personas.test.js +823 -0
  876. package/test/helpers/settings.test.js +292 -0
  877. package/test/helpers/setup-engines-pin.test.js +68 -0
  878. package/test/helpers/setup-manifest-sync.test.js +116 -0
  879. package/test/helpers/slugify.test.js +395 -0
  880. package/test/helpers/storage.test.js +195 -0
  881. package/test/helpers/test-banner-latch.test.js +139 -0
  882. package/test/helpers/usage-consume.test.js +460 -0
  883. package/test/helpers/usage-log-privacy.test.js +121 -0
  884. package/test/helpers/user-doc-heal.test.js +540 -0
  885. package/test/helpers/user.test.js +771 -0
  886. package/test/helpers/webhook-forward.test.js +419 -0
  887. package/test/helpers/wipe-auth-project.test.js +143 -0
  888. package/test/mcp/discovery.test.js +52 -0
  889. package/test/mcp/oauth.test.js +160 -0
  890. package/test/mcp/protocol.test.js +278 -0
  891. package/test/mcp/roles.test.js +203 -0
  892. package/test/mcp/utils.test.js +245 -0
  893. package/test/notification/identity.test.js +155 -0
  894. package/test/routes/admin/create-post.test.js +362 -0
  895. package/test/routes/admin/database.test.js +133 -0
  896. package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
  897. package/test/routes/admin/email-content-html.test.js +58 -0
  898. package/test/routes/admin/email-request-log.test.js +86 -0
  899. package/test/routes/admin/email.test.js +116 -0
  900. package/test/routes/admin/firestore-query.test.js +206 -0
  901. package/test/routes/admin/firestore.test.js +129 -0
  902. package/test/routes/admin/infer-contact.test.js +220 -0
  903. package/test/routes/admin/notification.test.js +199 -0
  904. package/test/routes/admin/post-convert-image.test.js +159 -0
  905. package/test/routes/admin/post-deploy-flag.test.js +81 -0
  906. package/test/routes/admin/post-download-error.test.js +90 -0
  907. package/test/routes/admin/post-resize-image.test.js +185 -0
  908. package/test/routes/admin/post.test.js +369 -0
  909. package/test/routes/admin/repo-content.test.js +223 -0
  910. package/test/routes/admin/stats.test.js +114 -0
  911. package/test/routes/admin/users-disable.test.js +63 -0
  912. package/test/routes/admin/users-list.test.js +71 -0
  913. package/test/routes/content/post.test.js +60 -0
  914. package/test/routes/general/uuid.test.js +133 -0
  915. package/test/routes/health.test.js +110 -0
  916. package/test/routes/marketing/campaign.test.js +184 -0
  917. package/test/routes/marketing/contact.test.js +416 -0
  918. package/test/routes/marketing/email-preferences.test.js +293 -0
  919. package/test/routes/marketing/push-send.test.js +34 -0
  920. package/test/routes/marketing/webhook-forward.test.js +63 -0
  921. package/test/routes/marketing/webhook.test.js +641 -0
  922. package/test/routes/payments/_route-harness.js +145 -0
  923. package/test/routes/payments/cancel-provider-errors.test.js +227 -0
  924. package/test/routes/payments/cancel-skip-guards.test.js +126 -0
  925. package/test/routes/payments/cancel-trialing.test.js +428 -0
  926. package/test/routes/payments/cancel.test.js +165 -0
  927. package/test/routes/payments/dedup-race.test.js +186 -0
  928. package/test/routes/payments/discount.test.js +82 -0
  929. package/test/routes/payments/dispute-alert.test.js +324 -0
  930. package/test/routes/payments/intent-discount-amounts.test.js +550 -0
  931. package/test/routes/payments/intent-discount-coupons.test.js +293 -0
  932. package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
  933. package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
  934. package/test/routes/payments/intent-zero-total.test.js +260 -0
  935. package/test/routes/payments/intent.test.js +413 -0
  936. package/test/routes/payments/plan.test.js +367 -0
  937. package/test/routes/payments/portal-return-url.test.js +107 -0
  938. package/test/routes/payments/portal.test.js +94 -0
  939. package/test/routes/payments/refund-one-time.test.js +254 -0
  940. package/test/routes/payments/refund-paypal-proration.test.js +190 -0
  941. package/test/routes/payments/refund.test.js +181 -0
  942. package/test/routes/payments/trial-eligibility.test.js +73 -0
  943. package/test/routes/payments/uncancel.test.js +270 -0
  944. package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
  945. package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
  946. package/test/routes/payments/webhook-test-provider.test.js +156 -0
  947. package/test/routes/payments/webhook.test.js +115 -0
  948. package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
  949. package/test/routes/payments/winback.test.js +597 -0
  950. package/test/routes/test/authenticate.test.js +79 -0
  951. package/test/routes/test/redirect.test.js +66 -0
  952. package/test/routes/test/reset-account.test.js +126 -0
  953. package/test/routes/test/roster.test.js +101 -0
  954. package/test/routes/test/schema.test.js +556 -0
  955. package/test/routes/test/usage.test.js +374 -0
  956. package/test/routes/user/api-keys.test.js +158 -0
  957. package/test/routes/user/connections-grant.test.js +759 -0
  958. package/test/routes/user/connections-identity.test.js +260 -0
  959. package/test/routes/user/connections-log-privacy.test.js +206 -0
  960. package/test/routes/user/connections-refresh-lease.test.js +696 -0
  961. package/test/routes/user/connections-return.test.js +261 -0
  962. package/test/routes/user/connections-uid.test.js +516 -0
  963. package/test/routes/user/delete.test.js +138 -0
  964. package/test/routes/user/feedback.test.js +106 -0
  965. package/test/routes/user/orders.test.js +233 -0
  966. package/test/routes/user/sessions.test.js +231 -0
  967. package/test/routes/user/settings-validate.test.js +84 -0
  968. package/test/routes/user/signup-emails.test.js +113 -0
  969. package/test/routes/user/signup-location.test.js +84 -0
  970. package/test/routes/user/signup-log-privacy.test.js +112 -0
  971. package/test/routes/user/signup.test.js +579 -0
  972. package/test/routes/user/subscription.test.js +101 -0
  973. package/test/routes/user/token.test.js +111 -0
  974. package/test/routes/user/user.test.js +158 -0
  975. package/test/routes/verts/cache.test.js +78 -0
  976. package/test/routes/verts/click-destination.test.js +76 -0
  977. package/test/routes/verts/crud.test.js +194 -0
  978. package/test/routes/verts/redirect.test.js +147 -0
  979. package/test/routes/verts/selection.test.js +237 -0
  980. package/test/routes/verts/serve.test.js +185 -0
  981. package/test/routes/verts/unit-document.test.js +117 -0
  982. package/test/rules/_environment.js +66 -0
  983. package/test/rules/brand-merge.test.js +195 -0
  984. package/test/rules/field-helpers.test.js +309 -0
  985. package/test/rules/notifications.test.js +577 -0
  986. package/test/rules/payments-carts.test.js +406 -0
  987. package/test/rules/sessions.test.js +170 -0
  988. package/test/rules/user.test.js +517 -0
  989. package/test/rules/verts.test.js +101 -0
  990. package/test/security/fetch-log-secrets.test.js +124 -0
  991. package/test/security/primitives.test.js +105 -0
  992. package/test/security/repo-pinning.test.js +77 -0
  993. package/test/stripe-live/subscription-lifecycle.test.js +141 -0
@@ -0,0 +1,497 @@
1
+ # Breaking changes — legacy → OMEGA
2
+
3
+ The register of every contract that changed SHAPE between a legacy framework and
4
+ its OMEGA successor, with the by-hand migration step for each. Read it once per
5
+ legacy brand: work down the sections that apply and the brand lands on the new
6
+ contracts in one pass.
7
+
8
+ **No framework dual-reads a legacy form.** An old key, an old name, an old file
9
+ is not "deprecated but accepted" — it is unknown, and the loud ones fail
10
+ validation ([#142](https://github.com/Omega-JS-Stack/omega/issues/142)). The only
11
+ sanctioned legacy-reading paths are the one-time converters: `npx omega migrate`
12
+ for a UJM website, and the mapping tables in
13
+ [config.md](config.md#migration--legacy-configs--omegajson5) for every other
14
+ target.
15
+
16
+ **Boundaries.** What never got PORTED is [#78](https://github.com/Omega-JS-Stack/omega/issues/78)'s
17
+ gap tables, not this file. Converter TOOLING is
18
+ [#40](https://github.com/Omega-JS-Stack/omega/issues/40) — these rows are its
19
+ input, not its implementation. The legacy repos stay read-only reference
20
+ (AGENTS.md HARD RULE 1): nothing here asks you to change them.
21
+
22
+ ## `ultimate-jekyll-manager` → `@omega.js/web`
23
+
24
+ | Contract | Old form | New form | Manual migration step |
25
+ |---|---|---|---|
26
+ | Build engine | Jekyll 4 + Ruby/Bundler (`Gemfile`, `Gemfile.lock`, `src/_config.yml`) | Eleventy 3 + LiquidJS, Node only | Run `npx omega migrate --check`, then `npx omega migrate` in the website target: it converts the config, runs the codemod, and deletes `src/_config.yml`, `config/ultimate-jekyll-manager.json`, `Gemfile`, `Gemfile.lock`, `.ruby-version`. Drop the Ruby toolchain from CI |
27
+ | Package + CLI | `ultimate-jekyll-manager` dependency; `uj` / `ujm` / `ultimate-jekyll` / `mgr` bins | `@omega.js/web`; `omega` / `omg` / `mgr` | Swap the dependency; replace `npx mgr <verb>` with `npx omega <verb>` in every npm script and workflow |
28
+ | Config | `src/_config.yml` + `config/ultimate-jekyll-manager.json` | `config/omega.json5` | Key-by-key table in [config.md](config.md#ultimate-jekyll-manager-src_configyml--configultimate-jekyll-managerjson--omega-migrate-b4-checkpoint-32); `omega migrate` writes it for you |
29
+ | Client-runtime config key | `web_manager: { … }` | `client` (`targets.web.client`) | Codemod rule `client-frontmatter` renames page-frontmatter blocks; config blocks relocate per the UJM mapping table in [config.md](config.md#migration--legacy-configs--omegajson5) (the SSOT for the key-by-key moves). The old name is a validation error, not a silent no-op |
30
+ | Framework file delivery | `distribute.js` COPIED framework layouts/includes/assets into the consumer repo | Layered resolution (consumer → active theme → base → core), first-layer-wins per relative path, zero copying | Delete every copied framework file from the repo; keep only files you truly override, at the same relative path. `npx omega customize --list` prints the override map |
31
+ | Layout values | `layout: themes/[ site.theme.id ]/frontend/pages/blog` (or hardcoded `themes/classy/…`) | The plain layout name: `layout: frontend/pages/blog` | Codemod rule `bracket-layout`, or strip the `themes/<id>/` prefix by hand. An unmigrated value fails the build loudly ("Problem creating an Eleventy Layout") — the engine no longer aliases the old spellings |
32
+ | Frontmatter refs | Bracket interpolation — `title: [ site.brand.name ]` | Real Liquid — `title: {{ resolved.config.brand.name }}` | Rewrite `[ … ]` to `{{ … }}` in frontmatter by hand (the codemod covers only `layout:` lines). An unmigrated value ships the literal brackets into the output — nothing resolves them anymore |
33
+ | Page data reads | `page.<frontmatterKey>`, `page.content`, `page.slug`, `page.resolved.*`, `page.canonical.url` | Bare `<key>`, `content`, `page.fileSlug`, `resolved.*`, `{{ site.url }}{{ page.url }}` | Codemod rules `page-props`, `page-resolved`, `canonical-url`. `page.next` / `page.previous` / `page.collection` have NO mechanical equivalent — port them by hand to the Eleventy collections API |
34
+ | Page frontmatter scope | Any frontmatter key fed the templates | META-ONLY allow-list — every key it holds is [docs/web/frontmatter.md](../web/frontmatter.md); other content keys are stripped with a build warning | Move page content out of frontmatter into `{% section %}` entries or a collection document (collection entries and layouts are exempt — their frontmatter IS the document) |
35
+ | Config namespace | A page restated omega.json5 keys BARE (`theme:`, `client:`, `inbound:`) and every template read config off the site global (`{{ site.brand.name }}`, `{{ resolved.theme.id }}`) | Three namespaces ([#607](https://github.com/Omega-JS-Stack/omega/issues/607)): a page's overrides go under a `config:` parent, templates read `resolved.config.<section>` (the WHOLE merged config), `meta` is PAGE machinery keeping its bare/flat spelling, and `site.*` is BUILD FACTS only (collections, `omega`, `time`, `pricing`, `targets`, …) | Codemod rules `config-parent` (the frontmatter keys) and `config-reads` (the reads, bodies + frontmatter values + section `.json` descriptors, [#611](https://github.com/Omega-JS-Stack/omega/issues/611)) — `omega migrate` runs both. Nothing dual-reads, and the `config:` rule runs both ways: a bare config section fails the build naming the key, a non-section key under `config:` fails the same way, and a `site.<section>` read fails naming the expression |
36
+ | Site-wide page meta | A `meta` block in the config (`_config.yml`, then omega.json5's `meta` section) set the default title/description/index for every page | NO config meta section at all ([#607](https://github.com/Omega-JS-Stack/omega/issues/607), Ian 2026-08-26 — meta never exists in two places). Page frontmatter `meta:` is the only meta; the site-wide default is `brand.name` / `brand.description` ([docs/web/frontmatter.md](../web/frontmatter.md)) | `omega migrate` drops the legacy block with a note; a converted omega.json5 still carrying `meta` (or `targets.web.meta`) is a retired-key error naming the move. Move a real site-wide title/description into `brand.name` / `brand.description`, and anything per-page into that page's own `meta:` |
37
+ | Redirects | Hand-maintained redirect pages under `src/defaults/dist/redirects/**` on UJM's `redirects` layout, and TEMPLATED ones (`/c/:id` → `/code?id=:id`, DashQR's QR codes) hand-authored in the Cloudflare dashboard where nothing declared them | Two declared mechanisms, split by whether the URLs can be enumerated ([#466](https://github.com/Omega-JS-Stack/omega/issues/466)): a redirect PAGE — `redirect.url` in frontmatter on the `modules/utilities/redirect` layout, and the build emits meta-refresh + canonical + noindex — and `edge.providers.cloudflare.rules.redirect`, the zone's dynamic-redirect ruleset `omega manage` reconciles | Redirect pages port as pages (the layout name is the only change). Move every dashboard-authored redirect rule into `edge.providers.cloudflare.rules.redirect` so the zone has a declared source; `omega manage` then owns the ruleset and REMOVES any rule config does not name. There is no web-config redirect key: `targets.web.redirects` existed only in 0.45.0–0.49.0 and is now a retired-key error naming this move |
38
+ | `append: true` frontmatter | A page body rendered BELOW the layout's default `{% composition %}` when the page declared the flag | Gone ([#607](https://github.com/Omega-JS-Stack/omega/issues/607)) — a page body always REPLACES the composition | Codemod rule `append-flag` drops the key with a finding. To keep the default bands, run `omega customize <url>` to materialize them into the page, then edit them |
39
+ | Nested loops | `forloop.parentloop.<prop>` | A hoisted `{% assign omega_parentloop<depth>_<prop> = forloop.<prop> %}` after the parent `{% for %}` (LiquidJS has no parentloop) | Codemod rule `parentloop`; it flags the cases it will not touch (multi-level, same-line) for hand-hoisting |
40
+ | Interpolated tag args | `{% uj_icon "{{ page.icon }}" %}` — Jekyll silently no-op'd it | `{% capture omega_migrate_arg_1 %}…{% endcapture %}` hoisted above, the variable passed as the arg | Codemod rule `tag-arg-interpolation`; verify each reported line that it could not rewrite |
41
+ | Icons | The `uj_icon`/`omega_icon` TAG, plus a `prerender_icons` frontmatter list that drew hidden copies for JS to clone | Native Font Awesome markup, `<i class="fa-solid fa-rocket"></i>`, everywhere ([#619](https://github.com/Omega-JS-Stack/omega/issues/619)): the build inlines every icon the rendered page names, and a runtime watcher upgrades whatever JS creates afterwards, from `/assets/icons/<style>/<name>.svg`. Country flags are the second namespace, `<i class="omega-flag omega-flag-us">` | Codemod rule `icon-tag-markup` converts every icon tag (both spellings) to markup; drop `prerender_icons` from frontmatter and delete any `getPrerenderedIcon` call in favour of an `<i class="fa-…">` string. A `label=` option has no native equivalent and is dropped — hand-write `role="img" aria-label` where an icon really carries meaning. Contract: [docs/shared/icons.md](icons.md) |
42
+ | Includes | `{% include /components/x.html %}` | `{% include components/x.html %}` (no leading slash) | Codemod rule `include-slash` |
43
+ | Analytics template reads | `site.analytics.google` | `resolved.config.analytics.providers.google.id` | Codemod rules `analytics-shape` (the provider shape) then `config-reads` (the namespace) |
44
+ | Classy gradient utilities | `.gradient-animated` (gradient shimmer) + `.gradient-grain` (noise overlay) on hero markup | `omega-dotgrid` — the masked dot backdrop v2 puts behind every hero — plus `data-omega-dotfield` where the animation was. Classy v2 ships zero gradients, so the old classes are silent no-ops | Codemod rule `gradient-utilities` converts both once ([#296](https://github.com/Omega-JS-Stack/omega/issues/296)); the pair on one element collapses to ONE `omega-dotgrid`. `.bg-gradient-*` names are NOT touched — v2 still neutralizes those to flat token paint |
45
+ | Sass entry | `@use 'ultimate-jekyll-manager' as * with (…)` in `src/assets/css/main.scss`; page CSS files `@use`-ing themselves | `@use 'omega:main' with (…)`; the self-`@use` lines are gone (every layer's page sheet already loads, [#624](https://github.com/Omega-JS-Stack/omega/issues/624)) | `omega migrate`'s consumer-assets pass rewrites both; by hand it is a one-line edit plus deleting the self-`@use` lines |
46
+ | JS entry | Seeded `src/assets/js/main.js` bootstrapping the manager | Deleted — core main + the boot runtime own it | `omega migrate` deletes an untouched seed and FLAGS a customized one; port custom logic into a page or section module |
47
+ | Client bootstrap | `import webManager from 'web-manager'`; `window.Manager` global | `import omega from '@omega.js/client'`; no window global (the client is a singleton — import it) | Replace the import in every module; delete `window.Manager` references |
48
+ | Deploy | `npu sync --message='Deploy'` shell-out | `omega deploy` (plain git sync + `workflow_dispatch`, or the direct lane) | Replace the script; contract in [deploys.md](deploys.md) |
49
+ | Version maintenance | Setup-time `ensureManagerVersion()` + peer-dependency auto-install | The explicit `omega update` verb | Stop expecting self-updates; run `npx omega update` (`--apply` to install) — [updates.md](updates.md) |
50
+ | Charts | A page imported `chart.js` bare (it was a `@omega.js/web` dependency) and built its own `new Chart(canvas, config)` | `chart.js` is gone — the framework draws with TanStack Charts ([#772](https://github.com/Omega-JS-Stack/omega/issues/772)), reached ONLY through `__main_assets__/js/libs/charts.js` (`loadCharts`, `chartSlot`, `barChart`/`stackedBarChart`/`doughnutChart`/`lineChart`) | Replace the bare import and the hand-built config with the helpers, and the page's `<canvas>` with `chartSlot`'s markup (an SVG chart has no canvas). A page that genuinely needs the raw grammar imports `@tanstack/charts` bare instead — same framework-resolution rule, new name |
51
+
52
+ ## `backend-manager` → `@omega.js/backend`
53
+
54
+ | Contract | Old form | New form | Manual migration step |
55
+ |---|---|---|---|
56
+ | Package + init | `require('backend-manager')`; `Manager.init({ backendManagerConfigPath: 'backend-manager-config.json' })` | `require('@omega.js/backend')`; the config loader discovers the file — the option is GONE | Swap the dependency and delete the `backendManagerConfigPath` option from `functions/index.js` |
57
+ | Config | `functions/backend-manager-config.json` | `functions/config/omega.json5` | Key-by-key table in [config.md](config.md#backend-manager-functionsbackend-manager-configjson--functionsconfigomegajson5--done-checkpoint-19) |
58
+ | Exported Cloud Functions | `bm_api`, `bm_signUpHandler`, `bm_createPost`, `bm_cronDaily`, … | `omega_api`, `omega_signUpHandler`, `omega_createPost`, `omega_cronDaily`, … | Deploy the new names, repoint every trigger/scheduler/webhook that names a function, then DELETE the orphaned `bm_*` functions from the Firebase project (a rename leaves the old ones running) |
59
+ | Hosting rewrite | `{ source: '/backend-manager/**', function: 'bm_api' }` | `{ source: '{/omega,/omega/**,/backend-manager,/backend-manager/**,/mcp,/mcp/**,/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server,/authorize,/token,/register}', function: 'omega_api' }` | The next verb's `ensureTarget()` writes it (and removes duplicates); by hand, replace the rewrite and keep it FIRST in the list |
60
+ | API dispatch | Command-based: `POST /backend-manager` with `{ command: 'user:sign-up', payload: {…} }` | REST: `POST /omega/user/sign-up` with the payload AS the body | Rewrite each caller: the command's `:` becomes a path segment, `payload` becomes the body. On the client, `omega.request('/omega/user/sign-up', { method: 'POST', body: {…} })` |
61
+ | URL prefix | `/backend-manager/*` | `/omega/*` (also `/omega_api/*` on the direct function URL) | Repoint every first-party caller. The old prefix still resolves — a deliberate external-client alias, see [Deliberate compatibility that REMAINS](#deliberate-compatibility-that-remains) |
62
+ | Per-request object | `BackendAssistant`; handler signature `module.exports = async ({ assistant, settings, analytics }) => …` | `RouteContext`; handler signature `module.exports = async ({ ctx, settings, analytics }) => …` | Rename the destructured argument and every `assistant.` call site (`ctx.respond`, `ctx.log`, `ctx.request`) in each custom route, event, and cron handler |
63
+ | Environment | `BACKEND_MANAGER_KEY`, `BACKEND_MANAGER_WEBHOOK_KEY`, `BEM_TEST_RUNNER`, `BEM_HTTPS_PORT` | `OMEGA_ADMIN_KEY`, `OMEGA_WEBHOOK_KEY`, `OMEGA_TEST_RUNNER`, `OMEGA_HTTPS_PORT` | Rename in `.env`, in CI secrets, and in anything that reads them. Values carry over unchanged |
64
+ | AI provider keys | TWO names per provider: `BACKEND_MANAGER_OPENAI_API_KEY` (the company-wide fallback) beside a bare `OPENAI_API_KEY` (the brand's own), and the same pair for Anthropic. The provider preferred the bare one and fell back to the prefixed; `inferContact` read ONLY the prefixed one | ONE name per provider: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` ([#639](https://github.com/Omega-JS-Stack/omega/issues/639)). Every reader takes the bare name through the one env reader; the OMEGA-era `OMEGA_OPENAI_API_KEY` / `OMEGA_ANTHROPIC_API_KEY` twins are removed outright, with no dual-read | Rename `BACKEND_MANAGER_OPENAI_API_KEY` (or `OMEGA_OPENAI_API_KEY`) to `OPENAI_API_KEY` in `.env` and CI secrets, same for Anthropic, and delete the old rows. A key that served EVERY brand moves to the COMPANY `.env` under that same bare name — the cascade is the fallback, never a second key. An interactive `npx omega manage` asks for both once (the `ai` service) and writes them for you |
65
+ | CLI | `bm` / `bem` / `backend-manager` / `mgr` bins | `omega` / `omg` / `mgr` (one dispatcher; a backend's `functions/` dir resolves to the backend CLI) | Replace the bin name in npm scripts and workflows |
66
+ | `test/*` routes | Every route under `routes/test/` served at its production URL | The whole `test/` route folder 404s outside dev/testing, with zero carve-outs; contract in the backend's `docs/routes.md` ([#238](https://github.com/Omega-JS-Stack/omega/issues/238)) | Use `/omega/health` for liveness (a real route, public, no input echoed) — not `/omega/test/health`. A brand whose own `routes/test/*` route must serve in production moves it out of the `test/` folder; debug routes belong in `test/` and are gated by default |
67
+ | Test discovery | The BEM runner discovered plain `.js` files under `test/` | `discoverTests()` matches `*.test.js` only (`packages/backend/src/test/runner.js`) — a `test/<name>.js` file is invisible, and the run reports zero tests with no error ([#481](https://github.com/Omega-JS-Stack/omega/issues/481)) | Rename every ported test file to `<name>.test.js`; a suite left on the old spelling goes silently dark |
68
+ | Marketing prune cron | Ran daily unless `marketing.prune.enabled: false` — a brand with NO `marketing.prune` block was pruning | ON by default again: the config schema's `marketing.prune.enabled: true` default rides the resolved chain ([#478](https://github.com/Omega-JS-Stack/omega/issues/478), superseding #422's opt-in interlude); only an explicit `false` stops it, and the per-provider safety floors bound what a run may delete | A brand that must NOT prune sets `marketing.prune.enabled: false`; everyone else does nothing — matching the legacy default |
69
+ | Firestore rules file | `firestore.rules` carried a framework-managed `// ========== OMEGA Rules (vX.Y.Z) ==========` block that `omega setup` regenerated wholesale on every run | `firestore.rules` is the brand's SOURCE — pure rules, no managed block. The framework half ships inside `@omega.js/backend` and compiles in; `firebase.json` points the emulator and `firebase deploy` at the generated `dist/firestore.rules` ([#255](https://github.com/Omega-JS-Stack/omega/issues/255)) | `npx omega migrate:rules` converts the file ONCE (custom region preserved) and retargets `firebase.json`. It is a RUN-ALONE verb, never a side effect of another one ([#522](https://github.com/Omega-JS-Stack/omega/issues/522)) — while `firebase.json` still names your own rules file, the verbs report the deferral and change nothing, because adopting the compiled artifact changes what the LIVE project enforces (see the two rows below). But per-field protection a legacy BEM brand added by HAND-EDITING the managed block is not carried over, because the block is gone: re-declare those keys in your own `match /users/{uid}` block, which merges into the framework's (row below). Keys are TOP-LEVEL — protecting `xp.total` means listing `'xp'` |
70
+ | Firestore rules hooks (0.36.0 only) | The compiled model shipped with two brand HOOKS the framework called: `protectedFields()` (a list folded into the user write rule) and `canWriteUser()` (a condition ANDed into it), linted and re-seeded by the compiler | Merge-by-match: a brand match block whose path names a framework block's is MERGED into it, ANDing the brand's condition onto every op both declare. No hooks, no lint, no injection. Rules schema v2.0.0 → v3.0.0 ([#353](https://github.com/Omega-JS-Stack/omega/issues/353)) | `npx omega migrate:rules` migrates ONCE: a hook still carrying its shipped default body is deleted, a CUSTOMIZED one is kept as an ordinary function and reported — nothing calls it any more, so move what it enforced into `match /users/{uid} { allow create, update: if …; }` in your own file and delete it. `protectedFields()` becomes `allow create, update: if !isWritingAny(['xp', …]);` in that block |
71
+ | Firestore rules helpers | `belongsTo(identity)`, `emailVerified()`, `isWritingProtectedUserField()`, `authUid()`, `authEmail()`, `existingData()`, `incomingData()`; `existingData()` was `resource.data`, so every field helper ERRORED on a create (which denied the write) | One naming convention, `is*` for predicates and `get*` for values: `isUser(identity)`, `isEmailVerified()`, `isWritingFrameworkField()`, `getAuthUid()`, `getAuthEmail()`, `getExistingData()`, `getIncomingData()`, plus new `isWritingAny(fields)` and `isOwner()`. `getRoles()` keeps its name and stays the one helper that bills a document read. `getExistingData()` reads an absent document as `{}`, so the field helpers mean the same thing on create and update ([#353](https://github.com/Omega-JS-Stack/omega/issues/353)) | The `npx omega migrate:rules` migration renames every one of those calls inside your rules file. Three behaviour changes to know: a signed-in client may now CREATE its own `users/{uid}` document as long as it carries no framework-owned key (before, every client create was denied by the error); `isUser()`'s EMAIL arm now requires a verified token, so an unverified signup claiming an address no longer matches a document keyed by it (the uid arm is unchanged); and `plan` came off the framework's protected key list — nothing in the stack reads or writes `users/{uid}.plan`, and a brand that still stores one protects it in its own merged block |
72
+ | `isEmailVerified()` source of truth | Read the STORED `users/{uid}.verifications.email` through `getVerifications()`, at one billed document read per call | Reads the AUTH TOKEN: `request.auth != null && request.auth.token.get('email_verified', false) == true`. `getVerifications()` is REMOVED, and `verifications` joins the framework-owned key list ([#353](https://github.com/Omega-JS-Stack/omega/issues/353)) | Nothing to rename — the helper keeps its name and gets honest: nothing in the stack has ever WRITTEN `verifications`, so the old gate was satisfiable only by a client planting the field on its own user document. Rules of yours that called `getVerifications()` must stop (it is gone), and a client write to `users/{uid}.verifications` is denied from now on |
73
+ | Framework `match /users/{uid}` ops | `allow read` + `allow write` — and `write` covers delete, where `request.resource` is null, so the field guard ERRORED and an owner deleting their own user document was denied by that error | `allow read` + `allow create, update`. Delete falls through to the admin catch-all and is denied by the RULE ([#353](https://github.com/Omega-JS-Stack/omega/issues/353)) | Only if you TIGHTEN that block from your own `firestore.rules`: ops pair by NAME, so change your `allow write:` to `allow create, update:` or your condition appends as a widening instead of ANDing on. The compiler reports the mismatch loudly rather than letting it look like a tightening |
74
+ | Pre-family markers | Four marker shapes with no common grammar: the hand-written `{{ backend-manager }}` placeholder (`firestore.rules`, `database.rules.json`), the `# BEM>>>` … `# <<<BEM` block in `.gitignore`, and the `///---backend-manager---///` … `///---------end---------///` rules block (plus its short-lived `///---omega---///` OMEGA-era flavor) | ONE family grammar, `<comment> ========== <Label> ==========`, everywhere a marker is machine-parsed. Every evergreen verb reads ONLY the family, so a pre-family file converges at no verb | `npx omega migrate:markers` converts all four ONCE, run alone: the `# BEM>>>` block is deleted whole (its lines were framework-owned and the Default zone re-writes them), `firestore.rules` lands on the compiled-rules source shape with your own rules kept, and `database.rules.json`'s block is re-cut to the family markers with its rules untouched. The target checks DEFER on a pre-family file — reported as a warning (not a failure, so nothing auto-fixes it), naming this verb, tree untouched — and the compiler REFUSES to write `dist/firestore.rules` from a source still carrying one, rather than splicing the marker in as if it were a rule ([#40](https://github.com/Omega-JS-Stack/omega/issues/40)) |
75
+ | Storage rules scaffold | `templates/storage.rules` granted the whole bucket to any signed-in user (`allow read, write: if request.auth!=null`) | Deny-all (`allow read, write: if false`) — a brand that serves files from Storage opts in per path it actually exposes ([#278](https://github.com/Omega-JS-Stack/omega/issues/278)) | Nothing is rewritten in place: the scaffold writes `storage.rules` only when the file is missing, so a migrated brand keeps whatever it arrived with. Read that file once and narrow it by hand — a bucket-wide grant carried over from the old scaffold stays live until you do |
76
+
77
+ ## `electron-manager` → `@omega.js/desktop`
78
+
79
+ | Contract | Old form | New form | Manual migration step |
80
+ |---|---|---|---|
81
+ | Package + entries | `require('electron-manager/main' \| '/preload' \| '/renderer' \| '/gulp')` (the scaffold README named a `/test/assert` entry the package never exported) | `require('@omega.js/desktop/main' \| '/preload' \| '/renderer' \| '/gulp')`; no assert entry: a test's `run`/`inspect` receives `expect` on its context ([#812](https://github.com/Omega-JS-Stack/omega/issues/812)) | Swap the dependency and every require path in `src/main.js`, `src/preload.js`, the renderer components, and `gulpfile.js`; in the test files, drop the assert require and use `ctx.expect` |
82
+ | CLI | `em` / `electron-manager` / `mgr` bins | `omega` / `omg` / `mgr` | Replace the bin name in npm scripts and workflows |
83
+ | Config | `config/electron-manager.json` | `config/omega.json5` | Key-by-key table in [config.md](config.md#electron-manager-configelectron-managerjson--configomegajson5--done-checkpoint-18) — note the per-OS move: `targets.mac` / `.win` / `.linux` → `targets.desktop.platforms.mac` / `.win` / `.linux` |
84
+ | Windows signing strategy | `config/electron-manager.json` → `signing.windows.strategy` | `config/omega.json5` → `targets.desktop.platforms.win.signing.strategy` | Move the key; the `.env` credential slots (`CSC_LINK`, signtool path, cloud-provider creds) keep their names |
85
+ | Windows runner logon account | `WIN_RUNNER_LOGON_ACCOUNT` + `WIN_RUNNER_LOGON_PASSWORD`, plus a DPAPI-encrypted `runner-logon.json` and an `omega runner set-credentials` subcommand, named the account the runner's Windows service ran as | GONE ([#337](https://github.com/Omega-JS-Stack/omega/issues/337)). The signing runner is a Startup-folder `.cmd` in the logged-in user's own session — there is no service and no scheduled task, so there is no second account to name. Nothing read either key | **On the signing box, by hand**: delete `WIN_RUNNER_LOGON_ACCOUNT` and `WIN_RUNNER_LOGON_PASSWORD` from its `.env` (and from GitHub Actions secrets if they were ever pushed), and delete `%APPDATA%\@omega.js/desktop\runner-logon.json`. Nothing replaces them |
86
+ | Windows EV cert reference | `WIN_EV_TOKEN_PATH`, with `WIN_CSC_LINK` silently accepted as a fallback alias by the signer and by `validate-certs` | `WIN_EV_TOKEN_PATH` only — the env-schema name, and the ONE name ([#337](https://github.com/Omega-JS-Stack/omega/issues/337)). Nothing reads `WIN_CSC_LINK`, so a box that only sets it now fails loudly naming `WIN_EV_TOKEN_PATH` | **On the signing box, by hand**: rename the key in its `.env` (and in any GitHub Actions secret feeding the `windows-sign` job) from `WIN_CSC_LINK` to `WIN_EV_TOKEN_PATH`. The value — a SHA1 thumbprint or a `.pfx` path — is unchanged. electron-builder's own `WIN_CSC_LINK` is a different variable and is untouched |
87
+ | Windows signing runner install | `em runner`: home `%LOCALAPPDATA%\em-runner` (`C:\actions-runners` before v1.2.36), Startup shortcuts and GitHub-side runners named `em-runner-<host>-<org>` | `omega runner`: home `%LOCALAPPDATA%\omega-runner`, shortcuts and runners named `omega-runner-<host>-<org>` ([#337](https://github.com/Omega-JS-Stack/omega/issues/337)). The legacy install is detected: `status` names it, `uninstall` deregisters and removes it, `install` tears it down first | **On the signing box**: `npx omega runner install` with `GH_TOKEN` set (`admin:org`). Nothing to move by hand — the old registrations come off GitHub through their own `config.cmd remove`; if one does not, the summary names it and a later `uninstall` retries |
88
+ | Config validation | EM's local validation util | The shared `@omega.js/config` schema, at boot and in the audit task | Nothing to move — but expect boot to report schema findings a legacy config silently carried, and fix them |
89
+ | Preload global | `contextBridge.exposeInMainWorld('em', …)` → `window.em` | `window.desktop` | Rename every `window.em.*` call in renderer code |
90
+ | Theme controls | `data-em-theme-set="system\|light\|dark"` | `data-omega-theme-set="system\|light\|dark"` | Rename the attribute in every view |
91
+ | Client bridge | `web-manager-bridge.js`; `manager.webManager` in renderer entries | `client-bridge.js` + `@omega.js/client`; `manager.omega` | Rename the destructured property (`const { logger, ipc, storage, omega } = manager`) and every `webManager.` call to `omega.` |
92
+ | Environment | `BACKEND_MANAGER_KEY` in the app's `.env` | `OMEGA_ADMIN_KEY`, resolved through the `.env` cascade (shell > local > brand root > company) | Rename the key; in a brand monorepo put the value at the brand root and leave the target's placeholder commented |
93
+ | Bundler overrides | `config.em.webpack.externals` — an array of extra module names the desktop webpack build marked `commonjs2` external | GONE ([#737](https://github.com/Omega-JS-Stack/omega/issues/737)): the bundler is esbuild and there is no consumer-facing override key. The externals set is the framework's native-module list plus what the consumer's own `package.json` declares from it | Delete the key. A consumer with a genuinely native module the list does not name raises it upstream — the list lives in `src/gulp/tasks/bundle.js` (`nativeExternals`) and grows there, so every brand gets the fix |
94
+ | Gulp task name | `webpack` — `npm run gulp -- webpack`, and `[@omega.js/desktop:webpack]` in the logs | `bundle` — `npm run gulp -- bundle`, `[@omega.js/desktop:bundle]` ([#737](https://github.com/Omega-JS-Stack/omega/issues/737)) | Nothing for a normal consumer: the `build` / `package` / `publish` verbs are unchanged and nobody's npm scripts name the sub-task. Rename it in anything that invokes the gulp task directly, or greps `build.log` for the old tag |
95
+ | Downloads mirror | `targets.desktop.downloads: { enabled, owner, repo, tag }`: a second public repo (`download-server`) holding one `installer` tag of fixed-name copies of every artifact, written by the `mirror-downloads` gulp task and by `finalize-release` | GONE ([#799](https://github.com/Omega-JS-Stack/omega/issues/799)): ONE public releases repo per brand, `<brand.id>-releases` by default, whose assets already carry no version ([#620](https://github.com/Omega-JS-Stack/omega/issues/620)), so `/releases/latest/download/<asset>` is the permanent link the mirror existed to provide. All four `downloads.*` keys are retired keys a carrying config now fails validation on | Delete the `downloads` block. Nothing replaces it: the site already links the releases repo, and `targets.desktop.releases` (both keys optional) addresses it. If a published link points at the old mirror repo, redirect it or re-point it at `https://github.com/<owner>/<brand.id>-releases/releases/latest/download/<asset>` |
96
+ | Startup config | `startup.openAtLogin: true\|false` — a bare boolean | `startup: { openAtLogin: { enabled, mode } }` (`startup.mode` is a SEPARATE knob: the user-launch mode) | Rewrite the boolean as the object under the same `openAtLogin` key. The boolean is TEMPORARILY still read: the config schema declares only `startup.mode`, so it cannot reject the old shape yet — the acceptance leg retires with the schema entry ([#148](https://github.com/Omega-JS-Stack/omega/issues/148) trail) |
97
+
98
+ ## `browser-extension-manager` → `@omega.js/extension`
99
+
100
+ | Contract | Old form | New form | Manual migration step |
101
+ |---|---|---|---|
102
+ | Package + entries | `require('browser-extension-manager/build')` (the scaffold README named a `/test/assert` entry the package never exported) | `require('@omega.js/extension/build')`; no assert entry: a test's `run`/`inspect` receives `expect` on its context ([#812](https://github.com/Omega-JS-Stack/omega/issues/812)) | Swap the dependency and the require paths in `hooks/build/pre.js`, `hooks/build/post.js`, and `gulpfile.js`; in the tests, drop the assert require and use `ctx.expect` |
103
+ | CLI | `xm` / `bxm` / `ext` / `browser-extension-manager` / `mgr` bins | `omega` / `omg` / `mgr` | Replace the bin name in npm scripts and workflows |
104
+ | Config | `config/browser-extension-manager.json` | `config/omega.json5` (`targets.extension: {}` — key presence enables the target) | Key-by-key table in [config.md](config.md#browser-extension-manager-configbrowser-extension-managerjson--configomegajson5--done-checkpoint-20) |
105
+ | Analytics secret | `analytics.providers.google.secret` in the config file | `.env` → `GOOGLE_ANALYTICS_SECRET` (the loader hard-fails secret-shaped config keys) | Move the value to `.env`; the build snapshot bakes it exactly as before |
106
+ | Runtime singleton | `import webManager from 'web-manager'`; `manager.webManager` | `import omega from '@omega.js/client'`; `manager.omega` | Swap the import and rename the property in every context (background, popup, options, sidepanel, content scripts) |
107
+ | DOM bindings | `data-wm-bind` | `data-omega-bind` | Rename the attribute in every view |
108
+ | Cross-context messages | `{ command: 'bxm:syncAuth' }`, `{ command: 'bxm:signOut' }` | `{ command: 'omega:syncAuth' }`, `{ command: 'omega:signOut' }` | Rename in any custom `runtime.onMessage` handler or sender the extension ships |
109
+ | Bundler | webpack 5 + `babel-loader` + `@babel/preset-env`, with per-lane code splitting (`*.chunk.<hash>.js` plus `.LICENSE.txt` sidecars beside every bundle) | esbuild through @omega.js/devkit's `bundle()` wrapper ([#738](https://github.com/Omega-JS-Stack/omega/issues/738)). No consumer-facing bundler override key existed and none was added; there are no chunks and no license sidecars — every entry is ONE self-contained iife — and the syntax floor is esbuild's `target`, `chrome88, firefox91` (the MV3 minimums), not a browserslist guess | Nothing in a normal project: entry filenames (`assets/js/components/<name>.bundle.js`) are unchanged, and the manifest / views ask for the same paths. Two things to check: anything that referenced a chunk file BY NAME (nothing generated does), and any code that imported a package @omega.js/extension merely carries TRANSITIVELY — only the framework's DECLARED dependencies resolve from the framework now, so declare that package yourself or raise it upstream ([#87](https://github.com/Omega-JS-Stack/omega/issues/87)) |
110
+ | Gulp task name | `webpack` — `npm run gulp -- webpack`, and `[@omega.js/extension:webpack]` in the logs | `bundle` — `npm run gulp -- bundle`, `[@omega.js/extension:bundle]` ([#738](https://github.com/Omega-JS-Stack/omega/issues/738)) | Nothing for a normal consumer: the `build` / `publish` verbs are unchanged and nobody's npm scripts name the sub-task. Rename it in anything that invokes the gulp task directly, or greps `build.log` for the old tag |
111
+ | Build snapshot delivery | `packaged/<browser>/raw/build.js` — a JSONP file the service worker loaded with `importScripts('/build.js')` and every page loaded with a `<script src="/build.js">` tag the page template emitted — plus a `build.json` sidecar beside it | `OMEGA_BUILD_JSON`, baked into EVERY emitted bundle by the bundle task's esbuild `define` + `banner` ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)) — the same mechanism @omega.js/desktop uses. Neither file is written any more | Nothing to do if you read `window.OMEGA_BUILD_JSON` / `self.OMEGA_BUILD_JSON` — those still answer, earlier than before. Delete any `<script src="/build.js">` tag from a page template you overrode and any `importScripts('/build.js')` from your own service-worker code; both now 404 in the packaged artifact. Tooling that read `packaged/<browser>/raw/build.json` reads the bake back out of a bundle instead (`readBakedBuildJson()` in `src/gulp/tasks/utils/build-json.js`) |
112
+
113
+ ## `web-manager` → `@omega.js/client`
114
+
115
+ | Contract | Old form | New form | Manual migration step |
116
+ |---|---|---|---|
117
+ | Package + import | `import webManager from 'web-manager'` (npm `web-manager`) | `import omega from '@omega.js/client'` — still a singleton default export | Swap the dependency and every import; the module API (`auth()`, `firestore()`, `bindings()`, `initialize(configuration)`) carries over unchanged |
118
+ | Global | `window.Manager` / a page-attached `webManager` | No window global — import the singleton wherever it is needed | Delete the window assignments and the code that reads them |
119
+ | DOM bindings | `data-wm-bind` | `data-omega-bind` | Codemod rule `client-markup` (templates, consumer JS, and section `.json` descriptors alike) |
120
+ | Sign-out hook | `.auth-signout-btn` class, hardcoded in the auth module | The generic `omega-signout` click trigger (`registerTrigger('signout', …)` → class `omega-signout`) | Codemod rule `client-markup` renames the class everywhere it appears; custom behavior registers its own trigger instead of patching auth |
121
+ | Device module | `webManager.usage()` | `omega.device()` | Rename the call sites (`usage.js` became `device.js` verbatim) |
122
+ | Configuration payload | The site emitted a `web_manager` block into `window.Configuration` | The `client` block, composed from `resolved.client` | Config-side rename — see the UJM row and [config.md](config.md); nothing dual-reads the old key |
123
+ | Version-check manifest | The client probed `/build.json` then `/@output/build/build.json`, reading `data.timestamp` OR `data['npm-build'].timestamp` | One fetch of the site's `build.json` — mounted under the page's `data-omega-path-prefix` stamp on a site served from a URL path ([#364](https://github.com/Omega-JS-Stack/omega/issues/364)), `/build.json` at the domain root — and one read of `data.timestamp` (the omega web build emits exactly this) | Nothing to do on a migrated site. A site still serving the old path or the `npm-build` wrapper logs "No timestamp found in build.json" and never auto-reloads on a new deploy |
124
+
125
+ ## `jekyll-uj-powertools` → `@omega.js/template-kit`
126
+
127
+ | Contract | Old form | New form | Manual migration step |
128
+ |---|---|---|---|
129
+ | Delivery | Ruby gem `jekyll-uj-powertools` in the `Gemfile` | JS package `@omega.js/template-kit`, vendored into `@omega.js/web` | Remove the gem line (the whole `Gemfile` goes — see the UJM engine row); nothing to install, the filters and tags are registered by the web engine |
130
+ | Tag + filter names | `uj_icon`, `uj_image`, `uj_readtime`, `uj_liquify`, … (`iftruthy`, `iffalsy`, `iffile`, `urlmatches` were already unprefixed) | `omega_image`, `omega_readtime`, `omega_liquify`, … — the four unprefixed names are unchanged, and `uj_icon` becomes MARKUP rather than a renamed tag (the icons row above) | Codemod rules `legacy-prefix` (every registered name) and `icon-tag-markup` (the icon tag); there are NO aliases, so a missed `uj_*` is an undefined tag/filter. Inventory: [docs/web/template-kit.md](../web/template-kit.md) |
131
+ | Site namespace | `site.uj.*` (`cache_breaker`, `date.year`, `date.iso`, `placeholder.src`); Jekyll's `site.time` | `site.omega.*`; the build stamp is `site.omega.date.iso`. `site.time` stays, set by the engine to the same instant ([#613](https://github.com/Omega-JS-Stack/omega/issues/613)) | Codemod rule `legacy-prefix` handles `site.uj`; `site.time` needs nothing |
132
+ | Markup hooks | `uj-password-show`, `uj-password-hide`, `uj-language-flag`, `uj-language-dropdown`, `uj-schema-*`, `data-uj-no-translate` | `omega-password-show`, `omega-password-hide`, `omega-language-flag`, `omega-language-dropdown`, `omega-schema-*`, `data-omega-no-translate` | Codemod rule `legacy-prefix` renames all of them; check hand-written CSS/JS that selects on the old names |
133
+ | Ruby generators + hooks | `variable_resolver.rb`, `blog-taxonomy.rb`, `inject-properties.rb`, `dynamic-pages.rb`, `limit-collections.rb`, `markdown-images.rb`, `parallel-build.rb` | Engine features of `@omega.js/web` (frontmatter Liquid, collections + pagination, the `resolved` cascade) — not template functions | Nothing to migrate: they were never consumer-callable. Which of them exist today and which never got ported is [#78](https://github.com/Omega-JS-Stack/omega/issues/78)'s table, not this register |
134
+
135
+ ## `omega-manager` → `@omega.js/manager`
136
+
137
+ | Contract | Old form | New form | Manual migration step |
138
+ |---|---|---|---|
139
+ | Brand config home | Central: `omega-manager/.brands/<id>/config.json` (one repo describing every brand) | The brand's OWN repo: `config/omega.json5` | Convert the brand's config file into the brand repo using the tables in [config.md](config.md) (shared sections at the top level, per-surface settings under `targets.<type>`), then retire the `.brands/<id>/` entry |
140
+ | Enabled targets | `targets: ['website', 'backend']` — an array | `targets: { web: {}, backend: {} }` — an object where key PRESENCE enables the target | Rewrite the array as object keys; note the rename `website` → `web` |
141
+ | Repo topology | One clone per surface, located by `local.folder` + `github.orgMain` / `orgWebsite` | ONE brand monorepo: npm workspaces, `targets/<target>/` per enabled target ([docs/manager/brand.md](../manager/brand.md)) | Merge the per-surface repos into one brand repo as `targets/website`, `targets/backend`, `targets/desktop`, `targets/extension`; the brand root holds `config/omega.json5`, `.env`, `assets/`, and the workspace `package.json` |
142
+ | Durable state | `omega-manager/.output/<id>/state.json` | No durable-state CACHE at all — every provisioned fact lands in the brand's `config/omega.json5`, every secret in its `.env` ([#434](https://github.com/Omega-JS-Stack/omega/issues/434)); the brand's own `.omega/state.json` keeps only per-machine records (the deploy stamps, [#479](https://github.com/Omega-JS-Stack/omega/issues/479)) | Nothing to copy: a manage run resolves the ids from the platform and writes them into their real homes. Never commit `.omega/` |
143
+ | Service names (the last eight) | Services still carried their PROVIDER's name: `github` / `cloudflare` / `recaptcha` / `search-console` / `adsense` / `slapform` / `chatsy` / `replyify` ([#418](https://github.com/Omega-JS-Stack/omega/issues/418)) | Every service now matches its config ROLE key: `repo` / `edge` / `captcha` / `search` / `advertising` / `forms` / `chat` / `email`. The provider KEYS underneath are unchanged (`repo.providers.github`, `edge.providers.cloudflare`, `captcha.providers.recaptcha`, `search.providers.searchConsole`, `advertising.providers.adsense`, `forms.providers.slapform`, `inbound.chat.providers.chatsy`, `inbound.email.providers.replyify`) — the service is the role, the provider is a value | Update `--service=<name>` in any script or cron (`--service=cloudflare` is now `--service=edge`, …) and expect the walk's `[NAME]` log tag to change with it |
144
+ | AGENTS.md healing | Line 1 imported `@node_modules/@omega.js/manager/AGENTS.md`; the walk rewrote that target and scrubbed the cp244 marker/skeleton lines | Line 1 imports `@node_modules/@omega.js/AGENTS.md`; the walk only prepends a missing current import, never rewrites retired lines | Delete the old import line and any cp244 marker/skeleton line by hand — left in place they survive as consumer content (a dangling duplicate import) |
145
+ | package.json script healing | Script values leading with the `omega-manager` bin token were healed to `omega` on every walk | Missing `manage: "omega"` and `deploy: "omega deploy"` scripts are minted, and exactly one value migrates: the legacy `start: "omega"` becomes `start: "omega dev"` ([#227](https://github.com/Omega-JS-Stack/omega/issues/227)); every other script value, the `omega-manager` token included, is never rewritten | Replace the leading `omega-manager` token with `omega` by hand (arguments unchanged) — unedited, the script fails at run time with command-not-found: no `omega-manager` bin exists (Ian retired the vestigial shim, 2026-08-05) |
146
+ | Secrets store | `omega-manager/.output/<id>/secrets/*.json` | The brand's `.omega/secrets/*` plus the `.env` cascade (local → brand → company) | Move the files into the brand's `.omega/secrets/`; secret VALUES belong in `.env`, never in `omega.json5`. The Google OAuth client changed shape (`oauth.json` `{ googleClientId, googleClientSecret }` → `google-oauth.json` `{ clientId, clientSecret }`): carry the legacy file in and `npx omega onboard` converts it ONCE and removes it ([#501](https://github.com/Omega-JS-Stack/omega/issues/501)) — nothing dual-reads the old name |
147
+ | Brand assets | `omega-manager/.brands/<id>/assets/` | The brand repo's `assets/` (logo sources, templates); derived variants land in the gitignored `.omega/assets/` | Copy the source assets into the brand repo; a manage cycle regenerates the derived set |
148
+ | Entry point | `npm start` inside the omega-manager repo, all brands at once, `--brand <id>` to narrow | `npx omega` (or `npm run manage`) inside the BRAND root — one brand, always; `--service=<name>` still narrows to one service | Run the manage cycle from the brand repo; there is no cross-brand run |
149
+ | Retired flags | `--bump`, `--build`, `--sync`, `--deploy`, `--exec`, `--dirty`, `--force-recreate` | Gone with the multi-repo-clone model | Use the per-target verbs instead: `omega deploy` at the brand root fans out (backend → web → extension/desktop), `omega update` handles version bumps. The drop calls are recorded in [#78](https://github.com/Omega-JS-Stack/omega/issues/78) |
150
+
151
+ ## Cross-cutting
152
+
153
+ | Contract | Old form | New form | Manual migration step |
154
+ |---|---|---|---|
155
+ | Config keys (every framework) | Per-framework key names and homes | The omega.json5 schema | **Do not re-derive them here** — the key-by-key mapping tables are in [config.md](config.md#migration--legacy-configs--omegajson5), one table per legacy framework, and they are the SSOT |
156
+ | Config file | One JSON file per framework (`ultimate-jekyll-manager.json`, `backend-manager-config.json`, `electron-manager.json`, `browser-extension-manager.json`) | ONE format everywhere: `config/omega.json5`, merged `defaults ← company ← brand shared ← brand targets.<type> ← local shared ← local targets.<type>` | Convert once, delete the old file. In a brand monorepo the targets carry no config of their own — the brand root's file owns everything |
157
+ | Retired key names | Renamed keys used to validate clean and their contents vanished | Retired names FAIL validation wherever they sit, naming their replacement (`web_manager` → `client`, `firebaseConfig` → `cloud`), plus path-based retirements (`slapform` → `forms.providers.slapform`, `cloudflare` → `edge.providers.cloudflare`, …) | Fix what the validator names; the full retired list is [config.md](config.md#migration--legacy-configs--omegajson5) |
158
+ | Secrets | Secret values sat in the framework config files | Config hard-fails secret-shaped keys; secrets live in `.env`, resolved through the cascade shell > local > brand root > company | Move every secret out of config into `.env`, brand-root first so the targets inherit it |
159
+ | CLI bins | EVERY legacy framework shipped a `mgr` bin (plus `uj`/`bm`/`em`/`bxm`), so in a multi-target repo whichever npm hoisted won | One context-aware dispatcher: `omega` / `omg` / `mgr`, all identical — the nearest `package.json` walking up from cwd names the framework whose CLI runs | Replace legacy bin names in npm scripts and CI; run the verb from the target dir that owns it |
160
+ | CLI default | A bare `omega` / `mgr` at a brand root RAN the whole service walk (omega-manager's default command) | Every verb is named: `omega manage` is the walk (one name, no alias), `omega dev` the local stack, `omega deploy` the publish. A bare `omega` prints help and touches nothing ([#229](https://github.com/Omega-JS-Stack/omega/issues/229)) | Replace bare `omega`/`mgr` invocations with `omega manage` in scripts, cron, and CI. A brand still carrying `manage: 'omega'` must be walked ONCE by hand — `npx omega manage` — because its own `npm run manage` would print help; that walk heals the script to `omega manage` |
161
+ | Per-target setup | `omega setup`, run BY HAND in every target (`cd targets/<dir> && npx omega setup`), and the default command a bare `omega` ran | The command is gone. Its LOCAL half — node check, defaults scaffold, `package.json` scripts sync, peer-dependency check, locality check — is `ensureTarget()`, which every verb (`dev`/`build`/`test`/`deploy`) runs first, idempotently and offline. Its NETWORK half — secret publication, cert validation, repo provisioning, framework freshness — is a precheck inside `omega deploy`, opted out with `--no-secrets` on all three frameworks (desktop's `--quick` is gone). A bare `omega` prints help ([#675](https://github.com/Omega-JS-Stack/omega/issues/675)) | Delete `npx omega setup` from npm scripts, CI workflows and runbooks — the verb beside it already does it. A scaffolded workflow's `npx omega setup && npm run build` becomes `npm run build`. Extension consumers on a pre-2.0.0 hook layout run the one-time `npx omega migrate` once |
162
+ | Package script spelling | Backend, desktop and extension wrote `npx omega <verb>` into every framework-owned target script (web already spelled it bare) | Bare `omega <verb>` on all four frameworks ([#748](https://github.com/Omega-JS-Stack/omega/issues/748)) — npm puts `node_modules/.bin` on the path inside a script, so the prefix bought nothing there; `npx omega` stays canonical for docs and the terminal | Nothing by hand: the next verb's `ensureTarget()` rewrites every framework-owned key in place and a second run is a no-op — commit the one-line diff. A script key the framework never declares is yours and is never touched, and `npx omega <verb>` keeps working wherever you spell it yourself |
163
+ | Publish | Per-framework release scripts and `npu sync` | `omega deploy` on every target — deliberate, never triggered by a push; at a brand root it fans out (backend → web → extension/desktop) | Replace publish scripts with `omega deploy`; contract in [deploys.md](deploys.md) |
164
+ | Dependency updates | Framework self-update + peer-dependency auto-install at setup | The explicit `omega update` verb (report first, `--apply` installs, majors opt-in). The peer-dependency install still rides every verb's `ensureTarget()` — a satisfied target installs nothing — and the framework self-update moved to the `omega deploy` precheck ([#675](https://github.com/Omega-JS-Stack/omega/issues/675)) | Run it deliberately — [updates.md](updates.md) |
165
+ | Environment prefixes | `BACKEND_MANAGER_*`, `BEM_*`, framework-specific names | `OMEGA_*` — except a THIRD-PARTY credential, which keeps the vendor's own name (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `STRIPE_SECRET_KEY`); `OMEGA_*` is for the keys OMEGA itself mints or owns | Rename in `.env`, CI secrets, and every reader. `@omega.js/config`'s env schema is the current list — the brand root's `.env` stub renders from it |
166
+ | Default/Custom file markers | The marker grammar in `_.gitignore`, `AGENTS.md` (no framework scaffolds a target `.env` any more) | **UNCHANGED — this is not legacy.** The Default/Custom marker grammar is the live defaults-engine mechanism that merges framework-owned lines into consumer-owned files | Leave the markers alone in migrated files; every verb's `ensureTarget()` rewrites the Default block and preserves everything under Custom |
167
+
168
+ ## One provider shape — `role.providers.<provider>` ([#425](https://github.com/Omega-JS-Stack/omega/issues/425))
169
+
170
+ Not a legacy→OMEGA row: this is an OMEGA-internal config rename, made
171
+ deliberately before the first npm publish closes the window. Config sections
172
+ carried four shapes for the same idea — a `providers` block, a singular pick
173
+ (`provider: 'sentry'`), a bare vendor key (`certificates.apple`) and a fourth
174
+ word (`payment.processors`) — so a consumer had to memorise which section used
175
+ which. Every role now names its vendors ONE way: `role.providers.<provider>`,
176
+ where key PRESENCE is the pick and `false` is the deliberate off switch. The
177
+ null = unset / false = disabled tri-state is unchanged; it just moved into the
178
+ key. `cloud` is the ratified exception (`cloud.provider` + `cloud.config` — its
179
+ discriminator is read by 34 runtime files across every framework's bootstrap
180
+ and already delivers no-rename-on-a-second-provider).
181
+
182
+ Every row below is enforced: the old path FAILS validation naming its
183
+ replacement, so nothing is silently lost. There is no dual-read.
184
+
185
+ | Contract | Old form | New form | Manual migration step |
186
+ |---|---|---|---|
187
+ | Payment providers | `payment.processors.{stripe,paypal,chargebee,coinbase}` | `payment.providers.{stripe,paypal,chargebee,coinbase}` — contents identical | Rename the one key in `config/omega.json5`. The singular `processor` followed one issue later — the whole word is `provider` now, stored data included ([#428](#one-word--provider-everywhere-428) below) |
188
+ | Apple signing | `certificates.apple.{bundleIdPrefix,capabilities,profiles,certificates}` | `certificates.providers.apple.{…}` — contents identical | Nest the `apple` block one level under `providers`. Windows signing will sit beside it rather than adding a second bare vendor key. Nothing on disk moves: the signing tree stays `{companyRoot\|\|brandRoot}/.omega/certificates/apple/` |
189
+ | Domain registrar | `domain.provider: 'namecheap' \| 'squarespace' \| null` | `domain.providers.<registrar>` — e.g. `providers: { namecheap: {} }` | Replace the string with a keyed entry. **No entry = none chosen** and the domain service skips, exactly what `null` meant |
190
+ | Mailbox provider | `domain.email.provider: 'cloudflare' \| 'squarespace' \| 'privateemail' \| null` | `domain.email.providers.<provider>` | Same edit one level down. `domain.email.forwarding` stays role-level — it is provider-agnostic |
191
+ | Translation engine | `translation.provider: 'claude' \| 'chatgpt'` | `translation.providers.<name>` — `{ claude: {} }` or `{ chatgpt: {} }` | Replace the string with a keyed entry. An absent block still means `claude` (the no-API-key default), so a brand that never set `provider` needs no edit. `translation.model` stays role-level — it overrides whichever engine is chosen |
192
+ | Devlog writer | `devlog.provider: 'ghostii'` plus its settings flat on `devlog` (`lookbackDays`, `orgs`, `excludeRepos`, `excludeCommits`, `excludeTopics`, `includePrivate`, `postPath`, `destinations`, `overrides`) | `devlog.providers.ghostii.{lookbackDays,orgs,excludeRepos,excludeCommits,excludeTopics,includePrivate,postPath,destinations,overrides}` | Move the nine provider-hung keys inside `providers.ghostii` and drop the `provider` string (the key IS the writer). `devlog.enabled` stays role-level — it is the pipeline's switch, not the writer's |
193
+ | Error monitoring | `monitoring.provider: 'sentry'` plus its settings flat on `monitoring` (`org`, `dsn`, `environment`, `sampleRate`, `tracesSampleRate`, `scrubEmail`, `attachScreenshot`, `bundlePatterns`) | `monitoring.providers.sentry.{org,dsn,environment,sampleRate,tracesSampleRate,scrubEmail,attachScreenshot,bundlePatterns}` | Move the eight knobs inside `providers.sentry` and drop the `provider` string (the key IS the monitor). `monitoring.enabled` stays role-level. **Per-surface DSNs move too**: `targets.<type>.monitoring.dsn` → `targets.<type>.monitoring.providers.sentry.dsn` — the manager's `monitoring/dsn` operation writes the new path, so a rerun re-lands them. DSN presence is still the runtime enable signal; nothing on Sentry's side changes (same projects, same keys, same release tags) |
194
+ | Email marketing | `marketing.campaigns.provider: 'sendgrid'` + `marketing.campaigns.listId` | `marketing.campaigns.providers.sendgrid.listId` | Nest `listId` under `providers.sendgrid` and drop the `provider` string. `marketing.campaigns.enabled` stays role-level. The list itself is untouched — the id is the same SendGrid UUID, and the campaigns service writes the new path on its next run |
195
+ | Newsletter | `marketing.newsletter.provider: 'beehiiv'` + `marketing.newsletter.publicationId` | `marketing.newsletter.providers.beehiiv.publicationId` | Nest `publicationId` under `providers.beehiiv` and drop the `provider` string. **`marketing.newsletter.enabled` AND `marketing.newsletter.content` stay role-level** — `content` (sources, categories, tone, template, theme, sponsorships) configures @omega.js/backend's newsletter GENERATOR, not Beehiiv, so it does not move. Beehiiv holds no copy of the id: inbound webhooks are matched against config, so there is no data migration |
196
+ | AI blog | `blog.provider: 'ghostii'` | `blog.providers.ghostii` (key presence chooses the writer) | Replace the string with an (empty) `providers.ghostii` entry, or omit the block — an absent `providers` still defaults to ghostii. `blog.enabled` and `blog.content` stay role-level (content is pipeline config) |
197
+
198
+ Per-target overrides convert on the same terms: a `targets.<type>` block
199
+ carrying any of these keys is resolved at the top level, so it fails validation
200
+ with the same message and takes the same edit.
201
+
202
+ Two words the brand files carried but nothing read are simply gone with the
203
+ edit: `marketing.campaigns.platform` / `marketing.newsletter.platform` (a
204
+ legacy-BEM spelling of the same pick) BECOME the provider key. They are not in
205
+ the retired-path guard — they were already dead config, so nothing was lost
206
+ before or after — but leaving one behind now buys nothing.
207
+
208
+ ## One word — `provider` everywhere ([#428](https://github.com/Omega-JS-Stack/omega/issues/428))
209
+
210
+ The other half of #425, and the same window: #425 normalized the config SHAPE
211
+ and deliberately left the singular runtime word `processor` standing, which
212
+ meant a brand read `payment.providers` in config and wrote `processor` on every
213
+ document it produced. Ian's ruling (2026-08-21): consistency wins — ONE word,
214
+ `provider`, in code, on the API, and in stored data. Everything below changed
215
+ together; there is no dual-read on any of it.
216
+
217
+ Third-party vocabulary is untouched: where a field name belongs to Stripe,
218
+ PayPal, Chargebee or Chargeblast, it is still read verbatim (Chargeblast's
219
+ alert payload still sends `processor`, and the route normalizes it onto our
220
+ `provider` on the way in).
221
+
222
+ | Contract | Old form | New form | Manual migration step |
223
+ |---|---|---|---|
224
+ | Payments API param | `processor` on all eight payments routes (`intent`, `cancel`, `plan`, `portal`, `refund`, `uncancel`, `webhook`, `winback`) — body field or `?processor=` query, per route | `provider` in the same position | Rename it in every caller. A first-party brand gets this from the framework's own frontend; a CUSTOM caller (a mobile client, a server integration, a saved webhook URL at Stripe/PayPal/Chargebee) must be repointed — the webhook and dispute-alert routes read it from the QUERY STRING, so the URL registered at the provider changes too. `npx omega manage` rewrites the webhook URLs it owns; anything registered by hand is a by-hand edit. An unrenamed param is an `Unknown provider: undefined` 400, never a silent default |
225
+ | Checkout dev param | `?_dev_cardProcessor=test\|stripe\|chargebee` | `?_dev_cardProvider=…` | Update saved QA links and any script driving a dev checkout. The old param is ignored (the palette control reads only the new name) |
226
+ | Email merge field | `user_subscription_payment_processor` (path `subscription.payment.processor`) | `user_subscription_payment_provider` (path `subscription.payment.provider`) | Rename the token in every custom email template. An unrenamed token resolves to nothing — the merge-field table no longer declares the old name |
227
+ | Analytics param | `payment_processor` on every commerce event (`purchase`, `refund`, plan changes, trial events) | `payment_provider` | Rename it in saved GA4 explorations, custom dimensions, and any dashboard filtered on the old key. Registered GA4 custom dimensions are per-name: register `payment_provider` alongside, and historical rows keep the old name |
228
+ | Stored Firestore field | `processor` on `payments-orders` / `payments-intents` / `payments-webhooks`, `alert.processor` on `payments-disputes`, and `subscription.payment.processor` on `users` | `provider` / `alert.provider` / `subscription.payment.provider` | **This one has DATA.** Deploy the new backend, then run the backfill against the brand's Firestore: `npx omega manage --migration=payment-provider` audits (prints per-collection would-change counts and writes nothing), and `--migration=payment-provider --execute` performs it. Idempotent and re-runnable: a doc already on the new field is skipped, a doc carrying both keeps `provider` and drops the leftover. Order matters only in that the sweep is safe either side of the deploy — a doc written by the new code needs no fix |
229
+ | Firestore composite index | `subscription.payment.processor` ASC + `subscription.cancellation.pending` ASC (the PayPal expiry cron's query) | Same index on `subscription.payment.provider` | The next verb's `ensureTarget()` rewrites `firestore.indexes.json`; deploy indexes before the backfill so the cron's query has one when the data lands. The old index is orphaned — delete it in the Firebase console once nothing queries the old field |
230
+ | Payment module directory | `libraries/payment/processors/<vendor>.js`, the per-route `<route>/processors/` folders, and `libraries/load-processor.js` (`loadProcessor()`) | `libraries/payment/providers/`, `<route>/providers/`, `libraries/load-provider.js` (`loadProvider()`) | Only a brand that requires a framework payment module by path (a custom route, a custom cron) is affected: repoint the require and rename the call |
231
+ | Admin payment sub-handler dir | `<brandRoot>/payment-processors/<productId>.js` — the per-product handler `POST /omega/admin/payment` loads | `<brandRoot>/payment-providers/<productId>.js` | Rename the directory. Nothing else about the handler contract changed; a brand with no such directory (almost all) has nothing to do |
232
+
233
+ ## Two homes, not three — the `.omega/state.json` CACHE retired ([#434](https://github.com/Omega-JS-Stack/omega/issues/434))
234
+
235
+ omega-manager's three-bucket principle came across intact: user choices in
236
+ config, durable derived data in `.omega/state.json`, per-run transients in
237
+ `.omega/runs/`. The middle bucket did not earn its keep. Almost everything in
238
+ it was a CACHE of what each idempotent ensure re-reads from the platform on
239
+ every run anyway, and the handful of facts that were genuinely durable were
240
+ sitting in a gitignored per-machine file instead of the two homes the
241
+ frameworks actually read — so a fresh clone silently lost them, and a
242
+ brand's `omega.json5` could stay `null` for months next to a state file that
243
+ had the answer.
244
+
245
+ The rule now: **a provisioned fact lands in `config/omega.json5`, a secret
246
+ lands in the brand `.env`, and everything else re-derives.** The handler
247
+ return key `state` survives as the WITHIN-RUN carry (how the zone operation
248
+ hands its zone id to the operations after it) and is written to no file.
249
+
250
+ `.omega/runs/{ts}.json` is unchanged, and so is `.omega/state.json` — what
251
+ retired is its CONTENT, not the file. It lives on as the ONE per-machine
252
+ RECORD file, sectioned per fact kind
253
+ ([#479](https://github.com/Omega-JS-Stack/omega/issues/479)): its `deploy`
254
+ section is the record `@omega.js/devkit/deploy-record` writes on every
255
+ successful deploy verb, and future record kinds join it as sibling top-level
256
+ sections. The state-retirement migration owns only the service-keyed sections
257
+ it retires and leaves every record section alone; deploy-record owns only
258
+ `deploy` and passes every other section through verbatim, so an unmigrated
259
+ brand's records are read in place beside its config-shaped leftovers. The one
260
+ record move that still happens is the interim `.omega/deploys.json` the
261
+ records spent 0.45.0 in
262
+ ([#449](https://github.com/Omega-JS-Stack/omega/issues/449)): it needs no
263
+ migration step — deploy-record folds it into state.json on its first read or
264
+ write, says so in one line, and removes the old file.
265
+
266
+ | Contract | Old form | New form | Manual migration step |
267
+ |---|---|---|---|
268
+ | The state file | `.omega/state.json`, per-service keyed, written after every service | The service-keyed sections are gone. The migration moves each fact to its home and deletes the file — or trims it to its machine records (`deploy`, and any future record section) when the brand has any | **This one has DATA.** `npx omega manage --migration=state-retirement` audits (prints every planned move and writes nothing); `--migration=state-retirement --execute` performs it. Idempotent: the executed run leaves nothing to move, so a re-run is a clean no-op. It is the ONE `local: true` migration — no backend target, no service account, no Firestore |
269
+ | GA4 Measurement Protocol secrets | `analytics.streams.{target}.apiSecret` in state; the disperse service read it from there | `GOOGLE_ANALYTICS_SECRET_{TARGET}` in the brand `.env`, written by the analytics service; the schema's `deliverAs` composes each target's own `GOOGLE_ANALYTICS_SECRET` from it on every verb | The migration moves them. A brand with no state file re-resolves them from GA on the next `npx omega manage` |
270
+ | VAPID key pair | `cloud.cloudMessaging.{vapidPublicKey,vapidPrivateKey}` in state | Public half → `cloud.messaging.vapidKey` in omega.json5 (it ships to every browser); private half → `VAPID_PRIVATE_KEY` in the brand `.env` | The migration splits them. Lose them and there is no API to re-read them: an interactive run re-prompts for the pair from the Firebase console |
271
+ | Cloudflare zone id | `edge.zoneId` in state (read by nothing across runs) | `edge.providers.cloudflare.zone` in omega.json5 — the id `omega purge` targets from a build, where no Cloudflare token is in play | The migration moves it, and the edge service now keeps it current: the resolved id always wins over a stale hand-set one |
272
+ | Reconcile confirmations | `search.gaLinked`, `payment.{radarConfirmed,disputesConfirmed}`, `cloud.authentication.oauthRedirectsConfigured`, `captcha.domainsConfirmed` in state | `search.providers.searchConsole.gaLinked`, `payment.providers.stripe.{radarConfirmed,disputesConfirmed}`, `cloud.oauthRedirectsConfigured`, `captcha.providers.recaptcha.domainsConfirmed` in omega.json5 | The migration moves them. These are the only reconcile flags config keeps — each is a human console action with no read API on either side, so nothing can re-check it. Every OTHER flag was dropped: the ensure re-reads the platform |
273
+ | Everything else | Repo identity, Search Console property URL, GA stream ids and URIs, Stripe account id, provider product ids, Apple certificates/profiles/bundle id, Sentry project map, the Firebase SDK config, billing/firestore/database/storage/hosting/functions status, SendGrid + Beehiiv names | Not persisted anywhere | Nothing to do — every one of them is re-read from its platform (or re-derived from config) by the idempotent ensure that owned it. The provider product ids and the SDK config already had their config home; state was a duplicate |
274
+ | `@omega.js/manager/state` subpath export | `require('@omega.js/manager/state')` → `readState` / `writeState` / `statePath` / `STATE_DIR` | Removed. `writeRunOutput` moved to `src/lib/run-output.js` and is still exported from the package root | Delete the import. Nothing in the monorepo or any brand used it |
275
+
276
+ ## One vocabulary — a brand's surfaces live in `targets/` ([#443](https://github.com/Omega-JS-Stack/omega/issues/443))
277
+
278
+ Config has always called them `targets`; the folder they lived in said `apps/`,
279
+ and this monorepo's own `apps/` meant something else again — the in-repo test
280
+ BRANDS. Three meanings for two words. Ian's ruling (2026-08-21): one
281
+ vocabulary. A brand monorepo's surfaces live under `targets/`, and this
282
+ monorepo's brands live under `brands/`. Same behavior, new names — discovery,
283
+ scaffolding, the FILE_MAP semantics, disperse, the CI workflow templates and
284
+ every doc changed together, and nothing dual-reads `apps/`.
285
+
286
+ | Contract | Old form | New form | Manual migration step |
287
+ |---|---|---|---|
288
+ | Brand folder | `<brandRoot>/apps/<target>` — `apps/website`, `apps/backend`, `apps/desktop`, `apps/extension`, `apps/website-admin` | `<brandRoot>/targets/<target>` — the dir names inside are unchanged | **Run the migration ONCE, per brand**: `npx omega manage --migration=targets-rename --execute` (bare, without `--execute`, prints the plan and moves nothing), then `npm install` at the brand root. Nothing heals this inside a normal run — every other verb FAILS LOUD on the old shape and points here. Idempotent — a migrated brand re-runs as a no-op. A brand carrying BOTH folders is a half-done migration and fails loudly instead of guessing: merge them into `targets/` by hand, delete `apps/`, run again |
289
+ | Root workspaces glob | `"workspaces": ["apps/*"]` | `"workspaces": ["targets/*"]` | The same migration flips the entry (every other entry is preserved), and heals it on its own for a folder you renamed by hand. Run `npm install` at the brand root afterwards so npm re-links `node_modules/<app>` at the new path |
290
+ | Paths in YOUR files | `apps/website/...` in the brand's own scripts, CI workflows, editor config, READMEs, `.env` comments | `targets/website/...` | By hand — the migration moves the folder, never your text. `npx omega manage` rewrites the workflow templates it owns; anything you authored is yours to grep |
291
+ | This monorepo's brand folder | `apps/sandbox-brand`, `apps/omega-playground`, `apps/newsflash-brand` | `brands/<same>` | Monorepo-internal — nothing for a consumer brand to do |
292
+ | CI base-path helper ([#455](https://github.com/Omega-JS-Stack/omega/issues/455)) | `require('@omega.js/web/deploy').appPathPrefix()` — called by name from the scaffolded `.github/workflows/build.yml` | `targetPathPrefix()` — same signature, same behavior | A brand whose workflow still calls the old name re-scaffolds it (`npx omega manage` rewrites the workflows it owns) at its migration session; no alias exists |
293
+ | Config merge-layer word ([#455](https://github.com/Omega-JS-Stack/omega/issues/455)) | The per-target-dir layer was the **app layer**: docs said `… ← app shared ← app targets.<type>`, `loadConfig()`/`composeTargetConfig()` returned `files.app`, and `resolveEnvChain()` returned `{ app, brand, company }` | The **local layer** — `… ← local shared ← local targets.<type>`, `files.local`, `{ local, brand, company }` | Nothing in an authored `omega.json5` changes (the layer is positional — no `app:` key ever existed). Code reading `files.app` or `chain.app` renames the key; no alias exists |
294
+
295
+ ## One DNS flip — `emailurl.<domain>` rides the Cloudflare proxy ([#646](https://github.com/Omega-JS-Stack/omega/issues/646))
296
+
297
+ Links inside a transactional email opened with the browser's insecure-site
298
+ warning on every brand not yet on OMEGA. The cause, confirmed live on
299
+ 2026-08-27: SendGrid rewrites every link through the branded link host
300
+ `emailurl.<domain>`, that host serves NO certificate of its own
301
+ (`https://emailurl.<legacy domain>` fails certificate validation, while plain
302
+ HTTP answers), and the account's link branding carries no SSL setting at all —
303
+ every one of its 36 entries is `valid: true`, `legacy: false`, with no `ssl`
304
+ field. So a click could only ever land on an `http://` hop first, and Chrome
305
+ said so. The edge service's DNS default now creates that one record
306
+ **proxied**, so Cloudflare terminates TLS at the edge with the zone's own
307
+ certificate and forwards to sendgrid.net.
308
+
309
+ **The flip is ORDERED, and the DNS ensure enforces the order itself.** SendGrid
310
+ validates a branded link by resolving `emailurl.<domain>` as a CNAME to
311
+ sendgrid.net, and a proxied record answers with Cloudflare's addresses instead
312
+ — so proxying first locks a NEW domain's branding out of ever validating. Every
313
+ run the edge service reads `GET /v3/whitelabel/links` and only writes
314
+ `proxied: true` when SendGrid reports that host `valid: true`; until then the
315
+ record stays grey-clouded and the run warns, naming the record. Nothing to
316
+ sequence by hand: since
317
+ [#693](https://github.com/Omega-JS-Stack/omega/issues/693) the campaigns
318
+ service CREATES the branding when the account has none, writes both of its
319
+ CNAMEs, waits for the validation later in the same walk, and flips that record
320
+ to proxied itself —
321
+ the edge service's next read agrees, because a valid branding desires a proxied
322
+ record.
323
+
324
+ | Contract | Old form | New form | Manual migration step |
325
+ |---|---|---|---|
326
+ | `emailurl.<domain>` CNAME → `sendgrid.net` | Grey-clouded (`proxied: false`), so the click resolved straight to SendGrid over HTTP | Orange-clouded (`proxied: true`) once SendGrid reports the link branding valid — the only SendGrid record that is; `emailauth`, the `<sendgrid id>` owner CNAME and both DKIM keys stay grey, because SendGrid validates those by CNAME lookup | **Per legacy brand, once**: run `npx omega manage` for a brand the edge service owns — an already-validated branding (every live ITW brand) flips on that run; a branding still pending stays grey with a warning, and the rerun after SendGrid validates flips it. By hand elsewhere: validate the branded link in SendGrid FIRST, then turn the proxy ON for `emailurl.<domain>` in Cloudflare |
327
+
328
+ ## Unsubscribe-group ids leave the code — config carries them ([#649](https://github.com/Omega-JS-Stack/omega/issues/649))
329
+
330
+ Every transactional send attaches a SendGrid unsubscribe (ASM) group, and BEM
331
+ hardcoded the seven ids in `constants.js`. Those ids belong to the SendGrid
332
+ ACCOUNT they were created in — ITW's — so a brand on any other account either
333
+ sent under a group that does not exist there or under a stranger's. Ids are
334
+ account data, never code: the campaigns service now provisions the seven groups
335
+ by NAME and writes each id into the brand's own `config/omega.json5`, and the
336
+ backend keeps only the KEYS (`GROUP_KEYS` in `constants.js`).
337
+
338
+ Matching by name is what makes sibling brands sharing one SendGrid account
339
+ converge: the second brand's run finds the groups the first one created and
340
+ lands the SAME ids.
341
+
342
+ **A missing id fails the send LOUDLY** (coded 400, naming the config path). It
343
+ is a programmer error — the manage walk never ran for this brand — and a
344
+ fallback would be the exact bug this removes.
345
+
346
+ | Contract | Old form | New form | Manual migration step |
347
+ |---|---|---|---|
348
+ | ASM group ids | `GROUPS` in `packages/backend/src/manager/libraries/email/constants.js` — seven literal ids compiled into the framework | `marketing.campaigns.providers.sendgrid.groups.<key>` in the brand's `config/omega.json5`, one integer per key (`orders`, `hello`, `account`, `marketing`, `security`, `newsletter`, `internal`) | **Per brand, once**: run `npx omega manage` (or `--service=campaigns`) — the campaigns service creates any group the account is missing and writes all seven ids into `config/omega.json5`. By hand: read the ids from SendGrid → Suppressions → Unsubscribe Groups and write the block yourself. The ensure matches by NAME, so on an account whose groups carry legacy-prefixed names ("BEM - Order Updates"), RENAME them to the canonical "OMEGA - " names FIRST (ids never change, so legacy sends keep working) — otherwise the ensure creates a duplicate set and unsubscribe state splits (this happened once on the shared account, repaired 2026-08-29). A brand on its own fresh account just gets its own groups. Until the block exists, every send throws instead of mailing under a foreign group |
349
+ | `prepare.resolveSender()` signature | `resolveSender({ sender, from, group }, brand, brandDomain)` | `resolveSender({ sender, from, group }, brand, brandDomain, Manager)` — the fourth argument carries the config the ids live in | Only affects code calling `prepare.js` directly: pass `Manager` as the fourth argument. `group:` still accepts a raw numeric id AND now accepts a group KEY, which resolves through config |
350
+
351
+ ## `brand.subdomains` becomes web instances ([#588](https://github.com/Omega-JS-Stack/omega/issues/588))
352
+
353
+ A legacy brand running sibling sites off one domain declared them as a
354
+ `brand.subdomains` list (soundgrail: `[app, music, exhale]`). Nothing in OMEGA
355
+ DECLARED that key (no schema rule, no default, never materialized) and one
356
+ thing read it: the cloud hosting op, which ensured an `api.{sub}.{domain}`
357
+ Firebase Hosting domain per entry. Ian's 2026-09-01 call: each subdomain shares
358
+ the ONE backend, and the fact the list was reaching for is a web INSTANCE.
359
+
360
+ So the instance is the home. The instance `id` IS the subdomain, so a bare
361
+ `{ id: 'admin' }` resolves to `https://admin.<brand host>`; an entry's own
362
+ `url` overrides it for a custom host, and every instance shares one
363
+ `api.<domain>`. `subdomains` is a retired KEY now (a name test, so it fires at
364
+ every depth): a config still carrying the list fails validation with the recipe
365
+ instead of silently steering the hosting op.
366
+
367
+ The brand-level facts stay brand-level. `cloud.config.authDomain` is compared
368
+ against `brand.url` for every instance (one Firebase project, one backend, one
369
+ authDomain), and so is the persona domain the test lanes seed. What IS per
370
+ instance is the public surface: `site.url`, the gh-pages CNAME, and the deploy
371
+ path prefix, so `targets/website-admin` publishes to admin.acme.test instead of
372
+ over the main site.
373
+
374
+ | Contract | Old form | New form | Manual migration step |
375
+ |---|---|---|---|
376
+ | Sibling sites of one brand | `brand: { subdomains: ["admin", "cdn"] }`, unvalidated, read only by the cloud hosting op | `targets: { web: [{ id: 'main' }, { id: 'admin' }, { id: 'cdn' }] }`, the multi-instance form ([config.md](config.md#multi-instance-targets)); each instance lives in `targets/website-<id>` | **Per brand, once, by hand**: delete the `brand.subdomains` list and write the instance array (the ids are the subdomains verbatim; add `url:` only where the host is NOT `<id>.<brand host>`). Create each instance's `targets/website-<id>` dir; `npx omega manage` names the missing ones. No converter: the validator refuses the old key and names the replacement |
377
+ | Firebase Hosting API domains | `api.{domain}` **plus** `api.{sub}.{domain}` per subdomain entry | `api.{domain}` alone: one backend, one api host, however many instances | **Nothing to write**: the next `npx omega manage` reconciles the default site with the single domain. Any `api.{sub}.{domain}` a previous run created stays in Firebase and Cloudflare until removed BY HAND, because nothing deletes a live domain, so drop the custom domain in the Firebase console and its Cloudflare CNAME once no client calls it |
378
+
379
+ ## Plan limits become the features catalog ([#647](https://github.com/Omega-JS-Stack/omega/issues/647))
380
+
381
+ A metered feature used to be spelled TWICE on every product — a number in
382
+ `limits` and a display row in the `features` array — and its display copy was
383
+ unified across products by a BACKFILL rule (a definition on any one product's
384
+ copy explained every other). Its PACING was a third thing again: a product-wide
385
+ `rateLimit` that no single feature could opt out of.
386
+
387
+ So a feature is defined ONCE now, in a top-level `features` catalog (name, icon,
388
+ definition, and the `usage` block that meters it), and each product names only
389
+ its VALUE. A limit and the row that renders it can no longer disagree, because
390
+ there is only one place each fact is written. The backfill retires with the
391
+ duplication it existed to paper over: nothing repeats, so nothing needs
392
+ unifying.
393
+
394
+ The counting side changes with it. `Usage.init()` / `validate()` / `increment()`
395
+ / `set()` / `update()` / `setUser()` / `addMirror()` / `setMirrors()` are gone,
396
+ replaced by ONE call — `await ctx.usage.consume('<feature>')` — which checks
397
+ both counters, refuses with a 429 naming which one hit, else counts and writes
398
+ ([packages/backend/docs/usage-rate-limiting.md](../../packages/backend/docs/usage-rate-limiting.md)).
399
+ The hCaptcha over-limit fallback `validate()` carried goes with it: it had no
400
+ caller (`marketing/contact`, its only user, passed
401
+ `useCaptchaResponse: false`), and a captcha is a bot check, not a quota.
402
+
403
+ | Contract | Old form | New form | Manual migration step |
404
+ |---|---|---|---|
405
+ | A metered feature's limit | `payment.products[].limits: { requests: 100 }` | `payment.products[].features: { requests: 100 }`, against a `features.requests` catalog entry carrying a `usage` block | **Per brand, once, by hand**: move each `limits` key into the product's `features` map (same number, same meaning — a MONTHLY limit, `-1` unlimited), and give every id a `features.<id>` catalog entry with at least a `name`. The validator refuses the old key and names the replacement |
406
+ | A feature's display copy | `payment.products[].features: [{ id, name, icon, definition, value }]`, repeated per product, definitions unified by a cross-product backfill | `features.<id>: { name, icon, definition }` once, and `payment.products[].features: { <id>: value }` | **Per brand, once, by hand**: lift the name/icon/definition of each entry into the catalog (authored once, in the order you want the rows rendered), and leave only `<id>: value` on each product. `value: true` stays `true`; a feature a tier does NOT include becomes `false` or is simply omitted. The array shape is a validation error naming the map |
407
+ | Pacing | `payment.products[].rateLimit: 'daily' \| 'monthly'`, one shape for every metric on the product | `features.<id>.usage.pace: 'daily' \| false`, per feature | **Per brand, once, by hand**: delete `rateLimit`. Day pacing is now the DEFAULT on every counted feature, so a product that carried `rateLimit: 'daily'` needs nothing; one that carried `'monthly'` sets `usage: { pace: false }` on each of its features' catalog entries |
408
+ | Mirrored counters | `usage.addMirror('agents/x')` / `setMirrors([...])` at the call site, per request, writing the WHOLE usage object | `features.<id>.usage.mirror: ['agents']` in config, resolved from `user.owns.agents`, writing only the touched feature's counters | **Per brand, once, by hand**: delete the call-site mirror calls, name the document KINDS on the catalog entry, and make sure the code that creates an owned document records its id on the owner's `owns.<kind>` array (a framework field — server-written only) |
409
+ | Proxy billing | `await usage.setUser(ownerUid)` swapped the counter's target user mid-request | Removed. A route that must bill another account resolves that account itself and counts against the mirror the catalog declares | **Per brand, by hand**: rework any proxy-billing route around `consume` + a catalog mirror. There is no replacement for silently re-pointing a counter at another user |
410
+ | The counting call | `await usage.validate(m); usage.increment(m); await usage.update();` | `await ctx.usage.consume(m)` | **Per brand, by hand**: replace the three-call dance (and the hand-rolled `getUsage() >= LIMIT` gates that grew around it) with one `consume`. It THROWS the 429 — catch it only to reshape the message |
411
+ | Anonymous counting | `Usage().init(ctx, { key: ip })` — an explicit key silently switched a signed-in user's storage too | `ctx.usage.forKey(ip)` — a SEPARATE counter | **Per brand, by hand**: replace each keyed `init` with `forKey`. A signed-in caller's own counters can no longer be redirected by passing a key |
412
+ | Per-user extra credits | Not expressible — a limit was the plan's number, full stop | `user.usage.overrides.<feature>`, a number that wins over the plan's | **Nothing to migrate**: new capability. Admin-written only (`usage` is a rules-protected framework field), and the reset cron never touches it |
413
+ | A page's own `features:` block | A page or layout could name a top-level `features:` frontmatter block of its own content (a marketing "Features" band) and read it as `resolved.features` | `features` is a CONFIG SECTION now, so a page key may not shadow it — rename the block (the framework's own `/download`, `/extension` and the neobrutalism index call theirs `highlights`) | **Per brand, by hand**: rename any top-level `features:` frontmatter block and its `resolved.features.*` reads. A section restated bare in `config:` is a build error naming the key, and a `site.features` read is one too, so an unmigrated page fails loudly rather than rendering an empty band |
414
+
415
+ ## The user-connection feature is `connections` ([#788](https://github.com/Omega-JS-Stack/omega/issues/788))
416
+
417
+ The lane a brand's users link third-party accounts through was named `oauth2`
418
+ in the API, the user record, the config section, the env prefix, the brand
419
+ provider folder and the callback URL, while the UI called it "Connections".
420
+ Ian's 2026-09-03 ruling: the product concept is a CONNECTION, and a connection
421
+ will not always be an OAuth grant — an API key or a bot token is one too — so
422
+ the whole feature carries the product word, and each stored record names its
423
+ own kind with a `type` field (`'oauth2'` today, the only kind that exists).
424
+ OAuth's own version is not the reason; OAuth 2.1 still calls itself OAuth 2.
425
+
426
+ Nothing dual-reads the old names. The user DOCUMENTS move in one step of the
427
+ brand migration — `npx omega manage --migration=users --execute`, whose `users`
428
+ migration writes `connections.<provider>` with its `type` and deletes `oauth2`
429
+ in the same write ([manager/migrations.md](../manager/migrations.md)) — and the
430
+ config key is a retired-key error naming its replacement. Everything else in
431
+ the table is by hand, once per brand. One transient at the deploy itself: a
432
+ connect attempt whose provider login was already open fails its callback once
433
+ (the state cipher and the session key changed with the name) and succeeds on
434
+ retry; its stale `usage/{uid}.oauth2` session clears with the daily clean.
435
+
436
+ | Contract | Old form | New form | Manual migration step |
437
+ |---|---|---|---|
438
+ | User record | `users/{uid}.oauth2.{provider}` | `users/{uid}.connections.{provider}`, each record carrying `type: 'oauth2'` | **The migration moves them**: `npx omega manage --migration=users` audits, `--execute` writes. Idempotent — a document already moved is a no-op, and a document carrying both keeps `connections` and drops the leftover |
439
+ | Route | `GET \| POST \| DELETE /omega/user/oauth2` | `/omega/user/connections`; the actions (`authorize`, `status`, `tokenize`, `refresh`) and the delete keep their names, but `authorize` and `tokenize` no longer accept an admin `uid` — a passed one answers 400 naming the argument, since both need the connecting user's own browser. `status`, `refresh` and the delete still take one as an admin (each acts at the provider on that user's behalf), and a trusted read of a stored token is `GET /omega/admin/firestore?path=users/<uid>` with the admin key ([#782](https://github.com/Omega-JS-Stack/omega/issues/782)) | Repoint any caller of your own that names the path. The framework's own callers (the account page, the callback page) move with the framework. A server-side caller that passed a `uid` to `authorize` or `tokenize` moves that call to the user's OWN browser session — no admin key can stand in for it — and reads the resulting token through the admin firestore route |
440
+ | Config section | `oauth2: { <provider>: {…} }` | `connections: { <provider>: {…} }` — the per-provider block is byte-identical, with ONE change of meaning ([#793](https://github.com/Omega-JS-Stack/omega/issues/793)): a PACKAGED provider (google, discord, spotify, twitch, kick) is `enabled: false` in the framework defaults, so an entry that relied on "absent means enabled" is now off | Rename the key in `config/omega.json5`, and add `enabled: true` to each packaged provider's block that does not already carry it — a brand's OWN provider is unchanged (present unless `enabled: false`). A config still carrying `oauth2` is a retired-key error naming the move ([config.md](config.md#retired-keys-fail-loudly)) |
441
+ | Credentials | `OAUTH2_<PROVIDER>_CLIENT_ID` / `OAUTH2_<PROVIDER>_CLIENT_SECRET` | `CONNECTIONS_<PROVIDER>_CLIENT_ID` / `CONNECTIONS_<PROVIDER>_CLIENT_SECRET` | **Rename the pair by hand** in the brand `.env` (and every `.env.<environment>` overlay), and in the CI secrets any workflow injects them from. There is no boot-time check for a leftover `OAUTH2_*` key: the env schema simply does not know that name any more, so the provider reads an empty client id and its authorize leg 500s |
442
+ | Provider console redirect URI | `<websiteUrl>/oauth2` | `<websiteUrl>/connections/callback` | **Register the new URI at every provider** whose block the brand declares (Google Cloud console, Discord developer portal, Spotify dashboard, …). Add it BESIDE the old one, deploy, then remove the old one — a redirect URI is matched exactly, so a deploy ahead of the console edit breaks every link attempt |
443
+ | Brand provider module | `targets/backend/src/oauth2/<name>.js` | `targets/backend/src/connections/<name>.js` | Move the directory. The lane resolves `${Manager.cwd}/connections/` first and the package's own second, exactly as before |
444
+ | Website callback page | the `/oauth2` default page (`blueprint/auth/oauth2`) | `/connections/callback` (`blueprint/connections/callback`) | **Nothing for a brand that never overrode it.** A brand carrying its own copy moves it to the new path and layout name; `/connections` itself stays free for a future listing page |
445
+ | Provider module shape ([#793](https://github.com/Omega-JS-Stack/omega/issues/793)) | `verifyIdentity(tokenizeResult, Manager, ctx, uid)`, `buildAuthorizeUrl(context)`, `revokeToken(token, {…})`, `verifyConnection(refreshToken, {…})`, `authParams`, `urls.tokenize` + `urls.refresh` + `urls.status`, and a copy of the "already connected" query inside `verifyIdentity` | ONE context object for every step, called as a method on the module: `identity(context)` (required, → `{ id, … }`), `authorize`, `exchange`, `refresh`, `revoke`, `status`; `params` for extra authorize params; ONE `urls.token`; `urls.revoke` optional; `urls.status` deleted | **Per provider module, by hand** (switchboard's YouTube provider is the known one outside this repo): rename the six members, fold `urls.tokenize`/`urls.refresh` into `urls.token`, rename `authParams` → `params`, drop `urls.status`, read `tokenizeResult`/`Manager`/`ctx`/`uid`/`clientId` off the one `context` argument, and DELETE the uniqueness query — the route owns it now and matches on `identity.id`, so the step just answers the identity, with a string `id`. A module missing `identity()` or `urls.token` throws at load naming the file |
446
+
447
+ ## One target picker — `--target=` on every brand-root verb ([#780](https://github.com/Omega-JS-Stack/omega/issues/780))
448
+
449
+ The brand-root fan-outs picked targets with `--only=<a,b>` / `--except=<a,b>`
450
+ while `omega test` picked with `--target=<a,b>`. One picker now, one spelling on
451
+ all six verbs (`deploy`, `build`, `clean`, `dev`, `update`, `test`): the tokens
452
+ are unchanged (a target key like `web`, or a target dir name like `website`), and
453
+ a token matching nothing stops the run: never a run-everything fallback, and
454
+ never the matched subset either.
455
+
456
+ Beyond parity, `--only` was also firebase's own flag on the backend target:
457
+ `firebase deploy --only hosting` picks a SERVICE, so one flag name meant two
458
+ things on one verb. That pass-through is untouched and stays a target-level
459
+ flag, run from `targets/backend`.
460
+
461
+ | Contract | Old form | New form | Manual migration step |
462
+ |---|---|---|---|
463
+ | The brand-root target picker | `omega deploy --only=web,backend`, and the same flag on `build`, `clean`, `dev`, `update` | `omega deploy --target=web,backend` on every one of them | **Per brand, by hand**: respell `--only=` as `--target=` in npm scripts, CI workflows and shell aliases. Passing `--only` now fails loudly naming `--target=`, so nothing runs the wrong set silently |
464
+ | Subtracting from the set | `--except=<a,b>` (the default set minus these) | No subtractive form: name the exact set with `--target=` | **Per brand, by hand**: replace `omega dev --except=backend` with `omega dev --target=web`. A GUI/watcher target (desktop, extension) still has to be named to boot |
465
+
466
+ ## The brand SOURCE repo derives as `<brand.id>-omega` ([#809](https://github.com/Omega-JS-Stack/omega/issues/809))
467
+
468
+ Brand repo names grew inconsistent (`clockii-omega`, `omega-playground`,
469
+ `studymonkey-website`), so Ian ruled one rule on 2026-09-07: every repo a brand owns is
470
+ `<brand.id>-<role>`. The releases repo already derived that way (`<brand.id>-releases`);
471
+ the SOURCE repo's default was the bare `brand.id` and is now `<brand.id>-omega`. Nothing
472
+ else moved: the typed `repo.providers.github.repo` slug still wins, and the owner half is
473
+ unchanged.
474
+
475
+ The blast radius is every reader of `brandRepo()`: web's `omega deploy --direct` (its
476
+ Pages project address included), the manager's github service (org, repo and Pages
477
+ reconciliation), the backend's `resolved.github` CMS commits, and the release dispatch.
478
+
479
+ | Contract | Old form | New form | Manual migration step |
480
+ |---|---|---|---|
481
+ | The brand source repo's default name | `brand.id` verbatim (`acme` → `Acme-Org/acme`) | `<brand.id>-omega` (`acme` → `Acme-Org/acme-omega`) | **Per brand, by hand, and only if the brand relied on the default**: either declare the existing name once with `repo.providers.github.repo: "<name>"` (a bare name, or an `owner/name` slug when the repo sits under another org), or rename the GitHub repo to `<brand.id>-omega` and let the default derive it. A brand that already types the slug is untouched |
482
+
483
+ ## Deliberate compatibility that REMAINS
484
+
485
+ Old forms the new system still speaks ON PURPOSE, because a party outside this
486
+ ecosystem still sends them. They are NOT accommodations to clean up: each one
487
+ retires when its named condition is met, and never unilaterally.
488
+
489
+ | Compatibility | Who still speaks it | Retirement condition |
490
+ |---|---|---|
491
+ | The `/backend-manager/*` URL alias — the backend router's prefix strip and its Cloudflare edge-worker twin (`packages/manager/src/services/edge/workers/omega-api-proxy.js`) | In-the-wild clients of migrated brands: shipped apps, third-party integrations, and pages still calling the old path | Every known caller moved to `/omega/*` and the alias shows no traffic |
492
+ | `backendManagerKey` sent in outbound request bodies (`process.env.OMEGA_ADMIN_KEY` under the OLD field name) | Legacy-BEM parent deployments, the Ghostii API, and ITW's `wrapper` Cloud Function — all still reading that field | Each upstream migrates to the new stack and accepts the `omega-admin-key` header; fix per upstream, never unilaterally |
493
+ | The Ghostii flat-article response fallback | api.ghostii.ai, whose production backend runs legacy BEM and can return the flat field shape | Ghostii returns only the structured shape |
494
+ | The `gatherings/online` sign-out leg | Old somiibo / electron-manager desktop clients that still write that RTDB path | Those app versions are out of circulation |
495
+ | `legacyProductIds` / `legacyPlanIds` matching in the PayPal and Chargebee providers | Currently-billing subscribers on plan/product IDs created before the current catalog | The last subscription on a legacy ID ends or is migrated |
496
+ | The legacy desktop deep-link param translation in web core auth (`?destination=&source=app&signout=&cb=` → `authReturnUrl` / `authSignout`, chained through `/token`) | Shipped legacy desktop apps whose auth links are baked into installed binaries | Those app versions are out of circulation |
497
+ | Fixed legacy download filenames in the desktop mirror-downloads task (`Somiibo.dmg`, `Somiibo-Setup.exe`, `somiibo_amd64.deb`) | Every published download link and site pointing at the stable, version-less filename | No published link depends on the stable filename |