Skip to main content

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.