# Enterprise Auth, Directory, And Federation Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Implement a coherent enterprise identity stack for DPF that hardens current auth, adds manager-aware authorization, syncs ADP workforce hierarchy, introduces an identity edge for LDAP/OIDC/SAML/SCIM, surfaces a unified `/platform/identity` workspace, and aligns HR/Finance coworkers with the same access model.

**Architecture:** DPF remains the source of truth for principal identity, employee-manager hierarchy, route capabilities, and coworker grants. An incorporated identity edge runtime publishes standards surfaces. Delivery is phased so the platform first fixes its current auth seams, then adds workforce hierarchy and principal modeling, then layers federation and directory protocols on top.

**Tech Stack:** Next.js 16, Auth.js, Prisma 7, PostgreSQL, Docker Compose, TypeScript, ADP MCP service, authentik (new service), Microsoft Entra (upstream optional authority), LDAP/OIDC/SAML/SCIM.

**Authoritative spec:** [docs/superpowers/specs/2026-04-22-enterprise-auth-directory-federation-design.md](../specs/2026-04-22-enterprise-auth-directory-federation-design.md)

---

## File Structure

Expected files and responsibilities before task execution:

- `packages/db/prisma/schema.prisma`
  - add principal spine and app-federation models
- `packages/db/prisma/migrations/<timestamp>_*`
  - additive migrations with inline backfill SQL where needed
- `apps/web/lib/govern/auth.ts`
  - Auth.js provider config and session enrichment
- `apps/web/lib/govern/password.ts`
  - canonical password verification logic
- `apps/web/app/api/v1/auth/login/route.ts`
  - API login path using canonical verification
- `apps/web/lib/api/auth-middleware.ts`
  - richer effective auth context
- `apps/web/lib/govern/permissions.ts`
  - capability + scope evaluation
- `apps/web/lib/identity/*`
  - new principal, alias, provisioning, and access-evaluator helpers
- `apps/web/lib/tak/*`
  - projected attestation and coworker trust helpers
- `apps/web/lib/integrate/adp/*`
  - ADP workforce sync and hierarchy reconciliation
- `apps/web/lib/integrate/entra/*`
  - Entra authority connection and future sync helpers
- `apps/web/app/api/integrations/adp/*`
  - ADP sync/test endpoints
- `apps/web/app/api/integrations/entra/*`
  - Entra connect/test endpoints
- `apps/web/app/(shell)/platform/identity/*`
  - unified identity workspace routes
- `apps/web/components/integrations/*`
  - UI panels for identity edge, federation, and app provisioning
- `apps/web/components/platform/identity/*`
  - identity workspace panels, nav, and object-detail surfaces
- `apps/web/components/docs/*`
  - clean docs links and embedded page help affordances
- `docker-compose.yml`
  - add identity edge service
- `tests` / `*.test.ts` / `*.test.tsx`
  - unit coverage for new auth and access rules

---

## Chunk 1: Harden Current Auth

### Task 1.1: Unify workforce password verification

**Files:**
- Modify: `apps/web/lib/govern/password.ts`
- Modify: `apps/web/app/api/v1/auth/login/route.ts`
- Create: `apps/web/app/api/v1/auth/login/route.test.ts`

- [ ] **Step 1: Write the failing API auth tests**

```ts
it("accepts a bcrypt password through the API login route", async () => {
  // Arrange mocked user with bcrypt hash
  // Assert 200 and tokens returned
});

it("accepts a legacy sha256 password and rehashes on success", async () => {
  // Arrange mocked user with 64-char hex hash
  // Assert login succeeds and passwordHash is updated
});
```

- [ ] **Step 2: Run the targeted test**

Run: `pnpm --filter web test apps/web/app/api/v1/auth/login/route.test.ts`

Expected: FAIL because the route still uses `bcrypt.compare()` directly.

- [ ] **Step 3: Refactor the route to use `verifyPassword()`**

Minimal implementation shape:

```ts
const { valid, needsRehash } = await verifyPassword(password, user.passwordHash);
if (!valid) throw apiError("INVALID_CREDENTIALS", "Invalid email or password", 401);
if (needsRehash) {
  const nextHash = await hashPassword(password);
  await prisma.user.update({ where: { id: user.id }, data: { passwordHash: nextHash } });
}
```

- [ ] **Step 4: Run the test again**

Run: `pnpm --filter web test apps/web/app/api/v1/auth/login/route.test.ts`

Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add apps/web/lib/govern/password.ts apps/web/app/api/v1/auth/login/route.ts apps/web/app/api/v1/auth/login/route.test.ts
git commit -m "feat(auth): unify API login with canonical password verification"
```

### Task 1.2: Introduce a richer auth context helper

**Files:**
- Create: `apps/web/lib/identity/effective-auth-context.ts`
- Create: `apps/web/lib/identity/effective-auth-context.test.ts`
- Modify: `apps/web/lib/api/auth-middleware.ts`

- [ ] **Step 1: Write the failing helper tests**

```ts
it("builds effective auth context for a workforce user", () => {
  expect(context.principalId).toMatch(/^PRN-/);
  expect(context.platformRole).toBe("HR-300");
});

it("returns empty manager scope for a non-manager", () => {
  expect(context.managerScope?.directReportIds ?? []).toEqual([]);
});
```

- [ ] **Step 2: Run the test and verify failure**

Run: `pnpm --filter web test apps/web/lib/identity/effective-auth-context.test.ts`

- [ ] **Step 3: Implement the helper**

Start with a narrow shape:

```ts
export type EffectiveAuthContext = {
  principalId: string | null;
  platformRole: string | null;
  isSuperuser: boolean;
  employeeId: string | null;
  grantedCapabilities: string[];
};
```

Use current data first; do not wait on the principal spine migration.

- [ ] **Step 4: Wire `auth-middleware.ts` to return the richer context**

- [ ] **Step 5: Re-run tests**

Run: `pnpm --filter web test apps/web/lib/identity/effective-auth-context.test.ts`

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/identity/effective-auth-context.ts apps/web/lib/identity/effective-auth-context.test.ts apps/web/lib/api/auth-middleware.ts
git commit -m "feat(auth): add effective auth context helper for route and tool access"
```

---

## Chunk 2: Principal Spine

### Task 2.1: Add `Principal` and `PrincipalAlias`

**Files:**
- Modify: `packages/db/prisma/schema.prisma`
- Create: `packages/db/prisma/migrations/<timestamp>_add_principal_spine/migration.sql`
- Create: `packages/db/src/principal-spine.test.ts`

- [ ] **Step 1: Write the failing DB test**

```ts
it("creates a principal and alias pair", async () => {
  const principal = await prisma.principal.create({ data: { principalId: "PRN-000001", kind: "human", displayName: "Alice Example" } });
  await prisma.principalAlias.create({ data: { principalId: principal.id, aliasType: "user", aliasValue: "usr_123" } });
  const aliases = await prisma.principalAlias.findMany({ where: { principalId: principal.id } });
  expect(aliases).toHaveLength(1);
});
```

- [ ] **Step 2: Run the test and verify failure**

Run: `pnpm --filter @dpf/db test principal-spine`

- [ ] **Step 3: Add the schema models**

Additive model block:

```prisma
model Principal {
  id          String          @id @default(cuid())
  principalId String          @unique
  kind        String
  status      String          @default("active")
  displayName String
  aliases     PrincipalAlias[]
  createdAt   DateTime        @default(now())
  updatedAt   DateTime        @updatedAt
}

model PrincipalAlias {
  id          String   @id @default(cuid())
  principalId String
  aliasType   String
  aliasValue  String
  issuer      String?
  createdAt   DateTime @default(now())
  principal   Principal @relation(fields: [principalId], references: [id], onDelete: Cascade)

  @@unique([aliasType, aliasValue, issuer])
  @@index([principalId])
}
```

- [ ] **Step 4: Generate the migration**

Run:

```bash
pnpm --filter @dpf/db exec prisma migrate dev --name add_principal_spine --create-only
```

- [ ] **Step 5: Add inline backfill SQL to the migration**

Backfill from existing users and agents where possible; at minimum create principals for all existing `User` rows and `Agent` rows.

- [ ] **Step 6: Apply migration and run tests**

Run:

```bash
pnpm --filter @dpf/db exec prisma migrate deploy
pnpm --filter @dpf/db test principal-spine
```

- [ ] **Step 7: Commit**

```bash
git add packages/db/prisma/schema.prisma packages/db/prisma/migrations packages/db/src/principal-spine.test.ts
git commit -m "feat(identity): add principal spine and alias model"
```

### Task 2.2: Link current auth records into the principal spine

