Compare commits
5 Commits
5282089c7d
...
f36fd1ed7c
| Author | SHA1 | Date | |
|---|---|---|---|
| f36fd1ed7c | |||
| aebebcae04 | |||
| df9f82fcd2 | |||
| 569707d002 | |||
| fbebd8887a |
@ -177,6 +177,8 @@ mod tests {
|
|||||||
use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource};
|
use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource};
|
||||||
use prometeu_hal::syscalls::caps;
|
use prometeu_hal::syscalls::caps;
|
||||||
use prometeu_system::CrashReport;
|
use prometeu_system::CrashReport;
|
||||||
|
use prometeu_system::process::ProcessState;
|
||||||
|
use prometeu_system::task::TaskState;
|
||||||
|
|
||||||
fn halting_program() -> Vec<u8> {
|
fn halting_program() -> Vec<u8> {
|
||||||
let code = assemble("HALT").expect("assemble");
|
let code = assemble("HALT").expect("assemble");
|
||||||
@ -289,7 +291,10 @@ mod tests {
|
|||||||
firmware.load_cartridge(trapping_game_cartridge());
|
firmware.load_cartridge(trapping_game_cartridge());
|
||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
|
let task_id = match &firmware.state {
|
||||||
|
FirmwareState::GameRunning(step) => step.task_id,
|
||||||
|
other => panic!("expected GameRunning state, got {:?}", other),
|
||||||
|
};
|
||||||
|
|
||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
@ -302,6 +307,13 @@ mod tests {
|
|||||||
},
|
},
|
||||||
other => panic!("expected AppCrashes state, got {:?}", other),
|
other => panic!("expected AppCrashes state, got {:?}", other),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let task = firmware.os.task_manager.get(task_id).expect("task should exist");
|
||||||
|
let process =
|
||||||
|
firmware.os.process_manager.get(task.process_id).expect("process should exist");
|
||||||
|
|
||||||
|
assert_eq!(task.state, TaskState::Crashed);
|
||||||
|
assert_eq!(process.state, ProcessState::Crashed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -19,19 +19,20 @@ impl GameRunningStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
let Some(task) = ctx.os.task_manager.get(self.task_id) else {
|
let Some(task_state) = ctx.os.task_manager.get(self.task_id).map(|task| task.state) else {
|
||||||
// TODO: is it panic?
|
// TODO: is it panic?
|
||||||
let report =
|
let report =
|
||||||
CrashReport::VmPanic { message: "task is gone, panic!".to_string(), pc: None };
|
CrashReport::VmPanic { message: "task is gone, panic!".to_string(), pc: None };
|
||||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
};
|
};
|
||||||
|
|
||||||
if task.state != TaskState::Foreground {
|
if task_state != TaskState::Foreground {
|
||||||
// TODO: is it panic?
|
// TODO: is it panic?
|
||||||
let report = CrashReport::VmPanic {
|
let report = CrashReport::VmPanic {
|
||||||
message: "should be running as foreground, panic!".to_string(),
|
message: "should be running as foreground, panic!".to_string(),
|
||||||
pc: None,
|
pc: None,
|
||||||
};
|
};
|
||||||
|
let _ = ctx.os.crash_task(self.task_id, Some(&report));
|
||||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +42,12 @@ impl GameRunningStep {
|
|||||||
ctx.hw.gfx_mut().present();
|
ctx.hw.gfx_mut().present();
|
||||||
}
|
}
|
||||||
|
|
||||||
result.map(|report| FirmwareState::AppCrashes(AppCrashesStep { report }))
|
if let Some(report) = result {
|
||||||
|
let _ = ctx.os.crash_task(self.task_id, Some(&report));
|
||||||
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}
|
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ impl SystemRunningStep {
|
|||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
let outcome = ctx.hub.update_system_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw);
|
let outcome = ctx.hub.update_system_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw);
|
||||||
if let Some(report) = outcome.crash {
|
if let Some(report) = outcome.crash {
|
||||||
|
let _ = ctx.os.crash_task(self.task_id, Some(&report));
|
||||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ mod programs;
|
|||||||
mod services;
|
mod services;
|
||||||
|
|
||||||
pub use crash_report::CrashReport;
|
pub use crash_report::CrashReport;
|
||||||
pub use os::SystemOS;
|
pub use os::{LifecycleError, LifecycleOperation, SystemOS};
|
||||||
pub use programs::PrometeuHub;
|
pub use programs::PrometeuHub;
|
||||||
pub use services::fs;
|
pub use services::fs;
|
||||||
pub use services::process;
|
pub use services::process;
|
||||||
|
|||||||
18
crates/console/prometeu-system/src/os/lifecycle.rs
Normal file
18
crates/console/prometeu-system/src/os/lifecycle.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use crate::process::ProcessId;
|
||||||
|
use crate::task::{TaskId, TaskState};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LifecycleOperation {
|
||||||
|
SetForeground,
|
||||||
|
Suspend,
|
||||||
|
Resume,
|
||||||
|
Close,
|
||||||
|
Crash,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LifecycleError {
|
||||||
|
TaskNotFound(TaskId),
|
||||||
|
ProcessNotFound(ProcessId),
|
||||||
|
InvalidTransition { task_id: TaskId, from: TaskState, operation: LifecycleOperation },
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
mod lifecycle;
|
||||||
mod system_os;
|
mod system_os;
|
||||||
|
|
||||||
|
pub use lifecycle::{LifecycleError, LifecycleOperation};
|
||||||
pub use system_os::SystemOS;
|
pub use system_os::SystemOS;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
use crate::VirtualMachineRuntime;
|
use crate::VirtualMachineRuntime;
|
||||||
use crate::fs::{FsBackend, FsState, VirtualFS};
|
use crate::fs::{FsBackend, FsState, VirtualFS};
|
||||||
|
use crate::os::{LifecycleError, LifecycleOperation};
|
||||||
use crate::process::ProcessManager;
|
use crate::process::ProcessManager;
|
||||||
use crate::services::memcard::MemcardService;
|
use crate::services::memcard::MemcardService;
|
||||||
use crate::services::window_manager::WindowManager;
|
use crate::services::window_manager::WindowManager;
|
||||||
use crate::task::TaskId;
|
|
||||||
use crate::task::TaskManager;
|
use crate::task::TaskManager;
|
||||||
|
use crate::task::{TaskId, TaskState};
|
||||||
use prometeu_hal::cartridge::Cartridge;
|
use prometeu_hal::cartridge::Cartridge;
|
||||||
use prometeu_hal::log::{LogLevel, LogService, LogSource};
|
use prometeu_hal::log::{LogLevel, LogService, LogSource};
|
||||||
use prometeu_hal::telemetry::CertificationConfig;
|
use prometeu_hal::telemetry::CertificationConfig;
|
||||||
@ -102,7 +103,8 @@ impl SystemOS {
|
|||||||
let title = title.into();
|
let title = title.into();
|
||||||
let process_id = self.process_manager.spawn_vm_game(app_id, title.clone());
|
let process_id = self.process_manager.spawn_vm_game(app_id, title.clone());
|
||||||
let task_id = self.task_manager.create_game_task(process_id, app_id, title);
|
let task_id = self.task_manager.create_game_task(process_id, app_id, title);
|
||||||
self.task_manager.set_foreground(task_id);
|
self.set_foreground_task(task_id)
|
||||||
|
.expect("newly created game task should have an associated process");
|
||||||
|
|
||||||
task_id
|
task_id
|
||||||
}
|
}
|
||||||
@ -111,7 +113,8 @@ impl SystemOS {
|
|||||||
let title = title.into();
|
let title = title.into();
|
||||||
let process_id = self.process_manager.spawn_vm_shell(app_id, title.clone());
|
let process_id = self.process_manager.spawn_vm_shell(app_id, title.clone());
|
||||||
let task_id = self.task_manager.create_shell_task(process_id, app_id, title);
|
let task_id = self.task_manager.create_shell_task(process_id, app_id, title);
|
||||||
self.task_manager.set_foreground(task_id);
|
self.set_foreground_task(task_id)
|
||||||
|
.expect("newly created shell task should have an associated process");
|
||||||
|
|
||||||
task_id
|
task_id
|
||||||
}
|
}
|
||||||
@ -120,8 +123,202 @@ impl SystemOS {
|
|||||||
let title = title.into();
|
let title = title.into();
|
||||||
let process_id = self.process_manager.spawn_native_shell(app_id, title.clone());
|
let process_id = self.process_manager.spawn_native_shell(app_id, title.clone());
|
||||||
let task_id = self.task_manager.create_shell_task(process_id, app_id, title);
|
let task_id = self.task_manager.create_shell_task(process_id, app_id, title);
|
||||||
self.task_manager.set_foreground(task_id);
|
self.set_foreground_task(task_id)
|
||||||
|
.expect("newly created native shell task should have an associated process");
|
||||||
|
|
||||||
task_id
|
task_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> {
|
||||||
|
let process_id = self.process_id_for_task(task_id)?;
|
||||||
|
|
||||||
|
self.task_manager.set_foreground(task_id);
|
||||||
|
self.process_manager.mark_running(process_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suspend_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> {
|
||||||
|
let process_id = self.process_id_for_task(task_id)?;
|
||||||
|
|
||||||
|
self.task_manager.mark_suspended(task_id);
|
||||||
|
self.process_manager.mark_suspended(process_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> {
|
||||||
|
let task = self.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?;
|
||||||
|
|
||||||
|
if task.state != TaskState::Suspended {
|
||||||
|
return Err(LifecycleError::InvalidTransition {
|
||||||
|
task_id,
|
||||||
|
from: task.state,
|
||||||
|
operation: LifecycleOperation::Resume,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let process_id = self.process_id_for_task(task_id)?;
|
||||||
|
|
||||||
|
self.task_manager.set_foreground(task_id);
|
||||||
|
self.process_manager.mark_running(process_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> {
|
||||||
|
let process_id = self.process_id_for_task(task_id)?;
|
||||||
|
|
||||||
|
self.task_manager.close_task(task_id);
|
||||||
|
self.process_manager.mark_stopped(process_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crash_task(
|
||||||
|
&mut self,
|
||||||
|
task_id: TaskId,
|
||||||
|
_report: Option<&CrashReport>,
|
||||||
|
) -> Result<(), LifecycleError> {
|
||||||
|
let process_id = self.process_id_for_task(task_id)?;
|
||||||
|
|
||||||
|
self.task_manager.mark_crashed(task_id);
|
||||||
|
self.process_manager.mark_crashed(process_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_id_for_task(
|
||||||
|
&self,
|
||||||
|
task_id: TaskId,
|
||||||
|
) -> Result<crate::process::ProcessId, LifecycleError> {
|
||||||
|
let task = self.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?;
|
||||||
|
|
||||||
|
if !self.process_manager.contains(task.process_id) {
|
||||||
|
return Err(LifecycleError::ProcessNotFound(task.process_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(task.process_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::process::ProcessState;
|
||||||
|
|
||||||
|
fn process_state_for_task(os: &SystemOS, task_id: TaskId) -> ProcessState {
|
||||||
|
let task = os.task_manager.get(task_id).expect("task should exist");
|
||||||
|
os.process_manager.get(task.process_id).expect("process should exist").state
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_foreground_task_marks_task_and_process_running() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
|
||||||
|
os.suspend_task(task_id).expect("suspend should succeed");
|
||||||
|
os.set_foreground_task(task_id).expect("foreground should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
os.task_manager.get(task_id).expect("task should exist").state,
|
||||||
|
TaskState::Foreground
|
||||||
|
);
|
||||||
|
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Running);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suspend_task_marks_task_and_process_suspended() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
|
||||||
|
os.suspend_task(task_id).expect("suspend should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
os.task_manager.get(task_id).expect("task should exist").state,
|
||||||
|
TaskState::Suspended
|
||||||
|
);
|
||||||
|
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Suspended);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resume_task_marks_suspended_task_foreground_and_process_running() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
|
||||||
|
os.suspend_task(task_id).expect("suspend should succeed");
|
||||||
|
os.resume_task(task_id).expect("resume should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
os.task_manager.get(task_id).expect("task should exist").state,
|
||||||
|
TaskState::Foreground
|
||||||
|
);
|
||||||
|
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Running);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn close_task_marks_task_closed_and_process_stopped_without_removing_entities() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
let process_id = os.task_manager.get(task_id).expect("task should exist").process_id;
|
||||||
|
|
||||||
|
os.close_task(task_id).expect("close should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
os.task_manager.get(task_id).expect("task should remain").state,
|
||||||
|
TaskState::Closed
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
os.process_manager.get(process_id).expect("process should remain").state,
|
||||||
|
ProcessState::Stopped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crash_task_marks_task_and_process_crashed() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
|
||||||
|
os.crash_task(task_id, None).expect("crash should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
os.task_manager.get(task_id).expect("task should exist").state,
|
||||||
|
TaskState::Crashed
|
||||||
|
);
|
||||||
|
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Crashed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lifecycle_operation_missing_task_returns_typed_error() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
|
||||||
|
assert_eq!(os.suspend_task(TaskId(999)), Err(LifecycleError::TaskNotFound(TaskId(999))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lifecycle_operation_missing_process_returns_typed_error() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
let process_id = os.task_manager.get(task_id).expect("task should exist").process_id;
|
||||||
|
|
||||||
|
os.process_manager.mark_stopped(process_id);
|
||||||
|
os.process_manager.remove_stopped();
|
||||||
|
|
||||||
|
assert_eq!(os.suspend_task(task_id), Err(LifecycleError::ProcessNotFound(process_id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resume_task_from_non_suspended_state_returns_invalid_transition() {
|
||||||
|
let mut os = SystemOS::new(None);
|
||||||
|
let task_id = os.create_vm_game_task(42, "Sector Crawl");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
os.resume_task(task_id),
|
||||||
|
Err(LifecycleError::InvalidTransition {
|
||||||
|
task_id,
|
||||||
|
from: TaskState::Foreground,
|
||||||
|
operation: LifecycleOperation::Resume,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{"type":"meta","next_id":{"DSC":34,"AGD":34,"DEC":25,"PLN":55,"LSN":41,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":34,"AGD":34,"DEC":26,"PLN":58,"LSN":41,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"discussion/lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"discussion/lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
||||||
@ -30,5 +30,5 @@
|
|||||||
{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"discussion/lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"discussion/lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]}
|
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]}
|
||||||
{"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
{"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
||||||
{"type":"discussion","id":"DSC-0032","status":"open","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[{"id":"AGD-0032","file":"AGD-0032-system-os-lifecycle-process-task-contract.md","status":"open","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[{"id":"AGD-0032","file":"AGD-0032-system-os-lifecycle-process-task-contract.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0025","file":"DEC-0025-systemos-lifecycle-authority-for-tasks-and-processes.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0032"}],"plans":[{"id":"PLN-0055","file":"PLN-0055-systemos-lifecycle-core-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]},{"id":"PLN-0056","file":"PLN-0056-firmware-lifecycle-delegation.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]},{"id":"PLN-0057","file":"PLN-0057-lifecycle-tests-and-invariants.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0025"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[{"id":"AGD-0033","file":"AGD-0033-system-os-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[{"id":"DEC-0024","file":"DEC-0024-systemos-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14","ref_agenda":"AGD-0033"}],"plans":[{"id":"PLN-0051","file":"PLN-0051-move-vm-runtime-into-services.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0052","file":"PLN-0052-promote-window-manager-to-systemos-service.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0053","file":"PLN-0053-move-logging-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0054","file":"PLN-0054-move-fs-and-memcard-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[{"id":"AGD-0033","file":"AGD-0033-system-os-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[{"id":"DEC-0024","file":"DEC-0024-systemos-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14","ref_agenda":"AGD-0033"}],"plans":[{"id":"PLN-0051","file":"PLN-0051-move-vm-runtime-into-services.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0052","file":"PLN-0052-promote-window-manager-to-systemos-service.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0053","file":"PLN-0053-move-logging-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0054","file":"PLN-0054-move-fs-and-memcard-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]}
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
id: AGD-0032
|
id: AGD-0032
|
||||||
ticket: system-os-lifecycle-process-task-contract
|
ticket: system-os-lifecycle-process-task-contract
|
||||||
title: Agenda - SystemOS Lifecycle, Process and Task Contract
|
title: Agenda - SystemOS Lifecycle, Process and Task Contract
|
||||||
status: open
|
status: accepted
|
||||||
created: 2026-05-14
|
created: 2026-05-14
|
||||||
resolved:
|
resolved: 2026-05-15
|
||||||
decision:
|
decision: DEC-0025
|
||||||
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ AppMode::Game
|
|||||||
-> marca task como Foreground
|
-> marca task como Foreground
|
||||||
-> entra em GameRunningStep com TaskId
|
-> entra em GameRunningStep com TaskId
|
||||||
|
|
||||||
AppMode::System
|
AppMode::Shell
|
||||||
-> cria processo VmShell
|
-> cria processo VmShell
|
||||||
-> cria task Shell
|
-> cria task Shell
|
||||||
-> marca task como Foreground
|
-> marca task como Foreground
|
||||||
@ -249,38 +249,83 @@ O primeiro corte deve ser pequeno:
|
|||||||
O `WindowManager` deve ser a agenda/decisão seguinte: promovê-lo a serviço real
|
O `WindowManager` deve ser a agenda/decisão seguinte: promovê-lo a serviço real
|
||||||
do sistema quando a base `Task/Process/Lifecycle` estiver fechada.
|
do sistema quando a base `Task/Process/Lifecycle` estiver fechada.
|
||||||
|
|
||||||
Também precisamos resolver a nomenclatura `System` versus `Shell`. O código e o
|
`AppMode::System` já foi renomeado para `AppMode::Shell`. A nomenclatura pública
|
||||||
contrato anterior usam `AppMode::System`; a narrativa nova fala em
|
fica alinhada com o modelo atual: `AppMode::Game` executa cartuchos como
|
||||||
`AppMode::Shell`. A agenda deve decidir se:
|
jogo/session fullscreen, enquanto `AppMode::Shell` executa cartuchos como apps
|
||||||
|
gerenciados pela Shell do Prometeu OS.
|
||||||
- mantemos `AppMode::System` como perfil formal e usamos `Shell` como tipo de
|
|
||||||
task/processo/UI; ou
|
|
||||||
- renomeamos o perfil público para `Shell`, com migração explícita.
|
|
||||||
|
|
||||||
Minha inclinação inicial é manter `AppMode::System` por compatibilidade de
|
|
||||||
contrato e usar `Shell` para a camada de UI/app gerenciado dentro do OS.
|
|
||||||
|
|
||||||
## Perguntas em Aberto
|
## Perguntas em Aberto
|
||||||
|
|
||||||
- [ ] `SystemOS` deve expor `suspend_task`, `resume_task`, `close_task` e
|
- [x] `SystemOS` deve expor `suspend_task`, `resume_task`, `close_task` e
|
||||||
`crash_task` já no próximo corte?
|
`crash_task` já no próximo corte? Sim. Eles são a próxima API semântica de
|
||||||
- [ ] `foreground_task` e `background_task` devem ser operações explícitas do
|
lifecycle e evitam que firmware ou outros chamadores manipulem
|
||||||
`SystemOS`, ou apenas detalhe do `TaskManager`?
|
`TaskManager` e `ProcessManager` separadamente.
|
||||||
- [ ] Qual é a semântica exata de `Background`: processo ainda `Running`,
|
- [x] `foreground_task` e `background_task` devem ser operações explícitas do
|
||||||
|
`SystemOS`, ou apenas detalhe do `TaskManager`? `set_foreground_task(task_id)`
|
||||||
|
deve ser operação explícita do `SystemOS`. `background_task` fica fora da
|
||||||
|
primeira wave.
|
||||||
|
- [x] Qual é a semântica exata de `Background`: processo ainda `Running`,
|
||||||
processo `Suspended`, ou estado de presença sem garantia de execução?
|
processo `Suspended`, ou estado de presença sem garantia de execução?
|
||||||
- [ ] `resume_task` deve sempre mover a task para `Foreground`, ou pode retomar
|
Fora de escopo nesta wave. `Background` permanece estado reservado sem
|
||||||
para `Background`?
|
semântica normativa no primeiro contrato.
|
||||||
- [ ] O que acontece quando a task existe mas o processo associado não existe?
|
- [x] `resume_task` deve sempre mover a task para `Foreground`, ou pode retomar
|
||||||
- [ ] `close_task` remove imediatamente entidades fechadas ou apenas marca
|
para `Background`? Nesta wave, `resume_task` sempre move para
|
||||||
`Closed`/`Stopped` para coleta posterior?
|
`Foreground` e move o processo associado para `Running`.
|
||||||
- [ ] `crash_task` deve receber/armazenar `CrashReport`, ou isso fica em outro
|
- [x] O que acontece quando a task existe mas o processo associado não existe?
|
||||||
serviço de diagnóstico?
|
Isso é erro de lifecycle e deve retornar erro tipado, não `bool`.
|
||||||
- [ ] `GameRunningStep` deve transicionar para Hub/Home quando sua task deixa de
|
- [x] `close_task` remove imediatamente entidades fechadas ou apenas marca
|
||||||
estar `Foreground`, ou apenas não executar frame?
|
`Closed`/`Stopped` para coleta posterior? Apenas marca `TaskState::Closed` e
|
||||||
- [ ] O perfil de manifesto continua `AppMode::System`, ou abrimos uma migração
|
`ProcessState::Stopped`; coleta é operação futura separada.
|
||||||
formal para `AppMode::Shell`?
|
- [x] `crash_task` deve receber/armazenar `CrashReport`, ou isso fica em outro
|
||||||
- [ ] A decisão de ownership/layout em `AGD-0033` deve ser pré-requisito para o
|
serviço de diagnóstico? A operação deve aceitar ou preparar o contrato para
|
||||||
primeiro plan de lifecycle, ou pode ser executada em paralelo?
|
`CrashReport`, mas a primeira wave pode apenas coordenar estados e deixar o
|
||||||
|
armazenamento para diagnóstico/logging futuro.
|
||||||
|
- [x] `GameRunningStep` deve transicionar para Hub/Home quando sua task deixa de
|
||||||
|
estar `Foreground`, ou apenas não executar frame? Nesta wave, mantém a
|
||||||
|
invariant forte: `GameRunningStep` só suporta game task em `Foreground`; outro
|
||||||
|
estado é inválido e deve gerar diagnóstico/crash claro.
|
||||||
|
- [x] O perfil de manifesto continua `AppMode::System`, ou abrimos uma migração
|
||||||
|
formal para `AppMode::Shell`? Resolvido: `AppMode::System` já virou
|
||||||
|
`AppMode::Shell`.
|
||||||
|
- [x] A decisão de ownership/layout em `AGD-0033` deve ser pré-requisito para o
|
||||||
|
primeiro plan de lifecycle, ou pode ser executada em paralelo? Pode executar
|
||||||
|
em paralelo e não bloqueia esta wave. O plan de lifecycle deve evitar mover
|
||||||
|
`WindowManager`, `VirtualMachineRuntime`, Hub ou background services.
|
||||||
|
|
||||||
|
## Resolucao
|
||||||
|
|
||||||
|
A agenda fecha pela **Opcao B**: centralizar lifecycle no `SystemOS`.
|
||||||
|
|
||||||
|
`SystemOS` deixa de ser apenas um agregador de serviços e passa a ser a
|
||||||
|
autoridade normativa para coordenar `TaskState` e `ProcessState`. `TaskManager`
|
||||||
|
e `ProcessManager` continuam existindo, mas como mecanismos internos de
|
||||||
|
armazenamento e transição simples. O firmware não deve decidir diretamente como
|
||||||
|
task e processo mudam juntos.
|
||||||
|
|
||||||
|
A primeira wave deve cobrir apenas:
|
||||||
|
|
||||||
|
- `set_foreground_task(task_id)`
|
||||||
|
- `suspend_task(task_id)`
|
||||||
|
- `resume_task(task_id)`
|
||||||
|
- `close_task(task_id)`
|
||||||
|
- `crash_task(task_id, report?)`
|
||||||
|
|
||||||
|
O mapeamento inicial é:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TaskState::Foreground -> ProcessState::Running
|
||||||
|
TaskState::Suspended -> ProcessState::Suspended
|
||||||
|
TaskState::Closed -> ProcessState::Stopped
|
||||||
|
TaskState::Crashed -> ProcessState::Crashed
|
||||||
|
```
|
||||||
|
|
||||||
|
`Background` permanece reservado e explicitamente fora da primeira wave.
|
||||||
|
|
||||||
|
O primeiro plan recomendado é `PLAN-0032-A: SystemOS Lifecycle API`, com escopo
|
||||||
|
limitado a erro tipado, métodos de lifecycle em `SystemOS`, coordenação de
|
||||||
|
`TaskState`/`ProcessState`, testes unitários no nível `SystemOS` e nenhuma
|
||||||
|
mudança em `WindowManager`, `PrometeuHub`, background services ou UX completa de
|
||||||
|
suspensão/retomada.
|
||||||
|
|
||||||
## Criterio para Encerrar
|
## Criterio para Encerrar
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,203 @@
|
|||||||
|
---
|
||||||
|
id: DEC-0025
|
||||||
|
ticket: system-os-lifecycle-process-task-contract
|
||||||
|
title: SystemOS Lifecycle Authority for Tasks and Processes
|
||||||
|
status: accepted
|
||||||
|
created: 2026-05-15
|
||||||
|
ref_agenda: AGD-0032
|
||||||
|
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted. Esta decisão foi aceita em 2026-05-15 e passa a ser referência
|
||||||
|
normativa para a primeira wave de lifecycle de `Task` e `Process` no
|
||||||
|
`SystemOS`.
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
`SystemOS` já agrega serviços centrais do Prometeu OS, incluindo runtime VM,
|
||||||
|
processos e tasks. A arquitetura anterior ainda permitia que firmware,
|
||||||
|
`TaskManager` e `ProcessManager` decidissem transições de lifecycle de forma
|
||||||
|
distribuída.
|
||||||
|
|
||||||
|
Esse arranjo é insuficiente para um console OS: `Task` representa presença
|
||||||
|
navegável e experiência do usuário, enquanto `Process` representa execução
|
||||||
|
técnica. Essas duas dimensões precisam mudar juntas por uma autoridade
|
||||||
|
semântica única.
|
||||||
|
|
||||||
|
`DEC-0024` já separou ownership/layout de serviços do `SystemOS`. Esta decisão
|
||||||
|
é focada apenas em lifecycle de `Task`/`Process` e não reabre a migração de
|
||||||
|
serviços, `WindowManager`, Hub ou ownership da VM.
|
||||||
|
|
||||||
|
`AppMode::System` já foi renomeado para `AppMode::Shell`. O contrato público
|
||||||
|
fica:
|
||||||
|
|
||||||
|
- `AppMode::Game`: cartucho executado como jogo/session fullscreen.
|
||||||
|
- `AppMode::Shell`: cartucho executado como app gerenciado pela Shell do
|
||||||
|
Prometeu OS.
|
||||||
|
|
||||||
|
## Decisao
|
||||||
|
|
||||||
|
`SystemOS` SHALL ser a autoridade normativa de lifecycle para `Task` e
|
||||||
|
`Process`.
|
||||||
|
|
||||||
|
`TaskManager` e `ProcessManager` SHALL continuar existindo como mecanismos
|
||||||
|
internos de armazenamento e transição local, mas MUST NOT ser a API semântica
|
||||||
|
principal para lifecycle coordenado. Firmware e outros chamadores MUST delegate
|
||||||
|
transições semânticas de lifecycle para `SystemOS`.
|
||||||
|
|
||||||
|
A primeira wave de lifecycle SHALL expor o contrato mínimo:
|
||||||
|
|
||||||
|
```text
|
||||||
|
set_foreground_task(task_id)
|
||||||
|
suspend_task(task_id)
|
||||||
|
resume_task(task_id)
|
||||||
|
close_task(task_id)
|
||||||
|
crash_task(task_id, report?)
|
||||||
|
```
|
||||||
|
|
||||||
|
Essas operações SHALL coordenar `TaskState` e `ProcessState` juntos.
|
||||||
|
|
||||||
|
O mapeamento normativo inicial SHALL ser:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TaskState::Foreground -> ProcessState::Running
|
||||||
|
TaskState::Suspended -> ProcessState::Suspended
|
||||||
|
TaskState::Closed -> ProcessState::Stopped
|
||||||
|
TaskState::Crashed -> ProcessState::Crashed
|
||||||
|
```
|
||||||
|
|
||||||
|
`Background` SHALL remain reserved and MUST NOT guide any public lifecycle API
|
||||||
|
in the first wave.
|
||||||
|
|
||||||
|
`resume_task` SHALL mean resuming active user-perceived execution. In this first
|
||||||
|
wave, it MUST move a suspended task to `TaskState::Foreground` and its associated
|
||||||
|
process to `ProcessState::Running`.
|
||||||
|
|
||||||
|
`close_task` SHALL mark task/process state only. It MUST NOT remove task or
|
||||||
|
process entities immediately. Collection of closed/stopped entities is a future
|
||||||
|
separate operation.
|
||||||
|
|
||||||
|
`crash_task` SHALL be the semantic crash operation for a task. The first wave MAY
|
||||||
|
only coordinate `TaskState::Crashed` and `ProcessState::Crashed`, but the
|
||||||
|
signature SHOULD either accept or remain compatible with a future `CrashReport`
|
||||||
|
handoff to diagnostics/logging.
|
||||||
|
|
||||||
|
`GameRunningStep` SHALL keep the strong invariant that an active game execution
|
||||||
|
step only supports a game task in `TaskState::Foreground`. In this wave, any
|
||||||
|
non-foreground game task observed by `GameRunningStep` is invalid and should
|
||||||
|
produce clear diagnostic/crash behavior rather than silently running frames.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
Centralizing lifecycle in `SystemOS` prevents firmware from becoming the de
|
||||||
|
facto OS policy layer. Firmware should remain a boot/execution state machine; it
|
||||||
|
should not know how to update `TaskState` and `ProcessState` in lockstep.
|
||||||
|
|
||||||
|
Keeping `TaskManager` and `ProcessManager` as internal mechanisms preserves
|
||||||
|
simple storage and local transition responsibilities while giving the OS a
|
||||||
|
single semantic boundary for suspend, resume, close, crash and foreground
|
||||||
|
selection.
|
||||||
|
|
||||||
|
Leaving `Background` out of the first wave avoids freezing an incomplete model
|
||||||
|
for docked apps, background services, non-focused shell apps or capability-gated
|
||||||
|
execution. The reserved enum state can remain, but no normative public lifecycle
|
||||||
|
API should depend on it yet.
|
||||||
|
|
||||||
|
Making `resume_task` return to `Foreground` keeps the first contract simple and
|
||||||
|
matches the user-perceived meaning of resume. Future background wake/service
|
||||||
|
semantics should be introduced as separate concepts rather than overloading
|
||||||
|
`resume_task`.
|
||||||
|
|
||||||
|
Marking closed/stopped entities instead of removing them immediately preserves
|
||||||
|
room for diagnostics, crash handling, system UI history and later collection
|
||||||
|
policy.
|
||||||
|
|
||||||
|
## Invariantes / Contrato
|
||||||
|
|
||||||
|
- `SystemOS` MUST coordinate lifecycle operations that affect both task and
|
||||||
|
process state.
|
||||||
|
- Firmware MUST NOT call separate task/process manager transitions to implement
|
||||||
|
semantic lifecycle operations such as suspend, resume, close or crash.
|
||||||
|
- `set_foreground_task(task_id)` MUST set the task to `TaskState::Foreground`
|
||||||
|
and its associated process to `ProcessState::Running`.
|
||||||
|
- `suspend_task(task_id)` MUST set the task to `TaskState::Suspended` and its
|
||||||
|
associated process to `ProcessState::Suspended`.
|
||||||
|
- `resume_task(task_id)` MUST move from suspended user-visible execution back to
|
||||||
|
foreground execution: `TaskState::Foreground` and `ProcessState::Running`.
|
||||||
|
- `close_task(task_id)` MUST set `TaskState::Closed` and
|
||||||
|
`ProcessState::Stopped`.
|
||||||
|
- `crash_task(task_id, report?)` MUST set `TaskState::Crashed` and
|
||||||
|
`ProcessState::Crashed`.
|
||||||
|
- A missing task MUST return a typed lifecycle error such as
|
||||||
|
`LifecycleError::TaskNotFound(TaskId)`.
|
||||||
|
- A task whose associated process is missing MUST return a typed lifecycle error
|
||||||
|
such as `LifecycleError::ProcessNotFound(ProcessId)`.
|
||||||
|
- Invalid lifecycle transitions SHOULD return a typed error carrying the task,
|
||||||
|
source state and requested operation.
|
||||||
|
- `Background` MUST NOT have normative first-wave semantics.
|
||||||
|
- This decision MUST NOT move `WindowManager`, redesign `PrometeuHub`, introduce
|
||||||
|
background services, implement docking, implement app switching, or make
|
||||||
|
`ProcessManager` own the VM.
|
||||||
|
|
||||||
|
The expected first-wave error shape is:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum LifecycleError {
|
||||||
|
TaskNotFound(TaskId),
|
||||||
|
ProcessNotFound(ProcessId),
|
||||||
|
InvalidTransition {
|
||||||
|
task_id: TaskId,
|
||||||
|
from: TaskState,
|
||||||
|
operation: LifecycleOperation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`LifecycleOperation` SHOULD identify at least:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SetForeground
|
||||||
|
Suspend
|
||||||
|
Resume
|
||||||
|
Close
|
||||||
|
Crash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Impactos
|
||||||
|
|
||||||
|
- `prometeu-system` must expose lifecycle methods on `SystemOS`.
|
||||||
|
- `TaskManager` and `ProcessManager` may need smaller internal transition
|
||||||
|
helpers, but not public semantic ownership of lifecycle.
|
||||||
|
- Firmware code should call `system_os.suspend_task(...)`,
|
||||||
|
`system_os.resume_task(...)`, `system_os.close_task(...)`,
|
||||||
|
`system_os.crash_task(...)` and `system_os.set_foreground_task(...)` instead
|
||||||
|
of coordinating task/process managers directly.
|
||||||
|
- Tests should move lifecycle assertions to the `SystemOS` level.
|
||||||
|
- Specs do not need to change before the first code plan unless the lifecycle
|
||||||
|
API is promoted to a public external contract.
|
||||||
|
- `DEC-0024` remains valid and complementary; ownership/layout work can execute
|
||||||
|
in parallel and does not block this first lifecycle wave.
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- Agenda: `AGD-0032`
|
||||||
|
- Related decision: `DEC-0024`
|
||||||
|
|
||||||
|
## Propagacao Necessaria
|
||||||
|
|
||||||
|
- Plan: create `PLAN-0032-A: SystemOS Lifecycle API`.
|
||||||
|
- Code: update `prometeu-system` `SystemOS`, task/process lifecycle helpers,
|
||||||
|
firmware callsites if they currently coordinate managers directly, and unit
|
||||||
|
tests.
|
||||||
|
- Tests: add unit coverage for successful lifecycle mappings, missing task,
|
||||||
|
missing process and invalid transition handling.
|
||||||
|
- Discussion: keep `Background` reserved for a future agenda covering dock,
|
||||||
|
services, non-focused shell apps and capability-gated execution.
|
||||||
|
- Learn: after implementation is complete, write or update lessons only from the
|
||||||
|
published state.
|
||||||
|
|
||||||
|
## Revisao
|
||||||
|
|
||||||
|
- 2026-05-15: Initial accepted decision from `AGD-0032`.
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0055
|
||||||
|
ticket: system-os-lifecycle-process-task-contract
|
||||||
|
title: SystemOS Lifecycle Core API
|
||||||
|
status: done
|
||||||
|
created: 2026-05-15
|
||||||
|
ref_decisions: [DEC-0025]
|
||||||
|
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Implement the first-wave `SystemOS` lifecycle API required by `DEC-0025`.
|
||||||
|
`SystemOS` becomes the semantic coordinator for `TaskState` and `ProcessState`;
|
||||||
|
`TaskManager` and `ProcessManager` remain storage and simple transition
|
||||||
|
mechanisms.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0025`: `SystemOS` is the lifecycle authority for tasks and processes.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Add typed lifecycle operations to `prometeu-system` without changing firmware
|
||||||
|
behavior, WindowManager ownership, Hub UI, background services, docking, app
|
||||||
|
switching, or VM ownership.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Add `LifecycleError` and `LifecycleOperation` in `prometeu-system`.
|
||||||
|
- Add lifecycle methods on `SystemOS`:
|
||||||
|
- `set_foreground_task(task_id)`
|
||||||
|
- `suspend_task(task_id)`
|
||||||
|
- `resume_task(task_id)`
|
||||||
|
- `close_task(task_id)`
|
||||||
|
- `crash_task(task_id, report?)`
|
||||||
|
- Coordinate task and process state changes inside `SystemOS`.
|
||||||
|
- Preserve `TaskManager` and `ProcessManager` as internal state managers.
|
||||||
|
- Keep `Background` reserved and out of public lifecycle semantics.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Background semantics.
|
||||||
|
- Docking.
|
||||||
|
- Background services.
|
||||||
|
- App switcher.
|
||||||
|
- WindowManager migration.
|
||||||
|
- PrometeuHub redesign.
|
||||||
|
- Game suspend/resume UX.
|
||||||
|
- ProcessManager owning VM execution.
|
||||||
|
- Collection/removal of closed or stopped entities.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Add lifecycle types.
|
||||||
|
- Create a lifecycle module or colocate the types under
|
||||||
|
`crates/console/prometeu-system/src/os/`.
|
||||||
|
- Define `LifecycleOperation` with `SetForeground`, `Suspend`, `Resume`,
|
||||||
|
`Close`, and `Crash`.
|
||||||
|
- Define `LifecycleError` with `TaskNotFound(TaskId)`,
|
||||||
|
`ProcessNotFound(ProcessId)`, and `InvalidTransition { task_id, from,
|
||||||
|
operation }`.
|
||||||
|
- Re-export the types through `prometeu-system` only if the existing public
|
||||||
|
module style requires it.
|
||||||
|
|
||||||
|
2. Add the `SystemOS` coordination helpers.
|
||||||
|
- Target file: `crates/console/prometeu-system/src/os/system_os.rs`.
|
||||||
|
- Resolve the task first through `task_manager`.
|
||||||
|
- Extract the task's associated `ProcessId`.
|
||||||
|
- Validate that the process exists before mutating state.
|
||||||
|
- Return typed errors instead of `bool`.
|
||||||
|
|
||||||
|
3. Implement first-wave operations.
|
||||||
|
- `set_foreground_task`: call `task_manager.set_foreground(task_id)` and
|
||||||
|
`process_manager.mark_running(process_id)`.
|
||||||
|
- `suspend_task`: set `TaskState::Suspended` and
|
||||||
|
`ProcessState::Suspended`.
|
||||||
|
- `resume_task`: require a suspended task, then set
|
||||||
|
`TaskState::Foreground` and `ProcessState::Running`.
|
||||||
|
- `close_task`: set `TaskState::Closed` and `ProcessState::Stopped`.
|
||||||
|
- `crash_task`: set `TaskState::Crashed` and `ProcessState::Crashed`.
|
||||||
|
|
||||||
|
4. Keep manager APIs mechanical.
|
||||||
|
- Target files:
|
||||||
|
`crates/console/prometeu-system/src/services/task/task_manager.rs` and
|
||||||
|
`crates/console/prometeu-system/src/services/process/process_manager.rs`.
|
||||||
|
- Do not remove existing manager methods in this plan.
|
||||||
|
- Add only narrow helpers if needed to support the `SystemOS` API cleanly.
|
||||||
|
|
||||||
|
5. Update task creation to use the lifecycle API internally.
|
||||||
|
- In `SystemOS::create_vm_game_task`, `create_vm_shell_task`, and
|
||||||
|
`create_native_shell_task`, replace direct foreground manager calls with
|
||||||
|
`set_foreground_task`.
|
||||||
|
- Keep the constructors returning `TaskId`; handle impossible internal
|
||||||
|
errors with explicit debug/assert behavior or an internal helper that
|
||||||
|
cannot fail after creation.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `SystemOS` exposes all five first-wave lifecycle methods from `DEC-0025`.
|
||||||
|
- All five methods coordinate both task and process state.
|
||||||
|
- Missing task returns `LifecycleError::TaskNotFound`.
|
||||||
|
- Missing associated process returns `LifecycleError::ProcessNotFound`.
|
||||||
|
- Invalid resume from a non-suspended task returns `InvalidTransition`.
|
||||||
|
- `Background` is not exposed through a new lifecycle API.
|
||||||
|
- Closed and stopped entities are not removed by `close_task`.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Add or update unit tests in `prometeu-system` for each lifecycle mapping.
|
||||||
|
- Add tests for missing task, missing process, and invalid resume transition.
|
||||||
|
- Run the affected Rust test suite for `prometeu-system`.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Existing direct manager access is public enough that callsites may keep using
|
||||||
|
it. This plan does not remove those methods; enforcement continues in
|
||||||
|
`PLN-0056`.
|
||||||
|
- `TaskManager::set_foreground` currently moves the previous foreground task to
|
||||||
|
`Background`. Since `Background` is out of first-wave semantics, tests must
|
||||||
|
avoid treating that transition as normative lifecycle policy.
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0056
|
||||||
|
ticket: system-os-lifecycle-process-task-contract
|
||||||
|
title: Firmware Lifecycle Delegation
|
||||||
|
status: done
|
||||||
|
created: 2026-05-15
|
||||||
|
ref_decisions: [DEC-0025]
|
||||||
|
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Move firmware lifecycle callsites to the `SystemOS` semantic API introduced by
|
||||||
|
`PLN-0055`. Firmware remains a boot and execution state machine; it must not
|
||||||
|
coordinate task/process lifecycle by calling `TaskManager` and `ProcessManager`
|
||||||
|
separately.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0025`: firmware must delegate semantic lifecycle transitions to
|
||||||
|
`SystemOS`.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Replace direct lifecycle manipulation in firmware with `SystemOS` lifecycle
|
||||||
|
methods while preserving current observable behavior.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Update cartridge launch paths to use `SystemOS` lifecycle authority when
|
||||||
|
placing newly created tasks in foreground.
|
||||||
|
- Update crash paths where a task crash is detected so task/process state moves
|
||||||
|
through `SystemOS::crash_task`.
|
||||||
|
- Preserve the strong `GameRunningStep` invariant: a running game step only
|
||||||
|
supports a game task in `TaskState::Foreground`.
|
||||||
|
- Keep `SystemRunningStep`, Hub, and WindowManager behavior unchanged except for
|
||||||
|
lifecycle delegation required by `DEC-0025`.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Full game suspend/resume UX.
|
||||||
|
- Returning to Hub/Home on suspend or close.
|
||||||
|
- Shell UI redesign.
|
||||||
|
- App switcher.
|
||||||
|
- Background task API.
|
||||||
|
- WindowManager migration.
|
||||||
|
- Diagnostics service or crash report persistence.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Audit direct lifecycle manager usage.
|
||||||
|
- Target files:
|
||||||
|
`crates/console/prometeu-firmware/src/firmware/*.rs`,
|
||||||
|
`crates/console/prometeu-system/src/os/system_os.rs`.
|
||||||
|
- Search for direct calls to `task_manager.set_foreground`,
|
||||||
|
`task_manager.mark_suspended`, `task_manager.close_task`,
|
||||||
|
`task_manager.mark_crashed`, `process_manager.mark_suspended`,
|
||||||
|
`process_manager.mark_stopped`, and `process_manager.mark_crashed`.
|
||||||
|
|
||||||
|
2. Keep launch construction inside `SystemOS`.
|
||||||
|
- Target file:
|
||||||
|
`crates/console/prometeu-system/src/os/system_os.rs`.
|
||||||
|
- Ensure `create_vm_game_task`, `create_vm_shell_task`, and
|
||||||
|
`create_native_shell_task` coordinate foreground/running state through the
|
||||||
|
lifecycle core API from `PLN-0055`.
|
||||||
|
|
||||||
|
3. Update game crash handling.
|
||||||
|
- Target file:
|
||||||
|
`crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs`.
|
||||||
|
- When the task is missing, preserve current crash-screen behavior. If the
|
||||||
|
task does not exist, `SystemOS::crash_task` cannot attach state and the
|
||||||
|
firmware should continue producing a clear crash report.
|
||||||
|
- When a foreground invariant fails for an existing task, call
|
||||||
|
`ctx.os.crash_task(task_id, report?)` before transitioning to
|
||||||
|
`AppCrashesStep`.
|
||||||
|
- When `tick_vm` returns a crash report for the active task, call
|
||||||
|
`ctx.os.crash_task(task_id, report?)` before transitioning to
|
||||||
|
`AppCrashesStep`.
|
||||||
|
|
||||||
|
4. Keep shell launch behavior unchanged.
|
||||||
|
- Target file:
|
||||||
|
`crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs`.
|
||||||
|
- Do not move `WindowManager` logic.
|
||||||
|
- Do not change shell window creation or focus behavior.
|
||||||
|
- Rely on `SystemOS::create_vm_shell_task` to coordinate task/process
|
||||||
|
foreground state.
|
||||||
|
|
||||||
|
5. Avoid introducing background lifecycle.
|
||||||
|
- Do not add `background_task`.
|
||||||
|
- Do not treat `TaskState::Background` as a public lifecycle result in
|
||||||
|
firmware.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Firmware does not coordinate task/process lifecycle by manually changing both
|
||||||
|
managers.
|
||||||
|
- Game VM crash results in `SystemOS::crash_task` for the active task when the
|
||||||
|
task still exists.
|
||||||
|
- Foreground invariant violation in `GameRunningStep` crashes the task through
|
||||||
|
`SystemOS`.
|
||||||
|
- Launch behavior for `AppMode::Game` and `AppMode::Shell` remains unchanged.
|
||||||
|
- WindowManager and Hub behavior are not redesigned or moved.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Run firmware tests that cover cartridge launch for `AppMode::Game` and
|
||||||
|
`AppMode::Shell`.
|
||||||
|
- Add or update tests for `GameRunningStep` crash delegation if the existing
|
||||||
|
test harness supports firmware step testing.
|
||||||
|
- Run the affected Rust test suite for `prometeu-firmware`.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Crash report ownership is not final. This plan should pass the report through
|
||||||
|
the lifecycle API only to the extent supported by `PLN-0055`; persistence
|
||||||
|
remains out of scope.
|
||||||
|
- `GameRunningStep` currently reads task state directly. This is acceptable for
|
||||||
|
enforcing the foreground invariant, but it must not become a place that
|
||||||
|
coordinates lifecycle state across managers.
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0057
|
||||||
|
ticket: system-os-lifecycle-process-task-contract
|
||||||
|
title: Lifecycle Tests and Invariants
|
||||||
|
status: done
|
||||||
|
created: 2026-05-15
|
||||||
|
ref_decisions: [DEC-0025]
|
||||||
|
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Add focused test coverage and invariant checks for the lifecycle contract
|
||||||
|
introduced by `PLN-0055` and consumed by `PLN-0056`.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0025`: first-wave lifecycle maps task states to process states through
|
||||||
|
`SystemOS` and keeps `Background` outside the public lifecycle contract.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Make the lifecycle contract executable through unit tests and regression tests,
|
||||||
|
without expanding scope into background execution, UI behavior, or service
|
||||||
|
ownership.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Add `SystemOS` unit tests for all first-wave lifecycle operations.
|
||||||
|
- Add negative tests for missing task, missing process, and invalid transition.
|
||||||
|
- Add firmware-level regression coverage for lifecycle delegation where the
|
||||||
|
existing test harness can observe it.
|
||||||
|
- Keep manager tests focused on local storage and simple transitions.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- End-to-end UX tests for suspend/resume.
|
||||||
|
- Hub/Home navigation after lifecycle changes.
|
||||||
|
- Background services.
|
||||||
|
- Docking.
|
||||||
|
- App switching.
|
||||||
|
- Diagnostics persistence.
|
||||||
|
- WindowManager behavior changes.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Add core lifecycle mapping tests.
|
||||||
|
- Target file: `crates/console/prometeu-system/src/os/system_os.rs` or a
|
||||||
|
dedicated `os` test module following existing crate style.
|
||||||
|
- Verify:
|
||||||
|
- `set_foreground_task`: task becomes `Foreground`, process becomes
|
||||||
|
`Running`.
|
||||||
|
- `suspend_task`: task becomes `Suspended`, process becomes `Suspended`.
|
||||||
|
- `resume_task`: suspended task becomes `Foreground`, process becomes
|
||||||
|
`Running`.
|
||||||
|
- `close_task`: task becomes `Closed`, process becomes `Stopped`.
|
||||||
|
- `crash_task`: task becomes `Crashed`, process becomes `Crashed`.
|
||||||
|
|
||||||
|
2. Add error tests.
|
||||||
|
- Verify missing task returns `LifecycleError::TaskNotFound`.
|
||||||
|
- Verify a task whose process no longer exists returns
|
||||||
|
`LifecycleError::ProcessNotFound`.
|
||||||
|
- Verify invalid resume from non-suspended task returns
|
||||||
|
`LifecycleError::InvalidTransition` with operation `Resume`.
|
||||||
|
|
||||||
|
3. Add non-removal tests.
|
||||||
|
- Verify `close_task` leaves the closed task available through
|
||||||
|
`TaskManager::get`.
|
||||||
|
- Verify `close_task` leaves the stopped process available through
|
||||||
|
`ProcessManager::get`.
|
||||||
|
- Do not call `remove_closed` or `remove_stopped` as part of lifecycle API
|
||||||
|
tests.
|
||||||
|
|
||||||
|
4. Add background exclusion tests.
|
||||||
|
- Assert there is no `SystemOS::background_task` API added by this wave.
|
||||||
|
- Keep any existing `TaskState::Background` behavior in manager-level tests
|
||||||
|
documented as manager behavior, not first-wave lifecycle semantics.
|
||||||
|
|
||||||
|
5. Add firmware regression tests after `PLN-0056`.
|
||||||
|
- Target file: `crates/console/prometeu-firmware/src/firmware/firmware.rs`
|
||||||
|
or the relevant step test module.
|
||||||
|
- Verify game launch still enters `GameRunningStep`.
|
||||||
|
- Verify shell launch still enters `SystemRunningStep`.
|
||||||
|
- Verify a game crash path marks the active task as crashed when the test
|
||||||
|
harness can observe `ctx.os.task_manager`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Every lifecycle method from `DEC-0025` has a positive `SystemOS` test.
|
||||||
|
- Missing task and missing process return typed lifecycle errors.
|
||||||
|
- Invalid resume transition is covered by a test.
|
||||||
|
- `close_task` is tested as mark-only, not removal.
|
||||||
|
- No test encodes `Background` as a normative first-wave lifecycle operation.
|
||||||
|
- Firmware regression tests still pass for game and shell launch.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Run `cargo test -p prometeu-system`.
|
||||||
|
- Run `cargo test -p prometeu-firmware`.
|
||||||
|
- If workspace-level tests are practical after these changes, run the relevant
|
||||||
|
workspace command used by this repository.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Some negative tests may require test-only helpers to create an inconsistent
|
||||||
|
task/process relationship. Prefer narrow test fixtures over making corruption
|
||||||
|
APIs public.
|
||||||
|
- Compile-time absence of `background_task` may not be directly testable
|
||||||
|
without compile-fail infrastructure. In that case, validate it by code review
|
||||||
|
and keep runtime tests focused on the exposed first-wave methods.
|
||||||
Loading…
x
Reference in New Issue
Block a user