@rulvar/core 1.105.0 → 1.107.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
@@ -5978,8 +5978,9 @@ declare class AdmissionController {
5978
5978
  declare function buildCostReport(attribution: CostAttribution, totalUsd: number, abandoned?: CostReport["abandoned"]): CostReport;
5979
5979
  /**
5980
5980
  * The pure journal fold: the complete CostReport from terminal entries,
5981
- * the same summation the kernel ledger uses (terminal usage exactly
5982
- * once, priced per servedBy slice, abandoned subtrees contribute zero).
5981
+ * the same summation the kernel ledger uses (each terminal entry's
5982
+ * usage enters the sum once, priced per servedBy slice, abandoned
5983
+ * subtrees contribute zero).
5983
5984
  * The orchestrator block folds too: spend attributed to the
5984
5985
  * orchestrator sub-account, the reserve-funded share of it, the armed
5985
5986
  * wake count, and the at-cap freeze flag from the journaled cap
@@ -6873,7 +6874,14 @@ declare const DEFAULT_EVIDENCE_MIN_SHARE = .95;
6873
6874
  * list the missing (and unknown) citations, capped at 20, so the repair
6874
6875
  * turn can restore them. Purely textual and deterministic; checking
6875
6876
  * that cited targets EXIST on disk is host territory (a custom
6876
- * validator), not this contract. Default name 'evidence-preserved'.
6877
+ * validator), not this contract. Intake is fail closed (RV610): a
6878
+ * pattern that can match the empty string is refused typed (an empty
6879
+ * match would enter the pool as fabricated evidence and defeat
6880
+ * `requireNonEmptyPool`), zero-length matches never enter the pool
6881
+ * even when a lookaround produces them in context, and the strict-mode
6882
+ * booleans must be real booleans, so a stray `'true'` can never
6883
+ * silently disable the mode it names. Default name
6884
+ * 'evidence-preserved'.
6877
6885
  */
6878
6886
  declare function evidencePreservedValidator(options?: {
6879
6887
  pattern?: string;
package/dist/index.js CHANGED
@@ -2363,6 +2363,7 @@ function entryUsageSlices(entry) {
2363
2363
  usage: entry.usage
2364
2364
  }];
2365
2365
  }
2366
+ const foldOverflow = (seq, servedBy) => new ConfigError(`cost accounting overflow: the price sum for entry seq ${String(seq)} became non-finite at model ${servedBy} (individually finite prices overflowed); no public report may carry a non-finite number`);
2366
2367
  /**
2367
2368
  * The single pricing fold over one terminal entry, shared by the kernel
2368
2369
  * ledger and the CostReport fold so a run's total and its per-model
@@ -2388,6 +2389,7 @@ function priceEntryUsage(entry, priceUsd) {
2388
2389
  continue;
2389
2390
  }
2390
2391
  result.usd += usd;
2392
+ if (!Number.isFinite(result.usd)) throw foldOverflow(entry.seq, slice.servedBy);
2391
2393
  result.priced.push({
2392
2394
  ...slice,
2393
2395
  usd
@@ -2486,6 +2488,7 @@ function priceEntryBilling(entry, priceUsd) {
2486
2488
  continue;
2487
2489
  }
2488
2490
  usd += price;
2491
+ if (!Number.isFinite(usd)) throw foldOverflow(entry.seq, record.servedBy);
2489
2492
  units.push({
2490
2493
  source: "call",
2491
2494
  servedBy: record.servedBy,
@@ -2512,6 +2515,7 @@ function priceEntryBilling(entry, priceUsd) {
2512
2515
  continue;
2513
2516
  }
2514
2517
  usd += price;
2518
+ if (!Number.isFinite(usd)) throw foldOverflow(entry.seq, slice.servedBy);
2515
2519
  units.push({
2516
2520
  source: "slice",
2517
2521
  servedBy: slice.servedBy,
@@ -2827,6 +2831,31 @@ function validateEvidenceContract(value, site) {
2827
2831
  if (overheadCalls !== void 0) requireNonNegativeInteger(overheadCalls, `${site}.overheadCalls`);
2828
2832
  if (enforce !== void 0 && enforce !== "warn" && enforce !== "refuse") throw new ConfigError(`${site}.enforce must be 'warn' or 'refuse'; got ${JSON.stringify(enforce)}`);
2829
2833
  }
2834
+ /**
2835
+ * Walks a finished public accounting object (a cost report, an
2836
+ * invoice) and throws a typed ConfigError on the first non-finite
2837
+ * number (RV610): JSON serializes Infinity and NaN as null, so a
2838
+ * non-finite value in a published report is silent telemetry
2839
+ * corruption, never a representable answer. Individually finite
2840
+ * amounts can overflow in accumulation, which is exactly why the
2841
+ * boundary is guarded and not only the per-item validations.
2842
+ */
2843
+ function requireFiniteNumbersDeep(value, site) {
2844
+ const walk = (node, path) => {
2845
+ if (typeof node === "number") {
2846
+ if (!Number.isFinite(node)) throw new ConfigError(`cost accounting overflow at ${path}: the value is ${String(node)}; no public report or invoice may carry a non-finite number (JSON would serialize it as null)`);
2847
+ return;
2848
+ }
2849
+ if (Array.isArray(node)) {
2850
+ node.forEach((item, index) => {
2851
+ walk(item, `${path}[${String(index)}]`);
2852
+ });
2853
+ return;
2854
+ }
2855
+ if (typeof node === "object" && node !== null) for (const [key, item] of Object.entries(node)) walk(item, `${path}.${key}`);
2856
+ };
2857
+ walk(value, site);
2858
+ }
2830
2859
  //#endregion
2831
2860
  //#region src/knowledge/file-store.ts
2832
2861
  /**
@@ -8310,8 +8339,9 @@ function buildCostReport(attribution, totalUsd, abandoned = {
8310
8339
  }
8311
8340
  /**
8312
8341
  * The pure journal fold: the complete CostReport from terminal entries,
8313
- * the same summation the kernel ledger uses (terminal usage exactly
8314
- * once, priced per servedBy slice, abandoned subtrees contribute zero).
8342
+ * the same summation the kernel ledger uses (each terminal entry's
8343
+ * usage enters the sum once, priced per servedBy slice, abandoned
8344
+ * subtrees contribute zero).
8315
8345
  * The orchestrator block folds too: spend attributed to the
8316
8346
  * orchestrator sub-account, the reserve-funded share of it, the armed
8317
8347
  * wake count, and the at-cap freeze flag from the journaled cap
@@ -8370,7 +8400,7 @@ function costReportFromJournal(entries, priceUsd) {
8370
8400
  if (facts.finalizeReserve === true) reserveUsedUsd += priced.usd;
8371
8401
  }
8372
8402
  }
8373
- return {
8403
+ const report = {
8374
8404
  totalUsd,
8375
8405
  grossUsd: totalUsd + abandonedUsd,
8376
8406
  abandoned: {
@@ -8392,6 +8422,8 @@ function costReportFromJournal(entries, priceUsd) {
8392
8422
  unpriced,
8393
8423
  ...usageApprox ? { usageApprox: true } : {}
8394
8424
  };
8425
+ requireFiniteNumbersDeep(report, "costReport");
8426
+ return report;
8395
8427
  }
8396
8428
  //#endregion
8397
8429
  //#region src/engine/invoice.ts
@@ -8628,7 +8660,7 @@ function invoiceFromJournal(entries, priceUsd, options) {
8628
8660
  }
8629
8661
  const unallocatedUsd = allocateRows(rows, entries, priceUsd, report.grossUsd);
8630
8662
  const usageApprox = report.usageApprox === true || report.abandoned.usageApprox === true;
8631
- return {
8663
+ const invoice = {
8632
8664
  rows,
8633
8665
  totalUsd: report.grossUsd,
8634
8666
  netUsd: report.totalUsd,
@@ -8645,6 +8677,8 @@ function invoiceFromJournal(entries, priceUsd, options) {
8645
8677
  ...usageApprox ? { usageApprox: true } : {},
8646
8678
  ...options?.pricing === void 0 ? {} : { pricing: options.pricing }
8647
8679
  };
8680
+ requireFiniteNumbersDeep(invoice, "invoice");
8681
+ return invoice;
8648
8682
  }
8649
8683
  //#endregion
8650
8684
  //#region src/model/pricing.ts
@@ -16510,26 +16544,39 @@ function listCitations(values) {
16510
16544
  * list the missing (and unknown) citations, capped at 20, so the repair
16511
16545
  * turn can restore them. Purely textual and deterministic; checking
16512
16546
  * that cited targets EXIST on disk is host territory (a custom
16513
- * validator), not this contract. Default name 'evidence-preserved'.
16547
+ * validator), not this contract. Intake is fail closed (RV610): a
16548
+ * pattern that can match the empty string is refused typed (an empty
16549
+ * match would enter the pool as fabricated evidence and defeat
16550
+ * `requireNonEmptyPool`), zero-length matches never enter the pool
16551
+ * even when a lookaround produces them in context, and the strict-mode
16552
+ * booleans must be real booleans, so a stray `'true'` can never
16553
+ * silently disable the mode it names. Default name
16554
+ * 'evidence-preserved'.
16514
16555
  */
16515
16556
  function evidencePreservedValidator(options) {
16516
16557
  const pattern = options?.pattern ?? "[\\w./-]+\\.\\w+:\\d+";
16517
16558
  const flags = options?.flags ?? "";
16518
16559
  const globalFlags = flags.includes("g") ? flags : `${flags}g`;
16560
+ let probe;
16519
16561
  try {
16520
- new RegExp(pattern, globalFlags);
16562
+ probe = new RegExp(pattern, flags.replace("g", ""));
16521
16563
  } catch (thrown) {
16522
16564
  throw new ConfigError(`evidencePreservedValidator pattern does not compile: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
16523
16565
  }
16566
+ if (probe.test("")) throw new ConfigError(`evidencePreservedValidator pattern must not be able to match the empty string (an empty match would enter the citation pool as fabricated evidence); got ${JSON.stringify(pattern)}`);
16524
16567
  const minShare = options?.minShare ?? .95;
16525
16568
  if (typeof minShare !== "number" || !Number.isFinite(minShare) || minShare <= 0 || minShare > 1) throw new ConfigError(`evidencePreservedValidator minShare must be a number in (0, 1]; got ${String(minShare)}`);
16569
+ for (const option of ["requireKnown", "requireNonEmptyPool"]) {
16570
+ const value = options?.[option];
16571
+ if (value !== void 0 && typeof value !== "boolean") throw new ConfigError(`evidencePreservedValidator ${option} must be a boolean when given; got ${JSON.stringify(value)}`);
16572
+ }
16526
16573
  return {
16527
16574
  name: options?.name ?? "evidence-preserved",
16528
16575
  validate: (input) => {
16529
16576
  const cited = /* @__PURE__ */ new Set();
16530
16577
  for (const child of input.children ?? []) {
16531
16578
  if (child.status !== "ok" && child.salvageableOutput !== true) continue;
16532
- for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) cited.add(match);
16579
+ for (const match of child.text.match(new RegExp(pattern, globalFlags)) ?? []) if (match.length > 0) cited.add(match);
16533
16580
  }
16534
16581
  const reasons = [];
16535
16582
  if (cited.size === 0 && options?.requireNonEmptyPool === true) reasons.push("empty child citation pool: no ok child output contains a citation matching the pattern");
@@ -16539,7 +16586,7 @@ function evidencePreservedValidator(options) {
16539
16586
  if (preserved < Math.ceil(minShare * cited.size - 1e-9)) reasons.push(`evidence preservation ${String(preserved)} of ${String(cited.size)} child citations is below the required share ${String(minShare)}; missing: ${listCitations(missing)}`);
16540
16587
  }
16541
16588
  if (options?.requireKnown === true) {
16542
- const fabricated = [...new Set(input.text.match(new RegExp(pattern, globalFlags)) ?? [])].filter((citation) => !cited.has(citation));
16589
+ const fabricated = [...new Set(input.text.match(new RegExp(pattern, globalFlags)) ?? [])].filter((citation) => citation.length > 0 && !cited.has(citation));
16543
16590
  if (fabricated.length > 0) reasons.push(`unknown citations not present in any child report: ${listCitations(fabricated)}`);
16544
16591
  }
16545
16592
  return reasons.length === 0 ? ok : {
@@ -17623,7 +17670,7 @@ function makeOrchestratorWorkflow(goal, opts) {
17623
17670
  }, { childScope });
17624
17671
  const dispatched = internals.replayer.snapshot().find((entry) => entry.seq === record.handle);
17625
17672
  if (dispatched !== void 0) {
17626
- for (const prior of internals.replayer.snapshot()) if (prior.kind === "agent" && prior.status === "running" && prior.seq !== record.handle && prior.scope === dispatched.scope && prior.key === dispatched.key && prior.ordinal === dispatched.ordinal && !records.has(prior.seq)) records.set(prior.seq, record);
17673
+ for (const prior of internals.replayer.snapshot()) if (prior.kind === "agent" && prior.status === "running" && prior.seq !== record.handle && prior.scope === dispatched.scope && prior.key === dispatched.key && !records.has(prior.seq)) records.set(prior.seq, record);
17627
17674
  }
17628
17675
  }
17629
17676
  for (const entry of internals.replayer.snapshot()) {
@@ -17700,7 +17747,7 @@ function makeOrchestratorWorkflow(goal, opts) {
17700
17747
  extension?.onWake?.(digest);
17701
17748
  };
17702
17749
  const buildDigest = (ordinal) => {
17703
- const undelivered = [...records.values()].filter((record) => record.settled !== void 0 && !deliveredNodeIds.has(record.nodeId)).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
17750
+ const undelivered = [...byOrdinal.values()].filter((record) => record.settled !== void 0 && !deliveredNodeIds.has(record.nodeId)).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
17704
17751
  const escalations = [];
17705
17752
  for (const record of undelivered) {
17706
17753
  const settled = record.settled;
@@ -17891,7 +17938,7 @@ function makeOrchestratorWorkflow(goal, opts) {
17891
17938
  })) throw new ConfigError("child_terminal can never fire: every referenced child already settled and was delivered in a prior digest");
17892
17939
  }
17893
17940
  if (trigger.kind === "escalation") {
17894
- if (![...records.values()].some((record) => record.settled === void 0 || record.settled.status === "escalated" && !deliveredNodeIds.has(record.nodeId))) throw new ConfigError("escalation can never fire: no live or undelivered escalated children");
17941
+ if (![...byOrdinal.values()].some((record) => record.settled === void 0 || record.settled.status === "escalated" && !deliveredNodeIds.has(record.nodeId))) throw new ConfigError("escalation can never fire: no live or undelivered escalated children");
17895
17942
  }
17896
17943
  }
17897
17944
  const ordinal = wakeOrdinal;
@@ -17902,9 +17949,9 @@ function makeOrchestratorWorkflow(goal, opts) {
17902
17949
  await internals.replayer.flush();
17903
17950
  const entryRef = external.pending().find((item) => item.key === wakeKey && item.scope === wakeScope)?.entryRef;
17904
17951
  const isReady = (trigger) => {
17905
- const undelivered = [...records.values()].filter((record) => record.settled !== void 0 && !deliveredNodeIds.has(record.nodeId));
17952
+ const undelivered = [...byOrdinal.values()].filter((record) => record.settled !== void 0 && !deliveredNodeIds.has(record.nodeId));
17906
17953
  switch (trigger.kind) {
17907
- case "quiescence": return [...records.values()].every((record) => record.settled !== void 0) && (extension?.quiescent?.() ?? true);
17954
+ case "quiescence": return [...byOrdinal.values()].every((record) => record.settled !== void 0) && (extension?.quiescent?.() ?? true);
17908
17955
  case "child_terminal":
17909
17956
  if (trigger.handles === void 0) return undelivered.length > 0;
17910
17957
  return trigger.handles.some((handle) => undelivered.some((record) => record.handle === handle));
@@ -18142,7 +18189,7 @@ function makeOrchestratorWorkflow(goal, opts) {
18142
18189
  */
18143
18190
  const validationChildren = () => {
18144
18191
  const salvageOutputOn = opts?.acceptance?.acceptValidatedTerminalOutputOnLimit === true;
18145
- return [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => ({
18192
+ return [...byOrdinal.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => ({
18146
18193
  handle: record.handle,
18147
18194
  nodeId: record.nodeId,
18148
18195
  status: record.settled?.status ?? "running",
@@ -18378,7 +18425,7 @@ function makeOrchestratorWorkflow(goal, opts) {
18378
18425
  return {
18379
18426
  forcedFinishFallback: true,
18380
18427
  planHash: capValue?.snapshot?.planHash ?? "",
18381
- completed: [...records.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => digestOf(record, record.settled))
18428
+ completed: [...byOrdinal.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => digestOf(record, record.settled))
18382
18429
  };
18383
18430
  };
18384
18431
  /**
@@ -18453,7 +18500,7 @@ function makeOrchestratorWorkflow(goal, opts) {
18453
18500
  */
18454
18501
  const reconcileIncremental = async (draft, spec) => {
18455
18502
  synthesisSettleFrozen = true;
18456
- const settledRecords = [...records.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
18503
+ const settledRecords = [...byOrdinal.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
18457
18504
  const sections = [];
18458
18505
  for (const record of settledRecords) {
18459
18506
  const settled = record.settled;
@@ -18646,7 +18693,7 @@ function makeOrchestratorWorkflow(goal, opts) {
18646
18693
  const fullContext = spec.context === "full";
18647
18694
  const synthesisToolNames = /* @__PURE__ */ new Set([FINISH_TOOL_NAME, ...exposeTools ? [GET_CHILD_RESULT_TOOL_NAME, READ_CHILD_ARTIFACT_TOOL_NAME] : []]);
18648
18695
  const synthesisTools = buildOrchestratorTools(orchestratorRuntime, fullCardText, { childResultTools: exposeTools }).filter((tool) => synthesisToolNames.has(tool.name));
18649
- const settledEntries = [...records.entries()].filter((entry) => entry[1].settled !== void 0).sort((a, b) => a[1].spawnOrdinal - b[1].spawnOrdinal);
18696
+ const settledEntries = [...byOrdinal.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => [record.handle, record]);
18650
18697
  const settledDigests = settledEntries.map(([handle, record]) => ({
18651
18698
  ...exposeTools ? { handle } : {},
18652
18699
  ...digestOf(record, record.settled)
@@ -18899,7 +18946,7 @@ function makeOrchestratorWorkflow(goal, opts) {
18899
18946
  let hardDegraded = 0;
18900
18947
  const acceptPartial = opts.acceptance.acceptPartialChildren === true;
18901
18948
  const acceptOutput = opts.acceptance.acceptValidatedTerminalOutputOnLimit === true;
18902
- const sortedRecords = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
18949
+ const sortedRecords = [...byOrdinal.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
18903
18950
  for (const record of sortedRecords) {
18904
18951
  const status = record.settled?.status ?? "running";
18905
18952
  childStatusCounts[status] = (childStatusCounts[status] ?? 0) + 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.105.0",
3
+ "version": "1.107.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",