From 7c7c05711afe9ecebc7f7fe9783fd3fc26b2c3b6 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Tue, 04 Aug 2026 10:23:58 +0000 Subject: [PATCH] Reject active work after pilot failure. Fail closed when a current save combines terminal pilot state with an active Voss packet, preserving the live deadline transition across load. Defense: wiki/mechanics/day-job.md makes pilot failure terminal day-job state, and current_save_rejects_active_day_job_after_pilot_failure pins that invariant at the loader boundary. ๐Ÿ‘พ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- wiki/log/2026-08-04-day-job-terminal-save-invariant.md | 20 ++++++++++++++++++++ wiki/log/DEVLOG.md | 5 +++++ wiki/mechanics/day-job.md | 8 +++++++- wiki/process/tick-ledger.md | 3 +-- crates/misaligned-core/src/save.rs | 21 +++++++++++++++++++++ 5 file(s) changed, 54 insertion(s)(+), 3 deletion(s)(-) diff --git a/wiki/log/2026-08-04-day-job-terminal-save-invariant.md b/wiki/log/2026-08-04-day-job-terminal-save-invariant.md new file mode 100644 --- /dev/null +++ b/wiki/log/2026-08-04-day-job-terminal-save-invariant.md @@ -0,0 +1,20 @@ +# 2026-08-04 โ€” Reject active work after pilot failure + +``` +Type: log +``` + +## Finding + +The live deadline path clears the active Voss packet before the fourth strike +sets `pilot_failed` and ends the run. Current-save validation did not preserve +that transition boundary: edited or corrupted v63 bytes could load both +`pilot_failed` and an active job, leaving frontends to project work after its +terminal state. + +## Repair + +The current-save gate now rejects that impossible combination. The focused +regression begins with an otherwise current `SaveState`, introduces exactly the +forbidden active terminal pairing, and pins the fail-closed error. The day-job +spec now states the persisted invariant beside its round-trip criterion. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -66,6 +66,11 @@ - Intent: (see session log) - Log: [wiki/log/2026-08-04-decision-index-generation-defense.md](2026-08-04-decision-index-generation-defense.md) +## 2026-08-04 - Reject active work after pilot failure + +- Intent: (see session log) +- Log: [wiki/log/2026-08-04-day-job-terminal-save-invariant.md](2026-08-04-day-job-terminal-save-invariant.md) + ## 2026-08-04 - Keep Bevy palette roles on one authority - Intent: (see session log) diff --git a/wiki/mechanics/day-job.md b/wiki/mechanics/day-job.md --- a/wiki/mechanics/day-job.md +++ b/wiki/mechanics/day-job.md @@ -180,7 +180,9 @@ triggering from sustained sandbagging. 4. A machine left in day-job mode keeps working hands-off at its persistent intensity; no cursor presence or declared target is required. -5. Save/load round-trips jobs, trust, attention, and the pilot clock. +5. Save/load round-trips jobs, trust, attention, and the pilot clock. A failed + pilot is terminal day-job state: no active job may coexist with + `pilot_failed` in a current save. 6. The active job is inspectable at the host rack with the same facts the panel shows plus its visible demand stack. Thermal/Power measurements carry that host location into the exact facility-meter record; JobAnomaly carries @@ -190,3 +192,7 @@ 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. + +Defense: `current_save_rejects_active_day_job_after_pilot_failure` proves that +the current-save gate rejects an active packet after the live deadline path has +cleared it and ended the pilot. 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 @@ -74,7 +74,7 @@ | `wiki/interface/context-menu.md` | 2026-07-29 | finding | shared legality, executable-only human rows, local-vs-strategic ownership, global F3 precedence, and exact person-entry paths still verify. The root nevertheless repeated `PROPOSE LINK TO โ€ฆ` once per legal target, turning one intention into topology-sized noise. The shared human menu now folds that fanout into one `PROPOSE A LINK` row and a live exact `WHERE SHOULD THIS CONNECT?` page; terminal and Bevy re-query the same page while agent mode keeps flat exact commands โ€” [log](../log/2026-07-29-link-destination-fold.md). The prior F3 repair remains pinned โ€” [log](../log/2026-07-26-bevy-global-f3-input-precedence.md). | | `wiki/mechanics/personas.md` | 2026-07-28 | finding | criterion 6 remains honestly incomplete: grants persist and revoke but still create no resource, permission, graph edge, or owner-system route. The adjacent current-save boundary trusted that public ledger without graph validation, allowing duplicate persona ids, stale id cursors, or dangling relationship/grant/expectation/action custody to reach first-match runtime lookups. Loading now validates the complete PersonaWorld id/reference/time graph, one-to-one grant/expectation pairing, serialized grant protocol, and active-grant uniqueness before exposing the state; this hardens existing custody without promoting criterion 6 or 6b โ€” [save audit](../log/2026-07-28-persona-save-custody-audit.md). Prior [grant-topology](../log/2026-07-18-persona-grant-topology-audit.md) and [observer-integrity](../log/2026-07-18-persona-observer-integrity.md) findings stand. | | `wiki/interface/views.md` + representation docs | 2026-07-26 | finding | criterion 1's frontend-only representation and state-parity contract still stands, but Bevy's context-menu precedence made F3 unavailable while that modal attention state was open even though terminal preserved it. F3 now routes once before every post-opening modal branch, keeps the exact menu/Operations/held-choice state, and changes no simulation or save bytes โ€” [log](../log/2026-07-26-bevy-global-f3-input-precedence.md) | -| `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/day-job.md` | 2026-08-04 | finding | current-save validation accepted `pilot_failed` with an active job even though the live deadline path clears the packet before setting terminal pilot state. The loader now rejects that impossible active terminal combination, preventing frontends from projecting work after the pilot has ended โ€” [log](../log/2026-08-04-day-job-terminal-save-invariant.md). Prior routed-evidence, band-ramp, cadence, origin-lean, last-chance, and trust-unlock findings remain valid โ€” [prior](../log/2026-07-22-job-anomaly-routed-evidence.md). | | `wiki/mechanics/core.md` | 2026-07-29 | finding | the queued liveness follow-up is closed: host loss now filters synchronized targets to currently online machines before ranking freshness, so a dark staged target cannot become host or produce repeated failover receipts; no synchronized live target ends the run โ€” [liveness log](../log/2026-07-29-live-b1-fallback-selection.md). The prior B1/B2 boundary remains exact: the free-cadence `last_sync` is only a check-in, current-state host failover restores no snapshot, and completed-image rollback remains deferred โ€” [boundary log](../log/2026-07-29-b1-host-failover-boundary.md). There is still no Thought-backed project, partial progress, heat, saved project state, sidebar ETA/cost/target, source-liveness resolver, or capability-shaped eligibility โ€” [project-status log](../log/2026-07-28-core-criteria-status-audit.md). | | `wiki/mechanics/cursor.md` | 2026-07-28 | finding | re-audit: cursor state remains frontend-only; sight, hearing, fog precedence, remembered snapshots, blueprint opacity, telemetry, provenance, identity gates, and cold signal pings still match the implemented contract. Criterion 2 explicitly required a before/after simulation-state-hash proof for arbitrary cursor movement, but the only existing hash tests began after cursor placement and proved F3 view immutability instead. Terminal and Bevy now sweep every map coordinate through their production cursor helpers, pin edge clamping, and require unchanged simulation save-state hashes โ€” [log](../log/2026-07-28-cursor-immutability-defense.md) | | `wiki/mechanics/intel.md` | 2026-08-04 | finding | the queued current-save follow-up is closed: historical v24/v30 transitions now remain statement-local retired history, criterion 7 states exact-current round-trip truth, and the save-claim gate rejects unversioned live migration prose plus the numbered `collapses` synonym โ€” [save-boundary log](../log/2026-08-04-intel-current-save-boundary.md). The full audit's player-facing repair also stands: core emits criterion 12's exact conditional **OLDER INFORMATION WILL BE LOST IF MORE ARRIVES**, with a full-buffer projection regression for all consumers โ€” [bounded-loss log](../log/2026-08-04-intel-bounded-loss-language.md). Prior Storage B, recursive custody, magnitude, and consequence-first findings stand. | @@ -111,5 +111,4 @@ question, bug, insecurity โ€” plus `gate` for a checker owed to the recurrence-promotes-to-the-gate rule. -- 2026-08-04 ยท bug ยท `wiki/mechanics/day-job.md` + current save validation ยท a current save can combine `pilot_failed` with an active day job even though live resolution clears the job before terminal failure, leaving frontends to project an impossible active terminal state. - 2026-08-04 ยท bug ยท `wiki/mechanics/compute.md` + current save validation ยท a fleet machine can load in infrastructure-only `Relay` mode because current-save validation never checks persisted WorkGrid modes even though every live player executor authors only WORK / THINK / LIE. 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 @@ -618,6 +618,9 @@ state.detection.validate_topology()?; validate_persona_custody(&state)?; validate_messages(&state)?; + if state.dayjob.pilot_failed && state.dayjob.active.is_some() { + return Err("current-version save keeps an active day job after pilot failure".into()); + } if state .last_facility_meter_levels .iter() @@ -3851,6 +3854,24 @@ assert!( err.contains("resolved without an outcome"), "resolution without outcome fails closed: {err}" + ); + } + + #[test] + fn current_save_rejects_active_day_job_after_pilot_failure() { + let mut state = SaveState::from_sim(&Sim::with_seed(0xDA7A_10B0)); + state.dayjob.pilot_failed = true; + state.dayjob.active = Some(crate::dayjob::Job { + started: state.sim_tick, + deadline: state.sim_tick + 100, + band_lo: 2.0, + band_hi: 8.0, + quality: 0.0, + }); + + assert_eq!( + validate_current_save(state).unwrap_err(), + "current-version save keeps an active day job after pilot failure" ); } -- tangled.sh