diff --git a/product-source/hololake-platform/docs/GUANGHU-OS-COGNITIVE-CONTROL-EXECUTION-ARCHITECTURE.md b/product-source/hololake-platform/docs/GUANGHU-OS-COGNITIVE-CONTROL-EXECUTION-ARCHITECTURE.md index 048eb60..0caa4d5 100644 --- a/product-source/hololake-platform/docs/GUANGHU-OS-COGNITIVE-CONTROL-EXECUTION-ARCHITECTURE.md +++ b/product-source/hololake-platform/docs/GUANGHU-OS-COGNITIVE-CONTROL-EXECUTION-ARCHITECTURE.md @@ -105,6 +105,11 @@ - 执行后目标读回,随后停止或冻结副控环境并登记资源回收回执; - 独立 Linux 救援槽保留,不受日常副控生命周期影响。 +第一份源码合同位于 `guanghu-os/crates/supervisor`。它已经把请求、策略、能力白名单、授权、 +回滚检查点、救援槽、状态迁移、目标读回和失败回收绑定为一个确定性闭环。发生唤醒尝试后, +任何失败都必须回收;只有再次读回副控为 `DORMANT` 才能解除锁定。该合同已有隔离测试, +但尚未接入真实 Linux 虚拟机或容器后端,也没有取得京东整机启动权。 + ### 阶段 F:裸机研究后端 - 现有 GOSK/GHAL 候选继续作为研究和专用设备后端; @@ -119,6 +124,7 @@ - 第一条类型化协议执行桥; - systemd 状态与重启动作的白名单、授权、回滚和目标读回合同; - 光湖语言主控的世界启动门、系统服务依赖、公共入口验收和服务器回执合同; +- 光湖监督器与 Linux 副控生命周期的类型化源码合同、失败回收和锁死语义; - 京东节点已将 `guanghu-language-primary.target` 设为默认目标,并完成一次真实重启后 `PASS_100` 回读;本次启动总耗时 36.146 秒; - 对应单元和集成测试。 @@ -129,7 +135,8 @@ - 全部注册协议的工程实现; - 普通 Linux 管理入口的紧急维护边界收紧。 - 光湖独立先启动的监督器; -- Linux 平时休眠、按需唤醒、目标读回和任务后收回的完整生命周期。 +- 监督器到真实隔离 Linux 后端的接线; +- Linux 平时休眠、按需唤醒、目标读回和任务后收回的实体生命周期。 因此,当前京东有界语言服务控制层为100;最终整机光湖 OS 主控为0。Linux-free 或删除 Linux 不在最终完成公式中。 diff --git a/product-source/hololake-platform/guanghu-os/Cargo.lock b/product-source/hololake-platform/guanghu-os/Cargo.lock index 28b300f..5a78e1a 100644 --- a/product-source/hololake-platform/guanghu-os/Cargo.lock +++ b/product-source/hololake-platform/guanghu-os/Cargo.lock @@ -40,6 +40,13 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "guanghu-supervisor" +version = "0.1.0" +dependencies = [ + "serde", +] + [[package]] name = "hashbrown" version = "0.17.1" diff --git a/product-source/hololake-platform/guanghu-os/Cargo.toml b/product-source/hololake-platform/guanghu-os/Cargo.toml index f47166c..98435f9 100644 --- a/product-source/hololake-platform/guanghu-os/Cargo.toml +++ b/product-source/hololake-platform/guanghu-os/Cargo.toml @@ -5,5 +5,6 @@ members = [ "crates/ghctl", "crates/hldp-native-compiler", "crates/hldp-runtime", + "crates/supervisor", ] resolver = "2" diff --git a/product-source/hololake-platform/guanghu-os/README.md b/product-source/hololake-platform/guanghu-os/README.md index e3ce147..0431eb7 100644 --- a/product-source/hololake-platform/guanghu-os/README.md +++ b/product-source/hololake-platform/guanghu-os/README.md @@ -57,6 +57,27 @@ The registered translation chain is: TCS -> HLDP -> GLC -> GIR -> BTCP -> GOSK -> GHAL -> hardware ``` +## Guanghu supervisor and Linux subcontrol lifecycle + +`crates/supervisor` implements the first fail-closed source contract for +ADR-0175. A typed, policy-bound capability request can advance only through: + +```text +DORMANT -> STARTING -> READY -> EXECUTING -> VERIFYING -> RECLAIMING -> DORMANT +``` + +The request must bind the exact node, subject, capability, authorization, +rollback checkpoint, backend, required protocol chain, and preserved rescue +slot. Every failure after a wake attempt enters reclaim. A receipt can report +`PASS_100` only after target-side readback and a second observation proving the +Linux subcontrol returned to `DORMANT`; a failed reclaim is +`FAIL_0_LOCKED`. + +The deterministic contract and isolation harness are implemented and covered +at 100% of declared source lines and functions. No production Linux +subcontrol backend, independent Guanghu boot supervisor, JD boot change, or +physical cutover is implied by that source result. + ## Language-primary boot target `guanghu-language-primary.target` makes the accepted cognitive-control model diff --git a/product-source/hololake-platform/guanghu-os/crates/supervisor/Cargo.toml b/product-source/hololake-platform/guanghu-os/crates/supervisor/Cargo.toml new file mode 100644 index 0000000..bea31b0 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/crates/supervisor/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "guanghu-supervisor" +version = "0.1.0" +edition = "2021" +license = "AGPL-3.0-or-later" +description = "Fail-closed lifecycle contract for Guanghu-controlled on-demand Linux subcontrol" + +[dependencies] +serde = { version = "1", features = ["derive"] } diff --git a/product-source/hololake-platform/guanghu-os/crates/supervisor/src/lib.rs b/product-source/hololake-platform/guanghu-os/crates/supervisor/src/lib.rs new file mode 100644 index 0000000..ae6aad6 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/crates/supervisor/src/lib.rs @@ -0,0 +1,378 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; + +pub const REQUEST_SCHEMA: &str = "guanghu.linux-subcontrol-request/v1"; +pub const POLICY_SCHEMA: &str = "guanghu.supervisor-policy/v1"; + +pub const REQUIRED_PROTOCOL_CHAIN: [&str; 12] = [ + "GLS-0301", // message envelope + "GLS-0302", // identity + "GLS-0303", // context + "GLS-0306", // receipt + "GLS-0309", // work order and authorization + "GLS-0311", // witness + "GLS-0130", // compiler + "GLS-0131", // deterministic representation + "GLS-0709", // adapter + "GLS-0710", // immutable module + "GLS-0803", // bounded execution lifecycle + "GLS-0819", // resource reclaim +]; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LifecycleRequest { + pub schema: String, + pub request_id: String, + pub subject_id: String, + pub target_node_id: String, + pub protocol_chain: Vec, + pub capability: String, + pub authorization_id: String, + pub rollback_checkpoint_id: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct CapabilityGrant { + pub capability: String, + pub backend_id: String, + pub authorization_id: String, + pub rollback_checkpoint_id: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct SupervisorPolicy { + pub schema: String, + pub policy_id: String, + pub target_node_id: String, + pub rescue_slot_id: String, + pub capability_grants: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LifecyclePlan { + pub request_id: String, + pub subject_id: String, + pub target_node_id: String, + pub policy_id: String, + pub capability: String, + pub backend_id: String, + pub authorization_id: String, + pub rollback_checkpoint_id: String, + pub rescue_slot_id: String, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ObservedSubcontrolState { + Dormant, + Ready, + Active, + Failed, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum SupervisorState { + Dormant, + Starting, + Ready, + Executing, + Verifying, + Reclaiming, + FailedLocked, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct LifecycleReceipt { + pub schema: String, + pub request_id: String, + pub subject_id: String, + pub target_node_id: String, + pub policy_id: String, + pub capability: String, + pub backend_id: String, + pub authorization_id: String, + pub rollback_checkpoint_id: String, + pub rescue_slot_id: String, + pub rescue_verified: bool, + pub target_state_verified: bool, + pub reclaim_attempted: bool, + pub reclaim_verified: bool, + pub final_state: String, + pub failure_stage: Option, + pub failure_detail: Option, + pub transitions: Vec, +} + +pub trait LifecycleBackend { + fn rescue_available(&self, rescue_slot_id: &str) -> Result; + fn observe_state(&self, backend_id: &str) -> Result; + fn wake(&self, backend_id: &str) -> Result<(), String>; + fn execute(&self, backend_id: &str, capability: &str) -> Result<(), String>; + fn verify_target(&self, backend_id: &str, capability: &str) -> Result; + fn reclaim(&self, backend_id: &str) -> Result<(), String>; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SupervisorError(String); + +impl SupervisorError { + fn new(message: impl Into) -> Self { + Self(message.into()) + } +} + +impl fmt::Display for SupervisorError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.0) + } +} + +impl std::error::Error for SupervisorError {} + +pub fn compile_request( + request: &LifecycleRequest, + policy: &SupervisorPolicy, +) -> Result { + require( + request.schema == REQUEST_SCHEMA, + "unsupported request schema", + )?; + require(policy.schema == POLICY_SCHEMA, "unsupported policy schema")?; + require( + !request.request_id.trim().is_empty(), + "request_id is required", + )?; + require( + !request.subject_id.trim().is_empty(), + "subject_id is required", + )?; + require( + request.target_node_id == policy.target_node_id, + "request target does not match policy target", + )?; + require( + !policy.rescue_slot_id.trim().is_empty(), + "an independent Linux rescue slot is required", + )?; + + for protocol in REQUIRED_PROTOCOL_CHAIN { + require( + request + .protocol_chain + .iter() + .any(|candidate| candidate == protocol), + format!("required protocol is missing: {protocol}"), + )?; + } + + let grant = policy + .capability_grants + .iter() + .find(|grant| { + grant.capability == request.capability + && grant.authorization_id == request.authorization_id + && grant.rollback_checkpoint_id == request.rollback_checkpoint_id + }) + .ok_or_else(|| SupervisorError::new("capability mutation is not granted by policy"))?; + require( + !grant.backend_id.trim().is_empty(), + "backend_id is required", + )?; + + Ok(LifecyclePlan { + request_id: request.request_id.clone(), + subject_id: request.subject_id.clone(), + target_node_id: request.target_node_id.clone(), + policy_id: policy.policy_id.clone(), + capability: request.capability.clone(), + backend_id: grant.backend_id.clone(), + authorization_id: request.authorization_id.clone(), + rollback_checkpoint_id: request.rollback_checkpoint_id.clone(), + rescue_slot_id: policy.rescue_slot_id.clone(), + }) +} + +pub fn run_lifecycle( + plan: &LifecyclePlan, + backend: &dyn LifecycleBackend, +) -> Result { + let rescue_verified = backend + .rescue_available(&plan.rescue_slot_id) + .map_err(|error| SupervisorError::new(format!("cannot verify rescue slot: {error}")))?; + require( + rescue_verified, + "independent Linux rescue slot is unavailable", + )?; + + let initial_state = backend.observe_state(&plan.backend_id).map_err(|error| { + SupervisorError::new(format!("cannot observe Linux subcontrol: {error}")) + })?; + require( + initial_state == ObservedSubcontrolState::Dormant, + "Linux subcontrol is not dormant before wake", + )?; + + let mut transitions = vec![SupervisorState::Dormant, SupervisorState::Starting]; + if let Err(error) = backend.wake(&plan.backend_id) { + return Ok(reclaim_after( + plan, + backend, + rescue_verified, + false, + "wake", + error, + transitions, + )); + } + + match backend.observe_state(&plan.backend_id) { + Ok(ObservedSubcontrolState::Ready) => transitions.push(SupervisorState::Ready), + Ok(state) => { + return Ok(reclaim_after( + plan, + backend, + rescue_verified, + false, + "wake_readiness", + format!("unexpected state after wake: {state:?}"), + transitions, + )); + } + Err(error) => { + return Ok(reclaim_after( + plan, + backend, + rescue_verified, + false, + "wake_readiness", + error, + transitions, + )); + } + } + + transitions.push(SupervisorState::Executing); + if let Err(error) = backend.execute(&plan.backend_id, &plan.capability) { + return Ok(reclaim_after( + plan, + backend, + rescue_verified, + false, + "execution", + error, + transitions, + )); + } + + transitions.push(SupervisorState::Verifying); + match backend.verify_target(&plan.backend_id, &plan.capability) { + Ok(true) => Ok(reclaim_complete( + plan, + backend, + rescue_verified, + true, + None, + None, + transitions, + )), + Ok(false) => Ok(reclaim_after( + plan, + backend, + rescue_verified, + false, + "target_readback", + "target-side readback did not reach the expected state".to_owned(), + transitions, + )), + Err(error) => Ok(reclaim_after( + plan, + backend, + rescue_verified, + false, + "target_readback", + error, + transitions, + )), + } +} + +fn reclaim_after( + plan: &LifecyclePlan, + backend: &dyn LifecycleBackend, + rescue_verified: bool, + target_state_verified: bool, + failure_stage: &str, + failure_detail: String, + transitions: Vec, +) -> LifecycleReceipt { + reclaim_complete( + plan, + backend, + rescue_verified, + target_state_verified, + Some(failure_stage.to_owned()), + Some(failure_detail), + transitions, + ) +} + +fn reclaim_complete( + plan: &LifecyclePlan, + backend: &dyn LifecycleBackend, + rescue_verified: bool, + target_state_verified: bool, + failure_stage: Option, + failure_detail: Option, + mut transitions: Vec, +) -> LifecycleReceipt { + transitions.push(SupervisorState::Reclaiming); + let reclaim_verified = backend.reclaim(&plan.backend_id).is_ok() + && matches!( + backend.observe_state(&plan.backend_id), + Ok(ObservedSubcontrolState::Dormant) + ); + if reclaim_verified { + transitions.push(SupervisorState::Dormant); + } else { + transitions.push(SupervisorState::FailedLocked); + } + + let passed = target_state_verified && reclaim_verified && failure_stage.is_none(); + LifecycleReceipt { + schema: "guanghu.linux-subcontrol-receipt/v1".to_owned(), + request_id: plan.request_id.clone(), + subject_id: plan.subject_id.clone(), + target_node_id: plan.target_node_id.clone(), + policy_id: plan.policy_id.clone(), + capability: plan.capability.clone(), + backend_id: plan.backend_id.clone(), + authorization_id: plan.authorization_id.clone(), + rollback_checkpoint_id: plan.rollback_checkpoint_id.clone(), + rescue_slot_id: plan.rescue_slot_id.clone(), + rescue_verified, + target_state_verified, + reclaim_attempted: true, + reclaim_verified, + final_state: if passed { + "PASS_100" + } else if reclaim_verified { + "FAIL_0" + } else { + "FAIL_0_LOCKED" + } + .to_owned(), + failure_stage, + failure_detail, + transitions, + } +} + +fn require(condition: bool, message: impl Into) -> Result<(), SupervisorError> { + if condition { + Ok(()) + } else { + Err(SupervisorError::new(message)) + } +} diff --git a/product-source/hololake-platform/guanghu-os/crates/supervisor/tests/supervisor_lifecycle.rs b/product-source/hololake-platform/guanghu-os/crates/supervisor/tests/supervisor_lifecycle.rs new file mode 100644 index 0000000..31abe43 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/crates/supervisor/tests/supervisor_lifecycle.rs @@ -0,0 +1,357 @@ +use std::{cell::RefCell, collections::VecDeque}; + +use guanghu_supervisor::{ + compile_request, run_lifecycle, CapabilityGrant, LifecycleBackend, LifecycleRequest, + ObservedSubcontrolState, SupervisorPolicy, SupervisorState, POLICY_SCHEMA, REQUEST_SCHEMA, + REQUIRED_PROTOCOL_CHAIN, +}; + +fn request() -> LifecycleRequest { + LifecycleRequest { + schema: REQUEST_SCHEMA.to_owned(), + request_id: "REQ-JD-SUBCONTROL-001".to_owned(), + subject_id: "ICE-P-ZY001".to_owned(), + target_node_id: "JD-FD-PRIMARY".to_owned(), + protocol_chain: REQUIRED_PROTOCOL_CHAIN + .iter() + .map(|protocol| (*protocol).to_owned()) + .collect(), + capability: "public-entry-readback".to_owned(), + authorization_id: "AUTH-JD-SUBCONTROL-001".to_owned(), + rollback_checkpoint_id: "CHECKPOINT-JD-LINUX-RESCUE-001".to_owned(), + } +} + +fn policy() -> SupervisorPolicy { + SupervisorPolicy { + schema: POLICY_SCHEMA.to_owned(), + policy_id: "JD-GUANGHU-SUPERVISOR-001".to_owned(), + target_node_id: "JD-FD-PRIMARY".to_owned(), + rescue_slot_id: "ubuntu-maintenance".to_owned(), + capability_grants: vec![CapabilityGrant { + capability: "public-entry-readback".to_owned(), + backend_id: "isolated-linux-fixture".to_owned(), + authorization_id: "AUTH-JD-SUBCONTROL-001".to_owned(), + rollback_checkpoint_id: "CHECKPOINT-JD-LINUX-RESCUE-001".to_owned(), + }], + } +} + +#[test] +fn compiles_only_an_exact_authorized_capability() { + let plan = compile_request(&request(), &policy()).expect("compile exact grant"); + + assert_eq!(plan.backend_id, "isolated-linux-fixture"); + assert_eq!(plan.capability, "public-entry-readback"); + assert_eq!(plan.rescue_slot_id, "ubuntu-maintenance"); +} + +#[test] +fn rejects_missing_protocol_and_invented_authority() { + let mut missing_protocol = request(); + missing_protocol + .protocol_chain + .retain(|protocol| protocol != "GLS-0819"); + let error = + compile_request(&missing_protocol, &policy()).expect_err("missing lifecycle protocol"); + assert!(error.to_string().contains("GLS-0819")); + + let mut invented = request(); + invented.authorization_id = "AUTH-INVENTED".to_owned(); + let error = compile_request(&invented, &policy()).expect_err("invented grant"); + assert!(error.to_string().contains("not granted")); +} + +struct FakeBackend { + rescue_result: Result, + states: RefCell>>, + wake_result: Result<(), String>, + execute_result: Result<(), String>, + readback_result: Result, + reclaim_result: Result<(), String>, + calls: RefCell>, +} + +impl FakeBackend { + fn successful() -> Self { + Self { + rescue_result: Ok(true), + states: RefCell::new( + [ + Ok(ObservedSubcontrolState::Dormant), + Ok(ObservedSubcontrolState::Ready), + Ok(ObservedSubcontrolState::Dormant), + ] + .into(), + ), + wake_result: Ok(()), + execute_result: Ok(()), + readback_result: Ok(true), + reclaim_result: Ok(()), + calls: RefCell::new(Vec::new()), + } + } +} + +impl LifecycleBackend for FakeBackend { + fn rescue_available(&self, rescue_slot_id: &str) -> Result { + self.calls + .borrow_mut() + .push(format!("rescue:{rescue_slot_id}")); + self.rescue_result.clone() + } + + fn observe_state(&self, backend_id: &str) -> Result { + self.calls + .borrow_mut() + .push(format!("observe:{backend_id}")); + self.states + .borrow_mut() + .pop_front() + .unwrap_or_else(|| Err("unexpected state observation".to_owned())) + } + + fn wake(&self, backend_id: &str) -> Result<(), String> { + self.calls.borrow_mut().push(format!("wake:{backend_id}")); + self.wake_result.clone() + } + + fn execute(&self, backend_id: &str, capability: &str) -> Result<(), String> { + self.calls + .borrow_mut() + .push(format!("execute:{backend_id}:{capability}")); + self.execute_result.clone() + } + + fn verify_target(&self, backend_id: &str, capability: &str) -> Result { + self.calls + .borrow_mut() + .push(format!("verify:{backend_id}:{capability}")); + self.readback_result.clone() + } + + fn reclaim(&self, backend_id: &str) -> Result<(), String> { + self.calls + .borrow_mut() + .push(format!("reclaim:{backend_id}")); + self.reclaim_result.clone() + } +} + +#[test] +fn completes_wake_execute_verify_and_reclaim_to_dormant() { + let plan = compile_request(&request(), &policy()).expect("compile"); + let backend = FakeBackend::successful(); + let receipt = run_lifecycle(&plan, &backend).expect("run lifecycle"); + + assert_eq!(receipt.final_state, "PASS_100"); + assert!(receipt.target_state_verified); + assert!(receipt.reclaim_verified); + assert!(receipt.rescue_verified); + assert_eq!( + receipt.transitions, + [ + SupervisorState::Dormant, + SupervisorState::Starting, + SupervisorState::Ready, + SupervisorState::Executing, + SupervisorState::Verifying, + SupervisorState::Reclaiming, + SupervisorState::Dormant, + ] + ); + assert_eq!( + backend.calls.borrow().last().unwrap(), + "observe:isolated-linux-fixture" + ); +} + +#[test] +fn failed_target_readback_is_fail_zero_but_reclaims_linux() { + let plan = compile_request(&request(), &policy()).expect("compile"); + let mut backend = FakeBackend::successful(); + backend.readback_result = Ok(false); + let receipt = run_lifecycle(&plan, &backend).expect("bounded failure receipt"); + + assert_eq!(receipt.final_state, "FAIL_0"); + assert!(!receipt.target_state_verified); + assert!(receipt.reclaim_attempted); + assert!(receipt.reclaim_verified); + assert_eq!(receipt.failure_stage.as_deref(), Some("target_readback")); + assert_eq!(receipt.transitions.last(), Some(&SupervisorState::Dormant)); +} + +#[test] +fn reclaim_failure_locks_the_supervisor_instead_of_claiming_safety() { + let plan = compile_request(&request(), &policy()).expect("compile"); + let mut backend = FakeBackend::successful(); + backend.readback_result = Ok(false); + backend.reclaim_result = Err("cannot stop subordinate".to_owned()); + backend.states = RefCell::new( + [ + Ok(ObservedSubcontrolState::Dormant), + Ok(ObservedSubcontrolState::Ready), + ] + .into(), + ); + let receipt = run_lifecycle(&plan, &backend).expect("locked failure receipt"); + + assert_eq!(receipt.final_state, "FAIL_0_LOCKED"); + assert!(!receipt.reclaim_verified); + assert_eq!( + receipt.transitions.last(), + Some(&SupervisorState::FailedLocked) + ); +} + +#[test] +fn missing_rescue_or_non_dormant_linux_prevents_wake() { + let plan = compile_request(&request(), &policy()).expect("compile"); + let mut no_rescue = FakeBackend::successful(); + no_rescue.rescue_result = Ok(false); + let error = run_lifecycle(&plan, &no_rescue).expect_err("rescue is mandatory"); + assert!(error.to_string().contains("rescue")); + assert!(!no_rescue + .calls + .borrow() + .iter() + .any(|call| call.starts_with("wake:"))); + + let mut active = FakeBackend::successful(); + active.states = RefCell::new([Ok(ObservedSubcontrolState::Ready)].into()); + let error = run_lifecycle(&plan, &active).expect_err("must begin dormant"); + assert!(error.to_string().contains("not dormant")); + assert!(!active + .calls + .borrow() + .iter() + .any(|call| call.starts_with("wake:"))); +} + +#[test] +fn compile_validation_fails_closed_for_every_required_boundary() { + let mut invalid_request = request(); + invalid_request.schema = "wrong".to_owned(); + assert!(compile_request(&invalid_request, &policy()).is_err()); + + let mut invalid_policy = policy(); + invalid_policy.schema = "wrong".to_owned(); + assert!(compile_request(&request(), &invalid_policy).is_err()); + + let mut missing_request_id = request(); + missing_request_id.request_id = " ".to_owned(); + assert!(compile_request(&missing_request_id, &policy()).is_err()); + + let mut missing_subject = request(); + missing_subject.subject_id.clear(); + assert!(compile_request(&missing_subject, &policy()).is_err()); + + let mut wrong_target = request(); + wrong_target.target_node_id = "OTHER-NODE".to_owned(); + assert!(compile_request(&wrong_target, &policy()).is_err()); + + let mut missing_rescue = policy(); + missing_rescue.rescue_slot_id.clear(); + assert!(compile_request(&request(), &missing_rescue).is_err()); + + let mut wrong_capability = request(); + wrong_capability.capability = "unregistered".to_owned(); + assert!(compile_request(&wrong_capability, &policy()).is_err()); + + let mut wrong_rollback = request(); + wrong_rollback.rollback_checkpoint_id = "CHECKPOINT-INVENTED".to_owned(); + assert!(compile_request(&wrong_rollback, &policy()).is_err()); + + let mut empty_backend = policy(); + empty_backend.capability_grants[0].backend_id.clear(); + assert!(compile_request(&request(), &empty_backend).is_err()); +} + +#[test] +fn observation_errors_before_wake_do_not_mutate_linux() { + let plan = compile_request(&request(), &policy()).expect("compile"); + let mut rescue_error = FakeBackend::successful(); + rescue_error.rescue_result = Err("rescue probe unavailable".to_owned()); + let error = run_lifecycle(&plan, &rescue_error).expect_err("rescue probe error"); + assert!(error.to_string().contains("cannot verify rescue")); + + let mut state_error = FakeBackend::successful(); + state_error.states = RefCell::new([Err("state probe unavailable".to_owned())].into()); + let error = run_lifecycle(&plan, &state_error).expect_err("state probe error"); + assert!(error.to_string().contains("cannot observe")); + assert!(!state_error + .calls + .borrow() + .iter() + .any(|call| call.starts_with("wake:"))); +} + +#[test] +fn every_post_wake_failure_attempts_reclaim() { + let plan = compile_request(&request(), &policy()).expect("compile"); + + let mut wake_failure = FakeBackend::successful(); + wake_failure.wake_result = Err("wake failed".to_owned()); + wake_failure.states = RefCell::new( + [ + Ok(ObservedSubcontrolState::Dormant), + Ok(ObservedSubcontrolState::Dormant), + ] + .into(), + ); + let receipt = run_lifecycle(&plan, &wake_failure).expect("wake failure receipt"); + assert_eq!(receipt.failure_stage.as_deref(), Some("wake")); + assert!(receipt.reclaim_verified); + + let mut not_ready = FakeBackend::successful(); + not_ready.states = RefCell::new( + [ + Ok(ObservedSubcontrolState::Dormant), + Ok(ObservedSubcontrolState::Active), + Ok(ObservedSubcontrolState::Dormant), + ] + .into(), + ); + let receipt = run_lifecycle(&plan, ¬_ready).expect("not-ready failure receipt"); + assert_eq!(receipt.failure_stage.as_deref(), Some("wake_readiness")); + + let mut readiness_error = FakeBackend::successful(); + readiness_error.states = RefCell::new( + [ + Ok(ObservedSubcontrolState::Dormant), + Err("readiness probe failed".to_owned()), + Ok(ObservedSubcontrolState::Dormant), + ] + .into(), + ); + let receipt = run_lifecycle(&plan, &readiness_error).expect("readiness failure receipt"); + assert_eq!(receipt.failure_stage.as_deref(), Some("wake_readiness")); + + let mut execution_error = FakeBackend::successful(); + execution_error.execute_result = Err("capability failed".to_owned()); + let receipt = run_lifecycle(&plan, &execution_error).expect("execution failure receipt"); + assert_eq!(receipt.failure_stage.as_deref(), Some("execution")); + + let mut readback_error = FakeBackend::successful(); + readback_error.readback_result = Err("readback probe failed".to_owned()); + let receipt = run_lifecycle(&plan, &readback_error).expect("readback failure receipt"); + assert_eq!(receipt.failure_stage.as_deref(), Some("target_readback")); +} + +#[test] +fn reclaim_requires_a_final_dormant_readback() { + let plan = compile_request(&request(), &policy()).expect("compile"); + let mut backend = FakeBackend::successful(); + backend.states = RefCell::new( + [ + Ok(ObservedSubcontrolState::Dormant), + Ok(ObservedSubcontrolState::Ready), + Ok(ObservedSubcontrolState::Failed), + ] + .into(), + ); + + let receipt = run_lifecycle(&plan, &backend).expect("locked receipt"); + assert_eq!(receipt.final_state, "FAIL_0_LOCKED"); + assert!(!receipt.reclaim_verified); +} diff --git a/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh b/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh index 39d758a..94859b3 100755 --- a/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh +++ b/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh @@ -98,6 +98,7 @@ run_gate auditable_line_coverage_100_percent \ --test compiler_library \ --test compiler_command \ --test world_manifest \ + --test supervisor_lifecycle \ --no-report cargo llvm-cov report --manifest-path "$1/Cargo.toml" \ --ignore-filename-regex "/src/main\\.rs$" \ diff --git a/routing/guanghu-os-control-architecture.json b/routing/guanghu-os-control-architecture.json index 6ba1656..d48b463 100644 --- a/routing/guanghu-os-control-architecture.json +++ b/routing/guanghu-os-control-architecture.json @@ -1,7 +1,7 @@ { "schema": "guanghu.os-control-architecture/v1", "record_id": "HLP-GUANGHU-OS-CONTROL-001", - "version": "2026-08-10.1", + "version": "2026-08-15.1", "state": "CURRENT_CANONICAL", "final_topology": "GUANGHU_MASTER_WITH_ON_DEMAND_LINUX_SUBCONTROL_AND_RESCUE", "control_ownership": { @@ -39,7 +39,16 @@ "linux_on_demand_subcontrol": 0, "linux_rescue_preserved": 100, "final_guanghu_os_master": 0, - "next_engineering_gate": "IMPLEMENT_AND_ISOLATION_TEST_GUANGHU_SUPERVISOR_AND_LINUX_SUBCONTROL_LIFECYCLE_BEFORE_REAL_BOOT_CUTOVER" + "next_engineering_gate": "INTEGRATE_A_REAL_ISOLATED_LINUX_BACKEND_AND_GUANGHU_FIRST_BOOT_SUPERVISOR_BEFORE_JD_PHYSICAL_CUTOVER" + }, + "implementation": { + "guanghu_supervisor_lifecycle_contract_source": 100, + "typed_policy_authorization_and_rollback_binding": 100, + "target_readback_and_mandatory_reclaim_state_machine": 100, + "declared_supervisor_core_line_and_function_coverage": 100, + "real_isolated_linux_backend": 0, + "independent_guanghu_first_boot_supervisor": 0, + "jd_physical_deployment": 0 }, "historical_evidence_policy": { "bare_metal_gosk_ghal_receipts_remain_valid_for_observed_capabilities": true, diff --git a/routing/guanghu-os-control-architecture.test.mjs b/routing/guanghu-os-control-architecture.test.mjs index 2d69fd5..e6eadc0 100644 --- a/routing/guanghu-os-control-architecture.test.mjs +++ b/routing/guanghu-os-control-architecture.test.mjs @@ -31,6 +31,16 @@ test("current JD state stays transitional and cannot impersonate final master co assert.equal(contract.jd_fd_primary.final_guanghu_os_master, 0); }); +test("source lifecycle progress does not impersonate backend, boot, or JD deployment", () => { + assert.equal(contract.version, "2026-08-15.1"); + assert.equal(contract.implementation.guanghu_supervisor_lifecycle_contract_source, 100); + assert.equal(contract.implementation.target_readback_and_mandatory_reclaim_state_machine, 100); + assert.equal(contract.implementation.declared_supervisor_core_line_and_function_coverage, 100); + assert.equal(contract.implementation.real_isolated_linux_backend, 0); + assert.equal(contract.implementation.independent_guanghu_first_boot_supervisor, 0); + assert.equal(contract.implementation.jd_physical_deployment, 0); +}); + test("current architecture and global engineering rules project the same control contract", () => { assert.equal(architecture.version, "2026-08-14.1"); assert.equal(architecture.server_os_control.machine_projection, "routing/guanghu-os-control-architecture.json");