# UFP attestations — off-network transactions and the evidence model

A vendor sells a job the network never saw — an agent quoted it directly
over the [vendor binding](VENDOR-BINDING.md), or the deal closed over
email. In v0.1 that transaction was invisible: no reputation credit for
the vendor, no demand data for anyone. In v0.2 the vendor **attests** it:
one authenticated POST reports what was asked, what was made, and what
couldn't be delivered. The vendor earns reputation credit; the network
learns about demand — and demand *gaps* — it never witnessed.

The canonical spec is code:
[`packages/protocol/src/attestation.ts`](../packages/protocol/src/attestation.ts)
and [`evidence.ts`](../packages/protocol/src/evidence.ts). This document
is the human-readable rendering.

## The evidence model: mandatory when available, never necessitated

Verification data follows one doctrine across the whole protocol:

- An implementation **MUST NOT fabricate** evidence.
- It **SHOULD provide** each item when it exists.
- It **SHOULD declare unavailability** with a reason rather than silently
  omit.

Nothing is schema-required — a missing tracking number can never break an
integration — but the spec makes absence *expressive*. Shipment tracking
is the canonical example: it is either the tracking data, or an explicit
declaration of why there is none —

```jsonc
{ "available": true, "carrier": "ups", "number": "1Z999AA10123456784" }
// or
{ "available": false, "reason": "untracked_shipping" }   // also: not_yet_assigned | vendor_policy
```

**"Shipped untracked" is honest; an omitted field is not.** Networks grade
reputation credit by evidence completeness, where a declared
unavailability scores nearly as well as the evidence itself and silent
omission scores worst. That asymmetry is the whole incentive: honesty
about what you don't have is almost free; saying nothing costs you.

The full evidence bundle: `tracking`, three payload hashes
(`request_payload_sha256`, `offer_payload_sha256`, `file_sha256` — they
make claims spot-checkable after the fact), `result_photo_urls`,
`invoice_ref`, `delivered_at`, `buyer_hash` (HMAC-SHA256 of the lowercased
buyer email under a vendor-held salt — links repeat buyers without
exposing PII), and `buyer_confirmation_token` (see the credit ladder).

## TransactionAttestation — annotated

```jsonc
// POST /v1/attestations   (Authorization: Bearer ufp_vk_…)
{
  "attestation_id": "acme_2026_07_0412",   // vendor-generated idempotency key
  "ufp_version": "0.2.0",
  "request": {                             // what the buyer ASKED for — raw,
    "process": "fdm_print",                // pre-resolution, demand-signal grade
    "spec": { "material": "asa", "size_in": 24, "quantities": [3] },
    "constraints": { "outdoor_rated": true }
  },
  "offer": {                               // what was offered/made; prices may be
    "spec_resolved": { "material": "asa", "color": "black" },  // omitted if commercially
    "quantity": 10,                        // sensitive (less credit)
    "subtotal_cents": 42000
  },
  "outcome": "fulfilled",  // quoted_only | ordered | fulfilled | cancelled
                           // | refunded | lost_capability_gap
  "gaps": [
    { "field": "quantity", "requested": 3, "provided": 10,
      "reason": "vendor_minimum" },        // fulfilled, but only at the shop minimum
    { "field": "size_in", "requested": 24, "provided": null,
      "reason": "vendor_maximum",          // provided: null = the sale was LOST on
      "note": "max build volume is 20in" } // this gap — buyer took the 20in variant
  ],                                       // elsewhere or not at all
  "occurred_at": "2026-07-08T16:00:00Z",
  "fulfilled_at": "2026-07-10T21:30:00Z",
  "evidence": {
    "tracking": { "available": false, "reason": "untracked_shipping" },
    "file_sha256": "9c1e07…",
    "invoice_ref": "INV-3310",
    "buyer_hash": "hmac256:74af…"
  }
}
```

Vendor identity comes exclusively from the authenticated key — a
`vendor_id` in the payload is ignored by construction.

