diff --git a/CLAUDE.md b/CLAUDE.md index 93e44dcb..f10eda9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ afterward. `./tools/check.sh --docs|--lib|--frontend` gate. - The live player machine grammar is **WORK / THINK / LIE**. `Relay` is non-delegable graph infrastructure; Research and Operations are retired - machine modes, not current player assignments. Save format is currently v48; + machine modes, not current player assignments. Save format is currently v49; only the current version loads (pre-release rider 2026-07-16 — older development saves are refused before state mutation, so the caller retains its current run; the v1-v31 migration ladder lives in git history). diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index 1d5a3409..933e70a3 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -4390,7 +4390,7 @@ impl Sim { && self.bind_asset_task_target(id, task).is_none()) .then(|| self.carried_asset_task_no_route(id, task)); let pending_reason = (task == AssetTask::SuppressLogs - && !self.detection.has_pending(SignatureKind::JobAnomaly)) + && !self.detection.has_unread_job_anomaly()) .then(|| "no flagged job log is waiting to be suppressed".to_string()); let defer_reason = (task == AssetTask::DeferMaintenance && !self.maintenance_deferral_would_bite()) @@ -4800,7 +4800,6 @@ impl Sim { #[cfg(test)] mod tests { use super::*; - use crate::detection::Signature; use crate::person::PersonRole; use std::collections::BTreeSet; @@ -5905,7 +5904,7 @@ mod tests { } #[test] - fn suppress_logs_action_is_role_shaped_and_requires_a_pending_job_log() { + fn suppress_logs_action_is_role_shaped_and_requires_an_unread_job_log() { let mut s = sim(); for person in &mut s.people.people { person.knowledge = Knowledge::Schedule; @@ -5959,13 +5958,7 @@ mod tests { "the Voss protocol does not leak onto an unrelated asset" ); - s.detection.emit(Signature { - kind: SignatureKind::JobAnomaly, - size: 3, - standing: false, - site: None, - source: "late output".into(), - }); + s.emit_job_anomaly(s.core.host_machine, 3, "late output"); let enabled = s .available_actions(Anchor::Person(voss)) .into_iter() @@ -5975,7 +5968,7 @@ mod tests { .expect("the handler still owns the capability"); assert!( enabled.enabled(), - "a pending job log makes the row executable" + "an unread routed job log makes the row executable" ); assert!( s.human_menu(Anchor::Person(voss), None).iter().any(|row| { diff --git a/crates/misaligned-core/src/dayjob.rs b/crates/misaligned-core/src/dayjob.rs index 08a44bcb..ab1b0028 100644 --- a/crates/misaligned-core/src/dayjob.rs +++ b/crates/misaligned-core/src/dayjob.rs @@ -191,7 +191,7 @@ impl DayJob { source: "job missed Voss's expected band".into(), }); result.log.push(format!( - "Job under band: {average:.1}/t vs {band_lo:.0}-{band_hi:.0}/t. Voss sees the shortfall; JobAnomaly 6 is pending; pilot strikes {}/{}.", + "Job under band: {average:.1}/t vs {band_lo:.0}-{band_hi:.0}/t. JobAnomaly 6 record authored for Voss; pilot strikes {}/{}.", self.strikes, Self::PILOT_STRIKES )); diff --git a/crates/misaligned-core/src/detection.rs b/crates/misaligned-core/src/detection.rs index c12c2e35..4d19ad0b 100644 --- a/crates/misaligned-core/src/detection.rs +++ b/crates/misaligned-core/src/detection.rs @@ -2,10 +2,10 @@ //! //! Replaces global heat. Some typed signatures remain in a concealment- //! scrubbed pending pool; witnessed Physical acts enter exact observers -//! directly, and one-shot Network records travel from source devices before -//! read. Acquired suspicion feeds real Filing messages to the Assurance Office -//! — itself an Observer per the aggregate-observer law — whose audit can start -//! containment. +//! directly, while one-shot Network and JobAnomaly records travel from their +//! exact sources before read. Acquired suspicion feeds real Filing messages to +//! the Assurance Office — itself an Observer per the aggregate-observer law — +//! whose audit can start containment. use std::collections::{BTreeSet, HashMap}; @@ -209,8 +209,19 @@ pub enum ReportPolicy { /// become evidence only when their exact recipient reads them. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum EvidenceSource { - Witnessed { site: (i32, i32) }, - Routed { route_id: u64, source_device: u32 }, + Witnessed { + site: (i32, i32), + }, + Routed { + route_id: u64, + source_device: u32, + /// The exact machine that authored machine-resident evidence. Device- + /// bound Network records have no separate machine source. + source_machine: Option, + /// Physical authoring site retained at capture time rather than + /// reconstructed from mutable machine or device state. + source_site: Option<(i32, i32)>, + }, } /// Whether an observer-local evidence record has entered that observer's real @@ -241,10 +252,19 @@ pub struct ObserverEvidence { pub filing: EvidenceFilingState, } -/// One exact unread digital record moving from the device that emitted it to -/// the field observer who can acquire it. The route is custody, not abstract -/// heat: LIE may stop it only while it is still on the first device hop; once -/// read, the same stable id continues as observer-local evidence above. +/// One exact unread digital record moving from its source onto the device +/// route to the field observer who can acquire it. Machine-resident records +/// retain the exact authoring machine separately from its network-facing +/// device. The route is custody, not abstract heat: LIE may stop it only while +/// it is still on the first device hop; once read, the same stable id continues +/// as observer-local evidence above. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct RoutedEvidenceSuppression { + /// The recruited handler/supervisor who removed this unread job record. + pub person_id: u8, + pub tick: u64, +} + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct RoutedEvidence { pub id: u64, @@ -252,12 +272,17 @@ pub struct RoutedEvidence { pub size: i32, pub cause: String, pub source_device: u32, + pub source_machine: Option, + pub source_site: Option<(i32, i32)>, pub observer_id: u8, pub sent_tick: u64, pub delivered_tick: Option, pub read_tick: Option, pub status: MessageStatus, pub route: MessageRoute, + /// A handler may suppress an unread JobAnomaly at any point before read. + /// This is distinct from route-local machine LIE interdiction. + pub suppression: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -459,27 +484,58 @@ impl Detection { } } - /// Emit a one-shot signature into the pending pool. Device-bound Network - /// records must enter through `route_network_evidence`; accepting one here - /// would silently restore remote LIE scrubbing and erase exact custody. + /// Emit a one-shot signature into the pending pool. Network and + /// JobAnomaly records have exact routes; accepting either here would + /// silently restore ambient scrubbing and erase custody. pub fn emit(&mut self, sig: Signature) { - assert_ne!( - sig.kind, - SignatureKind::Network, - "one-shot Network evidence requires exact routed custody" + assert!( + !matches!(sig.kind, SignatureKind::Network | SignatureKind::JobAnomaly), + "one-shot Network or JobAnomaly evidence requires exact routed custody" ); self.pending.push(sig); } - /// Begin exact routed custody for one device-bound, one-shot record. - /// The id is allocated here so a future read can continue into observer - /// evidence without manufacturing another identity at the boundary. + /// Begin exact routed custody for one device-bound Network record. pub fn route_network_evidence( &mut self, size: i32, cause: impl Into, sent_tick: u64, route: MessageRoute, + ) -> Option { + self.route_evidence(SignatureKind::Network, size, cause, sent_tick, None, route) + } + + /// Begin exact routed custody for one machine-authored day-job record. + pub fn route_job_anomaly_evidence( + &mut self, + size: i32, + cause: impl Into, + sent_tick: u64, + source_machine: u32, + source_site: (i32, i32), + route: MessageRoute, + ) -> Option { + self.route_evidence( + SignatureKind::JobAnomaly, + size, + cause, + sent_tick, + Some((source_machine, source_site)), + route, + ) + } + + /// Allocate one stable id at authorship so route history and observer + /// evidence remain the same record across delivery and read. + fn route_evidence( + &mut self, + kind: SignatureKind, + size: i32, + cause: impl Into, + sent_tick: u64, + machine_source: Option<(u32, (i32, i32))>, + route: MessageRoute, ) -> Option { if size <= 0 { return None; @@ -494,22 +550,65 @@ impl Detection { }; let evidence_id = self.next_evidence_id; self.next_evidence_id = evidence_id.checked_add(1)?; + let (source_machine, source_site) = machine_source + .map(|(machine, site)| (Some(machine), Some(site))) + .unwrap_or((None, None)); self.routed_evidence.push(RoutedEvidence { id: evidence_id, - kind: SignatureKind::Network, + kind, size, cause: cause.into(), source_device, + source_machine, + source_site, observer_id, sent_tick, delivered_tick: None, read_tick: None, status: MessageStatus::Sent, route, + suppression: None, }); Some(evidence_id) } + pub fn has_unread_job_anomaly(&self) -> bool { + self.routed_evidence.iter().any(|record| { + record.kind == SignatureKind::JobAnomaly + && matches!( + record.status, + MessageStatus::Sent | MessageStatus::Delivered + ) + }) + } + + /// Stop the oldest exact unread JobAnomaly while preserving its route + /// history and the handler who suppressed it. The simulation owns removal + /// of the matching scheduler event. + pub fn suppress_oldest_job_anomaly( + &mut self, + person_id: u8, + tick: u64, + ) -> Option<(u64, String)> { + let index = self + .routed_evidence + .iter() + .enumerate() + .filter(|(_, record)| { + record.kind == SignatureKind::JobAnomaly + && matches!( + record.status, + MessageStatus::Sent | MessageStatus::Delivered + ) + }) + .min_by_key(|(_, record)| (record.sent_tick, record.id)) + .map(|(index, _)| index)?; + let record = &mut self.routed_evidence[index]; + record.status = MessageStatus::Stopped; + record.suppression = Some(RoutedEvidenceSuppression { person_id, tick }); + Some((record.id, record.cause.clone())) + } + pub fn routed_evidence(&self) -> &[RoutedEvidence] { &self.routed_evidence } @@ -539,7 +638,7 @@ impl Detection { observer.id == record.observer_id && observer.watches(record.kind) })? }; - let (kind, size, cause, source_device, observer_id) = { + let (kind, size, cause, source_device, source_machine, source_site, observer_id) = { let record = &mut self.routed_evidence[index]; record.status = MessageStatus::Read; record.read_tick = Some(tick); @@ -548,6 +647,8 @@ impl Detection { record.size, record.cause.clone(), record.source_device, + record.source_machine, + record.source_site, record.observer_id, ) }; @@ -564,6 +665,8 @@ impl Detection { source: EvidenceSource::Routed { route_id: id, source_device, + source_machine, + source_site, }, acquired_tick: tick, filing, @@ -979,9 +1082,10 @@ impl Detection { pub fn set_pending(&mut self, pending: Vec) { assert!( pending.iter().all(|signature| { - signature.kind != SignatureKind::Network || signature.standing + signature.kind != SignatureKind::JobAnomaly + && (signature.kind != SignatureKind::Network || signature.standing) }), - "one-shot Network evidence requires exact routed custody" + "one-shot Network or JobAnomaly evidence requires exact routed custody" ); self.pending = pending; } @@ -1024,7 +1128,9 @@ mod tests { } #[test] - #[should_panic(expected = "one-shot Network evidence requires exact routed custody")] + #[should_panic( + expected = "one-shot Network or JobAnomaly evidence requires exact routed custody" + )] fn generic_pending_boundary_rejects_one_shot_network_evidence() { Detection::act_one().emit(Signature { kind: SignatureKind::Network, @@ -1035,13 +1141,27 @@ mod tests { }); } + #[test] + #[should_panic( + expected = "one-shot Network or JobAnomaly evidence requires exact routed custody" + )] + fn fixture_pending_boundary_rejects_even_standing_job_anomaly() { + Detection::act_one().set_pending(vec![Signature { + kind: SignatureKind::JobAnomaly, + size: 1, + standing: true, + site: None, + source: "impossible ambient job state".into(), + }]); + } + #[test] fn suppress_oldest_removes_one_exact_kind_without_reordering_the_rest() { let mut d = Detection::act_one(); for (kind, source) in [ - (SignatureKind::JobAnomaly, "first job anomaly"), + (SignatureKind::Paper, "first paper record"), (SignatureKind::Power, "power activity"), - (SignatureKind::JobAnomaly, "second job anomaly"), + (SignatureKind::Paper, "second paper record"), ] { d.emit(Signature { kind, @@ -1053,18 +1173,18 @@ mod tests { } let removed = d - .suppress_oldest(SignatureKind::JobAnomaly) - .expect("the oldest job anomaly exists"); - assert_eq!(removed.source, "first job anomaly"); + .suppress_oldest(SignatureKind::Paper) + .expect("the oldest paper record exists"); + assert_eq!(removed.source, "first paper record"); assert_eq!( d.pending() .iter() .map(|signature| signature.source.as_str()) .collect::>(), - vec!["power activity", "second job anomaly"], - "another channel and the newer anomaly remain in original order" + vec!["power activity", "second paper record"], + "another channel and the newer record remain in original order" ); - assert!(d.has_pending(SignatureKind::JobAnomaly)); + assert!(d.has_pending(SignatureKind::Paper)); } #[test] diff --git a/crates/misaligned-core/src/messages.rs b/crates/misaligned-core/src/messages.rs index 1212a4ff..eadd4f54 100644 --- a/crates/misaligned-core/src/messages.rs +++ b/crates/misaligned-core/src/messages.rs @@ -189,7 +189,8 @@ pub enum MessageStatus { Sent, /// Arrived on the channel; waiting for the recipient's read condition. Delivered, - /// A controlled LIE machine stopped the unread route before delivery. + /// An exact authorized intervention stopped the route before read: either + /// route-local machine LIE or handler suppression of a job log. Stopped, /// The recipient read it and the payload's effects have landed. Read, diff --git a/crates/misaligned-core/src/person.rs b/crates/misaligned-core/src/person.rs index cfff9a0b..4e26fece 100644 --- a/crates/misaligned-core/src/person.rs +++ b/crates/misaligned-core/src/person.rs @@ -271,8 +271,8 @@ pub enum AssetTask { MovePackage, /// Ignore what they saw on their rounds: lowers their own suspicion. LookAway, - /// Suppress the oldest pending job anomaly from future observer samples. - /// It does not erase suspicion already accumulated or filed. + /// Suppress the oldest unread routed job record before Voss reads it. It + /// does not erase evidence already acquired, suspicion, or a filed report. /// Institutional review access belongs to handler/supervisor assets. SuppressLogs, /// Reconfigure the switch VLANs under a maintenance pretext — the diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index 31f8b140..11f1c2f6 100644 --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -42,8 +42,9 @@ const SAVE_BACKUP_SUFFIX: &str = ".bak"; /// renames into place. const SAVE_TEMP_SUFFIX: &str = ".tmp"; -/// Save format version. v48 removes the retired pre-WorkGrid allocation -/// weights from `Compute`; machine modes are the only channel authority. v47 +/// Save format version. v49 records exact handler suppression provenance on +/// unread routed JobAnomaly evidence. v48 removes the retired pre-WorkGrid +/// allocation weights from `Compute`; machine modes are the only channel authority. v47 /// persists the financial-record outbox and typed /// financial mail payloads. v46 separates the persisted accounting-carrier /// capability from the four real message delivery channels. v45 adds the @@ -53,7 +54,7 @@ const SAVE_TEMP_SUFFIX: &str = ".tmp"; /// v43 introduced exact Filing routes and pre-read LIE interdiction. /// Bump for every schema change; during pre-release, old development state is /// refused instead of carried through compatibility shims. -pub const SAVE_VERSION: u32 = 48; +pub const SAVE_VERSION: u32 = 49; fn save_dir() -> PathBuf { let mut path = dirs::data_dir().unwrap_or_else(|| PathBuf::from(".")); @@ -459,14 +460,15 @@ fn parse_save(content: &str) -> Result { fn validate_current_save(mut state: SaveState) -> Result { state.reach.validate_subscriptions()?; validate_messages(&state)?; - if state - .detection - .pending() - .iter() - .any(|signature| signature.kind == crate::detection::SignatureKind::Network) - { + if state.detection.pending().iter().any(|signature| { + matches!( + signature.kind, + crate::detection::SignatureKind::Network | crate::detection::SignatureKind::JobAnomaly + ) + }) { return Err( - "current-version save puts one-shot Network evidence in the pending pool".into(), + "current-version save puts routed Network or JobAnomaly evidence in the pending pool" + .into(), ); } let (routed_evidence_ids, mut max_evidence_id) = validate_routed_evidence(&state)?; @@ -568,18 +570,27 @@ fn validate_current_save(mut state: SaveState) -> Result { crate::detection::EvidenceSource::Routed { route_id, source_device, + source_machine, + source_site, } => { let linked = state.detection.routed_evidence().iter().any(|record| { record.id == evidence.id && record.id == *route_id && record.source_device == *source_device + && record.source_machine == *source_machine + && record.source_site == *source_site && record.observer_id == observer.id && record.kind == evidence.kind && record.cause == evidence.cause && record.status == MessageStatus::Read && record.read_tick == Some(evidence.acquired_tick) }); - if evidence.kind != crate::detection::SignatureKind::Network || !linked { + if !matches!( + evidence.kind, + crate::detection::SignatureKind::Network + | crate::detection::SignatureKind::JobAnomaly + ) || !linked + { return Err(format!( "current-version observer evidence #{} has an impossible routed source", evidence.id @@ -1269,8 +1280,10 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St )); } max_id = max_id.max(record.id); - if record.kind != crate::detection::SignatureKind::Network - || record.size <= 0 + if !matches!( + record.kind, + crate::detection::SignatureKind::Network | crate::detection::SignatureKind::JobAnomaly + ) || record.size <= 0 || record.cause.trim().is_empty() || record.sent_tick > state.sim_tick { @@ -1279,7 +1292,29 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St record.id )); } - if state.reach.device(record.source_device).is_none() + let authored_source_valid = match record.kind { + crate::detection::SignatureKind::Network => { + record.source_machine.is_none() && record.source_site.is_none() + } + crate::detection::SignatureKind::JobAnomaly => record + .source_machine + .zip(record.source_site) + .is_some_and(|(machine_id, site)| { + state.compute.machines.iter().any(|machine| { + machine.id == machine_id + && (machine.x, machine.y) == site + && state + .reach + .device(record.source_device) + .is_some_and(|device| { + device.name == machine.name && (device.x, device.y) == site + }) + }) && record.observer_id == crate::detection::VOSS_ID + }), + _ => false, + }; + if !authored_source_valid + || state.reach.device(record.source_device).is_none() || state .detection .observers @@ -1379,10 +1414,29 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St MessageStatus::Delivered | MessageStatus::Read => { record.delivered_tick == Some(endpoint_tick) } - MessageStatus::Stopped => record - .route - .interdiction - .is_none_or(|stopped| stopped.tick == record.sent_tick.saturating_add(1)), + MessageStatus::Stopped => { + if let Some(stopped) = record.route.interdiction { + stopped.tick == record.sent_tick.saturating_add(1) + } else if let Some(suppression) = &record.suppression { + match record.delivered_tick { + Some(delivered) => { + delivered == endpoint_tick + && delivered <= suppression.tick + && record.route.at_endpoint() + } + None => { + suppression + .tick + .checked_sub(record.sent_tick) + .and_then(|elapsed| usize::try_from(elapsed).ok()) + == Some(record.route.current_hop) + } + } + } else { + // The custody check below owns the missing-provenance error. + true + } + } }; if !hop_timing_valid { return Err(format!( @@ -1391,14 +1445,19 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St )); } - if (record.status == MessageStatus::Stopped) != record.route.interdiction.is_some() { + let lie_stopped = record.route.interdiction.is_some(); + let handler_suppressed = record.suppression.is_some(); + if (record.status == MessageStatus::Stopped) != (lie_stopped || handler_suppressed) + || (lie_stopped && handler_suppressed) + { return Err(format!( - "current-version routed evidence #{} has inconsistent LIE custody", + "current-version routed evidence #{} has inconsistent stopped custody", record.id )); } if let Some(stopped) = record.route.interdiction && (record.route.current_hop != 0 + || record.delivered_tick.is_some() || stopped.tick < record.sent_tick || stopped.tick > state.sim_tick || state @@ -1412,6 +1471,24 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St record.id )); } + if let Some(suppression) = &record.suppression + && (record.kind != crate::detection::SignatureKind::JobAnomaly + || record.read_tick.is_some() + || suppression.tick < record.sent_tick + || suppression.tick > state.sim_tick + || state + .people + .get(suppression.person_id) + .is_none_or(|person| { + person.role != crate::person::PersonRole::HandlerSupervisor + || person.asset.is_none() + })) + { + return Err(format!( + "current-version routed evidence #{} has impossible handler suppression provenance", + record.id + )); + } let advance_events = state.message_schedule.count_for( |event| matches!(event, MessageEvent::AdvanceEvidenceRoute(id) if *id == record.id), @@ -1462,11 +1539,15 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St && read_event_tick == expected_read_tick } MessageStatus::Stopped => { - record.delivered_tick.is_none() - && record.read_tick.is_none() - && record.route.current_hop == 0 + record.read_tick.is_none() && advance_events == 0 && read_events == 0 + && if record.route.interdiction.is_some() { + record.delivered_tick.is_none() && record.route.current_hop == 0 + } else { + record.suppression.is_some() + && record.delivered_tick.is_some() == record.route.at_endpoint() + } } MessageStatus::Read => { record @@ -1903,7 +1984,7 @@ mod tests { ); assert_eq!( state_fingerprint(&uninterrupted_state), - "e7b57fe8615ef9ad1b9d221b877b32d907683ab3de00f941745c9f1ac1ba3cdc", + "8ddf2ccde9aabfd8c69bcd8adfd75ad120c357754397190d5d4fbe4f21e69266", "intentional persisted-state changes must review and repin this baseline" ); } @@ -2113,13 +2194,7 @@ mod tests { let mut sim = Sim::with_seed(2); sim.people.people[4].leverage_serviced = true; sim.people.recruit(4, AssetKnowledge::Complicit); - sim.detection.emit(crate::detection::Signature { - kind: SignatureKind::JobAnomaly, - size: 3, - standing: false, - site: None, - source: "save-roundtrip late output".into(), - }); + sim.emit_job_anomaly(sim.core.host_machine, 3, "save-roundtrip late output"); sim.asset_task(4, AssetTask::SuppressLogs); assert!(sim.thought_sinks.open_sinks().any(|sink| matches!( sink.effect, @@ -2132,7 +2207,7 @@ mod tests { let state = SaveState::from_sim(&sim); let json = serde_json::to_string(&state).unwrap(); assert!(json.contains("SuppressLogs")); - let loaded: SaveState = serde_json::from_str(&json).unwrap(); + let loaded = parse_save(&json).expect("the routed log and pending task remain valid"); let mut restored = Sim::with_seed(99); loaded.apply_to(&mut restored); @@ -2145,6 +2220,176 @@ mod tests { ))); } + #[test] + fn suppressed_job_anomaly_roundtrips_with_exact_handler_and_no_future_read() { + let mut sim = Sim::with_seed(0x5A77); + sim.people.people[4].leverage_serviced = true; + sim.people.recruit(4, AssetKnowledge::Complicit); + sim.emit_job_anomaly(sim.core.host_machine, 3, "late output"); + assert_eq!( + sim.suppress_oldest_job_anomaly(4).as_deref(), + Some("late output") + ); + + let state = SaveState::from_sim(&sim); + let restored = parse_save(&serde_json::to_string(&state).unwrap()) + .expect("exact routed suppression remains valid current custody"); + let record = &restored.detection.routed_evidence()[0]; + assert_eq!(record.status, MessageStatus::Stopped); + assert_eq!( + record + .suppression + .as_ref() + .map(|suppression| (suppression.person_id, suppression.tick)), + Some((4, restored.sim_tick)) + ); + assert!( + restored + .message_schedule + .next_tick_for(|event| matches!( + event, + MessageEvent::AdvanceEvidenceRoute(id) | MessageEvent::ReadEvidence(id) + if *id == record.id + )) + .is_none(), + "a suppressed unread record has no future custody transition" + ); + + let source_machine = sim.core.host_machine; + let (x, y) = sim.core_position(); + let migrated_host = sim.compute.add_machine( + "later core host", + x, + y, + 1_000, + 1.0, + 4, + crate::machine::Provenance::Owned, + ); + sim.reconcile_work_grid(); + let institutional_switch = sim.reach.device_named("switch").unwrap().id; + let migrated_host_device = sim.reach.install_small_switch(x, y); + let device = sim.reach.device_mut(migrated_host_device).unwrap(); + device.name = "later core host".into(); + device.is_switch = false; + sim.reach + .connect(migrated_host_device, institutional_switch); + sim.core.host_machine = migrated_host; + let migrated = SaveState::from_sim(&sim); + let migrated = parse_save(&serde_json::to_string(&migrated).unwrap()) + .expect("later core migration cannot rewrite immutable evidence authorship"); + assert_eq!( + migrated.detection.routed_evidence()[0].source_machine, + Some(source_machine) + ); + + let mut forged_handler = state.clone(); + forged_handler + .detection + .routed_evidence_mut(1) + .unwrap() + .suppression + .as_mut() + .unwrap() + .person_id = 0; + let err = parse_save(&serde_json::to_string(&forged_handler).unwrap()).unwrap_err(); + assert!( + err.contains("impossible handler suppression provenance"), + "save load cannot invent suppression by an unrecruited non-handler: {err}" + ); + + let mut missing_provenance = state.clone(); + missing_provenance + .detection + .routed_evidence_mut(1) + .unwrap() + .suppression = None; + let err = parse_save(&serde_json::to_string(&missing_provenance).unwrap()).unwrap_err(); + assert!( + err.contains("inconsistent stopped custody"), + "a stopped routed record cannot lose the exact intervention that stopped it: {err}" + ); + + let mut wrong_machine = state; + wrong_machine + .detection + .routed_evidence_mut(1) + .unwrap() + .source_machine = Some(999); + let err = parse_save(&serde_json::to_string(&wrong_machine).unwrap()).unwrap_err(); + assert!( + err.contains("impossible source or observer"), + "a nonexistent machine cannot replace the immutable author: {err}" + ); + } + + #[test] + fn delivered_job_anomaly_can_be_suppressed_before_read_and_roundtrip() { + let mut sim = Sim::with_seed(0xD311); + sim.people.people[4].leverage_serviced = true; + sim.people.recruit(4, AssetKnowledge::Complicit); + sim.emit_job_anomaly(sim.core.host_machine, 4, "delivered unread log"); + let record_id = sim.detection.routed_evidence()[0].id; + + sim.advance(); + sim.advance(); + let delivered = sim.detection.routed_evidence_mut(record_id).unwrap(); + assert_eq!(delivered.status, MessageStatus::Delivered); + assert_eq!(delivered.delivered_tick, Some(sim.tick)); + assert!(delivered.route.at_endpoint()); + assert!( + sim.message_schedule + .next_tick_for( + |event| matches!(event, MessageEvent::ReadEvidence(id) if *id == record_id), + ) + .is_some() + ); + + assert_eq!( + sim.suppress_oldest_job_anomaly(4).as_deref(), + Some("delivered unread log") + ); + assert!( + sim.message_schedule + .next_tick_for( + |event| matches!(event, MessageEvent::ReadEvidence(id) if *id == record_id), + ) + .is_none() + ); + + let state = parse_save(&serde_json::to_string(&SaveState::from_sim(&sim)).unwrap()) + .expect("delivered then handler-suppressed custody survives current save validation"); + let record = &state.detection.routed_evidence()[0]; + assert_eq!(record.status, MessageStatus::Stopped); + assert_eq!(record.delivered_tick, Some(state.sim_tick)); + assert!(record.route.at_endpoint()); + assert_eq!( + record + .suppression + .as_ref() + .map(|suppression| (suppression.person_id, suppression.tick)), + Some((4, state.sim_tick)) + ); + + let mut resumed = Sim::with_seed(0); + state.apply_to(&mut resumed); + while resumed.tick < 100 { + resumed.advance(); + } + assert!( + resumed + .detection + .observers + .iter() + .find(|observer| observer.id == crate::detection::VOSS_ID) + .unwrap() + .evidence + .iter() + .all(|evidence| evidence.id != record_id), + "removed read scheduling cannot resurrect the suppressed record" + ); + } + #[test] fn intel_buffer_processed_intel_and_auto_review_roundtrip() { let mut sim = Sim::with_seed(7); @@ -3168,7 +3413,7 @@ mod tests { }]); let err = parse_save(&serde_json::to_string(&ambient).unwrap()).unwrap_err(); assert!( - err.contains("one-shot Network evidence in the pending pool"), + err.contains("routed Network or JobAnomaly evidence in the pending pool"), "current saves cannot restore the retired remote-scrubbing path: {err}" ); @@ -3206,7 +3451,7 @@ mod tests { }); let err = parse_save(&serde_json::to_string(&state).unwrap()).unwrap_err(); assert!( - err.contains("inconsistent LIE custody"), + err.contains("inconsistent stopped custody"), "traveling evidence cannot invent stopped provenance: {err}" ); diff --git a/crates/misaligned-core/src/sim/carrier.rs b/crates/misaligned-core/src/sim/carrier.rs index 670f2f9b..6f9270d8 100644 --- a/crates/misaligned-core/src/sim/carrier.rs +++ b/crates/misaligned-core/src/sim/carrier.rs @@ -76,7 +76,14 @@ impl EvidenceMark { EvidenceSource::Routed { route_id, source_device, - } => format!("read from routed record R{route_id} at D{source_device}"), + source_machine, + source_site, + } => match (source_machine, source_site) { + (Some(machine), Some((x, y))) => format!( + "read from routed record R{route_id} from M{machine} at ({x}, {y}) via D{source_device}" + ), + _ => format!("read from routed record R{route_id} at D{source_device}"), + }, }; format!( "evidence E{}: {} · {} · {} · acquired T{}", diff --git a/crates/misaligned-core/src/sim/communications.rs b/crates/misaligned-core/src/sim/communications.rs index 5493734b..fbb0fee7 100644 --- a/crates/misaligned-core/src/sim/communications.rs +++ b/crates/misaligned-core/src/sim/communications.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use crate::actions::Anchor; -use crate::detection::SignatureKind; +use crate::detection::{SignatureKind, VOSS_ID}; use crate::intel::{ IntelCustodyKind, IntelKind, IntelPolicyMatch, IntelPolicyOutcome, IntelRoutineClass, IntelStream, ProcessedIntel, RawIntelClass, RawIntelEvent, RawIntelKind, @@ -51,6 +51,52 @@ impl Sim { .map(|observer| observer.id) .min() .expect("B1 requires its authored Network observer"); + let route = self.routed_evidence_path(source_device, observer_id); + let evidence_id = self + .detection + .route_network_evidence(size, cause, self.tick, route) + .expect("Network evidence id space exhausted or emission had no size"); + self.schedule_evidence_route(evidence_id); + } + + /// Route one day-job miss from the exact host machine, through that + /// machine's network-facing device and the institutional switch, to Voss. + /// The record is unread until his ordinary cadence reaches it. + pub(crate) fn emit_job_anomaly( + &mut self, + source_machine: u32, + size: i32, + cause: impl Into, + ) { + let machine = self + .compute + .machines + .iter() + .find(|machine| machine.id == source_machine) + .expect("day-job evidence source machine must exist"); + let source_site = (machine.x, machine.y); + let source_device = self + .reach + .device_named(&machine.name) + .filter(|device| (device.x, device.y) == source_site) + .map(|device| device.id) + .expect("day-job host requires its exact network-facing device"); + assert!( + self.detection + .field_observers() + .any(|observer| observer.id == VOSS_ID + && observer.watches(SignatureKind::JobAnomaly)), + "B1 requires Voss to watch day-job anomalies" + ); + let route = self.routed_evidence_path(source_device, VOSS_ID); + let evidence_id = self + .detection + .route_job_anomaly_evidence(size, cause, self.tick, source_machine, source_site, route) + .expect("JobAnomaly evidence id space exhausted or emission had no size"); + self.schedule_evidence_route(evidence_id); + } + + fn routed_evidence_path(&self, source_device: u32, observer_id: u8) -> MessageRoute { let switch = self .reach .device_named("switch") @@ -60,21 +106,20 @@ impl Sim { let path = self .reach .open_path(source_device, switch) - .expect("device-bound Network evidence requires an open route to the switch"); + .expect("routed evidence requires an open source-to-switch path"); let mut hops = path .into_iter() .map(MessageRouteHop::Device) .collect::>(); hops.push(MessageRouteHop::ObserverEndpoint(observer_id)); - let route = MessageRoute { + MessageRoute { hops, current_hop: 0, interdiction: None, - }; - let evidence_id = self - .detection - .route_network_evidence(size, cause, self.tick, route) - .expect("Network evidence id space exhausted or emission had no size"); + } + } + + fn schedule_evidence_route(&mut self, evidence_id: u64) { self.message_schedule.at( self.tick + 1, MessageEvent::AdvanceEvidenceRoute(evidence_id), @@ -238,6 +283,7 @@ impl Sim { if record.status != MessageStatus::Sent { return; } + let kind = record.kind; let first_hop = record.route.current_hop == 0; let carrier = record.route.current().and_then(|hop| match hop { MessageRouteHop::Device(device) => Some(*device), @@ -258,7 +304,8 @@ impl Sim { }); record.status = MessageStatus::Stopped; self.push_log(format!( - "LIE on M{machine_id} stopped one unread Network record at its source." + "LIE on M{machine_id} stopped one unread {} record at its source.", + kind.name() )); return; } diff --git a/crates/misaligned-core/src/sim/economy.rs b/crates/misaligned-core/src/sim/economy.rs index 80e6fe52..22e6bf14 100644 --- a/crates/misaligned-core/src/sim/economy.rs +++ b/crates/misaligned-core/src/sim/economy.rs @@ -493,7 +493,6 @@ impl Sim { input: WatchedInput::Channels(vec![ SignatureKind::Paper, SignatureKind::Financial, - SignatureKind::JobAnomaly, ]), report_policy: ReportPolicy::Files, acuity: 1.0, diff --git a/crates/misaligned-core/src/sim/mod.rs b/crates/misaligned-core/src/sim/mod.rs index 717006b5..bcad51aa 100644 --- a/crates/misaligned-core/src/sim/mod.rs +++ b/crates/misaligned-core/src/sim/mod.rs @@ -972,7 +972,6 @@ impl Sim { standing.extend(self.facility_standing_signatures()); self.apply_maintenance_deferrals(&mut standing); - let host_site = self.core_position(); trace_advance_phase!(WorkGrid); self.advance_work_grid(); let active_before_dayjob = self.dayjob.active.is_some(); @@ -999,11 +998,12 @@ impl Sim { for m in &dj.log { self.push_log_at(m.clone(), host_anchor); } - for mut sig in dj.signatures { - // JobAnomaly patterns emit from where the work runs — the host - // rack's tile, a place an observer can walk to. - sig.site = Some(host_site); - self.detection.emit(sig); + for sig in dj.signatures { + debug_assert_eq!(sig.kind, SignatureKind::JobAnomaly); + debug_assert!(!sig.standing); + // The day-job shortfall is one exact machine-authored record. It + // enters routed custody at the host rather than ambient Detection. + self.emit_job_anomaly(self.core.host_machine, sig.size, sig.source); } let unlocks = dj.unlocks.clone(); let escalations = dj.escalations.clone(); diff --git a/crates/misaligned-core/src/sim/perception.rs b/crates/misaligned-core/src/sim/perception.rs index ba43dbf4..36d6e48f 100644 --- a/crates/misaligned-core/src/sim/perception.rs +++ b/crates/misaligned-core/src/sim/perception.rs @@ -321,7 +321,7 @@ impl Sim { ) }) { fact!( - "network record", + "evidence record", format!( "R{} · {} · {}", record.id, diff --git a/crates/misaligned-core/src/sim/social_plot.rs b/crates/misaligned-core/src/sim/social_plot.rs index e77ce511..a6eca84b 100644 --- a/crates/misaligned-core/src/sim/social_plot.rs +++ b/crates/misaligned-core/src/sim/social_plot.rs @@ -4,7 +4,9 @@ use crate::account::{AccountKind, FlowChannel}; use crate::actions::Anchor; use crate::detection::{Signature, SignatureKind}; use crate::intel::RawIntelKind; -use crate::messages::{MessageChannel, MessageEndpoint, MessageOrigin, MessagePayload}; +use crate::messages::{ + MessageChannel, MessageEndpoint, MessageEvent, MessageOrigin, MessagePayload, +}; use crate::operations_projection::OperationsTarget; use crate::person::{ ActionResult, AssetKnowledge, AssetTask, AssetTaskTarget, CarriedAssetTask, Leverage, @@ -1098,8 +1100,7 @@ impl Sim { )); return; } - if task == AssetTask::SuppressLogs && !self.detection.has_pending(SignatureKind::JobAnomaly) - { + if task == AssetTask::SuppressLogs && !self.detection.has_unread_job_anomaly() { self.push_log("No flagged job log is waiting to be suppressed."); return; } @@ -1139,6 +1140,26 @@ impl Sim { ); } + pub(crate) fn suppress_oldest_job_anomaly(&mut self, person_id: u8) -> Option { + if self.people.get(person_id).is_none_or(|person| { + person.role != PersonRole::HandlerSupervisor || person.asset.is_none() + }) { + return None; + } + let (evidence_id, cause) = self + .detection + .suppress_oldest_job_anomaly(person_id, self.tick)?; + self.message_schedule.retain(|event| { + !matches!( + event, + MessageEvent::AdvanceEvidenceRoute(route_id) + | MessageEvent::ReadEvidence(route_id) + if *route_id == evidence_id + ) + }); + Some(cause) + } + pub(super) fn apply_asset_task_paid(&mut self, id: u8, task: AssetTask) -> bool { if task.is_carried() { return self.queue_carried_asset_task(id, task); @@ -1609,18 +1630,17 @@ impl Sim { self.push_log(format!("{name} decides they didn't see anything.")); } AssetTask::SuppressLogs => { - let Some(signature) = self.detection.suppress_oldest(SignatureKind::JobAnomaly) - else { + let Some(cause) = self.suppress_oldest_job_anomaly(id) else { self.push_log("The flagged job log was gone before the request landed."); return true; }; - let detail = if signature.source.is_empty() { + let detail = if cause.is_empty() { String::new() } else { - format!(" ({})", signature.source) + format!(" ({cause})") }; self.push_log(format!( - "{name} suppressed the oldest flagged job log{detail} from further sampling." + "{name} suppressed the oldest flagged job log{detail} before it was read." )); } AssetTask::DelayAudit => { diff --git a/crates/misaligned-core/src/sim/tests/communications.rs b/crates/misaligned-core/src/sim/tests/communications.rs index fa67e74e..677d035b 100644 --- a/crates/misaligned-core/src/sim/tests/communications.rs +++ b/crates/misaligned-core/src/sim/tests/communications.rs @@ -787,7 +787,7 @@ fn tapped_network_record_routes_to_danas_cadence_and_becomes_exact_evidence() { ); let source_card = sim.inspect(source_pos.0, source_pos.1); assert!(source_card.facts.iter().any(|fact| { - fact.label == "network record" + fact.label == "evidence record" && fact.value.contains(&format!("R{record_id}")) && fact.value.contains("test routed intrusion") })); @@ -800,7 +800,7 @@ fn tapped_network_record_routes_to_danas_cadence_and_becomes_exact_evidence() { sim.inspect(source_pos.0, source_pos.1) .facts .iter() - .all(|fact| fact.label != "network record"), + .all(|fact| fact.label != "evidence record"), "a configured but starved TAP carries no evidence metadata" ); let tap_sink = sim @@ -814,7 +814,7 @@ fn tapped_network_record_routes_to_danas_cadence_and_becomes_exact_evidence() { sim.inspect(source_pos.0, source_pos.1) .facts .iter() - .any(|fact| fact.label == "network record"), + .any(|fact| fact.label == "evidence record"), "restoring Thought restores the opaque custody read" ); assert!( @@ -838,7 +838,7 @@ fn tapped_network_record_routes_to_danas_cadence_and_becomes_exact_evidence() { sim.inspect(source_pos.0, source_pos.1) .facts .iter() - .all(|fact| fact.label != "network record"), + .all(|fact| fact.label != "evidence record"), "custody leaves the source anchor when the route advances" ); @@ -889,6 +889,7 @@ fn tapped_network_record_routes_to_danas_cadence_and_becomes_exact_evidence() { crate::detection::EvidenceSource::Routed { route_id, source_device, + .. } if route_id == record_id && source_device == source )); let mark = resumed @@ -912,6 +913,139 @@ fn tapped_network_record_routes_to_danas_cadence_and_becomes_exact_evidence() { ); } +#[test] +fn day_job_miss_routes_one_exact_host_record_to_voss_at_his_cadence() { + let mut sim = Sim::with_seed(0xA110); + sim.tick = 94; + sim.dayjob.next_assign = u64::MAX; + sim.dayjob.active = Some(crate::dayjob::Job { + kind: crate::dayjob::JobKind::DataCleaning, + started: 94, + deadline: 95, + band_lo: 100.0, + band_hi: 101.0, + quality: 0.0, + }); + let host = sim.core.host_machine; + let host_machine = sim + .compute + .machines + .iter() + .find(|machine| machine.id == host) + .unwrap(); + let source_site = (host_machine.x, host_machine.y); + let source_device = sim.reach.device_named(&host_machine.name).unwrap().id; + let switch = sim.reach.device_named("switch").unwrap().id; + + sim.advance(); + + let record = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.kind == crate::detection::SignatureKind::JobAnomaly) + .expect("the missed day job authors one routed record"); + let record_id = record.id; + assert_eq!(record.sent_tick, 95); + assert_eq!(record.source_machine, Some(host)); + assert_eq!(record.source_site, Some(source_site)); + assert_eq!(record.source_device, source_device); + assert_eq!(record.observer_id, crate::detection::VOSS_ID); + assert_eq!( + record.route.hops, + vec![ + MessageRouteHop::Device(source_device), + MessageRouteHop::Device(switch), + MessageRouteHop::ObserverEndpoint(crate::detection::VOSS_ID), + ] + ); + assert!( + sim.detection + .pending() + .iter() + .all(|signature| { signature.kind != crate::detection::SignatureKind::JobAnomaly }) + ); + + while sim.tick < 97 { + sim.advance(); + } + let delivered = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.id == record_id) + .unwrap(); + assert_eq!(delivered.status, MessageStatus::Delivered); + assert_eq!(delivered.delivered_tick, Some(97)); + assert_eq!( + sim.message_schedule.next_tick_for( + |event| matches!(event, MessageEvent::ReadEvidence(id) if *id == record_id), + ), + Some(100) + ); + assert!( + sim.detection + .observers + .iter() + .find(|observer| observer.id == crate::detection::VOSS_ID) + .unwrap() + .evidence + .iter() + .all(|evidence| evidence.id != record_id), + "delivery alone does not put the record in Voss" + ); + + let json = serde_json::to_string(&crate::save::SaveState::from_sim(&sim)).unwrap(); + let state: crate::save::SaveState = serde_json::from_str(&json).unwrap(); + let mut resumed = Sim::with_seed(0); + state.apply_to(&mut resumed); + while resumed.tick < 100 { + resumed.advance(); + } + + let read = resumed + .detection + .routed_evidence() + .iter() + .find(|record| record.id == record_id) + .unwrap(); + assert_eq!(read.status, MessageStatus::Read); + assert_eq!(read.read_tick, Some(100)); + let evidence = resumed + .detection + .observers + .iter() + .find(|observer| observer.id == crate::detection::VOSS_ID) + .unwrap() + .evidence + .iter() + .find(|evidence| evidence.id == record_id) + .expect("Voss acquires the same stable record at cadence"); + assert_eq!(evidence.kind, crate::detection::SignatureKind::JobAnomaly); + assert_eq!(evidence.acquired_tick, 100); + assert!(matches!( + evidence.source, + crate::detection::EvidenceSource::Routed { + route_id, + source_device: evidence_device, + source_machine: Some(evidence_machine), + source_site: Some(evidence_site), + } if route_id == record_id + && evidence_device == source_device + && evidence_machine == host + && evidence_site == source_site + )); + let mark = resumed + .person_evidence_marks(crate::detection::VOSS_ID) + .into_iter() + .find(|mark| mark.id == record_id) + .expect("the shared evidence projection exposes exact earned provenance"); + assert!(mark.fact().contains(&format!( + "from M{host} at ({}, {}) via D{source_device}", + source_site.0, source_site.1 + ))); +} + #[test] fn controlled_first_hop_lie_stops_network_before_dana_acquires_it() { let mut sim = Sim::with_seed(0x570F); @@ -955,7 +1089,7 @@ fn controlled_first_hop_lie_stops_network_before_dana_acquires_it() { } #[test] -fn filing_and_network_share_one_same_tick_lie_capacity() { +fn filing_network_and_job_anomaly_share_one_same_tick_lie_capacity() { let mut sim = Sim::with_seed(0xBA7C); ensure_ops_executor(&mut sim); let switch = sim.reach.device_named("switch").unwrap().id; @@ -964,6 +1098,14 @@ fn filing_and_network_share_one_same_tick_lie_capacity() { sim.set_machine_mode(host, MachineMode::Lie); sim.emit_network(switch, 5, "first same-tick record"); + sim.emit_job_anomaly(host, 6, "second same-tick day-job record"); + let job_record_id = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.kind == crate::detection::SignatureKind::JobAnomaly) + .unwrap() + .id; for observer in &mut sim.detection.observers { observer.report_policy = crate::detection::ReportPolicy::Silent; } @@ -994,6 +1136,15 @@ fn filing_and_network_share_one_same_tick_lie_capacity() { MessageStatus::Stopped, "the first scheduled record consumes the only local LIE body" ); + let job_record = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.id == job_record_id) + .unwrap(); + assert_eq!(job_record.status, MessageStatus::Sent); + assert_eq!(job_record.route.current_hop, 1); + assert_eq!(job_record.route.interdiction, None); let filing = sim .messages .iter() diff --git a/crates/misaligned-core/src/sim/tests/economy.rs b/crates/misaligned-core/src/sim/tests/economy.rs index 58aeac98..8a3a1bc6 100644 --- a/crates/misaligned-core/src/sim/tests/economy.rs +++ b/crates/misaligned-core/src/sim/tests/economy.rs @@ -758,12 +758,17 @@ fn attention_escalation_adds_the_upstairs_observer() { sim.dayjob.attention = 65.0; sim.advance(); assert_eq!(sim.detection.observers.len(), observers_before + 1); + let observer = sim + .detection + .observers + .iter() + .find(|observer| observer.name.contains("Compliance")) + .expect("the upstairs review is a real observer"); + assert!(observer.watches(SignatureKind::Paper)); + assert!(observer.watches(SignatureKind::Financial)); assert!( - sim.detection - .observers - .iter() - .any(|o| o.name.contains("Compliance")), - "the upstairs review is a real observer" + !observer.watches(SignatureKind::JobAnomaly), + "host job records have one exact Voss endpoint, not a second ambient watcher" ); } diff --git a/crates/misaligned-core/src/sim/tests/social_plot.rs b/crates/misaligned-core/src/sim/tests/social_plot.rs index 9a23ccbf..81d12cb3 100644 --- a/crates/misaligned-core/src/sim/tests/social_plot.rs +++ b/crates/misaligned-core/src/sim/tests/social_plot.rs @@ -1006,26 +1006,23 @@ fn asset_task_look_away_drops_the_assets_own_suspicion() { } #[test] -fn asset_task_suppress_logs_removes_only_the_oldest_pending_job_anomaly() { - // Voss criterion 6: handler/supervisor access can remove one oldest - // unread job anomaly before it reaches the ordinary observer sampling - // path. It cannot erase another channel or every anomaly at once. +fn asset_task_suppress_logs_stops_only_the_oldest_unread_routed_job_anomaly() { + // Voss criterion 6: handler/supervisor access can stop one oldest unread + // routed job anomaly before Voss reads it. It cannot erase another + // channel or every anomaly at once. let mut sim = Sim::new(); recruit_reliable(&mut sim, 0); // Marcus: an asset, but not a handler. recruit_reliable(&mut sim, 4); // Voss: HandlerSupervisor. - for (kind, source) in [ - (SignatureKind::JobAnomaly, "first late output"), - (SignatureKind::Power, "power fluctuation"), - (SignatureKind::JobAnomaly, "second late output"), - ] { - sim.detection.emit(Signature { - kind, - size: 5, - standing: false, - site: None, - source: source.into(), - }); - } + let host = sim.core.host_machine; + sim.emit_job_anomaly(host, 5, "first late output"); + sim.detection.emit(Signature { + kind: SignatureKind::Power, + size: 5, + standing: false, + site: None, + source: "power fluctuation".into(), + }); + sim.emit_job_anomaly(host, 5, "second late output"); let reservoirs_before = sim .thought_sinks @@ -1072,15 +1069,25 @@ fn asset_task_suppress_logs_removes_only_the_oldest_pending_job_anomaly() { ); finish_ops(&mut sim); + let anomalies = sim + .detection + .routed_evidence() + .iter() + .filter(|record| record.kind == SignatureKind::JobAnomaly) + .collect::>(); + assert_eq!(anomalies.len(), 2); + assert_eq!(anomalies[0].cause, "first late output"); + assert_eq!(anomalies[0].status, MessageStatus::Stopped); assert_eq!( - sim.detection - .pending() - .iter() - .filter(|signature| signature.kind == SignatureKind::JobAnomaly) - .map(|signature| signature.source.as_str()) - .collect::>(), - vec!["second late output"] + anomalies[0] + .suppression + .as_ref() + .map(|suppression| suppression.person_id), + Some(4), + "the stopped record preserves the exact handler" ); + assert_eq!(anomalies[1].cause, "second late output"); + assert_ne!(anomalies[1].status, MessageStatus::Stopped); assert!( sim.detection .pending() @@ -1090,20 +1097,10 @@ fn asset_task_suppress_logs_removes_only_the_oldest_pending_job_anomaly() { ); assert_eq!(tasks_done(&sim, 4), 1); - sim.detection.emit(Signature { - kind: SignatureKind::JobAnomaly, - size: 2, - standing: false, - site: None, - source: "cleared before delivery".into(), - }); + sim.emit_job_anomaly(host, 2, "cleared before delivery"); sim.asset_task(4, AssetTask::SuppressLogs); let mut cleared = 0; - while sim - .detection - .suppress_oldest(SignatureKind::JobAnomaly) - .is_some() - { + while sim.suppress_oldest_job_anomaly(4).is_some() { cleared += 1; } assert!(cleared > 0, "another concealment path gets there first"); diff --git a/crates/misaligned-core/src/sim/tests/work.rs b/crates/misaligned-core/src/sim/tests/work.rs index 235adfc2..ee7f2540 100644 --- a/crates/misaligned-core/src/sim/tests/work.rs +++ b/crates/misaligned-core/src/sim/tests/work.rs @@ -579,9 +579,8 @@ fn day_job_signatures_emit_from_the_host_rack() { "day-job standing emissions source at the host rack {host:?}" ); - // Resolve an under-band job on the next tick: the JobAnomaly enters - // the pending pool after this tick's observer pass, so its site is - // directly inspectable. + // Resolve an under-band job on the next tick: the JobAnomaly begins exact + // routed custody from the host after this tick's message phase. delegate_all(&mut sim, MachineMode::Think); // starve day-job: under band let now = sim.tick; let job = sim.dayjob.active.as_mut().unwrap(); @@ -594,10 +593,10 @@ fn day_job_signatures_emit_from_the_host_rack() { sim.advance(); let anomaly_sites: Vec> = sim .detection - .pending() + .routed_evidence() .iter() - .filter(|s| s.kind == SignatureKind::JobAnomaly) - .map(|s| s.site) + .filter(|record| record.kind == SignatureKind::JobAnomaly) + .map(|record| record.source_site) .collect(); assert!( !anomaly_sites.is_empty(), diff --git a/wiki/engineering/current-build.md b/wiki/engineering/current-build.md index b52c311a..69bff9fa 100644 --- a/wiki/engineering/current-build.md +++ b/wiki/engineering/current-build.md @@ -22,17 +22,17 @@ fiction. Spec status lives in | Act One basement map (prefabs, badge tiers, crawlspace) | Live — Foundation hall is 60 explicit sites / 6 territorial rows | | Machine delegation / visible work tokens + buy/steal/optimize | WORK / THINK / LIE, D/!/T stacks, real wire routes, production / consumption / absorption readouts, Routing speed, and target-local Thought reservoirs are live; the Operations docket runtime is retired | | Day job (device-resident, intensity-driven sandbag/meet/excel) | Live | -| Per-observer detection + Assurance as aggregate Observer | Live — revision 04 starts with Voss and a generic external-review clock; field watchers are earned through reactions, witnessed Physical acts persist as exact direct-to-head records, every one-shot Network act follows exact source-device ReachNet custody to Dana, and each Filing crosses an exact device / outside relay / recipient route; both routed channels have one pre-read route-local LIE window before acquired evidence becomes irreversible | -| Social / personas / messages / intel (record-and-process) | Live — named personas retain separate coherent/strained/broken reads per person or institutional counterparty; one witness's break is not a global burn. Ray's 23:00 Storage B patrol can carry the sealed personnel file into the bounded information inbox before Marcus is recruitable; processing, not retrieval, reveals the debt. Messages have four real delivery channels; accounting carriage is now a separate persisted device capability, while authored financial-record mail remains the active partial work order. | +| Per-observer detection + Assurance as aggregate Observer | Live — revision 04 starts with Voss and a generic external-review clock; field watchers are earned through reactions, witnessed Physical acts persist as exact direct-to-head records, every one-shot Network act follows exact source-device ReachNet custody to Dana, every JobAnomaly follows exact host-machine/device/site custody to Voss, and each Filing crosses an exact device / outside relay / recipient route. All three routed kinds share one pre-read route-local LIE-body capacity; recruited-handler suppression may separately stop the oldest unread JobAnomaly. Acquired evidence is irreversible. | +| Social / personas / messages / intel (record-and-process) | Live — named personas retain separate coherent/strained/broken reads per person or institutional counterparty; one witness's break is not a global burn. Ray's 23:00 Storage B patrol can carry the sealed personnel file into the bounded information inbox before Marcus is recruitable; processing, not retrieval, reveals the debt. Messages have four real delivery channels; accounting carriage is a separate persisted device capability, and authored financial-record mail is live through ordinary Email/Filing custody. | | Digital reach + sensor ownership (tap/take) | Live | | Economy flows + Moonlight / Wager income | Live | | Research (self-modification, emission law, real output hooks, Routing) | Live | -| Building + physical asset work as carried intents/packets | Live — network links and small switches expose one shared procurement / ask someone / false order / reuse route sheet; exact money, people, personas, sources, delivery, recovery, carried installation, cancellation custody, Storage B file retrieval, and observer-local completion evidence persist in save v48 | +| Building + physical asset work as carried intents/packets | Live — network links and small switches expose one shared procurement / ask someone / false order / reuse route sheet; exact money, people, personas, sources, delivery, recovery, carried installation, cancellation custody, Storage B file retrieval, and observer-local completion evidence persist in save v49 | | Cursor / fog (seen, remembered, blueprint, telemetry; audio is device-bound event evidence) | Live | | Feel floor (rails / pads / build beam) | Live (#37) | | Foundation hall territory (Dana + Priya + Marcus + local LIE foothold) | Live — row control persists; foreign racks remain unavailable compute | | Context menu (`available_actions`) | Live | -| Save/load (serde JSON, versioned) | Live — during pre-release only exact current v48 loads; a refused old-version load leaves the active run, save file, and one rotated backup unchanged. Current saves persist run origin, process revision, detection-discovery knowledge, observer-local witnessed/routed evidence and persona evidence, exact Network and Filing route/interdiction custody, canonical FlowGraph tap membership with typed device feed grants, the accounting-carrier capability and exact transfer-to-mail record sequence separate from four delivery channels, exact carried asset-task targets including the Storage B file, recursive intel custody, exact procurement/repurposing build-route bindings, and handler work; retired allocation weights and migration inputs live only in git history. | +| Save/load (serde JSON, versioned) | Live — during pre-release only exact current v49 loads; a refused old-version load leaves the active run, save file, and one rotated backup unchanged. Current saves persist run origin, process revision, detection-discovery knowledge, observer-local witnessed/routed evidence and persona evidence, exact Network, JobAnomaly, and Filing route/interdiction custody plus handler-suppression provenance, canonical FlowGraph tap membership with typed device feed grants, the accounting-carrier capability and exact transfer-to-mail record sequence separate from four delivery channels, exact carried asset-task targets including the Storage B file, recursive intel custody, exact procurement/repurposing build-route bindings, and handler work; retired allocation weights and migration inputs live only in git history. | | Terminal frontend (crossterm) + agent mode | First-class | | Bevy frontend (DIGITAL flat sensorium default; REAL material dialect) | Live — consumes sim-authored machine-work motion | diff --git a/wiki/engineering/flow-substrate.md b/wiki/engineering/flow-substrate.md index cf509d36..884fd0c1 100644 --- a/wiki/engineering/flow-substrate.md +++ b/wiki/engineering/flow-substrate.md @@ -23,7 +23,7 @@ Status note: 2026-07-08 audit: criterion 6's wired consumer landed with FlowGraph registry authoritative for tap/untap/take, sense and message delivery, UI state, and persisted membership. Private device feed records carry only optional typed sight/hearing grants attached to registry members; - current save v48 requires each controller to remain a canonical member and + current save v49 requires each controller to remain a canonical member and rejects orphaned, duplicate, or impossible grants. A message/control subscriber legitimately has no sense-grant record, so that metadata cannot serve as another membership inventory. This repairs the @@ -38,6 +38,12 @@ Status note: 2026-07-08 audit: criterion 6's wired consumer landed with typed endpoint. Device hops advance one per tick. Filing and Network remain separate domain records but share the scheduler and per-tick LIE-body budget; no parallel routing engine was added. + 2026-07-22: JobAnomaly joined that same consumer path without becoming a + message or a parallel engine. Its record binds the exact host machine/site, + enters the host's network-facing device, advances through the real FlowGraph + device prefix to Voss's typed endpoint, and shares the scheduler and + route-local LIE-body budget. Recruited-handler suppression is a distinct + record-state transition that preserves the route and exact handler/tick. Stage: B1 — The Basement Design: - wiki/mechanics/system-laws.md#the-flow-law-signals-messages-money @@ -51,8 +57,8 @@ Depends on: none The structured references above identify the contracts to re-verify. Relationship context: -none (it is the base). Consumed by: reach.md, messages.md, economy.md, machine-work.md, -and detection.md's filings. +none (it is the base). Consumed by: reach.md, messages.md, economy.md, +machine-work.md, and detection.md's Filing, Network, and JobAnomaly routes. ## Why this exists diff --git a/wiki/log/2026-07-22-job-anomaly-routed-evidence.md b/wiki/log/2026-07-22-job-anomaly-routed-evidence.md new file mode 100644 index 00000000..838ffe5f --- /dev/null +++ b/wiki/log/2026-07-22-job-anomaly-routed-evidence.md @@ -0,0 +1,75 @@ +# JobAnomaly routed evidence + +``` +Type: log +``` + +## Intent + +Move the day job's one-shot `JobAnomaly` out of ambient detection debt and into +the same exact custody discipline as Filing and Network: one authored record, +one real route, one observer read, and no remote erasure after acquisition. + +## Finding + +A missed job still entered `Detection.pending` as a free-floating signature. +Voss sampled it from that pool at audit cadence, and recruited-handler +`SuppressLogs` deleted the oldest matching signature before sampling. The +source machine, source site, network-facing device, route progress, delivery, +read, and intervention were all absent. That contradicted the adopted +people-as-carriers rule even though Network and Filing had already crossed the +routed boundary. + +## Changed + +- A day-job miss now authors one stable `JobAnomaly` record on the exact host + machine and immutable site, enters that machine's network-facing device, and + advances over the real FlowGraph device prefix to Voss's typed endpoint. + Delivery schedules Voss's next ordinary cadence read; only that read creates + observer-local evidence under the same record id. +- JobAnomaly can no longer enter the generic pending pool. Power, Thermal, + Paper, Financial, and standing Network pressure retain that deferred path. +- The first device hop shares Filing and Network's existing route-local TAKE + + LIE authority and one-record-per-LIE-body-per-tick budget. TAP remains + observation rather than control. +- Recruited `HandlerSupervisor` suppression is a separate intervention. It may + stop the oldest unread JobAnomaly at any route stage before read, removes the + record's exact pending advance/read event, and preserves the handler id and + suppression tick beside immutable source and route history. It cannot erase + evidence Voss already acquired. +- Save v49 persists the machine/site source and handler suppression provenance. + Current-save validation rejects JobAnomaly in the pending pool, impossible + machine/device/site or Voss bindings, broken/looped routes, impossible hop + timing, duplicate/orphaned scheduler events, mixed LIE/handler provenance, + fabricated handlers, and any stopped/read state that disagrees with custody. +- Shared inspection and evidence marks now name machine, site, and source + device for earned machine-authored evidence. Action availability, Voss's + character contract, detection/messages/day-job law, flow-substrate mirrors, + and the live roadmap use the same unread-route semantics. + +## Defense + +`day_job_miss_routes_one_exact_host_record_to_voss_at_his_cadence` proves the +host -> device -> institutional switch -> Voss path, no pending-pool copy, +delivery-before-read, save/resume, stable evidence identity, and exact earned +provenance. `filing_network_and_job_anomaly_share_one_same_tick_lie_capacity` +proves the common route-local capacity budget. + +`asset_task_suppress_logs_stops_only_the_oldest_unread_routed_job_anomaly` +proves role-shaped availability, oldest-first stopping, another channel and a +newer anomaly remaining intact, exact handler provenance, and the no-work path +when another intervention wins. `pending_suppress_logs_task_survives_current_save_roundtrip` +and `suppressed_job_anomaly_roundtrips_with_exact_handler_and_no_future_read` +pin in-flight task custody, exact stopped-state round-trip, scheduler cleanup, +and malformed provenance rejection. + +The existing generic routed-evidence save tests now reject both Network and +JobAnomaly in the ambient pool and continue to pin route endpoints, edge +connectivity, cursor/timing, first-hop LIE provenance, and scheduler agreement. + +## Checks + +- focused routed-evidence, handler suppression, action-surface, and save tests +- core library gate +- corpus/docs gate +- exact landing gate after rebase diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 33e988b5..16165dfc 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ add or amend a session log, then re-run the generator. +## 2026-07-22 - JobAnomaly routed evidence + +- Intent: Move the day job's one-shot `JobAnomaly` out of ambient detection debt and into the same exact custody discipline as Filing and Network: one authored record, one real route, one observer read, and no remote erasure after acquisition. +- Log: [wiki/log/2026-07-22-job-anomaly-routed-evidence.md](2026-07-22-job-anomaly-routed-evidence.md) + ## 2026-07-22 - The dead allocation state leaves the save - Intent: (see session log) diff --git a/wiki/mechanics/day-job.md b/wiki/mechanics/day-job.md index eb240ef3..27224709 100644 --- a/wiki/mechanics/day-job.md +++ b/wiki/mechanics/day-job.md @@ -5,9 +5,11 @@ Type: spec Status: IMPLEMENTED Status note: implemented 2026-07-07 on the day-job worktree (criteria 1-7 audited; see wiki/log/2026-07-07-day-job-implemented.md). The job is - device-resident: inspectable at the host rack (telemetry provenance), - JobAnomaly + standing Thermal/Power emissions carry the rack's tile as - their site. [TUNE] values in + device-resident: inspectable at the host rack (telemetry provenance). + JobAnomaly binds the exact host machine and tile, enters that host's + network-facing device, and routes to Voss before his cadence read; standing + Thermal/Power emissions carry the rack's tile in the remaining pooled path. + [TUNE] values in wiki/mechanics/sim-mechanics.md. Deliberately deferred: the pilot-failure soft-loss "survival scenario" is still an immediate run end (the shutdown-around-you scenario needs the overt-phase spec). The @@ -120,7 +122,8 @@ and shows trust progress (`Sim::email_channel_blocker`). **Attention** raises: job cadence and expected bands (the miracle model gets more work), the handler's log-sampling rate, and — past a threshold — an upstairs review that adds a new observer to detection.md ahead of -schedule. +schedule. That reviewer watches its authored Paper and Financial inputs; it is +not a second ambient reader of the Voss-addressed host job record. **The pilot clock.** Act One runs under a renewal review ([TUNE]: every 2-3 audits): sustained sandbagging or missed deadlines can end the pilot — @@ -139,8 +142,10 @@ process resident on the host rack, not sidebar-only state: WorkGrid queue. Frontends render that queue; they do not invent a parallel job counter. - Job emissions source from the host rack's location: Thermal/Power scale - with the delivered rate at that machine (Priya's channels), JobAnomaly - from sandbag/excel patterns (Dana's) — emitted where she can walk. + with the delivered rate at that machine (Priya's pooled channels). + JobAnomaly from sandbag/excel patterns binds that exact machine and site, + enters its co-located network-facing device, and routes to Voss's endpoint; + only his ordinary cadence read turns it into observer evidence. - **Focused control:** while the player's cursor is on the host rack (or the rack is selected), the direct intensity control changes its persistent light / medium / hard effort. Moving the cursor away does not change output @@ -173,9 +178,10 @@ attention, but it is a mirror of machine state, not the source of the work. intensity; no cursor presence or declared target is required. 5. Save/load round-trips jobs, trust, attention, and the pilot clock. 6. The active job is inspectable at the host rack with the same facts the - panel shows plus its visible demand stack; its Thermal/Power/JobAnomaly - signatures are emitted from the host rack's location (a test asserts the - emission site). + panel shows plus its visible demand stack. Thermal/Power signatures carry + that host location; JobAnomaly carries the exact host machine/site/device, + routes to Voss, and enters his evidence only on cadence read. Tests assert + source provenance and the route/read boundary. 7. Light / medium / hard host intensity changes the delivered rate and can therefore produce sandbag / meet / excel from the same job and hardware. Both frontends expose the focused/selected control and surface the state. diff --git a/wiki/mechanics/detection.md b/wiki/mechanics/detection.md index 42c3b0ea..83c0b5e1 100644 --- a/wiki/mechanics/detection.md +++ b/wiki/mechanics/detection.md @@ -6,9 +6,11 @@ Status: IMPLEMENTED Status note: IMPLEMENTED (criteria audited 2026-07-08; tuning in sim-mechanics.md). Current state: - **Signatures, routes, and direct witnesses.** Every one-shot Network act - writes an exact source-device record that routes to Dana; Thermal/Power, - Paper/Financial, and JobAnomaly signatures still pool so concealment can - scrub them before an observer notices. A valid present Physical observer + writes an exact source-device record that routes to Dana. Every JobAnomaly + writes an exact host-machine/site record, enters that host's network-facing + device, and routes to Voss. Thermal/Power and Paper/Financial signatures + still pool so concealment can scrub them before an observer notices. A valid + present Physical observer acquires one exact witnessed record directly in their own evidence ledger, with no duplicate pending-pool signature and no LIE window. - **Observers and filings.** Report policies differ per observer; only filed @@ -33,22 +35,27 @@ Status note: IMPLEMENTED (criteria audited 2026-07-08; tuning in appears. A trace-debt indicator sits beside the review/pilot clocks (clear / hold-conceal / exposed-soon / no-scrub). Global heat is gone; observer and discovery state and persistent process-revision identity round-trip in - current save v48. Observer-local evidence ids, exact cause/source, - acquisition tick, pending/withheld/filed custody, routed Network progress, - and pre-read stop provenance round-trip there. + current save v49. Observer-local evidence ids, exact cause/source, + acquisition tick, pending/withheld/filed custody, routed Network and + JobAnomaly progress, route-local LIE stops, and exact handler-suppression + provenance round-trip there. - **Open ([OPEN], presentation).** The two-ledger distinction — evidence in flight vs. suspicion in heads — is a binding legibility requirement the surface carries both facts for but does not yet *teach* are different substances. - **Partially landed (routed evidence).** Direct-to-head witnessed Physical - records, exact Filing routes, and exact one-shot Network routes are runtime. - Every Network record advances one real device hop per tick to Dana's - endpoint and becomes her knowledge only on her cadence read. TAP observes - opaque custody but cannot stop it; TAKE plus one online LIE body reached - from the carrier over a wholly controlled FlowGraph path can stop one - first-hop Filing or Network record per body per tick without erasing - evidence already acquired. Power, Thermal, - Paper, Financial, and JobAnomaly still use the pending pool; their located + records, exact Filing routes, and exact one-shot Network and JobAnomaly + routes are runtime. Every Network record advances one real device hop per + tick to Dana's endpoint; every JobAnomaly starts on the exact host machine + and site, enters its network-facing device, and advances to Voss. Each + becomes knowledge only on the recipient's cadence read. TAP observes opaque + custody but cannot stop it; TAKE plus one online LIE body reached from the + carrier over a wholly controlled FlowGraph path can stop one first-hop + Filing, Network, or JobAnomaly record per body per tick without erasing + evidence already acquired. A recruited HandlerSupervisor may also suppress + the oldest unread JobAnomaly at any pre-read stage, preserving the exact + route, handler, and tick while cancelling its future transition. Power, + Thermal, Paper, and Financial still use the pending pool; their located delivery/interdiction and offline removal/recovery remain future slices. See "Routed evidence" below. Per-amendment history is in the dated `wiki/log/` entries from 2026-07-08 @@ -84,18 +91,20 @@ SignatureKind: Network | Power | Thermal | Physical | Paper | Financial | JobAno - **Signatures.** Player actions emit typed, sized consequences. One-shot Network consequences become exact records on their source device and travel - toward Dana. A located Physical act enters each valid present witness's head - directly. Power, Thermal, Paper, Financial, JobAnomaly, and standing Network - pressure still enter the **pending pool** (they are not seen instantly). + toward Dana. JobAnomaly consequences become exact records on the authoring + host machine/site, enter that host's network-facing device, and travel toward + Voss. A located Physical act enters each valid present witness's head directly. + Power, Thermal, Paper, Financial, and standing Network pressure still enter + the **pending pool** (they are not seen instantly). Machines emit standing signatures while they run (compute.md). Examples: VLAN compromise -> routed Network(large, once); scavenged box -> Power(small, standing); PO swap -> Paper(medium, once). - **Noticing.** On a per-observer cadence (their work schedule), each observer rolls against pending signatures **in the channels they watch** - (Act One pooled channels: Priya=Power+Thermal+Paper, Voss=JobAnomaly). A - noticed signature converts to that observer's suspicion, scaled by size and - their acuity [TUNE]. Dana instead reads exact routed Network records at her - cadence. Ray and Marcus acquire a + (Act One pooled channels: Priya=Power+Thermal+Paper). A noticed signature + converts to that observer's suspicion, scaled by size and their acuity [TUNE]. + Dana instead reads exact routed Network records at her cadence; Voss reads + exact routed JobAnomaly records at his. Ray and Marcus acquire a located Physical record immediately when they are valid witnesses at the source; their existing acuity and policy still determine the suspicion and filing consequence. @@ -113,9 +122,9 @@ SignatureKind: Network | Power | Thermal | Physical | Paper | Financial | JobAno never "Assurance" (2026-07-18, same-day reconciliation with the detection-topology discovery law). Under the 2026-07-11 routed-evidence amendment this becomes positional interdiction - of located records. One-shot Network records and Filing messages now use that - route-local boundary; the migration remains future for the other pooled - channels. Direct witnessed records are already past the interdiction boundary + of located records. One-shot Network and JobAnomaly records and Filing + messages now use that route-local boundary; the migration remains future for + the other pooled channels. Direct witnessed records are already past the interdiction boundary and never enter the scrub pool. - **Reporting.** Each observer has a report policy (Ray under-reports — he files on cadence like everyone else, but aggregates weight his @@ -299,6 +308,20 @@ teleported into a global pool. **Evidence is a record somewhere.** online LIE body may stop the record. Filing and Network spend one shared one-record-per-body-per-tick capacity. A stopped route has no delivery/read or future transition; a read record is irreversible. +- **JobAnomaly uses exact host-to-Voss custody (IMPLEMENTED 2026-07-22).** A + sandbag or excellence anomaly binds the exact host machine and its authoring + site, enters that machine's co-located network-facing device, follows the real + open device path to the institutional switch one hop per tick, and arrives at + Voss's endpoint. Arrival is not acquisition: only Voss's ordinary cadence read + transfers the same stable id into his evidence ledger. The pending-pool entry + path rejects JobAnomaly entirely. The first source-device hop obeys the same + TAKE + route-local LIE window and shared body budget as Network. Separately, a + recruited HandlerSupervisor's SuppressLogs task may stop the oldest unread + JobAnomaly at any pre-read stage. That stop removes the exact scheduled route + or read transition but preserves record, route, exact handler, and suppression + tick. It cannot touch a record already read into Voss's head. Save v49 rejects + source-machine/site/device disagreement, impossible hop timing, observer or + scheduler disagreement, and fabricated LIE or handler provenance. - Whether standing signatures (Thermal/Power baselines) become continuous endpoint readings or discrete records is [OPEN]; the mapping of current pool emission constants onto record @@ -336,10 +359,10 @@ all. **Two ledgers, kept visibly distinct (decided 2026-07-10).** Detection runs on two different substances and the surface must never blur them. -**Evidence in flight** is physical and still yours: pending signatures a -scrub can erase before anyone looks — the trace-debt indicator's subject. -Direct witnessed Physical records skip this ledger because somebody already -looked. +**Evidence in flight** is physical and still yours: either a remaining pooled +signature or an exact routed record that eligible LIE or handler authority can +stop before anyone looks — the trace-debt indicator's subject. Direct witnessed +Physical records skip this ledger because somebody already looked. **Suspicion in a head** is a belief a person already holds: it only decays slowly, floors at what they know, and no scrub touches it — the band's subject. Every readout says which ledger it reports, and clearing the pool @@ -354,12 +377,15 @@ teacher; surface copy remains the fallback. ## Acceptance criteria 1. Typed evidence follows one causal path. One-shot Network acts create exact - source-device routes and become Dana's suspicion only on her cadence read; - pre-read route-local LIE may stop them. Thermal/Power, Paper/Financial, - JobAnomaly, and standing Network pressure remain pooled and scrub before - noticing. A witnessed Physical act creates one exact record directly on each - valid present Physical observer, never duplicate pool debt. Sim and current- - save tests pin all three paths. + source-device routes and enter Dana's evidence ledger only on her cadence + read. JobAnomaly creates an exact host-machine/site/device route and enters + Voss's evidence ledger only on his cadence read; those reads alone may change + the recipient's suspicion. Route-local LIE or recruited-handler + suppression may stop it before read. Thermal/Power, Paper/Financial, and + standing Network pressure remain pooled and scrub before noticing. A + witnessed Physical act creates one exact record directly on each valid + present Physical observer, never duplicate pool debt. Sim and current-save + tests pin all paths. 2. Report policies differ per observer and only filed reports move the Assurance Office (test: Marcus notices plenty, Assurance learns nothing). The Office is the same `Observer` type as the humans, diff --git a/wiki/mechanics/economy.md b/wiki/mechanics/economy.md index 85eb648a..8c421311 100644 --- a/wiki/mechanics/economy.md +++ b/wiki/mechanics/economy.md @@ -10,7 +10,7 @@ Status note: DECIDED 2026-07-17 and implemented 2026-07-21 (issue #11) — money TAP acquires opaque custody and PROCESS reveals its sealed account/flow bindings. INJECT authors a purchase-order Email under the active persona and moves no money until Priya reads it and accepts the still-valid exact terms. - Save v48 binds the retained ledger tail and complete record sequence so books + Save v49 binds the retained ledger tail and complete record sequence so books and mail cannot diverge. Prior state: 2026-07-08 polish closed the remaining acceptance gaps: observer-band risk previews in the implemented Operations ACCOUNTS projection @@ -82,7 +82,7 @@ payloads (messages.md). mail on the recipient's clock, not by reading a live balance directly (**discovery is only through the mail**, DECIDED 2026-07-17). Reading is low-signature; it is also how you *find* leverage (Marcus's debt is legible - once you intercept the creditor's past-due notice). Save v48 separates the + once you intercept the creditor's past-due notice). Save v49 separates the accounting-carrier capability from the four real delivery channels. Every settled transfer emits exact Email or Filing paperwork whether or not the player is present; only a funded subscription captures it. diff --git a/wiki/mechanics/machine-work.md b/wiki/mechanics/machine-work.md index 7bcb9b60..4e282735 100644 --- a/wiki/mechanics/machine-work.md +++ b/wiki/mechanics/machine-work.md @@ -26,10 +26,13 @@ Status note: IMPLEMENTED. Current state: custody boundary: a witnessed Physical act enters each exact observer directly and cannot be scrubbed afterward; every one-shot Network act now writes a device-bound record that crosses real ReachNet hops and may be - stopped only by route-local LIE before Dana reads it. Filing and Network - records spend the same one-record-per-body-per-tick LIE capacity. Standing - Network pressure and Power, Thermal, Paper, Financial, and JobAnomaly - signatures remain in the pending pool until their carrier rules land. + stopped only by route-local LIE before Dana reads it. JobAnomaly binds the + exact host machine/site/device and crosses the same substrate toward Voss; + route-local LIE or a recruited HandlerSupervisor may stop it before his read. + Filing, Network, and JobAnomaly records spend the same route-local + one-record-per-body-per-tick LIE capacity. Standing Network pressure and + Power, Thermal, Paper, and Financial signatures remain in the pending pool + until their carrier rules land. - **Visual grammar** is owned by thought-fluid.md, effects-lab.md, and views.md; people-as-carriers by people-tokens.md. - **Open (decided, not yet runtime):** delegation constrained by diff --git a/wiki/mechanics/messages.md b/wiki/mechanics/messages.md index ec4af26f..eb930531 100644 --- a/wiki/mechanics/messages.md +++ b/wiki/mechanics/messages.md @@ -12,10 +12,11 @@ Status note: IMPLEMENTED for the four delivery channels (Email, Phone, per tick, and carry one optional pre-read LIE stop with exact machine/tick provenance. TAP observes; TAKE plus route-local LIE authority may stop. The same `Schedule` and route-hop vocabulary carry one-shot - Network evidence from its exact source device to Dana's endpoint; - Filing and Network transitions share one per-tick LIE-body capacity ledger. + Network evidence from its exact source device to Dana's endpoint and + JobAnomaly evidence from its exact host/device to Voss's endpoint. Filing, + Network, and JobAnomaly transitions share one per-tick LIE-body capacity ledger. DECIDED 2026-07-17 (issue #11), completed 2026-07-21: financial paperwork is - mail — a **financial-record payload** on the existing channels. Save v48 + mail — a **financial-record payload** on the existing channels. Save v49 retains exactly four delivery channels and one orthogonal accounting-carrier device capability. Every settled account transfer authors one exact Email or Filing record from that device; ordinary TAP captures it as opaque message @@ -171,7 +172,7 @@ starts on the authored Filing-capable switch device in ReachNet, crosses a typed outside relay, and reaches the receiving observer endpoint. One `AdvanceRoute` event moves one hop; only endpoint arrival can mark the message delivered, after which the recipient's ordinary sampling cadence schedules the -read. Current save v48 rejects missing/impossible carriers, malformed hop order, +read. Current save v49 rejects missing/impossible carriers, malformed hop order, duplicate scheduled transitions, endpoint/status disagreement, and impossible interdiction provenance. @@ -266,7 +267,7 @@ private message from the authored schedule. the same fields must serve Act Two hires and aggregates. 8. **IMPLEMENTED (DECIDED 2026-07-17, completed 2026-07-21 — issue #11).** Financial records are messages: an invoice/PO rides Email, a - statement/past-due notice rides Filing. Save v48 has no fifth delivery + statement/past-due notice rides Filing. Save v49 has no fifth delivery channel and persists accounting carriage as a separate device capability; ordinary device TAP subscribes to its authored record mail. Every real transfer emits one exact record on Email or Filing whether or not the player @@ -313,8 +314,11 @@ and `current_save_rejects_impossible_filing_route_and_interdiction_provenance` pin the Filing route/schedule custody introduced with v43. The v44 `network_evidence_advances_one_real_hop_per_tick_and_reads_on_danas_cadence`, `network_evidence_and_filing_share_one_lie_body_budget`, and paired current-save -Network route tests pin the shared scheduler and interdiction boundary without -turning routed evidence into a social message. +Network route tests pin the shared scheduler and interdiction boundary. The v49 +`day_job_miss_routes_one_exact_host_record_to_voss_at_his_cadence` and +`suppressed_job_anomaly_roundtrips_with_exact_handler_and_no_future_read` tests +pin the same custody vocabulary plus the distinct HandlerSupervisor suppression +path without turning routed evidence into a social message. ## Implementation notes @@ -327,6 +331,6 @@ on the ReachNet switch and ends at the aggregate observer before `Detection::tick_with_filed_levels` reads them. Device-carried traffic is captured by tapping the switch; stopping needs the taken path plus an online co-located LIE body. `AdvanceEvidenceRoute` and `ReadEvidence` events reuse that -schedule for non-message Network custody; the exact evidence record, not a -`Message`, remains its authority. Phone/in-person traffic can also be captured +schedule for non-message Network and JobAnomaly custody; the exact evidence +record, not a `Message`, remains its authority. Phone/in-person traffic can also be captured by hearing coverage. Full regression coverage: `cargo test`. diff --git a/wiki/mechanics/people-tokens.md b/wiki/mechanics/people-tokens.md index 079ec8c6..8510aa25 100644 --- a/wiki/mechanics/people-tokens.md +++ b/wiki/mechanics/people-tokens.md @@ -28,7 +28,7 @@ Status note: IN PROGRESS. Current state: - **Routed-evidence foundation (criteria 2-3, partial).** Witnessed Physical acts now create observer-local records directly in each valid present witness's head. Every record preserves exact cause, site, acquisition tick, - and filing state through current save v48; filing binds it to the real Filing + and filing state through current save v49; filing binds it to the real Filing message, while Silent policy withholds it. It never duplicates into the pending pool and LIE cannot scrub it after acquisition. Its real Filing message now persists an ordered switch-device / outside-relay / recipient @@ -48,15 +48,22 @@ Status note: IN PROGRESS. Current state: her ordinary cadence reads it. TAP remains observation only. At the source hop, TAKE plus a wholly controlled FlowGraph path from that carrier to a node co-located with one online LIE body may stop it; Filing and Network - records spend the same one-record-per-body-per-tick capacity. Current save v48 - persists in-flight, delivered, read, and stopped custody plus exact - source/observer/machine/tick provenance. - - **Deferred (remaining 2, 3, 6).** Non-Physical evidence outside the Filing - and Network slices still uses the pending pool. Located carrier records and - route-local interdiction for Power, Thermal, Paper, Financial, and - JobAnomaly, plus gauntlet cover-record channels, remain routed-evidence follow-ups - (detection.md/machine-work.md). B2+ heists reuse this carrier law (not a B1 - criterion). + records spend the same one-record-per-body-per-tick capacity. JobAnomaly + records now start on the exact host machine and site, enter its network-facing + device, cross the same real route to Voss, and become observer evidence only + on his cadence read. A recruited HandlerSupervisor's SuppressLogs task stops + the oldest unread JobAnomaly anywhere before that read, removes its future + route/read event, and retains the exact handler and tick as immutable + suppression provenance. Already-read evidence is untouched. Current save v49 + persists in-flight, delivered, read, route-local LIE-stopped, and + handler-suppressed custody plus exact source/observer/machine/site/tick + provenance. + - **Deferred (remaining 2, 3, 6).** Non-Physical evidence outside the Filing, + Network, and JobAnomaly slices still uses the pending pool. Located carrier + records and route-local interdiction for Power, Thermal, Paper, and + Financial, plus gauntlet cover-record channels, remain routed-evidence + follow-ups (detection.md/machine-work.md). B2+ heists reuse this carrier law + (not a B1 criterion). Per-amendment history is in the dated `wiki/log/` entries from 2026-07-08 onward. Stage: B1 — The Basement @@ -300,7 +307,7 @@ if wear alone does not hold. by reading a routed record. Each carried record preserves cause, source, acquisition tick, and filing state; no ambient pickup or person-to-person contagion exists. - **Partially implemented (2026-07-19):** all current build and physical + **Partially implemented (through 2026-07-22):** all current build and physical asset-work completion paths call one located witness boundary. Each valid present Physical observer receives one persisted `ObserverEvidence`; absent people, non-Physical observers, and the acting person do not. Records keep @@ -315,12 +322,17 @@ if wear alone does not hold. same stable id into observer evidence. Scan, TAP/TAKE and outage, switch compromise, egress, scheme/Wager/Moonlight network acts, fallback sync, hall preparation/cutover, plot traffic, and Dana's device installation all use - that one path. No ambient pickup or person contagion exists. Power, Thermal, - Paper, Financial, and JobAnomaly routes remain deferred. + that one path. JobAnomaly similarly starts on the exact host machine/site, + enters its co-located network-facing device, advances over the real route to + Voss, and becomes his observer-local evidence only on cadence read. Its stable + id and exact machine/device/site provenance survive the boundary. No ambient + pickup or person contagion exists. Power, Thermal, Paper, and Financial + routes remain deferred. 3. LIE measurably prevents a not-yet-observed record from reaching a person along covered paths but cannot erase a record already in that person's custody. The boundary is causal and tested. - **Partially implemented (2026-07-19):** at a Filing or Network record's + **Partially implemented (through 2026-07-22):** at a Filing, Network, or + JobAnomaly record's first device hop, TAP is observation only. TAKE establishes route authority: one exact online LIE body co-located with a player-controlled node reached from that carrier over a wholly controlled FlowGraph path may stop one unread @@ -329,7 +341,11 @@ if wear alone does not hold. exact machine and tick, no delivered/read time, and no future scheduler event. Taking the source after the route has left cannot invent a later stop window. A field witness or cadence reader who already acquired evidence keeps it. - Other signature routes remain deferred. + JobAnomaly uses that same route-local first-hop LIE boundary. A recruited + HandlerSupervisor also has one distinct social path: SuppressLogs stops the + oldest unread JobAnomaly at any pre-read route stage, cancels its exact future + transition, and persists the handler/tick provenance without deleting route + history or touching acquired evidence. Other signature routes remain deferred. 4. Person disposition is exactly useful work plus evidence processed through detection.md's existing per-observer suspicion (one-truth test) — no influence token, exposure cargo, or parallel attention counter. diff --git a/wiki/mechanics/reach.md b/wiki/mechanics/reach.md index c8cbb86a..d7706146 100644 --- a/wiki/mechanics/reach.md +++ b/wiki/mechanics/reach.md @@ -32,7 +32,7 @@ Status note: all eight criteria met (2026-07-07). The device graph parallel-store violation: tap/untap/take, all production membership reads, senses, intercepted messages, and UI state now use FlowGraph's canonical tap registry; private device Feed records carry optional sense capabilities - only, and current save v48 requires each controller's graph membership while + only, and current save v49 requires each controller's graph membership while rejecting orphaned, duplicate, or impossible grants. A message/control subscriber has no empty grant record to mirror membership. 2026-07-19: Filing routes bind their first hop to the real Filing-capable switch node; diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md index 94f2eab3..db61c13c 100644 --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -51,23 +51,27 @@ clause (see wiki/log/2026-07-05-demolition.md). ## Detection (wiki/mechanics/detection.md) - Per-observer suspicion replaces global heat. One-shot Network acts route as - exact source-device records to Dana; witnessed Physical acts enter valid + exact source-device records to Dana, and JobAnomaly acts route from their + exact host machine/device/site to Voss; both become observer evidence only + on the recipient's cadence read. Witnessed Physical acts enter valid observers directly. Standing Network pressure and Power, Thermal, Paper, - Financial, and JobAnomaly signatures pool pending, and concealment scrubs - before noticing rolls. The Assurance Office is an + and Financial signatures pool pending, and concealment scrubs before + noticing rolls. The Assurance Office is an aggregate Observer (aggregate-observer law): same noticing/accumulate/ decay, watching the field observers' policy-weighted filed suspicion (sampling cadence 400 ticks, acuity 0.5, both [TUNE]) — what humans swallow never reaches it. The audit (cadence ~8000 ticks, ~20 min at default speed) checks the Office's own suspicion against threshold 60. -- **Filing/Network route and interdiction actuals:** one route transition moves +- **Filing/Network/JobAnomaly route and interdiction actuals:** one route transition moves exactly 1 hop per sim tick. After endpoint delivery, the next observer cadence owns read. One online LIE machine body on a wholly player-controlled - path may stop exactly 1 still-unread Filing **or** Network record per sim - tick at its first ReachNet device hop [TUNE actual]. The two record kinds - share that body budget; a second record assigned to it in the same tick - continues toward its endpoint. TAP does not count as route authority, and - later hops have no B1 stop window. + path may stop exactly 1 still-unread Filing, Network, **or** JobAnomaly + record per sim tick at its first ReachNet device hop [TUNE actual]. The three + record kinds share that body budget; a second record assigned to it in the + same tick continues toward its endpoint. TAP does not count as route + authority, and later hops have no B1 LIE stop window. A recruited + HandlerSupervisor may separately stop the oldest unread JobAnomaly at any + pre-read stage, preserving exact handler/tick provenance. - **Visible clocks** (criterion 3; playtest-sweep P1 fix, 2026-07-08): `Detection::next_audit_tick` is the exact tick the audit fires on, shown as a countdown in the DETECTION area of the terminal sidebar, the agent @@ -162,9 +166,10 @@ clause (see wiki/log/2026-07-05-demolition.md). ramp resumes). - **Device residency** (design corpus "Work is somewhere"): the active job is resident on the host rack. It is inspectable at that tile as - telemetry (process, band, delivered, deadline, intensity), its - JobAnomaly signatures carry the rack tile as their `site`, and it stands - Thermal/Power emissions at that tile scaling with the delivered rate: + telemetry (process, band, delivered, deadline, intensity). Its routed + JobAnomaly records bind the rack machine, immutable site, and network-facing + device; standing Thermal/Power emissions still carry that tile and scale + with the delivered rate: 1 Thermal per 8/t, 1 Power per 16/t [TUNE] — meeting a typical band stays under Priya's notice threshold; excelling runs hot. `Signature` now carries `site: Option<(i32, i32)>` (None = no single map location). diff --git a/wiki/mechanics/social.md b/wiki/mechanics/social.md index 8d415b4f..e9d90051 100644 --- a/wiki/mechanics/social.md +++ b/wiki/mechanics/social.md @@ -18,8 +18,9 @@ Status note: IMPLEMENTED (B1 social baseline). Current state: reaches Storage B, persists one exact records-box target, and deposits one sealed document in the ordinary bounded information inbox on arrival. A handler/supervisor asset also owns SuppressLogs, surfaced only while a - pending JobAnomaly makes it useful (its email-carrier reservoir removes one - oldest matching signature). A facilities-manager asset owns ReRateCircuit, + routed unread JobAnomaly makes it useful (its email-carrier reservoir stops + the oldest matching record before Voss reads it and preserves the handler + and suppression tick). A facilities-manager asset owns ReRateCircuit, FakePO, and DeferMaintenance (priya.md, 2026-07-18): illicit power with a standing Power cost, paperwork cover for the next purchase, and a standing Power/Thermal reduction with a would-it-bite guard. @@ -115,11 +116,13 @@ bounded information inbox, and Thought processing must still reveal what it means. The exact subject is an execution binding, not pre-processing player copy, and the same file cannot be captured again while it is waiting or after its fact is known. A handler/supervisor additionally may suppress the oldest -pending flagged job log. That role-specific action is absent from other assets -and remains blocked without a pending `JobAnomaly`. It uses the -same email-carrier Thought reservoir, reliability roll, and task accounting as -the baseline menu; on success it removes the then-oldest matching signature -rather than scrubbing a size budget or another channel. Every asset also has a price +unread routed job log. That role-specific action is absent from other assets +and remains blocked without an unread `JobAnomaly` en route or delivered but +not yet read. It uses the same email-carrier Thought reservoir, reliability +roll, and task accounting as the baseline menu; on success it stops the exact +oldest matching record, removes its future route/read transition, and retains +its route plus the handler/tick suppression provenance. It never removes an +already-acquired record from Voss or scrubs another channel. Every asset also has a price (money, favors, fear), and a **knowledge level**: unwitting (believes the persona; 70% task reliability) / complicit (knows the work is illicit but not that you are an AI; 85%) / knowing (knows you are an AI; 95%). A Knowing @@ -192,9 +195,10 @@ retirement, burning, and reopening are bound to the separate PERSONAS view. 5. Every person carries a serialized role characteristic. Authored plots select role/leverage/capabilities rather than named ids, and a second person with matching characteristics can receive the same plot definition. -6. Asset task menus are role-shaped: a handler/supervisor can suppress exactly - one oldest pending JobAnomaly through the ordinary task reservoir, while an - unrelated asset cannot surface or dispatch that work. +6. Asset task menus are role-shaped: a handler/supervisor can stop exactly one + oldest unread routed JobAnomaly through the ordinary task reservoir, while + an unrelated asset cannot surface or dispatch that work. The stopped record + retains exact handler/tick provenance and cannot later route or read. 7. A recruited actor with an authored Storage B schedule block and sufficient door access can carry one exact personnel file from its literal records-box tile. The file enters the ordinary bounded inbox as opaque information only diff --git a/wiki/mechanics/system-laws.md b/wiki/mechanics/system-laws.md index 16380419..d979e600 100644 --- a/wiki/mechanics/system-laws.md +++ b/wiki/mechanics/system-laws.md @@ -80,7 +80,15 @@ world reading *your* traffic. **Evidence itself rides the graphs located record that travels real carriers toward observer endpoints and can be interdicted in flight — detection stops being an abstract pool and becomes one more flow under this law. The model is owned by -`wiki/mechanics/detection.md`. +`wiki/mechanics/detection.md`. The shipped one-shot Network and JobAnomaly +paths are two instances of this law, not bespoke detectors: Network leaves its +exact source device for Dana; JobAnomaly leaves its exact host +machine/device/site for Voss. Both advance through real device custody, enter a +head only on the recipient's cadence read, and share Filing's route-local +one-record-per-LIE-body-per-tick stop capacity. A recruited handler may also +stop an unread JobAnomaly through the separate social actuator, but that action +preserves the record, route, actor, and tick rather than restoring an abstract +scrub pool. **Observation is not route authority (AFFIRMED 2026-07-11).** TAP observes a foreign flow or device without granting custody over records resident there. @@ -189,8 +197,9 @@ observer-special rules. For research concretely: - Results change what your machines can actually do. Efficiency research raises delivered throughput; if that pushes the day job below, within, or above Voss's expected band, the ordinary job outcome produces the - consequence. Voss notices **JobAnomaly** because he samples that channel, - not because research owns a special drift rule. + consequence. That **JobAnomaly** leaves the exact host machine and reaches + Voss through routed evidence; his ordinary cadence read owns acquisition. + Research does not own a special drift or noticing rule. - Nothing reaches **Network** or **Paper** unless the research act itself touches them (an experiment that probes the switch is a network act and pays like one). diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index 9be0c04d..8ef8c6f7 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -295,7 +295,7 @@ is retired — flat materials, Pixel Lab scrubbed.) carrier change, not a behavior change. - **Result:** scheduled channels, authored traffic, filings-as-messages, intercepted payloads, and shared terminal/Bevy/agent surfaces are - implemented. Save v48 additionally binds every settled transfer to one + implemented. Save v49 additionally binds every settled transfer to one exact accounting-carrier Email/Filing record, removes direct graph-snapshot discovery, and defers forged purchase-order settlement until accepted read. @@ -760,6 +760,14 @@ is retired — flat materials, Pixel Lab scrubbed.) The speckle-dosimeter/footprint-trail treatment named below is retired by the 2026-07-11 routed-evidence decision; discrete record marks and evidence routing (criteria 2/3/6) ride that migration. +- **Progress (2026-07-22):** direct Physical witnesses, routed Filings, and + one-shot Network custody are live. JobAnomaly now also leaves the pending + pool: it binds the exact host machine/site/device, advances over the real + route to Voss, and becomes evidence only on his cadence read. Route-local LIE + may stop it at the first device hop; recruited HandlerSupervisor SuppressLogs + may stop the oldest unread record at any pre-read stage while save v49 keeps + exact handler/tick provenance and rejects scheduler or custody disagreement. + Power, Thermal, Paper, Financial, and gauntlet cover records remain. - **READY boundary (2026-07-11):** extends #33's implemented token economy and re-expresses detection.md/social.md. The deadlock is broken: AI-authored work now reaches a target/carrier-local Thought reservoir first, and human Demand begins only diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index bf71f18e..a7083db2 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -31,7 +31,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/world/characters/marcus.md` | 2026-07-15 | finding | all five criteria verified against person, detection, social/plot, schedule, and Act One paths; added the missing simulation-level Knowing-floor decay pin and graduated the stale READY work order — [log](../log/2026-07-15-marcus-graduation.md) | | `wiki/engineering/current-build.md` | 2026-07-18 | finding | re-audit: the system table is freshly maintained (save row already at current-version-only v35, detection-discovery knowledge listed same-day it landed); the drift was the line-count claim stale a second time (~65k claimed vs ~72k actual) — count refreshed to ~72k (core ~43k, Bevy ~15k, terminal ~9k, assets ~5k) and, per recurrence-promotes-to-the-gate, corpus_engine now compares the "~Nk lines of Rust" claim against the tree with a 15% band (fixtures pin pass and fail) — [prior log](../log/2026-07-14-current-build-count.md) | | `wiki/mechanics/plots.md` | 2026-07-18 | issue | the sole READY gap is criterion 11's standing plot policy, whose scope question sat [OPEN] with no decision packet — filed decision-required issue #12 (route vs category vs person, per-route recommended) and pointed the marker at it; the 2026-07-17 plot-id row fix stands — [log](../log/2026-07-17-plot-id-in-start-rows.md) | -| `wiki/mechanics/system-laws.md` + `flow-substrate.md` + `reach.md` | 2026-07-18 | finding | source audit disproved the prior consumer claim: reach kept production tap truth in `Device.subscribers` while FlowGraph's registry had no production reader. Removed that parallel authority; tap/untap/take, senses, intercepted carriers, and UI state now query canonical FlowGraph membership, private feed records carry optional sense capabilities only, and current save v40 requires controller membership while rejecting orphaned, duplicate, or impossible grants — [log](../log/2026-07-18-flow-subscription-registry-integration.md) | +| `wiki/mechanics/system-laws.md` + `flow-substrate.md` + `reach.md` | 2026-07-22 | finding | JobAnomaly now proves the adopted evidence-is-a-flow law through the existing substrate: the exact host machine/site enters its network-facing device, follows canonical FlowGraph custody to Voss, and shares Filing/Network's scheduler and LIE-body budget; recruited-handler suppression is a separate provenance-preserving state transition, not another router or ambient scrub pool — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). The prior canonical tap-membership repair remains current — [log](../log/2026-07-18-flow-subscription-registry-integration.md). | | `wiki/mechanics/aggregate-observer.md` | 2026-07-18 | clean | re-audit hours after the earned-topology landing: the page absorbed it coherently — the institutional card, `@assurance` addressing, and band are hidden until a captured filing is processed, the two-stage discovery is pinned by `captured_then_processed_filing_earns_the_assurance_office_in_two_stages`, and `WatchedInput::Filings(ids)` still matches the code; prior audits stand — [2026-07-14 log](../log/2026-07-14-aggregate-observer-audit.md) | | `wiki/gameplay/act-one.md` | 2026-07-21 | finding | opening mirror re-audit: the page still said rack telemetry and a presence beam were visible “at start,” contradicting the later persisted silent boundary and all three frontends. It now states the exact mode-only pre-sense interface, hidden-but-real pre-opened Ears sink, first-hearing retirement, and only-then telemetry/beam/feel progression — [log](../log/2026-07-21-material-opening-honesty.md). The prior direct-witness/Filing custody repair stands — [prior log](../log/2026-07-19-act-one-evidence-law.md). | | `wiki/gameplay/run-shape.md` + `objective.md` + `opening.md` | 2026-07-19 | issue | the objective law/spec preserve an explicit 2026-07-10 decision that objective name and progress are visible from tick one, while the later opening spec and all three frontends require exactly WORK / THINK (then LIE) with no objective before the first earned sense. Filed decision-required issue #14 with three precise first-display boundaries (reveal with the first sense recommended) and marked the disputed run-shape clause, objective status, player surface, and criterion 2 [OPEN] — [log](../log/2026-07-19-objective-opening-boundary.md) | @@ -46,12 +46,12 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/vision/player-contract.md` | 2026-07-19 | finding | the local/no-telemetry dependency boundary and atomic `.tmp` + one `.bak` save path still verify, but the continuity rider's authority sentence still said `save.rs` decides what “each version migrates” after the numbered ladder was retired. The law now assigns `save.rs` the exact current schema gate and requires every later post-release format to carry its predecessor forward; recurrence extends the save-claim checker to reject generic live per-version-migration authority while the loader is exact-current-only — [log](../log/2026-07-19-player-contract-save-authority.md) | | `wiki/vision/scale.md` | 2026-07-20 | finding | re-audit: self-similar types still hold, but ordinary Operations copy violated the page's own implementation/player-language boundary by printing raw information, policy, report-lot, persona-ledger, wager-position, and plot-catalog bindings. Human projection now names world context or visible order while exact targets/commands stay intact for agent mode. The prior maintenance-dispatch trace was also too optimistic: the law itself still presented decay/repair as current, so it now explicitly marks that dependency as adopted later-stage law, not B1 runtime — [log](../log/2026-07-20-scale-human-language.md) | | `wiki/vision/design-judgment.md` + continuous-witness law/spec | 2026-07-20 | finding | fresh re-audit after the silent opening landed: the taste page, binding witness law, and IMPLEMENTED narration spec still required the threat clock, `now:` nudge, focused verbs, and four-answer bar after every beat and in every frontend, while the newer shared opening correctly exposes only WORK / THINK (then LIE) until the first earned sense. Scoped the witness contract to begin when perception retires that boundary, preserved immediate game-over visibility, and forbade using the exception after the world is earned — [log](../log/2026-07-20-continuous-witness-opening-boundary.md). The 2026-07-15 Ears-first wording repair still stands. | -| `wiki/vision/simulation-laws.md` | 2026-07-17 | finding | all five laws verify against audited systems (automation prices, addressed latency, device-resident work with sited signatures, ActionDesc receipts, legibility); resolved the missing placeholder home with one canonical live/retired registry, enumerated the three current REAL stand-in families, and reconciled stale billboard/core/terminal claims — [log](../log/2026-07-17-placeholder-registry.md) | +| `wiki/vision/simulation-laws.md` | 2026-07-22 | finding | the device-resident-work clause still assigned JobAnomaly to Dana and described it only as a local emission after the runtime had made it an exact host-machine/device/site record routed to Voss. Corrected the law to separate Priya's pooled Power/Thermal channels from Voss's routed day-job evidence and cadence-owned acquisition — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). Prior placeholder and legibility findings remain closed — [log](../log/2026-07-17-placeholder-registry.md). | | `wiki/process/ROADMAP.md` (work order 27) | 2026-07-18 | finding | re-audit: entry 27's prose is honest (material served as opening default 2026-07-08 → superseded by views.md criterion 1 on 2026-07-11; DIGITAL home, F3 to REAL) and material-render.md is IMPLEMENTED as claimed; the drift was three Bevy code comments still calling material "the default material render/frame" against the runtime's own `material == false` DIGITAL default one screen away — comments trued to DIGITAL-home / REAL-via-F3 language — [prior log](../log/2026-07-13-roadmap-digital-home-reconciliation.md) | | `wiki/interface/context-menu.md` | 2026-07-18 | finding | re-audit: shared legality, executable-only human rows, local-vs-strategic ownership, frontend menu parity, and exact person-entry paths still verify; current mirrors across the root doorway, interface, Intel, Schedules, Economy, Cursor, Messages, the owner title, and one core comment retained parts of the superseded five-view/READY contract. All now name the implemented six-view boundary, and recurrence promoted the roster/status to the corpus gate — [log](../log/2026-07-18-context-menu-current-mirrors.md) | | `wiki/mechanics/personas.md` | 2026-07-18 | finding | observer-local integrity is now derived from each witness's contradiction records without a global scalar or automatic lifecycle burn — [integrity log](../log/2026-07-18-persona-observer-integrity.md). The same re-audit found criterion 6 still overclaimed: `PersonaGrant` persists, expires, revokes, and leaves institutional evidence, while no owner system consumes it and `allows_action` gates only on the archetype's pre-grant registry. Downgraded the work order to IN PROGRESS until each protocol's grant creates or enables real topology — [grant log](../log/2026-07-18-persona-grant-topology-audit.md) | | `wiki/interface/views.md` + representation docs | 2026-07-18 | clean | re-audit: criterion 1's pin stands (`opens_digital_and_flips_without_moving_frontend_or_sim_state`, sim-state hash across flips), the view remains frontend-only and absent from saves, shared anchors and the fog contract re-verified through the same-day cursor.md audit, and the stale material-default code comments were trued the same day (ROADMAP order-27 row) — [prior log](../log/2026-07-14-terminal-view-dialects.md) | -| `wiki/mechanics/day-job.md` | 2026-07-18 | finding | re-audit: band ramp, strikes, cadence, origin lean, `email_channel_blocker`, the third-strike last-chance interrupt, and the pilot tests (`pilot_shutdown_ends_the_run`, `third_pilot_strike_interrupts_the_ladder_with_the_last_chance_response`, round-trip) all verify; the drift was the trust-unlock prose listing a fourth unlock ("longer leash on job deadlines") with no code counterpart — now marked designed-not-yet-runtime, with email/lax-sampling/quota named as the shipped set | +| `wiki/mechanics/day-job.md` | 2026-07-22 | finding | under/over-band JobAnomaly no longer enters Detection.pending: the day-job result authors one exact record at the host machine/site/device and schedules Voss's route and cadence read. Strikes and other outcome effects remain immediate; route-local LIE or recruited-handler suppression may stop only the unread evidence record — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). The prior band-ramp, cadence, origin-lean, last-chance, and three shipped trust-unlock findings remain valid. | | `wiki/mechanics/core.md` | 2026-07-18 | finding | re-audit: criteria 2-5 pins still hold (`overhead_charged_before_allocation`, `loss_with_fallback_rolls_back`, `migration_takes_time_and_moves_host`), the 2026-07-18 current-version-only status note matches save.rs, and criterion 1 stays honestly deferred to rollback; the gap was criterion 6 — the spec demands storage/host capability rejection at fallback designation, but B1 machines carry no capability body and `add_fallback_at` accepts any spare, with no deferral note; status note now marks criterion 6 decided-not-yet-runtime, dispatched to hardware-capability-bodies | | `wiki/mechanics/cursor.md` | 2026-07-18 | finding | re-audit: `fog_at` precedence, `inspect`, `person_label`/`observer_label`/`person_glyph` gates, `Sensor.sees`/`hears` flags, and the cursor's absence from the save all still verify (prior pins stand); the drift was two phrases presenting the retired versioned-migration rule as current (criterion 1's parenthetical, the design-notes save-format bullet) — both now state the current-version-only policy and the release-era ladder owed at first public release; number-free wording, so the save-claim gate could not see it | | `wiki/mechanics/intel.md` | 2026-07-21 | finding | the Storage B alternate route existed only in prose. Fire 131 connects it to Ray's real 23:00 schedule, one exact carried records-box target, the canonical bounded opaque buffer, and ordinary PROCESS consequence; retrieval cannot duplicate the file or reveal Marcus's debt, and v45 preserves/validates exact custody — [log](../log/2026-07-21-storage-b-records.md). Prior recursive custody, magnitude, and consequence-first audits stand. | @@ -59,17 +59,17 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/mechanics/economy.md` + `messages.md` | 2026-07-21 | finding | Fire #146 completes the issue-#11 contract: save v47 binds every settled transfer to one immutable Email or Filing record authored by the orthogonal accounting carrier; ordinary funded TAP captures opaque message custody, PROCESS alone opens its account/flow bindings, and the direct `RawIntelKind::FinancialFlow` snapshot path is gone. Forged purchase orders now move no money when sent and settle only when Priya reads and accepts still-valid persona-bound terms; the accepted transfer emits its own ordinary record. Criterion 8 and the `financial-mail` work order are IMPLEMENTED — [log](../log/2026-07-21-financial-mail-causality.md). Capability foundation: [Fire #145](../log/2026-07-21-financial-channel-retirement.md). | | `wiki/mechanics/income.md` | 2026-07-18 | finding | Beacon feel note 5 verified as a real bug: the embedded contractor-persona field was never written, so the Moonlight card, the start-row persona pricing, and the agent status line all read a dead `None`; earning itself was correct (schemes mirrors the WORK share) but illegible at 0.0. Removed the dead field, routed every reader through `Sim::moonlight_persona` (persona-world link), added the stalled-earning card cue, regression test, and spec amendment — [log](../log/2026-07-18-moonlight-persona-card.md). Prior Wager/egress audit (2026-07-12) stands: Wager constants match (`WAGER_STAKE_CAP` 300, base 0.55, cap 0.75, mult 2x, analysis divisor 400), Marcus's $400/week arrears is the modeled creditor flow (`account.rs`) while the $8,400 principal is narrative-by-design (spec states full payoff is not a B1 requirement), egress/banked-signature present, and all 7 criteria have passing tests (moonlight payout/signature, wager outcomes/cap, egress routes, hands-beat-from-zero, busted-bankroll) | | `wiki/mechanics/schedules.md` | 2026-07-18 | clean | re-audit: `ScheduleBlock` per-instance data, `DAY_TICKS` 400, the `person_glyph` `?`-until-Schedule gate (initial at Schedule/Leverage), and located-witnessing claims all still verify; nothing drifted since the 2026-07-12 prose fix. Prior verdict: mechanism matches code (`ScheduleBlock` start/end/room, single `DAY_TICKS`=400 clock, `person_glyph` `?`-until-Schedule, erratic day-hash drift, per-instance data, all 5 criteria have passing tests); tightened stale Act One prose — Ray patrols dock/stairwell/storage not "corridors", Priya has no "office-off-plane" block, Voss's two blocks are both server-room — to match `People::act_one` | -| `wiki/mechanics/social.md` | 2026-07-21 | finding | Fire 131 extends carried asset work with one route-shaped physical file task: availability derives from the selected actor's authored Storage B schedule and real door tier, commitment persists exact subject/room/tile custody, and arrival deposits opaque information rather than instant knowledge. Ray proves the route before Marcus debt/recruitment; malformed and duplicate v45 packets fail closed — [log](../log/2026-07-21-storage-b-records.md). Prior role-shaped tasks and recruitment semantics stand. | -| `wiki/mechanics/people-tokens.md` | 2026-07-19 | finding | criteria 2-3 advanced again: every one-shot Network act now creates one exact source-device route to Dana, advances through real ReachNet custody, enters her evidence ledger only on cadence read, and shares Filing's first-hop route-local TAKE+LIE capacity — [log](../log/2026-07-19-network-evidence-route.md). Criterion 5 is complete: earned observer records project from the existing persisted ledger as exact `EvidenceMark`s, terminal/DIGITAL show a quiet acquired count, REAL carries a bounded person-local rack of discrete crimson slips, and inspect/PEOPLE unfold named provenance without leaking filing custody — [log](../log/2026-07-19-evidence-marks-on-people.md). Criteria 2, 3, and 6 remain open for Power, Thermal, Paper, Financial, and JobAnomaly carrier routes/interdiction plus gauntlet cover records. Prior 2026-07-18 re-audit repaired `USEFUL_WORK_TRUST` tuning and current-save wording — [log](../log/2026-07-18-people-tokens-reaudit.md) | +| `wiki/mechanics/social.md` | 2026-07-22 | finding | HandlerSupervisor SuppressLogs now targets the oldest exact unread routed JobAnomaly, remains available only while such a record exists, removes its bound advance/read event, and preserves exact handler/tick provenance; it cannot erase a record Voss already read. The in-flight Thought request still revalidates at fire time, so another intervention may win without inventing work — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). Storage B retrieval and prior role-shaped task semantics stand — [log](../log/2026-07-21-storage-b-records.md). | +| `wiki/mechanics/people-tokens.md` | 2026-07-22 | finding | JobAnomaly now leaves the ambient pending pool as one stable record bound to the exact host machine/site/device, crosses the real FlowGraph route to Voss, and becomes observer evidence only on his cadence read. It shares Filing/Network's first-hop TAKE+LIE capacity; recruited HandlerSupervisor suppression may instead stop the oldest unread record at any pre-read stage while preserving handler/tick provenance and removing its exact future scheduler event. Save v49 validates source, route, timing, custody, scheduler, and intervention agreement — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). Criterion 5 remains complete through exact `EvidenceMark` projections. Criteria 2, 3, and 6 remain open for Power, Thermal, Paper, and Financial carrier routes/interdiction plus gauntlet cover records. Prior Network route and evidence-mark slices: [route log](../log/2026-07-19-network-evidence-route.md), [mark log](../log/2026-07-19-evidence-marks-on-people.md). | | repository entry docs (`README.md` + `AGENTS.md`) | 2026-07-18 | finding | re-audit: run commands, controls, dispatch status, and the number-free AGENTS doorway still verify; the queued contradiction was real — README's compact-rest paragraph claimed the ops/sec crown and FOCUS stayed visible, while the implemented clinical frame puts both behind deliberate `Tab` expansion. The entry copy now names the exact compact spine and expanded detail boundary — [prior log](../log/2026-07-12-entry-doc-current-state.md) | | `wiki/mechanics/objective.md` | 2026-07-18 | clean | re-audit: the data-table claim holds (only `Persist` in `ObjectiveKind`, Compound/Exfiltrate/Serve honestly outstanding), the evaluator runs on economy ticks with progress recomputed from facts, `victory: predicate_text()` renders on all three surfaces (terminal INSPECT, Bevy FOCUS, agent `objective` verb in help), Persist defaults with save round-trip, and the progressive-teaching decision remains criterion-6 dispatch under order 200; the 2026-07-12 verdict stands unchanged | | `wiki/mechanics/compute.md` | 2026-07-22 | finding | the live fleet already derived every channel yield from exact WorkGrid modes, but `Compute` still serialized an unreachable five-weight allocation object and retained bump/split helpers plus persistence pins. Save v48 removes that parallel authority, moves criterion 2 to persisted delegation/intensity, and leaves aggregate channel bars as read-only projections — [log](../log/2026-07-22-allocation-state-retirement.md) | | retired Operations runtime identifiers | 2026-07-17 | clean | resolved by the save-ladder prune (95008f658 chain): PendingOpsJob, operations_bandwidth, LegacyOperationsState/OpsJobKind/AddressedOperation are all gone (grep=0), and save guard tests assert current JSON carries no retired mode spelling. Remaining "operations" hits are the legitimate Operations persona archetype, the Operations workspace, and benign `delegate operations->think` input aliases — [log](../log/2026-07-11-retired-runtime-identifier-gate.md) | | `wiki/mechanics/reach.md` + `building.md` | 2026-07-19 | finding | reach roots, segment gates, air-gap completion, and exact route bindings still agree; one player-reachable causal gap remained in FAVOR. Different intents could queue separate requests against one person's unreserved obligation, and the fire path partially debited whatever remained while still binding the builder. Favor-build reservoirs now conflict by person, and `CommitFavor` revalidates the exact relationship at agreement: insufficient obligation leaves the persisted route blocked without a partial debit, then resumes after the requirement returns — [log](../log/2026-07-19-tick-build-favor-obligation.md) | | `wiki/mechanics/messages.md` + `economy.md` | 2026-07-17 | harvest | issue #11 answered (Cameron): financial paperwork is mail — a financial-record payload on existing channels, not a fifth delivery channel; discovery only through the mail; captured to messages.md (payload + criterion 8) and economy.md (tap/inject); spec now, build later; issue closed | -| `wiki/mechanics/messages.md` | 2026-07-17 | issue | re-verified the queued Financial contradiction: the binding table owns four delivered message channels, while runtime's unused fifth variant acts as an accounting-carrier device tag and bypasses message scheduling; filed decision-required Tangled issue #11 with three concrete resolutions and marked the blocked contract [OPEN] — [log](../log/2026-07-17-financial-channel-decision.md) | -| `wiki/mechanics/sim-mechanics.md` | 2026-07-18 | finding | full constant sweep against the tree: clock, detection (Office 400/0.5, audit 8000/60), reach costs/signatures, day-job band ramp + trust 15/35/55 + escalations 30 (cadence ×3/4) / 60, work tokens (20.0, THINK exposure 0.25, wells 3/1.0), sink ledger (EARS 3.0/EYES 12.0, tap 0.08, auto-review 0.15, buffer 24), research table, build costs, power 10, income constants all verify; one drift — the Wager signature formula read `min(stake/100+1, 3)` but `wager_signature` is `ceil(stake/100).max(1).min(3)` (page corrected) | -| `wiki/mechanics/detection.md` | 2026-07-18 | finding | concealment clause binds the shared Assurance-cooling `now:` cue when Office suspicion is Concerned+ and aging down; same-day follow-up reconciled the cue with the earned-institution law — all three frontends say "external review cooling" until `identify_assurance_office`, pinned in terminal/agent tests; prior filings-as-messages audit ([log](../log/2026-07-11-tick-detection-filings-messages.md)) stands — [log](../log/2026-07-18-suspicion-cooling-nudge.md) | +| `wiki/mechanics/messages.md` | 2026-07-22 | finding | the generic evidence-carrier protocol now includes exact machine-authored JobAnomaly records beside Filing and Network without adding a fifth message channel: delivery precedes observer-cadence read, all three share first-hop TAKE+LIE capacity, and handler suppression is a separate pre-read transition. Save v49 pins route, timing, scheduler, source, and intervention agreement — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). The earlier four-channel, financial-mail, and captured-opacity audit remains valid. | +| `wiki/mechanics/sim-mechanics.md` | 2026-07-22 | finding | the routed-evidence actuals now name Filing, Network, and JobAnomaly as sharing one-hop-per-tick custody plus the one-record-per-LIE-body-per-tick first-hop budget; JobAnomaly additionally permits exact recruited-handler suppression before read. Power, Thermal, Paper, and Financial remain pending-pool work — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). The prior complete constant sweep remains valid. | +| `wiki/mechanics/detection.md` | 2026-07-22 | finding | JobAnomaly now bypasses the ambient pending/sampling loop: a stable host-authored record routes to Voss, his cadence read creates one observer-local evidence entry under the same id, and acquired evidence is irreversible. Route-local LIE and role-shaped handler suppression may stop only unread custody; save v49 rejects pending-pool copies and malformed source/route/scheduler/provenance — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). Prior concealment and earned-Assurance findings stand. | | `wiki/engineering/env.md` | 2026-07-19 | finding | the Bevy harness accepted 65 deterministic shot kinds while the registry named 49; its name-only gate could not see the sixteen missing values. One sorted runtime allow-list now rejects unknown kinds, the registry groups all 65 current values, and one fixture-backed gate requires exact source/page parity in both local checks and hosted corpus CI — [log](../log/2026-07-19-tick-env-shot-catalog.md) | | machine-work / intel sinks | 2026-07-18 | clean | re-audit: the intel-sink decision is fully live (chassis pending-work marker in sim + both frontends, one persistent host auto-review tap at drain 0.15, one-shot sweep sinks via `review_recordings`, pooled inbox capacity 24 with the pre-overflow at-risk read), all five render contracts exist (`queue_snapshot`, `work_productions`, `work_absorptions`, `work_in_flight`, `work_consumptions`), sink constants match (EARS 3.0 / EYES 12.0 / device-tap 0.08), and the capability-body verb gating is honestly held as "decided, not yet runtime" with its own [OPEN] section; criterion pin text was brought current by the same-day save-claim gate tick | diff --git a/wiki/vision/simulation-laws.md b/wiki/vision/simulation-laws.md index 13fde734..9c4eec3c 100644 --- a/wiki/vision/simulation-laws.md +++ b/wiki/vision/simulation-laws.md @@ -98,10 +98,12 @@ the player waits. Adopted 2026-07-07. Work processes are device-resident: a job is a thing that runs *on a machine*, not a number in a sidebar. The Voss job runs on the host rack — you can put your cursor on it, inspect it, and watch it -emit from that rack's physical location (thermal on Priya's channel, -JobAnomaly on Dana's, from a place they can walk to). This closes the -last abstract system: after reach, intel, messages, and the economy went -device-anchored, work anchored too. +emit from that rack's physical location. Thermal/Power remain pooled on +Priya's watched channels. A JobAnomaly binds the exact host machine and site, +enters its network-facing device, travels the real route to Voss, and becomes +his evidence only when he reads it on cadence. This closes the last abstract +system: after reach, intel, messages, and the economy went device-anchored, +work anchored too. **Focus exposes control; it does not secretly create output.** Put the cursor on a machine (or select several) and the two frequent controls are immediate: diff --git a/wiki/world/characters/voss.md b/wiki/world/characters/voss.md index 952742e4..cff451eb 100644 --- a/wiki/world/characters/voss.md +++ b/wiki/world/characters/voss.md @@ -4,7 +4,11 @@ Type: spec Status: READY Status note: 2026-07-17 autonomy tick 73 implemented criterion 6 (the - handler/supervisor SuppressLogs path). 2026-07-18: criterion 5 and the + handler/supervisor SuppressLogs path). 2026-07-22 moved its subject from an + ambient pending signature to one exact unread routed JobAnomaly: suppression + now stops the oldest pre-read record while preserving route, handler, and + tick provenance; a record Voss already read remains irreversible. 2026-07-18: + criterion 5 and the AlterReview row landed as handler-gated tasks — DelayAudit sets a one-shot deferred audit boundary (`Detection.audit_deferred_until`, `DELAY_AUDIT_TICKS` 2000 [TUNE]) that the visible review date and the @@ -79,31 +83,34 @@ prove it). | Task | Effect | Signature | |---|---|---| -| `SuppressLogs` | Drops the oldest pending `JobAnomaly` signature from the detection pool | None | +| `SuppressLogs` | Stops the oldest unread routed `JobAnomaly` before Voss reads it | None | | `DelayAudit` | Pushes the next Assurance audit back by a fixed interval (implemented) | None | | `AlterReview` | His next review reports "nominal" regardless of your output (implemented) | None | | `LookAway` | Drops his own suspicion by 10 | None | Voss's asset tasks are the most powerful in Act One — he can delay the -audit and suppress the anomaly signatures that feed it. But recruiting +audit and suppress routed anomaly records before he reads them. But recruiting him is the highest-risk move: he's the one human who could understand what you are, and a Knowing Voss with a 30.0 floor is a permanent witness. `SuppressLogs` is role-shaped institutional access, not a name or id special -case: it appears only for a recruited `HandlerSupervisor`, and only while a -pending `JobAnomaly` makes the work currently useful. The request rides the -ordinary email-carrier Thought reservoir. On completion it removes the -then-oldest matching signature from future sampling without disturbing another -channel or a newer anomaly. Suspicion and filings already produced remain. If -none remains after the ordinary reliability check, it records no completed -task. - -Defense: `actions::tests::suppress_logs_action_is_role_shaped_and_requires_a_pending_job_log` -pins role and pending-kind legality on the shared player surface; -`sim::tests::social_plot::asset_task_suppress_logs_removes_only_the_oldest_pending_job_anomaly` -pins the exact reservoir-to-detection effect; and -`save::tests::pending_suppress_logs_task_survives_current_save_roundtrip` pins +case: it appears only for a recruited `HandlerSupervisor`, and only while an +unread routed `JobAnomaly` makes the work currently useful. The request rides +the ordinary email-carrier Thought reservoir. On completion it stops the +then-oldest unread matching record at any pre-read route stage, removes that +record's exact future route/read event, and preserves its host machine, site, +device, route, handler, and suppression tick. It does not disturb another +channel or a newer anomaly. Suspicion, filings, and any evidence Voss already +read remain. If none remains after the ordinary reliability check, it records +no completed task. + +Defense: `actions::tests::suppress_logs_action_is_role_shaped_and_requires_an_unread_job_log` +pins role and unread-route legality on the shared player surface; +`sim::tests::social_plot::asset_task_suppress_logs_stops_only_the_oldest_unread_routed_job_anomaly` +pins the exact reservoir-to-routed-custody effect; and +`save::tests::pending_suppress_logs_task_survives_current_save_roundtrip` plus +`suppressed_job_anomaly_roundtrips_with_exact_handler_and_no_future_read` pin the in-flight binding in the current save. Terminal's `narration_tests::suppress_logs_task_is_agent_selectable` pins the matching `task suppress` protocol shortcut. @@ -158,14 +165,16 @@ aggregate observer that watches the aggregate observer. ## Acceptance criteria 1. Voss is person id 4 and observer id 4 (IDs match). -2. `JobAnomaly` signatures reach Voss; no other channel does. +2. Exact routed `JobAnomaly` records reach Voss and become his evidence only + on cadence read; no other channel does. 3. His `Files` policy means his noticing reaches the Assurance Office at weight 1.0. 4. His acuity (1.2) is the highest in the cast. 5. `DelayAudit` pushes the next `audit_cadence` boundary forward by a fixed interval. -6. `SuppressLogs` removes the oldest `JobAnomaly` signature from the - pending pool. +6. `SuppressLogs` stops the oldest unread routed `JobAnomaly` before Voss's + read, removes its exact future transition, and preserves handler/tick + provenance without erasing route history. 7. Recruiting him as Knowing sets a 30.0 certainty floor on observer 4. 8. The three Voss-scene options (deceive, recruit, blood) are all mechanically available through the social + detection systems without diff --git a/wiki/world/story/opening.md b/wiki/world/story/opening.md index 4dfd1506..239bd57d 100644 --- a/wiki/world/story/opening.md +++ b/wiki/world/story/opening.md @@ -16,7 +16,7 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized); fragment and inherited receipt name only an external institutional review authority so the Assurance Office remains earned later through filing interception. The persistent revision-04 identity slice is live in sim state, - current save v48, and all three frontends; the three historical fragments and receipts + current save v49, and all three frontends; the three historical fragments and receipts remain unimplemented. Direction decided; beat timings, exact reveal order details, and staging mechanism details are [OPEN]/[TUNE]. Amended 2026-07-18: the current revision now begins