{"slug":"architecture/CURRENT_CONTEXT_FLOW","title":"Projection Context Audit — Current Flow (as-built)","section":"architecture","sectionLabel":"Architecture","status":"superseded","type":"document","path":"architecture/CURRENT_CONTEXT_FLOW.mdx","bytes":8475,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n> **Tier:** B (Audit / Derived). Target behavior: `PROJECTION_MODEL.md` §5. Map: `docs/CANON.md`.\n> **Date:** June 12, 2026 | **Status:** SUPERSEDED — historical audit. Current authority is INTEGRATEWISE_ARCHITECTURE.md v2.1.\n> **Purpose:** Prove where context (identity → department → persona → workspace → projection) is\n> resolved today, and pinpoint where it diverges (the `CUSTOMER_SUCCESS → Business Operations` bug).\n\n---\n\n## 1. The chain, as it actually runs\n\n```text\nLogin → Gateway JWT (auth.ts)\n      → tenant_users.department + tenant_spine_config.department/domains   [BACKEND: authoritative]\n      → GET /api/v1/workspace/profile (workspace-spine.ts)  → { domain, department }\n      → AppShell  (reads profile.domain) ──passes `domain` prop──▶ WorkspaceShellNew\n                                                                      │\n        WorkspaceShellNew uses TWO independent sources: ─────────────┤\n          (a) `domain` prop  → activeDomain → ContentRouter, workbenchConfig, domainConfig\n          (b) useProjection() → user.department (re-queried, defaults \"bizops\") → left nav\n```\n\nThe split at **(a) vs (b)** inside one component is the core defect: the workbench/content uses the\ngateway-resolved **domain prop**, while the **left nav** uses a _separately re-resolved_ department.\nWhen they disagree, the header says one thing and the nav shows another.\n\n---\n\n## 2. Who resolves what (evidence)\n\n| Concern                                 | Resolver                                                        | File                                             | Notes                                                                   |\n| --------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------- |\n| **Identity**                            | Gateway credential auth (RS256 JWT)                             | `services/gateway/src/auth.ts`                   | enriches headers; persists `department` to `tenant_users`               |\n| **Department → Domain (authoritative)** | `resolveDomain(domain, department)` via `DEPARTMENT_TO_DOMAIN`  | `services/gateway/src/workspace-spine.ts`        | has the explicit CS→BizOps fix comment; stores in `tenant_spine_config` |\n| **Workspace/profile read**              | `department = row.department \\|\\| primaryDomain`                | `services/gateway/src/workspace-spine.ts`        | backend returns `{ domain, department }`                                |\n| **Onboarding resolution**               | `USE_CASE_DOMAINS`, `DEPARTMENT_CTX_MAP`                        | `apps/web/.../onboarding/OnboardingFlow.tsx`     | first write of domain+CTX                                               |\n| **Shell domain (workbench/content)**    | `activeDomain = domain` prop (immutable)                        | `apps/web/.../workspace/workspace-shell-new.tsx` | drives `ContentRouter`, `workbenchConfig`, `domainConfig`               |\n| **Shell nav (persona)**                 | `useProjection().user.department`                               | `apps/web/src/lib/projection/use-projection.ts`  | **re-resolves independently**, default `\"bizops\"`                       |\n| **Projection context**                  | `useProjection()` re-queries `profiles` / `tenant_spine_config` | `use-projection.ts`                              | Supabase-style client; not the gateway-resolved value                   |\n| **Domain → CTX**                        | `DOMAIN_TO_CTX`                                                 | `apps/web/.../workspace/content-router.tsx`      | yet another mapping table                                               |\n\n---\n\n## 3. The divergence points (root cause)\n\n### 3a. Two independent department/domain sources in one shell\n\n`workspace-shell-new.tsx` consumes **both** the `domain` prop (gateway-authoritative) **and**\n`useProjection().user.department` (independently re-resolved). Nothing guarantees they agree.\n\n### 3b. Conflicting defaults across the codebase\n\nThe same missing value defaults to **three different things** depending on the file:\n\n| Default              | Where                                                              |\n| -------------------- | ------------------------------------------------------------------ |\n| `\"bizops\"`           | `use-projection.ts`, gateway `twin-handoff.ts`, `customer-zero.ts` |\n| `\"CUSTOMER_SUCCESS\"` | `data-density-scorer.ts` (`shouldRenderModule` default)            |\n| `\"unified\"`          | `l1-module-content.tsx` (`const domain = \"unified\"`)               |\n\nA user whose `profile.department` is unset gets `bizops` from the projection (→ \"Business\nOperations\" nav) while the gateway may have resolved `CUSTOMER_SUCCESS` for the workbench. That is\nexactly the screenshot.\n\n### 3c. Many mapping tables, no single resolver\n\nAt least six department/domain maps exist independently and can drift:\n`DEPARTMENT_TO_DOMAIN` (gateway), `USE_CASE_DOMAINS` + `DEPARTMENT_CTX_MAP` (onboarding),\n`DOMAIN_TO_CTX` (content-router), `DEPARTMENT_TO_CONFIG_KEY` (`packages/types`), `DEPARTMENTS`\n(`lib/spine/departments`), and the `workbenchConfig` defaults table (shell).\n\n---\n\n## 4. Why the backend is (mostly) right and the frontend isn't\n\nThe gateway already implements the canonical rule — `resolveDomain()` derives the domain from\n`department` and refuses to silently default to BIZOPS (the comment literally cites the CS→BizOps\nregression). The **frontend** undoes this by re-resolving department inside `useProjection()`\ninstead of consuming the gateway-resolved `domain`/`department` that AppShell already has.\n\nSo the truth is computed correctly once (gateway) and then **recomputed inconsistently** on the\nclient.\n\n---\n\n## 5. The minimal fix (per PROJECTION_MODEL.md §5) — proposal, not yet applied\n\n```text\nResolve ONE ProjectionContext at the top, from the gateway-authoritative values; every layer reads it.\n```\n\n1. **Single source.** `useProjection()` should seed `UserContext.department` from the\n   gateway-resolved `/api/v1/workspace/profile` value (the same one AppShell passes as `domain`),\n   not by re-querying `profiles` with its own `\"bizops\"` default.\n2. **One object to the shell.** `workspace-shell-new.tsx` should take department/domain from the\n   single projection context — not mix a `domain` prop with a separately-resolved projection.\n3. **One default, one place.** Remove the `\"CUSTOMER_SUCCESS\"` and `\"unified\"` ad-hoc defaults;\n   any fallback happens once, at the single resolution point.\n4. **One mapping module.** Collapse the duplicate domain/CTX maps to a single shared resolver\n   (re-exported from `packages/types` `DOMAIN_SPINE_CONFIG` / `DEPARTMENT_TO_CONFIG_KEY`).\n\nExpected diff size: small and surgical — the canonical behavior already exists in the gateway; the\nwork is making the client consume it instead of recomputing it. No new architecture required.\n\n---\n\n## 6. Files to change when we move from audit → fix\n\n| File                                                           | Change                                                                                |\n| -------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| `apps/web/src/lib/projection/use-projection.ts`                | seed department/domain from gateway profile; drop independent `\"bizops\"` default      |\n| `apps/web/src/components/l1/workspace/workspace-shell-new.tsx` | derive department/domain from the single projection context, not a separate prop path |\n| `apps/web/src/components/l1/workspace/data-density-scorer.ts`  | remove `\"CUSTOMER_SUCCESS\"` default; require explicit domain                          |\n| `apps/web/src/components/l1/l1-module-content.tsx`             | remove hardcoded `domain = \"unified\"`                                                 |\n| `apps/web/src/components/l1/workspace/content-router.tsx`      | source `DOMAIN_TO_CTX` from the shared map                                            |\n\n---\n\n_Audit only. The fix is gated on confirming there is no foreign WIP mid-flight in these `apps/web`\nfiles. Target behavior is already canon (`PROJECTION_MODEL.md` §5)._\n"}