Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ All notable changes to this project are documented here. The format is based on
now declined with a warning rather than silently running.
(wave-av/wave-foundation-public#73)

- **`X402PaymentRequired.error_detail` nesting** (`openapi.yaml`): `error_detail` referenced the
`Error` envelope (`{ error: { code, ... } }`), but the gateway nests the bare error object
directly under `error_detail` (`{ code, message, ... }`), with no inner `error` wrapper —
confirmed against a live 402 receipt (`curl https://api.wave.online/v1/clips`). The inner
object is now extracted as the `ErrorBody` component schema (referenced by `Error`, so every
other response is unchanged) and `error_detail` composes `ErrorBody` instead, matching the wire
shape so generated types no longer expect a nonexistent `error_detail.error` member.

### Added

- **Fleet agent directory resolve** (`GET /identity/resolve`) — identity-fabric E1. Adds the
Expand Down Expand Up @@ -65,6 +73,14 @@ All notable changes to this project are documented here. The format is based on
tag description explains the direct-to-relay flow, the `join` query parameter /
`x-wave-moq-join` header token carriers, and pins the surface to
`draft-ietf-moq-transport-18` (draft-19, published 2026-07-06, is not yet deployed).
- **`X402PaymentRequired.error_detail.payment_rejected`** (`openapi.yaml`) — documents the
additive `{ reason, rail }` deny verdict the gateway now publishes on a 402 when a submitted
payment was REJECTED, so generated types and docs can discover the diagnostic field. Optional
and conditional: absent on an ordinary unpaid challenge, present with a rejection reason token
and rail name when a submitted credential was denied — confirmed against a live rejected-payment
receipt (`curl -H "X-PAYMENT: <malformed>" https://api.wave.online/v1/clips`), which returned
`error_detail.payment_rejected: { reason: "invalid_payment_header", rail: "base-usdc" }`.
Closes #46.
- **WAVE Attestation Standard v1** (`attestation/` directory):
- `attestation/ATTESTATION-STANDARD-v1.md` — Frozen v1 specification: wire envelope
shape, field types, canonicalization algorithm (`canonicalJson` + `attestationId`),
Expand Down
48 changes: 29 additions & 19 deletions generated/api-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1013,25 +1013,27 @@ export interface components {
schemas: {
/** @description WAVE-normalized error envelope returned by the api.wave.online gateway. Upstream provider, auth, and quota failures are *interpreted* into this single shape — raw upstream errors (stack traces, vendor SDK objects) are never leaked to the caller. `error.code` is a stable machine-readable code (e.g. AUTH_REQUIRED, SCOPE_OVERREACH, RATE_LIMIT_EXCEEDED, UPSTREAM_ERROR); `error.message` is human-readable. */
Error: {
error: {
/** @description Stable machine-readable error code. */
code: string;
/** @description Human-readable explanation, safe to surface to end users. */
message: string;
/** @description Optional structured context (e.g. failing field, retry limit, requested scopes) — never raw upstream payloads. */
details?: {
[key: string]: unknown;
};
/** @description Actionable next steps, ordered most→least likely to resolve the error. Written to be acted on by a human OR an agent. */
suggestions?: string[];
/** @description Closest valid alternatives when the caller likely made a typo or wrong choice (e.g. an unknown scope, product, or route). */
did_you_mean?: string[];
/**
* Format: uri
* @description Documentation link for this error.
*/
doc_url?: string;
error: components["schemas"]["ErrorBody"];
};
/** @description The normalized WAVE error object itself — the value of the `Error` envelope's `error` member. Referenced directly by bodies that carry the error object under a different key (the 402 x402 challenge nests it under `error_detail` without the envelope wrapper). */
ErrorBody: {
/** @description Stable machine-readable error code. */
code: string;
/** @description Human-readable explanation, safe to surface to end users. */
message: string;
/** @description Optional structured context (e.g. failing field, retry limit, requested scopes) — never raw upstream payloads. */
details?: {
[key: string]: unknown;
};
/** @description Actionable next steps, ordered most→least likely to resolve the error. Written to be acted on by a human OR an agent. */
suggestions?: string[];
/** @description Closest valid alternatives when the caller likely made a typo or wrong choice (e.g. an unknown scope, product, or route). */
did_you_mean?: string[];
/**
* Format: uri
* @description Documentation link for this error.
*/
doc_url?: string;
};
Pagination: {
page?: number;
Expand Down Expand Up @@ -1920,7 +1922,15 @@ export interface components {
error: string;
/** @description Payment options; sign one and retry with the `x-payment` header. */
accepts: components["schemas"]["X402Accepts"][];
error_detail?: components["schemas"]["Error"];
error_detail?: components["schemas"]["ErrorBody"] & {
/** @description Present ONLY when a submitted payment was REJECTED (as opposed to no payment having been made at all): the sanitized deny verdict, so a paying caller can tell a rejected payment apart from an unpaid challenge and correct the named field. `reason` is a lowercase snake_case token from the gateway's closed deny vocabulary (e.g. `invalid_permit_header`, `session_expired`, `duplicate`, `rate_limited`); internal detail that cannot be published verbatim collapses to the generic `payment_rejected`. `rail` names the payment rail that denied (e.g. `base-usdc`, `tempo-pathusd`), or `unknown`. */
payment_rejected?: {
/** @description Sanitized machine-readable rejection reason token. */
reason: string;
/** @description The payment rail the rejection occurred on. */
rail: string;
};
};
/** @description Machine-executable directive for agent callers — a `pay` directive carrying the same `accepts` options. */
next_action?: {
[key: string]: unknown;
Expand Down
96 changes: 63 additions & 33 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2367,39 +2367,47 @@ components:
required: [error]
properties:
error:
$ref: '#/components/schemas/ErrorBody'

ErrorBody:
type: object
description: >
The normalized WAVE error object itself — the value of the `Error` envelope's
`error` member. Referenced directly by bodies that carry the error object under
a different key (the 402 x402 challenge nests it under `error_detail` without
the envelope wrapper).
required: [code, message]
properties:
code:
type: string
description: Stable machine-readable error code.
message:
type: string
description: Human-readable explanation, safe to surface to end users.
details:
type: object
required: [code, message]
properties:
code:
type: string
description: Stable machine-readable error code.
message:
type: string
description: Human-readable explanation, safe to surface to end users.
details:
type: object
additionalProperties: true
description: >
Optional structured context (e.g. failing field, retry limit,
requested scopes) — never raw upstream payloads.
suggestions:
type: array
description: >
Actionable next steps, ordered most→least likely to resolve the
error. Written to be acted on by a human OR an agent.
items:
type: string
did_you_mean:
type: array
description: >
Closest valid alternatives when the caller likely made a typo or
wrong choice (e.g. an unknown scope, product, or route).
items:
type: string
doc_url:
type: string
format: uri
description: Documentation link for this error.
additionalProperties: true
description: >
Optional structured context (e.g. failing field, retry limit,
requested scopes) — never raw upstream payloads.
suggestions:
type: array
description: >
Actionable next steps, ordered most→least likely to resolve the
error. Written to be acted on by a human OR an agent.
items:
type: string
did_you_mean:
type: array
description: >
Closest valid alternatives when the caller likely made a typo or
wrong choice (e.g. an unknown scope, product, or route).
items:
type: string
doc_url:
type: string
format: uri
description: Documentation link for this error.

Pagination:
type: object
Expand Down Expand Up @@ -4058,7 +4066,29 @@ components:
items:
$ref: '#/components/schemas/X402Accepts'
error_detail:
$ref: '#/components/schemas/Error'
allOf:
- $ref: '#/components/schemas/ErrorBody'
- type: object
properties:
payment_rejected:
type: object
description: >-
Present ONLY when a submitted payment was REJECTED (as opposed to no payment
having been made at all): the sanitized deny verdict, so a paying caller can
tell a rejected payment apart from an unpaid challenge and correct the named
field. `reason` is a lowercase snake_case token from the gateway's closed deny
vocabulary (e.g. `invalid_permit_header`, `session_expired`, `duplicate`,
`rate_limited`); internal detail that cannot be published verbatim collapses
to the generic `payment_rejected`. `rail` names the payment rail that denied
(e.g. `base-usdc`, `tempo-pathusd`), or `unknown`.
required: [reason, rail]
properties:
reason:
type: string
description: Sanitized machine-readable rejection reason token.
rail:
type: string
description: The payment rail the rejection occurred on.
next_action:
type: object
additionalProperties: true
Expand Down
Loading