ADR 0153: An account signs the Mac in
Status: accepted (James, 2026-09-01). Amends the manual host-token enrollment in ADR 0151; the public doorway, device authority, and Mac-owned Clankie boundaries remain unchanged.
Context
The first gateway deployment binds a hand-made host id to a bearer copied into the Mac Keychain and the gateway host. That proves the transport, but it is not a product journey: every tester needs operator intervention, rotation requires a coordinated restart, and another Mac creates another secret to distribute.
The ordinary journey needs to identify a user, bind each Mac installation to
that account without granting AWS or gateway administration, survive access
token expiry and login, and retain the existing /pair phone flow. The gateway
does not need a customer database merely to validate that identity.
Decision
One Amazon Cognito Essentials user pool is Clankie's account authority. Its
public client requests passwordless EMAIL_OTP; it has no client secret,
hosted UI, or social provider. Cognito currently requires PASSWORD to appear
beside EMAIL_OTP when the pool is created, but Clankie creates every user
without a password and never presents or requests the password challenge.
During the invited beta, self-sign-up is disabled and an operator adds allowed
email addresses. Opening signup later changes the same pool flag, not the client
or gateway protocol.
The Mac discovers the non-secret Cognito endpoint, issuer, client id, and signup
mode from GET /gateway/v1/config. /gateway asks for an email, completes the
one-time email challenge, stores the access and rotating refresh token in the
existing Keychain credential broker, creates one random installation id, and
restarts the connector. It then tells the user to run the unchanged /pair
flow. clankie autostart enable installs a per-user LaunchAgent so the existing
supervisor starts Clankie and its relay after login.
An installation's public host id is deterministic:
base64url(sha256("clankie-host-v1\0" + cognito-sub + "\0" + installation-id))
The Mac and gateway compute it independently. The gateway verifies the Cognito
RS256 access JWT against the issuer's JWKS and requires the exact issuer, client
id, token_use=access, signing key, signature, and time claims. It accepts the
derived host id only when it matches the authenticated subject and presented
installation id. There is no host registry or enrollment database.
Access tokens last one hour. The Mac refreshes within five minutes of expiry, persists Cognito's rotated refresh token, and reconnects the WebSocket; the gateway closes a connection at token expiry as a backstop. Refresh tokens last 90 days. Disabling a Cognito user revokes the account across its installations; each installation still has a distinct route identity.
The Cognito stack is separate from the Lightsail stack. The gateway continues to accept the original static host token during migration, so deploying account support cannot strand the existing review Mac. Tailscale remains only the private operator and release path to the gateway host.
Alternatives considered
- Keep manual bearer enrollment. Rejected because operator-mediated setup is not acceptable user onboarding and rotation couples two machines.
- Add a gateway-owned user and host database. Rejected because Cognito can issue and verify identity while the installation-derived route removes the only proposed registry lookup.
- Use Cognito Hosted UI or a browser callback. Rejected because email OTP is native in the existing TUI and does not need a redirect scheme, domain, or browser session.
- Make Tailscale identity the customer account. Rejected because Tailscale is the deployment plane, not an App Store dependency.
Consequences and ceilings
- An invited tester can configure the Mac with an email and one code, then pair the phone without AWS, SSH, Tailscale, URLs, host ids, or bearer tokens.
- Cognito owns account identity and account-wide disable; Clankie on the Mac still owns devices, grants, conversations, terminals, and model credentials.
- Cognito email OTP requires a verified Amazon SES sender. SES production access is required before an unverified tester address can receive a code.
- Account disable is intentionally account-wide. Per-installation remote revocation requires a persisted installation registry if the product later needs it.
- This removes the automatic-enrollment launch gap. ADR 0151's application-layer device-to-Mac encryption gate still remains before unrelated customers share the public gateway.
