What the app uses of the device
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Accepted · Audience: Both · Area: N — Companion
Purpose
A phone offers things a terminal does not — a camera, a secure enclave, a fingerprint, a notification that arrives when the app is closed — and each one is a permission somebody grants and a surface somebody can lose.
This feature is the whole of what the app asks of the device, why each is asked for, and what happens when it is declined. It is separate from N1 because the answer to “what does this app do with my camera” should be readable in one place by somebody who is not going to read the rest.
The rule underneath all of it is G8’s: nothing leaves the device that the operator did not ask to send.
Behaviour
Every permission is asked for at the moment it is used
Nothing is requested on first launch. The camera is asked for when somebody taps scan, notifications when they choose to be notified. A permission sheet on launch, before the person knows what the app is, is a request they cannot meaningfully answer.
Each one states what it is for in the app’s own words before the system asks.
Every permission is optional, and the app works without it
Declining the camera leaves typed pairing. Declining notifications leaves an app that is checked rather than one that tells you. Declining biometrics leaves a passcode. Nothing is a dead end, and nothing is asked for twice after a refusal.
The session lives in the platform’s secure storage
A session token is kept in the Keychain or the Android Keystore, never in application preferences, never in a file the app writes, and never in a backup that leaves the device unencrypted.
The app locks
Anything that can stop a stack or read a household’s requests is behind a lock: biometric where offered, passcode otherwise. It engages on backgrounding rather than after a timer, because the case it exists for is a phone handed to somebody for a moment.
Biometric failure falls back to the device passcode. It does not fall back to nothing.
A lock over nothing is an obstacle, not a protection
N4-R19 asks for the device’s own authentication on a cold start. Read without
a qualifier that is also true of the first launch of a freshly installed app,
where no stack is paired, no session is held and the secure store is empty —
and the first thing the app does is demand a fingerprint to see a screen that
says no stacks yet.
What the lock is for is what decides when it applies. It stands between a person holding the phone and what the operator’s credential reaches: a stack that can be stopped, a household’s requests, a diagnostic. Where the device holds none of that, there is nothing on the other side of the prompt, and an authentication that protects nothing teaches an operator that the prompt is noise. That is the way a lock gets turned off.
So the first run is the case the requirement does not cover, and the way to
tell it is not a flag the app sets but the store it already reads: nothing
paired and nothing held is a device with nothing to protect. It is the same
question N1-R35 answers on the other side — a launch with no stack configured
is an invitation to pair, and a lock in front of an invitation is a door onto a
field.
It is not a grace period and it is not configurable. The moment the device holds a pairing, the prompt applies on the next cold start with no further condition. An operator who has paired once is never again offered the unlocked launch, which is what keeps this from being a setting somebody can leave on.
Notifications say what happened without saying too much
lemonfiber already decides what is worth telling somebody about (B5). A notification on a lock screen is read by whoever is holding the phone, so it carries what happened and never a credential, a household member’s name, or a title somebody requested.
The app does not invent alerts of its own.
Notifications reach the phone without a vendor’s push service
No notification passes through Apple’s or Google’s push service, or through anything that could read it (ADR-0047). The app raises each one itself, from its own pinned connection to the stack: while it runs, and when the platform wakes it for background refresh, when it reads what the stack raised while it slept. On Android, where a UnifiedPush distributor such as ntfy is installed, the stack can wake the app through it; the push says only that the stack has something to say, and what is notified is read over the app’s own connection.
A closed app on iOS hears its stacks when the system lets it, which may be rarely. The app says so where notifications are turned on, rather than promising what it cannot do, and says the same of Android without a distributor.
What it reads on waking is what the stack hands back of what it raised meanwhile. Unless the stack says that hand-back is whole, the app cannot tell whether an alert was lost while it was not listening, so it never presents what it told as everything, and says to the operator, where notifications are turned on, that some may be missing.
Nothing is sent anywhere else
No analytics, no crash reporting to a third party, no telemetry. A diagnostic the operator chooses to share is assembled and handed to them (C4’s rules apply to its contents), and they send it. The app does not.
The platform’s own accessibility, not a second one
Because the UI is the platform’s own components (ADR-0017), text scaling, screen readers, contrast and reduced motion are the system’s and are honoured by being used rather than re-implemented. G3’s bar applies here as it does to every surface.
What the brand does and does not assert on this surface is its own mapping: the accent, the neutral roles in a light and a dark value each, the app icon and the launch mark, and nothing that would override the reader’s own type size or choice of light or dark.
States
| State | Meaning |
|---|---|
| Locked | The app is open and showing nothing until unlocked. |
| Unlocked | Normal operation. |
| Permission not asked | The feature needing it has not been used yet. |
| Permission declined | The alternative path is offered; the request is not repeated. |
| No biometric enrolled | Passcode, without presenting biometric as unavailable-and-broken. |
Edge cases
| Situation | Behaviour |
|---|---|
| Biometric fails repeatedly | Falls back to the device passcode, never to no lock at all. |
| The device has no secure storage | The app refuses to store a session and says why, rather than writing it somewhere weaker. |
| The stack cannot hand back everything raised while the app was away | The app notifies what it was handed and never calls it everything; where notifications are turned on, it says some may be missing. |
| Notifications are declined after being granted | Honoured immediately; the app does not keep trying. |
| A notification arrives for a stack that has since been removed | Not shown. A notification for a stack the person no longer has is confusing and leaks that it existed. |
| The app is backgrounded mid-repair | The repair is the core’s, and continues there. The app locks and reports the outcome when next opened. |
| The OS screenshots the app for its task switcher | The locked screen is what is captured, not the last screen shown. |
| A permission is revoked in system settings while the app runs | Detected at next use and handled as declined, not as an error. |
Acceptance criteria
| ID | Requirement |
|---|---|
| N4-R1 | A permission MUST be requested at the point of first use, and MUST NOT be requested on launch. |
| N4-R2 | The app MUST state what a permission is for, in its own words, before the system prompt. |
| N4-R3 | Every permission MUST be optional, and the app MUST offer a working alternative for each declined one. |
| N4-R4 | A declined permission MUST NOT be requested again automatically. |
| N4-R5 | A session token MUST be stored in the platform’s secure storage, and MUST NOT be written to application preferences, an app-readable file, or an unencrypted backup. |
| N4-R6 | Where the device offers no secure storage, the app MUST refuse to persist a session and MUST say why. |
| N4-R7 | The app MUST lock on backgrounding, and MUST require biometric or passcode to resume. |
| N4-R8 | Biometric failure MUST fall back to the device passcode and MUST NOT fall back to unlocked. |
| N4-R9 | The task-switcher representation of the app MUST NOT show application content. |
| N4-R10 | A notification MUST NOT contain a credential, a household member’s name, or the title of a requested item. |
| N4-R11 | The app MUST NOT raise alerts of its own; every notification MUST originate in the core’s notification decisions. |
| N4-R12 | The app MUST NOT send analytics, telemetry or crash reports to any third party. |
| N4-R13 | A diagnostic report MUST be assembled for the operator to send, and MUST NOT be transmitted by the app. |
| N4-R14 | The app MUST honour the platform’s text scaling, screen reader, contrast and reduced-motion settings, and MUST NOT implement a parallel accessibility layer. |
| N4-R15 | A notification MUST NOT be shown for a stack no longer configured on the device. |
| N4-R16 | The app MUST declare a local-network permission purpose that states what the network is used for in the operator’s terms, and MUST NOT ship a placeholder string. |
| N4-R17 | Where the platform requires permission before the local network may be reached, the app MUST report a refused permission as a distinct condition from an unreachable stack, and MUST offer the way to grant it. |
| N4-R18 | A screen showing a credential, a session token or pairing material MUST be excluded from the task-switcher snapshot and from screen recording. |
| N4-R19 | The app MUST require the device’s own authentication on a cold start, and again on resume after a period the operator can configure; it MUST NOT prompt while an action it has already sent is still outstanding. |
| N4-R20 | A notification shown while the app is locked MUST NOT disclose a finding’s detail, a service name, or any value read from a stack. |
| N4-R21 | Every control that can be operated MUST carry a label the screen reader announces, and a control whose only content is an icon MUST NOT rely on that icon to convey its purpose. |
| N4-R22 | Authentication on a cold start (N4-R19) MUST NOT be required where the device holds no pairing and no session, and the app MUST reach its first-run surface without a prompt. |
| N4-R23 | Whether the device holds a pairing MUST be read from the store itself rather than from a flag the app maintains, and the unlocked first run MUST NOT be reachable once a pairing has been held. |
| N4-R24 | While the app is locked, no application content MUST be drawn behind the lock, including a first-run surface, an empty state and any previously rendered frame. |
| N4-R25 | The app MUST NOT use Apple’s or Google’s push service, or any service able to read a notification; it MUST raise each notification locally, from its own pinned connection to the stack, while it runs and when the platform wakes it for background refresh, and on waking MUST notify what the stack raised since it last listened (ARCH-R281). |
| N4-R26 | On Android, where a UnifiedPush distributor is installed, the app MUST offer waking through it, MUST register the endpoint the distributor gives with each stack the operator turns it on for and remove it when turned off or the stack is removed (ARCH-R282), and MUST treat a push as a wake only, reading what to notify over its own connection. |
| N4-R27 | Where the operator turns notifications on, the app MUST say that while it is closed it hears a stack only when the platform wakes it, and so may notify late or not at all, on iOS and on Android without a UnifiedPush distributor. |
| N4-R28 | Unless the stack says its replay holds every alert raised since the app last listened (ARCH-R281), the app MUST NOT present the alerts it notifies on waking as all of them, and MUST say to the operator, where notifications are turned on, that some may be missing; a household member MUST NOT be shown it. |