# Attested Gateway Profile

**Version 0.1.0-draft.2 — 2026-08-20**
**Extends:** Verifiable Settlement Receipt (VSR) v0.4
**Publisher:** Rubric Protocol (rubric-protocol.com)
**Status:** Draft. The receipt format is specified here ahead of the gateway
being generally available, so that integrators can verify against a fixed
target rather than a moving one.

## 1. What this profile attests

Read this paragraph before building against the profile. It is the whole
claim, and it is deliberately narrow:

> **Rubric attests that this operator returned exactly these bytes for this
> request at this time.** It does not attest that the bytes are true, that the
> upstream operator is honest or competent, that the service was fit for the
> requester's purpose, or that the price was fair. A gateway receipt is
> evidence of delivery, not evidence of correctness.

An attested gateway sits between a paying agent and an upstream x402 service.
The agent pays the gateway; the gateway pays the upstream and returns the
upstream's response verbatim. Both payments and the exact bytes delivered are
committed into one signed, ledger-anchored receipt.

Everything downstream of this profile — pricing copy, marketing, an auditor's
reading of a receipt — inherits that boundary. A receipt that is presented as
proof the upstream's *answer* was right is being misread, and the profile
gives a verifier no basis for that reading.

## 2. Receipt structure

A gateway receipt is a VSR v0.4 receipt with two additional core members and a
second payment leg. Canonicalization, hash constructions, domain tags and the
signature block are unchanged from VSR v0.4 §4–§5; the core hash covers these
members because it covers the whole core.

~~~json
{
  "vsrVersion": "0.4-draft",
  "receiptId": "<uuid>",
  "hashProfile": "vsr/v0.4/sha3-256",
  "profile": "attested-gateway/0.1",

  "payment":  { "...": "buyer -> gateway, exactly as VSR v0.4 §3" },

  "upstreamPayment": {
    "rail": "x402",
    "railVersion": "2",
    "network": "eip155:8453",
    "asset": "<token contract>",
    "amount": "<atomic units, string>",
    "amountExponent": 6,
    "payer": "<gateway address>",
    "payee": "<upstream address, as advertised in the upstream challenge>",
    "reference": { "type": "eip3009-authorization", "value": "<tx>" },
    "settledAt": "<RFC 3339>"
  },

  "upstream": {
    "resource": "<absolute upstream URL, as requested>",
    "challengeHash": "sha3-256:<hex>",
    "payeeAdvertised": "<address from the upstream 402 challenge>",
    "status": 200,
    "responseBytes": <integer>,
    "attestationDeclined": false
  },

  "delivery": { "...": "as VSR v0.4 §3; responseHash covers the bytes returned to the buyer" },
  "anchor":   { "...": "as VSR v0.4" },
  "signature":{ "...": "as VSR v0.4" }
}
~~~

`profile` MUST be `attested-gateway/0.1` so a verifier can require the extra
members rather than silently accepting a single-leg receipt for a gateway call.

## 3. The two legs are not interchangeable

`payment` is what the buyer paid the gateway. `upstreamPayment` is what the
gateway paid the upstream. They differ by the attestation margin, and a
verifier MUST NOT treat either as the other.

    margin = payment.amount − upstreamPayment.amount

A receipt where `upstreamPayment` is absent, or where `upstreamPayment.payer`
is not the gateway, is not a valid gateway receipt. A receipt where
`upstreamPayment.payee` differs from `upstream.payeeAdvertised` means the
gateway paid someone other than the party the upstream challenge named, and a
verifier MUST reject it: that is the substituted-payee case, and it is exactly
what an independent record is for.

## 4. Response integrity

`delivery.responseHash` covers the bytes returned to the buyer. Those bytes
MUST be the upstream response verbatim. A gateway that rewrites, reformats,
re-encodes or truncates the upstream body MUST NOT emit a receipt under this
profile, because the receipt would commit to bytes the upstream never sent
while implying it did.

`upstream.responseBytes` and `delivery.responseBytes` MUST therefore be equal.
A verifier SHOULD compare them and treat inequality as `passthrough-violation`
rather than a generic mismatch: the two have different causes.

Transport encodings applied and removed in transit (`Content-Encoding`) are
not rewrites. The comparison is over decoded bytes.

## 5. Upstream opt-out

