Renames the product to Meridian across 1826 files: 24 crates (codebase-chat-* -> meridian-*), the Flutter package, env vars (CODEBASE_CHAT_* -> MERIDIAN_*), the deep-link scheme (meridian://), Postgres GUCs, Helm charts, skills, and the agent surface. White-labels every external identity onto self-hosted infrastructure: hosts move from *.codebase.design to *.meridian.r2d2.office.ilab.zone, images to registry.r2d2.office.ilab.zone/meridian-*, the repo slug to r2d2/meridian, and bundle IDs to zone.ilab.office.r2d2.meridian.*. The Block staging relay and the four Block-internal build repos are not reachable from a self-hosted deployment and are no longer referenced. The mark becomes a pixel M. It is 5x6 rather than a square 5x5 because the avatar-pile mask asserts the hole clears the glyph's right edge: at 5x5 that edge moves from 68.4% to 73% of the tile, which overruns the 56px team-card hole outright and leaves the other three piles under a pixel. At 5x6 the aspect is 0.833 against the retired C's 0.800, so all four masks clear it unchanged. All 59 materializations are regenerated from the generators; `just check-brand` passes. Four things are deliberately NOT renamed, because they match what was *stored* rather than what now ships. Rewriting any of them makes a migration no-op on exactly the installs it exists to repair: - Frozen migrations 0001-0030. Their SHA-256 digests are pinned in n-minus-one-pins.json and embedded in the attested N-1 image. The new vocabulary lands as forward migration 0031, which dual-reads all three generations' GUCs, lock names, app profiles and mesh d_tags. The push-gateway's own 0001 is likewise restored byte-identical, with 0002 widening its app_profile CHECK. - Legacy namespace chains. xyz.block.codebasechat.app is *prepended* to LEGACY_RELEASE_IDENTIFIERS and its dev/localStorage twins, per the rule in legacy_dirs.rs that a previous rename already broke once. - Bead IDs (codebaseChat-*), which are cited from commits and docs. - CHANGELOG history and upstream issue links. The Codebase-era persona ids are added to RETIRED_PERSONAS with their prompts verbatim, but deliberately NOT to RETIRED_PERSONA_REPLACEMENTS: that map drives migration::retire_agents, which deletes deployed instances, and its safety argument is that the successor is already deployed alongside. That held for Buzz->Codebase; nothing provisions a Meridian agent on an install that already onboarded, so mapping these would delete a working agent and leave nothing in its place. The brand-guard self-test changes axis: the M is symmetric about its vertical axis, so a mirrored M *is* the canonical M and asserting a rejection there would assert a bug. It now flips top-to-bottom (the mark reads as a W) and pins the horizontal symmetry so the coupling is visible if the mark ever becomes asymmetric again. Verified: cargo check --workspace --all-targets clean, just fix-all clean, flutter analyze clean, just check-skills pass, check-brand 59/59, brand-core 9/9, avatarPileMask 4/4, starter-avatar contrast 2/2. Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
7.2 KiB
🛡️ Meridian Moderation — Your community, your rules
Someone spams #general at midnight. A member taps Report — a category, an optional note, done. The report doesn't appear in anyone's feed; it lands in a queue only the community's owners and admins can see. In the morning an admin opens the queue, finds three reports against the same account, deletes the messages, and times the account out for a day. The room sees an honest marker where the spam was. The author gets a message explaining why. The reporter gets a message saying it was handled. Nobody else saw anything.
A Meridian community is a trust group with its own rules, and rules only matter if the people who own the room can enforce them. Meridian moderation gives community owners and admins the full loop: members report, the community's own owners and admins see and act, the relay enforces, and everyone affected hears the truth about what happened. The relay provides the mechanics — queue, authority, enforcement, audit, notices. The community provides the judgment. Meridian doesn't decide what your rules are; it makes sure you can actually have them.
Most of the nostr ecosystem treats moderation as admission policy — allow lists, block lists, a hook that rejects an event at the door. Meridian treats it as workflow: a report is the start of a human decision, not a trigger for an automatic one. That difference is the whole design.
Two Layers, Not One
Moderation splits the way it does on every serious platform:
Community moderation — subjective, per-community rule enforcement. Your owners and admins decide what's spam in your community, what crosses your line, who gets a second chance. This layer belongs to the community and never reaches past it: an admin's authority ends at the community boundary, structurally, because every moderation decision is scoped to the tenant it was made in.
Platform safety — the severe class: illegal content, network-level abuse, legal reporting obligations. That is never delegated to community admins. A community owner or admin can escalate a report upward, and the escalation is recorded durably for the platform operator's safety process. The community layer is the front line; the platform layer is the backstop.
This document is about the first layer. The second has its own lane.
What You See
As a member, every message has a Report action. Pick why — spam, profanity, illegal content, impersonation, or another supported reason — add context if you want, send. Your report is private: it is never broadcast, never stored as a public event, never visible to the person you reported. It goes to the people who can act on it, and only them.
As an owner or admin, you have a queue. Reports arrive grouped by target, newest first, with the reporter's identity visible to you — accountability runs both ways — and never to the reported author. From the queue you act in one motion: dismiss, delete the message, kick, timeout, ban, or escalate. Reasons travel where they should — to the audit trail, to the tombstone, to the restricted user — without exposing private report context to the room.
As the room, a removed message leaves an honest tombstone — "removed by a community moderator," with a sanitized reason — instead of a silent hole. The room learns that the rules are real without republishing the offense.
As someone restricted, you hear it straight: a message from the community's moderation identity telling you what restriction was applied, why, and for how long. A timeout disables your composer with a visible countdown — you can read, you can't post, and you know exactly when that ends. No silent write-drops, no shadow bans, no guessing.
As the reporter, you hear the outcome. The loop closes. Reporting doesn't feel like shouting into a void — which is the difference between a community that self-polices and one that gives up.
The Mechanics That Matter
-
Reports are signals, never triggers. No user report auto-removes anything. Reports are gameable; human judgment is the gate. The queue aggregates and an owner or admin decides.
-
Reports are private structural state. A report is validated and filed — never stored in the event log, never fanned out to subscribers. Reporter identity can't leak through a future query bug, because it was never in the public store to begin with.
-
Moderation actions are signed commands. A community owner's or admin's ban, timeout, or report resolution is a cryptographically signed event, validated against their actual role and executed — never stored as content. Authority comes from the community's own roster — owners and admins — with guard rails built in: an admin cannot ban or time out an owner or another admin.
-
Enforcement lives at the identity seam. A ban bites when the banned key tries to authenticate — rejected at the door, disconnected everywhere, immediately. A timeout is a write-block with a stated expiry. Enforcement isn't scattered through the codebase as filters; it happens where identity is established, which is why it can't be sidestepped.
-
The important decisions are audited. Bans, timeouts, report dismissals, escalations, and report resolutions write durable audit rows — who, what, whom, why, when — with the decision recorded separately from its enforcement, so the trail never claims something happened that didn't. Message removals also leave visible tombstones for the room. The full report record (including reporter identity and notes) stays moderator-only; the public sees only the sanitized reason.
-
The wire uses nostr where nostr has the right primitive. Reports are NIP-56. Group roles and membership actions are NIP-29. Meridian's moderation commands and private reads fill the workflow gaps those NIPs deliberately leave open.
Honest Edges
Escalation is a hook today, not a pipeline. Escalating writes a durable, queryable record for the platform operator — but the platform-side inbox that consumes it is a separate build. The substrate is there; the tooling above it comes next.
Two roles, not three. Owners and admins moderate. There is no volunteer-moderator tier yet — deliberately. Authority is structured as capabilities, so adding a moderator tier later is a policy change, not a rewrite. We'd rather ship a loop that works and grow the org chart when communities ask for it.
Notices are best-effort. The DMs that close the loop never block enforcement — a ban lands even if the notice fails. Enforcement is the promise; notification is the courtesy. A later platform-escalation pass should also make escalated reports say exactly that, instead of reusing the generic handled message.
No automod. Nothing scans content before it posts. Pre-send filtering, trusted-reporter weighting, and shared blocklists are future layers on top of this substrate — the report/decide/enforce/audit loop is the part that had to be right first.
The Point
A community you can't moderate isn't yours — it just has your name on it. The relay is the workspace; moderation is what makes it governable by the people it belongs to. Judgment stays human. Enforcement is structural. Everyone affected hears the truth.
Meridian — your community, your rules.