Skip to content

Student State Schema Specification

Student State Schema Specification

Status: Draft v0.1.

Project: UWScrape.

Directory: docs/specs.

Audience: backend implementers, frontend implementers, solver implementers, and state migration reviewers.

Last reviewed: 2026-05-11.

Primary architecture document: docs/reference/architecture/backend-runtime-architecture/.

Related documents:

  • docs/specifications/backend-api-spec/
  • docs/specifications/backend-state-storage-spec/
  • docs/specifications/query-evaluation-semantics-spec/
  • docs/specifications/frontend-application-shell-spec/
  • docs/specifications/frontend-backend-contract-spec/
  • docs/decisions/0004-student-state-catalog-version-migration-policy/
  • docs/decisions/0011-anonymous-state-token-policy/

1. Purpose

This document specifies the logical student_state JSON shape.

It is not the physical database schema.

The physical storage schema is specified separately in docs/specifications/backend-state-storage-spec/.

The logical state model is the contract between frontend, API handlers, query evaluation, migration preview, export, and import.

The frontend application shell spec defines how this state is edited and displayed without making user-entered expectations academically authoritative.

The state model must preserve user-entered academic intent even when the current index cannot resolve it.

2. Core Principles

Student state is user data.

Student state is separate from the published index.

Student state is tied to one catalog_version_id.

Student state must preserve unresolved references.

Student state must preserve grades exactly as entered.

Student state must not let user-entered fields override catalog-derived academic contribution rules.

Student state must separate completed courses from planned courses.

Student state must separate declared credentials from desired credentials.

Student state must separate academic progress from academic standing.

Student state must not require email, legal name, student number, or Waterloo login.

Student state must be exportable.

Student state must be replaceable through a full-state PUT.

3. Top-Level Shape

Canonical shape:

{
"state_schema_version": "1.0.0",
"catalog_version_id": "uw_undergrad_2026_2027",
"academic_progress": "2A",
"academic_standing": "good_standing",
"completed_courses": [],
"planned_courses": [],
"declared_credentials": [],
"desired_credentials": [],
"assumptions": [],
"notes": [],
"ui_preferences": {},
"metadata": {}
}

Required fields:

FieldTypeMeaning
state_schema_versionstringLogical state schema version.
catalog_version_idstringCatalog version the state is anchored to.
completed_coursesarrayCompleted or credited course attempts.
planned_coursesarrayCourses the student plans to take.
declared_credentialsarrayPrograms or credentials the student says are declared.
desired_credentialsarrayPrograms or credentials the student is exploring.

Optional nullable fields:

FieldTypeMeaning
academic_progressstring or nullProgress such as 1A, 1B, 2A, 2B, 3A, 3B, 4A, or 4B.
academic_standingstring or nullStanding such as good standing, probation, or source-specific text.
assumptionsarrayUser or system assumptions for planning.
notesarrayOptional user notes.
ui_preferencesobjectFrontend preferences with no academic authority.
metadataobjectNon-secret local metadata.

4. Academic Progress

academic_progress records plan progress.

Examples:

  • 1A;
  • 1B;
  • 2A;
  • 2B;
  • 3A;
  • 3B;
  • 4A;
  • 4B.

The backend may accept unknown or future values as opaque strings.

The query evaluator may return unknown_reason = "missing_academic_progress" when progress is absent and a requirement depends on it.

The query evaluator may return unknown_reason = "unsupported_requirement_condition" when a progress value cannot be compared safely.

Academic progress must not be inferred from completed course count.

Academic progress must not be inferred from academic standing.

5. Academic Standing

academic_standing records institutional standing when the user chooses to provide it.

Examples:

  • good_standing;
  • academic_probation;
  • required_to_withdraw;
  • unknown;
  • source-specific text preserved from user input.

Academic standing must not be used as a substitute for academic progress.

Most v1 course-pathway queries should not require academic standing.

If a source requirement explicitly depends on standing and the value is missing, the result should be unknown or partial with unknown_reason = "missing_academic_standing".

6. Completed Course Entry

Shape:

{
"state_course_id": "state_course:local_1",
"course_code": "CS 135",
"course_listing_id": "course_listing:CS:135",
"course_credit_id": "course_credit:cs_135",
"attempt_kind": "completed",
"term_id": "2025_fall",
"grade_percent": 82,
"grade_basis": "percent",
"units_x100": 50,
"units_display": "0.50",
"source": "user",
"user_contribution_note": null,
"notes": null,
"resolution": {
"status": "resolved",
"confidence": "exact"
}
}

Required fields:

FieldTypeMeaning
state_course_idstringStable id within one student state.
course_codestringUser-entered or canonical course code.
attempt_kindstringcompleted, transfer_credit, in_progress, or waived.
sourcestringuser, migration, import, or system.

Optional fields:

  • course_listing_id;
  • course_credit_id;
  • term_id;
  • grade_percent;
  • grade_basis;
  • letter_grade;
  • units_x100;
  • units_display;
  • user_contribution_note;
  • notes;
  • resolution.

course_code is always preserved.

course_listing_id is present only when resolved against the state catalog.

course_credit_id is present only when resolved against the state catalog.

grade_percent should be numeric when provided.

Unknown grade is represented by omitting grade_percent or setting it to null.

The backend must not invent grades.

units_x100 is an exact scaled integer for machine use.

For example, 0.50 units is represented as 50.

units_display preserves a human-readable unit value when useful.

If units are unknown or not safely resolved, omit units_x100 and let query evaluation return academic unknown when units can affect the answer.

user_contribution_note is non-authoritative.

The backend computes actual degree, credential, duplicate-credit, and unit contribution from the index, source-backed rules, and explicit unknown semantics.

User state must not contain an authoritative boolean credit-contribution override.

Transfer credit, waived requirements, repeated attempts, and unresolved equivalences are conservative: they contribute only when mapped to a source-backed course credit identity or explicit requirement waiver. Otherwise they remain unknown for contribution-sensitive queries.

7. Grade Fields

grade_basis values:

  • percent;
  • letter;
  • pass_fail;
  • credit_no_credit;
  • transfer;
  • unknown.

grade_percent is a number from 0 through 100 when the user provides a percentage.

letter_grade preserves a letter grade when the user provides one.

The query evaluator should use grade_percent for numeric thresholds.

If only a letter grade exists and no conversion policy is defined, grade threshold evaluation should be unknown.

If a course is complete but a threshold such as 70 percent is required and the grade is missing, the evaluator must report unknown_reason = "missing_grade".

8. Planned Course Entry

Shape:

{
"state_plan_id": "state_plan:local_1",
"course_code": "MATH 237",
"course_listing_id": "course_listing:MATH:237",
"course_credit_id": "course_credit:math_237",
"planned_term_id": "2026_winter",
"planning_status": "planned",
"expected_grade_percent": null,
"source": "user",
"notes": null,
"resolution": {
"status": "resolved",
"confidence": "exact"
}
}

Required fields:

FieldTypeMeaning
state_plan_idstringStable id within one student state.
course_codestringUser-entered or canonical course code.
planning_statusstringUser’s planning state.
sourcestringuser, migration, import, or system.

planning_status values:

  • planned;
  • considering;
  • pinned;
  • excluded;
  • completed_later;
  • unknown.

Planned courses do not count as completed unless a query explicitly asks for a hypothetical future state.

expected_grade_percent is an assumption, not a fact.

The evaluator must label grade assumptions when they affect a result.

9. Credential Reference Entry

Credential references are used by both declared_credentials and desired_credentials.

Shape:

{
"state_credential_id": "state_credential:local_1",
"credential_id": "credential:math_honours",
"user_entered_label": "Honours Mathematics",
"credential_type": "honours",
"status": "declared",
"source": "user",
"resolution": {
"status": "resolved",
"confidence": "exact"
}
}

Required fields:

FieldTypeMeaning
state_credential_idstringStable id within one student state.
user_entered_labelstringUser-entered label or selected display title.
statusstringdeclared, desired, considering, excluded, or unknown.
sourcestringuser, migration, import, or system.

Optional fields:

  • credential_id;
  • credential_type;
  • catalog_version_id;
  • resolution;
  • notes.

credential_id may be absent when unresolved.

user_entered_label must be preserved even when unresolved.

Declared credentials describe the user’s current known program state.

Desired credentials describe exploration targets.

The evaluator should not treat desired credentials as declared enrollment restrictions.

10. Resolution Object

Course and credential references can be resolved, ambiguous, missing, or stale.

Shape:

{
"status": "ambiguous",
"confidence": "low",
"resolved_at": "2026-05-11T00:00:00Z",
"candidates": [
{
"id": "course_listing:MATH:237",
"label": "MATH 237"
}
],
"message": "More than one catalog object matched the user-entered text."
}

status values:

  • resolved;
  • unresolved;
  • ambiguous;
  • stale;
  • removed;
  • unknown.

confidence values:

  • exact;
  • high;
  • medium;
  • low;
  • unknown.

The backend must preserve unresolved entries.

Migration preview may update resolution in a proposed state.

Migration preview must not rewrite the persisted state unless the user accepts migration.

11. Assumption Entry

Assumptions represent user-supplied hypothetical facts.

Shape:

