@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.
@@ -8198,7 +8198,7 @@ var init_validate = __esm(() => {
8198
8198
  OFFLINE_ACTORS = new Set(["player", "automation"]);
8199
8199
  });
8200
8200
 
8201
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_validateReport.mjs
8201
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_validateReport.mjs
8202
8202
  var _validateReport = (array) => {
8203
8203
  const isAncestor = (ancestor, descendant) => descendant === ancestor || descendant.startsWith(`${ancestor}.`) || descendant.startsWith(`${ancestor}[`);
8204
8204
  const reportable = (path) => {
@@ -8223,7 +8223,7 @@ var _validateReport = (array) => {
8223
8223
  };
8224
8224
  var init__validateReport = () => {};
8225
8225
 
8226
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_createStandardSchema.mjs
8226
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_createStandardSchema.mjs
8227
8227
  var _createStandardSchema = (fn) => Object.assign(fn, { "~standard": {
8228
8228
  version: 1,
8229
8229
  vendor: "typia",
@@ -10404,6 +10404,14 @@ var init_goalRead = __esm(() => {
10404
10404
  ledgers = new WeakMap;
10405
10405
  });
10406
10406
 
10407
+ // ../core/dist/sim/milestoneKeys.js
10408
+ function actionMilestoneKeys(actionId) {
10409
+ return [`action.${actionId}.firstApplied`, "progress.first-upgrade"];
10410
+ }
10411
+ function mayRecordAtActionStart(key) {
10412
+ return key === "progress.first-upgrade" || key.startsWith("action.") && key.endsWith(".firstApplied");
10413
+ }
10414
+
10407
10415
  // ../core/dist/sim/observation.js
10408
10416
  function emitCommittedResets(observer, resets) {
10409
10417
  if (!observer?.onReset || resets.length === 0)
@@ -10584,7 +10592,11 @@ function createObservationRecorder(args) {
10584
10592
  milestones.push(sample);
10585
10593
  }
10586
10594
  if (args.observer?.onMilestone) {
10587
- notify(() => args.observer?.onMilestone?.({ t: sample.firstSeenT, key: sample.key }));
10595
+ notify(() => args.observer?.onMilestone?.({
10596
+ t: sample.firstSeenT,
10597
+ key: sample.key,
10598
+ phase: sample.source === "action" ? "action-start" : "income-end"
10599
+ }));
10588
10600
  }
10589
10601
  };
10590
10602
  const open = (goal) => !reachedGoals.has(goal.id) && !droppedGoals.has(goal.id);
@@ -10642,8 +10654,8 @@ function createObservationRecorder(args) {
10642
10654
  if (args.observer?.onAction) {
10643
10655
  notify(() => args.observer?.onAction?.({ t: step.t0, actionId: event.actionId, outcome: "applied" }));
10644
10656
  }
10645
- rememberMilestone({ key: `action.${event.actionId}.firstApplied`, firstSeenT: step.t0, source: "action" });
10646
- rememberMilestone({ key: "progress.first-upgrade", firstSeenT: step.t0, source: "action" });
10657
+ for (const key of actionMilestoneKeys(event.actionId))
10658
+ rememberMilestone({ key, firstSeenT: step.t0, source: "action" });
10647
10659
  } else if (event.type === "action.skipped") {
10648
10660
  if (event.reason === "cannotApply")
10649
10661
  actions.skippedCannotApply += 1;
@@ -11316,6 +11328,63 @@ function logicalDrawId(drawIndex) {
11316
11328
  return String(drawIndex);
11317
11329
  }
11318
11330
 
11331
+ // ../core/dist/sim/sessionOffline.js
11332
+ function validateCreditPolicy(policy) {
11333
+ if (!policy || typeof policy !== "object" || Array.isArray(policy) || "actions" in policy) {
11334
+ throw new RangeError("prepareOffline policy must be a credit policy; set actions on scenario.run.offline");
11335
+ }
11336
+ if (policy.maxSec !== undefined && (typeof policy.maxSec !== "number" || !Number.isFinite(policy.maxSec) || policy.maxSec < 0)) {
11337
+ throw new RangeError("prepareOffline policy.maxSec must be finite and nonnegative");
11338
+ }
11339
+ if (policy.overflowPolicy !== undefined && policy.overflowPolicy !== "clamp" && policy.overflowPolicy !== "reject") {
11340
+ throw new RangeError("prepareOffline policy.overflowPolicy must be clamp or reject");
11341
+ }
11342
+ const decay = policy.decay;
11343
+ if (decay !== undefined) {
11344
+ if (!decay || typeof decay !== "object" || Array.isArray(decay) || decay.kind !== "none" && decay.kind !== "linear") {
11345
+ throw new RangeError("prepareOffline policy.decay must select none or linear");
11346
+ }
11347
+ if (decay.floorRatio !== undefined && (typeof decay.floorRatio !== "number" || !Number.isFinite(decay.floorRatio) || decay.floorRatio < 0 || decay.floorRatio > 1)) {
11348
+ throw new RangeError("prepareOffline policy.decay.floorRatio must be finite and in 0..1");
11349
+ }
11350
+ }
11351
+ }
11352
+ function prepareOfflineSegment(args) {
11353
+ const { scenario, prepare } = args;
11354
+ if (!prepare)
11355
+ return { model: scenario.model, policy: scenario.run.offline, stepSec: scenario.run.stepSec };
11356
+ const prepared = prepare({
11357
+ ctx: scenario.ctx,
11358
+ model: scenario.model,
11359
+ state: deepClonePreservingPrototype(args.state),
11360
+ seconds: args.seconds,
11361
+ wallStartT: args.wallStartT,
11362
+ wallEndT: args.wallEndT,
11363
+ ...scenario.run.offline ? { policy: deepClonePreservingPrototype(scenario.run.offline) } : {}
11364
+ });
11365
+ if (!prepared || typeof prepared !== "object" || Array.isArray(prepared) || "then" in prepared) {
11366
+ throw new RangeError("prepareOffline must synchronously return a preparation object");
11367
+ }
11368
+ if (prepared.stepSec !== undefined && (typeof prepared.stepSec !== "number" || !Number.isFinite(prepared.stepSec) || prepared.stepSec <= 0)) {
11369
+ throw new RangeError("prepareOffline stepSec must be finite and positive");
11370
+ }
11371
+ if (prepared.policy !== undefined)
11372
+ validateCreditPolicy(prepared.policy);
11373
+ const model = prepared.model === undefined ? scenario.model : prepared.model;
11374
+ if (!model || typeof model !== "object" || typeof model.income !== "function" || typeof model.actions !== "function") {
11375
+ throw new RangeError("prepareOffline model must provide income and actions");
11376
+ }
11377
+ return {
11378
+ model,
11379
+ stepSec: prepared.stepSec ?? scenario.run.stepSec,
11380
+ policy: prepared.policy === undefined ? scenario.run.offline : {
11381
+ ...deepClonePreservingPrototype(prepared.policy),
11382
+ actions: scenario.run.offline?.actions
11383
+ }
11384
+ };
11385
+ }
11386
+ var init_sessionOffline = () => {};
11387
+
11319
11388
  // ../core/dist/sim/session.js
11320
11389
  function buildBlocks(pattern) {
11321
11390
  const blocks = [];
@@ -11411,6 +11480,9 @@ function withClocks(scenario, wallT, wallEndT, rewardT, activeT) {
11411
11480
  };
11412
11481
  }
11413
11482
  function simulateSessionPattern(args) {
11483
+ if (args.prepareOffline !== undefined && typeof args.prepareOffline !== "function") {
11484
+ throw new TypeError("prepareOffline must be a function");
11485
+ }
11414
11486
  const sc = args.seed === undefined ? args.scenario : { ...args.scenario, ctx: { ...args.scenario.ctx, seed: args.seed } };
11415
11487
  const start = sc.initial;
11416
11488
  const horizonSec = args.pattern.days * 86400;
@@ -11497,7 +11569,19 @@ function simulateSessionPattern(args) {
11497
11569
  return;
11498
11570
  const requested = wallEnd - wallT;
11499
11571
  const wallStart = wallT;
11500
- const segment = segmentScenario(wallStart, wallEnd);
11572
+ let segment = segmentScenario(wallStart, wallEnd);
11573
+ const prepared = prepareOfflineSegment({
11574
+ scenario: segment,
11575
+ state,
11576
+ seconds: requested,
11577
+ wallStartT: wallStart,
11578
+ wallEndT: wallEnd,
11579
+ prepare: args.prepareOffline
11580
+ });
11581
+ if (prepared.model !== segment.model) {
11582
+ const { clocks: _clocks, ...ctx } = segment.ctx;
11583
+ segment = withClocks({ ...segment, ctx, model: prepared.model }, wallStart, wallEnd, state.t, activeSec);
11584
+ }
11501
11585
  const offlineRun = applyOfflineSeconds({
11502
11586
  scenario: segment,
11503
11587
  seconds: requested,
@@ -11509,15 +11593,16 @@ function simulateSessionPattern(args) {
11509
11593
  enabled: sc.run.eventLog?.enabled ?? true,
11510
11594
  maxEvents: sc.run.eventLog?.maxEvents
11511
11595
  },
11512
- policy: sc.run.offline,
11596
+ stepSec: prepared.stepSec,
11597
+ policy: prepared.policy,
11513
11598
  ...stopFn ? { until: stopFn } : {}
11514
11599
  }
11515
11600
  });
11516
11601
  const credited = offlineRun.offline.simulatedSec;
11517
11602
  const reason = classify();
11518
- const absence = reason ? offlineAbsenceForCredit(credited, requested, sc.run.offline) : requested;
11603
+ const absence = reason ? offlineAbsenceForCredit(credited, requested, prepared.policy) : requested;
11519
11604
  const gapEnd = wallStart + absence;
11520
- const effective = absence === requested ? offlineRun.offline.effectiveSec : resolveOfflineSeconds(absence, sc.run.offline).effectiveSec;
11605
+ const effective = absence === requested ? offlineRun.offline.effectiveSec : resolveOfflineSeconds(absence, prepared.policy).effectiveSec;
11521
11606
  const lost = Math.max(0, absence - effective);
11522
11607
  offlineElapsedSec += absence;
11523
11608
  offlineCreditedSec += credited;
@@ -11688,6 +11773,7 @@ var init_session = __esm(() => {
11688
11773
  init_goalRead();
11689
11774
  init_observation();
11690
11775
  init_offline();
11776
+ init_sessionOffline();
11691
11777
  init_simulator();
11692
11778
  });
11693
11779
 
@@ -12683,11 +12769,20 @@ var init_params2 = __esm(() => {
12683
12769
  }));
12684
12770
  });
12685
12771
 
12772
+ // ../core/dist/sim/strategy/opt/finiteAmountLog10.js
12773
+ function finiteAmountLog10(E, amount) {
12774
+ if (!E.isFinite(amount))
12775
+ throw new RangeError("Tuning requires finite monetary amounts in the selected engine");
12776
+ const value = E.absLog10(amount);
12777
+ if (value === Number.NEGATIVE_INFINITY)
12778
+ return ZERO_AMOUNT_LOG10_FLOOR;
12779
+ if (!Number.isFinite(value))
12780
+ throw new RangeError("Tuning requires a finite monetary logarithm");
12781
+ return Math.max(ZERO_AMOUNT_LOG10_FLOOR, value);
12782
+ }
12783
+ var ZERO_AMOUNT_LOG10_FLOOR = -300;
12784
+
12686
12785
  // ../core/dist/sim/strategy/opt/objectives/builtins.js
12687
- function safeAbsLog10(E, x) {
12688
- const v = E.absLog10(x);
12689
- return Number.isFinite(v) ? v : -300;
12690
- }
12691
12786
  var builtinObjectiveFactories;
12692
12787
  var init_builtins2 = __esm(() => {
12693
12788
  init_parseMoney2();
@@ -12701,7 +12796,7 @@ var init_builtins2 = __esm(() => {
12701
12796
  id: "endMoneyLog10",
12702
12797
  score: ({ scenario, run }) => {
12703
12798
  const { E } = scenario.ctx;
12704
- return safeAbsLog10(E, run.end.wallet.money.amount);
12799
+ return finiteAmountLog10(E, run.end.wallet.money.amount);
12705
12800
  }
12706
12801
  })
12707
12802
  },
@@ -12714,7 +12809,7 @@ var init_builtins2 = __esm(() => {
12714
12809
  score: ({ scenario, run }) => {
12715
12810
  const { E } = scenario.ctx;
12716
12811
  const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
12717
- return safeAbsLog10(E, worth.amount);
12812
+ return finiteAmountLog10(E, worth.amount);
12718
12813
  }
12719
12814
  })
12720
12815
  },
@@ -12729,7 +12824,7 @@ var init_builtins2 = __esm(() => {
12729
12824
  const sec = Math.max(1, runElapsedSec(run));
12730
12825
  const hours = sec / 3600;
12731
12826
  const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
12732
- return safeAbsLog10(E, worth.amount) - Math.log10(Math.max(0.000000000001, hours));
12827
+ return finiteAmountLog10(E, worth.amount) - Math.log10(Math.max(0.000000000001, hours));
12733
12828
  }
12734
12829
  })
12735
12830
  },
@@ -12743,7 +12838,7 @@ var init_builtins2 = __esm(() => {
12743
12838
  const { E } = scenario.ctx;
12744
12839
  const sec = Math.max(1, runElapsedSec(run));
12745
12840
  const hours = sec / 3600;
12746
- return safeAbsLog10(E, run.end.prestige.points) - Math.log10(Math.max(0.000000000001, hours));
12841
+ return finiteAmountLog10(E, run.end.prestige.points) - Math.log10(Math.max(0.000000000001, hours));
12747
12842
  }
12748
12843
  })
12749
12844
  },
