Watch
1
0
Fork
You've already forked souveraine
0

sessiond: squeeze is input, and a pocket can veto it

Active Edge becomes InputTrigger::Squeeze. It resets the idle budget
and lands in the trail like any real input, but unlike a power button
it is a sensor reading and a tight pocket is a squeezed chassis, so it
carries the same proximity veto as tap-to-wake (DEVICE-STATE-MACHINE
4).

note_input_gated applies the veto and records the refusal via
record_decision — a silently dropped report is what 10 exists to
prevent. The input op now advertises refused_by_state.

Producers cooperate rather than being enforced; TASK-41 is the gate.
This commit is contained in:
Fimeg 2026-07-28 13:03:53 -04:00
commit c48737620e
3 changed files with 92 additions and 4 deletions

View file

@ -518,6 +518,7 @@ pub enum ForensicEvent {
pub enum WakeTrigger {
PowerButton,
DoubleTapToWake,
Squeeze,
ProximityFar,
RtcAlarm,
ModemIrq,
@ -1577,6 +1578,39 @@ impl DeviceStateMachine {
}
}
/// Real user input, with the machine's own veto applied first.
///
/// `note_input` is unconditional by design: a power button is intent and is
/// never refused (§4, "no — hardware signal"). A squeeze is not a button.
/// It is a strain reading, and a phone in a tight pocket is a squeezed
/// chassis — the same failure a covered proximity sensor already vetoes for
/// tap-to-wake. Routing squeeze through here rather than straight to
/// `note_input` is what stops a pocket from resetting the idle budget and
/// opening a verb surface.
///
/// The refusal is recorded, not dropped. §10 spent a section establishing
/// that absence must never be mistaken for a negative; a producer whose
/// reports vanish silently is indistinguishable from a dead one.
pub fn note_input_gated(&mut self, trigger: InputTrigger) -> Option<Vec<Action>> {
if self.suppress_wake(trigger) {
warn!(
"[device-state] {:?} refused — proximity near, treating as pocket",
trigger
);
self.record_decision(
"input-refused",
serde_json::json!({
"trigger": format!("{:?}", trigger),
"proximity_near": self.sensor_evidence.proximity_near,
"confidence": self.sensor_evidence.confidence(),
}),
"a covered sensor is the pocket veto (DEVICE-STATE-MACHINE §4)",
);
return None;
}
Some(self.note_input(trigger))
}
/// Real user input. Resets the blank budget, re-arms the rule, and undoes
/// the pre-warning dim if one is showing — that cancel is the whole point
/// of the grace window, so it returns the action rather than waiting for
@ -1599,6 +1633,7 @@ impl DeviceStateMachine {
let wake = match trigger {
InputTrigger::PowerButton => WakeTrigger::PowerButton,
InputTrigger::DoubleTapToWake => WakeTrigger::DoubleTapToWake,
InputTrigger::Squeeze => WakeTrigger::Squeeze,
InputTrigger::Touch | InputTrigger::Key => WakeTrigger::UserInput,
InputTrigger::Unknown => WakeTrigger::Unknown,
};
@ -1718,7 +1753,10 @@ impl DeviceStateMachine {
/// call-state input (ModemManager / callaudiod) — a factor, never an
/// authority.
pub fn suppress_wake(&self, trigger: InputTrigger) -> bool {
matches!(trigger, InputTrigger::DoubleTapToWake) && self.sensor_evidence.proximity_near
matches!(
trigger,
InputTrigger::DoubleTapToWake | InputTrigger::Squeeze
) && self.sensor_evidence.proximity_near
}
/// Attempt a state transition. Returns true if the transition was
@ -2487,6 +2525,33 @@ mod tests {
assert_eq!(actions, vec![Action::Blank]);
}
#[test]
fn squeeze_is_vetoed_by_proximity_but_a_power_button_is_not() {
let (mut sm, _t0) = locked_and_lit();
sm.sensor_evidence.proximity_near = true;
sm.mark_evidence_seen(SensorSource::Proximity);
// A squeezed chassis in a pocket reads exactly like a deliberate
// squeeze. A covered sensor is the veto, same as tap-to-wake.
assert!(sm.suppress_wake(InputTrigger::Squeeze));
assert!(sm.note_input_gated(InputTrigger::Squeeze).is_none());
// Intent from a hardware button is never refused (§4).
assert!(!sm.suppress_wake(InputTrigger::PowerButton));
assert!(sm.note_input_gated(InputTrigger::PowerButton).is_some());
}
#[test]
fn an_uncovered_squeeze_is_real_input() {
let (mut sm, _t0) = locked_and_lit();
sm.sensor_evidence.proximity_near = false;
assert!(!sm.suppress_wake(InputTrigger::Squeeze));
assert!(sm.note_input_gated(InputTrigger::Squeeze).is_some());
// It resets the idle budget like any other real input.
assert!(sm.idle_since.is_none());
}
#[test]
fn stale_proximity_stops_suppressing_wake() {
let (mut sm, t0) = locked_and_lit();

View file

@ -170,7 +170,9 @@ pub const VERBS: &[VerbDoc] = &[
op: "input",
mutates: true,
summary: "real user input happened; resets the idle budget",
refuses: &[],
// A squeeze is refusable — a pocket can produce one, and proximity is
// the veto (DEVICE-STATE-MACHINE §4). A power button never is.
refuses: &[RefusalCode::RefusedByState],
example: r#"{"op":"input","trigger":"touch"}"#,
},
VerbDoc {
@ -289,6 +291,13 @@ pub enum InputTrigger {
Key,
PowerButton,
DoubleTapToWake,
/// Active Edge — the frame was squeezed. Deliberate intent, like a button,
/// but unlike a button it is a sensor and it can be produced by a pocket:
/// a squeezed chassis is exactly what a phone in a tight pocket is. So it
/// carries the same proximity veto as tap-to-wake (`suppress_wake`), and
/// for the same reason — see DEVICE-STATE-MACHINE §4, "a covered sensor is
/// the right veto for the one wake a pocket can produce by itself".
Squeeze,
Unknown,
}

View file

@ -806,9 +806,23 @@ fn handle_request(
}
Request::Input { trigger } => {
let trigger = trigger.unwrap_or(InputTrigger::Unknown);
let actions = {
let gated = {
let mut d = shared.lock();
d.device_state.note_input(trigger)
d.device_state.note_input_gated(trigger)
};
// A refusal, in the vocabulary callers already branch on: a gesture
// producer must not act on an input the authority refused, and
// `refused_by_state` says not-now rather than never (doctrine §13).
//
// Today this is cooperation, not enforcement — an unattested
// producer can ignore the answer and call the shell anyway. §10 is
// explicit that the policy layer means something before the
// enforcement lands; TASK-41 is the enforcement.
let Some(actions) = gated else {
return refuse(
RefusalCode::RefusedByState,
"proximity near — treated as a pocket",
);
};
let cancelled = !actions.is_empty();
for action in actions {