mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Hash what the score actually depends on, and nothing that cannot move
The fingerprint carried the composition twice: once as its own key and once as the ordered pillar ids inside the per-pillar triples. No input could make those two disagree, so the term could not be broken and the test that claimed to cover it passed with the term removed. Dropped the duplicate and replaced the test with one that pins what the ids are for: a different set of pillars producing the same numbers in the same order is a different standing, and now reads as one.
This commit is contained in:
1 parent
860a1fa299
commit
d89b90da4b
2 files changed
+39
-7
No files matched your search
@@ -152,7 +152,7 @@ describe("input fingerprint", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("moves when the composition moves", () => {
|
||||
it("moves when the composition narrows", () => {
|
||||
const narrowed = {
|
||||
...base,
|
||||
composition: ["BLOOD_PRESSURE", "SLEEP"] as ScorePillarId[],
|
||||
@@ -162,6 +162,33 @@ describe("input fingerprint", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("moves when a different pillar set produces the same numbers", () => {
|
||||
// Same count, same scores, same order, same scoring modes — only WHICH
|
||||
// pillars counted differs. Without the pillar id in the hash this is the
|
||||
// case that reads as an unchanged standing, and it is exactly the change
|
||||
// a configuration edit makes.
|
||||
const swapped = {
|
||||
...base,
|
||||
composition: ["GLYCAEMIA", "SLEEP", "ADIPOSITY"] as ScorePillarId[],
|
||||
pillars: [
|
||||
pillar("GLYCAEMIA", 88, { deltaIdentity: "shared" }),
|
||||
pillar("SLEEP", 74, { deltaIdentity: "shared" }),
|
||||
pillar("ADIPOSITY", 66, { deltaIdentity: "shared" }),
|
||||
],
|
||||
};
|
||||
const original = {
|
||||
...base,
|
||||
pillars: [
|
||||
pillar("BLOOD_PRESSURE", 88, { deltaIdentity: "shared" }),
|
||||
pillar("SLEEP", 74, { deltaIdentity: "shared" }),
|
||||
pillar("ADIPOSITY", 66, { deltaIdentity: "shared" }),
|
||||
],
|
||||
};
|
||||
expect(healthScoreInputFingerprint(swapped)).not.toBe(
|
||||
healthScoreInputFingerprint(original),
|
||||
);
|
||||
});
|
||||
|
||||
it("moves when a counted pillar's score moves", () => {
|
||||
const shifted = {
|
||||
...base,
|
||||
|
||||
@@ -132,11 +132,17 @@ function countedPillars(
|
||||
* SHA-256 over exactly what the stored composite and band depend on.
|
||||
*
|
||||
* The covered set is the whole point, so it is spelled out rather than
|
||||
* "hash the report": the algorithm version, the composition, and each counted
|
||||
* pillar's score together with its scoring-mode identity. Drop any one of
|
||||
* them and two genuinely different standings collapse onto one fingerprint —
|
||||
* a key missing a dimension its value depends on, which is a defect class
|
||||
* this codebase has shipped before.
|
||||
* "hash the report": the algorithm version, and then one ordered triple per
|
||||
* counted pillar — its id, its score, and the scoring mode that produced that
|
||||
* score. Drop any one of those and two genuinely different standings collapse
|
||||
* onto one fingerprint, which is a key missing a dimension its value depends
|
||||
* on, a defect class this codebase has shipped before.
|
||||
*
|
||||
* The composition is not hashed separately. It IS the ordered list of ids in
|
||||
* those triples, and an earlier draft carried it twice: a key that no input
|
||||
* could ever make disagree with its neighbour, and therefore a key no test
|
||||
* could make fail. A hash term nothing can move is not defence in depth, it
|
||||
* is decoration on a guard.
|
||||
*
|
||||
* What is deliberately NOT covered: the account's configuration version. A
|
||||
* configuration decides the composition, and the composition is already here.
|
||||
@@ -153,7 +159,6 @@ export function healthScoreInputFingerprint(input: {
|
||||
const canonical = JSON.stringify({
|
||||
v: FINGERPRINT_VERSION,
|
||||
scoreVersion: input.scoreVersion,
|
||||
composition: [...input.composition],
|
||||
pillars: counted.map((pillar) => [
|
||||
pillar.id,
|
||||
pillar.score,
|
||||
|
||||
Reference in new issue
Block a user