@rulvar/openai 1.133.0 → 1.134.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.d.ts CHANGED
@@ -277,6 +277,18 @@ interface StatementReconciliation {
277
277
  usageUnknownRows: number;
278
278
  componentToleranceUsd: number;
279
279
  verdict: "match" | "divergence" | "partial-coverage" | "no-overlap";
280
+ /**
281
+ * The settlement-grade composite, first class (RV1006): true exactly
282
+ * when the verdict is 'match' AND coverage is complete AND no row's
283
+ * usage is unknown AND no model went unpriced. A 'match' alone is
284
+ * not enough: an export can cover every KNOWN row to the cent while
285
+ * a usage-unknown attempt still holds unattributed money, and a safe
286
+ * consumer must not assemble this predicate by hand. The last two
287
+ * conditions overlap today's verdict semantics deliberately: the
288
+ * predicate states the full contract so it cannot drift apart from
289
+ * a future verdict refinement.
290
+ */
291
+ settleable: boolean;
280
292
  }
281
293
  /**
282
294
  * Reconciles the invoice against a normalized provider export. Pure and
@@ -285,9 +297,12 @@ interface StatementReconciliation {
285
297
  * headline total with no rows), a request row without a response id, a
286
298
  * duplicate response id (an ambiguous join), a request export whose
287
299
  * rows carry neither dollars, components, nor usage, any non-finite or
288
- * negative dollar amount, any non-integer or negative token count, or
289
- * a non-finite or negative tolerance (RV903: a statement that cannot
290
- * be summed must refuse loudly, never verdict 'match' on NaN totals).
300
+ * negative dollar amount, any non-integer or negative token count, a
301
+ * non-finite or negative tolerance (RV903: a statement that cannot
302
+ * be summed must refuse loudly, never verdict 'match' on NaN totals),
303
+ * or a row whose usd and componentsUsd contradict each other beyond
304
+ * totalToleranceUsd (RV1005: an internally contradictory export is
305
+ * not evidence either).
291
306
  */
