Watch
1
0
Fork
You've already forked SouveraineOS
0

docs: reconcile USB, Waydroid, audio and charging

This commit is contained in:
Fimeg 2026-08-07 14:52:28 -04:00
commit 4aa61d735c
9 changed files with 357 additions and 50 deletions

View file

@ -1,10 +1,67 @@
# PAF — Pixel 3 (blueline) Arch Architecture Framework # PAF — Pixel 3 (blueline) architecture
The single living spine for this port. Migrated 2026-07-10 from the pmOS The single living spine for the phone. It began in the pmOS project, moved to
project and modernized; fully self-contained — the old location is Pixel3Arch in July, and was copied here on 2026-07-24 without completing the
disposable. Doctrine unchanged: **update the relevant layer in place**, move. SouveraineOS became canonical on 2026-08-07; device code remains in
never append another dated handoff. Dated docs are evidence, the spine is `../../Pixel3Arch/`.
what you read first.
**Update the relevant layer in place.** Dated captures are evidence, not a
second spine. Git holds the old arrangements: SouveraineOS `72aaa53` is the
July import; Pixel3Arch `9b1afd1` is the last full in-repo PAF snapshot.
## Start here — "I want to work on my phone"
The six things you need before any of the layer docs below matter.
**Where it is.** `10.10.20.234` over WiFi (stable since 2026-07-20; the MAC is
pinned, so it does not drift the way this doc used to claim). `172.16.42.1`
over USB, which is the fallback when WiFi is wedged.
ssh -i ~/.ssh/ani casey@10.10.20.234
**What it runs.** Souveraine OS — Arch Linux ARM, systemd, pacman, bash. Our
own kernel (`uname -r``7.1.1-sdm845-g<hash>`; the hash is ground truth for
what is actually booted). Slot A is ours, slot B is LineageOS and is the
fallback — never touch it. `casey` has passwordless sudo.
**Where things get built.** Never here, and never on the laptop:
ArchDev, `casey@10.10.20.123`, same key. The phone has 3.5 GB of RAM and is the
daily driver; the laptop is for device ops only. This holds for `cargo test`
and `cargo clippy` too, not just release builds.
**How it updates.** `sudo pacman -Syu`. Everything we build ships as a signed
package — kernel included. Reaching for fastboot because a kernel changed is a
regression, not a fallback; see `../START-HERE.md` and
`../../Pixel3Arch/CLAUDE.md` for the few real reasons to flash. COLD boots only
(a warm reboot poisons TZ/remoteproc), and reboots are Casey's, at the device.
**When it will not answer.** Two failures look identical (ping works, ssh
hangs) and both are routing, not the phone:
- *USB:* networkd matches `Name=usb*`, so a stale down `usb0` and the live
`usb1` both hold 172.16.42.1 and replies leave by the dead one.
`sudo ip addr flush dev usb0`, and on the laptop
`sudo ip neigh del 172.16.42.1 dev <iface>` if the MAC looks wrong.
- *WiFi:* ath10k_snoc wedges with auth timeouts — reload the module, then
`systemctl restart wpa_supplicant`.
**Work that started on the phone.** This is the one with no tooling, so it is
the one that bites. Things get hacked on-device and then live only there —
`~/stevia-pkg` was four pkgrels deep before any of it came back. To find it:
ssh -i ~/.ssh/ani casey@10.10.20.234 'ls -lt ~ | head -20'
pacman -Qm # foreign packages — built here, owned by nobody upstream
pacman -Qo /usr/bin/<x> # "No package owns" = it was hand-copied
Finishing it means three steps, in order, and the middle one is the one that
gets skipped: **merge it back** into `../../Pixel3Arch/pkgs/<name>/` (the repo
copy may have moved too — diff both ways, they can both claim the same pkgrel),
**build it on ArchDev** (`CARCH=aarch64 makepkg -f --ignorearch --nodeps`;
`../../Pixel3Arch/pkgs/stevia` is the worked cross example), then **install the
built package** rather than the on-device one. A thing that only exists on the
phone is one reflash from gone, and `../../Pixel3Arch/rootfs-overlay/` only
reaches a device at provision time — which is TASK-27's whole subject.
---
- **[platform.md](platform.md)** — hardware map, live partition layout, - **[platform.md](platform.md)** — hardware map, live partition layout,
reaching the phone, boot chain, recovery. reaching the phone, boot chain, recovery.
@ -16,19 +73,21 @@ what you read first.
telemetry, the tap-to-wake/SLPI handoff goal. telemetry, the tap-to-wake/SLPI handoff goal.
- **[slpi.md](slpi.md)** — sensor island: the five-fix bring-up chain and - **[slpi.md](slpi.md)** — sensor island: the five-fix bring-up chain and
the `sar.cc:27` wall. Arch status header + pmOS-era record. the `sar.cc:27` wall. Arch status header + pmOS-era record.
- **[audio.md](audio.md)** — WCD9340/SLIMbus audio state: working speaker - **[audio.md](audio.md)** — WCD9340/SLIMbus audio: working HiFi speaker and
path, verified zero-capture transport state, and the current mic frontier. microphone profiles, working two-way calls, and the lifecycle archaeology.
- **[edge-sense.md](edge-sense.md)** — Active Edge force path, calibration,
sessiond gating, and the remaining doze gap.
- **[modem.md](modem.md)** — WORKING on Arch. Status header + the full - **[modem.md](modem.md)** — WORKING on Arch. Status header + the full
52-wall research record (modemsmem root cause). 52-wall research record (modemsmem root cause).
- **[evidence/](evidence/)** — the dated capture/research docs the spine - **[evidence/](evidence/)** — dated capture/research docs the spine cites:
cites (modem/qcril/TZ archaeology, pmOS-era platform/build/README audio handoffs under `evidence/audio/`, power handoffs under
snapshots, diag captures). Read-only history; never update these. `evidence/power/`, plus modem/QCRIL/TZ archaeology and pmOS-era snapshots.
Read-only history; amend the living layer doc instead.
Related in-repo: `../CLAUDE.md` (project doctrine + session resume state), Related: `../START-HERE.md` for triage, `../docs/tasks/` for live work, and
`../docs/tasks/` (per-subsystem task scopes; auto-rotate-task.md is the `../../Pixel3Arch/` for kernel, packages, overlays, and device tooling.
live SLPI log), `../docs/phone-shell-ux.md` (Hyprland/Quickshell layer).
## Status at a glance (2026-07-11) ## Status at a glance (reconciled 2026-08-07)
| Layer | State | | Layer | State |
|---|---| |---|---|
@ -38,13 +97,15 @@ live SLPI log), `../docs/phone-shell-ux.md` (Hyprland/Quickshell layer).
| Modem / RF (LTE data) | 🟢 WORKING on Arch — mmcli connected, LTE, Fido, clat up (verified live 2026-07-10) | | Modem / RF (LTE data) | 🟢 WORKING on Arch — mmcli connected, LTE, Fido, clat up (verified live 2026-07-10) |
| WiFi (ath10k_snoc) | 🟢 True unplugged cold boot verified 2026-07-11: no `rejected:90`, associates, gets DHCP, and reaches gateway + Internet. `failed to install key … -110` still occurs during association/roam but did not break connectivity; `cryptmode=1` remains a fallback only if it does. | | WiFi (ath10k_snoc) | 🟢 True unplugged cold boot verified 2026-07-11: no `rejected:90`, associates, gets DHCP, and reaches gateway + Internet. `failed to install key … -110` still occurs during association/roam but did not break connectivity; `cryptmode=1` remains a fallback only if it does. |
| Bluetooth (bluetoothd) | 🟢 WORKING 2026-07-10: controller was invisible because WCN3990 ships no BD address (kernel leaves hci0 unconfigured) — blueline-bt-bdaddr.service sets a persisted one; live scan verified | | Bluetooth (bluetoothd) | 🟢 WORKING 2026-07-10: controller was invisible because WCN3990 ships no BD address (kernel leaves hci0 unconfigured) — blueline-bt-bdaddr.service sets a persisted one; live scan verified |
| USB device / KVM | 🟠 Full GUD + HID + NCM + smoo composite builds on ArchDev with transactional rollback and a sessiond-owned power-sheet verb. Not installed on glass yet: the phone still runs usb-signaller 0.3.1-3 and has no smoo package — see `../USB/README.md` and TASK-62. |
| SLPI sensor island | 🟢 `sar.cc` fatal is gone (regression, not a real limit) — `iio-sensor-proxy` runs always-on via `blueline-sensors-enable.service` (After=blueline-hexagonrpcd-sdsp), no more on-demand start/stop. Proximity (TMD2725) and accelerometer both live — see slpi.md | | SLPI sensor island | 🟢 `sar.cc` fatal is gone (regression, not a real limit) — `iio-sensor-proxy` runs always-on via `blueline-sensors-enable.service` (After=blueline-hexagonrpcd-sdsp), no more on-demand start/stop. Proximity (TMD2725) and accelerometer both live — see slpi.md |
| Tap-to-wake | 🟠 AP-side gesture mode detects DBLTAP and delivers wake, but the current screen-toggle path can half-wake on the first tap. DT2W now gates on proximity — `blueline-screen-toggle on` checks `ProximityNear` over D-Bus and suppresses the wake if blocked (phone in pocket). The SLPI gesture-nanoapp path is unblocked by the sar.cc fix but not required for AP-side DT2W. | | Tap-to-wake | 🟠 AP-side gesture mode detects DBLTAP and delivers wake, but the current screen-toggle path can half-wake on the first tap. DT2W now gates on proximity — `blueline-screen-toggle on` checks `ProximityNear` over D-Bus and suppresses the wake if blocked (phone in pocket). The SLPI gesture-nanoapp path is unblocked by the sar.cc fix but not required for AP-side DT2W. |
| Audio | 🟠 SPEAKERS WORK end-to-end 2026-07-10 (pipewire→UCM→QUAT_TDM→CS35L36). Mic capture is verified all-zero despite a live Q6 SLIMBUS_0_TX start, correct TX7/TX8 map and WCD interface-port configuration. IFD `0x097` remains `00` (Android capture has `21`; direct debugfs write does not stick); cause unproven — see audio.md | | Audio | 🟢 WORKING. PipeWire/WirePlumber exposes the HiFi speaker and microphone; Casey uses the mic for STT. The Voice Call profile carries real two-way calls, with earpiece/speaker switching handled by `blueline-callaudio-hook` — see audio.md. |
| Camera / NFC / haptics / fingerprint | ⚪ parked — task scopes in ../docs/tasks/ | | Active Edge (squeeze) | 🟢 WORKING 2026-07-28 — force detected (2502-2990 deflection) after the rail became a DT gpio-hog; producer packaged, enabled, and gated through sessiond's pocket veto. No detection while the AP sleeps — see edge-sense.md |
| Fingerprint (FPC1020) | 🟠 driver written and building 2026-08-01 — `drivers/input/misc/fpc1020.c` on branch `fingerprint-fpc1020`, reset gpio7 / IRQ gpio121 verified against this unit's DTBO, reports `KEY_WAKEUP`. **Never run on the device.** Electrical half only; capture/matching are a TrustZone app we do not load — see TASK-44 |
| Camera | 🟠 Front IMX355 streams real frames. Rear IMX363 DT exists but remains unbuilt — see TASK-46. |
| NFC / haptics | ⚪ Open — see TASK-45 and the live task index. |
**One-line state:** daily-driver Arch on slot A with LTE, WiFi, BT, speaker **One-line state:** daily-driver Arch on slot A with LTE, WiFi, Bluetooth,
audio, working daytime touch, and an on-demand SSC client. Current reliability speaker/microphone/call audio, daytime touch, Active Edge, always-on sensors,
work is the libssc-triggered wake lockup plus green/half-wake display behavior; and a working front camera. Android (LOS) remains intact on slot B.
microphone capture remains the audio wall. Android (LOS) is intact on slot B as
fallback.

View file

@ -1,5 +1,35 @@
# Audio — WCD9340 / SLIMbus # Audio — WCD9340 / SLIMbus
## Current — 2026-08-07
This is a working phone audio stack, not an active microphone bring-up.
- **Runtime:** PipeWire + WirePlumber + pipewire-pulse + pipewire-alsa. Native
PulseAudio and the ABI-provider shims are historical experiments, not the
running authority.
- **HiFi:** both CS35L36 speakers play; the WCD9340 microphone captures clean
mono audio through DMIC4 → DEC5/TX5. Casey uses it regularly for STT.
- **Calls:** the `Voice Call` UCM profile, `souveraine-callaudio`,
`souveraine-q6voiced`, and `souveraine-callaudiod` carry real two-way calls
and the earpiece/speaker path.
- **Persistence:** WirePlumber restores volumes and the selected profile. Do
not add another volume writer; role policy remains TASK-39.
- **One hardware debt remains:** the kernel permanently holds codec sysclk and
MICB2 from probe. Audio works, but the hold costs idle power and blocks a
clean suspend story. TASK-07 owns removing it.
The July **“UN-PIN TEST PASSED”** below removed the userspace always-open mic
pin. It did **not** remove kernel commit `cd7758eae`'s sysclk + MICB2 hold; that
similar noun is the likely source of the remembered “we resolved it.”
The implementation lives in Pixel3Arch under `pkgs/souveraine-ucm-blueline`,
`pkgs/blueline-callaudio` (package name `souveraine-callaudio`),
`pkgs/souveraine-callaudiod`, and `pkgs/q6voiced` (package name
`souveraine-q6voiced`).
Everything below is the evidence ledger. Headings such as “still open” describe
the date beneath them unless the current block above repeats the defect.
## PIPEWIRE RESTORED — 2026-07-19 late evening ## PIPEWIRE RESTORED — 2026-07-19 late evening
The PA-native arrangement is retired. Live state, all verified on-device: The PA-native arrangement is retired. Live state, all verified on-device:
@ -36,12 +66,58 @@ The PA-native arrangement is retired. Live state, all verified on-device:
the voice BE routes (`SLIMBUS_0_RX Voice Mixer VoiceMMode1`, the voice BE routes (`SLIMBUS_0_RX Voice Mixer VoiceMMode1`,
`VoiceMMode1 Capture Mixer SLIMBUS_0_TX`) are applied — DPCM `VoiceMMode1 Capture Mixer SLIMBUS_0_TX`) are applied — DPCM
no-backend rejection. With routes applied the PCM opens. This is why no-backend rejection. With routes applied the PCM opens. This is why
q6voiced logged "Failed to open tx/rx: Invalid argument". Because the q6voiced logged "Failed to open tx/rx: Invalid argument". ~~Because the
PCM is unprobeable, ACP can never synthesize a VoiceCall profile, so PCM is unprobeable, ACP can never synthesize a VoiceCall profile, so
callaudiod's profile-switching path is structurally dead on this card. callaudiod's profile-switching path is structurally dead on this card.~~
Shipped `blueline-callaudio-hook` (system service): MM `CallAdded` **Wrong — see 2026-07-29.** ACP never probes `hw:0,2`; it probes the PCMs
UCM VoiceCall verb, last `CallDeleted` → HiFi verb restore. q6voiced the verb *declares*, and the profile was lost for two reasons of our own
then opens an already-routed PCM. Real two-way call test pending. making. Shipped `blueline-callaudio-hook` (system service): MM `CallAdded`
→ UCM voice verb, last call terminating → HiFi verb restore. q6voiced then
opens an already-routed PCM.
## PHONE CALLS WORK — TWO-WAY AUDIO ON A REAL BEARER — 2026-07-29
Incoming call, answered, audio both directions, confirmed by ear at both ends.
Five defects had to fall, every one of them ours:
- **Verb named `VoiceCall`.** alsa-lib's `SND_USE_CASE_VERB_VOICECALL` is
`"Voice Call"`, with the space, and callaudiod accepts only a profile
carrying that prefix (`cad-pulse.c:508`). Renamed.
- **The voice verb gave MultiMedia1 no backend.** HiFi routes it through
`QUAT_TDM_RX_0`; the voice verb routed nothing, so opening `hw:0,0` under
the verb returned -EINVAL and ACP dropped the profile — *"Profile 'Voice
Call' mapping 'Voice Call: Speaker: sink': output PCM open failed"*,
`spa-acp-tool -v -v`. That, not an unprobeable `hw:0,2`, is why no voice
profile ever appeared. The card carries one now.
- **Downlink went to `SLIMBUS_0_RX`.** blueline's earpiece and loudspeaker are
both CS35L36 amps on `QUAT_TDM_RX_0`; the WCD9340 is the uplink and the
headset jack. Voice sent to the codec reached nothing — audible as a call
the far end heard perfectly while this end heard silence.
- **A front-end takes exactly one backend.** These mixers are *not* additive:
enabling a second silently clears the first, measured in both directions.
Setting `QUAT` and `SLIMBUS` together is not belt-and-braces, it is a no-op.
- **q6voiced latches a failed open.** tinyalsa's `pcm_open()` returns a
non-NULL handle on failure and `q6voiced_open()` guards on `if (v->tx)`, so
one lost race silences the whole call. Reachable on any outgoing call:
`mm_state_is_active()` counts DIALING, emitted in the same instant as
`CallAdded`. Incoming calls are safe only by accident — RINGING_IN is not
counted active, so their open lands well after the routes. Patched in
`souveraine-q6voiced` to release and retry.
Two further findings:
- **The mic died after every call**, not just occasionally. `alsaucm` holds its
UCM context in-process, so a one-shot `set _verb HiFi` has no previous verb
to dismantle and never runs its `DisableSequence`. TX5 and TX7 both stayed
mapped into `AIF1_CAP`, SLIM_0_TX carried two channels into a mono front-end,
and capture read exact zeros. The hook now unmaps the outgoing leg explicitly.
- **callaudiod cannot own earpiece/speaker on this card.** It switches a sink
*port* (`cad-pulse.c:961`) and needs both port types on one sink, but ACP
models UCM devices as one profile per combination — `Voice Call (Earpiece,
Mic)` and `Voice Call (Mic, Speaker)`. `ConflictingDevices` does not change
it. enchilada sidesteps this by giving each transducer its own PCM; blueline
has both amps behind one. The toggle is therefore
`blueline-callaudio-hook speaker on|off`, writing the amps directly.
## ROOT CAUSE FOUND AND FIXED — MICS CAPTURE REAL AUDIO — 2026-07-19 ## ROOT CAUSE FOUND AND FIXED — MICS CAPTURE REAL AUDIO — 2026-07-19
@ -90,7 +166,7 @@ kexecs `/boot/vmlinuz-blueline` + `/boot/dtbs/sdm845-google-blueline.dtb` per
live chosen node). Flashing boot_a via fastboot does NOT change the running live chosen node). Flashing boot_a via fastboot does NOT change the running
kernel/DTB — install to `/boot` on the rootfs instead. kernel/DTB — install to `/boot` on the rootfs instead.
### Still open — capture intermittency (the remaining bug) ### Then-open capture intermittency — superseded later on 2026-07-19
- Ordering effect observed on the first post-fix boot: DMIC3/4 through - Ordering effect observed on the first post-fix boot: DMIC3/4 through
MUX6/MUX7 read zero until the DEC5/DEC6 (Android-native) routes ran once; MUX6/MUX7 read zero until the DEC5/DEC6 (Android-native) routes ran once;
after that, the previously-dead MUX7+DMIC4 handset route worked. Some after that, the previously-dead MUX7+DMIC4 handset route worked. Some
@ -561,7 +637,7 @@ returned `0` and the audio data was still all zero.
- This work did not start `iio-sensor-proxy`. There is no evidence that the - This work did not start `iio-sensor-proxy`. There is no evidence that the
sensor-client HACK or SLPI wake-lockup mechanism gates this audio path. sensor-client HACK or SLPI wake-lockup mechanism gates this audio path.
## Verified capture path ## Verified zero-capture path — historical 2026-07-11
The controlled route is Android's documented handset/endfire pair: The controlled route is Android's documented handset/endfire pair:
@ -600,14 +676,14 @@ The controlled route is Android's documented handset/endfire pair:
- This is **not** a missing FE-to-BE connection: runtime ASoC state reports - This is **not** a missing FE-to-BE connection: runtime ASoC state reports
`MultiMedia2 Capture: start` with `SLIM Capture 1: start` as its active `MultiMedia2 Capture: start` with `SLIM Capture 1: start` as its active
backend, and the codec's AIF/decimator event callbacks run. With the CDC backend, and the codec's AIF/decimator event callbacks run. With the CDC
muxes selected, the DMIC callbacks run too; the remaining wall is after the muxes selected, the DMIC callbacks run too; the unresolved point at that
fully powered codec capture path. date was after the fully powered codec capture path.
- Reducing the route to the downstream-style single TX7/DMIC4 channel did not - Reducing the route to the downstream-style single TX7/DMIC4 channel did not
change the result: a four-second `hw:0,1` mono capture contained 192,000 change the result: a four-second `hw:0,1` mono capture contained 192,000
zero samples. Channel count/configuration is therefore not the activation zero samples. Channel count/configuration is therefore not the activation
trigger. trigger.
## What the evidence does and does not say ## What the 2026-07-11 evidence did and did not say
Android's captured recording state has IFD `0x097=21`; mainline reads `00`. Android's captured recording state has IFD `0x097=21`; mainline reads `00`.
During a five-second live TX7/TX8 capture, writing `0097 21` through the During a five-second live TX7/TX8 capture, writing `0097 21` through the
@ -623,7 +699,7 @@ unconstrained map three channels (`0x86`, `0x87`, `0x88`), while disabling it
produces the correct TX7/TX8 pair above. That is a real UCM/machine-routing produces the correct TX7/TX8 pair above. That is a real UCM/machine-routing
cleanup item, but it did not restore capture and is not the root cause. cleanup item, but it did not restore capture and is not the root cause.
## Next narrow work ## Proposed next work on 2026-07-11 — superseded
1. Capture the working Android interface-device transition, or find the 1. Capture the working Android interface-device transition, or find the
downstream owner/meaning of IFD `0x097`; compare the actual write sequence, downstream owner/meaning of IFD `0x097`; compare the actual write sequence,

