--- name: debug-bridge description: Drive and verify Knit on a device or emulator through the headless debug bridge (`am broadcast` to `app.getknit.knit.debug.`, replies as JSON) — send a message on one phone and confirm it landed on another without screenshots, read mesh state, check custody / spool / ratchet convergence, run a LoRa, direct-transfer or Wi-Fi Aware fault trial on a locked lab device, or post a fixture notification for a UI test. Use whenever a task says run it on a device, check it on the phones, lab trial, device trial, soak, convergence check, `KnitBridge`, or names any `…debug.`; and before adding a bridge action. --- # Debug bridge Debug builds carry an exported receiver (`app/src/debug/.../debug/DebugBridgeReceiver.kt`) that calls the mesh directly and answers in JSON, so an agent can drive the send→verify loop with no UI and no screenshots. This skill is the procedure. The per-action extras and reply fields are in `.agents/context/debug-bridge.md`, and this file points into it rather than copying it. ## Gate 0: whether you may Obey `.agents/rules/devices.md`. An emulator is fair game. A physical phone, including every lab phone on network adb, needs the user's go-ahead **for this task, every time**. Being reachable in `adb devices` is not permission, and neither is a yes from an earlier task. Never toggle Wi-Fi over adb on a lab phone. Always pin the target with `-s ` or `ANDROID_SERIAL`: several devices are usually attached. ## Gate 1: a debug build that matches - The bridge exists only in debug builds. A release or `nonMinifiedRelease` install answers nothing. - Install with `./gradlew :app:installDebug`. Don't `adb install` the `intermediates/apk` file, which AGP 9 never rewrites. - A lab phone may run a feature branch with a newer Room schema than yours, and installing over it half-runs the app. Check what the device has before you install, and say what you replaced. - After an install the package is in the stopped state. `scripts/bridge.sh` delivers to it anyway (`-f 0x20`). A hand-written `am broadcast` without that flag gets an empty reply until the app is opened. ## The call ```sh scripts/bridge.sh [am-extras…] # JSON on stdout; "-" = $ANDROID_SERIAL scripts/bridge.sh emulator-5554 STATE | jq '{self, health, reachable}' scripts/bridge.sh - SEND --es text 'hi there 😀' --es conv nearby ``` The script single-quotes every extra for the device shell, adds `-f 0x20` and `-p app.getknit.knit`, pulls the `data="…"` out of `Broadcast completed`, and falls back to the receiver's `KnitBridge:I` log mirror (only lines newer than the call). It exits 1 with a hint on stderr when nothing parsable came back. A refusal is still a reply, though: a missing extra answers `{"status":"error","message":"missing 'path' extra"}` with exit 0, so check `.status` (`jq -e '.status == "ok"'`) before reading fields. Extras use `am`'s own flags: `--es` string, `--ei` int, `--ez` bool. If you must write the raw form, quote the whole remote command and single-quote the values. adb re-parses on the device, so a bare `--es text "hi there"` arrives as `hi`: ```sh adb -s A shell "am broadcast -f 0x20 -a app.getknit.knit.debug.SEND -p app.getknit.knit --es text 'hi there' --es conv nearby" ``` ## The core loop 1. `STATE` on each device: note `self.nodeId` and confirm the other side is in `reachable[]`. A peer that isn't there is a radio problem, not a delivery one. Read `transports[]` (per-radio `health`, `linked`, `nearby`) first, then `meshEnabled`, `meshPaused` and `meshStartDeferred`, any of which leaves a mesh that looks alive with no peers. 2. Act on A, e.g. `SEND --es text '' --es conv `. Use a body you can grep for (prefix it `soak ` or `burst ` so `PURGE` can clean it up later). 3. Verify on B with `STATE --es conv `: the body appears in `messages[]`. Then, if the test is about the ✓✓, read A's own row (`mine: true`) until its `received` flips: that flag is the delivery tick coming home to the sender, not something the receiver's row carries. Poll in a bounded loop. Never trust one bare `sleep`: delivery order and timing across radios are not promised. ```sh for i in $(seq 1 30); do scripts/bridge.sh B STATE --es conv nearby | jq -e --arg b "$BODY" '.messages[] | select(.body == $b)' && break sleep 2 done ``` ## Which oracle answers which question | Question | Action | Read | |---|---|---| | Did it arrive? | `STATE --es conv ` on the receiver | the body in `messages[]` | | Did the ✓✓ get home? | `STATE --es conv ` on the sender | its own row's (`mine: true`) `received` | | Did a reaction or typing cue land? | `STATE` on the receiver | `messages[].reactions`; `typing` | | Do two nodes carry the same custody? | `STORE` on each | `liveFingerprint` equal across devices (never `allFingerprint`); `digestVersion == liveFingerprint` on each | | Which frame is stranded? | `STORE` | `allIds` sorted per device, then `comm` between devices | | Is the Internet plane converged? | `SPOOL` | per scope `local == spool`, `invalid` 0, `lastError` empty, `connected` | | Is a DM session healthy? | `RATCHET` | `hasSession` and `confirmed`; `peerPrekeyPinned` before any reset | | LoRa range or airtime? | `LORA` | `loraSent`/`loraReceived`, `loraNakByReason` | | Why didn't a transfer start? | `XFER` | `refusal` **first**, then the live transfers | | Is the mesh paused? | `STATE` | `meshPaused` / `meshPausedUntil` | Each row's caveats (the `retiring` and `accounted` spool fields, the TTL edge on `allFingerprint`) are in the context doc. Read its bullet before you call a result converged or diverged. ## Action index Extras and reply fields: the action's bullet in `.agents/context/debug-bridge.md` under *Headless bridge*. - **Messaging:** `SEND`, `SENDIMG` (stage the file into app storage with `run-as` first), `REACT`, `TYPING`, `MKGROUP`, `LEAVE`, `PURGE` (clean soak traffic off every phone). - **Mesh state:** `STATE`, `STORE`, `HEAL`, `PAUSE`, `RESUME`, `BLECAP`, `NANOFF` (Wi-Fi Aware off, Bluetooth alone), `PHY` (the Coded PHY experiment). - **Planes:** `SPOOL`, `COMMONS`, `LORA`, `LORATX`, `LORAPROV`, `XFER`. - **Identity, crypto, data:** `INTRO` (contact cards), `RATCHET`, `BACKUP`. - **Fault injection and trials:** `NANFAIL`, `NANSTORM`, `NANREFUSE`, `NANICM`, `NANDIAL`, `NANINIT`, `NANMSG`, `MODEL`. Each has a negative control in its bullet; run it too. - **UI-test fixtures:** `FLAGMSG`, `MSGNOTIF`, `REQNOTIF`, `REVIEW`. - **Media and packaging probes:** `WEBPPROBE`, `WEBPCONV`, `WEBPCHECK`, `SHAREAPK`. ## When it answers nothing - `result=0` with no `data=`, and nothing under `KnitBridge` usually means the action isn't in the debug manifest's ``, or is misspelt. It can also mean a non-debug build. Without `-f 0x20` it can be a stopped package. - A text cut off at its first space means it went through a raw `adb shell` without the inner quotes. - `missing 'path' extra`, or a path the app can't read: scoped storage means the app can't read `/sdcard` or `/data/local/tmp`. Copy the file in with `adb shell "cat /data/local/tmp/x | run-as app.getknit.knit sh -c 'cat > files/x'"` and pass `/data/data/app.getknit.knit/files/x`. - `error` from `COMMONS` means the build hides the commons (`BuildConfig.COMMONS`). - A reply that looks healthy while nothing moves: read the oracle's caveat fields (`lastError`, `refusal`, `declined`). Most gates in the mesh return silently, and these fields exist to tell them apart. ## When the UI is unavoidable Use the stable resource-ids, cold-start navigation (`--es demo_route chat/`), and the rule that popups, dialogs and bottom sheets don't surface their test tags. These are the *Stable resource-ids* and *Cold-start navigation* sections of the context doc. Prefer a bridge action over driving a sheet. `XFER` exists partly so nobody has to drive the consent sheet. ## Adding an action 1. A branch in the `when` in `DebugBridgeReceiver` and an `ACTION_` constant. 2. An `` in the receiver's `` in `app/src/debug/AndroidManifest.xml`. Without it the broadcast is never delivered, and nothing errors. 3. A bullet in `.agents/context/debug-bridge.md` covering the extras, the reply fields, what the oracle is, and the negative control if it injects a fault. 4. A word in this skill's action index. Check for drift after any change. This must print nothing: ```sh comm -3 <(grep -oE '"app\.getknit\.knit\.debug\.[A-Z]+"' app/src/debug/java/app/getknit/knit/debug/DebugBridgeReceiver.kt | tr -d '"' | sed 's/.*\.//' | sort -u) \ <(grep -oE 'debug\.[A-Z]+' .agents/context/debug-bridge.md | sed 's/.*\.//' | sort -u) ```