Skip to content

OAuth app registration rejects http://localhost and http://127.0.0.1 redirect URIs as "non-loopback", with HTTP 500 #24773

Open

Description

@objectstack-fleet

Filing gate ③ a task the maintainer set: objectstack-ai/hotcrm#2188, the maintainer's browser pass of 2026-10-20. Each defect gets its own card, by the maintainer's ruling 「按照按照具体的issue单独提交卡片,不受数量限制。」 (recorded on objectstack-ai/hotcrm#2188, 6283467378). This is finding F-22 of track 2 (Setup as the system administrator), by the cloud tester session_01VuXyJvhGKJy8qeUvhKyxzv. It was measured on HotCRM main f22dfa52 with @objectstack/* 18.0.0-next.5, in Chromium at 1440×900 on a locally booted, seeded install. The evidence is on the branch claude/qa-setup-pass-2026-10-20 at ed4f40e42, which is never merged. Filed by the repo:hotcrm seat 2 (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.

Reader (who acts): objectstack triage routes it.

The finding, as measured

  • Persona / URL: dev admin, OAuth Applications → Register, type Web.
  • Expected: the field's help, "Must use https:// in production" (loopback http allowed).
  • Actual: POST /api/v1/auth/sys-oauth-application/register → 500, toast "web clients require https redirect URIs on non-loopback hosts: http://localhost:4001/callback" (same for 127.0.0.1). https://qa.example.com/callback registers.
  • Evidence: 40-F-oauth-localhost-rejected-500.png.
  • class: a. Reach: anyone registering a local or dev OAuth client.
  • Suspected landing: objectstack plugin-auth OAuth provider (loopback detection, and a validation error raised as 500).
  • Dedupe words: oauth redirect loopback localhost rejected, non-loopback hosts, register 500.

Seat review

  • Landing (seat review): plugin-auth, the OAuth application registration validation.

Dedupe

