@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
package/docs/brand.md ADDED
@@ -0,0 +1,151 @@
1
+ # Working in a consumer brand monorepo
2
+
3
+ > Framework-owned. This is the brand-root guide: what a session inside ANY consumer brand needs before touching files. Brands reach it through the agent-docs chain (their root `AGENTS.md` imports the top-level omega `AGENTS.md` via `node_modules` — contract: [shared/agent-docs.md](shared/agent-docs.md)) and through the omega plugin's `omega:manager` skill. Brand-specific notes never go here; they live below the import in that brand's own `AGENTS.md`.
4
+
5
+ ## What you are working in
6
+
7
+ A **brand monorepo**: one brand (`config/omega.json5`), npm workspaces, one dir per enabled target under `targets/`. The `@omega.js/*` frameworks do the heavy lifting — targets stay thin (config + content + custom routes/pages).
8
+
9
+ | Target dir | Target | Framework | Required reading |
10
+ |---|---|---|---|
11
+ | `targets/website/` | web | `@omega.js/web` | the `omega:web` skill → `docs/web/index.md` |
12
+ | `targets/backend/` | backend | `@omega.js/backend` | the `omega:backend` skill → `docs/backend/index.md` |
13
+ | `targets/desktop/` | desktop | `@omega.js/desktop` | the `omega:desktop` skill → `docs/desktop/index.md` |
14
+ | `targets/extension/` | extension | `@omega.js/extension` | the `omega:extension` skill → `docs/extension/index.md` |
15
+ | `targets/<name>/` | custom ([#603](https://github.com/Omega-JS-Stack/omega/issues/603)) | none — the target's own stack | its own README; the contract is [index.md](index.md) § Custom targets |
16
+
17
+ **Before doing ANY work inside a target, read its framework's guide** — the omega plugin's inject hook loads the matching skill automatically in that target, and the skill points at the guide; architecture, conventions, APIs, and gotchas live there, not here.
18
+
19
+ A **custom target** is the exception: `targets.<name>: { type: 'custom' }` in the brand config declares a target no framework owns (a Render API, a worker, a script). It has no framework guide and no framework services — everything it can do is what its own `package.json` scripts (`start`, `build`, `test`, `deploy`, `clean`) declare, and the manager runs those.
20
+
21
+ ## Brand root anatomy
22
+
23
+ - `config/omega.json5` — THE brand config (shared sections + `targets.<type>`; key presence = target enabled). Targets in a brand carry NO config file of their own.
24
+ - `.env` — secrets, ALWAYS (the config loader hard-fails secret-shaped keys in omega.json5). Gitignored.
25
+ - `.env.development` / `.env.testing` / `.env.production` — the per-environment OVERLAYS of that `.env` ([#586](https://github.com/Omega-JS-Stack/omega/issues/586)). Only the keys that differ for one environment: the run's own environment picks exactly one file, its values win over the base, and every other environment's file is never read. Put a `sk_test_…` in `.env.development` and the live key in `.env`. Onboard scaffolds all three empty; `.env.*` is gitignored. The mechanics: [shared/config.md](shared/config.md#the-env-cascade-secrets--d15).
26
+ - `.omega/` — the secrets store, run output, caches, and the per-machine deploy record. Gitignored; never commit it. No durable state file: every provisioned fact the manage cycle resolves lands in `config/omega.json5`, every secret in `.env` ([#434](https://github.com/Omega-JS-Stack/omega/issues/434)).
27
+ - `targets/<target>/` — one workspace per enabled target (see table above). A brand still carrying the old `apps/` folder is fixed ONCE, by hand — `npx omega manage --migration=targets-rename --execute` (bare = the audit, moving nothing) renames the folder and flips the root `workspaces` glob with it; re-run `npm install` afterwards so npm re-links the workspaces. Nothing heals it inside a run: every other verb fails loud on the old shape and names that command. Both folders at once is a half-done migration and fails loudly rather than guessing ([shared/breaking-changes.md](shared/breaking-changes.md#one-vocabulary--a-brands-surfaces-live-in-targets-443)).
28
+ - `logs/` — the brand-level run logs, one file per brand-root verb: `logs/manage.log`, `logs/dev.log`, `logs/build.log`, `logs/clean.log`, `logs/deploy.log`, `logs/update.log`, `logs/test.log`, `logs/pipeline.log` ([#623](https://github.com/Omega-JS-Stack/omega/issues/623)). Gitignored, truncated on every launch.
29
+ - `AGENTS.md` / `CLAUDE.md` — the doc chain: `CLAUDE.md` is a one-line `@AGENTS.md` pointer; `AGENTS.md`'s first line imports the framework guide; everything below the import is the brand's own.
30
+
31
+ **The brand's repos are named `<brand.id>-<role>`** (Ian 2026-09-07, [#809](https://github.com/Omega-JS-Stack/omega/issues/809)). Two roles exist today: `omega`, the brand's SOURCE monorepo (the one you are working in), and `releases`, the one public repo the desktop releases and the autoupdater feed live in. Both are derived, never typed: `@omega.js/config` answers `<brand.id>-omega` for the source repo and `<brand.id>-releases` for the releases one, under the owner `repo.providers.github.org` names, so every verb that addresses a repo (`omega deploy --direct`, the manager's github service, desktop's release lane) reaches the same address. A brand whose repo is named something else declares it: `repo.providers.github.repo` takes a bare name or an `owner/name` slug and wins over the default, which is how a repo under the paid company org is addressed.
32
+
33
+ ### The placement contract — where a thing lives, and what refreshes it
34
+
35
+ **Authored-once shared things live at the brand ROOT and are distributed from there** (`config/omega.json5`, the `.env` files, the agent docs); **target code and every generated artifact live IN the target** (build hooks, logs, `dist/`, the composed per-target env). A file that is not authored is DERIVED: it refreshes on the verb that owns it and is never hand-edited ([#623](https://github.com/Omega-JS-Stack/omega/issues/623)).
36
+
37
+ The asymmetry the contract had to settle: `config/omega.json5` is **walked**, never copied — a key deleted at the root is gone from the next read everywhere. `.env` is walked too since [#678](https://github.com/Omega-JS-Stack/omega/issues/678) retired dispersal, so the ONE env copy left is the backend's `dist/.env`, recomposed by every verb that stages it. Neither surface can leave a deleted key behind.
38
+
39
+ One row per surface; **Siblings** is the set that must match each other (a new mechanism on one sibling and not the others is a defect):
40
+
41
+ | Surface | Authored where | Lands where | Mechanism | Siblings |
42
+ |---|---|---|---|---|
43
+ | Brand config | `<brandRoot>/config/omega.json5` | read in place — no copy | **walked** (`defaults ← company ← brand shared ← brand targets.<type> ← local`) | every target reads the one file through `@omega.js/config` |
44
+ | Env + secrets | `<brandRoot>/.env`, `.env.<environment>`, company `.env` under both; `.omega/secrets/` for provisioned key material; `targets/<target>/.env` is an optional per-key override | the run's own process env; backend also `targets/backend/dist/.env` | **walked** per verb; the backend `dist/.env` is **generated** per verb ([#678](https://github.com/Omega-JS-Stack/omega/issues/678)) | web · backend · desktop · extension compose from the same cascade (the runtime `environment()` accessor is the open parity gap: [#717](https://github.com/Omega-JS-Stack/omega/issues/717)) |
45
+ | Owner hooks | `<brandRoot>/config/hooks/<point>.js`, else the company root's | loaded in place — no copy | **walked** at the call site | every hook point the services declare (today `account/password.js`); committed by default |
46
+ | Target build hooks | `targets/<target>/hooks/<point>/{pre,post}.js` | in the target | **scaffolded once** (copy-once), then the target's own file | desktop · extension — one ctx (`{ manager, projectRoot, mode }`), same per-target discovery, desktop adds release/notarize; web has NONE by design and backend's "hooks" are Firebase blocking functions, a name collision ([#591](https://github.com/Omega-JS-Stack/omega/issues/591)) |
47
+ | Logs | nobody — output only | `<brandRoot>/logs/<verb>.log` · `targets/<target>/logs/{dev,build,test}.log` | **generated**, truncated per launch, gitignored | every brand-root verb (manage, dev, build, clean, deploy, update, test, pipeline) · every target |
48
+ | Translations | source strings in the target's own content | `targets/<target>/translations/{lang}/{namespace}.json` | **generated incrementally, COMMITTED** — a warm cache builds with no AI credentials ([#24](https://github.com/Omega-JS-Stack/omega/issues/24)) | web today; any future target that renders copy |
49
+ | Caches | nobody — derived data | `<brandRoot>/.omega/cache/**` | **generated**, gitignored, safe to delete (`--force` ignores them) | every caching service: edge reads, the update fingerprints, web's firebase-auth helpers |
50
+ | Build output | nobody — compiled | `targets/<target>/dist/` (+ `packaged/`) | **generated** per verb, gitignored, never edited | every target |
51
+ | Agent docs | the framework monorepo (`AGENTS.md` + `docs/`); the brand's own notes below the import in `<brandRoot>/AGENTS.md` | `<brandRoot>/AGENTS.md` + `CLAUDE.md`; `.claude/settings.json` | **distributed** — the workspace service maintains the `node_modules/@omega.js/AGENTS.md` symlink the import resolves through ([shared/agent-docs.md](shared/agent-docs.md)) | every brand root; targets carry none by design |
52
+ | Package scripts | the manifest's other keys are yours | `<brandRoot>/package.json` + each `targets/<target>/package.json` | **generated write-on-change** — only the named verb keys, every other key and the ordering untouched | brand root (`start`, `manage`, `deploy`) · every target's framework `projectScripts` |
53
+ | Tests | per surface: `targets/<target>/test/`; the brand's own browser lane at `<brandRoot>/test/e2e/run.js` | in place | **walked**: each target's own runner, then the brand lane, fanned out by the brand-root `omega test` ([#775](https://github.com/Omega-JS-Stack/omega/issues/775)) | every target · the brand e2e lane |
54
+
55
+ One file inside `.omega/` is not derived data: `.omega/company.json`, present only in a brand that belongs to a **company workspace**. It is the stamp that makes this brand inherit the company's config layer, its `.env` (under this brand's own), and its shared Apple signing tree — written by `omega company adopt` (or by `omega onboard` inside a company) and re-stamped by company-wide runs. Nothing else about the brand changes. What a company workspace is, and the two commands that own it: [company.md](company.md).
56
+
57
+ ## Verbs (the whole interface)
58
+
59
+ Run from the **brand root**:
60
+
61
+ ```bash
62
+ npm start # local dev stack (website + backend by default; `npm run dev` is the same)
63
+ npm run manage # manage: reconcile EVERY service to omega.json5 (idempotent)
64
+ npm run manage -- --service=<name> # reconcile one service (workspace, repo, cloud, edge, …)
65
+ npm run deploy # DELIBERATE publish fan-out: each target's own deploy, backend first
66
+ npx omega build # build fan-out: every target, backend first
67
+ npx omega clean # clean fan-out: wipe every target's build output
68
+ npx omega test # test fan-out: every target, then the brand's own e2e lane
69
+ ```
70
+
71
+ Every fan-out covers EVERY target type ([#603](https://github.com/Omega-JS-Stack/omega/issues/603)) — a framework target runs its framework's own verb, a custom target runs the matching `package.json` script, and a target that declares no such script steps aside loudly (naming the target and the verb) instead of failing. `build` and `clean` take `--target=` like `deploy` (and like `dev`, `update` and `test`: one picker on every fan-out, [#780](https://github.com/Omega-JS-Stack/omega/issues/780)), plus `--dry-run` (every target prints the command it would have run and nothing executes), and unlike `deploy` a failing target never stops the rest: nothing is published, so one run names every broken target.
72
+
73
+ **The test walk covers the brand itself, not only its targets** ([#775](https://github.com/Omega-JS-Stack/omega/issues/775)). After every target's own runner, `omega test` runs `<brandRoot>/test/e2e/run.js` when that file exists: ONE entry file by convention, so the walk never guesses which file in the folder is the runner. That lane is the brand's browser proof, driving its real pages against its real local stack (the backend emulator with its seeded personas, plus the real `omega dev`), built on `@omega.js/devkit/test/e2e-harness`. It is a runner script, so like a custom target's `test` script it hears no scope ids and no flags: a bare run reaches it, a scoped or picked or laned run does not. A `test/e2e/` folder with no `run.js` is skipped in one line, never failed. **The browser comes with the manager**: `@omega.js/manager` carries puppeteer, so a brand installs nothing for the lane, and the flip side is that every brand install downloads a Chrome (~150 MB). A brand that will never run the lane skips that download with `PUPPETEER_SKIP_DOWNLOAD=1` in the environment its `npm install` runs in (CI included); the lane then reports the missing browser by name if it is ever run.
74
+
75
+ Three flags shape the walk. `--target=<a,b>` is the target picker (a comma list of target keys like `web`, or dir names like `website`) and it composes with every scope: `omega test --target=backend framework:` is the backend target's framework suite, `omega test --target=web` its project tests. A framework scope whose owning target the picker excludes is a contradiction, and the run refuses by name rather than testing nothing. `--extended` (real external services) reaches every target. `--lane=<name>` reaches the targets whose framework DECLARES that lane in its package.json `omega.testLanes`; a target that declares none prints one line and the run stays green, because a lane a framework does not serve is not a failure there. A forwarded PATH works the same way: the walk sets `OMEGA_TEST_FANOUT=1` on every target run, so a target that carries no file matching the path answers with a distinct exit code and counts as a miss rather than a failure. The run fails only when every target missed, and says so in one line naming the target, because a path no target carries is a typo and testing nothing is never a green ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)).
76
+
77
+ The scripts are the named verbs (`omega manage`, `omega dev`, `omega deploy`) — a bare `omega` prints help and runs nothing. `npm start`'s boot reconciles the LOCAL lane only (workspace, assets, disperse); `npm run manage` is the full setup. There is no per-target setup step to remember ([#675](https://github.com/Omega-JS-Stack/omega/issues/675)): a target's verbs scaffold and heal its framework-owned files on first run, so a fresh brand goes `npm install` → `npm start`.
78
+
79
+ Run from a **target root** (`targets/<target>/`):
80
+
81
+ ```bash
82
+ npx omega dev # this target's dev server/build watch
83
+ npx omega test # the target's test suites
84
+ npx omega deploy # DELIBERATE publish for this target (commits never auto-deploy)
85
+ npx omega i local # link the local framework monorepo (ONE-TIME — the link is durable; rerun only to heal, never per change)
86
+ npx omega i live # restore published registry versions
87
+ ```
88
+
89
+ `omega`, `omg`, and `mgr` are the same context-aware dispatcher — the nearest target names the framework that runs.
90
+
91
+ Tests follow the layered doctrine in each framework's own `docs/test-framework.md` (unit for functions, integration for in-package systems, e2e only across framework boundaries; never mock what you can test real). Bare `npx omega test` runs are PROJECT-only — the framework corpus needs an explicit `framework:` or `full:` target.
92
+
93
+ ## One version for the whole family ([#794](https://github.com/Omega-JS-Stack/omega/issues/794))
94
+
95
+ Every `@omega.js/*` package in this brand carries the SAME version number, and the
96
+ manager writes each one as an exact pin — `"@omega.js/manager"` at the root,
97
+ `"@omega.js/<framework>"` in each target. `npm run manage` and `npm start` both
98
+ open by comparing what is actually INSTALLED (each target's framework and the
99
+ `@omega.js/client` under it) against the manager's own version, and a brand that
100
+ has drifted is refused before any service or dev leg runs:
101
+
102
+ ```
103
+ the @omega.js family ships ONE version — this brand is mixed (#794):
104
+ backend: @omega.js/backend 0.4.0, @omega.js/client 0.4.0
105
+ this manager is 0.5.0
106
+ fix: run `omega update --apply` at the brand root — it moves every target together
107
+ ```
108
+
109
+ **The fix is always `omega update --apply` at the brand root** — it fans out over
110
+ every target AND the root's own `@omega.js/manager` pin, so the whole family
111
+ lands on one number (a bare `omega update` only reports). Never bump a single
112
+ target's version by hand: one target ahead means two copies of
113
+ `@omega.js/client` in one brand and one omega.json5 validated by two validators,
114
+ which nothing downstream can see. A target linked with a `file:` spec is exempt
115
+ (the local era — its version is the monorepo's by construction), and a target
116
+ with nothing installed yet is skipped with a line rather than failed:
117
+ `npm install` is that fix. An installed `package.json` that cannot be parsed, or
118
+ carries no version, is its own refusal naming the file — reinstall.
119
+
120
+ ## Logs — grep them, never restart
121
+
122
+ Every verb tees its whole run to a file: truncated on each launch, ANSI-stripped, gitignored. Server state, build errors, test failures and emulator traffic are ALREADY on disk — read them instead of restarting a process or re-running a suite.
123
+
124
+ | Where | Files |
125
+ |---|---|
126
+ | brand root | one file per verb — `logs/manage.log` (the manage cycle) · `logs/dev.log` (`npm start`'s dev fan-out across every leg; consecutive duplicate lines collapse to one ` (repeated N×)` note) · `logs/build.log` · `logs/clean.log` · `logs/deploy.log` · `logs/update.log` · `logs/test.log` · `logs/pipeline.log`. A fan-out log holds the walk's OWN verdict (header, skips, summary) — each target's output is in that target's own log |
127
+ | any target | `targets/<target>/logs/dev.log`, `logs/build.log`, `logs/test.log` |
128
+ | backend, extra | `targets/backend/dist/emulator.log` (the emulator's own traffic), `dist/dev.log`, `dist/test.log` — beside firebase-tools' `*-debug.log` |
129
+ | desktop, extra | `targets/desktop/logs/runtime.log` — the running app itself (packaged builds: the OS log dir) |
130
+
131
+ ```bash
132
+ tail -50 targets/website/logs/dev.log # is the dev server up, what did it last build
133
+ grep -i error targets/backend/dist/emulator.log # what the emulator actually served
134
+ ```
135
+
136
+ The mechanism, the retention rule, and the complete path table: [shared/logging.md](shared/logging.md).
137
+
138
+ ## Working locally against the framework (upstream-first)
139
+
140
+ When this brand runs `omega i local` / `omega dev --local`, every `@omega.js/*` dep is linked LIVE from the local framework monorepo — a framework change reflects here instantly. That linkage exists for a reason: real applications expose framework holes. **When you hit a defect or gap that EVERY consumer would face — a broken core style, a missing option, a wrong default — it belongs in the FRAMEWORK (the linked monorepo), not in this brand.** The test: would the next consumer project need the same patch? Then it belongs upstream. **But ask first, always: SURFACE the proposed framework change (what is broken, what you would change, why every consumer needs it) and WAIT for Ian's go before editing the monorepo — or file it as an upstream issue.** Never edit the framework silently as a side effect of brand work. Within reason: brand-specific looks, content, and one-off behavior stay in the brand; framework edits follow the framework's own rules (its docs, its tests). When the link is NOT active (published versions installed), file the gap upstream instead of patching around it locally.
141
+
142
+ **The guard hook enforces this half of the rule.** A `Write|Edit` to a framework-owned file in this brand is refused by `omega:guard` ([#452](https://github.com/Omega-JS-Stack/omega/issues/452)): generated and vendored files (`node_modules/`, any `dist/`, a generated header, the OMEGA-managed `database.rules.json`) are hard-refused and name the real source to edit, and a SHADOW COPY — a file whose path mirrors one the installed framework ships through its override layer — is refused with the upstream-first message. Two exits for the shadow case, both deliberate: file the framework issue, or declare the override with `omega:consumer-override: <reason>` in the file's first five lines (`OMEGA_CONSUMER_OVERRIDE=1` skips it for one session). `omega customize` already writes that marker into what it materializes, so the sanctioned copy needs nothing from you. Your pages under `src/pages/`, your `src/assets/css/main.scss` and `src/assets/js/main.js`, and your backend's `src/index.js` and `firestore.rules` are yours and are never guarded. The checklist line the hook cannot check for you: **a hand-written rule or template that fills a framework hole is a framework issue, not a brand patch.**
143
+
144
+ ## Hard rules
145
+
146
+ - **Never edit generated output**: `dist/`, `packaged/`, anything gitignored. Edit `src/`, run the build.
147
+ - **Never edit `node_modules/`** — framework bugs get fixed in the framework.
148
+ - **Secrets never enter omega.json5** — `.env` / `.omega/secrets/` only.
149
+ - **Deploys are deliberate**: only `omega deploy` publishes. Commits and pushes never auto-publish.
150
+ - **Don't start long-running dev processes the user may already be running** (`npm run dev`, emulators) — assume theirs is up and GREP THE LOGS; every surface already wrote its output to disk.
151
+ - **Framework-owned file sections** (marked `Default Values` / `OMEGA Rules` blocks) are rewritten by the target's own verbs when they scaffold — put customizations in the marked custom sections only. One exception, by design: a backend target's `firestore.rules` is YOURS end to end — it is compiled with the framework half into `dist/firestore.rules` (never edit that), and a match block you write whose path names a framework block's is MERGED into it (your condition ANDs onto the framework's, for every op you both name), which is how the brand tightens a framework rule. See [docs/backend/index.md](../backend/index.md) § Firestore rules.
@@ -0,0 +1,96 @@
1
+ # The campaigns service — the brand's email marketing (SendGrid)
2
+
3
+ The `campaigns` service reconciles the brand's email-marketing infrastructure on SendGrid:
4
+ domain authentication, link branding, a verified sender for Single Sends, the brand's marketing
5
+ list, the unsubscribe groups the backend sends through, `@omega.js/backend`'s custom fields and
6
+ segments, and the account-global Event Webhook. It runs after `edge`, because the DKIM records
7
+ are written into the Cloudflare zone.
8
+
9
+ The whole service reconciles ONE SendGrid account per brand-or-company: the
10
+ groups/fields/segments/webhook operations converge on the same result when sibling brands
11
+ share the account.
12
+
13
+ ## What it reconciles
14
+
15
+ | Operation | What it does |
16
+ |---|---|
17
+ | `domain-auth` | SendGrid domain authentication for `brand.url`'s domain. Missing → authenticate (`automatic_security` returns three CNAMEs under `emailauth.`), diff-sync those records into the Cloudflare apex zone, then validate — an interactive run polls until DNS propagates and SendGrid confirms. |
18
+ | `link-branding` | SendGrid link branding for `brand.url`'s domain — the `emailurl.` host every transactional link is rewritten through. Missing → brand the links (`POST /v3/whitelabel/links` with subdomain `emailurl`), diff-sync both returned CNAMEs into the Cloudflare apex zone UNPROXIED, then validate — an interactive run polls until SendGrid confirms. On valid, the `emailurl` CNAME is PATCHed to proxied ([#693](https://github.com/Omega-JS-Stack/omega/issues/693)). Skipped on a subdomain project: the apex records belong to the parent brand's walk. |
19
+ | `sender-identity` | The verified sender Single Sends require: `offers@{contact domain}` with the brand's name. It auto-verifies because `domain-auth` ran first. |
20
+ | `list` | The brand's marketing list, resolved config id → exact-name lookup → create, with the id written back to `marketing.campaigns.providers.sendgrid.listId`. |
21
+ | `unsubscribe-groups` | The account's ASM groups (below). |
22
+ | `custom-fields` | `@omega.js/backend`'s custom fields, from its marketing SSOT, honoring each field's provider skip list (SendGrid has first/last name built in) — ONE derivation (`fieldsForProvider()`), the same one the backend's contact sync writes through, so a skipped field is neither provisioned here nor sent there ([#695](https://github.com/Omega-JS-Stack/omega/issues/695)). A type mismatch cannot be patched in SendGrid, so the field is deleted and recreated. |
23
+ | `segments` | `@omega.js/backend`'s segments, each one's `query_dsl` rebuilt from its conditions and compared against the live segment; stale ones are PATCHed, falling back to delete + recreate. Orphaned `__temp_` segments (leaked by a brand-scoped campaign send that crashed) are swept. |
24
+ | `event-webhook` | The account-global Event Webhook pointed at the PARENT backend's forwarder, with the consent toggles (`bounce`, `dropped`, `spam_report`, `unsubscribe`, `group_unsubscribe`) enabled and drift patched by minimum diff. |
25
+ | `contact-person` | `brand.contact.person.name` exists — the human `@omega.js/backend`'s welcome, discount-nudge and checkup emails sign off as. Missing → the operation FAILS the walk naming the key ([#694](https://github.com/Omega-JS-Stack/omega/issues/694)). Last in the order: it gates the backend's sends, not anything provisioned here, so every SendGrid operation still converges first. |
26
+
27
+ Fields and segments `@omega.js/backend` does not own are never touched.
28
+
29
+ ## Unsubscribe groups ([#649](https://github.com/Omega-JS-Stack/omega/issues/649))
30
+
31
+ The `unsubscribe-groups` operation ensures the SendGrid ASM groups the backend's send path
32
+ names actually exist, and lands their ids in config. The KEYS come from `@omega.js/backend`'s
33
+ email SSOT (`GROUP_KEYS`); the recipient-facing NAME and description of each live here, on the
34
+ provider side:
35
+
36
+ | Key | Group name |
37
+ |---|---|
38
+ | `orders` | OMEGA - Order Updates |
39
+ | `hello` | OMEGA - Onboarding |
40
+ | `account` | OMEGA - Account |
41
+ | `marketing` | OMEGA - Marketing & Promotions |
42
+ | `security` | OMEGA - Security |
43
+ | `newsletter` | OMEGA - Newsletter |
44
+ | `internal` | OMEGA - Internal Alerts |
45
+
46
+ Groups are matched by NAME, never by id: ASM ids are per SendGrid ACCOUNT, so sibling brands
47
+ sharing one account converge on the same groups and land the same ids. Each resolved id is
48
+ written to `marketing.campaigns.providers.sendgrid.groups.<key>` — its ONE authoritative home,
49
+ through the comment-preserving editor, so a converged brand leaves omega.json5 byte-identical.
50
+ `@omega.js/backend` reads the id there and fails loudly at send time when one is missing. A
51
+ backend group key with no name+description row here throws at the top of the operation, not at
52
+ send time. A dry run names the groups it would create and writes nothing.
53
+
54
+ ## Config
55
+
56
+ - `marketing.campaigns.enabled: false` — skip.
57
+ - `marketing.campaigns.providers.sendgrid` — the vendor is a KEY under `providers`
58
+ ([#425](https://github.com/Omega-JS-Stack/omega/issues/425)): no entry means none chosen.
59
+ - `marketing.campaigns.providers.sendgrid.listId` — written back by `list`.
60
+ - `marketing.campaigns.providers.sendgrid.groups.<key>` — written back by `unsubscribe-groups`.
61
+ - `brand.address` — the physical mailing address CAN-SPAM requires on a sender. omega-manager
62
+ stamped the company's address on every brand; this ASKS for the brand's own
63
+ ([#635](https://github.com/Omega-JS-Stack/omega/issues/635)), five fields behind ONE gate,
64
+ and warns only when nobody can be asked.
65
+ - `brand.contact.person.name` — the human the backend's personal sends sign off as. REQUIRED
66
+ here: without it the walk fails ([#694](https://github.com/Omega-JS-Stack/omega/issues/694)).
67
+ - `parent` — whose backend the Event Webhook points at (`'self'` when this brand IS the
68
+ parent; `false` is a deliberate opt-out).
69
+
70
+ **Credentials**: `SENDGRID_API_KEY` in the brand `.env`. `OMEGA_WEBHOOK_KEY` is OMEGA's own
71
+ and is MINTED by the setup contract rather than asked for
72
+ ([#635](https://github.com/Omega-JS-Stack/omega/issues/635)), so the webhook operation can
73
+ read it even when this service runs before the workspace one did.
74
+
75
+ ## Gotchas
76
+
77
+ - **SendGrid supports ONE Event Webhook per account**, so it always targets the parent brand's
78
+ forwarder and the parent fans events out to each brand's own `/marketing/webhook` endpoint.
79
+ Only the consent toggles are managed — tracking toggles (open/click/delivered) are not ours
80
+ to touch.
81
+ - **No Cloudflare token is survivable.** `domain-auth` prints the records to add by hand and
82
+ still attempts validation, so a manual fix converges on the next run; `link-branding` prints
83
+ the one-line manual flip (orange-cloud the `emailurl` CNAME) and warns.
84
+ - **`link-branding` writes its own CNAMEs, because on run 1 nobody else has.** The `edge`
85
+ service builds the SendGrid record set from a LIVE domain-auth read, and a brand new brand has
86
+ no authenticated domain until `domain-auth` creates one minutes later in the same walk — so
87
+ edge wrote none of those records, and a branding with no CNAME behind it can never validate.
88
+ This operation diff-syncs both link CNAMEs itself (unproxied, the only state SendGrid can
89
+ validate) through the same writer `domain-auth` uses. Edge still stays the RECONCILER: its
90
+ next live read sees `valid: true` and desires the record proxied, so both services agree
91
+ ([#693](https://github.com/Omega-JS-Stack/omega/issues/693)).
92
+ - **The sender's identity needs human input** where config has none: the address prompt is a
93
+ genuine ask, not a wait, so a headless run legitimately warns.
94
+ - **Changing the query builder re-syncs every segment.** The generated SQL is compared against
95
+ the live segments for staleness, so any edit to the DSL translation is a fleet-wide re-sync
96
+ on the next run.
@@ -0,0 +1,46 @@
1
+ # The captcha service — the brand's own reCAPTCHA keys
2
+
3
+ The `captcha` service proves the brand's OWN classic reCAPTCHA keys work. Every brand mints
4
+ its own key in its own GCP project — never a company-shared key — and interactive runs ask
5
+ for both halves through the shared setup contract, pointing at the GCP reCAPTCHA console.
6
+
7
+ ## What it reconciles
8
+
9
+ One operation, `site-key`:
10
+
11
+ - **The secret is PROVEN.** Classic reCAPTCHA has exactly one documented endpoint,
12
+ `siteverify`, and it doubles as a validity check: verifying a throwaway token answers
13
+ `invalid-input-response` for a good secret and `invalid-input-secret` for a bad one. The
14
+ probe is a pure read — no assessment is created — so a dry run behaves identically.
15
+ - **The domain list cannot be reconciled.** Classic reCAPTCHA has no key-management API, so
16
+ the key's allowed-domain list is manual: an interactive run opens the console deep-link and
17
+ confirms once, stamping `captcha.providers.recaptcha.domainsConfirmed`
18
+ ([#434](https://github.com/Omega-JS-Stack/omega/issues/434) — a confirmation nothing can
19
+ re-check is the one kind of reconcile flag config keeps). Non-interactive runs keep the
20
+ printed guidance.
21
+
22
+ ## Config and credentials
23
+
24
+ | Key | Meaning |
25
+ |---|---|
26
+ | `captcha.providers.recaptcha.enabled: false` | Skip the service. |
27
+ | `captcha.providers.recaptcha.siteKey` | The public site key — the client mints tokens with it. |
28
+ | `captcha.providers.recaptcha.project` | Only for a key minted OUTSIDE the brand's own GCP project; otherwise the deep-link resolves from `cloud.config.projectId`. |
29
+ | `captcha.providers.recaptcha.domainsConfirmed` | The one-time domain-list confirmation. |
30
+
31
+ `RECAPTCHA_SITE_KEY` + `RECAPTCHA_SECRET_KEY` in the brand `.env`. Missing keys → an
32
+ interactive run asks; a non-interactive one skips with guidance.
33
+
34
+ ## Gotcha: the orphan secret
35
+
36
+ `RECAPTCHA_SECRET_KEY` set with NO `captcha.providers.recaptcha.siteKey` in config is a silent
37
+ 403 on every protected POST ([#507](https://github.com/Omega-JS-Stack/omega/issues/507)): the
38
+ backend enforces verification the moment the secret exists, and the client has no key to mint
39
+ a token with. This service is the one place that sees both halves, so it says that direction
40
+ out loud, with the console link to paste the site key from.
41
+
42
+ The REVERSE direction — a site key requiring its secret — is the env schema's `requiredWhen`
43
+ rule ([#626](https://github.com/Omega-JS-Stack/omega/issues/626)): warned brand-wide by the
44
+ workspace `env-rules` op and refused by a production backend boot. NEITHER half is the
45
+ sanctioned unkeyed brand ([#17](https://github.com/Omega-JS-Stack/omega/issues/17)) — green,
46
+ and skipped.
@@ -0,0 +1,68 @@
1
+ # The certificates service — Apple signing material
2
+
3
+ The `certificates` service reconciles Apple signing certificates, bundle IDs, and provisioning
4
+ profiles for brands with a desktop or mobile target, via the App Store Connect API. It runs
5
+ before `disperse`, which copies the artifacts into the targets.
6
+
7
+ ## The signing home
8
+
9
+ ONE Apple account signs everything a company ships, so a company-managed brand (the
10
+ `.omega/company.json` marker) shares the COMPANY workspace's signing tree; a standalone brand
11
+ keeps it brand-local, and the material simply moves to the company root when one is born.
12
+ Layout under the gitignored `{companyRoot||brandRoot}/.omega/certificates/apple/`:
13
+
14
+ ```
15
+ AuthKey_*.p8 App Store Connect API key
16
+ certificates/{TYPE}.cer + .p12 downloaded or created, then exported
17
+ csr/{TYPE}/{private.key,request.csr} PRESERVED — the issued cert pairs with this key
18
+ profiles/{BRAND_ID}/{TYPE}/{PLATFORM}.mobileprovision per-brand — a profile binds ONE bundle ID
19
+ ```
20
+
21
+ ## What it reconciles
22
+
23
+ - **`api-key`** — the sanity checkpoint: which Apple account (issuer, team, key) the
24
+ downstream operations act on.
25
+ - **`certificates`** — per cert type: a valid cert on the account is downloaded when the local
26
+ `.cer` is missing (delete it to force a re-download) and its `.p12` refreshed when stale; no
27
+ valid cert and automatable → created via a reused CSR, downloaded, exported; no valid cert
28
+ and manual (`DEVELOPER_ID_*`) → the local `.cer` is validated, and a missing or expired one
29
+ prints the portal URL and warns. Exported `.p12` files are then imported into the macOS
30
+ login keychain (best-effort, never in dry-run).
31
+ - **`bundle-ids`** — the brand's bundle ID exists with the required capabilities. It is
32
+ `composeBundleId(certificates.providers.apple.bundleIdPrefix, brand.id)` — a reverse-DNS
33
+ prefix plus the brand id with hyphens as dots. Platforms derive from the enabled targets
34
+ (desktop → MACOS, mobile → IOS).
35
+ - **`profiles`** — provisioning profiles for the brand's bundle ID × every applicable cert
36
+ type and platform, downloaded per brand. Device lists are fetched lazily, only when a
37
+ development profile actually needs creating.
38
+
39
+ ## Config and credentials
40
+
41
+ | Key | Meaning |
42
+ |---|---|
43
+ | `certificates.enabled: false` | Skip. Also skipped with no desktop or mobile target. |
44
+ | `certificates.providers.apple.bundleIdPrefix` | The reverse-DNS prefix. A brand without one is ASKED ([#635](https://github.com/Omega-JS-Stack/omega/issues/635)). |
45
+ | `certificates.providers.apple.certificates` | Which cert types to reconcile. |
46
+
47
+ `APPLE_API_ISSUER`, `APPLE_API_KEY_ID`, `APPLE_TEAM_ID` in the brand `.env` are required.
48
+ `CSC_KEY_PASSWORD` is auto-generated on the first real run and persisted to the SIGNING ROOT's
49
+ `.env` (the company workspace when company-managed — shared `.p12` files need the one shared
50
+ password). `APPLE_KEYCHAIN_PASSWORD` is optional and suppresses macOS keychain prompts.
51
+
52
+ ## Gotchas
53
+
54
+ - **Never rotate `CSC_KEY_PASSWORD`.** A `.p12` must carry a real password (modern macOS
55
+ rejects an empty one with "MAC verification failed"), and rotating orphans every existing
56
+ `.p12`.
57
+ - **The `.p8` downloads ONCE** — Apple never re-serves it. An interactive run opens the keys
58
+ page Enter-gated and watches `~/Downloads` for a fresh `AuthKey_*.p8`, filing it into the
59
+ signing tree; the key is auto-detected by prefix, so no path config exists.
60
+ - **`DEVELOPER_ID_*` certs have no API path.** Apple requires an Account Holder login to issue
61
+ them, so the interactive walkthrough stages the CSR itself, opens the portal create page, and
62
+ watches Downloads for the issued `.cer`; a headless run keeps printed guidance and converges
63
+ on rerun.
64
+ - **The per-brand profile segment matters.** The signing tree is shared at the company root,
65
+ but a profile binds one bundle ID — without `profiles/{BRAND_ID}/`, sibling brands would
66
+ overwrite each other's profiles.
67
+ - **`--force-recreate` was not ported.** Delete the local `.cer` to force a re-download;
68
+ expired certs recreate automatically.
package/docs/chat.md ADDED
@@ -0,0 +1,47 @@
1
+ # The chat service — the brand's Chatsy agent
2
+
3
+ The `chat` service keeps the brand's support chat agent on [Chatsy](https://chatsy.ai) in
4
+ sync — settings plus knowledge — and sets the agent-owner account to the configured plan.
5
+ Agents live in Chatsy's OWN Firestore, so this is an OPERATOR service: it needs a service
6
+ account for Chatsy's Firebase project, and every other brand's clean skip is the sanctioned
7
+ outcome (the credential never gates a run).
8
+
9
+ ## What it reconciles
10
+
11
+ - **`chat`** — the agent's managed fields (name, website, image from `brand.images.brandmark`)
12
+ and its KNOWLEDGE: the packaged baseline with the brand's values filled in — `{website}`,
13
+ `{description}`, `{pricing}` generated from `payment.products`, `{sponsorshipsUrl}` — plus
14
+ the brand repo's `config/chatsy.md` appended when present. Diff-synced and patched with a
15
+ LEAF MASK, so Chatsy-owned fields (owner, id, metadata, other settings) survive.
16
+ - **`user`** — the agent owner's subscription set to
17
+ `inbound.chat.providers.chatsy.plan` (Chatsy's Max top tier by default) through the shared
18
+ owner-plan reconciliation.
19
+
20
+ ## Config
21
+
22
+ | Key | Meaning |
23
+ |---|---|
24
+ | `inbound.chat.providers.chatsy.enabled: false` (or `chatsy: false`) | Skip. |
25
+ | `inbound.chat.providers.chatsy.agentId` | The agent. Missing → minted (below) or asked for. |
26
+ | `inbound.chat.providers.chatsy.templateAgentId` | The shape donor for create-on-missing. |
27
+ | `inbound.chat.providers.chatsy.plan` | The owner account's plan (`plan.id` required). |
28
+ | `inbound.chat.providers.chatsy.sponsorshipsUrl` | The knowledge baseline's sponsorship link (default `{website}/contact`). |
29
+ | `inbound.chat.providers.chatsy.updateAgentInfo: false` | A shared agent another brand manages — never rewritten from here. |
30
+
31
+ A `web` target is required: the chat widget lives on the brand's website.
32
+
33
+ ## The three auth tiers (Ian 2026-07-13)
34
+
35
+ 1. **Operator SA** — `CHATSY_SERVICE_ACCOUNT` in the brand `.env`. Full create + manage: a
36
+ missing `agentId` with a `templateAgentId` MINTS the brand's own agent (product user + doc
37
+ templated from the donor), writes the id back, and converges name/knowledge/plan in the same
38
+ run.
39
+ 2. **User API key** — `CHATSY_API_KEY` is recognized; product-API management lands when those
40
+ routes are verified.
41
+ 3. **Dashboard** — an interactive run opens chatsy.ai and takes the pasted agent id, with a
42
+ Disable option. Non-interactive and dry runs skip cleanly.
43
+
44
+ ## Gotcha
45
+
46
+ An `agentId` that points at no document is a visible ERROR. omega-manager silently created an
47
+ orphan agent document instead; a wrong id should be fixed, not written around.
package/docs/cloud.md ADDED
@@ -0,0 +1,110 @@
1
+ # The cloud service — the brand's Firebase/GCP project
2
+
3
+ The `cloud` service (Firebase provider) reconciles the brand's cloud project to `cloud: {}` in
4
+ `config/omega.json5`: billing, the required Google Cloud APIs, project identity, the OAuth
5
+ consent screen, the Admin SDK service account and its key, the Hosting `api.{domain}` custom
6
+ domains, Firestore, the Realtime Database, Authentication, Storage, Cloud Functions readiness,
7
+ Cloud Messaging, and the web SDK config. It runs fifth — after the zone exists, before
8
+ everything that depends on a backend.
9
+
10
+ ## What it reconciles
11
+
12
+ | Operation | What it does |
13
+ |---|---|
14
+ | `billing` | The Blaze plan. `cloud.billingAccount` set → linked; `false` → the user chose Spark, silently; missing → an interactive run picks from the accounts the authed user can see (or creates one) and lands the answer in omega.json5. |
15
+ | `services` | The required Google Cloud APIs (serviceusage first — the Firebase CLI's preflight needs it — then firebase, firestore, storage, hosting, database, identitytoolkit, cloudfunctions, cloudbuild, run, artifactregistry, iap, recaptchaenterprise, fcm) plus the compute service account's Cloud Functions deploy roles. Reads the enabled set first, so a converged project is a zero-mutation no-op. |
16
+ | `project-settings` | The GCP display name matches `brand.name`, and a web app named "Web App" exists. Diffed before writing. |
17
+ | `oauth-consent` | The OAuth consent screen (IAP brand): application title, support email, and the AUDIENCE (below). |
18
+ | `service-account` | The Admin SDK service account with `firebase.admin`, `firebaseauth.admin`, `datastore.owner`, `serviceusage.serviceUsageConsumer`, and its key downloaded. The IAM grant diffs the policy first. |
19
+ | `hosting` | The default hosting site gets `api.{domain}`: ONE api domain, shared by every web instance the brand runs ([#588](https://github.com/Omega-JS-Stack/omega/issues/588)); DNS is written through Cloudflare. The main domain is NOT added — the website hosts on GitHub Pages. |
20
+ | `firestore` | The Firestore database (nam5 US multi-region) with Point-in-Time Recovery. |
21
+ | `database` | The default Realtime Database instance (`{projectId}-default-rtdb`, us-central1). |
22
+ | `authentication` | Identity Platform, email/password sign-in, email-enumeration privacy, anonymous auto-delete, the password policy, authorized domains — all diffed via Identity Toolkit — plus Google sign-in. |
23
+ | `storage` | The default storage bucket, created and linked. |
24
+ | `functions` | A read-only readiness check on cloudfunctions/cloudbuild/run. |
25
+ | `cloud-messaging` | The FCM API plus a VAPID key pair — the PUBLIC half to `cloud.messaging.vapidKey`, the private half to `VAPID_PRIVATE_KEY` in the brand `.env`. |
26
+ | `sdk-config` | The web SDK config fetched into state and diffed against `cloud.config` in omega.json5, written back key by key. |
27
+
28
+ ## Config
29
+
30
+ - `cloud.enabled: false` — skip.
31
+ - `cloud.config.projectId` — the ONE home of the project id
32
+ ([#23](https://github.com/Omega-JS-Stack/omega/issues/23)). Missing → an interactive run
33
+ offers the selection/creation flow and lands it in omega.json5; otherwise the service skips.
34
+ - `cloud.config.*` — the SDK keys (`apiKey`, `authDomain`, `databaseURL`, `projectId`,
35
+ `storageBucket`, `messagingSenderId`, `appId`, `measurementId`), owned by `sdk-config`.
36
+ - `cloud.billingAccount`, `cloud.organizationId` — both tri-state
37
+ ([#33](https://github.com/Omega-JS-Stack/omega/issues/33)): a value, `false` for a deliberate
38
+ opt-out, or missing to be asked once.
39
+ - `cloud.supportEmail` — the consent screen's support address.
40
+ - `cloud.oauthRedirectsConfigured` — the one-time confirm that the OAuth client's redirect
41
+ URIs are set (there is no API for them).
42
+ - `cloud.consentAudience: false` — the tri-state opt-out
43
+ ([#33](https://github.com/Omega-JS-Stack/omega/issues/33)) for the audience stopper below: the brand
44
+ accepts an org-only sign-in, so only that step goes quiet — every other cloud operation still runs.
45
+ - `cloud.messaging.vapidKey` — the public web-push key.
46
+ - `cloud.shared: true` — the project is shared by several brands, so only the per-brand
47
+ operations run (`service-account`, `sdk-config`): one brand never rewrites a shared
48
+ project's settings.
49
+
50
+ **Credentials**: `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` in the brand `.env` (OAuth2;
51
+ tokens cache to `.omega/auth/google-tokens.json`, and the first run prints an auth URL).
52
+
53
+ ## The consent screen's audience — a manage-time STOPPER
54
+
55
+ An Internal consent screen admits only the owning org's Workspace users — `Error 403: org_internal` for
56
+ every Gmail account — and **Google gives no API write for the audience**
57
+ ([#667](https://github.com/Omega-JS-Stack/omega/issues/667), proven live): the PATCH is a 404 (the method
58
+ does not exist), `orgInternalOnly` is output-only on create, an API-made brand is born Internal always, and
59
+ brand creation itself is org-only. So `oauth-consent` READS the audience and stops on it instead of
60
+ attempting a write that cannot exist:
61
+
62
+ - **External** — a `✓` line, no prompt, no poll.
63
+ - **Internal, interactive** — the uniform three-outcome gate: **Yes** opens
64
+ [the audience page](https://console.cloud.google.com/auth/audience) behind the ENTER gate and polls the
65
+ brand read until `orgInternalOnly` flips (the [#662](https://github.com/Omega-JS-Stack/omega/issues/662)
66
+ one-walk-finishes-the-job wait, so the flip lands in THIS run); **Skip for now** — and `s` at the poll —
67
+ warns with the console URL and asks again next run; **Disable** lands `cloud.consentAudience: false` and
68
+ nothing ever asks again (later runs print a dim `⊘` line).
69
+ - **Internal, non-interactive** (CI, a piped `omega dev` boot) — the warn plus the console URL, `status:
70
+ warned`. A dry run plans the stopper and touches nothing.
71
+
72
+ Creating the screen is org-only too: on a project that belongs to no organization the create answers
73
+ 400 "Project must belong to an organization", and the ensure names that cause with the console consent URL
74
+ instead of the generic could-not-create line.
75
+
76
+ ## The branding page — a NAMED manual step
77
+
78
+ The consent screen's **branding** extras (logo, home/privacy/terms links, authorized domains) have no API
79
+ at all, so nothing here can reconcile them. Per the automation ruling
80
+ ([#693](https://github.com/Omega-JS-Stack/omega/issues/693)) what cannot be automated gets NAMED, so
81
+ `oauth-consent` prints
82
+ [the branding page](https://console.cloud.google.com/auth/branding) with its one-line checklist
83
+ ([#696](https://github.com/Omega-JS-Stack/omega/issues/696)): links and authorized domains are safe to
84
+ change anytime, while uploading a LOGO starts Google's verification review for External apps. It is a
85
+ named step, not a warning — the run's status never changes for it.
86
+
87
+ ## Gotchas
88
+
89
+ - **A `demo-*` project is emulator-only** by Firebase's own convention, so there is no real
90
+ cloud to reconcile and the service skips. Found live: an offline brand's `demo-` id sent the
91
+ ensure at real Google APIs, 403'd, and killed the whole boot.
92
+ - **The service-account key is shown ONCE.** Google never re-serves it, so its ONE home is the
93
+ brand's gitignored `.omega/secrets/service-account.json`; the backend's stage step carries it
94
+ into the staged `dist/` tree.
95
+ - **Access self-heals before any operation runs.** A manage identity with no role on the
96
+ project gets granted one through a local `gcloud` account that can (owner first, then
97
+ editor + firebase.admin — projects outside an organization only accept new owners via a
98
+ console invitation), then the probe re-runs through a short IAM-propagation window.
99
+ - **`supportEmail` must be an address the AUTHORIZING USER owns** (their own, or a Google Group
100
+ they manage) — anything else is "Request contains an invalid argument". The default is the
101
+ authenticated user's own email.
102
+ - **Google sign-in's OAuth client cannot be created programmatically**, and its redirect URIs
103
+ have no API either: instructions plus an interactive confirm, stamped in config.
104
+ - **`authDomain` is the BRAND host** (Ian's ruling 2026-07-23): a first-party authDomain keeps
105
+ `signInWithRedirect` working under storage partitioning, and the site self-hosts Firebase's
106
+ `/__/auth/*` helper files at build time.
107
+ - **API enablement is eventually consistent.** The `functions` readiness check reads three APIs
108
+ once; a Service Usage lag can report a freshly enabled API as off, and the next run agrees.
109
+ - **The API-domain TLS gate** — why a subdomain project's `api.` host stays DNS-only, and what
110
+ the paid Cloudflare add-on changes — is documented in [edge.md](edge.md).
@@ -0,0 +1,64 @@
1
+ # The company workspace
2
+
3
+ > The COMPANY → BRAND rung: one workspace whose config, secrets, and signing material every brand it manages inherits. Two commands own it — `omega company init` makes the workspace, `omega company adopt` joins a brand to it. The runtime behavior (discovery, the per-brand fan-out, the child-process model) is in [packages/manager/README.md](../README.md) § Company mode.
4
+
5
+ ## What a company workspace IS
6
+
7
+ A **repo, not a machine cache.** Machine-wide state stays in `~/.omega` (auth token caches, the per-machine deploy record); the company workspace is a directory you clone, commit, and share with the company's other machines. It owns exactly four things:
8
+
9
+ | What | Where | Reaches a brand by |
10
+ |---|---|---|
11
+ | The config layer | `config/omega.json5` (the `brands` key is what MAKES it a company root) | the merge chain: manager DEFAULTS ← **company** ← brand ← `targets.<type>` ← local |
12
+ | The shared secrets | `.env` | the env chain: shell env > brand `.env` > **company `.env`** |
13
+ | The shared Apple signing tree | `.omega/certificates/apple/` | `signingRoot = companyRoot \|\| brandRoot` — the certificates + disperse services read the company's tree for every company-managed brand |
14
+ | Where the brands are | `brands.roots` in that config (default `['./brands']`; `['..']` treats the workspace's siblings as brands) | company-wide runs (`omega manage` from the company root) |
15
+
16
+ A brand behaves IDENTICALLY everywhere — standalone, nested under `brands/`, or a loose sibling. The company layer only changes what defaults it inherits.
17
+
18
+ ## The stamp is the whole link
19
+
20
+ The reverse link needs no nesting and no registry: a brand carries `.omega/company.json` (`{ "root": "/abs/path/to/company" }`), and every brand-local run reads it, layers the company config (minus the `brands` key), loads the company `.env` under the brand's own, resolves the signing tree there, and prints a `Company:` line in its header. A marker pointing at something that is no longer a company root warns and the run continues standalone; no marker at all = standalone. That is the ONE mechanism — `omega company adopt` writes exactly this file, `omega onboard` writes it for brands it creates in a company, and a company-wide `omega manage` re-stamps every brand it discovers.
21
+
22
+ ## `omega company init [path]`
23
+
24
+ Scaffolds the workspace in one command (default: the current directory). **Idempotent** — it fills gaps only and never overwrites a file that exists, so rerunning it after an edit is a byte-level no-op.
25
+
26
+ ```bash
27
+ mkdir my-company && cd my-company
28
+ npx omega company init # or: npx omega company init ~/Developer/my-company
29
+ ```
30
+
31
+ What it writes:
32
+
33
+ | Path | What it is |
34
+ |---|---|
35
+ | `config/omega.json5` | The company layer: a real `brands: { roots: ['./brands'] }` plus commented, brand-agnostic placeholders for the sections a company usually owns (identity, `account.admins`, the GA account, the Sentry org, the Apple `bundleIdPrefix`) |
36
+ | `.env` | The canonical group template with every key commented out — no values, no generated secrets (a brand's own `.env` always wins over this file) |
37
+ | `.gitignore` | The tracking decisions below |
38
+ | `README.md` | A short stub: what's here, and the verbs |
39
+ | `.omega/certificates/apple/{certificates,csr,profiles}/` | The shared signing tree, created empty — the App Store Connect `AuthKey_*.p8` goes at its root. Carries its own self-protecting `.gitignore` (`*`) so signing material can never be committed even if the tree is copied elsewhere |
40
+ | `brands/` | The default `brands.roots` entry, created empty |
41
+
42
+ **Tracked vs ignored.** The repo tracks the config skeleton, the README, and the `.gitignore` — the shareable half. Ignored: `.env` (secrets), `.omega/` (the signing tree's `.p8`/`.p12`/CSR private keys, plus run output and caches), `logs/`, and `brands/` — each managed brand is its OWN git repo and must never be embedded in this one. Signing material moves between machines out of band, never through git; the certificates service re-downloads or recreates what it can, and preserves the CSR private key that pairs with each issued certificate.
43
+
44
+ ## `omega company adopt <brand-path>`
45
+
46
+ Run from anywhere inside the company workspace. Adoption is the ONE step a brand needs:
47
+
48
+ ```bash
49
+ npx omega company adopt brands/acme # or any path: ../acme, /abs/path/to/acme
50
+ ```
51
+
52
+ It writes the brand's `.omega/company.json` and nothing else. Idempotent: a marker already pointing here is left untouched. It refuses what would be wrong rather than guessing — a cwd that is not a company root, a path with no `config/omega.json5`, or a path that is itself a company workspace (the hierarchy is one rung: company → brands). A brand outside `brands.roots` is still stamped — its own runs inherit the company — but the command warns that company-wide runs won't include it until its parent dir joins `brands.roots`.
53
+
54
+ New brands need no adopt step: `omega onboard` from a company root lands the brand under `brands.roots[0]` and stamps it in the same pass.
55
+
56
+ ## After adoption
57
+
58
+ ```bash
59
+ npx omega manage # from the company root: the full walk, once per brand (child process each)
60
+ npx omega manage --brand=acme # one brand
61
+ npx omega manage --parallel # every brand concurrently
62
+ ```
63
+
64
+ Brand-local runs need nothing new — `npm run manage` inside an adopted brand already layers the company. See [packages/manager/README.md](../README.md) § Company mode for the fan-out mechanics, the per-brand log tee, and why children are processes; [shared/config.md](shared/config.md) for the merge chain itself.