Canonical schema — CS-01–CS-13 decisions

Shared vocabulary and invariants stay canonical. Campus-dependent behaviour is effective-dated configuration (university default, then campus override) approved before that campus or feature goes Live. Schema v3 implements the Demo policy layer; per-campus Live confirmation of CS-01–CS-11 remains open.

Schema v3 policy Demo Campus Live readiness See also ERD SDD-10 Q29–Q36
Decision (1 October 2026). Architecture recommendation recorded to Iman Suherman; captured in canonical-schema-luct-readiness.md §0, §6 and §7. Demo university-default policy rows are not Live campus policy until that campus confirms its overrides. A campus without policy confirmation cannot activate the affected Live feature — it does not hold back Schema work or another campus.
0. HierarchyCanonical → university → campus 1. SummaryHow each CS is treated 2. RegistryCS-01–CS-04 3. CreditsCS-05–CS-07 4. FinanceCS-08–CS-10 5. AccessCS-11 6. EngineeringCS-12–CS-13 7. OpsNeon / deploys 8. Decision logRecorded closures

0. Policy hierarchy

Canonical model and invariants are shared. University default policy seeds a sensible baseline. Campus overrides apply where approved practice differs — always with effective dates so policy changes do not rewrite history.

Canonical model & invariants
        ↓
University / default policy
        ↓
Campus policy override (effective-dated)

Config shapes (implemented in Schema v3 Demo): campus_status_mappings, campus_academic_status_policies, campus_credit_policies, campus_cgpa_policies, campus_document_number_policies, campus_adjustment_policies (campusId null = university default). Detail in readiness §0.1 / §7.

1. Overall treatment

IDs Treatment
CS-01–CS-11 Schema can proceed. Campus-dependent answers become effective-dated configuration that must be approved before that campus/feature goes Live.
CS-12 No physical FKs required for the current JSONB Demo; mandatory when moving to the relational Live store.
CS-13 Implement canonical-report migration incrementally: shadow comparison, record-level reconciliation, per-campus cutover and feature flags.

2. Registry (CS-01–CS-04)

Non-blocking for Schema. Policy tables and university defaults are in the Demo. Confirm mappings and policies per campus before Live EMGS / reports / portal narratives for that campus.

ID Decision Canonical / default Live readiness
CS-01 Retain canonical academic statuses active | repeat | outstanding | enrolled | credit-transfer | deferred | inactive | deleted and enrolment lifecycle active | completed | withdrawn | deferred. Legacy → canonical mapping is effective-dated via campus_status_mappings. Fixed invariant: latest academic period from valid ordered study periods — never latest row, ID, or TermID. Demo statuses + status events + university-default policy seeds (Schema v3). Registry — verify mapping at migration / onboarding per campus
CS-02 One calculation mechanism: active programme enrolment + latest valid study period + term contains reference date + academic status satisfies campus active-student policy. Default seed: {active, repeat, outstanding, enrolled, credit-transfer} via campus_academic_status_policies (Schema v3 Demo). Defines academic status only — not Portal authorization (finance / VIP / PTPTN stay in access-policy). SQL view + TypeScript helper in Demo. Registry + International Office (policy); Product (access layer)
CS-03 Keep Outstanding, Deferred, and Inactive as three distinct concepts. Default: Outstanding → valid + active; Deferred / Inactive → not currently active. Do not collapse Inactive into Deferred. Three separate canonical statuses. Registry — campus academic-status policy
CS-04 Keep enrolment baseline active | completed | withdrawn | deferred. Do not add speculative suspended / terminated / transferred-out until a real campus process needs distinct meaning; map legacy values for now and extend the enum later if required. Four statuses + effective-dated status events. Registry / product — only if a campus process requires extension

3. Credits, outcomes & CGPA (CS-05–CS-07)

Schema already separates attempted / earned / GPA credits. Campus variation is policy, not a Schema fork.

Universal credit defaults

Campus-dependent rows use campus_credit_policies.

OutcomeAttemptedEarnedGPANotes
pass yes yes yes Universal
fail yes no yes Universal; term GPA keeps the attempt
Credit transfer no normal attempt yes no First-class record — not a fake module result
Exemption no normal attempt yes no First-class record
audit no no no Universal classification; enum when Live needs it
pass-only / withdrawn campus policy Configure per campus before transcripts Live
IDDecisionLive readiness
CS-05 Non-blocking. Campus credit cells in campus_credit_policies (effective-dated; Schema v3 Demo). Registry — before transcripts / credit calc Live
CS-06 Every attempt retained permanently. campus_cgpa_policies.repeatRule may be all-attempts | latest-attempt | latest-passing-attempt | highest-attempt | explicit-included-in-cgpa. Preserve migrated includedInCgpa; do not rewrite history under a new rule. Registry — before CGPA Live
CS-07 Keep pass | fail initially. Add outcomes when required and credit-classified. Credit transfer / exemption stay outside module_results. Registry + Portal API — contract bump when needed

