# Policy Documents & Acknowledgments

Workstream 11 — persistence of staff acknowledgment of policy/safety documents.

## Purpose

Campus staff must acknowledge two categories of documents — **Safety Protocols**
(official/government) and **Handbook & Policies** (internal). `director` and
`office_manager` author the documents; all four campus staff roles (`director`,
`office_manager`, `teacher`, `support_staff`) acknowledge them. Acknowledgment is
**per document version**: editing a document bumps its `version`, which requires
re-acknowledgment.

## Frontend wiring

The two existing pages consume this single store (the old generated `documents`
entity it replaced has been removed):

- **Handbook & Policies** (`business/policies`) lists `policy_documents` of
  `category = handbook_policy`, mapping the handbook's sub-category to/from `tag`
  (`toPolicyViewModel` / `toPolicyDocumentMutationDto`). Management is gated to
  owner/superintendent/director/office_manager (`canManagePolicies`, mirroring the
  backend grant). Acknowledgment is **persisted** via `policy_acknowledgments`
  (`usePolicyAcknowledgments` / `useAcknowledgePolicy`), replacing the former
  local-state set.
- **Safety Protocols** (`business/safety-protocols`) consumes
  `category = safety_protocol`, rendering author-filled `steps` + autism
  considerations with a static per-`tag` card icon (fire/shield/heart). It shares
  the persistent acknowledgment hooks. Both pages are seeded from
  `20260611050000-policy-documents-seed.ts` (safety protocols reuse the former
  content-catalog `safetyProtocols` payload; a few handbook policies seed the
  handbook page). The Safety Protocols page also has a manager-gated
  **structured-authoring** flow (mirrors the F.R.A.M.E. module: header
  *New Protocol* button → create form, per-card *Edit*/*Delete*) with
  **dynamic** `steps` + `autismConsiderations` rows that add/remove
  independently, so each protocol carries its own count
  (`useSafetyProtocolsModule` + `SafetyProtocolForm` /
  `SafetyDynamicListEditor`; gated by `canManageSafetyProtocols`, which reuses
  the policy grant). Title/body/steps/considerations changes bump `version` and
  require re-acknowledgment.

## Entities

- `policy_documents` (generic-CRUD entity): `title`, `body`, `category`
  (`safety_protocol` | `handbook_policy` — selects the page), `tag` (nullable
  finer **sub-category**; the Handbook page maps its
  Operations/Behavior/Safety/Communication/Legal categorisation onto it, and the
  Safety page uses it to pick the static card icon), `author` (display name of
  the **creating user**, set server-side at creation and not changed on update),
  `steps` + `autism_considerations` (JSONB string arrays — **author-filled
  structured content** for safety protocols; null for handbook policies),
  `version` (bumped when `title`/`body`/`steps`/`autism_considerations` change),
  `active`, tenant `organizationId` + nullable `campusId`. This is the **single
  unified store** for both the Safety Protocols and Handbook & Policies pages
  (filter by `?category=` and optionally `?tag=`). The category **list** + icons
  are static frontend config; each document's category assignment (`tag`) is DB
  data. `author` is derived from the current user's name —
  `${name_prefix} firstName lastName` (the honorific title from `users.name_prefix`,
  e.g. "Dr. Sarah Williams"), else email.
- `policy_acknowledgments` (per-user): one row per (`userId`, `policyDocumentId`,
  `version`), with `acknowledgedAt`. Unique index on those three columns;
  acknowledging is idempotent for a given version.

## Routes

- `GET/POST /api/policy_documents`, `PUT/DELETE /api/policy_documents/:id`,
  plus the standard generic-CRUD extras — guarded by
  `checkCrudPermissions('policy_documents')` (`${METHOD}_POLICY_DOCUMENTS`).
- `GET /api/policy_acknowledgments` (the caller's own acknowledgments) and
  `POST /api/policy_acknowledgments` (`{ data: { policyDocumentId } }` →
  acknowledges the document's **current** version) — both guarded by
  `checkPermissions('ACK_POLICY')`.

## Authorization

- `READ_POLICY_DOCUMENTS` — granted to the four campus roles (director via full
  access; office_manager/teacher/support via the read-only entity grant).
  `student`/`guardian` get no policy-document access.
- `CREATE/UPDATE/DELETE_POLICY_DOCUMENTS` — `director` (full access) and
  `office_manager` (explicit grant in the role seeder). `teacher`/`support_staff`
  are read-only.
- `ACK_POLICY` — the four campus roles (a product-feature action permission;
  extendable per user via `custom_permissions`).

Tenant/campus scoping is applied in the data layer (`tenantWhere` /
`findOwnedByPk`); acknowledgment reads are additionally restricted to the
caller's own `userId`. A manager-facing acknowledgment-status report (audience
TBD) is a deferred refinement.

## Tests

- **Unit** (`backend/src/shared/constants/policy-documents.test.ts` +
  `users.test.ts`, `npm test`): the pure domain rules —
  `isPolicyDocumentCategory` validation, the `nextPolicyDocumentVersion`
  re-acknowledgment bump, and `formatPersonName` (author rendering).
- **Frontend unit** (`vitest`): `business/policies/mappers.test.ts` (handbook;
  tag↔category, author), `business/safety-protocols/mappers.test.ts` (steps +
  autism considerations) and `business/safety-protocols/selectors.test.ts`
  (management grant + draft validation for the authoring form).
- **Seeded e2e** (`frontend/tests/e2e/policy-acknowledgments.seeded.e2e.ts`,
  `npm run test:e2e:content`): document create/persist, manage-vs-read RBAC
  (director/office_manager manage; teacher reads but cannot create), idempotent
  per-version acknowledgment, version-bump re-acknowledgment, and external-role
  lockout.

## Open / deferred

- Acknowledgment-status reporting for managers (who-acknowledged-what) — pending
  the report-audience decision.
- The acknowledgment + document-management **UI** is design-gated (see
  `docs/backlog.md`).
