ASELSANMicrokernel
S561 · SOURCE-BOUND GATE EVIDENCE

S561 · R1 uygulama: izinli uygulama başlatma akışı modeli

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

S561Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s561-r1-permissioned-application-launch-flow-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–L1106
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s561_r1_permissioned_application_launch_flow_model.rs::S561 r1 permissioned application launch flow model implementation
#![allow(unexpected_cfgs)]

//! S561 models a permissioned application launch flow (R1 stage 4:
//! application, recovery and update demonstration).
//!
//! The model consists of an application manifest (`id`, ASCII `name` of at
//! most 32 bytes, packed `version`, requested-permission bitset over
//! {DISPLAY, INPUT, MODEM, AUDIO, STORAGE, NETWORK}, 32-byte ELF hash and a
//! trust class), a table-driven policy that maps each trust class
//! (Lab / Signed / Untrusted) to the permissions it may be granted, and a
//! six-stage launch pipeline `Requested -> PolicyChecked ->
//! CapabilitiesMinted -> Started -> Running -> Exited`.  Every stage
//! transition returns a `G8lS561LaunchStepReceipt` and is folded into an
//! FNV-1a 64 trace checksum.  Capabilities are minted only for granted
//! permissions, carry a deterministic id derived from the monotonic launch
//! index and the permission bit, and are all revoked when the application
//! exits.  At most eight applications may be resident at once; denials are
//! recorded in a bounded denial log with their diagnostic code and the
//! requested/allowed/denied bit sets.
//!
//! Fail-closed conditions: any manifest error (zero id, empty / too long /
//! non-ASCII / non-canonical name, zero version, unknown permission bits,
//! all-zero ELF hash), permission escalation beyond the trust-class policy,
//! a duplicate id among resident applications, the ninth concurrent launch,
//! any out-of-order stage transition, an unknown application id, counter
//! overflow, an empty or oversized command script and any divergent script
//! after the first published receipt.
//!
//! What this gate does NOT claim: there is no ELF loader, no scheduler task,
//! no IPC endpoint, no real capability table and no production callsite.  No
//! hardware exists for this gate; `physical observations = 0`,
//! `RUNBOOK_EXECUTED_IN_S561=NO`, Boot-to-UI physically observed = false and
//! R1 acceptance complete = false.  S540 and S543 remain immutable physical
//! RED verdicts.  Predecessor: S560 (modem subsystem capability supervision
//! model).  Next: S562 (service kill/restart supervision model).

pub const S561_SEQUENCE: usize = 561;
pub const S561_EXPECTED_PREDECESSOR: usize = 560;
pub const S561_R1_STAGE: u8 = 4;
pub const S561_R1_RANGE_FIRST: usize = 536;
pub const S561_R1_RANGE_LAST: usize = 568;
pub const S561_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S561_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S561_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S561_SD_WRITES: usize = 0;
pub const S561_UART_OPENS: usize = 0;
pub const S561_POWER_TRANSITIONS: usize = 0;
pub const S561_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S561_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S561_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S561_AUTOMATIC_PROMOTION: bool = false;
pub const S561_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S561_HARDWARE_PRESENT: bool = false;
pub const S561_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S561: bool = false;

/// Maximum number of resident (Requested..Running) applications.
pub const S561_MAX_CONCURRENT_APPLICATIONS: usize = 8;
/// Maximum manifest name length in bytes (printable ASCII only).
pub const S561_MAX_APPLICATION_NAME_BYTES: usize = 32;
/// ELF hash width carried by the manifest.
pub const S561_ELF_HASH_BYTES: usize = 32;
/// Number of distinct permissions in the bitset.
pub const S561_PERMISSION_COUNT: usize = 6;
/// Bitmask of every known permission bit.
pub const S561_ALL_PERMISSION_BITS: u8 = 0b11_1111;
/// Maximum number of commands accepted by one model service call.
pub const S561_MAX_SCRIPT_COMMANDS: usize = 64;
/// Bounded denial and exit log capacities (ring buffers).
pub const S561_DENIAL_LOG_CAPACITY: usize = 8;
pub const S561_EXIT_LOG_CAPACITY: usize = 8;
/// Number of bytes folded into the trace checksum per step receipt.
pub const S561_STEP_RECEIPT_BYTES: usize = 17;
/// FNV-1a 64-bit parameters.
pub const S561_FNV1A_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
pub const S561_FNV1A_PRIME: u64 = 0x0000_0100_0000_01b3;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS561Permission {
    Display,
    Input,
    Modem,
    Audio,
    Storage,
    Network,
}

impl G8lS561Permission {
    pub const ALL: [Self; S561_PERMISSION_COUNT] = [
        Self::Display,
        Self::Input,
        Self::Modem,
        Self::Audio,
        Self::Storage,
        Self::Network,
    ];

    pub const fn bit(self) -> u8 {
        match self {
            Self::Display => 0b00_0001,
            Self::Input => 0b00_0010,
            Self::Modem => 0b00_0100,
            Self::Audio => 0b00_1000,
            Self::Storage => 0b01_0000,
            Self::Network => 0b10_0000,
        }
    }

    pub const fn index(self) -> usize {
        match self {
            Self::Display => 0,
            Self::Input => 1,
            Self::Modem => 2,
            Self::Audio => 3,
            Self::Storage => 4,
            Self::Network => 5,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct G8lS561PermissionSet(u8);

impl G8lS561PermissionSet {
    pub const NONE: Self = Self(0);
    pub const DISPLAY: Self = Self(0b00_0001);
    pub const INPUT: Self = Self(0b00_0010);
    pub const MODEM: Self = Self(0b00_0100);
    pub const AUDIO: Self = Self(0b00_1000);
    pub const STORAGE: Self = Self(0b01_0000);
    pub const NETWORK: Self = Self(0b10_0000);
    pub const ALL: Self = Self(S561_ALL_PERMISSION_BITS);

    /// Returns `None` when `bits` carries any unknown bit.
    pub const fn from_bits(bits: u8) -> Option<Self> {
        if bits & !S561_ALL_PERMISSION_BITS != 0 {
            None
        } else {
            Some(Self(bits))
        }
    }

    pub const fn bits(self) -> u8 {
        self.0
    }

    pub const fn contains(self, permission: G8lS561Permission) -> bool {
        self.0 & permission.bit() != 0
    }

    pub const fn union(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }

    pub const fn difference(self, other: Self) -> Self {
        Self(self.0 & !other.0)
    }

    pub const fn is_subset_of(self, other: Self) -> bool {
        self.0 & !other.0 == 0
    }

    pub const fn count(self) -> u8 {
        self.0.count_ones() as u8
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS561TrustClass {
    Lab,
    Signed,
    Untrusted,
}

impl G8lS561TrustClass {
    pub const fn code(self) -> u8 {
        match self {
            Self::Lab => 1,
            Self::Signed => 2,
            Self::Untrusted => 3,
        }
    }

    /// Table-driven policy: the permissions a trust class may be granted.
    pub const fn allowed_permissions(self) -> G8lS561PermissionSet {
        let mut i = 0;
        while i < S561_POLICY_TABLE.len() {
            let (class, bits) = S561_POLICY_TABLE[i];
            if class as u8 == self as u8 {
                return G8lS561PermissionSet(bits);
            }
            i += 1;
        }
        G8lS561PermissionSet::NONE
    }
}

/// Policy table: Lab may hold everything, Signed everything except MODEM,
/// Untrusted only DISPLAY and INPUT.
pub const S561_POLICY_TABLE: [(G8lS561TrustClass, u8); 3] = [
    (G8lS561TrustClass::Lab, 0b11_1111),
    (G8lS561TrustClass::Signed, 0b11_1011),
    (G8lS561TrustClass::Untrusted, 0b00_0011),
];

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561ApplicationName {
    bytes: [u8; S561_MAX_APPLICATION_NAME_BYTES],
    len: u8,
}

impl G8lS561ApplicationName {
    /// Builds a canonical name from printable ASCII; `None` when empty, too
    /// long or containing a non-printable / non-ASCII byte.
    pub const fn from_ascii(text: &str) -> Option<Self> {
        let source = text.as_bytes();
        if source.is_empty() || source.len() > S561_MAX_APPLICATION_NAME_BYTES {
            return None;
        }
        let mut bytes = [0u8; S561_MAX_APPLICATION_NAME_BYTES];
        let mut i = 0;
        while i < source.len() {
            if source[i] < 0x20 || source[i] > 0x7e {
                return None;
            }
            bytes[i] = source[i];
            i += 1;
        }
        Some(Self {
            bytes,
            len: source.len() as u8,
        })
    }

    /// Raw constructor (no validation) so malformed manifests can be modelled.
    pub const fn from_raw(bytes: [u8; S561_MAX_APPLICATION_NAME_BYTES], len: u8) -> Self {
        Self { bytes, len }
    }

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

    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub const fn raw_bytes(&self) -> [u8; S561_MAX_APPLICATION_NAME_BYTES] {
        self.bytes
    }

    pub fn as_bytes(&self) -> &[u8] {
        let len = core::cmp::min(self.len as usize, S561_MAX_APPLICATION_NAME_BYTES);
        &self.bytes[..len]
    }

    pub const fn validate(&self) -> Result<(), G8lS561ApplicationLaunchFlowError> {
        if self.len == 0 {
            return Err(G8lS561ApplicationLaunchFlowError::ManifestEmptyName);
        }
        if self.len as usize > S561_MAX_APPLICATION_NAME_BYTES {
            return Err(G8lS561ApplicationLaunchFlowError::ManifestNameTooLong);
        }
        let mut i = 0;
        while i < S561_MAX_APPLICATION_NAME_BYTES {
            let byte = self.bytes[i];
            if i < self.len as usize {
                if byte < 0x20 || byte > 0x7e {
                    return Err(G8lS561ApplicationLaunchFlowError::ManifestNameNotAscii);
                }
            } else if byte != 0 {
                return Err(G8lS561ApplicationLaunchFlowError::ManifestNamePaddingNotZero);
            }
            i += 1;
        }
        Ok(())
    }
}

/// Packs a semantic version as `major << 16 | minor << 8 | patch`.
pub const fn s561_pack_version(major: u8, minor: u8, patch: u8) -> u32 {
    ((major as u32) << 16) | ((minor as u32) << 8) | patch as u32
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561ApplicationManifest {
    pub id: u32,
    pub name: G8lS561ApplicationName,
    pub version: u32,
    /// Raw requested bitset; unknown bits are rejected by `validate`.
    pub requested_permissions: u8,
    pub elf_hash: [u8; S561_ELF_HASH_BYTES],
    pub trust_class: G8lS561TrustClass,
}

impl G8lS561ApplicationManifest {
    pub const fn new(
        id: u32,
        name: G8lS561ApplicationName,
        version: u32,
        requested_permissions: u8,
        elf_hash: [u8; S561_ELF_HASH_BYTES],
        trust_class: G8lS561TrustClass,
    ) -> Self {
        Self {
            id,
            name,
            version,
            requested_permissions,
            elf_hash,
            trust_class,
        }
    }

    pub const fn validate(
        &self,
    ) -> Result<G8lS561PermissionSet, G8lS561ApplicationLaunchFlowError> {
        if self.id == 0 {
            return Err(G8lS561ApplicationLaunchFlowError::ManifestZeroId);
        }
        if let Err(error) = self.name.validate() {
            return Err(error);
        }
        if self.version == 0 {
            return Err(G8lS561ApplicationLaunchFlowError::ManifestZeroVersion);
        }
        let requested = match G8lS561PermissionSet::from_bits(self.requested_permissions) {
            Some(set) => set,
            None => return Err(G8lS561ApplicationLaunchFlowError::ManifestUnknownPermissionBits),
        };
        let mut i = 0;
        let mut nonzero = false;
        while i < S561_ELF_HASH_BYTES {
            nonzero |= self.elf_hash[i] != 0;
            i += 1;
        }
        if !nonzero {
            return Err(G8lS561ApplicationLaunchFlowError::ManifestZeroElfHash);
        }
        Ok(requested)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS561LaunchStage {
    Requested,
    PolicyChecked,
    CapabilitiesMinted,
    Started,
    Running,
    Exited,
}

impl G8lS561LaunchStage {
    pub const fn code(self) -> u8 {
        match self {
            Self::Requested => 1,
            Self::PolicyChecked => 2,
            Self::CapabilitiesMinted => 3,
            Self::Started => 4,
            Self::Running => 5,
            Self::Exited => 6,
        }
    }

    pub const fn next(self) -> Option<Self> {
        match self {
            Self::Requested => Some(Self::PolicyChecked),
            Self::PolicyChecked => Some(Self::CapabilitiesMinted),
            Self::CapabilitiesMinted => Some(Self::Started),
            Self::Started => Some(Self::Running),
            Self::Running => Some(Self::Exited),
            Self::Exited => None,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561Capability {
    pub capability_id: u64,
    pub app_id: u32,
    pub launch_index: u32,
    pub permission: G8lS561Permission,
    pub revoked: bool,
}

/// Deterministic capability id: the globally unique launch index in the
/// upper bits and the permission bit in the low byte.
pub const fn s561_capability_id(launch_index: u32, permission: G8lS561Permission) -> u64 {
    ((launch_index as u64) << 8) | permission.bit() as u64
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561LaunchSlot {
    pub manifest: G8lS561ApplicationManifest,
    pub launch_index: u32,
    pub stage: G8lS561LaunchStage,
    pub granted_permissions: G8lS561PermissionSet,
    pub capabilities: [Option<G8lS561Capability>; S561_PERMISSION_COUNT],
}

impl G8lS561LaunchSlot {
    pub fn live_capability_count(&self) -> u8 {
        self.capabilities
            .iter()
            .filter(|slot| matches!(slot, Some(cap) if !cap.revoked))
            .count() as u8
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561LaunchStepReceipt {
    pub app_id: u32,
    pub launch_index: u32,
    /// `0` for the initial request, otherwise the previous stage code.
    pub previous_stage_code: u8,
    pub stage: G8lS561LaunchStage,
    pub granted_permissions: u8,
    pub capabilities_minted: u8,
    pub capabilities_revoked: u8,
    pub exit_code: u32,
}

impl G8lS561LaunchStepReceipt {
    pub const fn encode(&self) -> [u8; S561_STEP_RECEIPT_BYTES] {
        let app = self.app_id.to_le_bytes();
        let launch = self.launch_index.to_le_bytes();
        let exit = self.exit_code.to_le_bytes();
        [
            app[0],
            app[1],
            app[2],
            app[3],
            launch[0],
            launch[1],
            launch[2],
            launch[3],
            self.previous_stage_code,
            self.stage.code(),
            self.granted_permissions,
            self.capabilities_minted,
            self.capabilities_revoked,
            exit[0],
            exit[1],
            exit[2],
            exit[3],
        ]
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561DenialRecord {
    pub app_id: u32,
    pub diagnostic_code: u64,
    pub requested_permissions: u8,
    pub allowed_permissions: u8,
    pub denied_permissions: u8,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561ExitRecord {
    pub app_id: u32,
    pub launch_index: u32,
    pub exit_code: u32,
    pub capabilities_revoked: u8,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS561LaunchCommand {
    Request(G8lS561ApplicationManifest),
    CheckPolicy(u32),
    MintCapabilities(u32),
    Start(u32),
    Run(u32),
    Exit { app_id: u32, exit_code: u32 },
}

/// The five commands that take a manifest from Requested to Running.
pub const fn s561_full_launch_commands(
    manifest: G8lS561ApplicationManifest,
) -> [G8lS561LaunchCommand; 5] {
    [
        G8lS561LaunchCommand::Request(manifest),
        G8lS561LaunchCommand::CheckPolicy(manifest.id),
        G8lS561LaunchCommand::MintCapabilities(manifest.id),
        G8lS561LaunchCommand::Start(manifest.id),
        G8lS561LaunchCommand::Run(manifest.id),
    ]
}

pub const fn s561_fnv1a_64(seed: u64, bytes: &[u8]) -> u64 {
    let mut hash = seed;
    let mut i = 0;
    while i < bytes.len() {
        hash ^= bytes[i] as u64;
        hash = hash.wrapping_mul(S561_FNV1A_PRIME);
        i += 1;
    }
    hash
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS561ApplicationLaunchFlowError {
    EmptyScript,
    ScriptTooLong,
    ManifestZeroId,
    ManifestEmptyName,
    ManifestNameTooLong,
    ManifestNameNotAscii,
    ManifestNamePaddingNotZero,
    ManifestZeroVersion,
    ManifestUnknownPermissionBits,
    ManifestZeroElfHash,
    DuplicateApplicationId,
    ConcurrentApplicationLimit,
    PermissionEscalation,
    UnknownApplication,
    StageOrderViolation,
    CounterOverflow,
    PublishedStateDrift,
}

impl G8lS561ApplicationLaunchFlowError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::EmptyScript => 1,
            Self::ScriptTooLong => 2,
            Self::ManifestZeroId => 3,
            Self::ManifestEmptyName => 4,
            Self::ManifestNameTooLong => 5,
            Self::ManifestNameNotAscii => 6,
            Self::ManifestNamePaddingNotZero => 7,
            Self::ManifestZeroVersion => 8,
            Self::ManifestUnknownPermissionBits => 9,
            Self::ManifestZeroElfHash => 10,
            Self::DuplicateApplicationId => 11,
            Self::ConcurrentApplicationLimit => 12,
            Self::PermissionEscalation => 13,
            Self::UnknownApplication => 14,
            Self::StageOrderViolation => 15,
            Self::CounterOverflow => 16,
            Self::PublishedStateDrift => 17,
        }
    }

    pub const ALL: [Self; 17] = [
        Self::EmptyScript,
        Self::ScriptTooLong,
        Self::ManifestZeroId,
        Self::ManifestEmptyName,
        Self::ManifestNameTooLong,
        Self::ManifestNameNotAscii,
        Self::ManifestNamePaddingNotZero,
        Self::ManifestZeroVersion,
        Self::ManifestUnknownPermissionBits,
        Self::ManifestZeroElfHash,
        Self::DuplicateApplicationId,
        Self::ConcurrentApplicationLimit,
        Self::PermissionEscalation,
        Self::UnknownApplication,
        Self::StageOrderViolation,
        Self::CounterOverflow,
        Self::PublishedStateDrift,
    ];
}

/// The launch pipeline engine: bounded slot table, monotonic launch index,
/// bounded denial and exit logs, counters and the FNV-1a trace checksum.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561LaunchEngine {
    slots: [Option<G8lS561LaunchSlot>; S561_MAX_CONCURRENT_APPLICATIONS],
    next_launch_index: u32,
    denials: [Option<G8lS561DenialRecord>; S561_DENIAL_LOG_CAPACITY],
    denial_count: u32,
    exits: [Option<G8lS561ExitRecord>; S561_EXIT_LOG_CAPACITY],
    exit_count: u32,
    launches_requested: u32,
    policy_checks_passed: u32,
    capabilities_minted: u32,
    applications_started: u32,
    applications_run: u32,
    capabilities_revoked: u32,
    peak_concurrent: u8,
    step_count: u32,
    trace_checksum: u64,
}

impl G8lS561LaunchEngine {
    pub const fn new() -> Self {
        Self::with_next_launch_index(1)
    }

    /// Starts the monotonic launch index at `index` (used to model overflow).
    pub const fn with_next_launch_index(index: u32) -> Self {
        Self {
            slots: [None; S561_MAX_CONCURRENT_APPLICATIONS],
            next_launch_index: index,
            denials: [None; S561_DENIAL_LOG_CAPACITY],
            denial_count: 0,
            exits: [None; S561_EXIT_LOG_CAPACITY],
            exit_count: 0,
            launches_requested: 0,
            policy_checks_passed: 0,
            capabilities_minted: 0,
            applications_started: 0,
            applications_run: 0,
            capabilities_revoked: 0,
            peak_concurrent: 0,
            step_count: 0,
            trace_checksum: S561_FNV1A_OFFSET_BASIS,
        }
    }

    pub fn slot(&self, app_id: u32) -> Option<&G8lS561LaunchSlot> {
        self.slots
            .iter()
            .flatten()
            .find(|slot| slot.manifest.id == app_id)
    }

    pub fn capability(
        &self,
        app_id: u32,
        permission: G8lS561Permission,
    ) -> Option<G8lS561Capability> {
        self.slot(app_id)
            .and_then(|slot| slot.capabilities[permission.index()])
    }

    pub fn active_count(&self) -> u8 {
        self.slots.iter().flatten().count() as u8
    }

    pub const fn denial_count(&self) -> u32 {
        self.denial_count
    }

    pub const fn denial(&self, index: usize) -> Option<G8lS561DenialRecord> {
        if index < S561_DENIAL_LOG_CAPACITY {
            self.denials[index]
        } else {
            None
        }
    }

    pub const fn exit_count(&self) -> u32 {
        self.exit_count
    }

    pub const fn exit_record(&self, index: usize) -> Option<G8lS561ExitRecord> {
        if index < S561_EXIT_LOG_CAPACITY {
            self.exits[index]
        } else {
            None
        }
    }

    pub const fn next_launch_index(&self) -> u32 {
        self.next_launch_index
    }

    pub const fn peak_concurrent(&self) -> u8 {
        self.peak_concurrent
    }

    pub const fn step_count(&self) -> u32 {
        self.step_count
    }

    pub const fn trace_checksum(&self) -> u64 {
        self.trace_checksum
    }

    pub const fn counters(&self) -> [u32; 7] {
        [
            self.launches_requested,
            self.policy_checks_passed,
            self.capabilities_minted,
            self.applications_started,
            self.applications_run,
            self.exit_count,
            self.capabilities_revoked,
        ]
    }

    pub fn apply(
        &mut self,
        command: G8lS561LaunchCommand,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        match command {
            G8lS561LaunchCommand::Request(manifest) => self.request(manifest),
            G8lS561LaunchCommand::CheckPolicy(app_id) => self.check_policy(app_id),
            G8lS561LaunchCommand::MintCapabilities(app_id) => self.mint_capabilities(app_id),
            G8lS561LaunchCommand::Start(app_id) => self.start(app_id),
            G8lS561LaunchCommand::Run(app_id) => self.run(app_id),
            G8lS561LaunchCommand::Exit { app_id, exit_code } => self.exit(app_id, exit_code),
        }
    }

    /// Requested: validates the manifest, rejects a duplicate resident id and
    /// the ninth resident application, then occupies the first free slot.
    pub fn request(
        &mut self,
        manifest: G8lS561ApplicationManifest,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        if let Err(error) = manifest.validate() {
            return Err(self.deny(manifest, error, 0, 0));
        }
        if self.slot(manifest.id).is_some() {
            return Err(self.deny(
                manifest,
                G8lS561ApplicationLaunchFlowError::DuplicateApplicationId,
                0,
                0,
            ));
        }
        let Some(free) = self.slots.iter().position(Option::is_none) else {
            return Err(self.deny(
                manifest,
                G8lS561ApplicationLaunchFlowError::ConcurrentApplicationLimit,
                0,
                0,
            ));
        };
        let launch_index = self.next_launch_index;
        let next = launch_index
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        let launches = self
            .launches_requested
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        self.slots[free] = Some(G8lS561LaunchSlot {
            manifest,
            launch_index,
            stage: G8lS561LaunchStage::Requested,
            granted_permissions: G8lS561PermissionSet::NONE,
            capabilities: [None; S561_PERMISSION_COUNT],
        });
        self.next_launch_index = next;
        self.launches_requested = launches;
        let active = self.active_count();
        if active > self.peak_concurrent {
            self.peak_concurrent = active;
        }
        self.record(G8lS561LaunchStepReceipt {
            app_id: manifest.id,
            launch_index,
            previous_stage_code: 0,
            stage: G8lS561LaunchStage::Requested,
            granted_permissions: 0,
            capabilities_minted: 0,
            capabilities_revoked: 0,
            exit_code: 0,
        })
    }

    /// PolicyChecked: the requested set must be a subset of the trust-class
    /// policy; escalation releases the slot and records the denied bits.
    pub fn check_policy(
        &mut self,
        app_id: u32,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        let index = self.find(app_id, G8lS561LaunchStage::Requested)?;
        let slot =
            self.slots[index].ok_or(G8lS561ApplicationLaunchFlowError::UnknownApplication)?;
        let requested = slot.manifest.validate()?;
        let allowed = slot.manifest.trust_class.allowed_permissions();
        if !requested.is_subset_of(allowed) {
            self.slots[index] = None;
            return Err(self.deny(
                slot.manifest,
                G8lS561ApplicationLaunchFlowError::PermissionEscalation,
                allowed.bits(),
                requested.difference(allowed).bits(),
            ));
        }
        self.policy_checks_passed = self
            .policy_checks_passed
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        let updated = G8lS561LaunchSlot {
            stage: G8lS561LaunchStage::PolicyChecked,
            granted_permissions: requested,
            ..slot
        };
        self.slots[index] = Some(updated);
        self.record(G8lS561LaunchStepReceipt {
            app_id,
            launch_index: slot.launch_index,
            previous_stage_code: G8lS561LaunchStage::Requested.code(),
            stage: G8lS561LaunchStage::PolicyChecked,
            granted_permissions: requested.bits(),
            capabilities_minted: 0,
            capabilities_revoked: 0,
            exit_code: 0,
        })
    }

    /// CapabilitiesMinted: one capability per granted permission, none else.
    pub fn mint_capabilities(
        &mut self,
        app_id: u32,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        let index = self.find(app_id, G8lS561LaunchStage::PolicyChecked)?;
        let mut slot =
            self.slots[index].ok_or(G8lS561ApplicationLaunchFlowError::UnknownApplication)?;
        let mut minted = 0u8;
        for permission in G8lS561Permission::ALL {
            if slot.granted_permissions.contains(permission) {
                slot.capabilities[permission.index()] = Some(G8lS561Capability {
                    capability_id: s561_capability_id(slot.launch_index, permission),
                    app_id,
                    launch_index: slot.launch_index,
                    permission,
                    revoked: false,
                });
                minted += 1;
            }
        }
        self.capabilities_minted = self
            .capabilities_minted
            .checked_add(minted as u32)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        slot.stage = G8lS561LaunchStage::CapabilitiesMinted;
        self.slots[index] = Some(slot);
        self.record(G8lS561LaunchStepReceipt {
            app_id,
            launch_index: slot.launch_index,
            previous_stage_code: G8lS561LaunchStage::PolicyChecked.code(),
            stage: G8lS561LaunchStage::CapabilitiesMinted,
            granted_permissions: slot.granted_permissions.bits(),
            capabilities_minted: minted,
            capabilities_revoked: 0,
            exit_code: 0,
        })
    }

    pub fn start(
        &mut self,
        app_id: u32,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        let index = self.find(app_id, G8lS561LaunchStage::CapabilitiesMinted)?;
        self.applications_started = self
            .applications_started
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        self.advance(index, G8lS561LaunchStage::Started)
    }

    pub fn run(
        &mut self,
        app_id: u32,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        let index = self.find(app_id, G8lS561LaunchStage::Started)?;
        self.applications_run = self
            .applications_run
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        self.advance(index, G8lS561LaunchStage::Running)
    }

    /// Exited: every live capability is revoked, the slot is released and a
    /// bounded exit record is kept.
    pub fn exit(
        &mut self,
        app_id: u32,
        exit_code: u32,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        let index = self.find(app_id, G8lS561LaunchStage::Running)?;
        let mut slot =
            self.slots[index].ok_or(G8lS561ApplicationLaunchFlowError::UnknownApplication)?;
        let mut revoked = 0u8;
        for capability in slot.capabilities.iter_mut().flatten() {
            if !capability.revoked {
                capability.revoked = true;
                revoked += 1;
            }
        }
        self.capabilities_revoked = self
            .capabilities_revoked
            .checked_add(revoked as u32)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        let exits = self
            .exit_count
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        slot.stage = G8lS561LaunchStage::Exited;
        self.exits[(self.exit_count as usize) % S561_EXIT_LOG_CAPACITY] = Some(G8lS561ExitRecord {
            app_id,
            launch_index: slot.launch_index,
            exit_code,
            capabilities_revoked: revoked,
        });
        self.exit_count = exits;
        self.slots[index] = None;
        self.record(G8lS561LaunchStepReceipt {
            app_id,
            launch_index: slot.launch_index,
            previous_stage_code: G8lS561LaunchStage::Running.code(),
            stage: G8lS561LaunchStage::Exited,
            granted_permissions: slot.granted_permissions.bits(),
            capabilities_minted: 0,
            capabilities_revoked: revoked,
            exit_code,
        })
    }

    fn find(
        &self,
        app_id: u32,
        expected: G8lS561LaunchStage,
    ) -> Result<usize, G8lS561ApplicationLaunchFlowError> {
        let index = self
            .slots
            .iter()
            .position(|slot| matches!(slot, Some(slot) if slot.manifest.id == app_id))
            .ok_or(G8lS561ApplicationLaunchFlowError::UnknownApplication)?;
        match self.slots[index] {
            Some(slot) if slot.stage == expected => Ok(index),
            _ => Err(G8lS561ApplicationLaunchFlowError::StageOrderViolation),
        }
    }

    fn advance(
        &mut self,
        index: usize,
        stage: G8lS561LaunchStage,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        let mut slot =
            self.slots[index].ok_or(G8lS561ApplicationLaunchFlowError::UnknownApplication)?;
        if slot.stage.next() != Some(stage) {
            return Err(G8lS561ApplicationLaunchFlowError::StageOrderViolation);
        }
        let previous = slot.stage;
        slot.stage = stage;
        self.slots[index] = Some(slot);
        self.record(G8lS561LaunchStepReceipt {
            app_id: slot.manifest.id,
            launch_index: slot.launch_index,
            previous_stage_code: previous.code(),
            stage,
            granted_permissions: slot.granted_permissions.bits(),
            capabilities_minted: 0,
            capabilities_revoked: 0,
            exit_code: 0,
        })
    }

    fn deny(
        &mut self,
        manifest: G8lS561ApplicationManifest,
        error: G8lS561ApplicationLaunchFlowError,
        allowed: u8,
        denied: u8,
    ) -> G8lS561ApplicationLaunchFlowError {
        self.denials[(self.denial_count as usize) % S561_DENIAL_LOG_CAPACITY] =
            Some(G8lS561DenialRecord {
                app_id: manifest.id,
                diagnostic_code: error.diagnostic_code(),
                requested_permissions: manifest.requested_permissions,
                allowed_permissions: allowed,
                denied_permissions: denied,
            });
        self.denial_count = self.denial_count.saturating_add(1);
        error
    }

    fn record(
        &mut self,
        receipt: G8lS561LaunchStepReceipt,
    ) -> Result<G8lS561LaunchStepReceipt, G8lS561ApplicationLaunchFlowError> {
        self.step_count = self
            .step_count
            .checked_add(1)
            .ok_or(G8lS561ApplicationLaunchFlowError::CounterOverflow)?;
        self.trace_checksum = s561_fnv1a_64(self.trace_checksum, &receipt.encode());
        Ok(receipt)
    }
}

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

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS561ApplicationLaunchFlowReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub command_count: usize,
    pub step_count: u32,
    pub launches_requested: u32,
    pub policy_checks_passed: u32,
    pub capabilities_minted: u32,
    pub applications_started: u32,
    pub applications_run: u32,
    pub applications_exited: u32,
    pub capabilities_revoked: u32,
    pub denials_recorded: u32,
    pub peak_concurrent_applications: u8,
    pub final_resident_applications: u8,
    pub max_concurrent_applications: u8,
    pub trace_checksum: u64,
    pub s540_physical_verdict_retained_red: bool,
    pub s543_physical_verdict_retained_red: bool,
    pub automatic_promotion: bool,
    pub hardware_present: bool,
    pub supported_profile_runtime_observations: usize,
    pub physical_observations: usize,
    pub runbook_executed: bool,
}

#[derive(Clone, Copy, Debug)]
pub struct G8lS561ApplicationLaunchFlowState {
    engine: G8lS561LaunchEngine,
    receipt: Option<G8lS561ApplicationLaunchFlowReceipt>,
}

impl G8lS561ApplicationLaunchFlowState {
    pub const fn new() -> Self {
        Self {
            engine: G8lS561LaunchEngine::new(),
            receipt: None,
        }
    }

    pub const fn receipt(&self) -> Option<G8lS561ApplicationLaunchFlowReceipt> {
        self.receipt
    }

    pub const fn engine(&self) -> &G8lS561LaunchEngine {
        &self.engine
    }
}

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

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS561ApplicationLaunchFlowOutcome {
    Published(G8lS561ApplicationLaunchFlowReceipt),
    Retained(G8lS561ApplicationLaunchFlowReceipt),
}

/// Runs a bounded command script on a fresh engine.  Any denied launch,
/// stage-order violation or malformed manifest fails the whole script closed
/// and leaves the state untouched.  An exact replay of the published script
/// is retained with the same receipt; any divergent script is rejected.
pub fn service_s561_model_launch_flow(
    state: &mut G8lS561ApplicationLaunchFlowState,
    commands: &[G8lS561LaunchCommand],
) -> Result<G8lS561ApplicationLaunchFlowOutcome, G8lS561ApplicationLaunchFlowError> {
    if commands.is_empty() {
        return Err(G8lS561ApplicationLaunchFlowError::EmptyScript);
    }
    if commands.len() > S561_MAX_SCRIPT_COMMANDS {
        return Err(G8lS561ApplicationLaunchFlowError::ScriptTooLong);
    }
    let mut engine = G8lS561LaunchEngine::new();
    for command in commands {
        engine.apply(*command)?;
    }
    let counters = engine.counters();
    let receipt = G8lS561ApplicationLaunchFlowReceipt {
        sequence: S561_SEQUENCE,
        predecessor_sequence: S561_EXPECTED_PREDECESSOR,
        r1_stage: S561_R1_STAGE,
        command_count: commands.len(),
        step_count: engine.step_count(),
        launches_requested: counters[0],
        policy_checks_passed: counters[1],
        capabilities_minted: counters[2],
        applications_started: counters[3],
        applications_run: counters[4],
        applications_exited: counters[5],
        capabilities_revoked: counters[6],
        denials_recorded: engine.denial_count(),
        peak_concurrent_applications: engine.peak_concurrent(),
        final_resident_applications: engine.active_count(),
        max_concurrent_applications: S561_MAX_CONCURRENT_APPLICATIONS as u8,
        trace_checksum: engine.trace_checksum(),
        s540_physical_verdict_retained_red: S561_S540_PHYSICAL_VERDICT_RETAINED_RED,
        s543_physical_verdict_retained_red: S561_S543_PHYSICAL_VERDICT_RETAINED_RED,
        automatic_promotion: S561_AUTOMATIC_PROMOTION,
        hardware_present: S561_HARDWARE_PRESENT,
        supported_profile_runtime_observations: S561_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
        physical_observations: S561_PHYSICAL_OBSERVATIONS,
        runbook_executed: RUNBOOK_EXECUTED_IN_S561,
    };
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(G8lS561ApplicationLaunchFlowError::PublishedStateDrift);
        }
        return Ok(G8lS561ApplicationLaunchFlowOutcome::Retained(published));
    }
    state.engine = engine;
    state.receipt = Some(receipt);
    Ok(G8lS561ApplicationLaunchFlowOutcome::Published(receipt))
}
snippet sha256: cc5be8c3b0fbfile sha256: cc5be8c3b0fb
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L760
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s561_r1_permissioned_application_launch_flow_model.rs::S561 r1 permissioned application launch flow model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s561_r1_permissioned_application_launch_flow_model::*;
use std::collections::BTreeSet;

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

/// Pinned FNV-1a 64 trace checksum of the reference script (two full
/// launches, one exit) computed by an independent reference implementation
/// of the 17-byte step-receipt encoding.
const REFERENCE_SCRIPT_TRACE_CHECKSUM: u64 = 0xc640_3643_8d60_dd56;

fn name(text: &str) -> G8lS561ApplicationName {
    G8lS561ApplicationName::from_ascii(text).unwrap()
}

fn hash(seed: u8) -> [u8; S561_ELF_HASH_BYTES] {
    let mut out = [0u8; S561_ELF_HASH_BYTES];
    for (i, byte) in out.iter_mut().enumerate() {
        *byte = seed.wrapping_add(i as u8);
    }
    out
}

fn manifest(id: u32, permissions: u8, trust: G8lS561TrustClass) -> G8lS561ApplicationManifest {
    G8lS561ApplicationManifest::new(
        id,
        name("dialer"),
        s561_pack_version(1, 2, 3),
        permissions,
        hash(id as u8),
        trust,
    )
}

fn signed_dialer() -> G8lS561ApplicationManifest {
    manifest(
        0x5601,
        G8lS561PermissionSet::DISPLAY
            .union(G8lS561PermissionSet::INPUT)
            .union(G8lS561PermissionSet::AUDIO)
            .bits(),
        G8lS561TrustClass::Signed,
    )
}

fn untrusted_viewer() -> G8lS561ApplicationManifest {
    manifest(0x5602, G8lS561PermissionSet::DISPLAY.bits(), G8lS561TrustClass::Untrusted)
}

fn reference_script() -> Vec<G8lS561LaunchCommand> {
    let mut script = s561_full_launch_commands(signed_dialer()).to_vec();
    script.extend(s561_full_launch_commands(untrusted_viewer()));
    script.push(G8lS561LaunchCommand::Exit { app_id: 0x5602, exit_code: 0 });
    script
}

fn launch_to_running(engine: &mut G8lS561LaunchEngine, manifest: G8lS561ApplicationManifest) {
    for command in s561_full_launch_commands(manifest) {
        engine.apply(command).unwrap();
    }
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S561_SEQUENCE, 561);
    assert_eq!(S561_EXPECTED_PREDECESSOR, 560);
    assert_eq!(S561_R1_STAGE, 4);
    assert_eq!(S561_R1_RANGE_FIRST, 536);
    assert_eq!(S561_R1_RANGE_LAST, 568);
    assert_eq!(S561_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S561_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S561_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S561_SD_WRITES, 0);
    assert_eq!(S561_UART_OPENS, 0);
    assert_eq!(S561_POWER_TRANSITIONS, 0);
    assert_eq!(S561_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S561_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S561_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S561_AUTOMATIC_PROMOTION);
    assert!(!S561_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S561_HARDWARE_PRESENT);
    assert!(!S561_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S561);
    assert_eq!(S561_MAX_CONCURRENT_APPLICATIONS, 8);
    assert_eq!(S561_MAX_APPLICATION_NAME_BYTES, 32);
    assert_eq!(S561_ELF_HASH_BYTES, 32);
    assert_eq!(S561_PERMISSION_COUNT, 6);
    assert_eq!(S561_MAX_SCRIPT_COMMANDS, 64);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s561_r1_permissioned_application_launch_flow_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/disk",
        "/dev/cu.",
        "diskutil",
        "dd if=",
        "TIOCEXCL",
        "crate::kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("no production callsite"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S561=NO"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    let codes: BTreeSet<_> = G8lS561ApplicationLaunchFlowError::ALL
        .into_iter()
        .map(G8lS561ApplicationLaunchFlowError::diagnostic_code)
        .collect();
    assert_eq!(codes.len(), G8lS561ApplicationLaunchFlowError::ALL.len());
    assert_eq!(codes.len(), 17);
    assert!(!codes.contains(&0));
    assert_eq!(G8lS561ApplicationLaunchFlowError::PublishedStateDrift.diagnostic_code(), 17);
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS561ApplicationLaunchFlowState::new();
    let script = reference_script();
    let G8lS561ApplicationLaunchFlowOutcome::Published(receipt) =
        service_s561_model_launch_flow(&mut state, &script).unwrap()
    else {
        panic!("first publication missing")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &script),
        Ok(G8lS561ApplicationLaunchFlowOutcome::Retained(receipt))
    );
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(state.engine().active_count(), 1);
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS561ApplicationLaunchFlowState::new();
    let script = reference_script();
    service_s561_model_launch_flow(&mut state, &script).unwrap();
    let before = state.receipt();

    // Same counts, different exit code: only the trace checksum differs.
    let mut different_exit = script.clone();
    *different_exit.last_mut().unwrap() = G8lS561LaunchCommand::Exit { app_id: 0x5602, exit_code: 7 };
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &different_exit),
        Err(G8lS561ApplicationLaunchFlowError::PublishedStateDrift)
    );

    // One command fewer.
    let shorter = &script[..script.len() - 1];
    assert_eq!(
        service_s561_model_launch_flow(&mut state, shorter),
        Err(G8lS561ApplicationLaunchFlowError::PublishedStateDrift)
    );

    // Reordered launches (same multiset of commands).
    let mut reordered = s561_full_launch_commands(untrusted_viewer()).to_vec();
    reordered.extend(s561_full_launch_commands(signed_dialer()));
    reordered.push(G8lS561LaunchCommand::Exit { app_id: 0x5602, exit_code: 0 });
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &reordered),
        Err(G8lS561ApplicationLaunchFlowError::PublishedStateDrift)
    );
    assert_eq!(state.receipt(), before);
}

#[test]
fn permission_bits_and_policy_table_are_exact() {
    assert_eq!(G8lS561Permission::Display.bit(), 1);
    assert_eq!(G8lS561Permission::Input.bit(), 2);
    assert_eq!(G8lS561Permission::Modem.bit(), 4);
    assert_eq!(G8lS561Permission::Audio.bit(), 8);
    assert_eq!(G8lS561Permission::Storage.bit(), 16);
    assert_eq!(G8lS561Permission::Network.bit(), 32);
    assert_eq!(G8lS561PermissionSet::ALL.bits(), 63);
    assert_eq!(G8lS561PermissionSet::ALL.count(), 6);
    assert_eq!(G8lS561PermissionSet::from_bits(64), None);
    assert_eq!(G8lS561PermissionSet::from_bits(0b1000_0001), None);
    assert_eq!(G8lS561PermissionSet::from_bits(63).map(|s| s.bits()), Some(63));
    for (i, permission) in G8lS561Permission::ALL.into_iter().enumerate() {
        assert_eq!(permission.index(), i);
        assert_eq!(permission.bit(), 1 << i);
    }
    assert_eq!(G8lS561TrustClass::Lab.allowed_permissions(), G8lS561PermissionSet::ALL);
    assert_eq!(
        G8lS561TrustClass::Signed.allowed_permissions(),
        G8lS561PermissionSet::ALL.difference(G8lS561PermissionSet::MODEM)
    );
    assert_eq!(G8lS561TrustClass::Signed.allowed_permissions().bits(), 0b11_1011);
    assert_eq!(
        G8lS561TrustClass::Untrusted.allowed_permissions(),
        G8lS561PermissionSet::DISPLAY.union(G8lS561PermissionSet::INPUT)
    );
    assert!(!G8lS561TrustClass::Signed.allowed_permissions().contains(G8lS561Permission::Modem));
    assert!(!G8lS561TrustClass::Untrusted.allowed_permissions().contains(G8lS561Permission::Storage));
    assert_eq!(S561_POLICY_TABLE.len(), 3);
    assert_eq!(
        [G8lS561TrustClass::Lab.code(), G8lS561TrustClass::Signed.code(), G8lS561TrustClass::Untrusted.code()],
        [1, 2, 3]
    );
    assert_eq!(s561_pack_version(1, 2, 3), 0x0001_0203);
    assert_eq!(s561_pack_version(255, 255, 255), 0x00ff_ffff);
}

#[test]
fn full_pipeline_walks_requested_to_exited_with_receipts() {
    let mut engine = G8lS561LaunchEngine::new();
    let app = signed_dialer();
    let expected = [
        (0, G8lS561LaunchStage::Requested, 0u8),
        (1, G8lS561LaunchStage::PolicyChecked, 0b00_1011),
        (2, G8lS561LaunchStage::CapabilitiesMinted, 0b00_1011),
        (3, G8lS561LaunchStage::Started, 0b00_1011),
        (4, G8lS561LaunchStage::Running, 0b00_1011),
    ];
    for ((previous, stage, granted), command) in
        expected.into_iter().zip(s561_full_launch_commands(app))
    {
        let receipt = engine.apply(command).unwrap();
        assert_eq!(receipt.app_id, app.id);
        assert_eq!(receipt.launch_index, 1);
        assert_eq!(receipt.previous_stage_code, previous);
        assert_eq!(receipt.stage, stage);
        assert_eq!(receipt.granted_permissions, granted);
        assert_eq!(engine.slot(app.id).unwrap().stage, stage);
    }
    let exit = engine.exit(app.id, 0x2a).unwrap();
    assert_eq!(exit.previous_stage_code, 5);
    assert_eq!(exit.stage, G8lS561LaunchStage::Exited);
    assert_eq!(exit.stage.code(), 6);
    assert_eq!(exit.exit_code, 0x2a);
    assert_eq!(exit.capabilities_revoked, 3);
    assert_eq!(engine.step_count(), 6);
    assert_eq!(engine.counters(), [1, 1, 3, 1, 1, 1, 3]);
    assert_eq!(engine.slot(app.id), None);
    assert_eq!(G8lS561LaunchStage::Exited.next(), None);
    assert_eq!(G8lS561LaunchStage::Requested.next(), Some(G8lS561LaunchStage::PolicyChecked));
}

#[test]
fn capabilities_are_minted_only_for_granted_permissions() {
    let mut engine = G8lS561LaunchEngine::new();
    let app = signed_dialer();
    engine.request(app).unwrap();
    assert_eq!(engine.slot(app.id).unwrap().live_capability_count(), 0);
    engine.check_policy(app.id).unwrap();
    assert_eq!(engine.slot(app.id).unwrap().live_capability_count(), 0);
    let minted = engine.mint_capabilities(app.id).unwrap();
    assert_eq!(minted.capabilities_minted, 3);
    let slot = engine.slot(app.id).unwrap();
    assert_eq!(slot.live_capability_count(), 3);
    for permission in [G8lS561Permission::Display, G8lS561Permission::Input, G8lS561Permission::Audio] {
        let capability = engine.capability(app.id, permission).unwrap();
        assert_eq!(capability.capability_id, s561_capability_id(1, permission));
        assert_eq!(capability.capability_id, (1u64 << 8) | permission.bit() as u64);
        assert_eq!(capability.app_id, app.id);
        assert_eq!(capability.launch_index, 1);
        assert_eq!(capability.permission, permission);
        assert!(!capability.revoked);
    }
    for permission in [G8lS561Permission::Modem, G8lS561Permission::Storage, G8lS561Permission::Network] {
        assert_eq!(engine.capability(app.id, permission), None);
    }
    assert_eq!(engine.counters()[2], 3);
}

#[test]
fn lab_trust_class_is_granted_every_permission_and_zero_request_mints_nothing() {
    let mut engine = G8lS561LaunchEngine::new();
    let lab = manifest(0x5610, G8lS561PermissionSet::ALL.bits(), G8lS561TrustClass::Lab);
    launch_to_running(&mut engine, lab);
    assert_eq!(engine.slot(lab.id).unwrap().live_capability_count(), 6);
    assert_eq!(engine.slot(lab.id).unwrap().granted_permissions, G8lS561PermissionSet::ALL);
    let ids: BTreeSet<u64> = G8lS561Permission::ALL
        .into_iter()
        .map(|permission| engine.capability(lab.id, permission).unwrap().capability_id)
        .collect();
    assert_eq!(ids.len(), 6);

    let none = manifest(0x5611, 0, G8lS561TrustClass::Untrusted);
    launch_to_running(&mut engine, none);
    assert_eq!(engine.slot(none.id).unwrap().live_capability_count(), 0);
    assert_eq!(engine.counters()[2], 6);
    let exit = engine.exit(none.id, 0).unwrap();
    assert_eq!(exit.capabilities_revoked, 0);
}

#[test]
fn permission_escalation_is_denied_with_reason_and_slot_released() {
    let mut engine = G8lS561LaunchEngine::new();
    let requested = G8lS561PermissionSet::DISPLAY
        .union(G8lS561PermissionSet::MODEM)
        .union(G8lS561PermissionSet::NETWORK);
    let escalating = manifest(0x5620, requested.bits(), G8lS561TrustClass::Untrusted);
    engine.request(escalating).unwrap();
    assert_eq!(engine.active_count(), 1);
    assert_eq!(
        engine.check_policy(escalating.id),
        Err(G8lS561ApplicationLaunchFlowError::PermissionEscalation)
    );
    assert_eq!(engine.active_count(), 0);
    assert_eq!(engine.slot(escalating.id), None);
    assert_eq!(engine.denial_count(), 1);
    assert_eq!(
        engine.denial(0),
        Some(G8lS561DenialRecord {
            app_id: 0x5620,
            diagnostic_code: 13,
            requested_permissions: 0b10_0101,
            allowed_permissions: 0b00_0011,
            denied_permissions: 0b10_0100,
        })
    );
    assert_eq!(engine.counters()[1], 0);
    assert_eq!(engine.counters()[2], 0);

    // Signed may not hold MODEM either, even when everything else is allowed.
    let signed_modem = manifest(0x5621, G8lS561PermissionSet::MODEM.bits(), G8lS561TrustClass::Signed);
    engine.request(signed_modem).unwrap();
    assert_eq!(
        engine.check_policy(signed_modem.id),
        Err(G8lS561ApplicationLaunchFlowError::PermissionEscalation)
    );
    assert_eq!(engine.denial(1).unwrap().denied_permissions, 0b00_0100);

    // Lab may hold MODEM.
    let lab_modem = manifest(0x5622, G8lS561PermissionSet::MODEM.bits(), G8lS561TrustClass::Lab);
    engine.request(lab_modem).unwrap();
    assert!(engine.check_policy(lab_modem.id).is_ok());
    assert_eq!(engine.denial_count(), 2);

    // A denied launch inside a service script fails the whole script closed.
    let mut state = G8lS561ApplicationLaunchFlowState::new();
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &s561_full_launch_commands(escalating)),
        Err(G8lS561ApplicationLaunchFlowError::PermissionEscalation)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn manifest_errors_fail_closed_and_are_logged() {
    let base = signed_dialer();
    let mut long_name = [b'a'; 32];
    long_name[0] = b'z';
    let mut padded = [0u8; 32];
    padded[..3].copy_from_slice(b"abc");
    padded[31] = 1;
    let mut control = [0u8; 32];
    control[..3].copy_from_slice(b"a\nb");
    let cases: [(G8lS561ApplicationManifest, G8lS561ApplicationLaunchFlowError); 8] = [
        (
            G8lS561ApplicationManifest { id: 0, ..base },
            G8lS561ApplicationLaunchFlowError::ManifestZeroId,
        ),
        (
            G8lS561ApplicationManifest { name: G8lS561ApplicationName::from_raw([0; 32], 0), ..base },
            G8lS561ApplicationLaunchFlowError::ManifestEmptyName,
        ),
        (
            G8lS561ApplicationManifest { name: G8lS561ApplicationName::from_raw(long_name, 33), ..base },
            G8lS561ApplicationLaunchFlowError::ManifestNameTooLong,
        ),
        (
            G8lS561ApplicationManifest { name: G8lS561ApplicationName::from_raw(control, 3), ..base },
            G8lS561ApplicationLaunchFlowError::ManifestNameNotAscii,
        ),
        (
            G8lS561ApplicationManifest { name: G8lS561ApplicationName::from_raw(padded, 3), ..base },
            G8lS561ApplicationLaunchFlowError::ManifestNamePaddingNotZero,
        ),
        (
            G8lS561ApplicationManifest { version: 0, ..base },
            G8lS561ApplicationLaunchFlowError::ManifestZeroVersion,
        ),
        (
            G8lS561ApplicationManifest { requested_permissions: 0b0100_0001, ..base },
            G8lS561ApplicationLaunchFlowError::ManifestUnknownPermissionBits,
        ),
        (
            G8lS561ApplicationManifest { elf_hash: [0; 32], ..base },
            G8lS561ApplicationLaunchFlowError::ManifestZeroElfHash,
        ),
    ];
    let mut engine = G8lS561LaunchEngine::new();
    for (i, (bad, error)) in cases.into_iter().enumerate() {
        assert_eq!(bad.validate(), Err(error));
        assert_eq!(engine.request(bad), Err(error));
        assert_eq!(engine.active_count(), 0);
        assert_eq!(engine.step_count(), 0);
        assert_eq!(engine.denial_count() as usize, i + 1);
        let denial = engine.denial(i).unwrap();
        assert_eq!(denial.diagnostic_code, error.diagnostic_code());
        assert_eq!(denial.app_id, bad.id);
        assert_eq!(denial.requested_permissions, bad.requested_permissions);
    }
    // Ninth denial wraps the bounded ring onto index 0.
    assert_eq!(
        engine.request(G8lS561ApplicationManifest { id: 0, ..base }),
        Err(G8lS561ApplicationLaunchFlowError::ManifestZeroId)
    );
    assert_eq!(engine.denial_count(), 9);
    assert_eq!(engine.denial(0).unwrap().diagnostic_code, 3);
    assert_eq!(engine.denial(8), None);
    assert_eq!(base.validate(), Ok(G8lS561PermissionSet::from_bits(0b00_1011).unwrap()));
}

#[test]
fn name_encoding_boundaries_are_exact() {
    let exact = "a".repeat(32);
    let name32 = G8lS561ApplicationName::from_ascii(&exact).unwrap();
    assert_eq!(name32.len(), 32);
    assert_eq!(name32.as_bytes(), exact.as_bytes());
    assert_eq!(name32.validate(), Ok(()));
    assert_eq!(G8lS561ApplicationName::from_ascii(&"a".repeat(33)), None);
    assert_eq!(G8lS561ApplicationName::from_ascii(""), None);
    assert_eq!(G8lS561ApplicationName::from_ascii("tab\there"), None);
    assert_eq!(G8lS561ApplicationName::from_ascii("çağrı"), None);
    assert_eq!(G8lS561ApplicationName::from_ascii("del\x7f"), None);
    let spaced = G8lS561ApplicationName::from_ascii("Sesli Arama ~!").unwrap();
    assert_eq!(spaced.len(), 14);
    assert!(!spaced.is_empty());
    assert_eq!(&spaced.raw_bytes()[14..], &[0u8; 18]);
    // A raw name whose declared length exceeds the buffer is clamped when
    // read as bytes and rejected by validation.
    let raw = G8lS561ApplicationName::from_raw([b'x'; 32], 200);
    assert_eq!(raw.as_bytes().len(), 32);
    assert_eq!(raw.validate(), Err(G8lS561ApplicationLaunchFlowError::ManifestNameTooLong));
}

#[test]
fn duplicate_id_fails_closed_while_resident_and_relaunch_after_exit_succeeds() {
    let mut engine = G8lS561LaunchEngine::new();
    let app = signed_dialer();
    engine.request(app).unwrap();
    for stage_command in [
        G8lS561LaunchCommand::CheckPolicy(app.id),
        G8lS561LaunchCommand::MintCapabilities(app.id),
        G8lS561LaunchCommand::Start(app.id),
        G8lS561LaunchCommand::Run(app.id),
    ] {
        // Same id (even with a different manifest body) is rejected at every
        // resident stage.
        let different_body = G8lS561ApplicationManifest { version: 9, ..app };
        assert_eq!(
            engine.request(different_body),
            Err(G8lS561ApplicationLaunchFlowError::DuplicateApplicationId)
        );
        engine.apply(stage_command).unwrap();
    }
    assert_eq!(engine.denial_count(), 4);
    assert_eq!(engine.denial(0).unwrap().diagnostic_code, 11);
    assert_eq!(engine.active_count(), 1);

    let first_display = engine.capability(app.id, G8lS561Permission::Display).unwrap();
    engine.exit(app.id, 0).unwrap();
    let relaunch = engine.request(app).unwrap();
    assert_eq!(relaunch.launch_index, 2);
    engine.check_policy(app.id).unwrap();
    engine.mint_capabilities(app.id).unwrap();
    let second_display = engine.capability(app.id, G8lS561Permission::Display).unwrap();
    assert_ne!(first_display.capability_id, second_display.capability_id);
    assert_eq!(second_display.capability_id, s561_capability_id(2, G8lS561Permission::Display));
    assert!(!second_display.revoked);

    let mut state = G8lS561ApplicationLaunchFlowState::new();
    let script = [G8lS561LaunchCommand::Request(app), G8lS561LaunchCommand::Request(app)];
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &script),
        Err(G8lS561ApplicationLaunchFlowError::DuplicateApplicationId)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn concurrency_limit_of_eight_is_enforced() {
    let mut engine = G8lS561LaunchEngine::new();
    for i in 0..8u32 {
        let app = manifest(0x5630 + i, G8lS561PermissionSet::DISPLAY.bits(), G8lS561TrustClass::Untrusted);
        launch_to_running(&mut engine, app);
    }
    assert_eq!(engine.active_count(), 8);
    assert_eq!(engine.peak_concurrent(), 8);
    let ninth = manifest(0x5640, G8lS561PermissionSet::DISPLAY.bits(), G8lS561TrustClass::Untrusted);
    assert_eq!(
        engine.request(ninth),
        Err(G8lS561ApplicationLaunchFlowError::ConcurrentApplicationLimit)
    );
    assert_eq!(engine.denial(0).unwrap().diagnostic_code, 12);
    assert_eq!(engine.active_count(), 8);
    assert_eq!(engine.next_launch_index(), 9);

    engine.exit(0x5633, 1).unwrap();
    assert_eq!(engine.active_count(), 7);
    let receipt = engine.request(ninth).unwrap();
    assert_eq!(receipt.launch_index, 9);
    assert_eq!(engine.active_count(), 8);
    assert_eq!(engine.peak_concurrent(), 8);
    assert_eq!(engine.exit_record(0).unwrap().app_id, 0x5633);
    assert_eq!(engine.exit_record(0).unwrap().launch_index, 4);
}

#[test]
fn exit_revokes_every_capability_and_frees_the_slot() {
    let mut engine = G8lS561LaunchEngine::new();
    let lab = manifest(0x5650, G8lS561PermissionSet::ALL.bits(), G8lS561TrustClass::Lab);
    launch_to_running(&mut engine, lab);
    let live_before: Vec<G8lS561Capability> = G8lS561Permission::ALL
        .into_iter()
        .map(|permission| engine.capability(lab.id, permission).unwrap())
        .collect();
    assert!(live_before.iter().all(|capability| !capability.revoked));
    let exit = engine.exit(lab.id, 0xdead).unwrap();
    assert_eq!(exit.capabilities_revoked, 6);
    assert_eq!(exit.granted_permissions, 63);
    assert_eq!(engine.slot(lab.id), None);
    assert_eq!(engine.capability(lab.id, G8lS561Permission::Modem), None);
    assert_eq!(engine.counters()[6], 6);
    assert_eq!(engine.exit_count(), 1);
    assert_eq!(
        engine.exit_record(0),
        Some(G8lS561ExitRecord { app_id: 0x5650, launch_index: 1, exit_code: 0xdead, capabilities_revoked: 6 })
    );
    assert_eq!(engine.exit_record(1), None);
    assert_eq!(engine.exit_record(8), None);
    // A second exit of the same id is an unknown application.
    assert_eq!(
        engine.exit(lab.id, 0),
        Err(G8lS561ApplicationLaunchFlowError::UnknownApplication)
    );
    // Exit log wraps after eight exits.
    for i in 0..8u32 {
        let app = manifest(0x5660 + i, 0, G8lS561TrustClass::Untrusted);
        launch_to_running(&mut engine, app);
        engine.exit(app.id, i).unwrap();
    }
    assert_eq!(engine.exit_count(), 9);
    assert_eq!(engine.exit_record(0).unwrap().app_id, 0x5667);
    assert_eq!(engine.exit_record(1).unwrap().app_id, 0x5660);
}

#[test]
fn stage_order_violations_fail_closed() {
    let mut engine = G8lS561LaunchEngine::new();
    let app = signed_dialer();
    let violation = Err(G8lS561ApplicationLaunchFlowError::StageOrderViolation);
    let unknown = Err(G8lS561ApplicationLaunchFlowError::UnknownApplication);
    assert_eq!(engine.check_policy(app.id), unknown);
    assert_eq!(engine.exit(app.id, 0), unknown);
    engine.request(app).unwrap();
    assert_eq!(engine.mint_capabilities(app.id), violation);
    assert_eq!(engine.start(app.id), violation);
    assert_eq!(engine.run(app.id), violation);
    assert_eq!(engine.exit(app.id, 0), violation);
    engine.check_policy(app.id).unwrap();
    assert_eq!(engine.check_policy(app.id), violation);
    assert_eq!(engine.start(app.id), violation);
    engine.mint_capabilities(app.id).unwrap();
    assert_eq!(engine.mint_capabilities(app.id), violation);
    assert_eq!(engine.run(app.id), violation);
    engine.start(app.id).unwrap();
    assert_eq!(engine.start(app.id), violation);
    assert_eq!(engine.exit(app.id, 0), violation);
    engine.run(app.id).unwrap();
    assert_eq!(engine.run(app.id), violation);
    assert_eq!(engine.check_policy(app.id), violation);
    assert_eq!(engine.step_count(), 5);
    assert_eq!(engine.denial_count(), 0);
    engine.exit(app.id, 0).unwrap();
    assert_eq!(engine.step_count(), 6);

    let mut state = G8lS561ApplicationLaunchFlowState::new();
    let script = [G8lS561LaunchCommand::Request(app), G8lS561LaunchCommand::Start(app.id)];
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &script),
        Err(G8lS561ApplicationLaunchFlowError::StageOrderViolation)
    );
    let script = [G8lS561LaunchCommand::Run(0x9999)];
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &script),
        Err(G8lS561ApplicationLaunchFlowError::UnknownApplication)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn script_bounds_and_counter_overflow_fail_closed() {
    let mut state = G8lS561ApplicationLaunchFlowState::new();
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &[]),
        Err(G8lS561ApplicationLaunchFlowError::EmptyScript)
    );
    let too_long = vec![G8lS561LaunchCommand::Request(signed_dialer()); 65];
    assert_eq!(
        service_s561_model_launch_flow(&mut state, &too_long),
        Err(G8lS561ApplicationLaunchFlowError::ScriptTooLong)
    );
    assert_eq!(state.receipt(), None);

    // Exactly 64 commands are accepted: eight full launches, then eight exits
    // (8 * 5 + 8 = 48) padded with 16 more launch/exit pairs on one slot.
    let mut script = Vec::new();
    for i in 0..8u32 {
        script.extend(s561_full_launch_commands(manifest(0x5670 + i, 0, G8lS561TrustClass::Untrusted)));
    }
    for i in 0..8u32 {
        script.push(G8lS561LaunchCommand::Exit { app_id: 0x5670 + i, exit_code: 0 });
    }
    let filler = manifest(0x5680, 0, G8lS561TrustClass::Untrusted);
    while script.len() < 64 {
        script.push(G8lS561LaunchCommand::Request(filler));
        script.push(G8lS561LaunchCommand::CheckPolicy(filler.id));
        script.push(G8lS561LaunchCommand::MintCapabilities(filler.id));
        script.push(G8lS561LaunchCommand::Start(filler.id));
        script.push(G8lS561LaunchCommand::Run(filler.id));
        script.push(G8lS561LaunchCommand::Exit { app_id: filler.id, exit_code: 0 });
        if script.len() > 64 {
            script.truncate(64);
        }
    }
    assert_eq!(script.len(), 64);
    let G8lS561ApplicationLaunchFlowOutcome::Published(receipt) =
        service_s561_model_launch_flow(&mut state, &script).unwrap()
    else {
        panic!("64-command script must publish")
    };
    assert_eq!(receipt.command_count, 64);
    assert_eq!(receipt.step_count, 64);
    assert_eq!(receipt.peak_concurrent_applications, 8);

    let mut engine = G8lS561LaunchEngine::with_next_launch_index(u32::MAX - 1);
    let last = engine.request(manifest(0x5690, 0, G8lS561TrustClass::Lab)).unwrap();
    assert_eq!(last.launch_index, u32::MAX - 1);
    assert_eq!(engine.next_launch_index(), u32::MAX);
    // The launch that would exhaust the index space fails closed and
    // mutates nothing.
    assert_eq!(
        engine.request(manifest(0x5691, 0, G8lS561TrustClass::Lab)),
        Err(G8lS561ApplicationLaunchFlowError::CounterOverflow)
    );
    assert_eq!(engine.active_count(), 1);
    assert_eq!(engine.next_launch_index(), u32::MAX);
}

#[test]
fn fnv1a_64_matches_reference_vectors_and_step_receipt_encoding() {
    assert_eq!(s561_fnv1a_64(S561_FNV1A_OFFSET_BASIS, b""), 0xcbf2_9ce4_8422_2325);
    assert_eq!(s561_fnv1a_64(S561_FNV1A_OFFSET_BASIS, b"a"), 0xaf63_dc4c_8601_ec8c);
    assert_eq!(s561_fnv1a_64(S561_FNV1A_OFFSET_BASIS, b"foobar"), 0x8594_4171_f739_67e8);
    let receipt = G8lS561LaunchStepReceipt {
        app_id: 0x0403_0201,
        launch_index: 0x0807_0605,
        previous_stage_code: 5,
        stage: G8lS561LaunchStage::Exited,
        granted_permissions: 0b11_1111,
        capabilities_minted: 0,
        capabilities_revoked: 6,
        exit_code: 0x0d0c_0b0a,
    };
    assert_eq!(
        receipt.encode(),
        [1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 63, 0, 6, 0x0a, 0x0b, 0x0c, 0x0d]
    );
    assert_eq!(S561_STEP_RECEIPT_BYTES, 17);
}

#[test]
fn reference_script_pins_exact_trace_checksum_and_counts() {
    let mut state = G8lS561ApplicationLaunchFlowState::new();
    let G8lS561ApplicationLaunchFlowOutcome::Published(receipt) =
        service_s561_model_launch_flow(&mut state, &reference_script()).unwrap()
    else {
        panic!("reference script must publish")
    };
    assert_eq!(receipt.command_count, 11);
    assert_eq!(receipt.step_count, 11);
    assert_eq!(receipt.launches_requested, 2);
    assert_eq!(receipt.policy_checks_passed, 2);
    assert_eq!(receipt.capabilities_minted, 4);
    assert_eq!(receipt.applications_started, 2);
    assert_eq!(receipt.applications_run, 2);
    assert_eq!(receipt.applications_exited, 1);
    assert_eq!(receipt.capabilities_revoked, 1);
    assert_eq!(receipt.denials_recorded, 0);
    assert_eq!(receipt.peak_concurrent_applications, 2);
    assert_eq!(receipt.final_resident_applications, 1);
    assert_eq!(receipt.trace_checksum, REFERENCE_SCRIPT_TRACE_CHECKSUM);
    // Independent re-fold of the same eleven step receipts.
    let mut engine = G8lS561LaunchEngine::new();
    let mut folded = S561_FNV1A_OFFSET_BASIS;
    for command in reference_script() {
        let step = engine.apply(command).unwrap();
        folded = s561_fnv1a_64(folded, &step.encode());
    }
    assert_eq!(folded, REFERENCE_SCRIPT_TRACE_CHECKSUM);
    assert_eq!(engine.trace_checksum(), REFERENCE_SCRIPT_TRACE_CHECKSUM);
    assert_eq!(state.engine().trace_checksum(), REFERENCE_SCRIPT_TRACE_CHECKSUM);
    assert_eq!(state.engine().slot(0x5601).unwrap().stage, G8lS561LaunchStage::Running);
    assert_eq!(state.engine().slot(0x5602), None);
}

#[test]
fn receipt_carries_scope_limits_and_zero_claims() {
    let mut state = G8lS561ApplicationLaunchFlowState::default();
    let G8lS561ApplicationLaunchFlowOutcome::Published(receipt) =
        service_s561_model_launch_flow(&mut state, &reference_script()).unwrap()
    else {
        panic!("reference script must publish")
    };
    assert_eq!(receipt.sequence, 561);
    assert_eq!(receipt.predecessor_sequence, 560);
    assert_eq!(receipt.r1_stage, 4);
    assert_eq!(receipt.max_concurrent_applications, 8);
    assert!(receipt.s540_physical_verdict_retained_red);
    assert!(receipt.s543_physical_verdict_retained_red);
    assert!(!receipt.automatic_promotion);
    assert!(!receipt.hardware_present);
    assert_eq!(receipt.supported_profile_runtime_observations, 0);
    assert_eq!(receipt.physical_observations, 0);
    assert!(!receipt.runbook_executed);
    let fresh = G8lS561LaunchEngine::default();
    assert_eq!(fresh, G8lS561LaunchEngine::new());
    assert_eq!(fresh.trace_checksum(), S561_FNV1A_OFFSET_BASIS);
    assert_eq!(fresh.next_launch_index(), 1);
}

#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
    assert!(SOURCE.contains("S561_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S561_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S561_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
    assert!(SOURCE.contains("S561_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S561_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
    assert!(SOURCE.contains("S561_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S561: bool = false"));
    assert!(SOURCE.contains("S561_MAX_CONCURRENT_APPLICATIONS: usize = 8"));
}
snippet sha256: 1636d7fee865file sha256: 1636d7fee865
03 · Kapı kimlik kaydı

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

tam Operations kaydıL2245–L2305
website/src/lib/operations.ts::g8l-s561-r1-permissioned-application-launch-flow-model
  {
    id: "g8l-s561-r1-permissioned-application-launch-flow-model",
    date: "2026-08-30",
    sequence: 561,
    status: "passed",
    umbrella_status: "partial",
    title: "S561 · R1 uygulama: izinli uygulama başlatma akışı modeli",
    summary:
      "S561 kaynak/host model kapısı PASS'tir: uygulama manifest'i (nonzero id, ≤32 bayt yazdırılabilir ASCII ad, paketli sürüm, {DISPLAY, INPUT, MODEM, AUDIO, STORAGE, NETWORK} izin bitset'i, 32 bayt ELF hash, güven sınıfı), güven sınıfı başına izin veren tablo güdümlü politika (Lab=hepsi, Signed=MODEM hariç hepsi, Untrusted=yalnız DISPLAY+INPUT) ve Requested→PolicyChecked→CapabilitiesMinted→Started→Running→Exited başlatma hattı receipt'lerle modellenmiştir. Capability'ler yalnız verilen izinler için deterministik id'lerle mint edilir, çıkışta tamamı revoke edilir ve slot serbest bırakılır; aynı anda en fazla 8 uygulama residenttir ve dokuzuncu istek reddedilir. Manifest hatası, izin escalation'ı, duplicate id, sıra dışı aşama geçişi ve 64 komut üstü script fail-closed'dur; referans script trace checksum'ı 0xc64036438d60dd56 olarak sabitlenmiştir. Focused 22/22 PASS'tir; hiçbir ELF loader, scheduler task, IPC endpoint veya production çağrı noktası yoktur. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; physical observation=0, RUNBOOK_EXECUTED_IN_S561=NO, Boot-to-UI=false ve R1 acceptance=false'dur. S562, S561 başlatma hattı şekilleri üzerine kurulan host-only servis kill/restart süpervizyon modeli kapısıdır.",
    evidence: [
      "S561, S560'tan ayrı kaynak modülü, 22-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; production callsite eklenmemiştir.",
      "Dar S561 source/host status=PASS; R1 umbrella=PARTIAL, R1 aşaması 4 (uygulama, recovery ve update gösterimi); S540 ve S543 physical gate status=RED olarak ayrı tutulur.",
      "Manifest doğrulaması fail-closed'dur: sıfır id, boş/32 bayt üstü/yazdırılamaz veya ASCII dışı/sıfır dolgusuz ad, sıfır sürüm, bilinmeyen izin biti ve tamamı sıfır ELF hash ManifestZeroId..ManifestZeroElfHash kodlarıyla reddedilir ve bounded denial log'una işlenir.",
      "İzin bitset'i exact'tır: DISPLAY=1, INPUT=2, MODEM=4, AUDIO=8, STORAGE=16, NETWORK=32; tüm bitler 0b111111'dir ve 63 üstü bit ManifestUnknownPermissionBits verir.",
      "Politika tablosu exact'tır: Lab 0b111111, Signed 0b111011 (MODEM asla verilmez), Untrusted 0b000011; istenen küme izin kümesinin alt kümesi değilse PermissionEscalation ile slot serbest bırakılır ve requested/allowed/denied bitleri denial kaydına yazılır.",
      "Başlatma hattı exact sıra Requested(1)→PolicyChecked(2)→CapabilitiesMinted(3)→Started(4)→Running(5)→Exited(6) biçimindedir; her geçiş 17 baytlık kodlanmış G8lS561LaunchStepReceipt döndürür ve FNV-1a 64 trace checksum'ına katlanır; sıra dışı her geçiş StageOrderViolation, bilinmeyen id UnknownApplication verir.",
      "Capability mint yalnız verilen izinler içindir: capability id deterministik (launch_index << 8) | permission_bit'tir; Signed dialer örneği 3, Lab tam-izin örneği 6, sıfır-izin örneği 0 capability üretir.",
      "Çıkışta canlı capability'lerin tamamı revoke edilir, slot serbest kalır ve bounded exit kaydı (8'lik ring) app_id/launch_index/exit_code/revoked sayısını tutar; aynı id yeni launch_index ve yeni capability id'leriyle yeniden başlatılabilir.",
      "Eşzamanlılık sınırı 8'dir: dokuzuncu istek ConcurrentApplicationLimit ile reddedilir ve denial log'una yazılır; bir uygulama çıktıktan sonra aynı istek kabul edilir; peak concurrency receipt'te taşınır.",
      "Duplicate id denetimi resident her aşamada uygulanır: Requested..Running arasındaki bir id yeniden istenirse DuplicateApplicationId döner; farklı manifest gövdesi bunu aşamaz.",
      "service_s561_model_launch_flow 1..=64 komutluk script'i taze engine üzerinde koşar; boş script EmptyScript, 65 komut ScriptTooLong verir; exact tekrar aynı receipt ile Retained döner ve yayın sonrası farklı exit kodu, eksik komut veya yeniden sıralanmış script PublishedStateDrift ile reddedilir.",
      "Referans script (iki tam başlatma + bir çıkış, 11 adım) exact sayaçları ve trace checksum 0xc64036438d60dd56 değerini sabitler; FNV-1a 64 referans vektörleri (boş, 'a', 'foobar') ayrıca doğrulanır.",
      "On yedi hata kodu sıfırdan farklı ve benzersizdir; launch index ve tüm sayaçlar checked aritmetikle taşmaya karşı CounterOverflow ile korunur.",
      "Focused target 1 grup / 22 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 37668 B / cc5be8c3b0fb0e44b9de71ea3da635f0218b7ecd57ba9d0ab9a58e01c0b9de22; focused test 32223 B / 1636d7fee86573244082da7be988a2d602d9af5c0df490dea896246ce36d24e7 SHA-256'dır.",
      "Proof 5056 B'dir.",
      "S540 immutable raw 20525 B ve S543 immutable raw 20509 B byte-exact korunur; her ikisi RED, automatic promotion=false ve rerun=false'dur.",
      "S561 sırasında ELF loader, scheduler task, IPC endpoint, gerçek capability tablosu, SD write/read-back/eject, UART open/capture, power transition veya yeni immutable raw üretimi yapılmadı.",
      "RUNBOOK_EXECUTED_IN_S561=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S562 yalnız host üzerinde S561 başlatma hattı şekilleri üzerine servis kill/restart süpervizyon modelini kuracaktır; 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_s561_r1_permissioned_application_launch_flow_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s561-focused",
        title: "S561 izinli uygulama başlatma akışı focused kabulü",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s561_r1_permissioned_application_launch_flow_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 22 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S561 focused=1 group / 22 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S561 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S561 yalnız host üzerinde derlenen ve focused testle sürülen bir modeldir; hiçbir donanım/panel/modem/board gözlemi yoktur.",
      "Model hiçbir gerçek ELF yüklemez, task başlatmaz, IPC endpoint açmaz veya gerçek capability tablosuna dokunmaz; başlatma hattı yalnız receipt ve checksum assertion'ları ile doğrulanır.",
      "Modül hiçbir boot, IRQ, scheduler veya driver yoluna bağlanmamıştır; production callsite wired=false'dur.",
      "S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşu ayrı bir kapıdır ve kararı burada varsayılmaz.",
      "Boot-to-UI fiziksel olarak gözlenmedi; Boot-to-UI ve R1 acceptance false kalır.",
      "S562 host-only servis kill/restart süpervizyon modeli tamamlanmadan uygulama yaşam döngüsü yolu için yeni bir fiziksel aday yoktur.",
    ],
  },
snippet sha256: 2665c83f896efile 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_s561_r1_permissioned_application_launch_flow_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S561-R1-Permissioned-Application-Launch-Flow-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06