@idlekit/cli 0.5.1 → 0.7.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/main.js CHANGED
@@ -6605,6 +6605,69 @@ var require_src = __commonJS(function(exports, module) {
6605
6605
  module.exports = { cursor, scroll, erase, beep };
6606
6606
  });
6607
6607
 
6608
+ // src/runtime/invocation.ts
6609
+ import { AsyncLocalStorage } from "async_hooks";
6610
+ import { resolve } from "path";
6611
+ function runWithInvocation(invocation, fn) {
6612
+ return storage.run({ ...invocation, reads: new Set }, fn);
6613
+ }
6614
+ function recordInputRead(path) {
6615
+ storage.getStore()?.reads.add(resolve(path));
6616
+ }
6617
+ function flagPaths(key, value) {
6618
+ if (typeof value !== "string" || !value.trim())
6619
+ return [];
6620
+ if (LIST_FLAGS.has(key))
6621
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
6622
+ return INPUT_FLAGS.has(key) ? [value] : [];
6623
+ }
6624
+ function recordNestedInputs(base, positional, flags) {
6625
+ for (const value of positional)
6626
+ if (value.length > 0)
6627
+ recordInputRead(resolve(base, value));
6628
+ for (const [key, value] of Object.entries(flags))
6629
+ for (const path of flagPaths(key, value))
6630
+ recordInputRead(resolve(base, path));
6631
+ }
6632
+ function invocationInputCandidates(options = {}) {
6633
+ const invocation = storage.getStore();
6634
+ if (!invocation)
6635
+ return [];
6636
+ const allow = new Set(options.allow ?? []);
6637
+ const allowed = new Set;
6638
+ const candidates = [];
6639
+ for (const [key, value] of Object.entries(invocation.flags)) {
6640
+ const paths = flagPaths(key, value);
6641
+ if (allow.has(key))
6642
+ for (const path of paths)
6643
+ allowed.add(resolve(path));
6644
+ else
6645
+ candidates.push(...paths);
6646
+ }
6647
+ for (const path of invocation.reads)
6648
+ if (!allowed.has(path))
6649
+ candidates.push(path);
6650
+ return candidates;
6651
+ }
6652
+ var INPUT_FLAGS, LIST_FLAGS, storage;
6653
+ var init_invocation = __esm(() => {
6654
+ INPUT_FLAGS = new Set([
6655
+ "scenario",
6656
+ "baseline",
6657
+ "current",
6658
+ "baseline-artifact",
6659
+ "tune",
6660
+ "checks",
6661
+ "rubric",
6662
+ "cache",
6663
+ "resume",
6664
+ "rc",
6665
+ "plugin-trust-file"
6666
+ ]);
6667
+ LIST_FLAGS = new Set(["plugin"]);
6668
+ storage = new AsyncLocalStorage;
6669
+ });
6670
+
6608
6671
  // ../core/dist/engine/types.js
6609
6672
  var init_types = () => {};
6610
6673
 
@@ -8397,7 +8460,7 @@ var init_validate = __esm(() => {
8397
8460
  OFFLINE_ACTORS = new Set(["player", "automation"]);
8398
8461
  });
8399
8462
 
8400
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_validateReport.mjs
8463
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_validateReport.mjs
8401
8464
  var _validateReport = (array) => {
8402
8465
  const isAncestor = (ancestor, descendant) => descendant === ancestor || descendant.startsWith(`${ancestor}.`) || descendant.startsWith(`${ancestor}[`);
8403
8466
  const reportable = (path) => {
@@ -8422,7 +8485,7 @@ var _validateReport = (array) => {
8422
8485
  };
8423
8486
  var init__validateReport = () => {};
8424
8487
 
8425
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_createStandardSchema.mjs
8488
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_createStandardSchema.mjs
8426
8489
  var _createStandardSchema = (fn) => Object.assign(fn, { "~standard": {
8427
8490
  version: 1,
8428
8491
  vendor: "typia",
@@ -10693,6 +10756,14 @@ var init_goalRead = __esm(() => {
10693
10756
  ledgers = new WeakMap;
10694
10757
  });
10695
10758
 
10759
+ // ../core/dist/sim/milestoneKeys.js
10760
+ function actionMilestoneKeys(actionId) {
10761
+ return [`action.${actionId}.firstApplied`, "progress.first-upgrade"];
10762
+ }
10763
+ function mayRecordAtActionStart(key) {
10764
+ return key === "progress.first-upgrade" || key.startsWith("action.") && key.endsWith(".firstApplied");
10765
+ }
10766
+
10696
10767
  // ../core/dist/sim/observation.js
10697
10768
  function emitCommittedResets(observer, resets) {
10698
10769
  if (!observer?.onReset || resets.length === 0)
@@ -10873,7 +10944,11 @@ function createObservationRecorder(args) {
10873
10944
  milestones.push(sample);
10874
10945
  }
10875
10946
  if (args.observer?.onMilestone) {
10876
- notify(() => args.observer?.onMilestone?.({ t: sample.firstSeenT, key: sample.key }));
10947
+ notify(() => args.observer?.onMilestone?.({
10948
+ t: sample.firstSeenT,
10949
+ key: sample.key,
10950
+ phase: sample.source === "action" ? "action-start" : "income-end"
10951
+ }));
10877
10952
  }
10878
10953
  };
10879
10954
  const open = (goal) => !reachedGoals.has(goal.id) && !droppedGoals.has(goal.id);
@@ -10931,8 +11006,8 @@ function createObservationRecorder(args) {
10931
11006
  if (args.observer?.onAction) {
10932
11007
  notify(() => args.observer?.onAction?.({ t: step.t0, actionId: event.actionId, outcome: "applied" }));
10933
11008
  }
10934
- rememberMilestone({ key: `action.${event.actionId}.firstApplied`, firstSeenT: step.t0, source: "action" });
10935
- rememberMilestone({ key: "progress.first-upgrade", firstSeenT: step.t0, source: "action" });
11009
+ for (const key of actionMilestoneKeys(event.actionId))
11010
+ rememberMilestone({ key, firstSeenT: step.t0, source: "action" });
10936
11011
  } else if (event.type === "action.skipped") {
10937
11012
  if (event.reason === "cannotApply")
10938
11013
  actions.skippedCannotApply += 1;
@@ -11605,6 +11680,63 @@ function logicalDrawId(drawIndex) {
11605
11680
  return String(drawIndex);
11606
11681
  }
11607
11682
 
11683
+ // ../core/dist/sim/sessionOffline.js
11684
+ function validateCreditPolicy(policy) {
11685
+ if (!policy || typeof policy !== "object" || Array.isArray(policy) || "actions" in policy) {
11686
+ throw new RangeError("prepareOffline policy must be a credit policy; set actions on scenario.run.offline");
11687
+ }
11688
+ if (policy.maxSec !== undefined && (typeof policy.maxSec !== "number" || !Number.isFinite(policy.maxSec) || policy.maxSec < 0)) {
11689
+ throw new RangeError("prepareOffline policy.maxSec must be finite and nonnegative");
11690
+ }
11691
+ if (policy.overflowPolicy !== undefined && policy.overflowPolicy !== "clamp" && policy.overflowPolicy !== "reject") {
11692
+ throw new RangeError("prepareOffline policy.overflowPolicy must be clamp or reject");
11693
+ }
11694
+ const decay = policy.decay;
11695
+ if (decay !== undefined) {
11696
+ if (!decay || typeof decay !== "object" || Array.isArray(decay) || decay.kind !== "none" && decay.kind !== "linear") {
11697
+ throw new RangeError("prepareOffline policy.decay must select none or linear");
11698
+ }
11699
+ if (decay.floorRatio !== undefined && (typeof decay.floorRatio !== "number" || !Number.isFinite(decay.floorRatio) || decay.floorRatio < 0 || decay.floorRatio > 1)) {
11700
+ throw new RangeError("prepareOffline policy.decay.floorRatio must be finite and in 0..1");
11701
+ }
11702
+ }
11703
+ }
11704
+ function prepareOfflineSegment(args) {
11705
+ const { scenario, prepare } = args;
11706
+ if (!prepare)
11707
+ return { model: scenario.model, policy: scenario.run.offline, stepSec: scenario.run.stepSec };
11708
+ const prepared = prepare({
11709
+ ctx: scenario.ctx,
11710
+ model: scenario.model,
11711
+ state: deepClonePreservingPrototype(args.state),
11712
+ seconds: args.seconds,
11713
+ wallStartT: args.wallStartT,
11714
+ wallEndT: args.wallEndT,
11715
+ ...scenario.run.offline ? { policy: deepClonePreservingPrototype(scenario.run.offline) } : {}
11716
+ });
11717
+ if (!prepared || typeof prepared !== "object" || Array.isArray(prepared) || "then" in prepared) {
11718
+ throw new RangeError("prepareOffline must synchronously return a preparation object");
11719
+ }
11720
+ if (prepared.stepSec !== undefined && (typeof prepared.stepSec !== "number" || !Number.isFinite(prepared.stepSec) || prepared.stepSec <= 0)) {
11721
+ throw new RangeError("prepareOffline stepSec must be finite and positive");
11722
+ }
11723
+ if (prepared.policy !== undefined)
11724
+ validateCreditPolicy(prepared.policy);
11725
+ const model = prepared.model === undefined ? scenario.model : prepared.model;
11726
+ if (!model || typeof model !== "object" || typeof model.income !== "function" || typeof model.actions !== "function") {
11727
+ throw new RangeError("prepareOffline model must provide income and actions");
11728
+ }
11729
+ return {
11730
+ model,
11731
+ stepSec: prepared.stepSec ?? scenario.run.stepSec,
11732
+ policy: prepared.policy === undefined ? scenario.run.offline : {
11733
+ ...deepClonePreservingPrototype(prepared.policy),
11734
+ actions: scenario.run.offline?.actions
11735
+ }
11736
+ };
11737
+ }
11738
+ var init_sessionOffline = () => {};
11739
+
11608
11740
  // ../core/dist/sim/session.js
11609
11741
  function buildBlocks(pattern) {
11610
11742
  const blocks = [];
@@ -11700,6 +11832,9 @@ function withClocks(scenario, wallT, wallEndT, rewardT, activeT) {
11700
11832
  };
11701
11833
  }
11702
11834
  function simulateSessionPattern(args) {
11835
+ if (args.prepareOffline !== undefined && typeof args.prepareOffline !== "function") {
11836
+ throw new TypeError("prepareOffline must be a function");
11837
+ }
11703
11838
  const sc = args.seed === undefined ? args.scenario : { ...args.scenario, ctx: { ...args.scenario.ctx, seed: args.seed } };
11704
11839
  const start = sc.initial;
11705
11840
  const horizonSec = args.pattern.days * 86400;
@@ -11786,7 +11921,19 @@ function simulateSessionPattern(args) {
11786
11921
  return;
11787
11922
  const requested = wallEnd - wallT;
11788
11923
  const wallStart = wallT;
11789
- const segment = segmentScenario(wallStart, wallEnd);
11924
+ let segment = segmentScenario(wallStart, wallEnd);
11925
+ const prepared = prepareOfflineSegment({
11926
+ scenario: segment,
11927
+ state,
11928
+ seconds: requested,
11929
+ wallStartT: wallStart,
11930
+ wallEndT: wallEnd,
11931
+ prepare: args.prepareOffline
11932
+ });
11933
+ if (prepared.model !== segment.model) {
11934
+ const { clocks: _clocks, ...ctx } = segment.ctx;
11935
+ segment = withClocks({ ...segment, ctx, model: prepared.model }, wallStart, wallEnd, state.t, activeSec);
11936
+ }
11790
11937
  const offlineRun = applyOfflineSeconds({
11791
11938
  scenario: segment,
11792
11939
  seconds: requested,
@@ -11798,15 +11945,16 @@ function simulateSessionPattern(args) {
11798
11945
  enabled: sc.run.eventLog?.enabled ?? true,
11799
11946
  maxEvents: sc.run.eventLog?.maxEvents
11800
11947
  },
11801
- policy: sc.run.offline,
11948
+ stepSec: prepared.stepSec,
11949
+ policy: prepared.policy,
11802
11950
  ...stopFn ? { until: stopFn } : {}
11803
11951
  }
11804
11952
  });
11805
11953
  const credited = offlineRun.offline.simulatedSec;
11806
11954
  const reason = classify();
11807
- const absence = reason ? offlineAbsenceForCredit(credited, requested, sc.run.offline) : requested;
11955
+ const absence = reason ? offlineAbsenceForCredit(credited, requested, prepared.policy) : requested;
11808
11956
  const gapEnd = wallStart + absence;
11809
- const effective = absence === requested ? offlineRun.offline.effectiveSec : resolveOfflineSeconds(absence, sc.run.offline).effectiveSec;
11957
+ const effective = absence === requested ? offlineRun.offline.effectiveSec : resolveOfflineSeconds(absence, prepared.policy).effectiveSec;
11810
11958
  const lost = Math.max(0, absence - effective);
11811
11959
  offlineElapsedSec += absence;
11812
11960
  offlineCreditedSec += credited;
@@ -11977,6 +12125,7 @@ var init_session = __esm(() => {
11977
12125
  init_goalRead();
11978
12126
  init_observation();
11979
12127
  init_offline();
12128
+ init_sessionOffline();
11980
12129
  init_simulator();
11981
12130
  });
11982
12131
 
@@ -12972,11 +13121,20 @@ var init_params2 = __esm(() => {
12972
13121
  }));
12973
13122
  });
12974
13123
 
12975
- // ../core/dist/sim/strategy/opt/objectives/builtins.js
12976
- function safeAbsLog10(E, x) {
12977
- const v = E.absLog10(x);
12978
- return Number.isFinite(v) ? v : -300;
13124
+ // ../core/dist/sim/strategy/opt/finiteAmountLog10.js
13125
+ function finiteAmountLog10(E, amount) {
13126
+ if (!E.isFinite(amount))
13127
+ throw new RangeError("Tuning requires finite monetary amounts in the selected engine");
13128
+ const value = E.absLog10(amount);
13129
+ if (value === Number.NEGATIVE_INFINITY)
13130
+ return ZERO_AMOUNT_LOG10_FLOOR;
13131
+ if (!Number.isFinite(value))
13132
+ throw new RangeError("Tuning requires a finite monetary logarithm");
13133
+ return Math.max(ZERO_AMOUNT_LOG10_FLOOR, value);
12979
13134
  }
13135
+ var ZERO_AMOUNT_LOG10_FLOOR = -300;
13136
+
13137
+ // ../core/dist/sim/strategy/opt/objectives/builtins.js
12980
13138
  var builtinObjectiveFactories;
12981
13139
  var init_builtins2 = __esm(() => {
12982
13140
  init_parseMoney2();
@@ -12990,7 +13148,7 @@ var init_builtins2 = __esm(() => {
12990
13148
  id: "endMoneyLog10",
12991
13149
  score: ({ scenario, run }) => {
12992
13150
  const { E } = scenario.ctx;
12993
- return safeAbsLog10(E, run.end.wallet.money.amount);
13151
+ return finiteAmountLog10(E, run.end.wallet.money.amount);
12994
13152
  }
12995
13153
  })
12996
13154
  },
@@ -13003,7 +13161,7 @@ var init_builtins2 = __esm(() => {
13003
13161
  score: ({ scenario, run }) => {
13004
13162
  const { E } = scenario.ctx;
13005
13163
  const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
13006
- return safeAbsLog10(E, worth.amount);
13164
+ return finiteAmountLog10(E, worth.amount);
13007
13165
  }
13008
13166
  })
13009
13167
  },
@@ -13018,7 +13176,7 @@ var init_builtins2 = __esm(() => {
13018
13176
  const sec = Math.max(1, runElapsedSec(run));
13019
13177
  const hours = sec / 3600;
13020
13178
  const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
13021
- return safeAbsLog10(E, worth.amount) - Math.log10(Math.max(0.000000000001, hours));
13179
+ return finiteAmountLog10(E, worth.amount) - Math.log10(Math.max(0.000000000001, hours));
13022
13180
  }
13023
13181
  })
13024
13182
  },
@@ -13032,7 +13190,7 @@ var init_builtins2 = __esm(() => {
13032
13190
  const { E } = scenario.ctx;
13033
13191
  const sec = Math.max(1, runElapsedSec(run));
13034
13192
  const hours = sec / 3600;
13035
- return safeAbsLog10(E, run.end.prestige.points) - Math.log10(Math.max(0.000000000001, hours));
13193
+ return finiteAmountLog10(E, run.end.prestige.points) - Math.log10(Math.max(0.000000000001, hours));
13036
13194
  }
13037
13195
  })
13038
13196
  },
@@ -13047,7 +13205,7 @@ var init_builtins2 = __esm(() => {
13047
13205
  const sec = Math.max(1, runElapsedSec(run));
13048
13206
  const startWorth = scenario.model.netWorth?.(scenario.ctx, run.start) ?? run.start.wallet.money;
13049
13207
  const endWorth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
13050
- const deltaLog = safeAbsLog10(E, endWorth.amount) - safeAbsLog10(E, startWorth.amount);
13208
+ const deltaLog = finiteAmountLog10(E, endWorth.amount) - finiteAmountLog10(E, startWorth.amount);
13051
13209
  return deltaLog / (sec / 3600);
13052
13210
  }
13053
13211
  })
@@ -13073,6 +13231,9 @@ var init_builtins2 = __esm(() => {
13073
13231
  suffix: { kind: "alphaInfinite", minLen: 2 }
13074
13232
  }).amount;
13075
13233
  const endWorth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
13234
+ if (!E.isFinite(threshold) || !E.isFinite(endWorth.amount)) {
13235
+ throw new RangeError("ETA objective requires finite target and net worth in the selected engine");
13236
+ }
13076
13237
  const reached = E.cmp(endWorth.amount, threshold) >= 0;
13077
13238
  if (!reached)
13078
13239
  return -(params.unreachedPenaltySec ?? 1e9);
