live: heartbeat sync 2026-10-30T17:23:49-07:00 (Refs #11)

This commit is contained in:
Frank committed 2026-10-30 17:23:49 -07:00
1 parent 92c127ca08
commit 674a22be48
18 files changed
+226 -65

No files matched your search

@@ -1,58 +1,48 @@
---
name: dankvoice-bundle-swap-red-proof
description: "Prove a dankvoice behavior test is red against the pre-change bundle by pointing DV_EXT at git show HEAD:dist/index.js, with the driver extracted from the bats heredoc into a pinned scratch directory. Use before claiming a new bats assertion is red-capable."
description: Prove a dankvoice behavior test is red against the pre-change bundle by pointing DV_EXT at a built old bundle; includes the source-swap fallback when the extracted bundle cannot resolve node_modules outside the repo.
---
Prove a new `tests/behavior.bats` assertion is **red-capable** — that it fails against the *previous* build, not just passes against the new one — without stashing, branching, or editing `src/`.
# Red-proofing a dankvoice behavior test against the pre-change bundle
## Why
The suite drives the **built** bundle (`dist/index.js`, rebuilt by `setup()` from `src/`), so "is this new assertion actually red-capable?" is a question about a bundle that no longer exists after the fix. Two routes; try A first, fall back to B.
`tests/behavior.bats` is a black-box suite: the plugin bundle is loaded through `DV_EXT`, so a *different bundle* is the red side. Stashing the fix would break the `dist` byte-identity pin test and the shared worktree.
## Working recipe
## Route A — `DV_EXT` at the old committed bundle
```bash
cd ~/Projects/dankvoice # or the feature worktree: ~/Projects/wt-dv-*
bun run build # current src = the green side
git show HEAD:dist/index.js > /tmp/old-dist.js # the red side (pre-change bundle)
W=/tmp/dv-redproof # PINNED name; see traps below
rm -rf "$W" && mkdir -p "$W/bin"
cat > "$W/player.sh" <<'SH'
#!/usr/bin/env bash
printf '%s\n' "$*" >> "$DV_STUB_LOG"
for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$DV_COPY" ;; esac; done
sleep "${DV_HOLD:-0.1}"
SH
chmod +x "$W/player.sh"
ln -s "$W/player.sh" "$W/bin/pw-play"
printf '#!/usr/bin/env bash\nprintf "116\\talsa_output.usb-KT.0\\taudio/sink\\t*\\n"\n' > "$W/bin/wpctl"
chmod +x "$W/bin/wpctl"
# The driver lives inside the bats file as a heredoc: extract it verbatim.
end=$(grep -n '^TS$' tests/behavior.bats | cut -d: -f1)
sed -n "86,$((end - 1))p" tests/behavior.bats > "$W/driver.ts"
export DV_EXT="$PWD/dist/index.js" DV_STUB="$W/player.sh" DV_STUB_LOG="$W/player.log" \
DV_COPY="$W/played.wav" DV_OMPCONFIG=/dev/null DANKVOICE_CONFIG="$W/settings.json" \
DV_MODELS="$W/models.yml" DV_WPCTL=x DV_SELECT=""
for ext in /tmp/old-dist.js "$PWD/dist/index.js"; do
rm -f "$W/player.log" "$DANKVOICE_CONFIG"
DV_EXT="$ext" bun "$W/driver.ts" <scenario> 2>&1 | grep -E '^PLAYS|^CALLS|^NOTIFY|^SETTINGS'
done
git show HEAD:dist/index.js > "$W/old-dist.js"
DV_EXT="$W/old-dist.js" DV_STUB=... <same env as the test> bun "$DV_DRIVER" <scenario> # expect RED
DV_EXT="$PWD/dist/index.js" <same env> bun "$DV_DRIVER" <scenario> # expect GREEN
```
The scenario name is the driver's third argv (`speak`, `auto`, `repeat`, `humanizecycle`, `badurl`, …); the bats `-f` test body's env (`DV_PLAYER_BIN`, `DV_SETTINGS_JSON`, `DANKVOICE_VERBOSITY`, …) is passed the same way. `DV_SELECT` holds the menu answers, `|`-separated, consumed in order.
Build the driver harness by extracting the heredoc `setup()` writes into `tests/behavior.bats`:
## Traps, each one cost a turn
```bash
sed -n '<first-body-line>,<last-body-line>p' tests/behavior.bats > "$W/driver.ts"
```
- **The scratch directory must be pinned by name.** `ls -dt /tmp/tmp.*` picks up *other sessions'* temp dirs, and then `player.sh` and `driver.ts` live in different directories — the run silently does the wrong thing.
- **The stub must append to `$DV_STUB_LOG`.** The driver counts player invocations from that file; a stub that only `sleep`s reports `PLAYS 0` for *both* bundles and the whole proof reads as "fix does nothing".
- **`DV_EXT` is required.** Missing it yields `Cannot find package 'undefined'` from the driver, which looks like a broken driver, not a missing env var.
- **`DV_SETTINGS_JSON` supports `__PORT__`** (the driver substitutes the fixture port), so a preseed can name a prefixed route — needed whenever the test is about *which* endpoint got resolved.
- **Never run this while a `bats` run is in flight** in the same repo: `setup()` runs `bun run build`, so two runs clobber `dist/` and the pin test.
Re-check that range after every edit that adds lines to the driver (`console.log` census blocks included) — a stale end line silently appends the `TS` terminator and the driver dies after printing, which looks like a passing probe.
## Reading the result
Copy the project's real player stub, `wpctl` fixture, `models.yml` and `ompconfig.yml` verbatim; a simplified stub prints a false `PLAYS 0`.
Red side must show the *defect* (`PLAYS 0`, `644`, a chat `CALL` with the wrong prefix, `Invalid URL`), green side the fix — assert on printed markers only (`PLAYS`, `CALLS`, `CALL <url>`, `NOTIFY`, `SETTINGS`). `CALLS` counts `/audio/speech` calls only; the rewrite call appears as its own `CALL /…/chat/completions … sys=<full system prompt>` line, which is how prompt-level knobs (verbosity, heckle) are observed.
**Trap (why A fails in a worktree):** module externals resolve from the *bundle's* directory. A bundle extracted to `/tmp` cannot resolve `@oh-my-pi/pi-natives` and dies with a version-pinned `Cannot find module .../pi-natives@<ver>/.../loader-state.js` — nothing to do with the fix under test. Putting the old bundle inside the repo (next to `node_modules`) fixes resolution, but in a linked-worktree where `node_modules` is a symlink to another checkout whose installed version differs from the version baked into `HEAD:dist`, it still fails. Then use route B.
Prefer this over a synthetic leak: if the check already caught a real leftover/failure once, that event *is* the sensitivity proof — say so in the commit message instead of faking one.
## Route B — swap the source, rebuild, restore (private worktree only)
`setup()` rebuilds `dist` from `src/`, so reverting one source file reproduces the pre-fix bundle exactly:
```bash
cp src/<file>.ts /tmp/new-<file>.ts
git show HEAD:src/<file>.ts > src/<file>.ts
bun run build >/dev/null
bats tests/behavior.bats --filter '<new test names>' # expect every new test NOT ok
cp /tmp/new-<file>.ts src/<file>.ts && bun run build >/dev/null && echo RESTORED
```
Always restore in the same command line that ran the red check, so an interrupted session cannot leave pre-fix source behind. `bats --filter` takes a regex over the `@test` titles; use it instead of the whole suite (the full run is ~5 min).
Read the failure lines, not just the count: a red test that fails because the driver crashed (`PLAYS 0`, no `CALL` lines) proves nothing about the fix.
## Gate afterwards
`bun run build` and commit `dist/` in the same commit as `src/` — `tests/packaging.bats` byte-compares the **committed** dist (`git diff --name-only -- dist` must be empty), so an uncommitted rebuild shows up as a red packaging test, not as a missing fix.