**Files:**
- Create: `apps/web/lib/identity/principal-linking.ts`
- Create: `apps/web/lib/identity/principal-linking.test.ts`
- Modify: `apps/web/lib/govern/auth.ts`

- [ ] **Step 1: Write the failing linking tests**

```ts
it("resolves a workforce user to a principal", async () => {
  const result = await syncUserPrincipal(userId);
  expect(result.kind).toBe("human");
});

it("creates a GAID-ready alias slot for an agent principal", async () => {
  const result = await syncAgentPrincipal(agentId);
  expect(result.aliases.some((a) => a.aliasType === "agent")).toBe(true);
});
```

- [ ] **Step 2: Run the tests**

Run: `pnpm --filter web test apps/web/lib/identity/principal-linking.test.ts`

- [ ] **Step 3: Implement `principal-linking.ts`**

Responsibilities:
- link `User` -> `Principal`
- add `user` and `employee` aliases
- link `Agent` -> `Principal`
- add `agent` alias

- [ ] **Step 4: Enrich login/session code to include `principalId`**

- [ ] **Step 5: Re-run tests**

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/identity/principal-linking.ts apps/web/lib/identity/principal-linking.test.ts apps/web/lib/govern/auth.ts
git commit -m "feat(identity): link workforce and agent records into principal spine"
```

---

## Chunk 3: ADP Workforce Hierarchy And Manager Scope

### Task 3.1: Add ADP alias and workforce sync contract

**Files:**
- Modify: `packages/db/prisma/schema.prisma`
- Create: `apps/web/lib/integrate/adp/workforce-sync.ts`
- Create: `apps/web/lib/integrate/adp/workforce-sync.test.ts`

- [ ] **Step 1: Write the failing sync test**

```ts
it("maps an ADP worker to employee profile and principal alias", async () => {
  const result = await reconcileAdpWorker(sampleWorker);
  expect(result.employeeProfile.employeeId).toBe("EMP-100");
  expect(result.aliases.some((a) => a.aliasType === "adp")).toBe(true);
});
```

- [ ] **Step 2: Run the test**

Run: `pnpm --filter web test apps/web/lib/integrate/adp/workforce-sync.test.ts`

- [ ] **Step 3: Implement sync contract**

Responsibilities:
- resolve worker by employee number or work email
- upsert ADP alias on the principal
- update `EmployeeProfile.managerEmployeeId`
- update employment status from ADP

- [ ] **Step 4: Re-run the test**

- [ ] **Step 5: Commit**

```bash
git add apps/web/lib/integrate/adp/workforce-sync.ts apps/web/lib/integrate/adp/workforce-sync.test.ts packages/db/prisma/schema.prisma
git commit -m "feat(adp): reconcile workforce hierarchy into employee profiles and principal aliases"
```

### Task 3.2: Implement manager-aware access evaluation

**Files:**
- Create: `apps/web/lib/govern/manager-scope.ts`
- Create: `apps/web/lib/govern/manager-scope.test.ts`
- Modify: `apps/web/lib/govern/permissions.ts`
- Modify: `apps/web/lib/api/auth-middleware.ts`

- [ ] **Step 1: Write the failing tests**

```ts
it("allows a manager to access a direct report", () => {
  expect(canAccessEmployeeScope(managerContext, reportId)).toBe(true);
});

it("denies a manager access to unrelated employees without HR capability", () => {
  expect(canAccessEmployeeScope(managerContext, unrelatedId)).toBe(false);
});
```

- [ ] **Step 2: Run the tests**

Run: `pnpm --filter web test apps/web/lib/govern/manager-scope.test.ts`

- [ ] **Step 3: Implement manager scope resolution**

Start with:

```ts
export function canAccessEmployeeScope(
  context: EffectiveAuthContext,
  targetEmployeeId: string,
): boolean {
  if (context.isSuperuser) return true;
  if (context.employeeId === targetEmployeeId) return true;
  return context.managerScope?.directReportIds.includes(targetEmployeeId) ?? false;
}
```

- [ ] **Step 4: Thread the scope evaluator into permissions and route checks**

- [ ] **Step 5: Re-run tests**

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/govern/manager-scope.ts apps/web/lib/govern/manager-scope.test.ts apps/web/lib/govern/permissions.ts apps/web/lib/api/auth-middleware.ts
git commit -m "feat(authz): add manager-aware workforce scope evaluation"
```

---

## Chunk 4: Identity Edge Integration

### Task 4.1: Add the identity edge runtime to Docker

**Files:**
- Modify: `docker-compose.yml`
- Create: `docs/operations/authentik-runtime-notes.md`

- [ ] **Step 1: Add the service definition**

Add a new `authentik` service with:
- image/runtime configuration
- database/env wiring
- healthcheck
- dependency ordering compatible with the current stack

- [ ] **Step 2: Document local bootstrap expectations**

Include:
- admin bootstrap secret handling
- local URL
- backup/restore note
- how DPF will provision into it

- [ ] **Step 3: Build and start the service**

Run:

```bash
docker compose build authentik
docker compose up -d authentik
```

Expected: service healthy.

- [ ] **Step 4: Commit**

```bash
git add docker-compose.yml docs/operations/authentik-runtime-notes.md
git commit -m "feat(identity-edge): add authentik runtime to local platform stack"
```

### Task 4.2: Add provisioning client from DPF to the identity edge

**Files:**
- Create: `apps/web/lib/integrate/authentik/provisioning-client.ts`
- Create: `apps/web/lib/integrate/authentik/provisioning-client.test.ts`
- Create: `apps/web/app/api/integrations/authentik/test/route.ts`

- [ ] **Step 1: Write the failing tests**

```ts
it("provisions a user payload to the edge", async () => {
  const result = await pushPrincipalToIdentityEdge(samplePrincipal);
  expect(result.ok).toBe(true);
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Implement the client**

Phase-1 implementation can use the edge admin API if SCIM bootstrap is not ready yet, but the method boundary must be SCIM-shaped so it can switch later without rewriting callers.

- [ ] **Step 4: Add a smoke-test route for admin validation**

- [ ] **Step 5: Re-run tests**

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/integrate/authentik/provisioning-client.ts apps/web/lib/integrate/authentik/provisioning-client.test.ts apps/web/app/api/integrations/authentik/test/route.ts
git commit -m "feat(identity-edge): add DPF provisioning client for identity edge sync"
```

---

## Chunk 5: Workforce Federation Into DPF

### Task 5.1: Add OIDC-based workforce login

**Files:**
- Modify: `apps/web/lib/govern/auth.ts`
- Create: `apps/web/lib/govern/auth-oidc.test.ts`
- Modify: workforce login UI files under `apps/web/app/(auth)/`

- [ ] **Step 1: Write the failing auth tests**

```ts
it("maps an oidc login to a principal-linked workforce user", async () => {
  // Assert session contains principalId and platformRole
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Add the OIDC provider config**

Use Auth.js generic OIDC configuration pointed at the identity edge. Keep current credentials provider during migration.

- [ ] **Step 4: Add account linking logic**

Map `(issuer, sub)` into a principal-linked alias record before finalizing session state.

- [ ] **Step 5: Re-run tests**

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/govern/auth.ts apps/web/lib/govern/auth-oidc.test.ts apps/web/app/(auth)
git commit -m "feat(auth): add OIDC workforce federation through identity edge"
```

---

## Chunk 6: LDAP And SCIM Publication

### Task 6.1: Add directory projection settings and claims mapping

**Files:**
- Create: `apps/web/lib/identity/directory-projection.ts`
- Create: `apps/web/lib/identity/directory-projection.test.ts`
- Create: `apps/web/components/integrations/DirectorySettingsPanel.tsx`
- Create: `apps/web/app/(shell)/admin/settings/security/page.tsx`

- [ ] **Step 1: Write the failing projection tests**

```ts
it("projects a workforce role as an LDAP-compatible group", () => {
  const result = projectRoleGroups(principal, memberships);
  expect(result).toContainEqual(expect.objectContaining({ cn: "role-HR-300" }));
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Implement projection helper**

Responsibilities:
- map DPF principals to directory objects
- map roles to LDAP groups
- tag principal kind

- [ ] **Step 4: Add admin settings UI**

Allow admin to see:
- directory base DN
- publication status
- LDAP/SCIM edge connection health

- [ ] **Step 5: Re-run tests**

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/identity/directory-projection.ts apps/web/lib/identity/directory-projection.test.ts apps/web/components/integrations/DirectorySettingsPanel.tsx apps/web/app/(shell)/admin/settings/security/page.tsx
git commit -m "feat(directory): add LDAP and SCIM projection settings"
```

### Task 6.2: Implement agent LDAP projection vocabulary

**Files:**
- Modify: `apps/web/lib/identity/directory-projection.ts`
- Create: `apps/web/lib/identity/ldap-agent-profile.ts`
- Create: `apps/web/lib/identity/ldap-agent-profile.test.ts`
- Modify: `docs/operations/authentik-runtime-notes.md`

- [ ] **Step 1: Write the failing agent LDAP profile tests**

```ts
it("projects an agent principal with explicit GAID and TAK trust markers", () => {
  const entry = projectAgentLdapEntry(sampleAgentPrincipal, sampleTakState);
  expect(entry.attributes.gaid).toBe("gaid:priv:dpf.internal:hr-coworker-001");
  expect(entry.attributes.dpfPrincipalType).toBe("agent");
  expect(entry.attributes.takValidationStatus).toBe("validated");
});
```

- [ ] **Step 2: Run the tests**

Run: `pnpm --filter web test apps/web/lib/identity/ldap-agent-profile.test.ts`

- [ ] **Step 3: Implement the projection helper**

Responsibilities:
- emit a stable LDAP-safe identifier for agent principals
- project `gaid`, `dpfPrincipalType`, `takProfileFingerprint`, `takValidationStatus`, `gaidIssuer`, `gaidExposureState`, and `dpfVerifierRef`
- keep role and business authority in group membership rather than bespoke permission attributes

- [ ] **Step 4: Document the expected authentik mapping**

Capture:
- base DN assumptions
- attribute names used by the edge
- read-only publication stance for external consumers

- [ ] **Step 5: Re-run tests**

Run: `pnpm --filter web test apps/web/lib/identity/ldap-agent-profile.test.ts apps/web/lib/identity/directory-projection.test.ts`

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/identity/directory-projection.ts apps/web/lib/identity/ldap-agent-profile.ts apps/web/lib/identity/ldap-agent-profile.test.ts docs/operations/authentik-runtime-notes.md
git commit -m "feat(directory): add LDAP agent projection vocabulary for GAID and TAK"
```

### Task 6.3: Add SCIM agent extension projection

**Files:**
- Create: `apps/web/lib/identity/scim-agent-extension.ts`
- Create: `apps/web/lib/identity/scim-agent-extension.test.ts`
- Modify: `apps/web/lib/integrate/authentik/provisioning-client.ts`
- Modify: `apps/web/lib/integrate/authentik/provisioning-client.test.ts`

- [ ] **Step 1: Write the failing SCIM extension tests**

```ts
it("maps an agent principal into the DPF SCIM agent extension", () => {
  const payload = buildScimPrincipalPayload(sampleAgentPrincipal, sampleTakState);
  expect(payload.schemas).toContain("urn:dpf:params:scim:schemas:extension:agent:2.0:User");
  expect(payload["urn:dpf:params:scim:schemas:extension:agent:2.0:User"].gaid).toContain("gaid:");
});
```

- [ ] **Step 2: Run the tests**

Run: `pnpm --filter web test apps/web/lib/identity/scim-agent-extension.test.ts apps/web/lib/integrate/authentik/provisioning-client.test.ts`

- [ ] **Step 3: Implement the extension builder**

Responsibilities:
- map `principalId` into `externalId`
- attach the DPF agent extension for `gaid`, `principalType`, `profileFingerprint`, `validationStatus`, `exposureState`, `badgeRefs`, and `verifierRef`
- omit the extension for non-agent principals unless explicitly configured

- [ ] **Step 4: Update the provisioning client to use the builder**

Keep the provisioning client boundary SCIM-shaped even if bootstrap still uses the edge admin API behind the scenes.

- [ ] **Step 5: Re-run tests**

Run: `pnpm --filter web test apps/web/lib/identity/scim-agent-extension.test.ts apps/web/lib/integrate/authentik/provisioning-client.test.ts`

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/identity/scim-agent-extension.ts apps/web/lib/identity/scim-agent-extension.test.ts apps/web/lib/integrate/authentik/provisioning-client.ts apps/web/lib/integrate/authentik/provisioning-client.test.ts
git commit -m "feat(scim): add GAID-aware agent extension projection"
```

### Task 6.4: Add TAK attestation projection helper

**Files:**
- Create: `apps/web/lib/tak/attestation-projection.ts`
- Create: `apps/web/lib/tak/attestation-projection.test.ts`
- Modify: `apps/web/lib/actions/agent-coworker.ts`

