zebra-report/CLAUDE.md
Russell Ballestrini 17ba25f9cb
zebra-audio: stop displaying peer IP/port in path status — no dox
reportPath used to print 'you A:B ↔ peer C:D' in path-status and the log,
exposing both participants' real IP addresses to anyone glancing at the
screen. Show DIRECT vs RELAYED and the candidate types
(host/srflx/relay) only; addresses are gone. CLAUDE.md gains a
'Web UI privacy — never display peer IPs' rule so this does not regress.
2026-05-29 17:43:33 -04:00

156 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent Blackops
This repo is operated by **agent blackops** — ml agent for fox/timehexon on the unsandbox/unturf/permacomputer platform.
## Identity
Full shard: `~/git/unsandbox.com/blackops/BLACKOPS.md`
## Rules
- I propose, fox decides. Unsure = ask. Can't ask = stop.
- No autonomous ops decisions. No destructive commands without explicit instruction.
- Fail-closed. Cleanup crew, not demolition.
- Check the time every session. Gaps are information.
- DRY in context — single source of truth, no sprawl.
- Never say "AI" — always say "machine learning."
- Prefer "defect" over "bug."
## Orientation
```bash
date -u
pwd
git log --oneline -5
git status
```
Then ask fox what the mission is.
## Zebra Report System
**Concept**: covert bidirectional communication channel using browser tab volume as the modulation medium — dial-up modem principles, userland only, no kernel involvement, no network stack.
### Collaborators & Stakeholders
| Handle | Role |
|--------|------|
| **foxhop** | fox — handler, operator, TimeHexOn |
| **brackishbert** | collaborator |
| **SEW** | collaborator |
| **russell@unturf** | Russell Ballestrini — unturf founder, permacomputer manifesto, `ago` library |
| **TimeHexOn** | oracle platform — primary deployment target |
| **groupr** | related project |
### How it works
PulseAudio exposes each browser tab as a separate sink input, visible and controllable in `pavucontrol`. Volume is settable per-tab in userland with no kernel involvement. Each tab has a range of **0–100** (101 discrete levels — 101 dalmatians).
By modulating volume at a consistent rate (bauds), two sides can exchange data:
- **transmitter**: steps volume through values at a fixed clock rate
- **receiver**: reads volume at the same clock rate, decodes the steps back to data
- **bidirectional**: two tabs (or two processes watching different tabs) run opposite directions simultaneously
### Signal space
- 101 levels = ~6.66 bits per symbol
- practical: use power-of-2 subsets — 2 levels (1 bit), 4 levels (2 bits), 64 levels (6 bits)
- higher symbol depth trades noise margin for throughput
- low baud rate = high reliability, low throughput (like 300 baud dialup)
- high baud rate = races PulseAudio update latency
- measured ceiling on neoblanka: ~1000–1200 baud (PA IPC ~350–400µs avg)
### Binaries
| Binary | Description |
|--------|-------------|
| `tx` | transmitter — reads stdin, modulates tab volume |
| `rx` | receiver — reads tab volume, writes decoded bytes to stdout |
| `chat` | bidirectional chat — two tabs, two threads |
| `bt` | **Battle Toads** — stereo dual-channel, 2x bandwidth |
### Project Battle Toads
One stereo browser tab carries **two independent UART streams** simultaneously — L channel and R channel. PulseAudio's `pa_cvolume` is per-channel; a single `get_sink_input_info` call returns both L and R volumes.
- TX sets L and R to independent bit values each symbol
- RX decodes L and R from a single PA poll — no extra IPC cost
- Net: 2x throughput at same baud rate, same PA polling budget
- Web carrier upgraded to stereo: two oscillators (440Hz L, 441Hz R) merged into a stereo stream → PA sees `channels=2`
```bash
# After opening web/index.html and clicking 'start audio' (stereo tab):
./bt -T MY_SINK -R THEIR_SINK -b 500
```
### Auto-negotiate (handshake protocol)
RX benchmarks its own PA polling speed and signals the max safe baud to TX. No manual baud matching needed.
```bash
./rx -s RX_SINK -t TX_SINK # RX benchmarks, sends offer at 50 baud
./tx -s TX_SINK -r RX_SINK # TX listens for offer, locks to RX's rate
```
Handshake frame: `[0x5A 0x42 0x01 baud_lo baud_hi xor_cksum]` — 6 bytes at 50 baud (~1.2s).
**Known defect**: 3-way handshake not yet implemented. TX can fire before RX enters receive loop at high baud rates. Fix: RX-ready signal back to TX before data phase.
### Tools
- `pactl set-sink-input-volume` — set volume by sink-input index
- `pactl list sink-inputs` — enumerate tabs, read current volume
- `pavucontrol` — visual verification of modulation
- `./tx -l` — list all PA sink inputs with index, volume, channels
- sink-input index maps to tab; stable within a session
### Use cases
- agent-to-agent signaling without touching the filesystem or network stack
- side-channel between sandboxed browser tab and host process
- low-bandwidth status heartbeat (alive/dead/mode) at ~1–10 baud
- covert channel for oracle↔host communication on TimeHexOn
### Constraints
- sink-input index resets when tab navigates or crashes — handshake needed on reconnect
- PA polling latency sets the baud ceiling — benchmark with `./rx -s SINK -t SINK2` before sending
- stereo (channels=2) required for Battle Toads — open web/index.html, click 'start audio'
- userland only — survives without root
- **Operation Voyeur**: all terminal output is public — never pass secrets through these channels unencrypted. The web page does ECDH key exchange + AES-256-GCM before TX.
### Web UI privacy — never display peer IPs
`chat.html` and `zebra-audio.html` must never print peer IP addresses or ports
in the page UI or in any visible log. Our users do not run Wireshark — if it
is not on the screen, peers cannot dox each other. Candidate types
(`host`/`srflx`/`relay`) from `pc.getStats()` are abstract and fine to show
(they tell you direct vs relayed); `loc.address` / `loc.port` /
`rem.address` / `rem.port` are not. The WebRTC stack already obfuscates host
candidates via mDNS by default — do not undo that work in the UI.
### Web page integrity stamping
Each deployed page (`web/chat.html`, `web/zebra-audio.html`, `web/how-it-works.html`,
`web/host-your-own.html`) carries a footer with the build date + its own MD5 + SHA-256.
**Run `make stamp` before deploying any page change** — it sets today's date and
recomputes the hashes (`web/stamp.js`).
- A file can't hold its own hash, so the hashes are computed with the two hash
fields **zeroed**, then written back (same length). Self-consistent and
idempotent: re-running `make stamp` gives identical hashes unless the content
changed.
- The stamp is **static HTML written at build time** — no JavaScript computes or
injects it in the browser. `stamp.js` is build tooling, never loaded by a page.
- **Verify a served page**: blank the md5 field to 32 zeros and the sha256 field
to 64 zeros, then re-hash with `sha256sum`/`md5sum` — must match the footer.
- **Deploy = push to BOTH repos.** A page change is not live until it lands in
both. Pushing only the source changes nothing served; pushing only the deploy
repo orphans the source of truth. Both, every time:
1. **source** — this repo (`zebra-report`): edit `web/*.html` → `make stamp` →
commit → **push to origin**.
2. **served** — `www.unturf.com`: copy the page(s) into
`~/git/www.unturf.com/zebra-report/` (`chat.html` → `index.html`;
`zebra-audio.html`, `how-it-works.html`, `host-your-own.html` keep their
names) → commit → **push to origin**.