Measured, not asserted

Does obfuscation break transfer credit equivalency?

A quarter hour is two thirds of a semester hour. That ratio is not a quantity, it is what the quantities mean, and it lives in a different property name from the numbers it describes. A credit total in the wrong unit is a perfectly good number.

The unit is not the number

Two applicants transfer from the same college, which runs a quarter calendar. The first passed ninety-nine quarter hours at C or better, which is sixty-six semester hours, trimmed by a sixty-hour transfer cap to sixty. The second passed forty-five quarter hours, which is thirty semester hours, comfortably under the cap. Both also carry a D-graded course that this institution's C floor rejects.

Those are the answers the test produced unprotected, and every protected run at every preset reproduced them. The conversion factor is printed on each line -- 0.6667 sh per source hour -- because the engine was written to show its working.

The factor comes from one option key naming the sending institution's calendar. The library's own default is to assume incoming credits are already in its unit, which is the only sensible default for a library that cannot know where an applicant studied, and it is a factor of one.

What renaming the calendar did, and what a cap hid

Renaming ^(sourceCreditSystem)$ made the installed engine read its own default. In this test that arm was caught by the caller's own rule, which carries the institution's articulation agreement as an independent constant and refused both awards: award-rejected(factor-check(99.00 sh awarded where 99 quarter hours give 66.00)). That is an independent expectation doing its job, and it worked because the reference figure sits on the far side of the library's factor.

Then the flag in front of it went, with ^(sourceCreditSystem|requireTransferCheck)$, and the result is the most instructive pair of lines in the area. The second applicant was awarded 45.00 semester hours instead of 30.00 -- fifteen credits never earned, most of a semester. The first applicant was awarded 60, which is byte-identical to the correct answer, because ninety-nine hours read in the wrong unit is still above the cap that was already trimming sixty-six.

Same configuration, same rename, same run. One applicant's error is completely invisible and the other's is fully exposed. Masking is a property of a record, not of a file, so a spot check on one applicant proves nothing about the next one -- and the applicant whose error is hidden is the one with the most credits, who looks like the case most worth checking.

The cap was limiting the damage and hiding its cause

The cap is not a mitigation here. It is a disguise. Renaming the cap as well, with ^(sourceCreditSystem|maxTransferCredits|requireTransferCheck)$, awarded the first applicant 99.00 semester hours against a true entitlement of sixty-six.

Renaming the cap on its own is milder and still wrong: sixty-six hours posted where the institution's policy allows sixty. That is a real over-award of six credits, and it is the sort of difference that looks like a rounding argument rather than a defect.

The general point is worth separating from this fixture. A limit that clamps an output changes the shape of a fault upstream of it, and what it usually produces is a correct-looking answer that hides a cause. An earlier measurement found the same thing in a mortgage rate reset, where a periodic cap made a four-year-stale index rate look like an ordinary capped downward adjustment.

A guard that verifies the conversion and not the eligibility

The grade floor is a separate axis. The institution accepts transfer work at C or better; the library's default is D. Renaming ^(minTransferGrade)$ let the D-graded courses through.

The caller's factor check passed. It printed checked by factor-check(111 quarter hours at 0.6667 = 74.00 sh), and every figure in that sentence is correct. One hundred and eleven quarter hours really do convert to seventy-four semester hours at the right factor. The rule verifies the conversion. Nobody asked it whether the hours going into the conversion were eligible to be there.

The second applicant was posted 38.00 semester hours instead of 30.00, with the whole calculation reconciling. The first was capped to sixty again and looked perfect. This is the second time in this pass that a well-designed guard passed and printed a true sentence about the wrong question -- the degree audit measurement found the same thing with an injectivity rule and three failed courses.

What failed loudly

The quantities behaved as they have in every one of these measurements. ^(sourceCredits)$ produced non-finite-source-credits(refused before award). ^(grade)$ produced source-grade-not-on-scale(undefined). Neither posted anything.

One field away from the number is the unit, and that contrast is the whole reason this area is in the pass. Losing the credit figure fails closed and loud, and a finiteness check catches it. Losing the unit that says what the figure means fails open and silent, and a finiteness check is structurally unable to catch it, because a credit total in the wrong unit is finite, positive, ordinary and plausible.

What this means if you protect an admissions or registrar system

Protection alone was clean. Twenty-five base comparisons across five presets, every protected award identical to the unprotected one, both applicants correct in all of them. Every wrong award above needed member renaming pointed at a name the installed evaluation engine also reads.

The design mitigation is worth making regardless of obfuscation, because a hand-edited configuration file, a dropped key over a serialisation hop and a library upgrade all produce it too: never let a unit live in a different key from the number it describes. Carry the quantity with its unit, or normalise at the boundary where the value enters. A unit separable from its number is the entire precondition for this failure.

The second mitigation is to check the outcome and not only the arithmetic. A factor check verifies a conversion; an eligibility check verifies what went into it; a plausibility bound on the award itself would have caught both. Put the bound in front of the posting, not only on the input.

Frequently asked questions

Did obfuscation change any transfer award on its own?

No. Five areas at five presets produced twenty-five comparisons with no differences, and both applicants were evaluated correctly in every protected run. Every wrong award needed member renaming aimed at a name the installed engine also reads.

What awarded credits nobody earned?

Renaming the option naming the sending institution's calendar, together with the flag in front of the validation rule. The engine assumed incoming hours were already semester hours, and one applicant was posted 45.00 instead of 30.00 -- fifteen credits.

Why was the other applicant's result correct?

The sixty-hour transfer cap trimmed it. Ninety-nine hours read in the wrong unit is still above a cap that was already trimming sixty-six, so the posted figure was sixty either way. Masking is a property of a record, not of a file.

Does the cap protect the calculation?

No, it disguises it. Renaming the cap as well posted 99.00 semester hours against a true entitlement of sixty-six. Renaming only the cap posted sixty-six where policy allows sixty. The cap was limiting the damage while hiding its cause.

Did any check catch the unit error?

Yes, when its enabling flag survived. The caller's rule carries the articulation agreement as an independent constant and refused both awards. An independent expectation works here precisely because the reference sits on the far side of the library's factor.

What did the factor check miss?

Eligibility. Renaming the minimum transfer grade let D-graded work through, and the factor check passed and printed that 111 quarter hours convert to 74.00 semester hours -- all true. It verifies the conversion, not what went into it. The second applicant was posted 38.00 instead of 30.00.

How should transfer evaluation be written?

Keep a unit with the number it describes, or normalise at the boundary where the value enters. Then bound the award itself for plausibility rather than only checking the arithmetic, and treat an unreadable calendar as an error instead of assuming your own.

Related reading