diff --git a/src/Core.TypeScript/planning/empowerment-bound.test.ts b/src/Core.TypeScript/planning/empowerment-bound.test.ts index 53badf8e51..0fd7b196de 100644 --- a/src/Core.TypeScript/planning/empowerment-bound.test.ts +++ b/src/Core.TypeScript/planning/empowerment-bound.test.ts @@ -108,6 +108,58 @@ describe("EmpowermentBound", () => { }); // EB-10: empowermentBound returns null for empty interaction list + // ── 081KZKYDJ9Q: the τ / consent-inversion fix (Soraya's P0) ──────────────── + // + // These lock behaviour the previous tests could not see. A material semantic + // change to externalitySafe left all 10 originals green, which is itself the + // evidence that they were self-certifying (Soraya: EB-8 asserts the vacuous + // `delta >= 0` branch and therefore cannot fail). + // + // Semantics under test — the boxing-ring table: + // bystander (no declared τ) -> ANY harm fails; they are the audience. + // entered the ring (declared τ) -> harm down to their declared τ is permitted. + + test("EB-11: Soraya L4 — a bystander at floor 0.8 hit by −0.7 is NOT safe", () => { + // The shipped version returned TRUE here (0.8 − 0.7 = 0.1 >= 0), reporting a + // bystander pushed far below their own floor as safe. That was the consent + // inversion: τ hardcoded to 0 reads silence as consent to maximal harm. + expect(externalitySafe({ mu: 0.8, sigma: 0.0 } as CalibrationPosterior, -0.7)).toBe(false); + }); + + test("EB-12: Soraya L3 — a 90% reach reduction on a well-established bystander is NOT safe", () => { + expect(externalitySafe({ mu: 0.95, sigma: 0.01 } as CalibrationPosterior, -0.9)).toBe(false); + }); + + test("EB-13: ANY harm to a bystander fails, however small (no-harm default)", () => { + // The default τ is the party's own pre-interaction floor, so the predicate + // reduces to harm >= 0. This is the property EB-9 gestured at but did not pin. + expect(externalitySafe({ mu: 0.5, sigma: 0.1 } as CalibrationPosterior, -0.01)).toBe(false); + }); + + test("EB-14: zero externality is safe; benefit is safe but carries no credit", () => { + const p = { mu: 0.5, sigma: 0.1 } as CalibrationPosterior; + expect(externalitySafe(p, 0)).toBe(true); + // Benefit is clamped away rather than offsetting: a claimed benefit to someone + // not at the table is an unverified assertion, so it may not buy harm elsewhere. + expect(externalitySafe(p, 0.5)).toBe(true); + }); + + test("EB-15: a DECLARED permissive τ permits harm — entering the ring is the only way in", () => { + const p = { mu: 0.5, sigma: 0.1 } as CalibrationPosterior; + // Same harm that fails for a bystander (EB-13 shape) succeeds once τ is declared. + expect(externalitySafe(p, -0.4)).toBe(false); + expect(externalitySafe(p, -0.4, 3, -1)).toBe(true); + }); + + test("EB-16: τ is never inferred — the permissive path requires an explicit argument", () => { + // Guards the regression that caused this: a missing parameter silently became + // τ = 0. If the default ever stops meaning "the party's own floor", EB-13 and + // this test fail together rather than passing vacuously. + const p = { mu: 0.9, sigma: 0.0 } as CalibrationPosterior; + expect(externalitySafe(p, -0.5)).toBe(false); // no τ declared -> refused + expect(externalitySafe(p, -0.5, 3, 0.0)).toBe(true); // τ = 0 declared -> permitted + }); + test("EB-10: empowermentBound returns null for empty interaction list", () => { const self = makePosterior(0.8); const other = makePosterior(0.8); diff --git a/src/Core.TypeScript/planning/empowerment-bound.ts b/src/Core.TypeScript/planning/empowerment-bound.ts index f613c11ca0..80bfa03472 100644 --- a/src/Core.TypeScript/planning/empowerment-bound.ts +++ b/src/Core.TypeScript/planning/empowerment-bound.ts @@ -240,9 +240,44 @@ export function externalitySafe( thirdPartyPosterior: CalibrationPosterior, externalityDelta: number, kTrust = 3, + // 081KZKYDJ9Q — the τ this predicate must compare against. It was MISSING, and its + // absence hardcoded τ = 0, which Soraya's review named a CONSENT INVERSION: it reads + // a bystander's silence as consent to the maximum harm the predicate can permit. + // + // The default is the BYSTANDER row of the boxing-ring table: no harm. A party that + // has not entered the ring is the audience, and the audience does not get punched. + // A party that HAS entered may declare a permissive (negative) τ — that is the whole + // point of the ring — but it must be *declared*, never inferred from silence. + // + // Ref: docs/research/2026-08-09-the-boxing-ring-consent-capacity-floor-values-arise-naturally-aaron.md + // Omitted ⇒ the party's OWN pre-interaction floor (computed below), which makes + // "no harm" true by construction rather than by choosing a magic constant. + tauThirdParty?: number, ): boolean { + // Only harm counts. A claimed BENEFIT to a non-consenting party is an unverified + // assertion made about someone who is not at the table — letting +0.3 offset −0.3 + // would let the interacting pair self-certify a compensation the bystander never + // agreed to (paternalistic benefit). Keep the clamp; this comment is the "why" that + // Soraya's review found missing. + const harm = Math.min(0, externalityDelta); + + // ⚠ PROXY — labelled, per the D_f 1.322 lesson (a proxy that stops being called one + // becomes a fabricated measurement). The property we want is + // trustBound(posterior_after(third, interaction), k) >= tau + // but `posterior_after` DOES NOT EXIST: there is no operator mapping an interaction + // to a change in a bystander's calibration posterior. Until one exists we approximate + // it as floor + harm, which is a UNITS APPROXIMATION as well as a temporal one — + // `floor` is a calibration-score bound while `externalityDelta` is a reach + // (option-space) quantity. Adding them is the lbf-vs-N shape; it is tolerated here + // ONLY because the alternative is shipping no bystander check at all, and it is + // marked so the next reader does not mistake it for the real operator. const floor = trustBound(thirdPartyPosterior, kTrust); - const afterFloor = floor + Math.min(0, externalityDelta); - // Safe if the third party's floor is not pushed below 0 - return afterFloor >= 0; + const floorAfterProxy = floor + harm; + + // Default τ = the party's own pre-interaction floor, so the predicate reduces to + // `harm >= 0` — i.e. ANY harm to a bystander fails. A declared τ below that floor is + // how a party that entered the ring permits harm to itself; it can never be inferred. + const tau = tauThirdParty ?? floor; + + return floorAfterProxy >= tau; }