---
path: /studio/data-retention-lifecycle-controls
surface: studio
domain: governance
auth: signed-in + studio entitlement
source: apps/oshun/web/src/app/studio/data-retention-lifecycle-controls/page.tsx
status: walked
last_walked:
  '2026-06-30 real-dev-infra Playwright addendum — focused route journey
  verified direct admin navigation, live BFF catalog, seeded retention
  classification, unknown-class safe retain issue, legal-hold precedence,
  client-only JSON validation, live BFF 400 details, network-unreachable
  classify failure, catalog loading/error, non-admin fail-closed auth,
  pending-submit lockout, quick-action affordances, shared axe, and anonymous
  redirect. Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §233;
  apps/oshun/web/e2e/studio-data-retention-lifecycle-controls.spec.ts'
---

# Studio · Data Retention and Lifecycle Controls

## Purpose

Classify records against their data-class retention policy with the real
lifecycle engine: the Lifecycle Classification Lane decides retain / archive /
purge by age, with legal-hold precedence that freezes a record regardless of
age. It is a live admin console wired to `/v1/admin/studio/data-retention`, not
an in-page simulation.

## Entry points

- **Direct URL / bookmark** — `/studio/data-retention-lifecycle-controls`
  (signed-in + studio entitlement; admin scope required to evaluate)
- **Sibling Studio routes** — reached via the quick-action panel on
  security-hardening-program, audit-compliance-surfaces,
  backup-disaster-recovery-ux, and other governance/operations pages

## Layout regions

`page.tsx` mounts `<ShellLayout active="studio">` and renders
`<StudioDataRetentionLifecycleControlsWorkspace />` followed by a single `panel`
of quick-action links.

- **Shell**: `ShellLayout` chrome with the Studio tab active
- **Workspace heading**: `WorkspaceHeading` renders the page `<h1>` "Studio Data
  Retention & Lifecycle Controls", followed by the `data-data-retention-summary`
  paragraph
- **Lifecycle Classification Lane**: an `<h2>` `data-dr-lane-heading`
  ("Lifecycle Classification Lane") section with the load states and, when
  ready, the classify form and result table
- **Quick-actions panel**: `data-dr-quick-actions`
  `data-dr-quick-action-count="5"` wraps 5 sibling `quickAction` `Link`s:
  `data-dr-quick-action="backup-disaster-recovery-ux"`,
  `security-hardening-program`, `sdk-documentation-integration`,
  `webhooks-external-automation`, and `audit-compliance-surfaces`. No self-link
  and no Route Map panel.

## States

- [x] **Loading** — `data-dr-loading` ("Loading retention evaluator…")
- [x] **Unauthorized** — `data-dr-unauthorized` ("Access restricted.") on
      401/403
- [x] **Error** — `data-dr-error` on a failed catalog GET
- [x] **Ready / form** — `data-dr-enums` ("N lifecycle actions") plus the
      `data-dr-form` classify form seeded with `DEFAULT_PAYLOAD` and
      `data-dr-form-state="idle|pending"`
- [x] **Pending submit** — `data-dr-submit-state="pending"`, disabled,
      `aria-disabled="true"`, and `aria-busy="true"` while the evaluate POST is
      in flight
- [x] **Result** — `data-dr-result` with the `data-dr-headline`
      (`data-dr-count="retain|archive|purge|hold"` tallies), the
      `data-dr-records` table of `data-dr-record-row` rows (`data-record-id`,
      `data-action`, `data-dr-record-action`), and the optional `data-dr-issues`
      list (`data-dr-issue-row`)
- [x] **Classify error** — `data-dr-evaluate-error` for invalid JSON or a
      non-200 response
- [x] **Offline** — catalog GET fails closed to the error panel; the classify
      POST surfaces the generic error when the network is unreachable
- [x] **Gated** — admin scope denied → `data-dr-unauthorized`; the route itself
      is proxy-gated to signed-in + studio
- [ ] **Standalone PWA** — renders within the shell; no URL-bar dependency

## Interactions

- [x] **Records (JSON)** — `textarea` `data-dr-payload`,
      `aria-label="retention     json"`, seeded with `DEFAULT_PAYLOAD` (records
      with `recordId`, `classId`, `ageDays`, legal-hold, plus the policy
      classes)
- [x] **Classify records** — `button` `data-dr-submit` (`type="submit"`); POSTs
      the parsed payload to `/v1/admin/studio/data-retention/evaluate`, setting
      `data-dr-result` on a 200 with a valid `result`, or
      `data-dr-evaluate-error` otherwise
- [x] **Quick-action links** — each `quickAction` `Link` navigates to its
      sibling Studio route (see Layout regions for the 5 targets)

## Data & contracts

- **Reads**: `GET /v1/admin/studio/data-retention` on mount — returns the
  catalog (`lifecycleActions[]`)
- **Writes**: `POST /v1/admin/studio/data-retention/evaluate` — returns
  `{ summary: { retainCount, archiveCount, purgeCount, holdCount }, records[], issues[] }`
- **Realtime**: none
- **Caching**: client `fetch` with `cache: 'no-store'`; `buildBffAuthHeaders()`
  attaches the bearer token; SSR shell only
- **Auth/role check**: admin-scoped fail-closed — `admin:*` or `admin:studio`,
  401/403 under the studio workspace gate

## E2E coverage

- `apps/oshun/web/e2e/studio-data-retention-lifecycle-controls.spec.ts` forwards
  `GET /v1/admin/studio/data-retention` and
  `POST /v1/admin/studio/data-retention/evaluate` to the live dev BFF. It covers
  direct admin navigation, catalog bearer forwarding, seeded classification
  counts (`1 retain · 1 archive · 2 purge · 1 hold`), unknown-class issue
  rendering, legal-hold precedence, malformed JSON zero-POST rejection, live BFF
  invalid-payload details, network-unreachable classify error, catalog
  loading/error state, pending-submit lockout, non-admin fail-closed state,
  quick-action href/touch targets, shared axe, and anonymous redirect before the
  workspace renders.

## Cross-references

- Parent: [`../studio-overview.md`](../studio-overview.md)
- Siblings:
  [`studio-audit-compliance-surfaces.md`](./studio-audit-compliance-surfaces.md),
  [`studio-security-hardening-program.md`](./studio-security-hardening-program.md),
  [`studio-enterprise-tenant-isolation.md`](./studio-enterprise-tenant-isolation.md)
- Component source:
  `apps/oshun/web/src/components/studio/StudioDataRetentionLifecycleControlsWorkspace.tsx`
  behind `/v1/admin/studio/data-retention`

## Open questions / known gaps

- [ ] Confirm whether the page will gain a breadcrumb/Route Map panel like other
      Studio surfaces, or keep the flat quick-action panel
- [ ] Document the persisted record source the lane should classify once it runs
      against a live retention store rather than a pasted JSON payload
- [ ] Manual screen-reader and installed/standalone PWA passes remain outside
      the automated Playwright route journey.
