Skip to content

Validate the /me and usage response shapes at runtime (account surfaces of #277) #292

Description

@Davidson3556

Scoped slice of #277, which asks for the remaining typed response shapes to be brought onto the #266 schema layer and notes that "one PR per coherent group of shapes is fine". This issue tracks the account surfaces group so the umbrella #277 can stay open for the read, write and http.ts-helper groups.

Problem

All three GET /me callers go through the generic client.get, which still blind-casts the body:

  • auth status / whoami (auth.ts) renders m.scopes.join(', ') and computes missing scopes via m.scopes.includes(...), both unguarded. A /me body without scopes throws a raw TypeError: Cannot read properties of undefined (reading 'join') and exits 1.

    Under --output json it is worse: the text renderer closure never runs, so nothing throws — the CLI prints the partial identity and exits 0. An agent reading error.code sees nothing and reads scopes as undefined.

  • usage / credits (usage.ts) feeds credits and creditsPerRun into Math.floor(credits / creditsPerRun) for the "can trigger ~N runs" pre-flight hint, so a non-numeric balance reaches the arithmetic unchecked.

  • doctor (doctor.ts) reads a fully-optional identity projection. Its local MeIdentity interface has already drifted from the ME_IDENTITY_SCHEMA stub that feat(http): validate API responses at runtime with valibot instead of blind casts #266 left in place: the interface carries v3Enabled, the schema does not.

Repro

Point the CLI at a server returning a 200 /me body with no scopes:

$ testsprite auth status
Error: Cannot read properties of undefined (reading 'join')

$ testsprite auth status --output json
{ "userId": "u-1", "keyId": "k-1", "env": "development" }   # exit 0

Proposed fix

Add ME_RESPONSE_SCHEMA and USAGE_RESPONSE_SCHEMA, wire the stubbed ME_IDENTITY_SCHEMA into doctor, and alias doctor's MeIdentity to the schema's wire type so the two cannot drift again. Follow the #266 policy unchanged: looseObject throughout so additive server fields pass through to --output json, env validated as an open string so a new deployment tier cannot hard-fail, and required-vs-optional decided from fixture evidence rather than from the TypeScript interface alone.

The pre-write /me ping in runConfigure should stay unvalidated — it discards the body and only checks that the key was accepted, so validating it would let an unrelated /me change block credential setup.

Scope

Account surfaces only: MeResponse, MeIdentity, UsageResponse. The other #277 groups (CliTest, CliTestCode, CliProject, CliCreateTestResponse, CliFailureSummary, and the remaining as T casts in the http.ts helpers) are follow-ups under the umbrella.

Metadata

Metadata

Assignees

Labels

in-progressAssigned and actively being worked on

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions