Skip to content

refactor(plugin-auth): move AuthManager's email and SMS delivery into auth-manager-email-sms-delivery.ts (#25224 cut 5) - #25395

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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 ca381a8a19 b2f9b260de 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 ca381a8a19, 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 fix(plugin-auth): the last-administrator guard answers a read fault 503 SERVICE_UNAVAILABLE, the code its sibling guards answer with #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

🤖 Generated with Claude Code

https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr

… auth-manager-email-sms-delivery.ts

Seven method bodies move unedited: sendChangeEmailNotice,
getOtpSendGuard, assertPhoneOtpSendAllowed, deliverPhoneOtp,
sendPhoneInviteSms, storedRecipientLocale and renderPhoneSmsBody. The
four module-level declarations only they read move with them: the SMS
quota refusal code and its matcher, the quota wall's 429, and the one
phone OTP send refusal code. The class keeps all seven methods, their
docblocks and signatures, and hands each body the members it reads.
The APIError pin's file list, the dispatcher error vocabulary row for
deliverPhoneOtp and the five-sends population comments follow the
moved text.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-auth, @objectstack/runtime, touching 39 documentable anchor(s).

8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))
  • content/docs/api/error-catalog.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))
  • content/docs/api/error-handling-client.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))
  • content/docs/api/wire-format.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))
  • content/docs/permissions/authentication.mdx (via SMS_SERVICE_REQUIRED (literal, a string literal in deliverPhoneOtp))
  • content/docs/protocol/kernel/error-handling.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))
  • content/docs/protocol/kernel/http-protocol.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))
  • content/docs/protocol/kernel/realtime-protocol.mdx (via RATE_LIMIT_EXCEEDED (literal, a string literal in PHONE_OTP_SEND_REFUSAL_CODE))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-7.mdx (via SMS_SERVICE_REQUIRED (literal, a string literal in deliverPhoneOtp))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: sys_user (literal, 42 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 38 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5f6c2f1952355504a7db5a956c4a86c11feefd57 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8a6b1cc6fe65ebd3dc5b56481e09f39891e4048e — the merge of head b2f9b260de6999cf8df224a525bd484c1594da68 into base 5f6c2f1952355504a7db5a956c4a86c11feefd57, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8a6b1cc6fe65ebd3dc5b56481e09f39891e4048e && git checkout 8a6b1cc6fe65ebd3dc5b56481e09f39891e4048e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5f6c2f1952355504a7db5a956c4a86c11feefd57 b2f9b260de6999cf8df224a525bd484c1594da68 && git checkout -B drift-repro 5f6c2f1952355504a7db5a956c4a86c11feefd57 && git merge --no-ff b2f9b260de6999cf8df224a525bd484c1594da68

node scripts/docs-audit/affected-docs.mjs --json 5f6c2f1952355504a7db5a956c4a86c11feefd57

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 5f6c2f1952355504a7db5a956c4a86c11feefd57 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-23T07:40Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25224 cut 5 (claim 6359424739, Ruling-ref: 6348891160; os-dev-report 6359866002; card session branch claude/project-thread-lg3wrj) · reviewed head b2f9b260de6999cf8df224a525bd484c1594da68 · merge base ca381a8a19

  • Shape: the email and SMS delivery area moves unedited into auth-manager-email-sms-delivery.ts.
    • Seven method bodies: sendChangeEmailNotice, getOtpSendGuard, assertPhoneOtpSendAllowed, deliverPhoneOtp, sendPhoneInviteSms, storedRecipientLocale and renderPhoneSmsBody.
    • The four module-level declarations that only those bodies read.
    • The class keeps each shell, in cut 1's shape. 5 files, +527 / −332.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree. turbo run build --filter=@objectstack/plugin-auth ran at ca381a8a19 and at this head, and the hashes are the same at base and head:
    • dist/index.d.ts: raw 8a39f1035b6a64888117bb152de21865289e67bc8111d5a14ea457e0fb084d51, normalised 486138d3045a7ae05205c64d640eb98c77a85e3ac797a0be0e9ee351e8230bca.
    • dist/index.d.mts: raw 074a15f3d7e075752bfb5aa299151d77d013fedfae947a8bcc8d28d8446b15df, normalised 8cb244809d4e1cee802060cfe8c37ff784d6f16d341672b7e2d74700ba2e1229.
    • The raw diff is 0 lines.
    • Seat's positive control: head line 2664, private sendChangeEmailNotice, made public. Raw 05ebbba2c07dd219… and normalised 33de0d895c808488… both moved.
    • Export lists: 155 = 155 (report).
  • Path readers migrated in this PR:
    • api-error-code.pin.test.ts adds the new module. Without that, the floor of 24 would have passed silently at 24.
    • packages/runtime/src/dispatcher-error-vocabulary.ts (cli lane, also this project's) gets its deliverPhoneOtp row's file repointed, and its "in-file callers" evidence sentence is corrected to match the code. The seat cleared this ahead of the cut.
    • Two population comments in auth-email-locale.test.ts.
    • Seat re-run: check-dispatcher-error-vocabulary exits 0 at base and at head.
  • The judgement the card thread flagged: the ADR decision-letter layer. check-adr-anchors verifies ADR-NNNN Dk citations only in anchored files. The change-email notice body carries one ADR-0131 D7 citation into the unanchored new module. Seat re-run: the gate exits 0 at both ends, and its decision-letter count reads 2017 at base and 2016 at head.
    • The seat accepts this without a new anchor entry. An entry needs an invariant paragraph. The auth-manager.ts entry governs ADR-0093 / ADR-0108 only and has no paragraph about these sends, so writing one would be new normative text in a move-only cut (card ask 1).
    • The gate is not left silently empty. Its file-level layer still resolves every citation (52096 → 52097 across 6621 → 6622 files, the new module included). Only the letter-verification layer loses one site, and that drop is recorded here and in the claim.
    • Later cuts are to report this count at base and head too.
  • Anchors and census, re-run by the seat: check-adr-symbol-anchors exits 0 at both, 2383 … 466 symbol. check-tenant-audit-census exits 0 at both with 237 sites, and the moved bodies make no engine writes.
  • Tests and gates (report): plugin-auth 187 files, 3526 pass / 10 skipped, the same as base. dispatch-gates --ran 59 / 59. CI on this head: 38 success and 15 expected skips, none failed or pending. Mergeable clean, and git merge-tree onto today's main is clean.
  • Body: the seat added the attribution header and requested os-sales through the relay (read back identical).
  • Declarations: refactor(plugin-auth), Clause-②: no, Metadata-protocol: none, skip-changeset. No contract-review record or dogfood battery is owed. Out-of-scope findings: none.
  • Landing: not governed (check-governed-merges --pr 25395: 859 lines). Ready + auto-merge through the relay. The card stays open and dispatched for the later cuts.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 07:42
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 07:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit eca6b61 Oct 23, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lg3wrj branch October 23, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants