@nudojs/core 1.11.0 → 1.11.2

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.
@@ -4792,17 +4792,35 @@ var _activeCallKeys = [];
4792
4792
  var _fnCallIds = /* @__PURE__ */ new WeakMap();
4793
4793
  var _fnCallIdSeq = 0;
4794
4794
  var _callTruncated = false;
4795
+ var _cycleTruncated = false;
4796
+ var _loopTruncated = false;
4797
+ var _truncReasons = /* @__PURE__ */ new Set();
4798
+ function noteLoopTruncation(label) {
4799
+ _loopTruncated = true;
4800
+ noteAbsTruncation(label);
4801
+ }
4795
4802
  var _callDepthLimit = MAX_CALL_DEPTH;
4796
4803
  var _totalCallsLimit = MAX_TOTAL_CALLS;
4804
+ var _evalCallProbe = null;
4805
+ function registerEvalCallBudgetProbe(probe) {
4806
+ _evalCallProbe = probe;
4807
+ }
4797
4808
  function getAbsCallBudgetStats() {
4809
+ const b = _evalCallProbe?.stats();
4798
4810
  return {
4799
4811
  calls: _absTotalCalls,
4800
4812
  maxCalls: _totalCallsLimit,
4801
- forks: _evalForkCount,
4813
+ forks: _evalForkObsCount,
4802
4814
  maxForks: _evalForkBudgetLimit,
4803
4815
  callTruncated: _callTruncated,
4804
4816
  forkTruncated: _evalForkTruncNoted,
4805
- truncated: _callTruncated || _evalForkTruncNoted
4817
+ truncated: _callTruncated || (b?.callTruncated ?? false) || _evalForkTruncNoted || _loopTruncated,
4818
+ evalCalls: b?.calls ?? 0,
4819
+ maxEvalCalls: b?.maxCalls ?? MAX_TOTAL_CALLS,
4820
+ evalCallTruncated: b?.callTruncated ?? false,
4821
+ cycleTruncated: _cycleTruncated || (b?.cycleTruncated ?? false),
4822
+ loopTruncated: _loopTruncated,
4823
+ reasons: [..._truncReasons].sort()
4806
4824
  };
4807
4825
  }
