Files
blocker/core/src/never_block.rs
T
SellPL 585fd30440 feat(core): add workspace skeleton and the pure decision engine
Establishes the cargo workspace and the shared `blocker-core` crate. The
engine is deliberately written before any platform code, because CLAUDE.md
section 2 makes it the single source of decision truth and M1 acceptance
requires it to be pure.

**Workspace** — five crates plus a test crate, zero external dependencies.
Section 3 requires every crate to justify its binary-size cost, so M0 adds
none. [profile.release] is workspace-only (cargo rejects it in members) and
carries the section 5 settings that make the 5 MB stripped budget reachable.

**Purity contract** — `evaluate(&EngineInput) -> Decision` reads no clock, no
disk and no OS API; everything arrives in the input. `tests/purity.rs`
enforces this mechanically by asserting blocker-core has zero dependencies, so
a stray I/O reach-in fails the build rather than silently making the golden
tables useless as a parity contract.

Decisions worth calling out:

- **Monotonic time is a separate type from wall time.** Section 1.5.3 requires
  a system date change never to shorten a lock. Keeping them apart makes that
  structural rather than a rule to remember.
- **`start > end` means overnight.** No `crosses_midnight` flag: the comparison
  already encodes it and a second field is a second way to be wrong. The
  weekday for a window that started yesterday evening is yesterday's, which
  `overnight_window_survives_past_midnight` pins down.
- **`next_wakeup` is armed over every non-paused schedule, not only active
  ones.** Filtering to active conditions first would let the daemon sleep
  straight through the moment a schedule is due to start.
- **Allowlist needs the installed-app inventory.** `EngineInput` carries
  `installed_apps`; an empty slice fails open rather than guessing.
- **Wall-clock correction extends, never shortens, and clamps to the 24h cap.**
  Extending unbounded would satisfy anti-tamper and violate section 1.5.4.
- **`cast_possible_truncation` is denied workspace-wide.** Truncating a
  millisecond counter would corrupt a lock timer.

Verified: 22 tests pass, `cargo fmt --check` clean, `cargo clippy -D warnings`
clean, stripped release daemon 0.279 MB against the 5 MB budget.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 15:02:20 +03:00

66 lines
2.5 KiB
Rust

//! The never-block list (CLAUDE.md section 1.5.4).
//!
//! These targets are hardcoded, non-negotiable, and cannot be removed by any
//! user setting, by any schedule, or by any lock level. Blocking the dialler or
//! emergency services would make the device unusable, and section 1.5.4 places
//! these guardrails above every other feature in the project.
//!
//! The list is a `const`, not a setting, and [`is_never_blocked`] is consulted
//! inside the engine's target loop *after* schedule evaluation. That ordering is
//! deliberate: a schedule that names a protected target still evaluates and
//! still blocks its other targets, but the protected one can never enter the
//! blocked set. There is no flag that reverses it.
/// Android: the dialler and the emergency path.
///
/// Section 1.5.4 names phone/dialer, emergency calls and SOS, and emergency
/// alerts. Matching is by substring so a variant package name cannot slip past.
#[cfg(target_os = "android")]
pub const NEVER_BLOCK: &[&str] = &[
"com.android.dialer",
"com.android.server.telecom",
"com.android.emergency",
"com.android.phone",
"com.android.server.emergency",
"com.android.cellbroadcastreceiver",
"com.android.systemui", // holds the emergency call UI
];
/// Desktop: the OS shell and the settings basics needed to keep the machine
/// usable. A user who cannot reach their file manager or display settings has
/// lost the machine, not been disciplined.
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub const NEVER_BLOCK: &[&str] = &[
// Session shell / compositor.
"plasmashell",
"plasmawindowmanager",
"kwin_wayland",
"kwin_x11",
"gnome-shell",
"xfce4-session",
"explorer.exe",
// Settings and the ability to undo things.
"systemsettings",
"systemsettings5",
"systemsettings6",
"gnome-control-center",
"ksysguard",
"plasma-systemmonitor",
"dolphin",
"org.kde.plasma.systemsettings",
// Terminal is deliberately NOT here: it can undo anything. Section 1.5.4
// names the shell and Settings basics, not the terminal.
];
#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "windows")))]
pub const NEVER_BLOCK: &[&str] = &[];
/// True when `target` may never be blocked.
///
/// Substring matching, case-insensitive, so `com.android.dialer` and
/// `com.android.dialer.standalone` are both covered.
pub fn is_never_blocked(target: &str) -> bool {
let needle = target.to_lowercase();
NEVER_BLOCK.iter().any(|protected| needle.contains(&protected.to_lowercase()))
}