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.
- package/CHANGELOG.md +33 -4
- package/CLAUDE.md +36 -11
- package/README.md +6 -5
- package/docs/audit.md +70 -0
- package/docs/build-system.md +29 -0
- package/docs/cli-logs.md +2 -2
- package/docs/common-mistakes.md +1 -1
- package/docs/consent.md +4 -2
- package/docs/email-system.md +28 -2
- package/docs/environment-detection.md +1 -1
- package/docs/firestore.md +134 -0
- package/docs/logging.md +27 -0
- package/docs/migration.md +107 -0
- package/docs/routes.md +110 -14
- package/docs/schemas.md +121 -25
- package/docs/test-boot-layer.md +67 -0
- package/docs/{testing.md → test-framework.md} +150 -19
- package/docs/usage-rate-limiting.md +15 -0
- package/package.json +12 -5
- package/src/cli/commands/base-command.js +5 -5
- package/src/cli/commands/logs.js +2 -2
- package/src/cli/commands/serve.js +3 -3
- package/src/cli/commands/test.js +140 -0
- package/src/cli/commands/watch.js +4 -4
- package/src/defaults/CLAUDE.md +11 -0
- package/src/defaults/test/README.md +15 -0
- package/src/defaults/test/_init.js +1 -1
- package/src/manager/libraries/email/data/blocked-local-patterns.js +0 -2
- package/src/manager/libraries/email/data/custom-disposable-domains.json +47 -1
- package/src/manager/libraries/email/data/disposable-domains.json +3 -0
- package/src/manager/libraries/email/data/typo-domains.js +83 -0
- package/src/manager/libraries/email/validation.js +74 -8
- package/src/manager/libraries/email/validation.test.js +125 -0
- package/src/test/fixtures/firebase-project/.firebaserc +5 -0
- package/src/test/fixtures/firebase-project/database.rules.json +6 -0
- package/src/test/fixtures/firebase-project/firebase.json +49 -0
- package/src/test/fixtures/firebase-project/firestore.indexes.json +4 -0
- package/src/test/fixtures/firebase-project/firestore.rules +10 -0
- package/src/test/fixtures/firebase-project/functions/backend-manager-config.json +24 -0
- package/src/test/fixtures/firebase-project/functions/index.js +10 -0
- package/src/test/fixtures/firebase-project/functions/package.json +15 -0
- package/src/test/fixtures/firebase-project/public/index.html +5 -0
- package/src/test/fixtures/firebase-project/storage.rules +8 -0
- package/src/test/runner.js +16 -4
- package/test/boot/emulator-boots.js +37 -0
- 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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
- **`
|
|
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
|
|
26
|
-
- `npx mgr test
|
|
27
|
-
- `npx mgr test
|
|
28
|
-
-
|
|
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
|
|
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 `
|
|
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
|
|
126
|
-
- [docs/schemas.md](docs/schemas.md) — schema
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
package/docs/common-mistakes.md
CHANGED
|
@@ -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 [
|
|
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
|
|
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/
|
|
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
|
|
package/docs/email-system.md
CHANGED
|
@@ -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 (
|
|
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 [
|
|
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
|
-
- [
|
|
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
|