@docmana/sdk 0.32.0 → 0.36.0-hitl.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.36.0-hitl.2 - 2026-10-23 (HITL prerelease)
4
+
5
+ - Add `executionEvents.list()` and typed, non-consuming sequence-paged node
6
+ history for formulas with or without human review. Add the expanded
7
+ `verifyExecutionEventCallback` / `parseExecutionEventCallback` while retaining
8
+ the existing strict review-only helpers and final callback behavior.
9
+ - Publish pilot versions under the explicit `hitl` npm distribution tag;
10
+ other prereleases use `next` and stable releases use `latest`.
11
+
12
+ ## 0.36.0-hitl.1 - 2026-10-23 (local POC)
13
+
14
+ - Add `reviews.calculation()` and exported `ReviewCalculation` for non-consuming
15
+ provisional calculation state, coherent/target revisions, review counts and
16
+ per-revision node reuse. Preserve legacy null metadata, organization scope,
17
+ versioned decisions, assignment and final callback behavior.
18
+
19
+ ## 0.35.0-hitl.3 - 2026-10-22 (local POC)
20
+
21
+ - Add optional `ReviewTask.findings` and exported `ReviewFinding` for explicit,
22
+ review-scoped field signals. Preserve missing/empty findings and existing
23
+ corrections, assignment, global confirmation and final callbacks.
24
+
25
+ ## 0.35.0-hitl.2 - 2026-10-22 (local POC)
26
+
27
+ - Add optional read-only frozen review assessments (node score, description, feedback, explanation and reasoning). No per-field findings or decision/callback change.
28
+
29
+ ## 0.35.0-hitl.1 - 2026-10-22 (local POC)
30
+
31
+ - Preserve optional native review lifecycle timestamps for Live's human-wait and activity display. No final result fetch, pipeline or callback contract change.
32
+
33
+ ## 0.34.0 - 2026-10-22
34
+
35
+ - Add `reviews.document(id, docKey, "preview", options)` and exported
36
+ `DocumentPreviewManifest` for progressive import-time WebP preparation.
37
+ Existing document streams, review decisions and final callbacks are unchanged.
38
+
39
+ ## 0.33.0 - 2026-10-21
40
+
41
+ - Add typed review event callbacks and raw-body HMAC signature verification for
42
+ Consumer's separate review event relay. Existing execution callbacks are unchanged.
43
+ - Add `Docmana.reviews` for organization-scoped queues, task details, versioned
44
+ decisions and streaming page/content/location reads. Preserve range/cache
45
+ statuses and safe conflict metadata; never automatically retry decisions.
46
+ - Expose `AWAITING_REVIEW` as a non-terminal execution status.
47
+
3
48
  ## Unreleased
4
49
 
5
50
  ## 0.32.0 - 2026-10-19
@@ -257,3 +302,7 @@ consu## 0.16.0
257
302
  - Typed error hierarchy (`DocmanaError`, `DocmanaExecutionError`, …).
258
303
  - Flexible file inputs: path, `Buffer`, `Stream`, and multi-file uploads.
259
304
  - Dual ESM + CJS distribution with bundled type definitions.
305
+
306
+ ### Local HITL outline addition
307
+
308
+ - Add reviews.outline with typed published formula steps, edges and review marks. Existing execution, decision and final callback contracts are unchanged.
package/README.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @docmana/sdk
2
2
 
