ASELSANMicrokernel
S562 · SOURCE-BOUND GATE EVIDENCE

S562 · R1 uygulama: servis öldürme ve yeniden başlatma gözetim modeli

tam S562 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S562 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.

S562Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s562-r1-service-kill-restart-supervision-model

uygulama/model · focused test · Operations · 3 exact excerpt

sequence-bound=true · implementation-bound=true
01 · Yürütme / doğrulama kodu

Kapının gerçek repository sözleşmesi

tam dosyaL1–L807
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model.rs::S562 r1 service kill restart supervision model implementation
//! S562 models the service kill/restart supervision policy that an R1
//! application/recovery stage would apply to system services.
//!
//! The model is a pure, table-driven, fail-closed state machine: a fixed
//! service table (identity, Critical/Normal kind, Always/OnFailure/Never
//! restart policy, dependencies), kill requests carrying a reason (Operator,
//! Watchdog, Fault, Oom), a graceful-shutdown grace period measured in ticks
//! followed by a forced kill, exponential restart backoff (1, 2, 4, 8 ticks,
//! capped, reset after 60 stable ticks), restart-storm detection (more than
//! five restarts inside 60 ticks quarantines the service), dependency
//! ordering (dependents stop with their dependency and may only restart after
//! it is Running again) and receipts numbered by a monotonic sequence.
//!
//! S562 claims nothing beyond the host model.  No supervisor task, scheduler
//! hook, watchdog, IPC endpoint, panel, modem, board or UART observation exists
//! for it; the module is not wired into any boot, IRQ, scheduler or driver
//! path and performs no device operation.  S540 and S543 remain immutable
//! physical RED observations and are not promoted here.  The tick clock is
//! supplied by the caller (the focused test) and never read from hardware.
//!
//! Predecessor: S561 (permissioned application launch flow model).
//! Next gate: S563 (bounded recovery and fault containment model).

pub const S562_SEQUENCE: usize = 562;
pub const S562_EXPECTED_PREDECESSOR: usize = 561;
pub const S562_R1_STAGE: u8 = 4;
pub const S562_R1_RANGE_FIRST: usize = 536;
pub const S562_R1_RANGE_LAST: usize = 568;
pub const S562_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S562_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S562_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S562_SD_WRITES: usize = 0;
pub const S562_UART_OPENS: usize = 0;
pub const S562_POWER_TRANSITIONS: usize = 0;
pub const S562_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S562_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S562_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S562_AUTOMATIC_PROMOTION: bool = false;
pub const S562_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S562_HARDWARE_PRESENT: bool = false;
pub const S562_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S562: bool = false;

