@remnic/cli 9.6.23 → 9.6.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +269 -59
  2. package/package.json +28 -28
package/dist/index.js CHANGED
@@ -782,6 +782,14 @@ var BENCH_VALUE_FLAGS = Object.freeze([
782
782
  "--provider",
783
783
  "--base-url",
784
784
  "--request-timeout",
785
+ "--local-judge-request-timeout",
786
+ "--frontier-judge-request-timeout",
787
+ "--calibration-dir",
788
+ "--calibration-local-config-sha256",
789
+ "--calibration-frontier-config-sha256",
790
+ "--source-result-id",
791
+ "--expected-answer-set-sha256",
792
+ "--expected-question-id-list-sha256",
785
793
  "--drain-timeout",
786
794
  "--max-429-wait",
787
795
  "--ama-bench-judge-protocol",
@@ -862,6 +870,9 @@ var RUN_VALUE_FLAGS = Object.freeze([
862
870
  "--provider",
863
871
  "--base-url",
864
872
  "--request-timeout",
873
+ "--calibration-dir",
874
+ "--calibration-local-config-sha256",
875
+ "--calibration-frontier-config-sha256",
865
876
  "--drain-timeout",
866
877
  "--max-429-wait",
867
878
  "--ama-bench-judge-protocol",
@@ -937,9 +948,16 @@ var BENCH_ACTION_FLAGS = {
937
948
  "--judge-provider",
938
949
  "--judge-model",
939
950
  "--judge-base-url",
940
- "--judge-api-key"
951
+ "--judge-api-key",
952
+ "--local-judge-request-timeout",
953
+ "--frontier-judge-request-timeout",
954
+ "--max-429-wait",
955
+ "--calibration-dir",
956
+ "--source-result-id",
957
+ "--expected-answer-set-sha256",
958
+ "--expected-question-id-list-sha256"
941
959
  ],
942
- boolean: ["--json", "--help", "-h"]
960
+ boolean: ["--disable-thinking", "--json", "--help", "-h"]
943
961
  },
944
962
  published: {
945
963
  value: PUBLISHED_VALUE_FLAGS,
@@ -1121,6 +1139,14 @@ function parseBenchArgs(argv) {
1121
1139
  const output = readBenchOptionValue(args, "--output");
1122
1140
  const targetRaw = readBenchOptionValue(args, "--target");
1123
1141
  const requestTimeoutRaw = readBenchOptionValue(args, "--request-timeout");
1142
+ const localJudgeRequestTimeoutRaw = readBenchOptionValue(args, "--local-judge-request-timeout");
1143
+ const frontierJudgeRequestTimeoutRaw = readBenchOptionValue(args, "--frontier-judge-request-timeout");
1144
+ const calibrationDirRaw = readBenchOptionValue(args, "--calibration-dir");
1145
+ const calibrationLocalConfigSha256 = readBenchOptionValue(args, "--calibration-local-config-sha256");
1146
+ const calibrationFrontierConfigSha256 = readBenchOptionValue(args, "--calibration-frontier-config-sha256");
1147
+ const sourceResultId = readBenchOptionValue(args, "--source-result-id");
1148
+ const expectedAnswerSetSha256 = readBenchOptionValue(args, "--expected-answer-set-sha256");
1149
+ const expectedQuestionIdListSha256 = readBenchOptionValue(args, "--expected-question-id-list-sha256");
1124
1150
  const drainTimeoutRaw = readBenchOptionValue(args, "--drain-timeout");
1125
1151
  const judgeCacheDirRaw = readBenchOptionValue(args, "--judge-cache-dir");
1126
1152
  const localLabManifestRaw = readBenchOptionValue(args, "--local-lab-manifest");
@@ -1296,6 +1322,26 @@ function parseBenchArgs(argv) {
1296
1322
  );
1297
1323
  }
1298
1324
  }
1325
+ const parseJudgeTimeout = (raw, flag) => {
1326
+ if (raw === void 0) return void 0;
1327
+ const value = Number(raw);
1328
+ if (!Number.isInteger(value) || value <= 0 || value > 36e5) {
1329
+ throw new Error(`ERROR: ${flag} must be a positive integer no greater than 3,600,000 ms.`);
1330
+ }
1331
+ return value;
1332
+ };
1333
+ const localJudgeRequestTimeout = parseJudgeTimeout(localJudgeRequestTimeoutRaw, "--local-judge-request-timeout");
1334
+ const frontierJudgeRequestTimeout = parseJudgeTimeout(frontierJudgeRequestTimeoutRaw, "--frontier-judge-request-timeout");
1335
+ for (const [flag, digest] of [
1336
+ ["--expected-answer-set-sha256", expectedAnswerSetSha256],
1337
+ ["--expected-question-id-list-sha256", expectedQuestionIdListSha256],
1338
+ ["--calibration-local-config-sha256", calibrationLocalConfigSha256],
1339
+ ["--calibration-frontier-config-sha256", calibrationFrontierConfigSha256]
1340
+ ]) {
1341
+ if (digest !== void 0 && !/^[0-9a-f]{64}$/.test(digest)) {
1342
+ throw new Error(`ERROR: ${flag} must be a lowercase SHA-256 hex digest.`);
1343
+ }
1344
+ }
1299
1345
  let drainTimeout;
1300
1346
  if (drainTimeoutRaw !== void 0) {
1301
1347
  drainTimeout = Number(drainTimeoutRaw);
@@ -1604,6 +1650,14 @@ function parseBenchArgs(argv) {
1604
1650
  publishedOut: publishedOutRaw ? path4.resolve(expandTilde(publishedOutRaw)) : void 0,
1605
1651
  publishedDryRun: args.includes("--dry-run"),
1606
1652
  requestTimeout,
1653
+ localJudgeRequestTimeout,
1654
+ frontierJudgeRequestTimeout,
1655
+ calibrationDir: calibrationDirRaw ? path4.resolve(expandTilde(calibrationDirRaw)) : void 0,
1656
+ calibrationLocalConfigSha256,
1657
+ calibrationFrontierConfigSha256,
1658
+ sourceResultId,
1659
+ expectedAnswerSetSha256,
1660
+ expectedQuestionIdListSha256,
1607
1661
  drainTimeout,
1608
1662
  // Issue #1573 PR1: surface judge-cache flags into the runner options.
1609
1663
  noJudgeCache: args.includes("--no-judge-cache"),
@@ -3449,7 +3503,9 @@ Commands:
3449
3503
  Generate the Remnic.ai benchmark feed from stored runs
3450
3504
  ui Launch the local benchmark overview UI
3451
3505
  providers discover Auto-detect available local provider backends
3452
- judge-calibrate --benchmark <id> --local-lab-manifest <path> --judge-provider <p> --judge-model <m>
3506
+ judge-calibrate --benchmark <id> --source-result-id <id> --expected-answer-set-sha256 <sha256>
3507
+ --expected-question-id-list-sha256 <sha256>
3508
+ --local-lab-manifest <path> --judge-provider <p> --judge-model <m>
3453
3509
  Cross-tier judge calibration (issue #1573): runs the
3454
3510
  local + frontier judges over a benchmark's cached
3455
3511
  answers, reports Cohen's kappa, and persists it so
@@ -3510,6 +3566,7 @@ Options:
3510
3566
  Codex CLI reasoning effort for Remnic's internal LLM
3511
3567
  --ama-bench-judge-protocol <default|recommended>
3512
3568
  For ama-bench, use the recommended binary LLM-judge protocol
3569
+ (default-protocol calibration is not attached to recommended runs)
3513
3570
  --ama-bench-cross-judge-model <model>
3514
3571
  For ama-bench, add a second recommended-protocol judge for agreement checks
3515
3572
  --ama-bench-cross-judge-provider <provider>
@@ -3522,6 +3579,22 @@ Options:
3522
3579
  --results-dir <path> Override the stored benchmark results directory
3523
3580
  --baselines-dir <path> Override the named baseline directory
3524
3581
  --request-timeout <ms> Provider request timeout in milliseconds (codex-cli default: 180000)
3582
+ --local-judge-request-timeout <ms>
3583
+ Calibration-only timeout for each local judge call
3584
+ --frontier-judge-request-timeout <ms>
3585
+ Calibration-only timeout for each frontier judge call
3586
+ --max-429-wait <ms> Maximum cumulative 429 retry wait for provider calls
3587
+ --disable-thinking Disable thinking for supported provider-backed models
3588
+ --source-result-id <id> Exact full stored result used by judge-calibrate
3589
+ --expected-answer-set-sha256 <sha256>
3590
+ Expected deterministic calibration-slice payload hash
3591
+ --expected-question-id-list-sha256 <sha256>
3592
+ Expected ordered source task-ID list hash
3593
+ --calibration-dir <path> Private final-state and resumable-checkpoint directory
3594
+ --calibration-local-config-sha256 <sha256>
3595
+ Required on later runs that attach calibration state
3596
+ --calibration-frontier-config-sha256 <sha256>
3597
+ Required on later runs that attach calibration state
3525
3598
  --drain-timeout <ms> Memory drain timeout in milliseconds
3526
3599
  (defaults to --request-timeout; implicit codex-cli default: 600000)
3527
3600
  --local-lab-manifest <path>
@@ -3596,7 +3669,7 @@ function buildBenchRuntimeProfileRequest(parsed, runtimeProfile) {
3596
3669
  max429WaitMs: parsed.max429WaitMs,
3597
3670
  disableThinking: parsed.disableThinking,
3598
3671
  lcmObserveConcurrency: parsed.publishedIngestConcurrency,
3599
- localLabManifestPath: runtimeProfile === "local-lab" ? parsed.localLabManifestPath : void 0
3672
+ localLabManifestPath: parsed.localLabManifestPath
3600
3673
  };
3601
3674
  }
3602
3675
  var BENCH_STDOUT_REDACTED_SECRET = "[REDACTED]";
@@ -4673,12 +4746,19 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4673
4746
  );
4674
4747
  process.exit(1);
4675
4748
  }
4749
+ if (!parsed.sourceResultId || !parsed.expectedAnswerSetSha256 || !parsed.expectedQuestionIdListSha256) {
4750
+ console.error(
4751
+ "ERROR: judge-calibrate requires --source-result-id, --expected-answer-set-sha256, and --expected-question-id-list-sha256 so all source drift is rejected before judge calls."
4752
+ );
4753
+ process.exit(1);
4754
+ }
4676
4755
  const bench = await loadBenchModule();
4677
4756
  const resultsDir = expandTilde(
4678
4757
  parsed.resultsDir ?? path11.join(resolveHomeDir(), ".remnic", "bench", "results")
4679
4758
  );
4680
- const calibrationDir = path11.join(resolveHomeDir(), ".remnic", "bench", "calibration");
4681
- const previousCalibration = await bench.loadJudgeCalibrationState?.(benchmarkId, calibrationDir);
4759
+ const calibrationDir = expandTilde(
4760
+ parsed.calibrationDir ?? path11.join(resolveHomeDir(), ".remnic", "bench", "calibration")
4761
+ );
4682
4762
  const stored = await bench.listBenchmarkResults(resultsDir);
4683
4763
  const allForBenchmark = stored.filter((entry) => entry.benchmark === benchmarkId);
4684
4764
  const candidates = allForBenchmark.filter((entry) => entry.mode === "full").sort((a, b) => {
@@ -4687,32 +4767,16 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4687
4767
  }
4688
4768
  return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
4689
4769
  });
4690
- const pinnedSourceId = previousCalibration?.sourceResultId;
4691
- const latest = pinnedSourceId ? candidates.find((entry) => entry.id === pinnedSourceId) : candidates[0];
4770
+ const pinnedSourceId = parsed.sourceResultId;
4771
+ const latest = candidates.find((entry) => entry.id === pinnedSourceId);
4692
4772
  if (!latest) {
4693
- if (pinnedSourceId) {
4694
- console.error(
4695
- `ERROR: pinned calibration source ${pinnedSourceId} for "${benchmarkId}" is no longer present in ${resultsDir}. Restore that stored result or remove the calibration state intentionally before selecting a new answer set.`
4696
- );
4697
- process.exit(1);
4698
- }
4699
- const quickCount = allForBenchmark.filter((entry) => entry.mode === "quick").length;
4700
4773
  console.error(
4701
- quickCount > 0 ? `ERROR: no full stored results for "${benchmarkId}" in ${resultsDir} (found ${quickCount} quick run(s); a 1-task quick sample cannot calibrate the judge). Run a full benchmark first (remnic bench run ${benchmarkId}).` : `ERROR: no stored benchmark results for "${benchmarkId}" in ${resultsDir}. Run the benchmark first (remnic bench run ${benchmarkId}) so cached answers exist to calibrate against.`
4774
+ `ERROR: explicitly pinned full calibration source ${pinnedSourceId} for "${benchmarkId}" is not present in ${resultsDir}. Restore that exact stored result.`
4702
4775
  );
4703
4776
  process.exit(1);
4704
4777
  }
4705
4778
  let loaded = await bench.loadBenchmarkResult(latest.path);
4706
- if (!pinnedSourceId && loaded.meta.status === "partial") {
4707
- for (const candidate of candidates.slice(1)) {
4708
- const candidateResult = await bench.loadBenchmarkResult(candidate.path);
4709
- if (candidateResult.meta.status !== "partial") {
4710
- loaded = candidateResult;
4711
- break;
4712
- }
4713
- }
4714
- }
4715
- if (pinnedSourceId && loaded.meta.status === "partial") {
4779
+ if (loaded.meta.status === "partial") {
4716
4780
  console.error(
4717
4781
  `ERROR: pinned calibration source ${pinnedSourceId} is partial. Restore a complete copy or remove the calibration state intentionally before selecting a new answer set.`
4718
4782
  );
@@ -4732,23 +4796,53 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4732
4796
  predicted: task.actual,
4733
4797
  expected: task.expected
4734
4798
  }));
4735
- const manifest = await bench.loadLocalLabManifest(expandTilde(manifestPath));
4736
- const resolvedProfile = bench.resolveLocalLabProfile(manifest);
4737
- const localJudgeConfig = resolvedProfile.judge.providerConfig;
4738
- const localJudge = bench.createProviderBackedJudge(localJudgeConfig);
4739
- const frontierJudge = bench.createProviderBackedJudge({
4740
- provider: parsed.judgeProvider,
4741
- model: parsed.judgeModel,
4742
- ...parsed.judgeBaseUrl ? { baseUrl: parsed.judgeBaseUrl } : {},
4743
- ...parsed.judgeApiKey ? { apiKey: parsed.judgeApiKey } : {}
4799
+ const orderedQuestionIdsHash = bench.hashOrderedQuestionIds(
4800
+ loaded.results.tasks.map((task) => task.taskId)
4801
+ );
4802
+ if (orderedQuestionIdsHash !== parsed.expectedQuestionIdListSha256) {
4803
+ console.error(
4804
+ `ERROR: ordered question-id list changed for ${loaded.meta.id} (expected sha256:${parsed.expectedQuestionIdListSha256}, got sha256:${orderedQuestionIdsHash}); refusing to call judges.`
4805
+ );
4806
+ process.exit(1);
4807
+ }
4808
+ const sourceResultSha256 = createHash("sha256").update(fs7.readFileSync(latest.path)).digest("hex");
4809
+ const expandedManifestPath = expandTilde(manifestPath);
4810
+ if (!bench.resolveLocalLabJudgeProviderConfig) {
4811
+ console.error(
4812
+ "ERROR: installed @remnic/bench version does not export resolveLocalLabJudgeProviderConfig; rebuild or upgrade @remnic/bench before calibrating."
4813
+ );
4814
+ process.exit(1);
4815
+ }
4816
+ const localJudgeConfig = await bench.resolveLocalLabJudgeProviderConfig({
4817
+ localLabManifestPath: expandedManifestPath,
4818
+ ...parsed.localJudgeRequestTimeout ? { requestTimeout: parsed.localJudgeRequestTimeout } : {},
4819
+ max429WaitMs: parsed.max429WaitMs,
4820
+ disableThinking: parsed.disableThinking
4744
4821
  });
4822
+ const localJudge = bench.createProviderBackedJudge(localJudgeConfig);
4823
+ const frontierJudgeConfig = buildCalibrationFrontierJudgeConfig(
4824
+ parsed
4825
+ );
4826
+ const frontierJudge = bench.createProviderBackedJudge(frontierJudgeConfig);
4827
+ const localJudgeConfigHash = hashCalibrationProviderConfig(localJudgeConfig);
4828
+ const frontierJudgeConfigHash = hashCalibrationProviderConfig(frontierJudgeConfig);
4745
4829
  const result = await bench.runJudgeCalibration({
4746
4830
  benchmarkId,
4747
4831
  localJudge,
4748
4832
  frontierJudge,
4749
4833
  answers,
4750
- ...previousCalibration?.sliceQuestionIds ? { pinnedQuestionIds: previousCalibration.sliceQuestionIds } : {},
4751
- ...previousCalibration?.answerSetHash ? { expectedAnswerSetHash: previousCalibration.answerSetHash } : {}
4834
+ expectedAnswerSetHash: parsed.expectedAnswerSetSha256,
4835
+ expectedOrderedQuestionIdsHash: parsed.expectedQuestionIdListSha256,
4836
+ checkpoint: {
4837
+ dir: calibrationDir,
4838
+ sourceResultId: loaded.meta.id,
4839
+ sourceResultSha256,
4840
+ orderedQuestionIdsHash,
4841
+ localJudgePromptIdentity: bench.getProviderBackedJudgePromptIdentity(localJudgeConfig),
4842
+ frontierJudgePromptIdentity: bench.getProviderBackedJudgePromptIdentity(frontierJudgeConfig),
4843
+ localJudgeConfigHash,
4844
+ frontierJudgeConfigHash
4845
+ }
4752
4846
  });
4753
4847
  const calibrationIdentities = {
4754
4848
  localJudgeProvider: String(localJudgeConfig.provider),
@@ -4760,10 +4854,10 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4760
4854
  result,
4761
4855
  calibrationDir,
4762
4856
  calibrationIdentities,
4763
- { sourceResultId: loaded.meta.id }
4857
+ { sourceResultId: loaded.meta.id, localJudgeConfigHash, frontierJudgeConfigHash }
4764
4858
  );
4765
4859
  const persisted = await bench.loadJudgeCalibrationState(benchmarkId, calibrationDir);
4766
- if (!persisted || persisted.kappa !== result.kappa || persisted.warning !== result.warning) {
4860
+ if (!persisted || persisted.kappa !== result.kappa || persisted.warning !== result.warning || persisted.localJudgeConfigHash !== localJudgeConfigHash || persisted.frontierJudgeConfigHash !== frontierJudgeConfigHash) {
4767
4861
  console.error(
4768
4862
  `ERROR: calibration state round-trip failed for ${benchmarkId} (wrote kappa ${result.kappa}, read back ${persisted ? persisted.kappa : "nothing"}). Re-run judge-calibrate.`
4769
4863
  );
@@ -4785,6 +4879,11 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4785
4879
  answerSetHash: result.answerSetHash,
4786
4880
  sourceResultId: loaded.meta.id,
4787
4881
  categories: result.categories,
4882
+ orderedQuestionIdsHash,
4883
+ sourceResultSha256,
4884
+ localJudgeConfigHash,
4885
+ frontierJudgeConfigHash,
4886
+ execution: result.execution,
4788
4887
  statePath
4789
4888
  },
4790
4889
  null,
@@ -4800,6 +4899,8 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4800
4899
  ` ${(result.confidenceInterval.level * 100).toFixed(0)}% bootstrap CI: [${result.confidenceInterval.lower.toFixed(4)}, ${result.confidenceInterval.upper.toFixed(4)}] (${result.bootstrapSamples} paired resamples)`
4801
4900
  );
4802
4901
  console.log(` Pinned source: ${loaded.meta.id} (answer set sha256:${result.answerSetHash})`);
4902
+ console.log(` Ordered question ids: sha256:${orderedQuestionIdsHash}`);
4903
+ console.log(` Judge calls: local ${result.execution.localJudgeCalls}, frontier ${result.execution.frontierJudgeCalls}; resumed outputs ${result.execution.resumedJudgeOutputs}`);
4803
4904
  console.log(` Observed agreement: ${result.observedAgreement.toFixed(4)}`);
4804
4905
  console.log(` Expected agreement: ${result.expectedAgreement.toFixed(4)}`);
4805
4906
  if (result.warning) {
@@ -4812,6 +4913,26 @@ async function calibrateBenchJudges(parsed, rawArgs) {
4812
4913
  console.log(` Calibration state written + verified (round-trip ok): ${statePath}`);
4813
4914
  console.log(` Subsequent local artifacts for ${benchmarkId} will carry kappa ${persisted.kappa.toFixed(4)}.`);
4814
4915
  }
4916
+ function buildCalibrationFrontierJudgeConfig(parsed) {
4917
+ if (!parsed.judgeProvider || !parsed.judgeModel) {
4918
+ throw new Error(
4919
+ "Calibration frontier judge requires both --judge-provider and --judge-model."
4920
+ );
4921
+ }
4922
+ return {
4923
+ provider: parsed.judgeProvider,
4924
+ model: parsed.judgeModel,
4925
+ ...parsed.judgeBaseUrl ? { baseUrl: parsed.judgeBaseUrl } : {},
4926
+ ...parsed.judgeApiKey ? { apiKey: parsed.judgeApiKey } : {},
4927
+ ...parsed.frontierJudgeRequestTimeout !== void 0 || parsed.max429WaitMs !== void 0 ? {
4928
+ retryOptions: {
4929
+ ...parsed.frontierJudgeRequestTimeout !== void 0 ? { timeoutMs: parsed.frontierJudgeRequestTimeout } : {},
4930
+ ...parsed.max429WaitMs !== void 0 ? { max429WaitMs: parsed.max429WaitMs } : {}
4931
+ }
4932
+ } : {},
4933
+ ...parsed.disableThinking ? { disableThinking: true } : {}
4934
+ };
4935
+ }
4815
4936
  async function publishBenchPackageResults(parsed) {
4816
4937
  if (parsed.benchmarks.length > 0) {
4817
4938
  console.error(
@@ -5200,6 +5321,12 @@ async function runBenchViaPackage(parsed, benchmarkId, runtimeProfile, benchStat
5200
5321
  if (!plan) {
5201
5322
  return { ok: false };
5202
5323
  }
5324
+ const judgeCalibration = await preparePersistedJudgeCalibrationAttachment(
5325
+ benchModule,
5326
+ benchmarkId,
5327
+ plan.runtime.judgeProvider,
5328
+ parsed
5329
+ );
5203
5330
  await preflightLocalLabEndpointsIfNeeded(benchModule, plan);
5204
5331
  const outputDir = parsed.resultsDir ?? resolveBenchOutputDir();
5205
5332
  const datasetDir = resolveBenchDatasetDir(
@@ -5283,7 +5410,7 @@ async function runBenchViaPackage(parsed, benchmarkId, runtimeProfile, benchStat
5283
5410
  });
5284
5411
  result.config.remnicConfig = plan.runtime.remnicConfig;
5285
5412
  result.config.internalProvider = plan.runtime.internalProvider;
5286
- await attachPersistedJudgeCalibration(benchModule, benchmarkId, result);
5413
+ attachPreparedJudgeCalibration(result, judgeCalibration);
5287
5414
  const writtenPath = await benchModule.writeBenchmarkResult(result, outputDir);
5288
5415
  if (parsed.json) {
5289
5416
  console.log(JSON.stringify(redactBenchResultForStdout(benchModule, result), null, 2));
@@ -5304,7 +5431,7 @@ async function runBenchViaPackage(parsed, benchmarkId, runtimeProfile, benchStat
5304
5431
  err instanceof Error ? err.message : String(err),
5305
5432
  parsed.quick ? "quick" : "full"
5306
5433
  );
5307
- await attachPersistedJudgeCalibration(benchModule, benchmarkId, partialResult);
5434
+ attachPreparedJudgeCalibration(partialResult, judgeCalibration);
5308
5435
  try {
5309
5436
  const partialPath = await benchModule.writeBenchmarkResult(partialResult, outputDir);
5310
5437
  console.error(` Partial results (${partialTasks.length} tasks) written to ${partialPath}`);
@@ -5328,32 +5455,111 @@ async function runBenchViaPackage(parsed, benchmarkId, runtimeProfile, benchStat
5328
5455
  }
5329
5456
  }
5330
5457
  }
5331
- async function attachPersistedJudgeCalibration(benchModule, benchmarkId, result) {
5332
- const calibrationDir = path11.join(resolveHomeDir(), ".remnic", "bench", "calibration");
5458
+ async function preparePersistedJudgeCalibrationAttachment(benchModule, benchmarkId, runJudgeProvider, calibrationBinding) {
5459
+ const hasLocalPin = Boolean(calibrationBinding.calibrationLocalConfigSha256);
5460
+ const hasFrontierPin = Boolean(calibrationBinding.calibrationFrontierConfigSha256);
5461
+ const hasAnyPin = hasLocalPin || hasFrontierPin;
5462
+ const hasBothPins = hasLocalPin && hasFrontierPin;
5463
+ if (benchmarkId === "ama-bench" && calibrationBinding.amaBenchJudgeProtocol === "recommended") {
5464
+ if (hasAnyPin) {
5465
+ throw new Error(
5466
+ "AMA-Bench recommended-protocol runs cannot attach default-protocol judge calibration; remove both calibration pins or calibrate the recommended prompt contract separately."
5467
+ );
5468
+ }
5469
+ return void 0;
5470
+ }
5471
+ if (hasAnyPin && !hasBothPins) {
5472
+ throw new Error(
5473
+ "--calibration-local-config-sha256 and --calibration-frontier-config-sha256 must be supplied together."
5474
+ );
5475
+ }
5476
+ const calibrationDir = expandTilde(
5477
+ calibrationBinding.calibrationDir ?? path11.join(resolveHomeDir(), ".remnic", "bench", "calibration")
5478
+ );
5333
5479
  const state = await benchModule.loadJudgeCalibrationState?.(benchmarkId, calibrationDir);
5334
- if (!state) return;
5335
- if (state.localJudgeModel !== void 0 && state.frontierJudgeModel !== void 0) {
5336
- const runJudgeProvider = result.config.judgeProvider?.provider;
5337
- const runJudgeModel = result.config.judgeProvider?.model;
5338
- const matchesLocal = runJudgeProvider === state.localJudgeProvider && runJudgeModel === state.localJudgeModel;
5480
+ if (!state) {
5481
+ if (hasAnyPin) {
5482
+ throw new Error(
5483
+ `Calibration binding pins were supplied for ${benchmarkId}, but no valid calibration state could be loaded from ${calibrationDir}; rerun judge-calibrate or remove both pins.`
5484
+ );
5485
+ }
5486
+ return void 0;
5487
+ }
5488
+ if (state.localJudgeProvider !== void 0 && state.localJudgeModel !== void 0) {
5489
+ const matchesLocal = runJudgeProvider?.provider === state.localJudgeProvider && runJudgeProvider.model === state.localJudgeModel;
5339
5490
  if (!matchesLocal) {
5340
- return;
5491
+ if (hasBothPins) {
5492
+ throw new Error(
5493
+ `Calibration binding pins for ${benchmarkId} target the calibrated local judge ${state.localJudgeProvider}/${state.localJudgeModel}, but the run judge is ${runJudgeProvider?.provider ?? "unset"}/${runJudgeProvider?.model ?? "unset"}; refusing to ignore explicit pins.`
5494
+ );
5495
+ }
5496
+ return void 0;
5341
5497
  }
5342
5498
  }
5499
+ if (!state.localJudgeConfigHash || !state.frontierJudgeConfigHash) {
5500
+ throw new Error(
5501
+ `Calibration state for ${benchmarkId} is missing bound judge configuration hashes; recalibrate before attaching it.`
5502
+ );
5503
+ }
5504
+ const resolvedRunJudgeConfigHash = hashCalibrationProviderConfig(runJudgeProvider);
5505
+ const hasCompleteLocalIdentity = state.localJudgeProvider !== void 0 && state.localJudgeModel !== void 0;
5506
+ if (!hasCompleteLocalIdentity && resolvedRunJudgeConfigHash !== state.localJudgeConfigHash) {
5507
+ if (hasBothPins) {
5508
+ throw new Error(
5509
+ `Calibration binding pins for ${benchmarkId} do not match the resolved run judge configuration; refusing to ignore explicit pins.`
5510
+ );
5511
+ }
5512
+ return void 0;
5513
+ }
5514
+ if (!calibrationBinding.calibrationLocalConfigSha256 || !calibrationBinding.calibrationFrontierConfigSha256) {
5515
+ throw new Error(
5516
+ `Calibration state exists for ${benchmarkId}; --calibration-local-config-sha256 and --calibration-frontier-config-sha256 are required to attach it.`
5517
+ );
5518
+ }
5519
+ if (calibrationBinding.calibrationLocalConfigSha256 !== state.localJudgeConfigHash || calibrationBinding.calibrationFrontierConfigSha256 !== state.frontierJudgeConfigHash) {
5520
+ throw new Error(`Calibration configuration hash mismatch for ${benchmarkId}; refusing to attach stale kappa.`);
5521
+ }
5522
+ if (resolvedRunJudgeConfigHash !== state.localJudgeConfigHash) {
5523
+ throw new Error(
5524
+ `Resolved run judge configuration hash mismatch for ${benchmarkId}; expected sha256:${state.localJudgeConfigHash}, got sha256:${resolvedRunJudgeConfigHash}. Refusing to attach stale kappa before benchmark dispatch.`
5525
+ );
5526
+ }
5527
+ return {
5528
+ kappa: state.kappa,
5529
+ sampleSize: state.sampleSize,
5530
+ threshold: state.threshold,
5531
+ warning: state.warning,
5532
+ ...state.confidenceInterval ? { confidenceInterval: state.confidenceInterval } : {},
5533
+ ...state.bootstrapSamples ? { bootstrapSamples: state.bootstrapSamples } : {},
5534
+ ...state.answerSetHash ? { answerSetHash: state.answerSetHash } : {},
5535
+ ...state.sourceResultId ? { sourceResultId: state.sourceResultId } : {},
5536
+ ...state.sliceQuestionIds ? { sliceQuestionIds: state.sliceQuestionIds } : {},
5537
+ localJudgeConfigHash: state.localJudgeConfigHash,
5538
+ frontierJudgeConfigHash: state.frontierJudgeConfigHash
5539
+ };
5540
+ }
5541
+ function attachPreparedJudgeCalibration(result, judgeCalibration) {
5542
+ if (!judgeCalibration) return;
5343
5543
  result.config.benchmarkOptions = {
5344
5544
  ...result.config.benchmarkOptions ?? {},
5345
- judgeCalibration: {
5346
- kappa: state.kappa,
5347
- sampleSize: state.sampleSize,
5348
- threshold: state.threshold,
5349
- warning: state.warning,
5350
- ...state.confidenceInterval ? { confidenceInterval: state.confidenceInterval } : {},
5351
- ...state.bootstrapSamples ? { bootstrapSamples: state.bootstrapSamples } : {},
5352
- ...state.answerSetHash ? { answerSetHash: state.answerSetHash } : {},
5353
- ...state.sourceResultId ? { sourceResultId: state.sourceResultId } : {},
5354
- ...state.sliceQuestionIds ? { sliceQuestionIds: state.sliceQuestionIds } : {}
5545
+ judgeCalibration
5546
+ };
5547
+ }
5548
+ function hashCalibrationProviderConfig(config) {
5549
+ const canonicalize = (value, key = "") => {
5550
+ if (typeof value === "string" && /(?:api.?key|authorization|token|secret)/i.test(key)) {
5551
+ return { secretSha256: createHash("sha256").update(value).digest("hex") };
5355
5552
  }
5553
+ if (Array.isArray(value)) return value.map((item) => canonicalize(item));
5554
+ if (value && typeof value === "object") {
5555
+ return Object.fromEntries(Object.keys(value).sort().map((childKey) => [
5556
+ childKey,
5557
+ canonicalize(value[childKey], childKey)
5558
+ ]));
5559
+ }
5560
+ return value;
5356
5561
  };
5562
+ return createHash("sha256").update(JSON.stringify(canonicalize(config))).digest("hex");
5357
5563
  }
5358
5564
  function restoreOptionalEnv(key, previousValue) {
5359
5565
  if (previousValue === void 0) {
@@ -12920,7 +13126,9 @@ export {
12920
13126
  __benchDatasetTestHooks,
12921
13127
  advanceOfflineBaseFilesForSuccessfulPush,
12922
13128
  advanceOfflineLargeFileFailureCounts,
13129
+ attachPreparedJudgeCalibration,
12923
13130
  buildBenchRuntimeProfileRequest,
13131
+ buildCalibrationFrontierJudgeConfig,
12924
13132
  buildPackageBenchExecutionPlans,
12925
13133
  buildQueryRecallRequest,
12926
13134
  chunkOfflineChangesetApplyBatches,
@@ -12932,6 +13140,7 @@ export {
12932
13140
  formatOfflineRequestForError,
12933
13141
  getBenchUsageText,
12934
13142
  hasFlag,
13143
+ hashCalibrationProviderConfig,
12935
13144
  hydrateOfflineSnapshotContent,
12936
13145
  isOfflineMissingContentDeferrablePath,
12937
13146
  isOfflineSnapshotPostFallbackError,
@@ -12947,6 +13156,7 @@ export {
12947
13156
  parseOfflineSyncRequestTimeoutMs,
12948
13157
  parseTaxonomyResolveArgs,
12949
13158
  parseTrainingExportArgs,
13159
+ preparePersistedJudgeCalibrationAttachment,
12950
13160
  pushOfflineFileContent,
12951
13161
  pushOfflineFileContentFromChunkReader,
12952
13162
  renderQueryTextLines,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/cli",
3
- "version": "9.6.23",
3
+ "version": "9.6.25",
4
4
  "description": "CLI for Remnic memory — init, query, doctor, daemon management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,23 +26,23 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "yaml": "^2.4.2",
29
- "@remnic/plugin-pi": "^9.6.23",
30
- "@remnic/core": "^9.6.23",
31
- "@remnic/server": "^9.6.23"
29
+ "@remnic/plugin-pi": "^9.6.25",
30
+ "@remnic/core": "^9.6.25",
31
+ "@remnic/server": "^9.6.25"
32
32
  },
33
33
  "peerDependencies": {
34
- "@remnic/bench": "^9.6.23",
35
- "@remnic/export-weclone": "^9.6.23",
36
- "@remnic/import-weclone": "^9.6.23",
37
- "@remnic/import-chatgpt": "^9.6.23",
38
- "@remnic/import-claude": "^9.6.23",
39
- "@remnic/import-gemini": "^9.6.23",
40
- "@remnic/import-lossless-claw": "^9.6.23",
41
- "@remnic/import-mem0": "^9.6.23",
42
- "@remnic/import-supermemory": "^9.6.23",
43
- "@remnic/connector-limitless": "^9.6.23",
44
- "@remnic/connector-bee": "^9.6.23",
45
- "@remnic/connector-omi": "^9.6.23"
34
+ "@remnic/bench": "^9.6.25",
35
+ "@remnic/export-weclone": "^9.6.25",
36
+ "@remnic/import-weclone": "^9.6.25",
37
+ "@remnic/import-chatgpt": "^9.6.25",
38
+ "@remnic/import-claude": "^9.6.25",
39
+ "@remnic/import-gemini": "^9.6.25",
40
+ "@remnic/import-lossless-claw": "^9.6.25",
41
+ "@remnic/import-mem0": "^9.6.25",
42
+ "@remnic/import-supermemory": "^9.6.25",
43
+ "@remnic/connector-limitless": "^9.6.25",
44
+ "@remnic/connector-bee": "^9.6.25",
45
+ "@remnic/connector-omi": "^9.6.25"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@remnic/bench": {
@@ -85,18 +85,18 @@
85
85
  "devDependencies": {
86
86
  "tsup": "^8.5.1",
87
87
  "typescript": "^5.9.3",
88
- "@remnic/bench": "9.6.23",
89
- "@remnic/export-weclone": "9.6.23",
90
- "@remnic/import-weclone": "9.6.23",
91
- "@remnic/import-chatgpt": "9.6.23",
92
- "@remnic/import-claude": "9.6.23",
93
- "@remnic/import-gemini": "9.6.23",
94
- "@remnic/import-lossless-claw": "9.6.23",
95
- "@remnic/import-mem0": "9.6.23",
96
- "@remnic/connector-limitless": "9.6.23",
97
- "@remnic/connector-bee": "9.6.23",
98
- "@remnic/import-supermemory": "9.6.23",
99
- "@remnic/connector-omi": "9.6.23"
88
+ "@remnic/bench": "9.6.25",
89
+ "@remnic/export-weclone": "9.6.25",
90
+ "@remnic/import-weclone": "9.6.25",
91
+ "@remnic/import-chatgpt": "9.6.25",
92
+ "@remnic/import-claude": "9.6.25",
93
+ "@remnic/import-mem0": "9.6.25",
94
+ "@remnic/import-supermemory": "9.6.25",
95
+ "@remnic/import-gemini": "9.6.25",
96
+ "@remnic/import-lossless-claw": "9.6.25",
97
+ "@remnic/connector-limitless": "9.6.25",
98
+ "@remnic/connector-bee": "9.6.25",
99
+ "@remnic/connector-omi": "9.6.25"
100
100
  },
101
101
  "license": "MIT",
102
102
  "repository": {