---
path: /content
surface: tenant-admin
domain: governance
auth: signed-in (tenant) + scope:tenant:admin:{tenantId} (or tenant:admin:*)
source: apps/oshun/tenant-admin/src/app/content/page.tsx
status: drafted
last_walked: '—'
---

# Content scopes

## Purpose

Shows the effective tenant visibility (allow/deny) for content objects —
courses, personas, briefings, sources — after resolving an inherited district
policy plus tenant-level overrides via `bulkResolveScope`. It is the `content`
workspace (`group: configuration`) in `@oshun/navigation`.

## Entry points

- Shell sidebar **Configuration › Content** item (`TenantSidebar`,
  `shortLabel: 'Content'`, path `/content`).
- Direct URL `/content` (bookmarkable; scope-gated).

## Layout regions

Wrapped in `<TenantShell session currentWorkspaceId="content">`:

- **Sidebar / Header**: standard chrome (header crumb "Content scopes").
- **Main**
  (`<section data-testid="tenant-content-panel" data-tenant-id={...}>`):
  - `<h1>Content scopes</h1>` + subtitle on inherited district policy + tenant
    overrides.
  - `styles.summaryGrid` of three tiles: **Allowed** (count of
    `r.visibility === 'allow'`), **Denied** (count of `'deny'`), **Overrides**
    (`schoolScope.entries.length` = 2).
  - `styles.table` columns **Object / Kind / Effective visibility**, one row per
    resolved object; visibility renders a `badgeGood`/`badgeBad` badge.

## States

- [ ] **Loading** — N/A; synchronous server render from `bulkResolveScope()`
      over hard-coded district + school scope chains (no fetch).
- [ ] **Empty** — N/A; four objects are always resolved
      (`metis-civic-reasoning`, `metis-socratic-guide`, `q2-exam-prep`,
      `unapproved-archive`).
- [ ] **Populated (short)** — always 4 rows. The chain resolves district-allow
      then tenant override (e.g. `metis-socratic-guide` flips to `deny` at the
      school scope; `unapproved-archive` falls through to
      `defaultPolicy: deny`).
- [ ] **Populated (long)** — N/A; fixed 4-object dataset; no pagination.
- [ ] **Error (recoverable)** — N/A; no query.
- [ ] **Error (unrecoverable)** — no custom handler; render throw → Next
      default.
- [ ] **Offline** — N/A (server-rendered).
- [ ] **Gated** — no session →
      `redirect('/unauthorized?reason=missing-session&returnTo=/content')`;
      middleware blocks unauthenticated requests upstream.
- [ ] **Standalone PWA** — N/A; no manifest. (verify.)

## Interactions

- [ ] **Summary tiles** (`styles.summaryItem` × 3) — display-only counts
      (Allowed / Denied / Overrides); not focusable, no telemetry.
- [ ] **Resolved-object table rows** (`<tr>` keyed by `objectKind:objectId`)
  - Function: render-only; shows `objectId` (mono `<code>`), `objectKind`, and a
    visibility badge. No toggle/override-edit controls exist in code —
    read-only.
  - Keyboard: table is not interactive.
  - Screen reader: standard table semantics.
  - Touch target: N/A.
  - Mobile (≤ 640 px): `workspace.module.css` table styling.
  - Disabled when: N/A.
  - Offline behavior: N/A.
  - Telemetry: _None._

## Data & contracts

- **Reads**: `getTenantServerSession()`; `bulkResolveScope({ chain, objects })`
  from `@oshun/tenant-console` — input is a `TenantContentScope[]` precedence
  chain (district → school) plus the list of objects to resolve; returns each
  object's effective `visibility`. Both scopes and the object list are
  hard-coded in the page. **No BFF call.**
- **Writes**: _None._
- **Realtime**: _None._
- **Caching**: SSR (dynamic; cookie read). Deterministic per tenant id.
- **Auth/role check**: `middleware.ts` + page `getTenantServerSession()` →
  `canEnterTenantShell`.

## Cross-references

- Domain logic: `libs/oshun/tenant-console/src` (`bulkResolveScope`,
  `TenantContentScope`). Index: `libs/oshun/tenant-console/src/index.ts`.
- Workspace model: `libs/oshun/navigation/src/tenant-ia.ts` (`content`,
  `group: configuration`).
- Sibling routes: [home.md](./home.md), [agents.md](./agents.md). `/policy`
  (inherited bundles + tighten-only overrides) lacks a file yet.

## Open questions / known gaps

- [ ] District + school scope chains and the object list are hard-coded seed
      data; no BFF read of the tenant's real content-scope policy is wired.
      Document the eventual backing store and override-write path.
- [ ] Page is read-only — no UI to add/edit allow/deny overrides. Confirm
      whether editing is in scope for a later phase.
