docs: add TRUST_SCORE.md (trust-scoring review + plan)
Ideas-only review of a personalized web-of-trust approach: a spec-correct NIP-85 consumer (kind-10040 provider designation, house fallback, author + signature verification, the rank/followers/reporters/muters/hops tags, and the absence rules), a Verified/Unknown/Flagged display with a viewer mode, counterparty alerts, confidence-gated scoring, presets and negative signals. Notes what we already have, concrete file touch points, license and privacy caveats, and a recommended first slice. Links it from the parked identity/conflict section.
This commit is contained in:
parent
486ffdb000
commit
8833a15830
2 changed files with 229 additions and 0 deletions
|
|
@ -2512,6 +2512,12 @@ downstream of **centralized identity + centralized conflict resolution**; the fi
|
||||||
is identity at a lower level + **plural, viewer-applied filtering**. A full idea
|
is identity at a lower level + **plural, viewer-applied filtering**. A full idea
|
||||||
list was discussed this session but is deliberately **NOT started**.
|
list was discussed this session but is deliberately **NOT started**.
|
||||||
|
|
||||||
|
**Related, also ideas-only:** `TRUST_SCORE.md` collects the trust-scoring review
|
||||||
|
(NIP-85 consumer with kind-10040 provider choice, a Verified/Unknown/Flagged
|
||||||
|
display, confidence-gated scoring, counterparty alerts). It is the concrete form
|
||||||
|
of the "labeler choice / plural filtering" thesis below. Not started; read it
|
||||||
|
before touching `useTrustScore` / `useRelatrScore` / `trustFilter`.
|
||||||
|
|
||||||
**Keep the door open (cheap):** admin keys and relay sets must stay *data you can
|
**Keep the door open (cheap):** admin keys and relay sets must stay *data you can
|
||||||
swap*, not constants. Hardcoded `ADMIN_PUBKEYS` is fine now — converting it later
|
swap*, not constants. Hardcoded `ADMIN_PUBKEYS` is fine now — converting it later
|
||||||
is a small refactor, not a trap.
|
is a small refactor, not a trap.
|
||||||
|
|
|
||||||
223
TRUST_SCORE.md
Normal file
223
TRUST_SCORE.md
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
# Trust scores — ideas and plan
|
||||||
|
|
||||||
|
Status: ideas / proposal. No code yet.
|
||||||
|
Scope: better trust for people and content across the map, The Lab, the
|
||||||
|
marketplace, and moderation.
|
||||||
|
|
||||||
|
Source material: the **GrapeRank** and **Trusted Assertions** specifications in
|
||||||
|
the `NosFabrica/protocols` repository, plus a public reference
|
||||||
|
implementation and client by the same group. NIP-85 (Trusted Assertions) is the
|
||||||
|
published, stable contract; GrapeRank is one pre-NIP scoring model that produces
|
||||||
|
the published scores. We should treat NIP-85 as the interface and GrapeRank as a
|
||||||
|
model we may adopt or borrow from, not a hard dependency.
|
||||||
|
|
||||||
|
## Where we are today
|
||||||
|
|
||||||
|
Positive signals (per-viewer, client-side):
|
||||||
|
|
||||||
|
- `src/hooks/useTrustScore.ts` and `src/hooks/useBatchTrustScores.ts` compute a
|
||||||
|
static ladder from the viewer's follow graph:
|
||||||
|
`100` self, `90` direct follow, `60` friend-of-friend (2 hops), `30` has a
|
||||||
|
NIP-05, `0` unknown. Signature-verified via `onlyVerified`.
|
||||||
|
- `src/lib/trustFilter.ts` + `TrustFilter.tsx` expose the levels
|
||||||
|
`all / verified (>=90) / trusted (>=60) / following`.
|
||||||
|
- `useSpotCommunityScore` weights content signals by distinct accounts,
|
||||||
|
web-of-trust and activity (per-account cap of 3); `useTradeIntegrity` adds
|
||||||
|
ring detection. Reports drive `useAutoHiddenSpots` (>=3 distinct reporters,
|
||||||
|
no community signals) and the review queue.
|
||||||
|
|
||||||
|
NIP-85 consumption is half-built:
|
||||||
|
|
||||||
|
- `src/hooks/useRelatrScore.ts` (and `useBatchRelatrScores`) query kind `30382`
|
||||||
|
from one **hardcoded** provider pubkey + relay (auto-enabled), verify
|
||||||
|
signatures, and read only the `rank` tag. It ignores provider designation,
|
||||||
|
the `followers` / `reporters` / `muters` / `hops` tags, and the spec's
|
||||||
|
absence rules.
|
||||||
|
- `src/lib/relatr.ts` is a placeholder REST client, disabled (`RELATR_ENABLED =
|
||||||
|
false`); it was never finished and is the wrong shape (REST, not events).
|
||||||
|
|
||||||
|
## Gaps versus the specs
|
||||||
|
|
||||||
|
1. No **provider designation**: we neither read the viewer's own kind `10040`
|
||||||
|
nor let them choose providers, and we cannot show which provider a score
|
||||||
|
came from.
|
||||||
|
2. **Negative signals are ignored**: mutes (kind `10000`) and user-level reports
|
||||||
|
(NIP-56 `1984`) are not inputs to an author's score.
|
||||||
|
3. **No confidence gate**: a single follow instantly reads as `90`. A cluster of
|
||||||
|
accounts that only follow each other is indistinguishable from a well-connected
|
||||||
|
account. This is the biggest sybil hole in the current ladder.
|
||||||
|
4. **No trust-driven alerts**: the app filters content, but never tells a user
|
||||||
|
"someone you follow has been reported by trusted people."
|
||||||
|
5. **Display is a bare ladder**: no viewer choice in how (or whether) trust is
|
||||||
|
shown, and no distinct "flagged" state next to "verified" and "unknown".
|
||||||
|
|
||||||
|
## Ideas, highest value first
|
||||||
|
|
||||||
|
### 1. Make the NIP-85 consumer spec-correct
|
||||||
|
|
||||||
|
Replace the hardcoded single-provider lookup with a real consumer:
|
||||||
|
|
||||||
|
- Read the viewer's **kind 10040** designation (rows are
|
||||||
|
`["30382:<tag>", <provider pubkey>, <relay>]`) and resolve the provider and
|
||||||
|
relay per assertion type. Fall back to a configured "house" provider (the
|
||||||
|
community default) when the viewer has no designation or is logged out.
|
||||||
|
- Verify `event.pubkey == provider` **and** the signature before use (the
|
||||||
|
signature check already exists; the author check is the trust boundary).
|
||||||
|
- Read the normative tags: `rank` (0-100), `followers`, `reporters`, `muters`,
|
||||||
|
`hops`. Treat producer extensions as optional.
|
||||||
|
- Enforce the honesty rules the spec is emphatic about:
|
||||||
|
- an absent `hops` means "no known path", never `0`;
|
||||||
|
- absence of an assertion is **not** distrust;
|
||||||
|
- ranks from different observers or providers are **not** comparable;
|
||||||
|
- presence of an assertion is **not** endorsement (read the counts).
|
||||||
|
|
||||||
|
Sketch of the shape: `src/lib/nip85.ts` (designation parse, provider resolve,
|
||||||
|
assertion parse, verification) + `src/hooks/useTrustAssertions.ts` (batch
|
||||||
|
read via `querySkateContent`/relay hints, disabled when no provider).
|
||||||
|
`useRelatrScore.ts` becomes one configured provider behind that interface
|
||||||
|
rather than a special case. A Settings section ("Trust providers") shows the
|
||||||
|
active provider + relay, allows choosing the house provider or a personal one,
|
||||||
|
and can be switched off.
|
||||||
|
|
||||||
|
This alone turns trust from our fixed 0/30/60/90/100 into "whatever provider
|
||||||
|
the viewer chooses", with no new infrastructure.
|
||||||
|
|
||||||
|
### 2. Three buckets and a viewer display mode
|
||||||
|
|
||||||
|
Adopt a simple, honest presentation on top of whatever score source is active:
|
||||||
|
|
||||||
|
- **Verified / Unknown / Flagged**, with color **and** a glyph so the state is
|
||||||
|
legible without color. "Flashy intermediates" (highly verified vs verified)
|
||||||
|
were deliberately rejected — actionable, not graded.
|
||||||
|
- **Flagged = verified reporters >= threshold**, where "verified reporters" are
|
||||||
|
reporters who themselves clear the trust bar. Not a raw report count.
|
||||||
|
Show *who* reported, grouped as people you follow / verified / others, never
|
||||||
|
what the account "is".
|
||||||
|
- Let the viewer choose how it renders: number / level / word / **off**. "Off"
|
||||||
|
is a legitimate choice for people who do not want others graded; safety
|
||||||
|
warnings (flag/report) remain a separate channel and always show.
|
||||||
|
- Reuse the same ladder in the trust filter, author badges on the map/Lab/
|
||||||
|
marketplace, and the community-stats page, so a score reads identically
|
||||||
|
everywhere.
|
||||||
|
|
||||||
|
Where it lands: a small `lib/trustLadder.ts` (bucket + ladder helpers), a viewer
|
||||||
|
preference stored per account (the `useLocalStorage` pattern), and reuse of the
|
||||||
|
existing `Filter` UI. Content that is `Flagged` can keep our current soft
|
||||||
|
behaviour (deprioritize, badge, and feed the review queue) rather than hard
|
||||||
|
delete.
|
||||||
|
|
||||||
|
### 3. Trust-driven alerts for counterparties
|
||||||
|
|
||||||
|
The alert pattern is a strong fit for a marketplace of strangers and paid Lab
|
||||||
|
services:
|
||||||
|
|
||||||
|
- Compute "accounts in your network that trusted people report/mute" from the
|
||||||
|
provider's `reporters` / `muters` counts (or locally, from reports weighted
|
||||||
|
by reporter trust).
|
||||||
|
- Surface it as a dismissible banner plus a page of alerts, with actions
|
||||||
|
**ignore / unfollow / mute / report** and a short interaction history
|
||||||
|
(your follows, prior trades, prior reports). One row per account; a "new
|
||||||
|
since you last looked" marker.
|
||||||
|
- The posture is "inform, then let the user decide" — never auto-hide a seller
|
||||||
|
or coach on a single report. This complements the existing reputation cards
|
||||||
|
and the >=3-distinct-reporter auto-hide, not replace them.
|
||||||
|
|
||||||
|
### 4. Confidence-gated scoring
|
||||||
|
|
||||||
|
The single most important algorithmic idea from the specs: trust is
|
||||||
|
`influence = average x confidence`, with `confidence = 1 - rho^input` and each
|
||||||
|
edge weighted by the rater's own influence and an attenuation factor. A lone
|
||||||
|
endorsement scores near zero; breadth of already-trusted raters is what
|
||||||
|
accumulates. Defaults: `rho 0.5`, `attenuation 0.85`, follows `+1`, mutes and
|
||||||
|
reports negative.
|
||||||
|
|
||||||
|
Two ways to get it, in order of preference:
|
||||||
|
|
||||||
|
- **Consume it** from a provider (the house provider, or a personal one) — no
|
||||||
|
client cost, and the score is already published as a signed assertion.
|
||||||
|
- **Port it** into `cvm-server/` (it is ~a dozen parameters and a bounded
|
||||||
|
iteration) and publish our own assertions only if we decide to run a
|
||||||
|
provider. This keeps the heavy work off the phone while staying inside our
|
||||||
|
existing service.
|
||||||
|
|
||||||
|
Either way, the win is: stop treating one follow as trust. Our existing
|
||||||
|
per-account cap and activity weighting in `useSpotCommunityScore` are the same
|
||||||
|
instinct applied to content; this applies it to people.
|
||||||
|
|
||||||
|
### 5. Presets and negative signals as policy
|
||||||
|
|
||||||
|
- Expose scoring policy as named **presets** (Relax / Default / Strict) that
|
||||||
|
move the follower/muter/reporter cutoffs together, rather than hardcoded
|
||||||
|
constants. If we ever run a provider, persist presets server-side with an
|
||||||
|
append-only change history and show the active one in Settings.
|
||||||
|
- Add **mutes (10000)** and **user-level reports (1984)** as negative edges —
|
||||||
|
weighted, so a report from a trusted skater counts and a report from a
|
||||||
|
brand-new account does not. We already gather reports; this is where they
|
||||||
|
should feed a person-level signal, not only content moderation.
|
||||||
|
|
||||||
|
### 6. Vocabulary and honesty copy
|
||||||
|
|
||||||
|
Borrow the precise wording these specs insist on, because it matches our own
|
||||||
|
convention ("Conditions · weather only", "attested, not verified"):
|
||||||
|
|
||||||
|
- counts are "verified followers / reporters / muters" (trusted raters only);
|
||||||
|
- "No one in your network has vouched for this account yet." for Unknown;
|
||||||
|
- "presence is not endorsement", "absence is not distrust";
|
||||||
|
- "scores are point-of-view-relative" — always attribute a score to a provider
|
||||||
|
and a perspective, never present it as an objective fact.
|
||||||
|
|
||||||
|
## What we are not doing
|
||||||
|
|
||||||
|
- Standing up the reference infrastructure (graph store + cache + search engine
|
||||||
|
+ Java worker + Python orchestrator + bundled relays). It contradicts the
|
||||||
|
project's zero-server stance for this feature and is unnecessary if we consume
|
||||||
|
a provider. At most, a provider we operate would live in `cvm-server/`.
|
||||||
|
- Adopting an external search relay as a dependency. The portable idea is
|
||||||
|
ranking our own search and Lab results by the viewer's trust, which the
|
||||||
|
`trustFilter` already gestures at.
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
- **Licensing.** The reference implementation, server and client are
|
||||||
|
AGPL-3.0. Reading the specifications and reimplementing the algorithm is fine;
|
||||||
|
copying their code into our shipped client would pull AGPL obligations onto
|
||||||
|
SkateSpots. Prefer the specs and our own implementation.
|
||||||
|
- **Spec status.** GrapeRank and Trusted Assertions are pre-NIP drafts; NIP-85
|
||||||
|
itself is published. Code against NIP-85 (`10040` / `30382`) and treat
|
||||||
|
GrapeRank as one producer.
|
||||||
|
- **Privacy.** The current client-side ladder fetches only the viewer's own
|
||||||
|
follows and keeps the graph local. Consuming a provider discloses "this
|
||||||
|
viewer designates this provider, score this subject"; operating a provider
|
||||||
|
means someone holds the whole graph. Whatever we pick must be disclosed on
|
||||||
|
`/privacy`, the way geocoding and the AI proxy already are.
|
||||||
|
- **Existing code.** `lib/relatr.ts` should be retired (REST, disabled,
|
||||||
|
unconfirmed); `useRelatrScore.ts` is the seed to grow from.
|
||||||
|
|
||||||
|
## Recommended first slice
|
||||||
|
|
||||||
|
1. **Spec-correct NIP-85 consumer** (`src/lib/nip85.ts` +
|
||||||
|
`src/hooks/useTrustAssertions.ts`), with kind-10040 resolution, a configured
|
||||||
|
house provider, author + signature verification, and the four normative tags.
|
||||||
|
Keep `useRelatrScore` working by expressing it as a provider.
|
||||||
|
2. **A Flagged bucket** fed by verified reporter counts, shown with a glyph in
|
||||||
|
the trust filter and on author badges, with a "who reported" breakdown.
|
||||||
|
|
||||||
|
This reuses code we already have, needs no new infrastructure, and improves the
|
||||||
|
map, Lab, marketplace and moderation without changing the scoring model. The
|
||||||
|
confidence model (idea 4) and alerts (idea 3) are the natural next steps.
|
||||||
|
|
||||||
|
## Touch points
|
||||||
|
|
||||||
|
| Concern | File(s) |
|
||||||
|
| --- | --- |
|
||||||
|
| NIP-85 designation + assertion parsing | new `src/lib/nip85.ts` |
|
||||||
|
| Assertion read hook (batch) | new `src/hooks/useTrustAssertions.ts` |
|
||||||
|
| Current single-provider score | `src/hooks/useRelatrScore.ts` |
|
||||||
|
| Placeholder REST client (retire) | `src/lib/relatr.ts` |
|
||||||
|
| Local ladder (keep, or blend) | `src/hooks/useTrustScore.ts`, `src/hooks/useBatchTrustScores.ts` |
|
||||||
|
| Bucket/ladder helpers + viewer mode | new `src/lib/trustLadder.ts` |
|
||||||
|
| Filter levels | `src/lib/trustFilter.ts`, `src/components/TrustFilter.tsx` |
|
||||||
|
| Author badges | map / Lab / marketplace author surfaces |
|
||||||
|
| Alerts | new alerts page + banner; report hooks already exist |
|
||||||
|
| Policy presets | Settings; server-side only if we run a provider |
|
||||||
|
| Privacy disclosure | `/privacy` |
|
||||||
Loading…
Reference in a new issue