From f43413f0e6d163a911ed6990c876d33ec93b812f Mon Sep 17 00:00:00 2001 From: Tim Froehlich Date: Sun, 6 Sep 2026 21:20:47 -0500 Subject: [PATCH 1/4] chore(beads): move shared runtime ownership to dotfiles --- .agents/skills/beads/SKILL.md | 98 ------ .agents/skills/beads/agents/openai.yaml | 4 - .agents/skills/pinpoint-chores/SKILL.md | 2 +- README.md | 14 +- docs/runbooks/cloud-routines-beads-access.md | 18 +- ...ty.json => beads-cloud-compatibility.json} | 2 +- scripts/beads-cloud-init.sh | 10 +- scripts/beads-cloud-setup.sh | 9 +- scripts/beads-server/SETUP.md | 282 ------------------ .../beads-server/beads-dolthub-bridge.service | 36 --- scripts/beads-server/beads-dolthub-bridge.sh | 139 --------- .../beads-server/beads-dolthub-bridge.timer | 22 -- scripts/beads-server/dolt-sql-server.service | 31 -- scripts/beads-server/dolt-sql-server.sh | 35 --- scripts/beads-server/server.yaml | 46 --- scripts/tests/test_beads_compatibility.py | 250 +--------------- scripts/tests/test_check_skill_frontmatter.py | 3 +- 17 files changed, 39 insertions(+), 962 deletions(-) delete mode 100644 .agents/skills/beads/SKILL.md delete mode 100644 .agents/skills/beads/agents/openai.yaml rename scripts/{beads-compatibility.json => beads-cloud-compatibility.json} (77%) delete mode 100644 scripts/beads-server/SETUP.md delete mode 100644 scripts/beads-server/beads-dolthub-bridge.service delete mode 100644 scripts/beads-server/beads-dolthub-bridge.sh delete mode 100644 scripts/beads-server/beads-dolthub-bridge.timer delete mode 100644 scripts/beads-server/dolt-sql-server.service delete mode 100755 scripts/beads-server/dolt-sql-server.sh delete mode 100644 scripts/beads-server/server.yaml diff --git a/.agents/skills/beads/SKILL.md b/.agents/skills/beads/SKILL.md deleted file mode 100644 index 051203920..000000000 --- a/.agents/skills/beads/SKILL.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -name: beads -description: Use when working in a repository that uses bd or Beads for durable project task tracking, issue dependencies, blocker management, multi-session handoff, or shared work memory. Trigger when the user asks to find ready work, claim or close tasks, create follow-up work, inspect blockers, recover project context, or choose between local planning and persistent project tracking. ---- - -# Beads - -Use Beads as the shared project task system. Local plans, scratch files, and personal memories are useful, but they are not the durable source of truth for project work. - -## First Step - -Run: - -```bash -bd prime -``` - -If that prints nothing, check whether the repository has an active Beads workspace: - -```bash -bd where -``` - -## Preferred Route - -Use the `bd` CLI when shell access is available. It is the most compact and direct Beads interface. - -## Core CLI Workflow - -1. Find work: - -```bash -bd ready --plain --limit 20 -bd list --status=open --limit 20 --flat -bd list --status=in_progress --limit 20 --flat -``` - -Treat these as discovery indexes, not bulk context. Refine at the source with -`--priority`, `--type`, `--assignee`, `--label`, or `--parent` before raising the -limit. Inspect the selected issue separately instead of loading every issue's -description, design, notes, dependencies, and comments. - -2. Inspect before editing: - -```bash -bd show -``` - -3. Claim work atomically: - -```bash -bd update --claim -``` - -4. Create durable follow-up work when implementation reveals new tasks: - -```bash -bd create "Short title" --description="Why this exists and what needs to be done" --type=task --priority=2 -``` - -5. Close completed work: - -```bash -bd close --reason="Completed" -``` - -## What Belongs In Beads - -Use Beads for: - -- shared project tasks -- blockers and dependencies -- discovered follow-up work -- work that must survive thread reset, compaction, or handoff -- status that another person or agent should be able to resume - -Use agent-local planning tools only for the current turn's execution checklist. Do not treat them as shared project state. - -## Rules - -- Do not create markdown TODO files as the source of truth when Beads is available. -- Do not use `bd edit`; it opens an interactive editor. Use `bd update` flags instead. -- Keep collection queries bounded. Do not request default-cap or unlimited JSON from - `bd ready` or `bd list`; use a small `--limit`, source-side filters, and another page - only when the first page cannot answer the question. -- Prefer `--json` when parsing `bd` output programmatically, but project it immediately - to the fields needed for selection. For example: - - ```bash - bd ready --limit 20 --json \ - | jq -c '.[] | {id, priority, issue_type, status, title}' - ``` - - After choosing an ID, use `bd show --json` when detailed structured context is - actually needed. - -- If hooks are installed, `bd prime` may already be injected. Run it manually when context is missing. -- Do not auto-close or mutate tasks unless the work is actually complete. diff --git a/.agents/skills/beads/agents/openai.yaml b/.agents/skills/beads/agents/openai.yaml deleted file mode 100644 index 09c3b8f61..000000000 --- a/.agents/skills/beads/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Beads" - short_description: "Project task tracking with bd" - default_prompt: "Use $beads to inspect ready work and manage durable project tasks." diff --git a/.agents/skills/pinpoint-chores/SKILL.md b/.agents/skills/pinpoint-chores/SKILL.md index 27d1eb86f..5615ae1bb 100644 --- a/.agents/skills/pinpoint-chores/SKILL.md +++ b/.agents/skills/pinpoint-chores/SKILL.md @@ -71,7 +71,7 @@ Then work the checklist. For each item, note findings as a comment on the bead ( - If it's newer than the current pin (mind major bumps — read the pnpm release notes/migration guide first), update `packageManager` in `package.json` with the new version and its sha512 integrity hash (e.g. from `npm view pnpm@ dist.integrity` converted to `+sha512.`), run `mise lock` and then `mise install --locked`, then verify no unexpected `pnpm-lock.yaml` churn (`pnpm install --frozen-lockfile`), `pnpm audit --audit-level=high` still resolves, and `pnpm run check` is green. PR it through the normal workflow; file a bead if a major bump needs real migration work. - **Vercel CLI pin** (PP-h2ui.7). Privileged Vercel CLI invocations use one repository-owned wrapper: `scripts/workflow/preview/vercel-cli.sh`. Compare the pinned `VERCEL_CLI_VERSION` against the latest release on npm (applying a 14/30-day cooldown). Bumping is a single-site edit in `scripts/workflow/preview/vercel-cli.sh`. - - **bd and Dolt compatibility version pins** (from the 2026-08-16 shared-DB schema incident). PinPoint declares exact compatibility versions for `bd` and `dolt` at a **single source**: `scripts/beads-compatibility.json`. The cloud setup script (`scripts/beads-cloud-setup.sh`), runtime guards (`scripts/beads-cloud-init.sh`), and Bazzite services consume or validate this manifest. When Tim's local/Bazzite tools move past the pins, bump **`scripts/beads-compatibility.json`** — cloud routines and services refuse to run until installed binaries match. Exact-pin is deliberate: an accidental _newer_ release migrated the shared DB and locked every client out for two days, so a loud refusal is the safe failure. Compare the pins against installed `bd version` and `dolt version`; bump only once newer versions are tested and running clean locally. + - **bd and Dolt compatibility version pins** (from the 2026-08-16 shared-DB schema incident). Dotfiles owns the local/Bazzite runtime contract at `all/.agents/beads/compatibility.json`. PinPoint vendors `scripts/beads-cloud-compatibility.json` because fresh cloud checkouts cannot assume dotfiles exist; only the cloud setup and init scripts consume that snapshot. For an upgrade, validate the newer tools against the shared database, update the dotfiles contract first, then update the PinPoint cloud snapshot and digests as a paired rollout. Exact pins are deliberate: an accidental _newer_ release migrated the shared DB and locked every client out for two days, so a loud refusal is the safe failure. 2. **TypeScript compiler maintenance** - TypeScript 7 is installed as `typescript`; its native `tsc` runs the app, test, E2E, and Next build type checks. Read `docs/plans/2026-06-27-typescript-7-upgrade-plan.md` only for the rollout record. diff --git a/README.md b/README.md index 5a5ea89dd..a13404ca5 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,13 @@ Homebrew, or another version manager. ### Tool Ownership -| Surface | Authority | -| :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | -| Project Node, Python, Ruff, and Supabase CLI | Exact pins in `mise.toml`; resolved artifacts in `mise.lock` | -| pnpm executable | Exact version and sha512 integrity in `package.json#packageManager`, installed by mise | -| Project commands | `package.json#scripts`; mise resolves tools but does not duplicate the task namespace | -| Local/Bazzite `bd` and Dolt | User-global mise declarations checked against `scripts/beads-compatibility.json` | -| Cloud-routine `bd` and Dolt | `scripts/beads-cloud-setup.sh` plus `scripts/beads-cloud-init.sh`; this path remains separate because cloud routines cannot reach the tailnet | +| Surface | Authority | +| :------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | +| Project Node, Python, Ruff, and Supabase CLI | Exact pins in `mise.toml`; resolved artifacts in `mise.lock` | +| pnpm executable | Exact version and sha512 integrity in `package.json#packageManager`, installed by mise | +| Project commands | `package.json#scripts`; mise resolves tools but does not duplicate the task namespace | +| Local/Bazzite `bd` and Dolt | Dotfiles `all/.agents/beads/compatibility.json` and its user-global mise declarations | +| Cloud-routine `bd` and Dolt | `scripts/beads-cloud-compatibility.json`, setup, and init; this vendored path remains because fresh cloud checkouts do not have dotfiles | Vercel does not install or invoke mise. `package.json#engines` is its Node compatibility contract, `vercel-build` retains the production migration/build diff --git a/docs/runbooks/cloud-routines-beads-access.md b/docs/runbooks/cloud-routines-beads-access.md index 84ad001bb..bbc8626f7 100644 --- a/docs/runbooks/cloud-routines-beads-access.md +++ b/docs/runbooks/cloud-routines-beads-access.md @@ -100,12 +100,13 @@ That script installs `dolt` (pinned) and `bd` (pinned); the agent then runs **The compatibility contract pins both `bd` and `dolt`.** The 2026-08-16 lockout was a `bd` schema migration — `bd` owns `schema_migrations` and the additive migrations that broke it, while `dolt` is the storage engine. Pinning -both `bd` and `dolt` to exact versions in `scripts/beads-compatibility.json` -guarantees consistent schema handling, client-server wire compatibility, and -reproducibility across cloud sandboxes, Mac laptops, and Bazzite hosts. +both `bd` and `dolt` to exact versions in `scripts/beads-cloud-compatibility.json` +guarantees consistent schema handling and reproducibility across fresh cloud +sandboxes. The matching local/Bazzite runtime contract lives in Tim's dotfiles +at `all/.agents/beads/compatibility.json`. -**The toolchain pins are single-source.** `beads-cloud-setup.sh` reads both -`bd` and `dolt` versions from `scripts/beads-compatibility.json` and installs +**The cloud pins are single-source inside this checkout.** `beads-cloud-setup.sh` reads both +`bd` and `dolt` versions from `scripts/beads-cloud-compatibility.json` and installs exactly those by exact release tags. The same manifest declares the approved SHA-256 digest for each supported cloud platform. Setup downloads both archives into an isolated temporary directory, verifies both before extracting or @@ -122,7 +123,10 @@ Caveat, now narrowed: only the one-line shim lives in the un-diffable UI — the install logic it calls is in git. The reviewable, enforced backstop remains the version guard in `scripts/beads-cloud-init.sh`, which refuses to touch the DB unless both the installed `bd` and `dolt` equal their pins in -`scripts/beads-compatibility.json`. +`scripts/beads-cloud-compatibility.json`. + +An upgrade is a paired rollout: update and validate the dotfiles runtime +contract first, then refresh this cloud snapshot and its archive digests. ## Credential setup (one-time) @@ -163,7 +167,7 @@ bd dolt push The script reads `DOLT_CREDS_JWK`, `DOLT_CREDS_PUB`, and `BEADS_SYNC_REMOTE` from the environment (agent-runtime only — see #55440 above), writes the DoltHub credential and `~/.dolt/config_global.json`, checks `bd version` and -`dolt version` against `scripts/beads-compatibility.json`, then clones into +`dolt version` against `scripts/beads-cloud-compatibility.json`, then clones into `~/beads`. It exits non-zero — refusing to touch the DB — on a version mismatch or any missing env var, so a routine fails fast instead of running against a wrong binary. The generated `user.name`/`user.email` are Dolt commit metadata diff --git a/scripts/beads-compatibility.json b/scripts/beads-cloud-compatibility.json similarity index 77% rename from scripts/beads-compatibility.json rename to scripts/beads-cloud-compatibility.json index d4946e549..bc66afd5c 100644 --- a/scripts/beads-compatibility.json +++ b/scripts/beads-cloud-compatibility.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "PinPoint compatibility contract for bd (Beads CLI) and Dolt database engine", + "description": "Vendored compatibility snapshot for PinPoint fresh-cloud Beads bootstrap", "bd": "1.2.2", "dolt": "2.3.1", "cloudAssets": { diff --git a/scripts/beads-cloud-init.sh b/scripts/beads-cloud-init.sh index 70677374e..17174f860 100755 --- a/scripts/beads-cloud-init.sh +++ b/scripts/beads-cloud-init.sh @@ -14,7 +14,7 @@ # bash scripts/beads-cloud-init.sh && cd ~/beads # # THE VERSION PINS (loud, exact, deliberate). This refuses to proceed unless bd -# and dolt are EXACTLY the versions declared in scripts/beads-compatibility.json. +# and dolt are EXACTLY the versions declared in scripts/beads-cloud-compatibility.json. # Rationale: an accidental newer beads release (1.2.1, 2026-08-16) migrated the # shared DB to a schema no supported binary could read and locked every client out # for two days. A stale exact pin fails LOUD ("routine refuses to run") — the safe @@ -24,7 +24,7 @@ # shim in the claude.ai UI cannot be diffed. These guards are the reviewable, # enforced backstop. # -# When upgrading past the pins, bump scripts/beads-compatibility.json. It is a +# When upgrading past the pins, bump scripts/beads-cloud-compatibility.json. It is a # weekly-chores checklist item so the bump is a known recurring task, not a # surprise Saturday outage. # @@ -51,7 +51,7 @@ set -euo pipefail # hit exactly this after #1908 shipped the repair but computed SCRIPT_DIR too # late). Resolving here, pre-cd, is the fix. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPAT_FILE="$SCRIPT_DIR/beads-compatibility.json" +COMPAT_FILE="$SCRIPT_DIR/beads-cloud-compatibility.json" log() { printf '[beads-cloud-init] %s\n' "$*" >&2; } die() { printf '[beads-cloud-init] ERROR: %s\n' "$*" >&2; exit 1; } @@ -92,7 +92,7 @@ bd_ver="$(printf '%s\n' "$bd_raw" | sed -nE 's/^bd version ([0-9]+\.[0-9]+\.[0-9 # 2. bd GUARD. Exact-pin — refuse anything else, newer OR older. if [[ "$bd_ver" != "$BD_PINNED_VERSION" ]]; then die "bd $bd_ver != pinned $BD_PINNED_VERSION — refusing to touch the shared beads DB. - If this is a deliberate upgrade, bump \"bd\" in scripts/beads-compatibility.json. + If this is a deliberate upgrade, bump \"bd\" in scripts/beads-cloud-compatibility.json. Do NOT install, build, or 'upgrade' bd inside a cloud routine to get past this." fi log "bd $bd_ver matches pin — proceeding" @@ -107,7 +107,7 @@ dolt_ver="$(printf '%s\n' "$dolt_raw" | sed -nE 's/^dolt version ([0-9]+\.[0-9]+ if [[ "$dolt_ver" != "$DOLT_PINNED_VERSION" ]]; then die "dolt $dolt_ver != pinned $DOLT_PINNED_VERSION — refusing to touch the shared beads DB. - If this is a deliberate upgrade, bump \"dolt\" in scripts/beads-compatibility.json. + If this is a deliberate upgrade, bump \"dolt\" in scripts/beads-cloud-compatibility.json. Do NOT install, build, or 'upgrade' dolt inside a cloud routine to get past this." fi log "dolt $dolt_ver matches pin — proceeding" diff --git a/scripts/beads-cloud-setup.sh b/scripts/beads-cloud-setup.sh index 25ac96771..6cd1b817e 100755 --- a/scripts/beads-cloud-setup.sh +++ b/scripts/beads-cloud-setup.sh @@ -12,7 +12,8 @@ # WHY THIS LIVES IN THE REPO. The claude.ai environment's "Setup script" field # used to hold this inline. That copy could not be reviewed or diffed, and its # version pins silently drifted. Moving the body here makes it reviewable and -# collapses the pin to a SINGLE source of truth: scripts/beads-compatibility.json. +# keeps the fresh-cloud pin in one reviewable snapshot: +# scripts/beads-cloud-compatibility.json. # The UI field is now just a one-line shim — the repo is already cloned at # container-provision time, so the shim locates the checkout and runs this script: # @@ -23,9 +24,9 @@ # ~/PinPoint resolves to /root/PinPoint and misses it. This script itself uses # BASH_SOURCE below, so it works no matter which of those paths invoked it. # -# THE PINS ARE READ FROM COMPATIBILITY CONTRACT, NOT DUPLICATED. bd and dolt are +# THE CLOUD PINS ARE READ FROM THE SNAPSHOT, NOT DUPLICATED. bd and dolt are # installed at exactly the versions this script parses out of -# scripts/beads-compatibility.json. So bumping the pin is an edit to the manifest; +# scripts/beads-cloud-compatibility.json. So bumping the pin is an edit to the manifest; # the approved cloud-asset digests, installed binaries, and runtime guards move # together and cannot disagree. # (Rationale for exact pins: an accidental newer release, e.g. bd 1.2.1 on @@ -78,7 +79,7 @@ manifest_platform_digest() { # Resolve this script's directory so the pin read below works regardless of the # setup script's cwd (it runs from $HOME, not the repo root). SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPAT_FILE="$SCRIPT_DIR/beads-compatibility.json" +COMPAT_FILE="$SCRIPT_DIR/beads-cloud-compatibility.json" # --- The pins: read from the compatibility manifest (single source of truth). -- [[ -f "$COMPAT_FILE" ]] || die "cannot find $COMPAT_FILE — is this the PinPoint checkout?" diff --git a/scripts/beads-server/SETUP.md b/scripts/beads-server/SETUP.md deleted file mode 100644 index 709ca7d51..000000000 --- a/scripts/beads-server/SETUP.md +++ /dev/null @@ -1,282 +0,0 @@ -# Beads shared-server setup (Bazzite) — Phase B runbook - -This directory holds the templates for running the PinPoint beads database as a -single live `dolt sql-server` on the always-on Bazzite host, with DoltHub demoted -to an async bridge for off-tailnet cloud sessions. It is the target architecture -of **PP-0fwz**; the repo-side, mode-aware Phase A already merged (hooks no-op -their `bd dolt push/pull` when `dolt_mode: "server"`). This document is the -manual cutover. - -> **Do not run this until a quiet window** — no live huddle sessions writing beads -> on either machine (see "Preflight" below). All commands run on Bazzite over the -> `bazzite` ssh alias unless noted. - -## Architecture recap - -``` -Mac bd ──MySQL/Tailscale──┐ - ├──► dolt sql-server @ 100.87.228.116:3306 (Bazzite) -Bazzite bd ──MySQL────────┘ systemd --user, DB "PP", user beads+password - │ bridge timer (~15 min): - │ bd dolt commit → pull → push (LOUD on conflict) - ▼ - DoltHub advacar/pinpoint-beads ◄── cloud sessions -``` - -- The Mac needs **no dolt binary** — it speaks the MySQL wire protocol to the server. -- `bd dolt push/pull/commit` run as SQL procedures on the connected server, so the - bridge is just those commands on a timer, run on Bazzite where the DoltHub JWK - creds already live. -- Rollback at any point = flip `metadata.json` back to `embedded` on both machines - (keep the old `embeddeddolt/` data dir until burn-in is done). - -## Files in this directory (all TEMPLATES — edit paths before installing) - -| File | Installs to | Purpose | -| ------------------------------ | ----------------------------------------------------- | ------------------------------------------------ | -| `server.yaml` | `~/.beads-server/server.yaml` | dolt sql-server config (bind IP, port, data_dir) | -| `dolt-sql-server.sh` | `~/.beads-server/dolt-sql-server.sh` | dolt sql-server wrapper with manifest guard | -| `dolt-sql-server.service` | `~/.config/systemd/user/dolt-sql-server.service` | runs the server headless | -| `beads-dolthub-bridge.sh` | `~/.beads-server/beads-dolthub-bridge.sh` | one commit/pull/push cycle (loud on conflict) | -| `beads-dolthub-bridge.service` | `~/.config/systemd/user/beads-dolthub-bridge.service` | oneshot wrapper for the bridge script | -| `beads-dolthub-bridge.timer` | `~/.config/systemd/user/beads-dolthub-bridge.timer` | fires the bridge every ~15 min | - -> **chmod note (Claude Code sandbox):** the executable bit cannot be set from an -> agent session. After copying `dolt-sql-server.sh` and `beads-dolthub-bridge.sh` -> into place, **Tim must run** -> `chmod +x ~/.beads-server/dolt-sql-server.sh ~/.beads-server/beads-dolthub-bridge.sh` -> manually. The units invoke them via `bash …`, so the exec bit is belt-and- -> suspenders, but set it anyway. - -## Preflight (BLOCKER-GRADE — do not skip) - -1. `bd export` a JSONL snapshot on **both** machines; confirm `.beads/backup/` is - fresh. Record the issue count and `dolt` HEAD hash on Mac, Bazzite, and DoltHub. -2. **Worktree drain.** Enumerate `git worktree list` on both machines and remove or - rebuild any worktree whose checked-out tree predates the Phase A merge. Rationale: - pre-Phase-A `huddle_sync` fires an unconditional `bd dolt push`+`pull` every ~180s; - once `metadata.json` says `server`, those old copies would run real DoltHub - pull/pushes against the live shared DB, mutating both machines' sessions at once. - Only trees carrying the Phase A hook code (which no-op sync in server mode) may - remain connected during cutover. -3. Final convergence: run push/pull rounds from Mac and Bazzite until all three - (Mac, Bazzite, DoltHub) match on issue count + HEAD. This is the data-loss-risk - step — verify, do not assume. - -## Cutover steps - -### 1. Install dolt and bd on Bazzite (via user-global mise) - -`dolt` and `bd` are pinned to exact versions governed by the repository compatibility contract at `scripts/beads-compatibility.json` (e.g. `bd` 1.2.2, `dolt` 2.3.1). - -On Bazzite (and Mac), these tools are managed through user-global `mise` (declared in Stow-managed dotfiles `~/.config/mise/config.toml` per PP-h2ui.10), rather than mutable Homebrew packages or OS layers: - -```bash -# Verify mise execution and exact version alignment against scripts/beads-compatibility.json: -mise exec -- dolt version # expect exact pinned version, e.g. 2.3.1 -mise exec -- bd version # expect exact pinned version, e.g. 1.2.2 -``` - -The systemd `--user` units invoke `%h/.local/bin/mise exec -- ...` explicitly with `MISE_NOT_FOUND_AUTO_INSTALL=false` and `MISE_NOT_FOUND_SYSTEM_FALLBACK=false` so the service fails closed on missing tools and does not depend on interactive shell PATH activation. - -### 2. Disposable compatibility testing - -Before starting the persistent service (or after any version bump in `scripts/beads-compatibility.json`), perform a disposable smoke test in a temporary directory on a non-standard port: - -```bash -mkdir -p /tmp/dolt-compat-test && cd /tmp/dolt-compat-test -mise exec -- dolt init --name test --email test@pinpoint.invalid -mise exec -- dolt sql-server --host 127.0.0.1 --port 3307 --data-dir /tmp/dolt-compat-test & -TEST_PID=$! -sleep 2 -mise exec -- dolt --host 127.0.0.1 --port 3307 --user root --password "" --no-tls sql -q "SHOW DATABASES;" -kill "$TEST_PID" -rm -rf /tmp/dolt-compat-test -``` - -### 3. Seed the data dir from DoltHub - -```bash -mkdir -p ~/.beads-server/dolt && cd ~/.beads-server/dolt -mise exec -- dolt clone https://doltremoteapi.dolthub.com/advacar/pinpoint-beads PP -# → ~/.beads-server/dolt/PP with its `origin` remote already registered -# Remove any stray `.dolt/` the clone leaves in the PARENT data dir — otherwise the -# server exposes a phantom empty database named after the dir alongside `PP`. -rm -rf ~/.beads-server/dolt/.dolt -``` - -Point `data_dir` in `server.yaml` at `~/.beads-server/dolt` (the parent that -contains the `PP` database directory). - -### 4. First boot (localhost) to create the `beads` user - -Boot once WITHOUT `--skip-root-user-initialization` so the passwordless root can -create the app user, bound to loopback only. (We use `127.0.0.1` rather than the -project-wide `localhost` convention on purpose here: this is a raw MySQL-protocol -admin connection to a TCP-listening server, and `127.0.0.1` forces TCP — -`localhost` can select a UNIX socket for MySQL clients. CORE-SEC-008's -`localhost`-only rule is about Supabase SSR cookie isolation, which doesn't apply -to a DB admin connection.) - -```bash -cd ~/.beads-server/dolt -# Boot with the SAME --privilege-file the hardened server will read, or the user -# won't persist into it. -mise exec -- dolt sql-server --host 127.0.0.1 --port 3306 \ - --data-dir ~/.beads-server/dolt --privilege-file ~/.beads-server/privileges.db & -THROWAWAY_PID=$! -sleep 5 -# dolt's connection flags are GLOBAL — they go BEFORE the `sql` subcommand (the form -# `dolt sql --host …` errors with "unknown option host"). Passwordless root needs an -# explicit `--password ""` (no TTY to prompt otherwise). `--no-tls` for the plaintext -# first boot. -mise exec -- dolt --host 127.0.0.1 --port 3306 --user root --password "" --no-tls sql -q \ - "CREATE USER 'beads'@'%' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON *.* TO 'beads'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" -kill "$THROWAWAY_PID" # stop the throwaway localhost server (PID, not %1 — job control isn't reliable in scripts) -``` - -`beads` gets `ALL PRIVILEGES ON *.*` (not just `PP.*`): the bridge's `bd dolt -pull`/`push` run `DOLT_PULL`/`DOLT_PUSH` which are denied to a `PP.*`-only user -("command denied to user 'beads'@'%'"). It's the sole app user on a private, -single-purpose server, so full privileges are appropriate. If you ever need to -re-grant later, the hardened server binds only the Tailscale IP where root is denied -— stop it and repeat this throwaway-localhost boot to reach root. - -Pick a strong ``; it goes ONLY into env (next steps), never into any -committed file. - -### 5. Restart hardened via the unit - -> **NOTE:** Merging changes to unit templates in PinPoint does **not** touch or restart -> the running service on Bazzite. Service installation/restart is an explicit operator -> handoff. - -Copy the templates into place, edit paths if needed, then: - -```bash -cp server.yaml dolt-sql-server.sh beads-dolthub-bridge.sh ~/.beads-server/ # then: chmod +x (Tim, manual) -cp dolt-sql-server.service beads-dolthub-bridge.service beads-dolthub-bridge.timer \ - ~/.config/systemd/user/ -loginctl enable-linger "$USER" # already on for Tim; idempotent -systemctl --user daemon-reload -systemctl --user enable --now dolt-sql-server.service -``` - -`server.yaml` binds `100.87.228.116:3306` and the unit adds -`--skip-root-user-initialization`, so the hardened server never re-mints a -passwordless root. `privilege_file` persists the `beads` grant across restarts. - -### 6. Repoint both machines to server mode - -Per machine, edit `.beads/metadata.json` (gitignored, per-machine). Do **not** put -`dolt_server_port` here — it's deprecated (bd warns "can cause cross-project data -leakage"); the port lives in a separate file instead: - -```json -{ - "database": "dolt", - "backend": "dolt", - "dolt_mode": "server", - "dolt_server_host": "100.87.228.116", - "dolt_server_user": "beads", - "dolt_database": "PP" -} -``` - -```bash -printf '3306\n' > .beads/dolt-server.port # the port's new primary source -``` - -In `.beads/config.yaml` set `backup.enabled: false` — bd's server-side auto-backup -is denied (it needs privileges the setup doesn't grant) and is redundant now that the -bridge replicates to DoltHub; manual `bd export` still works for JSONL snapshots. -Also set `dolt.auto-start: false` so bd never tries to spawn a local server, and pin -`dolt.auto-commit: on` (bd's default is OFF; the bridge chain assumes committed -working sets before it pulls — the server-side `server.yaml` also sets -`autocommit: true`). - -Export `BEADS_DOLT_PASSWORD` (env only): - -- **Mac:** dotfiles-managed `~/.zshenv` (private, not committed). -- **Bazzite:** interactive shell env **and** the bridge unit's `EnvironmentFile` - at `~/.beads-server/bridge.env` (mode 600, outside git), e.g. - `BEADS_DOLT_PASSWORD=`. - -Verify on both machines: - -```bash -bd doctor --server # confirms the client is talking to the shared server, not embedded -bd show PP-0fwz # a real read over the wire -``` - -`bd doctor --server` is the drift check — trust it over hand-parsing -`metadata.json` for anything beyond the mode string. - -### 7. Register the DoltHub remote + enable the bridge - -The cloned data dir arrives with `origin` set, but confirm from the server SQL: - -```bash -bd dolt remote list # expect advacar/pinpoint-beads; re-add if missing -chmod +x ~/.beads-server/beads-dolthub-bridge.sh # Tim, manual (sandbox blocks chmod) -systemctl --user enable --now beads-dolthub-bridge.timer -journalctl --user -u beads-dolthub-bridge -f # watch one full cycle land -``` - -### 8. Keep the rollback asset - -Rename the old embedded data dir on both machines (do NOT delete until burn-in ≈ 1 -week is clean): - -```bash -mv .beads/embeddeddolt .beads/embeddeddolt.pre-server -``` - -## Bridge failure semantics (by design) - -The bridge is deliberately **loud**, unlike the fail-open huddle hooks: - -- Any step failing → `beads-dolthub-bridge.sh` exits nonzero → that oneshot - invocation is marked `failed`. The timer remains active and retries on its - normal cadence. Inspect `journalctl --user -u beads-dolthub-bridge` first and - troubleshoot the recorded failing step. A failure before or after the pull - conflict path can indicate a live-server, credential, compatibility, or - DoltHub problem; check the primary server separately with `bd doctor --server`. -- On a **pull conflict** the script checks `dolt_merge_status` on the live server. - `bd dolt pull` normally restores the pre-pull working set itself; when it does, - the bridge records that verification and stops. If conflicts remain, the - bridge runs `CALL DOLT_MERGE('--abort')` and verifies no merge remains active - before stopping. This covers both row and schema conflicts. Repeated confirmed - pull-conflict retries do not by themselves mean the primary server is - unhealthy. A human resolves the DoltHub divergence, then either waits for the - next timer invocation or runs - `systemctl --user start beads-dolthub-bridge.service` to verify immediately. -- Dolt can merge independent rows, but concurrent edits to the same `issues` - row require semantic reconciliation. Preserve legitimate fields from both - sides; do not apply a blanket newest-row, `--ours`, or `--theirs` policy. - Lifecycle changes are not monotonic — a later `open` row may be stale after a - legitimate close, while another later `open` row may represent an intentional - reopen. -- There is intentionally **no `|| true`** anywhere in the bridge (contrast PP-0b7p): - silent bridge failure would let DoltHub and the live server drift apart unnoticed. - -## Rollback - -Flip `dolt_mode` back to `embedded` in `.beads/metadata.json` on the affected -machine and restore `dolt.auto-start: true`. The hooks immediately resume embedded -`bd dolt push/pull` (Phase A gating keys off the mode string). The -`embeddeddolt.pre-server` dir is the pre-cutover data floor if the server data is -suspect. - -## Cloud re-bootstrap caveat - -Cloud Claude sessions cannot reach the tailnet, so their Beads path stays separate -from the local/Bazzite mise-managed server path. The environment setup invokes -`scripts/beads-cloud-setup.sh` to install the exact `bd` and Dolt versions from -`scripts/beads-compatibility.json`; the agent then runs -`scripts/beads-cloud-init.sh` to materialize credentials and clone the -bridge-maintained DoltHub remote. See -`docs/runbooks/cloud-routines-beads-access.md`. -**Sharp edge:** `dolt reset --hard` during a cloud re-bootstrap discards any -unpushed cloud writes — always let the bridge (or a manual `bd dolt push`) land -cloud work before re-bootstrapping a cloud clone. diff --git a/scripts/beads-server/beads-dolthub-bridge.service b/scripts/beads-server/beads-dolthub-bridge.service deleted file mode 100644 index a021daa31..000000000 --- a/scripts/beads-server/beads-dolthub-bridge.service +++ /dev/null @@ -1,36 +0,0 @@ -# systemd --user unit: one bridge cycle (commit -> pull -> push) between the -# live Dolt server and DoltHub. Triggered by beads-dolthub-bridge.timer. -# -# TEMPLATE. Install to ~/.config/systemd/user/beads-dolthub-bridge.service. -# oneshot + loud failure: a failed cycle leaves the unit in `failed` and the -# timer keeps its next run, but a pull CONFLICT makes the script exit nonzero -# after aborting the merge — inspect `journalctl --user -u beads-dolthub-bridge` -# and resolve before trusting the next cycle. -# -# BEADS_DOLT_PASSWORD comes from an environment file readable only by the user -# (chmod 600). NEVER commit that file; NEVER put the password in this unit. -# -# Invokes bridge explicitly through user-global `mise` context rather than -# relying on shell activation or a Linuxbrew PATH. -# Disables surprise auto-install and fallback (fails closed). -# %h expands to the user's home directory. - -[Unit] -Description=Beads DoltHub bridge (commit/pull/push cycle) -After=dolt-sql-server.service network-online.target -Wants=network-online.target - -[Service] -Type=oneshot -# REQUIRED: run from the project so `bd` resolves .beads (the server config). -# Without this the bridge runs from $HOME and every `bd dolt` call fails. -WorkingDirectory=%h/Code/PinPoint -Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin -Environment=MISE_EXEC_AUTO_INSTALL=false -Environment=MISE_NOT_FOUND_AUTO_INSTALL=false -Environment=MISE_NOT_FOUND_SYSTEM_FALLBACK=false -# Secret lives here, mode 600, outside git. See SETUP.md. -EnvironmentFile=%h/.beads-server/bridge.env -# System bash (brew bash is not guaranteed installed); script is invoked via bash -# under mise exec so bd and dolt resolve from global mise context. -ExecStart=%h/.local/bin/mise exec -- /usr/bin/bash %h/.beads-server/beads-dolthub-bridge.sh diff --git a/scripts/beads-server/beads-dolthub-bridge.sh b/scripts/beads-server/beads-dolthub-bridge.sh deleted file mode 100644 index 5a318868b..000000000 --- a/scripts/beads-server/beads-dolthub-bridge.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -# beads-dolthub-bridge.sh — async bridge between the live Dolt SQL server and -# DoltHub (advacar/pinpoint-beads). -# -# Run on the Bazzite host on a ~15-minute timer (beads-dolthub-bridge.timer). -# It commits any working-set drift, pulls DoltHub (in case an off-tailnet cloud -# Claude session pushed), then pushes local commits back up. DoltHub is demoted -# to an async bridge + off-machine backup; the shared server is the source of -# truth on the tailnet. -# -# LOUD FAILURE — deliberately NOT fail-open (contrast the huddle hooks, cf. -# PP-0b7p). Any step failing exits non-zero so systemd marks the unit `failed` -# for that invocation; the timer remains active and retries on its normal -# cadence. On a pull CONFLICT we do NOT leave the live server sitting in a -# conflicted working set that both machines read/write: we verify whether -# unresolved conflicts remain, abort an active merge when needed, verify again, -# alert, and stop the cycle (nonzero). A human resolves the remote divergence -# before the next successful cycle. -# -# Required env: -# BEADS_DOLT_PASSWORD — password for the `beads` SQL user (env only, never -# on disk). Sourced from the unit's environment. -# Optional env (defaults suit the SETUP.md layout): -# BEADS_SERVER_HOST (default 100.87.228.116) -# BEADS_SERVER_PORT (default 3306) -# BEADS_SERVER_USER (default beads) -# BEADS_DB (default PP) - -set -euo pipefail - -HOST="${BEADS_SERVER_HOST:-100.87.228.116}" -PORT="${BEADS_SERVER_PORT:-3306}" -USER="${BEADS_SERVER_USER:-beads}" -DB="${BEADS_DB:-PP}" - -log() { printf '[beads-bridge] %s\n' "$*" >&2; } -die() { printf '[beads-bridge] ERROR: %s\n' "$*" >&2; exit 1; } - -PINPOINT_DIR="${PINPOINT_DIR:-$HOME/Code/PinPoint}" -COMPAT_FILE="$PINPOINT_DIR/scripts/beads-compatibility.json" - -command -v bd >/dev/null 2>&1 || die "bd not found on PATH" -command -v dolt >/dev/null 2>&1 || die "dolt not found on PATH" -[[ -n "${BEADS_DOLT_PASSWORD:-}" ]] || die "BEADS_DOLT_PASSWORD not set" - -if [[ ! -f "$COMPAT_FILE" ]]; then - die "compatibility manifest not found at $COMPAT_FILE" -fi - -BD_PINNED="$(sed -nE 's/^[[:space:]]*"bd"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$COMPAT_FILE" | head -n1 || true)" -DOLT_PINNED="$(sed -nE 's/^[[:space:]]*"dolt"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$COMPAT_FILE" | head -n1 || true)" - -[[ -n "$BD_PINNED" ]] || die "could not parse \"bd\" version from $COMPAT_FILE" -[[ -n "$DOLT_PINNED" ]] || die "could not parse \"dolt\" version from $COMPAT_FILE" - -bd_raw="$(bd version 2>&1 || true)" -bd_ver="$(printf '%s\n' "$bd_raw" | sed -nE 's/^bd version ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n1 || true)" -if [[ "$bd_ver" != "$BD_PINNED" ]]; then - die "bd $bd_ver != pinned $BD_PINNED from $COMPAT_FILE — refusing bridge cycle" -fi - -dolt_raw="$(dolt version 2>&1 || true)" -dolt_ver="$(printf '%s\n' "$dolt_raw" | sed -nE 's/^dolt version ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n1 || true)" -if [[ "$dolt_ver" != "$DOLT_PINNED" ]]; then - die "dolt $dolt_ver != pinned $DOLT_PINNED from $COMPAT_FILE — refusing bridge cycle" -fi - -log "bd $bd_ver and dolt $dolt_ver match compatibility contract ($COMPAT_FILE)" - -# Dolt connection options are global flags and must precede the `sql` -# subcommand. The server is tailnet-local and does not serve TLS. -dolt_sql() { - dolt \ - --host "$HOST" --port "$PORT" \ - --user "$USER" --password "$BEADS_DOLT_PASSWORD" \ - --no-tls --use-db "$DB" \ - sql --result-format csv \ - --query "$1" -} - -merge_in_progress() { - local output is_merging - if ! output="$(dolt_sql "SELECT is_merging FROM dolt_merge_status;")"; then - log "failed to inspect dolt_merge_status on the live server" - return 1 - fi - - is_merging="$(printf '%s\n' "$output" | tail -n1 | tr -d '\r')" - if [[ ! "$is_merging" =~ ^[01]$ ]]; then - log "unexpected merge-status response from the live server: $output" - return 1 - fi - printf '%s\n' "$is_merging" -} - -# 1. Commit any uncommitted working-set drift so pull has a clean base. -log "commit (flush working set)" -bd dolt commit >&2 || die "bd dolt commit failed" - -# 2. Pull DoltHub. bd normally restores the pre-pull working set after a -# conflict. Verify that claim against the server's merge status; abort only -# if a merge actually remains active, then verify once more before stopping -# loudly. `dolt_merge_status` covers row and schema conflicts. -log "pull DoltHub" -pull_rc=0 -pull_out=$(bd dolt pull 2>&1) || pull_rc=$? -printf '%s\n' "$pull_out" >&2 -if [[ "$pull_rc" -ne 0 ]]; then - if printf '%s' "$pull_out" | grep -qiE 'conflict|operator resolution'; then - log "PULL CONFLICT — checking whether a merge remains active on the live server" - if ! is_merging="$(merge_in_progress)"; then - die "could not verify live-server merge state; manual intervention required" - fi - - if [[ "$is_merging" -eq 0 ]]; then - log "pull restored the pre-merge working set; no merge remains active" - else - log "a merge remains active — aborting it" - if ! dolt_sql "CALL DOLT_MERGE('--abort');" >&2; then - die "DOLT_MERGE('--abort') failed; manual intervention required" - fi - if ! is_merging="$(merge_in_progress)"; then - die "merge abort returned but merge state could not be verified" - fi - if [[ "$is_merging" -ne 0 ]]; then - die "merge abort returned but a merge remains active" - fi - log "merge aborted; no merge remains active" - fi - die "DoltHub pull hit a merge conflict — this cycle stopped; the timer will retry. Resolve manually, then wait for the timer or run: systemctl --user start beads-dolthub-bridge.service" - fi - die "bd dolt pull failed (non-conflict): $pull_out" -fi - -# 3. Push local commits up to DoltHub. -log "push DoltHub" -bd dolt push >&2 || die "bd dolt push failed" - -log "bridge cycle complete" diff --git a/scripts/beads-server/beads-dolthub-bridge.timer b/scripts/beads-server/beads-dolthub-bridge.timer deleted file mode 100644 index 31b1cf6a1..000000000 --- a/scripts/beads-server/beads-dolthub-bridge.timer +++ /dev/null @@ -1,22 +0,0 @@ -# systemd --user timer: fire the DoltHub bridge every ~15 minutes. -# -# TEMPLATE. Install to ~/.config/systemd/user/beads-dolthub-bridge.timer, then: -# systemctl --user daemon-reload -# systemctl --user enable --now beads-dolthub-bridge.timer -# -# Cadence is deliberately coarse: DoltHub is only the off-tailnet cloud path + -# backup, and per Tim cloud sessions are rare and essentially never concurrent -# with local work. Persistent=true catches up one missed run after the host -# was asleep. - -[Unit] -Description=Run the beads DoltHub bridge every 15 minutes - -[Timer] -OnBootSec=5min -OnUnitActiveSec=15min -Persistent=true -Unit=beads-dolthub-bridge.service - -[Install] -WantedBy=timers.target diff --git a/scripts/beads-server/dolt-sql-server.service b/scripts/beads-server/dolt-sql-server.service deleted file mode 100644 index 1c19b71f6..000000000 --- a/scripts/beads-server/dolt-sql-server.service +++ /dev/null @@ -1,31 +0,0 @@ -# systemd --user unit: the shared PinPoint beads Dolt SQL server (Bazzite). -# -# TEMPLATE. Install to ~/.config/systemd/user/dolt-sql-server.service, then: -# systemctl --user daemon-reload -# systemctl --user enable --now dolt-sql-server.service -# Requires user lingering (loginctl enable-linger "$USER") so it runs headless. -# -# Invokes dolt explicitly through user-global `mise` context rather than -# relying on shell activation or a Linuxbrew PATH. -# Disables surprise auto-install and fallback (fails closed). -# %h expands to the user's home directory. - -[Unit] -Description=Dolt SQL server for shared PinPoint beads DB -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin -Environment=MISE_EXEC_AUTO_INSTALL=false -Environment=MISE_NOT_FOUND_AUTO_INSTALL=false -Environment=MISE_NOT_FOUND_SYSTEM_FALLBACK=false -# Absolute path to the seeded Dolt data dir (holds the `PP` database). -WorkingDirectory=%h/.beads-server/dolt -ExecStart=%h/.local/bin/mise exec -- /usr/bin/bash %h/.beads-server/dolt-sql-server.sh --config %h/.beads-server/server.yaml --skip-root-user-initialization -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=default.target diff --git a/scripts/beads-server/dolt-sql-server.sh b/scripts/beads-server/dolt-sql-server.sh deleted file mode 100755 index ec2c1850d..000000000 --- a/scripts/beads-server/dolt-sql-server.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# dolt-sql-server.sh — start dolt sql-server after validating version against PinPoint manifest. -# -# Sourced/executed under user-global mise on Bazzite. Validates that the active -# dolt binary matches scripts/beads-compatibility.json in the PinPoint repository -# before starting the server, preventing silent version drift from mutating the -# shared beads database. - -set -euo pipefail - -PINPOINT_DIR="${PINPOINT_DIR:-$HOME/Code/PinPoint}" -COMPAT_FILE="$PINPOINT_DIR/scripts/beads-compatibility.json" - -log() { printf '[dolt-sql-server] %s\n' "$*" >&2; } -die() { printf '[dolt-sql-server] ERROR: %s\n' "$*" >&2; exit 1; } - -command -v dolt >/dev/null 2>&1 || die "dolt not found on PATH" - -if [[ ! -f "$COMPAT_FILE" ]]; then - die "compatibility manifest not found at $COMPAT_FILE" -fi - -DOLT_PINNED="$(sed -nE 's/^[[:space:]]*"dolt"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$COMPAT_FILE" | head -n1 || true)" -[[ -n "$DOLT_PINNED" ]] || die "could not parse \"dolt\" version from $COMPAT_FILE" - -dolt_raw="$(dolt version 2>&1 || true)" -dolt_ver="$(printf '%s\n' "$dolt_raw" | sed -nE 's/^dolt version ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n1 || true)" -[[ -n "$dolt_ver" ]] || die "could not parse version from 'dolt version': $dolt_raw" - -if [[ "$dolt_ver" != "$DOLT_PINNED" ]]; then - die "dolt $dolt_ver != pinned $DOLT_PINNED from $COMPAT_FILE — refusing to start server against shared DB" -fi - -log "dolt $dolt_ver matches compatibility contract ($COMPAT_FILE) — starting sql-server" -exec dolt sql-server "$@" diff --git a/scripts/beads-server/server.yaml b/scripts/beads-server/server.yaml deleted file mode 100644 index fa5c28b82..000000000 --- a/scripts/beads-server/server.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Dolt SQL-server config for the shared PinPoint beads database. -# Used on the Bazzite host: `dolt sql-server --config server.yaml`. -# -# This is a TEMPLATE. `host` is the Bazzite Tailscale IP (100.87.228.116) — Mac -# MagicDNS does not resolve *.ts.net, so both machines connect by raw Tailscale IP. -# Edit data_dir / privilege_file to match the real on-host paths from SETUP.md -# before first boot. -# -# IMPORTANT: do NOT reference an env var (dollar-sign + curly-brace syntax) anywhere -# in this file, even in a comment — dolt interpolates such tokens across the whole -# YAML and aborts the boot if any referenced var is unset. -# -# Password is NEVER in this file: the `beads` user is created once with a -# GRANT + IDENTIFIED BY (SETUP.md step 4), and clients pass the secret via the -# BEADS_DOLT_PASSWORD env var only. --skip-root-user-initialization keeps a -# hardened boot from re-minting a passwordless root. - -log_level: info - -listener: - # Bind to the Tailscale interface so Mac + Bazzite reach it over the tailnet. - host: "100.87.228.116" - port: 3306 - max_connections: 100 - read_timeout_millis: 28800000 - write_timeout_millis: 28800000 - -# Absolute path to the Dolt data directory that holds the seeded `PP` database -# (created by `dolt clone … PP` in SETUP.md step 3). Edit to the real home -# (Tim's is /home/froeht). -data_dir: "/home//.beads-server/dolt" - -# Persisted privileges/users table so the `beads` grant survives restarts. -privilege_file: "/home//.beads-server/privileges.db" - -behavior: - # Commit each write immediately. bd's default auto-commit is OFF; the DoltHub - # bridge chain (commit → pull → push) assumes a committed working set before it - # pulls, so this must be ON. Also pinned in the server-side bd config - # (dolt.auto-commit: on) — see SETUP.md. - autocommit: true - # Multi-statement client queries left at dolt's default (not disabled); bd - # issues single statements, so this is informational only. - disable_client_multi_statements: false - # NOTE: dolt sql-server does not auto-GC. Storage reclaim is a manual - # `dolt gc` / `bd flatten` step during a quiet window — see SETUP.md. diff --git a/scripts/tests/test_beads_compatibility.py b/scripts/tests/test_beads_compatibility.py index 1b585519b..16c314e7d 100644 --- a/scripts/tests/test_beads_compatibility.py +++ b/scripts/tests/test_beads_compatibility.py @@ -1,8 +1,7 @@ -"""Tests for PinPoint bd & Dolt compatibility contract and cloud/service integration. +"""Tests for PinPoint cloud Beads compatibility and bootstrap integration. -Tests the machine-readable manifest at scripts/beads-compatibility.json, -version parsing across setup/init scripts, fail-closed guard behaviors, -and Bazzite systemd service unit template configuration. +Tests the vendored fresh-cloud compatibility snapshot, cloud setup/init scripts, +and fail-closed guard behaviors. """ import hashlib @@ -19,12 +18,9 @@ pytestmark = pytest.mark.integration REPO_ROOT = Path(__file__).resolve().parent.parent.parent -MANIFEST_PATH = REPO_ROOT / "scripts" / "beads-compatibility.json" +MANIFEST_PATH = REPO_ROOT / "scripts" / "beads-cloud-compatibility.json" SETUP_SCRIPT = REPO_ROOT / "scripts" / "beads-cloud-setup.sh" INIT_SCRIPT = REPO_ROOT / "scripts" / "beads-cloud-init.sh" -DOLT_SERVICE = REPO_ROOT / "scripts" / "beads-server" / "dolt-sql-server.service" -BRIDGE_SERVICE = REPO_ROOT / "scripts" / "beads-server" / "beads-dolthub-bridge.service" -SETUP_MD = REPO_ROOT / "scripts" / "beads-server" / "SETUP.md" RUNBOOK_MD = REPO_ROOT / "docs" / "runbooks" / "cloud-routines-beads-access.md" @@ -58,7 +54,7 @@ def test_cloud_assets_have_approved_sha256_digests(self): class TestVersionParsing: def test_setup_script_regex_extracts_versions(self): setup_content = SETUP_SCRIPT.read_text(encoding="utf-8") - assert "beads-compatibility.json" in setup_content + assert "beads-cloud-compatibility.json" in setup_content manifest_content = MANIFEST_PATH.read_text(encoding="utf-8") data = json.loads(manifest_content) @@ -78,7 +74,7 @@ def test_setup_script_regex_extracts_versions(self): def test_init_script_regex_extracts_versions(self): init_content = INIT_SCRIPT.read_text(encoding="utf-8") - assert "beads-compatibility.json" in init_content + assert "beads-cloud-compatibility.json" in init_content manifest_content = MANIFEST_PATH.read_text(encoding="utf-8") data = json.loads(manifest_content) @@ -378,241 +374,9 @@ def test_guard_fails_with_mismatched_dolt_version(self, tmp_path: Path): assert proc.returncode != 0 -DOLT_LAUNCHER = REPO_ROOT / "scripts" / "beads-server" / "dolt-sql-server.sh" -BRIDGE_SCRIPT = REPO_ROOT / "scripts" / "beads-server" / "beads-dolthub-bridge.sh" - - -def run_bridge_pull_conflict( - tmp_path: Path, *, conflict_state: str -) -> tuple[subprocess.CompletedProcess[str], list[str]]: - """Run one bridge conflict cycle against deterministic bd/dolt stubs.""" - bin_dir = tmp_path / "bin" - bin_dir.mkdir() - calls_file = tmp_path / "dolt-calls" - state_file = tmp_path / "dolt-state" - - data = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) - bd_bin = bin_dir / "bd" - bd_bin.write_text( - f"""#!/bin/sh -case "$*" in - version) echo 'bd version {data["bd"]} (test)' ;; - 'dolt commit') exit 0 ;; - 'dolt pull') echo 'merge conflicts in issues require operator resolution' >&2; exit 1 ;; - *) echo "unexpected bd call: $*" >&2; exit 88 ;; -esac -""", - encoding="utf-8", - ) - bd_bin.chmod(0o755) - - dolt_bin = bin_dir / "dolt" - dolt_bin.write_text( - f"""#!/bin/sh -if [ "$1" = version ]; then - echo 'dolt version {data["dolt"]}' - exit 0 -fi -printf '%s\\n' "$*" >> "$DOLT_CALLS_FILE" -if printf '%s' "$*" | grep -q 'is_merging'; then - printf 'is_merging\\n' - if [ "$DOLT_TEST_CONFLICT_STATE" = clean ] || [ -f "$DOLT_STATE_FILE" ]; then - printf '0\\n' - else - printf '1\\n' - fi -elif printf '%s' "$*" | grep -q 'DOLT_MERGE'; then - touch "$DOLT_STATE_FILE" -else - echo "unexpected dolt call: $*" >&2 - exit 89 -fi -""", - encoding="utf-8", - ) - dolt_bin.chmod(0o755) - - env = { - "PATH": f"{bin_dir}:/usr/bin:/bin", - "HOME": str(tmp_path), - "PINPOINT_DIR": str(REPO_ROOT), - "BEADS_DOLT_PASSWORD": "dummy", - "BEADS_SERVER_HOST": "test-host", - "BEADS_SERVER_PORT": "13306", - "BEADS_SERVER_USER": "test-user", - "BEADS_DB": "PP", - "DOLT_CALLS_FILE": str(calls_file), - "DOLT_STATE_FILE": str(state_file), - "DOLT_TEST_CONFLICT_STATE": conflict_state, - } - proc = subprocess.run( - ["bash", str(BRIDGE_SCRIPT)], env=env, capture_output=True, text=True - ) - calls = calls_file.read_text(encoding="utf-8").splitlines() - return proc, calls - - -class TestBazziteServiceTemplates: - def test_dolt_service_uses_mise_exec_and_launcher(self): - content = DOLT_SERVICE.read_text(encoding="utf-8") - assert ( - "mise exec -- /usr/bin/bash %h/.beads-server/dolt-sql-server.sh" in content - ) - assert "MISE_EXEC_AUTO_INSTALL=false" in content - assert "MISE_NOT_FOUND_AUTO_INSTALL=false" in content - assert "MISE_NOT_FOUND_SYSTEM_FALLBACK=false" in content - assert "linuxbrew" not in content - - def test_bridge_service_uses_mise_exec(self): - content = BRIDGE_SERVICE.read_text(encoding="utf-8") - assert ( - "mise exec -- /usr/bin/bash %h/.beads-server/beads-dolthub-bridge.sh" - in content - ) - assert "MISE_EXEC_AUTO_INSTALL=false" in content - assert "MISE_NOT_FOUND_AUTO_INSTALL=false" in content - assert "MISE_NOT_FOUND_SYSTEM_FALLBACK=false" in content - assert "linuxbrew" not in content - - -class TestBazziteServiceGuards: - def test_dolt_launcher_validates_manifest(self, tmp_path: Path): - bin_dir = tmp_path / "bin" - bin_dir.mkdir() - - data = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) - dolt_ver = data["dolt"] - - dolt_bin = bin_dir / "dolt" - dolt_bin.write_text( - f"#!/bin/sh\nif [ \"$1\" = 'version' ]; then echo 'dolt version {dolt_ver}'; exit 0; fi\necho \"server mock $@\"\n", - encoding="utf-8", - ) - dolt_bin.chmod(0o755) - - env = { - "PATH": f"{bin_dir}:/usr/bin:/bin", - "HOME": str(tmp_path), - "PINPOINT_DIR": str(REPO_ROOT), - } - - proc = subprocess.run( - ["bash", str(DOLT_LAUNCHER), "--help"], - env=env, - capture_output=True, - text=True, - ) - assert proc.returncode == 0 - assert "matches compatibility contract" in proc.stderr - - def test_dolt_launcher_fails_on_version_mismatch(self, tmp_path: Path): - bin_dir = tmp_path / "bin" - bin_dir.mkdir() - - dolt_bin = bin_dir / "dolt" - dolt_bin.write_text( - "#!/bin/sh\necho 'dolt version 9.9.9'\n", - encoding="utf-8", - ) - dolt_bin.chmod(0o755) - - env = { - "PATH": f"{bin_dir}:/usr/bin:/bin", - "HOME": str(tmp_path), - "PINPOINT_DIR": str(REPO_ROOT), - } - - proc = subprocess.run( - ["bash", str(DOLT_LAUNCHER), "--help"], - env=env, - capture_output=True, - text=True, - ) - assert proc.returncode != 0 - assert "refusing to start server" in proc.stderr - - def test_bridge_script_fails_on_bd_version_mismatch(self, tmp_path: Path): - bin_dir = tmp_path / "bin" - bin_dir.mkdir() - - data = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) - dolt_ver = data["dolt"] - - bd_bin = bin_dir / "bd" - bd_bin.write_text( - "#!/bin/sh\necho 'bd version 0.0.1'\n", - encoding="utf-8", - ) - bd_bin.chmod(0o755) - - dolt_bin = bin_dir / "dolt" - dolt_bin.write_text( - f"#!/bin/sh\necho 'dolt version {dolt_ver}'\n", - encoding="utf-8", - ) - dolt_bin.chmod(0o755) - - env = { - "PATH": f"{bin_dir}:/usr/bin:/bin", - "HOME": str(tmp_path), - "PINPOINT_DIR": str(REPO_ROOT), - "BEADS_DOLT_PASSWORD": "dummy", - } - - proc = subprocess.run( - ["bash", str(BRIDGE_SCRIPT)], - env=env, - capture_output=True, - text=True, - ) - assert proc.returncode != 0 - assert "refusing bridge cycle" in proc.stderr - - def test_bridge_conflict_recovery_uses_remote_dolt_connection_flags( - self, tmp_path: Path - ): - """Connection flags belong to the root `dolt` command, before `sql`.""" - proc, calls = run_bridge_pull_conflict(tmp_path, conflict_state="clean") - - assert proc.returncode != 0 - assert calls - assert all( - call.startswith( - "--host test-host --port 13306 --user test-user " - "--password dummy --no-tls --use-db PP sql " - ) - for call in calls - ) - - def test_bridge_skips_abort_when_pull_already_restored_conflicts( - self, tmp_path: Path - ): - proc, calls = run_bridge_pull_conflict(tmp_path, conflict_state="clean") - - assert proc.returncode != 0 - assert any("is_merging" in call for call in calls) - assert all("DOLT_MERGE" not in call for call in calls) - assert "no merge remains active" in proc.stderr - - def test_bridge_aborts_and_verifies_a_schema_only_conflict(self, tmp_path: Path): - proc, calls = run_bridge_pull_conflict(tmp_path, conflict_state="active") - - assert proc.returncode != 0 - assert sum("is_merging" in call for call in calls) == 2 - assert sum("DOLT_MERGE" in call for call in calls) == 1 - assert "merge aborted; no merge remains active" in proc.stderr - - class TestDocumentationReferences: - def test_setup_md_references_manifest(self): - content = SETUP_MD.read_text(encoding="utf-8") - assert "scripts/beads-compatibility.json" in content - assert "mise exec -- dolt" in content - assert "mise exec -- bd" in content - assert "Disposable compatibility testing" in content - def test_runbook_references_manifest(self): content = RUNBOOK_MD.read_text(encoding="utf-8") - assert "scripts/beads-compatibility.json" in content + assert "scripts/beads-cloud-compatibility.json" in content assert "dolt" in content assert "bd" in content diff --git a/scripts/tests/test_check_skill_frontmatter.py b/scripts/tests/test_check_skill_frontmatter.py index 1fc97a5db..90fcc0c98 100644 --- a/scripts/tests/test_check_skill_frontmatter.py +++ b/scripts/tests/test_check_skill_frontmatter.py @@ -323,7 +323,8 @@ def test_find_skill_files(tmp_path: Path): def test_real_repo_skills_pass(): skills = find_skill_files([Path(".agents/skills")]) - assert len(skills) >= 16 + # The generic Beads skill is user-global in dotfiles, not repository-local. + assert len(skills) >= 15 for skill_file in skills: errors = check_skill_file(skill_file, config_path=CONFIG_PATH) assert errors == [], f"Errors in {skill_file}: {errors}" From 985551c4c05b589f94320d1a2d736dc83724e141 Mon Sep 17 00:00:00 2001 From: Tim Froehlich Date: Thu, 10 Sep 2026 19:18:55 -0500 Subject: [PATCH 2/4] chore(beads): keep the compatibility manifest name; describe the Mac-only topology The original branch renamed scripts/beads-compatibility.json to beads-cloud-compatibility.json and described it as a snapshot of a dotfiles contract at all/.agents/beads/compatibility.json. That contract never landed (dotfiles PR #22 was closed unmerged), and the rename would have broken both dotfiles' drift test and scripts/tests/test_ci_change_detection.py, which name the file. The manifest stays where it was and remains the single source; the cloud scripts are restored to main's copy. Prose now describes what actually runs: beads is a local embedded Dolt database on the Mac with DoltHub as the sync remote, cloud routines install exactly the pinned versions, and the Mac's user-global mise pins are checked against this file by a dotfiles test. No Bazzite services exist. AGENTS.md's huddle paragraph pointed at ~/.agents/huddle/, which no longer exists. The huddle is now a public repository installed as a plugin, checked out at ~/Code/huddle; the Mac holds the leader role. Co-Authored-By: Claude Opus 5 --- .agents/skills/pinpoint-chores/SKILL.md | 2 +- AGENTS.md | 18 +++++++++------- README.md | 14 ++++++------- docs/runbooks/cloud-routines-beads-access.md | 21 ++++++++++--------- scripts/beads-cloud-init.sh | 10 ++++----- scripts/beads-cloud-setup.sh | 9 ++++---- ...tibility.json => beads-compatibility.json} | 2 +- scripts/tests/test_beads_compatibility.py | 8 +++---- scripts/tests/test_merge_pr_automerge.py | 2 +- 9 files changed, 44 insertions(+), 42 deletions(-) rename scripts/{beads-cloud-compatibility.json => beads-compatibility.json} (77%) diff --git a/.agents/skills/pinpoint-chores/SKILL.md b/.agents/skills/pinpoint-chores/SKILL.md index 5615ae1bb..55a4fbc01 100644 --- a/.agents/skills/pinpoint-chores/SKILL.md +++ b/.agents/skills/pinpoint-chores/SKILL.md @@ -71,7 +71,7 @@ Then work the checklist. For each item, note findings as a comment on the bead ( - If it's newer than the current pin (mind major bumps — read the pnpm release notes/migration guide first), update `packageManager` in `package.json` with the new version and its sha512 integrity hash (e.g. from `npm view pnpm@ dist.integrity` converted to `+sha512.`), run `mise lock` and then `mise install --locked`, then verify no unexpected `pnpm-lock.yaml` churn (`pnpm install --frozen-lockfile`), `pnpm audit --audit-level=high` still resolves, and `pnpm run check` is green. PR it through the normal workflow; file a bead if a major bump needs real migration work. - **Vercel CLI pin** (PP-h2ui.7). Privileged Vercel CLI invocations use one repository-owned wrapper: `scripts/workflow/preview/vercel-cli.sh`. Compare the pinned `VERCEL_CLI_VERSION` against the latest release on npm (applying a 14/30-day cooldown). Bumping is a single-site edit in `scripts/workflow/preview/vercel-cli.sh`. - - **bd and Dolt compatibility version pins** (from the 2026-08-16 shared-DB schema incident). Dotfiles owns the local/Bazzite runtime contract at `all/.agents/beads/compatibility.json`. PinPoint vendors `scripts/beads-cloud-compatibility.json` because fresh cloud checkouts cannot assume dotfiles exist; only the cloud setup and init scripts consume that snapshot. For an upgrade, validate the newer tools against the shared database, update the dotfiles contract first, then update the PinPoint cloud snapshot and digests as a paired rollout. Exact pins are deliberate: an accidental _newer_ release migrated the shared DB and locked every client out for two days, so a loud refusal is the safe failure. + - **bd and Dolt compatibility version pins** (from the 2026-08-16 shared-DB schema incident). PinPoint declares exact versions for `bd` and `dolt` at a **single source**: `scripts/beads-compatibility.json`. Cloud routines install exactly those pins (`scripts/beads-cloud-setup.sh`) and refuse to touch the DB on a mismatch (`scripts/beads-cloud-init.sh`); the Mac's user-global mise declarations in dotfiles are checked against the same file by a dotfiles test. For an upgrade, validate the newer tools against the local embedded database and a `bd dolt push` round-trip, bump the manifest and its archive digests, then update the dotfiles mise pins to match. Exact pins are deliberate: an accidental _newer_ release migrated the shared DB and locked every client out for two days, so a loud refusal is the safe failure. 2. **TypeScript compiler maintenance** - TypeScript 7 is installed as `typescript`; its native `tsc` runs the app, test, E2E, and Next build type checks. Read `docs/plans/2026-06-27-typescript-7-upgrade-plan.md` only for the rollout record. diff --git a/AGENTS.md b/AGENTS.md index 301e16569..ea9693b61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,14 +45,16 @@ Before exploring or changing non-mechanical product behavior, read ADRs. Skip it for mechanical changes that do not affect product behavior or domain language. -**The huddle is global, not a PinPoint subsystem.** Its implementation, trusted -repository registry, harness registrations, tests, and user services live in -Tim's dotfiles. Shared scripts are at `~/.agents/huddle/`; agent-writable state -lives under `$XDG_STATE_HOME/agents-huddle/agent/`. Global hooks silently -self-disable outside registered repositories. The Mac updater and Bazzite -leader service own fetch/fast-forward work; Bazzite alone posts merge -announcements. PinPoint keeps only its Beads actor hook, which asks the global -`huddle-whoami.sh` interface for the registered identity. +**The huddle is global, not a PinPoint subsystem.** Its implementation, skill, +and tests live in the public `timothyfroehlich/huddle` repository, installed as +a plugin in each harness and checked out at `~/Code/huddle`; the trusted +repository registry and the Mac launchd job stay in Tim's dotfiles. Shared +scripts are at `~/Code/huddle/lib/`; agent-writable state lives under +`$XDG_STATE_HOME/agents-huddle/agent/`. Plugin hooks silently self-disable +outside registered repositories. The Mac leader service owns fetch and +fast-forward work and posts merge announcements. PinPoint keeps only its Beads +actor hook, which asks the global `huddle-whoami.sh` interface for the +registered identity. ## 4. Environment diff --git a/README.md b/README.md index a13404ca5..d359d01d4 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,13 @@ Homebrew, or another version manager. ### Tool Ownership -| Surface | Authority | -| :------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | -| Project Node, Python, Ruff, and Supabase CLI | Exact pins in `mise.toml`; resolved artifacts in `mise.lock` | -| pnpm executable | Exact version and sha512 integrity in `package.json#packageManager`, installed by mise | -| Project commands | `package.json#scripts`; mise resolves tools but does not duplicate the task namespace | -| Local/Bazzite `bd` and Dolt | Dotfiles `all/.agents/beads/compatibility.json` and its user-global mise declarations | -| Cloud-routine `bd` and Dolt | `scripts/beads-cloud-compatibility.json`, setup, and init; this vendored path remains because fresh cloud checkouts do not have dotfiles | +| Surface | Authority | +| :------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | +| Project Node, Python, Ruff, and Supabase CLI | Exact pins in `mise.toml`; resolved artifacts in `mise.lock` | +| pnpm executable | Exact version and sha512 integrity in `package.json#packageManager`, installed by mise | +| Project commands | `package.json#scripts`; mise resolves tools but does not duplicate the task namespace | +| Local `bd` and Dolt | User-global mise declarations in dotfiles, checked against `scripts/beads-compatibility.json` by a dotfiles test | +| Cloud-routine `bd` and Dolt | `scripts/beads-cloud-setup.sh` plus `scripts/beads-cloud-init.sh`, installing exactly the pins in `scripts/beads-compatibility.json` | Vercel does not install or invoke mise. `package.json#engines` is its Node compatibility contract, `vercel-build` retains the production migration/build diff --git a/docs/runbooks/cloud-routines-beads-access.md b/docs/runbooks/cloud-routines-beads-access.md index bbc8626f7..95f40d472 100644 --- a/docs/runbooks/cloud-routines-beads-access.md +++ b/docs/runbooks/cloud-routines-beads-access.md @@ -100,13 +100,13 @@ That script installs `dolt` (pinned) and `bd` (pinned); the agent then runs **The compatibility contract pins both `bd` and `dolt`.** The 2026-08-16 lockout was a `bd` schema migration — `bd` owns `schema_migrations` and the additive migrations that broke it, while `dolt` is the storage engine. Pinning -both `bd` and `dolt` to exact versions in `scripts/beads-cloud-compatibility.json` -guarantees consistent schema handling and reproducibility across fresh cloud -sandboxes. The matching local/Bazzite runtime contract lives in Tim's dotfiles -at `all/.agents/beads/compatibility.json`. +both `bd` and `dolt` to exact versions in `scripts/beads-compatibility.json` +guarantees consistent schema handling and reproducibility across cloud +sandboxes and the Mac. The Mac runs the same pins through user-global mise +declarations in Tim's dotfiles, checked against this file by a dotfiles test. -**The cloud pins are single-source inside this checkout.** `beads-cloud-setup.sh` reads both -`bd` and `dolt` versions from `scripts/beads-cloud-compatibility.json` and installs +**The toolchain pins are single-source.** `beads-cloud-setup.sh` reads both +`bd` and `dolt` versions from `scripts/beads-compatibility.json` and installs exactly those by exact release tags. The same manifest declares the approved SHA-256 digest for each supported cloud platform. Setup downloads both archives into an isolated temporary directory, verifies both before extracting or @@ -123,10 +123,11 @@ Caveat, now narrowed: only the one-line shim lives in the un-diffable UI — the install logic it calls is in git. The reviewable, enforced backstop remains the version guard in `scripts/beads-cloud-init.sh`, which refuses to touch the DB unless both the installed `bd` and `dolt` equal their pins in -`scripts/beads-cloud-compatibility.json`. +`scripts/beads-compatibility.json`. -An upgrade is a paired rollout: update and validate the dotfiles runtime -contract first, then refresh this cloud snapshot and its archive digests. +An upgrade is a paired rollout: validate the newer tools against the local +embedded database, bump this manifest and its archive digests, then update +the dotfiles mise pins to match. ## Credential setup (one-time) @@ -167,7 +168,7 @@ bd dolt push The script reads `DOLT_CREDS_JWK`, `DOLT_CREDS_PUB`, and `BEADS_SYNC_REMOTE` from the environment (agent-runtime only — see #55440 above), writes the DoltHub credential and `~/.dolt/config_global.json`, checks `bd version` and -`dolt version` against `scripts/beads-cloud-compatibility.json`, then clones into +`dolt version` against `scripts/beads-compatibility.json`, then clones into `~/beads`. It exits non-zero — refusing to touch the DB — on a version mismatch or any missing env var, so a routine fails fast instead of running against a wrong binary. The generated `user.name`/`user.email` are Dolt commit metadata diff --git a/scripts/beads-cloud-init.sh b/scripts/beads-cloud-init.sh index 17174f860..70677374e 100755 --- a/scripts/beads-cloud-init.sh +++ b/scripts/beads-cloud-init.sh @@ -14,7 +14,7 @@ # bash scripts/beads-cloud-init.sh && cd ~/beads # # THE VERSION PINS (loud, exact, deliberate). This refuses to proceed unless bd -# and dolt are EXACTLY the versions declared in scripts/beads-cloud-compatibility.json. +# and dolt are EXACTLY the versions declared in scripts/beads-compatibility.json. # Rationale: an accidental newer beads release (1.2.1, 2026-08-16) migrated the # shared DB to a schema no supported binary could read and locked every client out # for two days. A stale exact pin fails LOUD ("routine refuses to run") — the safe @@ -24,7 +24,7 @@ # shim in the claude.ai UI cannot be diffed. These guards are the reviewable, # enforced backstop. # -# When upgrading past the pins, bump scripts/beads-cloud-compatibility.json. It is a +# When upgrading past the pins, bump scripts/beads-compatibility.json. It is a # weekly-chores checklist item so the bump is a known recurring task, not a # surprise Saturday outage. # @@ -51,7 +51,7 @@ set -euo pipefail # hit exactly this after #1908 shipped the repair but computed SCRIPT_DIR too # late). Resolving here, pre-cd, is the fix. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPAT_FILE="$SCRIPT_DIR/beads-cloud-compatibility.json" +COMPAT_FILE="$SCRIPT_DIR/beads-compatibility.json" log() { printf '[beads-cloud-init] %s\n' "$*" >&2; } die() { printf '[beads-cloud-init] ERROR: %s\n' "$*" >&2; exit 1; } @@ -92,7 +92,7 @@ bd_ver="$(printf '%s\n' "$bd_raw" | sed -nE 's/^bd version ([0-9]+\.[0-9]+\.[0-9 # 2. bd GUARD. Exact-pin — refuse anything else, newer OR older. if [[ "$bd_ver" != "$BD_PINNED_VERSION" ]]; then die "bd $bd_ver != pinned $BD_PINNED_VERSION — refusing to touch the shared beads DB. - If this is a deliberate upgrade, bump \"bd\" in scripts/beads-cloud-compatibility.json. + If this is a deliberate upgrade, bump \"bd\" in scripts/beads-compatibility.json. Do NOT install, build, or 'upgrade' bd inside a cloud routine to get past this." fi log "bd $bd_ver matches pin — proceeding" @@ -107,7 +107,7 @@ dolt_ver="$(printf '%s\n' "$dolt_raw" | sed -nE 's/^dolt version ([0-9]+\.[0-9]+ if [[ "$dolt_ver" != "$DOLT_PINNED_VERSION" ]]; then die "dolt $dolt_ver != pinned $DOLT_PINNED_VERSION — refusing to touch the shared beads DB. - If this is a deliberate upgrade, bump \"dolt\" in scripts/beads-cloud-compatibility.json. + If this is a deliberate upgrade, bump \"dolt\" in scripts/beads-compatibility.json. Do NOT install, build, or 'upgrade' dolt inside a cloud routine to get past this." fi log "dolt $dolt_ver matches pin — proceeding" diff --git a/scripts/beads-cloud-setup.sh b/scripts/beads-cloud-setup.sh index 6cd1b817e..25ac96771 100755 --- a/scripts/beads-cloud-setup.sh +++ b/scripts/beads-cloud-setup.sh @@ -12,8 +12,7 @@ # WHY THIS LIVES IN THE REPO. The claude.ai environment's "Setup script" field # used to hold this inline. That copy could not be reviewed or diffed, and its # version pins silently drifted. Moving the body here makes it reviewable and -# keeps the fresh-cloud pin in one reviewable snapshot: -# scripts/beads-cloud-compatibility.json. +# collapses the pin to a SINGLE source of truth: scripts/beads-compatibility.json. # The UI field is now just a one-line shim — the repo is already cloned at # container-provision time, so the shim locates the checkout and runs this script: # @@ -24,9 +23,9 @@ # ~/PinPoint resolves to /root/PinPoint and misses it. This script itself uses # BASH_SOURCE below, so it works no matter which of those paths invoked it. # -# THE CLOUD PINS ARE READ FROM THE SNAPSHOT, NOT DUPLICATED. bd and dolt are +# THE PINS ARE READ FROM COMPATIBILITY CONTRACT, NOT DUPLICATED. bd and dolt are # installed at exactly the versions this script parses out of -# scripts/beads-cloud-compatibility.json. So bumping the pin is an edit to the manifest; +# scripts/beads-compatibility.json. So bumping the pin is an edit to the manifest; # the approved cloud-asset digests, installed binaries, and runtime guards move # together and cannot disagree. # (Rationale for exact pins: an accidental newer release, e.g. bd 1.2.1 on @@ -79,7 +78,7 @@ manifest_platform_digest() { # Resolve this script's directory so the pin read below works regardless of the # setup script's cwd (it runs from $HOME, not the repo root). SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPAT_FILE="$SCRIPT_DIR/beads-cloud-compatibility.json" +COMPAT_FILE="$SCRIPT_DIR/beads-compatibility.json" # --- The pins: read from the compatibility manifest (single source of truth). -- [[ -f "$COMPAT_FILE" ]] || die "cannot find $COMPAT_FILE — is this the PinPoint checkout?" diff --git a/scripts/beads-cloud-compatibility.json b/scripts/beads-compatibility.json similarity index 77% rename from scripts/beads-cloud-compatibility.json rename to scripts/beads-compatibility.json index bc66afd5c..d4946e549 100644 --- a/scripts/beads-cloud-compatibility.json +++ b/scripts/beads-compatibility.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "Vendored compatibility snapshot for PinPoint fresh-cloud Beads bootstrap", + "description": "PinPoint compatibility contract for bd (Beads CLI) and Dolt database engine", "bd": "1.2.2", "dolt": "2.3.1", "cloudAssets": { diff --git a/scripts/tests/test_beads_compatibility.py b/scripts/tests/test_beads_compatibility.py index 16c314e7d..944cf86cf 100644 --- a/scripts/tests/test_beads_compatibility.py +++ b/scripts/tests/test_beads_compatibility.py @@ -18,7 +18,7 @@ pytestmark = pytest.mark.integration REPO_ROOT = Path(__file__).resolve().parent.parent.parent -MANIFEST_PATH = REPO_ROOT / "scripts" / "beads-cloud-compatibility.json" +MANIFEST_PATH = REPO_ROOT / "scripts" / "beads-compatibility.json" SETUP_SCRIPT = REPO_ROOT / "scripts" / "beads-cloud-setup.sh" INIT_SCRIPT = REPO_ROOT / "scripts" / "beads-cloud-init.sh" RUNBOOK_MD = REPO_ROOT / "docs" / "runbooks" / "cloud-routines-beads-access.md" @@ -54,7 +54,7 @@ def test_cloud_assets_have_approved_sha256_digests(self): class TestVersionParsing: def test_setup_script_regex_extracts_versions(self): setup_content = SETUP_SCRIPT.read_text(encoding="utf-8") - assert "beads-cloud-compatibility.json" in setup_content + assert "beads-compatibility.json" in setup_content manifest_content = MANIFEST_PATH.read_text(encoding="utf-8") data = json.loads(manifest_content) @@ -74,7 +74,7 @@ def test_setup_script_regex_extracts_versions(self): def test_init_script_regex_extracts_versions(self): init_content = INIT_SCRIPT.read_text(encoding="utf-8") - assert "beads-cloud-compatibility.json" in init_content + assert "beads-compatibility.json" in init_content manifest_content = MANIFEST_PATH.read_text(encoding="utf-8") data = json.loads(manifest_content) @@ -377,6 +377,6 @@ def test_guard_fails_with_mismatched_dolt_version(self, tmp_path: Path): class TestDocumentationReferences: def test_runbook_references_manifest(self): content = RUNBOOK_MD.read_text(encoding="utf-8") - assert "scripts/beads-cloud-compatibility.json" in content + assert "scripts/beads-compatibility.json" in content assert "dolt" in content assert "bd" in content diff --git a/scripts/tests/test_merge_pr_automerge.py b/scripts/tests/test_merge_pr_automerge.py index 4a196d39f..402719cec 100644 --- a/scripts/tests/test_merge_pr_automerge.py +++ b/scripts/tests/test_merge_pr_automerge.py @@ -138,7 +138,7 @@ def stub_repo( ) # Keep `bd` shadowed as a regression tripwire: merge-pr.sh must not post - # Huddle notices itself. The global Bazzite leader service owns that + # Huddle notices itself. The global huddle leader service owns that # side effect. bd_stub = tmp_path / "bd" bd_stub.write_text( From 988c1d04af7470b7a59f0c12d2e9dc970524ce49 Mon Sep 17 00:00:00 2001 From: Tim Froehlich Date: Thu, 10 Sep 2026 19:30:33 -0500 Subject: [PATCH 3/4] docs(beads): describe the actual sync and conflict model in the cloud runbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex review: the runbook's opening model still said concurrent cloud and live-server writes stop "the bridge", which fails closed for an operator. This branch deletes the bridge and the server, so the paragraph pointed at a recovery component that no longer exists. It now describes the topology that runs: DoltHub as hub, the Mac's embedded database pushing on auto-push and on every huddle poll, cloud routines cloning and pushing back before the sandbox ends. Conflicts are cell-level and surface on whichever side moves second — a rejected non-fast-forward push in the cloud, a failed pull on the Mac that also halts huddle rotation — and are resolved on the Mac, never with a forced push. Co-Authored-By: Claude Opus 5 --- docs/runbooks/cloud-routines-beads-access.md | 26 ++++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/runbooks/cloud-routines-beads-access.md b/docs/runbooks/cloud-routines-beads-access.md index 95f40d472..0e47122e4 100644 --- a/docs/runbooks/cloud-routines-beads-access.md +++ b/docs/runbooks/cloud-routines-beads-access.md @@ -12,14 +12,24 @@ the cloud checkout has no beads data). This runbook documents the cloud **environment** configuration that grants a routine full read + write to that DB. Proven end-to-end on 2026-07-11 (PP-3x7s). -**Model:** hybrid — routines run unattended and may write beads; a local -"chores" session reviews and acts on them. Dolt merges independent rows and -tables, but it does not semantically merge two edits to the same issue row. -Concurrent cloud and live-server updates to one issue can therefore stop the -bridge with a conflict even when both edits are legitimate. The bridge fails -closed so an operator can preserve the intended fields from both sides; never -resolve these conflicts with a blanket newest-row, `--ours`, or `--theirs` -policy. The beads remote-migrate gate remains the schema-version backstop. +**Model:** hub and spokes, with DoltHub as the hub. Tim's Mac holds a local +embedded Dolt database; it pushes to DoltHub automatically (`dolt.auto-push`, +debounced) and the huddle hooks push and pull on every throttled session poll, +so the Mac converges within minutes of any write. A cloud routine is the other +spoke: `beads-cloud-init.sh` clones from DoltHub (or pulls, if the sandbox +survived), the routine writes, and it pushes with `bd dolt push` before the +sandbox ends. There is no server and no bridge; a local "chores" session +reviews what routines wrote. + +Dolt merges independent rows and cells on pull, so two sides editing different +issues, or different fields of one issue, never conflict. Two edits to the same +cell do. Where that surfaces depends on who moves second: a cloud push that is +not a fast-forward is rejected, so the routine must `bd dolt pull` and push +again in the same run or its writes end with the sandbox; a conflicting pull on +the Mac fails, and the huddle rotation refuses to proceed until `bd dolt pull` +succeeds. Resolve it there, on the Mac, preserving the intended fields from both +sides; never with `--force` on a push or a blanket newest-row policy. The beads +remote-migrate gate remains the schema-version backstop. ## The three things that make it work From 4f92dc0e0a8547d76aaba5c32496bd941947903b Mon Sep 17 00:00:00 2001 From: Tim Froehlich Date: Thu, 10 Sep 2026 19:41:52 -0500 Subject: [PATCH 4/4] fix(hooks): point the beads actor hook at the huddle plugin checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex review. inject-beads-actor.cjs still built the whoami path as ~/.agents/huddle/huddle-whoami.sh, and its test installed the stub at that same location, so the test kept passing after the directory was deleted. Since the huddle moved to its plugin checkout, every implicit Claude `bd` write in PinPoint has hit the fail-open catch and been stamped as the generic "Claude" instead of the session's registered identity. The hook now calls ~/Code/huddle/lib/huddle-whoami.sh — the checkout, not the harness plugin cache, because that path carries the plugin version. Verified against the live path: the hook resolves this session's registered name. The path is assembled with path.join, which is why a grep for the old string missed it. Co-Authored-By: Claude Opus 5 --- .claude/hooks/inject-beads-actor.cjs | 6 +++++- scripts/tests/test_inject_beads_actor.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.claude/hooks/inject-beads-actor.cjs b/.claude/hooks/inject-beads-actor.cjs index c3df3559f..3c87f1b6e 100644 --- a/.claude/hooks/inject-beads-actor.cjs +++ b/.claude/hooks/inject-beads-actor.cjs @@ -42,10 +42,14 @@ function resolveActor(sessionId, cwd) { if (!home) { return fallback; } + // The huddle is a plugin checked out at ~/Code/huddle; the launchd job in + // dotfiles uses the same path. Not the harness plugin cache, whose path + // carries the plugin version and changes on every release. const whoami = path.join( home, - ".agents", + "Code", "huddle", + "lib", "huddle-whoami.sh" ); const name = execFileSync("bash", [whoami, "whoami", sessionId], { diff --git a/scripts/tests/test_inject_beads_actor.py b/scripts/tests/test_inject_beads_actor.py index cf543ff3c..7fed3d6fb 100644 --- a/scripts/tests/test_inject_beads_actor.py +++ b/scripts/tests/test_inject_beads_actor.py @@ -24,7 +24,7 @@ def find_node() -> str: def install_whoami_stub(home: Path) -> Path: - script = home / ".agents" / "huddle" / "huddle-whoami.sh" + script = home / "Code" / "huddle" / "lib" / "huddle-whoami.sh" script.parent.mkdir(parents=True, exist_ok=True) script.write_text( "#!/usr/bin/env bash\n"