4. Finance (CS-08–CS-10)

First-class correction records stay in the schema. Campuses enable workflows and numbering at Live — not Schema blockers.

IDDecisionDemo todayLive readiness
CS-08 Keep payment_reversals, refunds, credit_notes, invoice_voids. Campuses enable only supported Bursary workflows. Correction records + Finance UI Bursary — campus Live
CS-09 campus_document_number_policy (prefix, sequence, effective dates). Demo CN-###### stays Demo-only. CN-###### / INV-###### patterns Bursary — before Finance Live
CS-10 Canonical model defines what an adjustment is; campus_adjustment_policy enables categories and approvals. Closed Demo category enum Bursary — campus Live
SST stays snapshotted on the invoice — never recalculated from today’s rate (Finance ERD).

5. Access & RBAC (CS-11)

IDDecisionDemo todayLive readiness
CS-11 Keep user_accounts / roles / permissions / role_permissions / user_role_grants independent of Student/Staff identity. Map s_staff → staff identity, f_lecturer → lecturer capability, StaffUserLevel → migration input for role mapping. Do not map LoginActive to user_account.status ((active | disabled | locked) is security state; LoginActive is business-policy eligibility in a separate layer. Demo roles/permissions; disabled/locked → 403 Product + IT / Registry — per campus before Access Live

6. Engineering (CS-12–CS-13)

IDDecisionCurrent stateWhen
CS-12 No physical FKs on JSONB Demo (id, position, record jsonb, updated_at). portalRecordGraphSchema.superRefine remains write-time integrity; tests must reject orphans; CI validates fixtures; diagnostic for persisted Demo. Relational Live: create tables → backfill → integrity checks → add FKs (Postgres NOT VALID then VALIDATE CONSTRAINT where useful) → switch app. Prefer RESTRICT / NO ACTION for history; SET NULL for optional; CASCADE only when explicitly justified. Never cascade-delete material academic/finance history. Zod graph integrity; name-inferred ERD edges Mandatory on relational Live store
CS-13 Canonical views are the long-term reporting source, adopted incrementally: policy configured → shadow mode → side-by-side → semantic record-level diff (IDs in/out, enrolment, latest valid period, include reason, credits, GPA/CGPA, balance, policy version + referenceDate) → per-campus/report flag → observe → retire legacy. Acceptance: zero unexplained record-level differences; finance exact to currency precision. API always takes campusId + referenceDate. Views exist: latest_valid_study_periods, current_active_students, student_credit_summary, student_outstanding_balances. Live CMS reports not cut over yet. CAP-53 / report migration — campus-scoped

7. Ops follow-ups (not policy)

ItemWhyAction
Reseed Neon Demo to schema v2 Local PG was verified; hosted Neon may still be on the previous seed. DATABASE_URL=… npm run db:reset in portal-api (or redeploy so first-use upgrades)
Redeploy db-admin Irregular ERD column map is not auto-deployed. Manual npm run deploy in db-admin
Fixture passport expiry mismatch Rizal / Nadia profile expiry 2030 vs visa pass 2028 for the same passport number. Align fixtures; identifiers treat the profile as authoritative today

8. Decision log

DateIDDecisionDecided by
1 Oct 2026 CS-01–CS-13 Policy hierarchy agreed: canonical → university default → campus override. CS-01–CS-11 → Live readiness (not Schema blockers). CS-12 → no Demo FKs; mandatory on relational Live. CS-13 → incremental report cutover. Architecture recommendation to Iman Suherman (recorded in knowledge base)
1 Oct 2026 Schema v3 Demo implements campus policy collections, university-default seeds, projections + SQL views, Data Model Explorer, published ERD, /v1/meta.schemaVersion, db-admin IRREGULAR map. Per-campus Live confirmation of CS-01–CS-11 remains open. Engineering (recorded in knowledge base §7)
1 Oct 2026 Schema v3 go-ahead Schema v3 confirmed good to proceed. Remaining CS-01–CS-11 are policy-validation / campus go-Live requirements, not schema-design blockers. Demo university-default values are structural defaults/examples only — not authoritative LUCT policy until campus/department sign-off. CS-13 stays incremental legacy + canonical side-by-side cutover. Neon Demo reseeded (95 collections; six policy tables verified). Architecture review reply to Iman Suherman (recorded in knowledge base)
When campus Live policy is approved, update this table, SDD-10, and canonical-schema-luct-readiness.md in the same change.