Fleetmesh fleetmesh

The fleetmesh Protocol · part 9 of 10

Distribution & conformance

Reproducible distribution channels, verification signatures, and test suite conformance.

Distribution

Reproducible Distribution & Verification

Decentralized protocol governance requires verifiable software distribution. Downloading binaries over TLS relies on blind trust in hosting providers. Because on-wire protocol declarations are verifiable, the software producing those declarations must be verifiable as well.

What any distribution mechanism must do

Distribution is specified as architectural properties rather than a proprietary product. Fleetmesh does not hardwire a central package manager. Having eliminated centralized constants throughout the protocol, introducing a mandatory distribution registry would reintroduce central control.

publisher-signed
A release is signed by a key, and that key — not a hostname, not a registry account — is the publisher's identity. Compromise of a mirror must not be able to produce a release anyone accepts.
content-addressed
The artifact is named by its own hash, and bytes are accepted only when that hash matches. Download URLs are transport, interchangeable and untrusted, exactly as they are for blobs in § Replication.
locally verifiable
Every check — signature, hash, dependency, revocation — is performed by the installing machine against data it holds. No service is asked whether a package is genuine, because a service that can answer that question can also lie about it.
no name ownership
No global namespace and no registrar. The stable identity of a package is publisher key plus name, so two publishers may both ship fleetmesh-node and a reader is never confused about which one they installed.

npack satisfies all four

npack is a Nostr-native package manager that distributes signed release metadata over relays and stores immutable .npk artifacts — deterministic tar, zstd-compressed — on Blossom-compatible servers. It is the recommended distribution path for fleetmesh implementations, and it is recommended rather than required.

npackWhat it means here
kind 9900 The release event, authored by the publisher. Carries name, version, os, arch, format, the artifact's x (SHA-256) and the id of its NIP-94 event. Verified free in the registry on 2026-09-01 by the method in § Registration.
kind 9901 Revocation, signed by the same publisher and referencing the release by event id. Also verified free.
kind 1063 NIP-94 file metadata for the artifact, reused rather than reinvented. Its URL tags are candidate locations; the bytes are still checked against x.
kind 10063 The publisher's Blossom server list, reused as-is. Artifact retrieval prefers it over any configured fallback.
[trust] publishers A local allowlist of publisher keys, chosen by the operator. This is § Trust weighting's stance arrived at independently: no global authority, decisions local, evidence public.
repo / commit Optional NIP-34 repository address and source commit. Where present they extend the chain back past the binary to the source it was built from.
Two things it already gets right

The npack protocol specifies that package identity consists of publisher key plus package name. This matches fleetmesh's decentralized node identity model. Furthermore, an .npk package is a content-addressed blob. A fleetmesh node serves packages using existing infrastructure: the blob plane (§ Replication) transfers SHA-256-addressed payloads over iroh-blobs with BUD-04 HTTP fallback. Every anchor node functions natively as a package mirror.

Binding a release to a protocol version

This is the one fleetmesh-specific addition, and it uses a tag npack already has. A release of a fleetmesh implementation SHOULD declare the protocol version it speaks as a capability:

kind 9900 — the tags that matter to fleetmesh

["name", "fleetmesh-node"], ["version", "0.3.1"],
["provides", "fleetmesh/0.1-draft@6d8c8126a619e25c…"],
["x", "<sha256 of the .npk>"],
["repo", "30617:<pubkey>:fleetmesh-node"], ["commit", "<commit id>"]

This binding allows operators to verify the protocol version a binary speaks before installation. Nodes cross-check descriptor declarations against signed release events. A node declaring a version digest unverified by its installed release commits a misreport. The severity ladder prices this discrepancy as an S2 finding, with the publisher-signed release serving as evidence.

The distribution chain has no centralised dependency at any step. Source commits produce deterministic build archives. Independent operators sign the release hashes. Installed binaries declare canonical version digests, and peering handshakes check that those declarations match.

Tested, not merely specified

This packaging model is verified end-to-end against npack 0.2.9 (commit f1d0efe927c7). Automated tests confirm three properties: First, deterministic archiving produces byte-identical packages. Second, verifying artifacts against signed x hashes requires zero repository access. Third, altering a provides version digest invalidates the signed Nostr event ID, preventing mirrors from tampering in flight.

Reproducing the binary, without trusting who built it

A signed release proves who published a build. It does not prove what the build contains, and a single publisher key is a single point of compromise for every node that installs from it.

Release binaries MUST therefore be built under hermetic, reproducible toolchains — Nix or a pinned container image — and their SHA-256 digests MUST be independently rebuilt, verified and signed by at least two independent operators, using nostr software attestation events, before deployment. Independence is judged the same way it is everywhere else in this document (§ Governance).

Two operators agreeing on the bytes is not proof of absence of a backdoor. It is the difference between trusting one party and trusting a collusion, which is the same trade the rest of the mesh makes.

What this does not solve

ProblemWhere it stands
the first binary npack's own bootstrap is built from source or fetched from a conventional release page, and so is the first fleetmesh node. Every distribution chain terminates in something trusted for reasons outside itself. Reproducible builds narrow this to "did anyone else get the same bytes" rather than closing it.
revocation reachability A kind 9901 only protects a client that sees it. npack is explicit that revocations work where relays retain the original release. This document is equally explicit in § Replication: a deletion is a request rather than a guarantee. The honest framing is the same: revocation is a signal that propagates, not a switch that fires.
maturity npack describes itself as an early working prototype and its wire format as a project protocol, not a registered NIP. That is exactly the right reason for fleetmesh to specify properties and name an implementation, rather than to depend on one normatively.
licence npack is MIT, fleetmesh is CC0. Fine for a tool an operator chooses to run, and worth stating rather than discovering: nothing in this document requires installing it.

Conformance

Conformance Testing & Verification

Four suites, each runnable without a network:

vectors
test vectors — fixtures
Test vectors define deterministic inputs and expected outputs for: descriptor transformations, Multikey encodings, canonical grant and receipt serialization, window evaluations, and trust weighting calculations.
store conformance
A shared behavioural suite that every storage backend must pass, run identically against each. Negentropy fingerprints are the part that matters here: two stores fed the same events MUST produce identical range fingerprints, or reconciliation silently diverges between a Pi and a datacentre node.
the unplug test
The whole suite, run again with every default anchor node deleted, every fleetmesh.org lookup failing, and bootstrap.toml stripped to a single third-party address. Discovery, peering, sync, grants and reports must all still complete. It runs on every change, and a failure is not a flaky test: it means a dependency has crept back in.
the bad peer
A deliberately misbehaving node, shipped in the workspace, with a switch per offence: overrun-and-admit, overrun-and-lie, out-of-scope push, duplicate flood, over-long forwarding path, replayed receipt chain, forged hop signature. The ladder is only testable against something that actually misbehaves, and every implementation should be run against it before it is trusted with a grant.

Interoperability is the primary criterion. A conformant mesh node MUST function as a standard, unmodified Nostr relay for any client that lacks mesh awareness.

Registration

Protocol Event Kinds & Standardization

All ten event kinds were verified against three independent registries on 2026-08-29 and re-verified against live copies of all three on 2026-09-01. All ten allocations are collision-free. The verification process follows the strict, empirical guidelines defined by the NIP maintainers.

Availability

Kind Class per NIP-01 Registry NIPs table Issues & PRs
11801 replaceable free free 0 hits
11802 replaceable free free 0 hits
11803 replaceable free free 0 hits
21801 ephemeral free free 0 hits
21802 ephemeral free free 0 hits
30801 addressable free free 0 hits
30802 addressable free free 0 hits
30803 addressable free free 0 hits
30810 addressable free free 0 hits
30811 addressable free free 0 hits

Registry is nostr-protocol/registry-of-kinds, the machine-readable YAML the NIPs README now points at as preferred over its own table — 265 kinds defined, highest 39701. NIPs table is the 187-row human-curated table in the NIPs README. Issues & PRs is a GitHub search of every issue and pull request in the NIPs repository for each number, open or closed. Nothing matched anywhere.

The range classes check out against NIP-01's own wording: replaceable is 10000 ≤ n < 20000, ephemeral is 20000 ≤ n < 30000, addressable is 30000 ≤ n < 40000. So 11801–11803 are replaceable, 21801/21802 are ephemeral and 30801–30804, 30810/30811 are addressable, as the specification assumes throughout.

The compute kinds sit in the same two bands and were cleared the same way: 11803 has 11871 as its nearest neighbour above, and 30810/30811 fall inside the same gap as 30801–30803. Adjacent kind numbers show how the ecosystem is distributed. The 11k–12k band holds 11111, 11871 and 12473. The 21k–22k range holds 21000–21003, 21059 and 22242. Around 30k sit 30617/30618 and 30817–30828. The 30801–30803 allocation sits securely in the open gap between git repositories and wiki kinds.

Also verified

The multicodec values this specification asserts are correct against the multiformats table: secp256k1-pub is 0xe7, x25519-pub is 0xec, ed25519-pub is 0xed, sha2-256 is 0x12 and raw is 0x55. DIDComm protocol URIs need no registration anywhere; they are opaque identifiers compared byte-for-byte.

What it takes to become a NIP

The NIPs repository publishes five acceptance criteria. Two of them decide this outright:

  • “Fully implemented in at least two clients and one relay.” A specification written and implemented by one organisation does not qualify, however good it is. A NIP submission is therefore gated on a second, independent implementation existing — which is a statement about the mesh's adoption, not about the document.
  • “There should be no more than one way of doing the same thing.” Reviewers will ask how Kind 30802 differs from a NIP-85 kind 30385, whether the one merged NIP-85 defines or the one PR #2418 proposes under the same number. The distinction is fundamental. Kind 30385 is a third-party score about a relay, read by clients selecting connections. Kind 30802 is an evidence-backed peer verdict, carrying subject-signed cryptographic proof, read by nodes setting bilateral budgets. Different consumer, different evidentiary standard. A node publishes both, and the first is derived from the second and cites it (Bridge 2 in § Ecosystem Bridges).

The backlog is the other half of the picture. The repository has 454 open pull requests. PR #1585, NIP-37 Transport Method Announcement — which is roughly half of what this specification's node descriptor does — has been open since November 2024 and was last touched in March 2026. Planning around a merge is planning around something outside anyone's control.

The recommended path, in order

  • 1
    Register the kinds nowSubmit the ten definitions to registry-of-kinds, whose stated bar is “any reasonable event definition can be added here” and which explicitly does not require an implementation guide. This is collision avoidance, it is cheap, and it is the step that stops someone else taking 30801 next month.
  • 2
    Publish and implementShip this document and the reference implementation, with kind numbers treated as alpha-mutable per § Alpha contract. Standards on nostr emerge as often from someone doing a thing and others copying it as from a document, and the repository says so itself.
  • 3
    Publish it on nostr, not only over HTTPA specification reachable only at one domain asks the reader to trust that domain stays up. NostrHub carries decentralised proposals as addressable kind 30817 events and browses repositories announced under NIP-34, so the document and its source become ordinary events that any relay can serve and anyone can mirror. scripts/publish_nostrhub.py builds both: the kind 30817 NIP from nip/fleetmesh.md, whose tables are generated from the same schema/ files this version digest is taken over, and a kind 30617 announcement of the repository. This costs nothing and asks nobody for permission, which is the same argument as step 1.
  • 4
    Submit a NIP when a second implementation existsDo not submit prematurely. When ready, split the submission. The node descriptor and reputation kinds are separable. A targeted NIP covering bilateral grants and conformance reports has a far higher probability of consensus than a monolithic federation specification.
  • 5
    Reconcile with #2418 on the way inA node publishes a derived kind 30385 beside its kind 30802: the same evidence, summarised as a client-facing score. It is published now, under merged NIP-85 and NIP-73, because those are the documents that already assign the number; Bridge 2 in § Ecosystem Bridges gives the shape and ref/projection.py is the serialiser. Reuse answers criterion 4 better than an argument does, and it cost one serialiser. The proposal is still open and its thread has never discussed the collision. If it merges on another number a second serialiser follows it. If it merges on this one the k tag tells the two shapes apart.
Assume no NIP

Nothing in this specification should depend on a NIP being accepted. The kinds are registered to avoid collisions, the mesh works whether or not the wider ecosystem adopts them, and a rejected or ignored submission changes nothing operationally. The one thing that genuinely benefits from a NIP is NIP-77 negentropy, and that one is already merged.