feat: rebrand Codebase Chat to Meridian

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>
This commit is contained in:
Josh Belke 2026-08-04 14:06:04 -04:00
commit de4cfec61e
1826 changed files with 18798 additions and 19686 deletions

View file

@ -1 +0,0 @@
../../skills/add-codebase-nostr-capability

View file

@ -0,0 +1 @@
../../skills/add-meridian-nostr-capability

View file

@ -1 +0,0 @@
../../skills/build-codebase-client

View file

@ -0,0 +1 @@
../../skills/build-meridian-client

View file

@ -1 +0,0 @@
../../skills/drive-codebase-maturity

View file

@ -0,0 +1 @@
../../skills/drive-meridian-maturity

View file

@ -1 +0,0 @@
../../skills/evolve-codebase-relay

View file

@ -0,0 +1 @@
../../skills/evolve-meridian-relay

View file

@ -1 +0,0 @@
../../skills/extend-codebase-agent-surface

View file

@ -0,0 +1 @@
../../skills/extend-meridian-agent-surface

View file

@ -1 +0,0 @@
../../skills/measure-codebase-relay

View file

@ -0,0 +1 @@
../../skills/measure-meridian-relay

View file

@ -1 +0,0 @@
../../skills/qa-codebase-projects

View file

@ -0,0 +1 @@
../../skills/qa-meridian-projects

View file

@ -1 +0,0 @@
../../skills/secure-codebase-boundaries

View file

@ -0,0 +1 @@
../../skills/secure-meridian-boundaries

View file

@ -1 +0,0 @@
../../skills/verify-codebase-change

View file

@ -0,0 +1 @@
../../skills/verify-meridian-change

View file

@ -1 +0,0 @@
../../skills/add-codebase-nostr-capability

View file

@ -0,0 +1 @@
../../skills/add-meridian-nostr-capability

View file

@ -1 +0,0 @@
../../skills/build-codebase-client

View file

@ -0,0 +1 @@
../../skills/build-meridian-client

View file

@ -1 +0,0 @@
../../skills/drive-codebase-maturity

View file

@ -0,0 +1 @@
../../skills/drive-meridian-maturity

View file

@ -1 +0,0 @@
../../skills/evolve-codebase-relay

View file

@ -0,0 +1 @@
../../skills/evolve-meridian-relay

View file

@ -1 +0,0 @@
../../skills/extend-codebase-agent-surface

View file

@ -0,0 +1 @@
../../skills/extend-meridian-agent-surface

View file

@ -1 +0,0 @@
../../skills/measure-codebase-relay

View file

@ -0,0 +1 @@
../../skills/measure-meridian-relay

View file

@ -1 +0,0 @@
../../skills/qa-codebase-projects

View file

@ -0,0 +1 @@
../../skills/qa-meridian-projects

View file

@ -1 +0,0 @@
../../skills/secure-codebase-boundaries

View file

@ -0,0 +1 @@
../../skills/secure-meridian-boundaries

View file

@ -1 +0,0 @@
../../skills/verify-codebase-change

View file

@ -0,0 +1 @@
../../skills/verify-meridian-change

View file

@ -1 +0,0 @@
../../skills/add-codebase-nostr-capability

View file

@ -0,0 +1 @@
../../skills/add-meridian-nostr-capability

View file

@ -1 +0,0 @@
../../skills/build-codebase-client

View file

@ -0,0 +1 @@
../../skills/build-meridian-client

View file

@ -1 +0,0 @@
../../skills/drive-codebase-maturity

View file

@ -0,0 +1 @@
../../skills/drive-meridian-maturity

View file

@ -1 +0,0 @@
../../skills/evolve-codebase-relay

View file

@ -0,0 +1 @@
../../skills/evolve-meridian-relay

View file

@ -1 +0,0 @@
../../skills/extend-codebase-agent-surface

View file

@ -0,0 +1 @@
../../skills/extend-meridian-agent-surface

View file

@ -1 +0,0 @@
../../skills/measure-codebase-relay

View file

@ -0,0 +1 @@
../../skills/measure-meridian-relay

View file

@ -1 +0,0 @@
../../skills/qa-codebase-projects

View file

@ -0,0 +1 @@
../../skills/qa-meridian-projects

View file

@ -1 +0,0 @@
../../skills/secure-codebase-boundaries

View file

@ -0,0 +1 @@
../../skills/secure-meridian-boundaries

View file

@ -1 +0,0 @@
../../skills/verify-codebase-change

View file

@ -0,0 +1 @@
../../skills/verify-meridian-change

View file

@ -1,5 +1,5 @@
# =============================================================================
# Codebase Chat Backend — Local Development Environment
# Meridian Backend — Local Development Environment
# =============================================================================
# Copy this file to .env and adjust as needed:
# cp .env.example .env
@ -51,14 +51,14 @@ COMPOSE_PROFILES=tools,auth,observability
# -----------------------------------------------------------------------------
# Database (Postgres 17)
# -----------------------------------------------------------------------------
DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat
DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/meridian
# Optional read-replica URL; unset/blank keeps all reads on the writer.
# READ_DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5433/codebase_chat
# READ_DATABASE_URL=postgres://meridian:meridian_dev@localhost:5433/meridian
PGHOST=localhost
PGPORT=5432
PGUSER=codebase_chat
PGPASSWORD=codebase_chat_dev
PGDATABASE=codebase_chat
PGUSER=meridian
PGPASSWORD=meridian_dev
PGDATABASE=meridian
# -----------------------------------------------------------------------------
# Dragonfly (drop-in Redis replacement — same wire protocol, reports
@ -71,7 +71,7 @@ REDIS_URL=redis://localhost:6379
# bind, so change BOTH values together.
# REDIS_HOST_PORT=6390
# Max connections in the relay's shared Redis pool (default 16).
# CODEBASE_CHAT_REDIS_POOL_SIZE=16
# MERIDIAN_REDIS_POOL_SIZE=16
# Dragonfly sizing. Defaults suit a dev laptop; raise for load testing.
# CONSTRAINT: maxmemory must be >= 256MiB per thread or Dragonfly refuses to
# start and the container just exits 1 ("There are N threads, so X MiB are
@ -81,28 +81,28 @@ REDIS_URL=redis://localhost:6379
# Max connections in each of the relay's Postgres pools — writer and, when
# READ_DATABASE_URL is set, reader (default 50).
# CODEBASE_CHAT_DB_POOL_SIZE=50
# MERIDIAN_DB_POOL_SIZE=50
# -----------------------------------------------------------------------------
# Typesense (search)
# -----------------------------------------------------------------------------
TYPESENSE_API_KEY=codebase_chat_dev_key
TYPESENSE_API_KEY=meridian_dev_key
TYPESENSE_URL=http://localhost:8108
# -----------------------------------------------------------------------------
# Relay (WebSocket server)
# -----------------------------------------------------------------------------
# Bind address for the relay (host:port)
CODEBASE_CHAT_BIND_ADDR=0.0.0.0:3000
MERIDIAN_BIND_ADDR=0.0.0.0:3000
# Public WebSocket URL — used in NIP-42 auth challenges
RELAY_URL=ws://localhost:3000
# Stable relay signing key. Set this in dev if you want REST-created forum posts
# to keep resolving to the original author across relay restarts.
# CODEBASE_CHAT_RELAY_PRIVATE_KEY=<32-byte hex private key>
# MERIDIAN_RELAY_PRIVATE_KEY=<32-byte hex private key>
# Optional: path to the web UI dist directory. When set, the relay serves
# the web frontend at / for browser requests. Leave unset for local dev
# (use `just web` for Vite HMR instead).
# CODEBASE_CHAT_WEB_DIR=./web/dist
# MERIDIAN_WEB_DIR=./web/dist
# -----------------------------------------------------------------------------
# Relay operator API (community provisioning)
@ -132,52 +132,52 @@ RELAY_URL=ws://localhost:3000
# browser sign-in, account <-> Nostr identity binding, and community
# provisioning via the relay operator API above.
#
# Scoped database. MUST NOT be the relay's `codebase_chat` database — the two
# Scoped database. MUST NOT be the relay's `meridian` database — the two
# would collide on sqlx's `_sqlx_migrations` table. `just setup` creates it.
# CODEBASE_CHAT_CONTROL_DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat_control_plane
# MERIDIAN_CONTROL_DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/meridian_control_plane
# Per-replica connection budget and maximum time to wait for the pool. For an
# HPA deployment, reserve maxReplicas * maxConnections plus headroom in Postgres.
# CODEBASE_CHAT_CONTROL_DATABASE_MAX_CONNECTIONS=10
# CODEBASE_CHAT_CONTROL_DATABASE_ACQUIRE_TIMEOUT_SECONDS=5
# CODEBASE_CHAT_CONTROL_BIND_ADDR=0.0.0.0:8090
# CODEBASE_CHAT_CONTROL_HEALTH_ADDR=0.0.0.0:8091
# MERIDIAN_CONTROL_DATABASE_MAX_CONNECTIONS=10
# MERIDIAN_CONTROL_DATABASE_ACQUIRE_TIMEOUT_SECONDS=5
# MERIDIAN_CONTROL_BIND_ADDR=0.0.0.0:8090
# MERIDIAN_CONTROL_HEALTH_ADDR=0.0.0.0:8091
# This service's externally reachable origin.
# CODEBASE_CHAT_CONTROL_PUBLIC_ORIGIN=http://127.0.0.1:8090
# MERIDIAN_CONTROL_PUBLIC_ORIGIN=http://127.0.0.1:8090
#
# DNS suffix for minted community hosts. `*.localtest.me` is public wildcard
# DNS that resolves to 127.0.0.1, which avoids editing /etc/hosts; note that
# `*.localhost` does NOT resolve on macOS. Fully offline? Add an explicit
# /etc/hosts entry per community and set the suffix to match.
# CODEBASE_CHAT_CONTROL_COMMUNITY_HOST_SUFFIX=communities.localtest.me
# MERIDIAN_CONTROL_COMMUNITY_HOST_SUFFIX=communities.localtest.me
# Port appended to minted authorities. The relay's host resolution preserves a
# non-default port, so this must match the relay's listening port or no
# community will resolve. Leave unset behind a :443 ingress.
# CODEBASE_CHAT_CONTROL_COMMUNITY_HOST_PORT=3000
# CODEBASE_CHAT_CONTROL_RELAY_SCHEME=ws
# MERIDIAN_CONTROL_COMMUNITY_HOST_PORT=3000
# MERIDIAN_CONTROL_RELAY_SCHEME=ws
#
# Browser communities page at {base}/communities. `off` makes the route 404
# rather than hiding its controls; the API it fronts stays available either way.
# CODEBASE_CHAT_CONTROL_COMMUNITIES_UI=on
# Keep this equal to CODEBASE_CHAT_MAX_COMMUNITIES_PER_OWNER on the relay.
# CODEBASE_CHAT_CONTROL_COMMUNITY_LIMIT=3
# MERIDIAN_CONTROL_COMMUNITIES_UI=on
# Keep this equal to MERIDIAN_MAX_COMMUNITIES_PER_OWNER on the relay.
# MERIDIAN_CONTROL_COMMUNITY_LIMIT=3
#
# Must match RELAY_OPERATOR_API_ORIGIN exactly (see the warning above).
# CODEBASE_CHAT_CONTROL_RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
# MERIDIAN_CONTROL_RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
# Secret key whose pubkey appears in RELAY_OPERATOR_PUBKEYS. nsec or 64-char hex.
# CODEBASE_CHAT_CONTROL_RELAY_OPERATOR_SECRET_KEY=<nsec or 64-char hex>
# MERIDIAN_CONTROL_RELAY_OPERATOR_SECRET_KEY=<nsec or 64-char hex>
#
# Identity provider: `dev` or `oidc`.
# dev — accepts ANY email address with no verification whatsoever. Refuses to
# start unless CODEBASE_CHAT_CONTROL_ALLOW_DEV_LOGIN=1 is also set, and
# start unless MERIDIAN_CONTROL_ALLOW_DEV_LOGIN=1 is also set, and
# reports `degraded: dev-login` on its readiness probe. Local use only.
# oidc — authorization-code + PKCE against a real issuer. Use this for any
# deployment reachable by anyone else. Keycloak is already in
# docker-compose.yml on :8180 for local OIDC testing.
# CODEBASE_CHAT_CONTROL_IDENTITY_PROVIDER=dev
# CODEBASE_CHAT_CONTROL_ALLOW_DEV_LOGIN=1
# CODEBASE_CHAT_CONTROL_OIDC_ISSUER=http://localhost:8180/realms/codebase-chat
# CODEBASE_CHAT_CONTROL_OIDC_CLIENT_ID=codebase-chat-control-plane
# CODEBASE_CHAT_CONTROL_OIDC_CLIENT_SECRET=<client secret>
# MERIDIAN_CONTROL_IDENTITY_PROVIDER=dev
# MERIDIAN_CONTROL_ALLOW_DEV_LOGIN=1
# MERIDIAN_CONTROL_OIDC_ISSUER=http://localhost:8180/realms/meridian
# MERIDIAN_CONTROL_OIDC_CLIENT_ID=meridian-control-plane
# MERIDIAN_CONTROL_OIDC_CLIENT_SECRET=<client secret>
#
# Point the desktop app at this control plane. There is no default and no
# hosted fallback: with either of these missing the app reports community
@ -187,33 +187,33 @@ RELAY_URL=ws://localhost:3000
# A packaged app launched from Finder, the Dock, or a deep link inherits no
# shell environment, so a build that must create communities has to set these at
# BUILD time — they are compiled in as a fallback to the runtime values.
# CODEBASE_CHAT_CONTROL_PLANE_URL=http://127.0.0.1:8090/api/codebase
# CODEBASE_CHAT_CONTROL_PLANE_ORIGIN=http://127.0.0.1:8090
# MERIDIAN_CONTROL_PLANE_URL=http://127.0.0.1:8090/api/codebase
# MERIDIAN_CONTROL_PLANE_ORIGIN=http://127.0.0.1:8090
# Shared Redis-backed admission limits. Defaults shown below; each value must
# be a positive integer.
# CODEBASE_CHAT_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=60
# CODEBASE_CHAT_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=300
# CODEBASE_CHAT_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10
# CODEBASE_CHAT_RATE_LIMIT_AGENT_STANDARD_MESSAGES_PER_MIN=120
# CODEBASE_CHAT_RATE_LIMIT_AGENT_STANDARD_API_CALLS_PER_MIN=600
# CODEBASE_CHAT_RATE_LIMIT_AGENT_ELEVATED_MESSAGES_PER_MIN=300
# CODEBASE_CHAT_RATE_LIMIT_AGENT_PLATFORM_MESSAGES_PER_MIN=600
# MERIDIAN_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=60
# MERIDIAN_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=300
# MERIDIAN_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10
# MERIDIAN_RATE_LIMIT_AGENT_STANDARD_MESSAGES_PER_MIN=120
# MERIDIAN_RATE_LIMIT_AGENT_STANDARD_API_CALLS_PER_MIN=600
# MERIDIAN_RATE_LIMIT_AGENT_ELEVATED_MESSAGES_PER_MIN=300
# MERIDIAN_RATE_LIMIT_AGENT_PLATFORM_MESSAGES_PER_MIN=600
# -----------------------------------------------------------------------------
# Git (NIP-34 bare repositories)
# -----------------------------------------------------------------------------
# Root directory for ephemeral Git workspaces and the disposable pack cache.
# Default: ./repos (relative to CWD).
# CODEBASE_CHAT_GIT_REPO_PATH=./repos
# CODEBASE_CHAT_GIT_MAX_PACK_BYTES=524288000
# CODEBASE_CHAT_GIT_MAX_REPO_BYTES=1048576000
# MERIDIAN_GIT_REPO_PATH=./repos
# MERIDIAN_GIT_MAX_PACK_BYTES=524288000
# MERIDIAN_GIT_MAX_REPO_BYTES=1048576000
# Process-local immutable pack/index cache. Zero disables retention.
# CODEBASE_CHAT_GIT_PACK_CACHE_PATH=./repos/.pack-cache
# CODEBASE_CHAT_GIT_PACK_CACHE_MAX_BYTES=5368709120
# CODEBASE_CHAT_GIT_PACK_CACHE_MAX_CONCURRENT_POPULATIONS=2
# MERIDIAN_GIT_PACK_CACHE_PATH=./repos/.pack-cache
# MERIDIAN_GIT_PACK_CACHE_MAX_BYTES=5368709120
# MERIDIAN_GIT_PACK_CACHE_MAX_CONCURRENT_POPULATIONS=2
# Periodically repair relay-authored 30618 state from authoritative manifests.
# CODEBASE_CHAT_GIT_STATE_RECONCILE_INTERVAL_SECS=60
# MERIDIAN_GIT_STATE_RECONCILE_INTERVAL_SECS=60
# -----------------------------------------------------------------------------
# S3-Compatible Object Storage (media + Git/CAS)
@ -221,25 +221,25 @@ RELAY_URL=ws://localhost:3000
# The local MinIO container is reachable from host processes at localhost:9000.
# Path style keeps the bucket in the URL path and is required by this local DNS
# setup. Use `virtual` only when the provider requires bucket-as-subdomain URLs.
CODEBASE_CHAT_S3_ENDPOINT=http://localhost:9000
CODEBASE_CHAT_S3_ACCESS_KEY=codebase_chat_dev
CODEBASE_CHAT_S3_SECRET_KEY=codebase_chat_dev_secret
CODEBASE_CHAT_S3_BUCKET=codebase-chat-media
CODEBASE_CHAT_S3_REGION=us-east-1
CODEBASE_CHAT_S3_ADDRESSING_STYLE=path
MERIDIAN_S3_ENDPOINT=http://localhost:9000
MERIDIAN_S3_ACCESS_KEY=meridian_dev
MERIDIAN_S3_SECRET_KEY=meridian_dev_secret
MERIDIAN_S3_BUCKET=meridian-media
MERIDIAN_S3_REGION=us-east-1
MERIDIAN_S3_ADDRESSING_STYLE=path
# -----------------------------------------------------------------------------
# Media Upload Admission
# -----------------------------------------------------------------------------
# Bound image/video parser and storage work admitted by one relay process.
# CODEBASE_CHAT_MEDIA_MAX_CONCURRENT_UPLOADS=8
# CODEBASE_CHAT_MEDIA_MAX_CONCURRENT_UPLOADS_PER_PUBKEY=2
# CODEBASE_CHAT_MEDIA_UPLOADS_PER_MINUTE=30
# MERIDIAN_MEDIA_MAX_CONCURRENT_UPLOADS=8
# MERIDIAN_MEDIA_MAX_CONCURRENT_UPLOADS_PER_PUBKEY=2
# MERIDIAN_MEDIA_UPLOADS_PER_MINUTE=30
# Require Blossom t=get auth and relay membership for GET/HEAD /media/*.
# Keep off until desktop/mobile/CLI clients that attach media read auth are deployed.
# CODEBASE_CHAT_REQUIRE_MEDIA_GET_AUTH=false
# MERIDIAN_REQUIRE_MEDIA_GET_AUTH=false
# Legacy alias accepted by the relay while rollout docs catch up:
# CODEBASE_CHAT_REQUIRE_MEDIA_READ_AUTH=false
# MERIDIAN_REQUIRE_MEDIA_READ_AUTH=false
# -----------------------------------------------------------------------------
# Ephemeral Channels (TTL testing)
@ -247,95 +247,95 @@ CODEBASE_CHAT_S3_ADDRESSING_STYLE=path
# Override the TTL for all ephemeral channels (in seconds). When set, any
# channel created with a TTL tag will use this value instead of the
# client-provided one. Unset to use the client-provided TTL.
# CODEBASE_CHAT_EPHEMERAL_TTL_OVERRIDE=60
# MERIDIAN_EPHEMERAL_TTL_OVERRIDE=60
# How often the reaper checks for expired ephemeral channels (default: 60s).
# CODEBASE_CHAT_REAPER_INTERVAL_SECS=5
# MERIDIAN_REAPER_INTERVAL_SECS=5
# -----------------------------------------------------------------------------
# Logging / Tracing
# -----------------------------------------------------------------------------
RUST_LOG=codebase_chat_relay=debug,codebase_chat_datastore=info,codebase_chat_db=debug,codebase_chat_auth=debug,codebase_chat_pubsub=debug,tower_http=debug
RUST_LOG=meridian_relay=debug,meridian_datastore=info,meridian_db=debug,meridian_auth=debug,meridian_pubsub=debug,tower_http=debug
# Optional OpenTelemetry-only target filter. This is deliberately independent
# from RUST_LOG so log verbosity changes cannot break trace parentage.
# CODEBASE_CHAT_OTEL_FILTER=codebase_chat_relay=info,codebase_chat_datastore=info
# MERIDIAN_OTEL_FILTER=meridian_relay=info,meridian_datastore=info
# OTLP tracing endpoint (optional — leave unset to disable)
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# -----------------------------------------------------------------------------
# ACP (Agent Communication Protocol — codebase-chat-acp harness)
# ACP (Agent Communication Protocol — meridian-acp harness)
# -----------------------------------------------------------------------------
# The ACP harness bridges Codebase Chat events to AI agents. Each env var below maps
# The ACP harness bridges Meridian events to AI agents. Each env var below maps
# to a CLI flag of the same name (lowercase, hyphens → underscores). All values
# are optional unless noted; defaults are shown in comments.
#
# Quick start:
# CODEBASE_CHAT_PRIVATE_KEY=<hex> CODEBASE_CHAT_RELAY_URL=ws://localhost:3000 codebase-chat-acp
# MERIDIAN_PRIVATE_KEY=<hex> MERIDIAN_RELAY_URL=ws://localhost:3000 meridian-acp
# ── Identity & auth ──────────────────────────────────────────────────────────
# Nostr private key (hex or bech32). REQUIRED — identifies the agent on the relay.
# CODEBASE_CHAT_PRIVATE_KEY=<32-byte hex or nsec1… private key>
# MERIDIAN_PRIVATE_KEY=<32-byte hex or nsec1… private key>
# Relay WebSocket URL the harness connects to.
# Note: the relay itself uses RELAY_URL (above); this is the ACP harness's
# connection target — they happen to point at the same place in local dev.
# CODEBASE_CHAT_RELAY_URL=ws://localhost:3000
# MERIDIAN_RELAY_URL=ws://localhost:3000
# ── Agent subprocess ─────────────────────────────────────────────────────────
# Binary to spawn as the AI agent (e.g. "goose", "codex-acp", "claude-code").
# CODEBASE_CHAT_ACP_AGENT_COMMAND=goose
# MERIDIAN_ACP_AGENT_COMMAND=goose
# Comma-separated arguments passed to the agent binary.
# Goose default: "acp". Codex/Claude default: "" (empty).
# CODEBASE_CHAT_ACP_AGENT_ARGS=acp
# MERIDIAN_ACP_AGENT_ARGS=acp
# Binary for an optional MCP server sidecar (e.g. codebase-chat-dev-mcp for codebase-chat-agent).
# CODEBASE_CHAT_ACP_MCP_COMMAND=
# Binary for an optional MCP server sidecar (e.g. meridian-dev-mcp for meridian-agent).
# MERIDIAN_ACP_MCP_COMMAND=
# Number of parallel agent subprocesses (1–32).
# CODEBASE_CHAT_ACP_AGENTS=1
# MERIDIAN_ACP_AGENTS=1
# Desired LLM model ID. Applied to every new ACP session.
# Use `codebase-chat-acp models` to discover available model IDs.
# CODEBASE_CHAT_ACP_MODEL=
# Use `meridian-acp models` to discover available model IDs.
# MERIDIAN_ACP_MODEL=
# ── Timeouts & sessions ──────────────────────────────────────────────────────
# Max seconds per agent turn before timeout (default 320 = ~5 min).
# CODEBASE_CHAT_ACP_TURN_TIMEOUT=320
# MERIDIAN_ACP_TURN_TIMEOUT=320
# Max turns per session before proactive rotation. 0 = disabled (rotate only
# on MaxTokens / MaxTurnRequests). Recommended: 50 for long-running agents.
# CODEBASE_CHAT_ACP_MAX_TURNS_PER_SESSION=0
# MERIDIAN_ACP_MAX_TURNS_PER_SESSION=0
# ── Prompts ──────────────────────────────────────────────────────────────────
# System prompt injected into every agent session (inline text).
# CODEBASE_CHAT_ACP_SYSTEM_PROMPT=
# MERIDIAN_ACP_SYSTEM_PROMPT=
# Path to a file containing the system prompt (mutually exclusive with above).
# CODEBASE_CHAT_ACP_SYSTEM_PROMPT_FILE=
# MERIDIAN_ACP_SYSTEM_PROMPT_FILE=
# Message sent to the agent immediately after session creation.
# CODEBASE_CHAT_ACP_INITIAL_MESSAGE=
# MERIDIAN_ACP_INITIAL_MESSAGE=
# ── Heartbeat ────────────────────────────────────────────────────────────────
# Seconds between heartbeat prompts. 0 = disabled. Must be 0 or ≥10.
# Recommended: 60 for long-running agents to prevent idle session timeouts.
# CODEBASE_CHAT_ACP_HEARTBEAT_INTERVAL=0
# MERIDIAN_ACP_HEARTBEAT_INTERVAL=0
# Heartbeat prompt text (inline). Mutually exclusive with file variant.
# CODEBASE_CHAT_ACP_HEARTBEAT_PROMPT=
# MERIDIAN_ACP_HEARTBEAT_PROMPT=
# Path to a file containing the heartbeat prompt.
# CODEBASE_CHAT_ACP_HEARTBEAT_PROMPT_FILE=
# MERIDIAN_ACP_HEARTBEAT_PROMPT_FILE=
# ── Desktop development ──────────────────────────────────────────────────────
# DEV-only: replay first-run onboarding and the Welcome Team kickoff on each
# app launch while keeping the current identity and relay data.
# VITE_CODEBASE_CHAT_FORCE_FRESH_ONBOARDING=true
# VITE_MERIDIAN_FORCE_FRESH_ONBOARDING=true
# DEV-only: resolve the dev identity before launch and pass it to the desktop
# app as CODEBASE_CHAT_PRIVATE_KEY, so the app never opens the OS keyring.
# app as MERIDIAN_PRIVATE_KEY, so the app never opens the OS keyring.
# Two effects:
# - macOS stops asking for the login-keychain password after every rebuild
# (`tauri dev` emits an ad-hoc signature whose hash changes each build, so
@ -344,66 +344,66 @@ RUST_LOG=codebase_chat_relay=debug,codebase_chat_datastore=info,codebase_chat_db
# "Always Allow" holds because Apple's signature is stable.
# - worktrees share the main checkout's identity and skip onboarding.
# `just desktop-standalone` ignores this and stays on its isolated identity.
# NOTE: an explicit CODEBASE_CHAT_PRIVATE_KEY above wins. Setting that var for
# NOTE: an explicit MERIDIAN_PRIVATE_KEY above wins. Setting that var for
# the ACP harness therefore also becomes the desktop app's identity.
# CODEBASE_CHAT_SHARE_IDENTITY=1
# MERIDIAN_SHARE_IDENTITY=1
# ── Subscription & filtering ─────────────────────────────────────────────────
# Subscribe mode: "mentions" (default), "all", or "config" (rule-based).
# CODEBASE_CHAT_ACP_SUBSCRIBE=mentions
# MERIDIAN_ACP_SUBSCRIBE=mentions
# Comma-separated event kind numbers to subscribe to (overrides mode defaults).
# CODEBASE_CHAT_ACP_KINDS=
# MERIDIAN_ACP_KINDS=
# Comma-separated channel UUIDs to limit subscription scope.
# CODEBASE_CHAT_ACP_CHANNELS=
# MERIDIAN_ACP_CHANNELS=
# Set to true to disable the @-mention filter in mentions mode.
# CODEBASE_CHAT_ACP_NO_MENTION_FILTER=false
# MERIDIAN_ACP_NO_MENTION_FILTER=false
# Path to TOML config file for rule-based subscriptions (config mode).
# CODEBASE_CHAT_ACP_CONFIG=./codebase-chat-acp.toml
# MERIDIAN_ACP_CONFIG=./meridian-acp.toml
# ── Dedup & self-ignore ──────────────────────────────────────────────────────
# How to handle duplicate events: "queue" (default) or "drop".
# CODEBASE_CHAT_ACP_DEDUP=queue
# MERIDIAN_ACP_DEDUP=queue
# Set to true to process the agent's own messages (default: ignore self).
# CODEBASE_CHAT_ACP_NO_IGNORE_SELF=false
# MERIDIAN_ACP_NO_IGNORE_SELF=false
# ── Context ──────────────────────────────────────────────────────────────────
# Max context messages fetched for thread replies and DMs (0–100). 0 = disabled.
# CODEBASE_CHAT_ACP_CONTEXT_MESSAGE_LIMIT=12
# MERIDIAN_ACP_CONTEXT_MESSAGE_LIMIT=12
# ── Presence & typing ────────────────────────────────────────────────────────
# Set to true to disable automatic online/offline presence status.
# CODEBASE_CHAT_ACP_NO_PRESENCE=false
# MERIDIAN_ACP_NO_PRESENCE=false
# Set to true to disable typing indicators while the agent is processing.
# CODEBASE_CHAT_ACP_NO_TYPING=false
# MERIDIAN_ACP_NO_TYPING=false
# ── Advanced tuning ──────────────────────────────────────────────────────────
# Event channel buffer capacity (WebSocket → harness). Increase for
# high-throughput agents. Minimum 1.
# CODEBASE_CHAT_ACP_EVENT_BUFFER=256
# MERIDIAN_ACP_EVENT_BUFFER=256
# ── Legacy aliases ───────────────────────────────────────────────────────────
# These are accepted for backward compatibility but the canonical names above
# are preferred:
# CODEBASE_CHAT_ACP_PRIVATE_KEY → CODEBASE_CHAT_PRIVATE_KEY
# MERIDIAN_ACP_PRIVATE_KEY → MERIDIAN_PRIVATE_KEY
# Optional relay join policy. Markdown is served by the relay so every join
# surface can present the same documents. Each document and the independent age
# attestation are optional; configuring any one enables policy acceptance.
# CODEBASE_CHAT_TERMS_OF_SERVICE_MARKDOWN="# Terms of Service\n\nFull terms here."
# CODEBASE_CHAT_PRIVACY_POLICY_MARKDOWN="# Privacy Policy\n\nFull policy here."
# CODEBASE_CHAT_AGE_ATTESTATION_REQUIRED=true
# MERIDIAN_TERMS_OF_SERVICE_MARKDOWN="# Terms of Service\n\nFull terms here."
# MERIDIAN_PRIVACY_POLICY_MARKDOWN="# Privacy Policy\n\nFull policy here."
# MERIDIAN_AGE_ATTESTATION_REQUIRED=true
# 32-byte hexadecimal root for encrypted workflow credentials. Required before
# any {{secret.NAME}} reference can be written or executed. Supply it through
# the deployment secret/KMS path and include the key version in recovery drills.
# CODEBASE_CHAT_WORKFLOW_SECRET_KEY=
# MERIDIAN_WORKFLOW_SECRET_KEY=
# Expand/contract barrier. Keep false while N-1 is a supported rollback target.
# Enabling permits {{secret.NAME}} definitions and removes legacy webhook
# credentials as definitions are migrated/edited.
CODEBASE_CHAT_WORKFLOW_SECRET_REFS_ENABLED=false
MERIDIAN_WORKFLOW_SECRET_REFS_ENABLED=false

2
.github/AGENTS.md vendored
View file

@ -18,7 +18,7 @@ other tree.
| `mobile-release-candidate.yml` | Mobile RC publishing |
| `signed-macos-canary.yml`, `linux-canary.yml`, `windows-canary.yml` | Platform canaries |
| `sprig.yml` | Sprig harness bundle |
| `benchmark-harbor.yml` | `benchmarks/harbor-codebase-chat-orchestra` run |
| `benchmark-harbor.yml` | `benchmarks/harbor-meridian-orchestra` run |
Also: `CODEOWNERS`, `PULL_REQUEST_TEMPLATE.md`, `ISSUE_TEMPLATE/`
(`bug-report.md`, `feature-request.md`, `config.yml`).

2
.github/CODEOWNERS vendored
View file

@ -1 +1 @@
* @block/codebase-chat-oss-team
* @block/meridian-oss-team

View file

@ -1,6 +1,6 @@
---
name: Bug report
about: Report a reproducible bug in Codebase Chat
about: Report a reproducible bug in Meridian
labels: bug
---
@ -17,7 +17,7 @@ What did you expect to happen?
**Version and platform**
Find your version at the bottom of the Settings sidebar. Write "unknown" if you can't determine it.
- Codebase Chat version:
- Meridian version:
- OS:
**Logs / additional context**

View file

@ -18,4 +18,4 @@ Anything else that helps — links, screenshots, prior art.
---
Before opening: please [search open issues and PRs](https://github.com/codebase/codebase-chat/issues?q=is%3Aopen) for duplicates — link the closest one, or say "none found".
Before opening: please [search open issues and PRs](https://github.com/r2d2/meridian/issues?q=is%3Aopen) for duplicates — link the closest one, or say "none found".

View file

@ -8,20 +8,20 @@ name: Auto-tag on Release PR Merge
# relay-release/<v> → tag relay-v<v> → docker.yml (relay image)
# chart-release/<v> → tag chart-v<v> → helm-chart.yml (main helm chart)
# push-chart-release/<v> → tag push-chart-v<v> → push-gateway-helm-chart.yml
# any internal PR that bumps deploy/charts/codebase-chat/Chart.yaml `version`
# any internal PR that bumps deploy/charts/meridian/Chart.yaml `version`
# → tag chart-v<v> → helm-chart.yml (helm chart)
#
# Mobile candidate tags do not come from merged PRs. Operators create immutable
# mobile-v<v>-rc.N tags directly from remote main with scripts/mobile-release.sh,
# then hand the exact tag to codebase-chat-releases.
# then hand the exact tag to meridian-releases.
#
# Release tags are created with a short-lived token from the dedicated
# codebase-chat-release-bot GitHub App. GitHub attributes the ref creation to that
# meridian-release-bot GitHub App. GitHub attributes the ref creation to that
# App, so the consumer's `on.push.tags` trigger runs normally. The workflow's
# default GITHUB_TOKEN remains read-only and is never used to create a tag.
#
# Mobile is manual-only by infosec necessity: OSS `codebase/codebase-chat` CI must
# not trigger CI in the private `codebase-chat-releases` repo. A human feeds the exact
# Mobile is manual-only by infosec necessity: OSS `r2d2/meridian` CI must
# not trigger CI in the private `meridian-releases` repo. A human feeds the exact
# mobile candidate tag to the private Buildkite pipeline, which builds and
# ships mobile.
@ -69,8 +69,8 @@ jobs:
TAG_PREFIX="push-chart-v" ;;
*)
parent_sha="$(git rev-parse HEAD^)"
old_version="$(git show "${parent_sha}:deploy/charts/codebase-chat/Chart.yaml" 2>/dev/null | awk '/^version:/ {print $2}')"
VERSION="$(awk '/^version:/ {print $2}' deploy/charts/codebase-chat/Chart.yaml)"
old_version="$(git show "${parent_sha}:deploy/charts/meridian/Chart.yaml" 2>/dev/null | awk '/^version:/ {print $2}')"
VERSION="$(awk '/^version:/ {print $2}' deploy/charts/meridian/Chart.yaml)"
if [ -z "$old_version" ] || [ -z "$VERSION" ] || [ "$old_version" = "$VERSION" ]; then
echo "No release branch or chart version bump — nothing to tag"
echo "enabled=false" >> "$GITHUB_OUTPUT"
@ -93,8 +93,8 @@ jobs:
id: release-tagger
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.CODEBASE_CHAT_RELEASE_TAGGER_CLIENT_ID }}
private-key: ${{ secrets.CODEBASE_CHAT_RELEASE_TAGGER_PRIVATE_KEY }}
client-id: ${{ vars.MERIDIAN_RELEASE_TAGGER_CLIENT_ID }}
private-key: ${{ secrets.MERIDIAN_RELEASE_TAGGER_PRIVATE_KEY }}
permission-contents: write
- name: Create and push tag

View file

@ -1,14 +1,14 @@
name: Harbor Codebase Chat Orchestra
name: Harbor Meridian Orchestra
on:
push:
branches: [main]
paths:
- "benchmarks/harbor-codebase-chat-orchestra/**"
- "benchmarks/harbor-meridian-orchestra/**"
- ".github/workflows/benchmark-harbor.yml"
pull_request:
paths:
- "benchmarks/harbor-codebase-chat-orchestra/**"
- "benchmarks/harbor-meridian-orchestra/**"
- ".github/workflows/benchmark-harbor.yml"
permissions:
@ -32,13 +32,13 @@ jobs:
- name: Install uv
run: python -m pip install --disable-pip-version-check "uv>=0.8,<0.9"
- name: Test adapter
working-directory: benchmarks/harbor-codebase-chat-orchestra
working-directory: benchmarks/harbor-meridian-orchestra
run: |
uv sync --locked --extra dev
uv run --no-sync pytest -q
uv run --no-sync ruff check .
- name: Test provisioner
working-directory: benchmarks/harbor-codebase-chat-orchestra/testbed
working-directory: benchmarks/harbor-meridian-orchestra/testbed
run: |
uv sync --locked --extra dev
uv run --no-sync pytest -q

View file

@ -13,7 +13,7 @@ concurrency:
env:
CARGO_TERM_COLOR: always
CODEBASE_CHAT_TEST_POSTGRES_PASSWORD: codebase_chat_dev
MERIDIAN_TEST_POSTGRES_PASSWORD: meridian_dev
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.cache/ms-playwright
jobs:
@ -347,7 +347,7 @@ jobs:
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: |
target/ci/codebase-chat-relay
target/ci/meridian-relay
target/ci/git-credential-nostr
target/ci/backend-integration-tests.tar.zst
key: relay-artifacts-${{ runner.os }}-${{ hashFiles('crates/**', 'migrations/**', 'Dockerfile', 'Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml', '.github/workflows/ci.yml') }}
@ -368,12 +368,12 @@ jobs:
- name: Build relay artifacts
if: steps.relay-artifacts-cache.outputs.cache-hit != 'true'
run: |
cargo build --profile ci -p codebase-chat-relay -p git-credential-nostr
cargo build --profile ci -p meridian-relay -p git-credential-nostr
cargo nextest archive \
--cargo-profile ci \
-p codebase-chat-db \
-p codebase-chat-relay \
-p codebase-chat-test-client \
-p meridian-db \
-p meridian-relay \
-p meridian-test-client \
--lib \
--test e2e_event_reminder \
--archive-file target/ci/backend-integration-tests.tar.zst
@ -382,7 +382,7 @@ jobs:
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: |
target/ci/codebase-chat-relay
target/ci/meridian-relay
target/ci/git-credential-nostr
target/ci/backend-integration-tests.tar.zst
key: relay-artifacts-${{ runner.os }}-${{ hashFiles('crates/**', 'migrations/**', 'Dockerfile', 'Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml', '.github/workflows/ci.yml') }}
@ -391,7 +391,7 @@ jobs:
with:
name: desktop-e2e-relay
path: |
target/ci/codebase-chat-relay
target/ci/meridian-relay
target/ci/git-credential-nostr
target/ci/backend-integration-tests.tar.zst
if-no-files-found: error
@ -474,9 +474,9 @@ jobs:
docker logs "${container}" || true
return 1
}
wait_healthy "Postgres" "codebase-chat-postgres"
wait_healthy "Dragonfly" "codebase-chat-dragonfly"
wait_healthy "MinIO" "codebase-chat-minio"
wait_healthy "Postgres" "meridian-postgres"
wait_healthy "Dragonfly" "meridian-dragonfly"
wait_healthy "MinIO" "meridian-minio"
- name: Download relay binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@ -489,54 +489,54 @@ jobs:
# exits permanently on an unmapped host, so the 'localhost:3000'
# community MUST exist before the relay starts — the retry loop only
# handles late-seeded channels, not a late-seeded community. The relay
# migrates at boot via CODEBASE_CHAT_AUTO_MIGRATE, but that's too late for the
# migrates at boot via MERIDIAN_AUTO_MIGRATE, but that's too late for the
# pre-boot seed, so apply the schema here first (then drop AUTO_MIGRATE
# below). lower(host) is the unique index → ON CONFLICT target. psql
# isn't on PATH in hermit → exec into the codebase-chat-postgres container.
# isn't on PATH in hermit → exec into the meridian-postgres container.
env:
PGHOST: localhost
PGPORT: "5432"
PGUSER: codebase-chat
PGPASSWORD: codebase_chat_dev
PGDATABASE: codebase-chat
PGUSER: meridian
PGPASSWORD: meridian_dev
PGDATABASE: meridian
# Use the already-running docker postgres for desired-state planning instead of
# downloading an embedded Postgres from Maven Central (transient-fetch flake source).
PGSCHEMA_PLAN_HOST: localhost
PGSCHEMA_PLAN_PORT: "5432"
PGSCHEMA_PLAN_DB: codebase-chat
PGSCHEMA_PLAN_USER: codebase-chat
PGSCHEMA_PLAN_PASSWORD: codebase_chat_dev
PGSCHEMA_PLAN_DB: meridian
PGSCHEMA_PLAN_USER: meridian
PGSCHEMA_PLAN_PASSWORD: meridian_dev
run: |
./bin/pgschema apply --file schema/schema.sql --auto-approve
docker exec -i -e PGPASSWORD=codebase_chat_dev codebase-chat-postgres \
psql -U codebase_chat -d codebase_chat -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql
docker exec -e PGPASSWORD=codebase_chat_dev codebase-chat-postgres \
psql -U codebase_chat -d codebase_chat -qtA -c "
docker exec -i -e PGPASSWORD=meridian_dev meridian-postgres \
psql -U meridian -d meridian -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql
docker exec -e PGPASSWORD=meridian_dev meridian-postgres \
psql -U meridian -d meridian -qtA -c "
INSERT INTO communities (id, host)
VALUES ('00000000-0000-4000-8000-00000000c0de', 'localhost:3000')
ON CONFLICT (lower(host)) DO NOTHING
;"
- name: Start relay
run: |
chmod +x ./target/ci/codebase-chat-relay
chmod +x ./target/ci/meridian-relay
nohup env \
DATABASE_URL="postgres://codebase_chat:${CODEBASE_CHAT_TEST_POSTGRES_PASSWORD}@localhost:5432/codebase_chat" \
DATABASE_URL="postgres://meridian:${MERIDIAN_TEST_POSTGRES_PASSWORD}@localhost:5432/meridian" \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
CODEBASE_CHAT_BIND_ADDR=0.0.0.0:3000 \
CODEBASE_CHAT_REQUIRE_AUTH_TOKEN=false \
CODEBASE_CHAT_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
CODEBASE_CHAT_RECONCILE_CHANNELS=true \
CODEBASE_CHAT_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=100000 \
CODEBASE_CHAT_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=100000 \
CODEBASE_CHAT_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10000 \
CODEBASE_CHAT_GIT_PROBE_WRITERS=8 \
MERIDIAN_BIND_ADDR=0.0.0.0:3000 \
MERIDIAN_REQUIRE_AUTH_TOKEN=false \
MERIDIAN_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
MERIDIAN_RECONCILE_CHANNELS=true \
MERIDIAN_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=100000 \
MERIDIAN_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=100000 \
MERIDIAN_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10000 \
MERIDIAN_GIT_PROBE_WRITERS=8 \
SPROUT_REMINDER_SCHEDULER_INTERVAL_SECS=1 \
./target/ci/codebase-chat-relay > /tmp/codebase-chat-relay.log 2>&1 &
echo $! > /tmp/codebase-chat-relay.pid
./target/ci/meridian-relay > /tmp/meridian-relay.log 2>&1 &
echo $! > /tmp/meridian-relay.pid
for attempt in $(seq 1 60); do
if ! kill -0 "$(cat /tmp/codebase-chat-relay.pid)" 2>/dev/null; then
cat /tmp/codebase-chat-relay.log
if ! kill -0 "$(cat /tmp/meridian-relay.pid)" 2>/dev/null; then
cat /tmp/meridian-relay.log
exit 1
fi
status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/_readiness || true)
@ -545,7 +545,7 @@ jobs:
fi
sleep 1
done
cat /tmp/codebase-chat-relay.log
cat /tmp/meridian-relay.log
exit 1
- name: Seed desktop e2e data
run: bash scripts/setup-desktop-test-data.sh
@ -564,7 +564,7 @@ jobs:
desktop/playwright-report
desktop/playwright-report.json
desktop/test-results
/tmp/codebase-chat-relay.log
/tmp/meridian-relay.log
if-no-files-found: ignore
retention-days: 7
- name: Save pnpm store cache
@ -635,9 +635,9 @@ jobs:
docker logs "${container}" || true
return 1
}
wait_healthy "Postgres" "codebase-chat-postgres"
wait_healthy "Dragonfly" "codebase-chat-dragonfly"
wait_healthy "MinIO" "codebase-chat-minio"
wait_healthy "Postgres" "meridian-postgres"
wait_healthy "Dragonfly" "meridian-dragonfly"
wait_healthy "MinIO" "meridian-minio"
- name: Download relay artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@ -650,51 +650,51 @@ jobs:
# unmapped host (no retry, unlike the channel reconciler), so the
# 'localhost:3000' community MUST exist before the relay starts — seeding
# after boot leaves the scheduler dead. The relay migrates at boot via
# CODEBASE_CHAT_AUTO_MIGRATE, but that's too late for the pre-boot seed, so apply
# MERIDIAN_AUTO_MIGRATE, but that's too late for the pre-boot seed, so apply
# the schema here first (then drop AUTO_MIGRATE below). lower(host) is the
# unique index → ON CONFLICT target. psql isn't on PATH in hermit → exec
# into the codebase-chat-postgres container.
# into the meridian-postgres container.
env:
PGHOST: localhost
PGPORT: "5432"
PGUSER: codebase-chat
PGPASSWORD: codebase_chat_dev
PGDATABASE: codebase-chat
PGUSER: meridian
PGPASSWORD: meridian_dev
PGDATABASE: meridian
# Use the already-running docker postgres for desired-state planning instead of
# downloading an embedded Postgres from Maven Central (transient-fetch flake source).
PGSCHEMA_PLAN_HOST: localhost
PGSCHEMA_PLAN_PORT: "5432"
PGSCHEMA_PLAN_DB: codebase-chat
PGSCHEMA_PLAN_USER: codebase-chat
PGSCHEMA_PLAN_PASSWORD: codebase_chat_dev
PGSCHEMA_PLAN_DB: meridian
PGSCHEMA_PLAN_USER: meridian
PGSCHEMA_PLAN_PASSWORD: meridian_dev
run: |
./bin/pgschema apply --file schema/schema.sql --auto-approve
docker exec -i -e PGPASSWORD=codebase_chat_dev codebase-chat-postgres \
psql -U codebase_chat -d codebase_chat -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql
docker exec -e PGPASSWORD=codebase_chat_dev codebase-chat-postgres \
psql -U codebase_chat -d codebase_chat -qtA -c "
docker exec -i -e PGPASSWORD=meridian_dev meridian-postgres \
psql -U meridian -d meridian -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql
docker exec -e PGPASSWORD=meridian_dev meridian-postgres \
psql -U meridian -d meridian -qtA -c "
INSERT INTO communities (id, host)
VALUES ('00000000-0000-4000-8000-00000000c0de', 'localhost:3000')
ON CONFLICT (lower(host)) DO NOTHING
;"
- name: Start relay
run: |
chmod +x ./target/ci/codebase-chat-relay
chmod +x ./target/ci/meridian-relay
nohup env \
DATABASE_URL="postgres://codebase_chat:${CODEBASE_CHAT_TEST_POSTGRES_PASSWORD}@localhost:5432/codebase_chat" \
DATABASE_URL="postgres://meridian:${MERIDIAN_TEST_POSTGRES_PASSWORD}@localhost:5432/meridian" \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
CODEBASE_CHAT_BIND_ADDR=0.0.0.0:3000 \
CODEBASE_CHAT_REQUIRE_AUTH_TOKEN=false \
CODEBASE_CHAT_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
CODEBASE_CHAT_RECONCILE_CHANNELS=true \
CODEBASE_CHAT_GIT_PROBE_WRITERS=8 \
MERIDIAN_BIND_ADDR=0.0.0.0:3000 \
MERIDIAN_REQUIRE_AUTH_TOKEN=false \
MERIDIAN_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
MERIDIAN_RECONCILE_CHANNELS=true \
MERIDIAN_GIT_PROBE_WRITERS=8 \
SPROUT_REMINDER_SCHEDULER_INTERVAL_SECS=1 \
./target/ci/codebase-chat-relay > /tmp/codebase-chat-relay.log 2>&1 &
echo $! > /tmp/codebase-chat-relay.pid
./target/ci/meridian-relay > /tmp/meridian-relay.log 2>&1 &
echo $! > /tmp/meridian-relay.pid
for attempt in $(seq 1 60); do
if ! kill -0 "$(cat /tmp/codebase-chat-relay.pid)" 2>/dev/null; then
cat /tmp/codebase-chat-relay.log
if ! kill -0 "$(cat /tmp/meridian-relay.pid)" 2>/dev/null; then
cat /tmp/meridian-relay.log
exit 1
fi
status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/_readiness || true)
@ -703,21 +703,21 @@ jobs:
fi
sleep 1
done
cat /tmp/codebase-chat-relay.log
cat /tmp/meridian-relay.log
exit 1
- name: Invite security tests
run: |
cargo nextest run \
--archive-file target/ci/backend-integration-tests.tar.zst \
-E '(package(codebase-chat-db) and test(/relay_invite::tests/)) or (package(codebase-chat-relay) and test(/api::invites::tests/))' \
-E '(package(meridian-db) and test(/relay_invite::tests/)) or (package(meridian-relay) and test(/api::invites::tests/))' \
--run-ignored ignored-only
env:
DATABASE_URL: postgres://codebase_chat:${{ env.CODEBASE_CHAT_TEST_POSTGRES_PASSWORD }}@localhost:5432/codebase_chat
DATABASE_URL: postgres://meridian:${{ env.MERIDIAN_TEST_POSTGRES_PASSWORD }}@localhost:5432/meridian
- name: NIP-ER reminder e2e
# Feature e2e for NIP-ER (Event Reminders, kind:30300): write-path
# validation, author-only read filtering, and scheduler delivery against
# a live relay. The schema-drift / migration-version guarantee is owned
# by the codebase-chat-db migration.rs unit tests, not this suite.
# by the meridian-db migration.rs unit tests, not this suite.
run: |
cargo nextest run \
--archive-file target/ci/backend-integration-tests.tar.zst \
@ -730,7 +730,7 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: backend-integration-relay-log
path: /tmp/codebase-chat-relay.log
path: /tmp/meridian-relay.log
if-no-files-found: ignore
relay-e2e:
@ -756,32 +756,32 @@ jobs:
path: target/ci
- name: Start relay
run: |
chmod +x ./target/ci/codebase-chat-relay ./target/ci/git-credential-nostr
CODEBASE_CHAT_MAX_CONCURRENT_HANDLERS=2048 \
CODEBASE_CHAT_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=5000 \
CODEBASE_CHAT_SEND_BUFFER=4096 \
chmod +x ./target/ci/meridian-relay ./target/ci/git-credential-nostr
MERIDIAN_MAX_CONCURRENT_HANDLERS=2048 \
MERIDIAN_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=5000 \
MERIDIAN_SEND_BUFFER=4096 \
./scripts/start-relay-for-tests.sh --no-build
- name: Start second relay replica
run: |
nohup env \
DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat \
DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/meridian \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
CODEBASE_CHAT_BIND_ADDR=0.0.0.0:3001 \
CODEBASE_CHAT_HEALTH_PORT=8081 \
CODEBASE_CHAT_METRICS_PORT=9103 \
CODEBASE_CHAT_REQUIRE_AUTH_TOKEN=false \
CODEBASE_CHAT_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
CODEBASE_CHAT_MAX_CONCURRENT_HANDLERS=2048 \
CODEBASE_CHAT_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=5000 \
CODEBASE_CHAT_SEND_BUFFER=4096 \
CODEBASE_CHAT_RECONCILE_CHANNELS=true \
CODEBASE_CHAT_GIT_PROBE_WRITERS=8 \
./target/ci/codebase-chat-relay > /tmp/codebase-chat-relay-b.log 2>&1 &
echo $! > /tmp/codebase-chat-relay-b.pid
MERIDIAN_BIND_ADDR=0.0.0.0:3001 \
MERIDIAN_HEALTH_PORT=8081 \
MERIDIAN_METRICS_PORT=9103 \
MERIDIAN_REQUIRE_AUTH_TOKEN=false \
MERIDIAN_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
MERIDIAN_MAX_CONCURRENT_HANDLERS=2048 \
MERIDIAN_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=5000 \
MERIDIAN_SEND_BUFFER=4096 \
MERIDIAN_RECONCILE_CHANNELS=true \
MERIDIAN_GIT_PROBE_WRITERS=8 \
./target/ci/meridian-relay > /tmp/meridian-relay-b.log 2>&1 &
echo $! > /tmp/meridian-relay-b.pid
for attempt in $(seq 1 60); do
if ! kill -0 "$(cat /tmp/codebase-chat-relay-b.pid)" 2>/dev/null; then
cat /tmp/codebase-chat-relay-b.log
if ! kill -0 "$(cat /tmp/meridian-relay-b.pid)" 2>/dev/null; then
cat /tmp/meridian-relay-b.log
exit 1
fi
if [[ "$(curl -s -o /dev/null -w '%{http_code}' -H 'Host: localhost:3000' http://127.0.0.1:3001/_readiness || true)" == 200 ]]; then
@ -789,26 +789,26 @@ jobs:
fi
sleep 1
done
cat /tmp/codebase-chat-relay-b.log
cat /tmp/meridian-relay-b.log
exit 1
- name: Start default-admission relay replica
run: |
nohup env \
DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat \
DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/meridian \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
CODEBASE_CHAT_BIND_ADDR=0.0.0.0:3002 \
CODEBASE_CHAT_HEALTH_PORT=8082 \
CODEBASE_CHAT_METRICS_PORT=9104 \
CODEBASE_CHAT_REQUIRE_AUTH_TOKEN=false \
CODEBASE_CHAT_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
CODEBASE_CHAT_RECONCILE_CHANNELS=true \
CODEBASE_CHAT_GIT_PROBE_WRITERS=8 \
./target/ci/codebase-chat-relay > /tmp/codebase-chat-relay-default-admission.log 2>&1 &
echo $! > /tmp/codebase-chat-relay-default-admission.pid
MERIDIAN_BIND_ADDR=0.0.0.0:3002 \
MERIDIAN_HEALTH_PORT=8082 \
MERIDIAN_METRICS_PORT=9104 \
MERIDIAN_REQUIRE_AUTH_TOKEN=false \
MERIDIAN_WORKFLOW_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
MERIDIAN_RECONCILE_CHANNELS=true \
MERIDIAN_GIT_PROBE_WRITERS=8 \
./target/ci/meridian-relay > /tmp/meridian-relay-default-admission.log 2>&1 &
echo $! > /tmp/meridian-relay-default-admission.pid
for attempt in $(seq 1 60); do
if ! kill -0 "$(cat /tmp/codebase-chat-relay-default-admission.pid)" 2>/dev/null; then
cat /tmp/codebase-chat-relay-default-admission.log
if ! kill -0 "$(cat /tmp/meridian-relay-default-admission.pid)" 2>/dev/null; then
cat /tmp/meridian-relay-default-admission.log
exit 1
fi
if [[ "$(curl -s -o /dev/null -w '%{http_code}' -H 'Host: localhost:3000' http://127.0.0.1:3002/_readiness || true)" == 200 ]]; then
@ -816,12 +816,12 @@ jobs:
fi
sleep 1
done
cat /tmp/codebase-chat-relay-default-admission.log
cat /tmp/meridian-relay-default-admission.log
exit 1
- name: Relay E2E tests
run: |
cargo test -p codebase-chat-test-client --test e2e_persona --test e2e_nostr_interop -- --ignored --nocapture
cargo test -p codebase-chat-test-client --test e2e_relay -- --ignored --nocapture
cargo test -p meridian-test-client --test e2e_persona --test e2e_nostr_interop -- --ignored --nocapture
cargo test -p meridian-test-client --test e2e_relay -- --ignored --nocapture
env:
RELAY_URL: ws://localhost:3000
RELAY_HTTP_URL_A: http://127.0.0.1:3000
@ -829,32 +829,32 @@ jobs:
RELAY_HTTP_URL_B: http://127.0.0.1:3001
RELAY_DEFAULT_ADMISSION_URL: ws://127.0.0.1:3002
RELAY_AUTHORITY: localhost:3000
CODEBASE_CHAT_E2E_GIT_COMMUNITY_ID: 00000000-0000-4000-8000-00000000c0de
MERIDIAN_E2E_GIT_COMMUNITY_ID: 00000000-0000-4000-8000-00000000c0de
GIT_CREDENTIAL_NOSTR_BIN: ${{ github.workspace }}/target/ci/git-credential-nostr
- name: Dedicated subscriber fault matrix
run: ./scripts/run-subscriber-fault-matrix.sh
env:
DATABASE_URL: postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat
DATABASE_URL: postgres://meridian:meridian_dev@localhost:5432/meridian
REDIS_URL: redis://localhost:6379
RELAY_URL: ws://localhost:3000
RELAY_AUTHORITY: localhost:3000
RELAY_TRANSPORT_URL_A: ws://127.0.0.1:3000
RELAY_HTTP_URL_A: http://127.0.0.1:3000
SUBSCRIBER_FAULT_RELAY_BIN: ${{ github.workspace }}/target/ci/codebase-chat-relay
SUBSCRIBER_FAULT_PROXY_LOG: /tmp/codebase-chat-subscriber-fault-proxy.log
SUBSCRIBER_FAULT_RELAY_LOG: /tmp/codebase-chat-subscriber-fault-relay.log
SUBSCRIBER_FAULT_RELAY_BIN: ${{ github.workspace }}/target/ci/meridian-relay
SUBSCRIBER_FAULT_PROXY_LOG: /tmp/meridian-subscriber-fault-proxy.log
SUBSCRIBER_FAULT_RELAY_LOG: /tmp/meridian-subscriber-fault-relay.log
- name: Scheduled relay E2E inventory lane
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: |
while IFS= read -r test_binary; do
cargo test -p codebase-chat-test-client --test "$test_binary" -- --ignored --nocapture
cargo test -p meridian-test-client --test "$test_binary" -- --ignored --nocapture
done < <(node scripts/check-relay-e2e-inventory.mjs --list scheduled)
env:
RELAY_URL: ws://localhost:3000
RELAY_URL_B: ws://127.0.0.1:3001
RELAY_HTTP_URL_B: http://127.0.0.1:3001
RELAY_AUTHORITY: localhost:3000
CODEBASE_CHAT_E2E_GIT_COMMUNITY_ID: 00000000-0000-4000-8000-00000000c0de
MERIDIAN_E2E_GIT_COMMUNITY_ID: 00000000-0000-4000-8000-00000000c0de
GIT_CREDENTIAL_NOSTR_BIN: ${{ github.workspace }}/target/ci/git-credential-nostr
- name: Upload relay logs
if: failure()
@ -862,11 +862,11 @@ jobs:
with:
name: relay-e2e-artifacts
path: |
/tmp/codebase-chat-relay.log
/tmp/codebase-chat-relay-b.log
/tmp/codebase-chat-relay-default-admission.log
/tmp/codebase-chat-subscriber-fault-proxy.log
/tmp/codebase-chat-subscriber-fault-relay.log
/tmp/meridian-relay.log
/tmp/meridian-relay-b.log
/tmp/meridian-relay-default-admission.log
/tmp/meridian-subscriber-fault-proxy.log
/tmp/meridian-subscriber-fault-relay.log
if-no-files-found: ignore
web:
@ -1032,10 +1032,10 @@ jobs:
CARGO_CMD: ${{ github.event_name == 'pull_request' && 'check' || 'build' }}
run: |
cross "$CARGO_CMD" --release --target "$TARGET" \
-p codebase-chat-relay \
-p codebase-chat-acp \
-p codebase-chat-agent \
-p codebase-chat-dev-mcp \
-p meridian-relay \
-p meridian-acp \
-p meridian-agent \
-p meridian-dev-mcp \
-p git-credential-nostr \
-p git-sign-nostr
@ -1073,19 +1073,19 @@ jobs:
shell: bash
run: |
mkdir -p desktop/src-tauri/binaries
for bin in codebase-chat-acp codebase-chat-agent codebase-chat-dev-mcp git-credential-nostr codebase-chat; do
for bin in meridian-acp meridian-agent meridian-dev-mcp git-credential-nostr meridian; do
touch "desktop/src-tauri/binaries/${bin}-${TARGET}.exe"
done
- name: Clippy (workspace)
run: cargo clippy --workspace --all-targets --target $env:TARGET -- -D warnings
- name: Check (workspace)
run: cargo check --workspace --all-targets --target $env:TARGET
- name: Test (codebase-chat-dev-mcp)
# The Windows-only bash resolver lives in codebase-chat-dev-mcp; its unit tests
- name: Test (meridian-dev-mcp)
# The Windows-only bash resolver lives in meridian-dev-mcp; its unit tests
# only gate if this crate is tested ON Windows.
# Serial: windows_resolver_tests mutate process-global env
# (CODEBASE_CHAT_SHELL/GIT_BASH/SystemRoot) that SharedState::new reads.
run: cargo test -p codebase-chat-dev-mcp --target $env:TARGET -- --test-threads=1
# (MERIDIAN_SHELL/GIT_BASH/SystemRoot) that SharedState::new reads.
run: cargo test -p meridian-dev-mcp --target $env:TARGET -- --test-threads=1
# Smoke-test the new host-prereq contract: Git for Windows (which provides
# bash) is available on the runner, a shell command round-trips, and bash
# does NOT resolve from System32 (so WSL's launcher is never picked up).
@ -1145,11 +1145,11 @@ jobs:
run: |
TARGET=$(rustc -vV | sed -n 's|host: ||p')
mkdir -p desktop/src-tauri/binaries
touch "desktop/src-tauri/binaries/codebase-chat-acp-$TARGET"
touch "desktop/src-tauri/binaries/codebase-chat-agent-$TARGET"
touch "desktop/src-tauri/binaries/codebase-chat-dev-mcp-$TARGET"
touch "desktop/src-tauri/binaries/meridian-acp-$TARGET"
touch "desktop/src-tauri/binaries/meridian-agent-$TARGET"
touch "desktop/src-tauri/binaries/meridian-dev-mcp-$TARGET"
touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET"
touch "desktop/src-tauri/binaries/codebase-chat-$TARGET"
touch "desktop/src-tauri/binaries/meridian-$TARGET"
# Mesh rev is derived from Cargo.lock so a dependency bump needs no
# lockstep edit here; the cache key tracks it automatically.
- name: Resolve mesh-llm rev

View file

@ -8,7 +8,7 @@ on:
paths:
- "crates/**"
- "migrations/**"
- "deploy/charts/codebase-chat-control-plane/**"
- "deploy/charts/meridian-control-plane/**"
- "deploy/compose/**"
- "Dockerfile.control-plane"
- ".dockerignore"
@ -25,7 +25,7 @@ on:
paths:
- "crates/**"
- "migrations/**"
- "deploy/charts/codebase-chat-control-plane/**"
- "deploy/charts/meridian-control-plane/**"
- "deploy/compose/**"
- "Dockerfile.control-plane"
- ".dockerignore"
@ -41,7 +41,7 @@ on:
permissions: {}
env:
CONTROL_PLANE_IMAGE: ghcr.io/block/codebase-chat-control-plane
CONTROL_PLANE_IMAGE: registry.r2d2.office.ilab.zone/meridian-control-plane
jobs:
chart:
@ -53,7 +53,7 @@ jobs:
with: { version: v3.16.4 }
# Asserts, among other things, that the dev identity provider cannot be
# selected through the chart.
- run: deploy/charts/codebase-chat-control-plane/tests/render.sh
- run: deploy/charts/meridian-control-plane/tests/render.sh
test:
runs-on: ubuntu-latest
@ -62,25 +62,25 @@ jobs:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: codebase_chat
POSTGRES_PASSWORD: codebase_chat_dev
POSTGRES_USER: meridian
POSTGRES_PASSWORD: meridian_dev
POSTGRES_DB: postgres
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U codebase_chat"
--health-cmd "pg_isready -U meridian"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- run: cargo fmt --check -p codebase-chat-control-plane
- run: cargo clippy -p codebase-chat-control-plane --all-targets -- -D warnings
- run: cargo fmt --check -p meridian-control-plane
- run: cargo clippy -p meridian-control-plane --all-targets -- -D warnings
# The integration suite SKIPs without a database, so the URL is what makes
# this job meaningful rather than a silent pass.
- run: cargo test -p codebase-chat-control-plane
- run: cargo test -p meridian-control-plane
env:
CODEBASE_CHAT_TEST_DATABASE_URL: postgres://codebase_chat:codebase_chat_dev@localhost:5432/postgres
MERIDIAN_TEST_DATABASE_URL: postgres://meridian:meridian_dev@localhost:5432/postgres
browser-e2e:
runs-on: ubuntu-latest
@ -95,7 +95,7 @@ jobs:
run: docker compose up -d postgres control-plane-db-init dragonfly minio minio-init
- name: Wait for backing services
run: |
for container in codebase-chat-postgres codebase-chat-dragonfly codebase-chat-minio; do
for container in meridian-postgres meridian-dragonfly meridian-minio; do
for attempt in $(seq 1 60); do
status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || true)
[ "$status" = healthy ] && break
@ -106,26 +106,26 @@ jobs:
exit 1
}
done
docker wait codebase-chat-control-plane-db-init
docker wait meridian-control-plane-db-init
- name: Build relay and control plane
run: cargo build -p codebase-chat-relay -p codebase-chat-control-plane
run: cargo build -p meridian-relay -p meridian-control-plane
- name: Start relay and control plane
run: |
secret=0000000000000000000000000000000000000000000000000000000000000003
pubkey=$(CODEBASE_CHAT_CONTROL_RELAY_OPERATOR_SECRET_KEY="$secret" \
target/debug/codebase-chat-control-plane --print-operator-pubkey)
pubkey=$(MERIDIAN_CONTROL_RELAY_OPERATOR_SECRET_KEY="$secret" \
target/debug/meridian-control-plane --print-operator-pubkey)
nohup env \
DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat \
DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/meridian \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
CODEBASE_CHAT_BIND_ADDR=0.0.0.0:3000 \
CODEBASE_CHAT_AUTO_MIGRATE=true \
CODEBASE_CHAT_REQUIRE_AUTH_TOKEN=false \
CODEBASE_CHAT_RECONCILE_CHANNELS=false \
MERIDIAN_BIND_ADDR=0.0.0.0:3000 \
MERIDIAN_AUTO_MIGRATE=true \
MERIDIAN_REQUIRE_AUTH_TOKEN=false \
MERIDIAN_RECONCILE_CHANNELS=false \
RELAY_OPERATOR_PUBKEYS="$pubkey" \
RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000 \
target/debug/codebase-chat-relay >/tmp/control-plane-e2e-relay.log 2>&1 &
target/debug/meridian-relay >/tmp/control-plane-e2e-relay.log 2>&1 &
for attempt in $(seq 1 60); do
[ "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/_readiness || true)" = 200 ] && break
@ -136,18 +136,18 @@ jobs:
exit 1
}
export CODEBASE_CHAT_CONTROL_DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/codebase_chat_control_plane
export CODEBASE_CHAT_CONTROL_RUNTIME_DATABASE_ROLE=codebase_chat
export CODEBASE_CHAT_CONTROL_RELAY_OPERATOR_SECRET_KEY="$secret"
export CODEBASE_CHAT_CONTROL_RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
export CODEBASE_CHAT_CONTROL_PUBLIC_ORIGIN=http://127.0.0.1:8090
export CODEBASE_CHAT_CONTROL_COMMUNITY_HOST_SUFFIX=communities.localtest.me
export CODEBASE_CHAT_CONTROL_COMMUNITY_HOST_PORT=3000
export CODEBASE_CHAT_CONTROL_RELAY_SCHEME=ws
export CODEBASE_CHAT_CONTROL_IDENTITY_PROVIDER=dev
export CODEBASE_CHAT_CONTROL_ALLOW_DEV_LOGIN=1
target/debug/codebase-chat-control-plane --migrate-only
nohup target/debug/codebase-chat-control-plane >/tmp/control-plane-e2e.log 2>&1 &
export MERIDIAN_CONTROL_DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/meridian_control_plane
export MERIDIAN_CONTROL_RUNTIME_DATABASE_ROLE=meridian
export MERIDIAN_CONTROL_RELAY_OPERATOR_SECRET_KEY="$secret"
export MERIDIAN_CONTROL_RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
export MERIDIAN_CONTROL_PUBLIC_ORIGIN=http://127.0.0.1:8090
export MERIDIAN_CONTROL_COMMUNITY_HOST_SUFFIX=communities.localtest.me
export MERIDIAN_CONTROL_COMMUNITY_HOST_PORT=3000
export MERIDIAN_CONTROL_RELAY_SCHEME=ws
export MERIDIAN_CONTROL_IDENTITY_PROVIDER=dev
export MERIDIAN_CONTROL_ALLOW_DEV_LOGIN=1
target/debug/meridian-control-plane --migrate-only
nohup target/debug/meridian-control-plane >/tmp/control-plane-e2e.log 2>&1 &
for attempt in $(seq 1 60); do
[ "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8091/health/ready || true)" = 200 ] && exit 0
sleep 1

View file

@ -1,6 +1,6 @@
name: Docker image
# Builds and publishes the public Codebase Chat relay images as ghcr.io/codebase/codebase-chat.
# Builds and publishes the public Meridian relay images as registry.r2d2.office.ilab.zone/meridian.
# Normal tags contain stripped binaries; matching debug-* tags contain the same
# optimized binaries with line-table debug information for native profilers.
#
@ -13,7 +13,7 @@ name: Docker image
# Versioning: the relay is versioned independently of the desktop app via
# its own `relay-v*` tags (see `just release-relay`). Desktop `v*` tags and
# agent `sprig-v*` tags do NOT publish this image — only `relay-v*` does, so
# the relay image version tracks crates/codebase-chat-relay/Cargo.toml, never desktop.
# the relay image version tracks crates/meridian-relay/Cargo.toml, never desktop.
#
# Triggers:
# - push to main → :main + :sha-<7>
@ -76,7 +76,7 @@ env:
# Single source of truth for the image name. Set GHCR_IMAGE as a repo
# variable to override (e.g., for forks that want to push to their own
# namespace without forking this file).
IMAGE_NAME: ${{ vars.GHCR_IMAGE != '' && vars.GHCR_IMAGE || 'ghcr.io/codebase/codebase-chat' }}
IMAGE_NAME: ${{ vars.GHCR_IMAGE != '' && vars.GHCR_IMAGE || 'registry.r2d2.office.ilab.zone/meridian' }}
jobs:
build:
@ -159,8 +159,8 @@ jobs:
type=semver,pattern={{major}}.{{minor}},match=^relay-v(.*)$,value=${{ inputs.version }}
type=semver,pattern={{major}},match=^relay-v(.*)$,value=${{ inputs.version }}
labels: |
org.opencontainers.image.title=Codebase Chat
org.opencontainers.image.description=WebSocket relay server for the Codebase Chat communications platform
org.opencontainers.image.title=Meridian
org.opencontainers.image.description=WebSocket relay server for the Meridian communications platform
org.opencontainers.image.licenses=Apache-2.0
- name: Build and push release image by digest
@ -310,7 +310,7 @@ jobs:
- name: Attest provenance for the merged image
# Sigstore-signed in-toto attestation, verifiable with:
# gh attestation verify oci://ghcr.io/codebase/codebase-chat:<tag> --owner block
# gh attestation verify oci://registry.r2d2.office.ilab.zone/meridian:<tag> --owner block
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ${{ env.IMAGE_NAME }}
@ -387,10 +387,10 @@ jobs:
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ghcr.io/block/codebase-chat-push-gateway
images: registry.r2d2.office.ilab.zone/meridian-push-gateway
labels: |
org.opencontainers.image.title=Codebase Chat Push Gateway
org.opencontainers.image.description=Capability-gated APNs last hop for Codebase Chat
org.opencontainers.image.title=Meridian Push Gateway
org.opencontainers.image.description=Capability-gated APNs last hop for Meridian
org.opencontainers.image.licenses=Apache-2.0
- name: Build and push by digest
id: build
@ -400,9 +400,9 @@ jobs:
file: ./Dockerfile.push-gateway
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=ghcr.io/block/codebase-chat-push-gateway,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: type=registry,ref=ghcr.io/block/codebase-chat-push-gateway-buildcache:${{ matrix.arch }}
cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref=ghcr.io/block/codebase-chat-push-gateway-buildcache:{0},mode=max,compression=zstd', matrix.arch) || '' }}
outputs: type=image,name=registry.r2d2.office.ilab.zone/meridian-push-gateway,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: type=registry,ref=registry.r2d2.office.ilab.zone/meridian-push-gateway-buildcache:${{ matrix.arch }}
cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref=registry.r2d2.office.ilab.zone/meridian-push-gateway-buildcache:{0},mode=max,compression=zstd', matrix.arch) || '' }}
- name: Export digest
if: github.event_name != 'pull_request'
env:
@ -447,7 +447,7 @@ jobs:
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ghcr.io/block/codebase-chat-push-gateway
images: registry.r2d2.office.ilab.zone/meridian-push-gateway
tags: |
type=ref,event=branch,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }}
type=sha,prefix=sha-,format=short,enable=${{ github.event_name != 'workflow_dispatch' || inputs.version == '' }}
@ -461,7 +461,7 @@ jobs:
run: |
set -euo pipefail
tags=(); while IFS= read -r tag; do [ -n "$tag" ] && tags+=("-t" "$tag"); done <<< "$META_TAGS"
digests=(); for digest in *; do digests+=("ghcr.io/block/codebase-chat-push-gateway@sha256:${digest}"); done
digests=(); for digest in *; do digests+=("registry.r2d2.office.ilab.zone/meridian-push-gateway@sha256:${digest}"); done
docker buildx imagetools create "${tags[@]}" "${digests[@]}"
first_tag=$(echo "$META_TAGS" | head -n1)
digest=$(docker buildx imagetools inspect "$first_tag" --format '{{json .Manifest}}' | jq -r '.digest')
@ -469,7 +469,7 @@ jobs:
- name: Attest gateway image provenance
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ghcr.io/block/codebase-chat-push-gateway
subject-name: registry.r2d2.office.ilab.zone/meridian-push-gateway
subject-digest: ${{ steps.manifest.outputs.digest }}
push-to-registry: true
- name: Gateway publication summary
@ -479,7 +479,7 @@ jobs:
run: |
set -euo pipefail
{
echo "### Published \`ghcr.io/block/codebase-chat-push-gateway\`"
echo "### Published \`registry.r2d2.office.ilab.zone/meridian-push-gateway\`"
echo
printf "**Digest:** \`%s\`\n" "$GATEWAY_DIGEST"
echo
@ -490,6 +490,6 @@ jobs:
echo
echo 'Verify provenance before deployment:'
echo "\`\`\`"
printf 'gh attestation verify oci://ghcr.io/block/codebase-chat-push-gateway@%s --owner block\n' "$GATEWAY_DIGEST"
printf 'gh attestation verify oci://registry.r2d2.office.ilab.zone/meridian-push-gateway@%s --owner block\n' "$GATEWAY_DIGEST"
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY"

View file

@ -36,8 +36,8 @@ on:
tags: ["chart-v[0-9]*"]
pull_request:
paths:
- "deploy/charts/codebase-chat/**"
- "deploy/charts/codebase-chat-push-gateway/**"
- "deploy/charts/meridian/**"
- "deploy/charts/meridian-push-gateway/**"
- ".github/workflows/helm-chart.yml"
- "ct.yaml"
@ -46,11 +46,11 @@ permissions: {}
env:
# Single source of truth for the OCI chart repository (helm appends the chart
# name `codebase-chat`, yielding oci://ghcr.io/codebase/codebase-chat/charts/codebase-chat, which is exactly
# the install ref documented in deploy/charts/codebase-chat/README.md). Set
# name `meridian`, yielding oci://registry.r2d2.office.ilab.zone/meridian/charts/meridian, which is exactly
# the install ref documented in deploy/charts/meridian/README.md). Set
# GHCR_CHART_REPO as a repo variable to override (e.g., forks pushing to their
# own namespace without editing this file) — mirrors docker.yml's GHCR_IMAGE.
CHART_REPO: ${{ vars.GHCR_CHART_REPO != '' && vars.GHCR_CHART_REPO || 'oci://ghcr.io/codebase/codebase-chat/charts' }}
CHART_REPO: ${{ vars.GHCR_CHART_REPO != '' && vars.GHCR_CHART_REPO || 'oci://registry.r2d2.office.ilab.zone/meridian/charts' }}
jobs:
lint-and-unittest:
@ -84,31 +84,31 @@ jobs:
uses: helm/chart-testing-action@0d28d3144d3a25ea2cc349d6e59901c4ff469b3b # v2.7.0
- name: Build chart dependencies
run: helm dependency build deploy/charts/codebase-chat
run: helm dependency build deploy/charts/meridian
- name: Push gateway chart lint + render guard
run: deploy/charts/codebase-chat-push-gateway/tests/render.sh
run: deploy/charts/meridian-push-gateway/tests/render.sh
- name: ct lint
run: ct lint --config ct.yaml --all
- name: helm-unittest
run: helm unittest deploy/charts/codebase-chat
run: helm unittest deploy/charts/meridian
- name: Prometheus alert semantics
run: deploy/charts/codebase-chat/tests/prometheus-rules-test.sh
run: deploy/charts/meridian/tests/prometheus-rules-test.sh
- name: helm template (render every fixture)
run: |
set -euo pipefail
for f in deploy/charts/codebase-chat/ci/*-values.yaml deploy/charts/codebase-chat/tests/fixtures/*-values.yaml; do
for f in deploy/charts/meridian/ci/*-values.yaml deploy/charts/meridian/tests/fixtures/*-values.yaml; do
echo "::group::render $f"
helm template codebase-chat deploy/charts/codebase-chat -f "$f"
helm template meridian deploy/charts/meridian -f "$f"
echo "::endgroup::"
done
install-on-kind:
# Full end-to-end install requires the public ghcr.io/codebase/codebase-chat image to
# Full end-to-end install requires the public registry.r2d2.office.ilab.zone/meridian image to
# exist and to embed Max's startup migrations. Runs only after Sami's
# image PR merges (`workflow_dispatch`) or on a schedule once main carries
# both prerequisites. Render/lint above is the per-PR signal.
@ -146,10 +146,10 @@ jobs:
node_image: kindest/node:v1.31.0
- name: Build chart dependencies
run: helm dependency build deploy/charts/codebase-chat
run: helm dependency build deploy/charts/meridian
- name: ct install (quickstart profile)
run: ct install --config ct.yaml --charts deploy/charts/codebase-chat --helm-extra-args "--timeout 600s"
run: ct install --config ct.yaml --charts deploy/charts/meridian --helm-extra-args "--timeout 600s"
publish:
# Packages the chart and pushes it to GHCR as an OCI artifact. Fires only on
@ -210,7 +210,7 @@ jobs:
# The tag is the source of truth, but the published artifact's version
# comes from Chart.yaml — they must agree or we'd publish a mislabeled
# chart. Fail loudly on drift rather than silently shipping a mismatch.
chart_version="$(helm show chart deploy/charts/codebase-chat | awk '/^version:/ {print $2}')"
chart_version="$(helm show chart deploy/charts/meridian | awk '/^version:/ {print $2}')"
if [ "$chart_version" != "$version" ]; then
echo "::error::Tag version '$version' != Chart.yaml version '$chart_version'. Bump Chart.yaml to match the tag."
exit 1
@ -226,10 +226,10 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build chart dependencies
run: helm dependency build deploy/charts/codebase-chat
run: helm dependency build deploy/charts/meridian
- name: Package chart
run: helm package deploy/charts/codebase-chat --destination dist
run: helm package deploy/charts/meridian --destination dist
- name: Push chart to GHCR
env:
@ -237,23 +237,23 @@ jobs:
VERSION: ${{ steps.ver.outputs.version }}
run: |
set -euo pipefail
helm push "dist/codebase-chat-${VERSION}.tgz" "$CHART_REPO"
helm push "dist/meridian-${VERSION}.tgz" "$CHART_REPO"
- name: Summary
env:
CHART_REPO: ${{ env.CHART_REPO }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
# CHART_REPO is oci://ghcr.io/codebase/codebase-chat/charts; helm push appends the
# chart name, so the install ref is .../charts/codebase-chat.
INSTALL_REF="${CHART_REPO}/codebase-chat"
# CHART_REPO is oci://registry.r2d2.office.ilab.zone/meridian/charts; helm push appends the
# chart name, so the install ref is .../charts/meridian.
INSTALL_REF="${CHART_REPO}/meridian"
{
echo "### Published chart \`codebase-chat\` \`${VERSION}\`"
echo "### Published chart \`meridian\` \`${VERSION}\`"
echo
echo "**OCI ref:** \`${INSTALL_REF}:${VERSION}\`"
echo
echo "Install:"
echo '```'
echo "helm install codebase-chat ${INSTALL_REF} --version ${VERSION}"
echo "helm install meridian ${INSTALL_REF} --version ${VERSION}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

View file

@ -19,7 +19,7 @@ permissions:
jobs:
build:
name: Build Linux canary
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: ubuntu-latest
container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
timeout-minutes: 60
@ -166,7 +166,7 @@ jobs:
- name: Build sidecars
run: |
cargo build --release -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh
- name: Build Linux Tauri app
@ -219,7 +219,7 @@ jobs:
- name: Upload Linux canary packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codebase-chat-linux-canary-${{ github.sha }}
name: meridian-linux-canary-${{ github.sha }}
path: |
${{ steps.artifacts.outputs.deb }}
${{ steps.artifacts.outputs.appimage }}

View file

@ -13,7 +13,7 @@ on:
required: true
type: string
target_sha:
description: Exact current codebase/codebase-chat main commit
description: Exact current r2d2/meridian main commit
required: true
type: string
@ -47,8 +47,8 @@ jobs:
env:
REPOSITORY: ${{ github.repository }}
run: |
if [ "$REPOSITORY" != "codebase/codebase-chat" ]; then
echo "::error::Mobile candidate publication is restricted to codebase/codebase-chat"
if [ "$REPOSITORY" != "r2d2/meridian" ]; then
echo "::error::Mobile candidate publication is restricted to r2d2/meridian"
exit 1
fi
@ -56,8 +56,8 @@ jobs:
id: release-tagger
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.CODEBASE_CHAT_RELEASE_TAGGER_CLIENT_ID }}
private-key: ${{ secrets.CODEBASE_CHAT_RELEASE_TAGGER_PRIVATE_KEY }}
client-id: ${{ vars.MERIDIAN_RELEASE_TAGGER_CLIENT_ID }}
private-key: ${{ secrets.MERIDIAN_RELEASE_TAGGER_PRIVATE_KEY }}
permission-contents: write
- name: Publish annotated candidate tag

View file

@ -45,8 +45,8 @@ jobs:
for attempt in $(seq 1 90); do
postgres_ok=false
redis_ok=false
docker exec codebase-chat-postgres pg_isready -U codebase_chat -d codebase_chat >/dev/null 2>&1 && postgres_ok=true
docker exec codebase-chat-dragonfly redis-cli ping 2>/dev/null | grep -q PONG && redis_ok=true
docker exec meridian-postgres pg_isready -U meridian -d meridian >/dev/null 2>&1 && postgres_ok=true
docker exec meridian-dragonfly redis-cli ping 2>/dev/null | grep -q PONG && redis_ok=true
if [[ "$postgres_ok" == true && "$redis_ok" == true ]]; then
exit 0
fi

View file

@ -13,11 +13,11 @@ on:
tags: ["push-chart-v[0-9]*"]
pull_request:
paths:
- "deploy/charts/codebase-chat-push-gateway/**"
- "deploy/charts/meridian-push-gateway/**"
- ".github/workflows/push-gateway-helm-chart.yml"
permissions: {}
env:
CHART_REPO: oci://ghcr.io/codebase/codebase-chat/charts
CHART_REPO: oci://registry.r2d2.office.ilab.zone/meridian/charts
jobs:
validate:
runs-on: ubuntu-latest
@ -29,8 +29,8 @@ jobs:
fetch-depth: 0
- uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4
with: { version: v3.16.4 }
- run: deploy/charts/codebase-chat-push-gateway/tests/render.sh
- run: deploy/charts/codebase-chat-push-gateway/tests/release-contract.sh
- run: deploy/charts/meridian-push-gateway/tests/render.sh
- run: deploy/charts/meridian-push-gateway/tests/release-contract.sh
publish:
if: github.event_name != 'pull_request'
needs: validate
@ -54,7 +54,7 @@ jobs:
set -euo pipefail
version="${INPUT_VERSION:-${REF_NAME#push-chart-v}}"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]
test "$(helm show chart deploy/charts/codebase-chat-push-gateway | awk '/^version:/ {print $2}')" = "$version"
test "$(helm show chart deploy/charts/meridian-push-gateway | awk '/^version:/ {print $2}')" = "$version"
if [ -n "$INPUT_VERSION" ]; then
test "$(git rev-parse HEAD)" = "$(git rev-parse "refs/tags/push-chart-v${version}^{commit}")"
fi
@ -64,5 +64,5 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: helm package deploy/charts/codebase-chat-push-gateway --destination dist
- run: helm push "dist/codebase-chat-push-gateway-${VERSION}.tgz" "$CHART_REPO"
- run: helm package deploy/charts/meridian-push-gateway --destination dist
- run: helm push "dist/meridian-push-gateway-${VERSION}.tgz" "$CHART_REPO"

View file

@ -15,7 +15,7 @@ jobs:
# create the release objects all four platform jobs upload into.
setup:
name: Setup
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
@ -70,7 +70,7 @@ jobs:
NOTES=$(awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found && !/^\$/" CHANGELOG.md)
fi
if [[ -z "$NOTES" ]]; then
NOTES="Codebase Chat Desktop v${VERSION}"
NOTES="Meridian Desktop v${VERSION}"
fi
PRERELEASE_FLAGS=()
if [[ "$VERSION" =~ -(test|alpha|beta|rc)([.-]|$) ]]; then
@ -78,7 +78,7 @@ jobs:
fi
gh release create "v${VERSION}" \
--target "$RELEASE_SHA" \
--title "Codebase Chat Desktop v${VERSION}" \
--title "Meridian Desktop v${VERSION}" \
--notes "$NOTES" \
"${PRERELEASE_FLAGS[@]}"
@ -86,15 +86,15 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create codebase-chat-desktop-latest \
gh release create meridian-desktop-latest \
--prerelease \
--title "Codebase Chat Desktop Auto-Update" \
--title "Meridian Desktop Auto-Update" \
--notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \
2>/dev/null || true
release:
name: Release
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: macos-latest
needs: setup
timeout-minutes: 60
@ -129,12 +129,12 @@ jobs:
- name: Generate release config
run: cd desktop && node scripts/build-release-config.mjs
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
- name: Build sidecars
run: |
cargo build --release -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh
# Mesh rev derived from Cargo.lock (no lockstep edit on dep bump); cache key tracks it.
@ -180,8 +180,8 @@ jobs:
- name: Build unsigned Tauri app
run: cd desktop && pnpm tauri build --verbose --no-sign --features mesh-llm --config src-tauri/tauri.release.conf.json
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
@ -220,7 +220,7 @@ jobs:
codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }}
entitlements-plist-path: ${{ runner.temp }}/entitlements.plist
artifact-name: codebase-chat-${{ github.sha }}-${{ github.run_id }}-arm64
artifact-name: meridian-${{ github.sha }}-${{ github.run_id }}-arm64
- name: Replace DMG and rebuild updater archive
env:
@ -241,30 +241,30 @@ jobs:
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract"
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
rm -rf "${APP_DIR}/Codebase Chat.app"
cp -R "${EXTRACT_DIR}/Codebase Chat.app" "${APP_DIR}/Codebase Chat.app"
rm -rf "${APP_DIR}/Meridian.app"
cp -R "${EXTRACT_DIR}/Meridian.app" "${APP_DIR}/Meridian.app"
# Rebuild the updater archive from the signed .app and re-sign it with the Tauri updater key.
rm -f "${APP_DIR}/Codebase Chat.app.tar.gz" "${APP_DIR}/Codebase Chat.app.tar.gz.sig"
(cd "$APP_DIR" && tar -czf Codebase Chat.app.tar.gz Codebase Chat.app)
TARBALL_ABS="$(pwd)/${APP_DIR}/Codebase Chat.app.tar.gz"
rm -f "${APP_DIR}/Meridian.app.tar.gz" "${APP_DIR}/Meridian.app.tar.gz.sig"
(cd "$APP_DIR" && tar -czf Meridian.app.tar.gz Meridian.app)
TARBALL_ABS="$(pwd)/${APP_DIR}/Meridian.app.tar.gz"
(cd desktop && pnpm tauri signer sign "$TARBALL_ABS")
- name: Verify code signature
run: |
codesign --verify --deep --strict --verbose=2 \
desktop/src-tauri/target/release/bundle/macos/Codebase Chat.app
desktop/src-tauri/target/release/bundle/macos/Meridian.app
spctl --assess --type execute --verbose=4 \
desktop/src-tauri/target/release/bundle/macos/Codebase Chat.app
desktop/src-tauri/target/release/bundle/macos/Meridian.app
desktop/scripts/verify-macos-entitlements.sh \
desktop/src-tauri/target/release/bundle/macos/Codebase Chat.app
desktop/src-tauri/target/release/bundle/macos/Meridian.app
- name: Locate build artifacts
id: artifacts
run: |
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
# Find the DMG (Tauri names it CodebaseChat_<version>_<arch>.dmg)
# Find the DMG (Tauri names it Meridian_<version>_<arch>.dmg)
DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
if [[ -z "$DMG" ]]; then
echo "::error::No DMG found in $BUNDLE_DIR/dmg"
@ -298,7 +298,7 @@ jobs:
- name: Upload updater archive to rolling release
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
run: |
gh release upload codebase-chat-desktop-latest \
gh release upload meridian-desktop-latest \
"$ARCHIVE_PATH" \
"$SIG_PATH" \
--clobber
@ -309,7 +309,7 @@ jobs:
release-macos-x64:
name: Release macOS (Intel)
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: macos-latest
needs: setup
timeout-minutes: 60
@ -348,19 +348,19 @@ jobs:
- name: Generate release config
run: cd desktop && node scripts/build-release-config.mjs
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
- name: Build sidecars
run: |
cargo build --release --target "$TARGET" -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release --target "$TARGET" -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh "$TARGET"
- name: Build unsigned Tauri app
run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
@ -396,7 +396,7 @@ jobs:
codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }}
entitlements-plist-path: ${{ runner.temp }}/entitlements.plist
artifact-name: codebase-chat-${{ github.sha }}-${{ github.run_id }}-x64
artifact-name: meridian-${{ github.sha }}-${{ github.run_id }}-x64
- name: Replace DMG and rebuild updater archive
env:
@ -416,18 +416,18 @@ jobs:
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64"
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
rm -rf "${APP_DIR}/Codebase Chat.app"
cp -R "${EXTRACT_DIR}/Codebase Chat.app" "${APP_DIR}/Codebase Chat.app"
rm -rf "${APP_DIR}/Meridian.app"
cp -R "${EXTRACT_DIR}/Meridian.app" "${APP_DIR}/Meridian.app"
# Rebuild the updater archive from the signed .app and re-sign with the Tauri updater key.
rm -f "${APP_DIR}/Codebase Chat.app.tar.gz" "${APP_DIR}/Codebase Chat.app.tar.gz.sig"
(cd "$APP_DIR" && tar -czf Codebase Chat.app.tar.gz Codebase Chat.app)
TARBALL_ABS="$(pwd)/${APP_DIR}/Codebase Chat.app.tar.gz"
rm -f "${APP_DIR}/Meridian.app.tar.gz" "${APP_DIR}/Meridian.app.tar.gz.sig"
(cd "$APP_DIR" && tar -czf Meridian.app.tar.gz Meridian.app)
TARBALL_ABS="$(pwd)/${APP_DIR}/Meridian.app.tar.gz"
(cd desktop && pnpm tauri signer sign "$TARBALL_ABS")
- name: Verify code signature
run: |
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Codebase Chat.app"
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Meridian.app"
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
spctl --assess --type execute --verbose=4 "$APP_DIR"
desktop/scripts/verify-macos-entitlements.sh "$APP_DIR"
@ -462,7 +462,7 @@ jobs:
- name: Upload updater archive to rolling release
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
run: |
gh release upload codebase-chat-desktop-latest \
gh release upload meridian-desktop-latest \
"$ARCHIVE_PATH" \
"$SIG_PATH" \
--clobber
@ -473,7 +473,7 @@ jobs:
release-linux:
name: Release Linux
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: ubuntu-latest
# Digest-pinned like the SHA-pinned actions below; Renovate keeps it fresh.
container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
@ -611,20 +611,20 @@ jobs:
- name: Build sidecars
run: |
cargo build --release -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh
- name: Generate release config
run: cd desktop && node scripts/build-release-config.mjs
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
- name: Build Linux Tauri app
run: cd desktop && pnpm tauri build --verbose --ci --bundles deb,appimage --config src-tauri/tauri.release.conf.json
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
@ -704,7 +704,7 @@ jobs:
- name: Upload updater archive to rolling release
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
run: |
gh release upload codebase-chat-desktop-latest \
gh release upload meridian-desktop-latest \
"$ARCHIVE_PATH" \
"$SIG_PATH" \
--clobber
@ -767,21 +767,21 @@ jobs:
shell: bash
run: cd desktop && node scripts/build-release-config.mjs
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
- name: Build sidecars
shell: bash
run: |
cargo build --release --target "$TARGET" -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release --target "$TARGET" -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh "$TARGET"
- name: Build Windows NSIS installer (unsigned)
shell: bash
run: cd desktop && pnpm tauri build --verbose --target "$TARGET" --bundles nsis --config src-tauri/tauri.release.conf.json
env:
CODEBASE_CHAT_UPDATER_PUBLIC_KEY: ${{ secrets.CODEBASE_CHAT_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
CODEBASE_CHAT_UPDATER_ENDPOINT: https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest/latest.json
MERIDIAN_UPDATER_PUBLIC_KEY: ${{ secrets.MERIDIAN_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
MERIDIAN_UPDATER_ENDPOINT: https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest/latest.json
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
@ -838,7 +838,7 @@ jobs:
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
shell: bash
run: |
gh release upload codebase-chat-desktop-latest \
gh release upload meridian-desktop-latest \
"$ARCHIVE_PATH" \
"$SIG_PATH" \
--clobber
@ -911,7 +911,7 @@ jobs:
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
run: |
set -euo pipefail
BASE="https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest"
BASE="https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest"
ARCHIVES=()
add_archive() {
@ -957,7 +957,7 @@ jobs:
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
run: |
set -euo pipefail
BASE="https://github.com/codebase/codebase-chat/releases/download/codebase-chat-desktop-latest"
BASE="https://github.com/r2d2/meridian/releases/download/meridian-desktop-latest"
TRIPLES=()
add_triple() {
@ -979,4 +979,4 @@ jobs:
- name: Upload latest.json to rolling release
run: |
gh release upload codebase-chat-desktop-latest latest.json --clobber
gh release upload meridian-desktop-latest latest.json --clobber

View file

@ -7,7 +7,7 @@ on:
pull_request:
paths:
- ".github/workflows/reply-persistence-benchmark.yml"
- "crates/codebase-chat-db/src/event.rs"
- "crates/meridian-db/src/event.rs"
- "docker-compose.yml"
- "scripts/benchmark-reply-path.sh"
- "scripts/test-benchmark-reply-path.sh"

View file

@ -12,7 +12,7 @@ permissions:
jobs:
build:
name: Build signed macOS canary
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: macos-latest
timeout-minutes: 60
permissions:
@ -93,7 +93,7 @@ jobs:
- name: Build sidecars
run: |
cargo build --release -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh
# Mesh rev derived from Cargo.lock (no lockstep edit on dep bump); cache key tracks it.
@ -176,7 +176,7 @@ jobs:
codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }}
entitlements-plist-path: ${{ runner.temp }}/entitlements.plist
artifact-name: codebase-chat-canary-${{ github.sha }}-${{ github.run_id }}-arm64
artifact-name: meridian-canary-${{ github.sha }}-${{ github.run_id }}-arm64
- name: Verify signed app
env:
@ -186,9 +186,9 @@ jobs:
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract"
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
codesign --verify --deep --strict --verbose=2 "$EXTRACT_DIR/Codebase Chat.app"
spctl --assess --type execute --verbose=4 "$EXTRACT_DIR/Codebase Chat.app"
desktop/scripts/verify-macos-entitlements.sh "$EXTRACT_DIR/Codebase Chat.app"
codesign --verify --deep --strict --verbose=2 "$EXTRACT_DIR/Meridian.app"
spctl --assess --type execute --verbose=4 "$EXTRACT_DIR/Meridian.app"
desktop/scripts/verify-macos-entitlements.sh "$EXTRACT_DIR/Meridian.app"
- name: Stage signed DMG
id: artifact
@ -197,7 +197,7 @@ jobs:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
NAME="CodebaseChat_${VERSION}_aarch64-signed.dmg"
NAME="Meridian_${VERSION}_aarch64-signed.dmg"
cp "$SIGNED_DMG" "$RUNNER_TEMP/$NAME"
echo "path=$RUNNER_TEMP/$NAME" >> "$GITHUB_OUTPUT"
echo "name=$NAME" >> "$GITHUB_OUTPUT"
@ -205,7 +205,7 @@ jobs:
- name: Upload signed canary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codebase-chat-macos-canary-${{ github.sha }}
name: meridian-macos-canary-${{ github.sha }}
path: ${{ steps.artifact.outputs.path }}
if-no-files-found: error
retention-days: 7

View file

@ -1,9 +1,9 @@
name: Sprig
# Builds and publishes Sprig — one deploy-anywhere Linux multicall binary for:
# codebase-chat-acp ACP harness that bridges Codebase Chat events to the LLM agent
# codebase-chat-agent ACP-compliant agent (spawns MCP, calls LLMs)
# codebase-chat-dev-mcp Developer MCP server (multicall: rg, tree, codebase-chat,
# meridian-acp ACP harness that bridges Meridian events to the LLM agent
# meridian-agent ACP-compliant agent (spawns MCP, calls LLMs)
# meridian-dev-mcp Developer MCP server (multicall: rg, tree, meridian,
# git-credential-nostr, git-sign-nostr)
#
# Targets: x86_64-unknown-linux-musl and aarch64-unknown-linux-musl (static
@ -137,7 +137,7 @@ jobs:
set -euo pipefail
TAG="sprig-latest"
TITLE="Sprig (rolling)"
NOTES="Rolling Linux build of Sprig (all-in-one codebase-chat-acp + codebase-chat-agent + codebase-chat-dev-mcp), tracking \`main\` (\`${SHA}\`)."
NOTES="Rolling Linux build of Sprig (all-in-one meridian-acp + meridian-agent + meridian-dev-mcp), tracking \`main\` (\`${SHA}\`)."
gh release edit "$TAG" \
--prerelease \
@ -180,5 +180,5 @@ jobs:
set -euo pipefail
gh release create "$TAG" \
--title "Sprig v${VERSION}" \
--notes "Sprig v${VERSION} — Linux all-in-one builds of codebase-chat-acp + codebase-chat-agent + codebase-chat-dev-mcp." \
--notes "Sprig v${VERSION} — Linux all-in-one builds of meridian-acp + meridian-agent + meridian-dev-mcp." \
dist/*

View file

@ -16,7 +16,7 @@ permissions:
jobs:
build:
name: Build Windows canary
if: github.repository == 'codebase/codebase-chat'
if: github.repository == 'r2d2/meridian'
runs-on: windows-latest
timeout-minutes: 60
permissions:
@ -122,7 +122,7 @@ jobs:
- name: Build sidecars
shell: bash
run: |
cargo build --release --target "$TARGET" -p codebase-chat-acp -p codebase-chat-agent -p codebase-chat-dev-mcp -p git-credential-nostr -p codebase-chat-cli
cargo build --release --target "$TARGET" -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p git-credential-nostr -p meridian-cli
./scripts/bundle-sidecars.sh "$TARGET"
- name: Build Windows NSIS installer (unsigned)
@ -147,7 +147,7 @@ jobs:
- name: Upload Windows canary installer
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codebase-chat-windows-canary-${{ github.sha }}
name: meridian-windows-canary-${{ github.sha }}
path: ${{ steps.artifact.outputs.exe }}
if-no-files-found: error
retention-days: 7

6
.gitignore vendored
View file

@ -52,9 +52,9 @@ node_modules/
.sqlx/
# SQLite database (created by relay in CWD)
codebase-chat.db
codebase-chat.db-wal
codebase-chat.db-shm
meridian.db
meridian.db-wal
meridian.db-shm
# Docker volumes (if mounted locally)
mysql-data/

View file

@ -14,15 +14,15 @@ lives in the root `VISION*.md`; history lives in `CHANGELOG.md`.
| --- | --- | --- |
| `features/` | Feature specs — five preview features, nine Stoat Chat parity gaps, a payments protocol spec, infrastructure plans, and hosted-service plans. `README.md` is the local contract: the index, the mandated doc shape, and the cross-cutting findings | yes |
| `epics/` | Implementation plans that connect feature intent to bead state, exact code surfaces, TDD slices, and current proof gates | yes |
| `rebrand/` | `README.md` — the Buzz → Codebase rebrand record: brand definition (palette, symbol, wordmark, starter agents, icons), what landed, what is outstanding | yes |
| `rebrand/` | `README.md` — the Buzz → Meridian rebrand record: brand definition (palette, symbol, wordmark, starter agents, icons), what landed, what is outstanding | yes |
| `reference-docs/goat/` | Derived Markdown distillations `00`–`06` of the GOAT/EFDI source PDFs — the basis of root `AGENTS.md` § Design Law | `.md` yes, `.pdf` **no** |
| `reference-code/` | Third-party checkouts read while writing the parity docs: `buzz-for-desktop`, `buzz-for-web`, `codebase-cli`, `emdash`, `nips`, `stoatchat` | **no** |
| `reference-code/` | Third-party checkouts read while writing the parity docs: `buzz-for-desktop`, `buzz-for-web`, `meridian-cli`, `emdash`, `nips`, `stoatchat` | **no** |
## Local Contracts
- **`reference-code/` is read-only, and its `AGENTS.md` files are not ours.** The
directory is gitignored (`.gitignore:93`); each entry is a full clone with its
own `.git`. `codebase-cli/` alone carries ~30 nested `AGENTS.md` plus a
own `.git`. `meridian-cli/` alone carries ~30 nested `AGENTS.md` plus a
`CLAUDE.md`, and `emdash/` carries more. Step 4 of Read Before Editing walks an
agent straight into them. **They are upstream documents describing upstream
repos and are never binding here** — the STELLAR chain stops at this file. Edits

View file

@ -26,14 +26,14 @@ this document.
| Surface | Files | Responsibility |
| --- | --- | --- |
| Production authentication | `crates/codebase-chat-control-plane/src/identity.rs` | OIDC discovery, PKCE exchange, trusted principal claims |
| Ownership lifecycle | `crates/codebase-chat-control-plane/src/http.rs`, `src/model.rs` | identity bind/unbind, relay-authoritative ownership denial |
| Repairable projection | `crates/codebase-chat-control-plane/src/store.rs`, `src/http.rs` | `community_id -> host` projection and reconciliation |
| Browser page | `crates/codebase-chat-control-plane/src/http.rs` | session bootstrap, CSP, resilient registration UI |
| Integration proof | `crates/codebase-chat-control-plane/tests/integration_control_plane.rs` | Postgres + NIP-98-verifying relay stub |
| Production authentication | `crates/meridian-control-plane/src/identity.rs` | OIDC discovery, PKCE exchange, trusted principal claims |
| Ownership lifecycle | `crates/meridian-control-plane/src/http.rs`, `src/model.rs` | identity bind/unbind, relay-authoritative ownership denial |
| Repairable projection | `crates/meridian-control-plane/src/store.rs`, `src/http.rs` | `community_id -> host` projection and reconciliation |
| Browser page | `crates/meridian-control-plane/src/http.rs` | session bootstrap, CSP, resilient registration UI |
| Integration proof | `crates/meridian-control-plane/tests/integration_control_plane.rs` | Postgres + NIP-98-verifying relay stub |
| Browser proof | `desktop/tests/control-plane/communities-page.spec.mjs` | real browser session, signed binding, create/archive/restore |
| Desktop parity | `desktop/src/features/communities/`, `desktop/src/features/settings/ui/HostedCommunitiesSettingsCard.tsx` | deployment limit and identity-lifecycle error copy |
| Deployment | `deploy/charts/codebase-chat-control-plane/`, `.github/workflows/control-plane.yml` | resource bounds, topology, monitoring, immutable delivery |
| Deployment | `deploy/charts/meridian-control-plane/`, `.github/workflows/control-plane.yml` | resource bounds, topology, monitoring, immutable delivery |
## Implementation Slices
@ -43,15 +43,15 @@ this document.
**Files:**
- Modify: `crates/codebase-chat-control-plane/src/identity.rs`
- Test: `crates/codebase-chat-control-plane/src/identity.rs`
- Modify: `crates/meridian-control-plane/src/identity.rs`
- Test: `crates/meridian-control-plane/src/identity.rs`
**Steps:**
1. Write an async OIDC-stub test whose token response contains a forged
`id_token` subject and whose bearer-authenticated UserInfo response contains
the real subject; expect the real subject.
2. Run `cargo test -p codebase-chat-control-plane identity::tests::oidc_` and
2. Run `cargo test -p meridian-control-plane identity::tests::oidc_` and
verify the test fails because the forged payload wins.
3. Require discovery to advertise UserInfo, check its HTTP status, and derive
the principal only from that response. Validate the discovered issuer and
@ -65,17 +65,17 @@ this document.
**Files:**
- Modify: `crates/codebase-chat-control-plane/src/model.rs`
- Modify: `crates/codebase-chat-control-plane/src/http.rs`
- Modify: `crates/meridian-control-plane/src/model.rs`
- Modify: `crates/meridian-control-plane/src/http.rs`
- Modify: `desktop/src/features/communities/hostedCommunityApi.ts`
- Test: `crates/codebase-chat-control-plane/tests/integration_control_plane.rs`
- Test: `crates/meridian-control-plane/tests/integration_control_plane.rs`
**Steps:**
1. Write integration tests proving delete is rejected for a key with an active
community and for the same community after archive.
2. Run the focused integration tests with
`CODEBASE_CHAT_TEST_DATABASE_URL=postgres://...`; expect FAIL.
`MERIDIAN_TEST_DATABASE_URL=postgres://...`; expect FAIL.
3. Add `identity_in_use`. Before unbind, resolve the current identity and query
authoritative relay ownership; reject when any row remains.
4. Add desktop/browser copy that says ownership must be transferred first.
@ -87,9 +87,9 @@ this document.
**Files:**
- Modify: `crates/codebase-chat-control-plane/src/store.rs`
- Modify: `crates/codebase-chat-control-plane/src/http.rs`
- Test: `crates/codebase-chat-control-plane/tests/integration_control_plane.rs`
- Modify: `crates/meridian-control-plane/src/store.rs`
- Modify: `crates/meridian-control-plane/src/http.rs`
- Test: `crates/meridian-control-plane/tests/integration_control_plane.rs`
**Steps:**
@ -110,8 +110,8 @@ this document.
**Files:**
- Modify: `crates/codebase-chat-control-plane/src/http.rs`
- Test: `crates/codebase-chat-control-plane/tests/integration_control_plane.rs`
- Modify: `crates/meridian-control-plane/src/http.rs`
- Test: `crates/meridian-control-plane/tests/integration_control_plane.rs`
**Steps:**
@ -155,13 +155,13 @@ this document.
**Files:**
- Modify: `crates/codebase-chat-control-plane/src/config.rs`
- Modify: `crates/codebase-chat-control-plane/src/http.rs`
- Modify: `crates/codebase-chat-control-plane/src/main.rs`
- Modify: `deploy/charts/codebase-chat-control-plane/values.yaml`
- Modify: `deploy/charts/codebase-chat-control-plane/values.schema.json`
- Modify: `deploy/charts/codebase-chat-control-plane/templates/deployment.yaml`
- Test: `deploy/charts/codebase-chat-control-plane/tests/render.sh`
- Modify: `crates/meridian-control-plane/src/config.rs`
- Modify: `crates/meridian-control-plane/src/http.rs`
- Modify: `crates/meridian-control-plane/src/main.rs`
- Modify: `deploy/charts/meridian-control-plane/values.yaml`
- Modify: `deploy/charts/meridian-control-plane/values.schema.json`
- Modify: `deploy/charts/meridian-control-plane/templates/deployment.yaml`
- Test: `deploy/charts/meridian-control-plane/tests/render.sh`
**Steps:**
@ -190,8 +190,8 @@ this document.
**Files:**
- Modify: `crates/codebase-chat-control-plane/src/metrics.rs`
- Modify: `deploy/charts/codebase-chat-control-plane/`
- Modify: `crates/meridian-control-plane/src/metrics.rs`
- Modify: `deploy/charts/meridian-control-plane/`
- Modify: `.github/workflows/control-plane.yml`
- Modify: `docs/control-plane-deployment.md`
@ -258,13 +258,13 @@ or workflow is ever allowed to grant relay ownership.
```bash
. ./bin/activate-hermit
CODEBASE_CHAT_TEST_DATABASE_URL=postgres://codebase_chat:codebase_chat_dev@localhost:5432/postgres \
cargo test -p codebase-chat-control-plane --all-targets
cargo clippy -p codebase-chat-control-plane --all-targets -- -D warnings
cargo fmt --check -p codebase-chat-control-plane
MERIDIAN_TEST_DATABASE_URL=postgres://meridian:meridian_dev@localhost:5432/postgres \
cargo test -p meridian-control-plane --all-targets
cargo clippy -p meridian-control-plane --all-targets -- -D warnings
cargo fmt --check -p meridian-control-plane
pnpm -C desktop check
pnpm -C desktop exec playwright test --config tests/control-plane/playwright.config.mjs
bash deploy/charts/codebase-chat-control-plane/tests/render.sh
bash deploy/charts/meridian-control-plane/tests/render.sh
```
Live gates remain the provisioned-host `101 Switching Protocols` and unknown-host
@ -274,8 +274,8 @@ Live gates remain the provisioned-host `101 Switching Protocols` and unknown-hos
- Initial council audit and red-test baseline: 2026-08-01.
- Relay quota concurrency was re-checked and is already safe via the per-owner
PostgreSQL advisory lock in `codebase-chat-db/src/lib.rs`.
- `cargo test -p codebase-chat-control-plane --all-targets --no-fail-fast`:
PostgreSQL advisory lock in `meridian-db/src/lib.rs`.
- `cargo test -p meridian-control-plane --all-targets --no-fail-fast`:
51 unit and 27 PostgreSQL integration tests passed. The added lifecycle tests
prove a one-connection pool cannot self-starve and cover successful transfer,
new-owner management, and old-owner disconnect.

View file

@ -22,7 +22,7 @@ Helm parity fixtures.
- `.settings/features/feature-portable-deployment-profiles.md`
- `deploy/compose/README.md`
- `deploy/charts/codebase-chat/README.md`
- `deploy/charts/meridian/README.md`
- `docs/git-on-object-storage.md`
- `docs/runbooks/relay-backup-restore.md`
@ -56,9 +56,9 @@ order below, determines executable work.
| Deployment parity | `deploy/profiles/README.md` (new), `scripts/test-deployment-profile-parity.sh` (new) | Shared ports, state, secret, health, migration, and support-tier assertions |
| Dokploy distribution | `deploy/dokploy/template/` (new) | Upstream-compatible Compose, `template.toml`, metadata fixture, import smoke |
| HA reference | `deploy/dokploy/stack.yml` (new) | Prebuilt relay replicas with external state and Swarm/Traefik labels |
| Object storage | `crates/codebase-chat-media/`, `crates/codebase-chat-relay/src/api/git/`, `docs/git-on-object-storage.md` | S3 configuration and startup A3 CAS admission |
| Object storage | `crates/meridian-media/`, `crates/meridian-relay/src/api/git/`, `docs/git-on-object-storage.md` | S3 configuration and startup A3 CAS admission |
| Recovery | `scripts/verify-backup-restore.sh`, `docs/runbooks/relay-backup-restore.md` | Consistency set, isolated restore, identity/object graph/ACL proof |
| Relay health/metrics | `crates/codebase-chat-relay/src/router.rs`, `src/main.rs`, chart Prometheus resources | `/_liveness`, `/_readiness`, `/metrics`, subscriber fencing |
| Relay health/metrics | `crates/meridian-relay/src/router.rs`, `src/main.rs`, chart Prometheus resources | `/_liveness`, `/_readiness`, `/metrics`, subscriber fencing |
| Cloudflare edge | `deploy/cloudflare/` (new) | R2 example, Tunnel/LB origin contract, optional Worker/Container experiment |
| CI | `.github/workflows/ci.yml`, `.github/workflows/docker.yml` | Static adapter validation and disposable live smoke artifacts |
@ -212,7 +212,7 @@ docker compose -f deploy/dokploy/compose.yml config
bash scripts/test-dokploy-contract.sh
bash scripts/test-deployment-profile-parity.sh
bash scripts/test-verify-backup-restore.sh
helm unittest deploy/charts/codebase-chat
helm unittest deploy/charts/meridian
just ci
```
@ -224,10 +224,10 @@ readiness, upgrade, failure, and restore probes named by their support profile.
- Planning inventory completed 2026-08-02 against the current Compose, Helm,
image, Git/CAS, and recovery contracts.
- Dokploy currently documents Compose, Stack/Swarm, clusters, remote servers,
automatic deployments, domains, and upstream templates. No Codebase Chat
automatic deployments, domains, and upstream templates. No Meridian
adapter or live proof exists in this repo.
- Cloudflare currently documents R2 S3 compatibility, strong consistency, and
conditional operations. The Codebase Chat startup A3 probe has not yet
conditional operations. The Meridian startup A3 probe has not yet
admitted an R2 deployment.
- No implementation or live support evidence is claimed by this plan.
@ -244,4 +244,4 @@ readiness, upgrade, failure, and restore probes named by their support profile.
- Cloudflare trusted client-IP capture is blocked until direct-origin bypass is
prevented and re-probed live.
- Upstream Dokploy publication is blocked on a disposable template deployment
using an immutable Codebase Chat image.
using an immutable Meridian image.

View file

@ -48,16 +48,16 @@ any non-Redis runtime mode or transport claim is introduced.
## Proof gates
```bash
cargo test -p codebase-chat-pubsub --lib
cargo clippy -p codebase-chat-pubsub --all-targets -- -D warnings
REDIS_URL=redis://127.0.0.1:6390 cargo test -p codebase-chat-pubsub \
cargo test -p meridian-pubsub --lib
cargo clippy -p meridian-pubsub --all-targets -- -D warnings
REDIS_URL=redis://127.0.0.1:6390 cargo test -p meridian-pubsub \
dedicated_subscriber_faults_fail_health_while_pool_stays_ready_and_recover \
-- --ignored --nocapture
just subscriber-fault-matrix
cargo test -p codebase-chat-relay \
cargo test -p meridian-relay \
subscriber_outage_fences_registration_and_reopens_after_recovery --lib
cargo check -p codebase-chat-relay
cargo test -p codebase-chat-db migration::tests
cargo check -p meridian-relay
cargo test -p meridian-db migration::tests
REPLY_BENCH_ALLOW_DATABASE_MUTATION=1 scripts/benchmark-reply-path.sh
just ci
```

View file

@ -1,6 +1,6 @@
# Features
Feature specs for Codebase Chat. Each doc is a working contract for one area:
Feature specs for Meridian. Each doc is a working contract for one area:
what it is, what it currently is *actually* (grounded in file paths, not
intent), where the gaps are, and the bite-sized tasks that close them.
@ -52,7 +52,7 @@ preview features; most have no flag yet because they have no code yet.
- [feature-parity-index.md](./feature-parity-index.md) — **read first.** The
matrix, the suggested order, and the table of reference features *examined and
excluded* because Codebase Chat already has an equivalent.
excluded* because Meridian already has an equivalent.
- [feature-granular-permissions.md](./feature-granular-permissions.md) —
**high** · authorization is a three-value string role; kind 39003 orphaned.
Unblocks four specs below.

View file

@ -12,15 +12,15 @@
# Feature: Agent Distribution
Codebase Chat has a deeper agent story than the reference has a bot story — ACP
Meridian has a deeper agent story than the reference has a bot story — ACP
harness, personas, teams, engram memory, owner-reviewed drafts, turn metrics.
On capability, this is not a gap; it is a lead.
On **distribution** it is a gap, and a sharp one. Every agent in Codebase Chat
On **distribution** it is a gap, and a sharp one. Every agent in Meridian
is defined by one workspace owner for one workspace. `KIND_MANAGED_AGENT`
(30177) is addressed by `(owner_pubkey, kind, agent_pubkey)` and its doc comment
says so plainly: *"published by the workspace owner"*
(`crates/codebase-chat-core/src/kind.rs:252-259`). There is no way for someone
(`crates/meridian-core/src/kind.rs:252-259`). There is no way for someone
to build an agent and let another community use it. Build a good code-review
agent for your team and a neighbouring team must rebuild it from scratch.
@ -41,7 +41,7 @@ The reference's model cannot be copied here. Its `Bot` carries a `token`
(`crates/core/models/src/v0/bots.rs:15`) and inviting a bot to your server
grants *the publisher's* running process access to your messages. That is a
reasonable trade for a hosted consumer platform and the wrong one for a tool
whose channels contain source code. A Codebase Chat community must never
whose channels contain source code. A Meridian community must never
execute, or route its content through, infrastructure controlled by a stranger
because someone clicked Install.
@ -89,9 +89,9 @@ published it.
## Current State
**Codebase Chat — agents are workspace-local by construction.**
**Meridian — agents are workspace-local by construction.**
`crates/codebase-chat-core/src/kind.rs:252-259`:
`crates/meridian-core/src/kind.rs:252-259`:
```rust
/// NIP-AP: Managed Agent (parameterized replaceable, owner-authored).
@ -116,20 +116,20 @@ reference, `:87`), `KIND_PERSONA` (30175), `KIND_TEAM` (30176, "user-facing
grouping of personas", `:245-250`), `KIND_AGENT_ENGRAM` (30174, encrypted
memory).
Crates: `codebase-chat-acp`, `codebase-chat-agent`, `codebase-chat-persona`,
`codebase-chat-dev-mcp`, `sprig`. Desktop: `features/agents/`,
Crates: `meridian-acp`, `meridian-agent`, `meridian-persona`,
`meridian-dev-mcp`, `sprig`. Desktop: `features/agents/`,
`features/agent-memory/`.
No discovery concept exists at any layer:
```bash
grep -rn "discoverable\|marketplace\|public_agent\|publicAgent\|directory" \
crates/codebase-chat-core/src crates/codebase-chat-relay/src/handlers
crates/meridian-core/src crates/meridian-relay/src/handlers
```
One hit, an unrelated comment in `moderation_notices.rs:149`.
| Capability | Codebase Chat | Reference |
| Capability | Meridian | Reference |
| --- | --- | --- |
| Owner-defined agent | ✅ 30177 | ✅ `Bot` |
| Persona / prompt config | ✅ 30175, 30176 | ❌ |
@ -140,7 +140,7 @@ One hit, an unrelated comment in `moderation_notices.rs:149`.
| **Third-party discovery** | ❌ | ✅ `fetch_public_bot` |
| **Install into a community** | ❌ | ✅ `invite_bot` |
Codebase Chat leads on five of eight rows and has none of the last three.
Meridian leads on five of eight rows and has none of the last three.
**Reference — a token and a public flag.**
@ -166,7 +166,7 @@ minimal.
| --- | --- | --- | --- |
| A1 | An agent definition cannot leave the workspace that created it. Every community rebuilds the same agents. | 30177 addressed by owner pubkey, `kind.rs:252-259` | medium |
| A2 | No discovery primitive — no way to fetch an agent definition you did not author. | `grep` for discovery terms returns one unrelated comment | medium |
| A3 | No install flow, so no capability gates it and no audit records it. | absence of any install path; `codebase-chat-audit` has no agent-install entry | medium |
| A3 | No install flow, so no capability gates it and no audit records it. | absence of any install path; `meridian-audit` has no agent-install entry | medium |
| A4 | Personas and teams (30175/30176) are equally workspace-local, so even the reusable, secret-free half of an agent cannot be shared. | `kind.rs:245-250` — "published by the workspace owner" | low |
| A5 | **Prospective:** a definition format that permits a URL, endpoint, or token would let a publisher route an installer's content off-box. The rejected model must be excluded by the schema, not by convention. | design risk of A1's fix | high (if built without Phase 1's exclusion list) |
| A6 | No definition versioning, so an accepted update has no identity to compare against. | 30177 is replaceable — a new publish silently supersedes | low |
@ -199,7 +199,7 @@ second one that will drift.
**Task 1.2: Failing test for the exclusion list**
Files:
- Create: `crates/codebase-chat-core/src/agent_definition.rs`
- Create: `crates/meridian-core/src/agent_definition.rs`
```rust
#[test]
@ -222,7 +222,7 @@ fn unknown_fields_are_rejected_not_ignored() {
}
```
Run `cargo test -p codebase-chat-core agent_definition` — expected FAIL.
Run `cargo test -p meridian-core agent_definition` — expected FAIL.
Implement, re-run, expect PASS.
The third test is the one that holds over time. A pure deny-list is bypassed by
@ -231,7 +231,7 @@ the next field anyone adds; parse strictly and reject unknown fields.
**Task 1.3: Allocate the kind**
Files:
- Modify: `crates/codebase-chat-core/src/kind.rs`
- Modify: `crates/meridian-core/src/kind.rs`
Addressable, author = publisher, `d` = stable definition id. Distinct from
30177: 30177 is *my agent, running here*; this is *a recipe anyone may
@ -246,7 +246,7 @@ and [`feature-user-blocking.md`](./feature-user-blocking.md)).
**Task 2.1: Ingest validation**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/ingest.rs`
- Modify: `crates/meridian-relay/src/handlers/ingest.rs`
Apply Task 1.2's parser. A definition that fails is rejected, not stored — a
stored-but-invalid definition is one client's lenient parser away from being
@ -255,13 +255,13 @@ honoured.
**Task 2.2: CLI first**
Files:
- Modify: `crates/codebase-chat-cli/src/commands/agents.rs`
- Modify: `crates/codebase-chat-cli/src/lib.rs`
- Modify: `crates/meridian-cli/src/commands/agents.rs`
- Modify: `crates/meridian-cli/src/lib.rs`
```
codebase-chat agents publish --agent <pubkey> --id <slug>
codebase-chat agents fetch-definition --author <hex> --id <slug>
codebase-chat agents install --author <hex> --id <slug> [--name "..."]
meridian agents publish --agent <pubkey> --id <slug>
meridian agents fetch-definition --author <hex> --id <slug>
meridian agents install --author <hex> --id <slug> [--name "..."]
```
`install` generates a **fresh keypair**, writes a local 30177 owned by the
@ -272,8 +272,8 @@ Phase 1.
**Task 2.3: Capability gate and audit**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/moderation_authz.rs`
- Modify: `crates/codebase-chat-audit/src/lib.rs`
- Modify: `crates/meridian-relay/src/handlers/moderation_authz.rs`
- Modify: `crates/meridian-audit/src/lib.rs`
`agent:install`, following the reference's per-scope split
(`invite.rs:44,56`). Audit records author, definition id, version, and installer.
@ -281,7 +281,7 @@ Files:
**Task 2.4: Personas and teams (closes A4)**
Files:
- Modify: `crates/codebase-chat-core/src/kind.rs` (doc comments)
- Modify: `crates/meridian-core/src/kind.rs` (doc comments)
30175/30176 are already secret-free projections. Allow them to be published and
installed by the same path; no new mechanism.
@ -337,8 +337,8 @@ why, so a later audit does not "correct" it.
## Verification
```bash
cargo test -p codebase-chat-core agent_definition
cargo test -p codebase-chat-relay agent_definition
cargo test -p meridian-core agent_definition
cargo test -p meridian-relay agent_definition
just test
just desktop-test
just desktop-e2e-smoke
@ -348,14 +348,14 @@ just ci
Confirm the parser rejects unknown fields rather than ignoring them:
```bash
cargo test -p codebase-chat-core unknown_fields_are_rejected_not_ignored
cargo test -p meridian-core unknown_fields_are_rejected_not_ignored
```
Confirm no secret can cross a community boundary — this is the invariant the
whole feature rests on:
```bash
grep -rn "token\|secret_key\|auth_tag\|env" crates/codebase-chat-core/src/agent_definition.rs
grep -rn "token\|secret_key\|auth_tag\|env" crates/meridian-core/src/agent_definition.rs
```
Hits outside the rejection list mean a secret-carrying field was added to the
@ -364,5 +364,5 @@ publishable schema.
Confirm install mints a new identity:
```bash
cargo test -p codebase-chat-cli agents_install_generates_fresh_keypair
cargo test -p meridian-cli agents_install_generates_fresh_keypair
```

View file

@ -14,7 +14,7 @@
A per-channel cooldown that a moderator sets on a busy channel: one message per
N seconds per member, with a capability to bypass it.
Codebase Chat has rate limiting, and it is not this. The existing limiter is an
Meridian has rate limiting, and it is not this. The existing limiter is an
**operator-configured anti-abuse control** — fixed windows, set by environment
variable, scoped to a principal *class*. Slowmode is a **moderator-configured
conversation-pacing tool** — set per channel, in the product, by the person
@ -35,10 +35,10 @@ with a TTL equal to the interval. Presence of the key means "still cooling
down"; its TTL is the remaining seconds. This costs one `SET NX EX` on the
success path and needs no scheduled cleanup.
**Rejected: reusing `LimitType` in `codebase-chat-auth`.** That enum is keyed by
**Rejected: reusing `LimitType` in `meridian-auth`.** That enum is keyed by
principal class (`human`, `agent-standard`, `agent-elevated`, `agent-platform`)
and configured from `CODEBASE_CHAT_RATE_LIMIT_*` env vars
(`crates/codebase-chat-relay/src/config.rs:305-329`). Slowmode is keyed by
and configured from `MERIDIAN_RATE_LIMIT_*` env vars
(`crates/meridian-relay/src/config.rs:305-329`). Slowmode is keyed by
channel and configured by a moderator at runtime. Threading a runtime, per-channel
value through an operator-configured enum would corrupt both.
@ -47,7 +47,7 @@ but the relay must be the authority — a client that does not implement slowmod
would otherwise bypass it entirely.
**Deliberate asymmetry: agents are not exempt.** The reference has no notion of
an agent principal. Codebase Chat does, and the tempting shortcut is to exempt
an agent principal. Meridian does, and the tempting shortcut is to exempt
them since they are already rate-limited by class. Do not — an agent posting
into a slowmoded channel is exactly the flood the moderator is trying to stop.
Exemption is a capability grant, not a principal-class property.
@ -68,21 +68,21 @@ Exemption is a capability grant, not a principal-class property.
- **No slowmode on DMs.** It is a moderation tool and DMs have no moderator.
- **No retroactive enforcement.** Raising the interval does not extend an
in-flight cooldown.
- **No replacement of the existing limiter.** The `CODEBASE_CHAT_RATE_LIMIT_*`
- **No replacement of the existing limiter.** The `MERIDIAN_RATE_LIMIT_*`
controls stay exactly as they are, and slowmode is checked in addition.
- **No auto-slowmode.** Automatic engagement under load is a plausible follow-up
and is out of scope until manual slowmode has shipped and been used.
## Current State
**Codebase Chat — no slowmode at any layer.**
**Meridian — no slowmode at any layer.**
```bash
grep -ril "slowmode" crates desktop/src mobile/lib web/src # → no output
```
What exists instead: `crates/codebase-chat-relay/src/admission.rs:17-26`,
`check_principal`, delegating to `codebase_chat_auth::RateLimiter` with a
What exists instead: `crates/meridian-relay/src/admission.rs:17-26`,
`check_principal`, delegating to `meridian_auth::RateLimiter` with a
`LimitType` and a window. The in-file comment at `:7` records the shape
candidly — "is still a fixed-window limiter, so a Redis-backed token bucket
would be a [better fit]".
@ -134,7 +134,7 @@ decision in
| --- | --- | --- | --- |
| S1 | No per-channel cooldown exists anywhere in the stack. | `grep -ril slowmode` over `crates desktop/src mobile/lib web/src` returns nothing | medium |
| S2 | Moderators have no pacing tool between "allow" and "remove the member". A flooding-but-not-abusive member can only be muted or kicked. | `moderation_authz.rs` `ModerationAction` variants | medium |
| S3 | The only rate control is operator-scoped, so per-community tuning requires a relay redeploy. | `config.rs:305-329`, all `CODEBASE_CHAT_RATE_LIMIT_*` env-sourced | medium |
| S3 | The only rate control is operator-scoped, so per-community tuning requires a relay redeploy. | `config.rs:305-329`, all `MERIDIAN_RATE_LIMIT_*` env-sourced | medium |
| S4 | Rate-limit rejections are not surfaced as a countdown; the client learns by failing. | `resetRateLimitGate()` is a gate, not a timer | low |
| S5 | Bypass has nowhere to live — there is no capability model to hang it on. | blocked on P1/P2 in `feature-granular-permissions.md` | low (blocking, not severe) |
@ -145,8 +145,8 @@ decision in
**Task 1.1: Failing test for the cooldown decision**
Files:
- Create: `crates/codebase-chat-relay/src/handlers/slowmode.rs`
- Modify: `crates/codebase-chat-relay/src/handlers/mod.rs`
- Create: `crates/meridian-relay/src/handlers/slowmode.rs`
- Modify: `crates/meridian-relay/src/handlers/mod.rs`
Keep the pure decision separate from the Dragonfly round trip so it is testable
without infrastructure:
@ -168,14 +168,14 @@ fn bypass_wins_over_active_cooldown() {
}
```
Run `cargo test -p codebase-chat-relay slowmode` — expected FAIL. Implement,
Run `cargo test -p meridian-relay slowmode` — expected FAIL. Implement,
re-run, expect PASS.
**Task 1.2: Interval on channel metadata**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/relay_admin.rs`
- Modify: `crates/codebase-chat-core/src/kind.rs` (doc comment on 39000 only)
- Modify: `crates/meridian-relay/src/handlers/relay_admin.rs`
- Modify: `crates/meridian-core/src/kind.rs` (doc comment on 39000 only)
A `slowmode` tag on kind 39000, seconds as a decimal string. Validate
`0..=21600` at ingest and reject out-of-range rather than clamping — a silently
@ -184,7 +184,7 @@ clamped 86400 is a moderator who thinks they set a day and got six hours.
**Task 1.3: Enforce at ingest**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/ingest.rs`
- Modify: `crates/meridian-relay/src/handlers/ingest.rs`
Check *after* signature verification and *after* the existing
`check_principal` admission call — slowmode is the narrower gate and should not
@ -196,7 +196,7 @@ oversight.
**Task 1.4: Integration test**
Files:
- Create: `crates/codebase-chat-test-client/tests/e2e_slowmode.rs`
- Create: `crates/meridian-test-client/tests/e2e_slowmode.rs`
Needs Postgres + Dragonfly, so `just test`, not `just test-unit`. Publish two
messages inside the window, assert the second is rejected with remaining
@ -207,10 +207,10 @@ seconds; assert a bypass holder is unaffected; assert kind 7 passes.
**Task 2.1: CLI first**
Files:
- Modify: `crates/codebase-chat-cli/src/commands/channels.rs`
- Modify: `crates/codebase-chat-cli/src/lib.rs`
- Modify: `crates/meridian-cli/src/commands/channels.rs`
- Modify: `crates/meridian-cli/src/lib.rs`
`codebase-chat channels set-slowmode --channel <uuid> --seconds 30`. Per the
`meridian channels set-slowmode --channel <uuid> --seconds 30`. Per the
root guide, the agent surface lands before the UI and becomes the reference
behavior.
@ -290,7 +290,7 @@ bug (see the cross-cutting findings in [`README.md`](./README.md)).
## Verification
```bash
cargo test -p codebase-chat-relay slowmode
cargo test -p meridian-relay slowmode
just test # integration; needs Postgres + Dragonfly
just desktop-test
just desktop-e2e-smoke
@ -302,7 +302,7 @@ Confirm slowmode is distinct from the operator limiter — these must not
converge on one code path:
```bash
grep -rn "LimitType\|check_principal" crates/codebase-chat-relay/src/handlers/slowmode.rs
grep -rn "LimitType\|check_principal" crates/meridian-relay/src/handlers/slowmode.rs
```
Any output means Task 1.1's separation was not maintained.

View file

@ -24,7 +24,7 @@ near-duplicate").
## Decision
**Select on one test: does the capability change meaning when the work becomes
shared?** Codebase Chat is making the Excel→Google-Sheets transition — the
shared?** Meridian is making the Excel→Google-Sheets transition — the
capability existed in the local artifact; what changes is that it becomes a
persistent, member-scoped, discoverable property of a workspace other people
also inhabit.
@ -43,7 +43,7 @@ That test sorts the nine cleanly:
| Rejected | Reason |
| --- | --- |
| Hooks (as a new mechanism) | `codebase-chat-workflow` already occupies this space — YAML automations with approval gates. Root `CLAUDE.md` § Design Law: *"Add attributes, not mechanisms… Rule- or formula-based policy DSLs are explicitly rejected."* A second automation mechanism is the exact failure that law prevents. If workflows are less discoverable than hooks, that is a surfacing bug — workflows are preview-gated today. |
| Hooks (as a new mechanism) | `meridian-workflow` already occupies this space — YAML automations with approval gates. Root `CLAUDE.md` § Design Law: *"Add attributes, not mechanisms… Rule- or formula-based policy DSLs are explicitly rejected."* A second automation mechanism is the exact failure that law prevents. If workflows are less discoverable than hooks, that is a surfacing bug — workflows are preview-gated today. |
| Pets, Appshots | Reference-product whimsy with no group semantics. |
| Computer use of arbitrary desktop apps | Controlling Excel and Chrome on the member's machine is a different product with a different threat model. **Tool rules (item 9) is the part we want** and it is scoped to agent tools we already run, not to the member's desktop. |
| Generic "General" catch-all section | Our sections are already specific; a catch-all is where settings go to become unfindable. Search (item 1) solves the discovery problem a General tab pretends to. |
@ -98,8 +98,8 @@ verifiable as presence rows.
| 4 | MCP servers | `agents/ui/McpServersSection.tsx`, rendered only at `AgentConfigPanel.tsx:426` | **per-agent** | n/a |
| 5 | Identity & Git readiness | `settings/ui/IdentityGitSettingsCard.tsx`; typed probe in `src-tauri/src/commands/identity_git.rs`; recovery gates in `app/App.tsx` | **per profile on this device**, read-only | n/a |
| 6 | Plan / billing | absent | — | — |
| 7 | Tool rules | absent as a member-visible grant; harness/MCP wiring in `crates/codebase-chat-acp/` | **per-agent config** | — |
| 8 | Extension distribution | `KIND_MANAGED_AGENT` (30177), `crates/codebase-chat-core/src/kind.rs:252-259` | **one workspace owner, one workspace** | — |
| 7 | Tool rules | absent as a member-visible grant; harness/MCP wiring in `crates/meridian-acp/` | **per-agent config** | — |
| 8 | Extension distribution | `KIND_MANAGED_AGENT` (30177), `crates/meridian-core/src/kind.rs:252-259` | **one workspace owner, one workspace** | — |
| 9 | Design preview | absent; only `opener:default` in `src-tauri/capabilities/default.json:19` | — | — |
Two facts that shape the plan:
@ -115,7 +115,7 @@ Two facts that shape the plan:
| # | Gap | Evidence | Severity |
| --- | --- | --- | --- |
| G1 | Agent tool authorization is not enumerable. There is no member-visible answer to "which tools may this agent run in this community", and no dated record of who granted it. | no grant rows for tools anywhere under `crates/codebase-chat-core/src/kind.rs` | **high** |
| G1 | Agent tool authorization is not enumerable. There is no member-visible answer to "which tools may this agent run in this community", and no dated record of who granted it. | no grant rows for tools anywhere under `crates/meridian-core/src/kind.rs` | **high** |
| G2 | **Resolved.** Audio input selection is available before a huddle, reacts in the same window, and survives restart per profile. A disconnected saved device falls back once to the system default; permission failures are not retried. | `VoiceSettingsCard.tsx`; `useAudioInputPreference.ts`; `acquireMicrophoneStream.ts`; focused unit and Playwright coverage | resolved |
| G3 | **Resolved.** Lost, locked, and reset-failed identities are intercepted by distinct fail-closed recovery screens before Settings or publish actions mount. Healthy Settings now reports event signing, Git >=2.46 compatibility, NIP-98 Project authentication, NIP-GS tool installation, and global Git author metadata without reading an nsec. | `app/App.tsx`; `commands/identity_git.rs`; `IdentityGitSettingsCard.tsx`; `identity-lost.spec.ts`; `identity-git-settings.spec.ts` | resolved |
| G4 | No monetization surface. Nothing in-client states a plan, a limit, or a remaining balance. | absent from `SettingsPanels.tsx` section union | medium |
@ -235,7 +235,7 @@ surfaces existing ones. Criteria that bind the later phases specifically:
- **#1/#2 Protocol complete and written down** — Phases 2–4 each introduce
kinds (entitlement, tool grant, extension listing, preview target). Each needs
a `docs/nips/` document and a reader before merge.
- **#6 Agent surface** — tool rules must be readable from `codebase-chat-cli`;
- **#6 Agent surface** — tool rules must be readable from `meridian-cli`;
an agent that cannot enumerate its own permitted tools cannot explain a
denial.
- **#7 Failure paths** — Phase 2's limit-reached and Phase 3's denial are the
@ -254,7 +254,7 @@ cd desktop && pnpm exec playwright test tests/e2e/voice-settings.spec.ts --proje
rg -n "useAudioInputPreference|acquireMicrophoneStream" desktop/src
grep -rn "McpServersSection" desktop/src | grep -v "ui/McpServersSection.tsx:"
sed -n '307,318p' desktop/src-tauri/src/app_state.rs
sed -n '252,259p' crates/codebase-chat-core/src/kind.rs
sed -n '252,259p' crates/meridian-core/src/kind.rs
# Standing rule for every phase that adds a kind
just check-kinds

View file

@ -1,11 +1,11 @@
> **Status** — production candidate · browser and desktop surfaces · server flag
> `CODEBASE_CHAT_CONTROL_COMMUNITIES_UI` · tracked by
> `MERIDIAN_CONTROL_COMMUNITIES_UI` · tracked by
> [`epic-community-registration-production.md`](../epics/epic-community-registration-production.md)
# Feature: Community Registration
Community registration is the account-shaped control-plane surface for creating
and managing relay communities. It is served by `codebase-chat-control-plane` at
and managing relay communities. It is served by `meridian-control-plane` at
`{public_origin}{api_base_path}/communities`; no third-party registration service
or identity provider is assumed.
@ -45,12 +45,12 @@ Names never encode ownership.
- Sign in and bootstrap a browser credential.
- Connect a Nostr identity by sending a kind `24243` challenge to the desktop
through `codebasechat://nostr-bind`; the secret key never enters the browser.
through `meridian://nostr-bind`; the secret key never enters the browser.
- Check name availability, create a community, copy/open its relay address,
archive it, and restore it.
- Disconnect the identity only after all communities have been transferred.
- Disable the browser page entirely with
`CODEBASE_CHAT_CONTROL_COMMUNITIES_UI=off` (the API remains available).
`MERIDIAN_CONTROL_COMMUNITIES_UI=off` (the API remains available).
The responsive browser page works at narrow viewports, but this is not Flutter
mobile parity. Identity connection and community opening currently require the
@ -73,15 +73,15 @@ unbounded request admission. The current gates are:
```bash
. ./bin/activate-hermit
CODEBASE_CHAT_TEST_DATABASE_URL=postgres://... \
cargo test -p codebase-chat-control-plane --all-targets --no-fail-fast
cargo clippy -p codebase-chat-control-plane --all-targets -- -D warnings
cargo fmt --check -p codebase-chat-control-plane
MERIDIAN_TEST_DATABASE_URL=postgres://... \
cargo test -p meridian-control-plane --all-targets --no-fail-fast
cargo clippy -p meridian-control-plane --all-targets -- -D warnings
cargo fmt --check -p meridian-control-plane
pnpm -C desktop check
CONTROL_PLANE_URL=http://127.0.0.1:8090 \
pnpm -C desktop exec playwright test \
--config tests/control-plane/playwright.config.mjs
bash deploy/charts/codebase-chat-control-plane/tests/render.sh
bash deploy/charts/meridian-control-plane/tests/render.sh
```
The Playwright suite signs the real binding event, creates, archives, restores,

View file

@ -47,7 +47,7 @@ second-resolution and 45002 is not replaceable, so a user correcting a misclick
within one second produces two votes with identical timestamps. The first
implementation broke the tie on event id; the second "fixed" it by breaking on
input-array position, on the assumption that the relay returns insertion order.
**It does not** — `codebase-chat-db/src/event.rs` orders
**It does not** — `meridian-db/src/event.rs` orders
`created_at DESC, id ASC`, so array position resolved right back to "largest
SHA-256 id wins". The two implementations were behaviorally identical.
@ -114,7 +114,7 @@ explicitly rather than by continued drift.
## Current State
**Protocol** — `crates/codebase-chat-core/src/kind.rs:487-495`. The comment
**Protocol** — `crates/meridian-core/src/kind.rs:487-495`. The comment
records the history: v1 used the addressable range 30001–30003, which was
wrong; forum kinds now live at 45001–45003.
@ -157,7 +157,7 @@ Tests: `mobile/test/features/forum/forum_models_test.dart`,
**CLI** — `channels create --type forum` (`commands/channels.rs:294,321`),
`messages vote --event --direction` (`lib.rs:497`, implemented at
`commands/messages.rs:843-865` via `codebase_chat_sdk::build_vote`).
`commands/messages.rs:843-865` via `meridian_sdk::build_vote`).
## Gap Register
@ -368,7 +368,7 @@ just desktop-typecheck
just desktop-e2e-smoke
just mobile-check
just mobile-test
cargo test -p codebase-chat-relay forum
cargo test -p meridian-relay forum
```
Confirm the vote kind is no longer orphaned:
@ -383,5 +383,5 @@ End-to-end through the CLI, which already works and is the reference behavior
the UIs must match:
```bash
codebase-chat --format compact messages vote --event <hex> --direction up
meridian --format compact messages vote --event <hex> --direction up
```

View file

@ -12,7 +12,7 @@
# Feature: Granular Permissions
Codebase Chat authorizes every moderation action against a three-value string
Meridian authorizes every moderation action against a three-value string
role — `owner`, `admin`, `member` — stored in `relay_members.role`. There is no
per-channel override, no capability decomposition, and no way to grant one
person one power. The reference implementation resolves the same questions
@ -24,9 +24,9 @@ bits* and cannot be specified coherently until this model exists. Read this
first.
The defining artifact: **kind 39003 `KIND_NIP29_GROUP_ROLES` is orphaned.** It
is declared at `crates/codebase-chat-core/src/kind.rs:368`, listed as
is declared at `crates/meridian-core/src/kind.rs:368`, listed as
addressable at `:621`, and range-matched for `d`-tag derivation at
`crates/codebase-chat-relay/src/handlers/event.rs:41`. Nothing else in the
`crates/meridian-relay/src/handlers/event.rs:41`. Nothing else in the
repository reads or writes it — not the relay, not the SDK, not the CLI, not
any client. The slot NIP-29 reserves for a role system is allocated and empty.
@ -35,7 +35,7 @@ any client. The slot NIP-29 reserves for a role system is allocated and empty.
**Introduce a capability set, not a permission bitfield.**
The reference packs permissions into `u64` bitflags because its transport is a
REST API with a fixed schema. Codebase Chat's transport is Nostr events, where
REST API with a fixed schema. Meridian's transport is Nostr events, where
the natural encoding is tags. A bitfield forces a registry of bit positions that
can never be reordered and is unreadable in a raw event; a tag list
(`["capability", "message:delete"]`) is self-describing, extends without
@ -48,13 +48,13 @@ already carrying numbering scars, and inheriting them buys nothing.
**Rejected: leaving the three-role model and adding special cases.** That is
the current trajectory. `moderation_authz.rs` already encodes "channel role
covers only delete and kick" as a hardcoded match arm
(`crates/codebase-chat-relay/src/handlers/moderation_authz.rs:173-177`, test at
(`crates/meridian-relay/src/handlers/moderation_authz.rs:173-177`, test at
`:296`). Every new power adds an arm. The reference's per-channel overlay exists
precisely because that approach does not scale past about five actions.
**Deliberate divergence: no role *rank* ordering in phase 1.** The reference has
`roles_edit_positions.rs` (5.8K) implementing rank comparison so a moderator
cannot ban someone above them. Codebase Chat gets this today for free from the
cannot ban someone above them. Meridian gets this today for free from the
flat owner > admin > member ladder, and the guard rail is already written
(`moderation_authz.rs:102` reads the target's role "only for the admin guard
rail"). Ranked custom roles are Phase 4 and may never be needed.
@ -68,7 +68,7 @@ rail"). Ranked custom roles are Phase 4 and may never be needed.
3. A community can grant "delete messages in #support" without granting `admin`.
4. The existing `owner`/`admin`/`member` roles keep working unchanged, expressed
as built-in capability bundles.
5. Capability denial is auditable — `codebase-chat-audit` records which
5. Capability denial is auditable — `meridian-audit` records which
capability was missing.
## Non-Goals
@ -85,9 +85,9 @@ rail"). Ranked custom roles are Phase 4 and may never be needed.
## Current State
**Codebase Chat — authorization is a three-arm match.**
**Meridian — authorization is a three-arm match.**
`crates/codebase-chat-relay/src/handlers/moderation_authz.rs:62-69`:
`crates/meridian-relay/src/handlers/moderation_authz.rs:62-69`:
```rust
pub enum ModerationAuthority {
@ -105,7 +105,7 @@ Validation lives in `relay_admin.rs:276-278` — `if role != "admin" && role !=
"member"` — i.e. the closed set is enforced by string comparison at the write
site, not by a type.
| Question | Reference answers with | Codebase Chat answers with |
| Question | Reference answers with | Meridian answers with |
| --- | --- | --- |
| May X delete a message? | `ManageMessages` bit, channel overlay | `ModerationAuthority` match arm |
| May X ban? | `BanMembers` bit | `owner`/`admin` string compare |
@ -131,8 +131,8 @@ Overlays are set by four routes — `servers/permissions_set.rs`,
`servers/permissions_set_default.rs`, `channels/permissions_set.rs`,
`channels/permissions_set_default.rs` — each writing an allow/deny pair.
**What Codebase Chat already has that the reference does not.** The audit
hash-chain (`codebase-chat-audit`) has no reference counterpart;
**What Meridian already has that the reference does not.** The audit
hash-chain (`meridian-audit`) has no reference counterpart;
`ViewAuditLogs` is a bit there and a subsystem here. Do not regress it.
## Gap Register
@ -144,7 +144,7 @@ hash-chain (`codebase-chat-audit`) has no reference counterpart;
| P3 | Each new moderated action needs a new match arm. `ChannelRole` is already special-cased to "delete and kick" in a match arm rather than a data lookup. | `moderation_authz.rs:173-177`, test `channel_role_covers_only_delete_and_kick` at `:296` | high |
| P4 | `relay_members.role` is an unvalidated `String` at the read site; the closed set is enforced only by string comparison at the write site. | `moderation_authz.rs:96-100` vs `relay_admin.rs:276-278` | medium |
| P5 | No per-channel authorization overlay. A community moderator is a moderator everywhere. | absence of any channel-scoped grant table in `migrations/` | medium |
| P6 | Capability denials are not distinguishable in the audit log — a denial records the action, not the missing capability. | `codebase-chat-audit` has no capability field | low |
| P6 | Capability denials are not distinguishable in the audit log — a denial records the action, not the missing capability. | `meridian-audit` has no capability field | low |
## Maturation Plan
@ -172,8 +172,8 @@ audit:read
**Task 1.2: Failing test for capability resolution**
Files:
- Create: `crates/codebase-chat-core/src/capability.rs`
- Modify: `crates/codebase-chat-core/src/lib.rs`
- Create: `crates/meridian-core/src/capability.rs`
- Modify: `crates/meridian-core/src/lib.rs`
```rust
#[test]
@ -191,7 +191,7 @@ fn unknown_role_grants_nothing() {
}
```
Run `cargo test -p codebase-chat-core capability` — expected FAIL. Implement,
Run `cargo test -p meridian-core capability` — expected FAIL. Implement,
re-run, expect PASS. The second test is the one that matters: an unrecognized
role string must be inert, not permissive. Today an unknown string falls to the
`_ =>` arm at `moderation_authz.rs:173` — verify what that arm actually does
@ -200,8 +200,8 @@ before assuming it is safe.
**Task 1.3: Give 39003 a schema**
Files:
- Modify: `crates/codebase-chat-core/src/kind.rs`
- Modify: `crates/codebase-chat-relay/src/handlers/ingest.rs`
- Modify: `crates/meridian-core/src/kind.rs`
- Modify: `crates/meridian-relay/src/handlers/ingest.rs`
A 39003 event is `d` = role name, plus one `capability` tag per grant:
@ -221,7 +221,7 @@ does nothing.
**Task 2.1: Make the role a type, not a string**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/moderation_authz.rs`
- Modify: `crates/meridian-relay/src/handlers/moderation_authz.rs`
Parse `relay_members.role` into an enum at the read site (`:96-100`). The
`unwrap_or("")` at `relay_admin.rs:225-228` currently makes "no membership row"
@ -231,7 +231,7 @@ rather than incidental.
**Task 2.2: Replace the match arms**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/moderation_authz.rs`
- Modify: `crates/meridian-relay/src/handlers/moderation_authz.rs`
`authorize(actor, action, target)` resolves the actor's capability set once, then
checks `caps.has(action.capability())`. `ModerationAuthority` stays as the
@ -246,7 +246,7 @@ today's behavior exactly.
Files:
- Create: `migrations/<timestamp>_capability_grants.sql`
- Modify: `crates/codebase-chat-db/src/lib.rs`
- Modify: `crates/meridian-db/src/lib.rs`
Forward-only, per `migrations/AGENTS.md`. Resolution order: channel grant, then
community role bundle, then deny.
@ -256,17 +256,17 @@ community role bundle, then deny.
**Task 3.1: Record the missing capability**
Files:
- Modify: `crates/codebase-chat-audit/src/lib.rs`
- Modify: `crates/meridian-audit/src/lib.rs`
A denial entry names the capability that was absent.
**Task 3.2: CLI before UI**
Files:
- Modify: `crates/codebase-chat-cli/src/commands/` — new `roles` subcommand
- Modify: `crates/meridian-cli/src/commands/` — new `roles` subcommand
Per the root guide, agent-facing operations land in `codebase-chat-cli` first.
`codebase-chat roles create --name support-mod --capability message:delete
Per the root guide, agent-facing operations land in `meridian-cli` first.
`meridian roles create --name support-mod --capability message:delete
--channel <id>`. This is the reference behavior the UI must later match, and it
makes Phase 4 optional rather than blocking.
@ -282,7 +282,7 @@ Checked against
- [ ] Protocol complete — 39003 has no reader or writer (P1). **Blocker.**
- [ ] No orphaned kinds — same
- [ ] Agent surface — no `roles` subcommand in `codebase-chat-cli`
- [ ] Agent surface — no `roles` subcommand in `meridian-cli`
- [ ] Failure paths — unknown-role behavior undocumented (P4)
- [ ] Docs — no `docs/nips/NIP-CAP.md`
- [ ] Platform parity — n/a until Phase 4
@ -291,8 +291,8 @@ Checked against
## Verification
```bash
cargo test -p codebase-chat-core capability
cargo test -p codebase-chat-relay moderation_authz
cargo test -p meridian-core capability
cargo test -p meridian-relay moderation_authz
just test # integration; needs Postgres + Dragonfly
just ci
```
@ -309,5 +309,5 @@ what else has landed.
Confirm no behavior regression on the built-in ladder:
```bash
cargo test -p codebase-chat-relay channel_role_covers_only_delete_and_kick
cargo test -p meridian-relay channel_role_covers_only_delete_and_kick
```

View file

@ -9,7 +9,7 @@
# Feature: Generic Link Embeds
Paste a GitHub PR link into Codebase Chat and you get a rich preview. Paste a
Paste a GitHub PR link into Meridian and you get a rich preview. Paste a
link to anything else — a blog post, a news article, a design doc on a domain
nobody allowlisted — and you get a bare URL.
@ -49,7 +49,7 @@ readable by every client without a new API.
**Rejected: porting `january` as a separate service.** The reference splits it
out because its architecture is already multi-service (delta, bonfire, autumn,
january, gifbox, pushd, crond). Codebase Chat runs one relay binary. A second
january, gifbox, pushd, crond). Meridian runs one relay binary. A second
deployable to parse HTML would be the largest operational change in this parity
set for the smallest feature.
@ -83,7 +83,7 @@ is the fallback, not the replacement.
the extension path.
- **No media proxying.** The reference's `/proxy` route
(`january/src/api.rs:15`) re-serves remote images through its own host.
Codebase Chat has Blossom for media it owns; proxying arbitrary remote images
Meridian has Blossom for media it owns; proxying arbitrary remote images
is a separate bandwidth and abuse conversation.
- **No embeds in DMs in phase 1.** Unfurling a link in a private conversation
tells the relay operator what was linked. Ship it opt-in later, or not at all.
@ -95,7 +95,7 @@ is the fallback, not the replacement.
## Current State
**Codebase Chat — a nine-entry pattern matcher, desktop-only.**
**Meridian — a nine-entry pattern matcher, desktop-only.**
`desktop/src/shared/lib/linkPreview.ts:1-10` — the complete supported set:
@ -134,7 +134,7 @@ Mobile and web have nothing:
grep -rln "linkPreview\|link_preview\|LinkPreview" mobile/lib web/src # → no output
```
| Capability | Codebase Chat | Reference |
| Capability | Meridian | Reference |
| --- | --- | --- |
| Arbitrary URL title | ❌ | ✅ `og:title` |
| Description | ❌ | ✅ `og:description` |
@ -187,7 +187,7 @@ building it first creates pressure to ship the fetcher unguarded.
**Task 1.1: Failing test for URL admission**
Files:
- Create: `crates/codebase-chat-relay/src/unfurl/guard.rs`
- Create: `crates/meridian-relay/src/unfurl/guard.rs`
```rust
#[test]
@ -213,7 +213,7 @@ fn rejects_redirect_into_private_space() {
}
```
Run `cargo test -p codebase-chat-relay unfurl::guard` — expected FAIL.
Run `cargo test -p meridian-relay unfurl::guard` — expected FAIL.
Implement, re-run, expect PASS.
The third test is the one that catches the real exploit. Checking only the
@ -224,11 +224,11 @@ hop, and cap redirects.
**Task 1.2: Budgets**
Files:
- Modify: `crates/codebase-chat-relay/src/unfurl/mod.rs`
- Modify: `crates/codebase-chat-relay/src/config.rs`
- Modify: `crates/meridian-relay/src/unfurl/mod.rs`
- Modify: `crates/meridian-relay/src/config.rs`
Response size cap, total timeout, redirect cap, per-community rate limit.
Env-configured alongside the existing `CODEBASE_CHAT_RATE_LIMIT_*` knobs
Env-configured alongside the existing `MERIDIAN_RATE_LIMIT_*` knobs
(`config.rs:305-329`) — this *is* an operator anti-abuse control, unlike
slowmode (see [`feature-channel-slowmode.md`](./feature-channel-slowmode.md)),
so it belongs there.
@ -240,7 +240,7 @@ Read only the `<head>`. A 4 GB response should cost 8 KB.
**Task 2.1: Failing test for the fallback chains**
Files:
- Create: `crates/codebase-chat-relay/src/unfurl/opengraph.rs`
- Create: `crates/meridian-relay/src/unfurl/opengraph.rs`
Port the reference's chains exactly (`website_embed.rs:48-137`) and test each
fallback step — a chain that silently stops at `og:image` and never tries
@ -263,7 +263,7 @@ fn missing_metadata_yields_none_not_empty_string() { /* ... */ }
**Task 2.2: Allocate the kind**
Files:
- Modify: `crates/codebase-chat-core/src/kind.rs`
- Modify: `crates/meridian-core/src/kind.rs`
Addressable, `d` = hash of the normalized URL. Confirm the number is unclaimed
before taking it, and **do not merge the constant without a reader** — three
@ -273,7 +273,7 @@ it).
**Task 2.3: Publish on ingest**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/ingest.rs`
- Modify: `crates/meridian-relay/src/handlers/ingest.rs`
Extract URLs from 40002, enqueue asynchronously. Never block message ingest on
an outbound fetch — a slow remote host must not slow the channel.
@ -323,7 +323,7 @@ Files:
## Verification
```bash
cargo test -p codebase-chat-relay unfurl
cargo test -p meridian-relay unfurl
just test
just desktop-test
just desktop-e2e-smoke
@ -334,7 +334,7 @@ just ci
Confirm the SSRF guard runs on every redirect hop, not just admission:
```bash
cargo test -p codebase-chat-relay rejects_redirect_into_private_space
cargo test -p meridian-relay rejects_redirect_into_private_space
```
Confirm no client-side fetch remains:

View file

@ -12,7 +12,7 @@
# Feature: Per-Community Member Profiles
One person, several communities, one name. Codebase Chat identity is the global
One person, several communities, one name. Meridian identity is the global
Nostr `kind:0` profile: change your display name for a work community and it
changes everywhere you have ever posted, retroactively, including in communities
run by people you have never met.
@ -80,7 +80,7 @@ four precedence levels on every message render.
- **No global profile changes.** `kind:0` semantics are untouched.
- **No per-channel names.** Community scope only. The reference is per-server
too.
- **No name history UI.** `codebase-chat-audit` records the change; surfacing a
- **No name history UI.** `meridian-audit` records the change; surfacing a
timeline is not this feature.
- **No uniqueness enforcement.** Two members may pick the same display name;
the npub remains the identity. Enforcing uniqueness would require a registry
@ -90,19 +90,19 @@ four precedence levels on every message render.
## Current State
**Codebase Chat — one global identity, no overlay.**
**Meridian — one global identity, no overlay.**
```bash
grep -ril "nickname" crates desktop/src mobile/lib web/src # → no output
```
Identity resolution today is `kind:0` → npub. `KIND_PROFILE` is
`crates/codebase-chat-core/src/kind.rs:11` (kind 0), replaceable per
`crates/meridian-core/src/kind.rs:11` (kind 0), replaceable per
`is_replaceable` at `:701-705`.
The near-miss is kind 9033 `RELAY_ADMIN_SET_WORKSPACE_PROFILE`
(`kind.rs:335`). Despite the name it sets the **community's own icon**, not any
member's profile — `crates/codebase-chat-relay/src/handlers/relay_admin.rs:232-251`
member's profile — `crates/meridian-relay/src/handlers/relay_admin.rs:232-251`
reads a single `icon` tag and calls `set_community_icon`. It is community
branding, and it is admin/owner-gated at `:233`.
@ -115,7 +115,7 @@ branding, and it is admin/owner-gated at `:233`.
| Relay | replaceable store | — |
| Desktop | `features/profile/` | — |
| Mobile | `mobile/lib/features/profile/` | — |
| CLI | `codebase-chat users` | — |
| CLI | `meridian users` | — |
**Reference — nickname on the membership record.**
@ -158,7 +158,7 @@ override, because a name is required for legibility and an avatar is not.
Files:
- Create: `docs/nips/NIP-MEMBER-PROFILE.md`
- Modify: `crates/codebase-chat-core/src/kind.rs`
- Modify: `crates/meridian-core/src/kind.rs`
Two addressable kinds in 30000–39999 (`PARAM_REPLACEABLE_KIND_MIN/MAX`,
`kind.rs`), `d` = community id:
@ -175,7 +175,7 @@ pub const KIND_MEMBER_PROFILE_OVERRIDE: u32 = 30179;
claiming them:
```bash
grep -n "30178\|30179" crates/codebase-chat-core/src/kind.rs
grep -n "30178\|30179" crates/meridian-core/src/kind.rs
```
Also fix N4 in the same pass — extend the doc comment on 9033 to say it sets
@ -184,7 +184,7 @@ community branding and is *not* a member profile.
**Task 1.2: Failing test for resolution order**
Files:
- Create: `crates/codebase-chat-core/src/member_profile.rs`
- Create: `crates/meridian-core/src/member_profile.rs`
```rust
#[test]
@ -206,17 +206,17 @@ fn falls_through_to_npub_when_nothing_set() {
}
```
Run `cargo test -p codebase-chat-core member_profile` — expected FAIL.
Run `cargo test -p meridian-core member_profile` — expected FAIL.
Implement, re-run, expect PASS.
Put resolution in `codebase-chat-core` so the relay, CLI, and SDK share one
Put resolution in `meridian-core` so the relay, CLI, and SDK share one
implementation. Every client reimplementing a four-level precedence chain is
four chances to disagree about who someone is.
**Task 1.3: Ingest validation**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/ingest.rs`
- Modify: `crates/meridian-relay/src/handlers/ingest.rs`
30178: author must be a member of the `d` community. 30179: author must hold the
manage-nicknames capability, and the target `p` tag is required. Length-bound
@ -229,13 +229,13 @@ limit.
**Task 2.1: CLI first**
Files:
- Modify: `crates/codebase-chat-cli/src/commands/users.rs`
- Modify: `crates/codebase-chat-cli/src/lib.rs`
- Modify: `crates/meridian-cli/src/commands/users.rs`
- Modify: `crates/meridian-cli/src/lib.rs`
```
codebase-chat users set-community-profile --community <id> --name "..." [--avatar <url>]
codebase-chat users clear-community-profile --community <id>
codebase-chat moderation override-name --community <id> --pubkey <hex> --name "..."
meridian users set-community-profile --community <id> --name "..." [--avatar <url>]
meridian users clear-community-profile --community <id>
meridian moderation override-name --community <id> --pubkey <hex> --name "..."
```
**Task 2.2: Desktop resolution at the render path**
@ -312,8 +312,8 @@ Files:
## Verification
```bash
cargo test -p codebase-chat-core member_profile
cargo test -p codebase-chat-relay member_profile
cargo test -p meridian-core member_profile
cargo test -p meridian-relay member_profile
just test
just desktop-test
just desktop-e2e-smoke
@ -324,7 +324,7 @@ just ci
Confirm the three resolvers agree. All three must encode the same precedence:
```bash
grep -rn "npub1" crates/codebase-chat-core/src/member_profile.rs \
grep -rn "npub1" crates/meridian-core/src/member_profile.rs \
desktop/src/features/profile/lib/resolveMemberProfile.ts \
mobile/lib/features/profile/
```