An upstream operator MAY decline attestation by returning:

    X-Rubric-No-Attest: 1

The gateway MUST honour it: the call is still proxied and the buyer still
receives the response, but no receipt is emitted, no attestation is written,
and the buyer is charged the upstream price without the attestation margin.
The gateway SHOULD return the same header downstream so the buyer knows why no
receipt arrived.

Where a receipt is emitted for an interaction in which the upstream had
previously declined, `upstream.attestationDeclined` MUST be `true` and no
`delivery.responseHash` may be published. Honouring the opt-out costs the
gateway a margin it was not owed and removes the only reasonable objection an
operator has to being proxied.

## 5a. Cached delivery

A gateway MAY serve a repeat request from cache. A cached response is a
different claim from a fresh one and MUST NOT be dressed as the same.

On a cache hit the upstream did not return anything *at this time* and no
upstream payment was made, so the receipt:

- MUST set `upstream.servedFromCache` to `true`
- MUST carry `upstream.originReceiptId` and `upstream.originServedAt`, naming
  the receipt for the fresh delivery these bytes came from
- MUST carry `upstream.cacheAgeSeconds`
- MUST omit `upstreamPayment` entirely rather than repeating the original leg.
  Repeating it would assert a payment that did not happen for this call.

The buyer leg is unchanged: the buyer really did pay the gateway, usually less,
because the gateway incurred no upstream cost.

~~~json
"upstream": {
  "resource": "https://…",
  "servedFromCache": true,
  "originReceiptId": "<uuid of the fresh delivery>",
  "originServedAt": "<RFC 3339>",
  "cacheAgeSeconds": 41,
  "status": 200,
  "responseBytes": 11956,
  "attestationDeclined": false
}
~~~

The claim for a cached receipt is correspondingly narrower, and section 1's
sentence does not apply to it unchanged:

> **Rubric attests that these are the exact bytes this operator returned for
> this request at `originServedAt`, replayed from cache `cacheAgeSeconds` later
> without contacting the operator.** Whether they are still accurate is a
> question about the upstream's data, not about this receipt.

### Cache scope

A cache entry MUST be scoped to the buyer who paid for the fresh delivery,
unless the upstream has explicitly permitted broader reuse.

This is a licensing boundary, not a performance tuning knob. Serving buyer B a
response that buyer A paid the upstream for means the gateway collected money
for the upstream's product and the upstream was paid once for two deliveries.
That is resale, whatever the receipt says, and no amount of attestation makes
it something else. Per-buyer reuse is the buyer re-reading something they
already bought.

A gateway MUST NOT cache a response when the upstream sends `Cache-Control`
containing `no-store` or `private`, when the status is not 200, or when the
upstream declined attestation.

## 6. Verification procedure

1. Check `profile` is `attested-gateway/0.1` and both payment legs are present.
2. Verify the buyer leg exactly as VSR v0.4.
3. Verify `upstreamPayment` settled on the named network and that its `payer`
   is the gateway's published address.
4. Compare `upstreamPayment.payee` to `upstream.payeeAdvertised`; reject on
   mismatch.
5. Compare `upstream.responseBytes` to `delivery.responseBytes`; on inequality
   report `passthrough-violation`.
6. Verify signature quorum, Merkle inclusion and anchor consistency as VSR
   v0.4.

Result tiers: `attested` | `attested-cached` | `passthrough-violation` |
`payee-mismatch` | `single-leg` | `unresolvable`.

`attested-cached` is returned for a receipt with `upstream.servedFromCache`
true. A verifier MUST NOT report it as `attested`: the two answer different
questions, and only one of them says the upstream was contacted. A cached
receipt missing `originReceiptId` is `unresolvable`, not `attested-cached` —
without it the claim cannot be traced to a delivery that actually happened.

## 7. What a verifier still cannot conclude

Restating section 1 in operational terms, because this is where receipts get
over-read:

- That the upstream's answer is correct. The gateway did not evaluate it.
- That the upstream is a legitimate business. The gateway checked that a 402
  challenge was well-formed and that a payment settled.
- That the price was reasonable. Both legs are recorded; neither is judged.
- That the same request would return the same bytes later. The receipt is
  about one interaction at one timestamp.

A receipt answers "what was delivered, by whom, for what payment, when." It
was built to answer that question and no other.