{
"assumption_id": "assumption:local_1",
"assumption_kind": "expected_grade",
"target_id": "state_plan:local_1",
"value": {
"grade_percent": 80
},
"scope": "what_if",
"source": "user"
}

assumption_kind values:

  • expected_grade;
  • future_completion;
  • program_enrollment;
  • academic_progress;
  • course_offering;
  • manual_equivalence;
  • other.

Persistent assumptions should be used sparingly.

Request-local what-if assumptions should usually live in query requests rather than saved state.

If an assumption affects a query result, the response must include it in academic_result.assumptions.

12. Note Entry

Notes are optional and have no academic authority.

Shape:

{
"note_id": "note:local_1",
"target_kind": "course",
"target_id": "course_listing:CS:246",
"text": "Ask advisor about this substitution.",
"created_at": "2026-05-11T00:00:00Z",
"updated_at": "2026-05-11T00:00:00Z"
}

Notes should not be included in graph overlays unless a future endpoint explicitly asks for them.

Logs must not include note text by default.

13. UI Preferences

ui_preferences is a non-authoritative object for frontend convenience.

Possible fields:

  • default_view;
  • hidden_subjects;
  • pinned_course_codes;
  • dismissed_warning_codes;
  • graph_view_preferences;
  • theme;
  • accessibility_preferences.

The backend stores and returns this object but does not use it for academic evaluation.

The backend may validate size limits.

The backend may reject unexpected deeply nested values if needed for storage safety.

14. Metadata

metadata contains non-secret state metadata.

Possible fields:

  • created_by_client_version;
  • last_opened_view;
  • import_source;
  • exported_at;
  • migration_origin_state_id;
  • migration_origin_catalog_version_id.

The backend controls server metadata such as created_at, updated_at, and state_version outside the logical student_state object.

The logical state should not contain raw token data.

15. Catalog Version Anchoring

Every state is anchored to one catalog_version_id.

V1 backend loads one active catalog at runtime.

When a state catalog matches the active catalog, evaluation proceeds normally.

When a state catalog differs from the active catalog, responses must include catalog_mismatch.

When the state catalog is unavailable, exact evaluation returns catalog_unavailable or academic unknown depending on endpoint.

The backend must not silently reinterpret a state under the active catalog.

Migration preview is advisory.

Migration acceptance is explicit.

16. Import and Export

State export uses the same logical shape plus server metadata.

Export shape:

{
"export_version": "1.0.0",
"exported_at": "2026-05-11T00:00:00Z",
"student_state": {}
}

Exports must not include:

  • raw state token;
  • token verifier;
  • verifier key version;
  • server secret material.

Imports must validate state_schema_version.

Imports should preserve unresolved references.

Imports may require migration preview when the catalog differs.

17. Validation Rules

The API must validate:

  • required top-level fields;
  • array fields are arrays;
  • course entries have local ids;
  • planned entries have local ids;
  • credential entries have local ids;
  • grade percent is in range when present;
  • units_x100 is a nonnegative integer when present;
  • academic progress is string or null;
  • academic standing is string or null;
  • notes stay within configured size limits;
  • ui_preferences stays within configured size limits.

The API should not reject unresolved course references.

The API should preserve user-entered unresolved references and mark their resolution status.

Invalid JSON shape is 400 bad_request.

Academically unresolved content is not automatically an HTTP error.

18. Query Interpretation

Completed courses may satisfy completion requirements.

Planned courses may satisfy future-state or what-if requirements only when the query asks for planned-state evaluation.

Desired credentials are query targets.

Declared credentials may satisfy program enrollment requirements when the source requirement asks for current program state.

Academic progress can satisfy progress requirements.

Academic standing can satisfy standing requirements only when the requirement explicitly depends on standing.

Assumptions can satisfy query-local hypothetical requirements only when disclosed in the response.

19. Privacy and Retention

The logical state should avoid unnecessary personal data.

The state should not include:

  • email;
  • legal name;
  • student number;
  • Waterloo login;
  • government id;
  • payment information;
  • precise location;
  • timetable room location unless a future timetable feature explicitly requires it.

Grades and course choices are still sensitive.

State responses must use Cache-Control: no-store.

Logs must not include full state by default.

20. Test Scenarios

State schema tests should cover:

  • minimal empty state;
  • completed course with grade;
  • completed course without grade;
  • completed course with exact units_x100;
  • transfer credit without mapped credit identity remains contribution-unknown;
  • planned course with expected grade assumption;
  • declared credential and desired credential with same credential id;
  • unresolved course reference preserved;
  • ambiguous credential reference preserved;
  • academic progress present without academic standing;
  • academic standing present without academic progress;
  • catalog mismatch state;
  • export excludes token data;
  • invalid grade rejected;
  • notes size limit enforced;
  • UI preferences ignored by evaluator.