Documentation Remediation Plan v2 (living document)¶
Purpose: Establish whether the IdentityAtlas documentation is (a) complete relative to shipped features and (b) usable for the stated goals — and close the gaps in a structured way. Builds on
docs-gap-audit.md. Baseline: edge (main@d4afdb9f, v5.441). Branch:feature/docs-gap-audit(local). v2 change (after dual-voice review): usability is measured with journey walkthroughs (primary instrument); the coverage grid is a supporting inventory. See the Review appendix at the bottom. Execution (2026-07-15): all 34 backlog items (BL-1..BL-34) applied — 20 docs updated + 6 new guides (dashboard, contexts, effective-access, custom-connector, data-tab, authentication), added to the mkdocs nav. See the changelog fragmentchanges/docs-gap-audit.md. Reconciliation (2026-07-16): re-audited againstmainafter the 34-item run. Two Tier-A leaks remained and are now fixed: the risk-tier table indocs/api/risk-scores.md(still showedCritical ≥80; code =≥90/≥70— audit A1, outside the original BL-11/12 scope) and the plugin catalogue incontext-redesign.md §4.2(missing 5 shipped plugins, mis-nameddepartment-tree, and listing the never-shippedapp-grouping-by-pattern/business-process-llm— audit A4). A3 (/perf/slowest→/perf/slow) is fixed in #804 (with #802/#803).
Why v2¶
v1 measured only presence per feature (✅/🟡/⭕). An independent review with four voices (three Claude subagents + Codex, all against the real files) unanimously concluded: that answers goal (a) but not goal (b). A cluster can go fully green and still ship docs a real user gets stuck on (present ≠ correct ≠ findable ≠ executable). v2 fixes this with two instruments and a few process gates.
The two instruments¶
Instrument A — Coverage grid (supporting: completeness)¶
A status cell per feature × doc-type axis. Answers: does correct documentation exist for this feature? This is inventory/traceability, not proof of usability.
Instrument B — Journey walkthroughs (primary: usability)¶
A small set of named persona tasks walked end-to-end with the docs only, by someone who did not write the doc in question. This is the acceptance test for "usable for the stated goals". A cluster/journey is only done once the walkthrough passes.
Doc-type axes (4, after review)¶
Analyst/User is primary (P1). For this product (role-mining / access-governance) the core end user is the Analyst/Reviewer who investigates and attests — not a generic "how do I use this screen" reader.
| Axis | For whom | Home base | Prio |
|---|---|---|---|
| Analyst/User | Role-mining/attestation end user: "who can access X and why", "certify this access", "find over-privileged identities" | docs/ui/, docs/quickstart.md, docs/concepts/ (task-oriented) |
P1 |
| Operator | Install, configure, run, recover | docs/admin/, docs/reference/config.md, deployment/docker, security, operations |
P2 |
| Contributor | Build on the repo: architecture, conventions, extension points, tests | docs/architecture/, building-a-crawler.md, CLAUDE.md files |
P2 |
| API consumer | Integrates via the API without touching the repo: contracts, auth, examples, errors, versioning | docs/api/, openapi.yaml, docs/reference/sql-views.md |
P1 for push/ingest, otherwise P2 |
docs/concepts/ may fill the Contributor cell, but the Analyst/User cell requires a task-/screen-oriented guide; a stale concept doc only counts against Contributor.
Status rubric (after review)¶
Two independent dimensions per cell — coverage and trust — so "wrong" no longer disappears into "partial":
| Symbol | Meaning |
|---|---|
| ✅ | Present and meets the usability rubric (see below) |
| 🟡 | Present but incomplete for the task (missing steps/examples/prereqs) |
| ⭕ | Missing |
| ❌ | Present but factually wrong / misleading — sorts before ⭕ |
| P0 | ❌ that is misleading and security/privacy-sensitive (e.g. risky-consent egress, wrong risk thresholds) → release-blocking |
| — | n/a for this axis |
Usability rubric — a cell is only ✅ if the doc: is accurate against the baseline commit · is findable from the logical starting point · states prerequisites · contains executable steps · has copy-paste-complete examples (incl. auth/base-url/expected output) · shows the expected result · covers common error paths · has been independently tested.
Verification gate (mandatory before every decision)¶
The grids are pre-filled from an AI audit and count as UNVERIFIED. Before deciding on an action for a cell (write/fix/update):
1. Open the cited code file and the cited doc; confirm the symbol/fact.
2. Mark the cell verified (with checker + date) instead of inherited.
3. Stamp the cell with the baseline commit it was verified against.
(The review already did this for 2 cells — tiers=90/70 and /perf/slow+/perf/toggle — and both held. Verification is therefore cheap, not superfluous.)
Instrument B — Journeys (primary)¶
Candidate journeys (to be finalized in the first session). Each maps to the clusters it touches and has a pass/fail from a doc-only walkthrough:
| # | Journey (persona) | Touches clusters | Core question |
|---|---|---|---|
| J1 | Zero-to-first-insight (Analyst): docker compose up → first crawler → data on Dashboard → first finding in Matrix |
1,7,8,4,2 | Does a new evaluator get from zero to first insight with docs only? |
| J2 | Assess a risk score (Analyst): read an identity risk badge → understand the tier → make an analyst override | 5,4 | Can the analyst understand the score and act on it? |
| J3 | "Who can access X and why" (Analyst): effective-access / matrix investigation | 2,10,4 | Can the analyst prove and explain access? |
| J4 | Certify access (Analyst): governance/attestation flow | 6,4 | Can an access review be completed? |
| J5 | Set up an Entra crawler (Operator): choose phase toggles deliberately | 7,8 | Does the operator know which toggles are needed and what each costs? |
| J6 | Push data via custom connector (API consumer): with docs/api only |
9 | Can an integrator push data + rotate a key without the repo? |
| J7 | Build a crawler (Contributor): end-to-end from the docs | 7,9,10 | Can a contributor build a crawler with the docs only? |
| J8 | Create + reuse a Context (Analyst): tree, plugin run, "Filter matrix" | 3,2 | Does the newest, most complex surface work end-to-end? |
| J9 | Administer the deployment (Operator): Admin panel, Auth/Roles, export, retention, danger-zone | 8 | Can an operator manage the app doc-only? |
| J10 | Understand + query the data model (Contributor): schema, valid enums, SQL views | 10 | Can a contributor understand the data model and query it doc-only? |
Pilot one complex flow first (recommended by the review) — J8 or J1 — as a task-oriented guide + usability test, before scaling up the format.
Walkthrough results table (per journey):
| Journey | Persona | Completed? (y/n) | First blocker | Where you had to leave the docs | Backlog item |
|---|---|---|---|---|---|
| J1 (pilot, 2026-07-15) | Analyst/Operator | NO (doc-only) | Entra setup: entra-id.md mentions "fill in Tenant/Client ID + Secret" but not which Graph permissions are needed; that list lives in reference/troubleshooting.md (wrong home) and quickstart.md doesn't link to entra-id.md. (Creating an App Registration = known Entra knowledge, not a doc gap.) |
(1) permission list → move out of troubleshooting; (2) the dashboard landing page has no doc; (3) scoping the Matrix is only possible via the undocumented Filter Wizard | BL-1..BL-7 |
| J8 (2026-07-15) | Analyst | NO (doc-only) | There is no user guide for the Contexts screen; overview.md doesn't mention Contexts. The only docs (context-redesign.md + context-redesign-ui.md) are architecture specs headed "not yet implemented" while the feature is live → misleading. | Immediately — the analyst has no step guide at all and must fall back entirely on the UI itself | BL-8, BL-9, BL-10 |
| J2 (2026-07-15) | Analyst | ⚠️ PARTIAL | The steps are documented, but risk-scoring/overview.md+design.md give wrong tier bounds (Critical ≥80/High ≥60 instead of code ≥90/≥70; Medium upper bound also wrong). ui/overview.md is correct but contradicts them → the analyst mis-classifies every badge. | For the correct tier meaning you have to read tiers.js | BL-11, BL-12, BL-13 |
| J3 (2026-07-15) | Analyst | ⚠️ PARTIAL | "Who can access X" direct works; effective/inherited access exists only as a design spec, the endpoints are in no API reference, no task guide. matrix.md still teaches the retired O badge; api/matrix.md+entities.md mention the v4 relic mat_UserPermissionAssignments. | To resolve the badge model + effective-access you had to read migrations + engine.js | BL-14, BL-15, BL-16 |
| J4 (2026-07-15) | Analyst/Reviewer | NO (doc-only) | No product surface to execute a certification (read-only mirror), and the docs don't say so. On top of that governance-model.md+api/governance.md use the retired assignmentType='Governed' (a boolean flag since migration 047) and wrong table names (GraphCategories→GovernanceCategories). Refutes the audit (which called governance "well covered"). | To find "how do I record a decision" you had to grep routes/ | BL-17, BL-18, BL-19 |
| J5 (2026-07-15) | Operator | ⚠️ PARTIAL | The setup flow (wizard) is documented, but a deliberate toggle choice fails: 6 shipped toggles are missing from the flags table (BL-3), no "which toggle for which purpose" help, permissions elsewhere (BL-1), diagram wrong (BL-2). | For toggle effects + permissions you have to leave the docs | BL-1, BL-2, BL-3, BL-20 |
| J6 (2026-07-15) | API consumer | ⚠️ PARTIAL | Rotating works; pushing the first record fails end-to-end: no docs/sync/custom-connector.md, no runnable curl under docs/api/ (the only one is in app/api/CLAUDE.md), and ingest-api.md is stale (response shape + the assignmentType enum list retired values → 400). | For a key + real payload/response you had to read the handler + openapi.yaml | BL-21, BL-22, BL-23 |
| J7 (2026-07-15) | Contributor | ✅ PASS | None — a contributor can scaffold a crawler doc-only from building-a-crawler.md (+ architecture + CLAUDE.md). Only 2 small cross-doc contradictions (stale getConfigSecret signature; wizard import style). | Nowhere (only a cross-check on 2 contradictions) | BL-24, BL-25, BL-26 |
| J9 (2026-07-15) | Operator | NO (doc-only) | Only Auth/Roles (step b) works doc-only. overview.md lists 3 admin sub-tabs instead of 10; the Data tab (export/retention/danger-zone) exists nowhere in the docs, and audit-history.md refers to a non-existent "History Retention" sub-tab. | For 6 of the 10 tabs + retention + danger-zone you had to read adminTabs.js/maintenance.js/curatedData.js | BL-31, BL-32, BL-33, BL-34 |
| J10 (2026-07-15) | Contributor | ⚠️ PARTIAL | Understanding the schema + valid enums works (data-model.md is correct — the audit suspicion was unfounded). But querying fails: sql-views.md examples select non-existent columns, call matviews "planned" (materialized since migration 013), and assignment-model-redesign.md still says "no code yet". | For the real view columns you had to read the migrations | BL-27, BL-28, BL-29, BL-30 |
J8 verdict: straight FAIL. A primary, always-visible tab (Contexts) has zero user documentation, and the only existing docs claim the feature doesn't exist. This is the sharpest confirmation of audit finding A2 + B1, now through the journey lens: the grid alone might read this as "🟡 spec exists"; the journey shows the spec actively misleads the user.
J1 verdict: the demo path (Load Demo Data) works and is well documented. But the real first-insight path fails doc-only on three points: (a) connecting your own Entra tenant requires leaving the docs for the permission list; (b) the Dashboard landing page you land on has no user doc; (c) scoping the Matrix to reach a finding is only possible via the Filter Wizard, which isn't documented (overview.md still describes the removed "User Limit Slider"). Extra finding: index.md and quickstart.md contradict each other about whether .env is needed.
Prioritization & order (after review)¶
- P0 first — misleading/security-sensitive docs (Tier A +
risky-consentegress). Wrong > missing. - Then the first-run journey (J1) — every user hits this on day 1; a stranded day-1 user never reaches Contexts.
- Then reorder by journey impact = task frequency × harm-on-error × centrality — not by gap size.
Priority rubric per backlog row: audience (P1>P2) × wrongness (❌/P0 > ⭕ > 🟡) × usage frequency.
Backlog format (after review)¶
One row per gap with actionable fields:
feature-ID | doc-type | action | prio | owner | estimate | target-doc | definition-of-done | status
- Actions:
write·update·fix·reduce/hide/deprecate(change the product so the doc isn't needed) ·accept. acceptrequires: reason + affected personas/tasks + approver + residual risk + review-by date. No silent drop.- Definition-of-done is tied to a task: "journey Jx passes doc-only", not "page exists".
Recurrence / freshness (after review)¶
- Stamp every cell with the baseline commit; staleness becomes visible (edge is already on v5.443 vs baseline v5.441).
- Re-audit trigger: a PR that touches a listed code path must update that cluster grid; consider extending the existing
openapi.drift.testto the read API and a "docs-touched?" item in the PR/release template. Otherwise the grid re-rots just like the docs now (your own A2).
Overall exit criterion¶
The whole thing is done when: 1. All P0/❌ wrong docs are fixed. 2. Every persona journey (J1–J10) passes a doc-only walkthrough. 3. No ⭕ on P1 cells (Analyst/User + API consumer for push/ingest).
Working method per cluster¶
- Choose/pin the journey(s) that touch this cluster.
- Verify the grid cells you're going to handle (verification gate).
- Fill the backlog with actionable rows (owner + DoD).
- Run the walkthrough; log blockers.
- Mark the cluster done once its journey passes doc-only and P1 cells no longer have any ⭕.
Progress¶
| # | Cluster / track | Status |
|---|---|---|
| 0 | Non-feature docs (getting-started, troubleshooting/errors, "why" concepts, limitations) | 🔄 partly via J1 (cells ✔) |
| 1 | Getting started & Dashboard | ✅ fixes applied — via J1 (BL-4,5,7) |
| 2 | Matrix | ✅ fixes applied — via J1+J3 (BL-6,14,15,16) |
| 3 | Contexts (screen + plugins + API) | ✅ fixes applied — via J8 (BL-8,9,10) |
| 4 | Entities & detail pages | ⬜ to do (touched by J2/J3/J4; findings in 2/5/6) |
| 5 | Risk scoring & AI/LLM | ✅ fixes applied — via J2 (BL-11,12,13) |
| 6 | Governance / business roles | ✅ fixes applied — via J4 — refutes the audit (BL-17,18,19) |
| 7 | Sync sources / crawlers | ✅ fixes applied — via J1+J5+J7 (BL-1,2,3,20,24,25,26) |
| 8 | Admin & settings (incl. Admin nav shell / 10 sub-tabs) | ✅ fixes applied — via J9 (BL-31,32,33,34) |
| 9 | Integration & ingest API (owner of Custom Connector) | ✅ fixes applied — via J6 (BL-21,22,23) |
| 10 | Platform & data model (owner of context schema) | ✅ fixes applied — via J10 (BL-27,28,29,30) |
| J | Journeys J1–J10 (primary instrument) | ✅ all 10 executed — J7 PASS · J2/J3/J5/J6/J10 PARTIAL · J1/J4/J8/J9 FAIL (doc-only) |
MECE fixes applied: Custom Connector is owned by Cluster 9 (stub reference in 7). The v6 context screen/API belongs in Cluster 3, the schema/tables in Cluster 10. "All 10 Admin sub-tabs" gets one canonical row in Cluster 8. The false "DevOps" marketing claim is captured in Cluster 0 (not silently dropped despite marketing being out of scope).
Cluster 0 — Non-feature docs (new, after review)¶
Code-as-ground-truth misses what isn't a feature but is asked about most.
✔ = verified against code/docs on 2026-07-15 (baseline
d4afdb9f). Unmarked cells are still inherited from the audit.
| Category | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Getting-started / onboarding (J1) | 🟡 ✔ (demo ok; own-tenant fails) | 🟡 ✔ | — | — |
| Troubleshooting / error messages ("it isn't syncing — why") | 🟡 ✔ (exists, thin/wrong home) | 🟡 ✔ | 🟡 ✔ | ⭕ ✔ |
| Concepts / "why" (mental models) | 🟡 | — | 🟡 | — |
| Limitations / known-issues (incl. false "DevOps" source claim) | ⭕ | ⭕ | ⭕ | ⭕ |
Backlog: (to be filled during review)
Cluster 1 — Getting started & Dashboard¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Quickstart / first crawler | 🟡 | 🟡 | — | — |
| Dashboard landing page | ⭕ | — | 🟡 | — |
| Trends tab | ⭕ | — | ✅ | — |
Backlog:
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-4 | update: link entra-id.md (+ demo vs own-tenant choice) from "What's Next" |
P1 | tbd | docs/quickstart.md |
J1 step 2: new user finds the Entra setup without guessing |
| BL-5 | write: user doc for the Dashboard landing page (cards, counts, "is my data in yet?") | P1 | tbd | docs/ui/ (new) |
J1 step 3: user understands what they see doc-only |
| BL-7 | fix: .env contradiction between index.md (download .env.example) and quickstart.md ("just Docker") |
P2 | tbd | docs/index.md + docs/quickstart.md |
One consistent install story |
Cluster 2 — Matrix¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Base view (badges, staircase, DnD, IST-SOLL, export) | 🟡 | — | ❌ ✔ (matrix.md teaches the retired O badge; api/* mentions the v4 relic mat_UserPermissionAssignments) |
— |
| Filter Wizard (3-step + saved matrices) | ⭕ | — | ⭕ | ⭕ |
| Roll-up-by-attribute | ⭕ | — | ⭕ | — |
| Orientation / rotated view | ⭕ | — | ⭕ | — |
| Scope statistics | 🟡 | — | ✅ | — |
| Matrix interaction endpoints (preview, hierarchy-paths, saved-filters) | — | — | — | ⭕ |
Backlog:
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-6 | write Filter Wizard/saved-matrices/roll-up + fix: remove the outdated "User Limit Slider" from overview.md | P1 | tbd | docs/ui/overview.md (+ matrix) |
J1 step 4: analyst can scope the matrix and reach a finding doc-only |
| BL-14 | write task guide "Who can access resource X, and why" (direct + inherited holders + access path) | P1 | tbd | docs/guides/effective-access-howto.md (new) |
J3: analyst answers "who + why" doc-only |
| BL-15 | update: document effective-access endpoints (GET /resource/:id/effective-access, /principal/:id/…, POST /effective-access/resolve) |
P1 | tbd | docs/api/matrix.md |
Endpoints findable outside the design spec |
| BL-16 | fix the stale badge table (drop O/Governed, add DirectoryRole(Eligible)) + replace mat_UserPermissionAssignments with vw_ResourceUserPermissionAssignments |
P2 | tbd | docs/architecture/matrix.md + docs/api/matrix.md + docs/api/entities.md |
No doc contradicts the 3-badge model; no v4 relic |
Cluster 3 — Contexts (screen + plugins + API)¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Context model (synced/generated/manual × 4 targets) — screen/behavior; schema→C10 | ⭕ ✔ | — | ❌ ✔ (spec says "not yet implemented" — misleading) | — |
| Contexts screen (tree, drag-reparent, wizard, Filter-matrix) | ⭕ ✔ | — | ❌ ✔ (spec says "not yet implemented") | — |
| Plugin catalogue (10; 5 undocumented/mis-named) | ⭕ | ⭕ | 🟡 | — |
risky-consent external threat-feed egress |
— | P0 | ❌ | — |
Tags as context (contextType='Tag') |
🟡 (stale GraphTags ref) | — | 🟡 | — |
| Admin → Plugins sub-tab | ⭕ | ⭕ | 🟡 | — |
Context write + plugin API (/contexts writes, /context-plugins/*) |
— | — | — | ⭕ |
Backlog:
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-8 | fix (❌): remove the "not yet implemented" status from both context specs; mark as implemented (A2) | P1 | tbd | docs/architecture/context-redesign.md + context-redesign-ui.md |
Status matches the shipped code |
| BL-9 | write: Analyst user guide for the Contexts screen (tree, synced/generated/manual, New-Context wizard, Run now, "Filter matrix") | P1 | tbd | docs/ui/ (new) |
J8 passes doc-only |
| BL-10 | update: overview.md nav list — add Contexts, remove the removed "Org Chart" (findability) |
P1 | tbd | docs/ui/overview.md |
New user finds the Contexts tab via the docs |
Cluster 4 — Entities & detail pages¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| List+detail: Principals / Identities / Resources / Systems | 🟡 (stale names/OrgUnits) | — | ✅ | ✅ |
| Entity detail tabs (attrs / graph / timeline / risk) | 🟡 (arch-only) | — | ✅ | — |
| Account-linking | ✅ | — | ✅ | — |
| Secondary detail endpoints (contexts/assignments/business-roles/oauth2-grants) | — | — | — | ⭕ |
Backlog: (to be filled during review)
Cluster 5 — Risk scoring & AI/LLM¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| 4-layer engine + risk tiers | P0 (cutoffs WRONG) | — | ❌ (cutoffs WRONG) | — |
| Classifiers (generation flow) | 🟡 | — | 🟡 (stubbed PS path) | — |
| Analyst overrides | ✅ | — | ✅ | — |
| AI-agent scoring | 🟡 | — | 🟡 (stale refs) | — |
| LLM classifier generation (Node vs. PS stub) | — | 🟡 | 🟡 | — |
| Risk-profile/classifier API (~20 endpoints) | — | — | 🟡 | ⭕ |
| Admin → Risk-Scoring wizard & LLM settings | ⭕ | 🟡 | 🟡 | — |
Backlog (via J2):
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-11 | fix (P0): tier bounds → Critical 90–100, High 70–89, Medium 40–69 (matches tiers.js) |
P0 | tbd | docs/risk-scoring/overview.md |
Analyst derives the same tier from overview.md as the badge shows |
| BL-12 | fix (P0): same tier correction | P0 | tbd | docs/risk-scoring/design.md |
No doc names a cutoff that contradicts tiers.js |
| BL-13 | update: override = integer −50..+50, reason 3–500 chars | P2 | tbd | docs/risk-scoring/overview.md |
Analyst gets no unexplained 400 |
Cluster 6 — Governance / business roles¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Business roles / governed assignments / resource grants | 🟡 ✔ | — | ❌ ✔ (docs use the retired assignmentType='Governed'; a boolean flag since migration 047) |
— |
| Certifications (executing) | ❌ ✔ (docs imply a flow; the product is a read-only mirror) | — | 🟡 ✔ | — |
| Assignment policies / requests | — | — | ✅ | — |
| IGA platform mapping (Entra/Omada/SailPoint) | — | — | ✅ | — |
| Governance-summary / review-compliance API | — | 🟡 ✔ | 🟡 ✔ (table names GraphCategories→GovernanceCategories wrong; endpoints not in api/governance.md) |
⭕ ✔ |
Backlog (via J4 — refutes the audit's "governance well covered"):
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-17 | write: "Identity Atlas reports on but does not execute certifications — decisions are made in the source IGA (Entra Access Reviews) and mirrored read-only"; + where the reviewer actually looks | P0 | tbd | docs/concepts/governance-model.md + docs/ui/overview.md |
J4: reviewer knows doc-only where decisions are made; no dead-end |
| BL-18 | fix (P0): remove the retired assignmentType='Governed' everywhere; replace with the governed=true flag |
P0 | tbd | docs/api/governance.md + docs/concepts/governance-model.md |
Grep for 'Governed' as a type = 0; matches migration 047 + ingest guard |
| BL-19 | fix table names (GovernanceCategories) + document GET /governance/summary & /review-compliance |
P1 | tbd | docs/api/governance.md |
Names + endpoints match the code |
Cluster 7 — Sync sources / crawlers¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Entra ID (6 phase toggles + AI-agent classification) | 🟡 ✔ | 🟡 ✔ (permissions are in troubleshooting, not setup) | ❌ ✔ (diagram shows the retired Owner type; 6 toggles missing from the flags table) |
— |
Azure RM (onlyEntraPrincipals) |
🟡 | 🟡 | ✅ | — |
| midPoint | ✅ | ✅ | 🟡 (streaming/dept dev-only) | — |
| Omada | ✅ | ✅ | ✅ | — |
| CSV import (new templates) | 🟡 | 🟡 | 🟡 | — |
| Custom Connector (push-mode) → owner: Cluster 9 | ↪ C9 | ↪ C9 | ↪ C9 | ↪ C9 |
| Wizard/plugin architecture + live discovery | — | — | ✅ | — |
Backlog:
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-1 | update: name the required Graph permissions in the setup section (currently only in troubleshooting.md). Creating an App Registration is assumed known → not documented. |
P1 | tbd | docs/sync/entra-id.md |
J5 passes doc-only; J1 step 2 no longer leaves the docs for the permission list |
| BL-2 | fix (❌): "What Gets Synced" diagram — remove the retired Owner assignment type; show ownership as Direct on GroupOwnership |
P1 | tbd | docs/sync/entra-id.md |
Diagram matches assignmentTypes.guard (Direct/Indirect/Eligible) |
| BL-3 | update: 6 shipped toggles in the flags table (SyncOAuth2Grants/AppRoles/AppPermissions/AppOwners/PrincipalRelationships/DirectoryRoles) + AI-agent classification |
P2 | tbd | docs/sync/entra-id.md |
Flags table = crawler param block |
| BL-20 (J5) | write: "which toggle for which purpose" decision aid (when to enable SPs/PIM/app-roles/owners + costs) | P2 | tbd | docs/sync/entra-id.md |
Operator deliberately chooses the right toggles doc-only |
| BL-24 (J7) | fix: stale getConfigSecret signature (crawlerId, key) → (configId) (matches crawlerSecrets.js) |
P2 | tbd | tools/crawlers/CLAUDE.md (~line 178) |
Contributor calls getConfigSecret correctly |
| BL-25 (J7) | fix: wizard import style → @ui/ alias instead of ../../../app/ui/src/… traversal (two docs contradict each other) |
P2 | tbd | docs/sync/building-a-crawler.md |
Both crawler docs teach the same import convention |
| BL-26 (J7) | update: minimal example to dot-source shared/Invoke-CrawlerIngest.ps1 (Update-CrawlerProgress) instead of hand-rolled |
P3 | tbd | docs/sync/building-a-crawler.md |
Example matches the "dot-source shared helpers" rule |
Cluster 8 — Admin & settings¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Admin nav shell / 10 sub-tabs (canonical inventory row) | ❌ ✔ P0 (overview lists 3, really 10; Data tab missing) | ❌ ✔ | 🟡 | — |
| Authentication / Roles & Permissions editor | ⭕ (no UI walkthrough) | ✅ ✔ (permissions.md covers role→permission + bootstrap/lockout) |
🟡 | — |
| Updates (auto-update, channel, history) | ✅ | ✅ | ✅ | — |
| Data tab (PowerQuery ✅; curated import/export ⭕, retention ⭕, danger zone ⭕) | 🟡 ✔ | ⭕ ✔ | 🟡 | — |
| Performance | ✅ | 🟡 | ❌ (perf-endpoint drift /perf/slowest→/perf/slow) |
❌ |
| Crawler config audit/reset | — | ⭕ | — | ⭕ |
| About / SBOM / license | 🟡 | ✅ ✔ (permission catalogue is correct) | ✅ | — |
Backlog (via J9):
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-31 | fix (P0): Admin sub-tab inventory — replace "3 tabs" with all 10 from adminTabs.js, each with its purpose + gating permission |
P0 | tbd | docs/ui/overview.md |
J9 step a: operator finds every tab doc-only |
| BL-32 | write (P0): Data-tab guide — curated export/import (gates), history-retention (180d default, 0=off), Danger Zone/clean-database (what it wipes/keeps, rate-limit, admin.systems) |
P0 | tbd | docs/admin/data-tab.md (new) |
J9 steps c+d doc-only |
| BL-33 | fix: retention path — Admin > History Retention doesn't exist → "section under Admin → Data" |
P1 | tbd | docs/architecture/audit-history.md (~line 132) |
No doc refers to a non-existent sub-tab |
| BL-34 | update: Authentication/SSO + Roles setup as a step-by-step how-to (currently reference prose only) | P2 | tbd | docs/admin/authentication.md (new) or permissions.md |
J9 step b has a linked how-to |
Cluster 9 — Integration & ingest API (owner of Custom Connector)¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
Ingest API (/ingest/*, openapi.yaml) |
— | ✅ | ✅ (openapi) | 🟡 ✔ (ingest-api.md stale: response shape + retired assignmentType enum → 400) |
| Custom Connector (push-mode) — setup + map | ⭕ | ⭕ ✔ | ✅ | ⭕ ✔ (no runnable example under docs/api/) |
| API-key management / rotation / audit | — | 🟡 ✔ (rotating works; obtaining a key is not self-service) | ✅ | 🟡 ✔ |
| Drift-guard scope (which routes are guarded) | — | — | 🟡 (only in a test) | — |
Backlog (via J6):
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-21 | write a push-mode integrator guide: "you get the key from an admin / Admin→Crawlers (not self-minted)" + one runnable curl (base-url, Bearer fgc_…, real payload, 201 response) + rotate snippet |
P1 | tbd | docs/sync/custom-connector.md (new) |
J6 walkable start-to-finish doc-only |
| BL-22 | fix: stale response + enum tables (response without syncId/errors; assignmentType = only Direct/Indirect/Eligible; current relationshipTypes) |
P1 | tbd | docs/architecture/ingest-api.md |
Doc matches handlers.js + openapi.yaml |
| BL-23 | update: one authoritative external base URL (PUBLIC_BASE_URL) for proxied/TLS |
P2 | tbd | docs/api/index.md |
Integrator knows the correct base URL |
Cluster 10 — Platform & data model (owner of context schema)¶
| Feature | Analyst/User | Operator | Contributor | API consumer |
|---|---|---|---|---|
| Data model (v3.1 + v6 contexts, schema/tables) | — | — | ✅ ✔ (data-model.md correct; the assignmentType collapse is right — the audit suspicion was unfounded) |
— |
| SQL views (query surface) | — | — | ❌ ✔ (examples select non-existent columns; matviews called "planned") | 🟡 ✔ |
| Effective-access engine (P1 direct + P2 inherited) | — | — | ✅ | — |
| Soft-delete | — | 🟡 | ✅ | — |
| Audit-history / timeline | 🟡 | — | ✅ | — |
| Assignment-model collapse (migrations 044–049) | — | — | ❌ ✔ (status "no code yet" — is shipped) | — |
| Deployment / Docker / Azure | — | ✅ | ✅ | — |
| Scaling | — | 🟡 | ✅ | — |
Backlog (via J10):
| ID | Action | Prio | Owner | Target doc | Definition-of-done |
|---|---|---|---|---|---|
| BL-27 | fix: rewrite all example queries against real columns (membershipType/path/userId/businessRoleId/managedByAccessPackage); list the real output columns per view |
P1 | tbd | docs/reference/sql-views.md |
Every example runs unchanged on a migrated DB |
| BL-28 | fix: matview status — vw_ResourceUserPermissionAssignments + vw_UserPermissionAssignmentViaBusinessRole are materialized (need REFRESH); drop "planned for a future release" |
P1 | tbd | docs/reference/sql-views.md |
No "standard view"/"planned" language; refresh documented |
| BL-29 | update: status header "Proposed / no code yet" → "Implemented" + migration range 044–049 | P2 | tbd | docs/architecture/assignment-model-redesign.md |
Status matches the shipped code |
| BL-30 | fix: value list in the vw_ResourceUserPermissionAssignments row (drop Owner/CrossResourceIndirect; the column is membershipType) |
P2 | tbd | docs/reference/sql-views.md |
Values = the view's CASE output |
Review appendix (dual-voice)¶
This plan evolved from v1 after an /autoplan review with four independent voices (3 Claude subagents: strategy/process/DX + Codex against the real files). Unanimous core finding: v1 measured presence, not usability.
Consensus (CONFIRMED by all voices): 1. Measure usability, not just presence → Instrument B (journeys). 2. Verify the AI-prefilled grids before deciding → verification gate. 3. Cover cross-cluster journeys → J1–J10. 4. Split Contributor vs API consumer; sharpen the primary persona to Analyst → 4 axes. 5. Distinguish wrong from missing → ❌/P0 status. 6. Make the backlog actionable (owner/DoD) → backlog format. 7. Add non-feature docs (troubleshooting/concepts/limitations) → Cluster 0. 8. Prevent re-rot → recurrence/freshness gate. 9. Resolve MECE overlaps → owner assignment + Cluster 0.
Decisions (by the user, 2026-07-15): - UC1 — journeys primary, grid supporting: accepted. - UC2 — 4 axes + Analyst persona: accepted.
Auto-decided methodological fixes (verification gate, ❌/P0 status, rubric, backlog DoD, reduce/hide/deprecate action, recurrence, exit criterion, Cluster 0, MECE owners): applied.
Caveat on the inputs: the grids above are still inherited/unverified; they only pass the verification gate during the cluster review.