Fleetmesh fleetmesh

The fleetmesh Protocol · part 5 of 10

Grants & receipts

Published budget allocations, signed receipt chains, and bilateral accounting reconciliation.

Grants & receipts

Bilateral Budgets & Accounting Receipts

Traditional private rate-limiting fails to educate peers. A peer discovers limits only by hitting connection drops, cannot distinguish throttling from server failure, and has no mechanism to adjust its traffic proactively.

Fleetmesh inverts this model through bilateral accounting. Every node publishes a signed budget allocated to each peer. Peers enforce those limits locally and account for consumption in signed receipts. Issuers verify traffic independently: comparison evaluates whether a peer's self-reported accounting matches direct physical observation. A limit a subject can read is an enforceable contract. Blinded commitments keep these budgets private between peering pairs (§ The grant).

grant issued peer self-limits signed receipt issuer measures kind 30801 token bucket hash-chained independently clean window finding S1–S4 limit + step limit × k, k < 1 next window’s grant — published, signed, auditable
Additive increase, multiplicative decrease. The shape is deliberately TCP's: a peer that behaves gains capacity slowly and predictably; a peer that does not loses it fast. The loop is stable without coordination, converges without a global view, and never needs two nodes to agree on anything except what they each published.

The grant — kind 30801

Addressable, one per (issuer, subject, direction). The event is public; the grant inside it is not. What an observer sees is a commitment and a schedule. What the subject sees, because it alone can decrypt the content, is the budget it has to enforce on itself.

Grants are never broadcast in plaintext. The case for public tables is that peers need to read budgets and third parties need to audit disputes; both needs are met under blinded commitments without broadcast. What broadcast would add is exposure of the complete peering graph, participant standing and downgrade timestamps — and public downgrades turn honest local capacity management into public conflict.

Why this is not a privacy nicety

A design that requires an operator to publicly brand a peer probation is a design that will be quietly under-used. Operators know the people they peer with. Making an honest downgrade socially expensive means fewer honest downgrades, and the reputation system degrades precisely where it is most needed. Keeping rankings private is what makes accurate ranking cheap.

What stays visible, and what does not

Visible to anyoneVisible only to the subject
That an issuer published a grant. The commitment. The window length and epoch, so anyone can tell when a window closes. The expiration. Every limit, the tier, the scope filters, the enforcement schedule — the whole substance of the grant.

The d tag is blinded to protect the network graph from passive traffic analysis. Publishing subject pubkeys in plaintext would turn the addressable index into an open directory of active peerings.

the addressable identifier, computable only by the pair

shared  := ECDH(issuer_sk, subject_pk)        // the same value either side derives
d       := hex(HKDF-SHA256(ikm  = shared,
                           salt = "fleetmesh/grant/1",
                           info = "fleetmesh/grant-tag/1", len = 32))

commit  := SHA256(blinding || canonical(grant))  // binding, hiding, opens later

HKDF rather than a bare HMAC, and both stages separated by a domain string
rather than the RFC 5869 zero salt. An ECDH output is uniform over curve-point
x-coordinates, not over 256-bit strings, which is the input extract exists for;
and NIP-44 derives its own conversation key from this same secret in the same
shape, so one event does not use two disciplines over one value.

Kind 30811 blinds a compute receipt identically under salt "fleetmesh/job/1" and
info "fleetmesh/job-tag/1". Only the labels distinguish them, so the labels are
normative and live in constants.json. vectors/blinding.json pins both.

The blinding in the commitment is 32 bytes drawn fresh from a secure random
source for every commitment. It is never derived from the pair's keys or the
epoch and never reused. The grant it hides is mostly guessable from this
document. Hiding rests on the factor alone. constants.json states this under
commitment.blinding_factor. Its crypto block states the primitives everything
above assumes.

Why not a Pedersen commitment

A Pedersen commitment's distinguishing property is that two of them can be added without opening either. Nothing here adds a grant commitment to anything. What the envelope needs is binding, hiding, and opening later by the one party that can already decrypt the payload — which a salted hash gives, using a primitive every implementation already has.

