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>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
//! Windows platform layer.
|
||||
//!
|
||||
//! Observes the OS and enforces decisions. Contains **no decision logic**
|
||||
//! (CLAUDE.md section 2).
|
||||
//!
|
||||
//! Nothing is implemented here in M0 — this crate cannot be compiled or tested
|
||||
//! on the development machine, so writing enforcement code now would mean
|
||||
//! shipping unverified code (section 0 rule 2: never fake a feature). The
|
||||
//! design is recorded so the Windows milestone has a fixed target.
|
||||
//!
|
||||
//! # Planned shape (CLAUDE.md section 4.2)
|
||||
//!
|
||||
//! - **Foreground detection:** `SetWinEventHook(EVENT_SYSTEM_FOREGROUND)` —
|
||||
//! event-driven, no polling. The hook resolves `HWND` to a PID, then to the
|
||||
//! exe path, with package identity for UWP apps.
|
||||
//! - **Block action:** show a topmost block window, then minimise or close the
|
||||
//! offending window. Termination only as a configurable escalation.
|
||||
//! - **Runs as:** a per-user background daemon at login, plus a Windows Service
|
||||
//! watchdog that restarts it if it dies.
|
||||
//! - **Wi-Fi:** the WLAN API notification callback.
|
||||
//! - **Website blocking:** the weak point. The decision is a Firefox extension
|
||||
//! over native messaging (section 6.2), which is what makes keyword and
|
||||
//! path-level blocking possible — DNS and hosts-file approaches cannot do
|
||||
//! either, and are defeated by DoH.
|
||||
|
||||
/// A window that has taken focus, resolved to something a schedule can match.
|
||||
///
|
||||
/// Mirrors `blocker_platform_linux::ForegroundWindow` deliberately: the two
|
||||
/// platform crates implement the same shape so a schedule means the same thing
|
||||
/// on both.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ForegroundWindow {
|
||||
/// `HWND` as a string, so this type is not tied to the `windows` crate.
|
||||
pub handle: String,
|
||||
pub pid: Option<u32>,
|
||||
pub exe_path: Option<String>,
|
||||
pub title: Option<String>,
|
||||
/// UWP package family name, where the app is packaged.
|
||||
pub package_identity: Option<String>,
|
||||
}
|
||||
|
||||
/// Events a watcher can raise. The daemon blocks on this channel; no polling.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FocusEvent {
|
||||
Activated(ForegroundWindow),
|
||||
Cleared,
|
||||
}
|
||||
|
||||
/// Source of focus events, backed by `SetWinEventHook(EVENT_SYSTEM_FOREGROUND)`.
|
||||
pub trait ForegroundWatcher {
|
||||
fn subscribe(&mut self, sink: std::sync::mpsc::Sender<FocusEvent>);
|
||||
fn name(&self) -> &'static str;
|
||||
fn selection_reason(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Enforcement actions, escalating and configurable (section 4.2).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Enforcement {
|
||||
BlockScreen,
|
||||
Minimise,
|
||||
Close,
|
||||
/// Terminate the process. Destructive, last resort.
|
||||
Terminate,
|
||||
}
|
||||
Reference in New Issue
Block a user