3
+ The HITL pilot uses the explicit prerelease `0.36.0-hitl.2` and the npm `hitl`
4
+ distribution tag. Install the exact version for reproducible pilot builds:
5
+
6
+ ```sh
7
+ npm install --save-exact @docmana/sdk@0.36.0-hitl.2
8
+ ```
9
+
10
+ The stable default remains on `latest`; the prerelease does not select a native
11
+ API. Configure the SDK's Consumer base URL on the application server.
12
+
13
+ ## Real formula node progress (local HITL pilot)
14
+
15
+ `sdk.executionEvents.list(executionId, { organization, after: 0, limit: 100, signal })`
16
+ reads a non-consuming metadata journal, including formulas without human review.
17
+ Follow `nextAfter` while non-null; order by `sequence`, deduplicate `eventId`, and
18
+ honor `truncated` when the cursor predates retained history. The native journal
19
+ keeps at most 2,000 events with the execution. No PDF or final-result retention
20
+ is added. Older APIs can return 404; show confirmed status without inventing
21
+ progress. Never turn a journal failure into a call to another backend.
22
+
23
+ The four new names are `execution.node.started`, `execution.node.completed`,
24
+ `execution.node.failed`, and `execution.node.reused`. Events identify a node,
25
+ document keys, revision and attempt. A reused output does not imply a new LLM
26
+ call. Completion of a node is technical completion, not a positive business
27
+ verdict or final execution completion. `occurredAt` is the real action time;
28
+ `publishedAt` is optional broker time, present on signed callbacks.
29
+
30
+ `verifyExecutionEventCallback` and `parseExecutionEventCallback` accept both the
31
+ new node metadata and the four existing review events. Existing review-only
32
+ verification helpers remain strict and unchanged. Final execution callbacks and
33
+ result ownership keep their existing contract; there are no pipeline events.
34
+
35
+ ## Review event callbacks
36
+
37
+ SDK 0.33.0 adds server-side `verifyReviewEventCallback`, `parseReviewEventCallback`
38
+ and the `ReviewEventCallback` type for Consumer's opt-in review event relay.
39
+ This is independent from `runFlowAsync({ callbackUrl })`, which still receives only
40
+ the final result. No Service Bus connection is required in your application.
41
+
42
+ ```ts
43
+ import { verifyReviewEventCallback } from "@docmana/sdk";
44
+ const event = verifyReviewEventCallback({
45
+ body: rawUtf8Body,
46
+ signature: request.headers.get("x-docmana-event-signature"),
47
+ timestamp: request.headers.get("x-docmana-event-timestamp"),
48
+ secret: serverConfiguredSecret, // at least 32 characters; never sent to a browser
49
+ organization: expectedOrganization,
50
+ });
51
+ await persistOnce(event.organization, event.eventId, event);
52
+ // Only now return 2xx. Never finalize an execution from a review notification.
53
+ ```
54
+
55
+ Supported types are `execution.awaiting_review`, `execution.review_done`,
56
+ `execution.review_closed` and `execution.review_expired`. `review_done` means the
57
+ decision is recorded, not that the execution is finished. `review_closed` also
58
+ describes the review rather than the execution. `publishedAt` is the broker's
59
+ publication time. The metadata excludes document names, values, notes and results.
60
+ Signatures use HMAC-SHA256 over `timestamp + "." + rawBody` with header `v1=<hex>`;
61
+ verification accepts a five-minute clock difference. Broker retries are re-signed
62
+ but retain the event ID; store it durably for deduplication. An event notification
63
+ never consumes the final result. Source publication is best-effort: reconcile
64
+ current review state separately after a missed notification.
65
+
3
66
  Official Docmana SDK for calling `docmana-consumer-edge-api`.
4
67
 
5
68
  SDK 0.32.0 adds native formula authoring, assisted conversations and revision-pinned
@@ -18,6 +81,17 @@ status, and returns the mapped Consumer execution DTO.
18
81
  npm install @docmana/sdk