@@ -13102,7 +13263,7 @@ var init_builtins2 = __esm(() => {
13102
13263
  const sec = Math.max(1, runElapsedSec(run));
13103
13264
  const hours = sec / 3600;
13104
13265
  const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
13105
- const worthLog = safeAbsLog10(E, worth.amount);
13266
+ const worthLog = finiteAmountLog10(E, worth.amount);
13106
13267
  const stats = run.stats;
13107
13268
  if (!stats || stats.money.droppedRate === null || stats.actions.status === "missing") {
13108
13269
  throw new Error("pacingBalancedLog10 needs observed money and action counters, but this run has none (run.observation.enabled is false)");
@@ -13301,8 +13462,45 @@ function cloneCandidateInitial(state) {
13301
13462
  }
13302
13463
  var init_freshRun = () => {};
13303
13464
 
13465
+ // ../core/dist/sim/strategy/opt/finiteMean.js
13466
+ function finiteMean(values) {
13467
+ if (values.length === 0)
13468
+ throw new RangeError("A mean requires at least one value");
13469
+ const bits = new DataView(new ArrayBuffer(8));
13470
+ let total = 0n;
13471
+ for (const value of values) {
13472
+ if (!Number.isFinite(value))
13473
+ throw new RangeError("A mean requires finite values");
13474
+ bits.setFloat64(0, value, false);
13475
+ const high = bits.getUint32(0, false);
13476
+ const low = bits.getUint32(4, false);
13477
+ const exponent = high >>> 20 & 2047;
13478
+ const fraction = BigInt(high & 1048575) << 32n | BigInt(low);
13479
+ const significand = exponent === 0 ? fraction : 1n << 52n | fraction;
13480
+ const units = significand << BigInt(Math.max(0, exponent - 1));
13481
+ total += high >>> 31 ? -units : units;
13482
+ }
13483
+ if (total === 0n)
13484
+ return 0;
13485
+ const negative = total < 0n;
13486
+ const magnitude = negative ? -total : total;
13487
+ const count = BigInt(values.length);
13488
+ const integerMean = magnitude / count;
13489
+ const shift = Math.max(0, integerMean.toString(2).length - 53);
13490
+ const denominator = count << BigInt(shift);
13491
+ let significand = magnitude / denominator;
13492
+ const twiceRemainder = 2n * (magnitude % denominator);
13493
+ if (twiceRemainder > denominator || twiceRemainder === denominator && (significand & 1n) !== 0n) {
13494
+ significand++;
13495
+ }
13496
+ const mean = Number(significand) * 2 ** (shift - 1074);
13497
+ return negative ? -mean : mean;
13498
+ }
13499
+
13304
13500
  // ../core/dist/sim/strategy/opt/runner.js
13305
13501
  function runCandidateAndScore(args) {
13502
+ if (args.seeds.length === 0)
13503
+ throw new RangeError("Tuning requires at least one seed");
13306
13504
  const stratFactory = args.strategyRegistry.get(args.strategyId);
13307
13505
  if (!stratFactory)
13308
13506
  throw new Error(`Unknown strategy: ${args.strategyId}`);
@@ -13349,24 +13547,28 @@ function runCandidateAndScore(args) {
13349
13547
  const sc = openScenario();
13350
13548
  const run = runScenario(sc);
13351
13549
  assertHorizonReached(run, "runCandidateAndScore");
13550
+ const worth = sc.model.netWorth?.(sc.ctx, run.end) ?? run.end.wallet.money;
13551
+ const endMoneyLog10 = finiteAmountLog10(sc.ctx.E, run.end.wallet.money.amount);
13552
+ const endNetWorthLog10 = finiteAmountLog10(sc.ctx.E, worth.amount);
13352
13553
  const s = objective.score({ scenario: sc, run, evaluation: {
13353
13554
  open: openScenario,
13354
13555
  registries: { models: args.modelRegistry, strategies: args.strategyRegistry },
13355
13556
  isolation: { model: args.model, statefulModel: args.statefulModel, strategy: { id: args.strategyId, params: candidateParams } }
13356
13557
  } });
13558
+ if (!Number.isFinite(s))
13559
+ throw new RangeError(`Objective ${args.objectiveId} must return a finite score (seed ${seed})`);
13357
13560
  seedScores.push(s);
13358
- const worth = sc.model.netWorth?.(sc.ctx, run.end) ?? run.end.wallet.money;
13359
13561
  seedResults.push({
13360
13562
  seed,
13361
13563
  score: s,
13362
13564
  durationSec: Math.max(0, runElapsedSec(run)),
13363
- endMoneyLog10: sc.ctx.E.absLog10(run.end.wallet.money.amount),
13364
- endNetWorthLog10: sc.ctx.E.absLog10(worth.amount),
13565
+ endMoneyLog10,
13566
+ endNetWorthLog10,
13365
13567
  droppedRate: run.stats?.money.droppedRate ?? null,
13366
13568
  actionsApplied: run.stats && run.stats.actions.status === "observed" ? run.stats.actions.applied : null
13367
13569
  });
13368
13570
  }
13369
- const score = seedScores.reduce((a, b) => a + b, 0) / Math.max(1, seedScores.length);
13571
+ const score = finiteMean(seedScores);
13370
13572
  return { score, seedScores, seedResults };
13371
13573
  }
13372
13574
  var init_runner = __esm(() => {
@@ -14353,7 +14555,7 @@ function compensatedSum(values, scale) {
14353
14555
  }
14354
14556
  return sum + compensation;
14355
14557
  }
14356
- function finiteMean(values) {
14558
+ function finiteMean2(values) {
14357
14559
  let smallest = Number.POSITIVE_INFINITY;
14358
14560
  let largest = Number.NEGATIVE_INFINITY;
14359
14561
  for (const value of values) {
@@ -14503,7 +14705,7 @@ function analyzeDistribution(request) {
14503
14705
  }
14504
14706
  const eligible = observed.length + rightCensored;
14505
14707
  const completionRate = eligible > 0 ? observed.length / eligible : null;
14506
- const arithmetic = observed.length > 0 ? finiteMean(observed) : null;
14708
+ const arithmetic = observed.length > 0 ? finiteMean2(observed) : null;
14507
14709
  const identical = observed.every((value) => value === observed[0]);
14508
14710
  const rawVariance = arithmetic === null ? null : finiteVariance(observed, arithmetic);
14509
14711
  const variance = rawVariance === 0 && !identical ? null : rawVariance;
@@ -15038,15 +15240,16 @@ function readBenefit(vars) {
15038
15240
  const value = vars.benefit;
15039
15241
  return typeof value === "number" && Number.isFinite(value) ? value : null;
15040
15242
  }
15041
- function observeMetrics(E, spec, end, startT, reachedAt, elapsedSec) {
15243
+ function observeMetrics(E, spec, end, moneyElapsedSec, milestoneElapsedSec, elapsedSec) {
15042
15244
  const amount = end.wallet.money.amount;
15043
15245
  const benefit = readBenefit(end.vars);
15044
15246
  return metricIds(spec).map((metricId) => {
15045
- if (metricId === "timeToMoney") {
15046
- if (reachedAt === undefined) {
15047
- return { metricId, status: "censored", value: null, lowerBound: { sec: elapsedSec, strict: true } };
15247
+ if (metricId === "timeToMoney" || metricId === "timeToMilestone") {
15248
+ const elapsed = metricId === "timeToMoney" ? moneyElapsedSec : milestoneElapsedSec;
15249
+ if (elapsed === undefined) {
15250
+ const strict = metricId === "timeToMoney" || !mayRecordAtActionStart(spec.milestoneKey ?? "");
15251
+ return { metricId, status: "censored", value: null, lowerBound: { sec: elapsedSec, strict } };
15048
15252
  }
15049
- const elapsed = reachedAt - startT;
15050
15253
  return { metricId, status: "observed", value: String(elapsed), num: elapsed };
15051
15254
  }
15052
15255
  if (metricId === "endMoney") {
@@ -15069,7 +15272,7 @@ function aggregate(E, rows, metricId) {
15069
15272
  }
15070
15273
  if (picked.some((metric) => metric.status !== "observed" || metric.value === null)) {
15071
15274
  const bounded = picked.every((metric) => metric.num !== undefined || metric.lowerBound !== undefined);
15072
- if (metricId !== "timeToMoney" || !bounded)
15275
+ if (metricId !== "timeToMoney" && metricId !== "timeToMilestone" || !bounded)
15073
15276
  return { metricId, status: "censored", value: null };
15074
15277
  const bounds = picked.map((metric) => metric.num ?? metric.lowerBound.sec);
15075
15278
  const total = bounds.reduce((sum, bound) => sum + bound, 0);
@@ -15156,6 +15359,10 @@ function prepareExperiment(request) {
15156
15359
  reject("goal target must be finite");
15157
15360
  if (metrics.includes("timeToMoney") && spec.goal === undefined)
15158
15361
  reject("timeToMoney requires a finite goal target");
15362
+ if (spec.milestoneKey !== undefined && spec.milestoneKey.trim().length === 0)
15363
+ reject("milestoneKey must not be blank");
15364
+ if (metrics.includes("timeToMilestone") && spec.milestoneKey === undefined)
15365
+ reject("timeToMilestone requires milestoneKey");
15159
15366
  if (!isJsonData(request.spec, new Set, true))
15160
15367
  reject("spec must be JSON data, because the spec hashes record it as JSON");
15161
15368
  requireScenarioData(request.scenario);
@@ -15182,7 +15389,8 @@ function checkExperimentSpec(request) {
15182
15389
  }
15183
15390
  function runExperiment(request) {
15184
15391
  const { spec, metrics, base, prepared, specHashes } = prepareExperiment(request);
15185
- const measuresReach = metrics.includes("timeToMoney");
15392
+ const measuresMoney = metrics.includes("timeToMoney");
15393
+ const measuresMilestone = metrics.includes("timeToMilestone");
15186
15394
  const E = request.engine;
15187
15395
  const { strategy: _scenarioStrategy, ...withoutStrategy } = request.scenario;
15188
15396
  const compiled = compileScenario({
@@ -15201,7 +15409,7 @@ function runExperiment(request) {
15201
15409
  models: request.modelRegistry,
15202
15410
  strategies: request.strategyRegistry
15203
15411
  });
15204
- const target = measuresReach && spec.goal !== undefined ? E.from(spec.goal.target) : undefined;
15412
+ const target = measuresMoney && spec.goal !== undefined ? E.from(spec.goal.target) : undefined;
15205
15413
  const stopAtReach = metrics.every((metricId) => metricId === "timeToMoney");
15206
15414
  const tasks = [];
15207
15415
  const perCandidate = [];
@@ -15226,21 +15434,41 @@ function runExperiment(request) {
15226
15434
  try {
15227
15435
  const opened = binding.fresh({ trialId: taskId, seed, plan });
15228
15436
  const scenario = opened.scenario;
15229
- let reachedAt;
15437
+ let moneyElapsedSec;
15438
+ let milestoneElapsedSec;
15439
+ let observedElapsedSec = 0;
15440
+ let stepStartElapsedSec = 0;
15230
15441
  const result = runScenario({
15231
15442
  ...scenario,
15232
15443
  run: {
15233
15444
  ...scenario.run,
15445
+ ...measuresMoney || measuresMilestone ? {
15446
+ observation: { ...scenario.run.observation, enabled: true, ...measuresMilestone ? { maxMilestones: 0 } : {} },
15447
+ observer: {
15448
+ ...scenario.run.observer,
15449
+ onStep: (fact) => {
15450
+ stepStartElapsedSec = observedElapsedSec;
15451
+ observedElapsedSec += fact.dt;
15452
+ scenario.run.observer?.onStep?.(fact);
15453
+ },
15454
+ onMilestone: (fact) => {
15455
+ if (measuresMilestone && milestoneElapsedSec === undefined && fact.key === spec.milestoneKey) {
15456
+ milestoneElapsedSec = fact.phase === "action-start" ? stepStartElapsedSec : observedElapsedSec;
15457
+ }
15458
+ scenario.run.observer?.onMilestone?.(fact);
15459
+ }
15460
+ }
15461
+ } : {},
15234
15462
  ...target !== undefined ? {
15235
15463
  until: (state) => {
15236
- if (reachedAt === undefined && E.cmp(state.wallet.money.amount, target) >= 0)
15237
- reachedAt = state.t;
15238
- return stopAtReach && reachedAt !== undefined;
15464
+ if (moneyElapsedSec === undefined && E.cmp(state.wallet.money.amount, target) >= 0)
15465
+ moneyElapsedSec = observedElapsedSec;
15466
+ return stopAtReach && moneyElapsedSec !== undefined;
15239
15467
  }
15240
15468
  } : {}
15241
15469
  }
15242
15470
  });
15243
- readings = observeMetrics(E, spec, result.end, result.start.t, reachedAt, runElapsedSec(result));
15471
+ readings = observeMetrics(E, spec, result.end, moneyElapsedSec, milestoneElapsedSec, runElapsedSec(result));
15244
15472
  } catch {
15245
15473
  readings = failedMetrics(spec);
15246
15474
  }
@@ -15328,6 +15556,7 @@ function runExperiment(request) {
15328
15556
  originalSpecHash: specHashes.original,
15329
15557
  effectiveSpecHash: specHashes.effective,
15330
15558
  effectiveRunHash: request.effectiveRunHash,
15559
+ ...measuresMilestone ? { milestoneKey: spec.milestoneKey } : {},
15331
15560
  budget: {
15332
15561
  candidates: spec.budget.candidates,
15333
15562
  seeds: spec.budget.seeds,
@@ -15357,30 +15586,31 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15357
15586
  const _ip8 = (input) => typeof input["originalSpecHash"] === "string";
15358
15587
  const _ip9 = (input) => typeof input["effectiveSpecHash"] === "string";
15359
15588
  const _ip10 = (input) => typeof input["effectiveRunHash"] === "string";
15360
- const _ip11 = (input) => typeof input["budget"] === "object" && input["budget"] !== null && _io2(input["budget"]);
15361
- const _ip12 = (input) => Array.isArray(input["tasks"]) && input["tasks"].every((elem) => typeof elem === "object" && elem !== null && _io3(elem));
15362
- const _ip13 = (input) => Array.isArray(input["candidates"]) && input["candidates"].every((elem) => typeof elem === "object" && elem !== null && _io4(elem));
15363
- const _ip14 = (input) => Array.isArray(input["elasticities"]) && input["elasticities"].every((elem) => typeof elem === "object" && elem !== null && _io6(elem));
15364
- const _ip15 = (input) => typeof input["pareto"] === "object" && input["pareto"] !== null && _io7(input["pareto"]);
15365
- const _ip16 = (input) => Array.isArray(input["warnings"]) && input["warnings"].every((elem) => typeof elem === "string");
15366
- const _ip17 = (input) => typeof input["taskId"] === "string";
15367
- const _ip18 = (input) => typeof input["candidateId"] === "string";
15368
- const _ip19 = (input) => typeof input["logicalSeed"] === "number";
15369
- const _ip20 = (input) => typeof input["engineId"] === "string";
15370
- const _ip21 = (input) => typeof input["engineVersion"] === "string";
15371
- const _ip22 = (input) => typeof input["modelId"] === "string";
15372
- const _ip23 = (input) => typeof input["modelVersion"] === "number";
15373
- const _ip24 = (input) => input["strategyId"] === null || typeof input["strategyId"] === "string";
15374
- const _ip25 = (input) => typeof input["modelParams"] === "string";
15375
- const _ip26 = (input) => typeof input["strategyParams"] === "string";
15376
- const _ip27 = (input) => typeof input["sessionPattern"] === "string";
15377
- const _ip28 = (input) => typeof input["sessionDays"] === "number";
15378
- const _ip29 = (input) => typeof input["horizonSec"] === "number";
15379
- const _ip30 = (input) => typeof input["stepSec"] === "number";
15380
- const _ip31 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15381
- const _ip32 = (input) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected";
15382
- const _ip33 = (input) => input["value"] === null || typeof input["value"] === "string";
15383
- const _ve0 = 'Readonly<{ schemaVersion: 1; contract: "idlekit.experiment.v1"; estimatorVersion: "one-at-a-time-local-v1"; algorithmVersion: "experiment-oat-v1"; method: "fixed-sweep-and-one-at-a-time"; ... 11 more ...; warnings: readonly string[]; }>';
15589
+ const _ip11 = (input) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string";
15590
+ const _ip12 = (input) => typeof input["budget"] === "object" && input["budget"] !== null && _io2(input["budget"]);
15591
+ const _ip13 = (input) => Array.isArray(input["tasks"]) && input["tasks"].every((elem) => typeof elem === "object" && elem !== null && _io3(elem));
15592
+ const _ip14 = (input) => Array.isArray(input["candidates"]) && input["candidates"].every((elem) => typeof elem === "object" && elem !== null && _io4(elem));
15593
+ const _ip15 = (input) => Array.isArray(input["elasticities"]) && input["elasticities"].every((elem) => typeof elem === "object" && elem !== null && _io6(elem));
15594
+ const _ip16 = (input) => typeof input["pareto"] === "object" && input["pareto"] !== null && _io7(input["pareto"]);
15595
+ const _ip17 = (input) => Array.isArray(input["warnings"]) && input["warnings"].every((elem) => typeof elem === "string");
15596
+ const _ip18 = (input) => typeof input["taskId"] === "string";
15597
+ const _ip19 = (input) => typeof input["candidateId"] === "string";
15598
+ const _ip20 = (input) => typeof input["logicalSeed"] === "number";
15599
+ const _ip21 = (input) => typeof input["engineId"] === "string";
15600
+ const _ip22 = (input) => typeof input["engineVersion"] === "string";
15601
+ const _ip23 = (input) => typeof input["modelId"] === "string";
15602
+ const _ip24 = (input) => typeof input["modelVersion"] === "number";
15603
+ const _ip25 = (input) => input["strategyId"] === null || typeof input["strategyId"] === "string";
15604
+ const _ip26 = (input) => typeof input["modelParams"] === "string";
15605
+ const _ip27 = (input) => typeof input["strategyParams"] === "string";
15606
+ const _ip28 = (input) => typeof input["sessionPattern"] === "string";
15607
+ const _ip29 = (input) => typeof input["sessionDays"] === "number";
15608
+ const _ip30 = (input) => typeof input["horizonSec"] === "number";
15609
+ const _ip31 = (input) => typeof input["stepSec"] === "number";
15610
+ const _ip32 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15611
+ const _ip33 = (input) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected";
15612
+ const _ip34 = (input) => input["value"] === null || typeof input["value"] === "string";
15613
+ const _ve0 = 'Readonly<{ schemaVersion: 1; contract: "idlekit.experiment.v1"; estimatorVersion: "one-at-a-time-local-v1"; algorithmVersion: "experiment-oat-v1"; method: "fixed-sweep-and-one-at-a-time"; ... 12 more ...; warnings: readonly string[]; }>';
15384
15614
  const _vp0 = (input, _path, _exceptionable = true) => input["schemaVersion"] === 1 || _report(_exceptionable, {
15385
15615
  path: _path + ".schemaVersion",
15386
15616
  expected: "1",
@@ -15440,7 +15670,12 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15440
15670
  expected: "string",
15441
15671
  value: input["effectiveRunHash"]
15442
15672
  });
15443
- const _vp11 = (input, _path, _exceptionable = true) => (typeof input["budget"] === "object" && input["budget"] !== null || _report(_exceptionable, {
15673
+ const _vp11 = (input, _path, _exceptionable = true) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string" || _report(_exceptionable, {
15674
+ path: _path + ".milestoneKey",
15675
+ expected: "(string | undefined)",
15676
+ value: input["milestoneKey"]
15677
+ });
15678
+ const _vp12 = (input, _path, _exceptionable = true) => (typeof input["budget"] === "object" && input["budget"] !== null || _report(_exceptionable, {
15444
15679
  path: _path + ".budget",
15445
15680
  expected: "Readonly<{ candidates: number; seeds: number; usedCandidates: number; usedSeeds: number; }>",
15446
15681
  value: input["budget"]
@@ -15449,7 +15684,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15449
15684
  expected: "Readonly<{ candidates: number; seeds: number; usedCandidates: number; usedSeeds: number; }>",
15450
15685
  value: input["budget"]
15451
15686
  });
15452
- const _vp12 = (input, _path, _exceptionable = true) => (Array.isArray(input["tasks"]) || _report(_exceptionable, {
15687
+ const _vp13 = (input, _path, _exceptionable = true) => (Array.isArray(input["tasks"]) || _report(_exceptionable, {
15453
15688
  path: _path + ".tasks",
15454
15689
  expected: _ve2,
15455
15690
  value: input["tasks"]
@@ -15468,7 +15703,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15468
15703
  });
15469
15704
  const _ve1 = "Readonly<{ taskId: string; candidateId: string; logicalSeed: number; engineId: string; engineVersion: string; modelId: string; modelVersion: number; strategyId: string | null; modelParams: string; ... 7 more ...; value: string | null; }>";
15470
15705
  const _ve2 = "ReadonlyArray<Readonly<{ taskId: string; candidateId: string; logicalSeed: number; engineId: string; engineVersion: string; modelId: string; modelVersion: number; strategyId: string | null; modelParams: string; ... 7 more ...; value: string | null; }>>";
15471
- const _vp13 = (input, _path, _exceptionable = true) => (Array.isArray(input["candidates"]) || _report(_exceptionable, {
15706
+ const _vp14 = (input, _path, _exceptionable = true) => (Array.isArray(input["candidates"]) || _report(_exceptionable, {
15472
15707
  path: _path + ".candidates",
15473
15708
  expected: _ve4,
15474
15709
  value: input["candidates"]
@@ -15487,7 +15722,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15487
15722
  });
15488
15723
  const _ve3 = "Readonly<{ id: string; metrics: readonly Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>[]; constraintViolations: readonly string[]; }>";
15489
15724
  const _ve4 = "ReadonlyArray<Readonly<{ id: string; metrics: readonly Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>[]; constraintViolations: readonly string[]; }>>";
15490
- const _vp14 = (input, _path, _exceptionable = true) => (Array.isArray(input["elasticities"]) || _report(_exceptionable, {
15725
+ const _vp15 = (input, _path, _exceptionable = true) => (Array.isArray(input["elasticities"]) || _report(_exceptionable, {
15491
15726
  path: _path + ".elasticities",
15492
15727
  expected: _ve6,
15493
15728
  value: input["elasticities"]
@@ -15506,7 +15741,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15506
15741
  });
15507
15742
  const _ve5 = 'Readonly<{ dimensionId: string; candidateId: string; metricId: ExperimentMetricId; method: "one-at-a-time-local"; methodVersion: 1; globalSensitivity: false; sobol: false; status: "observed" | "unavailable"; elasticity: string | null; absoluteChange: string | null; direction: "flat" | ... 2 more ... | "unavailable";...';
15508
15743
  const _ve6 = 'ReadonlyArray<Readonly<{ dimensionId: string; candidateId: string; metricId: ExperimentMetricId; method: "one-at-a-time-local"; methodVersion: 1; globalSensitivity: false; sobol: false; status: "observed" | "unavailable"; elasticity: string | null; absoluteChange: string | null; direction: "flat" | ... 2 more ... | "unavailable";...>';
15509
- const _vp15 = (input, _path, _exceptionable = true) => (typeof input["pareto"] === "object" && input["pareto"] !== null || _report(_exceptionable, {
15744
+ const _vp16 = (input, _path, _exceptionable = true) => (typeof input["pareto"] === "object" && input["pareto"] !== null || _report(_exceptionable, {
15510
15745
  path: _path + ".pareto",
15511
15746
  expected: 'Readonly<{ sense: "per-metric"; nonDominated: readonly string[]; uncompared: readonly string[]; }>',
15512
15747
  value: input["pareto"]
@@ -15515,7 +15750,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15515
15750
  expected: 'Readonly<{ sense: "per-metric"; nonDominated: readonly string[]; uncompared: readonly string[]; }>',
15516
15751
  value: input["pareto"]
15517
15752
  });
15518
- const _vp16 = (input, _path, _exceptionable = true) => (Array.isArray(input["warnings"]) || _report(_exceptionable, {
15753
+ const _vp17 = (input, _path, _exceptionable = true) => (Array.isArray(input["warnings"]) || _report(_exceptionable, {
15519
15754
  path: _path + ".warnings",
15520
15755
  expected: "ReadonlyArray<string>",
15521
15756
  value: input["warnings"]
@@ -15528,100 +15763,100 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15528
15763
  expected: "ReadonlyArray<string>",
15529
15764
  value: input["warnings"]
15530
15765
  });
15531
- const _vp17 = (input, _path, _exceptionable = true) => typeof input["taskId"] === "string" || _report(_exceptionable, {
15766
+ const _vp18 = (input, _path, _exceptionable = true) => typeof input["taskId"] === "string" || _report(_exceptionable, {
15532
15767
  path: _path + ".taskId",
15533
15768
  expected: "string",
15534
15769
  value: input["taskId"]
15535
15770
  });
15536
- const _vp18 = (input, _path, _exceptionable = true) => typeof input["candidateId"] === "string" || _report(_exceptionable, {
15771
+ const _vp19 = (input, _path, _exceptionable = true) => typeof input["candidateId"] === "string" || _report(_exceptionable, {
15537
15772
  path: _path + ".candidateId",
15538
15773
  expected: "string",
15539
15774
  value: input["candidateId"]
15540
15775
  });
15541
- const _vp19 = (input, _path, _exceptionable = true) => typeof input["logicalSeed"] === "number" || _report(_exceptionable, {
15776
+ const _vp20 = (input, _path, _exceptionable = true) => typeof input["logicalSeed"] === "number" || _report(_exceptionable, {
15542
15777
  path: _path + ".logicalSeed",
15543
15778
  expected: "number",
15544
15779
  value: input["logicalSeed"]
15545
15780
  });
15546
- const _vp20 = (input, _path, _exceptionable = true) => typeof input["engineId"] === "string" || _report(_exceptionable, {
15781
+ const _vp21 = (input, _path, _exceptionable = true) => typeof input["engineId"] === "string" || _report(_exceptionable, {
15547
15782
  path: _path + ".engineId",
15548
15783
  expected: "string",
15549
15784
  value: input["engineId"]
15550
15785
  });
15551
- const _vp21 = (input, _path, _exceptionable = true) => typeof input["engineVersion"] === "string" || _report(_exceptionable, {
15786
+ const _vp22 = (input, _path, _exceptionable = true) => typeof input["engineVersion"] === "string" || _report(_exceptionable, {
15552
15787
  path: _path + ".engineVersion",
15553
15788
  expected: "string",
15554
15789
  value: input["engineVersion"]
15555
15790
  });
15556
- const _vp22 = (input, _path, _exceptionable = true) => typeof input["modelId"] === "string" || _report(_exceptionable, {
15791
+ const _vp23 = (input, _path, _exceptionable = true) => typeof input["modelId"] === "string" || _report(_exceptionable, {
15557
15792
  path: _path + ".modelId",
15558
15793
  expected: "string",
15559
15794
  value: input["modelId"]
15560
15795
  });
15561
- const _vp23 = (input, _path, _exceptionable = true) => typeof input["modelVersion"] === "number" || _report(_exceptionable, {
15796
+ const _vp24 = (input, _path, _exceptionable = true) => typeof input["modelVersion"] === "number" || _report(_exceptionable, {
15562
15797
  path: _path + ".modelVersion",
15563
15798
  expected: "number",
15564
15799
  value: input["modelVersion"]
15565
15800
  });
15566
- const _vp24 = (input, _path, _exceptionable = true) => input["strategyId"] === null || typeof input["strategyId"] === "string" || _report(_exceptionable, {
15801
+ const _vp25 = (input, _path, _exceptionable = true) => input["strategyId"] === null || typeof input["strategyId"] === "string" || _report(_exceptionable, {
15567
15802
  path: _path + ".strategyId",
15568
15803
  expected: "(null | string)",
15569
15804
  value: input["strategyId"]
15570
15805
  });
15571
- const _vp25 = (input, _path, _exceptionable = true) => typeof input["modelParams"] === "string" || _report(_exceptionable, {
15806
+ const _vp26 = (input, _path, _exceptionable = true) => typeof input["modelParams"] === "string" || _report(_exceptionable, {
15572
15807
  path: _path + ".modelParams",
15573
15808
  expected: "string",
15574
15809
  value: input["modelParams"]
15575
15810
  });
15576
- const _vp26 = (input, _path, _exceptionable = true) => typeof input["strategyParams"] === "string" || _report(_exceptionable, {
15811
+ const _vp27 = (input, _path, _exceptionable = true) => typeof input["strategyParams"] === "string" || _report(_exceptionable, {
15577
15812
  path: _path + ".strategyParams",
15578
15813
  expected: "string",
15579
15814
  value: input["strategyParams"]
15580
15815
  });
15581
- const _vp27 = (input, _path, _exceptionable = true) => typeof input["sessionPattern"] === "string" || _report(_exceptionable, {
15816
+ const _vp28 = (input, _path, _exceptionable = true) => typeof input["sessionPattern"] === "string" || _report(_exceptionable, {
15582
15817
  path: _path + ".sessionPattern",
15583
15818
  expected: "string",
15584
15819
  value: input["sessionPattern"]
15585
15820
  });
15586
- const _vp28 = (input, _path, _exceptionable = true) => typeof input["sessionDays"] === "number" || _report(_exceptionable, {
15821
+ const _vp29 = (input, _path, _exceptionable = true) => typeof input["sessionDays"] === "number" || _report(_exceptionable, {
15587
15822
  path: _path + ".sessionDays",
15588
15823
  expected: "number",
15589
15824
  value: input["sessionDays"]
15590
15825
  });
15591
- const _vp29 = (input, _path, _exceptionable = true) => typeof input["horizonSec"] === "number" || _report(_exceptionable, {
15826
+ const _vp30 = (input, _path, _exceptionable = true) => typeof input["horizonSec"] === "number" || _report(_exceptionable, {
15592
15827
  path: _path + ".horizonSec",
15593
15828
  expected: "number",
15594
15829
  value: input["horizonSec"]
15595
15830
  });
15596
- const _vp30 = (input, _path, _exceptionable = true) => typeof input["stepSec"] === "number" || _report(_exceptionable, {
15831
+ const _vp31 = (input, _path, _exceptionable = true) => typeof input["stepSec"] === "number" || _report(_exceptionable, {
15597
15832
  path: _path + ".stepSec",
15598
15833
  expected: "number",
15599
15834
  value: input["stepSec"]
15600
15835
  });
15601
- const _vp31 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15836
+ const _vp32 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15602
15837
  path: _path + ".metricId",
15603
- expected: '("endMoney" | "timeToMoney" | "varBenefit")',
15838
+ expected: '("endMoney" | "timeToMilestone" | "timeToMoney" | "varBenefit")',
15604
15839
  value: input["metricId"]
15605
15840
  });
15606
- const _vp32 = (input, _path, _exceptionable = true) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected" || _report(_exceptionable, {
15841
+ const _vp33 = (input, _path, _exceptionable = true) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected" || _report(_exceptionable, {
15607
15842
  path: _path + ".status",
15608
15843
  expected: '("censored" | "failed" | "observed" | "rejected")',
15609
15844
  value: input["status"]
15610
15845
  });
15611
- const _vp33 = (input, _path, _exceptionable = true) => input["value"] === null || typeof input["value"] === "string" || _report(_exceptionable, {
15846
+ const _vp34 = (input, _path, _exceptionable = true) => input["value"] === null || typeof input["value"] === "string" || _report(_exceptionable, {
15612
15847
  path: _path + ".value",
15613
15848
  expected: "(null | string)",
15614
15849
  value: input["value"]
15615
15850
  });
15616
- const _io0 = (input) => _ip0(input) && _ip1(input) && _ip2(input) && _ip3(input) && _ip4(input) && _ip5(input) && _ip6(input) && _ip7(input) && _ip8(input) && _ip9(input) && _ip10(input) && _ip11(input) && _ip12(input) && _ip13(input) && _ip14(input) && _ip15(input) && _ip16(input);
15851
+ const _io0 = (input) => _ip0(input) && _ip1(input) && _ip2(input) && _ip3(input) && _ip4(input) && _ip5(input) && _ip6(input) && _ip7(input) && _ip8(input) && _ip9(input) && _ip10(input) && _ip11(input) && _ip12(input) && _ip13(input) && _ip14(input) && _ip15(input) && _ip16(input) && _ip17(input);
15617
15852
  const _io1 = (input) => typeof input.ref === "string" && typeof input.hash === "string";
15618
15853
  const _io2 = (input) => typeof input.candidates === "number" && typeof input.seeds === "number" && typeof input.usedCandidates === "number" && typeof input.usedSeeds === "number";
15619
- const _io3 = (input) => _ip17(input) && _ip18(input) && _ip19(input) && _ip20(input) && _ip21(input) && _ip22(input) && _ip23(input) && _ip24(input) && _ip25(input) && _ip26(input) && _ip27(input) && _ip28(input) && _ip29(input) && _ip30(input) && _ip31(input) && _ip32(input) && _ip33(input);
15854
+ const _io3 = (input) => _ip18(input) && _ip19(input) && _ip20(input) && _ip21(input) && _ip22(input) && _ip23(input) && _ip24(input) && _ip25(input) && _ip26(input) && _ip27(input) && _ip28(input) && _ip29(input) && _ip30(input) && _ip31(input) && _ip32(input) && _ip33(input) && _ip34(input);
15620
15855
  const _io4 = (input) => typeof input.id === "string" && (Array.isArray(input.metrics) && input.metrics.every((elem) => typeof elem === "object" && elem !== null && _io5(elem))) && (Array.isArray(input.constraintViolations) && input.constraintViolations.every((elem) => typeof elem === "string"));
15621
- const _io5 = (input) => _ip31(input) && _ip32(input) && _ip33(input);
15622
- const _io6 = (input) => typeof input.dimensionId === "string" && _ip18(input) && _ip31(input) && input.method === "one-at-a-time-local" && input.methodVersion === 1 && _ip5(input) && _ip6(input) && (input.status === "observed" || input.status === "unavailable") && (input.elasticity === null || typeof input.elasticity === "string") && (input.absoluteChange === null || typeof input.absoluteChange === "string") && (input.direction === "flat" || input.direction === "higher" || input.direction === "lower" || input.direction === "unavailable");
15856
+ const _io5 = (input) => _ip32(input) && _ip33(input) && _ip34(input);
15857
+ const _io6 = (input) => typeof input.dimensionId === "string" && _ip19(input) && _ip32(input) && input.method === "one-at-a-time-local" && input.methodVersion === 1 && _ip5(input) && _ip6(input) && (input.status === "observed" || input.status === "unavailable") && (input.elasticity === null || typeof input.elasticity === "string") && (input.absoluteChange === null || typeof input.absoluteChange === "string") && (input.direction === "flat" || input.direction === "higher" || input.direction === "lower" || input.direction === "unavailable");
15623
15858
  const _io7 = (input) => input.sense === "per-metric" && (Array.isArray(input.nonDominated) && input.nonDominated.every((elem) => typeof elem === "string")) && (Array.isArray(input.uncompared) && input.uncompared.every((elem) => typeof elem === "string"));
15624
- const _vo0 = (input, _path, _exceptionable = true) => [_vp0(input, _path, _exceptionable), _vp1(input, _path, _exceptionable), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), _vp5(input, _path, _exceptionable), _vp6(input, _path, _exceptionable), _vp7(input, _path, _exceptionable), _vp8(input, _path, _exceptionable), _vp9(input, _path, _exceptionable), _vp10(input, _path, _exceptionable), _vp11(input, _path, _exceptionable), _vp12(input, _path, _exceptionable), _vp13(input, _path, _exceptionable), _vp14(input, _path, _exceptionable), _vp15(input, _path, _exceptionable), _vp16(input, _path, _exceptionable)].every((flag) => flag);
15859
+ const _vo0 = (input, _path, _exceptionable = true) => [_vp0(input, _path, _exceptionable), _vp1(input, _path, _exceptionable), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), _vp5(input, _path, _exceptionable), _vp6(input, _path, _exceptionable), _vp7(input, _path, _exceptionable), _vp8(input, _path, _exceptionable), _vp9(input, _path, _exceptionable), _vp10(input, _path, _exceptionable), _vp11(input, _path, _exceptionable), _vp12(input, _path, _exceptionable), _vp13(input, _path, _exceptionable), _vp14(input, _path, _exceptionable), _vp15(input, _path, _exceptionable), _vp16(input, _path, _exceptionable), _vp17(input, _path, _exceptionable)].every((flag) => flag);
15625
15860
  const _vo1 = (input, _path, _exceptionable = true) => [typeof input.ref === "string" || _report(_exceptionable, {
15626
15861
  path: _path + ".ref",
15627
15862
  expected: "string",
@@ -15648,7 +15883,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15648
15883
  expected: "number",
15649
15884
  value: input.usedSeeds
15650
15885
  })].every((flag) => flag);
15651
- const _vo3 = (input, _path, _exceptionable = true) => [_vp17(input, _path, _exceptionable), _vp18(input, _path, _exceptionable), _vp19(input, _path, _exceptionable), _vp20(input, _path, _exceptionable), _vp21(input, _path, _exceptionable), _vp22(input, _path, _exceptionable), _vp23(input, _path, _exceptionable), _vp24(input, _path, _exceptionable), _vp25(input, _path, _exceptionable), _vp26(input, _path, _exceptionable), _vp27(input, _path, _exceptionable), _vp28(input, _path, _exceptionable), _vp29(input, _path, _exceptionable), _vp30(input, _path, _exceptionable), _vp31(input, _path, _exceptionable), _vp32(input, _path, _exceptionable), _vp33(input, _path, _exceptionable)].every((flag) => flag);
15886
+ const _vo3 = (input, _path, _exceptionable = true) => [_vp18(input, _path, _exceptionable), _vp19(input, _path, _exceptionable), _vp20(input, _path, _exceptionable), _vp21(input, _path, _exceptionable), _vp22(input, _path, _exceptionable), _vp23(input, _path, _exceptionable), _vp24(input, _path, _exceptionable), _vp25(input, _path, _exceptionable), _vp26(input, _path, _exceptionable), _vp27(input, _path, _exceptionable), _vp28(input, _path, _exceptionable), _vp29(input, _path, _exceptionable), _vp30(input, _path, _exceptionable), _vp31(input, _path, _exceptionable), _vp32(input, _path, _exceptionable), _vp33(input, _path, _exceptionable), _vp34(input, _path, _exceptionable)].every((flag) => flag);
15652
15887
  const _vo4 = (input, _path, _exceptionable = true) => [typeof input.id === "string" || _report(_exceptionable, {
15653
15888
  path: _path + ".id",
15654
15889
  expected: "string",
@@ -15682,12 +15917,12 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15682
15917
  expected: "ReadonlyArray<string>",
15683
15918
  value: input.constraintViolations
15684
15919
  })].every((flag) => flag);
15685
- const _vo5 = (input, _path, _exceptionable = true) => [_vp31(input, _path, _exceptionable), _vp32(input, _path, _exceptionable), _vp33(input, _path, _exceptionable)].every((flag) => flag);
15920
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp32(input, _path, _exceptionable), _vp33(input, _path, _exceptionable), _vp34(input, _path, _exceptionable)].every((flag) => flag);
15686
15921
  const _vo6 = (input, _path, _exceptionable = true) => [typeof input.dimensionId === "string" || _report(_exceptionable, {
15687
15922
  path: _path + ".dimensionId",
15688
15923
  expected: "string",
15689
15924
  value: input.dimensionId
15690
- }), _vp18(input, _path, _exceptionable), _vp31(input, _path, _exceptionable), input.method === "one-at-a-time-local" || _report(_exceptionable, {
15925
+ }), _vp19(input, _path, _exceptionable), _vp32(input, _path, _exceptionable), input.method === "one-at-a-time-local" || _report(_exceptionable, {
15691
15926
  path: _path + ".method",
15692
15927
  expected: '"one-at-a-time-local"',
15693
15928
  value: input.method
@@ -15773,105 +16008,82 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15773
16008
  };
15774
16009
  });
15775
16010
  })())(input), validateExperimentSpecValidator, validateExperimentSpec = (input) => (validateExperimentSpecValidator ??= (() => {
15776
- const _ip0 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15777
- const _ip1 = (input) => typeof input["id"] === "string";
15778
- const _ip2 = (input) => typeof input["min"] === "number";
15779
- const _ip3 = (input) => typeof input["max"] === "number";
15780
- const _ve0 = "Readonly<{ schemaVersion: 1; scenario: Readonly<{ ref: string; hash: string; }>; model: Readonly<{ id: string; version: number; }>; strategy?: Readonly<{ id: string; }> | undefined; ... 10 more ...; session: Readonly<...>; }>";
15781
- const _vp0 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15782
- path: _path + ".metricId",
15783
- expected: '("endMoney" | "timeToMoney" | "varBenefit")',
15784
- value: input["metricId"]
15785
- });
15786
- const _ve1 = 'Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>';
15787
- const _ve2 = 'ReadonlyArray<Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>>';
15788
- const _ve3 = "(ReadonlyArray<Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>> | undefined)";
15789
- const _vp1 = (input, _path, _exceptionable = true) => typeof input["id"] === "string" || _report(_exceptionable, {
15790
- path: _path + ".id",
15791
- expected: "string",
15792
- value: input["id"]
15793
- });
15794
- const _ve4 = '(Readonly<{ kind: "bool"; }> | Readonly<{ kind: "choice"; values: readonly ExperimentScalar[]; }> | Readonly<{ kind: "int"; min: number; max: number; }> | Readonly<{ kind: "number"; min: number; max: number; scale?: "linear" | "log" | undefined; }>)';
15795
- const _vp2 = (input, _path, _exceptionable = true) => typeof input["min"] === "number" || _report(_exceptionable, {
15796
- path: _path + ".min",
15797
- expected: "number",
15798
- value: input["min"]
15799
- });
15800
- const _vp3 = (input, _path, _exceptionable = true) => typeof input["max"] === "number" || _report(_exceptionable, {
15801
- path: _path + ".max",
15802
- expected: "number",
15803
- value: input["max"]
15804
- });
15805
- const _ve5 = '(Readonly<{ kind: "bool"; }> | Readonly<{ kind: "int"; min: number; max: number; }> | Readonly<{ kind: "number"; min: number; max: number; scale?: "linear" | "log" | undefined; }> | Readonly<{ kind: "choice"; values: readonly ExperimentScalar[]; }>)';
15806
- const _io0 = (input) => input.schemaVersion === 1 && (typeof input.scenario === "object" && input.scenario !== null && _io1(input.scenario)) && (typeof input.model === "object" && input.model !== null && _io2(input.model)) && (input.strategy === undefined || typeof input.strategy === "object" && input.strategy !== null && _io3(input.strategy)) && _ip0(input) && (input.goal === undefined || typeof input.goal === "object" && input.goal !== null && _io4(input.goal)) && (input.objectives === undefined || Array.isArray(input.objectives) && input.objectives.every((elem) => typeof elem === "object" && elem !== null && _io5(elem))) && (input.constraints === undefined || Array.isArray(input.constraints) && input.constraints.every((elem) => typeof elem === "object" && elem !== null && _io6(elem))) && (Array.isArray(input.dimensions) && input.dimensions.every((elem) => typeof elem === "object" && elem !== null && _io7(elem))) && (input.candidates === undefined || Array.isArray(input.candidates) && input.candidates.every((elem) => typeof elem === "object" && elem !== null && _io12(elem))) && (typeof input.budget === "object" && input.budget !== null && _io14(input.budget)) && (Array.isArray(input.seeds) && input.seeds.every((elem) => typeof elem === "number")) && typeof input.horizonSec === "number" && typeof input.stepSec === "number" && (typeof input.session === "object" && input.session !== null && _io15(input.session));
15807
- const _io1 = (input) => typeof input.ref === "string" && typeof input.hash === "string";
15808
- const _io10 = (input) => input.kind === "number" && _ip2(input) && _ip3(input) && (input.scale === undefined || input.scale === "linear" || input.scale === "log");
15809
- const _io11 = (input) => input.kind === "choice" && (Array.isArray(input.values) && input.values.every((elem) => elem === null || typeof elem === "string" || typeof elem === "number" || typeof elem === "boolean"));
15810
- const _io12 = (input) => _ip1(input) && (Array.isArray(input.assignments) && input.assignments.every((elem) => typeof elem === "object" && elem !== null && _io13(elem)));
15811
- const _io13 = (input) => typeof input.dimensionId === "string" && (input.value === null || typeof input.value === "string" || typeof input.value === "number" || typeof input.value === "boolean");
15812
- const _io14 = (input) => typeof input.candidates === "number" && typeof input.seeds === "number";
15813
- const _io15 = (input) => typeof input.pattern === "string" && typeof input.days === "number";
15814
- const _io2 = (input) => _ip1(input) && typeof input.version === "number";
15815
- const _io3 = (input) => _ip1(input);
15816
- const _io4 = (input) => typeof input.target === "number";
15817
- const _io5 = (input) => _ip1(input) && _ip0(input) && (input.sense === "higher" || input.sense === "lower");
15818
- const _io6 = (input) => _ip1(input) && _ip0(input) && (input.op === "gte" || input.op === "lte") && typeof input.limit === "number";
15819
- const _io7 = (input) => _ip1(input) && (input.target === "model" || input.target === "session" || input.target === "strategy") && typeof input.path === "string" && (typeof input.space === "object" && input.space !== null && _iu0(input.space)) && (input.baseline === null || typeof input.baseline === "string" || typeof input.baseline === "number" || typeof input.baseline === "boolean") && (input.probes === undefined || Array.isArray(input.probes) && input.probes.every((elem) => elem === null || typeof elem === "string" || typeof elem === "number" || typeof elem === "boolean"));
15820
- const _io8 = (input) => input.kind === "bool";
15821
- const _io9 = (input) => input.kind === "int" && _ip2(input) && _ip3(input);
15822
- const _iu0 = (input) => (() => {
15823
- if (input.kind === "bool")
15824
- return _io8(input);
15825
- else if (input.kind === "int")
15826
- return _io9(input);
15827
- else if (input.kind === "number")
15828
- return _io10(input);
15829
- else if (input.kind === "choice")
15830
- return _io11(input);
15831
- else
15832
- return false;
15833
- })();
15834
- const _vo0 = (input, _path, _exceptionable = true) => [input.schemaVersion === 1 || _report(_exceptionable, {
16011
+ const _ip0 = (input) => input["schemaVersion"] === 1;
16012
+ const _ip1 = (input) => typeof input["scenario"] === "object" && input["scenario"] !== null && _io1(input["scenario"]);
16013
+ const _ip2 = (input) => typeof input["model"] === "object" && input["model"] !== null && _io2(input["model"]);
16014
+ const _ip3 = (input) => input["strategy"] === undefined || typeof input["strategy"] === "object" && input["strategy"] !== null && _io3(input["strategy"]);
16015
+ const _ip4 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
16016
+ const _ip5 = (input) => input["goal"] === undefined || typeof input["goal"] === "object" && input["goal"] !== null && _io4(input["goal"]);
16017
+ const _ip6 = (input) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string";
16018
+ const _ip7 = (input) => input["objectives"] === undefined || Array.isArray(input["objectives"]) && input["objectives"].every((elem) => typeof elem === "object" && elem !== null && _io5(elem));
16019
+ const _ip8 = (input) => input["constraints"] === undefined || Array.isArray(input["constraints"]) && input["constraints"].every((elem) => typeof elem === "object" && elem !== null && _io6(elem));
16020
+ const _ip9 = (input) => Array.isArray(input["dimensions"]) && input["dimensions"].every((elem) => typeof elem === "object" && elem !== null && _io7(elem));
16021
+ const _ip10 = (input) => input["candidates"] === undefined || Array.isArray(input["candidates"]) && input["candidates"].every((elem) => typeof elem === "object" && elem !== null && _io12(elem));
16022
+ const _ip11 = (input) => typeof input["budget"] === "object" && input["budget"] !== null && _io14(input["budget"]);
16023
+ const _ip12 = (input) => Array.isArray(input["seeds"]) && input["seeds"].every((elem) => typeof elem === "number");
16024
+ const _ip13 = (input) => typeof input["horizonSec"] === "number";
16025
+ const _ip14 = (input) => typeof input["stepSec"] === "number";
16026
+ const _ip15 = (input) => typeof input["session"] === "object" && input["session"] !== null && _io15(input["session"]);
16027
+ const _ip16 = (input) => typeof input["id"] === "string";
16028
+ const _ip17 = (input) => typeof input["min"] === "number";
16029
+ const _ip18 = (input) => typeof input["max"] === "number";
16030
+ const _ve0 = "Readonly<{ schemaVersion: 1; scenario: Readonly<{ ref: string; hash: string; }>; model: Readonly<{ id: string; version: number; }>; strategy?: Readonly<{ id: string; }> | undefined; ... 11 more ...; session: Readonly<...>; }>";
16031
+ const _vp0 = (input, _path, _exceptionable = true) => input["schemaVersion"] === 1 || _report(_exceptionable, {
15835
16032
  path: _path + ".schemaVersion",
15836
16033
  expected: "1",
15837
- value: input.schemaVersion
15838
- }), (typeof input.scenario === "object" && input.scenario !== null || _report(_exceptionable, {
16034
+ value: input["schemaVersion"]
16035
+ });
16036
+ const _vp1 = (input, _path, _exceptionable = true) => (typeof input["scenario"] === "object" && input["scenario"] !== null || _report(_exceptionable, {
15839
16037
  path: _path + ".scenario",
15840
16038
  expected: "Readonly<{ ref: string; hash: string; }>",
15841
- value: input.scenario
15842
- })) && _vo1(input.scenario, _path + ".scenario", _exceptionable) || _report(_exceptionable, {
16039
+ value: input["scenario"]
16040
+ })) && _vo1(input["scenario"], _path + ".scenario", _exceptionable) || _report(_exceptionable, {
15843
16041
  path: _path + ".scenario",
15844
16042
  expected: "Readonly<{ ref: string; hash: string; }>",
15845
- value: input.scenario
15846
- }), (typeof input.model === "object" && input.model !== null || _report(_exceptionable, {
16043
+ value: input["scenario"]
16044
+ });
16045
+ const _vp2 = (input, _path, _exceptionable = true) => (typeof input["model"] === "object" && input["model"] !== null || _report(_exceptionable, {
15847
16046
  path: _path + ".model",
15848
16047
  expected: "Readonly<{ id: string; version: number; }>",
15849
- value: input.model
15850
- })) && _vo2(input.model, _path + ".model", _exceptionable) || _report(_exceptionable, {
16048
+ value: input["model"]
16049
+ })) && _vo2(input["model"], _path + ".model", _exceptionable) || _report(_exceptionable, {
15851
16050
  path: _path + ".model",
15852
16051
  expected: "Readonly<{ id: string; version: number; }>",
15853
- value: input.model
15854
- }), input.strategy === undefined || (typeof input.strategy === "object" && input.strategy !== null || _report(_exceptionable, {
16052
+ value: input["model"]
16053
+ });
16054
+ const _vp3 = (input, _path, _exceptionable = true) => input["strategy"] === undefined || (typeof input["strategy"] === "object" && input["strategy"] !== null || _report(_exceptionable, {
15855
16055
  path: _path + ".strategy",
15856
16056
  expected: "(Readonly<{ id: string; }> | undefined)",
15857
- value: input.strategy
15858
- })) && _vo3(input.strategy, _path + ".strategy", _exceptionable) || _report(_exceptionable, {
16057
+ value: input["strategy"]
16058
+ })) && _vo3(input["strategy"], _path + ".strategy", _exceptionable) || _report(_exceptionable, {
15859
16059
  path: _path + ".strategy",
15860
16060
  expected: "(Readonly<{ id: string; }> | undefined)",
15861
- value: input.strategy
15862
- }), _vp0(input, _path, _exceptionable), input.goal === undefined || (typeof input.goal === "object" && input.goal !== null || _report(_exceptionable, {
16061
+ value: input["strategy"]
16062
+ });
16063
+ const _vp4 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
16064
+ path: _path + ".metricId",
16065
+ expected: '("endMoney" | "timeToMilestone" | "timeToMoney" | "varBenefit")',
16066
+ value: input["metricId"]
16067
+ });
16068
+ const _vp5 = (input, _path, _exceptionable = true) => input["goal"] === undefined || (typeof input["goal"] === "object" && input["goal"] !== null || _report(_exceptionable, {
15863
16069
  path: _path + ".goal",
15864
16070
  expected: "(Readonly<{ target: number; }> | undefined)",
15865
- value: input.goal
15866
- })) && _vo4(input.goal, _path + ".goal", _exceptionable) || _report(_exceptionable, {
16071
+ value: input["goal"]
16072
+ })) && _vo4(input["goal"], _path + ".goal", _exceptionable) || _report(_exceptionable, {
15867
16073
  path: _path + ".goal",
15868
16074
  expected: "(Readonly<{ target: number; }> | undefined)",
15869
- value: input.goal
15870
- }), input.objectives === undefined || (Array.isArray(input.objectives) || _report(_exceptionable, {
16075
+ value: input["goal"]
16076
+ });
16077
+ const _vp6 = (input, _path, _exceptionable = true) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string" || _report(_exceptionable, {
16078
+ path: _path + ".milestoneKey",
16079
+ expected: "(string | undefined)",
16080
+ value: input["milestoneKey"]
16081
+ });
16082
+ const _vp7 = (input, _path, _exceptionable = true) => input["objectives"] === undefined || (Array.isArray(input["objectives"]) || _report(_exceptionable, {
15871
16083
  path: _path + ".objectives",
15872
16084
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; sense: "higher" | "lower"; }>> | undefined)',
15873
- value: input.objectives
15874
- })) && input.objectives.map((elem, _index9) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
16085
+ value: input["objectives"]
16086
+ })) && input["objectives"].map((elem, _index9) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15875
16087
  path: _path + ".objectives[" + _index9 + "]",
15876
16088
  expected: 'Readonly<{ id: string; metricId: ExperimentMetricId; sense: "higher" | "lower"; }>',
15877
16089
  value: elem
@@ -15882,12 +16094,13 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15882
16094
  })).every((flag) => flag) || _report(_exceptionable, {
15883
16095
  path: _path + ".objectives",
15884
16096
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; sense: "higher" | "lower"; }>> | undefined)',
15885
- value: input.objectives
15886
- }), input.constraints === undefined || (Array.isArray(input.constraints) || _report(_exceptionable, {
16097
+ value: input["objectives"]
16098
+ });
16099
+ const _vp8 = (input, _path, _exceptionable = true) => input["constraints"] === undefined || (Array.isArray(input["constraints"]) || _report(_exceptionable, {
15887
16100
  path: _path + ".constraints",
15888
16101
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; op: "gte" | "lte"; limit: number; }>> | undefined)',
15889
- value: input.constraints
15890
- })) && input.constraints.map((elem, _index10) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
16102
+ value: input["constraints"]
16103
+ })) && input["constraints"].map((elem, _index10) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15891
16104
  path: _path + ".constraints[" + _index10 + "]",
15892
16105
  expected: 'Readonly<{ id: string; metricId: ExperimentMetricId; op: "gte" | "lte"; limit: number; }>',
15893
16106
  value: elem
@@ -15898,12 +16111,13 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15898
16111
  })).every((flag) => flag) || _report(_exceptionable, {
15899
16112
  path: _path + ".constraints",
15900
16113
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; op: "gte" | "lte"; limit: number; }>> | undefined)',
15901
- value: input.constraints
15902
- }), (Array.isArray(input.dimensions) || _report(_exceptionable, {
16114
+ value: input["constraints"]
16115
+ });
16116
+ const _vp9 = (input, _path, _exceptionable = true) => (Array.isArray(input["dimensions"]) || _report(_exceptionable, {
15903
16117
  path: _path + ".dimensions",
15904
16118
  expected: _ve2,
15905
- value: input.dimensions
15906
- })) && input.dimensions.map((elem, _index11) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
16119
+ value: input["dimensions"]
16120
+ })) && input["dimensions"].map((elem, _index11) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15907
16121
  path: _path + ".dimensions[" + _index11 + "]",
15908
16122
  expected: _ve1,
15909
16123
  value: elem
@@ -15914,12 +16128,15 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15914
16128
  })).every((flag) => flag) || _report(_exceptionable, {
15915
16129
  path: _path + ".dimensions",
15916
16130
  expected: _ve2,
15917
- value: input.dimensions
15918
- }), input.candidates === undefined || (Array.isArray(input.candidates) || _report(_exceptionable, {
16131
+ value: input["dimensions"]
16132
+ });
16133
+ const _ve1 = 'Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>';
16134
+ const _ve2 = 'ReadonlyArray<Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>>';
16135
+ const _vp10 = (input, _path, _exceptionable = true) => input["candidates"] === undefined || (Array.isArray(input["candidates"]) || _report(_exceptionable, {
15919
16136
  path: _path + ".candidates",
15920
16137
  expected: _ve3,
15921
- value: input.candidates
15922
- })) && input.candidates.map((elem, _index12) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
16138
+ value: input["candidates"]
16139
+ })) && input["candidates"].map((elem, _index12) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15923
16140
  path: _path + ".candidates[" + _index12 + "]",
15924
16141
  expected: "Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>",
15925
16142
  value: elem
@@ -15930,44 +16147,96 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15930
16147
  })).every((flag) => flag) || _report(_exceptionable, {
15931
16148
  path: _path + ".candidates",
15932
16149
  expected: _ve3,
15933
- value: input.candidates
15934
- }), (typeof input.budget === "object" && input.budget !== null || _report(_exceptionable, {
16150
+ value: input["candidates"]
16151
+ });
16152
+ const _ve3 = "(ReadonlyArray<Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>> | undefined)";
16153
+ const _vp11 = (input, _path, _exceptionable = true) => (typeof input["budget"] === "object" && input["budget"] !== null || _report(_exceptionable, {
15935
16154
  path: _path + ".budget",
15936
16155
  expected: "Readonly<{ candidates: number; seeds: number; }>",
15937
- value: input.budget
15938
- })) && _vo14(input.budget, _path + ".budget", _exceptionable) || _report(_exceptionable, {
16156
+ value: input["budget"]
16157
+ })) && _vo14(input["budget"], _path + ".budget", _exceptionable) || _report(_exceptionable, {
15939
16158
  path: _path + ".budget",
15940
16159
  expected: "Readonly<{ candidates: number; seeds: number; }>",
15941
- value: input.budget
15942
- }), (Array.isArray(input.seeds) || _report(_exceptionable, {
16160
+ value: input["budget"]
16161
+ });
16162
+ const _vp12 = (input, _path, _exceptionable = true) => (Array.isArray(input["seeds"]) || _report(_exceptionable, {
15943
16163
  path: _path + ".seeds",
15944
16164
  expected: "ReadonlyArray<number>",
15945
- value: input.seeds
15946
- })) && input.seeds.map((elem, _index13) => typeof elem === "number" || _report(_exceptionable, {
16165
+ value: input["seeds"]
16166
+ })) && input["seeds"].map((elem, _index13) => typeof elem === "number" || _report(_exceptionable, {
15947
16167
  path: _path + ".seeds[" + _index13 + "]",
15948
16168
  expected: "number",
15949
16169
  value: elem
15950
16170
  })).every((flag) => flag) || _report(_exceptionable, {
15951
16171
  path: _path + ".seeds",
15952
16172
  expected: "ReadonlyArray<number>",
15953
- value: input.seeds
15954
- }), typeof input.horizonSec === "number" || _report(_exceptionable, {
16173
+ value: input["seeds"]
16174
+ });
16175
+ const _vp13 = (input, _path, _exceptionable = true) => typeof input["horizonSec"] === "number" || _report(_exceptionable, {
15955
16176
  path: _path + ".horizonSec",
15956
16177
  expected: "number",
15957
- value: input.horizonSec
15958
- }), typeof input.stepSec === "number" || _report(_exceptionable, {
16178
+ value: input["horizonSec"]
16179
+ });
16180
+ const _vp14 = (input, _path, _exceptionable = true) => typeof input["stepSec"] === "number" || _report(_exceptionable, {
15959
16181
  path: _path + ".stepSec",
15960
16182
  expected: "number",
15961
- value: input.stepSec
15962
- }), (typeof input.session === "object" && input.session !== null || _report(_exceptionable, {
16183
+ value: input["stepSec"]
16184
+ });
16185
+ const _vp15 = (input, _path, _exceptionable = true) => (typeof input["session"] === "object" && input["session"] !== null || _report(_exceptionable, {
15963
16186
  path: _path + ".session",
15964
16187
  expected: "Readonly<{ pattern: string; days: number; }>",
15965
- value: input.session
15966
- })) && _vo15(input.session, _path + ".session", _exceptionable) || _report(_exceptionable, {
16188
+ value: input["session"]
16189
+ })) && _vo15(input["session"], _path + ".session", _exceptionable) || _report(_exceptionable, {
15967
16190
  path: _path + ".session",
15968
16191
  expected: "Readonly<{ pattern: string; days: number; }>",
15969
- value: input.session
15970
- })].every((flag) => flag);
16192
+ value: input["session"]
16193
+ });
16194
+ const _vp16 = (input, _path, _exceptionable = true) => typeof input["id"] === "string" || _report(_exceptionable, {
16195
+ path: _path + ".id",
16196
+ expected: "string",
16197
+ value: input["id"]
16198
+ });
16199
+ const _ve4 = '(Readonly<{ kind: "bool"; }> | Readonly<{ kind: "choice"; values: readonly ExperimentScalar[]; }> | Readonly<{ kind: "int"; min: number; max: number; }> | Readonly<{ kind: "number"; min: number; max: number; scale?: "linear" | "log" | undefined; }>)';
16200
+ const _vp17 = (input, _path, _exceptionable = true) => typeof input["min"] === "number" || _report(_exceptionable, {
16201
+ path: _path + ".min",
16202
+ expected: "number",
16203
+ value: input["min"]
16204
+ });
16205
+ const _vp18 = (input, _path, _exceptionable = true) => typeof input["max"] === "number" || _report(_exceptionable, {
16206
+ path: _path + ".max",
16207
+ expected: "number",
16208
+ value: input["max"]
16209
+ });
16210
+ const _ve5 = '(Readonly<{ kind: "bool"; }> | Readonly<{ kind: "int"; min: number; max: number; }> | Readonly<{ kind: "number"; min: number; max: number; scale?: "linear" | "log" | undefined; }> | Readonly<{ kind: "choice"; values: readonly ExperimentScalar[]; }>)';
16211
+ const _io0 = (input) => _ip0(input) && _ip1(input) && _ip2(input) && _ip3(input) && _ip4(input) && _ip5(input) && _ip6(input) && _ip7(input) && _ip8(input) && _ip9(input) && _ip10(input) && _ip11(input) && _ip12(input) && _ip13(input) && _ip14(input) && _ip15(input);
16212
+ const _io1 = (input) => typeof input.ref === "string" && typeof input.hash === "string";
16213
+ const _io10 = (input) => input.kind === "number" && _ip17(input) && _ip18(input) && (input.scale === undefined || input.scale === "linear" || input.scale === "log");
16214
+ const _io11 = (input) => input.kind === "choice" && (Array.isArray(input.values) && input.values.every((elem) => elem === null || typeof elem === "string" || typeof elem === "number" || typeof elem === "boolean"));
16215
+ const _io12 = (input) => _ip16(input) && (Array.isArray(input.assignments) && input.assignments.every((elem) => typeof elem === "object" && elem !== null && _io13(elem)));
16216
+ const _io13 = (input) => typeof input.dimensionId === "string" && (input.value === null || typeof input.value === "string" || typeof input.value === "number" || typeof input.value === "boolean");
16217
+ const _io14 = (input) => typeof input.candidates === "number" && typeof input.seeds === "number";
16218
+ const _io15 = (input) => typeof input.pattern === "string" && typeof input.days === "number";
16219
+ const _io2 = (input) => _ip16(input) && typeof input.version === "number";
16220
+ const _io3 = (input) => _ip16(input);
16221
+ const _io4 = (input) => typeof input.target === "number";
16222
+ const _io5 = (input) => _ip16(input) && _ip4(input) && (input.sense === "higher" || input.sense === "lower");
16223
+ const _io6 = (input) => _ip16(input) && _ip4(input) && (input.op === "gte" || input.op === "lte") && typeof input.limit === "number";
16224
+ const _io7 = (input) => _ip16(input) && (input.target === "model" || input.target === "session" || input.target === "strategy") && typeof input.path === "string" && (typeof input.space === "object" && input.space !== null && _iu0(input.space)) && (input.baseline === null || typeof input.baseline === "string" || typeof input.baseline === "number" || typeof input.baseline === "boolean") && (input.probes === undefined || Array.isArray(input.probes) && input.probes.every((elem) => elem === null || typeof elem === "string" || typeof elem === "number" || typeof elem === "boolean"));
16225
+ const _io8 = (input) => input.kind === "bool";
16226
+ const _io9 = (input) => input.kind === "int" && _ip17(input) && _ip18(input);
16227
+ const _iu0 = (input) => (() => {
16228
+ if (input.kind === "bool")
16229
+ return _io8(input);
16230
+ else if (input.kind === "int")
16231
+ return _io9(input);
16232
+ else if (input.kind === "number")
16233
+ return _io10(input);
16234
+ else if (input.kind === "choice")
16235
+ return _io11(input);
16236
+ else
16237
+ return false;
16238
+ })();
16239
+ const _vo0 = (input, _path, _exceptionable = true) => [_vp0(input, _path, _exceptionable), _vp1(input, _path, _exceptionable), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), _vp5(input, _path, _exceptionable), _vp6(input, _path, _exceptionable), _vp7(input, _path, _exceptionable), _vp8(input, _path, _exceptionable), _vp9(input, _path, _exceptionable), _vp10(input, _path, _exceptionable), _vp11(input, _path, _exceptionable), _vp12(input, _path, _exceptionable), _vp13(input, _path, _exceptionable), _vp14(input, _path, _exceptionable), _vp15(input, _path, _exceptionable)].every((flag) => flag);
15971
16240
  const _vo1 = (input, _path, _exceptionable = true) => [typeof input.ref === "string" || _report(_exceptionable, {
15972
16241
  path: _path + ".ref",
15973
16242
  expected: "string",
@@ -15981,7 +16250,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15981
16250
  path: _path + ".kind",
15982
16251
  expected: '"number"',
15983
16252
  value: input.kind
15984
- }), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable), input.scale === undefined || input.scale === "linear" || input.scale === "log" || _report(_exceptionable, {
16253
+ }), _vp17(input, _path, _exceptionable), _vp18(input, _path, _exceptionable), input.scale === undefined || input.scale === "linear" || input.scale === "log" || _report(_exceptionable, {
15985
16254
  path: _path + ".scale",
15986
16255
  expected: '("linear" | "log" | undefined)',
15987
16256
  value: input.scale
@@ -16003,7 +16272,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
16003
16272
  expected: "ReadonlyArray<ExperimentScalar>",
16004
16273
  value: input.values
16005
16274
  })].every((flag) => flag);
16006
- const _vo12 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), (Array.isArray(input.assignments) || _report(_exceptionable, {
16275
+ const _vo12 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), (Array.isArray(input.assignments) || _report(_exceptionable, {
16007
16276
  path: _path + ".assignments",
16008
16277
  expected: "ReadonlyArray<Readonly<{ dimensionId: string; value: ExperimentScalar; }>>",
16009
16278
  value: input.assignments
@@ -16047,23 +16316,23 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
16047
16316
  expected: "number",
16048
16317
  value: input.days
16049
16318
  })].every((flag) => flag);
16050
- const _vo2 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), typeof input.version === "number" || _report(_exceptionable, {
16319
+ const _vo2 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), typeof input.version === "number" || _report(_exceptionable, {
16051
16320
  path: _path + ".version",
16052
16321
  expected: "number",
16053
16322
  value: input.version
16054
16323
  })].every((flag) => flag);
16055
- const _vo3 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable)].every((flag) => flag);
16324
+ const _vo3 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable)].every((flag) => flag);
16056
16325
  const _vo4 = (input, _path, _exceptionable = true) => [typeof input.target === "number" || _report(_exceptionable, {
16057
16326
  path: _path + ".target",
16058
16327
  expected: "number",
16059
16328
  value: input.target
16060
16329
  })].every((flag) => flag);
16061
- const _vo5 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), _vp0(input, _path, _exceptionable), input.sense === "higher" || input.sense === "lower" || _report(_exceptionable, {
16330
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), input.sense === "higher" || input.sense === "lower" || _report(_exceptionable, {
16062
16331
  path: _path + ".sense",
16063
16332
  expected: '("higher" | "lower")',
16064
16333
  value: input.sense
16065
16334
  })].every((flag) => flag);
16066
- const _vo6 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), _vp0(input, _path, _exceptionable), input.op === "gte" || input.op === "lte" || _report(_exceptionable, {
16335
+ const _vo6 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), input.op === "gte" || input.op === "lte" || _report(_exceptionable, {
16067
16336
  path: _path + ".op",
16068
16337
  expected: '("gte" | "lte")',
16069
16338
  value: input.op
@@ -16072,7 +16341,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
16072
16341
  expected: "number",
16073
16342
  value: input.limit
16074
16343
  })].every((flag) => flag);
16075
- const _vo7 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), input.target === "model" || input.target === "session" || input.target === "strategy" || _report(_exceptionable, {
16344
+ const _vo7 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), input.target === "model" || input.target === "session" || input.target === "strategy" || _report(_exceptionable, {
16076
16345
  path: _path + ".target",
16077
16346
  expected: '("model" | "session" | "strategy")',
16078
16347
  value: input.target
@@ -16114,7 +16383,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
16114
16383
  path: _path + ".kind",
16115
16384
  expected: '"int"',
16116
16385
  value: input.kind
16117
- }), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable)].every((flag) => flag);
16386
+ }), _vp17(input, _path, _exceptionable), _vp18(input, _path, _exceptionable)].every((flag) => flag);
16118
16387
  const _vu0 = (input, _path, _exceptionable = true) => (() => {
16119
16388
  if (input.kind === "bool")
16120
16389
  return _vo8(input, _path, _exceptionable);
@@ -18414,7 +18683,7 @@ var init_checks3 = __esm(() => {
18414
18683
  FAIL_ON = ["error", "warn", "inconclusive", "unsupported"];
18415
18684
  });
18416
18685
 
18417
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
18686
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
18418
18687
  var _accessExpressionAsString = (str) => variable(str) ? `.${str}` : `[${JSON.stringify(str)}]`, variable = (str) => reserved(str) === false && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/g.test(str), reserved = (str) => RESERVED.has(str), RESERVED;
18419
18688
  var init__accessExpressionAsString = __esm(() => {
18420
18689
  RESERVED = /* @__PURE__ */ new Set([
@@ -18478,6 +18747,9 @@ function nonfiniteThresholds(rubric) {
18478
18747
  if (rule.upperThreshold !== null && !Number.isFinite(rule.upperThreshold)) {
18479
18748
  issues.push(`$input.rules[${index}].upperThreshold finite number or null`);
18480
18749
  }
18750
+ if (rule.metricVersion !== undefined && rule.metricVersion !== null && !Number.isFinite(rule.metricVersion)) {
18751
+ issues.push(`$input.rules[${index}].metricVersion finite number or null`);
18752
+ }
18481
18753
  });
18482
18754
  return issues;
18483
18755
  }
@@ -18503,8 +18775,16 @@ function evaluateGroups(failOn, groups, checks) {
18503
18775
  const pass = reports.every((report) => report.pass);
18504
18776
  return { ...first, results, pass, exitCode: pass ? 0 : 2 };
18505
18777
  }
18506
- function findReadings(artifact, metricId) {
18507
- return artifact.metrics.filter((reading) => reading.metricId === metricId);
18778
+ function ruleReadings(artifact, rule) {
18779
+ const pinned = rule.metricVersion ?? null;
18780
+ return artifact.metrics.filter((reading) => reading.metricId === rule.metricId && (pinned === null || reading.metricVersion === pinned));
18781
+ }
18782
+ function metricVersionRef(version) {
18783
+ return `metric-version:${version}`;
18784
+ }
18785
+ function ruleRefLabel(rule) {
18786
+ const pinned = rule.metricVersion ?? null;
18787
+ return pinned === null ? `metric:${rule.metricId}` : `metric:${rule.metricId} ${metricVersionRef(pinned)}`;
18508
18788
  }
18509
18789
  function isUnavailable(reading) {
18510
18790
  return reading.reading === "unavailable" || reading.status !== "ok";
@@ -18671,27 +18951,40 @@ function judgeArtifact(artifact, rubric) {
18671
18951
  const issues = [];
18672
18952
  const groups = new Map;
18673
18953
  const estimatedObservations = [];
18954
+ const estimatedReadings = new Set;
18674
18955
  const prepared = [];
18675
18956
  for (const rule of rubric.rules) {
18676
- const readings = findReadings(artifact, rule.metricId);
18957
+ const label = ruleRefLabel(rule);
18958
+ const readings = ruleReadings(artifact, rule);
18677
18959
  const reading = readings[0];
18678
18960
  if (!reading) {
18679
- issues.push(`missing evidenceRef metric:${rule.metricId}`);
18961
+ const others = artifact.metrics.filter((item) => item.metricId === rule.metricId).map((item) => metricVersionRef(item.metricVersion));
18962
+ issues.push(others.length > 0 ? `missing evidenceRef ${label} (artifact has ${others.join(", ")})` : `missing evidenceRef ${label}`);
18680
18963
  continue;
18681
18964
  }
18682
18965
  if (readings.length > 1) {
18683
- issues.push(`ambiguous evidenceRef metric:${rule.metricId} matches ${readings.length} readings`);
18966
+ issues.push(`ambiguous evidenceRef ${label} matches ${readings.length} readings`);
18684
18967
  continue;
18685
18968
  }
18686
18969
  const mapped = toObservation(artifact, reading, rule);
18687
- const key = JSON.stringify([reading.metricId, mapped.observation.identity.method, mapped.observation.identity.unit]);
18970
+ const key = JSON.stringify([
18971
+ reading.metricId,
18972
+ reading.metricVersion,
18973
+ mapped.observation.identity.method,
18974
+ mapped.observation.identity.unit
18975
+ ]);
18688
18976
  const group = groups.get(key) ?? { observation: mapped.observation, indices: [] };
18689
18977
  group.indices.push(prepared.length);
18690
18978
  groups.set(key, group);
18691
- if (mapped.estimated && !estimatedObservations.some((item) => item.metricId === reading.metricId)) {
18979
+ if (mapped.estimated && !estimatedReadings.has(reading)) {
18980
+ estimatedReadings.add(reading);
18692
18981
  estimatedObservations.push(mapped.estimated);
18693
18982
  }
18694
- prepared.push({ rule, estimated: mapped.estimated, ref: `metric:${rule.metricId}` });
18983
+ prepared.push({
18984
+ rule,
18985
+ estimated: mapped.estimated,
18986
+ refs: [`metric:${rule.metricId}`, metricVersionRef(reading.metricVersion)]
18987
+ });
18695
18988
  }
18696
18989
  if (issues.length > 0)
18697
18990
  return { ok: false, issues };
@@ -18704,7 +18997,7 @@ function judgeArtifact(artifact, rubric) {
18704
18997
  id: item.rule.id,
18705
18998
  category: outcome.category,
18706
18999
  severity: outcome.severity,
18707
- evidenceRefs: [item.ref],
19000
+ evidenceRefs: item.refs,
18708
19001
  assumptionRefs,
18709
19002
  recommendationId: outcome.recommendationId,
18710
19003
  status: outcome.status,
@@ -18728,9 +19021,9 @@ function judgeArtifact(artifact, rubric) {
18728
19021
  return { ok: true, report };
18729
19022
  }
18730
19023
  var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validateRubric = (input) => (validateRubricValidator ??= (() => {
18731
- const _ve0 = 'Readonly<{ schemaVersion: 1; contract: "idlekit.judge.rubric.v1"; rubricVersion: string; failOn: readonly FailOnToken[]; rules: readonly Readonly<{ id: string; metricId: string; operator: CheckOperator; ... 13 more ...; recommendationId: RecommendationId; }>[]; }>';
18732
- const _ve1 = "Readonly<{ id: string; metricId: string; operator: CheckOperator; threshold: number; upperThreshold: number | null; unit: string; severity: CheckSeverity; missingPolicy: MissingPolicy; ... 8 more ...; recommendationId: RecommendationId; }>";
18733
- const _ve2 = "ReadonlyArray<Readonly<{ id: string; metricId: string; operator: CheckOperator; threshold: number; upperThreshold: number | null; unit: string; severity: CheckSeverity; missingPolicy: MissingPolicy; ... 8 more ...; recommendationId: RecommendationId; }>>";
19024
+ const _ve0 = 'Readonly<{ schemaVersion: 1; contract: "idlekit.judge.rubric.v1"; rubricVersion: string; failOn: readonly FailOnToken[]; rules: readonly Readonly<{ id: string; metricId: string; metricVersion?: number | null | undefined; ... 14 more ...; recommendationId: RecommendationId; }>[]; }>';
19025
+ const _ve1 = "Readonly<{ id: string; metricId: string; metricVersion?: number | null | undefined; operator: CheckOperator; threshold: number; upperThreshold: number | null; unit: string; severity: CheckSeverity; ... 9 more ...; recommendationId: RecommendationId; }>";
19026
+ const _ve2 = "ReadonlyArray<Readonly<{ id: string; metricId: string; metricVersion?: number | null | undefined; operator: CheckOperator; threshold: number; upperThreshold: number | null; unit: string; severity: CheckSeverity; ... 9 more ...; recommendationId: RecommendationId; }>>";
18734
19027
  const _ve3 = '("rec.metric.estimate-not-measured" | "rec.metric.identity-mismatch" | "rec.metric.insufficient" | "rec.metric.outside-threshold" | "rec.metric.unsupported" | "rec.metric.within-threshold")';
18735
19028
  const _io0 = (input, _exceptionable = true) => input.schemaVersion === 1 && input.contract === "idlekit.judge.rubric.v1" && typeof input.rubricVersion === "string" && (Array.isArray(input.failOn) && input.failOn.every((elem, _index1) => elem === "error" || elem === "inconclusive" || elem === "unsupported" || elem === "warn")) && (Array.isArray(input.rules) && input.rules.every((elem, _index2) => typeof elem === "object" && elem !== null && _io1(elem, _exceptionable))) && (Object.keys(input).length === 5 || Object.keys(input).every((key) => {
18736
19029
  if (["schemaVersion", "contract", "rubricVersion", "failOn", "rules"].some((prop) => key === prop))
@@ -18740,8 +19033,8 @@ var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validate
18740
19033
  return true;
18741
19034
  return false;
18742
19035
  }));
18743
- const _io1 = (input, _exceptionable = true) => typeof input.id === "string" && typeof input.metricId === "string" && (input.operator === "between" || input.operator === "eq" || input.operator === "gt" || input.operator === "gte" || input.operator === "lt" || input.operator === "lte") && typeof input.threshold === "number" && (input.upperThreshold === null || typeof input.upperThreshold === "number") && typeof input.unit === "string" && (input.severity === "error" || input.severity === "warn") && (input.missingPolicy === "fail" || input.missingPolicy === "inconclusive" || input.missingPolicy === "unsupported") && typeof input.definition === "string" && typeof input.method === "string" && (input.scenarioVersion === null || typeof input.scenarioVersion === "string") && (input.specVersion === null || typeof input.specVersion === "string") && (input.engineId === null || typeof input.engineId === "string") && (input.pluginVersion === null || typeof input.pluginVersion === "string") && (input.seed === null || typeof input.seed === "string") && (input.algorithmVersion === null || typeof input.algorithmVersion === "string") && (input.recommendationId === "rec.metric.estimate-not-measured" || input.recommendationId === "rec.metric.identity-mismatch" || input.recommendationId === "rec.metric.insufficient" || input.recommendationId === "rec.metric.outside-threshold" || input.recommendationId === "rec.metric.unsupported" || input.recommendationId === "rec.metric.within-threshold") && (Object.keys(input).length === 17 || Object.keys(input).every((key) => {
18744
- if (["id", "metricId", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
19036
+ const _io1 = (input, _exceptionable = true) => typeof input.id === "string" && typeof input.metricId === "string" && (input.metricVersion === null || input.metricVersion === undefined || typeof input.metricVersion === "number") && (input.operator === "between" || input.operator === "eq" || input.operator === "gt" || input.operator === "gte" || input.operator === "lt" || input.operator === "lte") && typeof input.threshold === "number" && (input.upperThreshold === null || typeof input.upperThreshold === "number") && typeof input.unit === "string" && (input.severity === "error" || input.severity === "warn") && (input.missingPolicy === "fail" || input.missingPolicy === "inconclusive" || input.missingPolicy === "unsupported") && typeof input.definition === "string" && typeof input.method === "string" && (input.scenarioVersion === null || typeof input.scenarioVersion === "string") && (input.specVersion === null || typeof input.specVersion === "string") && (input.engineId === null || typeof input.engineId === "string") && (input.pluginVersion === null || typeof input.pluginVersion === "string") && (input.seed === null || typeof input.seed === "string") && (input.algorithmVersion === null || typeof input.algorithmVersion === "string") && (input.recommendationId === "rec.metric.estimate-not-measured" || input.recommendationId === "rec.metric.identity-mismatch" || input.recommendationId === "rec.metric.insufficient" || input.recommendationId === "rec.metric.outside-threshold" || input.recommendationId === "rec.metric.unsupported" || input.recommendationId === "rec.metric.within-threshold") && (Object.keys(input).length === 17 || Object.keys(input).every((key) => {
19037
+ if (["id", "metricId", "metricVersion", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
18745
19038
  return true;
18746
19039
  const value = input[key];
18747
19040
  if (value === undefined)
@@ -18814,6 +19107,10 @@ var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validate
18814
19107
  path: _path + ".metricId",
18815
19108
  expected: "string",
18816
19109
  value: input.metricId
19110
+ }), input.metricVersion === null || input.metricVersion === undefined || typeof input.metricVersion === "number" || _report(_exceptionable, {
19111
+ path: _path + ".metricVersion",
19112
+ expected: "(null | number | undefined)",
19113
+ value: input.metricVersion
18817
19114
  }), input.operator === "between" || input.operator === "eq" || input.operator === "gt" || input.operator === "gte" || input.operator === "lt" || input.operator === "lte" || _report(_exceptionable, {
18818
19115
  path: _path + ".operator",
18819
19116
  expected: '("between" | "eq" | "gt" | "gte" | "lt" | "lte")',
@@ -18875,7 +19172,7 @@ var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validate
18875
19172
  expected: _ve3,
18876
19173
  value: input.recommendationId
18877
19174
  }), Object.keys(input).length === 17 || (_exceptionable === false || Object.keys(input).map((key) => {
18878
- if (["id", "metricId", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
19175
+ if (["id", "metricId", "metricVersion", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
18879
19176
  return true;
18880
19177
  const value = input[key];
18881
19178
  if (value === undefined)
@@ -18961,7 +19258,7 @@ var init_judge = __esm(() => {
18961
19258
  // ../core/dist/sim/analysis/flow.js
18962
19259
  var init_flow = () => {};
18963
19260
 
18964
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_isTypeUint32.mjs
19261
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_isTypeUint32.mjs
18965
19262
  var _isTypeUint32 = (value) => Math.floor(value) === value && MINIMUM <= value && value <= MAXIMUM, MINIMUM = 0, MAXIMUM;
18966
19263
  var init__isTypeUint32 = __esm(() => {
18967
19264
  MAXIMUM = 2 ** 32 - 1;
@@ -19353,32 +19650,46 @@ function buildTimeline(args) {
19353
19650
  }
19354
19651
 
19355
19652
  // ../core/dist/report/compare.js
19356
- function safeNum(input) {
19357
- if (typeof input === "number")
19358
- return input;
19359
- if (typeof input === "string") {
19360
- const n = Number(input);
19361
- if (Number.isFinite(n))
19362
- return n;
19363
- const m = /^\s*([+-]?\d+(?:\.\d+)?)e([+-]?\d+)\s*$/i.exec(input);
19364
- if (m) {
19365
- const mantissa = Number(m[1]);
19366
- const exp = Number(m[2]);
19367
- if (mantissa === 0)
19368
- return 0;
19369
- if (Number.isFinite(mantissa) && Number.isFinite(exp)) {
19370
- return Math.log10(Math.abs(mantissa)) + exp;
19371
- }
19372
- }
19373
- }
19374
- return Number.NaN;
19653
+ function decimalParts(input) {
19654
+ const match = /^([+-]?)(?:(\d+)(?:\.(\d+))?|\.(\d+))(?:e([+-]?\d+))?([a-z]{2,})?$/i.exec(input.trim());
19655
+ if (!match)
19656
+ throw new RangeError("Static monetary comparison requires decimal amounts");
19657
+ let suffixExponent = 0n;
19658
+ if (match[6]) {
19659
+ let index = 0n;
19660
+ for (const ch of match[6].toLowerCase())
19661
+ index = index * 26n + BigInt(ch.charCodeAt(0) - 96);
19662
+ suffixExponent = (index - 26n) * 3n;
19663
+ }
19664
+ const fraction = match[3] ?? match[4] ?? "";
19665
+ const digits = `${match[2] ?? ""}${fraction}`.replace(/^0+/, "");
19666
+ let end = digits.length;
19667
+ while (end > 0 && digits[end - 1] === "0")
19668
+ end--;
19669
+ return {
19670
+ sign: digits.length === 0 ? 0 : match[1] === "-" ? -1 : 1,
19671
+ digits: digits.slice(0, end),
19672
+ magnitude: BigInt(match[5] ?? "0") + suffixExponent - BigInt(fraction.length) + BigInt(digits.length)
19673
+ };
19674
+ }
19675
+ function compareStaticMoney(left, right) {
19676
+ const a = decimalParts(left), b = decimalParts(right);
19677
+ if (a.sign !== b.sign)
19678
+ return a.sign < b.sign ? -1 : 1;
19679
+ if (a.sign === 0)
19680
+ return 0;
19681
+ if (a.magnitude !== b.magnitude)
19682
+ return (a.magnitude < b.magnitude ? -1 : 1) * a.sign;
19683
+ const length = Math.max(a.digits.length, b.digits.length);
19684
+ const ad = a.digits.padEnd(length, "0"), bd = b.digits.padEnd(length, "0");
19685
+ return (ad < bd ? -1 : ad > bd ? 1 : 0) * a.sign;
19375
19686
  }
19376
19687
  function score(s, metric) {
19377
19688
  switch (metric) {
19378
19689
  case "endMoney":
19379
- return safeNum(s.initial.wallet.amount);
19690
+ return s.initial.wallet.amount;
19380
19691
  case "endNetWorth":
19381
- return safeNum(s.initial.maxMoneyEver ?? s.initial.wallet.amount);
19692
+ return s.initial.maxMoneyEver ?? s.initial.wallet.amount;
19382
19693
  case "droppedRate":
19383
19694
  return -(s.policy.maxLogGap ?? 0);
19384
19695
  case "etaToTargetWorth":
@@ -19414,9 +19725,21 @@ function compareScenarios(args) {
19414
19725
  }
19415
19726
  const measuredA = measuredScore(args.measured?.a, args.metric);
19416
19727
  const measuredB = measuredScore(args.measured?.b, args.metric);
19728
+ for (const value of [measuredA, measuredB]) {
19729
+ if (value !== undefined && !Number.isFinite(value)) {
19730
+ throw new RangeError(`Measured ${args.metric} comparison requires finite values`);
19731
+ }
19732
+ }
19417
19733
  const useMeasured = measuredA !== undefined && measuredB !== undefined;
19418
19734
  const aScore = useMeasured ? measuredA : score(args.a, args.metric);
19419
19735
  const bScore = useMeasured ? measuredB : score(args.b, args.metric);
19736
+ if (!useMeasured && (args.metric === "endMoney" || args.metric === "endNetWorth")) {
19737
+ const order = compareStaticMoney(String(aScore), String(bScore));
19738
+ return {
19739
+ better: order === 0 ? "tie" : order > 0 ? "a" : "b",
19740
+ detail: { aScore, bScore, metric: args.metric, source: "static" }
19741
+ };
19742
+ }
19420
19743
  const higherBetter = args.metric === "endMoney" || args.metric === "endNetWorth" || args.metric === "visibleChangesPerMinute";
19421
19744
  if (aScore === bScore) {
19422
19745
  return {
@@ -19526,12 +19849,12 @@ function estimateLtvPerUser(args) {
19526
19849
  const fullDays = Math.floor(Math.max(0, horizonDays));
19527
19850
  const remainder = Math.max(0, horizonDays - fullDays);
19528
19851
  let gross = 0;
19529
- for (let day = 1;day <= fullDays; day++) {
19852
+ for (let day = 0;day < fullDays; day++) {
19530
19853
  const active = retentionAtDay(config.retention, day);
19531
19854
  gross += active * (payerConversion * arppuDaily + adArpDau);
19532
19855
  }
19533
19856
  if (remainder > 0) {
19534
- const active = retentionAtDay(config.retention, fullDays + 1);
19857
+ const active = retentionAtDay(config.retention, fullDays);
19535
19858
  gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
19536
19859
  }
19537
19860
  const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
@@ -19653,10 +19976,11 @@ function applyLogNormalShock(mean, sigma, z) {
19653
19976
  }
19654
19977
  function makeUncertainConfig(base, rng) {
19655
19978
  const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
19656
- const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
19657
- const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
19658
- const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
19659
- const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
19979
+ const retention = (anchor) => clamp013(applyLogNormalShock(anchor, base.uncertainty.sigma.retention, shock.retention));
19980
+ const d1 = retention(base.retention.d1);
19981
+ const d7 = retention(base.retention.d7);
19982
+ const d30 = retention(base.retention.d30);
19983
+ const d90 = retention(base.retention.d90);
19660
19984
  return {
19661
19985
  ...base,
19662
19986
  retention: {
@@ -19680,6 +20004,23 @@ function quantileFromSorted(sorted, q) {
19680
20004
  const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
19681
20005
  return sorted[idx] ?? 0;
19682
20006
  }
20007
+ function quantileKey(q) {
20008
+ const [mantissa = "0", exponent = "0"] = String(q).split("e");
20009
+ const [whole = "0", fraction = ""] = mantissa.split(".");
20010
+ const digits = whole + fraction;
20011
+ const point = whole.length + Number(exponent) + 2;
20012
+ const shifted = point <= 0 ? `0.${"0".repeat(-point)}${digits}` : point >= digits.length ? digits + "0".repeat(point - digits.length) : `${digits.slice(0, point)}.${digits.slice(point)}`;
20013
+ const [int = "0", frac = ""] = shifted.split(".");
20014
+ let start = 0;
20015
+ while (start < int.length - 1 && int[start] === "0")
20016
+ start++;
20017
+ let end = frac.length;
20018
+ while (end > 0 && frac[end - 1] === "0")
20019
+ end--;
20020
+ const trimmedInt = int.slice(start);
20021
+ const trimmedFrac = frac.slice(0, end);
20022
+ return `q${trimmedFrac ? `${trimmedInt}.${trimmedFrac}` : trimmedInt}`;
20023
+ }
19683
20024
  function estimateLtvDistribution(args) {
19684
20025
  const rng = mulberry32(args.seed);
19685
20026
  const grossSamples = [];
@@ -19701,7 +20042,7 @@ function estimateLtvDistribution(args) {
19701
20042
  const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
19702
20043
  const byQuantile = {};
19703
20044
  for (const q of args.quantiles) {
19704
- byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
20045
+ byQuantile[quantileKey(q)] = quantileFromSorted(sortedLtv, q);
19705
20046
  }
19706
20047
  return {
19707
20048
  mean: {
@@ -19736,8 +20077,7 @@ function assertQuantileValues(quantiles) {
19736
20077
  throw new RangeError("quantiles must be nonempty and in 0..1");
19737
20078
  }
19738
20079
  }
19739
- function assertConfig(config) {
19740
- const { retention, ...other } = config;
20080
+ function assertRetention(retention) {
19741
20081
  for (const day of ["d1", "d7", "d30", "d90"]) {
19742
20082
  if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
19743
20083
  throw new RangeError(`retention.${day} must be finite and in 0..1`);
@@ -19746,6 +20086,10 @@ function assertConfig(config) {
19746
20086
  if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
19747
20087
  throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
19748
20088
  }
20089
+ }
20090
+ function assertConfig(config) {
20091
+ const { retention, ...other } = config;
20092
+ assertRetention(retention);
19749
20093
  const { quantiles, ...uncertainty } = other.uncertainty;
19750
20094
  assertQuantileValues(quantiles);
19751
20095
  assertMonetization({ ...other, uncertainty });
@@ -19756,6 +20100,14 @@ function deriveMonetizationConfig2(scenario) {
19756
20100
  assertConfig(config);
19757
20101
  return config;
19758
20102
  }
20103
+ function progressionFactor2(startWorthLog10, endWorthLog10, span) {
20104
+ if (Number.isNaN(startWorthLog10) || Number.isNaN(endWorthLog10)) {
20105
+ throw new RangeError("worth logs must not be NaN");
20106
+ }
20107
+ if (!Number.isFinite(span) || span <= 0)
20108
+ throw new RangeError("span must be finite and > 0");
20109
+ return progressionFactor(startWorthLog10, endWorthLog10, span);
20110
+ }
19759
20111
  function assertPoint(args) {
19760
20112
  assertConfig(args.config);
19761
20113
  if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
@@ -19776,18 +20128,24 @@ function estimateLtvPerUser2(args) {
19776
20128
  }
19777
20129
  function estimateLtvDistribution2(args) {
19778
20130
  assertPoint(args);
19779
- if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
20131
+ const draws = args.draws ?? args.config.uncertainty.draws;
20132
+ const quantiles = args.quantiles ?? args.config.uncertainty.quantiles;
20133
+ const seed = args.seed ?? args.config.uncertainty.seed;
20134
+ if (!Number.isSafeInteger(draws) || draws < 1 || draws > 1e5 || draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
19780
20135
  throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
19781
20136
  }
19782
- if (!Number.isFinite(args.seed))
19783
- throw new RangeError("seed must be finite");
19784
- if (args.quantiles.length > 101)
19785
- throw new RangeError("quantiles exceed the 101 distinct percent keys");
19786
- assertQuantileValues(args.quantiles);
19787
- if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
19788
- throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
20137
+ if (seed === undefined)
20138
+ throw new RangeError("seed is required: pass seed or set config.uncertainty.seed");
20139
+ if (!Number.isInteger(seed) || seed < 0 || seed > 4294967295) {
20140
+ throw new RangeError("seed must be an integer in 0..4294967295");
20141
+ }
20142
+ if (quantiles.length > 101)
20143
+ throw new RangeError("quantiles exceed 101 entries");
20144
+ assertQuantileValues(quantiles);
20145
+ if (new Set(quantiles.map(quantileKey)).size !== quantiles.length) {
20146
+ throw new RangeError("quantiles must be distinct values in 0..1");
19789
20147
  }
19790
- const result = estimateLtvDistribution(args);
20148
+ const result = estimateLtvDistribution({ ...args, draws, quantiles, seed });
19791
20149
  finiteResult(result.mean);
19792
20150
  finiteResult(result.quantiles);
19793
20151
  return result;
@@ -19797,6 +20155,151 @@ var init_public = __esm(() => {
19797
20155
  init_uncertainty();
19798
20156
  });
19799
20157
 
20158
+ // ../core/dist/ltv/cohort.js
20159
+ function wilsonInterval(activeUsers, users) {
20160
+ const rate = activeUsers / users;
20161
+ const z2 = 1.959963984540054 ** 2;
20162
+ const denominator = 1 + z2 / users;
20163
+ const center = (rate + z2 / (2 * users)) / denominator;
20164
+ const radius = Math.sqrt(z2 * (rate * (1 - rate) / users + z2 / (4 * users * users))) / denominator;
20165
+ return { min: activeUsers === 0 ? 0 : Math.max(0, center - radius), max: activeUsers === users ? 1 : Math.min(1, center + radius) };
20166
+ }
20167
+ function observeCohort(rows, observedThroughDay) {
20168
+ if (!Number.isSafeInteger(observedThroughDay) || observedThroughDay < 0)
20169
+ throw new Error("observed-through-day must be a nonnegative safe integer");
20170
+ if (!Array.isArray(rows) || rows.length === 0)
20171
+ throw new Error("Observed cohort requires at least one install record");
20172
+ const users = new Map;
20173
+ for (const row of rows) {
20174
+ if (!row || typeof row.userId !== "string" || !row.userId.trim() || !Number.isSafeInteger(row.day) || row.day < 0 || row.day > observedThroughDay) {
20175
+ throw new Error("Observed cohort requires a user ID and integer days within observed-through-day");
20176
+ }
20177
+ if (![row.iapRevenue, row.adRevenue, row.acquisitionCost].every((v) => v === undefined || typeof v === "number" && Number.isFinite(v) && v >= 0)) {
20178
+ throw new Error("Observed cohort revenue and acquisition costs must be finite and nonnegative");
20179
+ }
20180
+ if (row.active !== undefined && typeof row.active !== "boolean")
20181
+ throw new Error("Observed cohort active must be boolean");
20182
+ const user = users.get(row.userId) ?? { days: new Set, active: new Set };
20183
+ if (user.days.has(row.day))
20184
+ throw new Error("Observed cohort requires one aggregated row per user and day");
20185
+ user.days.add(row.day);
20186
+ if (row.active ?? true)
20187
+ user.active.add(row.day);
20188
+ users.set(row.userId, user);
20189
+ }
20190
+ for (const user of users.values()) {
20191
+ if (!user.days.has(0))
20192
+ throw new Error("Observed cohort requires a day 0 install record for every user, including inactive users");
20193
+ }
20194
+ const n = users.size;
20195
+ const observation = {
20196
+ observedThroughDay,
20197
+ users: n,
20198
+ retention: RETENTION_DAYS.map((day) => {
20199
+ if (day > observedThroughDay)
20200
+ return { day, status: "not-observed", users: n, activeUsers: null, rate: null, interval95: null };
20201
+ const activeUsers = [...users.values()].filter((user) => user.active.has(day)).length;
20202
+ const rate = activeUsers / n;
20203
+ return { day, status: "observed", users: n, activeUsers, rate, interval95: wilsonInterval(activeUsers, n) };
20204
+ })
20205
+ };
20206
+ assertCohortObservation(observation);
20207
+ return observation;
20208
+ }
20209
+ function object2(value) {
20210
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20211
+ }
20212
+ function assertCohortObservation(observation) {
20213
+ if (!object2(observation) || typeof observation.users !== "number" || !Number.isSafeInteger(observation.users) || observation.users < 1 || typeof observation.observedThroughDay !== "number" || !Number.isSafeInteger(observation.observedThroughDay) || observation.observedThroughDay < 0) {
20214
+ throw new Error("Invalid cohort population or observation cutoff");
20215
+ }
20216
+ if (!Array.isArray(observation.retention) || observation.retention.length !== RETENTION_DAYS.length)
20217
+ throw new Error("Cohort retention must contain D1, D7, D30 and D90");
20218
+ for (const [index, value] of observation.retention.entries()) {
20219
+ const point = value;
20220
+ if (!object2(point) || typeof point.day !== "number" || point.day !== RETENTION_DAYS[index] || point.users !== observation.users)
20221
+ throw new Error("Cohort retention days and population must match the observation");
20222
+ const observed = point.day <= observation.observedThroughDay;
20223
+ if (point.status !== (observed ? "observed" : "not-observed"))
20224
+ throw new Error("Cohort retention status must match the observation cutoff");
20225
+ if (!observed) {
20226
+ if (point.activeUsers !== null || point.rate !== null || point.interval95 !== null)
20227
+ throw new Error("Future retention must remain unobserved");
20228
+ continue;
20229
+ }
20230
+ if (typeof point.activeUsers !== "number" || !Number.isSafeInteger(point.activeUsers) || point.activeUsers < 0 || point.activeUsers > observation.users || point.rate !== point.activeUsers / observation.users) {
20231
+ throw new Error("Retained users and retention rate must match the installed population");
20232
+ }
20233
+ const interval = point.interval95;
20234
+ if (typeof point.rate !== "number" || !object2(interval) || typeof interval.min !== "number" || typeof interval.max !== "number" || !Number.isFinite(interval.min) || !Number.isFinite(interval.max) || interval.min < 0 || interval.max > 1 || interval.min > point.rate || interval.max < point.rate) {
20235
+ throw new Error("Retention confidence interval must be finite and contain the observed rate");
20236
+ }
20237
+ const expected = wilsonInterval(point.activeUsers, observation.users);
20238
+ if (interval.min !== expected.min || interval.max !== expected.max) {
20239
+ throw new Error("Retention confidence interval must match the Wilson 95% bounds for the observed population");
20240
+ }
20241
+ }
20242
+ }
20243
+ function observedRetentionRates(observation) {
20244
+ assertCohortObservation(observation);
20245
+ const rates = {};
20246
+ for (const row of observation.retention) {
20247
+ if (row.rate !== null)
20248
+ rates[`d${row.day}`] = row.rate;
20249
+ }
20250
+ return rates;
20251
+ }
20252
+ function assertRetentionAssumptions(assumptions) {
20253
+ if (!object2(assumptions))
20254
+ throw new RangeError("Retention assumptions must be an object");
20255
+ for (const day of RETENTION_DAYS) {
20256
+ const rate = assumptions[`d${day}`];
20257
+ if (rate !== undefined && (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1)) {
20258
+ throw new RangeError(`Retention assumption d${day} must be finite and in 0..1`);
20259
+ }
20260
+ }
20261
+ }
20262
+ function planRetentionPatch(observation, assumptions = {}) {
20263
+ assertRetentionAssumptions(assumptions);
20264
+ const rates = observedRetentionRates(observation);
20265
+ if (Object.keys(rates).length === 0)
20266
+ return { status: "unavailable", retention: {}, warnings: ["No retention day has been fully observed."] };
20267
+ const merged = { ...assumptions, ...rates };
20268
+ const warnings = [];
20269
+ for (const day of RETENTION_DAYS) {
20270
+ if (merged[`d${day}`] === undefined)
20271
+ warnings.push(`Unobserved D${day} has no explicit retained assumption. An LTV default could conflict with the observations; supply monetization.retention.d${day} in the scenario or observe this day before applying a patch.`);
20272
+ }
20273
+ let previous;
20274
+ for (const day of RETENTION_DAYS) {
20275
+ const rate = merged[`d${day}`];
20276
+ if (rate === undefined)
20277
+ continue;
20278
+ if (previous && previous.rate < rate)
20279
+ warnings.push(`D${previous.day}=${previous.rate} is below D${day}=${rate}. A complete scenario retention curve must satisfy d1 >= d7 >= d30 >= d90, and the LTV model would interpolate a rise as given; review observations and retained assumptions before applying a patch.`);
20280
+ previous = { day, rate };
20281
+ }
20282
+ return warnings.length ? { status: "requires-review", retention: {}, warnings } : { status: "ready", retention: rates, warnings };
20283
+ }
20284
+ function compareRetention(observation, assumptions) {
20285
+ assertCohortObservation(observation);
20286
+ assertRetentionAssumptions(assumptions);
20287
+ return observation.retention.map((row) => {
20288
+ const assumption = assumptions[`d${row.day}`] ?? null;
20289
+ return {
20290
+ day: row.day,
20291
+ assumption,
20292
+ observed: row.rate,
20293
+ differencePercentagePoints: row.rate === null || assumption === null ? null : (row.rate - assumption) * 100,
20294
+ status: row.rate === null ? "not-observed" : assumption === null ? "unconfigured" : "compared"
20295
+ };
20296
+ });
20297
+ }
20298
+ var RETENTION_DAYS;
20299
+ var init_cohort = __esm(() => {
20300
+ RETENTION_DAYS = Object.freeze([1, 7, 30, 90]);
20301
+ });
20302
+
19800
20303
  // ../core/dist/index.js
19801
20304
  var init_dist2 = __esm(() => {
19802
20305
  init_types();
@@ -19863,15 +20366,18 @@ var init_dist2 = __esm(() => {
19863
20366
  init_flow();
19864
20367
  init_jevAdvisory();
19865
20368
  init_public();
20369
+ init_cohort();
19866
20370
  });
19867
20371
 
19868
20372
  // src/runtime/bun.ts
19869
20373
  var {$ } = globalThis.Bun;
19870
20374
  import { dirname } from "path";
19871
20375
  async function readTextFile(path) {
20376
+ recordInputRead(path);
19872
20377
  return Bun.file(path).text();
19873
20378
  }
19874
20379
  async function readJsonFile(path) {
20380
+ recordInputRead(path);
19875
20381
  return Bun.file(path).json();
19876
20382
  }
19877
20383
  async function writeTextFile(path, body) {
@@ -19921,6 +20427,7 @@ function runText(args, opts) {
19921
20427
  var MAX_CAPTURE_BYTES;
19922
20428
  var init_bun = __esm(() => {
19923
20429
  init_errors3();
20430
+ init_invocation();
19924
20431
  MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
19925
20432
  });
19926
20433
 
@@ -19928,7 +20435,7 @@ var init_bun = __esm(() => {
19928
20435
  var require_package = __commonJS(function(exports, module) {
19929
20436
  module.exports = {
19930
20437
  name: "@idlekit/cli",
19931
- version: "0.5.1",
20438
+ version: "0.7.0",
19932
20439
  description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
19933
20440
  license: "MIT",
19934
20441
  type: "module",
@@ -19981,7 +20488,7 @@ var require_package = __commonJS(function(exports, module) {
19981
20488
  "@clack/prompts": "1.8.1",
19982
20489
  "@gunshi/plugin-completion": "0.37.3",
19983
20490
  "@gunshi/plugin-suggestion": "0.37.3",
19984
- "@idlekit/core": "0.5.0",
20491
+ "@idlekit/core": "0.7.0",
19985
20492
  ajv: "^8.20.0",
19986
20493
  gunshi: "0.37.3",
19987
20494
  "proper-lockfile": "4.1.2",
@@ -19996,7 +20503,7 @@ var require_package = __commonJS(function(exports, module) {
19996
20503
 
19997
20504
  // src/runtime/selfCli.ts
19998
20505
  import { existsSync } from "fs";
19999
- import { dirname as dirname3, resolve as resolve2 } from "path";
20506
+ import { dirname as dirname3, resolve as resolve3 } from "path";
20000
20507
  function currentEntry() {
20001
20508
  return Bun.main;
20002
20509
  }
@@ -20013,11 +20520,11 @@ function cliPackageRoot(entry = currentEntry()) {
20013
20520
  if (isNativeEntry(entry))
20014
20521
  return process.cwd();
20015
20522
  if (isBundledCliProcess(entry))
20016
- return resolve2(dirname3(entry), "..");
20017
- return resolve2(import.meta.dir, "../..");
20523
+ return resolve3(dirname3(entry), "..");
20524
+ return resolve3(import.meta.dir, "../..");
20018
20525
  }
20019
20526
  function bundledCliConfig(entry) {
20020
- const config = resolve2(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
20527
+ const config = resolve3(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
20021
20528
  if (!existsSync(config))
20022
20529
  return;
20023
20530
  return config;
@@ -20032,7 +20539,7 @@ function selfCliCommand(args, entry = currentEntry()) {
20032
20539
  return [bun, `--config=${config}`, entry, ...args];
20033
20540
  return [bun, entry, ...args];
20034
20541
  }
20035
- const bunfigApplies = resolve2(process.cwd()) === CLI_ROOT;
20542
+ const bunfigApplies = resolve3(process.cwd()) === CLI_ROOT;
20036
20543
  if (entry.endsWith(".ts") && !bunfigApplies) {
20037
20544
  return [bun, "--preload", "@ttsc/unplugin/bun-register", entry, ...args];
20038
20545
  }
@@ -20054,16 +20561,16 @@ function runSelfCli(args) {
20054
20561
  }
20055
20562
  var CLI_ROOT, MAX_CAPTURE_BYTES2;
20056
20563
  var init_selfCli = __esm(() => {
20057
- CLI_ROOT = resolve2(import.meta.dir, "../..");
20564
+ CLI_ROOT = resolve3(import.meta.dir, "../..");
20058
20565
  MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
20059
20566
  });
20060
20567
 
20061
20568
  // src/io/outputMeta.ts
20062
- import { dirname as dirname4, resolve as resolve3 } from "path";
20569
+ import { dirname as dirname4, resolve as resolve4 } from "path";
20063
20570
  function repoRoot() {
20064
20571
  if (isBundledCliProcess())
20065
- return resolve3(dirname4(Bun.main), "../..");
20066
- return resolve3(import.meta.dir, "../../..");
20572
+ return resolve4(dirname4(Bun.main), "../..");
20573
+ return resolve4(import.meta.dir, "../../..");
20067
20574
  }
20068
20575
  function stableStringify(value) {
20069
20576
  if (value === null || typeof value !== "object") {
@@ -28787,7 +29294,7 @@ var init_jev_schema = __esm(() => {
28787
29294
 
28788
29295
  // src/lib/evaluation/providers/jev.ts
28789
29296
  import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, realpathSync as realpathSync2, renameSync, unlinkSync, writeFileSync } from "fs";
28790
- import { basename as basename2, dirname as dirname5, resolve as resolve6 } from "path";
29297
+ import { basename as basename2, dirname as dirname5, resolve as resolve7 } from "path";
28791
29298
  function pathEnd(value, start, run) {
28792
29299
  const end = start + run.length;
28793
29300
  if (FILE_COMPONENT.test(run))
@@ -28844,19 +29351,18 @@ function isSafeJevReport(report, keyMaterial) {
28844
29351
  return JSON.stringify(redact(report, keyMaterial)) === JSON.stringify(report);
28845
29352
  }
28846
29353
  function projectJevState(artifact, rubric, keyMaterial) {
28847
- const referenced = new Set(rubric.rules.map((rule) => rule.metricId));
28848
- const seenMetricIds = new Set;
28849
- const firstReadings = artifact.metrics.filter((metric) => {
28850
- if (!referenced.has(metric.metricId) || seenMetricIds.has(metric.metricId))
28851
- return false;
28852
- seenMetricIds.add(metric.metricId);
28853
- return true;
28854
- });
29354
+ const rulesByReading = new Map;
29355
+ for (const rule of rubric.rules) {
29356
+ const selected = ruleReadings(artifact, rule)[0];
29357
+ if (selected)
29358
+ rulesByReading.set(selected, [...rulesByReading.get(selected) ?? [], rule]);
29359
+ }
29360
+ const firstReadings = artifact.metrics.filter((metric) => rulesByReading.has(metric));
28855
29361
  const coverage = artifact.observationCoverage;
28856
29362
  const judged = judgeArtifact(artifact, rubric);
28857
29363
  const verdicts = new Map(judged.ok ? judged.report.numerical.results.map((result) => [result.id, result.verdict]) : []);
28858
29364
  const withheld = coverage === "incomplete" || coverage === "unknown" || coverage === "disabled" || artifact.capabilities.metrics !== "statically-known";
28859
- const metrics = firstReadings.flatMap((metric) => rubric.rules.filter((rule) => rule.metricId === metric.metricId).map((rule) => {
29365
+ const metrics = firstReadings.flatMap((metric) => (rulesByReading.get(metric) ?? []).map((rule) => {
28860
29366
  const { observation } = toObservation(artifact, metric, rule);
28861
29367
  return {
28862
29368
  ...observation.identity,
@@ -29092,14 +29598,14 @@ function cacheTarget(path, depth = 0) {
29092
29598
  } catch {
29093
29599
  try {
29094
29600
  if (lstatSync(path).isSymbolicLink())
29095
- return cacheTarget(resolve6(dirname5(path), readlinkSync(path)), depth + 1);
29601
+ return cacheTarget(resolve7(dirname5(path), readlinkSync(path)), depth + 1);
29096
29602
  } catch (error) {
29097
29603
  if (error.code !== "ENOENT")
29098
29604
  throw error;
29099
29605
  }
29100
- const absolute = resolve6(path);
29606
+ const absolute = resolve7(path);
29101
29607
  const parent = dirname5(absolute);
29102
- return parent === absolute ? absolute : resolve6(cacheTarget(parent, depth), basename2(absolute));
29608
+ return parent === absolute ? absolute : resolve7(cacheTarget(parent, depth), basename2(absolute));
29103
29609
  }
29104
29610
  }
29105
29611
  async function writeCache(requestedPath, entry) {
@@ -34053,6 +34559,7 @@ function getTerminalInfo() {
34053
34559
  }
34054
34560
 
34055
34561
  // src/runtime/command.ts
34562
+ init_invocation();
34056
34563
  function option(schema, metadata = {}) {
34057
34564
  return { schema, ...metadata };
34058
34565
  }
@@ -34173,14 +34680,15 @@ async function createCLI(config) {
34173
34680
  throw usageError(`Invalid --${key}: ${parsed.error.message}`);
34174
34681
  flags[key] = parsed.data;
34175
34682
  }
34176
- await selected.handler({
34683
+ const positional = [...ctx.positionals.slice(ctx.commandPath.length), ...ctx.rest];
34684
+ await runWithInvocation({ positional, flags }, () => selected.handler({
34177
34685
  flags,
34178
- positional: [...ctx.positionals.slice(ctx.commandPath.length), ...ctx.rest],
34686
+ positional,
34179
34687
  terminal: getTerminalInfo(),
34180
34688
  prompt,
34181
34689
  cwd: process.cwd(),
34182
34690
  runtime: { args: [...ctx._] }
34183
- });
34691
+ }));
34184
34692
  }
34185
34693
  });
34186
34694
  };
@@ -34251,8 +34759,9 @@ init_zod();
34251
34759
  // src/plugin/load.ts
34252
34760
  init_dist2();
34253
34761
  init_bun();
34762
+ init_invocation();
34254
34763
  import { realpathSync } from "fs";
34255
- import { dirname as dirname2, extname, isAbsolute, relative, resolve, sep } from "path";
34764
+ import { dirname as dirname2, extname, isAbsolute, relative, resolve as resolve2, sep } from "path";
34256
34765
  import { fileURLToPath } from "url";
34257
34766
 
34258
34767
  // src/lib/designObjectives.ts
@@ -34264,7 +34773,7 @@ init_dist2();
34264
34773
  function quantile(values, q) {
34265
34774
  return quantileInclusiveFloor(values, q) ?? undefined;
34266
34775
  }
34267
- function quantileKey(quantiles, q) {
34776
+ function quantileKey2(quantiles, q) {
34268
34777
  const percent = Math.round(q * 100);
34269
34778
  const legacy = `q${percent}`;
34270
34779
  const collided = quantiles.some((other) => other !== q && `q${Math.round(other * 100)}` === legacy);
@@ -34277,7 +34786,7 @@ function summarizeNumeric(values, quantiles) {
34277
34786
  return null;
34278
34787
  return {
34279
34788
  mean: values.reduce((sum, value) => sum + value, 0) / values.length,
34280
- quantiles: Object.fromEntries(quantiles.map((q) => [quantileKey(quantiles, q), quantileInclusiveFloor(values, q) ?? Number.NaN]))
34789
+ quantiles: Object.fromEntries(quantiles.map((q) => [quantileKey2(quantiles, q), quantileInclusiveFloor(values, q) ?? Number.NaN]))
34281
34790
  };
34282
34791
  }
34283
34792
  function resolveSessionPatternSpec(args) {
@@ -34918,10 +35427,11 @@ init_errors3();
34918
35427
  function ownedCount(state) {
34919
35428
  return Number(state?.vars?.owned ?? 0);
34920
35429
  }
35430
+ var NUMBER_ONLY_COMMANDS = ["report", "analyze", "eta", "growth", "prestige-cycle"];
34921
35431
  function finiteLinearAmount(E, value, label) {
34922
35432
  if (!E.isFinite(value)) {
34923
- throw cliError("SCENARIO_INVALID", `linear ${label} exceeds the selected engine's finite range. Use --engine breakInfinity with simulate/evaluate/experience/ltv for a large economy, or reduce the inputs.`, {
34924
- hint: "Other commands, including report/compare/tune, use the number engine; reduce their inputs."
35433
+ throw cliError("SCENARIO_INVALID", `linear ${label} exceeds the selected engine's finite range. Use --engine breakInfinity with simulate/evaluate/experience/ltv/compare/tune/balance for a large economy, or reduce the inputs.`, {
35434
+ hint: `${NUMBER_ONLY_COMMANDS.join(", ")} run only the number engine; reduce their inputs.`
34925
35435
  });
34926
35436
  }
34927
35437
  return value;
@@ -35157,7 +35667,7 @@ function normalizeSha256(input) {
35157
35667
  return x;
35158
35668
  }
35159
35669
  function parsePluginRoots(input) {
35160
- const list = parseCommaSeparated(input).map((x) => resolve(x));
35670
+ const list = parseCommaSeparated(input).map((x) => resolve2(x));
35161
35671
  return [...new Set(list)];
35162
35672
  }
35163
35673
  function parsePluginSha256(input) {
@@ -35170,7 +35680,7 @@ function parsePluginSha256(input) {
35170
35680
  }
35171
35681
  const key = entry.slice(0, idx).trim();
35172
35682
  const value = entry.slice(idx + 1).trim();
35173
- const abs = resolve(key);
35683
+ const abs = resolve2(key);
35174
35684
  const digest = normalizeSha256(value);
35175
35685
  if (out[abs] && out[abs] !== digest) {
35176
35686
  throw new Error(`Conflicting sha256 values for plugin path: ${key}`);
@@ -35182,7 +35692,7 @@ function parsePluginSha256(input) {
35182
35692
  function parsePluginSecurityOptions(input) {
35183
35693
  const allowedRoots = parsePluginRoots(input.roots);
35184
35694
  const requiredSha256 = parsePluginSha256(input.sha256);
35185
- const trustFile = typeof input.trustFile === "string" && input.trustFile.trim().length > 0 ? resolve(input.trustFile) : undefined;
35695
+ const trustFile = typeof input.trustFile === "string" && input.trustFile.trim().length > 0 ? resolve2(input.trustFile) : undefined;
35186
35696
  return {
35187
35697
  allowedRoots,
35188
35698
  requiredSha256,
@@ -35204,7 +35714,7 @@ async function loadPluginTrustFile(pathAbs) {
35204
35714
  if (typeof v !== "string")
35205
35715
  continue;
35206
35716
  const digest = normalizeSha256(v);
35207
- const absPath = isAbsolute(k) ? resolve(k) : resolve(baseDir, k);
35717
+ const absPath = isAbsolute(k) ? resolve2(k) : resolve2(baseDir, k);
35208
35718
  out[absPath] = digest;
35209
35719
  }
35210
35720
  return out;
@@ -35230,7 +35740,7 @@ async function loadRegistries(pluginPaths = [], securityOptions = {}) {
35230
35740
  let requiredSha256;
35231
35741
  let requiredClosureSha256;
35232
35742
  try {
35233
- allowedRoots = (securityOptions.allowedRoots ?? []).map((x) => resolve(x));
35743
+ allowedRoots = (securityOptions.allowedRoots ?? []).map((x) => resolve2(x));
35234
35744
  const trustFileSha256 = securityOptions.trustFile !== undefined ? await loadPluginTrustFile(securityOptions.trustFile) : {};
35235
35745
  requiredSha256 = mergeShaPolicies(trustFileSha256, securityOptions.requiredSha256 ?? {});
35236
35746
  requiredClosureSha256 = mergeShaPolicies({}, securityOptions.requiredClosureSha256 ?? {});
@@ -35327,6 +35837,7 @@ function isPathInsideRoot(pathAbs, rootAbs) {
35327
35837
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
35328
35838
  }
35329
35839
  async function sha256File(pathAbs) {
35840
+ recordInputRead(pathAbs);
35330
35841
  const buffer = await Bun.file(pathAbs).bytes();
35331
35842
  return sha256Hex(buffer);
35332
35843
  }
@@ -35366,11 +35877,16 @@ async function pluginClosureDigest(entryAbs, entryDigest) {
35366
35877
  return sha256Hex(listing.join(`
35367
35878
  `));
35368
35879
  }
35880
+ async function recordPluginClosureReads(entryAbs) {
35881
+ try {
35882
+ await pluginClosureDigest(entryAbs, await sha256File(entryAbs));
35883
+ } catch {}
35884
+ }
35369
35885
  async function resolveAndValidatePluginPath(input, allowedRoots) {
35370
35886
  if (input.includes("://")) {
35371
35887
  throw new Error(`Plugin path must be a local file path: ${input}`);
35372
35888
  }
35373
- const abs = resolve(input);
35889
+ const abs = resolve2(input);
35374
35890
  const ext = extname(abs).toLowerCase();
35375
35891
  if (!ALLOWED_PLUGIN_EXTENSIONS.has(ext)) {
35376
35892
  throw new Error(`Unsupported plugin extension '${ext}' for ${input}`);
@@ -35420,7 +35936,7 @@ init_outputMeta();
35420
35936
 
35421
35937
  // src/io/readScenario.ts
35422
35938
  init_dist2();
35423
- import { extname as extname2, resolve as resolve4 } from "path";
35939
+ import { extname as extname2, resolve as resolve5 } from "path";
35424
35940
 
35425
35941
  // ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/index.js
35426
35942
  var exports_dist2 = {};
@@ -35539,7 +36055,7 @@ async function readScenarioFile(path) {
35539
36055
  return out.value;
35540
36056
  }
35541
36057
  async function readScenarioFileWithMeta(path) {
35542
- const abs = resolve4(path);
36058
+ const abs = resolve5(path);
35543
36059
  const raw = await readTextFile(abs).catch((error) => {
35544
36060
  throw scenarioReadFailedError(abs, error);
35545
36061
  });
@@ -35561,7 +36077,8 @@ async function readScenarioFileWithMeta(path) {
35561
36077
  // src/io/writeOutput.ts
35562
36078
  init_errors3();
35563
36079
  init_bun();
35564
- import { resolve as resolve5 } from "path";
36080
+ init_invocation();
36081
+ import { resolve as resolve6 } from "path";
35565
36082
  import { realpath, stat } from "fs/promises";
35566
36083
  function toCsv(data) {
35567
36084
  if (!Array.isArray(data)) {
@@ -35624,6 +36141,39 @@ function renderOutput(format, data) {
35624
36141
  return toMarkdown(data);
35625
36142
  return JSON.stringify(data, null, 2);
35626
36143
  }
36144
+ async function sameFile(output, input) {
36145
+ try {
36146
+ const [outputReal, inputReal, outputStat, inputStat] = await Promise.all([
36147
+ realpath(output),
36148
+ realpath(input),
36149
+ stat(output, { bigint: true }),
36150
+ stat(input, { bigint: true })
36151
+ ]);
36152
+ return outputReal === inputReal || outputStat.ino !== 0n && outputStat.dev === inputStat.dev && outputStat.ino === inputStat.ino;
36153
+ } catch (error) {
36154
+ const code = error.code;
36155
+ if (code !== "ENOENT" && code !== "ENOTDIR")
36156
+ throw error;
36157
+ return false;
36158
+ }
36159
+ }
36160
+ async function exists(path) {
36161
+ return stat(path).then(() => true, () => false);
36162
+ }
36163
+ async function assertOutputNotInput(outPath, options = {}) {
36164
+ const abs = resolve6(outPath);
36165
+ const explicit = (options.protectedInputs ?? []).map((input) => ({ input, required: true }));
36166
+ const implicit = invocationInputCandidates({ allow: options.allowFlags }).map((input) => ({ input, required: false }));
36167
+ for (const { input, required } of [...explicit, ...implicit]) {
36168
+ const source = resolve6(input);
36169
+ if (!required && !await exists(source))
36170
+ continue;
36171
+ if (source === abs || await sameFile(abs, source)) {
36172
+ throw usageError(`Output path must not overwrite an input file: ${input}`);
36173
+ }
36174
+ }
36175
+ return abs;
36176
+ }
35627
36177
  async function writeOutput(args) {
35628
36178
  const dataWithMeta = args.meta && args.format === "json" && typeof args.data === "object" && args.data !== null && !Array.isArray(args.data) ? {
35629
36179
  ...args.data,
@@ -35635,28 +36185,7 @@ async function writeOutput(args) {
35635
36185
  `);
35636
36186
  return;
35637
36187
  }
35638
- const abs = resolve5(args.outPath);
35639
- for (const input of args.protectedInputs ?? []) {
35640
- const source = resolve5(input);
35641
- let same = source === abs;
35642
- if (!same) {
35643
- try {
35644
- const [outputReal, inputReal, outputStat, inputStat] = await Promise.all([
35645
- realpath(abs),
35646
- realpath(source),
35647
- stat(abs, { bigint: true }),
35648
- stat(source, { bigint: true })
35649
- ]);
35650
- same = outputReal === inputReal || outputStat.ino !== 0n && outputStat.dev === inputStat.dev && outputStat.ino === inputStat.ino;
35651
- } catch (error) {
35652
- const code = error.code;
35653
- if (code !== "ENOENT" && code !== "ENOTDIR")
35654
- throw error;
35655
- }
35656
- }
35657
- if (same)
35658
- throw usageError(`Output path must not overwrite an input file: ${input}`);
35659
- }
36188
+ const abs = await assertOutputNotInput(args.outPath, { protectedInputs: args.protectedInputs });
35660
36189
  await writeTextFile(abs, body);
35661
36190
  console.log(`Wrote ${args.format} output to ${abs}`);
35662
36191
  }
@@ -36508,7 +37037,7 @@ init_errors3();
36508
37037
  init_outputMeta();
36509
37038
  init_bun();
36510
37039
  import { closeSync as closeSync2, lstatSync as lstatSync2, openSync as openSync2, readlinkSync as readlinkSync2, realpathSync as realpathSync3, statSync, unlinkSync as unlinkSync2 } from "fs";
36511
- import { basename as basename3, dirname as dirname6, resolve as resolve7 } from "path";
37040
+ import { basename as basename3, dirname as dirname6, resolve as resolve8 } from "path";
36512
37041
 
36513
37042
  // src/lib/evaluation/input.ts
36514
37043
  init_dist2();
@@ -36538,17 +37067,17 @@ function evaluateRules(input) {
36538
37067
 
36539
37068
  // src/commands/judge.ts
36540
37069
  function canonical(path, depth = 0) {
36541
- const absolute = resolve7(path);
37070
+ const absolute = resolve8(path);
36542
37071
  try {
36543
37072
  return realpathSync3(absolute);
36544
37073
  } catch {
36545
37074
  try {
36546
37075
  if (depth < 32 && lstatSync2(absolute).isSymbolicLink()) {
36547
- return canonical(resolve7(dirname6(absolute), readlinkSync2(absolute)), depth + 1);
37076
+ return canonical(resolve8(dirname6(absolute), readlinkSync2(absolute)), depth + 1);
36548
37077
  }
36549
37078
  } catch {}
36550
37079
  const parent = dirname6(absolute);
36551
- return parent === absolute ? absolute : resolve7(canonical(parent, depth), basename3(absolute));
37080
+ return parent === absolute ? absolute : resolve8(canonical(parent, depth), basename3(absolute));
36552
37081
  }
36553
37082
  }
36554
37083
  function existingDirectory(path) {
@@ -36567,12 +37096,12 @@ function existingDirectory(path) {
36567
37096
  }
36568
37097
  function ignoresCase(directory) {
36569
37098
  const name = `.idlekit-case-${crypto.randomUUID()}`;
36570
- const path = resolve7(directory, name);
37099
+ const path = resolve8(directory, name);
36571
37100
  let fd;
36572
37101
  try {
36573
37102
  fd = openSync2(path, "wx");
36574
37103
  const original = statSync(path);
36575
- const folded = statSync(resolve7(directory, name.toUpperCase()));
37104
+ const folded = statSync(resolve8(directory, name.toUpperCase()));
36576
37105
  return original.dev === folded.dev && original.ino === folded.ino;
36577
37106
  } catch {
36578
37107
  return fd === undefined && process.platform === "win32";
@@ -36586,7 +37115,7 @@ function ignoresCase(directory) {
36586
37115
  }
36587
37116
  }
36588
37117
  }
36589
- function sameFile(left, right) {
37118
+ function sameFile2(left, right) {
36590
37119
  const aPath = canonical(left);
36591
37120
  const bPath = canonical(right);
36592
37121
  if (aPath === bPath)
@@ -36606,7 +37135,7 @@ function sameFile(left, right) {
36606
37135
  function ancestorFile(parent, child) {
36607
37136
  let directory = dirname6(canonical(child));
36608
37137
  for (;; ) {
36609
- if (sameFile(parent, directory))
37138
+ if (sameFile2(parent, directory))
36610
37139
  return true;
36611
37140
  const next = dirname6(directory);
36612
37141
  if (next === directory)
@@ -36638,6 +37167,17 @@ function requireFileTarget(path, flag) {
36638
37167
  current = parent;
36639
37168
  }
36640
37169
  }
37170
+ function readMetricVersion(diagnostic) {
37171
+ const ref = diagnostic?.evidenceRefs.find((item) => item.startsWith("metric-version:"));
37172
+ if (ref === undefined)
37173
+ return null;
37174
+ const version = Number(ref.slice("metric-version:".length));
37175
+ return Number.isFinite(version) ? version : null;
37176
+ }
37177
+ function versionLabel(diagnostic) {
37178
+ const version = readMetricVersion(diagnostic);
37179
+ return version === null ? "" : ` (metric version ${version})`;
37180
+ }
36641
37181
  function renderJudgeMarkdown(report) {
36642
37182
  const lines = [
36643
37183
  "# Rules judge",
@@ -36651,9 +37191,10 @@ function renderJudgeMarkdown(report) {
36651
37191
  "## Numerical",
36652
37192
  ""
36653
37193
  ];
36654
- for (const result of report.numerical.results) {
36655
- lines.push(`- ${result.metricId}: ${result.verdict} ${result.evidence.value ?? "none"} ${result.evidence.unit}`);
36656
- }
37194
+ report.numerical.results.forEach((result, index) => {
37195
+ const version = versionLabel(report.diagnostics[index]);
37196
+ lines.push(`- ${result.metricId}${version}: ${result.verdict} ${result.evidence.value ?? "none"} ${result.evidence.unit}`);
37197
+ });
36657
37198
  lines.push("", "## Estimates", "");
36658
37199
  if (report.estimatedObservations.length === 0)
36659
37200
  lines.push("None.");
@@ -36662,7 +37203,7 @@ function renderJudgeMarkdown(report) {
36662
37203
  }
36663
37204
  lines.push("", "## Semantic", "");
36664
37205
  for (const diagnostic of report.diagnostics) {
36665
- lines.push(`- ${diagnostic.status} ${diagnostic.recommendationId}: ${diagnostic.text.en}`);
37206
+ lines.push(`- ${diagnostic.status} ${diagnostic.recommendationId}${versionLabel(diagnostic)}: ${diagnostic.text.en}`);
36666
37207
  }
36667
37208
  return lines.join(`
36668
37209
  `);
@@ -36682,7 +37223,8 @@ function judgeCsvRows(report) {
36682
37223
  threshold: result.evidence.threshold,
36683
37224
  upperThreshold: result.evidence.upperThreshold,
36684
37225
  diagnosticStatus: diagnostic?.status ?? null,
36685
- recommendationId: diagnostic?.recommendationId ?? null
37226
+ recommendationId: diagnostic?.recommendationId ?? null,
37227
+ metricVersion: readMetricVersion(diagnostic)
36686
37228
  };
36687
37229
  });
36688
37230
  }
@@ -36694,14 +37236,17 @@ async function runJudge(flags, analysisPath, hooks) {
36694
37236
  if (flags.provider === "jev" && flags.format === "csv") {
36695
37237
  throw usageError("--provider jev writes json or md. csv cannot hold the rules report and the advisory together.");
36696
37238
  }
37239
+ const overwritten = flags.out ? [analysisPath, flags.rubric].find((input) => sameFile2(flags.out, input)) : undefined;
37240
+ if (overwritten)
37241
+ throw usageError(`Output path must not overwrite an input file: ${overwritten}`);
36697
37242
  if (flags.provider === "jev" && flags.cache) {
36698
- if (flags.out && sameFile(flags.cache, flags.out)) {
37243
+ if (flags.out && sameFile2(flags.cache, flags.out)) {
36699
37244
  throw usageError("--cache and --out name the same file. Give the Jev cache its own path.");
36700
37245
  }
36701
37246
  if (flags.out && (ancestorFile(flags.cache, flags.out) || ancestorFile(flags.out, flags.cache))) {
36702
37247
  throw usageError("--cache and --out cannot be ancestor paths. Give each file a separate target.");
36703
37248
  }
36704
- if (sameFile(flags.cache, analysisPath) || sameFile(flags.cache, flags.rubric)) {
37249
+ if (sameFile2(flags.cache, analysisPath) || sameFile2(flags.cache, flags.rubric)) {
36705
37250
  throw usageError("--cache names an input file. Give the Jev cache its own path.");
36706
37251
  }
36707
37252
  }
@@ -36734,6 +37279,7 @@ async function runJudge(flags, analysisPath, hooks) {
36734
37279
  await writeOutput({
36735
37280
  format: flags.format,
36736
37281
  outPath: flags.out,
37282
+ protectedInputs: [analysisPath, flags.rubric],
36737
37283
  data: flags.format === "md" ? renderJudgeMarkdown(report) : flags.format === "csv" ? judgeCsvRows(report) : report
36738
37284
  });
36739
37285
  return report;
@@ -36773,20 +37319,13 @@ async function runJudge(flags, analysisPath, hooks) {
36773
37319
  ...advisory.answers.length === 0 ? ["None."] : []
36774
37320
  ].join(`
36775
37321
  `);
36776
- if (flags.out) {
36777
- const outPath = resolve7(flags.out);
36778
- await writeTextFile(outPath, `${markdown}
36779
- `);
36780
- console.log(`Wrote md output to ${outPath}`);
36781
- } else {
36782
- await writeStdout(`${markdown}
36783
- `);
36784
- }
37322
+ await writeOutput({ format: "md", outPath: flags.out, data: markdown, protectedInputs: [analysisPath, flags.rubric] });
36785
37323
  return bundle;
36786
37324
  }
36787
37325
  await writeOutput({
36788
37326
  format: flags.format,
36789
37327
  outPath: flags.out,
37328
+ protectedInputs: [analysisPath, flags.rubric],
36790
37329
  data: bundle,
36791
37330
  meta: buildOutputMeta({
36792
37331
  command: "judge",
@@ -36827,7 +37366,7 @@ var judge_default = defineCommand({
36827
37366
  init_zod();
36828
37367
  init_errors3();
36829
37368
  init_outputMeta();
36830
- import { extname as extname3, resolve as resolve8 } from "path";
37369
+ import { extname as extname3, resolve as resolve9 } from "path";
36831
37370
 
36832
37371
  // src/lib/ltvCalibration.ts
36833
37372
  function assertFiniteNumbers(value, path) {
@@ -36858,18 +37397,50 @@ function clampCorrelation2(v) {
36858
37397
  function mean(values) {
36859
37398
  if (values.length === 0)
36860
37399
  return 0;
36861
- return values.reduce((a, b) => a + b, 0) / values.length;
37400
+ const sum = values.reduce((a, b) => a + b, 0);
37401
+ if (Number.isFinite(sum))
37402
+ return sum / values.length;
37403
+ return values.reduce((a, b) => a + b / values.length, 0);
37404
+ }
37405
+ function moments(x, y) {
37406
+ const n = Math.min(x.length, y.length);
37407
+ const mx = mean(x.slice(0, n));
37408
+ const my = mean(y.slice(0, n));
37409
+ const sums = (scaleX, scaleY) => {
37410
+ let cov = 0;
37411
+ let vx = 0;
37412
+ let vy = 0;
37413
+ for (let i = 0;i < n; i++) {
37414
+ const dx = ((x[i] ?? 0) - mx) / scaleX;
37415
+ const dy = ((y[i] ?? 0) - my) / scaleY;
37416
+ cov += dx * dy;
37417
+ vx += dx * dx;
37418
+ vy += dy * dy;
37419
+ }
37420
+ return { cov, vx, vy, scaleX, scaleY };
37421
+ };
37422
+ const plain = sums(1, 1);
37423
+ if (Number.isFinite(plain.cov) && Number.isFinite(plain.vx) && Number.isFinite(plain.vy))
37424
+ return plain;
37425
+ let scaleX = 0;
37426
+ let scaleY = 0;
37427
+ for (let i = 0;i < n; i++) {
37428
+ scaleX = Math.max(scaleX, Math.abs((x[i] ?? 0) - mx));
37429
+ scaleY = Math.max(scaleY, Math.abs((y[i] ?? 0) - my));
37430
+ }
37431
+ return sums(scaleX || 1, scaleY || 1);
37432
+ }
37433
+ function scaledVariance(sum, scale, n) {
37434
+ if (scale === 1)
37435
+ return sum / n;
37436
+ const deviation = scale * Math.sqrt(sum / n);
37437
+ return deviation * deviation;
36862
37438
  }
36863
37439
  function variance(values) {
36864
37440
  if (values.length === 0)
36865
37441
  return 0;
36866
- const m = mean(values);
36867
- let acc = 0;
36868
- for (const v of values) {
36869
- const d = v - m;
36870
- acc += d * d;
36871
- }
36872
- return acc / values.length;
37442
+ const { vx, scaleX } = moments(values, values);
37443
+ return scaledVariance(vx, scaleX, values.length);
36873
37444
  }
36874
37445
  function estimateCorrelation(x, y) {
36875
37446
  const n = Math.min(x.length, y.length);
@@ -36884,23 +37455,11 @@ function estimateCorrelation(x, y) {
36884
37455
  varianceY: 0
36885
37456
  };
36886
37457
  }
36887
- const sx = x.slice(0, n);
36888
- const sy = y.slice(0, n);
36889
- const mx = mean(sx);
36890
- const my = mean(sy);
36891
- let cov = 0;
36892
- let vx = 0;
36893
- let vy = 0;
36894
- for (let i = 0;i < n; i++) {
36895
- const dx = (sx[i] ?? 0) - mx;
36896
- const dy = (sy[i] ?? 0) - my;
36897
- cov += dx * dy;
36898
- vx += dx * dx;
36899
- vy += dy * dy;
36900
- }
36901
- const varianceX = vx / n;
36902
- const varianceY = vy / n;
36903
- if (vx <= 0.000000000001 || vy <= 0.000000000001) {
37458
+ const { cov, vx, vy, scaleX, scaleY } = moments(x.slice(0, n), y.slice(0, n));
37459
+ const varianceX = scaledVariance(vx, scaleX, n);
37460
+ const varianceY = scaledVariance(vy, scaleY, n);
37461
+ const flat = (sum, scale) => scale === 1 ? sum <= 0.000000000001 : scale * Math.sqrt(sum) <= 0.000001;
37462
+ if (flat(vx, scaleX) || flat(vy, scaleY)) {
36904
37463
  return {
36905
37464
  raw: 0,
36906
37465
  value: 0,
@@ -36930,18 +37489,53 @@ function estimateCorrelation(x, y) {
36930
37489
  varianceY
36931
37490
  };
36932
37491
  }
37492
+ var FEATURE_SOURCES = {
37493
+ conversion: ["iapRevenue"],
37494
+ arppu: ["iapRevenue"],
37495
+ adArpDau: ["adRevenue"]
37496
+ };
37497
+ var CORRELATION_SOURCES = {
37498
+ retentionConversion: ["iapRevenue"],
37499
+ retentionArppu: ["iapRevenue"],
37500
+ retentionAd: ["adRevenue"],
37501
+ conversionArppu: ["iapRevenue"],
37502
+ conversionAd: ["iapRevenue", "adRevenue"],
37503
+ arppuAd: ["iapRevenue", "adRevenue"]
37504
+ };
37505
+ function pickMeasured(keys, measured, value) {
37506
+ const out = {};
37507
+ for (const key of Object.keys(keys)) {
37508
+ if (keys[key].every((source) => measured.has(source)))
37509
+ out[key] = value(key);
37510
+ }
37511
+ return out;
37512
+ }
36933
37513
  function calibrateMonetization(rows) {
36934
37514
  if (rows.length === 0) {
36935
37515
  throw new Error("calibration rows cannot be empty");
36936
37516
  }
37517
+ const measured = new Set;
37518
+ for (const source of ["iapRevenue", "adRevenue"]) {
37519
+ const supplied = rows.filter((row) => row[source] !== undefined).length;
37520
+ if (supplied > 0 && supplied < rows.length) {
37521
+ throw new RangeError(`${source} must be supplied on every telemetry row or omitted from every row`);
37522
+ }
37523
+ if (supplied > 0)
37524
+ measured.add(source);
37525
+ }
37526
+ if (rows.some((row) => row.acquisitionCost !== undefined))
37527
+ measured.add("acquisitionCost");
37528
+ const iapMeasured = measured.has("iapRevenue");
37529
+ const adMeasured = measured.has("adRevenue");
37530
+ const revenueMeasured = iapMeasured || adMeasured;
36937
37531
  const users = new Map;
36938
37532
  for (const row of rows) {
36939
37533
  const day = Math.max(0, Math.floor(row.day));
36940
37534
  const u = users.get(row.userId) ?? { days: new Set, iap: 0, ad: 0, acq: undefined };
36941
- if (row.active ?? true)
37535
+ if ((row.active ?? true) || day === 0)
36942
37536
  u.days.add(day);
36943
- u.iap += Math.max(0, row.iapRevenue);
36944
- u.ad += Math.max(0, row.adRevenue);
37537
+ u.iap += Math.max(0, row.iapRevenue ?? 0);
37538
+ u.ad += Math.max(0, row.adRevenue ?? 0);
36945
37539
  if (row.acquisitionCost !== undefined && Number.isFinite(row.acquisitionCost)) {
36946
37540
  u.acq = (u.acq ?? 0) + Math.max(0, row.acquisitionCost);
36947
37541
  }
@@ -36986,52 +37580,25 @@ function calibrateMonetization(rows) {
36986
37580
  const adArpDau = totalAd / Math.max(1, activeUserDays);
36987
37581
  const cpi = totalAcq / Math.max(1, acqUsers);
36988
37582
  const userRetention = [];
36989
- const userConversion = [];
36990
- const userArppu = [];
36991
- const userAdArpDau = [];
37583
+ const features = { conversion: [], arppu: [], adArpDau: [] };
36992
37584
  for (const user of users.values()) {
36993
37585
  const activeDays = Math.max(1, user.days.size);
36994
- const retentionScore = clamp014(activeDays / 30);
36995
- const conversionScore = user.iap > 0 ? 1 : 0;
36996
- const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
36997
- const adScore = user.ad / activeDays;
36998
- userRetention.push(retentionScore);
36999
- userConversion.push(conversionScore);
37000
- userArppu.push(arppuScore);
37001
- userAdArpDau.push(adScore);
37002
- }
37003
- const estimatedCorrelationPair = {
37004
- retentionConversion: estimateCorrelation(userRetention, userConversion),
37005
- retentionArppu: estimateCorrelation(userRetention, userArppu),
37006
- retentionAd: estimateCorrelation(userRetention, userAdArpDau),
37007
- conversionArppu: estimateCorrelation(userConversion, userArppu),
37008
- conversionAd: estimateCorrelation(userConversion, userAdArpDau),
37009
- arppuAd: estimateCorrelation(userArppu, userAdArpDau)
37010
- };
37011
- const estimatedCorrelation = {
37012
- retentionConversion: estimatedCorrelationPair.retentionConversion.value,
37013
- retentionArppu: estimatedCorrelationPair.retentionArppu.value,
37014
- retentionAd: estimatedCorrelationPair.retentionAd.value,
37015
- conversionArppu: estimatedCorrelationPair.conversionArppu.value,
37016
- conversionAd: estimatedCorrelationPair.conversionAd.value,
37017
- arppuAd: estimatedCorrelationPair.arppuAd.value
37018
- };
37019
- const estimatedCorrelationRaw = {
37020
- retentionConversion: estimatedCorrelationPair.retentionConversion.raw,
37021
- retentionArppu: estimatedCorrelationPair.retentionArppu.raw,
37022
- retentionAd: estimatedCorrelationPair.retentionAd.raw,
37023
- conversionArppu: estimatedCorrelationPair.conversionArppu.raw,
37024
- conversionAd: estimatedCorrelationPair.conversionAd.raw,
37025
- arppuAd: estimatedCorrelationPair.arppuAd.raw
37026
- };
37027
- const correlationConfidence = {
37028
- retentionConversion: estimatedCorrelationPair.retentionConversion.confidence,
37029
- retentionArppu: estimatedCorrelationPair.retentionArppu.confidence,
37030
- retentionAd: estimatedCorrelationPair.retentionAd.confidence,
37031
- conversionArppu: estimatedCorrelationPair.conversionArppu.confidence,
37032
- conversionAd: estimatedCorrelationPair.conversionAd.confidence,
37033
- arppuAd: estimatedCorrelationPair.arppuAd.confidence
37034
- };
37586
+ userRetention.push(clamp014(activeDays / 30));
37587
+ features.conversion.push(user.iap > 0 ? 1 : 0);
37588
+ features.arppu.push(user.iap > 0 ? user.iap / activeDays : 0);
37589
+ features.adArpDau.push(user.ad / activeDays);
37590
+ }
37591
+ const correlationInputs = {
37592
+ retentionConversion: [userRetention, features.conversion],
37593
+ retentionArppu: [userRetention, features.arppu],
37594
+ retentionAd: [userRetention, features.adArpDau],
37595
+ conversionArppu: [features.conversion, features.arppu],
37596
+ conversionAd: [features.conversion, features.adArpDau],
37597
+ arppuAd: [features.arppu, features.adArpDau]
37598
+ };
37599
+ const estimatedCorrelationPair = pickMeasured(CORRELATION_SOURCES, measured, (key) => estimateCorrelation(...correlationInputs[key]));
37600
+ const correlationField = (field) => pickMeasured(CORRELATION_SOURCES, measured, (key) => estimatedCorrelationPair[key][field]);
37601
+ const estimatedCorrelation = correlationField("value");
37035
37602
  const result = {
37036
37603
  monetization: {
37037
37604
  cohorts: {
@@ -37044,44 +37611,38 @@ function calibrateMonetization(rows) {
37044
37611
  d90: clamp014(Math.min(d1, d7, d30, d90)),
37045
37612
  longTailDailyDecay: 0.02
37046
37613
  },
37047
- revenue: {
37048
- payerConversion: clamp014(payerConversion),
37049
- arppuDaily: Math.max(0, arppuDaily),
37050
- adArpDau: Math.max(0, adArpDau),
37051
- platformFeeRate: 0.3,
37052
- grossMarginRate: 0.92
37053
- },
37054
- acquisition: {
37055
- cpi: Math.max(0, cpi)
37056
- },
37057
- uncertainty: {
37058
- enabled: true,
37059
- draws: 300,
37060
- quantiles: [0.5, 0.9],
37061
- sigma: {
37062
- retention: 0.08,
37063
- conversion: 0.12,
37064
- arppu: 0.2,
37065
- ad: 0.15
37066
- },
37067
- correlation: {
37068
- retentionConversion: estimatedCorrelation.retentionConversion,
37069
- retentionArppu: estimatedCorrelation.retentionArppu,
37070
- retentionAd: estimatedCorrelation.retentionAd,
37071
- conversionArppu: estimatedCorrelation.conversionArppu,
37072
- conversionAd: estimatedCorrelation.conversionAd,
37073
- arppuAd: estimatedCorrelation.arppuAd
37614
+ ...revenueMeasured ? {
37615
+ revenue: {
37616
+ ...iapMeasured ? { payerConversion: clamp014(payerConversion), arppuDaily: Math.max(0, arppuDaily) } : {},
37617
+ ...adMeasured ? { adArpDau: Math.max(0, adArpDau) } : {},
37618
+ platformFeeRate: 0.3,
37619
+ grossMarginRate: 0.92
37074
37620
  }
37075
- }
37621
+ } : {},
37622
+ ...measured.has("acquisitionCost") ? { acquisition: { cpi: Math.max(0, cpi) } } : {},
37623
+ ...revenueMeasured ? {
37624
+ uncertainty: {
37625
+ enabled: true,
37626
+ draws: 300,
37627
+ quantiles: [0.5, 0.9],
37628
+ sigma: {
37629
+ retention: 0.08,
37630
+ conversion: 0.12,
37631
+ arppu: 0.2,
37632
+ ad: 0.15
37633
+ },
37634
+ correlation: estimatedCorrelation
37635
+ }
37636
+ } : {}
37076
37637
  },
37077
37638
  diagnostics: {
37078
37639
  users: cohortSize,
37079
37640
  totalRows: rows.length,
37080
- totalIap,
37081
- totalAd,
37641
+ ...iapMeasured ? { totalIap } : {},
37642
+ ...adMeasured ? { totalAd } : {},
37082
37643
  activeUserDays,
37083
- payerUsers,
37084
- payerDays,
37644
+ ...iapMeasured ? { payerUsers, payerDays } : {},
37645
+ ...measured.size < 3 ? { unmeasured: ["iapRevenue", "adRevenue", "acquisitionCost"].filter((source) => !measured.has(source)) } : {},
37085
37646
  retention: {
37086
37647
  d1,
37087
37648
  d7,
@@ -37090,14 +37651,14 @@ function calibrateMonetization(rows) {
37090
37651
  },
37091
37652
  userFeatureVariance: {
37092
37653
  retention: variance(userRetention),
37093
- conversion: variance(userConversion),
37094
- arppu: variance(userArppu),
37095
- adArpDau: variance(userAdArpDau)
37654
+ ...pickMeasured(FEATURE_SOURCES, measured, (key) => variance(features[key]))
37096
37655
  },
37097
- estimatedCorrelation,
37098
- estimatedCorrelationRaw,
37099
- correlationConfidence,
37100
- correlationDiagnostics: estimatedCorrelationPair
37656
+ ...revenueMeasured ? {
37657
+ estimatedCorrelation,
37658
+ estimatedCorrelationRaw: correlationField("raw"),
37659
+ correlationConfidence: correlationField("confidence"),
37660
+ correlationDiagnostics: estimatedCorrelationPair
37661
+ } : {}
37101
37662
  }
37102
37663
  };
37103
37664
  assertFiniteNumbers(result, "calibration");
@@ -37108,115 +37669,7 @@ init_dist2();
37108
37669
  // src/lib/ltvUncertainty.ts
37109
37670
  init_dist2();
37110
37671
  // src/lib/cohortObservation.ts
37111
- var RETENTION_DAYS = [1, 7, 30, 90];
37112
- function observeCohort(rows, observedThroughDay) {
37113
- if (!Number.isSafeInteger(observedThroughDay) || observedThroughDay < 0)
37114
- throw new Error("observed-through-day must be a nonnegative safe integer");
37115
- if (rows.length === 0)
37116
- throw new Error("Observed cohort requires at least one install record");
37117
- const users = new Map;
37118
- for (const row of rows) {
37119
- if (!row.userId.trim() || !Number.isSafeInteger(row.day) || row.day < 0 || row.day > observedThroughDay) {
37120
- throw new Error("Observed cohort requires a user ID and integer days within observed-through-day");
37121
- }
37122
- if (![row.iapRevenue, row.adRevenue, ...row.acquisitionCost === undefined ? [] : [row.acquisitionCost]].every((v) => Number.isFinite(v) && v >= 0)) {
37123
- throw new Error("Observed cohort revenue and acquisition costs must be finite and nonnegative");
37124
- }
37125
- if (row.active !== undefined && typeof row.active !== "boolean")
37126
- throw new Error("Observed cohort active must be boolean");
37127
- const user = users.get(row.userId) ?? { days: new Set, active: new Set };
37128
- if (user.days.has(row.day))
37129
- throw new Error("Observed cohort requires one aggregated row per user and day");
37130
- user.days.add(row.day);
37131
- if (row.active ?? true)
37132
- user.active.add(row.day);
37133
- users.set(row.userId, user);
37134
- }
37135
- for (const user of users.values()) {
37136
- if (!user.days.has(0))
37137
- throw new Error("Observed cohort requires a day 0 install record for every user, including inactive users");
37138
- }
37139
- const n = users.size;
37140
- const observation = {
37141
- observedThroughDay,
37142
- users: n,
37143
- retention: RETENTION_DAYS.map((day) => {
37144
- if (day > observedThroughDay)
37145
- return { day, status: "not-observed", users: n, activeUsers: null, rate: null, interval95: null };
37146
- const activeUsers = [...users.values()].filter((user) => user.active.has(day)).length;
37147
- const rate = activeUsers / n;
37148
- const z2 = 1.959963984540054 ** 2;
37149
- const denominator = 1 + z2 / n;
37150
- const center = (rate + z2 / (2 * n)) / denominator;
37151
- const radius = Math.sqrt(z2 * (rate * (1 - rate) / n + z2 / (4 * n * n))) / denominator;
37152
- return { day, status: "observed", users: n, activeUsers, rate, interval95: { min: activeUsers === 0 ? 0 : Math.max(0, center - radius), max: activeUsers === n ? 1 : Math.min(1, center + radius) } };
37153
- })
37154
- };
37155
- assertCohortObservation(observation);
37156
- return observation;
37157
- }
37158
- function assertCohortObservation(observation) {
37159
- if (!Number.isSafeInteger(observation.users) || observation.users < 1 || !Number.isSafeInteger(observation.observedThroughDay) || observation.observedThroughDay < 0) {
37160
- throw new Error("Invalid cohort population or observation cutoff");
37161
- }
37162
- if (observation.retention.length !== RETENTION_DAYS.length)
37163
- throw new Error("Cohort retention must contain D1, D7, D30 and D90");
37164
- for (const [index, point] of observation.retention.entries()) {
37165
- if (point.day !== RETENTION_DAYS[index] || point.users !== observation.users)
37166
- throw new Error("Cohort retention days and population must match the observation");
37167
- const observed = point.day <= observation.observedThroughDay;
37168
- if (point.status !== (observed ? "observed" : "not-observed"))
37169
- throw new Error("Cohort retention status must match the observation cutoff");
37170
- if (!observed) {
37171
- if (point.activeUsers !== null || point.rate !== null || point.interval95 !== null)
37172
- throw new Error("Future retention must remain unobserved");
37173
- continue;
37174
- }
37175
- if (point.activeUsers === null || !Number.isSafeInteger(point.activeUsers) || point.activeUsers < 0 || point.activeUsers > point.users || point.rate !== point.activeUsers / point.users) {
37176
- throw new Error("Retained users and retention rate must match the installed population");
37177
- }
37178
- const interval = point.interval95;
37179
- if (point.rate === null || !interval || !Number.isFinite(interval.min) || !Number.isFinite(interval.max) || interval.min < 0 || interval.max > 1 || interval.min > point.rate || interval.max < point.rate) {
37180
- throw new Error("Retention confidence interval must be finite and contain the observed rate");
37181
- }
37182
- }
37183
- }
37184
- function observedRetentionRates(observation) {
37185
- return Object.fromEntries(observation.retention.filter((row) => row.rate !== null).map((row) => [`d${row.day}`, row.rate]));
37186
- }
37187
- function planRetentionPatch(observation, assumptions = {}) {
37188
- const rates = observedRetentionRates(observation);
37189
- if (Object.keys(rates).length === 0)
37190
- return { status: "unavailable", retention: {}, warnings: ["No retention day has been fully observed."] };
37191
- const merged = { ...assumptions, ...rates };
37192
- const warnings = [];
37193
- for (const day of RETENTION_DAYS) {
37194
- if (merged[`d${day}`] === undefined)
37195
- warnings.push(`Unobserved D${day} has no explicit retained assumption. An LTV default could conflict with the observations; supply this assumption in --scenario or observe this day before applying a patch.`);
37196
- }
37197
- let previous;
37198
- for (const day of RETENTION_DAYS) {
37199
- const rate = merged[`d${day}`];
37200
- if (rate === undefined)
37201
- continue;
37202
- if (previous && previous.rate < rate)
37203
- warnings.push(`D${previous.day}=${previous.rate} is below D${day}=${rate}. LTV requires d1 >= d7 >= d30 >= d90; review observations and retained assumptions before applying a patch.`);
37204
- previous = { day, rate };
37205
- }
37206
- return warnings.length ? { status: "requires-review", retention: {}, warnings } : { status: "ready", retention: rates, warnings };
37207
- }
37208
- function compareRetention(observation, assumptions) {
37209
- return observation.retention.map((row) => {
37210
- const assumption = assumptions[`d${row.day}`] ?? null;
37211
- return {
37212
- day: row.day,
37213
- assumption,
37214
- observed: row.rate,
37215
- differencePercentagePoints: row.rate === null || assumption === null ? null : (row.rate - assumption) * 100,
37216
- status: row.rate === null ? "not-observed" : assumption === null ? "unconfigured" : "compared"
37217
- };
37218
- });
37219
- }
37672
+ init_dist2();
37220
37673
  function renderCohortMarkdown(observation, comparison, patch) {
37221
37674
  const percent = (value) => value === null ? "unavailable" : `${(value * 100).toFixed(2)}%`;
37222
37675
  const lines = [
@@ -37348,13 +37801,14 @@ function parseCsvTelemetry(raw, strictCohort = false) {
37348
37801
  throw usageError("Observed cohort requires explicit user_id and day fields");
37349
37802
  const userId = userInput ?? `anon-${i}`;
37350
37803
  const dayRaw = dayInput ?? "0";
37351
- const iapRaw = pick2(rec, ["iap_revenue", "revenue", "iap", "purchase_revenue"]) ?? "0";
37352
- const adRaw = pick2(rec, ["ad_revenue", "adrevenue", "ads"]) ?? "0";
37804
+ const missingRevenue = strictCohort ? undefined : "0";
37805
+ const iapRaw = pick2(rec, ["iap_revenue", "revenue", "iap", "purchase_revenue"]) ?? missingRevenue;
37806
+ const adRaw = pick2(rec, ["ad_revenue", "adrevenue", "ads"]) ?? missingRevenue;
37353
37807
  const activeRaw = pick2(rec, ["active", "is_active"]);
37354
37808
  const acqRaw = pick2(rec, ["acquisition_cost", "ua_cost", "cpi"]);
37355
37809
  const day = parseNumeric(dayRaw, i + 1, "day");
37356
- const iapRevenue = parseNumeric(iapRaw, i + 1, "revenue");
37357
- const adRevenue = parseNumeric(adRaw, i + 1, "ad_revenue");
37810
+ const iapRevenue = iapRaw === undefined ? undefined : parseNumeric(iapRaw, i + 1, "revenue");
37811
+ const adRevenue = adRaw === undefined ? undefined : parseNumeric(adRaw, i + 1, "ad_revenue");
37358
37812
  const acquisitionCost = acqRaw !== undefined ? parseNumeric(acqRaw, i + 1, "acquisition_cost", { allowEmpty: true }) : undefined;
37359
37813
  const active = parseActive(activeRaw, strictCohort);
37360
37814
  rows.push({
@@ -37390,11 +37844,14 @@ function parseJsonTelemetry(raw, strictCohort = false) {
37390
37844
  }
37391
37845
  const userId = String(userInput ?? `anon-${idx}`);
37392
37846
  const day = Number(dayInput ?? 0);
37393
- const iapRevenue = Number(r.iapRevenue ?? r.iap_revenue ?? r.revenue ?? 0);
37394
- const adRevenue = Number(r.adRevenue ?? r.ad_revenue ?? 0);
37847
+ const missingRevenue = strictCohort ? undefined : 0;
37848
+ const iapInput = r.iapRevenue ?? r.iap_revenue ?? r.revenue ?? missingRevenue;
37849
+ const adInput = r.adRevenue ?? r.ad_revenue ?? missingRevenue;
37850
+ const iapRevenue = iapInput === undefined ? undefined : Number(iapInput);
37851
+ const adRevenue = adInput === undefined ? undefined : Number(adInput);
37395
37852
  const acquisitionCost = r.acquisitionCost !== undefined || r.acquisition_cost !== undefined || r.cpi !== undefined ? Number(r.acquisitionCost ?? r.acquisition_cost ?? r.cpi) : undefined;
37396
37853
  const active = parseActive(r.active, strictCohort);
37397
- if (!Number.isFinite(day) || !Number.isFinite(iapRevenue) || !Number.isFinite(adRevenue)) {
37854
+ if (!Number.isFinite(day) || ![iapRevenue, adRevenue].every((v) => v === undefined || Number.isFinite(v))) {
37398
37855
  throw cliError("CLI_USAGE", `Invalid numeric value in json row ${idx}`);
37399
37856
  }
37400
37857
  rows.push({
@@ -37429,7 +37886,7 @@ var calibrate_default = defineCommand({
37429
37886
  }
37430
37887
  if (flags.scenario && flags["observed-through-day"] === undefined)
37431
37888
  throw usageError("--scenario requires --observed-through-day for a mature cohort comparison");
37432
- const abs = resolve8(process.cwd(), telemetryPath);
37889
+ const abs = resolve9(process.cwd(), telemetryPath);
37433
37890
  const raw = await readTextFile(abs);
37434
37891
  const format = flags["input-format"] === "auto" ? extname3(abs).toLowerCase() : flags["input-format"];
37435
37892
  const rows = format === ".csv" || format === "csv" ? parseCsvTelemetry(raw, flags["observed-through-day"] !== undefined) : parseJsonTelemetry(raw, flags["observed-through-day"] !== undefined);
@@ -37468,12 +37925,12 @@ var calibrate_default = defineCommand({
37468
37925
  command: "calibrate",
37469
37926
  telemetry: observation ? { rows, observedThroughDay: observation.observedThroughDay } : rows,
37470
37927
  scenario,
37471
- scenarioPath: flags.scenario ? resolve8(flags.scenario) : undefined
37928
+ scenarioPath: flags.scenario ? resolve9(flags.scenario) : undefined
37472
37929
  });
37473
37930
  await writeOutput({
37474
37931
  format: flags.format,
37475
37932
  outPath: flags.out,
37476
- protectedInputs: [abs, ...flags.scenario ? [resolve8(flags.scenario)] : []],
37933
+ protectedInputs: [abs, ...flags.scenario ? [resolve9(flags.scenario)] : []],
37477
37934
  data: flags.format === "json" ? {
37478
37935
  ok: true,
37479
37936
  source: abs,
@@ -37513,7 +37970,7 @@ var calibrate_default = defineCommand({
37513
37970
  payerConversion: (calibrated.monetization?.revenue?.payerConversion ?? 0).toFixed(4),
37514
37971
  arppuDaily: (calibrated.monetization?.revenue?.arppuDaily ?? 0).toFixed(4),
37515
37972
  adArpDau: (calibrated.monetization?.revenue?.adArpDau ?? 0).toFixed(4),
37516
- cpi: (calibrated.monetization?.acquisition?.cpi ?? 0).toFixed(4)
37973
+ cpi: calibrated.monetization?.acquisition?.cpi?.toFixed(4) ?? ""
37517
37974
  }
37518
37975
  ],
37519
37976
  meta: outputMeta
@@ -37523,12 +37980,12 @@ var calibrate_default = defineCommand({
37523
37980
 
37524
37981
  // src/commands/balance.ts
37525
37982
  init_zod();
37526
- import { resolve as resolve11 } from "path";
37983
+ import { resolve as resolve12 } from "path";
37527
37984
 
37528
37985
  // src/balance/workflow.ts
37529
37986
  init_dist2();
37530
37987
  init_zod();
37531
- import { resolve as resolve10, dirname as dirname8 } from "path";
37988
+ import { resolve as resolve11, dirname as dirname8 } from "path";
37532
37989
  import { stat as stat2 } from "fs/promises";
37533
37990
 
37534
37991
  // src/cliMeta.ts
@@ -37541,6 +37998,42 @@ var CLI_DESCRIPTION = import_package2.default.description ?? "Bun-first idle gam
37541
37998
  // src/balance/workflow.ts
37542
37999
  init_bun();
37543
38000
 
38001
+ // src/balance/pacingAmount.ts
38002
+ var MAX_DECIMAL_LENGTH = 200;
38003
+ var DECIMAL = /^([+-]?)(?:(\d+)(?:\.(\d+))?|\.(\d+))(?:e([+-]?\d+))?$/i;
38004
+ function pacingValue(value) {
38005
+ if (typeof value === "number")
38006
+ return Number.isFinite(value) ? Object.is(value, -0) ? 0 : value : undefined;
38007
+ return typeof value === "string" && value.length <= MAX_DECIMAL_LENGTH && DECIMAL.test(value) ? value : undefined;
38008
+ }
38009
+ function parts(value) {
38010
+ if (pacingValue(value) === undefined)
38011
+ throw new RangeError("Pacing value must be finite or bounded decimal text");
38012
+ const match = DECIMAL.exec(String(value));
38013
+ const fraction = match[3] ?? match[4] ?? "";
38014
+ const digits = `${match[2] ?? ""}${fraction}`.replace(/^0+/, "");
38015
+ const sign = digits.length === 0 ? 0 : match[1] === "-" ? -1 : 1;
38016
+ return {
38017
+ sign,
38018
+ digits: digits.replace(/0+$/, ""),
38019
+ magnitude: BigInt(match[5] ?? "0") - BigInt(fraction.length) + BigInt(digits.length)
38020
+ };
38021
+ }
38022
+ function comparePacingValues(left, right) {
38023
+ if (typeof left === "number" && typeof right === "number")
38024
+ return left < right ? -1 : left > right ? 1 : 0;
38025
+ const a = parts(left), b = parts(right);
38026
+ if (a.sign !== b.sign)
38027
+ return a.sign < b.sign ? -1 : 1;
38028
+ if (a.sign === 0)
38029
+ return 0;
38030
+ if (a.magnitude !== b.magnitude)
38031
+ return (a.magnitude < b.magnitude ? -1 : 1) * a.sign;
38032
+ const length = Math.max(a.digits.length, b.digits.length);
38033
+ const ad = a.digits.padEnd(length, "0"), bd = b.digits.padEnd(length, "0");
38034
+ return (ad < bd ? -1 : ad > bd ? 1 : 0) * a.sign;
38035
+ }
38036
+
37544
38037
  // src/balance/pacing.ts
37545
38038
  var PACING_LIMITS = Object.freeze({
37546
38039
  maxRuns: 1000,
@@ -37582,6 +38075,12 @@ function finite2(value, label) {
37582
38075
  invalid(`${label} must be finite`);
37583
38076
  return Object.is(value, -0) ? 0 : value;
37584
38077
  }
38078
+ function bound(value, label) {
38079
+ const parsed = pacingValue(value);
38080
+ if (parsed === undefined)
38081
+ invalid(`${label} must be finite or decimal text of at most 200 characters`);
38082
+ return parsed;
38083
+ }
37585
38084
  function limit(value, maximum, label) {
37586
38085
  if (value === undefined)
37587
38086
  return maximum;
@@ -37640,11 +38139,11 @@ function planPacingRuns(input) {
37640
38139
  if (metrics.has(metric))
37641
38140
  invalid(`duplicate target metric ${metric}`);
37642
38141
  metrics.add(metric);
37643
- const min = raw.min === undefined ? undefined : finite2(raw.min, `targets[${index}].min`);
37644
- const max = raw.max === undefined ? undefined : finite2(raw.max, `targets[${index}].max`);
38142
+ const min = raw.min === undefined ? undefined : bound(raw.min, `targets[${index}].min`);
38143
+ const max = raw.max === undefined ? undefined : bound(raw.max, `targets[${index}].max`);
37645
38144
  if (min === undefined && max === undefined)
37646
38145
  invalid(`target ${metric} needs min or max`);
37647
- if (min !== undefined && max !== undefined && min > max)
38146
+ if (min !== undefined && max !== undefined && comparePacingValues(min, max) > 0)
37648
38147
  invalid(`target ${metric} min exceeds max`);
37649
38148
  return Object.freeze({ metric, unit, ...min !== undefined ? { min } : {}, ...max !== undefined ? { max } : {} });
37650
38149
  }).sort((left, right) => compareText(left.metric, right.metric));
@@ -37723,11 +38222,12 @@ function checkTarget(target, metrics) {
37723
38222
  const value = metrics[target.metric];
37724
38223
  if (value === null)
37725
38224
  return { ...target, value, status: "unreached", message: "Metric not reached within the run horizon" };
37726
- if (typeof value !== "number" || !Number.isFinite(value)) {
37727
- return { ...target, value: null, status: "error", message: "Evaluator metric must be a finite number or null" };
38225
+ const parsed = pacingValue(value);
38226
+ if (parsed === undefined) {
38227
+ return { ...target, value: null, status: "error", message: "Evaluator metric must be a finite number, bounded decimal text or null" };
37728
38228
  }
37729
- const breached = target.min !== undefined && value < target.min || target.max !== undefined && value > target.max;
37730
- return { ...target, value: Object.is(value, -0) ? 0 : value, status: breached ? "breach" : "pass" };
38229
+ const breached = target.min !== undefined && comparePacingValues(parsed, target.min) < 0 || target.max !== undefined && comparePacingValues(parsed, target.max) > 0;
38230
+ return { ...target, value: parsed, status: breached ? "breach" : "pass" };
37731
38231
  }
37732
38232
  async function runPacingChecks(input, evaluator) {
37733
38233
  const plan = planPacingRuns(input);
@@ -37776,7 +38276,7 @@ async function runPacingChecks(input, evaluator) {
37776
38276
  init_bun();
37777
38277
  import { constants } from "fs";
37778
38278
  import { lstat, mkdir, open as open2, realpath as realpath2, rename, rm } from "fs/promises";
37779
- import { dirname as dirname7, isAbsolute as isAbsolute2, join, relative as relative2, resolve as resolve9, sep as sep2 } from "path";
38279
+ import { dirname as dirname7, isAbsolute as isAbsolute2, join, relative as relative2, resolve as resolve10, sep as sep2 } from "path";
37780
38280
  var SHEET_LIMITS = Object.freeze({
37781
38281
  maxCsvBytes: 2 * 1024 * 1024,
37782
38282
  maxRows: 10001,
@@ -38148,7 +38648,7 @@ async function snapshotInputs(inputFiles, contextValue, limits) {
38148
38648
  const inputs = Object.create(null);
38149
38649
  let total = 0;
38150
38650
  for (const name of validInputNames(inputFiles, limits)) {
38151
- const path = resolve9(inputFiles[name]);
38651
+ const path = resolve10(inputFiles[name]);
38152
38652
  const realPath = await realpath2(path);
38153
38653
  const bytes = await boundedRead(realPath, Math.min(limits.maxInputBytes, limits.maxTotalInputBytes - total));
38154
38654
  total += bytes.length;
@@ -38174,7 +38674,7 @@ async function snapshotInputs(inputFiles, contextValue, limits) {
38174
38674
  });
38175
38675
  }
38176
38676
  async function canonicalOutput(path) {
38177
- path = resolve9(path);
38677
+ path = resolve10(path);
38178
38678
  try {
38179
38679
  return await realpath2(path);
38180
38680
  } catch (error) {
@@ -38213,7 +38713,7 @@ async function refreshSheetBundle(options) {
38213
38713
  validInputNames(options.inputFiles, limits);
38214
38714
  const output = await canonicalOutput(options.outputDir);
38215
38715
  for (const [name, path] of Object.entries(options.inputFiles)) {
38216
- if (isWithin(output, resolve9(path)) || isWithin(output, await realpath2(path)))
38716
+ if (isWithin(output, resolve10(path)) || isWithin(output, await realpath2(path)))
38217
38717
  throw new Error(`Output/input collision: ${name}`);
38218
38718
  }
38219
38719
  await mkdir(output, { recursive: true });
@@ -38473,7 +38973,7 @@ function normalizeRunDocument(document, strategy, horizon) {
38473
38973
  target.outputs = { ...target.outputs, report: { ...target.outputs?.report, includeTrace: true, traceEverySteps: MAX_STEPS_PER_RUN + 1 } };
38474
38974
  }
38475
38975
  async function refreshBalanceWorkflow(configPath, flags, check = false) {
38476
- const absoluteConfig = resolve10(configPath);
38976
+ const absoluteConfig = resolve11(configPath);
38477
38977
  const configInfo = await stat2(absoluteConfig);
38478
38978
  if (!configInfo.isFile() || configInfo.size > SHEET_LIMITS.maxInputBytes)
38479
38979
  throw new Error("Workflow config is not a bounded regular file");
@@ -38482,17 +38982,18 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38482
38982
  const base = dirname8(absoluteConfig);
38483
38983
  const inputFiles = {
38484
38984
  config: absoluteConfig,
38485
- sheet: resolve10(base, config.sheet),
38486
- schema: resolve10(base, config.schema),
38487
- scenario: resolve10(base, config.scenario)
38985
+ sheet: resolve11(base, config.sheet),
38986
+ schema: resolve11(base, config.schema),
38987
+ scenario: resolve11(base, config.scenario)
38488
38988
  };
38489
38989
  if (flags["plugin-trust-file"])
38490
- inputFiles.pluginTrust = resolve10(flags["plugin-trust-file"]);
38990
+ inputFiles.pluginTrust = resolve11(flags["plugin-trust-file"]);
38491
38991
  const loaded = await loadRegistriesFromFlags(flags);
38492
38992
  for (const [index, path] of Object.keys(loaded.pluginDigest).entries())
38493
38993
  inputFiles[`plugin${index}`] = path;
38494
- const context = { cliVersion: CLI_VERSION, bunVersion: Bun.version, pluginDigests: JSON.stringify(loaded.pluginDigest), engine: "number" };
38495
- const outputDir = resolve10(base, config.outputDir);
38994
+ const engine = resolveEffectiveEngine({ requested: flags.engine });
38995
+ const context = { cliVersion: CLI_VERSION, bunVersion: Bun.version, pluginDigests: JSON.stringify(loaded.pluginDigest), engine: engine.effectiveId };
38996
+ const outputDir = resolve11(base, config.outputDir);
38496
38997
  if (check)
38497
38998
  return readCurrentBundle(outputDir, inputFiles, { context });
38498
38999
  let outcome;
@@ -38528,7 +39029,7 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38528
39029
  const metricIds = new Set(config.metrics.map((metric) => metric.id));
38529
39030
  if (metricIds.size !== config.metrics.length)
38530
39031
  throw new Error("Metric IDs must be unique");
38531
- const targets = config.pacing.targets ?? [];
39032
+ const targets = plan.targets;
38532
39033
  for (const target of targets)
38533
39034
  if (!metricIds.has(target.metric))
38534
39035
  throw new Error(`Unknown target metric: ${target.metric}`);
@@ -38550,6 +39051,9 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38550
39051
  const unit = metric.kind === "firstAction" || metric.kind === "firstActionPrefix" ? "seconds" : metric.kind === "endWallet" || metric.kind === "endNetWorth" ? baselineDocument.unit.code : "count";
38551
39052
  if (target.unit !== unit)
38552
39053
  throw new Error(`Target ${target.metric} must use unit '${unit}'`);
39054
+ if (metric.kind !== "endWallet" && metric.kind !== "endNetWorth" && (typeof target.min === "string" || typeof target.max === "string")) {
39055
+ throw new Error(`Target ${target.metric} requires finite numeric time or count bounds`);
39056
+ }
38553
39057
  }
38554
39058
  const materialize = (patches) => {
38555
39059
  const candidate = applySheetCsv(template, schema, overlayCsv(canonicalCsv, patches));
@@ -38567,7 +39071,7 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38567
39071
  modelRegistry: loaded.modelRegistry,
38568
39072
  strategyRegistry: loaded.strategyRegistry,
38569
39073
  pluginDigest: loaded.pluginDigest,
38570
- engineRequest: "number",
39074
+ engineRequest: engine.effectiveId,
38571
39075
  seed: descriptor.seed
38572
39076
  });
38573
39077
  const opened = prepared.open("simulate", `simulate:${descriptor.seed}`, { durationSec: descriptor.horizonSec });
@@ -38589,6 +39093,13 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38589
39093
  throw new Error("Action history exceeded the result budget; pacing cannot be inferred from truncated history");
38590
39094
  const actions = run.actionsLog ?? [];
38591
39095
  const result = {};
39096
+ const monetary = (amount) => {
39097
+ const E = opened.scenario.ctx.E;
39098
+ if (!E.isFinite(amount))
39099
+ throw new Error("Monetary metric overflowed the selected engine");
39100
+ const decimal = E.toString(amount);
39101
+ return engine.effectiveId === "number" ? Number(decimal) : decimal;
39102
+ };
38592
39103
  for (const metric of config.metrics) {
38593
39104
  if (metric.kind === "firstAction" || metric.kind === "firstActionPrefix") {
38594
39105
  const action = actions.find((item) => metric.kind === "firstAction" ? item.actionId === metric.actionId : item.actionId.startsWith(metric.prefix));
@@ -38598,12 +39109,12 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38598
39109
  else if (metric.kind === "actionCount")
38599
39110
  result[metric.id] = actions.length;
38600
39111
  else if (metric.kind === "endWallet")
38601
- result[metric.id] = Number(run.end.wallet.money.amount);
39112
+ result[metric.id] = monetary(run.end.wallet.money.amount);
38602
39113
  else {
38603
39114
  const worth = opened.scenario.model.netWorth?.(opened.scenario.ctx, run.end);
38604
39115
  if (!worth)
38605
39116
  throw new Error("endNetWorth requires model.netWorth; wallet is not a substitute");
38606
- result[metric.id] = Number(worth.amount);
39117
+ result[metric.id] = monetary(worth.amount);
38607
39118
  }
38608
39119
  }
38609
39120
  return result;
@@ -38661,7 +39172,7 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38661
39172
  `);
38662
39173
  return { id: variant.id, scenario: name };
38663
39174
  });
38664
- addArtifact("variants.json", JSON.stringify({ version: 1, engine: "number", seeds: plan.seeds, horizonSec: horizon, strategy: plan.strategy, variants }, null, 2) + `
39175
+ addArtifact("variants.json", JSON.stringify({ version: 1, engine: engine.effectiveId, seeds: plan.seeds, horizonSec: horizon, strategy: plan.strategy, variants }, null, 2) + `
38665
39176
  `);
38666
39177
  }
38667
39178
  return artifacts;
@@ -38674,12 +39185,24 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
38674
39185
  function cell(value) {
38675
39186
  return String(value).replaceAll("|", "\\|").replace(/[\r\n]/g, " ");
38676
39187
  }
39188
+ function decimals(value) {
39189
+ const [mantissa = "", exponent = "0"] = String(value).split("e");
39190
+ return Math.max(0, (mantissa.split(".")[1]?.length ?? 0) - Number(exponent));
39191
+ }
39192
+ function gap(larger, smaller) {
39193
+ const difference = larger - smaller;
39194
+ if (!Number.isFinite(difference) || difference === 0)
39195
+ return difference;
39196
+ const lastPlace = Math.max(decimals(larger), decimals(smaller));
39197
+ const digits = lastPlace + Math.floor(Math.log10(Math.abs(difference))) + 1;
39198
+ return Number(difference.toPrecision(Math.min(17, Math.max(1, digits))));
39199
+ }
38677
39200
  function explain(result) {
38678
39201
  if (result.status === "breach" && result.value !== null) {
38679
- if (result.min !== undefined && result.value < result.min)
38680
- return `below the minimum by ${result.min - result.value} ${result.unit}`;
38681
- if (result.max !== undefined && result.value > result.max)
38682
- return `above the maximum by ${result.value - result.max} ${result.unit}`;
39202
+ if (result.min !== undefined && comparePacingValues(result.value, result.min) < 0)
39203
+ return typeof result.min === "number" && typeof result.value === "number" ? `below the minimum by ${gap(result.min, result.value)} ${result.unit}` : `below the minimum ${result.min} ${result.unit}`;
39204
+ if (result.max !== undefined && comparePacingValues(result.value, result.max) > 0)
39205
+ return typeof result.max === "number" && typeof result.value === "number" ? `above the maximum by ${gap(result.value, result.max)} ${result.unit}` : `above the maximum ${result.max} ${result.unit}`;
38683
39206
  }
38684
39207
  if (result.status === "pass")
38685
39208
  return "within the inclusive target bounds";
@@ -38718,6 +39241,7 @@ var balance_default = defineCommand({
38718
39241
  description: "Refresh a typed CSV balance sheet into scenario, pacing checks, and result sheets",
38719
39242
  options: {
38720
39243
  ...pluginOptions(),
39244
+ engine: option(string2().min(1).optional(), { description: "Numeric engine (default: number; explicit: breakInfinity)" }),
38721
39245
  check: option(exports_coerce.boolean().default(false), { description: "Check result freshness without rerunning simulations" }),
38722
39246
  format: option(_enum(["json", "md"]).default("json"), { description: "JSON manifest or human-readable target explanations" })
38723
39247
  },
@@ -38725,7 +39249,7 @@ var balance_default = defineCommand({
38725
39249
  if (positional.length !== 1)
38726
39250
  throw usageError("Usage: idk balance <workflow.json> [--check] [plugin options]");
38727
39251
  const result = await refreshBalanceWorkflow(positional[0], flags, flags.check);
38728
- const body = flags.format === "json" ? JSON.stringify(result, null, 2) : ("state" in result) ? renderBalanceFreshness(result) : renderBalanceReport(JSON.parse(await readTextFile(resolve11(result.generationPath, "results.json"))), result.generationPath);
39252
+ const body = flags.format === "json" ? JSON.stringify(result, null, 2) : ("state" in result) ? renderBalanceFreshness(result) : renderBalanceReport(JSON.parse(await readTextFile(resolve12(result.generationPath, "results.json"))), result.generationPath);
38729
39253
  await writeStdout(body + `
38730
39254
  `);
38731
39255
  if (flags.check && "state" in result && result.state !== "current")
@@ -38770,7 +39294,6 @@ import { resolve as resolve13 } from "path";
38770
39294
  // src/io/replayArtifact.ts
38771
39295
  init_outputMeta();
38772
39296
  init_bun();
38773
- import { resolve as resolve12 } from "path";
38774
39297
  var REDACTED_KEYS = new Set([
38775
39298
  "generatedAt",
38776
39299
  "scenario",
@@ -38836,7 +39359,7 @@ function toReplayCommandLine(args) {
38836
39359
  return `bun run --cwd packages/cli dev -- ${rendered}`;
38837
39360
  }
38838
39361
  async function writeReplayArtifact(args) {
38839
- const abs = resolve12(args.outPath);
39362
+ const abs = await assertOutputNotInput(args.outPath);
38840
39363
  const runId = args.meta.runId;
38841
39364
  const seed = args.meta.seed;
38842
39365
  const scenarioHash = args.meta.scenarioHash;
@@ -39174,6 +39697,7 @@ function compareIdentity2(args) {
39174
39697
  return {
39175
39698
  a: side(args.scenarios.a),
39176
39699
  b: side(args.scenarios.b),
39700
+ ...args.engineId && args.engineId !== "number" ? { engineId: args.engineId } : {},
39177
39701
  pluginDigests: pluginDigestValues(args.loaded.pluginDigest),
39178
39702
  metrics,
39179
39703
  bundle: flags.bundle ?? null,
@@ -39181,7 +39705,13 @@ function compareIdentity2(args) {
39181
39705
  milestoneKey: metrics.includes("timeToMilestone") ? compareMilestoneKey(flags) ?? null : null
39182
39706
  };
39183
39707
  }
39708
+ function requireFiniteAmount(E, amount, label) {
39709
+ if (!E.isFinite(amount))
39710
+ throw new RangeError(`compare ${label} must be finite in the selected engine`);
39711
+ return amount;
39712
+ }
39184
39713
  function measureScenario(args) {
39714
+ const requireFinite = (amount, label) => requireFiniteAmount(args.E, amount, label);
39185
39715
  const runInput = args.compiled.fresh("economy");
39186
39716
  const run = runScenario(runInput);
39187
39717
  const endWorth = runInput.model.netWorth?.(runInput.ctx, run.end) ?? run.end.wallet.money;
@@ -39193,7 +39723,12 @@ function measureScenario(args) {
39193
39723
  unit: etaInput.ctx.unit,
39194
39724
  suffix: { kind: "alphaInfinite", minLen: 2 }
39195
39725
  }).amount;
39196
- const reachedFn = (s) => args.E.cmp((etaInput.model.netWorth?.(etaInput.ctx, s) ?? s.wallet.money).amount, target) >= 0;
39726
+ requireFinite(target, "ETA target");
39727
+ const reachedFn = (s) => {
39728
+ requireFinite(s.wallet.money.amount, "ETA money");
39729
+ const worth = etaInput.model.netWorth?.(etaInput.ctx, s) ?? s.wallet.money;
39730
+ return args.E.cmp(requireFinite(worth.amount, "ETA net worth"), target) >= 0;
39731
+ };
39197
39732
  const etaRun = runScenario({
39198
39733
  ...etaInput,
39199
39734
  run: {
@@ -39377,9 +39912,9 @@ function bundleMetrics(bundle) {
39377
39912
  function compareDecisionForMetric(args) {
39378
39913
  switch (args.metric) {
39379
39914
  case "endMoney":
39380
- return betterFromCmp(args.E.cmp(args.ma.endMoney, args.mb.endMoney));
39915
+ return betterFromCmp(args.E.cmp(requireFiniteAmount(args.E, args.ma.endMoney, "end money"), requireFiniteAmount(args.E, args.mb.endMoney, "end money")));
39381
39916
  case "endNetWorth":
39382
- return betterFromCmp(args.E.cmp(args.ma.endNetWorth, args.mb.endNetWorth));
39917
+ return betterFromCmp(args.E.cmp(requireFiniteAmount(args.E, args.ma.endNetWorth, "end net worth"), requireFiniteAmount(args.E, args.mb.endNetWorth, "end net worth")));
39383
39918
  case "droppedRate":
39384
39919
  return betterFromCmp(args.ma.droppedRate < args.mb.droppedRate ? 1 : args.ma.droppedRate > args.mb.droppedRate ? -1 : 0);
39385
39920
  case "etaToTargetWorth": {
@@ -39453,7 +39988,7 @@ function buildSingleCompareOutput(args) {
39453
39988
  },
39454
39989
  insights: buildCompareInsights({
39455
39990
  metric: args.metric,
39456
- endNetWorthWinner: betterFromCmp(args.E.cmp(args.ma.endNetWorth, args.mb.endNetWorth)),
39991
+ endNetWorthWinner: args.E.isFinite(args.ma.endNetWorth) && args.E.isFinite(args.mb.endNetWorth) ? betterFromCmp(args.E.cmp(args.ma.endNetWorth, args.mb.endNetWorth)) : "tie",
39457
39992
  better: result.better,
39458
39993
  a: {
39459
39994
  endNetWorth: args.E.toString(args.ma.endNetWorth),
@@ -39475,6 +40010,7 @@ var compare_default = defineCommand({
39475
40010
  description: "Compare two scenarios via measured simulation metrics",
39476
40011
  options: {
39477
40012
  ...pluginOptions(),
40013
+ engine: option(string2().min(1).optional(), { description: "Numeric engine (default: number; explicit: breakInfinity)" }),
39478
40014
  duration: option(exports_coerce.number().optional(), { description: "Override durationSec" }),
39479
40015
  step: option(exports_coerce.number().optional(), { description: "Override stepSec" }),
39480
40016
  strategy: option(strategySchema, { description: "Override strategy id (greedy|planner|scripted)" }),
@@ -39530,7 +40066,8 @@ var compare_default = defineCommand({
39530
40066
  throw usageError("metric=timeToMilestone requires --milestone-key <key>");
39531
40067
  }
39532
40068
  const scenarios = { a: aScenario, b: bScenario };
39533
- const identity = compareIdentity2({ scenarios, flags, loaded });
40069
+ const engine = resolveEffectiveEngine({ requested: flags.engine });
40070
+ const identity = compareIdentity2({ scenarios, flags, loaded, engineId: engine.effectiveId });
39534
40071
  const effectiveSeed = flags.seed ?? defaultRunSeed({
39535
40072
  base: {
39536
40073
  command: "compare",
@@ -39550,9 +40087,12 @@ var compare_default = defineCommand({
39550
40087
  milestoneKey: undefined
39551
40088
  }
39552
40089
  },
39553
- runs: [{ identity, defaults: compareIdentity2({ scenarios, flags: { "max-duration": DEFAULT_MAX_DURATION }, loaded }) }]
40090
+ runs: [{ identity, defaults: {
40091
+ ...compareIdentity2({ scenarios, flags: { "max-duration": DEFAULT_MAX_DURATION }, loaded }),
40092
+ engineId: "number"
40093
+ } }]
39554
40094
  });
39555
- const E = createNumberEngine();
40095
+ const E = engine.engine;
39556
40096
  const aCompiled = compileComparableScenario({
39557
40097
  scenario: aScenario,
39558
40098
  E,
@@ -39627,7 +40167,8 @@ var compare_default = defineCommand({
39627
40167
  a: aScenario,
39628
40168
  b: bScenario
39629
40169
  },
39630
- pluginDigest: loaded.pluginDigest
40170
+ pluginDigest: loaded.pluginDigest,
40171
+ effectiveEngine: engine.effectiveId
39631
40172
  });
39632
40173
  const markdownWarnings = new Set;
39633
40174
  if (selectedMetrics.some((metric) => ["endMoney", "endNetWorth", "droppedRate"].includes(metric))) {
@@ -39637,6 +40178,9 @@ var compare_default = defineCommand({
39637
40178
  markdownWarnings.add(`Economy runs cover different elapsed durations (A: ${aElapsed}s, B: ${bElapsed}s); end values are not normalized to a common horizon.`);
39638
40179
  }
39639
40180
  }
40181
+ if ([ma.endMoney, ma.endNetWorth, mb.endMoney, mb.endNetWorth].some((amount) => !E.isFinite(amount))) {
40182
+ markdownWarnings.add("The full-duration economy run left the selected engine's finite range; its end values are reported but not ranked.");
40183
+ }
39640
40184
  const singleResults = selectedMetrics.map((metric) => {
39641
40185
  const design = getDesignPair(metric);
39642
40186
  if (design) {
@@ -39732,8 +40276,8 @@ init_outputMeta();
39732
40276
  // src/lib/setup.ts
39733
40277
  init_errors3();
39734
40278
  init_bun();
39735
- init_selfCli();
39736
40279
  import { basename as basename4, dirname as dirname9, relative as relative3, resolve as resolve15 } from "path";
40280
+ init_selfCli();
39737
40281
  var START_MARKER = "# >>> idk completions >>>";
39738
40282
  var END_MARKER = "# <<< idk completions <<<";
39739
40283
  function userHome() {
@@ -39874,7 +40418,7 @@ async function writePluginTrust(args) {
39874
40418
  if (args.plugins.length === 0) {
39875
40419
  throw usageError("setup plugin-trust requires at least one --plugin path.");
39876
40420
  }
39877
- const outPath = resolve15(args.outPath);
40421
+ const outPath = await assertOutputNotInput(args.outPath, { protectedInputs: args.plugins });
39878
40422
  if (await fileExists(outPath) && !args.force) {
39879
40423
  throw usageError(`Output file already exists: ${outPath}`, "Pass --force true to overwrite.");
39880
40424
  }
@@ -40375,6 +40919,9 @@ import { resolve as resolve17 } from "path";
40375
40919
  init_errors3();
40376
40920
  init_outputMeta();
40377
40921
  var strategySchema2 = string2().min(1).optional();
40922
+ function uint32Seed(seed) {
40923
+ return Number.isFinite(seed) ? seed >>> 0 : seed;
40924
+ }
40378
40925
  function parseHorizonToken(raw) {
40379
40926
  const token = raw.trim().toLowerCase();
40380
40927
  if (!token)
@@ -40530,7 +41077,7 @@ function runLtvAnalysis(args) {
40530
41077
  }
40531
41078
  const worth = args.compiled.model.netWorth?.(args.compiled.ctx, state) ?? state.wallet.money;
40532
41079
  const endWorthLog10 = args.compiled.ctx.E.absLog10(worth.amount);
40533
- const progression = progressionFactor(startWorthLog10, endWorthLog10, monetizationConfig.revenue.progressionLogSpan);
41080
+ const progression = progressionFactor2(startWorthLog10, endWorthLog10, monetizationConfig.revenue.progressionLogSpan);
40534
41081
  const horizonDays = h.seconds / 86400;
40535
41082
  const point = estimateLtvPerUser2({
40536
41083
  config: monetizationConfig,
@@ -40543,7 +41090,7 @@ function runLtvAnalysis(args) {
40543
41090
  progression,
40544
41091
  draws,
40545
41092
  quantiles: monetizationConfig.uncertainty.quantiles,
40546
- seed: (args.seed ?? monetizationConfig.uncertainty.seed ?? 1) + h.seconds
41093
+ seed: uint32Seed((args.seed ?? monetizationConfig.uncertainty.seed ?? 1) + h.seconds)
40547
41094
  }) : undefined;
40548
41095
  const E = args.compiled.ctx.E;
40549
41096
  const deltaWorth = E.sub(worth.amount, previousWorth.amount);
@@ -41117,6 +41664,8 @@ var evaluate_default = defineCommand({
41117
41664
  files.ltv = resolve18(outDir, "ltv.json");
41118
41665
  const summaryPath = resolve18(process.cwd(), flags["out-dir"], flags.format === "md" ? "summary.md" : "summary.json");
41119
41666
  files.summary = summaryPath;
41667
+ for (const path of [files.simulate, files.experience, files.ltv, summaryPath])
41668
+ await assertOutputNotInput(path);
41120
41669
  await writeTextFile(files.simulate, `${JSON.stringify({ ...simulate, _meta: simulateMeta }, null, 2)}
41121
41670
  `);
41122
41671
  await writeTextFile(files.experience, `${JSON.stringify({ ...experience, _meta: experienceMeta }, null, 2)}
@@ -41851,7 +42400,7 @@ var simulate_default = defineCommand({
41851
42400
  state: strategyState
41852
42401
  } : undefined
41853
42402
  });
41854
- await writeTextFile(stateOutPath, `${JSON.stringify(serialized, null, 2)}
42403
+ await writeTextFile(await assertOutputNotInput(stateOutPath, { allowFlags: ["resume"] }), `${JSON.stringify(serialized, null, 2)}
41855
42404
  `);
41856
42405
  }
41857
42406
  const offlineSummary = buildOfflineSummary(offlineRun);
@@ -42096,6 +42645,13 @@ function readBestScoreFromArtifact(x) {
42096
42645
  }
42097
42646
  return score;
42098
42647
  }
42648
+ function readEngineFromArtifact(x) {
42649
+ const recorded = x?.meta?.effectiveEngine;
42650
+ if (typeof recorded === "string" && recorded.trim())
42651
+ return recorded.trim();
42652
+ const requested = x?.input?.flags?.engine;
42653
+ return typeof requested === "string" && requested.trim() ? requested.trim() : "number";
42654
+ }
42099
42655
  function buildTuneInsights(outputBase) {
42100
42656
  const report = outputBase.report;
42101
42657
  const bestScore = readBestScoreFromTuneResult(outputBase);
@@ -42190,6 +42746,7 @@ var tune_default = defineCommand({
42190
42746
  description: "Tune strategy parameters with objective scoring",
42191
42747
  options: {
42192
42748
  ...pluginOptions(),
42749
+ engine: option(string2().min(1).optional(), { description: "Numeric engine (default: number; explicit: breakInfinity)" }),
42193
42750
  tune: option(string2().optional(), { description: "TuneSpec file path (.json|.yaml)" }),
42194
42751
  wizard: option(exports_coerce.boolean().default(false), {
42195
42752
  description: "Interactively generate a TuneSpec before tuning"
@@ -42245,11 +42802,22 @@ var tune_default = defineCommand({
42245
42802
  const tuneSpecInput = wizardResult?.tuneSpec ?? await readScenarioFile(tunePath);
42246
42803
  const baselinePath = flags["baseline-artifact"] ? resolve21(flags["baseline-artifact"]) : undefined;
42247
42804
  const baselineRaw = baselinePath ? await readJsonFile(baselinePath) : undefined;
42805
+ const engine = resolveEffectiveEngine({ requested: flags.engine });
42806
+ if (baselineRaw !== undefined) {
42807
+ const baselineEngine = readEngineFromArtifact(baselineRaw);
42808
+ if (baselineEngine !== engine.effectiveId) {
42809
+ throw cliError("CLI_USAGE", `Baseline artifact engine mismatch: expected ${engine.effectiveId}, got ${baselineEngine}`, {
42810
+ hint: `Pass --engine ${baselineEngine} to compare against this baseline, or record a new baseline with --engine ${engine.effectiveId}.`
42811
+ });
42812
+ }
42813
+ }
42814
+ const engineIdentity = engine.effectiveId === "number" ? {} : { engineId: engine.effectiveId };
42248
42815
  const seed = flags.seed ?? deriveDeterministicSeed({
42249
42816
  command: "tune",
42250
42817
  scenario: scenarioInput,
42251
42818
  tuneSpec: tuneSpecInput,
42252
42819
  options: {
42820
+ ...engineIdentity,
42253
42821
  baselineArtifact: baselineRaw === undefined ? undefined : hashContent(baselineRaw),
42254
42822
  regressionTolerance: flags["regression-tolerance"]
42255
42823
  }
@@ -42257,7 +42825,7 @@ var tune_default = defineCommand({
42257
42825
  const runId = flags["run-id"] ?? deriveDeterministicRunId({
42258
42826
  command: "tune",
42259
42827
  seed,
42260
- scope: { scenario: scenarioInput, tuneSpec: tuneSpecInput, pluginDigests: Object.values(loaded.pluginDigest) }
42828
+ scope: { scenario: scenarioInput, tuneSpec: tuneSpecInput, pluginDigests: Object.values(loaded.pluginDigest), ...engineIdentity }
42261
42829
  });
42262
42830
  const outputMeta = buildOutputMeta({
42263
42831
  command: "tune",
@@ -42266,10 +42834,11 @@ var tune_default = defineCommand({
42266
42834
  tuneSpec: tuneSpecInput,
42267
42835
  runId,
42268
42836
  seed,
42269
- pluginDigest: loaded.pluginDigest
42837
+ pluginDigest: loaded.pluginDigest,
42838
+ effectiveEngine: engine.effectiveId
42270
42839
  });
42271
42840
  const result = await cmdTune({
42272
- E: createNumberEngine(),
42841
+ E: engine.engine,
42273
42842
  scenarioInput,
42274
42843
  tuneSpecInput,
42275
42844
  modelRegistry: loaded.modelRegistry,
@@ -43658,7 +44227,6 @@ init_zod();
43658
44227
  // src/commands/_shared/listOutput.ts
43659
44228
  init_outputMeta();
43660
44229
  init_bun();
43661
- import { resolve as resolve25 } from "path";
43662
44230
  async function writeListOutput(args) {
43663
44231
  const outputForRender = args.format === "json" ? {
43664
44232
  ...args.payload,
@@ -43669,7 +44237,7 @@ async function writeListOutput(args) {
43669
44237
  process.stdout.write(body);
43670
44238
  return;
43671
44239
  }
43672
- const path = resolve25(args.out);
44240
+ const path = await assertOutputNotInput(args.out);
43673
44241
  await writeTextFile(path, body);
43674
44242
  console.log(`Wrote ${args.format} output to ${path}`);
43675
44243
  }
@@ -43818,8 +44386,9 @@ var objectives_default2 = defineGroup({
43818
44386
  init_zod();
43819
44387
  init_errors3();
43820
44388
  init_outputMeta();
43821
- import { resolve as resolve26 } from "path";
44389
+ import { resolve as resolve25 } from "path";
43822
44390
  init_bun();
44391
+ init_invocation();
43823
44392
  init_selfCli();
43824
44393
  function deepEqual(a, b) {
43825
44394
  return stableStringify(a) === stableStringify(b);
@@ -43839,6 +44408,7 @@ function parseArtifact(input) {
43839
44408
  flags: record(string2(), unknown())
43840
44409
  }),
43841
44410
  replay: object({
44411
+ cwd: string2().optional(),
43842
44412
  args: array(string2()).min(1),
43843
44413
  commandLine: string2(),
43844
44414
  verify: object({
@@ -43885,13 +44455,15 @@ var replayVerify_default = defineCommand({
43885
44455
  if (!artifactPath) {
43886
44456
  throw usageError("Usage: idk replay verify <artifact.json>");
43887
44457
  }
43888
- const artifactAbs = resolve26(process.cwd(), artifactPath);
44458
+ const artifactAbs = resolve25(process.cwd(), artifactPath);
43889
44459
  const raw = await readJsonFile(artifactAbs).catch((error) => {
43890
44460
  if (error instanceof Error && error.name === "CliError")
43891
44461
  throw error;
43892
44462
  throw replayArtifactInvalidError("Invalid replay artifact.", errorDetail(error), error);
43893
44463
  });
43894
44464
  const artifact = parseArtifact(raw);
44465
+ const recordedCwd = artifact.replay.cwd || process.cwd();
44466
+ recordNestedInputs(recordedCwd, artifact.input.positional, artifact.input.flags);
43895
44467
  const requiredChecks = {
43896
44468
  runId: typeof artifact.replay.verify.runId === "string" && artifact.replay.verify.runId.length > 0,
43897
44469
  seed: typeof artifact.replay.verify.seed === "number" && Number.isFinite(artifact.replay.verify.seed),
@@ -43907,6 +44479,15 @@ var replayVerify_default = defineCommand({
43907
44479
  const expectedHash = artifact.replay.verify.resultHash ?? hashReplayResult(expectedCanonical);
43908
44480
  const actualHash = hashReplayResult(actualCanonical);
43909
44481
  const replayMeta = replayOutput && typeof replayOutput === "object" && !Array.isArray(replayOutput) ? replayOutput._meta : undefined;
44482
+ const replayScenarios = replayMeta?.scenarioPath;
44483
+ const replayPlugins = replayMeta?.pluginDigest;
44484
+ const pluginEntries = replayPlugins && typeof replayPlugins === "object" ? Object.keys(replayPlugins) : [];
44485
+ recordNestedInputs(cliPackageRoot(), [
44486
+ ...typeof replayScenarios === "string" ? [replayScenarios] : Array.isArray(replayScenarios) ? replayScenarios.filter((path) => typeof path === "string") : [],
44487
+ ...pluginEntries
44488
+ ], {});
44489
+ for (const entry of pluginEntries)
44490
+ await recordPluginClosureReads(resolve25(cliPackageRoot(), entry));
43910
44491
  const semanticChecks = {
43911
44492
  runId: replayMeta && typeof replayMeta.runId === "string" && replayMeta.runId === artifact.replay.verify.runId,
43912
44493
  seed: replayMeta && typeof replayMeta.seed === "number" && replayMeta.seed === artifact.replay.verify.seed,
@@ -44186,4 +44767,4 @@ export {
44186
44767
  cli2 as cli
44187
44768
  };
44188
44769
 
44189
- //# debugId=A3CAAFDF1955C7F064756E2164756E21
44770
+ //# debugId=75B3E1ECDA8A1B6D64756E2164756E21