Aller au contenu

Mobile connection and pairing proposal

Status: proposal, not implemented. Today the Node API listens only on http://127.0.0.1:<port> and the native Expo client has no Node connection, pairing, secret store or remote authentication. Planned with R9 (native mobile follows Desktop with real secure-storage and pairing evidence) after remote authentication (MN0); open owner questions block implementation.

Current boundary

The Node API is an authenticated HTTP/WebSocket server bound to 127.0.0.1. Its private descriptor admits that exact loopback origin and the fixed operation and observation paths. Each Node start creates a new local bearer and observation cursor-signing key. Native Expo Node connections, pairing, a native secret-store adapter, managed-device credentials, remote authentication and private-network listeners do not exist. The design below keeps loopback as the default and is delivered later as a protocol-first vertical slice.

Outcome and invariants

An iPhone Expo development build registers several exact Control Nodes, keeps a bounded set reachable and explicitly selects one. Each registration may have approved LAN and private-tailnet paths.

  • Identity is the exact Node ID plus a manually verified Node public-key fingerprint, never a hostname, IP address, route, Bonjour result, tailnet account or Tailscale identity.
  • Each registration has one Node-issued device ID and a Node-scoped device key. Node-owned grants bind actor, device, Node audience, allowed Projects/capabilities, revision and expiry, independent of network path.
  • Tailscale supplies private reachability and enrollment evidence only; it never authenticates a Maia principal or authorizes an operation.
  • Connection health, registration and selection are separate states; reconnect or path choice never changes selection.
  • Paths are deduplicated only within one verified registration. No request, state, receipt or cursor is deduplicated across Node IDs.
  • Cached data is visibly stale, bounded and never authoritative for health, grants, revisions, operation success or selected server state.
  • Any missing identity, pin, credential, grant, policy revision or recovery fact fails closed.

Trust bootstrap

No automatic network discovery and no trust-on-first-use prompt.

  1. An authenticated local administrator asks the Node through the CLI for a short-lived, single-use pending pairing. The CLI shows the exact Node ID, full public-key fingerprint, expiry, verification words and optional locator hints. A QR code may carry the same material but is not an approval.
  2. The user compares that material through a trusted physical or authenticated channel. The app shows the full fingerprint and requires manual verification before sending application data. A locator entered before verification stays untrusted.
  3. The phone generates a non-exportable, Node-scoped device key in the native secret store. TLS must authenticate the displayed Node key before the app presents the one-time challenge and device public key.
  4. Node allocates a pending device ID. The CLI shows that ID, the full device-key fingerprint, proposed grants, Node ID and expiry. The user compares the device fingerprint with the phone; the administrator confirms that exact record only after it matches. Holding the challenge never approves a device or chooses grants.
  5. Node issues a short-lived, Node-audience-bound managed credential. The phone stores only its protected handle and the verified pin. A first authenticated status read must echo the exact Node ID and grant revision before the registration is usable.

The exchange needs authenticated encryption and channel binding. No TLS/mTLS provider, certificate profile or pinning adapter is approved yet; do not assume one. If the iOS networking stack cannot verify the expected key before sending application bytes, it is unsuitable.

Identity, keys and native credential storage

After pairing, use mutually authenticated TLS: Node presents the pinned server identity; the device presents a short-lived certificate or equivalent proof bound to its device record. Node still checks current grants and revocation on every request.

  • Device private keys, managed credentials and pins live only in a Keychain-backed ClientSecretStore adapter. Never in AsyncStorage, React state, deep links, clipboard, logs, crash reports, project files, protocol results or default backups.
  • Application code gets opaque handles and public metadata, never secret bytes. Unlock, background access, migration, restore and backup behavior need an explicit native policy and physical-device tests.
  • Renewal authenticates the same device and Node, rechecks the grant revision and overlaps old and new credentials only briefly. Expiry or revocation closes streams and blocks new requests.
  • Device-key loss requires local-admin re-pairing under a new device ID and revocation of the old one. No plaintext recovery export.
  • A Node key or pin change blocks every path. Rotation is an explicit ceremony showing old and new fingerprints, under local-admin policy; without the old key, recovery uses the local CLI and re-pairing. Path failover never hides a rotation.

