From 1b4565558d5b2d979e9f30880b95aa2c558512c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:50:27 +0200 Subject: [PATCH 1/7] Add Ghost Twin execution crate --- crates/ghost-twin/Cargo.toml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 crates/ghost-twin/Cargo.toml diff --git a/crates/ghost-twin/Cargo.toml b/crates/ghost-twin/Cargo.toml new file mode 100644 index 0000000..de4f62e --- /dev/null +++ b/crates/ghost-twin/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ghost-twin" +version = "0.1.0" +edition = "2021" +description = "Deterministic Ghost/Twin execution boundary for SentinelAI" +license = "Apache-2.0" + +[dependencies] +sentinel-core = { path = "../sentinel-core" } From f7d8275d1d24844900a4f9f17700e7cbbcd9d88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:50:32 +0200 Subject: [PATCH 2/7] Add deterministic Ghost Twin gate --- crates/ghost-twin/src/lib.rs | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 crates/ghost-twin/src/lib.rs diff --git a/crates/ghost-twin/src/lib.rs b/crates/ghost-twin/src/lib.rs new file mode 100644 index 0000000..9e9c503 --- /dev/null +++ b/crates/ghost-twin/src/lib.rs @@ -0,0 +1,104 @@ +#![forbid(unsafe_code)] + +use sentinel_core::{Decision, SentinelCore}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TradeIntent { + pub chain_id: u64, + pub max_slippage_bps: u16, + pub amount_units: u64, + pub live_requested: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PaperFill { + pub filled: bool, + pub amount_units: u64, + pub slippage_bps: u16, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GateDecision { + Shadow, + Deny, + Live, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ExecutionGate { + pub allow_live: bool, +} + +impl ExecutionGate { + pub const fn denied() -> Self { + Self { allow_live: false } + } + + pub const fn live_enabled() -> Self { + Self { allow_live: true } + } + + #[inline(always)] + pub const fn decide(&self, risk: Decision, live_requested: bool) -> GateDecision { + match risk { + Decision::Block => GateDecision::Deny, + Decision::Monitor if live_requested && self.allow_live => GateDecision::Live, + Decision::Allow if live_requested && self.allow_live => GateDecision::Live, + _ => GateDecision::Shadow, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TwinEngine { + pub gate: ExecutionGate, +} + +impl TwinEngine { + pub const fn new(gate: ExecutionGate) -> Self { + Self { gate } + } + + #[inline(always)] + pub fn evaluate(&self, core: &mut SentinelCore, intent: TradeIntent, payload: &[u8]) -> (Decision, GateDecision, PaperFill) { + let risk = core.scan(payload); + let gate = self.gate.decide(risk, intent.live_requested); + let fill = match gate { + GateDecision::Deny => PaperFill { filled: false, amount_units: 0, slippage_bps: 0 }, + GateDecision::Shadow | GateDecision::Live => PaperFill { + filled: true, + amount_units: intent.amount_units, + slippage_bps: intent.max_slippage_bps, + }, + }; + (risk, gate, fill) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn live_is_disabled_by_default() { + let engine = TwinEngine::new(ExecutionGate::denied()); + let mut core = SentinelCore::new(); + let intent = TradeIntent { chain_id: 1, max_slippage_bps: 50, amount_units: 100, live_requested: true }; + let (_, gate, fill) = engine.evaluate(&mut core, intent, b"clean"); + assert_eq!(gate, GateDecision::Shadow); + assert!(fill.filled); + } + + #[test] + fn breach_denies_even_when_live_enabled() { + let engine = TwinEngine::new(ExecutionGate::live_enabled()); + let mut core = SentinelCore::new(); + core.observe(sentinel_core::Signal::Tamper, 0); + core.observe(sentinel_core::Signal::Root, 0); + let intent = TradeIntent { chain_id: 1, max_slippage_bps: 50, amount_units: 100, live_requested: true }; + let (risk, gate, fill) = engine.evaluate(&mut core, intent, b"clean"); + assert_eq!(risk, Decision::Block); + assert_eq!(gate, GateDecision::Deny); + assert!(!fill.filled); + } +} From 3935fc05884d5bc26bd575c8dea1fe4694b0bdbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:50:37 +0200 Subject: [PATCH 3/7] Document Ghost Twin boundary --- crates/ghost-twin/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 crates/ghost-twin/README.md diff --git a/crates/ghost-twin/README.md b/crates/ghost-twin/README.md new file mode 100644 index 0000000..420ff7d --- /dev/null +++ b/crates/ghost-twin/README.md @@ -0,0 +1,12 @@ +# ghost-twin + +Isolated deterministic execution boundary for SentinelAI. + +```text +Ghost -> TradeIntent -> Sentinel risk boundary -> Twin paper/shadow fill + \\-> ExecutionGate -> live only when allow_live=true +``` + +The crate contains no key management or signing and performs no external network operations. A future Jito adapter should accept already-signed transactions at an outer integration boundary. + +Live execution is disabled by default. From bcd512b3056fffd17a4eda03b5b8e38ff8bf7699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:50:48 +0200 Subject: [PATCH 4/7] Register Ghost Twin workspace member --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a81a6fd..8832925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [workspace] -members = ["crates/sentinel-core"] +members = ["crates/sentinel-core", "crates/ghost-twin"] resolver = "2" [dependencies] From 7956cc3e744593c33bb0afe0d1505e3da595f680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:54:09 +0200 Subject: [PATCH 5/7] Harden Ghost Twin live gate --- crates/ghost-twin/src/lib.rs | 48 +++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/crates/ghost-twin/src/lib.rs b/crates/ghost-twin/src/lib.rs index 9e9c503..763cd4e 100644 --- a/crates/ghost-twin/src/lib.rs +++ b/crates/ghost-twin/src/lib.rs @@ -42,9 +42,9 @@ impl ExecutionGate { pub const fn decide(&self, risk: Decision, live_requested: bool) -> GateDecision { match risk { Decision::Block => GateDecision::Deny, - Decision::Monitor if live_requested && self.allow_live => GateDecision::Live, Decision::Allow if live_requested && self.allow_live => GateDecision::Live, - _ => GateDecision::Shadow, + Decision::Monitor => GateDecision::Shadow, + Decision::Allow => GateDecision::Shadow, } } } @@ -60,11 +60,20 @@ impl TwinEngine { } #[inline(always)] - pub fn evaluate(&self, core: &mut SentinelCore, intent: TradeIntent, payload: &[u8]) -> (Decision, GateDecision, PaperFill) { + pub fn evaluate( + &self, + core: &mut SentinelCore, + intent: TradeIntent, + payload: &[u8], + ) -> (Decision, GateDecision, PaperFill) { let risk = core.scan(payload); let gate = self.gate.decide(risk, intent.live_requested); let fill = match gate { - GateDecision::Deny => PaperFill { filled: false, amount_units: 0, slippage_bps: 0 }, + GateDecision::Deny => PaperFill { + filled: false, + amount_units: 0, + slippage_bps: 0, + }, GateDecision::Shadow | GateDecision::Live => PaperFill { filled: true, amount_units: intent.amount_units, @@ -83,19 +92,46 @@ mod tests { fn live_is_disabled_by_default() { let engine = TwinEngine::new(ExecutionGate::denied()); let mut core = SentinelCore::new(); - let intent = TradeIntent { chain_id: 1, max_slippage_bps: 50, amount_units: 100, live_requested: true }; + let intent = TradeIntent { + chain_id: 1, + max_slippage_bps: 50, + amount_units: 100, + live_requested: true, + }; let (_, gate, fill) = engine.evaluate(&mut core, intent, b"clean"); assert_eq!(gate, GateDecision::Shadow); assert!(fill.filled); } + #[test] + fn monitor_can_never_go_live() { + let engine = TwinEngine::new(ExecutionGate::live_enabled()); + let mut core = SentinelCore::new(); + core.observe(sentinel_core::Signal::Debugger, 0); + let intent = TradeIntent { + chain_id: 1, + max_slippage_bps: 50, + amount_units: 100, + live_requested: true, + }; + let (risk, gate, fill) = engine.evaluate(&mut core, intent, b"clean"); + assert_eq!(risk, Decision::Monitor); + assert_eq!(gate, GateDecision::Shadow); + assert!(fill.filled); + } + #[test] fn breach_denies_even_when_live_enabled() { let engine = TwinEngine::new(ExecutionGate::live_enabled()); let mut core = SentinelCore::new(); core.observe(sentinel_core::Signal::Tamper, 0); core.observe(sentinel_core::Signal::Root, 0); - let intent = TradeIntent { chain_id: 1, max_slippage_bps: 50, amount_units: 100, live_requested: true }; + let intent = TradeIntent { + chain_id: 1, + max_slippage_bps: 50, + amount_units: 100, + live_requested: true, + }; let (risk, gate, fill) = engine.evaluate(&mut core, intent, b"clean"); assert_eq!(risk, Decision::Block); assert_eq!(gate, GateDecision::Deny); From e8169b36165fc5af8a5647e65f478b9ff1b09605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:54:13 +0200 Subject: [PATCH 6/7] Add Rust workspace validation gate --- .github/workflows/rust-workspace.yml | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/rust-workspace.yml diff --git a/.github/workflows/rust-workspace.yml b/.github/workflows/rust-workspace.yml new file mode 100644 index 0000000..dad46de --- /dev/null +++ b/.github/workflows/rust-workspace.yml @@ -0,0 +1,38 @@ +name: Rust Workspace Validation + +on: + pull_request: + paths: + - 'src/**/*.rs' + - 'tests/**/*.rs' + - 'crates/**/*.rs' + - 'crates/**/Cargo.toml' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust-abi.yml' + - '.github/workflows/rust-workspace.yml' + push: + branches: + - main + +permissions: + contents: read + +jobs: + test-rust-workspace: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust Stable Toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Check workspace + run: cargo check --workspace + + - name: Test workspace + run: cargo test --workspace --all-targets From cffdef3d845a41a0c51bf2a4eb8825d9c0632d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cryptofixyup=F0=9F=94=A5?= <199330534+cryptofixyup@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:54:48 +0200 Subject: [PATCH 7/7] Fix const atomic decision compile error --- crates/sentinel-core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sentinel-core/src/lib.rs b/crates/sentinel-core/src/lib.rs index 2af4349..2b4aa49 100644 --- a/crates/sentinel-core/src/lib.rs +++ b/crates/sentinel-core/src/lib.rs @@ -138,7 +138,7 @@ impl SentinelCore { self.decision() } #[inline(always)] fn scan_token(&mut self, data: &[u8], token: &[u8], signal: Signal) { if let Some(offset) = find_subslice(data, token) { self.observe(signal, offset); } } - #[inline(always)] pub const fn decision(&self) -> Decision { match State::from_raw(self.state.load(Ordering::Acquire)) { State::Monitor => Decision::Allow, State::Anomalous => Decision::Monitor, State::Breached => Decision::Block } } + #[inline(always)] pub fn decision(&self) -> Decision { match State::from_raw(self.state.load(Ordering::Acquire)) { State::Monitor => Decision::Allow, State::Anomalous => Decision::Monitor, State::Breached => Decision::Block } } #[inline(always)] pub const fn event_count(&self) -> usize { self.ring.len() } #[inline(always)] pub fn event(&self, index: usize) -> Option { self.ring.get(index) } }