View file

@ -18,7 +18,7 @@ it. It is how bridges work. A Matrix or IRC bridge holds one account and relays
messages from hundreds of people; without masquerade every bridged message
appears to come from "bridge-bot", and the conversation is unreadable.
Codebase Chat has no equivalent. It also has the strictest possible ground truth
Meridian has no equivalent. It also has the strictest possible ground truth
to build on — every event carries a signature over its author's pubkey, so a
display override can never be mistaken for an authorship claim by any client
that checks. That property makes masquerade *safer* here than in the reference,
@ -47,7 +47,7 @@ message, and the real author is always one hover away.
**Non-negotiable: the real author must be discoverable without leaving the
message.** The reference makes this a client convention. Here it is a
requirement of the spec, because Codebase Chat is a work tool where "who
requirement of the spec, because Meridian is a work tool where "who
actually said this" is an accountability question. A masqueraded message
renders with a persistent marker and reveals the true pubkey on hover or tap.
A client that hides the real author is non-conformant.
@ -67,7 +67,7 @@ a first-class product rather than an integration.
**Deliberate divergence: `masquerade_colour` requires the same capability as the
other two.** The reference gates colour behind `ManageRole`
(`models/src/v0/messages.rs:155`) because colour implies role membership in its
UI. Codebase Chat has no role-colour convention, so the extra gate protects
UI. Meridian has no role-colour convention, so the extra gate protects
nothing and adds a second authorization path to test.
## Goals
@ -91,28 +91,28 @@ nothing and adds a second authorization path to test.
## Current State
**Codebase Chat — absent.**
**Meridian — absent.**
```bash
grep -rin "masquerade" crates desktop/src mobile/lib web/src
```
Three hits, all the English word in unrelated comments:
`crates/codebase-chat-relay/src/conformance/mod.rs:185`,
`crates/codebase-chat-db/src/replica_fence.rs:47`,
`crates/meridian-relay/src/conformance/mod.rs:185`,
`crates/meridian-db/src/replica_fence.rs:47`,
`desktop/src/features/onboarding/welcomeCanvas.ts:33`.
Message identity today is `event.pubkey` → `kind:0` → npub, with no override at
any layer.
The adjacent surface is webhooks, which exist but are workflow triggers, not
message identity. `crates/codebase-chat-relay/src/api/bridge.rs:1817`
message identity. `crates/meridian-relay/src/api/bridge.rs:1817`
`workflow_webhook` authenticates by shared secret (`:1860`, header preferred
over query param because proxies log query strings) and fires a workflow. It
does not post a message under a custom identity, and there is no code path that
does.
| Capability | Reference | Codebase Chat |
| Capability | Reference | Meridian |
| --- | --- | --- |
| Post under a custom name | `Masquerade` bit 28 | — |
| Webhook posts as named service | webhook + masquerade | workflow trigger only |
@ -143,7 +143,7 @@ from the presence of the field. That is the part this spec tightens.
| --- | --- | --- | --- |
| M1 | No display override exists, so a bridge cannot relay legibly — every bridged message appears authored by the bridge account. | `grep -rin masquerade` returns 3 unrelated comment hits | medium |
| M2 | Webhooks cannot post under a service identity. A CI notification is indistinguishable from a human message by the same relay account. | `api/bridge.rs:1817` fires workflows; no message-identity path | medium |
| M3 | The reference has no server-side masquerade marker — detection depends entirely on the client noticing a field. Porting as-is imports that weakness. | absence of a marker in `models/src/v0/messages.rs:144-158` | medium (design risk, not a Codebase Chat defect) |
| M3 | The reference has no server-side masquerade marker — detection depends entirely on the client noticing a field. Porting as-is imports that weakness. | absence of a marker in `models/src/v0/messages.rs:144-158` | medium (design risk, not a Meridian defect) |
| M4 | No capability exists to gate it. | blocked on P1/P2 in `feature-granular-permissions.md` | low (blocking) |
| M5 | Moderation acts on `event.pubkey`, which is correct and must be verified to *stay* correct once a display name diverges from the author. | `moderation_authz.rs` resolves by pubkey throughout | low (regression risk) |
@ -174,8 +174,8 @@ absence of a stated rule.
**Task 1.2: Failing test for extraction and validation**
Files:
- Create: `crates/codebase-chat-core/src/masquerade.rs`
- Modify: `crates/codebase-chat-core/src/lib.rs`
- Create: `crates/meridian-core/src/masquerade.rs`
- Modify: `crates/meridian-core/src/lib.rs`
```rust
#[test]
@ -199,7 +199,7 @@ fn absent_tags_yield_none() {
}
```
Run `cargo test -p codebase-chat-core masquerade` — expected FAIL. Implement,
Run `cargo test -p meridian-core masquerade` — expected FAIL. Implement,
re-run, expect PASS.
Match the reference's 1–32 and 1–256 bounds
@ -211,7 +211,7 @@ a bridge to find it.
**Task 1.3: Ingest gate**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/ingest.rs`
- Modify: `crates/meridian-relay/src/handlers/ingest.rs`
Reject masquerade tags on kind 40002 from an author without the capability, and
on any other kind unconditionally. Rejecting is right: accepting-and-ignoring
@ -221,7 +221,7 @@ some other client will render.
**Task 1.4: Verify moderation is unaffected (closes M5)**
Files:
- Modify: `crates/codebase-chat-relay/src/handlers/moderation_authz.rs` (tests
- Modify: `crates/meridian-relay/src/handlers/moderation_authz.rs` (tests
only)
Add a case: a masqueraded message is moderated by real author, and a ban on the
@ -234,10 +234,10 @@ on first run. If it does not, masquerade has leaked into authorization and Phase
**Task 2.1: CLI first**
Files:
- Modify: `crates/codebase-chat-cli/src/commands/messages.rs`
- Modify: `crates/codebase-chat-cli/src/lib.rs`
- Modify: `crates/meridian-cli/src/commands/messages.rs`
- Modify: `crates/meridian-cli/src/lib.rs`
`codebase-chat messages send --channel <uuid> --content "..." --as-name "CI"
`meridian messages send --channel <uuid> --content "..." --as-name "CI"
--as-avatar <url>`. Reference behavior for the UIs, and immediately useful to
the webhook case without any UI work.
@ -313,9 +313,9 @@ Files:
## Verification
```bash
cargo test -p codebase-chat-core masquerade
cargo test -p codebase-chat-relay masquerade
cargo test -p codebase-chat-relay moderation_authz
cargo test -p meridian-core masquerade
cargo test -p meridian-relay masquerade
cargo test -p meridian-relay moderation_authz
just test
just desktop-test
just desktop-e2e-smoke
@ -327,7 +327,7 @@ Confirm moderation never resolves through the display name — this is the
security-relevant invariant:
```bash
grep -rn "masquerade" crates/codebase-chat-relay/src/handlers/moderation_authz.rs
grep -rn "masquerade" crates/meridian-relay/src/handlers/moderation_authz.rs
```
Any hit outside a test module means authorization is reading display identity.

View file

@ -6,14 +6,14 @@
>
> **Tracked in:** `.beads` — epic `codebaseChat-parity`
# Parity Index: Stoat Chat → Codebase Chat
# Parity Index: Stoat Chat → Meridian
What the reference implementation has that Codebase Chat does not, why each gap
What the reference implementation has that Meridian does not, why each gap
matters, and — equally important — **what was examined and found not to be a
gap**.
Scope was deliberately narrowed to *confirmed* gaps. A reference feature was
excluded when Codebase Chat has a working equivalent, even a differently-shaped
excluded when Meridian has a working equivalent, even a differently-shaped
one. The exclusion table below is the evidence that the eight included specs are
a considered subset rather than wherever the survey happened to stop.
@ -45,7 +45,7 @@ against the current three-role model would mean specifying them twice.
## Three orphaned kinds
The survey's most reusable finding. Codebase Chat has a recurring failure mode:
The survey's most reusable finding. Meridian has a recurring failure mode:
an event kind is declared in `kind.rs`, registered in `ALL_KINDS`, sometimes
range-matched by the relay, and then read and written by nothing.
@ -71,23 +71,23 @@ in a diff and is indistinguishable from nothing at runtime.
## Examined and excluded
Reference features that are **not** gaps. Each has a working Codebase Chat
Reference features that are **not** gaps. Each has a working Meridian
equivalent; the shape often differs.
| Reference feature | Codebase Chat equivalent | Verdict |
| Reference feature | Meridian equivalent | Verdict |
| --- | --- | --- |
| Custom emoji (`routes/customisation`) | `desktop/src/features/custom-emoji/`, kinds 10030/30030 | present |
| Voice channels (LiveKit, `voice_join.rs`) | `features/huddle/`, relay-hosted audio | present, different model |
| Server audit log (`audit_log_query.rs`) | `codebase-chat-audit` hash-chain — **stronger**; reference has a bit, this has a subsystem | present |
| Server audit log (`audit_log_query.rs`) | `meridian-audit` hash-chain — **stronger**; reference has a bit, this has a subsystem | present |
| Bans / kicks / timeouts | kinds 9040–9044, `moderation_authz.rs` | present |
| Safety reports (`safety/report_content.rs`) | `KIND_REPORT` 1984, `features/moderation/` | present |
| Invites (`routes/invites`) | kind 9009, `mobile/lib/features/invites/`, `api/invites.rs` | present |
| Group DMs (`group_create.rs`) | kinds 41010–41012, 41001 | present |
| Message pins (`message_pin.rs`) | kind 40004 | present |
| Message search (`message_search.rs`) | `codebase-chat-search`, Postgres FTS, NIP-50 | present |
| Message search (`message_search.rs`) | `meridian-search`, Postgres FTS, NIP-50 | present |
| Read state / acks (`channel_ack.rs`) | `KIND_READ_STATE` 30078 | present |
| File uploads (`services/autumn`) | `codebase-chat-media`, Blossom/S3 | present |
| Push notifications (`daemons/pushd`) | `codebase-chat-push-gateway`, `KIND_PUSH_LEASE` | present |
| File uploads (`services/autumn`) | `meridian-media`, Blossom/S3 | present |
| Push notifications (`daemons/pushd`) | `meridian-push-gateway`, `KIND_PUSH_LEASE` | present |
| Webhooks (`routes/webhooks`) | `/hooks/{id}`, `api/bridge.rs:1817` | present, workflow-scoped |
| User settings sync (`routes/sync`) | `features/settings/`, relay-persisted | present |
| Onboarding (`routes/onboard`) | `features/onboarding/` | present |
@ -95,7 +95,7 @@ equivalent; the shape often differs.
| Typing indicators | kind 20002 | present |
| Reactions (`message_react.rs`) | kind 7 | present |
| Sessions (`routes/session`) | Nostr keypair + NIP-42; no password sessions to manage | n/a by design |
| MFA / TOTP (`routes/mfa`) | key-based auth; NIP-AB device pairing (`codebase-chat-pair-relay`) | n/a by design |
| MFA / TOTP (`routes/mfa`) | key-based auth; NIP-AB device pairing (`meridian-pair-relay`) | n/a by design |
| Account email/password (`routes/account`) | no passwords exist | n/a by design |
| Friend requests (`add_friend.rs`, `find_mutual.rs`) | `KIND_CONTACT_LIST` (kind 3) follow graph | different product concept — see [blocking](./feature-user-blocking.md) Current State |
| GIF search (`services/gifbox`, Tenor proxy) | none | genuine gap, **deliberately excluded** — a third-party API integration, not architecture |
@ -116,7 +116,7 @@ silent.
## What the reference does not have
Direction matters when reading these specs. Codebase Chat leads in areas with no
Direction matters when reading these specs. Meridian leads in areas with no
reference counterpart at all, and several parity items must be built *without
regressing* them:
@ -126,7 +126,7 @@ regressing* them:
Identity gaps here (nicknames, masquerade) are *harder* to solve because the
reference's answer is "mutable server row".
- **Audit hash-chain** — `ViewAuditLogs` is one permission bit there.
- **Workflows** — `codebase-chat-workflow`, YAML-as-code. No counterpart.
- **Workflows** — `meridian-workflow`, YAML-as-code. No counterpart.
- **Projects / NIP-34 git** — no counterpart.
- **Mesh compute, huddle audio, canvas, pulse** — no counterparts.
@ -140,7 +140,7 @@ Two specs turn this into a design constraint rather than a footnote:
`Keybinds` store is a **hollow shell** — `TypeKeybinds = { _phantom?: never }`,
`default()` returns `{}`, defaults commented out. Its `DEFAULT_SEQUENCES` also
binds `NAVIGATION_CHANNEL_UP` and `_DOWN` to the same chord, making channel-up
unreachable on non-Mac. Codebase Chat's 27-entry table is more complete and
unreachable on non-Mac. Meridian's 27-entry table is more complete and
more correct.
**The reference is a source of evidence, not a specification.** Where it is

View file

@ -95,7 +95,7 @@ would shadow it.
3. A payer attests settlement (kind 50402, carrying the txid); any observer
can verify the claim against the chain with their own RPC, and clients
render **claimed** and **verified** as visibly distinct states.
4. Agents can do all of the above through `codebase-chat payments …` with JSON
4. Agents can do all of the above through `meridian payments …` with JSON
output and the documented exit codes.
5. An agent can buy an x402-protected HTTP resource (`exact` scheme, EVM)
under a hard local spend cap.
@ -119,7 +119,7 @@ would shadow it.
web; Phase 0.3 scopes that non-goal's wording so it stops shadowing this
feature.
- **No workflow `send_payment` action yet.** Only `CallWebhook` is retry-safe
(`crates/codebase-chat-workflow/src/schema.rs:222-235`); a payment action is
(`crates/meridian-workflow/src/schema.rs:222-235`); a payment action is
the least idempotent action conceivable and waits for the idempotency-key
design that comment names as missing. When it lands it joins the
elevated-authority set (`schema.rs:238-257`).
@ -135,13 +135,13 @@ implementation. All "zap" hits are the lucide icon; all "wallet" hits are OS
keyrings (`SECURITY.md:80`).
**The relay already advertises a payment slot, hardcoded off.**
`crates/codebase-chat-relay/src/nip11.rs:127-128` declares
`crates/meridian-relay/src/nip11.rs:127-128` declares
`payment_required: bool`; `:159` pins it `false`. `RelayInfo` has
`supported_extensions: Option<Vec<String>>` (`nip11.rs:73-92`) — the natural
place to advertise `"x402"` without touching `supported_nips`.
**The repo's only 402 handling is buyer-side already:**
`crates/codebase-chat-agent/src/llm.rs:6017` maps `402 => "402 Payment
`crates/meridian-agent/src/llm.rs:6017` maps `402 => "402 Payment
Required"` from upstream LLM providers.
**The record says payments are rejected.** `docs/nostr-nip-support-matrix.md`
@ -154,10 +154,10 @@ enough to cover this feature).
| Seam | Where |
| --- | --- |
| Kind registry + closed ingest allowlist | `crates/codebase-chat-core/src/kind.rs` (`ALL_KINDS` :580); `required_scope_for_kind` at `crates/codebase-chat-relay/src/handlers/ingest.rs:211` — an unlisted kind is rejected `restricted: unknown event kind` (the NIP-17/10050 lesson) |
| Kind registry + closed ingest allowlist | `crates/meridian-core/src/kind.rs` (`ALL_KINDS` :580); `required_scope_for_kind` at `crates/meridian-relay/src/handlers/ingest.rs:211` — an unlisted kind is rejected `restricted: unknown event kind` (the NIP-17/10050 lesson) |
| Envelope validators | `ingest.rs:985` (engram), `:1054` (persona), `:1298` (reminder) — the pattern for `validate_payment_envelope` |
| Kind-reader gate | `just check-kinds` (`scripts/check-kind-readers.mjs`); the CLI is inside the `crates` scan root, so a CLI reader satisfies it |
| SDK builders | `crates/codebase-chat-sdk/src/builders.rs` |
| SDK builders | `crates/meridian-sdk/src/builders.rs` |
| CLI group pattern | `commands/mod.rs` + `Cmd` enum (`lib.rs:178-243`) + dispatch (`lib.rs:1842-1862`) + `commands/workflows.rs` as the model; stderr-caveat idiom at `commands/workflows.rs:77-79` |
| x402 seller seams (deferred) | global layer next to `track_metrics` (`router.rs:201`), per-sub-router layer (`media_router`, `router.rs:46`), or `api_error(StatusCode, msg)` per-route (`api/mod.rs:26`) |
| Desktop kind mirror | `desktop/src/shared/constants/kinds.ts` (bare numbers; timeline set `CHANNEL_TIMELINE_CONTENT_KINDS` :135) |
@ -227,7 +227,7 @@ cross-reference this spec.
### Phase 1 — Vocabulary + CLI (closes P1)
**Task 1.1: Failing tests for the payment grammar.**
Create `crates/codebase-chat-core/src/payment.rs`, register in `lib.rs`:
Create `crates/meridian-core/src/payment.rs`, register in `lib.rs`:
```rust
#[test]
@ -253,9 +253,9 @@ fn attestation_dedupe_key_is_network_and_tx() {
}
```
`cargo test -p codebase-chat-core payment` — FAIL, implement, PASS.
`cargo test -p meridian-core payment` — FAIL, implement, PASS.
**Task 1.2: Kinds.** `crates/codebase-chat-core/src/kind.rs`: constants with
**Task 1.2: Kinds.** `crates/meridian-core/src/kind.rs`: constants with
`/// NIP-CP:` doc comments, a `// Payments (50400–50499)` group header,
`ALL_KINDS` entries, and range assertions:
@ -271,19 +271,19 @@ KIND_PAYMENT_ATTESTATION => Scope::MessagesWrite` (the workflow :321 pattern)
plus `validate_payment_envelope` (required tags, CAIP-2 shape, amount and
txid format, sane expiration; **no chain calls**). Failing ingest test first.
**Task 1.4: SDK builders.** `crates/codebase-chat-sdk/src/builders.rs`:
**Task 1.4: SDK builders.** `crates/meridian-sdk/src/builders.rs`:
`build_payment_endpoints`, `build_payment_request`, `build_payment_attestation`.
**Task 1.5: CLI group** (the reader that satisfies `check-kinds`). New
`crates/codebase-chat-cli/src/commands/payments.rs`, `pub mod payments;`,
`crates/meridian-cli/src/commands/payments.rs`, `pub mod payments;`,
`Payments(PaymentsCmd)` in `Cmd`, dispatch line at `lib.rs:1842-1862`:
```
codebase-chat payments endpoints-set --addr eip155:8453=0x… [--addr kaspa:mainnet=kaspa:…]
codebase-chat payments endpoints --user <pubkey>
codebase-chat payments request --channel <uuid> --network <caip2> --asset <caip19|native> --amount <atomic> [--payer <pubkey>] [--expires <unix>]
codebase-chat payments attest --channel <uuid> --to <pubkey> --tx <txid> --network … --asset … --amount … [--request <event-id>]
codebase-chat payments list --channel <uuid> # explicit kinds filter — p-gate
meridian payments endpoints-set --addr eip155:8453=0x… [--addr kaspa:mainnet=kaspa:…]
meridian payments endpoints --user <pubkey>
meridian payments request --channel <uuid> --network <caip2> --asset <caip19|native> --amount <atomic> [--payer <pubkey>] [--expires <unix>]
meridian payments attest --channel <uuid> --to <pubkey> --tx <txid> --network … --asset … --amount … [--request <event-id>]
meridian payments list --channel <uuid> # explicit kinds filter — p-gate
```
Conventions: JSON stdout, warnings to stderr — `list` emits the
@ -292,7 +292,7 @@ before treating one as settled."* Exit 5 on 10402 LWW conflict, documented in
`lib.rs:76`.
**Task 1.6: Integration test.**
`crates/codebase-chat-test-client/tests/e2e_payments.rs` (`just test`):
`crates/meridian-test-client/tests/e2e_payments.rs` (`just test`):
endpoints round-trip; request accepted in-channel and rejected cross-channel
(h-scope); attestation with malformed txid rejected by the envelope validator;
attestation readable by channel members.
@ -304,7 +304,7 @@ against a mocked RPC first. Ethereum: `eth_getTransactionReceipt` — status 1,
`to`/`value` for native, or an ERC-20 `Transfer` log matching
`(contract, to, amount)`. Kaspa: REST (`api.kaspa.org`-compatible, URL
configurable) — outputs contain `(address, amount)`. Config:
`CODEBASE_CHAT_ETH_RPC_URL`, `CODEBASE_CHAT_KASPA_API_URL`, confirmation
`MERIDIAN_ETH_RPC_URL`, `MERIDIAN_KASPA_API_URL`, confirmation
threshold flag.
**Task 2.2: Fail stale, never wrong.** RPC unreachable → exit 2 with
@ -334,13 +334,13 @@ address in the signed request they can see.
### Phase 4 — x402 buyer (closes P5)
**Task 4.1:** `codebase-chat x402 get --url <url> --max-amount <atomic>
**Task 4.1:** `meridian x402 get --url <url> --max-amount <atomic>
[--network <caip2>]`. Failing test against a local mock 402 server first.
Flow: request → parse `accepts` (v1 + v2 shapes) → select `exact`/EVM
requirement within the cap → sign EIP-3009 `transferWithAuthorization`
(EIP-712, `alloy` crates) → retry with `X-PAYMENT` → surface
`X-PAYMENT-RESPONSE` (settlement tx) in JSON output. Key:
`CODEBASE_CHAT_X402_EVM_KEY` (client env only). **The cap check precedes
`MERIDIAN_X402_EVM_KEY` (client env only). **The cap check precedes
signing** — a signed authorization is spent authority even if the request
aborts. Optionally publish a 50402 attestation when the seller is a community
member (`--attest --channel …`).
@ -373,7 +373,7 @@ Against [`feature-preview-graduation.md`](./feature-preview-graduation.md):
## Verification
```bash
cargo test -p codebase-chat-core payment
cargo test -p meridian-core payment
just check-kinds # kinds have readers, no allowlist growth
just test # e2e_payments.rs; needs Postgres + Dragonfly
just desktop-test
@ -384,7 +384,7 @@ Confirm the kinds are not orphaned and custody never crept in:
```bash
grep -rn "KIND_PAYMENT" crates desktop/src | grep -v kind.rs | head
git grep -inE "private.?key|secret.?key" crates/codebase-chat-relay/src | grep -i "eth\|evm\|kaspa\|x402" # → no output
git grep -inE "private.?key|secret.?key" crates/meridian-relay/src | grep -i "eth\|evm\|kaspa\|x402" # → no output
```
Confirm the record was amended before code (Phase 0):

View file

@ -4,7 +4,7 @@
# Feature: Portable Deployment Profiles
Codebase Chat needs a go-to-market path that does not guess the final hosting
Meridian needs a go-to-market path that does not guess the final hosting
provider. The deployable product is therefore the published relay image plus its
PostgreSQL, Redis-protocol, S3-compatible, health, metrics, migration, identity,
and recovery contracts. Docker Compose, Dokploy, Helm/Kubernetes, and selected
@ -46,7 +46,7 @@ can see them.
## Goals
1. Let an evaluator discover and deploy Codebase Chat from Dokploy with one
1. Let an evaluator discover and deploy Meridian from Dokploy with one
domain, generated stable secrets, a pinned relay image, and an honest
evaluation warning.
2. Let an operator use the same image and environment contract under bare
@ -70,8 +70,8 @@ can see them.
- Treating Docker Swarm replicas as HA while PostgreSQL, Redis, or MinIO remain
single-node named volumes.
- Treating Dokploy's own control-plane backup, a database dump, or a volume copy
as a Codebase Chat consistency-set restore proof.
- Executing `curl -sSL https://dokploy.com/install.sh | bash` from Codebase Chat
as a Meridian consistency-set restore proof.
- Executing `curl -sSL https://dokploy.com/install.sh | bash` from Meridian
automation. Installation is an explicit operator action; record and verify
the installed Dokploy version before qualification.
- Adding the hosted-community control plane or push gateway to the first relay
@ -88,15 +88,15 @@ can see them.
| TLS bundle | `deploy/compose/compose.caddy.yml` and `deploy/compose/Caddyfile` put Caddy in front of the relay | Dokploy must use Traefik instead of starting a second public TLS owner |
| External-state limitation | `deploy/compose/README.md:39-45` says the bundle fixes object storage to MinIO and requires a custom Compose file for external S3 | A reusable external-state Compose/Stack profile is missing |
| Recovery | `deploy/compose/README.md:47-60` and `scripts/verify-backup-restore.sh` define an application-aware recovery set and isolated restore proof | Platform backup buttons are supplementary, never acceptance evidence |
| HA contract | `deploy/charts/codebase-chat/README.md:164-188` requires Redis, subscriber-aware readiness, and per-replica Git scratch; `values.yaml` defines autoscaling | Swarm must preserve these invariants rather than merely setting `replicas` |
| Object-store admission | `docs/git-on-object-storage.md` defines the A1-A3 contract; `crates/codebase-chat-relay/src/main.rs` makes its concurrent CAS probe startup-fatal | R2 is plausible but unqualified until the exact deployment passes the probe |
| HA contract | `deploy/charts/meridian/README.md:164-188` requires Redis, subscriber-aware readiness, and per-replica Git scratch; `values.yaml` defines autoscaling | Swarm must preserve these invariants rather than merely setting `replicas` |
| Object-store admission | `docs/git-on-object-storage.md` defines the A1-A3 contract; `crates/meridian-relay/src/main.rs` makes its concurrent CAS probe startup-fatal | R2 is plausible but unqualified until the exact deployment passes the probe |
| Dokploy adapter | No `deploy/dokploy/`, Dokploy template, Dokploy contract test, or Dokploy guide exists; the existing relay host mapping uses `3000`, which conflicts with the default Dokploy panel port | The product is deployable manually, but the current bundle cannot be imported unchanged or supportably distributed through Dokploy |
| Cloudflare adapter | No committed R2 example, Tunnel/LB origin policy, Worker, or Container adapter exists | Cloudflare remains an operator experiment rather than a named profile |
## External Capability Evidence
Re-check these before implementation; they describe third-party behavior as of
2026-08-02, not a contract Codebase Chat controls.
2026-08-02, not a contract Meridian controls.
| Capability | Current evidence | Design implication |
| --- | --- | --- |
@ -130,7 +130,7 @@ Binding requirements:
remains authoritative.
- WebSocket upgrades and long-lived connections pass end-to-end.
- Direct origin access is denied before
`CODEBASE_CHAT_MEDIA_UPLOAD_IP_HEADER=cf-connecting-ip` is enabled. The edge
`MERIDIAN_MEDIA_UPLOAD_IP_HEADER=cf-connecting-ip` is enabled. The edge
must strip inbound copies and set its own value.
- HA relay replicas share one PostgreSQL authority, one supported Redis-protocol
authority/fan-out plane, one S3-compatible object namespace, the same relay
@ -161,7 +161,7 @@ dependencies. This spec does not imply that any implementation slice is done.
| --- | --- | --- | --- |
| PDP1 | No canonical deployment-profile matrix or parity test | Compose and Helm document similar contracts independently | P1 |
| PDP2 | Existing public port/TLS ownership conflicts with Dokploy | `deploy/compose/compose.yml:23-24` binds host `3000`; Caddy owns `80/443`; Dokploy uses its panel and Traefik on those ports | P1 |
| PDP3 | No upstream one-click Dokploy template | Codebase Chat absent from `Dokploy/templates` | P2 |
| PDP3 | No upstream one-click Dokploy template | Meridian absent from `Dokploy/templates` | P2 |
| PDP4 | External S3 is not configurable in the production Compose bundle | `deploy/compose/README.md:41-45` | P1 |
| PDP5 | R2 conditional-write and recovery behavior is not live-qualified | No R2 evidence artifact exists; startup probe has not been run there | P1 |
| PDP6 | Swarm replica/migration/state/rollback contracts are unspecified | Helm has HA guards; no Stack file or tests exist | P1 before any HA claim |
@ -271,7 +271,7 @@ docker compose -f deploy/dokploy/compose.yml config
bash scripts/test-dokploy-contract.sh
bash scripts/test-deployment-profile-parity.sh
bash scripts/test-verify-backup-restore.sh
helm unittest deploy/charts/codebase-chat
helm unittest deploy/charts/meridian
just ci
```

View file

@ -301,7 +301,7 @@ This is the checklist every per-feature spec closes against.
1. **Protocol complete** — every event kind the relay accepts for this feature
has a client implementation, or the unused kind is deleted from
`codebase-chat-core/src/kind.rs`. A kind the relay validates and no client
`meridian-core/src/kind.rs`. A kind the relay validates and no client
ever sends is not a feature, it is a liability.
2. **Protocol written down** — every kind the feature ships has a normative
document **in `docs/nips/`**, and that document is listed in the `nips/`
@ -314,7 +314,7 @@ This is the checklist every per-feature spec closes against.
- **A spec off the documented path is indistinguishable from a missing one.**
NIP-AB shipped complete — 824 normative lines and a Tamarin model — from
`crates/codebase-chat-core/src/pairing/`, where the index never pointed and every
`crates/meridian-core/src/pairing/`, where the index never pointed and every
kind-vs-spec audit reported `kind:24134` as unspecified. Now at
`docs/nips/NIP-AB.md`.
- **A kind can hide inside a family that has a NIP.** `30176` (teams) sits
@ -326,7 +326,7 @@ This is the checklist every per-feature spec closes against.
`.settings/reference-code/nips/AGENTS.md` § Verification.
3. **Advertisement is honest** — if the feature depends on a NIP being reachable
by third parties, that NIP is in `SUPPORTED_NIPS`
(`crates/codebase-chat-relay/src/nip11.rs`) **and** its carrier kinds are admitted
(`crates/meridian-relay/src/nip11.rs`) **and** its carrier kinds are admitted
by `required_scope_for_kind`. The ingest allowlist is closed, so a NIP that is
advertised but unadmitted turns a client feature-check into a silent runtime
failure — strictly worse than not advertising it. NIP-17 sat in this state
@ -338,7 +338,7 @@ This is the checklist every per-feature spec closes against.
renders. A surface shipping on a platform the manifest omits is a bug.
5. **e2e coverage** — at least one spec in `desktop/tests/e2e/` exercises the
primary flow, registered in `playwright.config.ts`.
6. **Agent surface** — the capability is reachable from `codebase-chat-cli`, or
6. **Agent surface** — the capability is reachable from `meridian-cli`, or
the spec states why an agent has no business touching it.
7. **Failure paths** — empty state, permission denial, and relay-offline each
render something deliberate rather than a blank panel.

View file

@ -37,7 +37,7 @@
The `codebaseChat-ssx` real-repository QA lane is closed. Its five findings now
have executable evidence and durable contracts:
- `NIP-GC` normatively defines the `codebase-chat-channel` ACL and the stable
- `NIP-GC` normatively defines the `meridian-channel` ACL and the stable
owner/repository coordinate.
- host-scoped credential configuration resets inherited macOS keychain helpers,
while the development environment rejects Git versions without `authtype`.
@ -100,7 +100,7 @@ why its replacement test sweeps 52 of them. Screenshot the surface before
declaring a register complete.
Projects was otherwise left untouched. Its remaining gaps are structural, and a
refactor competing with the in-flight `buzz` → `codebase-chat` rename already in
refactor competing with the in-flight `buzz` → `meridian` rename already in
the working tree would produce conflicts for no user-visible gain.
Git repository browser and collaboration built on NIP-34. Protocol coverage is

View file

@ -102,7 +102,7 @@ Tabs (`pulse_page.dart:17`): `everyone`, `following`, `liked`, `agents`, `mine`.
Tests: `mobile/test/features/pulse/note_card_test.dart`,
`compose_note_page_test.dart`.
**CLI** — `crates/codebase-chat-cli/src/commands/notes.rs` (46K),
**CLI** — `crates/meridian-cli/src/commands/notes.rs` (46K),
`social.rs` (8.4K), `feed.rs` (2.6K), `reactions.rs`.
## Gap Register

View file

@ -9,7 +9,7 @@
# Feature: Rebindable Keyboard Shortcuts
Codebase Chat ships 26 keyboard shortcuts across four categories and a settings
Meridian ships 26 keyboard shortcuts across four categories and a settings
card that tells you what they are — and, since 2026-08-01, lets you search them.
The card still says the rest in its own description: **"Shortcuts are
read-only."**
@ -20,7 +20,7 @@ this; a second reference — a local agentic coding tool surveyed 2026-08-01 —
solves it thoroughly, and is now the exemplar for the *surface*. Keep them
straight:
| | Revolt fork (`for-web`) | Agentic coding tool | Codebase Chat |
| | Revolt fork (`for-web`) | Agentic coding tool | Meridian |
| --- | --- | --- | --- |
| Action indirection | ✅ `KeybindAction` enum | ✅ (implied by rebinding) | ❌ |
| Conflict priority | ✅ `ACTION_PRIORITY` | not observable | ❌ |
@ -34,7 +34,7 @@ straight:
So the target is: take **structure** from the Revolt fork (action indirection,
priority), take **surface** from the coding tool (search, per-binding edit,
multi-binding, `Unassigned`), and add the persistence neither the Revolt fork
nor Codebase Chat has.
nor Meridian has.
The Revolt fork's `Keybinds` store is a hollow shell:
@ -72,7 +72,7 @@ standalone value, and it removes duplication that exists today.
contains a copy-paste bug — `NAVIGATION_CHANNEL_UP` and
`NAVIGATION_CHANNEL_DOWN` are both bound to `["Alt", "ArrowDown"]`
(`keybindSequences.ts:7-8`), so channel-up is unreachable on non-Mac. The Mac
table below it gets it right. Codebase Chat's existing 27-entry table is more
table below it gets it right. Meridian's existing 27-entry table is more
complete and more correct; keep it and add the action layer around it.
**Rejected: syncing keybinds via a Nostr kind.** Tempting for cross-device
@ -108,7 +108,7 @@ proposed here.
## Current State
**Codebase Chat — 26 shortcuts, fully documented, searchable, entirely fixed.**
**Meridian — 26 shortcuts, fully documented, searchable, entirely fixed.**
`desktop/src/shared/lib/keyboard-shortcuts.ts` exports a `KeyboardShortcut[]`
with `id`, `label`, `description`, `keys`, `keysWindows`, `category`. Categories
@ -137,7 +137,7 @@ desktop/src` returns 15 files including `app/AppShell.tsx`,
— i.e. the table describes bindings that each handler implements independently.
Nothing enforces that the documented key and the implemented key agree.
| Property | Codebase Chat | Reference |
| Property | Meridian | Reference |
| --- | --- | --- |
| Shortcuts documented | 26, 4 categories | 12 actions |
| Platform variants | `getPlatformKeys()` | two tables, one buggy |
@ -265,7 +265,7 @@ Files:
- Create: `desktop/src/features/settings/lib/keybindOverrides.test.mjs`
`localStorage`, keyed like `channelMutesStorage.ts` does
(`codebase-chat-channel-mutes.v1` → `codebase-chat-keybinds.v1`). Not
(`meridian-channel-mutes.v1` → `meridian-keybinds.v1`). Not
community-scoped, so it must **not** go in `resetCommunityState()` — bindings
survive a community switch. Note that explicitly in the file header; the
default assumption in this codebase is the opposite.

Some files were not shown because too many files have changed in this diff Show more