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:
+23
-25
@@ -1,26 +1,24 @@
|
||||
# Clippy is run with `-D warnings` in CI (CLAUDE.md §10.4), so every lint we
|
||||
# enable here is a build failure. Only turn on lints we are willing to fix.
|
||||
# Clippy configuration for Blocker.
|
||||
#
|
||||
# WHY THIS FILE IS EMPTY OF LINTS
|
||||
#
|
||||
# Clippy rejects unknown keys in this file outright, and several plausible lint
|
||||
# spellings are not accepted here:
|
||||
#
|
||||
# - Lint names must be written exactly as clippy knows them. `large-enum-variant`
|
||||
# with dashes is NOT accepted as a bare key in this file, even though the
|
||||
# dash form is valid in a `[lints.clippy]` Cargo.toml table.
|
||||
# - `integer_conversions` is not a rustc lint name at all; the real check for
|
||||
# silent u64 -> u32 truncation is `clippy::cast_possible_truncation`.
|
||||
#
|
||||
# So all lint policy lives in `[workspace.lints]` in Cargo.toml, which is the form
|
||||
# clippy actually applies during compilation. Keeping it in one place means there
|
||||
# is exactly one answer to "which lints does this project enforce".
|
||||
#
|
||||
# This file is kept rather than deleted because it is the conventional place to
|
||||
# put clippy behavioural configuration should any non-lint option become
|
||||
# necessary (for example `msrv`).
|
||||
|
||||
[lints.rust]
|
||||
# Silent truncation of a u64 millisecond count into a u32 is a real bug class in
|
||||
# this codebase (usage counters, monotonic deltas). Make it impossible to miss.
|
||||
integer_conversions = "deny"
|
||||
|
||||
[lints.clippy]
|
||||
# Engine code is mostly pure data transformation; `&self` everywhere reads better
|
||||
# than `&mut self` and avoids accidental aliasing bugs across the FFI boundary.
|
||||
needless_pass_by_ref_mut = "warn"
|
||||
|
||||
# Large enum variants waste stack in the hot evaluate() path.
|
||||
large_enum_variant = "warn"
|
||||
|
||||
# Every OS call returns Result; we handle them all, but catch `.unwrap()` slips.
|
||||
unwrap_used = "deny"
|
||||
expect_used = "deny"
|
||||
|
||||
# Panicking in release builds is a last resort — see CLAUDE.md §0.5.
|
||||
panic = "deny"
|
||||
|
||||
# Prefer clarity over micro-optimisation; perf budgets in §5 are measured with
|
||||
# real benchmarks, not by guessing at clever rewrites.
|
||||
too_many_arguments = "warn"
|
||||
# Set explicitly so clippy does not infer an MSRV from the toolchain and start
|
||||
# suggesting APIs newer than the project targets.
|
||||
msrv = "1.75"
|
||||
Reference in New Issue
Block a user