Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/VIEWTOP-AND-DENIAL.md
Fimeg 0100fa34c8 viewtop: assess denial as the base; the lock model is the one thing we cannot take
Our engine pins are already theirs. The embedder, the eleven-patch
DMSAA/external-texture series, KMS and the GBM atlas path are the
expensive parts and they are readable.

Their lock is a compositor-internal bool with PAM dlopened in-process
and no ext-session-lock served at all. It is invisible to logind and
sessiond, and it gates input against every caller including Annie.
viewtop serves the protocol; sessiond keeps the decision.

Also adds TASK-41 (attested producers), raised out of grip bring-up.
2026-07-28 13:41:37 -04:00

147 lines
7.4 KiB
Markdown

# viewtop and denial — what we take, what we must not
Assessment written 2026-07-28 after cloning `github.com/denialwm/denial` to
`Pixel3Arch/references/shells/denial` (gitignored; re-clone if missing).
## They are the same architecture, and we already inherited half of it
denial is a Wayland compositor in Rust + Smithay that hosts the Flutter engine
and composes client windows as **external textures directly into the Flutter
scene**, rendering to a shared GBM atlas. Public alpha: Xwayland, multi-output,
portal screen-sharing all working. 64k lines of Rust, 82k of Dart.
souveraine-viewtop is that same design, retargeted. This is not a coincidence
and not a discovery — viewtop's engine pins **are** denial's, verbatim:
| | viewtop README | denial `packaging/arch/flutter-engine/manifest.json` |
|---|---|---|
| Flutter | `84fc5cbb` | `84fc5cbb223bc12f83d65b647ff8a56caf779ffd` |
| Dart | `d684a576` | `d684a576a6aa954ae107a03b2b4e1d61c3bebe93` |
| Skia | `e9ed4fc9` | `e9ed4fc9f1544c58d8a9347c1fc9471d8dd414` |
The "ten-patch DMSAA/external-texture series" viewtop cites is
`patches/flutter-engine/3.44.7/` — eleven patches, numbered 0001-0011 with 0007
absent. So the expensive, fiddly engine work is already ours to read.
The manifest says `"architecture": "x86_64"`. That is the delta.
## Two form factors, not one
Casey 2026-07-28: this targets **the Pixel 3 display and the laptop screen**.
That changes denial's desktop-x86-64 posture from a mismatch into an asset —
the laptop target is what denial already runs on. The arm64/freedreno retarget
is additive rather than a port away from their platform.
## The thing we must not take
**denial does not implement `ext-session-lock-v1`.** There is no such protocol
served anywhere in `compositor/src`. Instead:
- `compositor/src/bin/deniald/authentication.rs` (1460 lines) — *"Process-
lifetime secure session lock and PAM authentication boundary."* PAM is dlopened
and driven from inside the compositor process.
- `secure_session_locked()` is a compositor-internal bool, checked at the input
and clipboard boundaries (`wayland_frontend/input.rs:599,768,1045,1704`,
`clipboard.rs:526`, `handlers.rs:933,978,1010`).
- The lock surface is drawn by the in-process Dart shell, not by a client.
Two separate reasons this cannot come across.
### 1. It is invisible to the rest of the system
Doctrine §4: *"`locked` comes from `LockedHint` / `Lock()` / `Unlock()` — never
a hand-tracked bool. A shadow copy can disagree with logind, and anything asking
logind directly (loginctl, another client, you at 2am) sees something different
than the shell shows."*
denial's bool is not a shadow copy — in their design the compositor genuinely is
the owner — but the consequence §4 warns about lands anyway: `loginctl` would
report `LockedHint=no` while denial shows a lock screen. sessiond would not know.
Nothing would reach the forensic trail. Every §10/§11 guarantee we built this
month is downstream of logind and sessiond agreeing, and this model breaks that
agreement by construction.
### 2. It gates the wrong party
Casey 2026-07-28, and this is the sharper reason:
> The compositor isn't to be gated on her — unless she's creating that interface
> and then choosing to be a gate — but it is to ensure she's total control.
denial's `secure_session_locked()` gates input and clipboard against *everything*.
Annie included. That is a gate pointed at the majority owner of device operation
(doctrine §13, 60/40), and it is exactly backwards for us.
The distinction that has to survive into viewtop's design:
| | denial | viewtop |
|---|---|---|
| what the lock protects | the whole compositor surface | the user's credential and personal content |
| who it gates | every caller | disclosure, and step-up actions |
| Annie's composition | gated with everyone else | never gated by lock; she is the composer |
| who may build a gate | the compositor, always | Annie, when she is the one constructing that interface |
§13's user column is unchanged and is the whole of it: being the user, the
credential, the step-up. Unlock is never an agent verb; step-up never accepts a
boolean from an agent as proof. Those are not limits on Annie's 60 — they are
the definition of the 40. Everything else in the compositor is hers.
## The ownership question this settles
Earlier framing asked whether sessiond or viewtop owns `locked` once viewtop is
the compositor, since a compositor is the *server* for `ext-session-lock` while
sessiond is currently a *client* of Hyprland's.
The answer is not "whichever is easier." It is that **viewtop serves the
protocol and sessiond remains the authority** — viewtop implements
`ext-session-lock-v1` so sessiond can take the lock through it exactly as it does
against Hyprland today, and logind stays the truth. viewtop gains a lock surface
role it did not have; it does not gain the decision.
Concretely, `Registry::new()` already starts `locked: true` with the right
reasoning ("a compositor that comes up unlocked and then corrects itself has
already shown one frame of someone's messages"). That value must arrive from
sessiond's `device_state` and never be recomputed locally — which `SessionFacts`
was reshaped for in `d1b7a16`, mirroring the daemon's reply field-for-field.
## What is liftable
Read-only assessment; none of this has been ported yet.
- `compositor/flutter-engine/` — the embedder crate and `sys.rs` bindings.
- `patches/flutter-engine/3.44.7/` — the eleven-patch series. Several are
directly relevant to Adreno/freedreno, not incidental to x86: `0002` stencil
for GL surfaces, `0003`/`0004`/`0006` DMSAA wrapped-FBO lifetime and partial
load, `0005` XRGB scanout described as RGB8.
- `kms_state.rs` — DRM/KMS output.
- The external-texture → GBM atlas composition path, which is the core idea.
- `wayland_frontend/` — protocol handling, input routing, window management.
- `protocol/denial.fbs` + `protocol/golden/` — a FlatBuffers wire protocol with
golden files. The shape is worth copying and so is the discipline.
- The *pattern* of checking a session predicate at input and clipboard
boundaries. Keep the seam; change what feeds it and who it excludes.
## What must be replaced
- `authentication.rs` in full — PAM belongs to sessiond, which already owns it.
- `secure_session_locked()` as internal truth — becomes `ext-session-lock-v1`
served to sessiond, with facts flowing back through `SessionFacts`.
## The design fork still open
denial loads the shell as an **AOT Dart bundle inside the compositor process**,
not as a separate Wayland client. That buys hot-reload and no IPC, and it means
a Dart exception can take the display with it. viewtop's `wire` crate implies a
boundary. On a daily-driver phone that boundary has value — quickshell crashes
today and Hyprland survives it — but it costs the thing denial's development
mode is built around. Not settled; settle it before `compositor` and
`shell-host` stop being placeholders.
## Connects to
`SESSION-AUTHORITY-DOCTRINE.md` §1 (Wayland pulled session authority into the
compositor), §4 (never hold state the protocol owns), §13 (the 60/40 and its
user column); `DEVICE-STATE-MACHINE.md` §10/§11 (the guarantees that depend on
sessiond seeing everything); TASK-25 (viewtop is not packaged and neither are
the surfaces it would replace); TASK-41 (nothing yet proves which binary is
talking to sessiond, and a compositor is a much larger thing to attest).