zk-credits 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,7 +85,7 @@ can observe traffic metadata. This does not hide your IP address.
85
85
 
86
86
  Base Sepolia test credits only. The circuit is not independently audited.
87
87
 
88
- ### Coding-task evidence (0.2.12 candidate)
88
+ ### Coding-task evidence (0.2.14 candidate)
89
89
 
90
90
  After setup, `zk-credits codex --task-evidence` launches Codex against a fresh,
91
91
  dedicated local sidecar and prints aggregate payment counters when Codex exits.
@@ -100,10 +100,10 @@ zk-credits codex --task-evidence
100
100
  ```
101
101
 
102
102
  Enter your task, review the change and tests, then exit Codex to see the summary.
103
- Version 0.2.12 is prepared for owner publication; 0.2.11 lacks this command.
103
+ Version 0.2.12 includes this command; 0.2.11 lacks it.
104
104
  See the [indexer sample](https://github.com/mangekyou-labs/haze-api/tree/main/examples/base-usdc-indexer).
105
105
 
106
- ### Transaction investigator (0.2.12 candidate)
106
+ ### Transaction investigator (0.2.14 candidate)
107
107
 
108
108
  After closing Codex, run `zk-credits investigate --chain base <transaction-hash>`
109
109
  with the same configured credential. It fetches public execution evidence and
@@ -112,8 +112,19 @@ the investigated chain; credit funding remains on Base Sepolia and consumption
112
112
  is offchain. It does not purchase external traces. Keep missing-evidence notices
113
113
  visible and distinguish observed errors from model hypotheses.
114
114
 
115
- Repeating a transaction hash recovers its existing local task; it need not make
116
- a new paid call or return a complete report. Do not delete task journals or reset
115
+ Version 0.2.14 fixes the investigator's packaged payment recipient pin and refuses
116
+ builds with missing or placeholder treasury addresses. Payment requirements must
117
+ still match the reviewed deployment before any proof is authorized.
118
+
119
+ Repeating a transaction hash recovers its existing local task without authorizing
120
+ a new paid call. The report shows the original stopping reason and labels prior
121
+ accounting separately from current invocation counters, including when the saved
122
+ run authorized no credits. Upgrading does not resume a stopped investigation.
123
+ Do not delete task journals or reset
117
124
  credential accounting to force a fresh exchange. Unknown payment outcomes must
118
125
  be reconciled before another paid task. A saved report must be labeled as a prior
119
126
  run, with its own payment summary.
127
+
128
+ `challengeObserved` counts challenges presented for authorization, including
129
+ refused terms. A challenge alone does not imply a proof or consumed credit;
130
+ check confirmed, failed, unknown and retired counters separately.
@@ -33,6 +33,7 @@
33
33
  "deployment": {
34
34
  "chainId": 84532,
35
35
  "bondAddress": "0xcc1909dD30485d6D9a46e3d33cf04C1378b586Ab",
36
+ "treasuryAddress": "0xa8b9b38e748563AF7FbAA276e8a843Da417D7Bc1",
36
37
  "deploymentBlock": "47372040",
37
38
  "deploymentDomain": "84532",
38
39
  "circuitId": "private-credit-spend-bn254-dev",
@@ -0,0 +1,18 @@
1
+ import { type CredentialStore } from './credential-store.js';
2
+ import { type ResolveSetupInputsOptions, type SetupConfig } from './setup-config.js';
3
+ export interface ActivatedCredentialSetupOptions {
4
+ environment?: NodeJS.ProcessEnv;
5
+ homeDirectory?: string;
6
+ stateDirectory?: string;
7
+ credentialStore?: CredentialStore;
8
+ chooseCandidate?: ResolveSetupInputsOptions['chooseCandidate'];
9
+ /** The caller owns hidden terminal input for explicit legacy import. */
10
+ readLegacyPassword?(): Promise<string>;
11
+ }
12
+ /**
13
+ * Imports or loads the credential and prepares validated runtime configuration.
14
+ * Configuration is saved only after all checks pass. Credential import, artifact
15
+ * acquisition and witness synchronization may persist progress on failure so a
16
+ * retry can reuse it. Applies RPC and successful setup paths to the environment.
17
+ */
18
+ export declare function setupActivatedCredential(options?: ActivatedCredentialSetupOptions): Promise<SetupConfig>;
@@ -0,0 +1,64 @@
1
+ /** Interactive credential preparation; runtime loading and launch remain separate. */
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { loadCircuitManifest } from './artifact-bundle.js';
5
+ import { validateBaseCreditWitness } from './base-sidecar.js';
6
+ import { importCredentialPath, nativeCredentialStore, readStoredCredential, } from './credential-store.js';
7
+ import { requireGatewayKnownRoot, resolveGatewayOrigin } from './gateway-root-check.js';
8
+ import { createPinnedBaseProofGenerator } from './proof-coordinator.js';
9
+ import { createBaseProofMetrics } from './proof-metrics.js';
10
+ import { applyRpcConfig, checkBaseSepoliaRpc, resolveBaseSepoliaRpc } from './rpc-config.js';
11
+ import { applySetupConfig, readSetupConfig, resolveSetupInputs, writeSetupConfig, } from './setup-config.js';
12
+ import { witnessProviderForSource } from './sidecar-runtime.js';
13
+ /**
14
+ * Imports or loads the credential and prepares validated runtime configuration.
15
+ * Configuration is saved only after all checks pass. Credential import, artifact
16
+ * acquisition and witness synchronization may persist progress on failure so a
17
+ * retry can reuse it. Applies RPC and successful setup paths to the environment.
18
+ */
19
+ export async function setupActivatedCredential(options = {}) {
20
+ const environment = options.environment ?? process.env;
21
+ const homeDirectory = options.homeDirectory ?? homedir();
22
+ const stateDirectory = options.stateDirectory ?? (environment.ZK_CREDITS_HOME || join(homeDirectory, '.zk-credits'));
23
+ const configPath = join(stateDirectory, 'config.json');
24
+ const credentialStore = options.credentialStore ?? nativeCredentialStore(stateDirectory);
25
+ await applyRpcConfig(environment, stateDirectory);
26
+ const saved = await readSetupConfig(configPath);
27
+ const rpc = resolveBaseSepoliaRpc(environment, saved?.witness.kind === 'base-events' ? saved.witness.rpcUrl : undefined);
28
+ await checkBaseSepoliaRpc(rpc);
29
+ environment.BASE_RPC_URL = rpc;
30
+ const credentialPath = environment.ZK_CREDITS_CREDENTIAL_PATH;
31
+ const legacyPassword = await options.readLegacyPassword?.();
32
+ const credential = credentialPath
33
+ ? await importCredentialPath(credentialPath, credentialStore, legacyPassword)
34
+ : await readStoredCredential(credentialStore);
35
+ const manifest = await loadCircuitManifest();
36
+ const config = await resolveSetupInputs({
37
+ environment,
38
+ homeDirectory,
39
+ stateDirectory,
40
+ configPath,
41
+ manifest,
42
+ validateWitness: async (source) => {
43
+ const provider = await witnessProviderForSource(source, join(stateDirectory, 'base-event-sync.json'));
44
+ const witness = validateBaseCreditWitness(await provider.witnessForCredential(credential));
45
+ if (manifest.deployment)
46
+ await requireGatewayKnownRoot(witness.root, resolveGatewayOrigin(environment));
47
+ },
48
+ chooseCandidate: options.chooseCandidate,
49
+ });
50
+ try {
51
+ await createPinnedBaseProofGenerator({
52
+ artifactDirectory: config.artifactDirectory,
53
+ metrics: createBaseProofMetrics(),
54
+ });
55
+ }
56
+ catch (error) {
57
+ if (error instanceof Error)
58
+ throw new Error(`Pinned proving bundle check failed: ${error.message}`);
59
+ throw new Error('Pinned proving bundle check failed. Reinstall the bundle from your pilot invite.');
60
+ }
61
+ await writeSetupConfig(configPath, config);
62
+ applySetupConfig(environment, config);
63
+ return config;
64
+ }
@@ -13,6 +13,7 @@ import { baseSepolia } from 'viem/chains';
13
13
  import { createPublicClient, http, parseAbi } from 'viem';
14
14
  import { BASE_MEMBERSHIP_TREE_CAPACITY, BN254_FIELD_ORDER, computeCreditLeaf, deriveSparseCreditWitness, } from '@zk-credits/shared/base';
15
15
  import { RPC_GUIDANCE } from './rpc-config.js';
16
+ import { rpcFetch } from './rpc-transport.js';
16
17
  const BUNDLE_FUNDED_ABI = parseAbi([
17
18
  'event BundleFunded(bytes32 indexed commitment, uint8 indexed tierId, uint64 expiry, uint256 leafIndex, uint256 bondAmount, bytes32 root)',
18
19
  ]);
@@ -82,12 +83,15 @@ function serializeLeaves(leaves) {
82
83
  }
83
84
  /** Creates a witness provider that syncs finalized BundleFunded events. */
84
85
  export function createBaseEventWitnessProvider(options) {
85
- const client = options.client ?? createPublicClient({ chain: baseSepolia, transport: http(options.rpcUrl) });
86
+ const client = options.client ?? createPublicClient({ chain: baseSepolia,
87
+ transport: http(options.rpcUrl, { fetchFn: rpcFetch, retryCount: 0, timeout: 30_000 }),
88
+ });
86
89
  const contract = address(options.contractAddress);
87
90
  const confirmations = options.confirmations ?? 3n;
88
91
  const maxBlockRange = options.maxBlockRange ?? 2000n;
89
92
  let cache;
90
93
  let leaves = new Map();
94
+ let pendingSync;
91
95
  async function rpcCall(operation) {
92
96
  try {
93
97
  return await operation;
@@ -150,7 +154,8 @@ export function createBaseEventWitnessProvider(options) {
150
154
  }
151
155
  return {
152
156
  async witnessForCredential(credential) {
153
- await synchronize();
157
+ pendingSync ??= synchronize().finally(() => { pendingSync = undefined; });
158
+ await pendingSync;
154
159
  const commitment = field(credential.commitment, 'credential commitment');
155
160
  const entry = [...leaves.entries()].find(([, value]) => value.commitment === commitment);
156
161
  if (!entry)
package/dist/cli.js CHANGED
@@ -1,33 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import { homedir } from 'node:os';
3
+ import { readFile } from 'node:fs/promises';
3
4
  import { join } from 'node:path';
4
5
  import { createInterface } from 'node:readline/promises';
5
6
  import { codingTaskEvidence, codingTaskProviderArgs, validateCodingTaskArgs } from './task-evidence.js';
6
7
  import { launchCodexProcess } from './codex-launcher.js';
7
8
  import { launchClineProcess } from './cline-launcher.js';
8
9
  import { isCodexProfileInstalled, resolveCodexHome, writeCodexProfile, } from './codex-profile.js';
9
- import { validateBaseCreditWitness } from './base-sidecar.js';
10
- import { createSidecarRuntime, RuntimeCredentialError, witnessProviderForSource } from './sidecar-runtime.js';
10
+ import { createSidecarRuntime, RuntimeCredentialError } from './sidecar-runtime.js';
11
+ import { setupActivatedCredential } from './activated-credential-setup.js';
11
12
  import { createLocalX402Agent } from './local-x402-agent.js';
12
- import { createPinnedBaseProofGenerator } from './proof-coordinator.js';
13
- import { createBaseProofMetrics } from './proof-metrics.js';
14
- import { nativeCredentialStore, importCredentialPath, readStoredCredential } from './credential-store.js';
15
- import { applyRpcConfig, checkBaseSepoliaRpc, writeRpcConfig, RPC_GUIDANCE, resolveBaseSepoliaRpc } from './rpc-config.js';
13
+ import { nativeCredentialStore } from './credential-store.js';
14
+ import { applyRpcConfig, checkBaseSepoliaRpc, writeRpcConfig, RPC_GUIDANCE } from './rpc-config.js';
16
15
  import { diagnoseSidecar, stopSidecar } from './sidecar-diagnostics.js';
17
16
  import { runCliCommand } from './cli-runtime.js';
18
17
  import { createNodeSidecarLifecycle } from './node-sidecar-lifecycle.js';
19
18
  import { activateSidecarServer } from './server-startup.js';
20
19
  import { createLoopbackToken, sidecarStatePaths } from './sidecar-config.js';
21
- import { applySetupConfig, readSetupConfig, resolveSetupInputs, writeSetupConfig, } from './setup-config.js';
20
+ import { applySetupConfig, readSetupConfig, } from './setup-config.js';
22
21
  import { ensureSidecarReady } from './sidecar-lifecycle.js';
23
22
  import { readLoopbackToken, writeLoopbackToken } from './sidecar-state.js';
24
23
  import { createSidecarServer } from './sidecar.js';
25
24
  import { buildBaseRegisteredTrialRequestBody } from './base-registered-trial.js';
26
- import { loadCircuitManifest } from './artifact-bundle.js';
27
25
  import { BASE_SEPOLIA_NETWORK } from '@zk-credits/x402-zk-prepaid';
28
- import { requireGatewayKnownRoot as checkGatewayKnownRoot } from './gateway-root-check.js';
26
+ import { DEFAULT_GATEWAY_URL, resolveGatewayOrigin } from './gateway-root-check.js';
29
27
  import { evaluateGatewayReadiness, isGatewayStatusFresh } from './gateway-trial-status.js';
30
- const DEFAULT_GATEWAY_URL = 'https://zk-credits-gateway.onrender.com';
31
28
  const DEFAULT_PORT = 3210;
32
29
  function stateDirectory() {
33
30
  return process.env.ZK_CREDITS_HOME || join(homedir(), '.zk-credits');
@@ -120,6 +117,7 @@ async function readHiddenValue(prompt) {
120
117
  }
121
118
  function printHelp() {
122
119
  console.log(`Usage:
120
+ zk-credits --version
123
121
  zk-credits cline [cline arguments...]
124
122
  zk-credits config rpc
125
123
  zk-credits setup [codex|x402] [--legacy] [--model <model>]
@@ -200,11 +198,7 @@ function safeCount(value) {
200
198
  return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
201
199
  }
202
200
  function gatewayOrigin() {
203
- const origin = new URL(process.env.ZK_CREDITS_GATEWAY_URL || DEFAULT_GATEWAY_URL);
204
- if (origin.protocol !== 'https:' || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== '/') {
205
- throw new Error('The internal trial requires a plain HTTPS gateway origin');
206
- }
207
- return origin;
201
+ return resolveGatewayOrigin(process.env);
208
202
  }
209
203
  async function readGatewayJson(url, headers) {
210
204
  try {
@@ -222,9 +216,6 @@ async function readGatewayJson(url, headers) {
222
216
  return { status: null };
223
217
  }
224
218
  }
225
- async function requireGatewayKnownRoot(root) {
226
- await checkGatewayKnownRoot(root, gatewayOrigin());
227
- }
228
219
  async function gatewayReadiness(origin) {
229
220
  const response = await readGatewayJson(new URL('/ready', origin), { accept: 'application/json' });
230
221
  const gate = evaluateGatewayReadiness(response.status, response.body);
@@ -591,6 +582,11 @@ async function runCodingTask(args) {
591
582
  }
592
583
  async function main() {
593
584
  const args = process.argv.slice(2);
585
+ if (args[0] === '--version' || args[0] === '-v') {
586
+ const metadata = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
587
+ console.log(metadata.version);
588
+ return;
589
+ }
594
590
  const localHelp = ['doctor', 'stop', 'status', 'serve', 'setup', 'config', 'investigate'].includes(args[0] ?? '')
595
591
  && args.slice(1).some((arg) => arg === '--help' || arg === '-h');
596
592
  if (args[0] === '--help' || args[0] === '-h' || !args[0] || localHelp) {
@@ -670,45 +666,14 @@ async function main() {
670
666
  return Boolean(await credentialStore.read());
671
667
  },
672
668
  validateSetupPrerequisites: async () => {
673
- await applyRpcConfig(process.env, sidecarHome);
674
- const saved = await readSetupConfig(setupConfigPath(sidecarHome));
675
- const rpc = resolveBaseSepoliaRpc(process.env, saved?.witness.kind === 'base-events' ? saved.witness.rpcUrl : undefined);
676
- await checkBaseSepoliaRpc(rpc);
677
- process.env.BASE_RPC_URL = rpc;
678
- const credentialPath = process.env.ZK_CREDITS_CREDENTIAL_PATH;
679
- const legacyPassword = args.includes('--legacy') ? await readHiddenValue('Legacy export password: ') : undefined;
680
- const credential = credentialPath
681
- ? await importCredentialPath(credentialPath, credentialStore, legacyPassword)
682
- : await readStoredCredential(credentialStore);
683
- const manifest = await loadCircuitManifest();
684
- const config = await resolveSetupInputs({
669
+ await setupActivatedCredential({
685
670
  environment: process.env,
686
671
  homeDirectory: homedir(),
687
672
  stateDirectory: sidecarHome,
688
- configPath: setupConfigPath(sidecarHome),
689
- manifest,
690
- validateWitness: async (source) => {
691
- const provider = await witnessProviderForSource(source, join(stateDirectory(), 'base-event-sync.json'));
692
- const witness = validateBaseCreditWitness(await provider.witnessForCredential(credential));
693
- if (manifest.deployment)
694
- await requireGatewayKnownRoot(witness.root);
695
- },
673
+ credentialStore,
696
674
  chooseCandidate: chooseSetupCandidate,
675
+ ...(args.includes('--legacy') ? { readLegacyPassword: () => readHiddenValue('Legacy export password: ') } : {}),
697
676
  });
698
- const artifactDirectory = config.artifactDirectory;
699
- try {
700
- await createPinnedBaseProofGenerator({
701
- artifactDirectory,
702
- metrics: createBaseProofMetrics(),
703
- });
704
- }
705
- catch (error) {
706
- if (error instanceof Error)
707
- throw new Error(`Pinned proving bundle check failed: ${error.message}`);
708
- throw new Error('Pinned proving bundle check failed. Reinstall the bundle from your pilot invite.');
709
- }
710
- await writeSetupConfig(setupConfigPath(sidecarHome), config);
711
- applySetupConfig(process.env, config);
712
677
  console.log('Pinned proving bundle and witness validated for this activated credential.');
713
678
  console.log('Local setup paths saved to ' + setupConfigPath(sidecarHome));
714
679
  },
@@ -1,2 +1,5 @@
1
+ export declare const DEFAULT_GATEWAY_URL = "https://zk-credits-gateway.onrender.com";
2
+ /** Shared gateway origin validation for setup and the internal trial. */
3
+ export declare function resolveGatewayOrigin(environment: NodeJS.ProcessEnv): URL;
1
4
  /** Public setup check. It sends the public root only and never needs an admin credential. */
2
5
  export declare function requireGatewayKnownRoot(root: string, origin: URL, fetcher?: typeof fetch): Promise<void>;
@@ -1,28 +1,60 @@
1
1
  function isRecord(value) {
2
2
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
3
3
  }
4
+ export const DEFAULT_GATEWAY_URL = 'https://zk-credits-gateway.onrender.com';
5
+ /** Shared gateway origin validation for setup and the internal trial. */
6
+ export function resolveGatewayOrigin(environment) {
7
+ const origin = new URL(environment.ZK_CREDITS_GATEWAY_URL || DEFAULT_GATEWAY_URL);
8
+ if (origin.protocol !== 'https:' || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== '/') {
9
+ throw new Error('The internal trial requires a plain HTTPS gateway origin');
10
+ }
11
+ return origin;
12
+ }
4
13
  /** Public setup check. It sends the public root only and never needs an admin credential. */
5
14
  export async function requireGatewayKnownRoot(root, origin, fetcher = fetch) {
6
- let response;
7
- try {
8
- response = await fetcher(new URL('/v1/root-known', origin), {
9
- method: 'POST',
10
- headers: { accept: 'application/json', 'content-type': 'application/json' },
11
- body: JSON.stringify({ root }),
12
- signal: AbortSignal.timeout(10_000),
13
- });
14
- }
15
- catch {
16
- throw new Error('The gateway could not confirm that the activated credential root is known');
17
- }
18
- let body;
19
- try {
20
- body = await response.json();
21
- }
22
- catch {
23
- body = undefined;
24
- }
25
- if (response.status !== 200 || !isRecord(body) || body.known !== true) {
26
- throw new Error('The activated credential root is not in the gateway known-root set; stop setup and reconcile the V2 activation');
15
+ const deadline = AbortSignal.timeout(60_000);
16
+ for (let attempt = 0; attempt < 3 && !deadline.aborted; attempt += 1) {
17
+ let response;
18
+ let body;
19
+ try {
20
+ response = await fetcher(new URL('/v1/root-known', origin), {
21
+ method: 'POST',
22
+ headers: { accept: 'application/json', 'content-type': 'application/json' },
23
+ body: JSON.stringify({ root }),
24
+ signal: AbortSignal.any([deadline, AbortSignal.timeout(30_000)]),
25
+ });
26
+ if (response.status === 200)
27
+ body = await response.json();
28
+ }
29
+ catch { /* Reachability/body failures remain uncertain, never unknown. */ }
30
+ if (response?.status === 200 && isRecord(body) && body.known === true)
31
+ return;
32
+ if (response?.status === 200 && isRecord(body) && body.known === false) {
33
+ throw new Error('The activated credential root is not in the gateway known-root set; stop setup and reconcile the V2 activation');
34
+ }
35
+ if (response && ![429, 500, 502, 503, 504].includes(response.status))
36
+ break;
37
+ if (attempt === 2)
38
+ break;
39
+ const retry = response?.headers.get('Retry-After');
40
+ const providerDelay = retry ? (/^\d+$/u.test(retry) ? Number(retry) * 1000 : Date.parse(retry) - Date.now()) : 0;
41
+ const delay = Math.max(500 * 2 ** attempt, Number.isFinite(providerDelay) ? providerDelay : 0);
42
+ await response?.body?.cancel().catch(() => { });
43
+ try {
44
+ await waitForGateway(delay, deadline);
45
+ }
46
+ catch {
47
+ break;
48
+ }
27
49
  }
50
+ throw new Error('The gateway is temporarily unavailable and could not confirm the activated credential root. Retry zk-credits setup codex; changing the local RPC does not change the gateway connection.');
51
+ }
52
+ function waitForGateway(ms, signal) {
53
+ return new Promise((resolve, reject) => {
54
+ const abort = () => { clearTimeout(timer); reject(signal.reason); };
55
+ const timer = setTimeout(() => { signal.removeEventListener('abort', abort); resolve(); }, ms);
56
+ signal.addEventListener('abort', abort, { once: true });
57
+ if (signal.aborted)
58
+ abort();
59
+ });
28
60
  }
@@ -2450,7 +2450,7 @@ async function persistJournal(stateDir, journalPath, nextRecords) {
2450
2450
  }
2451
2451
 
2452
2452
  // ../../examples/base-transaction-investigator/.generated/live-defaults.mjs
2453
- var LIVE_DEFAULTS = { "build": "14533b1ebbca1bf21194fd289539ab08dbaeac711c2e6f2719e453bb874f972e", "inference": { "gateway": "https://zk-credits-gateway.onrender.com/v1/chat/completions", "deploymentDomain": "84532", "contract": "0xcc1909dD30485d6D9a46e3d33cf04C1378b586Ab", "recipient": "0xa8b9b38e748563AF7FbAA276e8a843Da417D7Bc1", "circuit": "private-credit-spend-bn254-dev", "verifyingKey": "private-credit-spend-vk-dev-sepolia-v2", "serviceClass": "coding-deepseek-v4-flash-v1" } };
2453
+ var LIVE_DEFAULTS = { "build": "67ef6dc5b0f4270103c0c4128a6fc85cf88148513c5d88ad7f471aa5637b4a55", "inference": { "gateway": "https://zk-credits-gateway.onrender.com/v1/chat/completions", "deploymentDomain": "84532", "contract": "0xcc1909dD30485d6D9a46e3d33cf04C1378b586Ab", "recipient": "0xa8b9b38e748563AF7FbAA276e8a843Da417D7Bc1", "circuit": "private-credit-spend-bn254-dev", "verifyingKey": "private-credit-spend-vk-dev-sepolia-v2", "serviceClass": "coding-deepseek-v4-flash-v1" } };
2454
2454
 
2455
2455
  // ../../examples/base-transaction-investigator/live.mjs
2456
2456
  var CHAINS = { base: 8453, "base-sepolia": 84532 };
@@ -2538,10 +2538,10 @@ async function runLiveInvestigation(args, options = {}) {
2538
2538
  const inferenceFactory = async ({ authorization }) => {
2539
2539
  const agent = await prepared.open({
2540
2540
  async beforeProof(request) {
2541
+ challengeObserved++;
2541
2542
  if ([...observations.values()].some((a) => a.state === "retired" && a.outcome === "unknown"))
2542
2543
  throw Error("unknown_inference_outcome");
2543
2544
  await authorization.beforeProof(request);
2544
- challengeObserved++;
2545
2545
  observations.set(request.attemptId, { state: "reserved", outcome: "unknown" });
2546
2546
  },
2547
2547
  async observe(event) {
@@ -2588,7 +2588,7 @@ async function runLiveInvestigation(args, options = {}) {
2588
2588
  unknown: attempts.filter((a) => a.state !== "released" && a.outcome === "unknown").length,
2589
2589
  retired: attempts.filter((a) => a.state === "retired").length,
2590
2590
  modelDeliveryObserved,
2591
- recoveredPriorTask: attempts.length === 0 && report.inference.authorizedAllocation > 0,
2591
+ recoveredPriorTask: report.stoppingReason === "recovered_stopped",
2592
2592
  build: LIVE_DEFAULTS.build
2593
2593
  };
2594
2594
  return report;
@@ -2605,6 +2605,8 @@ function formatLiveReport(report) {
2605
2605
  `Result: ${report.completion} (${report.stoppingReason})`,
2606
2606
  ""
2607
2607
  ];
2608
+ if (report.stoppingReason === "recovered_stopped" && report.previousStoppingReason)
2609
+ lines.push(`Original stopping reason: ${report.previousStoppingReason}`);
2608
2610
  for (const [label, items] of [
2609
2611
  ["Observed", report.observedFacts],
2610
2612
  ["Supported", report.supportedDiagnoses],
@@ -2615,10 +2617,12 @@ function formatLiveReport(report) {
2615
2617
  }
2616
2618
  for (const gap of [...report.missingEvidence ?? [], ...report.uncertainty ?? []]) lines.push(`Uncertainty: ${gap}`);
2617
2619
  if (report.paymentEvidence.unknown > 0 || report.inference?.unknownOutcomes > 0) lines.push("Action: preserve this task and credential state; resolve the unknown outcome before starting another paid task. Repeating this hash only recovers the local report.");
2618
- if (report.stoppingReason === "inference_unavailable") lines.push("Action: run zk-credits doctor and check gateway/model availability. A useful model diagnosis is unavailable; inspect the separate payment counters.");
2619
- if (["rpc_unavailable", "evidence_unavailable", "insufficient_evidence", "unsupported_input"].includes(report.stoppingReason)) lines.push("Action: check transaction availability on the selected chain and public RPC access; missing evidence cannot be replaced with fixtures.");
2620
+ const cause = report.stoppingReason === "recovered_stopped" ? report.previousStoppingReason : report.stoppingReason;
2621
+ if (cause === "inference_terms_refused") lines.push("Action: payment terms did not match the pinned deployment. Update zk-credits and check gateway configuration before starting a new investigation; repeating this hash only recovers the saved report.");
2622
+ if (cause === "inference_unavailable") lines.push("Action: run zk-credits doctor and check gateway/model availability. A useful model diagnosis is unavailable; inspect the separate payment counters.");
2623
+ if (["rpc_unavailable", "evidence_unavailable", "insufficient_evidence", "unsupported_input"].includes(cause)) lines.push("Action: check transaction availability on the selected chain and public RPC access; missing evidence cannot be replaced with fixtures.");
2620
2624
  for (const entry of report.evidence ?? []) lines.push(`[${entry.id}] ${entry.operation}: ${entry.provenance}; SHA-256 ${entry.contentDigest}`);
2621
- if (report.paymentEvidence.recoveredPriorTask) lines.push(`Recovered prior task accounting; no new inference was authorized. Prior confirmed credits: ${report.inference.confirmedCommitted}; prior unknown outcomes: ${report.inference.unknownOutcomes}.`);
2625
+ if (report.paymentEvidence.recoveredPriorTask) lines.push(`Recovered saved investigation; no new inference was authorized. Prior confirmed credits: ${report.inference.confirmedCommitted}; prior unknown outcomes: ${report.inference.unknownOutcomes}.`);
2622
2626
  lines.push(
2623
2627
  "",
2624
2628
  "Payment: custom zk-prepaid; bundle funding on Base Sepolia, credit consumption offchain.",