backend-manager 5.5.4 → 5.6.1

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 (46) hide show
  1. package/CHANGELOG.md +33 -4
  2. package/CLAUDE.md +36 -11
  3. package/README.md +6 -5
  4. package/docs/audit.md +70 -0
  5. package/docs/build-system.md +29 -0
  6. package/docs/cli-logs.md +2 -2
  7. package/docs/common-mistakes.md +1 -1
  8. package/docs/consent.md +4 -2
  9. package/docs/email-system.md +28 -2
  10. package/docs/environment-detection.md +1 -1
  11. package/docs/firestore.md +134 -0
  12. package/docs/logging.md +27 -0
  13. package/docs/migration.md +107 -0
  14. package/docs/routes.md +110 -14
  15. package/docs/schemas.md +121 -25
  16. package/docs/test-boot-layer.md +67 -0
  17. package/docs/{testing.md → test-framework.md} +150 -19
  18. package/docs/usage-rate-limiting.md +15 -0
  19. package/package.json +12 -5
  20. package/src/cli/commands/base-command.js +5 -5
  21. package/src/cli/commands/logs.js +2 -2
  22. package/src/cli/commands/serve.js +3 -3
  23. package/src/cli/commands/test.js +140 -0
  24. package/src/cli/commands/watch.js +4 -4
  25. package/src/defaults/CLAUDE.md +11 -0
  26. package/src/defaults/test/README.md +15 -0
  27. package/src/defaults/test/_init.js +1 -1
  28. package/src/manager/libraries/email/data/blocked-local-patterns.js +0 -2
  29. package/src/manager/libraries/email/data/custom-disposable-domains.json +47 -1
  30. package/src/manager/libraries/email/data/disposable-domains.json +3 -0
  31. package/src/manager/libraries/email/data/typo-domains.js +83 -0
  32. package/src/manager/libraries/email/validation.js +74 -8
  33. package/src/manager/libraries/email/validation.test.js +125 -0
  34. package/src/test/fixtures/firebase-project/.firebaserc +5 -0
  35. package/src/test/fixtures/firebase-project/database.rules.json +6 -0
  36. package/src/test/fixtures/firebase-project/firebase.json +49 -0
  37. package/src/test/fixtures/firebase-project/firestore.indexes.json +4 -0
  38. package/src/test/fixtures/firebase-project/firestore.rules +10 -0
  39. package/src/test/fixtures/firebase-project/functions/backend-manager-config.json +24 -0
  40. package/src/test/fixtures/firebase-project/functions/index.js +10 -0
  41. package/src/test/fixtures/firebase-project/functions/package.json +15 -0
  42. package/src/test/fixtures/firebase-project/public/index.html +5 -0
  43. package/src/test/fixtures/firebase-project/storage.rules +8 -0
  44. package/src/test/runner.js +16 -4
  45. package/test/boot/emulator-boots.js +37 -0
  46. package/test/rules/user.js +35 -30
package/CHANGELOG.md CHANGED
@@ -14,6 +14,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.6.1] - 2026-06-11
18
+
19
+ ### Added
20
+ - **`docs/audit.md` — full-audit check catalog (`/omega:bem audit`).** ID'd, severity-graded checks with scope auto-detect (consumer vs framework via `functions/package.json`): mirrored universal checks (U-01..U-14 — tests at every surface, sanitization, secrets incl. `service-account.json`, config canon, doc parity, dead/legacy patterns, dep health, …), BEM-specific checks (BEM-01..BEM-09 — name-matched context-object schemas, the required-vs-default footgun, ownership checks + `assistant.respond()`, index.js/rewrites wiring, Firestore canon, usage helper + rate limits, composite indexes, auth gates, rules coverage), and framework-repo checks (F-01..F-04). Findings persist to `functions/.temp/audit/claude-audit.md`; fixes run as a severity-ordered TodoWrite loop ending with a green `npx mgr test`. Wired to the `omega:bem` router's Audit process; `docs/audit.md` is mirrored across UJM/BXM/EM. Indexed in CLAUDE.md.
21
+
22
+ ### Changed
23
+ - **package.json `keywords` corrected** — replaced the thin generic set (`cli`, `backend manager`, `firebase`) with accurate, discovery-oriented ones (`firebase`, `firebase-functions`, `cloud-functions`, `firestore`, `backend`, `serverless`, `api`, `express`, `cli`). npm-listing metadata only; no behavior change. Mirrored across UJM/BXM/EM.
24
+
25
+ # [5.6.0] - 2026-06-11
26
+
27
+ ### Added
28
+ - **`docs/migration.md` (action-skill consolidation).** The standalone `BEM:migrate` skill was deleted and folded into `omega:bem` as a process checklist; its playbooks landed in the repo as `docs/migration.md` — env-var migration (runtime config → top-level `functions/.env` keys with the full mapping table), legacy code conversion (`Manager.config.*` → `process.env.*`), and route/schema migration to the current context-object/flat formats. Indexed in CLAUDE.md.
29
+ - **Dev-process guidance relaxed: only `npx mgr serve` / `npx mgr emulator` are off-limits.** The "NEVER run" rule in CLAUDE.md now prohibits only the long-running dev processes (instruct the user to start them if they aren't running; read `functions/*.log`, never tail) — `npx mgr test` is fine to run (it auto-starts its own emulator).
30
+ - **Skills-as-routers migration — `docs/firestore.md` (new) + `routes.md`/`schemas.md` rewrites + `test-framework.md`/`usage-rate-limiting.md` extensions.** Framework facts migrated from the `omega:bem` skill into the repo so they version-match the installed package, with stale content corrected against source: `routes.md`'s consumer-route recipe now shows the CURRENT context-object handler (`module.exports = async ({ Manager, assistant, user, settings, ... })` — middleware calls `routeHandler(context)`; the old `Route.prototype.main` constructor example was stale) plus the CRUD method-file table, ownership checks, plural naming, firebase.json rewrite syntax + first-match ordering, and the `functions/index.js` entry pattern; `schemas.md` rewritten to the current contract (context object `{ assistant, user, data, method, headers, geolocation, client }` → FLAT schema with in-function plan branching — the old positional-args + `defaults:/premium:` tier examples were stale) plus field properties (`value`/`clean`/function `required`), the required-vs-default footgun, and the ID-generation/path-extraction patterns; new `firestore.md` carries the Firestore conventions (`.doc('col/id')` style, NO subcollections, ~500-doc cursor-paginated batch reads, `metadata.{created,updated}` timestamps, mirror-the-doc response format + delete-don't-redact); `test-framework.md` gains the `rules` client reference (`asAccount`/`expectSuccess`/`expectFailure` + seed-as-admin pattern), the journey-account isolation rule (CRITICAL — never use shared accounts with the `test` processor), naming conventions, and common patterns (auth rejection, concurrent-duplicate rejection, suite cleanup); `usage-rate-limiting.md` gains the core API table (`validate`/`increment`/`update`/`getLimit`/`getProduct`/`getUsage`) and the never-write-usage-manually rules. All indexed in CLAUDE.md; the skill is now a thin router (pointers + hard rules + process checklists).
31
+ - **`--extended` CLI flag for cross-framework parity.** `npx mgr test --extended` is now the CLI shorthand for the shared, unprefixed `TEST_EXTENDED_MODE` env var (standardized across BEM/BXM/UJM/EM) — equivalent to `TEST_EXTENDED_MODE=true npx mgr test`. The flag is read pre-flight in `src/cli/commands/test.js` (before the `captureSyncedEnv`/`writeTestMode` pre-flight), so it propagates to BOTH the test-runner subprocess (`{ ...process.env }`) AND the live emulator (via `.temp/test-mode.json`) exactly like the env var. The env-var path keeps working — env var OR flag opts into REAL external services (default: skipped).
32
+ - **Framework self-test from the repo (bundled fixture + `BEM_TEST_BOOT_PROJECT`).** `npx mgr test` run from the backend-manager repo now boots a bundled fixture Firebase project ([`src/test/fixtures/firebase-project/`](src/test/fixtures/firebase-project)) and runs a `test/boot/` smoke suite (emulator boots → fixture `Manager.init()` wires `bm_api` → health returns 200), instead of failing with "no firebase.json". Brings BEM into parity with BXM's `BXM_TEST_BOOT_PROJECT` / UJM's `UJ_TEST_BOOT_PROJECT`. The runner symlinks the local `backend-manager` (+ `firebase-admin`/`firebase-functions`) into the fixture, injects the fixture admin keys, and generates a throwaway emulator-only service account at runtime (all gitignored). `BEM_TEST_BOOT_PROJECT=<path>` overrides the fixture to self-test against a real consumer. The `boot/` smoke is excluded from consumer runs; the full `routes`/`events`/`rules` suites still run against a real consumer as before.
33
+ - **Docs parity — new `docs/build-system.md` + `docs/logging.md`.** `build-system.md` documents BEM's deliberate outlier status (no consumer build pipeline; framework prepare-package; deploy flow); `logging.md` is now the SSOT for the `functions/*.log` file table (extracted from test-framework.md, which keeps a pointer — mirrors EM/BXM/UJM). Both indexed in CLAUDE.md → Documentation.
34
+ - **Test coverage convention (docs).** New mirrored "Test coverage" sections in `CLAUDE.md`, `docs/test-framework.md`, `src/defaults/CLAUDE.md`, and `src/defaults/test/README.md` — every feature ships with tests at every surface it exposes (logic via handler suites, wiring via `http.as(...)` round-trips, rules suites when Firestore rules change); a surface is skipped only when the feature genuinely doesn't have one. UI coverage explicitly lives in the consuming frontends. Mirrored across EM/BXM/UJM.
35
+ - **Universal `mgr:` test source prefix.** `npx mgr test` now accepts the universal cross-framework `mgr:` prefix (alias for `bem:`) to run framework-only tests, complementing the existing `bem:` and `project:` prefixes. `npx mgr test mgr:` runs all framework tests, `npx mgr test mgr:<path>` runs framework tests matching a path, and multiple space-separated targets compose (e.g. `npx mgr test bem:rules project:routes`).
36
+
37
+ ### Changed
38
+ - **Router skill renamed `BEM:patterns` → `omega:bem`** — all framework skills now live under the `omega:` namespace (`omega:em`/`omega:bxm`/`omega:ujm`/`omega:bem` + the `omega:main` hub). CLAUDE.md's Recommended skills section updated.
39
+ - **`docs/testing.md` renamed `docs/test-framework.md`** (H1 `# Testing` → `# Test Framework`) for cross-framework doc-file parity — EM/BXM/UJM all name their test reference `docs/test-framework.md`, and the mirrored docs must match down to the file name. All references updated (`CLAUDE.md`, `README.md`, `docs/*.md` cross-links, `src/defaults/CLAUDE.md`, `src/defaults/test/_init.js`, historical CHANGELOG links).
40
+ - **Log files renamed for cross-framework parity.** `functions/serve.log` → `functions/dev.log` (the `npx mgr serve` dev-server output) and `functions/logs.log` → `functions/production.log` (the `npx mgr logs` Cloud Logging output). The `dev`/`test` names now match EM/BXM/UJM; `emulator.log` and `test.log` are unchanged. BEM logs still live in `functions/` (not `logs/`) — that directory is a deliberate exception so they sit beside firebase-tools' own `*-debug.log` files. The watcher reset sentinel `serve.log.reset` is correspondingly `dev.log.reset` (internal, in `.temp/`).
41
+
42
+ ### Fixed
43
+ - **`npm publish` vs the fixture's runtime symlinks.** New `prepublishOnly` script removes `src/test/fixtures/firebase-project/functions/node_modules` before packing — the self-test's `backend-manager` symlink points back at the repo root, and prepare-package's publish-time cleanup walk (`jetpack.find` with a top-level-only `!node_modules/**` exclusion) followed the cycle until `ENAMETOOLONG`. The symlinks are throwaway runtime artifacts; the next self-test run regenerates them via `linkFixtureDeps()`.
44
+ - **Fixture `.firebaserc` re-included over the global `.gitignore` rule** (`!src/test/fixtures/firebase-project/.firebaserc`) — the emulator boots with no `--project` flag and resolves the demo project from `.firebaserc`, so a fresh clone's self-test would have failed without it.
45
+
17
46
  # [5.5.4] - 2026-06-09