19
82
  ```
20
83
 
84
+ ### GitHub release recovery
85
+
86
+ The Release workflow publishes an existing `v<version>` tag. GitHub Actions are
87
+ pinned to full commit SHAs to satisfy the enterprise policy. If a tag-triggered
88
+ run fails before starting, merge the workflow correction first, then manually
89
+ run **Release** from `main` with that existing tag. The workflow checks out the
90
+ tag and verifies its package version before publishing; do not move a release
91
+ tag to repair a pipeline. Publication requires the existing npm publisher
92
+ credential or trusted-publishing configuration. Verify the version is available
93
+ in npm before updating a consumer's lockfile.
94
+
21
95
  Requires Node.js 20+.
22
96
 
23
97
  ## Quickstart
@@ -450,6 +524,85 @@ user confirmation before submitting the same file with `confirmOverwrite: true`.
450
524
  `remove(id, options)` permanently deletes the formula and its authoring data;
451
525
  confirm that action with the user. Import and deletion do not use operation IDs.
452
526
 
527
+ ## Human review of formula executions
528
+
529
+ Read calculation metadata repeatedly with
530
+ `client.reviews.calculation(executionId, { organization })`. It returns
531
+ `executionResultId`, normalized `executionStatus` and `calculation` (null for
532
+ legacy/unmarked runs). Calculation includes `status`, coherent `revision`
533
+ (0 before the first calculation), optional `targetRevision`, `provisional`,
534
+ review counts and computed/reused node counts for that revision. Node counts
535
+ are not individual model-call counts. Calculation revision is distinct from
536
+ `ReviewTask.version`, which remains required for decisions. A completed
537
+ calculation stays provisional until the persisted execution is terminal.
538
+ This read is available before the first review and after final callback delivery;
539
+ it never consumes the single-use final result or triggers pipelines/callbacks.
540
+
541
+ Requires Consumer's review routes and the compatible HITL Document API branch.
542
+ No pipelines are required. Start published manual formulas with `runFlowAsync`;
543
+ `AWAITING_REVIEW` is non-terminal. `runFlow` still waits for final completion and
544
+ retains its configured timeout, so do not hold an HTTP request open for human work.
545
+
546
+ ```ts
547
+ const scope = { organization: "your-organization", signal: controller.signal };
548
+ const { reviews, nextCursor } = await docmana.reviews.list({
549
+ ...scope,
550
+ status: "pending",
551
+ limit: 50,
552
+ });
553
+ const task = await docmana.reviews.get(reviews[0].executionResultId, scope);
554
+ await docmana.reviews.decide(
555
+ task.executionResultId,
556
+ {
557
+ decision: "continue",
558
+ version: task.version,
559
+ reviewer: authenticatedServerActor, // Never trust an identity from the browser.
560
+ corrections: [{ itemId: task.documents[0].items![0].itemId, value: false }],
561
+ },
562
+ scope,
563
+ );
564
+ // Preparation starts at import. Poll queued/processing; other states are terminal.
565
+ const preparation = await docmana.reviews.document(
566
+ task.executionResultId,
567
+ task.documents[0].docKey,
568
+ "preview",
569
+ {
570
+ ...scope,
571
+ version: task.reviewId,
572
+ },
573
+ );
574
+ const manifest = await preparation.json(); // DocumentPreviewManifest
575
+ // Only fetch numbers present in manifest.pages; page 1 precedes later batches.
576
+ // Original content remains available as the PDF fallback.
577
+ const page = await docmana.reviews.document(
578
+ task.executionResultId,
579
+ "doc01",
580
+ { page: 1 },
581
+ {
582
+ ...scope,
583
+ version: task.reviewId,
584
+ size: "thumb",
585
+ },
586
+ );
587
+ // page is a streaming Fetch Response. Consume or cancel its body; do not buffer a large PDF.
588
+ ```
589
+
590
+ `list` accepts `pending`, `submitted`, `resuming` and an opaque `cursor`. It lists
591
+ open tasks only. The `"*"` dossier entry is not a file. Use the returned item IDs
592
+ unchanged; JSON values preserve `null`, `false`, `0`, objects and arrays.
593
+ `document` also accepts `"content"` or `"locations"`, Range/If-Range and ETag
594
+ options. It returns 200/206/304/416 without consuming the body.
595
+
596
+ A decision receipt means **recorded and resume queued**, never final completion.
597
+ On 409 inspect `DocmanaError.reviewConflict` and `apiCode`, then re-read the task.
598
+ On 503 re-read first: the decision may already be recorded. The SDK does not retry
599
+ a decision after a conflict, 503 or uncertain transport outcome. The standard one-time
600
+ 401 token-refresh retry still applies. Keep the existing final callback/polling result owner;
601
+ opening a review must not consume the single-use execution result.
602
+
603
+ Release order: compatible Document API, Consumer, published SDK, then Arcana's
604
+ exact pinned dependency. This local 0.33.0 candidate is not published by these steps.
605
+
453
606
  ## License
454
607
 
455
608
  MIT
@@ -476,3 +629,20 @@ Use `authoring.trialDocument(fileBlob, filename, spec, { organization, signal })
476
629
  For assisted Mongo nodes, call `authoring.documentTrialDefinitions({ flowId, expectedRevisions, node | allNodes }, { organization, signal })`. This read-only preparation pins active node-owned rules and settings to the coherent native snapshot, checking revisions again after resolution. Pass the returned definition and uploaded file to the server-side `runAssistedDocumentTrial` library in `@docmana/docmana-flows`. Git callers use Flows' `prepareDocumentTrial` directly. Neither path requires `/extract/test-assisted` or a modified DocMana API. Calling the old native trial method with assisted nodes now fails before upload with HTTP 422.