@@ -12758,7 +12853,7 @@ var init_builtins2 = __esm(() => {
12758
12853
  const sec = Math.max(1, runElapsedSec(run));
12759
12854
  const startWorth = scenario.model.netWorth?.(scenario.ctx, run.start) ?? run.start.wallet.money;
12760
12855
  const endWorth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
12761
- const deltaLog = safeAbsLog10(E, endWorth.amount) - safeAbsLog10(E, startWorth.amount);
12856
+ const deltaLog = finiteAmountLog10(E, endWorth.amount) - finiteAmountLog10(E, startWorth.amount);
12762
12857
  return deltaLog / (sec / 3600);
12763
12858
  }
12764
12859
  })
@@ -12784,6 +12879,9 @@ var init_builtins2 = __esm(() => {
12784
12879
  suffix: { kind: "alphaInfinite", minLen: 2 }
12785
12880
  }).amount;
12786
12881
  const endWorth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
12882
+ if (!E.isFinite(threshold) || !E.isFinite(endWorth.amount)) {
12883
+ throw new RangeError("ETA objective requires finite target and net worth in the selected engine");
12884
+ }
12787
12885
  const reached = E.cmp(endWorth.amount, threshold) >= 0;
12788
12886
  if (!reached)
12789
12887
  return -(params.unreachedPenaltySec ?? 1e9);
