Skip to content

feat(mobile): add account-owned local access state - #5642

Open
iscekic wants to merge 2 commits into
mainfrom
mobile-context-lock-758a
Open

feat(mobile): add account-owned local access state#5642
iscekic wants to merge 2 commits into
mainfrom
mobile-context-lock-758a

Conversation

@iscekic

@iscekic iscekic commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

No new behavior — the account protection stays inactive, with no new controls in the app.


Summary

LocalAccessSnapshot, LocalAccessDependencies, LocalAccessAction, and LocalAccessActionResult give one owner atomic control over authenticated preference changes and access grants.
LocalAccessScope and LocalAccessLease capture authEpoch and unlockGeneration; rejected admission raises LocalAccessDeniedError (LOCAL_ACCESS_DENIED) rather than accepting stale work.
On return, five background minutes or invalid elapsed time expire protected grants and active attempts before foreground publication; native effects require independent foreground checks.

Files
  • apps/mobile/src/lib/local-access.ts — Source; added (A); 395 changed lines. Adds frozen snapshots, subscriptions, one process owner, and cleanup that rejects pending completions. Owner changes and preference reloads revoke grants and leases; callers supply a validated identity and its authentication epoch. New work requires a captured lease, current ownership, foreground readiness, a ready context, and unlocked access. Owner-only checks permit passive completion of accepted work; lease scopes carry organizationId without checking organization membership. Unlock requests share the pending attempt; competing preference actions return busy, and cancelled authentication requires explicit retry. Enable, disable, and malformed-record repair authenticate before a guarded write; only a committed write publishes the preference with its grant. Missing or disabled preferences allow foreground access, while failed reads and malformed records deny access. Authentication completed while inactive waits to grant access until the foreground; expiry checks cover pending grants and active enable or repair writes. Locking revokes grants without deleting the preference, and owner or attempt changes discard stale results. The coordinator has no app-provider or screen integration at this level.
  • apps/mobile/src/lib/local-access.test.ts — Test; added (A); 953 changed lines. Introduces coordinator and grant-expiry tests.

LocalAccessStorage, LocalAccessReadResult, and LocalAccessWriteResult serialize account preferences through SecurityStore, keeping them separate from credentials and retaining them after sign-out.
recordSchema accepts only version: 1 and Boolean enabled; absent records disable protection, but malformed records or failed reads deny access.
local-access-v1- keys use WHEN_UNLOCKED_THIS_DEVICE_ONLY; isCurrent prevents old native writes from granting access, even when they finish saving the original account's preference.

Files
  • apps/mobile/src/lib/local-access-storage.ts — Source; added (A); 73 changed lines. Encodes account identifiers into keys and parses stored JSON with a strict schema. Serializes reads behind existing writes for the same account. Checks attempt ownership before each native write and after its completion, returning committed, failed, or stale. Leaves credential keys and storage options unchanged.
  • apps/mobile/src/lib/local-access-storage.test.ts — Test; added (A); 167 changed lines. Introduces tests for durable protection preferences.

authenticateLocalAccess rechecks device capability through NativeAuthentication; LocalAuthenticationOutcome and LocalAuthenticationFailure report authentication or recovery classified by RecoveryStatus.
disableDeviceFallback: false keeps passcodes and Expo's default Android Class 2-or-better authentication, including Android 9/10, without turning protection off after failures.
expo-local-authentication ~57.0.2 and faceIDPermission add native support and a Face ID explanation; developers must rebuild the development apps.

Files
  • apps/mobile/src/lib/local-authentication.ts — Source; added (A); 70 changed lines. Imports the native module only when called and supports an injected adapter. Accepts passcode-only enrollment and reports missing enrollment as unavailable. Classifies cancellation, lockout, timeout, processing failures, and authentication failures as retryable. Invalid context, missing usage descriptions, and storage exhaustion require terminal recovery; missing device security requires unavailable recovery. Known unknown errors remain retryable; unrecognized error codes produce terminal unexpected_error. Exceptions return retryable rejected, and recovery never clears protection.
  • apps/mobile/app.config.ts — Source; modified (M); 6 changed lines. Adds the Expo authentication plugin and its permission message for Face ID account unlocking on this device.
  • apps/mobile/package.json — Source; modified (M); 1 changed line. Adds expo-local-authentication at ~57.0.2 to runtime dependencies.
  • apps/mobile/src/lib/local-authentication.test.ts — Test; added (A); 114 changed lines. Introduces tests for native authentication outcomes.
  • pnpm-lock.yaml — Generated; modified (M); 13 changed lines. Records the dependency lockfile update.

