You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SafetyOverride.deactivate() returns early when the grant is already inactive, and the early return is above its SEL audit. So when an operator switches back to normal mode on a grant that had already lapsed, the action is recorded nowhere: no safety_override:deactivate event is emitted.
Lazy expiry makes this the common case rather than an edge one: it clears _active while leaving the rest of the grant's state in place, so any deactivate arriving after the TTL has elapsed takes the early return.
Why
The audit stream is the only durable record of who changed the auto-approval posture and when. An auditor reconstructing "was auto-approval on at 03:00, and who turned it off?" sees the activate event and then nothing — the operator's explicit decision is invisible, and it is indistinguishable from the grant simply having been left to lapse.
That distinction is not academic. It was load-bearing in #2443: a guard there assumed deactivate() always zeroes _expires_at, which the early return makes false, and the result was that an automatic lease could re-enable auto-approval an operator had just turned off. The guard is fixed (it now requires the grant to be live), but the underlying observation stands — this method's early return makes an operator action leave no trace at all.
Additional Context
src/kiro_crew/safety_override.py — deactivate(). Compare deactivate_scope(), which has the same shape but only logs when something was actually removed; the difference there is that no state change happened, whereas here an operator DID make a decision.
Same family as SafetyOverride.renew() extends a grant before auditing it, and swallows audit failure #2453 (renew() commits before auditing and swallows audit failure). Both are audit-completeness gaps on the same object; worth deciding together whether the object's contract is "log every decision" or "log every state change", because the two disagree exactly on this case.
Suggested shape: emit the event whenever deactivate() is called with a grant that exists in any form (including lapsed), with the pre-call state in resources so a no-change deactivate is distinguishable from one that actually revoked a live grant. Genuinely absent grants (never activated) can stay silent.
Test shape: activate, force lapse, call is_active() to trip lazy expiry, then deactivate() and assert a safety_override:deactivate SEL event was emitted. Inject the failure at the SEL sink rather than at _log_sel so the assertion cannot pass vacuously.
What
SafetyOverride.deactivate()returns early when the grant is already inactive, and the early return is above its SEL audit. So when an operator switches back to normal mode on a grant that had already lapsed, the action is recorded nowhere: nosafety_override:deactivateevent is emitted.Lazy expiry makes this the common case rather than an edge one: it clears
_activewhile leaving the rest of the grant's state in place, so any deactivate arriving after the TTL has elapsed takes the early return.Why
The audit stream is the only durable record of who changed the auto-approval posture and when. An auditor reconstructing "was auto-approval on at 03:00, and who turned it off?" sees the
activateevent and then nothing — the operator's explicit decision is invisible, and it is indistinguishable from the grant simply having been left to lapse.That distinction is not academic. It was load-bearing in #2443: a guard there assumed
deactivate()always zeroes_expires_at, which the early return makes false, and the result was that an automatic lease could re-enable auto-approval an operator had just turned off. The guard is fixed (it now requires the grant to be live), but the underlying observation stands — this method's early return makes an operator action leave no trace at all.Additional Context
src/kiro_crew/safety_override.py—deactivate(). Comparedeactivate_scope(), which has the same shape but only logs when something was actually removed; the difference there is that no state change happened, whereas here an operator DID make a decision.renew()commits before auditing and swallows audit failure). Both are audit-completeness gaps on the same object; worth deciding together whether the object's contract is "log every decision" or "log every state change", because the two disagree exactly on this case.deactivate()is called with a grant that exists in any form (including lapsed), with the pre-call state inresourcesso a no-change deactivate is distinguishable from one that actually revoked a live grant. Genuinely absent grants (never activated) can stay silent.is_active()to trip lazy expiry, thendeactivate()and assert asafety_override:deactivateSEL event was emitted. Inject the failure at the SEL sink rather than at_log_selso the assertion cannot pass vacuously.