4808
4826
  function resetAbsCallBudget() {
@@ -4810,8 +4828,12 @@ function resetAbsCallBudget() {
4810
4828
  _absTotalCalls = 0;
4811
4829
  _activeCallKeys = [];
4812
4830
  _callTruncated = false;
4831
+ _cycleTruncated = false;
4832
+ _loopTruncated = false;
4833
+ _truncReasons = /* @__PURE__ */ new Set();
4813
4834
  _callDepthLimit = MAX_CALL_DEPTH;
4814
4835
  _totalCallsLimit = MAX_TOTAL_CALLS;
4836
+ _evalCallProbe?.resetObservability();
4815
4837
  resetEvalForkBudget();
4816
4838
  }
4817
4839
  function withFnBudgetOverride(budget, run) {
@@ -4862,18 +4884,21 @@ function setAbsTruncationCollector(collector) {
4862
4884
  absTruncCollectorSlot.set(collector);
4863
4885
  return prev;
4864
4886
  }
4865
- function noteAbsTruncation(label) {
4887
+ function noteAbsTruncation(label, reason) {
4888
+ if (reason) _truncReasons.add(reason);
4866
4889
  const absTruncCollector = absTruncCollectorSlot.get();
4867
4890
  if (!absTruncCollector) return;
4868
4891
  try {
4869
- absTruncCollector(label);
4892
+ absTruncCollector(label, reason);
4870
4893
  } catch {
4871
4894
  }
4872
4895
  }
4873
4896
  function enterCall(key, label) {
4874
- if (_activeCallKeys.includes(key) || _absCallDepth >= _callDepthLimit || _absTotalCalls >= _totalCallsLimit) {
4897
+ const reason = _activeCallKeys.includes(key) ? "cycle" : _absCallDepth >= _callDepthLimit ? "depth" : _absTotalCalls >= _totalCallsLimit ? "calls" : void 0;
4898
+ if (reason) {
4875
4899
  _callTruncated = true;
4876
- noteAbsTruncation(label);
4900
+ if (reason === "cycle") _cycleTruncated = true;
4901
+ noteAbsTruncation(label, reason);
4877
4902
  return false;
4878
4903
  }
4879
4904
  _activeCallKeys.push(key);
@@ -4888,6 +4913,7 @@ function exitCall() {
4888
4913
  var MAX_EVAL_TOTAL_FORKS = 5e3;
4889
4914
  var _evalForkBudgetLimit = MAX_EVAL_TOTAL_FORKS;
4890
4915
  var _evalForkCount = 0;
4916
+ var _evalForkObsCount = 0;
4891
4917
  var _evalForkTruncNoted = false;
4892
4918
  var FORK_TRUNCATION_LABEL = "#fork-budget";
4893
4919
  var HOST_EFFECT_LABEL_PREFIX = "#host-effect:";
@@ -4914,11 +4940,16 @@ function getEvalForkBudgetLimit() {
4914
4940
  function getEvalForkCount() {
4915
4941
  return _evalForkCount;
4916
4942
  }
4917
- function resetEvalForkBudget() {
4943
+ function resetEvalForkCount() {
4918
4944
  _evalForkCount = 0;
4945
+ }
4946
+ function resetEvalForkBudget() {
4947
+ resetEvalForkCount();
4919
4948
  _evalForkTruncNoted = false;
4949
+ _evalForkObsCount = 0;
4920
4950
  }
4921
4951
  function bumpEvalForkBudget() {
4952
+ _evalForkObsCount++;
4922
4953
  if (++_evalForkCount <= _evalForkBudgetLimit) return true;
4923
4954
  if (!_evalForkTruncNoted) {
4924
4955
  _evalForkTruncNoted = true;
@@ -13857,6 +13888,10 @@ function leafGrew(acc, next) {
13857
13888
  if (aR.ok) return true;
13858
13889
  return !leqAbs(next, acc).ok;
13859
13890
  }
13891
+ function joinElems(t) {
13892
+ const parts = t.holes?.length ? [...t.elements, oobUndef()] : t.elements;
13893
+ return parts.length > 0 ? parts.reduce((x, y) => joinAbs(x, y)) : void 0;
13894
+ }
13860
13895
  function diffGrowth(acc, next) {
13861
13896
  const as = acc.shape;
13862
13897
  const ns = next.shape;
@@ -13874,7 +13909,10 @@ function diffGrowth(acc, next) {
13874
13909
  return any2 ? { slots } : null;
13875
13910
  }
13876
13911
  if (as.k === "tuple" && ns.k === "tuple") {
13877
- if (as.elements.length !== ns.elements.length) return true;
13912
+ if (as.elements.length !== ns.elements.length) {
13913
+ const g = diffGrowth(joinElems(as) ?? never, joinElems(ns) ?? never);
13914
+ return { el: g };
13915
+ }
13878
13916
  let any2 = false;
13879
13917
  const els = [];
13880
13918
  for (let i = 0; i < ns.elements.length; i++) {
@@ -13902,6 +13940,51 @@ function primTop(type) {
13902
13940
  return abs({ k: "prim", type }, void 0, void 0, "exact");
13903
13941
  }
13904
13942
  }
13943
+ function sumArrParts(s) {
13944
+ const elems = [];
13945
+ const lengths = /* @__PURE__ */ new Set();
13946
+ let hasArr = false;
13947
+ for (const m of s.members) {
13948
+ const ms = m.shape;
13949
+ if (ms.k === "tuple") {
13950
+ lengths.add(ms.elements.length);
13951
+ const e = joinElems(ms);
13952
+ if (e) elems.push(e);
13953
+ } else if (ms.k === "arr") {
13954
+ hasArr = true;
13955
+ elems.push(ms.element);
13956
+ } else {
13957
+ return { ok: false, collapse: false };
13958
+ }
13959
+ }
13960
+ return {
13961
+ ok: true,
13962
+ collapse: hasArr || lengths.size > 1,
13963
+ element: elems.length > 0 ? elems.reduce((x, y) => joinAbs(x, y)) : void 0
13964
+ };
13965
+ }
13966
+ function arrify(v2) {
13967
+ const s = v2.shape;
13968
+ if (s.k === "tuple") {
13969
+ return abs(
13970
+ { k: "arr", element: joinElems(s) ?? unknown },
13971
+ void 0,
13972
+ void 0,
13973
+ confJoin(v2.conf, "widened")
13974
+ );
13975
+ }
13976
+ if (s.k === "sum") {
13977
+ const p = sumArrParts(s);
13978
+ if (!p.ok) return void 0;
13979
+ return abs(
13980
+ { k: "arr", element: p.element ?? unknown },
13981
+ void 0,
13982
+ void 0,
13983
+ confJoin(v2.conf, "widened")
13984
+ );
13985
+ }
13986
+ return void 0;
13987
+ }
13905
13988
  function domainTop(v2) {
13906
13989
  const s = v2.shape;
13907
13990
  if (s.k === "sum" && s.members.length === 2) {
@@ -13913,6 +13996,17 @@ function domainTop(v2) {
13913
13996
  }
13914
13997
  let top;
13915
13998
  if (s.k === "sum") {
13999
+ const collapsed = arrify(v2);
14000
+ if (collapsed) {
14001
+ return confJoin22(
14002
+ abs(
14003
+ { k: "arr", element: domainTop(collapsed.shape.element) },
14004
+ void 0,
14005
+ void 0,
14006
+ collapsed.conf
14007
+ )
14008
+ );
14009
+ }
13916
14010
  let acc;
13917
14011
  for (const m of s.members) {
13918
14012
  const t = domainTop(m);
@@ -13922,15 +14016,9 @@ function domainTop(v2) {
13922
14016
  } else if (s.k === "prim") {
13923
14017
  top = primTop(s.type);
13924
14018
  } else if (s.k === "tuple") {
13925
- const els = s.elements.map((e) => domainTop(e));
13926
- const known = els.length ? els.reduce((x, y) => joinAbs(x, y)) : unknown;
13927
- top = abs(
13928
- { k: "arr", element: joinAbs(known, abs({ k: "unknown" }, { op: "lit", value: void 0 }, void 0, "partial")) },
13929
- void 0,
13930
- void 0,
13931
- "widened"
13932
- );
13933
- return top;
14019
+ const parts = s.holes?.length ? [...s.elements, oobUndef()] : s.elements;
14020
+ const known = parts.length > 0 ? parts.map((e) => domainTop(e)).reduce((x, y) => joinAbs(x, y)) : unknown;
14021
+ return abs({ k: "arr", element: known }, void 0, void 0, "widened");
13934
14022
  } else if (s.k === "arr") {
13935
14023
  top = abs({ k: "arr", element: domainTop(s.element) }, void 0, void 0, v2.conf);
13936
14024
  } else if (s.k === "obj") {
@@ -13946,6 +14034,23 @@ function confJoin22(top) {
13946
14034
  const c = confJoin(top.conf, "widened");
13947
14035
  return c === top.conf ? top : { ...top, conf: c };
13948
14036
  }
14037
+ function normalizeForWiden(v2, g) {
14038
+ if (g === null) return v2;
14039
+ const s = v2.shape;
14040
+ let target = v2;
14041
+ if (s.k === "sum" && s.members.length > 0 && s.members.every((m) => m.shape.k === "obj")) {
14042
+ target = s.members.reduce((x, y) => joinObjects(x, y));
14043
+ }
14044
+ if (g === true || !("slots" in g)) return target;
14045
+ const ts = target.shape;
14046
+ if (ts.k !== "obj") return target;
14047
+ const slots = {};
14048
+ for (const [k, slot] of Object.entries(ts.slots)) {
14049
+ const gi = g.slots[k];
14050
+ slots[k] = gi === void 0 ? slot : { ...slot, value: normalizeForWiden(slot.value, gi) };
14051
+ }
14052
+ return abs({ ...ts, slots }, void 0, void 0, target.conf);
14053
+ }
13949
14054
  function widenWithPath(v2, g) {
13950
14055
  if (g === null) return v2;
13951
14056
  if (g === true) return domainTop(v2);
@@ -13962,11 +14067,40 @@ function widenWithPath(v2, g) {
13962
14067
  const elements = s.elements.map((e, i) => widenWithPath(e, g.els[i] ?? null));
13963
14068
  return abs({ ...s, elements }, void 0, void 0, v2.conf);
13964
14069
  }
13965
- if ("el" in g && s.k === "arr") {
13966
- return abs({ k: "arr", element: widenWithPath(s.element, g.el) }, void 0, void 0, v2.conf);
14070
+ if ("el" in g) {
14071
+ const a = s.k === "arr" ? v2 : arrify(v2);
14072
+ if (a && a.shape.k === "arr") {
14073
+ return abs(
14074
+ { k: "arr", element: widenWithPath(a.shape.element, g.el) },
14075
+ void 0,
14076
+ void 0,
14077
+ a.conf
14078
+ );
14079
+ }
13967
14080
  }
13968
14081
  return domainTop(v2);
13969
14082
  }
14083
+ function collapseArrSums(v2) {
14084
+ const s = v2.shape;
14085
+ if (s.k === "sum") {
14086
+ const p = sumArrParts(s);
14087
+ if (p.ok && p.collapse && p.element) {
14088
+ return abs({ k: "arr", element: p.element }, void 0, void 0, v2.conf);
14089
+ }
14090
+ return v2;
14091
+ }
14092
+ if (s.k === "obj") {
14093
+ let changed = false;
14094
+ const slots = {};
14095
+ for (const [k, slot] of Object.entries(s.slots)) {
14096
+ const w = collapseArrSums(slot.value);
14097
+ if (w !== slot.value) changed = true;
14098
+ slots[k] = { ...slot, value: w };
14099
+ }
14100
+ return changed ? abs({ ...s, slots }, void 0, void 0, v2.conf) : v2;
14101
+ }
14102
+ return v2;
14103
+ }
13970
14104
  function makeLoopWidener() {
13971
14105
  let acc;
13972
14106
  let growth = null;
@@ -13988,7 +14122,10 @@ function makeLoopWidener() {
13988
14122
  observe(final);
13989
14123
  const base = acc ?? final;
13990
14124
  const g = interrupted ? true : growth;
13991
- return g === null ? base : widenWithPath(base, g === true ? true : g);
14125
+ if (g === null) {
14126
+ return collapseArrSums(base);
14127
+ }
14128
+ return widenWithPath(normalizeForWiden(base, g), g);
13992
14129
  }
13993
14130
  };
13994
14131
  }
@@ -15091,7 +15228,7 @@ function $forOf(iterable, body, maxIters = DEFAULT_MAX_LOOP_ITERS, opts) {
15091
15228
  }
15092
15229
  if (unbounded && n === 0) snapExit();
15093
15230
  if (concreteOverrun) {
15094
- noteAbsTruncation(LOOP_TRUNCATION_LABEL);
15231
+ noteLoopTruncation(LOOP_TRUNCATION_LABEL);
15095
15232
  if (pack && unpack) unpack(confPartialPacked(pack()));
15096
15233
  return;
15097
15234
  }
@@ -15886,7 +16023,7 @@ function $for(init, test, step, body, maxIters = DEFAULT_MAX_LOOP_ITERS, opts) {
15886
16023
  snapCounter();
15887
16024
  snapExt();
15888
16025
  if (concreteExhausted) {
15889
- noteAbsTruncation(LOOP_TRUNCATION_LABEL);
16026
+ noteLoopTruncation(LOOP_TRUNCATION_LABEL);
15890
16027
  if (extJoin && pack && unpack) unpack(confPartialPacked(widenLoopJoin(joinAbs(extJoin, pack()))));
15891
16028
  return confPartial(exitJoin ?? state);
15892
16029
  }
@@ -15918,7 +16055,7 @@ function widenLoopJoin(a) {
15918
16055
  const known = els.length ? els.reduce((x, y) => joinAbs(x, y)) : unknown;
15919
16056
  const element = undefArms.length ? undefArms.reduce((x, y) => joinAbs(x, y), known) : known;
15920
16057
  return abs(
15921
- { k: "arr", element: joinAbs(element, oobUndef()) },
16058
+ { k: "arr", element },
15922
16059
  void 0,
15923
16060
  void 0,
15924
16061
  confJoin(v2.conf, "widened")
@@ -15975,7 +16112,7 @@ function $while(init, test, step, maxIters = DEFAULT_MAX_LOOP_ITERS) {
15975
16112
  }
15976
16113
  }
15977
16114
  if (concreteExhausted) {
15978
- noteAbsTruncation(LOOP_TRUNCATION_LABEL);
16115
+ noteLoopTruncation(LOOP_TRUNCATION_LABEL);
15979
16116
  return confPartial(exitJoin ? joinAbs(exitJoin, state) : state);
15980
16117
  }
15981
16118
  return exitJoin ? widen.widenFinal(joinAbs(exitJoin, state)) : state;
@@ -16038,7 +16175,7 @@ function $whileSeq(test, body, maxIters = DEFAULT_MAX_LOOP_ITERS, opts) {
16038
16175
  }
16039
16176
  snapExit();
16040
16177
  if (concreteExhausted) {
16041
- noteAbsTruncation(LOOP_TRUNCATION_LABEL);
16178
+ noteLoopTruncation(LOOP_TRUNCATION_LABEL);
16042
16179
  if (exitJoin && pack && unpack) unpack(confPartialPacked(joinAbs(exitJoin, pack())));
16043
16180
  return;
16044
16181
  }
@@ -17379,6 +17516,7 @@ function argLacksInformation(a) {
17379
17516
  return false;
17380
17517
  }
17381
17518
  function adoptCallArgsToFaces(args, faces) {
17519
+ if (currentAssumePremises() !== pTrue) return args;
17382
17520
  const n = Math.min(args.length, faces.paramFaces.length);
17383
17521
  let changed = false;
17384
17522
  const out = args.slice();
@@ -17654,6 +17792,22 @@ var evalCallDepthLimit = MAX_EVAL_CALL_DEPTH;
17654
17792
  var evalTotalCallsLimit = MAX_EVAL_TOTAL_CALLS;
17655
17793
  var evalFnCallIds = /* @__PURE__ */ new WeakMap();
17656
17794
  var evalFnCallIdSeq = 0;
17795
+ var evalObsCalls = 0;
17796
+ var evalObsCallTruncated = false;
17797
+ var evalObsCycleTruncated = false;
17798
+ registerEvalCallBudgetProbe({
17799
+ stats: () => ({
17800
+ calls: evalObsCalls,
17801
+ maxCalls: evalTotalCallsLimit,
17802
+ callTruncated: evalObsCallTruncated,
17803
+ cycleTruncated: evalObsCycleTruncated
17804
+ }),
17805
+ resetObservability: () => {
17806
+ evalObsCalls = 0;
17807
+ evalObsCallTruncated = false;
17808
+ evalObsCycleTruncated = false;
17809
+ }
17810
+ });
17657
17811
  function evalStableId(obj2) {
17658
17812
  let id = evalFnCallIds.get(obj2);
17659
17813
  if (id === void 0) {
@@ -17669,6 +17823,9 @@ function resetEvalCallBudget() {
17669
17823
  evalBudgetSessionDepth = 0;
17670
17824
  evalCallDepthLimit = MAX_EVAL_CALL_DEPTH;
17671
17825
  evalTotalCallsLimit = MAX_EVAL_TOTAL_CALLS;
17826
+ evalObsCalls = 0;
17827
+ evalObsCallTruncated = false;
17828
+ evalObsCycleTruncated = false;
17672
17829
  resetEvalForkBudget();
17673
17830
  clearPureMemo();
17674
17831
  clearPureCallMemo();
@@ -17694,7 +17851,7 @@ function enterEvalCallBudgetSession() {
17694
17851
  evalCallDepth = 0;
17695
17852
  evalTotalCalls = 0;
17696
17853
  evalActiveCallKeys = [];
17697
- resetEvalForkBudget();
17854
+ resetEvalForkCount();
17698
17855
  }
17699
17856
  evalBudgetSessionDepth++;
17700
17857
  }
@@ -17718,13 +17875,17 @@ function evalCallBudgetKey(name, fn2, args) {
17718
17875
  }
17719
17876
  function evalEnterCall(name, fn2, args) {
17720
17877
  const key = evalCallBudgetKey(name, fn2, args);
17721
- if (evalActiveCallKeys.includes(key) || evalCallDepth >= evalCallDepthLimit || evalTotalCalls >= evalTotalCallsLimit) {
17722
- noteAbsTruncation(name);
17878
+ const reason = evalActiveCallKeys.includes(key) ? "cycle" : evalCallDepth >= evalCallDepthLimit ? "depth" : evalTotalCalls >= evalTotalCallsLimit ? "calls" : void 0;
17879
+ if (reason) {
17880
+ evalObsCallTruncated = true;
17881
+ if (reason === "cycle") evalObsCycleTruncated = true;
17882
+ noteAbsTruncation(name, reason);
17723
17883
  return { ok: false };
17724
17884
  }
17725
17885
  evalActiveCallKeys.push(key);
17726
17886
  evalCallDepth++;
17727
17887
  evalTotalCalls++;
17888
+ evalObsCalls++;
17728
17889
  return { ok: true, key };
17729
17890
  }
17730
17891
  function evalExitCall() {
@@ -21633,9 +21794,7 @@ function callTranspiledExportFullInner(exports, name, args, opts) {
21633
21794
  return runWithLoopExits(() => {
21634
21795
  try {
21635
21796
  const invoke = () => withNewTargetReset(() => fn2(...callArgs));
21636
- const ap = opts?.assumePremises;
21637
- const seeded = ap !== void 0 ? () => withAssumePremises(ap, invoke) : invoke;
21638
- const r = opts?.phi ? withExecPhi(opts.phi, seeded) : seeded();
21797
+ const r = opts?.phi ? withExecPhi(opts.phi, invoke) : invoke();
21639
21798
  drainPromiseMicros();
21640
21799
  if (!isAbsVal(r)) return joinControlExits(asAbsVal(r));
21641
21800
  return joinControlExits(r);
@@ -26243,6 +26402,7 @@ export {
26243
26402
  $delRes,
26244
26403
  $del,
26245
26404
  DEFAULT_MAX_LOOP_ITERS,
26405
+ LOOP_TRUNCATION_LABEL,
26246
26406
  defineClass,
26247
26407
  getClass,
26248
26408
  getClassChain,
@@ -47,7 +47,7 @@ import {
47
47
  withEvalBudgetOverride,
48
48
  withExecPhi,
49
49
  withFnBudgetOverride
50
- } from "./chunk-OYQMVCUV.js";
50
+ } from "./chunk-NEKTOZLJ.js";
51
51
 
52
52
  // src/algebra/directive-scan.ts
53
53
  function cleanDirectiveLine(raw, kind) {
package/dist/exec.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as Abs, M as MayThrowEffect, b as Phi } from './run-CDBM1fJZ.js';
2
- export { W as $assignRecord, X as $call, Y as $callNamed, bI as $nodoPos, Z as $recordBinding, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, g as EvalFallbackStats, a8 as EvalPosRecord, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, ad as RUNTIME_IMPORT_RE, V as RunTranspiledOptions, af as TranspiledCallResult, at as bindingsOf, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, aD as evalExprAbs, p as exitEvalCallBudgetSession, v as getEvalCallBudgetState, aG as getEvalCallCollector, w as getEvalFallbackStats, bJ as getPosCollector, aO as isAbsVal, aQ as isCjsMainRun, b5 as noteEvalCallRecord, b6 as noteEvalFallback, bn as resetEvalCallBudget, H as resetEvalFallbackStats, bK as routeApplyThrows, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bv as setPosCollector, bF as tryRunTranspiled, O as withEvalBudgetOverride } from './run-CDBM1fJZ.js';
1
+ import { A as Abs, M as MayThrowEffect, b as Phi } from './run-dg1bW60I.js';
2
+ export { W as $assignRecord, X as $call, Y as $callNamed, bI as $nodoPos, Z as $recordBinding, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, g as EvalFallbackStats, a8 as EvalPosRecord, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, ad as RUNTIME_IMPORT_RE, V as RunTranspiledOptions, af as TranspiledCallResult, at as bindingsOf, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, aD as evalExprAbs, p as exitEvalCallBudgetSession, v as getEvalCallBudgetState, aG as getEvalCallCollector, w as getEvalFallbackStats, bJ as getPosCollector, aO as isAbsVal, aQ as isCjsMainRun, b5 as noteEvalCallRecord, b6 as noteEvalFallback, bn as resetEvalCallBudget, H as resetEvalFallbackStats, bK as routeApplyThrows, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bv as setPosCollector, bF as tryRunTranspiled, O as withEvalBudgetOverride } from './run-dg1bW60I.js';
3
3
  import { Node, Expression, File } from '@babel/types';
4
4
 
5
5
  /**
package/dist/exec.js CHANGED
@@ -202,7 +202,7 @@ import {
202
202
  withAssumePremises,
203
203
  withEvalBudgetOverride,
204
204
  withExecPhi
205
- } from "./chunk-OYQMVCUV.js";
205
+ } from "./chunk-NEKTOZLJ.js";
206
206
  export {
207
207
  $absVal,
208
208
  $add,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { d as AstEnv, A as Abs, b as Phi, T as Term, Q as HofCollectCtx, C as Confidence, S as Shape, a as Pred, R as RelSource, U as HofSite, c as AbsModuleExports, V as RunTranspiledOptions } from './run-CDBM1fJZ.js';
2
- export { W as $assignRecord, X as $call, Y as $callNamed, Z as $recordBinding, _ as AbsApplyNoThrow, a0 as AbsApplyNoThrowFn, a1 as AbsApplyResult, a2 as AbsApplyReturn, a3 as AbsFnImpl, a4 as AbsSigImpl, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, a8 as EvalPosRecord, a9 as ImplicationOracle, aa as LitValueResult, L as LiteralValue, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, P as PrimName, ad as RUNTIME_IMPORT_RE, ae as TYPEOF_NAMES, af as TranspiledCallResult, ag as TypeofName, ah as abs, ai as absFunction, aj as absOnly, ak as absToString, al as and, am as anyAbs, an as anyVar, ao as app, ap as assumeFinite, aq as attachFnImpl, ar as bigintLit, as as bindImports, at as bindingsOf, au as bool, av as boolLit, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, az as collectAbsExports, aA as confJoin, aB as emptyPhi, aC as eq, aD as evalExprAbs, aE as ge, aF as geNum, aG as getEvalCallCollector, aH as getFnImpl, aI as getImplicationOracle, aJ as gt, aK as gtNum, aL as hostFnCtorFacet, aM as implies, aN as isAbsApplyResult, aO as isAbsVal, aP as isBigPrim, aQ as isCjsMainRun, aR as isExactLit, aS as isNumPrim, aT as isStrPrim, aU as le, aV as leNum, aW as lit, aX as litValue, aY as lt, aZ as ltNum, a_ as makeAbsApplyResult, a$ as markPureFn, b0 as namespaceAbsOf, b1 as ne, b2 as negatePred, b3 as never, b4 as not, b5 as noteEvalCallRecord, b6 as noteEvalFallback, b7 as num, b8 as numLit, b9 as numVar, ba as obj, bb as or, bc as pFalse, bd as pTrue, be as phiAnd, bf as predEquals, bg as predToString, bh as predVars, bi as primToTypeof, bj as ptypeof, bk as pureFnNameOf, bl as relationFingerprint, bm as relationFn, bn as resetEvalCallBudget, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bu as setImplicationOracle, bv as setPosCollector, bw as shapeOfTerm, bx as shapeOnlyFn, by as shapeToString, bz as simplifyTerm, bA as str, bB as strLit, bC as substPred, bD as termEquals, bE as termToString, bF as tryRunTranspiled, bG as unknown, bH as v } from './run-CDBM1fJZ.js';
1
+ import { d as AstEnv, A as Abs, b as Phi, T as Term, Q as HofCollectCtx, C as Confidence, S as Shape, a as Pred, R as RelSource, U as HofSite, c as AbsModuleExports, V as RunTranspiledOptions } from './run-dg1bW60I.js';
2
+ export { W as $assignRecord, X as $call, Y as $callNamed, Z as $recordBinding, _ as AbsApplyNoThrow, a0 as AbsApplyNoThrowFn, a1 as AbsApplyResult, a2 as AbsApplyReturn, a3 as AbsFnImpl, a4 as AbsSigImpl, a5 as EvalAbsAssignRecord, a6 as EvalCallRecord, a7 as EvalFallback, a8 as EvalPosRecord, a9 as ImplicationOracle, aa as LitValueResult, L as LiteralValue, ab as MAX_EVAL_CALL_DEPTH, ac as MAX_EVAL_TOTAL_CALLS, P as PrimName, ad as RUNTIME_IMPORT_RE, ae as TYPEOF_NAMES, af as TranspiledCallResult, ag as TypeofName, ah as abs, ai as absFunction, aj as absOnly, ak as absToString, al as and, am as anyAbs, an as anyVar, ao as app, ap as assumeFinite, aq as attachFnImpl, ar as bigintLit, as as bindImports, at as bindingsOf, au as bool, av as boolLit, aw as callTranspiledExport, ax as callTranspiledExportApply, ay as callTranspiledExportFull, az as collectAbsExports, aA as confJoin, aB as emptyPhi, aC as eq, aD as evalExprAbs, aE as ge, aF as geNum, aG as getEvalCallCollector, aH as getFnImpl, aI as getImplicationOracle, aJ as gt, aK as gtNum, aL as hostFnCtorFacet, aM as implies, aN as isAbsApplyResult, aO as isAbsVal, aP as isBigPrim, aQ as isCjsMainRun, aR as isExactLit, aS as isNumPrim, aT as isStrPrim, aU as le, aV as leNum, aW as lit, aX as litValue, aY as lt, aZ as ltNum, a_ as makeAbsApplyResult, a$ as markPureFn, b0 as namespaceAbsOf, b1 as ne, b2 as negatePred, b3 as never, b4 as not, b5 as noteEvalCallRecord, b6 as noteEvalFallback, b7 as num, b8 as numLit, b9 as numVar, ba as obj, bb as or, bc as pFalse, bd as pTrue, be as phiAnd, bf as predEquals, bg as predToString, bh as predVars, bi as primToTypeof, bj as ptypeof, bk as pureFnNameOf, bl as relationFingerprint, bm as relationFn, bn as resetEvalCallBudget, bo as runTranspiled, bp as runTranspiledOptionsMemoKey, bq as setEvalAssignCollector, br as setEvalBindingSink, bs as setEvalCallCollector, bt as setEvalFallbackCollector, bu as setImplicationOracle, bv as setPosCollector, bw as shapeOfTerm, bx as shapeOnlyFn, by as shapeToString, bz as simplifyTerm, bA as str, bB as strLit, bC as substPred, bD as termEquals, bE as termToString, bF as tryRunTranspiled, bG as unknown, bH as v } from './run-dg1bW60I.js';
3
3
  import { Node, File } from '@babel/types';
4
- import { p as parseSource, L as LeakBudget, E as EffectiveInterfaceOpts, b as LoadDepsFingerprint, v as CheckReport, N as NudoConstraint, c as ProjectionBudget } from './projection-budget-BB2U2rwC.js';
5
- export { w as CONSTRAINT_BUILDERS, x as CONSTRAINT_BUILDER_NAMES, y as CONSTRAINT_EXPR_RE, z as CheckAction, C as CheckIssue, A as CheckJson, B as CheckJsonMulti, D as ConstraintBuilder, F as Diagnostic, G as EffectiveInterface, H as GitlabCodeQualityIssue, J as INTERFACE_SOURCE_ABBR, K as InterfaceDiag, I as InterfaceSource, O as InterfaceTierInfo, a as InterfaceTierOpts, Q as NamedImport, R as NudoField, S as NudoFnConstraint, T as NudoSidecarError, U as NudoSig, V as RefineDiag, W as RefineEntry, X as RefineResolveOpts, Y as SELF, Z as actionsForIssue, _ as andC, $ as any, a0 as array, a1 as boolean, a2 as checkArg, a3 as checkCall, a4 as constraintAdmitsNullish, a5 as constraintToEntryAbs, a6 as derefConstraint, a7 as drainPromiseMicros, a8 as effectiveInterface, a9 as enterPromiseExecutorScope, aa as evalPromiseCtor, ab as evalPromiseMethod, ac as evalPromiseStatic, ad as execNudoModule, ae as extractDeclaredThrows, af as extractFnBudget, ag as extractNudoImports, ah as extractRefineReturnFromSource, ai as extractRefinesFromSource, aj as fn, ak as fnConstraintToEntryReqs, al as formatCheckReport, am as formatConstraint, an as formatDiagnostics, ao as formatEffectiveInterfaceDisplay, ap as formatGithubAnnotations, aq as formatGitlabCodeQuality, ar as formatInterfaceTierLine, as as generatedExportNames, at as getParseSourceCacheSize, au as getTerm, av as instantiateConstraint, aw as interfaceDiagCount, ax as interfaceSourceOf, ay as interfaceTierOf, az as isIntFlag, aA as isNodeModulesPath, aB as isNudoConstraint, aC as lazy, aD as leavePromiseExecutorScope, aE as lenTerm, aF as litC, aG as localNamedExports, aH as notePromiseExecutorFork, aI as nullable, aJ as number, aK as omit, aL as partial, aM as pick, aN as queuePromiseMicro, aO as refineDiagCount, aP as refineToIndexedFull, aQ as resetNudoModuleExecCache, aR as resetParseSourceCache, aS as resetSidecarLoadFailureCache, aT as serializeCheckJson, aU as serializeCheckJsonMulti, aV as setInterfaceDiagCollector, aW as setRefineDiagCollector, aX as shape, aY as sidecarClosureFingerprint, aZ as sidecarPathOf, a_ as string, a$ as takeInterfaceDiags, b0 as takeInterfaceDiagsSince, b1 as takeRefineDiags, b2 as takeRefineDiagsSince, b3 as throwConstraintToKinds, b4 as union } from './projection-budget-BB2U2rwC.js';
4
+ import { p as parseSource, L as LeakBudget, E as EffectiveInterfaceOpts, b as LoadDepsFingerprint, v as CheckReport, N as NudoConstraint, c as ProjectionBudget } from './projection-budget-PN_0_9D3.js';
5
+ export { w as CONSTRAINT_BUILDERS, x as CONSTRAINT_BUILDER_NAMES, y as CONSTRAINT_EXPR_RE, z as CheckAction, C as CheckIssue, A as CheckJson, B as CheckJsonMulti, D as ConstraintBuilder, F as Diagnostic, G as EffectiveInterface, H as GitlabCodeQualityIssue, J as INTERFACE_SOURCE_ABBR, K as InterfaceDiag, I as InterfaceSource, O as InterfaceTierInfo, a as InterfaceTierOpts, Q as NamedImport, R as NudoField, S as NudoFnConstraint, T as NudoSidecarError, U as NudoSig, V as RefineDiag, W as RefineEntry, X as RefineResolveOpts, Y as SELF, Z as actionsForIssue, _ as andC, $ as any, a0 as array, a1 as boolean, a2 as checkArg, a3 as checkCall, a4 as constraintAdmitsNullish, a5 as constraintToEntryAbs, a6 as derefConstraint, a7 as drainPromiseMicros, a8 as effectiveInterface, a9 as enterPromiseExecutorScope, aa as evalPromiseCtor, ab as evalPromiseMethod, ac as evalPromiseStatic, ad as execNudoModule, ae as extractDeclaredThrows, af as extractFnBudget, ag as extractNudoImports, ah as extractRefineReturnFromSource, ai as extractRefinesFromSource, aj as fn, ak as fnConstraintToEntryReqs, al as formatCheckReport, am as formatConstraint, an as formatDiagnostics, ao as formatEffectiveInterfaceDisplay, ap as formatGithubAnnotations, aq as formatGitlabCodeQuality, ar as formatInterfaceTierLine, as as generatedExportNames, at as getParseSourceCacheSize, au as getTerm, av as instantiateConstraint, aw as interfaceDiagCount, ax as interfaceSourceOf, ay as interfaceTierOf, az as isIntFlag, aA as isNodeModulesPath, aB as isNudoConstraint, aC as lazy, aD as leavePromiseExecutorScope, aE as lenTerm, aF as litC, aG as localNamedExports, aH as notePromiseExecutorFork, aI as nullable, aJ as number, aK as omit, aL as partial, aM as pick, aN as queuePromiseMicro, aO as refineDiagCount, aP as refineToIndexedFull, aQ as resetNudoModuleExecCache, aR as resetParseSourceCache, aS as resetSidecarLoadFailureCache, aT as serializeCheckJson, aU as serializeCheckJsonMulti, aV as setInterfaceDiagCollector, aW as setRefineDiagCollector, aX as shape, aY as sidecarClosureFingerprint, aZ as sidecarPathOf, a_ as string, a$ as takeInterfaceDiags, b0 as takeInterfaceDiagsSince, b1 as takeRefineDiags, b2 as takeRefineDiagsSince, b3 as throwConstraintToKinds, b4 as union } from './projection-budget-PN_0_9D3.js';
6
6
  export { $add, $arguments, $arr, $arrMutContainer, $arrRest, $arrWithHoles, $async, $asyncReturn, $await, $bitand, $bitnot, $bitor, $bitxor, $catchVal, $class, $classExpr, $collectionForEach, $concat, $copy, $del, $delRes, $div, $dynamicImport, $elems, $eq, $eqLoose, $fnVal, $for, $forInKeys, $forIter, $forOf, $fork, $ge, $gen, $get, $gt, $idx, $idxSet, $importMeta, $in, $instanceof, $instanceofNonIdent, $invoke, $invokeSuper, $isBreakTo, $isForkExit, $join, $le, $len, $lit, $loopBreak, $loopContinue, $loopReturn, $lt, $mod, $mul, $ne, $neLoose, $neg, $new, $not, $nullishTest, $obj, $objAccessor, $objRest, $optionalGet, $optionalInvoke, $orDefault, $pow, $pushLoopExit, $rawThis, $reStateCall, $regex, $rethrowIfNudoReturn, $set, $setKey, $shl, $shr, $spread, $staticInvoke, $sub, $super, $switch, $thisGet, $thisSet, $throw, $toNumber, $toNumeric, $tryCurrentMark, $tryDetachSoftCatch, $tryDigestSoftCatch, $tryDiscardSoft, $tryMark, $tryOrphanSoft, $tryPopMark, $tryReleaseSoftCatch, $tryReleaseSoftOut, $tryTakeSince, $typeof, $unknown, $updateAdd, $updateSub, $ushr, $while, $whileSeq, $yield, DEFAULT_MAX_LOOP_ITERS, EvalClassSpec, NudoLoopSignal, NudoReturn, NudoThrow, NudoUnsupportedError, TranspileOptions, asAbsVal, callAtFunctionBoundary, clearBClasses, clearStaleTermPred, currentExecPhi, fillTuple, foldRequireSpecArg, foldStaticStringExpr, getEvalClass, isArrMutator, isDefinitelyFalse, isDefinitelyTrue, isNudoBreak, isNudoContinue, isNudoReturn, isNudoThrow, litTruth, lookupObjAccessor, namespaceNameOf, pushLoopExit, pushThrowExit, registerEvalClass, runWithLoopExits, runtimeImportOf, takeLoopExits, takeThrowExits, transpile, transpileBodyNode, transpileExpression, transpileFile, transpileSource, withExecPhi } from './exec.js';
7
7
 
8
8
  /**
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ import {
65
65
  takeInterfaceDiagsSince,
66
66
  takeRefineDiags,
67
67
  takeRefineDiagsSince
68
- } from "./chunk-UTRLGWJL.js";
68
+ } from "./chunk-SWG6PLYE.js";
69
69
  import {
70
70
  $add,
71
71
  $arguments,
@@ -183,6 +183,7 @@ import {
183
183
  DEFAULT_MAX_LOOP_ITERS,
184
184
  FORK_TRUNCATION_LABEL,
185
185
  HOST_EFFECT_LABEL_PREFIX,
186
+ LOOP_TRUNCATION_LABEL,
186
187
  MATH_FOLD_ERROR_LABEL,
187
188
  MAX_EVAL_CALL_DEPTH,
188
189
  MAX_EVAL_TOTAL_CALLS,
@@ -524,7 +525,7 @@ import {
524
525
  withAssumePremises,
525
526
  withExecPhi,
526
527
  withVar
527
- } from "./chunk-OYQMVCUV.js";
528
+ } from "./chunk-NEKTOZLJ.js";
528
529
 
529
530
  // src/environment.ts
530
531
  function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
@@ -797,6 +798,17 @@ function flattenSumArms(ret) {
797
798
  function isWidenedSumArm(m, parent) {
798
799
  return (parent.conf === "partial" || m.conf === "widened") && m.shape.k === "prim" && !m.term && (!m.pred || m.pred.op === "true");
799
800
  }
801
+ function constraintMatchesStructuralArm(arm, c) {
802
+ if (c.element) return arm.shape.k === "arr" || arm.shape.k === "tuple";
803
+ if (c.fields) return arm.shape.k === "obj";
804
+ if (c.members) {
805
+ return c.members.some((m) => constraintMatchesStructuralArm(arm, derefConstraint(m)));
806
+ }
807
+ return false;
808
+ }
809
+ function isWidenedStructuralArm(arm, constraint) {
810
+ return arm.conf === "widened" && constraintMatchesStructuralArm(arm, constraint);
811
+ }
800
812
  function provesBarePrimDomain(arm, constraint) {
801
813
  if (constraint.members) {
802
814
  return constraint.members.some((m) => provesBarePrimDomain(arm, derefConstraint(m)));
@@ -855,6 +867,11 @@ function assertSumArms(arms, parent, constraint, opts, phi) {
855
867
  if (isWidenedSumArm(arm, parent) && provesBarePrimDomain(arm, constraint)) {
856
868
  continue;
857
869
  }
870
+ if (isWidenedStructuralArm(arm, constraint)) {
871
+ const r2 = assertImpliesSingle(arm, constraint, opts, phi);
872
+ if (r2.status !== "proved") sawUnprovable = true;
873
+ continue;
874
+ }
858
875
  sawUnprovable = true;
859
876
  continue;
860
877
  }
@@ -877,7 +894,7 @@ function assertImpliesSingle(arm, constraint, opts, phi) {
877
894
  if (isNullishLitAbs(arm)) {
878
895
  return constraintAdmitsNullish(constraint) ? proved() : disproved(`nullish return \u22AD ${formatConstraint(constraint)}`);
879
896
  }
880
- if (isValueSetUnknown(arm)) {
897
+ if (isValueSetUnknown(arm) && !isWidenedStructuralArm(arm, constraint)) {
881
898
  if (arm.conf === "widened" && arm.shape.k === "prim" && !arm.term && (!arm.pred || arm.pred.op === "true") && provesBarePrimDomain(arm, constraint)) {
882
899
  return proved();
883
900
  }
@@ -2194,9 +2211,13 @@ function checkSourceInScope(filePath, source, phi, opts) {
2194
2211
  const file = parseSource(source);
2195
2212
  const names = listTopFunctions(source, file);
2196
2213
  const memoPaths = sidecarFp !== void 0 && callOpts.fromFile ? [.../* @__PURE__ */ new Set([...depsFp.paths, normPath(sidecarPathOf(callOpts.fromFile))])] : depsFp.paths;
2197
- const truncated = /* @__PURE__ */ new Set();
2214
+ const truncated = /* @__PURE__ */ new Map();
2198
2215
  resetAbsCallBudget();
2199
- const prevTrunc = setAbsTruncationCollector((label) => truncated.add(label));
2216
+ const prevTrunc = setAbsTruncationCollector((label, reason) => {
2217
+ const reasons = truncated.get(label) ?? /* @__PURE__ */ new Set();
2218
+ if (reason) reasons.add(reason);
2219
+ truncated.set(label, reasons);
2220
+ });
2200
2221
  try {
2201
2222
  const prevFaceResolver = setContractFaceResolver(
2202
2223
  makeSameFileFaceResolver(callOpts, filePath, source, file)
@@ -2675,7 +2696,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2675
2696
  });
2676
2697
  }
2677
2698
  }
2678
- for (const label of truncated) {
2699
+ for (const [label, reasons] of truncated) {
2679
2700
  if (label === FORK_TRUNCATION_LABEL) {
2680
2701
  issues.push({
2681
2702
  severity: "info",
@@ -2723,11 +2744,24 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2723
2744
  });
2724
2745
  continue;
2725
2746
  }
2747
+ if (label === LOOP_TRUNCATION_LABEL) {
2748
+ issues.push({
2749
+ severity: "info",
2750
+ code: "nudo:loop-truncated",
2751
+ message: `A loop did not terminate within its iteration budget (abstract unroll cap reached and the exit condition stayed concrete-true); affected loop state was widened (bounded, not exact)`,
2752
+ suggestion: "optional: restructure so the exit condition becomes abstract (data-dependent) within a few iterations, or pin the result with @nudo:case / @nudo:contract \u2014 non-blocking"
2753
+ });
2754
+ continue;
2755
+ }
2756
+ const allCycle = reasons.size > 0 && [...reasons].every((r) => r === "cycle");
2757
+ const depth = reasons.has("depth");
2758
+ const calls = reasons.has("calls");
2726
2759
  issues.push({
2727
2760
  severity: "info",
2728
2761
  code: "nudo:recursion-truncated",
2729
- message: `Recursive evaluation of '${label}' was truncated (depth/size budget); result widened to unknown#opaque (budget \u2014 not inference debt)`,
2730
- suggestion: "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
2762
+ message: allCycle ? `Recursive evaluation of '${label}' hit a call cycle (same abstract arguments already on the stack); result widened to unknown#opaque (cycle \u2014 budget cannot lift this)` : `Recursive evaluation of '${label}' was truncated (${depth && calls ? "depth/size budget" : depth ? "call-depth budget" : calls ? "total-calls budget" : "depth/size budget"}); result widened to unknown#opaque (budget \u2014 not inference debt)`,
2763
+ suggestion: allCycle ? "a call cycle is not a budget: make the recursion arguments vary per level, narrow the base case, or declare @nudo:contract \u2014 raising @nudo:budget will not change this" : depth && !calls ? "raise the function budget with @nudo:budget depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)" : calls && !depth ? "raise the function budget with @nudo:budget calls=\u2026 (or narrow the recursion base case / declare @nudo:contract return)" : "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
2764
+ reason: allCycle ? "cycle" : depth && calls ? "budget" : depth ? "depth" : calls ? "calls" : "budget",
2731
2765
  fn: label
2732
2766
  });
2733
2767
  }
@@ -2837,15 +2871,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2837
2871
  ok: errors === 0,
2838
2872
  signatures,
2839
2873
  summary: { errors, warnings, infos, functions: signatures.length },
2840
- budget: {
2841
- truncated: budget.truncated,
2842
- callTruncated: budget.callTruncated,
2843
- forkTruncated: budget.forkTruncated,
2844
- calls: budget.calls,
2845
- maxCalls: budget.maxCalls,
2846
- forks: budget.forks,
2847
- maxForks: budget.maxForks
2848
- }
2874
+ budget: { ...budget }
2849
2875
  };
2850
2876
  }
2851
2877
  function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay) {
@@ -2974,7 +3000,6 @@ function actionsForIssue(i) {
2974
3000
  { kind: "info", label: "add a call site or @nudo:case" }
2975
3001
  ];
2976
3002
  case "nudo:fork-truncated":
2977
- case "nudo:recursion-truncated":
2978
3003
  case "nudo:promise-micro-truncated":
2979
3004
  case "nudo:promise-micro-error":
2980
3005
  return [
@@ -2983,6 +3008,20 @@ function actionsForIssue(i) {
2983
3008
  label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
2984
3009
  }
2985
3010
  ];
3011
+ case "nudo:loop-truncated":
3012
+ return [
3013
+ {
3014
+ kind: "info",
3015
+ label: "loop state widened \u2014 the exit condition stayed concrete-true past the iteration cap; make the bound data-dependent (abstract) or pin the result with @nudo:case / @nudo:contract"
3016
+ }
3017
+ ];
3018
+ case "nudo:recursion-truncated":
3019
+ return [
3020
+ {
3021
+ kind: "info",
3022
+ label: i.reason === "cycle" ? "call cycle: same abstract arguments already on the stack \u2014 raising @nudo:budget cannot lift it; make the arguments vary per recursion level or declare @nudo:contract" : i.reason === "depth" || i.reason === "calls" || i.reason === "budget" ? `results widened to unknown \u2014 raise budget (@nudo:budget ${i.reason === "depth" ? "depth" : i.reason === "calls" ? "calls" : "calls/depth"} on the function) or narrow the recursion` : "results widened to unknown \u2014 raise budget (@nudo:budget calls/depth on the function) or narrow control flow"
3023
+ }
3024
+ ];
2986
3025
  case "nudo:host-effect-blocked":
2987
3026
  return [
2988
3027
  { kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
@@ -3036,6 +3075,7 @@ function serializeCheckJson(r) {
3036
3075
  ...i.actual !== void 0 ? { actual: i.actual } : {},
3037
3076
  ...i.expected !== void 0 ? { expected: i.expected } : {},
3038
3077
  ...i.suggestion !== void 0 ? { suggestion: i.suggestion } : {},
3078
+ ...i.reason !== void 0 ? { reason: i.reason } : {},
3039
3079
  ...acts.length > 0 ? { actions: acts } : {}
3040
3080
  };
3041
3081
  }),
@@ -3056,6 +3096,12 @@ function serializeCheckJsonMulti(reports) {
3056
3096
  let maxForks = 0;
3057
3097
  let callTruncated = false;
3058
3098
  let forkTruncated = false;
3099
+ let evalCalls = 0;
3100
+ let maxEvalCalls = 0;
3101
+ let evalCallTruncated = false;
3102
+ let cycleTruncated = false;
3103
+ let loopTruncated = false;
3104
+ const reasons = /* @__PURE__ */ new Set();
3059
3105
  for (const r of reports) {
3060
3106
  summary.errors += r.summary.errors;
3061
3107
  summary.warnings += r.summary.warnings;
@@ -3068,9 +3114,15 @@ function serializeCheckJsonMulti(reports) {
3068
3114
  maxForks = Math.max(maxForks, r.budget.maxForks);
3069
3115
  callTruncated = callTruncated || r.budget.callTruncated;
3070
3116
  forkTruncated = forkTruncated || r.budget.forkTruncated;
3117
+ evalCalls += r.budget.evalCalls ?? 0;
3118
+ maxEvalCalls = Math.max(maxEvalCalls, r.budget.maxEvalCalls ?? 0);
3119
+ evalCallTruncated = evalCallTruncated || (r.budget.evalCallTruncated ?? false);
3120
+ cycleTruncated = cycleTruncated || (r.budget.cycleTruncated ?? false);
3121
+ loopTruncated = loopTruncated || (r.budget.loopTruncated ?? false);
3122
+ for (const x of r.budget.reasons ?? []) reasons.add(x);
3071
3123
  }
3072
3124
  }
3073
- const budgetTruncated = callTruncated || forkTruncated;
3125
+ const budgetTruncated = callTruncated || evalCallTruncated || forkTruncated || loopTruncated;
3074
3126
  return {
3075
3127
  version: 1,
3076
3128
  kind: "multi",
@@ -3087,7 +3139,13 @@ function serializeCheckJsonMulti(reports) {
3087
3139
  calls,
3088
3140
  maxCalls,
3089
3141
  forks,
3090
- maxForks
3142
+ maxForks,
3143
+ evalCalls,
3144
+ maxEvalCalls,
3145
+ evalCallTruncated,
3146
+ cycleTruncated,
3147
+ loopTruncated,
3148
+ reasons: [...reasons].sort()
3091
3149
  }
3092
3150
  } : {},
3093
3151
  reports
@@ -3109,13 +3167,14 @@ function formatCheckReport(r, opts = {}) {
3109
3167
  ` ${r.summary.errors} error \xB7 ${r.summary.warnings} warning \xB7 ${r.summary.infos} info \xB7 ${r.summary.functions} fn`
3110
3168
  );
3111
3169
  if (r.budget?.truncated) {
3170
+ const onlyLoop = r.budget.loopTruncated && !r.budget.callTruncated && !r.budget.evalCallTruncated && !r.budget.forkTruncated;
3112
3171
  lines.push("");
3113
3172
  lines.push("budget");
3114
3173
  lines.push(
3115
- ` truncated calls ${r.budget.calls}/${r.budget.maxCalls} \xB7 forks ${r.budget.forks}/${r.budget.maxForks}${r.budget.callTruncated ? " (calls)" : ""}${r.budget.forkTruncated ? " (forks)" : ""}`
3174
+ ` truncated calls ${r.budget.calls}/${r.budget.maxCalls} \xB7 eval calls ${r.budget.evalCalls}/${r.budget.maxEvalCalls} \xB7 forks ${r.budget.forks}/${r.budget.maxForks}${r.budget.callTruncated ? " (calls)" : ""}${r.budget.evalCallTruncated ? " (eval calls)" : ""}${r.budget.forkTruncated ? " (forks)" : ""}${r.budget.loopTruncated ? " (loop iters)" : ""}`
3116
3175
  );
3117
3176
  lines.push(
3118
- ` \u2192 some results widened to unknown (not a contract failure) \u2014 raise nudo.analysis.maxForks / simplify recursion`
3177
+ r.budget.cycleTruncated ? ` \u2192 some results widened to unknown (not a contract failure) \u2014 includes call cycles (same abstract args; a budget cannot lift them)` : onlyLoop ? ` \u2192 loop state widened (iteration budget, not a contract failure) \u2014 @nudo:budget does not apply; make the bound data-dependent or pin with @nudo:case / @nudo:contract` : ` \u2192 some results widened to unknown (not a contract failure) \u2014 raise @nudo:budget / nudo.analysis.maxForks or simplify recursion`
3119
3178
  );
3120
3179
  }
3121
3180
  if (r.signatures.length > 0) {
@@ -1,7 +1,7 @@
1
- import { I as InterfaceSource, a as InterfaceTierOpts, p as parseSource, C as CheckIssue } from './projection-budget-BB2U2rwC.js';
2
- export { L as LeakBudget, b as LoadDepsFingerprint, M as MAX_PROMISE_MICROS, P as PROJECTION_MAX_DEPTH, c as ProjectionBudget, d as ProjectionStopReason, e as defaultLeakBudget, f as exceedsBudget, g as exportedNameOfLocal, h as extractAllLoadSpecs, i as getPromiseMicrosLength, l as leakIfNeeded, j as loadModuleDepsFingerprint, m as maybeLeak, n as normPath, r as resetLeakCounter, k as resolveDepPath, s as sidecarSpecsOf, o as stablePathKey, q as stablePathKeyGraph, t as termDepth, u as termNodes } from './projection-budget-BB2U2rwC.js';
3
- import { A as Abs, T as Term, c as AbsModuleExports, d as AstEnv } from './run-CDBM1fJZ.js';
4
- export { $ as $tryDigestSoft, e as $tryMarkSoft, f as $tryReleaseSoft, E as ERROR_FAMILY, g as EvalFallbackStats, M as MayThrowEffect, h as absLit, i as absNumLit, j as absStrLit, k as allAbsStr, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, o as errorTypeAbs, p as exitEvalCallBudgetSession, q as filterDeclaredThrows, r as filterGateThrows, s as filterIgnoredThrows, t as flushMayThrowEffects, u as formatThrowsAbs, v as getEvalCallBudgetState, w as getEvalFallbackStats, x as getMayThrowCollector, y as isThrowsIgnored, z as mayThrowEffectsToAbs, B as orphanMayThrowEffects, D as popMayThrowFrame, F as pushMayThrowFrame, G as recordMayThrow, H as resetEvalFallbackStats, I as runWithMayThrowSession, J as setMayThrowCollector, K as throwAbsToKinds, N as throwsKindCovered, O as withEvalBudgetOverride } from './run-CDBM1fJZ.js';
1
+ import { I as InterfaceSource, a as InterfaceTierOpts, p as parseSource, C as CheckIssue } from './projection-budget-PN_0_9D3.js';
2
+ export { L as LeakBudget, b as LoadDepsFingerprint, M as MAX_PROMISE_MICROS, P as PROJECTION_MAX_DEPTH, c as ProjectionBudget, d as ProjectionStopReason, e as defaultLeakBudget, f as exceedsBudget, g as exportedNameOfLocal, h as extractAllLoadSpecs, i as getPromiseMicrosLength, l as leakIfNeeded, j as loadModuleDepsFingerprint, m as maybeLeak, n as normPath, r as resetLeakCounter, k as resolveDepPath, s as sidecarSpecsOf, o as stablePathKey, q as stablePathKeyGraph, t as termDepth, u as termNodes } from './projection-budget-PN_0_9D3.js';
3
+ import { A as Abs, T as Term, c as AbsModuleExports, d as AstEnv } from './run-dg1bW60I.js';
4
+ export { $ as $tryDigestSoft, e as $tryMarkSoft, f as $tryReleaseSoft, E as ERROR_FAMILY, g as EvalFallbackStats, M as MayThrowEffect, h as absLit, i as absNumLit, j as absStrLit, k as allAbsStr, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, o as errorTypeAbs, p as exitEvalCallBudgetSession, q as filterDeclaredThrows, r as filterGateThrows, s as filterIgnoredThrows, t as flushMayThrowEffects, u as formatThrowsAbs, v as getEvalCallBudgetState, w as getEvalFallbackStats, x as getMayThrowCollector, y as isThrowsIgnored, z as mayThrowEffectsToAbs, B as orphanMayThrowEffects, D as popMayThrowFrame, F as pushMayThrowFrame, G as recordMayThrow, H as resetEvalFallbackStats, I as runWithMayThrowSession, J as setMayThrowCollector, K as throwAbsToKinds, N as throwsKindCovered, O as withEvalBudgetOverride } from './run-dg1bW60I.js';
5
5
  import { File, Node } from '@babel/types';
6
6
 
7
7
  /**
@@ -60,6 +60,13 @@ declare function sourceHasCjsExports(source: string): boolean;
60
60
  declare const MAX_CALL_DEPTH = 64;
61
61
  /** 与 B 命名调用(calls.ts MAX_EVAL_TOTAL_CALLS)同阀:病态展开下 200k 级不可接受 */
62
62
  declare const MAX_TOTAL_CALLS = 20000;
63
+ /**
64
+ * 调用截断归因(issue #158):
65
+ * - `cycle`:同 name+arg 指纹的调用已在栈上——**不是预算**,抬高
66
+ * `@nudo:budget` 永远解不开(数据依赖自递归在抽象层就是不动点);
67
+ * - `depth` / `calls`:真预算超限,`@nudo:budget` 可抬。
68
+ */
69
+ type AbsTruncationReason = "cycle" | "depth" | "calls";
63
70
  /** A2:预算/截断快照(check --json / health 上屏) */
64
71
  type AbsBudgetStats = {
65
72
  calls: number;
@@ -70,6 +77,18 @@ type AbsBudgetStats = {
70
77
  forkTruncated: boolean;
71
78
  /** 任一预算截断 → true(结果可能已 widen 成 unknown) */
72
79
  truncated: boolean;
80
+ /** B 命名调用(exec $callNamed)累计进入次数——宿主入口内跨执行会话累计 */
81
+ evalCalls: number;
82
+ /** B 命名调用当前生效上限 */
83
+ maxEvalCalls: number;
84
+ /** B 命名调用发生截断(cycle/depth/calls 任一) */
85
+ evalCallTruncated: boolean;
86
+ /** 任一路径的 cycle 截断(预算抬不动的那类;issue #158) */
87
+ cycleTruncated: boolean;
88
+ /** 循环迭代预算截断(#loop-iterations;maxIters/硬上限,非调用预算) */
89
+ loopTruncated: boolean;
90
+ /** 调用截断原因集合(A+B;稳定排序,进 JSON) */
91
+ reasons: AbsTruncationReason[];
73
92
  };
74
93
  declare function getAbsCallBudgetStats(): AbsBudgetStats;
75
94
  /** 宿主入口(runTranspiled / callTranspiledExportFull / check)前重置 */
@@ -95,9 +114,11 @@ declare function truncatedAbs(): Abs;
95
114
  declare function callBudgetKey(kind: string, id: string, args: unknown[]): string;
96
115
  /** 记录被截断的递归(service 可映射为 nudo:recursion-truncated)。
97
116
  * 返回先前 collector,便于嵌套 save/restore。 */
98
- declare function setAbsTruncationCollector(collector: ((fnLabel: string) => void) | null): ((fnLabel: string) => void) | null;
99
- declare function noteAbsTruncation(label: string): void;
100
- /** 进入调用:超限/cycle 则不执行 body,返回 opaque */
117
+ declare function setAbsTruncationCollector(collector: ((fnLabel: string, reason?: AbsTruncationReason) => void) | null): ((fnLabel: string, reason?: AbsTruncationReason) => void) | null;
118
+ declare function noteAbsTruncation(label: string, reason?: AbsTruncationReason): void;
119
+ /** 进入调用:cycle/超限则不执行 body,返回 opaque。
120
+ * 归因顺序 = 守卫顺序(cycle 先于 depth 先于 calls):同指纹调用已在栈上
121
+ * 时无论预算多深都会立即截断——报 cycle 而不是预算(#158)。 */
101
122
  declare function enterCall(key: string, label: string): boolean;
102
123
  declare function exitCall(): void;
103
124
  /**
package/dist/internal.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  stableAnalyzeKeySource,
22
22
  stablePathKey,
23
23
  stablePathKeyGraph
24
- } from "./chunk-UTRLGWJL.js";
24
+ } from "./chunk-SWG6PLYE.js";
25
25
  import {
26
26
  $tryDigestSoft,
27
27
  $tryMarkSoft,
@@ -169,7 +169,7 @@ import {
169
169
  withEvalBudgetOverride,
170
170
  withFnBudgetOverride,
171
171
  wrapPromise
172
- } from "./chunk-OYQMVCUV.js";
172
+ } from "./chunk-NEKTOZLJ.js";
173
173
 
174
174
  // src/algebra/inlay.ts
175
175
  function listFunctions(source) {
@@ -1,5 +1,5 @@
1
1
  import { File } from '@babel/types';
2
- import { A as Abs, T as Term, P as PrimName, a as Pred, L as LiteralValue, b as Phi, C as Confidence } from './run-CDBM1fJZ.js';
2
+ import { A as Abs, T as Term, P as PrimName, a as Pred, L as LiteralValue, b as Phi, C as Confidence } from './run-dg1bW60I.js';
3
3
 
4
4
  declare function resetParseSourceCache(): void;
5
5
  declare function getParseSourceCacheSize(): number;
@@ -670,6 +670,12 @@ type CheckIssue = Diagnostic & {
670
670
  actual?: string;
671
671
  /** 期望约束(Pred 或 Abs 展示) */
672
672
  expected?: string;
673
+ /**
674
+ * 截断归因(`nudo:recursion-truncated`,issue #158):
675
+ * `cycle`=同抽象实参调用环(抬预算无效);`depth`/`calls`=真预算超限;
676
+ * `budget`=混合(cycle+预算或 depth+calls 同报)。
677
+ */
678
+ reason?: "cycle" | "depth" | "calls" | "budget";
673
679
  /**
674
680
  * AI1:结构化下一步(additive)。agent 优先消费 `actions`,
675
681
  * 不必解析 `suggestion` 散文;`command` 可直接执行。
@@ -703,6 +709,8 @@ type CheckReport = {
703
709
  /**
704
710
  * A2 预算/截断可观测(additive)。`truncated=true` 时部分结果可能已
705
711
  * widen 成 unknown——不是契约失败,是引擎预算。
712
+ * evalCalls/evalCallTruncated = B 命名调用($callNamed)路径;
713
+ * cycleTruncated/reasons = 归因(#158):cycle 抬 @nudo:budget 无效。
706
714
  */
707
715
  budget?: {
708
716
  truncated: boolean;
@@ -712,6 +720,13 @@ type CheckReport = {
712
720
  maxCalls: number;
713
721
  forks: number;
714
722
  maxForks: number;
723
+ evalCalls: number;
724
+ maxEvalCalls: number;
725
+ evalCallTruncated: boolean;
726
+ cycleTruncated: boolean;
727
+ /** 循环迭代预算截断(#loop-iterations;maxIters/硬上限,非调用预算) */
728
+ loopTruncated: boolean;
729
+ reasons: string[];
715
730
  };
716
731
  };
717
732
  /**
@@ -744,6 +759,8 @@ type CheckJson = {
744
759
  actual?: string;
745
760
  expected?: string;
746
761
  suggestion?: string;
762
+ /** 截断归因(nudo:recursion-truncated;issue #158,additive) */
763
+ reason?: CheckIssue["reason"];
747
764
  /** AI1:结构化 next-action(只增字段) */
748
765
  actions?: CheckAction[];
749
766
  }>;
@@ -756,6 +773,7 @@ declare function actionsForIssue(i: {
756
773
  expected?: string;
757
774
  suggestion?: string;
758
775
  fn?: string;
776
+ reason?: string;
759
777
  }): CheckAction[];
760
778
  /** 多文件 `check --json` 信封(CI / monorepo)。单文件仍输出裸 CheckJson。 */
761
779
  type CheckJsonMulti = {
@@ -840,12 +840,11 @@ type TranspiledCallResult = {
840
840
  declare function isAbsVal(v: unknown): v is Abs;
841
841
  /** 调用 runTranspiled 导出(捕获 $throw)。opts.phi:入口 Φ 种子
842
842
  * (instantiate/symbolic 的约束入口——eval 侧路径条件收窄)。
843
- * opts.assumePremises(#152 用户引理):入口前提独立槽种子——与 phi 分槽
844
- * (不随调用链继承),消费方经 currentAssumePremises() 读。
845
- * 嵌套进入(导出桥 apply)继承外层预算;仅最外层宿主入口重置。 */
843
+ * 嵌套进入(导出桥 apply)继承外层预算;仅最外层宿主入口重置。
844
+ * #152 assume 前提不设选项位:播种统一走 withAssumePremises 动态域
845
+ * 包裹(check.ts 的 runSeeded extent),单一机制无第二路径。 */
846
846
  declare function callTranspiledExportFull(exports: Record<string, unknown>, name: string, args: Abs[], opts?: {
847
847
  phi?: Phi;
848
- assumePremises?: Phi;
849
848
  }): TranspiledCallResult;
850
849
  /** 调用 runTranspiled 导出(仅结果) */
851
850
  declare function callTranspiledExport(exports: Record<string, unknown>, name: string, args: Abs[]): Abs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/core",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },