nudojs 1.3.19 → 1.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +251 -52
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -19039,6 +19039,18 @@ function withExecPhi(p, body) {
19039
19039
  phi = prev;
19040
19040
  }
19041
19041
  }
19042
+ function currentAssumePremises() {
19043
+ return assumePremises;
19044
+ }
19045
+ function withAssumePremises(p, body) {
19046
+ const prev = assumePremises;
19047
+ assumePremises = p;
19048
+ try {
19049
+ return body();
19050
+ } finally {
19051
+ assumePremises = prev;
19052
+ }
19053
+ }
19042
19054
  function setHostGlobalFnCall(bridge) {
19043
19055
  hostGlobalFnCall = bridge;
19044
19056
  }
@@ -19075,6 +19087,12 @@ function $absVal(v2) {
19075
19087
  return asAbsVal(v2);
19076
19088
  }
19077
19089
  function callAtFunctionBoundary(body) {
19090
+ if (assumePremises !== pTrue) {
19091
+ return withAssumePremises(pTrue, () => boundaryBody(body));
19092
+ }
19093
+ return boundaryBody(body);
19094
+ }
19095
+ function boundaryBody(body) {
19078
19096
  let forwarded = [];
19079
19097
  try {
19080
19098
  return runWithLoopExits(() => {
@@ -19383,7 +19401,7 @@ function $isBreakTo(e, label) {
19383
19401
  function $rethrowIfNudoReturn(e) {
19384
19402
  if (isNudoReturn(e)) throw e;
19385
19403
  }
19386
- var phi, noBody2, hostGlobalFnCall, newTargetStack, loopExitsAls, throwExitsAls, tryMarksAls, softFrameActiveAls, NudoReturn, loopSignalFromAbstractArm, NudoLoopSignal;
19404
+ var phi, assumePremises, noBody2, hostGlobalFnCall, newTargetStack, loopExitsAls, throwExitsAls, tryMarksAls, softFrameActiveAls, NudoReturn, loopSignalFromAbstractArm, NudoLoopSignal;
19387
19405
  var init_state = __esm({
19388
19406
  "../core/src/algebra/exec/runtime/state.ts"() {
19389
19407
  "use strict";
@@ -19396,6 +19414,7 @@ var init_state = __esm({
19396
19414
  init_containers();
19397
19415
  init_objects();
19398
19416
  phi = pTrue;
19417
+ assumePremises = pTrue;
19399
19418
  noBody2 = { type: "BlockStatement", body: [], directives: [] };
19400
19419
  newTargetStack = [];
19401
19420
  loopExitsAls = new AsyncLocalStorage3();
@@ -23800,7 +23819,12 @@ function emitArrMutatorRebinds(expr, opts, pad) {
23800
23819
  if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.computed !== true && node.callee.property?.type === "Identifier" && ARR_MUTATOR_NAMES.has(node.callee.property.name)) {
23801
23820
  const methodName = node.callee.property.name;
23802
23821
  const argSrcs = (node.arguments ?? []).map(
23803
- (a) => a.type === "SpreadElement" ? "$unknown()" : isExpression(a) ? emitTranspileExpression(a, opts) : "$unknown()"
23822
+ (a) => (
23823
+ // spread 实参展开(issue #153):$elems → JS Abs[],数组字面量 JS
23824
+ // spread 平铺(与 expr.ts 普通调用同口径);此前折 $unknown() 单参
23825
+ // 让重绑行以 unknown 实参执行真实变更,污染容器元素域
23826
+ a.type === "SpreadElement" ? `...$elems(${emitTranspileExpression(a.argument, opts)})` : isExpression(a) ? emitTranspileExpression(a, opts) : "$unknown()"
23827
+ )
23804
23828
  ).join(", ");
23805
23829
  const objNode = node.callee.object;
23806
23830
  if (objNode.type === "Identifier") {
@@ -24947,7 +24971,12 @@ ${pad}export default ${name};`;
24947
24971
  const methodName = expr.callee.property.name;
24948
24972
  if (["push", "unshift", "splice", "pop", "shift", "reverse", "sort"].includes(methodName)) {
24949
24973
  const argSrcs = expr.arguments.map(
24950
- (a) => a.type === "SpreadElement" ? "$unknown()" : emitTranspileExpression(a, opts)
24974
+ (a) => (
24975
+ // spread 实参展开(issue #153):$elems → JS Abs[],数组字面量 JS spread
24976
+ // 平铺(与普通调用 $callNamed/$invoke 同口径,DEC-006 K5);
24977
+ // 此前折 $unknown() 单参会污染容器元素域
24978
+ a.type === "SpreadElement" ? `...$elems(${emitTranspileExpression(a.argument, opts)})` : emitTranspileExpression(a, opts)
24979
+ )
24951
24980
  ).join(", ");
24952
24981
  const objNode = expr.callee.object;
24953
24982
  if (objNode.type === "Identifier") {
@@ -27251,7 +27280,7 @@ function $mul(a, b) {
27251
27280
  return mul(a, b, currentExecPhi());
27252
27281
  }
27253
27282
  function $div(a, b) {
27254
- return div(a, b, currentExecPhi());
27283
+ return div(a, b, currentExecPhi(), currentAssumePremises());
27255
27284
  }
27256
27285
  function $mod(a, b) {
27257
27286
  return mod(a, b, currentExecPhi());
@@ -29873,14 +29902,32 @@ function $concat(a, b) {
29873
29902
  if (isMapAbs(x)) return mapEntriesAbs(x);
29874
29903
  const mi = matchIterElements(x);
29875
29904
  if (mi) return mi;
29905
+ if (x.shape.k === "brand") return expand(x.shape.shape);
29906
+ if (x.shape.k === "tuple") return [...x.shape.elements];
29876
29907
  return null;
29877
29908
  };
29909
+ const sideEl = (x, expanded) => {
29910
+ if (expanded) {
29911
+ return expanded.length ? expanded.reduce((u, y) => joinAbs(u, y)) : abs({ k: "never" }, void 0, void 0, "exact");
29912
+ }
29913
+ if (x?.shape?.k === "any") return anyMemberResult();
29914
+ if (x?.shape?.k === "tuple") {
29915
+ const els = x.shape.elements;
29916
+ return els.length ? els.reduce((u, y) => joinAbs(u, y)) : abs({ k: "never" }, void 0, void 0, "exact");
29917
+ }
29918
+ if (x?.shape?.k === "prim" && x.shape.type === "string") {
29919
+ return abs({ k: "prim", type: "string" }, void 0, void 0, "path");
29920
+ }
29921
+ if (x?.shape?.k === "arr") return x.shape.element;
29922
+ if (x?.shape?.k === "brand") return sideEl(x.shape.shape, null);
29923
+ return unknown;
29924
+ };
29878
29925
  if (as.k === "tuple") {
29879
29926
  const be = expand(b);
29880
29927
  if (be) {
29881
29928
  return tupleOrWiden([...as.elements, ...be], confJoin(a.conf, b.conf));
29882
29929
  }
29883
- const bEl = b?.shape?.k === "any" ? anyMemberResult() : unknown;
29930
+ const bEl = sideEl(b, be);
29884
29931
  return abs(
29885
29932
  { k: "arr", element: [...as.elements, bEl].reduce((x, y) => joinAbs(x, y)) },
29886
29933
  void 0,
@@ -29892,20 +29939,6 @@ function $concat(a, b) {
29892
29939
  if (ae && bs.k === "tuple") {
29893
29940
  return tupleOrWiden([...ae, ...bs.elements], confJoin(a.conf, b.conf));
29894
29941
  }
29895
- const sideEl = (x, expanded) => {
29896
- if (expanded) {
29897
- return expanded.length ? expanded.reduce((u, y) => joinAbs(u, y)) : abs({ k: "never" }, void 0, void 0, "exact");
29898
- }
29899
- if (x?.shape?.k === "any") return anyMemberResult();
29900
- if (x?.shape?.k === "tuple") {
29901
- const els = x.shape.elements;
29902
- return els.length ? els.reduce((u, y) => joinAbs(u, y)) : abs({ k: "never" }, void 0, void 0, "exact");
29903
- }
29904
- if (x?.shape?.k === "prim" && x.shape.type === "string") {
29905
- return abs({ k: "prim", type: "string" }, void 0, void 0, "path");
29906
- }
29907
- return unknown;
29908
- };
29909
29942
  return abs(
29910
29943
  { k: "arr", element: joinAbs(sideEl(a, ae), sideEl(b, expand(b))) },
29911
29944
  void 0,
@@ -29939,6 +29972,9 @@ function iterabilityKind(a) {
29939
29972
  if (s.slots["@@iterator"]) return "total";
29940
29973
  return s.open ? "may" : "definite";
29941
29974
  }
29975
+ if (s.k === "brand") {
29976
+ return iterabilityKind(s.shape) === "total" ? "total" : "may";
29977
+ }
29942
29978
  return "may";
29943
29979
  }
29944
29980
  function asyncIterabilityKind(a) {
@@ -29956,6 +29992,12 @@ function asyncIterabilityKind(a) {
29956
29992
  if (s.k === "obj" && !s.slots["@@iterator"] && s.slots["@@asyncIterator"]) {
29957
29993
  return "total";
29958
29994
  }
29995
+ if (s.k === "brand") {
29996
+ const ish = s.shape.shape;
29997
+ if (ish.k === "obj" && !ish.slots["@@iterator"] && ish.slots["@@asyncIterator"]) {
29998
+ return "total";
29999
+ }
30000
+ }
29959
30001
  return iterabilityKind(a);
29960
30002
  }
29961
30003
  function guardIterable(a, ctx2) {
@@ -30019,6 +30061,7 @@ function elemsOf(a) {
30019
30061
  if (a.shape.k === "prim" && a.shape.type === "string") {
30020
30062
  return [abs({ k: "prim", type: "string" }, void 0, void 0, "path")];
30021
30063
  }
30064
+ if (a.shape.k === "brand") return elemsOf(a.shape.shape);
30022
30065
  return [unknown];
30023
30066
  }
30024
30067
  function $forInKeys(o) {
@@ -31808,6 +31851,7 @@ __export(runtime_exports, {
31808
31851
  callAtFunctionBoundary: () => callAtFunctionBoundary,
31809
31852
  clearStaleTermPred: () => clearStaleTermPred,
31810
31853
  confPartial: () => confPartial,
31854
+ currentAssumePremises: () => currentAssumePremises,
31811
31855
  currentExecPhi: () => currentExecPhi,
31812
31856
  fillTuple: () => fillTuple,
31813
31857
  isArrMutator: () => isArrMutator,
@@ -31825,6 +31869,7 @@ __export(runtime_exports, {
31825
31869
  runWithLoopExits: () => runWithLoopExits,
31826
31870
  takeLoopExits: () => takeLoopExits,
31827
31871
  takeThrowExits: () => takeThrowExits,
31872
+ withAssumePremises: () => withAssumePremises,
31828
31873
  withExecPhi: () => withExecPhi,
31829
31874
  withNewTargetReset: () => withNewTargetReset
31830
31875
  });
@@ -31918,6 +31963,15 @@ function isNudoConstraint(x) {
31918
31963
  function isIntFlag(c) {
31919
31964
  return c.int === true || intFlaggedBuilders.has(c);
31920
31965
  }
31966
+ function finiteBoundArg(method, n) {
31967
+ if (typeof n !== "number" || !Number.isFinite(n)) {
31968
+ const shown = typeof n === "string" ? JSON.stringify(n) : String(n);
31969
+ throw new Error(
31970
+ `nudo ${method}(): finite numeric bound expected, got ${shown} \u2014 variable references are not supported; express cross-variable lemmas with @nudo:assume (issue #152)`
31971
+ );
31972
+ }
31973
+ return n;
31974
+ }
31921
31975
  function makeBuilder(prim2, preds, extra) {
31922
31976
  const fields = extra?.fields;
31923
31977
  const element = extra?.element;
@@ -31942,17 +31996,16 @@ function makeBuilder(prim2, preds, extra) {
31942
31996
  /* @__PURE__ */ Object.create(null),
31943
31997
  base,
31944
31998
  {
31945
- gt: (n) => add2(gt(selfTerm(), lit(n))),
31946
- ge: (n) => add2(ge(selfTerm(), lit(n))),
31947
- lt: (n) => add2(lt(selfTerm(), lit(n))),
31948
- le: (n) => add2(le(selfTerm(), lit(n))),
31999
+ gt: (n) => add2(gt(selfTerm(), lit(finiteBoundArg("gt", n)))),
32000
+ ge: (n) => add2(ge(selfTerm(), lit(finiteBoundArg("ge", n)))),
32001
+ lt: (n) => add2(lt(selfTerm(), lit(finiteBoundArg("lt", n)))),
32002
+ le: (n) => add2(le(selfTerm(), lit(finiteBoundArg("le", n)))),
31949
32003
  int: () => makeBuilder(prim2 ?? "number", preds, { ...extra, int: true }),
31950
- min: (n) => add2(ge(lenTerm(selfTerm()), lit(n))),
31951
- max: (n) => add2(le(lenTerm(selfTerm()), lit(n))),
32004
+ min: (n) => add2(ge(lenTerm(selfTerm()), lit(finiteBoundArg("min", n)))),
32005
+ max: (n) => add2(le(lenTerm(selfTerm()), lit(finiteBoundArg("max", n)))),
31952
32006
  length: (n) => add2(and(ge(lenTerm(selfTerm()), lit(n)), le(lenTerm(selfTerm()), lit(n)))),
31953
32007
  shift: (n) => {
31954
- if (!Number.isFinite(n))
31955
- throw new Error("nudo shift(): offset must be a finite number");
32008
+ finiteBoundArg("shift", n);
31956
32009
  if (fields || element || members || fnSlot || lazySlot)
31957
32010
  throw new Error("nudo shift(): only numeric scalar constraint chains are allowed (shape/array/union/fn are not supported)");
31958
32011
  if (prim2 !== void 0 && prim2 !== "number")
@@ -36948,7 +37001,7 @@ ${js}`;
36948
37001
  (k) => !ENV_SHADOW_SKIP.has(k)
36949
37002
  );
36950
37003
  if (envInjectNames.length > 0) {
36951
- const envBinds = envInjectNames.map((k) => `const ${k} = __nudoEnv[${JSON.stringify(k)}];`).join("\n");
37004
+ const envBinds = envInjectNames.map((k) => `let ${k} = __nudoEnv[${JSON.stringify(k)}];`).join("\n");
36952
37005
  js = `${envBinds}
36953
37006
  ${js}`;
36954
37007
  }
@@ -37064,7 +37117,9 @@ function callTranspiledExportFullInner(exports, name, args, opts) {
37064
37117
  return runWithLoopExits(() => {
37065
37118
  try {
37066
37119
  const invoke = () => withNewTargetReset(() => fn2(...callArgs));
37067
- const r = opts?.phi ? withExecPhi(opts.phi, invoke) : invoke();
37120
+ const ap = opts?.assumePremises;
37121
+ const seeded = ap !== void 0 ? () => withAssumePremises(ap, invoke) : invoke;
37122
+ const r = opts?.phi ? withExecPhi(opts.phi, seeded) : seeded();
37068
37123
  drainPromiseMicros();
37069
37124
  if (!isAbsVal(r)) return joinControlExits(asAbsVal(r));
37070
37125
  return joinControlExits(r);
@@ -40802,7 +40857,50 @@ function mul(a, b, phi2 = pTrue) {
40802
40857
  noteAbstractArithMayThrow(a, b);
40803
40858
  return abs({ k: "unknown" }, void 0, void 0, "partial");
40804
40859
  }
40805
- function div(a, b, phi2 = pTrue) {
40860
+ function assumePremiseList(assume) {
40861
+ if (!assume || assume.op === "true") return [];
40862
+ const out = [];
40863
+ const walk = (p) => {
40864
+ if (p.op === "and") {
40865
+ p.args.forEach(walk);
40866
+ return;
40867
+ }
40868
+ if (p.op !== "true") out.push(p);
40869
+ };
40870
+ walk(assume);
40871
+ return out;
40872
+ }
40873
+ function divAssumeBounds(a, b, resultTerm, phi2, assume) {
40874
+ const premises = assumePremiseList(assume);
40875
+ if (premises.length === 0) return void 0;
40876
+ if (!a.term || !b.term) return void 0;
40877
+ const bb = numericBounds(b, phi2);
40878
+ const posDen = bb?.lo !== void 0 && (bb.lo.value > 0 || bb.lo.value === 0 && bb.lo.strict);
40879
+ const negDen = bb?.hi !== void 0 && (bb.hi.value < 0 || bb.hi.value === 0 && bb.hi.strict);
40880
+ if (!posDen && !negDen) return void 0;
40881
+ let aLeB = false;
40882
+ let bLeA = false;
40883
+ for (const p of premises) {
40884
+ if (p.op !== "le") continue;
40885
+ if (termEquals(p.a, a.term) && termEquals(p.b, b.term)) aLeB = true;
40886
+ else if (termEquals(p.a, b.term) && termEquals(p.b, a.term)) bLeA = true;
40887
+ }
40888
+ if (!aLeB && !bLeA) return void 0;
40889
+ const facts = [];
40890
+ if (aLeB) facts.push(posDen ? le(resultTerm, lit(1)) : ge(resultTerm, lit(1)));
40891
+ if (bLeA) facts.push(posDen ? ge(resultTerm, lit(1)) : le(resultTerm, lit(1)));
40892
+ const ab = numericBounds(a, phi2);
40893
+ if (ab?.lo !== void 0 && (ab.lo.value > 0 || ab.lo.value === 0)) {
40894
+ if (posDen) facts.push(ab.lo.strict ? gt(resultTerm, lit(0)) : ge(resultTerm, lit(0)));
40895
+ else facts.push(ab.lo.strict ? lt(resultTerm, lit(0)) : le(resultTerm, lit(0)));
40896
+ }
40897
+ if (ab?.hi !== void 0 && (ab.hi.value < 0 || ab.hi.value === 0)) {
40898
+ if (posDen) facts.push(ab.hi.strict ? lt(resultTerm, lit(0)) : le(resultTerm, lit(0)));
40899
+ else facts.push(ab.hi.strict ? gt(resultTerm, lit(0)) : ge(resultTerm, lit(0)));
40900
+ }
40901
+ return facts.length ? and(...facts) : void 0;
40902
+ }
40903
+ function div(a, b, phi2 = pTrue, assume) {
40806
40904
  {
40807
40905
  const fa = builtinBrandToPrimitive(a, "number");
40808
40906
  const fb = builtinBrandToPrimitive(b, "number");
@@ -40857,7 +40955,13 @@ function div(a, b, phi2 = pTrue) {
40857
40955
  term.op === "lit" ? "exact" : confJoin(confJoin(a.conf, b.conf), "path")
40858
40956
  );
40859
40957
  }
40860
- return abs(num().shape, term, void 0, confJoin(confJoin(a.conf, b.conf), "path"));
40958
+ const assumePred = divAssumeBounds(a, b, term, phi2, assume);
40959
+ return abs(
40960
+ num().shape,
40961
+ term,
40962
+ assumePred,
40963
+ confJoin(confJoin(a.conf, b.conf), "path")
40964
+ );
40861
40965
  }
40862
40966
  if (isNumericLike(a) && isNumericLike(b)) {
40863
40967
  return abs(num().shape, void 0, void 0, "partial");
@@ -40868,7 +40972,7 @@ function div(a, b, phi2 = pTrue) {
40868
40972
  if (isAnyLike(a) || isAnyLike(b)) {
40869
40973
  return toNumberResult(a, b, "/");
40870
40974
  }
40871
- const distributed = distributeSumBinOp("/", a, b, phi2, div);
40975
+ const distributed = distributeSumBinOp("/", a, b, phi2, (x, y, p) => div(x, y, p, assume));
40872
40976
  if (distributed) return distributed;
40873
40977
  noteAbstractArithMayThrow(a, b);
40874
40978
  return abs({ k: "unknown" }, void 0, void 0, "partial");
@@ -41678,6 +41782,14 @@ function scanContractSegments(lines) {
41678
41782
  }
41679
41783
  return reqs;
41680
41784
  }
41785
+ function scanAssumeSegments(lines) {
41786
+ const out = [];
41787
+ for (const line of lines) {
41788
+ const m = line.match(/^@nudo:assume\s+(.+)$/);
41789
+ if (m) out.push(m[1].trim().replace(/\*\/$/, "").trim());
41790
+ }
41791
+ return out;
41792
+ }
41681
41793
  function scanThrowsDecl(lines) {
41682
41794
  const kinds = /* @__PURE__ */ new Set();
41683
41795
  let any2 = false;
@@ -42552,6 +42664,24 @@ function extractRefineLines(source, fnName) {
42552
42664
  const lines = fnDirectiveCommentLines(source, fnName);
42553
42665
  return scanContractSegments(lines);
42554
42666
  }
42667
+ function extractAssumeSegs(source, fnName) {
42668
+ if (!stripStringsKeepComments(source).includes("@nudo:assume")) return [];
42669
+ const out = [];
42670
+ for (const seg of scanAssumeSegments(fnDirectiveCommentLines(source, fnName))) {
42671
+ const geSeg = seg.match(/^([A-Za-z_$][\w$]*)\s*>=\s*([A-Za-z_$][\w$]*)$/);
42672
+ if (geSeg) {
42673
+ out.push({ kind: "ok", lhs: geSeg[2], rhs: geSeg[1] });
42674
+ continue;
42675
+ }
42676
+ const leSeg = seg.match(/^([A-Za-z_$][\w$]*)\s*<=\s*([A-Za-z_$][\w$]*)$/);
42677
+ if (leSeg) {
42678
+ out.push({ kind: "ok", lhs: leSeg[1], rhs: leSeg[2] });
42679
+ continue;
42680
+ }
42681
+ out.push({ kind: "bad", raw: seg });
42682
+ }
42683
+ return out;
42684
+ }
42555
42685
  function extractRefinesFromSource(source, fnName, opts = {}) {
42556
42686
  if (!stripStringsKeepComments(source).includes("@nudo:contract")) return [];
42557
42687
  const constraints = collectConstraints(source, opts);
@@ -45355,7 +45485,7 @@ function flattenSumArms(ret) {
45355
45485
  return out;
45356
45486
  }
45357
45487
  function isWidenedSumArm(m, parent) {
45358
- return parent.conf === "partial" && m.shape.k === "prim" && !m.term && (!m.pred || m.pred.op === "true");
45488
+ return (parent.conf === "partial" || m.conf === "widened") && m.shape.k === "prim" && !m.term && (!m.pred || m.pred.op === "true");
45359
45489
  }
45360
45490
  function provesBarePrimDomain(arm, constraint) {
45361
45491
  if (constraint.members) {
@@ -45382,7 +45512,7 @@ function assertImplies(ret, constraint, opts) {
45382
45512
  const phi2 = opts?.phi ?? pTrue;
45383
45513
  if (ret.shape.k === "never") return proved();
45384
45514
  if (isAnyConstraint(constraint)) return proved();
45385
- if (isValueSetUnknown(ret)) {
45515
+ if (isValueSetUnknown(ret) && ret.conf !== "widened") {
45386
45516
  return unprovable(
45387
45517
  ret.shape.k === "any" ? "any does not establish the return contract" : "inference failure (unknown/opaque) \u2014 cannot prove the return contract"
45388
45518
  );
@@ -45438,6 +45568,9 @@ function assertImpliesSingle(arm, constraint, opts, phi2) {
45438
45568
  return constraintAdmitsNullish(constraint) ? proved() : disproved(`nullish return \u22AD ${formatConstraint(constraint)}`);
45439
45569
  }
45440
45570
  if (isValueSetUnknown(arm)) {
45571
+ if (arm.conf === "widened" && arm.shape.k === "prim" && !arm.term && (!arm.pred || arm.pred.op === "true") && provesBarePrimDomain(arm, constraint)) {
45572
+ return proved();
45573
+ }
45441
45574
  return unprovable(
45442
45575
  arm.shape.k === "any" ? "any does not establish the return contract" : "inference failure (unknown/opaque) \u2014 cannot prove the return contract"
45443
45576
  );
@@ -47495,6 +47628,7 @@ function numericBound(p, self) {
47495
47628
  if (p.op !== "gt" && p.op !== "ge" && p.op !== "lt" && p.op !== "le") return void 0;
47496
47629
  if (!self) return void 0;
47497
47630
  if (termEquals(p.a, self) && p.b.op === "lit" && typeof p.b.value === "number") {
47631
+ if (!Number.isFinite(p.b.value)) return void 0;
47498
47632
  return { op: p.op, n: p.b.value };
47499
47633
  }
47500
47634
  return void 0;
@@ -48564,6 +48698,49 @@ function sidecarDiagIssues(diags) {
48564
48698
  }
48565
48699
  return out;
48566
48700
  }
48701
+ function assumePremisesPhiOf(name, source, file, issues) {
48702
+ const segs = extractAssumeSegs(source, name);
48703
+ if (segs.length === 0) return void 0;
48704
+ const meta = extractFn(source, name, file);
48705
+ const bindable = /* @__PURE__ */ new Set([
48706
+ ...contractParamNameSet(meta?.formals ?? []),
48707
+ ...meta?.params ?? []
48708
+ ]);
48709
+ const surface = [...bindable].join(", ");
48710
+ const preds = [];
48711
+ const display = [];
48712
+ for (const seg of segs) {
48713
+ if (seg.kind === "bad") {
48714
+ issues.push({
48715
+ severity: "error",
48716
+ code: "nudo:assume-invalid",
48717
+ message: `${name}: malformed @nudo:assume '${seg.raw}' (expected <param> <= <param> or <param> >= <param>; constant bounds belong to sidecar contracts)`,
48718
+ actual: seg.raw,
48719
+ expected: "<param> <= <param>",
48720
+ suggestion: "cross-variable lemmas: @nudo:assume d <= max \u2014 constant bounds: sidecar number().le(10)",
48721
+ fn: name
48722
+ });
48723
+ continue;
48724
+ }
48725
+ const bad = [seg.lhs, seg.rhs].filter((n) => !bindable.has(n));
48726
+ if (bad.length > 0) {
48727
+ issues.push({
48728
+ severity: "error",
48729
+ code: "nudo:assume-invalid",
48730
+ message: `${name}: @nudo:assume parameter name(s) not in the formal parameter list (${bad.join(", ")})`,
48731
+ actual: bad.join(", "),
48732
+ expected: surface || "(no params)",
48733
+ suggestion: `reference one of: ${surface || "(function has no params)"}`,
48734
+ fn: name
48735
+ });
48736
+ continue;
48737
+ }
48738
+ preds.push(le(v(seg.lhs), v(seg.rhs)));
48739
+ display.push(`${seg.lhs} <= ${seg.rhs}`);
48740
+ }
48741
+ if (preds.length === 0) return void 0;
48742
+ return { phi: and(...preds), display };
48743
+ }
48567
48744
  function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures, names2, truncated, identityOpts = opts, depsFp, sidecarFp) {
48568
48745
  const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
48569
48746
  const entryNames = localNamedExports(source);
@@ -48591,6 +48768,8 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
48591
48768
  return eff;
48592
48769
  };
48593
48770
  for (const name of names2) {
48771
+ const assumePhi = assumePremisesPhiOf(name, source, file, issues);
48772
+ const runSeeded = (body) => assumePhi === void 0 ? body() : withAssumePremises(assumePhi.phi, body);
48594
48773
  if (opts.skips?.has(name)) {
48595
48774
  const declared = opts.skips.get(name) ?? null;
48596
48775
  const meta = extractFn(source, name, file);
@@ -48635,19 +48814,21 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
48635
48814
  }
48636
48815
  continue;
48637
48816
  }
48638
- const g = generalizeFromAst(name, source, {
48639
- file,
48640
- refine: {
48641
- loadModule: refineLoad,
48642
- fromFile: refineFrom,
48643
- ...autoBind !== void 0 ? { autoBind } : {},
48644
- ...projectDir !== void 0 ? { projectDir } : {}
48645
- },
48646
- depsFp,
48647
- sidecarFp,
48648
- modules: opts.modules,
48649
- ...opts.inject ? { inject: opts.inject } : {}
48650
- });
48817
+ const g = runSeeded(
48818
+ () => generalizeFromAst(name, source, {
48819
+ file,
48820
+ refine: {
48821
+ loadModule: refineLoad,
48822
+ fromFile: refineFrom,
48823
+ ...autoBind !== void 0 ? { autoBind } : {},
48824
+ ...projectDir !== void 0 ? { projectDir } : {}
48825
+ },
48826
+ depsFp,
48827
+ sidecarFp,
48828
+ modules: opts.modules,
48829
+ ...opts.inject ? { inject: opts.inject } : {}
48830
+ })
48831
+ );
48651
48832
  if (!g) {
48652
48833
  const isEntryCandidate = entryNames.has(name) || name === "default" || entryNames.has("default") && isDefaultExportName(source, name);
48653
48834
  if (isEntryCandidate && entryThrowsMode !== "off") {
@@ -48707,7 +48888,7 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
48707
48888
  const isEntry = entryNames.has(name) || name === "default" || entryNames.has("default") && isDefaultExportName(source, name) || name.includes(".") && entryNames.has(name);
48708
48889
  let throwsDisplay;
48709
48890
  if (isEntry && entryThrowsMode !== "off") {
48710
- const effects = collectEntryMayThrows(source, name, g, file, phi2, opts);
48891
+ const effects = runSeeded(() => collectEntryMayThrows(source, name, g, file, phi2, opts));
48711
48892
  throwsDisplay = formatThrowsAbs(mayThrowEffectsToAbs(effects));
48712
48893
  const declared = extractDeclaredThrows(source, name) ?? effectiveInterfaceCached(name)?.throws?.kinds;
48713
48894
  const remaining = filterGateThrows(effects, declared, ignoreThrows);
@@ -48728,7 +48909,7 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
48728
48909
  });
48729
48910
  }
48730
48911
  } else if (isEntry) {
48731
- const effects = collectEntryMayThrows(source, name, g, file, phi2, opts);
48912
+ const effects = runSeeded(() => collectEntryMayThrows(source, name, g, file, phi2, opts));
48732
48913
  throwsDisplay = formatThrowsAbs(mayThrowEffectsToAbs(effects));
48733
48914
  }
48734
48915
  const fmt = formatSigCached(g.symbolic, name);
@@ -48822,7 +49003,8 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
48822
49003
  name,
48823
49004
  display,
48824
49005
  eff.returns.constraint,
48825
- g.symbolic
49006
+ g.symbolic,
49007
+ assumePhi?.display
48826
49008
  );
48827
49009
  if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
48828
49010
  issues.push(...retIssues);
@@ -49018,9 +49200,24 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
49018
49200
  }
49019
49201
  };
49020
49202
  }
49021
- function checkReturnConstraint(fnName, cName, constraint, ret) {
49203
+ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay) {
49022
49204
  const proof = assertImplies(ret, constraint);
49023
- if (proof.status === "proved") return [];
49205
+ if (proof.status === "proved") {
49206
+ if (assumeDisplay && assumeDisplay.length > 0) {
49207
+ return [
49208
+ {
49209
+ severity: "info",
49210
+ code: "nudo:assume-relied",
49211
+ message: `${fnName}: return contract proved via @nudo:assume ${assumeDisplay.join("; ")}`,
49212
+ actual: `proof relies on user lemma${assumeDisplay.length > 1 ? "s" : ""}`,
49213
+ expected: "engine verifies the obligation, not the lemma itself",
49214
+ suggestion: "@nudo:assume is a user-supplied axiom \u2014 falsify it with concrete @nudo:case runs (nudo test)",
49215
+ fn: fnName
49216
+ }
49217
+ ];
49218
+ }
49219
+ return [];
49220
+ }
49024
49221
  const push = (severity, actual, expected, suggestion) => [
49025
49222
  {
49026
49223
  severity,
@@ -49068,6 +49265,7 @@ var init_check = __esm({
49068
49265
  init_abs();
49069
49266
  init_abs();
49070
49267
  init_pred();
49268
+ init_term();
49071
49269
  init_format();
49072
49270
  init_scan();
49073
49271
  init_check_interface_drift();
@@ -49076,6 +49274,7 @@ var init_check = __esm({
49076
49274
  init_check_assign();
49077
49275
  init_check_case_scan();
49078
49276
  init_run();
49277
+ init_runtime();
49079
49278
  init_calls();
49080
49279
  init_may_throw2();
49081
49280
  init_check_memo();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nudojs",
3
- "version": "1.3.19",
3
+ "version": "1.3.21",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -46,11 +46,11 @@
46
46
  "dependencies": {
47
47
  "@babel/generator": "^8.0.6",
48
48
  "@babel/types": "^8.0.6",
49
- "@nudojs/core": "1.10.1",
50
- "@nudojs/env": "0.4.33",
51
- "@nudojs/harvester": "0.3.19",
52
- "@nudojs/parser": "1.4.15",
53
- "@nudojs/service": "1.6.18",
49
+ "@nudojs/core": "1.11.0",
50
+ "@nudojs/env": "0.4.35",
51
+ "@nudojs/harvester": "0.3.21",
52
+ "@nudojs/parser": "1.4.17",
53
+ "@nudojs/service": "1.6.20",
54
54
  "commander": "^15.0.0"
55
55
  },
56
56
  "devDependencies": {