#!/usr/bin/env bash # Parley ship gate — run before every commit (the six hard-stops the conductor/autopilot enforce). # Bundles: tests green · zero-dependency core intact · examples smoke · published claims true. # The redteam extraction + net hardening are covered inside the test suite (tests/test_redteam.py, # tests/test_net.py), so a green pytest already exercises them. # # Usage: scripts/ship-gate.sh (uses ./.venv) # Exit 0 = all gates pass; non-zero = a gate failed (with which one). set -euo pipefail cd "$(dirname "$0")/.." # A worktree sharing another tree's .venv would import that tree's editable parley/ otherwise. export PYTHONPATH="$PWD${PYTHONPATH:+:$PYTHONPATH}" PY="${PARLEY_PY:-.venv/bin/python}" [ -x "$PY" ] || PY="python3" export PARLEY_PY="$PY" fail() { echo "✗ ship-gate FAILED: $1" >&2; exit 1; } echo "▸ 1/5 tests" "$PY" -m pytest -q || fail "pytest not green" echo "▸ 2/5 zero-dependency core" # The core (parley/, minus the optional signed layer net/identity.py) must import only stdlib # or local parley modules. Correct check via sys.stdlib_module_names — not an ad-hoc grep. "$PY" - <<'PYCHECK' || fail "core imports a third-party package (keep it zero-dependency)" import ast, sys, pathlib ALLOWED_NONSTDLIB_FILE = {"parley/net/identity.py"} # the optional Ed25519 layer may import nacl std = set(sys.stdlib_module_names) bad = [] for p in sorted(pathlib.Path("parley").rglob("*.py")): rel = p.as_posix() if rel in ALLOWED_NONSTDLIB_FILE: continue tree = ast.parse(p.read_text(), rel) for node in ast.walk(tree): if isinstance(node, ast.Import): roots = [(a.name.split(".")[0], a.name) for a in node.names] elif isinstance(node, ast.ImportFrom): if node.level: # relative import (from . / from ..) is local — fine continue roots = [(node.module.split(".")[0], node.module)] if node.module else [] else: continue for root, full in roots: if root == "parley" or root in std: continue bad.append(f"{rel}: import {full}") if bad: print("\n".join(bad)); sys.exit(1) print(" core is stdlib-only (nacl confined to net/identity.py)") PYCHECK echo "▸ 3/5 examples smoke" "$PY" examples/meeting.py >/dev/null || fail "examples/meeting.py errored" # Gates 4 and 5 exist because the site twice shipped a number or a hash that the code did not # back. Both failures were caught by a human reading the page, not by anything mechanical. echo "▸ 4/5 homepage tape matches a real engine run" "$PY" landing/scripts/gen-tape.py --check >/dev/null \ || fail "landing/src/data/tape.ts is stale — regenerate with: $PY landing/scripts/gen-tape.py" echo " tape hashes come from a live run" echo "▸ 5/5 published test-count claims are current" # Report what the checker actually found, rather than a sentence written here. The two trees # differ — the public mirror carries no data room — so a hardcoded verdict would state # something this gate did not verify. The detail is printed on failure too, so whoever hits # this gate sees which file and line is at fault without re-running the checker by hand. claims_rc=0 claims_out="$(bash scripts/claim-freshness-check.sh)" || claims_rc=$? if [[ "$claims_rc" -ne 0 ]]; then echo "$claims_out" fail "a test count is stale or has spread beyond the data room" fi echo " ${claims_out#OK }" echo "✓ ship-gate passed — tests green · zero-dep core · examples run · claims true"