View file

@ -1,10 +1,29 @@
# USB — the Pixel 3 as a KVM device # USB — the Pixel 3 as a KVM device
**Exploration opened 2026-08-04** from Casey: "start exploring the notion of ## Current — 2026-08-07
KVM from this thing." This folder holds USB-related design and measurement;
the KVM exploration starts here.
## What exists, measured on the phone (2026-08-04) The direction is settled: another computer sends its display **into** the
phone over GUD; the phone returns keyboard, pointer and touch as boot HID.
The same composite keeps NCM for control and carries a smoo FunctionFS
responder for host-backed storage. This is not the earlier UVC/VNC sketch.
| Piece | Current state |
|---|---|
| Display in | `souveraine-usb-kvm`: GUD FunctionFS responder, RGB565 surface on Wayland |
| Hands back | Boot keyboard + relative pointer; window keyboard, mouse and touch become HID reports |
| Storage | `smoo-gadget` FunctionFS responder; `ublk_drv` on the phone |
| Control link | NCM remains in the composite |
| Mode owner | `usb-signaller` exposes `kvm_mode`; GUD and smoo readiness gate the bind, and failure restores the prior mode |
| Human/agent surface | `sessiond` owns the audited `usb` / `set_usb_mode` verbs; held-power **Power Options** exposes HID and full KVM |
| Canonical source | receiver/session/UI/package in `~/Projects/souveraine`; device mechanism in `~/Projects/usb-signaller`; package recipes in Pixel3Arch |
| Build proof | full ARM64 release receiver and sessiond built on ArchDev; receiver tests + strict Clippy pass |
| Phone | **Not installed yet.** Live phone still has `usb-signaller 0.3.1-3`, no `smoo`, and advertises no HID/KVM mode |
TASK-62 owns the package and glass landing. The material below is the
2026-08-04 exploration ledger; it remains useful for host mode and for why the
discarded UVC/VNC routes were considered, but it is no longer the build plan.
## Baseline measured on the phone (2026-08-04, historical)
| Piece | State | | Piece | State |
|---|---| |---|---|
@ -81,7 +100,7 @@ real keyboard for free. If the dock's *monitors* ever light up, the DP lanes
exist and we go hunt the mux wiring; with no typec/altmode wired, expect exist and we go hunt the mux wiring; with no typec/altmode wired, expect
nothing from them. nothing from them.
## The KVM notion ## Original KVM notion — superseded by TASK-62
Plug the phone into another machine's USB port. The phone controls that Plug the phone into another machine's USB port. The phone controls that
machine and sees its screen — keyboard, mouse, monitor, in one cable. Two machine and sees its screen — keyboard, mouse, monitor, in one cable. Two
@ -122,7 +141,7 @@ BIOS, so a BIOS-stage KVM is HID plus whatever screen the machine itself has.
variety of sources", i.e. the phone-side userspace we would adapt to feed variety of sources", i.e. the phone-side userspace we would adapt to feed
viewtop/compositor frames into the gadget. viewtop/compositor frames into the gadget.
## Questions to settle, in order ## Original questions — answered by TASK-62
1. **Directions:** KVM-into-another-machine only, or also the reverse 1. **Directions:** KVM-into-another-machine only, or also the reverse
(phone screen out as webcam)? Both share the UVC config; the userspace (phone screen out as webcam)? Both share the UVC config; the userspace
@ -141,7 +160,7 @@ BIOS, so a BIOS-stage KVM is HID plus whatever screen the machine itself has.
package still publishes unsigned raw `.pkg.tar.zst` (TASK-25's package still publishes unsigned raw `.pkg.tar.zst` (TASK-25's
`repo-add --sign` is unbuilt) and installs are cold-boot events. `repo-add --sign` is unbuilt) and installs are cold-boot events.
## Next steps (ordered, each measurable) ## Original next steps — historical
1. **Prove HID today** — provision a HID keyboard function into a scratch 1. **Prove HID today** — provision a HID keyboard function into a scratch
gadget on the phone (configfs, no reboot), bind it, write gadget on the phone (configfs, no reboot), bind it, write
@ -155,7 +174,7 @@ BIOS, so a BIOS-stage KVM is HID plus whatever screen the machine itself has.
4. **usb-signaller "kvm" mode** once the pieces exist, with clean 4. **usb-signaller "kvm" mode** once the pieces exist, with clean
teardown/provision against the dev mode. teardown/provision against the dev mode.
## Acceptance (measured) ## Original acceptance sketch — historical
- Phone plugged into the laptop enumerates as keyboard+mouse; typing from - Phone plugged into the laptop enumerates as keyboard+mouse; typing from
the phone lands in the laptop's focused app. the phone lands in the laptop's focused app.