/// One model tick is one second of supervisor time; no hardware clock exists.
pub const S562_TICK_MILLISECONDS: u64 = 1000;
pub const S562_SERVICE_TABLE_LEN: usize = 6;
pub const S562_MAX_DEPENDENCIES: usize = 2;
pub const S562_GRACEFUL_SHUTDOWN_TIMEOUT_TICKS: u64 = 5;
pub const S562_RESTART_BACKOFF_SCHEDULE_TICKS: [u64; 4] = [1, 2, 4, 8];
pub const S562_RESTART_BACKOFF_CAP_TICKS: u64 = 8;
pub const S562_STABLE_RESET_TICKS: u64 = 60;
pub const S562_RESTART_STORM_WINDOW_TICKS: u64 = 60;
pub const S562_RESTART_STORM_THRESHOLD: u8 = 5;
pub const S562_RESTART_HISTORY_CAPACITY: usize = 8;
pub const S562_RECEIPT_LEDGER_CAPACITY: usize = 64;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562ServiceKind {
    Critical,
    Normal,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562RestartPolicy {
    Always,
    OnFailure,
    Never,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562KillReason {
    Operator,
    Watchdog,
    Fault,
    Oom,
}

impl G8lS562KillReason {
    /// Operator and watchdog kills allow a graceful shutdown window; a
    /// faulted or OOM-reclaimed service is already gone and stops at once.
    pub const fn is_graceful(self) -> bool {
        matches!(self, Self::Operator | Self::Watchdog)
    }

    pub const fn is_failure(self) -> bool {
        !matches!(self, Self::Operator)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562ServiceStatus {
    Running,
    Stopping,
    Stopped,
    WaitingDependency,
    Quarantined,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS562ServiceDescriptor {
    pub id: u8,
    pub name: &'static str,
    pub kind: G8lS562ServiceKind,
    pub policy: G8lS562RestartPolicy,
    /// Dependency identifiers; `0` marks an unused slot.
    pub depends_on: [u8; S562_MAX_DEPENDENCIES],
}

pub const S562_SERVICE_TABLE: [G8lS562ServiceDescriptor; S562_SERVICE_TABLE_LEN] = [
    G8lS562ServiceDescriptor {
        id: 1,
        name: "ipc_router",
        kind: G8lS562ServiceKind::Critical,
        policy: G8lS562RestartPolicy::Always,
        depends_on: [0, 0],
    },
    G8lS562ServiceDescriptor {
        id: 2,
        name: "display_compositor",
        kind: G8lS562ServiceKind::Critical,
        policy: G8lS562RestartPolicy::Always,
        depends_on: [1, 0],
    },
    G8lS562ServiceDescriptor {
        id: 3,
        name: "modem_daemon",
        kind: G8lS562ServiceKind::Critical,
        policy: G8lS562RestartPolicy::OnFailure,
        depends_on: [1, 0],
    },
    G8lS562ServiceDescriptor {
        id: 4,
        name: "telephony_ui",
        kind: G8lS562ServiceKind::Normal,
        policy: G8lS562RestartPolicy::OnFailure,
        depends_on: [2, 3],
    },
    G8lS562ServiceDescriptor {
        id: 5,
        name: "launcher",
        kind: G8lS562ServiceKind::Normal,
        policy: G8lS562RestartPolicy::Always,
        depends_on: [2, 0],
    },
    G8lS562ServiceDescriptor {
        id: 6,
        name: "diagnostics_probe",
        kind: G8lS562ServiceKind::Normal,
        policy: G8lS562RestartPolicy::Never,
        depends_on: [0, 0],
    },
];

pub fn s562_lookup_service(service_id: u8) -> Option<&'static G8lS562ServiceDescriptor> {
    S562_SERVICE_TABLE
        .iter()
        .find(|descriptor| descriptor.id == service_id)
}

pub const fn s562_backoff_ticks(backoff_index: u8) -> u64 {
    let index = backoff_index as usize;
    if index >= S562_RESTART_BACKOFF_SCHEDULE_TICKS.len() {
        S562_RESTART_BACKOFF_CAP_TICKS
    } else {
        S562_RESTART_BACKOFF_SCHEDULE_TICKS[index]
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562SupervisionEvent {
    KillRequested {
        service_id: u8,
        reason: G8lS562KillReason,
        now_ticks: u64,
    },
    ShutdownAcknowledged {
        service_id: u8,
        now_ticks: u64,
    },
    GraceTick {
        service_id: u8,
        now_ticks: u64,
    },
    RestartDue {
        service_id: u8,
        now_ticks: u64,
    },
    OperatorRelease {
        service_id: u8,
        now_ticks: u64,
    },
}

impl G8lS562SupervisionEvent {
    pub const fn service_id(self) -> u8 {
        match self {
            Self::KillRequested { service_id, .. }
            | Self::ShutdownAcknowledged { service_id, .. }
            | Self::GraceTick { service_id, .. }
            | Self::RestartDue { service_id, .. }
            | Self::OperatorRelease { service_id, .. } => service_id,
        }
    }

    pub const fn now_ticks(self) -> u64 {
        match self {
            Self::KillRequested { now_ticks, .. }
            | Self::ShutdownAcknowledged { now_ticks, .. }
            | Self::GraceTick { now_ticks, .. }
            | Self::RestartDue { now_ticks, .. }
            | Self::OperatorRelease { now_ticks, .. } => now_ticks,
        }
    }

    pub const fn kind(self) -> G8lS562EventKind {
        match self {
            Self::KillRequested { .. } => G8lS562EventKind::KillRequested,
            Self::ShutdownAcknowledged { .. } => G8lS562EventKind::ShutdownAcknowledged,
            Self::GraceTick { .. } => G8lS562EventKind::GraceTick,
            Self::RestartDue { .. } => G8lS562EventKind::RestartDue,
            Self::OperatorRelease { .. } => G8lS562EventKind::OperatorRelease,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562EventKind {
    KillRequested,
    ShutdownAcknowledged,
    GraceTick,
    RestartDue,
    OperatorRelease,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS562SupervisionReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub receipt_no: u64,
    pub event: G8lS562EventKind,
    pub service_id: u8,
    pub kind: G8lS562ServiceKind,
    pub policy: G8lS562RestartPolicy,
    pub reason: Option<G8lS562KillReason>,
    pub now_ticks: u64,
    pub from: G8lS562ServiceStatus,
    pub to: G8lS562ServiceStatus,
    pub forced: bool,
    pub grace_deadline_ticks: u64,
    pub backoff_ticks: u64,
    pub restart_due_ticks: u64,
    pub restart_scheduled: bool,
    pub restarts_in_window: u8,
    pub cascaded_dependents: u8,
    pub system_degraded: bool,
    pub hardware_present: bool,
    pub physical_observations: usize,
    pub runbook_executed: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct G8lS562ServiceRow {
    status: G8lS562ServiceStatus,
    started_at_ticks: u64,
    backoff_index: u8,
    grace_deadline_ticks: u64,
    restart_due_ticks: u64,
    restart_scheduled: bool,
    last_reason: Option<G8lS562KillReason>,
    history: [u64; S562_RESTART_HISTORY_CAPACITY],
    history_len: u8,
    history_head: u8,
}

impl G8lS562ServiceRow {
    const fn running() -> Self {
        Self {
            status: G8lS562ServiceStatus::Running,
            started_at_ticks: 0,
            backoff_index: 0,
            grace_deadline_ticks: 0,
            restart_due_ticks: 0,
            restart_scheduled: false,
            last_reason: None,
            history: [0; S562_RESTART_HISTORY_CAPACITY],
            history_len: 0,
            history_head: 0,
        }
    }

    fn record_restart(&mut self, now_ticks: u64) -> u8 {
        self.history[self.history_head as usize] = now_ticks;
        self.history_head =
            ((self.history_head as usize + 1) % S562_RESTART_HISTORY_CAPACITY) as u8;
        if (self.history_len as usize) < S562_RESTART_HISTORY_CAPACITY {
            self.history_len += 1;
        }
        let mut count = 0u8;
        for slot in 0..self.history_len as usize {
            if now_ticks.saturating_sub(self.history[slot]) < S562_RESTART_STORM_WINDOW_TICKS {
                count += 1;
            }
        }
        count
    }
}

#[derive(Debug)]
pub struct G8lS562SupervisionState {
    rows: [G8lS562ServiceRow; S562_SERVICE_TABLE_LEN],
    last_now_ticks: u64,
    next_receipt_no: u64,
    system_degraded: bool,
    last_event: Option<G8lS562SupervisionEvent>,
    last_receipt: Option<G8lS562SupervisionReceipt>,
    ledger: [Option<G8lS562SupervisionReceipt>; S562_RECEIPT_LEDGER_CAPACITY],
    ledger_len: usize,
}

impl G8lS562SupervisionState {
    pub const fn new() -> Self {
        Self {
            rows: [G8lS562ServiceRow::running(); S562_SERVICE_TABLE_LEN],
            last_now_ticks: 0,
            next_receipt_no: 1,
            system_degraded: false,
            last_event: None,
            last_receipt: None,
            ledger: [None; S562_RECEIPT_LEDGER_CAPACITY],
            ledger_len: 0,
        }
    }

    pub fn status_of(&self, service_id: u8) -> Option<G8lS562ServiceStatus> {
        Self::index_of(service_id).map(|index| self.rows[index].status)
    }

    pub fn restart_due_of(&self, service_id: u8) -> Option<u64> {
        Self::index_of(service_id).map(|index| self.rows[index].restart_due_ticks)
    }

    pub const fn system_degraded(&self) -> bool {
        self.system_degraded
    }

    pub const fn last_receipt(&self) -> Option<G8lS562SupervisionReceipt> {
        self.last_receipt
    }

    pub const fn ledger_len(&self) -> usize {
        self.ledger_len
    }

    pub fn ledger_entry(&self, position: usize) -> Option<G8lS562SupervisionReceipt> {
        if position < self.ledger_len {
            self.ledger[position]
        } else {
            None
        }
    }

    fn index_of(service_id: u8) -> Option<usize> {
        S562_SERVICE_TABLE
            .iter()
            .position(|descriptor| descriptor.id == service_id)
    }
}

impl Default for G8lS562SupervisionState {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562SupervisionOutcome {
    GracefulStopStarted(G8lS562SupervisionReceipt),
    KilledImmediately(G8lS562SupervisionReceipt),
    GracefulExit(G8lS562SupervisionReceipt),
    GracePending(G8lS562SupervisionReceipt),
    ForcedKill(G8lS562SupervisionReceipt),
    Restarted(G8lS562SupervisionReceipt),
    Quarantined(G8lS562SupervisionReceipt),
    Released(G8lS562SupervisionReceipt),
    Retained(G8lS562SupervisionReceipt),
}

impl G8lS562SupervisionOutcome {
    pub const fn receipt(self) -> G8lS562SupervisionReceipt {
        match self {
            Self::GracefulStopStarted(receipt)
            | Self::KilledImmediately(receipt)
            | Self::GracefulExit(receipt)
            | Self::GracePending(receipt)
            | Self::ForcedKill(receipt)
            | Self::Restarted(receipt)
            | Self::Quarantined(receipt)
            | Self::Released(receipt)
            | Self::Retained(receipt) => receipt,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS562SupervisionError {
    UnknownService,
    ClockRegression,
    ServiceNotRunning,
    KillAlreadyPending,
    NoShutdownPending,
    GracePeriodExpired,
    ServiceNotStopped,
    BackoffNotElapsed,
    NeverPolicyRestart,
    OperatorStopNotRestartable,
    QuarantinedRestart,
    DependencyNotRunning,
    NotQuarantined,
    TickOverflow,
    ReceiptLedgerExhausted,
}

impl G8lS562SupervisionError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::UnknownService => 1,
            Self::ClockRegression => 2,
            Self::ServiceNotRunning => 3,
            Self::KillAlreadyPending => 4,
            Self::NoShutdownPending => 5,
            Self::GracePeriodExpired => 6,
            Self::ServiceNotStopped => 7,
            Self::BackoffNotElapsed => 8,
            Self::NeverPolicyRestart => 9,
            Self::OperatorStopNotRestartable => 10,
            Self::QuarantinedRestart => 11,
            Self::DependencyNotRunning => 12,
            Self::NotQuarantined => 13,
            Self::TickOverflow => 14,
            Self::ReceiptLedgerExhausted => 15,
        }
    }
}

/// Whether the tabled policy allows a restart after a kill for `reason`.
pub const fn s562_restart_allowed(
    policy: G8lS562RestartPolicy,
    reason: G8lS562KillReason,
) -> Result<(), G8lS562SupervisionError> {
    match policy {
        G8lS562RestartPolicy::Always => Ok(()),
        G8lS562RestartPolicy::OnFailure => {
            if reason.is_failure() {
                Ok(())
            } else {
                Err(G8lS562SupervisionError::OperatorStopNotRestartable)
            }
        }
        G8lS562RestartPolicy::Never => Err(G8lS562SupervisionError::NeverPolicyRestart),
    }
}

struct G8lS562Transition {
    from: G8lS562ServiceStatus,
    to: G8lS562ServiceStatus,
    forced: bool,
    grace_deadline_ticks: u64,
    backoff_ticks: u64,
    restart_due_ticks: u64,
    restart_scheduled: bool,
    restarts_in_window: u8,
    cascaded_dependents: u8,
    reason: Option<G8lS562KillReason>,
}

/// Moves a stopped service into `Stopped`, computes its backoff and restart
/// eligibility, and returns the backoff and restart-due ticks.
fn schedule_restart(
    row: &mut G8lS562ServiceRow,
    policy: G8lS562RestartPolicy,
    reason: G8lS562KillReason,
    kill_ticks: u64,
    now_ticks: u64,
) -> Result<(u64, u64, bool), G8lS562SupervisionError> {
    if kill_ticks.saturating_sub(row.started_at_ticks) >= S562_STABLE_RESET_TICKS {
        row.backoff_index = 0;
    }
    let backoff_ticks = s562_backoff_ticks(row.backoff_index);
    let restart_due_ticks = now_ticks
        .checked_add(backoff_ticks)
        .ok_or(G8lS562SupervisionError::TickOverflow)?;
    let restart_scheduled = s562_restart_allowed(policy, reason).is_ok();
    row.backoff_index = row.backoff_index.saturating_add(1).min(3);
    row.status = G8lS562ServiceStatus::Stopped;
    row.restart_due_ticks = restart_due_ticks;
    row.restart_scheduled = restart_scheduled;
    Ok((backoff_ticks, restart_due_ticks, restart_scheduled))
}

/// Transitively stops every dependent of `root` that is Running or Stopping.
fn cascade_dependents(
    rows: &mut [G8lS562ServiceRow; S562_SERVICE_TABLE_LEN],
    root: u8,
    reason: G8lS562KillReason,
    restart_due_ticks: u64,
) -> u8 {
    let mut stopped = [false; S562_SERVICE_TABLE_LEN];
    stopped[root as usize - 1] = true;
    let mut cascaded = 0u8;
    let mut changed = true;
    while changed {
        changed = false;
        for (index, descriptor) in S562_SERVICE_TABLE.iter().enumerate() {
            if stopped[index] {
                continue;
            }
            let depends_on_stopped = descriptor
                .depends_on
                .iter()
                .any(|&dependency| dependency != 0 && stopped[dependency as usize - 1]);
            if !depends_on_stopped {
                continue;
            }
            stopped[index] = true;
            changed = true;
            let row = &mut rows[index];
            if matches!(
                row.status,
                G8lS562ServiceStatus::Running | G8lS562ServiceStatus::Stopping
            ) {
                row.status = G8lS562ServiceStatus::WaitingDependency;
                row.last_reason = Some(reason);
                row.restart_due_ticks = restart_due_ticks;
                row.restart_scheduled = s562_restart_allowed(descriptor.policy, reason).is_ok();
                cascaded += 1;
            }
        }
    }
    cascaded
}

pub fn service_s562_model_supervise(
    state: &mut G8lS562SupervisionState,
    event: G8lS562SupervisionEvent,
) -> Result<G8lS562SupervisionOutcome, G8lS562SupervisionError> {
    if let (Some(last_event), Some(last_receipt)) = (state.last_event, state.last_receipt) {
        if last_event == event {
            return Ok(G8lS562SupervisionOutcome::Retained(last_receipt));
        }
    }
    let index = G8lS562SupervisionState::index_of(event.service_id())
        .ok_or(G8lS562SupervisionError::UnknownService)?;
    let descriptor = S562_SERVICE_TABLE[index];
    let now_ticks = event.now_ticks();
    if now_ticks < state.last_now_ticks {
        return Err(G8lS562SupervisionError::ClockRegression);
    }
    if state.ledger_len >= S562_RECEIPT_LEDGER_CAPACITY {
        return Err(G8lS562SupervisionError::ReceiptLedgerExhausted);
    }
    let mut rows = state.rows;
    let from = rows[index].status;
    let (transition, variant) = match event {
        G8lS562SupervisionEvent::KillRequested { reason, .. } => {
            match from {
                G8lS562ServiceStatus::Running => {}
                G8lS562ServiceStatus::Stopping => {
                    return Err(G8lS562SupervisionError::KillAlreadyPending)
                }
                _ => return Err(G8lS562SupervisionError::ServiceNotRunning),
            }
            let row = &mut rows[index];
            row.last_reason = Some(reason);
            if reason.is_graceful() {
                let grace_deadline_ticks = now_ticks
                    .checked_add(S562_GRACEFUL_SHUTDOWN_TIMEOUT_TICKS)
                    .ok_or(G8lS562SupervisionError::TickOverflow)?;
                row.status = G8lS562ServiceStatus::Stopping;
                row.grace_deadline_ticks = grace_deadline_ticks;
                let cascaded = cascade_dependents(&mut rows, descriptor.id, reason, now_ticks);
                (
                    G8lS562Transition {
                        from,
                        to: G8lS562ServiceStatus::Stopping,
                        forced: false,
                        grace_deadline_ticks,
                        backoff_ticks: 0,
                        restart_due_ticks: 0,
                        restart_scheduled: false,
                        restarts_in_window: 0,
                        cascaded_dependents: cascaded,
                        reason: Some(reason),
                    },
                    0u8,
                )
            } else {
                let (backoff_ticks, restart_due_ticks, restart_scheduled) =
                    schedule_restart(row, descriptor.policy, reason, now_ticks, now_ticks)?;
                let cascaded =
                    cascade_dependents(&mut rows, descriptor.id, reason, restart_due_ticks);
                (
                    G8lS562Transition {
                        from,
                        to: G8lS562ServiceStatus::Stopped,
                        forced: true,
                        grace_deadline_ticks: now_ticks,
                        backoff_ticks,
                        restart_due_ticks,
                        restart_scheduled,
                        restarts_in_window: 0,
                        cascaded_dependents: cascaded,
                        reason: Some(reason),
                    },
                    1,
                )
            }
        }
        G8lS562SupervisionEvent::ShutdownAcknowledged { .. }
        | G8lS562SupervisionEvent::GraceTick { .. } => {
            if from != G8lS562ServiceStatus::Stopping {
                return Err(G8lS562SupervisionError::NoShutdownPending);
            }
            let row = &mut rows[index];
            let reason = row.last_reason.unwrap_or(G8lS562KillReason::Watchdog);
            let deadline = row.grace_deadline_ticks;
            let is_ack = matches!(event, G8lS562SupervisionEvent::ShutdownAcknowledged { .. });
            if is_ack && now_ticks > deadline {
                return Err(G8lS562SupervisionError::GracePeriodExpired);
            }
            if !is_ack && now_ticks < deadline {
                (
                    G8lS562Transition {
                        from,
                        to: G8lS562ServiceStatus::Stopping,
                        forced: false,
                        grace_deadline_ticks: deadline,
                        backoff_ticks: 0,
                        restart_due_ticks: 0,
                        restart_scheduled: false,
                        restarts_in_window: 0,
                        cascaded_dependents: 0,
                        reason: Some(reason),
                    },
                    3,
                )
            } else {
                let kill_ticks = deadline.saturating_sub(S562_GRACEFUL_SHUTDOWN_TIMEOUT_TICKS);
                let (backoff_ticks, restart_due_ticks, restart_scheduled) =
                    schedule_restart(row, descriptor.policy, reason, kill_ticks, now_ticks)?;
                let cascaded =
                    cascade_dependents(&mut rows, descriptor.id, reason, restart_due_ticks);
                (
                    G8lS562Transition {
                        from,
                        to: G8lS562ServiceStatus::Stopped,
                        forced: !is_ack,
                        grace_deadline_ticks: deadline,
                        backoff_ticks,
                        restart_due_ticks,
                        restart_scheduled,
                        restarts_in_window: 0,
                        cascaded_dependents: cascaded,
                        reason: Some(reason),
                    },
                    if is_ack { 2 } else { 4 },
                )
            }
        }
        G8lS562SupervisionEvent::RestartDue { .. } => {
            match from {
                G8lS562ServiceStatus::Stopped | G8lS562ServiceStatus::WaitingDependency => {}
                G8lS562ServiceStatus::Quarantined => {
                    return Err(G8lS562SupervisionError::QuarantinedRestart)
                }
                _ => return Err(G8lS562SupervisionError::ServiceNotStopped),
            }
            let reason = rows[index]
                .last_reason
                .unwrap_or(G8lS562KillReason::Operator);
            s562_restart_allowed(descriptor.policy, reason)?;
            if now_ticks < rows[index].restart_due_ticks {
                return Err(G8lS562SupervisionError::BackoffNotElapsed);
            }
            for &dependency in descriptor.depends_on.iter() {
                if dependency != 0
                    && rows[dependency as usize - 1].status != G8lS562ServiceStatus::Running
                {
                    return Err(G8lS562SupervisionError::DependencyNotRunning);
                }
            }
            let row = &mut rows[index];
            let restarts_in_window = row.record_restart(now_ticks);
            let stormed = restarts_in_window > S562_RESTART_STORM_THRESHOLD;
            row.status = if stormed {
                G8lS562ServiceStatus::Quarantined
            } else {
                G8lS562ServiceStatus::Running
            };
            row.started_at_ticks = now_ticks;
            row.restart_scheduled = false;
            (
                G8lS562Transition {
                    from,
                    to: row.status,
                    forced: false,
                    grace_deadline_ticks: 0,
                    backoff_ticks: 0,
                    restart_due_ticks: row.restart_due_ticks,
                    restart_scheduled: false,
                    restarts_in_window,
                    cascaded_dependents: 0,
                    reason: Some(reason),
                },
                if stormed { 6 } else { 5 },
            )
        }
        G8lS562SupervisionEvent::OperatorRelease { .. } => {
            if from != G8lS562ServiceStatus::Quarantined {
                return Err(G8lS562SupervisionError::NotQuarantined);
            }
            let row = &mut rows[index];
            row.status = G8lS562ServiceStatus::Stopped;
            row.backoff_index = 0;
            row.history = [0; S562_RESTART_HISTORY_CAPACITY];
            row.history_len = 0;
            row.history_head = 0;
            row.last_reason = Some(G8lS562KillReason::Watchdog);
            row.restart_due_ticks = now_ticks;
            row.restart_scheduled = true;
            (
                G8lS562Transition {
                    from,
                    to: G8lS562ServiceStatus::Stopped,
                    forced: false,
                    grace_deadline_ticks: 0,
                    backoff_ticks: 0,
                    restart_due_ticks: now_ticks,
                    restart_scheduled: true,
                    restarts_in_window: 0,
                    cascaded_dependents: 0,
                    reason: None,
                },
                7,
            )
        }
    };
    let degraded_now = transition.to == G8lS562ServiceStatus::Quarantined
        && descriptor.kind == G8lS562ServiceKind::Critical;
    let released_critical = transition.from == G8lS562ServiceStatus::Quarantined
        && descriptor.kind == G8lS562ServiceKind::Critical;
    let system_degraded = if released_critical {
        rows.iter()
            .zip(S562_SERVICE_TABLE.iter())
            .any(|(row, entry)| {
                entry.kind == G8lS562ServiceKind::Critical
                    && row.status == G8lS562ServiceStatus::Quarantined
            })
    } else {
        state.system_degraded || degraded_now
    };
    let receipt = G8lS562SupervisionReceipt {
        sequence: S562_SEQUENCE,
        predecessor_sequence: S562_EXPECTED_PREDECESSOR,
        receipt_no: state.next_receipt_no,
        event: event.kind(),
        service_id: descriptor.id,
        kind: descriptor.kind,
        policy: descriptor.policy,
        reason: transition.reason,
        now_ticks,
        from: transition.from,
        to: transition.to,
        forced: transition.forced,
        grace_deadline_ticks: transition.grace_deadline_ticks,
        backoff_ticks: transition.backoff_ticks,
        restart_due_ticks: transition.restart_due_ticks,
        restart_scheduled: transition.restart_scheduled,
        restarts_in_window: transition.restarts_in_window,
        cascaded_dependents: transition.cascaded_dependents,
        system_degraded,
        hardware_present: S562_HARDWARE_PRESENT,
        physical_observations: S562_PHYSICAL_OBSERVATIONS,
        runbook_executed: RUNBOOK_EXECUTED_IN_S562,
    };
    state.rows = rows;
    state.last_now_ticks = now_ticks;
    state.next_receipt_no = state
        .next_receipt_no
        .checked_add(1)
        .ok_or(G8lS562SupervisionError::TickOverflow)?;
    state.system_degraded = system_degraded;
    state.last_event = Some(event);
    state.last_receipt = Some(receipt);
    state.ledger[state.ledger_len] = Some(receipt);
    state.ledger_len += 1;
    Ok(match variant {
        0 => G8lS562SupervisionOutcome::GracefulStopStarted(receipt),
        1 => G8lS562SupervisionOutcome::KilledImmediately(receipt),
        2 => G8lS562SupervisionOutcome::GracefulExit(receipt),
        3 => G8lS562SupervisionOutcome::GracePending(receipt),
        4 => G8lS562SupervisionOutcome::ForcedKill(receipt),
        5 => G8lS562SupervisionOutcome::Restarted(receipt),
        6 => G8lS562SupervisionOutcome::Quarantined(receipt),
        _ => G8lS562SupervisionOutcome::Released(receipt),
    })
}
snippet sha256: aa748dcba3d3file sha256: aa748dcba3d3
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L566
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model.rs::S562 r1 service kill restart supervision model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model::*;
use std::collections::BTreeSet;

const SOURCE: &str = include_str!(
    "../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");

const IPC_ROUTER: u8 = 1;
const DISPLAY_COMPOSITOR: u8 = 2;
const MODEM_DAEMON: u8 = 3;
const TELEPHONY_UI: u8 = 4;
const LAUNCHER: u8 = 5;
const DIAGNOSTICS_PROBE: u8 = 6;

type Outcome = G8lS562SupervisionOutcome;
type Error = G8lS562SupervisionError;
type Status = G8lS562ServiceStatus;
type Reason = G8lS562KillReason;

fn kill(
    state: &mut G8lS562SupervisionState,
    service_id: u8,
    reason: Reason,
    now_ticks: u64,
) -> Result<Outcome, Error> {
    service_s562_model_supervise(
        state,
        G8lS562SupervisionEvent::KillRequested {
            service_id,
            reason,
            now_ticks,
        },
    )
}

fn ack(state: &mut G8lS562SupervisionState, service_id: u8, now_ticks: u64) -> Result<Outcome, Error> {
    service_s562_model_supervise(
        state,
        G8lS562SupervisionEvent::ShutdownAcknowledged {
            service_id,
            now_ticks,
        },
    )
}

fn tick(state: &mut G8lS562SupervisionState, service_id: u8, now_ticks: u64) -> Result<Outcome, Error> {
    service_s562_model_supervise(
        state,
        G8lS562SupervisionEvent::GraceTick {
            service_id,
            now_ticks,
        },
    )
}

fn restart(
    state: &mut G8lS562SupervisionState,
    service_id: u8,
    now_ticks: u64,
) -> Result<Outcome, Error> {
    service_s562_model_supervise(
        state,
        G8lS562SupervisionEvent::RestartDue {
            service_id,
            now_ticks,
        },
    )
}

fn release(
    state: &mut G8lS562SupervisionState,
    service_id: u8,
    now_ticks: u64,
) -> Result<Outcome, Error> {
    service_s562_model_supervise(
        state,
        G8lS562SupervisionEvent::OperatorRelease {
            service_id,
            now_ticks,
        },
    )
}

fn fault_kill_and_restart(state: &mut G8lS562SupervisionState, service_id: u8, kill_at: u64) -> Outcome {
    let killed = kill(state, service_id, Reason::Fault, kill_at).unwrap();
    let due = killed.receipt().restart_due_ticks;
    restart(state, service_id, due).unwrap()
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S562_SEQUENCE, 562);
    assert_eq!(S562_EXPECTED_PREDECESSOR, 561);
    assert_eq!(S562_R1_STAGE, 4);
    assert_eq!(S562_R1_RANGE_FIRST, 536);
    assert_eq!(S562_R1_RANGE_LAST, 568);
    assert_eq!(S562_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S562_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S562_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S562_SD_WRITES, 0);
    assert_eq!(S562_UART_OPENS, 0);
    assert_eq!(S562_POWER_TRANSITIONS, 0);
    assert_eq!(S562_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S562_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S562_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S562_AUTOMATIC_PROMOTION);
    assert!(!S562_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S562_HARDWARE_PRESENT);
    assert!(!S562_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S562);
    assert_eq!(S562_TICK_MILLISECONDS, 1000);
    assert_eq!(S562_GRACEFUL_SHUTDOWN_TIMEOUT_TICKS, 5);
    assert_eq!(S562_RESTART_BACKOFF_SCHEDULE_TICKS, [1, 2, 4, 8]);
    assert_eq!(S562_RESTART_BACKOFF_CAP_TICKS, 8);
    assert_eq!(S562_STABLE_RESET_TICKS, 60);
    assert_eq!(S562_RESTART_STORM_WINDOW_TICKS, 60);
    assert_eq!(S562_RESTART_STORM_THRESHOLD, 5);
    assert_eq!(S562_RESTART_HISTORY_CAPACITY, 8);
    assert_eq!(S562_RECEIPT_LEDGER_CAPACITY, 64);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model";
    assert!(MAIN.contains(&format!("mod {module};")));
    assert!(SIMULATION_LIB.contains(&format!("pub mod {module};")));
}

#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
    for forbidden in [
        "unsafe",
        "asm!",
        "write_volatile",
        "crate::uart",
        "crate::arch",
        "#[no_mangle]",
        "spin::",
        "std::",
        "std::process::Command",
        "/dev/cu.",
        "TIOCEXCL",
        "crate::kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("performs no device operation"));
    assert!(SOURCE.contains("S562_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S562_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S562_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S562: bool = false"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    let errors = [
        Error::UnknownService,
        Error::ClockRegression,
        Error::ServiceNotRunning,
        Error::KillAlreadyPending,
        Error::NoShutdownPending,
        Error::GracePeriodExpired,
        Error::ServiceNotStopped,
        Error::BackoffNotElapsed,
        Error::NeverPolicyRestart,
        Error::OperatorStopNotRestartable,
        Error::QuarantinedRestart,
        Error::DependencyNotRunning,
        Error::NotQuarantined,
        Error::TickOverflow,
        Error::ReceiptLedgerExhausted,
    ];
    let codes: BTreeSet<_> = errors.into_iter().map(Error::diagnostic_code).collect();
    assert_eq!(codes.len(), errors.len());
    assert!(!codes.contains(&0));
}

#[test]
fn service_table_is_exact_and_dependencies_are_acyclic() {
    assert_eq!(S562_SERVICE_TABLE.len(), S562_SERVICE_TABLE_LEN);
    for (index, descriptor) in S562_SERVICE_TABLE.iter().enumerate() {
        assert_eq!(descriptor.id as usize, index + 1);
        assert_eq!(s562_lookup_service(descriptor.id), Some(descriptor));
        for &dependency in &descriptor.depends_on {
            assert!(dependency < descriptor.id, "dependency must precede {}", descriptor.name);
        }
    }
    assert_eq!(s562_lookup_service(0), None);
    assert_eq!(s562_lookup_service(7), None);
    let critical = S562_SERVICE_TABLE
        .iter()
        .filter(|d| d.kind == G8lS562ServiceKind::Critical)
        .map(|d| d.id)
        .collect::<Vec<_>>();
    assert_eq!(critical, [IPC_ROUTER, DISPLAY_COMPOSITOR, MODEM_DAEMON]);
    assert_eq!(s562_lookup_service(DIAGNOSTICS_PROBE).unwrap().policy, G8lS562RestartPolicy::Never);
    assert_eq!(s562_lookup_service(TELEPHONY_UI).unwrap().depends_on, [DISPLAY_COMPOSITOR, MODEM_DAEMON]);
    assert_eq!(
        (0..6).map(s562_backoff_ticks).collect::<Vec<_>>(),
        [1, 2, 4, 8, 8, 8]
    );
    assert!(Reason::Operator.is_graceful() && Reason::Watchdog.is_graceful());
    assert!(!Reason::Fault.is_graceful() && !Reason::Oom.is_graceful());
    assert!(!Reason::Operator.is_failure() && Reason::Oom.is_failure());
    assert_eq!(s562_restart_allowed(G8lS562RestartPolicy::Always, Reason::Operator), Ok(()));
    assert_eq!(
        s562_restart_allowed(G8lS562RestartPolicy::OnFailure, Reason::Operator),
        Err(Error::OperatorStopNotRestartable)
    );
    assert_eq!(s562_restart_allowed(G8lS562RestartPolicy::OnFailure, Reason::Watchdog), Ok(()));
    assert_eq!(
        s562_restart_allowed(G8lS562RestartPolicy::Never, Reason::Fault),
        Err(Error::NeverPolicyRestart)
    );
    let state = G8lS562SupervisionState::default();
    for descriptor in &S562_SERVICE_TABLE {
        assert_eq!(state.status_of(descriptor.id), Some(Status::Running));
    }
    assert_eq!(state.status_of(0), None);
    assert!(!state.system_degraded());
}

#[test]
fn operator_kill_follows_graceful_exit_and_backoff_restart() {
    let mut state = G8lS562SupervisionState::new();
    let Outcome::GracefulStopStarted(started) = kill(&mut state, LAUNCHER, Reason::Operator, 10).unwrap()
    else {
        panic!("operator kill must open a grace window")
    };
    assert_eq!(started.sequence, S562_SEQUENCE);
    assert_eq!(started.predecessor_sequence, S562_EXPECTED_PREDECESSOR);
    assert_eq!(started.receipt_no, 1);
    assert_eq!(started.event, G8lS562EventKind::KillRequested);
    assert_eq!(started.kind, G8lS562ServiceKind::Normal);
    assert_eq!(started.policy, G8lS562RestartPolicy::Always);
    assert_eq!(started.reason, Some(Reason::Operator));
    assert_eq!((started.from, started.to), (Status::Running, Status::Stopping));
    assert!(!started.forced);
    assert_eq!(started.grace_deadline_ticks, 15);
    assert_eq!(started.cascaded_dependents, 0);
    assert!(!started.hardware_present);
    assert_eq!(started.physical_observations, 0);
    assert!(!started.runbook_executed);
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Stopping));

    let Outcome::GracefulExit(exited) = ack(&mut state, LAUNCHER, 12).unwrap() else {
        panic!("acknowledged shutdown inside the window must be graceful")
    };
    assert_eq!(exited.receipt_no, 2);
    assert_eq!((exited.from, exited.to), (Status::Stopping, Status::Stopped));
    assert!(!exited.forced);
    assert_eq!(exited.backoff_ticks, 1);
    assert_eq!(exited.restart_due_ticks, 13);
    assert!(exited.restart_scheduled);
    assert_eq!(state.restart_due_of(LAUNCHER), Some(13));

    assert_eq!(restart(&mut state, LAUNCHER, 12), Err(Error::BackoffNotElapsed));
    let Outcome::Restarted(restarted) = restart(&mut state, LAUNCHER, 13).unwrap() else {
        panic!("restart at the due tick must succeed")
    };
    assert_eq!(restarted.receipt_no, 3);
    assert_eq!((restarted.from, restarted.to), (Status::Stopped, Status::Running));
    assert_eq!(restarted.restarts_in_window, 1);
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Running));
}

#[test]
fn grace_timeout_forces_kill_after_exact_ticks() {
    let mut state = G8lS562SupervisionState::new();
    kill(&mut state, LAUNCHER, Reason::Watchdog, 0).unwrap();
    let Outcome::GracePending(pending) = tick(&mut state, LAUNCHER, 3).unwrap() else {
        panic!("tick before the deadline must stay pending")
    };
    assert_eq!((pending.from, pending.to), (Status::Stopping, Status::Stopping));
    assert_eq!(pending.grace_deadline_ticks, 5);
    let Outcome::GracePending(_) = tick(&mut state, LAUNCHER, 4).unwrap() else {
        panic!("tick 4 is still inside the window")
    };
    let Outcome::ForcedKill(forced) = tick(&mut state, LAUNCHER, 5).unwrap() else {
        panic!("tick at the deadline must force the kill")
    };
    assert!(forced.forced);
    assert_eq!((forced.from, forced.to), (Status::Stopping, Status::Stopped));
    assert_eq!(forced.reason, Some(Reason::Watchdog));
    assert_eq!(forced.backoff_ticks, 1);
    assert_eq!(forced.restart_due_ticks, 6);
    assert!(forced.restart_scheduled);

    let mut late = G8lS562SupervisionState::new();
    kill(&mut late, LAUNCHER, Reason::Watchdog, 0).unwrap();
    assert_eq!(ack(&mut late, LAUNCHER, 6), Err(Error::GracePeriodExpired));
    let Outcome::ForcedKill(forced_late) = tick(&mut late, LAUNCHER, 9).unwrap() else {
        panic!("late tick must still force the kill")
    };
    assert_eq!(forced_late.restart_due_ticks, 10);
    assert_eq!(ack(&mut late, LAUNCHER, 9), Err(Error::NoShutdownPending));
}

#[test]
fn fault_and_oom_kills_are_immediate_and_forced() {
    let mut state = G8lS562SupervisionState::new();
    let Outcome::KilledImmediately(faulted) = kill(&mut state, LAUNCHER, Reason::Fault, 0).unwrap()
    else {
        panic!("fault kill must be immediate")
    };
    assert!(faulted.forced);
    assert_eq!((faulted.from, faulted.to), (Status::Running, Status::Stopped));
    assert_eq!(faulted.grace_deadline_ticks, 0);
    assert_eq!(faulted.backoff_ticks, 1);
    assert_eq!(faulted.restart_due_ticks, 1);
    assert!(faulted.restart_scheduled);

    let Outcome::KilledImmediately(oom) =
        kill(&mut state, DIAGNOSTICS_PROBE, Reason::Oom, 0).unwrap()
    else {
        panic!("oom kill must be immediate")
    };
    assert_eq!(oom.policy, G8lS562RestartPolicy::Never);
    assert!(oom.forced);
    assert!(!oom.restart_scheduled);
    assert_eq!(restart(&mut state, DIAGNOSTICS_PROBE, 5), Err(Error::NeverPolicyRestart));
    assert_eq!(state.status_of(DIAGNOSTICS_PROBE), Some(Status::Stopped));
}

#[test]
fn backoff_doubles_to_cap_and_resets_after_stable_window() {
    let mut state = G8lS562SupervisionState::new();
    let mut expected_backoff = Vec::new();
    let mut kill_at = 0;
    for _ in 0..5 {
        let killed = kill(&mut state, LAUNCHER, Reason::Fault, kill_at).unwrap().receipt();
        expected_backoff.push(killed.backoff_ticks);
        assert_eq!(killed.restart_due_ticks, kill_at + killed.backoff_ticks);
        restart(&mut state, LAUNCHER, killed.restart_due_ticks).unwrap();
        kill_at = killed.restart_due_ticks;
    }
    assert_eq!(expected_backoff, [1, 2, 4, 8, 8]);
    assert_eq!(kill_at, 23);
    let stable = kill(&mut state, LAUNCHER, Reason::Fault, 23 + S562_STABLE_RESET_TICKS)
        .unwrap()
        .receipt();
    assert_eq!(stable.backoff_ticks, 1);
    assert_eq!(stable.restart_due_ticks, 84);
    let mut unstable = G8lS562SupervisionState::new();
    kill(&mut unstable, LAUNCHER, Reason::Fault, 0).unwrap();
    restart(&mut unstable, LAUNCHER, 1).unwrap();
    let again = kill(&mut unstable, LAUNCHER, Reason::Fault, 60).unwrap().receipt();
    assert_eq!(again.backoff_ticks, 2);
}

#[test]
fn restart_storm_quarantines_after_more_than_five_in_sixty_ticks() {
    let mut state = G8lS562SupervisionState::new();
    let mut kill_at = 0;
    let mut last = None;
    for _ in 0..6 {
        let outcome = fault_kill_and_restart(&mut state, LAUNCHER, kill_at);
        kill_at = outcome.receipt().now_ticks;
        last = Some(outcome);
    }
    let Some(Outcome::Quarantined(quarantined)) = last else {
        panic!("sixth restart inside the window must quarantine")
    };
    assert_eq!(quarantined.now_ticks, 31);
    assert_eq!(quarantined.restarts_in_window, 6);
    assert_eq!((quarantined.from, quarantined.to), (Status::Stopped, Status::Quarantined));
    assert!(!quarantined.system_degraded);
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Quarantined));
    assert_eq!(restart(&mut state, LAUNCHER, 40), Err(Error::QuarantinedRestart));
    assert_eq!(kill(&mut state, LAUNCHER, Reason::Operator, 40), Err(Error::ServiceNotRunning));
    let Outcome::Released(released) = release(&mut state, LAUNCHER, 40).unwrap() else {
        panic!("operator release must clear quarantine")
    };
    assert_eq!((released.from, released.to), (Status::Quarantined, Status::Stopped));
    assert_eq!(released.restart_due_ticks, 40);
    assert!(released.restart_scheduled);
    let Outcome::Restarted(recovered) = restart(&mut state, LAUNCHER, 40).unwrap() else {
        panic!("restart after release must run")
    };
    assert_eq!(recovered.restarts_in_window, 1);
    let mut spaced = G8lS562SupervisionState::new();
    let mut at = 0;
    for _ in 0..7 {
        let outcome = fault_kill_and_restart(&mut spaced, LAUNCHER, at);
        assert!(matches!(outcome, Outcome::Restarted(_)));
        at = outcome.receipt().now_ticks + S562_RESTART_STORM_WINDOW_TICKS;
    }
}

#[test]
fn critical_quarantine_marks_system_degraded_and_release_recovers() {
    let mut state = G8lS562SupervisionState::new();
    let mut kill_at = 0;
    let mut last = None;
    for _ in 0..6 {
        let outcome = fault_kill_and_restart(&mut state, IPC_ROUTER, kill_at);
        kill_at = outcome.receipt().now_ticks;
        last = Some(outcome);
    }
    let Some(Outcome::Quarantined(quarantined)) = last else {
        panic!("critical storm must quarantine")
    };
    assert_eq!(quarantined.kind, G8lS562ServiceKind::Critical);
    assert!(quarantined.system_degraded);
    assert!(state.system_degraded());
    assert_eq!(restart(&mut state, DISPLAY_COMPOSITOR, 31), Err(Error::DependencyNotRunning));
    let Outcome::Released(released) = release(&mut state, IPC_ROUTER, 31).unwrap() else {
        panic!("release must succeed")
    };
    assert!(!released.system_degraded);
    assert!(!state.system_degraded());
    let Outcome::Restarted(recovered) = restart(&mut state, IPC_ROUTER, 31).unwrap() else {
        panic!("released critical service must restart")
    };
    assert_eq!(recovered.restarts_in_window, 1);
    assert!(matches!(restart(&mut state, DISPLAY_COMPOSITOR, 31), Ok(Outcome::Restarted(_))));
}

#[test]
fn dependency_kill_cascades_and_dependents_restart_after_dependency() {
    let mut state = G8lS562SupervisionState::new();
    let Outcome::KilledImmediately(root) = kill(&mut state, IPC_ROUTER, Reason::Fault, 0).unwrap()
    else {
        panic!("root fault must stop immediately")
    };
    assert_eq!(root.cascaded_dependents, 4);
    for dependent in [DISPLAY_COMPOSITOR, MODEM_DAEMON, TELEPHONY_UI, LAUNCHER] {
        assert_eq!(state.status_of(dependent), Some(Status::WaitingDependency));
    }
    assert_eq!(state.status_of(DIAGNOSTICS_PROBE), Some(Status::Running));
    assert_eq!(restart(&mut state, DISPLAY_COMPOSITOR, 1), Err(Error::DependencyNotRunning));
    assert_eq!(restart(&mut state, IPC_ROUTER, 0), Err(Error::BackoffNotElapsed));
    assert!(matches!(restart(&mut state, IPC_ROUTER, 1), Ok(Outcome::Restarted(_))));
    assert_eq!(restart(&mut state, TELEPHONY_UI, 1), Err(Error::DependencyNotRunning));
    assert!(matches!(restart(&mut state, DISPLAY_COMPOSITOR, 1), Ok(Outcome::Restarted(_))));
    assert_eq!(restart(&mut state, TELEPHONY_UI, 1), Err(Error::DependencyNotRunning));
    assert!(matches!(restart(&mut state, MODEM_DAEMON, 1), Ok(Outcome::Restarted(_))));
    let Outcome::Restarted(ui) = restart(&mut state, TELEPHONY_UI, 1).unwrap() else {
        panic!("dependent must restart once both dependencies run")
    };
    assert_eq!((ui.from, ui.to), (Status::WaitingDependency, Status::Running));
    assert_eq!(ui.reason, Some(Reason::Fault));
    assert!(matches!(restart(&mut state, LAUNCHER, 1), Ok(Outcome::Restarted(_))));
    for descriptor in &S562_SERVICE_TABLE {
        assert_eq!(state.status_of(descriptor.id), Some(Status::Running));
    }
    let cascaded_kill = kill(&mut state, DISPLAY_COMPOSITOR, Reason::Fault, 2).unwrap().receipt();
    assert_eq!(cascaded_kill.backoff_ticks, 1, "cascaded stops do not consume backoff");
    assert_eq!(cascaded_kill.cascaded_dependents, 2);
    assert_eq!(state.status_of(MODEM_DAEMON), Some(Status::Running));
}

#[test]
fn operator_stop_of_on_failure_service_is_not_restarted() {
    let mut state = G8lS562SupervisionState::new();
    let started = kill(&mut state, MODEM_DAEMON, Reason::Operator, 0).unwrap().receipt();
    assert_eq!(started.cascaded_dependents, 1);
    assert_eq!(state.status_of(TELEPHONY_UI), Some(Status::WaitingDependency));
    let Outcome::GracefulExit(exited) = ack(&mut state, MODEM_DAEMON, 2).unwrap() else {
        panic!("operator stop must exit gracefully")
    };
    assert!(!exited.restart_scheduled);
    assert_eq!(exited.cascaded_dependents, 0);
    assert_eq!(restart(&mut state, MODEM_DAEMON, 10), Err(Error::OperatorStopNotRestartable));
    assert_eq!(restart(&mut state, TELEPHONY_UI, 10), Err(Error::OperatorStopNotRestartable));
    let mut watchdog = G8lS562SupervisionState::new();
    kill(&mut watchdog, MODEM_DAEMON, Reason::Watchdog, 0).unwrap();
    let forced = tick(&mut watchdog, MODEM_DAEMON, 5).unwrap().receipt();
    assert!(forced.restart_scheduled);
    assert!(matches!(restart(&mut watchdog, MODEM_DAEMON, 6), Ok(Outcome::Restarted(_))));
    assert!(matches!(restart(&mut watchdog, TELEPHONY_UI, 6), Ok(Outcome::Restarted(_))));
}

#[test]
fn unknown_service_and_clock_regression_fail_closed() {
    let mut state = G8lS562SupervisionState::new();
    assert_eq!(kill(&mut state, 0, Reason::Fault, 0), Err(Error::UnknownService));
    assert_eq!(kill(&mut state, 7, Reason::Fault, 0), Err(Error::UnknownService));
    assert_eq!(restart(&mut state, u8::MAX, 0), Err(Error::UnknownService));
    assert_eq!(ack(&mut state, LAUNCHER, 0), Err(Error::NoShutdownPending));
    assert_eq!(tick(&mut state, LAUNCHER, 0), Err(Error::NoShutdownPending));
    assert_eq!(restart(&mut state, LAUNCHER, 0), Err(Error::ServiceNotStopped));
    assert_eq!(release(&mut state, LAUNCHER, 0), Err(Error::NotQuarantined));
    assert_eq!(state.ledger_len(), 0);
    kill(&mut state, LAUNCHER, Reason::Operator, 10).unwrap();
    assert_eq!(tick(&mut state, LAUNCHER, 9), Err(Error::ClockRegression));
    assert_eq!(kill(&mut state, IPC_ROUTER, Reason::Fault, 9), Err(Error::ClockRegression));
    assert_eq!(kill(&mut state, LAUNCHER, Reason::Watchdog, 11), Err(Error::KillAlreadyPending));
    assert_eq!(state.ledger_len(), 1);
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Stopping));
}

#[test]
fn tick_overflow_fails_closed() {
    let mut state = G8lS562SupervisionState::new();
    assert_eq!(kill(&mut state, LAUNCHER, Reason::Fault, u64::MAX), Err(Error::TickOverflow));
    assert_eq!(
        kill(&mut state, LAUNCHER, Reason::Operator, u64::MAX - 2),
        Err(Error::TickOverflow)
    );
    assert_eq!(state.ledger_len(), 0);
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Running));
    kill(&mut state, LAUNCHER, Reason::Watchdog, u64::MAX - 6).unwrap();
    assert_eq!(tick(&mut state, LAUNCHER, u64::MAX), Err(Error::TickOverflow));
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Stopping));
}

