ci.yml calls `scripts/ci.sh` rather than restating the checks: a workflow that
reimplements them becomes a second definition of "done", and the two drift.
Android runs as a separate job. It fails when the SDK is absent while ci.sh
skips it — a developer should not be blocked by a missing toolchain, but a
silently skipped Android build in CI means regressions ship unnoticed. Every
action is pinned to an exact version.
LICENSE.md records that no licence is chosen yet, and why it is not a formality:
the choice is upstream of the UI toolkit decision, since Slint is GPL-3.0.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The documentation set CLAUDE.md sections 2, 6, 8 and 10.1 call for, written as
records of decisions actually made rather than as a description of intent.
**DECISIONS.md** — 17 ADRs. Two are deliberately left open and say so: ADR-6
(the desktop UI toolkit, which section 3 requires be decided on measurement and
which needs the owner's call, partly because Slint is GPL-3.0) and ADR-17 (the
licence, which is a legal commitment nobody should make on the owner's behalf).
Where CLAUDE.md gives a reason for a decision, that reason is recorded; where it
does not, the file says the reason is unrecorded rather than inventing one.
**LIMITATIONS.md** — all eight section 6 limits plus the section 1.5.4 safety
guarantees, stated as facts about the operating systems. The words
"unbypassable", "tamper-proof", "guaranteed" and "secure" appear nowhere, per
sections 6.1 and 6.7. Ends with a separate list of shortfalls in the *current
code* — storage has no I/O, the golden tables cannot yet be parsed, LockState
always returns Normal — so a known gap in this implementation is never mistaken
for a platform limit.
**ARCHITECTURE.md** — the layering, the purity contract and its mechanical
enforcement, why `next_wakeup` must be armed over inactive schedules too, the
overnight weekday-of-start rule, and the end-to-end flow for one block event.
**PERF.md** — targets and results as separate tables, so an aspiration is never
read as a measurement. The one real number recorded (0.279 MB stripped daemon) is
labelled as nearly meaningless, since the daemon is two print statements. Lists
the common causes of idle-wakeup regressions.
**PERMISSIONS.md** — per platform, what each permission is for and what degrades
without it. Flags the two invasive ones (Android accessibility, extension host
permissions) rather than burying them, and records that unprivileged eBPF is
*assumed* unavailable here because the sysctls were never read.
**TESTING.md** — the golden-table contract, the add-the-case-first rule, and why
clock tampering needs code fixtures instead of data.
Co-Authored-By: Claude Code <noreply@anthropic.com>
`scripts/ci.sh` is the source of truth for what must pass before a PR is
mergeable. CLAUDE.md section 10.4 requires the Gitea workflow to run the same
checks; the workflow calls this script rather than reimplementing the steps, so
the two cannot drift apart.
Runs: cargo fmt --check, clippy -D warnings, cargo test, the section 5 size
gate, Android lint/assembleRelease, and a gitleaks scan.
Verified behaviour, rather than assumed:
- **Failures really fail.** Injected a syntax error into `core/src/lib.rs` and
confirmed `ci.sh --keep-going` exits 1 and names all four dependent checks;
then restored the file and confirmed a clean exit 0. `--keep-going` captures
failures per step in a subshell instead of letting `set -e` abort the run, so
the summary records what already passed.
- **Skips are never passes.** Android and gitleaks both skip on this machine
(no SDK, no gitleaks), and each SKIP names what was skipped and why, with the
summary listing them separately from the passes. A skip that reads as a green
tick is how CI quietly stops testing anything.
- **Runs from any CWD.** `cargo` resolves a workspace from the CWD, not from
the script's location, so `ci.sh` cds to the repo root. Verified by running it
from `/tmp`.
- **`cargo` is on PATH.** rustup adds `~/.cargo/bin` via `~/.bashrc`, which a
non-interactive shell does not read — so a script invoked from CI or by
absolute path failed with "command not found" until `lib.sh` prepends it.
- **Size gate resolves absolute paths.** `CARGO_TARGET_DIR` is absolute by
default, so joining it onto the repo root produced a doubled path; the default
is now only joined when relative.
Build artifacts go to `$HOME/.blocker-target`, outside the repo, so `target/`
never appears in `git status` and can be deleted in one command.
Co-Authored-By: Claude Code <noreply@anthropic.com>
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>
Bootstrap the repository with the shared configuration every other piece
depends on:
- .gitignore: strict. Keeps build output, cargo caches, Android keystores,
signing keys, .env files, local SQLite databases, IDE state and any
credential-shaped filename out of history. CLAUDE.md 10.1 requires these
never enter the repo.
- .editorconfig, rustfmt.toml, clippy.toml: formatting and lint policy.
clippy.toml is written in the lints-file form so lints are not duplicated
into every crate manifest.
CLAUDE.md is committed verbatim; it is the project spec and the parity
contract for every platform layer.
Co-Authored-By: Claude Code <noreply@anthropic.com>