Skip to content

Commit eca6b61

Browse files
refactor(plugin-auth): move AuthManager's email and SMS delivery into auth-manager-email-sms-delivery.ts (#25224 cut 5) (#25395)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuMjbPDrsigsyqoKsV4WZQMz)_ Part of #25224 Clause-②: no Metadata-protocol: none Ruling-ref: 6348891160 The basis for the `Clause-②: no` line: this is a move. No accept set, export, route, refusal, code or answer changes. The `.d.ts` bar is ruling `6348891160` item 1 (on #25222, covering this card). `@objectstack/plugin-auth`'s published `dist/index.d.ts` and `dist/index.d.mts` are raw byte-identical to base (0 diff lines, so the union-order normalisation has nothing to do), with the ruling's positive control. Its entry exports the same 155 names under ESM and CJS (measured below). Cut 5 of the `auth-manager.ts` split: **the email and SMS delivery.** Seven `AuthManager` method bodies (230 lines) and the four module-level declarations that only they read (81 lines) move unedited, the bodies re-indented one level, into one new module, `packages/plugins/plugin-auth/src/auth-manager-email-sms-delivery.ts`. #25224 stays open for the later cuts. This follows the amended claim `6359424739`, which amends `6357765071` on the file cut 4 left (#25383, landed note `6358711845`). The shape is cuts 1 to 4's, which is #23050's. Branch: `claude/project-thread-lg3wrj`, the harness-pinned branch for this session, restarted from `origin/main` `ca381a8a19` after #25383 merged. It names no issue, so the card relation is this body's first line. `auth-manager.ts` goes from 9,212 to 8,968 lines. The cut is +527 / −332 over 5 files: 859 changed lines, under the 3,000-line line. One file is cross-lane (see "Readers re-pointed"). It is not a governed surface (`check-governed-merges.mjs --branch HEAD`: 0 of the 5 paths hit the register). ## Why this seam These are the sends `AuthManager` makes itself, outside better-auth's callbacks: - the notice to the address a change-email request moves the account away from; - the phone OTP send, with the per-number send guard and the admission check in front of it; - the SMS invitation; - and the two helpers both kinds of send share: the read of the recipient's own stored `sys_user.locale`, and the auth SMS body render. The four module-level declarations are the SMS quota wall's: the code the SMS service reports a quota refusal with and its matcher, the 429 the wall answers with, and the one ADR-0112 code both phone OTP send walls carry. Every reader of each, comments included, is in the moved text. So they move with it rather than being exported: `index.ts` re-exports `auth-manager.ts` whole, so an export there would widen the published surface, and a value import of that file from the new module would be a cycle. What stays, and why: - `emailLocaleArg` and the `setDefault*Locale` setters stay. The first reads `authEmailLocaleFromRequest` and `normalizeAuthEmailLocale`, which `auth-manager.ts` exports. - The transport wiring stays: `getEmailService`, `hasEmailTransport`, `setSmsService`, `getSmsService`, `isSmsServiceAvailable` and `isPhoneOtpDeliverable`. These are one- to four-line bodies that other sections of the class call too, and the moved bodies reach the ones they need through the host. - The other email sends (reset, verification, invitation, magic link, existing-account sign-up) are closures inside `createAuthInstance` and `buildPluginList`, and stay for those areas. ## What moved | method | base signature line | body lines | members it reads | |---|---:|---:|---| | `sendChangeEmailNotice` | `:6299` | 37 | `config`, `emailLocaleArg`, `getAppName`, `getEmailService`, `storedRecipientLocale` | | `getOtpSendGuard` | `:6359` | 23 | `_otpSendGuard` (read and written), `config` | | `assertPhoneOtpSendAllowed` | `:6391` | 12 | `getOtpSendGuard`, `isPhoneOtpDeliverable` | | `deliverPhoneOtp` | `:6420` | 78 | `config`, `getAppName`, `getSmsService`, `isPhoneOtpDeliverable`, `renderPhoneSmsBody`, `storedRecipientLocale` | | `sendPhoneInviteSms` | `:6508` | 52 | `getAppName`, `getCanonicalOrigin`, `getConsolePageUrl`, `getSmsService`, `renderPhoneSmsBody`, `storedRecipientLocale` | | `storedRecipientLocale` | `:6661` | 17 | `getDataEngine` | | `renderPhoneSmsBody` | `:6745` | 11 | `config`, `getDataEngine`, `smsLocale` | | module-level declaration | base lines | its readers | |---|---|---| | `SMS_QUOTA_EXCEEDED_CODE` | `:1216`–`:1247` | `isSmsQuotaRefusal`, and the docblock of `smsQuotaExceededApiError` | | `isSmsQuotaRefusal` | `:1265`–`:1275` | `deliverPhoneOtp`, `sendPhoneInviteSms` | | `smsQuotaExceededApiError` | `:1409`–`:1435` | `deliverPhoneOtp`, `sendPhoneInviteSms` | | `PHONE_OTP_SEND_REFUSAL_CODE` | `:1437`–`:1447` | `assertPhoneOtpSendAllowed`, `smsQuotaExceededApiError` | Lines are base `ca381a8a19`. The members come from a walk of every `this.X` in each body with the TypeScript compiler API. No body has a bare `this`, `this[...]`, `arguments` or `super`, or a `this` inside a `function` or a nested class. No body reads a static (`AuthManager.X`). The one assignment to a `this` member is `getOtpSendGuard`'s to `_otpSendGuard`. The four declarations keep their order in the module, and `smsQuotaExceededApiError` reads `PHONE_OTP_SEND_REFUSAL_CODE` only when it is called, as before. `SCIM_PROTOCOL_PATH_PREFIX` and `isScimProtocolPath`, which sat between them, stay. **Imports.** Five imported names are read only by the moved text, so they leave `auth-manager.ts` and the new module imports each from the same place: - `PHONE_SMS_TOPICS`, `interpolatePhoneSms` and `resolvePhoneSmsTemplateBody` (`./phone-sms-texts.js`); - `RECIPIENT_LOCALE_FIELD` and `normalizeRecipientLocale` (`@objectstack/service-messaging`). The comment that explains why that normaliser is reused moves with its import, verbatim. `OtpSendGuard` (`./otp-send-guard.js`) is read by the moved `new OtpSendGuard(…)` and by the field type `_otpSendGuard?: OtpSendGuard` that stays, so both files import it. So do both files import the `StandardErrorCode` type: `SLUG_GUARD_READ_FAULT_CODE` stays. **Evaluation order.** `auth-manager.ts` no longer imports `./phone-sms-texts.js` or `@objectstack/service-messaging` itself. Nothing observable moves: - `@objectstack/service-messaging` is first evaluated through `./idp-locale-claim.js`, which `auth-manager.ts` imports at `:39`, at base and at head alike. - `./phone-sms-texts.js` has no imports, and its top level is declarations with literal initialisers, so the point at which it is evaluated cannot be observed. ## The shape - **The class keeps all seven methods** at their places, with their names, signatures and docblocks: the published `.d.ts` lists each one. Each body is now a host literal of exactly the members that body reads, typed as a `Pick` of `EmailSmsDeliveryHost`, plus `.call`. - **`EmailSmsDeliveryHost`** lives in the new module. It names the 14 members the bodies read, each typed as the class member (`AuthManager['storedRecipientLocale']`, …). The module imports `AuthManager` as a type only, so there is no runtime cycle. - **Methods go as late-bound closures** (`(...a) => this.storedRecipientLocale(...a)`). A test double or spy set on the manager is still the one a body reaches. That includes `storedRecipientLocale`, `renderPhoneSmsBody` and `getOtpSendGuard`, which the moved bodies ask through the manager. - **`config` and `smsLocale` go as getters.** The class reassigns `config` after construction (`setRuntimeBaseUrl`, `applyConfigPatch`) and `smsLocale` on every `setDefaultSmsLocale`, so each is read off the manager when a body reads it, as before. - **`_otpSendGuard` goes as a get/set pair.** `getOtpSendGuard` creates the guard once and stores it, so the guard, and the per-number budget it counts, stays the one the manager holds. - **Six stubs are no longer `async`.** Each returns the moved async function's own promise, so a caller awaits the same promise the body settles. Building the host cannot throw, so no method now throws synchronously where it used to reject. `sendPhoneInviteSms`'s missing-service `throw` is still inside an async function, so it still rejects. `getOtpSendGuard` was synchronous and stays so. - **The cost:** one small host object per call. A phone OTP send builds five: the admission check, the guard lookup, the send, and the send's locale read and body render. - **Nothing re-exports the new module**, so the published surface does not move. - **Two `{@link normalizeRecipientLocale}` references stay in `auth-manager.ts`,** in the docblocks of `storedRecipientLocale` and `emailLocaleArg`. The name is no longer imported there, so an editor no longer resolves those two links. The base file already carries one such unresolved link, `{@link phoneSmsLocaleChain}`, which it never imported. The text is unchanged, and neither docblock reaches the `.d.ts` (both methods are private). ## Readers re-pointed in the same PR | reader | what it reads | base | head | |---|---|---|---| | `api-error-code.pin.test.ts` `authManagerFiles` | a floor (≥ 24) on the `new APIError(` sites over the files `AuthManager`'s bodies live in | 27 sites: `auth-manager.ts` 15, actor guards 9, password and session policy 2, sign-in lockout 1 | 27 sites. The list gains `auth-manager-email-sms-delivery.ts`, which holds the 3 sites that moved (`assertPhoneOtpSendAllowed`, `deliverPhoneOtp`, `smsQuotaExceededApiError`). Without it the floor would pass on 24, in silence. | | `packages/runtime/src/dispatcher-error-vocabulary.ts`, the `UNRESOLVED_CODE_HELPERS` row for `deliverPhoneOtp` (**cross-lane: `domain:cli`**, as the seat agreed on this card) | `check-dispatcher-error-vocabulary.mjs` finds the `objlithelper` helper `deliverPhoneOtp(code)` in its declaring file, and the row must name that file | `file: '…/auth-manager.ts'` | `file: '…/auth-manager-email-sms-delivery.ts'`. The row's evidence said "Its two in-file callers are better-auth `sendOTP` hooks". It now says those two hooks are in `auth-manager.ts` and reach the helper through the class method of the same name. Nothing else in the row or the file changes. | | `auth-email-locale.test.ts`, two comments | the stated population of the five auth sends: "the five `sendTemplate` call sites in `auth-manager.ts`", and "no other file under `plugin-auth/src` calls either" | names `auth-manager.ts` | names `auth-manager.ts` and `auth-manager-email-sms-delivery.ts`, where the change-email notice's send now sits. No assertion changes. | **Each migration is load-bearing.** Measured on the draft: - With the vocabulary row unmigrated, `node scripts/check-dispatcher-error-vocabulary.mjs` exits 1: `stale-helper-row` (the row names a file the helper left) and `unresolved-constant` (the helper is found in the new module with no row). Migrated, it exits 0: 56 sites found, 56 classified, the same as base. - The `authManagerFiles` floor is the silent case: unmigrated, it reads 24 and passes. So the per-file count above is the evidence, not the test's exit code. **Measured as reading no moved line:** - **The tenant-audit census** (`check-tenant-audit-census.mjs` and its two documents): the moved text holds no engine write. `storedRecipientLocale`'s `findOne` and `resolvePhoneSmsTemplateBody`'s template read are reads. Compared per site, base and head read the same 237 sites with the same totals, and no total moves (below). So neither document is regenerated. - **Plugin-factory scans** in `better-auth-schema-parity.test.ts` and `managed-extension-fields.test.ts`: the moved text's three `await import(…)` are of `better-auth/api`, which their pattern does not match. - **`check-auth-mount-ledger.mjs`** and **`auth-manager-base-path.test.ts`**: the moved text does not spell the base-path default. - **`check-dispatcher-error-vocabulary.mjs`'s `liveMemberFile` record** names `BASE_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL` in `auth-manager.ts`, which stays. Its self-test passes (a derived gate below). - **The census's `UNTYPED_RECEIVERS` row for `auth-manager.ts`**: it names the SCIM transaction's `db`, which stays. - **The system-context census**: the moved text holds no `isSystem` read. `storedRecipientLocale`'s `{ isSystem: true, … }` is a context it builds, not a read. The census reads 148 elevation read sites at base and at head. - **`scripts/adr-anchors/`**: no entry's invariant describes these sends, so no entry is added. The `auth-manager.ts` entry (ADR-0093 and ADR-0108) is unchanged and still holds: the moved text cites neither. - **Callers through the manager**: the suites that drive the seven methods, among them `phone-otp-no-sms-service-refusal.test.ts` (a spy on `deliverPhoneOtp`), `phone-sms-template-organization.integration.test.ts`, `auth-email-locale.test.ts`, `auth-manager.test.ts`, `auth-plugin.test.ts` and the two `admin-import-users-*` suites, call each through the class method. All pass at head (below). - **Every symbol anchor into the file.** Before the cut, every `auth-manager.ts#symbol` anchor in the tracked tree outside changelogs was mapped with `symbolSegmentResolution` from `scripts/symbol-anchors.mjs`. There are 29, in ADR-0127, ADR-0134, ADR-0135 and four platform-checklist areas, and all 29 are `declaration`-class at base, with the 7 bodies and 4 declarations blanked, and at head. None names a moved method or declaration. Three name a token the moved text also carries, and each resolves on a declaration that stays: `#secondaryStorage` (ADR-0127), `#AuthManager` (ADR-0135) and `#isPhoneOtpDeliverable` (`identity-auth.json:1345`). **One reading the move does shift: `check-adr-anchors`' decision-letter layer.** That layer checks each `ADR-NNNN Dk` citation in an anchored file's text against the cited record's decisions. It reads anchored files only, by design (the checker's header: "Scope is the anchored surface deliberately"). - The change-email notice body carries one such citation, `[ADR-0131 D7, Q2 A]` (a notice to the account holder is identity mail). It moves from `auth-manager.ts`, which is anchored, to the new module, which is not. - So that layer reads 2,017 citations at base and 2,016 at head. The citation still resolves in the number layer, which covers the whole tree: 52,096 citations across 6,621 files at base, 52,097 across 6,622 at head. Both readings exit 0. - An anchor entry for the new module would bring it back. But it would need an `invariant` paragraph that no existing entry carries for these sends. Writing one is new governance text, not a move, so this PR does not add it. The modules of cuts 1 to 3 carry no anchor entry either. ## Proof Builds and suites ran through `os-verify-lock`, and every exit code was captured before any pipe. Base `ca381a8a19`, head `b2f9b260de`. A full `turbo run build` ran first. Then `plugin-auth` was built and its suite run at base, then built and run at head, on the same dependency dists. **1. The moved text.** - For each of the 7 methods, the base class body and the new module's function body are the same TypeScript token sequence (scanner tokens, kind and text, trivia skipped, each template literal re-scanned at its substitutions as the parser does). - Every line is the base line minus exactly two leading spaces, and the parameters and return type are the same text. - Each class method keeps its docblock and signature, with `async` dropped from the six that had it. - Each of the 4 module-level ranges is in the module verbatim and absent from `auth-manager.ts`. - Result: 7 of 7 and 4 of 4 pass, 230 body lines. | method | body lines | tokens | |---|---:|---:| | `sendChangeEmailNotice` | 37 | 227 | | `getOtpSendGuard` | 23 | 120 | | `assertPhoneOtpSendAllowed` | 12 | 75 | | `deliverPhoneOtp` | 78 | 207 | | `sendPhoneInviteSms` | 52 | 151 | | `storedRecipientLocale` | 17 | 100 | | `renderPhoneSmsBody` | 11 | 77 | - **Negative control:** one token changed in the module (`otpCfg.expiresIn ?? 300` → `?? 301` in `deliverPhoneOtp`) makes that method fail (tokens and lines DIFFER), and the run exits 1. Restored afterwards; `cmp` against the saved copy was clean. - **An independent reading:** `git diff --color-moved=plain --color-moved-ws=allow-indentation-change ca381a8 b2f9b26` over the two source files marks 317 of the 323 deleted lines and 345 of the 513 added lines as moved. - The other 6 deleted lines are the six `async` signature lines. - The other added lines are the stubs' host literals, `EmailSmsDeliveryHost`, the module header, the per-function pointer docblocks and the import blocks. **2. `.d.ts` and exports (ruling `6348891160` items 1 and 2).** | file | raw base | raw head | raw diff lines | normalised base | normalised head | |---|---|---|---:|---|---| | `dist/index.d.ts` | `8a39f1035b6a64888117bb152de21865289e67bc8111d5a14ea457e0fb084d51` | `8a39f1035b6a64888117bb152de21865289e67bc8111d5a14ea457e0fb084d51` | 0 | `486138d3045a7ae05205c64d640eb98c77a85e3ac797a0be0e9ee351e8230bca` | `486138d3045a7ae05205c64d640eb98c77a85e3ac797a0be0e9ee351e8230bca` | | `dist/index.d.mts` | `074a15f3d7e075752bfb5aa299151d77d013fedfae947a8bcc8d28d8446b15df` | `074a15f3d7e075752bfb5aa299151d77d013fedfae947a8bcc8d28d8446b15df` | 0 | `8cb244809d4e1cee802060cfe8c37ff784d6f16d341672b7e2d74700ba2e1229` | `8cb244809d4e1cee802060cfe8c37ff784d6f16d341672b7e2d74700ba2e1229` | - The normaliser, verbatim (the ruling's one-liner): `node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum`. - The raw files are already byte-identical, so no union-order swap occurs in this cut: items 1(b) and 1(d) hold with nothing to account for. - **Positive control:** the head `dist/index.d.ts` with one private member made public (`private deliverPhoneOtp;` → `deliverPhoneOtp;`, line 2703, a one-line change) hashes raw `72873388418fd744bdf706e2f4099a8650e58da20cf49526a1b8aed2b9b31c7e` and normalised `b15a8158861a2a047848872c97c420a06660ff583e9fa806bad0766fddd323b4`. Both differ from the readings above. - **Export lists:** ESM `dist/index.mjs` and CJS `dist/index.js` each have 155 names at base and at head, the same list (the sha256 of the sorted names is `ed3ac8b902031d30…` on all four). - **The bundle:** 20 files in `dist/` on both sides, the same list. Only `index.js`, `index.mjs`, their two `.map` files and the two build-input hash stamps differ, as a move of code inside the bundle must. **3. Tests and lint.** - `pnpm exec vitest run --maxWorkers=3` in `packages/plugins/plugin-auth`: base `Test Files 187 passed (187)`, `Tests 3526 passed | 10 skipped (3536)`, VERDICT command-exit 0. Head: the same counts, VERDICT command-exit 0. - `pnpm --filter @objectstack/plugin-auth typecheck` (both `tsc` projects and `check:test-typecheck`) and `pnpm --filter @objectstack/runtime typecheck`: exit 0. - `eslint --no-inline-config` (the root `lint` script's binary and flags) over the 5 TypeScript files: exit 0. **4. The census and the anchors, at base and at head.** - The tenant-audit census (`node scripts/tenant-audit-census.mjs --json`), compared per site with the new module's file name mapped back to `auth-manager.ts`: 237 sites on both sides, 0 per-site differences in object, verb, tenancy, context and elevation, 0 unresolved and 0 unledgered on both, and every total the same. `check-tenant-audit-census` passes at head on the committed documents. - These checkers were run by hand at base and at head, because `dispatch-gates` does not derive the ADR one. All six exit 0 at both. - `check-adr-symbol-anchors`: 2,383 anchors across 142 records, 466 symbol (440 declaration, 26 literal), the same at both. - `check-adr-anchors`: 80 anchored files at both, and the decision-letter shift above. - `check-scripts-symbol-anchors` (4,058 anchors), `check-spec-docblock-symbol-anchors` (5,071 anchors, both read in the same built tree), `check-system-context-census` (148 elevation read sites) and `check-platform-checklist` (16 areas, 329 items): the same reading at both. **5. Gates.** - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 59 commands for the 5 paths. All 59 ran at `b2f9b260de` after the full build, and all 59 exited 0. - `--ran`: 59 derived, 59 run, 0 NOT-MEASURED. - `check-governed-merges.mjs --branch HEAD`: NOT governed, 859 changed lines. - **Docs drift** (`node scripts/docs-audit/affected-docs.mjs --json ca381a8`, exit 0): 7 hand-written pages and 1 release-owned page (`releases/v17/17-7.mdx`). Each is reached through a literal the moved text carries unchanged: `RATE_LIMIT_EXCEEDED` (the value of `PHONE_OTP_SEND_REFUSAL_CODE`) or `SMS_SERVICE_REQUIRED` (in `deliverPhoneOtp`). No page restates a rule this move changes, so no page edit is owed. - **Serial constraints** (in the claim): no open PR touches `auth-manager.ts`, the new module, `api-error-code.pin.test.ts`, `auth-email-locale.test.ts` or `dispatcher-error-vocabulary.ts`. `origin/main` has since moved to `5f6c2f1952`. Of its 12 new commits, only #25388 touches `plugin-auth` (`auth-manager-actor-guards.ts`, `objectql-adapter.ts`, `last-admin-guard.ts` and four suites), none of this cut's files. `git merge-tree` of it with this head is clean, and on that merge `check-dispatcher-error-vocabulary` (56 found, 56 classified) and `api-error-code.pin.test.ts` pass. ## Changeset None: `skip-changeset`, per ruling `6348891160` item 4. The published declarations and export lists are unchanged, and the bundle's bytes move only because code moved inside it. --- _Generated by [Claude Code](https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2abc0d3 commit eca6b61