18
47
 
19
48
  ### Fixed
@@ -61,7 +90,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
61
90
  ### Added
62
91
  - **Push notification campaigns.** Marketing campaign POST route and cron now handle `type: 'push'` with brand-aware icon/clickAction defaults and test-mode owner filtering.
63
92
  - **`filters` field** in campaign schema for push notification targeting.
64
- - **Test filtering docs** in CLAUDE.md and docs/testing.md (`npx mgr test <path>`, `bem:`, `project:` prefixes).
93
+ - **Test filtering docs** in CLAUDE.md and docs/test-framework.md (`npx mgr test <path>`, `bem:`, `project:` prefixes).
65
94
 
66
95
  ### Fixed
67
96
  - **Email migration shims (temporary).** Old template names (`default` → `card`, `core/engagement/feedback` → `feedback`) and old data format (`data.body` → `data.content`) mapped for queued emails saved before the MJML migration.
@@ -163,7 +192,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
163
192
  - **AI tools passthrough (nested) — `ai.request({ tools: { list, choice } })`.** `tools.list` is an array of tool definitions passed to the OpenAI Responses API verbatim — built-in hosted tools (e.g. `{ type: 'web_search' }`) OR custom function tools (`{ type: 'function', name, parameters }`); `tools.choice` *(optional)* maps to `tool_choice`. Opt-in — omitted/empty means no tools and identical behavior to a plain request. Primary use is OpenAI's built-in **web search** so the model finds and cites real, currently-live URLs instead of hallucinating them. When tools are active the response `output` may carry tool-call items (e.g. `web_search_call`) + `url_citation` annotations; the message-text extractor ignores non-message items so `r.content` is unaffected. See [docs/ai-library.md](docs/ai-library.md).
164
193
  - **`image-illustrator.js` — newsletter section illustrations now generated as flat-vector PNGs via `gpt-image-2` by default**, replacing the SVG-author-then-rasterize approach as the default. Clean flat 2D vector style (Stripe / Linear / undraw.co aesthetic) built from the brand palette (`content.theme.{primary,secondary,accent}Color`), white background, no text. The legacy `svg-illustrator.js` method is still available per-brand via `marketing.beehiiv.content.method.image = 'svg'`. Both return the same `{ png: Buffer, fallback, meta }` contract, so `image-host.js` / `uploadAssets` are unchanged. Validated end-to-end against live `gpt-image-2` with Somiibo's real config. See [docs/marketing-campaigns.md](docs/marketing-campaigns.md).
165
194
  - **Full emulator-Firestore flush before every test run.** `deleteTestUsers()` now calls `flushEmulatorFirestore()` — `listCollections()` + `recursiveDelete()` on the entire emulator DB — before recreating test accounts. The emulator DB is 100% test data, so a full flush is the simplest correct clean slate; there are no per-collection allowlists to maintain. Guarded to run ONLY when `FIRESTORE_EMULATOR_HOST` is set, so it can never touch a real project.
166
- - **`test/_init.js` pre-test lifecycle hook.** The test runner loads an optional `test/_init.js` from BOTH test roots (BEM core + consumer project) and runs it before any test (it is not run as a test itself). The module **must export a function** — `module.exports = (ctx) => ({ ... })` — called with `{ config, Manager }` and returning the hook object. It may declare `accounts` (array of extra test accounts `{ id, uid, email, properties }`, created/fetched/deleted on the same path as the built-ins so a project has a user per lifecycle) and `async setup({ admin, config, accounts, Manager, assistant })` (reseed fixtures into the freshly-flushed DB, after account creation). There is **no `cleanup` hook** — the whole DB is flushed each run and each test cleans up after itself. A default boilerplate `test/_init.js` now ships via `src/defaults/` (copied into consumers on first `npx mgr setup`, never overwriting an existing one). Mirrored across all four OMEGA frameworks. See `docs/testing.md`.
195
+ - **`test/_init.js` pre-test lifecycle hook.** The test runner loads an optional `test/_init.js` from BOTH test roots (BEM core + consumer project) and runs it before any test (it is not run as a test itself). The module **must export a function** — `module.exports = (ctx) => ({ ... })` — called with `{ config, Manager }` and returning the hook object. It may declare `accounts` (array of extra test accounts `{ id, uid, email, properties }`, created/fetched/deleted on the same path as the built-ins so a project has a user per lifecycle) and `async setup({ admin, config, accounts, Manager, assistant })` (reseed fixtures into the freshly-flushed DB, after account creation). There is **no `cleanup` hook** — the whole DB is flushed each run and each test cleans up after itself. A default boilerplate `test/_init.js` now ships via `src/defaults/` (copied into consumers on first `npx mgr setup`, never overwriting an existing one). Mirrored across all four OMEGA frameworks. See `docs/test-framework.md`.
167
196
 
168
197
  ### Changed
169
198
  - **Environment detection consolidated onto the Manager as SSOT.** `getEnvironment()` returns exactly one of `development | testing | production` (mutually exclusive, testing wins), read **live** from `process.env` on every call (no caching). `assistant.isDevelopment/isProduction/isTesting/getEnvironment` forward to the Manager. Fixes a bug where a cached environment made `getApiUrl()` resolve to the production URL inside the test runner.