Continuous integration (CI) now accepts pull_request events for every target branch in CI and kilo-app CI, allowing stacked pull requests to receive checks.
push remains restricted to main; kilo-app CI retains workflow_call and broadens paths to include the root workflow and admission tests.
Both workflows run admission regressions and preserve their existing job structure, permissions, and cancellation behavior.

Files
  • .github/workflows/ci.yml — Source; modified (M); 4 changed lines. Removes the pull-request target restriction and runs stacked-admission tests before change detection.
  • .github/workflows/kilo-app-ci.yml — Source; modified (M); 8 changed lines. Removes the pull-request target restriction and adds the root workflow and admission tests to both event filters. Runs admission tests before the mobile tests.
  • scripts/stacked-ci.test.mjs — Test; added (A); 254 changed lines. Introduces stacked-workflow admission tests.

Tests: 4 new files, totaling 1,488 changed lines: local-access.test.ts, local-access-storage.test.ts, local-authentication.test.ts, and stacked-ci.test.mjs.
Generated: 1 updated lockfile, pnpm-lock.yaml (13 changed lines).


Verification

  • No manual device tests ran because this level does not expose biometric controls.
  • Device verification remains pending in the final tip's bot-e2e workflow; the orchestrator performs it before completion, not a human tester.
  • The section remains bot-e2e, not human-e2e.

Visual Changes

Visual Changes: N/A

Reviewer Notes

Human steps

  • after merge — Rebuild and install the iOS and Android development apps before testing native authentication.
  • This level needs no environment setup, new secrets, data migration, or flag changes before merge.

Supplied verification evidence

  • The handoff reports five passing scoped checks and 37 passing CI regression tests.
  • The handoff reports 156 core tests, including 43 expiry regressions.

Scope

  • Repository: Kilo-Org/cloud.
  • Worktree: /Users/igor/Projects/.worktrees/mobile-context-lock-758a.
  • Branch: mobile-context-lock-758a; base: origin/main.
  • Commits: 7503e72a5 and cf2150203; level 1 only.

Notes

This level does not expose biometric controls. Full iOS and Android verification runs on the final stack level.

Stacked PRs — merge bottom to top. Each level shows only its own diff.

Runtime verification (E2E, user advocacy, simplify) runs on the tip PR over every level.
Every level keeps its own checks, its own bot review, and its own threads; each one is answered on its own PR.
Each level is its own deliverable: it builds and passes its own checks alone.
A finding on a level is repaired on that level, then carried upward with stack.sh forward.

  1. mobile-context-lock-758afeat(mobile): add account-owned local access state #5642 ← this PR
  2. mobile-context-lock-758a-s2fix(mobile): bind restored context and drafts to account scope #5651 (tip)

@iscekic iscekic self-assigned this Aug 28, 2026
@kilo-code-bot

kilo-code-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (12 files)
  • .github/workflows/ci.yml
  • .github/workflows/kilo-app-ci.yml
  • apps/mobile/app.config.ts
  • apps/mobile/package.json
  • apps/mobile/src/lib/local-access-storage.test.ts
  • apps/mobile/src/lib/local-access-storage.ts
  • apps/mobile/src/lib/local-access.test.ts
  • apps/mobile/src/lib/local-access.ts
  • apps/mobile/src/lib/local-authentication.test.ts
  • apps/mobile/src/lib/local-authentication.ts
  • pnpm-lock.yaml
  • scripts/stacked-ci.test.mjs

Reviewed by grok-4.6 · Input: 210.8K · Output: 22.8K · Cached: 439.8K

Review guidance: REVIEW.md from base branch main

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.

1 participant