> ## 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.

# Quick start

# Quick start guides

This document is for a new user sitting at the keyboard with the demo environment running, who wants to understand Find My Data by driving it — one guided walkthrough per seeded persona, about 10 minutes each. It applies to release 26.7.15.0. Everything below runs against the deterministic Microsoft 365–shaped fixture tenant for the fictional demo org **Meridian Grove Holdings** — no cloud credentials, no live tenant. Where a capability is feature-gated or mock-only, the walkthrough says so; see [STATUS.md](../STATUS.md) for the full honest capability table.

## Before you begin

Requirements: [Bun](https://bun.sh) ≥ 1.3 (tested with 1.3.14). No Node.js, no database server, no Docker.

```bash theme={null}
bun install          # install workspace dependencies
bun run seed         # create + migrate the dev database and seed the demo org
bun run dev          # start API (http://localhost:8710) + web (http://localhost:5173)
```

Open **[http://localhost:5173](http://localhost:5173)**. You land on a persona picker: this is development identity mode (`FMD_AUTH_MODE=dev`), explicitly banner-marked in the UI, and refused at boot when `FMD_ENV=production` (see [ADR-0004](adr/ADR-0004-authentication-dev-mode-and-entra.md)). Entra ID is the production sign-in path; the OIDC flow itself is a documented seam, not active in this build.

`bun run seed` leaves the tenant in a post-onboarding state: org profile and a policy document ingested, an HR + Finance taxonomy generated by the mock AI provider and human-approved, owners assigned, the mock M365 connector registered with three source scopes, and a completed baseline inventory scan of all scopes. That means every persona has something real to look at immediately.

To reset at any point: `rm -rf packages/server/data && bun run seed`.

### The seeded personas and what they can see

Navigation is role-conditional (`packages/web/src/App.tsx`); enforcement is capability-based on the server, deny by default (`packages/shared/src/capabilities.ts`). What each persona's sidebar actually shows:

| Persona           | Title                        | Role                             | Sidebar navigation                                                         |
| ----------------- | ---------------------------- | -------------------------------- | -------------------------------------------------------------------------- |
| **Avery Chen**    | Platform Administrator       | `platform_admin`                 | Home, Findings, Actions, Setup & config, Operations                        |
| **Sam Whitfield** | Governance Lead              | `governance_admin`               | Home, Findings, Actions, Governance, Model releases, Setup & config        |
| **Lena Fischer**  | Model Governance Lead        | `governance_admin`               | Same as Sam — she exists so release approval can come from a second person |
| **Priya Raman**   | HR Operations Director       | `domain_owner` (Human Resources) | Home, My Data Landscape, Review, Findings, Analyst, Actions                |
| **Diego Alvarez** | Finance Controller           | `domain_owner` (Finance)         | Same as Priya, scoped to Finance                                           |
| **Morgan Ellis**  | Information Security Manager | `remediation_approver`           | Home, Findings, Actions                                                    |
| **Ren Nakamura**  | Internal Auditor             | `auditor`                        | Home, Findings, Actions, Governance, Operations, Audit trail               |

Two deliberate exclusions worth noticing as you switch personas (use the **Sign out** button in the sidebar): the platform admin has **no** review or evidence capabilities — operating the machine does not grant access to what it finds — and the auditor reads everything relevant to oversight but can mutate nothing. Evidence redaction applies to auditors too.

***

## 1. Platform admin — Avery Chen

*Operate the pipeline: setup state, connectivity, scans, fleet, metrics, change notifications, Teams/Exchange.*

Sign in as **Avery Chen**.

### Setup & config (`/setup`)

The **Onboarding checklist** is derived entirely from observable database state — nothing is a stored "wizard step". Eight steps, six required (two marked *optional*): describe the organization, upload governing policy *(optional)*, generate a taxonomy draft, approve domains and information types, assign data-domain owners, connect a source, set governed-location expectations *(optional)*, run the first scan. After seeding, all required steps show **done** and the bar reads "Required steps complete — the platform loop is live". Each step names the capability that gates it (e.g. `governance.owner.assign`) and links to the page where it happens.

Below the checklist:

* **Data handling** — switch the tenant profile between *Metadata only*, *Minimized evidence (recommended)*, and *Enhanced evidence*. Changing it bumps the tenant config version, which participates in the stage cache key, so evidence-bearing stages re-derive on the next scan. This is the one tenant setting writable from this page (`tenant.profile.write`).
* **Runtime & connector** — read-only, boot-bound configuration: environment, auth mode, AI provider, connector mode. Secrets are shown only as configured/not-configured flags (tenant id, client id, client secret, remediation identity). Feature flags are listed the same way.
* **Connectivity test** — click **Test connector**. In mock mode the probe reports the fixture reachable with its scope count. In `graph` mode this same probe was validated once against the live CDX tenant (app-only token + site read — see [cdx-test-runbook.md](cdx-test-runbook.md)); it is honest about failure rather than pretending.
* **Licensing** and **Fleet-health telemetry** — a locally verified signed entitlement (with offline grace) and an opt-out telemetry panel that shows the **exact payload** that would be sent. The payload is strictly allowlisted: licensing and health bands only, no customer content. With no entitlement configured (dev), the card says so.

### Operations (`/operations`)

The **Integration modes** card is the honesty dashboard: each integration is chipped `live`, `mock`, `blocked`, or `disabled` — blocked capabilities state their prerequisites in [permissions-manifest.md](permissions-manifest.md) rather than pretending to work.

**Source scopes** lists the three fixture scopes ("FMD-CDX HR Authoritative — Screening", "FMD-CDX Project Phoenix — Documents", "OneDrive — Priya Raman (fixture)") with mode, coverage state, asset count, and delta-cursor status. Per scope you get three buttons: **Inventory**, **Delta** (enabled once a cursor exists), and **Watch** (creates a change-notification subscription). **Recent campaigns** shows running scans with **Pause**/**Resume**.

Now run the core operator loop:

1. In **Simulate source changes (fixture)** — development-only controls that mutate the in-memory mock tenant — click one of: *Add a new screening file*, *Edit content of Roussel report*, *Rename the merit letter*, *Overshare Walsh report (tenant-wide)*, *Remove label from Quintana report*, *Delete the handbook excerpt*, or *Invalidate delta cursor (410 resync)*.
2. Click **Delta** on the HR scope and watch the pipeline react incrementally in **Metrics & observability**: queue pending / in flight / oldest queue age / dead letters, extraction outcomes, stage latency percentiles (p50/p95/max), and counters. Permission-only changes never re-fetch content — this is test-enforced, not aspirational.
3. Click **Watch** on the HR scope, then under **Change notifications** click **Simulate a change (dev)**. The notification appears in *Recent notifications & freshness* with its outcome and reconcile lag. A notification is treated as a *hint* — the pipeline always re-reads the delta feed, never the notification body. Live Graph subscriptions require a public webhook URL and remain honestly `blocked` without one; the simulator drives the identical code path.

Note a dev-only quirk: fixture mutations live in server memory, so a server restart resets them while the database keeps its observations. A delta scan reconciles.

The **Scanner fleet** card shows registered scanners with capabilities, heartbeat, leased/processed/failed counts; an offline scanner's in-flight work is reclaimed for takeover (`bun run worker` starts one as a separate process if you want to see a second fleet member).

Finally, the **Microsoft Teams** and **Microsoft Exchange** cards: click **Sync Teams** and **Sync Exchange**. Both connectors are built and mock-validated; their live Graph paths are feature-gated (`FMD_FEATURE_TEAMS_CONNECTOR`, `FMD_FEATURE_EXCHANGE_CONNECTOR`). Two identity rules are visible in the tables: a Teams channel file is **canonicalized** to its backing SharePoint document (never a second identity), and an Exchange attachment is a **copy**, so a content match to a SharePoint document is recorded as *similarity*, never merged.

**Work queue** (with dead letters carrying safe diagnostics only) and **Recent log events** round out the page — logs are redacted by default, end to end, and a test scans for leaks.

***

## 2. Governance admin — Sam Whitfield (with Lena Fischer)

*Own the taxonomy and the governed model-release loop.*

Sign in as **Sam Whitfield**.

### Onboarding state

Open **Setup & config** — governance admins share this page — and note the checklist is complete: the seed created the org profile and ingested a policy document through the same governance services the API exposes. There is currently **no UI form** for the profile or policy upload; they are API endpoints (this is honest, not an omission — the UI covers review and approval):

* `GET`/`POST /api/onboarding/profile` — org name, industries, geographies, description (grounds taxonomy generation)
* `POST /api/onboarding/policy-documents` — `{ title, filename, text }`; sections are parsed so regulatory mappings can cite them
* `GET /api/onboarding/policy-documents`

### Governance (`/governance`)

The page motto is the design rule: **generated objects stay drafts until a human approves them.**

* The seeded HR/Finance taxonomy was produced by exactly this loop: the deterministic mock AI provider proposed it as `draft`, and the seed then performed the governance approval, so everything you see carries `approved` status with provenance chips. (Azure OpenAI / Anthropic adapters exist behind config, unexercised by tests — the mock is the default.)
* **Generate draft taxonomy (mock AI)** re-runs generation. It is duplicate-safe: the provider skips domain names that already exist, so on a freshly seeded tenant (where HR and Finance are already approved) it honestly creates zero new drafts rather than duplicating them. Whenever drafts do exist, each draft domain shows an **Approve domain** button and each draft information type an **Approve** button (`governance.taxonomy.approve`), and the Home page shows governance admins a draft-taxonomy count until review is done.
* **Regulatory mapping candidates** appear per domain with jurisdiction, rationale, and — where the mock provider grounded them — a citation into the uploaded policy ("cites policy §…"). The UI is explicit: *mapped as relevant — not a determination of legal compliance*.
* **Domain owners** shows active assignments (Priya Raman → Human Resources, Diego Alvarez → Finance) with attestation state. Assigning owners is API-driven: `POST /api/governance/owners` (`principalId`, `domainId`, `kind: owner|delegate|steward`, optional `attestationDays`), with evidence-ranked suggestions from `GET /api/governance/owner-suggestions?domainId=…`.
* **Release ensemble + re-score** is the quick path: it creates a new ensemble release and re-scores retained features **without any source I/O** (test-enforced). The governed path is the next section.

### Model releases (`/models`) — the governed learning loop

Prerequisite: dataset candidates come from owner review decisions, so run part of the [domain owner walkthrough](#3-domain-owner--priya-raman) first (or after) — every confirmation ("mine" in any form) and every "not a match" decision Priya makes lands here as a candidate (domain routing and deferrals do not). Never silent training.

1. **Dataset candidates** — approve or reject each pending candidate (a positive or counterexample derived from a review decision, with the model's confidence at the time).
2. **Snapshot a dataset** — name it (e.g. `hr-baseline-v1`), pick an eval fraction, and choose the consent scope: *Tenant internal only* or *Vendor shareable (explicit)*. The split is cluster-aware: near-duplicate clusters stay on one side to prevent leakage.
3. **Propose an ensemble release** — tune the candidate decision threshold, select the snapshot to evaluate against, write a rationale, and click **Propose & evaluate**. The release row shows candidate-vs-current precision, recall, and F1 on the held-out labels.
4. **Separation of duties — this is the point where Sam must stop.** The server refuses self-approval: *"Separation of duties: a release must be approved by someone other than its proposer."* Sign out and sign in as **Lena Fischer**, the second governance admin. On `/models`, the evaluated release now shows **Approve** / **Reject**.
5. Once approved, **Promote & rescore** activates the new ensemble and re-scores without touching sources; a promoted release can later be **rolled back** (forward-applied, history preserved).

This whole loop also ran end-to-end on live CDX data in a prior validated session (real HR candidates, SoD blocking self-approval, 174 assets rescored) — see [STATUS.md](../STATUS.md).

***

## 3. Domain owner — Priya Raman

*The heart of the product: "Is this yours?"*

Sign in as **Priya Raman**.

### Home (`/home`)

Her HR domain card shows confirmed assets, AI-inferred candidates, decisions waiting (with a link into Review), and what changed in the last 7 days, plus open findings, the attestation state, and source coverage for HR-governed scopes. Note the caption under coverage: *a low number of findings can mean a clean environment or incomplete coverage — they are not the same thing.*

### My Data Landscape (`/landscape`)

Aggregate before detail: information-type cards (assets / confirmed / unlabeled, across N locations) that filter the asset table when clicked, and a full-text search box over titles, keyphrases, and excerpts. Every row drills into asset detail.

### Review (`/review`) — keyboard-first

The queue shows prioritized candidates with the question **"Is this yours?"**. Keyboard shortcuts (verified in `packages/web/src/pages/Review.tsx`; never active while typing in an input):

| Key       | Decision                 | Meaning                                     |
| --------- | ------------------------ | ------------------------------------------- |
| `1`       | Yes — this is mine       | confirm type and domain                     |
| `2`       | Mine, but different type | opens a picker of approved types            |
| `3`       | Another domain           | opens a domain picker to route to its owner |
| `4`       | Mine, not sensitive      | meaning yes, sensitivity no                 |
| `5`       | Not a match              | records a counterexample                    |
| `6`       | Need more evidence       | defers the decision                         |
| `j` / `k` | —                        | next / previous candidate                   |

Each card explains itself: signed evidence weights with provenance chips, exposure chips (e.g. tenant-wide sharing), cluster context ("exact duplicate family · N items"), label state, a confidence bar, and — under Priya's `asset.evidence.excerpt.read` capability — bounded, redacted excerpts. Excerpt access is audited.

Confirm the top candidate (the README demo starts with the Walsh screening copy found in the project collaboration site) and watch the **propagation acknowledgment**: related byte-identical and same-template candidates are reprioritized with stated reasons. Propagation never auto-confirms — a human decided one asset, and the system only *re-ranks* its relatives. Your decision also just created a dataset candidate for the governance admins' model-release loop.

### Asset detail (`/assets/:id`)

From Review or Landscape, open any asset:

* **Classification & provenance** — assertion chains with confidence, explanation, and human state; the original model inference is never destroyed by a correction.
* **Risk** — named components with notes, policy version, and computation time.
* **Connections** — a bounded graph plus a synchronized table where *every connection has a reason*.
* **Semantically similar documents** — nearest neighbors by embedding cosine (approximate vector search, deterministic feature-hash embeddings by default), filtered to what Priya is allowed to see.
* **Access & label** — permission grants with an explicit completeness caveat: absence of a grant here is not evidence it does not exist.
* **History** — location and version timelines; **Redacted evidence excerpts** where held; findings with a **Draft an action →** link.

### Analyst (`/analyst`)

Ask the canonical question (also available as an example button):

> Show employee background content on SharePoint sites that do not appear to be owned by HR.

The question becomes a typed, server-validated plan — never free-form database access — and the tenant/domain scope is injected server-side so a plan cannot widen it (see [ADR-0009](adr/ADR-0009-analyst-typed-plan.md)). The answer arrives with its interpretation (marked as AI output to verify against the rows), scope note, evidence rows linking to assets, and a **coverage & freshness** section including blind spots.

### Draft a label action (`/actions`)

In **Draft an action**: paste an asset ID from asset detail or findings (`ast_…`), choose *Apply Purview sensitivity label*, pick a label from the catalog, and write a business/policy justification (minimum 10 characters — the button stays disabled without it). **Preview & draft**, then **Submit**. The status moves to awaiting approval — and stops there: Priya can draft and submit, but approval requires Morgan. Requesters can never approve their own actions.

**Isolation check:** sign in as **Diego Alvarez**. Same navigation, same pages — zero HR data. His landscape, queue, and Analyst answers are Finance-scoped, and probing an HR asset URL returns 404 with no existence leak (test-enforced).

***

## 4. Remediation approver — Morgan Ellis

*Approve or reject governed changes. Two minutes of clicking, and every click is audited.*

Sign in as **Morgan Ellis**. Home shows an **Approvals** card counting actions awaiting approval, linking to `/actions`.

The **Action ledger** shows each request with its target, kind, current → desired state, requester, justification, status, and verification result. On a row in `awaiting_approval`, Morgan sees **Approve** and **Reject**. Approve Priya's label action and watch the state machine finish: execute once (idempotent — duplicate delivery produces one mutation, test-enforced), then **verify against the source**, then the related label-gap finding resolves on recompute. If the target changed since preview, the action lands in `drifted` and must be **re-previewed** — no stale writes. The full state machine is [ADR-0010](adr/ADR-0010-remediation-state-machine.md).

Honesty note: in this demo the label write executes against the mock fixture. The **live** Purview label write path is implemented but feature-gated (`FMD_FEATURE_PURVIEW_LABEL_WRITE` plus a separate remediation identity, `FMD_GRAPH_REMEDIATION_*`); it refuses to run when unconfigured rather than pretending. Morgan holds `action.approve`/`action.cancel` tenant-wide plus asset metadata read — he approves, but he does not draft actions or browse evidence excerpts.

***

## 5. Auditor — Ren Nakamura

*Read everything that matters for oversight; change nothing.*

Sign in as **Ren Nakamura**.

**Audit trail** (`/audit`) is the page only this role sees: an append-only, hash-chained business audit with a category filter — `auth`, `config`, `governance`, `scan`, `evidence_access`, `review`, `analyst`, `action`, `model`. After the walkthroughs above, the categories tell the whole story: Priya's review decisions and excerpt accesses, Sam's proposal and Lena's approval, Morgan's approval and the execution/verification, Avery's scans and config changes. Details are redaction-safe JSON — evidence redaction applies to auditors too, and the chain is verifiable.

Ren also gets read access across **Governance** (taxonomy, owners, provenance), **Operations** (scopes, campaigns, fleet, metrics, notifications, work queue), **Findings**, and the **Actions** ledger. The pages render their controls, but every mutation — starting a scan, syncing Teams, approving anything — is refused by the server: Ren's capabilities (`audit.read`, `operations.read`, read-only governance/findings/scan) contain no writes, and authorization is deny-by-default.

***

## Where to next

* [STATUS.md](../STATUS.md) — the honest capability table: built + validated live (CDX), built + mock-validated + live-gated, and not built
* [architecture.md](architecture.md) — how the modules you just used fit together
* [permissions-manifest.md](permissions-manifest.md) — exact Microsoft Graph permissions each live capability needs
* [cdx-test-runbook.md](cdx-test-runbook.md) — the live-tenant validation record and plan
* [threat-model.md](threat-model.md) — why the capability exclusions above look the way they do
