92afdc3bf6
Updates the ADR log, architecture, limitations, perf table and README to match what now exists, rather than what was expected. **The perf number was wrong and is now honest.** The M0 figure of 0.279 MB excluded SQLite entirely — the placeholder binary never referenced the crate, so dead-code elimination dropped it. With the daemon actually opening a database it is **1.364 MB**, of which bundled SQLite is ~1.1 MB. That is 1.1 MB of the 5 MB budget spoken for, leaving 3.8 MB for D-Bus, X11 and a UI toolkit. This is the first number that makes the section 5 budget feel like a budget rather than a formality, and it turns ADR-4's bundled-vs-system-linked choice into a real trade-off with a measured price on both sides. - ADR-4 marked accepted and implemented, with that trade-off left open. - ARCHITECTURE.md documents why `blocker-storage` is a sibling crate and not a submodule: the dependency direction is what keeps the zero-dependency purity proof automatic rather than a grep someone can route around. - LIMITATIONS.md drops the stale "storage has no I/O" shortfall and records the bundled-vs-system question instead, plus the fact that `LockState` still always evaluates to Normal. - README status table and layout updated. Co-Authored-By: Claude Code <noreply@anthropic.com>
197 lines
8.8 KiB
Markdown
197 lines
8.8 KiB
Markdown
# Limitations
|
|
|
|
CLAUDE.md section 6 is titled "Known hard limits (tell me, don't hide them)". This
|
|
file is that promise, kept in writing.
|
|
|
|
Two rules govern the wording here. Every limitation is stated as a fact about the
|
|
operating system, not as a bug with a workaround. And no entry uses the words
|
|
"unbypassable", "tamper-proof", "guaranteed", or "secure" — section 6.1 and 6.7
|
|
require those claims not be made, and writing around them is how a document
|
|
quietly starts lying.
|
|
|
|
Nothing in this file is aspirational. Where a limitation is not yet implemented,
|
|
it says so.
|
|
|
|
---
|
|
|
|
## The safety guarantees, stated first
|
|
|
|
Before the limits, the guarantees. Section 1.5.4 makes these non-negotiable, and
|
|
they constrain what the limitations below are allowed to be.
|
|
|
|
- **Never-block list.** Phone, dialler, emergency calls and SOS, and emergency
|
|
alerts can never be blocked. On desktop, the OS shell and the Settings basics
|
|
needed to keep the machine usable. Hardcoded in
|
|
`core/src/never_block.rs`, not a setting, with no flag that reverses it. The
|
|
check happens inside the engine after schedule evaluation, so a schedule naming
|
|
a protected target blocks its other targets and cannot block the protected one.
|
|
- **Finite worst case.** The longest possible path to disabling blocking is 24
|
|
hours. No lock exists without an exit. Asserted by
|
|
`worst_case_disable_path_is_capped`.
|
|
- **Recovery is never interfered with.** Factory reset, safe mode, and OS-level
|
|
recovery are not blocked, and never will be. Section 6.7: that is correct
|
|
behaviour, not a gap.
|
|
- **Fail open.** A service crash loop or a corrupt database must never leave a
|
|
device unusable. Recovery renames the damaged file aside and starts clean
|
|
rather than failing to launch.
|
|
- **Opt-in only.** Strict and Stubborn are never enabled by default, and never by
|
|
a schedule template.
|
|
- **Always escapable.** The "How to turn this off" documentation is reachable
|
|
*without* disabling anything, including under Strict Mode.
|
|
|
|
---
|
|
|
|
## 1. Desktop Strict Mode cannot be made tamper-proof against root or admin
|
|
|
|
**Status:** fundamental. Not being worked around.
|
|
|
|
On desktop, a user with administrative rights, or root, can stop the service, edit
|
|
its files, delete it, or inspect its memory. No design prevents this. Section 6.1
|
|
requires this be documented honestly and that we not claim otherwise.
|
|
|
|
Mitigations actually implemented:
|
|
|
|
- A watchdog service that restarts the daemon if it dies.
|
|
- File ACLs, and where the filesystem supports it, an immutable flag on config.
|
|
- Restart-on-kill, so a kill is not durable.
|
|
- A tamper log, so the attempt is recorded even though it succeeds.
|
|
|
|
What none of this changes: a determined user with root removes the app. The goal
|
|
is to outlast an impulse, not to defeat a determined person.
|
|
|
|
## 2. Desktop website blocking has no single clean mechanism
|
|
|
|
**Status:** decided (ADR-7), with a residual gap.
|
|
|
|
Shipping a Firefox extension over native messaging. That makes keyword and
|
|
path-level blocking work, and unlike DNS or a hosts file it is not defeated by
|
|
DoH, because it inspects the URL in the browser rather than the network path.
|
|
|
|
The costs are real: the user must install an extension and grant permissions that
|
|
look alarming, and Firefox-only for now.
|
|
|
|
**The residual gap:** an extension covers browsers it is installed in. A
|
|
determined user can `curl`, or use a browser without it. This is section 6.7
|
|
territory — friction, not a wall.
|
|
|
|
DNS/hosts-only blocking was rejected because it cannot do keyword or path-level
|
|
matching and cannot defeat DoH. Doing both was rejected because two mechanisms
|
|
means two sources of truth, and this project has a strong interest in exactly one
|
|
decision path.
|
|
|
|
## 3. Wayland focused-window detection is compositor-dependent
|
|
|
|
**Status:** partially implemented (ADR-14), partly not.
|
|
|
|
There is no portable API for "which window has focus" on Wayland. Each compositor
|
|
has its own mechanism, and none of them is a standard.
|
|
|
|
Implemented:
|
|
|
|
- **KDE Plasma** — a script loaded over `org.kde.KWin /Scripting`. Verified
|
|
available on the development machine.
|
|
- **X11** — `x11rb` on `_NET_ACTIVE_WINDOW`. Works on any X11 session, and on
|
|
XWayland clients under Wayland. **Blind to native Wayland apps**, which is why
|
|
it is a fallback rather than the primary.
|
|
- **Process-based fallback** — no idea what has focus, acts on process creation,
|
|
and cannot show a meaningful block screen.
|
|
|
|
Not implemented, still in M7:
|
|
|
|
- **wlroots** compositors, via `foreign-toplevel-management`.
|
|
- **GNOME**, via an extension.
|
|
|
|
A further degradation on this machine specifically: `/proc/connectors` does not
|
|
exist on its kernel, so the netlink proc-connector process watcher is
|
|
unavailable. The process fallback must use eBPF or `/proc` polling instead, both
|
|
of which cost CPU and none of which can produce a proper block screen.
|
|
|
|
## 4. Location schedules are mobile-only
|
|
|
|
**Status:** implemented as a deliberate absence on desktop.
|
|
|
|
There is no reliable desktop equivalent. The engine carries the condition and
|
|
validation *rejects* it on non-Android platforms
|
|
(`ValidationError::LocationOnDesktop`), and the desktop UI hides it.
|
|
|
|
This is a design choice rather than an omission. A condition that is offered but
|
|
never fires is worse than one that is not offered: it teaches the user the app
|
|
does not work.
|
|
|
|
## 5. Android OEMs kill background services aggressively
|
|
|
|
**Status:** not yet implemented. M3-Android.
|
|
|
|
Xiaomi, Huawei, Samsung and others kill background services in ways that vary by
|
|
model and by version. An accessibility-service-based blocker is exactly the kind
|
|
of process these vendors target.
|
|
|
|
The plan is a troubleshooting screen covering autostart and battery settings per
|
|
OEM. **Not yet written**, and no OEM-specific workaround exists yet.
|
|
|
|
## 6. Android accessibility URL reading breaks when browser UIs change
|
|
|
|
**Status:** not yet implemented. M5.
|
|
|
|
Reading a browser's URL bar through the accessibility tree means depending on that
|
|
browser's internal view structure, which changes without notice. Every browser
|
|
needs its own resource-id table, and those tables rot.
|
|
|
|
The plan is a per-browser table with a maintenance path. **No table exists yet**,
|
|
and there is no mechanism for a user to report a broken browser other than an
|
|
issue. This will be a recurring support cost, not a one-time fix.
|
|
|
|
## 7. Friction is not a wall
|
|
|
|
**Status:** by design. Section 1.5.4 and 6.7.
|
|
|
|
A user with ADB, root, safe mode, a factory reset, or administrative rights on
|
|
desktop can always remove this app. Blocking ADB, safe mode, or OS recovery is
|
|
not attempted and never will be — ADR-11.
|
|
|
|
What the friction ladder does buy is time, in the moment, against an impulse. A
|
|
user who decides calmly and deliberately still gets through, within a bounded 24
|
|
hours.
|
|
|
|
Stating this plainly matters for a reason beyond honesty: a product that claims to
|
|
be unremovable is a product whose users cannot trust its other claims either.
|
|
|
|
## 8. Play Store policy is hostile to apps that resist uninstall
|
|
|
|
**Status:** accepted. Distribution decision not yet finalised.
|
|
|
|
Accessibility and Device Admin use require policy justification, and both are used
|
|
here for core functionality. An app that resists uninstall is close to
|
|
categorically unpublishable.
|
|
|
|
The plan is to keep the strongest friction behind an explicit opt-in and treat a
|
|
sideload or F-Droid flavour as the primary distribution for it. **Not yet built.**
|
|
|
|
---
|
|
|
|
## Limits specific to this implementation
|
|
|
|
Not platform limits — known shortfalls in the current code, recorded so they are
|
|
not mistaken for platform facts.
|
|
|
|
- **`ProcessBackend` is not implemented.** ADR-14 lists three backends; the KWin
|
|
and X11 paths land in M2-Linux, and the process fallback is weaker than the
|
|
spec assumes because of the `/proc/connectors` gap above.
|
|
- **`rusqlite` vs system-linked SQLite is unresolved** (ADR-4). Bundled is built
|
|
and costs ~1.1 MB of the 5 MB daemon budget. System-linking would reclaim most
|
|
of that but adds a runtime dependency on the host's libsqlite3.
|
|
- **Lock state is not yet persisted or enforced.** `core/src/lock.rs` has the
|
|
timers, the escalation, and the wall-clock correction, all tested. The state
|
|
machine is not yet driven from a UI, and `LockState::evaluate` currently returns
|
|
the Normal level unconditionally.
|
|
- **The golden tables cannot be parsed.** The format is specified in
|
|
`tests/src/golden.rs`; parsing needs serde, which is a dependency decision
|
|
awaiting sign-off (ADR-13, ADR-16). The behavioural coverage that exists today
|
|
is hand-written unit tests in the same spirit.
|
|
- **`LockState` still always evaluates to `Normal`** — persistence works, but no
|
|
UI drives the lock state machine yet.
|
|
- **`Condition::UsageLimit` and `LaunchCount` sum across all targets** rather
|
|
than the schedule's own target set, until targets carry usage keys.
|
|
- **Windows is entirely unimplemented** and cannot be compiled or tested on the
|
|
development machine, so writing enforcement now would mean shipping unverified
|
|
code. |