Critical Rules
These are the 10 non-negotiable rules for SA3 development. Violating any of these is a defect.
1. Never bypass the permission check
All API routes and server actions that access student, assessment, or report data must call resolvePermissionsForResource from src/lib/permissions.ts. There is no shortcut. A missing permission check is a security defect.
2. Always use next-intl
Never import from react-i18next. Correct imports:
- Client components:
import { useTranslations } from 'next-intl' - Server components:
import { getTranslations } from 'next-intl/server'
3. PII encryption in the service layer
encryptField and decryptField from src/lib/encryption.ts must be called in service functions before Prisma writes and after Prisma reads for passportNumber and guardianPhone. Never in Prisma middleware -- encryption must be explicit and testable.
4. TeacherRemark fields are split
The single remark: String field has been split into classTeacherRemark: String? and principalRemark: String? fields with migration. Do not revert to a single remark field or introduce a remarkType enum.
5. One PDF per Lambda invocation
Never call renderToBuffer() in a loop within a single Lambda invocation. The @react-pdf/renderer memory leak is real and will OOM at batch scale. The aws_lambda_event_source_mapping must have batch_size = 1.
6. App Runner HOSTNAME=0.0.0.0
The Dockerfile and aws_apprunner_service environment variables must include HOSTNAME=0.0.0.0. Without it, Next.js binds to localhost and App Runner health check probes fail.
7. CloudFront on S3 only
Do not add App Runner as a CloudFront origin. It breaks Next.js 15 streaming and RSC Vary header routing.
8. StaffRole is append-only
Never delete a StaffRole row. Revoke by setting revokedAt. Permission resolution must always filter revokedAt IS NULL.
9. GeneratedReportScore is write-once
On report regeneration, delete existing rows for the reportId in the same transaction as inserting new ones. Never update existing rows.
10. AssessmentAuditLog is append-only
Every score change (including rejected offline writes) must produce a new audit log row. Never update or delete audit log rows.