docs: quickshell design docs moved to SouveraineOS/docs
They're cross-surface (laptop + phone) OS docs, not shell-tree internals. Live copies now in ~/Projects/SouveraineOS/docs/ alongside SESSION-AUTHORITY-DOCTRINE.md and the session dumps.
This commit is contained in:
parent
6532572b8d
commit
0f50a3e9c5
7 changed files with 0 additions and 859 deletions
|
|
@ -1,55 +0,0 @@
|
|||
# How the phone shell works
|
||||
|
||||
Three surfaces: **bar** (top), **dock** (bottom, above the navigation rail),
|
||||
and Souveraine's integrated **navigation rail** (bottom edge).
|
||||
Edit here → `./deploy.sh --phone` → restart. Deployed via symlink; live edits = git-tracked.
|
||||
|
||||
## Navigation rail (`modules/souveraine/navigation/SystemGestureRail.qml`)
|
||||
|
||||
- **Always visible.** `WlrLayer.Overlay` + `ExclusionMode.Ignore` + `margins.bottom:0`. Survives fullscreen, dock, OSK. Never lower the layer.
|
||||
- **Owned by Souveraine.** It is loaded by `SouveraineFamily` on phone mode;
|
||||
there is no second Quickshell configuration, external IPC hop, or Hyprland
|
||||
layer rule to keep in sync.
|
||||
- Gestures (MouseArea — pointer handlers don't get touch here):
|
||||
- **double-tap** → toggles the active app's named `fullscreen` mode (whole
|
||||
display, no border/gaps). Targets `Hyprland.activeToplevel.address` — not
|
||||
hyprctl's focus (the tap focuses the shell).
|
||||
- **swipe up** → reveals the dock.
|
||||
- **swipe down** → dismisses the dock.
|
||||
- **No keyboard on the rail.** OSK = 3-finger hyprgrass swipe only.
|
||||
|
||||
## Dock (`modules/ii/dock/Dock.qml`)
|
||||
|
||||
- **On `WlrLayer.Overlay`** so the navigation rail can reveal it over fullscreen apps.
|
||||
- **Hidden = layer unmounted** (`visible:false`), not just tucked — else it paints over fullscreen.
|
||||
- **Visibility** (`computeDockState`, first match wins):
|
||||
1. fullscreen app on focused monitor → **Hidden** (unless rail-revealed)
|
||||
2. pinned → **Pinned** (only state that reserves exclusive zone)
|
||||
3. preview-hover → **Shown**
|
||||
4. empty desktop / no focused app → **Shown**
|
||||
5. else (normal app focused) → **Hidden**
|
||||
- **Reserves the navigation rail height** at the bottom visually *and in its
|
||||
layer-shell input mask*, so the dock cannot intercept rail touches.
|
||||
- **Bar height is content-driven**: the window sizes itself to the button row (64px buttons) + row margin + rail height; `Config.options.dock.height` is only a floor. Don't tune the config height to "fix" icon clipping.
|
||||
- **App list width is capped** to the screen (`DockApps.maxWidth`); past that the icon row scrolls horizontally by touch (flick is only enabled when overflowing, so drag-to-combine keeps working when everything fits).
|
||||
- **DockStack renders with the same content block as DockAppButton** (icon + half-reserved dot strip, centered as one unit). Keep them structurally identical or they drift apart on the bar.
|
||||
- Depends on **`GlobalStates.dockRevealed`** — lives in the surface tree (`GlobalStates.qml`, stock ii + patch) and the deploy manifest. If ii updates its GlobalStates, re-diff and re-apply the patch.
|
||||
|
||||
## souveraine-settings (planned, not built)
|
||||
|
||||
A future settings app for the phone shell. Planned sections:
|
||||
|
||||
- **Dock** — pinned apps, height floor, monochrome icons, ignored-app regexes (today: hand-edited in `~/.config/illogical-impulse/config.json`).
|
||||
- **Stacks** — create/rename/reorder app stacks and their members (today: `dock.stacks` strings in config.json, or drag-to-combine on the dock).
|
||||
|
||||
The dock already reserves its entry points: the long-press menu's "App settings…"
|
||||
and the `dockSettings` IPC target (`openApp(appId)` / `open()`) in `Dock.qml`.
|
||||
Both are STUBS — they log and pulse the dock, nothing opens. That's deliberate:
|
||||
the IPC name stays stable so the settings app can take it over without touching
|
||||
the dock.
|
||||
|
||||
## Fullscreen API (Hyprland 0.55, Lua dispatch)
|
||||
|
||||
- Dispatch: `hl.dsp.window.fullscreen({ window="address:0x…", mode="fullscreen", action="toggle" })`.
|
||||
- **`fullscreen`** = whole display, no borders or gaps. ← the navigation rail uses this.
|
||||
- **`maximized`** = keeps the normal workspace layout margins.
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
# Phone shell — interface architecture
|
||||
|
||||
The phone has four primary interfaces the user actually touches: the **pill**,
|
||||
the **dock**, the **keyboard**, and **touch/gesture**. This doc names what each
|
||||
one is *meant to be*, what it is *today*, and the single seam where a hardcode
|
||||
should become a small framework so the surface can grow without rewrites.
|
||||
|
||||
Principle: **clean beats feature-rich.** Each surface has one job and one place
|
||||
its behaviour is tuned. A new feature should be a new entry in an existing
|
||||
table, not a new hardcoded branch. Where a value is tuned in more than one
|
||||
place today, that is the bug — not the value.
|
||||
|
||||
Truth hierarchy: this doc describes intent; `HOW-IT-WORKS.md` describes the
|
||||
current wiring; the code is the reality. When they disagree, fix the code or
|
||||
fix the doc in the same change — never leave a third story.
|
||||
|
||||
---
|
||||
|
||||
## 1. Pill — the always-on gesture bar
|
||||
|
||||
**Meant to be:** the one surface that is *always* reachable, on any app, in any
|
||||
state. A thin, dumb input strip at the bottom edge that recognises a small
|
||||
fixed vocabulary of gestures and forwards them — it owns no app state and makes
|
||||
no decisions beyond "which gesture was this."
|
||||
|
||||
**Today (`pill/shell.qml`):** `WlrLayer.Overlay`, `ExclusionMode.Ignore`,
|
||||
`margins.bottom:0`. Gesture arbitration is one MouseArea (double-tap, swipe up,
|
||||
swipe down) → `dock` IPC. This part is clean and well-reasoned.
|
||||
|
||||
**The seam / the bug:** the pill's z-order guarantee is *documented but not
|
||||
enforced*. `pill/shell.qml:23-27` sets namespace `quickshell:pill` and points
|
||||
at "the `order` layerrule in hyprland.lua" — **that layerrule does not exist**
|
||||
(hyprland.lua only has a commented-out generic `^my-overlay$` example). So
|
||||
after an `ii` restart the pill can be buried under the dock and stop taking
|
||||
touch — the exact failure the comment says is solved. Fix: add the real
|
||||
`order` layerrule for `quickshell:pill`, or drop the promise from the code and
|
||||
docs. Do not add more pill gestures until stacking is enforced — an always-on
|
||||
surface that silently dies is worse than a plain one.
|
||||
|
||||
**Growth rule:** new pill gestures are new cases in the one MouseArea, each
|
||||
forwarding to a *named* IPC target — never doing app logic inline.
|
||||
|
||||
---
|
||||
|
||||
## 2. Dock — the app switcher and stack surface
|
||||
|
||||
**Meant to be:** the surface that shows and switches running/pinned apps and
|
||||
lets the user group them into stacks. It owns app-presentation state; it does
|
||||
not own gesture recognition (the pill and hyprgrass feed it via IPC).
|
||||
|
||||
**Today:** interaction layer (`Dock.qml`, `DockAppButton.qml`, `DockStack.qml`)
|
||||
is strong — drag-to-combine, fan-out arc, visibility state machine
|
||||
(`computeDockState`). The weak layer is **state/config**:
|
||||
|
||||
- Stacks are a hand-parsed string `"stackId|appId,appId"` (`TaskbarApps.qml:25`).
|
||||
A `|` or `,` in a name corrupts it silently; parsed/encoded in ~6 places.
|
||||
→ move to **one JSON blob per entry** (array-of-strings the adapter tolerates,
|
||||
but each string is JSON): kills the delimiter-corruption class, keeps the
|
||||
adapter happy. This is the agreed direction.
|
||||
- Stack ids are count-based (`nextStackName` = `"Stack " + (count+1)`,
|
||||
`:87`) → collide after a delete, and `id` is the lookup key. Two stacks can
|
||||
share an id and cross-contaminate members. → stable uid, display name
|
||||
separate from id.
|
||||
- Combining into a new stack suppresses the target's standalone pin at *render*
|
||||
time (`apps` getter) but never removes it from `pinnedApps` in config
|
||||
(`:99-108`) → the pin lingers; unstack works by luck. → remove-on-combine so
|
||||
config matches what's shown.
|
||||
- Tap-cycle `lastFocused` index (`DockAppButton :196`) is never reconciled when
|
||||
windows open/close and is also written by hover (`:180`) → tapping can focus
|
||||
the wrong window. → clamp/reconcile against the live toplevel model.
|
||||
- Magic numbers live in the components: `pillStripHeight 32` (`Dock.qml:161`),
|
||||
dwell `500` (twice: `DockAppButton:133`, `DockStack:116`), double-tap `350`,
|
||||
arc geometry (`DockStack:33-36`). → lift into `Config.options.dock` next to
|
||||
the `height`/`monochromeIcons`/`hoverRegionHeight` that already live there.
|
||||
- `reorderStackMember` (`TaskbarApps:113`) is dead — superseded by
|
||||
`setStackOrder`. → remove, or wire the menu reorder it was meant for.
|
||||
|
||||
**The seam:** `Config.options.dock` already exists — the framework anchor is
|
||||
already there. "More structure" here means *finishing* that namespace (all
|
||||
tuning knobs + a real stack schema under it), not inventing a new one.
|
||||
|
||||
**Growth rule:** dock feel/config knobs go under `Config.options.dock`; stack
|
||||
members are structured entries, not delimited strings. Rename + ungroup are
|
||||
table-stakes and should exist before any fancier stack feature.
|
||||
|
||||
---
|
||||
|
||||
## 3. Keyboard (OSK) — squeekboard
|
||||
|
||||
**Meant to be:** an on-screen keyboard that appears on text focus and can be
|
||||
summoned by one deliberate gesture, driven through **one** IPC target so its
|
||||
visibility and any focus-grab shield always move together.
|
||||
|
||||
**Today:** squeekboard, launched from `hyprland.lua`, auto-shows via
|
||||
input-method-v2, toggled by 3-finger swipe-up routed through ii's `osk` IPC.
|
||||
The *routing* is right (single `osk` target).
|
||||
|
||||
**The seam / the drift:** the comments and the retained
|
||||
`quickshell-ii-patches/OnScreenKeyboard.qml` still describe **wvkbd**
|
||||
(retired 2026-07-10) and its focus-grab shield — the `osk` IPC now drives
|
||||
squeekboard, but the code comments and the history-only patch file still tell
|
||||
the wvkbd story. Same doc-vs-reality drift as the pill layerrule. → update the
|
||||
comments to squeekboard reality; confirm whether `OnScreenKeyboard.qml`'s
|
||||
GlobalStates.oskOpen plumbing is still the thing `osk toggle` hits, or vestigial.
|
||||
|
||||
**Growth rule:** one `osk` IPC target owns show/hide; layout/theme is
|
||||
squeekboard config, addressable later from souveraine-settings. No second path
|
||||
to toggle the keyboard.
|
||||
|
||||
---
|
||||
|
||||
## 4. Touch / gesture — the input spine
|
||||
|
||||
**Meant to be:** one routing layer that maps a physical touch gesture to a
|
||||
named interface action. Today this is the messiest surface because it is spread
|
||||
across four dispatch styles.
|
||||
|
||||
**Today (`hyprland.lua`):**
|
||||
- `hl.gesture{fingers=3,horizontal}` → workspace (Hyprland-native)
|
||||
- `hl.plugin.hyprgrass.bind{...}` → close / terminal / edge-swipes →
|
||||
`qs -c ii ipc call <target>`
|
||||
- the pill's own MouseArea → `dock` IPC
|
||||
- `touchdevice.output = "DSI-1"` binds touch to the panel for rotation
|
||||
|
||||
Two input sources (pill swipe-up, hyprgrass edge-swipe-up) both drive the dock;
|
||||
no single arbiter. Gesture → action bindings are inline `exec_cmd` strings
|
||||
scattered through the file.
|
||||
|
||||
**The seam:** this is the memory note "don't hardcode bindings blindly; need a
|
||||
routing system to avoid overlap." The clean form is **one gesture table** —
|
||||
edge/finger/direction → named IPC target — read top-to-bottom so overlaps are
|
||||
visible in one place. Every binding already ends in a `qs ... ipc call <target>`;
|
||||
the framework is just making that table explicit and single-sourced instead of
|
||||
sprinkled through `hyprland.lua`.
|
||||
|
||||
**Growth rule:** a new gesture is a new row in the gesture table pointing at a
|
||||
named IPC target. Two rows may not target the same action from different edges
|
||||
without being adjacent in the table (so overlap is obvious).
|
||||
|
||||
---
|
||||
|
||||
## Cross-cutting: the one recurring defect
|
||||
|
||||
Three of four surfaces have the **same** flaw — *a documented guarantee the
|
||||
code doesn't enforce*: the pill layerrule (doc says pinned, code doesn't pin),
|
||||
the keyboard (comments say wvkbd, code runs squeekboard), the dock stack pin
|
||||
(render says suppressed, config says still pinned). Cleaning these up is mostly
|
||||
making the code match its own stated contract, then giving each surface exactly
|
||||
one place it's tuned.
|
||||
|
||||
## Status (2026-07-13)
|
||||
|
||||
1. ~~Pill layerrule~~ DONE — `pill-above-dock` rule added to hyprland.lua
|
||||
(`order = 1` on `^quickshell:pill$`). VERIFY ON GLASS: `order`
|
||||
passthrough in the Lua layer_rule wrapper is untested on this build.
|
||||
2. ~~Gesture table~~ DONE — hyprgrass binds are one `touch_gestures` table
|
||||
in hyprland.lua; the two other gesture sources (hl.gesture workspace
|
||||
swipe, the pill's MouseArea) are listed in its header so overlap stays
|
||||
visible in one read.
|
||||
3. ~~Dock state layer~~ DONE — JSON-per-entry stacks (legacy pipe entries
|
||||
still parse, rewritten on next write), minted `stack-N` ids that never
|
||||
reuse after delete, name separate from id (+ `renameStack()`, no UI
|
||||
yet), pin actually removed from config on combine, tap-cycle starts
|
||||
from the truly focused window, dead `reorderStackMember` removed,
|
||||
`dragDwellMs`/`pillStripHeight` lifted into `Config.options.dock`.
|
||||
4. ~~Keyboard~~ DONE — hyprland.lua comments tell the squeekboard story;
|
||||
`OnScreenKeyboard.qml` (the "osk" IPC owner) moved into this surface +
|
||||
deploy manifest. Remaining hand-deployed patches (BarContent, Network,
|
||||
QuickSliders, switchwall.sh) noted in overlays/QUICKSHELL-MOVED.md.
|
||||
|
||||
## Open decision — two-stage swipe-up was removed, not lost by accident?
|
||||
|
||||
Souveraine commit `6e21c50` ("resume agent conversations from server")
|
||||
reworked the dock state machine: the Peek state, `dockRevealPulse`, and the
|
||||
two-stage swipeUp (reveal → escalate to overview) all went away in favor of
|
||||
a flat reveal/hide contract — under a commit title that says nothing about
|
||||
the dock. Pixel3Arch CLAUDE.md still records two-stage as SHIPPED
|
||||
(2026-07-11). The replacement comment is articulate, so this reads as a
|
||||
deliberate redesign, but it needs Casey's confirmation: keep the flat
|
||||
contract (overview = the dock's apps button), or restore the escalation.
|
||||
Comments in hyprland.lua/HOW-IT-WORKS now describe the flat behavior that
|
||||
actually runs.
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
# Souveraine quickshell surface
|
||||
|
||||
The desktop shell as a Souveraine surface — the primary visual frontend for
|
||||
SouveraineOS, with the TUI remaining the dive-in instrument.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `services/Souveraine.qml` — the substrate singleton. The ONE server
|
||||
connection every shell module hangs off: agent inventory, conversation
|
||||
lifecycle, the SSE turn stream (raw events re-emitted via
|
||||
`streamEvent(var)`), the backchannel (`cancelTurn()` / `interject(text)`),
|
||||
and the desktop sensorium — every send carries ambient context (active
|
||||
window, open apps, cursor position) so she perceives the room she is being
|
||||
spoken to in. Device sensors (SouveraineOS positional data from the Pixel
|
||||
3 kernel path) extend `collectAmbient()`.
|
||||
- `services/Ai.qml` — ii-compat adapter. Keeps the API the illogical-impulse
|
||||
sidebar expects; owns no transport. Shapes wire events into the message
|
||||
objects the existing chat UI renders.
|
||||
- `modules/` (coming) — presence (portrait PNGs from memfs, posture state
|
||||
machine), cockpit (subconscious pane), agents (master–detail manager),
|
||||
settings, schedules. Each subscribes to the Souveraine singleton.
|
||||
|
||||
## What changes
|
||||
|
||||
- "Models" in the sidebar are **Souveraine agents** (`GET /v1/agents`).
|
||||
Picking one starts a conversation with that agent — memory, sensors,
|
||||
subconscious and all.
|
||||
- Messages stream over the server's SSE endpoint
|
||||
(`POST /v1/conversations/:id/messages`), authenticated with the agent's
|
||||
bearer token from `~/.souveraine/server/agents/<id>/api_token`.
|
||||
- Subconscious **surfacings**, **reflection**, and **archivist** pressure
|
||||
render in the chat as interface notes (dedicated widgets later).
|
||||
- Reasoning and sensor activity render inside collapsible `<think>` blocks.
|
||||
- Keys/providers/temperature are owned by `souveraine.toml` — the sidebar's
|
||||
`/key` and `/temp` commands now just point there. The keyring path is dead.
|
||||
- Token pressure is fetched after each turn from
|
||||
`GET /v1/conversations/:id/tokens`.
|
||||
|
||||
## Portability (KDE / non-Hyprland)
|
||||
|
||||
`Souveraine.qml` itself is compositor-agnostic: quickshell runs on any
|
||||
wlroots-ish Wayland compositor and KWin; window sensing uses the
|
||||
foreign-toplevel protocol (KWin implements it); the cursor read tries
|
||||
`hyprctl`, then `kdotool`, then degrades to nothing — ambient never blocks
|
||||
a send. Server autostart is desktop-neutral (systemd user unit, nohup
|
||||
fallback), so opening any surface summons her.
|
||||
|
||||
What is NOT portable yet is the chrome: the chat UI is illogical-impulse's
|
||||
sidebar. The path for "I run KDE, can I use this?" is a standalone
|
||||
quickshell config (own ShellRoot + a window hosting the chat/presence
|
||||
modules) that ships `Souveraine.qml` unchanged — planned once the modules
|
||||
stop being ii-embedded. Same service, same mappings, different shell.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
./deploy.sh # backs up upstream Ai.qml, symlinks ours in
|
||||
./deploy.sh -u # restore upstream
|
||||
```
|
||||
|
||||
Requires the server: `souveraine server` (default http://127.0.0.1:8484,
|
||||
override with `ai.souveraineUrl` in the ii config).
|
||||
|
||||
## Wire contract
|
||||
|
||||
The server's SSE layer is a full mirror of `BackendEvent` (see
|
||||
`src/api/models.rs::StreamEvent` — exhaustive `From` impls both ways, so a
|
||||
new engine event is a compile error at the seam, not a silent skip). The
|
||||
surface consumes the personification channel: subconscious tokens buffer and
|
||||
flush as one bubble when the N+1 pass ends (`subconscious_pass`), halts land
|
||||
as body signals, interstitials render by register (cenno = quiet aside,
|
||||
her_voice = gutter passage), `primary_complete` releases the input while the
|
||||
stream stays open for the subconscious, and `context_pressure` drives the
|
||||
live token counter. `atmosphere`/`outfit`/`itinerary` are logged, awaiting
|
||||
their shell-chrome layer.
|
||||
|
||||
Server-side, the backchannel and verbs exist for every surface:
|
||||
`POST /v1/conversations/:id/cancel` (interrupt, `*[raised hand]*`
|
||||
semantics), `.../interject` (mid-turn notes, queued between turns),
|
||||
`GET .../messages` (transcript backfill), `POST .../fork` (`/btw`
|
||||
side-quests). `SendMessageRequest.ambient` injects the sensorium note.
|
||||
RemoteBackend rides all of it, so TUI remote mode gained cancel/interject/
|
||||
fork/resume in the same stroke.
|
||||
|
||||
## Not yet wired
|
||||
|
||||
- Sidebar UI hooks for cancel (Esc) and interject (type-while-busy) — the
|
||||
service functions exist, the ii chat input doesn't call them yet
|
||||
- Conversation resume in the sidebar (server verb exists; surface always
|
||||
starts fresh)
|
||||
- Atmosphere/outfit/itinerary driving actual shell chrome (events arrive;
|
||||
modules pending)
|
||||
- File/image attachments (server has an image path; surface doesn't use it yet)
|
||||
- Regenerate (Souveraine conversations are forward-only by doctrine)
|
||||
- "Blank LLM mode" — a memoryless passthrough agent for throwaway questions;
|
||||
needs a server-side agent flavor first
|
||||
- Dedicated widgets for surfacing/subconscious bubbles instead of interface
|
||||
notes
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
# Lock, idle, and wallpaper reference extraction
|
||||
|
||||
This is a design extraction, not a dependency list. Souveraine should retain
|
||||
one session authority across desktop, laptop, and phone; form factor changes
|
||||
the chosen layout and cards, never the meaning of lock, idle, or auth.
|
||||
|
||||
Migration rule: ii is a temporary compatibility substrate, not a source of
|
||||
new architecture. New behavior belongs in `surfaces/quickshell` under a
|
||||
Souveraine-owned module or service. Borrowed code is replaced deliberately,
|
||||
with the replacement's contract documented before the borrowed file leaves.
|
||||
|
||||
## What the active shell has today
|
||||
|
||||
The phone lock has a credential keypad, fingerprint attempt, battery state,
|
||||
and guarded power actions. It does **not** yet have lockscreen media,
|
||||
notifications, timers, weather, calls, agent output policy, a staged idle
|
||||
coordinator, or a real sleep-inhibitor backend. `Idle.qml` currently controls
|
||||
hypridle because the tested invisible Wayland idle inhibitor is not honoured
|
||||
on the Pixel 3 compositor build.
|
||||
|
||||
That is a sound small base. It is not yet a phone-grade lock surface.
|
||||
|
||||
## Ideas to extract
|
||||
|
||||
| Source | Keep | Do not inherit |
|
||||
| --- | --- | --- |
|
||||
| Sailfish/Lipstick lockscreen | A lock screen as layered/pannable content: glance surface, credential surface, and event surface. Wake/display-off are separate from lock. | Its device-specific Lipstick and DeviceLock plumbing; source provenance must be audited before copying. |
|
||||
| Glacier Home | A compact MPRIS card with capability-gated previous/play/next controls; media is loaded only if a player exists. Clock, media, keypad, and notifications are separate components. | Its older Nemo/Amber APIs and visual code verbatim unless copyright notices travel with it. |
|
||||
| DankMaterialShell | Independent idle monitors for dim/DPMS/lock/suspend, explicit re-arming, AC-vs-battery thresholds, and a wake monitor after screen-off. | Treating its shell state as authority or blindly importing its settings model. |
|
||||
| ii/iNiR | MPRIS card mechanics, downloaded artwork, palette adaptation, and image "least busy region" placement. ii already calculates content placement against wallpaper pixels. | The current one-file wallpaper assumption and arbitrary widget dimensions. |
|
||||
| Ambxst | Screen capture at lock, short-lived credential handling, failure lockout feedback, and listening to logind Lock/PrepareForSleep events. | Its AGPL code, polling `axctl` layer, and its separate lockscreen authority. Concepts only. |
|
||||
|
||||
DankMaterialShell and iNiR are MIT; Glacier carries MIT/BSD notices. Ambxst is
|
||||
AGPL and Vast is GPL. Do not copy from the latter two into this repository
|
||||
without intentionally accepting their licence obligations. Sailfish files in
|
||||
the local research extraction need per-file provenance review before reuse.
|
||||
|
||||
## One lock surface, composable cards
|
||||
|
||||
The lock surface should be a host with slots rather than a new shell for each
|
||||
device:
|
||||
|
||||
```
|
||||
WlSessionLockSurface
|
||||
└─ LockSurfaceHost
|
||||
├─ glance slot clock · date · battery · weather
|
||||
├─ continuity slot timer · ongoing call · navigation · media transport
|
||||
├─ event slot notification summaries / previews
|
||||
├─ credential slot PIN/password/fingerprint PAM conversation
|
||||
└─ ambient-agent slot explicitly safe question/answer cards
|
||||
```
|
||||
|
||||
The host chooses a layout profile:
|
||||
|
||||
| Profile | Layout |
|
||||
| --- | --- |
|
||||
| phone portrait | One vertical glance surface; a swipe/press brings up credentials; continuity cards occupy the lower third. |
|
||||
| laptop | Center credentials with a side or lower card rail; multi-monitor cards remain local to their screen. |
|
||||
| desktop | Center credentials and a sparse, large-screen card rail; no phone gesture assumptions. |
|
||||
|
||||
Each card declares a tier and a lock-surface visibility policy. This prevents
|
||||
an attractive card from silently becoming a data leak.
|
||||
|
||||
| Tier | Suitable cards | Rule |
|
||||
| --- | --- | --- |
|
||||
| ambient | clock, battery, weather, timer, transport buttons, an ongoing-call *indicator* | no user data beyond the explicitly permitted setting |
|
||||
| personal | track title/art, notification body, calendar title, agent conversation, call identity | hidden until unlock unless the user opts that field into ambient |
|
||||
| step-up | send/reply/delete/purchase/unlock a physical thing | never performed by a card tap alone; needs a recent step-up grant |
|
||||
|
||||
An ambient agent is not a diluted personal agent. It has a separate tool and
|
||||
memory allowlist, cannot read conversation history or sensors, and its output
|
||||
is labelled ambient. If the lock starts while a personal response streams, the
|
||||
host immediately withdraws it.
|
||||
|
||||
## Idle is a transition graph, not a timer
|
||||
|
||||
The useful DMS extraction is the vocabulary and re-arm discipline. The actual
|
||||
authority remains Wayland idle protocols plus logind:
|
||||
|
||||
```
|
||||
active ─idle timeout→ dimmed ─timeout→ lock-requested
|
||||
│
|
||||
WlSessionLock.secure
|
||||
↓
|
||||
lock-secure ─timeout→ display-off
|
||||
│ │
|
||||
PrepareForSleep(true) ─────────────────────────────────────┘
|
||||
↓
|
||||
suspending → asleep → waking → active
|
||||
```
|
||||
|
||||
Input can cancel dimming and display-off before lock. It cannot cancel an
|
||||
already secure lock; credentials do that. Every transition has an event source
|
||||
and an observable result. `screenLocked` is the request; `screenLockSecure` is
|
||||
the compositor acknowledgement.
|
||||
|
||||
The implementation sequence is:
|
||||
|
||||
1. Add one `IdleCoordinator` that owns the staged state and form-factor
|
||||
thresholds. It consumes native `IdleMonitor` events when verified on the
|
||||
target; the Pixel can retain the hypridle adapter behind that interface.
|
||||
2. Add a logind event watcher for this session's `Lock` and the manager's
|
||||
`PrepareForSleep` signal.
|
||||
3. Hold a delay-mode sleep inhibitor while the shell is running. Release it
|
||||
only after the Wayland lock is secure; re-acquire it on wake. Measure the
|
||||
actual logind delay budget on the Pixel 3.
|
||||
4. Make `idle`, `sleep`, `logout`, and `user-switch` explicit inhibitor kinds.
|
||||
Only expose a kind after it owns a real backend. `idle` is the sole
|
||||
implemented kind today.
|
||||
|
||||
## Wallpaper should be an asset, not a path
|
||||
|
||||
The current background path gives every output the same image and asks crop to
|
||||
solve incompatible aspect ratios. A downloaded landscape image consequently
|
||||
loses its subject on the phone. Preserve originals, but track a portable asset
|
||||
record:
|
||||
|
||||
```
|
||||
id, originalPath, origin, licence, palette,
|
||||
focalPoint(x,y), safeRegions[],
|
||||
variants: { landscape, portrait, square },
|
||||
previewPath, generatedAt
|
||||
```
|
||||
|
||||
Selection order for a screen is:
|
||||
|
||||
1. Use an explicit per-device assignment when the user set one.
|
||||
2. Select a sufficiently close aspect-ratio variant.
|
||||
3. Crop a derived local variant around `focalPoint` while preserving a
|
||||
`safeRegion`; never overwrite the original.
|
||||
4. Fall back to a blurred/palette background with the image contained, rather
|
||||
than crop the only meaningful part away.
|
||||
|
||||
ii's existing least-busy-region analysis is useful twice: place a clock/card
|
||||
where it does not cover the subject, and propose an initial safe region.
|
||||
However, it must run against the **actual output crop** and card dimensions,
|
||||
not a fixed 300×300 desktop widget. A manual focal-point editor is the escape
|
||||
hatch; an analyser may propose, never silently decide.
|
||||
|
||||
For the lock surface, prefer a frozen screencopy or an already-derived local
|
||||
variant over downloading or decoding a remote wallpaper at lock time. Locking
|
||||
must remain instant and work offline.
|
||||
|
||||
## Proposed Souveraine map
|
||||
|
||||
```
|
||||
modules/common/functions/Session.qml lifecycle verbs + IPC projection
|
||||
services/SessionEvents.qml logind Lock / PrepareForSleep ingress
|
||||
services/IdleCoordinator.qml transition graph + target adapters
|
||||
services/LockContentPolicy.qml card tier / field visibility decisions
|
||||
modules/ii/lock/LockSurfaceHost.qml shared card host
|
||||
modules/ii/lock/cards/ Clock · Media · Notifications · Continuity · AmbientAgent
|
||||
services/StepUpAuth.qml independent PamContext + expiring grants
|
||||
services/WallpaperAssets.qml asset records, variants, safe regions
|
||||
```
|
||||
|
||||
None of these are separate session managers. They are consumers of the single
|
||||
`session` lifecycle and its state projection.
|
||||
|
||||
## Build order
|
||||
|
||||
1. **Truth before visuals:** logind event ingress, delay inhibitor, and
|
||||
`IdleCoordinator` with a visible transition log. [done — SessionEvents.qml, delay inhibitor in SessionEvents, IdleCoordinator extended with sleep states]
|
||||
2. **First lock cards:** clock/date, battery, transport-only MPRIS. Add the
|
||||
metadata privacy switch before title/artist/artwork.
|
||||
3. **Continuity:** timers, ongoing call, navigation, and notification summary
|
||||
cards with per-field redaction.
|
||||
4. **Trust:** ambient-agent allowlist, lock-time response redaction, then
|
||||
`StepUpAuth` and action-family grants. [partially done — StepUpAuth.qml built, minTier wiring and ambient-agent allowlist remain]
|
||||
5. **Wallpaper assets:** per-device assignments, focal-point editor, derived
|
||||
portrait/landscape variants, and crop-aware card placement.
|
||||
|
||||
The first useful vertical slice is therefore not a huge lockscreen redesign:
|
||||
one shared `LockSurfaceHost`, a transport-only media card, a real staged idle
|
||||
state projection, and a wallpaper asset descriptor. It proves the architecture
|
||||
on both shapes without committing personal data to the lock surface.
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
# Session and interaction trust model
|
||||
|
||||
This is the contract for the Souveraine shell on laptop and phone. It records
|
||||
the boundary between orchestration we own and the Linux/Wayland authorities we
|
||||
must consume rather than replace.
|
||||
|
||||
## Authorities and projections
|
||||
|
||||
| Question | Authority | Souveraine's role |
|
||||
| --- | --- | --- |
|
||||
| Can this machine suspend, hibernate, power off, or reboot? | logind `Can*` methods | Query and expose the answer; execute the matching logind/systemd verb. |
|
||||
| Has the compositor secured the display? | `WlSessionLock.secure` | Mirror it as `screenLockSecure`; never infer it from a button press. |
|
||||
| Does the shell want its lock surface shown? | `GlobalStates.screenLocked` | Maintain this request state and persist it across a shell crash. |
|
||||
| Has the user authenticated? | PAM and the lock's `LockContext` | Start a PAM conversation; only it may release `WlSessionLock`. |
|
||||
| Why is automatic idle sleep suppressed? | The active idle mechanism | Keep a reason/cookie registry, but report only mechanisms that are actually applied. |
|
||||
|
||||
`screenLocked` and `screenLockSecure` must stay distinct. The first closes
|
||||
ordinary shell surfaces immediately. The second is the only proof suitable for
|
||||
personal-data disclosure. A failed or delayed Wayland lock is therefore
|
||||
visible as `lockRequested: true, locked: false`, not mistaken for success.
|
||||
|
||||
## Session IPC
|
||||
|
||||
All structured IPC results are JSON strings. Quickshell IPC only marshals
|
||||
primitive QML types, so `: var` silently becomes `void` and loses its payload.
|
||||
|
||||
`session.lock()` means “the Wayland lock was requested”; it does not claim
|
||||
the compositor is secure yet. `session.state()` exposes the subsequent
|
||||
`locked` acknowledgement and the most recent asynchronous power-action result.
|
||||
Power verbs return `status: "started"` when their command has started; a later
|
||||
exit is available in `lastAction`. Refusals always log and return a reason.
|
||||
|
||||
The current public inhibitor surface implements `idle` and `sleep`. It is
|
||||
intentionally wrong to accept `shutdown`, `logout`, or `user-switch` until each
|
||||
owns a real mechanism. The eventual contract is:
|
||||
|
||||
| Kind | Backend | Status |
|
||||
| --- | --- | --- |
|
||||
| `idle` | Native Wayland idle inhibitor once verified on both targets; until then the tested hypridle control path. | Implemented. |
|
||||
| `sleep` | A live logind/systemd inhibitor FD or `systemd-inhibit` process, held for the cookie lifetime. | Implemented via SessionEvents delay-mode inhibitor. |
|
||||
| `logout`, `user-switch` | Shell policy checks at those operations, not fake logind inhibitors. | Not yet implemented. |
|
||||
|
||||
Every inhibitor must have a non-empty human reason, a cookie, and a visible
|
||||
holder in `state()`. Releasing an unknown cookie is a refusal.
|
||||
|
||||
## Lock and suspend sequencing
|
||||
|
||||
The next lifecycle change is not another cached state machine. It is a logind
|
||||
event ingress, implemented in `services/SessionEvents.qml`:
|
||||
|
||||
1. [done] Subscribe to the current logind session's `Lock` signal and request the
|
||||
Wayland lock on receipt. This makes external `loginctl lock-session` calls
|
||||
meaningful without assuming logind implements a screen locker.
|
||||
2. [done] Hold one delay-mode sleep inhibitor from shell startup.
|
||||
3. [done] On logind `PrepareForSleep(true)`, request the Wayland lock and wait for
|
||||
`WlSessionLock.secure`; release the delay inhibitor only then.
|
||||
4. [done] Reacquire it after `PrepareForSleep(false)`.
|
||||
|
||||
The delay budget is logind's `InhibitDelayMaxUSec` (commonly five seconds), so
|
||||
this must be timed on the Pixel 3 before it becomes a security claim. The
|
||||
failure policy is fail closed: if the lock is not secure before the deadline,
|
||||
leave a diagnostic event and do not pretend the session was locked.
|
||||
|
||||
## Interaction capability tiers
|
||||
|
||||
The lock screen, media controls, and agent are one policy system, not three.
|
||||
Each operation and each response is labelled with a minimum tier:
|
||||
|
||||
| Tier | Requirement | Examples |
|
||||
| --- | --- | --- |
|
||||
| `ambient` | None | clock, weather, timers, transport controls, non-personal answers |
|
||||
| `personal` | No lock is requested and the compositor is not secure (`!screenLocked && !screenLockSecure`) | messages, calendar, memories, fleet state, conversation history |
|
||||
| `stepUp` | Recent successful reauthentication | sending, pushes, deletion, payments, physical access |
|
||||
|
||||
Media metadata is `ambient` by default but must be configurable to `personal`.
|
||||
When the lock occurs, in-flight personal or step-up agent output is withheld or
|
||||
redacted; only ambient output remains visible.
|
||||
|
||||
Step-up authentication is a separate `PamContext`, using a dedicated PAM
|
||||
service such as `souveraine-stepup`. It never unlocks the session and it never
|
||||
accepts a boolean from an agent as proof. A successful result mints a
|
||||
short-lived, in-memory grant bound to the local action family. The grant must
|
||||
be cleared on lock, session end, PAM failure, and expiry. The exact freshness
|
||||
window is a deliberate policy setting, not an implementation accident.
|
||||
|
||||
Implemented in `services/StepUpAuth.qml`. The PAM service file
|
||||
(`/etc/pam.d/souveraine-stepup`) is NOT shipped by the shell — it is
|
||||
root-owned system configuration and must be installed/audited separately.
|
||||
Before enabling it, verify that one polkit agent owns the session and that
|
||||
no legacy desktop power manager is competing for idle or sleep policy.
|
||||
|
||||
The trust boundary matrix is documented in `TRUST-BOUNDARY-MATRIX.md`.
|
||||
|
||||
## Required verification
|
||||
|
||||
- `session.capabilities()` matches logind `Can*` on laptop and phone.
|
||||
- `session.lock()` reaches `locked: true` only after `WlSessionLock.secure`.
|
||||
- `loginctl lock-session` and suspend both take the same Wayland lock path.
|
||||
- Unsupported inhibitors refuse; each supported cookie visibly changes its
|
||||
mechanism and is released exactly once.
|
||||
- A lock during personal agent output hides it; a lock during an ambient timer
|
||||
leaves only the timer visible.
|
||||
- Step-up grants cannot survive lock, timeout, shell restart, or action-family
|
||||
changes. [done — StepUpAuth revokes on lock, expiry timer runs every 30s]
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
# Souveraine shell — plan (2026-07-13)
|
||||
|
||||
## What this is now
|
||||
|
||||
`qs -c souveraine` — our own quickshell config, composed by `deploy.sh`:
|
||||
our files (shell.qml, SouveraineFamily, services/, modules/settings/) are
|
||||
symlinked from this repo; untouched ii directories are borrowed as whole-dir
|
||||
symlinks; directories where we override any file are composed file-by-file.
|
||||
ii's tree stays pristine — no overlay symlinks, no `.upstream` backups. We
|
||||
step on end-4's toes nowhere; we borrow deliberately and vendor per-directory
|
||||
only when a reason forces it.
|
||||
|
||||
`SouveraineFamily.qml` is the convergence scoreboard: one family, both modes
|
||||
(desktop + phone). Panels that differ by form factor get an
|
||||
`extraCondition: Config.options.souveraine.phone` gate; a panel is
|
||||
"homogenized" when its gate is deleted. No `panelFamily` switching — the
|
||||
config loads SouveraineFamily, period.
|
||||
|
||||
Live on laptop + phone as of 2026-07-13. Hyprland flipped: laptop via the
|
||||
`qsConfig` var, phone via sed across its 4 hardcoded `qs -c ii` sites
|
||||
(backup: `hyprland.lua.bak-souveraine-config`).
|
||||
|
||||
### The Settings app
|
||||
|
||||
`souveraine/settings-phone.qml` — purpose-built mobile shell (list → push →
|
||||
back, 68px finger rows, fullscreen, no titlebar), launched by
|
||||
`settings-launch.sh` (imports compositor env from the user manager) via
|
||||
`souveraine-settings.desktop` in the phone's app grid. Page registry shape
|
||||
`[{name, icon, component}]` is identical to desktop settings.qml, so pages
|
||||
are interchangeable. Pages edit `Config.options` (JsonAdapter → config.json,
|
||||
hot-apply + persist).
|
||||
|
||||
Current pages: Device (souveraine.phone flag + future per-device overrides),
|
||||
Lock screen, Dock, Pill, Keyboard, About (ii's stock page verbatim).
|
||||
|
||||
## Doctrine — "tie back down"
|
||||
|
||||
Two tiers, nothing app-private:
|
||||
|
||||
1. **Shell settings** → `Config.options.*` → config.json. Idempotent,
|
||||
survives redeploy, diffable. Zero new persistence.
|
||||
2. **System settings** → the owning daemon, live: NetworkManager, ModemManager
|
||||
(mmcli -J), bluez, UPower, brightnessctl. The app is a *view* over system
|
||||
state — kill it and the system is still the truth. No shadow copies, no
|
||||
sync jobs. Privileged writes via polkit (`pkexec` — setuid fixed
|
||||
2026-07-13, Pixel3Arch gap #7), zero sudo.
|
||||
|
||||
## Upstream drift — the decision (2026-07-13)
|
||||
|
||||
Our overrides were forked against the **phone's vintage** of ii. The laptop
|
||||
runs a **newer** ii. Drift is large and one-directional. The question is not
|
||||
"rebase everything" — it's "which upstream additions do we actually want,
|
||||
and which are desktop features we're deliberately not running on a
|
||||
phone-first shell."
|
||||
|
||||
**Laptop-upstream additions we do NOT have:**
|
||||
- `background.widgets.{visualizer,stats,systemResources}` — desktop widgets
|
||||
(the pctrade-style widget system landed upstream). Phone doesn't use these.
|
||||
BUT: borrowed `Background.qml` reads `Config.options.background.widgets.*`
|
||||
unconditionally → TypeError spam on laptop. **Fix: backport the keys into
|
||||
our Config.qml (small surgical add), don't pull the widget machinery.**
|
||||
- `claudeUsage` — Claude Pro/Max subscription gauge in the bar. Want later.
|
||||
- `fileSearch` — indexed file search in the launcher. Want later.
|
||||
- `sidebar.width`/`widthExtended`, AiChat `fontSize` live-binding. Want.
|
||||
- `dpmsTimeout`, `unlockHook`, `autoIdleInhibit`, `termBgTone` — small knobs.
|
||||
- GlobalStates super-press timing (`superPressTime`,
|
||||
`shouldSuppressSuperReleaseSearch`) — press-and-hold-super suppresses
|
||||
search. Conflicts with our `dockRevealPulse` patch (both touch the
|
||||
super-release block). **Rebase needed eventually; not breaking anything
|
||||
today.**
|
||||
- Ai.qml: OpenAI Responses API strategy, `thoughtSignature`, FileView chat
|
||||
save. Our Ai.qml is heavily customized (no hardcoded models, Bifrost
|
||||
server, agent/resume) — **rebase here is the dangerous one; do carefully
|
||||
or not at all.**
|
||||
|
||||
**What we have that upstream DROPPED:**
|
||||
- `Config.options.dock.stacks` (fan-out stacks, "stackId|appId,appId") —
|
||||
upstream removed it. Our `DockStack.qml` arc feature is ours alone now,
|
||||
not just forked. Keep.
|
||||
|
||||
### Drift action items (priority order)
|
||||
1. **Backport `background.widgets.*` keys into our Config.qml** — kills the
|
||||
laptop TypeError spam. Small, surgical, no widget machinery pulled in.
|
||||
2. GlobalStates.qml — rebase `dockRevealPulse` onto upstream's super-press
|
||||
rewrite. Both touch the same block.
|
||||
3. Ai.qml — decide: carry our customizations forward on the old base (safe,
|
||||
current path) or rebase onto upstream's Responses-API Ai.qml (risky, gets
|
||||
us thoughtSignature + FileView). Defer until we want one of those.
|
||||
4. SidebarLeft/Right/Overview — dedupe the OSK flee-fix (15 lines triplicated)
|
||||
into one place after any rebase.
|
||||
5. Don't rebase dock/osk/TaskbarApps unless a specific breakage forces it.
|
||||
|
||||
## Pages still to build (deferred, "in time")
|
||||
|
||||
- **Display** — brightness (Brightness service), screen toggle
|
||||
(`blueline-screen-toggle`), idle/suspend timeouts. hypridle.conf is
|
||||
user-owned: regenerate whole from template, don't sed.
|
||||
- **Network** — WiFi via existing Network service; **Cellular** needs a new
|
||||
`services/Cellular.qml` wrapping `mmcli -J` (data toggle, APN, signal,
|
||||
operator, SIM PIN). The real new work.
|
||||
- **Bluetooth** — BluetoothStatus exists, but org.bluez activation fails on
|
||||
the phone (journal, every boot). Fix the service before building UI on it.
|
||||
- **App health check** — surfaced as a wanted page; not built.
|
||||
- **Dock pins/stacks editor** — list editor with rename; consumes the staged
|
||||
`renameStack`/`stackName` plumbing in TaskbarApps/DockStack.
|
||||
|
||||
## Other open items
|
||||
- Phone cold-boot verification (autostart line flipped, unproven until next boot).
|
||||
- `souveraine.phone` defaults false; Device page exposes the toggle but
|
||||
deploy.sh --phone doesn't flip it. Tying it down on phone deploy would
|
||||
satisfy idempotency doctrine.
|
||||
- Services rename to be more "souveraine-esq" — deferred.
|
||||
- Licensing sweep — see ~/Projects/MAKE_PROPER_LICENSES_FOR_ALL_PROJECTS.md.
|
||||
|
||||
## Ecosystem — the framework basics (2026-07-13)
|
||||
|
||||
Reference studied: `PostMarketOS-Blueline/references/phosh` (full source
|
||||
checkout). Phosh's shell is built on three primitives we can learn from
|
||||
without copying its C: `PhoshLayerSurface` (every visible thing subclasses
|
||||
it), the zwlr_layer_shell_v1 layer it sits on (BACKGROUND/TOP/OVERLAY), and
|
||||
`zphoc_stacked_layer_surface_v1` — phoc's extension for ordering
|
||||
layer-surfaces above/below each other *within* a layer (bare wlr can't).
|
||||
Plus a `PhoshState` bitmask (NONE / MODAL_SYSTEM_PROMPT / BLANKED / LOCKED /
|
||||
SETTINGS / OVERVIEW) that the shell reads to gate layer visibility —
|
||||
`use_top_layer = !phosh_shell_get_locked()` is the canonical line. State
|
||||
drives layer visibility, not the reverse.
|
||||
|
||||
Note: `PhoshDockedManager` is NOT "the dock" in our sense — it's *hardware*
|
||||
docking (phone → external display: disables OSK, stops auto-maximize, flips
|
||||
`is-phone`). It's the system-level analog of our `souveraine.phone` flag:
|
||||
the phone-becomes-desktop transition. They rhyme; they aren't the same
|
||||
feature.
|
||||
|
||||
### What we're actually building (three pieces, compose incrementally)
|
||||
|
||||
1. **Layer registry + ShellState** — declare each surface's layer and
|
||||
stack-order explicitly, driven by a state bitmask. GlobalStates is
|
||||
*almost* this already (it has the state bits) but doesn't own the
|
||||
layer/stack declarations. Quickshell gives us layer-shell via
|
||||
`PanelWindow` anchors + exclusive zones — same protocol as Phosh, QML
|
||||
binding not C. Steal the model, not the code.
|
||||
|
||||
2. **Live manifest per surface** — the dock (then each major surface)
|
||||
projects its current state as a queryable structure: pinned apps,
|
||||
stacks, positions, visibility, mode. This is a *service*, not UI —
|
||||
`dock.manifest` → `{pinned, stacks, hidden, mode}`. The dock already
|
||||
holds this state internally; the manifest is the projection for
|
||||
external consumers.
|
||||
|
||||
3. **Method abstraction for the agent** — the agent does NOT get a new
|
||||
toolcall integration. Souveraine is already a fully-fleshed harness
|
||||
with its own integration state; we use THAT. What lives on the dock
|
||||
side is a small method surface (`dock.pin(appId)`, `dock.restack(...)`,
|
||||
`dock.reveal()`) with **state checks baked in** — validate inputs,
|
||||
refuse mutation when the dock is in a state that forbids it (locked,
|
||||
mid-drag), return real results. The agent calls a guarded method, not
|
||||
a footgun; it genuinely can't mess up the dock through the abstraction.
|
||||
|
||||
### Where the lessons live — Souveraine School
|
||||
|
||||
The *teaching* of these methods — what they are, when to use them, the
|
||||
lessons — is NOT inline in the dock or the agent. It lives in the
|
||||
Souveraine School, taught to agent and human in proper time. The dock's
|
||||
job is to expose the abstraction; the School's job is to teach it. Two
|
||||
separate concerns; do not conflate.
|
||||
|
||||
### First artifact
|
||||
|
||||
The **dock manifest** is the smallest concrete first step: the dock is
|
||||
built, working, and already has internal state (pinned apps, stacks,
|
||||
reveal/pulse). Projecting it into a queryable manifest + guarded method
|
||||
surface proves the pattern. The layer registry generalizes it after.
|
||||
|
||||
### Reference material on disk
|
||||
- Phosh source: `~/Projects/PostMarketOS-Blueline/references/phosh` (full
|
||||
C/meson checkout, read-only reference — do not edit).
|
||||
- end4-pC fork (pctrade): `~/Projects/end4-pC` — studied for the
|
||||
panelFamilies + background-widget patterns; not copied.
|
||||
- Pure Maps (Kirigami/QML nav) — referenced in
|
||||
`Pixel3Arch/docs/car-and-dock-references.md`; not yet pulled as source.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# Trust boundary matrix
|
||||
|
||||
Every session operation classified by caller type, required auth gate, and
|
||||
current enforcement status. This is the reference for what is gated and what
|
||||
is not — if an operation is not in this matrix, it is not gated.
|
||||
|
||||
## Legend
|
||||
|
||||
| Column | Meaning |
|
||||
| --- | --- |
|
||||
| Operation | The IPC verb or internal action |
|
||||
| Caller | Who can invoke it |
|
||||
| Min tier | Minimum capability tier required |
|
||||
| Gate | What enforces the tier |
|
||||
| Status | `enforced`, `partial`, `ungated` |
|
||||
|
||||
## Operations
|
||||
|
||||
| Operation | Caller | Min tier | Gate | Status |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `session.lock()` | IPC / UI / logind signal | ambient | None (always allowed) | enforced |
|
||||
| `session.unlock()` | Credential gate only | stepUp | LockContext PIN/PAM | enforced — IPC refuses |
|
||||
| `session.suspend()` | IPC / UI | ambient | capability probe | enforced |
|
||||
| `session.hibernate()` | IPC / UI | ambient | capability probe | enforced |
|
||||
| `session.poweroff()` | IPC / UI | ambient | capability probe + polkit | enforced |
|
||||
| `session.reboot()` | IPC / UI | ambient | capability probe + polkit | enforced |
|
||||
| `session.logout()` | IPC / UI | ambient | None | enforced |
|
||||
| `session.inhibit("idle")` | IPC / internal | ambient | reason required | enforced |
|
||||
| `session.inhibit("sleep")` | IPC / internal | ambient | reason required | enforced |
|
||||
| `session.state()` | IPC / agent | ambient | None (read-only projection) | enforced |
|
||||
| `session.caps()` | IPC / agent | ambient | None (read-only projection) | enforced |
|
||||
| `StepUpAuth.requestAuth()` | UI / agent | ambient | PAM conversation | enforced |
|
||||
| `StepUpAuth.isGranted()` | UI / agent | ambient | None (read-only check) | enforced |
|
||||
| `StepUpAuth.revokeGrant()` | UI / internal | ambient | None | enforced |
|
||||
| Agent conversation | Souveraine IPC | ambient | Server auth token | enforced |
|
||||
| Agent send with personal context | Souveraine IPC | personal | !screenLocked && !screenLockSecure | enforced via LockContentPolicy |
|
||||
| Agent output on lock surface | Lock surface | ambient | LockContentPolicy.allowsOnLock() | enforced |
|
||||
| In-flight agent response on lock | Lock surface | personal | Ai.qml redacts on screenLocked | enforced |
|
||||
| Media metadata on lock surface | Lock surface | ambient (if opted) | LockContentPolicy.mediaMetadataAmbient | enforced |
|
||||
| Media transport controls | Lock surface | ambient | LockContentPolicy.mediaControlsVisible | enforced |
|
||||
| Lock screen power actions | Lock surface | ambient | Config.lock.security.requirePasswordToPower | enforced |
|
||||
| Idle state transition | IdleCoordinator | ambient | nativeEnabled config | enforced |
|
||||
| Sleep/suspend transition | SessionEvents | ambient | delay inhibitor + WlSessionLock.secure | enforced |
|
||||
| Session audit trail | SessionAudit | ambient | append-only JSONL with hash chain | enforced |
|
||||
|
||||
## Not yet gated (gaps)
|
||||
|
||||
| Operation | Caller | Required tier | Gap |
|
||||
| --- | --- | --- | --- |
|
||||
| Agent delete/push operations | Souveraine IPC | stepUp | StepUpAuth built but minTier metadata not wired |
|
||||
| Agent physical access | Souveraine IPC | stepUp | StepUpAuth built but minTier metadata not wired |
|
||||
| Break-glass override | Emergency | scoped grant | Done (StepUpAuth.breakGlass) |
|
||||
| Boot-time IPC audit | Shell startup | ambient | Done (log in Session.qml) |
|
||||
|
||||
## Notes
|
||||
|
||||
- The matrix is local-only: all callers are on the same machine, reachable
|
||||
only over quickshell's IPC socket by the user who owns the session.
|
||||
- If a network-reachable caller is ever added, every row needs re-evaluation.
|
||||
- The `personal` tier gate is `!screenLocked && !screenLockSecure` — the
|
||||
session must not be locked and the compositor must not have secured the
|
||||
lock surface. This is a live check, not a cached bool.
|
||||
- The `stepUp` tier requires a recent (TTL-window) StepUpAuth grant for the
|
||||
relevant action family.
|
||||
Loading…
Reference in a new issue