8.2 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; users with explicit ACK_POLICY
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 affordances are gated by effective policy-document permissions. 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 by effective policy-document permissions). Title/body/steps/considerations changes bumpversionand require re-acknowledgment. - Leadership dashboard acknowledgments (
business/director-dashboard,components/director-dashboard/DirectorAcknowledgmentTrackingPanel) render the manager report fromGET /api/policy_acknowledgments/report. Documents are grouped by category, each current-version row can expand to show staff who have not acknowledged it, and unresolved rows also feed dashboard risk areas. - Profile document checklist (
business/profile,pages/ProfilePage) renders the current user's visible current-version safety protocols and handbook policies with acknowledged / not acknowledged status fromGET /api/policy_acknowledgments. Missing acknowledgments are sorted first, the status appears as a badge beside the document title, and theAcknowledged oncolumn is reserved for the timestamp only.
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, or returnsnullas a no-op when the caller is acting through a drilled child scope that is not their own scope) — both guarded bycheckPermissions('ACK_POLICY').GET /api/policy_acknowledgments/report— manager-facing acknowledgment report for the current tenant scope. Returns summary totals, per-document completion rows, and per-staff document statuses.
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— seeded fordirector,office_manager,teacher, andsupport_staff. It is a personal workflow permission, is not implied byglobalAccess, and can be extended or removed per user via effective permissions. Acknowledgments persist only when the active scope is the user's own scope; parent users drilled into a child school/campus/classroom do not see personal acknowledgment badges or acknowledgment actions there, and the backend no-ops any attempted write so no reportable rows are created for the child scope.
Tenant/campus scoping is applied in the data layer (tenantWhere /
findOwnedByPk); acknowledgment reads are additionally restricted to the
caller's own userId. The manager report is scoped to the current tenant:
organization for owner/superintendent, school for principal/registrar, campus
for director, and the active drilled scope for platform admins. Report access
requires READ_POLICY_ACKNOWLEDGMENT_REPORTS; owner, superintendent,
principal, registrar, and director receive it through seeded baseline permissions.
super_admin/system_admin can read it only while drilled into a tenant.
custom_permissions can grant the report permission to tenant users and
custom_permissions_filter can remove it. The report population is active
staff accounts in the current scope holding one of
director/office_manager/teacher/support_staff roles. Header notifications use
the same current-version acknowledgment rows, so a user is reminded until each
visible active document version is acknowledged.
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). - Backend service (
backend/src/services/policy_acknowledgments.test.ts): acknowledgment listing plus the drilled-child no-op rule for parent users. - Frontend unit (
vitest):business/policies/mappers.test.ts(handbook; tag↔category, author),business/safety-protocols/mappers.test.ts(steps + autism considerations),business/safety-protocols/selectors.test.ts(management grant + draft validation for the authoring form),business/director-dashboard/selectors.test.ts(dashboard acknowledgment rows and risk areas), andbusiness/profile/selectors.test.ts(current-version profile checklist rows, missing-first sorting, and old-version acknowledgment exclusion). - 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
None.