The current user unit wins the boot race but still runs as the human whose Personal key the lock is meant to evict. Record the target souveraine-session principal, its narrow Wayland/PAM/data reach, and the fact that the rich shell is presentation—not release authority.
105 lines
6.1 KiB
Markdown
105 lines
6.1 KiB
Markdown
# 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 |
|
|
| --- | --- | --- |
|
|
| Who owns the secure pre-unlock surface? | Dedicated `souveraine-session` principal + compositor lock protocol | Sessiond decides and audits; the shell renders; Casey authenticates but does not own the lock process. Target design, not current user-unit state. |
|
|
| 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]
|