Listener and network policy

Loopback stays the default and the local descriptor stays local. Remote access needs an explicit private-listener opt-in naming exact interfaces or private addresses, serving authenticated HTTPS/WSS only, with a visible disable and revoke path. Out of scope: wildcard or public exposure, plain HTTP off loopback, automatic router configuration, Tailscale Funnel, hosted brokers, relays and a public web client. See networking decision.

Multiple Nodes and path failover

The client directory stores only non-secret registration metadata: Node and device IDs, opaque pin and secret handles, grant summary and revision, display label, approved locators, last verified epoch and last authoritative watermark. Each Node has its own SDK connection and projection. The UI always names the selected Node; switching is explicit and never retargets open work or forgets another Node.

  • After TLS and Node-ID verification, LAN and tailnet paths for the same Node may compete; one path per request or stream. A healthy verified path may replace a failed one without changing selection. A pin, Node ID, audience, policy or epoch mismatch blocks that locator instead of falling through.
  • Reads fail over with a fresh request or a Node-signed cursor; an expired, gapped, wrong-epoch or wrong-policy cursor requires a fresh snapshot.
  • Mutations persist the command ID before dispatch. On disconnect the outcome is unknown: reconnect to a verified path of the same Node and reconcile the receipt before any replay. A permitted replay reuses the idempotency key and never goes to another Node.

Mutations and confirmations

Pairing administration, grant changes, credential rotation, revocation and forget/recovery are consequential. Their registry contracts use exact targets and revisions and follow preview, trusted confirmation, compare-and-set/idempotency, durable event and receipt. Confirmation binds the operation, normalized input digest, actor, device, Node audience, exact targets, expected revisions, expiry and command ID. Voice, discovery and cached state cannot confirm. Operation IDs and schemas are defined only in packages/protocol.

Reconnect and bounded resources

Proposed limits, pending owner approval: 16 stored registrations, 4 concurrent Node connections, 2 approved paths per Node, 1 aggregate observation stream per connected Node and 1 in-flight pairing per device/Node pair.

  • Foreground reconnect uses capped exponential backoff with jitter, one attempt at a time per Node. Authentication, pin, revocation or policy failures never retry automatically.
  • iOS suspension means disconnected; resume reauthenticates before using cached projections.
  • Memory, queued frames, snapshots, retries, pairing attempts and renewal overlap all have hard limits with typed saturation or reset states.
  • Forgetting a registration removes only local handles and cached projection after confirmation; it never deletes, revokes or stops the Node. If cleanup is uncertain the client shows recovery required, and the CLI can inspect or revoke the exact device.

Delivery sequence

After the owner decisions, deliver one release-disabled read-only slice:

  1. Protocol registry: minimum pairing, device/grant, revocation, recovery, authenticated status, errors, risk/exposure, receipt and resource contracts, with conformance and hostile-bootstrap fixtures.
  2. @maiaos/sdk: one transport-neutral pinned managed-device transport with typed pairing, status and reconnect; no mobile-only backend.
  3. Maia Node: explicit private listener, identity and grant authority, secret-store boundary, handlers, revocation, audit evidence and fail-closed restart, keeping the loopback default.
  4. CLI and real-boundary tests: the CLI is the first complete pairing/revoke/recovery client. Prove wrong-server rejection, pin change, expiry, revocation, LAN/tailnet path loss, unknown-outcome reconciliation, saturation, Node restart and the no-Funnel loopback default.
  5. iPhone projection: compose the native secret store and SDK transport in a development build exposing only pair, explicit select, connection state and read-only Node status on a physical device.

This slice admits no mobile mutation, voice, notification, background connection or broader client capability. Android parity and more operations follow only after the iOS trust and recovery evidence passes.

Imported from Maia OS docs at 3dc3e07