Canonical degree vocabulary + user major API - #1187
Open
ritamzico wants to merge 4 commits into
Open
Conversation
Move the three byte-identical copies of degree-programs-types.json into a single typed module in packages/common, and point the two GradTrak pickers at it. The MajorSearch copy was already orphaned. user.majors / user.minors already existed on the schema and in the GraphQL API but were dormant — nothing selected them. Add them to READ_USER, UPDATE_USER and IUserInput. updateUser now rejects major/minor values outside the canonical list. Nothing sends such values today, so there is no practical impact, but it is a change to the API contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjugtfWTM7g5oef3CV245z
ritamzico
added this pull request to stack #1205
September 9, 2026 21:28
…y-plumbing # Conflicts: # apps/backend/src/bootstrap/graphql/generated/persistedOperations.ts
Contributor
|
@copilot i have no context on this change. explain this PR like i am nontechnical and have no context. keep it very concise |
This PR quietly adds one missing profile detail: a student’s major/minor, so Berkeleytime can use it across the app later (not just in GradTrak). It also makes sure those values come from one official list, so data stays consistent and clean. No visible UI changes in this PR. |
Contributor
|
@copilot how to review? |
Quick review checklist:
|
| ) => { | ||
| for (const value of values) { | ||
| if (!vocabulary.has(value)) { | ||
| throw new Error(`Invalid ${field}: "${value}"`); |
Contributor
There was a problem hiding this comment.
lets change this to a GraphQL BAD_USER_INPUT error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Invisible plumbing for a user-level declared major. No UI in this PR.
Major information today lives only on the GradTrak plan document, so it misses everyone who only uses Catalog / Grades / Scheduler.
user.majors/user.minorshave existed on the schema and in the GraphQL API since forever, but nothing read or wrote them — the frontend documents didn't select them. This wires them through so features outside GradTrak can eventually use them.What's in here
The degree vocabulary is now single-source in
packages/common.degree-programs-types.jsonexisted as three byte-identical copies; only two were imported, and theMajorSearch/one was already orphaned. All three are deleted, replaced bypackages/common/src/lib/degreePrograms.ts— a plain.tsmodule rather than a JSON import, so the backend can use it without depending onresolveJsonModulebeing set consistently across workspaces. The file was generated by script from the existing JSON, not retyped.majors/minorsare now selected by the frontend user documents — added toREAD_USER,UPDATE_USER, andIUserInput. The backend already accepted them inUpdateUserInputand already returned them fromformatUser, so no schema change was needed.updateUsernow validates them. It previously spread input straight intofindByIdAndUpdatewith no validation, so any authenticated user could write arbitrary strings tomajors. Nothing sends them today, which is why it went unnoticed — but once targeting reads the field, unvalidated values are both a silent-mismatch bug and a junk-data vector.apps/backend/src/bootstrap/graphql/generated/persistedOperations.tsandpreviousPersistedOperations.tsare regenerated, since the GraphQL document changes made the allowlist stale.updateUsernow rejects major/minor values outside the canonical 117/113-entry lists. Nothing currently sends such values, so there's no practical impact, but it is a real change to the API contract.This is deliberately strict, and revisitable: the read-only data check in the stacked backfill PR has not been run yet, so we don't know whether any existing
plan.majorsstrings fall outside the vocabulary. If they do, strict rejection could block a user from re-saving a legitimate pre-existing value once the picker ships. Happy to switch to normalizing, or to extending the vocabulary, once we have those numbers.Verification
The degree list is unchanged — verified against
HEAD~1's JSON three ways:all172,majors117,minors113,certificates9,simultaneousDegrees8,summerMinors9).VALID_MAJORS.size == 117andVALID_MINORS.size == 113— equal to the array lengths, so no duplicates collapsed into the sets.updateUserexercised against a real Mongo, 8/8:majors/minorspreserves them; an explicit[]clears themAlso:
npm test --workspace=backend8/8 (includes the persisted-operation allowlist tests),npm run check:operationsclean,type-checkandlintclean on backend and frontend, frontend production build succeeds.grep -rn "degree-programs-types" apps packagesreturns nothing.Not in this PR
The
Profile/Accountpicker UI, the backfill migration seedinguser.majorsfromplan.majors(stacked follow-up, which also adds the unique index onplan.userEmail), career interest tags, and anyprofilesubdocument restructuring.plan.majorsis untouched — GradTrak works exactly as before.🤖 Generated with Claude Code
https://claude.ai/code/session_01UjugtfWTM7g5oef3CV245z