`gaps` is the demand-gap record and the reason attestations are worth a
network's while: `{quantity: 3 → 10, vendor_minimum}` says "I win these
but force a bump — route me buyers who want 10+"; `{size_in: 24 → null,
vendor_maximum}` says "I lose every 24-inch job". Repeated across
attestations, that is routing signal no quote log contains.

## The credit ladder

Trust is a ladder, not a gate. Nothing beyond schema validity is ever
required, but each rung earns more:

1. **Bare attestation → partial credit.** Accepted, counted, and always
   labeled `attested_transaction` — never blended into verified-purchase
   stars (see [REPUTATION.md](REPUTATION.md)).
2. **+ Evidence → spot-checkable.** Payload hashes, invoice refs, photos,
   and a buyer hash make the claim auditable after the fact. Credit is
   still `partial`, but evidence completeness feeds the receipt's
   `detail` — the vendor is told exactly what would have earned more.
3. **+ Buyer confirmation → full credit.** `buyer_confirmation_token` is a
   network-minted token the vendor hands the buyer; one click on the
   network's domain countersigns the transaction. A confirmed attestation
   earns `full` credit and becomes review-eligible. **An unverified token
   never grants full credit** — presenting one moves the receipt to
   `pending_buyer_confirmation` with credit still `partial`.

The recommended grading semantics (this is the protocol's recommendation;
exact weights are implementation guidance): evidence that is *provided*
scores full weight, evidence *declared unavailable* scores nearly as much
— the reference network weighs it 0.75 against 1.0 — and *silent omission*
scores zero. Reject sparingly: the reference network rejects only payloads
it can falsify from the payload alone (a `fulfilled_at` earlier than
`occurred_at` — a fulfillment cannot predate its own order). A permissive
endpoint that grades low beats a strict one that teaches vendors to stop
reporting.

## Idempotent resubmission

`attestation_id` is the vendor's idempotency key: resubmitting the same id
**updates** the attestation, never duplicates it. The intended lifecycle
is progression — attest `quoted_only` when the quote goes out, resubmit as
`fulfilled` (with `fulfilled_at` and evidence) when the job ships. Demand
signals are counted once per attestation, not once per resubmission.

## Why vendors bother

Two returns on a few minutes of integration:

- **Ranked placement.** Attested volume feeds the per-spec reliability
  facets ("prints PC reliably" — REPUTATION.md) that win placement when
  agents filter on track record.
- **"Route me differently."** `gaps` is a vendor's direct channel into a
  network's routing and sourcing: minimum bumps, lost 24-inch jobs, and
  out-of-stock materials reported here shape which requests the network
  sends next.

## Network endpoint

Any UFP network exposes:

```
POST /v1/attestations
Authorization: Bearer ufp_vk_…        (vendor attestation key — distinct
                                       from agent API keys; issued by the
                                       network operator)
Body: TransactionAttestation (JSON)
```

Response — the `AttestationReceipt`:

```jsonc
{
  "attestation_id": "acme_2026_07_0412",
  "status": "accepted",            // accepted | pending_buyer_confirmation | rejected
  "reputation_credit": "partial",  // none | partial | full
  "detail": "accepted with partial credit. evidence score 2.75/8: provided file_sha256,
             invoice_ref, buyer_hash; declared unavailable tracking (untracked_shipping).
             A buyer confirmation token is what upgrades partial to full credit…"
}
```

`detail` is the feedback loop: every receipt tells the vendor exactly what
would have earned more. Enforcement is incentive-based, never
validation-based. A vendor's discovery doc lists the networks it reports
to (`attestation_networks`, plural — UFP is network-neutral).

The same `ufp_vk_` key now authorizes **two** endpoints on the reference
network: this attestation POST, and the vendor's own economics slice —
`GET /v1/vendor/report?days=` returns the authenticated vendor's
per-process lanes (quotes seen, bids, wins over an anonymized lane
total, losses by reason, unmet asks), displayed rating, and latest
health, scoped strictly to that vendor and never exposing another
vendor's data.

## Anti-gaming

Attested data enters as a **prior, not a verdict**. Attested records are
permanently labeled, never blended into verified-purchase stars, and the
economics are self-correcting: attestations matter most for vendors with
little verified history, and as ranked placement drives real orders
through a network, verified purchases dominate the record — a vendor
cannot self-attest its way past vendors with witnessed transactions.
Rate limits keep a runaway integration from flooding the demand ledger.

## v0.3 hardening (documented, not built)

- **Signing keys / JWKS** — vendors sign attestations; networks verify
  against published keys instead of bearer possession.
- **Buyer-token verification** — the confirmation flow that actually mints
  `full` credit (v0.2 records the token and parks the receipt at
  `pending_buyer_confirmation`).
- **Volume caps** — per-vendor attested-volume ceilings relative to
  verified volume.
- **Anomaly detection** — evidence-hash reuse, buyer-hash farms,
  gap-pattern outliers.