292
307
  declare function reconcileStatement(invoice: {
293
308
  rows: readonly InvoiceRow[];
package/dist/index.js CHANGED
@@ -1108,9 +1108,12 @@ function assertTokenCount(where, field, value) {
1108
1108
  * headline total with no rows), a request row without a response id, a
1109
1109
  * duplicate response id (an ambiguous join), a request export whose
1110
1110
  * rows carry neither dollars, components, nor usage, any non-finite or
1111
- * negative dollar amount, any non-integer or negative token count, or
1112
- * a non-finite or negative tolerance (RV903: a statement that cannot
1113
- * be summed must refuse loudly, never verdict 'match' on NaN totals).
1111
+ * negative dollar amount, any non-integer or negative token count, a
1112
+ * non-finite or negative tolerance (RV903: a statement that cannot
1113
+ * be summed must refuse loudly, never verdict 'match' on NaN totals),
1114
+ * or a row whose usd and componentsUsd contradict each other beyond
1115
+ * totalToleranceUsd (RV1005: an internally contradictory export is
1116
+ * not evidence either).
1114
1117
  */
1115
1118
  function reconcileStatement(invoice, statement, options) {
1116
1119
  for (const [name, value] of [["componentToleranceUsd", options.componentToleranceUsd], ["totalToleranceUsd", options.totalToleranceUsd]]) if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new ConfigError(`statement reconciliation refused: ${name} ${String(value)} is not a finite nonnegative dollar tolerance`);
@@ -1136,6 +1139,8 @@ function reconcileStatement(invoice, statement, options) {
1136
1139
  const statementOnlyIdSample = [];
1137
1140
  let statementTotalUsd;
1138
1141
  let statementComponents;
1142
+ let matchedStatementRows = 0;
1143
+ let matchedUsdRows = 0;
1139
1144
  let tokenMismatches = 0;
1140
1145
  let partialOverlap = false;
1141
1146
  const tokenMismatchSample = [];
@@ -1148,9 +1153,18 @@ function reconcileStatement(invoice, statement, options) {
1148
1153
  if (byId.has(row.responseId)) throw new ConfigError(`statement reconciliation refused: duplicate response id '${row.responseId}' in the export makes the join ambiguous`);
1149
1154
  const where = `row '${row.responseId}'`;
1150
1155
  if (row.usd !== void 0) assertStatementUsd(where, "usd", row.usd);
1151
- if (row.componentsUsd !== void 0) for (const component of COMPONENTS) {
1152
- const usd = row.componentsUsd[component];
1153
- if (usd !== void 0) assertStatementUsd(where, `componentsUsd.${component}`, usd);
1156
+ if (row.componentsUsd !== void 0) {
1157
+ let componentsSum = 0;
1158
+ let componentsSeen = 0;
1159
+ for (const component of COMPONENTS) {
1160
+ const usd = row.componentsUsd[component];
1161
+ if (usd !== void 0) {
1162
+ assertStatementUsd(where, `componentsUsd.${component}`, usd);
1163
+ componentsSum += usd;
1164
+ componentsSeen += 1;
1165
+ }
1166
+ }
1167
+ if (row.usd !== void 0 && componentsSeen > 0 && Math.abs(row.usd - componentsSum) > totalToleranceUsd) throw new ConfigError(`statement reconciliation refused: ${where} carries usd ${String(row.usd)} and a component split summing to ${String(componentsSum)}, claims that contradict each other beyond the ${String(totalToleranceUsd)} totals tolerance; an export whose own total disagrees with its own components is not evidence: normalize it to one dollar claim per row or fix the export`);
1154
1168
  }
1155
1169
  if (row.usage !== void 0) for (const field of [
1156
1170
  "inputTokens",
@@ -1222,7 +1236,9 @@ function reconcileStatement(invoice, statement, options) {
1222
1236
  statementComponents = /* @__PURE__ */ new Map();
1223
1237
  for (const row of statement.rows) {
1224
1238
  if (!matchedStatement.has(row.responseId)) continue;
1239
+ matchedStatementRows += 1;
1225
1240
  if (row.usd !== void 0) {
1241
+ matchedUsdRows += 1;
1226
1242
  totalSeen = true;
1227
1243
  total += row.usd;
1228
1244
  }
@@ -1318,7 +1334,7 @@ function reconcileStatement(invoice, statement, options) {
1318
1334
  }
1319
1335
  const divergent = components.filter((line) => line.divergent).sort((a, b) => Math.abs(b.deltaUsd ?? 0) - Math.abs(a.deltaUsd ?? 0));
1320
1336
  const totalsDelta = statementTotalUsd === void 0 ? void 0 : statementTotalUsd - ourUsd;
1321
- const totalsDivergent = statementComponents === void 0 && totalsDelta !== void 0 && Math.abs(totalsDelta) > totalToleranceUsd;
1337
+ const totalsDivergent = unpricedModels.size === 0 && (statement.kind === "requests" ? matchedUsdRows === matchedStatementRows : statementOnlyRows === 0 && components.every((line) => line.statementUsd !== void 0)) && totalsDelta !== void 0 && Math.abs(totalsDelta) > totalToleranceUsd;
1322
1338
  const coverageComplete = unmatchedRows === 0 && statementOnlyRows === 0 && unpricedModels.size === 0 && (statement.kind === "categories" || matchedRows === rowsWithResponseId && rowsWithResponseId === billable.length) && (statement.kind === "requests" || components.every((line) => line.statementUsd !== void 0));
1323
1339
  const tokensDivergent = tokenComparison === "verdict" && tokenMismatches > 0;
1324
1340
  let verdict;
@@ -1350,7 +1366,8 @@ function reconcileStatement(invoice, statement, options) {
1350
1366
  unpricedModels: [...unpricedModels].sort(),
1351
1367
  usageUnknownRows,
1352
1368
  componentToleranceUsd,
1353
- verdict
1369
+ verdict,
1370
+ settleable: verdict === "match" && coverageComplete && usageUnknownRows === 0 && unpricedModels.size === 0
1354
1371
  };
1355
1372
  }
1356
1373
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/openai",
3
- "version": "1.133.0",
3
+ "version": "1.134.0",
4
4
  "description": "Rulvar first-class provider adapter for the OpenAI Responses API, plus the openaiCompatible factory.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -23,13 +23,13 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "openai": "^6.49.0",
26
- "@rulvar/core": "1.133.0"
26
+ "@rulvar/core": "1.134.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.1",
30
30
  "tsdown": "^0.22.14",
31
31
  "typescript": "~6.0.3",
32
- "@rulvar/testing": "1.133.0"
32
+ "@rulvar/testing": "1.134.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",