@@ -12813,7 +12911,7 @@ var init_builtins2 = __esm(() => {
12813
12911
  const sec = Math.max(1, runElapsedSec(run));
12814
12912
  const hours = sec / 3600;
12815
12913
  const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
12816
- const worthLog = safeAbsLog10(E, worth.amount);
12914
+ const worthLog = finiteAmountLog10(E, worth.amount);
12817
12915
  const stats = run.stats;
12818
12916
  if (!stats || stats.money.droppedRate === null || stats.actions.status === "missing") {
12819
12917
  throw new Error("pacingBalancedLog10 needs observed money and action counters, but this run has none (run.observation.enabled is false)");
@@ -13012,8 +13110,45 @@ function cloneCandidateInitial(state) {
13012
13110
  }
13013
13111
  var init_freshRun = () => {};
13014
13112
 
13113
+ // ../core/dist/sim/strategy/opt/finiteMean.js
13114
+ function finiteMean(values) {
13115
+ if (values.length === 0)
13116
+ throw new RangeError("A mean requires at least one value");
13117
+ const bits = new DataView(new ArrayBuffer(8));
13118
+ let total = 0n;
13119
+ for (const value of values) {
13120
+ if (!Number.isFinite(value))
13121
+ throw new RangeError("A mean requires finite values");
13122
+ bits.setFloat64(0, value, false);
13123
+ const high = bits.getUint32(0, false);
13124
+ const low = bits.getUint32(4, false);
13125
+ const exponent = high >>> 20 & 2047;
13126
+ const fraction = BigInt(high & 1048575) << 32n | BigInt(low);
13127
+ const significand = exponent === 0 ? fraction : 1n << 52n | fraction;
13128
+ const units = significand << BigInt(Math.max(0, exponent - 1));
13129
+ total += high >>> 31 ? -units : units;
13130
+ }
13131
+ if (total === 0n)
13132
+ return 0;
13133
+ const negative = total < 0n;
13134
+ const magnitude = negative ? -total : total;
13135
+ const count = BigInt(values.length);
13136
+ const integerMean = magnitude / count;
13137
+ const shift = Math.max(0, integerMean.toString(2).length - 53);
13138
+ const denominator = count << BigInt(shift);
13139
+ let significand = magnitude / denominator;
13140
+ const twiceRemainder = 2n * (magnitude % denominator);
13141
+ if (twiceRemainder > denominator || twiceRemainder === denominator && (significand & 1n) !== 0n) {
13142
+ significand++;
13143
+ }
13144
+ const mean = Number(significand) * 2 ** (shift - 1074);
13145
+ return negative ? -mean : mean;
13146
+ }
13147
+
13015
13148
  // ../core/dist/sim/strategy/opt/runner.js
13016
13149
  function runCandidateAndScore(args) {
13150
+ if (args.seeds.length === 0)
13151
+ throw new RangeError("Tuning requires at least one seed");
13017
13152
  const stratFactory = args.strategyRegistry.get(args.strategyId);
13018
13153
  if (!stratFactory)
13019
13154
  throw new Error(`Unknown strategy: ${args.strategyId}`);
@@ -13060,24 +13195,28 @@ function runCandidateAndScore(args) {
13060
13195
  const sc = openScenario();
13061
13196
  const run = runScenario(sc);
13062
13197
  assertHorizonReached(run, "runCandidateAndScore");
13198
+ const worth = sc.model.netWorth?.(sc.ctx, run.end) ?? run.end.wallet.money;
13199
+ const endMoneyLog10 = finiteAmountLog10(sc.ctx.E, run.end.wallet.money.amount);
13200
+ const endNetWorthLog10 = finiteAmountLog10(sc.ctx.E, worth.amount);
13063
13201
  const s = objective.score({ scenario: sc, run, evaluation: {
13064
13202
  open: openScenario,
13065
13203
  registries: { models: args.modelRegistry, strategies: args.strategyRegistry },
13066
13204
  isolation: { model: args.model, statefulModel: args.statefulModel, strategy: { id: args.strategyId, params: candidateParams } }
13067
13205
  } });
13206
+ if (!Number.isFinite(s))
13207
+ throw new RangeError(`Objective ${args.objectiveId} must return a finite score (seed ${seed})`);
13068
13208
  seedScores.push(s);
13069
- const worth = sc.model.netWorth?.(sc.ctx, run.end) ?? run.end.wallet.money;
13070
13209
  seedResults.push({
13071
13210
  seed,
13072
13211
  score: s,
13073
13212
  durationSec: Math.max(0, runElapsedSec(run)),
13074
- endMoneyLog10: sc.ctx.E.absLog10(run.end.wallet.money.amount),
13075
- endNetWorthLog10: sc.ctx.E.absLog10(worth.amount),
13213
+ endMoneyLog10,
13214
+ endNetWorthLog10,
13076
13215
  droppedRate: run.stats?.money.droppedRate ?? null,
13077
13216
  actionsApplied: run.stats && run.stats.actions.status === "observed" ? run.stats.actions.applied : null
13078
13217
  });
13079
13218
  }
13080
- const score = seedScores.reduce((a, b) => a + b, 0) / Math.max(1, seedScores.length);
13219
+ const score = finiteMean(seedScores);
13081
13220
  return { score, seedScores, seedResults };
13082
13221
  }
13083
13222
  var init_runner = __esm(() => {
@@ -14064,7 +14203,7 @@ function compensatedSum(values, scale) {
14064
14203
  }
14065
14204
  return sum + compensation;
14066
14205
  }
14067
- function finiteMean(values) {
14206
+ function finiteMean2(values) {
14068
14207
  let smallest = Number.POSITIVE_INFINITY;
14069
14208
  let largest = Number.NEGATIVE_INFINITY;
14070
14209
  for (const value of values) {
@@ -14214,7 +14353,7 @@ function analyzeDistribution(request) {
14214
14353
  }
14215
14354
  const eligible = observed.length + rightCensored;
14216
14355
  const completionRate = eligible > 0 ? observed.length / eligible : null;
14217
- const arithmetic = observed.length > 0 ? finiteMean(observed) : null;
14356
+ const arithmetic = observed.length > 0 ? finiteMean2(observed) : null;
14218
14357
  const identical = observed.every((value) => value === observed[0]);
14219
14358
  const rawVariance = arithmetic === null ? null : finiteVariance(observed, arithmetic);
14220
14359
  const variance = rawVariance === 0 && !identical ? null : rawVariance;
@@ -14749,15 +14888,16 @@ function readBenefit(vars) {
14749
14888
  const value = vars.benefit;
14750
14889
  return typeof value === "number" && Number.isFinite(value) ? value : null;
14751
14890
  }
14752
- function observeMetrics(E, spec, end, startT, reachedAt, elapsedSec) {
14891
+ function observeMetrics(E, spec, end, moneyElapsedSec, milestoneElapsedSec, elapsedSec) {
14753
14892
  const amount = end.wallet.money.amount;
14754
14893
  const benefit = readBenefit(end.vars);
14755
14894
  return metricIds(spec).map((metricId) => {
14756
- if (metricId === "timeToMoney") {
14757
- if (reachedAt === undefined) {
14758
- return { metricId, status: "censored", value: null, lowerBound: { sec: elapsedSec, strict: true } };
14895
+ if (metricId === "timeToMoney" || metricId === "timeToMilestone") {
14896
+ const elapsed = metricId === "timeToMoney" ? moneyElapsedSec : milestoneElapsedSec;
14897
+ if (elapsed === undefined) {
14898
+ const strict = metricId === "timeToMoney" || !mayRecordAtActionStart(spec.milestoneKey ?? "");
14899
+ return { metricId, status: "censored", value: null, lowerBound: { sec: elapsedSec, strict } };
14759
14900
  }
14760
- const elapsed = reachedAt - startT;
14761
14901
  return { metricId, status: "observed", value: String(elapsed), num: elapsed };
14762
14902
  }
14763
14903
  if (metricId === "endMoney") {
@@ -14780,7 +14920,7 @@ function aggregate(E, rows, metricId) {
14780
14920
  }
14781
14921
  if (picked.some((metric) => metric.status !== "observed" || metric.value === null)) {
14782
14922
  const bounded = picked.every((metric) => metric.num !== undefined || metric.lowerBound !== undefined);
14783
- if (metricId !== "timeToMoney" || !bounded)
14923
+ if (metricId !== "timeToMoney" && metricId !== "timeToMilestone" || !bounded)
14784
14924
  return { metricId, status: "censored", value: null };
14785
14925
  const bounds = picked.map((metric) => metric.num ?? metric.lowerBound.sec);
14786
14926
  const total = bounds.reduce((sum, bound) => sum + bound, 0);
@@ -14867,6 +15007,10 @@ function prepareExperiment(request) {
14867
15007
  reject("goal target must be finite");
14868
15008
  if (metrics.includes("timeToMoney") && spec.goal === undefined)
14869
15009
  reject("timeToMoney requires a finite goal target");
15010
+ if (spec.milestoneKey !== undefined && spec.milestoneKey.trim().length === 0)
15011
+ reject("milestoneKey must not be blank");
15012
+ if (metrics.includes("timeToMilestone") && spec.milestoneKey === undefined)
15013
+ reject("timeToMilestone requires milestoneKey");
14870
15014
  if (!isJsonData(request.spec, new Set, true))
14871
15015
  reject("spec must be JSON data, because the spec hashes record it as JSON");
14872
15016
  requireScenarioData(request.scenario);
@@ -14893,7 +15037,8 @@ function checkExperimentSpec(request) {
14893
15037
  }
14894
15038
  function runExperiment(request) {
14895
15039
  const { spec, metrics, base, prepared, specHashes } = prepareExperiment(request);
14896
- const measuresReach = metrics.includes("timeToMoney");
15040
+ const measuresMoney = metrics.includes("timeToMoney");
15041
+ const measuresMilestone = metrics.includes("timeToMilestone");
14897
15042
  const E = request.engine;
14898
15043
  const { strategy: _scenarioStrategy, ...withoutStrategy } = request.scenario;
14899
15044
  const compiled = compileScenario({
@@ -14912,7 +15057,7 @@ function runExperiment(request) {
14912
15057
  models: request.modelRegistry,
14913
15058
  strategies: request.strategyRegistry
14914
15059
  });
14915
- const target = measuresReach && spec.goal !== undefined ? E.from(spec.goal.target) : undefined;
15060
+ const target = measuresMoney && spec.goal !== undefined ? E.from(spec.goal.target) : undefined;
14916
15061
  const stopAtReach = metrics.every((metricId) => metricId === "timeToMoney");
14917
15062
  const tasks = [];
14918
15063
  const perCandidate = [];
@@ -14937,21 +15082,41 @@ function runExperiment(request) {
14937
15082
  try {
14938
15083
  const opened = binding.fresh({ trialId: taskId, seed, plan });
14939
15084
  const scenario = opened.scenario;
14940
- let reachedAt;
15085
+ let moneyElapsedSec;
15086
+ let milestoneElapsedSec;
15087
+ let observedElapsedSec = 0;
15088
+ let stepStartElapsedSec = 0;
14941
15089
  const result = runScenario({
14942
15090
  ...scenario,
14943
15091
  run: {
14944
15092
  ...scenario.run,
15093
+ ...measuresMoney || measuresMilestone ? {
15094
+ observation: { ...scenario.run.observation, enabled: true, ...measuresMilestone ? { maxMilestones: 0 } : {} },
15095
+ observer: {
15096
+ ...scenario.run.observer,
15097
+ onStep: (fact) => {
15098
+ stepStartElapsedSec = observedElapsedSec;
15099
+ observedElapsedSec += fact.dt;
15100
+ scenario.run.observer?.onStep?.(fact);
15101
+ },
15102
+ onMilestone: (fact) => {
15103
+ if (measuresMilestone && milestoneElapsedSec === undefined && fact.key === spec.milestoneKey) {
15104
+ milestoneElapsedSec = fact.phase === "action-start" ? stepStartElapsedSec : observedElapsedSec;
15105
+ }
15106
+ scenario.run.observer?.onMilestone?.(fact);
15107
+ }
15108
+ }
15109
+ } : {},
14945
15110
  ...target !== undefined ? {
14946
15111
  until: (state) => {
14947
- if (reachedAt === undefined && E.cmp(state.wallet.money.amount, target) >= 0)
14948
- reachedAt = state.t;
14949
- return stopAtReach && reachedAt !== undefined;
15112
+ if (moneyElapsedSec === undefined && E.cmp(state.wallet.money.amount, target) >= 0)
15113
+ moneyElapsedSec = observedElapsedSec;
15114
+ return stopAtReach && moneyElapsedSec !== undefined;
14950
15115
  }
14951
15116
  } : {}
14952
15117
  }
14953
15118
  });
14954
- readings = observeMetrics(E, spec, result.end, result.start.t, reachedAt, runElapsedSec(result));
15119
+ readings = observeMetrics(E, spec, result.end, moneyElapsedSec, milestoneElapsedSec, runElapsedSec(result));
14955
15120
  } catch {
14956
15121
  readings = failedMetrics(spec);
14957
15122
  }
@@ -15039,6 +15204,7 @@ function runExperiment(request) {
15039
15204
  originalSpecHash: specHashes.original,
15040
15205
  effectiveSpecHash: specHashes.effective,
15041
15206
  effectiveRunHash: request.effectiveRunHash,
15207
+ ...measuresMilestone ? { milestoneKey: spec.milestoneKey } : {},
15042
15208
  budget: {
15043
15209
  candidates: spec.budget.candidates,
15044
15210
  seeds: spec.budget.seeds,
@@ -15068,30 +15234,31 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15068
15234
  const _ip8 = (input) => typeof input["originalSpecHash"] === "string";
15069
15235
  const _ip9 = (input) => typeof input["effectiveSpecHash"] === "string";
15070
15236
  const _ip10 = (input) => typeof input["effectiveRunHash"] === "string";
15071
- const _ip11 = (input) => typeof input["budget"] === "object" && input["budget"] !== null && _io2(input["budget"]);
15072
- const _ip12 = (input) => Array.isArray(input["tasks"]) && input["tasks"].every((elem) => typeof elem === "object" && elem !== null && _io3(elem));
15073
- const _ip13 = (input) => Array.isArray(input["candidates"]) && input["candidates"].every((elem) => typeof elem === "object" && elem !== null && _io4(elem));
15074
- const _ip14 = (input) => Array.isArray(input["elasticities"]) && input["elasticities"].every((elem) => typeof elem === "object" && elem !== null && _io6(elem));
15075
- const _ip15 = (input) => typeof input["pareto"] === "object" && input["pareto"] !== null && _io7(input["pareto"]);
15076
- const _ip16 = (input) => Array.isArray(input["warnings"]) && input["warnings"].every((elem) => typeof elem === "string");
15077
- const _ip17 = (input) => typeof input["taskId"] === "string";
15078
- const _ip18 = (input) => typeof input["candidateId"] === "string";
15079
- const _ip19 = (input) => typeof input["logicalSeed"] === "number";
15080
- const _ip20 = (input) => typeof input["engineId"] === "string";
15081
- const _ip21 = (input) => typeof input["engineVersion"] === "string";
15082
- const _ip22 = (input) => typeof input["modelId"] === "string";
15083
- const _ip23 = (input) => typeof input["modelVersion"] === "number";
15084
- const _ip24 = (input) => input["strategyId"] === null || typeof input["strategyId"] === "string";
15085
- const _ip25 = (input) => typeof input["modelParams"] === "string";
15086
- const _ip26 = (input) => typeof input["strategyParams"] === "string";
15087
- const _ip27 = (input) => typeof input["sessionPattern"] === "string";
15088
- const _ip28 = (input) => typeof input["sessionDays"] === "number";
15089
- const _ip29 = (input) => typeof input["horizonSec"] === "number";
15090
- const _ip30 = (input) => typeof input["stepSec"] === "number";
15091
- const _ip31 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15092
- const _ip32 = (input) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected";
15093
- const _ip33 = (input) => input["value"] === null || typeof input["value"] === "string";
15094
- 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[]; }>';
15237
+ const _ip11 = (input) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string";
15238
+ const _ip12 = (input) => typeof input["budget"] === "object" && input["budget"] !== null && _io2(input["budget"]);
15239
+ const _ip13 = (input) => Array.isArray(input["tasks"]) && input["tasks"].every((elem) => typeof elem === "object" && elem !== null && _io3(elem));
15240
+ const _ip14 = (input) => Array.isArray(input["candidates"]) && input["candidates"].every((elem) => typeof elem === "object" && elem !== null && _io4(elem));
15241
+ const _ip15 = (input) => Array.isArray(input["elasticities"]) && input["elasticities"].every((elem) => typeof elem === "object" && elem !== null && _io6(elem));
15242
+ const _ip16 = (input) => typeof input["pareto"] === "object" && input["pareto"] !== null && _io7(input["pareto"]);
15243
+ const _ip17 = (input) => Array.isArray(input["warnings"]) && input["warnings"].every((elem) => typeof elem === "string");
15244
+ const _ip18 = (input) => typeof input["taskId"] === "string";
15245
+ const _ip19 = (input) => typeof input["candidateId"] === "string";
15246
+ const _ip20 = (input) => typeof input["logicalSeed"] === "number";
15247
+ const _ip21 = (input) => typeof input["engineId"] === "string";
15248
+ const _ip22 = (input) => typeof input["engineVersion"] === "string";
15249
+ const _ip23 = (input) => typeof input["modelId"] === "string";
15250
+ const _ip24 = (input) => typeof input["modelVersion"] === "number";
15251
+ const _ip25 = (input) => input["strategyId"] === null || typeof input["strategyId"] === "string";
15252
+ const _ip26 = (input) => typeof input["modelParams"] === "string";
15253
+ const _ip27 = (input) => typeof input["strategyParams"] === "string";
15254
+ const _ip28 = (input) => typeof input["sessionPattern"] === "string";
15255
+ const _ip29 = (input) => typeof input["sessionDays"] === "number";
15256
+ const _ip30 = (input) => typeof input["horizonSec"] === "number";
15257
+ const _ip31 = (input) => typeof input["stepSec"] === "number";
15258
+ const _ip32 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15259
+ const _ip33 = (input) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected";
15260
+ const _ip34 = (input) => input["value"] === null || typeof input["value"] === "string";
15261
+ 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[]; }>';
15095
15262
  const _vp0 = (input, _path, _exceptionable = true) => input["schemaVersion"] === 1 || _report(_exceptionable, {
15096
15263
  path: _path + ".schemaVersion",
15097
15264
  expected: "1",
@@ -15151,7 +15318,12 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15151
15318
  expected: "string",
15152
15319
  value: input["effectiveRunHash"]
15153
15320
  });
15154
- const _vp11 = (input, _path, _exceptionable = true) => (typeof input["budget"] === "object" && input["budget"] !== null || _report(_exceptionable, {
15321
+ const _vp11 = (input, _path, _exceptionable = true) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string" || _report(_exceptionable, {
15322
+ path: _path + ".milestoneKey",
15323
+ expected: "(string | undefined)",
15324
+ value: input["milestoneKey"]
15325
+ });
15326
+ const _vp12 = (input, _path, _exceptionable = true) => (typeof input["budget"] === "object" && input["budget"] !== null || _report(_exceptionable, {
15155
15327
  path: _path + ".budget",
15156
15328
  expected: "Readonly<{ candidates: number; seeds: number; usedCandidates: number; usedSeeds: number; }>",
15157
15329
  value: input["budget"]
@@ -15160,7 +15332,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15160
15332
  expected: "Readonly<{ candidates: number; seeds: number; usedCandidates: number; usedSeeds: number; }>",
15161
15333
  value: input["budget"]
15162
15334
  });
15163
- const _vp12 = (input, _path, _exceptionable = true) => (Array.isArray(input["tasks"]) || _report(_exceptionable, {
15335
+ const _vp13 = (input, _path, _exceptionable = true) => (Array.isArray(input["tasks"]) || _report(_exceptionable, {
15164
15336
  path: _path + ".tasks",
15165
15337
  expected: _ve2,
15166
15338
  value: input["tasks"]
@@ -15179,7 +15351,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15179
15351
  });
15180
15352
  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; }>";
15181
15353
  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; }>>";
15182
- const _vp13 = (input, _path, _exceptionable = true) => (Array.isArray(input["candidates"]) || _report(_exceptionable, {
15354
+ const _vp14 = (input, _path, _exceptionable = true) => (Array.isArray(input["candidates"]) || _report(_exceptionable, {
15183
15355
  path: _path + ".candidates",
15184
15356
  expected: _ve4,
15185
15357
  value: input["candidates"]
@@ -15198,7 +15370,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15198
15370
  });
15199
15371
  const _ve3 = "Readonly<{ id: string; metrics: readonly Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>[]; constraintViolations: readonly string[]; }>";
15200
15372
  const _ve4 = "ReadonlyArray<Readonly<{ id: string; metrics: readonly Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>[]; constraintViolations: readonly string[]; }>>";
15201
- const _vp14 = (input, _path, _exceptionable = true) => (Array.isArray(input["elasticities"]) || _report(_exceptionable, {
15373
+ const _vp15 = (input, _path, _exceptionable = true) => (Array.isArray(input["elasticities"]) || _report(_exceptionable, {
15202
15374
  path: _path + ".elasticities",
15203
15375
  expected: _ve6,
15204
15376
  value: input["elasticities"]
@@ -15217,7 +15389,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15217
15389
  });
15218
15390
  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";...';
15219
15391
  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";...>';
15220
- const _vp15 = (input, _path, _exceptionable = true) => (typeof input["pareto"] === "object" && input["pareto"] !== null || _report(_exceptionable, {
15392
+ const _vp16 = (input, _path, _exceptionable = true) => (typeof input["pareto"] === "object" && input["pareto"] !== null || _report(_exceptionable, {
15221
15393
  path: _path + ".pareto",
15222
15394
  expected: 'Readonly<{ sense: "per-metric"; nonDominated: readonly string[]; uncompared: readonly string[]; }>',
15223
15395
  value: input["pareto"]
@@ -15226,7 +15398,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15226
15398
  expected: 'Readonly<{ sense: "per-metric"; nonDominated: readonly string[]; uncompared: readonly string[]; }>',
15227
15399
  value: input["pareto"]
15228
15400
  });
15229
- const _vp16 = (input, _path, _exceptionable = true) => (Array.isArray(input["warnings"]) || _report(_exceptionable, {
15401
+ const _vp17 = (input, _path, _exceptionable = true) => (Array.isArray(input["warnings"]) || _report(_exceptionable, {
15230
15402
  path: _path + ".warnings",
15231
15403
  expected: "ReadonlyArray<string>",
15232
15404
  value: input["warnings"]
@@ -15239,100 +15411,100 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15239
15411
  expected: "ReadonlyArray<string>",
15240
15412
  value: input["warnings"]
15241
15413
  });
15242
- const _vp17 = (input, _path, _exceptionable = true) => typeof input["taskId"] === "string" || _report(_exceptionable, {
15414
+ const _vp18 = (input, _path, _exceptionable = true) => typeof input["taskId"] === "string" || _report(_exceptionable, {
15243
15415
  path: _path + ".taskId",
15244
15416
  expected: "string",
15245
15417
  value: input["taskId"]
15246
15418
  });
15247
- const _vp18 = (input, _path, _exceptionable = true) => typeof input["candidateId"] === "string" || _report(_exceptionable, {
15419
+ const _vp19 = (input, _path, _exceptionable = true) => typeof input["candidateId"] === "string" || _report(_exceptionable, {
15248
15420
  path: _path + ".candidateId",
15249
15421
  expected: "string",
15250
15422
  value: input["candidateId"]
15251
15423
  });
15252
- const _vp19 = (input, _path, _exceptionable = true) => typeof input["logicalSeed"] === "number" || _report(_exceptionable, {
15424
+ const _vp20 = (input, _path, _exceptionable = true) => typeof input["logicalSeed"] === "number" || _report(_exceptionable, {
15253
15425
  path: _path + ".logicalSeed",
15254
15426
  expected: "number",
15255
15427
  value: input["logicalSeed"]
15256
15428
  });
15257
- const _vp20 = (input, _path, _exceptionable = true) => typeof input["engineId"] === "string" || _report(_exceptionable, {
15429
+ const _vp21 = (input, _path, _exceptionable = true) => typeof input["engineId"] === "string" || _report(_exceptionable, {
15258
15430
  path: _path + ".engineId",
15259
15431
  expected: "string",
15260
15432
  value: input["engineId"]
15261
15433
  });
15262
- const _vp21 = (input, _path, _exceptionable = true) => typeof input["engineVersion"] === "string" || _report(_exceptionable, {
15434
+ const _vp22 = (input, _path, _exceptionable = true) => typeof input["engineVersion"] === "string" || _report(_exceptionable, {
15263
15435
  path: _path + ".engineVersion",
15264
15436
  expected: "string",
15265
15437
  value: input["engineVersion"]
15266
15438
  });
15267
- const _vp22 = (input, _path, _exceptionable = true) => typeof input["modelId"] === "string" || _report(_exceptionable, {
15439
+ const _vp23 = (input, _path, _exceptionable = true) => typeof input["modelId"] === "string" || _report(_exceptionable, {
15268
15440
  path: _path + ".modelId",
15269
15441
  expected: "string",
15270
15442
  value: input["modelId"]
15271
15443
  });
15272
- const _vp23 = (input, _path, _exceptionable = true) => typeof input["modelVersion"] === "number" || _report(_exceptionable, {
15444
+ const _vp24 = (input, _path, _exceptionable = true) => typeof input["modelVersion"] === "number" || _report(_exceptionable, {
15273
15445
  path: _path + ".modelVersion",
15274
15446
  expected: "number",
15275
15447
  value: input["modelVersion"]
15276
15448
  });
15277
- const _vp24 = (input, _path, _exceptionable = true) => input["strategyId"] === null || typeof input["strategyId"] === "string" || _report(_exceptionable, {
15449
+ const _vp25 = (input, _path, _exceptionable = true) => input["strategyId"] === null || typeof input["strategyId"] === "string" || _report(_exceptionable, {
15278
15450
  path: _path + ".strategyId",
15279
15451
  expected: "(null | string)",
15280
15452
  value: input["strategyId"]
15281
15453
  });
15282
- const _vp25 = (input, _path, _exceptionable = true) => typeof input["modelParams"] === "string" || _report(_exceptionable, {
15454
+ const _vp26 = (input, _path, _exceptionable = true) => typeof input["modelParams"] === "string" || _report(_exceptionable, {
15283
15455
  path: _path + ".modelParams",
15284
15456
  expected: "string",
15285
15457
  value: input["modelParams"]
15286
15458
  });
15287
- const _vp26 = (input, _path, _exceptionable = true) => typeof input["strategyParams"] === "string" || _report(_exceptionable, {
15459
+ const _vp27 = (input, _path, _exceptionable = true) => typeof input["strategyParams"] === "string" || _report(_exceptionable, {
15288
15460
  path: _path + ".strategyParams",
15289
15461
  expected: "string",
15290
15462
  value: input["strategyParams"]
15291
15463
  });
15292
- const _vp27 = (input, _path, _exceptionable = true) => typeof input["sessionPattern"] === "string" || _report(_exceptionable, {
15464
+ const _vp28 = (input, _path, _exceptionable = true) => typeof input["sessionPattern"] === "string" || _report(_exceptionable, {
15293
15465
  path: _path + ".sessionPattern",
15294
15466
  expected: "string",
15295
15467
  value: input["sessionPattern"]
15296
15468
  });
15297
- const _vp28 = (input, _path, _exceptionable = true) => typeof input["sessionDays"] === "number" || _report(_exceptionable, {
15469
+ const _vp29 = (input, _path, _exceptionable = true) => typeof input["sessionDays"] === "number" || _report(_exceptionable, {
15298
15470
  path: _path + ".sessionDays",
15299
15471
  expected: "number",
15300
15472
  value: input["sessionDays"]
15301
15473
  });
15302
- const _vp29 = (input, _path, _exceptionable = true) => typeof input["horizonSec"] === "number" || _report(_exceptionable, {
15474
+ const _vp30 = (input, _path, _exceptionable = true) => typeof input["horizonSec"] === "number" || _report(_exceptionable, {
15303
15475
  path: _path + ".horizonSec",
15304
15476
  expected: "number",
15305
15477
  value: input["horizonSec"]
15306
15478
  });
15307
- const _vp30 = (input, _path, _exceptionable = true) => typeof input["stepSec"] === "number" || _report(_exceptionable, {
15479
+ const _vp31 = (input, _path, _exceptionable = true) => typeof input["stepSec"] === "number" || _report(_exceptionable, {
15308
15480
  path: _path + ".stepSec",
15309
15481
  expected: "number",
15310
15482
  value: input["stepSec"]
15311
15483
  });
15312
- const _vp31 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15484
+ const _vp32 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15313
15485
  path: _path + ".metricId",
15314
- expected: '("endMoney" | "timeToMoney" | "varBenefit")',
15486
+ expected: '("endMoney" | "timeToMilestone" | "timeToMoney" | "varBenefit")',
15315
15487
  value: input["metricId"]
15316
15488
  });
15317
- const _vp32 = (input, _path, _exceptionable = true) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected" || _report(_exceptionable, {
15489
+ const _vp33 = (input, _path, _exceptionable = true) => input["status"] === "censored" || input["status"] === "failed" || input["status"] === "observed" || input["status"] === "rejected" || _report(_exceptionable, {
15318
15490
  path: _path + ".status",
15319
15491
  expected: '("censored" | "failed" | "observed" | "rejected")',
15320
15492
  value: input["status"]
15321
15493
  });
15322
- const _vp33 = (input, _path, _exceptionable = true) => input["value"] === null || typeof input["value"] === "string" || _report(_exceptionable, {
15494
+ const _vp34 = (input, _path, _exceptionable = true) => input["value"] === null || typeof input["value"] === "string" || _report(_exceptionable, {
15323
15495
  path: _path + ".value",
15324
15496
  expected: "(null | string)",
15325
15497
  value: input["value"]
15326
15498
  });
15327
- 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);
15499
+ 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);
15328
15500
  const _io1 = (input) => typeof input.ref === "string" && typeof input.hash === "string";
15329
15501
  const _io2 = (input) => typeof input.candidates === "number" && typeof input.seeds === "number" && typeof input.usedCandidates === "number" && typeof input.usedSeeds === "number";
15330
- 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);
15502
+ 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);
15331
15503
  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"));
15332
- const _io5 = (input) => _ip31(input) && _ip32(input) && _ip33(input);
15333
- 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");
15504
+ const _io5 = (input) => _ip32(input) && _ip33(input) && _ip34(input);
15505
+ 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");
15334
15506
  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"));
15335
- 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);
15507
+ 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);
15336
15508
  const _vo1 = (input, _path, _exceptionable = true) => [typeof input.ref === "string" || _report(_exceptionable, {
15337
15509
  path: _path + ".ref",
15338
15510
  expected: "string",
@@ -15359,7 +15531,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15359
15531
  expected: "number",
15360
15532
  value: input.usedSeeds
15361
15533
  })].every((flag) => flag);
15362
- 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);
15534
+ 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);
15363
15535
  const _vo4 = (input, _path, _exceptionable = true) => [typeof input.id === "string" || _report(_exceptionable, {
15364
15536
  path: _path + ".id",
15365
15537
  expected: "string",
@@ -15393,12 +15565,12 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15393
15565
  expected: "ReadonlyArray<string>",
15394
15566
  value: input.constraintViolations
15395
15567
  })].every((flag) => flag);
15396
- const _vo5 = (input, _path, _exceptionable = true) => [_vp31(input, _path, _exceptionable), _vp32(input, _path, _exceptionable), _vp33(input, _path, _exceptionable)].every((flag) => flag);
15568
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp32(input, _path, _exceptionable), _vp33(input, _path, _exceptionable), _vp34(input, _path, _exceptionable)].every((flag) => flag);
15397
15569
  const _vo6 = (input, _path, _exceptionable = true) => [typeof input.dimensionId === "string" || _report(_exceptionable, {
15398
15570
  path: _path + ".dimensionId",
15399
15571
  expected: "string",
15400
15572
  value: input.dimensionId
15401
- }), _vp18(input, _path, _exceptionable), _vp31(input, _path, _exceptionable), input.method === "one-at-a-time-local" || _report(_exceptionable, {
15573
+ }), _vp19(input, _path, _exceptionable), _vp32(input, _path, _exceptionable), input.method === "one-at-a-time-local" || _report(_exceptionable, {
15402
15574
  path: _path + ".method",
15403
15575
  expected: '"one-at-a-time-local"',
15404
15576
  value: input.method
@@ -15484,105 +15656,82 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15484
15656
  };
15485
15657
  });
15486
15658
  })())(input), validateExperimentSpecValidator, validateExperimentSpec = (input) => (validateExperimentSpecValidator ??= (() => {
15487
- const _ip0 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15488
- const _ip1 = (input) => typeof input["id"] === "string";
15489
- const _ip2 = (input) => typeof input["min"] === "number";
15490
- const _ip3 = (input) => typeof input["max"] === "number";
15491
- 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<...>; }>";
15492
- const _vp0 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15493
- path: _path + ".metricId",
15494
- expected: '("endMoney" | "timeToMoney" | "varBenefit")',
15495
- value: input["metricId"]
15496
- });
15497
- const _ve1 = 'Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>';
15498
- const _ve2 = 'ReadonlyArray<Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>>';
15499
- const _ve3 = "(ReadonlyArray<Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>> | undefined)";
15500
- const _vp1 = (input, _path, _exceptionable = true) => typeof input["id"] === "string" || _report(_exceptionable, {
15501
- path: _path + ".id",
15502
- expected: "string",
15503
- value: input["id"]
15504
- });
15505
- 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; }>)';
15506
- const _vp2 = (input, _path, _exceptionable = true) => typeof input["min"] === "number" || _report(_exceptionable, {
15507
- path: _path + ".min",
15508
- expected: "number",
15509
- value: input["min"]
15510
- });
15511
- const _vp3 = (input, _path, _exceptionable = true) => typeof input["max"] === "number" || _report(_exceptionable, {
15512
- path: _path + ".max",
15513
- expected: "number",
15514
- value: input["max"]
15515
- });
15516
- 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[]; }>)';
15517
- 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));
15518
- const _io1 = (input) => typeof input.ref === "string" && typeof input.hash === "string";
15519
- const _io10 = (input) => input.kind === "number" && _ip2(input) && _ip3(input) && (input.scale === undefined || input.scale === "linear" || input.scale === "log");
15520
- 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"));
15521
- const _io12 = (input) => _ip1(input) && (Array.isArray(input.assignments) && input.assignments.every((elem) => typeof elem === "object" && elem !== null && _io13(elem)));
15522
- const _io13 = (input) => typeof input.dimensionId === "string" && (input.value === null || typeof input.value === "string" || typeof input.value === "number" || typeof input.value === "boolean");
15523
- const _io14 = (input) => typeof input.candidates === "number" && typeof input.seeds === "number";
15524
- const _io15 = (input) => typeof input.pattern === "string" && typeof input.days === "number";
15525
- const _io2 = (input) => _ip1(input) && typeof input.version === "number";
15526
- const _io3 = (input) => _ip1(input);
15527
- const _io4 = (input) => typeof input.target === "number";
15528
- const _io5 = (input) => _ip1(input) && _ip0(input) && (input.sense === "higher" || input.sense === "lower");
15529
- const _io6 = (input) => _ip1(input) && _ip0(input) && (input.op === "gte" || input.op === "lte") && typeof input.limit === "number";
15530
- 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"));
15531
- const _io8 = (input) => input.kind === "bool";
15532
- const _io9 = (input) => input.kind === "int" && _ip2(input) && _ip3(input);
15533
- const _iu0 = (input) => (() => {
15534
- if (input.kind === "bool")
15535
- return _io8(input);
15536
- else if (input.kind === "int")
15537
- return _io9(input);
15538
- else if (input.kind === "number")
15539
- return _io10(input);
15540
- else if (input.kind === "choice")
15541
- return _io11(input);
15542
- else
15543
- return false;
15544
- })();
15545
- const _vo0 = (input, _path, _exceptionable = true) => [input.schemaVersion === 1 || _report(_exceptionable, {
15659
+ const _ip0 = (input) => input["schemaVersion"] === 1;
15660
+ const _ip1 = (input) => typeof input["scenario"] === "object" && input["scenario"] !== null && _io1(input["scenario"]);
15661
+ const _ip2 = (input) => typeof input["model"] === "object" && input["model"] !== null && _io2(input["model"]);
15662
+ const _ip3 = (input) => input["strategy"] === undefined || typeof input["strategy"] === "object" && input["strategy"] !== null && _io3(input["strategy"]);
15663
+ const _ip4 = (input) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit";
15664
+ const _ip5 = (input) => input["goal"] === undefined || typeof input["goal"] === "object" && input["goal"] !== null && _io4(input["goal"]);
15665
+ const _ip6 = (input) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string";
15666
+ const _ip7 = (input) => input["objectives"] === undefined || Array.isArray(input["objectives"]) && input["objectives"].every((elem) => typeof elem === "object" && elem !== null && _io5(elem));
15667
+ const _ip8 = (input) => input["constraints"] === undefined || Array.isArray(input["constraints"]) && input["constraints"].every((elem) => typeof elem === "object" && elem !== null && _io6(elem));
15668
+ const _ip9 = (input) => Array.isArray(input["dimensions"]) && input["dimensions"].every((elem) => typeof elem === "object" && elem !== null && _io7(elem));
15669
+ const _ip10 = (input) => input["candidates"] === undefined || Array.isArray(input["candidates"]) && input["candidates"].every((elem) => typeof elem === "object" && elem !== null && _io12(elem));
15670
+ const _ip11 = (input) => typeof input["budget"] === "object" && input["budget"] !== null && _io14(input["budget"]);
15671
+ const _ip12 = (input) => Array.isArray(input["seeds"]) && input["seeds"].every((elem) => typeof elem === "number");
15672
+ const _ip13 = (input) => typeof input["horizonSec"] === "number";
15673
+ const _ip14 = (input) => typeof input["stepSec"] === "number";
15674
+ const _ip15 = (input) => typeof input["session"] === "object" && input["session"] !== null && _io15(input["session"]);
15675
+ const _ip16 = (input) => typeof input["id"] === "string";
15676
+ const _ip17 = (input) => typeof input["min"] === "number";
15677
+ const _ip18 = (input) => typeof input["max"] === "number";
15678
+ 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<...>; }>";
15679
+ const _vp0 = (input, _path, _exceptionable = true) => input["schemaVersion"] === 1 || _report(_exceptionable, {
15546
15680
  path: _path + ".schemaVersion",
15547
15681
  expected: "1",
15548
- value: input.schemaVersion
15549
- }), (typeof input.scenario === "object" && input.scenario !== null || _report(_exceptionable, {
15682
+ value: input["schemaVersion"]
15683
+ });
15684
+ const _vp1 = (input, _path, _exceptionable = true) => (typeof input["scenario"] === "object" && input["scenario"] !== null || _report(_exceptionable, {
15550
15685
  path: _path + ".scenario",
15551
15686
  expected: "Readonly<{ ref: string; hash: string; }>",
15552
- value: input.scenario
15553
- })) && _vo1(input.scenario, _path + ".scenario", _exceptionable) || _report(_exceptionable, {
15687
+ value: input["scenario"]
15688
+ })) && _vo1(input["scenario"], _path + ".scenario", _exceptionable) || _report(_exceptionable, {
15554
15689
  path: _path + ".scenario",
15555
15690
  expected: "Readonly<{ ref: string; hash: string; }>",
15556
- value: input.scenario
15557
- }), (typeof input.model === "object" && input.model !== null || _report(_exceptionable, {
15691
+ value: input["scenario"]
15692
+ });
15693
+ const _vp2 = (input, _path, _exceptionable = true) => (typeof input["model"] === "object" && input["model"] !== null || _report(_exceptionable, {
15558
15694
  path: _path + ".model",
15559
15695
  expected: "Readonly<{ id: string; version: number; }>",
15560
- value: input.model
15561
- })) && _vo2(input.model, _path + ".model", _exceptionable) || _report(_exceptionable, {
15696
+ value: input["model"]
15697
+ })) && _vo2(input["model"], _path + ".model", _exceptionable) || _report(_exceptionable, {
15562
15698
  path: _path + ".model",
15563
15699
  expected: "Readonly<{ id: string; version: number; }>",
15564
- value: input.model
15565
- }), input.strategy === undefined || (typeof input.strategy === "object" && input.strategy !== null || _report(_exceptionable, {
15700
+ value: input["model"]
15701
+ });
15702
+ const _vp3 = (input, _path, _exceptionable = true) => input["strategy"] === undefined || (typeof input["strategy"] === "object" && input["strategy"] !== null || _report(_exceptionable, {
15566
15703
  path: _path + ".strategy",
15567
15704
  expected: "(Readonly<{ id: string; }> | undefined)",
15568
- value: input.strategy
15569
- })) && _vo3(input.strategy, _path + ".strategy", _exceptionable) || _report(_exceptionable, {
15705
+ value: input["strategy"]
15706
+ })) && _vo3(input["strategy"], _path + ".strategy", _exceptionable) || _report(_exceptionable, {
15570
15707
  path: _path + ".strategy",
15571
15708
  expected: "(Readonly<{ id: string; }> | undefined)",
15572
- value: input.strategy
15573
- }), _vp0(input, _path, _exceptionable), input.goal === undefined || (typeof input.goal === "object" && input.goal !== null || _report(_exceptionable, {
15709
+ value: input["strategy"]
15710
+ });
15711
+ const _vp4 = (input, _path, _exceptionable = true) => input["metricId"] === "endMoney" || input["metricId"] === "timeToMilestone" || input["metricId"] === "timeToMoney" || input["metricId"] === "varBenefit" || _report(_exceptionable, {
15712
+ path: _path + ".metricId",
15713
+ expected: '("endMoney" | "timeToMilestone" | "timeToMoney" | "varBenefit")',
15714
+ value: input["metricId"]
15715
+ });
15716
+ const _vp5 = (input, _path, _exceptionable = true) => input["goal"] === undefined || (typeof input["goal"] === "object" && input["goal"] !== null || _report(_exceptionable, {
15574
15717
  path: _path + ".goal",
15575
15718
  expected: "(Readonly<{ target: number; }> | undefined)",
15576
- value: input.goal
15577
- })) && _vo4(input.goal, _path + ".goal", _exceptionable) || _report(_exceptionable, {
15719
+ value: input["goal"]
15720
+ })) && _vo4(input["goal"], _path + ".goal", _exceptionable) || _report(_exceptionable, {
15578
15721
  path: _path + ".goal",
15579
15722
  expected: "(Readonly<{ target: number; }> | undefined)",
15580
- value: input.goal
15581
- }), input.objectives === undefined || (Array.isArray(input.objectives) || _report(_exceptionable, {
15723
+ value: input["goal"]
15724
+ });
15725
+ const _vp6 = (input, _path, _exceptionable = true) => input["milestoneKey"] === undefined || typeof input["milestoneKey"] === "string" || _report(_exceptionable, {
15726
+ path: _path + ".milestoneKey",
15727
+ expected: "(string | undefined)",
15728
+ value: input["milestoneKey"]
15729
+ });
15730
+ const _vp7 = (input, _path, _exceptionable = true) => input["objectives"] === undefined || (Array.isArray(input["objectives"]) || _report(_exceptionable, {
15582
15731
  path: _path + ".objectives",
15583
15732
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; sense: "higher" | "lower"; }>> | undefined)',
15584
- value: input.objectives
15585
- })) && input.objectives.map((elem, _index9) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15733
+ value: input["objectives"]
15734
+ })) && input["objectives"].map((elem, _index9) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15586
15735
  path: _path + ".objectives[" + _index9 + "]",
15587
15736
  expected: 'Readonly<{ id: string; metricId: ExperimentMetricId; sense: "higher" | "lower"; }>',
15588
15737
  value: elem
@@ -15593,12 +15742,13 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15593
15742
  })).every((flag) => flag) || _report(_exceptionable, {
15594
15743
  path: _path + ".objectives",
15595
15744
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; sense: "higher" | "lower"; }>> | undefined)',
15596
- value: input.objectives
15597
- }), input.constraints === undefined || (Array.isArray(input.constraints) || _report(_exceptionable, {
15745
+ value: input["objectives"]
15746
+ });
15747
+ const _vp8 = (input, _path, _exceptionable = true) => input["constraints"] === undefined || (Array.isArray(input["constraints"]) || _report(_exceptionable, {
15598
15748
  path: _path + ".constraints",
15599
15749
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; op: "gte" | "lte"; limit: number; }>> | undefined)',
15600
- value: input.constraints
15601
- })) && input.constraints.map((elem, _index10) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15750
+ value: input["constraints"]
15751
+ })) && input["constraints"].map((elem, _index10) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15602
15752
  path: _path + ".constraints[" + _index10 + "]",
15603
15753
  expected: 'Readonly<{ id: string; metricId: ExperimentMetricId; op: "gte" | "lte"; limit: number; }>',
15604
15754
  value: elem
@@ -15609,12 +15759,13 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15609
15759
  })).every((flag) => flag) || _report(_exceptionable, {
15610
15760
  path: _path + ".constraints",
15611
15761
  expected: '(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; op: "gte" | "lte"; limit: number; }>> | undefined)',
15612
- value: input.constraints
15613
- }), (Array.isArray(input.dimensions) || _report(_exceptionable, {
15762
+ value: input["constraints"]
15763
+ });
15764
+ const _vp9 = (input, _path, _exceptionable = true) => (Array.isArray(input["dimensions"]) || _report(_exceptionable, {
15614
15765
  path: _path + ".dimensions",
15615
15766
  expected: _ve2,
15616
- value: input.dimensions
15617
- })) && input.dimensions.map((elem, _index11) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15767
+ value: input["dimensions"]
15768
+ })) && input["dimensions"].map((elem, _index11) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15618
15769
  path: _path + ".dimensions[" + _index11 + "]",
15619
15770
  expected: _ve1,
15620
15771
  value: elem
@@ -15625,12 +15776,15 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15625
15776
  })).every((flag) => flag) || _report(_exceptionable, {
15626
15777
  path: _path + ".dimensions",
15627
15778
  expected: _ve2,
15628
- value: input.dimensions
15629
- }), input.candidates === undefined || (Array.isArray(input.candidates) || _report(_exceptionable, {
15779
+ value: input["dimensions"]
15780
+ });
15781
+ const _ve1 = 'Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>';
15782
+ const _ve2 = 'ReadonlyArray<Readonly<{ id: string; target: "model" | "session" | "strategy"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>>';
15783
+ const _vp10 = (input, _path, _exceptionable = true) => input["candidates"] === undefined || (Array.isArray(input["candidates"]) || _report(_exceptionable, {
15630
15784
  path: _path + ".candidates",
15631
15785
  expected: _ve3,
15632
- value: input.candidates
15633
- })) && input.candidates.map((elem, _index12) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15786
+ value: input["candidates"]
15787
+ })) && input["candidates"].map((elem, _index12) => (typeof elem === "object" && elem !== null || _report(_exceptionable, {
15634
15788
  path: _path + ".candidates[" + _index12 + "]",
15635
15789
  expected: "Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>",
15636
15790
  value: elem
@@ -15641,44 +15795,96 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15641
15795
  })).every((flag) => flag) || _report(_exceptionable, {
15642
15796
  path: _path + ".candidates",
15643
15797
  expected: _ve3,
15644
- value: input.candidates
15645
- }), (typeof input.budget === "object" && input.budget !== null || _report(_exceptionable, {
15798
+ value: input["candidates"]
15799
+ });
15800
+ const _ve3 = "(ReadonlyArray<Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>> | undefined)";
15801
+ const _vp11 = (input, _path, _exceptionable = true) => (typeof input["budget"] === "object" && input["budget"] !== null || _report(_exceptionable, {
15646
15802
  path: _path + ".budget",
15647
15803
  expected: "Readonly<{ candidates: number; seeds: number; }>",
15648
- value: input.budget
15649
- })) && _vo14(input.budget, _path + ".budget", _exceptionable) || _report(_exceptionable, {
15804
+ value: input["budget"]
15805
+ })) && _vo14(input["budget"], _path + ".budget", _exceptionable) || _report(_exceptionable, {
15650
15806
  path: _path + ".budget",
15651
15807
  expected: "Readonly<{ candidates: number; seeds: number; }>",
15652
- value: input.budget
15653
- }), (Array.isArray(input.seeds) || _report(_exceptionable, {
15808
+ value: input["budget"]
15809
+ });
15810
+ const _vp12 = (input, _path, _exceptionable = true) => (Array.isArray(input["seeds"]) || _report(_exceptionable, {
15654
15811
  path: _path + ".seeds",
15655
15812
  expected: "ReadonlyArray<number>",
15656
- value: input.seeds
15657
- })) && input.seeds.map((elem, _index13) => typeof elem === "number" || _report(_exceptionable, {
15813
+ value: input["seeds"]
15814
+ })) && input["seeds"].map((elem, _index13) => typeof elem === "number" || _report(_exceptionable, {
15658
15815
  path: _path + ".seeds[" + _index13 + "]",
15659
15816
  expected: "number",
15660
15817
  value: elem
15661
15818
  })).every((flag) => flag) || _report(_exceptionable, {
15662
15819
  path: _path + ".seeds",
15663
15820
  expected: "ReadonlyArray<number>",
15664
- value: input.seeds
15665
- }), typeof input.horizonSec === "number" || _report(_exceptionable, {
15821
+ value: input["seeds"]
15822
+ });
15823
+ const _vp13 = (input, _path, _exceptionable = true) => typeof input["horizonSec"] === "number" || _report(_exceptionable, {
15666
15824
  path: _path + ".horizonSec",
15667
15825
  expected: "number",
15668
- value: input.horizonSec
15669
- }), typeof input.stepSec === "number" || _report(_exceptionable, {
15826
+ value: input["horizonSec"]
15827
+ });
15828
+ const _vp14 = (input, _path, _exceptionable = true) => typeof input["stepSec"] === "number" || _report(_exceptionable, {
15670
15829
  path: _path + ".stepSec",
15671
15830
  expected: "number",
15672
- value: input.stepSec
15673
- }), (typeof input.session === "object" && input.session !== null || _report(_exceptionable, {
15831
+ value: input["stepSec"]
15832
+ });
15833
+ const _vp15 = (input, _path, _exceptionable = true) => (typeof input["session"] === "object" && input["session"] !== null || _report(_exceptionable, {
15674
15834
  path: _path + ".session",
15675
15835
  expected: "Readonly<{ pattern: string; days: number; }>",
15676
- value: input.session
15677
- })) && _vo15(input.session, _path + ".session", _exceptionable) || _report(_exceptionable, {
15836
+ value: input["session"]
15837
+ })) && _vo15(input["session"], _path + ".session", _exceptionable) || _report(_exceptionable, {
15678
15838
  path: _path + ".session",
15679
15839
  expected: "Readonly<{ pattern: string; days: number; }>",
15680
- value: input.session
15681
- })].every((flag) => flag);
15840
+ value: input["session"]
15841
+ });
15842
+ const _vp16 = (input, _path, _exceptionable = true) => typeof input["id"] === "string" || _report(_exceptionable, {
15843
+ path: _path + ".id",
15844
+ expected: "string",
15845
+ value: input["id"]
15846
+ });
15847
+ 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; }>)';
15848
+ const _vp17 = (input, _path, _exceptionable = true) => typeof input["min"] === "number" || _report(_exceptionable, {
15849
+ path: _path + ".min",
15850
+ expected: "number",
15851
+ value: input["min"]
15852
+ });
15853
+ const _vp18 = (input, _path, _exceptionable = true) => typeof input["max"] === "number" || _report(_exceptionable, {
15854
+ path: _path + ".max",
15855
+ expected: "number",
15856
+ value: input["max"]
15857
+ });
15858
+ 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[]; }>)';
15859
+ 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);
15860
+ const _io1 = (input) => typeof input.ref === "string" && typeof input.hash === "string";
15861
+ const _io10 = (input) => input.kind === "number" && _ip17(input) && _ip18(input) && (input.scale === undefined || input.scale === "linear" || input.scale === "log");
15862
+ 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"));
15863
+ const _io12 = (input) => _ip16(input) && (Array.isArray(input.assignments) && input.assignments.every((elem) => typeof elem === "object" && elem !== null && _io13(elem)));
15864
+ const _io13 = (input) => typeof input.dimensionId === "string" && (input.value === null || typeof input.value === "string" || typeof input.value === "number" || typeof input.value === "boolean");
15865
+ const _io14 = (input) => typeof input.candidates === "number" && typeof input.seeds === "number";
15866
+ const _io15 = (input) => typeof input.pattern === "string" && typeof input.days === "number";
15867
+ const _io2 = (input) => _ip16(input) && typeof input.version === "number";
15868
+ const _io3 = (input) => _ip16(input);
15869
+ const _io4 = (input) => typeof input.target === "number";
15870
+ const _io5 = (input) => _ip16(input) && _ip4(input) && (input.sense === "higher" || input.sense === "lower");
15871
+ const _io6 = (input) => _ip16(input) && _ip4(input) && (input.op === "gte" || input.op === "lte") && typeof input.limit === "number";
15872
+ 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"));
15873
+ const _io8 = (input) => input.kind === "bool";
15874
+ const _io9 = (input) => input.kind === "int" && _ip17(input) && _ip18(input);
15875
+ const _iu0 = (input) => (() => {
15876
+ if (input.kind === "bool")
15877
+ return _io8(input);
15878
+ else if (input.kind === "int")
15879
+ return _io9(input);
15880
+ else if (input.kind === "number")
15881
+ return _io10(input);
15882
+ else if (input.kind === "choice")
15883
+ return _io11(input);
15884
+ else
15885
+ return false;
15886
+ })();
15887
+ 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);
15682
15888
  const _vo1 = (input, _path, _exceptionable = true) => [typeof input.ref === "string" || _report(_exceptionable, {
15683
15889
  path: _path + ".ref",
15684
15890
  expected: "string",
@@ -15692,7 +15898,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15692
15898
  path: _path + ".kind",
15693
15899
  expected: '"number"',
15694
15900
  value: input.kind
15695
- }), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable), input.scale === undefined || input.scale === "linear" || input.scale === "log" || _report(_exceptionable, {
15901
+ }), _vp17(input, _path, _exceptionable), _vp18(input, _path, _exceptionable), input.scale === undefined || input.scale === "linear" || input.scale === "log" || _report(_exceptionable, {
15696
15902
  path: _path + ".scale",
15697
15903
  expected: '("linear" | "log" | undefined)',
15698
15904
  value: input.scale
@@ -15714,7 +15920,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15714
15920
  expected: "ReadonlyArray<ExperimentScalar>",
15715
15921
  value: input.values
15716
15922
  })].every((flag) => flag);
15717
- const _vo12 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), (Array.isArray(input.assignments) || _report(_exceptionable, {
15923
+ const _vo12 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), (Array.isArray(input.assignments) || _report(_exceptionable, {
15718
15924
  path: _path + ".assignments",
15719
15925
  expected: "ReadonlyArray<Readonly<{ dimensionId: string; value: ExperimentScalar; }>>",
15720
15926
  value: input.assignments
@@ -15758,23 +15964,23 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15758
15964
  expected: "number",
15759
15965
  value: input.days
15760
15966
  })].every((flag) => flag);
15761
- const _vo2 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), typeof input.version === "number" || _report(_exceptionable, {
15967
+ const _vo2 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), typeof input.version === "number" || _report(_exceptionable, {
15762
15968
  path: _path + ".version",
15763
15969
  expected: "number",
15764
15970
  value: input.version
15765
15971
  })].every((flag) => flag);
15766
- const _vo3 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable)].every((flag) => flag);
15972
+ const _vo3 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable)].every((flag) => flag);
15767
15973
  const _vo4 = (input, _path, _exceptionable = true) => [typeof input.target === "number" || _report(_exceptionable, {
15768
15974
  path: _path + ".target",
15769
15975
  expected: "number",
15770
15976
  value: input.target
15771
15977
  })].every((flag) => flag);
15772
- const _vo5 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), _vp0(input, _path, _exceptionable), input.sense === "higher" || input.sense === "lower" || _report(_exceptionable, {
15978
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), input.sense === "higher" || input.sense === "lower" || _report(_exceptionable, {
15773
15979
  path: _path + ".sense",
15774
15980
  expected: '("higher" | "lower")',
15775
15981
  value: input.sense
15776
15982
  })].every((flag) => flag);
15777
- const _vo6 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), _vp0(input, _path, _exceptionable), input.op === "gte" || input.op === "lte" || _report(_exceptionable, {
15983
+ const _vo6 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), _vp4(input, _path, _exceptionable), input.op === "gte" || input.op === "lte" || _report(_exceptionable, {
15778
15984
  path: _path + ".op",
15779
15985
  expected: '("gte" | "lte")',
15780
15986
  value: input.op
@@ -15783,7 +15989,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15783
15989
  expected: "number",
15784
15990
  value: input.limit
15785
15991
  })].every((flag) => flag);
15786
- const _vo7 = (input, _path, _exceptionable = true) => [_vp1(input, _path, _exceptionable), input.target === "model" || input.target === "session" || input.target === "strategy" || _report(_exceptionable, {
15992
+ const _vo7 = (input, _path, _exceptionable = true) => [_vp16(input, _path, _exceptionable), input.target === "model" || input.target === "session" || input.target === "strategy" || _report(_exceptionable, {
15787
15993
  path: _path + ".target",
15788
15994
  expected: '("model" | "session" | "strategy")',
15789
15995
  value: input.target
@@ -15825,7 +16031,7 @@ var EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1", EXPERIMENT_ESTIMATOR_VER
15825
16031
  path: _path + ".kind",
15826
16032
  expected: '"int"',
15827
16033
  value: input.kind
15828
- }), _vp2(input, _path, _exceptionable), _vp3(input, _path, _exceptionable)].every((flag) => flag);
16034
+ }), _vp17(input, _path, _exceptionable), _vp18(input, _path, _exceptionable)].every((flag) => flag);
15829
16035
  const _vu0 = (input, _path, _exceptionable = true) => (() => {
15830
16036
  if (input.kind === "bool")
15831
16037
  return _vo8(input, _path, _exceptionable);
@@ -18125,7 +18331,7 @@ var init_checks3 = __esm(() => {
18125
18331
  FAIL_ON = ["error", "warn", "inconclusive", "unsupported"];
18126
18332
  });
18127
18333
 
18128
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
18334
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
18129
18335
  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;
18130
18336
  var init__accessExpressionAsString = __esm(() => {
18131
18337
  RESERVED = /* @__PURE__ */ new Set([
@@ -18189,6 +18395,9 @@ function nonfiniteThresholds(rubric) {
18189
18395
  if (rule.upperThreshold !== null && !Number.isFinite(rule.upperThreshold)) {
18190
18396
  issues.push(`$input.rules[${index}].upperThreshold finite number or null`);
18191
18397
  }
18398
+ if (rule.metricVersion !== undefined && rule.metricVersion !== null && !Number.isFinite(rule.metricVersion)) {
18399
+ issues.push(`$input.rules[${index}].metricVersion finite number or null`);
18400
+ }
18192
18401
  });
18193
18402
  return issues;
18194
18403
  }
@@ -18214,8 +18423,16 @@ function evaluateGroups(failOn, groups, checks) {
18214
18423
  const pass = reports.every((report) => report.pass);
18215
18424
  return { ...first, results, pass, exitCode: pass ? 0 : 2 };
18216
18425
  }
18217
- function findReadings(artifact, metricId) {
18218
- return artifact.metrics.filter((reading) => reading.metricId === metricId);
18426
+ function ruleReadings(artifact, rule) {
18427
+ const pinned = rule.metricVersion ?? null;
18428
+ return artifact.metrics.filter((reading) => reading.metricId === rule.metricId && (pinned === null || reading.metricVersion === pinned));
18429
+ }
18430
+ function metricVersionRef(version) {
18431
+ return `metric-version:${version}`;
18432
+ }
18433
+ function ruleRefLabel(rule) {
18434
+ const pinned = rule.metricVersion ?? null;
18435
+ return pinned === null ? `metric:${rule.metricId}` : `metric:${rule.metricId} ${metricVersionRef(pinned)}`;
18219
18436
  }
18220
18437
  function isUnavailable(reading) {
18221
18438
  return reading.reading === "unavailable" || reading.status !== "ok";
@@ -18382,27 +18599,40 @@ function judgeArtifact(artifact, rubric) {
18382
18599
  const issues = [];
18383
18600
  const groups = new Map;
18384
18601
  const estimatedObservations = [];
18602
+ const estimatedReadings = new Set;
18385
18603
  const prepared = [];
18386
18604
  for (const rule of rubric.rules) {
18387
- const readings = findReadings(artifact, rule.metricId);
18605
+ const label = ruleRefLabel(rule);
18606
+ const readings = ruleReadings(artifact, rule);
18388
18607
  const reading = readings[0];
18389
18608
  if (!reading) {
18390
- issues.push(`missing evidenceRef metric:${rule.metricId}`);
18609
+ const others = artifact.metrics.filter((item) => item.metricId === rule.metricId).map((item) => metricVersionRef(item.metricVersion));
18610
+ issues.push(others.length > 0 ? `missing evidenceRef ${label} (artifact has ${others.join(", ")})` : `missing evidenceRef ${label}`);
18391
18611
  continue;
18392
18612
  }
18393
18613
  if (readings.length > 1) {
18394
- issues.push(`ambiguous evidenceRef metric:${rule.metricId} matches ${readings.length} readings`);
18614
+ issues.push(`ambiguous evidenceRef ${label} matches ${readings.length} readings`);
18395
18615
  continue;
18396
18616
  }
18397
18617
  const mapped = toObservation(artifact, reading, rule);
18398
- const key = JSON.stringify([reading.metricId, mapped.observation.identity.method, mapped.observation.identity.unit]);
18618
+ const key = JSON.stringify([
18619
+ reading.metricId,
18620
+ reading.metricVersion,
18621
+ mapped.observation.identity.method,
18622
+ mapped.observation.identity.unit
18623
+ ]);
18399
18624
  const group = groups.get(key) ?? { observation: mapped.observation, indices: [] };
18400
18625
  group.indices.push(prepared.length);
18401
18626
  groups.set(key, group);
18402
- if (mapped.estimated && !estimatedObservations.some((item) => item.metricId === reading.metricId)) {
18627
+ if (mapped.estimated && !estimatedReadings.has(reading)) {
18628
+ estimatedReadings.add(reading);
18403
18629
  estimatedObservations.push(mapped.estimated);
18404
18630
  }
18405
- prepared.push({ rule, estimated: mapped.estimated, ref: `metric:${rule.metricId}` });
18631
+ prepared.push({
18632
+ rule,
18633
+ estimated: mapped.estimated,
18634
+ refs: [`metric:${rule.metricId}`, metricVersionRef(reading.metricVersion)]
18635
+ });
18406
18636
  }
18407
18637
  if (issues.length > 0)
18408
18638
  return { ok: false, issues };
@@ -18415,7 +18645,7 @@ function judgeArtifact(artifact, rubric) {
18415
18645
  id: item.rule.id,
18416
18646
  category: outcome.category,
18417
18647
  severity: outcome.severity,
18418
- evidenceRefs: [item.ref],
18648
+ evidenceRefs: item.refs,
18419
18649
  assumptionRefs,
18420
18650
  recommendationId: outcome.recommendationId,
18421
18651
  status: outcome.status,
@@ -18439,9 +18669,9 @@ function judgeArtifact(artifact, rubric) {
18439
18669
  return { ok: true, report };
18440
18670
  }
18441
18671
  var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validateRubric = (input) => (validateRubricValidator ??= (() => {
18442
- 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; }>[]; }>';
18443
- const _ve1 = "Readonly<{ id: string; metricId: string; operator: CheckOperator; threshold: number; upperThreshold: number | null; unit: string; severity: CheckSeverity; missingPolicy: MissingPolicy; ... 8 more ...; recommendationId: RecommendationId; }>";
18444
- 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; }>>";
18672
+ 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; }>[]; }>';
18673
+ 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; }>";
18674
+ 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; }>>";
18445
18675
  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")';
18446
18676
  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) => {
18447
18677
  if (["schemaVersion", "contract", "rubricVersion", "failOn", "rules"].some((prop) => key === prop))
@@ -18451,8 +18681,8 @@ var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validate
18451
18681
  return true;
18452
18682
  return false;
18453
18683
  }));
18454
- 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) => {
18455
- if (["id", "metricId", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
18684
+ 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) => {
18685
+ if (["id", "metricId", "metricVersion", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
18456
18686
  return true;
18457
18687
  const value = input[key];
18458
18688
  if (value === undefined)
@@ -18525,6 +18755,10 @@ var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validate
18525
18755
  path: _path + ".metricId",
18526
18756
  expected: "string",
18527
18757
  value: input.metricId
18758
+ }), input.metricVersion === null || input.metricVersion === undefined || typeof input.metricVersion === "number" || _report(_exceptionable, {
18759
+ path: _path + ".metricVersion",
18760
+ expected: "(null | number | undefined)",
18761
+ value: input.metricVersion
18528
18762
  }), input.operator === "between" || input.operator === "eq" || input.operator === "gt" || input.operator === "gte" || input.operator === "lt" || input.operator === "lte" || _report(_exceptionable, {
18529
18763
  path: _path + ".operator",
18530
18764
  expected: '("between" | "eq" | "gt" | "gte" | "lt" | "lte")',
@@ -18586,7 +18820,7 @@ var JUDGE_ALGORITHM_VERSION = "judge-rules-1", validateRubricValidator, validate
18586
18820
  expected: _ve3,
18587
18821
  value: input.recommendationId
18588
18822
  }), Object.keys(input).length === 17 || (_exceptionable === false || Object.keys(input).map((key) => {
18589
- if (["id", "metricId", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
18823
+ if (["id", "metricId", "metricVersion", "operator", "threshold", "upperThreshold", "unit", "severity", "missingPolicy", "definition", "method", "scenarioVersion", "specVersion", "engineId", "pluginVersion", "seed", "algorithmVersion", "recommendationId"].some((prop) => key === prop))
18590
18824
  return true;
18591
18825
  const value = input[key];
18592
18826
  if (value === undefined)
@@ -18672,7 +18906,7 @@ var init_judge = __esm(() => {
18672
18906
  // ../core/dist/sim/analysis/flow.js
18673
18907
  var init_flow = () => {};
18674
18908
 
18675
- // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_isTypeUint32.mjs
18909
+ // ../../node_modules/.bun/typia@15.2.2+38c91d72aa8392f2/node_modules/typia/lib/internal/_isTypeUint32.mjs
18676
18910
  var _isTypeUint32 = (value) => Math.floor(value) === value && MINIMUM <= value && value <= MAXIMUM, MINIMUM = 0, MAXIMUM;
18677
18911
  var init__isTypeUint32 = __esm(() => {
18678
18912
  MAXIMUM = 2 ** 32 - 1;
@@ -19106,12 +19340,12 @@ function estimateLtvPerUser(args) {
19106
19340
  const fullDays = Math.floor(Math.max(0, horizonDays));
19107
19341
  const remainder = Math.max(0, horizonDays - fullDays);
19108
19342
  let gross = 0;
19109
- for (let day = 1;day <= fullDays; day++) {
19343
+ for (let day = 0;day < fullDays; day++) {
19110
19344
  const active = retentionAtDay(config.retention, day);
19111
19345
  gross += active * (payerConversion * arppuDaily + adArpDau);
19112
19346
  }
19113
19347
  if (remainder > 0) {
19114
- const active = retentionAtDay(config.retention, fullDays + 1);
19348
+ const active = retentionAtDay(config.retention, fullDays);
19115
19349
  gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
19116
19350
  }
19117
19351
  const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
@@ -19233,10 +19467,11 @@ function applyLogNormalShock(mean, sigma, z) {
19233
19467
  }
19234
19468
  function makeUncertainConfig(base, rng) {
19235
19469
  const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
19236
- const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
19237
- const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
19238
- const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
19239
- const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
19470
+ const retention = (anchor) => clamp013(applyLogNormalShock(anchor, base.uncertainty.sigma.retention, shock.retention));
19471
+ const d1 = retention(base.retention.d1);
19472
+ const d7 = retention(base.retention.d7);
19473
+ const d30 = retention(base.retention.d30);
19474
+ const d90 = retention(base.retention.d90);
19240
19475
  return {
19241
19476
  ...base,
19242
19477
  retention: {
@@ -19260,6 +19495,23 @@ function quantileFromSorted(sorted, q) {
19260
19495
  const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
19261
19496
  return sorted[idx] ?? 0;
19262
19497
  }
19498
+ function quantileKey(q) {
19499
+ const [mantissa = "0", exponent = "0"] = String(q).split("e");
19500
+ const [whole = "0", fraction = ""] = mantissa.split(".");
19501
+ const digits = whole + fraction;
19502
+ const point = whole.length + Number(exponent) + 2;
19503
+ 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)}`;
19504
+ const [int = "0", frac = ""] = shifted.split(".");
19505
+ let start = 0;
19506
+ while (start < int.length - 1 && int[start] === "0")
19507
+ start++;
19508
+ let end = frac.length;
19509
+ while (end > 0 && frac[end - 1] === "0")
19510
+ end--;
19511
+ const trimmedInt = int.slice(start);
19512
+ const trimmedFrac = frac.slice(0, end);
19513
+ return `q${trimmedFrac ? `${trimmedInt}.${trimmedFrac}` : trimmedInt}`;
19514
+ }
19263
19515
  function estimateLtvDistribution(args) {
19264
19516
  const rng = mulberry32(args.seed);
19265
19517
  const grossSamples = [];
@@ -19281,7 +19533,7 @@ function estimateLtvDistribution(args) {
19281
19533
  const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
19282
19534
  const byQuantile = {};
19283
19535
  for (const q of args.quantiles) {
19284
- byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
19536
+ byQuantile[quantileKey(q)] = quantileFromSorted(sortedLtv, q);
19285
19537
  }
19286
19538
  return {
19287
19539
  mean: {
@@ -19316,8 +19568,7 @@ function assertQuantileValues(quantiles) {
19316
19568
  throw new RangeError("quantiles must be nonempty and in 0..1");
19317
19569
  }
19318
19570
  }
19319
- function assertConfig(config) {
19320
- const { retention, ...other } = config;
19571
+ function assertRetention(retention) {
19321
19572
  for (const day of ["d1", "d7", "d30", "d90"]) {
19322
19573
  if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
19323
19574
  throw new RangeError(`retention.${day} must be finite and in 0..1`);
@@ -19326,6 +19577,10 @@ function assertConfig(config) {
19326
19577
  if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
19327
19578
  throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
19328
19579
  }
19580
+ }
19581
+ function assertConfig(config) {
19582
+ const { retention, ...other } = config;
19583
+ assertRetention(retention);
19329
19584
  const { quantiles, ...uncertainty } = other.uncertainty;
19330
19585
  assertQuantileValues(quantiles);
19331
19586
  assertMonetization({ ...other, uncertainty });
@@ -19336,6 +19591,14 @@ function deriveMonetizationConfig2(scenario) {
19336
19591
  assertConfig(config);
19337
19592
  return config;
19338
19593
  }
19594
+ function progressionFactor2(startWorthLog10, endWorthLog10, span) {
19595
+ if (Number.isNaN(startWorthLog10) || Number.isNaN(endWorthLog10)) {
19596
+ throw new RangeError("worth logs must not be NaN");
19597
+ }
19598
+ if (!Number.isFinite(span) || span <= 0)
19599
+ throw new RangeError("span must be finite and > 0");
19600
+ return progressionFactor(startWorthLog10, endWorthLog10, span);
19601
+ }
19339
19602
  function assertPoint(args) {
19340
19603
  assertConfig(args.config);
19341
19604
  if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
@@ -19356,18 +19619,24 @@ function estimateLtvPerUser2(args) {
19356
19619
  }
19357
19620
  function estimateLtvDistribution2(args) {
19358
19621
  assertPoint(args);
19359
- if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
19622
+ const draws = args.draws ?? args.config.uncertainty.draws;
19623
+ const quantiles = args.quantiles ?? args.config.uncertainty.quantiles;
19624
+ const seed = args.seed ?? args.config.uncertainty.seed;
19625
+ if (!Number.isSafeInteger(draws) || draws < 1 || draws > 1e5 || draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
19360
19626
  throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
19361
19627
  }
19362
- if (!Number.isFinite(args.seed))
19363
- throw new RangeError("seed must be finite");
19364
- if (args.quantiles.length > 101)
19365
- throw new RangeError("quantiles exceed the 101 distinct percent keys");
19366
- assertQuantileValues(args.quantiles);
19367
- if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
19368
- throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
19628
+ if (seed === undefined)
19629
+ throw new RangeError("seed is required: pass seed or set config.uncertainty.seed");
19630
+ if (!Number.isInteger(seed) || seed < 0 || seed > 4294967295) {
19631
+ throw new RangeError("seed must be an integer in 0..4294967295");
19369
19632
  }
19370
- const result = estimateLtvDistribution(args);
19633
+ if (quantiles.length > 101)
19634
+ throw new RangeError("quantiles exceed 101 entries");
19635
+ assertQuantileValues(quantiles);
19636
+ if (new Set(quantiles.map(quantileKey)).size !== quantiles.length) {
19637
+ throw new RangeError("quantiles must be distinct values in 0..1");
19638
+ }
19639
+ const result = estimateLtvDistribution({ ...args, draws, quantiles, seed });
19371
19640
  finiteResult(result.mean);
19372
19641
  finiteResult(result.quantiles);
19373
19642
  return result;
@@ -19377,6 +19646,151 @@ var init_public = __esm(() => {
19377
19646
  init_uncertainty();
19378
19647
  });
19379
19648
 
19649
+ // ../core/dist/ltv/cohort.js
19650
+ function wilsonInterval(activeUsers, users) {
19651
+ const rate = activeUsers / users;
19652
+ const z2 = 1.959963984540054 ** 2;
19653
+ const denominator = 1 + z2 / users;
19654
+ const center = (rate + z2 / (2 * users)) / denominator;
19655
+ const radius = Math.sqrt(z2 * (rate * (1 - rate) / users + z2 / (4 * users * users))) / denominator;
19656
+ return { min: activeUsers === 0 ? 0 : Math.max(0, center - radius), max: activeUsers === users ? 1 : Math.min(1, center + radius) };
19657
+ }
19658
+ function observeCohort(rows, observedThroughDay) {
19659
+ if (!Number.isSafeInteger(observedThroughDay) || observedThroughDay < 0)
19660
+ throw new Error("observed-through-day must be a nonnegative safe integer");
19661
+ if (!Array.isArray(rows) || rows.length === 0)
19662
+ throw new Error("Observed cohort requires at least one install record");
19663
+ const users = new Map;
19664
+ for (const row of rows) {
19665
+ if (!row || typeof row.userId !== "string" || !row.userId.trim() || !Number.isSafeInteger(row.day) || row.day < 0 || row.day > observedThroughDay) {
19666
+ throw new Error("Observed cohort requires a user ID and integer days within observed-through-day");
19667
+ }
19668
+ if (![row.iapRevenue, row.adRevenue, row.acquisitionCost].every((v) => v === undefined || typeof v === "number" && Number.isFinite(v) && v >= 0)) {
19669
+ throw new Error("Observed cohort revenue and acquisition costs must be finite and nonnegative");
19670
+ }
19671
+ if (row.active !== undefined && typeof row.active !== "boolean")
19672
+ throw new Error("Observed cohort active must be boolean");
19673
+ const user = users.get(row.userId) ?? { days: new Set, active: new Set };
19674
+ if (user.days.has(row.day))
19675
+ throw new Error("Observed cohort requires one aggregated row per user and day");
19676
+ user.days.add(row.day);
19677
+ if (row.active ?? true)
19678
+ user.active.add(row.day);
19679
+ users.set(row.userId, user);
19680
+ }
19681
+ for (const user of users.values()) {
19682
+ if (!user.days.has(0))
19683
+ throw new Error("Observed cohort requires a day 0 install record for every user, including inactive users");
19684
+ }
19685
+ const n = users.size;
19686
+ const observation = {
19687
+ observedThroughDay,
19688
+ users: n,
19689
+ retention: RETENTION_DAYS.map((day) => {
19690
+ if (day > observedThroughDay)
19691
+ return { day, status: "not-observed", users: n, activeUsers: null, rate: null, interval95: null };
19692
+ const activeUsers = [...users.values()].filter((user) => user.active.has(day)).length;
19693
+ const rate = activeUsers / n;
19694
+ return { day, status: "observed", users: n, activeUsers, rate, interval95: wilsonInterval(activeUsers, n) };
19695
+ })
19696
+ };
19697
+ assertCohortObservation(observation);
19698
+ return observation;
19699
+ }
19700
+ function object2(value) {
19701
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19702
+ }
19703
+ function assertCohortObservation(observation) {
19704
+ 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) {
19705
+ throw new Error("Invalid cohort population or observation cutoff");
19706
+ }
19707
+ if (!Array.isArray(observation.retention) || observation.retention.length !== RETENTION_DAYS.length)
19708
+ throw new Error("Cohort retention must contain D1, D7, D30 and D90");
19709
+ for (const [index, value] of observation.retention.entries()) {
19710
+ const point = value;
19711
+ if (!object2(point) || typeof point.day !== "number" || point.day !== RETENTION_DAYS[index] || point.users !== observation.users)
19712
+ throw new Error("Cohort retention days and population must match the observation");
19713
+ const observed = point.day <= observation.observedThroughDay;
19714
+ if (point.status !== (observed ? "observed" : "not-observed"))
19715
+ throw new Error("Cohort retention status must match the observation cutoff");
19716
+ if (!observed) {
19717
+ if (point.activeUsers !== null || point.rate !== null || point.interval95 !== null)
19718
+ throw new Error("Future retention must remain unobserved");
19719
+ continue;
19720
+ }
19721
+ if (typeof point.activeUsers !== "number" || !Number.isSafeInteger(point.activeUsers) || point.activeUsers < 0 || point.activeUsers > observation.users || point.rate !== point.activeUsers / observation.users) {
19722
+ throw new Error("Retained users and retention rate must match the installed population");
19723
+ }
19724
+ const interval = point.interval95;
19725
+ 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) {
19726
+ throw new Error("Retention confidence interval must be finite and contain the observed rate");
19727
+ }
19728
+ const expected = wilsonInterval(point.activeUsers, observation.users);
19729
+ if (interval.min !== expected.min || interval.max !== expected.max) {
19730
+ throw new Error("Retention confidence interval must match the Wilson 95% bounds for the observed population");
19731
+ }
19732
+ }
19733
+ }
19734
+ function observedRetentionRates(observation) {
19735
+ assertCohortObservation(observation);
19736
+ const rates = {};
19737
+ for (const row of observation.retention) {
19738
+ if (row.rate !== null)
19739
+ rates[`d${row.day}`] = row.rate;
19740
+ }
19741
+ return rates;
19742
+ }
19743
+ function assertRetentionAssumptions(assumptions) {
19744
+ if (!object2(assumptions))
19745
+ throw new RangeError("Retention assumptions must be an object");
19746
+ for (const day of RETENTION_DAYS) {
19747
+ const rate = assumptions[`d${day}`];
19748
+ if (rate !== undefined && (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1)) {
19749
+ throw new RangeError(`Retention assumption d${day} must be finite and in 0..1`);
19750
+ }
19751
+ }
19752
+ }
19753
+ function planRetentionPatch(observation, assumptions = {}) {
19754
+ assertRetentionAssumptions(assumptions);
19755
+ const rates = observedRetentionRates(observation);
19756
+ if (Object.keys(rates).length === 0)
19757
+ return { status: "unavailable", retention: {}, warnings: ["No retention day has been fully observed."] };
19758
+ const merged = { ...assumptions, ...rates };
19759
+ const warnings = [];
19760
+ for (const day of RETENTION_DAYS) {
19761
+ if (merged[`d${day}`] === undefined)
19762
+ 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.`);
19763
+ }
19764
+ let previous;
19765
+ for (const day of RETENTION_DAYS) {
19766
+ const rate = merged[`d${day}`];
19767
+ if (rate === undefined)
19768
+ continue;
19769
+ if (previous && previous.rate < rate)
19770
+ 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.`);
19771
+ previous = { day, rate };
19772
+ }
19773
+ return warnings.length ? { status: "requires-review", retention: {}, warnings } : { status: "ready", retention: rates, warnings };
19774
+ }
19775
+ function compareRetention(observation, assumptions) {
19776
+ assertCohortObservation(observation);
19777
+ assertRetentionAssumptions(assumptions);
19778
+ return observation.retention.map((row) => {
19779
+ const assumption = assumptions[`d${row.day}`] ?? null;
19780
+ return {
19781
+ day: row.day,
19782
+ assumption,
19783
+ observed: row.rate,
19784
+ differencePercentagePoints: row.rate === null || assumption === null ? null : (row.rate - assumption) * 100,
19785
+ status: row.rate === null ? "not-observed" : assumption === null ? "unconfigured" : "compared"
19786
+ };
19787
+ });
19788
+ }
19789
+ var RETENTION_DAYS;
19790
+ var init_cohort = __esm(() => {
19791
+ RETENTION_DAYS = Object.freeze([1, 7, 30, 90]);
19792
+ });
19793
+
19380
19794
  // ../core/dist/index.js
19381
19795
  var init_dist2 = __esm(() => {
19382
19796
  init_types();
@@ -19443,6 +19857,7 @@ var init_dist2 = __esm(() => {
19443
19857
  init_flow();
19444
19858
  init_jevAdvisory();
19445
19859
  init_public();
19860
+ init_cohort();
19446
19861
  });
19447
19862
 
19448
19863
  // src/errors.ts
@@ -19586,13 +20001,78 @@ var init_errors3 = __esm(() => {
19586
20001
  };
19587
20002
  });
19588
20003
 
20004
+ // src/runtime/invocation.ts
20005
+ import { AsyncLocalStorage } from "async_hooks";
20006
+ import { resolve } from "path";
20007
+ function runWithInvocation(invocation, fn) {
20008
+ return storage.run({ ...invocation, reads: new Set }, fn);
20009
+ }
20010
+ function recordInputRead(path) {
20011
+ storage.getStore()?.reads.add(resolve(path));
20012
+ }
20013
+ function flagPaths(key, value) {
20014
+ if (typeof value !== "string" || !value.trim())
20015
+ return [];
20016
+ if (LIST_FLAGS.has(key))
20017
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
20018
+ return INPUT_FLAGS.has(key) ? [value] : [];
20019
+ }
20020
+ function recordNestedInputs(base, positional, flags) {
20021
+ for (const value of positional)
20022
+ if (value.length > 0)
20023
+ recordInputRead(resolve(base, value));
20024
+ for (const [key, value] of Object.entries(flags))
20025
+ for (const path of flagPaths(key, value))
20026
+ recordInputRead(resolve(base, path));
20027
+ }
20028
+ function invocationInputCandidates(options = {}) {
20029
+ const invocation = storage.getStore();
20030
+ if (!invocation)
20031
+ return [];
20032
+ const allow = new Set(options.allow ?? []);
20033
+ const allowed = new Set;
20034
+ const candidates = [];
20035
+ for (const [key, value] of Object.entries(invocation.flags)) {
20036
+ const paths = flagPaths(key, value);
20037
+ if (allow.has(key))
20038
+ for (const path of paths)
20039
+ allowed.add(resolve(path));
20040
+ else
20041
+ candidates.push(...paths);
20042
+ }
20043
+ for (const path of invocation.reads)
20044
+ if (!allowed.has(path))
20045
+ candidates.push(path);
20046
+ return candidates;
20047
+ }
20048
+ var INPUT_FLAGS, LIST_FLAGS, storage;
20049
+ var init_invocation = __esm(() => {
20050
+ INPUT_FLAGS = new Set([
20051
+ "scenario",
20052
+ "baseline",
20053
+ "current",
20054
+ "baseline-artifact",
20055
+ "tune",
20056
+ "checks",
20057
+ "rubric",
20058
+ "cache",
20059
+ "resume",
20060
+ "rc",
20061
+ "plugin-trust-file"
20062
+ ]);
20063
+ LIST_FLAGS = new Set(["plugin"]);
20064
+ storage = new AsyncLocalStorage;
20065
+ });
20066
+
19589
20067
  // src/runtime/bun.ts
19590
20068
  var {$ } = globalThis.Bun;
19591
20069
  import { dirname } from "path";
19592
20070
  async function readTextFile(path) {
20071
+ recordInputRead(path);
19593
20072
  return Bun.file(path).text();
19594
20073
  }
19595
20074
  async function readJsonFile(path) {
20075
+ recordInputRead(path);
19596
20076
  return Bun.file(path).json();
19597
20077
  }
19598
20078
  async function writeTextFile(path, body) {
@@ -19642,6 +20122,7 @@ function runText(args, opts) {
19642
20122
  var MAX_CAPTURE_BYTES;
19643
20123
  var init_bun = __esm(() => {
19644
20124
  init_errors3();
20125
+ init_invocation();
19645
20126
  MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
19646
20127
  });
19647
20128
 
@@ -19649,7 +20130,7 @@ var init_bun = __esm(() => {
19649
20130
  var require_package = __commonJS(function(exports, module) {
19650
20131
  module.exports = {
19651
20132
  name: "@idlekit/cli",
19652
- version: "0.5.1",
20133
+ version: "0.7.0",
19653
20134
  description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
19654
20135
  license: "MIT",
19655
20136
  type: "module",
@@ -19702,7 +20183,7 @@ var require_package = __commonJS(function(exports, module) {
19702
20183
  "@clack/prompts": "1.8.1",
19703
20184
  "@gunshi/plugin-completion": "0.37.3",
19704
20185
  "@gunshi/plugin-suggestion": "0.37.3",
19705
- "@idlekit/core": "0.5.0",
20186
+ "@idlekit/core": "0.7.0",
19706
20187
  ajv: "^8.20.0",
19707
20188
  gunshi: "0.37.3",
19708
20189
  "proper-lockfile": "4.1.2",
@@ -19717,7 +20198,7 @@ var require_package = __commonJS(function(exports, module) {
19717
20198
 
19718
20199
  // src/runtime/selfCli.ts
19719
20200
  import { existsSync } from "fs";
19720
- import { dirname as dirname3, resolve as resolve2 } from "path";
20201
+ import { dirname as dirname3, resolve as resolve3 } from "path";
19721
20202
  function currentEntry() {
19722
20203
  return Bun.main;
19723
20204
  }
@@ -19734,11 +20215,11 @@ function cliPackageRoot(entry = currentEntry()) {
19734
20215
  if (isNativeEntry(entry))
19735
20216
  return process.cwd();
19736
20217
  if (isBundledCliProcess(entry))
19737
- return resolve2(dirname3(entry), "..");
19738
- return resolve2(import.meta.dir, "../..");
20218
+ return resolve3(dirname3(entry), "..");
20219
+ return resolve3(import.meta.dir, "../..");
19739
20220
  }
19740
20221
  function bundledCliConfig(entry) {
19741
- const config = resolve2(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
20222
+ const config = resolve3(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
19742
20223
  if (!existsSync(config))
19743
20224
  return;
19744
20225
  return config;
@@ -19753,7 +20234,7 @@ function selfCliCommand(args, entry = currentEntry()) {
19753
20234
  return [bun, `--config=${config}`, entry, ...args];
19754
20235
  return [bun, entry, ...args];
19755
20236
  }
19756
- const bunfigApplies = resolve2(process.cwd()) === CLI_ROOT;
20237
+ const bunfigApplies = resolve3(process.cwd()) === CLI_ROOT;
19757
20238
  if (entry.endsWith(".ts") && !bunfigApplies) {
19758
20239
  return [bun, "--preload", "@ttsc/unplugin/bun-register", entry, ...args];
19759
20240
  }
@@ -19775,16 +20256,16 @@ function runSelfCli(args) {
19775
20256
  }
19776
20257
  var CLI_ROOT, MAX_CAPTURE_BYTES2;
19777
20258
  var init_selfCli = __esm(() => {
19778
- CLI_ROOT = resolve2(import.meta.dir, "../..");
20259
+ CLI_ROOT = resolve3(import.meta.dir, "../..");
19779
20260
  MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
19780
20261
  });
19781
20262
 
19782
20263
  // src/io/outputMeta.ts
19783
- import { dirname as dirname4, resolve as resolve3 } from "path";
20264
+ import { dirname as dirname4, resolve as resolve4 } from "path";
19784
20265
  function repoRoot() {
19785
20266
  if (isBundledCliProcess())
19786
- return resolve3(dirname4(Bun.main), "../..");
19787
- return resolve3(import.meta.dir, "../../..");
20267
+ return resolve4(dirname4(Bun.main), "../..");
20268
+ return resolve4(import.meta.dir, "../../..");
19788
20269
  }
19789
20270
  function stableStringify(value) {
19790
20271
  if (value === null || typeof value !== "object") {
@@ -19897,8 +20378,9 @@ var init_outputMeta = __esm(() => {
19897
20378
  // src/plugin/load.ts
19898
20379
  init_dist2();
19899
20380
  init_bun();
20381
+ init_invocation();
19900
20382
  import { realpathSync } from "fs";
19901
- import { dirname as dirname2, extname, isAbsolute, relative, resolve, sep } from "path";
20383
+ import { dirname as dirname2, extname, isAbsolute, relative, resolve as resolve2, sep } from "path";
19902
20384
  import { fileURLToPath } from "url";
19903
20385
 
19904
20386
  // src/lib/designObjectives.ts
@@ -19910,7 +20392,7 @@ init_dist2();
19910
20392
  function quantile(values, q) {
19911
20393
  return quantileInclusiveFloor(values, q) ?? undefined;
19912
20394
  }
19913
- function quantileKey(quantiles, q) {
20395
+ function quantileKey2(quantiles, q) {
19914
20396
  const percent = Math.round(q * 100);
19915
20397
  const legacy = `q${percent}`;
19916
20398
  const collided = quantiles.some((other) => other !== q && `q${Math.round(other * 100)}` === legacy);
@@ -19923,7 +20405,7 @@ function summarizeNumeric(values, quantiles) {
19923
20405
  return null;
19924
20406
  return {
19925
20407
  mean: values.reduce((sum, value) => sum + value, 0) / values.length,
19926
- quantiles: Object.fromEntries(quantiles.map((q) => [quantileKey(quantiles, q), quantileInclusiveFloor(values, q) ?? Number.NaN]))
20408
+ quantiles: Object.fromEntries(quantiles.map((q) => [quantileKey2(quantiles, q), quantileInclusiveFloor(values, q) ?? Number.NaN]))
19927
20409
  };
19928
20410
  }
19929
20411
  function resolveSessionPatternSpec(args) {
@@ -20564,10 +21046,11 @@ init_errors3();
20564
21046
  function ownedCount(state) {
20565
21047
  return Number(state?.vars?.owned ?? 0);
20566
21048
  }
21049
+ var NUMBER_ONLY_COMMANDS = ["report", "analyze", "eta", "growth", "prestige-cycle"];
20567
21050
  function finiteLinearAmount(E, value, label) {
20568
21051
  if (!E.isFinite(value)) {
20569
- 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.`, {
20570
- hint: "Other commands, including report/compare/tune, use the number engine; reduce their inputs."
21052
+ 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.`, {
21053
+ hint: `${NUMBER_ONLY_COMMANDS.join(", ")} run only the number engine; reduce their inputs.`
20571
21054
  });
20572
21055
  }
20573
21056
  return value;
@@ -20803,7 +21286,7 @@ function normalizeSha256(input) {
20803
21286
  return x;
20804
21287
  }
20805
21288
  function parsePluginRoots(input) {
20806
- const list = parseCommaSeparated(input).map((x) => resolve(x));
21289
+ const list = parseCommaSeparated(input).map((x) => resolve2(x));
20807
21290
  return [...new Set(list)];
20808
21291
  }
20809
21292
  function parsePluginSha256(input) {
@@ -20816,7 +21299,7 @@ function parsePluginSha256(input) {
20816
21299
  }
20817
21300
  const key = entry.slice(0, idx).trim();
20818
21301
  const value = entry.slice(idx + 1).trim();
20819
- const abs = resolve(key);
21302
+ const abs = resolve2(key);
20820
21303
  const digest = normalizeSha256(value);
20821
21304
  if (out[abs] && out[abs] !== digest) {
20822
21305
  throw new Error(`Conflicting sha256 values for plugin path: ${key}`);
@@ -20828,7 +21311,7 @@ function parsePluginSha256(input) {
20828
21311
  function parsePluginSecurityOptions(input) {
20829
21312
  const allowedRoots = parsePluginRoots(input.roots);
20830
21313
  const requiredSha256 = parsePluginSha256(input.sha256);
20831
- const trustFile = typeof input.trustFile === "string" && input.trustFile.trim().length > 0 ? resolve(input.trustFile) : undefined;
21314
+ const trustFile = typeof input.trustFile === "string" && input.trustFile.trim().length > 0 ? resolve2(input.trustFile) : undefined;
20832
21315
  return {
20833
21316
  allowedRoots,
20834
21317
  requiredSha256,
@@ -20850,7 +21333,7 @@ async function loadPluginTrustFile(pathAbs) {
20850
21333
  if (typeof v !== "string")
20851
21334
  continue;
20852
21335
  const digest = normalizeSha256(v);
20853
- const absPath = isAbsolute(k) ? resolve(k) : resolve(baseDir, k);
21336
+ const absPath = isAbsolute(k) ? resolve2(k) : resolve2(baseDir, k);
20854
21337
  out[absPath] = digest;
20855
21338
  }
20856
21339
  return out;
@@ -20876,7 +21359,7 @@ async function loadRegistries(pluginPaths = [], securityOptions = {}) {
20876
21359
  let requiredSha256;
20877
21360
  let requiredClosureSha256;
20878
21361
  try {
20879
- allowedRoots = (securityOptions.allowedRoots ?? []).map((x) => resolve(x));
21362
+ allowedRoots = (securityOptions.allowedRoots ?? []).map((x) => resolve2(x));
20880
21363
  const trustFileSha256 = securityOptions.trustFile !== undefined ? await loadPluginTrustFile(securityOptions.trustFile) : {};
20881
21364
  requiredSha256 = mergeShaPolicies(trustFileSha256, securityOptions.requiredSha256 ?? {});
20882
21365
  requiredClosureSha256 = mergeShaPolicies({}, securityOptions.requiredClosureSha256 ?? {});
@@ -20973,6 +21456,7 @@ function isPathInsideRoot(pathAbs, rootAbs) {
20973
21456
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
20974
21457
  }
20975
21458
  async function sha256File(pathAbs) {
21459
+ recordInputRead(pathAbs);
20976
21460
  const buffer = await Bun.file(pathAbs).bytes();
20977
21461
  return sha256Hex(buffer);
20978
21462
  }
@@ -21012,11 +21496,16 @@ async function pluginClosureDigest(entryAbs, entryDigest) {
21012
21496
  return sha256Hex(listing.join(`
21013
21497
  `));
21014
21498
  }
21499
+ async function recordPluginClosureReads(entryAbs) {
21500
+ try {
21501
+ await pluginClosureDigest(entryAbs, await sha256File(entryAbs));
21502
+ } catch {}
21503
+ }
21015
21504
  async function resolveAndValidatePluginPath(input, allowedRoots) {
21016
21505
  if (input.includes("://")) {
21017
21506
  throw new Error(`Plugin path must be a local file path: ${input}`);
21018
21507
  }
21019
- const abs = resolve(input);
21508
+ const abs = resolve2(input);
21020
21509
  const ext = extname(abs).toLowerCase();
21021
21510
  if (!ALLOWED_PLUGIN_EXTENSIONS.has(ext)) {
21022
21511
  throw new Error(`Unsupported plugin extension '${ext}' for ${input}`);
@@ -21521,4 +22010,4 @@ export {
21521
22010
  workerEntryMarker
21522
22011
  };
21523
22012
 
21524
- //# debugId=36317EC36EA8D6E664756E2164756E21
22013
+ //# debugId=FA1764B671C6333464756E2164756E21