@rulvar/evals 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +36 -14
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -20,27 +20,49 @@ import { createHash } from "node:crypto";
|
|
|
20
20
|
* invocation and is never persisted.
|
|
21
21
|
*
|
|
22
22
|
* Accounting is integer micro-USD and conservative at the
|
|
23
|
-
* representation boundary (v1.17.0 review P1-4
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
23
|
+
* representation boundary (v1.17.0 review P1-4, hardened by the v1.18.0
|
|
24
|
+
* review P1-4): the cap converts DOWN (floor), every debit converts UP
|
|
25
|
+
* (ceil), only ULP-scale representation noise may snap to the
|
|
26
|
+
* neighboring integer, amounts outside the safe integer micro-USD
|
|
27
|
+
* domain are rejected, and a cap below one micro-USD is rejected
|
|
28
|
+
* outright, so for any admitted sequence the sum of the ORIGINAL
|
|
29
|
+
* ceilings can never exceed maxTotalUsd and no positive ceiling ever
|
|
30
|
+
* debits zero. Amounts that are integer micro-USD up to float noise
|
|
31
|
+
* stay exact, so 0.1 + 0.2 against a 0.3 envelope is a fit, not a
|
|
32
|
+
* float rejection.
|
|
30
33
|
*/
|
|
31
34
|
const MICRO = 1e6;
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* Directed-rounding snap window, in ULPs of the product `usd * 1e6`:
|
|
37
|
+
* only genuine IEEE-754 representation noise may snap to the neighboring
|
|
38
|
+
* integer (0.3 * 1e6 is 299999.99999999994, about 6e-11 away, and MUST
|
|
39
|
+
* count as 300000), while a genuinely sub-micro fraction like 0.4 must
|
|
40
|
+
* round in the conservative direction. The window scales with the ULP,
|
|
41
|
+
* never with a relative fraction of the amount: the v1.17.0 fix used a
|
|
42
|
+
* 1e-6 RELATIVE tolerance, which already reaches half a micro-USD at
|
|
43
|
+
* $0.50 and turned directed rounding into round-to-nearest, admitting
|
|
44
|
+
* aggregates above the cap (v1.18.0 review P1-4).
|
|
36
45
|
*/
|
|
37
|
-
const
|
|
46
|
+
const SNAP_ULPS = 4;
|
|
38
47
|
function microOf(usd, direction) {
|
|
39
48
|
const raw = usd * MICRO;
|
|
40
49
|
const nearest = Math.round(raw);
|
|
41
|
-
if (Math.abs(raw - nearest) <=
|
|
50
|
+
if (Math.abs(raw - nearest) <= SNAP_ULPS * Math.abs(raw) * Number.EPSILON) return nearest;
|
|
42
51
|
return direction === "floor" ? Math.floor(raw) : Math.ceil(raw);
|
|
43
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The accounting domain is integer micro-USD within Number's safe
|
|
55
|
+
* integer range (up to about $9.007e9). Outside it (Number.MAX_VALUE
|
|
56
|
+
* caps overflow to Infinity, huge finite amounts lose integer
|
|
57
|
+
* precision) the envelope arithmetic silently degrades: Infinity plus
|
|
58
|
+
* anything compares false against Infinity and every authorization
|
|
59
|
+
* would be admitted with remainingUsd NaN (v1.18.0 review P1-4), so
|
|
60
|
+
* out-of-domain amounts are rejected up front.
|
|
61
|
+
*/
|
|
62
|
+
function requireSafeMicro(micro, what, usd) {
|
|
63
|
+
if (!Number.isSafeInteger(micro)) throw new ConfigError(`${what} ${String(usd)} USD is outside the safe integer micro-USD accounting domain (at most \$9007199254.740991)`);
|
|
64
|
+
return micro;
|
|
65
|
+
}
|
|
44
66
|
/** Thrown when authorizing a run's ceiling would exceed the envelope. */
|
|
45
67
|
var SweepBudgetError = class extends Error {
|
|
46
68
|
/** What was about to start, e.g. `eval target 'sweep-math'`. */
|
|
@@ -71,7 +93,7 @@ var SpendEnvelope = class {
|
|
|
71
93
|
constructor(maxTotalUsd) {
|
|
72
94
|
if (!Number.isFinite(maxTotalUsd) || maxTotalUsd <= 0) throw new ConfigError(`SpendEnvelope maxTotalUsd must be a positive finite number, got ${String(maxTotalUsd)}`);
|
|
73
95
|
this.maxTotalUsd = maxTotalUsd;
|
|
74
|
-
this.maxMicroUsd = microOf(maxTotalUsd, "floor");
|
|
96
|
+
this.maxMicroUsd = requireSafeMicro(microOf(maxTotalUsd, "floor"), "SpendEnvelope maxTotalUsd", maxTotalUsd);
|
|
75
97
|
if (this.maxMicroUsd < 1) throw new ConfigError(`SpendEnvelope maxTotalUsd ${String(maxTotalUsd)} is below the 1 micro-USD accounting granularity (\$0.000001); such an envelope could never admit a run`);
|
|
76
98
|
}
|
|
77
99
|
/** Total authorized so far (debit-only; never decreases). */
|
|
@@ -89,7 +111,7 @@ var SpendEnvelope = class {
|
|
|
89
111
|
*/
|
|
90
112
|
authorize(ceilingUsd, runLabel) {
|
|
91
113
|
if (ceilingUsd === void 0 || !Number.isFinite(ceilingUsd) || ceilingUsd <= 0) throw new ConfigError(`the spend envelope requires an explicit positive per-run ceiling for ${runLabel}; got ${String(ceilingUsd)} (an unbounded run under an aggregate envelope would be unaccountable)`);
|
|
92
|
-
const micro = Math.max(1, microOf(ceilingUsd, "ceil"));
|
|
114
|
+
const micro = requireSafeMicro(Math.max(1, microOf(ceilingUsd, "ceil")), `per-run ceiling for ${runLabel}`, ceilingUsd);
|
|
93
115
|
if (this.authorizedMicroUsd + micro > this.maxMicroUsd) throw new SweepBudgetError(runLabel, ceilingUsd, this.authorizedUsd, this.maxTotalUsd);
|
|
94
116
|
this.authorizedMicroUsd += micro;
|
|
95
117
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/evals",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Rulvar evals: eval cases, golden outputs, rubric and judge graders, matrix sweeps, canary fingerprint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/
|
|
26
|
-
"@rulvar/
|
|
25
|
+
"@rulvar/testing": "1.19.0",
|
|
26
|
+
"@rulvar/core": "1.19.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.20.0",
|