@rulvar/core 1.110.0 → 1.112.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 +18 -0
- package/dist/index.js +43 -12
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -824,6 +824,15 @@ interface EntryBillingFold {
|
|
|
824
824
|
* semantics. False folds the aggregate slices, the historical basis.
|
|
825
825
|
*/
|
|
826
826
|
fullyAttributed: boolean;
|
|
827
|
+
/**
|
|
828
|
+
* The models this fold priced per call: record sums equal slice sums
|
|
829
|
+
* counter for counter under the symmetric per-model key (RV604).
|
|
830
|
+
* Published so a row builder can honor the same decision (RV703): a
|
|
831
|
+
* covered model's rows are exactly its records, so no per-slice
|
|
832
|
+
* remainder may be fabricated for it; recomputing coverage elsewhere
|
|
833
|
+
* is how the phantom-remainder skew was born.
|
|
834
|
+
*/
|
|
835
|
+
coveredModels: ReadonlySet<ModelRef>;
|
|
827
836
|
}
|
|
828
837
|
/**
|
|
829
838
|
* The billing fold over one terminal entry (RV504), shared by the
|
|
@@ -3160,6 +3169,15 @@ declare function quotaRuleKey(rule: QuotaRule): string;
|
|
|
3160
3169
|
* so ordinary JavaScript after the constructor (a pushed rule, a
|
|
3161
3170
|
* reassigned cap) can no longer change a decision, a bucket key, or a
|
|
3162
3171
|
* recorded fingerprint.
|
|
3172
|
+
*
|
|
3173
|
+
* A set containing two rules with the same canonical content key is
|
|
3174
|
+
* refused typed (RV704): the memory reference buckets by rule INDEX
|
|
3175
|
+
* (each copy counts independently, the full cap admits) while the
|
|
3176
|
+
* store references bucket by rule KEY (one shared bucket is debited
|
|
3177
|
+
* once per matching copy, half the cap admits), so the same duplicated
|
|
3178
|
+
* configuration admitted differently per storage. Refusing it at the
|
|
3179
|
+
* shared construction chokepoint is what keeps equal configurations
|
|
3180
|
+
* equal on every storage.
|
|
3163
3181
|
*/
|
|
3164
3182
|
declare function snapshotQuotaRules(rules: readonly QuotaRule[], site?: string): readonly QuotaRule[];
|
|
3165
3183
|
/** True when every dimension the rule pins matches the request. */
|
package/dist/index.js
CHANGED
|
@@ -2532,7 +2532,8 @@ function priceEntryBilling(entry, priceUsd) {
|
|
|
2532
2532
|
servedBy,
|
|
2533
2533
|
usage
|
|
2534
2534
|
})),
|
|
2535
|
-
fullyAttributed
|
|
2535
|
+
fullyAttributed,
|
|
2536
|
+
coveredModels: covered
|
|
2536
2537
|
};
|
|
2537
2538
|
}
|
|
2538
2539
|
/**
|
|
@@ -8542,16 +8543,22 @@ function costReportFromJournal(entries, priceUsd) {
|
|
|
8542
8543
|
* and per-row `allocatedUsd` is the additive column whose flat sum
|
|
8543
8544
|
* reproduces `totalUsd` exactly in every case.
|
|
8544
8545
|
*
|
|
8545
|
-
* Coverage is loss-free by construction:
|
|
8546
|
-
* cover its usage
|
|
8547
|
-
*
|
|
8548
|
-
*
|
|
8549
|
-
* shipped, or a fully replayed invocation) contributes one
|
|
8550
|
-
* `unattributed` row per usage slice.
|
|
8551
|
-
*
|
|
8552
|
-
*
|
|
8553
|
-
*
|
|
8554
|
-
*
|
|
8546
|
+
* Coverage is loss-free by construction: a model whose records do not
|
|
8547
|
+
* cover its usage (a resume restored from a checkpoint written before
|
|
8548
|
+
* the ledger shipped) contributes an `unattributed` remainder row per
|
|
8549
|
+
* slice, and an entry with no records at all (written before the
|
|
8550
|
+
* ledger shipped, or a fully replayed invocation) contributes one
|
|
8551
|
+
* `unattributed` row per usage slice. A COVERED model contributes no
|
|
8552
|
+
* remainder rows at all (RV703): its rows are exactly its records, the
|
|
8553
|
+
* same per-model decision the billing fold makes, so a role mismatch
|
|
8554
|
+
* between records and slices (the schema-extract default splits one
|
|
8555
|
+
* model's usage by role while the record carries one role, or none)
|
|
8556
|
+
* can no longer fabricate a phantom row that breaks the
|
|
8557
|
+
* `rowUsdNonAdditive: false` promise and siphons allocation from the
|
|
8558
|
+
* real call. Missing provider ids are marked, never dropped: a
|
|
8559
|
+
* finished call without one reconciles as `missing-provider-id`, a
|
|
8560
|
+
* failed or severed call without one as `unconfirmed` (the provider
|
|
8561
|
+
* may or may not have billed it; there is no id to match).
|
|
8555
8562
|
*
|
|
8556
8563
|
* Pricing happens at fold time from the table you pass, exactly like
|
|
8557
8564
|
* CostReport. For historical stability against price-table updates,
|
|
@@ -8577,6 +8584,12 @@ const USAGE_FIELDS = [
|
|
|
8577
8584
|
* model: the whole-entry remainder was published under `entry.servedBy`,
|
|
8578
8585
|
* so a slice with no records left its allocation pool rowless and the
|
|
8579
8586
|
* dust pass moved its dollars onto another model's row.
|
|
8587
|
+
*
|
|
8588
|
+
* Consulted only for UNCOVERED models (RV703): coverage is a per-model
|
|
8589
|
+
* decision, so a covered model's slices never reach this arithmetic.
|
|
8590
|
+
* The per-role subtraction here against the per-model coverage key was
|
|
8591
|
+
* exactly the mismatch that fabricated a phantom remainder whenever a
|
|
8592
|
+
* covered model's record roles differed from its slice roles.
|
|
8580
8593
|
*/
|
|
8581
8594
|
function sliceRemainder(slice, records) {
|
|
8582
8595
|
const remainder = {
|
|
@@ -8686,7 +8699,8 @@ function invoiceFromJournal(entries, priceUsd, options) {
|
|
|
8686
8699
|
let everyEntryFullyAttributed = true;
|
|
8687
8700
|
for (const entry of entries) {
|
|
8688
8701
|
if (entry.status === "running" || entry.usage === void 0) continue;
|
|
8689
|
-
|
|
8702
|
+
const billing = priceEntryBilling(entry, priceUsd);
|
|
8703
|
+
if (!billing.fullyAttributed) everyEntryFullyAttributed = false;
|
|
8690
8704
|
const abandoned = entry.kind !== "resolution" && entry.kind !== "abandon" && abandonFold.isAbandoned(entry.ref ?? entry.seq);
|
|
8691
8705
|
const base = {
|
|
8692
8706
|
entrySeq: entry.seq,
|
|
@@ -8737,6 +8751,7 @@ function invoiceFromJournal(entries, priceUsd, options) {
|
|
|
8737
8751
|
}
|
|
8738
8752
|
let remainderOrdinal = records.length + 1;
|
|
8739
8753
|
for (const slice of entryUsageSlices(entry)) {
|
|
8754
|
+
if (billing.coveredModels.has(slice.servedBy)) continue;
|
|
8740
8755
|
const remainder = sliceRemainder(slice, records);
|
|
8741
8756
|
if (remainder === void 0) continue;
|
|
8742
8757
|
const usd = rowUsd(priceUsd, slice.servedBy, remainder, entry.seq);
|
|
@@ -9271,9 +9286,25 @@ function quotaRuleKey(rule) {
|
|
|
9271
9286
|
* so ordinary JavaScript after the constructor (a pushed rule, a
|
|
9272
9287
|
* reassigned cap) can no longer change a decision, a bucket key, or a
|
|
9273
9288
|
* recorded fingerprint.
|
|
9289
|
+
*
|
|
9290
|
+
* A set containing two rules with the same canonical content key is
|
|
9291
|
+
* refused typed (RV704): the memory reference buckets by rule INDEX
|
|
9292
|
+
* (each copy counts independently, the full cap admits) while the
|
|
9293
|
+
* store references bucket by rule KEY (one shared bucket is debited
|
|
9294
|
+
* once per matching copy, half the cap admits), so the same duplicated
|
|
9295
|
+
* configuration admitted differently per storage. Refusing it at the
|
|
9296
|
+
* shared construction chokepoint is what keeps equal configurations
|
|
9297
|
+
* equal on every storage.
|
|
9274
9298
|
*/
|
|
9275
9299
|
function snapshotQuotaRules(rules, site = "quota rules") {
|
|
9276
9300
|
validateQuotaRules(rules, site);
|
|
9301
|
+
const firstIndexByKey = /* @__PURE__ */ new Map();
|
|
9302
|
+
rules.forEach((rule, index) => {
|
|
9303
|
+
const key = quotaRuleKey(rule);
|
|
9304
|
+
const first = firstIndexByKey.get(key);
|
|
9305
|
+
if (first !== void 0) throw new ConfigError(`${site}[${String(index)}] duplicates ${site}[${String(first)}] (rule key ${key}): identical rules occupy independent buckets in memory but share one key-debited bucket on keyed storage, so one configuration would admit differently per store; delete the duplicate`);
|
|
9306
|
+
firstIndexByKey.set(key, index);
|
|
9307
|
+
});
|
|
9277
9308
|
return Object.freeze(rules.map((rule) => Object.freeze({
|
|
9278
9309
|
...rule.provider === void 0 ? {} : { provider: rule.provider },
|
|
9279
9310
|
...rule.model === void 0 ? {} : { model: rule.model },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.112.0",
|
|
4
4
|
"description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|