@omega.js/manager 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 (416) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/LICENSE +98 -0
  3. package/README.md +165 -0
  4. package/bin/omega +2 -0
  5. package/bin/omg +2 -0
  6. package/claude-plugin/.claude-plugin/plugin.json +11 -0
  7. package/claude-plugin/.mcp.json +9 -0
  8. package/claude-plugin/README.md +139 -0
  9. package/claude-plugin/hooks/gate/mark.sh +58 -0
  10. package/claude-plugin/hooks/gate/run.sh +97 -0
  11. package/claude-plugin/hooks/guard/run.sh +249 -0
  12. package/claude-plugin/hooks/hooks.json +63 -0
  13. package/claude-plugin/hooks/inject/run.sh +127 -0
  14. package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
  15. package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
  16. package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
  17. package/claude-plugin/hooks/quality/run.sh +132 -0
  18. package/claude-plugin/hooks/shape/run.sh +45 -0
  19. package/claude-plugin/mcp-router-launch.js +29 -0
  20. package/claude-plugin/skills/README.md +48 -0
  21. package/claude-plugin/skills/accessibility/SKILL.md +31 -0
  22. package/claude-plugin/skills/analytics/SKILL.md +30 -0
  23. package/claude-plugin/skills/backend/SKILL.md +25 -0
  24. package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
  25. package/claude-plugin/skills/browser/SKILL.md +64 -0
  26. package/claude-plugin/skills/client/SKILL.md +24 -0
  27. package/claude-plugin/skills/desktop/SKILL.md +25 -0
  28. package/claude-plugin/skills/extension/SKILL.md +25 -0
  29. package/claude-plugin/skills/main/SKILL.md +54 -0
  30. package/claude-plugin/skills/manager/SKILL.md +25 -0
  31. package/claude-plugin/skills/seo/SKILL.md +36 -0
  32. package/claude-plugin/skills/theme/SKILL.md +33 -0
  33. package/claude-plugin/skills/web/SKILL.md +29 -0
  34. package/dist/cli-run.js +34 -0
  35. package/dist/cli.js +29 -0
  36. package/dist/commands/build.js +17 -0
  37. package/dist/commands/clean.js +16 -0
  38. package/dist/commands/company.js +46 -0
  39. package/dist/commands/deploy.js +143 -0
  40. package/dist/commands/dev.js +437 -0
  41. package/dist/commands/devlog.js +25 -0
  42. package/dist/commands/help.js +48 -0
  43. package/dist/commands/manage.js +53 -0
  44. package/dist/commands/migrate.js +76 -0
  45. package/dist/commands/onboard.js +31 -0
  46. package/dist/commands/pipeline.js +319 -0
  47. package/dist/commands/test.js +380 -0
  48. package/dist/commands/update.js +128 -0
  49. package/dist/commands/version.js +8 -0
  50. package/dist/company-init.js +167 -0
  51. package/dist/company.js +283 -0
  52. package/dist/config.js +1359 -0
  53. package/dist/devlog/index.js +226 -0
  54. package/dist/devlog/lib/collect-commits.js +136 -0
  55. package/dist/devlog/lib/generate-post.js +201 -0
  56. package/dist/devlog/lib/ghostii.js +122 -0
  57. package/dist/devlog/lib/project-map.js +98 -0
  58. package/dist/devlog/lib/publish-website.js +88 -0
  59. package/dist/index.js +45 -0
  60. package/dist/lib/agents-md.js +201 -0
  61. package/dist/lib/analytics-secret.js +22 -0
  62. package/dist/lib/argv.js +21 -0
  63. package/dist/lib/auth-admin.js +120 -0
  64. package/dist/lib/automation-client.js +210 -0
  65. package/dist/lib/backend-marketing.js +49 -0
  66. package/dist/lib/brand.js +240 -0
  67. package/dist/lib/bundle-id.js +53 -0
  68. package/dist/lib/claude-settings.js +125 -0
  69. package/dist/lib/company-scaffold.js +213 -0
  70. package/dist/lib/company.js +227 -0
  71. package/dist/lib/config-flow.js +350 -0
  72. package/dist/lib/config-write.js +41 -0
  73. package/dist/lib/custom-target.js +75 -0
  74. package/dist/lib/domain-utils.js +19 -0
  75. package/dist/lib/duration.js +39 -0
  76. package/dist/lib/env-order.js +236 -0
  77. package/dist/lib/env-secret.js +78 -0
  78. package/dist/lib/firestore-rest.js +326 -0
  79. package/dist/lib/framework-bin.js +156 -0
  80. package/dist/lib/gitignore.js +63 -0
  81. package/dist/lib/google-auth.js +495 -0
  82. package/dist/lib/google-token.js +66 -0
  83. package/dist/lib/jwt.js +31 -0
  84. package/dist/lib/legacy-oauth.js +59 -0
  85. package/dist/lib/node-version.js +127 -0
  86. package/dist/lib/owner-plan.js +78 -0
  87. package/dist/lib/package-scripts.js +152 -0
  88. package/dist/lib/preflight.js +470 -0
  89. package/dist/lib/product-create.js +144 -0
  90. package/dist/lib/run-command.js +74 -0
  91. package/dist/lib/run-gates.js +70 -0
  92. package/dist/lib/run-output.js +42 -0
  93. package/dist/lib/run-summary.js +384 -0
  94. package/dist/lib/scaffold.js +427 -0
  95. package/dist/lib/service-input.js +207 -0
  96. package/dist/lib/service-runner.js +466 -0
  97. package/dist/lib/stale.js +24 -0
  98. package/dist/lib/target-selection.js +155 -0
  99. package/dist/lib/verb-fanout.js +128 -0
  100. package/dist/lib/verify-live.js +188 -0
  101. package/dist/manage.js +311 -0
  102. package/dist/omega-bin.js +7 -0
  103. package/dist/onboard.js +532 -0
  104. package/dist/services/account/ensure/users.js +259 -0
  105. package/dist/services/account/index.js +97 -0
  106. package/dist/services/account/lib/backend-client.js +107 -0
  107. package/dist/services/account/lib/password.js +36 -0
  108. package/dist/services/account/lib/resolve-password.js +100 -0
  109. package/dist/services/advertising/ensure/sites.js +101 -0
  110. package/dist/services/advertising/index.js +101 -0
  111. package/dist/services/advertising/lib/adsense-api.js +41 -0
  112. package/dist/services/ai/ensure/keys.js +21 -0
  113. package/dist/services/ai/index.js +36 -0
  114. package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
  115. package/dist/services/analytics/ensure/google-streams.js +256 -0
  116. package/dist/services/analytics/ensure/meta-pixel.js +22 -0
  117. package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
  118. package/dist/services/analytics/index.js +134 -0
  119. package/dist/services/analytics/lib/analytics-api.js +156 -0
  120. package/dist/services/analytics/lib/meta-api.js +83 -0
  121. package/dist/services/analytics/lib/pixel-account.js +78 -0
  122. package/dist/services/analytics/lib/pixel-provision.js +152 -0
  123. package/dist/services/analytics/lib/pixel-specs.js +57 -0
  124. package/dist/services/analytics/lib/pixel-token.js +88 -0
  125. package/dist/services/analytics/lib/property-flow.js +77 -0
  126. package/dist/services/analytics/lib/tiktok-api.js +157 -0
  127. package/dist/services/analytics/lib/tiktok-auth.js +153 -0
  128. package/dist/services/assets/ensure/logo-gen.js +68 -0
  129. package/dist/services/assets/index.js +109 -0
  130. package/dist/services/assets/lib/assets-config.js +168 -0
  131. package/dist/services/assets/lib/brandmark-api.js +160 -0
  132. package/dist/services/assets/lib/derived.js +113 -0
  133. package/dist/services/assets/lib/font-loader.js +67 -0
  134. package/dist/services/assets/lib/reconcile.js +122 -0
  135. package/dist/services/assets/lib/reset.js +118 -0
  136. package/dist/services/assets/lib/svg-logo-generator.js +154 -0
  137. package/dist/services/assets/lib/svg-to-black.js +32 -0
  138. package/dist/services/assets/write/favicons.js +82 -0
  139. package/dist/services/assets/write/icons.js +64 -0
  140. package/dist/services/assets/write/process.js +96 -0
  141. package/dist/services/assets/write/reconcile.js +30 -0
  142. package/dist/services/assets/write/social-icons.js +108 -0
  143. package/dist/services/assets/write/templates.js +345 -0
  144. package/dist/services/bookmark/ensure/sync.js +255 -0
  145. package/dist/services/bookmark/index.js +17 -0
  146. package/dist/services/campaigns/ensure/contact-person.js +40 -0
  147. package/dist/services/campaigns/ensure/custom-fields.js +84 -0
  148. package/dist/services/campaigns/ensure/domain-auth.js +92 -0
  149. package/dist/services/campaigns/ensure/event-webhook.js +89 -0
  150. package/dist/services/campaigns/ensure/link-branding.js +187 -0
  151. package/dist/services/campaigns/ensure/list.js +50 -0
  152. package/dist/services/campaigns/ensure/segments.js +109 -0
  153. package/dist/services/campaigns/ensure/sender-identity.js +153 -0
  154. package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
  155. package/dist/services/campaigns/index.js +79 -0
  156. package/dist/services/campaigns/lib/dns-sync.js +81 -0
  157. package/dist/services/campaigns/lib/segment-query.js +138 -0
  158. package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
  159. package/dist/services/captcha/ensure/site-key.js +83 -0
  160. package/dist/services/captcha/index.js +78 -0
  161. package/dist/services/captcha/lib/console-url.js +45 -0
  162. package/dist/services/captcha/lib/recaptcha-api.js +36 -0
  163. package/dist/services/certificates/ensure/api-key.js +22 -0
  164. package/dist/services/certificates/ensure/bundle-ids.js +115 -0
  165. package/dist/services/certificates/ensure/certificates.js +187 -0
  166. package/dist/services/certificates/ensure/profiles.js +143 -0
  167. package/dist/services/certificates/index.js +225 -0
  168. package/dist/services/certificates/lib/apple-api.js +183 -0
  169. package/dist/services/certificates/lib/certificate-manager.js +252 -0
  170. package/dist/services/certificates/lib/identifier-manager.js +107 -0
  171. package/dist/services/certificates/lib/keychain.js +135 -0
  172. package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
  173. package/dist/services/certificates/lib/profile-manager.js +115 -0
  174. package/dist/services/chat/data/baseline-knowledge.md +96 -0
  175. package/dist/services/chat/ensure/chat.js +106 -0
  176. package/dist/services/chat/ensure/user.js +45 -0
  177. package/dist/services/chat/index.js +139 -0
  178. package/dist/services/chat/lib/baseline-knowledge.js +137 -0
  179. package/dist/services/cloud/ensure/authentication.js +267 -0
  180. package/dist/services/cloud/ensure/billing.js +120 -0
  181. package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
  182. package/dist/services/cloud/ensure/database.js +60 -0
  183. package/dist/services/cloud/ensure/firestore.js +79 -0
  184. package/dist/services/cloud/ensure/functions.js +49 -0
  185. package/dist/services/cloud/ensure/hosting.js +398 -0
  186. package/dist/services/cloud/ensure/oauth-consent.js +248 -0
  187. package/dist/services/cloud/ensure/project-settings.js +72 -0
  188. package/dist/services/cloud/ensure/sdk-config.js +95 -0
  189. package/dist/services/cloud/ensure/service-account.js +152 -0
  190. package/dist/services/cloud/ensure/services.js +103 -0
  191. package/dist/services/cloud/ensure/storage.js +47 -0
  192. package/dist/services/cloud/index.js +130 -0
  193. package/dist/services/cloud/lib/access-heal.js +130 -0
  194. package/dist/services/cloud/lib/firebase-api.js +662 -0
  195. package/dist/services/cloud/lib/project-flow.js +102 -0
  196. package/dist/services/directory/ensure/entry.js +48 -0
  197. package/dist/services/directory/index.js +64 -0
  198. package/dist/services/directory/lib/blocks.js +104 -0
  199. package/dist/services/disperse/index.js +45 -0
  200. package/dist/services/disperse/write/certs.js +117 -0
  201. package/dist/services/domain/ensure/nameservers.js +179 -0
  202. package/dist/services/domain/index.js +69 -0
  203. package/dist/services/domain/lib/namecheap-api.js +167 -0
  204. package/dist/services/domain/lib/registrars.js +46 -0
  205. package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
  206. package/dist/services/edge/ensure/cache-rules.js +105 -0
  207. package/dist/services/edge/ensure/dns-records.js +304 -0
  208. package/dist/services/edge/ensure/email-routing.js +292 -0
  209. package/dist/services/edge/ensure/rules-configuration.js +98 -0
  210. package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
  211. package/dist/services/edge/ensure/rules-redirect.js +107 -0
  212. package/dist/services/edge/ensure/rules-response-headers.js +90 -0
  213. package/dist/services/edge/ensure/rules-security.js +142 -0
  214. package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
  215. package/dist/services/edge/ensure/workers.js +219 -0
  216. package/dist/services/edge/ensure/zone-settings.js +108 -0
  217. package/dist/services/edge/ensure/zone.js +183 -0
  218. package/dist/services/edge/index.js +73 -0
  219. package/dist/services/edge/lib/cloudflare-api.js +84 -0
  220. package/dist/services/edge/lib/dns-records-helpers.js +518 -0
  221. package/dist/services/edge/lib/read-cache.js +16 -0
  222. package/dist/services/edge/lib/ruleset-helper.js +108 -0
  223. package/dist/services/edge/workers/omega-api-proxy.js +43 -0
  224. package/dist/services/email/data/baseline-filter.md +4 -0
  225. package/dist/services/email/data/baseline-knowledge.md +86 -0
  226. package/dist/services/email/ensure/agent.js +107 -0
  227. package/dist/services/email/ensure/user.js +45 -0
  228. package/dist/services/email/index.js +138 -0
  229. package/dist/services/email/lib/baseline.js +82 -0
  230. package/dist/services/email/lib/knowledge-file.js +53 -0
  231. package/dist/services/forms/ensure/form.js +46 -0
  232. package/dist/services/forms/ensure/user.js +45 -0
  233. package/dist/services/forms/index.js +138 -0
  234. package/dist/services/migrations/ensure/notifications.js +189 -0
  235. package/dist/services/migrations/ensure/orders.js +34 -0
  236. package/dist/services/migrations/ensure/payment-provider.js +132 -0
  237. package/dist/services/migrations/ensure/payments-intents.js +34 -0
  238. package/dist/services/migrations/ensure/state-retirement.js +266 -0
  239. package/dist/services/migrations/ensure/targets-rename.js +124 -0
  240. package/dist/services/migrations/ensure/users.js +1176 -0
  241. package/dist/services/migrations/index.js +101 -0
  242. package/dist/services/migrations/lib/attribution-touch.js +64 -0
  243. package/dist/services/migrations/lib/ensure-metadata.js +144 -0
  244. package/dist/services/migrations/lib/migration-runner.js +473 -0
  245. package/dist/services/migrations/lib/sanitize-strings.js +112 -0
  246. package/dist/services/migrations/lib/schema-validator.js +154 -0
  247. package/dist/services/monitoring/ensure/dsn.js +54 -0
  248. package/dist/services/monitoring/ensure/projects.js +135 -0
  249. package/dist/services/monitoring/index.js +54 -0
  250. package/dist/services/monitoring/lib/sentry-api.js +94 -0
  251. package/dist/services/newsletter/ensure/custom-fields.js +90 -0
  252. package/dist/services/newsletter/ensure/publication.js +94 -0
  253. package/dist/services/newsletter/ensure/segments.js +140 -0
  254. package/dist/services/newsletter/ensure/webhook.js +108 -0
  255. package/dist/services/newsletter/index.js +64 -0
  256. package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
  257. package/dist/services/newsletter/lib/segment-automation.js +577 -0
  258. package/dist/services/payment/ensure/chargebee-account.js +30 -0
  259. package/dist/services/payment/ensure/chargebee-products.js +359 -0
  260. package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
  261. package/dist/services/payment/ensure/paypal-account.js +36 -0
  262. package/dist/services/payment/ensure/paypal-products.js +374 -0
  263. package/dist/services/payment/ensure/paypal-webhook.js +128 -0
  264. package/dist/services/payment/ensure/stripe-account.js +93 -0
  265. package/dist/services/payment/ensure/stripe-disputes.js +56 -0
  266. package/dist/services/payment/ensure/stripe-products.js +245 -0
  267. package/dist/services/payment/ensure/stripe-radar.js +74 -0
  268. package/dist/services/payment/ensure/stripe-webhook.js +130 -0
  269. package/dist/services/payment/index.js +163 -0
  270. package/dist/services/payment/lib/chargebee-api.js +338 -0
  271. package/dist/services/payment/lib/payment-utils.js +116 -0
  272. package/dist/services/payment/lib/paypal-api.js +354 -0
  273. package/dist/services/payment/lib/provider-setup.js +134 -0
  274. package/dist/services/payment/lib/stripe-api.js +152 -0
  275. package/dist/services/repo/ensure/org.js +70 -0
  276. package/dist/services/repo/ensure/pages.js +72 -0
  277. package/dist/services/repo/ensure/repo.js +90 -0
  278. package/dist/services/repo/index.js +52 -0
  279. package/dist/services/repo/lib/github-api.js +196 -0
  280. package/dist/services/search/ensure/ga-link.js +57 -0
  281. package/dist/services/search/ensure/property.js +137 -0
  282. package/dist/services/search/ensure/sitemaps.js +70 -0
  283. package/dist/services/search/index.js +61 -0
  284. package/dist/services/search/lib/search-console-api.js +92 -0
  285. package/dist/services/seo/ensure/github-repos.js +271 -0
  286. package/dist/services/seo/index.js +55 -0
  287. package/dist/services/seo/lib/gh-api.js +161 -0
  288. package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
  289. package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
  290. package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
  291. package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
  292. package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
  293. package/dist/services/seo/templates/index.js +57 -0
  294. package/dist/services/server/ensure/brands.js +63 -0
  295. package/dist/services/server/index.js +42 -0
  296. package/dist/services/testing/ensure/target-checks.js +97 -0
  297. package/dist/services/testing/index.js +11 -0
  298. package/dist/services/testing/lib/checks.js +467 -0
  299. package/dist/services/update/index.js +20 -0
  300. package/dist/services/update/lib/cache.js +59 -0
  301. package/dist/services/update/lib/fingerprint.js +157 -0
  302. package/dist/services/update/write/targets.js +206 -0
  303. package/dist/services/workspace/ensure/agents.js +42 -0
  304. package/dist/services/workspace/ensure/claude-settings.js +34 -0
  305. package/dist/services/workspace/ensure/config.js +54 -0
  306. package/dist/services/workspace/ensure/defaults.js +60 -0
  307. package/dist/services/workspace/ensure/env-keys.js +55 -0
  308. package/dist/services/workspace/ensure/env-order.js +66 -0
  309. package/dist/services/workspace/ensure/env-rules.js +97 -0
  310. package/dist/services/workspace/ensure/gitignore.js +18 -0
  311. package/dist/services/workspace/ensure/scripts.js +126 -0
  312. package/dist/services/workspace/ensure/structure.js +97 -0
  313. package/dist/services/workspace/ensure/translation-sdk.js +148 -0
  314. package/dist/services/workspace/ensure/workflows.js +54 -0
  315. package/dist/services/workspace/index.js +8 -0
  316. package/dist/vendor/account/engine.js +182 -0
  317. package/dist/vendor/account/features.js +220 -0
  318. package/dist/vendor/account/index.js +53 -0
  319. package/dist/vendor/account/schema.js +272 -0
  320. package/dist/vendor/account/subscription.js +38 -0
  321. package/dist/vendor/config/company.js +31 -0
  322. package/dist/vendor/config/defaults.js +173 -0
  323. package/dist/vendor/config/demo.js +18 -0
  324. package/dist/vendor/config/desktop-artifacts.js +110 -0
  325. package/dist/vendor/config/edit.js +769 -0
  326. package/dist/vendor/config/env-delivery.js +145 -0
  327. package/dist/vendor/config/env-rules.js +93 -0
  328. package/dist/vendor/config/env-schema.js +1078 -0
  329. package/dist/vendor/config/env.js +445 -0
  330. package/dist/vendor/config/hooks.js +97 -0
  331. package/dist/vendor/config/index.js +237 -0
  332. package/dist/vendor/config/instances.js +208 -0
  333. package/dist/vendor/config/load.js +490 -0
  334. package/dist/vendor/config/merge.js +68 -0
  335. package/dist/vendor/config/order.js +139 -0
  336. package/dist/vendor/config/ports.js +374 -0
  337. package/dist/vendor/config/providers.js +32 -0
  338. package/dist/vendor/config/repo.js +142 -0
  339. package/dist/vendor/config/retired-keys.js +430 -0
  340. package/dist/vendor/config/schema.js +1610 -0
  341. package/dist/vendor/config/secrets.js +50 -0
  342. package/dist/vendor/config/seed.js +34 -0
  343. package/dist/vendor/config/site-global.js +205 -0
  344. package/dist/vendor/config/validate.js +554 -0
  345. package/dist/vendor/config/winback.js +61 -0
  346. package/dist/vendor/devkit/attach-log-file.js +262 -0
  347. package/dist/vendor/devkit/certs.js +199 -0
  348. package/dist/vendor/devkit/ci-workflows.js +520 -0
  349. package/dist/vendor/devkit/cli-router.js +156 -0
  350. package/dist/vendor/devkit/command-path.js +46 -0
  351. package/dist/vendor/devkit/deploy-record.js +180 -0
  352. package/dist/vendor/devkit/flows.js +317 -0
  353. package/dist/vendor/devkit/local-https.js +360 -0
  354. package/dist/vendor/devkit/local.js +1905 -0
  355. package/dist/vendor/devkit/logger.js +128 -0
  356. package/dist/vendor/devkit/omega-bin.js +345 -0
  357. package/dist/vendor/devkit/prompt.js +187 -0
  358. package/dist/vendor/devkit/safe-install.js +18 -0
  359. package/dist/vendor/devkit/stop-signals.js +28 -0
  360. package/dist/vendor/devkit/test/scope.js +162 -0
  361. package/dist/vendor/devkit/translate/cache.js +84 -0
  362. package/dist/vendor/devkit/translate/engine.js +243 -0
  363. package/dist/vendor/devkit/translate/index.js +49 -0
  364. package/dist/vendor/devkit/translate/languages.js +134 -0
  365. package/dist/vendor/devkit/translate/providers.js +188 -0
  366. package/dist/vendor/devkit/update.js +569 -0
  367. package/docs/AGENTS.md +200 -0
  368. package/docs/account.md +45 -0
  369. package/docs/advertising.md +46 -0
  370. package/docs/ai.md +34 -0
  371. package/docs/analytics.md +69 -0
  372. package/docs/assets.md +57 -0
  373. package/docs/bookmark.md +26 -0
  374. package/docs/brand.md +151 -0
  375. package/docs/campaigns.md +96 -0
  376. package/docs/captcha.md +46 -0
  377. package/docs/certificates.md +68 -0
  378. package/docs/chat.md +47 -0
  379. package/docs/cloud.md +110 -0
  380. package/docs/company.md +64 -0
  381. package/docs/directory.md +140 -0
  382. package/docs/disperse.md +46 -0
  383. package/docs/domain.md +56 -0
  384. package/docs/edge.md +234 -0
  385. package/docs/email.md +42 -0
  386. package/docs/forms.md +48 -0
  387. package/docs/index.md +109 -0
  388. package/docs/migration.md +203 -0
  389. package/docs/migrations.md +56 -0
  390. package/docs/monitoring.md +41 -0
  391. package/docs/newsletter.md +48 -0
  392. package/docs/payment.md +83 -0
  393. package/docs/repo.md +48 -0
  394. package/docs/search.md +44 -0
  395. package/docs/seo.md +34 -0
  396. package/docs/server.md +37 -0
  397. package/docs/shared/agent-docs.md +89 -0
  398. package/docs/shared/analytics.md +612 -0
  399. package/docs/shared/brands.md +51 -0
  400. package/docs/shared/breaking-changes.md +497 -0
  401. package/docs/shared/config.md +1387 -0
  402. package/docs/shared/deploys.md +215 -0
  403. package/docs/shared/icons.md +201 -0
  404. package/docs/shared/local-dev.md +147 -0
  405. package/docs/shared/logging.md +202 -0
  406. package/docs/shared/monitoring.md +153 -0
  407. package/docs/shared/publishing.md +183 -0
  408. package/docs/shared/rulings.md +34 -0
  409. package/docs/shared/testing.md +147 -0
  410. package/docs/shared/theming.md +604 -0
  411. package/docs/shared/translation.md +291 -0
  412. package/docs/shared/updates.md +61 -0
  413. package/docs/testing.md +34 -0
  414. package/docs/update.md +36 -0
  415. package/docs/workspace.md +64 -0
  416. package/package.json +88 -0
@@ -0,0 +1,612 @@
1
+ # Analytics — the catalog, the one call, and where an event fires
2
+
3
+ > Every event any OMEGA surface counts — a web page, the desktop main process, the
4
+ > extension, a Cloud Function — is declared ONCE in a catalog and fired through ONE
5
+ > call, and each provider hears it in its own dialect
6
+ > ([#328](https://github.com/Omega-JS-Stack/omega/issues/328)).
7
+
8
+ This file is the CONTRACT, not a migration report: the package and its catalog are built
9
+ ([#382](https://github.com/Omega-JS-Stack/omega/issues/382)), the web consent gate is built
10
+ ([#383](https://github.com/Omega-JS-Stack/omega/issues/383)), the server-side delivery is
11
+ built ([#385](https://github.com/Omega-JS-Stack/omega/issues/385)), and every front-end call
12
+ site now fires through the facade ([#386](https://github.com/Omega-JS-Stack/omega/issues/386)).
13
+ New code writes to what is below.
14
+
15
+ ## The shape
16
+
17
+ `@omega.js/analytics` is a private workspace package — it never publishes, and prepare
18
+ vendors it into the published frameworks (the `@omega.js/account` pattern, HARD RULE 3 in
19
+ the repo map; [publishing.md](publishing.md)). It is CJS, because a Cloud Function and the
20
+ Electron main process `require()` it while the browser bundles import it with standard
21
+ interop.
22
+
23
+ | File (`packages/analytics/src/`) | What it owns |
24
+ |---|---|
25
+ | `index.js` | The facade — `event()`, `configure()`, and the fire walk |
26
+ | `catalog.js` | The SSOT: one entry per canonical event, with every provider mapping |
27
+ | `adapters/resolve.js` | The shared adapter mechanism (catalog lookup → mapping → payload) |
28
+ | `adapters/ga4.js` · `meta.js` · `tiktok.js` | One file per provider: its consent category and where attribution attaches |
29
+ | `transports/browser.js` | Executes a descriptor against the page globals, guarded ([#306](https://github.com/Omega-JS-Stack/omega/issues/306)) |
30
+ | `consent.js` | The consent seam — `createConsentGate(providerFn)`, categories, `GRANT_ALL` |
31
+ | `core.js` | GA4 Measurement Protocol semantics (device_id/client_id/user_id derivation, payload shape) |
32
+ | `logger.js` | This package's `[@omega.js/analytics:<module>]` tag ([logging.md](logging.md)) |
33
+
34
+ **The one call**, from client code and backend code alike:
35
+
36
+ ```js
37
+ analytics.event('purchase', { transaction_id: id, value: 19, currency: 'USD', items: [...] });
38
+ ```
39
+
40
+ One fire walks three steps per provider, in this order:
41
+
42
+ 1. **consent** — a blocked category's providers never even resolve
43
+ 2. **adapter** — this provider's catalog mapping, or `null` when it has none
44
+ 3. **transport** — the host's seam; a missing or blocked page global is a silent no-op
45
+
46
+ Nothing in that walk throws at a visitor. A transport that could not deliver returns
47
+ `false`, and the dev log is the only trace. That guard is the WHOLE contract for an
48
+ unconfigured or blocked provider ([#606](https://github.com/Omega-JS-Stack/omega/issues/606)):
49
+ no surface defines a placeholder `gtag`/`fbq`/`ttq`, because a stub only makes a pixel
50
+ that never loaded read as present. @omega.js/web's page chrome carries none, and
51
+ `test/analytics-blocked.test.js` fails any template or module that names one.
52
+
53
+ **The host injects its seams** — nothing is sniffed:
54
+
55
+ ```js
56
+ analytics.configure({ transport, consent, context, environment });
57
+ ```
58
+
59
+ `transport` is `{ send(descriptor) => boolean }`; `consent` is a gate from
60
+ `createConsentGate`; `context` is `{ attribution, consent, runtime }`; `environment` mirrors
61
+ the client's `config.environment` seam and defaults to `'production'`. An unconfigured
62
+ runtime is INERT — it resolves and logs, and delivers nothing — never a guess at page
63
+ globals.
64
+
65
+ **Who injects what**, per runtime:
66
+
67
+ | Host | transport | consent | context | environment |
68
+ |---|---|---|---|---|
69
+ | web page (`@omega.js/web` `core/js/libs/analytics.js`) | the guarded browser transport | the banner's record, read live | attribution + `runtime: 'web'` | — |
70
+ | `@omega.js/client` | the browser transport on web; the Measurement Protocol in the extension; NONE in a desktop renderer, which forwards over the IPC bridge instead ([#411](https://github.com/Omega-JS-Stack/omega/issues/411)) | — | `runtime` in the extension | the brand's `config.environment` |
71
+ | `@omega.js/desktop` main process | its own Measurement Protocol fetch, and the ONE sender for the whole install — its own windows' events included | — | `runtime: 'electron'` | the app's dev flag |
72
+ | `@omega.js/backend` | the three HTTP APIs (`libraries/analytics/conversions.js`) | the order's/user's snapshot | the order's attribution | — |
73
+
74
+ Web core reaches the package THROUGH `@omega.js/client` (`@omega.js/client/modules/analytics.js`),
75
+ never as a bare `@omega.js/analytics`: the package is private, so in a consumer install it
76
+ exists only as the copy vendored into the client's dist — and the client is a real runtime
77
+ dependency of every framework.
78
+
79
+ **A fire may also carry options**: `event(name, params, { eventId, providers })`. `eventId`
80
+ is the platform dedupe key both halves of a two-sided event name (Meta's fourth pixel
81
+ argument `eventID`, TikTok's `event_id`). GA4 has no event-id slot — it deduplicates
82
+ ecommerce on the `transaction_id` PARAM instead
83
+ ([Google](https://support.google.com/analytics/answer/12313109),
84
+ [#656](https://github.com/Omega-JS-Stack/omega/issues/656)), which is why both halves of a
85
+ `purchase` send the same one. `providers` restricts a fire to the providers THIS side owns —
86
+ which is how a `both` event splits without double-counting.
87
+
88
+ **An unknown event name is a programmer error.** It throws in development (a typo must not
89
+ silently cost a conversion) and is logged-and-skipped in production, where the throw would
90
+ take the customer's action with it.
91
+
92
+ ## The catalog contract
93
+
94
+ One entry per canonical event; callers only ever speak canonical names and canonical
95
+ params. There is no unified cross-provider signature — each adapter speaks its provider's
96
+ dialect (Ian's constraint). The canonical vocabulary is GA4-flavoured (flat params plus an
97
+ `items` array), so GA4 is pass-through and the `map()`s live where the ad platforms
98
+ genuinely disagree.
99
+
100
+ ```js
101
+ add_to_cart: {
102
+ params: ['currency', 'value', 'items'],
103
+ placement: 'client',
104
+ providers: {
105
+ ga4: { name: 'add_to_cart', kind: 'standard' },
106
+ meta: { name: 'AddToCart', kind: 'standard', map: metaCommerce },
107
+ tiktok: { name: 'AddToCart', kind: 'standard', map: tiktokCommerce },
108
+ },
109
+ },
110
+ ```
111
+
112
+ - **`params`** — the canonical param contract; documentation for callers.
113
+ - **`placement`** — `'client' | 'server' | 'both'` (the rule below).
114
+ - **`providers`** — the per-provider mapping. Each carries `name` (the provider's NATIVE
115
+ event name), `kind`, and an optional `map(params, context) => payload` (default:
116
+ pass-through).
117
+ - **`method`** — the rare fourth key, browser only: the provider's own PIXEL METHOD to call
118
+ instead of its tracked-event command, for a signal the platform manages itself rather than
119
+ exposing as a trackable name. Exactly one mapping names one today, TikTok's `page_view` →
120
+ `ttq.page()` ([#409](https://github.com/Omega-JS-Stack/omega/issues/409)); it takes no name
121
+ and no payload, the pixel reads the page, and `name`/`kind` stay declared for the catalog
122
+ and the fire log.
123
+ - **`actionSource`** — the fifth key, Meta only and SERVER only: the `action_source` this
124
+ event's conversion carries instead of the default `website`. Meta requires the field and
125
+ asks that it be accurate, and its enum names the case itself, `system_generated`, "for
126
+ example, a subscription renewal that's set to auto-pay each month". So
127
+ `subscription_renew`, `payment_recovered` and `trial_convert` declare it: a card was billed
128
+ with nobody on the site ([#498](https://github.com/Omega-JS-Stack/omega/issues/498)). A
129
+ cancellation the subscriber clicked is a website conversion like any other and declares
130
+ nothing.
131
+
132
+ **The two commerce dialects**, as the 2026-09-02 standards review against the live references
133
+ left them ([#498](https://github.com/Omega-JS-Stack/omega/issues/498)). The browser and the
134
+ server halves send the SAME payload for one event, because that is what they deduplicate on:
135
+
136
+ - **Meta** `custom_data` carries `content_ids` AND `contents` (`{ id, quantity, item_price }`,
137
+ Meta's own field names; its pixel reference asks for `contents` or `content_ids` on a catalog campaign),
138
+ `content_type: 'product'`, `currency`, `value`, `num_items` (the UNITS bought, summed over
139
+ the items, not the number of lines), `order_id` from the canonical `transaction_id`, and
140
+ `content_name` only on an event with exactly ONE product, since Meta documents it as the
141
+ singular name of the page/product.
142
+ - **TikTok** `properties` carries `content_ids` AND `contents`
143
+ (`{ content_id, content_name, price, quantity }`), `content_type: 'product'` at the
144
+ PROPERTIES level where Events API 2.0 documents it (its `contents` object has no per-item
145
+ `content_type`; the older Pixel SDK model's does), `currency`, `value` (the order total,
146
+ where a `price` is one item's) and `order_id`.
147
+ - A churn moment's exclusion signal keeps neither platform's priced fields: Meta's `contents`
148
+ comes off whole (its `item_price` IS the amount, and the object requires `id` + `quantity`),
149
+ TikTok's keeps the array minus `price`/`quantity`, and `content_ids` names the plan on both.
150
+
151
+ **The three mapping kinds** — and the third one is an absence:
152
+
153
+ | Kind | Meaning | What the transport does |
154
+ |---|---|---|
155
+ | `standard` | The platform defines this event | Meta `fbq('track', …)`, the platform's own event |
156
+ | `custom` | A deliberate custom event (often preserving today's data continuity, e.g. Meta `ExitPopupShow`) | Meta `fbq('trackCustom', …)` |
157
+ | *absent* | This provider has no mapping for this event | The adapter returns `null`; the facade dev-logs `skipped (no mapping)` |
158
+
159
+ **Unmapped stays unmapped.** An absent mapping is a decision, not a gap to fill: inventing a
160
+ custom event so a provider "has coverage" ships junk into an ad account nobody will ever
161
+ read. Equally, a mapping must be HONEST — several pre-OMEGA call sites claimed standard
162
+ names their platform does not define, and those are `custom` here.
163
+
164
+ **Every name, kind and param is checked LIVE against the platform's own spec before it is
165
+ written or changed** (Ian 2026-08-27,
166
+ [#652](https://github.com/Omega-JS-Stack/omega/issues/652)) — never from memory, because a
167
+ platform renames and retires events (TikTok retired `CompletePayment` for `Purchase`, and
168
+ `ClickButton` was never on its standard list at all). The pages sit in the catalog
169
+ header so no edit can miss them:
170
+
171
+ | Spec | What it settles |
172
+ |---|---|
173
+ | [GA4 recommended events](https://support.google.com/analytics/answer/9267735) | Which canonical names are GA4 standards |
174
+ | [Meta standard events](https://www.facebook.com/business/help/402791146561655) | The exact wire name, and what each event MEANS (Purchase requires `value` + `currency`) |
175
+ | [Meta Pixel reference](https://developers.facebook.com/docs/meta-pixel/reference) | Which params each standard event takes, and which a catalog campaign needs (`contents` or `content_ids`) |
176
+ | [TikTok standard events](https://ads.tiktok.com/help/article/standard-events-parameters) | The current standard roster and its recommended params |
177
+ | [TikTok Events API 2.0](https://business-api.tiktok.com/portal/docs?id=1771100865818625) | The server half's envelope, `properties` and `contents` fields (the 2.0 shape, which is not the Pixel SDK's) |
178
+ | [GA4 transaction_id dedupe](https://support.google.com/analytics/answer/12313109) | The one PARAM that binds: one id per charge |
179
+
180
+ A `kind: 'standard'` may only ever claim a name the platform lists; anything else is
181
+ `custom`, under the canonical event's own PascalCase name. `packages/analytics/test/catalog.test.js`
182
+ pins both rosters, so a mapping that drifts off them fails.
183
+
184
+ **Adding an event is one catalog entry plus a test.** The entry lands in `catalog.js` under
185
+ its section, and `packages/analytics/test/catalog.test.js` gets the resolve case that proves
186
+ the mapping (name, kind, and the payload each provider receives). The shape guards —
187
+ placement valid, every mapping carrying a name and a known kind — already run over the whole
188
+ catalog, so a malformed entry fails without new test code.
189
+
190
+ **Naming.** Canonical names are snake_case and follow GA4's standard names where one exists
191
+ (`file_download`, not `download`). A custom name is `{category}_{action}` with the action in
192
+ PRESENT/imperative form — `trial_start`, `subscription_cancel`, `user_delete`, never the past
193
+ tense of the fact ([#416](https://github.com/Omega-JS-Stack/omega/issues/416)) — because that
194
+ is how GA4, Meta and TikTok all name a conversion; a platform's own standard WIRE name stays
195
+ verbatim wherever a mapping uses one (Meta and TikTok both spell it `StartTrial`), but a PascalCase
196
+ wire WE coin for a `custom` mapping obeys the same tense rule as the canonical it carries
197
+ (`SubscriptionCancel`, `Refund`, never the past-tense form). Action buckets
198
+ stay buckets — `user_billing_action`, `user_refund_request`, `user_section_view` carry an
199
+ `action`/`section` param rather than sprawling into an event per button.
200
+ Two events that look similar are only merged when they
201
+ answer the same question: `marketing_newsletter_subscribe` and `status_subscribe` are deliberately
202
+ distinct. A family's names move TOGETHER — the marketing opt-ins and their opt-outs all carry
203
+ the `marketing_` prefix (`marketing_email_subscribe` / `marketing_email_unsubscribe`), while
204
+ `status_subscribe` keeps none because status-page updates are operational, not marketing.
205
+
206
+ ## The placement rule
207
+
208
+ **Money and account truth fires SERVER-side. UI actions fire CLIENT-side. `sign_up` fires
209
+ BOTH.**
210
+
211
+ | Placement | Why | Examples |
212
+ |---|---|---|
213
+ | `server` | The browser cannot be trusted for revenue, and the outcome is only known where it happened | `refund`, `trial_convert`, `trial_lapse`, `subscription_cancel`, `subscription_uncancel`, `subscription_plan_change`, `subscription_renew`, `payment_recovered`, `user_delete` |
214
+ | `client` | The event IS the interaction — nothing server-side ever sees it | `view_item`, `add_to_cart`, `begin_checkout`, `add_payment_info`, `page_view`, `file_download`, the exit-popup, consent, notification-permission and account-navigation events |
215
+ | `both` | Ad platforms need the browser signal for retargeting AND the server signal for truth | `sign_up`, `purchase`, `trial_start` |
216
+
217
+ When both halves fire, both must carry the SAME event id so the platform collapses them into
218
+ one conversion (Meta's deduplication, TikTok's `event_id`). Sending both without the id is
219
+ double-counting; sending only the server half loses the retargeting signal — which is exactly
220
+ the hole the commented-out purchase pixel left
221
+ ([#302](https://github.com/Omega-JS-Stack/omega/issues/302)).
222
+
223
+ **GA4 deduplicates `purchase` on `transaction_id`** — web streams only, and never on an
224
+ empty string ([Google](https://support.google.com/analytics/answer/12313109)). It has no
225
+ event-id slot, so which providers each half owns is decided per event, and the three `both`
226
+ events split it three different ways:
227
+
228
+ | Event | The dedupe id | Browser half | Server half |
229
+ |---|---|---|---|
230
+ | `sign_up` | `sign_up.<uid>` — the uid is the only thing both sides hold before either fires | GA4 + Meta + TikTok (`libs/auth/tracking.js`) | Meta + TikTok (`routes/user/signup` → `libraries/analytics/signup.js`, the post-auth request — never the auth trigger, which has no request behind it, [#577](https://github.com/Omega-JS-Stack/omega/issues/577)) |
231
+ | `purchase` | `purchase.<order id>` for the ad platforms, and the same `<order id>` as `transaction_id` for GA4 | GA4 + Meta + TikTok (`pages/payment/confirmation/modules/tracking.js`) | GA4 + Meta + TikTok (`events/firestore/payments-webhooks/analytics.js`) |
232
+ | `trial_start` | `trial_start.<order id>` | Meta + TikTok (`pages/payment/confirmation/modules/tracking.js`) — GA4's `trial_start` is a CUSTOM event with no dedupe, so the server keeps it | GA4 + Meta + TikTok (`events/firestore/payments-webhooks/analytics.js`) |
233
+
234
+ GA4 rides the browser purchase because the id makes it safe, and the browser is where the
235
+ session, the campaign and the `client_id` are — a webhook has none of them
236
+ ([#656](https://github.com/Omega-JS-Stack/omega/issues/656)). And the browser half fires
237
+ `trial_start`, not `purchase`, on a trial checkout: `?track=true` rides EVERY checkout, and
238
+ two different event NAMES never deduplicate, so the old browser Purchase collapsed into
239
+ nothing and each platform counted it as a second conversion beside the server's trial. The
240
+ value was never the defect — the intent route already sends `amount=0` on a trial's
241
+ confirmation URL ([#654](https://github.com/Omega-JS-Stack/omega/issues/654)).
242
+
243
+ **`transaction_id` is ONE CHARGE's id, never the subscription's** (Ian 2026-08-27):
244
+
245
+ | The charge | The id | Why |
246
+ |---|---|---|
247
+ | First purchase — a paid checkout, a one-time buy, or a trial start | the ORDER id | The only id the confirmation page holds, so both halves match |
248
+ | Renewal, recovery, trial conversion | the provider's own charge id (Stripe/Chargebee invoice, PayPal sale), carried on the webhook event as `chargeId` | A subscription bills against one order forever; the subscription id collapsed every renewal after the first into a GA4 duplicate |
249
+ | Refund | the id of the charge it reverses — the provider's `chargeId` on a subscription, the ORDER id on a one-time buy (its single charge, and no provider names one on the refund) | So GA4 nets the two |
250
+ | Everything else (cancel, uncancel, plan change, lapse) | the subscription id | A GA4 CUSTOM event with no dedupe, and the subscription really is its subject |
251
+ | THE EXCEPTION — the trial-lapse sweep's own `trial_convert` (`events/cron/daily/trial-lapse-sweep.js`) | the subscription id | The provider never announced the charge (PayPal fires no trial-end event at all), so this path has no invoice to name. `trial.outcome` is stamped once, which keeps it to one fire |
252
+
253
+ Revenue is the server's, because a browser cannot be trusted with it; a registration's GA4
254
+ count is the browser's, because that is where the funnel it belongs to lives.
255
+
256
+ An entry's `placement` is where the rule is written down per event, so a call site in the
257
+ wrong tier is a reviewable finding, not a judgment call.
258
+
259
+ ## Consent
260
+
261
+ Nothing counts before the visitor's answer is known. Two tiers, chosen by the browser's own
262
+ timezone — no network call, nothing to consent to before consent exists
263
+ (`packages/web/core/js/libs/consent-region.js`):
264
+
265
+ - **Opt-in regions — a STRICT ROSTER**: `Europe/*` (the EEA, the UK, and Turkey, which rides
266
+ the prefix), the four Atlantic zones the EEA reaches outside it (Reykjavik, Canary, Madeira,
267
+ Azores), Brazil's sixteen zones (LGPD), China (PIPL: Asia/Shanghai, Asia/Urumqi) and South
268
+ Korea (PIPA: Asia/Seoul). No provider script loads until Accept, and the banner is the GATE
269
+ that asks. Google Consent Mode's `consent default` is queued into `dataLayer` before gtag.js
270
+ can load (denied), and every change pushes a `consent update`, so the tag itself honors the
271
+ flags on top of us not loading it. A timezone we cannot place reads as opt-in — the only safe
272
+ direction.
273
+ - **The join rule**: a country joins only when its law genuinely requires opt-in consent for
274
+ tracking cookies and that is verified; when in doubt it stays out
275
+ ([#423](https://github.com/Omega-JS-Stack/omega/issues/423)). Quebec's Law 25 qualifies but
276
+ cannot be expressed — America/Montreal aliases to America/Toronto — so Canada stays out.
277
+ - **Everywhere else**: auto opt-in, scripts load immediately, and a first visit sees **no
278
+ banner at all** ([#391](https://github.com/Omega-JS-Stack/omega/issues/391)) — only the
279
+ small Cookies Settings tab, which reopens the full panel for anyone who wants to turn things
280
+ off. There is nothing to gate and nothing worth interrupting, so `cookie_banner_show` fires
281
+ only where the banner actually shows.
282
+
283
+ The record is `trackingConsent` in the client storage blob
284
+ (`packages/web/core/js/libs/tracking-consent.js`): `{ analytics, marketing, region,
285
+ timestamp, version }`. Two categories, because that is what a visitor can meaningfully
286
+ answer — `analytics` (GA4) and `marketing` (Meta, TikTok); "necessary" is not a category
287
+ because nothing about it is optional. Bumping `TRACKING_CONSENT_VERSION` re-prompts everyone,
288
+ which is what changing what a category COVERS requires.
289
+
290
+ - The banner is `packages/web/core/js/core/consent.js`: a cookie-iconed message, one big
291
+ Accept, and a small Customize opening the panel — a dense legal intro, a row of pill
292
+ switches (Necessary locked on, then Analytics and Marketing), then **Accept all / Accept
293
+ none** bottom-right. Every switch APPLIES AND SAVES the moment it is flipped and the panel
294
+ stays open, so there is no Save to forget to press; either button flips every switch to
295
+ match, saves, and collapses to the tab. Refusal is one click at the same size as the grant
296
+ (the EU equal-ease rule). The loader that actually gates the scripts is
297
+ `packages/web/core/js/core/analytics-loader.js`, and it runs FIRST in `main.js` — nothing
298
+ may count an event ahead of that decision. That loader counts NOTHING of its own: the Meta
299
+ and TikTok page views it used to fire raw at pixel init now go through the facade like every
300
+ other event ([#409](https://github.com/Omega-JS-Stack/omega/issues/409)), restricted to the
301
+ pixel that just installed, since GA4 counts its own page view off the `config` command.
302
+ - **A save that grants nothing IS the denial** — Accept none and an all-off panel are the
303
+ same answer and fire the same `cookie_consent_deny`. That fire is made AFTER the record is
304
+ written, so the gate the visitor just closed is the one it is asked about: a denial is
305
+ honored, not counted.
306
+ - Granting a category injects its loader immediately, no reload. REVOKING cannot unload a
307
+ running script, so it takes effect on the next page load while Consent Mode updates now.
308
+ - In the package, each adapter declares its `CONSENT_CATEGORY` and the facade asks the gate
309
+ once per provider per event. The gate reads its provider function LIVE, so a visitor who
310
+ accepts mid-session is counted from that moment with nothing re-configuring. The default
311
+ when no host injects a gate is `GRANT_ALL` — desktop, extension and backend have no banner
312
+ to gate on.
313
+ - The same consent state gates the server-side match-data enrichment
314
+ ([#302](https://github.com/Omega-JS-Stack/omega/issues/302)): an opted-out user sends
315
+ nothing, from either side.
316
+ - **Absence is not denial.** Server-side, a null or absent `trackingConsent` snapshot
317
+ GRANTS both categories — only an explicit `false` blocks. Legacy orders predating the
318
+ consent system and the raw-API recovery lane (which mints orders with no client anywhere
319
+ near them) carry no snapshot at all, and refusing to report their revenue would be a
320
+ silent accounting hole rather than a privacy win.
321
+
322
+ **The `consent` name is taken.** `consent` in storage and in the signup payload is the
323
+ signup form's LEGAL record (`{ legal, marketing }`, captured by
324
+ `packages/web/core/js/libs/auth/forms.js` and interpreted by the backend signup route).
325
+ Tracking consent is `trackingConsent` — key, exports, and payload field — everywhere. Sharing
326
+ one key would read a tracking answer as a revoked terms agreement.
327
+
328
+ ## Attribution
329
+
330
+ Attribution is captured once and CARRIED; no event assembles its own.
331
+
332
+ - **The shape**: `attribution: { first, last, affiliate }` in client storage — first-touch
333
+ written on the first visit ever seen and never overwritten, last-touch replaced only by a
334
+ TAGGED visit, both timestamped (any lookback window is computed at read time).
335
+ - **Where it attaches**: the user doc at signup, `payments-intents` at creation, and
336
+ `payments-orders` via the intent fold. The tracking-consent snapshot rides the same
337
+ payloads under `trackingConsent`.
338
+ - **The reserved slot**: every event carries attribution through the facade's `context`, not
339
+ through its params — `configure({ context })` → `resolve(name, params, context)` → each
340
+ adapter attaches it the way ITS provider wants: GA4 takes campaign fields as flat event
341
+ params, while Meta (`fbc`, `fbp`) and TikTok (`ttclid`, `ttp`) take theirs in the
342
+ descriptor's `userData` match block, never in the event's custom data. The touch's
343
+ `url`/`referrer` ride the same slot server-side ([#497](https://github.com/Omega-JS-Stack/omega/issues/497)):
344
+ Meta sends `event_source_url`, TikTok a `page` object, GA4 nothing — omitted entirely
345
+ when the touch carried no url. A call site never hand-attaches attribution.
346
+
347
+ Capture and storage are [#384](https://github.com/Omega-JS-Stack/omega/issues/384); the
348
+ server-side delivery with full match data (hashed email/phone, IP, user agent, click ids) is
349
+ [#385](https://github.com/Omega-JS-Stack/omega/issues/385);
350
+ [#302](https://github.com/Omega-JS-Stack/omega/issues/302) owns the deep spec for both.
351
+
352
+ ## Running paid ads
353
+
354
+ Two systems count a paid campaign, they disagree by design, and each is right about a
355
+ different question. This is the reading guide, plus the one thing every campaign has to do
356
+ for either of them to work.
357
+
358
+ **Every paid ad link carries utm tags.** The system can only credit what the LINK declares:
359
+ the landing capture reads the utm tags off the URL, stores them as that visit's touch, and
360
+ carries the touch onto the user doc, the payment intent and the order. An untagged ad click
361
+ is an organic visit forever, in both ledgers below. So the destination URL of every ad, on
362
+ every platform, is tagged:
363
+
364
+ ```
365
+ https://brand.test/pricing?utm_source=meta&utm_medium=cpc&utm_campaign=launch-2026&utm_content=hero-video&utm_term=project-management
366
+ ```
367
+
368
+ | Tag | What it names |
369
+ |---|---|
370
+ | `utm_source` | The platform the click came from (`meta`, `tiktok`, `google`, `newsletter`) |
371
+ | `utm_medium` | How it was paid for (`cpc`, `paid_social`, `email`) |
372
+ | `utm_campaign` | The campaign, spelled the SAME way it is spelled in the ads manager |
373
+ | `utm_content` | The creative, so two ads in one campaign stay tellable apart |
374
+ | `utm_term` | The keyword or audience (optional: search and interest targeting) |
375
+
376
+ The platform's own click id (`fbclid`, `ttclid`, `gclid`) rides along on its own and is what
377
+ the server's match data links a conversion to. The utm tags are how a HUMAN reads the result.
378
+
379
+ **The two ledgers.** Both are honest; they measure different windows with different rules.
380
+
381
+ | Ledger | What it counts | Read it for |
382
+ |---|---|---|
383
+ | The ads manager (Meta Events Manager, TikTok Ads) | GROSS, short window: the conversions its own pixel and Conversions/Events API saw inside its attribution window, at the value that fired, credited its own way | Steering spend day to day, and feeding the optimizer the signal it bids on |
384
+ | GA4, `first user campaign` | NET, long run: everything a campaign's visitors ever did, renewals included and refunds netted out, credited to the campaign that FIRST brought them | Whether the campaign was worth running at all |
385
+
386
+ The ads manager runs ahead and reads high: it counts a conversion the moment its window says
387
+ so, it cannot subtract a refund, and each platform credits itself for clicks the other also
388
+ saw. GA4 runs behind and reads low: a renewal thirteen months out still lands on the campaign
389
+ that acquired the customer, and a refund nets against the purchase it reverses. Never subtract
390
+ one from the other and never average them. **The ads manager decides what to do today; GA4
391
+ decides whether to keep doing it.**
392
+
393
+ **Exclusion audiences are what a churn event is for.** Neither ad platform can subtract
394
+ revenue and neither optimizes against an event, but both can build an AUDIENCE from a custom
395
+ one ([#415](https://github.com/Omega-JS-Stack/omega/issues/415)). So the two churn moments
396
+ reach them as custom events worth zero: `subscription_cancel` as `SubscriptionCancel`,
397
+ `refund` as `Refund`, both `value: 0` because sending the real amount would ADD to the
398
+ return the ads manager reports. Build the platform's exclusion audience off those two, and
399
+ spend stops chasing people who already left. `trial_lapse` is deliberately NOT in that lane:
400
+ a lapsed trialist is a win-back audience worth RETARGETING, not somebody to hide ads from.
401
+
402
+ **The Measurement Protocol secret ships inside extension and desktop bundles, and that is an
403
+ accepted tradeoff** ([#413](https://github.com/Omega-JS-Stack/omega/issues/413), Ian's ruling
404
+ 2026-08-20). Those runtimes have no page pixels, so they report through GA4's Measurement
405
+ Protocol, which requires an `api_secret` in the request: a distributed bundle therefore
406
+ carries one, and anyone who unpacks it can extract it. The exposure is bounded and known: an
407
+ MP secret is WRITE-ONLY, it reads nothing back, and the worst an abuser can do is post junk
408
+ events into the property. That is data POLLUTION, not data theft, and it is a documented GA4
409
+ limitation rather than a bug here. The reopen trigger is real abuse: if junk events or secret
410
+ misuse ever show up in a live property, reopen #413 and spec the backend proxy (every
411
+ extension event routed through a Cloud Function, which is the cost the ruling declined to pay
412
+ up front).
413
+
414
+ ## Dev logging — one line per fire
415
+
416
+ In development the whole walk prints as ONE tagged line, the complete per-provider outcome
417
+ of that event:
418
+
419
+ ```
420
+ [@omega.js/analytics:events] add_to_cart → ga4 sent, meta sent, tiktok skipped (consent: marketing)
421
+ ```
422
+
423
+ The outcome vocabulary is the walk itself: `sent`, `skipped (consent: <category>)`,
424
+ `skipped (no mapping)`, `skipped (no transport)`, `blocked (no global)` — which is how a
425
+ blocker looks, since a blocked global is a silent no-op by contract. Production prints
426
+ nothing per fire.
427
+
428
+ This one line is the whole dev trace. It replaced web core's `setupTrackingInterceptors()`
429
+ (`packages/web/core/js/libs/dev.js`), which retired with the call-site rewire
430
+ ([#386](https://github.com/Omega-JS-Stack/omega/issues/386)): monkey-patching the globals
431
+ only ever saw the calls that survived the page's own guards, while the walk above reports the
432
+ skips too, and why.
433
+
434
+ ## Only production reaches a platform
435
+
436
+ **A server conversion is delivered in PRODUCTION and nowhere else.** An emulator boot seeds
437
+ personas, every seeded account fired the server half of `sign_up`, and Meta delivered dozens
438
+ of fake registrations to the brand's live pixel — a dev run polluting the ad data the
439
+ optimizer bids on ([#464](https://github.com/Omega-JS-Stack/omega/issues/464)). So
440
+ `deliverConversion` carries the gate `@omega.js/monitoring` and the Measurement Protocol
441
+ helper already carry: any non-production environment (development OR testing, the intentional
442
+ `!isProduction()` check) delivers nothing.
443
+
444
+ The gate sits AFTER the catalog resolve, so the walk still reports what WOULD have fired and
445
+ with what — a blocked send is information, not silence. Each blocked provider says so on its
446
+ own line, and the fire's summary line carries the outcome word `blocked (dev)` plus the match
447
+ keys that fire would have carried (providers separated by ` | `, since each summary has commas
448
+ of its own):
449
+
450
+ ```
451
+ [@omega.js/backend:omega_api] deliverConversion [meta]: sign_up blocked (dev) — nothing sent (event_id=sign_up.<uid>)
452
+ [@omega.js/backend:omega_api] deliverConversion: sign_up → ga4 skipped (not selected) | meta blocked (dev) sent em,fn,ln,external_id,client_ip_address,client_user_agent,fbp; empty ph,ct,st,zp,country,db,ge,fbc | tiktok blocked (dev) sent … (event_id=sign_up.<uid>)
453
+ ```
454
+
455
+ A real send is therefore proved the way **Verifying** says below — a live drive read in the
456
+ platform's own debugger — never by pointing a dev run at the live pixel.
457
+
458
+ ## Identity is not an event
459
+
460
+ An identity has no catalog entry, because it is not something that happened — it is a SETTING
461
+ the events after it inherit. Each provider takes its own: GA4's `set` (`user_id`, user
462
+ properties), the Meta Pixel's advanced-matching `init`, TikTok's `identify`. So it lives
463
+ beside the transport rather than inside the catalog, in exactly one module per runtime:
464
+
465
+ - **web** — `identify(user)` / `reset()` in `core/js/libs/analytics.js`, called by
466
+ `core/js/core/auth.js` off the auth state, guarded per provider like every other page call.
467
+ It sets GA4's user PROPERTIES, the Meta `init` and the TikTok `identify`. `external_id` is
468
+ each platform's own key and rides in the shape that platform's spec asks for: RAW for Meta,
469
+ which only RECOMMENDS hashing and whose own Pixel example passes a bare id, and SHA-256 for
470
+ TikTok, whose Events API REQUIRES the digest
471
+ ([#410](https://github.com/Omega-JS-Stack/omega/issues/410)). Every OTHER match
472
+ key is SHA-256 hashed before it reaches a pixel, normalized per that platform's own spec
473
+ (Meta hashes a phone as bare digits, TikTok as E.164), and the rules live in
474
+ `@omega.js/analytics/identity`. The server's match data agrees key for key: `match-data.js`
475
+ reads the SAME normalizers out of that module and carries both digests
476
+ (`metaPhoneHash` / `tiktokPhoneHash`, the browser's own key names, plus
477
+ `tiktokExternalIdHash`), so a pixel event and a server conversion for one person present
478
+ identical match keys
479
+ ([#392](https://github.com/Omega-JS-Stack/omega/issues/392)).
480
+ - **every runtime** — `@omega.js/client`'s `setUserId` / `setUserProperties`, which SEND on
481
+ web through the page's gtag and ride the Measurement Protocol payload elsewhere. The one
482
+ carve-out is a bridged desktop renderer: main owns identity, so its renderer forwards
483
+ `setUserProperties` over the bridge and `setUserId` throws loudly
484
+ ([#480](https://github.com/Omega-JS-Stack/omega/issues/480)). The
485
+ cross-surface value is `uuidv5(uid, namespace)`: the same human is the same `user_id` on a
486
+ page, in the desktop app, in the extension and from a Cloud Function.
487
+
488
+ **GA4's `user_id` has exactly ONE owner: the client.** Both modules run on a web page off the
489
+ same auth transition, so a second writer is simply the last writer — and web writing the raw
490
+ uid there would leave GA4 holding an id no other surface ever sends. `identify()` therefore
491
+ sets no `user_id`, and `reset()` clears none.
492
+
493
+ The client also fires `login` / `logout` off auth state — on every runtime EXCEPT web, where
494
+ the auth pages own them, because only the call site knows the method the visitor actually
495
+ used.
496
+
497
+ ### The device id every `client_id` starts from
498
+
499
+ `client_id` is `uuidv5(deviceId, namespace)`, and the deviceId under it comes from ONE
500
+ derivation — `core.deriveDeviceId({ get, set, seed })` — with each target injecting its own
501
+ world, on the `createRequest(deps)` mold
502
+ ([#396](https://github.com/Omega-JS-Stack/omega/issues/396)):
503
+
504
+ | Target | Persistence | Seed |
505
+ |---|---|---|
506
+ | web · extension (`@omega.js/client`) | `localStorage._omega_device_id` | none — a page can read nothing about the machine, so the generated UUID IS the id |
507
+ | desktop main (`lib/context.js`), for the whole install | electron-store `context.deviceId` | the first non-internal MAC, so a reinstalled app resolves the id it had before its storage was wiped |
508
+
509
+ A desktop RENDERER derives none and persists none ([#411](https://github.com/Omega-JS-Stack/omega/issues/411)):
510
+ its client is bridged, its events are delivered by main's sender, and main's id is the
511
+ install's identity. A renderer minting its own would be the identity fork the bridge exists to
512
+ prevent — one install counted as two GA clients.
513
+
514
+ The walk is **stored → seed → uuidv4**: a persisted id always wins (a desktop install survives
515
+ a NIC swap or a VPN), and the floor is the `uuid` package's `v4`, which is a real UUID in every
516
+ runtime — it uses the platform's `crypto.randomUUID` where that exists and `getRandomValues`
517
+ where it does not (an insecure origin), so no surface falls back to a random-looking string.
518
+
519
+ **One machine is NOT one GA client across surfaces**, and never was: each surface derives its
520
+ device id from its own storage, so the desktop app and the browser on one machine are two
521
+ `client_id`s. What unifies a human is `user_id`, and it rides ALONGSIDE the client id in every
522
+ payload rather than replacing it — providers need the stable client id for session stitching.
523
+
524
+ ## Match quality — every parameter, and where its value comes from
525
+
526
+ **The plumbing exists for EVERY match parameter each platform accepts, even where a brand does
527
+ not yet hold the value** (Ian's ruling, 2026-08-24,
528
+ [#577](https://github.com/Omega-JS-Stack/omega/issues/577)). An empty value compacts away as
529
+ it always has; what is never acceptable is a parameter the platform reads and we never wired.
530
+ Meta scored the playground's StartTrial 6.2/10 and its CompleteRegistration around 4/10 while
531
+ every server event carried `em` + `external_id` alone — and the account doc already held the
532
+ name, birthday, gender, location and phone.
533
+
534
+ **One normalization table per provider, and no shared "close enough" normalizer.** The rules
535
+ genuinely differ key by key, and a value normalized by the wrong platform's rule is ACCEPTED
536
+ by the API and matched to nobody — the same silent nothing an unhashed value is. The server's
537
+ tables live in `packages/backend/src/manager/libraries/analytics/match-data.js`; the browser
538
+ half's shared rules (email, phone, external id) live in `@omega.js/analytics/identity` so both
539
+ halves of one person present identical keys.
540
+
541
+ **Meta** — Conversions API `user_data`
542
+ ([customer-information parameters](https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters)).
543
+ Every key below is hashing-required except the last four.
544
+
545
+ | Key | Source | Normalization |
546
+ |---|---|---|
547
+ | `em` | `auth.email` (Auth's record on a signup) | trim, lowercase, SHA-256 |
548
+ | `ph` | `personal.telephone` `{ countryCode, national }` | digits only, country code included, SHA-256 |
549
+ | `fn` / `ln` | `personal.name.first` / `.last` | lowercase, no punctuation, SHA-256 |
550
+ | `ct` | `personal.location.city` | lowercase, no punctuation and NO SPACES (`newyork`), SHA-256 |
551
+ | `st` | `personal.location.region` | the 2-character ANSI code in lowercase (a US state NAME is looked up); other countries lowercase with no spaces, SHA-256 |
552
+ | `zp` | `personal.location.postalCode` — the account page's optional postal-code field ([#663](https://github.com/Omega-JS-Stack/omega/issues/663)); the bag key keeps Meta's own name | lowercase, no spaces or dashes, first five digits of a US zip, SHA-256 |
553
+ | `country` | `personal.location.country` | ISO 3166-1 alpha-2, lowercase, SHA-256 — a country NAME sends nothing |
554
+ | `db` | `personal.birthday` (the `$timestamp` pair) | `YYYYMMDD` in UTC, SHA-256 |
555
+ | `ge` | `personal.gender` | the lowercase initial, and Meta accepts `f`/`m` alone — anything else sends nothing |
556
+ | `external_id` | the uid | RAW (Meta only RECOMMENDS hashing, #410) |
557
+ | `client_ip_address` / `client_user_agent` | the request that created the checkout intent, or the post-auth signup request | never hashed |
558
+ | `fbc` / `fbp` | the browser's `_fbc`/`_fbp` cookies; `fbc` is CONSTRUCTED from a captured `fbclid` when the cookie never arrived | never hashed |
559
+
560
+ **TikTok** — Events API 2.0 `data[].user`
561
+ ([event/track/](https://business-api.tiktok.com/portal/docs?id=1771101303285761)): `email`,
562
+ `phone`, `external_id` (all three SHA-256 REQUIRED), `ttclid`, `ttp`, `ip`, `user_agent`.
563
+ **There is no name or address parameter** — its user object documents none, so the backend
564
+ sends none. A `first_name` or `zip_code` key there is read by nobody.
565
+
566
+ **GA4** — Measurement Protocol `user_data`
567
+ ([user-provided data](https://developers.google.com/analytics/devguides/collection/ga4/uid-data)),
568
+ built by `helpers/analytics.js` from the authenticated request's own user. Its rules are NOT
569
+ Meta's: `sha256_email_address` and `sha256_phone_number` (E.164 WITH the `+`), and an
570
+ `address` block whose `sha256_first_name` / `sha256_last_name` drop digits and symbols,
571
+ `sha256_street` keeps its digits, while `city`, `region` (the region NAME, not a code) and
572
+ `postal_code` ride in the CLEAR and `country` is UPPERCASE alpha-2. The address comes off the
573
+ account: `personal.location.street` and `personal.location.postalCode` are the account page's
574
+ optional fields ([#663](https://github.com/Omega-JS-Stack/omega/issues/663)). The account's own
575
+ `personal.location` wins over the request's geolocation, which stays the fallback.
576
+
577
+ **Where the signup's match data comes from.** The server half of `sign_up` fires from the
578
+ post-auth request (`routes/user/signup`, via `libraries/analytics/signup.js`), NOT from the
579
+ auth trigger: a trigger has no HTTP request behind it, so it could see no IP, no user agent,
580
+ no platform cookies and no attribution the browser had not posted yet. The browser posts the
581
+ cookies under `attribution.cookies` — the shape the checkout intent already sends — and the
582
+ route's own `flags.signupProcessed` gate keeps it to one fire per account. The dedupe id is
583
+ `sign_up.<uid>` on both halves, as before.
584
+
585
+ **Reading it back.** Two dev surfaces answer the two halves of "did this match?":
586
+
587
+ - the backend's fire log names, per provider, the keys that went and the accepted keys that
588
+ were empty — KEY NAMES ONLY, because a hashed email is still that person's email:
589
+ `deliverConversion: sign_up → meta blocked (dev) sent em,external_id,client_ip_address,client_user_agent; empty ph,fn,ln,… | tiktok …`
590
+ - the dev palette's **Ad match keys** section (the one dev surface,
591
+ [#342](https://github.com/Omega-JS-Stack/omega/issues/342)) shows the browser's side: the
592
+ consent state, which pixel scripts loaded, which platform cookies exist right now, and the
593
+ stored attribution's keys. The checkout intent's response echoes the cookie key names the
594
+ SERVER received, which is where a blocked pixel shows up.
595
+
596
+ ## Verifying
597
+
598
+ - **The package**: `npm test` in `packages/analytics` (and the root `test:packages` lane) —
599
+ catalog shape, per-provider resolve, the guarded transport, the consent gate, the dev log.
600
+ - **A call site**: drive the flow with a dev server running and read the fire-log line —
601
+ that is the proof the event fired, with what, to whom. Reading the template proves nothing;
602
+ the catalog is what decides.
603
+ - **A live drive**: the platforms' own debuggers — GA4 DebugView, Meta Events Manager's Test
604
+ Events, the TikTok Pixel Helper — and both consent regions (an opt-in timezone must show
605
+ nothing loading before Accept).
606
+
607
+ ## Enforcement
608
+
609
+ The checklist is the plugin's `omega:analytics` skill
610
+ ([agent-plugins/claude/skills/analytics/SKILL.md](../../agent-plugins/claude/skills/analytics/SKILL.md)),
611
+ and the quality hook fires it on every surface that owns an event: the web flow pages and the
612
+ auth modules, the backend payment routes and the auth/webhook events, and the package itself.