# Grade B — a pinned-IPFS dependency (the honest off-chain one)

Grade **A** means everything the app renders from is on-chain. Sometimes you genuinely need an asset that's too
big or too dynamic to store on-chain. Linking a plain URL drops you to **C (Live)** — the host can swap the bytes
under your users. There is a better, honest middle: pin the asset so it's **content-addressed and
re-verifiable**, and you earn **B (Pinned)**.

B is one notch below A (retrieval is off-chain) and one above C (a mutable URL). You reach it *only* when the
code actually enforces the pin — the grade is computed from what's enforced, never from a promise.

## The three things B requires

1. **A raw CIDv1.** Pin the file so its address is a raw CIDv1 — `bafkrei…`, sha256, a single block ≤ 1 MiB.
   That's the only shape a browser can re-hash and verify. A `Qm…` or `bafybe…` (dag-pb) hash is **not**
   accepted — it can't be verified client-side, so it would be dishonest to grade it B.

   ```bash
   ipfs add --cid-version 1 --raw-leaves ./emblem.svg   # → bafkrei…
   ```

2. **An on-chain pointer.** Store the `ipfs://<cid>` in an on-chain record you control — an ENS `contenthash`.
   That's what separates B from C: the pointer can't be silently swapped.

3. **Client verification that fails closed.** Before rendering, the app fetches the bytes, computes `sha256`,
   rebuilds the CIDv1, and checks it **equals** the on-chain CID. On any fetch failure, timeout, or mismatch it
   keeps the **on-chain floor** and says so — it never renders unverified bytes.

If any of the three isn't enforced, the honest grade is C (or a lie). All three, and it's B.

## Declaring it to the grader

The SDK reads a `pinnedDep` and grades the dependency axis `ATTESTED → B`:

```ts
import { grade } from "@chainrender/client";

const pinnedDep = { protocol: "ipfs", cid: "bafkrei…" };   // raw CIDv1

grade(app, { pinnedDep }).letter;        // "B"  (word: "Pinned")
// or attach it to the app node — grade() picks it up either way:
grade({ ...app, pinnedDep }).letter;     // "B"
```

Fail-closed by construction: an unverifiable CID is ignored (the app stays at its on-chain grade, A) — you can't
get a *silent* B. An explicit `dependency` option still wins if you set one. Through the MCP server, pass
`pinnedDep` to `chainrender_grade` / `chainrender_review`.

`isVerifiablePinnedDep(pinnedDep)` tells you up front whether a CID qualifies, so you can warn the author before
they build around it.

## What to tell your users

Show the computed grade, not an adjective. A pinned-IPFS app is **B (Pinned) — verifiable, but retrieved
off-chain**, and the breakdown names the pinned dependency as the reason. That's the honest trade-off: the user
sees exactly what they're trusting before they act.
