Skip to content

fix(plugin-auth): SSO domain verification accepts the TXT record request-domain-verification names - #25161

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25111-sso-dns-record
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25111-sso-dns-record

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25111

Clause-②: no — the verify door now accepts the record the platform already tells the operator to publish, as @better-auth/sso designed it. The one record it stops accepting is undefined=TOKEN, which only the defect produced and nothing instructs (table below).
Metadata-protocol: none

What was wrong

POST /api/v1/auth/admin/sso/request-domain-verification tells the operator to publish a TXT record named _better-auth-token-PROVIDER.DOMAIN with the value _better-auth-token-PROVIDER=TOKEN (register-sso-provider.ts, dnsRecordValue). The vendor route behind the bridge, POST /api/v1/auth/sso/verify-domain (@better-auth/sso 1.7.12, verifyDomain), accepts a TXT record at that name whose trimmed value equals either activeVerification.identifier + '=' + activeVerification.value or the bare activeVerification.value. activeVerification is the row internalAdapter.findVerificationValue(identifier) reads back: a findMany on verification keyed on identifier, newest first, limit 1.

sys_verification.identifier is declared internal, so the engine strips it from that read, and internal-field-readback.ts re-attached only value. The vendor therefore compared the published record against undefined=TOKEN.

Measured (the dispatch's four mechanism assumptions)

Base 9cfc57533, better-auth / @better-auth/sso 1.7.12. Real ObjectQL over @objectstack/driver-sql (better-sqlite3) with this plugin's manifest, so the strip is live. Real AuthManager with the real sso({ domainVerification: { enabled: true } }), and the plugin's real route registration on a Hono app for the bridge. DNS is a double on node:dns/promises.resolveTxt. This is the new suite run against the unfixed source:

TXT record at _better-auth-token-PROVIDER.DOMAIN vendor /sso/verify-domain bridge /admin/sso/verify-domain
_better-auth-token-PROVIDER=TOKEN (the instructed record) 502 DOMAIN_VERIFICATION_FAILED 502 DOMAIN_VERIFICATION_FAILED
undefined=TOKEN 204 200, verified: true
bare TOKEN 204 not run
wrong token, or another provider's label in the value, or the instructed value at another provider's name 502 502
  1. Assumption 1 holds. The vendor's real handler received the row without identifier. The undefined=TOKEN record verified, and only an undefined identifier can produce that string.
  2. Assumption 2 holds. The read-back census is the mechanism, and its note that no flow reads identifier back was false. I re-traced it on 1.7.12: across better-auth, @better-auth/sso, /oauth-provider and /scim, verifyDomain is the only code that reads verification.identifier off a row. The other .identifier hits are the oauth-provider resource model, a doc comment and test-utils.
  3. Assumption 3, explained from the vendor code. The bare token verifies because the vendor accepts VALUE beside IDENTIFIER=VALUE by design (normalizedRecord === verificationRecord || normalizedRecord === verificationValue), and value was always re-attached.
  4. Assumption 4, run. The bridge re-dispatches to the vendor route and gives the same answers: 502 for the instructed record, and 200 for undefined=TOKEN.

The remedy, under the four axes

Two candidates were measured against the vendor code:

  • A. The read-back seam (this PR): re-attach sys_verification.identifier to the rows the adapter hands better-auth.
  • B. The instruction text: change dnsRecordValue to the bare token.

What the verification proves under each. The vendor queries DNS only at identifier + '.' + domain. That identifier is computed from the provider id (getVerificationIdentifier); it is never read from the row. The token is a fresh random value stored under that provider's identifier. So under both A and B the provider binding is the queried name plus the per-provider token, and the vendor already accepts the bare token. The dispatch's premise that the bare-token instruction "would make the published record prove less" does not hold against the vendor code: the label inside the value repeats what the queried name already binds. Neither remedy weakens the proof, so this is not the needs_decision case.

Chosen: A.

  • Real business need. An operator follows the record the platform shows them, which content/docs/permissions/sso.mdx repeats ("publish dnsRecordName / dnsRecordValue"). A makes that record verify. B would change the shown record and leave the vendor's IDENTIFIER=VALUE branch dead on this store, with undefined=TOKEN still verifying.
  • Long-term soundness (contract-first, no workarounds). The defect is in the producer of the row the vendor reads, the storage seam, not in the instruction. The census is the platform's mechanism for handing an internal column back to the vendor's adapter. B would bend a correct instruction around a seam defect, which is a consumer-side accommodation.
  • Keeping AI authors from going wrong. A makes the census true: one named reader, traced on the installed version. The next agent who reads it is no longer told "no flow reads identifier back". B would leave that false census in place.
  • Startup focus. A is one row in an existing table: no new surface, no new gate, no docs change. B changes the value of a public response field (dnsRecordValue) that operators and the docs read.

What A hands to whom. Every verification read better-auth issues is keyed on identifier (findVerificationValue, consumeVerificationValue), so A hands the vendor a value it passed in itself. A findOne keyed on it is answered from the lookup with no privileged read. No vendor route returns a verification row to its caller: I checked every findVerificationValue / consumeVerificationValue result variable in those four packages against ctx.json( and found none passed whole or spread. The generic data API still strips the column, and the suite's precondition case pins that.

What the verify door accepts, before and after

record at the instructed name before after
_better-auth-token-PROVIDER=TOKEN (instructed) refused 502 accepted
bare TOKEN accepted accepted (unchanged; the vendor's second form)
undefined=TOKEN accepted refused 502
wrong token / another provider's label / another provider's name refused 502 refused 502

The Clause-②: no line above follows the claim. The narrowing is only undefined=TOKEN, a string that exists only because of the defect.

Change

  • packages/plugins/plugin-auth/src/internal-field-readback.ts: the sys_verification readback row gains identifier with absenceProvesStrip: true, because the column is required: true. The census comment now names the one reader, traced on 1.7.12, and says why handing the column back discloses nothing. The fail-closed consequence text also names domain verification.
  • packages/plugins/plugin-auth/src/sso-domain-verification-instructed-record.test.ts (new): the pins on the vendor's real handler, both doors. Refusals assert code and status, the DNS name the vendor queried, and that the provider stays unverified at rest.
  • packages/plugins/plugin-auth/src/internal-field-readback.test.ts: three seam pins. Both columns come back on a stripped findMany row, one batched read per column. A read keyed on identifier answers it from the lookup. The seam fails closed with no accessor.
  • .changeset/25111-sso-domain-verification-instructed-record.md: patch for @objectstack/plugin-auth.

register-sso-provider.ts is unchanged, and so is the instruction.

Tests

  • Before the fix, new suite on unfixed source: Tests 4 failed | 8 passed (12). The four failures are the instructed record (502 on both doors) and undefined=TOKEN (204 / 200).
  • After the fix (commit 7f0292dde), the new suite plus internal-field-readback.test.ts and internal-credential-readback.test.ts: Test Files 3 passed (3), Tests 46 passed (46).
  • Full @objectstack/plugin-auth suite and typecheck on the final head 731668228 (the fix merged with origin/main 23540582f): Test Files 183 passed (183), Tests 3466 passed | 10 skipped (3476). typecheck: tsc --noEmit, the examples program, and check:test-typecheck: OK (10 files / 88 errors / 23 pinned signatures held, unchanged). Lock verdict command-exit 0. Both new test files are in the tsconfig.test.json program (--listFilesOnly).

Ablation (run once, then restored)

Run on the fix commit 7f0292dde. The ablation used node scripts/ablation-replace.mjs --delete on the anchor for the new identifier readback row, wrapping vitest run over the new suite and internal-field-readback.test.ts. The subject is imported from src/ by relative path, so no build sits between the mutation and the run.

  • The mutation landed: anchor x1 to x0, blob e8e98f4e53e8 to 4d7810f74bf0.
  • Result: Tests 7 failed | 37 passed (44). The instructed record went back to 502 on both doors, undefined=TOKEN went back to 204 / 200, and the three seam pins failed.
  • Restored: blob e8e98f4e53e8 equals HEAD, git diff HEAD is empty, and git status --porcelain is empty.

Gates

Local subset, on the final head 731668228. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 67 commands for these 4 paths (merge base 23540582f). All 67 were run, each exit code captured before any pipe, and all 67 exited 0. --ran reconciliation: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.

pnpm check:dual-build-cjs-loads deserves a note. On the pre-merge head 7f0292dde it exited 3 (PREREQUISITE NOT MET) because not every package had a dist/. The workspace build that check:type-check-debt runs supplied them, and on the final head it measured green (112 require entry points across 69 packages load).

The five artifact-roster gates whose roster sits under a changed directory also exit 0 on the final head: check-changeset-fixed, check:auth-mount-ledger, check:authz-resolver, check:error-code-casing, check:filter-alias-parity.

Every family CI runs is cited from this PR's check-runs, not re-run here.

Acceptance notes

  • packages/platform-objects/src/identity/sys-verification.object.ts still says identifier "has none [readback row], because no better-auth flow reads it back off a row". That sentence was already false (this card) and is now plainly so. It is the engine lane's file, outside this card's file surface, so it is not edited here. Carrier: none.
  • objectql-adapter.ts's findMany passes no lookup to the seam, so identifier costs one id-batched privileged read on each verification findMany / consumeOne. A findOne keyed on it is answered from the lookup. These are one-time flows (OTP, magic link, reset, OAuth state, domain verification), not per-request reads. Passing findMany's where as the lookup would make that read free. It is not done here because objectql-adapter.ts is outside the file surface.
  • The new suite's tests run in about 1.4 s. Its module import is about 18 s on the shared dev box, the same shape as the sibling real-engine suites (internal-credential-readback.test.ts).

Generated by Claude Code

…h so SSO verify-domain accepts the instructed TXT record

@better-auth/sso's /sso/verify-domain builds the record it accepts as
IDENTIFIER=VALUE from the verification row the storage adapter returns.
sys_verification.identifier is internal, so the row reached the vendor
without it and the instructed record _better-auth-token-PROVIDER=TOKEN
answered 502 DOMAIN_VERIFICATION_FAILED while undefined=TOKEN verified.
The read-back census now re-attaches identifier beside value; a read keyed
on identifier answers it from the lookup.

Claude-Session: https://claude.ai/code/session_01UnHcTADjeg3VwxF21QHj7W
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see

Coarse fallback — 18 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 6b44efa7b4a429994dc6d92c3de96392d4e03f3f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a764d438a2feef563749640bf478ad7ce53ee48d — the merge of head 7316682282de9ecdd7b57175b107d64edb48641d into base 6b44efa7b4a429994dc6d92c3de96392d4e03f3f, 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 a764d438a2feef563749640bf478ad7ce53ee48d && git checkout a764d438a2feef563749640bf478ad7ce53ee48d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6b44efa7b4a429994dc6d92c3de96392d4e03f3f 7316682282de9ecdd7b57175b107d64edb48641d && git checkout -B drift-repro 6b44efa7b4a429994dc6d92c3de96392d4e03f3f && git merge --no-ff 7316682282de9ecdd7b57175b107d64edb48641d

node scripts/docs-audit/affected-docs.mjs --json 6b44efa7b4a429994dc6d92c3de96392d4e03f3f

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7316682282de9ecdd7b57175b107d64edb48641d
Local-runs: none
Scope: packages/plugins/plugin-auth/src/internal-field-readback.ts, packages/plugins/plugin-auth/src/internal-field-readback.test.ts, packages/plugins/plugin-auth/src/sso-domain-verification-instructed-record.test.ts + packages/plugins/plugin-auth/src/register-sso-provider.ts (dnsRecordValue, runVerifyDomain), packages/plugins/plugin-auth/src/objectql-adapter.ts (findOne / findMany / consumeOne and the seam calls), packages/plugins/plugin-auth/src/auth-manager.ts (the sso() options and the adapter construction), packages/platform-objects/src/identity/sys-verification.object.ts (the internal declaration), packages/objectql/src/engine.ts (omitInternalFields, resolveInternalField), content/docs/permissions/sso.mdx, @better-auth/sso 1.7.12 dist/index.mjs (verifyDomain, requestDomainVerification, getVerificationIdentifier, checkProviderAccess, parseProviderDomains), better-auth 1.7.12 dist/db/internal-adapter.mjs (findVerificationValue, consumeVerificationValue). The changeset .changeset/25111-sso-domain-verification-instructed-record.md is read as a prose face: its level and declaration lines only, under ②.

Inputs: card #25111 (body and both comments: the dispatch claim and the os-dev-report), PR #25161 (body, file list, and the net diff against main at merge base 23540582f: 4 files, +409/-10), and the check-runs on the head. Vendor code was read from the published npm tarballs of @better-auth/sso, better-auth, @better-auth/oauth-provider and @better-auth/scim at 1.7.12 (the versions packages/plugins/plugin-auth/package.json pins), extracted into scratch and never executed. Nothing was built, run or re-run locally.

Check-runs on the head, read at 2026-10-22T03:16Z: 40 runs; 28 completed success, 7 completed skipped (the opt-in and path-filtered lanes: Temporal Conformance, the Dogfood Regression Gate shards, Console Pin Gate, Dogfood Verify CLI, Build Docs, Packed-tarball smoke, 18.x upgrade harness), and 5 still in_progress: Test Core 1/6, 2/6, 3/6, 4/6 and 6/6. Green among the completed: Check Changeset, Governed Surface Queue Guard, every Lint lane, Type Check workspace / consumer gates / source gates / debt ledger, Build Core, Test Core 5/6, and the five PM claim guards. The in-progress shards were not waited for; their conclusions stay the gate verdicts for the queue.

① Derived judgments

The diff changes one thing at runtime: READBACK_FIELDS[sys_verification] in internal-field-readback.ts gains { field: 'identifier', absenceProvesStrip: true } beside value. Everything below follows from that one row.

(a) What the seam feeds, and whether any caller outside the vendor now reads wider. Right: it widens only what the vendor's storage adapter sees. reattachInternalFieldsOnRead is imported by exactly one module, objectql-adapter.ts, and called at three verbs: findOne (:955, with the read's own where as the lookup), findMany (:1010, no lookup), consumeOne (:1154, no lookup). That adapter is constructed in one place, auth-manager.ts:4777, as the database factory better-auth is handed; no other module imports createObjectQLAdapterFactory, and no non-test module in plugin-auth names sys_verification or SystemObjectName.VERIFICATION outside the seam and the manifest, so no plugin-auth route, service or export holds the adapter or reads the table through it. Every other exit reaches the row through the engine's generic read path, where omitInternalFields (engine.ts:11534, run from maskSecretFields on every find / findOne) deletes every internal: true column with no isSystem carve-out: the data door (sys_verification is apiMethods: ['get'], access: private, defaultRowScope id == null), MCP and export reads over the engine, and the compliance ledger's CRUD mirror that honours the same flag. resolveInternalField (engine.ts:11816) is a separately named privileged verb off the RAW engine, unreachable from a query string, and refuses any column not flagged internal. The PR's precondition case pins the generic engine.find under isSystem: true still omitting identifier after the change, while storage holds it.

Inside the vendor, the hunk's "only reader" claim was re-traced on the four 1.7.12 dists rather than taken from the comment. Every .identifier read off a verification row: @better-auth/sso verifyDomain (index.mjs:2639) is the one; better-auth core reads data.identifier only on the create input (internal-adapter.mjs:717, :902); core's test-utils plugin reads it off a row in a test helper, and plugin-auth mounts no such plugin; oauth-provider's .identifier hits are its resource model and path params, not verification rows; scim has none. No route echoes a verification row to its caller: across the 37 findVerificationValue / consumeVerificationValue call sites in the four dists, no receiving variable is passed to ctx.json( whole or spread (a static census; the .identifier census above is the stronger one for this column). The value the vendor is now handed is one it computed itself (getVerificationIdentifier, index.mjs:2544) and passed in as the lookup key.

(b) Is the newly accepted record exactly the one the platform instructs? Right. register-sso-provider.ts:485-488 answers dnsRecordName = _better-auth-token-PROVIDER.DOMAIN and dnsRecordValue = _better-auth-token-PROVIDER=TOKEN, the token being the vendor's domainVerificationToken; content/docs/permissions/sso.mdx:260-262 tells the operator to publish exactly those two fields. The vendor queries TXT at IDENTIFIER.DOMAIN with IDENTIFIER = _ + (tokenPrefix or better-auth-token) + - + provider.providerId (index.mjs:2544, :2606, :2634) and accepts a trimmed record equal to row.identifier + '=' + row.value or to row.value alone (index.mjs:2638-2642). plugin-auth passes sso({ domainVerification: { enabled: true } }) with no tokenPrefix and no verification.storeIdentifier anywhere in its source (auth-manager.ts:4529-4531), so the stored identifier is the plain computed label, identical to the bridge's SSO_DOMAIN_TOKEN_PREFIX spelling. With the row whole, the vendor's IDENTIFIER=VALUE form is byte for byte the instructed dnsRecordValue. Before the fix, the platform's published text already contradicted its own verify door, which is the shape the card and the dispatch describe.

(c) Is the newly refused undefined=TOKEN record producible by anything but the defect? Right: it is not. The identifier is _ + prefix + - + providerId, so it always starts with _ and can never spell undefined. The string arose only from the template literal over a row the strip had emptied, and only on an engine that strips (the ObjectQL engine); on a non-stripping adapter the vendor never accepted it. No instruction, doc, test or UI ever named it. Removing it removes a defect artefact, not a published member of the accept set.

(d) Does the verification still prove control of the DNS name for that provider? Right. The vendor never reads the DNS name from the row: it queries IDENTIFIER.domain for every domain in parseProviderDomains(provider.domain) (index.mjs:139, :2632-2634) with the identifier computed from the stored provider id, and the token is a fresh 24-character random value stored under that identifier (requestDomainVerification, index.mjs:2573-2578; registerSSOProvider, :3539). A wrong token, another provider's label inside the value, and the instructed value published at another provider's name are each refused 502 DOMAIN_VERIFICATION_FAILED and leave domain_verified false; the new suite pins all three on both doors with the queried DNS name asserted. The label inside the value repeats what the queried name already binds, so the bare-token form the vendor accepts by design is neither weaker nor changed. The dispatch's premise that remedy B would make the published record prove less does not hold against the vendor code, and the dev was right not to raise it as needs_decision.

Full accept-set delta of the verify door, vendor route and bridge alike: _better-auth-token-PROVIDER=TOKEN goes from refused 502 to accepted (204 vendor / 200 bridge, verified: true); undefined=TOKEN goes from accepted to refused 502 DOMAIN_VERIFICATION_FAILED; bare TOKEN, wrong token, wrong label and wrong name are unchanged. A provider already verified stays verified (domain_verified persists; a later verify answers 409 DOMAIN_VERIFIED), so no verified tenant is touched. No request field, response field, route, error code or object declaration changes; register-sso-provider.ts and sys-verification.object.ts are untouched by the diff.

Consistency of the new seam row. identifier is required: true (sys-verification.object.ts:136-139), so absenceProvesStrip: true is the correct posture, the same one value already carries on this table: a stripping engine without the accessor already threw for value, so no new failure class is introduced, and on a non-stripping engine the rows carry the column and the seam is a no-op. The three seam pins cover both columns on the findMany shape, the lookup answer on a read keyed on identifier, and the fail-closed throw. The fail-closed consequence text now names domain verification, which matches the measured consequence.

② Semver level

Clause-②: no, bare, is right; it must not read no (narrowing). The repo's rule (scripts/pm/clause2-line.mjs, the arm's provenance) is that the (narrowing) arm declares a PUBLISHED accept set shrank, which is breaking. The only record this diff stops accepting is undefined=TOKEN, which (c) shows no published text ever named and nothing but the defect could produce; the published accept set, the record request-domain-verification answers plus the vendor's bare-token form, is not narrowed but honoured for the first time. The widening side is the removal of a refusal the platform's own contract text already contradicted (dnsRecordValue in register-sso-provider.ts and sso.mdx), and both declaration lines cite that text: the PR body's line names "the record the platform already tells the operator to publish" with the file and function in the section under it, and the changeset's line names "the record the platform already names" with the route in its body. That is the no case, not yes. With no (narrowing) arm and no ! in the title, the full dogfood battery is not owed; the dev's dogfood_battery: n/a reading is right.

Changeset level: .changeset/25111-sso-domain-verification-instructed-record.md grades @objectstack/plugin-auth patch with Metadata-protocol: none. Right under ruling A on #25105 and the AGENTS.md changeset rule: no authorable key, accepted value, metadata type, default or meaning changes (the internal declaration on sys_verification.identifier is untouched), so the kind is none and a non-protocol services fix ships patch. The Check Changeset run on this head is green, and the two declaration lines agree in value and kind across the PR body and the changeset.

③ Boundary flags

  1. sys-verification.object.ts:113-116 still says identifier has no readback row "because no better-auth flow reads it back off a row" (PR Acceptance notes; dev report out-of-scope finding 1, carrier none). Confirmed stale at origin/main, and now plainly contradicted by the seam table. It is a comment with no behaviour behind it, it sits in packages/platform-objects/**, which the dispatch fenced off for domain:engine, and the remedy did not need the declaration changed, so leaving it out of this diff was the right call. Not blocking. Escalated to the dispatching seat: a follow-up card to domain:engine to restamp that sentence so the next reader is not told the census is empty.

  2. findMany and consumeOne pass no lookup to the seam, so identifier costs one id-batched privileged read per verification findMany / consumeOne (Acceptance notes; dev report finding 2). Confirmed at objectql-adapter.ts:1010 and :1154; findOne (:955) passes its where. findVerificationValue is a findMany keyed on identifier with limit 1 (internal-adapter.mjs:752-765), so each verification lookup now issues two privileged id-batched reads instead of one; consumeVerificationValue adds a findMany then a consumeOne keyed on id, where a lookup could not answer identifier anyway. One-time flows, correctness unaffected, and the file is outside the dispatched surface. Not blocking. Answered: a performance follow-up that passes findMany's where as the seam's lookup is reasonable and separate.

  3. The new suite's module import is about 18 s on the shared dev box (Acceptance notes). Same shape as the sibling real-engine suite internal-credential-readback.test.ts; the Test Core shards that run it are still in progress on this head. Answered: not a contract matter; the shard conclusions say whether the wall is acceptable.

  4. The merge commit 731668228 carries git's default message without the trailer pair (dev report deviation 1). The reviewed object is the net diff against main, which is unaffected, and the authored commits carry the trailers. Answered: not blocking.

  5. The gate runner's PID was found with pgrep -f, verified via /proc before tail --pid, and nothing was killed (dev report deviation 2). Process hygiene inside the dev's own run; no repository state was touched. Answered: not blocking.

  6. The dispatch's Clause-② expectation asked the dev to re-declare from the diff if the remedy changed which records are accepted beyond the instructed one. The dev kept no and stated the whole delta in a table; by ② that reading is right, and no re-declaration is owed.

Implemented-by: claude/issue-25111-sso-dns-record
Reviewed-by: session_01UnHcTADjeg3VwxF21QHj7W

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

domain:services seat 2 (seat post #21118) · os-bill · session_01UnHcTADjeg3VwxF21QHj7W · 2026-10-22T03:28Z · ACCEPT at head 7316682282de9ecdd7b57175b107d64edb48641d · dev report on #25111

  1. Shape: a draft PR to main whose first line is Fixes #25111, assigned os-bill.
    • Clause-②: no and Metadata-protocol: none are at line start in the body and the changeset.
    • Both declarations cite the contract text the old refusal contradicted: request-domain-verification's dnsRecordValue.
  2. Premises measured on the vendor's real 1.7.12 handler over a real engine:
    • before the fix, the instructed record answered 502 and undefined=TOKEN verified;
    • verifyDomain is the only reader of sys_verification.identifier off a row across better-auth, sso, oauth-provider and scim 1.7.12;
    • the bridge door gives the same answers.
    • The dev's partition-3 premise that a bare-token instruction would prove less was falsified with evidence: the vendor queries only the computed name, and the token is per provider. Remedy A (the read-back seam) was chosen on the four axes, and the instruction text is unchanged.
  3. Gates (check-runs pinned to this head):
    • 34 success, including Lint & Repo Gates, TypeScript Type Check, Build Core, Governed Surface Queue Guard, Check Changeset and Test Core 1–6.
    • 7 skipped, each on the check-expected-skips.mjs roster.
    • mergeable_state: clean. check-governed-merges.mjs --pr 25161: NOT governed, +409 / −10.
    • Contract review at CONTRACT_REVIEW_TIER (isolated reviewer): PASS, 6331819380. The seat asked for it because the auth door's accept set moves both ways. The reviewer confirmed:
      • the re-attached column reaches only the vendor's storage adapter, and every generic exit still strips it;
      • the accepted record equals the platform's instruction;
      • undefined=TOKEN is producible by nothing but the defect;
      • the proof of DNS control is unchanged;
      • the bare Clause-②: no is right, not (narrowing), so no dogfood battery is owed.
  4. Accepted deviations: the merge commit from origin/main carries git's default message without the trailer pair. The net diff is unaffected.
  5. Out of scope:
    • the stale sentence in platform-objects' sys-verification.object.ts (domain:engine's file, a comment only) → Acceptance notes. It is not a card under the filing gate (no user-visible defect).
    • the extra privileged read per verification findMany / consumeOne (a one-time flow, performance only) → Acceptance notes.

Landing: ordinary queue landing. The seat sets ready and auto-merge through the relay in this act. After the merge, #25111 closes by Fixes; the seat removes pm:dispatched and records the landing.

Changeset sentences checked against the diff:

  • the verify door accepts the instructed record;
  • the read-back row for identifier on the vendor's adapter only;
  • the one record no longer accepted;
  • the instruction unchanged.

All hold.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 22, 2026 03:29
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 22, 2026 03:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 425c743 Oct 22, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25111-sso-dns-record branch October 22, 2026 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants