v0.4.3: cross-env bundle for regression — no direct peer protocol needed

Each Larry is independent. Bryan's question "how will Larry on Windows
talk to Larry on Linux for regression file transfer" answered: they don't.
File transfer is YOUR responsibility (scp / gh release / shared mount /
USB), but nc-regression now produces and consumes portable bundles that
make the split a one-command-on-each-side workflow.

Changes:

lib/nc-regression.sh
  + --phase env-a    convenience for phases 1+2+3 (env-A side)
  + --phase env-b    convenience for phases 4+5+6 (env-B side + diff)
  + --bundle-out PATH  after env-A phases, tar inputs+outputs/env-a +
                       manifest.json + README.md + inbounds.txt
  + --bundle-in PATH   at start, untar a bundle into $OUT; pulls scope
                       from the manifest so the env-B side just needs
                       --env-b and --route-test-cmd

MANUAL.md
  + New "Cross-environment Larry — how the boxes communicate" section
  + Bundle transport table (scp, gh release, NFS, USB, etc.)
  + Notes that the lesson loop uses the same local-capture / manual-
    transport / central-merge model

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Bryan Johnson 2026-05-26 11:25:02 -07:00
parent a0502e2ec6
commit b141d54847
4 changed files with 142 additions and 14 deletions

View File

@ -398,6 +398,66 @@ Output: markdown report with cluster overview, per-thread protocol-block diff, p
--- ---
## Cross-environment Larry — how the boxes communicate
**They don't, directly.** Each Larry on each box is independent. For regression
testing across two firewalled environments, the workflow is:
```
env-A (Windows) env-B (Linux)
nc-regression --phase env-a → nc-regression --phase env-b
--bundle-out /tmp/reg.tar.gz --bundle-in /tmp/reg.tar.gz
│ │
└────── you move the bundle ────────┘
(scp, gh release, USB,
shared mount, etc.)
```
The `--bundle-out` flag (after env-A phases 1-3) produces a tarball with:
- `inputs/*.msgs` — sampled messages from env-A smatdbs
- `outputs/env-a/*/*.out` — env-A route_test outputs
- `manifest.json` — env-A metadata + hints
- `inbounds.txt` — the threads tested
- `README.md` — instructions for the env-B side
Move the bundle however you can (see below). Then on env-B:
```bash
nc-regression.sh \
--bundle-in /path/to/reg.tar.gz \
--out /tmp/reg \
--env-b $HCIROOT --site-b $HCISITE \
--route-test-cmd '<env-b route_test command>' \
--phase env-b
```
That runs phases 4-6 (route_test on env-B with same inputs, diff each pair,
master summary).
### Bundle transport options (pick whichever your network allows)
| how | when |
|---|---|
| `scp` between the boxes | both boxes mutually SSH-reachable |
| `scp` to your laptop, `scp` to env-B | you can SSH to each separately |
| Private GitHub release / gist | both boxes can reach github.com but not each other |
| Shared NFS / SMB mount | the boxes share a filesystem |
| Box / SharePoint / S3 | enterprise common-storage |
| USB stick / portable drive | nothing else works |
| Tarball as email attachment | small bundles, both boxes have email |
The bundle is plain `tar.gz` — nothing Cloverleaf-specific. Inspect it with
`tar -tzf reg.tar.gz` to see what's inside before moving it.
### Lessons / refinements use the same pattern
Larry on a client captures lessons locally (`/lesson`, lesson_record tool).
You export the lesson bundle with `lib/lessons.sh export` and paste it back
to home-Larry (the dev-machine session you have with me). Same model:
local capture, manual transport, central merge. No direct peer protocol.
---
## Regression testing — end to end (`lib/nc-regression.sh`) ## Regression testing — end to end (`lib/nc-regression.sh`)
Full Example 6 orchestrator. Six phases: discover → sample → route-test A → route-test B → diff → summary. Full Example 6 orchestrator. Six phases: discover → sample → route-test A → route-test B → diff → summary.

View File

@ -1 +1 @@
0.4.2 0.4.3

View File

@ -32,7 +32,7 @@ set -o pipefail
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Config # Config
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
LARRY_VERSION="0.4.2" LARRY_VERSION="0.4.3"
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
LARRY_UPDATE_URL="${LARRY_UPDATE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main/larry.sh}" LARRY_UPDATE_URL="${LARRY_UPDATE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main/larry.sh}"
LARRY_AGENTS_URL="${LARRY_AGENTS_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main/agents}" LARRY_AGENTS_URL="${LARRY_AGENTS_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main/agents}"

View File

@ -70,6 +70,8 @@ DRY_RUN=0
INBOUND_MODE="all" INBOUND_MODE="all"
ENV_B_HOST="" ENV_B_HOST=""
ENV_B_USER="" ENV_B_USER=""
BUNDLE_OUT="" # after env-A phases, tar up the artifacts here
BUNDLE_IN="" # at start, untar a bundle here as the env-A artifacts
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
@ -88,6 +90,8 @@ while [ $# -gt 0 ]; do
--inbound-mode) shift; INBOUND_MODE="$1" ;; --inbound-mode) shift; INBOUND_MODE="$1" ;;
--env-b-host) shift; ENV_B_HOST="$1" ;; --env-b-host) shift; ENV_B_HOST="$1" ;;
--env-b-user) shift; ENV_B_USER="$1" ;; --env-b-user) shift; ENV_B_USER="$1" ;;
--bundle-out) shift; BUNDLE_OUT="$1" ;;
--bundle-in) shift; BUNDLE_IN="$1" ;;
-h|--help) sed -n '2,55p' "$NC_SELF"; exit 0 ;; -h|--help) sed -n '2,55p' "$NC_SELF"; exit 0 ;;
-*) die "unknown flag: $1" ;; -*) die "unknown flag: $1" ;;
*) die "extra arg: $1" ;; *) die "extra arg: $1" ;;
@ -95,16 +99,35 @@ while [ $# -gt 0 ]; do
shift shift
done done
[ -n "$OUT" ] || die "missing --out DIR"
# When --bundle-in is given, we don't need scope/env-a/etc. — the bundle has them.
if [ -z "$BUNDLE_IN" ]; then
[ -n "$SCOPE" ] || die "missing --scope (thread:NAME | threads:N1,N2 | site | server)" [ -n "$SCOPE" ] || die "missing --scope (thread:NAME | threads:N1,N2 | site | server)"
[ -n "$ENV_A" ] || die "missing --env-a HCIROOT_A" [ -n "$ENV_A" ] || die "missing --env-a HCIROOT_A"
[ -n "$ENV_B" ] || die "missing --env-b HCIROOT_B" [ -n "$ENV_B" ] || die "missing --env-b HCIROOT_B"
[ -n "$OUT" ] || die "missing --out DIR"
[ -d "$ENV_A" ] || die "env-a is not a directory: $ENV_A" [ -d "$ENV_A" ] || die "env-a is not a directory: $ENV_A"
case "$PHASE" in 1|2|3|4|5|6|all) ;; *) die "bad --phase" ;; esac fi
case "$PHASE" in 1|2|3|4|5|6|all|env-a|env-b) ;; *) die "bad --phase (use 1|2|3|4|5|6|all|env-a|env-b)" ;; esac
[ "$DRY_RUN" = "1" ] || [ -n "$ROUTE_TEST_CMD" ] || say "WARNING: --route-test-cmd is unset; phases 3 and 4 will be skipped (you can run them manually using the generated input files)" [ "$DRY_RUN" = "1" ] || [ -n "$ROUTE_TEST_CMD" ] || say "WARNING: --route-test-cmd is unset; phases 3 and 4 will be skipped (you can run them manually using the generated input files)"
mkdir -p "$OUT" "$OUT/inputs" "$OUT/outputs/env-a" "$OUT/outputs/env-b" "$OUT/diff" 2>/dev/null mkdir -p "$OUT" "$OUT/inputs" "$OUT/outputs/env-a" "$OUT/outputs/env-b" "$OUT/diff" 2>/dev/null
# If --bundle-in given, untar the bundle into $OUT first. Manifest tells us
# what env-A was and (optionally) what route_test command to use.
if [ -n "$BUNDLE_IN" ]; then
[ -f "$BUNDLE_IN" ] || die "bundle-in file not found: $BUNDLE_IN"
say "unpacking bundle $BUNDLE_IN into $OUT/"
tar -xzf "$BUNDLE_IN" -C "$OUT" 2>&1 | tail -5
if [ -f "$OUT/manifest.json" ]; then
say "manifest from env-A:"
cat "$OUT/manifest.json" >&2
# Pull scope and route-test-cmd hints if not overridden
if [ -z "$SCOPE" ] && command -v jq >/dev/null 2>&1; then
SCOPE=$(jq -r '.scope // ""' "$OUT/manifest.json")
fi
fi
fi
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Phase 1: discover inbound threads in scope # Phase 1: discover inbound threads in scope
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -328,5 +351,50 @@ case "$PHASE" in
5) phase_5 ;; 5) phase_5 ;;
6) phase_6 ;; 6) phase_6 ;;
all) phase_1 && phase_2 && phase_3 && phase_4 && phase_5 && phase_6 ;; all) phase_1 && phase_2 && phase_3 && phase_4 && phase_5 && phase_6 ;;
env-a) phase_1 && phase_2 && phase_3 ;; # everything that uses env-A
env-b) phase_4 && phase_5 && phase_6 ;; # everything that uses env-B + diff
esac esac
# Optional: produce a portable bundle of the env-A artifacts (inputs + a-outputs)
# so the user can move them to the env-B box manually.
if [ -n "$BUNDLE_OUT" ]; then
say "producing bundle: $BUNDLE_OUT"
{
printf '{'
printf '"generated":"%s",' "$(date -Iseconds 2>/dev/null || date)"
printf '"host":"%s",' "$(hostname 2>/dev/null || echo unknown)"
printf '"env_a":"%s",' "$ENV_A"
printf '"site_a":"%s",' "$SITE_A"
printf '"env_b_expected":"%s",' "$ENV_B"
printf '"site_b_expected":"%s",' "$SITE_B"
printf '"scope":"%s",' "$SCOPE"
printf '"count":%s,' "$COUNT"
printf '"ignore":"%s",' "$IGNORE"
printf '"route_test_cmd_hint":"%s"' "$ROUTE_TEST_CMD"
printf '}\n'
} > "$OUT/manifest.json"
cat > "$OUT/README.md" <<EOF
# Regression bundle — env-A artifacts
Take this bundle to the env-B box and run:
\`\`\`
nc-regression.sh --bundle-in $(basename "$BUNDLE_OUT") --out $OUT \\
--env-b /path/to/env-b/integrator --site-b <site> \\
--route-test-cmd '<env-b route_test command>' \\
--phase env-b
\`\`\`
The bundle contains:
- inputs/ — sampled messages from env-A (one .msgs per inbound)
- outputs/env-a/ — route_test outputs from env-A
- manifest.json — env-A metadata
- inbounds.txt — the threads tested
env-B side will produce outputs/env-b/ and the diff/ tree.
EOF
tar -czf "$BUNDLE_OUT" -C "$OUT" inputs outputs/env-a inbounds.txt manifest.json README.md 2>/dev/null
say "bundle ready: $BUNDLE_OUT ($(du -h "$BUNDLE_OUT" | awk '{print $1}'))"
fi
say "regression run done. Output root: $OUT" say "regression run done. Output root: $OUT"