From 33fc9c671d5ab300f9e30b652dd2710eec9f76f6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 22 Sep 2026 08:05:22 +0200 Subject: [PATCH] Cover the dependency loop's retired-subject freeze (PA.f37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first PA.f37 freeze test only retires the farmer, who holds a routine and an obligation but no dependency, so it never exercised `derive_motion`'s dependency-loop retired check. Mutation testing against commit bc1852d confirmed the gap: disabling that loop's `is_retired` skip passed the full suite. This adds the other half — retiring the reeve, who holds the goal and the Bind dependency, while the farmer stays live — which kills that mutation. Co-Authored-By: Claude Sonnet 5 --- crates/ghostlight/src/lib.rs | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/crates/ghostlight/src/lib.rs b/crates/ghostlight/src/lib.rs index 4b5f316..9a4a741 100644 --- a/crates/ghostlight/src/lib.rs +++ b/crates/ghostlight/src/lib.rs @@ -18382,6 +18382,66 @@ mod clock_tests { ); } + /// PA.f37, the other half: retiring the subject that holds the goal and + /// the dependency freezes those instead, while the still-live farmer's + /// routine and obligation keep moving. Soul's probe retired a subject + /// whose routine rolled and whose goal and dependency kept pressing; this + /// pins the dependency loop in `derive_motion` the first test above never + /// exercises, since the farmer it retires holds no dependency. + #[test] + fn after_retirement_a_tick_moves_none_of_the_retired_subjects_goal_or_dependency() { + let directory = tempfile::tempdir().unwrap(); + let (mut kernel, clockwork, _) = clock_kernel(directory.path(), "PAf37FrozenDep"); + let snapshot = kernel.snapshot().unwrap(); + submit_owner( + &mut kernel, + &snapshot, + operations(vec![ComponentOp::CloseRoute { + route: Ref::Existing(clockwork.gate), + }]), + ); + owner_retires(&mut kernel, clockwork.reeve).expect("retire the reeve"); + let routine_before = due(&kernel, clockwork.farmer, clockwork.routine); + let dependency_source = PressureSource::Dependency(DependencyTarget::Route(clockwork.gate)); + + tick(&mut kernel, ROUTINE_DUE as u32 + LATE_DUE as u32).expect("the tick commits"); + + assert_eq!( + pressure( + &kernel, + clockwork.reeve, + PressureSource::Commitment { + subject: clockwork.reeve, + key: clockwork.goal, + } + ), + 0, + "a retired subject's goal still pressed" + ); + assert_eq!( + pressure(&kernel, clockwork.reeve, dependency_source), + 0, + "a retired subject's dependency still pressed" + ); + assert_ne!( + due(&kernel, clockwork.farmer, clockwork.routine), + routine_before, + "the live subject's routine did not roll" + ); + assert_eq!( + pressure( + &kernel, + clockwork.farmer, + PressureSource::Commitment { + subject: clockwork.farmer, + key: clockwork.obligation, + } + ), + 1, + "the live subject's obligation did not press" + ); + } + /// PA.f37: the freeze survives a full reopen-and-replay, the same way /// every other retirement effect in this batch does. #[test]