@@ -243,7 +272,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
243
272
  - **`AI` `claude-code` provider rewritten for serverside use** (`src/manager/libraries/ai/providers/claude-code.js`). Was a local-only wrapper around `@anthropic-ai/claude-agent-sdk` that spawned the `claude` binary (`forceLoginMethod: 'claudeai'`, keychain OAuth) — would not run in Cloud Functions. Now calls the Claude Messages API over plain HTTPS via `@anthropic-ai/sdk` using the OAuth token as `Authorization: Bearer` + `anthropic-beta: oauth-2025-04-20`, so it bills the Claude Pro/Max subscription (not API credits) and runs anywhere Node runs. Token resolution: `options.apiKey` → `config.claude_code.oauth_token` → `process.env.CLAUDE_CODE_OAUTH_TOKEN`. Renewal is a manual yearly `claude setup-token` (no auto-refresh). Verified live (text + JSON-schema paths). See `docs/ai-library.md`.
244
273
  - **Dependency bumps**: `@anthropic-ai/claude-agent-sdk` ^0.3.152 → ^0.3.153, `stripe` ^22.1.1 → ^22.2.0.
245
274
  - **`templates/_.env`**: consolidate OpenAI/Anthropic keys under an "AI" section and add `CLAUDE_CODE_OAUTH_TOKEN`.
246
- - **Marketing webhook tests** (`test/routes/marketing/webhook.js`): renamed `*-duplicate-event-skipped` → `*-reprocessed-idempotently` (assert re-delivery reprocesses and the user stays revoked); `sendgrid-event-without-eventId-*` now asserts the event is processed; beehiiv idempotency variant skips when no publication is configured. Updated `docs/consent.md` and `docs/testing.md` to describe the no-ledger design.
275
+ - **Marketing webhook tests** (`test/routes/marketing/webhook.js`): renamed `*-duplicate-event-skipped` → `*-reprocessed-idempotently` (assert re-delivery reprocesses and the user stays revoked); `sendgrid-event-without-eventId-*` now asserts the event is processed; beehiiv idempotency variant skips when no publication is configured. Updated `docs/consent.md` and `docs/test-framework.md` to describe the no-ledger design.
247
276
 
248
277
  # [5.2.12] - 2026-05-27
249
278
 
@@ -384,7 +413,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
384
413
  - **Cross-provider unsubscribe webhooks (Phase E).** New `POST /marketing/webhook?provider={sendgrid|beehiiv}&key=...` dispatcher with per-provider processor modules. SendGrid events (`unsubscribe`, `group_unsubscribe`, `spamreport`, `bounce`, `dropped`) and Beehiiv events (`subscription.unsubscribed`, `.deleted`, `.paused`) flip `consent.marketing.status` to `revoked`, attribute via `source`, and propagate to the OTHER provider. Idempotent via `marketing-webhooks/{eventId}` docs.
385
414
  - **Parent BEM forwarder.** `POST /marketing/webhook/forward` lets a parent BEM (one with `config.parent === 'self'`) fan webhook events out to sibling brands sharing a SendGrid account or Beehiiv publication. New `Manager.getParentUrl()`, `Manager.getParentApiUrl()`, `Manager.isParent()` helpers — children store the parent's `brand.url` with NO `api.` subdomain and the helper inserts `api.` at call time.
