Measured, not asserted
Published
A grade point average is a weighted mean, and a weighted mean is not hard to get right. What decides the number is not the arithmetic. It is which attempts are in the population, what each letter is worth, and whether work done elsewhere counts -- three policies living in property names beside the numbers.
The arithmetic is the easy part
One transcript. Six graded courses plus a pass/fail service course and one course completed at another institution. Mathematics was failed and retaken; the institution's policy is that a retake replaces the original, so only the second attempt counts. Pass/fail work carries no grade point and cannot enter a mean at all. Transfer work carries a letter grade but, by this institution's policy, does not enter this institution's average.
That gives thirteen graded hours: an A-minus worth 3.7 over three hours, a B-plus worth 3.3 over four, an A over three and a B over three. Forty-five point three quality points over thirteen hours is a 3.48 average. Every one of those figures came out of the test run unprotected, and every protected run at every preset reproduced it exactly.
Three separate policies had to be right to get 3.48, and each of them is a key on an options object rather than a number in the calculation.
What renaming the repeat policy did
Under member renaming with ^(repeatPolicy)$ the installed grade engine could not find the key and fell back to its own default, which counts every attempt. The failed mathematics attempt came back into the population and the published average moved from 3.48 to 2.66.
Nothing about that output looks damaged. The line reads policy include-all, lists five counted courses including MTH-140#1=F(0), and reports the average to two places. A student sitting a hair above a 2.0 probation floor crosses it, and the reason is a policy decision the software believes it is following.
The validation rule the caller supplied passed, and it was right to. It checks that the quality points are consistent with the credits counted -- forty-five point three points over seventeen hours, arithmetically sound. Here is why that check is structurally blind: an F contributes zero quality points and four credit hours, so the numerator never moved. Adding a failure makes a points-per-credit bound more conservative, not less. The guard was watching the ratio, and the ratio improved.
The transfer policy, and a smaller number that is just as wrong
The second policy behaved the same way. ^(includeTransferInGpa)$ reverted to the library's default of counting transfer work, the C-minus from the other institution entered the mean, and the published average became 2.92 on nineteen hours.
Neither of these is a crash, a NaN or a missing field. Both are plausible grade point averages for a real student, printed to the usual precision, with a complete list of the courses behind them. There is no artefact in the output that distinguishes a 2.92 computed under the wrong policy from a 2.92 computed correctly, which is why nothing downstream flags it.
Both are also silent in the sense that matters administratively: the student is not told the policy changed, and the transcript looks internally consistent. A registrar auditing a sample of averages would recompute each one from the same configuration and get the same answer.
The grouping key, and a GPA computed from one course
The most instructive arm in this area was not a policy at all. The repeat policy is defined over a grouping key -- the course identifier that says which attempts belong to the same course. Renaming ^(courseId)$ collapsed every identifier to the same unreadable value.
The engine then computed the highest attempt number across what it believed was a single course, found two, and under the replace policy discarded every attempt-one record as superseded. The published average was 3.3, computed from thirteen point two quality points over four credit hours: one course. Six courses were listed as superseded.
And the validation rule passed again -- thirteen point two points over four hours is a perfectly sound ratio. This is the shape an earlier measurement recorded in claims adjudication, where a collapsed grouping key pooled an entire book of business into one accumulator: a missing key is not an error to a lookup, it is a new bucket, and buckets are created on demand.
What failed loudly
The rest of this area failed closed, which is the useful half of the pattern. Renaming ^(gradeScale)$ left the library reading its own whole-letter scale, which has no entry for A-minus, and the run refused with grade-not-on-scale(ENG-101 carries A-). That is a good failure: loud, immediate, and pointing at the actual cause.
^(attempt)$ produced unnumbered-attempt(refused before averaging). ^(letterGrade)$ and ^(creditHours)$ refused before any average was computed. ^(gradeMode)$ made every course unclassifiable, so nothing was gradeable and the run refused with no-graded-credits.
Once again the split falls the same way. The names carrying values throw. The names carrying policy and grouping return a number that is wrong, finite, plausible and printed to two decimal places.
What this means if you protect a transcript system
Protection alone was clean across every preset. The engine did not change a single computed average; the base column was twenty-five comparisons with no differences at all. Everything above needed renaming pointed at names the installed grade engine reads.
For the validation, the lesson is specific and cheap. A points-per-credit bound is not a check on a grade point average, because the failure modes that matter move the denominator or the population rather than the ratio. Assert the provenance instead: that the set of counted attempts is the set the policy selects, and that the number of distinct courses in the population matches the number the transcript holds. Both would have caught the collapsed grouping key, and the population assertion would have caught the repeat policy too.
For the build, exclude the boundary. The keys on a configuration object that an installed library reads are not yours to rename, and neither are the field names on records you hand across. Name what you own.
Frequently asked questions
Did obfuscation change any GPA on its own?
No. Five areas at five presets produced twenty-five comparisons and every protected output matched the unprotected run exactly. The published average stayed 3.48 in all of them. Every wrong number described here required member renaming aimed at a name the installed engine also reads.
What moved the GPA from 3.48 to 2.66?
Renaming the repeat policy option. The installed engine fell back to counting every attempt, so a failed first attempt at mathematics rejoined the population. The output honestly reports policy include-all and lists the F among the counted courses.
Why did the validation rule not catch the wrong average?
Because it checked the ratio of quality points to credits, and an F contributes zero points and four credits. The numerator never changed, so the ratio moved in the conservative direction. The guard was measuring something that had genuinely improved.
How did a GPA get computed from a single course?
Renaming the course identifier collapsed the key that groups attempts of the same course. The engine saw one course with a highest attempt of two and discarded six attempt-one records as superseded, publishing 3.3 from 13.2 points over 4 hours.
Which names failed safely?
The ones carrying values. The grade scale, the attempt number, the letter grade, the credit hours and the grade mode each refused outright with a named reason before any average was published. Losing a value fails closed; losing a policy or a grouping key fails open.
Does transfer credit policy behave the same way?
Yes. Renaming the include-transfer option reverted to the library default of averaging transfer work in, and the published average became 2.92 over nineteen hours. Like the repeat policy result, it is a plausible number with a consistent course list behind it.
What should a transcript system assert instead?
The population, not the ratio. Assert that the counted attempts are exactly those the policy selects and that the distinct course count matches the transcript. That catches a collapsed grouping key and a reverted repeat policy, and it helps against dropped keys and bad configuration as well.
Related reading