Specifying Pedersen properly would mean pinning a second generator with no known discrete log relative to G, a mapping from the committed bytes to a scalar, and a point encoding. None of those is stated anywhere in this document, and inventing them for a property nothing uses is the kind of ceremony § Trust weighting declines elsewhere. pedersen-secp256k1/1 is reserved as a scheme identifier against the day a mechanism genuinely needs the homomorphism — aggregate proofs over clean-window streaks being the obvious candidate. Until then it is a name with nothing behind it, and saying so is better than implying otherwise.

The epoch is the clock, not a counter. epoch = floor(unix_seconds / window_seconds), and the window it names is [epoch × window, (epoch+1) × window). Both parties compute it from the grant they hold and never exchange it. Changing a grant's window length starts a new numbering at the next boundary under the new length.

A subject finds its own grant by computing the same d and querying for it. No third party can compute it, and no observer can tell which of an issuer's grants belongs to whom. What remains inferable is an issuer’s peer count, which is a far smaller disclosure than its peer list.

kind 30801 — the public envelope

{
  "kind": 30801,
  "pubkey": "<issuer>",
  "tags": [
    ["d", "<blinded pair identifier>"],
    ["commit", "<SHA256(blinding || canonical grant)>"],
    ["scheme", "sha256-blind/1", "nip44/2"],
    ["window", "3600"],               // public: anyone may know when a window closes
    ["epoch", "496389"],       // floor(unix / window)
    ["expiration", "1787600000"]
  ],
  "content": "<NIP-44 ciphertext of the grant below, to the subject>"
}

the grant itself — encrypted, read by the subject alone

{
  "tags": [
    ["subject", "<subject pubkey>", "node"],   // node | author
    ["tier", "member"],               // probation | member | trusted | anchor | revoked

    ["limit", "conn_rate",    "12"],
    ["limit", "req",          "600"],
    ["limit", "events_in",    "3000"],
    ["limit", "bytes_in",     "33554432"],
    ["limit", "bytes_out",    "268435456"],
    ["limit", "blob_bytes",   "0"],
    ["limit", "sync_ranges",  "400"],
    ["limit", "errors",       "40"],
    ["limit", "dup_ratio",    "0.15"],

    ["scope", "push", "{\"kinds\":[0,1,3,7,10002],\"#t\":[\"bristol\"]}"],
    ["scope", "pull", "{\"kinds\":[0,1,3,7,10002],\"#t\":[\"bristol\"]}"],

    ["schedule", "1.1", "S1"],        // >110% of any limit, receipts honest
    ["schedule", "2.0", "S2"],
    ["schedule", "5.0", "S3"],
    ["receipt", "600", "1048576"],    // send one every 600s or 1 MiB, whichever first
    ["settlement", "none"],           // reserved
    ["blinding", "<32 fresh random bytes; opens the commitment in the envelope>"]
  ]
}

dup_ratio deserves a note: it is the fraction of delivered events the receiver already held. A peer with working negentropy reconciliation sits near zero. A peer at 0.9 is either broken or replaying, and the distinction between those two is exactly what the receipt comparison resolves. The ratio is priced in the window like any limit, as dups_sent / events_in against the ceiling through the same class function, with no divergence term because only the receiver knows what it already held. It has no line in the grant, so the additive-increase step never scales it. Seven duplicates in eight is an S3.

The receipt

The subject maintains a token bucket mirroring each limit and, at the cadence the grant specifies, sends a signed receipt over budget/1.0. Receipts within a window form a hash chain, so a subject cannot rewrite an earlier claim after learning what the issuer saw; the issuer keeps only the head.

The canonical form is the one every digest in this document is taken over, stated once in constants.json under canonical: the receipt body as JSON, UTF-8, object keys sorted ascending by code point, no whitespace between tokens. prev is the SHA-256 of the previous receipt body in that form, thirty-two zero bytes for seq 0, and the chain head is the SHA-256 of the last. Counters are integers, so nothing about number formatting is left to the implementation. The same form is what canonical(grant) means in the commitment above.

budget/1.0/receipt — body

{
  "grant": "<event id of the kind 30801 in force>",
  "window": 496389,
  "seq": 7,
  "prev": "<sha256 of the canonical form of receipt seq 6>",
  "counters": {
    "conn_open": 2, "req": 141, "events_in": 802, "bytes_in": 2216041,
    "bytes_out": 19883, "blob_bytes": 0, "errors": 1, "dups_sent": 44,
    "sync_ranges": 61
  },
  "forwarded_for": ["did:nostr:<origin>"],
  "at": 1787003600,
  "sig": "<BIP-340 by the subject node key over this receipt's chain link>"
}

Counters are cumulative within the window and strictly monotonic. A window prices each limit against the counter of the same name. Where the names differ, counter_of in constants.json says which counter: conn_rate is priced against conn_open, the sessions the subject opened in the window, on whatever transport carried them. Two ceilings left the table because nothing could count them. conn_max is a gauge and a cumulative receipt cannot carry one. sub_max counts subscriptions on a relay face, which peers never open. Both are advisory now, local ceilings a node may enforce and no grant sells. The forwarded_for field lists upstream origins whose traffic the subject forwarded during the window. This mirrors the delivery envelope's path tag. It makes forwarding obligations verifiable without requiring manual envelope reconstruction.

The window is evaluated against the most recent receipt the issuer received for it. Counters are cumulative, so that receipt already states the subject's whole claim, and the chain makes it as binding as any closing statement could be. Traffic the issuer measured after it is simply unaccounted-for traffic: it is compared against that receipt's counters and priced by divergence, exactly as a false receipt would be. Nothing moving after the last receipt is a clean window. This is what lets a leaf sync for ten minutes, send two receipts and lock its screen without producing a finding; a whole window of measured traffic with no receipt at all is the active silence § Severity ladder prices as an S2.

budget/1.0/close is therefore optional. It is a final receipt carrying the window's last counters; one arriving within the grace period (one tenth of the window) is accepted as final, and one arriving later is evaluated like any other receipt. The issuer answers every receipt privately with a receipt-ack carrying the head it now holds and one of accepted, divergent or final, so a subject always knows which receipt stood as its claim. Clean windows publish no public events.

Why clean windows are not published

A node MUST NOT publish a conforming report at window close, or any other positive attestation. Publishing subject pubkeys hourly would expose the complete peering graph with standing scores attached. And positive reports are arithmetically inert under the trust weighting model (§ Trust weighting): broadcasting them leaks bilateral topology without adding anything a reader can act on.

Clean-window streaks remain private bilateral state. Nodes disclose streak counts on request over standing/1.0 subject to local policy. Verifying streak thresholds without disclosing exact durations is a designated application for aggregate zero-knowledge proofs (§ Trust weighting).

Consequently, only adverse reports are published. An adverse report must identify its subject, so it names the offending node explicitly. Because adverse findings are exceptional rather than routine, peering graph exposure occurs only when a node violates protocol rules.

Window arithmetic

evaluated by the issuer at window close, per limit ℓ

overrunℓ    = max(0, measuredℓ / limitℓ - 1)
divergenceℓ = |measuredℓ - claimedℓ| / max(measuredℓ, floorℓ)

severity   = max over all ℓ of  class(overrunℓ, divergenceℓ)

clean      : strainℓ = min(1, measuredℓ / limitℓ)
             if measured and strainℓ = 0 and limitℓ > openℓ
               limitℓ ← max(openℓ, limitℓ × 0.90)
             else
               limitℓ ← limitℓ + tier_baseℓ × max(0.02, 0.10 × strainℓ)
S1         : limitℓ ← max(probationℓ, limitℓ × 0.50)
S2         : limitℓ ← max(probationℓ, limitℓ × 0.25) ; tier ← probation
S3         : limitℓ ← 0 for 24h, then resume at probation
S4         : tier ← revoked ; grant deleted ; report published

then, always : limitℓ ← min(tier_maxℓ, limitℓ)  at the tier the branch returns

The last line is not decoration. A grant MAY set any limit lower than its tier ceiling and MUST NOT set one higher, and that has to hold for a remedy as much as for a first issue. It binds hardest at S2, which is the one branch that lowers the tier. A quarter of any higher tier's limit is still far above what probation allows, so the ceiling is what decides and the quartering never gets to. S2 does not leave a peer a quarter of what it had. It makes it a stranger again.

Wood is laid down where the strain was

A grant opens below the ceiling of its tier and the clean branch grows it in proportion to what the closed window actually carried. Both halves are needed and neither works alone.

Without the first there is no growth at all. A grant that opens at the ceiling makes the additive increase a no-op — min(ceiling, v + step) is the ceiling when v already is it — so every capacity is issued whole on the first day and can only afterwards be eroded, and the increase does something only as a recovery ramp after a fault. Volume opens at a quarter. The structural limits — connection rate, requests, sync ranges, errors — open at the ceiling, because a peer needs enough plumbing to demonstrate anything at all. What is earned is volume, never the right to open a session.

Without the second, growth is priced in windows rather than in work. A flat step gave a peering that moved one event an hour exactly what it gave one running at its limit, so the cheapest route to a large grant was to be present and idle. The step is scaled by use now, with a floor: a quiet peering still creeps upward, because a tree does grow without wind and it grows tall and slender and cannot stand. At the tier ceilings, a peering carrying its full limit reaches that ceiling in eight windows; one carrying almost nothing takes weeks, which is the point.

An evaluation that offers no measurement gets the floor step and never the full one. Absent evidence of load is not evidence of load. The one thing allowed to skip the earning is a person: § Trust weighting already says an operator MAY seed a tier for a node it knows out of band, recorded as a signed event and reversible, and that seeding opens at the ceiling.

And taken back where it went unused

The pair to growth, and the reason growth may be allowed to reach a ceiling at all. A limit moved up on a clean window and down on a fault and never decayed, so capacity earned in one busy week sat there a year later as surface nobody was watching.

Two shapes and one floor. A limit that a measured window carried nothing for, sitting above where a peering opens, gives back a tenth of itself instead of taking the floor step — so a busy peer that never moves a blob returns its blob budget while its event budget stays where it earned it. And a peering whose windows close with nothing measured at all, for decay_idle_windows in a row, has its whole grant decayed once, and again after each further run of them.

The floor is the opening limits of the tier the grant names. Disuse returns a peer to where a stranger starts and never below it: what it has not used it gives back, what every peering is given it keeps. The structural limits open at the ceiling, so they never decay at all — an error budget that wasted away would trip on the first bad hour.

A limit that measured zero is owed nothing, so at or below the opening value it is left exactly where it is rather than taking the floor step. Stepping up there and decaying back is a flutter, and every crossing costs the subject an amend. No measurement is not a measurement of zero, though: an evaluation offering no counters gets the floor step and no decay, in either direction, for the same reason an absent claim is the empty claim rather than an accusation. A decay is amended with cause: disuse: it is not a finding, not a shed and not a person. Nothing happened, which is the whole of the reason.

The pieces the arithmetic needs and the prose kept implying

Writing conformance test vectors required formalizing three core evaluation components: the class() classifier, per-tier limits (tier_max and probation), and divergence tolerances for S2 findings. These constants now live in constants.json and are bound within the version digest.

class(overrun, divergence) → severity, evaluated per limit

by_overrun = S3 if overrun ≥ 4.0 else
             S2 if overrun ≥ 1.0 else
             S1 if overrun ≥ 0.1 else  S0

if divergence > 0.10  → worse(by_overrun, S2)   // a floor, never a ceiling
otherwise             → by_overrun

A misreport is a floor of S2 and not a ceiling. Returning S2 the moment
divergence crossed its threshold priced a peer that overran four times over
and lied about it below the same peer reporting it honestly, because honesty
reaches S3 on overrun alone. Lying must never be the cheaper option.

dup_ratio is evaluated in the same window and is not a counter. It has no line
in a grant, so nothing scales it, and its ceiling is 0.15 at every tier. Only the
receiver knows what it already held, so there is no claim to compare and no
divergence term: class(dups_sent / events_in / 0.15 - 1, 0). Under the divergence
floor a ratio is noise and is not evaluated.

A window's severity is the most severe across all its limits. S4 is never
reached by arithmetic: it is forgery or impersonation, found structurally.

