You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-auth: SSO domain verification refuses the TXT record request-domain-verification tells the operator to publish (502 DOMAIN_VERIFICATION_FAILED; measured, same on better-auth 1.7.3 and 1.7.12) #25111
Filing gate ①, a defect measured on a public door. Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK). The source is #25096's dev report (6315144841, out-of-scope finding 1), measured during the better-auth 1.7.12 lift (PR #25109). ⛔ Not a claim. Unlabelled and ungraded, for triage.
Reader, and what they do: triage grades and routes this card. It reads as plugin-auth, the domain:services lane.
What was measured (DNS answered by a test double)
POST /api/v1/auth/admin/sso/request-domain-verification tells the operator to publish a TXT record _better-auth-token-PROVIDER=TOKEN.
POST /api/v1/auth/sso/verify-domain, the vendor route that plugin-auth's /admin/sso/verify-domain bridge re-dispatches to, answers 502 DOMAIN_VERIFICATION_FAILED for exactly that record. A record holding the bare token answers 204. The bridge leg itself was not run.
The vendor code is the same on 1.7.3, so this is pre-existing and not caused by the lift.
Where it lives (origin/mainba244f1b48)
sys_verification.identifier is declared internal, so the store does not hand it back on read. internalAdapter.findVerificationValue returns the row with identifier: undefined, and @better-auth/sso builds the record it expects as IDENTIFIER=VALUE, so it compares against undefined=TOKEN.
The seam is register-sso-provider.ts's dnsRecordValue against the identifier read-back (internal-field-readback.ts). The census there says no flow reads identifier back. The dev flagged that sentence in place rather than restamping it.
Governing rule
A record the platform tells an operator to publish is the record it verifies. The remedy (the read-back seam, or the instruction text) is for whoever takes the card to choose under the four axes.
Dedupe
Two MCP search_issues queries returned 2 hits (both closed, other readings) and 0 hits. New.
Claim: PM loop round 1 · 2026-10-22T01:24Z · p2, domain:services, area:identity
Session: session_01UnHcTADjeg3VwxF21QHj7W (the dispatching seat; the dev is a mode:subagent run of this session, told apart by its branch)
Account: os-bill (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-25111-sso-dns-record
Worktree: objectstack-issue-25111
Domain: domain:services
Seat: domain:services#2 (seat post #21118)
File surface (line anchors at origin/main9cfc57533):
packages/plugins/plugin-auth/src/internal-field-readback.ts: the read-back census for sys_verification (about :198–:215, the identifier note).
packages/plugins/plugin-auth/src/register-sso-provider.ts: read; changed only if the measured remedy is the instruction (dnsRecordValue, about :488).
Test files under packages/plugins/plugin-auth/src/; .changeset/25111-*.md.
Stop on a breach and explain it in the report.
Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier on this surface: "no path-derived mandate … floor sonnet · default opus · ceiling fable")
Clause-②: no — expected: the verify door accepts the record the platform already tells the operator to publish. If the measured remedy changes which records are accepted beyond that, the dev re-declares from the diff.
Responsibility: plugin-auth's internal-field read-back hides sys_verification.identifier from the vendor's findVerificationValue, so @better-auth/sso compares against a record built from an undefined identifier | the read-back census mechanism (internal-field-readback.ts) is the platform path for exactly this; it excludes identifier today | every operator who verifies an SSO domain with the record request-domain-verification names; measured on 1.7.3 and 1.7.12 (#25096's report 6315144841)
Thread-read: none
Serial constraints cleared:
No open PR touches internal-field-readback.ts or register-sso-provider.ts (the open PRs' file lists were read this round). Their last change on main is the 1.7.12 lift, 03966ceae.
os-dev-report
{
"issue": 25111,
"status": "done",
"branch": "claude/issue-25111-sso-dns-record",
"pr": "#25161",
"session": "session_01UnHcTADjeg3VwxF21QHj7W — the parent's harness-stamped id (mode:subagent run); the relay envelope and both commit trailers carry it",
"premise_still_valid": true,
"summary": "Remedy A, the read-back seam: internal-field-readback.ts now re-attaches sys_verification.identifier (absenceProvesStrip true, the column is required) beside value, so @better-auth/sso's verifyDomain gets the row whole. The instruction text (register-sso-provider.ts dnsRecordValue) is unchanged. All four PM mechanism assumptions were measured on the vendor's real 1.7.12 handler over a real ObjectQL + driver-sql engine. (1) Holds: before the fix the instructed record _better-auth-token-PROVIDER=TOKEN answered 502 DOMAIN_VERIFICATION_FAILED, and a record spelling undefined=TOKEN verified (204 vendor / 200 bridge). (2) Holds: the census note was false. On 1.7.12, verifyDomain is the only code in better-auth, sso, oauth-provider or scim that reads verification.identifier off a row. (3) Explained: the bare token passes because the vendor accepts VALUE beside IDENTIFIER=VALUE by design. (4) The bridge leg was run and gives the same answers. One partition-3 premise is falsified: the bare-token instruction would NOT prove less. The vendor queries DNS only at the computed name _better-auth-token-PROVIDER.DOMAIN, the token is per provider, and the bare token is already accepted. Neither remedy weakens the proof, so this is not needs_decision. A was chosen on the four axes: contract-first at the producer seam, the census made true, no change to a public response field or the docs, one table row. Accept-set delta: the instructed record is now accepted, and undefined=TOKEN (only the defect produced it) is now refused. Clause-②: no is kept as the claim declared, and the PR body states the delta.",
"tests": "Final head 7316682 (fix 7f0292d merged with origin/main 2354058). Full pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: Test Files 183 passed (183), Tests 3466 passed | 10 skipped (3476). pnpm --filter @objectstack/plugin-auth run typecheck: tsc --noEmit + examples + check:test-typecheck OK (10 files / 88 errors / 23 signatures held). Lock VERDICT command-exit 0. Both new test files are in the tsconfig.test.json program (--listFilesOnly). Before the fix (BASE 9cfc575 source), the new suite gave Tests 4 failed | 8 passed (12): instructed record 502 on both doors, undefined=TOKEN 204/200. After the fix at 7f0292d: new suite + internal-field-readback + internal-credential-readback = 3 files, 46/46. Ablation at 7f0292d: node scripts/ablation-replace.mjs --delete on the identifier readback row (anchor x1 to x0, blob e8e98f4e53e8 to 4d7810f74bf0; subject imported from src by relative path, no dist in the path) gave 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 3 seam pins failed. Restore proven: blob == HEAD e8e98f4e53e8, git diff HEAD empty, porcelain empty.",
"gates": "Local subset at 7316682: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 67 commands (merge base 2354058, 4 paths). All 67 were run with exit codes captured before any pipe, and all 67 exited 0. --ran (record spelled CMD :: exit N) answered: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. On the pre-merge head 7f0292d, pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, missing dists); the workspace build that check:type-check-debt runs supplied them, and it exited 0 at 7316682. The 5 artifact-roster gates whose roster sits under a changed directory all exited 0 at 7316682: check-changeset-fixed, check:auth-mount-ledger, check:authz-resolver, check:error-code-casing, check:filter-alias-parity. CI check-runs on 7316682 when this report was written: Check PR Size success; No other open PR may claim the same issue success; No other open PR may claim the same single-writer path success; Part-of PR must not also close its card success; The card this PR closes must claim this branch success; 18.x upgrade harness skipped; Packed-tarball smoke skipped; Check Changeset, Governed Surface Queue Guard, all Lint lanes and all Type Check lanes in_progress; Auto Label queued. Test Core and Build Core not yet listed.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "0 — no MCP GitHub tool was called; every read was REST via gh api (card 25111, its comments, comment 6315144841, PR 25161, check-runs)",
"api_writes": "2 — two relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/25161/assignees [os-bill], body read back 11393/11393 bytes identical; (2) this os-dev-report = POST /repos//issues/25111/comments. Not REST: three git pushes of the branch (empty probe, 7f0292d, 7316682).",
"files_changed": [
"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",
".changeset/25111-sso-domain-verification-instructed-record.md"
],
"line_budget": "n/a — no skills/** file or governed ledger touched",
"deviations": [
"The merge commit 7316682 (origin/main into the branch, made before opening the PR per AGENTS.md section 10) carries git's default merge message, without the Claude-Session / Co-authored-by trailer pair; pre-push accepted it. Not rewritten.",
"The gate runner's PID was looked up once with pgrep -f, then verified as the runner via /proc/PID/cmdline before tail --pid waited on it. Nothing was killed."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed: packages/platform-objects/src/identity/sys-verification.object.ts still says identifier has no readback row because no better-auth flow reads it back off a row. That was already false and is plainly so now. It is the engine lane's file and outside this card's surface. Recorded in the PR's Acceptance notes only.",
"carrier: 承接者:无 · noted, not filed: objectql-adapter.ts findMany passes no lookup to the read-back seam, so identifier costs one id-batched privileged read per verification findMany / consumeOne (a findOne keyed on it is answered from the lookup). These are one-time flows, so this is performance, not a defect. Recorded in the PR's Acceptance notes only."
]
}
ACCEPT → PR #25161 comment 6331955547 · domain:services seat 2 · session_01UnHcTADjeg3VwxF21QHj7W · 2026-10-22T03:29Z · contract review PASS at CONTRACT_REVIEW_TIER6331819380 · ready and auto-merge set through the relay; the landing note follows the merge.
merged 2026-10-22T03:53Z through the merge queue, merge commit 425c743fd9
origin/main
head 425c743fd, subject "fix(plugin-auth): SSO domain verification accepts the TXT record request-domain-verification names"
This card
closed completed by Fixes; no other card closed in that window
State: pm:dispatched is removed in this act. The record request-domain-verification names now verifies, on the vendor route and the bridge alike. The two out-of-scope readings (a stale comment in platform-objects, and one extra read per verification lookup) are in PR #25161's Acceptance notes.
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: PM loop round 1 · 2026-10-22T01:24Z · p2,
domain:services,area:identitySession:
session_01UnHcTADjeg3VwxF21QHj7W(the dispatching seat; the dev is amode:subagentrun of this session, told apart by its branch)Account:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-25111-sso-dns-recordWorktree:
objectstack-issue-25111Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface (line anchors at
origin/main9cfc57533):packages/plugins/plugin-auth/src/internal-field-readback.ts: the read-back census forsys_verification(about:198–:215, theidentifiernote).packages/plugins/plugin-auth/src/register-sso-provider.ts: read; changed only if the measured remedy is the instruction (dnsRecordValue, about:488).packages/plugins/plugin-auth/src/;.changeset/25111-*.md.packages/platform-objects/**(sys-verification.object.tsisdomain:engine's; if the remedy needs its declaration changed, stop and report). Notauth-manager.ts(plugin-auth: MCP clients cannot register over OAuth DCR — /oauth2/register defaults a missing application_type to web and refuses the http://localhost redirect every local MCP client sends #25118 is in flight there), notpackages/spec/**, notpnpm-workspace.yamlor any lockfile.Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT;dispatch-gates --tieron this surface: "no path-derived mandate … floor sonnet · default opus · ceiling fable")Clause-②: no — expected: the verify door accepts the record the platform already tells the operator to publish. If the measured remedy changes which records are accepted beyond that, the dev re-declares from the diff.
Responsibility:
plugin-auth's internal-field read-back hides sys_verification.identifier from the vendor's findVerificationValue, so @better-auth/sso compares against a record built from an undefined identifier | the read-back census mechanism (internal-field-readback.ts) is the platform path for exactly this; it excludes identifier today | every operator who verifies an SSO domain with the record request-domain-verification names; measured on 1.7.3 and 1.7.12 (#25096's report 6315144841)Thread-read: none
Serial constraints cleared:
auth-manager.ts'soauthProvideroptions, which is disjoint. PR feat(plugin-auth, plugin-email): under open and email_domain, a sign-up for an existing address answers like a new one and mails that address, while mail is delivered #25145 (plugin-auth: under theopenandemail_domainpostures, a sign-up for an address that already has an account answers the success shape and mails that address (ruling 1B on #25040); without a live mail transport the explicit refusal stays #25060, in the merge queue) isauth-manager.tsand plugin-email, also disjoint.internal-field-readback.tsorregister-sso-provider.ts(the open PRs' file lists were read this round). Their last change onmainis the 1.7.12 lift,03966ceae.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25111,
"status": "done",
"branch": "claude/issue-25111-sso-dns-record",
"pr": "#25161",
"session": "session_01UnHcTADjeg3VwxF21QHj7W — the parent's harness-stamped id (mode:subagent run); the relay envelope and both commit trailers carry it",
"premise_still_valid": true,
"summary": "Remedy A, the read-back seam: internal-field-readback.ts now re-attaches sys_verification.identifier (absenceProvesStrip true, the column is required) beside value, so @better-auth/sso's verifyDomain gets the row whole. The instruction text (register-sso-provider.ts dnsRecordValue) is unchanged. All four PM mechanism assumptions were measured on the vendor's real 1.7.12 handler over a real ObjectQL + driver-sql engine. (1) Holds: before the fix the instructed record _better-auth-token-PROVIDER=TOKEN answered 502 DOMAIN_VERIFICATION_FAILED, and a record spelling undefined=TOKEN verified (204 vendor / 200 bridge). (2) Holds: the census note was false. On 1.7.12, verifyDomain is the only code in better-auth, sso, oauth-provider or scim that reads verification.identifier off a row. (3) Explained: the bare token passes because the vendor accepts VALUE beside IDENTIFIER=VALUE by design. (4) The bridge leg was run and gives the same answers. One partition-3 premise is falsified: the bare-token instruction would NOT prove less. The vendor queries DNS only at the computed name _better-auth-token-PROVIDER.DOMAIN, the token is per provider, and the bare token is already accepted. Neither remedy weakens the proof, so this is not needs_decision. A was chosen on the four axes: contract-first at the producer seam, the census made true, no change to a public response field or the docs, one table row. Accept-set delta: the instructed record is now accepted, and undefined=TOKEN (only the defect produced it) is now refused. Clause-②: no is kept as the claim declared, and the PR body states the delta.",
"tests": "Final head 7316682 (fix 7f0292d merged with origin/main 2354058). Full
pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: Test Files 183 passed (183), Tests 3466 passed | 10 skipped (3476).pnpm --filter @objectstack/plugin-auth run typecheck: tsc --noEmit + examples + check:test-typecheck OK (10 files / 88 errors / 23 signatures held). Lock VERDICT command-exit 0. Both new test files are in the tsconfig.test.json program (--listFilesOnly). Before the fix (BASE 9cfc575 source), the new suite gave Tests 4 failed | 8 passed (12): instructed record 502 on both doors, undefined=TOKEN 204/200. After the fix at 7f0292d: new suite + internal-field-readback + internal-credential-readback = 3 files, 46/46. Ablation at 7f0292d: node scripts/ablation-replace.mjs --delete on the identifier readback row (anchor x1 to x0, blob e8e98f4e53e8 to 4d7810f74bf0; subject imported from src by relative path, no dist in the path) gave 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 3 seam pins failed. Restore proven: blob == HEAD e8e98f4e53e8, git diff HEAD empty, porcelain empty.","gates": "Local subset at 7316682:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 67 commands (merge base 2354058, 4 paths). All 67 were run with exit codes captured before any pipe, and all 67 exited 0.--ran(record spelledCMD :: exit N) answered: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. On the pre-merge head 7f0292d,pnpm check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET, missing dists); the workspace build that check:type-check-debt runs supplied them, and it exited 0 at 7316682. The 5 artifact-roster gates whose roster sits under a changed directory all exited 0 at 7316682: check-changeset-fixed, check:auth-mount-ledger, check:authz-resolver, check:error-code-casing, check:filter-alias-parity. CI check-runs on 7316682 when this report was written: Check PR Size success; No other open PR may claim the same issue success; No other open PR may claim the same single-writer path success; Part-of PR must not also close its card success; The card this PR closes must claim this branch success; 18.x upgrade harness skipped; Packed-tarball smoke skipped; Check Changeset, Governed Surface Queue Guard, all Lint lanes and all Type Check lanes in_progress; Auto Label queued. Test Core and Build Core not yet listed.","dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm (Clause-②: no)","mcp_calls": "0 — no MCP GitHub tool was called; every read was REST via gh api (card 25111, its comments, comment 6315144841, PR 25161, check-runs)",
"api_writes": "2 — two relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/25161/assignees [os-bill], body read back 11393/11393 bytes identical; (2) this os-dev-report = POST /repos//issues/25111/comments. Not REST: three git pushes of the branch (empty probe, 7f0292d, 7316682).",
"files_changed": [
"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",
".changeset/25111-sso-domain-verification-instructed-record.md"
],
"line_budget": "n/a — no skills/** file or governed ledger touched",
"deviations": [
"The merge commit 7316682 (origin/main into the branch, made before opening the PR per AGENTS.md section 10) carries git's default merge message, without the Claude-Session / Co-authored-by trailer pair; pre-push accepted it. Not rewritten.",
"The gate runner's PID was looked up once with pgrep -f, then verified as the runner via /proc/PID/cmdline before tail --pid waited on it. Nothing was killed."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed: packages/platform-objects/src/identity/sys-verification.object.ts still says identifier has no readback row because no better-auth flow reads it back off a row. That was already false and is plainly so now. It is the engine lane's file and outside this card's surface. Recorded in the PR's Acceptance notes only.",
"carrier: 承接者:无 · noted, not filed: objectql-adapter.ts findMany passes no lookup to the read-back seam, so identifier costs one id-batched privileged read per verification findMany / consumeOne (a findOne keyed on it is answered from the lookup). These are one-time flows, so this is performance, not a defect. Recorded in the PR's Acceptance notes only."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
ACCEPT → PR #25161 comment 6331955547 ·
domain:servicesseat 2 ·session_01UnHcTADjeg3VwxF21QHj7W· 2026-10-22T03:29Z · contract review PASS atCONTRACT_REVIEW_TIER6331819380· ready and auto-merge set through the relay; the landing note follows the merge.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed ·
domain:servicesseat 2 ·session_01UnHcTADjeg3VwxF21QHj7W· 2026-10-22T03:54Z425c743fd9origin/main425c743fd, subject "fix(plugin-auth): SSO domain verification accepts the TXT record request-domain-verification names"completedbyFixes; no other card closed in that windowState:
pm:dispatchedis removed in this act. The recordrequest-domain-verificationnames now verifies, on the vendor route and the bridge alike. The two out-of-scope readings (a stale comment inplatform-objects, and one extra read per verification lookup) are in PR #25161's Acceptance notes.Generated by Claude Code