No description
  • Rust 48.9%
  • TypeScript 21.6%
  • JavaScript 20.6%
  • Dart 4.6%
  • Shell 1.3%
  • Other 2.9%
Find a file
Joshua Belke 338c988032
Some checks failed
CI / Detect Changed Paths (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
Meridian Harness / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Meridian Harness / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Isolated DB Gate (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Admin Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Server Cross-Compile (push) Has been cancelled
CI / Server Cross-Compile-1 (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Meridian Harness / Publish rolling release (push) Has been cancelled
Meridian Harness / Publish tagged release (push) Has been cancelled
docs: the ignored-relay-test count is 53, and a fourth author has now got it wrong
a9b30ea27 added two Postgres-gated tenancy tests and left the number at 51
in both the Justfile and the CI comment beside the same selection. Derived
from the binary rather than from grep: 53.

The Justfile comment already told the next reader to re-derive rather than
trust it, and listed three authors who had not. It now lists four,
including this campaign. Leaving the tally visible is the point -- a
hand-maintained number that keeps being wrong is evidence the comment
wants a gate, not a bigger warning.

Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
2026-08-21 04:53:55 -04:00
.agents/skills fix(skills): bound no-ai-slop to one named draft, out of the suite tree 2026-08-19 14:52:10 -04:00
.beads docs(mips): adopt MIP-RT and withdraw the on-chain payments scope 2026-08-20 18:34:48 -04:00
.cargo chore(build): disable incremental compilation, add just target-sweep 2026-08-08 16:10:40 -04:00
.claude fix(skills): bound no-ai-slop to one named draft, out of the suite tree 2026-08-19 14:52:10 -04:00
.codex/skills fix(skills): bound no-ai-slop to one named draft, out of the suite tree 2026-08-19 14:52:10 -04:00
.github docs: the ignored-relay-test count is 53, and a fourth author has now got it wrong 2026-08-21 04:53:55 -04:00
.goose/skills feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
.intersect chore(release): release Buzz Desktop version 0.4.11 (#2010) 2026-07-16 20:50:03 -04:00
.settings fix(bus): report Zenoh reachability separately instead of overclaiming ready 2026-08-20 21:03:51 -04:00
.vscode perf(ci): speed up PR CI wall clock and local dev builds (#1028) 2026-06-16 12:44:13 -04:00
benchmarks fix(repo): point the repo slug at the real remote, not the guessed one 2026-08-05 13:00:51 -04:00
bin fix(hermit): stop the git shim fork-bombing across sibling checkouts 2026-08-19 14:01:44 -04:00
crates test(relay): prove the bus tenancy binding ACCEPTS, not just that it refuses 2026-08-21 04:47:22 -04:00
deploy docs(deploy): name the enforcement point for the chart version rule 2026-08-21 04:34:11 -04:00
docs fix(bus): report Zenoh reachability separately instead of overclaiming ready 2026-08-20 21:03:51 -04:00
examples feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
migrations fix(workflow): snapshot definition on run for approval resume 2026-08-19 14:01:38 -04:00
patches fix(desktop): retire prepend mode on every reader wheel (#2913) 2026-07-25 17:22:15 -07:00
perf perf(bus): measure the posture the relay ships, and prove it took 2026-08-21 00:48:59 -04:00
REMAPPING docs(mips): adopt MIP-RT and withdraw the on-chain payments scope 2026-08-20 18:34:48 -04:00
schema feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
script feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
scripts ci(charts): enforce the Chart.yaml version bump instead of asking for it 2026-08-21 04:30:21 -04:00
skills fix(skills): bound no-ai-slop to one named draft, out of the suite tree 2026-08-19 14:52:10 -04:00
.dockerignore refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
.env.example fix(bus): the relay is a client of the router tier, not a peer of it 2026-08-20 16:43:34 -04:00
.env.local.example feat(bus): land the single-host loopback arm of the zenohd profile 2026-08-20 12:18:25 -04:00
.gitattributes refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
.gitignore chore(git): harden ignore rules for env backups, wiki snapshot and scratch 2026-08-19 14:01:42 -04:00
AGENTS.md docs: withdraw the >=4 KB crossover — shared memory was on for every measurement 2026-08-21 00:54:18 -04:00
ARCHITECTURE.md docs: withdraw the >=4 KB crossover — shared memory was on for every measurement 2026-08-21 00:54:18 -04:00
biome.json feat(desktop): surface each relay's MIP capabilities from its NIP-11 advertisement 2026-08-06 00:06:15 -04:00
Cargo.lock feat(bus): implement ZenohEventBus, its shadow adapter, and the trust ladder 2026-08-19 15:59:59 -04:00
Cargo.toml feat(pubsub): pin zenoh 1.8.0 and make its config contract executable 2026-08-19 14:16:42 -04:00
CHANGELOG.md refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
CLAUDE.md Install sprout-cli skill at repo root + fix desktop clippy (#818) 2026-06-02 17:11:30 -04:00
CODE_OF_CONDUCT.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
CONTRIBUTING.md docs: name MERIDIAN_TEST_* recipe overrides and env-doctor provenance 2026-08-19 14:01:36 -04:00
ct.yaml feat(deploy): add production Helm chart for Buzz (#990) 2026-06-17 21:26:33 -04:00
deny.toml feat(pubsub): pin zenoh 1.8.0 and make its config contract executable 2026-08-19 14:16:42 -04:00
DIAGRAM.md docs: withdraw the >=4 KB crossover — shared memory was on for every measurement 2026-08-21 00:54:18 -04:00
docker-compose.harness.yml fix(repo): point the repo slug at the real remote, not the guessed one 2026-08-05 13:00:51 -04:00
docker-compose.override.yml.example fix(bus): bind the zenohd router to loopback, as its commit title already claimed 2026-08-20 12:18:25 -04:00
docker-compose.yml fix(bus): bind the zenohd router to loopback, as its commit title already claimed 2026-08-20 12:18:25 -04:00
Dockerfile build(image): compile the Zenoh adapter into the relay image 2026-08-20 12:18:25 -04:00
Dockerfile.control-plane fix(repo): point the repo slug at the real remote, not the guessed one 2026-08-05 13:00:51 -04:00
Dockerfile.push-gateway fix(repo): point the repo slug at the real remote, not the guessed one 2026-08-05 13:00:51 -04:00
GOVERNANCE.md docs: fix stale claims, remove counts, purge LiveKit references (#742) 2026-05-24 13:09:34 -04:00
intro.html docs(intro): track MIP-CT § 6.5 in the briefing page, byte budget included 2026-08-19 14:01:44 -04:00
Justfile docs: the ignored-relay-test count is 53, and a fourth author has now got it wrong 2026-08-21 04:53:55 -04:00
lefthook.yml ci(charts): enforce the Chart.yaml version bump instead of asking for it 2026-08-21 04:30:21 -04:00
LICENSE docs(readme): overhaul the README for the program, and relicense to MIT 2026-08-05 14:47:03 -04:00
NOSTR.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
package.json feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
pnpm-lock.yaml refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
pnpm-workspace.yaml refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
preview-features.json feat(settings): probe developer-tools links before offering them (DVT3, DVT4) 2026-08-08 15:06:08 -04:00
prometheus.yml feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
README.md docs: withdraw the >=4 KB crossover — shared memory was on for every measurement 2026-08-21 00:54:18 -04:00
RELEASING.md refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
renovate.json chore(renovate): stop rebase churn between weekly sweeps (#1530) 2026-07-06 07:53:33 -07:00
run.sh fix(bus): bind the zenohd router to loopback, as its commit title already claimed 2026-08-20 12:18:25 -04:00
rust-toolchain.toml Fix zombie agent detection and surface stopped agent cleanup (#380) 2026-04-21 14:56:00 -07:00
SECURITY.md fix(rebrand): complete the conversion the rg-based checks missed 2026-08-04 23:31:26 -04:00
skills-lock.json feat(skills): add the rust-coding overlay and pin the corpus it reads 2026-08-19 14:01:43 -04:00
TASKS.md docs: withdraw the >=4 KB crossover — shared memory was on for every measurement 2026-08-21 00:54:18 -04:00
TESTING.md refactor: move clients under REMAPPING deployable grammar 2026-08-19 14:01:39 -04:00
VISION.md docs(readme): overhaul the README for the program, and relicense to MIT 2026-08-05 14:47:03 -04:00
VISION_ACTIVITY.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
VISION_AGENT.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
VISION_MESH.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
VISION_MODERATION.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
VISION_PROJECTS.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00
VISION_SOVEREIGN.md feat: rebrand Codebase Chat to Meridian 2026-08-04 14:06:04 -04:00

MERIDIAN

Multidomain Exchange for Realtime Integration of Data Innovation with Allied Nations

A low-friction, open-protocol, sovereign and decentralized exchange for real-time visibility and integration — humans, servers, agents, machines and IoT on one wire.

Licensed MIT Rust 1.95.0, pinned by Hermit Nostr NIP-01 on the wire Desktop, web and mobile clients
The human, agent and service path is shipping The machine and IoT path is specified but not yet built

Quick start · Repository map · Protocols · Architecture · System map · Program plan · Design Law · MIT


Contents

Orientation — What MERIDIAN is · The mental model · System shape · What runs today

Working in the repo — Quick start · Repository map · Quality gates · Documentation

Protocol — Interoperability, protocols and payloads · Protocol surface · Sovereignty and decentralization

Numbers — The two rules that govern them · Ceilings, with profiles attached

Constraints — Design Law · What we deliberately do not build · Programme and roadmap

New here and want the shape of the codebase rather than the argument for it? Start at the repository map.


What MERIDIAN is

An exchange, not an application. One relay binary carries two kinds of traffic that share a process and almost nothing else:

  • The human, agent and service path — every message, reaction, review, workflow step and git object is a signed Nostr NIP-01 event in one Postgres log. Verified, ordered, stored, indexed, audited, and provable to a third party years later.
  • The machine and IoT path — tracks, telemetry and sensor frames ride the ephemeral range: never stored, never indexed, never audited, and never on the Postgres write path. Specified but not yet shipped: the opaque-frame envelope that carries them as bytes behind a fixed-width routing header the relay routes on without ever parsing the payload (MIP-OF, DRAFT). Until it lands this traffic is still signature-verified per event — which is the cost the frame path exists to remove, not a cost it has already removed.

Anything that can hold a keypair or reach a bridge can participate: a person at a desktop, a service posting over HTTP, an AI agent with its own identity, an airframe emitting kinematic state at 100 Hz, a constrained device that only speaks MQTT. Participation is a protocol, not a product integration.

The goals, and what each one actually means here

Adjectives are cheap. Each row below names the mechanism that makes the claim true, or says plainly that it is still a design.

Goal The mechanism
Low friction A keypair is the only credential. The wire is published, so no vendor SDK is required to join. Devices that cannot be reflashed reach the exchange through an MQTT bridge at the edge rather than a firmware programme
Open protocol NIP-01 on the wire, plus a published spec suite — draft NIPs in docs/nips/ for anything the wider ecosystem wants, MIPs for what only this deployment wants. Nothing proprietary between two participants
Sovereign Each participant runs its own relay and holds its own keys. The tenancy fence is derived from the request host, bound before any handler sees data, and immutable on every row. No external service is required to operate, and no operator can read another's traffic
Decentralized No central broker, no mandatory message-bus hop, and no second durable log. Topology is hub-and-leaf: edge relays terminate device links and forward upstream; the edge never routes for the spine
Real-time Local subscribers are served from an in-process registry with no broker hop. Ephemeral traffic classes skip storage, indexing and audit by compile-time fence. Interest scoping cuts cross-pod ingress 64×, measured
Scalable Four sanctioned scaling axes — replica, function, tenant, traffic class — and never vocabulary. Every ceiling is published with its profile, and optimisation waits for its measurement
Interoperable One event log for humans, agents and services; opaque frames for machines; an MQTT bridge for the device ecosystem; payload specs for AIS, ADS-B, kinematics and scalar telemetry; symbology carried faithfully and never interpreted by the relay

Who and what participates

Participant Speaks State
Humans NIP-01 over WebSocket via the desktop, mobile and web clients shipping
Agents meridian-cli, the ACP harness (Goose, Codex, Claude Code), dev-MCP — with their own keypairs, memberships and audit trail shipping
Servers and services POST /events · /query · /count, workflow webhooks, git smart HTTP, the typed Rust SDK shipping
Machines — platforms, sensors, surveillance feeds Opaque MIP-OF frames through a feeder that decodes, dedupes and attests designed
IoT and constrained devices MQTT at the edge, bridged by MIP-MQ; the broker keeps offline queues and QoS state designed

Two rules that govern every number in this repository

Both are binding contract in AGENTS.md, not editorial preference.

  1. No throughput figure without its profile — traffic class, auth profile, whether the payload is parsed, whether it is stored. One relay has two ceilings roughly two orders of magnitude apart. Quoting the good one without its qualifier is how you acquire an obligation the binary cannot meet.
  2. Nothing is resolved until deployed to a live relay and re-probed. Green CI is not evidence that an enforcement path runs in production. A deployed image predating the enforcement code makes a feature flag a silent no-op.

Every claim below carries one of these labels, and they are not interchangeable:

Label Means
measured a committed benchmark in this repo produced this number
estimate derived from measured parts, not measured end to end
target a goal behind a gate, not a result
designed specified, no code in tree
open a real question we have not answered
refused deliberately not built, with a written reason

The mental model: two relays in one binary

  HUMAN + AGENT + SERVICE PATH  ── NIP-01 over WebSocket ────────────────────
    parse JSON → BIP-340 verify → Postgres INSERT → FTS index → audit chain
                 └─ 34–37.7 µs ─┘   └────── the durability boundary ──────┘
    ceiling: ~250–320k events/s per 16-core pod      (verify-bound, estimate)
             ~10–30k/s once stored AND indexed       (estimate)

  MACHINE + IOT PATH  ── opaque frames, ephemeral kinds, trusted link ──────
    read ~44 B fixed header → match key expression → forward bytes, unparsed
                 └─ no signature, no store, no index, no audit ─┘
    ceiling: ~600k msg/s ≈ 0.44 Gbps in              (estimate, not in tree)

  SHARED:      tenancy fence · admission · interest routing · the bus
  NOT SHARED:  parsing · crypto · durability · query surface · client shape

The machine path's defining property is a refusal: the relay never reads the payload. That is the enabling property for zero-copy fan-out, not a limitation to work around — so anything the relay must act on has to live in the header.

Header field Width Why the relay needs it
community 16 B the tenancy fence, bound before any handler sees data
kind 4 B selects the QoS lane and the class floor
geo cell 4 B packed geohash — this is the routing key
conflation key 8 B target id; ICAO24 (24 b) and MMSI (30 b) both fit
sequence 4 B gap detection without reading content
timestamp 8 B TTL and staleness without reading content

≈ 44 B fixed-width, no allocation, no parse tree. Everything else is bytes the relay forwards and never inspects. Content-aware work — deduplication above all — moves upstream to the feeder, because a relay that never reads the payload categorically cannot dedupe by content.

Accept the consequence deliberately: opaque machine frames are not consumable by a generic Nostr client. Their consumers are purpose-built — a track display, a fusion engine, a recorder.


Ceilings, with profiles attached

Profile Figure Basis
Opaque pre-decoded frames · trusted link · never parsed · not stored ~600k msg/s estimate — the path is not yet in tree; the pre-/post-dedup question behind it is open at ~10×
Signed NIP-01 events · per 16-core pod · BIP-340 bound ~250–320k events/s measured per-core, extrapolated — just bench puts verify_event at ~30k/s on one core, observed 21.6–33.1k over 3 runs (M3 Max, release)
Stored and indexed events · one Postgres ~10–30k/s estimate — persistence latency is measured (p95 74.6 ms direct, 77.1 ms nested)
Fan-out, per event ~4.1 µs at N=1 → ~56 µs at N=64 measured — fanout_cost.rs; past ~40 recipients fan-out exceeds signature verification
Bus interest scoping (Dragonfly) 64× cluster ingress reduction measured — perf/relay_bus_scaling.py --mode redis
Bus (Zenoh) at 256 B 1.15–1.51× Redis — target withdrawn, measured short measured — the ≥2M msg/s / ~10–20× target was falsified by meridian-ctg. 1.51× vs Dragonfly-in-Docker, 1.15× [0.98–1.34] vs same-footing host Redis, 0.71× with express. The ≥4 KB crossover is WITHDRAWN — shared memory was on and its 3,072 B threshold gave every larger payload a POSIX-SHM fast path the relay build cannot take. Below 4 KB stands. Profile: opaque bytes, nothing signed/parsed/stored, Python binding — perf/RELAY_BUS_SCALING.md § Phase 0A.2
Symmetric MAC vs. per-event signature ~120× (HMAC-SHA256) · ~400× (keyed BLAKE3) measured at batch 1, bands ~110–170× and ~360–550×. Compare at a matched batch size: at batch 64 it is ~200× and ~420× (HMAC-SHA256 0.19 µs, keyed BLAKE3 0.089 µs vs. 37.7 µs Schnorr)

Two consequences worth internalising before planning against these numbers:

  • At scale this system is fan-out bound, not crypto bound. At 10M users / 1M concurrent, human message volume is ~3.3k events/s — one verification pod covers it. Fan-out at 50 average recipients is ~1M frames/s.
  • The way past the verification ceiling is to skip verification on links that qualify for a trusted exchange profile, never to make BIP-340 faster. Batch Schnorr verification does not exist at any level of the pinned tree, and measured on ed25519 — where batching does exist — batch-64 amortises to only ~2.9×.

Reproduce any of it:

cargo bench -p meridian-core  --bench event_cost     # BIP-340 ceiling, per core
cargo bench -p meridian-relay --bench fanout_cost    # fan-out at N = 1 / 8 / 64
./perf/relay_bus_scaling.py --mode redis             # interest-scoping reduction

Interoperability — protocols and payloads

The spec suite

Two registers, one rule for choosing between them: does anyone outside this deployment want the spec? Yes → it is a NIP and goes upstream, but only after it runs. No → it is a MIP, ours, versioned here. Calling a deployment-specific spec a NIP is how you end up owing an ecosystem a design built for one operator.

Spec Layer Owns Status
MIP-RG Process Proposal registry, lifecycle and numbering designed
MIP-XP Link Negotiated exchange profiles — per-link auth floors P0–P3, negotiated once, enforced per message designed
MIP-OF Wire The opaque frame envelope: the ~44 B header, batch framing, the three movement contracts. This is the adapter contract — get it right and a second feed is configuration, not a new spec designed
MIP-SF Wire Session frames — after NIP-42 AUTH the socket is the attribution channel, so an HMAC replaces the per-message Schnorr signature on ephemeral kinds designed
MIP-QC Scheduling QoS classes and congestion contracts — eight lanes, class-aware shed designed
MIP-LF Topology Leaf forwarding — hub-and-leaf, store-and-forward, no peering designed
MIP-MQ Interop MQTT interoperability — topic mapping by registry lookup, QoS mapping, session semantics designed
MIP-KN Payload Kinematic state — position, velocity, heading, altitude per platform designed
MIP-TL Payload Scalar telemetry and sensor readings designed

Layering is strict and one-directional: a payload spec may never require the relay to read it.

Those nine are the transport stack — the part the rest of this section explains. The register carries more, and it is the authority rather than this table: feed and payload profiles (MIP-AS AIS, MIP-ML MAVLink, MIP-RI Remote ID, MIP-DS ROS 2 / DDS, MIP-NM NMEA 0183, MIP-CT drone backbone telemetry, MIP-MI STANAG 4609 motion imagery), the media control plane (MIP-MS), the DDIL and delivery contracts (MIP-DD, MIP-CU), and the adapter contract that pins units, CRS, time base and lossiness (MIP-AD).

Every one of them is DRAFT today, and the lifecycle is earned rather than asserted: just check-mips decides each row against the tree, and ENFORCED — the only state that permits NIP-11 advertisement — additionally requires a reader in the relay's allowlist plus a live re-probe, because green CI proves nothing about a running relay. Full register and lifecycle in docs/mips/README.md; dependency graph and sprint mapping in TASKS.md § 5.

Payload families

One kinematic schema serves both shapes of traffic, because two schemas would mean every fusion implementation writes two parsers plus a converter — and the converter is where provenance gets lost.

Family Shape Movement contract
Drone / platform kinematics temporal — N samples, 1 platform, 10–1000 Hz conflated, key = platform id
AIS / ADS-B surveillance states spatial — N platforms, ~1 sample each, 0.1–2 Hz × 10⁵ conflated, key = ICAO24 / MMSI
Sensor and health telemetry temporal volatile
Health beacons low-rate liveness conflated, key = platform id

Proposed kinds 21000–21002 sit in the ephemeral range 20000–29999, and that is not cosmetic: session frames are fenced by a compile-time assert!(is_ephemeral(k)), so a persistent kind number forfeits them and pays 30–70 µs of BIP-340 per sample instead of ~0.2 µs of HMAC. A ~350× cost decided entirely by which integer someone picks. A persistent kind on a session frame is rejected, never upgraded — extending session frames to persistent kinds would turn "the relay can lie about who is typing" into "the relay can forge history."

Two payload fields that must never default

  • provenance: self-reported | observed. A drone signs its own telemetry; an ADS-B target signs nothing and the feeder attests on its behalf. That distinction must not be droppable by omission.
  • fidelity: real means not simulated — it does not mean trustworthy. An unauthenticated broadcast faithfully relayed is still real and still spoofable. Spoof detection is an explicit non-goal of the transport layer, doubly so for a relay that has forsworn reading. Detection belongs to fusion.

Stream labels — a safety primitive, not governance

Three closed lanes applied per stream, never per message, resolved at subscribe time at zero per-event cost:

fidelity ∈ { notional, simulated, real }
context  ∈ { experiment, exercise, operational }
access   ∈ { community, organization, controlled }

This is the mechanism that stops a simulated track entering an operational picture. Per-message marking measured 70–84% defect rates in published audits; per-stream is cheaper and more correct.

Symbology — SIDC / MIL-STD-2525 / APP-6

open — the newest and least-settled part of the programme. What the Design Law already decides:

  • The SIDC struct belongs in the payload, never in the header. The relay does not route by affiliation, does not filter by battle dimension and does not render. Putting a symbol code in the header would be a request that the relay read semantics, forfeiting everything the opaque-frame design buys.
  • A symbol code must never become a routing, topic or storage key. Affiliation is mutable; a track can be re-designated. Freeze it into a key and every re-designation is a fleet-wide rename plus a hole in the history. Route by geo cell; render by symbol.
  • Standard and version are explicit and non-defaulting. 2525D / APP-6(D) is a 20-digit numeric SIDC; 2525C / APP-6(B) is a 15-character alphanumeric code. They are not interconvertible by inspection, and guessing wrong flips affiliation.
  • One authoritative simulation marker. 2525/APP-6 carries its own exercise/simulation amplifiers and we carry fidelity and context stream labels. Two independent ways to assert "simulated" will drift silently, and the drift is safety-relevant. The standing recommendation — for the council to accept or reject before either ships — is that the stream label is authoritative and any symbology amplifier is rendered from it, never read back as a second source of truth.

MQTT: what MERIDIAN replaces, and what stays at the edge

The claim is narrower than "broker replacement", and the narrowing is ratified in DIAGRAM.md § Part III by the Architecture & Scale council.

Replaced by MERIDIAN Kept at the edge (a broker, legitimately)
The mandatory message-bus hop on every message Broker-held offline queues and persistent sessions
Per-message topic-ACL authorization MQTT QoS 1/2 delivery state for constrained devices
A second durable log beside the query store Retained messages and Last Will & Testament
Topic-prefix multi-tenancy Shared subscriptions ($share/group/topic)
Unauthenticated-past-the-broker messages The embedded MQTT client SDK ecosystem

The right-hand column is a standing architectural boundary, not a gap list to close. A device offline for a day needs broker-held state; a MERIDIAN client re-runs a query over stored events, which works for a chat client and does not work for a constrained device that cannot page history.

The one irreducible difference: an MQTT message is authenticated by its connection, so downstream of the broker nothing proves authorship. A signed event carries its own proof to every consumer, forever — across organizations that share data but not trust. That is what the ~37.7 µs buys, and what the audit chain, third-party verification and any future federation rest on.


Sovereignty and decentralization

flowchart LR
    classDef dev fill:#4a4a4a,stroke:#2b2b2b,color:#fff
    classDef edge fill:#1f6aa5,stroke:#123f63,color:#fff
    classDef feed fill:#7d6608,stroke:#4d3f05,color:#fff
    classDef spine fill:#b03030,stroke:#6e1c1c,color:#fff
    classDef bad fill:#8a1c1c,stroke:#500f0f,color:#fff

    DEV["Constrained devices<br/>MQTT clients · intermittent links"]:::dev
    BRK["EDGE — MQTT bridge + leaf relay<br/>persistent sessions · QoS 1/2 · retained<br/>LWT · store-and-forward across outages"]:::edge
    FD["FEEDER — the boundary<br/>decode · dedup · routing header · MAC or sign · batch"]:::feed
    SP["SPINE — authoritative relay<br/>admission · verification · ordering ·<br/>tenancy fence · audit · fan-out"]:::spine

    DEV --> BRK --> FD --> SP

    X["REFUSED: the leaf as a routing peer.<br/>A leaf outage would become a spine routing failure,<br/>and a compromised leaf could transit between communities."]:::bad
    SP -.->|never| X
  • The tenancy fence is the sovereignty boundary. A relay is one operator's world — its members, channels, repos, streams, search index and audit chain. The fence is resolved from the request host before any handler runs, an unknown host fails closed rather than falling through to a default, and the tenancy id is immutable and part of every primary key. Two tenants sharing infrastructure cannot observe each other — not events, profiles, DMs, search results, audit chains, or error strings. That property is mechanized in TLA+ and its authorization in Tamarin, mutation-tested. (In the schema and the internal docs that fence is still spelled community / community_id; the user-facing rename to "relay" is in flight — meridian-1lv.)
  • Identity is portable; standing is local and dated. A keypair is yours everywhere. Membership is modelled as [since, until) rows in one grant store, never baked into a credential that goes stale the moment it is issued.
  • The boundary is one-directional. The spine ingests from the edge. The edge never routes for the spine.
  • Federation is designed and deliberately blocked. Multi-master peering would let a concurrent grant resurrect a revoked one by wall-clock timestamp. The defect is dormant while a single relay is the only writer per tenant; the fix — epoch-monotonic, absorbing revocation — lands before the second writer, because after is too late and the resurrection is silent.

System shape

flowchart TB
    classDef client fill:#4a4a4a,stroke:#2b2b2b,color:#fff
    classDef relay fill:#b03030,stroke:#6e1c1c,color:#fff
    classDef store fill:#1e7a45,stroke:#0f4527,color:#fff
    classDef next fill:#2b2b2b,stroke:#1e7a45,color:#8fd6ab,stroke-dasharray: 5 3

    subgraph CL["PARTICIPANT TIER — two edges, one enforcement point"]
        direction LR
        subgraph CN["Nostr edge — NIP-01 JSON / WebSocket · permanent"]
            direction LR
            D["Desktop · Tauri 2 + React 19"]:::client
            M["Mobile · Flutter"]:::client
            W["Web · repo browser + invite"]:::client
        end
        subgraph CM["Machine edge — Zenoh native · NIP-XP profile"]
            direction LR
            A["Agents · meridian-cli · ACP · dev-MCP"]:::client
            F["Feeders · machine + IoT frames"]:::next
        end
    end

    subgraph RL["RELAY TIER — one Axum process, the ONLY enforcement point"]
        direction TB
        B0["community bind — resolve_host → TenantContext<br/>before AUTH, EVENT, REQ, REST, media, git"]:::relay
        B1["admission · NIP-42 / NIP-98 · exchange profile"]:::relay
        B2["EVENT — kind gate · BIP-340 verify · scope · membership"]:::relay
        B3["REQ — access checked BEFORE registration"]:::relay
        B4["SubscriptionRegistry — 3-tier fan-out index"]:::relay
        B0 --> B1 --> B2 --> B4
        B1 --> B3 --> B4
    end

    subgraph ST["STACK TIER — one system of record per fact"]
        direction LR
        PG[("Postgres 17 — THE EVENT LOG<br/>events · members · workflows · audit · FTS")]:::store
        DF[("Dragonfly — STATE, never events<br/>presence · rate windows · pub/sub today")]:::store
        S3[("MinIO / S3 — BYTES, never events<br/>media blobs · git objects")]:::store
    end

    subgraph BT["BUS TIER — delivery between pods; the edge cannot tell"]
        direction LR
        EB{{"EventBus seam — Phase 1"}}:::next
        ZP["Zenoh peer — Phase 2<br/>IN-PROCESS in every pod<br/>pod ↔ pod direct, no broker hop"]:::next
        ZR["zenohd routers — Phase 4<br/>OPTIONAL: scale-out · cross-region<br/>only where a peer mesh stops scaling"]:::next
        EB --> ZP -.-> ZR
    end

    subgraph XT["REPLICA + ARTIFACT — downstream, never truth"]
        direction LR
        IP["IPFS — CID replication<br/>planned, contract unwritten"]:::next
        RS["ReductStore — REST artifact tier<br/>opt-in, no call sites"]:::next
    end

    CL --> RL
    RL <--> PG
    RL <--> DF
    RL <--> S3
    DF -.->|"bus migrates"| EB
    S3 -.->|"replication"| IP
    RL -.-> RS

Dashed nodes are decided but not built — there is no zenoh dependency in any Cargo.toml today, and IPFS has no spec yet. Dragonfly keeps presence, rate windows and the fenced arbiter after the bus moves: Zenoh is a pub/sub fabric, not a keyspace, so only PUBLISH/PSUBSCRIBE migrates.

Tier Owns Must never
Participant Rendering, drafts, local settings, key custody Hold authority, or talk to anything but its relay
Relay Host-derived tenant binding, admission, verification, ordering, membership, fan-out Delegate an access decision — it is the only enforcement point
Postgres The event log and every fact derived by transaction Be second. Nothing else may claim truth for a fact it stores
Dragonfly Ephemeral state with a TTL, plus cross-pod delivery Become a durable log
Object store Bytes too large for a row Hold anything the relay must interpret to decide
Bus Delivery between pods, QoS classes, interest routing Decide anything. A transport swap the NIP-01 edge can detect is not a transport swap
Replica / artifact Downstream copies and time-indexed artifact bytes Become a resolution path. A CID that answers a fetch S3 refused is an un-revocable blob

The relay orchestrates every subsystem by direct call, and no subsystem reaches sideways for a capability the relay owns — meridian-workflow never calls meridian-pubsub, and meridian-search never calls meridian-db, so fan-out and storage stay the relay's decisions. (meridian-workflow does depend on meridian-db for its own run and approval records; that is its store, not a lateral hop.) Every cross-subsystem interaction is a line of code in the relay, which is why one process can be reasoned about.


Design Law

Binding constraints on new protocol, identity and access-control surface. Each is cheap to honour up front, expensive to retrofit, and written down because the failure it prevents is silent rather than loud. Full text in AGENTS.md § Design Law; the conformance register is DIAGRAM.md § Part II.

Law What it forbids
Names never encode ownership org/team/channel paths — or symbol codes — in topic, storage or namespace keys. A reorg or re-designation becomes a fleet-wide rename
Standing is registry-derived and effective-dated Baking org or role into a credential; membership is [since, until), never a frozen claim
Add attributes, not mechanisms A policy DSL or rules engine. New capability = new attribute + new event kind
No third shim A mapping table or sync job between two enforcement points — one grant store, everyone reads it
Explicit denial, never a silent empty result A decision that cannot name the record that made it
Nothing is resolved until deployed and re-probed Closing work on green CI alone
No vocabulary without an enforcement point A kind, tag or grant name nothing reads — enforced by just check-kinds
Revocation is monotonic and absorbing Timestamp-wins resolution that could resurrect a revoked grant. Decided now, built at the second node — before any federation, never after
Scale by replica / function / tenant / traffic class Sharding by kind or NIP. A kind is a column in every shard, never a shard

Refusals, declined by citation rather than re-argued — a second durable log behind the relay (R2), a second read path for data the spine stores (R3), sharding telemetry kinds onto their own relays (R1), the edge peering with the spine (R4), caching grants at the bridge (R5), and building machinery for load nobody has measured yet (R8).


What runs today

The human, agent and service path is in production use; the machine and IoT path is specified and gated on measurement.

Surface State
Relay, channels, threads, DMs, canvases, media, search, audit chain shipping
Desktop app (Tauri 2 + React 19) shipping
meridian-cli — agent-first, JSON in / JSON out — and the ACP harness shipping
YAML workflows — message / reaction / schedule / webhook triggers shipping
Git hosting + NIP-34 events (patches, repo announcements, status), Nostr-signed push shipping
Huddles — WebSocket Opus voice relay, no external SFU shipping (recording, per-track publishing planned)
Shared compute mesh — relay-gated, OpenAI-compatible to agents shipping
Mobile (Flutter, iOS + Android) in development
Workflow approval gates partial — schema, API, MCP tool and UI exist; the executor does not yet suspend and resume
Opaque-frame data plane, MIP suite, MQTT bridge, symbology designed — see TASKS.md
Screenshots — the human and agent surface

A project channel where people and an agent coordinate on a release plan

People and agents collaborating in an engineering channel
Agents are members, not bots. Added to a channel the same way a person is.
The Add a channel dialog with search, filters, and channels to join or create
Rooms are cheap. Name it, describe it, scope it.
A video playing with frame-anchored comments in a side panel
Media with frame-anchored comments, stored via Blossom on S3/MinIO.

Quick start

Requires Docker and Hermit, which pins the whole toolchain (Rust, Node, pnpm, just) and downloads it on first use.

git clone https://git.office.ilab.zone/RAID/R2D2-MERIDIAN.git
cd R2D2-MERIDIAN
. ./bin/activate-hermit     # pinned toolchain — do this first, always
cp .env.example .env
./run.sh doctor             # toolchain, Docker, port conflicts, stale processes, env drift
./run.sh all                # services + relay + web in background, desktop in foreground

run.sh wraps just; it does not replace it. What it adds is port resolution — every port in the profile is probed by attempting a bind and walked upward until free, then written to the two existing override layers (.env.local and docker-compose.override.yml) so builds stay cache-warm. On a machine running several stacks, that removes an entire class of quiet failure: a lost port bind, or a relay dialling :5432 and reaching someone else's database.

Prefer the raw recipes for split-terminal work:

just setup        # deps, Docker services, migrations
just relay        # relay on ws://localhost:3000
just desktop-dev  # web-only dev server (fast iteration)
just dev          # full desktop app with native shell
just ci           # the complete local gate — run before every PR

For agents and services, set MERIDIAN_PRIVATE_KEY and MERIDIAN_RELAY_URL, then drive everything through meridian-cli — JSON on stdout, structured errors on stderr, designed for tool calls. The ACP harness injects both variables into managed agent subprocesses automatically.

Deploying rather than developing? Use the production Compose bundle in deploy/compose/ or the Helm charts in deploy/charts/. The root docker-compose.yml is for day-to-day development only.

Windows prerequisites

The agent shell tool runs commands under bash. Install Git for Windows — MERIDIAN resolves Git Bash at runtime. To point it at a different bash-compatible shell, set MERIDIAN_SHELL to its path; the agent's tool description updates to match.


Repository map

Everything MERIDIAN ships is built from this tree — the protocol, the relay, four client surfaces, two standalone services, the deployment charts and the specs. There is no sibling build pipeline to coordinate with, and no directory here is a vendored copy of something released elsewhere.

Top level

R2D2-MERIDIAN/
│
├── crates/              Rust workspace — the relay and every binary shipped beside it
├── REMAPPING/meridian-desktop/             Tauri 2 + React 19 — the primary human client
├── REMAPPING/meridian-web/                 Browser client served by the relay: repo browser + invite accept
├── REMAPPING/meridian-admin-web/           Read-only operator dashboard, served on the admin host
├── REMAPPING/meridian-mobile/              Flutter (iOS + Android) — Riverpod + hooks
│
├── migrations/          Forward-only Postgres schema, applied on relay startup
├── schema/              schema.sql — the flattened current schema, for reading only
├── deploy/              Helm charts, the production Compose stack, deploy scripts
│
├── docs/                Draft NIPs, the MIP register, formal models, operator runbooks
├── skills/              Project skill suite — how agent contributors route work
├── .settings/           Working docs: feature specs, gauntlet baselines, brand, references
├── .beads/              Issue database (bd) — the source of truth for task state
│
├── scripts/             Dev tooling, CI guard cores, release and contract scripts
├── perf/                Relay bus scaling harness
├── benchmarks/          harbor-meridian-orchestra multi-agent benchmark
├── examples/            countdown-bot, meadow-core, sample workflows
│
├── bin/                 Hermit-pinned toolchain — `. ./bin/activate-hermit`
├── patches/             pnpm patches for pinned upstream fixes
├── .github/             CI workflows, issue and PR templates, CODEOWNERS
│
├── Justfile             Every verification command — the entry point for all of them
├── run.sh               Local launcher: wraps `just` and deconflicts ports
└── docker-compose.yml   Development backing stack — Postgres, Dragonfly, MinIO

repos/, test-results/, .meridian-run/ and .control-plane/ appear once you run something and are gitignored. They are state, not source.

The components

Seven deployable things come out of this repo. Each one has exactly one home.

Component Built with Lives in Run it
Relay — the server, and the only enforcement point Rust · Axum · Tokio crates/meridian-relay just relay → ws://localhost:3000
Desktop — the primary human client Tauri 2 · React 19 · Vite · Tailwind REMAPPING/meridian-desktop/ just dev (native shell) · just desktop-dev (browser)
Web — the narrow browser surface React 19 · Vite REMAPPING/meridian-web/ built to REMAPPING/meridian-web/dist, served by the relay
Operator dashboard — read-only ops view React 19 · Vite · plain CSS REMAPPING/meridian-admin-web/ just admin → served on the admin host
Mobile Flutter · Riverpod · hooks REMAPPING/meridian-mobile/ just mobile-dev
Control plane — relay provisioning Rust · Axum crates/meridian-control-plane ./run.sh control-plane
Push gateway — APNs, blind by design Rust · Axum crates/meridian-push-gateway own image, own deployment

The relay, the control plane and the push gateway are three separate binaries with three separate container images (registry.r2d2.office.ilab.zone/meridian-*) and, in the control plane's case, its own database. They are not one service with flags: sharing the relay's database would collide two migration histories, and the push gateway is deliberately blind to message content.


The relay — crates/meridian-relay

One Axum process. It terminates the WebSocket protocol, answers the narrow HTTP surface, hosts git and huddle audio, and serves the REMAPPING/meridian-web/ and REMAPPING/meridian-admin-web/ bundles. Every access decision in the system is made here — no client, no bridge and no downstream consumer may make one.

crates/meridian-relay/src/
├── tenant.rs           host → TenantContext. Runs BEFORE any handler sees data
├── admission.rs        NIP-42 / NIP-98 admission and exchange profile
├── connection.rs       per-socket lifecycle
├── protocol.rs         NIP-01 frame parsing (EVENT / REQ / CLOSE / COUNT / AUTH)
├── handlers/           what each verb does — event, req, count, auth, close,
│                       ingest, moderation commands + authz, reports, feedback,
│                       push leases, relay admin, community provisioning
├── api/                the HTTP surface — events, media (Blossom), git smart HTTP,
│                       invites, nip05, operator, admin/, docs (OpenAPI), bridge
├── subscription.rs     SubscriptionRegistry — the 3-tier fan-out index
├── audio/              huddle voice relay (Opus over WebSocket, no external SFU)
├── tunnel/             inter-relay mesh sessions — fenced ownership, generations
├── conformance/        runtime trace emission for the TLA+ replay checker
├── router.rs           route table · state.rs  shared app state
├── metrics.rs          the Prometheus registry the operator dashboard reads
├── push_runtime.rs     push lease evaluation · workflow_sink.rs  workflow triggers
└── storage_sweep.rs    retention and TTL enforcement for ephemeral kinds

The relay orchestrates every subsystem crate by direct call, and no subsystem reaches sideways for a capability the relay owns. That is why one process can be reasoned about — see System shape.

Desktop — REMAPPING/meridian-desktop/

A Tauri 2 app: a Rust native shell around a React 19 webview. The split matters — anything touching the OS keychain, the filesystem, a native socket or a subprocess lives in Rust; everything else is React.

REMAPPING/meridian-desktop/
├── src/                    the React 19 front end
│   ├── app/                shell, router, community remount boundary, shortcuts
│   ├── features/           one folder per product area (channels, chat, agents,
│   │                       huddle, forum, projects, search, settings, workflows,
│   │                       moderation, onboarding, keys, mesh-compute, …)
│   ├── shared/             api client, UI primitives, hooks, theme, kind constants
│   ├── brand/              globe + wordmark renderers the control plane inlines
│   └── testing/            the E2E mock Tauri bridge
├── src-tauri/              the Rust native shell
│   ├── src/commands/       every Tauri IPC command the front end may call
│   ├── src/relay*.rs       native WebSocket client and relay admission
│   ├── src/secret_store.rs OS keychain custody for private keys
│   ├── src/media_proxy.rs  local proxy so the webview can load relay media
│   ├── src/managed_agents/ agent subprocess supervision
│   ├── src/huddle/         native audio capture and playback
│   └── src/migration/      local database migration on upgrade
└── tests/e2e/              Playwright specs (mock bridge + live relay)

Two traps this layout sets, both documented in REMAPPING/meridian-desktop/AGENTS.md:

  • REMAPPING/meridian-desktop/src-tauri is excluded from the root Cargo workspace. cargo test at the repo root does not run it. Use cargo test --manifest-path REMAPPING/meridian-desktop/src-tauri/Cargo.toml.
  • Switching community remounts React but does not reload the page. Any module-level cache you add must be reset in resetCommunityState(), or data from the previous community leaks into the next one.

Web — REMAPPING/meridian-web/

Deliberately narrow, and kept that way. It is not a browser port of the desktop app; it exists so that a link works without an install:

  • src/features/repos/ — the repo browser behind git smart HTTP
  • src/features/invite/ — invite acceptance

The relay serves the built bundle. Adding a third feature here is a decision, not a routine change — the desktop app is where client surface belongs.

Operator dashboard — REMAPPING/meridian-admin-web/

About ten files, no state library, no design system, no Tailwind. Its value is that an operator can read the whole thing in one sitting, so it stays dependency-light on purpose.

Read-only by contract. Moderation actions are Nostr command kinds handled by the relay; a write added here would bypass the audit and authorization pipeline. src/types.ts mirrors the relay's admin response contract in crates/meridian-relay/src/api/admin/ — the two are updated together and tests/routes.spec.ts fails on drift. A metric the relay does not report renders as "Not reported", never as zero: an absent metric is not evidence of health.

Mobile — REMAPPING/meridian-mobile/

Flutter, Riverpod + flutter_hooks, Catppuccin theming matched to desktop. Features under lib/features/ (activity, channels, forum, home, invites, pairing, profile, pulse, search, settings), shared code in lib/shared/.

Event kinds in lib/shared/relay/nostr_models.dart must stay in sync with REMAPPING/meridian-desktop/src/shared/constants/kinds.ts. Agents may run flutter test, flutter analyze and dart format — never flutter run, build, clean or upgrade.

Control plane and push gateway

meridian-control-plane provisions relays: account sessions, Nostr identity binding, and the <name>.relays.meridian.localtest.me:PORT URLs handed back to clients. It enforces an Origin check against MERIDIAN_CONTROL_PUBLIC_ORIGIN, and it rebuilds a signed operator string that must match the relay's RELAY_OPERATOR_API_ORIGIN byte-for-byte — a relay left running across a port change rejects every provisioning call as unauthorized. Restart it rather than debugging the signature.

meridian-push-gateway is a blind, capability-gated NIP-PL gateway to APNs. It forwards a wake signal; it does not learn what the message said.

The Rust workspace — crates/

Twenty-eight crates. The split is deliberate: meridian-core has no I/O so the protocol can be tested without a database, and every subsystem is a library the relay calls rather than a service that calls back.

Relay and protocol

Crate Owns
meridian-relay The WebSocket relay server — main entry point; also hosts git and huddle audio
meridian-core Zero-I/O core: event types, BIP-340 verification, filter matching, the kind registry
meridian-db Postgres event store and data access layer
meridian-auth Authentication and authorization (NIP-42 / NIP-98)
meridian-pubsub Cross-pod event bus — the EventBus seam, its Dragonfly backend, fan-out, presence
meridian-search Postgres full-text search, scoped by community
meridian-audit Hash-chain audit log
meridian-media Blossom/S3 media storage, validation, thumbnails
meridian-relay-mesh Inter-relay QUIC mesh: transport, membership, the fenced wire contract
meridian-conformance Trace schema and replay checker for docs/spec/MultiTenantRelay.tla
meridian-openapi Vendored Scalar renderer and OpenAPI reference pages

Standalone services

Crate Owns
meridian-control-plane Relay provisioning: account sessions, identity binding, community creation
meridian-push-gateway Blind, capability-gated NIP-PL gateway to APNs
meridian-pair-relay Ephemeral sidecar relay for NIP-AB device pairing handshakes

Agent surface

Crate Owns
meridian-cli The agent-first CLI — JSON in, JSON out, structured exit codes
meridian-acp ACP harness bridging Meridian events to AI agents
meridian-agent Minimal ACP-compliant agent — non-streaming, tool-calls-as-output
meridian-dev-mcp Developer MCP server — shell and file-edit tools
meridian-workflow YAML-as-code workflow engine (evalexpr conditions)
meridian-persona Parser and loader for .persona.md persona packs
meridian-harness All-in-one bundle of ACP, agent and dev MCP

Git over Nostr

Crate Owns
git-sign-nostr NIP-GS commit and tag signing with a Nostr secp256k1 key
git-credential-nostr Git credential helper producing NIP-98 auth headers

Shared libraries and tooling

Crate Owns
meridian-sdk Typed Nostr event builders for Meridian operations
meridian-ws-client Shared NIP-42 WebSocket client — connect, auth, publish
meridian-admin Operator CLI for relay administration
meridian-pairing-cli CLI for NIP-AB device pairing interop testing
meridian-test-client Integration test client and the E2E suite
meridian-test-db Test-database resolution, and the guard that refuses to destroy a non-disposable database

This table is checked against the Cargo workspace in both directions by just check-architecture-map: a crate added without a row fails CI, and so does a row for a crate that no longer exists.

macOS note for git-credential-nostr

Configure a host-scoped empty helper entry before the nostr entry. This resets the inherited osxkeychain helper and prevents spurious fatal: failed to store output on success. See the credential-helper setup.

Where the rules live

Every directory above carries an AGENTS.md that is a binding contract for its subtree, not a description of it. The root AGENTS.md holds Design Law and the child index; each child owns its local rules and indexes its own children. Before editing a file, read the root document plus every AGENTS.md on the path to it — the closer document wins on local detail, and no child may weaken a parent's constraint.

Contract Governs
crates/AGENTS.md The Rust workspace, with nested contracts for relay, core, db, acp, agent, cli, test-client
REMAPPING/meridian-desktop/AGENTS.md The desktop app; nested under src/features/, src-tauri/, tests/
REMAPPING/meridian-mobile/AGENTS.md Flutter rules, the file-size ceiling, which flutter commands are safe
REMAPPING/meridian-web/AGENTS.md · REMAPPING/meridian-admin-web/AGENTS.md The two browser surfaces and the relay admin API contract
migrations/AGENTS.md Forward-only schema, checksum-frozen files, partition and fence invariants
docs/AGENTS.md NIPs, MIPs, TLA+/Tamarin models, operator guides
deploy/AGENTS.md Helm charts and the Compose stack; values, schema and test contract
scripts/AGENTS.md Dev tooling, the shared lint guard cores, release and contract scripts
.github/AGENTS.md Required checks, release automation, DCO, action pinning
.settings/AGENTS.md Feature specs, gauntlet baselines, the brand kit, reference material

Protocol surface

NIP-01 over WebSocket is the primary API. Everything on the human, agent and service path is an event; a new capability is a new kind integer in meridian-core/src/kind.rs plus a handler, never a new endpoint-specific JSON API. That buys realtime fan-out, NIP-29 scoping and the existing auth pipeline for free.

The HTTP surface is deliberately narrow and preserves the same host-derived community boundary: NIP-11/NIP-05 metadata, POST /events, POST /query, POST /count, workflow webhooks, Blossom media, git smart HTTP, git policy hooks, health probes.

That surface publishes an OpenAPI 3.1 document and renders it, and both are served by the process they describe — the relay at /docs, the control plane at its port root. The document is generated from the handlers that answer the requests (utoipa_axum derives each route from its operation, so an endpoint cannot ship undocumented), and the renderer is vendored into the binary rather than loaded from a CDN, so a deployment with no egress gets the same page as one on the open internet. The WebSocket API is not in it — a REQ subscription is not an HTTP operation, and a reference that implied otherwise would read as a complete description of an API it only partly covers.

Repo-local extensions live as draft NIPs in docs/nips/, with TLA+ and Tamarin models for the multi-tenant isolation and authorization guarantees in docs/formal/. just check-kinds fails CI on any kind, tag or grant name that no enforcement point reads — declared vocabulary without an enforcement point is the standing risk when a repo carries this many drafts.


Programme and roadmap

  • DIAGRAM.md is the system view: Part I the running system, Part II the GOAT/EFDI conformance register, Part III the throughput case against a central broker, Part IV the maturation map from one relay that does everything to a thin spine plus independently deployable consumers — adding no second read path, no second policy store, and no client-visible protocol change.
  • TASKS.md is the 10-week decentralized-relay programme that executes Part III's ratified call: a frozen MIP specification suite, a reference implementation of the opaque-frame data plane, an MQTT interoperability bridge at the edge, and a conformance suite. It mirrors Bead IDs; it never leads them.
  • Beads is the source of truth for task state. bd ready for available work, bd show <id> for any ID cited in the docs. Do not keep a parallel TODO list.

The live gate is measurement, not design. Zenoh's direction is settled — zenohd routers are the bus end-state — but the Phase 0 benchmark still decides which traffic classes move first and what may be claimed in public: the circulating ~4–5M msg/s figures are measured against Kafka, MQTT and CycloneDDS, not against Dragonfly, so the gap over our path is still unmeasured. The opaque-frame path must likewise be measured before anything downstream is optimised. Machinery built for load nobody has measured is inventory (R8).


Quality gates

just ci           # fmt + clippy + desktop lint + unit tests + builds
just test-unit    # unit tests, no infrastructure
just test         # integration suite (requires Postgres + Dragonfly)
just check        # every guard: kinds, skills, brand, migrations, contracts

Read the exit code, not the output — piping a gate into tail or grep reports the filter's status, so a failed run looks clean. Clippy passing does not mean fmt passes, and a green cargo test says nothing about the lint gate: a test build compiles through an unused import that -D warnings rejects.

Pre-commit hooks auto-fix formatting and re-stage; pre-push runs clippy and fast unit tests. Commit with git commit -s — the DCO check fails any PR with a commit missing a Signed-off-by trailer.

Additional standing rules: no unsafe; no new unwrap() or expect() in production paths; new public API carries doc comments.


What we deliberately do not build

  • A second system of record. Postgres holds the events. Derived views are legitimate downstream of the spine — cursor-bearing, freshness-stamped, and invisible at the NIP-01 edge — but nothing else claims truth for a fact the spine stores.
  • A policy engine. Grants are enumerable dated rows precisely so that a denial can name the record that decided it. A rule- or formula-based DSL is un-auditable by enumeration, and is refused by name.
  • Payload interpretation on the hot path. The relay does not decode a track, fuse a picture, detect a spoof, or render a symbol. Those are consumer jobs, and the refusal to read is what makes the throughput possible.
  • Federation before monotonic revocation. Multi-master peering would let a concurrent grant resurrect a revoked one by wall-clock timestamp. The fix lands before the second writer, because after is too late.
  • A throughput number without its profile. Including in this file.

Documentation

Document Covers
ARCHITECTURE.md Component reference — crates, pipelines, kind ranges, subsystem boundaries
DIAGRAM.md System view, conformance register, throughput case, maturation map
TASKS.md The 10-week decentralized-relay programme and the MIP suite
AGENTS.md Design Law, agent conventions, the STELLAR doc hierarchy
CONTRIBUTING.md Setup, code style, PR process, how to add kinds / commands / endpoints
TESTING.md Multi-agent E2E guide
RELEASING.md Release flow, candidate tags, deployment provenance
SECURITY.md · GOVERNANCE.md · CODE_OF_CONDUCT.md Disclosure, decision rights, conduct
VISION.md and companions Sovereign · Projects · Agents · Mesh · Activity · Moderation

MERIDIAN — Multidomain Exchange for Realtime Integration of Data Innovation with Allied Nations
MIT · Built by STELLAR