fix(sdk): normalize wire-vs-type drift in subdomains/faucet/usage#164
Merged
Conversation
Three SDK methods declared one return shape and returned another at
runtime in 1.51.0, surfaced in run402#163:
- `subdomains.list` returned the gateway envelope `{ subdomains: [...] }`
but was typed as `SubdomainSummary[]` — crashed the shipped
`list_subdomains` MCP tool with TypeError on iteration.
- `getUsage` declared `lease_expires_at: string` but the gateway omits
the field entirely. Relaxed to `?: string | null` here; gateway-side
fix filed in run402-private#143.
- `allowance.faucet` declared camelCase `transactionHash`/`amount` but
the gateway returns snake_case `transaction_hash`/`amount_usd_micros`,
so callers saw `undefined` in log lines. Now normalized at the SDK
boundary; `amountUsdMicros` added for callers wanting the raw number.
Test mocks updated to mirror the actual wire shapes so the next
regression gets caught before shipping — the previous mocks agreed with
the typed shape, not reality, which is how the drift slipped through.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The e2e mocks for `GET /subdomains/v1` and `POST /faucet/v1` returned
the SDK's old typed shape rather than what the live gateway emits, so
they couldn't catch the drifts fixed in the previous commit. After the
SDK normalization, the subdomains mock no longer matched the unwrap
path and `subdomains list` failed in CI.
- `/subdomains/v1` GET → `{ subdomains: [...] }` (was a bare array).
- `/faucet/v1` POST → `{ transaction_hash, amount_usd_micros, ... }`
(was `{ tx_hash, amount, ... }`).
Same principle as the SDK unit-test mock updates: tests verify reality,
not what the type says.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the three SDK type-vs-runtime drifts called out in #163. All three were the result of unit-test mocks agreeing with the typed shape rather than the live gateway, so the drift shipped quietly.
subdomains.listwas typedPromise<SubdomainSummary[]>but the gateway responds{ subdomains: [...] }. The shippedlist_subdomainsMCP tool was throwingTypeErroron iteration in production. SDK now unwraps the envelope.UsageReport.lease_expires_atwas typed as requiredstring, butGET /projects/v1/admin/:id/usagedoesn't return the field. Relaxed to?: string | null(the field is alsonullfor unleased accounts per the gateway's billing module). Gateway-side fix filed in run402-private#143 — once that ships, the type can tighten back up.FaucetResultwas typed in camelCase, butPOST /faucet/v1returns snake_case + anamount_usd_microsinteger. Bothrequest-faucetandinitMCP tools were loggingundefinedfor amount/token/transactionHash. Now normalized at the SDK boundary;amountUsdMicros: numberadded for callers that want the raw value.Test mocks updated to mirror the actual wire shapes — so the next regression gets caught before it ships.
Why this happened
Every affected unit-test mock returned the typed shape, not the wire shape. Tests passed; reality didn't. Worth flagging that our integration tests don't catch this either:
cli-integration.test.tsandcli-e2e.test.mjsuse string-containment assertions (captured().includes("api_calls")) rather than schema/field validation, and the faucet has zero real-gateway test coverage at all (the integration test pre-funds the wallet to skip the faucet path). A contract-test harness would be a worthwhile follow-up.Test plan
npm test— 483/484 unit + sync tests pass; one pre-existing failure (sync.test.ts"all SURFACE endpoints appear in llms.txt") is unrelated and reproduces onmainnode --test --import tsx sdk/src/namespaces/allowance.test.ts sdk/src/namespaces/secrets.test.ts sdk/src/namespaces/projects.test.ts— affected SDK suites: 44/44 passnode --test --import tsx src/tools/init.test.ts— MCP init suite: 10/10 passnpm run build:sdk— cleanr.subdomains.list(id)returns array,r.allowance.faucet()returns populated camelCase fields,r.projects.getUsage(id).lease_expires_atisundefinedFiles
sdk/src/namespaces/subdomains.ts— unwrapbody.subdomainssdk/src/namespaces/projects.types.ts—lease_expires_at?: string | nullsdk/src/namespaces/allowance.ts— normalize wire body to camelCase, addamountUsdMicrossdk/src/namespaces/secrets.test.ts,projects.test.ts,allowance.test.ts,src/tools/init.test.ts— mocks now use wire shapeCloses #163.