477
630
 
478
631
  Flows owns assisted IDP/model calls and requires its server-side provider configuration. It analyzes the file once and tries up to 100 compatible classification/extraction nodes independently, retaining evidence envelopes. Files are nonempty PDF/images up to 20 MiB. Definitions and uploads use disposable local storage, cleaned after completion; no formula, rule, reference expectation or publication is saved. These trials do not execute production routing, translate results, compare expectations or produce release approval evidence. Missing upstream context is rejected. Native API accounting and PromptShield are not implied by the Flows executor. No new CLI command is introduced.
632
+
633
+ ### Formula steps during human review
634
+
635
+ Task detail also preserves optional native `createdAt`, `submittedAt`, `resumingAt` and `closedAt` timestamps. Human waiting is bounded by task creation and native decision acceptance; review closure does not prove final execution completion. Older backends can omit these fields; clients must show an unavailable duration rather than infer it from callback delivery time.
636
+
637
+ Use client.reviews.outline(flowId, { organization }) to read the published nodes, edges, ordering and human-review marks through Consumer. Compare outline.version with the review flowVersion before displaying progress. This read never consumes an execution result and does not introduce pipelines.
638
+
639
+ ### Frozen review evidence (local HITL pilot)
640
+
641
+ `reviews.get()` optionally returns `documents[].assessments`: the stored node score (0–100), `scoreDescription`, `feedback`, `explanation` and `reasoning` from the same frozen snapshot as the items. These are read-only node-level evidence, not uncertain-field findings. Older backends may omit assessments or scores; do not infer missing scores. Decisions still send only editable item corrections through Consumer.
642
+
643
+ `ReviewTask.findings?: ReviewFinding[]` separately carries explicit field signals:
644
+ stable review-scoped `id`, `code`, `message`, resolved `itemIds`, and optional
645
+ `source` (`model` or `rule`). Consumers must not infer fields from node scores or
646
+ explanation text. Missing or empty findings preserve the existing presentation.
647
+ These are frozen evidence, not per-field decisions; editing a value does not
648
+ erase its finding. Continue using the same global, versioned decision call.
package/dist/cli.mjs CHANGED
@@ -171,6 +171,234 @@ var FormulaAuthoring = class {
171
171
  }
172
172
  };
173
173
 
174
+ // src/sdk/reviews.ts
175
+ function segment(value) {
176
+ if (!value || value === "." || value === ".." || /[\\/]/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32))
177
+ throw new Error("Invalid review identifier");
178
+ return encodeURIComponent(value);
179
+ }
180
+ var Reviews = class {
181
+ constructor(http) {
182
+ this.http = http;
183
+ }
184
+ http;
185
+ /** Repeatable metadata read: never consumes the final execution result. */
186
+ calculation(executionId, options) {
187
+ return this.http.requestJson("GET", `/executions/${segment(executionId)}/review/calculation`, {
188
+ signal: options.signal,
189
+ query: { organization: options.organization }
190
+ });
191
+ }
192
+ /** Published formula structure only. Compare its version with the review's flowVersion. */
193
+ outline(flowId, options) {
194
+ return this.http.requestJson("GET", `/flows/${segment(flowId)}/outline`, {
195
+ signal: options.signal,
196
+ query: { organization: options.organization }
197
+ });
198
+ }
199
+ list(options) {
200
+ return this.http.requestJson("GET", "/reviews", {
201
+ signal: options.signal,
202
+ query: {
203
+ organization: options.organization,
204
+ ...options.status ? { status: options.status } : {},
205
+ ...options.cursor ? { cursor: options.cursor } : {},
206
+ ...options.limit ? { limit: String(options.limit) } : {}
207
+ }
208
+ });
209
+ }
210
+ get(executionId, options) {
211
+ return this.http.requestJson("GET", `/executions/${segment(executionId)}/review`, {
212
+ signal: options.signal,
213
+ query: { organization: options.organization }
214
+ });
215
+ }
216
+ decide(executionId, decision, options) {
217
+ return this.http.requestJson("POST", `/executions/${segment(executionId)}/review`, {
218
+ signal: options.signal,
219
+ query: { organization: options.organization },
220
+ headers: { "content-type": "application/json" },
221
+ body: JSON.stringify(decision)
222
+ });
223
+ }
224
+ /** Streaming Response: callers own consumption/cancellation. Never buffers the PDF. */
225
+ document(executionId, docKey, resource, options) {
226
+ if (typeof resource === "object" && (!Number.isSafeInteger(resource.page) || resource.page < 1 || resource.page > 999999))
227
+ throw new Error("Invalid page number");
228
+ const path = typeof resource === "object" ? `pages/${resource.page}` : resource;
229
+ const headers = Object.fromEntries(
230
+ Object.entries({
231
+ range: options.range,
232
+ "if-range": options.ifRange,
233
+ "if-none-match": options.ifNoneMatch,
234
+ "if-modified-since": options.ifModifiedSince
235
+ }).filter((entry) => typeof entry[1] === "string")
236
+ );
237
+ return this.http.requestRaw(
238
+ "GET",
239
+ `/executions/${segment(executionId)}/review/documents/${segment(docKey)}/${path}`,
240
+ {
241
+ signal: options.signal,
242
+ headers,
243
+ acceptStatuses: [304, 416],
244
+ query: {
245
+ organization: options.organization,
246
+ ...options.version ? { v: options.version } : {},
247
+ ...options.size ? { size: options.size } : {}
248
+ }
249
+ }
250
+ );
251
+ }
252
+ };
253
+
254
+ // src/sdk/execution-events.ts
255
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
256
+
257
+ // src/sdk/review-events.ts
258
+ import { createHmac, timingSafeEqual } from "crypto";
259
+ var REVIEW_EVENT_TYPES = [
260
+ "execution.awaiting_review",
261
+ "execution.review_done",
262
+ "execution.review_closed",
263
+ "execution.review_expired"
264
+ ];
265
+ var ReviewEventVerificationError = class extends Error {
266
+ constructor(reason) {
267
+ super(`Invalid review event ${reason}`);
268
+ this.reason = reason;
269
+ this.name = "ReviewEventVerificationError";
270
+ }
271
+ reason;
272
+ };
273
+ function parseReviewEventCallback(value) {
274
+ const fail = () => {
275
+ throw new ReviewEventVerificationError("payload");
276
+ };
277
+ if (!value || typeof value !== "object" || Array.isArray(value)) return fail();
278
+ const raw = value;
279
+ const text = (key) => {
280
+ const v = raw[key];
281
+ if (typeof v !== "string" || !v.trim() || v.length > 512 || [...v].some((char) => char.charCodeAt(0) < 32))
282
+ return fail();
283
+ return v;
284
+ };
285
+ if (raw.schemaVersion !== 1 || !REVIEW_EVENT_TYPES.includes(raw.type))
286
+ return fail();
287
+ const event = {
288
+ schemaVersion: 1,
289
+ eventId: text("eventId"),
290
+ type: raw.type,
291
+ organization: text("organization"),
292
+ executionResultId: text("executionResultId"),
293
+ reviewId: text("reviewId"),
294
+ publishedAt: text("publishedAt")
295
+ };
296
+ if (!Number.isFinite(Date.parse(event.publishedAt))) return fail();
297
+ for (const key of ["flowId", "nodeId"])
298
+ if (raw[key] !== void 0) event[key] = text(key);
299
+ for (const key of ["flowVersion", "reviewVersion"]) {
300
+ if (raw[key] === void 0) continue;
301
+ if (!Number.isSafeInteger(raw[key]) || Number(raw[key]) < 1) return fail();
302
+ event[key] = Number(raw[key]);
303
+ }
304
+ if (raw.documentKeys !== void 0) {
305
+ if (!Array.isArray(raw.documentKeys) || raw.documentKeys.length > 1e3 || raw.documentKeys.some((v) => typeof v !== "string" || !v || v.length > 512))
306
+ return fail();
307
+ event.documentKeys = raw.documentKeys;
308
+ }
309
+ if (raw.decision !== void 0) {
310
+ if (!["continue", "exclude", "rerun", "reject"].includes(String(raw.decision))) return fail();
311
+ event.decision = raw.decision;
312
+ }
313
+ if (raw.reviewStatus !== void 0) {
314
+ if (!["completed", "rejected", "failed"].includes(String(raw.reviewStatus))) return fail();
315
+ event.reviewStatus = raw.reviewStatus;
316
+ }
317
+ if (event.type === "execution.review_done" && (!event.decision || !event.reviewVersion))
318
+ return fail();
319
+ if (event.type === "execution.review_closed" && !event.reviewStatus) return fail();
320
+ return event;
321
+ }
322
+
323
+ // src/sdk/execution-events.ts
324
+ var EXECUTION_NODE_EVENT_TYPES = [
325
+ "execution.node.started",
326
+ "execution.node.completed",
327
+ "execution.node.failed",
328
+ "execution.node.reused"
329
+ ];
330
+ function parseExecutionEventCallback(value) {
331
+ const raw = value;
332
+ if (!raw || !EXECUTION_NODE_EVENT_TYPES.includes(raw.type))
333
+ return parseReviewEventCallback(value);
334
+ const fail = () => {
335
+ throw new ReviewEventVerificationError("payload");
336
+ };
337
+ const text = (key) => {
338
+ const v = raw[key];
339
+ return typeof v === "string" && v.length > 0 && v.length <= 512 && [...v].every((c) => c.charCodeAt(0) >= 32) ? v : fail();
340
+ };
341
+ const integer = (key) => Number.isSafeInteger(raw[key]) && Number(raw[key]) >= 1 ? Number(raw[key]) : fail();
342
+ if (raw.schemaVersion !== 1) return fail();
343
+ const event = {
344
+ schemaVersion: 1,
345
+ eventId: text("eventId"),
346
+ type: raw.type,
347
+ organization: text("organization"),
348
+ executionResultId: text("executionResultId"),
349
+ sequence: integer("sequence"),
350
+ occurredAt: text("occurredAt"),
351
+ nodeId: text("nodeId"),
352
+ nodeType: text("nodeType"),
353
+ revision: integer("revision"),
354
+ attemptId: text("attemptId"),
355
+ documentKeys: []
356
+ };
357
+ if (!Number.isFinite(Date.parse(event.occurredAt))) return fail();
358
+ if (!Array.isArray(raw.documentKeys) || !raw.documentKeys.length || raw.documentKeys.length > 1e3 || raw.documentKeys.some(
359
+ (v) => typeof v !== "string" || !v || v.length > 512 || [...v].some((c) => c.charCodeAt(0) < 32)
360
+ ))
361
+ return fail();
362
+ event.documentKeys = raw.documentKeys;
363
+ if (raw.flowId !== void 0) event.flowId = text("flowId");
364
+ if (raw.flowVersion !== void 0) event.flowVersion = integer("flowVersion");
365
+ if (raw.publishedAt !== void 0) {
366
+ event.publishedAt = text("publishedAt");
367
+ if (!Number.isFinite(Date.parse(event.publishedAt))) return fail();
368
+ }
369
+ return event;
370
+ }
371
+ var ExecutionEvents = class {
372
+ constructor(http) {
373
+ this.http = http;
374
+ }
375
+ http;
376
+ /** One bounded page; never polls for completion or consumes a final result. */
377
+ async list(id, options) {
378
+ const after = options.after ?? 0, limit = options.limit ?? 100;
379
+ if (!id || id.length > 512 || id === "." || id === ".." || /[\\/]/.test(id) || [...id].some((c) => c.charCodeAt(0) < 32) || !options.organization?.trim() || !Number.isSafeInteger(after) || after < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 100)
380
+ throw new Error("Invalid execution event query");
381
+ const page = await this.http.requestJson(
382
+ "GET",
383
+ `/executions/${encodeURIComponent(id)}/events`,
384
+ {
385
+ signal: options.signal,
386
+ query: { organization: options.organization, after: String(after), limit: String(limit) }
387
+ }
388
+ );
389
+ if (page.executionResultId !== id || !Array.isArray(page.events) || page.events.length > limit || !Number.isSafeInteger(page.lastSequence) || page.lastSequence < 0 || typeof page.truncated !== "boolean")
390
+ throw new ReviewEventVerificationError("payload");
391
+ const events = page.events.map((value) => parseExecutionEventCallback(value));
392
+ if (events.some(
393
+ (event, i) => !("sequence" in event) || event.executionResultId !== id || event.organization !== options.organization || event.sequence <= (i ? events[i - 1].sequence : after) || event.sequence > page.lastSequence
394
+ ))
395
+ throw new ReviewEventVerificationError("payload");
396
+ if (page.nextAfter !== null && (!Number.isSafeInteger(page.nextAfter) || page.nextAfter <= after || page.nextAfter !== events.at(-1)?.sequence))
397
+ throw new ReviewEventVerificationError("payload");
398
+ return { ...page, events };
399
+ }
400
+ };
401
+
174
402
  // src/sdk/errors.ts
175
403
  var DocmanaError = class extends Error {
176
404
  status;
@@ -178,6 +406,7 @@ var DocmanaError = class extends Error {
178
406
  code;
179
407
  /** Public API business code; `code` retains the SDK's existing error category. */
180
408
  apiCode;
409
+ reviewConflict;
181
410
  constructor(message, opts = {}) {
182
411
  super(message);
183
412
  this.name = new.target.name;
@@ -303,7 +532,7 @@ var HttpClient = class {
303
532
  if (res.status === 401 && this.opts.getAccessToken) {
304
533
  res = await this.send(method, path, options, true);
305
534
  }
306
- if (!res.ok) {
535
+ if (!res.ok && !options.acceptStatuses?.includes(res.status)) {
307
536
  const requestId = res.headers.get("x-request-id") ?? void 0;
308
537
  const message = await this.extractMessage(res);
309
538
  const error = errorFromResponse(
@@ -315,6 +544,15 @@ var HttpClient = class {
315
544
  const data = await res.clone().json().catch(() => null);
316
545
  if (typeof data?.code === "string" && /^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(data.code))
317
546
  Object.defineProperty(error, "apiCode", { value: data.code, enumerable: true });
547
+ if (res.status === 409 && data?.details)
548
+ Object.defineProperty(error, "reviewConflict", {
549
+ value: {
550
+ version: Number.isInteger(data.details.version) ? data.details.version : null,
551
+ submittedBy: typeof data.details.submittedBy === "string" ? data.details.submittedBy.slice(0, 200) : null,
552
+ submittedAt: typeof data.details.submittedAt === "string" ? data.details.submittedAt.slice(0, 40) : null
553
+ },
554
+ enumerable: true
555
+ });
318
556
  throw error;
319
557
  }
320
558
  return res;
@@ -816,6 +1054,8 @@ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
816
1054
  // src/sdk/client.ts
817
1055
  var Docmana = class {
818
1056
  authoring;
1057
+ reviews;
1058
+ executionEvents;
819
1059
  config;
820
1060
  http;
821
1061
  constructor(config) {
@@ -828,6 +1068,8 @@ var Docmana = class {
828
1068
  headers: this.config.headers
829
1069
  });
830
1070
  this.authoring = new FormulaAuthoring(this.http);
1071
+ this.reviews = new Reviews(this.http);
1072
+ this.executionEvents = new ExecutionEvents(this.http);
831
1073
  }
832
1074
  async runFlowAsync(flowId, params, options = {}) {
833
1075
  const parts = await resolveInputs(params);