Skip to content

Commit 95eb789

Browse files
committed
Use exact rational evidence gates independent of Decimal context
1 parent 5427d43 commit 95eb789

3 files changed

Lines changed: 66 additions & 5 deletions

File tree

‎docs/capability-fixture.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ necessary to establish that two differently named actors are independent.
4848

4949
Metrics must match policy exactly. Conservative uncertainty intervals must satisfy
5050
resource/value bounds, every regression budget and each required positive gain.
51-
Decimal comparison avoids binary floating-point subtraction at policy boundaries.
51+
Exact rational comparisons of decoded numeric values avoid binary floating-point
52+
subtraction and do not depend on ambient Decimal precision, rounding or traps.
5253
Missing/conflicting evidence or failed gates returns experimental with no proposed
5354
name or version increment. Passing evidence that does not classify the intended
5455
capability returns unclassified. Internal versions stop at 4096. No training-run

‎src/aethermesh_core/capability_fixture.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import json
77
import re
88
from dataclasses import dataclass
9-
from decimal import Decimal
9+
from fractions import Fraction
1010
from typing import Any, Literal
1111

1212
from .artifact_codec import CheckCancel, _cancel, _integer
@@ -75,11 +75,11 @@ def _digest(value: Any) -> str:
7575
return str(value)
7676

7777

78-
def _number(value: Any) -> Decimal:
78+
def _number(value: Any) -> Fraction:
7979
if type(value) not in (int, float):
8080
raise ValueError("fixture metric must be a finite number")
81-
result = Decimal(str(value))
82-
if not result.is_finite() or abs(result) > 10**12:
81+
result = Fraction(str(value))
82+
if abs(result) > 10**12:
8383
raise ValueError("fixture metric magnitude bound")
8484
return result
8585

‎tests/test_capability_fixture.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Synthetic evidence must never become publication or live capability authority."""
22

33
import copy
4+
import decimal
45
import json
56
import unittest
67
from pathlib import Path
@@ -291,3 +292,62 @@ def cancel(stop=stop):
291292

292293
with self.subTest(stop=stop), self.assertRaises(InterruptedError):
293294
evaluate(fixture(), check_cancel=cancel)
295+
296+
def test_gate_decisions_ignore_ambient_decimal_context(self):
297+
accepted = fixture()
298+
for report in accepted["reports"].values():
299+
report["metrics"]["accuracy"]["uncertainty"] = 0
300+
accepted["reports"]["candidate"]["metrics"]["accuracy"]["value"] = 0.85
301+
rejected = copy.deepcopy(accepted)
302+
rejected["reports"]["candidate"]["metrics"]["accuracy"]["value"] = 0.849
303+
rounded_up = fixture()
304+
rounded_up["reports"]["candidate"]["metrics"]["accuracy"]["value"] = 0.84
305+
cases = [accepted, rejected, rounded_up]
306+
for uncertainty in (1e-30, 5e-324):
307+
tiny = copy.deepcopy(accepted)
308+
tiny["reports"]["candidate"]["metrics"]["accuracy"]["uncertainty"] = (
309+
uncertainty
310+
)
311+
self.assertIn("insufficient_gain", evaluate(tiny).reasons)
312+
cases.append(tiny)
313+
classified = copy.deepcopy(cases[3])
314+
classified["policy"]["metrics"][0]["gain"] = 0.04
315+
classified["taxonomy"]["rules"][0]["minimum"] = 0.85
316+
retax(classified)
317+
self.assertEqual(evaluate(classified).status, "unclassified")
318+
resource = copy.deepcopy(cases[3])
319+
resource["policy"]["metrics"][0].update(gain=0.04, maximum=0.85)
320+
self.assertIn("resource_or_uncertainty", evaluate(resource).reasons)
321+
regression = copy.deepcopy(accepted)
322+
regression["reports"]["candidate"]["metrics"]["memory"].update(
323+
value=1024, uncertainty=1e-30
324+
)
325+
regression["policy"]["metrics"][1]["uncertainty"] = 1
326+
self.assertIn("regression", evaluate(regression).reasons)
327+
cases.extend((classified, resource, regression))
328+
expected = tuple(evaluate(case) for case in cases)
329+
self.assertEqual(expected[0].status, "eligible")
330+
self.assertIn("insufficient_gain", expected[1].reasons)
331+
self.assertIn("insufficient_gain", expected[2].reasons)
332+
for precision in (1, 2, 28, 100, 350):
333+
for rounding in (
334+
decimal.ROUND_DOWN,
335+
decimal.ROUND_UP,
336+
decimal.ROUND_HALF_EVEN,
337+
decimal.ROUND_FLOOR,
338+
decimal.ROUND_CEILING,
339+
):
340+
with (
341+
self.subTest(precision=precision, rounding=rounding),
342+
decimal.localcontext() as context,
343+
):
344+
context.prec = precision
345+
context.rounding = rounding
346+
context.Emax = 1
347+
context.Emin = -1
348+
for signal in context.traps:
349+
context.traps[signal] = True
350+
self.assertEqual(tuple(evaluate(case) for case in cases), expected)
351+
self.assertFalse(any(context.flags.values()))
352+
with self.assertRaises(ValueError):
353+
naming._number(10**12 + 1)

0 commit comments

Comments
 (0)