Repository navigation
2.0.0 (2026-10-21)
- feat(deps)!: upgrade js-yaml to v5 (#4029) (b3de6b6)
- fix(engines)!: raise node/npm floor to match the npm-11 lock (#4053) (#4055) (30059b1)
Bug Fixes
- address CodeRabbit review round 3 — expiry check, migration flow, invite guards (1674e3a)
- address CodeRabbit review round 4 — typo, model name, null guards, atomic invite (6951d89)
- address CodeRabbit review round 6 (3c56530)
- address copilot review comments (ff0358f)
- address Copilot review comments (c4fec03)
- address PR review — atomic migrations, upload backfill, invite binding, org cleanup (b900490)
- address review feedback — security hardening and code quality (b05a365)
- address second round of Copilot review comments (d4522ce)
- ai-config: address Copilot review comments (a2c8690)
- analytics: make service error-resilient, fix failing tests (#3361) (793e1ba), closes #3350 #3348 #3340
- analytics: redact single-use path tokens before auto-capture (#3932) (#3935) (626ff5e)
- audit: add config flags for IP/User-Agent capture (GDPR) (#3323) (98b2b8e), closes #3320
- audit: mailer attachment validation + policy guard (#3392) (e99107b), closes #3390 #3391 #3390 #3391
- auth,mail: unify base URL helper, fix email templates, keep emailVerified false (e6153fc)
- auth,mail: unify base URL helper, fix email templates, keep emailVerified false (c7812f2)
- auth,orgs,audit: replace silent .catch(() => {}) with error logging (#3366) (67114b2), closes #3360
- auth,orgs,uploads,users: address CodeRabbit review findings (c658417)
- auth,users: address second round of CodeRabbit review (e03232a)
- auth: address review feedback from pass 1 (f2e835a)
- auth: address review feedback from pass 1 (257cf7c)
- auth: canonical responses.error envelope on OAuth redirect (#3514) (4fa2df1), closes #4021 #3513
- auth: clean up stale test users before signup in integration tests (#3441) (77b6994), closes #3437
- auth: correct AppError import path in local strategy (9e457cc)
- auth: double check mail config 🐛 (#2595) (0edcd1f)
- auth: enforce zxcvbn strength check on password reset (#3721) (#3730) (0547013)
- auth: expose billing.{enabled,meterMode} in authenticated /api/auth/config (#3566) (5093f39), closes #3565
- auth: expose domainMatching and autoCreate in public config endpoint (b590f5f)
- auth: gate OAuth user creation behind config.sign.up (#3503) (#3507) (25a0900)
- auth: guard OAuth :strategy route — 404 unknown/disabled + stateless authenticate (#3947) (997c51d), closes #3900
- auth: guard user.roles with Array.isArray across all CASL policies (c7734c6)
- auth: handle GET OAuth callback with undefined req.body on Express 5 (#3492) (#3496) (35f28ff)
- auth: hash new password in updatePassword to stop plaintext storage + self-lockout (#3925) (#3937) (e8ef982)
- auth: log OAuth callback errors and drop useless error={} redirect (#3506) (c93a577), closes #3495
- auth: optional JWT on /api/auth/config, central rate limiter passthrough, org list 403 (cfac0c7)
- auth: provision an organization on OAuth signup (#4123) (41ff740), closes #3762 #3765 #3680
- auth: remove unverified client-asserted OAuth identity branch + throttle callback (#3866) (c09de24)
- auth: require verified local account for OAuth email link (#3504) (#3511) (383f555), closes #3503
- auth: restrict signup write-surface to client-settable fields (mass assignment) (#3867) (9f10307)
- auth: return emailPreferences with the current user (#4170) (009b89f)
- auth: signup analytics invited follows the claimed invite (#3983) (#4211) (66bc5c4)
- auth: stop writing OAuth validation errors directly to res (1ae9393)
- auth: use getBrut for lockout re-read to include lockout fields (32dda07)
- billing: add checkout metadata, harden webhook and customer creation (#3265) (0fe2056), closes #3259 #3260 #3261 #3262 #3259 #3260 #3261 #3262
- billing: admin sync reads period from items.data[0] (Stripe API ≥ 2025-08-27) (#3733) (d093c86)
- billing: architecture compliance + quality fixes (Batch 3a) (#3621) (3f171f2)
- billing: bump event markers on direct-write paths (#3628) (998e302)
- billing: clamp meter quota headroom so overflow is not double-counted (#3931) (#3934) (953cb48)
- billing: classify non-transient Stripe errors via rawType + auth/permission (#3700) (d87542b), closes #3699
- billing: cleanup 4 Copilot nits on subscription_changed analytics (#3772) (#3773) (0a2b3b4)
- billing: convert cents to units in creditDisputeReinstated (#3625) (cd3ec29)
- billing: credit signupGrant on the generic org-creation path (#3949) (7c7c0bf)
- billing: cumulative review — refund correlation + cleanup (#3543) (9d39136)
- billing: dead-letter mechanism + dispute.funds_withdrawn handler (#3603) (f51e40a)
- billing: default NODE_ENV to development in isAllowedUrl (f520d8a)
- billing: derive meterQuota from live plan config, not stale DB snapshot (#3667) (9d15821), closes trawl_node#1174
- billing: dispute reinstate credit + reconcile meter↔extras + balance guards (Batch 2) (#3620) (1a0c7eb), closes #1
- billing: fall back to getBaseUrl() when config.app.url is unset (#4175) (e896520), closes #4128
- billing: hard-fail missing planVersion + zero-cost short-circuit + meter API options shape (#3581) (9b46842)
- billing: hardening — webhook retries, canceled guard, fail-closed metering, grant and verify races (#4155) (7b7b1c2), closes #4151 #4151 #4151
- billing: honour ratios.default as the per-key meter fallback (#4119) (0902ebd), closes #4025
- billing: log + surface root cause when checkout 502s (#3660) (dd4ec25)
- billing: mega hardening v3 — atomicity + DRY + config (#3583) (5f12d2a)
- billing: migrate consumedHistoryIds to [String] for per-step idempotency (#3580) (3b5aada), closes #3576
- billing: migrate the ProcessedStripeEvent TTL index to its dead-letter-preserving spec (#4007) (5d41fe2), closes #4004 #4004 #4006
- billing: mirror the vendor cancel state on terminal subscription writes (#4101) (772481c), closes #4100
- billing: pack expiry removes only the pack's own unspent units (#4122) (06ce893), closes #4120
- billing: pass the AppError to responses.error so the 402 whitelist emits in production (#4065) (dcd8c7a), closes pierreb-devkit/Node#4062
- billing: plan-change forced reset + atomic usage+extras outbox (#3582) (fcc7183)
- billing: pre-LIVE critical fixes — free plan extras, /plans DoS, dispute/customer handlers (#3602) (7b89555)
- billing: public plans listing only returns products tagged with metadata.planId (#4124) (e408163), closes pierreb-devkit/Node#4113
- billing: re-apply lost C.2 review nits (warn-on-unknown-priceId + 4 hardening) (#3758) (0a9db27), closes #3743 #1250 #3743
- billing: reconcile trialing subscriptions (#3626) (73e4062)
- billing: refund idempotency + grace/dunning config (audit Codex P1) (#3586) (070a754)
- billing: rename config flag activated → enabled to match auth controller exposure (#3568) (8ed762b), closes #3566
- billing: replace $sortArray with portable $unwind+$sort+$group in listLedgerPage (#3796) (b355e97), closes #3774
- billing: report a subscription missing in Stripe as a divergence (#4027) (3808cbe), closes #4026
- billing: requirePlan gates on subscription.status + top-level errorCode (#3682) (fd393ac), closes #1183
- billing: resolve plan via priceId map, not price.metadata.planId (#1250) (#3743) (0693e49), closes #3742 #3741 #3742
- billing: resolve Stripe plan via the priceId map in admin sync and reconcile (#3969) (9e6bc2d), closes #3964
- billing: restore coverage gate + wire ops alerting + structured logger (Batch 1) (#3619) (a92fdad), closes #1
- billing: restore plan after dunning recovery (#3627) (cd3cde1)
- billing: retry refund correlation backfill 3x + dead-letter (#3690) (1b9f7a6)
- billing: rotate week-quota snapshot on mid-week plan activation (#3989) (a6bcd98), closes #3988
- billing: sentinel backfill + trialing guard + intentId + structured logger (#3601) (e1a7bfd)
- billing: short-circuit retryWithBackoff on non-transient Stripe errors (#3697) (1141ffd), closes #3690 #3691
- billing: stop the webhook from forcing free on an unresolvable price (#3974) (319a675), closes #3970 #3970
- billing: Stripe state safety + webhook ordering (audit Codex P0+P1) (#3585) (b45af89)
- billing: surface live cancelAt through getSubscription (#4022) (#4023) (5f4b1d5)
- billing: treat incomplete subscriptions as fail-closed (#3624) (0005b45)
- billing: unblock Stripe Checkout (auto address) + admin meter bypass (#3548) (d8f412b)
- billing: use active/trialing status allowlist in requireQuota middleware (#3280) (6df1249)
- billing: use default import for billingEvents in README example (#3282) (197768c)
- billing: v4 hardening — finish v3 oversights + cohérence API (#3584) (83dfc56)
- billing: V6 P1 #1 #2 + P2 — applied:false debit + admin refundRequestId + /usage local (#3593) (0f2e3b9)
- billing: V7 polish — debit upsert + concurrent integration tests + refundRequestId required (#3595) (277a18d)
- billing: V8.1 post-merge hardening (#3629) (5036e01)
- billing: validate the Stripe key at boot instead of only checking presence (#4102) (7be76a4)
- billing: validate webhook plan metadata against known enum values (#3284) (2e079e9)
- billing: weekKey index becomes a true partial — legacy usage tracking works past month one (#4000) (fa3d02c), closes #3991 #3990 #3991
- billing: weekly reset repays overflow debt once (#4118) (611169d), closes #3914
- billing: weekly reset semantics + lazy seed + plan-change reset (#3575) (226c9fd), closes #3569 #3571
- billing: wrap reconcile cron in distributed lock (#3728) (#3734) (813092b)
- bypass gulp for test scripts to fix Node 22 ESM compatibility (420b261)
- ci: add codecov.yml configuration (#3347) (4f278ea), closes #3346
- ci: add MongoDB health check to prevent startup race condition (42f2f12)
- ci: increase test timeout to 30min for self-hosted runners (58f36fd)
- ci: run test:coverage in CI to enforce thresholds (add678d), closes #3238
- ci: simplify CI.yml — remove ARC-specific conditionals (#3343) (37db74d), closes #3342
- ci: update mongodb-github-action from 1.8.0 to 1.12.1 (3bcbdf4)
- claude: add CHECKS_FOUND guard to monitoring.md retry loop (2af2bef)
- claude: address pass-2 review feedback - watch guard, $PR placeholder, jq line field (38372a8)
- claude: address review feedback - stale refs, retry loop, jq pagination, variables (8971cfa)
- code review — CASL policy guards, null checks, config cleanup (2131713)
- config: add trust.proxy from master merge, fix auth comment typo (3dcf31f)
- config: address review — remove spurious error log, fix test assertion, add md lang tag (5a78bca)
- config: address review feedback from pass 1 (2dc944c)
- config: detect module env config files by path existence, not merged keys (bdc33ab)
- config: export assertSafeEnv, rename glob test, add allow/deny tests, fix env restore (42899ad)
- config: gate production hardening on isProd predicate (env-gate defect class) (#3874) (edf5b93)
- config: replace _.merge with deepMerge to fix array handling (515d6ba), closes #3198
- config: use pathToFileURL and improve error handling in config loader (57300ff)
- config: use port 3000 in test environment (43ea2a4)
- config: validate env safety, avoid double-glob, fix test shape and cross-platform import (60d2c16)
- core: handle graceful shutdown when server fails to start (#3281) (4b0938c)
- delete orphaned .release-it.json (ecce8ee)
- deps: adjust eslint 🐛 (88d90da)
- deps: bump js-yaml to 4.3.2 to close CVE-2026-59870 (#4028) (2198beb)
- deps: mongodb Usage 🐛 (#2558) (5c74d38)
- deps: prettier plugin 🐛 (5c24056)
- deps: revert 🐛 (2c33bac)
- docker: copy .npmrc before npm ci and pin the Node major to .nvmrc (#4061) (bcbefc8), closes #4060 #4053 #4060
- docker: make docker-compose.test.yml work for downstream projects (#3324) (b0301d7), closes #3319
- docs: use placeholder and clarify file layout scope (aeea2f5)
- errors: address review feedback on response helpers (9296ed7)
- errors: align ERRORS.md JSDoc rule with guardrail nuance (non-void + async) (79137f3)
- errors: curate AppError.details call sites and gate description leaks (#4063) (b9caa95), closes pierreb-devkit/Node#4059 #4059 pierreb-devkit/Node#4059
- errors: pass an AppError to responses.error at the last two non-error sites (#4066) (d3f13aa), closes #4064
- express: add JSDoc to initMiddleware and use afterEach for config restore (df3dbe4)
- express: catch-all 404 covers every HTTP verb, not just GET (#4010) (257a061), closes #3975 #3978 #3975
- express: content-negotiated 404 for unmatched routes (#3975) (#3977) (d34528c)
- express: honor configured trust proxy hop count (#3933) (#3936) (2344d4c)
- guardrails: apply Copilot feedback — use @return, clarify async rule (db516ce)
- guardrails: use @return (no trailing s) for cross-repo consistency (7004fec)
- guardrails: use @returns to match Node codebase convention (40373d6)
- guardrails: use @returns to match Node codebase convention, clarify async rule (d0f1274)
- handle populated organizationId in requestByID and update migration test key (1a7ed09)
- home: address CodeRabbit review — try/finally guards, 401 assertion (6eec090)
- home: address readiness review — JSDoc, JWT check, mail sanitization (05c5037)
- home: address remaining review — JSDoc, CASL manage, log errors, cache isSet (43d3ad9)
- home: handle GitHub API failures gracefully in releases/changelogs (#3292) (e8e20aa), closes #3291
- home: mail readiness warning states the consequence (#3861) (dceba95), closes pierreb-devkit/Vue#4297
- home: remove doc from home module (#3428) (7b87cc9), closes #3427
- home: restrict /team fields, remove releases/changelog, add jwt readiness check (#3724 #3725 #3718) (#3735) (de86c6b)
- inline openssl command in error message, simplify WIKI wording (6f5315e)
- invitations: pre-P9 hardening — legacy email normalization + best-effort finalize (#3829) (77330f2), closes #3815
- invitations: revoke/duplicate guards + resend endpoint (#3857) (26dc3ff), closes #3834 #3834 #3834 #3834
- invitations: scope list, self-invite guard, open-signup gate (#3833) (#3855) (d321509)
- jsdoc: add backticks to @param and @returns for consistent Markdown formatting (c0e9738)
- jsdoc: align ERRORS.md wording with guardrail (new or modified functions) (076f14a)
- lib: address CodeRabbit findings on config.js, gridfs.js, multer.js (ecd58f9), closes #3178
- lib: address review feedback from pass 1 (cf8aab8)
- logging: use logger instead of console in stack modules (#3442) (b7388c3), closes #3437 #3434
- log: redact OAuth code and state query params in the access log (#3972) (b9f4951), closes 3955/#3961 #3967
- log: redact single-use path tokens in the morgan access log (#3955) (5c43bb1), closes #3951
- mailer,audit: propagate send and write failures so caller context logging fires (#3973) (82b5cf0), closes lib/helpers/mailer/index.js#sendMail modules/audit/services/audit.service.js#log invitations.service.js#resend #3966
- mailer: address copilot review comments (84b7091)
- mailer: drop hand-built appName/appContact, brand the subjects (#4177) (da96fc1), closes 4164-#4171 #4131
- mailer: normalize provider response to nodemailer format (82228c8), closes #3373
- make jest globalSetup resilient to missing MongoDB in CI (189b5f1)
- migrations: claim-with-status — interrupted runs resume instead of being skipped forever (#4001) (fb771a8), closes #3992 pre-#3992 #3992 migration.repository.js#claim #3992
- migrations: remove reference to deleted users.myproject.config.js template (cbc6be0)
- migrations: sync Migration indexes on boot to enforce unique name (bcb28c0), closes #3454
- model: preserve original body keys in isValid middleware (#3432) (b4e2da9), closes #3431 #3431
- mongoose: await model index builds at startup + repair the never-created billing month index (#3993) (c8049d6), closes #3990 #3990 #3990 #3990
- mongoose: tolerate same-name index conflicts at boot so migrations can repair them (#4005) (f85f49a), closes #3990 #4004
- Node low-severity security hardening bundle (timing oracle, sharp DoS, dependabot, deps, audit) (#3876) (3937953)
- observability: pass err via { error } in initErrorRoutes for Winston dedup (#3657) (829b91b)
- observability: use posthog-node captureException SDK method for UI grouping (#3659) (25db1be)
- organizations,users: address CodeRabbit review — returnDocument, membership permissions (f8b7b56)
- organizations,users: harden plan assignment, remove domain auto-set, fix N+1 admin query (dce92e4), closes #3232
- organizations: address audit findings — slug race, dead guest abilities, central rate limiter (c2c0e11)
- organizations: address CodeRabbit review — guards, JSDoc, tests (c07f64e)
- organizations: address CodeRabbit review comments (9b1af95)
- organizations: address review — 403 status, reuse user, surface flag, update JSDoc (a281fa6)
- organizations: allow global admin to bypass org-level guards (#3509) (247ba7b), closes #3505
- organizations: close self-join via shadowed Organization policy subject (#3868) (351ed34)
- organizations: extend global-admin bypass to updateRole + isGlobalAdmin helper (#3512) (34e2adc), closes #3509 #3510
- organizations: handle falsy approval result, add missing @returns JSDoc (492ed89)
- organizations: membership user_org_unique partial index root-cause (#3856) (01740e6), closes #3841
- organizations: replace dynamic-import cleanup with onOrganizationRemoved extension point (#3863) (#3865) (c31a5f0)
- organizations: replace full-doc save with $set sparse patch to eliminate race clobber (#3607) (5b5490b), closes #3605 #3605
- organizations: route user-deletion org cascade through the removal seam and make org removal atomic (#3971) (ac772a4), closes #3709 #3965
- package.json & package-lock.json to reduce vulnerabilities (#2198) (131852b)
- package.json & package-lock.json to reduce vulnerabilities (#2476) (2dc6db4)
- package.json & package-lock.json to reduce vulnerabilities (#2628) (0770fd8)
- policy: handle unmapped HTTP methods in methodToAction (HEAD, OPTIONS) (#3158) (0792497), closes #3137
- policy: remove dead guest read rule on authenticated task routes (7079476)
- policy: startup warn for missing SubjectRegistration + mailer test coverage (#3397) (3337568), closes #3395 #3396
- pr-skill: add git push -f deny pattern and fix bug report placeholder (52d0311)
- pr-skill: add text language tag to pseudocode fence (MD040) (5b00075)
- pr-skill: address bot review feedback on pr skill (4af6af3)
- pr-skill: address Copilot review pass 2 (6ffc453)
- pr-skill: clarify draft PR scope — bots trigger only after gh pr ready (fba2daa)
- public: make docs integration tests consumer-independent (#4016) (585d5c6), closes #4011 #4014 #4015
- public: make slug-collision precedence fixture self-contained (#4014) (05f3621), closes #4012
- public: resolve duplicate guide slugs by precedence, not scan order (#4011) (adbc2b8), closes #3979
- pull-request skill: add consecutive_zero guard and re-check pending review checks (a8c4e7e)
- pull-request skill: align stop-condition wording across the doc (f05985e)
- pull-request skill: clarify reply+resolve for review threads vs PR-level comments (f1df941)
- pull-request skill: reply and resolve all threads including non-actionable (23cb7e1)
- pull-request: add perfect mode with CodeRabbit auto-trigger strategy (b60a2ac)
- pull-request: enforce reply before resolve + cleanup outdated threads (eeaf833)
- pull-request: fail-safe when stack remote is missing (e87af0f)
- rate-limit: wrap IP fallback with ipKeyGenerator for IPv6 safety (#3452) (c882495), closes #3450
- rateLimit: key by userId instead of IP + trust proxy (#3435) (87a4553), closes #3429
- readme: align /feature table cell trailing space (1a30a89)
- release: add .release-it.json config, update README release examples (d243d17)
- remove redundant contentType from GridFS upload options (ignored by MongoDB driver 7) (a1296b6)
- remove stale SSL script references from config helper and WIKI (51dee84)
- remove unused result variable in recordFailedAttempt (0126546)
- resolve integration issues after merging all feature branches (f09b3d3)
- resolve lint errors for unused destructured variables (9fbb9e6)
- resolve merge conflict in getBaseUrl.js, keep trailing-slash strip (1dfb405)
- responses: derive error title from http.STATUS_CODES when omitted (#4136) (fd063d8), closes #4009 #4009
- review: address CodeRabbit findings — guards, races, JSDoc, query fixes (1f1756f)
- review: address Copilot comments and improve coverage (e379f8e)
- run tests sequentially to fix CI timeout on integration suites (1621977)
- security: address CodeRabbit review round 5 (2e3a9fd)
- security: auth enumeration + prod error message leak (#3790) (428fb18), closes #3722 #3726 #3722 #3726 #3722 #3726
- security: escape regex in search, filter active memberships on switch, expire invite tokens (8f6f3cd)
- security: fail-closed JWT secret validation in non-dev envs (#3791) (fbba499), closes #3737
- security: harden auth endpoints against NoSQL injection and add JWT warning (#3268) (d529d7a)
- security: sanitize the user document in org-switch and /me responses (#3968) (86def2c), closes #3963
- settings: deny git switch to main/master branches (082aff6)
- skill: add merge conflict check in pull-request convergence loop (62b1dc3), closes #3236
- skill: address copilot review — consistent $PR placeholder, explicit UNKNOWN handling (b0f19e9)
- skill: address review feedback from pass 1 (b7894d6)
- skills/update-stack: switch drift gate scan to git diff (catches missing-locally case) (#3783) (af80ac4), closes #3779
- skills: flip PR to ready before waiting on CodeRabbit to avoid a draft deadlock (#3976) (d89f05c), closes #3956
- skills: harden CodeRabbit polling with --paginate and timeout (edaa754)
- stats: add .exec() and fix JSDoc tag consistency (cb6f82b)
- tasks: make stats endpoint public — remove CASL policy gate (e5e8798)
- tasks: remove dead model.isValid on DELETE route (a6b88fd)
- tasks: require JWT + org scope on stats endpoint (#3436) (2be9390), closes #3430
- tasks: use document .save() instead of new Model() in repository update (#3440) (5fc9ba4), closes #3433
- templates: rewrite billing-quota-reached-100 with noun-agnostic copy (#3776) (3086820)
- test: drop unused arrow-fn params to satisfy no-unused-vars lint (073c6c0)
- test: eliminate Winston uncaughtException listener leak (#3544) (229ecf1), closes 3535-#3539
- test: ensure Migration indexes exist on per-worker DBs when migrations pre-warmed (#3561) (77e895e), closes trawl_node#1075
- test: make array excludes test non-vacuous (2880e44)
- test: replace --runInBand with worker children + per-worker DB isolation (#3558) (ec17597), closes #3550
- tests: add _id and re-read mock for atomic lockout unit test (6d85431)
- test: set --max-old-space-size=6144 on test:coverage to prevent OOM under --runInBand (#3554) (a40e4c5), closes #3550 #3550
- tests: guard jest globalSetup against non-test NODE_ENV (#3476) (#3488) (93f23e0)
- tests: ignore server 🐛 (#2556) (32b055c)
- test: skip per-suite migration re-runs via globalSetup pre-warm (#3549) (eae9410)
- tests: update tests for auth-protected task list and autoCreate (b7a7c51)
- untrack httpmethodsfix worktree (c61b023)
- untrack worktrees directory (embedded repo artifact) (95ead39)
- update CI to use npm run lint (renamed from test:lint) (7fcd654)
- update-stack: add concrete gh issue create command and resolution guidance (6d0e20c)
- update-stack: add downstream-only new files rule to conflict table (85d238e)
- update-stack: address review feedback from pass 1 (5239664)
- update-stack: address review feedback from pass 2 (f44f0b2)
- update-stack: clarify failure origin criteria in step 3bis (e4aa764)
- uploads: allow public read access to images (d1df7b0), closes #3197
- uploads: allowlist Content-Type on serve paths — defense-in-depth against stored-XSS (#3729) (#3732) (1310c9f)
- uploads: avoid merged map allocation, sanitize config extensions, add bin fallback test (b2b55d7)
- uploads: treat an already-deleted blob as gone in purgeUnreferenced (#4207) (592a4cb), closes #4018
- use DEVKIT_NODE_ env prefix instead of NODE_ (786c86f)
- use repo-relative migration keys and scope membership requests to org (217c7c0)
- users/organizations/auth: prevent 500 on signin when org deleted without cascade (#3709) (#3713) (56b17a0)
- users: address third round of CodeRabbit review (8a709b2)
- users: admin GET /users/:id no longer leaks password hash and tokens (#3731) (b08e9a3), closes #3723
- users: relax firstName to optional to accept digit-only email local-parts (#3478) (f4ba797), closes #3477
- users: reset email verification on email change (#3825) (#3854) (137f37e)
- users: split unsubscribe header and settings links, build the API link from config.domain (#4171) (7b42263)
- validation: address Copilot review comments (cf3a66d)
Features
- add Claude Code setup with skills and aligned README (c223a49)
- add self-improvement and elegance workflow rules (#3233) (c27069d)
- align lint scripts with Vue stack convention (f1416ac)
- analytics: add feature flags service and requireFeatureFlag middleware (#3307) (c22fcfe), closes hi#level #3297
- analytics: add request-aware feature flag helpers (#3487) (#3491) (bcfe797)
- analytics: auto-capture middleware for API requests (#3305) (723ba4a), closes #3295
- analytics: explicit source param + 'system' default for capture/captureException (#3653) (48766e5), closes #3651
- analytics: extend analytics service with posthog-node SDK (#3637) (8db3757)
- analytics: identify + groupIdentify on auth/org events (#3306) (7a24d2a), closes #3296
- analytics: PostHog server-side SDK + analytics service (#3299) (7e60bbb), closes #3294
- analytics: restructure PostHog config under analytics.posthog + wire env vars (#3641) (2f5ebd9)
- audit: make route→type map config-driven instead of hardcoded (b6295fd), closes #3413
- auth: add account lockout and lastLoginAt tracking (1c6472c), closes #3217
- auth: add email verification on signup (#3216) (ddb5a9f)
- auth: add POST /api/auth/signout to clear TOKEN cookie (#3508) (9a1a513), closes #3502
- auth: add public GET /api/auth/config endpoint (047a92c)
- auth: expose app version in GET /api/auth/config (#3803) (f0a0f8b)
- auth: expose sign.cap + remaining on /auth/config (beta seat counter) (#3761) (7ed5291)
- auth: include user.complementary in /token response (#3739) (5f757e6), closes #3738
- auth: invite-only / capped signup gate (#3715) (f0e7a83)
- auth: link OAuth signin to existing user by verified email (#3497) (c4532ba), closes #3493 #3494
- auth: return abilities in signin and token responses (a7c1876), closes #3222
- auth: shareable referral link, credit only (#4212) (#4213) (49a0897)
- auth: signup attribution + OAuth user_signed_up event (#4024) (133d6df), closes #4003
- auth: signup flow with org creation/joining (domain matching) (2aed16b), closes #3225
- auth: trigger handleSignupOrganization on verifyEmail success (best-effort) (#3765) (5604011), closes #3762 trawl#1317
- billing: add checkout, portal, and subscription endpoints (#3257) (ca14c0f), closes #3245
- billing: add creditGrant idempotent method + source field on entries (#3662) (41e7bd9)
- billing: add demo equivalences config for the nav compute gauge (#3899) (7a26112)
- billing: add generic BillingUsage model for quota tracking (#3273) (cf4763f)
- billing: add GET /api/billing/plans public endpoint (#3250) (5e59e6f), closes #3241 #3244
- billing: add GET /api/billing/usage endpoint (#3277) (0c2efb2)
- billing: add requirePlan middleware to gate routes by subscription plan (#3255) (6a16c9a), closes #3246
- billing: add requireQuota middleware for plan-based limits (#3275) (4ee7e48)
- billing: add signupGrant + oneShot config fields to Free plan (#3661) (5a113f9)
- billing: add Stripe webhook endpoint and handler (#3256) (596fcae), closes #3243
- billing: add Subscription model + repository (#3249) (9836a8e), closes #3241
- billing: admin toolkit + operational foundations (Étape 3) (#3612) (5ff7deb), closes #5
- billing: compute layer foundation - PR-N1 (no behavior change) (#3535) (0f2320d), closes #3533
- billing: config-gate allow_promotion_codes on subscription Checkout (#3793) (#3794) (690e756)
- billing: config-gated referral grant — listener + reconcile cron (closes #3842) (#3843) (2219a82), closes #3833 #5 #5 #3833 #3833
- billing: credit signupGrant on org creation post-signup (#3663) (3a3ca09)
- billing: credit-balance alerts for plans without a weekly quota (#4126) (36703d2), closes #3536
- billing: crons + dunning grace 7d (PR-N5) (#3539) (dd8ea07)
- billing: distributed lock for multi-pod crons (#3688) (d397764)
- billing: emit plan.changed event on subscription plan change (#3274) (fc34ae8)
- billing: emit subscription_changed analytics on plan change (#3769) (c860b0f), closes trawl#1315
- billing: expose config.billing.equivalences via /api/auth/config (#3577) (f7d73c9)
- billing: extract email listeners + ship generic templates (#3746) (#3747) (8393489)
- billing: extras routes + middleware + meter quota gate (PR-N4) (#3538) (209bb3c)
- billing: hygiene polish — stack trace, req.id, $slice ledger, TTL archived, cache utils (#3614) (402a9a5)
- billing: instant referee grant on organization provisioning (#3859) (7d4f605), closes #3844 #3842 #3844
- billing: lastResetAt + archivedAt schema + meter cap (PR-N8) (#3557) (478944d)
- billing: meter service, extras balance, weekly reset (PR-N2) (#3536) (c568a3c), closes #3533
- billing: migration — backfill 500 signupGrant for existing Free orgs (#3664) (cf6ea66)
- billing: operational maturity — runbooks, stale cache, structured logs (Étape 4) (#3613) (f81aa78)
- billing: per-step idempotency keys + automatic_tax config flag (#3576) (c68fd0e), closes #3570 #3572
- billing: plan source-of-truth + plan seed + meterExempt Zod (PR-N6) (#3555) (b257c87)
- billing: referral link signups can earn the referral reward (#4214) (#4215) (a37747d)
- billing: refund packId + attachUsageHeader + admin endpoints (PR-N7) (#3556) (036e1bb)
- billing: scaffold billing module with Stripe SDK (#3248) (7871d58), closes #3240
- billing: ship pricing constants contract + refundCharge service (#3775) (2e25408)
- billing: stripe live blockers — TTL guard + idempotency + event coverage + admin route (#3611) (34dadb2)
- billing: subscription cancelAt + cancelAtPeriodEnd lifecycle fields (#3741) (889626a), closes #3740
- billing: webhook hardening — 9 reliability improvements (#3598) (b8d4008)
- billing: webhook idempotency + refund flow (PR-N3) (#3537) (e43cfcd)
- ci: add container mode for ARC kubernetes compatibility (#3331) (1ac565d)
- ci: skip setup-node and container when using self-hosted runner (#3339) (921a706), closes #3338
- ci: split test job into unit/integration/e2e matrix + Codecov flags (#3559) (efd7bbc)
- ci: support self-hosted runners via RUNNER variable (#3328) (140054f), closes #3327
- ci: use APP_ENV and MONGO_URI variables for downstream configs (#3337) (2a6b6ef), closes #3333
- config: docs.excludeModules — doc-only module exclusion independent of activation (#3924) (7916876), closes #3923
- config: per-module activated flag (#3384) (af0cc3f), closes #3383
- config: support {module}.{project}.config.js per-module project overrides (3ca7e64)
- core: add migration system with auto-run at boot and DB tracking (4042536), closes #3218
- core: add pre-parser route hook for raw body access (#3247) (6da16f1), closes #3242
- deps: upgrade zod from v3 to v4 (#3153) (d8ce87b), closes #3149
- docs: embed markdown guides in Scalar API reference (#3453) (67acceb), closes #3451
- docs: polish default Redoc theme — Inter + JetBrains Mono, optional override (#3687) (da02947)
- docs: replace Scalar with Redoc for /api/docs UI (#3466) (dd97798), closes #3464
- docs: replace swagger-ui-express with Scalar + /api/spec.json (#3388) (3e0081b), closes #3385
- emails: bulletproof CTA buttons in transactional templates (#3920) (039f553)
- express: derive api. for OpenAPI servers.url (#3770) (7ae3664)
- express: support trust proxy setting via env var (308b8ca), closes #3191
- feature: read ERRORS.md in Phase 0 before coding (#3604) (514fbae)
- guides: add sections option for grouped Redoc sidebar layout (#3771) (b56d3b6), closes #3767
- guides: strip numeric ordering prefix from rendered titles (#3465) (9ebb211), closes #3463
- home: add SaaS readiness checklist + admin route (60b22b5)
- invitations: claim invite token at open signup when userFacing is on (#3982) (00768d0), closes #3981 hi#severity pre-#3981 Pre-#3981 pre-#3981
- invitations: config-gated lifetime cap on invitation creation (#3987) (5ac7fbc), closes #3986
- invitations: edge-case hardening — two-phase claim, CI email index, cap unify (P3) (#3821) (3b8035b), closes #3811
- invitations: referral substrate — referredBy + invitation.accepted event (P8a) (#3824) (1aacb58), closes #3814 #5 #5 #5
- invitations: user-facing referral abilities, redemption event, referrer notification (#3980) (223ca9e), closes #3945 #3945
- logging: Winston structured logging, AuditLog module, and Sentry integration (#3318) (24a8574)
- mailer: add Resend provider (#3349) (5a9a6a7), closes #3309
- mailer: overridable layout and partials (header, footer, button, styles) (#4166) (c527390), closes #4130 #4132 #2563eb #if #x27
- mailer: per-send from, replyTo and headers (#4167) (a79d0b6), closes #4161
- mailer: resolve central brand values via config.mailer.brand (#4165) (463aa6c)
- mailer: resolve templates through config.mailer.templates (#4164) (6bde0c1), closes #4129 4020/#4135 #4129
- mailer: support file attachments in sendMail() (4659916), closes #3374
- monitoring: single-source PostHog Error Tracking + CLI User-Agent middleware (#3640) (131eddf)
- observability: fan-out errors Sentry + PostHog + opt-in flags (#3489) (ef1f6de), closes #3486 #3486
- observability: forward Winston error logs to PostHog Error Tracking (closes #3651) (#3654) (a91b07b)
- openapi: add bearerAuth description + apiKeyAuth alias + schema descriptions (#3781) (cdacb9b), closes infra#38
- organizations: add config (enable/disable, autoCreate, domainMatching) (404accf)
- organizations: add migration script for existing users and data (47b16e4), closes #3226
- organizations: add org switch endpoint (new JWT + new abilities) (31e4816)
- organizations: add org-scoped middleware and organizationId to models (8838ba0)
- organizations: always-create workspace + suggestedJoin hint (drop onboarding limbo) (#3680) (5e166d7)
- organizations: config-gated email verification policy {strict|off} (#3919) (2cc9781)
- organizations: create module (models, controllers, routes, services) (69976f4), closes #3219
- organizations: org.addMember(userId) + consent flow + source discriminator (P5a) (#3823) (609771f), closes #3813 #1
- organizations: owner_add lifecycle + addMember invitee email (#3858) (12b961c), closes #3831 #1 #3832 #3832 #3832
- organizations: require email verification before domain-based org features (5906821), closes #3232
- organizations: welcome email after signup (#4125) (cc97e14), closes #4116
- phase 2 organizations — domain join, membership requests, and bug fixes (3f8926e)
- pr-skill: make monitoring loop explicitly autonomous with polling (fd60555)
- public: docs content contract (/api/public/docs) + decommission Redoc (#3878) (580f6a3)
- rename WAOS_ env prefix to NODE_, remove all weareopensource refs (ded08d1)
- responses: expose whitelisted AppError details in the production error envelope (#4056) (05ee310), closes #3958 config/index.js#deepMerge
- skill/feature: add Phase 0.0 issue claim-on-start (#3639) (3eee971), closes pierreb-projects/infra#28
- skills: /feature non-interactive invocation — structured SKIP/STOP instead of prompts (#3962) (dffc577), closes #N #failure #3959
- skills/update-stack: block on undeclared drift vs upstream (#3760) (6ab44fd), closes #3759
- skills/update-stack: drop ledger condition + auto-derive scan list (#3778) (4f505c1), closes #3777 infra#37
- skills: thin service layer and crud-only module scaffold (#4176) (f6fbf83), closes #4174
- swagger: opt-in publicInProd to serve /api/spec.json in deployed envs (#3879) (8e89a21)
- tasks: add operationId fields to OpenAPI operations for Redoc deep-linking (#3764) (11ab4b7)
- templates: convert the 15 shared emails to body fragments (#4178) (fe2b59e), closes #4133 #4133 #4164-4171
- tooling: add /pr skill with full PR lifecycle (948826a)
- update-stack: report upstream issues when verify fails on stack code (203cf9c)
- uploads: add createFromBuffer for programmatic GridFS storage (#3290) (cf58689), closes #3289
- uploads: headersSent guard on stream errors + text/html MIME for GridFS (#3745) (2ff4161), closes #3744
- uploads: idempotent remove + age-graced sweep of unreferenced GridFS blobs (#4017) (174f3b1), closes #4013
- uploads: make MIME type map extensible via config.uploads.mimeTypes (#3407) (b2d65e3)
- users: config-declared email sequences cron (#4169) (5faa9e2), closes #4162 #4163
- users: email preferences, product mail sender, one-click unsubscribe (#4168) (7d84f25), closes #4162 #4162
- verify: add coverage enforcement to verify skill (#3304) (a3d4216)
Performance Improvements
- ci: add timeout, skip merge push, standardize Node LTS (#3329) (c01d0a9), closes #3326
- stats: replace countDocuments with estimatedDocumentCount (066d262), closes #3199
- test: explicit GC between suites — peak heap 5GB → 1.64GB (−12%) (96bf8df), closes #3544
BREAKING CHANGES
- engines.node raised from >=22.0.0 to >=24.15.0 and
engines.npm now requires >=11.0.0. Node 22 and Node 23 are no longer
supported; consumers must upgrade to Node >=24.15.0 (npm >=11 ships with it)
before running npm ci or npm install. See MIGRATIONS.md.
Claude-Session: https://claude.ai/code/session_0185ELiCjZaBJx8PH4xoSsZb
-
downstream projects using
import yaml from 'js-yaml'must
switch to a namespace or named import, and any project YAML relying on merge
keys, unquoted dates or !! tags changes meaning under CORE_SCHEMA. See
MIGRATIONS.md for the full downstream checklist. -
fix(openapi): keep an empty spec file a skipped warning under js-yaml v5
Review gate caught two things the v5 migration missed.
-
load('')throws in v5 where v4 returned undefined, so an empty (or
whitespace-only) OpenAPI file went from 'skip with a warning' to a rethrown
error — i.e. a boot failure for any downstream project with an empty spec.
express.js now checks the raw content before calling load(), which routes
the empty case back through the existing not-a-plain-object guard. Locked
by a test that fails exactly the way boot would if the guard is removed. -
MIGRATIONS.md pointed downstream at
{ schema: DEFAULT_SCHEMA }to restore
YAML 1.1 behaviour, but v5 removed that export. The replacement is
YAML11_SCHEMA. Verified by executing the package, not by reading docs.
Also drops the now-dead default: keys from the three js-yaml jest mocks: v5
has no default export, so a mock that supplies one would let a reintroduced
default import pass tests and still fail at boot.
- fix(openapi): cover comment-only spec files and correct the v5 migration notes
Independent review (CodeRabbit was rate-limited on this public repo) found the
previous commit's guard incomplete and three factual errors in MIGRATIONS.md.
Every claim below was checked by executing js-yaml 5.4.1.
The guard: raw.trim() is truthy for a file containing only a comment, but v5
raises 'expected a document, but the input is empty' for that too — as it does
for a BOM-only file. Since config.files.openapi globs modules//doc/.yml, a
downstream module shipping a stubbed or commented-out spec still bricked boot.
express.js now lets load() run and discriminates on err.reason, so all four
content-free shapes skip with a warning while real parse errors still propagate.
The tests: the mocked case is replaced by express.docsEmptySpec.unit.tests.js,
which runs the REAL parser over zero-byte, whitespace-only, comment-only and
BOM-only input, asserts the warning is emitted, and adds two controls — a
malformed document must still fail loudly, a valid one must still be served.
A mock could only ever throw for the inputs its author imagined, which is
exactly how the comment-only case slipped through. Mutation-checked: removing
the guard fails all four cases.
MIGRATIONS.md corrections:
- a '<<:' merge key does NOT raise under CORE_SCHEMA, it is silently kept as a
literal '<<' key. Saying it raises would send a downstream maintainer looking
for a failure that never comes. - YAML11_SCHEMA is not a drop-in for v4's DEFAULT_SCHEMA. It also restores YAML
1.1 scalar rules v4 did not apply (12:30 -> 750, 014 -> 12, 0o14 -> '0o14',
key y -> true). Documented as a last resort with the divergences listed. - '!!set' does not load as a native Set under the default schema, it throws
unknown tag; the Set only appears under YAML11_SCHEMA, and serialises to {}. - the content-free trigger set and the recommended guard now match the code.
- test(openapi): add the multi-document control to the empty-spec guard
Follow-up nits from the independent review.
A multi-document file is the closest sibling of the guarded condition: v5 raises
'expected a single document in the stream, but found more' from the same function
over the same documents array, two lines below the empty-document throw. It is
therefore the input any future loosening of the reason check would swallow first,
so it earns an explicit control alongside the malformed-syntax one.
Also records why loadAll() was not used. It looks like the tidier fix — it returns
[] for content-free input and drops the message-string dependency entirely — but it
accepts a multi-document file that both v4 and v5 reject, trading a loud failure for
a silent partial parse.
Drops an HTML entity from the test file's JSDoc that only existed to avoid closing
the block comment.