Protocol council, 2026-08-20, parallel mode, five seats — Protocol Steward (chair), Interop Skeptic, Enforcement Auditor, plus Revocation Guardian and Second-Node Skeptic borrowed at the charter cap of two. Each seat received only its charter row, the decision sentence and the evidence. Call: approve with conditions, 5/5 concurring, no seat blocking at close. **Withdrawn, not deferred — and none of the prior call's conditions fired.** `meridian-ded` approved on-chain payments on 2026-08-02 against three reversal conditions: a competing upstream payments NIP merging, a measured fake-claim burden, and an upstream x402 Kaspa scheme. All three verified NOT fired. The reversal proceeds on different grounds and the record says so plainly: the requirement was never present, rather than refuted — no counterparty outside the issuing community, no reachable public chain from an exercise network. A scope held "deferred" on conditions that never fired is inventory with no owner or expiry, and it keeps a chain-settled second system of record alive as a live option. The chair initially **blocked** the withdrawal limb, because "withdrawn" was being chosen blind to the one trigger the prior council said would point the other way: the NIPs mirror was pinned at `8228afb5`, dated 2026-07-31 — *before* the call being reversed. Lifted only after fetching refs (working tree still pinned, confirmed by `rev-parse`) and re-running the sweep: `origin/master` = `656cecc7` (2026-08-08), delta two commits touching only `29.md` and `47.md`, zero hits tree-wide for `x402|kaspa|erc-20|eip-681|eip-3009|stablecoin`. `git ls-remote` returned the same head on 2026-08-20, so the twelve-day gap is upstream quiet, not stale data. That check also revealed it is a **weaker instrument than it looks**, which is why `docs/AGENTS.md` now carries the rule rather than the anecdote: upstream `c538775` moved NIP-47's authorization models and wallet extensions out of `nostr-protocol/nips` entirely, into `nostr-wallet-connect/nwc` — 332 of 351 changed lines — behind an open numeric `extensions` namespace that nothing here mirrors. A future payment scheme can now merge upstream without ever appearing in the grep that just cleared this decision. So a scan record must state the mirror's commit hash *and* its commit date separately from the scan date, and name the surface it actually covered; the mirror is merged-state only and cannot see an open PR. Three amendments the council made to the spec, all at DRAFT deliberately because deferring any of them forces a renumber: - **Kind `50416`, a consumer-signed spend hold**, accepted before serving. Without it `50412` is a post-hoc self-report by the party that gained the resource, and ingest rejects an overdraft only after the GPU-seconds are already gone. - **The epoch rule is rewritten.** Wire `epoch` is advisory and ignored on input; the relay assigns it per `(community, holder, class)`; it is absorbing at *any* epoch, following the MIP-MS pattern, resolved fail-closed. Without this a peer transfer at `epoch n+1` reinstates a holder reclaimed at `epoch n` — the monotonic-revocation law, live on one relay today. - **The arbiter is registered at scaling axis A1**, with `SELECT … FOR UPDATE` in the same transaction as the ledger append. MIP-RT is a **non-monetary internal resource instrument, not a payment rail**, and the support matrix now says exactly that: nine payment rows keep `Reject` with strengthened rationale, an MIP-RT row is added, and there is **no NIP-CP row** — that was the withdrawn bead's ask. The register row cites `meridian-8t6h` (Phase 1), not the closing Phase 0 bead: `just check-mips` requires a non-terminal row to name a live bead, so pointing it at `meridian-yzr6` would turn the gate red the moment that bead closed. Bead created and row repointed before closing, not after. Verified: `check-mips`, `check-architecture-map`, `check-kinds`, `check-feature-specs`, `check-external-copy` all exit 0. `check-stellar` and `check-gauntlet` exit 1, both pre-existing and provably not from this work — `check-stellar`'s findings are byte-identical to a baseline captured before any edit, and `check-gauntlet` fails on F001/F003 marked open against beads already closed in the committed export at HEAD. Beads: meridian-yzr6 (Phase 0), meridian-grxu (minutes), meridian-8t6h (Phase 1) Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
10 KiB
docs/ — Protocol Specs & Operator Guides
Purpose
Durable reference material: the repo-local NIP extensions that define wire
protocol, machine-checked formal specs, deployment/operations guides,
troubleshooting playbooks, and audience-orientation guides — documents that
teach an incoming contributor a whole surface from one file, bound to the code
by a provenance table. Not a changelog and not a design diary — top-level
VISION*.md files hold direction, CHANGELOG.md holds history.
Ownership
| Path | Owns |
|---|---|
nips/ |
Repo-local NIP extensions: NIP-AA, AB, AE, AM, AO, AP, CW, DV, ER, GC, GS, IA, OA, PL, RS, WF, WP — the normative wire contracts. GC (git collaboration) and WF (channel workflows) are profiles: they restrict and pin down kinds defined upstream (NIP-34) or already shipped, rather than adding new ones |
mips/ |
Meridian Implementation Possibilities — repo-local proposals documenting what Meridian-compatible relay and client software may implement, under the two-letter scheme, with README.md as the register and lifecycle. Register rows without a file are specified in TASKS.md § 5 and migrate here under MRDN-201. DRONE-PROTOCOL-COVERAGE.md is the ETL coverage register behind the vehicle-protocol profiles — a coverage map, never a commitment schedule. ENFORCED is the only state that permits NIP-11 advertisement |
spec/ |
Machine-checked models: MultiTenantRelay.tla/.cfg, GitOnObjectStore.tla/.cfg (TLA+), MultiTenantAuth.spthy, NIP-AB.spthy (Tamarin) |
formal/ |
STATEFUL_GATEWAY.md and nip-pl/ — executable acceptance + mutation tests in Python (acceptance.py, delivery.py, *_mutation.py, mutation_test.py, NOTE.md) |
admin/ |
Operator dashboard guide |
| Orientation | ux-end-to-end-workflow.md — the end-to-end user journey for an incoming UI/UX designer: acts 1–3 in depth, act 4 indexed, with the surface map, the port map, required-versus-optional user input, and a provenance table binding every claim to its source file. Its §8 platform table states reachability on a stock install (preview flags default off), not module existence |
| Loose guides | multi-tenant-relay.md, multi-tenant-conformance.md, push-gateway-deployment.md, control-plane-deployment.md, community-registration-launch-policy.md, git-on-object-storage.md, meridian-shared-compute-dev.md, bridge-channel-window.md, nostr-nip-support-matrix.md, linux-rendering-troubleshooting.md, welcome-kickoff-silent-failures.md, workflow-variables.md, projects-qa-runbook.md, MCP_DRIVEN_HOOKS.md |
plans/ |
Dated closeout and hardening plans for completed programs — a record of what was done, not a live queue |
benchmarks/ |
Measured performance records and the capacity contract they hold the relay to (relay-capacity-contract.md), with raw runs under data/ |
runbooks/ |
Executable operator diagnosis, containment, recovery, rollback, and verification procedures for relay and control-plane alerts, backup/restore, and network isolation |
assets/ |
Images referenced by docs (screenshots/, icons) |
Local Contracts
- A NIP in
nips/is normative. It follows upstream NIP format: title, one-line summary, status line (draft/optional/relay), Motivation, then the kind and tag definitions. Implementation must match the document; if they diverge, one of them is a bug — say which in the change. - A new event kind that other clients must understand gets a written spec, and its
integer goes in
crates/meridian-core/src/kind.rs. A kind with no written contract cannot be implemented by a second client. Whether that spec is a NIP or a MIP is decided by the constituency rule below — not by how important the kind is, and not by which directory is closer to hand. - Constituency decides NIP vs MIP, and it is the first question, not the last.
A NIP is a proposal to the Nostr commons: if anyone outside this deployment would
want it, it is a NIP and belongs upstream. If the only constituency is this
deployment, it is a MIP and lives in
mips/. Calling a deployment-specific spec a NIP pollutes both namespaces and implies an upstream ambition we do not have; the precedent is SLIPs to BIPs. Deconfliction is not theoretical — upstream already uses two-letter suffixes (A0, A4, B0, B7, BE, C0, C7, CC, EE, F4, 5A, 7D) expanding into exactly our space, andNIP-SF/FP/GR/KBare all plausible upstream names for other things. Before minting either, check whether an upstream NIP already covers the behaviour — profiling one spends no letter and gets third-party interoperation for free. That is the cheapest of the three outcomes and the most commonly missed. - The repo-local NIPs in
nips/are being migrated, not grandfathered.MRDN-201(TASKS.md § 5.1) is a P0 story: every repo-local proposal becomesMIP-XXand moves tomips/, leavingnips/for profiles of genuinely upstream NIPs. Until it runs, an existingNIP-XXhere is a legacy name and never evidence that a new repo-local spec may be filed as a NIP. (The count of those files is stated and CI-guarded in the rootAGENTS.md; it is deliberately not repeated here, because a second hand-maintained number is the failure that law names.)NIP-ERis the one that cannot be renamed silently. It is the only house name on the wire —SUPPORTED_EXTENSIONSincrates/meridian-relay/src/nip11.rsis["nip-er"], andENFORCED_MIPSis empty — so every other rename is invisible to clients, and this one is not. Clients feature-detect that string. Advertise both names through a deprecation window, or keepnip-erpermanently as an alias; do not swap it. The failure is already on this repo's record in the opposite direction: NIP-17 was advertised whilekind:10050was rejected, and conformant clients detected support and then failed silently. nips/is the only place a repo-local NIP may live — never beside the crate that implements it, and the row above must list it. NIP-AB spent time undercrates/meridian-core/src/pairing/: complete, Tamarin-modelled, and invisible to anyone following this file. Any kind-vs-spec audit reportedkind:24134as unspecified, because a spec off the documented path is indistinguishable from a missing one.- Never renumber or repurpose a published NIP letter or kind. Supersede with a new document and mark the old one.
- A kind collision scan carries the limits of the instrument that produced it.
Record the mirror's commit hash and its commit date, separately from the
date you scanned — they are routinely weeks apart, and a scan run against a
mirror pinned before the decision it justifies proves nothing. Two limits are
structural and must be written into the scan record rather than assumed away:
the mirror is merged-state only, so it cannot see an open upstream PR
claiming a number; and since upstream commit
c538775(2026-08-08),nostr-protocol/nipsno longer covers payment-scheme vocabulary — NIP-47's authorization models and wallet extensions moved out togithub.com/nostr-wallet-connect/nwc, behind an open numericextensionsnamespace that nothing here mirrors. A grep over the NIPs mirror is therefore a narrower instrument than it looks, and a clean result states which surface it actually covered. Advance the pin and re-run before any constant lands. - A MIP in
mips/carries its lifecycle state on the status line and its row inmips/README.md, and the two must agree. A profile MIP adds no mechanism — it binds an existing envelope, payload schema, and link profile to one real feed. The enforcement obligation is staged with the lifecycle, because a MIP whose wire format is not yet frozen can only name speculative checks: aDRAFTowes a § Enforcement point naming who refuses a non-conforming frame; fromIMPLEMENTABLEit owes a § Enforcement points table naming a check that fails for every normative MUST.just check-mipsenforces the staging, the register/file state agreement, and that each row names a live bead; a MIP whose vocabulary nothing reads also failsjust check-kinds. Neither waits for an audit. spec/models are checked, not decorative. If a change alters relay admission, replay ordering, or object-store semantics, update the model and the tracers incrates/meridian-relay/src/conformance/—crates/meridian-conformancereplays runtime traces againstMultiTenantRelay.tla.formal/nip-pl/mutation tests must keep failing on mutants. A mutation that the acceptance suite accepts means the suite lost its teeth — fix the suite, not the mutant.- Operator guides state the current deployment, not history. Delete superseded procedure rather than annotating it.
- Upstream NIPs are referenced, never copied: https://github.com/nostr-protocol/nips.
Work Guidance
- Put implementation-adjacent rules in the owning
AGENTS.md(they must be read before editing code); put protocol contracts and operator procedure here. - Reference docs from code with a path, and keep the path true when files move —
a stale
docs/reference in a//!comment is a silent dead end.
Verification
cargo test -p meridian-conformance
cargo test -p meridian-test-client --test conformance_multitenant
cd docs/formal/nip-pl # models run from their own directory
python3 acceptance.py && python3 mutation_test.py
python3 delivery.py && python3 delivery_mutation.py
python3 fixed_payload.py && python3 fixed_payload_mutation.py
docs/formal/nip-pl/NOTE.md states the run order and the models' honest limits —
they enumerate bounded abstract transitions, not SQL schedules or network
behavior. TLA+/Tamarin models are checked with their own tools (TLC, Tamarin);
see the header of each model file.
Child STELLAR Index
None — docs/ is governed by this file, including nips/, spec/, formal/,
and admin/.