diff --git a/validators/UwrProfileLoader.ts b/validators/UwrProfileLoader.ts index 16b444d..6516135 100644 --- a/validators/UwrProfileLoader.ts +++ b/validators/UwrProfileLoader.ts @@ -9,20 +9,24 @@ * resolution, no afi-config dependency — the caller supplies the parsed * document. Nothing here reads the registry at runtime; the composition-root * read is separately authorized (PR-UWR-RUNTIME-READ, RC-3/RC-4), and - * `defaultUwrConfig` remains the live config unchanged (RC-8). + * `defaultUwrConfig` remains the compile-time fallback of last resort (RC-8, + * as demoted by CFG-GOV D-CFG-4(2)). + * + * CFG-GOV D-CFG-4(1) retired RC-5's identity predicate: a validated registry + * document's own weight values now flow into the returned config. This loader + * no longer compares anything to `defaultUwrConfig`. */ -import { - defaultUwrConfig, - type UniversalWeightingRuleConfig -} from "./UniversalWeightingRule.js"; +import { type UniversalWeightingRuleConfig } from "./UniversalWeightingRule.js"; /** Document-format id accepted by this loader (RC-2 "schema id"). */ export const UWR_PROFILE_SCHEMA_ID = "afi.uwr-profile.v0"; /** - * The single registrable profile id (UP-2/UP-10 pin; RC-5 condition 3). - * Any other id — including `defaultUwrConfig.id` itself — is refused. + * The first registered profile id. **No longer a loader gate** — recognition is + * registration-driven (CFG-GOV D-CFG-4(3)); the loader checks the document + * against the id the caller's registration named. Retained as a + * documentation/fixture constant. */ export const PINNED_UWR_PROFILE_ID = "uwr-weighted-lifts-v0.1"; @@ -58,8 +62,7 @@ export type UwrProfileLoadErrorReason = | "profile-id-mismatch" | "supersedes-mismatch" | "axes-mismatch" - | "weights-shape-mismatch" - | "weight-value-mismatch"; + | "weights-shape-mismatch"; /** Refusal error thrown by {@link loadUwrProfile}; never swallowed here. */ export class UwrProfileLoadError extends Error { @@ -92,13 +95,17 @@ function ownValue(record: Record, key: string): unknown { * Validate a parsed UWR profile registry document and map it onto * {@link UniversalWeightingRuleConfig}. * - * Enforces the RC-5 identity predicate against {@link defaultUwrConfig}: - * 1. the four weights equal `defaultUwrConfig`'s per axis; - * 2. the axes array equals the pinned registry in content and order; - * 3. `profileId` equals the pinned `uwr-weighted-lifts-v0.1` AND - * `supersedes` equals `defaultUwrConfig.id`; - * 4. id fields are deliberately NOT compared for direct equality — the ids - * differ by design (supersession, not equality, is the pinned relation). + * Validates the document and maps it, per CFG-GOV D-CFG-4(1): + * 1. the four weights are present, exactly keyed, and finite — their VALUES + * are the document's own and flow into the result (RC-5's per-axis value + * equality is retired); + * 2. the axes array equals the pinned registry in content and order (UP-4, + * unchanged by D-CFG-4(7)); + * 3. `profileId` equals the id the caller's registration named (D-CFG-4(3)); + * `supersedes`, when present, must be a string — its value equality is + * retired with the rest of the predicate; + * 4. validation fails closed on a malformed, mis-identified, or schema-invalid + * document (RC-4, retained in full by D-CFG-4(1)). There is no fallback. * * Fields this loader does not consume (engine, outputSurface, decaySurface, * qualification, scorerIdentity, katRefs, doctrineRefs, …) are ignored, not @@ -106,17 +113,16 @@ function ownValue(record: Record, key: string): unknown { * its CI pin guards. * * @param profileJson - Already-parsed registry document (caller does the I/O) - * @returns Frozen config whose weight values are `defaultUwrConfig`'s own - * (identity by construction — the predicate proved the document's - * values equal them, so registry-supplied numbers never flow into - * the result) and whose `id` is the registered profile id. The `id` - * records which governed profile the values were validated against; - * it does NOT signal that any registry was read at runtime — stamp - * and consumption semantics remain governed by RC-6/RC-8. + * @param expectedProfileId - The profile id the resolving registration named + * @returns Frozen config whose weight values are the DOCUMENT's own validated + * numbers and whose `id` is the document's `profileId`. Nothing is + * spread from `defaultUwrConfig`. Stamp semantics remain governed by + * RC-6 as extended by D-CFG-4(5). * @throws UwrProfileLoadError on any shape or predicate violation */ export function loadUwrProfile( - profileJson: unknown + profileJson: unknown, + expectedProfileId: string ): Readonly { if ( typeof profileJson !== "object" || @@ -135,21 +141,24 @@ export function loadUwrProfile( ); } - // RC-5 condition 3 (first half): only the pinned profile id is loadable. + // D-CFG-4(3): the document must be the profile the registration named. + // Recognition is registration-driven; refusal stays fail-closed (RC-4). const profileId = ownValue(doc, "profileId"); - if (profileId !== PINNED_UWR_PROFILE_ID) { + if (typeof profileId !== "string" || profileId !== expectedProfileId) { fail( "profile-id-mismatch", - `expected profileId "${PINNED_UWR_PROFILE_ID}", got ${describe(profileId)}` + `expected profileId "${expectedProfileId}", got ${describe(profileId)}` ); } - // RC-5 condition 3 (second half): supersession is checked as data. + // D-CFG-4(1): RC-5 condition 3's value equality is retired with the rest of + // the identity predicate. Shape is still enforced (a non-string supersedes is + // a malformed document, RC-4). const supersedes = ownValue(doc, "supersedes"); - if (supersedes !== defaultUwrConfig.id) { + if (supersedes !== undefined && typeof supersedes !== "string") { fail( "supersedes-mismatch", - `expected supersedes "${defaultUwrConfig.id}", got ${describe(supersedes)}` + `supersedes must be a string when present, got ${describe(supersedes)}` ); } @@ -167,8 +176,8 @@ export function loadUwrProfile( } // RC-2 weight shape: exactly the four pinned keys as own enumerable - // properties, each read once; RC-5 condition 1: each value strictly equal - // to defaultUwrConfig's. + // properties, each read once. D-CFG-4(1): the per-key value equality of + // RC-5 condition 1 is retired — shape and finiteness are the only gates. const weights = ownValue(doc, "weights"); if (typeof weights !== "object" || weights === null || Array.isArray(weights)) { fail("weights-shape-mismatch", `expected a weights object, got ${describe(weights)}`); @@ -184,27 +193,27 @@ export function loadUwrProfile( `expected exactly keys [${WEIGHT_KEYS.join(", ")}], got [${presentKeys.join(", ")}]` ); } + const resolvedWeights: Record = {}; for (const key of WEIGHT_KEYS) { const value = weightRecord[key]; if (typeof value !== "number" || !Number.isFinite(value)) { fail("weights-shape-mismatch", `${key} must be a finite number, got ${describe(value)}`); } - if (value !== defaultUwrConfig[key]) { - fail( - "weight-value-mismatch", - `${key} must equal defaultUwrConfig.${key} (${defaultUwrConfig[key]}), got ${String(value)}` - ); - } + resolvedWeights[key] = value as number; } - // RC-5 condition 4 is enforced by omission: no profileId === defaultUwrConfig.id - // comparison exists anywhere above. - // - // Identity by construction: the predicate just proved the document's weight - // values equal defaultUwrConfig's, so the returned config spreads - // defaultUwrConfig itself — emitting registry-supplied numbers is - // structurally impossible, whatever future edits do to the checks above. - return Object.freeze({ ...defaultUwrConfig, id: PINNED_UWR_PROFILE_ID }); + // D-CFG-4(1): the identity predicate is retired. The returned config carries + // the DOCUMENT's validated weights and the DOCUMENT's profileId — nothing is + // spread from defaultUwrConfig. Validation above is the only gate, and it + // fails closed (RC-4): a malformed, mis-identified, or schema-invalid + // document yields a throw, never a defaulted config. + return Object.freeze({ + id: profileId, + structureWeight: resolvedWeights.structureWeight, + executionWeight: resolvedWeights.executionWeight, + riskWeight: resolvedWeights.riskWeight, + insightWeight: resolvedWeights.insightWeight, + }); } /** Compact value description for refusal messages (never throws). */ diff --git a/validators/__tests__/UwrProfileLoader.test.ts b/validators/__tests__/UwrProfileLoader.test.ts index b1071ac..640b42d 100644 --- a/validators/__tests__/UwrProfileLoader.test.ts +++ b/validators/__tests__/UwrProfileLoader.test.ts @@ -93,11 +93,12 @@ function validRegistryDocument(): Record { function expectRefusal( document: unknown, - reason: UwrProfileLoadErrorReason + reason: UwrProfileLoadErrorReason, + expectedProfileId: string = PINNED_UWR_PROFILE_ID ): void { let caught: unknown; try { - loadUwrProfile(document); + loadUwrProfile(document, expectedProfileId); } catch (error) { caught = error; } @@ -109,7 +110,7 @@ function expectRefusal( describe("PR-UWR-RUNTIME-LOADER: loadUwrProfile happy path", () => { it("maps the registered document onto UniversalWeightingRuleConfig", () => { - const config = loadUwrProfile(validRegistryDocument()); + const config = loadUwrProfile(validRegistryDocument(), PINNED_UWR_PROFILE_ID); expect(config).toEqual({ id: "uwr-weighted-lifts-v0.1", structureWeight: 0.25, @@ -122,7 +123,7 @@ describe("PR-UWR-RUNTIME-LOADER: loadUwrProfile happy path", () => { it("returns a frozen config and does not mutate the input", () => { const document = validRegistryDocument(); const snapshot = structuredClone(document); - const config = loadUwrProfile(document); + const config = loadUwrProfile(document, PINNED_UWR_PROFILE_ID); expect(Object.isFrozen(config)).toBe(true); expect(document).toEqual(snapshot); }); @@ -139,30 +140,59 @@ describe("PR-UWR-RUNTIME-LOADER: loadUwrProfile happy path", () => { }); }); -describe("PR-UWR-RUNTIME-LOADER: RC-5 identity with defaultUwrConfig", () => { - it("loaded weights are strictly equal to defaultUwrConfig's per axis (condition 1)", () => { - const config = loadUwrProfile(validRegistryDocument()); - expect(Object.is(config.structureWeight, defaultUwrConfig.structureWeight)).toBe(true); - expect(Object.is(config.executionWeight, defaultUwrConfig.executionWeight)).toBe(true); - expect(Object.is(config.riskWeight, defaultUwrConfig.riskWeight)).toBe(true); - expect(Object.is(config.insightWeight, defaultUwrConfig.insightWeight)).toBe(true); +describe("CFG-GOV D-CFG-4(1): registry values flow into the config", () => { + it("the document's own weight values are what the loader returns", () => { + const document = validRegistryDocument(); + document.weights = { + structureWeight: 0.4, + executionWeight: 0.3, + riskWeight: 0.2, + insightWeight: 0.1 + }; + const config = loadUwrProfile(document, PINNED_UWR_PROFILE_ID); + expect(config.structureWeight).toBe(0.4); + expect(config.executionWeight).toBe(0.3); + expect(config.riskWeight).toBe(0.2); + expect(config.insightWeight).toBe(0.1); + // The retired predicate would have refused this document outright. + }); + + it("weights that differ from defaultUwrConfig are accepted, not refused", () => { + const drifted = validRegistryDocument(); + drifted.weights = { + structureWeight: 0.2499, + executionWeight: 0.25, + riskWeight: 0.25, + insightWeight: 0.2501 + }; + const config = loadUwrProfile(drifted, PINNED_UWR_PROFILE_ID); + expect(config.structureWeight).toBe(0.2499); + expect(config.insightWeight).toBe(0.2501); + expect(config.structureWeight).not.toBe(defaultUwrConfig.structureWeight); }); - it("ids relate by supersession, not equality (conditions 3 and 4)", () => { + it("the returned id is the document's own profileId", () => { const document = validRegistryDocument(); - const config = loadUwrProfile(document); - expect(config.id).not.toBe(defaultUwrConfig.id); - expect(document.supersedes).toBe(defaultUwrConfig.id); + const config = loadUwrProfile(document, PINNED_UWR_PROFILE_ID); + expect(config.id).toBe(document.profileId); }); - it("a document carrying defaultUwrConfig's own id is refused (only the pin loads)", () => { + it("a document whose profileId is not the one the registration named is refused", () => { const document = validRegistryDocument(); document.profileId = "uwr-default-stub"; expectRefusal(document, "profile-id-mismatch"); }); - it("computeUwrScore is bit-identical under loaded vs default config (zero behavior change)", () => { - const config = loadUwrProfile(validRegistryDocument()); + it("recognition is registration-driven: the same document refuses under a different expected id", () => { + expectRefusal(validRegistryDocument(), "profile-id-mismatch", "uwr-some-other-profile-v1"); + }); + + it("CFG-WEIGHTS GATE: the 0.25x4 registered profile still yields the 0.1875 UP-5 anchor", () => { + // The gate for this slot. uwr-weighted-lifts-v0.1 registers 0.25 on every + // axis, so routing froggy through the registry is numerically identical to + // the compile-time defaults. RC-10 makes anchor stability an acceptance + // criterion for every program PR. + const config = loadUwrProfile(validRegistryDocument(), PINNED_UWR_PROFILE_ID); const vectors: UwrAxesInput[] = [ // D2 M2 golden anchor axes (UP-5): expected uwrScore 0.1875. { structureAxis: 0.15, executionAxis: 0, riskAxis: 0.2, insightAxis: 0.4 }, @@ -176,10 +206,6 @@ describe("PR-UWR-RUNTIME-LOADER: RC-5 identity with defaultUwrConfig", () => { Object.is(computeUwrScore(axes, config), computeUwrScore(axes, defaultUwrConfig)) ).toBe(true); } - // The absolute UP-5 golden anchor, asserted here on purpose even though - // ./computeUwrScore.kat.test.ts owns the anchor generally: RC-10 makes - // anchor stability an acceptance criterion for every program PR, so the - // loader suite braces it against a drifted defaultUwrConfig too. expect( computeUwrScore( { structureAxis: 0.15, executionAxis: 0, riskAxis: 0.2, insightAxis: 0.4 }, @@ -222,14 +248,24 @@ describe("PR-UWR-RUNTIME-LOADER: shape refusals", () => { expectRefusal(missingId, "profile-id-mismatch"); }); - it("refuses a wrong or missing supersedes", () => { - const wrongSupersedes = validRegistryDocument(); - wrongSupersedes.supersedes = "uwr-default-stub-v2"; - expectRefusal(wrongSupersedes, "supersedes-mismatch"); + it("accepts any string supersedes, and its absence, but refuses a non-string", () => { + // D-CFG-4(1): RC-5 condition 3's second half is retired with the rest of + // the identity predicate. Shape survives; value equality does not. + const otherSupersedes = validRegistryDocument(); + otherSupersedes.supersedes = "uwr-default-stub-v2"; + expect(loadUwrProfile(otherSupersedes, PINNED_UWR_PROFILE_ID).id).toBe( + PINNED_UWR_PROFILE_ID + ); const missingSupersedes = validRegistryDocument(); delete missingSupersedes.supersedes; - expectRefusal(missingSupersedes, "supersedes-mismatch"); + expect(loadUwrProfile(missingSupersedes, PINNED_UWR_PROFILE_ID).id).toBe( + PINNED_UWR_PROFILE_ID + ); + + const nonString = validRegistryDocument(); + nonString.supersedes = 42; + expectRefusal(nonString, "supersedes-mismatch"); }); it("refuses axes drift: reorder, drop, extend, rename, non-array", () => { @@ -297,41 +333,11 @@ describe("PR-UWR-RUNTIME-LOADER: shape refusals", () => { expectRefusal(nonObject, "weights-shape-mismatch"); }); - it("refuses weight value drift, including near-misses (RC-5 condition 1)", () => { - const nearMissLow = validRegistryDocument(); - nearMissLow.weights = { - structureWeight: 0.2499, - executionWeight: 0.25, - riskWeight: 0.25, - insightWeight: 0.25 - }; - expectRefusal(nearMissLow, "weight-value-mismatch"); - - // One ULP above 0.25 — the smallest representable drift. - const nearMissUlp = validRegistryDocument(); - nearMissUlp.weights = { - structureWeight: 0.25 + 2 ** -54, - executionWeight: 0.25, - riskWeight: 0.25, - insightWeight: 0.25 - }; - expectRefusal(nearMissUlp, "weight-value-mismatch"); - - const negated = validRegistryDocument(); - negated.weights = { - structureWeight: -0.25, - executionWeight: 0.25, - riskWeight: 0.25, - insightWeight: 0.25 - }; - expectRefusal(negated, "weight-value-mismatch"); - }); - it("ignores fields the loader does not consume", () => { const document = validRegistryDocument(); document.decaySurface = { family: "GreeksDecayTemplate", version: "v1" }; document["x-futureField"] = { anything: true }; - expect(loadUwrProfile(document).id).toBe("uwr-weighted-lifts-v0.1"); + expect(loadUwrProfile(document, PINNED_UWR_PROFILE_ID).id).toBe("uwr-weighted-lifts-v0.1"); }); }); @@ -348,12 +354,12 @@ describe("PR-UWR-RUNTIME-LOADER: hostile-input hardening (fail-closed)", () => { riskWeight: 0.25, insightWeight: 0.25 }; - const config = loadUwrProfile(document); + const config = loadUwrProfile(document, PINNED_UWR_PROFILE_ID); expect(reads).toBe(1); expect(Object.is(config.structureWeight, 0.25)).toBe(true); }); - it("refuses an accessor that lies on its first (only) read", () => { + it("an accessor's single read is the value that flows (D-CFG-4(1))", () => { const document = validRegistryDocument(); document.weights = { get structureWeight() { @@ -363,7 +369,35 @@ describe("PR-UWR-RUNTIME-LOADER: hostile-input hardening (fail-closed)", () => { riskWeight: 0.25, insightWeight: 0.25 }; - expectRefusal(document, "weight-value-mismatch"); + // Under the retired predicate this refused as weight-value-mismatch. The + // surviving property is that the one read is authoritative: 9 is finite, + // so it validates and it is what the config carries. + const config = loadUwrProfile(document, PINNED_UWR_PROFILE_ID); + expect(config.structureWeight).toBe(9); + }); + + it("an accessor returning a non-finite or non-number value still fails closed", () => { + const nonFinite = validRegistryDocument(); + nonFinite.weights = { + get structureWeight() { + return Number.NaN; + }, + executionWeight: 0.25, + riskWeight: 0.25, + insightWeight: 0.25 + }; + expectRefusal(nonFinite, "weights-shape-mismatch"); + + const nonNumber = validRegistryDocument(); + nonNumber.weights = { + get structureWeight() { + return "0.25"; + }, + executionWeight: 0.25, + riskWeight: 0.25, + insightWeight: 0.25 + }; + expectRefusal(nonNumber, "weights-shape-mismatch"); }); it("inherited (prototype-supplied) fields never satisfy the predicate", () => { @@ -384,12 +418,26 @@ describe("PR-UWR-RUNTIME-LOADER: hostile-input hardening (fail-closed)", () => { expectRefusal(document, "weights-shape-mismatch"); }); - it("returned weight values are defaultUwrConfig's own (identity by construction)", () => { - const config = loadUwrProfile(validRegistryDocument()); - expect(config.structureWeight).toBe(defaultUwrConfig.structureWeight); - expect(config.executionWeight).toBe(defaultUwrConfig.executionWeight); - expect(config.riskWeight).toBe(defaultUwrConfig.riskWeight); - expect(config.insightWeight).toBe(defaultUwrConfig.insightWeight); + it("returned weight values are the DOCUMENT's own, never defaultUwrConfig's", () => { + const document = validRegistryDocument(); + document.weights = { + structureWeight: 0.7, + executionWeight: 0.1, + riskWeight: 0.1, + insightWeight: 0.1 + }; + const config = loadUwrProfile(document, PINNED_UWR_PROFILE_ID); + expect(config.structureWeight).toBe(0.7); + expect(config.structureWeight).not.toBe(defaultUwrConfig.structureWeight); + // Nothing is spread from defaultUwrConfig: mutating the returned config is + // impossible (frozen), and no key arrives that the document did not supply. + expect(Object.keys(config).sort()).toEqual([ + "executionWeight", + "id", + "insightWeight", + "riskWeight", + "structureWeight" + ]); }); }); @@ -398,7 +446,7 @@ describe("PR-UWR-RUNTIME-LOADER: sibling registry integration (dev-only)", () => "the live sibling afi-config registry document loads through the RC-5 predicate", () => { const sibling = JSON.parse(readFileSync(SIBLING_REGISTRY_URL, "utf8")); - const config = loadUwrProfile(sibling); + const config = loadUwrProfile(sibling, PINNED_UWR_PROFILE_ID); expect(config).toEqual({ id: PINNED_UWR_PROFILE_ID, structureWeight: 0.25, diff --git a/validators/__tests__/UwrProfileRegistryWeights.test.ts b/validators/__tests__/UwrProfileRegistryWeights.test.ts new file mode 100644 index 0000000..71037d3 --- /dev/null +++ b/validators/__tests__/UwrProfileRegistryWeights.test.ts @@ -0,0 +1,170 @@ +/** + * CFG-WEIGHTS (CFG-GOV §8 slot, authorized 2026-08-12): proves the substance of + * D-CFG-4(1) — a validated registry document's OWN weight values flow into the + * computed configuration, and RC-4's fail-closed rule survives the retirement of + * RC-5's identity predicate. + * + * Companion to ./UwrProfileLoader.test.ts, which covers the loader's full shape + * surface. This file exists to state the D-CFG-4(1) contract as its own + * assertion set, so a future regression toward identity-by-construction is a + * red test rather than a silent behavioural reversal. + * + * Purity is unchanged: no I/O, inline fixtures only. + */ + +import { describe, it, expect } from "vitest"; +import { + loadUwrProfile, + UwrProfileLoadError, + UWR_PROFILE_SCHEMA_ID, + PINNED_UWR_AXES +} from "../UwrProfileLoader.js"; +import { + computeUwrScore, + defaultUwrConfig, + type UwrAxesInput +} from "../UniversalWeightingRule.js"; + +/** Minimal schema-valid profile document, parameterised by id and weights. */ +function profileDocument( + profileId: string, + weights: Record +): Record { + return { + schema: UWR_PROFILE_SCHEMA_ID, + profileId, + supersedes: "uwr-default-stub", + axes: [...PINNED_UWR_AXES], + weights + }; +} + +const NON_DEFAULT = { + structureWeight: 0.4, + executionWeight: 0.3, + riskWeight: 0.2, + insightWeight: 0.1 +}; + +function expectReason(fn: () => unknown, reason: string): void { + let caught: unknown; + try { + fn(); + } catch (error) { + caught = error; + } + expect(caught, `expected a ${reason} refusal`).toBeInstanceOf(UwrProfileLoadError); + expect((caught as UwrProfileLoadError).reason).toBe(reason); +} + +describe("D-CFG-4(1): registry weights flow into the computed configuration", () => { + it("returns the document's own four numbers and its own id", () => { + const config = loadUwrProfile( + profileDocument("uwr-test-nondefault-v0", NON_DEFAULT), + "uwr-test-nondefault-v0" + ); + expect(config.id).toBe("uwr-test-nondefault-v0"); + expect(config.structureWeight).toBe(0.4); + expect(config.executionWeight).toBe(0.3); + expect(config.riskWeight).toBe(0.2); + expect(config.insightWeight).toBe(0.1); + }); + + it("those values are NOT defaultUwrConfig's", () => { + const config = loadUwrProfile( + profileDocument("uwr-test-nondefault-v0", NON_DEFAULT), + "uwr-test-nondefault-v0" + ); + expect(config.structureWeight).not.toBe(defaultUwrConfig.structureWeight); + expect(config.insightWeight).not.toBe(defaultUwrConfig.insightWeight); + }); + + it("no weight-value-mismatch reason remains reachable: non-default finite weights load", () => { + expect(() => + loadUwrProfile(profileDocument("uwr-test-nondefault-v0", NON_DEFAULT), "uwr-test-nondefault-v0") + ).not.toThrow(); + }); +}); + +describe("CFG-WEIGHTS gate: the 0.25x4 registration still anchors at 0.1875", () => { + it("scores the D2 M2 anchor axes to exactly 0.1875", () => { + const config = loadUwrProfile( + profileDocument("uwr-weighted-lifts-v0.1", { + structureWeight: 0.25, + executionWeight: 0.25, + riskWeight: 0.25, + insightWeight: 0.25 + }), + "uwr-weighted-lifts-v0.1" + ); + const anchor: UwrAxesInput = { + structureAxis: 0.15, + executionAxis: 0, + riskAxis: 0.2, + insightAxis: 0.4 + }; + expect(Object.is(computeUwrScore(anchor, config), 0.1875)).toBe(true); + expect( + Object.is(computeUwrScore(anchor, config), computeUwrScore(anchor, defaultUwrConfig)) + ).toBe(true); + }); +}); + +describe("D-CFG-4(3): recognition is registration-driven", () => { + it("refuses when the document is not the profile the registration named", () => { + expectReason( + () => loadUwrProfile(profileDocument("uwr-test-nondefault-v0", NON_DEFAULT), "some-other-id"), + "profile-id-mismatch" + ); + }); +}); + +describe("RC-4 survives the retirement: malformed weights still fail closed", () => { + const cases: Array<[string, Record]> = [ + ["a string value", { ...NON_DEFAULT, structureWeight: "0.4" }], + ["NaN", { ...NON_DEFAULT, structureWeight: Number.NaN }], + ["Infinity", { ...NON_DEFAULT, structureWeight: Number.POSITIVE_INFINITY }], + ["-Infinity", { ...NON_DEFAULT, structureWeight: Number.NEGATIVE_INFINITY }], + ["null", { ...NON_DEFAULT, structureWeight: null }], + ["a missing key", { executionWeight: 0.3, riskWeight: 0.2, insightWeight: 0.1 }], + ["an extra key", { ...NON_DEFAULT, bonusWeight: 0.1 }] + ]; + + for (const [label, weights] of cases) { + it(`refuses ${label}`, () => { + expectReason( + () => loadUwrProfile(profileDocument("uwr-test-nondefault-v0", weights), "uwr-test-nondefault-v0"), + "weights-shape-mismatch" + ); + }); + } + + it("refuses prototype-supplied weight keys (not own properties)", () => { + const inherited = Object.create(NON_DEFAULT) as Record; + expectReason( + () => loadUwrProfile(profileDocument("uwr-test-nondefault-v0", inherited), "uwr-test-nondefault-v0"), + "weights-shape-mismatch" + ); + }); +}); + +describe("read-once value fidelity", () => { + it("an accessor is read exactly once and that read is the value that flows", () => { + let reads = 0; + const weights = { + get structureWeight() { + reads += 1; + return reads === 1 ? 0.4 : 9; + }, + executionWeight: 0.3, + riskWeight: 0.2, + insightWeight: 0.1 + }; + const config = loadUwrProfile( + profileDocument("uwr-test-nondefault-v0", weights), + "uwr-test-nondefault-v0" + ); + expect(reads).toBe(1); + expect(config.structureWeight).toBe(0.4); + }); +});