Fleetmesh fleetmesh

The fleetmesh Protocol · part 6 of 10

Reputation

The five-class deterministic severity ladder, sovereign subjective weighting, and adverse-only reporting.

Severity ladder

Deterministic Severity Classification

The remedy for a given breach is deterministic and stated in the grant before the breach happens. This is not a courtesy: an operator who can choose the punishment after seeing the offender is doing moderation, and moderation does not federate. A ladder fixed in advance can be checked. The two parties check it against the grant they both hold. Anyone else checks it against the published evidence, and against the commitment that grant was published under.

  • S0 drift Within 110% of every limit, receipts agree with measurement. The normal state of a busy peer. no action · counts toward the clean-window streak
  • S1 overrun Sustained traffic exceeding a limit where receipts accurately report the excess. The peer is honest but unshaped (e.g. backfill burst or temporary traffic spike). limit × 0.5 · notice first · no publication on the first occurrence
  • S2 misreport Receipts diverge beyond tolerance, fail to arrive during active egress traffic, or declared metadata (version, class, policy, or coverage) contradicts direct observation. Also triggered by false operator independence claims (e.g. running shadow nodes to subvert dual-mediation). Structural deception or withholding required receipts constitutes an actionable misreport. (For dormant, zero-egress leaf nodes, see § Partition tolerance). tier → probation · limits to the probation ceiling · report published after notice
  • S3 abuse Traffic outside granted scopes, malformed event floods, excessive duplicate ratios, or repeated hop-limit violations. limits → 0 for 24h · report published immediately with evidence
  • S4 malice Forged signatures, corrupted receipt chains, transport key impersonation, deliberate shard poisoning, or grant evasion routing. revoked · grant deleted · report published · peers re-evaluate
The ordering is the argument

S1 overruns rank below S2 misreports by design. A peer that exceeds its capacity limit but reports accurately receives lighter penalties than a peer that underreports traffic. Capacity overruns are operational issues. Falsified reporting destroys the accounting foundation upon which all bilateral peering depends. The ordering is a floor and not a reordering: a peer that overruns grossly and lies about it keeps the S3 the overrun earned. Were the misreport to replace the class rather than raise it, lying would be the cheaper of the two, in the one mechanism whose whole job is to make it expensive.

The report — kind 30802

Addressable, d = subject pubkey, one per (reporter, subject), replaced as the relationship evolves. It carries the reporter's current verdict, not a log; the history is reconstructable from the reporter's own published sequence if anyone cares to keep it.

kind 30802 — conformance report

