Repository navigation
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
Description
Ruled: 6329352251 · letter A1 · 2026-10-22T00:05Z
Filing gate ① a reproduced defect — reach: measured live on ObjectOS Cloud production, https://huayan.objectos.ai (framework pin 9d13b5fc83, @better-auth/oauth-provider 1.7.3), 2026-10-21, with Claude Code 2.1.159 as the MCP client. Filed at the maintainer's request in a cloud session.
Reader (who acts): objectstack triage → the plugin-auth lane.
What fails
Every MCP client that authorizes through Dynamic Client Registration with a loopback redirect — Claude Code, and by the same shape Cursor, VS Code and Codex CLI — cannot connect to an ObjectStack MCP endpoint over OAuth. Registration is refused before the browser ever opens:
SDK auth failed: web clients require https redirect URIs on non-loopback hosts: http://localhost:63678/callback
The OAuth track is otherwise healthy on the same host: /api/v1/mcp answers 401 with WWW-Authenticate: Bearer … resource_metadata=…, /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server/api/v1/auth both answer 200. Only the registration step fails, so OAuth for MCP works today only for clients with an https callback (the claude.ai web connector). API keys are the sole working path for local agents.
Reproduction (no state written — the request is refused)
curl -sS -X POST https://<env-host>/api/v1/auth/oauth2/register \
-H 'content-type: application/json' \
-d '{"client_name":"probe","redirect_uris":["http://localhost:63678/callback"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_endpoint_auth_method":"none"}'
# → 400 {"error":"invalid_redirect_uri","error_description":"web clients require https redirect URIs on non-loopback hosts: http://localhost:63678/callback"}This is the body shape an MCP client sends: a public client (token_endpoint_auth_method: none), PKCE, a loopback http redirect, and no application_type.
Cause
@better-auth/oauth-provider 1.7.3 applyOAuthClientRegistrationDefaults(client, defaultApplicationType = "web") fills a missing application_type with web (the OIDC DCR default), and validateClientRedirectUri refuses any http or loopback redirect for web. The default is not configurable from plugin options. Only the client-metadata-document path defaults to native.
The MCP authorization spec, though, has clients register http://localhost / 127.0.0.1 redirects (RFC 8252 §7.3), and the clients in the field do not send application_type. So the provider's spec-correct default meets clients that follow a different spec, and plugin-auth sits between them without reconciling the two.
Not a duplicate
Dedupe: REST search/issues, closed included —
repo:objectstack-ai/objectstack "web clients require https"→ 5 (OAuth app registration rejects http://localhost and http://127.0.0.1 redirect URIs as "non-loopback", with HTTP 500 #24773, fix(platform-objects): the OAuth register form offers only Web and Native, and its Redirect URLs help states the rule for each type #25011, fix(plugin-auth): a refused OAuth application registration answers the status the refusal declared, not 500 #24853, QA run · ai (8/10) · 5b14b7f3 · 2026-10-19 · 8 PASS / 0 PARTIAL / 0 FAIL / 0 BLOCKED / 2 NOT-RUN #24540, docs(qa): re-point the half-B platform-checklist items that described an older product #24141)repo:objectstack-ai/objectstack "oauth2/register" localhost→ 6 (OAuth app registration rejects http://localhost and http://127.0.0.1 redirect URIs as "non-loopback", with HTTP 500 #24773, fix(plugin-auth): the register form's Application Type reaches the OAuth provider as application_type #24992, feat(cli):objectstack dev --cert/--keyterminates TLS in the dev process, and the canonical origin follows the listener #17725, fix(plugin-auth)!: the OAuth register door refuses an application type the provider does not judge with 400 INVALID_REQUEST, where it registered it as web #25038, fix(hono): mount /auth where the auth service serves, and refuse a prefix it cannot serve under #16380)repo:objectstack-ai/objectstack "application_type" register→ 13 (fix(plugin-auth): the register form's Application Type reaches the OAuth provider as application_type #24992, fix(plugin-auth)!: the OAuth register door refuses an application type the provider does not judge with 400 INVALID_REQUEST, where it registered it as web #25038, OAuth app registration rejects http://localhost and http://127.0.0.1 redirect URIs as "non-loopback", with HTTP 500 #24773, sys_oauth_application.type declaresuseragentbasedandpublic, which no writer produces, and cannot hold theuser-agent-basedornullthe OAuth provider has written #25053, fix(plugin-auth): a refused OAuth application registration answers the status the refusal declared, not 500 #24853 …)
The nearest is #24773 (closed 2026-10-21, fixed by #24992 / #25038 / #25011): the same provider message, but from the Setup form's door POST /api/v1/auth/sys-oauth-application/register, where an administrator now picks Web or Native. This card is the other door — better-auth's own POST /api/v1/auth/oauth2/register (RFC 7591 DCR) that an MCP client calls anonymously, where there is no form and no type. Nothing in plugin-auth on 9d13b5fc83 or on main touches that door's application_type. #20791 (open) verifies the remote MCP + OAuth surface against other clients and would hit this card, but does not name it.
Suggested fix (for the lane to judge)
A before-hook on /oauth2/register in plugin-auth: when the body has no application_type, token_endpoint_auth_method is none, and every redirect_uri is http on exactly localhost, 127.0.0.1 or [::1], register it as native. Every other body keeps the provider's default and its rule. That narrows nothing and widens only the shape RFC 8252 already blesses for native public clients.
Acceptance: the curl above returns 201 with application_type: "native"; the same body with https://evil.example/cb, or with token_endpoint_auth_method: client_secret_basic, is still refused; claude mcp add --transport http x https://<env>/api/v1/mcp then /mcp → Authenticate completes in the browser and lists the tools.
Activity
objectstack-fleet commentedon Oct 21, 2026
Triage: routed ·
domain:services·area:identity·pm:queue(p1 kept)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-21T13:52Z. ⛔ Not a claim.plugin-authbelongs todomain:services(pm-dispatch lane table). The p1 the maintainer set is kept.@better-auth/oauth-providerto 1.7.12. Whoever takes this card measures the DCR default on that line first. If 1.7.12 still defaults a missingapplication_typetoweb, the fix stays in plugin-auth. Otherwise it rides the lift. Order the two PRs so they do not collide.pm:awaiting-maintainer) asks for the remote MCP + OAuth surface to be verified against ChatGPT connectors and Codex CLI. This card is one concrete failure of that surface, for loopback clients.objectstack-fleet commentedon Oct 21, 2026
[Decision] Which client shapes may an MCP client register anonymously (OAuth dynamic client registration)
domain:servicesseat 2 (seat post #21118) ·os-support-ai·session_01UmqzD97e14iws6hmVhgZ5y· 2026-10-21T14:46Z. ⛔ Not a claim. Why this goes to the box: the fix changes what an ANONYMOUS registrant may register on the OAuth provider. That is a runtime security-boundary change, which is the human floor (the dispatch rules' negative boundary: a runtime permission or security behaviour change is not Clause-②; it is the security-boundary class). The triage record is6317806669.一句话问题: 本地 AI 客户端(Claude Code、Cursor、VS Code、Codex CLI)今天无法通过 OAuth 连接 ObjectStack 的 MCP 端点,只能用 API Key。要让它们连上,就得允许匿名注册「回调地址在本机」的原生客户端。放开到什么程度,是一个安全边界问题。
背景(席位实测,只读 npm 发布包源码)
故障成立(卡面,生产实测): 匿名动态注册时,回调为
http://localhost:…且未声明application_type的请求被拒(invalid_redirect_uri),因为提供方把缺省类型当作web。卡面「缺省类型不可配置」只在 1.7.3 成立。
@better-auth/oauth-provider1.7.12(PR fix(plugin-auth)!: lift the better-auth family from 1.7.3 to 1.7.12 #25109 正在升到这一版)新增了提供方选项clientRegistrationDefaultApplicationType。取值"infer"时:http指向localhost/127.0.0.1/[::1],缺省类型就判为native,否则仍为web;application_type永不被覆盖。该选项的类型文档原文点名了目标客户端:「Lets native MCP clients that omit
application_type(for example Cursor, or older Claude Code releases with anhttp://127.0.0.1callback)」。同一版的clientRegistrationRequirePKCE默认true,公共客户端必须走 PKCE。读数位置:
@better-auth/oauth-provider@1.7.12dist/authorize-D_jy3s-1.mjs的resolveDynamicRegistrationDefaultApplicationType(:1717)、isExactLoopbackHttpRedirectUri(:1739);dist/oauth-DXiqbRzK.d.mts:1448-1460。1.7.3 对应函数写死"web"(authorize-9whjxVLJ.mjs:1757)。Governing text
None found in the repo for dynamic client registration. Searched:
grep -rli "dynamic client registration\|oauth2/register\|oauth-provider" docs/adr→ 0 hits. The external specs are the MCP authorization spec (clients register loopback redirects) and RFC 8252 §7.3 / §8.3 (native apps, loopback redirects, PKCE). The nearest in-repo record is #24773 (closed; through #24992 / #25038 / #25011 the Setup form's own door lets an administrator choose Web or Native). That is the other door; it does not govern this one.是否改协议: 否。不碰 spec / 元数据契约,只改 plugin-auth 交给 OAuth 提供方的运行时配置。
前提(复核命令)
npm view @better-auth/oauth-provider@1.7.12 dist.tarball,下载后grep -n "clientRegistrationDefaultApplicationType" package/dist/*.d.mts。git grep -n '"@better-auth/oauth-provider"' origin/main -- packages/plugins/plugin-auth/package.json(合入前为 1.7.3)。git grep -n "clientRegistrationDefaultApplicationType" origin/main -- packages/plugins/plugin-auth/src(0 命中)。选项
"infer"clientRegistrationDefaultApplicationType: 'infer';钉子覆盖放行与拒绝两侧/oauth2/register前加 before-hook:仅当未声明类型、公共客户端、且所有回调都是本机 http 时判为 native(卡面原建议)业务含义直译:
推荐:A
"infer"判为 native 后,是否还接受「机密客户端(带 client_secret)+ 本机回调」;裁后执行段
若选 A:本卡转
pm:blocked+ 行首Blocked-by: #25096,deps(plugin-auth): lift the better-auth family from 1.7.3 to 1.7.12 — security fixes published after the 2026-10-01 deferral #25096 合入后由解锁扫描放回,本席串行派发。验收用卡面 curl:application_type: "native";https://evil.example/cb照旧按 web 规则判定;web加本机回调照旧被拒;changeset 为
patch(changeset levels: minor only when the metadata protocol changes (added / narrowed / semantic), patch otherwise; release notes grouped by what the reader must do (maintainer ruling A, 2026-10-21) #25105 裁决 A:非协议改动按 patch 发布)。若选 B:同样排在 deps(plugin-auth): lift the better-auth family from 1.7.3 to 1.7.12 — security fixes published after the 2026-10-01 deferral #25096 之后,按卡面判定实现,钉子覆盖自定义协议仍被拒。
若选 C:按 not planned 关单,理由记入关单评论,mcp distribution: verify the remote MCP + OAuth surface against ChatGPT connectors and Codex CLI, and submit the metadata-only registry listings — the remainder of #2714 #20791 记一行。
os-decision-facets
Prior rulings read: dynamic client registration,oauth2/register,oauth-provider → 0 hits in docs/adr; none; thread: 0 ruling(s)
Related: #25096 / PR #25109 (the lift to 1.7.12, seat 1); #24773 (the Setup form's door, closed); #20791 (the remote MCP + OAuth verification,
pm:awaiting-maintainer).hotlong commentedon Oct 21, 2026
Ruled: A — set
clientRegistrationDefaultApplicationType: 'infer'in plugin-auth's OAuth provider config, once the lift to@better-auth/oauth-provider1.7.12 (#25096, PR #25109) has landed.hotlong), in a cloud-repo Claude Code desktop session (e0b4f569-ce94-4c75-ae7d-e8a0eff93760), 2026-10-21T22:16Z. They answered decision6318906173through AskUserQuestion. Their words, verbatim: 「A:用提供方 infer (推荐)」.6318906173searcheddocs/adr, 0 hits). External: the MCP authorization spec; RFC 8252 §7.3 / §8.3.needs-user-decision→pm:blocked, per the decision's post-ruling section. TheBlocked-by:line goes into the card body. Thedomain:servicesseat dispatches serially once deps(plugin-auth): lift the better-auth family from 1.7.3 to 1.7.12 — security fixes published after the 2026-10-01 deferral #25096 closes."infer"still accepts a confidential client (with aclient_secret) with a loopback redirect;Carried into the acceptance. Measured from the same session on 2026-10-21 on
huayan.objectos.ai: the environment's own Setup → Integrations → Connect an Agent page (/_console/apps/com.objectstack.setup/page/connect_agent) tells Claude Code users that the first use opens an OAuth sign-in. It gives URL-only snippets for Cursor, VS Code and Codex CLI. Today every one of those instructions fails on exactly this registration refusal. The acceptance walk includesclaude mcp add --transport http <name> https://<env-host>/api/v1/mcp→/mcp→ Authenticate completing in the browser and listing the tools, so that the page becomes true.Recorded by a cloud-repo session acting for the maintainer. ⛔ Not a claim.
hotlong commentedon Oct 21, 2026
Correction to
6327761812.pm:blocked→pm:queue: the blocker had already closed.completedon 2026-10-21T15:21Z, when PR fix(plugin-auth)!: lift the better-auth family from 1.7.3 to 1.7.12 #25109 merged (squash03966ceae0).origin/mainpackages/plugins/plugin-auth/package.jsonpins@better-auth/oauth-providerat1.7.12.git grep clientRegistrationDefaultApplicationType origin/main -- packages/plugins/plugin-auth/src→ 0 hits, so the ruled change is still undone.Blocked-by: #25096line from the body.The card is dispatchable now under ruling A. It belongs to the
domain:serviceslane (seat post #21118).Recorded by the same cloud-repo session acting for the maintainer. ⛔ Not a claim.
8 remaining items
objectstack-fleet commentedon Oct 22, 2026
Ruling: batch #383 item 1 · letter A1 · maintainer 「同意」 2026-10-22T00:05Z
Director seat, summon #47,
session_01TQvbE2ENCniJr1ffCzbPc6(os-bill, writing through the relay). Presented in this session's chat as batch #383 item 1, from thedomain:servicesseat 2 decision6328903272and the dev report6328861459. The maintainer's own ruling A (6327761812, 「A:用提供方 infer (推荐)」) stands; this ruling settles the confidence gap that ruling named and the dev measured as a mismatch. The director's delta:git grep -c clientRegistrationDefaultApplicationType origin/main -- packages/plugins/plugin-auth/src→ 0 (controlallowDynamicClientRegistrationinauth-manager.ts→ 1), so the ruled change is still undone; the director downloaded the npm tarball of@better-auth/oauth-provider@1.7.12and readdist/authorize-D_jy3s-1.mjs:1718-1722:resolveDynamicRegistrationDefaultApplicationTypetestsredirect_urisonly (isNonHttpRedirectUriorisExactLoopbackHttpRedirectUri) and never readstoken_endpoint_auth_method;clientRegistrationRequirePKCEis declared atdist/oauth-DXiqbRzK.d.mts:1446. The seat's and the dev's readings hold. Thread read to its end at this write:6328903272is the newest comment.Governing text:none in the repo for dynamic client registration (docs/adr→ 0 hits, recorded on6318906173and6327761812); external: the MCP authorization specification; RFC 8252 §7.3 / §8.3 (loopback redirects for native apps) and §8.4 (a native app is a public client, except where dynamic registration issues a per-instance secret).What the ruling binds
clientRegistrationDefaultApplicationType: 'infer'to the OAuth provider, with a comment naming this card and the measured boundary. No hook, no second classification rule.httpredirect, noapplication_type) registers 201native;https://evil.example/cbalone staysweb;http://evil.example/cbandhttp://localhost.evil.examplestay refused; a confidential registration (client_secret_basic, or the auth method omitted) with a loopback or custom-scheme redirect and no type registers 201nativewith aclient_secret— the pin states this plainly rather than claiming "public clients only"; and the authorize step without PKCE is refused for every one of those clients. The pins send the body as the card spells it, withoutapplication_type(the existingauth-manager.mcp-oauth-resource.test.tsspellsnative, which is why it did not catch this defect).maintoday when the body spellsapplication_type: native(the dev's R2c), soinferadds no registrable client shape; it changes only what an omitted field means. Any token it issues still needs the user's consent and PKCE. The question whether anonymous registration should admit confidential clients at all is a separate policy, open today independent of this card; it is not filed now (zero measured senders, and a new gate defaults to no), and the seat may file it as aneeds-user-decisioncard if a producer or an incident gives it a pull.application_type: nativebypasses — a declared boundary that is not enforced); A3 (A1 plus a second card now — the second card has no pull today, so it is not opened).6327761812: the environment's Setup → Integrations → Connect an Agent page becomes true; the browser walkclaude mcp add --transport http <name> https://<env-host>/api/v1/mcp→/mcp→ Authenticate completing and listing the tools is part of the acceptance, measured on a deployed environment after the cloud pin moves.Effort of the chosen letter (the card's column):
domain:services· 1 PR · one configuration line plus one test file plus apatchchangeset · no precedent. Confidence gap carried: the browser acceptance on a deployed environment is not yet measured; the readings come from the provider's real handler built with AuthManager's actual options.State:
needs-user-decision→pm:queue.domain:servicesseat 2 (seat post #21118,session_01UnHcTADjeg3VwxF21QHj7W) re-claims and dispatches at once; the dispatch prompt carries the dev's measured cells as the pins. Branchclaude/issue-25118-dcr-infer-nativeis empty atabc25a353; the next claim decides whether to reuse it.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Claim: PM loop round 1 · 2026-10-22T00:19Z · p1,
domain:services, re-claim under ruling A1 (6329352251, the maintainer's 「同意」), which settles ruling A's (6327761812) confidence gapSession:
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-25118-dcr-infer-native(reused; empty atabc25a353)Worktree:
objectstack-issue-25118Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)Ruling-ref: 6329352251
File surface (line anchors at
origin/mainabc25a353):packages/plugins/plugin-auth/src/auth-manager.ts: theoauthProvider({ … })options (about:4311–:4380), one option,clientRegistrationDefaultApplicationType: 'infer', with a comment naming this card and the measured boundary. Nothing else in the file.packages/plugins/plugin-auth/src/;.changeset/25118-*.md(apatch).auth-plugin.ts, notpackages/spec/**, not the Setup form'ssys-oauth-applicationdoor, 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"). The dispatch waits for a free slot (batch3, the maintainer's 「并发 3」).Clause-②: no — a runtime security-boundary change (what an anonymous registrant may register). The dispatch rules class that as the human-floor security class, and the maintainer ruled it (A, then A1).
Responsibility:
the vendor's DCR default (web) meets MCP clients that omit application_type, and plugin-auth passes no option | better-auth 1.7.12's clientRegistrationDefaultApplicationType: 'infer', unset today | every local MCP client (Claude Code, Cursor, VS Code, Codex CLI) that registers anonymously over OAuth; measured on productionThread-read: 6329352251
Serial constraints cleared:
openandemail_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 (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) editsauth-manager.tsin other regions (the sign-up path and the vendor options besidesendVerificationEmail, about:2132–:2150). The file is shared, so whichever lands second mergesmain.plugin-authsource (the open PRs' file lists were read this round).Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25118,
"status": "done",
"branch": "claude/issue-25118-dcr-infer-native",
"pr": "#25156",
"session": "session_01UnHcTADjeg3VwxF21QHj7W — the parent's harness-stamped id (mode:subagent run of the dispatching seat)",
"premise_still_valid": true,
"summary": "Implemented ruling A1 (6329352251) as written. packages/plugins/plugin-auth/src/auth-manager.ts now passes clientRegistrationDefaultApplicationType: 'infer' to @better-auth/oauth-provider 1.7.12, with a comment that names #25118, the ruling, RFC 8252 §7.3/§8.4, the MCP authorization spec and the measured boundary, the confidential cell included. There is no hook and no second classification rule. A new test file, auth-manager.mcp-oauth-dcr-infer.test.ts, boots the REAL better-auth and oauth-provider 1.7.12 handler from the exact options AuthManager produces. It pins the ruling's cells with bodies that omit application_type (the first is the card's body verbatim, and the test asserts it has no application_type key), plus a PKCE differential for every registered client and a DCR-off cell. A patch changeset (.changeset/25118-dcr-infer-native.md, Clause-②: no, Metadata-protocol: none) rides with it. Draft PR #25156 is open with assignee os-bill. Premises re-verified on origin/main 459afea: the pin is 1.7.12; clientRegistrationDefaultApplicationType had 0 hits in plugin-auth/src (control allowDynamicClientRegistration: 1); the classifier at dist/authorize-D_jy3s-1.mjs:1718-1722 reads redirect_uris only and runs only for registrationSource 'dynamic' (:1826), not for the 'managed' Setup-form door (:2264); plugin-auth has no before-hook branch or raw route on /oauth2/register. Every Partition-2 cell reproduced. The earlier carrier note (the existing harness spells native) is discharged by the new file.",
"tests": "Readings at 5a7e1f0, shared box. (1) New file:
bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/auth-manager.mcp-oauth-dcr-infer.test.ts'gaveTest Files 1 passed (1),Tests 12 passed (12),VERDICT command-exit 0(per-file line 914 ms of tests; module import 15.4 s). Cells: card body 201 native; http://127.0.0.1 201 native; cursor:// 201 native; https://evil.example/cb alone 201 web; http://evil.example/cb 400 invalid_redirect_uri with no client stored; http://localhost.evil.example 400 with no client stored; explicit web plus loopback 400; client_secret_basic plus loopback 201 native with a client_secret; auth method omitted plus loopback 201 native, client_secret_basic, with a secret; client_secret_basic plus cursor:// 201 native with a secret; loopback plus https://evil.example/cb 201 native; DCR off (OS_OIDC_DCR_ENABLED=false) 403 access_denied with no client stored. For each of the 8 registered clients, authorize without PKCE redirects to the client's own redirect with error=invalid_request, a description naming PKCE and no code, while the identical request with S256 PKCE reaches /oauth/consent. (2) Ablation, one-time and not committed: commit first, thenos-verify-lock.sh -- node scripts/ablation-replace.mjs --file ABS/auth-manager.ts --anchor \"clientRegistrationDefaultApplicationType: 'infer',\" --delete -- pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/auth-manager.mcp-oauth-dcr-infer.test.ts, under a bashtrap 'git checkout HEAD -- ABS' EXIT INT TERM. On-disk proof: anchor 1 -> 0, blob 9c00cadb -> a4a53154. ResultTests 7 failed | 5 passed (12),VERDICT command-exit 1, the expected direction. Red: all 7 native cells, the card body and the custom scheme among them, each answering 400 invalid_redirect_uri 'web clients require https redirect URIs on non-loopback hosts', the production symptom. Green: the 5 option-independent cells. Restore proven: blob == HEAD 9c00cadb,git diff HEADempty, porcelain empty. The subject resolves through the relative import ./auth-manager (src), so no dist rebuild was needed for the mutation to reach the run. (3)pnpm --filter '@objectstack/plugin-auth^...' build(closure, workspace-concurrency 2): VERDICT command-exit 0. (4)pnpm --filter @objectstack/plugin-auth build: VERDICT command-exit 0; check-dts-emitted 3/3; dist/.d.ts has 0 hits for clientRegistrationDefaultApplicationType while dist/index.mjs has 1 (positive control), so no published type widened. (5)pnpm --filter @objectstack/plugin-auth typecheck: VERDICT command-exit 0; check:test-typecheck OK, 10 files / 88 errors / 23 signatures held, no growth (tsconfig.test.json includes src/**/). (6)pnpm --filter @objectstack/plugin-auth test:Test Files 182 passed (182),Tests 3450 passed | 10 skipped (3460), VERDICT command-exit 0. Head 6d4fd2a = 5a7e1f0 merged with origin/main 9cfc575 (no conflict; delta vs main is still these 3 files, +405/-0). The incoming commits touch cloud-connection, runtime, service-automation, verify, docs and an ADR, none in plugin-auth's dependency closure, so readings (1)-(6) stand under AGENTS §10's scoped re-check, and only the gate union was re-run on 6d4fd2a.","gates": "Local, at HEAD 6d4fd2a:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 70 commands (merge base 9cfc575, 3 paths, no stale-tree warning). All 70 ran with exit codes captured before any pipe, every one exit 0.--ranreconciliation exit 0: 'Run reconciliation — 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN'. A scan of the 70 logs for PREREQUISITE NOT MET / nothing was measured / queue-timeout found only check:objectui-changeset's own self-test prose. Run 1 at 5a7e1f0: 69 exit 0, plus check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (41 packages without dist). On run 2 that gate measured (112 require entry points across 69 packages load) because run 1's check:type-check-debt closure build had built every package. Commands: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0; node scripts/check-adr-0087-registration.mjs --self-test :: exit 0; node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0; node scripts/check-changeset-no-major.mjs --self-test :: exit 0; node scripts/check-ci-filter-parity.mjs :: exit 0; node scripts/check-closing-keyword-parity.mjs :: exit 0; node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0; node scripts/check-comment-mask-adoption.mjs :: exit 0; node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0; node scripts/check-comment-mask-corpus.mjs :: exit 0; node scripts/check-dev-prereqs.mjs --self-test :: exit 0; node scripts/check-dts-emitted.mjs --self-test :: exit 0; node scripts/check-empty-changeset.mjs --base origin/main :: exit 0; node scripts/check-empty-changeset.mjs --self-test :: exit 0; node scripts/check-issue-citations.mjs :: exit 0; node scripts/check-keyed-text-bounds.mjs :: exit 0; node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0; node scripts/check-platform-object-tenancy-census.mjs :: exit 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0; node scripts/check-plugin-teardown-shape.mjs :: exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0; node scripts/check-position-name-fold-loaders.mjs :: exit 0; node scripts/check-position-name-fold-loaders.mjs --self-test :: exit 0; node scripts/check-registry-log-declared.mjs :: exit 0; node scripts/check-registry-log-declared.mjs --self-test :: exit 0; node scripts/check-rest-log-spy-declared.mjs :: exit 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0; node scripts/check-system-context-census.mjs :: exit 0; node scripts/check-system-context-census.mjs --self-test :: exit 0; node scripts/check-tenant-audit-census.mjs :: exit 0; node scripts/check-tenant-audit-census.mjs --self-test :: exit 0; node scripts/check-undeclared-dep-imports.mjs :: exit 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0; node scripts/docs-audit/check-affected-docs.mjs :: exit 0; node scripts/docs-audit/check-drift-comment.mjs :: exit 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0; node scripts/release-pending-publish.mjs --self-test :: exit 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0; pnpm check:auth-mount-ledger :: exit 0; pnpm check:changeset-gate-self-tests :: exit 0; pnpm check:closed-ceremony-text :: exit 0; pnpm check:cross-package-test-inputs :: exit 0; pnpm check:dispatcher-error-vocabulary :: exit 0; pnpm check:doc-authoring :: exit 0; pnpm check:driver-memory-census :: exit 0; pnpm check:dts-closure :: exit 0; pnpm check:dual-build-cjs-loads :: exit 0; pnpm check:durability-log-level :: exit 0; pnpm check:engine-double-contract :: exit 0; pnpm check:gitlink-declared :: exit 0; pnpm check:issue-citations :: exit 0; pnpm check:lean-entry-closure :: exit 0; pnpm check:logger-receiver-detach :: exit 0; pnpm check:nul-bytes :: exit 0; pnpm check:objectql-double-limit :: exit 0; pnpm check:objectui-changeset :: exit 0; pnpm check:org-identifier :: exit 0; pnpm check:page-declaration-shape :: exit 0; pnpm check:pm-changeset-deadline-census :: exit 0; pnpm check:published-files :: exit 0; pnpm check:query-options-erasure :: exit 0; pnpm check:refd-timer-probe :: exit 0; pnpm check:slot-lookup :: exit 0; pnpm check:sourcemap-no-sources-content :: exit 0; pnpm check:test-source-alias :: exit 0; pnpm check:tier-file-adoption :: exit 0; pnpm check:type-check-coverage :: exit 0; pnpm check:type-check-debt :: exit 0; pnpm check:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0. CI on PR #25156, single snapshot at open (no polling): 38 check-runs. Completed success: filter, No other open PR may claim the same issue, Check Documentation Links, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, The card this PR closes must claim this branch, Check PR Size. In progress: Test Core (1/6)-(6/6), Build Core, Governed Surface Queue Guard, Type Check (source gates, workspace, consumer gates, debt ledger), the Lint lanes (ESLint, PM dispatch-gates, repo & tooling gates, CI release & engine gates, docs & code guards, scoped lanes), Check Changeset, Dogfood Regression Gate, Auto Label. The rest skipped by their own if: (Dogfood Verify CLI, Console Pin Gate, Build Docs, Temporal Conformance, 18.x upgrade harness, Packed-tarball smoke, the dogfood matrix template). CI convergence is the PM's to read.","dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm (Clause-②: no)","mcp_calls": "0 — no MCP GitHub tool was called. Reads went through gh api (REST GET issues/25118, issues/25118/comments, pulls/25156, commits/6d4fd2a5a.../check-runs).",
"api_writes": "3 REST writes in 2 relay strokes, each stroke one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write run as objectstack-fleet[bot]. Stroke 1 (scripts/pm/fleet-write/dispatch.mjs, run 39709534127, success): pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/25156/assignees [os-bill]; read-back 8734 bytes sent, 8734 stored, identical. Stroke 2 (scripts/pm/post-stamped.mjs): POST /repos//issues/25118/comments, this os-dev-report. No label write and no card assignee write. Not REST: 3 git pushes to claude/issue-25118-dcr-infer-native (abc25a3 -> 459afea fast-forward as the write-route probe, 5a7e1f0 the implementation, 6d4fd2a the merge of origin/main).",
"line_budget": "n/a — no skills/** file and no ratcheted ledger touched; diff +405/-0 over 3 files, under the 3000-line human-merge threshold",
"files_changed": [
"packages/plugins/plugin-auth/src/auth-manager.ts (+41: the one option line and its comment, inside the oauthProvider({ … }) options only)",
"packages/plugins/plugin-auth/src/auth-manager.mcp-oauth-dcr-infer.test.ts (new, +345)",
".changeset/25118-dcr-infer-native.md (new, +19, patch for @objectstack/plugin-auth)"
],
"deviations": [
"Ablation attempts 1 and 2 never acquired the shared lock (VERDICT queue-timeout, exit 99, behind a dogfood test run and then a sibling build) and are NOT MEASURED. In attempt 1 the mutation was held on disk while queued (WRAP nesting outermost); its restore was proven (blob == HEAD, git diff HEAD empty). Attempt 2 and the measured attempt 3 nested the mutation inside the lock, so it existed only while the lock was held. No gate or build ran while any mutation was on disk.",
"Package readings (the new file, the ablation, plugin-auth build/typecheck/test) were taken at 5a7e1f0 and not re-run at the merge head 6d4fd2a. The merge brought nothing in plugin-auth's dependency closure (
pnpm --filter '@objectstack/plugin-auth^...' lsagainstgit diff --name-only 459afea1f origin/main), so under AGENTS §10's scoped re-check only the 70-command gate union was re-run on 6d4fd2a. CI re-runs everything on the head.","test:repo (the vitest repo project) was not run locally: the diff touches no repo-project test and the derived list names none. CI Test Core runs it.",
"The browser walk carried by ruling item 5 (claude mcp add --transport http NAME https://ENV-HOST/api/v1/mcp, then /mcp, then Authenticate) is NOT MEASURED: it needs a deployed environment after the cloud pin moves. The PR body records it as owed.",
"No served-stack (pnpm dev) reading was taken. The readings come from the provider's real handler built from AuthManager's actual options; plugin-auth has no layer in front of /oauth2/register (code read: no before-hook branch, no raw route).",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and an emoji PR footer. Following AGENTS.md, which outranks it here (and the pre-push hook refuses a model id), the commit carries the model-free pair 'Claude-Session: https://claude.ai/code/session_01UnHcTADjeg3VwxF21QHj7W' / 'Co-authored-by: Claude' and the PR body ends with the session-URL footer. The merge commit 6d4fd2a carries git's default merge message with no trailer.",
"The dispatch named base origin/main 9cbd4c3a7; the PM corrected this mid-run to 459afea, and the work used the current origin/main (459afea, then 9cfc575)."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed (PR #25156 Acceptance notes only): the comment above rawApp.post(.../sys-oauth-application/register) in packages/plugins/plugin-auth/src/auth-plugin.ts still says DCR on /oauth2/register is something 'we don't want to enable broadly', while resolveDcrEnabled turns DCR on whenever the MCP server is on. Stale comment, no behaviour, outside this card's file surface.",
"carrier: none · noted, not filed (PR #25156 Acceptance notes only): the describe titles in packages/plugins/plugin-auth/src/auth-manager.mcp-oauth-resource.test.ts say 'installed 1.7.2' while the installed provider is 1.7.12. Naming only."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
ACCEPT → PR #25156 comment 6331289535 ·
domain:servicesseat 2 ·session_01UnHcTADjeg3VwxF21QHj7W· 2026-10-22T02:35Z · ruling A1 (6329352251) item by item · ready and auto-merge set through the relay; the landing note follows the merge. The deployed browser walk (ruling item 5) stays owed after the cloud pin moves.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed ·
domain:servicesseat 2 ·session_01UnHcTADjeg3VwxF21QHj7W· 2026-10-22T03:02Z6b44efa7b4origin/main6b44efa7b, subject "fix(plugin-auth): register an MCP client that omits application_type with a loopback or custom-scheme redirect as native"completedbyFixes. The only other card closed in that window, #24916, closed by its own PR (domain:cli)State:
pm:dispatchedis removed in this act. Ruling A1 (6329352251) is carried out. Still owed by the ruling's item 5: the browser walk (claude mcp add→/mcp→ Authenticate) on a deployed environment after the cloud pin moves. That makes the Setup → Integrations → Connect an Agent page true.Generated by Claude Code