5 files changed

Lines changed: 527 additions & 332 deletions

File tree

‎packages/plugins/plugin-auth/src/api-error-code.pin.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ describe('every new APIError( in plugin-auth/src carries an ADR-0112 code (#2424
180180
'auth-manager-actor-guards.ts',
181181
'auth-manager-sign-in-lockout.ts',
182182
'auth-manager-password-session-policy.ts',
183+
'auth-manager-email-sms-delivery.ts',
183184
];
184185
expect(sites.length).toBeGreaterThanOrEqual(30);
185186
expect(new Set(sites.map((s) => s.file)).size).toBeGreaterThanOrEqual(4);

‎packages/plugins/plugin-auth/src/auth-email-locale.test.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -858,10 +858,12 @@ describe("#14641 — an invitation reads the INVITEE's own sys_user.locale", ()
858858
*
859859
* ⚠️ POPULATION of the class claim these blocks close, stated rather than
860860
* implied: the auth mail surface is the five `sendTemplate` call sites in
861-
* `auth-manager.ts` — `IEmailService` declares exactly two delivering members
862-
* (`send`, `sendTemplate`), plugin-auth's only `.send(` calls are the SMS
863-
* service's, and no other file under `plugin-auth/src` calls either. Those
864-
* five are the same five `driveAllFive` above drives and `TEMPLATES` names.
861+
* `auth-manager.ts` and `auth-manager-email-sms-delivery.ts` (the body of the
862+
* change-email notice) — `IEmailService` declares exactly two delivering
863+
* members (`send`, `sendTemplate`), plugin-auth's only `.send(` calls are the
864+
* SMS service's, and no other file under `plugin-auth/src` calls either.
865+
* Those five are the same five `driveAllFive` above drives and `TEMPLATES`
866+
* names.
865867
* Before this change four of them read the column and `sendMagicLink` did not;
866868
* `the whole surface reads it` is pinned below by driving all five against one
867869
* recipient-keyed engine, not by asserting it about this send alone.
@@ -1120,8 +1122,9 @@ describe('#15106 — the CLASS claim: all five auth sends read the recipient col
11201122
});
11211123

11221124
it('every one of the five sends is written in the stored language of its own recipient', async () => {
1123-
// ⚠️ POPULATION: the five `sendTemplate` sites in `auth-manager.ts` — the
1124-
// same five `driveAllFive` drives and `TEMPLATES` names, and the whole of
1125+
// ⚠️ POPULATION: the five `sendTemplate` sites in `auth-manager.ts` and
1126+
// `auth-manager-email-sms-delivery.ts` — the same five `driveAllFive`
1127+
// drives and `TEMPLATES` names, and the whole of
11251128
// plugin-auth's mail surface (see this section's header). This is the pin
11261129
// that closes the class; the per-send pins above only cover one member.
11271130
//

0 commit comments

Comments
 (0)