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

§8. Schema (crate schema)

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

Draft; §18 lists what must be preserved. BTreeMap below is the readable form; the implementation uses vstd-compatible structures.

#![allow(unused)]
fn main() {
pub struct Hash([u8; 32]);
pub struct GenerationId(u64);
pub struct StorePath(String);
pub struct ProbeId(String);
pub struct ChangeId(String);
pub struct IncidentId(u64);
pub struct TxnId(u64);
pub struct PolicyVersion(Hash);

pub struct UnitId { pub kind: UnitKind, pub name: String, pub scope: Scope }
pub enum UnitKind { Service, Device }
pub enum Scope { System, User { uid: u32, session: Option<SessionId> } }
pub struct SessionId(String);               // logind session id
pub struct SeatId(String);                  // logind seat; None for SSH and other remote sessions

// ---------- Desired ----------
pub enum Condition {
    Always,
    SessionEstablished { uid: Option<u32>, seated: bool },
                                              // logind reports a new session of class `user` (authentication succeeded);
                                              // greeter and background/`manager` sessions are excluded; independent of
                                              // whether the desktop then comes up. `seated: false` matches any such
                                              // session, local or remote (user-scoped units); `seated: true` matches
                                              // only sessions with a seat (the graphical expectation, §9, §10)
    DeviceConnected(UnitId),
    OnAcPower, OnBattery,
    Foreground { session: SessionId },        // quiet mode (§12f; signals in §10): a GameMode client, a gamescope session, a fullscreen
                                              // surface holding a Wayland idle-inhibitor, or the user's Do Not Disturb
    All(Vec<Condition>), Any(Vec<Condition>),
}
pub enum DesiredStatus { Running, Stopped, Bound, Absent }
pub struct Desired { pub status: DesiredStatus, pub when: Condition }

/// A deliberate, attributed change to a unit's desired state (§11). Generalizes v4.3's `Suppression`;
/// "suppression" in older prose means an `Override` with `desired: LeaveAlone`. The `StopAndSuppress`
/// transition keeps its v4.3 name: it journals `Override { desired: Stopped, attribution: Attested }`, then stops.
pub struct Override {
    pub unit: UnitId,
    pub desired: OverrideDesired,
    pub until: Until,                        // time, next boot, or explicit lift
    pub reason: String,
    pub by: Actor,                           // derived from the socket peer (§11); never from the proposal;
                                             // `Actor::Unattributed` exactly when `attribution == Inferred`
    pub attribution: Attribution,
}
pub enum OverrideDesired {
    LeaveAlone,                              // do not repair; take no action
    Stopped,                                 // stop / kill a service
    Absent,                                  // disable / mask
    Detected,                                // unmount, eject, or unbind a device
}
pub enum Attribution {
    Attested,                                // the action reached Planarian with the actor's identity before it took effect
    Inferred,                                // the unattributed residue (§11): recorded as an inference, never as fact
}
pub enum Until { At(SystemTime), NextBoot, Explicit }

// ---------- Graph ----------
pub enum EdgeKind {
    Ordering,                                // After=/Before=: never followed in diagnosis
    Wants,                                   // Wants=: weak; the wanting unit runs without it; never a root cause for it
    Requires,                                // Requires=/BindsTo=/Requisite= or manifest: functional prerequisite
    Diagnostic { confidence: f32 },          // manifest or learned; may form cycles (§13)
}
pub struct Edge { pub from: UnitId, pub to: UnitId, pub kind: EdgeKind, pub origin: EdgeOrigin }
pub enum EdgeOrigin { SystemdRequires, SystemdWants, SystemdAfter, Manifest, Learned }

// ---------- Risk metadata (static, from the evaluated generation) ----------
pub enum Sensitivity { Routine, Security, UserData, External, Core }
pub struct Sensitivities(BTreeSet<Sensitivity>);       // composable
pub enum Disruption { None, Brief, SessionLoss, DataInterruption, ConnectivityLoss }
pub struct Disruptions(BTreeSet<Disruption>);         // composable: every kind a change carries (owner decision, Nov 2; C10)
pub enum Recovery { Idempotent, Revertible, NonReversible }

pub struct UnitDef {
    pub id: UnitId,
    pub desired: Desired,
    pub edges: Vec<Edge>,
    pub sensitivity: Sensitivities,
    pub restart_disruption: Disruption,
    pub probes: Vec<ProbeDef>,
    pub synonyms: Vec<String>,
    pub systemd_restart: Option<SystemdRestartPolicy>,   // so incident mgmt can defer to it
}
pub type UnitDefs = BTreeMap<UnitId, UnitDef>;

// ---------- Health ----------
pub struct ProbeDef {
    pub id: ProbeId,
    pub kind: ProbeKind,
    pub timeout: Duration,
    pub origin: ProbeOrigin,                 // Canonical | User
    pub role: ProbeRole,                     // Readiness feeds Running/Unknown; Diagnostic never does
    pub cadence: Cadence,
    pub cost: ProbeCost,
    pub side_effects: SideEffects,           // None | Audible | Visible | Network | Disk
    pub establishes: String,                 // what a pass actually shows, in plain words
}
pub enum ProbeKind { SystemdActive, Tcp { addr: String }, Http { url: String, status: u16 }, DBus { service: String, path: String, method: String }, FilePresent { path: String },
                     Exec { argv: Vec<String>, sandbox: ProbeSandbox } }   // Exec: administrator-authored manifest entries only;
                                                                          // flagged in the console and journal (§9)
pub struct ProbeSandbox { pub uid: u32, pub caps: Vec<String>, pub max_output: usize, pub cpu_ms: u32, pub network: bool }
pub enum ProbeRole { Readiness, Diagnostic }   // Readiness permits side_effects None or Measurement only; everything else MUST be Diagnostic
pub enum Cadence { EveryTick, Every(Duration), OnEvent, OnDemand }
pub enum ProbeCost { Trivial, Cheap, WakesHardware }
pub enum SideEffects {
    None,
    Measurement { bytes_max: u32, per: Duration },   // bounded read-only I/O (a TCP connect, a GET to a health endpoint);
                                                     // permitted for Readiness probes at a declared rate
    Audible, Visible, RemoteStateChange, Disk,       // Diagnostic only, OnDemand only
}
pub enum ProbeResult { Pass, Fail(String), Timeout, Error(String) }
pub struct Evidence { pub result: ProbeResult, pub at: SystemTime, pub stale_after: Duration }

// ---------- Observed ----------
pub enum Observed {
    Unknown { since: SystemTime, reason: String },   // stale evidence, probe timeout, backend error
    Transitioning { from: Box<Observed>, since: SystemTime },
    Absent, Detected, Bound, Active, Failed(Reason),
}
pub enum Reason { ExitCode(i32), DriverError(String), Backend(String) }

/// Derived, never stored as truth on its own.
pub enum UnitStatus { Running, Degraded(Vec<ProbeId>), Stopped, Overridden(OverrideDesired), Unknown, Failed(Reason), Absent, Detected, Bound, Transitioning }

pub struct SystemState {
    pub generation: GenerationId,
    pub known_good: GenerationId,            // boot-confirmed
    pub live_tested: Option<GenerationId>,   // passed probes live, not yet rebooted
    pub security_floor: Option<GenerationId>,  // §12e; derived, never set by a proposal
    pub units: BTreeMap<UnitId, (Observed, Vec<Evidence>)>,
    pub overrides: Vec<Override>,
    pub conditions: BTreeSet<Condition>,     // which conditions currently hold
    pub resources: Resources,                // excluded from digest
    pub core_hash: Hash,
    pub observed_at: SystemTime,             // excluded from digest
}
impl SystemState { pub fn digest(&self) -> Hash; pub fn status(&self, id: &UnitId, defs: &UnitDefs) -> UnitStatus; }

// ---------- Transitions ----------
pub enum Transition {
    RestartUnit(UnitId),
    StopUnit(UnitId),
    BindDriver { device: UnitId, driver: StorePath },
    ApplyChange { change: ChangeId, args: BTreeMap<String, Value> },   // cataloged (§9); executor computes the diff
    SwitchGeneration(GenerationId),
    Revert { to: GenerationId },
    Rollback,                                // to known_good
    LoadModel(ModelId),                      // B
    RestoreSnapshot(SnapshotId),             // C
    CollectGarbage { emergency: bool },
    SetRuntimeOption { path: String, value: Value },
    SetOverride(Override),                   // journals the override; for LeaveAlone, does nothing else
    LiftOverride(UnitId),
    StopAndSuppress { unit: UnitId, until: Until, reason: String },   // the user's "stop X": Override { desired: Stopped } journaled, then stop
}

/// Per-transition recovery strategy, fixed by the spec, not the proposer (§12).
pub enum RecoveryStrategy {
    ReobserveOnly,             // runtime unit actions: never a generation operation
    RevertGeneration,          // generation operations: fixed `revert_to`, full journal path
    UnbindDevice,              // BindDriver: on failure, unbind and rebind the previously bound driver
    RestoreSnapshotThenRevert,
}
/// Every transition has exactly one handler and one recovery strategy, fixed by the spec and
/// independent of its tier (§12). The crash-recovery table branches on `recovery`, never on tier.
pub enum Handler { UnitOp, GenerationOp, DeviceOp, SnapshotOp, GcOp, IntentOp }

pub enum Tier { Observe, Runtime, Tested, AskFirst }
/// Who may approve an AskFirst decision. The class is the maximum over every floor and table row that
/// raised the decision; it is ignored below AskFirst. `Administrator` requires the approver to hold
/// `Capability::Administrator` as well as `Capability::Approve`.
pub enum ApproverClass { AnyApprover, Administrator }
pub enum Decision { Allow { tier: Tier, approver: ApproverClass }, Reject(RejectReason) }
pub enum RejectReason { Forbidden, CoreTarget, ProbeTamper, NotInCatalog, LockedOption, Stale, Unauthorized, Interrupted, NoSpace, Precondition, ActivationUnknown, Other(String) }
// ActivationUnknown: the dry activation failed, timed out or was truncated, so the change's consequences cannot be derived (§9 step 3)

/// Derived by the executor from the evaluated generation — never from the proposal (I14).
pub struct DerivedFacts {
    pub affected_units: BTreeSet<UnitId>,
    pub sensitivity: Sensitivities,
    pub disruption: Disruptions,
    pub recovery: Recovery,
    pub touches_manifest: bool,
    pub touches_canonical_probe: bool,
    pub touches_core: bool,
    pub below_security_floor: bool,         // target generation (revert, rollback, switch) is below `security_floor`
    pub other_sessions_affected: bool,       // a system-scope effect reaches another uid's online or active session (§11)
    pub restarts_failed_unit: bool,          // a RestartUnit whose unit has already failed when the facts are derived (§11; owner decision, Nov 2)
    pub removes_sensitivity: Sensitivities,  // a generation transition: the sensitivity of the units it would remove or stop (§11 Rollback row; owner decision, Nov 2)
    pub removes: BTreeSet<UnitId>,           // those units, as an approval shows them (§16)
    pub restarts_held_unit: bool,            // a RestartUnit of a network-security unit (§12d) that has failed, whose traffic is held (§11; owner decision, Nov 2)
    pub caller: Actor,
    pub config_diff: Option<ConfigDiff>,
}

pub trait TransitionSpec {
    fn precondition(&self, s: &SystemState, defs: &UnitDefs) -> bool;
    fn predict(&self, s: &SystemState, defs: &UnitDefs) -> Expectation;
    fn derive(&self, s: &SystemState, defs: &UnitDefs, gen: &EvaluatedGeneration, caller: Actor) -> DerivedFacts;
}

pub struct Expectation {
    pub units: BTreeMap<UnitId, UnitStatus>,
    pub generation: Option<GenerationId>,
    pub may_change: BTreeSet<UnitId>,
    pub others_unchanged: bool,
    pub settle: Settle,
}
pub enum Settle { Duration(Duration), Ready { unit: UnitId, timeout: Duration } }

pub struct Proposal {
    pub observed: Hash,
    pub transition: Transition,
    pub expected: Expectation,               // MUST refine spec.predict(); checked in §12 step 3
    pub reason: String,
    pub source: Source,
}
pub enum Source { Diagnoser { incident: IncidentId }, Model(ModelId), User, Intent { phrase: String, confidence: f32 } }
pub enum Actor {
    Daemon, Watchdog,
    User { uid: u32, session: Option<SessionId>, seat: Option<SeatId>, active: bool },  // filled from SO_PEERCRED → pid → logind
    Model(ModelId),
    Agent(PrincipalId),                      // an agent peer the journal has no live `AgentStarted` for (holds nothing), and
                                             // from later phases remote principals via a relay (future-work §3). A *started*
                                             // agent is authorized as `Actor::User { uid: by, session: None, seat: None, .. }`
                                             // (§11); its agent name and instance travel in `LogRecord.agent`, not in `Actor`,
                                             // so the proved `Actor` shape is unchanged
    Unattributed,                            // the actor of an `Inferred` override (§11); never a caller, never authorized
}
// `session` is None only for callers outside any logind session (e.g. a root timer); such callers never
// satisfy an "own session" floor.

// ---------- Principals and authorization (§11) ----------
pub struct PrincipalId(pub u64);             // stable; a human principal maps to a uid, an agent to a registered key
pub enum Capability {
    OwnScope,                                // act on units in one's own Scope::User
    UserControllable,                        // act on units in services.planarian.userControllable
    Administrator,
    Approve,                                 // approve others' AskFirst proposals within one's other capabilities (I19)
    Catalog, ModelCatalog,                   // propose catalog / model-catalog entries (the only non-human grants in B)
    Manifest, Merge,                         // human-only through B; Merge needs two distinct approvers
}
pub struct Grant { pub to: PrincipalId, pub capability: Capability, pub approved_by: PrincipalId, pub txn: TxnId }
/// Computed by the daemon for each call from SO_PEERCRED, logind, group membership, and journaled grants.
/// Never read from a proposal (I14).
pub struct Authorization { pub principal: PrincipalId, pub actor: Actor, pub capabilities: BTreeSet<Capability> }

/// Journaled by `planarian agent run` (§11, §16) once the agent's unit has started. `instance` is the
/// unit's InvocationID (or an equivalent per-start identity the backend can read back from the peer's
/// cgroup); a request from an agent peer is authorized as `by` only if the peer's instance equals this
/// record's and no later record names the same instance (I24). The name alone never identifies an agent.
pub struct AgentStarted { pub name: String, pub instance: InstanceId, pub by: u32 /* uid */, pub at: SystemTime }
pub struct InstanceId([u8; 16]);

// ---------- Failure posture (§12d) ----------
pub enum Posture { Relaxed, Standard, Guarded, Sealed }
pub enum FailureClass { Absent, Crashed, Integrity }

// ---------- Incidents ----------
pub enum IncidentState { Open, Cooldown { until: SystemTime }, Escalated(EscalationReason), Closed, Regressed { count: u8 } }
pub enum EscalationReason { Regressed, BudgetExhausted, DiagnoserExhausted, RevertBlockedByFloor, RecoveryFailed }
pub struct Incident {
    pub id: IncidentId,
    pub target: UnitId,
    pub divergence: (DesiredStatus, UnitStatus),
    pub opened: SystemTime,
    pub attempts: Vec<(TxnId, Outcome)>,
    pub budget_left: u8,                     // persisted; survives daemon restart
    pub state: IncidentState,
    pub backoff: Duration,
}

// ---------- Transactions ----------
pub enum TxnState {
    Prepared, Staged, Built, Activating, Checking,
    Publishing,                              // boot-only path (§12 step 7): journaled before the first boot mutation
                                             // (`publish_boot_once`); recovery from here reads the op's OpBegin/OpEnd
    Published,                               // boot entry written
    AwaitingBoot,                            // boot-only candidate published as a one-shot next-boot entry (§12f); active for I6
    Promoted,                                // live path: override source promoted; boot-only path: candidate admitted as the
                                             // boot default at the boot it was published for, confirmation pending (§10)
    Committed, Reverting, Reverted, RecoveryFailed,
    Rejected(RejectReason),                  // terminal: refused after Prepared (precondition, expired/invalidated approval, policy)
    Cancelled(Actor),                        // terminal: withdrawn before any mutation
}
impl TxnState {
    /// The one list I6 and §12 "Concurrency" both mean: no transition executes while any transaction is in one of these.
    pub fn exclusive(&self) -> bool { matches!(self, Activating | Checking | Publishing | Published | Promoted | Reverting) }
}
/// Exclusive ownership of a transaction (§12). Stored in the journal; every
/// mutating backend call carries the epoch and is refused if it is not current.
pub struct Lease { pub owner: OwnerId, pub epoch: u64, pub worker_pid: Option<u32>, pub renewed: SystemTime, pub jobs: Vec<JobId> }   // jobs: supervisor jobs this owner submitted
pub enum OwnerId { Daemon { pid: u32, boot_id: Hash }, Watchdog { pid: u32, boot_id: Hash } }

pub struct Transaction {
    pub id: TxnId,
    pub state: TxnState,
    pub lease: Lease,
    pub base_source: Option<Hash>,           // NAR hash of the owner's Nix-visible source with the BASE override, at Prepared (§9)
    pub staged_source: Option<Hash>,         // NAR hash of the candidate tree actually evaluated and built (§9)
    pub base_override: Option<Hash>,         // content hash of the override file as it was at Prepared (§9)
    pub candidate_override: Option<Hash>,    // content hash of the staged candidate override
    pub gc_root: Option<PathBuf>,            // /nix/var/nix/gcroots/planarian/<txn>, created at Built (I3)
    pub recovery: RecoveryStrategy,
    pub transition: Transition,
    pub facts: DerivedFacts,
    pub base: GenerationId,
    pub candidate: Option<(GenerationId, StorePath)>,
    pub policy: PolicyVersion,
    pub approval: Option<ApprovalToken>,
    pub snapshot: Option<SnapshotId>,
    pub expected: Expectation,
    pub started: SystemTime,
    pub deadline: SystemTime,                // watchdog acts after this
    pub revert_to: GenerationId,             // fixed at Prepared; authorized with the txn
}
pub enum Outcome { Succeeded, Failed(String), Partial(String), Unknown(String) }

pub struct ApprovalToken {
    pub bound_to: Hash,      // hash(transition, staged_source, candidate store path, base generation, policy version,
                             //      cumulative-diff hash, derived facts, expected, revert_to, recovery strategy)
    pub approver: PrincipalId,               // must differ from the proposer and hold the decision's ApproverClass (I19)
    pub class: ApproverClass,
    pub issued: SystemTime, pub expires: SystemTime, pub nonce: [u8; 16], pub used: bool,
}

pub struct Session { pub applied: Vec<TxnId>, pub cumulative: ConfigDiff, pub policy: PolicyVersion }

// ---------- Backends (generic, one impl each in A) ----------
pub trait UnitBackend {
    fn observe(&self, id: &UnitId) -> Result<Observed, Outcome>;
    fn restart(&self, id: &UnitId) -> Outcome;
    fn stop(&self, id: &UnitId) -> Outcome;
    fn list(&self) -> Result<Vec<UnitId>, Outcome>;
}
pub trait GenerationBackend {
    // read-only (allowed before Prepared)
    fn current(&self) -> Result<GenerationId, Outcome>;
    fn boot_entries(&self) -> Result<Vec<GenerationId>, Outcome>;
    fn last_booted(&self) -> Result<GenerationId, Outcome>;
    fn stage(&self, owner_cfg: &PinnedSource, overrides: &OverrideSet) -> Result<StagedSource, Outcome>;  // immutable copy; returns its NAR hash
    fn evaluate(&self, src: &StagedSource) -> Result<EvaluatedGeneration, Outcome>;   // pure-eval; option set, UnitDefs, activation plan
    fn build(&self, eval: &EvaluatedGeneration) -> Result<(GenerationId, StorePath, PathBuf /* build root */), Outcome>;   // store-only; roots its output under gcroots/planarian/builds/
    // mutating (require durable Prepared and a current lease epoch)
    fn activate_live(&self, g: GenerationId, lease: &Lease) -> Outcome;              // switch-to-configuration test
    fn publish_boot(&self, g: GenerationId, lease: &Lease) -> Outcome;               // boot entry only; no re-activation
    fn protect(&self, g: GenerationId, lease: &Lease) -> Outcome;
    fn pending_jobs(&self) -> Result<Vec<JobId>, Outcome>;                           // supervisor jobs still running
    fn jobs_tagged(&self, op: &OpId) -> Result<Option<Vec<JobId>>, Outcome>;         // discover jobs by durable operation id; None = backend cannot say
    fn cancel(&self, job: &JobId, lease: &Lease) -> Outcome;
    fn verify_candidate(&self, path: &StorePath, expected: &Hash) -> Result<bool, Outcome>;
}
pub trait SnapshotBackend { fn supported(&self) -> bool; fn snapshot(&self, label: &str) -> Result<SnapshotId, Outcome>; fn restore(&self, id: &SnapshotId) -> Outcome; fn usage_bytes(&self) -> u64; }
pub trait InventoryBackend {
    fn devices(&self) -> Result<Vec<DeviceInfo>, Outcome>;
    fn resources(&self) -> Result<Resources, Outcome>;
    fn bound_driver(&self, device: &UnitId) -> Result<Option<StorePath>, Outcome>;
    fn bind(&self, device: &UnitId, driver: &StorePath, lease: &Lease) -> Outcome;     // mutating; sysfs driver bind
    fn unbind(&self, device: &UnitId, lease: &Lease) -> Outcome;                        // mutating
}
}

Status derivation (SystemState::status), in order: Overridden if an override holds (reported with its OverrideDesired); else if the desired condition does not hold, not expected (observed status reported, no incident); else Failed if Observed::Failed (a definite supervisor failure is never masked by stale probes); else Unknown if any Readiness probe’s evidence is stale or timed out; else Running if observed Active and all Readiness probes passed fresh; Degraded if Active with a failed Readiness probe; else from Observed. Diagnostic probes never affect status; their evidence is shown in the console and used by the diagnoser only.

systemd mapping: active → Active; failed → Failed(ExitCode); inactive/dead → Bound (Stopped once desired is considered); activating/deactivating/reloading → Transitioning; not found → Absent; D-Bus error → Unknown. Devices via udev: present without driver → Detected; bound → Bound/Active.