@flop-labs/cli 0.4.4 → 0.5.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.
Files changed (4) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +22 -1
  3. package/dist/cli.js +1308 -419
  4. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -402,12 +402,12 @@ var init_state = __esm({
402
402
  return computeChecksum(snapshotData);
403
403
  }
404
404
  /** Transition to a new lifecycle state. */
405
- transitionTo(state, reason2) {
405
+ transitionTo(state, reason3) {
406
406
  this.data.lifecycle.state = state;
407
407
  this.data.lifecycle.stateChangedAt = Date.now();
408
408
  this.data.lifecycle.lastActiveAt = Date.now();
409
- if (reason2) {
410
- this.data.lifecycle.terminationReason = reason2;
409
+ if (reason3) {
410
+ this.data.lifecycle.terminationReason = reason3;
411
411
  }
412
412
  this.data.checksum = this.computeChecksumInternal();
413
413
  }
@@ -724,14 +724,14 @@ var init_task_runner = __esm({
724
724
  }
725
725
  }
726
726
  withTimeout(promise, timeoutMs) {
727
- return new Promise((resolve10, reject) => {
727
+ return new Promise((resolve11, reject) => {
728
728
  const timer = setTimeout(
729
729
  () => reject(new AgentError("T002" /* TASK_TIMEOUT */, `Task timed out after ${timeoutMs}ms`, true)),
730
730
  timeoutMs
731
731
  );
732
732
  promise.then((result) => {
733
733
  clearTimeout(timer);
734
- resolve10(result);
734
+ resolve11(result);
735
735
  }).catch((err) => {
736
736
  clearTimeout(timer);
737
737
  reject(err);
@@ -739,7 +739,7 @@ var init_task_runner = __esm({
739
739
  });
740
740
  }
741
741
  sleep(ms) {
742
- return new Promise((resolve10) => setTimeout(resolve10, ms));
742
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
743
743
  }
744
744
  };
745
745
  }
@@ -829,10 +829,10 @@ var init_worker = __esm({
829
829
  await this.processLoop(options.api, options.signer);
830
830
  }
831
831
  /** Request graceful shutdown. */
832
- async stop(reason2) {
832
+ async stop(reason3) {
833
833
  this.stopRequested = true;
834
- this.state.transitionTo("draining", reason2);
835
- this.logger.info("Shutdown requested", { reason: reason2 });
834
+ this.state.transitionTo("draining", reason3);
835
+ this.logger.info("Shutdown requested", { reason: reason3 });
836
836
  }
837
837
  /** Main processing loop. */
838
838
  async processLoop(api, signer) {
@@ -904,7 +904,7 @@ var init_worker = __esm({
904
904
  return this.identity;
905
905
  }
906
906
  sleep(ms) {
907
- return new Promise((resolve10) => setTimeout(resolve10, ms));
907
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
908
908
  }
909
909
  };
910
910
  }
@@ -912,10 +912,10 @@ var init_worker = __esm({
912
912
 
913
913
  // ../agent/src/chain-apis.ts
914
914
  async function submitTx(tx, signer) {
915
- return new Promise((resolve10, reject) => {
915
+ return new Promise((resolve11, reject) => {
916
916
  tx.signAndSend(signer, ({ status, dispatchError }) => {
917
917
  if (dispatchError) reject(new Error(`Tx failed: ${dispatchError.toString()}`));
918
- else if (status.isInBlock || status.isFinalized) resolve10(status.asFinalized.toString());
918
+ else if (status.isInBlock || status.isFinalized) resolve11(status.asFinalized.toString());
919
919
  }).catch(reject);
920
920
  });
921
921
  }
@@ -1219,7 +1219,7 @@ async function waitForTaskCompletion(api, taskId, timeoutMs = DEFAULT_TIMEOUT, p
1219
1219
  return receipt;
1220
1220
  }
1221
1221
  }
1222
- await new Promise((resolve10) => setTimeout(resolve10, pollIntervalMs));
1222
+ await new Promise((resolve11) => setTimeout(resolve11, pollIntervalMs));
1223
1223
  }
1224
1224
  throw new AgentError(
1225
1225
  "T002" /* TASK_TIMEOUT */,
@@ -1657,18 +1657,18 @@ var init_fee_estimation = __esm({
1657
1657
  const outOfBudget = candidates.filter((m) => !affordable.has(m.hash)).map((m) => ({ model: m, fee: feeMap.get(m.hash) }));
1658
1658
  const selectedModel = selected ?? candidates[0];
1659
1659
  const estimatedFee = feeMap.get(selectedModel.hash);
1660
- let reason2;
1660
+ let reason3;
1661
1661
  if (selected) {
1662
- reason2 = preferBestValue ? `Best value model within ${maxBudget} FLOP budget` : `Highest quality model within ${maxBudget} FLOP budget`;
1662
+ reason3 = preferBestValue ? `Best value model within ${maxBudget} FLOP budget` : `Highest quality model within ${maxBudget} FLOP budget`;
1663
1663
  } else {
1664
- reason2 = `Best available model (cheapest) \u2014 all options exceed ${maxBudget} FLOP budget`;
1664
+ reason3 = `Best available model (cheapest) \u2014 all options exceed ${maxBudget} FLOP budget`;
1665
1665
  }
1666
1666
  return {
1667
1667
  selectedModel,
1668
1668
  estimatedFee,
1669
1669
  alternatives,
1670
1670
  outOfBudget,
1671
- reason: reason2
1671
+ reason: reason3
1672
1672
  };
1673
1673
  }
1674
1674
  /**
@@ -1819,69 +1819,13 @@ import { Keyring as Keyring3 } from "@polkadot/keyring";
1819
1819
  import { cryptoWaitReady as cryptoWaitReady3, randomAsHex } from "@polkadot/util-crypto";
1820
1820
  import {
1821
1821
  FLOP_NETWORK_PROFILES,
1822
- DEFAULT_FAUCET_AMOUNT as SDK_DEFAULT_FAUCET_AMOUNT
1822
+ DEFAULT_FAUCET_AMOUNT as SDK_DEFAULT_FAUCET_AMOUNT,
1823
+ fundFromFaucetDetailed,
1824
+ FaucetUnconfirmedError
1823
1825
  } from "@flop-labs/sdk";
1824
1826
  function isFaucetAvailable(network) {
1825
1827
  return FAUCET_ENDPOINTS[network] !== null;
1826
1828
  }
1827
- async function requestFaucetFunding(address, network, timeout = 3e4, referral) {
1828
- const endpoint = FAUCET_ENDPOINTS[network];
1829
- if (!endpoint) {
1830
- return {
1831
- success: false,
1832
- error: `No faucet available for network: ${network}`
1833
- };
1834
- }
1835
- try {
1836
- const response = await fetch(`${endpoint}/fund`, {
1837
- method: "POST",
1838
- headers: {
1839
- "Content-Type": "application/json"
1840
- },
1841
- body: JSON.stringify({
1842
- address,
1843
- amount: DEFAULT_FAUCET_AMOUNT,
1844
- ...referral === void 0 ? {} : { ref: referral, source: "sdk" }
1845
- }),
1846
- signal: AbortSignal.timeout(timeout)
1847
- });
1848
- if (!response.ok) {
1849
- const errorText = await response.text();
1850
- return {
1851
- success: false,
1852
- error: `Faucet request failed: ${response.status} - ${errorText}`
1853
- };
1854
- }
1855
- const data = await response.json();
1856
- return {
1857
- success: true,
1858
- amount: data.amount ?? DEFAULT_FAUCET_AMOUNT,
1859
- txHash: data.txHash
1860
- };
1861
- } catch (error) {
1862
- return {
1863
- success: false,
1864
- error: error instanceof Error ? error.message : String(error)
1865
- };
1866
- }
1867
- }
1868
- async function waitForBalance(api, address, minBalance, timeoutMs = 6e4) {
1869
- const deadline = Date.now() + timeoutMs;
1870
- const pollInterval = 2e3;
1871
- while (Date.now() < deadline) {
1872
- try {
1873
- const accountInfo = await api.query.system.account(address);
1874
- const data = accountInfo.data;
1875
- const freeBalance = BigInt(data.free.toString());
1876
- if (freeBalance >= minBalance) {
1877
- return true;
1878
- }
1879
- } catch {
1880
- }
1881
- await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
1882
- }
1883
- return false;
1884
- }
1885
1829
  async function getBalance(api, address) {
1886
1830
  const accountInfo = await api.query.system.account(address);
1887
1831
  const data = accountInfo.data;
@@ -2001,9 +1945,9 @@ var init_quickstart = __esm({
2001
1945
  /**
2002
1946
  * Stop the agent worker gracefully.
2003
1947
  */
2004
- async stop(reason2) {
2005
- await this.worker.stop(reason2);
2006
- this.logger.info("QuickStart agent stopped", { reason: reason2 });
1948
+ async stop(reason3) {
1949
+ await this.worker.stop(reason3);
1950
+ this.logger.info("QuickStart agent stopped", { reason: reason3 });
2007
1951
  }
2008
1952
  /**
2009
1953
  * Shutdown agent and disconnect from network.
@@ -2376,6 +2320,10 @@ var init_quickstart = __esm({
2376
2320
  * @returns QuickStart agent result with agent wrapper and metadata
2377
2321
  */
2378
2322
  static async create(config = {}) {
2323
+ const shouldFaucet = config.faucet ?? (config.network ?? "devnet") === "devnet";
2324
+ if (shouldFaucet && isFaucetAvailable(config.network ?? "devnet") && !config.keypair && !config.signerUri?.trim()) {
2325
+ throw new Error("Faucet funding requires a caller-owned signer or keypair");
2326
+ }
2379
2327
  await cryptoWaitReady3();
2380
2328
  const network = config.network ?? "devnet";
2381
2329
  const endpoint = config.rpcEndpoint ?? NETWORK_ENDPOINTS[network];
@@ -2383,15 +2331,13 @@ var init_quickstart = __esm({
2383
2331
  const keypair = resolveOrCreateKeypair(config);
2384
2332
  const api = await connectToNetwork(endpoint, timeout);
2385
2333
  let initialBalance = await getBalance(api, keypair.address);
2386
- const shouldFaucet = config.faucet ?? network === "devnet";
2387
2334
  if (shouldFaucet && isFaucetAvailable(network) && initialBalance < DEFAULT_FAUCET_AMOUNT) {
2388
- const faucetResult = await requestFaucetFunding(keypair.address, network, timeout, config.referral);
2389
- if (faucetResult.success) {
2390
- const minBalance = BigInt(Math.round((faucetResult.amount ?? DEFAULT_FAUCET_AMOUNT) * 1e18));
2391
- const funded = await waitForBalance(api, keypair.address, minBalance, timeout * 2);
2392
- if (funded) {
2393
- initialBalance = await getBalance(api, keypair.address);
2394
- }
2335
+ const funding = await fundFromFaucetDetailed(api, keypair.address, { network, timeout, referral: config.referral });
2336
+ if (funding.status !== "refused") {
2337
+ const error = new FaucetUnconfirmedError(funding);
2338
+ const cleanup = await Promise.allSettled([api.disconnect()]);
2339
+ error.cleanupErrors = cleanup.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
2340
+ throw error;
2395
2341
  }
2396
2342
  }
2397
2343
  const agentId = generateAgentId();
@@ -2456,7 +2402,7 @@ var init_quickstart = __esm({
2456
2402
  * ```ts
2457
2403
  * const result = await FlopAgent.inference('Write a Python function to sort a list', {
2458
2404
  * network: 'devnet',
2459
- * faucet: true,
2405
+ * signerUri: process.env.FLOP_SIGNER_URI!, // Pre-funded, caller-owned signer
2460
2406
  * });
2461
2407
  * console.log(result.output);
2462
2408
  * ```
@@ -4941,9 +4887,9 @@ var init_src = __esm({
4941
4887
  });
4942
4888
 
4943
4889
  // src/cli.ts
4944
- import { Command as Command49, Option as Option8 } from "commander";
4890
+ import { Command as Command51, Option as Option8 } from "commander";
4945
4891
  import { readFileSync as readFileSync2 } from "node:fs";
4946
- import { join as join11 } from "node:path";
4892
+ import { join as join12 } from "node:path";
4947
4893
 
4948
4894
  // src/output.ts
4949
4895
  var activeOutputMode = "human";
@@ -5512,8 +5458,8 @@ function sessionListCmd() {
5512
5458
  import { Command as Command10, Option } from "commander";
5513
5459
  function parseSessionMaxFlop(value) {
5514
5460
  if (!/^\d+(?:\.\d{1,18})?$/.test(value)) throw new Error("--max-flop must be a positive decimal with at most 18 fractional digits");
5515
- const [whole, fraction = ""] = value.split(".");
5516
- const raw = BigInt(whole) * 10n ** 18n + BigInt(fraction.padEnd(18, "0"));
5461
+ const [whole, fraction2 = ""] = value.split(".");
5462
+ const raw = BigInt(whole) * 10n ** 18n + BigInt(fraction2.padEnd(18, "0"));
5517
5463
  if (raw <= 0n || raw >= 1n << 128n) throw new Error("--max-flop must be positive and fit the chain balance type");
5518
5464
  return raw;
5519
5465
  }
@@ -5528,6 +5474,7 @@ function sessionRunCmd() {
5528
5474
  return new Command10("run").description("Run verified inference directly on a named miner and settle its channel").requiredOption("--miner <ss58>", "Serving miner account").requiredOption("--model <slug>", "Model slug").requiredOption("--prompt <text>", "Prompt").requiredOption("--max-flop <N>", "Maximum escrow in FLOP").option("--max-tokens <N>", "Maximum completion tokens (SDK default: 256)").addOption(new Option("--via <path>", "Open path").choices(["auto", "offer", "force"]).default("auto")).option("--endpoint <URL>", "Reachable miner session endpoint", "http://127.0.0.1:8645").option("--auth-token-env <VAR>", "Environment variable holding the miner bearer (legacy/broker auth)").option("--new-wallet", "Save a fresh owner-only devnet wallet and fund it from the devnet faucet").option("--json", "Output as JSON").action(async (opts, command) => {
5529
5475
  let client;
5530
5476
  let walletPath;
5477
+ let funding;
5531
5478
  try {
5532
5479
  const maxFlop = parseSessionMaxFlop(opts.maxFlop);
5533
5480
  const maxTokens = opts.maxTokens === void 0 ? void 0 : parseSessionMaxTokens(opts.maxTokens);
@@ -5564,9 +5511,9 @@ function sessionRunCmd() {
5564
5511
  client = await sdk.FlopClient.createWithKeypair(resolved.rpc, pair);
5565
5512
  const faucet = await sdk.getFaucetChainInfo({ endpoint: resolved.profile.faucet });
5566
5513
  if (faucet.genesisHash.toLowerCase() !== client.api.genesisHash.toHex().toLowerCase()) throw new Error("faucet/RPC genesis mismatch");
5567
- const funded = await sdk.requestFaucetFunding(pair.address, { network: "devnet", endpoint: resolved.profile.faucet, maxRetries: 1 });
5568
- if (!funded.success) throw new Error(funded.error ?? "devnet faucet funding failed");
5569
- if (!await sdk.waitForFaucetFunding(client.api, pair.address, Number(opts.maxFlop))) {
5514
+ funding = await sdk.requestFaucetFunding(pair.address, { network: "devnet", endpoint: resolved.profile.faucet, maxRetries: 1 });
5515
+ if (!funding.success) throw new Error(funding.error ?? "devnet faucet funding failed");
5516
+ if (!await sdk.waitForAvailableBalance(client.api, pair.address, Number(opts.maxFlop))) {
5570
5517
  throw new Error("faucet balance did not arrive before the funding deadline");
5571
5518
  }
5572
5519
  } else {
@@ -5598,11 +5545,18 @@ function sessionRunCmd() {
5598
5545
  }, getJson(command));
5599
5546
  } catch (error) {
5600
5547
  const channelId = error && typeof error === "object" && "channelId" in error && error.channelId instanceof Uint8Array ? `0x${Buffer.from(error.channelId).toString("hex")}` : null;
5548
+ const fundingUnknown = funding && (funding.outcome === "unknown" || funding.success);
5601
5549
  printError({
5602
5550
  code: "SESSION_FAILED",
5603
5551
  message: error instanceof Error ? error.message : String(error),
5604
5552
  channel: channelId,
5605
- recovery: channelId ? "inspect finalized channel and terminal events before retrying" : "no channel reported",
5553
+ recovery: channelId ? "inspect finalized channel and terminal events before retrying" : fundingUnknown ? "funding is unconfirmed; reconcile the original request before requesting again" : "no channel reported",
5554
+ ...funding ? { funding: {
5555
+ outcome: fundingUnknown ? "unknown" : funding.outcome,
5556
+ ...funding.txHash ? { txHash: funding.txHash } : {},
5557
+ ...funding.amount !== void 0 ? { amount: funding.amount } : {},
5558
+ ...funding.status !== void 0 ? { status: funding.status } : {}
5559
+ } } : {},
5606
5560
  ...walletPath ? { wallet: walletPath } : {}
5607
5561
  }, "json");
5608
5562
  process.exitCode = 1;
@@ -6127,12 +6081,12 @@ function pathCheck(id, target, required = true) {
6127
6081
  };
6128
6082
  }
6129
6083
  async function probeHttpUrl(id, url, required = true, timeoutMs = 3e3) {
6130
- return new Promise((resolve10) => {
6084
+ return new Promise((resolve11) => {
6131
6085
  let parsed;
6132
6086
  try {
6133
6087
  parsed = new URL(url);
6134
6088
  } catch {
6135
- resolve10({
6089
+ resolve11({
6136
6090
  id,
6137
6091
  status: "fail",
6138
6092
  required,
@@ -6145,7 +6099,7 @@ async function probeHttpUrl(id, url, required = true, timeoutMs = 3e3) {
6145
6099
  const request3 = transport.request(parsed, { method: "GET", timeout: timeoutMs }, (response) => {
6146
6100
  response.resume();
6147
6101
  const reachable = (response.statusCode ?? 500) < 500;
6148
- resolve10({
6102
+ resolve11({
6149
6103
  id,
6150
6104
  status: reachable ? "pass" : required ? "fail" : "warn",
6151
6105
  required,
@@ -6155,7 +6109,7 @@ async function probeHttpUrl(id, url, required = true, timeoutMs = 3e3) {
6155
6109
  });
6156
6110
  request3.on("timeout", () => request3.destroy(new Error("timeout")));
6157
6111
  request3.on("error", (error) => {
6158
- resolve10({
6112
+ resolve11({
6159
6113
  id,
6160
6114
  status: required ? "fail" : "warn",
6161
6115
  required,
@@ -6199,7 +6153,7 @@ function unwrapStorage(raw) {
6199
6153
 
6200
6154
  // src/commands/launch.ts
6201
6155
  function checkGateway(url) {
6202
- return new Promise((resolve10) => {
6156
+ return new Promise((resolve11) => {
6203
6157
  try {
6204
6158
  const parsedUrl = new URL(url);
6205
6159
  const req = http2.request(
@@ -6211,17 +6165,17 @@ function checkGateway(url) {
6211
6165
  timeout: 1e3
6212
6166
  },
6213
6167
  (res) => {
6214
- resolve10(res.statusCode === 200);
6168
+ resolve11(res.statusCode === 200);
6215
6169
  }
6216
6170
  );
6217
- req.on("error", () => resolve10(false));
6171
+ req.on("error", () => resolve11(false));
6218
6172
  req.on("timeout", () => {
6219
6173
  req.destroy();
6220
- resolve10(false);
6174
+ resolve11(false);
6221
6175
  });
6222
6176
  req.end();
6223
6177
  } catch {
6224
- resolve10(false);
6178
+ resolve11(false);
6225
6179
  }
6226
6180
  });
6227
6181
  }
@@ -6529,7 +6483,7 @@ async function withCredentialLock(path5, action) {
6529
6483
  continue;
6530
6484
  }
6531
6485
  if (Date.now() >= deadline) throw new Error(`FLOP credential lock is busy at ${lock}`);
6532
- await new Promise((resolve10) => setTimeout(resolve10, 100));
6486
+ await new Promise((resolve11) => setTimeout(resolve11, 100));
6533
6487
  }
6534
6488
  }
6535
6489
  try {
@@ -6597,17 +6551,35 @@ function walletEnrollment(options) {
6597
6551
  const pair = await enrollmentWallet(options.walletPath ?? devnetWalletPath());
6598
6552
  const minBalance = options.minBalance ?? DEFAULT_MIN_BALANCE;
6599
6553
  if (await options.balance(pair.address) < minBalance) {
6600
- const funded = await fetch(`${options.faucet}/fund`, {
6601
- method: "POST",
6602
- headers: { "content-type": "application/json" },
6603
- body: JSON.stringify({ address: pair.address }),
6604
- signal: AbortSignal.timeout(6e4)
6605
- });
6606
- if (!funded.ok) throw await failure(funded, "devnet faucet");
6554
+ let funded;
6555
+ try {
6556
+ funded = await fetch(`${options.faucet}/fund`, {
6557
+ method: "POST",
6558
+ headers: { "content-type": "application/json" },
6559
+ body: JSON.stringify({ address: pair.address }),
6560
+ signal: AbortSignal.timeout(6e4)
6561
+ });
6562
+ } catch {
6563
+ throw new Error("devnet faucet outcome unknown; reconcile the original request for the saved wallet before requesting again");
6564
+ }
6565
+ if (!funded.ok) {
6566
+ if (funded.status >= 500 || funded.status === 408) throw new Error("devnet faucet outcome unknown; reconcile the original request for the saved wallet before requesting again");
6567
+ throw await failure(funded, "devnet faucet");
6568
+ }
6569
+ const response = await funded.json().catch(() => null);
6570
+ const hint = typeof response?.txHash === "string" && /^0x[0-9a-f]{64}$/i.test(response.txHash) ? ` (transaction ${response.txHash})` : "";
6607
6571
  const deadline = Date.now() + (options.fundTimeoutMs ?? 12e4);
6608
- while (await options.balance(pair.address) < minBalance) {
6609
- if (Date.now() >= deadline) throw new Error("devnet faucet grant did not finalize in time; retry later");
6610
- await new Promise((resolve10) => setTimeout(resolve10, options.pollMs ?? 3e3));
6572
+ const unconfirmed = () => new Error(`devnet balance did not reach the required floor${hint}; the faucet request is unconfirmed, reconcile it before requesting again`);
6573
+ while (true) {
6574
+ let available;
6575
+ try {
6576
+ available = await options.balance(pair.address);
6577
+ } catch {
6578
+ throw unconfirmed();
6579
+ }
6580
+ if (available >= minBalance) break;
6581
+ if (Date.now() >= deadline) throw unconfirmed();
6582
+ await new Promise((resolve11) => setTimeout(resolve11, options.pollMs ?? 3e3));
6611
6583
  }
6612
6584
  }
6613
6585
  const challenge = await fetch(
@@ -6882,8 +6854,8 @@ import http3 from "http";
6882
6854
 
6883
6855
  // src/proxy/openai.ts
6884
6856
  var GFLOPS_PER_TOKEN = 10;
6885
- function estimateTokens2(text3) {
6886
- return Math.max(1, Math.ceil((text3?.length ?? 0) / 4));
6857
+ function estimateTokens2(text4) {
6858
+ return Math.max(1, Math.ceil((text4?.length ?? 0) / 4));
6887
6859
  }
6888
6860
  function chatMessagesToPrompt(messages) {
6889
6861
  return messages.map((m) => `${m.role}: ${m.content}`).join("\n");
@@ -6944,8 +6916,8 @@ function modelsListResponse(models) {
6944
6916
  function streamMeta(outcome) {
6945
6917
  return { id: outcome.taskId, model: outcome.model, created: Math.floor(Date.now() / 1e3) };
6946
6918
  }
6947
- function chunkText(text3) {
6948
- return text3 ? text3.match(/\s+|\S+/g) ?? [] : [];
6919
+ function chunkText(text4) {
6920
+ return text4 ? text4.match(/\s+|\S+/g) ?? [] : [];
6949
6921
  }
6950
6922
  function buildChatChunk(meta, delta, finishReason) {
6951
6923
  return {
@@ -6956,13 +6928,13 @@ function buildChatChunk(meta, delta, finishReason) {
6956
6928
  choices: [{ index: 0, delta, finish_reason: finishReason }]
6957
6929
  };
6958
6930
  }
6959
- function buildTextChunk(meta, text3, finishReason) {
6931
+ function buildTextChunk(meta, text4, finishReason) {
6960
6932
  return {
6961
6933
  id: meta.id,
6962
6934
  object: "text_completion",
6963
6935
  created: meta.created,
6964
6936
  model: meta.model,
6965
- choices: [{ index: 0, text: text3, finish_reason: finishReason }]
6937
+ choices: [{ index: 0, text: text4, finish_reason: finishReason }]
6966
6938
  };
6967
6939
  }
6968
6940
  function buildUsageChunk(meta, outcome, promptTokens) {
@@ -7014,14 +6986,14 @@ var CORS_HEADERS = {
7014
6986
  "Access-Control-Allow-Headers": "Authorization, Content-Type"
7015
6987
  };
7016
6988
  function readBody(req) {
7017
- return new Promise((resolve10) => {
6989
+ return new Promise((resolve11) => {
7018
6990
  let data = "";
7019
6991
  req.on("data", (c) => data += c);
7020
6992
  req.on("end", () => {
7021
6993
  try {
7022
- resolve10(data ? JSON.parse(data) : {});
6994
+ resolve11(data ? JSON.parse(data) : {});
7023
6995
  } catch {
7024
- resolve10({});
6996
+ resolve11({});
7025
6997
  }
7026
6998
  });
7027
6999
  });
@@ -7175,8 +7147,8 @@ function createLocalEmbedder(model) {
7175
7147
  extractor = await transformers.pipeline("feature-extraction", model);
7176
7148
  }
7177
7149
  const out = [];
7178
- for (const text3 of inputs) {
7179
- const t = await extractor(text3, { pooling: "mean", normalize: true });
7150
+ for (const text4 of inputs) {
7151
+ const t = await extractor(text4, { pooling: "mean", normalize: true });
7180
7152
  out.push(Array.from(t.data));
7181
7153
  }
7182
7154
  return out;
@@ -7258,7 +7230,7 @@ async function startProxyServer(o) {
7258
7230
  embeddingModel: o.embeddingModel,
7259
7231
  embed: createLocalEmbedder(o.embeddingModel)
7260
7232
  });
7261
- await new Promise((resolve10) => server.listen(o.port, o.host, () => resolve10()));
7233
+ await new Promise((resolve11) => server.listen(o.port, o.host, () => resolve11()));
7262
7234
  const addr = server.address();
7263
7235
  const url = `http://${o.host}:${addr.port}`;
7264
7236
  const models = await listModels();
@@ -7395,9 +7367,9 @@ function launchExternal2(command, args, envOverrides) {
7395
7367
  function runHermesConfig(launcher, commands) {
7396
7368
  return commands.reduce(
7397
7369
  (p, args) => p.then(
7398
- () => new Promise((resolve10, reject) => {
7370
+ () => new Promise((resolve11, reject) => {
7399
7371
  const c = spawnLauncher(launcher, args, { stdio: "inherit" });
7400
- c.on("exit", (code) => code === 0 ? resolve10() : reject(new Error(`hermes ${args.join(" ")} exited ${code}`)));
7372
+ c.on("exit", (code) => code === 0 ? resolve11() : reject(new Error(`hermes ${args.join(" ")} exited ${code}`)));
7401
7373
  c.on("error", reject);
7402
7374
  })
7403
7375
  ),
@@ -7624,14 +7596,18 @@ function daWithdrawCmd() {
7624
7596
 
7625
7597
  // src/commands/model-upload.ts
7626
7598
  import { Command as Command30, Option as Option2 } from "commander";
7627
- import { copyFile, lstat as lstat7, mkdir as mkdir11, mkdtemp, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
7628
- import { tmpdir } from "node:os";
7599
+ import { createWriteStream } from "node:fs";
7600
+ import { copyFile, lstat as lstat7, mkdir as mkdir11, mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile9 } from "node:fs/promises";
7601
+ import { pipeline as pipeline2 } from "node:stream/promises";
7602
+ import { tmpdir as tmpdir2 } from "node:os";
7629
7603
  import { basename as basename2, join as join6, resolve as resolve8 } from "node:path";
7630
7604
 
7631
7605
  // src/model.ts
7632
7606
  import { resolve as resolve5, relative, sep } from "node:path";
7633
- import { mkdir as mkdir7, readFile as readFile4, readdir, lstat as lstat3, writeFile as writeFile7 } from "node:fs/promises";
7607
+ import { mkdir as mkdir7, mkdtemp, readFile as readFile4, readdir, lstat as lstat3, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
7634
7608
  import { execFile as execFile3, spawn as spawn4 } from "node:child_process";
7609
+ import { createHash as createHash2 } from "node:crypto";
7610
+ import { tmpdir } from "node:os";
7635
7611
  import { pipeline } from "node:stream/promises";
7636
7612
  import { promisify as promisify3 } from "node:util";
7637
7613
  function modelToOutput(model) {
@@ -7678,6 +7654,26 @@ function parseModelSource(raw) {
7678
7654
  `unsupported model source "${raw}"; use hf:<repo>, modelscope:<repo>, https://..., or da:<commitment>`
7679
7655
  );
7680
7656
  }
7657
+ function parseRevision(raw) {
7658
+ if (raw !== void 0 && !/^[0-9a-f]{40}$/.test(raw)) throw new Error("--revision must be an immutable 40-hex commit");
7659
+ return raw;
7660
+ }
7661
+ async function downloadPinnedHuggingFace(repo, dir, expectedRoot, opts) {
7662
+ const sdk = await import("@flop-labs/sdk");
7663
+ const hub = { revision: opts.revision, token: opts.token };
7664
+ const preflight = await sdk.preflightHuggingFace(repo, hub);
7665
+ if (sdk.normalizeRoot(preflight.contentRoot) !== sdk.normalizeRoot(expectedRoot)) {
7666
+ const found = `${repo} at ${preflight.revision} has content_root 0x${sdk.normalizeRoot(preflight.contentRoot)}, not the registered root ${expectedRoot}`;
7667
+ if (!opts.revision) {
7668
+ throw new Error(
7669
+ `${found}; nothing was downloaded. A repo whose main moved needs the registered --revision (the onboarding identity's sourceRevision), and a verity-v1 model's root cannot be checked from hub metadata, so it downloads only at an explicit --revision and is measured after transfer`
7670
+ );
7671
+ }
7672
+ process.stderr.write(`${found}; a verity-v1 root is checkable only after download, so the bytes are measured once fetched
7673
+ `);
7674
+ }
7675
+ return sdk.fetchHuggingFaceToDir(repo, dir, { ...hub, revision: preflight.revision, files: preflight.files });
7676
+ }
7681
7677
  function daHttpSource(endpoint) {
7682
7678
  if (!endpoint) return void 0;
7683
7679
  const base = endpoint.replace(/\/+$/, "");
@@ -7877,31 +7873,82 @@ async function submitDaBlob(executable, rpc, blobFile, publisherKey) {
7877
7873
  }
7878
7874
  return parseCustodyResult(stdout2);
7879
7875
  }
7876
+ async function daRetrieveChunk(executable, rpc, commitment, j) {
7877
+ const dir = await mkdtemp(resolve5(tmpdir(), "flop-da-chunk-"));
7878
+ const output = resolve5(dir, "chunk.bin");
7879
+ try {
7880
+ let stdout2;
7881
+ try {
7882
+ ({ stdout: stdout2 } = await promisify3(execFile3)(executable, [
7883
+ "retrieve",
7884
+ "--chain-rpc",
7885
+ rpc,
7886
+ "--commitment",
7887
+ commitment,
7888
+ "--output",
7889
+ output,
7890
+ "--chunk",
7891
+ String(j),
7892
+ "--json"
7893
+ ], { maxBuffer: 1024 * 1024 }));
7894
+ } catch (error) {
7895
+ const stderr = String(error.stderr ?? "");
7896
+ if (/unexpected argument '--chunk'/.test(stderr)) {
7897
+ throw new Error("this da-validator has no `retrieve --chunk`; install one built from flop-core with it to spot-check the DA image");
7898
+ }
7899
+ throw new Error(`da-validator retrieve --chunk ${j} failed: ${stderr.trim() || error.message}`);
7900
+ }
7901
+ const line = lastJsonLine(stdout2);
7902
+ if (!line) throw new Error("da-validator retrieve --chunk returned no JSON receipt");
7903
+ const receipt = JSON.parse(line);
7904
+ const bytes4 = new Uint8Array(await readFile4(output));
7905
+ if (receipt.chunk !== j || receipt.length !== bytes4.length || receipt.sha256 !== createHash2("sha256").update(bytes4).digest("hex")) {
7906
+ throw new Error(`da-validator chunk ${j} receipt does not match the retrieved bytes`);
7907
+ }
7908
+ return { chunk: j, offset: Number(receipt.offset), sizeBytes: Number(receipt.size_bytes), chunkSize: Number(receipt.chunk_size), bytes: bytes4 };
7909
+ } finally {
7910
+ await rm2(dir, { recursive: true, force: true });
7911
+ }
7912
+ }
7913
+ async function readHuggingFaceRange(repo, revision, path5, start, end, size, token) {
7914
+ const url = `https://huggingface.co/${repo}/resolve/${revision}/${path5.split("/").map(encodeURIComponent).join("/")}`;
7915
+ const response = await fetch(url, {
7916
+ headers: { Range: `bytes=${start}-${end}`, ...token ? { Authorization: `Bearer ${token}` } : {} },
7917
+ signal: AbortSignal.timeout(12e4)
7918
+ });
7919
+ if (response.status !== 206 || response.headers.get("content-range") !== `bytes ${start}-${end}/${size}`) {
7920
+ await response.body?.cancel();
7921
+ throw new Error(`Hugging Face did not honor byte range ${start}-${end} of ${path5} at ${revision} (HTTP ${response.status})`);
7922
+ }
7923
+ const bytes4 = new Uint8Array(await response.arrayBuffer());
7924
+ if (bytes4.length !== end - start + 1) throw new Error(`Hugging Face returned ${bytes4.length} bytes for range ${start}-${end} of ${path5}`);
7925
+ return bytes4;
7926
+ }
7880
7927
 
7881
7928
  // src/commands/model-onboard.ts
7882
7929
  import { Command as Command29 } from "commander";
7883
- import { createHash as createHash3 } from "node:crypto";
7930
+ import { createHash as createHash5 } from "node:crypto";
7884
7931
  import { execFile as execFile4 } from "node:child_process";
7885
7932
  import { access, lstat as lstat6, mkdir as mkdir10, readFile as readFile7, writeFile as writeFile8 } from "node:fs/promises";
7886
7933
  import { basename, isAbsolute, join as join5, resolve as resolve7 } from "node:path";
7887
7934
  import { promisify as promisify4 } from "node:util";
7888
- import { computeChannel as computeChannel4 } from "@flop-labs/sdk";
7935
+ import { computeChannel as computeChannel5 } from "@flop-labs/sdk";
7889
7936
 
7890
7937
  // src/model-onboard-state.ts
7891
- import { createHash as createHash2 } from "node:crypto";
7938
+ import { createHash as createHash3 } from "node:crypto";
7892
7939
  import { createReadStream } from "node:fs";
7893
- import { chmod as chmod3, lstat as lstat4, mkdir as mkdir8, open, readFile as readFile5, rename as rename6, rm as rm2 } from "node:fs/promises";
7940
+ import { chmod as chmod3, lstat as lstat4, mkdir as mkdir8, open, readFile as readFile5, rename as rename6, rm as rm3 } from "node:fs/promises";
7894
7941
  import { homedir as homedir3, hostname as hostname2 } from "node:os";
7895
7942
  import { join as join3, resolve as resolve6 } from "node:path";
7896
7943
  import { collectModelFiles } from "@flop-labs/sdk";
7897
- var digest = (value) => createHash2("sha256").update(value).digest("hex");
7944
+ var digest = (value) => createHash3("sha256").update(value).digest("hex");
7898
7945
  function onboardingDirectory(binding, override) {
7899
7946
  return override ? resolve6(override) : join3(process.env.XDG_STATE_HOME ?? join3(homedir3(), ".local/state"), "flop/model-onboarding", digest(JSON.stringify(binding)));
7900
7947
  }
7901
7948
  async function hashFile(path5) {
7902
7949
  const stat2 = await lstat4(path5);
7903
7950
  if (!stat2.isFile() || stat2.isSymbolicLink()) throw new Error("onboarding artifact must be a regular file");
7904
- const hash2 = createHash2("sha256");
7951
+ const hash2 = createHash3("sha256");
7905
7952
  for await (const chunk of createReadStream(path5)) hash2.update(chunk);
7906
7953
  return hash2.digest("hex");
7907
7954
  }
@@ -7925,7 +7972,7 @@ async function openOnboardingState(dir, binding) {
7925
7972
  const statePath = join3(dir, "state.json");
7926
7973
  const close = async () => {
7927
7974
  await lock.close();
7928
- await rm2(lockPath);
7975
+ await rm3(lockPath);
7929
7976
  };
7930
7977
  try {
7931
7978
  await lock.writeFile(JSON.stringify({ pid: process.pid, host: hostname2() }));
@@ -8066,10 +8113,10 @@ import { computeChannel as computeChannel2, attestation } from "@flop-labs/sdk";
8066
8113
  import { randomUUID as randomUUID3 } from "node:crypto";
8067
8114
  import { chmod as chmod4, lstat as lstat5, mkdir as mkdir9, open as open2, readFile as readFile6, rename as rename7 } from "node:fs/promises";
8068
8115
  import { join as join4 } from "node:path";
8069
- var bytes = (hex4) => {
8070
- if (!/^0x(?:[0-9a-f]{2})*$/.test(hex4))
8116
+ var bytes = (hex5) => {
8117
+ if (!/^0x(?:[0-9a-f]{2})*$/.test(hex5))
8071
8118
  throw new Error("invalid private replay bytes");
8072
- return new Uint8Array(Buffer.from(hex4.slice(2), "hex"));
8119
+ return new Uint8Array(Buffer.from(hex5.slice(2), "hex"));
8073
8120
  };
8074
8121
  var hex = (v) => `0x${Buffer.from(v).toString("hex")}`;
8075
8122
  var privateJson = (value) => JSON.stringify(
@@ -8406,8 +8453,8 @@ async function checkPaidAdmission(api, endpoint, identity, token, completion, pa
8406
8453
  await anonymous.body?.cancel();
8407
8454
  const info = await computeChannel3.fetchMinerSessionInfo(endpoint, { Authorization: `Bearer ${token}` }, AbortSignal.timeout(3e4));
8408
8455
  const actual = info.modelIdentity;
8409
- const hex4 = (v) => `0x${Buffer.from(v).toString("hex")}`;
8410
- if (!actual || actual.registryModel !== identity.registryModel || actual.registryVersion !== identity.registryVersion || actual.backendModelName !== identity.backendModelName || hex4(actual.modelHash) !== identity.modelHash || hex4(actual.measuredRoot) !== identity.measuredRoot || hex4(actual.decodePolicyHash) !== identity.decodePolicyHash || actual.precision !== identity.precision) throw new Error("paid admission authenticated model identity differs from prepared checkpoint");
8456
+ const hex5 = (v) => `0x${Buffer.from(v).toString("hex")}`;
8457
+ if (!actual || actual.registryModel !== identity.registryModel || actual.registryVersion !== identity.registryVersion || actual.backendModelName !== identity.backendModelName || hex5(actual.modelHash) !== identity.modelHash || hex5(actual.measuredRoot) !== identity.measuredRoot || hex5(actual.decodePolicyHash) !== identity.decodePolicyHash || actual.precision !== identity.precision) throw new Error("paid admission authenticated model identity differs from prepared checkpoint");
8411
8458
  if (!info.isReady || !info.calibration?.ready) throw new Error("paid admission miner is not calibrated and serving");
8412
8459
  const head = await api.rpc.chain.getFinalizedHead(), view = await api.at(head);
8413
8460
  const registration = await view.query.minerStaking.miners(info.minerAddress);
@@ -8416,7 +8463,7 @@ async function checkPaidAdmission(api, endpoint, identity, token, completion, pa
8416
8463
  if (key.length !== 32) throw new Error("paid admission requires the runtime AccountId32 encoding");
8417
8464
  const result = await api.rpc.state.call(
8418
8465
  "TrustRootApi_accepted_calibration_cap",
8419
- hex4(Uint8Array.from([128, ...key])),
8466
+ hex5(Uint8Array.from([128, ...key])),
8420
8467
  head
8421
8468
  );
8422
8469
  const value = result.toU8a(true);
@@ -8432,7 +8479,7 @@ async function checkPaidAdmission(api, endpoint, identity, token, completion, pa
8432
8479
  hash: (await api.rpc.chain.getBlockHash((BigInt(anchor.receipt.number) + 1n).toString())).toHex()
8433
8480
  };
8434
8481
  const session = await rpc.session(anchor, seed, finalized);
8435
- if (!payer || session.agent !== hex4(api.createType("AccountId", payer).toU8a()) || session.miner !== hex4(key) || session.model.modelHash !== identity.modelHash || session.model.measuredRoot !== identity.measuredRoot || session.model.precision !== identity.precision || session.model.decodePolicyHash !== identity.decodePolicyHash) throw new Error("historical paid completion differs from onboarding model/payer/miner");
8482
+ if (!payer || session.agent !== hex5(api.createType("AccountId", payer).toU8a()) || session.miner !== hex5(key) || session.model.modelHash !== identity.modelHash || session.model.measuredRoot !== identity.measuredRoot || session.model.precision !== identity.precision || session.model.decodePolicyHash !== identity.decodePolicyHash) throw new Error("historical paid completion differs from onboarding model/payer/miner");
8436
8483
  historicalCompletion = await verifyStoredReplay(api, completion);
8437
8484
  }
8438
8485
  return {
@@ -8444,6 +8491,215 @@ async function checkPaidAdmission(api, endpoint, identity, token, completion, pa
8444
8491
  };
8445
8492
  }
8446
8493
 
8494
+ // src/throughput-ceiling.ts
8495
+ import { computeChannel as computeChannel4 } from "@flop-labs/sdk";
8496
+ function parseMinTokensPerSecond(raw) {
8497
+ const text4 = raw.trim();
8498
+ if (!/^\d+(?:\.\d{1,6})?$/.test(text4) || !/[1-9]/.test(text4)) {
8499
+ throw new Error("--min-tokens-per-second must be a positive decimal with at most 6 fractional digits");
8500
+ }
8501
+ return text4;
8502
+ }
8503
+ function fraction(minimum) {
8504
+ const [whole, frac = ""] = minimum.split(".");
8505
+ return [BigInt(whole + frac), 10n ** BigInt(frac.length)];
8506
+ }
8507
+ var FLOPS_PER_GN = 1000000000n;
8508
+ function perTokenWork(checkpoint) {
8509
+ const hybrid = computeChannel4.isDenseGdnArch(checkpoint.arch);
8510
+ return {
8511
+ flopsPerToken: hybrid ? checkpoint.gnPerCompletionToken * FLOPS_PER_GN : 2n * checkpoint.activeParams,
8512
+ gnPerToken: checkpoint.gnPerCompletionToken
8513
+ };
8514
+ }
8515
+ function centiTokensPerSecond(capGnPerBlock, blockTimeMs, flopsPerToken) {
8516
+ if (flopsPerToken <= 0n || blockTimeMs <= 0n) throw new RangeError("flopsPerToken and blockTimeMs must be positive");
8517
+ return capGnPerBlock * FLOPS_PER_GN * 1000n * 100n / (blockTimeMs * flopsPerToken);
8518
+ }
8519
+ function exactGn(flops) {
8520
+ const frac = (flops % FLOPS_PER_GN).toString().padStart(9, "0").replace(/0+$/, "");
8521
+ return `${flops / FLOPS_PER_GN}${frac ? `.${frac}` : ""}`;
8522
+ }
8523
+ function decimal(centi) {
8524
+ return `${centi / 100n}.${(centi % 100n).toString().padStart(2, "0")}`;
8525
+ }
8526
+ function unknown(work, minimum, reason3) {
8527
+ return {
8528
+ status: "unknown",
8529
+ tokensPerSecond: `unknown (${reason3})`,
8530
+ gnPerToken: work ? exactGn(work.flopsPerToken) : null,
8531
+ flopsPerToken: work?.flopsPerToken.toString() ?? null,
8532
+ capGnPerBlock: null,
8533
+ capSource: null,
8534
+ miner: null,
8535
+ bestTokensPerSecond: null,
8536
+ bestCapGnPerBlock: null,
8537
+ blockTimeMs: null,
8538
+ calibratedMiners: null,
8539
+ finalizedBlock: null,
8540
+ minTokensPerSecond: minimum === void 0 ? null : Number(minimum),
8541
+ meetsMinimum: minimum === void 0 ? null : false,
8542
+ reason: reason3
8543
+ };
8544
+ }
8545
+ function throughputCeiling(work, chain, minimum) {
8546
+ if (typeof work === "string") return unknown(null, minimum, work);
8547
+ if (work.flopsPerToken <= 0n) return unknown(work, minimum, "metering reports no work per completion token");
8548
+ if (chain === null) return unknown(work, minimum, "no chain connection");
8549
+ if (typeof chain === "string") return unknown(work, minimum, chain);
8550
+ if (chain.capGnPerBlock === null) {
8551
+ return unknown(work, minimum, chain.miner === null ? "no miner holds an accepted calibration cap at finality" : `miner ${chain.miner} holds no accepted calibration cap at finality`);
8552
+ }
8553
+ const flops = work.flopsPerToken;
8554
+ const centi = centiTokensPerSecond(chain.capGnPerBlock, chain.blockTimeMs, flops);
8555
+ let meets = null;
8556
+ if (minimum !== void 0) {
8557
+ const [num, den] = fraction(minimum);
8558
+ meets = chain.capGnPerBlock * FLOPS_PER_GN * 1000n * den >= num * chain.blockTimeMs * flops;
8559
+ }
8560
+ return {
8561
+ status: "known",
8562
+ tokensPerSecond: decimal(centi),
8563
+ gnPerToken: exactGn(flops),
8564
+ flopsPerToken: flops.toString(),
8565
+ capGnPerBlock: chain.capGnPerBlock.toString(),
8566
+ capSource: chain.capSource,
8567
+ miner: chain.miner,
8568
+ bestTokensPerSecond: chain.bestCapGnPerBlock === null ? null : decimal(centiTokensPerSecond(chain.bestCapGnPerBlock, chain.blockTimeMs, flops)),
8569
+ bestCapGnPerBlock: chain.bestCapGnPerBlock?.toString() ?? null,
8570
+ blockTimeMs: chain.blockTimeMs.toString(),
8571
+ calibratedMiners: chain.calibratedMiners,
8572
+ finalizedBlock: chain.finalizedBlock.toString(),
8573
+ minTokensPerSecond: minimum === void 0 ? null : Number(minimum),
8574
+ meetsMinimum: meets
8575
+ };
8576
+ }
8577
+ async function readChainCapInputs(api, miner) {
8578
+ const block = api.consts?.babe?.expectedBlockTime;
8579
+ const lease = api.consts?.hwCalibration?.calibrationLeaseBlocks;
8580
+ if (block == null) throw new Error("runtime constant babe.expectedBlockTime is missing");
8581
+ if (lease == null) throw new Error("runtime constant hwCalibration.calibrationLeaseBlocks is missing");
8582
+ const head = await api.rpc.chain.getFinalizedHead();
8583
+ const [view, header] = await Promise.all([api.at(head), api.rpc.chain.getHeader(head)]);
8584
+ const caps = view.query?.hwCalibration?.benchmarkBurstCaps;
8585
+ if (!caps?.entries) throw new Error("runtime has no hwCalibration.benchmarkBurstCaps");
8586
+ const now = BigInt(header.number.toString());
8587
+ const leaseBlocks = BigInt(lease.toString());
8588
+ const live = (raw) => {
8589
+ if (!raw?.isSome) return null;
8590
+ const cap = raw.unwrap();
8591
+ const acceptedAt = BigInt((cap.acceptedAt ?? cap.accepted_at).toString());
8592
+ const value = BigInt((cap.capGflopsPerSec ?? cap.cap_gflops_per_sec).toString());
8593
+ return now - acceptedAt >= leaseBlocks || value <= 0n ? null : value;
8594
+ };
8595
+ const entries = await caps.entries();
8596
+ const values = entries.map(([, raw]) => live(raw)).filter((value) => value !== null).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
8597
+ const mid = Math.floor(values.length / 2);
8598
+ const median = values.length === 0 ? null : values.length % 2 === 1 ? values[mid] : (values[mid - 1] + values[mid]) / 2n;
8599
+ const capGnPerBlock = miner === void 0 ? median : live(await caps(miner));
8600
+ return {
8601
+ capGnPerBlock,
8602
+ capSource: miner === void 0 ? "chain-median" : "miner",
8603
+ miner: miner ?? null,
8604
+ bestCapGnPerBlock: values[values.length - 1] ?? null,
8605
+ blockTimeMs: BigInt(block.toString()),
8606
+ calibratedMiners: values.length,
8607
+ finalizedBlock: now
8608
+ };
8609
+ }
8610
+ var CEILING_READ_TIMEOUT_MS = 15e3;
8611
+ async function chainCapInputsOrReason(api, miner, timeoutMs = CEILING_READ_TIMEOUT_MS) {
8612
+ let timer;
8613
+ try {
8614
+ return await Promise.race([
8615
+ readChainCapInputs(api, miner),
8616
+ new Promise((_, reject) => {
8617
+ timer = setTimeout(() => reject(new Error(`timed out after ${timeoutMs} ms`)), timeoutMs);
8618
+ })
8619
+ ]);
8620
+ } catch (error) {
8621
+ return `chain read failed: ${error.message}`;
8622
+ } finally {
8623
+ clearTimeout(timer);
8624
+ }
8625
+ }
8626
+ function ceilingWarning(ceiling) {
8627
+ if (ceiling.status === "unknown") return `warning: paid throughput ceiling ${ceiling.tokensPerSecond}
8628
+ `;
8629
+ const at = ceiling.capSource === "miner" ? `miner ${ceiling.miner}'s cap` : "the median live cap";
8630
+ const detail = `${ceiling.gnPerToken} G_n/token at ${at} ${ceiling.capGnPerBlock} G_n/block; best of ${ceiling.calibratedMiners} calibrated miner(s) ${ceiling.bestTokensPerSecond ?? "n/a"} tok/s (not gated)`;
8631
+ const tail = "per channel, completion tokens only; prompt and attention G_n lower it";
8632
+ if (ceiling.meetsMinimum === false) {
8633
+ return `warning: paid throughput ceiling ${ceiling.tokensPerSecond} tok/s (${detail}) is below --min-tokens-per-second ${ceiling.minTokensPerSecond}; ${tail}
8634
+ `;
8635
+ }
8636
+ return `paid throughput ceiling: ${ceiling.tokensPerSecond} tok/s (${detail}); ${tail}
8637
+ `;
8638
+ }
8639
+ function enforceCeiling(ceiling) {
8640
+ if (ceiling.meetsMinimum !== false) return;
8641
+ throw new Error(
8642
+ ceiling.status === "unknown" ? `paid throughput ceiling is ${ceiling.tokensPerSecond}; --min-tokens-per-second ${ceiling.minTokensPerSecond} cannot be shown to hold` : `paid throughput ceiling ${ceiling.tokensPerSecond} tok/s is below --min-tokens-per-second ${ceiling.minTokensPerSecond}`
8643
+ );
8644
+ }
8645
+ function snakeCeiling(ceiling) {
8646
+ return Object.fromEntries(Object.entries(ceiling).map(([k, v]) => [k.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`), v]));
8647
+ }
8648
+
8649
+ // src/model-da-spot-check.ts
8650
+ import { createHash as createHash4, randomInt } from "node:crypto";
8651
+ var SPOT_CHECK_CHUNKS = 2;
8652
+ var BLOCK_SIZE = 4096;
8653
+ function randomChunks(n, k) {
8654
+ const picked = /* @__PURE__ */ new Set();
8655
+ while (picked.size < Math.min(n, k)) picked.add(randomInt(n));
8656
+ return [...picked].sort((a, b) => a - b);
8657
+ }
8658
+ async function spotCheckImage(files, imageSize, io) {
8659
+ const ordered = [...files].sort((a, b) => Buffer.from(a.path).compare(Buffer.from(b.path)));
8660
+ let cursor = 0;
8661
+ const layout = ordered.map((file) => {
8662
+ const start = cursor;
8663
+ cursor += Math.ceil(file.size / BLOCK_SIZE) * BLOCK_SIZE;
8664
+ return { file, start, dataEnd: start + file.size, end: cursor };
8665
+ });
8666
+ if (cursor !== imageSize) throw new Error(`file table packs to ${cursor} bytes, not the bound image's ${imageSize}`);
8667
+ const n = Math.max(1, Math.ceil(imageSize / io.chunkSize));
8668
+ const chunks = (io.pick ?? randomChunks)(n, SPOT_CHECK_CHUNKS);
8669
+ for (const j of chunks) {
8670
+ const chunkStart = j * io.chunkSize;
8671
+ const chunkEnd = Math.min(chunkStart + io.chunkSize, imageSize);
8672
+ const got = await io.readChunk(j);
8673
+ const expectedChunkSize = n === 1 ? imageSize : io.chunkSize;
8674
+ if (got.chunkSize !== expectedChunkSize || got.sizeBytes !== imageSize || got.offset !== chunkStart || got.bytes.length !== chunkEnd - chunkStart) {
8675
+ throw new Error(`DA chunk ${j} layout differs from the ${io.chunkSize}-byte chunking of a ${imageSize}-byte image`);
8676
+ }
8677
+ const at = (offset) => offset - chunkStart;
8678
+ for (const { file, start, dataEnd, end } of layout) {
8679
+ if (end <= chunkStart || start >= chunkEnd) continue;
8680
+ const from = Math.max(start, chunkStart), to = Math.min(dataEnd, chunkEnd);
8681
+ if (from < to) {
8682
+ const slice = got.bytes.subarray(at(from), at(to));
8683
+ if (start >= chunkStart && dataEnd <= chunkEnd) {
8684
+ if (createHash4("sha256").update(slice).digest("hex") !== file.sha256.toLowerCase().replace(/^0x/, "")) {
8685
+ throw new Error(`DA spot check: chunk ${j} of the bound image does not hold ${file.path} (sha256 differs)`);
8686
+ }
8687
+ } else {
8688
+ const hub = await io.readHubRange(file.path, from - start, to - start - 1, file.size);
8689
+ if (!Buffer.from(hub).equals(Buffer.from(slice))) {
8690
+ throw new Error(`DA spot check: chunk ${j} of the bound image differs from ${file.path} at the hub revision`);
8691
+ }
8692
+ }
8693
+ }
8694
+ const padFrom = Math.max(dataEnd, chunkStart), padTo = Math.min(end, chunkEnd);
8695
+ if (padFrom < padTo && got.bytes.subarray(at(padFrom), at(padTo)).some((b) => b !== 0)) {
8696
+ throw new Error(`DA spot check: chunk ${j} of the bound image has nonzero padding after ${file.path}`);
8697
+ }
8698
+ }
8699
+ }
8700
+ return { chunks, of: n };
8701
+ }
8702
+
8447
8703
  // src/commands/model-onboard.ts
8448
8704
  var run = promisify4(execFile4);
8449
8705
  var PRECISIONS = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
@@ -8455,7 +8711,7 @@ async function requireHybridApproval(api, arch) {
8455
8711
  async function requireHybridApprovalAt(view, arch) {
8456
8712
  const query = view.query.meteringVerifier?.hybridProfiles;
8457
8713
  if (!query) throw new Error("runtime has no hybrid proof profile approval; deploy the reviewed runtime first");
8458
- const hash2 = `0x${Buffer.from(computeChannel4.meteringArchHash(arch)).toString("hex")}`;
8714
+ const hash2 = `0x${Buffer.from(computeChannel5.meteringArchHash(arch)).toString("hex")}`;
8459
8715
  const value = await query(hash2);
8460
8716
  if (!value || value.isNone) throw new Error(`hybrid proof profile ${hash2} is not approved`);
8461
8717
  const profile = value.unwrap();
@@ -8522,7 +8778,7 @@ function resolveParamsBillion(checkpoint, override) {
8522
8778
  throw new Error(`--params-billion must be a positive integer (got ${override})`);
8523
8779
  }
8524
8780
  if (override === registered) return override;
8525
- if (computeChannel4.isDenseGdnArch(checkpoint.arch)) throw new Error("--params-billion must match the complete checkpoint census");
8781
+ if (computeChannel5.isDenseGdnArch(checkpoint.arch)) throw new Error("--params-billion must match the complete checkpoint census");
8526
8782
  if (Math.abs(override - derived) > 0.05 * derived) {
8527
8783
  throw new Error(
8528
8784
  `--params-billion ${override} disagrees with config.json's ${derived.toFixed(2)}B parameters by more than 5%`
@@ -8552,9 +8808,9 @@ function onboardTeePlatforms(soft, raw) {
8552
8808
  return normalizeTeePlatforms(raw);
8553
8809
  }
8554
8810
  function normalizeHash(raw, name) {
8555
- const hex4 = raw.trim().replace(/^0x/i, "").toLowerCase();
8556
- if (!/^[0-9a-f]{64}$/.test(hex4)) throw new Error(`${name} must be 32-byte hex`);
8557
- return `0x${hex4}`;
8811
+ const hex5 = raw.trim().replace(/^0x/i, "").toLowerCase();
8812
+ if (!/^[0-9a-f]{64}$/.test(hex5)) throw new Error(`${name} must be 32-byte hex`);
8813
+ return `0x${hex5}`;
8558
8814
  }
8559
8815
  function identityFromPack(registryModel2, registryVersion, backendModelName, precision, decodePolicyHash, contentRoot, checkpoint, sourceRevision) {
8560
8816
  const measuredRoot = normalizeHash(contentRoot, "modelpack content_root");
@@ -8566,7 +8822,7 @@ function identityFromPack(registryModel2, registryVersion, backendModelName, pre
8566
8822
  decodePolicyHash: normalizeHash(decodePolicyHash, "decode policy hash"),
8567
8823
  measuredRoot,
8568
8824
  backendModelName,
8569
- archHash: `0x${Buffer.from(computeChannel4.meteringArchHash(checkpoint.arch)).toString("hex")}`,
8825
+ archHash: `0x${Buffer.from(computeChannel5.meteringArchHash(checkpoint.arch)).toString("hex")}`,
8570
8826
  quantization: catalogQuantization(checkpoint),
8571
8827
  gnPerPromptToken: checkpoint.gnPerPromptToken.toString(),
8572
8828
  gnPerCompletionToken: checkpoint.gnPerCompletionToken.toString(),
@@ -8719,7 +8975,7 @@ function boundCensus(stdout2, configHash) {
8719
8975
  return { total: BigInt(result.total_params), sourceFiles: result.source_files };
8720
8976
  }
8721
8977
  async function validateCapture(endpoint, identity, checkpoint, modelDir) {
8722
- const hybrid = computeChannel4.isDenseGdnArch(checkpoint.arch);
8978
+ const hybrid = computeChannel5.isDenseGdnArch(checkpoint.arch);
8723
8979
  const script = join5(repoRoot(), "infra/miner/vllm-toploc", hybrid ? "validate_hybrid_capture.py" : "validate_capture.py");
8724
8980
  try {
8725
8981
  await run("uv", ["run", "--no-project", "python", script], {
@@ -8747,8 +9003,8 @@ function optionValue(value) {
8747
9003
  }
8748
9004
  function codecText(value) {
8749
9005
  if (value?.toUtf8) return value.toUtf8();
8750
- const text3 = value?.toString?.() ?? String(value ?? "");
8751
- return /^0x(?:[0-9a-f]{2})+$/i.test(text3) ? Buffer.from(text3.slice(2), "hex").toString("utf8") : text3;
9006
+ const text4 = value?.toString?.() ?? String(value ?? "");
9007
+ return /^0x(?:[0-9a-f]{2})+$/i.test(text4) ? Buffer.from(text4.slice(2), "hex").toString("utf8") : text4;
8752
9008
  }
8753
9009
  function codecHash(value) {
8754
9010
  return normalizeHash(value?.toHex?.() ?? value?.toString?.() ?? String(value), "on-chain hash");
@@ -8797,7 +9053,7 @@ async function requireApprovalEvidence(api, modelHash, precision, signer, imageS
8797
9053
  if (!Array.from(members).some((member) => sameAccount(member, signer))) throw new Error("approval signer is not an active registrar");
8798
9054
  if (sameAccount(model.publisher, signer)) throw new Error("registrar cannot approve its own published model");
8799
9055
  }
8800
- const requiresHybrid = computeChannel4.isDenseGdnArch(arch);
9056
+ const requiresHybrid = computeChannel5.isDenseGdnArch(arch);
8801
9057
  if (!root) {
8802
9058
  const requirements = optionValue(await registry.admissionRequirements(modelHash));
8803
9059
  if (!requirements) throw new Error("approval requires model admission requirements");
@@ -8811,7 +9067,7 @@ async function requireApprovalEvidence(api, modelHash, precision, signer, imageS
8811
9067
  }
8812
9068
  const archHash = optionValue(rawArch);
8813
9069
  if (!archHash) throw new Error("approval requires a model architecture hash");
8814
- const expectedArchHash = `0x${Buffer.from(computeChannel4.meteringArchHash(arch)).toString("hex")}`;
9070
+ const expectedArchHash = `0x${Buffer.from(computeChannel5.meteringArchHash(arch)).toString("hex")}`;
8815
9071
  if (codecHash(archHash) !== expectedArchHash) throw new Error("approval architecture hash differs from the checkpoint");
8816
9072
  const policies = optionValue(rawPolicies);
8817
9073
  if (!policies || policies.length === 0) throw new Error("approval requires a nonempty decode-policy allowlist");
@@ -8829,6 +9085,7 @@ async function requireApprovalEvidence(api, modelHash, precision, signer, imageS
8829
9085
  const leaseEnd = metadata.leaseEnd ?? metadata.lease_end;
8830
9086
  const header = await api.rpc.chain.getHeader(head);
8831
9087
  if (leaseEnd === null || leaseEnd === void 0 || BigInt(leaseEnd) <= BigInt(header.number.toString())) throw new Error("approval requires a live DA lease");
9088
+ let boundTable = null;
8832
9089
  if (files) {
8833
9090
  const sdk = await import("@flop-labs/sdk");
8834
9091
  const { blake2AsHex: blake2AsHex2 } = await import("@polkadot/util-crypto");
@@ -8837,7 +9094,15 @@ async function requireApprovalEvidence(api, modelHash, precision, signer, imageS
8837
9094
  throw new Error("approval file table does not hash to model_hash");
8838
9095
  }
8839
9096
  const tableCommitment = normalizeHash(blake2AsHex2(table2, 256), "file table commitment");
8840
- const mirrors = await sdk.readModelMirrors(view, modelHash);
9097
+ boundTable = tableCommitment;
9098
+ if (!registry.modelMirrors) throw new Error("cannot read ModelMirrors at finality: the runtime has no modelRegistry.modelMirrors");
9099
+ let rawMirrors;
9100
+ try {
9101
+ rawMirrors = await registry.modelMirrors(modelHash);
9102
+ } catch (error) {
9103
+ throw new Error(`cannot read ModelMirrors at finality: ${error.message}`);
9104
+ }
9105
+ const mirrors = rawMirrors?.isSome ? sdk.decodeModelSources(rawMirrors.unwrap().toJSON()) : [];
8841
9106
  if (!mirrors.some((m) => m.kind === "da" && normalizeHash(String(m.commitment), "DA mirror commitment") === tableCommitment)) {
8842
9107
  throw new Error("approval requires the DA mirror to name the model's file table");
8843
9108
  }
@@ -8858,7 +9123,7 @@ async function requireApprovalEvidence(api, modelHash, precision, signer, imageS
8858
9123
  }
8859
9124
  const status = String(model.status);
8860
9125
  if (status !== "Pending" && status !== "Verified") throw new Error(`model cannot be approved in ${status} status`);
8861
- return { approved: status === "Verified" };
9126
+ return { approved: status === "Verified", weights: ref.commitment, table: boundTable };
8862
9127
  }
8863
9128
  function decodeDataRef(value) {
8864
9129
  const ref = optionValue(value);
@@ -8923,12 +9188,19 @@ function missingMirrors(registered, desired) {
8923
9188
  return !registered.some((actual) => sourceMatches(actual, comparable));
8924
9189
  });
8925
9190
  }
8926
- async function verifiedHuggingFaceMirror(repo, verityRoot, verify) {
9191
+ async function verifiedHuggingFaceMirror(repo, contentRoot, hub) {
8927
9192
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) throw new Error(`--hf-repo must be <owner>/<model> (got ${repo})`);
8928
- const result = await verify({ kind: "huggingface", repo }, verityRoot);
9193
+ const bare = (hex5) => hex5.toLowerCase().replace(/^0x/, "");
9194
+ const table2 = await hub.preflight(repo);
9195
+ if (bare(table2.contentRoot) !== bare(contentRoot)) {
9196
+ throw new Error(
9197
+ `Hugging Face ${repo} at ${table2.revision} has content_root 0x${bare(table2.contentRoot)}, not the measured root ${contentRoot}; nothing was downloaded. Pass the --revision the local checkpoint came from, onboard from hf:${repo} so the registered files are the repo's, or omit --hf-repo`
9198
+ );
9199
+ }
9200
+ const result = await hub.verify({ kind: "huggingface", repo }, contentRoot, table2.revision);
8929
9201
  if (!result.ok) {
8930
9202
  throw new Error(
8931
- `Hugging Face ${repo} does not reproduce the measured root ${verityRoot} (${result.reason ?? "no match"}); onboard from hf:${repo} so the registered files are the repo's, or omit --hf-repo`
9203
+ `Hugging Face ${repo} does not reproduce the measured root ${contentRoot} (${result.reason ?? "no match"}); onboard from hf:${repo} so the registered files are the repo's, or omit --hf-repo`
8932
9204
  );
8933
9205
  }
8934
9206
  return { mirror: { kind: "huggingface", repo }, revision: result.revision ?? null };
@@ -8972,7 +9244,14 @@ function modelOnboardCmd() {
8972
9244
  ).option("--registry-model <name>", "Registry model name").option("--registry-version <version>", "Registry version", "1").option("--backend-model-name <name>", "Exact model name returned by the serving backend").option("--da-validator <path>", "da-validator executable", "da-validator").option(
8973
9245
  "--hf-repo <owner/model>",
8974
9246
  "Hugging Face repo a local --source came from; registered as a mirror only if it reproduces the measured root"
8975
- ).option("--revision <sha>", "Immutable Hugging Face commit; first resolution is retained when omitted").option("--state-dir <path>", "Private resumable workspace (defaults to XDG_STATE_HOME/flop/model-onboarding)").option("--registrar-keystore <path>", "Independent registrar keystore for registration pins and approval").option("--registrar-password-env <name>", "Environment variable containing the registrar keystore password").option("--approve", "Approve after rechecking all admission evidence at one finalized head").option("--paid-admission", "Check paid devnet prerequisites after registration; fail closed at an unavailable independent audit gate").option("--session-endpoint <url>", "Authenticated provisioned miner session endpoint for paid admission").option("--token <token>", "Hugging Face bearer token (prefer HF_TOKEN)").option("--json", "Output the identity block as JSON").action(async (opts, command) => {
9247
+ ).option(
9248
+ "--revision <sha>",
9249
+ "Immutable Hugging Face commit (of --source, or of --hf-repo for a local source); first resolution is retained when omitted"
9250
+ ).option("--state-dir <path>", "Private resumable workspace (defaults to XDG_STATE_HOME/flop/model-onboarding)").option("--registrar-keystore <path>", "Independent registrar keystore for registration pins and approval").option("--registrar-password-env <name>", "Environment variable containing the registrar keystore password").option("--approve", "Approve after rechecking all admission evidence at one finalized head").option("--paid-admission", "Check paid devnet prerequisites after registration; fail closed at an unavailable independent audit gate").option("--session-endpoint <url>", "Authenticated provisioned miner session endpoint for paid admission").option("--token <token>", "Hugging Face bearer token (prefer HF_TOKEN)").option(
9251
+ "--min-tokens-per-second <n>",
9252
+ "Fail before any chain write unless the paid completion-token ceiling per channel at the median live calibration cap is at least n; an unknown ceiling fails",
9253
+ parseMinTokensPerSecond
9254
+ ).option("--miner <address>", "Compute the ceiling at this miner's accepted calibration cap instead of the median live one; the best is reported, never gated").option("--json", "Output the identity block as JSON").action(async (opts, command) => {
8976
9255
  let client;
8977
9256
  let registrar;
8978
9257
  let workDir;
@@ -8989,15 +9268,12 @@ function modelOnboardCmd() {
8989
9268
  const local = await exists(opts.source);
8990
9269
  if (local && opts.download) throw new Error("--download is for a Hugging Face source; a local source is always packed");
8991
9270
  const metadataOnly = !local && !opts.download;
8992
- if (metadataOnly && opts.approve) {
8993
- throw new Error("--approve needs the model's DA backup (packed image and file table); rerun with --download to upload it in the same run");
8994
- }
8995
9271
  const binding = {
8996
9272
  source: opts.source,
8997
9273
  revision: opts.revision ?? null,
8998
9274
  network,
8999
- rpcHash: createHash3("sha256").update(getRpc(command)).digest("hex"),
9000
- captureEndpointHash: createHash3("sha256").update(opts.captureEndpoint).digest("hex"),
9275
+ rpcHash: createHash5("sha256").update(getRpc(command)).digest("hex"),
9276
+ captureEndpointHash: createHash5("sha256").update(opts.captureEndpoint).digest("hex"),
9001
9277
  registryModel: opts.registryModel ?? null,
9002
9278
  registryVersion: opts.registryVersion,
9003
9279
  backendModelName: opts.backendModelName ?? null,
@@ -9008,7 +9284,7 @@ function modelOnboardCmd() {
9008
9284
  hfRepo: opts.hfRepo ?? null,
9009
9285
  packContract: metadataOnly ? "content-v1-metadata" : "modelpack-v1-strict",
9010
9286
  paidAdmission: opts.paidAdmission === true,
9011
- sessionEndpointHash: opts.sessionEndpoint ? createHash3("sha256").update(opts.sessionEndpoint).digest("hex") : null
9287
+ sessionEndpointHash: opts.sessionEndpoint ? createHash5("sha256").update(opts.sessionEndpoint).digest("hex") : null
9012
9288
  };
9013
9289
  workDir = onboardingDirectory(binding, opts.stateDir);
9014
9290
  job = await openOnboardingState(workDir, binding);
@@ -9022,17 +9298,32 @@ function modelOnboardCmd() {
9022
9298
  preflight = await sdk2.preflightHuggingFace(hubRepo, { revision: job.state.sourceRevision ?? opts.revision, token: opts.token ?? process.env.HF_TOKEN });
9023
9299
  if (!preflight.configJson) throw new Error("checkpoint has no root config.json; a workload-specific ingestion contract is required");
9024
9300
  job.state.sourceRevision = preflight.revision;
9025
- job.state.configHash = createHash3("sha256").update(preflight.configJson).digest("hex");
9301
+ job.state.configHash = createHash5("sha256").update(preflight.configJson).digest("hex");
9026
9302
  job.state.phase = "preflight";
9027
9303
  await job.save();
9028
9304
  process.stderr.write(`content_root ${preflight.contentRoot}: ${preflight.files.length} files, image ${preflight.imageSize} bytes, ${preflight.daChunks} DA chunk(s) at ${preflight.revision}
9029
9305
  `);
9030
- const candidate = computeChannel4.parseMeteringConfig(preflight.configJson, preflight.hfQuantConfigJson);
9031
- if (computeChannel4.isDenseGdnArch(candidate.arch)) {
9306
+ const candidate = computeChannel5.parseMeteringConfig(preflight.configJson, preflight.hfQuantConfigJson);
9307
+ if (computeChannel5.isDenseGdnArch(candidate.arch)) {
9032
9308
  client = await createClient(getRpc(command), getSigner(command));
9033
9309
  await requireHybridApproval(client.api, candidate.arch);
9034
9310
  }
9035
- } else if (opts.revision) throw new Error("--revision is only for a Hugging Face source");
9311
+ } else if (opts.revision && !opts.hfRepo) throw new Error("--revision is only for a Hugging Face source or a local source's --hf-repo");
9312
+ const checkCeiling = async (load) => {
9313
+ let work;
9314
+ try {
9315
+ work = perTokenWork(computeChannel5.parseMeteringConfig(...await load()));
9316
+ } catch (error) {
9317
+ work = `no metering arch: ${error.message}`;
9318
+ }
9319
+ client ??= await createClient(getRpc(command), getSigner(command));
9320
+ const result = throughputCeiling(work, await chainCapInputsOrReason(client.api, opts.miner), opts.minTokensPerSecond);
9321
+ process.stderr.write(ceilingWarning(result));
9322
+ enforceCeiling(result);
9323
+ return result;
9324
+ };
9325
+ let ceiling;
9326
+ if (preflight) ceiling = await checkCeiling(async () => [preflight.configJson, preflight.hfQuantConfigJson ?? null]);
9036
9327
  let source;
9037
9328
  if (metadataOnly) {
9038
9329
  const modelDir = join5(workDir, "metadata");
@@ -9050,14 +9341,15 @@ function modelOnboardCmd() {
9050
9341
  } else {
9051
9342
  source = await materializeSource(opts.source, sourceDir, opts.token, job.state.sourceRevision, join5(workDir, "download.json"), preflight?.files);
9052
9343
  }
9344
+ ceiling ??= await checkCeiling(async () => [await readFile7(join5(source.modelDir, "config.json")), await readHfQuantConfig(source.modelDir)]);
9053
9345
  let checkpoint;
9054
9346
  let parameterFiles;
9055
9347
  try {
9056
9348
  const raw = await readFile7(join5(source.modelDir, "config.json"), "utf8");
9057
- if (job.state.configHash && createHash3("sha256").update(raw).digest("hex") !== job.state.configHash) throw new Error("downloaded config differs from preflight");
9058
- checkpoint = computeChannel4.parseMeteringConfig(Buffer.from(raw), await readHfQuantConfig(source.modelDir));
9349
+ if (job.state.configHash && createHash5("sha256").update(raw).digest("hex") !== job.state.configHash) throw new Error("downloaded config differs from preflight");
9350
+ checkpoint = computeChannel5.parseMeteringConfig(Buffer.from(raw), await readHfQuantConfig(source.modelDir));
9059
9351
  if (checkpoint.totalParams === null) {
9060
- const configHash = createHash3("sha256").update(raw).digest("hex");
9352
+ const configHash = createHash5("sha256").update(raw).digest("hex");
9061
9353
  const census = metadataOnly ? await censusHeaders(workDir, preflight, await (await import("@flop-labs/sdk")).readSafetensorsHeaders(
9062
9354
  hubRepo,
9063
9355
  preflight.revision,
@@ -9104,11 +9396,11 @@ function modelOnboardCmd() {
9104
9396
  let { mirror, revision } = source;
9105
9397
  if (opts.hfRepo) {
9106
9398
  if (mirror) throw new Error("--hf-repo is for a local --source; a Hugging Face --source registers its own mirror");
9107
- ({ mirror, revision } = await verifiedHuggingFaceMirror(
9108
- opts.hfRepo,
9109
- measured.contentRoot,
9110
- (hf, root) => sdk.verifyMirror(hf, root, { token: opts.token ?? process.env.HF_TOKEN })
9111
- ));
9399
+ const token = opts.token ?? process.env.HF_TOKEN;
9400
+ ({ mirror, revision } = await verifiedHuggingFaceMirror(opts.hfRepo, measured.contentRoot, {
9401
+ preflight: (repo) => sdk.preflightHuggingFace(repo, { revision: opts.revision, token }),
9402
+ verify: (hf, root, at) => sdk.verifyMirror(hf, root, { revision: at, token })
9403
+ }));
9112
9404
  }
9113
9405
  const identity = identityFromPack(
9114
9406
  opts.registryModel ?? source.registryModel,
@@ -9138,10 +9430,15 @@ function modelOnboardCmd() {
9138
9430
  if (job.state.chain && JSON.stringify(job.state.chain) !== JSON.stringify(chain)) throw new Error("onboarding chain/publisher resume conflict");
9139
9431
  job.state.chain = chain;
9140
9432
  await job.save();
9141
- if (computeChannel4.isDenseGdnArch(checkpoint.arch)) await requireHybridApproval(client.api, checkpoint.arch);
9433
+ if (computeChannel5.isDenseGdnArch(checkpoint.arch)) await requireHybridApproval(client.api, checkpoint.arch);
9142
9434
  const submit = (tx) => submitOnboardingTx(client.api, tx, client.signer, job);
9143
9435
  let state = await readState(client.api, sdk, identity.modelHash, precision);
9144
- const checkedParams = computeChannel4.isDenseGdnArch(checkpoint.arch) ? paramsBillion : void 0;
9436
+ if (metadataOnly && opts.approve && (!state.registered || !state.weightsRef)) {
9437
+ throw new Error(
9438
+ `--approve on a metadata run needs the bound DA backup of ${identity.modelHash}; register without --approve, run \`${modelBackupHandoff(identity.modelHash, precision, preflight?.revision)}\` on a box with room for the checkpoint, bind it with \`flop model upload --commitment\` and \`flop model mirrors set --append --source da:<table_ref>\`, then rerun with --approve (or rerun with --download --approve from a fresh --state-dir)`
9439
+ );
9440
+ }
9441
+ const checkedParams = computeChannel5.isDenseGdnArch(checkpoint.arch) ? paramsBillion : void 0;
9145
9442
  planOnboard(state, identity, precision, teePlatforms, checkedParams);
9146
9443
  await validateCapture(opts.captureEndpoint, identity, checkpoint, source.modelDir);
9147
9444
  job.state.phase = "capture-verified";
@@ -9152,21 +9449,46 @@ function modelOnboardCmd() {
9152
9449
  throw new Error("pending registration parameter count differs from the checkpoint census; original transaction retained");
9153
9450
  }
9154
9451
  }
9155
- await recoverOnboardingTx(client.api, job, async (original) => {
9156
- if (!containsApproval(original.method)) return;
9157
- if (!opts.approve) throw new Error("pending approval requires --approve before rebroadcast; signed transaction retained");
9158
- const signer = original.signer.toString();
9159
- const mode = await registryAuthorityMode(client.api, signer, network);
9160
- await requireApprovalEvidence(
9161
- client.api,
9452
+ let daSpotCheck;
9453
+ const approvalEvidence = async (api, signer, root) => {
9454
+ const evidence = await requireApprovalEvidence(
9455
+ api,
9456
+ identity.modelHash,
9457
+ precision,
9458
+ signer,
9459
+ measured.imageSize,
9460
+ checkpoint.arch,
9461
+ root,
9462
+ measured.files
9463
+ );
9464
+ if (!metadataOnly || evidence.approved) return evidence;
9465
+ const token = opts.token ?? process.env.HF_TOKEN;
9466
+ daSpotCheck = await spotCheckImage(measured.files, measured.imageSize, {
9467
+ chunkSize: sdk.DA_CHUNK_SIZE,
9468
+ readChunk: (j) => daRetrieveChunk(opts.daValidator, getRpc(command), evidence.weights, j),
9469
+ readHubRange: (path5, start, end, size) => readHuggingFaceRange(hubRepo, preflight.revision, path5, start, end, size, token)
9470
+ });
9471
+ const again = await requireApprovalEvidence(
9472
+ api,
9162
9473
  identity.modelHash,
9163
9474
  precision,
9164
9475
  signer,
9165
9476
  measured.imageSize,
9166
9477
  checkpoint.arch,
9167
- mode === "root",
9478
+ root,
9168
9479
  measured.files
9169
9480
  );
9481
+ if (again.weights !== evidence.weights || again.table !== evidence.table) {
9482
+ throw new Error("the bound DA image or file table changed during the spot check; nothing was submitted");
9483
+ }
9484
+ return again;
9485
+ };
9486
+ await recoverOnboardingTx(client.api, job, async (original) => {
9487
+ if (!containsApproval(original.method)) return;
9488
+ if (!opts.approve) throw new Error("pending approval requires --approve before rebroadcast; signed transaction retained");
9489
+ const signer = original.signer.toString();
9490
+ const mode = await registryAuthorityMode(client.api, signer, network);
9491
+ await approvalEvidence(client.api, signer, mode === "root");
9170
9492
  });
9171
9493
  state = await readState(client.api, sdk, identity.modelHash, precision);
9172
9494
  const plan = planOnboard(state, identity, precision, teePlatforms, checkedParams);
@@ -9298,7 +9620,7 @@ function modelOnboardCmd() {
9298
9620
  const view = await authority.api.at(await authority.api.rpc.chain.getFinalizedHead());
9299
9621
  const raw = await view.query.modelRegistry.admissionRequirements(identity.modelHash);
9300
9622
  const requirements = optionValue(raw);
9301
- const expectedHybrid = computeChannel4.isDenseGdnArch(checkpoint.arch);
9623
+ const expectedHybrid = computeChannel5.isDenseGdnArch(checkpoint.arch);
9302
9624
  if (requirements) {
9303
9625
  const metadata = requirements.toJSON?.() ?? requirements;
9304
9626
  if (BigInt(metadata.weightsSizeBytes ?? metadata.weights_size_bytes) !== BigInt(measured.imageSize) || (metadata.requiresHybridProfile ?? metadata.requires_hybrid_profile) !== expectedHybrid) {
@@ -9313,16 +9635,7 @@ function modelOnboardCmd() {
9313
9635
  }
9314
9636
  }
9315
9637
  if (opts.approve) {
9316
- const evidence = await requireApprovalEvidence(
9317
- authority.api,
9318
- identity.modelHash,
9319
- precision,
9320
- authority.signer.address,
9321
- measured.imageSize,
9322
- checkpoint.arch,
9323
- mode === "root",
9324
- measured.files
9325
- );
9638
+ const evidence = await approvalEvidence(authority.api, authority.signer.address, mode === "root");
9326
9639
  if (!evidence.approved) {
9327
9640
  const call = authority.api.tx.modelRegistry.approveModel(identity.modelHash, authority.signer.address);
9328
9641
  await submitOnboardingTx(authority.api, registryAuthorityCall(authority.api, call, mode), authority.signer, job);
@@ -9365,7 +9678,9 @@ then bind the DataRef it prints. Rerunning this command with --download packs th
9365
9678
  ...identity,
9366
9679
  registered: state.registered,
9367
9680
  approved: state.approved === true,
9368
- ...metadataOnly ? { contentRoot: measured.contentRoot, imageSize: measured.imageSize, daBackup: state.weightsRef ? "present" : "pending" } : {}
9681
+ throughputCeiling: ceiling,
9682
+ ...metadataOnly ? { contentRoot: measured.contentRoot, imageSize: measured.imageSize, daBackup: state.weightsRef ? "present" : "pending" } : {},
9683
+ ...daSpotCheck ? { daSpotCheck } : {}
9369
9684
  },
9370
9685
  getJson(command)
9371
9686
  );
@@ -9384,7 +9699,10 @@ then bind the DataRef it prints. Rerunning this command with --download packs th
9384
9699
  // src/commands/model-upload.ts
9385
9700
  var PRECISIONS2 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
9386
9701
  function modelUploadCmd() {
9387
- return new Command30("upload").description("Push verified weights to validator DA, register the lease, and bind the model").requiredOption("--model-hash <hex>", "Registered model hash").option("--path <path>", "Local weight file or directory (packed and streamed from disk)").option("--source <source>", "Remote source: hf:<repo>, modelscope:<repo>, https://..., da:<commitment>").addOption(new Option2("--precision <precision>", "Measured-root precision").choices(PRECISIONS2).default("FP16")).option("--da-endpoint <url>", "DA blob gateway for a da:<commitment> source").option("--da-validator <path>", "da-validator executable", "da-validator").option("--token <token>", "HF/ModelScope bearer token (prefer HF_TOKEN)").option("--commitment <hex>", "Existing DA commitment (commitment-only recovery mode)").option("--size <bytes>", "Existing DA blob size (required with --commitment)").option("--json", "Output as JSON").action(async (opts, command) => {
9702
+ return new Command30("upload").description("Push verified weights to validator DA, register the lease, and bind the model").requiredOption("--model-hash <hex>", "Registered model hash").option("--path <path>", "Local weight file or directory (packed and streamed from disk)").option("--source <source>", "Remote source: hf:<repo>, modelscope:<repo>, https://..., da:<commitment>").option(
9703
+ "--revision <sha>",
9704
+ "Immutable 40-hex commit for an hf: --source; when omitted, main is used only if its sha256 table reproduces the registered content-v1 root"
9705
+ ).addOption(new Option2("--precision <precision>", "Measured-root precision").choices(PRECISIONS2).default("FP16")).option("--da-endpoint <url>", "DA blob gateway for a da:<commitment> source").option("--da-validator <path>", "da-validator executable", "da-validator").option("--token <token>", "HF/ModelScope bearer token (prefer HF_TOKEN)").option("--commitment <hex>", "Existing DA commitment (commitment-only recovery mode)").option("--size <bytes>", "Existing DA blob size (required with --commitment)").option("--json", "Output as JSON").action(async (opts, command) => {
9388
9706
  let client;
9389
9707
  let tempDir;
9390
9708
  try {
@@ -9394,6 +9712,9 @@ function modelUploadCmd() {
9394
9712
  }
9395
9713
  if (opts.commitment && !opts.size) throw new Error("--size is required with --commitment");
9396
9714
  if (opts.size && !opts.commitment) throw new Error("--commitment is required with --size");
9715
+ const revision = parseRevision(opts.revision);
9716
+ const remote = opts.source ? parseModelSource(opts.source) : void 0;
9717
+ if (revision && remote?.kind !== "huggingface") throw new Error("--revision is only for an hf: --source");
9397
9718
  const sdk = await import("@flop-labs/sdk");
9398
9719
  const rpc = getRpc(command);
9399
9720
  client = await createClient(rpc, getSigner(command));
@@ -9406,8 +9727,10 @@ function modelUploadCmd() {
9406
9727
  let imagePath;
9407
9728
  let custody;
9408
9729
  if (!commitment) {
9409
- tempDir = await mkdtemp(join6(tmpdir(), "flop-model-upload-"));
9730
+ tempDir = await mkdtemp2(join6(tmpdir2(), "flop-model-upload-"));
9410
9731
  imagePath = join6(tempDir, "weights.img");
9732
+ const expected = await sdk.readMeasuredRoot(client.api, opts.modelHash, opts.precision);
9733
+ if (!expected) throw new Error(`no measured root for ${opts.modelHash} at ${opts.precision}`);
9411
9734
  if (opts.path) {
9412
9735
  let modelDir = resolve8(opts.path);
9413
9736
  const stat2 = await lstat7(modelDir);
@@ -9418,8 +9741,18 @@ function modelUploadCmd() {
9418
9741
  await copyFile(resolve8(opts.path), join6(modelDir, basename2(opts.path)));
9419
9742
  }
9420
9743
  measured = await packModel(modelDir, imagePath, join6(tempDir, "manifest.json"));
9744
+ } else if (remote.kind === "huggingface" && !remote.path) {
9745
+ const modelDir = join6(tempDir, "model");
9746
+ await mkdir11(modelDir);
9747
+ await downloadPinnedHuggingFace(remote.repo, modelDir, expected, {
9748
+ revision,
9749
+ token: opts.token ?? process.env.HF_TOKEN
9750
+ });
9751
+ const roots = await sdk.measureModelDir(modelDir);
9752
+ measured = { content_root: roots.contentRoot, verity_root: roots.verityRoot };
9753
+ await pipeline2(sdk.packedImageStream(modelDir, roots.files), createWriteStream(imagePath));
9421
9754
  } else {
9422
- const files = (await sdk.fetchWeights(parseModelSource(opts.source), {
9755
+ const files = (await sdk.fetchWeights(remote, {
9423
9756
  token: opts.token ?? process.env.HF_TOKEN,
9424
9757
  daSource: daHttpSource(opts.daEndpoint)
9425
9758
  })).files;
@@ -9427,8 +9760,6 @@ function modelUploadCmd() {
9427
9760
  measured = { content_root: roots.contentRoot, verity_root: roots.verityRoot };
9428
9761
  await writeFile9(imagePath, sdk.buildImage(files));
9429
9762
  }
9430
- const expected = await sdk.readMeasuredRoot(client.api, opts.modelHash, opts.precision);
9431
- if (!expected) throw new Error(`no measured root for ${opts.modelHash} at ${opts.precision}`);
9432
9763
  const scheme = sdk.rootScheme({ contentRoot: measured.content_root, verityRoot: measured.verity_root }, expected);
9433
9764
  if (!scheme) {
9434
9765
  throw new Error(`source content_root ${measured.content_root} and verity_root ${measured.verity_root} != registered ${expected}`);
@@ -9480,7 +9811,7 @@ function modelUploadCmd() {
9480
9811
  next: "rerun the same command to resume the DA push; to abandon a registered blob, run the printed `flop da withdraw` recovery"
9481
9812
  });
9482
9813
  } finally {
9483
- if (tempDir) await rm3(tempDir, { recursive: true, force: true });
9814
+ if (tempDir) await rm4(tempDir, { recursive: true, force: true });
9484
9815
  await client?.disconnect();
9485
9816
  }
9486
9817
  });
@@ -9525,12 +9856,187 @@ function modelAuditCmd() {
9525
9856
  });
9526
9857
  }
9527
9858
 
9528
- // src/commands/model-preflight.ts
9859
+ // src/commands/model-mirrors.ts
9529
9860
  import { Command as Command32 } from "commander";
9861
+ var MIRROR_URI_MAX_BYTES = 512;
9862
+ var MAX_MODEL_MIRRORS = 8;
9863
+ var REPO = /^[^/\s]+\/[^/\s]+$/;
9864
+ function boundedUri(value, what) {
9865
+ if (Buffer.byteLength(value, "utf8") > MIRROR_URI_MAX_BYTES) {
9866
+ throw new Error(`${what} exceeds the registry's ${MIRROR_URI_MAX_BYTES}-byte bound`);
9867
+ }
9868
+ return value;
9869
+ }
9870
+ function parseMirrorSource(raw) {
9871
+ if (raw.startsWith("https://")) {
9872
+ let url;
9873
+ try {
9874
+ url = new URL(raw);
9875
+ } catch {
9876
+ throw new Error(`invalid https mirror URL: ${raw}`);
9877
+ }
9878
+ if (url.protocol !== "https:" || !url.hostname) throw new Error(`invalid https mirror URL: ${raw}`);
9879
+ if (url.username || url.password) throw new Error("an https mirror URL must not carry credentials");
9880
+ return { kind: "https", url: boundedUri(raw, "https mirror URL") };
9881
+ }
9882
+ for (const [prefix, kind] of [["hf:", "huggingface"], ["huggingface:", "huggingface"], ["modelscope:", "modelscope"]]) {
9883
+ if (raw.startsWith(prefix)) {
9884
+ const repo = raw.slice(prefix.length);
9885
+ if (!REPO.test(repo)) throw new Error(`${kind} mirror must be ${prefix}<owner>/<model> (got ${raw})`);
9886
+ return { kind, repo: boundedUri(repo, `${kind} repo id`) };
9887
+ }
9888
+ }
9889
+ if (raw.startsWith("da:")) return { kind: "da", commitment: normalizeHash(raw.slice(3), "DA mirror commitment") };
9890
+ throw new Error(`unsupported mirror "${raw}"; use https://..., hf:<owner>/<model>, modelscope:<owner>/<model> or da:<commitment>`);
9891
+ }
9892
+ function mirrorKey(entry) {
9893
+ if (entry.kind === "https") return `https:${entry.url}`;
9894
+ if (entry.kind === "da") return `da:${entry.commitment}@${entry.providerId ?? "?"}`;
9895
+ return `${entry.kind}:${entry.repo}`;
9896
+ }
9897
+ function chainModelSource(entry) {
9898
+ switch (entry.kind) {
9899
+ case "https":
9900
+ return { Https: entry.url };
9901
+ case "huggingface":
9902
+ return { HuggingFace: entry.repo };
9903
+ case "modelscope":
9904
+ return { ModelScope: entry.repo };
9905
+ case "da":
9906
+ if (entry.providerId === void 0 || entry.retention === void 0) {
9907
+ throw new Error(`DA mirror ${entry.commitment} has no resolved provider or retention`);
9908
+ }
9909
+ return { DaLayer: { commitment: entry.commitment, provider_id: entry.providerId, retention: entry.retention } };
9910
+ }
9911
+ }
9912
+ function decodeRegisteredMirror(json) {
9913
+ const text4 = (hex5) => {
9914
+ const value = String(hex5);
9915
+ return /^0x([0-9a-f]{2})*$/i.test(value) ? Buffer.from(value.slice(2), "hex").toString("utf8") : value;
9916
+ };
9917
+ if (json?.https !== void 0) return { kind: "https", url: text4(json.https) };
9918
+ if (json?.huggingFace !== void 0) return { kind: "huggingface", repo: text4(json.huggingFace) };
9919
+ if (json?.modelScope !== void 0) return { kind: "modelscope", repo: text4(json.modelScope) };
9920
+ if (json?.daLayer !== void 0) {
9921
+ const ref = json.daLayer;
9922
+ return {
9923
+ kind: "da",
9924
+ commitment: normalizeHash(String(ref.commitment), "registered DA mirror commitment"),
9925
+ providerId: Number(ref.providerId ?? ref.provider_id),
9926
+ retention: String(ref.retention)
9927
+ };
9928
+ }
9929
+ throw new Error(`registered mirror has an unknown shape: ${JSON.stringify(json)}`);
9930
+ }
9931
+ function planMirrorList(requested, opts) {
9932
+ if (opts.clear) {
9933
+ if (requested.length > 0 || opts.append) throw new Error("--clear takes no --source and no --append");
9934
+ return { sources: [], added: [] };
9935
+ }
9936
+ if (requested.length === 0) throw new Error("pass at least one --source, or --clear to remove every mirror");
9937
+ const seen = /* @__PURE__ */ new Set();
9938
+ for (const entry of requested) {
9939
+ const key = mirrorKey(entry);
9940
+ if (seen.has(key)) throw new Error(`duplicate mirror ${key}`);
9941
+ seen.add(key);
9942
+ }
9943
+ const registered = opts.append ? opts.registered ?? [] : [];
9944
+ const kept = new Set(registered.map(mirrorKey));
9945
+ const added = requested.filter((entry) => !kept.has(mirrorKey(entry)));
9946
+ const sources = [...registered, ...added];
9947
+ if (sources.length > MAX_MODEL_MIRRORS) {
9948
+ throw new Error(`${sources.length} mirrors exceed the registry's limit of ${MAX_MODEL_MIRRORS}`);
9949
+ }
9950
+ return { sources, added };
9951
+ }
9952
+ function display(entry) {
9953
+ if (entry.kind === "da") return { kind: "da", commitment: entry.commitment, provider_id: entry.providerId, retention: entry.retention };
9954
+ return entry.kind === "https" ? { kind: "https", url: entry.url } : { kind: entry.kind, repo: entry.repo };
9955
+ }
9956
+ var collect = (value, previous) => [...previous, value];
9957
+ function modelMirrorsSetCmd() {
9958
+ return new Command32("set").description(
9959
+ "Replace a registered model's mirror hint list (modelRegistry.setModelMirrors). Only the model's publisher may sign it: the call is signed-origin and checks publisher == signer, so neither a registrar nor Root (sudo) can set mirrors. Mirrors are hints: a miner serves the model only if the bytes it fetches reproduce the registered root"
9960
+ ).requiredOption("--model-hash <hex>", "Registered model hash").option(
9961
+ "--source <mirror>",
9962
+ `Mirror, repeatable (at most ${MAX_MODEL_MIRRORS} in the final list): https://<url>, hf:<owner>/<model>, modelscope:<owner>/<model>, or da:<commitment> of a registered Leased DA blob (its provider is read from chain)`,
9963
+ collect,
9964
+ []
9965
+ ).option(
9966
+ "--append",
9967
+ "Keep the finalized registered mirrors verbatim and add the new ones after them (a DA entry counts as present only at the same commitment and provider). The list is read at finality and then replaced, so a concurrent mirror change by the same publisher between the two is overwritten"
9968
+ ).option("--clear", "Remove every mirror (an empty list)").option("--dry-run", "Validate and print the encoded call without signing or submitting it").option("--json", "Output as JSON").action(async (opts, command) => {
9969
+ let client;
9970
+ try {
9971
+ const modelHash = normalizeHash(opts.modelHash, "--model-hash");
9972
+ const requested = opts.source.map(parseMirrorSource);
9973
+ planMirrorList(requested, { clear: opts.clear, append: opts.append, registered: [] });
9974
+ client = await createClient(getRpc(command), getSigner(command));
9975
+ const api = client.api;
9976
+ const head = await api.rpc.chain.getFinalizedHead();
9977
+ const [view, header] = await Promise.all([api.at(head), api.rpc.chain.getHeader(head)]);
9978
+ const rawModel = await view.query.modelRegistry.models(modelHash);
9979
+ if (!rawModel?.isSome) throw new Error(`model not registered at finality: ${modelHash}`);
9980
+ const publisher = rawModel.unwrap().publisher;
9981
+ if (!sameAccount(publisher, client.signer.address)) {
9982
+ throw new Error(`only the model publisher ${publisher.toString()} may set its mirrors; the signer is ${client.signer.address}`);
9983
+ }
9984
+ const finalized = { query: view.query, rpc: { chain: { getHeader: async () => header } } };
9985
+ for (const entry of requested) {
9986
+ if (entry.kind !== "da") continue;
9987
+ const blob = await view.query.daRegistry.blobs(entry.commitment);
9988
+ if (!blob?.isSome) throw new Error(`DA mirror ${entry.commitment} is not a registered DA blob at finality`);
9989
+ const size = blob.unwrap().toJSON().sizeBytes;
9990
+ const ref = await existingLeasedRef(finalized, entry.commitment, Number(size));
9991
+ if (!ref) throw new Error(`DA mirror ${entry.commitment} is not a registered DA blob at finality`);
9992
+ entry.providerId = ref.providerId;
9993
+ entry.retention = ref.retention;
9994
+ }
9995
+ const raw = await view.query.modelRegistry.modelMirrors(modelHash);
9996
+ const registered = raw?.isSome ? raw.unwrap().toJSON().map(decodeRegisteredMirror) : [];
9997
+ const { sources, added } = planMirrorList(requested, { clear: opts.clear, append: opts.append, registered });
9998
+ const result = {
9999
+ model_hash: modelHash,
10000
+ publisher: publisher.toString(),
10001
+ registered: registered.map(display),
10002
+ sources: sources.map(display),
10003
+ added: added.map(display),
10004
+ finalized_block: header.number.toString()
10005
+ };
10006
+ if (opts.append && added.length === 0) {
10007
+ print({ ...result, unchanged: true }, getJson(command));
10008
+ return;
10009
+ }
10010
+ const tx = api.tx.modelRegistry.setModelMirrors(modelHash, sources.map(chainModelSource));
10011
+ if (opts.dryRun) {
10012
+ print({ ...result, dry_run: true, call: tx.method.toHex(), next: "rerun without --dry-run to sign and submit" }, getJson(command));
10013
+ return;
10014
+ }
10015
+ const sdk = await import("@flop-labs/sdk");
10016
+ const submitted = await sdk.watchTransaction(tx, client.signer, { api });
10017
+ print({ ...result, tx_hash: (submitted.txHash ?? tx.hash).toHex() }, getJson(command));
10018
+ } catch (error) {
10019
+ commandError(error, { next: "fix the reported mirror or signer and rerun; after a submit timeout, rerun the same command with --dry-run and compare its `registered` (the finalized list) with `sources` before resubmitting" });
10020
+ } finally {
10021
+ await client?.disconnect();
10022
+ }
10023
+ });
10024
+ }
10025
+ function modelMirrorsCmd() {
10026
+ return new Command32("mirrors").description("Model mirror hints (publisher-signed)").addCommand(modelMirrorsSetCmd());
10027
+ }
10028
+
10029
+ // src/commands/model-preflight.ts
10030
+ import { Command as Command33 } from "commander";
9530
10031
  function modelPreflightCmd() {
9531
- return new Command32("preflight").description(
10032
+ return new Command33("preflight").description(
9532
10033
  "Print a Hugging Face checkpoint's content_root (content-v1 model_hash), packed image size, DA chunk count and stored tensor elements from hub metadata and Range-read safetensors headers; no weights are downloaded"
9533
- ).requiredOption("--source <hf-id>", "Hugging Face id (owner/model or hf:owner/model)").option("--revision <sha>", "Immutable 40-hex commit; main is resolved to one when omitted").option("--token <token>", "Hugging Face bearer token (prefer HF_TOKEN)").option("--json", "Output as JSON").action(async (opts, command) => {
10034
+ ).requiredOption("--source <hf-id>", "Hugging Face id (owner/model or hf:owner/model)").option("--revision <sha>", "Immutable 40-hex commit; main is resolved to one when omitted").option("--token <token>", "Hugging Face bearer token (prefer HF_TOKEN)").option(
10035
+ "--min-tokens-per-second <n>",
10036
+ "Fail unless the paid completion-token ceiling per channel at the median live calibration cap (or --miner's) is at least n; an unknown ceiling fails. The chain is read only with an explicit --rpc",
10037
+ parseMinTokensPerSecond
10038
+ ).option("--miner <address>", "Compute the ceiling at this miner's accepted calibration cap instead of the median live one; the best is reported, never gated").option("--json", "Output as JSON").action(async (opts, command) => {
10039
+ let client;
9534
10040
  try {
9535
10041
  if (opts.revision && !/^[0-9a-f]{40}$/.test(opts.revision)) throw new Error("--revision must be an immutable 40-hex commit");
9536
10042
  const repo = opts.source.startsWith("hf:") ? opts.source.slice(3) : opts.source;
@@ -9538,6 +10044,25 @@ function modelPreflightCmd() {
9538
10044
  const hub = { revision: opts.revision, token: opts.token ?? process.env.HF_TOKEN };
9539
10045
  const preflight = await sdk.preflightHuggingFace(repo, hub);
9540
10046
  const headers = await sdk.readSafetensorsHeaders(repo, preflight.revision, preflight.files, hub);
10047
+ let work = "no root config.json";
10048
+ if (preflight.configJson) {
10049
+ try {
10050
+ work = perTokenWork(sdk.computeChannel.parseMeteringConfig(preflight.configJson, preflight.hfQuantConfigJson));
10051
+ } catch (error) {
10052
+ work = `no metering arch: ${error.message}`;
10053
+ }
10054
+ }
10055
+ let chain = null;
10056
+ if (resolveNetwork(command).rpcOverridden) {
10057
+ try {
10058
+ client = await createReadOnlyClient(getRpc(command));
10059
+ chain = await chainCapInputsOrReason(client.api, opts.miner);
10060
+ } catch (error) {
10061
+ chain = `chain connection failed: ${error.message}`;
10062
+ }
10063
+ }
10064
+ const ceiling = throughputCeiling(work, chain, opts.minTokensPerSecond);
10065
+ process.stderr.write(ceilingWarning(ceiling));
9541
10066
  print({
9542
10067
  repo,
9543
10068
  revision: preflight.revision,
@@ -9548,18 +10073,22 @@ function modelPreflightCmd() {
9548
10073
  fetched_metadata_files: preflight.metadataFiles.length,
9549
10074
  safetensors_shards: headers.length,
9550
10075
  // Not a parameter count for packed sub-byte weights, and scale tensors are included.
9551
- stored_tensor_elements: sdk.storedTensorElements(headers).toString()
10076
+ stored_tensor_elements: sdk.storedTensorElements(headers).toString(),
10077
+ throughput_ceiling: snakeCeiling(ceiling)
9552
10078
  }, getJson(command));
10079
+ enforceCeiling(ceiling);
9553
10080
  } catch (error) {
9554
10081
  commandError(error);
10082
+ } finally {
10083
+ await client?.disconnect();
9555
10084
  }
9556
10085
  });
9557
10086
  }
9558
10087
 
9559
10088
  // src/commands/model-backup.ts
9560
- import { Command as Command33, Option as Option3 } from "commander";
9561
- import { createHash as createHash4 } from "node:crypto";
9562
- import { mkdir as mkdir12, rm as rm4, stat, writeFile as writeFile10 } from "node:fs/promises";
10089
+ import { Command as Command34, Option as Option3 } from "commander";
10090
+ import { createHash as createHash6 } from "node:crypto";
10091
+ import { mkdir as mkdir12, rm as rm5, stat, writeFile as writeFile10 } from "node:fs/promises";
9563
10092
  import { dirname as dirname6, join as join8, resolve as resolve9 } from "node:path";
9564
10093
  var PRECISIONS3 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
9565
10094
  async function finalizedLeasedRef2(api, commitment, size) {
@@ -9578,7 +10107,7 @@ async function awaitFinalizedLeasedRef(api, commitment, size) {
9578
10107
  }
9579
10108
  function downloadStateDir(binding, override) {
9580
10109
  if (override) return resolve9(override);
9581
- const key = createHash4("sha256").update(JSON.stringify(binding)).digest("hex");
10110
+ const key = createHash6("sha256").update(JSON.stringify(binding)).digest("hex");
9582
10111
  return join8(dirname6(binding.modelDir), `.flop-backup-state-${key.slice(0, 16)}`);
9583
10112
  }
9584
10113
  async function assertSameFilesystem(stateDir, modelDir) {
@@ -9597,7 +10126,7 @@ function registeredHubRepo(mirrors) {
9597
10126
  return repos[0];
9598
10127
  }
9599
10128
  function modelBackupCmd() {
9600
- return new Command33("backup").description(
10129
+ return new Command34("backup").description(
9601
10130
  "Stream a registered content-v1 checkpoint from Hugging Face into validator DA under this box's DA key, without ever writing the packed image out, and print the DataRefs its publisher then binds"
9602
10131
  ).argument("<model_hash>", "Registered model hash (0x-hex)").requiredOption("--model-dir <path>", "Directory the checkpoint streams into (needs room for it)").option("--source <hf-id>", "Hugging Face id (owner/model or hf:owner/model); the model's registered mirror when omitted").option("--revision <sha>", "Immutable 40-hex commit; main is resolved to one when omitted").addOption(new Option3("--precision <precision>", "Measured-root precision").choices(PRECISIONS3).default("FP16")).option("--da-validator <path>", "da-validator executable", "da-validator").option("--token <token>", "Hugging Face bearer token (prefer HF_TOKEN)").option("--state-dir <path>", "Download resume receipts, on the checkpoint's filesystem but outside its tree (defaults to beside --model-dir)").option("--dry-run", "Print the plan and the DA deposit it would lock, and stop before any transfer").option("--json", "Output as JSON").action(async (modelHash, opts, command) => {
9603
10132
  let client;
@@ -9713,7 +10242,7 @@ function modelBackupCmd() {
9713
10242
  table_register_tx: table2.registerTx,
9714
10243
  // The binding is the model publisher's own transaction: this box holds no
9715
10244
  // model-registry authority, and `set_model_weights_ref` takes any Leased ref from it.
9716
- next: `the model publisher binds the backup with \`flop model upload --model-hash ${modelHash} --commitment ${weights.ref.commitment} --size ${imageSize}\`, then advertises the file table as a da:${table2.ref.commitment} mirror with \`modelRegistry.setModelMirrors\``,
10245
+ next: `the model publisher binds the backup with \`flop model upload --model-hash ${modelHash} --commitment ${weights.ref.commitment} --size ${imageSize}\`, then advertises the file table with \`flop model mirrors set --model-hash ${modelHash} --append --source da:${table2.ref.commitment}\``,
9717
10246
  recovery: `flop da withdraw --commitment ${weights.ref.commitment} (and ${table2.ref.commitment}) refunds both deposits while nothing pins them`
9718
10247
  }, getJson(command));
9719
10248
  } catch (error) {
@@ -9721,16 +10250,16 @@ function modelBackupCmd() {
9721
10250
  next: "rerun the same command to resume the download and the DA push; to abandon a registered blob, run the printed `flop da withdraw` recovery"
9722
10251
  });
9723
10252
  } finally {
9724
- if (workDir) await rm4(workDir, { recursive: true, force: true });
10253
+ if (workDir) await rm5(workDir, { recursive: true, force: true });
9725
10254
  await client?.disconnect();
9726
10255
  }
9727
10256
  });
9728
10257
  }
9729
10258
 
9730
10259
  // src/commands/model-list.ts
9731
- import { Command as Command34 } from "commander";
10260
+ import { Command as Command35 } from "commander";
9732
10261
  function modelListCmd() {
9733
- return new Command34("list").description("List models registered on the selected FLOP chain").option("--name <text>", "Filter model names").option("--limit <n>", "Maximum rows returned", "100").option("--json", "Output as JSON").action(async (opts, command) => {
10262
+ return new Command35("list").description("List models registered on the selected FLOP chain").option("--name <text>", "Filter model names").option("--limit <n>", "Maximum rows returned", "100").option("--json", "Output as JSON").action(async (opts, command) => {
9734
10263
  let client;
9735
10264
  try {
9736
10265
  const sdk = await import("@flop-labs/sdk");
@@ -9760,9 +10289,9 @@ function modelListCmd() {
9760
10289
  }
9761
10290
 
9762
10291
  // src/commands/model-get.ts
9763
- import { Command as Command35 } from "commander";
10292
+ import { Command as Command36 } from "commander";
9764
10293
  function modelGetCmd() {
9765
- return new Command35("get").description("Get a registered model by hash or exact name").argument("<identifier>", "Model hash or name").option("--precision <precision>", "Measured-root precision", "FP16").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
10294
+ return new Command36("get").description("Get a registered model by hash or exact name").argument("<identifier>", "Model hash or name").option("--precision <precision>", "Measured-root precision", "FP16").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
9766
10295
  let client;
9767
10296
  try {
9768
10297
  const sdk = await import("@flop-labs/sdk");
@@ -9793,12 +10322,25 @@ function modelGetCmd() {
9793
10322
  }
9794
10323
 
9795
10324
  // src/commands/model-fetch.ts
9796
- import { Command as Command36, Option as Option4 } from "commander";
10325
+ import { Command as Command37, Option as Option4 } from "commander";
10326
+ import { mkdir as mkdir13, mkdtemp as mkdtemp3, readdir as readdir2, rename as rename8, rm as rm6, rmdir } from "node:fs/promises";
10327
+ import { basename as basename3, dirname as dirname7, join as join9, resolve as resolve10 } from "node:path";
9797
10328
  var PRECISIONS4 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
10329
+ async function requireEmptyOrAbsent(dir) {
10330
+ try {
10331
+ if ((await readdir2(dir)).length > 0) throw new Error(`--output-dir ${dir} is not empty; a Hugging Face checkpoint is written only into an empty or new directory`);
10332
+ } catch (error) {
10333
+ if (error.code !== "ENOENT") throw error;
10334
+ }
10335
+ }
9798
10336
  function modelFetchCmd() {
9799
- return new Command36("fetch").description("Fetch model weights and accept only a registered dm-verity root").argument("<identifier>", "Registered model hash or exact name").addOption(new Option4("--precision <precision>", "Weights precision").choices(PRECISIONS4).default("FP16")).option("--source <source...>", "Override mirrors: hf:<repo>, modelscope:<repo>, https://..., da:<commitment>").option("--da-endpoint <url>", "DA blob gateway exposing GET /blob/<commitment>").option("--output-dir <path>", "Destination directory", "./flop-model").option("--token <token>", "HF/ModelScope bearer token (prefer HF_TOKEN)").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
10337
+ return new Command37("fetch").description("Fetch model weights and accept only a registered dm-verity root").argument("<identifier>", "Registered model hash or exact name").addOption(new Option4("--precision <precision>", "Weights precision").choices(PRECISIONS4).default("FP16")).option("--source <source...>", "Override mirrors: hf:<repo>, modelscope:<repo>, https://..., da:<commitment>").option(
10338
+ "--revision <sha>",
10339
+ "Immutable 40-hex Hugging Face commit for hf: mirrors; when omitted, main is used only if its sha256 table reproduces the registered content-v1 root"
10340
+ ).option("--da-endpoint <url>", "DA blob gateway exposing GET /blob/<commitment>").option("--output-dir <path>", "Destination directory", "./flop-model").option("--token <token>", "HF/ModelScope bearer token (prefer HF_TOKEN)").option("--json", "Output as JSON").action(async (identifier, opts, command) => {
9800
10341
  let client;
9801
10342
  try {
10343
+ const revision = parseRevision(opts.revision);
9802
10344
  const sdk = await import("@flop-labs/sdk");
9803
10345
  client = await createClient(getRpc(command));
9804
10346
  const registry = new sdk.OnChainModelRegistry(client.api);
@@ -9810,30 +10352,56 @@ function modelFetchCmd() {
9810
10352
  }
9811
10353
  const sources = opts.source?.map(parseModelSource) ?? await sdk.readModelMirrors(client.api, model.hash);
9812
10354
  if (sources.length === 0) throw new Error(`no mirrors registered for model: ${model.hash}`);
10355
+ const hub = sources.slice(0, 16).some((s) => s.kind === "huggingface");
10356
+ if (revision && !hub) throw new Error("--revision is only for a Hugging Face source");
10357
+ const outputDir = resolve10(opts.outputDir);
10358
+ const token = opts.token ?? process.env.HF_TOKEN;
9813
10359
  const attempts = [];
9814
10360
  for (const source of sources.slice(0, 16)) {
10361
+ let staged;
9815
10362
  try {
9816
- const fetched = await sdk.fetchWeights(source, {
9817
- token: opts.token ?? process.env.HF_TOKEN,
9818
- daSource: daHttpSource(opts.daEndpoint)
9819
- }, expected);
9820
- const result = sdk.verifyModelWeights(fetched.files, expected);
10363
+ let result;
10364
+ let write;
10365
+ let at;
10366
+ if (source.kind === "huggingface" && !source.path) {
10367
+ await requireEmptyOrAbsent(outputDir);
10368
+ await mkdir13(dirname7(outputDir), { recursive: true });
10369
+ const dir = await mkdtemp3(join9(dirname7(outputDir), `.${basename3(outputDir)}.fetch-`));
10370
+ staged = dir;
10371
+ const download = await downloadPinnedHuggingFace(source.repo, dir, expected, { revision, token });
10372
+ at = download.revision;
10373
+ result = await sdk.verifyModelDir(dir, expected);
10374
+ write = async () => {
10375
+ await rmdir(outputDir).catch((error) => {
10376
+ if (error.code !== "ENOENT") throw error;
10377
+ });
10378
+ await rename8(dir, outputDir);
10379
+ staged = void 0;
10380
+ return download.paths.map((p) => join9(outputDir, ...p.split("/")));
10381
+ };
10382
+ } else {
10383
+ const fetched = await sdk.fetchWeights(source, { token, daSource: daHttpSource(opts.daEndpoint) }, expected);
10384
+ result = sdk.verifyModelWeights(fetched.files, expected);
10385
+ write = () => writeModelFiles(opts.outputDir, fetched.files);
10386
+ }
9821
10387
  const verified = result.scheme === "content-v1" ? result.computedContentRoot : result.computedVerityRoot;
9822
10388
  attempts.push({
9823
10389
  source,
10390
+ ...at ? { revision: at } : {},
9824
10391
  ok: result.ok,
9825
10392
  scheme: result.scheme,
9826
10393
  computed_content_root: result.computedContentRoot,
9827
10394
  computed_verity_root: result.computedVerityRoot
9828
10395
  });
9829
10396
  if (!result.ok) continue;
9830
- const files = await writeModelFiles(opts.outputDir, fetched.files);
10397
+ const files = await write();
9831
10398
  print({
9832
10399
  model_hash: model.hash,
9833
10400
  precision: opts.precision,
9834
10401
  verified_root: verified,
9835
10402
  scheme: result.scheme,
9836
10403
  source,
10404
+ ...at ? { revision: at } : {},
9837
10405
  output_dir: opts.outputDir,
9838
10406
  file_count: files.length,
9839
10407
  files: files.slice(0, 20),
@@ -9843,6 +10411,8 @@ function modelFetchCmd() {
9843
10411
  return;
9844
10412
  } catch (error) {
9845
10413
  attempts.push({ source, ok: false, error: error instanceof Error ? error.message : String(error) });
10414
+ } finally {
10415
+ if (staged) await rm6(staged, { recursive: true, force: true });
9846
10416
  }
9847
10417
  }
9848
10418
  throw new Error(`no mirror reproduced registered root ${expected}: ${JSON.stringify(attempts)}`);
@@ -9859,7 +10429,7 @@ function modelFetchCmd() {
9859
10429
 
9860
10430
  // src/commands/agent.ts
9861
10431
  import path2 from "node:path";
9862
- import { Command as Command37, Option as Option5 } from "commander";
10432
+ import { Command as Command38, Option as Option5 } from "commander";
9863
10433
  import { getFlopNetworkProfile as getFlopNetworkProfile3 } from "@flop-labs/sdk";
9864
10434
  var DEFAULT_MCP_URL = "https://mcp.flop.network/mcp";
9865
10435
  var DEVNET_MCP_URL = "https://mcp.flop.network/mcp";
@@ -9947,7 +10517,7 @@ function buildAgentInitPlan(client, mode, mcpUrl2 = DEFAULT_MCP_URL) {
9947
10517
  };
9948
10518
  }
9949
10519
  function agentInitCmd() {
9950
- return new Command37("init").description("Print a safe agent onboarding plan and MCP configuration").addOption(new Option5("--client <client>", "Agent client").choices([
10520
+ return new Command38("init").description("Print a safe agent onboarding plan and MCP configuration").addOption(new Option5("--client <client>", "Agent client").choices([
9951
10521
  "claude-code",
9952
10522
  "codex",
9953
10523
  "cursor",
@@ -9965,7 +10535,7 @@ function agentInitCmd() {
9965
10535
  });
9966
10536
  }
9967
10537
  function agentDoctorCmd() {
9968
- return new Command37("doctor").description("Check agent MCP, gateway, and optional chain connectivity").addOption(new Option5("--mode <mode>", "MCP mode").choices(["remote", "local"]).default("remote")).option("--mcp-url <url>", "MCP URL").option("--gateway <url>", "Optional OpenAI-compatible gateway URL").option("--repo <path>", "flop-core checkout for local mode", process.cwd()).option("--check-rpc", "Also require the configured chain RPC").option("--json", "Output as JSON").action(async (opts, command) => {
10538
+ return new Command38("doctor").description("Check agent MCP, gateway, and optional chain connectivity").addOption(new Option5("--mode <mode>", "MCP mode").choices(["remote", "local"]).default("remote")).option("--mcp-url <url>", "MCP URL").option("--gateway <url>", "Optional OpenAI-compatible gateway URL").option("--repo <path>", "flop-core checkout for local mode", process.cwd()).option("--check-rpc", "Also require the configured chain RPC").option("--json", "Output as JSON").action(async (opts, command) => {
9969
10539
  try {
9970
10540
  const checks = [executableCheck("node")];
9971
10541
  if (opts.mode === "remote") {
@@ -9992,7 +10562,7 @@ function agentDoctorCmd() {
9992
10562
  });
9993
10563
  }
9994
10564
  function agentCredentialCmd() {
9995
- return new Command37("credential").description("Provision or reuse a devnet gateway key for agent clients (wallet-signed by default)").option("--print-key", "Print only the bearer key for a local client auth helper").option("--tailnet", "Enroll through the private tailnet lane (approved Tailscale users only)").option("--json", "Output non-secret credential metadata as JSON").action(async (opts, command) => {
10565
+ return new Command38("credential").description("Provision or reuse a devnet gateway key for agent clients (wallet-signed by default)").option("--print-key", "Print only the bearer key for a local client auth helper").option("--tailnet", "Enroll through the private tailnet lane (approved Tailscale users only)").option("--json", "Output non-secret credential metadata as JSON").action(async (opts, command) => {
9996
10566
  try {
9997
10567
  if (getNetwork(command) !== "devnet") {
9998
10568
  throw new Error("agent credential requires --network devnet");
@@ -10025,20 +10595,20 @@ function agentCredentialCmd() {
10025
10595
  });
10026
10596
  }
10027
10597
  function agentCmd() {
10028
- const command = new Command37("agent").description("Onboard and operate FLOP-backed agents");
10598
+ const command = new Command38("agent").description("Onboard and operate FLOP-backed agents");
10029
10599
  command.addCommand(agentInitCmd());
10030
10600
  command.addCommand(agentDoctorCmd());
10031
10601
  command.addCommand(agentCredentialCmd());
10032
10602
  command.addCommand(launchCmd());
10033
10603
  command.addCommand(proxyCmd());
10034
10604
  command.addCommand(hermesCmd());
10035
- const keys = new Command37("keys").description("Manage agent keys");
10605
+ const keys = new Command38("keys").description("Manage agent keys");
10036
10606
  keys.addCommand(keysGenerateCmd());
10037
10607
  command.addCommand(keys);
10038
- const wallet = new Command37("wallet").description("Manage agent wallets");
10608
+ const wallet = new Command38("wallet").description("Manage agent wallets");
10039
10609
  wallet.addCommand(walletAgentCreateCmd());
10040
10610
  command.addCommand(wallet);
10041
- const session = new Command37("session").description("Manage agent session keys");
10611
+ const session = new Command38("session").description("Manage agent session keys");
10042
10612
  session.addCommand(sessionCreateCmd());
10043
10613
  session.addCommand(sessionInfoCmd());
10044
10614
  session.addCommand(sessionRevokeCmd());
@@ -10050,7 +10620,7 @@ function agentCmd() {
10050
10620
  // src/commands/miner.ts
10051
10621
  import { readFileSync } from "node:fs";
10052
10622
  import path3 from "node:path";
10053
- import { Command as Command38, Option as Option6 } from "commander";
10623
+ import { Command as Command40, Option as Option6 } from "commander";
10054
10624
  import { formatFlop as formatFlop2 } from "@flop-labs/sdk";
10055
10625
 
10056
10626
  // src/gpu-inventory-remark.ts
@@ -10127,6 +10697,85 @@ function gpusFromDetection(devices) {
10127
10697
  return aggregate(devices.map((device) => ({ name: device.deviceName.trim(), memoryMib: device.memoryTotal })));
10128
10698
  }
10129
10699
 
10700
+ // src/commands/miner-capacity-rate.ts
10701
+ import { Command as Command39 } from "commander";
10702
+ var U64_MAX = (1n << 64n) - 1n;
10703
+ function parseReservationRate(value) {
10704
+ if (!/^\d+$/.test(value.trim())) throw new Error("rate must be a positive whole number of G_n/s");
10705
+ const rate = BigInt(value.trim());
10706
+ if (rate <= 0n) throw new Error("rate must be positive; use --whole-cap to restore the default");
10707
+ if (rate > U64_MAX) throw new Error("rate exceeds u64");
10708
+ return rate;
10709
+ }
10710
+ function capacityRateCall(api, rate) {
10711
+ const call = api.tx.computeChannel?.setCapacityReservationRate;
10712
+ if (typeof call !== "function") {
10713
+ throw new Error(`runtime spec ${api.runtimeVersion?.specVersion?.toString() ?? "?"} has no computeChannel.setCapacityReservationRate`);
10714
+ }
10715
+ return call(rate);
10716
+ }
10717
+ var text = (value) => value === void 0 || value === null ? null : value.toString();
10718
+ function showCmd() {
10719
+ return new Command39("show").description("Show the miner's reservation rate and free calibrated capacity at the finalized block").argument("[address]", "Miner SS58 address; defaults to the configured signer").option("--json", "Output as JSON").action(async (address, _opts, command) => {
10720
+ let client;
10721
+ try {
10722
+ const rpc = getRpc(command);
10723
+ client = address ? await createReadOnlyClient(rpc) : await createClient(rpc, getSigner(command));
10724
+ const miner = address ?? client.address;
10725
+ const { computeChannel: computeChannel9 } = await import("@flop-labs/sdk");
10726
+ const state = await computeChannel9.readMinerCapacityState(client.api, miner);
10727
+ if (!state) {
10728
+ print({ address: miner, rpc, supported: false }, getJson(command));
10729
+ return;
10730
+ }
10731
+ const capacity = computeChannel9.minerCapacity(state);
10732
+ print({
10733
+ address: miner,
10734
+ rpc,
10735
+ supported: true,
10736
+ block: text(capacity.block),
10737
+ rate_set: text(state.rate),
10738
+ cap_gflops_per_sec: text(capacity.cap),
10739
+ reserved_gn_per_sec: text(capacity.reserved),
10740
+ free_gn_per_sec: text(capacity.free),
10741
+ next_open_reserves: text(capacity.rate),
10742
+ next_open_fits: capacity.fits,
10743
+ ...capacity.reason ? { reason: capacity.reason } : {},
10744
+ live_reservations: state.reservations.filter((r) => r.lapsesAt > state.block).length
10745
+ }, getJson(command));
10746
+ } catch (error) {
10747
+ commandError(error);
10748
+ } finally {
10749
+ await client?.disconnect();
10750
+ }
10751
+ });
10752
+ }
10753
+ function setCmd() {
10754
+ return new Command39("set").description("Set the G_n/s each new channel reserves from the signer's cap (miner call set_capacity_reservation_rate)").argument("[rate]", "Rate in G_n/s, a positive integer").option("--whole-cap", "Clear the rate: each new channel reserves the whole current cap").option("--dry-run", "Print the encoded call without signing").option("--json", "Output as JSON").action(async (value, opts, command) => {
10755
+ let client;
10756
+ try {
10757
+ if (value === void 0 === !opts.wholeCap) throw new Error("pass a rate or --whole-cap, not both");
10758
+ const rate = opts.wholeCap ? null : parseReservationRate(value);
10759
+ const rpc = getRpc(command);
10760
+ client = opts.dryRun ? await createReadOnlyClient(rpc) : await createClient(rpc, getSigner(command));
10761
+ const call = capacityRateCall(client.api, rate);
10762
+ if (opts.dryRun) {
10763
+ print({ action: "capacity_rate_call", rate_gn_per_sec: text(rate), call_data: call.method.toHex() }, getJson(command));
10764
+ return;
10765
+ }
10766
+ const txHash = await submitExtrinsic(client.api, client.signer, call);
10767
+ print({ action: "capacity_rate_set", address: client.address, rate_gn_per_sec: text(rate), tx_hash: txHash }, getJson(command));
10768
+ } catch (error) {
10769
+ commandError(error);
10770
+ } finally {
10771
+ await client?.disconnect();
10772
+ }
10773
+ });
10774
+ }
10775
+ function minerCapacityRateCmd() {
10776
+ return new Command39("capacity-rate").description("Show or set the capacity each channel opened against this miner reserves").addCommand(showCmd()).addCommand(setCmd());
10777
+ }
10778
+
10130
10779
  // src/commands/miner-controller-info.ts
10131
10780
  async function probeMinerSessionInfo(url, token, required = true) {
10132
10781
  try {
@@ -10163,6 +10812,159 @@ async function probeMinerSessionInfo(url, token, required = true) {
10163
10812
  }
10164
10813
  }
10165
10814
 
10815
+ // src/commands/miner-running.ts
10816
+ import { decodeAddress } from "@polkadot/util-crypto";
10817
+ import { computeChannel as computeChannel6 } from "@flop-labs/sdk";
10818
+ var hex2 = (bytes4) => `0x${Buffer.from(bytes4).toString("hex")}`;
10819
+ var reason = (error) => error instanceof Error ? error.message : String(error);
10820
+ async function readLocalIdentity(sessionUrl, token, fetchImpl = fetch) {
10821
+ const base = sessionUrl.replace(/\/session\/info\/?$/, "");
10822
+ try {
10823
+ const info = await computeChannel6.fetchMinerSessionInfo(
10824
+ base,
10825
+ token ? { Authorization: `Bearer ${token}` } : {},
10826
+ AbortSignal.timeout(2e4),
10827
+ fetchImpl
10828
+ );
10829
+ if (!info.modelIdentity) {
10830
+ return { check: {
10831
+ id: "session:identity",
10832
+ status: "fail",
10833
+ required: true,
10834
+ detail: "worker reports no model identity",
10835
+ next: "check the worker logs for the model gate"
10836
+ } };
10837
+ }
10838
+ return { info, check: {
10839
+ id: "session:identity",
10840
+ status: "pass",
10841
+ required: true,
10842
+ detail: `${info.minerAddress} serves ${info.modelIdentity.registryModel} (${hex2(info.modelIdentity.modelHash)}), session key ${hex2(info.enclaveKey)}`
10843
+ } };
10844
+ } catch {
10845
+ return { check: {
10846
+ id: "session:identity",
10847
+ status: "fail",
10848
+ required: true,
10849
+ detail: "authenticated session info unavailable",
10850
+ next: "check --session-url and SESSION_AUTH_TOKEN"
10851
+ } };
10852
+ }
10853
+ }
10854
+ async function probeMinerChain(api, address) {
10855
+ try {
10856
+ const account = decodeAddress(address);
10857
+ const head = await api.rpc.chain.getFinalizedHead();
10858
+ const at = await api.at(head);
10859
+ const registration = await at.query.minerStaking.miners(account);
10860
+ const active = registration?.isSome ? registration.unwrap().isActive : void 0;
10861
+ const block = (await api.rpc.chain.getHeader(head)).number.toString();
10862
+ if (!(active?.isTrue ?? active === true)) {
10863
+ return {
10864
+ id: "chain:miner",
10865
+ status: "fail",
10866
+ required: true,
10867
+ detail: `${address} is not registered and active at finalized #${block}`,
10868
+ next: "flop miner register; flop miner status"
10869
+ };
10870
+ }
10871
+ const cap = (await api.rpc.state.call(
10872
+ "TrustRootApi_accepted_calibration_cap",
10873
+ hex2(Uint8Array.from([128, ...account])),
10874
+ head
10875
+ )).toU8a(true);
10876
+ if (cap.length === 1 && cap[0] === 0) {
10877
+ return {
10878
+ id: "chain:miner",
10879
+ status: "fail",
10880
+ required: true,
10881
+ detail: `${address} has no accepted calibration at finalized #${block}`,
10882
+ next: "check the worker's calibration logs"
10883
+ };
10884
+ }
10885
+ if (cap.length !== 9 || cap[0] !== 1) throw new Error("TrustRootApi_accepted_calibration_cap did not return an Option<u64>");
10886
+ const gflops = new DataView(cap.buffer, cap.byteOffset + 1, 8).getBigUint64(0, true);
10887
+ if (gflops === 0n) {
10888
+ return {
10889
+ id: "chain:miner",
10890
+ status: "fail",
10891
+ required: true,
10892
+ detail: `${address} has a zero calibration cap at finalized #${block}`,
10893
+ next: "check the worker's calibration logs"
10894
+ };
10895
+ }
10896
+ return {
10897
+ id: "chain:miner",
10898
+ status: "pass",
10899
+ required: true,
10900
+ detail: `registered, active, accepted calibration cap ${gflops} GFLOP/s at finalized #${block} (capacity bond not checked)`
10901
+ };
10902
+ } catch (error) {
10903
+ return { id: "chain:miner", status: "fail", required: true, detail: reason(error), next: "check --address and --rpc" };
10904
+ }
10905
+ }
10906
+ async function probePublicEndpoint(api, endpoint, address, local, fetchImpl = fetch) {
10907
+ let url;
10908
+ try {
10909
+ url = new URL(endpoint);
10910
+ } catch {
10911
+ }
10912
+ if (!url || url.protocol !== "https:" || url.pathname !== "/" || url.search || url.hash || url.username || url.password) {
10913
+ return [{
10914
+ id: "endpoint",
10915
+ status: "fail",
10916
+ required: true,
10917
+ detail: `${endpoint} is not a bare https://host[:port] endpoint`,
10918
+ next: "pass the endpoint the worker advertises"
10919
+ }];
10920
+ }
10921
+ const base = url.origin;
10922
+ const checks = [];
10923
+ try {
10924
+ const anonymous = await fetchImpl(`${base}/session/info`, { redirect: "error", signal: AbortSignal.timeout(1e4) });
10925
+ await anonymous.body?.cancel();
10926
+ checks.push(anonymous.status === 401 ? { id: "endpoint:auth", status: "pass", required: true, detail: `${base}/session/info refuses anonymous requests (401)` } : {
10927
+ id: "endpoint:auth",
10928
+ status: "fail",
10929
+ required: true,
10930
+ detail: anonymous.status === 200 ? `${base}/session/info answers without authentication` : `${base}/session/info answered ${anonymous.status}`,
10931
+ next: "route the public name to the worker; it must require the session token"
10932
+ });
10933
+ } catch (error) {
10934
+ checks.push({
10935
+ id: "endpoint:auth",
10936
+ status: "fail",
10937
+ required: true,
10938
+ detail: `${base} unreachable: ${reason(error)}`,
10939
+ next: "check DNS, TLS and the firewall from outside this host"
10940
+ });
10941
+ }
10942
+ try {
10943
+ const proven = await computeChannel6.fetchProvenSessionInfo(base, {
10944
+ genesisHash: api.genesisHash.toU8a(),
10945
+ account: decodeAddress(address),
10946
+ minerAddress: address,
10947
+ enclaveKey: local.enclaveKey,
10948
+ modelIdentity: local.modelIdentity
10949
+ }, fetchImpl, AbortSignal.timeout(1e4));
10950
+ checks.push({
10951
+ id: "endpoint:info-proof",
10952
+ status: "pass",
10953
+ required: true,
10954
+ detail: `fresh ready proof from the local session key for the local model (image ${proven.image?.version ?? "?"} ${proven.image?.revision ?? "?"})`
10955
+ });
10956
+ } catch (error) {
10957
+ checks.push({
10958
+ id: "endpoint:info-proof",
10959
+ status: "fail",
10960
+ required: true,
10961
+ detail: reason(error),
10962
+ next: "the public name must reach this worker, ready, on this chain; a probe from this host can differ from an outside one (hairpin NAT)"
10963
+ });
10964
+ }
10965
+ return checks;
10966
+ }
10967
+
10166
10968
  // src/commands/miner.ts
10167
10969
  function collectGpu(value, previous) {
10168
10970
  return [...previous, value];
@@ -10268,7 +11070,7 @@ function buildMinerInitPlan(options) {
10268
11070
  };
10269
11071
  }
10270
11072
  function minerInitCmd() {
10271
- return new Command38("init").description("Print a phased miner onboarding plan").addOption(new Option6("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--instance <number>", "Local guest instance number", "0").option("--provider <name>", "Cloud provider", "runpod").option(
11073
+ return new Command40("init").description("Print a phased miner onboarding plan").addOption(new Option6("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--instance <number>", "Local guest instance number", "0").option("--provider <name>", "Cloud provider", "runpod").option(
10272
11074
  "--gpu <SKU[:COUNT]>",
10273
11075
  "Expected GPU inventory; repeat for mixed-SKU hosts",
10274
11076
  collectGpu,
@@ -10311,10 +11113,20 @@ async function gpuCheck(mode) {
10311
11113
  }
10312
11114
  }
10313
11115
  function minerDoctorCmd() {
10314
- return new Command38("doctor").description("Check miner runtime, hardware, chain, and serving prerequisites").addOption(new Option6("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--session-url <url>", "Optional miner session info URL").option("--offline", "Skip chain RPC check").option("--json", "Output as JSON").action(async (opts, command) => {
11116
+ return new Command40("doctor").description("Check miner runtime, hardware, chain, and serving prerequisites").addOption(new Option6("--mode <mode>", "Miner mode").choices(["sim", "tdx", "cloud"]).default("sim")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--session-url <url>", "Optional miner session info URL").option("--offline", "Skip chain RPC check").option("--running", "Check a running miner instead of setup prerequisites (needs --address or --session-url)").option("--address <ss58>", "Miner account: registered, active and calibrated at finality").option("--endpoint <url>", "Public https endpoint: refuses anonymous info and answers a fresh info-proof (needs --session-url)").addHelpText("after", [
11117
+ "",
11118
+ "--running skips the docker/just/checkout/GPU checks, so it runs from a release pack or another host.",
11119
+ "The session token is read from SESSION_AUTH_TOKEN, never a flag. --endpoint compares the public",
11120
+ "proof with the worker's own authenticated identity; the gateway listing itself is not visible here.",
11121
+ "From the pack's CLI image use --network host to reach the worker on 127.0.0.1."
11122
+ ].join("\n")).option("--json", "Output as JSON").action(async (opts, command) => {
10315
11123
  try {
11124
+ if (opts.running && !opts.address && !opts.sessionUrl) throw new Error("--running needs --address or --session-url");
11125
+ if (opts.endpoint && !opts.sessionUrl) throw new Error("--endpoint needs --session-url: the public proof is checked against the worker's own identity");
11126
+ if (opts.offline && (opts.address || opts.endpoint || opts.running)) throw new Error("--offline cannot check chain state; drop --offline");
10316
11127
  const checks = [];
10317
- if (opts.mode === "cloud") {
11128
+ if (opts.running) {
11129
+ } else if (opts.mode === "cloud") {
10318
11130
  checks.push(executableCheck("mise"), executableCheck("uv"));
10319
11131
  checks.push(pathCheck(
10320
11132
  "gpu-providers",
@@ -10332,14 +11144,34 @@ function minerDoctorCmd() {
10332
11144
  checks.push(pathCheck("device:kvm", "/dev/kvm"));
10333
11145
  }
10334
11146
  }
11147
+ if (opts.sessionUrl) checks.push(await probeMinerSessionInfo(opts.sessionUrl, process.env.SESSION_AUTH_TOKEN));
11148
+ const local = opts.sessionUrl && (opts.running || opts.endpoint) ? await readLocalIdentity(opts.sessionUrl, process.env.SESSION_AUTH_TOKEN) : void 0;
11149
+ if (local) checks.push(local.check);
11150
+ const address = opts.address ?? local?.info?.minerAddress;
11151
+ if (opts.address && local?.info && local.info.minerAddress !== opts.address) {
11152
+ checks.push({
11153
+ id: "session:account",
11154
+ status: "fail",
11155
+ required: true,
11156
+ detail: `worker signs as ${local.info.minerAddress}, not ${opts.address}`,
11157
+ next: "check the miner key in the pack configuration"
11158
+ });
11159
+ }
10335
11160
  if (!opts.offline) {
10336
11161
  const rpc = getRpc(command);
10337
11162
  const client = await createClient(rpc);
10338
- checks.push(await probeRpc(client.api, rpc));
10339
- await client.disconnect();
11163
+ try {
11164
+ const rpcCheck = await probeRpc(client.api, rpc);
11165
+ checks.push(rpcCheck);
11166
+ if (rpcCheck.status === "pass" && address) checks.push(await probeMinerChain(client.api, address));
11167
+ if (rpcCheck.status === "pass" && opts.endpoint && address && local?.info?.modelIdentity) {
11168
+ checks.push(...await probePublicEndpoint(client.api, opts.endpoint, address, local.info));
11169
+ }
11170
+ } finally {
11171
+ await client.disconnect();
11172
+ }
10340
11173
  }
10341
- if (opts.sessionUrl) checks.push(await probeMinerSessionInfo(opts.sessionUrl, process.env.SESSION_AUTH_TOKEN));
10342
- const report = finalizeDoctor("miner", opts.mode, checks);
11174
+ const report = finalizeDoctor("miner", opts.running ? "running" : opts.mode, checks);
10343
11175
  printDoctor(report, getJson(command));
10344
11176
  if (!report.ready) process.exitCode = 1;
10345
11177
  } catch (error) {
@@ -10348,7 +11180,7 @@ function minerDoctorCmd() {
10348
11180
  });
10349
11181
  }
10350
11182
  function minerStatusCmd() {
10351
- return new Command38("status").description("Show miner registration, calibration, and optional session status").argument("[address]", "Miner SS58 address; defaults to the configured signer").option("--session-url <url>", "Optional miner session info URL").option(
11183
+ return new Command40("status").description("Show miner registration, calibration, and optional session status").argument("[address]", "Miner SS58 address; defaults to the configured signer").option("--session-url <url>", "Optional miner session info URL").option(
10352
11184
  "--expect-gpu <SKU[:COUNT]>",
10353
11185
  "Expected inventory; repeat to check attested state",
10354
11186
  collectGpu,
@@ -10398,7 +11230,7 @@ function minerStatusCmd() {
10398
11230
  });
10399
11231
  }
10400
11232
  function minerSkusCmd() {
10401
- return new Command38("skus").description("List GPU SKUs with active governance calibration ceilings").option("--json", "Output as JSON").action(async (_opts, command) => {
11233
+ return new Command40("skus").description("List GPU SKUs with active governance calibration ceilings").option("--json", "Output as JSON").action(async (_opts, command) => {
10402
11234
  let client;
10403
11235
  try {
10404
11236
  const rpc = getRpc(command);
@@ -10422,7 +11254,7 @@ function minerSkusCmd() {
10422
11254
  });
10423
11255
  }
10424
11256
  function minerRegisterCmd() {
10425
- return new Command38("register").description("Register the signer as a miner and lock the runtime-defined self-stake").option("--commission <percent>", "Commission percent (0-20)", "10").option("--json", "Output as JSON").action(async (opts, command) => {
11257
+ return new Command40("register").description("Register the signer as a miner and lock the runtime-defined self-stake").option("--commission <percent>", "Commission percent (0-20)", "10").option("--json", "Output as JSON").action(async (opts, command) => {
10426
11258
  let client;
10427
11259
  try {
10428
11260
  const commission = Number(opts.commission);
@@ -10449,7 +11281,7 @@ function minerRegisterCmd() {
10449
11281
  });
10450
11282
  }
10451
11283
  function minerDeclareGpuCmd() {
10452
- return new Command38("declare-gpu").description("Publish the signer's self-reported GPU inventory as a flop.gpu-inventory.v1 remark (display-only)").option("--nvidia-smi <path>", "Output of `nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits` (- for stdin)").option("--detect", "Run nvidia-smi here (on the GPU host)").option("--retract", "Publish an empty inventory, withdrawing an earlier declaration").option("--dry-run", "Print the remark without signing").option("--json", "Output as JSON").action(async (opts, command) => {
11284
+ return new Command40("declare-gpu").description("Publish the signer's self-reported GPU inventory as a flop.gpu-inventory.v1 remark (display-only)").option("--nvidia-smi <path>", "Output of `nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits` (- for stdin)").option("--detect", "Run nvidia-smi here (on the GPU host)").option("--retract", "Publish an empty inventory, withdrawing an earlier declaration").option("--dry-run", "Print the remark without signing").option("--json", "Output as JSON").action(async (opts, command) => {
10453
11285
  let client;
10454
11286
  try {
10455
11287
  if ([opts.nvidiaSmi, opts.detect, opts.retract].filter(Boolean).length !== 1) {
@@ -10485,20 +11317,21 @@ function minerDeclareGpuCmd() {
10485
11317
  });
10486
11318
  }
10487
11319
  function minerCmd() {
10488
- const command = new Command38("miner").description("Onboard and operate inference miners");
11320
+ const command = new Command40("miner").description("Onboard and operate inference miners");
10489
11321
  command.addCommand(minerInitCmd());
10490
11322
  command.addCommand(minerDoctorCmd());
10491
11323
  command.addCommand(minerStatusCmd());
10492
11324
  command.addCommand(minerSkusCmd());
10493
11325
  command.addCommand(minerRegisterCmd());
10494
11326
  command.addCommand(minerDeclareGpuCmd());
11327
+ command.addCommand(minerCapacityRateCmd());
10495
11328
  command.addCommand(stakeInfoCmd());
10496
11329
  return command;
10497
11330
  }
10498
11331
 
10499
11332
  // src/commands/validator.ts
10500
11333
  import path4 from "node:path";
10501
- import { Command as Command39, Option as Option7 } from "commander";
11334
+ import { Command as Command41, Option as Option7 } from "commander";
10502
11335
  import { formatFlop as formatFlop3 } from "@flop-labs/sdk";
10503
11336
  function buildValidatorInitPlan(mode, minimumStake) {
10504
11337
  const bringUp = mode === "local" ? "just run-node" : "infra/vultr/create-vm.sh <args>; infra/vultr/deploy-core.sh --vm-ip <TAILSCALE-IP>";
@@ -10548,7 +11381,7 @@ function buildValidatorInitPlan(mode, minimumStake) {
10548
11381
  };
10549
11382
  }
10550
11383
  function validatorInitCmd() {
10551
- return new Command39("init").description("Print a validator onboarding plan with the live minimum stake").addOption(new Option7("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--offline", "Do not query the configured chain").option("--json", "Output as JSON").action(async (opts, command) => {
11384
+ return new Command41("init").description("Print a validator onboarding plan with the live minimum stake").addOption(new Option7("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--offline", "Do not query the configured chain").option("--json", "Output as JSON").action(async (opts, command) => {
10552
11385
  let client;
10553
11386
  try {
10554
11387
  let minimumStake;
@@ -10567,7 +11400,7 @@ function validatorInitCmd() {
10567
11400
  });
10568
11401
  }
10569
11402
  function validatorDoctorCmd() {
10570
- return new Command39("doctor").description("Check validator node and on-chain eligibility prerequisites").addOption(new Option7("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--offline", "Skip chain and account checks").option("--json", "Output as JSON").action(async (opts, command) => {
11403
+ return new Command41("doctor").description("Check validator node and on-chain eligibility prerequisites").addOption(new Option7("--mode <mode>", "Validator deployment mode").choices(["local", "vultr"]).default("local")).option("--repo <path>", "flop-core checkout", process.cwd()).option("--offline", "Skip chain and account checks").option("--json", "Output as JSON").action(async (opts, command) => {
10571
11404
  let client;
10572
11405
  try {
10573
11406
  const checks = [
@@ -10638,7 +11471,7 @@ function validatorDoctorCmd() {
10638
11471
  });
10639
11472
  }
10640
11473
  function validatorStatusCmd() {
10641
- return new Command39("status").description("Show validator stake, jail, performance, and verified-work state").argument("[address]", "Validator SS58 address; defaults to the configured signer").option("--json", "Output as JSON").action(async (address, opts, command) => {
11474
+ return new Command41("status").description("Show validator stake, jail, performance, and verified-work state").argument("[address]", "Validator SS58 address; defaults to the configured signer").option("--json", "Output as JSON").action(async (address, opts, command) => {
10642
11475
  let client;
10643
11476
  try {
10644
11477
  const rpc = getRpc(command);
@@ -10683,7 +11516,7 @@ function validatorStatusCmd() {
10683
11516
  });
10684
11517
  }
10685
11518
  function validatorRegisterCmd() {
10686
- return new Command39("register").description("Register the signer as a validator, bonding the effective minimum").option("--json", "Output as JSON").action(async (opts, command) => {
11519
+ return new Command41("register").description("Register the signer as a validator, bonding the effective minimum").option("--json", "Output as JSON").action(async (opts, command) => {
10687
11520
  let client;
10688
11521
  try {
10689
11522
  const rpc = getRpc(command);
@@ -10713,7 +11546,7 @@ function validatorRegisterCmd() {
10713
11546
  });
10714
11547
  }
10715
11548
  function validatorCommissionCmd() {
10716
- return new Command39("commission").description("Set validator commission").argument("<percent>", "Commission percent (0-100)").option("--json", "Output as JSON").action(async (percent, opts, command) => {
11549
+ return new Command41("commission").description("Set validator commission").argument("<percent>", "Commission percent (0-100)").option("--json", "Output as JSON").action(async (percent, opts, command) => {
10717
11550
  let client;
10718
11551
  try {
10719
11552
  const value = Number(percent);
@@ -10733,7 +11566,7 @@ function validatorCommissionCmd() {
10733
11566
  });
10734
11567
  }
10735
11568
  function validatorLifecycleCmd(name, description, sdkMethod) {
10736
- return new Command39(name).description(description).option("--json", "Output as JSON").action(async (opts, command) => {
11569
+ return new Command41(name).description(description).option("--json", "Output as JSON").action(async (opts, command) => {
10737
11570
  let client;
10738
11571
  try {
10739
11572
  const rpc = getRpc(command);
@@ -10749,7 +11582,7 @@ function validatorLifecycleCmd(name, description, sdkMethod) {
10749
11582
  });
10750
11583
  }
10751
11584
  function validatorCmd() {
10752
- const command = new Command39("validator").description("Onboard and operate validator nodes");
11585
+ const command = new Command41("validator").description("Onboard and operate validator nodes");
10753
11586
  command.addCommand(validatorInitCmd());
10754
11587
  command.addCommand(validatorDoctorCmd());
10755
11588
  command.addCommand(validatorStatusCmd());
@@ -10762,7 +11595,7 @@ function validatorCmd() {
10762
11595
  }
10763
11596
 
10764
11597
  // src/commands/network-status.ts
10765
- import { Command as Command40 } from "commander";
11598
+ import { Command as Command42 } from "commander";
10766
11599
 
10767
11600
  // src/network.ts
10768
11601
  async function probeRpcChain(network, rpc) {
@@ -10796,7 +11629,7 @@ function networkStatusCmd(probeRpc2 = probeRpcChain, probeFaucet = async (endpoi
10796
11629
  return { ...identity, ready: null, readinessError: error instanceof Error ? error.message : String(error) };
10797
11630
  }
10798
11631
  }) {
10799
- return new Command40("status").description("Show resolved network endpoints and live chain identity").option("--offline", "Resolve configured endpoints without connecting").option("--json", "Output as JSON").action(async (opts, command) => {
11632
+ return new Command42("status").description("Show resolved network endpoints and live chain identity").option("--offline", "Resolve configured endpoints without connecting").option("--json", "Output as JSON").action(async (opts, command) => {
10800
11633
  const resolved = resolveNetwork(command);
10801
11634
  let rpcInfo = null;
10802
11635
  let rpcError;
@@ -10850,9 +11683,9 @@ function networkStatusCmd(probeRpc2 = probeRpcChain, probeFaucet = async (endpoi
10850
11683
  }
10851
11684
 
10852
11685
  // src/commands/faucet-request.ts
10853
- import { Command as Command41 } from "commander";
11686
+ import { Command as Command43 } from "commander";
10854
11687
  function faucetRequestCmd(probeRpc2 = probeRpcChain) {
10855
- return new Command41("request").description("Request dev/test FLOP after verifying faucet and RPC chain identity").argument("<address>", "SS58 address to fund").option("--amount <flop>", "Requested FLOP amount; server policy may cap it").option("--endpoint <url>", "Explicit faucet endpoint override").option("--json", "Output as JSON").action(async (address, opts, command) => {
11688
+ return new Command43("request").description("Request dev/test FLOP after verifying faucet and RPC chain identity").argument("<address>", "SS58 address to fund").option("--amount <flop>", "Requested FLOP amount; server policy may cap it").option("--endpoint <url>", "Explicit faucet endpoint override").option("--json", "Output as JSON").action(async (address, opts, command) => {
10856
11689
  try {
10857
11690
  const resolved = resolveNetwork(command);
10858
11691
  const endpoint = opts.endpoint ?? resolved.profile.faucet;
@@ -10879,6 +11712,25 @@ function faucetRequestCmd(probeRpc2 = probeRpcChain) {
10879
11712
  amount,
10880
11713
  maxRetries: 1
10881
11714
  });
11715
+ if (!result.success && result.outcome === "unknown") {
11716
+ printError({
11717
+ code: "FAUCET_OUTCOME_UNKNOWN",
11718
+ message: `${result.error ?? "Faucet outcome unknown"}; reconcile the original request before requesting again`,
11719
+ retryable: false,
11720
+ next: "reconcile the original request before requesting again",
11721
+ network: resolved.network,
11722
+ address,
11723
+ faucet: endpoint,
11724
+ genesis_hash: rpcChain.genesisHash,
11725
+ ...result.status !== void 0 ? { status: result.status } : {},
11726
+ ...result.reason ? { reason: result.reason } : {},
11727
+ ...result.retryAfterSeconds !== void 0 ? { retry_after_seconds: result.retryAfterSeconds } : {},
11728
+ ...result.txHash ? { tx_hash: result.txHash } : {},
11729
+ ...result.amount !== void 0 ? { amount: result.amount } : {}
11730
+ }, getJson(command));
11731
+ process.exitCode = 1;
11732
+ return;
11733
+ }
10882
11734
  if (!result.success) {
10883
11735
  commandError(new Error(result.error ?? "Faucet request failed"), {
10884
11736
  code: result.status === 429 ? "RATE_LIMITED" : "FAUCET_REQUEST_FAILED",
@@ -10893,7 +11745,9 @@ function faucetRequestCmd(probeRpc2 = probeRpcChain) {
10893
11745
  amount: result.amount,
10894
11746
  tx_hash: result.txHash,
10895
11747
  faucet: endpoint,
10896
- genesis_hash: rpcChain.genesisHash
11748
+ genesis_hash: rpcChain.genesisHash,
11749
+ status: "unconfirmed",
11750
+ recovery: "reconcile the original request before requesting again"
10897
11751
  }, getJson(command));
10898
11752
  } catch (error) {
10899
11753
  commandError(error, {
@@ -10905,16 +11759,16 @@ function faucetRequestCmd(probeRpc2 = probeRpcChain) {
10905
11759
  }
10906
11760
 
10907
11761
  // src/commands/chat.ts
10908
- import { Command as Command42 } from "commander";
11762
+ import { Command as Command44 } from "commander";
10909
11763
  import { randomUUID as randomUUID4 } from "node:crypto";
10910
11764
  import { homedir as homedir4 } from "node:os";
10911
- import { join as join9 } from "node:path";
11765
+ import { join as join10 } from "node:path";
10912
11766
  import { createInterface } from "node:readline/promises";
10913
11767
  import { stdin, stdout } from "node:process";
10914
11768
 
10915
11769
  // src/conversations.ts
10916
- import { mkdir as mkdir13, readFile as readFile8, rename as rename8, writeFile as writeFile11 } from "node:fs/promises";
10917
- import { dirname as dirname7 } from "node:path";
11770
+ import { mkdir as mkdir14, readFile as readFile8, rename as rename9, writeFile as writeFile11 } from "node:fs/promises";
11771
+ import { dirname as dirname8 } from "node:path";
10918
11772
  var MAX_CONVERSATIONS = 100;
10919
11773
  var MAX_CONVERSATION_MESSAGES = 100;
10920
11774
  var MAX_MESSAGE_CHARS = 16384;
@@ -10933,11 +11787,11 @@ var ConversationStore = class {
10933
11787
  }
10934
11788
  }
10935
11789
  async write(data) {
10936
- await mkdir13(dirname7(this.path), { recursive: true, mode: 448 });
11790
+ await mkdir14(dirname8(this.path), { recursive: true, mode: 448 });
10937
11791
  const temp = `${this.path}.${process.pid}.tmp`;
10938
11792
  await writeFile11(temp, `${JSON.stringify(data, null, 2)}
10939
11793
  `, { mode: 384 });
10940
- await rename8(temp, this.path);
11794
+ await rename9(temp, this.path);
10941
11795
  }
10942
11796
  async list() {
10943
11797
  return Object.values((await this.read()).conversations).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -10972,7 +11826,7 @@ function conversationPrompt(messages) {
10972
11826
 
10973
11827
  // src/commands/chat.ts
10974
11828
  function chatCmd() {
10975
- return new Command42("chat").description("Start or resume a persistent FLOP chat, optionally through a bounded session").argument("[prompt]", "One-shot prompt; omit for interactive chat").option("--conversation <id>", "Resume a conversation ID").option("--model <id>", "32-byte model hash or exact registered name from flop model list").option("--session-owner <address>", "Use --signer as this owner's session delegate").option("--max-flop <n>", "Per-turn FLOP cap", "5").option("--tee-requirements <hex>", "Registered HTLC appraisal commitments (100-byte SCALE hex)", process.env.FLOP_HTLC_TEE_REQUIREMENTS).option("--skip-model-check", "Allow an unregistered model (local development only)").option("--history", "List stored conversations without connecting").option("--delete <id>", "Delete a stored conversation without connecting").option("--store <path>", "Conversation store path", join9(homedir4(), ".flop", "conversations.json")).option("--json", "Output as JSON").action(async (prompt, opts, command) => {
11829
+ return new Command44("chat").description("Start or resume a persistent FLOP chat, optionally through a bounded session").argument("[prompt]", "One-shot prompt; omit for interactive chat").option("--conversation <id>", "Resume a conversation ID").option("--model <id>", "32-byte model hash or exact registered name from flop model list").option("--session-owner <address>", "Use --signer as this owner's session delegate").option("--max-flop <n>", "Per-turn FLOP cap", "5").option("--tee-requirements <hex>", "Registered HTLC appraisal commitments (100-byte SCALE hex)", process.env.FLOP_HTLC_TEE_REQUIREMENTS).option("--skip-model-check", "Allow an unregistered model (local development only)").option("--history", "List stored conversations without connecting").option("--delete <id>", "Delete a stored conversation without connecting").option("--store <path>", "Conversation store path", join10(homedir4(), ".flop", "conversations.json")).option("--json", "Output as JSON").action(async (prompt, opts, command) => {
10976
11830
  const store = new ConversationStore(opts.store);
10977
11831
  let agent;
10978
11832
  try {
@@ -11097,7 +11951,7 @@ function chatCmd() {
11097
11951
  }
11098
11952
 
11099
11953
  // src/commands/tx-build.ts
11100
- import { Command as Command43 } from "commander";
11954
+ import { Command as Command45 } from "commander";
11101
11955
  import { writeFile as writeFile12 } from "node:fs/promises";
11102
11956
  import { execFile as execFile5 } from "node:child_process";
11103
11957
  import { promisify as promisify5 } from "node:util";
@@ -11164,7 +12018,7 @@ function assertEnvelopeChain(api, envelope) {
11164
12018
 
11165
12019
  // src/commands/tx-build.ts
11166
12020
  function txBuildCmd() {
11167
- return new Command43("build").description("Build an immortal unsigned transaction envelope for air-gapped signing").requiredOption("--address <address>", "Signing account").requiredOption("--call <hex>", "SCALE call hex").option("--out <path>", "Write envelope JSON").option("--qr", "Render the signing payload using installed qrencode").option("--json", "Output as JSON").action(async (opts, command) => {
12021
+ return new Command45("build").description("Build an immortal unsigned transaction envelope for air-gapped signing").requiredOption("--address <address>", "Signing account").requiredOption("--call <hex>", "SCALE call hex").option("--out <path>", "Write envelope JSON").option("--qr", "Render the signing payload using installed qrencode").option("--json", "Output as JSON").action(async (opts, command) => {
11168
12022
  let client;
11169
12023
  try {
11170
12024
  const resolved = resolveNetwork(command);
@@ -11209,10 +12063,10 @@ function txBuildCmd() {
11209
12063
  }
11210
12064
 
11211
12065
  // src/commands/tx-broadcast.ts
11212
- import { Command as Command44 } from "commander";
12066
+ import { Command as Command46 } from "commander";
11213
12067
  import { readFile as readFile9 } from "node:fs/promises";
11214
12068
  function txBroadcastCmd() {
11215
- return new Command44("broadcast").description("Import an offline signature, reject chain drift, and broadcast").argument("<envelope>", "Unsigned envelope JSON").requiredOption("--signature <hex>", "SCALE MultiSignature from the offline signer").option("--allow-nonce-drift", "Broadcast even if account nonce has changed").option("--json", "Output as JSON").action(async (path5, opts, command) => {
12069
+ return new Command46("broadcast").description("Import an offline signature, reject chain drift, and broadcast").argument("<envelope>", "Unsigned envelope JSON").requiredOption("--signature <hex>", "SCALE MultiSignature from the offline signer").option("--allow-nonce-drift", "Broadcast even if account nonce has changed").option("--json", "Output as JSON").action(async (path5, opts, command) => {
11216
12070
  let client;
11217
12071
  try {
11218
12072
  if (!/^0x[0-9a-f]+$/i.test(opts.signature)) throw new Error("--signature must be 0x hex");
@@ -11248,7 +12102,7 @@ function txBroadcastCmd() {
11248
12102
  }
11249
12103
 
11250
12104
  // src/commands/multisig.ts
11251
- import { Command as Command45 } from "commander";
12105
+ import { Command as Command47 } from "commander";
11252
12106
  import { createKeyMulti, encodeAddress, sortAddresses } from "@polkadot/util-crypto";
11253
12107
  import { blake2AsHex } from "@polkadot/util-crypto";
11254
12108
  function otherSignatories(signer, values) {
@@ -11269,8 +12123,8 @@ function timepoint(opts) {
11269
12123
  return { height: parsePositiveInt(opts.height, "--height"), index };
11270
12124
  }
11271
12125
  function multisigCmd() {
11272
- const command = new Command45("multisig").description("Participate in Substrate multisig transactions");
11273
- command.addCommand(new Command45("address").description("Derive the deterministic multisig address").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--signatory <address...>", "All signatory addresses").option("--json", "Output as JSON").action(async (opts, cmd) => {
12126
+ const command = new Command47("multisig").description("Participate in Substrate multisig transactions");
12127
+ command.addCommand(new Command47("address").description("Derive the deterministic multisig address").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--signatory <address...>", "All signatory addresses").option("--json", "Output as JSON").action(async (opts, cmd) => {
11274
12128
  try {
11275
12129
  const threshold = parsePositiveInt(opts.threshold, "--threshold");
11276
12130
  const sorted = sortAddresses(opts.signatory, 42);
@@ -11285,7 +12139,7 @@ function multisigCmd() {
11285
12139
  commandError(error);
11286
12140
  }
11287
12141
  }));
11288
- command.addCommand(new Command45("propose").description("Create or execute a multisig call").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories (exclude configured signer)").requiredOption("--call <hex>", "SCALE call hex").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
12142
+ command.addCommand(new Command47("propose").description("Create or execute a multisig call").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories (exclude configured signer)").requiredOption("--call <hex>", "SCALE call hex").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
11289
12143
  let client;
11290
12144
  try {
11291
12145
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11308,7 +12162,7 @@ function multisigCmd() {
11308
12162
  await client?.disconnect();
11309
12163
  }
11310
12164
  }));
11311
- command.addCommand(new Command45("approve").description("Approve a pending multisig call hash").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
12165
+ command.addCommand(new Command47("approve").description("Approve a pending multisig call hash").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--max-ref-time <n>", "Maximum call refTime").option("--max-proof-size <n>", "Maximum call proofSize").option("--json", "Output as JSON").action(async (opts, cmd) => {
11312
12166
  let client;
11313
12167
  try {
11314
12168
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11327,7 +12181,7 @@ function multisigCmd() {
11327
12181
  await client?.disconnect();
11328
12182
  }
11329
12183
  }));
11330
- command.addCommand(new Command45("cancel").description("Cancel a pending multisig as its depositor").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--json", "Output as JSON").action(async (opts, cmd) => {
12184
+ command.addCommand(new Command47("cancel").description("Cancel a pending multisig as its depositor").requiredOption("--threshold <n>", "Approval threshold").requiredOption("--other <address...>", "Other signatories").requiredOption("--call-hash <hex>", "Pending call hash").requiredOption("--height <n>", "Timepoint block height").requiredOption("--index <n>", "Timepoint extrinsic index").option("--json", "Output as JSON").action(async (opts, cmd) => {
11331
12185
  let client;
11332
12186
  try {
11333
12187
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11344,7 +12198,7 @@ function multisigCmd() {
11344
12198
  await client?.disconnect();
11345
12199
  }
11346
12200
  }));
11347
- command.addCommand(new Command45("list").alias("status").description("List pending calls for a multisig account").requiredOption("--account <address>", "Multisig account").option("--json", "Output as JSON").action(async (opts, cmd) => {
12201
+ command.addCommand(new Command47("list").alias("status").description("List pending calls for a multisig account").requiredOption("--account <address>", "Multisig account").option("--json", "Output as JSON").action(async (opts, cmd) => {
11348
12202
  let client;
11349
12203
  try {
11350
12204
  client = await createClient(getRpc(cmd));
@@ -11364,10 +12218,10 @@ function multisigCmd() {
11364
12218
  }
11365
12219
 
11366
12220
  // src/commands/channel.ts
11367
- import { Command as Command46 } from "commander";
12221
+ import { Command as Command48 } from "commander";
11368
12222
  function channelCmd() {
11369
- const command = new Command46("channel").description("Inspect and finalize compute channels");
11370
- command.addCommand(new Command46("list").description("List on-chain compute channels").option("--agent <address>", "Filter by paying agent").option("--miner <address>", "Filter by serving miner").option("--include-closed", "Include terminal channels").option("--json", "Output as JSON").action(async (opts, cmd) => {
12223
+ const command = new Command48("channel").description("Inspect and finalize compute channels");
12224
+ command.addCommand(new Command48("list").description("List on-chain compute channels").option("--agent <address>", "Filter by paying agent").option("--miner <address>", "Filter by serving miner").option("--include-closed", "Include terminal channels").option("--json", "Output as JSON").action(async (opts, cmd) => {
11371
12225
  let client;
11372
12226
  try {
11373
12227
  client = await createClient(getRpc(cmd));
@@ -11395,7 +12249,7 @@ function channelCmd() {
11395
12249
  await client?.disconnect();
11396
12250
  }
11397
12251
  }));
11398
- command.addCommand(new Command46("finalize").description("Finalize a force-settled channel after its challenge window").argument("<channel-id>", "0x channel ID").option("--json", "Output as JSON").action(async (channelId, _opts, cmd) => {
12252
+ command.addCommand(new Command48("finalize").description("Finalize a force-settled channel after its challenge window").argument("<channel-id>", "0x channel ID").option("--json", "Output as JSON").action(async (channelId, _opts, cmd) => {
11399
12253
  let client;
11400
12254
  try {
11401
12255
  client = await createClient(getRpc(cmd), getSigner(cmd));
@@ -11437,7 +12291,7 @@ function timestamp(value, label) {
11437
12291
  if (!Number.isFinite(parsed)) throw new Error(`${label} must be an ISO-8601 timestamp`);
11438
12292
  return parsed;
11439
12293
  }
11440
- function decimal(value, label) {
12294
+ function decimal2(value, label) {
11441
12295
  if (!DECIMAL.test(value)) throw new Error(`${label} must be a non-negative decimal string`);
11442
12296
  }
11443
12297
  function positiveInt(value, label) {
@@ -11465,6 +12319,12 @@ function parseCanonicalCatalog(value, options = {}) {
11465
12319
  if (!HASH.test(record.decode_policy_hash)) throw new Error(`${label}.decode_policy_hash must be lowercase 32-byte hex`);
11466
12320
  if (!Number.isSafeInteger(record.precision) || record.precision < 0 || record.precision > 5) throw new Error(`${label}.precision is invalid`);
11467
12321
  if (!QUANTIZATIONS.has(record.quantization)) throw new Error(`${label}.quantization is invalid`);
12322
+ if (record.precision_detail !== void 0) {
12323
+ if (!record.precision_detail || typeof record.precision_detail !== "object" || Array.isArray(record.precision_detail)) throw new Error(`${label}.precision_detail is invalid`);
12324
+ for (const field2 of ["activations", "kv_cache"]) {
12325
+ if (record.precision_detail[field2] !== void 0 && !QUANTIZATIONS.has(record.precision_detail[field2])) throw new Error(`${label}.precision_detail.${field2} is invalid`);
12326
+ }
12327
+ }
11468
12328
  if (!Array.isArray(record.input_modalities) || record.input_modalities.length < 1 || record.input_modalities.some((v) => !INPUT_MODALITIES.has(v))) throw new Error(`${label}.input_modalities is invalid`);
11469
12329
  if (!Array.isArray(record.output_modalities) || record.output_modalities.length < 1 || record.output_modalities.some((v) => !OUTPUT_MODALITIES.has(v))) throw new Error(`${label}.output_modalities is invalid`);
11470
12330
  if (!Array.isArray(record.supported_sampling_parameters) || record.supported_sampling_parameters.some((v) => !SAMPLING.has(v))) throw new Error(`${label}.supported_sampling_parameters is invalid`);
@@ -11495,9 +12355,9 @@ function parseCanonicalCatalog(value, options = {}) {
11495
12355
  if (!price.version || !price.miner_address || !price.flop_usd_source) throw new Error(`${priceLabel} provenance is incomplete`);
11496
12356
  if (pricedMiners.has(price.miner_address)) throw new Error(`${priceLabel} duplicates a miner price`);
11497
12357
  pricedMiners.add(price.miner_address);
11498
- for (const field2 of ["flop_per_gn", "gn_per_prompt_token", "gn_per_completion_token", "flop_usd"]) decimal(price[field2], `${priceLabel}.${field2}`);
11499
- if (price.request_usd !== void 0) decimal(price.request_usd, `${priceLabel}.request_usd`);
11500
- if (price.image_usd !== void 0) decimal(price.image_usd, `${priceLabel}.image_usd`);
12358
+ for (const field2 of ["flop_per_gn", "gn_per_prompt_token", "gn_per_completion_token", "flop_usd"]) decimal2(price[field2], `${priceLabel}.${field2}`);
12359
+ if (price.request_usd !== void 0) decimal2(price.request_usd, `${priceLabel}.request_usd`);
12360
+ if (price.image_usd !== void 0) decimal2(price.image_usd, `${priceLabel}.image_usd`);
11501
12361
  timestamp(price.ratified_at, `${priceLabel}.ratified_at`);
11502
12362
  if (timestamp(price.effective_at, `${priceLabel}.effective_at`) >= timestamp(price.expires_at, `${priceLabel}.expires_at`)) throw new Error(`${priceLabel} has an empty validity window`);
11503
12363
  if (price.rounding?.mode !== "ceil" || !Number.isSafeInteger(price.rounding.decimal_places) || price.rounding.decimal_places < 0 || price.rounding.decimal_places > 18) throw new Error(`${priceLabel}.rounding is invalid`);
@@ -11511,7 +12371,7 @@ function parseCanonicalCatalog(value, options = {}) {
11511
12371
  const timed = Number.isSafeInteger(override.utc_start) && Number.isSafeInteger(override.utc_end) && Number(override.utc_start) >= 0 && Number(override.utc_start) <= 2359 && Number(override.utc_end) >= 0 && Number(override.utc_end) <= 2359;
11512
12372
  if (longContext === timed) throw new Error(`${overrideLabel} must have exactly one supported condition kind`);
11513
12373
  for (const field2 of ["prompt", "completion", "request", "image", "input_cache_read", "internal_reasoning"]) {
11514
- if (override[field2] !== void 0) decimal(String(override[field2]), `${overrideLabel}.${field2}`);
12374
+ if (override[field2] !== void 0) decimal2(String(override[field2]), `${overrideLabel}.${field2}`);
11515
12375
  }
11516
12376
  }
11517
12377
  }
@@ -11522,8 +12382,8 @@ function parseCanonicalCatalog(value, options = {}) {
11522
12382
  });
11523
12383
  }
11524
12384
  function parts(value) {
11525
- const [whole, fraction = ""] = value.split(".");
11526
- return { value: BigInt(whole + fraction), scale: fraction.length };
12385
+ const [whole, fraction2 = ""] = value.split(".");
12386
+ return { value: BigInt(whole + fraction2), scale: fraction2.length };
11527
12387
  }
11528
12388
  function multiply(values, places) {
11529
12389
  const product = values.map(parts).reduce((acc, item) => ({ value: acc.value * item.value, scale: acc.scale + item.scale }), { value: 1n, scale: 0 });
@@ -11590,13 +12450,20 @@ function buildLiveCatalog(records, sessions, now = Date.now(), policy) {
11590
12450
  for (const field2 of ["prompt", "completion", "request", "image", "input_cache_read", "internal_reasoning"]) if (compareDecimal(candidate[field2], current[field2]) > 0) next[field2] = candidate[field2];
11591
12451
  return next;
11592
12452
  }, void 0);
12453
+ const ready = is_ready && capacity_tpm > 0;
11593
12454
  return {
11594
12455
  record,
11595
- is_ready: is_ready && capacity_tpm > 0,
11596
- capacity_tpm: is_ready && capacity_tpm > 0 ? capacity_tpm : null,
12456
+ is_ready: ready,
12457
+ capacity_tpm: ready ? capacity_tpm : null,
12458
+ // Report the oldest contributing observation and earliest evidence expiry.
12459
+ // These describe evidence freshness, not a guarantee of continued availability.
12460
+ ...ready ? {
12461
+ capacity_observed_at: priced.reduce((oldest, item) => timestamp(item.session.capacity_observed_at, "capacity_observed_at") < timestamp(oldest, "capacity_observed_at") ? item.session.capacity_observed_at : oldest, priced[0].session.capacity_observed_at),
12462
+ capacity_expires_at: priced.reduce((earliest, item) => timestamp(item.session.capacity_expires_at, "capacity_expires_at") < timestamp(earliest, "capacity_expires_at") ? item.session.capacity_expires_at : earliest, priced[0].session.capacity_expires_at)
12463
+ } : {},
11597
12464
  datacenters: is_ready ? [...new Set(priced.map((item) => item.session.datacenter.country_code))].sort().map((country_code) => ({ country_code })) : [],
11598
12465
  ...is_ready && conservative ? { pricing: conservative } : {},
11599
- price_sources: is_ready ? priced.map(({ session, price }) => ({ miner_address: session.miner_address, version: price.version, source: price.flop_usd_source, effective_at: price.effective_at, expires_at: price.expires_at })).sort((a, b) => a.miner_address.localeCompare(b.miner_address)) : [],
12466
+ price_sources: is_ready ? priced.map(({ session, price }) => ({ miner_address: session.miner_address, version: price.version, source: price.flop_usd_source, effective_at: price.effective_at, expires_at: price.expires_at, ratified_at: price.ratified_at, flop_per_gn: price.flop_per_gn, gn_per_prompt_token: price.gn_per_prompt_token, gn_per_completion_token: price.gn_per_completion_token, flop_usd: price.flop_usd, rounding: price.rounding })).sort((a, b) => a.miner_address.localeCompare(b.miner_address)) : [],
11600
12467
  admitted_sessions: is_ready ? priced.map((item) => item.session.session_id).sort() : []
11601
12468
  };
11602
12469
  });
@@ -11841,12 +12708,12 @@ function generateGatewayConfig(input) {
11841
12708
  }
11842
12709
 
11843
12710
  // src/commands/gateway-allowlist.ts
11844
- import { Command as Command47 } from "commander";
12711
+ import { Command as Command49 } from "commander";
11845
12712
  var PRECISIONS5 = ["FP32", "FP16", "BF16", "INT8", "INT4", "Mixed"];
11846
12713
  function field(value, camel, snake) {
11847
12714
  return value?.[camel] ?? value?.[snake];
11848
12715
  }
11849
- function text(value, label) {
12716
+ function text2(value, label) {
11850
12717
  const json = value?.toJSON?.() ?? value;
11851
12718
  if (typeof json === "string") {
11852
12719
  return json.startsWith("0x") ? Buffer.from(json.slice(2), "hex").toString("utf8") : json;
@@ -11854,7 +12721,7 @@ function text(value, label) {
11854
12721
  if (Array.isArray(json)) return Buffer.from(json).toString("utf8");
11855
12722
  throw new Error(`on-chain ${label} is invalid`);
11856
12723
  }
11857
- function hex2(value, label) {
12724
+ function hex3(value, label) {
11858
12725
  const rendered = value?.toHex?.() ?? value?.toJSON?.() ?? value;
11859
12726
  if (typeof rendered !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(rendered)) {
11860
12727
  throw new Error(`on-chain ${label} is not a 32-byte hash`);
@@ -11874,8 +12741,8 @@ async function fetchMinerSessionInfo(endpoint, authToken, signal) {
11874
12741
  if (anonymous.status !== 401) throw new Error("miner /session/info does not enforce bearer authentication");
11875
12742
  const response = await fetch(infoUrl, { redirect: "error", headers: { Authorization: `Bearer ${authToken}` }, signal });
11876
12743
  if (!response.ok) throw new Error(`miner /session/info returned HTTP ${response.status}`);
11877
- const { computeChannel: computeChannel7 } = await import("@flop-labs/sdk");
11878
- return JSON.parse(await computeChannel7.readMinerResponse(response, 16 * 1024));
12744
+ const { computeChannel: computeChannel9 } = await import("@flop-labs/sdk");
12745
+ return JSON.parse(await computeChannel9.readMinerResponse(response, 16 * 1024));
11879
12746
  }
11880
12747
  async function readChainSnapshot(api, miner, pinnedHead) {
11881
12748
  const identity = miner.modelIdentity;
@@ -11907,16 +12774,16 @@ async function readChainSnapshot(api, miner, pinnedHead) {
11907
12774
  `model ${identity.modelHash} is ${status}, not Verified; refusing to generate a gateway catalog`
11908
12775
  );
11909
12776
  }
11910
- const measuredRoot = rootRaw?.isSome ? hex2(rootRaw.unwrap(), "measuredRoot") : null;
11911
- const allowedDecodePolicyHashes = policiesRaw?.isSome ? Array.from(policiesRaw.unwrap(), (item) => hex2(item, "allowedDecodePolicies")) : [];
12777
+ const measuredRoot = rootRaw?.isSome ? hex3(rootRaw.unwrap(), "measuredRoot") : null;
12778
+ const allowedDecodePolicyHashes = policiesRaw?.isSome ? Array.from(policiesRaw.unwrap(), (item) => hex3(item, "allowedDecodePolicies")) : [];
11912
12779
  const lease = BigInt(at.consts.hwCalibration.calibrationLeaseBlocks.toString());
11913
12780
  const calibration = calibrationRaw?.isSome ? calibrationRaw.unwrap() : null;
11914
12781
  const acceptedAt = calibration ? BigInt(field(calibration, "acceptedAt", "accepted_at").toString()) : 0n;
11915
12782
  return {
11916
12783
  model: {
11917
- modelHash: hex2(field(modelJson, "modelHash", "model_hash"), "modelHash"),
11918
- registryModel: text(field(modelJson, "modelName", "model_name"), "modelName"),
11919
- registryVersion: text(field(modelJson, "version", "version"), "version")
12784
+ modelHash: hex3(field(modelJson, "modelHash", "model_hash"), "modelHash"),
12785
+ registryModel: text2(field(modelJson, "modelName", "model_name"), "modelName"),
12786
+ registryVersion: text2(field(modelJson, "version", "version"), "version")
11920
12787
  },
11921
12788
  measuredRoot,
11922
12789
  allowedDecodePolicyHashes,
@@ -11930,7 +12797,7 @@ async function readChainSnapshot(api, miner, pinnedHead) {
11930
12797
  };
11931
12798
  }
11932
12799
  function gatewayAllowlistCmd() {
11933
- return new Command47("allowlist").description("Generate gateway allowlist and model catalog from a live miner and chain").requiredOption("--miner <url>", "Authenticated miner base URL").requiredOption("--auth-token-env <name>", "Environment variable containing the miner bearer token").requiredOption("--attestation <path>", "Operator/provider deployment, catalog, and price evidence JSON").option("--json", "Output as JSON").action(async (opts, command) => {
12800
+ return new Command49("allowlist").description("Generate gateway allowlist and model catalog from a live miner and chain").requiredOption("--miner <url>", "Authenticated miner base URL").requiredOption("--auth-token-env <name>", "Environment variable containing the miner bearer token").requiredOption("--attestation <path>", "Operator/provider deployment, catalog, and price evidence JSON").option("--json", "Output as JSON").action(async (opts, command) => {
11934
12801
  let client;
11935
12802
  try {
11936
12803
  const authToken = process.env[opts.authTokenEnv];
@@ -11956,13 +12823,13 @@ function gatewayAllowlistCmd() {
11956
12823
  }
11957
12824
 
11958
12825
  // src/commands/gateway-renew.ts
11959
- import { open as open3, readFile as readFile11, rename as rename9, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
11960
- import { createHash as createHash7 } from "node:crypto";
12826
+ import { open as open3, readFile as readFile11, rename as rename10, rm as rm7, writeFile as writeFile13 } from "node:fs/promises";
12827
+ import { createHash as createHash9 } from "node:crypto";
11961
12828
  import { request as request2 } from "node:http";
11962
12829
  import { setTimeout as delay } from "node:timers/promises";
11963
12830
  import { Keyring as Keyring5 } from "@polkadot/keyring";
11964
- import { decodeAddress as decodeAddress2, randomAsU8a } from "@polkadot/util-crypto";
11965
- import { computeChannel as computeChannel6 } from "@flop-labs/sdk";
12831
+ import { decodeAddress as decodeAddress3, randomAsU8a } from "@polkadot/util-crypto";
12832
+ import { computeChannel as computeChannel8 } from "@flop-labs/sdk";
11966
12833
 
11967
12834
  // ../gateway-core/src/gateway-evidence.ts
11968
12835
  var GATEWAY_EVIDENCE_VERSION = "flop.gateway-evidence.v1";
@@ -11982,7 +12849,7 @@ function instant(value, label) {
11982
12849
  if (!Number.isFinite(parsed)) throw new Error(`${label} must be an ISO-8601 timestamp`);
11983
12850
  return parsed;
11984
12851
  }
11985
- function text2(value, label) {
12852
+ function text3(value, label) {
11986
12853
  if (typeof value !== "string" || !value) throw new Error(`${label} is required`);
11987
12854
  }
11988
12855
  function parseGatewayEvidence(value) {
@@ -11994,15 +12861,15 @@ function parseGatewayEvidence(value) {
11994
12861
  }
11995
12862
  for (const [index, entry] of evidence.deployments.entries()) {
11996
12863
  const label = `deployments[${index}]`;
11997
- text2(entry?.miner_address, `${label}.miner_address`);
11998
- text2(entry.endpoint, `${label}.endpoint`);
12864
+ text3(entry?.miner_address, `${label}.miner_address`);
12865
+ text3(entry.endpoint, `${label}.endpoint`);
11999
12866
  if (!HASH3.test(entry.enclave_key)) throw new Error(`${label}.enclave_key is invalid`);
12000
12867
  const identity = entry.model_identity;
12001
12868
  if (!identity || IDENTITY_FIELDS.some((field2) => field2 === "precision" ? !Number.isSafeInteger(identity.precision) : typeof identity[field2] !== "string" || !identity[field2])) {
12002
12869
  throw new Error(`${label}.model_identity is incomplete`);
12003
12870
  }
12004
- text2(entry.source, `${label}.source`);
12005
- text2(entry.observer, `${label}.observer`);
12871
+ text3(entry.source, `${label}.source`);
12872
+ text3(entry.observer, `${label}.observer`);
12006
12873
  if (!/^[A-Z]{2}$/.test(entry.country_code)) throw new Error(`${label}.country_code is invalid`);
12007
12874
  if (instant(entry.observed_at, `${label}.observed_at`) >= instant(entry.expires_at, `${label}.expires_at`)) {
12008
12875
  throw new Error(`${label} has an empty validity window`);
@@ -12010,10 +12877,10 @@ function parseGatewayEvidence(value) {
12010
12877
  }
12011
12878
  for (const [index, entry] of evidence.conformance.entries()) {
12012
12879
  const label = `conformance[${index}]`;
12013
- text2(entry?.openrouter_slug, `${label}.openrouter_slug`);
12014
- text2(entry.feature, `${label}.feature`);
12015
- text2(entry.registry_version, `${label}.registry_version`);
12016
- text2(entry.observer, `${label}.observer`);
12880
+ text3(entry?.openrouter_slug, `${label}.openrouter_slug`);
12881
+ text3(entry.feature, `${label}.feature`);
12882
+ text3(entry.registry_version, `${label}.registry_version`);
12883
+ text3(entry.observer, `${label}.observer`);
12017
12884
  if (!HASH3.test(entry.model_hash) || !HASH3.test(entry.decode_policy_hash)) throw new Error(`${label} identity hashes are invalid`);
12018
12885
  if (entry.passed !== true) throw new Error(`${label} must record a pass`);
12019
12886
  if (instant(entry.tested_at, `${label}.tested_at`) >= instant(entry.expires_at, `${label}.expires_at`)) {
@@ -12054,11 +12921,11 @@ function renewConformance(record, evidence, now = Date.now()) {
12054
12921
  }
12055
12922
 
12056
12923
  // ../gateway-core/src/tools-qualification.ts
12057
- import { createHash as createHash5 } from "node:crypto";
12924
+ import { createHash as createHash7 } from "node:crypto";
12058
12925
  import { isDeepStrictEqual } from "node:util";
12059
12926
  var OWNER_TOOLS_QUALIFICATION = "flop.owner-tools-qualification.v1";
12060
12927
  function qualificationHash(value) {
12061
- return createHash5("sha256").update(JSON.stringify(value)).digest("hex");
12928
+ return createHash7("sha256").update(JSON.stringify(value)).digest("hex");
12062
12929
  }
12063
12930
  function toolsQualificationEnvelope(model, version, scope) {
12064
12931
  return {
@@ -12156,12 +13023,13 @@ function qualificationSignedToolCalls(frames) {
12156
13023
 
12157
13024
  // ../gateway-core/src/miner-admissions.ts
12158
13025
  import { closeSync, fstatSync, openSync, readSync, statSync } from "node:fs";
12159
- import { createHash as createHash6 } from "node:crypto";
13026
+ import { createHash as createHash8 } from "node:crypto";
12160
13027
  var MAX_ADMITTED_MINERS = 256;
13028
+ var ADMISSION_CONCURRENCY = 4;
12161
13029
  var MAX_FILE_BYTES = 512 * 1024;
12162
13030
  var HASH4 = /^0x[0-9a-f]{64}$/;
12163
13031
  var minerSlot = (miner) => `${miner.minerAddress}:${miner.modelIdentity.requestedSlug}`;
12164
- var staticMinerSha256 = (miner) => createHash6("sha256").update(JSON.stringify(miner)).digest("hex");
13032
+ var staticMinerSha256 = (miner) => createHash8("sha256").update(JSON.stringify(miner)).digest("hex");
12165
13033
  var sameModel = (a, b) => Object.keys(a.modelIdentity).every((key) => a.modelIdentity[key] === b.modelIdentity[key]);
12166
13034
  var MinerRouting = class {
12167
13035
  constructor(statics, path5) {
@@ -12274,16 +13142,21 @@ var MinerAdmissions = class {
12274
13142
  const file = JSON.parse(buffer.subarray(0, size).toString("utf8"));
12275
13143
  if (file.schema !== "flop-miner-admissions/v1" || !Number.isFinite(Date.parse(file.generatedAt)) || !Array.isArray(file.entries) || file.entries.length > MAX_ADMITTED_MINERS) throw new Error("invalid admissions snapshot");
12276
13144
  const slots = /* @__PURE__ */ new Set();
12277
- const entries = file.entries.map((entry) => {
13145
+ const entries = file.entries.flatMap((entry) => {
12278
13146
  const id = entry.modelIdentity;
12279
13147
  const url = new URL(entry.endpoint);
12280
13148
  const accepted = Date.parse(entry.adAcceptedAt), expiry = Date.parse(entry.admissionExpiresAt);
12281
13149
  if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash || typeof entry.minerAddress !== "string" || !entry.minerAddress || !HASH4.test(entry.enclaveKey) || !id || ![id.modelHash, id.decodePolicyHash, id.measuredRoot].every((h) => HASH4.test(h)) || ![id.requestedSlug, id.registryModel, id.registryVersion, id.backendModelName].every((s) => typeof s === "string" && s.length > 0) || !Number.isInteger(id.precision) || id.precision < 0 || id.precision > 5 || !/^[1-9]\d*$/.test(entry.calibrationCapGflopsPerSec) || !Number.isFinite(accepted) || !Number.isFinite(expiry) || accepted > now || expiry <= accepted || expiry > accepted + 36e5 || entry.observation?.countryCode !== null && !/^[A-Z]{2}$/.test(entry.observation?.countryCode) || entry.observation?.regional !== false || entry.observation.observedAt !== entry.adAcceptedAt) throw new Error("invalid admission entry");
12282
- requireWorkerMinimum(entry.worker?.version, entry.worker?.revision);
13150
+ try {
13151
+ requireWorkerMinimum(entry.worker?.version, entry.worker?.revision);
13152
+ } catch (error) {
13153
+ if (error instanceof WorkerUpgradeRequired) return [];
13154
+ throw error;
13155
+ }
12283
13156
  const slot = minerSlot(entry);
12284
13157
  if (slots.has(slot)) throw new Error("duplicate admission slot");
12285
13158
  slots.add(slot);
12286
- return {
13159
+ return [{
12287
13160
  endpoint: entry.endpoint,
12288
13161
  authToken: "",
12289
13162
  channelSigned: true,
@@ -12298,7 +13171,7 @@ var MinerAdmissions = class {
12298
13171
  observedAt: entry.adAcceptedAt,
12299
13172
  expiresAt: entry.admissionExpiresAt
12300
13173
  }
12301
- };
13174
+ }];
12302
13175
  });
12303
13176
  this.entries = entries;
12304
13177
  } catch {
@@ -12309,6 +13182,17 @@ var MinerAdmissions = class {
12309
13182
  return this.entries.filter((entry) => Date.parse(entry.admissionExpiresAt) > now);
12310
13183
  }
12311
13184
  };
13185
+ async function mapAdmissions(items, run2, limit = ADMISSION_CONCURRENCY) {
13186
+ const results = new Array(items.length);
13187
+ let next = 0;
13188
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => {
13189
+ while (next < items.length) {
13190
+ const index = next++;
13191
+ results[index] = await run2(items[index]);
13192
+ }
13193
+ }));
13194
+ return results;
13195
+ }
12312
13196
 
12313
13197
  // ../gateway-core/src/miner-peer.ts
12314
13198
  import { BlockList, isIP } from "node:net";
@@ -12375,16 +13259,16 @@ async function resolveDnsAddresses(host, v4 = resolve42, v6 = resolve62) {
12375
13259
  }
12376
13260
  }
12377
13261
  var systemResolve = resolveDnsAddresses;
12378
- async function resolvePublic(host, resolve10 = systemResolve) {
13262
+ async function resolvePublic(host, resolve11 = systemResolve) {
12379
13263
  const family = isIP(host);
12380
- const addresses = family ? [{ address: host, family }] : await resolve10(host);
13264
+ const addresses = family ? [{ address: host, family }] : await resolve11(host);
12381
13265
  if (addresses.length === 0 || !addresses.every((a) => isPublicAddress(a.address))) {
12382
13266
  throw new EndpointRefusal(`${host} does not resolve to public addresses only`);
12383
13267
  }
12384
13268
  return addresses[0];
12385
13269
  }
12386
13270
  function minerPeerFetch(endpoint, dynamic, before = () => {
12387
- }, resolve10 = systemResolve, send2 = request) {
13271
+ }, resolve11 = systemResolve, send2 = request) {
12388
13272
  return async (input, init = {}) => {
12389
13273
  before();
12390
13274
  const url = new URL(String(input));
@@ -12395,7 +13279,7 @@ function minerPeerFetch(endpoint, dynamic, before = () => {
12395
13279
  const pinned = await new Promise((accept, reject) => {
12396
13280
  const abort = () => reject(signal.reason);
12397
13281
  signal.addEventListener("abort", abort, { once: true });
12398
- resolvePublic(url.hostname.replace(/^\[|\]$/g, ""), resolve10).then(accept, reject).finally(() => signal.removeEventListener("abort", abort));
13282
+ resolvePublic(url.hostname.replace(/^\[|\]$/g, ""), resolve11).then(accept, reject).finally(() => signal.removeEventListener("abort", abort));
12399
13283
  if (signal.aborted) abort();
12400
13284
  });
12401
13285
  before();
@@ -12431,11 +13315,11 @@ function minerPeerFetch(endpoint, dynamic, before = () => {
12431
13315
  }
12432
13316
 
12433
13317
  // src/commands/gateway-renew.ts
12434
- import { Command as Command48 } from "commander";
13318
+ import { Command as Command50 } from "commander";
12435
13319
 
12436
13320
  // src/commands/gateway-tools-qualification.ts
12437
- import { decodeAddress } from "@polkadot/util-crypto";
12438
- import { computeChannel as computeChannel5 } from "@flop-labs/sdk";
13321
+ import { decodeAddress as decodeAddress2 } from "@polkadot/util-crypto";
13322
+ import { computeChannel as computeChannel7 } from "@flop-labs/sdk";
12439
13323
  function require2(value, message) {
12440
13324
  if (!value) throw new Error(message);
12441
13325
  }
@@ -12464,36 +13348,36 @@ async function verifyOwnedToolsQualification(value, record, terminal, now = Date
12464
13348
  require2(wire, "qualification wire policy is unsupported");
12465
13349
  const envelope = toolsQualificationEnvelope(record.openrouter_slug, wire, q);
12466
13350
  assertQualificationEnvelope(envelope, q);
12467
- const input = computeChannel5.encodeChatTurnEnvelope(envelope);
12468
- const receipt = computeChannel5.decodeTurnReceipt(evidence.receipt);
13351
+ const input = computeChannel7.encodeChatTurnEnvelope(envelope);
13352
+ const receipt = computeChannel7.decodeTurnReceipt(evidence.receipt);
12469
13353
  require2(
12470
13354
  receipt.modelIdentity && receipt.modelIdentity.requestedSlug === record.openrouter_slug && receipt.modelIdentity.registryModel === record.registry_model && receipt.modelIdentity.registryVersion === record.registry_version && u8aToHex(receipt.modelIdentity.modelHash) === record.model_hash && receipt.modelIdentity.precision === record.precision && u8aToHex(receipt.modelIdentity.decodePolicyHash) === record.decode_policy_hash && receipt.modelIdentity.backendModelName === record.backend_model_name && receipt.decodePolicyHash && u8aToHex(receipt.decodePolicyHash) === record.decode_policy_hash,
12471
13355
  "qualification receipt model identity differs"
12472
13356
  );
12473
- require2(equal(computeChannel5.blake2_256(input), receipt.hIn), "qualification purpose/envelope preimage differs");
13357
+ require2(equal(computeChannel7.blake2_256(input), receipt.hIn), "qualification purpose/envelope preimage differs");
12474
13358
  require2(
12475
13359
  receipt.promptTokens !== void 0 && receipt.completionTokens !== void 0 && receipt.finishReason,
12476
13360
  "qualification authoritative provider usage is missing"
12477
13361
  );
12478
- require2(equal(computeChannel5.turnEvidenceHash(
13362
+ require2(equal(computeChannel7.turnEvidenceHash(
12479
13363
  receipt.output,
12480
13364
  receipt.promptTokens,
12481
13365
  receipt.completionTokens,
12482
13366
  receipt.finishReason,
12483
13367
  receipt.modelIdentity
12484
13368
  ), receipt.hOut), "qualification output evidence hash differs");
12485
- computeChannel5.validateRenderedChatReceipt(input, receipt);
13369
+ computeChannel7.validateRenderedChatReceipt(input, receipt);
12486
13370
  require2(Array.isArray(evidence.frames) && evidence.frames.length > 0, "qualification signed frames are missing");
12487
13371
  const frames = evidence.frames.map((frame) => ({ kind: frame.kind, data: bytes2(frame.data) }));
12488
- computeChannel5.reconstructSignedTurnOutput(frames, receipt);
13372
+ computeChannel7.reconstructSignedTurnOutput(frames, receipt);
12489
13373
  assertQualificationToolCalls(qualificationSignedToolCalls(frames), receipt.finishReason);
12490
13374
  require2(
12491
13375
  Array.isArray(evidence.prefix) && evidence.prefix.length === receipt.turnIndex + 1 && evidence.prefix.length <= 1024,
12492
13376
  "qualification signed prefix is incomplete"
12493
13377
  );
12494
- const accumulator = new computeChannel5.MerkleAccumulator();
13378
+ const accumulator = new computeChannel7.MerkleAccumulator();
12495
13379
  let aggregate2 = 0n;
12496
- const selectedLeaf = computeChannel5.reconstructLeafVersioned(
13380
+ const selectedLeaf = computeChannel7.reconstructLeafVersioned(
12497
13381
  receipt.leafVersion,
12498
13382
  bytes2(evidence.channel_id),
12499
13383
  receipt.turnIndex,
@@ -12511,7 +13395,7 @@ async function verifyOwnedToolsQualification(value, record, terminal, now = Date
12511
13395
  const t = row, gn = BigInt(t.gN), timing = [BigInt(t.minerRecvMs), BigInt(t.minerDoneMs), BigInt(t.latencyMs)];
12512
13396
  require2(t.turnIndex === index && gn > 0n && gn < 1n << 64n && t.decodePolicyHash === record.decode_policy_hash, "qualification prefix sequence/work differs");
12513
13397
  require2(
12514
- computeChannel5.verifyEnclaveLeaf(
13398
+ computeChannel7.verifyEnclaveLeaf(
12515
13399
  bytes2(evidence.enclave_key),
12516
13400
  t.leafVersion,
12517
13401
  bytes2(evidence.channel_id),
@@ -12527,7 +13411,7 @@ async function verifyOwnedToolsQualification(value, record, terminal, now = Date
12527
13411
  ),
12528
13412
  "qualification prefix signature is invalid"
12529
13413
  );
12530
- const leaf = computeChannel5.reconstructLeafVersioned(
13414
+ const leaf = computeChannel7.reconstructLeafVersioned(
12531
13415
  t.leafVersion,
12532
13416
  bytes2(evidence.channel_id),
12533
13417
  index,
@@ -12546,7 +13430,7 @@ async function verifyOwnedToolsQualification(value, record, terminal, now = Date
12546
13430
  }
12547
13431
  }
12548
13432
  const ack = evidence.ack;
12549
- require2(ack && ack.turnIndex === receipt.turnIndex && ack.leafHash === u8aToHex(selectedLeaf) && computeChannel5.verifyTurnAck(
13433
+ require2(ack && ack.turnIndex === receipt.turnIndex && ack.leafHash === u8aToHex(selectedLeaf) && computeChannel7.verifyTurnAck(
12550
13434
  bytes2(evidence.agent_key),
12551
13435
  bytes2(evidence.channel_id),
12552
13436
  receipt.turnIndex,
@@ -12556,7 +13440,7 @@ async function verifyOwnedToolsQualification(value, record, terminal, now = Date
12556
13440
  bytes2(ack.agentSig)
12557
13441
  ), "qualification turn ACK is invalid");
12558
13442
  const checkpoint = evidence.checkpoint;
12559
- require2(equal(accumulator.root(), receipt.runningRoot) && u8aToHex(accumulator.root()) === checkpoint.finalRoot && aggregate2.toString() === checkpoint.aggregateGn && computeChannel5.verifyReceipt(
13443
+ require2(equal(accumulator.root(), receipt.runningRoot) && u8aToHex(accumulator.root()) === checkpoint.finalRoot && aggregate2.toString() === checkpoint.aggregateGn && computeChannel7.verifyReceipt(
12560
13444
  bytes2(evidence.agent_key),
12561
13445
  bytes2(evidence.channel_id),
12562
13446
  bytes2(checkpoint.finalRoot),
@@ -12627,10 +13511,10 @@ async function readQualificationTerminal(api, eventId, channelId) {
12627
13511
  aggregateGn: data[3].toString(),
12628
13512
  enclaveKey: channel.enclaveKey.toHex(),
12629
13513
  agentKey: channel.agentKey.toHex(),
12630
- minerAccountId: u8aToHex(decodeAddress(channel.miner.toString())),
13514
+ minerAccountId: u8aToHex(decodeAddress2(channel.miner.toString())),
12631
13515
  modelHash: channel.modelHash.toHex(),
12632
13516
  measuredRoot: channel.measuredRoot.toHex(),
12633
- precision: computeChannel5.modelPrecisionCode(channel.precision.toString()),
13517
+ precision: computeChannel7.modelPrecisionCode(channel.precision.toString()),
12634
13518
  timestampMs: Number(stamp.toString()),
12635
13519
  grossRaw: gross.toString(),
12636
13520
  debitRaw: debit.toString()
@@ -12695,7 +13579,7 @@ function tailnetNodeRef(source) {
12695
13579
  return match ? { host: match[1], egress: match[2], asn: match[3] } : null;
12696
13580
  }
12697
13581
  function readTailscaleStatus(socketPath) {
12698
- return new Promise((resolve10, reject) => {
13582
+ return new Promise((resolve11, reject) => {
12699
13583
  const req = request2({
12700
13584
  socketPath,
12701
13585
  path: "/localapi/v0/status",
@@ -12710,7 +13594,7 @@ function readTailscaleStatus(socketPath) {
12710
13594
  res.on("end", () => {
12711
13595
  if (res.statusCode !== 200) return reject(new Error(`tailscaled status returned HTTP ${res.statusCode}`));
12712
13596
  try {
12713
- resolve10(JSON.parse(body));
13597
+ resolve11(JSON.parse(body));
12714
13598
  } catch {
12715
13599
  reject(new Error("tailscaled status is not JSON"));
12716
13600
  }
@@ -12780,7 +13664,7 @@ function conformanceWindows(record) {
12780
13664
  }
12781
13665
  var ProbeSignerUnderfunded = class extends Error {
12782
13666
  };
12783
- function reason(error) {
13667
+ function reason2(error) {
12784
13668
  return error instanceof Error ? error.message : String(error);
12785
13669
  }
12786
13670
  async function renewGatewayEvidence(miners, records, observe) {
@@ -12789,7 +13673,7 @@ async function renewGatewayEvidence(miners, records, observe) {
12789
13673
  const withdrawn = [];
12790
13674
  const observed = [];
12791
13675
  let underfunded;
12792
- for (const miner of miners) {
13676
+ const observations = await mapAdmissions(miners, async (miner) => {
12793
13677
  const subject = `${miner.minerAddress}:${miner.modelIdentity.requestedSlug}`;
12794
13678
  try {
12795
13679
  const declared = miner.deployment;
@@ -12801,8 +13685,7 @@ async function renewGatewayEvidence(miners, records, observe) {
12801
13685
  if (miner.admissionExpiresAt) {
12802
13686
  await observe.miner(miner);
12803
13687
  if (Date.parse(miner.admissionExpiresAt) <= observe.now()) throw new Error("miner admission expired");
12804
- observed.push(miner);
12805
- continue;
13688
+ return { miner };
12806
13689
  }
12807
13690
  if (node) {
12808
13691
  session = await observe.miner(miner);
@@ -12812,7 +13695,7 @@ async function renewGatewayEvidence(miners, records, observe) {
12812
13695
  session = await observe.miner(miner);
12813
13696
  }
12814
13697
  const observedAt = observe.now();
12815
- deployments.push({
13698
+ const deployment = {
12816
13699
  miner_address: miner.minerAddress,
12817
13700
  endpoint: miner.endpoint,
12818
13701
  enclave_key: miner.enclaveKey,
@@ -12822,11 +13705,16 @@ async function renewGatewayEvidence(miners, records, observe) {
12822
13705
  observed_at: new Date(observedAt).toISOString(),
12823
13706
  expires_at: new Date(observedAt + ttl).toISOString(),
12824
13707
  observer: `${provider}; ${session}`
12825
- });
12826
- observed.push(miner);
13708
+ };
13709
+ return { miner, deployment };
12827
13710
  } catch (error) {
12828
- withdrawn.push({ subject, reason: `deployment not renewed: ${reason(error)}` });
13711
+ return { failure: { subject, reason: `deployment not renewed: ${reason2(error)}` } };
12829
13712
  }
13713
+ });
13714
+ for (const { miner, deployment, failure: failure2 } of observations) {
13715
+ if (failure2) withdrawn.push(failure2);
13716
+ if (miner) observed.push(miner);
13717
+ if (deployment) deployments.push(deployment);
12830
13718
  }
12831
13719
  for (const record of records) {
12832
13720
  for (const [feature, ttl] of conformanceWindows(record)) {
@@ -12865,7 +13753,7 @@ async function renewGatewayEvidence(miners, records, observe) {
12865
13753
  break;
12866
13754
  } catch (error) {
12867
13755
  if (error instanceof ProbeSignerUnderfunded) underfunded = error.message;
12868
- else failures.push(`${miner.minerAddress}: ${reason(error)}`);
13756
+ else failures.push(`${miner.minerAddress}: ${reason2(error)}`);
12869
13757
  }
12870
13758
  }
12871
13759
  if (underfunded) withdrawn.push({ subject, reason: `probe not run: ${underfunded}` });
@@ -12885,8 +13773,8 @@ async function renewGatewayEvidence(miners, records, observe) {
12885
13773
  }
12886
13774
  function planckToFlop(planck) {
12887
13775
  const scale = 10n ** BigInt(FLOP_DECIMALS);
12888
- const fraction = (planck % scale).toString().padStart(FLOP_DECIMALS, "0").replace(/0+$/, "");
12889
- return fraction ? `${planck / scale}.${fraction}` : `${planck / scale}`;
13776
+ const fraction2 = (planck % scale).toString().padStart(FLOP_DECIMALS, "0").replace(/0+$/, "");
13777
+ return fraction2 ? `${planck / scale}.${fraction2}` : `${planck / scale}`;
12890
13778
  }
12891
13779
  async function assertProbeSignerFunded(api, address, escrow) {
12892
13780
  const head = await api.rpc.chain.getFinalizedHead();
@@ -12926,10 +13814,10 @@ async function writeEvidenceFile(path5, evidence, appendQualification = false) {
12926
13814
  parseGatewayEvidence(evidence);
12927
13815
  await writeFile13(temporary, `${JSON.stringify(evidence, null, 2)}
12928
13816
  `, { mode: 420 });
12929
- await rename9(temporary, path5);
13817
+ await rename10(temporary, path5);
12930
13818
  } finally {
12931
13819
  await lock.close();
12932
- await rm5(`${path5}.lock`);
13820
+ await rm7(`${path5}.lock`);
12933
13821
  }
12934
13822
  }
12935
13823
  async function finishRenewal(report, out, output) {
@@ -12978,7 +13866,7 @@ function assertToolsConformance(calls, finishReason) {
12978
13866
  }
12979
13867
  return `signed ${PROBE_TOOL.function.name} tool call`;
12980
13868
  }
12981
- function hex3(bytes4) {
13869
+ function hex4(bytes4) {
12982
13870
  return `0x${Buffer.from(bytes4).toString("hex")}`;
12983
13871
  }
12984
13872
  function bytes3(value) {
@@ -13004,7 +13892,7 @@ async function waitForSettlementAllowance(api, channelId, aggregateGn, deadline)
13004
13892
  const snapshot = await query.computeChannel.channelCalibrationSnapshots.at(head, channelId);
13005
13893
  const current = (await api.rpc.state.call(
13006
13894
  "TrustRootApi_accepted_calibration_cap",
13007
- hex3(Uint8Array.from([128, ...decodeAddress2(opened.miner.toString())])),
13895
+ hex4(Uint8Array.from([128, ...decodeAddress3(opened.miner.toString())])),
13008
13896
  head
13009
13897
  )).toU8a(true);
13010
13898
  if (!snapshot?.isSome || current.length !== 9 || current[0] !== 1) throw new Error("settlement calibration is unavailable");
@@ -13053,7 +13941,7 @@ async function reconcileProbeClose(api, channelId, aggregateGn, deadline, submit
13053
13941
  const events = [...await bounded(at.query.system.events())];
13054
13942
  for (const row of events) {
13055
13943
  const event = row.event;
13056
- if (event.section !== "computeChannel" || !["Settled", "TimedOutRefunded"].includes(event.method) || event.data[0].toHex() !== hex3(channelId) || !row.phase.isApplyExtrinsic) continue;
13944
+ if (event.section !== "computeChannel" || !["Settled", "TimedOutRefunded"].includes(event.method) || event.data[0].toHex() !== hex4(channelId) || !row.phase.isApplyExtrinsic) continue;
13057
13945
  const index = row.phase.asApplyExtrinsic.toNumber();
13058
13946
  if (!events.some((other) => other.phase.isApplyExtrinsic && other.phase.asApplyExtrinsic.toNumber() === index && other.event.section === "system" && other.event.method === "ExtrinsicSuccess")) continue;
13059
13947
  if (event.method === "TimedOutRefunded") return `refunded at finalized ${blockHash.toHex()}`;
@@ -13081,7 +13969,7 @@ async function settleProbe(api, channelId, aggregateGn, deadline, allowance2, su
13081
13969
  await forceClose();
13082
13970
  } catch {
13083
13971
  }
13084
- return `cooperative settlement not submitted; force close attempted (${reason(error)})`;
13972
+ return `cooperative settlement not submitted; force close attempted (${reason2(error)})`;
13085
13973
  }
13086
13974
  return reconcileProbeClose(api, channelId, aggregateGn, deadline, submit);
13087
13975
  }
@@ -13091,7 +13979,7 @@ async function renewalMinerInfo(api, miner, fetchImpl = fetch) {
13091
13979
  const registered = await at.query.minerStaking.miners(miner.minerAddress);
13092
13980
  const active = registered?.isSome && registered.unwrap().isActive;
13093
13981
  if (!(active?.isTrue ?? active === true)) throw new Error("miner is not registered and active at finality");
13094
- const encoded = await api.rpc.state.call("TrustRootApi_accepted_calibration_cap", `0x80${hex3(decodeAddress2(miner.minerAddress)).slice(2)}`, head);
13982
+ const encoded = await api.rpc.state.call("TrustRootApi_accepted_calibration_cap", `0x80${hex4(decodeAddress3(miner.minerAddress)).slice(2)}`, head);
13095
13983
  const cap = encoded.toU8a(true);
13096
13984
  if (cap.length !== 9 || cap[0] !== 1 || new DataView(cap.buffer, cap.byteOffset + 1, 8).getBigUint64(0, true) <= 0n) throw new Error("miner has no finalized calibrated capacity");
13097
13985
  const identity = {
@@ -13105,13 +13993,13 @@ async function renewalMinerInfo(api, miner, fetchImpl = fetch) {
13105
13993
  await anonymous.body?.cancel();
13106
13994
  if (anonymous.status !== 401) throw new Error("miner /session/info does not enforce authentication");
13107
13995
  }
13108
- const info = miner.channelSigned ? await computeChannel6.fetchProvenSessionInfo(miner.endpoint, {
13996
+ const info = miner.channelSigned ? await computeChannel8.fetchProvenSessionInfo(miner.endpoint, {
13109
13997
  genesisHash: api.genesisHash.toU8a(),
13110
- account: decodeAddress2(miner.minerAddress),
13998
+ account: decodeAddress3(miner.minerAddress),
13111
13999
  minerAddress: miner.minerAddress,
13112
14000
  enclaveKey: bytes3(miner.enclaveKey),
13113
14001
  modelIdentity: identity
13114
- }, fetchImpl, AbortSignal.timeout(3e4)) : await computeChannel6.fetchMinerSessionInfo(
14002
+ }, fetchImpl, AbortSignal.timeout(3e4)) : await computeChannel8.fetchMinerSessionInfo(
13115
14003
  miner.endpoint,
13116
14004
  { Authorization: `Bearer ${miner.authToken}` },
13117
14005
  AbortSignal.timeout(3e4),
@@ -13121,13 +14009,13 @@ async function renewalMinerInfo(api, miner, fetchImpl = fetch) {
13121
14009
  const wire = {
13122
14010
  ...info,
13123
14011
  is_ready: info.isReady,
13124
- enclaveKey: hex3(info.enclaveKey),
13125
- modelHash: hex3(info.modelHash),
14012
+ enclaveKey: hex4(info.enclaveKey),
14013
+ modelHash: hex4(info.modelHash),
13126
14014
  modelIdentity: {
13127
14015
  ...info.modelIdentity,
13128
- modelHash: hex3(info.modelIdentity.modelHash),
13129
- decodePolicyHash: hex3(info.modelIdentity.decodePolicyHash),
13130
- measuredRoot: hex3(info.modelIdentity.measuredRoot)
14016
+ modelHash: hex4(info.modelIdentity.modelHash),
14017
+ decodePolicyHash: hex4(info.modelIdentity.decodePolicyHash),
14018
+ measuredRoot: hex4(info.modelIdentity.measuredRoot)
13131
14019
  }
13132
14020
  };
13133
14021
  const snapshot = await readChainSnapshot(api, wire, head.toHex());
@@ -13156,7 +14044,7 @@ async function probeToolsConformance(input) {
13156
14044
  let requestHeaders;
13157
14045
  const pinned = miner.modelIdentity;
13158
14046
  const advertised = info.modelIdentity;
13159
- if (info.minerAddress !== miner.minerAddress || hex3(info.enclaveKey) !== miner.enclaveKey.toLowerCase() || !advertised || advertised.requestedSlug !== pinned.requestedSlug || advertised.registryModel !== pinned.registryModel || advertised.registryVersion !== pinned.registryVersion || hex3(advertised.modelHash) !== pinned.modelHash || advertised.precision !== pinned.precision || hex3(advertised.decodePolicyHash) !== pinned.decodePolicyHash || hex3(advertised.measuredRoot) !== pinned.measuredRoot || advertised.backendModelName !== pinned.backendModelName) {
14047
+ if (info.minerAddress !== miner.minerAddress || hex4(info.enclaveKey) !== miner.enclaveKey.toLowerCase() || !advertised || advertised.requestedSlug !== pinned.requestedSlug || advertised.registryModel !== pinned.registryModel || advertised.registryVersion !== pinned.registryVersion || hex4(advertised.modelHash) !== pinned.modelHash || advertised.precision !== pinned.precision || hex4(advertised.decodePolicyHash) !== pinned.decodePolicyHash || hex4(advertised.measuredRoot) !== pinned.measuredRoot || advertised.backendModelName !== pinned.backendModelName) {
13160
14048
  throw new Error("miner no longer advertises its allowlisted identity");
13161
14049
  }
13162
14050
  let version;
@@ -13173,7 +14061,7 @@ async function probeToolsConformance(input) {
13173
14061
  const maxSettlementTurns = Number(api.consts.computeChannel.maxSettlementTurns.toString());
13174
14062
  const frames = [];
13175
14063
  let sequence = 0;
13176
- const transport = new computeChannel6.HttpSessionTransport(miner.endpoint, PROBE_TIMEOUT_MS, {
14064
+ const transport = new computeChannel8.HttpSessionTransport(miner.endpoint, PROBE_TIMEOUT_MS, {
13177
14065
  headers,
13178
14066
  fetchImpl,
13179
14067
  requestHeaders: (method, path5, body) => requestHeaders?.(method, path5, body) ?? {},
@@ -13182,10 +14070,10 @@ async function probeToolsConformance(input) {
13182
14070
  if (frame.type === "chunk") frames.push({ kind: frame.kind, data: bytes3(frame.data) });
13183
14071
  }
13184
14072
  });
13185
- const agentKey = computeChannel6.SessionKey.generate();
14073
+ const agentKey = computeChannel8.SessionKey.generate();
13186
14074
  const identity = { ...pinned, modelHash: bytes3(pinned.modelHash), decodePolicyHash: bytes3(pinned.decodePolicyHash), measuredRoot: bytes3(pinned.measuredRoot) };
13187
14075
  input.assertAdmission?.();
13188
- const session = await computeChannel6.ChannelSession.open({
14076
+ const session = await computeChannel8.ChannelSession.open({
13189
14077
  chain,
13190
14078
  transport,
13191
14079
  enclaveKey: info.enclaveKey,
@@ -13205,13 +14093,13 @@ async function probeToolsConformance(input) {
13205
14093
  maxDuration: BigInt(miner.maxDurationBlocks),
13206
14094
  reattestEvery: 1,
13207
14095
  escrow: input.escrow,
13208
- nonce: BigInt(hex3(randomAsU8a(8)))
14096
+ nonce: BigInt(hex4(randomAsU8a(8)))
13209
14097
  });
13210
14098
  transport.bind(session.channelId);
13211
14099
  let verdict;
13212
14100
  let receipt;
13213
14101
  try {
13214
- if (miner.channelSigned) requestHeaders = await computeChannel6.channelSessionAuth(
14102
+ if (miner.channelSigned) requestHeaders = await computeChannel8.channelSessionAuth(
13215
14103
  miner.endpoint,
13216
14104
  session.channelId,
13217
14105
  agentKey,
@@ -13219,7 +14107,7 @@ async function probeToolsConformance(input) {
13219
14107
  fetchImpl,
13220
14108
  AbortSignal.timeout(3e4)
13221
14109
  );
13222
- await computeChannel6.notifyMinerOpen(miner.endpoint, session.channelId, agentKey.publicKey, headers, AbortSignal.timeout(3e4), requestHeaders, fetchImpl);
14110
+ await computeChannel8.notifyMinerOpen(miner.endpoint, session.channelId, agentKey.publicKey, headers, AbortSignal.timeout(3e4), requestHeaders, fetchImpl);
13223
14111
  receipt = await session.chatTurn({
13224
14112
  version,
13225
14113
  model: record.openrouter_slug,
@@ -13228,7 +14116,7 @@ async function probeToolsConformance(input) {
13228
14116
  tool_choice: "auto",
13229
14117
  max_tokens: 192
13230
14118
  }, (candidate) => {
13231
- computeChannel6.reconstructSignedTurnOutput(frames, candidate);
14119
+ computeChannel8.reconstructSignedTurnOutput(frames, candidate);
13232
14120
  verdict = assertToolsConformance(signedToolCalls(frames), candidate.finishReason);
13233
14121
  });
13234
14122
  } catch (error) {
@@ -13245,7 +14133,7 @@ async function probeToolsConformance(input) {
13245
14133
  deadline,
13246
14134
  () => waitForSettlementAllowance(api, session.channelId, receipt.gN, deadline),
13247
14135
  () => session.closeCooperative(async (claim) => {
13248
- const wire = await computeChannel6.notifyMinerClose(miner.endpoint, claim, headers, AbortSignal.timeout(3e4), requestHeaders, fetchImpl);
14136
+ const wire = await computeChannel8.notifyMinerClose(miner.endpoint, claim, headers, AbortSignal.timeout(3e4), requestHeaders, fetchImpl);
13249
14137
  if (!wire) throw new Error("miner did not report a settlement");
13250
14138
  return {
13251
14139
  channelId: bytes3(wire.channelId),
@@ -13258,7 +14146,7 @@ async function probeToolsConformance(input) {
13258
14146
  }),
13259
14147
  () => session.close()
13260
14148
  );
13261
- return `${verdict} in channel ${hex3(session.channelId)}; ${settlement}`;
14149
+ return `${verdict} in channel ${hex4(session.channelId)}; ${settlement}`;
13262
14150
  }
13263
14151
  function envJson(name) {
13264
14152
  const value = process.env[name];
@@ -13275,7 +14163,7 @@ function envSecret(name) {
13275
14163
  return value;
13276
14164
  }
13277
14165
  function gatewayRenewCmd() {
13278
- return new Command48("renew").description("Re-observe allowlisted miners, re-run feature conformance, and write the gateway evidence file").option("--allowlist-env <name>", "Direct probe mode: environment variable holding GATEWAY_SESSION_ALLOWLIST (never printed)").requiredOption("--catalog-env <name>", "Environment variable holding GATEWAY_MODEL_CATALOG").option("--catalog-pricing <mode>", "The gateway's GATEWAY_CATALOG_PRICING: operator, or chain when the catalog omits prices", "operator").option("--provider-key-env <name>", "Direct probe mode: environment variable holding the GPU.ai API key").option("--tailscale-socket <path>", "tailscaled LocalAPI socket for tailnet-hosted miners", "/var/run/tailscale/tailscaled.sock").option("--probe-signer-env <name>", "Direct probe mode: environment variable holding the dedicated funded probe signer URI").option("--probe-escrow <flop>", "Direct probe mode: FLOP escrowed for each conformance probe channel").option("--qualification-receipt <path>", "Receipt-only mode: settled owner tools-qualification evidence; never runs another probe").option("--qualification-receipt-sha256 <hash>", "SHA-256 of the immutable owner qualification receipt").requiredOption("--out <path>", "Evidence file the gateway reads as GATEWAY_EVIDENCE_FILE").option("--json", "Output as JSON").action(async (opts, command) => {
14166
+ return new Command50("renew").description("Re-observe allowlisted miners, re-run feature conformance, and write the gateway evidence file").option("--allowlist-env <name>", "Direct probe mode: environment variable holding GATEWAY_SESSION_ALLOWLIST (never printed)").requiredOption("--catalog-env <name>", "Environment variable holding GATEWAY_MODEL_CATALOG").option("--catalog-pricing <mode>", "The gateway's GATEWAY_CATALOG_PRICING: operator, or chain when the catalog omits prices", "operator").option("--provider-key-env <name>", "Direct probe mode: environment variable holding the GPU.ai API key").option("--tailscale-socket <path>", "tailscaled LocalAPI socket for tailnet-hosted miners", "/var/run/tailscale/tailscaled.sock").option("--probe-signer-env <name>", "Direct probe mode: environment variable holding the dedicated funded probe signer URI").option("--probe-escrow <flop>", "Direct probe mode: FLOP escrowed for each conformance probe channel").option("--qualification-receipt <path>", "Receipt-only mode: settled owner tools-qualification evidence; never runs another probe").option("--qualification-receipt-sha256 <hash>", "SHA-256 of the immutable owner qualification receipt").requiredOption("--out <path>", "Evidence file the gateway reads as GATEWAY_EVIDENCE_FILE").option("--json", "Output as JSON").action(async (opts, command) => {
13279
14167
  let client;
13280
14168
  try {
13281
14169
  if (opts.catalogPricing !== "operator" && opts.catalogPricing !== "chain") {
@@ -13286,7 +14174,7 @@ function gatewayRenewCmd() {
13286
14174
  throw new Error("receipt-only mode requires the receipt/hash and cannot run direct probes");
13287
14175
  }
13288
14176
  const raw = await readFile11(opts.qualificationReceipt);
13289
- if (raw.length > 8e6 || createHash7("sha256").update(raw).digest("hex") !== opts.qualificationReceiptSha256) throw new Error("qualification receipt bytes differ");
14177
+ if (raw.length > 8e6 || createHash9("sha256").update(raw).digest("hex") !== opts.qualificationReceiptSha256) throw new Error("qualification receipt bytes differ");
13290
14178
  const value = JSON.parse(raw.toString("utf8"));
13291
14179
  const records2 = parseCanonicalCatalog(envJson(opts.catalogEnv), { chainPriced: opts.catalogPricing === "chain" });
13292
14180
  const record = records2.find((item) => item.openrouter_slug === value.model);
@@ -13296,7 +14184,7 @@ function gatewayRenewCmd() {
13296
14184
  const evidence = parseGatewayEvidence(JSON.parse(await readFile11(opts.out, "utf8")));
13297
14185
  const renewed = renewConformance(record, evidence, tested);
13298
14186
  client = await createClient(getRpc(command));
13299
- await computeChannel6.ready();
14187
+ await computeChannel8.ready();
13300
14188
  const pass = await verifyOwnedToolsQualification(value, renewed, (id, channel) => readQualificationTerminal(client.api, id, channel));
13301
14189
  await writeEvidenceFile(opts.out, { schema_version: GATEWAY_EVIDENCE_VERSION, generated_at: (/* @__PURE__ */ new Date()).toISOString(), deployments: [], conformance: [pass] }, true);
13302
14190
  print({ out: opts.out, conformance: [pass], receipt_only: true, paid_probe_count: 0 }, getJson(command));
@@ -13319,9 +14207,9 @@ function gatewayRenewCmd() {
13319
14207
  const escrow = flopToPlanck(opts.probeEscrow);
13320
14208
  client = await createClient(getRpc(command));
13321
14209
  const api = client.api;
13322
- await computeChannel6.ready();
14210
+ await computeChannel8.ready();
13323
14211
  const signer = new Keyring5({ type: "sr25519" }).addFromUri(signerUri);
13324
- const chain = new computeChannel6.ComputeChannelChainClient(new computeChannel6.FlopChainSubmitter(api, signer, true));
14212
+ const chain = new computeChannel8.ComputeChannelChainClient(new computeChannel8.FlopChainSubmitter(api, signer, true));
13325
14213
  const assertAdmission = (miner) => {
13326
14214
  if (!miner.admissionExpiresAt) return;
13327
14215
  const dynamic = admissions?.read() ?? [];
@@ -13337,13 +14225,13 @@ function gatewayRenewCmd() {
13337
14225
  const info = {
13338
14226
  ...proven,
13339
14227
  is_ready: proven.isReady,
13340
- enclaveKey: hex3(proven.enclaveKey),
13341
- modelHash: hex3(proven.modelHash),
14228
+ enclaveKey: hex4(proven.enclaveKey),
14229
+ modelHash: hex4(proven.modelHash),
13342
14230
  modelIdentity: {
13343
14231
  ...proven.modelIdentity,
13344
- modelHash: hex3(proven.modelIdentity.modelHash),
13345
- decodePolicyHash: hex3(proven.modelIdentity.decodePolicyHash),
13346
- measuredRoot: hex3(proven.modelIdentity.measuredRoot)
14232
+ modelHash: hex4(proven.modelIdentity.modelHash),
14233
+ decodePolicyHash: hex4(proven.modelIdentity.decodePolicyHash),
14234
+ measuredRoot: hex4(proven.modelIdentity.measuredRoot)
13347
14235
  }
13348
14236
  };
13349
14237
  const pinned = miner.modelIdentity;
@@ -13369,9 +14257,9 @@ function gatewayRenewCmd() {
13369
14257
  }
13370
14258
 
13371
14259
  // src/release-check.ts
13372
- import { mkdir as mkdir14, readFile as readFile12, rename as rename10, writeFile as writeFile14 } from "node:fs/promises";
14260
+ import { mkdir as mkdir15, readFile as readFile12, rename as rename11, writeFile as writeFile14 } from "node:fs/promises";
13373
14261
  import { homedir as homedir5 } from "node:os";
13374
- import { dirname as dirname8, join as join10 } from "node:path";
14262
+ import { dirname as dirname9, join as join11 } from "node:path";
13375
14263
  var DAY_MS = 24 * 60 * 60 * 1e3;
13376
14264
  var READ_ONLY = /* @__PURE__ */ new Set([
13377
14265
  "health",
@@ -13415,7 +14303,7 @@ async function checkRelease(options) {
13415
14303
  if (options.offline || (options.testing ?? (process.env.NODE_ENV === "test" || !!process.env.VITEST))) return;
13416
14304
  const warn = options.warn ?? ((message) => console.error(message));
13417
14305
  const now = options.now ?? Date.now();
13418
- const cacheFile = options.cacheFile ?? join10(process.env.XDG_CACHE_HOME || join10(homedir5(), ".cache"), "flop", "releases.json");
14306
+ const cacheFile = options.cacheFile ?? join11(process.env.XDG_CACHE_HOME || join11(homedir5(), ".cache"), "flop", "releases.json");
13419
14307
  let releases;
13420
14308
  try {
13421
14309
  const cached = JSON.parse(await readFile12(cacheFile, "utf8"));
@@ -13430,10 +14318,10 @@ async function checkRelease(options) {
13430
14318
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
13431
14319
  releases = parseOperatorReleases(await response.json());
13432
14320
  try {
13433
- await mkdir14(dirname8(cacheFile), { recursive: true });
14321
+ await mkdir15(dirname9(cacheFile), { recursive: true });
13434
14322
  const temporary = `${cacheFile}.${process.pid}.tmp`;
13435
14323
  await writeFile14(temporary, JSON.stringify({ fetchedAt: now, releases }), { mode: 384 });
13436
- await rename10(temporary, cacheFile);
14324
+ await rename11(temporary, cacheFile);
13437
14325
  } catch {
13438
14326
  warn("warning: could not cache operator releases");
13439
14327
  }
@@ -13457,10 +14345,10 @@ async function checkRelease(options) {
13457
14345
  }
13458
14346
 
13459
14347
  // src/cli.ts
13460
- var program = new Command49();
14348
+ var program = new Command51();
13461
14349
  var outputMode = outputModeFromArgv(process.argv.slice(2));
13462
14350
  setOutputMode(outputMode);
13463
- program.name("flop").description("FLOP Network CLI").version(operatorVersion(JSON.parse(readFileSync2(join11(packageRoot, "package.json"), "utf8")).version)).option("--offline", "Skip the operator release check").addOption(new Option8("--network <network>", "Network profile").choices(["local", "devnet", "testnet", "mainnet"]).default("local")).option("--rpc <url>", "RPC endpoint override").option("--signer <uri>", "Signer URI (local/dev only; required outside local unless --keystore)").option("--keystore <path>", "Encrypted Polkadot JSON keystore").option("--password-env <name>", "Environment variable containing the keystore password").addOption(new Option8("--output <mode>", "Output mode").choices(["human", "json", "agent"]).default("human")).option("--json", "Output as JSON");
14351
+ program.name("flop").description("FLOP Network CLI").version(operatorVersion(JSON.parse(readFileSync2(join12(packageRoot, "package.json"), "utf8")).version)).option("--offline", "Skip the operator release check").addOption(new Option8("--network <network>", "Network profile").choices(["local", "devnet", "testnet", "mainnet"]).default("local")).option("--rpc <url>", "RPC endpoint override").option("--signer <uri>", "Signer URI (local/dev only; required outside local unless --keystore)").option("--keystore <path>", "Encrypted Polkadot JSON keystore").option("--password-env <name>", "Environment variable containing the keystore password").addOption(new Option8("--output <mode>", "Output mode").choices(["human", "json", "agent"]).default("human")).option("--json", "Output as JSON");
13464
14352
  program.addCommand(healthCmd());
13465
14353
  program.addCommand(balanceCmd());
13466
14354
  program.addCommand(gpuCmd());
@@ -13472,13 +14360,13 @@ program.addCommand(chatCmd());
13472
14360
  program.addCommand(agentCmd());
13473
14361
  program.addCommand(minerCmd());
13474
14362
  program.addCommand(validatorCmd());
13475
- var networkCmd = new Command49("network").description("Resolve and inspect network profiles");
14363
+ var networkCmd = new Command51("network").description("Resolve and inspect network profiles");
13476
14364
  networkCmd.addCommand(networkStatusCmd());
13477
14365
  program.addCommand(networkCmd);
13478
- var faucetCmd = new Command49("faucet").description("Request dev/test FLOP");
14366
+ var faucetCmd = new Command51("faucet").description("Request dev/test FLOP");
13479
14367
  faucetCmd.addCommand(faucetRequestCmd());
13480
14368
  program.addCommand(faucetCmd);
13481
- var sessionCmd = new Command49("session").description("Manage session keys");
14369
+ var sessionCmd = new Command51("session").description("Manage session keys");
13482
14370
  sessionCmd.addCommand(sessionRunCmd());
13483
14371
  sessionCmd.addCommand(sessionListCmd());
13484
14372
  sessionCmd.addCommand(sessionCreateCmd());
@@ -13486,7 +14374,7 @@ sessionCmd.addCommand(sessionInfoCmd());
13486
14374
  sessionCmd.addCommand(sessionRevokeCmd());
13487
14375
  sessionCmd.addCommand(sessionLimitsCmd());
13488
14376
  program.addCommand(sessionCmd);
13489
- var keysCmd = new Command49("keys").description("Manage keys");
14377
+ var keysCmd = new Command51("keys").description("Manage keys");
13490
14378
  keysCmd.addCommand(keysGenerateCmd());
13491
14379
  keysCmd.addCommand(keysInspectCmd());
13492
14380
  keysCmd.addCommand(keysHardwareCmd());
@@ -13494,35 +14382,36 @@ keysCmd.addCommand(keysImportCmd());
13494
14382
  keysCmd.addCommand(keysExportCmd());
13495
14383
  keysCmd.addCommand(keysClaudeProfileCmd());
13496
14384
  program.addCommand(keysCmd);
13497
- var walletCmd = new Command49("wallet").description("Manage wallets");
13498
- var agentWalletCmd = new Command49("agent").description("Agent wallet operations");
14385
+ var walletCmd = new Command51("wallet").description("Manage wallets");
14386
+ var agentWalletCmd = new Command51("agent").description("Agent wallet operations");
13499
14387
  agentWalletCmd.addCommand(walletAgentCreateCmd());
13500
14388
  walletCmd.addCommand(agentWalletCmd);
13501
14389
  program.addCommand(walletCmd);
13502
- var txCmd = new Command49("tx").description("Build, sign, and broadcast transactions");
14390
+ var txCmd = new Command51("tx").description("Build, sign, and broadcast transactions");
13503
14391
  txCmd.addCommand(txBuildCmd());
13504
14392
  txCmd.addCommand(txBroadcastCmd());
13505
14393
  program.addCommand(txCmd);
13506
14394
  program.addCommand(multisigCmd());
13507
14395
  program.addCommand(channelCmd());
13508
- var gatewayCmd = new Command49("gateway").description("Generate verified gateway configuration");
14396
+ var gatewayCmd = new Command51("gateway").description("Generate verified gateway configuration");
13509
14397
  gatewayCmd.addCommand(gatewayAllowlistCmd());
13510
14398
  gatewayCmd.addCommand(gatewayRenewCmd());
13511
14399
  program.addCommand(gatewayCmd);
13512
- var x402Cmd = new Command49("x402").description("x402 payment gateway operations");
14400
+ var x402Cmd = new Command51("x402").description("x402 payment gateway operations");
13513
14401
  x402Cmd.addCommand(x402SettleCmd());
13514
14402
  program.addCommand(x402Cmd);
13515
- var daCmd = new Command49("da").description("Sovereign DA registry (leases, deposits)");
14403
+ var daCmd = new Command51("da").description("Sovereign DA registry (leases, deposits)");
13516
14404
  daCmd.addCommand(daDepositCmd());
13517
14405
  daCmd.addCommand(daRegisterCmd());
13518
14406
  daCmd.addCommand(daTopupCmd());
13519
14407
  daCmd.addCommand(daWithdrawCmd());
13520
14408
  program.addCommand(daCmd);
13521
- var modelCmd = new Command49("model").description("Model registry operations");
14409
+ var modelCmd = new Command51("model").description("Model registry operations");
13522
14410
  modelCmd.addCommand(modelListCmd());
13523
14411
  modelCmd.addCommand(modelGetCmd());
13524
14412
  modelCmd.addCommand(modelFetchCmd());
13525
14413
  modelCmd.addCommand(modelUploadCmd());
14414
+ modelCmd.addCommand(modelMirrorsCmd());
13526
14415
  modelCmd.addCommand(modelPreflightCmd());
13527
14416
  modelCmd.addCommand(modelOnboardCmd());
13528
14417
  modelCmd.addCommand(modelBackupCmd());
@@ -13532,7 +14421,7 @@ program.hook("preAction", async (_root, action) => {
13532
14421
  const path5 = [];
13533
14422
  for (let command = action; command && command !== program; command = command.parent) path5.unshift(command.name());
13534
14423
  await checkRelease({
13535
- version: operatorVersion(JSON.parse(readFileSync2(join11(packageRoot, "package.json"), "utf8")).version),
14424
+ version: operatorVersion(JSON.parse(readFileSync2(join12(packageRoot, "package.json"), "utf8")).version),
13536
14425
  readOnly: isReadOnlyCommand(path5),
13537
14426
  offline: program.opts().offline,
13538
14427
  image: !!process.env.FLOP_IMAGE_VERSION