Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/tooling/driver-soup.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,29 @@ git cherry-pick 64583aa7 # sendModelError only; resolve fcm imports if needed

Do **not** fix this ad hoc in `~/coding/hapi/driver` during rebuild — fix the **branch tip**, then rebuild.

### Layer collisions (shared hot files)

Some files are merged by **every** layer that touches them; **last layer wins** per hunk — there is no automatic union.

| Hot file | Typical collision |
|---|---|
| `hub/src/sync/rpcGateway.ts` | Later layer re-merges for cursor/model work; drops RPC methods an **earlier** layer added |
| `hub/src/sync/syncEngine.ts` | May keep calls via a union repair layer (`fix/soup-sync-engine-collision`) while `rpcGateway` lost the method |
| `hub/src/web/routes/machines.ts` | REST route dropped while `syncEngine` + web client still reference it |
| `web/src/components/MarkdownRenderer.tsx` | Standalone markdown cast fixes overwritten |

**Symptom:** `hapi-driver-rebuild --verify` red on homelab/guest even though feature branches typecheck clean in isolation.

**Fix pattern (2026-07-04):** add a **thin collision-repair layer** on top of the manifest — do not hand-edit `~/coding/hapi/driver`:

- `fix/soup-codex-sessions-rpc-collision` — restore `listCodexSessionsForMachine` + route
- `fix/soup-markdown-standalone-cast` — restore react-markdown component casts
- `fix/soup-sync-engine-collision` — overseer + scratchlist union on `syncEngine`

**Prevention:** `hapi-driver-rebuild --verify` runs `hapi-soup-hotfiles-check.mjs` (syncEngine calls ⊆ rpcGateway methods). When adding a layer that edits hot files, comment in the manifest which symbols must survive lower layers.

**Guest migration (oos-linux):** promote soup by syncing **manifest** homelab → guest, then `hapi-driver-rebuild --build-web --verify` **on guest** — do not `sync-oos-hapi-driver.sh` homelab→guest after a guest-only rebuild (overwrites composed soup).

**Bypass** (testing only): `HAPI_SKIP_DRIVER_LOCK=1`. Skips both flock and status writes; collisions corrupt the driver tree.

**Why no hub API route?** The hub may be down *during* a switch — exactly when status is most wanted. File-backed status is readable when the hub is dead.
Expand Down
9 changes: 9 additions & 0 deletions scripts/tooling/hapi-driver-rebuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,17 @@ fi
if [[ "$VERIFY" -eq 1 ]]; then
echo "Running typecheck..."
(cd "$DRIVER" && "$BUN" typecheck)
HOTFILES="$PRIMARY/scripts/tooling/hapi-soup-hotfiles-check.mjs"
if [[ -f "$HOTFILES" ]]; then
echo "Checking soup hot-file consistency (syncEngine vs rpcGateway)..."
"$BUN" run "$HOTFILES" "$DRIVER"
fi
echo "Running tests..."
(cd "$DRIVER" && "$BUN" run test)
STAMP="${HAPI_DRIVER_VERIFY_STAMP:-$HOME/.config/hapi/driver-verify-stamp}"
mkdir -p "$(dirname "$STAMP")"
git -C "$DRIVER" rev-parse HEAD >"$STAMP"
echo "Verify stamp: $STAMP ($(cat "$STAMP" | head -c 12)…)"
fi

echo ""
Expand Down
42 changes: 42 additions & 0 deletions scripts/tooling/hapi-soup-hotfiles-check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bun
/**
* Soup hot-file consistency gate — run from hapi-driver-rebuild --verify.
*
* Catches layer-collision class bugs where syncEngine keeps rpcGateway calls
* but a later manifest layer dropped the matching rpcGateway method (or REST route).
*
* Usage: bun run hapi-soup-hotfiles-check.mjs [driver-dir]
*/
import { readFileSync } from 'node:fs'
import { join } from 'node:path'

const driver = process.argv[2] ?? process.env.HAPI_DRIVER ?? join(process.env.HOME, 'coding/hapi/driver')

function read(rel) {
return readFileSync(join(driver, rel), 'utf8')
}

const syncEngine = read('hub/src/sync/syncEngine.ts')
const rpcGateway = read('hub/src/sync/rpcGateway.ts')

const calls = [...new Set([...syncEngine.matchAll(/this\.rpcGateway\.(\w+)\(/g)].map((m) => m[1]))]
const methods = new Set([...rpcGateway.matchAll(/^\s+async (\w+)\(/gm)].map((m) => m[1]))
Comment on lines +22 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include generic calls in the parity scan

When syncEngine calls a generic gateway method with type arguments, as it already does with this.rpcGateway.callPiRpc<T>(...), this scan requires the method name to be followed immediately by ( and the method definition pattern has the same limitation for async callPiRpc<T = unknown>(...). In that collision case the new hot-file gate can still print OK while a generic rpcGateway call/method pair is missing from the advertised syncEnginerpcGateway parity check.

Useful? React with 👍 / 👎.


const missing = calls.filter((name) => !methods.has(name))
if (missing.length > 0) {
console.error('hapi-soup-hotfiles-check: FAIL')
console.error(' syncEngine -> rpcGateway calls missing from rpcGateway:', missing.join(', '))
console.error(' Add a collision-repair manifest layer — docs/tooling/driver-soup.md § Layer collisions.')
process.exit(1)
}

if (syncEngine.includes('listCodexSessionsForMachine')) {
const machines = read('hub/src/web/routes/machines.ts')
if (!machines.includes('/codex-sessions')) {
console.error('hapi-soup-hotfiles-check: FAIL')
console.error(' syncEngine.listCodexSessionsForMachine but machines.ts missing GET /codex-sessions')
process.exit(1)
}
}

console.log(`hapi-soup-hotfiles-check: OK (${calls.length} rpcGateway call site(s) checked)`)
Loading