Skip to content

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

Closed

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. 8 remaining items

  6. 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.

  7. 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.

  8. 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.
  9. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "done",
    "branch": "claude/issue-24773-oauth-form-types",
    "pr": "#25011",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y. This is the parent PM session id (mode:subagent); this run is identified by its branch.",
    "premise_still_valid": true,
    "summary": "Half 2b is built: draft PR #25011 (Part of #24773) at head fd686be. In sys-oauth-application.object.ts the create_oauth_application action (label 'Register OAuth Application') now offers only Web and Native for its type param (required, default 'web'). The Redirect URLs help states the provider's rule per type: Web needs https:// on a host other than localhost or another loopback address, and Native may also use http:// on exactly localhost, 127.0.0.1 or [::1], on any port. A new Application Type help says to choose Native for a desktop or mobile app, or for a local-development app that redirects to http://localhost. The en / zh-CN / ja-JP / es-ES objects bundles were regenerated with pnpm i18n:extract, and the translated leaves were written by hand; the source-hash companions end unchanged. Not changed: the register route's accept set, the stored type select and the public column. One patch changeset, Clause-② no. #24992 (half 2a) landed as fbb42d9 during this run and is merged into the branch. With it, the register door was measured live to do what the help says: Native accepted on localhost, 127.0.0.1 and [::1], stored native; Web refused on localhost.",
    "stop_condition": "MEASURED NEGATIVE, so the work proceeded. Nothing on the server refuses a type value the form no longer offers. LIVE, on pnpm dev -- --fresh (showcase, OS_OIDC_PROVIDER_ENABLED=1, seeded admin) at 2dc0745 and again at fd686be: POST /api/v1/auth/sys-oauth-application/register with type 'user-agent-based' and with type 'public' (https redirect) both answered 200 success:true and stored type 'web'. At fd686be, which includes #24992, each also logged one server warning. The generic action door, POST /api/v1/actions/sys_oauth_application/create_oauth_application with params.type 'user-agent-based', answered 400 VALIDATION_ERROR 'is type: 'api' — it dispatches on target, not through the action registry'. The control with type 'web' got the same answer: the door refuses an api-typed action by its type (headlessActionTypeError, runtime domains/actions.ts) BEFORE enforceActionParams runs. The MCP run_action door refuses it the same way (isHeadlessInvokableAction, before its enforceActionParams). SOURCE: the register route reads body.type from the raw body and never reads object metadata. The hono middleware seam hands middleware body: undefined. No code matches an action's target. GET /api/v1/meta/object/sys_oauth_application now serves type options ['web','native'] and the new help texts; ?locale=zh-CN serves the zh-CN texts.",
    "stored_columns_reading": "For the 19.0 card. (1) type: @better-auth/oauth-provider 1.7.3 writes only 'web' or 'native' (checkOAuthClient refuses any other application_type, dist/authorize-9whjxVLJ.mjs:1770), or null for a client-metadata-document registration that names no application_type (:1956). Live: every row read through GET /api/v1/data/sys_oauth_application (8 at 2dc0745, 6 at fd686be) was 'web' or 'native'. The user-agent-based and public registrations were stored 'web' with token_endpoint_auth_method client_secret_basic. (2) public: never written; the client record field list (OAUTH_CLIENT_RECORD_FIELD_NAMES, :1034) has no such field, and no live row carried a value. (3) NEW READING: the stored type select is declared through Field.select's string-array shorthand, which strips hyphens. Its declared values are web, native, useragentbased, public (labels keep 'user-agent-based'). Measured from built dist and served by GET /api/v1/meta. So a stored 'user-agent-based' row is ALREADY outside the declared set today. Keeping the column four-valued in 18.x protects 'public' only. Neither column was changed. The new pin holds the column exactly as declared.",
    "tests": "New packages/platform-objects/src/identity/oauth-register-form-types.test.ts, 7 cases, reading the object and the four bundles from source. Cases: the type param offers exactly ['web','native'], required, default 'web'; every locale's type options are exactly web/native; the redirect help names https://, http://, localhost, 127.0.0.1, [::1]; the type help names Native and http://localhost; the en bundle mirrors the source; each locale names the hosts in its own translation, distinct from the English; the stored type select declares its four options (labels web/native/user-agent-based/public, values web/native/useragentbased/public); the public column is a declared boolean. The first run of the file was 'Tests 1 failed | 6 passed (7)': the stored-column pin expected 'user-agent-based' and received 'useragentbased'. That is the reading in stored_columns_reading(3); the pin was corrected to the declared values. At 2dc0745: 'pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2' gave 'Test Files 83 passed (83) / Tests 1239 passed (1239)' (os-verify-lock VERDICT command-exit 0). 'pnpm --filter @objectstack/platform-objects typecheck' gave exit 0, 'check:test-typecheck: OK ... 1 file(s) / 3 error(s) / 2 pinned signature(s) held', no debt added. The new file is in the tsconfig.test.json program (--listFilesOnly: 1 hit). platform-objects is byte-identical at fd686be: the second merge brought only #24992's 3 plugin-auth/changeset files. ABLATION through scripts/ablation-replace.mjs in a runner with its own EXIT/INT/TERM trap; each anchor x1 -> x0, blob changed on disk; each restore verified as blob == HEAD (object 240ded11b, zh-CN 5326f33a2) with git diff HEAD empty. Source-only test, so no dist leg. A: put back {label 'User-agent based', value 'user-agent-based'} on the action options gave 1 failed, expected ['web','native','user-agent-based'] to deeply equal ['web','native']. B: redirect help back to 'Must use https:// in production.' gave 2 failed. C: stored select narrowed to ['web','native'] gave 1 failed. D: zh-CN help back to its old translation gave 1 failed, 'zh-CN redirectURLs help ... to contain http://'. E: public option put back in zh-CN's action options gave 1 failed, 'zh-CN type options: expected [web, native, public]'. F: type help deleted gave 2 failed. G: public column renamed is_public gave 1 failed, 'expected undefined to be boolean'. Unmutated control: 7 passed. LIVE (dev server, 4 boots, each stopped by its recorded pid and port): see stop_condition. Provider rule via POST /api/v1/auth/oauth2/create-client at 2dc0745: native + http://[::1]:4001, http://localhost:59999 and http://127.0.0.1 (no port) gave 201 native; native + https://app.example.com gave 201; native + https://localhost gave 400 invalid_redirect_uri; native + http://127.0.0.2 gave 400; web + https://localhost and web + https://127.0.0.1 gave 400; web + https://app.example.com gave 201. Register door at fd686be: native + localhost / 127.0.0.1 / [::1] gave 200 stored native; web + localhost gave 400 OAUTH_REGISTER_FAILED invalid_redirect_uri. Logs: scratchpad issue-24773/2b/ (suite.log, typecheck.log, test-new*.log, ablate/, probe-final.log, probe-door.log, probe-zh-final.log).",
    "gates": "Local union: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' at the final head fd686be derived 66 commands for 7 paths. That is the PM's 60 at 4b8f597 plus 6: check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:where-matcher. Each ran as 'cmd > log 2>&1; rc=$?' after a workspace build. Result: 66/66 exit 0. Verdict lines include: check-adr-0087-registration 'this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'; check-empty-changeset 'No changeset from the merge base modified or deleted'; check:i18n 'OK (10 package(s) — all bundles in sync, no undeclared authoring keys)'; check:i18n-stale-fill '0 stale-fill leaf/leaves'; check:nul-bytes 'OK'; check:doc-authoring 'no growth'; check:type-check-debt 'surplus: none'. '--ran' (each line with ':: exit N') printed 'Run reconciliation — 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 66 derived famil(ies) accounted for'. The same 66 also ran at 2dc0745: 66/66 exit 0, reconciled. Not run locally: test:repo (CI Test Core). check:skill-frame-freshness: no frame file is touched. check:dev-prereqs: passed as the first step of each pnpm dev boot. check-platform-checklist --changed-from: no export renamed or removed. The CI-only type-check lanes and jobs outside the 66 are CI's. CI on PR #25011 at fd686be, read right after pr_create: 'No other open PR may claim the same issue' success; the 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', 'Flag docs affected by code changes' and Check Documentation Links were in_progress. Test Core and Build Core were not yet listed. in_progress is the honest value.",
    "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 39519854199: POST /repos/objectstack-ai/objectstack/pulls (created #25011, draft) and POST /repos//issues/25011/assignees (os-support-ai). Read-back: 12714 bytes sent, 12714 stored, identical; assignees os-support-ai; labels none (none named by the dispatch; skip-changeset does not apply, a patch changeset is included). (2) comment: POST /repos//issues/24773/comments, this report. git push x7 is not REST: the empty-branch probe, then 5377c04, 8e864bf, d64e6e5, 5212c67, merge 2dc0745 and merge fd686be. Reads were plain curl GETs through the container proxy: #24773 and its comments, PR #24992, PR #25011 and its check-runs.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: c · reach: named producers (two shipped platform objects) · Field.select's string-array shorthand silently strips hyphens from option VALUES: toSnakeCase in packages/spec/src/data/field.zod.ts selectWithOptions turns spaces into '_' but drops '-', and keeps the written string as the label. Measured from built dist: sys_oauth_application.type (packages/platform-objects/src/identity/sys-oauth-application.object.ts:449) declares 'useragentbased', and GET /api/v1/meta/object/sys_oauth_application serves that value. sys_metadata_audit.lock_state (packages/metadata-core/src/objects/sys-metadata-audit.object.ts:190) declares 'nooverlay' / 'nodelete', while recordMetadataAudit (packages/metadata-protocol/src/protocol.ts) writes 'no-overlay' / 'no-delete'. Seam: spec:Field.select (selectWithOptions) → runtime:record-validator invalid_option (packages/objectql/src/validation/record-validator.ts, the select/radio arm). The trap: an author who writes Field.select(['in-progress']) and then writes 'in-progress' gets invalid_option on a non-readonly field. LIMITS: no refusal was measured through a door. lock_state is readonly: true, so the validator judges only its shape. 1.7.3 never writes the hyphenated type. For the seat to judge and file. Dedupe words: Field.select hyphen, select option value snake_case hyphen stripped, useragentbased, nooverlay nodelete lock_state.",
    "carrier: #24773's 19.0 card (the seat files it) · noted, not filed. The stored-column readings in stored_columns_reading: 1.7.3 writes web / native (null for a metadata-document client) to type and never writes public. The declared third value is 'useragentbased', not 'user-agent-based'. The 19.0 narrowing of the column should start from what is declared, not from the literal in the source. In the PR's Acceptance notes."
    ],
    "line_budget": "208 changed lines (+185 / -23) across 7 files, per dispatch-gates at fd686be, against the 3000 human-merge threshold: under. sys-oauth-application.object.ts +31 / -7 (the action params and their comment only); new test +127; the four objects bundles +12 / -16 in total; changeset +15. No governed surface is touched; skills/** is untouched, so no ratchet ledger applies.",
    "deviations": [
    "The dispatch names the action 'register_oauth_application'. In the file it is 'create_oauth_application' (label 'Register OAuth Application', :139-:169 at 4b8f597), the action at the cited lines. That is the one changed.",
    "The stored-column pin: the dispatch says 'still lists all four values'. The declared values are web/native/useragentbased/public (Field.select strips hyphens), so the pin asserts the four labels and the four declared values as they are. It deliberately holds the stripped value; the test comment says so.",
    "Pins beyond the three named: the locale options are exactly web/native; each locale's help names the hosts; the en bundle mirrors the source; the type help; the public column is still a boolean. Every one is ablation-verified (legs D-G).",
    "'Say only what #24992's pins pin': the help also states [::1], any port, and that Native takes what Web takes. 2a's pins do not cover these. Each was measured live through the provider's own create-client route at 2dc0745, and [::1] through the register door itself at fd686be. Native private-use URI schemes, which the provider also allows, are left out of the help because they were not measured.",
    "The optional Application Type helpText was added.",
    "origin/main was merged twice: 4fe3af9 (spec addRecord retirement and more; full rebuild, suite, typecheck and gate union at 2dc0745), then fbb42d9 (#24992 landed mid-run). After the second merge platform-objects was byte-identical, so the suite and typecheck were not re-run at fd686be. The workspace build and the full gate union were re-run there.",
    "The live measurements booted pnpm dev -- --fresh four times on random ports with OS_OIDC_PROVIDER_ENABLED=1. Each boot was stopped by its recorded pnpm pid and its port's listener pid, with the port verified free. The tempdirs auto-delete.",
    "The commit trailers use 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.",
    "check-platform-checklist --changed-from was not run: no exported symbol was renamed or removed."
    ],
    "files_changed": [
    ".changeset/24773-oauth-form-types.md",
    "packages/platform-objects/src/identity/sys-oauth-application.object.ts",
    "packages/platform-objects/src/identity/oauth-register-form-types.test.ts",
    "packages/platform-objects/src/apps/translations/en.objects.generated.ts",
    "packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts",
    "packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts",
    "packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts"
    ],
    "pr_body_edits": "none"
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25011 comment 6305169073 (half 2b, Part of; the card stays open for half 2c, the 18.x 400 under the amended 「甲」 6303865025).

  11. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (half 2b) · domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-21T01:19Z

  12. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "done",
    "branch": "claude/issue-24773-oauth-type-refusal",
    "pr": "#25038",
    "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 2c is built: draft PR #25038 (Fixes #24773) at head 3f934a7. In plugin-auth's POST /api/v1/auth/sys-oauth-application/register, half 2a's warning branch is now the refusal. Any type present in the body other than web / native (user-agent-based, public, any other string including '', a non-string) answers the route's existing 400 { success: false, error: { code: 'INVALID_REQUEST', message } } before createOAuthClient is called, so nothing is stored and nothing is logged. The message names the value (first 64 chars, then an ellipsis) and both accepted types, and points a localhost / 127.0.0.1 / [::1] redirect at native. web / native are forwarded unchanged; absent and null stay the provider default, web. No new error code. The platform-objects action comment now states the 18.x refusal and the stored-column readings (comment only, same line count). One changeset: BREAKING minor for plugin-auth, plus patch for platform-objects, because esbuild ships the comment in dist JS (measured). Clause-② no (narrowing), disposition not-required (no-migration-prescription), in the 24813 changeset shape. CI at 3f934a7: 34 success, 7 skipped, 0 failed. The full dogfood battery on that head passed.",
    "absent_null_empty": "MEASURED at 7343229 through the real route: absent gave 200, stored web, no warning. null gave 200, stored web, no warning (half 2a read null as absent). '' gave 200, stored web, WITH the warning (a present unhonoured value). This PR refuses '' and keeps absent and null. Absent and null name no type: JSON null is the body's spelling of no value, and the provider default and the form's declared default are both web. Refusing null would narrow past the ruling, which refuses present values. '' is a present string that names no type, and it already took the warning branch, whose text said 'A future major version refuses this value'. So the refused set is exactly the set that warned. No caller sends '': objectui's ActionParamDialog refuses an empty required param (M3).",
    "measured_first": {
    "M1": "Real stack in-process: real better-auth 1.7.3 with the OIDC provider on, behind the plugin's real route registration on a Hono app, over a real ObjectQL + SqlDriver (better-sqlite3 :memory:) with the auth manifest's objects. Rows were read at driver level. A throwaway probe file ran each value with https://app.example.com/callback and with http://localhost:4001/callback. It was placed in plugin-auth/src only while running, never committed, removed, and git status was clean afterwards. BEFORE (7343229), https: web 200 stored web; native 200 stored native; user-agent-based, public, spa, 42, true, {a:1}, '' and a 200-char string each gave 200 stored web with exactly 1 warning (meta requestedType '(number)' / '(boolean)' / '(object)' for non-strings, cut to 64 chars for the long one); absent and null gave 200 stored web with 0 warnings. BEFORE, loopback: web 400 OAUTH_REGISTER_FAILED invalid_redirect_uri; native 200 stored native; every unhonoured value gave 400 OAUTH_REGISTER_FAILED invalid_redirect_uri with 1 warning; absent and null gave the same 400 with no warning. AFTER (fix, ee660f7 source), https: web / native / absent / null unchanged; every other value gave 400 INVALID_REQUEST, 0 rows, 0 warnings. AFTER, loopback: web / absent / null 400 OAUTH_REGISTER_FAILED (the provider's web rule, unchanged); native 200 stored native; every other value 400 INVALID_REQUEST, now refused before the provider's web rule. Every stored row, before and after: public null, token_endpoint_auth_method client_secret_basic, client secret set. Logs: scratchpad issue-24773/dev-2c/probe/m1-before.json, m1-after.json.",
    "M2": "Not narrowed: the select and the public column are unchanged. (1) WRITERS, installed @better-auth/oauth-provider 1.7.3 (dist/authorize-9whjxVLJ.mjs): every creation path goes through persistOAuthClientRegistration (:1916) and checkOAuthClient. That covers dynamic registration /oauth2/register (registrationSource dynamic; MCP clients' route), /oauth2/create-client and /admin/oauth2/create-client (managed; this door and the SDK) and the client-metadata document. checkOAuthClient refuses any application_type other than web / native (:1770). The default is web, or null for a client-metadata-document client that names none (:1956). Both update paths declare application_type z.enum(['web','native']) (:2885, :2923) and re-run checkOAuthClient over the merged record. better-auth 1.7.3 core ships no oidc-provider or mcp plugin. The generic data API cannot write the table (apiMethods ['get','list']). No in-tree code writes the column except through the provider (auth-schema-config.ts:489, applicationType: 'type'). So the installed set writes only web, native or null. (2) THIS DOOR BEFORE #24992: type: safeType, with safeType in web / native / user-agent-based or else web (fbb42d9^:auth-plugin.ts:3640). 1.7.x strips type, so every row was web. Under the earlier lines below, the door sent no token_endpoint_auth_method (a confidential client), so user-agent-based was refused and public became web. (3) EARLIER PROVIDER LINES (npm tarballs in half 2a's scratchpad, grep only, nothing executed): oauth-provider 1.6.33 and 1.7.0-rc.2 declared type web / native / user-agent-based. They required native or user-agent-based for a public client (token_endpoint_auth_method none) and web for a confidential one, and wrote public: isPublic (rc.2 index.mjs :3412, :3564). plugin-auth package.json history, 245 commits through the commits API: oauth-provider ^1.6.9 from 2026-05-08, ^1.6.11 to ^1.6.23 until 2026-07-16, 1.7.0-rc.1 from 2026-07-22, 1.7.0-rc.2 from 2026-08-19, ^1.7.1 from 2026-08-27, 1.7.3 since 2026-10-20. So a deployment that ran plugin-auth 2026-05-08 to 2026-08-27 can hold type = 'user-agent-based' (a public client registered by that name through DCR or create-client) and a public value. (4) type = 'public': NO writer found. No oauth-provider line declares it. better-auth 1.6.33 core's mcp plugin wrote type 'public' (plugins/mcp/index.mjs:630), but plugin-auth never imported it: auth-manager.ts history 2026-02-01 to 2026-05-09, 31 commits read through the contents API, 0 hits for plugins/mcp or mcp( (control: oidc-provider / oidcProvider(, 8 hits). The deprecated core oidc-provider wrote web on registration (index.mjs:901). (5) DECLARED: web, native, useragentbased, public (half 2b: the string-array shorthand strips hyphens). So the only non-web/native value any writer could produce, user-agent-based, is not a declared value, and the declared useragentbased and public have no writer in the tree, in 1.7.3 or in the earlier lines read. (6) READERS: no in-tree reader branches on the values (git grep: the select declaration, the form and this wrapper only). 1.7.3 has 0 'user-agent-based' tokens in its dist and reads no public field; a public client is token_endpoint_auth_method none (utils :837). The earlier lines did branch: PKCE was required for native, user-agent-based or public === true (1.6.33 utils-Cdzs0w5-.mjs:420; rc.2 :755). One residual branch on legacy data, from source reading only: 1.7.3's update paths re-run checkOAuthClient over stored plus update, so a legacy user-agent-based row would answer 'application_type must be web or native' to an update that does not set application_type. Not measured on a row.",
    "M3": "No sender of a type other than web / native was found, so this PR breaks no in-repo sender. THIS REPO at 7343229: every caller sends no type or web. in-process-session-renewal.pin.test.ts:398 (none), request-json-body-refusal.test.ts:221 (none), oauth-register-refusal-status.test.ts (web), this PR's test file. content/docs/protocol/objectui/actions.mdx:435 shows the action with no type param. docs/qa/platform-checklist/areas/identity-auth.json:1104 registers with no type. examples/ and packages/qa: 0 hits for the door path. The SDK never builds this URL (auth-route-ledger disposition server-only). CONSOLE FORM: since #25011 it offers web / native, required, default web. objectui ActionParamDialog.tsx pre-fills defaultValue (:503) and refuses to submit a required param that is undefined, null, empty or whitespace (:515, :537). This holds at the pinned .objectui-sha cf153eb2e and at objectui main a91f6bbb1. OBJECTUI a91f6bbb1: 0 files for the door path, user-agent-based or application_type (control sys_oauth_application: 5 files). HOTCRM d723f6f: 0 files for the door path, user-agent-based, application_type, oauth2/create-client or applications.register (control -i oauth: 18 files)."
    },
    "tests": "Edited packages/plugins/plugin-auth/src/oauth-register-application-type.test.ts (half 2a's file, the real stack above). createOAuthClient is wrapped in a pass-through spy (getApi() returns one api object per manager), cleared on each request. Half 2a's warning describe is replaced by refusal pins over 7 values (user-agent-based, public, spa, '', 42, true, an object) x https and loopback. Each asserts 400, envelopeViolations empty, error.code INVALID_REQUEST, the provider never called, no row written, no warning and no error logged. A message pin: a 64-char value plus 'TAIL-NEVER-ECHOED' is echoed up to 64 chars and never the tail; the message names 'web', 'native', http://localhost, http://127.0.0.1 and http://[::1]; a short value is named whole; 42 is named '(number)'. Added: null registers web with no warning. Every accepted registration now asserts exactly one provider call carrying the application_type it stored. Half 2a's other pins pass unchanged. RESULTS at ee660f7: this file plus oauth-register-refusal-status.test.ts gave 'Test Files 2 passed (2) / Tests 42 passed (42)' (os-verify-lock VERDICT command-exit 0). Full plugin-auth suite: 'Test Files 174 passed (174) / Tests 3344 passed | 10 skipped (3354)' (VERDICT 0). platform-objects suite: 'Test Files 83 passed (83) / Tests 1239 passed (1239)' (VERDICT 0), half 2b's oauth-register-form-types.test.ts included. platform-objects typecheck: exit 0, check:test-typecheck OK, 2 pinned signatures held. At 3f934a7, after merging main 4d99158 (which touched plugin-auth's auth-manager.ts and driver-sql), install and closure rebuild, then the full plugin-auth suite gave 'Test Files 175 passed (175) / Tests 3348 passed | 10 skipped (3358)'. plugin-auth typecheck: exit 0, 'check:test-typecheck: OK ... 10 file(s) / 88 error(s) / 23 pinned signature(s) held', so no debt was added. The test file is in the tsconfig.test.json program (--listFilesOnly: 1 hit) and absent from test-typecheck-debt.json. ABLATION through scripts/ablation-replace.mjs (WRAP mode) in a runner with its own EXIT/INT/TERM trap that re-proved each restore. Each anchor hit x1 and fell to x0 with the blob changed on disk. Each restore was proven as blob d065fc855 == HEAD with git diff HEAD empty, and git status --porcelain was empty at the end. The test imports ./auth-plugin from source, so there is no dist leg. Leg A (half 2a's warn-and-register-as-web put back): 15 failed | 10 passed, 'expected 200 to be 400' / on loopback 'expected OAUTH_REGISTER_FAILED to be INVALID_REQUEST'. Leg G (silently mapped to native): 15 failed. Leg B (refusal kept plus a warning): 14 failed, 'the door still warns instead of (or as well as) refusing: expected [ [ ...(2) ] ] to deeply equal []'. Leg C (provider called before the refusal): 14 failed, 'the door called the provider for a type it refuses'. Leg D (untruncated echo): 1 failed, 'expected type "aaaa... not to contain TAIL'. Leg E (null refused): 1 failed, null pin 'expected 400 to be 200'. Leg F (absent refused): 2 failed. Unmutated control: 25 passed. The first runs of legs B and C were refused by the tool ('the anchor count moved 1 -> 1': the replacement contained the anchor). They ran nothing and were redone with a non-overlapping anchor; the figures above are from the redone runs. Logs: scratchpad issue-24773/dev-2c/ablate/.",
    "gates": "Local union: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' at the final head 3f934a7 derived 74 commands. That is the PM's 57 at 7343229 plus 17: the changeset families adr-0087-registration, changeset-no-major and empty-changeset (each with --base and --self-test), release-rehearsal-clone and release-pending-publish self-tests, changeset-gate-self-tests, objectui-changeset, pm-changeset-deadline-census, engine-double-contract, objectql-double-limit, query-options-erasure, where-matcher, type-check-coverage and type-check-debt. Each ran as 'eval cmd > log 2>&1; status=$?' and was recorded with ':: exit N'. First pass: 72/74 exit 0. pnpm check:dual-build-cjs-loads and pnpm check:i18n exited 3 (PREREQUISITE NOT MET: no whole-workspace dist, no built CLI), so they measured nothing. After 'turbo run build --filter=!@objectstack/docs --concurrency=2' (76 tasks), both exited 0: '112 published require entry point(s) across 69 package(s)' and 'check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)'. Their two ran.list lines were replaced with the re-run exits; the first pass is kept as ran.first-pass.list. '--ran' printed 'Run reconciliation — 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 74 derived famil(ies) accounted for' (exit 0). Verdicts: check-adr-0087-registration --base origin/main '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription); check-empty-changeset 'No changeset from the merge base modified or deleted'; check:error-status-conformance 'every derivable runtime status is documented, and every documented status is reachable'; check:issue-citations, check:doc-authoring, check:nul-bytes ('no raw ASCII control bytes'), check:route-envelope, check:auth-mount-ledger, check:closing-keyword-parity and check:type-check-debt all exit 0. Not run locally: test:repo (CI Test Core). check:dev-prereqs and check:skill-frame-freshness do not apply (no packages/spec build input or frame file touched). check-platform-checklist --changed-from does not apply (no export renamed or removed). CI on PR #25038 at 3f934a7, read after the dogfood run: 41 check-runs, 34 success, 7 skipped, 0 failed or pending. The five required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core (and all 6 shards), Build Core, Governed Surface Queue Guard. Also success: Check Changeset, Check PR Size, every Lint · and Type Check · lane, '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 issue', 'No other open PR may claim the same single-writer path', Dogfood Regression Gate, Flag docs affected by code changes and Check Documentation Links. Skipped by their if: on a PR: Temporal Conformance, Dogfood Verify CLI, Console Pin Gate, Build Docs, the Dogfood Regression Gate shard matrix, Packed-tarball smoke (opt-in) and 18.x upgrade harness (opt-in).",
    "dogfood_battery": "3f934a772 · 'os-verify-lock: VERDICT command-exit 0 · held the lock 1392s (23m12s)' · Test Files 284 passed | 3 skipped (287) · Tests 2361 passed | 18 skipped (2379) · no failures. Command as CI: closure 'pnpm --workspace-concurrency=2 --filter @objectstack/dogfood^... build' (VERDICT 0), then 'pnpm --filter @objectstack/dogfood test' under the lock, with no OS_TEST_SHARD and no paths.",
    "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 39538105782: POST /repos/objectstack-ai/objectstack/pulls (created #25038, draft) and POST /repos//issues/25038/assignees (os-support-ai). Read-back: 19833 bytes sent, 19833 stored, identical (re-checked by diff against a GET: identical); assignees os-support-ai; labels none (none named by the dispatch; skip-changeset does not apply). (2) comment: POST /repos//issues/24773/comments, this report. git push x5 is not REST: the empty-branch probe, then 2a8bc5d, 9d0b183, ee660f7 and merge 3f934a7. Reads were plain gh api GETs: #24773 and its comments; #6015 comments 6303865025 and 6292005854; issue #24813 (an issue, not a PR: the changeset shape was read from .changeset/24813-adopt-orphans-readonly-package.md on main); the commits API and about 280 contents-API reads of plugin-auth's package.json and auth-manager.ts history; PR #25038 and its check-runs.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: exception: release-text · Half 2a's and 2b's PENDING changesets state behaviour this PR removes, and they ship in the same release as this PR's entry. .changeset/24773-oauth-application-type.md (plugin-auth): 'Unchanged, with a warning. Any other type ... keeps today's outcome: registered and judged as web ... The server now logs one warning'. .changeset/24773-oauth-form-types.md (platform-objects): 'Unchanged. The register route accepts the same values as before, including user-agent-based and public'. Failing probe: M1 AFTER (scratchpad dev-2c/probe/m1-after.json): 400 INVALID_REQUEST, 0 warnings. This PR does not edit them: they are outside the claim's file surface, and check:empty-changeset refuses a merge-base changeset modification. Mitigation in this PR: its changeset names BOTH packages, so each CHANGELOG carries the refusal entry beside the stale one in the same version. Route for the seat: a deliberate correction of those two bullets (check-empty-changeset's DELIBERATE CORRECTION class, confirmed on the PR), here or in a separate PR before the release. Dedupe words: stale pending changeset 24773, oauth application type warning changeset, register route accepts user-agent-based public.",
    "class: b · reach: public door GET /api/v1/meta/object/sys_oauth_application serves type options web, native, useragentbased, public (measured by half 2b at fd686be; not re-measured here), while no writer can produce useragentbased or public (M2) · Seam: spec:sys_oauth_application.type (Field.select ['web','native','user-agent-based','public']) → runtime:@better-auth/oauth-provider 1.7.3 persistOAuthClientRegistration / checkOAuthClient (writes web | native | null) | renderer:Console list and record views of sys_oauth_application. Evidence: M2 (1)-(5). The declared useragentbased and public have no writer in the tree, in 1.7.3, or in oauth-provider 1.6.33 / 1.7.0-rc.2. The only extra value any writer ever produced, 'user-agent-based' (public clients under 1.6.x / rc, 2026-05-08 to 2026-08-27, other doors), is NOT declared. The column also cannot express the null a client-metadata-document registration stores. Not narrowed here, per the dispatch. For the seat to route. Dedupe words: sys_oauth_application type select unwritten options, useragentbased public declared never written, oauth client type column narrowing.",
    "carrier: none (承接者:无) · noted, not filed, in the PR's Acceptance notes. Legacy rows from oauth-provider 1.6.x / 1.7.0-rc that hold type 'user-agent-based' would answer 'application_type must be web or native' to /oauth2/update-client and /admin/oauth2/update-client under 1.7.3 unless the update sets application_type, because both re-run checkOAuthClient over stored plus update (authorize-9whjxVLJ.mjs updateClientEndpoint). Source reading only: no row was measured, and no deployment's data is in reach."
    ],
    "line_budget": "190 changed lines (+138 / -52) across 4 files against the merge base 4d99158, under the 3000 human-merge threshold. auth-plugin.ts +25 / -16 (the register wrapper's application-type branch and its comment only). oauth-register-application-type.test.ts +85 / -28. sys-oauth-application.object.ts +8 / -8 (the action's code comment only, same line count). Changeset +20. No governed surface is touched; skills/** is untouched, so no ratchet ledger applies.",
    "deviations": [
    "The os-dev definition loaded for this run did not carry the rule that main's .claude/agents/os-dev.md gained in be1fd31: a PR with a (narrowing) arm runs the full dogfood battery once on its final head and reports dogfood_battery. I followed main's file, ran the battery and added the field.",
    "The changeset names @objectstack/platform-objects with patch. The dispatch said to include it only if its published output changes, and it does. platform-objects is not private. Rebuilding its dist after the comment edit changed dist/index.{js,mjs} and dist/identity/index.{js,mjs}: esbuild keeps the comment text. The .map and .d.ts files are byte-identical (sha256 of all 66 dist files compared before and after). AGENTS' fast lane treats comments as non-publishing; the measurement overrides it here.",
    "'' is refused while null is kept as absent. This is my reading, argued in absent_null_empty, of the dispatch's 'state which of them this PR refuses and why'.",
    "The refusal message appends an ellipsis after the 64th character when it cuts the value. Half 2a's warning cut silently.",
    "The dispatch says to read 'PR #24813's changeset'. #24813 is an issue (closed). The BREAKING-minor shape was read from .changeset/24813-adopt-orphans-readonly-package.md on main (commit 4cef6e2, PR #24861).",
    "PR #25038 was opened at 3f934a7, which merges main 4d99158. main then advanced to 3b3830c: 5 commits in service-settings, cli tests, .claude hooks, docs/qa and the skills README, none overlapping this diff. I did not merge again before pr_create; the gate union, suites and dogfood are all at 3f934a7.",
    "M1 was measured in-process on the real stack (allowed by the dispatch), not on a dev server; no dev server was started. The probe file sat in packages/plugins/plugin-auth/src only while it ran, was never staged, and was deleted (git status clean).",
    "Sibling repos were read through an owned ref: 'git fetch origin main:refs/os-dev/issue-24773-2c -f' in /home/user/objectui and /home/user/hotcrm. No shared ref and no working tree moved. The refs are deleted at cleanup.",
    "The earlier-provider readings reused half 2a's npm tarballs in scratchpad issue-24773/2a/npm-dl, read with grep and sed only. Nothing was downloaded, executed or installed this run.",
    "Two first-pass gate failures (exit 3, PREREQUISITE NOT MET) were re-run after a workspace build, and their ran.list lines were replaced with the re-run exits before --ran. The first-pass list is kept.",
    "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named form, which the pre-push hook refuses. The merge commit carries git's default message.",
    "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",
    "packages/platform-objects/src/identity/sys-oauth-application.object.ts",
    ".changeset/24773-oauth-type-refusal.md"
    ],
    "pr_body_edits": "none"
    }


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK (patch round) for half 2c, PR #25038 · domain:services seat 2 (seat post #21118) · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-21T03:52Z

    • The dev's report (head 3f934a772): the measurements and the refusal are accepted as built. CI was 34 success and 7 skipped.
    • Why a patch round: the dev's finding 1 (class a). This card's own pending changesets from halves 2a and 2b still say the register route registers user-agent-based / public as web with a warning, and accepts the same values as before. They ship in the same release as this PR's refusal.
      • So this PR corrects them in the same stroke. That is check-empty-changeset's DELIBERATE CORRECTION class: red by design, and confirmed by the same-head contract review at CONTRACT_REVIEW_TIER, which judges every rewritten sentence.
      • The claim 6302570331 is widened in place to the three pending 24773-* changesets, only the falsified sentences.
    • Next: the contract review on the patched head, then ACCEPT.
    • Routed at ACCEPT:
      • finding 2 (class b: the stored type select declares useragentbased / public, and no writer produces either);
      • finding 3 (legacy 1.6.x rows and 1.7.3's update path, source reading only).
  14. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "done",
    "round": "patch round r2 (correct the card's own pending release notes)",
    "branch": "claude/issue-24773-oauth-type-refusal",
    "pr": "#25038",
    "head": "2da95c6da",
    "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": "The claim 6302570331's newest widening names the three pending 24773 changesets (re-read at updated_at 2026-10-21T03:51:33Z). The worktree was re-created on the branch at 3f934a7, which matched the remote branch. origin/main dfe60e3 was merged in as c922c08 (a merge, not a rebase; no conflict; no os-regen debt). Then 2da95c6 corrected only the falsified sentences. In .changeset/24773-oauth-application-type.md, the 'Unchanged, with a warning' bullet became 'Refused' and states the 400 INVALID_REQUEST. In .changeset/24773-oauth-form-types.md, the Clause-② reason, the 'registers the other two as web' sentence and the 'accepts the same values as before' sentence were corrected. .changeset/24773-oauth-register-refusal-status.md has no sentence this PR falsifies, so it is unchanged (reason in pr_body_edits). No file was deleted, and no frontmatter or bump changed. check-empty-changeset is red by design (DELIBERATE CORRECTION), locally and in CI's Check Changeset. Every other changeset gate is green.",
    "tests": "At 2da95c6, after merging main dfe60e3 (which touched driver-sql and objectql, both in plugin-auth's test closure) and running pnpm install: 'pnpm --workspace-concurrency=2 --filter @objectstack/plugin-auth... build && pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 && pnpm --filter @objectstack/plugin-auth typecheck' under os-verify-lock gave 'Test Files 175 passed (175) / Tests 3348 passed | 10 skipped (3358)' and 'check:test-typecheck: OK — ... 10 file(s) / 88 error(s) / 23 pinned signature(s) held', VERDICT command-exit 0 (held 845s, shared box). No debt was added. No ablation this round: the delta is changeset prose, not tested behaviour. The dogfood battery was NOT re-run. The merge moved driver-sql, objectql, service-settings, service-messaging, cli and core, none of which is under this diff (the merge-base diff is still the same 4 source/changeset files plus the 2 corrected changesets), and this round's delta is prose only. Round 1's battery at 3f934a7 stands: 284 files passed, 3 skipped; 2361 tests passed, 18 skipped.",
    "gates": "Changeset family gates at 2da95c6, each run as 'cmd > log 2>&1; status=$?'. The FIRST attempt, before 'pnpm install' in the re-created worktree, gave check-adr-0087-registration --base origin/main exit 1 ('1 input problem(s) -- ... the witness generator's runner tsx is not installed') and exit 3 for the adr-0087 self-test and check:changeset-gate-self-tests. Those measured nothing and are superseded. After install: 'node scripts/check-empty-changeset.mjs --base origin/main' exit 1, the EXPECTED red. First lines: '✓ No empty-frontmatter changeset introduced by this diff (3 declaring changeset(s) added).' then 'This PR changes a changeset it did not add: .changeset/24773-oauth-application-type.md / .changeset/24773-oauth-form-types.md — present on the merge base and CHANGED by this PR'. That is the DELIBERATE CORRECTION class: no label, no skip, no rename. 'node scripts/check-adr-0087-registration.mjs --base origin/main' exit 0: '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' The two corrected changesets are not read as breaking. 'node scripts/check-changeset-no-major.mjs --base origin/main' exit 0: '✓ This diff introduces no major bump.' Self-tests: check-empty-changeset 170 assertions, exit 0; check-adr-0087-registration 548 assertions, exit 0; check-changeset-no-major 401 assertions, exit 0; 'pnpm check:changeset-gate-self-tests' exit 0. dispatch-gates: '--commands --repo objectstack-ai/objectstack' with no paths at 2da95c6 derived 74 commands, identical to round 1's 74 (diff empty), so the derivation adds nothing. For the prose-only delta, '--commands' over the two edited paths derived the 21 families they trigger (the changeset families, closing-keyword-parity and its self-test, comment-mask-corpus, release-rehearsal-clone and release-pending-publish self-tests, objectui-changeset, pm-changeset-deadline-census, and the 8 whole-tree families: closed-ceremony-text, driver-memory-census, gitlink-declared, nul-bytes, published-files, refd-timer-probe, watch-hint-literal). Those 21 re-ran at 2da95c6: 20 exit 0, and check-empty-changeset exit 1 as above. The other 53 are carried from round 1 at 3f934a7. '--ran' over the combined record printed 'Run reconciliation — 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). CI at 2da95c6, read after the suite: 40 check-runs. 28 success and 7 skipped. Check Changeset is failure, and its annotations are exactly the two foreign-changeset refusals ('.changeset/24773-oauth-form-types.md exists on the merge base and was not added by this PR ...' and the same for 24773-oauth-application-type.md). Four Test Core shards (2/6, 3/6, 4/6, 6/6) were in_progress. Of the required contexts, Lint & Repo Gates, TypeScript Type Check, Build Core and Governed Surface Queue Guard are success; Test Core is pending on those shards.",
    "dogfood_battery": "not re-run this round. Prose-only delta, and the merge of main dfe60e3 moved no code under this diff. Round 1 at 3f934a7: VERDICT command-exit 0 · Test Files 284 passed | 3 skipped (287) · Tests 2361 passed | 18 skipped (2379).",
    "mcp_calls": "0",
    "api_writes": "1 relay stroke through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches, which executed POST /repos//issues/24773/comments (this round's os-dev-report). git push x1 (2da95c6, carrying merge c922c08) is not REST. Reads were plain gh api GETs: claim comment 6302570331, the check-runs for 2da95c6 and the Check Changeset run's annotations.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the seat (same-head contract review) · noted, not filed. This PR's OWN changeset (.changeset/24773-oauth-type-refusal.md, not in this round's list) says in FROM that the server 'logged one warning naming the value', and in TO that 'the refusal replaces that warning'. That warning existed only on main builds between fbb42d9 and this PR. If 2a and 2c ship in one release, a reader upgrading from the published 18.0.0 never saw it: there the type was stripped and registered as web silently. The sentence is true against the merge base and not contradicted by the corrected entries, so I left it. If the seat wants the release note to describe only published states, the one-line change is to drop ', and the server logged one warning naming the value' from FROM and ': the refusal replaces that warning' from TO."
    ],
    "line_budget": "Against the merge base dfe60e3: 6 files. Round 1's 4 files (+138 / -52) plus the two corrected changesets (+4 / -4). 198 changed lines, under the 3000 human-merge threshold. No governed surface touched.",
    "deviations": [
    "The first changeset-gate attempt ran before 'pnpm install' in the re-created worktree, so three commands refused for a missing tsx runner (exit 1 with '1 input problem(s)', and exit 3 twice). They measured nothing. All were re-run after install, and those readings are the ones reported.",
    "dispatch-gates reconciliation combines 21 families re-run at 2da95c6 (every family the two edited changeset paths trigger, the 8 whole-tree ones included) with 53 carried from round 1 at 3f934a7, per this round's 'prose-only delta' instruction. The no-path derivation at 2da95c6 is byte-identical to round 1's.",
    ".changeset/24773-oauth-register-refusal-status.md is not edited: no sentence in it is falsified (reason in pr_body_edits).",
    "In .changeset/24773-oauth-form-types.md the Clause-② line's parenthetical reason was rewritten, and its value 'no' was kept. I checked the arm reader (scripts/pm/clause2-line.mjs readArmToken): a parenthetical that does not start with widen/narrow is ordinary reasoning, so the reading is unchanged. check-adr-0087-registration still counts 1 declared-breaking changeset.",
    "The commit trailers use AGENTS.md's model-free pair, and the merge commit carries git's default message."
    ],
    "files_changed": [
    ".changeset/24773-oauth-application-type.md",
    ".changeset/24773-oauth-form-types.md"
    ],
    "pr_body_edits": "## Pending release notes corrected\n\nThis PR's refusal made sentences in two of the card's own pending changesets false. They are corrected in 2da95c6da. Every still-true sentence is kept, and each file keeps its frontmatter (package and bump). check-empty-changeset refuses the edit as a foreign-changeset modification. That is its DELIBERATE CORRECTION class, red by design (CI Check Changeset: "exists on the merge base and was not added by this PR"), for the seat's same-head contract review to confirm. The evidence for every correction is M1 AFTER above: user-agent-based, public, spa, '' and non-strings answer 400 INVALID_REQUEST, with no row stored and no warning logged.\n\n**.changeset/24773-oauth-application-type.md** (@objectstack/plugin-auth: patch, unchanged)\n\n- The bullet label changes from "Unchanged, with a warning." to "Refused.".\n- BEFORE: "It keeps today's outcome: registered and judged as web, with a client secret. The server now logs one warning per such registration, naming the value."\n- AFTER: "The register route refuses it with 400 INVALID_REQUEST and registers nothing (this release's breaking entry for the route)."\n- Why the original is false: M1 AFTER. These types are refused with 400; no web client is registered and no warning is logged.\n- Kept verbatim: "Any other type, including the form's user-agent-based and public, has no application_type in the provider." and "To use a loopback redirect URI, register the client as native."\n\n**.changeset/24773-oauth-form-types.md** (@objectstack/platform-objects: patch, unchanged)\n\n- The Clause-② reason.\n - BEFORE: "Clause-②: no (a form's offering: the register route still accepts every type value it accepted, and nothing on the server reads the action's param options, so no accept set or served shape moves)"\n - AFTER: "Clause-②: no (a form's offering: nothing on the server reads the action's param options, so this change moves no accept set or served shape; the register route's refusal of the other types is its own breaking entry in this release)"\n - Why the original is false: M1 AFTER; the route no longer accepts every value it accepted. The value stays no, because the form change itself moves no accept set. The parenthetical does not start with an arm word, so the line's reading is unchanged.\n- BEFORE: "The OAuth provider judges only web and native, so the register route registers the other two as web, with a client secret."\n - AFTER: "The OAuth provider judges only web and native, and the register route refuses any other type with 400 INVALID_REQUEST."\n - Why the original is false: M1 AFTER.\n- BEFORE: "The register route accepts the same values as before, including user-agent-based and public." (the first sentence of the Unchanged. bullet)\n - AFTER: removed. The bullet keeps "The stored sys_oauth_application.type column still lists all four values, so a row written under an earlier provider line keeps its value. The public column is unchanged."\n - Why the original is false: M1 AFTER. The refusal is stated in this PR's own entry and in the corrected sentence above.\n\n**.changeset/24773-oauth-register-refusal-status.md** (@objectstack/plugin-auth: patch) is not edited, because none of its sentences is false. Each one speaks of the refusals the provider raises (400 for invalid_redirect_uri, 401 for a missing session, 500 for a throw that declares no status, the body-schema 400), and those are unchanged. The new type refusal comes before the provider is called. So do the route's own name / redirectURLs refusals, which this note never covered either. Its closing sentence, "a web client still needs an https URI on a non-loopback host", stays true.\n\nAlso in this body, superseded by the above:\n\n- In What changes, the clause "beside half 2b's entry, which still says the route accepts the old values (Acceptance notes)" is no longer true. Replace it with "beside half 2b's entry".\n- In Acceptance notes, the first bullet ("Half 2a's and 2b's pending changesets go stale with this PR. ... This PR does not edit them ...") is resolved by this section. Delete it.\n- In Gates, check-empty-changeset is now red by design at 2da95c6da (see above). The re-verification at 2da95c6da, after merging main dfe60e3a0:\n - the full plugin-auth suite: Test Files 175 passed (175), Tests 3348 passed | 10 skipped (3358);\n - plugin-auth typecheck: check:test-typecheck: OK, 23 pinned signatures held;\n - --ran: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN. The 21 families that the edited changesets trigger were re-run at 2da95c6da: 20 exit 0, and check-empty-changeset exits 1 as above. The other 53 are carried from 3f934a772.\n- The dogfood battery is not re-run. The merge moved driver-sql, objectql, service-settings, service-messaging, cli and core, none of it under this diff, and this round's delta is prose only."
    }


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "done",
    "round": "r3 (prose only: this PR's own changeset describes the change from the last published state)",
    "branch": "claude/issue-24773-oauth-type-refusal",
    "pr": "#25038",
    "head": "8eba09f8c",
    "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": "8eba09f8c (on 2da95c6, no merge of main needed: the push was accepted) makes exactly the proposed change in .changeset/24773-oauth-type-refusal.md. FROM drops ', and the server logged one warning naming the value', so it ends '... for a loopback redirect URI.'. TO drops ': the refusal replaces that warning', so it ends '... and no warning is logged.'. I re-read the file: the only remaining mention of a warning is TO's 'no warning is logged', which states the new behaviour, not a released one, so nothing else was edited. No other file changed. The changeset gates are as before: check-empty-changeset is the expected red on the two corrected foreign changesets only; adr-0087, no-major and the rest are green.",
    "tests": "No test or source change this round (changeset prose only), so no suite, typecheck, ablation or dogfood re-run. The readings at 2da95c6 stand: plugin-auth 'Test Files 175 passed (175) / Tests 3348 passed | 10 skipped (3358)' and check:test-typecheck OK. Dogfood at 3f934a7: Test Files 284 passed | 3 skipped, Tests 2361 passed | 18 skipped.",
    "gates": "At 8eba09f: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack .changeset/24773-oauth-type-refusal.md' derived 21 commands, identical to r2's 21. The no-path derivation gave 74, identical to rounds 1 and 2. All 21 ran as 'eval cmd > log 2>&1; status=$?': 20 exit 0. 'node scripts/check-empty-changeset.mjs --base origin/main' exit 1, the EXPECTED red. First lines: 'Diffing HEAD from dfe60e3 (merge base with origin/main).', '✓ No empty-frontmatter changeset introduced by this diff (3 declaring changeset(s) added).', 'This PR changes a changeset it did not add:', naming only .changeset/24773-oauth-application-type.md and .changeset/24773-oauth-form-types.md (the DELIBERATE CORRECTION class; this PR's own changeset is not named, because it is absent at the merge base). Verdicts: 'node scripts/check-adr-0087-registration.mjs --base origin/main' exit 0, '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.'; 'node scripts/check-changeset-no-major.mjs --base origin/main' exit 0, '✓ This diff introduces no major bump.'; check-closing-keyword-parity exit 0, 'OK (3 parsers agree on all 9 keywords ...)'; pnpm check:nul-bytes exit 0, 'no raw ASCII control bytes'. The empty-changeset, adr-0087 and no-major self-tests and 'pnpm check:changeset-gate-self-tests' all exit 0. '--ran' over the 21 fresh plus 53 carried from 3f934a7 printed 'Run reconciliation — 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). Not read this round: CI on 8eba09f. Expect Check Changeset red on the same two foreign-changeset annotations as at 2da95c6.",
    "dogfood_battery": "not re-run: changeset prose only, and no merge of main this round. Round 1 at 3f934a7: VERDICT command-exit 0 · Test Files 284 passed | 3 skipped (287) · Tests 2361 passed | 18 skipped (2379).",
    "mcp_calls": "0",
    "api_writes": "1 relay stroke through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches, which executed POST /repos//issues/24773/comments (this round's os-dev-report). git push x1 (8eba09f) is not REST. No reads beyond git.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the seat · noted, not filed. The same last-published-state reading reaches one more phrase in this PR's own changeset, outside this round's 'warning only' scope, so it was left as written: FROM says the request 'answered the web rule's 400 OAUTH_REGISTER_FAILED for a loopback redirect URI'. In the published 18.0.0 that refusal answered 500. Half 1's status fix is still the pending .changeset/24773-oauth-register-refusal-status.md, and the card's own HotCRM measurement on 18.0.0-next.5 was 500. The one-line change, if the seat wants it: replace "answered the web rule's 400 OAUTH_REGISTER_FAILED for a loopback redirect URI" with "was refused by the web rule (OAUTH_REGISTER_FAILED) for a loopback redirect URI"."
    ],
    "line_budget": "Against the merge base dfe60e3: 6 files, 198 changed lines in r2. r3 changes 2 lines of the PR's own changeset in place (+2 / -2, same files), under the 3000 human-merge threshold. No governed surface touched.",
    "deviations": [
    "The worktree was re-created on the branch at 2da95c6 (local = remote), followed by 'pnpm install --frozen-lockfile' (exit 0) before any gate ran.",
    "The --ran record carries 53 families from 3f934a7 (via r2's combined record) and has 21 fresh at 8eba09f, per this round's instruction.",
    "After the comment read-back, the worktree is removed again (node_modules first, then git worktree remove)."
    ],
    "files_changed": [
    ".changeset/24773-oauth-type-refusal.md"
    ],
    "pr_body_edits": "## Pending release notes corrected\n\nThis PR's refusal made sentences in two of the card's own pending changesets false. They are corrected in 2da95c6da (head now 8eba09f8c). Every still-true sentence is kept, and each file keeps its frontmatter (package and bump). check-empty-changeset refuses the edit as a foreign-changeset modification. That is its DELIBERATE CORRECTION class, red by design at 8eba09f8c as at 2da95c6da (CI Check Changeset: "exists on the merge base and was not added by this PR"), for the seat's same-head contract review to confirm. The evidence for every correction is M1 AFTER above: user-agent-based, public, spa, '' and non-strings answer 400 INVALID_REQUEST, with no row stored and no warning logged.\n\n**.changeset/24773-oauth-application-type.md** (@objectstack/plugin-auth: patch, unchanged)\n\n- The bullet label changes from "Unchanged, with a warning." to "Refused.".\n- BEFORE: "It keeps today's outcome: registered and judged as web, with a client secret. The server now logs one warning per such registration, naming the value."\n- AFTER: "The register route refuses it with 400 INVALID_REQUEST and registers nothing (this release's breaking entry for the route)."\n- Why the original is false: M1 AFTER. These types are refused with 400; no web client is registered and no warning is logged.\n- Kept verbatim: "Any other type, including the form's user-agent-based and public, has no application_type in the provider." and "To use a loopback redirect URI, register the client as native."\n\n**.changeset/24773-oauth-form-types.md** (@objectstack/platform-objects: patch, unchanged)\n\n- The Clause-② reason.\n - BEFORE: "Clause-②: no (a form's offering: the register route still accepts every type value it accepted, and nothing on the server reads the action's param options, so no accept set or served shape moves)"\n - AFTER: "Clause-②: no (a form's offering: nothing on the server reads the action's param options, so this change moves no accept set or served shape; the register route's refusal of the other types is its own breaking entry in this release)"\n - Why the original is false: M1 AFTER; the route no longer accepts every value it accepted. The value stays no, because the form change itself moves no accept set. The parenthetical does not start with an arm word, so the line's reading is unchanged.\n- BEFORE: "The OAuth provider judges only web and native, so the register route registers the other two as web, with a client secret."\n - AFTER: "The OAuth provider judges only web and native, and the register route refuses any other type with 400 INVALID_REQUEST."\n - Why the original is false: M1 AFTER.\n- BEFORE: "The register route accepts the same values as before, including user-agent-based and public." (the first sentence of the Unchanged. bullet)\n - AFTER: removed. The bullet keeps "The stored sys_oauth_application.type column still lists all four values, so a row written under an earlier provider line keeps its value. The public column is unchanged."\n - Why the original is false: M1 AFTER. The refusal is stated in this PR's own entry and in the corrected sentence above.\n\n**.changeset/24773-oauth-register-refusal-status.md** (@objectstack/plugin-auth: patch) is not edited, because none of its sentences is false. Each one speaks of the refusals the provider raises (400 for invalid_redirect_uri, 401 for a missing session, 500 for a throw that declares no status, the body-schema 400), and those are unchanged. The new type refusal comes before the provider is called. So do the route's own name / redirectURLs refusals, which this note never covered either. Its closing sentence, "a web client still needs an https URI on a non-loopback host", stays true.\n\nAlso in this body, superseded by the above:\n\n- In What changes, the clause "beside half 2b's entry, which still says the route accepts the old values (Acceptance notes)" is no longer true. Replace it with "beside half 2b's entry".\n- In Acceptance notes, the first bullet ("Half 2a's and 2b's pending changesets go stale with this PR. ... This PR does not edit them ...") is resolved by this section. Delete it.\n- In Gates, check-empty-changeset is now red by design at the final head 8eba09f8c (see above). The re-verification at 2da95c6da, after merging main dfe60e3a0:\n - the full plugin-auth suite: Test Files 175 passed (175), Tests 3348 passed | 10 skipped (3358);\n - plugin-auth typecheck: check:test-typecheck: OK, 23 pinned signatures held;\n - --ran: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN. The 21 families that the edited changesets trigger were re-run at 2da95c6da: 20 exit 0, and check-empty-changeset exits 1 as above. The other 53 are carried from 3f934a772.\n- r3, 8eba09f8c (prose only, this PR's own changeset). .changeset/24773-oauth-type-refusal.md now describes the change from the last published state, 18.0.0, where the door registered such a type as web silently. The FROM bullet drops ", and the server logged one warning naming the value", and the TO bullet drops ": the refusal replaces that warning": half 2a's warning never reached a published release. dispatch-gates --commands over that one path derived the same 21 families. All 21 re-ran at 8eba09f8c: 20 exit 0 (check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition"), and check-empty-changeset --base origin/main exits 1, the expected red naming only the two corrected foreign changesets. --ran: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN, with the other 53 carried from 3f934a772.\n- The dogfood battery is not re-run. The merge moved driver-sql, objectql, service-settings, service-messaging, cli and core, none of it under this diff, and this round's delta is prose only."
    }


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24773,
    "status": "done",
    "round": "r4 (prose only: this PR's own changeset stated against the last published release, 18.0.0)",
    "branch": "claude/issue-24773-oauth-type-refusal",
    "pr": "#25038",
    "head": "48857fcc6",
    "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": "48857fcc6 (on 8eba09f; the push was accepted, so no merge of main) edits only .changeset/24773-oauth-type-refusal.md. (1) FROM: "answered the web rule's 400 OAUTH_REGISTER_FAILED for a loopback redirect URI" became "was refused by the web rule (OAUTH_REGISTER_FAILED) for a loopback redirect URI", exactly as proposed. (2) I re-read the file against 18.0.0, where the door sent type, which the provider stripped (every client web); provider refusals answered 500; and the form offered four types. Two more phrases stated this release's intermediate behaviour as the FROM state. The 'Unchanged:' bullet said web/native are forwarded as application_type, and that the other refusals keep their codes AND statuses. It is split into 'Accepted:' (web/native forwarded, citing this release's Application Type entry) and 'Unchanged:' (no type or null registers web; the other refusals keep their codes, with their statuses cited to this release's refusal-status entry; existing clients untouched). And 'The form already offers only Web and Native' became 'The form offers only Web and Native (this release's register-form entry)'. Checked and left: the title, the Clause-② line, the paragraph ('used to register such a request as a confidential web client' is true of 18.0.0), TO, the integrator fix, and the ADR-0087 marker. The marker's 'already offers only Web and Native' is the gate-read disposition reason, true at release, and not a FROM statement. No other file changed.",
    "tests": "No test or source change this round (changeset prose only), so no suite, typecheck, ablation or dogfood re-run. The readings at 2da95c6 stand: plugin-auth 'Test Files 175 passed (175) / Tests 3348 passed | 10 skipped (3358)' and check:test-typecheck OK. Dogfood at 3f934a7: Test Files 284 passed | 3 skipped, Tests 2361 passed | 18 skipped.",
    "gates": "At 48857fc: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack .changeset/24773-oauth-type-refusal.md' derived 21 commands, identical to r3's. The no-path derivation gave 74, identical to every earlier round. All 21 ran as 'eval cmd > log 2>&1; status=$?': 20 exit 0. 'node scripts/check-empty-changeset.mjs --base origin/main' exit 1, the EXPECTED red: 'Diffing HEAD from dfe60e3 (merge base with origin/main).', '✓ No empty-frontmatter changeset introduced by this diff (3 declaring changeset(s) added).', 'This PR changes a changeset it did not add:', naming only .changeset/24773-oauth-application-type.md and .changeset/24773-oauth-form-types.md (DELIBERATE CORRECTION). 'node scripts/check-adr-0087-registration.mjs --base origin/main' exit 0: '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' The new 'Accepted:' bullet raised no migration-prescription refusal. 'node scripts/check-changeset-no-major.mjs --base origin/main' exit 0: '✓ This diff introduces no major bump.' The other 18 exit 0, the changeset self-tests and 'pnpm check:changeset-gate-self-tests' among them. '--ran' over the 21 fresh plus 53 carried from 3f934a7 printed 'Run reconciliation — 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). CI on 48857fc was not read; expect Check Changeset red on the same two foreign-changeset annotations.",
    "dogfood_battery": "not re-run: changeset prose only, and no merge of main this round. Round 1 at 3f934a7: VERDICT command-exit 0 · Test Files 284 passed | 3 skipped (287) · Tests 2361 passed | 18 skipped (2379).",
    "mcp_calls": "0",
    "api_writes": "1 relay stroke through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches, which executed POST /repos//issues/24773/comments (this round's os-dev-report). git push x1 (48857fc) is not REST.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "line_budget": "Against the merge base dfe60e3: 6 files, about 200 changed lines (r2's 198 plus r3 and r4's in-place changeset prose: r4 is +4 / -3 in .changeset/24773-oauth-type-refusal.md), under the 3000 human-merge threshold. No governed surface touched.",
    "deviations": [
    "Beyond the proposed one-line FROM change, two more phrases in the same file were changed, under this round's 're-read against 18.0.0' clause. The 'Unchanged:' bullet was split into 'Accepted:' and 'Unchanged:', and 'already offers' became 'offers ... (this release's register-form entry)'. Each one stated this release's intermediate behaviour as the FROM state.",
    "The ADR-0087 disposition marker's 'already offers only Web and Native' was deliberately left as written: it is the gate-read reason, true at release, not a FROM statement.",
    "The worktree was re-created on the branch at 8eba09f (local = remote), with 'pnpm install --frozen-lockfile' (exit 0) before any gate. The --ran record carries 53 families from 3f934a7 and has 21 fresh at 48857fc.",
    "After the comment read-back, the worktree is removed again."
    ],
    "files_changed": [
    ".changeset/24773-oauth-type-refusal.md"
    ],
    "pr_body_edits": "## Pending release notes corrected\n\nThis PR's refusal made sentences in two of the card's own pending changesets false. They are corrected in 2da95c6da (final head 48857fcc6). Every still-true sentence is kept, and each file keeps its frontmatter (package and bump). check-empty-changeset refuses the edit as a foreign-changeset modification. That is its DELIBERATE CORRECTION class, red by design at the final head 48857fcc6, as since 2da95c6da (CI Check Changeset: "exists on the merge base and was not added by this PR"), for the seat's same-head contract review to confirm. The evidence for every correction is M1 AFTER above: user-agent-based, public, spa, '' and non-strings answer 400 INVALID_REQUEST, with no row stored and no warning logged.\n\n**.changeset/24773-oauth-application-type.md** (@objectstack/plugin-auth: patch, unchanged)\n\n- The bullet label changes from "Unchanged, with a warning." to "Refused.".\n- BEFORE: "It keeps today's outcome: registered and judged as web, with a client secret. The server now logs one warning per such registration, naming the value."\n- AFTER: "The register route refuses it with 400 INVALID_REQUEST and registers nothing (this release's breaking entry for the route)."\n- Why the original is false: M1 AFTER. These types are refused with 400; no web client is registered and no warning is logged.\n- Kept verbatim: "Any other type, including the form's user-agent-based and public, has no application_type in the provider." and "To use a loopback redirect URI, register the client as native."\n\n**.changeset/24773-oauth-form-types.md** (@objectstack/platform-objects: patch, unchanged)\n\n- The Clause-② reason.\n - BEFORE: "Clause-②: no (a form's offering: the register route still accepts every type value it accepted, and nothing on the server reads the action's param options, so no accept set or served shape moves)"\n - AFTER: "Clause-②: no (a form's offering: nothing on the server reads the action's param options, so this change moves no accept set or served shape; the register route's refusal of the other types is its own breaking entry in this release)"\n - Why the original is false: M1 AFTER; the route no longer accepts every value it accepted. The value stays no, because the form change itself moves no accept set. The parenthetical does not start with an arm word, so the line's reading is unchanged.\n- BEFORE: "The OAuth provider judges only web and native, so the register route registers the other two as web, with a client secret."\n - AFTER: "The OAuth provider judges only web and native, and the register route refuses any other type with 400 INVALID_REQUEST."\n - Why the original is false: M1 AFTER.\n- BEFORE: "The register route accepts the same values as before, including user-agent-based and public." (the first sentence of the Unchanged. bullet)\n - AFTER: removed. The bullet keeps "The stored sys_oauth_application.type column still lists all four values, so a row written under an earlier provider line keeps its value. The public column is unchanged."\n - Why the original is false: M1 AFTER. The refusal is stated in this PR's own entry and in the corrected sentence above.\n\n**.changeset/24773-oauth-register-refusal-status.md** (@objectstack/plugin-auth: patch) is not edited, because none of its sentences is false. Each one speaks of the refusals the provider raises (400 for invalid_redirect_uri, 401 for a missing session, 500 for a throw that declares no status, the body-schema 400), and those are unchanged. The new type refusal comes before the provider is called. So do the route's own name / redirectURLs refusals, which this note never covered either. Its closing sentence, "a web client still needs an https URI on a non-loopback host", stays true.\n\nAlso in this body, superseded by the above:\n\n- In What changes, the clause "beside half 2b's entry, which still says the route accepts the old values (Acceptance notes)" is no longer true. Replace it with "beside half 2b's entry".\n- In Acceptance notes, the first bullet ("Half 2a's and 2b's pending changesets go stale with this PR. ... This PR does not edit them ...") is resolved by this section. Delete it.\n- In Gates, check-empty-changeset is now red by design at the final head 48857fcc6 (see above). The re-verification at 2da95c6da, after merging main dfe60e3a0:\n - the full plugin-auth suite: Test Files 175 passed (175), Tests 3348 passed | 10 skipped (3358);\n - plugin-auth typecheck: check:test-typecheck: OK, 23 pinned signatures held;\n - --ran: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN. The 21 families that the edited changesets trigger were re-run at 2da95c6da: 20 exit 0, and check-empty-changeset exits 1 as above. The other 53 are carried from 3f934a772.\n- r3, 8eba09f8c (prose only, this PR's own changeset). .changeset/24773-oauth-type-refusal.md now describes the change from the last published state, 18.0.0, where the door registered such a type as web silently. The FROM bullet drops ", and the server logged one warning naming the value", and the TO bullet drops ": the refusal replaces that warning": half 2a's warning never reached a published release. dispatch-gates --commands over that one path derived the same 21 families. All 21 re-ran at 8eba09f8c: 20 exit 0 (check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition"), and check-empty-changeset --base origin/main exits 1, the expected red naming only the two corrected foreign changesets. --ran: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN, with the other 53 carried from 3f934a772.\n- r4, 48857fcc6 (prose only, this PR's own changeset). .changeset/24773-oauth-type-refusal.md was re-read against the last published state, 18.0.0. In 18.0.0 the door sent the type as type, which the provider stripped, so every client was web. Provider refusals answered 500, and the form offered four types. Three phrases stated this release's intermediate behaviour as the FROM state, and were changed:\n - FROM. "answered the web rule's 400 OAUTH_REGISTER_FAILED for a loopback redirect URI" became "was refused by the web rule (OAUTH_REGISTER_FAILED) for a loopback redirect URI". In 18.0.0 that refusal answered 500.\n - Unchanged. "Unchanged: web and native are forwarded as the provider's application_type, and the provider's own redirect-URI rule decides. ... The other refusals keep their codes and statuses." was split into two bullets. The first is "Accepted: web and native are forwarded as the provider's application_type (this release's Application Type entry), and the provider's own redirect-URI rule decides." The second is "Unchanged: ... The other refusals keep their codes (their statuses are this release's refusal-status entry)." The reason: in 18.0.0, native was judged and stored as web, and the statuses change in this release's refusal-status entry.\n - The form. "The form already offers only Web and Native." became "The form offers only Web and Native (this release's register-form entry)." In 18.0.0 the form offered four types.\n - Gates: dispatch-gates --commands over that path derived the same 21 families, re-run at 48857fcc6. 20 exit 0 (check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition"); check-empty-changeset --base origin/main exits 1, the expected red naming only the two corrected foreign changesets. --ran: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN, with the other 53 carried from 3f934a772.\n- The dogfood battery is not re-run. The merge moved driver-sql, objectql, service-settings, service-messaging, cli and core, none of it under this diff, and this round's delta is prose only."
    }


    Generated by Claude Code

  17. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25038 comment 6308500934 (half 2c, Fixes #24773; contract review PASS 6308302124 on the same head; Check Changeset red by design, the correction of the card's own pending notes confirmed). Out-of-scope finding filed as #25053.

  18. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (half 2c, the last half); card closed by Fixes #24773 · domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-21T05:38Z

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:servicespriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions