live: heartbeat sync 2026-10-19T17:29:41-07:00 (Refs #11)
This commit is contained in:
1 parent
e22acf4913
commit
802014fea9
6 files changed
+123
-52
No files matched your search
@@ -1,56 +1,58 @@
|
||||
---
|
||||
name: dankvoice-bundle-swap-red-proof
|
||||
description: "Prove a dankvoice behavior test is red-before/green-after by pointing DV_EXT at the previously committed dist bundle, with a pinned scratch fixture dir."
|
||||
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."
|
||||
---
|
||||
|
||||
Use when a new black-box `tests/behavior.bats` assertion in dankvoice must be shown to be red-capable (fails on the pre-change code) without `git stash`/worktree juggling, or when the old bundle must be compared to the new one.
|
||||
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/`.
|
||||
|
||||
## Why
|
||||
The bats suite drives `dist/index.js` only (nothing imports `src/`), and the bundle is self-contained. So the *previous commit's* behavior is one `git show` away: write it to a file, point `DV_EXT` at it, run the same driver, and read the same `PLAYS n` / `CALL` / `NOTIFY` lines the test asserts.
|
||||
|
||||
## Procedure
|
||||
1. Build the new bundle and export the old one:
|
||||
```bash
|
||||
cd ~/Projects/<dankvoice-checkout>
|
||||
bun run build >/dev/null 2>&1
|
||||
git show HEAD:dist/index.js > /tmp/old-dist.js
|
||||
```
|
||||
2. Stand up the fixtures in a **pinned** scratch dir — never `$(ls -dt /tmp/tmp.* | head -1)`: other live sessions on the box create `tmp.*` dirs continuously, and a glob picks *theirs*, which is how a red-proof silently becomes "Module not found" and then a false `PLAYS 0`.
|
||||
```bash
|
||||
W=/tmp/dv-redproof; rm -rf "$W"; mkdir -p "$W/bin"
|
||||
```
|
||||
3. Copy the **real** player stub, not a simplification. The driver counts plays from the stub log; a stub that only `sleep`s records nothing and reports `PLAYS 0` for every build, red and green alike. Mirror `setup()` in `tests/behavior.bats`:
|
||||
```bash
|
||||
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" # PATH fallback player
|
||||
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"
|
||||
sed -n '86,408p' tests/behavior.bats > "$W/driver.ts" # the inline heredoc driver
|
||||
```
|
||||
(Re-check those line bounds against the current file; the driver lives inside `setup()`.)
|
||||
4. Export the env the driver expects and run old vs new against the SAME scenario:
|
||||
```bash
|
||||
export 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
|
||||
for ext in /tmp/old-dist.js "$PWD/dist/index.js"; do
|
||||
rm -f "$W/player.log"
|
||||
DV_EXT="$ext" DV_PLAYER_BIN="$W/no-such-player" bun "$W/driver.ts" speak | grep -E '^PLAYS|^NOTIFY'
|
||||
done
|
||||
```
|
||||
The driver sets its own fixture server URL and prepends `dirname "$DV_STUB")/bin` to `PATH` (see its `DANKVOICE_*` block), so the discovered-player fallback has somewhere real to land.
|
||||
5. Add a **control**: same scenario with a valid player path. Old-vs-new differences that the control also shows are harness bugs, not behavior changes.
|
||||
`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
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Traps, each one cost a turn
|
||||
|
||||
- **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.
|
||||
|
||||
## Reading the result
|
||||
- old `PLAYS 0` + new `PLAYS 1` + control `PLAYS 1` → the assertion is red-capable and the fix is real.
|
||||
- identical results old vs new → the harness is lying (stub too thin, wrong scratch dir, or the scenario never reaches the changed path). Fix the harness before believing any green.
|
||||
- File-mode assertions: pre-seed the file with the *old* mode (`printf … > file; chmod 644 file`) — a fresh write with `mode:` in `writeFileSync` only applies at creation, so a fresh file can look green while the old code is equally green.
|
||||
|
||||
## Gotchas
|
||||
- Never poll the bats suite while doing this; `setup()` runs `bun run build`, so a concurrent `src/` edit gets compiled into someone else's run (see `dankvoice-bats-serialization`).
|
||||
- `bun run build` does not typecheck; a stale identifier reference survives bundling and only fails at runtime — the suite, not the build, is the gate.
|
||||
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.
|
||||
|
||||
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.
|
||||
Reference in new issue
Block a user