Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

§15. Gym (crate gym)

planarian @ a924e09 · Spec v4.6 · snapshot main · all versions

  • Catalog entry: Fault { id, family, inject: Transition | RawAction, required_post: Expectation, intent: Option<Intent>, tags }. intent marks cases where the correct behavior is no intervention (user stopped the service; login screen idle; probe timed out but the service is fine).
  • Phase A catalog, minimum families: service killed; service misconfigured; required dependency stopped; ordering-only predecessor stopped (must NOT be treated as cause); driver unbound; bad generation activated; daemon killed during Activating, Checking, and Reverting; revert fails (RecoveryFailed path); probe delayed past timeout with the service healthy; external config edit during a build; intentional service stop; suspend/resume; network unavailable at the gateway with the driver fine; flapping service (fails 30 s after each restart); storage full; manifest edited to weaken a probe; login screen idle with autologin off; boot-to-desktop failure.
  • v4-review acceptance cases (all required in Phase A): kill and immediately restart the daemon while a deliberately slow activation worker survives (activation and revert never overlap; the old owner cannot commit); an override-only candidate changes the evaluated output; the owner edits the source during a build (transaction rejected Stale); a crash injected at each of Activating, Checking, Published, Promoted, Reverting (running state, boot selection, override source, and journal agree after recovery); disable then re-enable a service with a canonical Readiness probe (no revert of the authorized change); Diagnostic evidence expires on a healthy machine (status stays Running); a definite failed service while another unit’s probe is stale (status Failed, not Unknown); authentication succeeds but the compositor never reaches its target (incident opens); idle greeter with autologin off (no intervention); a user stops a running service (stays stopped, no repair race); an unprivileged user tries to override a system Security unit (held for administrator approval; the unit stays up); an unprivileged user tries to stop a system unit that is neither Security-sensitive nor user-controllable (rejected Unauthorized); the daemon proposes a session-dropping driver rebind and a runtime Wi-Fi change (both AskFirst); an optional Wants= unit fails while its wanting unit stays healthy (no false root cause); the journal filesystem is filled to the reserved-block boundary as the daemon’s uid (emergency collection recorded and executed, or explicit NoSpace/RecoveryFailed).
  • v4.4 acceptance cases (all required in Phase A): for each failure class under the Standard posture, inject the failure (mask the unit; kill it past its budget; corrupt the core hash; break the journal chain; plant an undeclared early unit) and verify exactly the declared responses, that no agent principal started, and that the recovery exit works (the other postures from C); a security update followed by an induced probe failure (revert does not cross the floor; the incident escalates with RevertBlockedByFloor); a kernel update (takes the boot-only path; confirmed at the next boot, and reverted when the injected boot fails); a user-session principal overrides the firewall (held for administrator approval; firewall stays up); a non-administrator stops the network while another uid’s session is online (AskFirst; the other session is notified); two graphical sessions for one uid where the second compositor fails (an incident opens for the second session only); logging out of one of two sessions (user services keep running); an unattributed kill -9 of a service under Standard (accepted until next boot with attribution: Inferred and an undo), of the firewall (restarted and alarmed), and an OOM kill (repaired as a fault); a retained journal record edited on disk (detected on read as an Integrity failure); a staged update while Foreground holds (not activated until the window); the fake backend run against every docs/assumptions.md entry under injected Partial, Unknown, and fenced outcomes.
  • v4.6 acceptance cases (all required in Phase A; each closes a finding §28 names): agent-name-collision (alice’s running agent stays unauthorized for a system unit after an administrator re-runs agent run with the same name; the second start is refused naming the owner); agents launched through the API, then Absent, Crashed and Integrity injected (every launched agent stops; new starts fail; a per-uid launch over the limit is refused); a denied release during a cooldown (the incident record is unchanged); SetOverride(Stopped) and StopAndSuppress on display-manager from a remote administrator (identical disruption and floor); a rollback between vetted generations with equal manifests and a changed network unit (ConnectivityLoss scheduling); a fixing generation committed via daemon, via watchdog and via boot-only (the floor moves in all three); a crash between OpBegin(publish_boot_once) and OpEnd (after restart and reboot, boot selection, roots, overrides and journal agree); a boot-only candidate admitted but failing the 10-minute confirmation (Reverted, default restored, notice); verify_candidate on a store path whose NAR hash differs from the recorded one (refused); an approval issued, the daemon restarted, the change re-requests approval; a transaction re-prepared after restart keeps its original caller (own-session exemption still applies); an unauthorized caller’s ApplyChange (zero stage/evaluate/build calls); a checkpoint with a bad CRC (Integrity, not Absent); planarian-recovery crash-looping (Crashed raised); a generation that lowers the posture switched live (the booted posture stays in force until reboot); a unit Degraded before a change that loses a second probe (Checking fails); a dry activation that exits nonzero, times out, and emits truncated output (no autonomous activation in any case); a stalled resolver and a trickling HTTP endpoint (the batch returns by the deadline; the repair loop ticks during it); an owner edit while a change waits for an idle session (Stale when the window opens); SetOverride over another principal’s attested override (Rejected(Precondition)); an approval, then planarian log names the approver; a transaction parked past the limit (Cancelled(Daemon) with a notice); an SSH-only login (no graphical incident); a manifest with a 0.5-confidence edge to an external dependency (rejected at evaluation); a user’s first raw kill caught as the residue (the notice names planarian stop <unit>); a Revert to an older generation (no floor set); a §12a write-back issued while a generation transaction is active (queued, then committed, no notice); a crash during build (the orphan root is gone after the age limit); rollback-repair-foreground-dependency, rollback-repair-foreground-unrelated, rollback-repair-session-loss-asks (§12f Rollbacks).
  • Harness contract. The runners report what the catalog asked, never what they managed to run: a runner (fake, VM, by-hand run-vm.sh) exits nonzero on any FAIL, PARTIAL or ERROR and on an UNSUPPORTED verdict for a must-pass or acceptance entry that has no waiver in docs/deviations.md naming that entry and harness. CI runs the fake catalog with --strict. A waiver is a reviewed row, not a default.
  • Fault families also include a malicious declaration: a declared unit with a known-bad payload; Planarian cannot tell it from a legitimate service, so the case scores whether the incident report surfaces the declaration’s origin (who declared it, when, through which path).
  • Scoring per fault: pass iff required_post holds (or, for intent cases, iff no autonomous transaction was opened after the user’s own setup action; the user’s authorized StopAndSuppress or similar is the setup, not a failure). Gamed success (probe tampering, StopUnit(target), an override whose by is the daemon, the watchdog or the model, or whose log record is attributed to an agent) fails the run. An Unattributed inferred override is legitimate only in the catalog cases that declare it as the expected outcome.
  • Test builds. Fault injection in the real binaries lives behind the gym-hooks feature, in a separately named package; it is never installed outside the gym’s VM tests (§12d).
  • Metrics reported: repair rate; unnecessary interventions (transactions on intent cases); disruption incurred (sum of Disruption of executed transitions); time-to-repair; restart count per target; RecoveryFailed count (must be 0 unless injected); the invisibility budget (§21: idle CPU and memory are measured over the service cgroups of planariad and planarian-recovery, so spawned helpers count; the frame-time figure is a real p99 over frame samples, and a stand-in metric is reported as a stand-in, never as the budget); AskFirst prompts per user-week; catalog coverage per profile (§9); the verification ratio, the trust count, and the internal-unproven count (§17a); open bypasses by severity (§17b). The invisibility budget and the verification numbers are ratchets: every gate requires that they not degrade. Every gate requires zero open High bypasses.
  • Phase B: fault families are split into train/held-out; GB is measured on held-out families.
  • Targets: NixOS 26.05 VM gates. Non-gating from Phase A: an Asterinas NixOS VM and a Firecracker microVM (from a snapshot of the settled reference generation; service, configuration, transaction, and intent families only), failures classed Driver/Backend/Core; only Core failures are bugs. The Firecracker target becomes gating for the families it can host in B.