How the Peerseal Trust Score is computed.
Portable human trust for every platform — a Trust Score built from peer vouches, verified purchases, eyewitness confirmations, and identity-verified ownership.Each post’s score is a transparent additive function over four engine-backed attestation kinds — receipt, eyewitness, identity verified, and continuous recording — clamped to a 100-point ceiling. CAWG identity binding is documented below as an optional editorial rail with its own +15 weighting, outside the live engine. The math lives at src/lib/business/trust-score.ts and the same logic runs at request time on every /api/trust-score call.
01 — Formula
One additive function. Same code, every request.
The composite score is a pure function over the four attestation kinds on a post. No heuristics, no decay, no ML in v1 — the same input set always returns the same score.
score = Math.min(
MAX_SCORE,
Σ min(count_per_kind, 1) × WEIGHTS[kind]
)MAX_SCORE= the clamp ceiling (imported literal: 100).count_per_kindis capped at 1 per kind per post — multiple attestations of the same kind do not compound.WEIGHTS[kind]is the per-kind contribution table (next section).
worked example · all four engine kinds present
- receipt1 × 55 = 55
- eyewitness1 × 40 = 40
- identity_verified1 × 25 = 25
- continuous_recording1 × 20 = 20
Σ = 140 → Math.min(100, 140) → 100
pure function · same logic the live /api/trust-score endpoint runs · no DB read
02 — The five attestation kinds
One table. Four scoring signals. One optional rail. Each adds its weight.
Every engine-backed kind lives once in the engine’s WEIGHTS table. The page reads it directly — change a weight in code and this row updates next render. A methodologically documented 5th rail — CAWG identity binding — is rendered below as an optional signal; it does not contribute to the engine Σ, only to the editorial sum.
- 01
receipt
Verified purchase — the strongest single signal.
Per-post cap is 1 — multiple receipts on the same post still count as one signal; provider IDs are hashed, not displayed.
A receipt-signed photo of the item, an order-ID match, or a payment processor hash.
- 02
eyewitness
Human "I was there" — a strong personal signal.
Each vouch is signed by a distinct witnesser with a bounded invite token; one witness, one vouch per post.
A named witness with a free-text statement and an occurred-at timestamp.
- 03
identity verified
KYC-style anchor — links the score to a real person.
Anchored to a third-party KYC report keyed to the author — the post can't claim a stronger identity than the verified one.
An identity-verification provider report keyed to the post author.
- 04
continuous recording
Timed-media corroboration — points only, no badge.
Time-windowed clip only — partial or non-overlapping clips don't compound; contributes points but no badge by design.
A timed recording clip with a start → end window. Points add to the total, but never unlock a badge by design.
- optional
cawg identity binding
CAWG identity binding — cryptographic proof of author custody at issuance.
Optional. The +15 weight only applies when an active CAWG binding is present on the post — absent active binding, this signal contributes 0. CAWG identity binding certifies that the signer of the C2PA manifest holds the asserted identity and has custody of the signing key at issuance time.
A C2PA manifest whose X.509 signer chain references an active CAWG identity assertion for the post author.
CAWG is the identity-binding layer that ties the four engine-backed attestations above to a real keyholder, rather than treating each one as an unanchored signal. Where receipt, eyewitness, identity verified, and continuous recording describe what was claimed about a post, a CAWG identity binding proves that the signer of the C2PA manifest holds the asserted author identity and had custody of the signing key at the time of issuance. The +15 weight is additive and request-time like the rest of the engine, documented on this page so the model and the rendered output agree. The upstream spec lives at CAWG.
03 — Per-post caps & clamp
One of each kind, then clamp.
Per post, every engine-backed kind contributes at most its single weight — even if a creator attaches three receipts, only one receipt counts. This is an MVP constraint and lives in lines 66–70 of the engine.
The un-clamped sum of the four engine kinds is 140. Without the clamp, a post hitting all four engine kinds would score above the published 100-point ceiling. The clamp keeps the score a comparable unit across the network. CAWG identity binding is the 5th, optional editorial signal — present-on-the-post adds +15 to the editorial sum, and 0 when absent. The engine deliberately does not include it in the additive function; it is documented here so the model and the page agree.
Engine Σ (4 kinds) = 140 → clamped to 100. Editorial Σ (5 kinds, with optional CAWG) = 155 — clipped conceptually by the 100-point ceiling, plus the CAWG signal only when an active binding is present on the post.
04 — Badges
Earned by presence, not by a threshold.
Badges are unlocked the moment any attestation of the triggering kind is present on a post. There is no numeric score cutoff for a badge — the engine emits the label iff count[kind] > 0, in the canonical BADGE_LABELS order.
| Badge label | Trigger | Pts |
|---|---|---|
| Purchased by author | receipt> 0 | 55 |
| Vouched by witnesses | eyewitness> 0 | 40 |
| Identity verified | identity verified> 0 | 25 |
Note. continuous_recording adds +20 to the score but does not unlock a badge by design — its label would only inflate the badge shelf without adding meaningful attestable signal.
05 — Freshness & decay
v1 — no decay. Freshness is a roadmap item.
Attestations are scored exactly as presented. occurredAt and verifiedAtare stored with each attestation, but the engine’s score function does not weight them yet — an attestation two years old scores identically to one from yesterday.
Time-based decay (e.g. half-life windows, dispute-rate modifiers, recency boosts) is queued for the next sprint. Until that ships, freshness affects the score only through documented human judgement on the receiving platform.
06 — Live status
Read the four kinds end-to-end today — persistence is widening.
The full four-kind model round-trips through the public /api/trust-score endpoint and the contract-verification flow. Live persistence is currently scoped to two kinds — receipt and eyewitness — per the MVP schema. identity_verified and continuous_recording land in the next sprint alongside disputes and decay.
Engine · public · contract · preview only — DB scope is narrower than the model.
07 — Try the weights
Score without posting yet.
Toggle the four attestation kinds and watch the score update live. The same math the engine runs at request time, with nothing persisted below.
trust score · preview
What does each attestation earn?
Toggle kinds to see the score + badges update. Pure-function math — nothing is saved.
scoring…
08 — Worked example
A real DB post, scored by the same engine.
Below is a seeded demo post (id demopost0001marketplace00) attributable to @demo0001. The number rendered is produced by the same score(...) call the request-time /api/posts/[id] route runs. You can verify by navigating to /u/demo0001 and the post detail page itself.
seeded post · live fetch
Outdoor gear haul — verified purchase + witness
@demo0001 · Demo Marketplace Author
- receipt1 × 55 = 55
- eyewitness1 × 40 = 40
- identity_verified0
- continuous_recording0
Engine · public · persistence currently scoped to receipt + eyewitness · identity_verified and continuous_recording shown as 0 here is honest — not yet seeded (see section 06).
09 — CAWG identity binding
Cryptographic proof of author custody at issuance.
The Creator Assertions Working Group (CAWG) defines identity-assertion credentials referenced by a C2PA manifest’s X.509 signer chain. Where the four engine-backed attestations describe what was claimed about a post, a CAWG identity binding proves that the signer of the C2PA manifest holds the asserted author identity and had custody of the signing key at issuance time.
The Trust Score contribution from CAWG is +15 to the editorial Σ when an active CAWG binding is present on the post, and 0 when absent. CAWG is deliberately excluded from WEIGHTS and the request-time score() function so the headline engine Σ stays a stable 4-kind comparable unit clamped at 100. It is documented here so the model and the rendered page stay in sync.
| Kind | Weight | Scope |
|---|---|---|
| receipt | +55 | engine Σ · badge |
| eyewitness | +40 | engine Σ · badge |
| identity verified | +25 | engine Σ · badge |
| continuous recording | +20 | engine Σ · no badge |
| cawg identity binding | +15 | editorial Σ only · 0 when absent · no badge |
engine Σ (4 kinds) · editorial Σ (5 kinds, with optional CAWG)
10 — FAQ
Short answers to the questions we get most.
If something below doesn’t answer what you’re looking for, the engine is the next stop — it’s small on purpose.
- How fresh is a Trust Score?
- v1 scores attestations as-presented — there is no decay. Timed metadata (occurredAt, verifiedAt) is stored but the engine does not weight it yet; an attestation two years old scores identically to one from yesterday. Time-based decay is queued for the next sprint alongside disputes.
- Can one person game the score?
- Per-post caps are enforced in code: count_per_kind is clamped to 1, so three receipts on one post still count as one. Receipt provider IDs are hashed, eyewitnesses are signed with bounded invite tokens, and weights are sourced from WEIGHTS at request time — a user can't submit a higher weight.
- Why no badge for continuous recording?
- By design. Continuous recording contributes +20 to the score but cannot unlock a label, because the label would just inflate the badge shelf without adding meaningful attestable signal. The cap and the no-badge rule both live in the engine.
- Where does the math live?
- In src/lib/business/trust-score.ts. The same module is imported by the /api/trust-score route, the contract-verification flow, the /methodology page, and the score-preview client island — one source of truth, request-time evaluated.