#[test]
fn receipt_numbers_are_monotonic_and_ledger_is_bounded() {
    let mut state = G8lS562SupervisionState::new();
    let mut at = 0;
    for _ in 0..(S562_RECEIPT_LEDGER_CAPACITY / 2) {
        let outcome = fault_kill_and_restart(&mut state, LAUNCHER, at);
        assert!(matches!(outcome, Outcome::Restarted(_)));
        at = outcome.receipt().now_ticks + S562_STABLE_RESET_TICKS;
    }
    assert_eq!(state.ledger_len(), S562_RECEIPT_LEDGER_CAPACITY);
    for position in 0..S562_RECEIPT_LEDGER_CAPACITY {
        let receipt = state.ledger_entry(position).unwrap();
        assert_eq!(receipt.receipt_no, position as u64 + 1);
        if position > 0 {
            assert!(receipt.now_ticks >= state.ledger_entry(position - 1).unwrap().now_ticks);
        }
    }
    assert_eq!(state.ledger_entry(S562_RECEIPT_LEDGER_CAPACITY), None);
    assert_eq!(kill(&mut state, LAUNCHER, Reason::Fault, at), Err(Error::ReceiptLedgerExhausted));
    assert_eq!(state.status_of(LAUNCHER), Some(Status::Running));
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS562SupervisionState::new();
    let Outcome::GracefulStopStarted(receipt) = kill(&mut state, LAUNCHER, Reason::Operator, 10).unwrap()
    else {
        panic!("first publication missing")
    };
    assert_eq!(
        kill(&mut state, LAUNCHER, Reason::Operator, 10),
        Ok(Outcome::Retained(receipt))
    );
    assert_eq!(state.ledger_len(), 1);
    assert_eq!(state.last_receipt(), Some(receipt));
    let Outcome::GracefulExit(exited) = ack(&mut state, LAUNCHER, 12).unwrap() else {
        panic!("ack missing")
    };
    assert_eq!(ack(&mut state, LAUNCHER, 12), Ok(Outcome::Retained(exited)));
    assert_eq!(state.ledger_len(), 2);
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS562SupervisionState::new();
    let published = kill(&mut state, LAUNCHER, Reason::Operator, 10).unwrap().receipt();
    assert_eq!(kill(&mut state, LAUNCHER, Reason::Watchdog, 10), Err(Error::KillAlreadyPending));
    assert_eq!(kill(&mut state, LAUNCHER, Reason::Operator, 11), Err(Error::KillAlreadyPending));
    assert_eq!(restart(&mut state, LAUNCHER, 20), Err(Error::ServiceNotStopped));
    assert_eq!(state.last_receipt(), Some(published));
    assert_eq!(state.ledger_len(), 1);
    let exited = ack(&mut state, LAUNCHER, 12).unwrap().receipt();
    assert_eq!(ack(&mut state, LAUNCHER, 13), Err(Error::NoShutdownPending));
    assert_eq!(state.last_receipt(), Some(exited));
    assert_ne!(exited, published);
}
snippet sha256: 7acdb94149dcfile sha256: 7acdb94149dc
03 · Kapı kimlik kaydı

Operations sıra, kimlik ve başlık bağı

tam Operations kaydıL2190–L2244
website/src/lib/operations.ts::g8l-s562-r1-service-kill-restart-supervision-model
  {
    id: "g8l-s562-r1-service-kill-restart-supervision-model",
    date: "2026-08-30",
    sequence: 562,
    status: "passed",
    umbrella_status: "partial",
    title: "S562 · R1 uygulama: servis öldürme ve yeniden başlatma gözetim modeli",
    summary:
      "S562 kaynak/host model kapısı PASS'tir: R1 uygulama/recovery aşamasının servis öldürme ve yeniden başlatma gözetimi, sabit altı servisli tablo (Critical/Normal tür, Always/OnFailure/Never restart politikası, bağımlılıklar), Operator/Watchdog/Fault/Oom gerekçeli kill istekleri, 5 tick'lik graceful shutdown penceresi sonrası zorunlu kill, 1-2-4-8 tick üstel backoff (8'de sabitlenir, 60 stabil tick sonrası sıfırlanır), 60 tick içinde 5'ten fazla restart'ta Quarantined kararı, bağımlılık sıralı cascade/restart ve monoton numaralı receipt defteriyle fail-closed olarak modellendi. Bilinmeyen servis, Never-politika restart'ı, karantinada restart, saat gerilemesi ve tick taşması dahil 15 benzersiz diagnostic kodla reddedilir. Focused 18/18 PASS'tir. S540 ve S543 fiziksel RED immutable kalır; physical observation=0, donanım yoktur ve RUNBOOK_EXECUTED_IN_S562=NO'dur. S563 bu gözetim receipt'leri üzerine sınırlı recovery ve fault containment modelleme kapısıdır.",
    evidence: [
      "S562, S561'den ayrı bir source/host model module, 18-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; hiçbir production callsite, boot/IRQ/scheduler/driver bağlantısı yoktur.",
      "Dar S562 source-model status=PASS; R1 umbrella=PARTIAL ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
      "Servis tablosu sabittir: 1 ipc_router (Critical/Always), 2 display_compositor (Critical/Always, dep 1), 3 modem_daemon (Critical/OnFailure, dep 1), 4 telephony_ui (Normal/OnFailure, dep 2+3), 5 launcher (Normal/Always, dep 2), 6 diagnostics_probe (Normal/Never); bağımlılık kimlikleri her zaman bağımlıdan küçüktür ve döngü yoktur.",
      "Kill gerekçeleri tablolanmıştır: Operator ve Watchdog exact 5 tick'lik graceful shutdown penceresi açar; pencere içinde acknowledge graceful exit, deadline'da veya sonrasında grace tick forced kill üretir; Fault ve Oom servisi anında ve forced olarak durdurur.",
      "Restart backoff exact 1, 2, 4, 8 tick dizisini izler, 8 tick'te sabitlenir ve son start'tan itibaren 60 stabil tick geçtiyse 1'e sıfırlanır; tüm tick aritmetiği checked'tir ve taşma TickOverflow ile fail-closed döner.",
      "Restart fırtınası 8 slotluk sınırlı history ile izlenir: 60 tick'lik pencerede 5'ten fazla restart servisi Quarantined durumuna alır; Critical bir servisin karantinası system_degraded bayrağını kurar ve yalnız açık operatör release'i karantinayı, history'yi ve backoff'u temizler.",
      "Bağımlılık sıralaması korunur: bir servisin kill'i Running/Stopping bağımlılarını transitif olarak WaitingDependency durumuna taşır; bir bağımlı ancak tüm bağımlılıkları yeniden Running olduktan sonra restart edilebilir, aksi halde DependencyNotRunning döner.",
      "Politika denetimi fail-closed'dur: Never servisleri hiç restart edilmez (NeverPolicyRestart), OnFailure servisleri Operator stop sonrası restart edilmez (OperatorStopNotRestartable), Always servisleri her gerekçeden sonra restart edilir.",
      "Receipt'ler monoton receipt_no ile numaralanır ve 64 slotluk sınırlı deftere yazılır; defter dolduğunda ReceiptLedgerExhausted ile reddedilir; exact replay aynı receipt ile Retained döner ve yayın sonrası sapan girdi (aynı serviste farklı gerekçeli ikinci kill dahil) fail-closed reddedilir.",
      "Bilinmeyen servis kimliği, saat gerilemesi, Running olmayan servise kill, bekleyen shutdown olmadan acknowledge/tick, deadline sonrası acknowledge, backoff dolmadan restart ve karantinasız release dahil 15 hata varyantının diagnostic kodları sıfırdan farklı ve benzersizdir.",
      "Focused target 1 grup / 18 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 29064 B / aa748dcba3d391492b7e6886518b9171f077c77e413bff280d7b439b5c04d1a0; focused test 24333 B / 7acdb94149dc6e449794c4e4451e5c7d80d83c2349673ac10b343746893d4b83 SHA-256'dır.",
      "Proof 4909 B'dir.",
      "Kaynak unsafe, asm!, write_volatile, crate::uart, crate::arch, #[no_mangle] veya spin:: içermez; tick saati focused test tarafından verilen model değeridir ve hiçbir donanım sayacı okunmaz.",
      "S540 ve S543 immutable raw ve fiziksel RED kararları byte-exact korunur; automatic promotion=false'dur ve S546 kararı varsayılmaz.",
      "RUNBOOK_EXECUTED_IN_S562=NO; supported-profile runtime observations=0, physical observations=0, SD/UART/power/new-raw=0/0/0/0, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S563 yalnız host üzerinde S562 gözetim receipt'leri üzerine sınırlı recovery ve fault containment modelleyecektir; aygıt veya fiziksel koşu yetkisi değildir.",
    ],
    commands: [
      "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s562-focused",
        title: "S562 service kill/restart supervision model focused",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S562 focused=1 group / 18 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    limitations: [
      "S562 yalnız host üzerinde çalışan bir kaynak modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve gerçek bir supervisor task, watchdog veya scheduler kancası çalıştırılmamıştır.",
      "Tick saati caller tarafından verilen model değeridir; gerçek zaman, timer IRQ veya güç geçişi gözlenmemiştir; BOOT_TO_UI_READY gerçek UART'ta görülmedi, Boot-to-UI ve R1 acceptance false kalır.",
      "S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşunun kararı bu kapıda varsayılmaz veya yükseltilmez.",
      "Modül hiçbir boot, IRQ, scheduler veya driver yoluna bağlı değildir ve hiçbir gerçek servis öldürülmemiş veya yeniden başlatılmamıştır.",
      "S563 host-only sınırlı recovery ve fault containment kapısıdır; yeni SD/UART/power koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
    ],
  },
snippet sha256: 8f37b843d83bfile sha256: 9726dbf00f84
Focused test komutu
CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s562_r1_service_kill_restart_supervision_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S562-R1-Service-Kill-Restart-Supervision-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9