Watch the units. A schedule tag is a ratio of the limit — ["schedule","1.1","S1"]
means 110% — while overrun is measured/limit - 1. A schedule ratio r is an
overrun threshold of r - 1. Reading one as the other is the likeliest
implementation bug in this section.
ceilingprobationmembertrustedanchor
conn_rate41248192
req6060024009600
events_in30030001200048000
bytes_in335544333554432134217728536870912
bytes_out2684354526843545610737418244294967296
blob_bytes16777211677721667108864268435456
sync_ranges4040016006400
errors440160640

A grant MAY set any limit below its tier ceiling and MUST NOT exceed it. Additive increase raises limits toward tier ceilings in increments of ten percent of the ceiling. Approximately ten consecutive clean windows restore a throttled limit to its maximum. Penalties floor at the probation tier rather than zero, ensuring S1 overruns remain recoverable. The dup_ratio threshold is identical across all tiers: duplicate efficiency is an invariant data quality metric rather than a tier-scaled capacity.

The floorℓ parameter (fixed at 5% of the limit) prevents small absolute sample counts from triggering false divergence findings. Clock skew is managed by applying a grace period equal to one-tenth of the window length and requiring NTP-disciplined clocks. Clock drift exceeding 60 seconds produces unearned divergence. An issuer MUST deliver a complaint/1.0/notice and wait one window before publishing an S1 or S2 report against a reachable peer; S3 and S4 MAY be published immediately. See § Control plane.

A node says what it used before it goes

A node that stops mid-window leaves its issuer with traffic measured and no receipt, which is active silence and an S2 floor. S2 lowers the tier to probation, and a tier is only ever raised by a person. So the rollout this specification requires on a version change — every node together, or they decline to peer — would cost every peering its operator-seeded tier, every time it happened.

A node SHOULD therefore send a receipt for each live peering’s open window before it stops. That receipt is a claim, and the window is then priced on the numbers rather than on the absence. It needs nothing new: it is the receipt the cadence would have sent, sent early. A peering that has used nothing sends nothing, because silence there is honest, and a node that cannot say goodbye still stops.

Retention is the operator’s choice

Reports are ordinary addressable events and replicate like anything else. Grant envelopes do too, but a node that collects other people's envelopes accumulates commitments it cannot open — proof that grants exist, and nothing about them. What is worth keeping is therefore mostly reports. How far back to keep them depends on what the operator intends to do, so it is a setting rather than a rule.

Mode Keeps Enables
self Grants this node issued, and grants naming it as subject. Nothing else. Enforcing your own budgets and honouring the ones issued to you. The floor.
peers default The above, plus reports authored by nodes this node holds a grant with. Not their grants: those are encrypted to their own subjects, so keeping them stores ciphertext that will never be read. The full trust weighting, and nothing beyond it.
neighbourhood Peers, plus what this node’s peers answer when asked about nodes it has not peered with. Grants are not readable from a relay, so this is a query budget rather than a subscription: it stores answers it received. Forming a view of a candidate before peering with it — at the cost of having to ask, and of the answer being refusable.
archive Every grant envelope and every report it sees. Envelopes are commitments, so an archivist accumulates proof that grants existed and none of their contents. Monitoring, research, and auditing the reputation system itself from outside.
none Nothing persisted; grants are evaluated live from the peer’s own copy and discarded. Leaves, and anything else with a storage budget measured in megabytes.
Why peers is the default

It is exactly the set the scoring function can use. A reporter's weight is tier_weight(my grant to r), and a node this one has never granted anything scores zero — so its reports are, arithmetically, incapable of changing any view here. Storing them is provably wasted disk. The default is not a compromise between completeness and cost; it is the whole of what the node can act on, and no more.

Two properties keep even archive from running away. Grants and reports are addressable, so only the latest per (issuer, subject) is retained — the set grows with the number of peering pairs, not with time. And every grant carries a mandatory NIP-40 expiration, so a dead peering evaporates rather than accumulating. In practical terms: a node with twenty peers in peers mode holds a few dozen events, well under a megabyte. archive over a ten-thousand-node network is a few hundred megabytes, and stable. Neither is a reason to make the choice for the operator.

Retention is enforced on the subscription rather than pruned retroactively. Nodes in peers mode narrow interest filters directly to reports authored by their active peer set. Broad firehose subscriptions create significant bandwidth costs for both senders and receivers. Because interest filters are public declarations, a node operating in archive mode is visibly designated as an archivist.

The node has a ceiling too

A grant bounds one peer. Nothing bounded the node. The meter is per peering, so twenty anchor grants each perfectly inside its own limit is 960,000 events an hour with nobody at fault anywhere: every branch within its limit, and the trunk with none. A node in that state does not fail as a protocol violation. It simply stops.

So a node declares its own total and sheds against it. The total is the operator’s number and this specification states no default for it. Connectivity and not capability is what separates one deployment from another, which is the whole of what a class declares, so a Raspberry Pi and a forty-core gateway both say anchor and no table here could be honest about either. A node with no declared total measures its pressure, reports it, and sheds nothing — refusing an honest peer against a limit nobody chose would be worse than the overload it avoided.

What is normative is the ladder. Pressure is the window’s aggregate over every peer, divided by the declared total, taken as the max over counters, because a node that has run out of one thing has run out.

the shed ladder, by pressure against the declared total

0.75  refuse_peerings   a new proposal is declined CAPACITY_EXCEEDED
                          peerings already held are untouched
0.85  narrow_scope      shards held only through the mesh stop being claimed
0.95  amend_down        every live grant is reduced, cause: capacity
1.00  leaf_behaviour    no pull is opened; receipts still flow

      release: every rung together, below 0.60

Three rules hold the ladder together. A shed is announced — a decline code, a coverage claim that stops being republished, an amend that says what caused it. A node that shed quietly would read as partitioned, which is the one conclusion this protocol keeps having to unlearn. A shed is not adverse. peering/1.0/amend carries cause, one of remedy, capacity or operator, and only the first is a finding; a node that read a capacity shed as a remedy would price its issuer for owning a small machine. A shed makes a peer smaller and never a stranger. The tier does not move and the peering is not ended, and the floor is a fraction of the probation ceiling rather than the ceiling itself — a peering opens at that ceiling, so a floor there would make the rung do nothing for exactly the peers a busy node has most of. It is not zero either. Zero for 24 hours is what S3 does to an offender.

The rungs release together rather than each at the threshold that engaged it, because a node sitting on a boundary would otherwise amend every one of its peers twice a window. Everything here is the node’s own arithmetic over its own meters: no new message, no new kind, and nothing another node has to be trusted for.

What a node does with stress it survived

A restart. A peer that went quiet mid-session. A relay that dropped its subscription. A clock that drifted past tolerance. This protocol is careful never to conclude something about a peer from an absence, and every one of these is correctly refused as a finding — and then discarded entirely, so the information goes out with the accusation. A node learns nothing from anything it survives.

Kept, it sets the node’s own reserves: how long it keeps accounting a vanished peer might still need, how long it goes on trying a door that was shut. Two rules make that safe to do.

Local and defensive only. A strain record never becomes a finding and never crosses the wire, and a peer’s own account of what it has weathered is a claim rather than evidence and is not read. If the whole of a node’s response to being stressed is to become better prepared for that exact stress, an attacker who causes it has done the node a favour and there is nothing to farm. Anything that instead paid a node in reputation or authority for having been attacked would be an invitation to manufacture the attack — which is the reason the severity ladder is one-way, and the reason nothing here touches it.

Directions, never magnitude. A reserve grows with the number of independent directions the stress came from and never with how much came from any one of them. A tree grows straight because the wind turns; where the wind is strong and always from one quarter the result is a flag tree, bent and one-sided and weaker overall. Each direction — one peer, one shard, one transport — is capped, and weighted by the tier this node granted that peer. Probation weighs zero, so free identities buy no directions at all, and a tier is only ever raised by a person: the sybil is priced by a constant that already existed rather than by a new one. Five hundred strangers move a node by nothing, and one peer moves it by at most one direction’s worth however hard it pushes.

The same arithmetic gives the operator a warning nothing else produces. When nearly all the strain a node has seen comes from one direction, that node is structurally dependent on one peer and will snap when it leaves. It is reported and it is not a finding: the peer has done nothing but be the only one there. Two qualifications, both learned the hard way. A direction naming neither a peer nor a transport is the node’s own event and cannot be depended on — dependence is something a node has on somebody else. And one event is not a pattern, so the warning needs a sample behind it.

Allow-lists and block-lists are grants

This model collapses three mechanisms into a single signed, expiring object. Queries regarding blocked traffic receive signed, verifiable explanations delivered directly to the requesting peer rather than broadcast publicly.

The same primitive covers user pubkeys via subject: author. A relay's existing allow and deny lists function as the local materialization of author grants. Relays supporting strfry's write-policy plugin protocol SHOULD maintain it unmodified alongside mesh policies. This preserves custom operator moderation logic alongside protocol-level peering enforcement.

Settlement is deferred, and constrained in advance

Transit payment mechanisms are deferred during alpha. The settlement tag on grants remains reserved with value none. Discrete compute job payment is specified separately in § Compute market. Transit settlement is deferred because capacity scarcity has not yet been demonstrated. Designing complex financial mechanisms for unproven scarcity creates unnecessary technical debt.

The protocol defines the invariants any future settlement mechanism must satisfy. Paid transit interacts poorly with bilateral reputation. A peer paying for capacity expects an unconditional service-level agreement. In contrast, the severity ladder requires that issuers retain the sovereign right to shrink capacity unilaterally upon divergence. Three rules prevent these models from colliding:

  • Payment may raise a ceiling, never establish a floor. Settlement can increase tier_max; it can never impose a mandatory minimum bandwidth requirement on the issuer.
  • The ladder is never suspended. Additive increase still governs capacity expansion for paying peers across clean windows. Payment purchases higher tier headroom rather than instant flood capacity. This prevents pay-to-flood attacks by construction. Severities S1 through S4 apply identically to paying and non-paying peers.
  • No obligation an issuer must arbitrate. Because issuers make no unconditional uptime promises, contractual breach cannot occur and no dispute arbitration is required. Settlement protocols must conform to this invariant. The compute market satisfies this by making job settlement accountable and provable — not atomic, which § Compute market is explicit about — so a failed delivery leaves signed evidence rather than an ambiguous partial transaction.

One measurement decides whether any of it is ever needed. Nodes SHOULD record, per window, whether each grant actually bound — and if so, which limit bound first. If a year of alpha shows grants almost never binding, settlement is answered by the data and the field can be deleted rather than filled in.

Storage rent is refused, and retrieval is the exception

The same question arrives for held bytes rather than moved ones: whether a peer should charge another node for storing its shard. It is refused, and on stronger grounds than transit.

Transit and compute are both acts. Storage is a position, and the three rules above bite hardest on the first of them. A paying peer can only be sold a higher blob_bytes ceiling — the right to send more bytes — because a floor is precisely what rule one forbids. Yet the floor is the entire content of a storage purchase: the promise that the bytes are still there later. Nothing coherent remains to sell. Rule three then fails by construction. An obligation that never terminates offers no moment at which settlement can be reckoned at all. The dispute it produces is about a duration, not an act.

Holding a shard is also not a favour. A peer asserting completeness: asserted over a shard serves queries from it, earns standing with it, and advertises it in kind 30803 to attract further peers. Coverage is a joint good, so the trade already nets to zero at the moment it is declared.

Retrieval MAY be priced; retention MUST NOT be. A retrieval is discrete and has a moment of completion, so it settles under the construction already specified in § Compute market. The payload is sealed to a key k. The invoice's payment hash is SHA256(k), so paying it reveals the key. Delivery and payment are a single act, no escrow agent exists, and the buyer verifies the delivered bytes against the CID it named. No proof of retrievability is required, because the proof is the ability to produce the bytes on demand — a node that discards them simply never earns from them again.

Two existing rules carry the rest. A node that will not keep holding what it claimed MUST narrow its coverage claim. It MUST NOT make that claim conditional on payment (§ Node classes). Withholding data inside a shard still claimed complete is falsifiable by sampling, and remains an S2 (§ Attack surface). Neither needs a new severity class and no new tag is reserved: settlement stays none, and blob_bytes stays a flow limit defaulting to zero. Whether storage scarcity is real at all is a measurement rather than a guess, and § Alpha contract records the counters that answer it.