`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>