Some checks failed
CI / Detect Changed Paths (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
Meridian Harness / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Meridian Harness / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Isolated DB Gate (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Admin Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Server Cross-Compile (push) Has been cancelled
CI / Server Cross-Compile-1 (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Meridian Harness / Publish rolling release (push) Has been cancelled
Meridian Harness / Publish tagged release (push) Has been cancelled
a9b30ea27 added two Postgres-gated tenancy tests and left the number at 51
in both the Justfile and the CI comment beside the same selection. Derived
from the binary rather than from grep: 53.
The Justfile comment already told the next reader to re-derive rather than
trust it, and listed three authors who had not. It now lists four,
including this campaign. Leaving the tally visible is the point -- a
hand-maintained number that keeps being wrong is evidence the comment
wants a gate, not a bigger warning.
Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
2094 lines
108 KiB
Makefile
2094 lines
108 KiB
Makefile
# Meridian — development task runner
|
||
|
||
set dotenv-load := true
|
||
|
||
# dotenv-load above reads .env only. Compose reads .env.local ONLY via
|
||
# COMPOSE_ENV_FILES, and ONLY when it comes from the shell — setting it inside
|
||
# .env is silently ignored. Exporting it here means every recipe that shells out
|
||
# to `docker compose` honours .env.local automatically, with no per-recipe flag.
|
||
# Recipes that run the relay/control-plane source .env.local explicitly, since
|
||
# those read the environment rather than going through Compose.
|
||
export COMPOSE_ENV_FILES := if path_exists(".env.local") == "true" { ".env,.env.local" } else { ".env" }
|
||
|
||
desktop_dir := "REMAPPING/meridian-desktop"
|
||
desktop_tauri_manifest := "REMAPPING/meridian-desktop/src-tauri/Cargo.toml"
|
||
web_dir := "REMAPPING/meridian-web"
|
||
|
||
# Opt-in mesh-llm. Off by default so `just dev`/`just staging`/`just production`
|
||
# skip ~420 extra crates + the llama.cpp native runtime build and stay fast to
|
||
# iterate on. Turn on to test mesh compute features: `just mesh=1 dev` /
|
||
# `just mesh=1 staging` / `just mesh=1 production`.
|
||
mesh := ""
|
||
|
||
# Reset only the current standalone desktop instance before launch.
|
||
# Usage: `just fresh=1 desktop-standalone`.
|
||
fresh := ""
|
||
|
||
# List all available tasks
|
||
default:
|
||
@just --list
|
||
|
||
# ─── Dev Environment ─────────────────────────────────────────────────────────
|
||
|
||
# Install required dev tools via Hermit and create .env (safe to re-run)
|
||
bootstrap:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# Hermit's bin/ symlinks auto-download pinned tool versions on first use.
|
||
# Running each tool once triggers the download if not already cached.
|
||
echo "Ensuring toolchain via Hermit..."
|
||
cargo --version &
|
||
node --version &
|
||
pnpm --version &
|
||
git --version &
|
||
wait
|
||
if ! command -v docker &>/dev/null; then
|
||
echo "Error: Docker is required but not installed."
|
||
echo "Install it from https://docs.docker.com/get-docker/"
|
||
exit 1
|
||
fi
|
||
if [[ ! -f .env ]]; then
|
||
cp .env.example .env
|
||
echo "Created .env from .env.example — review it before running just dev."
|
||
fi
|
||
|
||
# Start Docker services, run migrations, install desktop deps
|
||
setup: bootstrap
|
||
./scripts/dev-setup.sh
|
||
|
||
# Install git hooks via lefthook (dispatches from the shared .git/hooks dir so all
|
||
# linked worktrees inherit the same hooks without a worktree-relative .hooks path)
|
||
hooks:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
# Use the Hermit-pinned lefthook (bin/lefthook self-downloads on first use):
|
||
# works with no pre-installed lefthook and guarantees the pinned version
|
||
# rather than whatever happens to be on PATH.
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# --path-format=absolute guarantees an absolute path from every invocation context:
|
||
# without it, --git-common-dir returns ".git" from the main checkout and a
|
||
# relative hooksPath would break linked-worktree dispatch just like .hooks did.
|
||
HOOKS_DIR="$(git rev-parse --path-format=absolute --git-common-dir)/hooks"
|
||
git config --local core.hooksPath "$HOOKS_DIR"
|
||
lefthook install --force
|
||
# Beads chains its section onto the lefthook shims, so it must run AFTER
|
||
# `lefthook install --force` — which clobbers what beads writes if the order
|
||
# is reversed. Skipped silently when bd is absent; check-git-hooks reports a
|
||
# missing beads section anyway.
|
||
if command -v bd >/dev/null 2>&1; then bd hooks install >/dev/null 2>&1 || true; fi
|
||
# LAST, because both installers above rewrite the files and drop it: make
|
||
# lefthook's exit status reach git. Without this a shell script exits with
|
||
# its last command's status — beads' — and every local gate is advisory
|
||
# (meridian-qqct). Pinned by check-git-hooks.
|
||
node scripts/patch-hook-exit-propagation.mjs
|
||
|
||
# Wipe development state and recreate a clean environment. Installed Meridian is preserved.
|
||
[confirm("This will DELETE all development data and preserve installed Meridian. Continue? (y/N)")]
|
||
reset:
|
||
./scripts/dev-reset.sh --yes
|
||
|
||
# Start dev services. Honours COMPOSE_PROFILES and .env.local (via the exported
|
||
# COMPOSE_ENV_FILES above), unlike a bare `docker compose up -d`.
|
||
up *ARGS:
|
||
docker compose up -d {{ARGS}}
|
||
|
||
# Set MERIDIAN_DEV_FILE_SECRETS=1 (in .env.local) and run.sh builds the desktop
|
||
# with --no-default-features, so it never opens the keychain. This migrates the
|
||
# secrets into the 0600 file stores that build reads. --check reports only.
|
||
# The keychain items are left untouched, so it is reversible.
|
||
|
||
# macOS: move dev secrets out of the login keychain so rebuilds stop prompting
|
||
dev-secrets-to-files *ARGS:
|
||
./scripts/dev-secrets-to-files.sh {{ARGS}}
|
||
|
||
# Unattended equivalent of the Keychain Access → Certificate Assistant steps in
|
||
# REMAPPING/meridian-desktop/src-tauri/AGENTS.md. Needed for CI and any machine nobody can click
|
||
# through. Idempotent; set MERIDIAN_DEV_SIGN_IDENTITY afterwards to use it.
|
||
|
||
# macOS: create the self-signed identity that keeps dev builds' code identity stable
|
||
dev-signing-identity *ARGS:
|
||
./scripts/dev-signing-identity.sh {{ARGS}}
|
||
|
||
# Show which env files and profiles are in effect, and the resolved host ports.
|
||
# Run this first when a service is reachable on an unexpected port.
|
||
#
|
||
# Layer provenance: this recipe should report which layer supplied each
|
||
# resolved value (`.env`, `.env.local`, shell, compose override) — not only
|
||
# the winner — so a wrong port is diagnosable without re-reading every file.
|
||
# Caller overrides for test recipes use dedicated MERIDIAN_TEST_* names
|
||
# (MERIDIAN_TEST_DATABASE_URL, MERIDIAN_TEST_REDIS_URL, MERIDIAN_TEST_RELAY_URL)
|
||
# because those names appear in no env file; see CONTRIBUTING.md § Running Tests.
|
||
env-doctor:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
# COMPOSE_PROFILES usually arrives from .env.local, which Compose reads via
|
||
# COMPOSE_ENV_FILES but this recipe's shell does not — so source it before
|
||
# reporting, or the value shown contradicts the service list below.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
echo "COMPOSE_ENV_FILES = ${COMPOSE_ENV_FILES:-<unset>}"
|
||
# This recipe exports COMPOSE_ENV_FILES=.env, but .env is gitignored — so
|
||
# on a fresh clone Compose fails closed with "couldn't find env file" and
|
||
# env-doctor, the tool you reach for BECAUSE the layers are confusing,
|
||
# dies before printing a single resolved port. Report what was asked for
|
||
# on the line above, then drop the names that do not exist.
|
||
_cef_keep=""
|
||
if [[ -n "${COMPOSE_ENV_FILES:-}" ]]; then
|
||
IFS=',' read -ra _cef_parts <<<"$COMPOSE_ENV_FILES"
|
||
for _cef in "${_cef_parts[@]}"; do
|
||
[[ -f "$_cef" ]] && _cef_keep="${_cef_keep:+${_cef_keep},}${_cef}"
|
||
done
|
||
if [[ "$_cef_keep" != "$COMPOSE_ENV_FILES" ]]; then
|
||
echo " (using ${_cef_keep:-<none>} — the rest do not exist)"
|
||
fi
|
||
if [[ -n "$_cef_keep" ]]; then export COMPOSE_ENV_FILES="$_cef_keep"; else unset COMPOSE_ENV_FILES; fi
|
||
fi
|
||
echo "COMPOSE_PROFILES = ${COMPOSE_PROFILES:-<unset> (core services only)}"
|
||
for f in .env .env.local docker-compose.override.yml; do
|
||
[[ -f "$f" ]] && echo "present: $f" || echo "absent : $f"
|
||
done
|
||
# The bus is reported separately from the port table because its failure is
|
||
# a MISMATCH, not a missing value: a relay on a Zenoh mode with no endpoint
|
||
# list, or an endpoint list pointing at a port Compose is not publishing,
|
||
# both look completely healthy from `docker compose ps`. Neither multicast
|
||
# nor gossip scouting is enabled, so nothing discovers its way out of it.
|
||
echo "--- event bus ---"
|
||
bus_mode="${MERIDIAN_BUS:-redis (default)}"
|
||
bus_peers="${MERIDIAN_ZENOH_ENDPOINTS:-<none>}"
|
||
echo " MERIDIAN_BUS ${bus_mode}"
|
||
echo " MERIDIAN_ZENOH_ENDPOINTS ${bus_peers}"
|
||
if [[ "${MERIDIAN_BUS:-redis}" != "redis" && "${MERIDIAN_ZENOH_ENDPOINTS:-}" == "" ]]; then
|
||
echo " !! MERIDIAN_BUS=${MERIDIAN_BUS} with no endpoints: discovery is off on both"
|
||
echo " mechanisms, so the relay has no way to reach a router."
|
||
fi
|
||
if [[ ",${COMPOSE_PROFILES:-}," == *,bus,* ]]; then
|
||
echo " profile 'bus' is ON → zenohd on ${ZENOH_HOST_PORT:-7447}, image eclipse/zenoh:${ZENOH_IMAGE_TAG:-1.8.0}"
|
||
[[ "${MERIDIAN_BUS:-redis}" == "redis" ]] && \
|
||
echo " note: MERIDIAN_BUS=redis — the router will start with nothing dialing it."
|
||
else
|
||
echo " profile 'bus' is OFF → no zenohd router in this stack"
|
||
fi
|
||
echo "--- resolved services and host ports ---"
|
||
docker compose config --format json > /tmp/meridian-env-doctor.json
|
||
python3 - /tmp/meridian-env-doctor.json <<'PY'
|
||
import json, sys
|
||
services = json.load(open(sys.argv[1])).get("services", {})
|
||
for name, spec in sorted(services.items()):
|
||
published = [str(p.get("published")) for p in spec.get("ports", [])]
|
||
print(f" {name:24} {', '.join(published) or '-'}")
|
||
PY
|
||
rm -f /tmp/meridian-env-doctor.json
|
||
|
||
# Stop all dev services (keep data)
|
||
down:
|
||
docker compose down
|
||
|
||
# Show dev service status
|
||
ps:
|
||
docker compose ps
|
||
|
||
# Tail all service logs
|
||
logs *ARGS:
|
||
docker compose logs -f {{ARGS}}
|
||
|
||
# ─── Build & Check ───────────────────────────────────────────────────────────
|
||
|
||
# Build the Rust workspace
|
||
build:
|
||
cargo build --workspace
|
||
|
||
# Build the Rust workspace in release mode
|
||
build-release:
|
||
cargo build --workspace --release
|
||
|
||
# Run repo lint and formatting checks.
|
||
#
|
||
# `desktop-check` / `web-check` are biome plus the guard scripts — neither runs
|
||
# `tsc`. Without the typecheck recipes here, this whole gate passes on a tree
|
||
# that does not compile, and the first thing to notice is CI's `desktop-build`.
|
||
check: fmt-check clippy desktop-check desktop-typecheck desktop-tauri-fmt-check desktop-tauri-clippy web-check web-typecheck mobile-check compose-check scoped-commit-check launcher-check check-docker-context check-git-hooks backup-restore-check git-pointer-repair-contract nip34-search-rollout-check reply-persistence-benchmark-contract capacity-contract-check check-kinds perf-check zenoh-check check-skills check-stellar check-gauntlet check-architecture-map check-alert-runbooks check-chart-version-bump check-brand check-feature-specs check-mips check-unwrap-budget check-relay-e2e-inventory check-frozen-migrations check-legacy-namespaces check-relay-terminology check-npm-supply-chain check-external-copy check-reductstore-zenoh
|
||
|
||
# Guard run.sh's env layering and its process teardown.
|
||
#
|
||
# Both suites existed as evidence for a specific defect and neither ran in any
|
||
# gate — `test-run-env-layers.sh` was cited in the root contract as the thing
|
||
# that pins `.env` precedence while being reachable only by typing its path,
|
||
# which is the meridian-h73s shape (a test that runs nowhere proves nothing).
|
||
#
|
||
# Teardown is guarded because its two failure modes both report success:
|
||
# an EXIT trap scoped by binary path reaps a second agent's relay from the same
|
||
# checkout (meridian-mth0), and a stop that kills the tracked wrapper without
|
||
# checking the socket leaves an orphan answering health probes for the next
|
||
# build (meridian-we5n). No Docker, no database, no network; binds only
|
||
# ephemeral ports it asks the kernel for.
|
||
launcher-check:
|
||
bash scripts/test-run-env-layers.sh
|
||
bash scripts/test-run-process-teardown.sh
|
||
bash scripts/test-git-shim-recursion.sh
|
||
|
||
# Verify the local backing stack contract (Dragonfly swap, database isolation,
|
||
# and every caller that names those services). No Docker required — the
|
||
# resolved-config assertions self-skip when Docker is unavailable.
|
||
compose-check:
|
||
bash scripts/test-compose-stack-contract.sh
|
||
|
||
# Guard the shared-index commit contract. This branch is shared by construction
|
||
# — the root contract forbids branching so parallel agents interleave on `main`
|
||
# — so they share one working tree and one git index, and a bare `git commit -m`
|
||
# takes the whole index including another agent's staged work (finding F077).
|
||
# Runs in throwaway repos; no Docker, no database, no network.
|
||
scoped-commit-check:
|
||
bash scripts/test-git-commit-scoped.sh
|
||
|
||
# Guard the Docker build context against pruning a file the image then compiles.
|
||
# Every other check sees the full worktree; only `docker build` sees the pruned
|
||
# context, so a `.dockerignore` rule that excludes a transitively-imported file
|
||
# fails nowhere until the image build (meridian-xdl5: a published relay image was
|
||
# unbuildable on TS2307). Resolves each package's aliased imports and asserts that
|
||
# every one escaping its own package survives the prune. Static, milliseconds, no
|
||
# Docker required.
|
||
check-docker-context:
|
||
node scripts/check-docker-build-context.mjs
|
||
node --test scripts/check-docker-build-context-core.test.mjs
|
||
|
||
# Guard that git hooks are served by THIS repo and that both systems survive.
|
||
# core.hooksPath is consulted exclusively when set and prints nothing when it is
|
||
# wrong: on 2026-08-07 it pointed into an unrelated project, so every hook here
|
||
# was served from a tree this repo does not own, and a lefthook install followed
|
||
# the redirect and overwrote that project's hooks (meridian-89vi). Asserts
|
||
# resolved config and file content, never an installer's exit code — `lefthook
|
||
# install` has been observed succeeding while installing nothing.
|
||
check-git-hooks:
|
||
node scripts/check-git-hooks.mjs
|
||
node --test scripts/check-git-hooks-core.test.mjs
|
||
|
||
# Contract-test backup failure cleanup, signed-set integrity, restore gates,
|
||
# semantic probes, matching-image startup, and isolated resource cleanup.
|
||
backup-restore-check:
|
||
bash scripts/test-verify-backup-restore.sh
|
||
|
||
# Prove the quiesced Git watermark roll-forward validates every referenced
|
||
# content-addressed object and never repairs by lowering the durable fence.
|
||
git-pointer-repair-contract:
|
||
bash scripts/test-repair-git-pointer-from-watermark.sh
|
||
|
||
# Guard the online NIP-34 backfill/index procedure. The events table is
|
||
# partitioned, so PostgreSQL requires concurrent leaf builds plus attachment to
|
||
# a non-concurrently-created partitioned parent index.
|
||
nip34-search-rollout-check:
|
||
bash scripts/test-backfill-nip34-search-contract.sh
|
||
|
||
# Assert every kind constant in ALL_KINDS has a reader somewhere. A declared
|
||
# and unread kind looks like progress in a diff and is indistinguishable from
|
||
# nothing at runtime. Deliberate reservations live in the script's allowlist.
|
||
check-kinds:
|
||
node scripts/check-kind-readers.mjs
|
||
node --test scripts/check-kind-readers-core.test.mjs
|
||
|
||
# The bus-scaling harness produces the 64x interest-scoping figure that README,
|
||
# TASKS and DIAGRAM all publish as [MEASURED] — and DIAGRAM says the harness
|
||
# "fails below 95% of ideal, so the claim is load-bearing rather than
|
||
# decorative". Nothing ran it. It was not wired into any recipe, so the guard
|
||
# behind four published claims never executed in CI, and `assert_scaling`
|
||
# separately let a scoped arm delivering ZERO score an infinite reduction and
|
||
# pass. Both are the same failure the root contract names: a claim whose
|
||
# enforcement point is never reached. This runs the harness's own tests only —
|
||
# the measurement itself is a deliberate, quiet-machine act, never a CI job,
|
||
# because a throughput number taken under load is worse than no number.
|
||
perf-check:
|
||
python3 -m unittest discover -s perf -p 'test_*.py'
|
||
|
||
# Prove the pinned zenoh config survives contact with the DAEMON, not just the
|
||
# library. zenohd is not a passive reader of its own file: it re-enables
|
||
# adminspace and plugins_loading after loading it, and reads an ABSENT
|
||
# multicast-scouting key as consent. So a test that only shows the file parsed
|
||
# shows nothing -- this asserts the effective configuration zenohd reports in its
|
||
# own `Initial conf` line, and fails if zenohd ever stops force-enabling them, so
|
||
# the workaround cannot outlive the quirk.
|
||
#
|
||
# NOT in `just check`: it needs Docker and the pinned eclipse/zenoh:1.8.0 image.
|
||
# Wiring it in behind a compose-check-style self-skip is meridian-vt1 follow-up
|
||
# work -- a gate in `check` that has never been proven to skip cleanly is how a
|
||
# green CI stops meaning anything.
|
||
zenoh-config-check:
|
||
MERIDIAN_ZENOH_DAEMON_CHECK=1 cargo test -p meridian-pubsub --features zenoh zenoh_config
|
||
|
||
# Prove the bus DELIVERS in the topology it is deployed in: two relay sessions,
|
||
# one real zenohd, an event across. Nothing else does.
|
||
#
|
||
# `tests/zenoh_bus.rs` has 21 green tests and every one of them cross-connects
|
||
# two peers DIRECTLY -- a shape that exercises none of the router's routing
|
||
# tables. All 21 stayed green through meridian-2m45, where the documented
|
||
# default mode delivered NOTHING through a router while both sessions reported
|
||
# `zenoh_ready`. A test suite that never stands up the deployed topology cannot
|
||
# see a defect that only exists in it, however many assertions it carries.
|
||
#
|
||
# Same gate as `zenoh-config-check` above and for the same reason: it needs
|
||
# Docker and the pinned eclipse/zenoh:1.8.0 image, so it is opt-in behind
|
||
# MERIDIAN_ZENOH_DAEMON_CHECK=1 and NOT in `just check`.
|
||
zenoh-router-check:
|
||
MERIDIAN_ZENOH_DAEMON_CHECK=1 cargo test -p meridian-pubsub --features zenoh --test zenoh_router -- --test-threads=1
|
||
|
||
# Compile and run the Zenoh backend. Nothing else does.
|
||
#
|
||
# `just clippy` is `--workspace --all-targets` with no `--all-features`, and
|
||
# `just test-unit` names crates without `--features zenoh`, so every line of
|
||
# crates/meridian-pubsub/src/zenoh/, the shadow adapter, and the whole
|
||
# tests/zenoh_bus.rs suite were compiled and executed by NO gate -- not CI, not
|
||
# pre-push, not `just ci`. That included `ZenohConfigError::NonLocalEndpoint`,
|
||
# the single refusal standing between this build and unauthenticated
|
||
# federation: a compile break in it would have shipped undetected, and every
|
||
# "landed and tested" claim rested on an agent typing a local command in a
|
||
# worktree. Found by the Provenance Auditor seat, 2026-08-19.
|
||
#
|
||
# This is the same defect the repo already paid for twice this week -- perf/ in
|
||
# no recipe while four documents cited its number as [MEASURED], and cargo-deny
|
||
# reporting `licenses ok` with zenoh absent from the graph entirely. A gate that
|
||
# does not run is indistinguishable from one that passes.
|
||
#
|
||
# The daemon check above stays separate: it needs Docker and a pulled image.
|
||
# This one needs neither.
|
||
zenoh-check:
|
||
cargo clippy -p meridian-pubsub -p meridian-relay --all-targets --features zenoh -- -D warnings
|
||
cargo test -p meridian-pubsub --features zenoh
|
||
|
||
# Report the STELLAR documentation delta: which AGENTS.md subtrees moved since
|
||
# the committed snapshot, and what a regeneration has already dropped. Read this
|
||
# before editing a doc — rewriting one whose subtree did not move is how durable
|
||
# contracts get paraphrased away.
|
||
stellar-scan:
|
||
node scripts/stellar-scan.mjs
|
||
|
||
# Gate the chain. Fails on an orphaned doc, a child-index row pointing at
|
||
# nothing, or a loss: a section dropped or gutted, a child row removed, a doc
|
||
# shrunk past 60% of its bytes. The snapshot is tracked precisely so accepting
|
||
# a loss is a reviewable diff rather than a silent regeneration.
|
||
check-stellar:
|
||
node scripts/stellar-scan.mjs --check
|
||
node --test scripts/stellar-scan-core.test.mjs
|
||
bash scripts/test-stellar-scan-contract.sh
|
||
|
||
# Accept the current chain state and refresh .settings/stellar/reference-index.md.
|
||
# Commit the snapshot in the same commit as the doc edits it describes.
|
||
stellar-snapshot:
|
||
node scripts/stellar-scan.mjs --write
|
||
|
||
# Validate project skill frontmatter, UI metadata, and suite-index coverage.
|
||
# Guard text that leaves this repo for an external tracker or wiki.
|
||
#
|
||
# An external ticket states WORK TO BE DONE. Internal decision provenance
|
||
# (advisory-seat deliberation, automated review passes, model names) and prior
|
||
# product identities (Codebase Chat, Buzz, Sprout) are not work, and publishing
|
||
# them to a customer's GitLab is not recoverable by editing afterwards — the
|
||
# notification email already went out.
|
||
#
|
||
# Both failure modes already happened, which is why this is a gate and not a
|
||
# convention. The guard is the BACKSTOP: a generator must build external copy
|
||
# from a work statement, because a blocklist over freeform prose fails open on
|
||
# the phrasing nobody predicted. Widening the pattern list is the wrong repair
|
||
# for a failure — rewrite the copy.
|
||
check-external-copy:
|
||
node --test scripts/check-external-copy-core.test.mjs
|
||
node scripts/check-external-copy.mjs
|
||
|
||
check-skills:
|
||
node scripts/check-project-skills.mjs
|
||
node --test scripts/check-project-skills-core.test.mjs
|
||
|
||
# The gauntlet register may not claim a state its evidence cannot support. The
|
||
# maturation brief forbids an agent grading itself complete, secure, or
|
||
# production-ready; a prose rule cannot bind the agent it constrains, so the
|
||
# state machine, the evidence shape, the bead references, and the loop's own
|
||
# refusal list are parsed and asserted instead.
|
||
check-gauntlet:
|
||
node scripts/check-gauntlet.mjs
|
||
node --test scripts/check-gauntlet-core.test.mjs
|
||
|
||
# The G01 baseline inventory annotates the Cargo workspace with three facts the
|
||
# workspace cannot state about itself — maturity, test coverage, and whether the
|
||
# root architecture map names the crate at all. An annotation that drifts from
|
||
# what it annotates is worse than none, because it reads as verified. Every
|
||
# column is recomputed and compared bidirectionally, so a crate added or removed
|
||
# without touching the inventory fails here rather than at the next audit.
|
||
# The README's crate table is held to the same contract, in its own marker block:
|
||
# it is the most-read file in the repo and the least likely to be revisited when
|
||
# a crate lands, so an unguarded enumeration there would drift the fastest. Its
|
||
# Rust badge is pinned to rust-toolchain.toml for the same reason: a stale badge
|
||
# sends a contributor to the wrong toolchain and surfaces as a build error.
|
||
check-architecture-map:
|
||
node scripts/check-architecture-map.mjs
|
||
node --test scripts/check-architecture-map-core.test.mjs
|
||
|
||
# Two halves of the alerting surface fail silently. An alert whose expression
|
||
# names a metric no crate declares can never fire, and reads as coverage on
|
||
# every dashboard; an alert with no runbook is invisible until it pages. Both
|
||
# are asserted against the G17 baseline, bidirectionally, so an alert added or
|
||
# a metric renamed fails here rather than at 3am.
|
||
check-alert-runbooks:
|
||
node scripts/check-alert-runbooks.mjs
|
||
node --test scripts/check-alert-runbooks-core.test.mjs
|
||
|
||
# Assert every copy of the brand mark IS the brand mark, by measuring the
|
||
# rendered pixels rather than trusting the generator that produced them. The
|
||
# rebrand was called done three times over surfaces still carrying the old
|
||
# artwork; each miss was a copy whose generator was correct and whose committed
|
||
# artefact was not. See `.settings/rebrand/README.md` § Verifying a brand change.
|
||
check-brand:
|
||
node scripts/check-brand-marks.mjs
|
||
node --test scripts/check-brand-marks-core.test.mjs
|
||
# The control plane inlines the app's globe + wordmark from a committed
|
||
# bundle. Nothing fails when it goes stale — the pages keep serving the
|
||
# previous build — so freshness has to be asserted rather than noticed.
|
||
node scripts/generate-control-plane-brand.mjs --check
|
||
|
||
# Re-vendor the Scalar renderer both API references are served with.
|
||
#
|
||
# Downloads the pinned @scalar/api-reference tarball, verifies the registry's
|
||
# own sha512 integrity digest, and extracts one file. Bumping the version also
|
||
# means bumping SCALAR_VERSION in crates/meridian-openapi/src/lib.rs — a unit
|
||
# test there fails if the two disagree.
|
||
vendor-scalar:
|
||
node scripts/vendor-scalar.mjs
|
||
|
||
# Assert the committed renderer is the pinned release, byte for byte.
|
||
#
|
||
# Not in `check`: it reaches the npm registry, and a check that fails on a
|
||
# flaky network teaches people to ignore it. Run it when bumping Scalar, and
|
||
# in any job that already has egress.
|
||
check-scalar:
|
||
node scripts/vendor-scalar.mjs --check
|
||
|
||
# Keep preview manifest metadata and the specs' authoritative gap ledgers in sync.
|
||
# Assert `just test-unit`'s two branches run the same package set.
|
||
#
|
||
# CI takes the cargo-nextest lane; a developer without it takes the fallback.
|
||
# A package added to one and not the other makes them run different test sets,
|
||
# and neither goes red — CI is green because it never ran the missing package,
|
||
# and the developer is green for the same reason (meridian-1pmm).
|
||
check-test-unit-parity:
|
||
node scripts/check-test-unit-parity.mjs
|
||
node --test scripts/check-test-unit-parity-core.test.mjs
|
||
|
||
check-feature-specs:
|
||
node scripts/check-feature-specs.mjs
|
||
node --test scripts/check-feature-specs-core.test.mjs
|
||
|
||
# `docs/mips/README.md` is the one MIP register. The desktop capability view
|
||
# reads a generated copy; this fails when the two drift. Pass `--write` to
|
||
# regenerate after editing the register.
|
||
check-mips *ARGS:
|
||
node scripts/check-mip-registry.mjs {{ARGS}}
|
||
node --test scripts/mip-registry-core.test.mjs
|
||
|
||
# The root contract forbids new `unwrap()`/`expect()` in production paths, and
|
||
# until now nothing read that sentence. A per-crate ratchet fails on growth, so
|
||
# a new panic has to be argued for in a commit message rather than merged in
|
||
# silence. Rows only move down; `--write` regenerates after a real reduction.
|
||
check-unwrap-budget *ARGS:
|
||
node scripts/check-unwrap-budget.mjs {{ARGS}}
|
||
node --test scripts/check-unwrap-budget-core.test.mjs
|
||
|
||
# ReductStore's Zenoh integration is two environment variables away from being
|
||
# something the architecture refuses, and both wrong values WORK — records land,
|
||
# queries answer, and nothing downstream notices. Pointing the subscriber or the
|
||
# queryable at `meridian/v1/**` buys a second durable log and a second read path
|
||
# in one string (DIAGRAM.md R2/R3, the arrival R9 names). Omitting `mode=client`
|
||
# inherits Zenoh's default of `peer`, so a storage box becomes a routing peer
|
||
# that can transit between communities (DIAGRAM.md R4, Law 2). Lands BEFORE any
|
||
# Zenoh wiring, which is why its own fixtures carry the evidence: nothing in
|
||
# tree sets RS_ZENOH_* yet, so a scan set that reached nothing would pass
|
||
# forever. Static, no Docker, no network.
|
||
check-reductstore-zenoh:
|
||
node --test scripts/check-reductstore-zenoh-core.test.mjs
|
||
node scripts/check-reductstore-zenoh.mjs
|
||
bash scripts/test-reductstore-zenoh-guard.sh
|
||
|
||
# npm half of deny.toml: pnpm audit findings need an ignore entry with a reason,
|
||
# and every licence expression from `pnpm licenses list` must sit on the SPDX
|
||
# allow-list (or a named package exception with a reason). Policy lives in
|
||
# scripts/npm-supply-chain.json. Reaches the registry for audit metadata.
|
||
check-npm-supply-chain:
|
||
node scripts/check-npm-supply-chain.mjs
|
||
node --test scripts/check-npm-supply-chain-core.test.mjs
|
||
|
||
# Every ignored relay integration binary must have an executable or reviewed lane.
|
||
check-relay-e2e-inventory:
|
||
node scripts/check-relay-e2e-inventory.mjs
|
||
node --test scripts/check-relay-e2e-inventory-core.test.mjs
|
||
|
||
# ArgoCD tracks chart versions, so a template edited without a `Chart.yaml`
|
||
# `version` bump deploys the PREVIOUS templates — silently, and remotely: the
|
||
# chart renders, helm-unittest passes, CI is green. Commit b6443a823 added an
|
||
# entire alert group at 0.1.13 exactly that way. The contract is differential
|
||
# but this repo's guards run on a working tree with no trustworthy baseline
|
||
# (`main` is pushed to directly and rebased, so a merge-base is not a release
|
||
# boundary), so the baseline is checked in: scripts/chart-version-manifest.json
|
||
# records each chart's rendered bytes against the version they were recorded at.
|
||
# After a legitimate bump, `just check-chart-version-bump --write`.
|
||
check-chart-version-bump *ARGS:
|
||
node scripts/check-chart-version-bump.mjs {{ARGS}}
|
||
node --test scripts/check-chart-version-bump-core.test.mjs
|
||
bash scripts/test-chart-version-bump-guard.sh
|
||
|
||
# Applied migrations are immutable. Pin exact bytes to the attested schema-26
|
||
# N-1 image so comments or branding edits cannot break SQLx rollout checksums.
|
||
check-frozen-migrations:
|
||
node scripts/check-frozen-migrations.mjs
|
||
node --test scripts/check-frozen-migrations-core.test.mjs
|
||
bash scripts/test-n-minus-one-candidate-contract.sh
|
||
|
||
# A product rename moves four persisted namespaces; Rust declares each chain
|
||
# once and derives its readers, but the shell scripts hardcode. That is how the
|
||
# Meridian rename moved the current entry in `instance-env.sh` and
|
||
# `reset-desktop-dev-state.sh` and dropped both middle generations while
|
||
# `reset.rs` stayed correct — and both failures are silent. Enforces
|
||
# prepend-never-replace across every copy.
|
||
check-legacy-namespaces:
|
||
node scripts/check-legacy-namespaces.mjs
|
||
node --test scripts/check-legacy-namespaces-core.test.mjs
|
||
|
||
# The user-facing tenancy unit is a Relay, and the product is Meridian. The
|
||
# retired words survive in identifiers, test IDs and CSS custom properties,
|
||
# where they are correct and must stay — so a grep cannot tell a stale screen
|
||
# from working code, and the rename kept being declared done with copy like
|
||
# "Add a community" still on screen. Checks only strings that reach a user.
|
||
check-relay-terminology:
|
||
node scripts/check-relay-terminology.mjs
|
||
node --test scripts/check-relay-terminology-core.test.mjs
|
||
|
||
# Format all Rust code
|
||
fmt:
|
||
cargo fmt --all
|
||
|
||
# Check formatting without modifying files
|
||
fmt-check:
|
||
cargo fmt --all -- --check
|
||
|
||
# Run clippy with warnings as errors
|
||
clippy:
|
||
cargo clippy --workspace --all-targets -- -D warnings
|
||
|
||
# Install JS dependencies (pnpm workspace — installs all packages from root)
|
||
desktop-install:
|
||
pnpm install
|
||
|
||
# Install JS dependencies reproducibly for CI (pnpm workspace)
|
||
desktop-install-ci:
|
||
pnpm install --frozen-lockfile
|
||
|
||
# Run desktop lint and format checks
|
||
desktop-check:
|
||
cd {{desktop_dir}} && pnpm check
|
||
|
||
# Fix desktop lint and format issues
|
||
desktop-fix:
|
||
cd {{desktop_dir}} && pnpm exec biome check --write . && pnpm check:file-sizes
|
||
|
||
# Run desktop TS helper unit tests
|
||
desktop-test:
|
||
cd {{desktop_dir}} && pnpm test
|
||
|
||
# Run desktop TypeScript checks
|
||
desktop-typecheck:
|
||
cd {{desktop_dir}} && pnpm typecheck
|
||
|
||
# Build desktop frontend assets
|
||
desktop-build:
|
||
cd {{desktop_dir}} && pnpm build
|
||
|
||
# Format desktop Tauri Rust code
|
||
desktop-tauri-fmt:
|
||
cargo fmt --manifest-path {{desktop_tauri_manifest}} --all
|
||
|
||
# Check desktop Tauri Rust formatting
|
||
desktop-tauri-fmt-check:
|
||
cargo fmt --manifest-path {{desktop_tauri_manifest}} --all -- --check
|
||
|
||
# Format all code (Rust + Tauri Rust + Dart)
|
||
fmt-all: fmt desktop-tauri-fmt mobile-fmt
|
||
|
||
# Fix all formatting and lint issues
|
||
fix-all: fmt desktop-tauri-fmt desktop-fix web-fix mobile-fix
|
||
|
||
# Ensure sidecar placeholder binaries exist (Tauri validates externalBin at compile time)
|
||
# Sidecar binary list must stay in sync with desktop-release-build below.
|
||
_ensure-sidecar-stubs:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||
mkdir -p REMAPPING/meridian-desktop/src-tauri/binaries
|
||
for bin in meridian-acp meridian-agent meridian-dev-mcp git-credential-nostr meridian; do
|
||
touch "REMAPPING/meridian-desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||
done
|
||
|
||
# Ensure Docker dev services (Postgres, Dragonfly, etc.) are running and healthy
|
||
_ensure-services:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' meridian-postgres 2>/dev/null || echo "not_found")
|
||
dragonfly=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' meridian-dragonfly 2>/dev/null || echo "not_found")
|
||
if [[ "$pg" == "healthy" && "$dragonfly" == "healthy" ]]; then
|
||
echo "Services already healthy"
|
||
exit 0
|
||
fi
|
||
echo "Starting services..."
|
||
docker compose up -d || true
|
||
echo -n "Waiting for services"
|
||
for i in $(seq 1 40); do
|
||
pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' meridian-postgres 2>/dev/null || echo "not_found")
|
||
dragonfly=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' meridian-dragonfly 2>/dev/null || echo "not_found")
|
||
if [[ "$pg" == "healthy" && "$dragonfly" == "healthy" ]]; then
|
||
echo " ready"
|
||
exit 0
|
||
fi
|
||
echo -n "."
|
||
sleep 3
|
||
done
|
||
echo " timed out"
|
||
exit 1
|
||
|
||
# Apply database migrations and seed the local dev community if the dev database is running
|
||
_ensure-migrations: _ensure-services
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# dotenv-load already applied .env. Layer .env.local on top for the same
|
||
# reason `relay` does: when docker-compose.override.yml relocates the
|
||
# Postgres host port, only .env.local carries the matching DATABASE_URL.
|
||
# Without this the migrator dials .env's port, which on a machine running
|
||
# other stacks is a DIFFERENT database that happens to answer — it then
|
||
# fails with "migration N was previously applied but has been modified",
|
||
# which reads like a broken migration but is the checksum guard refusing to
|
||
# migrate someone else's data.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
cargo run -p meridian-admin -- migrate
|
||
./scripts/seed-local-community.sh
|
||
|
||
# Run clippy on the desktop Tauri Rust crate
|
||
desktop-tauri-clippy: _ensure-sidecar-stubs
|
||
cargo clippy --manifest-path {{desktop_tauri_manifest}} --all-targets -- -D warnings
|
||
|
||
# Check the desktop Tauri Rust crate compiles
|
||
desktop-tauri-check: _ensure-sidecar-stubs
|
||
cargo check --manifest-path {{desktop_tauri_manifest}}
|
||
|
||
# Run desktop Tauri Rust unit tests
|
||
desktop-tauri-test: _ensure-sidecar-stubs
|
||
cd REMAPPING/meridian-desktop/src-tauri && cargo test
|
||
|
||
# Verify compiled-flag behavior under both compile states (clean + internal).
|
||
# Runs the observer_archive focused test twice with independently supplied
|
||
# expected values; build.rs rerun-if-env-changed triggers recompilation.
|
||
desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
cd REMAPPING/meridian-desktop/src-tauri
|
||
echo "=== Clean build (no flag) → expect false ==="
|
||
env -u MERIDIAN_BUILD_OBSERVER_ARCHIVE_DEFAULT \
|
||
-u MERIDIAN_BUILD_AUTO_CONNECT_DEFAULT_RELAY \
|
||
MERIDIAN_TEST_EXPECTED_OBSERVER_ARCHIVE_DEFAULT=false \
|
||
cargo test observer_archive_default_enabled_matches_expected -- --ignored --nocapture
|
||
env -u MERIDIAN_BUILD_AUTO_CONNECT_DEFAULT_RELAY \
|
||
MERIDIAN_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=false \
|
||
cargo test compiled_flag_matches_expected -- --ignored --nocapture
|
||
echo "=== Internal build (flags set) → expect true ==="
|
||
MERIDIAN_BUILD_OBSERVER_ARCHIVE_DEFAULT=1 \
|
||
MERIDIAN_TEST_EXPECTED_OBSERVER_ARCHIVE_DEFAULT=true \
|
||
cargo test observer_archive_default_enabled_matches_expected -- --ignored --nocapture
|
||
MERIDIAN_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1 \
|
||
MERIDIAN_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true \
|
||
cargo test compiled_flag_matches_expected -- --ignored --nocapture
|
||
echo "Both compiled states verified."
|
||
|
||
# Build the full desktop Tauri app locally (unsigned, for testing)
|
||
# Sidecar binary list must stay in sync with _ensure-sidecar-stubs above.
|
||
# pnpm install is unconditional here: release builds must start from a clean dep tree.
|
||
desktop-release-build target="aarch64-apple-darwin":
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
TARGET={{target}}
|
||
# REAL sidecars, never `touch`ed stubs. Tauri's externalBin check only tests
|
||
# that the path exists, so zero-byte files build, bundle, install and launch
|
||
# cleanly — then the app dies the first time it shells out to an agent
|
||
# binary. Stubs are correct for check/clippy/test (`_ensure-sidecar-stubs`),
|
||
# and never correct for a bundle somebody will run.
|
||
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"
|
||
# This recipe passes --features mesh-llm, so it also needs the native
|
||
# runtime the feature resolves at RUNTIME. Same contract as `just mesh=1
|
||
# dev`; see the recipe comment there.
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
pnpm install
|
||
cd {{desktop_dir}} && pnpm tauri build --features mesh-llm --target {{target}}
|
||
|
||
# Run desktop checks suitable for CI / pre-push
|
||
desktop-ci: desktop-check desktop-test desktop-tauri-fmt-check desktop-build desktop-tauri-check desktop-tauri-test
|
||
|
||
# Seed deterministic channel data for desktop Playwright tests
|
||
desktop-e2e-seed: _ensure-migrations
|
||
./scripts/setup-desktop-test-data.sh
|
||
|
||
# Run desktop browser smoke tests
|
||
desktop-e2e-smoke:
|
||
cd {{desktop_dir}} && pnpm test:e2e:smoke
|
||
|
||
# Run desktop relay-backed e2e tests
|
||
desktop-e2e-integration: _ensure-migrations
|
||
cd {{desktop_dir}} && pnpm test:e2e:integration
|
||
|
||
# Run only the e2e specs changed vs origin/main (both projects) before pushing
|
||
desktop-e2e-pre-push: _ensure-migrations
|
||
git fetch origin main
|
||
cd {{desktop_dir}} && pnpm build:e2e && pnpm exec playwright test --only-changed=origin/main
|
||
|
||
# Run all checks suitable for CI / pre-push (no infra needed)
|
||
ci: check test-unit desktop-test desktop-build desktop-tauri-check desktop-tauri-test web-build web-e2e-smoke admin-web-e2e mobile-test
|
||
|
||
# ─── Test ─────────────────────────────────────────────────────────────────────
|
||
|
||
# Run all tests (unit + integration)
|
||
test:
|
||
./scripts/run-tests.sh all
|
||
|
||
# Run unit tests only (no infra needed)
|
||
test-unit:
|
||
#!/usr/bin/env bash
|
||
if command -v cargo-nextest &>/dev/null; then
|
||
cargo nextest run -p meridian-core -p meridian-auth --lib
|
||
# Webhook execution and redaction are feature-gated in the reusable
|
||
# workflow crate; run the production feature explicitly so those tests
|
||
# cannot disappear from the default package selection.
|
||
cargo nextest run -p meridian-workflow --features reqwest
|
||
# meridian-db migrator/lint tests: pure SQL-parsing unit tests (no infra).
|
||
# They guard the embedded-migrator invariant (exactly the consolidated
|
||
# 0001; cutover/backfill stays an operator script, not startup state)
|
||
# and the tenant-scoping lints. The Postgres-backed meridian-db tests are
|
||
# #[ignore]d, so --lib runs only the infra-free set. Without this gate a
|
||
# stray file in migrations/ or a broken lint ships green.
|
||
cargo nextest run -p meridian-db --lib
|
||
# Multi-tenant conformance gate (meridian-conformance): the independent
|
||
# replay checker + golden fixtures. No infra — pure in-process trace
|
||
# replay — so it belongs in the unit job. Run all targets (lib + the
|
||
# tests/replay_fixtures.rs integration test), not just --lib.
|
||
cargo nextest run -p meridian-conformance
|
||
# Gateway unit and black-box HTTP tests are infra-free. Postgres-backed
|
||
# contract/race tests run in the `backend-integration` job of
|
||
# .github/workflows/ci.yml — not anywhere in this file. The comment used
|
||
# to say "the dedicated CI job below", which sends a reader looking
|
||
# through the Justfile for something that was never in it (meridian-1pmm).
|
||
cargo nextest run -p meridian-push-gateway
|
||
# Inter-pod QUIC mesh. Infra-free — iroh binds 127.0.0.1:0 and the two
|
||
# registry tests construct a Redis pool without dialing it. The crate
|
||
# ships in every relay image unconditionally (no Cargo feature gates it
|
||
# out), so these are shipped-code tests; before this line they ran in no
|
||
# recipe and no CI job. `run-tests.sh`'s `cargo test --test '*'` sweep
|
||
# does not reach them either — all of them are in-file `#[cfg(test)]`
|
||
# under src/, and that sweep selects integration targets only.
|
||
cargo nextest run -p meridian-relay-mesh
|
||
# Redis pub/sub fan-out — the mechanism that actually moves Nostr events
|
||
# between relay pods, and unconditional (unlike the mesh above). `--lib`
|
||
# runs the infra-free set; the 12 Dragonfly-backed tests are `#[ignore]`d,
|
||
# including the dedicated-subscriber fault drill, and still run in no gate
|
||
# (meridian-4z3.16). Wiring the runnable 27 is not that gate.
|
||
cargo nextest run -p meridian-pubsub --lib
|
||
# The row's primary crate, and until now the least-tested lane: 819
|
||
# infra-free lib tests that ran in no `just` recipe at all, while CI
|
||
# executed only two filtered selections of them. `--lib` is the
|
||
# infra-free set here by the same convention as meridian-db — the 10
|
||
# Postgres-backed api::media/api::admin tests and the nondeterministic
|
||
# api::mesh_demo round trip (meridian-4z3.12) are `#[ignore]`d, and the
|
||
# Postgres-backed ones run in the backend-integration CI job against a
|
||
# real database rather than ceasing to run.
|
||
cargo nextest run -p meridian-relay --lib
|
||
# The agent-facing CLI, and the same story one crate over: 271 infra-free
|
||
# tests that ran in NO `just` recipe and NO CI job — `ci.yml` does not
|
||
# mention meridian-cli at all. Two of them are `subcommand_names_are_stable`
|
||
# and `subcommand_counts_are_stable`, whose entire job is to make a CLI
|
||
# surface change deliberate; they had been failing since de4cfec61
|
||
# because nothing executed them to say so.
|
||
cargo nextest run -p meridian-cli
|
||
# Eight more crates whose src/ unit tests ran in NO recipe and NO CI job.
|
||
# Found by auditing every workspace member against the Justfile, ci.yml
|
||
# and run-tests.sh: `run_unit_tests` selects crates explicitly, and
|
||
# `run_integration_tests`'s `cargo test --test '*'` sweep only reaches
|
||
# tests/ targets — so #[cfg(test)] modules under src/ in an unnamed crate
|
||
# execute nowhere at all.
|
||
#
|
||
# meridian-sdk is the one that matters most: 241 tests over the event
|
||
# builders every client and the CLI construct events with, including
|
||
# build_set_canvas. All 508 are infra-free and finish in well under a
|
||
# second combined, so there is no argument for leaving them out.
|
||
#
|
||
# --lib by the same convention as meridian-db: meridian-media,
|
||
# meridian-audit and meridian-search each carry #[ignore]d
|
||
# infra-dependent tests that this does not and should not run.
|
||
cargo nextest run --lib \
|
||
-p meridian-sdk -p meridian-persona -p meridian-media \
|
||
-p meridian-audit -p meridian-openapi -p meridian-search \
|
||
-p meridian-ws-client -p meridian-test-db
|
||
else
|
||
./scripts/run-tests.sh unit
|
||
# Keep the fallback equivalent to the nextest lane: the reusable
|
||
# workflow crate's outbound execution/redaction tests are hidden unless
|
||
# the production HTTP feature is selected explicitly.
|
||
cargo test -p meridian-workflow --features reqwest
|
||
cargo test -p meridian-relay-mesh
|
||
cargo test -p meridian-pubsub --lib
|
||
cargo test -p meridian-relay --lib
|
||
cargo test -p meridian-cli
|
||
cargo test --lib \
|
||
-p meridian-sdk -p meridian-persona -p meridian-media \
|
||
-p meridian-audit -p meridian-openapi -p meridian-search \
|
||
-p meridian-ws-client -p meridian-test-db
|
||
fi
|
||
|
||
# Run integration tests only (starts services if needed)
|
||
test-integration:
|
||
./scripts/run-tests.sh integration
|
||
|
||
# The isolated-DB gate: the Postgres-backed meridian-db tests.
|
||
#
|
||
# These are the 157 `#[ignore = "requires Postgres"]` tests in meridian-db, and
|
||
# they are the entire safety argument for read-replica routing — every
|
||
# replica-fence and floor-guard test lives here
|
||
# (`created_at_floor_guard_aborts_old_channel_rows_at_commit`,
|
||
# `fence_probe_refuses_to_start_without_verified_floor_guard`,
|
||
# `channel_cursor_above_fence_stays_on_writer_preventing_middle_hole`, …).
|
||
#
|
||
# Before this recipe they ran in **no target at all**: `test-unit` runs
|
||
# `--lib` without `--ignored`, and `test`/`run-tests.sh integration` invokes
|
||
# `cargo test -p meridian-db` without it either. `run-tests.sh` said so itself
|
||
# and pointed at "a separate isolated-DB gate" that did not exist. This is that
|
||
# gate (meridian-4z3.16). Their existence read as coverage while nothing
|
||
# executed them, which is worse than not having them.
|
||
#
|
||
# `--test-threads=1` is required by what these tests do, not caution: the
|
||
# read-replica routing and fence-probe tests each CREATE and DROP their own
|
||
# scratch database and mutate singleton rows inside it, so they must not share.
|
||
#
|
||
# WALL-CLOCK, measured over fifteen full runs on an M3 Max against the compose
|
||
# Postgres (17.7, `fsync=on`, `shared_buffers=512MB`): 119–224s, all 157 tests
|
||
# executed every time. Teardown dominates — `DROP DATABASE` forces an immediate
|
||
# checkpoint, and sampling `pg_stat_activity` mid-run catches each one parked on
|
||
# `wait_event = CheckpointDone` for 2–12s — but that is seconds, not the "on the
|
||
# order of a minute" this comment previously recorded, and the selection is
|
||
# minutes rather than the ~2.5h on meridian-s6vo. An empty scratch database
|
||
# drops in 0.09–0.13s. Re-derive the number before quoting it; the earlier
|
||
# figure did not reproduce and nobody knows why it was 40× slower.
|
||
#
|
||
# A killed run leaks its scratch databases; `test-db-clean` drops them.
|
||
test-db: _ensure-migrations test-db-run
|
||
|
||
# The gate itself, with no service bootstrap.
|
||
#
|
||
# CI starts Postgres its own way and would fail on `_ensure-migrations`' docker
|
||
# compose path, so the selection lives here and both entry points share it. The
|
||
# alternative — a second copy of the cargo invocation in ci.yml — is the
|
||
# hand-synchronised shape meridian-1pmm already records against `test-unit`.
|
||
test-db-run:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# Same layering as _ensure-migrations, and for the same reason: when the
|
||
# override relocates the Postgres host port, only .env.local carries the
|
||
# matching DATABASE_URL. Without it we dial .env's port, which on a machine
|
||
# running several stacks is a different database that happens to answer.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
# ONE name, no fallback. Every Postgres-backed test in the workspace now
|
||
# resolves through `meridian_test_db::disposable_url()` (34 call sites), so
|
||
# binding `MERIDIAN_TEST_DATABASE_URL` is sufficient and the old three-name
|
||
# workaround is gone — as is the hardcoded `localhost:5432` default, which
|
||
# on a multi-stack host was a neighbour's Postgres (meridian-8onc).
|
||
# THIS SUITE IS DESTRUCTIVE. Three of the migration tests call
|
||
# `reset_public_schema`, which is `DROP SCHEMA IF EXISTS public CASCADE`
|
||
# (crates/meridian-db/src/migration.rs:1275). The first version of this
|
||
# recipe defaulted to DATABASE_URL, so running it DROPPED THE DEVELOPER'S
|
||
# DEV DATABASE — observed: every community wiped, the desktop app left
|
||
# reporting "no community is configured for this host". A test suite that
|
||
# destroys the database you develop against must never be one env var's
|
||
# absence away from doing so.
|
||
#
|
||
# So the default is a SEPARATE, disposable database on the same server, and
|
||
# pointing this at the dev database requires saying so out loud.
|
||
dev_url="${DATABASE_URL:?DATABASE_URL is unset -- run 'just setup' or set it}"
|
||
default_test_url="$(printf '%s' "$dev_url" | sed -E 's#/[^/?]+(\?|$)#/meridian_test\1#')"
|
||
url="${MERIDIAN_TEST_DATABASE_URL:-${TEST_DATABASE_URL:-$default_test_url}}"
|
||
db_name="$(printf '%s' "$url" | sed -E 's#.*/([^/?]+)(\?.*)?$#\1#')"
|
||
dev_name="$(printf '%s' "$dev_url" | sed -E 's#.*/([^/?]+)(\?.*)?$#\1#')"
|
||
if [[ "$db_name" == "$dev_name" && "${MERIDIAN_TEST_DB_DESTROY_DEV:-}" != "1" ]]; then
|
||
echo "REFUSING: this suite runs DROP SCHEMA public CASCADE, and the target" >&2
|
||
echo " database (${db_name}) is the one DATABASE_URL develops against." >&2
|
||
echo " Point MERIDIAN_TEST_DATABASE_URL at a disposable database, or set" >&2
|
||
echo " MERIDIAN_TEST_DB_DESTROY_DEV=1 if you genuinely mean to wipe it." >&2
|
||
exit 1
|
||
fi
|
||
# Create the disposable database if it is not there yet. Do this through a
|
||
# server connection rather than `createdb "$url"` — createdb treats that URL
|
||
# as a database NAME, so it silently fails and the whole suite then dies on
|
||
# "database does not exist". CREATE DATABASE cannot run inside a
|
||
# transaction, hence the plain -c.
|
||
if ! psql "$dev_url" -tAc "SELECT 1 FROM pg_database WHERE datname = '${db_name}'" | grep -q 1; then
|
||
echo "Creating disposable test database ${db_name}"
|
||
psql "$dev_url" -c "CREATE DATABASE \"${db_name}\"" >/dev/null
|
||
fi
|
||
# DATABASE_URL still moves with it because `meridian-admin migrate` below
|
||
# reads it — but nothing in the TEST path does any more.
|
||
export MERIDIAN_TEST_DATABASE_URL="$url" DATABASE_URL="$url"
|
||
# Migrate the disposable database. `_ensure-migrations` above ran the
|
||
# migrator against the DEV database — that is its job and it must keep doing
|
||
# it — so without this the test target is an empty schema and 45 of the
|
||
# tests fail on missing tables. Measured, not predicted.
|
||
echo "Migrating ${db_name}"
|
||
cargo run -q -p meridian-admin -- migrate
|
||
# Stamp the disposability marker. This is the second half of the guard in
|
||
# `meridian-test-db`: the destructive tests refuse unless the database both
|
||
# is NAMED like a throwaway and CARRIES this row. A name alone is one
|
||
# environment variable away from pointing at real data, which is exactly how
|
||
# the dev database got dropped; the marker is what a real database will
|
||
# never have, because nothing but this line and `stamp_disposable` writes it.
|
||
psql "$url" -q -c "CREATE TABLE IF NOT EXISTS _meridian_disposable (
|
||
stamped_at timestamptz NOT NULL DEFAULT now(), note text NOT NULL)" >/dev/null
|
||
psql "$url" -q -c "INSERT INTO _meridian_disposable (note)
|
||
SELECT 'provisioned by just test-db'
|
||
WHERE NOT EXISTS (SELECT 1 FROM _meridian_disposable)" >/dev/null
|
||
# QUARANTINE — two tests are excluded BY NAME, and the exclusion is here
|
||
# rather than in ci.yml so the gate and a developer's box cannot disagree
|
||
# about what ran.
|
||
#
|
||
# Both are in `push::tests` and both are flaky against the shared database.
|
||
# Measured across nine full runs after the deterministic failures were
|
||
# repaired: 7 green, 2 red, and the two reds were these two tests. A gate
|
||
# that reds ~22% of the time on unrelated work teaches people to re-run it
|
||
# until it passes, which is worse than not having one — the same reason
|
||
# `check-scalar` is kept out of `check`.
|
||
#
|
||
# batch_claim_is_single_community_and_setwise_ops_honor_the_fence
|
||
# meridian-yuia. Failed twice, on two DIFFERENT assertions. Not pinned.
|
||
# exhausted_match_job_is_reaped_and_cannot_pin_retention
|
||
# meridian-woau. 1 failure / 30 isolated runs. The row it wants can
|
||
# only come from the lease-activation backfill, so this may be a
|
||
# PRODUCT race that drops a push wake — do not "fix" it by retrying.
|
||
#
|
||
# This is quarantine, not deletion, and it is the thing to delete first when
|
||
# either bead closes. `MERIDIAN_TEST_DB_ALL=1` runs the full 157 — use it
|
||
# when working those beads, and to re-measure the flake rate.
|
||
skips=(--skip push::tests::batch_claim_is_single_community_and_setwise_ops_honor_the_fence
|
||
--skip push::tests::exhausted_match_job_is_reaped_and_cannot_pin_retention)
|
||
if [[ "${MERIDIAN_TEST_DB_ALL:-0}" == "1" ]]; then
|
||
skips=()
|
||
echo "MERIDIAN_TEST_DB_ALL=1 — running the quarantined tests too (meridian-yuia, meridian-woau)"
|
||
else
|
||
echo "2 tests quarantined (meridian-yuia, meridian-woau); MERIDIAN_TEST_DB_ALL=1 to include them"
|
||
fi
|
||
echo "Isolated-DB gate against ${url}"
|
||
# `${skips[@]+...}` — macOS ships bash 3.2, where an empty array under
|
||
# `set -u` is an unbound-variable error rather than an empty expansion.
|
||
cargo test -p meridian-db --lib -- --ignored --test-threads=1 ${skips[@]+"${skips[@]}"}
|
||
|
||
# Drop scratch databases left behind by an interrupted `test-db` run.
|
||
#
|
||
# The routing and replica-fence tests create a database per test and drop it on
|
||
# the way out, so a killed or timed-out run leaks them. They are harmless but
|
||
# they accumulate, and a full disk is a confusing way to find that out.
|
||
test-db-clean:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
# To point this sweep somewhere other than the dev database, set
|
||
# MERIDIAN_TEST_DATABASE_URL — NOT DATABASE_URL.
|
||
#
|
||
# `set -a; . ./.env.local` overwrites names that are already exported, so a
|
||
# caller's DATABASE_URL does not survive to the line below. That is not a
|
||
# bug to patch by capturing the value first: `just`'s dotenv-load has
|
||
# already applied `.env` by the time this recipe starts, so "the value
|
||
# before sourcing" is `.env`'s, not the caller's, and preferring it makes
|
||
# `.env` beat `.env.local` — inverting the documented order and pointing a
|
||
# recipe that DROPS DATABASES at whatever Postgres `.env` names, which on a
|
||
# multi-stack host is a neighbour's. That patch was tried and reverted.
|
||
#
|
||
# MERIDIAN_TEST_DATABASE_URL works because neither env file defines it, so
|
||
# nothing overwrites it. Same reason `test-db-run` binds that one name and
|
||
# no fallback.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
url="${MERIDIAN_TEST_DATABASE_URL:-${TEST_DATABASE_URL:-${DATABASE_URL:?DATABASE_URL is unset}}}"
|
||
# Every scratch database is stamped `mdbtest_` by
|
||
# crates/meridian-db/src/test_scratch.rs. Anchor on that literal: this sweep
|
||
# runs against a developer's Postgres, which on a multi-stack host also holds
|
||
# neighbouring projects' databases, and a false positive here drops data.
|
||
#
|
||
# The prefix is the whole contract, and it is checked from the other side —
|
||
# `test_db_clean_sweeps_the_prefix_this_module_stamps` reads this line and
|
||
# fails if the two disagree. The previous shape-matching regex was not
|
||
# checked from anywhere and missed 13 of 32 prefixes, including every
|
||
# `fence_probe_*`, while still reporting success (meridian-5n3l).
|
||
#
|
||
# No `mapfile` — macOS ships bash 3.2 and this recipe runs on developer boxes.
|
||
count=0
|
||
while IFS= read -r db; do
|
||
[[ -z "$db" ]] && continue
|
||
echo "Dropping ${db}"
|
||
psql "$url" -c "DROP DATABASE IF EXISTS \"${db}\" WITH (FORCE)" >/dev/null
|
||
count=$((count + 1))
|
||
done < <(psql "$url" -tAc \
|
||
"select datname from pg_database where left(datname, 8) = 'mdbtest_'")
|
||
if [[ $count -eq 0 ]]; then echo "No leaked scratch databases."; else
|
||
echo "Dropped ${count} scratch database(s)."
|
||
fi
|
||
|
||
# The Redis-backed half of the infra gate: meridian-pubsub's #[ignore]d tests.
|
||
#
|
||
# Twelve tests across lib.rs (5), presence.rs (3) and nip98_replay.rs (4) — the
|
||
# fan-out, presence and NIP-98 replay paths — ran in no `just` target and no CI
|
||
# job, the other half of meridian-4z3.16. `test-unit` runs `-p meridian-pubsub
|
||
# --lib` without `--ignored`, so it covers the 27 infra-free ones and none of
|
||
# these.
|
||
#
|
||
# REDIS_URL is bound explicitly and verified with a PING before anything runs.
|
||
# The tests otherwise fall back to a hardcoded 127.0.0.1:6379, which on a
|
||
# multi-stack host is a neighbour's Redis — it answers, so the tests would pass
|
||
# against someone else's datastore while writing presence keys into it
|
||
# (meridian-7s3h is the same defect in meridian-relay). Unlike the Postgres
|
||
# gate this one is NOT destructive: no DROP, no schema reset.
|
||
# Split like `test-db` / `test-db-run`: the wrapper starts the local stack, the
|
||
# `-run` half is what CI invokes, so the selection lives in ONE place and a CI
|
||
# job and a developer's box cannot drift apart.
|
||
|
||
# The 53 `#[ignore]`d meridian-relay tests, which are the row's PRIMARY crate.
|
||
# Re-derive this rather than trusting it: FOUR authors have now written 46, 47,
|
||
# 49 and 51 beside the same selection, most recently while the tree held 53 —
|
||
# a9b30ea27 added two Postgres-gated tenancy tests and left the number behind. `grep -rc '#\[ignore'
|
||
# crates/meridian-relay/src/` overcounts — nine are the token inside comment
|
||
# prose — so count what the binary lists:
|
||
# cargo test -p meridian-relay --lib -- --ignored --list | grep -c ': test'
|
||
#
|
||
# Before this recipe, 27 of the crate's 48 ran in NO recipe and NO CI job: CI
|
||
# reached only `api::invites`, `api::media` and `api::admin` through two
|
||
# filtered nextest selections in backend-integration. Ungated were
|
||
# `api::operator` (12), `api::bridge` (6), `api::git::transport`'s
|
||
# `sec005_read_gate_tests` (4), `api::git::policy`, `api::git::access` and
|
||
# `workflow_sink` — a SECURITY READ GATE among them, executing nowhere
|
||
# (meridian-wxqw).
|
||
#
|
||
# They were also unrunnable-by-accident rather than merely unrun: `operator` and
|
||
# `bridge` hardcoded `localhost:5432` and returned `Option`, so every caller
|
||
# wrote `let Some(state) = ... else { return; }` and a missing database made all
|
||
# 12 operator tests report PASS having asserted nothing. Both now resolve
|
||
# through `meridian_test_db::disposable_url()` and fail loudly. Measured: with
|
||
# MERIDIAN_TEST_DATABASE_URL unset the group goes 0 passed / 12 FAILED, where it
|
||
# previously went 12 passed.
|
||
#
|
||
# NOT destructive — no DROP and no schema reset — but it writes communities, so
|
||
# it still targets the disposable database rather than the one you develop against.
|
||
test-relay-db: _ensure-services test-relay-db-run
|
||
|
||
# The gate itself, with no service bootstrap. Same split as `test-db` and
|
||
# `test-pubsub`, for the same reason: CI invokes the `-run` half, so the
|
||
# selection lives in one place and cannot drift from a developer's box.
|
||
test-relay-db-run:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# `.env.local` must win over `.env` — see `test-pubsub-run` for why that is
|
||
# load-bearing here and not cosmetic.
|
||
# `dev_url` is deliberately the LAYERED value, not the caller's: it is the
|
||
# database this checkout develops against, and the REFUSING guard below
|
||
# compares the test target to it. Preferring a caller's DATABASE_URL here
|
||
# would let someone move the goalposts of the very guard meant to stop them
|
||
# destroying their dev data. Override the TEST target with
|
||
# MERIDIAN_TEST_DATABASE_URL instead — see `test-db-clean` for why capturing
|
||
# a "caller value" before sourcing does not mean what it looks like.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
dev_url="${DATABASE_URL:?DATABASE_URL is unset -- run 'just setup' or set it}"
|
||
default_test_url="$(printf '%s' "$dev_url" | sed -E 's#/[^/?]+(\?|$)#/meridian_test\1#')"
|
||
url="${MERIDIAN_TEST_DATABASE_URL:-$default_test_url}"
|
||
db_name="$(printf '%s' "$url" | sed -E 's#.*/([^/?]+)(\?.*)?$#\1#')"
|
||
dev_name="$(printf '%s' "$dev_url" | sed -E 's#.*/([^/?]+)(\?.*)?$#\1#')"
|
||
if [[ "$db_name" == "$dev_name" && "${MERIDIAN_TEST_DB_DESTROY_DEV:-}" != "1" ]]; then
|
||
echo "REFUSING: ${db_name} is the database DATABASE_URL develops against." >&2
|
||
echo " These tests write communities into their target. Point" >&2
|
||
echo " MERIDIAN_TEST_DATABASE_URL at a disposable database." >&2
|
||
exit 1
|
||
fi
|
||
if ! psql "$dev_url" -tAc "SELECT 1 FROM pg_database WHERE datname = '${db_name}'" | grep -q 1; then
|
||
echo "Creating disposable test database ${db_name}"
|
||
psql "$dev_url" -c "CREATE DATABASE \"${db_name}\"" >/dev/null
|
||
fi
|
||
export MERIDIAN_TEST_DATABASE_URL="$url" DATABASE_URL="$url"
|
||
echo "Migrating ${db_name}"
|
||
cargo run -q -p meridian-admin -- migrate
|
||
# Same override pattern as `test-pubsub-run`: MERIDIAN_TEST_REDIS_URL, not
|
||
# REDIS_URL — `.env` defines REDIS_URL and dotenv-load has already applied it.
|
||
REDIS_URL="${MERIDIAN_TEST_REDIS_URL:-${REDIS_URL:-}}"
|
||
export REDIS_URL
|
||
: "${REDIS_URL:?REDIS_URL is unset -- run 'just setup' or set it}"
|
||
# `api::mesh_demo` — the repo's only end-to-end cross-pod round trip — was
|
||
# quarantined out of this selection while it failed ~60% of runs. The cause
|
||
# was a real product defect, not infrastructure: `run_demo_echo`'s drain tick
|
||
# cancelled a non-cancel-safe `recv_validated` and ate the frame. Fixed, and
|
||
# measured 20/20 green (meridian-4z3.12), so it is back in the gate. There is
|
||
# no skip list here any more; keep it that way — a selection with an
|
||
# exclusion is one commit away from excluding the thing it was built to run.
|
||
echo "Relay DB/Redis gate against ${db_name} and ${REDIS_URL}"
|
||
cargo test -p meridian-relay --lib -- --ignored --test-threads=1
|
||
|
||
# Cross-pod mesh round trip against TWO LIVE RELAY PROCESSES.
|
||
#
|
||
# The `live`-gate evidence for G03: `api::mesh_demo` covers the same round trip
|
||
# with both endpoints inside one process, so it proves the transport and the
|
||
# fence and says nothing about whether two relays DISCOVER each other through
|
||
# the shared Redis registry and route a session between them. Green CI is not
|
||
# evidence here — that is the root Design Law, not a preference.
|
||
#
|
||
# Deliberately not part of `just ci`: it boots two relays against the local
|
||
# stack, which a lint gate has no business doing. Run it when the claim matters.
|
||
mesh-probe: _ensure-services
|
||
./scripts/mesh-cross-pod-probe.sh
|
||
|
||
test-pubsub: _ensure-services test-pubsub-run
|
||
|
||
test-pubsub-run:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# `.env.local` MUST win over `.env` — that is the documented precedence
|
||
# (root AGENTS.md) and here it is load-bearing, not cosmetic: `.env` carries
|
||
# `REDIS_URL=redis://localhost:6390`, which on this host is a NEIGHBOURING
|
||
# stack's Redis. It answers PING, so a run that honours it passes while
|
||
# writing presence keys into someone else's datastore — observed, not
|
||
# theorised.
|
||
#
|
||
# An earlier version of this recipe tried to let "the caller" override the
|
||
# file by capturing REDIS_URL before sourcing. That cannot work: `just`'s
|
||
# `dotenv-load` has already put `.env`'s value in the environment by the
|
||
# time the body runs, so a `.env` value and a real shell export are
|
||
# indistinguishable — and the capture handed the run straight back to 6390.
|
||
# Override the target with MERIDIAN_TEST_REDIS_URL — not REDIS_URL.
|
||
# MERIDIAN_TEST_REDIS_URL works because no env file defines it, so nothing
|
||
# overwrites it after `.env.local` is sourced.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
REDIS_URL="${MERIDIAN_TEST_REDIS_URL:-${REDIS_URL:-}}"
|
||
export REDIS_URL
|
||
: "${REDIS_URL:?REDIS_URL is unset -- run 'just setup' or set it}"
|
||
# Pre-flight PING, but only when redis-cli exists: it is NOT in hermit's
|
||
# bin/, so a CI runner may not have it, and a missing diagnostic tool must
|
||
# not fail a gate that would otherwise pass. When it is absent the tests
|
||
# themselves surface an unreachable Redis — just less legibly.
|
||
if command -v redis-cli >/dev/null 2>&1; then
|
||
if ! redis-cli -u "$REDIS_URL" PING >/dev/null 2>&1; then
|
||
echo "REFUSING: ${REDIS_URL} did not answer PING." >&2
|
||
echo " These tests fall back to 127.0.0.1:6379 when unset, which on this" >&2
|
||
echo " host is usually another project's Redis. Start the stack first." >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
echo "Redis-backed pubsub gate against ${REDIS_URL}"
|
||
cargo test -p meridian-pubsub --lib -- --ignored --test-threads=1
|
||
|
||
# The canvas gate: kind 40100 read/write against a live relay.
|
||
#
|
||
# `crates/meridian-test-client/tests/e2e_canvas.rs` pins the rules that
|
||
# `meridian-acp/src/pool.rs` and the desktop's `canvasSync.ts` each implement
|
||
# separately — newest-wins, blank-means-cleared with no fallback, and the
|
||
# member/open-visibility split on writes. Two codebases agreeing by convention
|
||
# is not the same as a relay agreeing with both.
|
||
#
|
||
# Like `test-db`, this exists because the tests otherwise ran in NO target:
|
||
# `run-tests.sh integration` invokes `cargo test --test '*'` WITHOUT
|
||
# `--ignored`, so every `#[ignore]`d e2e case is silently skipped there. The
|
||
# whole `e2e_*` family has that problem; this recipe fixes it for one suite
|
||
# rather than turning on suites with known-failing cases (meridian-swcq,
|
||
# meridian-57j) and shipping a recipe that is red on arrival.
|
||
#
|
||
# It FAILS rather than skips when no relay is reachable. A gate that quietly
|
||
# passes when its subject is absent is the defect it was written to remove.
|
||
test-canvas:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# Override the target with MERIDIAN_TEST_RELAY_URL — not RELAY_URL.
|
||
#
|
||
# `.env` defines RELAY_URL and `just`'s dotenv-load applies it before this
|
||
# recipe starts, so a caller's RELAY_URL is indistinguishable from `.env`'s
|
||
# and cannot be preferred. Trying to (capture it before sourcing
|
||
# `.env.local`) makes `.env` beat `.env.local`, and this gate then looked
|
||
# for a relay on `.env`'s 3000 while the local one ran on `.env.local`'s
|
||
# 3002 — failing with "no relay reachable" on a machine where a relay was
|
||
# running. That patch was tried and reverted.
|
||
#
|
||
# MERIDIAN_TEST_RELAY_URL works because no env file defines it, so nothing
|
||
# overwrites it. Same reason `test-db-run` binds MERIDIAN_TEST_DATABASE_URL
|
||
# and no fallback.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
url="${MERIDIAN_TEST_RELAY_URL:-${MERIDIAN_RELAY_URL:-${RELAY_URL:-ws://localhost:3000}}}"
|
||
host_port="${url#*://}"; host_port="${host_port%%/*}"
|
||
host="${host_port%%:*}"; port="${host_port##*:}"
|
||
[[ "$port" == "$host" ]] && port=80
|
||
if ! (exec 3<>"/dev/tcp/${host}/${port}") 2>/dev/null; then
|
||
echo "error: no relay reachable at ${url}" >&2
|
||
echo " start one with 'just relay', or set RELAY_URL to a running one." >&2
|
||
exit 1
|
||
fi
|
||
echo "canvas gate against ${url}"
|
||
RELAY_URL="$url" cargo test -p meridian-test-client --test e2e_canvas \
|
||
-- --ignored --test-threads=1
|
||
|
||
# Deterministically sever each dedicated Redis subscriber while pooled PING
|
||
# remains healthy, then prove fail-closed drain, ACL/ban freshness, and recovery.
|
||
subscriber-fault-matrix:
|
||
./scripts/run-subscriber-fault-matrix.sh
|
||
|
||
# Freeze the production user-visible capacity profile and evidence manifest.
|
||
capacity-contract-check:
|
||
node scripts/check-relay-capacity-contract.mjs
|
||
|
||
# Dump the local relay DB, restore into an isolated disposable DB, and boot the
|
||
# current relay with migration disabled before comparing durable inventories.
|
||
relay-postgres-restore-rehearsal:
|
||
./scripts/rehearse-relay-postgres-restore.sh
|
||
|
||
# Exact pinned N-1 / current binary quiesced rollout, rollback, and restore.
|
||
n-minus-one-rollout output:
|
||
N_MINUS_ONE_ALLOW_DISPOSABLE=1 ./scripts/run-n-minus-one-rollout.sh {{output}}
|
||
|
||
# Meridian shared compute e2e: current desktop discovery/admission logic and
|
||
# Playwright UI coverage.
|
||
mesh-e2e:
|
||
cargo test --manifest-path {{desktop_dir}}/src-tauri/Cargo.toml --features mesh-llm mesh_llm --lib
|
||
cd {{desktop_dir}} && pnpm test:e2e:smoke -- mesh-compute.spec.ts
|
||
|
||
# Reset only development state, seed deterministic local channels, and launch
|
||
# the mesh-enabled desktop with the repository's public Tyler test identity.
|
||
# This is for local verification only; never point this identity at staging/prod.
|
||
[confirm("This will reset development data, preserve installed Meridian, then launch a seeded mesh dev app. Continue? (y/N)")]
|
||
mesh-dev-fresh:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
./scripts/dev-reset.sh --yes
|
||
./scripts/setup-desktop-test-data.sh
|
||
export MERIDIAN_PRIVATE_KEY="3dbaebadb5dfd777ff25149ee230d907a15a9e1294b40b830661e65bb42f6c03"
|
||
export MERIDIAN_REQUIRE_RELAY_MEMBERSHIP=true
|
||
export MERIDIAN_ALLOW_NIP_OA_AUTH=true
|
||
export RELAY_OWNER_PUBKEY="e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34"
|
||
export MERIDIAN_RELAY_PRIVATE_KEY="0000000000000000000000000000000000000000000000000000000000000001"
|
||
export MERIDIAN_RECONCILE_CHANNELS=true
|
||
export MERIDIAN_RESET_WEBVIEW_STATE=1
|
||
exec just mesh=1 dev
|
||
|
||
# Real serve->client->inference on this machine (not CI).
|
||
mesh-e2e-hardware:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
cargo run -p meridian-relay --example mesh_serve_client_smoke
|
||
|
||
# Three isolated node processes: trusted member joins and infers; stranger is rejected.
|
||
# Uses temp homes and explicit mesh owner keystores. Never reads the Meridian Keychain.
|
||
mesh-e2e-admission:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
cargo run -p meridian-relay --example mesh_admission_smoke
|
||
|
||
# Full hardware confidence suite: routing, owner admission, and real agent inference.
|
||
mesh-e2e-confidence:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
cargo build --release -p meridian-agent -p meridian-dev-mcp
|
||
cargo run -p meridian-relay --example mesh_serve_client_smoke
|
||
cargo run -p meridian-relay --example mesh_admission_smoke
|
||
cargo run -p meridian-relay --example mesh_agent_e2e
|
||
|
||
# Take desktop screenshots using the mock bridge
|
||
desktop-screenshot *ARGS:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
pnpm -C {{desktop_dir}} build:e2e
|
||
cd {{desktop_dir}}
|
||
if ! curl -sf http://127.0.0.1:4173/ >/dev/null 2>&1; then
|
||
python3 -m http.server 4173 -d dist >/dev/null 2>&1 &
|
||
trap "kill $! 2>/dev/null || true" EXIT
|
||
for i in $(seq 1 20); do curl -sf http://127.0.0.1:4173/ >/dev/null && break; sleep 0.5; done
|
||
fi
|
||
node tests/helpers/screenshot.mjs {{ARGS}}
|
||
|
||
# ─── Run ──────────────────────────────────────────────────────────────────────
|
||
|
||
# Start the relay server (auto-starts Docker services if needed)
|
||
relay: bootstrap _ensure-migrations
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
# dotenv-load already applied .env. Layer .env.local on top so a relocated
|
||
# REDIS_URL/port here matches what Compose published.
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
cargo run -p meridian-relay
|
||
|
||
# Start the hosted-community control plane (local replacement for the hosted one)
|
||
#
|
||
# Generates a relay-operator keypair on first run and stores it under
|
||
# .control-plane/. The relay must list that pubkey in RELAY_OPERATOR_PUBKEYS or
|
||
# every provisioning call is rejected, so the recipe prints the exact lines to
|
||
# add and refuses to start until they are present.
|
||
control-plane: bootstrap
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
cd {{justfile_directory()}}
|
||
# dotenv-load already applied .env; layer .env.local on top (see `relay`).
|
||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||
|
||
key_dir=".control-plane"
|
||
key_file="$key_dir/operator.key"
|
||
mkdir -p "$key_dir"
|
||
if [[ ! -f "$key_file" ]]; then
|
||
# 32 bytes of hex from the same CSPRNG the OS gives everything else.
|
||
openssl rand -hex 32 > "$key_file"
|
||
chmod 600 "$key_file"
|
||
echo "Generated a new relay-operator key at $key_file"
|
||
fi
|
||
secret="$(tr -d '[:space:]' < "$key_file")"
|
||
pubkey="$(MERIDIAN_CONTROL_RELAY_OPERATOR_SECRET_KEY="$secret" \
|
||
cargo run --quiet -p meridian-control-plane -- --print-operator-pubkey)"
|
||
if [[ -z "$pubkey" ]]; then
|
||
echo "error: could not derive the operator pubkey from $key_file" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# `.env` is the single source both processes read; the relay half must be
|
||
# present before this service can provision anything.
|
||
if ! grep -q "^RELAY_OPERATOR_PUBKEYS=.*$pubkey" .env 2>/dev/null; then
|
||
cat >&2 <<EOF
|
||
|
||
The relay does not yet trust this control plane's operator key.
|
||
Add these two lines to .env, restart \`just relay\`, then re-run:
|
||
|
||
RELAY_OPERATOR_PUBKEYS=$pubkey
|
||
RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
|
||
|
||
EOF
|
||
exit 1
|
||
fi
|
||
|
||
export MERIDIAN_CONTROL_DATABASE_URL="${MERIDIAN_CONTROL_DATABASE_URL:-postgres://meridian:meridian_dev@localhost:5432/meridian_control_plane}"
|
||
export MERIDIAN_CONTROL_PUBLIC_ORIGIN="${MERIDIAN_CONTROL_PUBLIC_ORIGIN:-http://127.0.0.1:8090}"
|
||
export MERIDIAN_CONTROL_RELAY_OPERATOR_API_ORIGIN="${MERIDIAN_CONTROL_RELAY_OPERATOR_API_ORIGIN:-http://127.0.0.1:3000}"
|
||
export MERIDIAN_CONTROL_RELAY_OPERATOR_SECRET_KEY="$secret"
|
||
export MERIDIAN_CONTROL_COMMUNITY_HOST_SUFFIX="${MERIDIAN_CONTROL_COMMUNITY_HOST_SUFFIX:-relays.meridian.localtest.me}"
|
||
export MERIDIAN_CONTROL_COMMUNITY_HOST_PORT="${MERIDIAN_CONTROL_COMMUNITY_HOST_PORT:-3000}"
|
||
export MERIDIAN_CONTROL_RELAY_SCHEME="${MERIDIAN_CONTROL_RELAY_SCHEME:-ws}"
|
||
export MERIDIAN_CONTROL_IDENTITY_PROVIDER="${MERIDIAN_CONTROL_IDENTITY_PROVIDER:-dev}"
|
||
export MERIDIAN_CONTROL_ALLOW_DEV_LOGIN="${MERIDIAN_CONTROL_ALLOW_DEV_LOGIN:-1}"
|
||
export MERIDIAN_CONTROL_RUNTIME_DATABASE_ROLE="${MERIDIAN_CONTROL_RUNTIME_DATABASE_ROLE:-meridian}"
|
||
|
||
cargo run -p meridian-control-plane -- --migrate-only
|
||
# Report the ports actually in effect. These were literals until ./run.sh
|
||
# started relocating the control plane off a busy 8090 — a hardcoded banner
|
||
# then sends you to a port nothing is listening on and reads like a crash.
|
||
base_path="${MERIDIAN_CONTROL_API_BASE_PATH:-/api/meridian}"
|
||
origin="${MERIDIAN_CONTROL_PUBLIC_ORIGIN:-http://127.0.0.1:8090}"
|
||
echo ""
|
||
echo "Control plane: ${origin}${base_path}"
|
||
echo "Operator key: $pubkey"
|
||
echo "Communities: <name>.${MERIDIAN_CONTROL_COMMUNITY_HOST_SUFFIX}:${MERIDIAN_CONTROL_COMMUNITY_HOST_PORT}"
|
||
echo ""
|
||
echo "Point the desktop app at it:"
|
||
echo " MERIDIAN_CONTROL_PLANE_URL=${origin}${base_path} \\"
|
||
echo " MERIDIAN_CONTROL_PLANE_ORIGIN=${origin} just dev"
|
||
echo ""
|
||
echo " (./run.sh desktop --attach exports both for you)"
|
||
echo ""
|
||
cargo run -p meridian-control-plane
|
||
|
||
# Start the relay with the built web UI served from it
|
||
relay-web: bootstrap _ensure-migrations
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
[[ -d node_modules ]] || pnpm install
|
||
pnpm -C REMAPPING/meridian-web build
|
||
MERIDIAN_WEB_DIR=./REMAPPING/meridian-web/dist cargo run -p meridian-relay
|
||
|
||
# Build and run the private read-only admin dashboard
|
||
admin: bootstrap _ensure-migrations
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
[[ -d node_modules ]] || pnpm install
|
||
pnpm -C REMAPPING/meridian-admin-web build
|
||
export MERIDIAN_ADMIN_HOST="${MERIDIAN_ADMIN_HOST:-admin.localhost:3000}"
|
||
export MERIDIAN_ADMIN_WEB_DIR="${MERIDIAN_ADMIN_WEB_DIR:-{{justfile_directory()}}/REMAPPING/meridian-admin-web/dist}"
|
||
echo "Admin dashboard: http://${MERIDIAN_ADMIN_HOST}/reports"
|
||
cargo run -p meridian-relay
|
||
|
||
# Seed deterministic reports and product feedback for local admin dashboard review
|
||
admin-seed: _ensure-migrations
|
||
./scripts/seed-admin-dashboard.sh
|
||
|
||
# Build admin-web and run its browser suite — the operator console's only tests.
|
||
# The build is not optional and must not be split from the run: the Playwright
|
||
# config serves `REMAPPING/meridian-admin-web/dist` through `vite preview`, and `dist/` is
|
||
# gitignored, so on a clean checkout a bare `playwright test` dies with
|
||
# "The directory \"dist\" does not exist" (F087). `pnpm test:e2e` is
|
||
# `pnpm build && playwright test`, which is why this delegates rather than
|
||
# calling playwright directly.
|
||
admin-web-e2e:
|
||
pnpm -C REMAPPING/meridian-admin-web test:e2e
|
||
|
||
# Run focused relay and browser checks for the read-only admin dashboard
|
||
admin-check: fmt-check admin-web-e2e
|
||
cargo check -p meridian-relay --all-targets
|
||
cargo test -p meridian-relay api::admin
|
||
cargo test -p meridian-relay router::tests
|
||
pnpm -C REMAPPING/meridian-admin-web check
|
||
|
||
# Start the relay server in release mode
|
||
relay-release: _ensure-migrations
|
||
cargo run -p meridian-relay --release
|
||
|
||
|
||
# Run the desktop Tauri app in dev mode with a local relay (ports and identity derived from worktree)
|
||
dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
bind_addr="${MERIDIAN_BIND_ADDR:-0.0.0.0:3000}"
|
||
relay_port="${bind_addr##*:}"; [[ -n "$relay_port" ]] || relay_port=3000
|
||
health_port="${MERIDIAN_HEALTH_PORT:-8080}"
|
||
metrics_port="${MERIDIAN_METRICS_PORT:-9102}"
|
||
if command -v lsof >/dev/null 2>&1; then
|
||
for spec in "relay:$relay_port" "health:$health_port" "metrics:$metrics_port"; do
|
||
name="${spec%%:*}"; port="${spec##*:}"
|
||
if lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
|
||
echo "Error: $name port $port is already in use; refusing to launch desktop against a stale relay." >&2
|
||
lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2 || true
|
||
echo "Stop the process above (often a stale meridian-relay) and rerun: just dev" >&2
|
||
exit 1
|
||
fi
|
||
done
|
||
fi
|
||
cargo build -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p meridian-cli -p git-credential-nostr -p meridian-relay
|
||
if [[ -n "{{mesh}}" ]]; then
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
fi
|
||
# Docker Desktop's forwarded MinIO port can stall under the deployment
|
||
# probe's 32 concurrent writers. Keep the gate enabled in local dev, using
|
||
# the bounded profile already used by the relay test launcher.
|
||
export MERIDIAN_GIT_PROBE_WRITERS="${MERIDIAN_GIT_PROBE_WRITERS:-8}"
|
||
export MERIDIAN_GIT_PROBE_ROUNDS="${MERIDIAN_GIT_PROBE_ROUNDS:-2}"
|
||
./target/debug/meridian-relay &
|
||
RELAY_PID=$!
|
||
cleanup() {
|
||
[[ -n "${INSTANCE_ID:-}" ]] && ../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true
|
||
kill "$RELAY_PID" 2>/dev/null || true
|
||
}
|
||
trap cleanup EXIT
|
||
relay_ready=false
|
||
for _ in $(seq 1 120); do
|
||
if ! kill -0 "$RELAY_PID" 2>/dev/null; then
|
||
echo "Error: meridian-relay exited during startup; refusing to launch desktop." >&2
|
||
wait "$RELAY_PID" || true
|
||
exit 1
|
||
fi
|
||
if curl --silent --fail --max-time 1 "http://127.0.0.1:${health_port}/_readiness" >/dev/null; then
|
||
relay_ready=true
|
||
break
|
||
fi
|
||
sleep 0.5
|
||
done
|
||
if [[ "$relay_ready" != true ]]; then
|
||
echo "Error: meridian-relay did not become healthy within 60 seconds; refusing to launch desktop." >&2
|
||
exit 1
|
||
fi
|
||
cd {{desktop_dir}}
|
||
[[ -d node_modules ]] || pnpm install
|
||
source ../scripts/instance-env.sh
|
||
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.MERIDIAN_TAURI_CONFIG).identifier)")
|
||
echo "Starting on Vite port ${MERIDIAN_VITE_PORT}, relay ${MERIDIAN_RELAY_URL}"
|
||
FEATURES=(); [[ -n "{{mesh}}" ]] && FEATURES=(--features mesh-llm)
|
||
if [[ "$(uname -s)" == "Darwin" && -n "${MERIDIAN_DEV_SIGN_IDENTITY:-}" ]]; then
|
||
# Stable dev code signing: OS grants survive rebuilds. See REMAPPING/meridian-desktop/src-tauri/AGENTS.md.
|
||
export "CARGO_TARGET_$(rustc -vV | sed -n 's|host: ||p' | tr 'a-z-' 'A-Z_')_RUNNER={{justfile_directory()}}/REMAPPING/meridian-desktop/src-tauri/scripts/macos-dev-sign-runner.sh"
|
||
fi
|
||
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$MERIDIAN_TAURI_CONFIG" {{ARGS}}
|
||
|
||
# Run only the desktop app. No relay, database, Docker, migrations, or .env are needed.
|
||
# The app opens normally and asks for a community before making a relay connection.
|
||
desktop-standalone *ARGS: _ensure-sidecar-stubs
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
cargo build -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p meridian-cli -p git-credential-nostr
|
||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
||
for bin in meridian-acp meridian-agent meridian-dev-mcp git-credential-nostr meridian; do
|
||
cp "${TARGET_DIR}/debug/${bin}" "REMAPPING/meridian-desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||
chmod +x "REMAPPING/meridian-desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||
done
|
||
cd {{desktop_dir}}
|
||
[[ -d node_modules ]] || pnpm install
|
||
unset MERIDIAN_PRIVATE_KEY MERIDIAN_SHARE_IDENTITY
|
||
if [[ -n "{{fresh}}" ]]; then
|
||
export MERIDIAN_RESET_WEBVIEW_STATE=1
|
||
fi
|
||
source ../scripts/instance-env.sh
|
||
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.MERIDIAN_TAURI_CONFIG).identifier)")
|
||
export MERIDIAN_DEV_KEYRING_SERVICE="meridian-desktop-dev.${MERIDIAN_INSTANCE_SLUG:-main}"
|
||
if [[ -n "{{fresh}}" ]]; then
|
||
../scripts/reset-desktop-standalone-state.sh "$INSTANCE_ID" "$MERIDIAN_DEV_KEYRING_SERVICE"
|
||
fi
|
||
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
||
echo "Starting standalone desktop on Vite port ${MERIDIAN_VITE_PORT}; no relay services were started"
|
||
if [[ "$(uname -s)" == "Darwin" && -n "${MERIDIAN_DEV_SIGN_IDENTITY:-}" ]]; then
|
||
# Stable dev code signing: OS grants survive rebuilds. See REMAPPING/meridian-desktop/src-tauri/AGENTS.md.
|
||
export "CARGO_TARGET_$(rustc -vV | sed -n 's|host: ||p' | tr 'a-z-' 'A-Z_')_RUNNER={{justfile_directory()}}/REMAPPING/meridian-desktop/src-tauri/scripts/macos-dev-sign-runner.sh"
|
||
fi
|
||
pnpm exec tauri dev --config "$MERIDIAN_TAURI_CONFIG" {{ARGS}}
|
||
|
||
# Run the desktop app against the internal staging relay (installs deps + builds agent tools automatically)
|
||
staging *ARGS: bootstrap _ensure-sidecar-stubs
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
pnpm install # unconditional: staging must always start with a clean dep tree
|
||
cargo build --release -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p meridian-cli -p git-credential-nostr
|
||
FEATURES=()
|
||
if [[ -n "{{mesh}}" ]]; then
|
||
FEATURES=(--features mesh-llm)
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
fi
|
||
# Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
|
||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
||
cp "${TARGET_DIR}/release/meridian" "REMAPPING/meridian-desktop/src-tauri/binaries/meridian-${TARGET}"
|
||
chmod +x "REMAPPING/meridian-desktop/src-tauri/binaries/meridian-${TARGET}"
|
||
cd {{desktop_dir}}
|
||
export MERIDIAN_RELAY_URL="wss://relay.meridian.r2d2.office.ilab.zone"
|
||
source ../scripts/instance-env.sh
|
||
# Ctrl+C kills the Tauri app before its in-process sweep finishes, leaking
|
||
# agent workers. Reap this instance's agents on exit as a backstop.
|
||
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.MERIDIAN_TAURI_CONFIG).identifier)")
|
||
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
||
echo "Starting staging on Vite port ${MERIDIAN_VITE_PORT}, relay ${MERIDIAN_RELAY_URL}"
|
||
if [[ "$(uname -s)" == "Darwin" && -n "${MERIDIAN_DEV_SIGN_IDENTITY:-}" ]]; then
|
||
# Stable dev code signing: OS grants survive rebuilds. See REMAPPING/meridian-desktop/src-tauri/AGENTS.md.
|
||
export "CARGO_TARGET_$(rustc -vV | sed -n 's|host: ||p' | tr 'a-z-' 'A-Z_')_RUNNER={{justfile_directory()}}/REMAPPING/meridian-desktop/src-tauri/scripts/macos-dev-sign-runner.sh"
|
||
fi
|
||
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$MERIDIAN_TAURI_CONFIG" {{ARGS}}
|
||
|
||
# Run the desktop app against the production relay (installs deps + builds agent tools automatically)
|
||
production *ARGS: bootstrap _ensure-sidecar-stubs
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
pnpm install # unconditional: production must always start with a clean dep tree
|
||
cargo build --release -p meridian-acp -p meridian-agent -p meridian-dev-mcp -p meridian-cli -p git-credential-nostr
|
||
FEATURES=()
|
||
if [[ -n "{{mesh}}" ]]; then
|
||
FEATURES=(--features mesh-llm)
|
||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||
fi
|
||
# Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
|
||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
||
cp "${TARGET_DIR}/release/meridian" "REMAPPING/meridian-desktop/src-tauri/binaries/meridian-${TARGET}"
|
||
chmod +x "REMAPPING/meridian-desktop/src-tauri/binaries/meridian-${TARGET}"
|
||
cd {{desktop_dir}}
|
||
export MERIDIAN_RELAY_URL="wss://meridian.block.builderlab.xyz"
|
||
source ../scripts/instance-env.sh
|
||
# Ctrl+C kills the Tauri app before its in-process sweep finishes, leaking
|
||
# agent workers. Reap this instance's agents on exit as a backstop.
|
||
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.MERIDIAN_TAURI_CONFIG).identifier)")
|
||
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
||
echo "Starting production on Vite port ${MERIDIAN_VITE_PORT}, relay ${MERIDIAN_RELAY_URL}"
|
||
if [[ "$(uname -s)" == "Darwin" && -n "${MERIDIAN_DEV_SIGN_IDENTITY:-}" ]]; then
|
||
# Stable dev code signing: OS grants survive rebuilds. See REMAPPING/meridian-desktop/src-tauri/AGENTS.md.
|
||
export "CARGO_TARGET_$(rustc -vV | sed -n 's|host: ||p' | tr 'a-z-' 'A-Z_')_RUNNER={{justfile_directory()}}/REMAPPING/meridian-desktop/src-tauri/scripts/macos-dev-sign-runner.sh"
|
||
fi
|
||
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$MERIDIAN_TAURI_CONFIG" {{ARGS}}
|
||
|
||
# Run the desktop frontend dev server (port derived from worktree)
|
||
desktop-dev:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
cd {{desktop_dir}}
|
||
[[ -d node_modules ]] || pnpm install
|
||
source ../scripts/instance-env.sh
|
||
echo "Starting frontend dev server on Vite port ${MERIDIAN_VITE_PORT}, relay ${MERIDIAN_RELAY_URL}"
|
||
pnpm exec vite --port "${MERIDIAN_VITE_PORT}" --strictPort
|
||
|
||
# ─── Web ─────────────────────────────────────────────────────────────────────
|
||
|
||
# Run the web frontend dev server (port derived from worktree to avoid collisions)
|
||
web:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
[[ -d node_modules ]] || pnpm install
|
||
source scripts/instance-env.sh
|
||
export VITE_PORT=$((MERIDIAN_VITE_PORT + 100))
|
||
export VITE_RELAY_URL="${MERIDIAN_RELAY_URL}"
|
||
echo "Starting web dev server on port ${VITE_PORT}, relay ${MERIDIAN_RELAY_URL}"
|
||
cd {{web_dir}}
|
||
pnpm exec vite --port "${VITE_PORT}" --strictPort
|
||
|
||
# Run web lint and format checks
|
||
web-check:
|
||
cd {{web_dir}} && pnpm check
|
||
|
||
# Fix web lint and format issues
|
||
web-fix:
|
||
cd {{web_dir}} && pnpm exec biome check --write . && pnpm check:file-sizes
|
||
|
||
# Run web TypeScript checks
|
||
web-typecheck:
|
||
cd {{web_dir}} && pnpm typecheck
|
||
|
||
# Build web frontend assets
|
||
web-build:
|
||
cd {{web_dir}} && pnpm build
|
||
|
||
# Run web browser smoke tests
|
||
web-e2e-smoke:
|
||
cd {{web_dir}} && pnpm test:e2e:smoke
|
||
|
||
# ─── Mobile ──────────────────────────────────────────────────────────────────
|
||
|
||
mobile_dir := "REMAPPING/meridian-mobile"
|
||
|
||
# Install mobile Flutter dependencies
|
||
mobile-install:
|
||
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter pub get
|
||
|
||
# Format all Dart code
|
||
mobile-fmt:
|
||
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format .
|
||
|
||
# Fix mobile formatting and run analysis
|
||
mobile-fix:
|
||
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format . && flutter analyze
|
||
|
||
# Run mobile lint and format checks
|
||
mobile-check:
|
||
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format --output=none --set-exit-if-changed . && flutter analyze && node ./scripts/check-file-sizes.mjs
|
||
|
||
# Run mobile tests
|
||
mobile-test:
|
||
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter test
|
||
|
||
# Compile an unsigned Android debug APK (worktree-aware debug identity)
|
||
mobile-build-android:
|
||
./scripts/mobile-worktree-overrides.sh
|
||
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter build apk --debug --no-pub
|
||
|
||
# Run the mobile app on iOS simulator (worktree-aware debug identity)
|
||
mobile-dev:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
if ! pgrep -x Simulator &>/dev/null; then
|
||
open -a Simulator
|
||
sleep 3
|
||
fi
|
||
./scripts/mobile-worktree-overrides.sh
|
||
cd {{mobile_dir}}
|
||
unset GIT_DIR GIT_WORK_TREE
|
||
flutter run
|
||
|
||
# Uninstall stale worktree-suffixed Meridian debug installs (production apps kept)
|
||
mobile-clean:
|
||
./scripts/mobile-worktree-clean.sh
|
||
|
||
# ─── Database ─────────────────────────────────────────────────────────────────
|
||
|
||
# Apply database migrations
|
||
migrate: _ensure-migrations
|
||
|
||
# ─── Utilities ────────────────────────────────────────────────────────────────
|
||
|
||
# Remove build artifacts
|
||
clean:
|
||
cargo clean
|
||
cargo clean --manifest-path REMAPPING/meridian-desktop/src-tauri/Cargo.toml
|
||
|
||
# Reclaim incremental-cache disk in both target trees without a cold rebuild
|
||
target-sweep *ARGS:
|
||
./scripts/target-sweep.sh {{ARGS}}
|
||
|
||
# Check the Rust workspace compiles without producing binaries
|
||
check-compile:
|
||
cargo check --workspace --all-targets
|
||
|
||
# ─── Release ─────────────────────────────────────────────────────────────────
|
||
|
||
# Read the current desktop version from package.json
|
||
get-current-version:
|
||
@node -p "require('./REMAPPING/meridian-desktop/package.json').version"
|
||
|
||
# Read the current relay version from its crate manifest
|
||
get-current-relay-version:
|
||
@grep -m1 '^version = ' crates/meridian-relay/Cargo.toml | sed -E 's/version = "(.*)"/\1/'
|
||
|
||
# Compute next minor version (e.g., 0.3.0 → 0.4.0)
|
||
get-next-minor-version:
|
||
@python3 -c "v='$(just get-current-version)'.split('.'); print(f'{v[0]}.{int(v[1])+1}.0')"
|
||
|
||
# Compute next patch version (e.g., 0.3.0 → 0.3.1)
|
||
get-next-patch-version:
|
||
@python3 -c "v='$(just get-current-version)'.split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')"
|
||
|
||
# Compute next relay patch version (e.g., 0.3.0 → 0.3.1)
|
||
get-next-relay-patch-version:
|
||
@python3 -c "v='$(just get-current-relay-version)'.split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')"
|
||
|
||
# Update version in desktop package manifests and regenerate lockfiles
|
||
bump-desktop-version version:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
# REMAPPING/meridian-desktop/package.json
|
||
(cd REMAPPING/meridian-desktop && npm pkg set "version={{ version }}")
|
||
# REMAPPING/meridian-desktop/src-tauri/tauri.conf.json
|
||
node -e "
|
||
const fs = require('fs');
|
||
const p = 'REMAPPING/meridian-desktop/src-tauri/tauri.conf.json';
|
||
const c = JSON.parse(fs.readFileSync(p, 'utf8'));
|
||
c.version = '{{ version }}';
|
||
fs.writeFileSync(p, JSON.stringify(c, null, 2) + '\n');
|
||
"
|
||
# JSON.stringify expands arrays/objects in a way biome rejects; reformat to match.
|
||
(cd REMAPPING/meridian-desktop && pnpm exec biome format --write src-tauri/tauri.conf.json)
|
||
# REMAPPING/meridian-desktop/src-tauri/Cargo.toml — only first version line (under [package])
|
||
node -e "
|
||
const fs = require('fs');
|
||
const p = 'REMAPPING/meridian-desktop/src-tauri/Cargo.toml';
|
||
let t = fs.readFileSync(p, 'utf8');
|
||
t = t.replace(/^version = \".*\"/m, 'version = \"{{ version }}\"');
|
||
fs.writeFileSync(p, t);
|
||
"
|
||
# Regenerate lockfiles
|
||
pnpm install --lockfile-only
|
||
cargo update -p meridian-desktop --manifest-path REMAPPING/meridian-desktop/src-tauri/Cargo.toml
|
||
echo "Bumped desktop manifests to {{ version }} and regenerated lockfiles"
|
||
|
||
# Bump the relay crate version and regenerate the lockfile
|
||
bump-relay-version version:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
# meridian-relay carries its own `version =` (not version.workspace), so the
|
||
# replace targets the package version line only.
|
||
perl -i -pe 's/^version = ".*"/version = "{{ version }}"/' crates/meridian-relay/Cargo.toml
|
||
cargo update -p meridian-relay
|
||
echo "Bumped meridian-relay to {{ version }} and regenerated Cargo.lock"
|
||
|
||
# Open or update the desktop release PR (signed desktop app)
|
||
release-desktop *ARGS:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
ARG="{{ ARGS }}"
|
||
if [[ -z "$ARG" || "$ARG" == "patch" ]]; then
|
||
VERSION=$(just get-next-patch-version)
|
||
else
|
||
VERSION="$ARG"
|
||
fi
|
||
just _release-pr desktop "$VERSION"
|
||
|
||
# Open or update the relay release PR (registry.r2d2.office.ilab.zone/meridian image)
|
||
release-relay *ARGS:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
ARG="{{ ARGS }}"
|
||
if [[ -z "$ARG" || "$ARG" == "patch" ]]; then
|
||
VERSION=$(just get-next-relay-patch-version)
|
||
else
|
||
VERSION="$ARG"
|
||
fi
|
||
just _release-pr relay "$VERSION"
|
||
|
||
# Shared release-PR engine for desktop and relay. Mobile publishes immutable
|
||
# candidate tags directly from remote main instead of using metadata-only PRs.
|
||
_release-pr lane version:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
VERSION="{{ version }}"
|
||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
||
echo "Error: '$VERSION' is not valid semver (expected X.Y.Z)"
|
||
exit 1
|
||
fi
|
||
# Lane-specific identifiers. The bump command runs after the branch switch.
|
||
case "{{ lane }}" in
|
||
desktop)
|
||
BRANCH_PREFIX="version-bump"
|
||
TAG_FETCH='v*'
|
||
TAG_MATCH='v[0-9]*'
|
||
TAG_EXCLUDE='*-*'
|
||
TAG_PREFIX="v"
|
||
CHANGELOG="CHANGELOG.md"
|
||
ADD_FILES=(REMAPPING/meridian-desktop/package.json REMAPPING/meridian-desktop/src-tauri/tauri.conf.json REMAPPING/meridian-desktop/src-tauri/Cargo.toml REMAPPING/meridian-desktop/src-tauri/Cargo.lock pnpm-lock.yaml CHANGELOG.md)
|
||
LOG_PATHS=(REMAPPING/meridian-desktop/ crates/meridian-core/ crates/meridian-persona/ crates/meridian-sdk/ crates/meridian-agent/)
|
||
ARTIFACT="Meridian Desktop" ;;
|
||
relay)
|
||
BRANCH_PREFIX="relay-release"
|
||
TAG_FETCH='relay-v*'
|
||
TAG_MATCH='relay-v[0-9]*'
|
||
TAG_EXCLUDE='relay-v*-*'
|
||
TAG_PREFIX="relay-v"
|
||
CHANGELOG="crates/meridian-relay/CHANGELOG.md"
|
||
ADD_FILES=(crates/meridian-relay/Cargo.toml Cargo.lock crates/meridian-relay/CHANGELOG.md)
|
||
LOG_PATHS=(crates/meridian-relay/ crates/meridian-core/ crates/meridian-db/ crates/meridian-auth/ crates/meridian-pubsub/ crates/meridian-search/ crates/meridian-audit/ crates/meridian-media/ crates/meridian-sdk/ crates/meridian-workflow/ crates/meridian-conformance/ migrations/)
|
||
ARTIFACT="Meridian Relay" ;;
|
||
*)
|
||
echo "Error: unknown release lane '{{ lane }}'"
|
||
exit 1 ;;
|
||
esac
|
||
echo "Preparing ${ARTIFACT} release v${VERSION}..."
|
||
# Must run on main with a clean, up-to-date tree.
|
||
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
|
||
if [[ "$CURRENT_BRANCH" != "main" ]]; then
|
||
echo "Error: must be on main branch (currently on '$CURRENT_BRANCH')"
|
||
exit 1
|
||
fi
|
||
git fetch origin refs/heads/main:refs/remotes/origin/main --no-tags
|
||
# Release tags are remote-owned state; sync only this lane's tags so stale
|
||
# local tags from older histories do not make release preflight fail.
|
||
git fetch origin "+refs/tags/${TAG_FETCH}:refs/tags/${TAG_FETCH}"
|
||
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
|
||
echo "Error: local main is not up-to-date with origin/main. Run 'git pull' first."
|
||
exit 1
|
||
fi
|
||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||
echo "Error: working tree is dirty. Commit or stash changes first."
|
||
exit 1
|
||
fi
|
||
# Switch to the release branch (create, or reset to main if it exists).
|
||
BRANCH="${BRANCH_PREFIX}/${VERSION}"
|
||
if git rev-parse --verify "refs/heads/$BRANCH" >/dev/null 2>&1; then
|
||
echo "Branch '$BRANCH' already exists — resetting to origin/main..."
|
||
git switch "$BRANCH"
|
||
git reset --hard origin/main
|
||
elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
|
||
echo "Branch '$BRANCH' exists on remote — checking out and resetting to origin/main..."
|
||
git switch -c "$BRANCH" --track "origin/$BRANCH"
|
||
git reset --hard origin/main
|
||
else
|
||
git switch -c "$BRANCH"
|
||
fi
|
||
# Lane-specific bump (the one diverging step).
|
||
case "{{ lane }}" in
|
||
desktop) just bump-desktop-version "$VERSION" ;;
|
||
relay) just bump-relay-version "$VERSION" ;;
|
||
esac
|
||
# Generate the changelog from commits since this lane's last release tag.
|
||
LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_MATCH" --exclude "$TAG_EXCLUDE" 2>/dev/null || echo "")
|
||
REPO=$(git remote get-url origin | sed -E 's|.*github\.com[:/]||; s|\.git$||')
|
||
format_log() {
|
||
local range="$1"
|
||
git log "$range" --format="%h %H %s" --no-merges -- "${LOG_PATHS[@]}" | while IFS=' ' read -r short full rest; do
|
||
local pr subject
|
||
pr=$(printf '%s' "$rest" | grep -oE '\(#[0-9]+\)$' | grep -oE '[0-9]+' || true)
|
||
if [[ -n "$pr" ]]; then
|
||
subject=$(printf '%s' "$rest" | sed -E 's/ \(#[0-9]+\)$//')
|
||
printf -- '- %s ([#%s](https://github.com/%s/pull/%s)) ([`%s`](https://github.com/%s/commit/%s))\n' \
|
||
"$subject" "$pr" "$REPO" "$pr" "$short" "$REPO" "$full"
|
||
else
|
||
printf -- '- %s ([`%s`](https://github.com/%s/commit/%s))\n' \
|
||
"$rest" "$short" "$REPO" "$full"
|
||
fi
|
||
done
|
||
}
|
||
TMPFILE=$(mktemp)
|
||
{
|
||
echo "# Changelog"
|
||
echo ""
|
||
echo "## ${TAG_PREFIX}${VERSION}"
|
||
echo ""
|
||
if [[ -n "$LAST_TAG" ]]; then
|
||
format_log "${LAST_TAG}..HEAD"
|
||
else
|
||
echo "- Initial release"
|
||
fi
|
||
echo ""
|
||
if [[ -f "$CHANGELOG" ]]; then
|
||
tail -n +2 "$CHANGELOG"
|
||
fi
|
||
} > "$TMPFILE"
|
||
mkdir -p "$(dirname "$CHANGELOG")"
|
||
mv "$TMPFILE" "$CHANGELOG"
|
||
# Commit.
|
||
git add "${ADD_FILES[@]}"
|
||
RELEASE_MSG="chore(release): release ${ARTIFACT} version ${VERSION}"
|
||
if [[ "$(git log -1 --format='%s' 2>/dev/null)" == "$RELEASE_MSG" ]]; then
|
||
git commit --amend --no-edit
|
||
else
|
||
git commit -m "$RELEASE_MSG"
|
||
fi
|
||
# Push and open/update the PR.
|
||
git push --force-with-lease -u origin "$BRANCH"
|
||
PR_BODY="## ${ARTIFACT} release v${VERSION}"$'\n\n'
|
||
if [[ -n "$LAST_TAG" ]]; then
|
||
PR_BODY+="### Changes since ${LAST_TAG}:"$'\n\n'
|
||
CHANGELOG_BODY=$(format_log "${LAST_TAG}..HEAD~1")
|
||
MAX_LOG=62000
|
||
if (( ${#CHANGELOG_BODY} > MAX_LOG )); then
|
||
TRUNCATED=$(printf '%s' "$CHANGELOG_BODY" | awk -v max="$MAX_LOG" \
|
||
'BEGIN{n=0} {line_len=length($0)+1; if(n+line_len>max) exit; n+=line_len; print}')
|
||
SHOWN=$(printf '%s\n' "$TRUNCATED" | grep -c '^-' || true)
|
||
TOTAL=$(printf '%s\n' "$CHANGELOG_BODY" | grep -c '^-' || true)
|
||
SKIPPED=$(( TOTAL - SHOWN ))
|
||
CHANGELOG_BODY="${TRUNCATED}"$'\n'"_… and ${SKIPPED} more commits — [compare ${LAST_TAG}…${TAG_PREFIX}${VERSION}](https://github.com/${REPO}/compare/${LAST_TAG}...${TAG_PREFIX}${VERSION})_"
|
||
fi
|
||
PR_BODY+="${CHANGELOG_BODY}"$'\n\n'
|
||
else
|
||
PR_BODY+="Initial release."$'\n\n'
|
||
fi
|
||
PR_BODY+="**To release:** merge this PR. The tag and build will happen automatically."
|
||
PR_TITLE="chore(release): release ${ARTIFACT} version ${VERSION}"
|
||
EXISTING_PR=$(gh pr list --head "$BRANCH" --json url --jq '.[0].url' 2>/dev/null || true)
|
||
if [[ -n "$EXISTING_PR" ]]; then
|
||
gh pr edit "$BRANCH" --title "$PR_TITLE" --body "$PR_BODY"
|
||
PR_URL="$EXISTING_PR"
|
||
echo ""
|
||
echo "Updated existing release PR: ${PR_URL}"
|
||
else
|
||
PR_URL=$(gh pr create --title "$PR_TITLE" --body "$PR_BODY")
|
||
echo ""
|
||
echo "Release PR opened: ${PR_URL}"
|
||
fi
|
||
echo "Merge it to trigger the release build."
|
||
|
||
# ─── Agent Harness ────────────────────────────────────────────────────────────
|
||
|
||
# Run a goose agent connected to a Meridian relay (foreground)
|
||
goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$MERIDIAN_PRIVATE_KEY":
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
source ./scripts/_goose-env.sh "{{relay}}" "{{key}}" "{{agents}}" "{{heartbeat}}" "{{prompt}}"
|
||
exec env "${env_args[@]}" ./target/release/meridian-acp
|
||
|
||
# Run a goose agent in the background (screen session named 'goose-agent-N')
|
||
goose-bg relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$MERIDIAN_PRIVATE_KEY":
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
source ./scripts/_goose-env.sh "{{relay}}" "{{key}}" "{{agents}}" "{{heartbeat}}" "{{prompt}}"
|
||
screen -dmS goose-agent-{{agents}} bash -c "$(printf '%q ' env "${env_args[@]}") ./target/release/meridian-acp"
|
||
echo "Agent running in screen session 'goose-agent-{{agents}}'. Attach with: screen -r goose-agent-{{agents}}"
|
||
|
||
# ─── Benchmarking ─────────────────────────────────────────────────────────────
|
||
|
||
# Run the Meridian orchestra benchmark — leaderboard-eligible by default (TB 2.1, k=5, Sonnet+Haiku). Stands up its own Docker stack; --gui opens a live spectator desktop app; other flags pass to benchmark.py (--dataset/--path, --include-task, --attempts, --manifest, --dry-run, ...)
|
||
benchmark *ARGS:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
uv run --project benchmarks/harbor-meridian-orchestra/testbed \
|
||
benchmarks/harbor-meridian-orchestra/scripts/benchmark.py {{ARGS}}
|
||
|
||
# Stop the benchmark Docker stack (state and channels are kept)
|
||
benchmark-down:
|
||
docker compose --project-name meridian-benchmark down
|
||
|
||
# Run the isolated, calibrated threaded-event PostgreSQL persistence gate.
|
||
reply-persistence-benchmark:
|
||
REPLY_BENCH_ALLOW_DATABASE_MUTATION=1 scripts/benchmark-reply-path.sh
|
||
|
||
# Verify benchmark safety, durability, invariant, and threshold wiring without Docker.
|
||
reply-persistence-benchmark-contract:
|
||
scripts/test-benchmark-reply-path.sh
|
||
|
||
# Run the criterion benches that back every published throughput figure.
|
||
#
|
||
# Before this recipe, `cargo bench` appeared in no recipe and no CI workflow
|
||
# (meridian-wpqw), while README.md, AGENTS.md and ARCHITECTURE.md all cite
|
||
# `event_cost.rs` for the verification ceiling and for the 125x/405x MAC ratios
|
||
# derived from it. AGENTS.md instructs "re-run the bench rather than trusting
|
||
# the extrapolation when the number matters" — and there was nothing to run.
|
||
#
|
||
# The profile header is the point, not decoration. The root Design Law refuses a
|
||
# throughput figure published without its traffic class, auth profile, and
|
||
# whether the payload is parsed and stored; printing those beside the numbers is
|
||
# what stops a bare figure being copied out of this output into a document.
|
||
#
|
||
# Pass criterion args through directly — `just bench schnorr_verify_nostr` to
|
||
# filter, `just bench --quick` for a fast smoke. Do NOT write `just bench --
|
||
# --quick`: this recipe already supplies the `--` that separates cargo's args
|
||
# from criterion's, and a second one reaches criterion as a positional filter
|
||
# ("error: unexpected argument found").
|
||
#
|
||
# `--quick` reads HIGH on this suite — measured ~34-40 Kelem/s on
|
||
# schnorr_verify_nostr against ~21-33 for full runs — so it is a smoke test that
|
||
# the benches still execute, and never a figure to publish.
|
||
#
|
||
# HOST LOAD IS THE DOMINANT VARIABLE, and it used to be missing from the profile
|
||
# entirely. Measured on one unchanged tree, `schnorr_verify_nostr/64`:
|
||
#
|
||
# load 1m ~9.8 42.6 Kelem/s load 24-43 15.5 Kelem/s
|
||
# load 20-24 27.5 Kelem/s load 43-81 21.2 Kelem/s
|
||
#
|
||
# A 2.75× swing with no code change (meridian-9fqe). This host runs 38
|
||
# containers for neighbouring projects, so a contended reading is the NORMAL
|
||
# case here, not the exception — which is why the recipe now measures load
|
||
# rather than asking a human to remember. A run that is not marked PUBLISHABLE
|
||
# below must not have its numbers copied into any document.
|
||
bench *ARGS:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export PATH="{{justfile_directory()}}/bin:$PATH"
|
||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||
ncpu=$(sysctl -n hw.ncpu)
|
||
else
|
||
ncpu=$(nproc)
|
||
fi
|
||
# 1-minute load average, portably: `uptime` is the one source present on
|
||
# both platforms, and its "load average:"/"load averages:" spelling differs.
|
||
load_start=$(uptime | sed 's/.*load averages*: *//' | awk '{print $1}' | tr -d ,)
|
||
echo "── Benchmark profile ──────────────────────────────────────────────"
|
||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||
echo " CPU: $(sysctl -n machdep.cpu.brand_string)"
|
||
echo " Cores: ${ncpu} logical, $(sysctl -n hw.perflevel0.logicalcpu 2>/dev/null || echo '?') performance"
|
||
echo " OS: macOS $(sw_vers -productVersion)"
|
||
else
|
||
echo " CPU: $(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' || echo unknown)"
|
||
echo " Cores: ${ncpu} logical"
|
||
echo " OS: $(uname -sr)"
|
||
fi
|
||
echo " Build: release (criterion), single-threaded, one core"
|
||
echo " Load: ${load_start} at start (1m avg, ${ncpu} cores) — verdict below"
|
||
echo ""
|
||
echo " event_cost traffic class: signed NIP-01 ingest"
|
||
echo " auth: full BIP-340 per event · parsed: yes · stored: no"
|
||
echo " fanout_cost traffic class: NIP-01 fan-out to N subscribers"
|
||
echo " auth: none on this leg (verified upstream)"
|
||
echo " parsed: yes, re-serialized per frame · stored: no"
|
||
echo ""
|
||
echo " A figure from this run is publishable only with the lines above"
|
||
echo " attached, and as an interval no narrower than the spread you see"
|
||
echo " across repeated runs — one run is not a measurement (meridian-9fqe)."
|
||
echo "───────────────────────────────────────────────────────────────────"
|
||
echo ""
|
||
cargo bench -p meridian-core --bench event_cost -- {{ARGS}}
|
||
cargo bench -p meridian-relay --bench fanout_cost -- {{ARGS}}
|
||
# The verdict goes AFTER the numbers, deliberately: it is the last thing on
|
||
# screen when someone is about to copy a figure out of this output.
|
||
load_end=$(uptime | sed 's/.*load averages*: *//' | awk '{print $1}' | tr -d ,)
|
||
echo ""
|
||
echo "── Publishability ─────────────────────────────────────────────────"
|
||
verdict=$(awk -v a="$load_start" -v b="$load_end" -v n="$ncpu" 'BEGIN{
|
||
m = (a > b ? a : b);
|
||
printf "%s %.2f %.2f", (m/n <= 1.0 ? "PUBLISHABLE" : "CONTENDED"), m, m/n }')
|
||
state=${verdict%% *}; rest=${verdict#* }; peak=${rest%% *}; per=${rest#* }
|
||
echo " Load: ${load_start} start → ${load_end} end, peak ${peak} over ${ncpu} cores (${per}/core)"
|
||
if [[ "$state" == "PUBLISHABLE" ]]; then
|
||
echo " Verdict: PUBLISHABLE — the machine was not oversubscribed."
|
||
echo " Still needs N runs; one run is not a measurement."
|
||
else
|
||
echo " Verdict: CONTENDED — DO NOT PUBLISH THESE NUMBERS."
|
||
echo " Load exceeded one runnable thread per core, which"
|
||
echo " moved schnorr_verify_nostr by 2.75× on this host with"
|
||
echo " no code change. Re-run on a quiet machine."
|
||
if [[ "${MERIDIAN_BENCH_REQUIRE_QUIET:-0}" == "1" ]]; then
|
||
echo " MERIDIAN_BENCH_REQUIRE_QUIET=1 — failing."
|
||
exit 1
|
||
fi
|
||
fi
|
||
echo "───────────────────────────────────────────────────────────────────"
|