Skip to content

Add offline trial, licence entry and Rust access enforcement - #1231

Open
enaboapps wants to merge 15 commits into
codex/1228-offline-licence-keysfrom
codex/1230-desktop-licensing
Open

enaboapps wants to merge 15 commits into
codex/1228-offline-licence-keysfrom
codex/1230-desktop-licensing

Conversation

@enaboapps

@enaboapps enaboapps commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Adds separate local/keychain trial and licence records, a 30-day trial for licensing-enabled builds, Settings → Licence entry/import/removal, paste-and-activate, and local-switch recovery confined to the licence page. Rust gates local scanning, dwell clicking and authenticated Android input while preserving pairing, account and settings access. Expiry cleans up active input and updates tray/status messages. Public issuer configuration is supplied at build time; production signing secrets are never embedded.

Closes #1230. Part of #1217. Parent PR: #1229; this PR targets its feature branch and must be retargeted after that parent merges.

Validation: Node.js 24 / Rust 1.97.1; npm lint, npm test (316 UI + 21 Node tests), npm build, docs-site build, cargo fmt/clippy and cargo test (850 unit + 8 integration passed; 4 environment-dependent ignored). Native hardware, paid build configuration and real switch recovery still need manual validation before shipping.

Independent review: no remaining actionable findings at latest head 95060e9639d9de0f23051ad3d1849da2433a9b14. Expiry cleanup observations run only on the main thread; authenticated release-only commands remain available; manual focus/input pauses automatic recovery scanning without losing the key draft.

Final CI: all applicable checks passed at 95060e9639d9de0f23051ad3d1849da2433a9b14, including macOS app/DMG, Windows NSIS installer/package verification, packaged prediction/neural checks, frontend tests/build and dependency audit. Feature remains unmerged and production deployment is out of scope for this PR.

Review follow-up (head e3e2120c)

Independent review of the stack found no issues in #1229 and the following in this PR, each fixed in its own commit: launch-time expiry cleanup ran on every start of an expired install (window raised on hidden login starts, Bluetooth state forced to Advertising); the home licence notice stayed visible for licensed users; the recovery-scanning docs paragraph had landed under credits; "1 days left"; allowed() re-verified the signature on every scanning tick (now reads a cached deadline refreshed by view()); the per-minute checkpoint rewrote the keychain for the life of the app (now file-only, retrying the keychain only while a storage warning is set); a fresh install with an unavailable keychain locked instead of starting its trial; build.rs declares rerun-if-env-changed for the public key configuration.

A second independent review of the fixes raised four hardening points, addressed in 74496dcd, 9ae08a34, 913bbb67 and e3e2120c: keychain self-heal on checkpoint (merging the stored keychain record first, with exponential backoff while the keychain stays locked), access cache starts closed until seeded, two more tests, and a plain statement of the fresh-trial path in docs/licensing.md.

Validation at e3e2120c on Windows: npm lint, npm test (317 UI + 21 Node), npm build, docs site build, cargo fmt --check, cargo clippy -D warnings, cargo test (861 unit + 8 integration passed; 5 environment-dependent ignored).

@enaboapps enaboapps added this to the v1.4.0 milestone Oct 28, 2026
@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
site Ready Ready Preview Oct 28, 2026 2:39pm UTC

Request Review

OwenMcGirr and others added 9 commits October 28, 2026 14:19
An install that was already expired at launch treated the first tick as
a fresh expiry, so every start ran the platform disconnect-all (forcing
Bluetooth to Advertising while the adapter may still be initialising)
and raised the settings window, including hidden login starts. Seed the
observer from the current state so cleanup only runs on a real change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The home notice rendered the licence message on every screen whenever
licensing was configured, so paid users saw a permanent licence banner.
Limit it to trial, expired and unavailable states.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The paragraph about pausing automatic button scanning had landed under
the credits section after the word-prediction note. Move it next to the
recovery mode description it belongs to.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
allowed() ran a full Ed25519 verification and JSON parse on every
scanning tick, switch action and dispatch. Cache the deadline computed by
view(), which already runs at launch, once a second from publish, and
after a key is redeemed or removed, and read the cached value instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The one-minute last-seen checkpoint wrote the keychain record for the
life of the app. The file alone carries the clock-rollback guard, so
keep the checkpoint on the file and refresh the keychain mirror only at
launch and when a key is entered or removed. A keychain warning now
stays visible until the next keychain write succeeds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A first run with no licence file and an unavailable keychain produced no
record, which locked the app with a request to unlock the keychain and
restart. Nothing readable says the trial already ran, so start it and
keep the storage warning. The next launch with the keychain available
reconciles to the earliest recorded first run, so an older trial still
wins. An unreadable file still fails closed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Independent review follow-ups: the per-minute checkpoint retries the
keychain write only while a storage warning is set, so a keychain that
was locked at launch heals without a relaunch; the cached access
deadline starts closed until open() seeds it; tests cover an unreadable
file with an intact keychain record and the file-only save path; the
operations doc states the fresh-trial path when the keychain record is
unreadable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The checkpoint heal wrote this session's record over whatever the
keychain already held, so a trial started while the keychain was locked
could replace an earlier trial or a newer removal once it unlocked. Read
and merge the keychain record first. Failed keychain retries now back
off from one minute to about an hour so a locked macOS keychain is not
prompted every minute.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A checkpoint minute that skipped the keychain retry still cleared the
storage warning and reset the backoff, so a locked keychain was retried
once and then never healed. Factor the checkpoint transition into a pure
helper on Data: a file failure sets the warning, a keychain failure
schedules the next retry, and only a successful keychain retry clears
the warning. A test walks the backoff schedule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Redeeming or removing a key set the storage warning directly and left
any scheduled keychain retry in place, so a later file-write warning
could linger for up to an hour. Route both through the checkpoint
transition so a successful keychain write clears the backoff.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — e3e2120c Deployed Oct 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants