> ## Documentation Index
> Fetch the complete documentation index at: https://docs.findmydata.io/llms.txt
> Use this file to discover all available pages before exploring further.

# ADR 0017 account portal

# ADR-0017: Account portal — human commercial front door (registration, self-service evaluation, license views)

**Status:** Accepted · 2026-07-15

## Context

ADR-0015 named `account.findmydata.io` as the human commercial plane and left it
"design + roadmap (Stage B)". ADR-0016 then built the machine Control service
(the account registry, activation-code minting, entitlement signing, deployment
status). This ADR builds the **human portal** on top of that registry: the place
a prospect registers, starts a 30-day evaluation, and watches their deployments
activate and stay licensed.

## Decisions

### 1. A thin human layer over the vendor registry, not a second source of truth

The portal (`packages/server/src/account/`, run via `bun run --cwd
packages/server account`) shares the **same vendor database** as the Control
service. The commercial account/org *is* the existing `cp_accounts` row; the
portal adds only the human side — `portal_users`, `org_members` (linking users to
accounts), server-side `portal_sessions`, and a human-facing `portal_evaluations`
record. Registration and "start evaluation" reuse the Control machinery
(`createAccount` + `createActivationCode`), so there is one registry, viewed two
ways (machine API + human portal). Proven by an interop test: a portal-minted
code activates on Control and the resulting deployment surfaces in the org view.

### 2. Human identity is distinct from machine Control identity

The portal issues its own opaque, server-side sessions (a high-entropy cookie
looked up in `portal_sessions`, not the machine Control credential). Mutations
that change state (`/evaluations`) require a per-session CSRF token; login,
register, and the session-establishing routes are the CSRF entry points. Org
views are **membership-guarded** — a user requesting an org they don't belong to
gets 404 (existence is not leaked), mirroring the product's cross-scope rule.

### 3. Dev auth for the reference build; production requires an IdP

`FMD_ACCOUNT_AUTH_MODE=dev` uses email-only sessions (no passwords) for local and
reference use — consistent with how the installed product treats its dev auth
mode, and avoiding a bespoke password store. Production **refuses** to boot in
`dev` mode; `idp` mode disables the dev-login + self-service register routes (501)
pending an external identity provider (OIDC/SSO/MFA). This is the honest state:
the portal's production identity is an integration point, not yet built.

### 4. The content boundary is unchanged

The portal knows emails and organization names — never anything from a
deployment's data. It reads only licensing/registry facts (plan, deployments +
coarse status, activation-code metadata, control events). Activation codes are
shown in full once (at creation); listings return metadata only, never the code
hash or plaintext.

### 5. A self-contained, honest UI

The portal UI (`account/ui.ts`, served at `/account`) is one self-contained,
brand-consistent page (navy/teal, Inter, no external assets) that talks to the
`/account/v1/*` JSON API. It states plainly that "Find My Data installs in *your*
environment — this portal only manages licensing," reinforcing the
customer-hosted model rather than implying a shared SaaS data plane.

## Consequences

* The core customer journey from ADR-0015 is now runnable end-to-end for the
  evaluation path: **visit marketing → register → receive an evaluation +
  activation code → activate the deployment (online or offline) → watch license
  state in the portal.**
* Out of scope here (future Stage B/C): production IdP integration, org member
  management + invitations, paid-conversion/billing, entitled downloads with
  short-lived URLs, and support-case linkage.

## Verification

`account/account.test.ts` (12 tests) — health/auth-mode, register (user + org +
one-time code), duplicate/invalid email, dev login (unknown 401 / known 200),
`me` requires a session, evaluations require CSRF, membership-guarded org views
(404 for non-members), logout invalidates the session, idp mode disables
dev-login/register (501), the UI is served, and an interop test where a
portal-minted code activates on Control and appears as a deployment. All 249
server tests pass; typecheck + lint clean; the service boots and the full
register → evaluation → dashboard flow was verified in a browser.