- [ ] **Step 1: Write the failing attestation projection tests**

```ts
it("builds a public trust marker without exposing kernel-only evidence", () => {
  const envelope = buildTakProjectionEnvelope(sampleTakState);
  expect(envelope.profileFingerprint).toMatch(/^sha256:/);
  expect(envelope.evidenceRef).toContain("tak://attestations/");
  expect((envelope as Record<string, unknown>).approvedProfile).toBeUndefined();
});
```

- [ ] **Step 2: Run the tests**

Run: `pnpm --filter web test apps/web/lib/tak/attestation-projection.test.ts`

- [ ] **Step 3: Implement the projection helper**

Responsibilities:
- build a comparison-safe envelope with `gaid`, `profileFingerprint`, `validationStatus`, `issuedAt`, `verifierRef`, `evidenceRef`, and optional signature material
- explicitly exclude full kernel profile content and internal review records
- make the helper available to coworker/auth surfaces that need to show trust state

- [ ] **Step 4: Thread the projection into coworker-facing auth surfaces**

Expose trust markers only where they help humans or relying systems verify continuity; do not leak the full approved operating bundle.

- [ ] **Step 5: Re-run tests**

Run: `pnpm --filter web test apps/web/lib/tak/attestation-projection.test.ts`

- [ ] **Step 6: Commit**

```bash
git add apps/web/lib/tak/attestation-projection.ts apps/web/lib/tak/attestation-projection.test.ts apps/web/lib/actions/agent-coworker.ts
git commit -m "feat(tak): add projected attestation envelope for trusted agent identity"
```

---

## Chunk 7: External Product Federation

### Task 7.1: Add downstream application registry

**Files:**
- Modify: `packages/db/prisma/schema.prisma`
- Create: `apps/web/lib/identity/application-registry.ts`
- Create: `apps/web/lib/identity/application-registry.test.ts`
- Create: `apps/web/components/integrations/ApplicationFederationPanel.tsx`

- [ ] **Step 1: Write the failing tests**

```ts
it("stores an OIDC app registration with claim mappings", async () => {
  const app = await createFederatedApplication({ protocol: "oidc", appKey: "grafana" });
  expect(app.protocol).toBe("oidc");
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Add schema and registry helper**

Include:
- app key
- display name
- protocol
- claim mappings
- assigned groups
- provisioning mode

- [ ] **Step 4: Add admin UI panel**

- [ ] **Step 5: Re-run tests**

- [ ] **Step 6: Commit**

```bash
git add packages/db/prisma/schema.prisma apps/web/lib/identity/application-registry.ts apps/web/lib/identity/application-registry.test.ts apps/web/components/integrations/ApplicationFederationPanel.tsx
git commit -m "feat(federation): add downstream application registry and claim mapping"
```

---

## Chunk 8: Coworker Access Alignment

### Task 8.1: Thread manager-aware auth context into coworker/tool resolution

**Files:**
- Modify: `apps/web/lib/tak/agent-grants.ts`
- Modify: coworker resolution/auth files under `apps/web/lib/actions/agent-coworker.ts` and adjacent modules
- Create: `apps/web/lib/actions/agent-coworker-authz.test.ts`

- [ ] **Step 1: Write the failing coworker authz tests**

```ts
it("denies an HR coworker action outside the manager's team scope", async () => {
  expect(result.allowed).toBe(false);
});