View file

@ -29,6 +29,7 @@ the threat register), [`souveraine-components/`](souveraine-components/) (the
authority binaries and their audit ladder). authority binaries and their audit ladder).
**Designs of record.** [`STORAGE-ENCRYPTION.md`](STORAGE-ENCRYPTION.md), **Designs of record.** [`STORAGE-ENCRYPTION.md`](STORAGE-ENCRYPTION.md),
[`WAYDROID.md`](WAYDROID.md), [`../USB/README.md`](../USB/README.md),
[`FEDERATION.md`](FEDERATION.md), [`FEDERATION.md`](FEDERATION.md),
[`session-authority-boot-order.md`](session-authority-boot-order.md). [`session-authority-boot-order.md`](session-authority-boot-order.md).

View file

@ -285,10 +285,11 @@ built once; the gating design is its own doc.
container (free once binder is up — `ANDROID_BINDER_IPC=y`, but container (free once binder is up — `ANDROID_BINDER_IPC=y`, but
`ANDROID_BINDERFS` off is a real Waydroid blocker, kernel change), per-agent `ANDROID_BINDERFS` off is a real Waydroid blocker, kernel change), per-agent
UNIX accounts for native callers (the Personal-class work above). UNIX accounts for native callers (the Personal-class work above).
- **Nothing installed today:** no Flatpak, bubblewrap, xdg-desktop-portal, or - **Waydroid is installed now:** measured 2026-08-07 on the phone with
Waydroid in either repo. The namespace/cgroup/seccomp primitives are compiled `ANDROID_BINDER_IPC=y` and `ANDROID_BINDERFS=y`. Its container unit was
in and unused. "Free granularity from Flatpak" is a design premise that active while its session was stopped; [`WAYDROID.md`](WAYDROID.md) owns that
requires installing Flatpak first. layered status. Flatpak and an app-identity portal backend remain absent, so
the runtime-isolation design here is still unbuilt.
- **Runtime vs at-rest:** fscrypt protects a powered-off device. It does not - **Runtime vs at-rest:** fscrypt protects a powered-off device. It does not
stop a process in the unlocked session reading another's files — that is the stop a process in the unlocked session reading another's files — that is the
runtime half, and per-app UIDs are its foundation too. At-rest encryption runtime half, and per-app UIDs are its foundation too. At-rest encryption

