backend-manager 5.9.4 → 5.9.6
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/CLAUDE.md +6 -0
- package/package.json +8 -1
- package/src/cli/commands/install.js +4 -3
- package/src/cli/commands/setup-tests/backend-manager.js +2 -1
- package/src/cli/commands/setup-tests/firebase-admin.js +2 -1
- package/src/cli/commands/setup-tests/firebase-functions.js +2 -1
- package/src/cli/utils/safe-install.js +13 -0
- package/src/manager/routes/payments/cancel/post.js +22 -2
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.mjml +97 -0
- package/{test/email/fixtures/clean.json → src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/structure.json} +16 -4
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/test-mode.json +6 -0
- package/src/test/fixtures/firebase-project/database-debug.log +12 -0
- package/src/test/fixtures/firebase-project/firestore-debug.log +136 -0
- package/src/test/fixtures/firebase-project/pubsub-debug.log +17 -0
- package/src/test/test-accounts.js +9 -0
- package/.codeclimate.yml +0 -32
- package/.nvmrc +0 -1
- package/CHANGELOG.md +0 -1432
- package/PROGRESS.md +0 -86
- package/TODO-2.md +0 -121
- package/TODO-CHARGEBLAST.md +0 -32
- package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +0 -15
- package/TODO-WEBHOOK-KEY-UPGRADE.md +0 -138
- package/TODO-email-auth.md +0 -14
- package/TODO.md +0 -187
- package/plans/mcp2.md +0 -247
- package/scripts/test-helper-providers.js +0 -162
- package/scripts/update-disposable-domains.js +0 -44
- package/templates/_.env +0 -42
- package/templates/_.gitignore +0 -60
- package/templates/backend-manager-config.json +0 -249
- package/templates/database.rules.json +0 -83
- package/templates/firebase.json +0 -66
- package/templates/firestore.indexes.json +0 -4
- package/templates/firestore.rules +0 -112
- package/templates/public/404.html +0 -26
- package/templates/public/index.html +0 -24
- package/templates/remoteconfig.template.json +0 -1
- package/templates/storage-lifecycle-config-1-day.json +0 -9
- package/templates/storage-lifecycle-config-30-days.json +0 -9
- package/templates/storage.rules +0 -8
- package/test/_init/accounts-validation.js +0 -58
- package/test/_legacy/ai/index.js +0 -231
- package/test/_legacy/ai/test.jpg +0 -0
- package/test/_legacy/ai/test.pdf +0 -0
- package/test/_legacy/index.js +0 -31
- package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +0 -98
- package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +0 -578
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +0 -38
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +0 -135
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +0 -42
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +0 -44
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +0 -39
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +0 -143
- package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +0 -48
- package/test/_legacy/payment-resolver/coinbase/orders/regular.json +0 -204
- package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +0 -204
- package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +0 -125
- package/test/_legacy/payment-resolver/index.js +0 -1558
- package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/paypal/orders/regular.json +0 -120
- package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +0 -323
- package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +0 -192
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +0 -125
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +0 -76
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +0 -114
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +0 -114
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +0 -111
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +0 -132
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +0 -107
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +0 -91
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +0 -147
- package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +0 -120
- package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
- package/test/_legacy/payment-resolver/stripe/orders/regular.json +0 -91
- package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +0 -421
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +0 -349
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +0 -430
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +0 -319
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +0 -319
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +0 -414
- package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +0 -319
- package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +0 -339
- package/test/_legacy/test-new +0 -1178
- package/test/_legacy/test.md +0 -89
- package/test/_legacy/usage.js +0 -175
- package/test/_legacy/user.js +0 -31
- package/test/ai/tools-live.js +0 -170
- package/test/boot/emulator-boots.js +0 -37
- package/test/content/blog-generate.js +0 -160
- package/test/email/campaign-send.js +0 -45
- package/test/email/consent-lifecycle.js +0 -257
- package/test/email/feedback-and-plain-send.js +0 -52
- package/test/email/fixtures/editorial.json +0 -30
- package/test/email/fixtures/field-report.json +0 -53
- package/test/email/marketing-lifecycle.js +0 -132
- package/test/email/newsletter-generate.js +0 -819
- package/test/email/newsletter-templates.js +0 -491
- package/test/email/templates.js +0 -207
- package/test/email/transactional-send.js +0 -34
- package/test/email/transactional.js +0 -560
- package/test/email/validation.js +0 -710
- package/test/events/auth-delete-race.js +0 -201
- package/test/events/payments/journey-payments-cancel-endpoint.js +0 -100
- package/test/events/payments/journey-payments-cancel.js +0 -182
- package/test/events/payments/journey-payments-discount.js +0 -89
- package/test/events/payments/journey-payments-failure.js +0 -146
- package/test/events/payments/journey-payments-legacy-product.js +0 -149
- package/test/events/payments/journey-payments-one-time-failure.js +0 -111
- package/test/events/payments/journey-payments-one-time.js +0 -136
- package/test/events/payments/journey-payments-plan-change.js +0 -144
- package/test/events/payments/journey-payments-refund-webhook.js +0 -180
- package/test/events/payments/journey-payments-suspend.js +0 -181
- package/test/events/payments/journey-payments-trial-cancel.js +0 -130
- package/test/events/payments/journey-payments-trial.js +0 -171
- package/test/events/payments/journey-payments-uid-resolution.js +0 -132
- package/test/events/payments/journey-payments-upgrade.js +0 -135
- package/test/fixtures/chargebee/invoice-one-time.json +0 -27
- package/test/fixtures/chargebee/subscription-active.json +0 -44
- package/test/fixtures/chargebee/subscription-cancelled.json +0 -42
- package/test/fixtures/chargebee/subscription-in-trial.json +0 -41
- package/test/fixtures/chargebee/subscription-legacy-plan.json +0 -41
- package/test/fixtures/chargebee/subscription-non-renewing.json +0 -41
- package/test/fixtures/chargebee/subscription-paused.json +0 -42
- package/test/fixtures/chargebee/webhook-payment-failed.json +0 -51
- package/test/fixtures/chargebee/webhook-subscription-created.json +0 -47
- package/test/fixtures/paypal/order-approved.json +0 -62
- package/test/fixtures/paypal/order-completed.json +0 -110
- package/test/fixtures/paypal/subscription-active.json +0 -76
- package/test/fixtures/paypal/subscription-cancelled.json +0 -50
- package/test/fixtures/paypal/subscription-suspended.json +0 -65
- package/test/fixtures/stripe/checkout-session-completed.json +0 -130
- package/test/fixtures/stripe/invoice-payment-failed.json +0 -148
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +0 -28
- package/test/fixtures/stripe/subscription-active.json +0 -161
- package/test/fixtures/stripe/subscription-canceled.json +0 -161
- package/test/fixtures/stripe/subscription-trialing.json +0 -161
- package/test/functions/admin/database-read.js +0 -134
- package/test/functions/admin/database-write.js +0 -159
- package/test/functions/admin/edit-post.js +0 -366
- package/test/functions/admin/firestore-query.js +0 -207
- package/test/functions/admin/firestore-read.js +0 -129
- package/test/functions/admin/firestore-write.js +0 -105
- package/test/functions/admin/get-stats.js +0 -115
- package/test/functions/admin/send-email.js +0 -119
- package/test/functions/admin/send-notification.js +0 -208
- package/test/functions/admin/write-repo-content.js +0 -223
- package/test/functions/general/add-marketing-contact.js +0 -335
- package/test/functions/general/fetch-post.js +0 -54
- package/test/functions/general/generate-uuid.js +0 -114
- package/test/functions/test/authenticate.js +0 -64
- package/test/functions/user/create-custom-token.js +0 -73
- package/test/functions/user/delete.js +0 -135
- package/test/functions/user/get-active-sessions.js +0 -111
- package/test/functions/user/get-subscription-info.js +0 -99
- package/test/functions/user/regenerate-api-keys.js +0 -156
- package/test/functions/user/resolve.js +0 -52
- package/test/functions/user/sign-out-all-sessions.js +0 -94
- package/test/functions/user/sign-up.js +0 -252
- package/test/functions/user/submit-feedback.js +0 -104
- package/test/functions/user/validate-settings.js +0 -82
- package/test/helpers/ai-request-payload.js +0 -300
- package/test/helpers/ai-test-provider.js +0 -202
- package/test/helpers/ai-tools-format.js +0 -383
- package/test/helpers/content/blog-auto-publisher.js +0 -484
- package/test/helpers/content/feed-parser.js +0 -528
- package/test/helpers/content/ghostii-blocks.js +0 -134
- package/test/helpers/content/ghostii-feed-integration.js +0 -404
- package/test/helpers/content/ghostii-write-article.js +0 -243
- package/test/helpers/environment.js +0 -230
- package/test/helpers/infer-contact.js +0 -113
- package/test/helpers/merge-line-files.js +0 -271
- package/test/helpers/payment/chargebee/parse-webhook.js +0 -413
- package/test/helpers/payment/chargebee/to-unified-one-time.js +0 -147
- package/test/helpers/payment/chargebee/to-unified-subscription.js +0 -648
- package/test/helpers/payment/paypal/parse-webhook.js +0 -539
- package/test/helpers/payment/paypal/to-unified-one-time.js +0 -382
- package/test/helpers/payment/paypal/to-unified-subscription.js +0 -820
- package/test/helpers/payment/stripe/parse-webhook.js +0 -447
- package/test/helpers/payment/stripe/to-unified-one-time.js +0 -306
- package/test/helpers/payment/stripe/to-unified-subscription.js +0 -708
- package/test/helpers/sanitize.js +0 -222
- package/test/helpers/slugify.js +0 -394
- package/test/helpers/storage.js +0 -194
- package/test/helpers/user.js +0 -755
- package/test/helpers/webhook-forward.js +0 -418
- package/test/mcp/discovery.js +0 -53
- package/test/mcp/oauth.js +0 -161
- package/test/mcp/protocol.js +0 -268
- package/test/mcp/roles.js +0 -188
- package/test/mcp/utils.js +0 -245
- package/test/routes/admin/create-post.js +0 -364
- package/test/routes/admin/database.js +0 -131
- package/test/routes/admin/deduplicate-image-alts.js +0 -190
- package/test/routes/admin/email.js +0 -114
- package/test/routes/admin/firestore-query.js +0 -204
- package/test/routes/admin/firestore.js +0 -127
- package/test/routes/admin/infer-contact.js +0 -218
- package/test/routes/admin/notification.js +0 -198
- package/test/routes/admin/post-resize-image.js +0 -184
- package/test/routes/admin/post.js +0 -368
- package/test/routes/admin/repo-content.js +0 -223
- package/test/routes/admin/stats.js +0 -112
- package/test/routes/content/post.js +0 -54
- package/test/routes/general/uuid.js +0 -115
- package/test/routes/marketing/campaign.js +0 -125
- package/test/routes/marketing/contact.js +0 -370
- package/test/routes/marketing/email-preferences.js +0 -292
- package/test/routes/marketing/push-send.js +0 -32
- package/test/routes/marketing/webhook-forward.js +0 -61
- package/test/routes/marketing/webhook.js +0 -639
- package/test/routes/payments/cancel.js +0 -140
- package/test/routes/payments/discount.js +0 -80
- package/test/routes/payments/dispute-alert.js +0 -320
- package/test/routes/payments/intent.js +0 -336
- package/test/routes/payments/portal.js +0 -92
- package/test/routes/payments/refund.js +0 -179
- package/test/routes/payments/trial-eligibility.js +0 -71
- package/test/routes/payments/webhook.js +0 -107
- package/test/routes/test/authenticate.js +0 -59
- package/test/routes/test/schema.js +0 -554
- package/test/routes/test/usage.js +0 -342
- package/test/routes/user/api-keys.js +0 -156
- package/test/routes/user/delete.js +0 -136
- package/test/routes/user/feedback.js +0 -104
- package/test/routes/user/sessions.js +0 -199
- package/test/routes/user/settings-validate.js +0 -82
- package/test/routes/user/signup.js +0 -542
- package/test/routes/user/subscription.js +0 -99
- package/test/routes/user/token.js +0 -73
- package/test/routes/user/user.js +0 -157
- package/test/rules/notifications.js +0 -410
- package/test/rules/payments-carts.js +0 -371
- package/test/rules/user.js +0 -396
package/CHANGELOG.md
DELETED
|
@@ -1,1432 +0,0 @@
|
|
|
1
|
-
# CHANGELOG
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
|
-
|
|
7
|
-
## Changelog Categories
|
|
8
|
-
|
|
9
|
-
- `BREAKING` for breaking changes.
|
|
10
|
-
- `Added` for new features.
|
|
11
|
-
- `Changed` for changes in existing functionality.
|
|
12
|
-
- `Deprecated` for soon-to-be removed features.
|
|
13
|
-
- `Removed` for now removed features.
|
|
14
|
-
- `Fixed` for any bug fixes.
|
|
15
|
-
- `Security` in case of vulnerabilities.
|
|
16
|
-
|
|
17
|
-
# [5.9.4] - 2026-06-21
|
|
18
|
-
|
|
19
|
-
### Fixed
|
|
20
|
-
- **Download-app-link email had blank body.** Template passed `data: {}` so the card rendered logo + signoff + footer but no content. Now populates title, message, and "Download Now" button linking to `{brand.url}/download`.
|
|
21
|
-
|
|
22
|
-
# [5.9.3] - 2026-06-21
|
|
23
|
-
|
|
24
|
-
### Changed
|
|
25
|
-
- **`admin/hook` route now triggers BEM internal crons.** Path resolution searches BEM's events directory (crons), consumer project root, and consumer hooks/ directory. Supports both function exports (`module.exports = async (opts) => {}`) and class-based hooks. Example: `POST /admin/hook { path: "cron/daily/blog-auto-publisher" }`.
|
|
26
|
-
- **MCP `run_hook` tool updated** with expanded description and example paths for all built-in cron jobs.
|
|
27
|
-
|
|
28
|
-
### Available hook paths (built-in BEM crons)
|
|
29
|
-
- `cron/daily/blog-auto-publisher` — generate + publish blog articles
|
|
30
|
-
- `cron/daily/marketing-newsletter-generate` — pre-generate newsletter content
|
|
31
|
-
- `cron/daily/marketing-prune` — prune inactive marketing contacts
|
|
32
|
-
- `cron/daily/reset-usage` — reset monthly usage counters
|
|
33
|
-
- `cron/daily/data-requests` — process GDPR data export requests
|
|
34
|
-
- `cron/daily/expire-paypal-cancellations` — expire pending PayPal cancellations
|
|
35
|
-
- `cron/frequent/marketing-campaigns` — send pending marketing campaigns
|
|
36
|
-
|
|
37
|
-
# [5.9.2] - 2026-06-20
|
|
38
|
-
|
|
39
|
-
### Added
|
|
40
|
-
- Blog generation extended test (`test/content/blog-generate.js`). Run `npx mgr test mgr:content/blog-generate --extended` to exercise the full blog auto-publisher pipeline (source resolution → Ghostii → publish). Supports `BLOG_SOURCE`, `BLOG_NO_PUBLISH`, `BLOG_OPEN` env vars.
|
|
41
|
-
|
|
42
|
-
# [5.9.1] - 2026-06-20
|
|
43
|
-
|
|
44
|
-
### Fixed
|
|
45
|
-
- Missing `require('node-powertools')` in newsletter generator — broke `powertools.arrayify()` for array content support added in v5.9.0
|
|
46
|
-
|
|
47
|
-
# [5.9.0] - 2026-06-19
|
|
48
|
-
|
|
49
|
-
### BREAKING
|
|
50
|
-
- **Config key `ghostii` renamed to `blog`.** Consumer projects must migrate their `backend-manager-config.json`: `ghostii[]` → `blog.content[]` with `blog.enabled` and `blog.platform: 'ghostii'`.
|
|
51
|
-
- **Field renames in blog content entries.** `articles` → `quantity`, `prompt` → `instructions`, `$app` → `$brand`.
|
|
52
|
-
- **Newsletter `claimSources()` removed.** Parent server now serves sources read-only (no claiming). Children track usage locally in `content-sources`.
|
|
53
|
-
|
|
54
|
-
### Added
|
|
55
|
-
- **Provider-based blog architecture.** `blog.platform` selects the article-generation provider (dispatched via `require(../content/${platform}.js)`). Only `ghostii` for now.
|
|
56
|
-
- **`$parent` source type.** Blog entries can fetch sources from the parent server's pool, filtered by categories, with local dedup tracking.
|
|
57
|
-
- **`$brand` source type.** Renamed from `$app` — generic brand-topic generation.
|
|
58
|
-
- **New content entry fields.** `tone`, `categories`, `keywords` added to both blog and newsletter content entries. Injected into AI prompts for better content targeting.
|
|
59
|
-
- **`links` and `keywords` support in newsletter.** Newsletter AI prompt now weaves in brand links and SEO keywords from content config.
|
|
60
|
-
- **`sources` field on newsletter content.** Newsletter entries now explicitly declare source types (default: `['$parent']`), matching blog config shape.
|
|
61
|
-
- **Newsletter `content` supports array format.** Matches blog config shape for structural parity.
|
|
62
|
-
- **Unified `content-sources` Firestore collection.** Both blog and newsletter track used sources in the same collection with `usedBy` field (`'blog'` or `'newsletter'`).
|
|
63
|
-
|
|
64
|
-
### Changed
|
|
65
|
-
- **Cron renamed.** `ghostii-auto-publisher.js` → `blog-auto-publisher.js`.
|
|
66
|
-
- **`postPath` defaults to platform name.** Instead of hardcoded `'blog'`, defaults to the platform value (e.g. `'ghostii'`).
|
|
67
|
-
- **Consistent field ordering.** `sources → categories → links → instructions → tone → keywords` across blog and newsletter config templates.
|
|
68
|
-
|
|
69
|
-
### Removed
|
|
70
|
-
- **`ghostii-auto-publisher.js`** — replaced by `blog-auto-publisher.js`.
|
|
71
|
-
- **`claimSources()` from newsletter generator** — parent no longer tracks claim state.
|
|
72
|
-
|
|
73
|
-
# [5.8.3] - 2026-06-19
|
|
74
|
-
|
|
75
|
-
### Fixed
|
|
76
|
-
- **Ghostii feed tracking timestamps.** `trackFeedItem` now uses BEM's standard `metadata.{created,updated}` with `timestamp` (ISO) and `timestampUNIX` (seconds) instead of `FieldValue.serverTimestamp()`, which failed in the emulator context.
|
|
77
|
-
|
|
78
|
-
### Changed
|
|
79
|
-
- **Firestore collection rename.** `ghostii-feed-items` → `ghostii-sources` for clarity and consistency with the OMEGA naming convention.
|
|
80
|
-
- **Extended feed test coverage.** Added 5 marketing/social feeds (Social Media Examiner, Hootsuite, Sprout Social, Buffer, Digiday) to the real-feed integration test. Threshold bumped from 2/3 to 5/7.
|
|
81
|
-
|
|
82
|
-
# [5.8.2] - 2026-06-19
|
|
83
|
-
|
|
84
|
-
### Fixed
|
|
85
|
-
- **Setup crash on fresh projects.** `npx mgr setup` crashed with `Cannot read properties of undefined (reading 'default')` when `.firebaserc`, `firebase.json`, or `backend-manager-config.json` didn't exist. Setup now scaffolds all three from templates before reading from them.
|
|
86
|
-
- **`engines.node` hard crash.** Missing `engines.node` in `package.json` threw an unrecoverable error. Now auto-fixes it using the current Node.js major version.
|
|
87
|
-
- **`dependencies['backend-manager']` crash.** The local-BEM detection crashed when `backend-manager` was listed in `devDependencies` instead of `dependencies`.
|
|
88
|
-
|
|
89
|
-
### Changed
|
|
90
|
-
- **Unified scaffold pass.** Consolidated config scaffolding, `engines.node` fix, and doc defaults into one `[DEFAULTS]` section with a single file reload afterwards. Eliminates scattered scaffolding steps and conditional re-loads.
|
|
91
|
-
|
|
92
|
-
### Added
|
|
93
|
-
- **`templates/firebase.json`** — standard Firebase config template (hosting, functions, firestore, database, storage, emulators) scaffolded into new projects by `npx mgr setup`.
|
|
94
|
-
|
|
95
|
-
# [5.8.1] - 2026-06-19
|
|
96
|
-
|
|
97
|
-
### Fixed
|
|
98
|
-
- **Ghostii 502 timeout fix.** Switched `writeArticle()` from the Firebase Hosting URL (`api.ghostii.ai`) to the raw Cloud Functions URL. Firebase Hosting rewrites have a hard 60-second proxy timeout that caused 502 errors when the AI article pipeline exceeded that limit. The raw URL uses the function's own 5-minute timeout.
|
|
99
|
-
|
|
100
|
-
### Added
|
|
101
|
-
- **Ghostii call duration logging.** `writeArticle()` now logs the round-trip time of each Ghostii API call (`[ghostii] writeArticle() completed in Xms`).
|
|
102
|
-
|
|
103
|
-
# [5.8.0] - 2026-06-19
|
|
104
|
-
|
|
105
|
-
### Added
|
|
106
|
-
- **RSS/Atom feed sources for Ghostii.** New `$feed:<url>` source type in `config.ghostii[]` entries. Parses RSS 2.0, Atom 1.0, and JSON Feed formats, selects unprocessed articles, extracts content, and passes it to the Ghostii API as `sourceContent` for AI-assisted original article generation. Feed items tracked in Firestore (`ghostii-feed-items`) to prevent re-processing. Falls back to `$app` when feeds are unreachable or exhausted.
|
|
107
|
-
- **Per-entry Ghostii API overrides.** `config.ghostii[].overrides` object lets each entry customize keywords, length, research, insertImages, headerImageUrl, maxLinks, sectionQuantity, and feedUrl — previously hardcoded in `writeArticle()`.
|
|
108
|
-
- **Configurable postPath.** `config.ghostii[].postPath` controls the `_posts/{year}/` sub-folder for published articles (default: `'ghostii'`).
|
|
109
|
-
- **Feed parser library.** New `src/manager/libraries/content/feed-parser.js` — `parseFeed()` and `extractArticleContent()` via Cheerio for parsing syndication feeds and extracting readable article text from URLs.
|
|
110
|
-
- **New dependencies:** `fast-xml-parser` for RSS/Atom parsing, `cheerio` for article content extraction.
|
|
111
|
-
|
|
112
|
-
### Changed
|
|
113
|
-
- **`writeArticle()` accepts overrides and sourceContent.** Signature changed from `({ brand, description, links })` to `({ brand, description, links, sourceContent, overrides })`. Existing callers (newsletter generator) pass no new args and get identical behavior.
|
|
114
|
-
- **Newsletter fact paraphrasing.** Newsletter writer prompt now instructs the AI to paraphrase all facts, figures, and statistics from sources — never copy exact numbers or phrasing.
|
|
115
|
-
|
|
116
|
-
# [5.7.6] - 2026-06-18
|
|
117
|
-
|
|
118
|
-
### Fixed
|
|
119
|
-
- **MCP admin role promotion.** Users with `roles.admin=true` on their Firestore doc now see all 25 admin tools when connecting via OAuth. Previously only the `BACKEND_MANAGER_KEY` granted admin MCP access; user-role admins were limited to 3 tools.
|
|
120
|
-
|
|
121
|
-
# [5.7.4] - 2026-06-18
|
|
122
|
-
|
|
123
|
-
### Added
|
|
124
|
-
- **Gitignore root proxy artifacts.** Default `.gitignore` template now excludes `/package.json` and `/package-lock.json` at the repo root, so the setup-generated root proxy and any lockfile are not tracked.
|
|
125
|
-
|
|
126
|
-
# [5.7.3] - 2026-06-18
|
|
127
|
-
|
|
128
|
-
### Changed
|
|
129
|
-
- **Root proxy preinstall forwards to functions/.** `npm install` at the project root now runs `cd functions && npm install` (forwarding deps to where they live) instead of just rejecting. Still exits 1 afterward to prevent npm from creating root-level artifacts.
|
|
130
|
-
|
|
131
|
-
# [5.7.2] - 2026-06-18
|
|
132
|
-
|
|
133
|
-
### Fixed
|
|
134
|
-
- **Newsletter generation crash.** v5.5.0 refactored `marketing.beehiiv` → `marketing.newsletter` but missed renaming `beehiivConfig` at 3 sites in `newsletter.js` (lines 331, 336, 340). The `ReferenceError` crashed generation after all AI work completed, preventing the campaign doc from being written. Newsletter generation has been silently failing since June 6.
|
|
135
|
-
- **HTTPS proxy silent failure.** `serve.js` `_startHttpsProxy` now returns a boolean. The caller uses `httpsReady` (not `httpsEnabled`) for port and env decisions, so when cert generation fails, the server correctly falls back to plain HTTP instead of setting `BEM_HTTPS_PORT` with no proxy listening.
|
|
136
|
-
- **AI system prompt injection for array content blocks.** `normalizeOptions` now handles system messages with array content (content blocks) — prepends rules as a `{ type: 'text' }` block. Previously, the if/else-if chain fell through and rules were silently dropped.
|
|
137
|
-
- **Setup retry loop treats warns as failures.** Added `warnCount` tracking; the `allPassed` check now includes warns (`testCount + warnCount === testTotal`). Warns no longer trigger unnecessary retries with `--retry N`.
|
|
138
|
-
- **Copy-paste: `sender: 'electron-manager'` in setup IPC.** Changed to `'backend-manager'`.
|
|
139
|
-
- **Test account creation race condition.** `deleteTestUsers` now uses the emulator's bulk-clear REST API instead of individual `deleteUser()` calls. Individual deletes triggered async on-delete handlers that could clobber freshly-created accounts (80-100% repro rate). Bulk clear eliminates the race entirely.
|
|
140
|
-
- **Consent rules test value collision.** Changed test value from `'granted'` to `'forged'` so the write always differs from prior test state.
|
|
141
|
-
- **`cancel-too-young` timestampUNIX convention.** `Date.now()` (milliseconds) → `Math.floor(Date.now() / 1000)` (seconds).
|
|
142
|
-
|
|
143
|
-
### Added
|
|
144
|
-
- **AI array-content test.** `normalize-options-structured-system-content-as-array-injects-rules` — covers the missing branch.
|
|
145
|
-
- **Auth on-delete race condition test.** `test/events/auth-delete-race.js` — proves the emulator race (clobber without mitigation) and verifies both mitigation strategies (wait-for-gone, force-delete).
|
|
146
|
-
- **Root package.json setup check.** Validates the project root `package.json` during `npx mgr setup`.
|
|
147
|
-
|
|
148
|
-
# [5.7.1] - 2026-06-17
|
|
149
|
-
|
|
150
|
-
### Added
|
|
151
|
-
- **MCP shorthand URL.** MCP endpoint now accessible at `/mcp` in addition to `/backend-manager/mcp`. Hosting rewrites updated; consumer projects pick up on next `npx mgr setup`.
|
|
152
|
-
- **`/register` hosting rewrite.** Dynamic client registration endpoint now included in the default hosting rewrite pattern.
|
|
153
|
-
|
|
154
|
-
# [5.7.0] - 2026-06-17
|
|
155
|
-
|
|
156
|
-
### Added
|
|
157
|
-
- **MCP role-based tool scoping.** 25 tools (was 19) with admin/user/public roles. Admin sees all, user sees `get_user` + `get_subscription` + `health_check`, unauthenticated gets 401 triggering OAuth. Defense-in-depth: route-level auth still validates.
|
|
158
|
-
- **MCP OAuth user authentication.** OAuth 2.1 with PKCE + dynamic client registration (RFC 7591). User sign-in via consumer website's `/token` page → Firebase ID token → exchanged for `api.privateKey`. Verified end-to-end in Claude Desktop.
|
|
159
|
-
- **MCP consumer tools.** Consumer projects define custom MCP tools in `functions/mcp.js` — route delegation (works on stdio + HTTP) or handler mode (HTTP only). Consumer tools override same-name built-ins.
|
|
160
|
-
- **MCP tool annotations.** `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` on all tools. Claude Desktop shows read/write categorization and human-readable titles.
|
|
161
|
-
- **6 new MCP tools:** `update_post`, `update_campaign`, `delete_campaign`, `create_contact`, `delete_contact`, `get_payment_portal`.
|
|
162
|
-
- **HTTPS local dev.** `npx mgr serve` starts an HTTPS proxy on port 5002 (firebase serve on 5443 internally) with auto-generated mkcert certificates. Claude Desktop requires HTTPS for MCP connectors. Disable with `--no-https`.
|
|
163
|
-
- **MCP CLI `--token` flag.** `npx mgr mcp --token <api-key>` for user-level stdio connections.
|
|
164
|
-
|
|
165
|
-
### Changed
|
|
166
|
-
- **MCP discovery endpoints** use root-level issuer per RFC 8414 (was path-scoped, broke Claude Desktop's discovery chain).
|
|
167
|
-
- **`getApiUrl()`** returns `https://localhost:<port>` when `BEM_HTTPS_PORT` is set (serve command sets this automatically).
|
|
168
|
-
- **`cancel_subscription`, `refund_payment`, `generate_uuid`** moved from user/public to admin role (destructive operations and dev utilities shouldn't be in user-facing MCP).
|
|
169
|
-
- **`resolveConsumerAuthUrl()`** uses `Manager.getWebsiteUrl()` (auto-resolves localhost in dev) instead of `brand.url` (always production).
|
|
170
|
-
|
|
171
|
-
# [5.6.6] - 2026-06-15
|
|
172
|
-
|
|
173
|
-
### Added
|
|
174
|
-
- **`'warn'` return type for setup checks.** `run()` can now return the string `'warn'` for non-blocking failures — the check prints as `⚠` with detail lines from `getWarning()`, is counted in the summary (`36 passed, 1 warned, 0 failed`), but does **not** halt setup. `BaseTest` provides a default `getWarning()` returning `[]`. `Summary` gains a `warn(name, details)` method alongside `pass()` and `fail()`.
|
|
175
|
-
- **Java setup check** (`setup-tests/java-installed.js`). Checks whether Java is installed (required by the Firebase Firestore emulator for testing). Uses the `'warn'` return type — setup continues without Java, but the summary reports it.
|
|
176
|
-
- **Java pre-check in test runner.** `npx mgr test` now checks for Java before starting the emulator and fails fast with a clear message (`Java is required to run tests`) instead of the raw emulator crash.
|
|
177
|
-
|
|
178
|
-
### Changed
|
|
179
|
-
- **Firebase CLI and auth setup checks no longer halt setup.** Both checks now use the `'warn'` return type instead of throwing from `fix()`. Missing Firebase CLI or unauthenticated state is reported in the summary but does not block the remaining checks.
|
|
180
|
-
|
|
181
|
-
# [5.6.5] - 2026-06-14
|
|
182
|
-
|
|
183
|
-
### Added
|
|
184
|
-
- **Cross-provider native tool calling in the AI library (agentic loops).** `ai.request()` now supports a unified tools interface on every text provider: `tools.list` accepts normalized function tools (`{ name, description, parameters }` — JSON Schema), `tools.choice` accepts `'auto' | 'required' | 'none' | { name }`, and the response adds `toolCalls: [{ id, name, arguments }]` (arguments parsed) plus `stopReason: 'tool_use' | 'end' | 'max_tokens'`. The Anthropic and claude-code providers gain native `tool_use` via a shared pure formatter (`providers/anthropic-format.js` — tool defs → `input_schema`, choice mapping incl. `required`→`any`, response extraction); the OpenAI provider normalizes function tools to the Responses API envelope (hosted tools like `{ type: 'web_search' }` still pass verbatim, and throw a clear error on Anthropic) and extracts `function_call` items. Multi-turn loop continuation is first-class through `options.messages`: `{ role: 'assistant', toolCalls }` and `{ role: 'tool', toolCallId, content }` turns map to each provider's wire format (consecutive tool results merge into one Anthropic user turn; OpenAI gets `function_call`/`function_call_output` items), raw Anthropic block arrays replay verbatim, and `normalizeOptions()` no longer string-flattens structured conversations (system-prompt injections still apply). OpenAI additionally gains a direct-messages mode: passing `messages[]` now sends ALL turns (previously middle turns were silently dropped in favor of prompt/message/history). JSON parsing (`response: 'json'`) is skipped on tool-call turns, where empty text is the normal intermediate state. Return shapes stay backward-compatible (`{ content, output, tokens, raw }` unchanged; OpenAI now also returns `raw`). Covered by `test/helpers/ai-tools-format.js` (pure, 22 cases) and `test/ai/tools-live.js` (extended-mode real 2-step tool loops on both providers; OpenAI live-validated).
|
|
185
|
-
- **Deterministic `test` AI provider** (`providers/test.js`, registered as `provider: 'test'`) — the AI analog of the `test` payment processor: a first-class provider that consumer suites drive with directives embedded in the last user message (`[[tool:name {json}]]`, `[[tools:[...]]]` for parallel calls, `[[reply:{json}]]`, `[[delay:ms]]`, `[[error:msg]]`), consumed sequentially across the turns of a tool loop. Refuses to run outside development/testing (Manager environment detection; falls back to `BEM_TESTING`/`FUNCTIONS_EMULATOR` signals when constructed without a Manager). Lets consumer chat/agent routes test the full loop — Firestore writes, usage, locks, tool executors — against the real emulator with zero paid API calls. Covered by `test/helpers/ai-test-provider.js`.
|
|
186
|
-
- **OpenAI provider constructor hardened** — `assistant.Manager` access now uses optional chaining (matching the Anthropic provider), so the provider can be constructed with a minimal assistant context (direct construction in tests/tools).
|
|
187
|
-
- **`docs/cdp-debugging.md` — launching a controllable browser (mirrored across UJM/BEM/BXM/EM).** The canonical Chrome launch for agents and humans: CDP port + REQUIRED dedicated `--user-data-dir` (Chrome 136+ silently ignores the debug port on the default profile — verified on 149), the persistent agent profile (`~/Library/Application Support/chrome-profiles/agent` — log in once, state survives relaunches, verified), the shared-instance model (CDP is multi-client — agents share the one logged-in Chrome on one port, one tab each; a second profile/port only for a second identity), safe quit by profile match, and driving via the `chrome-devtools` MCP (`CHROME_CDP_PORT` set before the session) or any CDP client. BEM flavor: aimed at verifying the frontend against your routes — watch network payloads and drive auth'd flows through the real UI. Indexed in CLAUDE.md.
|
|
188
|
-
|
|
189
|
-
# [5.6.4] - 2026-06-11
|
|
190
|
-
|
|
191
|
-
### Changed
|
|
192
|
-
- **Consent side effects moved off shared test accounts (journey-account isolation).** The marketing webhook suite's revoke-event tests (`test/routes/marketing/webhook.js`) repeatedly write `consent.marketing.status = 'revoked'` to their target account — persistent side-effect data that previously landed on the shared `basic` account, leaving it revoked for the remainder of every run (and, since the v5.6.3 library consent gate, changing `sync()`/`add()` behavior for every later suite touching it). They now target a dedicated `journey-webhook-revoke` account. The extended-mode lifecycle suite (`test/email/marketing-lifecycle.js`) likewise now syncs a dedicated `journey-marketing-sync` account (`_test.allow_*` prefix) instead of the shared `consent-granted` sentinel (which the signup + consent-lifecycle suites rely on). Its cleanup step also now deletes the contact the suite actually created — previously it deleted the ADMIN account's contact, which (post-v5.6.3) revoked admin's doc consent mid-run AND left the synced contact behind in SendGrid/Beehiiv after every extended run. `docs/test-framework.md`'s journey-account rule now lists `consent.marketing` writes as a trigger. Validated: marketing route suites pass (46 passing / 10 env-gated skips / 0 failures).
|
|
193
|
-
|
|
194
|
-
### Fixed
|
|
195
|
-
- **Anonymous HMAC unsubscribe tests now actually run.** The self-test boot (`src/cli/commands/test.js`) injects a test-only `UNSUBSCRIBE_HMAC_KEY` into the process env (the emulated functions inherit it — same mechanism as the fixture webhook key), closing the fixture gap that left all 8 anon-HMAC tests in `test/routes/marketing/email-preferences.js` failing as "known env gap". Those tests are the route-level coverage for the v5.6.3 HMAC changes (signature validation, rate limiting, consent mirroring); marketing suites went from 38 passing + 8 failing to 46 passing + 0 failing.
|
|
196
|
-
|
|
197
|
-
# [5.6.3] - 2026-06-11
|
|
198
|
-
|
|
199
|
-
### Fixed
|
|
200
|
-
- **NeverBounce single-check result parsing — every signup mailbox check has failed since v5.5.1.** `validation-provider-neverbounce.js` compared `data.result` against numeric codes (`data.result === 0 || 3 || 4`), but the NeverBounce v4 single/check API returns string textcodes (`"valid"`, `"invalid"`, ...) — so every completed check returned `{ valid: false, status: 'unknown' }`, including deliverable mailboxes, and the signup route silently skipped marketing sync for ALL signups. Production-confirmed via GCF logs: somiibo June 7 = 30 synced / 0 failed; June 9–11 = 0 synced / 93 failed; first failure 25 minutes after the v5.5.1 deploy (which switched signup to `ALL_CHECKS` and exposed the latent v5.5.0 bug). New `parseResult()` handles textcodes (canonical), tolerates numeric codes, and normalizes `catch-all` → `catchall`; allowed results stay valid/catchall/unknown. 11 regression parse cases added to `validation.test.js` (suite now 69 cases); fix live-validated against the real API. ZeroBounce provider audited — already string-based, no change.
|
|
201
|
-
- **Library-level marketing consent gate.** `Marketing.sync()`/`Marketing.add()` (`src/manager/libraries/email/marketing/index.js`) now skip users whose `consent.marketing.status` is the literal string `'revoked'` and return `{ blocked: 'consent', email }` (mirroring the `{ blocked: 'validation', ... }` shape) — BEFORE validation and provider calls. Previously the gate existed only at SOME call sites, so the payments on-write sync (`events/firestore/payments-webhooks/on-write.js`) and the admin PUT re-sync re-added users who had unsubscribed. `sync()` gates after the user doc is resolved (doc or uid); `add()` looks up the user by email first (same `auth.email` query as the webhook processors) — no user doc or lookup failure proceeds (fail open). ONLY `'revoked'` blocks: missing/null consent proceeds, so legacy users without a `consent` field keep syncing. `remove()` stays ungated. Covers all callers including the legacy API-command twins (`add-marketing-contact.js`). Gate logic unit-tested in `src/manager/libraries/email/marketing/consent-gate.test.js` (28 plain-node cases, no emulator).
|
|
202
|
-
- **Anonymous HMAC unsubscribe now removes the contact from Beehiiv too** (`routes/marketing/email-preferences/post.js`). The email-footer one-click unsubscribe only suppressed the SendGrid ASM group, leaving the contact live on Beehiiv — a compliance bug. The route now also calls `mailer.remove(email)` (best-effort, after the ASM call succeeds).
|
|
203
|
-
- **Anonymous HMAC re-subscribe now actually re-adds the contact.** Previously it only lifted the ASM suppression and wrote consent `granted` — the contact was never re-added to providers. `mirrorAnonymousToUserDoc` now returns the matched uid (or null); the route calls `mailer.sync(uid)` for matched users (the mirror writes `granted` first, so the library consent gate passes — no bypass flags) or `mailer.add({ email, source: 'resubscribe' })` for pure newsletter contacts. Best-effort, same testing guard as the ASM call.
|
|
204
|
-
- **Admin `DELETE /marketing/contact` now sticks** (`routes/marketing/contact/delete.js`). The route removed the contact from providers but left `consent.marketing.status = 'granted'`, so any later sync re-added the contact. After the provider removal it now mirrors `consent.marketing.status = 'revoked'` (`revokedAt` with `source: 'admin'`, same write shape as the webhook processors) to the matching user doc — best-effort, silent when the email maps to no user.
|
|
205
|
-
- **Stale docs/comments corrected in the same change set:** `docs/consent.md` now describes the shipped library gate, cross-provider HMAC unsubscribe, re-subscribe re-add, and admin-DELETE revoke mirror (new capture point 5); `validation.js` header no longer claims signup uses "disposable check only" (it runs `ALL_CHECKS` before marketing sync); `marketing/index.js` header no longer attributes the signup sync to the "Auth on-create handler" (it's the `/user/signup` route).
|
|
206
|
-
|
|
207
|
-
# [5.6.2] - 2026-06-11
|
|
208
|
-
|
|
209
|
-
### Fixed
|
|
210
|
-
- **4 stale framework tests aligned with shipped v5.5.4–v5.5.6 validation behavior.** The default `npx mgr test` run failed 4 tests whose expectations predated intentional source changes: `test/email/validation.js` still expected all-numeric (`123456@`) and short letter+number (`a123@`) local parts to be blocked (both patterns were removed in v5.5.6 after NeverBounce confirmed real users — QQ emails, real Gmail accounts — were being blocked; tests renamed `localpart-all-numeric-allowed` / `localpart-letter-plus-numbers-allowed`) and expected the pre-v5.5.5 `DEFAULT_CHECKS`/`ALL_CHECKS` lists (now include `typo`, and `dns` in `ALL_CHECKS`); `test/routes/marketing/webhook.js`'s bounce test sent no `bounce_classification`, which v5.5.4 deliberately skips (renamed `sendgrid-hard-bounce-event-handled`, now sends `'Invalid Address'`).
|
|
211
|
-
|
|
212
|
-
### Added
|
|
213
|
-
- **Suite coverage for the v5.5.5 `typo` + `dns` email validation checks** (previously only covered by the standalone `validation.test.js` script): typo-domain blocking (`gamil.com`, `gmail.con`) + correct-domain pass-through, dns-not-in-default-checks, an offline-safe dns positive (network errors skip, never block), and an extended-gated (`TEST_EXTENDED_MODE`) dns negative for nonexistent domains.
|
|
214
|
-
- **Suite coverage for the v5.5.4 bounce-classification filter**: `dropped` + `'Invalid Address'` revokes, technical bounce (`'Technical Failure'`) skipped, and unclassified bounce skipped — locking in that sender-side bounces never revoke recipient consent.
|
|
215
|
-
|
|
216
|
-
# [5.6.1] - 2026-06-11
|
|
217
|
-
|
|
218
|
-
### Added
|
|
219
|
-
- **`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.
|
|
220
|
-
|
|
221
|
-
### Changed
|
|
222
|
-
- **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.
|
|
223
|
-
|
|
224
|
-
# [5.6.0] - 2026-06-11
|
|
225
|
-
|
|
226
|
-
### Added
|
|
227
|
-
- **`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.
|
|
228
|
-
- **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).
|
|
229
|
-
- **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).
|
|
230
|
-
- **`--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).
|
|
231
|
-
- **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.
|
|
232
|
-
- **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.
|
|
233
|
-
- **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.
|
|
234
|
-
- **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`).
|
|
235
|
-
|
|
236
|
-
### Changed
|
|
237
|
-
- **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.
|
|
238
|
-
- **`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).
|
|
239
|
-
- **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/`).
|
|
240
|
-
|
|
241
|
-
### Fixed
|
|
242
|
-
- **`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()`.
|
|
243
|
-
- **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.
|
|
244
|
-
|
|
245
|
-
# [5.5.4] - 2026-06-09
|
|
246
|
-
|
|
247
|
-
### Fixed
|
|
248
|
-
- **SendGrid bounce/dropped webhook filtering.** Bounce and dropped events now only revoke marketing consent when `bounce_classification` is `'Invalid Address'`. Technical bounces (DMARC, TLS, DNS failures) are sender-side issues and no longer revoke the recipient's consent.
|
|
249
|
-
|
|
250
|
-
### Changed
|
|
251
|
-
- **`isSupported()` receives full parsed event.** All webhook processors (`sendgrid`, `beehiiv`) now receive the full parsed event object instead of just the event type string, enabling classification-aware filtering.
|
|
252
|
-
- **Config template `parent` field.** Added `parent` field to `backend-manager-config.json` template for newsletter-sources provider configuration.
|
|
253
|
-
|
|
254
|
-
# [5.5.3] - 2026-06-08
|
|
255
|
-
|
|
256
|
-
### Fixed
|
|
257
|
-
- **Per-provider error isolation in Marketing.sync/add/remove.** Added `.catch()` to each provider promise so one provider failing (e.g. SendGrid timeout during `buildFields`) no longer kills the other. Each provider now resolves independently with `{ success: false, error }` on failure, and the function always returns a per-provider status object instead of throwing.
|
|
258
|
-
|
|
259
|
-
# [5.5.2] - 2026-06-08
|
|
260
|
-
|
|
261
|
-
### Fixed
|
|
262
|
-
- **Await signup async operations.** `syncMarketingContact()`, `mailer.sync()`, and `sendWelcomeEmails()` were fire-and-forget — the Cloud Function returned the HTTP response while NeverBounce validation and provider syncs were still in flight, causing premature shutdown and request timeouts. All are now properly awaited.
|
|
263
|
-
|
|
264
|
-
### Changed
|
|
265
|
-
- **Welcome emails send in parallel.** `sendWelcomeEmails()` now uses `Promise.all()` for concurrent sends while still awaiting completion.
|
|
266
|
-
- **Log inferred contact name.** Signup route now logs the `inferUserContact` result for observability.
|
|
267
|
-
|
|
268
|
-
# [5.5.1] - 2026-06-07
|
|
269
|
-
|
|
270
|
-
### Changed
|
|
271
|
-
- **Test HTTP client sends plain requests.** `http.post('route')` now goes to `/${route}` instead of `/backend-manager/${route}`. All BEM test files updated to explicitly include `backend-manager/` prefix. Consumer projects can now test their own routes directly (e.g. `http.post('projects', {...})`).
|
|
272
|
-
- **`_processMiddleware` checks consumer routes.** Falls back to BEM's own routes directory only if the consumer doesn't have a matching route — enables consumer routes to work through `bm_api`.
|
|
273
|
-
- **Signup uses full email validation.** `syncMarketingContact` now runs `ALL_CHECKS` (including NeverBounce mailbox verification) before syncing to marketing lists.
|
|
274
|
-
|
|
275
|
-
# [5.5.0] - 2026-06-06
|
|
276
|
-
|
|
277
|
-
### BREAKING
|
|
278
|
-
- **Marketing config keys renamed from platform-specific to role-based.** `marketing.sendgrid` → `marketing.campaigns` (with `platform: 'sendgrid'`), `marketing.beehiiv` → `marketing.newsletter` (with `platform: 'beehiiv'`). Response object keys (`providers.sendgrid`/`providers.beehiiv`) renamed to `providers.campaigns`/`providers.newsletter`. Seed campaign provider arrays updated to match. All consumer `backend-manager-config.json` files must be updated.
|
|
279
|
-
|
|
280
|
-
### Added
|
|
281
|
-
- **NeverBounce mailbox verification.** Added as the preferred mailbox verification provider (`NEVERBOUNCE_API_KEY`), with ZeroBounce kept as fallback. Provider logic extracted into pluggable `validation-provider-neverbounce.js` and `validation-provider-zerobounce.js` modules.
|
|
282
|
-
- **`platform` property** on marketing config roles — specifies which provider backs each role (e.g. `platform: 'sendgrid'` under `campaigns`).
|
|
283
|
-
|
|
284
|
-
### Changed
|
|
285
|
-
- **Mailbox verification is now provider-agnostic.** `validation.js` dispatches to whichever provider has an API key set, instead of hardcoding ZeroBounce.
|
|
286
|
-
|
|
287
|
-
# [5.4.1] - 2026-06-06
|
|
288
|
-
|
|
289
|
-
### Added
|
|
290
|
-
- **Push notification campaigns.** Marketing campaign POST route and cron now handle `type: 'push'` with brand-aware icon/clickAction defaults and test-mode owner filtering.
|
|
291
|
-
- **`filters` field** in campaign schema for push notification targeting.
|
|
292
|
-
- **Test filtering docs** in CLAUDE.md and docs/test-framework.md (`npx mgr test <path>`, `bem:`, `project:` prefixes).
|
|
293
|
-
|
|
294
|
-
### Fixed
|
|
295
|
-
- **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.
|
|
296
|
-
- **Double-escaped URLs in email templates.** Button hrefs, signoff links, and CTA URLs were HTML-escaped via `escape()`, breaking URLs with `&` characters. Removed `escape()` from href attributes across all templates while keeping it on display text.
|
|
297
|
-
- **Spacer elements.** Replaced ` ` text hacks with proper `mj-spacer` elements in base template.
|
|
298
|
-
- **Notification library defaults.** Icon and click URL now use brand config instead of hardcoded ITW values.
|
|
299
|
-
|
|
300
|
-
# [5.4.0] - 2026-06-05
|
|
301
|
-
|
|
302
|
-
### Added
|
|
303
|
-
- **Unified MJML email system.** All emails (transactional, marketing, newsletter) rendered server-side via composable MJML templates. No more SendGrid dynamic templates (`d-xxx` IDs).
|
|
304
|
-
- **Composable template blocks** in `base.js`: `skeleton()`, `logo()`, `cardWrapper()`, `signoff()`, `button()`, `footer()` — templates compose what they need.
|
|
305
|
-
- **4 email templates:** `card` (default), `plain` (full-width personal), `order` (9 payment event types), `feedback` (rating faces with gift card incentive).
|
|
306
|
-
- **Shared preparation layer** (`prepare.js`): brand resolution, sender resolution, markdown rendering, signoff defaults, categories, HMAC unsubscribe URLs, template data building.
|
|
307
|
-
- **Marketing campaign CTA buttons** and discount codes in seed campaigns. Each audience segment gets tailored copy + audience-matched discount code.
|
|
308
|
-
- **`id="email-content"` wrapper** on all compiled email HTML (matches legacy SendGrid template convention).
|
|
309
|
-
- **`docs/email-system.md`** deep reference for the entire email pipeline.
|
|
310
|
-
- **New test accounts:** `intent-discount-validation`, names on all journey accounts for email personalization.
|
|
311
|
-
- **New test:** `journey-payments-discount` for subscription with promo code.
|
|
312
|
-
- **Email tests reorganized** under `test/email/` mirroring `src/manager/libraries/email/`.
|
|
313
|
-
|
|
314
|
-
### Changed
|
|
315
|
-
- **`data.body` → `data.content`** across all email callers and templates. Template-specific payload lives in `data.content`; system metadata in `data.brand`/`data.email`/`data.personalization`.
|
|
316
|
-
- **`data.order` → `data.content`** for the order template. All payment transition handlers updated.
|
|
317
|
-
- **Marketing campaigns consolidated:** top-level `content`/`discountCode` → `data.content.message`/`data.content.discountCode`. Frontend calendar updated.
|
|
318
|
-
- **UTM auto-tagging tags ALL HTTP links**, not just brand-domain. Campaign name derived from caller's first category. Signup signoff URLs no longer hardcode UTM.
|
|
319
|
-
- **Marketing unsubscribe** uses `<%asm_group_unsubscribe_raw_url%>` for per-recipient links in Single Sends.
|
|
320
|
-
- **Schema defaults** `'default'` → `'card'` in admin/email, marketing/campaign, MCP tools.
|
|
321
|
-
- **Seed campaign enforced templates** `'core/card'` → `'card'`.
|
|
322
|
-
- **Seed campaign discount codes** matched to audience-restricted codes from `discount-codes.js`.
|
|
323
|
-
|
|
324
|
-
### Fixed
|
|
325
|
-
- **UTM tags missing on template-generated links.** `renderEmail()` now calls `tagLinks()` on compiled HTML (CTA buttons, footer, signoff links).
|
|
326
|
-
- **Newsletter fallback alert email blank.** Used old `data.body` key — updated to `data.content`.
|
|
327
|
-
- **`IMAGE_MAX_DIMENSION` test expected 4096** but source was changed to 2048.
|
|
328
|
-
- **Intent discount test used hardcoded `frequency: 'monthly'`** — product only has `daily`. Now reads from product config.
|
|
329
|
-
- **Marketing lifecycle sync test** used `_test.admin` email blocked by validation. Now syncs the lifecycle test contact.
|
|
330
|
-
- **AI inference test assertions** too strict — softened to tolerate AI flakiness.
|
|
331
|
-
- **Test account isolation:** `intent-discount-validation` gets its own account to avoid pollution from journey tests.
|
|
332
|
-
|
|
333
|
-
### Removed
|
|
334
|
-
- **SendGrid template ID references** from schemas and seed campaign enforced fields.
|
|
335
|
-
- **`core/` template aliases** — callers use direct names (`card`, `order`, `feedback`, `plain`).
|
|
336
|
-
- **Top-level `content` and `discountCode`** fields from marketing campaign schema.
|
|
337
|
-
|
|
338
|
-
# [5.3.5] - 2026-06-04
|
|
339
|
-
|
|
340
|
-
### Added
|
|
341
|
-
- **Campaign integration test.** `test/routes/marketing/campaign.js` covers send-to-test_admin (extended mode), future-campaign-pending, and auth gate checks.
|
|
342
|
-
- **Provider observability logs.** SendGrid: `resolveFieldIds`, `addContact` (email + list + field count), `buildFields` (warns on unmapped fields). Beehiiv: `addSubscriber` (email + publication + field count). These were completely silent before.
|
|
343
|
-
|
|
344
|
-
### Fixed
|
|
345
|
-
- **Beehiiv fallback alert used `brand.url` for email domain.** Subdomain projects (e.g. Ultimate Jekyll) built `alerts@ultimate-jekyll.itwcreativeworks.com` instead of the correct `alerts@itwcreativeworks.com`. Now derives domain from `brand.contact.email`.
|
|
346
|
-
|
|
347
|
-
# [5.3.4] - 2026-06-04
|
|
348
|
-
|
|
349
|
-
### Added
|
|
350
|
-
- **`monthly-weekday` recurrence pattern.** Campaigns can now recur on the Nth weekday of each month (e.g., 2nd Wednesday). New `getNextOccurrence` case + `nextNthWeekday()` helper.
|
|
351
|
-
- **Minute precision for campaign scheduling.** All scheduling helpers (`nextWeekday`, `nextNthWeekday`, `nextMonthDay`, `getNextOccurrence`) now accept an optional `minute` parameter (previously hour-only).
|
|
352
|
-
- **Emulator project mismatch detection.** Health check returns `projectId`; the test runner compares it (via Emulator Hub + health endpoint) and aborts immediately if the running emulator belongs to a different project.
|
|
353
|
-
- **CDN image pre-scaling.** `admin/post` adds `?w=&q=` params to Unsplash URLs before downloading so the CDN delivers a pre-scaled image (~314KB vs 3.8MB) and sharp never decodes a massive original.
|
|
354
|
-
- **Emulator orphan cleanup.** `npx mgr emulator` sweeps all emulator ports after shutdown and kills orphaned Java processes (Firestore, Database, PubSub) that survived SIGTERM.
|
|
355
|
-
- **SendGrid sender identity resolution.** `resolveSenderIds()` fetches verified senders and maps `from_email → sender_id` for Single Send campaigns.
|
|
356
|
-
|
|
357
|
-
### Changed
|
|
358
|
-
- **Campaign scheduling helpers consolidated into `constants.js` (SSOT).** `getNextOccurrence`, `nextWeekday`, `nextNthWeekday`, and `nextMonthDay` were duplicated across `seed-campaigns.js` and both cron jobs — now imported from a single source.
|
|
359
|
-
- **Default campaign send times changed to Wednesday 10:30 AM PT (17:30 UTC).** Sale campaigns use the 2nd Wednesday of each month (`monthly-weekday`); newsletter sends every Wednesday (`weekly`). Previously: 15th of month at 14:00 UTC / Monday at 10:00 UTC.
|
|
360
|
-
- **`IMAGE_MAX_DIMENSION` reduced from 4096 to 2048.** Blog header images capped at 2048px on the long edge. Reduces peak memory in `admin/post` from ~71MB to ~11MB decoded. Matches UJM's `imagemin.js`.
|
|
361
|
-
- **`sharp.cache(false)` in `resizeImage()`.** Disables sharp's pixel cache so decoded buffers are freed immediately between images, preventing OOM on 256MB Cloud Functions.
|
|
362
|
-
- **Emulator SIGINT handling.** Ctrl+C handler is now synchronous (prevents Node from exiting before shutdown completes). Second Ctrl+C force-kills. Port sweep runs after exit regardless of spam.
|
|
363
|
-
- **SendGrid `createSingleSend` fixes.** Resolves `sender_id` from Sender Identities, builds `html_content` from preheader + dynamic content, uses `resolvedSettings` for segment mapping.
|
|
364
|
-
|
|
365
|
-
### Fixed
|
|
366
|
-
- **Marketing segment resolution used wrong settings object.** `sendCampaign` passed `settings.segments` instead of `resolvedSettings.segments`, ignoring campaign-level overrides.
|
|
367
|
-
|
|
368
|
-
# [5.3.3] - 2026-06-03
|
|
369
|
-
|
|
370
|
-
### BREAKING
|
|
371
|
-
- **Payment webhook routes no longer accept `BACKEND_MANAGER_KEY` as a fallback.** `/payments/webhook` and `/payments/dispute-alert` now only accept `BACKEND_MANAGER_WEBHOOK_KEY`. All consumer brands must have `BACKEND_MANAGER_WEBHOOK_KEY` set in their `.env` and registered with their payment providers (Stripe, PayPal, Chargebee, Chargeblast) before upgrading to this version.
|
|
372
|
-
|
|
373
|
-
# [5.3.2] - 2026-06-03
|
|
374
|
-
|
|
375
|
-
### Added
|
|
376
|
-
- **Newsletter HTML preview via GitHub Pages.** Enabled GitHub Pages on the `newsletter-assets` repo so newsletter HTML renders natively in the browser. Added `PAGES_BASE` constant and `previewUrl` to `image-host.js` uploads, threaded through to the Firestore campaign `assets` doc and the Beehiiv fallback alert email.
|
|
377
|
-
|
|
378
|
-
### Changed
|
|
379
|
-
- **Cleaner Beehiiv fallback email.** Restructured the asset links to use clean anchor text with a nested list (preview / raw HTML) under "Full HTML" instead of displaying raw GitHub URLs.
|
|
380
|
-
- **Download-app-link email subject.** Friendlier subject line format.
|
|
381
|
-
|
|
382
|
-
# [5.3.1] - 2026-06-02
|
|
383
|
-
|
|
384
|
-
### Fixed
|
|
385
|
-
- **Leaking `wonderful-fetch` mock in the webhook-forward unit test.** `test/helpers/webhook-forward.js` installs a stub into `require.cache['wonderful-fetch']` at module load (the sanctioned cross-project fan-out exception — there's no second BEM emulator to receive the real fan-out POSTs), but never restored it. Because every test file is `require()`d into the same process, the stub leaked process-wide: every later test whose route did `require('wonderful-fetch')` got `{ received: true }` in 0ms instead of a real HTTP round-trip (observed breaking consumer sponsorship + inbound-email route tests). The helper now saves the original cache entry and restores it in a suite-level `cleanup()`, confining the mock to its own file.
|
|
386
|
-
|
|
387
|
-
# [5.3.0] - 2026-06-02
|
|
388
|
-
|
|
389
|
-
### Added
|
|
390
|
-
- **`Manager.AI(assistant).image({ prompt, ... })`** — image generation via OpenAI's `gpt-image-2`. Separate method from `request()` (return type is bytes, not text; bypasses moderation/token-accounting/schema/prompt-normalization, none of which apply to image gen). Returns `{ buffer, b64, mime, revisedPrompt, model, size, quality, raw }` for a single image, or an array when `n > 1`. Options: `prompt` (required), `model` (default `gpt-image-2`), `size` (`1024x1024` default), `quality` (`medium` default), `background`, `n`, `timeout` (default 5min — image gen is slow). Only `openai` implements it. Lives on `OpenAI.prototype.image()` + dispatched via `AI.prototype.image()`. See [docs/ai-library.md](docs/ai-library.md).
|
|
391
|
-
- **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).
|
|
392
|
-
- **`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).
|
|
393
|
-
- **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.
|
|
394
|
-
- **`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`.
|
|
395
|
-
|
|
396
|
-
### Changed
|
|
397
|
-
- **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.
|
|
398
|
-
- **Install-command parity.** `npx mgr install` accepts the unified alias set across all four frameworks — `dev|d|development|local|l` for the local source install and `live|prod|p|production` for the published version; docs advertise the canonical `dev` + `live`.
|
|
399
|
-
- **`copyDefaults` no longer skips `_`-prefixed filenames.** The archive-skip rule now only applies to `_`-prefixed *directory* segments (e.g. `_legacy/`); a `_`-prefixed *filename* like `test/_init.js` ships verbatim. The `_.env` / `_.gitignore` dotfile rename is unchanged.
|
|
400
|
-
|
|
401
|
-
### Fixed
|
|
402
|
-
- **Test-account creation race.** A late `auth:on-delete` could clobber the fresh `auth:on-create` doc, leaving accounts without `api.clientId`/`privateKey` and cascading into dozens of auth/payment test failures. `createAccount()` now verifies the API keys landed and repairs (recreate) if a stale delete clobbered the doc, making the suite deterministic.
|
|
403
|
-
- **`meta/stats` seed ordering.** `ensureMetaStats()` ran before the emulator flush (so it was wiped) and skipped when the doc already existed (so the notification on-write trigger could leave it without a `users` field). It now merges the `users` baseline AFTER the flush, fixing flaky admin-stats tests.
|
|
404
|
-
- **Flaky payment/dispute tests.** The trial journey now skips cleanly when no paid product has `trial.days > 0` (mirrors trial-cancel) instead of timing out; the dispute-alert dedup test seeds a deterministic in-flight doc instead of racing a live webhook to `failed`.
|
|
405
|
-
|
|
406
|
-
# [5.2.19] - 2026-05-29
|
|
407
|
-
|
|
408
|
-
### Added
|
|
409
|
-
|
|
410
|
-
- **Shared CLI styling module (`src/cli/utils/ui.js`)** — the SSOT for BEM console output, matching the OMEGA Manager look: a `🚀` banner, 70-char `━` dividers, indented tree output, dimmed `Label:` fields, timestamps, a consistent set of status symbols (`→ ✓ ✗ ⊘ ⚠ ✅ + ↻`), and a `Summary` block (green `✅` / yellow `⚠`). Exposed on every command as `this.ui` (wired in `base-command.js`) and adoptable incrementally by `serve`/`deploy`/`test`/`emulator`. See `docs/cli-output.md`.
|
|
411
|
-
- **Regression test for the `.env`/`.gitignore` merge** (`test/helpers/merge-line-files.js`). Covers key-based alignment when key order drifts (the original scramble), Custom→Default promotion when the template adopts a key, Default→Custom migration of unknown keys, value preservation, quote normalization, idempotency, and that the setup-test helper is the same function as the canonical impl (SSOT guard).
|
|
412
|
-
|
|
413
|
-
### Changed
|
|
414
|
-
|
|
415
|
-
- **`npx mgr setup` now renders in the OMEGA style.** The old `---- RUNNING SETUP ----` / `[1] name: passed` / bare missing-keys dump is replaced with a banner, a divider-wrapped project header (brand name + Firebase console URL + `Project`/`API` fields), and `[DEFAULTS]` / `[CHECKS]` / `[STATS]` sections. Each check prints `[N] ✓ name`, with `⚠ … — fixing…` → `✓ fixed` on auto-fix and `✗ Could not fix: …` on hard failure. The run ends with a `Summary` block (checks count, duration, passed/failed, and any failing checks with detail lines). The `bem-config` check lists the missing `backend-manager-config.json` keys as a bulleted list and surfaces a compact version in the summary.
|
|
416
|
-
|
|
417
|
-
### Fixed
|
|
418
|
-
|
|
419
|
-
- **No more `UnhandledPromiseRejection` crash on setup failure.** Previously an unfixable check (e.g. missing config keys) rejected a promise with no reason, which bubbled out of the un-`catch`'d `bin/backend-manager` IIFE as Node's raw `UnhandledPromiseRejection: undefined` dump (exit non-zero, lost message). Setup now exits cleanly via `haltSetup()` → `process.exit(1)` after printing the styled summary, and `bin/backend-manager` wraps the run in a `try/catch` that prints a one-line `✗ <message>` backstop. The early-exit guards (missing `functions/package.json`, wrong directory) now print styled errors and exit `1` instead of `0`.
|
|
420
|
-
- **`.env` merge no longer scrambles keys under the wrong headers.** The `has correct .env file` / `has correct .gitignore` setup checks (`env-file.js`, `gitignore.js`) imported a SECOND, **positional** merge implementation in `src/cli/commands/setup-tests/helpers/merge-line-files.js` that zipped comment lines and value lines by index — so any drift between the consumer's key order and the template shifted every value down a slot (the last key of each group landed under the next group's header), dropped newly-added template keys, and duplicated keys. That duplicate is **deleted**; the helper is now a thin SSOT shim re-exporting the canonical key-based merge in `src/utils/merge-line-files.js`. The canonical merge also now **promotes** a key from the user's Custom section up into Default (with its value) when the framework template adopts that key, instead of emitting an empty Default line and leaving the value stranded in Custom.
|
|
421
|
-
|
|
422
|
-
# [5.2.17] - 2026-05-29
|
|
423
|
-
|
|
424
|
-
### Added
|
|
425
|
-
|
|
426
|
-
- **Newsletter-driven blog articles (`marketing.beehiiv.content.article`).** When enabled, the newsletter generator expands its **lead section** (`structure.sections[0]`) into a full blog article via the Ghostii engine, publishes it to the website repo through the `admin/post` route, and injects a "Read the full article" CTA (`section.cta = { label, url }`) onto that section so the newsletter links to the long-form post. The article build runs **concurrently** with SVG image generation (both are slow AI calls) and is **failure-isolated** — if it throws, no CTA is injected and the newsletter ships normally. The published URL (`{brand.url}/blog/{slug}`) is surfaced on the generator return as `assets.articleUrl` and `meta.article`. New config block: `content.article = { enabled, author }` (`enabled` default `false`). See `docs/marketing-campaigns.md`.
|
|
427
|
-
- **`section.cta` rendering.** Both the MJML template (`sectionCard`, already supported) and the markdown renderer (`lib/markdown-renderer.js`, new) now render `section.cta = { label, url }` when present. `getBodySections` passes `cta` through for both classic and field-report shapes. CTAs are still never authored by the AI — they're injected by code post-publish with a real, verified URL.
|
|
428
|
-
- **Generate/publish split on the linked article (`opts.publishArticle`).** When `config.article.enabled` is on, the article is always **generated** (Ghostii writes it + the public URL is computed from the title slug + the CTA is injected), but it's only **committed** to the website repo via `admin/post` when `opts.publishArticle` is true. The production cron passes `publishArticle: true`. The newsletter iteration test (`test/marketing/newsletter-generate.js`) leaves it false by default — so a full `TEST_EXTENDED_MODE=1` run exercises the Ghostii write + CTA path without committing a real post — and opts in with `NEWSLETTER_CREATE_ARTICLE=1`. The CTA URL is valid either way (derived from the same slugify `admin/post` uses). `meta.article.published` records whether the post was actually committed.
|
|
429
|
-
|
|
430
|
-
### Changed
|
|
431
|
-
|
|
432
|
-
- **Extracted the Ghostii article engine into `src/manager/libraries/content/ghostii.js`** (`writeArticle` + `publishArticle`) as the SSOT for the Ghostii API request shape and the `admin/post` publish payload. Both the standalone `ghostii-auto-publisher.js` daily cron and the new newsletter linked-article flow import it (DRY). No behavior change to the standalone cron.
|
|
433
|
-
- **Standalone Ghostii is now disabled by default** (`ghostii[0].articles: 0` in `templates/backend-manager-config.json`). The daily `ghostii-auto-publisher.js` cron remains fully functional — set `articles >= 1` to opt back into independent article publishing. For newsletter-linked articles, use `content.article.enabled` instead.
|
|
434
|
-
|
|
435
|
-
# [5.2.16] - 2026-05-28
|
|
436
|
-
|
|
437
|
-
### Removed
|
|
438
|
-
|
|
439
|
-
- **Dropped the legacy top-level `affiliateCode` field from `/user/signup`.** UJM and all current consumers send the referral code as `attribution.affiliate.code`; the top-level `affiliateCode` (and the normalize-to-`attribution` shim + the `processAffiliate` fallback that read it) was a dead legacy path. Removed from `src/manager/schemas/user/signup/post.js`, the `buildUserRecord` normalize block, and the `processAffiliate` lookup in `src/manager/routes/user/signup/post.js`. The route now reads referral codes exclusively from `attribution.affiliate.code`. (The legacy `bm_api` sign-up action `functions/core/actions/api/user/sign-up.js` is unchanged.)
|
|
440
|
-
|
|
441
|
-
### Fixed
|
|
442
|
-
|
|
443
|
-
- **Consent: never downgrade an existing granted consent on `/user/signup`** (`src/manager/routes/user/signup/post.js`). A legacy account — signed up before the `flags.signupProcessed` completion flow existed, so the flag was never set — re-fires `/user/signup` on every page load until the flag flips. Its consent payload arrives empty (the original is long gone from `localStorage`), which previously computed `'revoked'` and, on the `{ merge: true }` write, wiped the consent the user actually granted months ago. `buildConsentRecord` now reads the existing doc's consent and preserves any already-`granted` status when the incoming payload doesn't explicitly re-grant it. A genuine new grant still applies; an at-signup decline with no prior grant still records the decline. Added `consent-empty-payload-preserves-existing-grant` and `consent-explicit-decline-does-not-downgrade-existing-grant` tests (+ a dedicated `consent-preserve` test account). See `docs/consent.md`.
|
|
444
|
-
|
|
445
|
-
### Changed
|
|
446
|
-
|
|
447
|
-
- **`user/signup` schema: shaped the `consent` field.** `src/manager/schemas/user/signup/post.js` now declares the nested `consent.{legal,marketing}.{granted,text}` shape instead of a bare passthrough object, documenting the input contract at the schema layer (the SSOT for request shape). Each sub-object is optional — omitting it leaves existing consent untouched (see the downgrade guard above).
|
|
448
|
-
|
|
449
|
-
# [5.2.15] - 2026-05-28
|
|
450
|
-
|
|
451
|
-
### Changed
|
|
452
|
-
|
|
453
|
-
- **Standardized the GitHub token env var `GITHUB_TOKEN` → `GH_TOKEN`** across the entire repo, to match the convention used in all other ITW repos. Updated every GitHub-backed route and action (`admin/post`, `content/post`, `admin/repo/content`, `general/fetch-post`, `admin/write-repo-content`, `admin/edit-post`, `admin/create-post`, legacy `create-post`), the email image-host library (`libraries/email/generators/lib/image-host.js`), the CLI deprecated-env notice, the `templates/_.env` scaffold, the `docs/marketing-campaigns.md` reference, and all related test files. This is a hard rename with no fallback — any environment (CI, prod, local `.env`) that still sets `GITHUB_TOKEN` must switch to `GH_TOKEN` for the GitHub-backed routes to work.
|
|
454
|
-
|
|
455
|
-
# [5.2.14] - 2026-05-28
|
|
456
|
-
|
|
457
|
-
### Removed
|
|
458
|
-
|
|
459
|
-
- **Dropped the `marketing-webhooks` Firestore idempotency ledger.** The marketing-webhook dispatcher (`src/manager/routes/marketing/webhook/post.js`) no longer reads/writes `marketing-webhooks/{eventId}` docs for dedup. Both handler side effects — writing `consent.marketing.status = 'revoked'` and the cross-provider `mailer.remove()` — are naturally idempotent, so a provider retry or duplicate parent fan-out re-runs to the same end state with no extra side effects. (This is the key difference from `payments-webhooks`, where dedup is load-bearing because payment side effects are NOT idempotent.) Events with no `eventId` are now processed instead of skipped, since dedup is no longer required. Removed `marketing-webhooks` from the test runner's pre-test cleanup list (`src/test/test-accounts.js`). Consumers with an existing `marketing-webhooks` collection can safely delete it — nothing reads or writes it anymore.
|
|
460
|
-
|
|
461
|
-
### Fixed
|
|
462
|
-
|
|
463
|
-
- **`libraries/infer-contact.js`: guard the optional `assistant` arg.** All log/error calls now use `assistant?.` so `inferContact(email)` works without an assistant. Previously threw a `TypeError` when `BACKEND_MANAGER_OPENAI_API_KEY` was unset and no assistant was passed.
|
|
464
|
-
- **`libraries/email/marketing/index.js`: gate `Marketing.remove()` behind test mode.** `remove()` now short-circuits with `if (assistant.isTesting() && !process.env.TEST_EXTENDED_MODE) return {}`, matching `add()` and `sync()`. Closes a gap where auth `onDelete` (fired ~44× at test startup during user cleanup) could hit the live SendGrid/Beehiiv remove APIs during a normal test run. The gate lives at the library SSOT so every caller (onDelete, webhook processors, contact-delete route) inherits it.
|
|
465
|
-
|
|
466
|
-
### Changed
|
|
467
|
-
|
|
468
|
-
- **Signup timestamps stamped from Firebase Auth `creationTime`.** Both write paths — the `onCreate` auth event (`src/manager/events/auth/on-create.js`) and the `user/signup` fallback route (`src/manager/routes/user/signup/post.js`) — now set `metadata.created` and `consent.{legal,marketing}.grantedAt`/`revokedAt` from `user.metadata.creationTime` instead of "now"/request-time. The OMEGA user migration treats Auth's `creationTime` as the SSOT and reconciles every doc against it; the prior few-seconds drift meant every new signup got re-fixed on the next migration run. Stamping from `creationTime` makes new docs match the migration's expected value exactly, ending the recurring churn.
|
|
469
|
-
- **`user/signup`: `flags.signupProcessed` is the sole idempotency gate.** Removed the 5-minute account-age reject (`MAX_ACCOUNT_AGE_MS`). A genuinely-unprocessed account can now complete signup whenever it retries — fixes the failure where a slow/missing `onCreate` plus a retry after 5 minutes caused a legitimate signup to be rejected. The frontend (UJM) gate is being moved to the same doc flag in a parallel change.
|
|
470
|
-
- **CLI: port-conflict prompt auto-confirms after 5s.** The "Kill these processes to free the ports?" prompt in `src/cli/commands/base-command.js` now auto-confirms `Y` after 5 seconds of no input (via `AbortSignal.timeout` → `AbortPromptError` → default `true`), so unattended test/dev loops (`emulator`, `serve`, `test`) no longer hang waiting for input. Manual `y`/`n` and Ctrl+C still work.
|
|
471
|
-
- **`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`.
|
|
472
|
-
- **Dependency bumps**: `@anthropic-ai/claude-agent-sdk` ^0.3.152 → ^0.3.153, `stripe` ^22.1.1 → ^22.2.0.
|
|
473
|
-
- **`templates/_.env`**: consolidate OpenAI/Anthropic keys under an "AI" section and add `CLAUDE_CODE_OAUTH_TOKEN`.
|
|
474
|
-
- **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.
|
|
475
|
-
|
|
476
|
-
# [5.2.12] - 2026-05-27
|
|
477
|
-
|
|
478
|
-
### Changed
|
|
479
|
-
|
|
480
|
-
- **`before-signin`: move `activity.language` from `geolocation` to `client`.** In `src/manager/events/auth/before-signin.js`, the `language` field (sourced from `context.locale`) now lives under `activity.client` alongside `userAgent`, instead of under `activity.geolocation` alongside `ip`. Language is a client-side property (browser/locale) rather than an IP-derived geolocation property, so this aligns the before-signin write shape with the rest of the activity schema.
|
|
481
|
-
|
|
482
|
-
# [5.2.11] - 2026-05-27
|
|
483
|
-
|
|
484
|
-
### Added
|
|
485
|
-
|
|
486
|
-
- **CLI: bare `logs` is now an alias for `logs:read`.** `npx mgr logs [...flags]` now dispatches to the same handler as `npx mgr logs:read`, so callers (humans and AI assistants alike) no longer error out when they omit the `:read` suffix. Routing in `src/cli/index.js` accepts the bare `logs` option, and `src/cli/commands/logs.js` maps the bare subcommand to the `read` action. `logs:tail` and `logs:stream` are unchanged.
|
|
487
|
-
|
|
488
|
-
# [5.2.10] - 2026-05-26
|
|
489
|
-
|
|
490
|
-
### Added
|
|
491
|
-
|
|
492
|
-
- **`POST /admin/post`: resize images at ingest.** Downloaded post images are now checked against `IMAGE_MAX_DIMENSION` (4096px on the long edge) in `src/manager/routes/admin/post/post.js` and re-encoded as progressive JPEG at `IMAGE_JPEG_QUALITY` (80) when oversized. Prevents downstream Jekyll/imagemin pipelines from stalling on huge sources (a real 16384×10576 source decoded to ~520MB raw and silently broke 4 StudyMonkey posts on production). `resizeImage`, `IMAGE_MAX_DIMENSION`, and `IMAGE_JPEG_QUALITY` are exported for tests. Adds `sharp` as a dependency.
|
|
493
|
-
- **`test/routes/admin/post-resize-image.js`** — 7 unit tests covering the resize contract (pass-through under the limit, boundary at exact limit, landscape/portrait/square scaling, the 16384×10576 case, on-disk overwrite). No network, no auth, no GitHub.
|
|
494
|
-
- **`test/routes/admin/create-post.js`**: extended `create-post-rewrites-body-images` to submit a 5000×3000 header image, plus new `verify-oversized-header-image-was-resized` step that fetches the committed image back from GitHub and asserts long edge ≤ 4096px.
|
|
495
|
-
|
|
496
|
-
### Changed
|
|
497
|
-
|
|
498
|
-
- **Dependency bumps**: `sharp` ^0.34.4 → ^0.34.5, `sanitize-html` ^2.17.3 → ^2.17.4 (auto-bumped at install).
|
|
499
|
-
|
|
500
|
-
# [5.2.9] - 2026-05-25
|
|
501
|
-
|
|
502
|
-
### Added
|
|
503
|
-
|
|
504
|
-
- **OpenAI provider: multi-role prompt array support.** `options.prompt` now accepts either the legacy object form (`{ path|content, settings }`, auto-wrapped as a single `system`-role segment per the OpenAI Model Spec) OR an array form (`[{ role, path|content, settings }, ...]`) where each segment becomes its own message with its declared role. Valid roles: `system`, `developer`, `user`, `assistant`; order is preserved; role defaults to `system` if omitted; invalid roles throw. New internal helpers `normalizePrompt()` + `VALID_PROMPT_ROLES` canonicalize input; the request pipeline threads `promptSegments` end-to-end (per-segment load, per-role logging, per-segment error surfacing, `formatHistory` unshifts segments in declared order). `module.exports._internals` exposes `normalizePrompt`, `formatHistory`, `VALID_PROMPT_ROLES` for unit tests — not part of the public API. Backwards compatible: existing callers passing `prompt: { content: '...' }` are auto-wrapped as a single `system` segment with no consumer changes.
|
|
505
|
-
- **`test/helpers/ai-request-payload.js`** — 16 standalone tests covering the BEM → OpenAI payload transformation with no network and no assistant. Exercises `normalizePrompt` (undefined/null/empty handling, legacy object → single system segment, array-form role preservation, role-defaulting, invalid-role throw, full OpenAI Model Spec role coverage) and `formatHistory` (single-system emit, multi-segment order, empty-array → user-only, prompt+history+new-user interleaving, assistant `output_text` typing, history limit, `dedupeConsecutiveRoles` trailing-user drop, content trim/strip).
|
|
506
|
-
|
|
507
|
-
### Changed
|
|
508
|
-
|
|
509
|
-
- **Default OpenAI model bumped to `gpt-5.4-mini`** (was `gpt-5-mini`). Updated in `src/manager/libraries/ai/providers/openai.js` (`DEFAULT_MODEL`), `src/manager/libraries/ai/index.js` (usage example in JSDoc), and `src/manager/libraries/infer-contact.js` (`inferContactWithAI`). The pricing table in the OpenAI provider already includes `gpt-5.4-mini`, so no further config changes are required.
|
|
510
|
-
- **`inferContactWithAI` maxTokens doubled to 2048** (was 1024). Gives the model headroom for the richer multi-field contact response without truncation.
|
|
511
|
-
|
|
512
|
-
# [5.2.8] - 2026-05-25
|
|
513
|
-
|
|
514
|
-
### Changed
|
|
515
|
-
|
|
516
|
-
- **`/user/signup` precedence flip for `activity.client`.** `routes/user/signup/post.js` now spreads `assistant.request.client` FIRST and `settings.context.client` (the browser's `getContext()` payload) LAST, so the browser-supplied values win for the `client` block. `activity.client.language` is now `navigator.language` (e.g. `en-US`) instead of the raw `Accept-Language` header list (e.g. `en-US,en;q=0.9,fr;q=0.8`); falls back to the header when no browser context was sent (bots, non-browser clients). `activity.geolocation` precedence is unchanged — Cloudflare headers (`cf-ipcountry`, etc.) still win, since the browser doesn't know its own geo. Final shape mirrors `assistant.request`: geolocation is header-authoritative, client is browser-authoritative.
|
|
517
|
-
|
|
518
|
-
# [5.2.7] - 2026-05-24
|
|
519
|
-
|
|
520
|
-
### Fixed
|
|
521
|
-
|
|
522
|
-
- **`inferContact` silent-failure logging.** When the AI inference returned an empty result (either the AI call failed and returned null, or `gpt-5-mini` returned a parsed response with all-empty fields, or the response shape was missing `firstName`), the whole flow silently swallowed the failure and the signup's `user.personal.name` got written as `null`/`null`. Confirmed live on Somiibo: signed up `ian.wiedenman.business@gmail.com` twice on the same backend — first signup inferred nothing (empty name written), second signup correctly inferred "Ian Wiedenman". Same email, same code, transient AI hiccup, zero log trail. Added three unconditional diagnostic logs to `src/manager/libraries/infer-contact.js` (AI returned null, AI parsed response had all fields empty, AI response missing firstName) plus a log in `src/manager/routes/user/signup/post.js#inferUserContact` when the helper returns null. Next silent failure will at least leave a breadcrumb.
|
|
523
|
-
|
|
524
|
-
# [5.2.6] - 2026-05-24
|
|
525
|
-
|
|
526
|
-
### Added
|
|
527
|
-
|
|
528
|
-
- **`scripts/test-helper-providers.js`** — small CLI for live-test verification. Run from any consumer's `functions/` dir: `node <bem>/scripts/test-helper-providers.js find <email>` (check SendGrid + Beehiiv state without dashboards) or `purge <email>` (remove from both providers). Used during end-to-end consent-pipeline testing.
|
|
529
|
-
- **Email-template namespacing.** `general/email` route now translates `:` in `settings.id` to a folder separator, so `general:download-app-link` resolves to `templates/general/download-app-link.js`. Existing `templates/download-app-link.js` moved into `templates/general/` to match.
|
|
530
|
-
|
|
531
|
-
### Changed
|
|
532
|
-
|
|
533
|
-
- **Payment webhook + dispute-alert routes now accept either `BACKEND_MANAGER_WEBHOOK_KEY` (preferred) or `BACKEND_MANAGER_KEY` (legacy).** Phase 1 of the webhook-key migration plan in `TODO-WEBHOOK-KEY-UPGRADE.md` — non-breaking dual-acceptance so consumers can roll over at their own pace. `src/manager/routes/payments/webhook/post.js:28` + `src/manager/routes/payments/dispute-alert/post.js:20` validate against either env var. Test-processor self-fire in `src/manager/routes/payments/intent/processors/test.js:158` now uses `BACKEND_MANAGER_WEBHOOK_KEY` directly. All 13 payment test files (`test/routes/payments/*.js`, `test/events/payments/journey-*.js`) updated to use `BACKEND_MANAGER_WEBHOOK_KEY` for webhook URLs. `src/test/utils/http-client.js` + `src/test/runner.js` thread the new env var through the test context. `docs/stripe-webhook-forwarding.md` reflects the dual-key acceptance. Phase 2 (drop the legacy fallback) is tracked in `TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md`.
|
|
534
|
-
- **`npx mgr emulator` no longer wraps the emulator in a keep-alive subprocess.** Previously spawned a sleep-86400 wrapper via `runWithEmulator`; now spawns the emulator child directly and uses a clean SIGINT handler. Cleaner shutdown, no orphaned subprocesses, no `node-powertools` dep on the boot path. `npx mgr test`'s auto-start path uses the same helper.
|
|
535
|
-
|
|
536
|
-
### Fixed
|
|
537
|
-
|
|
538
|
-
- **Finished the v5.2.3 SendGrid timeout bump that missed 5 sites.** v5.2.3's CHANGELOG claimed "All 9 fetch sites updated" but actually 5 sites in `sendgrid.js` (including `upsertContacts:118` — the hot path used by `Marketing.sync()`) were still using `timeout: 15000`. Confirmed live on Somiibo: a signup with marketing consent granted silently dropped both SendGrid and Beehiiv with `Request timed out` after ~18s. Now every API call in `sendgrid.js` goes through the `SENDGRID_TIMEOUT_MS` (60s) constant. The only remaining literal is the S3 CSV download in `getSegmentContacts:578` (30s — not a SendGrid API call).
|
|
539
|
-
- **`beehiiv.js` timeouts unified under a new `BEEHIIV_TIMEOUT_MS` (60s) constant.** Two hot-path sites (`addSubscriber:71` and the unsub endpoint at `:455`) were still on `timeout: 15000`. Same silent-drop failure mode as SendGrid above. Every Beehiiv API call now uses the constant.
|
|
540
|
-
- **Audited entire BEM codebase for short timeouts. Zero prod-path timeouts under 60s remain.**
|
|
541
|
-
- **Bumped 6 sites from 10-15s → 60s:**
|
|
542
|
-
- `src/manager/libraries/email/validation.js:136` — ZeroBounce mailbox check
|
|
543
|
-
- `src/manager/libraries/email/generators/newsletter.js:520, 549` — parent-server `/newsletter-sources` GET + PUT
|
|
544
|
-
- `src/manager/routes/payments/intent/processors/test.js:163` — test-processor self-fire webhook
|
|
545
|
-
- `src/manager/routes/marketing/email-preferences/post.js:169, 179` — SendGrid ASM suppression POST + DELETE
|
|
546
|
-
- **Bumped 9 sites from 30s → 60s:**
|
|
547
|
-
- `src/manager/libraries/infer-contact.js:46` — PeopleDataLabs AI enrichment
|
|
548
|
-
- `src/manager/libraries/email/providers/sendgrid.js:578` — S3 CSV download for segment exports
|
|
549
|
-
- `src/manager/functions/core/actions/api/user/delete.js:34` + `src/manager/routes/user/delete.js:51` — internal sign-out fan-out
|
|
550
|
-
- `src/manager/events/firestore/payments-webhooks/analytics.js:228, 301` — Facebook + Reddit Conversions API
|
|
551
|
-
- `src/manager/routes/user/oauth2/providers/discord.js:25` + `…/google.js:30` — OAuth token-revoke
|
|
552
|
-
- `src/manager/helpers/analytics.js:331` — itwcw-package-analytics event fire
|
|
553
|
-
- **Bumped 2 sites from 30s → 120s:**
|
|
554
|
-
- `src/manager/events/cron/daily/ghostii-auto-publisher.js:106` — gpt-image-1 hero image generation (regularly 30-60s)
|
|
555
|
-
- `src/manager/events/cron/daily/ghostii-auto-publisher.js:177` — gpt-5+web-search blog post generation (regularly 60-90s)
|
|
556
|
-
- **Audit rule going forward:** any external-API call in BEM prod paths uses **60s minimum** (120s+ for LLM/image generation). Short timeouts are silent-failure machines in serverless code where Cloud Function timeouts already give us a generous outer bound. `_legacy/` files, `src/test/` infra, and `src/cli/` commands keep their existing timeouts — they're not on the request hot path.
|
|
557
|
-
|
|
558
|
-
# [5.2.5] - 2026-05-22
|
|
559
|
-
|
|
560
|
-
### Added
|
|
561
|
-
|
|
562
|
-
- **`utilities.trim(input)`** in `src/manager/helpers/utilities.js`. Walks objects/arrays recursively and trims whitespace on every string. Does NOT strip HTML. Middleware uses it to clean up incoming request data without mangling URLs, Markdown, or any payload with `<`/`>`/`&`.
|
|
563
|
-
- **`findContact(email)`** on the SendGrid + Beehiiv providers, and **`findSubscriber(email, publicationId)`** on Beehiiv. Both extracted from the existing `removeContact`/`removeSubscriber` search-by-email logic so tests (and other call sites) can verify provider state without forcing a delete.
|
|
564
|
-
- **`test/marketing/consent-lifecycle.js`** — 5-phase live integration test (gated on `TEST_EXTENDED_MODE=true`) that walks two long-lived sentinel accounts through pre-check → sync → declined-stays-out → unsubscribe → validation-gate, asserting actual SendGrid + Beehiiv state at every step. Uses `pollProvider()` to handle SendGrid's async background jobs (upsert/delete can take 10-60s+ to surface).
|
|
565
|
-
|
|
566
|
-
### Changed
|
|
567
|
-
|
|
568
|
-
- **Middleware no longer strips HTML by default.** The auto-`sanitize-html` pass on incoming request data was mangling legitimate input — URL query strings (`?a=1&b=2` → `?a=1&b=2`), Markdown, and any payload containing `<`/`>`/`&`. The middleware now only trims whitespace by default. HTML sanitization is opt-in per route via `Manager.Middleware(req, res).run('route', { sanitize: true })`. Sanitize at the HTML-insertion site (`utilities.sanitize()` in your template/email render) rather than at the request boundary. The existing schema-level `sanitize: false` opt-out still works when route-level sanitize is on.
|
|
569
|
-
- **Test sentinel accounts `consent-granted` and `consent-declined` now use the `_test.allow_*` prefix** (`_test.allow_consent-granted@...`, `_test.allow_consent-declined@...`). The `_test.*` validation block from v5.2.3 also blocks the previous `_test.consent-*` names from reaching providers — `_test.allow_*` is the carved-out exception specifically for live-provider integration tests.
|
|
570
|
-
- **Beehiiv API timeouts bumped to 60s** on the two remaining stragglers (`findSubscriber`, `resolveSegmentIds`) to match the SendGrid+Beehiiv 60s convention from v5.2.3.
|
|
571
|
-
|
|
572
|
-
### Removed
|
|
573
|
-
|
|
574
|
-
- **`cleanupMarketingProviders()` function and its pre-run/post-run hooks** in `src/test/test-accounts.js` + `src/test/runner.js`. No longer needed — the validation gate added in v5.2.3 blocks `_test.*` emails from reaching SendGrid + Beehiiv upstream, so there's nothing to clean up. The new `consent-lifecycle.js` test (which intentionally round-trips real contacts via `_test.allow_*`) manages its own pre-check force-clean inline.
|
|
575
|
-
|
|
576
|
-
# [5.2.3] - 2026-05-22
|
|
577
|
-
|
|
578
|
-
### Added
|
|
579
|
-
|
|
580
|
-
- **`marketing.sendgrid.listId` in `templates/backend-manager-config.json`.** Empty-string placeholder for OMEGA's `sendgrid/ensure/list.js` to populate at brand-onboarding time. Mirrors the existing `marketing.beehiiv.publicationId` convention.
|
|
581
|
-
- **`_test.*` local-part block** in `src/manager/libraries/email/data/blocked-local-patterns.js`. Test-suite accounts (`_test.<scenario>@somiibo.com`) are now blocked from reaching SendGrid + Beehiiv. The carved-out exception is `_test.allow_*` — used for live-provider integration tests that intentionally need to round-trip a real contact.
|
|
582
|
-
|
|
583
|
-
### Changed
|
|
584
|
-
|
|
585
|
-
- **`Marketing.add()` and `Marketing.sync()` now use the full `validate()` pipeline** instead of just `isCorporate()`. Single SSOT for "is this a valid marketing email" — runs format → disposable → corporate → localPart in one call. Stricter behavior: disposable-domain emails (mailinator etc.) and junk local-parts (`noreply`, `test*`, `_test.*`) are now blocked from marketing lists. They were previously waved through because the gate only checked corporate domains.
|
|
586
|
-
- **SendGrid list-ID lookup is now config-only.** `src/manager/libraries/email/providers/sendgrid.js#getListId()` reads `Manager.config.marketing.sendgrid.listId` and returns null if missing — no more runtime API call, no more fuzzy-match-by-brand-name. Old fuzzy logic kept commented out as `getListIdByFuzzyMatch()` backstop. **Brands must run OMEGA's sendgrid service to populate `listId` before this version sees their list assignments work** (without it, contacts land in SendGrid's global "All Contacts" pool, not the brand list).
|
|
587
|
-
- **Beehiiv publication-ID lookup is now config-only.** `src/manager/libraries/email/providers/beehiiv.js#getPublicationId()` reads `Manager.config.marketing.beehiiv.publicationId` and returns null if missing — same shape as SendGrid above. Old fuzzy logic kept commented out as backstop. Beehiiv side already preferred config when set, but now there's no API fallback.
|
|
588
|
-
- **`marketing.beehiiv.publicationId` is now an always-present empty string in the config template** (was a commented-out hint). Matches the SendGrid `listId` shape and means `Manager.config.marketing.beehiiv.publicationId` always returns `""` (never `undefined`) for legacy brands.
|
|
589
|
-
- **SendGrid API timeouts bumped from 10s → 60s** via a new top-level `SENDGRID_TIMEOUT_MS` constant in `sendgrid.js`. All 9 fetch sites updated. Catches the intermittent SendGrid backend hiccups that were dropping signups silently with "Request timed out".
|
|
590
|
-
|
|
591
|
-
# [5.2.2] - 2026-05-21
|
|
592
|
-
|
|
593
|
-
### Added
|
|
594
|
-
|
|
595
|
-
- **`consent` is now a protected user field.** `templates/firestore.rules` includes `consent` in `isWritingProtectedUserField()` so a logged-in user cannot retroactively forge their own consent record from the client — only the signup route + webhook processors can mutate it server-side. New rule test in `test/rules/user.js`.
|
|
596
|
-
- **`BaseCommand.getLogsPath(name)` / `getTempPath(name)`** in `src/cli/commands/base-command.js`. Two explicit helpers so the folder convention is the SSOT and easy to change later. `getLogsPath()` writes human-readable logs (`serve.log`, `emulator.log`, `test.log`, `logs.log`) to `<projectDir>/functions/` alongside firebase-tools' own `*-debug.log` files. `getTempPath()` writes transient internal-only stuff (`*.log.reset` sentinels, `bem-reload-trigger.js`, `test-mode.json`) to `<projectDir>/.temp/`.
|
|
597
|
-
- **`BaseCommand.sweepStaleLogs()`** wipes BEM's own `.log` files in `functions/` and `.reset` sentinels in `.temp/` on every emulator/serve boot and on `npx mgr setup`. Deliberately does NOT touch firebase-tools' debug logs (`firestore-debug.log`, `database-debug.log`, etc.) so users can grep them after a crash.
|
|
598
|
-
- **`npx mgr setup` cleanup step.** `cleanupGeneratedArtifacts()` now removes the watch trigger file (existing behavior) plus calls `sweepStaleLogs()` to clean up old BEM-owned logs/sentinels from previous runs.
|
|
599
|
-
|
|
600
|
-
# [5.2.1] - 2026-05-21
|
|
601
|
-
|
|
602
|
-
### Added
|
|
603
|
-
|
|
604
|
-
- **`BACKEND_MANAGER_WEBHOOK_KEY` in `templates/_.env`.** The `.env` scaffold the setup CLI copies into consumer projects now declares the webhook key alongside `BACKEND_MANAGER_KEY`. Required for the new `/marketing/webhook` + `/marketing/webhook/forward` routes shipped in 5.2.0. Existing consumers should add it to their own `.env` manually.
|
|
605
|
-
|
|
606
|
-
# [5.2.0] - 2026-05-21
|
|
607
|
-
|
|
608
|
-
### Added
|
|
609
|
-
|
|
610
|
-
- **Marketing consent capture (Phase A-B).** Canonical `consent.{legal,marketing}` sub-tree on every user doc (`status` + `grantedAt` / `revokedAt` with timestamp/source/ip/text). Signup route (`src/manager/routes/user/signup/post.js`) builds the record from the client payload using server-side time + IP, defending against client-clock spoofing. `mailer.sync(uid)` is gated on `consent.marketing.status === 'granted'` — opted-out signups never enter SendGrid/Beehiiv marketing lists.
|
|
611
|
-
- **Email-preferences route (Phase D).** `POST /marketing/email-preferences` now supports authenticated opt-in/opt-out (writes user doc + hits both providers via `email.sync()`/`email.remove()`) in addition to the existing HMAC anonymous unsub flow. Anonymous unsub also writes the consent revoke on the user doc with the right `source`.
|
|
612
|
-
- **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.
|
|
613
|
-
- **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.
|
|
614
|
-
- **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.
|
|
615
|
-
- **New docs.** `docs/consent.md` (consent system + webhook flows + migration template). `docs/test-framework.md` updated with the post-run-cleanup exception.
|
|
616
|
-
|
|
617
|
-
### Changed
|
|
618
|
-
|
|
619
|
-
- `src/test/runner.js`, `src/test/utils/http-client.js`, `src/cli/commands/test.js`, plus emulator/serve/watch CLIs: renamed `hostingUrl` → `apiUrl` to match the rest of the codebase. Touches most test files for the matching context-API rename.
|
|
620
|
-
- `src/manager/libraries/email/generators/newsletter.js`: uses `Manager.getParentApiUrl()` instead of reading `Manager.config.parent` directly so the `'self'` sentinel and the missing `api.` subdomain are both handled in one place.
|
|
621
|
-
- `src/manager/libraries/email/providers/beehiiv.js`: exposes `getPublicationId` so generators and tests can read it without reaching into the module.
|
|
622
|
-
- Disposable-domain blacklist refreshed (8 new domains) via `prepare-package`'s pre-hook.
|
|
623
|
-
|
|
624
|
-
### Fixed
|
|
625
|
-
|
|
626
|
-
- `mailer.sync()` and `mailer.add()` short-circuit when the target user's `consent.marketing.status === 'revoked'` so we never re-add an opted-out user.
|
|
627
|
-
- Payment processor + cancel route touchups picked up by the runner-API rename pass — no behavior change, just keeping signatures aligned.
|
|
628
|
-
|
|
629
|
-
# [5.1.4] - 2026-05-18
|
|
630
|
-
|
|
631
|
-
### Fixed
|
|
632
|
-
|
|
633
|
-
- **`POST /admin/post` response `path`** now returns the full `.md` file path (e.g. `src/_posts/2026/guest/2026-05-14-my-post.md`) instead of the parent directory. Consumers (e.g. the sponsorship system) were treating it as a file path — which it was named to be — and downstream deletes failed with `"sha" wasn't supplied` because GitHub got a directory listing. The parent directory is now exposed separately as `directory` for consumers that still want it. Updated `test/routes/admin/create-post.js` accordingly.
|
|
634
|
-
|
|
635
|
-
# [5.1.3] - 2026-05-18
|
|
636
|
-
|
|
637
|
-
### Added
|
|
638
|
-
|
|
639
|
-
- **Live `TEST_EXTENDED_MODE` sync between `npx mgr test` and the running emulator.** Test command writes an allowlisted env subset to `<projectRoot>/.temp/test-mode.json` pre-flight; emulator's function workers watch the file via `fs.watch` and mutate their own `process.env` in place — flag flips take effect within ~50ms with no env coordination across terminals. No more "restart the emulator with `TEST_EXTENDED_MODE=true`" dance. Health endpoint re-reads the file as a freshness guard. New helper `src/test/utils/test-mode-file.js` is the SSOT for the file format and allowlist.
|
|
640
|
-
- **Real BEM Manager in test contexts.** `run-tests.js` sets `BEM_TEST_RUNNER=1` before loading any BEM code; `Manager.init()` auto-detects this and skips Functions/server/Sentry wiring + `admin.initializeApp()` (which can't run outside a real Functions runtime). Result: tests receive `{ Manager, assistant }` in their context and can call `Manager.AI()`, `Manager.Email()`, `Manager.User()`, etc. exactly like production — no hand-rolled stubs.
|
|
641
|
-
- **Newsletter markdown + summary outputs.** `lib/markdown-renderer.js` walks the same `structure` JSON the HTML is rendered from (no AI cost) and emits `newsletter.md` — each section/dispatch is a standalone `## heading` block ready to paste into Beehiiv's editor one block at a time with ad blocks inserted between dispatches. A separate `summary.md` (2-3 sentence editorial recap) is written alongside.
|
|
642
|
-
- **`summary` and `tags` fields on the structure schema.** `summary` is ≤600 chars, used for the `summary.md` body and as a share snippet (distinct from preheader, which is an inbox hook). `tags` is 0-5 lowercase kebab-case topical tags, passed to Beehiiv's `content_tags` on draft creation.
|
|
643
|
-
- **GitHub asset host writes MD + summary alongside HTML.** `lib/image-host.js` accepts `markdown` + `summary` parameters and uploads `newsletter.md` / `summary.md` to `{brandId}/{campaignId}/` in the same atomic two-commit upload as PNGs + HTML. URLs surface on `assets.markdownUrl` / `assets.summaryUrl`.
|
|
644
|
-
- **Beehiiv fallback alert email.** When Beehiiv draft creation fails (e.g. free-plan `SEND_API_NOT_ENTERPRISE_PLAN`), the generator sends an internal alert via `sender: 'internal'` (alerts@{brandDomain}) to `brand.contact.email` containing the failure reason + subject/preheader/tags + direct links to HTML/MD/summary/folder. The newsletter is never stuck. Best-effort; failure to send is logged but never blocks the Firestore campaign-doc write.
|
|
645
|
-
- **Universal AI system-prompt injections.** `Manager.AI()` `normalizeOptions()` now prepends two rules (em-dash ban, confidentiality) to every system prompt — every caller picks them up automatically.
|
|
646
|
-
- **GPT-5 Codex family pricing** (`gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-max`, `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5-codex`, `codex-mini-latest`) added to the OpenAI provider's MODEL_TABLE.
|
|
647
|
-
- **`docs/common-mistakes.md`** and **`docs/key-files.md`** — extracted from CLAUDE.md to keep the architectural overview under 250 lines.
|
|
648
|
-
|
|
649
|
-
### Changed
|
|
650
|
-
|
|
651
|
-
- **SVG illustrator default flipped to `gpt-5.3-codex`.** Codex is the markup/code-specialized GPT-5 variant; SVG is structured markup, so it's the right fit. Anthropic remains supported as a fallback provider.
|
|
652
|
-
- **`structure` schema now requires `summary` and `tags`.** Existing generators pick these up automatically — the AI prompt was updated to instruct on both.
|
|
653
|
-
- **CTAs removed from generated section bodies.** The AI cannot author URLs reliably (no browse access, no real source URLs), so any link it produced was invented. Newsletters are self-contained reads; outbound links come exclusively from the template shell's sponsorship blocks (`marketing.beehiiv.content.sponsorships[]`). Test fixtures updated.
|
|
654
|
-
- **CLAUDE.md restructured** into the standard skeleton (Identity → Recommended skills → Quick Start → Architecture → CLI → File Conventions → Doc-update parity → Documentation index). Per-subsystem details extracted to `docs/`.
|
|
655
|
-
- **Consumer default CLAUDE.md** updated for the new `logs:read` / `logs:tail` / `firestore:*` / `auth:*` CLI commands.
|
|
656
|
-
- **Runner mode reporting.** The old `TEST_EXTENDED_MODE mismatch` warning is gone (made impossible by the live sync) — replaced with a "Mode: EXTENDED/normal" line sourced from the emulator's health-endpoint confirmation.
|
|
657
|
-
|
|
658
|
-
# [5.1.2] - 2026-05-14
|
|
659
|
-
|
|
660
|
-
### Changed
|
|
661
|
-
|
|
662
|
-
- **Broadened `test` / `example` local-part blocks** in `email/data/blocked-local-patterns.js`:
|
|
663
|
-
- `/^test[._-]/` → `/^test/` — now catches `testuser`, `test123abc`, etc., not only `test.user` / `test_123` / `test-foo`.
|
|
664
|
-
- Added `/^example/` — catches `example`, `exampleuser`, `example.user`, `examples`, etc.
|
|
665
|
-
- Both patterns are anchored to the start of the local part, so legitimate addresses that contain (but don't start with) those substrings are still allowed: `rachel.tester`, `contestant`, `exam` all pass.
|
|
666
|
-
|
|
667
|
-
# [5.1.1] - 2026-05-13
|
|
668
|
-
|
|
669
|
-
### Added
|
|
670
|
-
|
|
671
|
-
- **Corporate / social-media domain blacklist** — new `corporate` check in `email/validation.js` blocks marketing-list adds from corporate social-media domains (meta.com, instagram.com, soundcloud.com, tiktok.com, x.com, reddit.com, linkedin.com, youtube.com, discord.com, telegram.org, signal.org, and more — 21 domains total). Added to `DEFAULT_CHECKS` so every existing caller of `validate()` picks it up automatically. New `isCorporate(emailOrDomain)` helper exported alongside `isDisposable()`.
|
|
672
|
-
- **Defense-in-depth guards** in `Marketing.add()` and `Marketing.sync()` — even when validation is bypassed (e.g. testing mode), corporate domains are blocked before any Beehiiv or SendGrid call.
|
|
673
|
-
- **13 new tests** in `test/helpers/email-validation.js` covering the `corporate` validate-check, the `isCorporate()` helper, case-insensitivity, edge cases, and check-ordering behavior. Suite: 44 pass, 0 fail, 2 skip (ZeroBounce-only).
|
|
674
|
-
|
|
675
|
-
### Changed
|
|
676
|
-
|
|
677
|
-
- **Email data files reorganized** — all blacklists now live in `src/manager/libraries/email/data/` (one folder, next to their consumer), instead of being scattered one level up alongside unrelated libraries:
|
|
678
|
-
- `disposable-domains.json`, `custom-disposable-domains.json`, `corporate-domains.json` — domain blocklists
|
|
679
|
-
- `blocked-local-parts.json` — categorized local-part blocklist (`generic`, `system`, `junk`, `placeholder`), extracted from a hardcoded `Set` in `validation.js`
|
|
680
|
-
- `blocked-local-patterns.js` — regex patterns, extracted from `validation.js` (kept as JS so RegExp literals stay native)
|
|
681
|
-
- **`scripts/update-disposable-domains.js`** — prepare-step downloader now writes to `email/data/disposable-domains.json`.
|
|
682
|
-
|
|
683
|
-
# [5.1.0] - 2026-05-13
|
|
684
|
-
|
|
685
|
-
### Added
|
|
686
|
-
|
|
687
|
-
#### Newsletter generation system
|
|
688
|
-
- **Three production-quality templates** with distinct aesthetics, each owning its own content schema and AI prompt — switching templates produces fundamentally different content, not a recolor:
|
|
689
|
-
- `clean` — Stripe/Linear marketing aesthetic. Safe, conservative, works everywhere.
|
|
690
|
-
- `editorial` — Magazine-style: masthead, drop-cap intro, numbered sections, pull-quotes, italic signoff.
|
|
691
|
-
- `field-report` — Wire-service correspondent × Bloomberg terminal. Dispatch kickers, datelines, mono data callouts, end-of-dispatch terminators, correspondent signoff.
|
|
692
|
-
- **Schema-per-template architecture** (`lib/structure.js` is now a generic dispatcher). `BASE_SCHEMA` declares universals (subject, preheader, signoff, citations); each template extends with its own fields via `schema.properties` + `schema.required`. `clean` + `editorial` share `CLASSIC_SCHEMA` (`{intro, sections: [{title, body, cta?, image_prompt}]}`); `field-report` declares `{tldr, dateline, dispatches: [{kicker, headline, byline, location, lede, dispatch, dataPoints, cta, image_prompt}]}`.
|
|
693
|
-
- **Templates export `buildPrompt({brand, newsletterConfig, sources})`** — each template fully owns its AI brief. `clean` + `editorial` use the shared classic prompt; `field-report` has its own wire-service-correspondent voice.
|
|
694
|
-
- **Graceful omission everywhere** — every template's `build()` handles missing optional fields (returns `''` for omitted blocks instead of throwing). Empty sections/dispatches drop entirely (no hollow stubs).
|
|
695
|
-
- **Asset hosting pipeline (`lib/image-host.js`)** — `uploadAssets({ images, html, brandId, campaignId, subject })` uploads section PNGs + `newsletter.html` to `itw-creative-works/newsletter-assets/{brandId}/{campaignId}/` via Git Trees API. PNG magic-byte verification + strict path regex. Public-safety guarantees baked in.
|
|
696
|
-
|
|
697
|
-
#### Daily cron pipeline (production-ready)
|
|
698
|
-
- **`marketing-newsletter-generate.js` now runs the FULL pipeline end-to-end**: fetch sources → AI structure → AI SVG → upload PNGs to GitHub → render HTML with embedded CDN URLs → upload `newsletter.html` to the same folder → upload Beehiiv draft (fails gracefully on free plan with `SEND_API_NOT_ENTERPRISE_PLAN`) → write `marketing-campaigns/{newId}` Firestore doc with `assets: { folderUrl, htmlUrl, imageUrls, beehiivPostId, campaignId }`.
|
|
699
|
-
- **Reserved Firestore doc ID is used as the GitHub folder name** so URLs and the campaign doc always match.
|
|
700
|
-
|
|
701
|
-
#### Unified AI library
|
|
702
|
-
- **`src/manager/libraries/ai/`** — `Manager.AI(assistant).request({ provider: 'openai' | 'anthropic' | 'claude-code', ... })` dispatches to either provider with a consistent options + return shape. OpenAI is the default (back-compat); Anthropic added for SVG generation; `claude-code` provider added via `@anthropic-ai/claude-agent-sdk` for credit-less subscription-backed generation.
|
|
703
|
-
- New env var: `BACKEND_MANAGER_ANTHROPIC_API_KEY` / `ANTHROPIC_API_KEY`.
|
|
704
|
-
|
|
705
|
-
#### Iteration test as mirror of production
|
|
706
|
-
- **Fixture mode is the default** (`npx mgr test bem:marketing/newsletter-generate`) — loads `test/marketing/fixtures/<active-template>.json` and renders straight through MJML. ~35ms, $0, deterministic. Used in CI to catch layout regressions for free.
|
|
707
|
-
- **EXTENDED mode** (`TEST_EXTENDED_MODE=1`) is now a TRUE mirror of the production cron: always uploads to GitHub, always tries Beehiiv draft, no per-side-effect opt-outs.
|
|
708
|
-
- **Three fixture JSONs** shipped at `test/marketing/fixtures/{clean,editorial,field-report}.json` — one per template, each matching its template's content shape. Convention enforced: adding a new template REQUIRES a matching fixture (default test run fails with `fixture not found` otherwise).
|
|
709
|
-
- **17-test fixture suite** (`test/marketing/newsletter-templates.js`) covers graceful omission, empty-section drop, CTA conditional rendering, citation rendering, sponsorship rendering, button padding regression guard, long subjects, minimum-viable structures, template metadata, schema export contract.
|
|
710
|
-
|
|
711
|
-
#### Other additions
|
|
712
|
-
- **`Manager.Utilities().slugify()`** — canonical URL slug builder. Strips non-alphanumeric chars, collapses hyphens, trims, lowercases. Single source of truth shared by admin/post + any consumer that needs to predict slugs (e.g. sponsorship platform).
|
|
713
|
-
- **`routes/admin/post/deduplicate-image-alts.js`** — utility that suffixes alt-text when two images share alt-text but have different URLs (prevents filename collisions on upload). Wired into both `routes/admin/post/post.js` and `actions/api/admin/create-post.js`. Unit-tested.
|
|
714
|
-
- New env vars in `templates/_.env`: `BACKEND_MANAGER_ANTHROPIC_API_KEY`, `ANTHROPIC_API_KEY`.
|
|
715
|
-
- `templates/_.gitignore` now includes `.temp/` in the BEM defaults section.
|
|
716
|
-
|
|
717
|
-
#### Documentation split
|
|
718
|
-
- **CLAUDE.md reduced from ~1500 lines to a navigable table of contents.**
|
|
719
|
-
- **21 new `docs/*.md` deep references**: architecture, directory-structure, code-patterns, file-naming, environment-detection, response-headers, routes, schemas, sanitization, auth-hooks, common-operations, admin-post-route, payment-system, marketing-campaigns, mcp, usage-rate-limiting, ai-library, marketing-fields, stripe-webhook-forwarding, testing, cli-firestore-auth, cli-logs.
|
|
720
|
-
|
|
721
|
-
### Changed
|
|
722
|
-
|
|
723
|
-
- **Config restructuring**: `marketing.newsletter` → `marketing.beehiiv.content`. Nesting under the provider that publishes the result makes the coupling explicit and leaves room for future `marketing.sendgrid.content` for promo email blasts. `marketing.beehiiv.enabled` now gates the whole content pipeline (no separate `newsletter.enabled` flag — disabling beehiiv disables newsletter generation as a side effect, since there's nowhere for the generated content to land).
|
|
724
|
-
- **Marketing library** (`libraries/email/marketing/index.js`) — now prefers `settings.contentHtml` over running the markdown pipeline. Lets generators produce pre-rendered HTML directly. UTM tagging still runs on the chosen HTML either way.
|
|
725
|
-
- **`Manager.AI()`** now points to the unified `libraries/ai/index.js`. `libraries/openai.js` is a thin compatibility shim that re-exports `libraries/ai/providers/openai.js`.
|
|
726
|
-
- **Footer no longer renders a hand-rolled `${brandUrl}/unsubscribe` link** — Beehiiv and SendGrid both auto-append CAN-SPAM-compliant unsubscribe links on sent emails. The hand-rolled one was a dead second link. Fixture suite now has a regression guard preventing accidental re-adds.
|
|
727
|
-
- **Beehiiv provider** (`libraries/email/providers/beehiiv.js`) — `createPost()` accepts an explicit `publicationId` arg (bypasses singleton-Manager lookup); `getPublicationId()` null-guards an uninitialized Manager singleton (matters for test stubs).
|
|
728
|
-
- **`admin/post.js` + `actions/api/admin/create-post.js`** — replaced inline slugify implementations with calls to the new `Manager.Utilities().slugify()`. Wired alt-text dedup helper into the post-creation flow.
|
|
729
|
-
- **`.temp/` moved** from `functions/.temp/` to project root (matches UJM/BXM/electron-manager convention).
|
|
730
|
-
- **`src/defaults/CLAUDE.md`** — strengthened framework-docs callout for consumer projects; clarified the Default/Custom marker boundary.
|
|
731
|
-
|
|
732
|
-
### Removed
|
|
733
|
-
|
|
734
|
-
- **`marketing.newsletter.provider/sectionStyle`** — `provider` defaults now live in code (openai for structure, anthropic for SVG), overridable per-run via env vars. `sectionStyle` was a free-form hint string the AI ignored.
|
|
735
|
-
- **`marketing.beehiiv.uploadAs`** — vestigial config from the abandoned Beehiiv-send-path detour.
|
|
736
|
-
- **`NEWSLETTER_GITHUB_UPLOAD` env flag** — redundant. EXTENDED mode now always uploads (production-equivalent run).
|
|
737
|
-
- **Footer's hand-rolled unsubscribe link** (see Changed).
|
|
738
|
-
|
|
739
|
-
# [5.0.203] - 2026-05-13
|
|
740
|
-
### Fixed
|
|
741
|
-
- `Settings.resolve()` now surfaces a clear `No schema for <METHOD> request: expected <schema>/<method>.js or <schema>/index.js` error (code 500) when both the method-specific schema (e.g. `delete.js`) and the `index.js` fallback are absent. Previously the raw Node `Cannot find module .../<schema>/index.js` error propagated to consumers, leaking the require stack and surfacing the internal `/workspace/...` deploy path.
|
|
742
|
-
- Schema files that exist but throw (syntax error, runtime error) are now re-thrown directly instead of being silently masked by an unintended fallback to `index.js`. The real error surfaces, making bugs in schema files debuggable.
|
|
743
|
-
|
|
744
|
-
# [5.0.202] - 2026-05-12
|
|
745
|
-
### Added
|
|
746
|
-
- **`src/defaults/CLAUDE.md`** — new file shipped to consumer projects, marker-wrapped with `# ========== Default Values ==========` / `# ========== Custom Values ==========`. Framework section stays live-synced across `npx mgr setup` while the Custom section is preserved verbatim. Aligns BEM with EM/BXM/UJM, which already ship a consumer-facing default CLAUDE.md.
|
|
747
|
-
- **`src/utils/merge-line-files.js`** — new file. Implements the line-based merge for the marker-wrapped Default/Custom sections (copied verbatim from `electron-manager`'s utility). Reusable across `.env`, `.gitignore`, `CLAUDE.md`, and any other line-based files BEM might ship in the future.
|
|
748
|
-
- **`copyDefaults()` method** in `src/cli/commands/setup.js`. New defaults-shipping mechanism for BEM (which previously had no `src/defaults/` directory at all). Mirrors EM's `copyDefaults` pattern: iterates `src/defaults/**`, renames `_.foo` → `.foo`, routes files matching `MERGEABLE_BASENAMES` (`['.env', '.gitignore', 'CLAUDE.md']`) through `mergeLineBasedFiles`, copies non-mergeable files only if they don't already exist. Wired into `runSetup()` BEFORE `runTests()` so test inspections see the merged state.
|
|
749
|
-
|
|
750
|
-
# [5.0.201] - 2026-05-08
|
|
751
|
-
### Changed
|
|
752
|
-
- Account deletion confirmation email now includes a "Deletion details:" block with the user's account email, UID, and deletion timestamp (UTC) — matching the pattern used in the data-request download email.
|
|
753
|
-
|
|
754
|
-
# [5.0.200] - 2026-05-05
|
|
755
|
-
### Changed
|
|
756
|
-
- Bumped `uuid` from `^13.0.2` to `^14.0.0`. uuid v14 is ESM-only, but Node 22+'s native `require(esm)` support means existing CommonJS call sites (`require('uuid').v4`, `.v5`, etc.) work unchanged.
|
|
757
|
-
|
|
758
|
-
# [5.0.199] - 2026-04-23
|
|
759
|
-
### Fixed
|
|
760
|
-
- Storage helper (`Manager.storage().get()` / `.set()`) was referencing `_.get` and `_.set` without a lodash namespace import — lodash is destructured at the top of the file, so `_` was undefined and every call crashed. Destructured `get` and `set` (aliased as `_get` / `_set`) and updated both call sites.
|
|
761
|
-
|
|
762
|
-
# [5.0.198] - 2026-04-10
|
|
763
|
-
### Security
|
|
764
|
-
- Added Stripe idempotency keys on all Stripe write operations to prevent duplicate charges, refunds, customers, and coupons from webhook retries, concurrent requests, or user double-clicks. Keys are scoped to stable resource identifiers and Stripe caches responses for 24 hours.
|
|
765
|
-
- `bem-dispute-refund-{chargeId}` on auto-refunds from dispute alert Firestore triggers
|
|
766
|
-
- `bem-customer-create-{uid}` on Stripe customer creation
|
|
767
|
-
- `bem-coupon-{couponId}` on coupon creation in the intent route
|
|
768
|
-
- `bem-refund-{resourceId}` on manual subscription refunds
|
|
769
|
-
|
|
770
|
-
# [5.0.197] - 2026-04-10
|
|
771
|
-
### Added
|
|
772
|
-
- `--retry=N` flag on `npx mgr setup` — re-runs the full setup sequence up to N times, stopping early as soon as all checks pass. Useful for test cases that only succeed after a prior run creates fixtures or indexes propagate.
|
|
773
|
-
|
|
774
|
-
# [5.0.196] - 2026-04-10
|
|
775
|
-
### Changed
|
|
776
|
-
- Moved disposable domain fetch from `prepublishOnly` lifecycle hook to `prepare-package`'s new `hooks.before` config. The fetch now runs on every `npm run prepare` / `npm install` / `npm publish`, so fresh domains land in both the git working tree and the published tarball — no more drift between git and npm.
|
|
777
|
-
- Bumped `prepare-package` devDep to ^2.1.0 (required for hooks support)
|
|
778
|
-
|
|
779
|
-
# [5.0.195] - 2026-04-10
|
|
780
|
-
### Fixed
|
|
781
|
-
- 24-hour cancellation guard in `payments/cancel` was comparing `Date.now()` (milliseconds) against `startDateUNIX` (seconds), producing an "age" of ~56 years for every subscription — guard never fired and users could cancel brand-new subscriptions. Now multiplies `startDateUNIX` by 1000 before subtraction.
|
|
782
|
-
### Changed
|
|
783
|
-
- Standardized CLI examples in `CLAUDE.md` and `README.md` to use `npx mgr` instead of the deprecated `npx bm` alias
|
|
784
|
-
|
|
785
|
-
# [5.0.194] - 2026-04-08
|
|
786
|
-
### Fixed
|
|
787
|
-
- Fix email template data merge: caller's `settings.data` is now deep-merged at root of template data tree, removing the broken `data.` prefix indirection that caused empty order confirmation emails since 5.0.185
|
|
788
|
-
### Added
|
|
789
|
-
- `preview` as a top-level setting on `email.send()` (alongside `subject`)
|
|
790
|
-
- `logs:read` CLI: `--search`, `--order`, `--filter` flags and increased default limit to 300
|
|
791
|
-
### Changed
|
|
792
|
-
- Email templates now access caller data at root (`{{order.id}}`, `{{body.message}}`) instead of under `data.*`
|
|
793
|
-
|
|
794
|
-
# [5.0.192] - 2026-04-02
|
|
795
|
-
### Added
|
|
796
|
-
- Setup test to create `hooks/auth/` and `hooks/cron/daily/` directories in consumer projects during `npx bm setup`
|
|
797
|
-
|
|
798
|
-
# [5.0.186] - 2026-04-01
|
|
799
|
-
### Fixed
|
|
800
|
-
- Move markdown rendering and UTM link tagging to run after `_.merge()` so caller overrides to `body.message` and `email.body` are properly processed
|
|
801
|
-
|
|
802
|
-
# [5.0.185] - 2026-04-01
|
|
803
|
-
### Changed
|
|
804
|
-
- Use `_.merge` for dynamic template data so callers can override any nested field (e.g. `email.preview`, `personalization.name`, `data.body.*`)
|
|
805
|
-
- Set email schema `template` default to `'default'` instead of `undefined`
|
|
806
|
-
|
|
807
|
-
# [5.0.184] - 2026-03-31
|
|
808
|
-
### Changed
|
|
809
|
-
- Renamed email template shortcuts from `main/` to `core/` prefix across constants and all consumer files
|
|
810
|
-
- Added new templates: `core/plain` and `core/marketing/promotional`
|
|
811
|
-
|
|
812
|
-
# [5.0.177] - 2026-03-29
|
|
813
|
-
### Changed
|
|
814
|
-
- `payment-recovered` transition now sends email to internal team only — customer no longer receives a "Payment received" notification
|
|
815
|
-
|
|
816
|
-
# [5.0.176] - 2026-03-30
|
|
817
|
-
### Fixed
|
|
818
|
-
- Chargeblast `alert.created` events use `alertId` instead of `id` — normalizer now accepts either field
|
|
819
|
-
- Dispute charge matching now uses `charges.search()` instead of invoice search, fixing cases where Stripe invoices had `charge: null` even when paid (via balance/credit). Single reliable strategy: amount + ±2 day date window + card last4
|
|
820
|
-
### Changed
|
|
821
|
-
- Dispute `on-write` trigger is now processor-agnostic — Stripe-specific match/refund logic extracted to `processors/stripe.js`, matching the pattern used by payments-webhooks
|
|
822
|
-
|
|
823
|
-
# [5.0.174] - 2026-03-27
|
|
824
|
-
### Fixed
|
|
825
|
-
- Payments-orders `metadata.created` timestamp no longer overwritten on subsequent webhook events (renewals, cancellations, payment failures)
|
|
826
|
-
|
|
827
|
-
# [5.0.168] - 2026-03-21
|
|
828
|
-
### Fixed
|
|
829
|
-
- Immediately suspend subscription on payment denial (PAYMENT.SALE.DENIED, invoice.payment_failed) instead of waiting for the processor to give up retrying — recovery via PAYMENT.SALE.COMPLETED restores active status
|
|
830
|
-
|
|
831
|
-
# [5.0.167] - 2026-03-20
|
|
832
|
-
### Changed
|
|
833
|
-
- Extracted `resolveTemperature()` helper for consistency with `resolveFormatting()` and `resolveReasoning()`
|
|
834
|
-
|
|
835
|
-
# [5.0.166] - 2026-03-20
|
|
836
|
-
### Added
|
|
837
|
-
- `reasoning: true` feature flag to GPT-5.x and o-series models in MODEL_TABLE
|
|
838
|
-
- New GPT-5.4-mini and GPT-5.4-nano model entries with pricing
|
|
839
|
-
|
|
840
|
-
### Changed
|
|
841
|
-
- Reasoning parameter is now conditionally included in API requests only when the model supports it
|
|
842
|
-
- `resolveReasoning()` validates model support and warns when reasoning is requested for unsupported models
|
|
843
|
-
|
|
844
|
-
# [5.0.165] - 2026-03-20
|
|
845
|
-
### Changed
|
|
846
|
-
- Serve command now reads hosting port from `firebase.json` emulator config before falling back to default 5000
|
|
847
|
-
- Notification test fixtures migrated from flat `createdAt`/`updatedAt` to nested `metadata.created`/`metadata.updated` objects matching standard BEM metadata format
|
|
848
|
-
|
|
849
|
-
# [5.0.164] - 2026-03-18
|
|
850
|
-
### Added
|
|
851
|
-
- Default field backfill in campaign seed setup — missing fields are restored from seed defaults without overwriting user edits
|
|
852
|
-
|
|
853
|
-
# [5.0.163] - 2026-03-18
|
|
854
|
-
### Changed
|
|
855
|
-
- Refactored campaign POST/PUT routes to generic field passthrough — schema-validated fields flow through automatically via shared `buildCampaignDoc()` utility, no manual field assignments needed
|
|
856
|
-
- Extracted `normalizeSendAt()` and `DOC_LEVEL_FIELDS` into `routes/marketing/campaign/utils.js`
|
|
857
|
-
|
|
858
|
-
# [5.0.161] - 2026-03-18
|
|
859
|
-
### Added
|
|
860
|
-
- Port conflict detection in `serve` command — checks and kills blocking processes before starting Firebase server
|
|
861
|
-
|
|
862
|
-
### Changed
|
|
863
|
-
- Unblocked common team/role email local parts (`user`, `email`, `mail`, `hello`, `info`, `admin`, `support`, `contact`) from validation blocklist, as these are legitimate addresses
|
|
864
|
-
|
|
865
|
-
# [5.0.160] - 2026-03-18
|
|
866
|
-
### Added
|
|
867
|
-
- Beehiiv `resolveSegmentIds()` — fetches segments from API, builds name→ID cache (same pattern as SendGrid)
|
|
868
|
-
- Beehiiv segment resolution in `sendCampaign()` — SSOT keys auto-translate to Beehiiv segment IDs
|
|
869
|
-
|
|
870
|
-
### Changed
|
|
871
|
-
- Beehiiv `createPost()` now receives resolved segment IDs instead of raw SSOT keys
|
|
872
|
-
|
|
873
|
-
# [5.0.159] - 2026-03-18
|
|
874
|
-
### Added
|
|
875
|
-
- Audience-specific email discount codes: `UPGRADE15`, `COMEBACK20`, `MISSYOU25`, `TRYAGAIN10` with eligibility validation per user
|
|
876
|
-
- `{discount.code}` and `{discount.percent}` campaign template variables
|
|
877
|
-
- `test: true` flag on campaign route — sends real Single Send to `test_admin` segment only
|
|
878
|
-
- `test_admin` segment in SSOT (targets `hello@itwcreativeworks.com`)
|
|
879
|
-
- `trial_claimed` custom field (`user_subscription_trial_claimed`) for marketing sync
|
|
880
|
-
- `subscription_churned_paid` and `subscription_churned_trial` segments (replaces `subscription_churned`)
|
|
881
|
-
- 4 audience-specific recurring sale seed campaigns with tailored messaging + discount codes
|
|
882
|
-
- Full marketing campaign system documentation in CLAUDE.md, README.md, and BEM:patterns skill
|
|
883
|
-
|
|
884
|
-
### Changed
|
|
885
|
-
- Template variable resolution now recursive — walks all string values in settings (future-proof)
|
|
886
|
-
- UTM values resolved through template vars (`{holiday.name}_sale` → `black_friday_sale`)
|
|
887
|
-
- UTM sanitizer strips apostrophes before underscore conversion
|
|
888
|
-
- Payment intent + discount routes now pass user object for discount eligibility checking
|
|
889
|
-
- Discount code `validate()` accepts optional user param for eligibility checks (backwards compatible)
|
|
890
|
-
|
|
891
|
-
# [5.0.158] - 2026-03-17
|
|
892
|
-
### Added
|
|
893
|
-
- Newsletter generator system (`libraries/email/generators/newsletter.js`) — fetches sources from parent server, AI assembles branded content with subject/preheader
|
|
894
|
-
- Daily pre-generation cron (`cron/daily/marketing-newsletter-generate.js`) — generates newsletter content 24 hours before sendAt for calendar review
|
|
895
|
-
- `marketing.newsletter.enabled` and `marketing.newsletter.categories` config options
|
|
896
|
-
- `generator` field on campaign docs — tells cron to run content generation instead of sending directly
|
|
897
|
-
|
|
898
|
-
### Changed
|
|
899
|
-
- Seed campaign IDs are now timing-agnostic: `_recurring-sale`, `_recurring-newsletter`
|
|
900
|
-
- Recurrence timing removed from enforced fields — consuming projects can freely change schedule
|
|
901
|
-
- Newsletter subject/preheader are now AI-generated (empty in seed template)
|
|
902
|
-
- Frequent cron skips generator campaigns (handled by daily pre-generation cron)
|
|
903
|
-
- Admin cron route now passes `libraries` to cron handlers
|
|
904
|
-
|
|
905
|
-
# [5.0.157] - 2026-03-17
|
|
906
|
-
### Added
|
|
907
|
-
- Campaign template variables via `powertools.template()` — `{brand.name}`, `{season.name}`, `{holiday.name}`, `{date.month}`, `{date.year}`, `{date.full}`
|
|
908
|
-
- Separate SEASONS (Winter/Spring/Summer/Fall) and HOLIDAYS (New Year, Valentine's Day, Black Friday, Christmas, etc.) maps
|
|
909
|
-
- Audit logging in `getSegmentContacts()` — logs export start, poll status, download count, timeout
|
|
910
|
-
|
|
911
|
-
### Changed
|
|
912
|
-
- Seed sale campaign: quarterly → monthly on 15th, uses `{holiday.name}` template vars, targets free + cancelled + churned users, excludes paid
|
|
913
|
-
- Prune cron calls segment export with 3-minute timeout for large segments
|
|
914
|
-
|
|
915
|
-
### Fixed
|
|
916
|
-
- S3 presigned URL download broken by wonderful-fetch cache buster — set `cacheBreaker: false`
|
|
917
|
-
- CSV header parsing: normalize to lowercase for case-insensitive column matching
|
|
918
|
-
|
|
919
|
-
# [5.0.156] - 2026-03-17
|
|
920
|
-
### Added
|
|
921
|
-
- Marketing campaign system with full CRUD routes (`POST/GET/PUT/DELETE /marketing/campaign`)
|
|
922
|
-
- Calendar-backed scheduling: campaigns stored in `marketing-campaigns` Firestore collection, picked up by `bm_cronFrequent`
|
|
923
|
-
- Multi-provider campaign dispatch: SendGrid (Single Send) + Beehiiv (Post) + Push (FCM)
|
|
924
|
-
- Recurring campaigns with `recurrence` field — cron creates history docs and advances `sendAt`
|
|
925
|
-
- Markdown → HTML conversion at send time for campaign content
|
|
926
|
-
- UTM auto-tagging on brand domain links for both marketing and transactional emails (`libraries/email/utm.js`)
|
|
927
|
-
- Shared notification library (`libraries/notification.js`) extracted from admin route
|
|
928
|
-
- SEGMENTS SSOT dictionary in `constants.js` — 22 segments (subscription, lifecycle, engagement)
|
|
929
|
-
- Runtime segment ID resolution: `resolveSegmentIds()` maps SSOT keys to SendGrid segment IDs
|
|
930
|
-
- Contact pruning cron (`cron/daily/marketing-prune.js`) — monthly re-engagement + deletion of inactive contacts
|
|
931
|
-
- SendGrid `getSegmentContacts()` and `bulkDeleteContacts()` for segment export + batch deletion
|
|
932
|
-
- Seed campaigns via `npx bm setup`: `_recurring-quarterly-sale` (SendGrid) and `_recurring-weekly-newsletter` (Beehiiv) with enforced fields
|
|
933
|
-
- `marketing.prune.enabled` config option (default: true)
|
|
934
|
-
- Provider name extraction from OAuth on signup (Google, Facebook, etc.)
|
|
935
|
-
- Personalized greetings in welcome, checkup, deletion, and data request emails
|
|
936
|
-
|
|
937
|
-
### Changed
|
|
938
|
-
- `sendCampaign()` refactored for multi-provider dispatch with automatic SSOT segment key → provider ID translation
|
|
939
|
-
- `POST /admin/notification` slimmed down to use shared notification library
|
|
940
|
-
- Setup test data files (`required-indexes.js`, `seed-campaigns.js`) moved to `helpers/` directory
|
|
941
|
-
|
|
942
|
-
# [5.0.155] - 2026-03-16
|
|
943
|
-
### Added
|
|
944
|
-
- Setup test now ensures consuming project `functions/package.json` has `"private": true` to prevent accidental npm publish
|
|
945
|
-
|
|
946
|
-
# [5.0.154] - 2026-03-16
|
|
947
|
-
### Changed
|
|
948
|
-
- Add `display` property to all marketing FIELDS entries so display names are defined in the SSOT
|
|
949
|
-
- Beehiiv provider now maps fields to display names instead of raw keys
|
|
950
|
-
- Add `skip` flag for per-provider field creation control (e.g., SendGrid has first/last name built-in)
|
|
951
|
-
|
|
952
|
-
### Added
|
|
953
|
-
- `user_personal_name_first` and `user_personal_name_last` fields to FIELDS dictionary (skipped for SendGrid which has them built-in)
|
|
954
|
-
|
|
955
|
-
# [5.0.152] - 2026-03-16
|
|
956
|
-
### Fixed
|
|
957
|
-
- Email queue documents all stored at `emails-queue/NaN` — `powertools.random()` doesn't support string generation, replaced with `pushid()`
|
|
958
|
-
|
|
959
|
-
# [5.0.151] - 2026-03-16
|
|
960
|
-
### Fixed
|
|
961
|
-
- AI contact inference was silently broken — `ai.request()` returns `{content, tokens, ...}` but code read `result.firstName` instead of `result.content.firstName`, so AI was never used
|
|
962
|
-
- OpenAI API key not passed to AI library — now explicitly passes `BACKEND_MANAGER_OPENAI_API_KEY`
|
|
963
|
-
|
|
964
|
-
### Added
|
|
965
|
-
- `POST /admin/infer-contact` route for testing/debugging contact inference (admin-only, supports batch)
|
|
966
|
-
- `user_personal_company` custom field in FIELDS constant for marketing provider sync
|
|
967
|
-
- Company passthrough in `Marketing.add()` → SendGrid and Beehiiv providers
|
|
968
|
-
- Test suite for admin/infer-contact route
|
|
969
|
-
- Standalone test script (`scripts/test-infer-contact.js`)
|
|
970
|
-
|
|
971
|
-
### Changed
|
|
972
|
-
- Improved AI prompt: rejects placeholders/gibberish, always infers company from domain, preserves hyphenated name capitalization
|
|
973
|
-
- Disabled regex fallback — returns empty when AI can't infer a real name
|
|
974
|
-
- All 3 inferContact callsites (marketing/contact, user/signup, legacy add-marketing-contact) now extract and pass company
|
|
975
|
-
|
|
976
|
-
# [5.0.150] - 2026-03-16
|
|
977
|
-
### Added
|
|
978
|
-
- `marketing` config section in `backend-manager-config.json` — per-brand control over SendGrid and Beehiiv provider availability
|
|
979
|
-
- Beehiiv provider reads `publicationId` from config (skips fuzzy-match API call) with in-memory cache
|
|
980
|
-
|
|
981
|
-
### Changed
|
|
982
|
-
- Provider availability resolved once in Marketing constructor from `config.marketing` + env vars instead of per-request
|
|
983
|
-
- Removed `providers` parameter from `add()`, `sync()`, `remove()` and all route/schema callers
|
|
984
|
-
|
|
985
|
-
### Removed
|
|
986
|
-
- `DEFAULT_PROVIDERS` constant — no longer needed with config-driven provider resolution
|
|
987
|
-
- Provider-selection tests — no longer applicable
|
|
988
|
-
|
|
989
|
-
# [5.0.149] - 2026-03-14
|
|
990
|
-
### Added
|
|
991
|
-
- Modular email library (`libraries/email/`) — replaces monolithic `libraries/email.js` with provider-based architecture
|
|
992
|
-
- Marketing contact providers: SendGrid (`providers/sendgrid.js`) and Beehiiv (`providers/beehiiv.js`) with add/remove/sync operations
|
|
993
|
-
- Email validation library (`libraries/email/validation.js`) — format, local-part, and disposable domain checks with configurable check selection
|
|
994
|
-
- Runtime SendGrid custom field ID resolution — fetches field definitions from API and caches name→ID mapping (no hardcoded IDs)
|
|
995
|
-
- 15 marketing custom fields synced to SendGrid/Beehiiv: brand, auth, subscription, payment, and attribution data
|
|
996
|
-
- `PUT /marketing/contact` admin route for triggering contact sync by UID
|
|
997
|
-
- `POST /marketing/contact` now syncs full custom field data on signup
|
|
998
|
-
- Marketing contact sync in payment webhook pipeline — subscription changes automatically update SendGrid/Beehiiv custom fields
|
|
999
|
-
- `mailer.sync(uid)` method for full contact re-sync from Firestore user doc
|
|
1000
|
-
- `resolveFieldValues()` in `constants.js` — SSOT for building custom field payloads from user docs
|
|
1001
|
-
- `User.resolveSubscription()` now includes `everPaid` field for marketing segmentation
|
|
1002
|
-
- `TEST_EXTENDED_MODE` propagation from emulator to Firebase function workers
|
|
1003
|
-
- `TEST_EXTENDED_MODE` mismatch detection between test runner and emulator via health check
|
|
1004
|
-
- Email queue cron processor (`cron/frequent/email-queue.js`) — processes deferred emails every 10 minutes via the full `email.send()` pipeline
|
|
1005
|
-
- Feedback route review URL builder with full site URLs
|
|
1006
|
-
- 28 email validation unit tests, 7 marketing contact route tests, 5 marketing lifecycle integration tests
|
|
1007
|
-
|
|
1008
|
-
### Changed
|
|
1009
|
-
- Refactored `libraries/email.js` into modular `libraries/email/` directory (index, constants, validation, providers)
|
|
1010
|
-
- `POST /marketing/contact` validation now uses configurable check selection instead of boolean `skipValidation`
|
|
1011
|
-
- `DELETE /marketing/contact` uses new provider-based removal
|
|
1012
|
-
- Marketing contact schemas updated to match new validation options
|
|
1013
|
-
- `on-delete` auth event now uses new email library for contact removal
|
|
1014
|
-
- `saveToEmailQueue` now stores raw settings instead of pre-built SendGrid email, so queued emails re-enter the full build pipeline
|
|
1015
|
-
- Renamed `email-queue` collection to `emails-queue`
|
|
1016
|
-
- Feedback schema: renamed `like`/`dislike` fields to `positive`/`negative`
|
|
1017
|
-
- Feedback review prompt logic now checks total positive feedback length (50+ chars)
|
|
1018
|
-
- Renamed `GET /app` route to `GET /brand` (completes app→brand migration)
|
|
1019
|
-
|
|
1020
|
-
### Removed
|
|
1021
|
-
- Monolithic `libraries/email.js` — replaced by modular `libraries/email/` directory
|
|
1022
|
-
|
|
1023
|
-
# [5.0.148] - 2026-03-14
|
|
1024
|
-
### Added
|
|
1025
|
-
- Semantic email sender system — pass `sender: 'orders'` to `Email.send()` to auto-resolve from address, display name, and SendGrid ASM group
|
|
1026
|
-
- 7 sender categories: `orders`, `hello`, `account`, `marketing`, `security`, `newsletter`, `internal`
|
|
1027
|
-
- 7 dedicated SendGrid ASM groups for granular unsubscribe control
|
|
1028
|
-
- 4 new email tests for sender resolution, override precedence, and fallback behavior
|
|
1029
|
-
|
|
1030
|
-
### Changed
|
|
1031
|
-
- Migrated all email call sites from `group:` to `sender:` parameter
|
|
1032
|
-
- `sendOrderEmail()` now accepts optional `sender` parameter (defaults to `'orders'`)
|
|
1033
|
-
- `replyTo` now defaults to the resolved from address instead of brand default
|
|
1034
|
-
|
|
1035
|
-
# [5.0.147] - 2026-03-14
|
|
1036
|
-
### Added
|
|
1037
|
-
- 24-hour cancellation guard on `POST /payments/cancel` — blocks cancellations for subscriptions younger than 24 hours
|
|
1038
|
-
|
|
1039
|
-
# [5.0.146] - 2026-03-13
|
|
1040
|
-
### Added
|
|
1041
|
-
- Promo discount support in payment analytics — `resolveActualValue()` computes effective price accounting for trials ($0) and percentage discounts
|
|
1042
|
-
- Promo discount details (code, percent, savings, totalToday) in new-subscription order confirmation emails
|
|
1043
|
-
|
|
1044
|
-
### Changed
|
|
1045
|
-
- `trackPayment()` and `resolvePaymentEvent()` now accept `order` parameter to access discount data
|
|
1046
|
-
|
|
1047
|
-
# [5.0.144] - 2026-03-13
|
|
1048
|
-
### Added
|
|
1049
|
-
- `User.resolveSubscription()` static method that derives calculated subscription fields (plan, active, trialing, cancelling) from raw user data
|
|
1050
|
-
|
|
1051
|
-
# [5.0.143] - 2026-03-13
|
|
1052
|
-
### Changed
|
|
1053
|
-
- `sendOrderEmail()` now accepts a `copy` parameter to control whether admin receives a copy (defaults to `true` for backward compat)
|
|
1054
|
-
- Abandoned cart reminder emails no longer send admin copies (`copy: false`) to reduce inbox noise
|
|
1055
|
-
|
|
1056
|
-
# [5.0.140] - 2026-03-12
|
|
1057
|
-
### Fixed
|
|
1058
|
-
- Chargebee meta_data backfill not including `orderId`, causing `getOrderId()` to fail on future webhooks
|
|
1059
|
-
- `orderId` resolution now falls back to `pass_thru_content` orderId when `getOrderId()` returns null
|
|
1060
|
-
|
|
1061
|
-
### Changed
|
|
1062
|
-
- `setMetaData()` API simplified to accept `(resource, meta)` instead of individual params, writing the full meta object to both subscription and customer
|
|
1063
|
-
|
|
1064
|
-
# [5.0.139] - 2026-03-12
|
|
1065
|
-
### Fixed
|
|
1066
|
-
- Chargebee hosted page checkout failing to resolve UID from webhooks because `subscription[meta_data]` is not supported by Chargebee's hosted page API
|
|
1067
|
-
- Webhook pipeline now falls back to resolving UID from hosted page `pass_thru_content` when meta_data is missing
|
|
1068
|
-
|
|
1069
|
-
### Added
|
|
1070
|
-
- `resolveUidFromHostedPage()` in Chargebee library to search recent hosted pages by subscription ID and extract UID from `pass_thru_content`
|
|
1071
|
-
- `setMetaData()` in Chargebee library to backfill meta_data on subscriptions and customers after first UID resolution
|
|
1072
|
-
- Automatic meta_data backfill on subscription + customer after resolving UID from pass_thru_content, so future webhooks resolve directly
|
|
1073
|
-
|
|
1074
|
-
### Changed
|
|
1075
|
-
- Chargebee intent now uses `pass_thru_content` instead of `subscription.meta_data` to carry UID/orderId through checkout
|
|
1076
|
-
|
|
1077
|
-
# [5.0.132] - 2026-03-13
|
|
1078
|
-
### Fixed
|
|
1079
|
-
- Abandoned cart cron crashing with `FAILED_PRECONDITION` due to missing `payments-carts` composite index (status + nextReminderAt)
|
|
1080
|
-
- Abandoned cart email subject and template using raw `productId` instead of resolved `productName` and `brandName`
|
|
1081
|
-
|
|
1082
|
-
### Changed
|
|
1083
|
-
- Index sync (`npx bm setup`) now auto-merges local and live indexes instead of prompting to choose one direction
|
|
1084
|
-
- Added `payments-carts` composite index to `required-indexes.js`
|
|
1085
|
-
|
|
1086
|
-
# [5.0.131] - 2026-03-11
|
|
1087
|
-
### Changed
|
|
1088
|
-
- Analytics config restructured: consolidated `googleAnalytics`, `meta`, and `tiktok` under unified `analytics.providers` namespace with `google`, `meta`, and `tiktok` keys
|
|
1089
|
-
- Google Analytics secret moved from config (`googleAnalytics.secret`) to env var (`GOOGLE_ANALYTICS_SECRET`)
|
|
1090
|
-
- Meta pixel ID read from `analytics.providers.meta.id` instead of `meta.pixelId`
|
|
1091
|
-
- TikTok pixel code read from `analytics.providers.tiktok.id` instead of `tiktok.pixelCode`
|
|
1092
|
-
- Flattened `owner` field from `{ uid: string }` to plain UID string in feedback docs, notifications, and `getDocumentWithOwnerUser()` default path
|
|
1093
|
-
- Moved `created` timestamp inside `metadata` object in feedback documents
|
|
1094
|
-
- Added `GOOGLE_ANALYTICS_SECRET`, `META_ACCESS_TOKEN`, `TIKTOK_ACCESS_TOKEN` to `.env` template
|
|
1095
|
-
- Bumped version to 5.0.131
|
|
1096
|
-
|
|
1097
|
-
# [5.0.129] - 2026-03-11
|
|
1098
|
-
### Added
|
|
1099
|
-
- Usage proxy system: `setUser()` to bill usage to a different user, `addMirror()`/`setMirrors()` to write usage to additional Firestore docs in parallel
|
|
1100
|
-
- Admin post route image rewriting: `extractImages()` now returns a URL map and rewrites markdown body to use `@post/` prefix for uploaded images
|
|
1101
|
-
- `metadata` object on user schema with `created` and `updated` timestamps
|
|
1102
|
-
- Firestore security rules: added `metadata` to server-only write fields
|
|
1103
|
-
- Test for admin post creation route
|
|
1104
|
-
- CLAUDE.md and README.md documentation for usage proxy and admin post route
|
|
1105
|
-
|
|
1106
|
-
### Changed
|
|
1107
|
-
- User schema: moved `activity.lastActivity` to `metadata.updated` and `activity.created` to `metadata.created`
|
|
1108
|
-
- `before-signin` event handler writes to `metadata.updated` instead of `activity.lastActivity`
|
|
1109
|
-
- Admin user sync route writes to `metadata.created`/`metadata.updated` instead of `activity.created`/`activity.lastActivity`
|
|
1110
|
-
- `Usage.update()` refactored to execute primary + mirror writes in parallel via `Promise.all()`
|
|
1111
|
-
- Bumped version to 5.0.129
|
|
1112
|
-
|
|
1113
|
-
# [5.0.123] - 2026-03-10
|
|
1114
|
-
### Added
|
|
1115
|
-
- Dispute alert system: `POST /payments/dispute-alert` endpoint with Chargeblast processor for ingesting payment dispute webhooks
|
|
1116
|
-
- Firestore trigger (`payments-disputes/{alertId}`) that matches disputes to Stripe invoices by date/amount/card, auto-refunds, and cancels subscriptions
|
|
1117
|
-
- Discount code system: `GET /payments/discount` validation endpoint and `discount-codes.js` library (FLASH20, SAVE10, WELCOME15)
|
|
1118
|
-
- Discount code integration in payment intent flow — auto-creates/reuses Stripe and Chargebee coupons with deterministic IDs
|
|
1119
|
-
- Meta Conversions API and TikTok Events API tracking alongside existing GA4 in payment analytics
|
|
1120
|
-
- Subscription renewal tracking as payment events (fires on `invoice.payment_succeeded` / `PAYMENT.SALE.COMPLETED` even without a state transition)
|
|
1121
|
-
- `attribution`, `discount`, and `supplemental` fields on payment intent schema for checkout context tracking
|
|
1122
|
-
- Intent data (attribution, discount, supplemental) propagated to order objects during webhook on-write
|
|
1123
|
-
- `meta.pixelId` and `tiktok.pixelCode` fields in config template
|
|
1124
|
-
- Journey test accounts for discount and attribution flows
|
|
1125
|
-
- Tests for discount validation and dispute alert endpoints
|
|
1126
|
-
|
|
1127
|
-
### Changed
|
|
1128
|
-
- Renamed config key `google_analytics` → `googleAnalytics`
|
|
1129
|
-
- Payment analytics rewritten with independent per-platform fire functions (`fireGA4`, `fireMeta`, `fireTikTok`)
|
|
1130
|
-
- Test runner module resolution now tries normal resolution first before falling back to search paths
|
|
1131
|
-
- reCAPTCHA marketing contact test skipped when `TEST_EXTENDED_MODE` is not set
|
|
1132
|
-
|
|
1133
|
-
# [5.0.122] - 2026-03-09
|
|
1134
|
-
### Added
|
|
1135
|
-
- Abandoned cart reminder system: sends escalating emails at 15min, 3h, 24h, 48h, 72h to users who visit checkout but don't complete payment
|
|
1136
|
-
- `payments-carts/{uid}` Firestore collection with security rules (client-side write, server-side completion)
|
|
1137
|
-
- `bm_cronFrequent` Cloud Function running every 10 minutes for sub-daily cron jobs
|
|
1138
|
-
- Shared cron runner (`cron/runner.js`) consolidating daily and frequent cron orchestrators
|
|
1139
|
-
- `main/order/refunded` and `main/order/abandoned-cart` email templates
|
|
1140
|
-
- Firestore rules test for `payments-carts` (12 test cases)
|
|
1141
|
-
|
|
1142
|
-
### Changed
|
|
1143
|
-
- Migrated v1 email templates to v2 SendGrid template IDs
|
|
1144
|
-
- `cron/daily.js` and `cron/frequent.js` now delegate to shared `cron/runner.js`
|
|
1145
|
-
- Payment analytics tracking now fires independently of transitions
|
|
1146
|
-
|
|
1147
|
-
# [5.0.120] - 2026-03-09
|
|
1148
|
-
### Added
|
|
1149
|
-
- reCAPTCHA verification on `POST /payments/intent` route (reads `verification.g-recaptcha-response` from request body)
|
|
1150
|
-
- Shared `libraries/recaptcha.js` module for reCAPTCHA token verification (replaces duplicate helpers)
|
|
1151
|
-
- `verification` field in `payments/intent` schema to accept the reCAPTCHA token object
|
|
1152
|
-
|
|
1153
|
-
### Security
|
|
1154
|
-
- reCAPTCHA failure responses now return generic "Request could not be verified" (403) instead of revealing the verification mechanism
|
|
1155
|
-
- reCAPTCHA verification runs in all environments except automated tests (`isTesting()`)
|
|
1156
|
-
|
|
1157
|
-
### Changed
|
|
1158
|
-
- Marketing contact routes (`POST /marketing/contact`, `bm_api add-marketing-contact`) now use shared `recaptcha.verify()` instead of inline helpers
|
|
1159
|
-
- Marketing reCAPTCHA checks skip during automated tests (consistent with payment intent)
|
|
1160
|
-
|
|
1161
|
-
# [5.0.119] - 2026-03-07
|
|
1162
|
-
### Added
|
|
1163
|
-
- `POST /marketing/email-preferences` route for unsubscribe/resubscribe via SendGrid ASM suppression groups
|
|
1164
|
-
- HMAC signature verification (`UNSUBSCRIBE_HMAC_KEY`) on unsubscribe links to prevent forged requests
|
|
1165
|
-
- HMAC signature generation in email library when building unsubscribe URLs
|
|
1166
|
-
- `UNSUBSCRIBE_HMAC_KEY` environment variable in template `.env`
|
|
1167
|
-
- Test suite for email-preferences endpoint (10 tests covering sig verification, validation, auth)
|
|
1168
|
-
|
|
1169
|
-
### Changed
|
|
1170
|
-
- Unsubscribe URL in emails no longer includes `appName` and `appUrl` params (replaced by HMAC sig)
|
|
1171
|
-
|
|
1172
|
-
# [5.0.118] - 2026-03-06
|
|
1173
|
-
### Added
|
|
1174
|
-
- Chargebee payment processor with full pipeline support (intent, webhook, cancel, refund, portal).
|
|
1175
|
-
- Chargebee shared library (`payment/processors/chargebee.js`) with raw HTTP API wrapper, unified subscription/one-time transformers, and both Items model (new) and Plans model (legacy) product resolution.
|
|
1176
|
-
- Chargebee webhook processor supporting subscription lifecycle events (`subscription_created`, `subscription_cancelled`, `subscription_renewed`, `payment_failed`, `payment_refunded`, etc.) and one-time invoice events.
|
|
1177
|
-
- Chargebee intent processor for hosted page checkout (subscriptions and one-time purchases) with deterministic item price IDs (`{itemId}-{frequency}`).
|
|
1178
|
-
- Chargebee cancel processor with immediate cancellation during trials and end-of-term cancellation otherwise.
|
|
1179
|
-
- Chargebee refund processor with 7-day full/prorated refund logic (matching Stripe/PayPal behavior).
|
|
1180
|
-
- Chargebee portal processor for self-service subscription management via Chargebee Portal Sessions.
|
|
1181
|
-
- Backwards compatibility for legacy Chargebee subscriptions: reads `cf_clientorderid`/`cf_uid` custom fields alongside new `meta_data` JSON format.
|
|
1182
|
-
- Chargebee test suite: `to-unified-subscription`, `to-unified-one-time`, and `parse-webhook` group tests with fixtures covering all status mappings, product resolution (Items + legacy Plans), and edge cases.
|
|
1183
|
-
- Chargebee customer name extraction from `shipping_address`/`billing_address` in webhook on-write pipeline.
|
|
1184
|
-
- `chargebee` config keys in product templates (`itemId`, `legacyPlanIds`).
|
|
1185
|
-
|
|
1186
|
-
### Changed
|
|
1187
|
-
- `CHARGEBEE_SITE` environment variable is now set from config in Manager init (matching PayPal pattern), so the Chargebee library doesn't need a Manager reference.
|
|
1188
|
-
|
|
1189
|
-
# [5.0.111] - 2026-03-05
|
|
1190
|
-
### Changed
|
|
1191
|
-
- PayPal client ID is now read from `backend-manager-config.json` (`payment.processors.paypal.clientId`) instead of requiring a `PAYPAL_CLIENT_ID` environment variable.
|
|
1192
|
-
- PayPal auth now auto-detects sandbox vs live environment by trying both endpoints in parallel on first auth, with live taking priority.
|
|
1193
|
-
|
|
1194
|
-
# [5.0.109] - 2026-03-04
|
|
1195
|
-
### Added
|
|
1196
|
-
- Immediate trial cancellation: cancelling during a free trial now terminates the subscription instantly instead of scheduling cancel at period end, preventing free premium access for the remainder of the trial.
|
|
1197
|
-
- Intent status tracking: `payments-intents/{orderId}` is now updated with `status: completed/failed` and completion timestamp after webhook processing.
|
|
1198
|
-
- `journey-payments-trial-cancel` test suite covering the full trial → cancel → immediate cancellation flow.
|
|
1199
|
-
|
|
1200
|
-
### Changed
|
|
1201
|
-
- Stripe and test cancel processors now detect trialing state and dispatch immediate cancel (`customer.subscription.deleted`) vs period-end cancel (`customer.subscription.updated`).
|
|
1202
|
-
|
|
1203
|
-
# [5.0.106] - 2026-03-04
|
|
1204
|
-
### Added
|
|
1205
|
-
- `GET /payments/trial-eligibility`: returns whether the authenticated user is eligible for a free trial (checks for any previous subscription orders in `payments-orders`).
|
|
1206
|
-
|
|
1207
|
-
### Fixed
|
|
1208
|
-
- Payment frequency mapping now supports `daily` and `weekly` in addition to `monthly` and `annually` across Stripe (`resolvePriceId`), PayPal (`resolvePlanId`), and test processor (`createSubscriptionIntent`). Previously, these frequencies silently fell back to `monthly`.
|
|
1209
|
-
- Updated docs (CLAUDE.md, README.md) to list all four supported frequency values.
|
|
1210
|
-
|
|
1211
|
-
# [5.0.104] - 2026-03-02
|
|
1212
|
-
### Added
|
|
1213
|
-
- `POST /payments/cancel`: cancels subscription at period end via processor abstraction (Stripe sets `cancel_at_period_end=true`; test processor writes webhook directly into the Firestore pipeline).
|
|
1214
|
-
- `POST /payments/portal`: creates Stripe Billing Portal session with cancellation disabled (users must use the cancel endpoint).
|
|
1215
|
-
- Payment transition pipeline: `transitions/index.js` detects all subscription state changes (new-subscription, payment-failed, payment-recovered, cancellation-requested, subscription-cancelled, plan-changed) and one-time transitions (purchase-completed, purchase-failed). Handlers fire-and-forget, send transactional emails.
|
|
1216
|
-
- Payment analytics: `analytics.js` tracks GA4 payment events for all transitions (non-blocking, skipped in tests).
|
|
1217
|
-
- Shared payment processor libraries: `payment/processors/stripe.js` (toUnifiedSubscription, toUnifiedOneTime, resolveCustomer, resolvePriceId, fetchResource), `payment/processors/paypal.js`, `payment/processors/test.js`, `payment/order-id.js`.
|
|
1218
|
-
- `Email` library (`libraries/email.js`): shared transactional email via SendGrid, used by transition handlers and admin routes.
|
|
1219
|
-
- `infer-contact.js` library: infers user name from payment processor data, auto-fills on first purchase.
|
|
1220
|
-
- `routes/user/data-request/` (get/post/delete): GDPR data request endpoints.
|
|
1221
|
-
- `cron/daily/data-requests.js`: daily cron to process pending GDPR data requests.
|
|
1222
|
-
- CLI commands: `auth` (get/list/delete/set-claims), `firestore` (get/set/query/delete), `firebase-init`, `emulator` (renamed from `emulators`).
|
|
1223
|
-
- `setup-tests/firestore-indexes-required.js`: validates required Firestore indexes exist before tests run.
|
|
1224
|
-
- Comprehensive payment test suite: journey tests for one-time purchase, one-time failure, payment failure, plan change, cancel endpoint; route validation tests for cancel and portal; unit tests for `toUnifiedOneTime()`, `stripe-parse-webhook`, `infer-contact`, `email`; real Stripe CLI fixtures.
|
|
1225
|
-
- Dedicated isolated test accounts for every mutating payment test (no shared state between tests).
|
|
1226
|
-
|
|
1227
|
-
### Changed
|
|
1228
|
-
- `admin/email/post.js`, `general/email/post.js`: refactored to delegate to shared Email library (~400 lines removed from each).
|
|
1229
|
-
- `marketing/contact/post.js`, `api/general/add-marketing-contact.js`: delegate to infer-contact + marketing library.
|
|
1230
|
-
- `user/signup/post.js`: rewritten with new middleware pattern.
|
|
1231
|
-
- `auth/on-create.js`: simplified, inline logic moved to middleware.
|
|
1232
|
-
- `api/admin/send-email.js`: removed `ensureUnique` and SendGrid contact name lookup (handled by Email library).
|
|
1233
|
-
- All admin routes: middleware pattern cleanup.
|
|
1234
|
-
- `config.payment.products` now supports `type: 'one-time'` products with `prices.once` key.
|
|
1235
|
-
- Test runner: improved discovery, filtering, and output formatting.
|
|
1236
|
-
|
|
1237
|
-
### Removed
|
|
1238
|
-
- `src/manager/libraries/stripe.js`, `src/manager/libraries/test.js`: replaced by `payment/processors/` shared libs.
|
|
1239
|
-
- `REFACTOR-BEM-API.md`, `REFACTOR-MIDDLEWARE.md`, `REFACTOR-PAYMENT.md`: work completed, files deleted.
|
|
1240
|
-
- `bin/bem`: replaced by `bin/backend-manager`.
|
|
1241
|
-
|
|
1242
|
-
# [5.0.84] - 2026-02-19
|
|
1243
|
-
### BREAKING
|
|
1244
|
-
- Moved `config.products` to `config.payment.products`. All product lookups now use `config.payment.products`.
|
|
1245
|
-
- Renamed `subscription.trial.activated` to `subscription.trial.claimed` across the entire subscription schema, API responses, analytics properties, and tests.
|
|
1246
|
-
- Renamed analytics user property `plan_id` to `subscription_id` and `plan_trial_activated` to `subscription_trial_claimed`.
|
|
1247
|
-
- Removed `Manager.getApp()` method (previously fetched from ITW Creative Works endpoint).
|
|
1248
|
-
- Removed `Manager.SubscriptionResolver()` factory method.
|
|
1249
|
-
- Removed deprecated `RUNTIME_CONFIG` .env loading from config merge.
|
|
1250
|
-
- Test accounts now use `subscription.*` instead of `plan.*`.
|
|
1251
|
-
|
|
1252
|
-
### Added
|
|
1253
|
-
- Stripe payment integration with shared library (`src/manager/libraries/stripe.js`) and `toUnified()` transformer that maps Stripe subscription states to the unified subscription schema.
|
|
1254
|
-
- Test payment processor library that delegates to Stripe's transformer with `processor: 'test'`.
|
|
1255
|
-
- Payment webhook route (`POST /payments/webhook`) with processor-specific handlers for Stripe (with signature verification) and test, including idempotent event storage in `payments-webhooks` Firestore collection.
|
|
1256
|
-
- Payment intent route (`POST /payments/intent`) for creating checkout sessions with processor-specific handlers.
|
|
1257
|
-
- Firestore trigger (`bm_paymentsWebhookOnWrite`) that processes stored webhook events and updates user subscription documents.
|
|
1258
|
-
- Payment schemas for webhook and intent validation.
|
|
1259
|
-
- `payment.processors` config section for Stripe, PayPal, Chargebee, and Coinbase configuration.
|
|
1260
|
-
- `npx bm stripe` CLI command for standalone Stripe webhook forwarding.
|
|
1261
|
-
- Auto-start Stripe CLI webhook forwarding with `npx bm emulator` (gracefully skips when prerequisites are missing).
|
|
1262
|
-
- `Manager.version` property exposing the BEM package version.
|
|
1263
|
-
- Journey test accounts for payment lifecycle testing (upgrade, cancel, suspend, trial).
|
|
1264
|
-
- Stripe fixture data for subscription states (active, trialing, canceled).
|
|
1265
|
-
- Tests for `stripe-to-unified` transformer, payment webhook route, and payment intent route.
|
|
1266
|
-
- Test cleanup for payment-related Firestore collections (`payments-subscriptions`, `payments-webhooks`, `payments-intents`).
|
|
1267
|
-
|
|
1268
|
-
### Changed
|
|
1269
|
-
- Cron schedule from `every 24 hours` to `0 0 * * *` (explicit midnight UTC).
|
|
1270
|
-
- Test runner now passes full config object (with convenience aliases) for payment processor access.
|
|
1271
|
-
- Unauthenticated usage tests now use relative assertions instead of absolute values.
|
|
1272
|
-
|
|
1273
|
-
### Removed
|
|
1274
|
-
- Removed `PAYPAL_CLIENT_ID` and `CHARGEBEE_SITE` from `.env` template (now configured via `payment.processors` in config).
|
|
1275
|
-
|
|
1276
|
-
# [5.0.39] - 2025-01-12
|
|
1277
|
-
### Added
|
|
1278
|
-
- New test infrastructure with Firebase emulator support for reliable, isolated testing.
|
|
1279
|
-
- Test runner with emulator auto-detection and startup.
|
|
1280
|
-
- Test types: standalone, suite (sequential with shared state), group (independent).
|
|
1281
|
-
- Built-in test accounts with SSOT configuration (basic, admin, premium-active, etc.).
|
|
1282
|
-
- Firestore security rules testing support.
|
|
1283
|
-
- HTTP client with auth helpers (`http.as('admin').command()`).
|
|
1284
|
-
- Rich assertion library (`isSuccess`, `isError`, `hasProperty`, etc.).
|
|
1285
|
-
- New `bm emulator` command for standalone emulator management.
|
|
1286
|
-
- Enhanced `bm test` with path filtering and parallel test support.
|
|
1287
|
-
|
|
1288
|
-
### Changed
|
|
1289
|
-
- Reorganized test files to `test/functions/` with `admin/`, `user/`, `general/` categories.
|
|
1290
|
-
- Standardized auth test naming to `unauthenticated-rejected`.
|
|
1291
|
-
- Auth rejection tests moved to end of test files (before cleanup).
|
|
1292
|
-
|
|
1293
|
-
### Fixed
|
|
1294
|
-
- Changed unauthenticated API error from 500 to 401 with proper "Authentication required" message.
|
|
1295
|
-
|
|
1296
|
-
### Removed
|
|
1297
|
-
- Removed legacy test files (moved to `test/_legacy/`).
|
|
1298
|
-
- Removed deprecated CLI files and templates.
|
|
1299
|
-
- Consolidated test account creation from API endpoint to test runner.
|
|
1300
|
-
|
|
1301
|
-
# [5.0.31] - 2025-01-17
|
|
1302
|
-
### Changed
|
|
1303
|
-
- Refactored CLI to modular command architecture with individual command classes and test files for better maintainability.
|
|
1304
|
-
- Migrated from deprecated `.runtimeconfig.json` to `.env` file with `RUNTIME_CONFIG` environment variable.
|
|
1305
|
-
|
|
1306
|
-
### Removed
|
|
1307
|
-
- Removed deprecated Firebase config commands (`config:get`, `config:set`, `config:unset`).
|
|
1308
|
-
|
|
1309
|
-
### Fixed
|
|
1310
|
-
- Fixed `install:local` command to save to dependencies instead of devDependencies.
|
|
1311
|
-
- Fixed reserved word conflicts with `package` parameter.
|
|
1312
|
-
- Fixed template file path resolution in setup tests.
|
|
1313
|
-
|
|
1314
|
-
# [5.0.0] - 2025-07-10
|
|
1315
|
-
### ⚠️ BREAKING
|
|
1316
|
-
- Node.js version requirement is now `22`.
|
|
1317
|
-
- `Manager.init()` no longer wraps the initializeApp() in `try/catch` block.
|
|
1318
|
-
- `Settings()` API tries to look for a method-specific file first (e.g., `name/get.js`, `name/post.js`, etc.) before falling back to `name/index.js`. This allows for more modular and organized code structure. Also, `name.js` is no longer valid, we now look for `name/index.js` this is to make it consistent with the `Middleware()` API.
|
|
1319
|
-
- `Middleware()` API now tries to load method-specific files (e.g., `name/get.js`, `name/post.js`, etc.) before falling back to `name/index.js`.
|
|
1320
|
-
- `ai.request()` no longer accepts `options.message.images`. Use `options.message.attachments` instead.
|
|
1321
|
-
|
|
1322
|
-
# [4.2.22] - 2024-12-19
|
|
1323
|
-
### Changed
|
|
1324
|
-
- `Manager.install()` now automatically binds the fn with the proper `this` context (this may be breaking).
|
|
1325
|
-
|
|
1326
|
-
# [4.1.0] - 2024-12-19
|
|
1327
|
-
### Changed
|
|
1328
|
-
- Attach `schema` to `bm-properties` response header.
|
|
1329
|
-
- `assistant.request.url` is now properly set for all environments (development, production, etc) and works whether called from custom domain or Firebase default function domain.
|
|
1330
|
-
|
|
1331
|
-
## [4.0.0] - 2024-05-08
|
|
1332
|
-
### ⚠️ BREAKING
|
|
1333
|
-
- Require Node.js version `18` or higher.
|
|
1334
|
-
- Updated `firebase-functions` to `6.0.1` (now need to require `firebase-functions/v1` to use v1 functions or `firebase-functions/v2` to use v2 functions).
|
|
1335
|
-
|
|
1336
|
-
## [3.2.109] - 2024-05-08
|
|
1337
|
-
### Changed
|
|
1338
|
-
- Replaced all `methods` references with `routes`. This should be changed in your code as well.
|
|
1339
|
-
|
|
1340
|
-
## [3.2.32] - 2024-01-30
|
|
1341
|
-
### Changed
|
|
1342
|
-
- Modified `.assistant().errorify()` to have defaults of `log`, `sentry`, and `send` to `false` if not specified to prevent accidental logging and premature sending of errors.
|
|
1343
|
-
|
|
1344
|
-
## [3.2.30] - 2024-01-30
|
|
1345
|
-
### Changed
|
|
1346
|
-
- Modified `.assistant()` token/key check to use `options.apiKey || data.apiKey`
|
|
1347
|
-
|
|
1348
|
-
## [3.2.0] - 2024-01-19
|
|
1349
|
-
### Added
|
|
1350
|
-
- Added `.settings()` API. Put your settings in `./schemas/*.js` and access them with `assistant.settings.*`.
|
|
1351
|
-
|
|
1352
|
-
## [3.1.0] - 2023-12-19
|
|
1353
|
-
### Added
|
|
1354
|
-
- Added `.analytics()` API GA4 support.
|
|
1355
|
-
|
|
1356
|
-
#### New Analytics Format
|
|
1357
|
-
```js
|
|
1358
|
-
analytics.send({
|
|
1359
|
-
name: 'tutorial_begin',
|
|
1360
|
-
params: {
|
|
1361
|
-
tutorial_id: 'tutorial_1',
|
|
1362
|
-
tutorial_name: 'the_beginning',
|
|
1363
|
-
tutorial_step: 1,
|
|
1364
|
-
},
|
|
1365
|
-
});
|
|
1366
|
-
```
|
|
1367
|
-
- Added `.usage()` API to track user usage.
|
|
1368
|
-
- Added `.middleware()` API to help setup http functions.
|
|
1369
|
-
- Added `.respond()` function to `assistant.js` to help with http responses.
|
|
1370
|
-
|
|
1371
|
-
## [3.0.0] - 2023-09-05
|
|
1372
|
-
### ⚠️ BREAKING
|
|
1373
|
-
- Updated `firebase-admin` from `9.12.0` --> `11.10.1`
|
|
1374
|
-
- Updated `firebase-functions` from `3.24.1` --> `4.4.1`
|
|
1375
|
-
- This project now requires `firebase-tools` from `10.9.2` --> `12.5.2`
|
|
1376
|
-
|
|
1377
|
-
- Updated required Node.js version from `12` --> `16`
|
|
1378
|
-
|
|
1379
|
-
- Updated `@google-cloud/storage` from `5.20.5` --> `7.0.1`
|
|
1380
|
-
- Updated `fs-jetpack` from `4.3.1` --> `5.1.0`
|
|
1381
|
-
- Updated `uuid` from `8.3.2` --> `9.0.0`
|
|
1382
|
-
|
|
1383
|
-
- Removed `backend-assistant` dependency and moved to custom library within this module at `./src/manager/helpers/assistant.js`
|
|
1384
|
-
- Replaced `require('firebase-functions/lib/logger/compat')` with the updated `require('firebase-functions/logger/compat')`
|
|
1385
|
-
- Changed default for `options.setupFunctionsLegacy` from `true` --> `false`
|
|
1386
|
-
- `.analytics()` is broken due to GA4 updates and should not be used until the next feature release
|
|
1387
|
-
- Updated geolocation and client data retrieval to new format:
|
|
1388
|
-
#### New Way
|
|
1389
|
-
```js
|
|
1390
|
-
const assistant = new Assistant();
|
|
1391
|
-
|
|
1392
|
-
// Get geolocation data
|
|
1393
|
-
assistant.request.geolocation.ip;
|
|
1394
|
-
assistant.request.geolocation.continent;
|
|
1395
|
-
assistant.request.geolocation.country;
|
|
1396
|
-
assistant.request.geolocation.region;
|
|
1397
|
-
assistant.request.geolocation.city;
|
|
1398
|
-
assistant.request.geolocation.latitude;
|
|
1399
|
-
assistant.request.geolocation.longitude;
|
|
1400
|
-
|
|
1401
|
-
// Get Client data
|
|
1402
|
-
assistant.request.client.userAgent;
|
|
1403
|
-
assistant.request.client.language;
|
|
1404
|
-
assistant.request.client.platform;
|
|
1405
|
-
assistant.request.client.mobile;
|
|
1406
|
-
```
|
|
1407
|
-
|
|
1408
|
-
#### Old Way
|
|
1409
|
-
```js
|
|
1410
|
-
const assistant = new Assistant();
|
|
1411
|
-
|
|
1412
|
-
// Get geolocation data
|
|
1413
|
-
assistant.request.ip;
|
|
1414
|
-
assistant.request.continent;
|
|
1415
|
-
assistant.request.country;
|
|
1416
|
-
assistant.request.region;
|
|
1417
|
-
assistant.request.city;
|
|
1418
|
-
assistant.request.latitude;
|
|
1419
|
-
assistant.request.longitude;
|
|
1420
|
-
|
|
1421
|
-
// Get Client data
|
|
1422
|
-
assistant.request.userAgent;
|
|
1423
|
-
assistant.request.language;
|
|
1424
|
-
assistant.request.platform;
|
|
1425
|
-
assistant.request.mobile;
|
|
1426
|
-
```
|
|
1427
|
-
|
|
1428
|
-
## [2.6.0] - 2023-09-05
|
|
1429
|
-
### Added
|
|
1430
|
-
- Identity Platform auth/before-create.js
|
|
1431
|
-
- Identity Platform auth/before-signin.js
|
|
1432
|
-
- Disable these by passing `options.setupFunctionsIdentity: false`
|