Skip to main content

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.