mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 09:09:19 +00:00
feat: unified event bus, sealed state machines, and startup verification
Introduce a unified EventBus as the single broadcast channel for all system events, replacing the 6 disconnected event mechanisms. Seal Thread/Turn/ContainerState fields behind private accessors with validated transitions to prevent invalid state mutations. Fix TOCTOU races in thread_ops and session_manager. Event bus (src/event_bus/): - SystemEvent envelope with EventPayload (Domain, StateChange, Telemetry, StateTransition, ToolExecution, AuthEvent, ConfigChange) - Four sinks: SSE (→SseManager), audit (→DB with JSONL fallback), state (→StateBus), metrics (→Observer) - AuditStore trait + implementations for PostgreSQL and libSQL - V13 audit_log migration for both backends - Wired into AppComponents and AgentDeps (Option<EventBus> for compat) - Worker dual-emit through bus alongside legacy SSE+DB paths Sealed state machines: - Thread.state private with state() accessor, can_transition_to(), set_processing(), reset_to_idle() - Turn.state private with state() accessor - ContainerHandle.state private with new() constructor, mark_running/stopped/failed(), can_transition_to() - TOCTOU fix: thread_ops moves safety validation before lock, then checks state + starts turn atomically under single lock - SessionManager TOCTOU fix: atomic check-and-insert with write lock held for entire UUID adoption sequence Startup verification: - AppComponents::verify_readiness() checks component presence vs config - ToolRegistry::verify_expected_tools() validates builtin registration - Config::validate() checks cross-field invariants (Docker, WASM dir) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6fc652a24d
commit
274175184e
@@ -213,8 +213,10 @@ mod tests {
|
||||
assert_eq!(counts.len(), 3, "Should return 3 routines"); // safety: test-only
|
||||
assert_eq!(counts[&r1], 2, "r1 should have 2 running"); // safety: test-only
|
||||
assert_eq!(counts[&r2], 1, "r2 should have 1 running"); // safety: test-only
|
||||
assert_eq!( // safety: test-only
|
||||
counts[&r3], 0,
|
||||
assert_eq!(
|
||||
// safety: test-only
|
||||
counts[&r3],
|
||||
0,
|
||||
"r3 should have 0 running (Ok status is not running)"
|
||||
);
|
||||
}
|
||||
@@ -360,8 +362,10 @@ mod tests {
|
||||
.await
|
||||
.expect("batch query should work"); // safety: test-only
|
||||
|
||||
assert_eq!( // safety: test-only
|
||||
counts[&routine_id], 2,
|
||||
assert_eq!(
|
||||
// safety: test-only
|
||||
counts[&routine_id],
|
||||
2,
|
||||
"Should only count 2 Running status runs"
|
||||
);
|
||||
}
|
||||
@@ -454,12 +458,16 @@ mod tests {
|
||||
.expect("batch query should work"); // safety: test-only
|
||||
|
||||
// Verify counts match the limits
|
||||
assert_eq!( // safety: test-only
|
||||
counts[&r1], 1,
|
||||
assert_eq!(
|
||||
// safety: test-only
|
||||
counts[&r1],
|
||||
1,
|
||||
"r1 should have 1 running (at max_concurrent=1)"
|
||||
);
|
||||
assert_eq!( // safety: test-only
|
||||
counts[&r2], 2,
|
||||
assert_eq!(
|
||||
// safety: test-only
|
||||
counts[&r2],
|
||||
2,
|
||||
"r2 should have 2 running (at max_concurrent=2)"
|
||||
);
|
||||
|
||||
|
||||
@@ -310,7 +310,8 @@ fn domain_event_all_variants_serialize() {
|
||||
for variant in &variants {
|
||||
let json = serde_json::to_string(variant).unwrap(); // safety: test-only
|
||||
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); // safety: test-only
|
||||
assert!( // safety: test-only
|
||||
assert!(
|
||||
// safety: test-only
|
||||
// safety: test-only
|
||||
parsed.get("type").is_some(),
|
||||
"missing 'type' field in {:?}",
|
||||
|
||||
@@ -252,6 +252,7 @@ impl GatewayWorkflowHarness {
|
||||
http_interceptor: None,
|
||||
transcription: None,
|
||||
document_extraction: None,
|
||||
event_bus: Some(components.event_bus.clone()),
|
||||
},
|
||||
channels,
|
||||
None,
|
||||
|
||||
@@ -641,6 +641,7 @@ impl TestRigBuilder {
|
||||
},
|
||||
transcription: None,
|
||||
document_extraction: None,
|
||||
event_bus: Some(components.event_bus.clone()),
|
||||
};
|
||||
|
||||
// 7. Create TestChannel and ChannelManager.
|
||||
|
||||
Reference in New Issue
Block a user