Skip to content

Commit 7baf04a

Browse files
claude[bot]claude
andauthored
docs(client): say where a plain name IS honoured when registering an OAuth client (#17210) (#17755)
Fixes #17210 Implements the director seat's ruling on this card (`#issuecomment-5634036848`), option **B**: **the SDK gains no method; the ledger row is untouched.** The one measured trap is made visible in prose where its reader meets it. No SDK method is added, no request or response type moves, no accept set moves, and `packages/plugins/plugin-auth/src/auth-route-ledger.ts` is not in this diff. ## Premises — falsified first, on `origin/main` at `396eae33` All four rows of the dispatch's premise table hold, plus the falsification trigger it named. | # | premise | reading | |:--|:--|:--| | 1 | `oauth.applications.register` still exists | `applications: {` at `:3997`; `register: async (req: {` at `:4043`; pin named at `:4041` | | 2 | the request type is the vendor shape | `client_name?: string` at `:4044` and `redirect_uris: string[]` at `:4045` in the request type; the same two members at `:947` / `:960` in `OAuthApplication` | | 3 | the ledger row is unchanged and still `server-only` | `auth-route-ledger.ts:241`, `disposition: 'server-only'`, note verbatim as quoted on the card | | 4 | control | `disposition` returns **70** in that ledger — the probe is live | | 5 | PR #17209's removal held: no bare `name` in the typed request | **zero** hits for `^\s*name\??:` in lines `4043-4055`. Positive control, **same file, same regex**: 14 bare `name` members elsewhere (`:1062`, `:1218`, `:1267`, `:1305`, `:1741`, …) | Every `client_name` hit in `packages/client/src/index.ts` was read in context, not counted: `:947` (an `OAuthApplication` member), `:1008` (inside the `OAuthApplicationPublic` `Pick`), `:4025` / `:4027` (the existing #15447 docblock), `:4044` (the request type). Premise not falsified — `premise_still_valid: true`. ## 1. The JSDoc — landed bytes Added to the `oauth.applications.register` docblock, after the `metadata` paragraph and before the `Pinned by` line: ``` * ## ⚠️ A plain `name` IS honoured somewhere — not here (#17210) * * There is exactly one door that reads a body member spelled `name`, and * this method does not build it: * `POST /api/v1/auth/sys-oauth-application/register`, the ObjectStack * mount behind the Console's *Setup → OAuth Applications* create form. * The same #15447 round drove both doors on one real socket: that mount * answered **200** to a body spelled `name`, mapped it onto * `client_name`, and the `sys_oauth_application` row's `name` column was * set; this method's route, `/oauth2/create-client`, answered **201** * with the value **stripped** — absent from the response, absent from * `applications.get`, absent from `applications.list`, and `null` in * that same column. * * ⛔ That mount is **not** an SDK door, and it is not withheld by * oversight: it is `disposition: 'server-only'` in the auth route ledger * (`packages/plugins/plugin-auth/src/auth-route-ledger.ts`), a * session-required self-service wrapper written to serve the Console's * form — and #17210 ruled that it **stays** `server-only`, so no SDK * method builds its URL. To name a client from here, post `client_name`. * * The wrapper's other Console-shaped convenience is the same asymmetry's * second half: it splits that form's newline-separated redirect-URL * **textarea** into the array the vendor schema requires. ⛔ This route * performs no such split — `redirect_uris` must arrive **pre-split**, * one entry per URL, which is what an SDK caller holds anyway. ``` Both halves the ruling asked for are stated: `name` is honoured only by the Console's server-only mount, and this route requires `redirect_uris` pre-split. ## 2. `content/docs/protocol/objectui/actions.mdx` — the same fact, that page's own idiom Not a copy-paste of the JSDoc. The page is an action-authoring reference, so the note is written to an action author, about the `target` of the `create_oauth_application` example, and it is addressed at the `:424` neighbourhood — a new `#### ⚠️ That target is not interchangeable with the SDK's registration door` immediately after that example's `resultDialog` explanation, inside the same **Result Dialog** section. It says why the YAML above is that short (the mount does the plain-`name` mapping and the textarea split on the action's behalf), that the ledger carries it `server-only` for exactly that reason, and what an author would have to change — rename the name param to `client_name`, pre-split the redirect URLs — if they re-pointed `target` at the provider route. The docs-drift tool cannot surface this page for this diff (it states the rule by its YAML inputs, so it shares no identifier with the SDK file), which is why it was edited by hand. ## 3. The measured evidence, restated and not overstated From the #15447 round, on a real socket, driven through the real client: - `POST /api/v1/auth/sys-oauth-application/register` with a body spelled `name` answered **200**, the value came back as `client_name`, and the `sys_oauth_application` row's `name` column was set. - The same spelling posted to `/oauth2/create-client` answered **201** with the value **stripped**: absent from the response, absent from `oauth.applications.get`, absent from `oauth.applications.list`, `null` in the DB column. Nothing in this PR claims more than those two readings. In particular it does not claim the mount is reachable from the SDK, nor that any behaviour changed. ## 4. Changeset — `patch`, measured, not assumed `skip-changeset` was tested and refused. `@objectstack/client`'s `files[]` is `["dist","README.md","CHANGELOG.md"]`; after `pnpm --filter @objectstack/client build`, the new docblock prose is present in **both** `dist/index.d.ts` and `dist/index.d.mts` — it ships as editor hover to every consumer. - measurement: `grep -c "A plain \`name\` IS honoured somewhere"` → `1` in each of the two files - positive control, same files, same grep shape: `grep -c "the vendor's RECORD vocabulary"` (pre-existing docblock prose) → `1` in each - negative control: an absent marker string → `0` Published bytes move ⇒ `patch`. `.changeset/17210-oauth-register-name-trap-prose.md`. ## Gates Derived mechanically with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no hand-written path list), then reconciled with `--ran` carrying each command's own exit code, captured before any pipe. - **82 derived families — 81 run green, 0 UNRUN, 1 NOT MEASURED.** - `pnpm lint` (whole repo, `eslint . --no-inline-config`) — **exit 0**. - `pnpm --filter @objectstack/client test` — **42 files / 506 tests passed**, including the `oauth-applications-register-request-members.test.ts` pin. - `pnpm --filter @objectstack/client typecheck` — **exit 0**, test layer included (`check:test-typecheck`: 0 files / 0 errors). - Dependency closure built first: `pnpm --filter '@objectstack/client^...' build` — exit 0. Heavy runs went through `scripts/pm/os-verify-lock.sh`; verdicts read from its `VERDICT command-exit` lines. - Control characters: `grep -naP` over both edited files, zero hits, on top of `check:nul-bytes` green. **NOT MEASURED: `pnpm check:dual-build-cjs-loads`, reason:** it exited **3** with `PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ … ⛔ This is NOT a pass: nothing was measured`. It wants a whole-repo `pnpm build` (34 packages listed, none of them `@objectstack/client`, which is built). That is CI's `Build Core` run, and the gate reads emitted runtime JS where comments are stripped, so a docblock-only diff has no way to move it. Declared rather than run. `Clause-②: no`, re-derived from the delivered diff: three files, 33 inserted lines plus a changeset, zero deletions; no declaration, export, accept set or published symbol moves. ## Acceptance notes **`docs/qa/platform-checklist/areas/identity-auth.json` (`:1008`, `:1017`, `:1078`) — read, judged out of scope, not edited.** The dispatch left this call to me, so here is the reasoning rather than the verdict alone. All three lines were read in context. They belong to `identity-auth.oauth-app-consent-loop`, and **none of them states anything false.** `:1008` enumerates the three registration doors and already records this mount's distinguishing behaviour (*"the session-required wrapper that splits the redirect-URL textarea into redirect_uris"*); `:1017` offers two of those doors as alternatives for a step whose object is the **`client_secret`**, not the client's name; `:1078` is a `source` row naming the ledger. Nothing there claims the provider route honours a plain `name`. The item never names an application, at any step, in any acceptance clause or negative — so the trap is not reachable from it, and there is no assertion to correct. Against that, editing it has a real cost in the checklist's own machinery: a semantic edit owes a `revision` bump (`scripts/check-platform-checklist.mjs` holds `revision` equal to the last `history` entry precisely so that a changed item cannot silently re-validate old run results), which would invalidate the item's recorded runs for a clarification its steps never exercise. So: read, found accurate, left alone. It is not orphaned — the carriers that will next open it are the `checklist-author` coverage sweep and any `checklist-test` run selecting `identity-auth.oauth-app-consent-loop`. **Red lines held.** No `packages/spec` path in the diff. `auth-route-ledger.ts` untouched. No `content/docs/releases/` path. No file held by #17725, #17718 or #17093 is reached — the diff is `packages/client/src/index.ts`, `content/docs/protocol/objectui/actions.mdx` and one new changeset. --- _Generated by [Claude Code](https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 813f8e9 commit 7baf04a

3 files changed

Lines changed: 61 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'@objectstack/client': patch
3+
---
4+
5+
`oauth.applications.register`'s docblock says where a plain `name` IS honoured, and that it is not this route
6+
7+
A caller who wants to name an OAuth client reaches for `name`. On the route this
8+
method posts — the provider's `/oauth2/create-client` — that member is not in
9+
the body schema and is stripped: driven on a real socket, the call answered
10+
**201** and the value was absent from the response, from `applications.get`,
11+
from `applications.list`, and `null` in the `sys_oauth_application` row's `name`
12+
column. Nothing in the answer says so.
13+
14+
The spelling is not wrong everywhere, which is what made it worth writing down:
15+
`POST /api/v1/auth/sys-oauth-application/register` — the session-required
16+
ObjectStack mount behind the Console's *Setup → OAuth Applications* form —
17+
answered **200** to the same body, mapped `name` onto `client_name`, and set
18+
that column. That mount is `disposition: 'server-only'` in the auth route ledger
19+
and objectstack#17210 ruled it stays that way, so no SDK method builds its URL.
20+
21+
The docblock now states both halves where the caller reads them: post
22+
`client_name` to name a client from here, and `redirect_uris` must arrive
23+
pre-split — the newline-separated-textarea split is the Console wrapper's, not
24+
this route's.
25+
26+
Docblock only. No method is added, no request or response type changes, and the
27+
ledger row is untouched — but the text ships inside `dist/*.d.ts` as editor
28+
hover, so it is a `patch` rather than a no-publish change.

content/docs/protocol/objectui/actions.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,12 @@ resultDialog:
435435

436436
Each `fields[].path` is a dot path into `result.data`. The per-field `format` (`qrcode`, `code-list`, `secret`, `text`, `json`) controls rendering.
437437

438+
#### ⚠️ That `target` is not interchangeable with the SDK's registration door
439+
440+
`POST /api/v1/auth/sys-oauth-application/register` is an **ObjectStack mount written for this form**, and it is what keeps the YAML above this short. It accepts the application's display name as a plain `name` body key — the spelling an author writes as a param — and it splits a newline-separated redirect-URL **textarea** into the `redirect_uris` array the underlying provider schema requires. Both are form-shaped conveniences the mount performs on the action's behalf; the route ledger carries it as `disposition: server-only` (`packages/plugins/plugin-auth/src/auth-route-ledger.ts`) for exactly that reason, and [#17210](https://github.com/objectstack-ai/objectstack/issues/17210) ruled that it stays that way — no SDK method builds this URL.
441+
442+
So do **not** re-point this `target` at the provider's own registration route, `/api/v1/auth/oauth2/create-client` (what `@objectstack/client`'s `oauth.applications.register` posts). Driven on a real socket, the two doors answer the same body differently: the mount above returned **200**, mapped `name` onto `client_name`, and set the `sys_oauth_application` row's `name`; the provider route returned **201** with the value **stripped** — absent from the response, from `oauth.applications.get`, from `oauth.applications.list`, and `null` in that column. An action retargeted there needs its name param renamed to `client_name`, and its redirect URLs split into an array before the post — the textarea param would arrive as one unsplit string.
443+
438444
## Action Parameters
439445

440446
`params` declares inputs collected from the user before execution. Each entry is an `ActionParam` with two modes:

packages/client/src/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4038,6 +4038,33 @@ export class ObjectStackClient {
40384038
* router skips SERVER_ONLY endpoints, so over HTTP it answers 404 with a
40394039
* zero-byte body.
40404040
*
4041+
* ## ⚠️ A plain `name` IS honoured somewhere — not here (#17210)
4042+
*
4043+
* There is exactly one door that reads a body member spelled `name`, and
4044+
* this method does not build it:
4045+
* `POST /api/v1/auth/sys-oauth-application/register`, the ObjectStack
4046+
* mount behind the Console's *Setup → OAuth Applications* create form.
4047+
* The same #15447 round drove both doors on one real socket: that mount
4048+
* answered **200** to a body spelled `name`, mapped it onto
4049+
* `client_name`, and the `sys_oauth_application` row's `name` column was
4050+
* set; this method's route, `/oauth2/create-client`, answered **201**
4051+
* with the value **stripped** — absent from the response, absent from
4052+
* `applications.get`, absent from `applications.list`, and `null` in
4053+
* that same column.
4054+
*
4055+
* ⛔ That mount is **not** an SDK door, and it is not withheld by
4056+
* oversight: it is `disposition: 'server-only'` in the auth route ledger
4057+
* (`packages/plugins/plugin-auth/src/auth-route-ledger.ts`), a
4058+
* session-required self-service wrapper written to serve the Console's
4059+
* form — and #17210 ruled that it **stays** `server-only`, so no SDK
4060+
* method builds its URL. To name a client from here, post `client_name`.
4061+
*
4062+
* The wrapper's other Console-shaped convenience is the same asymmetry's
4063+
* second half: it splits that form's newline-separated redirect-URL
4064+
* **textarea** into the array the vendor schema requires. ⛔ This route
4065+
* performs no such split — `redirect_uris` must arrive **pre-split**,
4066+
* one entry per URL, which is what an SDK caller holds anyway.
4067+
*
40414068
* Pinned by `oauth-applications-register-request-members.test.ts`.
40424069
*/
40434070
register: async (req: {

0 commit comments

Comments
 (0)