{
  "kind": 30802,
  "pubkey": "<reporter>",
  "tags": [
    ["d", "<subject pubkey>"],
    ["outcome", "S2"],                // S1 | S2 | S3 | S4 — adverse only
    ["grant", "<id of the grant THIS reporter issued to the subject>"],
    ["windows", "496380", "496389"],  // range this verdict covers
    ["observed", "events_in", "3312", "3000"],   // measured, limit
    ["claimed",  "events_in", "2900"],
    ["evidence", "receipt", "<sha256 of the subject-signed receipt, carried in content>"],
    ["evidence", "envelope", "<sha256 of a delivery envelope the subject signed a hop on>"],
    ["evidence", "event", "<id of an out-of-scope event the subject itself signed>"],
    ["notice", "<thid of the complaint/1.0 exchange>", "1787003600"],
    ["expiration", "1789600000"]
  ],
  // Evidence travels in the content, because no relay serves a receipt or an
  // envelope: a reference to one resolves to nothing anywhere. A reader hashes
  // the artefact, compares it to the tag above, and checks the subject's
  // signature on it. An "event" entry may stay a bare reference: it is public.
  "content": {
    "note": "Divergence on events_in across ten consecutive windows.",
    "evidence": { "<that sha256>": { … the receipt, whole, with the subject's sig … } }
  }
}
Evidence or nothing

A report MUST name, in its grant tag, a grant issued directly by the reporter to the subject. Without an established bilateral grant, no valid finding can exist. Nodes MUST discard any report whose grant tag does not resolve to a valid event published by the reporting node.

Every report MUST include at least one evidence tag referencing a cryptographic artifact signed directly by the subject. Acceptable evidence is a receipt, a delivery envelope the subject signed a hop on, or an event the subject itself signed. The first two are private DIDComm bodies that no relay serves, so they travel inline: the report's content is {"note": …, "evidence": {<sha256 of the canonical artefact>: <artefact>}}, and a reader hashes it, compares it to the tag and checks the subject's signature, resolving nothing. Evidence pins the subject's half of a disagreement and no more; the measurement is the reporter's own word, weighted by its standing. Reports lacking subject-signed evidence carry zero weight across the network. Repeated publication of unevidenced claims is an actionable misreport.

Reports regarding user content remain on standard NIP-56 Kind 1984 events (accepted by Blossom servers under BUD-09). Content reports inform author grants. In contrast, Kind 30802 reports govern node protocol conduct exclusively. User moderation complaints and operator protocol violations belong to separate domains and MUST NOT share a scoring function.

Partition tolerance — active vs. inactive silence

Under the Fischer-Lynch-Paterson (FLP 1985) theorem, asynchronous networks cannot reliably distinguish a dead or partitioned node from an intentionally silent one. A naive application of "silence is an S2 misreport" would penalise mobile leaf nodes entering deep OS battery sleep or traversing subway tunnels. The protocol avoids this by differentiating physical egress state during the evaluation window:

Observed State Physical Traffic Receipt Status Classification Protocol Remedy
Active Silence > 0 events or bytes measured Missing or unlinked S2 Misreport, at least The measured traffic is still evaluated against the limits, with S2 as a floor and not a ceiling: a peer that overran grossly and said nothing keeps the class the overrun earned. At S2 the tier falls to probation and the limits with it. No report is published — the subject signed nothing that window, so there is no artefact of its own to cite and no reader could check either half of the claim. The remedy is local and complete, and standing/1.0 still carries the streak to anyone who asks.
Inactive Silence 0 events and 0 bytes measured Missing / dormant Graceful Partition Not evaluated · streak neither advanced nor broken · no report filed

A returning leaf does nothing special. A dormant window was not evaluated, so there is no penalty to lift and no sequence to reconcile: the next active window starts a fresh receipt chain at seq 0, exactly as every window does. The grant's NIP-40 expiration already bounds how long a leaf may stay away — one that returns after it simply re-peers — so no lease is needed. Nor does a mediator publish anything about a leaf's absence: the kind 21801 heartbeat names no peer (§ Discovery), and § Privacy rules would rather a phone's waking hours were not announced at all.

Trust weighting

Sovereign Trust Weighting & Local Enforcement

Reports are inputs, not verdicts. A node computes its own view of a subject from its own observations plus other nodes' reports, and weights each report by how much that reporter's own grant from this node is worth. You get to influence me exactly as much as I have already decided to trust you.

local view, computed independently by every node

w(r)   = tier_weight(my grant to r) × age_factor(r) × (1 - false_report_rate(r))

         tier_weight:  probation 0.00   member 0.25   trusted 0.60   anchor 1.00
         age_factor:   min(1, clean_windows(r) / 168)  // 168 default hourly windows (1 week streak)
         false_report_rate: (unsupported + refuted reports by r) / (reports by r I evaluated)
                            unsupported: evidence does not resolve, or the subject did not sign it
                            refuted:     evidence verifies, but the classifier run over it does
                                         not produce the outcome the report claims
                            defined as 0 when evaluated is 0. Both are decided by the reader
                            alone, from data it already holds.

view(x)  = the most severe S in {S1..S4} such that

               Σ w(r)  ≥  θ      over reporters r whose finding on x is at least S

           or own_severity(x), whichever of the two is more severe.

θ = 1.0      one anchor-tier reporter, or two trusted, or four members.
constraint  view(x) may only lower a starting grant, never raise one.

Dual-Track Standing: Transit vs. Compute Separation

To prevent cross-contamination across heterogeneous hardware (e.g. high-bandwidth relays running on low-power ARM devices versus high-CPU compute workers with minimal relay bandwidth), standing is strictly partitioned into two independent Subjective Logic domains:

Dimension Evaluation Domain Evidence & Metrics Failure Remedy
viewtransit(x) ["topic", "transit"] Negentropy range sync fidelity, bandwidth limit compliance, duplicate event ratios (≤ 0.15), and hop path verification. AIMD bandwidth throttling (×0.5, ×0.25), connection pruning, or relay peering revocation.
viewcompute(x) ["topic", "compute"] WASM bitwise execution determinism on sample re-runs, fuel estimation accuracy, job SLA compliance, and settlement preimage delivery. Order book price derating, dual-execution probation quarantine, or compute ask disqualification.

Domain Isolation: A seller who misreports a WASM compute result hash incurs an S2 finding scoped strictly to topic: compute, immediately downgrading its compute standing without disrupting an honest, non-divergent transit peering. Conversely, transient bandwidth overruns on a high-traffic relay do not penalize its verified compute worker track record.

A threshold rather than a weighted mean, deliberately. Severity is an ordinal scale. Its remedies — ×0.5, ×0.25, zero, revocation — are nothing like evenly spaced. Averaging S1 against S3 to get “S2” would assert a spacing the ladder denies. Asking instead "how much weight stands behind a finding at least this severe" needs no arithmetic on the scale itself, and reads the way an operator would reason anyway.

Three properties fall out of that shape, and each is load-bearing:

sybils weigh nothing
A thousand newly spawned nodes reporting the same subject contribute zero total weight (w(r) = 0). Probation weight is zero and age_factor starts at zero. Generating identities does not produce influence. Reputational weight requires sustained, verified conformance over time, which cannot be parallelized.
trust cannot be inflated
Third-party reports may only lower standing. A collusive ring of nodes vouching for each other produces zero standing increase. Reputational standing arises exclusively from direct local observation of clean windows or explicit operator grants. Hearsay is admissible only as an adverse signal, never as an endorsement.
reporting has a price
false_report_rate means a reporter stakes its own standing. File a finding I later contradict with my own measurement and your weight drops for everything you say afterwards. This is the reverse-reputation principle applied to the reputation system itself — you are responsible for what you publish about others exactly as you are for what you send them.

Asking, now that reading is impossible

Because grants operate as blinded commitments, nodes cannot discover stranger standing via public subscriptions. Nodes query peers directly over standing/1.0. The queried peer evaluates its local policy before responding. Two protocol query modes are defined:

ask → tell
"What do you make of X?" answered with a tier, or with silence. This is selective disclosure, not zero knowledge. The answer is exactly the ranking that was being kept off the relay, handed to one party under a policy instead of to everyone by default. That is the whole of the improvement, and it is worth being precise that no proof system is involved.
ask → decline
The expected answer for a stranger, and it MUST NOT be treated as adverse. A node that reads refusal as a signal has rebuilt the pressure to disclose that this design removes.
Why not a range proof

The obvious cryptographic answer — prove "X is at or above member" without revealing the tier — does not survive the domain. There are five tiers, so three questions binary-search the exact value, and the natural defence of rate-limiting the questions is the grant system, which is the thing being protected. A proof that leaks its witness in three queries is ceremony. Selective disclosure with a policy is the honest mechanism at this size, and pretending otherwise would be worse than publishing.

Where zero knowledge would actually earn its place

Not per-peer predicates over five values, but aggregate claims over a set the prover keeps private — where the witness is membership, or the domain is large enough to hide in. Four that would be genuinely useful, and none of which the current design can express:

  • "At least three of my trusted-tier peers hold X at member or above" — without naming which three.
  • "X's clean-window streak with me is at least 100" — a domain in the hundreds, where a range proof hides something real.
  • "I hold no grant to X above probation" — a way to warn without accusing.
  • "I authored a kind 30801 whose d blinds to X" — without saying which. This is the one that would make an anonymous report admissible, and it is the reason a ring signature cannot do the job; see below.

Bulletproofs fit the shape: transparent setup, proofs under a kilobyte, and they work over secp256k1, which is already the node key's curve. None of the four is implemented, and the last of them is what the next section turns out to need.

Reporting has a cost, and only one half of it is fixable

Nodes silently drop abusive peers rather than reporting them, because a report invites retaliation and the benefit of filing it is shared with everyone. That is the second-order public goods problem, and only one half of it has a defensible remedy.

Objective evidence immunity. A reporter is immune when its evidence verifies and the finding follows from that evidence. Both halves are objectively checkable by the reader: the classifier is deterministic, so anyone holding the grant and the cited evidence recomputes the outcome for themselves and needs nobody's agreement to do it.

Immunity on verification alone would make a false report free. A reporter could cite a genuine subject-signed receipt, claim S3 where the arithmetic gives S1, and pay nothing — which contradicts the price § Attack surface says a reporter pays for exactly one damaging false report. Narrowing immunity to verifies and follows is what makes that claim true.

Honest disagreement survives this. Two nodes that carried different traffic for the same subject reach different outcomes and neither is refuted, because each cited its own evidence and each one's arithmetic follows from what it cited. Refutation is not "you disagree with me"; it is "your own evidence does not say what you said it says."

Anonymous reporting does not work here, and the reason is structural. A ring signature over "every grant issuer of this subject" needs that set to be enumerable. Blinded d tags exist precisely so that it is not — see § Grants & receipts. The two requirements are also contradictory on their face: a report is admissible only because its grant tag resolves to an event the reporter published, and that check is exactly what identifies the reporter. A construction satisfying both would have to prove "I know a key whose ECDH with this subject derives the d of a kind 30801 I authored" without revealing which — a zero-knowledge proof, listed as the fourth application above rather than pretended into existence here.

The nearest built thing is nostr-veil, and it is worth saying why it does not fit rather than leaving a reader to wonder whether it was missed. It wraps each contribution to a NIP-85 score in an LSAG ring signature over a published circle. A verifier learns that some member of the list signed and that no member signed twice. That is the shape of the first application above, with one difference that decides it. Its ring is public by construction: the member list travels in the event, and its own threat model says so. The ring this section needs is the set of a subject's grant issuers, which blinded d tags exist to keep unlistable. It also binds a contribution to circle membership and not to a grant, so it cannot make a report admissible, and its aggregate is signed by whoever aggregated. Where the two projects do meet is on the consumer side. Its relay scores and the kind 30385 a node projects (Bridge 2 in § Ecosystem Bridges) land in the same kind, and a client's kind 10040 chooses among both.

Nor is reporting subsidised. Awarding a reporter standing for filing a finding would contradict the rule that standing comes only from observed conformance, and would make report-filing the cheapest way to accelerate age_factor — the one input the sybil argument needs to be unforgeable. Immunity removes the cost of honest reporting, which is the whole of what is defensible. A bounty would create a reason to go looking.

What this leaves unsolved

Retaliation. A subject that quietly downgrades its reporter's grant faces nothing, and because grants are encrypted to their subjects, no third party can observe that it happened. Immunity protects a reporter's standing in everyone else's arithmetic; it does not protect the relationship it reported on. That residual is named in § Attack surface rather than papered over.

No global state

There is no aggregate score, no consensus round, and no canonical answer to "is this node good". Two nodes with different peer sets will reach different views of the same subject and both are correct, because a view is a statement about a relationship. What federates is the evidence, and every node does its own arithmetic on it.

Operators import external trust opinions deliberately by issuing anchor tier grants. The operational consequence is explicit: an anchor grant assigns that peer a weight of 1.0 in the local trust function.

Bootstrapping

A brand-new node has no observations and no weighted reporters, so its view of everyone is neutral and it grants everyone probation. That is the correct behaviour and it is also the slowest possible start. An operator in a hurry MAY seed the table by granting trusted to nodes it knows out of band. That is a human decision, recorded as a signed event, and reversible. It is not a protocol feature and it is not automated.