@rulvar/evals 1.19.0 → 1.21.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.
Files changed (2) hide show
  1. package/dist/index.js +39 -21
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -20,16 +20,27 @@ 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, 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.
23
+ * representation boundary (v1.17.0 review P1-4, hardened by the
24
+ * v1.18.0 review P1-4 and the v1.19.0 review P1-3): the cap converts
25
+ * DOWN (floor), every debit converts UP (ceil), only ULP-scale
26
+ * representation noise may snap to the neighboring integer, amounts at
27
+ * or above 2^49 micro-USD (about $563M, where the noise window would
28
+ * reach half a micro and the nearest integer would stop being unique)
29
+ * are rejected, and a cap below one micro-USD is rejected outright.
30
+ *
31
+ * The exact input interpretation: every USD amount is a JavaScript
32
+ * double, and the envelope interprets a double within SNAP_ULPS ULPs
33
+ * of an integer micro value AS that integer; anything farther rounds
34
+ * in the conservative direction. Under that interpretation the sum of
35
+ * admitted ceilings can never exceed maxTotalUsd, and no positive
36
+ * ceiling ever debits zero. The honest raw-double bound: each admitted
37
+ * amount's double may sit below its interpreted integer by at most the
38
+ * noise window (4 ULPs, always under half a micro in-domain), so the
39
+ * sum of raw doubles can exceed the cap by at most half a micro-USD
40
+ * per admitted amount; distinguishing finer than that is impossible in
41
+ * double precision at the top of the domain. Amounts that are integer
42
+ * micro-USD up to float noise stay exact, so 0.1 + 0.2 against a 0.3
43
+ * envelope is a fit, not a float rejection.
33
44
  */
34
45
  const MICRO = 1e6;
35
46
  /**
@@ -51,16 +62,23 @@ function microOf(usd, direction) {
51
62
  return direction === "floor" ? Math.floor(raw) : Math.ceil(raw);
52
63
  }
53
64
  /**
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.
65
+ * The accounting domain is integer micro-USD strictly below 2^49
66
+ * (at most $562,949,953.421311). Two limits meet there. Outside the
67
+ * safe integer range the arithmetic silently degrades: a
68
+ * Number.MAX_VALUE cap overflowed to Infinity and admitted EVERYTHING
69
+ * with remainingUsd NaN (v1.18.0 review P1-4). Well before that, from
70
+ * 2^49 micro upward, the SNAP_ULPS window reaches half a micro-USD:
71
+ * the nearest integer stops being unique and the snap absorbs genuine
72
+ * sub-micro remainders, so a ceil debit could round DOWN and admit an
73
+ * aggregate whose raw doubles sum above the cap by more than the
74
+ * documented noise bound (v1.19.0 review P1-3). Below 2^49 the window
75
+ * stays strictly under half a micro, the snap target is unique, and
76
+ * only representation noise is absorbed. Out-of-domain amounts are
77
+ * rejected up front.
61
78
  */
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)`);
79
+ const MAX_MICRO_EXCLUSIVE = 2 ** 49;
80
+ function requireDomainMicro(micro, what, usd) {
81
+ if (!Number.isSafeInteger(micro) || micro >= MAX_MICRO_EXCLUSIVE) throw new ConfigError(`${what} ${String(usd)} USD is outside the micro-USD accounting domain (at most \$562949953.421311)`);
64
82
  return micro;
65
83
  }
66
84
  /** Thrown when authorizing a run's ceiling would exceed the envelope. */
@@ -93,7 +111,7 @@ var SpendEnvelope = class {
93
111
  constructor(maxTotalUsd) {
94
112
  if (!Number.isFinite(maxTotalUsd) || maxTotalUsd <= 0) throw new ConfigError(`SpendEnvelope maxTotalUsd must be a positive finite number, got ${String(maxTotalUsd)}`);
95
113
  this.maxTotalUsd = maxTotalUsd;
96
- this.maxMicroUsd = requireSafeMicro(microOf(maxTotalUsd, "floor"), "SpendEnvelope maxTotalUsd", maxTotalUsd);
114
+ this.maxMicroUsd = requireDomainMicro(microOf(maxTotalUsd, "floor"), "SpendEnvelope maxTotalUsd", maxTotalUsd);
97
115
  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`);
98
116
  }
99
117
  /** Total authorized so far (debit-only; never decreases). */
@@ -111,7 +129,7 @@ var SpendEnvelope = class {
111
129
  */
112
130
  authorize(ceilingUsd, runLabel) {
113
131
  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)`);
114
- const micro = requireSafeMicro(Math.max(1, microOf(ceilingUsd, "ceil")), `per-run ceiling for ${runLabel}`, ceilingUsd);
132
+ const micro = requireDomainMicro(Math.max(1, microOf(ceilingUsd, "ceil")), `per-run ceiling for ${runLabel}`, ceilingUsd);
115
133
  if (this.authorizedMicroUsd + micro > this.maxMicroUsd) throw new SweepBudgetError(runLabel, ceilingUsd, this.authorizedUsd, this.maxTotalUsd);
116
134
  this.authorizedMicroUsd += micro;
117
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/evals",
3
- "version": "1.19.0",
3
+ "version": "1.21.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/testing": "1.19.0",
26
- "@rulvar/core": "1.19.0"
25
+ "@rulvar/core": "1.21.0",
26
+ "@rulvar/testing": "1.21.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.0",