Skip to content

Handle empty appraisal API responses deterministically - #285

Merged
karagozemin merged 1 commit into
Sub-Rosa-Issue:mainfrom
ayomustap:Handle-empty-appraisal-API-responses-deterministically
Aug 31, 2026
Merged

Handle empty appraisal API responses deterministically#285
karagozemin merged 1 commit into
Sub-Rosa-Issue:mainfrom
ayomustap:Handle-empty-appraisal-API-responses-deterministically

Conversation

@ayomustap

Copy link
Copy Markdown

Summary

  • add a typed for empty or non-JSON appraisal API responses
  • validate both unpaid and paid response paths via a single safe JSON parser
  • keep the error surface stable by not exposing raw response bodies
  • add regression coverage for empty, malformed, and x402-payment failure paths

Verification

@sub-rosa/appraisal-api@0.1.0 test /workspaces/sub-rosa-issue/services/appraisal-api
node --import tsx --test src/appraisal.test.ts src/appraisal-fixtures.test.ts src/config.test.ts src/client.test.ts

▶ appraisal schema fixtures load
�[32m✔ every fixture parses as JSON �[90m(0.799438ms)�[39m�[39m
�[32m✔ valid-response carries the appraisal model id �[90m(0.166213ms)�[39m�[39m
�[32m✔ valid-response inputsHash matches the canonical request hash �[90m(0.469412ms)�[39m�[39m
�[32m✔ appraisal schema fixtures load �[90m(2.417427ms)�[39m�[39m
▶ valid request fixture
�[32m✔ parses cleanly and preserves the canonical inputsHash �[90m(0.446209ms)�[39m�[39m
�[32m✔ produces the exact appraisal the response fixture documents �[90m(0.344724ms)�[39m�[39m
�[32m✔ valid request fixture �[90m(0.950691ms)�[39m�[39m
▶ missing fields fixture
�[32m✔ rejects with a stable error message naming itemRef �[90m(0.248572ms)�[39m�[39m
�[32m✔ missing fields fixture �[90m(0.409411ms)�[39m�[39m
▶ wrong types fixture — full validation cascade
�[32m✔ first field check (itemRef) reports the type violation �[90m(0.873926ms)�[39m�[39m
�[32m✔ with itemRef repaired, basePrice type violation surfaces next �[90m(0.202038ms)�[39m�[39m
�[32m✔ category as object surfaces category must be a string �[90m(0.172469ms)�[39m�[39m
�[32m✔ attributes as bare string surfaces attributes must be an object �[90m(0.280039ms)�[39m�[39m
�[32m✔ wrong types fixture — full validation cascade �[90m(1.776679ms)�[39m�[39m
▶ oversized text fixture
�[32m✔ rejects itemRef strings past the documented length bound �[90m(0.353871ms)�[39m�[39m
�[32m✔ category length bound is enforced when itemRef is valid �[90m(0.294023ms)�[39m�[39m
�[32m✔ exactly-MAX_ITEMREF_LENGTH itemRef is accepted (off-by-one guard) �[90m(0.141911ms)�[39m�[39m
�[32m✔ exactly-MAX_CATEGORY_LENGTH category is accepted (off-by-one guard) �[90m(0.070573ms)�[39m�[39m
�[32m✔ oversized text fixture �[90m(1.073225ms)�[39m�[39m
▶ invalid score values fixture — full cascade
�[32m✔ first bad score (quality as string) is reported �[90m(0.16699ms)�[39m�[39m
�[32m✔ quality repaired → demand null surfaces next �[90m(0.141673ms)�[39m�[39m
�[32m✔ quality+demand repaired → scarcity as object surfaces next �[90m(0.074816ms)�[39m�[39m
�[32m✔ quality+demand+scarcity repaired → risk boolean surfaces last �[90m(0.092601ms)�[39m�[39m
�[32m✔ numeric scores out of [0,100] are clamped (intentional scoring semantic) �[90m(0.263848ms)�[39m�[39m
�[32m✔ invalid score values fixture — full cascade �[90m(0.865818ms)�[39m�[39m
�[32m✔ appraisal is deterministic — identical inputs give identical output �[90m(1.724573ms)�[39m�[39m
�[32m✔ inputsHash ignores property order (canonical form) �[90m(1.204815ms)�[39m�[39m
�[32m✔ higher risk lowers value; higher quality/demand/scarcity raises it �[90m(0.430904ms)�[39m�[39m
�[32m✔ confidence increases with the number of supplied attributes �[90m(0.208992ms)�[39m�[39m
�[32m✔ suggested max bid never exceeds fair value (preserves margin) �[90m(0.210086ms)�[39m�[39m
�[32m✔ unknown category falls back to a neutral multiplier �[90m(0.187088ms)�[39m�[39m
�[32m✔ parseAppraisalRequest validates and clamps �[90m(0.644484ms)�[39m�[39m
▶ createPaidFetch response parsing
�[32m✔ throws a typed parse error for an unpaid empty response body �[90m(3.738808ms)�[39m�[39m
�[32m✔ throws a typed parse error for a paid non-JSON response body without exposing raw content �[90m(2.714588ms)�[39m�[39m
�[32m✔ preserves x402 payment errors for non-JSON 402 responses �[90m(1.616043ms)�[39m�[39m
�[32m✔ createPaidFetch response parsing �[90m(8.990905ms)�[39m�[39m
▶ configFromEnv valid configurations
�[32m✔ builds a minimal testnet config with safe defaults �[90m(2.59578ms)�[39m�[39m
�[32m✔ accepts explicit testnet overrides �[90m(1.167375ms)�[39m�[39m
�[32m✔ uses pubnet USDC when pubnet and its RPC are configured �[90m(0.952075ms)�[39m�[39m
�[32m✔ configFromEnv valid configurations �[90m(5.807851ms)�[39m�[39m
▶ configFromEnv failure modes
�[32m✔ names missing required variables �[90m(0.936429ms)�[39m�[39m
�[32m✔ rejects malformed PRICE values �[90m(3.972648ms)�[39m�[39m
�[32m✔ rejects invalid PORT values �[90m(2.675845ms)�[39m�[39m
�[32m✔ rejects unsupported networks �[90m(0.311201ms)�[39m�[39m
�[32m✔ rejects a network/passphrase mismatch �[90m(0.275614ms)�[39m�[39m
�[32m✔ requires a custom RPC URL for pubnet �[90m(1.318114ms)�[39m�[39m
�[32m✔ rejects malformed Stellar keys and addresses �[90m(1.952362ms)�[39m�[39m
�[32m✔ rejects malformed RPC URLs �[90m(1.20971ms)�[39m�[39m
�[32m✔ configFromEnv failure modes �[90m(13.327762ms)�[39m�[39m
�[34mℹ tests 40�[39m
�[34mℹ suites 9�[39m
�[34mℹ pass 40�[39m
�[34mℹ fail 0�[39m
�[34mℹ cancelled 0�[39m
�[34mℹ skipped 0�[39m
�[34mℹ todo 0�[39m
�[34mℹ duration_ms 2845.020853�[39m

@sub-rosa/appraisal-api@0.1.0 typecheck /workspaces/sub-rosa-issue/services/appraisal-api
tsc --noEmit -p tsconfig.json

Closes #277

@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@ayomustap Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@karagozemin
karagozemin merged commit 0fe31c6 into Sub-Rosa-Issue:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle empty appraisal API responses deterministically

2 participants