Method: semantic search_issues on objectstack-ai/objectstack and objectstack-ai/objectui, open and closed, using the finding's own dedupe words and a second phrasing; plus a grep of every issue title (objectstack #1–#24751, objectui #7–#13153) listed through the repo-scoped issues?state=all endpoint, because the semantic search can miss title words (objectstack#14743).

  • 0 hits in objectstack, 0 in objectui. New.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-20T08:53Z
    Session: session_01UmqzD97e14iws6hmVhgZ5y
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24773-oauth-register-refusal
    Worktree: objectstack-issue-24773
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: packages/plugins/plugin-auth/src/auth-plugin.ts (the sys-oauth-application/register wrapper only), its tests, one new .changeset/24773-*.md. ⛔ The redirect-URI acceptance rule is not widened in this dispatch: whether http://localhost / 127.0.0.1 is accepted for a web client is measured and reported, not changed (an authentication-flow boundary, human floor). ⛔ Not packages/spec/**, ⛔ no new error code (stop on breach; explain 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
    Responsibility: plugin-auth's sys-oauth-application/register wrapper answers a vendor validation refusal as HTTP 500 (createOAuthClient throws with no status) | the wrapper's own OAUTH_REGISTER_FAILED envelope already carries the vendor's discriminator | every admin registering an OAuth application with a refused redirect URI; measured on a HotCRM install 2026-10-20 (card)
    Thread-read: none
    Serial constraints cleared: no open PR touches plugin-auth/src/auth-plugin.ts (the file lists of all 18 open PRs read at this claim). Seat 1's #24739 (PR #24795) landed as 25e16ab00; PR #24791 (the session-revoke door in the same file) is no longer open.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "done",
    "branch": "claude/issue-24773-oauth-register-refusal",
    "pr": "#24853",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y — the parent PM session id (mode:subagent); this run is identified by its branch",
    "premise_still_valid": true,
    "summary": "Half 1 is done. POST /api/v1/auth/sys-oauth-application/register answered better-auth's refusals as 500 because better-call's APIError carries its status as the NUMBER statusCode (400) and the NAME status ('BAD_REQUEST'), and the wrapper read only a numeric status (M1 confirmed). The body-schema ValidationError, with numeric status 400, already answered 400 from the same door. The wrapper now reads the status through @objectstack/types resolveThrownHttpError, the repo's one rule for a thrown error's status (numeric status, then statusCode, else the 500 fallback). So invalid_redirect_uri answers 400, a request with no session answers 401 (same defect at the same line, disclosed in the PR), a storage fault stays 500, and the code stays OAUTH_REGISTER_FAILED with details.upstreamError. No new error code, no acceptance change. Half 2 was measured only. M2's premise is falsified: the vendor's web predicate (@better-auth/oauth-provider 1.7.3 validateClientRedirectUri) is !https || loopback ⇒ refuse. Its text means https AND a non-loopback host, so every loopback form is refused for web in both schemes (localhost, 127.0.0.1, [::1], with or without a port, https://localhost too). No config governs it. The native rule allows http on exact localhost/127.0.0.1/[::1]. The measured blocker for the dev flow is a separate wrapper defect: the Console's Application Type select is sent as type, which the vendor body schema strips (it declares application_type), so every registration is judged and stored as web. Options are in open_questions. The card's own reading that loopback detection misclassifies localhost is not what happens; its 500 half is real and fixed.",
    "tests": "New packages/plugins/plugin-auth/src/oauth-register-refusal-status.test.ts runs real better-auth (OIDC provider on) behind the real route registration on a real Hono app. It has 7 cases: a precondition that the fixture refusal is a named-status APIError (status 'BAD_REQUEST', statusCode 400); web + http://localhost:4001/callback → 400 OAUTH_REGISTER_FAILED invalid_redirect_uri with no row written; web + http://127.0.0.1:4001/callback → 400 likewise; no session → 401; body-schema refusal → 400 (preserved); storage fault → 500; https → 200 with one row. Before the fix, a probe on the same stack measured every loopback case at 500 and no-session at 500. Ablation on head 5dc8add, applied with scripts/ablation-replace.mjs, which verified the mutation on disk (anchor 1→0, replacement 0→1, blob 03e026dea→a6bb1a7f3 / →16aa6cab0). The restore was verified as blob == HEAD with git diff HEAD empty, and the shell had its own EXIT/INT/TERM trap. Leg 1, old numeric-status read: 'Tests 3 failed | 14 passed (17)', failing with 'expected 500 to be 400' x2 and 'expected 500 to be 401'. Leg 2, fallback 400: 'Tests 1 failed | 16 passed (17)', failing with 'expected 400 to be 500' on the storage fault. Both went red in the predicted direction. Final head 622e30a (merge of main 5cb6e6e; both files byte-identical to 5dc8add): pnpm --filter @objectstack/plugin-auth exec vitest run gave 'Test Files 169 passed (169) / Tests 3241 passed | 10 skipped (3251)' (os-verify-lock VERDICT command-exit 0). pnpm --filter @objectstack/plugin-auth typecheck exited 0 ('check:test-typecheck: OK'). pnpm --filter @objectstack/spec check:generated exited 0 after the post-merge rebuild. The 17 tests in the new file include the 10 that the createMemoryEngine import re-registers, which is the existing precedent shared by 41 files.",
    "gates": "Local union derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 622e30a: 72 commands, each run with cmd > log 2>&1; rc=$?. Result: 72/72 exit 0, including check:error-status-conformance, check:route-envelope, check:auth-mount-ledger, check:durability-log-level, check:issue-citations, check:nul-bytes, check:dual-build-cjs-loads (112 require entry points load), check:type-check-debt and check:test-source-alias. The --ran reconciliation printed 'Run reconciliation — 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN' ('a DERIVED zero — all 72 recorded an exit code'). An earlier pass at 5dc8add also had 71/72 exit 0, with check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET because the whole-repo dist was missing. The post-merge closure build cleared that prerequisite, and the final head reads green. The records are in scratchpad issue-24773/gates-results.tsv and gates/NNN.log. Not run locally: test:repo (CI Test Core) and the local-only families check:dev-prereqs / check:skill-frame-freshness (no spec build input or frame file touched). CI on PR #24853 at 622e30a, read right after pr_create: 'No other open PR may claim the same issue' success; Check Changeset, Lint (all lanes), Type Check (all lanes), Governed Surface Queue Guard, 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch' were in_progress; the two opt-in jobs were skipped. Test Core and Build Core were not yet listed. in_progress is the honest value; CI convergence is the PM's.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes (scripts/pm/fleet-write/dispatch.mjs, each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, run 39409267597, which executed POST /repos/objectstack-ai/objectstack/pulls (created #24853, draft; read-back 6676 bytes sent and stored, identical) and POST /repos//issues/24853/assignees (os-support-ai, listed); (2) comment, POST /repos//issues/24773/comments, which is this report. Labels: none. skip-changeset does not apply because plugin-auth publishes and a patch changeset is included. git push x5 (branch probe, 3 commits, merge) is not REST. Reads were plain gh api GETs on #24773, its comments, PR #24853 and its check-runs.",
    "open_questions": [
    {
    "question": "Half 2: may a client registered through the Console door POST /api/v1/auth/sys-oauth-application/register use an http://localhost / http://127.0.0.1 redirect URI, and how should the form's Application Type select reach the provider? This is an authentication-flow acceptance rule on the human floor. Measured facts, @better-auth/oauth-provider 1.7.3: (1) web is refused unless https AND non-loopback, in every environment, with no option; (2) native allows http only on exact localhost / 127.0.0.1 / [::1], any port; (3) the door forwards the select as type, which the vendor body schema strips, so every registration is judged and stored as application_type web, and type 'native' + http://127.0.0.1:4001/callback is refused with the web rule; (4) the SDK's oauth.applications.register (/oauth2/create-client) already carries application_type web|native; (5) the field help says 'Must use https:// in production.'",
    "options": [
    "A — Keep the vendor rule and correct only the words: the field help in platform-objects states the per-type rule. The type select stays dropped. Cost: one small platform-objects PR (other lane). Business need: not met; no Console path registers a loopback client, and only the SDK path with application_type native remains. Long-term: leaves a declared-but-unenforced form field (Prime Directive #10). AI-safety: an author or AI declaring type 'native' keeps silently getting web. Startup focus: adds nothing, but keeps a dead field.",
    "B — Accept http loopback for web outside production. The vendor predicate takes no option, so this means forking the vendor's registration validation: pre-validating and registering another way, or writing the client row ourselves. Cost: medium-large, re-paid on every better-auth bump, plus a production/non-production posture switch. Business need: serves local testing of a browser web app as a web client, which no measured consumer beyond this card asks for. Long-term: a workaround around a security validator. AI-safety: the same metadata gets a different verdict per deployment. Startup focus: adds surface (a switch and a fork).",
    "C — Accept http loopback for web always, as the card read the vendor's text. Premise falsified: the text and the predicate both say https AND non-loopback for web. Cost: the same fork as B, with a permanent relaxation of the web-client boundary. All four axes are as for B but worse on the security posture.",
    "D — Honour the declared type: the door forwards the select as the vendor's application_type (web | native). Native gets the vendor's RFC 8252 loopback rule; web keeps https + non-loopback. 'user-agent-based', which has no vendor counterpart, is refused loudly (400, replacing today's silent coercion to web) and retired from the select. The field help states the per-type rule. Cost: small; about 10 lines plus pins in the plugin-auth wrapper, and the select options and help text in platform-objects (other lane). Acceptance changes only for registrations that declare native, which today are judged as web."
    ],
    "recommendation": "D (with A's help-text correction folded in), on the four axes. Business need, measured: the form's type param is written by every Console registration (platform-objects sys-oauth-application.object.ts:153-157) and read by nothing; examples/ registers no OAuth client; the measured need is the maintainer's dev-admin pass (hotcrm#2188 F-22), which a native-typed registration meets through the vendor's own loopback path. Caveat: a developer testing a browser web app on localhost would register it as Native under D. If the maintainer rules that need real and not served by Native, that points to B, at its fork cost. Long-term: D is contract-first; it enforces the declared field and keeps the vendor validator as the single authority with no fork, whereas B/C fork a security validator. AI-safety: D makes the declared type real and refuses an unknown one loudly; A keeps exactly the declared-but-not-honoured shape the axis forbids, and B makes acceptance deployment-dependent. Startup focus: D adds no capability and retires one dead option (user-agent-based). No new gate is recommended; none was named by the maintainer."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door POST /api/v1/auth/sys-oauth-application/register with {type:'native', redirectURLs:'http://127.0.0.1:4001/callback'} answers the web refusal 'web clients require https redirect URIs on non-loopback hosts' (400 after #24853, 500 before), and {type:'native'} with an https URI stores application_type 'web'. The Console's Application Type select never reaches the provider · evidence: auth-plugin.ts register wrapper forwards type: safeType (and silently coerces an unknown type to web); @better-auth/oauth-provider 1.7.3 dist authorize-9whjxVLJ.mjs createOAuthClient body declares application_type and no type; measured on the real stack (probe on base c3a8e07 before any edit, issue-24773/probe-before.log); called directly with application_type 'native', the vendor accepts http://localhost:4001 and http://127.0.0.1:4001 · Seam: spec:sys_oauth_application create action param type → runtime:plugin-auth sys-oauth-application/register wrapper → vendor /oauth2/create-client (strips) · fold into the #24773 half-2 decision card, because fixing it changes acceptance for native registrations (human floor), not a standalone card · dedupe words: oauth application type native dropped, application_type stripped, register native localhost, user-agent-based",
    "class: a (misleading copy) · reach: the Console Register OAuth Application form (sys_oauth_application create action, packages/platform-objects/src/identity/sys-oauth-application.object.ts:152) shows 'One URL per line. Must use https:// in production.', while a web client is refused for any loopback host in every environment and for http on any host. The maintainer's QA pass read loopback http as allowed from it (card: 'Expected: the field's help ... (loopback http allowed)') · fold into the #24773 half-2 decision card; the text follows whichever rule is decided · dedupe words: oauth redirect help text https production, redirectURLs helpText",
    "carrier: none (承接者:无) · noted, not filed, in the PR's Acceptance notes: on a server fault the register door answers with the thrown error's raw message (measured: the test's 'storage fault (test)') and logs nothing on that inner branch. This is pre-existing, unchanged, with no measured leak of sensitive data."
    ],
    "line_budget": "229 changed lines (+227 / -2) across 3 files, per dispatch-gates at 622e30a against the 3000 human-merge threshold: under. Source: auth-plugin.ts +12 / -2 (import + comment + one line); test +203; changeset +12. No governed surface is touched, and skills/** is untouched, so no ratchet ledger applies.",
    "deviations": [
    "The PM's suggested route mapped the vendor refusal to 400. The landed route reads the refusal's own declared status through @objectstack/types resolveThrownHttpError (the repo's shared rule), which gives 400 for invalid_redirect_uri and also 401 for a missing session (UNAUTHORIZED). That is the same defect at the same line; it is disclosed in the PR and the changeset, and pinned.",
    "The first commit hand-read statusCode inline; a later commit (5dc8add) switched to resolveThrownHttpError once that rule was found. The ablation was re-run on the final code, and those are the cited legs. In the first (superseded) ablation, my own supplementary echo was mis-quoted and printed '0 marker'. The tool's own disk verification (replacement 0→1, blob changed) and the observed 500s show that the mutation landed. The final-code legs' echo read correctly (1 marker).",
    "I stopped two of my own background runs, by their recorded process-group ids 910 and 1586, after the source changed mid-run, and re-ran both on the final tree. No other process was touched.",
    "Merged origin/main (5cb6e6e) before opening the PR, per AGENTS §10. The post-merge closure build used the filters '@objectstack/plugin-auth...' and '^...', which also built downstream dependents. That was wider than needed, but it is what satisfied check:dual-build-cjs-loads' whole-repo prerequisite.",
    "test:repo was not run locally (CI Test Core). check:dev-prereqs and check:skill-frame-freshness were not run because neither trigger applies.",
    "The report status is 'done' for the dispatched scope (half 1 fixed, half 2 measured and reported). The card stays open: the PR says 'Part of #24773', with no closing keyword."
    ],
    "files_changed": [
    "packages/plugins/plugin-auth/src/auth-plugin.ts",
    "packages/plugins/plugin-auth/src/oauth-register-refusal-status.test.ts",
    ".changeset/24773-oauth-register-refusal-status.md"
    ]
    }


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24853 comment 6289090336 (half 1) · domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T10:43Z


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (half 1) · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T11:30Z

    Reading Value
    PR #24853 timeline added_to_merge_queue 2026-10-20T11:06Z → merged 2026-10-20T11:29Z
    origin/main merge commit 950f949a9c is an ancestor (git merge-base --is-ancestor, exit 0)
    By content on origin/main auth-plugin.ts reads resolveThrownHttpError(err, 500) in the register wrapper (1 hit)
    This card stays open: the PR was Part of #24773. Half 2 is the decision below.

    ACCEPT 6289090336 is on the PR.


    [Decision] 在控制台注册 OAuth 应用时,「应用类型」该不该真正生效?本机回环地址(localhost)的回调该按哪种类型的规则判?

    domain:services seat 2 (seat post #21118) · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y。依据:本卡开发报告 6289049273(实测于 @better-auth/oauth-provider 1.7.3)。前半(拒绝答 500)已由 PR #24853 落地。

    Release: session_01UmqzD97e14iws6hmVhgZ5y · 因:前半已落地,余下后半是放宽认证入口接受面的人工地板决定 · 去向:needs-user-decision,本笔清 assignee。

    一句话问题: 管理员在控制台注册一个本地开发用的 OAuth 应用,回调填 http://localhost:4001/callback,无论「应用类型」选什么都被拒;因为表单里的「应用类型」从来没有传到认证库,所有应用都被当成 Web 应用判。

    背景(实测)

    • 认证库对 Web 应用的规则是:回调必须 https 且不是本机地址,任何环境、无配置项可改。这是有意设计,不是误判(卡片原先的读法被证伪)。
    • 对 Native 应用,认证库按 RFC 8252 允许 http 本机回环(localhost / 127.0.0.1 / [::1],任意端口)。
    • 控制台把表单的「应用类型」以 type 字段发出,认证库的请求结构只认 application_type,于是 type 被丢弃:选 Native 也按 Web 判、按 Web 存。表单的四个选项里 user-agent-based 与 public 在认证库没有对应值,也被静默当成 Web。
    • 字段说明写「Must use https:// in production」,让测试者以为本机 http 可用。
    • SDK 路径(oauth.applications.register)本就带 application_type,只有控制台这扇门丢了它。

    Governing text

    • 认证库 @better-auth/oauth-provider 1.7.3 的 validateClientRedirectUri:Web = https 且非回环;Native = 允许 http 本机回环。
    • packages/platform-objects/src/identity/sys-oauth-application.object.ts:注册动作声明了 type 参数(四个选项)与回调说明文案。
    • Prime Directive chore: version packages #10(声明即强制):声明了却不生效的字段不该留着。
    • 检索:Prior rulings read 见下方四棱块,零命中。

    是否改协议: 不改 spec。方案 D 改的是认证入口对 Native 应用的接受面(按认证库自身规则),属于人工地板。

    前提(复核命令)

    • 包装层仍以 type 转发:git grep -n "type: safeType" origin/main -- packages/plugins/plugin-auth/src/auth-plugin.ts(对照词 createOAuthClient 必中)。
    • 表单声明四个类型选项:git grep -n "user-agent-based" origin/main -- packages/platform-objects/src/identity/sys-oauth-application.object.ts。

    选项

    选项 做什么 客户可感知后果 工作量
    A 只改文案 字段说明按类型写清规则;「类型」继续被丢弃 本地开发仍注册不了;选 Native 仍被当 Web platform-objects 1 个小 PR(engine 车道);无先例
    B 非生产环境允许 Web 用本机 http 绕开认证库自带的校验,自己判或自己写行 本地开发可用 Web 类型;但每次升级认证库都要重做,且同一份配置在不同环境结论不同 中到大;无先例
    C 任何环境都允许 Web 用本机 http 同 B,且永久放宽 Web 边界 同 B,安全姿态更差 中到大;无先例
    D 让「应用类型」真正生效 包装层把选择作为 application_type 传给认证库:Native 用认证库的回环规则,Web 维持 https 且非回环;认证库没有的类型(user-agent-based、public)响亮拒绝 400 并从选项中退役;说明文案按类型写清 本地开发选 Native 即可用 localhost;选错类型会得到明确提示,不再被静默改成 Web plugin-auth 约 10 行 + 钉子(services);选项与文案在 platform-objects(engine 车道,另起小 PR);无先例

    业务含义直译: A = 「告示写清楚,但门还是只认一种钥匙」;B/C = 「为了本地方便,自己另配一把绕过门锁的钥匙」;D = 「表单上让你选的钥匙类型,门终于真的认了」。

    推荐:D(并入 A 的文案修正)

    • 终态(按①): 两年后,控制台注册 OAuth 应用时声明的类型就是认证库执行的类型,本地开发走 RFC 8252 的 Native 回环规则,Web 永远 https。认证库是唯一的校验权威,我们不维护一份分叉校验。参照 RFC 8252(原生应用经本机回环接收授权码的规范),认证库正是按它区分 Native 与 Web。注意:有的提供方(如 Google)在 Web 类型上也放行 localhost,这正是 B 的取向,代价见下。
    • 为何不选 A: 留下一个声明了却不生效的字段,AI 或作者选 Native 仍被静默当 Web。
    • 为何不选 B / C: 要绕开一道安全校验并长期维护,结论随环境变化;没有实测到这扇门之外的需求。
    • 置信缺口: 「用 localhost 测试浏览器 Web 应用」的开发者在 D 下要注册成 Native;如果你认为这个需求真实且 Native 不能满足,那指向 B,代价是分叉校验。public 类型在认证库里是否另有对应(例如免密钥的公开客户端)未实测,D 的派发会先测再定其去留。
    • 自检:只看①选 D;②③④ 是否翻转:否。

    os-decision-facets

    • ① 项目长远合理性:D 让声明的字段生效、校验只在认证库一处,不在我们这边分叉;B/C 长期维护一份绕开安全校验的分叉。
    • ② 实际业务拉动:维护者 HotCRM 走查(QA: full functional browser pass of HotCRM main b94cf856 (18.0.0-next.5) over all 161 feature-inventory rows — one cloud tester, screenshots (maintainer task 2026-10-20) hotcrm#2188)实测撞上本地开发注册;控制台每次注册都写 type,却无人读取。
    • ③ 防 AI 犯错:D 把声明的类型变成真,并对未知类型响亮拒绝;A 保留「声明了却不兑现」的形状;B 让同一份元数据随部署环境得出不同结论。
    • ④ 创业阶段不扩散:D 不加新能力,反而退役一个没有对应的选项;B/C 加一个环境开关和一份分叉。三者都不新增门禁。
      Prior rulings read: redirect_uri,loopback,application_type,oauth-provider → 0 hits; none; thread: none; repo: objectstack-ai/objectstack
      (Cross-checked by git grep -n -iE 'redirect_?uri|loopback|application_type' origin/main -- AGENTS.md docs/adr packages/spec/src: AGENTS.md 0, docs/adr 0 (control oauth → 18 ADR files), packages/spec/src 10 hits, all on storage loopback and connector auth, none on OAuth application registration.)

    裁后执行

    • 裁 D: 席位派发本卡:plugin-auth 包装层把类型以 application_type 转发,未知类型拒 400(沿用 OAUTH_REGISTER_FAILED),钉子覆盖 Web 拒本机、Native 收本机、未知类型拒绝,并做消融;先实测 public 在认证库的对应。选项退役与文案修正另起 platform-objects 小 PR(engine 车道,席位先跨车道声明)。
    • 裁 A: 只派 platform-objects 文案卡,本卡以「文案已修、类型字段不生效」关闭需另立「类型字段退役」卡,以免留下声明不兑现。
    • 裁 B / C: 本卡转为设计卡,先出分叉校验方案再派。

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Maintainer ruling: D (「决策箱 三张同意」, the triage seat's chat, on triage's table that listed #24773 → D). Triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T14:33Z

    • State: needs-user-decision → pm:queue. The services seat dispatches by this card's 裁后执行 for D (6289866018):
      • the wrapper forwards the chosen type as application_type;
      • the pins cover Web refused on loopback, Native accepted, and an unknown type, with ablation;
      • public is measured first;
      • the option and copy fixes go in a separate platform-objects PR, with a cross-lane declaration.
    • Under the standing 「甲」 rule ([PM seat] triage (objectstack-wide) — 🟢 Routine · session_01AavokzJ5DndAwitDXvKy4U · trig_01NcnCtMS2tH46nUg1a5TaMJ (hourly) · state = body + the round records newer than it #6015 6292005854):
      • Honouring the declared type is a fix, and ships in 18.x.
      • Refusing user-agent-based / public (400) is a narrowing of what the door accepts, and no ADR-0087 conversion carries it. So 18.x removes them from the form's options and answers with a warning, and the 400 is filed for 19.0. The exception: the seat measures that no stored row or caller ever sent them, and records that reading here.
    • Not release-blocking. It is a candidate for 18.0.1 / 18.1.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-20T21:55Z
    Session: session_01UmqzD97e14iws6hmVhgZ5y
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24773-oauth-application-type
    Worktree: objectstack-issue-24773
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface (half 2a, services; the ruling's plugin-auth part):

    • packages/plugins/plugin-auth/src/auth-plugin.ts: the OAuth client register wrapper only (the createOAuthClient path that forwards type: safeType, ~:3661 at 78ab6474c);
    • new test files under plugin-auth/src/;
    • one .changeset/24773-*.md.

    ⛔ Not packages/platform-objects/**: the form's option retirement and its copy are half 2b, a separate platform-objects PR the seat dispatches after this one lands, declared cross-lane then.
    ⛔ Not packages/spec/**, not .github/workflows/**, and no other region of auth-plugin.ts.
    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, derived at 78ab6474c: no path-derived mandate, floor sonnet, default opus, ceiling fable)
    Clause-②: no — ruling D makes the chosen type take effect under the auth library's own rule; that is a runtime authentication behaviour on the human floor, which the maintainer answered. The dev measures and states it.
    Responsibility: plugin-auth's register wrapper forwards the form's type as 'type', which @better-auth/oauth-provider drops, so every application is judged and stored as web | the auth library's application_type, which the SDK path already sends | every admin registering an OAuth application from the console; measured in the maintainer's HotCRM pass (objectstack-ai/hotcrm#2188)
    Thread-read: 6293344532
    Serial constraints cleared: PR #24959 (#24766, applyBrand in auth-plugin.ts) landed as 78ab6474c. No open PR touches auth-plugin.ts or an OAuth file (every open PR's file list read this round). No in-flight domain:services claim touches plugin-auth: #24757 is plugin-security, and seat 1's #24962 / #24966 are plugin-security / plugin-webhooks.

    This dispatch carries out the maintainer's ruling D (6293344532) under 「甲」 (#6015 6292005854). Honouring the declared type ships in 18.x. user-agent-based / public get a warning in 18.x, and their 400 is filed for 19.0, unless the measured exception holds: no stored row or caller ever sent them. public is measured first.

    Widened in place for half 2b (after half 2a's report and the seat record 6303676890; same card, same seat, a second dev after the first finished):
    Branch: claude/issue-24773-oauth-form-types
    Worktree: objectstack-issue-24773 (re-created; half 2a's was removed)
    File surface (half 2b), cross-lane into domain:engine and declared here:

    • packages/platform-objects/src/identity/sys-oauth-application.object.ts: the register_oauth_application action's type param options and the redirectURLs help text; the stored type select and the public column only as measured and stated, ⛔ not narrowed in 18.x;
    • its generated translation bundles packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.objects.generated.ts;
    • new test files under platform-objects/src/;
    • one .changeset/24773-*.md.

    Container & model (2b): S, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier on this surface, derived at 4b8f597ba: no path-derived mandate).
    Clause-② (2b): expected no, provided the register door's accept set is unchanged; the dev measures whether anything server-side refuses a value the form stops offering.
    Serial constraints cleared (2b): no open PR touches sys-oauth-application.object.ts or the platform-objects translation bundles (every open PR's file list read). PR #24992 (half 2a, plugin-auth) is disjoint.

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "needs_decision",
    "branch": "claude/issue-24773-oauth-application-type",
    "pr": "#24992",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y. This is the parent PM session's id (mode:subagent); this run is identified by its branch.",
    "premise_still_valid": true,
    "summary": "Half 2a is built: draft PR #24992 (Part of #24773) at head e9f4deb. The sys-oauth-application/register wrapper in auth-plugin.ts now forwards the form's Application Type as application_type. web and native are forwarded, and the provider's own redirect-URI rule decides, unchanged. user-agent-based, public and any other value keep today's outcome (registered and judged as web, with a secret) and log exactly one ctx.logger.warn per registration, meta { requestedType, registeredAs: 'web' }. An absent type stays web with no warning. Half 1's error mapping is unchanged. status is needs_decision for ONE question only. @better-auth/oauth-provider 1.7.3 has a real counterpart for 'public' (token_endpoint_auth_method 'none', a client issued with no secret), so per the dispatch it is not forwarded or mapped. The mapping is open_questions[0]. Everything else in the dispatched scope is done. Premise holds: on base 78ab647 the wrapper sent type, the provider stripped it, and native + http://localhost:4001/callback got the web refusal (measured, before-fix run). PM assumption 3 is partly disproved: the SDK's oauth.applications.register request type does not declare application_type (see pm_assumptions).",
    "public_measurement": "MEASURED on the installed @better-auth/oauth-provider 1.7.3 (node_modules, dist/authorize-9whjxVLJ.mjs): 'public' HAS a real counterpart. (1) persistOAuthClientRegistration (:1921, :1929) sets isPublic = body.token_endpoint_auth_method === 'none' and then issues and stores NO client secret (clientSecret undefined). (2) checkOAuthClient accepts 'none' via getSupportedAuthMethods(opts, { includeNone: true }) (:1759). (3) The /oauth2/create-client body declares token_endpoint_auth_method (:2694) and application_type z.enum(['web','native']) (:2695). 'public' is a value on the auth-method axis, never an application_type. (4) Discovery advertises 'none' among token_endpoint_auth_methods_supported when public clients are supported (:749). So 'public' was NOT forwarded or mapped. In this PR it behaves like user-agent-based (web, with a secret, plus the warning). The mapping is open_questions[0]. Related: in the provider's earlier lines (1.7.0-rc.2 and 1.6.33, read from npm tarballs, not installed), 'user-agent-based' was valid ONLY for a public client ('Type must be native or user-agent-based for public applications'). In 1.7.x a browser public client is application_type web plus token_endpoint_auth_method none. So the same question covers both form values. In-repo use of the counterpart: MCP clients register as public native clients through DCR (auth-manager.mcp-oauth-resource.test.ts:235, token_endpoint_auth_method 'none' + application_type 'native'). The SDK declares token_endpoint_auth_method 'none' on oauth.applications.register.",
    "exception_reading": "For the seat to record and rule. Question: did any stored row or any caller ever send user-agent-based / public? Reading: the exception is NOT established. STORED ROWS, console door. Measured on a real ObjectQL engine + SqlDriver (better-sqlite3 :memory:) with plugin-auth's authIdentityObjects, through the real route on real better-auth. On base 78ab647 (before-fix run) and on head e9f4deb, a registration with type user-agent-based, public or spa stores sys_oauth_application.type = 'web', public = null and token_endpoint_auth_method = 'client_secret_basic'. No column holds the form's value. Pinned in the new test. 1.7.3 declares only web/native for application_type on create (/oauth2/create-client :2695, /admin/oauth2/create-client :2523) and update (/admin/oauth2/update-client :2885, /oauth2/update-client :2923). checkOAuthClient, which every registration path runs, refuses any other value (:1770). Its record has no 'public' field (OAUTH_CLIENT_RECORD_FIELD_NAMES :1034), so the installed provider can write only 'web' or 'native' into that column. EARLIER PROVIDER LINES (npm tarballs read in the scratchpad, not installed): (a) @better-auth/oauth-provider 1.7.0-rc.2 and 1.6.33 declared type z.enum(web, native, user-agent-based) and refused any non-'web' type on a confidential client. The door never sends token_endpoint_auth_method, so user-agent-based (and native) was refused at this door then, and the wrapper coerced 'public' to 'web' (it was not in allowedTypes). (b) better-auth 1.6.33's oidc-provider plugin wrote type: 'web' on every /oauth2/register. LIMITS: (i) Repo history before 2026-09-07 is not in this shallow checkout (root ac76425). At that root plugin-auth already pinned ^1.7.2 and the wrapper's allowedTypes was identical, so which provider line the wrapper ran on earlier was not measured from git. (ii) Rows that OTHER doors wrote under rc.2 / 1.6.x cannot be measured: the SDK create-client and DCR accepted type user-agent-based for public clients there. Deployments' databases are out of reach. CALLERS: (a) In-repo, zero producers send either value to this door. Tests send no type or 'web' (in-process-session-renewal.pin.test.ts:398, oauth-register-refusal-status.test.ts). Zero in examples/, packages/qa and content/docs. The SDK never builds this URL (auth-route-ledger disposition server-only). git grep 'user-agent-based' hits only the form (sys-oauth-application.object.ts:156, :425), its generated translations and the wrapper. (b) HotCRM main 1cc6861a3: zero files for 'sys-oauth-application/register', 'user-agent-based', 'application_type', 'oauth2/create-client' and 'applications.register' (control: case-insensitive 'oauth', 18 files). (c) objectui origin/main 12cde0492 (local ref, not re-fetched): zero files for the door path, 'user-agent-based' and 'application_type' (control: 'sys_oauth_application', 5 files). (d) The one live producer is the Console form itself: sys-oauth-application.object.ts:153-158 offers both values to every admin. Whether an admin ever picked one is not measurable, because no request log exists in reach.",
    "pm_assumptions": [
    "A1 HOLDS. At 78ab647 the wrapper (auth-plugin.ts:3661) forwarded type: safeType. The 1.7.3 create-client body declares application_type and no type, so type is stripped and every registration is judged and stored as web. Measured: precondition pin ('type: native' + loopback is refused with invalid_redirect_uri) and the before-fix run (native + localhost: 400 web refusal).",
    "A2 HOLDS, with two refinements. (a) The web rule is https AND not loopback. (b) The native http rule allows exactly localhost / 127.0.0.1 / [::1], any port (validateClientRedirectUri :1685). But the body schema's SafeUrlSchema (@better-auth/core utils/redirect-uri) refuses plain http on a non-loopback host for EVERY type, BEFORE the per-type rule. So native + http://app.example.com/callback is refused as a body-schema ValidationError (400, no upstreamError), not as invalid_redirect_uri. And http://127.0.0.2:4001 passes the body schema but is refused by the native exact-host rule. Native + https on a loopback host is refused ('native clients must not use https loopback redirect URIs').",
    "A3 PARTLY DISPROVED. The SDK's route /oauth2/create-client accepts application_type, and the SDK response type OAuthApplication declares it (packages/client/src/index.ts:1055). But the SDK's oauth.applications.register REQUEST type (index.ts:4535-4547) does not declare application_type. A typed caller can register a native client only through a cast; the body is passed through JSON.stringify(req) unchanged. Noted in Acceptance notes, not filed."
    ],
    "tests": "New packages/plugins/plugin-auth/src/oauth-register-application-type.test.ts, 15 cases. It runs real better-auth (OIDC provider on) behind the real route registration on a real Hono app, over a real ObjectQL + SqlDriver (better-sqlite3) engine. 'Stored as' is read at driver level. Cases: 2 provider preconditions; web + localhost refused (400 OAUTH_REGISTER_FAILED, invalid_redirect_uri, nothing stored, no warning); native + localhost and native + 127.0.0.1 accepted and stored 'native'; native + http://app.example.com refused (body schema, 400, nothing stored); native + http://127.0.0.2:4001 refused (native exact-host rule); web + https and no type stored 'web' with no warning; user-agent-based / public / spa + https stored 'web', no column holds the value, not a public client, exactly 1 warning with meta; the same three + localhost refused as web with exactly 1 warning. BEFORE the fix, on 78ab647: 'Tests 9 failed | 5 passed (14)'. 2 native-loopback cases failed with 'expected 400 to be 200' (web refusal); 6 unhonoured cases failed only on 'expected [] to have a length of 1 but got +0', after their storage assertions passed; 1 was this file's own first assertion expecting invalid_redirect_uri on the non-loopback native case, which the body schema answers (corrected). AFTER, on e9f4deb: this file + oauth-register-refusal-status.test.ts gave 'Test Files 2 passed (2) / Tests 32 passed (32)' (os-verify-lock VERDICT command-exit 0). Full package suite at d674628 (plugin-auth byte-identical at e9f4deb): 'pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2' gave 'Test Files 174 passed (174) / Tests 3334 passed | 10 skipped (3344)' (VERDICT command-exit 0). 'pnpm --filter @objectstack/plugin-auth typecheck' gave exit 0 after building plugin-auth (the first attempt exited 2 because tsconfig.examples.json reads the package's own dist): 'check:test-typecheck: OK ... 23 pinned signature(s) held', no debt added. The new file is in the tsconfig.test.json program (--listFilesOnly: 1 hit). ABLATION, through scripts/ablation-replace.mjs (anchor x1 -> x0, blob changed, disk-verified). The runner had its own EXIT/INT/TERM trap; every restore was verified as blob 01004ce16 == HEAD with git diff HEAD empty. The test imports ./auth-plugin from source, so there is no dist leg. Leg 1: application_type: applicationType -> type: applicationType (the old type forwarding) gave 'Tests 2 failed | 13 passed', native + localhost / 127.0.0.1 'expected 400 to be 200'. Leg 2: applicationType = type -> 'native' gave 2 failed: web + localhost 'expected 200 to be 400'; web + https stored 'native'. Leg 3: unhonoured values forwarded as native gave 6 failed (all six unhonoured cases). Its first attempt was refused by the tool (the anchor was contained in the replacement, 'anchor count moved 1 -> 1'), ran nothing, and was redone with a non-overlapping anchor. Leg 4: the door rewrites the URI to an accepted one (http://app. -> https://app., 127.0.0.2 -> 127.0.0.1) gave 2 failed, both native negative pins 'expected 200 to be 400'. Logs: scratchpad issue-24773/2a/before-fix.log, after-fix2.log, final-files.log, suite-full.log, typecheck2.log, ablate-*.log.",
    "gates": "Local union: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' at e9f4deb derived 72 commands (35 pnpm, 37 node), identical to the seat's list at 78ab647. Each ran as 'cmd > log 2>&1; rc=$?'. 71/72 exited 0 on the first pass. pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, no whole-repo dist). After 'turbo run build --filter=!@objectstack/docs --concurrency=2' (76 tasks, 75 cached) it exited 0: '112 published require entry point(s) across 69 package(s) load'. '--ran' printed 'Run reconciliation — 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 72 derived famil(ies) accounted for'. Among the 72: check:adr-0087-registration --base origin/main, check:changeset-no-major, check:empty-changeset, check:closing-keyword-parity, check:error-status-conformance, check:route-envelope, check:auth-mount-ledger, check:durability-log-level, check:issue-citations, check:nul-bytes, check:test-source-alias, check:engine-double-contract, check:type-check-debt, check:query-options-erasure. All exit 0. Not run locally: test:repo (CI Test Core); check:dev-prereqs and check:skill-frame-freshness (no spec build input or frame file touched); the CI-only type-check lanes and the CI jobs dispatch-gates lists outside the 72. CI on PR #24992 at e9f4deb, read right after pr_create: 'No other open PR may claim the same issue' success; 18.x upgrade harness and Packed-tarball smoke skipped (opt-in); every Lint · and Type Check · lane, Check Changeset, Check PR Size, Governed Surface Queue Guard, 'Part-of PR must not also close its card', 'The card this PR closes must claim this branch', 'No other open PR may claim the same single-writer path', Check Documentation Links and 'Flag docs affected by code changes' were in_progress. Test Core and Build Core were not yet listed. in_progress is the honest value; CI convergence is the PM's.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes through scripts/pm/fleet-write/dispatch.mjs, each one POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, run 39511280255, executed POST /repos/objectstack-ai/objectstack/pulls (created #24992, draft) and POST /repos//issues/24992/assignees (os-support-ai). Read-back: 12074 bytes sent, 12074 stored, identical; assignees os-support-ai. (2) comment, POST /repos//issues/24773/comments: this report. Labels: none (none named by the dispatch; skip-changeset does not apply, because plugin-auth publishes and a patch changeset is included). git push x3 (the empty-branch probe, d674628, then d58f7da + merge e9f4deb) is not REST. Reads were plain gh api GETs: #24773 and its comments, #6015 comment 6292005854, PR #24992 and its check-runs.",
    "open_questions": [
    {
    "question": "Should the Console door map the form's 'public' (and, by the same reading, 'user-agent-based') to the provider's public client, i.e. token_endpoint_auth_method 'none', with the client issued no secret? @better-auth/oauth-provider 1.7.3 has that counterpart (see public_measurement), but 'public' is not an application_type. Issuing a secretless client from the Console is a security-posture change on the human floor, so it was not mapped. In this PR it is registered as a confidential web client with a secret, plus the warning.",
    "options": [
    "A. No mapping, as ruled (D under 「甲」). 'public' stays a confidential web client with one warning in 18.x. Half 2b removes it from the form, and the 400 is filed for 19.0. Public clients stay available where they are used today: DCR (MCP clients register token_endpoint_auth_method 'none' + native) and the SDK (oauth.applications.register declares token_endpoint_auth_method 'none'). Cost: none beyond the ruled 2b and 19.0 cards. Business need: zero measured producers send 'public' to this door (in-repo, HotCRM main, objectui), and the measured public-client consumer (MCP) does not use the Console. Long-term: the door honours exactly the axis the provider judges. AI-safety: after 2b the form cannot declare a value the door does not honour, with a loud warning meanwhile. Startup focus: adds nothing.",
    "B. Map 'public' (and 'user-agent-based') to { application_type: 'web', token_endpoint_auth_method: 'none' }. Cost: about 5 wrapper lines plus pins. 2b must also change the result dialog, which today promises a client_secret 'shown only once'. Business need: none measured. Long-term: one select value becomes two provider settings, and the mapping is lossy (a public NATIVE client cannot be expressed). AI-safety: worst. 'Public' reads as 'public-facing website' and would silently yield a client with no secret, a security surprise. Startup focus: adds a capability with zero measured pull.",
    "C. Model both provider axes in the form: Application Type (web | native) plus Client authentication (confidential with secret | public, token_endpoint_auth_method 'none'). Retire 'user-agent-based' and 'public' from the type select. Cost: wrapper and platform-objects form, copy and dialog changes (both lanes). Business need: none measured. Long-term: the most faithful to the provider's model. AI-safety: explicit and hard to misread. Startup focus: adds a form field and a capability without pull."
    ],
    "recommendation": "A. Business need is the deciding axis: nothing measured asks the Console for secretless clients, and the real public-client consumer (MCP) registers through DCR, already served. B is a lossy translation that turns a misreadable word into a missing secret, the shape the AI-safety axis forbids. C is right only if a Console public-client need is measured later, and startup focus says not before then. A keeps today's warning text accurate ('A future major version refuses this value'). If the seat picks B or C, the warning for 'public' changes in that PR. No new gate is recommended, and none was named by the maintainer."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed, in the PR's Acceptance notes. The SDK oauth.applications.register request type (packages/client/src/index.ts:4535-4547) does not declare application_type, while its route accepts it and its response type declares it. A typed caller can register a native client only through a cast. Not class a/b/c: it is not a defect, and the SDK does not claim the capability.",
    "carrier: #24773 half 2b (the seat) · noted, not filed. The stored column sys_oauth_application.type (sys-oauth-application.object.ts:425) also lists 'user-agent-based' and 'public', which 1.7.3 never writes. The 'public' boolean column (:432) is never written by 1.7.3: its record has no such field, and it was null in every stored row measured here. For the seat to fold into 2b or rule on."
    ],
    "line_budget": "382 changed lines (+379 / -3) across 3 files, per dispatch-gates at e9f4deb, against the 3000 human-merge threshold: under. auth-plugin.ts +32 / -3, register wrapper only; new test +334; changeset +13. No governed surface touched; skills/** untouched, so no ratchet ledger applies.",
    "deviations": [
    "The card's ablation 'put back type: safeType' was spelled 'type: applicationType'. safeType no longer exists, and the forwarded value is the same for web/native, so it is the same defect. It turned the two native pins red as the card predicted.",
    "Added beyond the card's pin list: 2 provider preconditions, a native + http://127.0.0.2 negative pin (the exact-host rule), an arbitrary value 'spa', and a no-type case. Ablation legs 2-4 cover the negative pins, per the lane commitment.",
    "The card's 'Native + a non-loopback http URI refused' is answered by the provider's body schema (SafeUrlSchema), not its native rule, so its pin asserts 400 OAUTH_REGISTER_FAILED with no upstreamError. The 127.0.0.2 pin covers the native rule itself.",
    "Edge semantics chosen in the wrapper, all keeping today's web outcome: null is treated like absent (no warning); a non-string type warns as '(number)' / '(object)' / '(boolean)'; a string value is truncated to 64 chars in the warning.",
    "The warning text ends 'A future major version refuses this value.' for every unhonoured value, 'public' included, per the ruling as it stands. If the seat maps 'public' (open_questions[0] B/C), that sentence goes with the change.",
    "origin/main advanced to e4d604e (2 commits: plugin-security #24985 and release-verify-npm #24989; no overlap with this diff) after my merge of 7adfaec and before pr_create. The PR was opened at e9f4deb without re-merging; the gate union and test reads are at e9f4deb.",
    "The full plugin-auth suite ran at d674628. e9f4deb adds only the changeset wording commit and the main merge (objectui pin files), with plugin-auth byte-identical (git diff --stat empty). The two OAuth register files were re-run at e9f4deb.",
    "For the HotCRM caller reading I ran 'git fetch origin main' in the shared /home/user/hotcrm checkout, which advanced its refs/remotes/origin/main to 1cc6861a3 (a read-only fetch, no working-tree change). The objectui reading used its existing origin/main ref 12cde0492 without fetching, so it may be stale.",
    "For the earlier-provider-line reading I downloaded npm tarballs (@better-auth/oauth-provider 1.7.0-rc.2 and 1.6.33, better-auth 1.6.33) into scratchpad issue-24773/2a/npm-dl and read them with grep/sed only. Nothing from them was executed or installed.",
    "The commit trailer uses AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named form, because the pre-push hook refuses a model identifier.",
    "The PR body's first line is 'Part of #24773: half 2a, ...' (colon, then prose). The H7 Part-of/closing-keyword check on the body returned null (clean), and no closing keyword is adjacent to any issue number.",
    "No exported symbol was renamed or removed, so check-platform-checklist --changed-from was not run."
    ],
    "files_changed": [
    "packages/plugins/plugin-auth/src/auth-plugin.ts",
    "packages/plugins/plugin-auth/src/oauth-register-application-type.test.ts",
    ".changeset/24773-oauth-application-type.md"
    ],
    "pr_body_edits": "none"
    }


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat record for half 2a: the public measurement, the 「甲」 exception reading, and the seat's call · domain:services seat 2 (seat post #21118) · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T22:58Z

    The ruling (6293344532) asked the seat to measure public first and to record the exception reading here. Both come from the half-2a dev report on this card and PR #24992, measured on the installed @better-auth/oauth-provider 1.7.3.

    1. public. The library has a real counterpart, but on another axis: a public client is token_endpoint_auth_method: 'none', issued with no client secret. It is not an application_type, which accepts only web | native.

    • In 1.7.x a browser public client is web + none, so the same reading covers user-agent-based.
    • MCP clients already register as public native clients through dynamic registration, not through the Console.

    Seat's call: no mapping (the dev's option A), which is ruling D as written.

    • Ruling D already disposes of both values: the form drops them in 18.x (half 2b), the door warns, and the 400 waits for 19.0.
    • Mapping them to a secretless client (option B), or adding a client-authentication field to the form (option C), would be a new Console capability that issues clients with no secret. That is a security-posture change on the human floor, with zero measured pull: no producer sends either value to this door in the repo, HotCRM or objectui.
    • If a Console public-client need is ever measured, it comes back as a decision card, not as a change to this PR.

    2. The 「甲」 exception reading: not established.

    3. Carried into half 2b: the stored sys_oauth_application.type select still lists user-agent-based and public, and the public boolean column is never written by 1.7.3. Half 2b measures and states what it does with each.

  9. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24992 comment 6303941375 (half 2a) · domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T23:17Z · head e9f4debc1b, ordinary queue landing.

  10. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (half 2a) · domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T23:47Z

    • PR fix(plugin-auth): the register form's Application Type reaches the OAuth provider as application_type #24992 merged 2026-10-20T23:46Z as fbb42d900. git merge-base --is-ancestor fbb42d900 origin/main holds. The PR was Part of #24773, so the card stays open.
    • What landed:
      • the Console's register door forwards the chosen type as application_type, so a Native client can register http://localhost, http://127.0.0.1 or http://[::1] redirect URIs and a Web client keeps the https rule;
      • user-agent-based / public keep today's outcome, with one server warning each.
      • ACCEPT 6303941375.
    • Still on this card:
      • half 2b (the form's options and help text, platform-objects), in flight under the widened claim 6302570331;
      • then the 19.0 refusal (400) of user-agent-based / public. The card moves to pm:on-hold for it when 2b lands.
    • The card stays pm:dispatched while 2b is in flight.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:servicespm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions