@pinionengineering/prover-client 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * Pass the full base URL including the path prefix, e.g.:
9
9
  * new PinionProverClient("https://hydrogen.pinion.build/prover", { getToken })
10
10
  */
11
- import type { AuditResult, ChallengeKeyInfo, CreateKeyResult, ParsedSetup, ProveJobStatusResponse, RawSetupResponse, TagJobListEntry, TagJobProgress, TagJobStatusResponse, TagResponse } from './types.js';
11
+ import type { AuditResult, ChallengeKeyInfo, CreateKeyResult, ParsedSetup, ProveJobStatusResponse, ProveSubmission, RawSetupResponse, TagJobListEntry, TagJobProgress, TagJobStatusResponse, TagResponse, TagSubmission } from './types.js';
12
12
  export interface PinionProverClientOptions {
13
13
  /**
14
14
  * Returns a JWT Bearer token for authenticated endpoints, or null/undefined
@@ -20,7 +20,7 @@ export interface PinionProverClientOptions {
20
20
  * Options for the audit() convenience wrapper.
21
21
  *
22
22
  * For exact control over block count, use buildChallenge(n, total) +
23
- * prove() + verifyProof() directly.
23
+ * prove() + waitForProve() + verifyProof() directly.
24
24
  */
25
25
  export interface AuditOptions {
26
26
  /**
@@ -36,13 +36,31 @@ export interface AuditOptions {
36
36
  * For an exact block count use buildChallenge(n, total) + prove() + verifyProof().
37
37
  */
38
38
  challengePct?: number;
39
+ /** Milliseconds between GET /prove/:job_id polls during audit()'s wait
40
+ * phase. Default 500. */
41
+ pollIntervalMs?: number;
42
+ /**
43
+ * Optional cancellation of audit()'s wait phase. There is no default
44
+ * deadline — omit to wait as long as the proof takes. Pass
45
+ * `AbortSignal.timeout(ms)` to restore a fixed ceiling.
46
+ */
47
+ signal?: AbortSignal;
48
+ /** Called after every status poll with the raw status string
49
+ * ("prove-queued" | "prove-running") — for progress UI. There is no
50
+ * per-block progress signal for proving, unlike tag()'s onProgress. */
51
+ onStatus?: (status: string) => void;
39
52
  }
40
- /** Options for tag()'s internal job-status polling. */
41
- export interface TagOptions {
53
+ /** Options for waitForTag()'s job-status polling. */
54
+ export interface WaitForTagOptions {
42
55
  /** Milliseconds between GET /tag/:job_id polls. Default 1000. */
43
56
  pollIntervalMs?: number;
44
- /** Give up and throw TagTimeoutError after this long. Default 10 minutes. */
45
- timeoutMs?: number;
57
+ /**
58
+ * Optional cancellation. There is no default deadline — omit to wait
59
+ * indefinitely. Pass `AbortSignal.timeout(ms)` to restore the old fixed
60
+ * ceiling (waitForTag() throws TagTimeoutError when the signal fires
61
+ * before the job reaches a terminal state).
62
+ */
63
+ signal?: AbortSignal;
46
64
  /**
47
65
  * Called after every status poll with the latest progress and the raw
48
66
  * status string ("tag-queued" | "tag-planning" | "tag-running" |
@@ -54,17 +72,28 @@ export interface TagOptions {
54
72
  */
55
73
  onProgress?: (progress: TagJobProgress, status: string) => void;
56
74
  }
57
- /** Options for prove()'s internal job-status polling. */
58
- export interface ProveOptions {
75
+ /** Options for waitForProve()'s job-status polling. */
76
+ export interface WaitForProveOptions {
59
77
  /** Milliseconds between GET /prove/:job_id polls. Default 500. */
60
78
  pollIntervalMs?: number;
61
79
  /**
62
- * Give up and throw ProveTimeoutError after this long. Default 60
63
- * seconds much shorter than tag()'s default, since a proof round is one
64
- * bounded crypto operation over the sampled blocks, not a per-block loop
65
- * over an entire file.
80
+ * Optional cancellation. There is no default deadline omit to wait
81
+ * indefinitely. Pass `AbortSignal.timeout(ms)` to restore the old fixed
82
+ * ceiling (waitForProve() throws ProveTimeoutError when the signal fires
83
+ * before the job reaches a terminal state).
66
84
  */
67
- timeoutMs?: number;
85
+ signal?: AbortSignal;
86
+ /**
87
+ * The challenge/roots this job was submitted with. Optional, but passing
88
+ * them lets a thrown ProveFailedError/ProveTimeoutError carry enough to
89
+ * reconstruct a retry — pass what prove() gave you back.
90
+ */
91
+ challenge?: string;
92
+ roots?: string[];
93
+ /** Called after every status poll with the raw status string
94
+ * ("prove-queued" | "prove-running"). There is no per-block progress
95
+ * signal for proving, unlike waitForTag()'s onProgress. */
96
+ onStatus?: (status: string) => void;
68
97
  }
69
98
  export declare class PinionProverClient {
70
99
  private readonly baseUrl;
@@ -98,20 +127,27 @@ export declare class PinionProverClient {
98
127
  * authentication tags, and store them under keyId.
99
128
  *
100
129
  * The root must already be in the "pinned" lifecycle state for the
101
- * authenticated account. Tagging runs asynchronously on the server —
102
- * this enqueues the job and polls GET /api/v1/tag/:job_id until it
103
- * reaches a terminal state, so the returned promise resolves only once
104
- * tagging is actually done (matching the pre-async blocking behavior).
105
- * Call getSetup() after tagging to get the updated block ID lists for the
106
- * next audit cycle.
107
- *
108
- * Throws TagFailedError if the job reaches "tag-failed", or
109
- * TagTimeoutError if it doesn't reach a terminal state within
110
- * options.timeoutMs.
130
+ * authenticated account. Tagging runs asynchronously on the server — this
131
+ * submits the job and returns immediately with a job handle. Poll
132
+ * tagStatus(jobId) yourself, or await waitForTag(jobId, options) to wait
133
+ * for a terminal state (with no default deadline — pass `signal:
134
+ * AbortSignal.timeout(ms)` if you want one). Call getSetup() after
135
+ * tagging completes to get the updated block ID lists for the next audit
136
+ * cycle.
111
137
  */
112
- tag(root: string, keyId: string, options?: TagOptions): Promise<TagResponse>;
113
- /** Poll the status of a tag job started by tag(). Exposed for callers that want progress UI without waiting on tag()'s full promise. */
138
+ tag(root: string, keyId: string): Promise<TagSubmission>;
139
+ /** Poll the status of a tag job started by tag(). Exposed for callers that want progress UI without waiting on waitForTag()'s full promise. */
114
140
  tagStatus(jobId: string): Promise<TagJobStatusResponse>;
141
+ /**
142
+ * Wait for a tag job started by tag() to reach a terminal state, polling
143
+ * tagStatus(jobId) on an interval.
144
+ *
145
+ * There is no default deadline — this waits as long as the job takes
146
+ * unless options.signal is given and fires first, in which case it
147
+ * throws TagTimeoutError with the last-seen status. Throws TagFailedError
148
+ * if the job reaches "tag-failed".
149
+ */
150
+ waitForTag(jobId: string, options?: WaitForTagOptions): Promise<TagResponse>;
115
151
  /**
116
152
  * List the caller's tag jobs, most recently created first.
117
153
  *
@@ -129,34 +165,42 @@ export declare class PinionProverClient {
129
165
  /**
130
166
  * POST /prove — unauthenticated, the server resolves the account from key_id.
131
167
  *
132
- * Proving is asynchronous: this posts the challenge, then polls GET
133
- * /prove/:job_id until the job reaches a terminal state, so the returned
134
- * promise resolves only once a proof is actually ready (matching the
135
- * pre-async blocking behavior). Returns the raw proof bytes. Most callers
136
- * should use audit() instead, which also cryptographically verifies the
137
- * response.
168
+ * Proving is asynchronous: this submits the challenge and returns
169
+ * immediately with a job handle. Poll proveStatus(jobId) yourself, or
170
+ * await waitForProve(jobId, options) to wait for a terminal state (with
171
+ * no default deadline pass `signal: AbortSignal.timeout(ms)` if you
172
+ * want one). Most callers should use audit() instead, which submits,
173
+ * waits, and cryptographically verifies the response in one call.
138
174
  *
139
175
  * @param keyId Challenge key ID.
140
176
  * @param roots CID strings to prove, in the same order as the challenge.
141
177
  * @param challenge base64(JSON(WireChallenge)) from buildChallenge().
142
178
  * @param challengeId Optional idempotency key. If a caller's own retry
143
179
  * logic re-calls prove() for what is logically the
144
- * same request (e.g. after a timeout with an unclear
145
- * outcome), passing the same challengeId across those
146
- * attempts makes the server return the original job
147
- * instead of starting a redundant one. Leave unset for
148
- * normal audit rounds each is a fresh, independently
149
- * random challenge, which should never be deduped
150
- * against a previous one.
180
+ * same request (e.g. after giving up waiting on an
181
+ * earlier attempt with an unclear outcome), passing the
182
+ * same challengeId across those attempts makes the
183
+ * server return the original job instead of starting a
184
+ * redundant one. Leave unset for normal audit rounds
185
+ * each is a fresh, independently random challenge,
186
+ * which should never be deduped against a previous one.
151
187
  *
152
188
  * Throws PinNotActiveError (409, checked synchronously before any job is
153
- * created), ProveFailedError if the job reaches "prove-failed", or
154
- * ProveTimeoutError if it doesn't reach a terminal state within
155
- * options.timeoutMs.
189
+ * created) or ProverError for any other non-2xx submit response.
156
190
  */
157
- prove(keyId: string, roots: string[], challenge: string, challengeId?: string, options?: ProveOptions): Promise<Uint8Array>;
158
- /** Poll the status of a proof job started by prove(). Exposed for callers that want to observe progress without waiting on prove()'s full promise. */
191
+ prove(keyId: string, roots: string[], challenge: string, challengeId?: string): Promise<ProveSubmission>;
192
+ /** Poll the status of a proof job started by prove(). Exposed for callers that want to observe progress without waiting on waitForProve()'s full promise. */
159
193
  proveStatus(jobId: string): Promise<ProveJobStatusResponse>;
194
+ /**
195
+ * Wait for a proof job started by prove() to reach a terminal state,
196
+ * polling proveStatus(jobId) on an interval. Returns the raw proof bytes.
197
+ *
198
+ * There is no default deadline — this waits as long as the proof takes
199
+ * unless options.signal is given and fires first, in which case it
200
+ * throws ProveTimeoutError with the last-seen status. Throws
201
+ * ProveFailedError if the job reaches "prove-failed".
202
+ */
203
+ waitForProve(jobId: string, options?: WaitForProveOptions): Promise<Uint8Array>;
160
204
  /**
161
205
  * Run one audit round against a pre-fetched setup.
162
206
  *
@@ -176,6 +220,10 @@ export declare class PinionProverClient {
176
220
  * const result = await client.audit(keyId, setup, { challengePct: 100 });
177
221
  * ```
178
222
  *
223
+ * Waits for the proof with no default deadline — pass `options.signal` if
224
+ * you want to cancel or bound how long this waits (e.g.
225
+ * `AbortSignal.timeout(60_000)` for the old fixed ceiling).
226
+ *
179
227
  * Throws `PinNotActiveError` if any challenged root is no longer pinned.
180
228
  */
181
229
  audit(keyId: string, setup: ParsedSetup, options?: AuditOptions): Promise<AuditResult>;
@@ -214,29 +262,49 @@ export declare class PinNotActiveError extends Error {
214
262
  readonly cid: string;
215
263
  constructor(cid: string);
216
264
  }
217
- /** Thrown by tag() when the async tag job reaches the "tag-failed" state. */
265
+ /** Thrown by waitForTag() when the async tag job reaches the "tag-failed" state. */
218
266
  export declare class TagFailedError extends Error {
219
267
  readonly jobId: string;
220
268
  readonly reason: string;
221
269
  constructor(jobId: string, reason: string);
222
270
  }
223
- /** Thrown by tag() when the job hasn't reached a terminal state within the configured timeout. */
271
+ /**
272
+ * Thrown by waitForTag() when its `options.signal` fires before the job
273
+ * reaches a terminal state. There is no default deadline anymore — this
274
+ * only happens if the caller opted into one (e.g. `signal:
275
+ * AbortSignal.timeout(ms)`).
276
+ */
224
277
  export declare class TagTimeoutError extends Error {
225
278
  readonly jobId: string;
226
279
  readonly lastStatus: string;
227
280
  constructor(jobId: string, lastStatus: string);
228
281
  }
229
- /** Thrown by prove() when the async proof job reaches the "prove-failed" state. */
282
+ /** Thrown by waitForProve() when the async proof job reaches the "prove-failed" state. */
230
283
  export declare class ProveFailedError extends Error {
231
284
  readonly jobId: string;
232
285
  readonly reason: string;
233
- constructor(jobId: string, reason: string);
286
+ /** The challenge this job was for — base64(JSON(WireChallenge)), decode with decodeChallenge(). Undefined if the caller didn't pass one to waitForProve(). */
287
+ readonly challenge: string | undefined;
288
+ readonly roots: string[] | undefined;
289
+ constructor(jobId: string, reason: string,
290
+ /** The challenge this job was for — base64(JSON(WireChallenge)), decode with decodeChallenge(). Undefined if the caller didn't pass one to waitForProve(). */
291
+ challenge: string | undefined, roots: string[] | undefined);
234
292
  }
235
- /** Thrown by prove() when the job hasn't reached a terminal state within the configured timeout. */
293
+ /**
294
+ * Thrown by waitForProve() when its `options.signal` fires before the job
295
+ * reaches a terminal state. There is no default deadline anymore — this
296
+ * only happens if the caller opted into one (e.g. `signal:
297
+ * AbortSignal.timeout(ms)`).
298
+ */
236
299
  export declare class ProveTimeoutError extends Error {
237
300
  readonly jobId: string;
238
301
  readonly lastStatus: string;
239
- constructor(jobId: string, lastStatus: string);
302
+ /** The challenge this job was for — base64(JSON(WireChallenge)), decode with decodeChallenge(). Undefined if the caller didn't pass one to waitForProve(). */
303
+ readonly challenge: string | undefined;
304
+ readonly roots: string[] | undefined;
305
+ constructor(jobId: string, lastStatus: string,
306
+ /** The challenge this job was for — base64(JSON(WireChallenge)), decode with decodeChallenge(). Undefined if the caller didn't pass one to waitForProve(). */
307
+ challenge: string | undefined, roots: string[] | undefined);
240
308
  }
241
309
  /**
242
310
  * Decode a raw /setup response into a ParsedSetup.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -71,45 +71,57 @@ export class PinionProverClient {
71
71
  * authentication tags, and store them under keyId.
72
72
  *
73
73
  * The root must already be in the "pinned" lifecycle state for the
74
- * authenticated account. Tagging runs asynchronously on the server —
75
- * this enqueues the job and polls GET /api/v1/tag/:job_id until it
76
- * reaches a terminal state, so the returned promise resolves only once
77
- * tagging is actually done (matching the pre-async blocking behavior).
78
- * Call getSetup() after tagging to get the updated block ID lists for the
79
- * next audit cycle.
80
- *
81
- * Throws TagFailedError if the job reaches "tag-failed", or
82
- * TagTimeoutError if it doesn't reach a terminal state within
83
- * options.timeoutMs.
74
+ * authenticated account. Tagging runs asynchronously on the server — this
75
+ * submits the job and returns immediately with a job handle. Poll
76
+ * tagStatus(jobId) yourself, or await waitForTag(jobId, options) to wait
77
+ * for a terminal state (with no default deadline — pass `signal:
78
+ * AbortSignal.timeout(ms)` if you want one). Call getSetup() after
79
+ * tagging completes to get the updated block ID lists for the next audit
80
+ * cycle.
84
81
  */
85
- async tag(root, keyId, options = {}) {
86
- const pollIntervalMs = options.pollIntervalMs ?? 1000;
87
- const timeoutMs = options.timeoutMs ?? 10 * 60 * 1000;
82
+ async tag(root, keyId) {
88
83
  const { job_id: jobId } = await this.post('/api/v1/tag', {
89
84
  root,
90
85
  key_id: keyId,
91
86
  });
92
- const deadline = Date.now() + timeoutMs;
93
- for (;;) {
94
- const status = await this.tagStatus(jobId);
95
- if (status.progress)
96
- options.onProgress?.(status.progress, status.status);
97
- if (status.status === 'tag-done') {
98
- return { block_ids: status.block_ids, block_count: status.block_count };
99
- }
100
- if (status.status === 'tag-failed') {
101
- throw new TagFailedError(jobId, status.error ?? 'unknown error');
102
- }
103
- if (Date.now() >= deadline) {
104
- throw new TagTimeoutError(jobId, status.status);
105
- }
106
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
107
- }
87
+ return { jobId, root, keyId };
108
88
  }
109
- /** Poll the status of a tag job started by tag(). Exposed for callers that want progress UI without waiting on tag()'s full promise. */
89
+ /** Poll the status of a tag job started by tag(). Exposed for callers that want progress UI without waiting on waitForTag()'s full promise. */
110
90
  async tagStatus(jobId) {
111
91
  return this.get(`/api/v1/tag/${encodeURIComponent(jobId)}`);
112
92
  }
93
+ /**
94
+ * Wait for a tag job started by tag() to reach a terminal state, polling
95
+ * tagStatus(jobId) on an interval.
96
+ *
97
+ * There is no default deadline — this waits as long as the job takes
98
+ * unless options.signal is given and fires first, in which case it
99
+ * throws TagTimeoutError with the last-seen status. Throws TagFailedError
100
+ * if the job reaches "tag-failed".
101
+ */
102
+ async waitForTag(jobId, options = {}) {
103
+ const pollIntervalMs = options.pollIntervalMs ?? 1000;
104
+ const status = await pollUntilTerminal({
105
+ fetchStatus: () => this.tagStatus(jobId),
106
+ isTerminal: (s) => s.status === 'tag-done' || s.status === 'tag-failed',
107
+ pollIntervalMs,
108
+ signal: options.signal,
109
+ onTick: (s) => {
110
+ if (s.progress)
111
+ options.onProgress?.(s.progress, s.status);
112
+ },
113
+ }).catch((e) => {
114
+ if (e instanceof PollAbortedError) {
115
+ const last = e.lastStatus;
116
+ throw new TagTimeoutError(jobId, last?.status ?? 'unknown');
117
+ }
118
+ throw e;
119
+ });
120
+ if (status.status === 'tag-failed') {
121
+ throw new TagFailedError(jobId, status.error ?? 'unknown error');
122
+ }
123
+ return { block_ids: status.block_ids, block_count: status.block_count };
124
+ }
113
125
  /**
114
126
  * List the caller's tag jobs, most recently created first.
115
127
  *
@@ -134,34 +146,30 @@ export class PinionProverClient {
134
146
  /**
135
147
  * POST /prove — unauthenticated, the server resolves the account from key_id.
136
148
  *
137
- * Proving is asynchronous: this posts the challenge, then polls GET
138
- * /prove/:job_id until the job reaches a terminal state, so the returned
139
- * promise resolves only once a proof is actually ready (matching the
140
- * pre-async blocking behavior). Returns the raw proof bytes. Most callers
141
- * should use audit() instead, which also cryptographically verifies the
142
- * response.
149
+ * Proving is asynchronous: this submits the challenge and returns
150
+ * immediately with a job handle. Poll proveStatus(jobId) yourself, or
151
+ * await waitForProve(jobId, options) to wait for a terminal state (with
152
+ * no default deadline pass `signal: AbortSignal.timeout(ms)` if you
153
+ * want one). Most callers should use audit() instead, which submits,
154
+ * waits, and cryptographically verifies the response in one call.
143
155
  *
144
156
  * @param keyId Challenge key ID.
145
157
  * @param roots CID strings to prove, in the same order as the challenge.
146
158
  * @param challenge base64(JSON(WireChallenge)) from buildChallenge().
147
159
  * @param challengeId Optional idempotency key. If a caller's own retry
148
160
  * logic re-calls prove() for what is logically the
149
- * same request (e.g. after a timeout with an unclear
150
- * outcome), passing the same challengeId across those
151
- * attempts makes the server return the original job
152
- * instead of starting a redundant one. Leave unset for
153
- * normal audit rounds each is a fresh, independently
154
- * random challenge, which should never be deduped
155
- * against a previous one.
161
+ * same request (e.g. after giving up waiting on an
162
+ * earlier attempt with an unclear outcome), passing the
163
+ * same challengeId across those attempts makes the
164
+ * server return the original job instead of starting a
165
+ * redundant one. Leave unset for normal audit rounds
166
+ * each is a fresh, independently random challenge,
167
+ * which should never be deduped against a previous one.
156
168
  *
157
169
  * Throws PinNotActiveError (409, checked synchronously before any job is
158
- * created), ProveFailedError if the job reaches "prove-failed", or
159
- * ProveTimeoutError if it doesn't reach a terminal state within
160
- * options.timeoutMs.
170
+ * created) or ProverError for any other non-2xx submit response.
161
171
  */
162
- async prove(keyId, roots, challenge, challengeId, options = {}) {
163
- const pollIntervalMs = options.pollIntervalMs ?? 500;
164
- const timeoutMs = options.timeoutMs ?? 60 * 1000;
172
+ async prove(keyId, roots, challenge, challengeId) {
165
173
  const resp = await fetch(`${this.baseUrl}/prove`, {
166
174
  method: 'POST',
167
175
  headers: { 'Content-Type': 'application/json' },
@@ -175,25 +183,41 @@ export class PinionProverClient {
175
183
  throw new ProverError(resp.status, await resp.text().catch(() => ''));
176
184
  }
177
185
  const { job_id: jobId } = await parseJsonBody(resp);
178
- const deadline = Date.now() + timeoutMs;
179
- for (;;) {
180
- const status = await this.proveStatus(jobId);
181
- if (status.status === 'prove-done') {
182
- return base64ToBytes(status.proof ?? '');
183
- }
184
- if (status.status === 'prove-failed') {
185
- throw new ProveFailedError(jobId, status.error ?? 'unknown error');
186
- }
187
- if (Date.now() >= deadline) {
188
- throw new ProveTimeoutError(jobId, status.status);
189
- }
190
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
191
- }
186
+ return { jobId, challenge, roots };
192
187
  }
193
- /** Poll the status of a proof job started by prove(). Exposed for callers that want to observe progress without waiting on prove()'s full promise. */
188
+ /** Poll the status of a proof job started by prove(). Exposed for callers that want to observe progress without waiting on waitForProve()'s full promise. */
194
189
  async proveStatus(jobId) {
195
190
  return this.get(`/prove/${encodeURIComponent(jobId)}`);
196
191
  }
192
+ /**
193
+ * Wait for a proof job started by prove() to reach a terminal state,
194
+ * polling proveStatus(jobId) on an interval. Returns the raw proof bytes.
195
+ *
196
+ * There is no default deadline — this waits as long as the proof takes
197
+ * unless options.signal is given and fires first, in which case it
198
+ * throws ProveTimeoutError with the last-seen status. Throws
199
+ * ProveFailedError if the job reaches "prove-failed".
200
+ */
201
+ async waitForProve(jobId, options = {}) {
202
+ const pollIntervalMs = options.pollIntervalMs ?? 500;
203
+ const status = await pollUntilTerminal({
204
+ fetchStatus: () => this.proveStatus(jobId),
205
+ isTerminal: (s) => s.status === 'prove-done' || s.status === 'prove-failed',
206
+ pollIntervalMs,
207
+ signal: options.signal,
208
+ onTick: (s) => options.onStatus?.(s.status),
209
+ }).catch((e) => {
210
+ if (e instanceof PollAbortedError) {
211
+ const last = e.lastStatus;
212
+ throw new ProveTimeoutError(jobId, last?.status ?? 'unknown', options.challenge, options.roots);
213
+ }
214
+ throw e;
215
+ });
216
+ if (status.status === 'prove-failed') {
217
+ throw new ProveFailedError(jobId, status.error ?? 'unknown error', options.challenge, options.roots);
218
+ }
219
+ return base64ToBytes(status.proof ?? '');
220
+ }
197
221
  /**
198
222
  * Run one audit round against a pre-fetched setup.
199
223
  *
@@ -213,6 +237,10 @@ export class PinionProverClient {
213
237
  * const result = await client.audit(keyId, setup, { challengePct: 100 });
214
238
  * ```
215
239
  *
240
+ * Waits for the proof with no default deadline — pass `options.signal` if
241
+ * you want to cancel or bound how long this waits (e.g.
242
+ * `AbortSignal.timeout(60_000)` for the old fixed ceiling).
243
+ *
216
244
  * Throws `PinNotActiveError` if any challenged root is no longer pinned.
217
245
  */
218
246
  async audit(keyId, setup, options = {}) {
@@ -231,14 +259,28 @@ export class PinionProverClient {
231
259
  throw new Error('no blocks to audit');
232
260
  const challengeSize = Math.max(1, Math.round((challengePct / 100) * allBlockIds.length));
233
261
  const challenge = buildChallenge(challengeSize, allBlockIds.length);
234
- const proofBytes = await this.prove(keyId, targetRoots, challenge);
262
+ const submission = await this.prove(keyId, targetRoots, challenge);
263
+ const proofBytes = await this.waitForProve(submission.jobId, {
264
+ pollIntervalMs: options.pollIntervalMs,
265
+ signal: options.signal,
266
+ challenge,
267
+ roots: targetRoots,
268
+ onStatus: options.onStatus,
269
+ });
235
270
  const verification = verifyProofResult({
236
271
  clientSetup: setup.clientSetup,
237
272
  blockIds: allBlockIds,
238
273
  challenge,
239
274
  proofBytes,
240
275
  });
241
- return { pass: verification.verified, verification, blocksChecked: challengeSize, keyId, roots: targetRoots };
276
+ return {
277
+ pass: verification.verified,
278
+ verification,
279
+ blocksChecked: challengeSize,
280
+ keyId,
281
+ roots: targetRoots,
282
+ challenge,
283
+ };
242
284
  }
243
285
  // ---------------------------------------------------------------------------
244
286
  // HTTP helpers
@@ -310,6 +352,57 @@ async function parseJsonBody(resp) {
310
352
  }
311
353
  }
312
354
  // ---------------------------------------------------------------------------
355
+ // Shared polling loop for waitForProve()/waitForTag()
356
+ // ---------------------------------------------------------------------------
357
+ /**
358
+ * Internal signal that a poll loop was cancelled via its caller-supplied
359
+ * AbortSignal before the job reached a terminal state. waitForProve()/
360
+ * waitForTag() catch this and rethrow as ProveTimeoutError/TagTimeoutError
361
+ * carrying lastStatus — never thrown to the outside on its own.
362
+ */
363
+ class PollAbortedError extends Error {
364
+ lastStatus;
365
+ constructor(lastStatus) {
366
+ super('poll aborted');
367
+ this.lastStatus = lastStatus;
368
+ this.name = 'PollAbortedError';
369
+ }
370
+ }
371
+ /**
372
+ * Polls fetchStatus() every pollIntervalMs until isTerminal(status) is
373
+ * true, calling onTick(status) after every poll (terminal or not) so
374
+ * callers can drive progress callbacks. Has NO built-in deadline — the
375
+ * caller's polling budget is unlimited unless `signal` is given, in which
376
+ * case an abort rejects with PollAbortedError(lastStatus) instead of
377
+ * resolving.
378
+ */
379
+ async function pollUntilTerminal(opts) {
380
+ let last;
381
+ for (;;) {
382
+ if (opts.signal?.aborted)
383
+ throw new PollAbortedError(last);
384
+ const status = await opts.fetchStatus();
385
+ last = status;
386
+ opts.onTick?.(status);
387
+ if (opts.isTerminal(status))
388
+ return status;
389
+ await sleepOrAbort(opts.pollIntervalMs, opts.signal, last);
390
+ }
391
+ }
392
+ function sleepOrAbort(ms, signal, lastStatus) {
393
+ return new Promise((resolve, reject) => {
394
+ if (signal?.aborted) {
395
+ reject(new PollAbortedError(lastStatus));
396
+ return;
397
+ }
398
+ const timer = setTimeout(resolve, ms);
399
+ signal?.addEventListener('abort', () => {
400
+ clearTimeout(timer);
401
+ reject(new PollAbortedError(lastStatus));
402
+ }, { once: true });
403
+ });
404
+ }
405
+ // ---------------------------------------------------------------------------
313
406
  // Errors
314
407
  // ---------------------------------------------------------------------------
315
408
  export class ProverError extends Error {
@@ -355,7 +448,7 @@ export class PinNotActiveError extends Error {
355
448
  this.name = 'PinNotActiveError';
356
449
  }
357
450
  }
358
- /** Thrown by tag() when the async tag job reaches the "tag-failed" state. */
451
+ /** Thrown by waitForTag() when the async tag job reaches the "tag-failed" state. */
359
452
  export class TagFailedError extends Error {
360
453
  jobId;
361
454
  reason;
@@ -366,7 +459,12 @@ export class TagFailedError extends Error {
366
459
  this.name = 'TagFailedError';
367
460
  }
368
461
  }
369
- /** Thrown by tag() when the job hasn't reached a terminal state within the configured timeout. */
462
+ /**
463
+ * Thrown by waitForTag() when its `options.signal` fires before the job
464
+ * reaches a terminal state. There is no default deadline anymore — this
465
+ * only happens if the caller opted into one (e.g. `signal:
466
+ * AbortSignal.timeout(ms)`).
467
+ */
370
468
  export class TagTimeoutError extends Error {
371
469
  jobId;
372
470
  lastStatus;
@@ -377,25 +475,42 @@ export class TagTimeoutError extends Error {
377
475
  this.name = 'TagTimeoutError';
378
476
  }
379
477
  }
380
- /** Thrown by prove() when the async proof job reaches the "prove-failed" state. */
478
+ /** Thrown by waitForProve() when the async proof job reaches the "prove-failed" state. */
381
479
  export class ProveFailedError extends Error {
382
480
  jobId;
383
481
  reason;
384
- constructor(jobId, reason) {
482
+ challenge;
483
+ roots;
484
+ constructor(jobId, reason,
485
+ /** The challenge this job was for — base64(JSON(WireChallenge)), decode with decodeChallenge(). Undefined if the caller didn't pass one to waitForProve(). */
486
+ challenge, roots) {
385
487
  super(`prove job ${jobId} failed: ${reason}`);
386
488
  this.jobId = jobId;
387
489
  this.reason = reason;
490
+ this.challenge = challenge;
491
+ this.roots = roots;
388
492
  this.name = 'ProveFailedError';
389
493
  }
390
494
  }
391
- /** Thrown by prove() when the job hasn't reached a terminal state within the configured timeout. */
495
+ /**
496
+ * Thrown by waitForProve() when its `options.signal` fires before the job
497
+ * reaches a terminal state. There is no default deadline anymore — this
498
+ * only happens if the caller opted into one (e.g. `signal:
499
+ * AbortSignal.timeout(ms)`).
500
+ */
392
501
  export class ProveTimeoutError extends Error {
393
502
  jobId;
394
503
  lastStatus;
395
- constructor(jobId, lastStatus) {
504
+ challenge;
505
+ roots;
506
+ constructor(jobId, lastStatus,
507
+ /** The challenge this job was for — base64(JSON(WireChallenge)), decode with decodeChallenge(). Undefined if the caller didn't pass one to waitForProve(). */
508
+ challenge, roots) {
396
509
  super(`prove job ${jobId} timed out (last status: ${lastStatus})`);
397
510
  this.jobId = jobId;
398
511
  this.lastStatus = lastStatus;
512
+ this.challenge = challenge;
513
+ this.roots = roots;
399
514
  this.name = 'ProveTimeoutError';
400
515
  }
401
516
  }