`cargo test --workspace` could not run at all: dependency resolution failed with "aws-types@1.3.16 requires rustc 1.91.1" on the 1.91.0 default toolchain, so the gate the project documents was dead. Making it run exposed a compile break and two wrong tests that had been invisible for months. Now green end to end: 143 suites, 3155 tests, exit 0. Toolchain - rust-toolchain.toml pins the DEV toolchain to 1.91.1. The published MSRV stays `rust-version = "1.91"` (the engine builds on 1.91.0); only tidalctl's AWS SDK chain needs the patch release, and it now declares that itself. Consumer crates migrated to the current engine API (clean cutover) - iknowyou-engine: `AgentPolicy` gained five m10 read/profile-override fields; the literal now spreads `..AgentPolicy::default()` as the engine's own doc example does, so future fields do not break it again. - forage-engine: `RetrieveResult` gained p1 `reasons`. The app builds its own candidate pool, so it now tags what it knows: PreferenceMatch for the preference-vector blend, SemanticMatch (with the seed item) for similar-to-saved, ExplorationBudget for pinned discoveries. - forage-engine: `url_to_item_id` folded into the u32 item universe. The engine narrows item IDs to a u32 slot in durable per-user state and rejects anything above u32::MAX rather than alias two items forever, so every add_item with a 64-bit FNV hash failed. 9 of 28 smoke tests were failing on this alone. - forage-engine: bridge items read the top-2 preference CLUSTERS via `query_vectors`, not the single centroid from `preference_vectors().get()`. Since m12 that accessor returns only the strongest cluster, so a tech+jazz user whose interests split into two clusters looked single-interest and never bridged. Falls back to top-2 dimensions when a user has one cluster. Reconcile tests corrected to the shipped contract - tidal/tests/m8p3_reconcile_production.rs asserted `3 + 5 == 8` for a windowed count after heal. `take_crdt_snapshot` deliberately keys signal contributions to ONE canonical contributor (ShardId::SINGLE) because signals are relayed from a single writer, so per-node attribution double-counted every replicated event on every reconcile. Merge is therefore LWW on (last_update_ns, score) plus PN-counter per-node max: nodes converge on the more complete accumulator. The old expectation was asserting the bug that fix removed. - Rewrote to assert convergence, count survival (not 0), and no inflation, and added `repeated_reconcile_of_converged_nodes_does_not_creep` - the regression guard for the creep itself, which nothing covered. Pre-commit hook unified - hooks/pre-commit dropped `-D warnings`: each crate's `[lints]` table is the source of truth (`clippy::all`/`unwrap_used` deny, `pedantic` warn), and the flag promoted ~58 deliberate pedantic warnings in integration tests to errors, making every Rust commit impossible. - It now lints all five tidal crates instead of path-matching `tidal/`, which silently skipped tidal-server, tidal-net, tidal-stress, tidalctl and applications/ - the rot above lived in exactly those crates. Ported the CODING_GUIDELINES file-length, println, and unsafe-SAFETY checks from the divergent untracked copy that this replaces. - CONTRIBUTING.md now documents the real commands and the toolchain/MSRV split. Fleet recovery and soak - scripts/restore-fleet.sh: the fail-closed selective restore, promoted out of an ignored tmp/ directory into the repository. Preflights retained storage, digest-pinned images, parked state, and aggregate plus per-PV-node scheduler headroom before the first scale; writes a durable transcript under tmp/restore-logs/ with structured start/error/rollback/complete events. - k8s manifests park the standalone store, the RF3 cluster, and the soak monitor at zero replicas with restore-fleet.sh as the only supported scale-up path. - soak-eval/soak-watch and the nightly CronJob fail closed on stale or missing restart evidence instead of silently skipping the restart-aware half of the gate. - docs/ops/capacity-planning.md corrects the RAM envelope to the real hot-tier formula and separates analytic totals from the measured process envelope.
150 lines
6.6 KiB
Bash
Executable File
150 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# build-release.sh — the one committed, repeatable tidalDB release path.
|
|
#
|
|
# Replaces the ad-hoc hand-typed cross-compile + buildx + push with a single
|
|
# command on a clean checkout:
|
|
#
|
|
# ./scripts/build-release.sh <tag> [server|dr|stress|all]
|
|
#
|
|
# It HOST cross-compiles (mac-arm64 -> x86_64-unknown-linux-gnu, glibc 2.41 via the
|
|
# homebrew toolchain), packages the binaries into the committed docker/release/*
|
|
# Dockerfiles via the amd64 buildx builder, pushes, and prints the resulting
|
|
# @sha256 digest for each image (pin that digest in k8s/cluster/statefulset.yaml).
|
|
#
|
|
# The project bans CI/CD pipelines (orchard9-k3sf/CLAUDE.md) — this is a SCRIPT,
|
|
# run manually or from an operator's shell, never a pipeline. Registry creds come
|
|
# from the Docker daemon's existing login (run `docker login registry.threesix.ai`
|
|
# first); NO secret is embedded here.
|
|
set -euo pipefail
|
|
|
|
TAG="${1:-}"
|
|
COMPONENT="${2:-all}"
|
|
REGISTRY="${TIDAL_REGISTRY:-registry.threesix.ai/tidal}"
|
|
TARGET="x86_64-unknown-linux-gnu"
|
|
BUILDER="${TIDAL_BUILDX_BUILDER:-amd64builder}"
|
|
|
|
die() { echo "build-release: $*" >&2; exit 1; }
|
|
|
|
[ -n "$TAG" ] || die "usage: build-release.sh <tag> [server|dr|stress|all]"
|
|
[ "$TAG" != "latest" ] || die "refusing mutable release tag 'latest'"
|
|
[[ "$TAG" =~ ^[a-z0-9][a-z0-9._-]{0,127}$ ]] \
|
|
|| die "tag must match ^[a-z0-9][a-z0-9._-]{0,127}$"
|
|
case "$COMPONENT" in server|dr|stress|all) ;; *) die "component must be server|dr|stress|all" ;; esac
|
|
|
|
# ── Toolchain preflight (fail fast, no half-built image) ─────────────────────
|
|
command -v docker >/dev/null || die "docker not on PATH"
|
|
command -v git >/dev/null || die "git not on PATH"
|
|
command -v jq >/dev/null || die "jq not on PATH"
|
|
if [ "$COMPONENT" != "stress" ]; then
|
|
command -v cargo >/dev/null || die "cargo not on PATH"
|
|
command -v "${TARGET}-gcc" >/dev/null \
|
|
|| die "missing ${TARGET}-gcc (brew install ${TARGET})"
|
|
PROTOC_BIN="${PROTOC:-$(command -v protoc || true)}"
|
|
[ -n "$PROTOC_BIN" ] || die "protoc not found (brew install protobuf), or set PROTOC"
|
|
rustup target list --installed 2>/dev/null | grep -qx "$TARGET" \
|
|
|| die "rust target $TARGET not installed (rustup target add $TARGET)"
|
|
fi
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
# Every image must have one reproducible source identity. Clean releases use
|
|
# HEAD. An explicitly allowed canary hashes HEAD, both tracked diffs, and every
|
|
# untracked non-ignored file (path + Git blob hash), so two distinct source
|
|
# trees cannot silently share the same `-dirty` identity. This check also covers
|
|
# stress: its Docker build consumes the repository source directly.
|
|
UNTRACKED="$(git ls-files --others --exclude-standard)"
|
|
if git diff --quiet && git diff --cached --quiet && [ -z "$UNTRACKED" ]; then
|
|
export GIT_HASH="${TIDAL_BUILD_HASH:-$(git rev-parse --verify HEAD)}"
|
|
else
|
|
[ "${TIDAL_ALLOW_DIRTY:-0}" = "1" ] \
|
|
|| die "source changes present; commit them or set TIDAL_ALLOW_DIRTY=1 for an explicit canary"
|
|
DIRTY_HASH="$(
|
|
{
|
|
git rev-parse --verify HEAD
|
|
git diff --binary
|
|
git diff --cached --binary
|
|
git ls-files --others --exclude-standard -z |
|
|
while IFS= read -r -d '' file; do
|
|
printf 'untracked\0%s\0' "$file"
|
|
git hash-object -- "$file"
|
|
done
|
|
} | shasum -a 256 | cut -c1-16
|
|
)"
|
|
export GIT_HASH="${TIDAL_BUILD_HASH:-${DIRTY_HASH}-dirty}"
|
|
fi
|
|
echo "==> source identity $GIT_HASH"
|
|
|
|
# ── Pinned cross-compile environment (the documented recipe) ─────────────────
|
|
export CC_x86_64_unknown_linux_gnu="${TARGET}-gcc"
|
|
export CXX_x86_64_unknown_linux_gnu="${TARGET}-g++"
|
|
export AR_x86_64_unknown_linux_gnu="${TARGET}-ar"
|
|
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="${TARGET}-gcc"
|
|
export PROTOC="${PROTOC_BIN:-}"
|
|
|
|
# Which crates to cross-compile for the selected component(s).
|
|
PKGS=()
|
|
case "$COMPONENT" in
|
|
server) PKGS=(-p tidal-server) ;;
|
|
dr) PKGS=(-p tidalctl -p tidal-server) ;;
|
|
stress) PKGS=() ;; # stress builds IN-container (pure Rust, no engine deps)
|
|
all) PKGS=(-p tidal-server -p tidalctl) ;;
|
|
esac
|
|
|
|
if [ "${#PKGS[@]}" -gt 0 ]; then
|
|
echo "==> cross-compiling ${PKGS[*]} for $TARGET (release)"
|
|
cargo build "${PKGS[@]}" --release --target "$TARGET" --locked
|
|
fi
|
|
|
|
# ── buildx amd64 builder (idempotent) ────────────────────────────────────────
|
|
if ! docker buildx inspect "$BUILDER" >/dev/null 2>&1; then
|
|
echo "==> creating buildx builder $BUILDER"
|
|
docker buildx create --name "$BUILDER" --driver docker-container >/dev/null
|
|
fi
|
|
|
|
BIN_DIR="target/$TARGET/release"
|
|
STAGE="$(mktemp -d)"
|
|
trap 'rm -rf "$STAGE"' EXIT
|
|
|
|
# Build one image from a staged context; print its pushed digest.
|
|
build_image() { # $1=image_name $2=dockerfile $3=stage_subdir
|
|
local image="$REGISTRY/$1:$TAG" dockerfile="$2" ctx="$STAGE/$3"
|
|
echo "==> building $image"
|
|
docker buildx build --builder "$BUILDER" --platform linux/amd64 \
|
|
-f "$dockerfile" -t "$image" --push "$ctx"
|
|
local digest
|
|
digest="$(docker buildx imagetools inspect "$image" --format '{{json .Manifest}}' | jq -er '.digest')"
|
|
[ -n "$digest" ] || die "registry returned no digest for $image"
|
|
echo "RELEASE $1: $image@$digest"
|
|
}
|
|
|
|
if [ "$COMPONENT" = server ] || [ "$COMPONENT" = all ]; then
|
|
mkdir -p "$STAGE/server"
|
|
cp "$BIN_DIR/tidal-server" "$STAGE/server/"
|
|
cp -a docker/release/config "$STAGE/server/config"
|
|
cp docker/release/Dockerfile "$STAGE/server/Dockerfile"
|
|
build_image server "$STAGE/server/Dockerfile" server
|
|
fi
|
|
|
|
if [ "$COMPONENT" = dr ] || [ "$COMPONENT" = all ]; then
|
|
mkdir -p "$STAGE/dr"
|
|
cp "$BIN_DIR/tidalctl" "$BIN_DIR/tidal-server" "$STAGE/dr/"
|
|
cp docker/release/dr.Dockerfile "$STAGE/dr/Dockerfile"
|
|
build_image tidalctl "$STAGE/dr/Dockerfile" dr
|
|
fi
|
|
|
|
if [ "$COMPONENT" = stress ] || [ "$COMPONENT" = all ]; then
|
|
# Pure-Rust, in-container build (includes soak-eval + soak-watch). Context =
|
|
# repo root so workspace manifests resolve; the .dockerignore prunes it. The
|
|
# OCI revision label makes the source identity inspectable after the build.
|
|
echo "==> building $REGISTRY/stress:$TAG (in-container, includes soak-eval + soak-watch)"
|
|
docker buildx build --builder "$BUILDER" --platform linux/amd64 \
|
|
--build-arg "GIT_HASH=$GIT_HASH" \
|
|
-f docker/stress/Dockerfile -t "$REGISTRY/stress:$TAG" --push .
|
|
d="$(docker buildx imagetools inspect "$REGISTRY/stress:$TAG" --format '{{json .Manifest}}' | jq -er '.digest')"
|
|
[ -n "$d" ] || die "registry returned no digest for $REGISTRY/stress:$TAG"
|
|
echo "RELEASE stress: $REGISTRY/stress:$TAG@$d"
|
|
fi
|
|
|
|
echo "==> done. Deploy only the printed tag@sha256 references."
|