Installation guide
This guide is for an engineer installing and configuring the Find My Data platform — locally for evaluation and development, or on Azure for a single-tenant production deployment. It covers prerequisites, the seeded demo state, the completeFMD_* configuration reference, production boot lockouts, worker topology, and the feature-gate matrix. Current release: 26.7.15.0 (scheme year.month.day.x; see VERSION at the repo root). For what is implemented vs. mocked vs. gated, STATUS.md is the authoritative honesty ledger.
Prerequisites
- Bun ≥ 1.3 (tested with 1.3.14). Bun is the only requirement — no Node.js, no database server, no Docker, no cloud credentials for the local path.
Local installation
bun run dev (scripts/dev.ts) starts two processes: the API server (packages/server, Bun with --watch, default port 8710) and the Vite web dev server (packages/web, port 5173). Open http://localhost:5173 and sign in with the development persona picker (see What the seed creates).
Verify the API directly:
GET http://localhost:8710/api/health→{"status":"ok"}GET http://localhost:8710/api/ready→ readiness plus per-integration modes (see Readiness and honest integration modes)
package.json):
The dev database and artifacts live under
packages/server/data/ (gitignored). To reset:
Seed guards
bun run seed (packages/server/src/fixtures/seed-cli.ts) is a development-only operation with two hard refusals:
- Exits with an error if
FMD_ENVis notdevelopment. - Exits with an error if the database already contains a tenant — it never overwrites. Delete the data directory to reseed.
What bun run seed creates
The seed builds the complete Meridian Grove Holdings demo state (a fictional mid-market retail/logistics group — all people, groups, and documents are synthetic) by running the same services and scan pipeline the UI uses:
- Tenant + org profile (industries Retail/Logistics; geographies United States/European Union) and an uploaded governance policy document.
- A sensitivity-label catalog fixture.
- An AI-generated taxonomy draft (deterministic mock AI provider), then governance-approved Human Resources and Finance business domains with owners assigned.
- A mock Microsoft 365 connector instance (
Microsoft 365 (deterministic fixture)), discovered source scopes (the HR screening drive is marked as HR’s expected/authoritative location), and a completed baseline inventory scan of every scope through the real durable pipeline.
packages/server/src/fixtures/identity.ts) — one per visible role, plus a second domain owner so cross-domain isolation is always demonstrable and a second governance admin so propose ≠ approve separation of duties works on model releases:
Fixture groups referenced by the mock tenant’s permission grants:
FMD-CDX HR Screening, FMD-CDX HR Owners, Project Phoenix Team.
Configuration reference
All configuration is environment variables, parsed and validated inpackages/server/src/kernel/config.ts (Zod schema; invalid values fail boot). Copy .env.example to .env as a starting point. Booleans are enabled only by the exact string true.
Secret-reference indirection (*_REF)
Variables ending in _REF never hold a secret. Their value is the name of another environment variable that holds the secret. Example:
blocked, never live.
Core
Connector mode
Microsoft Graph identifiers and secret references
Two separate app registrations by design — read/scan vs. remediation — for blast-radius containment (see permissions manifest §1.1). All are strings with no default; unset means the corresponding live path isblocked.
AI provider
Webhook public URL
Exchange mailboxes
Control plane entitlement (licensing)
Without both variables the deployment runs as
unlicensed. GET /api/controlplane/entitlement reports state (active / grace / expired / invalid / unlicensed), plan, expiry, and usage vs. limits. Past expiresAt but inside offlineGraceDays, the state is grace and the product keeps running.
Feature flags
All are booleans, defaultfalse, enabled only by the literal string true. See the feature-gate matrix for what each flag needs beyond itself.
Production boot lockouts
buildConfig in config.ts enforces two startup invariants when FMD_ENV=production. Both throw ConfigError before the server binds a port — the process refuses to boot (behavior locked by kernel/config.test.ts):
- Dev auth is refused.
FMD_AUTH_MODE=devfails with:FATAL: FMD_AUTH_MODE=dev is not permitted when FMD_ENV=production. Configure FMD_AUTH_MODE=entra with a valid Entra application. - The default session secret is refused.
FMD_SESSION_SECRET=dev-only-change-me(or unset, which yields the default) fails with:FATAL: default FMD_SESSION_SECRET is not permitted when FMD_ENV=production.
(issuer, subject) — never by email or display name.
Worker topology: embedded vs. standalone
The scan pipeline runs on a durable SQLite-backed job queue (ADR-0002, ADR-0007). Two ways to run workers, same code path (startWorkerLoop):
- Embedded (default).
bun run dev/ the API process (packages/server/src/main.ts) starts a worker loop in-process (worker IDembedded-<pid>). The embedded worker also owns the periodic sweep: renewing due change-notification subscriptions, stamping reconcile freshness, and expiring unrenewable subscriptions. - Standalone (optional).
bun run workerrunspackages/server/src/worker-main.tsas a separate process (worker IDworker-<pid>) against the same SQLite database and queue, with gracefulSIGINT/SIGTERMshutdown. Use it to scale scanning independently of the API; multiple workers can pull from the shared queue. Note the standalone worker does not run the subscription sweep — that stays with the embedded worker in the API process.
Readiness and honest integration modes
GET /api/ready (packages/server/src/routes/health.ts) returns status (ready/degraded, HTTP 503 when the database check fails), env, and one honest mode per integration:
mock— deterministic fixture path (exercised by tests).live— the feature is enabled and its credentials actually resolve (the*_REFpoints at a present value), not merely configured.blocked— configured or enabled but a prerequisite is unmet. Never silently faked.disabled— not enabled.
FMD_CONNECTOR_MODE=mock, the changeNotifications, teamsConnector, and exchangeConnector capabilities report mock regardless of their flags (the dev simulator exercises those paths).
One caveat, stated plainly in the permissions manifest §5: readiness computes from configuration, so setting FMD_FEATURE_PURVIEW_LABEL_WRITE=true with resolving remediation credentials shows purviewLabelWrite: live even though execution still fails at validation time without Microsoft’s protected-API enablement (the action transitions to a blocked state with the reason). Do not set feature flags ahead of their prerequisites.
Feature-gate matrix
Extra prerequisites are what the capability needs beyondFMD_CONNECTOR_MODE=graph with resolving read/scan Graph credentials (FMD_GRAPH_TENANT_ID, FMD_GRAPH_CLIENT_ID, FMD_GRAPH_CLIENT_SECRET_REF → resolving). Build-state legend: live-validated = exercised against a real tenant this run; mock-validated, live gated = full code path tested against fixtures, live path behind the gate and unvalidated; not built = no consumer.
Full permission-by-permission detail, least-privilege alternatives, and protected-API caveats: permissions manifest.
Azure deployment
Single-tenant, customer-hosted deployment as Bicep — see infra/README.md for the full flow and rationale; this is the summary. The template provisions a user-assigned managed identity, Key Vault (RBAC), a storage account + file share for the SQLite database and artifacts, a Log Analytics workspace, and an Azure Container App running the API with the embedded worker, ingress on port 8710.- No secrets in source control. Key Vault secrets are placeholders set out-of-band; the app reads them through Key-Vault-backed Container App secrets resolved by managed identity. The
*_REFindirection is preserved: the template setsFMD_GRAPH_CLIENT_SECRET_REF=GRAPH_CLIENT_SECRETand bindsGRAPH_CLIENT_SECRETto the Key Vault secret. - Production lockout active. The template sets
FMD_ENV=productionandFMD_AUTH_MODE=entra; the app refuses to boot with dev auth or the default session secret. - Honest scale posture. This first build is SQLite on an Azure File share with the worker embedded in the API replica. The documented scale path (managed PostgreSQL, a separate worker Container App, private endpoints, Azure OpenAI) is deliberately not provisioned to avoid implying it is done — see infra/README.md and ADR-0003.
GET /api/ready reports the integration modes you expect — nothing claims to be live until its credentials resolve.
Related documentation
- STATUS.md — implemented / mocked / gated honesty ledger
- Architecture and ADRs
- Permissions manifest — Microsoft Graph permissions and protected-API notes
- CDX test runbook — live-tenant validation record and procedure
- Threat model · Capacity model · Roadmap