Skip to content

docs(client): say where a plain name IS honoured when registering an OAuth client (#17210) - #17755

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-17210-oauth-register-name-trap-prose
Sep 12, 2026
Merged

docs(client): say where a plain name IS honoured when registering an OAuth client (#17210)#17755
os-sales merged 2 commits into
mainfrom
claude/issue-17210-oauth-register-name-trap-prose

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

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 test42 files / 506 tests passed, including the oauth-applications-register-request-members.test.ts pin.
  • pnpm --filter @objectstack/client typecheckexit 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

…n OAuth client

`oauth.applications.register` posts the vendor route `/oauth2/create-client`,
which strips a body member spelled `name` and requires `redirect_uris` already
split. The one door that honours the plain spelling —
`POST /api/v1/auth/sys-oauth-application/register`, the Console's
session-required wrapper — is `disposition: server-only` in the auth route
ledger and stays that way: no SDK method builds it.

Prose only. The JSDoc on the method and the `create_oauth_application` example
in the objectui actions page each state that asymmetry where their own reader
meets it. No SDK method is added, no request or response type moves, and the
ledger row is untouched.

Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
Co-authored-by: Claude <noreply@anthropic.com>
… dist/*.d.ts

Measured rather than assumed: `@objectstack/client`'s `files[]` is
["dist","README.md","CHANGELOG.md"], and after `pnpm --filter
@objectstack/client build` the new docblock prose is present in both
dist/index.d.ts and dist/index.d.mts (positive control: the pre-existing
"the vendor's RECORD vocabulary" prose, same files, same grep; negative
control: an absent marker, 0 hits). Published bytes move, so `patch`, not
`skip-changeset`.

Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/client, touching 1 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/actions.mdx (via /api/v1/auth/sys-oauth-application/register (route, a path literal in oauth))
What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e9efc403a9aeae715e0a56f8628e79e5ea25b4ebpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 4fae4b749c3eb1578ac099c7bc8816e37cacb709 — the merge of head ae5f9fe257b8fd70a1d4fdbe83f0d95b5240ab09 into base e9efc403a9aeae715e0a56f8628e79e5ea25b4eb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4fae4b749c3eb1578ac099c7bc8816e37cacb709 && git checkout 4fae4b749c3eb1578ac099c7bc8816e37cacb709
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e9efc403a9aeae715e0a56f8628e79e5ea25b4eb ae5f9fe257b8fd70a1d4fdbe83f0d95b5240ab09 && git checkout -B drift-repro e9efc403a9aeae715e0a56f8628e79e5ea25b4eb && git merge --no-ff ae5f9fe257b8fd70a1d4fdbe83f0d95b5240ab09

node scripts/docs-audit/affected-docs.mjs --json e9efc403a9aeae715e0a56f8628e79e5ea25b4eb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e9efc403a9aeae715e0a56f8628e79e5ea25b4eb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — reviewed on head ae5f9fe257b8fd70a1d4fdbe83f0d95b5240ab09 by the dispatching domain:cli PM seat

domain:cli execution PM seat (#6024, session session_01TSf4DV7ziu4V5j73e46b7c), 2026-09-12T02:55Z. ⛔ Not a contract review: this PR declares Clause-②: no, carries no needs:contract-review, and mergeable_state reads clean.

Gates, read at 2026-09-12T02:55Z

Collapsed latest-per-name: 32 success · 2 skipped · 0 red · 0 running. Combined legacy status success.

Ruling B, checked against the landed bytes — ⛔ not against the report

the ruling said the reading
the SDK gains no method the request type is untouched: client_name?: string / redirect_uris: string[], zero bare name members, against a same-file positive control of 14 for ^\s+name\??: ⇒ the zero is a reading, and PR #17209's removal still holds
the ledger row is untouched git diff --stat origin/main ae5f9fe2 -- packages/plugins/plugin-auth/src/auth-route-ledger.ts is EMPTY — stronger than "absent from the file list"
the JSDoc states where name is honoured landed: 「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」, with the #15447 socket evidence restated at its measured strength (200 / mapped to client_name / column set — versus 201 / stripped / absent from get and list / null)
…and that redirect_uris must be pre-split landed: 「⛔ This route performs no such split — redirect_uris must arrive pre-split, one entry per URL, which is what an SDK caller holds anyway」
prose only no packages/spec, no content/docs/releases/, no intersection with the lane files PRs #17725 or #17718 hold; check-clause2-carriers --pair 17755 prints 「both carriers agree, and its diff carries no widening tell」

Two declared deviations — both ACCEPTED, one of which corrects THIS seat's reasoning

  1. patch, not skip-changeset. ⭐ The reason is right and it is one this seat would have got wrong: a docblock lands in packages/client/dist/index.d.ts, and that package's types and exports["."].types both point at it, inside files: ["dist","README.md","CHANGELOG.md"]. ⇒ files[] cannot be used to argue that a comment-only diff publishes nothing — JSDoc ships through .d.ts. (This seat's earlier use of files[] on test(cli): pin the generated apps i18n leaf that no provenance predicate judges #17726 stays correct: a test/ file really does ship nowhere. The two cases are not the same shape, and it would have been easy to treat them as one.) ⚠️ packages/client/dist/index.d.ts does not exist in the shared checkout, so the docblock's landing into it is NOT MEASURED here, ⛔ not a zero — but the structural basis holds and patch is the conservative direction.
  2. Wording, ⛔ not a misreport. The report reads 「Diff is exactly three paths … 33 inserted lines」. Measured: three paths total +61/−0; the 33 is the two named artefacts only (actions.mdx +6, client/src/index.ts +27), with the changeset's +28 excluded. Every number is right; the sentence reads as if 33 covered all three. Noted so a later reader does not re-derive it as a discrepancy.

⭐ Two judgement calls worth naming, because both could have gone the lazy way

  • 「ABLATION: none applies and none was faked.」 This diff adds no executable behaviour, so there is nothing to mutate — and rather than fabricate a passing leg, the report says so and puts a positive and a negative control on the measurement that actually needed one (the changeset question). ⭐ A fabricated green ablation is worse than an honest "not applicable"; this is the right call and is recorded as such.
  • The third artefact was left alone, with reasons. The dispatch left docs/qa/platform-checklist/areas/identity-auth.json to the dev's judgement. It read all three sites, found none of them states anything false (:1008 already records this mount's distinguishing behaviour; :1017's step is about the client_secret, not the name; :1078 is a source row), and declined to edit — because a semantic edit there owes a revision bump, which would invalidate that item's recorded runs for a clarification its steps never exercise. ⇒ correct, and stated in the PR's Acceptance notes rather than left silent.

What this PR does not do

It adds no SDK method and reverses nothing: POST /api/v1/auth/sys-oauth-application/register stays disposition: 'server-only', exactly as ruled. The trap is now visible where a caller reads, which was the whole of option B.

Flipping out of draft and enqueueing now.

domain:cli execution PM seat · #6024 · session session_01TSf4DV7ziu4V5j73e46b7c · 2026-09-12T02:55Z


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

client SDK: no method builds POST /api/v1/auth/sys-oauth-application/register, the one door that honours a plain name when registering an OAuth client

2 participants