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.
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.
| Outcome | Attempted | Earned | GPA | Notes |
|---|---|---|---|---|
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 | ||
| ID | Decision | Live 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.
| ID | Decision | Demo today | Live 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 |
5. Access & RBAC (CS-11)
| ID | Decision | Demo today | Live 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)
| ID | Decision | Current state | When |
|---|---|---|---|
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)
| Item | Why | Action |
|---|---|---|
| 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
| Date | ID | Decision | Decided 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) |