# stream — zig jetstream default: test build: zig build test: zig build test zig build # Run three production processes without external network access and prove the # canonical split listener, disabled-debug, and historical combined surfaces. listener-contract: zig build -Doptimize=ReleaseSafe python3 tests/listener_contract.py # Offline: real SIGTERM, WebSocket 1001 handshakes, cooperative early exit, # and multiple silent peers forced by one shared client-drain deadline. shutdown-contract: zig build -Doptimize=ReleaseSafe python3 tests/shutdown_contract.py # Offline: canonical JSON/text handlers, runtime level filtering, persistent # root-flag placement, and fail-closed invalid logging configuration. logging-contract: zig build -Doptimize=ReleaseSafe python3 tests/logging_contract.py # Offline: exact pinned JETSTREAM_* source set, unknown-variable rejection, # source precedence, root/inspect inheritance, and typed serve parsing. environment-contract: zig build -Doptimize=ReleaseSafe STREAM_UPSTREAM_REPO='{{upstream}}' python3 tests/environment_contract.py # Offline: validate the vendored upstream Grafana dashboard and its parser. dashboard-test: python3 tools/adapt_dashboard.py --check # Run the file, not `-m unittest `: that form resolves the path to a # module name, and tests/ is not a package, so whether it imports depends on # the python version. 3.14 accepts it, the gate host's 3.10 does not. python3 tests/test_dashboard_contract.py # METRICS may be a saved scrape, localhost URL, or '-' for stdin. dashboard-contract METRICS: python3 tools/check_dashboard.py --metrics {{METRICS}} # Launch the production binary offline and prove its process metrics are real, # complete, and monotonic across independent HTTP scrapes. process-metrics-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_PROCESS_METRICS_PORT:-6020} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --no-verify >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done STREAM_BASE_URL="http://127.0.0.1:$PORT" python3 tests/process_metrics_contract.py python3 tools/check_dashboard.py --metrics "http://127.0.0.1:$PORT/metrics" # Seed a real sealed archive and prove the upstream HTTP middleware and # getBlock metric families against actual HTTP and WebSocket connections. http-metrics-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_HTTP_METRICS_PORT:-6021} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --archive "$DATA" ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --segment-cache-max-age=1500ms --no-verify >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done STREAM_BASE_URL="http://127.0.0.1:$PORT" STREAM_DATA_DIR="$DATA" python3 tests/http_metrics_contract.py # Seed a real sealed archive, serve it, then exercise all four archive XRPCs # plus whole-segment, block-plan, bounded, and live-cutover paths through the # exact pinned upstream Go client. Go and uv are forced offline; Zig's pinned # dependencies must already be present in its normal global cache. archive-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_ARCHIVE_PORT:-6018} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --archive "$DATA" ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --no-verify >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done curl -fsS "http://127.0.0.1:$PORT/healthz" >/dev/null kill -0 "$pid" STREAM_BASE_URL="http://127.0.0.1:$PORT" STREAM_DATA_DIR="$DATA" \ uv run --offline tests/backfill_api.py STREAM_BASE_URL="http://127.0.0.1:$PORT" \ STREAM_UPSTREAM_REPO='{{upstream}}' python3 tests/archive_client_e2e.py # Exercise all four non-default planBackfill controls through three real # production processes over a purpose-built, physically sealed JSS archive. plan-config-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_PLAN_CONFIG_PORT:-6022} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --plan-config-archive "$DATA" run_case() { local mode=$1; shift ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --no-verify "$@" >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done STREAM_BASE_URL="http://127.0.0.1:$PORT" STREAM_PLAN_CONFIG_MODE="$mode" \ python3 tests/plan_config_contract.py kill "$pid"; wait "$pid"; unset pid } run_case limited --plan-max-dids=1 --plan-max-collections=1 \ --plan-max-entries=1 --plan-whole-segment-threshold=1 run_case whole --plan-max-dids=1 --plan-max-collections=1 \ --plan-max-entries=0 --plan-whole-segment-threshold=0.75 run_case disabled --plan-max-dids=0 --plan-max-collections=0 \ --plan-max-entries=0 --plan-whole-segment-threshold=0.75 if ./zig-out/bin/stream --plan-whole-segment-threshold=0 \ --data-dir="$DATA" >"$LOG" 2>&1; then echo "zero planner threshold unexpectedly started" >&2; exit 1 fi # Reproduce the 2026-08-08 wedge: cursor subscriber connects against an empty # hot tail, drains cold, then MUST follow the live tip once ingest dials. seam-handoff-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_SEAM_PORT:-6031} SIMPORT=${STREAM_SEAM_SIM_PORT:-17931} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --archive "$DATA" ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:$SIMPORT --plc=http://127.0.0.1:$SIMPORT \ --relay-http=http://127.0.0.1:$SIMPORT --compaction-interval=0 \ --retry-interval=0 --no-verify >"$LOG" 2>&1 & pid=$! for _ in $(seq 1 50); do if body=$(curl -sf "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi sleep 0.2 done STREAM_SEAM_PORT=$PORT STREAM_SEAM_SIM_PORT=$SIMPORT \ STREAM_SEAM_UPSTREAM_DIR={{upstream}} \ python3 tests/seam_handoff_contract.py || { tail -30 "$LOG"; exit 1; } # Prove default, widened, and disabled replay windows over real sealed JSS via # pre-upgrade HTTP and actual WebSocket delivery. cursor-lookback-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_CURSOR_LOOKBACK_PORT:-6023} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --archive "$DATA" run_case() { local mode=$1; shift ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --no-verify "$@" >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done STREAM_CURSOR_LOOKBACK_PORT="$PORT" STREAM_CURSOR_LOOKBACK_MODE="$mode" \ python3 tests/cursor_lookback_contract.py kill "$pid"; wait "$pid"; unset pid } run_case default run_case wide --cursor-lookback=100000h run_case disabled --cursor-lookback=0 # Prove that both upstream compaction controls reach the physical rewrite pass. # cap=1 creates two durable chunks; cap=0 is the upstream unlimited sentinel. compaction-config-contract: #!/usr/bin/env bash set -euo pipefail ROOT=$(mktemp -d) PORT=${STREAM_COMPACTION_CONFIG_PORT:-6024} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$ROOT" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe run_case() { local name=$1 cap=$2 workers=$3 chunks=$4 interval=$5 local data="$ROOT/$name" log="$ROOT/$name.log" zig build write-sample -- --compaction-config-archive "$data" ./zig-out/bin/stream --port="$PORT" --data-dir="$data" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --retry-interval=0 --no-verify \ --compaction-interval="$interval" --compaction-tombstone-cap="$cap" \ --compaction-rewrite-workers="$workers" >"$log" 2>&1 & pid=$! STREAM_COMPACTION_CONFIG_PORT="$PORT" \ STREAM_COMPACTION_CONFIG_CHUNKS="$chunks" \ STREAM_COMPACTION_CONFIG_WORKERS="$workers" \ STREAM_COMPACTION_CONFIG_LOG="$log" \ python3 tests/compaction_config_contract.py kill "$pid"; wait "$pid"; unset pid } run_case bounded 1 1 2 1s run_case unlimited 0 2 1 1s run_case subsecond 0 2 1 250ms # Exercise retry admission and persisted backoff through real HTTP, RocksDB, # the production retry engine, and the ReleaseSafe daemon. retry-config-contract: #!/usr/bin/env bash set -euo pipefail zig build -Doptimize=ReleaseSafe python3 tests/retry_config_contract.py # Route upstream's configurable hot-log and slow-client policy through the # real ReleaseSafe process. Physical eviction and detector behavior are # asserted in the Zig suite; this receipt closes the CLI-to-runtime seam. subscribe-config-contract: #!/usr/bin/env bash set -euo pipefail ROOT=$(mktemp -d) PORT=${STREAM_SUBSCRIBE_CONFIG_PORT:-6027} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$ROOT" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe run_case() { local name=$1 expected=$2 shift 2 local data="$ROOT/$name" log="$ROOT/$name.log" ./zig-out/bin/stream --port="$PORT" --data-dir="$data" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --retry-interval=0 --no-verify \ "$@" >"$log" 2>&1 & pid=$! for _ in $(seq 1 200); do if grep -Fq "$expected" "$log"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$log" >&2; exit 1; fi sleep 0.05 done grep -F "$expected" "$log" kill "$pid"; wait "$pid"; unset pid } run_case explicit 'read-log-retention=12345 bytes block-cache=23456 bytes read-batch=17 slow-window=250000000ns slow-min-rate=7.5' \ --subscribe-read-log-retention-bytes=12345 --subscribe-slow-window=250ms \ --subscribe-slow-min-rate=7.5 --subscribe-block-cache-bytes=23456 --subscribe-read-batch=17 \ --cursor-block-index-cache-size=99 run_case zero 'read-log-retention=268435456 bytes block-cache=67108864 bytes read-batch=1024 slow-window=60000000000ns slow-min-rate=5' \ --subscribe-read-log-retention-bytes=0 --subscribe-slow-window=0 \ --subscribe-slow-min-rate=0 --subscribe-block-cache-bytes=0 --subscribe-read-batch=0 \ --cursor-block-index-cache-size=0 run_case negative 'read-log-retention=268435456 bytes block-cache=67108864 bytes read-batch=1024 slow-window=60000000000ns slow-min-rate=5' \ --subscribe-read-log-retention-bytes=-1 --subscribe-slow-window=-1s \ --subscribe-slow-min-rate=-1 --subscribe-block-cache-bytes=-1 --subscribe-read-batch=-1 \ --cursor-block-index-cache-size=-1 echo 'subscribe config: PASS (retention/cache/read-batch/slow controls + deprecated no-op reach runtime)' # Replay a real 5,000-row sealed archive through the production WebSocket. # A rejecting filter proves the batch boundary counts raw rows, not frames. subscribe-read-batch-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_SUBSCRIBE_BATCH_PORT:-6028} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --archive "$DATA" ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --no-verify --cursor-lookback=100000h \ --subscribe-read-batch=17 >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done STREAM_SUBSCRIBE_BATCH_PORT="$PORT" python3 tests/subscribe_read_batch_contract.py # Seed the real RocksDB repo/host schema, reopen it through the production # binary, and exercise the public host/account views without network access. status-contract: #!/usr/bin/env bash set -euo pipefail DATA=$(mktemp -d) LOG=$(mktemp) PORT=${STREAM_STATUS_PORT:-6019} cleanup() { if [[ -n "${pid:-}" ]]; then kill "$pid" 2>/dev/null || true; wait "$pid" 2>/dev/null || true; fi rm -rf "$DATA" "$LOG" } trap cleanup EXIT python3 -c 'import socket,sys; s=socket.socket(); s.bind(("127.0.0.1",int(sys.argv[1]))); s.close()' "$PORT" zig build -Doptimize=ReleaseSafe zig build write-sample -- --status "$DATA" ./zig-out/bin/stream --port="$PORT" --data-dir="$DATA" \ --upstream=ws://127.0.0.1:17999 --plc=http://127.0.0.1:17999 \ --relay-http=http://127.0.0.1:17999 --compaction-interval=0 \ --retry-interval=0 --no-verify >"$LOG" 2>&1 & pid=$! for _ in {1..100}; do if body=$(curl -fsS "http://127.0.0.1:$PORT/status" 2>/dev/null) && grep -qF "state live" <<<"$body"; then break; fi if ! kill -0 "$pid" 2>/dev/null; then cat "$LOG" >&2; exit 1; fi sleep 0.1 done curl -fsS "http://127.0.0.1:$PORT/healthz" >/dev/null STREAM_BASE_URL="http://127.0.0.1:$PORT" python3 tests/status_contract.py run *ARGS: zig build run -- {{ARGS}} # upstream bluesky jetstream clone (simulator lives there) upstream := env("STREAM_UPSTREAM_REPO", home_directory() / "github.com/bluesky-social/jetstream") # build + run the upstream atproto simulator (fake PLC/PDS/relay on :7777). # all traffic stays on localhost — never touches the real network. simulator *ARGS: cd {{upstream}} && go run ./cmd/simulator serve --accounts=100 --commits-per-sec=20 {{ARGS}} simulator-reset: cd {{upstream}} && go run ./cmd/simulator serve --reset --accounts=100 --commits-per-sec=20 # run stream against the local simulator run-sim *ARGS: zig build run -- --upstream=ws://localhost:7777 {{ARGS}} # full e2e: expects `just simulator` running in another terminal, # starts stream, runs the python checks, tears down e2e: zig build ./zig-out/bin/stream & echo $! > /tmp/stream-e2e.pid; sleep 2 uv run tests/e2e.py; status=$?; kill $(cat /tmp/stream-e2e.pid); exit $status # crash-matrix oracle: expects `just simulator` running; aborts the real # binary at every lifecycle crashpoint and verifies restart convergence oracle: zig build -Doptimize=ReleaseSafe uv run tests/oracle.py # One-time networked bootstrap for the pinned strict-storage tool image. # Actual power-loss runs never pull or install anything. # # No --platform: build for whatever the host is. Hardcoding linux/arm64 (fine # on an arm64 workstation) produced an arm64 image on x86_64 CI whose every # RUN died with "exec /bin/sh: exec format error". powerloss-image: docker build --pull=false \ -t stream-powerloss-oracle:ubuntu24.04 tests -f tests/powerloss.Dockerfile # Real Linux binary + RocksDB + ext4 + kernel NBD. Expects `just simulator`; # Go/Zig/uv and Docker are all forced to their existing offline state. # # A killed run can leave its NBD device claimed inside the Docker VM: the next # run then dies with "nbd: nbdN already in use" even though /sys/block/nbdN # reports size=0 and no pid. Point STREAM_POWERLOSS_DEVICE at another device # (nbd0..nbd3) rather than reading that as a Stream failure. powerloss-oracle: zig build write-sample -- /tmp/stream-powerloss-fixture.jss rm -rf /tmp/stream-powerloss-linux # The binary runs inside a Linux container on THIS host's architecture. # Hardcoding aarch64 (fine on an arm64 workstation) produced an unrunnable # binary on an x86_64 runner, exactly like the --platform bug above. target="${STREAM_POWERLOSS_TARGET:-$(uname -m | sed 's/^arm64$/aarch64/')-linux-gnu}"; \ echo "powerloss target: $target"; \ zig build -Doptimize=ReleaseSafe -Dtarget="$target" \ --prefix /tmp/stream-powerloss-linux STREAM_POWERLOSS_BIN=/tmp/stream-powerloss-linux/bin/stream \ STREAM_POWERLOSS_FIXTURE=/tmp/stream-powerloss-fixture.jss \ uv run --offline tests/powerloss_oracle.py # Deterministic, fully offline semantic comparison using the exact pinned # upstream simulator, JSS reader, oracle model, and public Go client. # batch larger than the job queue must still converge (see the docstring: # this does NOT reproduce the production deadlock) backfill-batch-contract: uv run tests/backfill_batch_contract.py differential-oracle: zig build -Doptimize=ReleaseSafe STREAM_UPSTREAM_REPO='{{upstream}}' python3 tests/differential_oracle.py # Five seed-derived upstream predicate kills against real Stream children. differential-oracle-multiseed: zig build -Doptimize=ReleaseSafe JETSTREAM_ORACLE_SEED=7001 \ STREAM_UPSTREAM_REPO='{{upstream}}' \ STREAM_ORACLE_RUN='^TestStreamRestartPredicateKillOracle$' \ python3 tests/differential_oracle.py # ReleaseSafe linux binary (musl breaks C++ deps upstream; gnu per zlay) package: zig build -Doptimize=ReleaseSafe -Dtarget=x86_64-linux-gnu @ls -lh zig-out/bin/stream # Offline: prove the deployment gate refuses every artifact it has not seen # pass -- unknown digest, unpublished receipt, skipped suite, failed suite. admission-contract: python3 tests/admission_contract.py # Exact-artifact admission. `run` requires a clean tree, runs every suite, # builds the linux/amd64 image and writes one receipt; `publish` pins that # receipt to the pushed registry digest; `verify` is the deployment gate. admit *ARGS: ./scripts/admit {{ARGS}} # build + push the deploy image from a FRESH CLONE — the image tag always # corresponds 1:1 to a pushed commit (provenance by construction; zlay's # dirty-tree lesson) publish-docker: #!/usr/bin/env bash set -euo pipefail TMPDIR=$(mktemp -d) trap "rm -rf $TMPDIR" EXIT git clone --depth 1 https://tangled.org/zat.dev/stream "$TMPDIR" cd "$TMPDIR" TAG=$(git rev-parse --short HEAD) IMAGE="atcr.io/zat.dev/stream:${TAG}" docker build --platform linux/amd64 -t "${IMAGE}" . ATCR_AUTO_AUTH=1 docker push "${IMAGE}" echo "==> pushed ${IMAGE}" # deploy the static site to the serving box (caddy picks changes up # instantly; no stream restart, no admission — site files only) site: for f in deploy/site/*; do [ "$(basename $f)" = "Caddyfile" ] && continue; scp -q -i ~/.ssh/waow_ed25519 -o IdentitiesOnly=yes "$f" root@89.167.122.160:/opt/stream-experiment/site/; done @echo "site deployed → https://stream.waow.tech (no proxy touch)" # proxy config is a separate, deliberate act: it reloads caddy. with # stream_close_delay set, subscriber websockets survive the reload. caddy: scp -q -i ~/.ssh/waow_ed25519 -o IdentitiesOnly=yes deploy/site/Caddyfile root@89.167.122.160:/opt/stream-experiment/Caddyfile ssh -i ~/.ssh/waow_ed25519 -o IdentitiesOnly=yes root@89.167.122.160 'docker exec stream-experiment-caddy-1 caddy reload --config /etc/caddy/Caddyfile' @echo "caddy config deployed + reloaded" # publish the public Strata playground: static assets + one summary snapshot. # Separate from site, Stream admission, and the Worker. Route installed by caddy. strata: python3 scripts/deploy-strata-preview # Compatibility alias for the former preview deployment command. strata-preview: strata