From af96ee8a5abb4cfae5103ea109d168864c375fac Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 8 Jul 2026 22:26:16 -0700 Subject: [PATCH] Seed Cargo target caches safely Replace full target sharing with a seed-only workflow after shared CARGO_TARGET_DIR produced stale local-package artifacts. The check gate now rejects unsafe external target dirs and the docs point agents at the private-target seeding helper. --- AGENT.md | 13 +++--- tools/check.sh | 29 ++++++++++-- tools/seed-cargo-target.sh | 91 ++++++++++++++++++++++++++++++++++++ tools/shared-cargo-target.sh | 46 ------------------ wiki/log/DEVLOG.md | 13 ++++++ wiki/process/workflows.md | 22 +++++---- 6 files changed, 148 insertions(+), 66 deletions(-) create mode 100755 tools/seed-cargo-target.sh delete mode 100644 tools/shared-cargo-target.sh diff --git a/AGENT.md b/AGENT.md index 9e200abe..08c76438 100644 --- a/AGENT.md +++ b/AGENT.md @@ -55,12 +55,13 @@ behavior change. - **Always use a worktree.** Agent sessions do not edit the primary checkout directly. Create a task-named git worktree, work there, and keep unrelated work out of the diff. -- **Share the Cargo build cache.** After entering a worktree, run - `source tools/shared-cargo-target.sh` before ad-hoc `cargo test`, - `cargo clippy`, or Bevy build commands. `./tools/check.sh` does this - automatically. Do not let every worktree grow its own multi-GB `target/` - directory unless you deliberately need an isolated build cache; the shared - target keeps Bevy/wgpu dependency builds warm across agents. +- **Seed, don't share, Cargo build caches.** After entering a worktree, run + `tools/seed-cargo-target.sh` once before long `cargo test`, `cargo clippy`, + or Bevy build commands. Do **not** point multiple worktrees at one shared + `CARGO_TARGET_DIR`: Cargo can reuse stale local-crate artifacts across + checkouts and give false-green tests. The seed script copies warm dependency + artifacts, scrubs `misaligned` outputs, and leaves the worktree with a + private `target/` that rebuilds local code correctly. - **Commit aggressively.** Cameron has standing permission for agents to commit coherent completed work in this repository. Do not stop to ask for commit permission unless there is a pending product/design question, diff --git a/tools/check.sh b/tools/check.sh index b7397504..786b7fa1 100755 --- a/tools/check.sh +++ b/tools/check.sh @@ -5,14 +5,33 @@ set -euo pipefail cd "$(dirname "$0")/.." -# Worktrees should not each rebuild Bevy/wgpu/etc. into private target trees. -# Share the primary checkout's target/ unless the caller deliberately set a -# different CARGO_TARGET_DIR. -source tools/shared-cargo-target.sh - fail=0 step() { printf '\n=== %s ===\n' "$1"; } +guard_cargo_target_dir() { + # Sharing one CARGO_TARGET_DIR across git worktrees can produce false-green + # checks: Cargo may reuse the local package artifact from another checkout. + # Seed private targets with tools/seed-cargo-target.sh instead. + [ -n "${CARGO_TARGET_DIR:-}" ] || return 0 + [ "${MISALIGNED_ALLOW_EXTERNAL_TARGET:-0}" = "1" ] && return 0 + + local root target default_target + root=$(pwd -P) + mkdir -p "$CARGO_TARGET_DIR" "$root/target" + target=$(cd "$CARGO_TARGET_DIR" && pwd -P) + default_target=$(cd "$root/target" && pwd -P) + + if [ "$target" != "$default_target" ]; then + echo "FAIL: CARGO_TARGET_DIR points outside this worktree: $target" + echo " Cargo target dirs are not safe to share across Misaligned worktrees." + echo " Run tools/seed-cargo-target.sh once, then unset CARGO_TARGET_DIR." + echo " Override only for a deliberate isolated target with MISALIGNED_ALLOW_EXTERNAL_TARGET=1." + exit 1 + fi +} + +guard_cargo_target_dir + step "format check" cargo fmt --check || { echo "FAIL: run 'cargo fmt'"; fail=1; } diff --git a/tools/seed-cargo-target.sh b/tools/seed-cargo-target.sh new file mode 100755 index 00000000..63ff1c3d --- /dev/null +++ b/tools/seed-cargo-target.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Seed this worktree's private Cargo target directory from the primary +# checkout's target/ without sharing local crate artifacts across source trees. +# +# Full CARGO_TARGET_DIR sharing between git worktrees is unsafe for this repo: +# Cargo can consider the local `misaligned` package fresh from another checkout +# and run stale test binaries. This helper only copies the cache, then removes +# the local package outputs so the current worktree's crate is rebuilt while +# heavy third-party dependencies stay warm. +set -euo pipefail +cd "$(dirname "$0")/.." + +primary_checkout() { + local common + common=$(git rev-parse --path-format=absolute --git-common-dir) + case "$common" in + */.git) printf '%s\n' "${common%/.git}" ;; + */.git/worktrees/*) printf '%s\n' "${common%%/.git/worktrees/*}" ;; + *) git rev-parse --path-format=absolute --show-toplevel ;; + esac +} + +root=$(git rev-parse --path-format=absolute --show-toplevel) +primary=$(primary_checkout) +source_target=${MISALIGNED_SEED_TARGET_FROM:-$primary/target} +dest_target=${CARGO_TARGET_DIR:-$root/target} + +if [ "$root" = "$primary" ]; then + echo "primary checkout already owns $primary/target; nothing to seed" + exit 0 +fi + +if [ ! -d "$source_target" ]; then + echo "no source target cache at $source_target" + exit 0 +fi + +if [ -L "$dest_target" ]; then + echo "refusing to seed symlinked target: $dest_target" + echo "remove the symlink first; each worktree needs a private target/ directory" + exit 1 +fi + +mkdir -p "$dest_target" + +rsync -a \ + --exclude '/debug/incremental/' \ + --exclude '/debug/.fingerprint/misaligned-*' \ + --exclude '/debug/.fingerprint/misaligned_bevy-*' \ + --exclude '/debug/.fingerprint/misaligned_assets-*' \ + --exclude '/debug/.fingerprint/act_one-*' \ + --exclude '/debug/deps/libmisaligned-*' \ + --exclude '/debug/deps/misaligned-*' \ + --exclude '/debug/deps/misaligned_bevy-*' \ + --exclude '/debug/deps/misaligned_assets-*' \ + --exclude '/debug/deps/act_one-*' \ + --exclude '/debug/misaligned*' \ + --exclude '/release/incremental/' \ + --exclude '/release/.fingerprint/misaligned-*' \ + --exclude '/release/.fingerprint/misaligned_bevy-*' \ + --exclude '/release/.fingerprint/misaligned_assets-*' \ + --exclude '/release/.fingerprint/act_one-*' \ + --exclude '/release/deps/libmisaligned-*' \ + --exclude '/release/deps/misaligned-*' \ + --exclude '/release/deps/misaligned_bevy-*' \ + --exclude '/release/deps/misaligned_assets-*' \ + --exclude '/release/deps/act_one-*' \ + --exclude '/release/misaligned*' \ + "$source_target/" "$dest_target/" + +# Belt and suspenders for artifacts copied by a future target layout or by a +# user-supplied MISALIGNED_SEED_TARGET_FROM. +find "$dest_target" -path '*/incremental' -type d -prune -exec rm -rf {} + +find "$dest_target" -path '*/.fingerprint/misaligned-*' -prune -exec rm -rf {} + +find "$dest_target" -path '*/.fingerprint/misaligned_bevy-*' -prune -exec rm -rf {} + +find "$dest_target" -path '*/.fingerprint/misaligned_assets-*' -prune -exec rm -rf {} + +find "$dest_target" -path '*/.fingerprint/act_one-*' -prune -exec rm -rf {} + +find "$dest_target" -path '*/incremental/misaligned-*' -prune -exec rm -rf {} + +find "$dest_target" -path '*/deps/libmisaligned-*' -type f -delete +find "$dest_target" -path '*/deps/misaligned-*' -type f -delete +find "$dest_target" -path '*/deps/misaligned_bevy-*' -type f -delete +find "$dest_target" -path '*/deps/misaligned_assets-*' -type f -delete +find "$dest_target" -path '*/deps/act_one-*' -type f -delete +find "$dest_target" \( \ + -name 'misaligned' -o -name 'misaligned.d' -o \ + -name 'misaligned-bevy' -o -name 'misaligned-bevy.d' -o \ + -name 'misaligned-assets' -o -name 'misaligned-assets.d' \ +\) -type f -delete + +echo "seeded Cargo target cache: $source_target -> $dest_target" +echo "local misaligned artifacts were scrubbed; the current worktree will rebuild them" diff --git a/tools/shared-cargo-target.sh b/tools/shared-cargo-target.sh deleted file mode 100644 index 4324d831..00000000 --- a/tools/shared-cargo-target.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -# Source this from a git worktree before long Cargo commands: -# -# source tools/shared-cargo-target.sh -# -# It points Cargo at the primary checkout's target/ directory so parallel -# worktrees reuse dependency builds instead of each compiling Bevy, wgpu, etc. -# into their own private target trees. tools/check.sh sources this -# automatically; the helper is for ad-hoc cargo test/clippy/build commands. - -misaligned_primary_checkout() { - local common - common=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null) || return 1 - - case "$common" in - */.git) - printf '%s\n' "${common%/.git}" - ;; - */.git/worktrees/*) - # Defensive fallback for git versions/configurations that report the - # per-worktree admin dir instead of the common .git directory. - printf '%s\n' "${common%%/.git/worktrees/*}" - ;; - *) - git rev-parse --path-format=absolute --show-toplevel - ;; - esac -} - -misaligned_use_shared_cargo_target() { - if [ -n "${CARGO_TARGET_DIR:-}" ]; then - return 0 - fi - - local primary target - primary=$(misaligned_primary_checkout) || return 0 - target="$primary/target" - mkdir -p "$target" - export CARGO_TARGET_DIR="$target" -} - -misaligned_use_shared_cargo_target - -if [ "${MISALIGNED_QUIET_SHARED_TARGET:-0}" != "1" ] && [ -n "${CARGO_TARGET_DIR:-}" ]; then - printf 'CARGO_TARGET_DIR=%s\n' "$CARGO_TARGET_DIR" -fi diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index bf4e6e23..28d4b5f1 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -18,6 +18,19 @@ Reverse chronological implementation notes. Keep this factual: what changed, why - Checks: docs-only; `./tools/check.sh`. - Log: wiki/log/2026-07-08-blast-control.md. +## 2026-07-08 - Cargo cache seeding, not shared targets + +- Intent: correct the shared-target workflow after it produced an unsafe + false-green check in a modified worktree. +- Changed: replace `tools/shared-cargo-target.sh` with + `tools/seed-cargo-target.sh`; `tools/check.sh` now rejects external + `CARGO_TARGET_DIR` by default; AGENT.md and workflows.md document seeding + dependency artifacts into a private target instead of sharing one target. +- Design/spec impact: process/tooling only; no game behavior or constitution + amendment. +- Checks: `bash -n tools/seed-cargo-target.sh tools/check.sh`; guard test + rejects a shared target; seed smoke test; `./tools/check.sh`. + ## 2026-07-08 - Backups require research - Intent: capture Cameron's decision that backups are expensive sync diff --git a/wiki/process/workflows.md b/wiki/process/workflows.md index a3ba305b..7c6ea7ac 100644 --- a/wiki/process/workflows.md +++ b/wiki/process/workflows.md @@ -5,19 +5,23 @@ Type: knowledge ``` ## Build, test, verify -Worktree builds share the primary checkout's Cargo target directory. This is -what keeps Bevy/wgpu and other heavy dependencies warm across parallel agents -instead of recompiling into a fresh multi-GB `target/` in every worktree. -`./tools/check.sh` sources the helper automatically. For ad-hoc Cargo commands, -source it once in the shell first: +Worktree builds can seed from the primary checkout's Cargo target directory. +This keeps Bevy/wgpu and other heavy dependencies warm without sharing local +crate artifacts across source trees. Run this once after creating a worktree, +before long Cargo commands: ```bash -source tools/shared-cargo-target.sh +tools/seed-cargo-target.sh ``` -If `CARGO_TARGET_DIR` is already set, the helper respects it. The default is -the primary checkout's `target/` as discovered from git's common directory, so -`.letta/`, `.claude/`, and other linked worktrees all reuse the same cache. +Do **not** point multiple worktrees at one shared `CARGO_TARGET_DIR`. Cargo can +reuse stale local-package artifacts across checkouts; this produced a false +green once (the test count matched `main`, not the edited worktree). The seed +script copies the cache, removes `misaligned` artifacts/fingerprints, and keeps +the current worktree's `target/` private so local code rebuilds correctly. +`./tools/check.sh` rejects external `CARGO_TARGET_DIR` by default for the same +reason; override only for a deliberately isolated target with +`MISALIGNED_ALLOW_EXTERNAL_TARGET=1`. **One command runs the whole gate — use it before every commit:** -- 2.51.2