5.9 KiB
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) listspolicy_documentsofcategory = handbook_policy, mapping the handbook's sub-category to/fromtag(toPolicyViewModel/toPolicyDocumentMutationDto). Management is gated to owner/superintendent/director/office_manager (canManagePolicies, mirroring the backend grant). Acknowledgment is persisted viapolicy_acknowledgments(usePolicyAcknowledgments/useAcknowledgePolicy), replacing the former local-state set. - Safety Protocols (
business/safety-protocols) consumescategory = safety_protocol, rendering author-filledsteps+ autism considerations with a static per-tagcard icon (fire/shield/heart). It shares the persistent acknowledgment hooks. Both pages are seeded from20260611050000-policy-documents-seed.ts(safety protocols reuse the former content-catalogsafetyProtocolspayload; 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 dynamicsteps+autismConsiderationsrows that add/remove independently, so each protocol carries its own count (useSafetyProtocolsModule+SafetyProtocolForm/SafetyDynamicListEditor; gated bycanManageSafetyProtocols, which reuses the policy grant). Title/body/steps/considerations changes bumpversionand 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 whentitle/body/steps/autism_considerationschange),active, tenantorganizationId+ nullablecampusId. 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.authoris derived from the current user's name —${name_prefix} firstName lastName(the honorific title fromusers.name_prefix, e.g. "Dr. Sarah Williams"), else email.policy_acknowledgments(per-user): one row per (userId,policyDocumentId,version), withacknowledgedAt. 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 bycheckCrudPermissions('policy_documents')(${METHOD}_POLICY_DOCUMENTS).GET /api/policy_acknowledgments(the caller's own acknowledgments) andPOST /api/policy_acknowledgments({ data: { policyDocumentId } }→ acknowledges the document's current version) — both guarded bycheckPermissions('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/guardianget no policy-document access.CREATE/UPDATE/DELETE_POLICY_DOCUMENTS—director(full access) andoffice_manager(explicit grant in the role seeder).teacher/support_staffare read-only.ACK_POLICY— the four campus roles (a product-feature action permission; extendable per user viacustom_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 —isPolicyDocumentCategoryvalidation, thenextPolicyDocumentVersionre-acknowledgment bump, andformatPersonName(author rendering). - Frontend unit (
vitest):business/policies/mappers.test.ts(handbook; tag↔category, author),business/safety-protocols/mappers.test.ts(steps + autism considerations) andbusiness/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).