386
415
  - **Self-contained TEST_EXTENDED_MODE.** `src/test/runner.js` + `src/test/test-accounts.js` now do pre + post-run cleanup of SendGrid/Beehiiv contacts (the only third-party state we can't wipe at start). Pure Firestore/Auth state is still wiped only at start, per existing convention.
387
- - **New docs.** `docs/consent.md` (consent system + webhook flows + migration template). `docs/testing.md` updated with the post-run-cleanup exception.
416
+ - **New docs.** `docs/consent.md` (consent system + webhook flows + migration template). `docs/test-framework.md` updated with the post-run-cleanup exception.
388
417
 
389
418
  ### Changed
390
419
 
package/CLAUDE.md CHANGED
@@ -10,7 +10,7 @@ Backend Manager (BEM) is a comprehensive framework for building modern Firebase
10
10
 
11
11
  ## Recommended skills
12
12
 
13
- - **`BEM:patterns`** — SSOT for Backend Manager routes, schemas, tests, Firebase functions, Firestore rules, usage tracking patterns. Auto-loads on BEM-specific keywords (`route`, `schema`, `endpoint`, `bm_api`, `Manager.init`, `npx mgr test`, `gcloud logs`, etc.) and when touching files in `functions/routes/`, `functions/schemas/`, `functions/index.js`, `test/`, `src/cli/commands/`.
13
+ - **`omega:bem`** — router skill. Auto-loads on BEM-specific keywords (`route`, `schema`, `endpoint`, `bm_api`, `Manager.init`, `npx mgr test`, `gcloud logs`, etc.) and points back to this CLAUDE.md + `docs/` (the SSOT), carrying only Claude-workflow hard rules and process checklists.
14
14
  - **`js:patterns`** — JavaScript/Node.js conventions: file structure, JSDoc, defensive coding (`?.` usage), template literals, `package.json` conventions. Auto-loads when creating new `.js` files or touching JS module structure.
15
15
 
16
16
  ## Quick Start
@@ -21,11 +21,15 @@ Backend Manager (BEM) is a comprehensive framework for building modern Firebase
21
21
  2. `npx mgr setup` — validates config, scaffolds defaults (CLAUDE.md, CHANGELOG.md, docs/, test/), provisions Firestore indexes
22
22
  3. `npx mgr emulator` — start Firebase emulators (auth/firestore/functions/database/storage)
23
23
  4. `npx mgr serve` — local serve with Stripe webhook forwarding (if `STRIPE_SECRET_KEY` is set)
24
- 5. `npx mgr test` — runs framework + project test suites against an emulator
25
- - `npx mgr test email/transactional` — run a specific test by path (relative to `test/`)
26
- - `npx mgr test bem:email/templates` — run only BEM framework tests matching a path
27
- - `npx mgr test project:routes/custom` — run only consumer project tests matching a path
28
- - Prefix with `TEST_EXTENDED_MODE=true` for tests that hit real external APIs (SendGrid, OpenAI, etc.)
24
+ 5. `npx mgr test` — runs framework + project test suites against an emulator. Positional target(s) select which test FILES run, by source + path (multiple space-separated targets compose):
25
+ - `npx mgr test` — everything (framework + project suites)
26
+ - `npx mgr test email/transactional` — bare path (no prefix): both sources, matched by path (relative to `test/`)
27
+ - `npx mgr test mgr:` / `npx mgr test bem:` ONLY framework tests (`mgr:` is the universal cross-framework alias for the manager's own tests; `bem:` is the equivalent BEM-specific alias)
28
+ - `npx mgr test mgr:email/templates` / `npx mgr test bem:email/templates` only framework tests matching a path
29
+ - `npx mgr test project:` — ONLY project tests (all of them)
30
+ - `npx mgr test project:routes/custom` — only consumer project tests matching a path
31
+ - `npx mgr test bem:rules project:routes` — multiple targets compose (runs both selections)
32
+ - Pass `--extended` (or prefix `TEST_EXTENDED_MODE=true`) for tests that hit real external APIs (SendGrid, OpenAI, etc.). `--extended` is the CLI shorthand for the shared, unprefixed `TEST_EXTENDED_MODE` env var standardized across BEM/BXM/UJM/EM; BEM propagates it to BOTH the runner subprocess and the live emulator. See [docs/test-framework.md](docs/test-framework.md#extended-mode-test_extended_mode).
29
33
  6. `npx mgr deploy` — deploy Cloud Functions to Firebase
30
34
  7. `npx mgr logs:read` / `npx mgr logs:tail` — Cloud Function logs from Google Cloud Logging
31
35
 
@@ -38,7 +42,7 @@ All `npx mgr <cmd>` aliases work: `npx bm <cmd>`, `npx bem <cmd>`, `npx backend-
38
42
  1. `npm install` — install BEM's own deps
39
43
  2. `npm run prepare` — build once: copies `src/` → `dist/` via prepare-package
40
44
  3. `npm run prepare:watch` — watch mode
41
- 4. Test in a consumer project: from inside the consumer, run `npx mgr install dev` to swap BEM to this local repo — required whenever you edit the framework source and want the consumer to pick up the changes (the consumer otherwise keeps its installed `node_modules/backend-manager`). Reverse with `npx mgr install live`. If `npx mgr` then errors with "could not determine executable to run", the local install skipped bin-linking — re-run `npm install` to relink, or call `node node_modules/backend-manager/bin/backend-manager <cmd>` directly.
45
+ 4. Test in the **designated test consumer** `../ultimate-jekyll-backend` is BEM's consumer for validating framework changes end-to-end (exercise any consumer-level flow there freely: emulator, tests, deploy paths). From inside it, run `npx mgr install dev` to swap BEM to this local repo — required whenever you edit the framework source and want the consumer to pick up the changes (the consumer otherwise keeps its installed `node_modules/backend-manager`). Reverse with `npx mgr install live`. If `npx mgr` then errors with "could not determine executable to run", the local install skipped bin-linking — re-run `npm install` to relink, or call `node node_modules/backend-manager/bin/backend-manager <cmd>` directly.
42
46
 
43
47
  ## Architecture
44
48
 
@@ -46,6 +50,14 @@ BEM exposes a single `Manager` class that orchestrates everything: it initialize
46
50
 
47
51
  For the directory layout of both the BEM library and consumer projects, see [docs/directory-structure.md](docs/directory-structure.md).
48
52
 
53
+ ### Test framework
54
+
55
+ `npx mgr test` runs framework + project suites against a **real Firebase emulator** (real Firestore/Auth — never mocked). Suites are organized by concern (`test/routes/`, `test/events/`, `test/rules/`, …) rather than runtime layers. See [docs/test-framework.md](docs/test-framework.md).
56
+
57
+ ### Test coverage
58
+
59
+ Every feature ships with tests at EVERY surface it exposes — logic (`test/routes/`/`test/events/` handler suites against the real emulator), wiring (route round-trips over `http.as(...)` — registration, auth gates, schema validation; this IS BEM's end-to-end), and rules (Firestore security-rules suites when rules change). BEM has no UI layer — a feature's UI coverage lives in the consuming frontend (UJM/BXM/EM). Skip a surface ONLY when the feature genuinely doesn't have one; "the handler test already covers it" is NOT a reason to skip the route round-trip. See [docs/test-framework.md](docs/test-framework.md).
60
+
49
61
  ## CLI
50
62
 
51
63
  `npx mgr <command>` (aliases `bm`, `bem`, `backend-manager`):
@@ -70,10 +82,17 @@ For the directory layout of both the BEM library and consumer projects, see [doc
70
82
 
71
83
  See [docs/cli-firestore-auth.md](docs/cli-firestore-auth.md) and [docs/cli-logs.md](docs/cli-logs.md) for full flag references.
72
84
 
85
+ ## Dependency Resolution
86
+
87
+ - **Consumer code can use `Manager.require(name)`** to load any BEM dependency from BEM's own module context (static + prototype). Consumer projects do NOT need to install BEM's transitive deps directly.
88
+ - **web-manager owns Firebase on the client side.** Consumer frontend code (UJM pages, BXM popup/options, EM renderers) NEVER imports Firebase directly — `firebase.firestore()` → `webManager.firestore()`, `firebase.auth()` → `webManager.auth()`. BEM backend code uses `firebase-admin` directly (server-side is different). The three frontend frameworks (EM/BXM/UJM) additionally resolve deps via webpack `resolve.modules`.
89
+
73
90
  ## Development Workflow
74
91
 
75
- - **🚫 NEVER run `npm start` / `npm test` / `npx mgr emulator`** unless the user explicitly asks. Assume the user is already running the emulator or dev process. Running these commands kills the user's process and wastes time. Instead, **check output logs** after editing files to confirm the change took effect.
92
+ - **🚫 NEVER run `npx mgr serve` / `npx mgr emulator`** they're the user's long-running dev processes. Assume they're already running; if they aren't, **instruct the user to run them** rather than running them yourself (running them again kills theirs). To see output, **read the `functions/*.log` files** (`dev.log`, `emulator.log`, `test.log`) — never tail/attach to the process. Running `npx mgr test` is fine (it auto-starts its own emulator if needed).
93
+ - **Where the output logs live:** BEM CLI commands tee output to `<projectDir>/functions/` (not `logs/` — BEM's deliberate exception, co-located with firebase-tools' `*-debug.log`): `dev.log` (`npx mgr serve`), `emulator.log` (`npx mgr emulator` / test with own emulator), `test.log` (`npx mgr test`), `production.log` (`npx mgr logs`). The `dev`/`test` names match EM/BXM/UJM; see [docs/test-framework.md](docs/test-framework.md#log-files).
76
94
  - **If the user reports an error**, check the emulator/test output for the root cause before guessing.
95
+ - **Live-test UI changes via CDP.** When working on admin dashboards or browser-facing endpoints, use the `chrome-devtools` MCP tools (screenshots, click, evaluate JS, console logs) to verify the change works in the running browser. See `~/.claude/mcp-server/servers/chrome-devtools/CLAUDE.md`.
77
96
 
78
97
  ## File Conventions
79
98
 
@@ -112,9 +131,11 @@ Deep references live in `docs/`. **Whenever you make a behavioral change, update
112
131
 
113
132
  - [docs/architecture.md](docs/architecture.md) — Manager class, dual-mode (firebase/custom), helper factory pattern
114
133
  - [docs/directory-structure.md](docs/directory-structure.md) — BEM library + consumer project layouts
134
+ - [docs/build-system.md](docs/build-system.md) — no consumer build (deliberate outlier), framework prepare-package, deploy pipeline
115
135
  - [docs/code-patterns.md](docs/code-patterns.md) — short-circuit returns, logical operators on new lines, Firestore shorthand, template-string requires, fs-jetpack preference
116
136
  - [docs/file-naming.md](docs/file-naming.md) — naming table for routes, schemas, API commands, events, cron jobs, hooks
117
137
  - [docs/common-mistakes.md](docs/common-mistakes.md) — anti-pattern checklist (don't modify Manager internals, always await, increment-before-update, etc.)
138
+ - [docs/audit.md](docs/audit.md) — full-audit check catalog (U-xx universal / BEM-xx / F-xx IDs with severity + scope), protocol + fix loop
118
139
  - [docs/key-files.md](docs/key-files.md) — quick lookup for the most-touched files (Manager, helpers, auth events, cron, payment processors, CLI commands)
119
140
  - [docs/cli-output.md](docs/cli-output.md) — shared CLI styling module (`src/cli/utils/ui.js`): OMEGA-style banner/dividers/sections/status symbols + the `Summary` block; used by `setup`, adoptable by other commands
120
141
  - [docs/environment-detection.md](docs/environment-detection.md) — `getEnvironment()` returns `'development' | 'testing' | 'production'` (mutually exclusive); gate side effects on the INTENTIONAL check (`isProduction()` for prod-only, `isDevelopment() || isTesting()` for local-or-test) — never `!isDevelopment()`. Plus the URL helper convention (always `Manager.getApiUrl()` — auto-resolves local in dev+test, never read `project.apiUrl`)
@@ -122,8 +143,10 @@ Deep references live in `docs/`. **Whenever you make a behavioral change, update
122
143
 
123
144
  ### Building Routes & Components
124
145
 
125
- - [docs/routes.md](docs/routes.md) — recipes for new API commands, routes, event handlers, cron jobs (with code templates)
126
- - [docs/schemas.md](docs/schemas.md) — schema definition format, defaults vs premium overrides
146
+ - [docs/routes.md](docs/routes.md) — recipes for new API commands, routes (context-object handlers, CRUD method files, ownership checks, firebase.json rewrites + ordering, functions/index.js entry), event handlers, cron jobs
147
+ - [docs/schemas.md](docs/schemas.md) — schema contract (context object → flat schema, in-function plan branching), field properties, ID generation + path extraction, required-vs-default footgun
148
+ - [docs/firestore.md](docs/firestore.md) — path style, NO subcollections, batch reads (~500 cursor pagination), `metadata.{created,updated}` timestamps, response format + redaction
149
+ - [docs/migration.md](docs/migration.md) — legacy-project migration: runtime config → top-level env vars, `Manager.config.*` → `process.env.*`, constructor routes / tiered schemas → current format
127
150
  - [docs/sanitization.md](docs/sanitization.md) — middleware trim-only default; opt-in HTML strip (`{ sanitize: true }`) with per-field opt-out (`sanitize: false`); manual `utilities.sanitize()` for HTML-insertion sites
128
151
  - [docs/auth-hooks.md](docs/auth-hooks.md) — consumer hooks for `before-create`/`before-signin`/`on-create`/`on-delete` (blocking + non-blocking examples)
129
152
  - [docs/common-operations.md](docs/common-operations.md) — inside-the-handler patterns: authenticate, read/write Firestore, error handling, send response, `bm_api` hook
@@ -146,6 +169,8 @@ Deep references live in `docs/`. **Whenever you make a behavioral change, update
146
169
 
147
170
  ### Testing & CLI
148
171
 
149
- - [docs/testing.md](docs/testing.md) — running, filtering, log files, test types (standalone/suite/group), context object, assertions, auth levels. **NEVER mock — test against the real emulator.** No `mockManager`/`mockAdmin`/fake `firestore`/stubbed `assistant`; every `run()` gets the real `Manager`/`assistant`/`firestore`/`http`/`accounts` — use them. Pure functions (zero I/O) are the only thing you call directly; anything touching Firestore or an external API runs for real. Real external APIs (OpenAI/PayPal/GitHub/SendGrid/Stripe) are gated behind `TEST_EXTENDED_MODE` in-source (not mocked), and anything an extended test creates externally must be cleaned up by the test. **Each test file `module.exports` a `{ description, type, tests }` object — NOT raw Mocha (`describe`/`it`/`beforeEach`); those globals are not injected and the file fails to load. Split tests one-file-per-concern under `test/<area>/`, never one giant `test/test.js`.** **All cleanup runs at the START of every run, never at the end** — the runner flushes the ENTIRE emulator Firestore before every run, so there's nothing to register; seed any needed fixtures in `test/_init.js`'s `setup()`, and never add a trailing cleanup step. Marketing providers (SendGrid/Beehiiv) don't need a special exception — `_test.*` emails are blocked at the validation layer so test signups never reach providers. The `_test.allow_*` carve-out exists only for the live-provider lifecycle test (`test/marketing/consent-lifecycle.js`), which manages its own teardown.
172
+ - [docs/test-framework.md](docs/test-framework.md) — running, filtering, log files, test types (standalone/suite/group), context object, assertions, auth levels. **NEVER mock — test against the real emulator.** No `mockManager`/`mockAdmin`/fake `firestore`/stubbed `assistant`; every `run()` gets the real `Manager`/`assistant`/`firestore`/`http`/`accounts` — use them. Pure functions (zero I/O) are the only thing you call directly; anything touching Firestore or an external API runs for real. Real external APIs (OpenAI/PayPal/GitHub/SendGrid/Stripe) are gated behind `TEST_EXTENDED_MODE` in-source (not mocked) — opt in with `--extended` or `TEST_EXTENDED_MODE=true` (shared, unprefixed across BEM/BXM/UJM/EM; propagates to BOTH runner + emulator) — and anything an extended test creates externally must be cleaned up by the test. **Each test file `module.exports` a `{ description, type, tests }` object — NOT raw Mocha (`describe`/`it`/`beforeEach`); those globals are not injected and the file fails to load. Split tests one-file-per-concern under `test/<area>/`, never one giant `test/test.js`.** **All cleanup runs at the START of every run, never at the end** — the runner flushes the ENTIRE emulator Firestore before every run, so there's nothing to register; seed any needed fixtures in `test/_init.js`'s `setup()`, and never add a trailing cleanup step. Marketing providers (SendGrid/Beehiiv) don't need a special exception — `_test.*` emails are blocked at the validation layer so test signups never reach providers. The `_test.allow_*` carve-out exists only for the live-provider lifecycle test (`test/marketing/consent-lifecycle.js`), which manages its own teardown.
173
+ - [docs/test-boot-layer.md](docs/test-boot-layer.md) — the `boot/` smoke layer: framework self-test from the repo via the bundled fixture project + `BEM_TEST_BOOT_PROJECT` (BEM's analog of BXM/UJM `*_TEST_BOOT_PROJECT`)
150
174
  - [docs/cli-firestore-auth.md](docs/cli-firestore-auth.md) — `npx mgr firestore:*` and `auth:*` commands, shared flags, examples
151
175
  - [docs/cli-logs.md](docs/cli-logs.md) — `npx mgr logs:read` / `logs:tail` with full flag reference and built-in Cloud Function names
176
+ - [docs/logging.md](docs/logging.md) — `functions/*.log` file table (the `functions/` location exception), `production.log`
package/README.md CHANGED
@@ -850,18 +850,19 @@ npx mgr test
850
850
 
851
851
  ### Extended Mode (real APIs)
852
852
 
853
- Set `TEST_EXTENDED_MODE=true` on the **test command** to opt into real external API calls (SendGrid, Beehiiv, Stripe webhook handlers, marketing libraries). The flag flows automatically to the running emulator via `<projectRoot>/.temp/test-mode.json` — no need to set it on the emulator too:
853
+ Pass `--extended` (or set `TEST_EXTENDED_MODE=true`) on the **test command** to opt into real external API calls (SendGrid, Beehiiv, Stripe webhook handlers, marketing libraries). `--extended` is the CLI shorthand for the shared, unprefixed `TEST_EXTENDED_MODE` env var standardized across BEM/BXM/UJM/EM — the two forms are equivalent. The mode flows automatically to BOTH the test-runner subprocess and the running emulator (via `<projectRoot>/.temp/test-mode.json`) — no need to set it on the emulator too:
854
854
 
855
855
  ```bash
856
856
  # Terminal 1 — start once, no flag needed
857
857
  npx mgr emulator
858
858
 
859
859
  # Terminal 2 — toggle freely between runs
860
- TEST_EXTENDED_MODE=true npx mgr test ... # extended mode
860
+ npx mgr test --extended ... # extended mode (--extended sets TEST_EXTENDED_MODE)
861
+ TEST_EXTENDED_MODE=true npx mgr test ... # identical — the env-var form
861
862
  npx mgr test ... # normal mode (next run flips back)
862
863
  ```
863
864
 
864
- See [docs/testing.md](docs/testing.md#extended-mode-test_extended_mode) for the full mechanism.
865
+ See [docs/test-framework.md](docs/test-framework.md#extended-mode-test_extended_mode) for the full mechanism.
865
866
 
866
867
  ### Filtering Tests
867
868
 
@@ -875,10 +876,10 @@ npx mgr test user/ admin/ # Multiple paths
875
876
  ### Log Files
876
877
 
877
878
  BEM CLI commands automatically save output to log files in the project's `functions/` directory (alongside firebase-tools' own `*-debug.log` files so everything is grep-able from one place):
878
- - **`functions/serve.log`** — Output from `npx mgr serve`
879
+ - **`functions/dev.log`** — Output from `npx mgr serve` (BEM's local dev server)
879
880
  - **`functions/emulator.log`** — Full emulator + Cloud Functions output (`npx mgr emulator`)
880
881
  - **`functions/test.log`** — Test runner output (`npx mgr test`, when running against an existing emulator)
881
- - **`functions/logs.log`** — Cloud Function logs (`npx mgr logs:read` or `npx mgr logs:tail`)
882
+ - **`functions/production.log`** — Production Cloud Function logs (`npx mgr logs:read` or `npx mgr logs:tail`)
882
883
 
883
884
  Logs are overwritten on each run and gitignored via `*.log`. Use them to debug failing tests or review function output. Transient internal artifacts (reset sentinels, watch trigger, `test-mode.json`) live separately in `<projectDir>/.temp/`.
884
885
 
package/docs/audit.md ADDED
@@ -0,0 +1,70 @@
1
+ # Audit Workflow
2
+
3
+ Full-project audit for BEM — runs against a CONSUMER backend or the FRAMEWORK repo itself (scope auto-detected). Invoked via the `omega:bem` skill (`/omega:bem audit`) or any "audit this backend/project" request.
4
+
5
+ Every check has a stable ID, a severity, and a scope. Findings are reported as `ID @ file:line`, fixed one at a time, then re-verified. The tables below do NOT restate the rules — each check links to the doc that owns the rule and the fix.
6
+
7
+ ## Protocol
8
+
9
+ 1. **Detect scope** — read `package.json` (consumer: `functions/package.json`): `name` is `backend-manager` → **framework audit** (U + BEM + F checks); `backend-manager` in (dev)dependencies → **consumer audit** (U + BEM checks).
10
+ 2. **Run the catalog** — every check matching the scope. Search with Grep/Glob/Read over `functions/` (`routes/`, `schemas/`, `hooks/`, `index.js`), `test/`, and config files; ALWAYS exclude `node_modules/`, `dist/`, `_legacy/`, `_backup/`. Record each finding as `ID @ file:line` + a one-line description.
11
+ 3. **Persist the report** — write the findings list to `functions/.temp/audit/claude-audit.md` (BEM's `functions/`-local convention, like its logs) so a long fix loop survives session breaks. Summarize counts by severity in chat.
12
+ 4. **Fix loop** — TodoWrite per finding, highest severity first, ONE at a time: mark in-progress → root cause → fix → verify → complete. Ask before structural or destructive fixes (file deletions, schema reshapes, data migrations).
13
+ 5. **Re-verify** — re-run every check that produced findings until clean; finish with `npx mgr test` from `functions/` (must be green — it auto-starts its own emulator if needed).
14
+ 6. **Doc parity** — if fixes changed behavior, update README / CLAUDE.md / `docs/<topic>.md` / CHANGELOG in the same change set.
15
+
16
+ Severity: **CRIT** security or broken functionality · **HIGH** hard-rule violation · **MED** convention drift · **LOW** optional improvement.
17
+ Scope: **C** consumer · **F** framework repo · **B** both.
18
+
19
+ ## Universal checks (U-xx)
20
+
21
+ Mirrored across all four OMEGA frameworks (UJM / BEM / BXM / EM) — same ID means the same check everywhere.
22
+
23
+ | ID | Sev | Scope | Check |
24
+ |----|-----|-------|-------|
25
+ | U-01 | HIGH | B | Every feature has tests at EVERY surface it exposes — handler suites + `http.as(...)` route round-trips + rules suites; never mocked, real emulator only ([test-framework.md](test-framework.md)) |
26
+ | U-02 | HIGH | B | Test hygiene — side-effect tests use dedicated `journey-*` accounts; real-external-API tests gated behind `TEST_EXTENDED_MODE` in-source (not mocked); files export `{ description, type, tests }` (no raw Mocha); no trailing cleanup steps ([test-framework.md](test-framework.md)) |
27
+ | U-03 | CRIT | B | Sanitization — middleware is trim-only by default; HTML strip via opt-in `{ sanitize: true }`; every HTML-insertion site calls `utilities.sanitize()` ([sanitization.md](sanitization.md)) |
28
+ | U-04 | HIGH | B | Firebase ownership — server code uses `firebase-admin` via Manager (correct here); NO client `firebase` SDK in functions code; consuming frontends go through web-manager ([CLAUDE.md](../CLAUDE.md) §Dependency Resolution) |
29
+ | U-05 | HIGH | C | No BEM transitive deps installed directly in `functions/package.json` — use `Manager.require(name)` ([CLAUDE.md](../CLAUDE.md) §Dependency Resolution) |
30
+ | U-06 | HIGH | B | Env behavior gated on the INTENTIONAL check — `isProduction()` or `isDevelopment() \|\| isTesting()`, never `!isDevelopment()`; always `Manager.getApiUrl()`, never the cached `Manager.project.apiUrl` ([environment-detection.md](environment-detection.md)) |
31
+ | U-07 | HIGH | B | Config canon — `backend-manager-config.json` matches the documented shape; canonical cross-framework blocks (`brand`, payment products, …) not reinvented ([architecture.md](architecture.md), [payment-system.md](payment-system.md)) |
32
+ | U-08 | CRIT | B | No private credentials committed — `service-account.json`, `.env` secrets, API keys (Stripe `sk_`, SendGrid `SG.`, …); `.gitignore` covers them. (The Firebase WEB `apiKey` is public by design — do NOT flag it.) |
33
+ | U-09 | HIGH | B | Source discipline — no live code referencing `_legacy/` / `_backup/`; framework edits in `src/` (never `dist/`) ([common-mistakes.md](common-mistakes.md)) |
34
+ | U-10 | MED | B | Doc parity — README / CLAUDE.md / `docs/` / CHANGELOG match shipped behavior; CLAUDE.md < 250 lines; the docs index lists every `docs/*.md`; no stale names for renamed commands/patterns |
35
+ | U-11 | MED | B | SSOT/DRY — no duplicated constants/config/logic; one authoritative home per value, imported everywhere else |
36
+ | U-12 | MED | B | JS conventions — file structure, JSDoc, short-circuit returns, leading logical operators, `fs-jetpack`, one `module.exports` per file ([code-patterns.md](code-patterns.md) + global `js:patterns` skill) |
37
+ | U-13 | MED | B | Dead code & stale patterns — no orphaned files nothing imports; no leftovers of migrated-away formats (constructor routes, tiered schemas, `Manager.config.*` reads — [migration.md](migration.md)); inventory TODO/FIXME (report only) |
38
+ | U-14 | LOW | B | Dependency health — review `npm outdated` / `npm audit` (in `functions/`); apply fixes via the `general:update-packages` workflow (includes supply-chain checks) |
39
+
40
+ ## BEM-specific checks
41
+
42
+ | ID | Sev | Scope | Check |
43
+ |----|-----|-------|-------|
44
+ | BEM-01 | HIGH | B | Every custom route has a name-matched schema; handlers are context-object exports (`async ({ Manager, assistant, … }) => {}`) — no legacy constructor routes ([routes.md](routes.md), [schemas.md](schemas.md)) |
45
+ | BEM-02 | HIGH | B | Schema field rules — never `required: true` + `default` together (required is checked BEFORE defaults; use `min: 1` for path-extracted IDs); flat schema with in-function plan branching, no tier arrays ([schemas.md](schemas.md)) |
46
+ | BEM-03 | HIGH | B | Route handlers — ownership checks on PUT/DELETE; plural-noun route names; `assistant.respond()` only (never `res.send()`) ([routes.md](routes.md), [common-operations.md](common-operations.md)) |
47
+ | BEM-04 | HIGH | C | Wiring — every route exported in `functions/index.js`; `firebase.json` rewrites use bracket syntax, ordered most-specific-first ([routes.md](routes.md)) |
48
+ | BEM-05 | HIGH | B | Firestore canon — NO subcollections; path-string `.doc('users/abc')`; batched collection reads (~500, cursor pagination); timestamps under `metadata.{created,updated}`; mirror-the-doc responses; delete-don't-redact ([firestore.md](firestore.md)) |
49
+ | BEM-06 | HIGH | B | Usage — never read/write `{doc}.usage.*` manually, always the `usage` helper; expensive/abusable routes carry usage validation or rate limiting ([usage-rate-limiting.md](usage-rate-limiting.md)) |
50
+ | BEM-07 | MED | B | Composite indexes — every compound query (`where` + `orderBy`, multiple `where`s) is registered in the required-indexes SSOT ([CLAUDE.md](../CLAUDE.md) §File Conventions) |
51
+ | BEM-08 | HIGH | B | Auth gates — routes resolve the caller via `assistant`/`user` before acting; admin-only routes verify admin status ([common-operations.md](common-operations.md), [routes.md](routes.md)) |
52
+ | BEM-09 | HIGH | B | Rules coverage — `firestore.rules` changes ship a rules suite (`rules.asAccount` / `expectSuccess` / `expectFailure`) ([test-framework.md](test-framework.md)) |
53
+
54
+ ## Framework-repo checks (F-xx)
55
+
56
+ Only when auditing the BEM repo itself. Mirrored across the four frameworks.
57
+
58
+ | ID | Sev | Check |
59
+ |----|-----|-------|
60
+ | F-01 | MED | Sister parity — mirrored sections (config shapes, test contract, CLAUDE.md skeleton, shared env/test conventions) in sync with UJM / BXM / EM; deviations are deliberate and documented |
61
+ | F-02 | HIGH | Consumer-shipped defaults in sync — what `npx mgr setup` scaffolds matches current conventions and docs |
62
+ | F-03 | MED | Docs completeness — every `docs/*.md` indexed in CLAUDE.md; every subsystem has a doc; no "(planned)" links for things that have shipped |
63
+ | F-04 | HIGH | `npx mgr test mgr:` green before treating the audit as complete |
64
+
65
+ ## See also
66
+
67
+ - [schemas.md](schemas.md) — the required-vs-default footgun behind BEM-02
68
+ - [firestore.md](firestore.md) — the data canon behind BEM-05
69
+ - [migration.md](migration.md) — the legacy formats U-13 hunts for
70
+ - [test-framework.md](test-framework.md) — the surfaces behind U-01 / U-02 / BEM-09
@@ -0,0 +1,29 @@
1
+ # Build System
2
+
3
+ BEM is the deliberate outlier among the four OMEGA frameworks: **consumer projects have no build pipeline**. There is no gulp, no webpack, no bundling — `functions/` runs as-is in the Firebase emulator locally and deploys as-is to Cloud Functions.
4
+
5
+ ## Pipeline overview
6
+
7
+ | Stage | Command | What happens |
8
+ |---|---|---|
9
+ | Local dev | `npx mgr emulator` / `npx mgr serve` | Functions run directly from `functions/` source in the Firebase emulator |
10
+ | Watch | `npx mgr watch` | Auto-reload functions on file change |
11
+ | Ship | `npx mgr deploy` | `functions/` deploys as-is to Firebase Cloud Functions |
12
+
13
+ There are no build modes — environment behavior is governed by emulator vs production, not a build flag. See [environment-detection.md](environment-detection.md).
14
+
15
+ ## prepare-package (framework-side)
16
+
17
+ The BEM library itself has one build step: `npm run prepare` copies `src/` → `dist/` via prepare-package (`npm run prepare:watch` for watch mode). Consumers always require from `dist/`. This mirrors the framework-side prepare step in EM/BXM/UJM.
18
+
19
+ At publish time, a `prepublishOnly` script first removes the self-test fixture's runtime `node_modules` (`src/test/fixtures/firebase-project/functions/node_modules`) — its `backend-manager` symlink points back at the repo root, which would send prepare-package's publish-time cleanup walk into an infinite cycle. The symlinks are throwaway; the next `npx mgr test` self-test regenerates them (see [test-boot-layer.md](test-boot-layer.md)).
20
+
21
+ ## Log files
22
+
23
+ CLI commands tee output to `functions/*.log` (`dev.log`, `emulator.log`, `test.log`, `production.log`). Full reference: [logging.md](logging.md).
24
+
25
+ ## See also
26
+
27
+ - [architecture.md](architecture.md) — Manager class, dual-mode support (`firebase` / `custom`)
28
+ - [directory-structure.md](directory-structure.md) — BEM library + consumer project layout
29
+ - [test-framework.md](test-framework.md) — the emulator-based test harness
package/docs/cli-logs.md CHANGED
@@ -19,9 +19,9 @@ npx mgr logs:tail # Stream live logs
19
19
  npx mgr logs:tail --fn bm_paymentsWebhookOnWrite # Stream filtered live logs
20
20
  ```
21
21
 
22
- Both commands save output to `functions/logs.log` (overwritten on each run). `logs:read` saves raw JSON; `logs:tail` streams text.
22
+ Both commands save output to `functions/production.log` (overwritten on each run). `logs:read` saves raw JSON; `logs:tail` streams text.
23
23
 
24
- **Cloud Logs vs Local Logs:** These commands query **production** Google Cloud Logging. For **local/dev** logs, read `functions/serve.log` (from `npx mgr serve`) or `functions/emulator.log` (from `npx mgr test`) directly — they are plain text files, not gcloud.
24
+ **Cloud Logs vs Local Logs:** These commands query **production** Google Cloud Logging. For **local/dev** logs, read `functions/dev.log` (from `npx mgr serve`) or `functions/emulator.log` (from `npx mgr test`) directly — they are plain text files, not gcloud.
25
25
 
26
26
  ## Flags
27
27
 
@@ -9,4 +9,4 @@
9
9
  7. **Use short-circuit returns** — Return early from error conditions
10
10
  8. **Increment usage before update** — Call `usage.increment()` then `usage.update()`
11
11
  9. **Add Firestore composite indexes for new compound queries** — Any new Firestore query using multiple `.where()` clauses or `.where()` + `.orderBy()` requires a composite index. Add it to `src/cli/commands/setup-tests/helpers/required-indexes.js` (the SSOT). Consumer projects pick these up via `npx mgr setup`, which syncs them into `firestore.indexes.json`. Without the index, the query will crash with `FAILED_PRECONDITION` in production.
12
- 10. **Don't put test data cleanup at the END of a test** — End-of-test cleanup doesn't fire when the previous run was killed mid-execution, so the next run inherits stale state. ALL test-data cleanup belongs in the runner's pre-test phase ([../src/test/test-accounts.js](../src/test/test-accounts.js) `deleteTestUsers()` + [../src/test/runner.js](../src/test/runner.js) `setupAccounts()`), which **flushes the entire emulator Firestore before every run** — so there's nothing to register when you add a test that writes data. Seed any required fixtures in `test/_init.js`'s `setup()` (runs after the flush). The only acceptable trailing cleanup is within-run state isolation (one test removes a doc so the NEXT test in the same run sees a clean slate) — that's not preparing the next run, it's intra-run housekeeping. See [testing.md](testing.md) "Test Data Cleanup".
12
+ 10. **Don't put test data cleanup at the END of a test** — End-of-test cleanup doesn't fire when the previous run was killed mid-execution, so the next run inherits stale state. ALL test-data cleanup belongs in the runner's pre-test phase ([../src/test/test-accounts.js](../src/test/test-accounts.js) `deleteTestUsers()` + [../src/test/runner.js](../src/test/runner.js) `setupAccounts()`), which **flushes the entire emulator Firestore before every run** — so there's nothing to register when you add a test that writes data. Seed any required fixtures in `test/_init.js`'s `setup()` (runs after the flush). The only acceptable trailing cleanup is within-run state isolation (one test removes a doc so the NEXT test in the same run sees a clean slate) — that's not preparing the next run, it's intra-run housekeeping. See [test-framework.md](test-framework.md) "Test Data Cleanup".
package/docs/consent.md CHANGED
@@ -138,7 +138,9 @@ Each processor's `handleEvent` does the same shape of work:
138
138
 
139
139
  | Provider | Event types treated as revoke |
140
140
  |---|---|
141
- | SendGrid | `unsubscribe`, `group_unsubscribe`, `spamreport`, `bounce`, `dropped` |
141
+ | SendGrid | `unsubscribe`, `group_unsubscribe`, `spamreport`, `bounce`\*, `dropped`\* |
142
+
143
+ \* `bounce` and `dropped` only revoke consent when `bounce_classification` is `'Invalid Address'` (hard bounce). Technical bounces (DMARC, TLS, DNS, reputation) are sender-side issues — the recipient's email is still valid, so consent is preserved.
142
144
  | Beehiiv | `subscription.unsubscribed`, `subscription.deleted`, `subscription.paused` |
143
145
 
144
146
  **Beehiiv publication filter.** Each Beehiiv event includes a `publication_id`. The processor compares this against `beehiivProvider.getPublicationId()`, which reads `Manager.config.marketing.newsletter.publicationId` (populated at brand-onboarding time by OMEGA's `beehiiv/ensure/publication.js`). Mismatch → silent skip. This is how shared-publication events (e.g. devbeans shared by 6 brands) get routed correctly — each brand processes only events matching its own publication. Brands without `publicationId` in config silently skip all Beehiiv webhook events. The same convention applies to SendGrid: `marketing.campaigns.listId` is populated by OMEGA's `sendgrid/ensure/list.js`.
@@ -313,7 +315,7 @@ Most BEM tests are self-contained against the local emulator. The marketing-cons
313
315
 
314
316
  The validation pipeline (`src/manager/libraries/email/validation.js`) blocks all `_test.*` emails from reaching providers via the `/^_test\.(?!allow_)/` pattern in `blocked-local-patterns.js`. The two `_test.allow_*` sentinels (`_test.allow_consent-granted` and `_test.allow_consent-declined`) used by the lifecycle test bypass that gate intentionally, and the test cleans up after itself (phase-3 removes the granted contact via `Manager.Email().remove()`).
315
317
 
316
- The "all cleanup runs at start, never at the end" rule documented in [docs/testing.md](testing.md) applies to all test data, including third-party providers.
318
+ The "all cleanup runs at start, never at the end" rule documented in [docs/test-framework.md](test-framework.md) applies to all test data, including third-party providers.
317
319
 
318
320
  ## Frontend pieces (cross-references)
319
321
 
@@ -56,6 +56,27 @@ After `build()`, `send()` delivers via SendGrid, handles scheduled sends (>71h
56
56
 
57
57
  `_sendCampaignSendGrid()` follows the same prepare → render → deliver pattern. Content comes from `data.content.message` (markdown) — same location as transactional callers. Key difference: audience targeting uses brand-scoped dynamic segments (see [marketing-campaigns.md](marketing-campaigns.md)).
58
58
 
59
+ ### Email Validation Pipeline (`validation.js`)
60
+
61
+ All marketing contact operations (`add`, `sync`) pass through `validate()` before reaching providers. Checks run in order; the first failure short-circuits.
62
+
63
+ | # | Check | What it catches | Cost | Default |
64
+ |---|---|---|---|---|
65
+ | 1 | `format` | Regex: must have `@`, domain, no spaces | Free | Yes |
66
+ | 2 | `disposable` | ~7k known disposable domains (vendor list + custom additions) | Free | Yes |
67
+ | 3 | `corporate` | Social/corporate domains (instagram.com, facebook.com, etc.) | Free | Yes |
68
+ | 4 | `localPart` | Junk local parts (test, noreply, all-numeric, `_test.*`) | Free | Yes |
69
+ | 5 | `typo` | Common domain misspellings via prefix match (`gamil.`, `gmai.`, `aol.con`, `gmail.cok`, etc.) | Free | Yes |
70
+ | 6 | `dns` | No MX record, null MX (RFC 7505), loopback MX, domain not found | Free | Opt-in |
71
+ | 7 | `mailbox` | SMTP mailbox verification via NeverBounce or ZeroBounce | Paid | Opt-in |
72
+
73
+ - **`DEFAULT_CHECKS`** = checks 1–5 (all free, run on every `mailer.add()`/`mailer.sync()` call)
74
+ - **`ALL_CHECKS`** = checks 1–7 (used at signup to include paid mailbox verification)
75
+ - The `dns` check is opt-in (not in DEFAULT_CHECKS) because it's async/slower — include it for bulk validation
76
+ - The `typo` check uses prefix matching (`"gamil."` catches `gamil.com`, `gamil.con`, `gamil.co`) — see `data/typo-domains.js`
77
+ - Custom disposable domains go in `data/custom-disposable-domains.json` (not the vendor list)
78
+ - Run `node src/manager/libraries/email/validation.test.js` to verify all checks
79
+
59
80
  ## Data Contract
60
81
 
61
82
  The template receives one `data` object with a clear separation of concerns:
@@ -263,7 +284,7 @@ All email tests live under `test/email/`, mirroring the source at `src/manager/l
263
284
  |---|---|---|
264
285
  | `templates.js` | MJML rendering for all 4 email templates (11 tests) | No |
265
286
  | `transactional.js` | Transactional email building (assertions on output shape) | No |
266
- | `validation.js` | Email format/disposable/corporate/local-part checks (80+ tests) | No |
287
+ | `validation.js` | Email format/disposable/corporate/local-part/typo/dns checks (60+ tests) | No |
267
288
  | `transactional-send.js` | Single transactional email send via SendGrid | Yes |
268
289
  | `campaign-send.js` | Marketing campaign send with title + CTA + discount code | Yes |
269
290
  | `feedback-and-plain-send.js` | Feedback + plain template visual test sends | Yes |
@@ -272,7 +293,7 @@ All email tests live under `test/email/`, mirroring the source at `src/manager/l
272
293
  | `marketing-lifecycle.js` | Contact lifecycle (add/sync/remove) | Yes |
273
294
  | `consent-lifecycle.js` | Consent webhook round-trip | Yes |
274
295
 
275
- Extended tests (`TEST_EXTENDED_MODE`) send real emails to `_test-*@{domain}` addresses. See [testing.md](testing.md) for the full test framework reference.
296
+ Extended tests (`TEST_EXTENDED_MODE`) send real emails to `_test-*@{domain}` addresses. See [test-framework.md](test-framework.md) for the full test framework reference.
276
297
 
277
298
  ### Test recipient convention
278
299
 
@@ -295,5 +316,10 @@ All extended email tests send to `_test-<purpose>@{domain}` addresses (e.g. `_te
295
316
  | UTM link tagging | `src/manager/libraries/email/utm.js` |
296
317
  | Constants (senders, groups, fields, segments) | `src/manager/libraries/email/constants.js` |
297
318
  | Email validation | `src/manager/libraries/email/validation.js` |
319
+ | Typo domain prefixes | `src/manager/libraries/email/data/typo-domains.js` |
320
+ | Custom disposable domains | `src/manager/libraries/email/data/custom-disposable-domains.json` |
321
+ | NeverBounce provider | `src/manager/libraries/email/validation-provider-neverbounce.js` |
322
+ | ZeroBounce provider | `src/manager/libraries/email/validation-provider-zerobounce.js` |
323
+ | Validation test | `src/manager/libraries/email/validation.test.js` |
298
324
  | Seed campaigns | `src/cli/commands/setup-tests/helpers/seed-campaigns.js` |
299
325
  | Transition email dispatcher | `src/manager/events/firestore/payments-webhooks/transitions/send-email.js` |
@@ -88,4 +88,4 @@ If you need a new environment-derived helper, add it next to the others on the M
88
88
 
89
89
  ## See also
90
90
 
91
- - [testing.md](testing.md) — `BEM_TESTING` is set automatically by the test runner; `TEST_EXTENDED_MODE` gates real external APIs.
91
+ - [test-framework.md](test-framework.md) — `BEM_TESTING` is set automatically by the test runner; `TEST_EXTENDED_MODE` gates real external APIs.
@@ -0,0 +1,134 @@
1
+ # Firestore Conventions
2
+
3
+ ## Path Style
4
+
5
+ Use `.doc('collectionId/documentId')` instead of `.collection('collectionId').doc('documentId')`.
6
+
7
+ ## No Subcollections
8
+
9
+ **NEVER use subcollections.** All collections MUST be top-level. Use an `owner` field to associate documents with a user instead of nesting under `/users/{uid}/`.
10
+
11
+ ```javascript
12
+ // CORRECT — top-level collection with owner field
13
+ await admin.firestore().doc(`items/${id}`).set({ owner: user.auth.uid, ... });
14
+
15
+ // WRONG — subcollection under users
16
+ await admin.firestore().doc(`users/${uid}/items/${id}`).set({ ... });
17
+ ```
18
+
19
+ ## Batch Collection Reads
20
+
21
+ **NEVER** dump an entire collection with a bare `.get()`. Always read and process in batches of ~500 using `.limit()` with `.startAfter()` cursor pagination. This applies to routes, cron jobs, standalone scripts — anywhere we query Firestore collections that could have many documents.
22
+
23
+ ```javascript
24
+ const BATCH_SIZE = 500;
25
+ let lastDoc = null;
26
+ let processed = 0;
27
+
28
+ while (true) {
29
+ let query = db.collection('items').limit(BATCH_SIZE);
30
+
31
+ if (lastDoc) {
32
+ query = query.startAfter(lastDoc);
33
+ }
34
+
35
+ const snapshot = await query.get();
36
+
37
+ if (snapshot.empty) {
38
+ break;
39
+ }
40
+
41
+ for (const doc of snapshot.docs) {
42
+ processed++;
43
+ // process doc...
44
+ }
45
+
46
+ lastDoc = snapshot.docs[snapshot.docs.length - 1];
47
+ }
48
+ ```
49
+
50
+ If you need a `.where()` filter, apply it before `.limit()`:
51
+
52
+ ```javascript
53
+ let query = db.collection('items')
54
+ .where('status', '==', 'active')
55
+ .limit(BATCH_SIZE);
56
+ ```
57
+
58
+ ## Document Metadata
59
+
60
+ All Firestore documents must nest `created` and `updated` timestamps under a `metadata` parent field — never as top-level fields:
61
+
62
+ ```javascript
63
+ // ✅ CORRECT — timestamps under metadata
64
+ const itemData = {
65
+ id: settings.id,
66
+ owner: user.auth.uid,
67
+ metadata: {
68
+ created: {
69
+ timestamp: assistant.meta.startTime.timestamp,
70
+ timestampUNIX: assistant.meta.startTime.timestampUNIX,
71
+ },
72
+ updated: {
73
+ timestamp: assistant.meta.startTime.timestamp,
74
+ timestampUNIX: assistant.meta.startTime.timestampUNIX,
75
+ },
76
+ },
77
+ };
78
+
79
+ // On update — preserve created, refresh updated
80
+ const updated = _.merge({}, existing, settings, {
81
+ metadata: {
82
+ created: existing.metadata.created,
83
+ updated: {
84
+ timestamp: assistant.meta.startTime.timestamp,
85
+ timestampUNIX: assistant.meta.startTime.timestampUNIX,
86
+ },
87
+ },
88
+ });
89
+
90
+ // ❌ WRONG — top-level timestamps
91
+ const itemData = {
92
+ created: { ... },
93
+ updated: { ... },
94
+ };
95
+ ```
96
+
97
+ In schemas, use `assistant.Manager.Settings().constant('timestampFULL')`:
98
+
99
+ ```javascript
100
+ metadata: {
101
+ created: assistant.Manager.Settings().constant('timestampFULL', { date: undefined }),
102
+ updated: assistant.Manager.Settings().constant('timestampFULL', { date: undefined }),
103
+ },
104
+ ```
105
+
106
+ ## Response Format
107
+
108
+ - **Return data in the same structure as Firestore.** Do NOT reshape, rename, or restructure fields (e.g., don't convert `metadata.created.timestamp` to `createdAt`).
109
+ - For single document responses: `{ id: docId, ...docData }`
110
+ - For collection responses: `[{ id: docId, ...docData }, ...]`
111
+ - **Redaction:** Delete sensitive fields entirely from the response object. Do NOT replace them with `'[REDACTED]'`.
112
+
113
+ ```javascript
114
+ // ✅ CORRECT — mirror the Firestore doc
115
+ const doc = await admin.firestore().doc(`items/${id}`).get();
116
+ return assistant.respond({ item: { id: doc.id, ...doc.data() } });
117
+
118
+ // ❌ WRONG — reshaping into a different structure
119
+ return assistant.respond({ item: { id, name: doc.data().name, createdAt: doc.data().metadata.created.timestamp } });
120
+
121
+ // ✅ CORRECT — redact by deleting
122
+ const data = doc.data();
123
+ delete data.api?.privateKey;
124
+ return assistant.respond({ item: { id: doc.id, ...data } });
125
+
126
+ // ❌ WRONG — redact by replacing
127
+ data.api.privateKey = '[REDACTED]';
128
+ ```
129
+
130
+ ## See also
131
+
132
+ - [routes.md](routes.md) — the route handlers doing these reads/writes
133
+ - [usage-rate-limiting.md](usage-rate-limiting.md) — the `usage` helper owns `{doc}.usage.*` fields (never write them manually)
134
+ - [cli-firestore-auth.md](cli-firestore-auth.md) — reading/writing Firestore from the terminal