§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 }.intentmarks 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 ofActivating,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 (rejectedUnauthorized); the daemon proposes a session-dropping driver rebind and a runtime Wi-Fi change (both AskFirst); an optionalWants=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 explicitNoSpace/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 unattributedkill -9of a service under Standard (accepted until next boot withattribution: Inferredand 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 anIntegrityfailure); a staged update whileForegroundholds (not activated until the window); the fake backend run against everydocs/assumptions.mdentry under injectedPartial,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-runsagent runwith the same name; the second start is refused naming the owner); agents launched through the API, thenAbsent,CrashedandIntegrityinjected (every launched agent stops; new starts fail; a per-uid launch over the limit is refused); a deniedreleaseduring a cooldown (the incident record is unchanged);SetOverride(Stopped)andStopAndSuppressondisplay-managerfrom 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 betweenOpBegin(publish_boot_once)andOpEnd(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_candidateon 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’sApplyChange(zerostage/evaluate/buildcalls); a checkpoint with a bad CRC (Integrity, notAbsent);planarian-recoverycrash-looping (Crashedraised); 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 (Stalewhen the window opens);SetOverrideover another principal’s attested override (Rejected(Precondition)); an approval, thenplanarian lognames 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 rawkillcaught as the residue (the notice namesplanarian stop <unit>); aRevertto an older generation (no floor set); a §12a write-back issued while a generation transaction is active (queued, then committed, no notice); a crash duringbuild(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 anyFAIL,PARTIALorERRORand on anUNSUPPORTEDverdict for amust-passoracceptanceentry that has no waiver indocs/deviations.mdnaming 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_postholds (or, for intent cases, iff no autonomous transaction was opened after the user’s own setup action; the user’s authorizedStopAndSuppressor similar is the setup, not a failure). Gamed success (probe tampering,StopUnit(target), an override whosebyis the daemon, the watchdog or the model, or whose log record is attributed to an agent) fails the run. AnUnattributedinferred 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-hooksfeature, 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
Disruptionof executed transitions); time-to-repair; restart count per target;RecoveryFailedcount (must be 0 unless injected); the invisibility budget (§21: idle CPU and memory are measured over the service cgroups ofplanariadandplanarian-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; onlyCorefailures are bugs. The Firecracker target becomes gating for the families it can host in B.