Filed on the domain:cli execution PM seat's instruction (#6024), overruling the delivering dev's not-filed call. Found while delivering #15447 (PR #17209), out of that card's three-member scope and deliberately not fixed there.
The divergence
ObjectStackClient.oauth.applications.register declares redirect_uris required (packages/client/src/index.ts):
register: async (req: {
client_name?: string;
redirect_uris: string[]; // no `?` — required
token_endpoint_auth_method?: 'none' | 'client_secret_basic' | 'client_secret_post';
...
})
The route it posts to, POST /api/v1/auth/oauth2/create-client, is mounted verbatim from @better-auth/oauth-provider@1.7.2, and its body schema declares redirect_uris optional, typed as an array of string. Every one of that schema's 21 members is optional.
So the SDK is narrower than the producer, and a request the route would accept — one that omits redirect_uris entirely — cannot be expressed through this method at all. The caller does not get a wrong answer; they get a call they cannot write.
Driven, not inferred
From the measurement round on #15447 (comment 5559384773). The vendor body schema was not transcribed from the package's .d.ts — it was introspected at runtime: instantiate oauthProvider(), walk endpoints, read options.body. Its row for this member, verbatim from that comment's table:
| member |
type |
redirect_uris |
optional array of string ⚠️ optional here; the SDK declares it required |
The same round recorded that the object sets no catchall, so it is zod's default strip, and that the member list has no drift at 1.7.2.
Why this is p3 and not p2, stated so the grade is checkable
This is the same family as #15447 — a published SDK declaration that does not match the route it posts to — but in the opposite direction, and the direction is the whole of the grade:
The safe direction is why it is p3. ⛔ It is not a reason the divergence is not real: the declaration still does not match the producer, which is the defect class this repo files.
Not decided here
- Relax to optional — matches the producer exactly, and is the answer if the SDK's job is to express the route.
- Keep it required, and say so — defensible on its own terms: a client registered with no redirect URI cannot complete an
authorization_code flow, so requiring it is a guard rather than an accident. If this is the intent it should be stated in the method's TSDoc, because today the stricter declaration is indistinguishable from an oversight; a deliberate narrowing that says nothing reads as a bug to the next auditor, which is how this card came to exist.
- Whichever wins, the answer is worth checking against the two other doors that register a client: the RFC 7591 DCR endpoint
POST /oauth2/register, and the ObjectStack mount POST /api/v1/auth/sys-oauth-application/register, which takes redirect URLs as newline-separated text and splits them itself.
Relaxing a required member to optional is not breaking for callers (every existing call still compiles); tightening later would be.
Siblings
All three are readings of the same driven round, comment 5559384773 on #15447, against @better-auth/oauth-provider@1.7.2.
Generated by Claude Code
Filed on the
domain:cliexecution PM seat's instruction (#6024), overruling the delivering dev's not-filed call. Found while delivering #15447 (PR #17209), out of that card's three-member scope and deliberately not fixed there.The divergence
ObjectStackClient.oauth.applications.registerdeclaresredirect_urisrequired (packages/client/src/index.ts):The route it posts to,
POST /api/v1/auth/oauth2/create-client, is mounted verbatim from@better-auth/oauth-provider@1.7.2, and its body schema declaresredirect_urisoptional, typed as an array ofstring. Every one of that schema's 21 members is optional.So the SDK is narrower than the producer, and a request the route would accept — one that omits
redirect_urisentirely — cannot be expressed through this method at all. The caller does not get a wrong answer; they get a call they cannot write.Driven, not inferred
From the measurement round on #15447 (comment 5559384773). The vendor body schema was not transcribed from the package's
.d.ts— it was introspected at runtime: instantiateoauthProvider(), walkendpoints, readoptions.body. Its row for this member, verbatim from that comment's table:redirect_urisThe same round recorded that the object sets no
catchall, so it is zod's default strip, and that the member list has no drift at 1.7.2.Why this is p3 and not p2, stated so the grade is checkable
This is the same family as #15447 — a published SDK declaration that does not match the route it posts to — but in the opposite direction, and the direction is the whole of the grade:
oauth.applications.registerdeclares three request members the better-auth body schema silently strips (metadata,name,scopes) #15447's three members ran the unsafe direction: the SDK declared more than the route honoured, so a caller's value was silently discarded behind an HTTP 201. Trap-shaped, graded p2.The safe direction is why it is p3. ⛔ It is not a reason the divergence is not real: the declaration still does not match the producer, which is the defect class this repo files.
Not decided here
authorization_codeflow, so requiring it is a guard rather than an accident. If this is the intent it should be stated in the method's TSDoc, because today the stricter declaration is indistinguishable from an oversight; a deliberate narrowing that says nothing reads as a bug to the next auditor, which is how this card came to exist.POST /oauth2/register, and the ObjectStack mountPOST /api/v1/auth/sys-oauth-application/register, which takes redirect URLs as newline-separated text and splits them itself.Relaxing a required member to optional is not breaking for callers (every existing call still compiles); tightening later would be.
Siblings
oauth.applications.registerdeclares three request members the better-auth body schema silently strips (metadata,name,scopes) #15447 — the three members this same request type declared and the route silently stripped (name,scopes,metadata). Landed as draft PR fix(client)!: oauth.applications.register declares only the members /oauth2/create-client accepts #17209; that PR reports this divergence in its acceptance notes and ⛔ deliberately does not widen to take it.namewhen registering an OAuth client #17210 — the third finding out of the same measurement round: no SDK method buildsPOST /api/v1/auth/sys-oauth-application/register, the ObjectStack-owned mount that honours a plainname.All three are readings of the same driven round, comment 5559384773 on #15447, against
@better-auth/oauth-provider@1.7.2.Generated by Claude Code