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: aa748dcba3d3…file 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: 7acdb94149dc…file 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: 8f37b843d83b…file 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=1proof: 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: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06