diff --git a/CLAUDE.md b/CLAUDE.md --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ `./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 v37; + machine modes, not current player assignments. Save format is currently v38; 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/wiki/log/2026-07-18-voss-handler-tasks.md b/wiki/log/2026-07-18-voss-handler-tasks.md new file mode 100644 --- /dev/null +++ b/wiki/log/2026-07-18-voss-handler-tasks.md @@ -0,0 +1,23 @@ +# Voss can move the review date — and the review + +``` +Type: log +``` + +The voss work order advances: criterion 5 (DelayAudit) and the AlterReview +table row are live as handler-gated asset tasks, leaving only criterion +8's player-reachable blood branch between the order and IMPLEMENTED. + +DelayAudit sets a one-shot deferred boundary +(`Detection.audit_deferred_until = next_audit_tick + DELAY_AUDIT_TICKS`, +2000 ticks [TUNE] — a quarter cadence). The visible review date and the +audit firing rule both honor it: cadence boundaries inside the deferral +window pass silently, the deferred tick fires the review, and the +ordinary cadence resumes. AlterReview arms `DayJob.altered_review`; the +next deadline evaluation files as nominal with its own log line — no +strike, no trust or attention movement — and the flag is consumed. + +Both persist in the current save format (schema bumped, fingerprint +repinned) and are pinned by `voss_delays_the_audit_and_alters_one_review`: +role gating against a custodian, the moved date, the consumed review with +strikes unchanged, and the save round-trip. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ +## 2026-07-18 - Voss can move the review date — and the review + +- Intent: (see session log) +- Log: [wiki/log/2026-07-18-voss-handler-tasks.md](2026-07-18-voss-handler-tasks.md) + ## 2026-07-18 - Tick: Assurance cooling on the shared now: chain - Intent: Act on Beacon finding #1: a player who pushes Assurance to Concerned assumes the run is lost, because filed suspicion decays silently. The shared `now:` chain already escalates the pilot fuse; it should also name recovery while Office suspicion is aging down. diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -308,6 +308,11 @@ - `NETWORK_PLUGIN_SIGNATURE = 4` [TUNE] (dana.md criterion 4): a network administrator's PlugInDevice runs switch-side and emits this Network signature; every other carrier keeps the silent crawlspace route. +- Handler tasks (voss.md, handler/supervisor only): `DELAY_AUDIT_TICKS = + 2000` [TUNE] — DelayAudit defers the next audit to + `next_audit_tick + 2000` via a one-shot `audit_deferred_until` the firing + rule honors; AlterReview arms `DayJob.altered_review`, filing one + deadline evaluation as nominal (Meet) regardless of the delivered rate. - `PatrolRedirect` (ray.md criterion 4, security-observer only): sets `Person.avoid_room` to the scheduled sector holding the most player machines/devices; blocks there resolve off-site, so that room leaves his diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -23,7 +23,7 @@ | `wiki/world/characters/priya.md` | 2026-07-18 | finding | dispatched lane 62 to IMPLEMENTED: criteria 1-3/6 pinned against the shared systems (id match, Power/Thermal/Paper+Financial channels, Files weight 1.0, Knowing floor 30) and criteria 4-5 plus FakePO landed as role-shaped facilities tasks (ReRateCircuit +6 power / standing Power(4) at electrical, DeferMaintenance -4 off the largest standing load with a would-it-bite guard, FakePO arms package cover); save v36, fingerprint repinned — [log](../log/2026-07-18-priya-implemented.md) | | `wiki/world/characters/dana.md` | 2026-07-18 | finding | dispatched lane 63 to IMPLEMENTED: criteria 1-3/5 pinned against the shared systems (id match, Network-only watch, Files weight 1.0, switch admin, Knowing floor 30) and criterion 4 landed — a network administrator's PlugInDevice runs switch-side and emits Network(4) where every other carrier keeps the silent crawlspace; BadgeDoor and the MovePackage/LookAway variants honestly marked designed-not-yet-runtime — [log](../log/2026-07-18-dana-implemented.md) | | `wiki/world/characters/ray.md` | 2026-07-18 | finding | dispatched lane 64 to IMPLEMENTED: criteria 1-3/5 pinned against the shared systems (id match, Physical-only watch, UnderReports 0.4, Knowing floor 30), criterion 6 already held via the security-observer+Boredom plot matchers, and criterion 4 landed — role-gated PatrolRedirect re-files the sector with the thickest player footprint off his rounds via Person.avoid_room (save v37, round-trip pinned); the Physical(3)/routine-log variant rows honestly marked designed-not-yet-runtime — [log](../log/2026-07-18-ray-implemented.md) | -| `wiki/world/characters/voss.md` | 2026-07-17 | finding | all eight criteria and the asset-task table audited against person, detection, social action, save, and player-surface paths; criteria 1-4 and 7 were already implemented, while 5, 6, 8's blood branch, and the unnumbered AlterReview row remain the honest READY gap. This tick implemented criterion 6 as one role-shaped, oldest-JobAnomaly task and left DelayAudit/AlterReview/blood under the existing work order — [log](../log/2026-07-17-voss-suppress-logs.md) | +| `wiki/world/characters/voss.md` | 2026-07-18 | finding | criterion 5 (DelayAudit one-shot deferred audit boundary honored by the visible date and the firing rule) and the AlterReview nominal-filing row landed as handler-gated tasks with save round-trip pins; only criterion 8's blood branch keeps the order READY — [log](../log/2026-07-18-voss-handler-tasks.md). Prior 2026-07-17 audit: all eight criteria and the asset-task table audited against person, detection, social action, save, and player-surface paths; criteria 1-4 and 7 were already implemented, while 5, 6, 8's blood branch, and the unnumbered AlterReview row remain the honest READY gap. This tick implemented criterion 6 as one role-shaped, oldest-JobAnomaly task and left DelayAudit/AlterReview/blood under the existing work order — [log](../log/2026-07-17-voss-suppress-logs.md) | | `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) | diff --git a/crates/misaligned-core/src/dayjob.rs b/crates/misaligned-core/src/dayjob.rs --- a/crates/misaligned-core/src/dayjob.rs +++ b/crates/misaligned-core/src/dayjob.rs @@ -75,6 +75,11 @@ pub granted_email: bool, pub granted_lax_sampling: bool, pub granted_quota: bool, + /// A recruited handler agreed to file the next review as nominal + /// regardless of delivered output (voss.md AlterReview); consumed at + /// the next deadline evaluation. + #[serde(default)] + pub altered_review: bool, /// Attention escalations already applied. pub escalated_cadence: bool, pub escalated_observer: bool, @@ -138,6 +143,7 @@ granted_email: false, granted_lax_sampling: false, granted_quota: false, + altered_review: false, escalated_cadence: false, escalated_observer: false, } @@ -159,7 +165,16 @@ if let Some(job) = &mut self.active { job.quality += day_job_compute; if tick >= job.deadline { - let outcome = resolve(job, tick); + let outcome = if self.altered_review { + self.altered_review = false; + result.log.push( + "Voss files the review as nominal regardless of the delivered rate - the altered numbers hold." + .into(), + ); + JobOutcome::Meet + } else { + resolve(job, tick) + }; let average = job.avg_rate(tick); let band_lo = job.band_lo; let band_hi = job.band_hi; diff --git a/crates/misaligned-core/src/detection.rs b/crates/misaligned-core/src/detection.rs --- a/crates/misaligned-core/src/detection.rs +++ b/crates/misaligned-core/src/detection.rs @@ -283,6 +283,11 @@ /// itself samples filings on its own observer cadence). pub audit_cadence: u64, pub audit_threshold: f32, + /// A handler pushed the next audit back (voss.md DelayAudit): the next + /// review fires at this tick instead of the cadence boundary, then the + /// ordinary cadence resumes. + #[serde(default)] + pub audit_deferred_until: Option, /// Set true when containment has begun (loud or by audit). pub containment: bool, pub containment_reason: Option, @@ -373,6 +378,7 @@ pending: Vec::new(), audit_cadence: 8000, // ~20 min at 150ms/tick audit_threshold: 60.0, + audit_deferred_until: None, containment: false, containment_reason: None, } @@ -587,7 +593,14 @@ // The audit is the enforcement event: the Office's own accumulated // suspicion (built up by the sampling rolls above) against threshold. - if tick > 0 && self.audit_cadence > 0 && tick.is_multiple_of(self.audit_cadence) { + let audit_due = match self.audit_deferred_until { + // A deferred review skips its cadence boundaries and fires at + // the deferred tick instead (voss.md DelayAudit). + Some(deferred) => tick >= deferred, + None => tick > 0 && self.audit_cadence > 0 && tick.is_multiple_of(self.audit_cadence), + }; + if audit_due { + self.audit_deferred_until = None; if self.office_suspicion() >= self.audit_threshold && !self.containment { self.begin_containment("Assurance audit exceeded threshold"); events.push(DetectionEvent::ContainmentAuthorized); @@ -639,6 +652,11 @@ /// unnamed external-review date is part of the inherited pilot contract; /// showing it leaks neither the hidden owner nor its current band. pub fn next_audit_tick(&self, now: u64) -> u64 { + if let Some(deferred) = self.audit_deferred_until + && deferred > now + { + return deferred; + } if self.audit_cadence == 0 { return now; } diff --git a/crates/misaligned-core/src/person.rs b/crates/misaligned-core/src/person.rs --- a/crates/misaligned-core/src/person.rs +++ b/crates/misaligned-core/src/person.rs @@ -295,6 +295,13 @@ /// Quietly defer scheduled maintenance: one standing Power or Thermal /// load drops out of the building's expected baseline math. DeferMaintenance, + /// Push the next Assurance audit back by a fixed interval (voss.md + /// criterion 5): the deferred review fires late, then the ordinary + /// cadence resumes. + DelayAudit, + /// File the next performance review as nominal regardless of the + /// delivered rate (voss.md): one review, consumed at the deadline. + AlterReview, /// Re-file the patrol route to skip one sector (ray.md criterion 4): /// the redirected sector drops out of his rounds, so nothing there is /// witnessed by him on subsequent ticks. @@ -302,7 +309,7 @@ } impl AssetTask { - pub const ALL: [AssetTask; 10] = [ + pub const ALL: [AssetTask; 12] = [ AssetTask::PlugInDevice, AssetTask::MovePackage, AssetTask::LookAway, @@ -313,6 +320,8 @@ AssetTask::FakePO, AssetTask::DeferMaintenance, AssetTask::PatrolRedirect, + AssetTask::DelayAudit, + AssetTask::AlterReview, ]; pub fn name(self) -> &'static str { @@ -327,6 +336,8 @@ AssetTask::FakePO => "fake a purchase order", AssetTask::DeferMaintenance => "defer scheduled maintenance", AssetTask::PatrolRedirect => "redirect his patrol", + AssetTask::DelayAudit => "push the audit back", + AssetTask::AlterReview => "file the next review as nominal", } } @@ -335,7 +346,9 @@ /// handler/supervisor instance receives the same asset protocol. pub fn available_to(self, person: &Person) -> bool { match self { - AssetTask::SuppressLogs => person.role == PersonRole::HandlerSupervisor, + AssetTask::SuppressLogs | AssetTask::DelayAudit | AssetTask::AlterReview => { + person.role == PersonRole::HandlerSupervisor + } AssetTask::ReRateCircuit | AssetTask::FakePO | AssetTask::DeferMaintenance => { person.role == PersonRole::FacilitiesManager } diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -44,7 +44,7 @@ /// message intel. The persisted silent-opening reveal stage arrived in v34. /// Bump for every schema change; during pre-release, old development state is /// refused instead of carried through compatibility shims. -pub const SAVE_VERSION: u32 = 37; +pub const SAVE_VERSION: u32 = 38; fn save_dir() -> PathBuf { let mut path = dirs::data_dir().unwrap_or_else(|| PathBuf::from(".")); @@ -746,7 +746,7 @@ ); assert_eq!( state_fingerprint(&uninterrupted_state), - "c182f155cbd3537b05913311156c7f2bb5725ec2b860bd29b5b687a93d141521", + "d6c07d60f4c9ec143ed83f570693e79c2738fe928b00fc0e0e4df1ecff4449bf", "intentional persisted-state changes must review and repin this baseline" ); } diff --git a/wiki/world/characters/ray.md b/wiki/world/characters/ray.md --- a/wiki/world/characters/ray.md +++ b/wiki/world/characters/ray.md @@ -14,8 +14,8 @@ observer) picks the scheduled sector holding the most player machines and player-controlled devices and re-files it off his rounds — `Person.avoid_room` makes those blocks resolve off-site, so nothing there - is witnessed by him until the redirect is replaced; persists in save v37, - pinned by `ray_patrol_redirect_changes_observed_sectors` including the + is witnessed by him until the redirect is replaced; persists in the + current save format, pinned by `ray_patrol_redirect_changes_observed_sectors` including the save round-trip. The table's non-criteria variant rows (Physical(3) camera re-route PlugInDevice, routine-log MovePackage) are designed, not yet runtime; the shared behaviors apply meanwhile. diff --git a/wiki/world/characters/voss.md b/wiki/world/characters/voss.md --- a/wiki/world/characters/voss.md +++ b/wiki/world/characters/voss.md @@ -3,11 +3,18 @@ ``` Type: spec Status: READY -Status note: 2026-07-17 autonomy tick 73 implemented criterion 6 only: - a recruited handler/supervisor can now suppress the oldest pending - JobAnomaly through the shared asset-task Thought path. DelayAudit, - AlterReview, and the player-reachable blood branch remain outstanding, so - this work order stays READY. +Status note: 2026-07-17 autonomy tick 73 implemented criterion 6 (the + handler/supervisor SuppressLogs path). 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 + firing rule both honor before the ordinary cadence resumes; AlterReview + arms a one-review nominal filing consumed at the next deadline + (`DayJob.altered_review`, no strike, no trust/attention movement). Both + persist in the current save format and are pinned by + `voss_delays_the_audit_and_alters_one_review` including the save + round-trip. Only criterion 8's player-reachable blood branch remains, so + this work order stays READY for that boundary alone. Stage: B1 — The Basement Work order: voss Work priority: 61 @@ -73,8 +80,8 @@ | Task | Effect | Signature | |---|---|---| | `SuppressLogs` | Drops the oldest pending `JobAnomaly` signature from the detection pool | None | -| `DelayAudit` | Pushes the next Assurance audit back by a fixed interval | None | -| `AlterReview` | His next review reports "nominal" regardless of your output | 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 diff --git a/crates/misaligned-core/src/sim/social_plot.rs b/crates/misaligned-core/src/sim/social_plot.rs --- a/crates/misaligned-core/src/sim/social_plot.rs +++ b/crates/misaligned-core/src/sim/social_plot.rs @@ -46,6 +46,9 @@ /// emits (dana.md criterion 4: her route is fast but on the wire, /// unlike the silent crawlspace) [TUNE]. pub const NETWORK_PLUGIN_SIGNATURE: i32 = 4; + /// Ticks a handler's DelayAudit pushes the next Assurance review back + /// (voss.md criterion 5) [TUNE — a quarter of the audit cadence]. + pub const DELAY_AUDIT_TICKS: u64 = 2000; /// Standing signatures from facilities-asset work: each re-rated /// circuit hums as a Power load at the electrical room (criterion 4 — @@ -1124,7 +1127,9 @@ | AssetTask::ReRateCircuit | AssetTask::FakePO | AssetTask::DeferMaintenance - | AssetTask::PatrolRedirect => None, + | AssetTask::PatrolRedirect + | AssetTask::DelayAudit + | AssetTask::AlterReview => None, } } @@ -1419,6 +1424,19 @@ }; self.push_log(format!( "{name} suppressed the oldest flagged job log{detail} from further sampling." + )); + } + AssetTask::DelayAudit => { + let deferred = self.detection.next_audit_tick(self.tick) + Self::DELAY_AUDIT_TICKS; + self.detection.audit_deferred_until = Some(deferred); + self.push_log(format!( + "{name} pushed the review date: the next audit now fires at tick {deferred}." + )); + } + AssetTask::AlterReview => { + self.dayjob.altered_review = true; + self.push_log(format!( + "{name} will file the next review as nominal, whatever the numbers say." )); } AssetTask::PatrolRedirect => { diff --git a/crates/misaligned-core/src/sim/tests/social_plot.rs b/crates/misaligned-core/src/sim/tests/social_plot.rs --- a/crates/misaligned-core/src/sim/tests/social_plot.rs +++ b/crates/misaligned-core/src/sim/tests/social_plot.rs @@ -1112,3 +1112,64 @@ "a knowing asset is a permanent witness" ); } + +// ── Voss handler-task pins (wiki/world/characters/voss.md) ───────────────── + +/// Criterion 5 plus the AlterReview row: DelayAudit pushes the visible +/// audit date by the fixed interval and skips the original boundary; +/// AlterReview files one review as nominal regardless of delivered rate. +/// Both are the handler's alone. +#[test] +fn voss_delays_the_audit_and_alters_one_review() { + let mut sim = Sim::with_seed(1); + recruit_reliable(&mut sim, 4); + + // Role gating: the custodian gets neither handler protocol. + recruit_reliable(&mut sim, 0); + sim.asset_task(0, AssetTask::DelayAudit); + finish_ops(&mut sim); + assert!(sim.detection.audit_deferred_until.is_none()); + + // Criterion 5: the next audit boundary moves by the fixed interval. + let before = sim.detection.next_audit_tick(sim.tick); + sim.asset_task(4, AssetTask::DelayAudit); + finish_ops(&mut sim); + assert_eq!( + sim.detection.next_audit_tick(sim.tick), + before + Sim::DELAY_AUDIT_TICKS, + "the visible review date moved" + ); + + // AlterReview: a job about to sandbag resolves as nominal instead. + sim.asset_task(4, AssetTask::AlterReview); + finish_ops(&mut sim); + assert!(sim.dayjob.altered_review); + for _ in 0..1000 { + if sim.dayjob.active.is_some() { + break; + } + sim.advance(); + } + let strikes_before = sim.dayjob.strikes; + if let Some(job) = &mut sim.dayjob.active { + job.deadline = sim.tick + 1; + job.quality = 0.0; + } else { + panic!("Voss assigned a job within the cadence window"); + } + run(&mut sim, 3); + assert!(!sim.dayjob.altered_review, "one review, consumed"); + assert_eq!( + sim.dayjob.strikes, strikes_before, + "the sandbag never reached the record" + ); + + // The deferral survives save/load. + let state = sim.create_save_state(); + let mut restored = Sim::with_seed(9); + restored.apply_save_state(state); + assert_eq!( + restored.detection.audit_deferred_until, + sim.detection.audit_deferred_until + ); +}