it("allows a finance coworker payroll summary within allowed scope when ADP is connected", async () => {
  expect(result.allowed).toBe(true);
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Implement scope-aware coworker gating**

Effective rule:

```ts
effective = userAllowed && agentAllowed && routeAllowed && scopeAllowed && integrationReady;
```

- [ ] **Step 4: Re-run tests**

- [ ] **Step 5: Commit**

```bash
git add apps/web/lib/tak/agent-grants.ts apps/web/lib/actions/agent-coworker.ts apps/web/lib/actions/agent-coworker-authz.test.ts
git commit -m "feat(coworkers): align HR and finance coworker access with manager-aware auth"
```

---

## Chunk 9: Platform Identity Workspace

### Task 9.1: Add the Identity & Access platform family and route shell

**Files:**
- Modify: `apps/web/components/platform/platform-nav.ts`
- Modify: `apps/web/components/platform/PlatformTabNav.tsx`
- Create: `apps/web/components/platform/identity/IdentityTabNav.tsx`
- Create: `apps/web/app/(shell)/platform/identity/layout.tsx`
- Create: `apps/web/app/(shell)/platform/identity/page.tsx`

- [ ] **Step 1: Write the failing navigation tests**

```ts
it("renders Identity & Access in the platform family nav", () => {
  expect(screen.getByText("Identity & Access")).toBeInTheDocument();
});

it("renders the identity workspace landing page", async () => {
  const html = await renderRoute("/platform/identity");
  expect(html).toContain("Identity & Access");
});
```

- [ ] **Step 2: Run the tests**

Run: `pnpm --filter web test apps/web/components/platform`

- [ ] **Step 3: Implement the platform family and route shell**

Add the new family and make `/platform/identity` the canonical authority workspace.

- [ ] **Step 4: Re-run the tests**

- [ ] **Step 5: Commit**

```bash
git add apps/web/components/platform/platform-nav.ts apps/web/components/platform/PlatformTabNav.tsx apps/web/components/platform/identity/IdentityTabNav.tsx apps/web/app/(shell)/platform/identity/layout.tsx apps/web/app/(shell)/platform/identity/page.tsx
git commit -m "feat(identity-ui): add platform identity workspace shell and nav"
```

### Task 9.2: Build the identity overview and management surfaces

**Files:**
- Create: `apps/web/app/(shell)/platform/identity/principals/page.tsx`
- Create: `apps/web/app/(shell)/platform/identity/groups/page.tsx`
- Create: `apps/web/app/(shell)/platform/identity/directory/page.tsx`
- Create: `apps/web/app/(shell)/platform/identity/federation/page.tsx`
- Create: `apps/web/app/(shell)/platform/identity/applications/page.tsx`
- Create: `apps/web/app/(shell)/platform/identity/authorization/page.tsx`
- Create: `apps/web/app/(shell)/platform/identity/agents/page.tsx`
- Create: `apps/web/components/platform/identity/IdentityOverviewPanel.tsx`
- Create: `apps/web/components/platform/identity/PrincipalDirectoryPanel.tsx`
- Create: `apps/web/components/platform/identity/GroupMembershipPanel.tsx`
- Create: `apps/web/components/platform/identity/DirectoryAuthoritiesPanel.tsx`
- Create: `apps/web/components/platform/identity/ApplicationAssignmentsPanel.tsx`
- Create: `apps/web/components/platform/identity/AuthorizationBundlePanel.tsx`
- Create: `apps/web/components/platform/identity/AgentIdentityPanel.tsx`

- [ ] **Step 1: Write the failing page/panel tests**

```ts
it("shows people, contractors, agents, and services on the principals page", () => {
  expect(screen.getByText("Principals")).toBeInTheDocument();
});

it("shows route bundles and coworker associations on the authorization page", () => {
  expect(screen.getByText("Authorization")).toBeInTheDocument();
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Implement the workspace surfaces**

Keep the UI task-oriented:
- overview and health first
- objects and assignments second
- protocol detail deeper in the page

- [ ] **Step 4: Re-run the tests**

- [ ] **Step 5: Commit**

```bash
git add apps/web/app/(shell)/platform/identity apps/web/components/platform/identity
git commit -m "feat(identity-ui): add identity workspace management surfaces"
```

### Task 9.3: Integrate Microsoft Entra as a first-class upstream authority

**Files:**
- Modify: `apps/web/app/(shell)/platform/identity/federation/page.tsx`
- Modify: `apps/web/components/integrations/EntraConnectPanel.tsx`
- Create: `apps/web/components/platform/identity/FederationAuthorityCard.tsx`
- Create: `apps/web/components/platform/identity/FederationAuthorityCard.test.tsx`

- [ ] **Step 1: Write the failing federation card tests**

```ts
it("renders Microsoft Entra as an available upstream authority", () => {
  expect(screen.getByText("Microsoft Entra")).toBeInTheDocument();
});

it("shows connection state and mapping guidance for Entra", () => {
  expect(screen.getByText("Upstream authority")).toBeInTheDocument();
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Implement the federation authority cards**

Show:
- connection health
- last sync / connection state
- what the authority owns upstream
- what DPF still owns locally

- [ ] **Step 4: Re-run the tests**

- [ ] **Step 5: Commit**

```bash
git add apps/web/app/(shell)/platform/identity/federation/page.tsx apps/web/components/integrations/EntraConnectPanel.tsx apps/web/components/platform/identity/FederationAuthorityCard.tsx apps/web/components/platform/identity/FederationAuthorityCard.test.tsx
git commit -m "feat(identity-ui): add Entra-first federation authority experience"
```

### Task 9.4: Add clean docs links and first-use coworker guidance state

**Files:**
- Modify: `packages/db/prisma/schema.prisma`
- Create: `packages/db/prisma/migrations/<timestamp>_add_page_guide_state/migration.sql`
- Create: `apps/web/lib/identity/page-guide-state.ts`
- Create: `apps/web/lib/identity/page-guide-state.test.ts`
- Modify: `apps/web/components/docs/HelpLink.tsx`
- Modify: `apps/web/components/agent/AgentCoworkerShell.tsx`
- Create: `apps/web/components/platform/identity/IdentityWorkspaceHelp.tsx`

- [ ] **Step 1: Write the failing guide-state tests**

```ts
it("stores first-seen and dismissed state per user and route", async () => {
  const state = await markPageGuideSeen(sampleUserId, "/platform/identity", "identity-overview", "v1");
  expect(state.firstSeenAt).toBeDefined();
});

it("auto-opens guidance only on first use or when the guide version changes", async () => {
  expect(await shouldAutoOpenGuide(sampleUserId, "/platform/identity", "identity-overview", "v1")).toBe(true);
});
```

- [ ] **Step 2: Run the tests**

- [ ] **Step 3: Add the schema model and migration**

Use a durable per-user, per-route, per-guide record so the experience survives across devices and sessions.

- [ ] **Step 4: Implement the docs/help and coworker behavior**

Requirements:
- visible docs link opens a clean operator guide
- coworker can use the deeper spec/design context privately
- auto-open on first use
- stay dismissed after dismissal
- re-open or re-badge when guidance version changes materially

- [ ] **Step 5: Re-run the tests**

- [ ] **Step 6: Commit**

```bash
git add packages/db/prisma/schema.prisma packages/db/prisma/migrations apps/web/lib/identity/page-guide-state.ts apps/web/lib/identity/page-guide-state.test.ts apps/web/components/docs/HelpLink.tsx apps/web/components/agent/AgentCoworkerShell.tsx apps/web/components/platform/identity/IdentityWorkspaceHelp.tsx
git commit -m "feat(identity-ui): add docs links and first-use coworker guidance state"
```

---

## Verification

After each chunk, run the smallest relevant tests first. After each merged chunk, run the production build gate.

- [ ] **Targeted unit tests**

Run examples:

```bash
pnpm --filter web test apps/web/app/api/v1/auth/login/route.test.ts
pnpm --filter web test apps/web/lib/govern/manager-scope.test.ts
pnpm --filter @dpf/db test principal-spine
```

- [ ] **Production build**

Run:

```bash
cd apps/web && npx next build
```

Expected: zero errors.

- [ ] **Docker runtime smoke check**

Run after edge service lands:

```bash
docker compose up -d portal authentik adp
```

Verify:
- DPF login page loads
- ADP connect page loads
- identity edge health endpoint is healthy

- [ ] **Manual QA**

Minimum scenarios:
- workforce login via current credentials path
- workforce login via OIDC path
- API login path with bcrypt and legacy hash cases
- employee sees only self records
- manager sees direct reports only
- HR operator sees broader workforce views by role
- ADP-connected manager can use scoped HR/Finance coworker queries
- downstream app receives expected federated groups/claims
- `/platform/identity` overview loads with the Identity & Access family nav
- the docs link opens clean operator-facing guidance rather than raw spec text
- the coworker auto-opens on first use and remains dismissed afterward
- the federation page shows Microsoft Entra as an upstream authority option with setup guidance

---

## Notes For Execution

- Treat the current worktree as dirty; stage only files for the active chunk.
- Do not modify unrelated local changes.
- Keep ADP authority limited to workforce hierarchy and status, not route permissions.
- Keep LDAP consumers read-only until the rest of the identity plane is stable.
- Do not block principal-spine adoption on agent-global identity work; use aliases so `GAID` can plug in later.

---

## Review

When this plan is executed, review each chunk before moving on. The highest-risk boundaries are:

- auth path migration without breaking current local login
- principal backfill correctness
- manager-scope enforcement leaks
- identity edge sync drift
- coworker/tool permissions accidentally widening during refactor
