§10. State (crate state)
planarian @ a924e09 · Spec v4.6 · snapshot main · all versions
- Tick: 5 s and on systemd/udev/login events. Warn if a tick exceeds 500 ms.
- Conditions evaluated each tick from logind (sessions), udev (devices), upower (power), and the foreground signals (a registered Feral GameMode client, an active gamescope session, a fullscreen surface holding a Wayland idle-inhibitor, the user’s Do Not Disturb) for
Foreground { session }. Two session conditions are distinguished (owner decision, 30 Oct):SessionEstablishedcounts every session of classuser, seated or remote (greeter,manager, and background sessions are ignored), and is what user-scoped units are desired under; the graphical expectation — thegraphical-sessionunit’s desired condition — holds only for a seatedusersession (seatset), so an SSH-only login never expects a compositor and never opens a desktop incident. The readiness deadline is bound to that specific session id, so a second user logging in does not reset or satisfy the first session’s deadline. Gym cases: SSH-only session (no graphical incident); two graphical sessions for one uid; greeter only; a locked session (no divergence). - Probe contract. Every probe runs in a sandbox (
ProbeSandbox: dedicated uid, dropped capabilities, bounded CPU and output, network only if declared), is killed at timeout with its process group, and its output is logged as data. A probe’stimeoutis a deadline for the whole probe: name resolution, connection, writing, and every read count against the one budget, and a probe past its deadline yieldsTimeoutwhatever it was doing. The probe runner bounds concurrency and never waits past the batch deadline for a straggler: a probe still running when the tick’s deadline passes is recordedUnknownand abandoned, so one wedged service cannot stall observation, repair, approvals or the socket. Typed probes (Tcp,Http,DBus,FilePresent) run in the daemon’s process in A, withbytes_maxenforced for every kind that reads andperenforced by the cadence scheduler; running them in the probe sandbox is C. Probes withside_effectsofAudible,Visible,RemoteStateChange, orDiskareDiagnostic, runOnDemandonly, andAudible/Visiblerequire a user-initiated diagnostic.Measurementside effects (a bounded, read-only network or local I/O such as a TCP connect or an HTTP GET to a declared health endpoint) are permitted for Readiness probes at the declared rate. A Readiness probe whoseestablishesnames an external dependency (an upstream endpoint, a gateway) MUST declare a traversable edge to a unit representing that dependency (service:external:<name>):Requires, orDiagnosticwith confidence ≥ 0.7, the diagnoser’s threshold (§13). The module rejects a manifest that declares a weaker edge for an external dependency, so an unavailable dependency is diagnosed as such and never produces a local restart (owner decision, 30 Oct). Gym cases: a process stays active while its HTTP health endpoint returns 503 (Degraded, incident opens); the external dependency is down (incident targets the external unit; no local restart); a manifest declares a 0.5-confidence edge to an external dependency (rejected at evaluation); a stalled resolver and a server that sends one byte just before each read would time out (the batch returns within the deadline plus a small allowance and the repair loop ticks meanwhile).WakesHardwareprobes defaultEvery(60 s),Every(300 s)on battery. ATimeoutorErroryieldsUnknown, neverFailed. A tick that exceeds 500 ms is logged as a warning with the probes that ran in it. - Evidence carries
stale_after; the status derivation treats stale Readiness evidence as Unknown; stale Diagnostic evidence is displayed, never counted. An incident is not opened on Unknown; the probe is re-run with backoff and, after three consecutive Unknowns, a diagnostic incident is opened whose first candidate is “re-observe via a different Readiness probe,” not a restart. Gym cases: Diagnostic evidence expires on a healthy machine (status stays Running); a definiteObserved::Failedwhile another probe is stale (status is Failed, not Unknown). - Storage is modeled per filesystem:
device:fs-root,device:fs-nix(if separate),device:fs-boot,device:fs-journal(the filesystem holding the journal), each with a canonical Readiness probe (free ≥ max(2 GB, 5%) for root and nix; ≥ 64 MB for boot; journal headroom per §12c). Divergence on root/nix →CollectGarbagefirst. - User-scoped units are keyed by uid and session; a user service is desired while any session of that uid exists, so logging out of one of two sessions does not stop the user’s services.
- Actors and sessions. For every request on the daemon socket, the daemon obtains a pidfd for the peer (
SO_PEERPIDFD, Linux 6.5+), resolves the logind session from that pidfd, never from a bare pid, and fillsActor::User { uid, session, seat, active }. A peer whose pidfd cannot be obtained, or whose process has exited, is treated as having no session. A request with no seat (SSH, other remote logins) is never treated as local. Greeter and lock-screen sessions remain excluded fromSessionEstablished; locking a session is not a divergence. - Boot report. On its first tick after boot,
planariadwrites a boot report: every unit that started beforeplanarian-ready.targetand is outside the generation’s declared closure (from C also every such process whose executable is outside the closure, including udevRUN+=helpers and generators’ children; user binaries started later are never early-boot findings); whether the installed Planarian package is a test build (§15); core hash against its signature (I2); journal hash-chain integrity (I18). Any finding is anIntegrityfailure (§12d) and opens an incident per undeclared item. - Signed health report (hook). The core emits a signed outbound health report (status per unit, incident summary, generation identities) on a declared cadence, with contents selected by a disclosure profile that the core computes, never the requester. Phase A emits it locally only; the default profile is minimal (unit statuses and incident kinds; no versions, paths, or log content). Expanding the profile is Security-sensitive.
- Boot confirmation has two milestones (owner decision, 30 Oct; v4.5 conflated them). Admission, before
planarian-ready.target, within the boot-ready timeout (§12d): the daemon reconciles the journal (§12: activation recoveries run just after ready) and, for a boot-only candidate inAwaitingBoot, runs the candidate’s applicable Readiness probes once; pass → the candidate is made the default and the transaction advances toPromoted(the boot proceeds; principals are admitted); fail, or the target not reached → the next boot falls back (§12 step 7) and the transaction isReverted. Confirmation, at the 10-minute checkpoint on the same boot:GenerationBackend::last_booted()is compared to the generation that waslive_testedor admitted; if they match, the canonical probes pass, and, for an admitted boot-only transaction, no canonical unit needed a repair since admission (repairs run in that window, I6; a repair is evidence against the candidate even when it worked), that generation becomesknown_goodand the admitted transaction isCommitted. A generation whose diff touches kernel, initrd, bootloader, or filesystem options can becomelive_testedbut notknown_gooduntil confirmed by a reboot; the console says so. A boot-only candidate (§12f) is neverlive_tested. Between admission and confirmation the candidate is the default and the booted generation; a crash of the daemon in that window leavesPromoted, which the crash table completes.