28
docs/WAYDROID.md Normal file
View file

@ -0,0 +1,28 @@
# Waydroid — current device state
## Current — 2026-08-07
Waydroid is present on the daily driver; documents saying it is absent are
stale.
Measured on the phone at `10.10.30.213`:
| Fact | State |
|---|---|
| Package | `waydroid 1.6.3-1` |
| Kernel | `7.1.1-sdm845-g08e9db54cfce` |
| Binder | `CONFIG_ANDROID_BINDER_IPC=y`, `CONFIG_ANDROID_BINDERFS=y`; binder, hwbinder and vndbinder devices configured |
| Container unit | active/running, disabled at boot |
| Waydroid session | stopped |
| Vendor type | mainline |
This is installation and runtime truth, not an ownership claim. Souveraine has
no Waydroid lifecycle verb, health model, power policy, or first-class app
surface yet. The old radial-dial memory of “Kill waydroid” is not present in
the canonical dial and should not be resurrected as a shell command. If the
container becomes a device verb, it belongs in sessiond with start, stop,
status, refusal and forensic history; the power sheet may then be its view.
Until that exists, a stopped session with a running container is expected to
look ambiguous. Do not call it stopped, running, or absent without naming
which layer was measured.

View file

@ -5,6 +5,25 @@
**Repos:** `~/Projects/souveraine` (`packaging/upower-souveraine`, **Repos:** `~/Projects/souveraine` (`packaging/upower-souveraine`,
`surfaces/quickshell`, `src/sessiond`). `surfaces/quickshell`, `src/sessiond`).
## Policy decision — 2026-08-07
This is a **charge ceiling + resume floor**, not “trickle charging.” Charge
type is evidence about what the charger is doing; thresholds are control over
when it may do it. Keep those as different fields and different verbs.
The live phone has only
`pmi8998-charger/charge_control_end_threshold=99`; there is no
`charge_control_start_threshold`. The resulting narrow hardware behaviour is
the observed 99→stop, 98→start twitch. The wanted policy is a wide, flexible
hysteresis pair: stay below 100%, and permit a bounded agent decision to hold
charging until a useful floor (30% was Casey's example) before re-enabling it.
Authority is settled: a human defines the safe envelope; an agent may propose
or choose inside it only through step-up, and every move is recorded. No agent
or QML surface writes charger sysfs directly. Until both thresholds and that
authority path exist, the power sheet shows this as a draft rather than a
switch that lies.
## Why this exists ## Why this exists
Charging has no owner. Every surface reads UPower directly and renders Charging has no owner. Every surface reads UPower directly and renders

View file

@ -0,0 +1,101 @@
# TASK 62 — full USB KVM: the phone becomes the glass and the hands
**Status: built on ArchDev, not installed on the phone.** Opened 2026-08-07.
This task owns the one-cable composite from mode request through rollback and
the first hardware acceptance. It does not own phone-as-host power; TASK-58
does.
## Contract
The attached computer is the host. It sends a GUD display into the Pixel 3.
The phone draws that display in a Wayland surface and returns keyboard,
pointer and touch through boot-protocol HID. NCM keeps a control lane and smoo
offers host-backed storage through FunctionFS/ublk.
One mode change owns all four functions:
```
kvm_mode = ffs.gud + ffs.smoo + ncm.usb0 + hid.keyboard + hid.pointer
```
`usb-signaller` prepares both FunctionFS mounts, starts both responders, waits
for both readiness signals, then binds the UDC. Any failure tears the partial
mode down and restores the previously working USB posture. The shell never
writes configfs: it asks sessiond, which records an `Action::UsbMode` and calls
the system D-Bus mechanism.
## Where the work lives
| Layer | Canonical path | State |
|---|---|---|
| Receiver + display + HID | `souveraine/src/bin/souveraine-usb-kvm.rs` | built ARM64 |
| Session authority | `souveraine/src/sessiond/{protocol,device_state,server}.rs` | `usb` + `set_usb_mode`, including `kvm` |
| Human surface | `souveraine/surfaces/quickshell/` | nested Power Options; HID and KVM are live leaves when advertised |
| Souveraine package | `souveraine/packaging/arch/PKGBUILD.prebuilt` | receiver included on aarch64 |
| Gadget mechanism | `usb-signaller/` | full composite, readiness gates, rollback, user-owned HID/GUD lane |
| Device packages | `Pixel3Arch/pkgs/{usb-signaller,smoo}` | recipes written; not published/installed |
The separate local `souveraine-usb` repo is a safety copy from the first
layout attempt, not a source of record. Do not build another package from it.
## Verification already earned
- Receiver unit tests: RGB565 dirty-rect conversion and HID usage mapping.
- Receiver strict Clippy: clean.
- ArchDev full release build: ARM64 PIE, 3,893,576 bytes,
SHA-256 `1fb58409cb1923599bcbcf229d1d2c1a61d4d7b2143387f21e7c6043ec44622d`.
- ArchDev sessiond build: ARM64 PIE,
SHA-256 `46046a53231b4f838fb47d8a8e75a51c6c8471ea3ccd2bc43bbf7ced2e38841a`.
- Mode scripts parse, the source/package copies match, and the Casey-owned
runtime directory, environment file and `/dev/hidg*` nodes are reachable by
the unprivileged receiver.
`gud-gadget` has no declared licence. Casey explicitly accepted that risk for
this private build. Do not publish its binary into a public archive until that
is resolved.
## Phone truth before landing
Measured at `10.10.30.213` on 2026-08-07:
- `usb-signaller 0.3.1-3`, `souveraine r379`, no `smoo` package;
- live mode `charging_only`;
- advertised modes: developer, tethering, charging-only, MTP and host;
- data role `device`, charger online;
- `charge_control_end_threshold=99`; no start-threshold attribute.
So a KVM button in a newly deployed shell must remain unavailable until the
new usb-signaller package advertises `kvm_mode`. That is deliberate feature
discovery, not a version guess.
## Landing order
1. Publish/install the new `souveraine`, `smoo`, and `usb-signaller` packages.
Keep Wi-Fi SSH alive; do not make a USB mode change the only recovery path.
2. Verify `get_modes` includes `hid_mode,kvm_mode` before opening the power
sheet. Verify all installed files are package-owned.
3. Enter HID mode first. Confirm keyboard and pointer enumerate on the host,
reports arrive, and developer mode restores cleanly.
4. Enter KVM mode. Confirm host enumeration for GUD, both HIDs, NCM and smoo;
then confirm a host frame reaches the phone and touch returns to the host.
5. Pull the cable and force one responder failure. In both cases the daemon
must stop services, release FunctionFS, and leave or restore a usable mode.
6. Only after those checks make KVM a normal daily-driver choice.
No phone mutation or flash belongs before step 1's packages exist.
## Acceptance
- A host frame is visible on the phone; resize redraws without a blank buffer.
- Physical keyboard, mouse and touch in that surface control the attached host.
- NCM remains usable and smoo reaches its ready endpoint.
- Closing the surface, unplugging, or a failed responder leaves no stuck UDC,
root-only endpoint or dead management port.
- The held-power sheet reports the actual mode and returns to developer USB.
## Connects to
`USB/README.md` (exploration and host-mode record), TASK-30 (verb tables),
TASK-33 (adjacent charging policy), TASK-50 (the inverse hand-on-glass path),
TASK-57 (phone-as-gadget dock), TASK-58 (host-role VBUS), SAF federation
(attached identity and probe ownership).

View file

@ -29,6 +29,7 @@ Four that are cheap relative to what they unblock:
| # | Task | What's left | | # | Task | What's left |
|---|------|-------------| |---|------|-------------|
| 60 | [Multitasking is a place, not a transition](60-the-multitasking-view.md) | One-owner compositor and shell halves are built; strand repro and measured landing pass on glass. `16c6ee1` is pushed and CI 1236 is green. What remains is Casey's thumb on the settle/fade and a real split-zone card check. |
| 53 | [viewtop audit: what it left open](53-viewtop-audit-open.md) | All ten fixed and **packaged onto the phone** (`r74.g6c9614c20471`). The cold boot then exposed a *third*: a lost sessiond race had left the power button dead — discovery waits now. Hardware found two more the gates did not — `delivered` reported true for a withheld contact, and a drift warning that fired on every normal close. What is left needs the PIN. | | 53 | [viewtop audit: what it left open](53-viewtop-audit-open.md) | All ten fixed and **packaged onto the phone** (`r74.g6c9614c20471`). The cold boot then exposed a *third*: a lost sessiond race had left the power button dead — discovery waits now. Hardware found two more the gates did not — `delivered` reported true for a withheld contact, and a drift warning that fired on every normal close. What is left needs the PIN. |
| 43 | [viewtop: the Souveraine compositor](43-viewtop-compositor.md) | Runs on the phone. Next lane is the ext-session-lock admission gate. The two-client lock handoff is the thing not to get wrong. | | 43 | [viewtop: the Souveraine compositor](43-viewtop-compositor.md) | Runs on the phone. Next lane is the ext-session-lock admission gate. The two-client lock handoff is the thing not to get wrong. |
| 29 | [SouveraineOS Updater](29-souveraine-updater.md) | Ships and installs by `pacman -Syu`. **Read-only on device** until something runs a polkit agent — that is the shell's job. Also owns TASK-42's layer 2. | | 29 | [SouveraineOS Updater](29-souveraine-updater.md) | Ships and installs by `pacman -Syu`. **Read-only on device** until something runs a polkit agent — that is the shell's job. Also owns TASK-42's layer 2. |
@ -37,6 +38,7 @@ Four that are cheap relative to what they unblock:
| # | Task | What's left | | # | Task | What's left |
|---|------|-------------| |---|------|-------------|
| 62 | [Full USB KVM](62-full-usb-kvm.md) | GUD display-in + HID + NCM + smoo, transactional mode rollback, sessiond verbs and the nested power-sheet control all build. Package publication and the staged host/glass acceptance remain; the phone still runs usb-signaller pkgrel 3 and has no smoo. |
| 08 | [Device state manager / power profiles](08-device-state-manager.md) | Largest task; sub-divide before starting. (e) lease missing, (f) **surfaced 2026-07-31** — health, evidence and the decision trail render on the Device page; last-seen and grip still absent from `device_state`. (g) confidence gates need a decision before code. | | 08 | [Device state manager / power profiles](08-device-state-manager.md) | Largest task; sub-divide before starting. (e) lease missing, (f) **surfaced 2026-07-31** — health, evidence and the decision trail render on the Device page; last-seen and grip still absent from `device_state`. (g) confidence gates need a decision before code. |
| 16 | [culver: messenger + contacts](16-culver-messenger-contacts.md) | A/B/D landed. C (contact card) builds and awaits phone acceptance. F is the folded-in TASK-01. | | 16 | [culver: messenger + contacts](16-culver-messenger-contacts.md) | A/B/D landed. C (contact card) builds and awaits phone acceptance. F is the folded-in TASK-01. |
| 19 | [First-party Settings control center](19-settings-control-center.md) | State-machine + source-health readout **built 2026-07-31**, unverified on glass. 34 and 42's layer 3 still surface here. | | 19 | [First-party Settings control center](19-settings-control-center.md) | State-machine + source-health readout **built 2026-07-31**, unverified on glass. 34 and 42's layer 3 still surface here. |
@ -59,7 +61,6 @@ Four that are cheap relative to what they unblock:
| # | Task | What's left | | # | Task | What's left |
|---|------|-------------| |---|------|-------------|
| 60 | [Multitasking is a place, not a transition](60-the-multitasking-view.md) | Written after building the wrong thing. The rail scales the active zone's windows on swipe — a *transition* mistaken for the *destination*. It scales the wrong windows (2 s poll, and the shell has no "focused window" fact at all), and scaling one zone can never show what is backgrounded. `WindowOverview` draws one column of window cards and has no concept of zones; its own header admits the gap. Needs deciding before code: card = window or zone, `ScreencopyView` pictures vs posed real windows, where focus comes from, and whether the poll gets replaced by a push. |
| 59 | [Her face on the glass: a Live2D presence](59-her-face-on-the-glass.md) | **Phase 1 passed on hardware 2026-08-05: ~58 fps, rig verified drawing (`painted_px` 23.6k/24k), 283 MB WebKit RSS.** Scoped from a Live2D board-girl found in a cloned blog. The reference streams its chat over **SSE terminated by `[DONE]`** — the contract souveraine's server already speaks, so the integration is accidentally done. Base layer is **wry**, not the Chromium already on the phone; the first deliverable is a measured frame rate for the Cubism runtime in WebKit on freedreno, and if that fails the task stops there. Replaces ani-avatar's 2.5 GB of sprite states with one rig. Reference rig ships as the working placeholder and gets edited toward Annie — it just stays out of any public repo. Summoned by app launch and/or the radial dial, not always-on. | | 59 | [Her face on the glass: a Live2D presence](59-her-face-on-the-glass.md) | **Phase 1 passed on hardware 2026-08-05: ~58 fps, rig verified drawing (`painted_px` 23.6k/24k), 283 MB WebKit RSS.** Scoped from a Live2D board-girl found in a cloned blog. The reference streams its chat over **SSE terminated by `[DONE]`** — the contract souveraine's server already speaks, so the integration is accidentally done. Base layer is **wry**, not the Chromium already on the phone; the first deliverable is a measured frame rate for the Cubism runtime in WebKit on freedreno, and if that fails the task stops there. Replaces ani-avatar's 2.5 GB of sprite states with one rig. Reference rig ships as the working placeholder and gets edited toward Annie — it just stays out of any public repo. Summoned by app launch and/or the radial dial, not always-on. |
| 58 | [Host power: the SMB2 OTG boost](58-host-power-smb2-boost.md) | Host data works; the boost isn't. The SMB2 5 V boost exists in silicon and mainline's `qcom-smbx-charger` never registers it. Investigation first (which tree builds the running kernel, does smb2 touch the boost, does `qcom_pmic_typec` bind pmi8998), then vbus-supply + regulator + typec. Unlocks host-mode power, power-bank mode, and the Pi without a wall wart. | | 58 | [Host power: the SMB2 OTG boost](58-host-power-smb2-boost.md) | Host data works; the boost isn't. The SMB2 5 V boost exists in silicon and mainline's `qcom-smbx-charger` never registers it. Investigation first (which tree builds the running kernel, does smb2 touch the boost, does `qcom_pmic_typec` bind pmi8998), then vbus-supply + regulator + typec. Unlocks host-mode power, power-bank mode, and the Pi without a wall wart. |
| 57 | [The Pi dock head](57-pi-dock-head.md) | Phone stays a gadget; the Pi 4 drives the monitor + dock. NCM link is default, Venus encoder is present, compositor capture landed — the unproven seams are `zwlr_screencopy` vs our own capture IPC, and the Pi's own bring-up ("major malfunctions TBD"). Four phases: Pi bring-up, static chain, real capture + udev trigger, input injection. | | 57 | [The Pi dock head](57-pi-dock-head.md) | Phone stays a gadget; the Pi 4 drives the monitor + dock. NCM link is default, Venus encoder is present, compositor capture landed — the unproven seams are `zwlr_screencopy` vs our own capture IPC, and the Pi's own bring-up ("major malfunctions TBD"). Four phases: Pi bring-up, static chain, real capture + udev trigger, input injection. |
@ -77,10 +78,10 @@ Four that are cheap relative to what they unblock:
| 36 | [CHRE nanoapp host for the SLPI](36-chre-nanoapp-host.md) | Three methods to add; method table recovered 07-27. No longer on grip's critical path — wanted for DozeDeep detection and `elmyra_haptics_control`. | | 36 | [CHRE nanoapp host for the SLPI](36-chre-nanoapp-host.md) | Three methods to add; method table recovered 07-27. No longer on grip's critical path — wanted for DozeDeep detection and `elmyra_haptics_control`. |
| 34 | [SLPI FastRPC session wedge](34-slpi-bringup-sequencing.md) | Re-diagnose. The real symptom: a DSP session survives its host daemon's death and only a cold boot clears it. **No longer blocks grip.** | | 34 | [SLPI FastRPC session wedge](34-slpi-bringup-sequencing.md) | Re-diagnose. The real symptom: a DSP session survives its host daemon's death and only a cold boot clears it. **No longer blocks grip.** |
| 03 | [Boot timing: splash → lock](03-boot-timing-splash-to-lock.md) | The EBUSY handoff race is the last timing bug. Splash text killed, holder added. | | 03 | [Boot timing: splash → lock](03-boot-timing-splash-to-lock.md) | The EBUSY handoff race is the last timing bug. Splash text killed, holder added. |
| 07 | [WCD9340 mic](07-mic-wcd9340-slim-tx.md) | Functional since 07-20. One defect: the power pin that makes it work. Kernel branch `mic-race-fix`. | | 07 | [WCD9340 mic](07-mic-wcd9340-slim-tx.md) | Functional and in daily STT use. One defect: the permanent sysclk + MICB2 power pin. Kernel branch `mic-race-fix`. |
| 09 | [Suspend-resume FTS calibration race](09-suspend-resume-fts.md) | Kernel-side real fix; interim fix shipped. Cold-boot only. | | 09 | [Suspend-resume FTS calibration race](09-suspend-resume-fts.md) | Kernel-side real fix; interim fix shipped. Cold-boot only. |
| 12 | [Face auth as a capability factor](12-gaze-reference-face-auth.md) | Per-target. Phone needs front-camera bring-up — **that is TASK-46's IMX355 half.** | | 12 | [Face auth as a capability factor](12-gaze-reference-face-auth.md) | Per-target. Phone needs front-camera bring-up — **that is TASK-46's IMX355 half.** |
| 17 | [Keyboard + dictation polish](17-keyboard-stevia-dictation.md) | No word completion (stevia asks hunspell for `en-us`, the dict is `en_US`); no recording indicator on the mic key. The layout half moved to TASK-24. **TTS now exists** (`10.10.20.123:7863`) — the settings field can be filled. | | 17 | [Keyboard + dictation polish](17-keyboard-stevia-dictation.md) | Dictation works in daily use. Open: no word completion (`en-us` vs `en_US`) and the stateful mic-key indicator is written but not built. Layout moved to TASK-24. **TTS now exists** (`10.10.20.123:7863`). |
| 18 | [Selection/highlight action menu](18-selection-menu.md) | Component first, fluid before functional. **Read Aloud is unblocked** — TTS is live. | | 18 | [Selection/highlight action menu](18-selection-menu.md) | Component first, fluid before functional. **Read Aloud is unblocked** — TTS is live. |
| 20 | [Player: video + agent control](20-player-video-agent.md) | Video, snapshot, manifest. Preserve the music path. | | 20 | [Player: video + agent control](20-player-video-agent.md) | Video, snapshot, manifest. Preserve the music path. |
| 21 | [Reboot-to-Android confirmation](21-reboot-android-confirmation.md) | Scoped 07-22. A one-tap irreversible slot flip still sits bare in app search. | | 21 | [Reboot-to-Android confirmation](21-reboot-android-confirmation.md) | Scoped 07-22. A one-tap irreversible slot flip still sits bare in app search. |
@ -91,7 +92,7 @@ Four that are cheap relative to what they unblock:
| 33 | [Battery belongs to the state machine](33-battery-as-device-state.md) | Last raw feed going straight to the glass. The charge ceiling is writable today. | | 33 | [Battery belongs to the state machine](33-battery-as-device-state.md) | Last raw feed going straight to the glass. The charge ceiling is writable today. |
| 37 | ["Back" as a first-class verb](37-navigation-back-verb.md) | Our `Gestures.qml` is already the compositor half; `can_go_back` is the dial's enabled/reason. | | 37 | ["Back" as a first-class verb](37-navigation-back-verb.md) | Our `Gestures.qml` is already the compositor half; `can_go_back` is the dial's enabled/reason. |
| 38 | [Pill swipe-to-max swaps the keyboard](38-pill-swipe-osk-swap.md) | Micro. The only route to a terminal keyboard since stevia dropped its layout. | | 38 | [Pill swipe-to-max swaps the keyboard](38-pill-swipe-osk-swap.md) | Micro. The only route to a terminal keyboard since stevia dropped its layout. |
| 39 | [Audio levels as tracked state](39-audio-levels-as-state.md) | Defaults + roles, **not** a save bug — WirePlumber persistence measured working 07-28. Do not add a second writer. | | 39 | [Audio levels as tracked state](39-audio-levels-as-state.md) | Open policy: roles, defaults, lock tiers, and state-driven profile intent. WirePlumber persistence works; do not add a second writer. |
| — | [Unify the shell trees](unify-shell-trees-laptop-phone.md) | **DONE 2026-07-31** — verified by md5 across both devices: 918 differing files → 7, all 7 declared in the `ii-phone` overlay, laptop-only **0**, zero stranded. The KEEP list was promoted to repo overrides, not lost. Blocks nothing; archive it. | | — | [Unify the shell trees](unify-shell-trees-laptop-phone.md) | **DONE 2026-07-31** — verified by md5 across both devices: 918 differing files → 7, all 7 declared in the `ii-phone` overlay, laptop-only **0**, zero stranded. The KEEP list was promoted to repo overrides, not lost. Blocks nothing; archive it. |
## Conventions ## Conventions