@cueai/omni-reader-mcp 1.5.5 → 1.7.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 (42) hide show
  1. package/README.md +106 -46
  2. package/dist/artifact-store.d.ts +2 -0
  3. package/dist/artifact-store.js +45 -10
  4. package/dist/capabilities.d.ts +129 -2
  5. package/dist/capabilities.js +122 -19
  6. package/dist/cli/agent-config.js +2 -2
  7. package/dist/cli/arguments.d.ts +8 -4
  8. package/dist/cli/arguments.js +62 -7
  9. package/dist/cli/config-inspection.d.ts +59 -0
  10. package/dist/cli/config-inspection.js +307 -0
  11. package/dist/cli/doctor.d.ts +7 -0
  12. package/dist/cli/doctor.js +37 -2
  13. package/dist/cli/setup.js +13 -2
  14. package/dist/constants.d.ts +6 -1
  15. package/dist/constants.js +9 -4
  16. package/dist/cube-client.d.ts +4 -1
  17. package/dist/cube-client.js +286 -32
  18. package/dist/cursor.d.ts +4 -0
  19. package/dist/cursor.js +11 -13
  20. package/dist/errors.d.ts +1 -0
  21. package/dist/errors.js +15 -0
  22. package/dist/iiis-client.d.ts +33 -2
  23. package/dist/iiis-client.js +368 -40
  24. package/dist/index.js +10 -1
  25. package/dist/operation-journal.d.ts +17 -1
  26. package/dist/operation-journal.js +260 -15
  27. package/dist/operation-manager.d.ts +6 -2
  28. package/dist/operation-manager.js +448 -89
  29. package/dist/path-normalization.d.ts +5 -0
  30. package/dist/path-normalization.js +25 -0
  31. package/dist/path-security.d.ts +2 -1
  32. package/dist/path-security.js +49 -28
  33. package/dist/protocol.d.ts +10 -2
  34. package/dist/protocol.js +23 -7
  35. package/dist/remote-client.js +3 -13
  36. package/dist/result-contract.d.ts +76 -32
  37. package/dist/result-contract.js +121 -5
  38. package/dist/task-runtime.d.ts +3 -2
  39. package/dist/task-runtime.js +2 -2
  40. package/dist/tools.d.ts +4 -0
  41. package/dist/tools.js +41 -31
  42. package/package.json +1 -1
@@ -1,13 +1,23 @@
1
+ import type { DirectProfile } from "./capabilities.js";
1
2
  import { type RepresentationIntent } from "./protocol.js";
2
3
  export type JournalState = "CREATED" | "GRANT_PENDING" | "GRANT_ISSUED" | "UPLOADING" | "PROCESSING" | "RESULT_READY" | "ACK_PENDING" | "CLEANUP_PENDING" | "COMPLETED" | "FAILED" | "CANCELED" | "EXPIRED";
3
4
  export type JournalProgressUnit = "page" | "sheet" | "slide" | "frame" | "segment";
4
5
  export type JournalCleanupState = "not_created" | "in_use" | "pending" | "deleted";
5
6
  export type JournalDeliveryState = "not_created" | "pending" | "deleted_after_ack";
7
+ export type JournalResultDelivery = "auto" | "artifact";
6
8
  export interface JournalProgress {
7
9
  readonly unit: JournalProgressUnit;
8
10
  readonly completed: number;
9
11
  readonly total: number;
10
12
  }
13
+ export interface JournalBillingFacts {
14
+ readonly creditsCharged: number;
15
+ readonly creditsRemaining: number;
16
+ }
17
+ export interface JournalSettlementFacts {
18
+ readonly directProfile: DirectProfile | null;
19
+ readonly billing: JournalBillingFacts;
20
+ }
11
21
  export type JournalSourceKind = "local" | "url";
12
22
  export type JournalDetail = "text" | "grounded" | "layout";
13
23
  export type JournalGroundingSchemaVersion = "none" | "omni.grounding.v1";
@@ -22,6 +32,9 @@ export interface JournalRecord {
22
32
  readonly detail: JournalDetail;
23
33
  readonly groundingSchemaVersion: JournalGroundingSchemaVersion;
24
34
  readonly bundleProtocolVersion: JournalBundleProtocolVersion;
35
+ readonly resultDeliveryEffective: JournalResultDelivery;
36
+ readonly directProfile: DirectProfile | null;
37
+ readonly billing: JournalBillingFacts | null;
25
38
  readonly operationId: string | null;
26
39
  readonly operationToken: string | null;
27
40
  readonly uploadUrl: string | null;
@@ -85,9 +98,12 @@ export declare class OperationJournal {
85
98
  constructor(options?: OperationJournalOptions);
86
99
  requestIdentityHmac(canonicalIdentityJson: string): Promise<string>;
87
100
  sourceLocatorHmac(sourceLocator: string): Promise<string>;
88
- beginIntent(clientRequestId: string, canonicalIdentityJson: string, sourceKind?: JournalSourceKind, sourceLocator?: string | null, representation?: RepresentationIntent): Promise<JournalRecord>;
101
+ beginIntent(clientRequestId: string, canonicalIdentityJson: string, sourceKind?: JournalSourceKind, sourceLocator?: string | null, representation?: RepresentationIntent, resultDelivery?: JournalResultDelivery): Promise<JournalRecord>;
89
102
  migrateLegacyRecord(clientRequestId: string, canonicalIdentityJson: string, sourceLocator?: string | null): Promise<JournalRecord | null>;
90
103
  transition(clientRequestId: string, expectedState: JournalState, nextState: JournalState, patch: JournalPatch): Promise<JournalRecord>;
104
+ strengthenResultDelivery(clientRequestId: string, requested: JournalResultDelivery): Promise<JournalRecord>;
105
+ bindDirectProfile(clientRequestId: string, directProfile: DirectProfile): Promise<JournalRecord>;
106
+ bindSettlementFacts(clientRequestId: string, facts: JournalSettlementFacts): Promise<JournalRecord>;
91
107
  loadByRequestId(clientRequestId: string): Promise<JournalRecord | null>;
92
108
  loadByOperationId(operationId: string): Promise<JournalRecord | null>;
93
109
  loadLatestByRequestIdentityHmac(requestIdentityHmac: string): Promise<JournalRecord | null>;
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { OmniBridgeError } from "./errors.js";
6
6
  import { GROUNDING_SCHEMA_VERSION, RESULT_BUNDLE_PROTOCOL_VERSION, normalizeRepresentation, } from "./protocol.js";
7
- // D2-D item 4-6: v3 records persist keyed identities instead of unkeyed
7
+ // D2-D item 4-6: v3/v4 records persist keyed identities instead of unkeyed
8
8
  // request/source hashes. requestIdentityHmac and sourceLocatorHmac are
9
9
  // lowercase full hex of HMAC-SHA-256 over the journal secret with the exact
10
10
  // domain prefixes below; they are never returned in tool output and never
@@ -43,6 +43,15 @@ const PERSISTED_V3_KEYS = [
43
43
  "resultExpiresAt",
44
44
  "errorCode",
45
45
  ];
46
+ const PERSISTED_V4_KEYS = [
47
+ ...PERSISTED_V3_KEYS,
48
+ "result_delivery_effective",
49
+ ];
50
+ const PERSISTED_V5_KEYS = [
51
+ ...PERSISTED_V4_KEYS,
52
+ "direct_profile",
53
+ "billing",
54
+ ];
46
55
  const JOURNAL_STATES = new Set([
47
56
  "CREATED",
48
57
  "GRANT_PENDING",
@@ -81,6 +90,7 @@ const DELIVERY_STATES = new Set([
81
90
  "pending",
82
91
  "deleted_after_ack",
83
92
  ]);
93
+ const RESULT_DELIVERIES = new Set(["auto", "artifact"]);
84
94
  const RECORD_FILE_PATTERN = /^[0-9a-f]{64}(?:\.issued)?\.json$/u;
85
95
  const LOCK_STALE_MS = 30_000;
86
96
  const HEX_64_PATTERN = /^[0-9a-f]{64}$/u;
@@ -255,6 +265,73 @@ function parseVersionThreeRecord(value) {
255
265
  }
256
266
  return value;
257
267
  }
268
+ function parseVersionFourRecord(value) {
269
+ const keys = Object.keys(value);
270
+ if (keys.length !== PERSISTED_V4_KEYS.length ||
271
+ PERSISTED_V4_KEYS.some((key) => !(key in value)) ||
272
+ value.version !== 4 ||
273
+ typeof value.result_delivery_effective !== "string" ||
274
+ !RESULT_DELIVERIES.has(value.result_delivery_effective)) {
275
+ throw new Error("version-4 record fields are invalid");
276
+ }
277
+ const { result_delivery_effective, ...versionThreeFields } = value;
278
+ const validated = parseVersionThreeRecord({
279
+ ...versionThreeFields,
280
+ version: 3,
281
+ });
282
+ return {
283
+ ...validated,
284
+ version: 4,
285
+ result_delivery_effective: result_delivery_effective,
286
+ };
287
+ }
288
+ function isBillingDirectProfile(value) {
289
+ return value === "omni.direct_text_billing.v1"
290
+ || value === "omni.direct_grounding_billing.v1";
291
+ }
292
+ function isPersistedBilling(value) {
293
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
294
+ return false;
295
+ }
296
+ const billing = value;
297
+ return Object.keys(billing).length === 2
298
+ && "credits_charged" in billing
299
+ && "credits_remaining" in billing
300
+ && typeof billing.credits_charged === "number"
301
+ && Number.isFinite(billing.credits_charged)
302
+ && billing.credits_charged >= 0
303
+ && typeof billing.credits_remaining === "number"
304
+ && Number.isFinite(billing.credits_remaining)
305
+ && billing.credits_remaining >= 0;
306
+ }
307
+ function parseVersionFiveRecord(value) {
308
+ const keys = Object.keys(value);
309
+ if (keys.length !== PERSISTED_V5_KEYS.length
310
+ || PERSISTED_V5_KEYS.some((key) => !(key in value))
311
+ || value.version !== 5
312
+ || !(value.direct_profile === null
313
+ || isBillingDirectProfile(value.direct_profile))
314
+ || !(value.billing === null || isPersistedBilling(value.billing))) {
315
+ throw new Error("version-5 record fields are invalid");
316
+ }
317
+ const { direct_profile, billing, ...versionFourFields } = value;
318
+ const validated = parseVersionFourRecord({
319
+ ...versionFourFields,
320
+ version: 4,
321
+ });
322
+ if ((validated.sourceKind === "url" && direct_profile !== null)
323
+ || (validated.sourceKind === "local"
324
+ && billing !== null
325
+ && direct_profile === null)) {
326
+ throw new Error("version-5 settlement facts are invalid");
327
+ }
328
+ return {
329
+ ...validated,
330
+ version: 5,
331
+ direct_profile: direct_profile,
332
+ billing: billing,
333
+ };
334
+ }
258
335
  function parsePersistedRecord(value) {
259
336
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
260
337
  throw new Error("record is not an object");
@@ -266,12 +343,16 @@ function parsePersistedRecord(value) {
266
343
  return parseVersionTwoRecord(record);
267
344
  if (record.version === 3)
268
345
  return parseVersionThreeRecord(record);
346
+ if (record.version === 4)
347
+ return parseVersionFourRecord(record);
348
+ if (record.version === 5)
349
+ return parseVersionFiveRecord(record);
269
350
  throw new Error("record version is invalid");
270
351
  }
271
352
  function migrateInMemory(stored) {
272
- if (stored.version === 3) {
353
+ if (stored.version === 3 || stored.version === 4 || stored.version === 5) {
273
354
  return {
274
- version: 3,
355
+ version: 5,
275
356
  clientRequestId: stored.clientRequestId,
276
357
  requestIdentityHmac: stored.requestIdentityHmac,
277
358
  sourceLocatorHmac: stored.sourceLocatorHmac,
@@ -282,6 +363,16 @@ function migrateInMemory(stored) {
282
363
  detail: stored.detail,
283
364
  groundingSchemaVersion: stored.groundingSchemaVersion,
284
365
  bundleProtocolVersion: stored.bundleProtocolVersion,
366
+ resultDeliveryEffective: stored.version === 4 || stored.version === 5
367
+ ? stored.result_delivery_effective
368
+ : "auto",
369
+ directProfile: stored.version === 5 ? stored.direct_profile : null,
370
+ billing: stored.version === 5 && stored.billing !== null
371
+ ? {
372
+ creditsCharged: stored.billing.credits_charged,
373
+ creditsRemaining: stored.billing.credits_remaining,
374
+ }
375
+ : null,
285
376
  operationId: stored.operationId,
286
377
  operationToken: stored.operationToken,
287
378
  state: stored.state,
@@ -308,7 +399,7 @@ function migrateInMemory(stored) {
308
399
  // are recomputed only when safe canonical source facts reconstruct the
309
400
  // stored unkeyed requestHash (see #reconstructLegacy).
310
401
  return {
311
- version: 3,
402
+ version: 5,
312
403
  clientRequestId: stored.clientRequestId,
313
404
  requestIdentityHmac: null,
314
405
  sourceLocatorHmac: null,
@@ -319,6 +410,9 @@ function migrateInMemory(stored) {
319
410
  detail: "text",
320
411
  groundingSchemaVersion: "none",
321
412
  bundleProtocolVersion: "none",
413
+ resultDeliveryEffective: "auto",
414
+ directProfile: null,
415
+ billing: null,
322
416
  operationId: stored.operationId,
323
417
  operationToken: stored.operationToken,
324
418
  state: stored.state,
@@ -341,7 +435,7 @@ function migrateInMemory(stored) {
341
435
  };
342
436
  }
343
437
  return {
344
- version: 3,
438
+ version: 5,
345
439
  clientRequestId: stored.clientRequestId,
346
440
  requestIdentityHmac: null,
347
441
  sourceLocatorHmac: null,
@@ -352,6 +446,9 @@ function migrateInMemory(stored) {
352
446
  detail: "text",
353
447
  groundingSchemaVersion: "none",
354
448
  bundleProtocolVersion: "none",
449
+ resultDeliveryEffective: "auto",
450
+ directProfile: null,
451
+ billing: null,
355
452
  operationId: stored.operationId,
356
453
  operationToken: stored.operationToken,
357
454
  state: stored.state,
@@ -407,6 +504,10 @@ function assertStableTransition(current, updated) {
407
504
  throw bridgeError("JOURNAL_IDENTITY_CONFLICT", "A stable local operation identifier cannot be replaced.", false);
408
505
  }
409
506
  }
507
+ if (current.directProfile !== null
508
+ && current.directProfile !== updated.directProfile) {
509
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile cannot be replaced.", false);
510
+ }
410
511
  if (current.operationToken !== null &&
411
512
  current.operationToken !== updated.operationToken &&
412
513
  !(updated.operationToken === null && TERMINAL_STATES.has(updated.state))) {
@@ -428,12 +529,28 @@ function assertStableTransition(current, updated) {
428
529
  throw bridgeError("JOURNAL_FACT_REGRESSION", "A confirmed local operation fact cannot be reverted.", false);
429
530
  }
430
531
  }
532
+ if (current.resultDeliveryEffective === "artifact" &&
533
+ updated.resultDeliveryEffective !== "artifact") {
534
+ throw bridgeError("JOURNAL_RESULT_DELIVERY_REGRESSION", "Artifact result delivery cannot be weakened to automatic delivery.", false);
535
+ }
431
536
  }
432
537
  function hmacEqual(left, right) {
433
538
  if (left.length !== right.length)
434
539
  return false;
435
540
  return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
436
541
  }
542
+ function assertJournalBilling(billing) {
543
+ if (!Number.isFinite(billing.creditsCharged)
544
+ || billing.creditsCharged < 0
545
+ || !Number.isFinite(billing.creditsRemaining)
546
+ || billing.creditsRemaining < 0) {
547
+ throw bridgeError("JOURNAL_BILLING_INVALID", "The authoritative billing facts are invalid.", false);
548
+ }
549
+ }
550
+ function billingEqual(left, right) {
551
+ return left.creditsCharged === right.creditsCharged
552
+ && left.creditsRemaining === right.creditsRemaining;
553
+ }
437
554
  export class OperationJournal {
438
555
  #rootDirectory;
439
556
  #now;
@@ -451,19 +568,22 @@ export class OperationJournal {
451
568
  async sourceLocatorHmac(sourceLocator) {
452
569
  return this.#hmac(SOURCE_LOCATOR_DOMAIN, sourceLocator);
453
570
  }
454
- // v3 intent creation. canonicalIdentityJson is the exact canonical
571
+ // v5 intent creation. canonicalIdentityJson is the exact canonical
455
572
  // serialization of {source_kind, source_facts} (which includes the
456
573
  // normalized representation tuple); the journal derives the keyed identity
457
574
  // from it and persists only the HMAC, never the payload or any unkeyed hash.
458
- async beginIntent(clientRequestId, canonicalIdentityJson, sourceKind = "local", sourceLocator = null, representation = TEXT_REPRESENTATION) {
575
+ async beginIntent(clientRequestId, canonicalIdentityJson, sourceKind = "local", sourceLocator = null, representation = TEXT_REPRESENTATION, resultDelivery = "auto") {
459
576
  validateClientRequestId(clientRequestId);
577
+ if (!RESULT_DELIVERIES.has(resultDelivery)) {
578
+ throw bridgeError("INVALID_RESULT_DELIVERY", "The result delivery mode is invalid.", false);
579
+ }
460
580
  const existing = await this.#readRecord(clientRequestId);
461
581
  if (existing !== null) {
462
582
  return this.#requireMatchingRequest(clientRequestId, canonicalIdentityJson, sourceLocator, existing);
463
583
  }
464
584
  const createdAt = this.#now().toISOString();
465
585
  const created = {
466
- version: 3,
586
+ version: 5,
467
587
  clientRequestId,
468
588
  requestIdentityHmac: await this.#hmac(REQUEST_IDENTITY_DOMAIN, canonicalIdentityJson),
469
589
  sourceLocatorHmac: sourceLocator === null
@@ -476,6 +596,9 @@ export class OperationJournal {
476
596
  detail: representation.detail,
477
597
  groundingSchemaVersion: representation.groundingSchemaVersion,
478
598
  bundleProtocolVersion: representation.bundleProtocolVersion,
599
+ resultDeliveryEffective: resultDelivery,
600
+ directProfile: null,
601
+ billing: null,
479
602
  operationId: null,
480
603
  operationToken: null,
481
604
  state: "CREATED",
@@ -507,7 +630,7 @@ export class OperationJournal {
507
630
  }
508
631
  // Migrate a legacy v1/v2 record using the caller's safe canonical source
509
632
  // facts. When the facts reconstruct the stored unkeyed requestHash, the
510
- // record migrates to v3 (durably) with domain-separated HMACs and keeps its
633
+ // record migrates to v4 (durably) with domain-separated HMACs and keeps its
511
634
  // state; otherwise it becomes a terminal source-free recovery failure and
512
635
  // never copies the unkeyed hashes. Returns null when no record exists.
513
636
  async migrateLegacyRecord(clientRequestId, canonicalIdentityJson, sourceLocator = null) {
@@ -515,8 +638,9 @@ export class OperationJournal {
515
638
  const stored = await this.#readRecord(clientRequestId);
516
639
  if (stored === null)
517
640
  return null;
518
- if (stored.version === 3)
641
+ if (stored.version === 3 || stored.version === 4 || stored.version === 5) {
519
642
  return this.#recordToPublic(clientRequestId, stored);
643
+ }
520
644
  const expected = `sha256:${createHash("sha256").update(canonicalIdentityJson, "utf8").digest("hex")}`;
521
645
  if (stored.requestHash === expected) {
522
646
  const migrated = await this.#reconstructLegacy(stored, canonicalIdentityJson, sourceLocator);
@@ -559,6 +683,105 @@ export class OperationJournal {
559
683
  return updated;
560
684
  });
561
685
  }
686
+ async strengthenResultDelivery(clientRequestId, requested) {
687
+ validateClientRequestId(clientRequestId);
688
+ if (!RESULT_DELIVERIES.has(requested)) {
689
+ throw bridgeError("INVALID_RESULT_DELIVERY", "The result delivery mode is invalid.", false);
690
+ }
691
+ return this.#withRecordLock(clientRequestId, async () => {
692
+ const stored = await this.#readRecord(clientRequestId);
693
+ if (stored === null) {
694
+ throw bridgeError("JOURNAL_RECORD_NOT_FOUND", "The local operation journal record is missing.", true);
695
+ }
696
+ const current = await this.#recordToPublic(clientRequestId, stored);
697
+ if (requested === "auto" || current.resultDeliveryEffective === "artifact") {
698
+ return current;
699
+ }
700
+ const updated = {
701
+ ...current,
702
+ resultDeliveryEffective: "artifact",
703
+ updatedAt: this.#now().toISOString(),
704
+ };
705
+ assertStableTransition(current, updated);
706
+ await this.#replaceRecord(await this.#persistedRecord(updated), this.#recordPath(clientRequestId));
707
+ await unlink(this.#legacyIssuedRecordPath(clientRequestId)).catch(() => undefined);
708
+ return updated;
709
+ });
710
+ }
711
+ async bindDirectProfile(clientRequestId, directProfile) {
712
+ validateClientRequestId(clientRequestId);
713
+ if (!isBillingDirectProfile(directProfile)) {
714
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile does not match the operation source.", false);
715
+ }
716
+ return this.#withRecordLock(clientRequestId, async () => {
717
+ const stored = await this.#readRecord(clientRequestId);
718
+ if (stored === null) {
719
+ throw bridgeError("JOURNAL_RECORD_NOT_FOUND", "The local operation journal record is missing.", true);
720
+ }
721
+ const current = await this.#recordToPublic(clientRequestId, stored);
722
+ if (current.sourceKind !== "local") {
723
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile does not match the operation source.", false);
724
+ }
725
+ if (current.directProfile !== null) {
726
+ if (current.directProfile !== directProfile) {
727
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile cannot be replaced.", false);
728
+ }
729
+ return current;
730
+ }
731
+ const updated = {
732
+ ...current,
733
+ directProfile,
734
+ updatedAt: this.#now().toISOString(),
735
+ };
736
+ assertStableTransition(current, updated);
737
+ await this.#replaceRecord(await this.#persistedRecord(updated), this.#recordPath(clientRequestId));
738
+ await unlink(this.#legacyIssuedRecordPath(clientRequestId)).catch(() => undefined);
739
+ return updated;
740
+ });
741
+ }
742
+ async bindSettlementFacts(clientRequestId, facts) {
743
+ validateClientRequestId(clientRequestId);
744
+ assertJournalBilling(facts.billing);
745
+ return this.#withRecordLock(clientRequestId, async () => {
746
+ const stored = await this.#readRecord(clientRequestId);
747
+ if (stored === null) {
748
+ throw bridgeError("JOURNAL_RECORD_NOT_FOUND", "The local operation journal record is missing.", true);
749
+ }
750
+ const current = await this.#recordToPublic(clientRequestId, stored);
751
+ const profileMatchesSource = current.sourceKind === "url"
752
+ ? facts.directProfile === null
753
+ : isBillingDirectProfile(facts.directProfile);
754
+ if (!profileMatchesSource) {
755
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile does not match the operation source.", false);
756
+ }
757
+ if (current.billing !== null) {
758
+ if (current.directProfile !== facts.directProfile) {
759
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile cannot be replaced.", false);
760
+ }
761
+ if (!billingEqual(current.billing, facts.billing)) {
762
+ throw bridgeError("JOURNAL_BILLING_MISMATCH", "The authoritative billing facts cannot be replaced.", false);
763
+ }
764
+ return current;
765
+ }
766
+ if (current.directProfile !== null
767
+ && current.directProfile !== facts.directProfile) {
768
+ throw bridgeError("JOURNAL_DIRECT_PROFILE_MISMATCH", "The direct billing profile cannot be replaced.", false);
769
+ }
770
+ const updated = {
771
+ ...current,
772
+ directProfile: facts.directProfile,
773
+ billing: {
774
+ creditsCharged: facts.billing.creditsCharged,
775
+ creditsRemaining: facts.billing.creditsRemaining,
776
+ },
777
+ updatedAt: this.#now().toISOString(),
778
+ };
779
+ assertStableTransition(current, updated);
780
+ await this.#replaceRecord(await this.#persistedRecord(updated), this.#recordPath(clientRequestId));
781
+ await unlink(this.#legacyIssuedRecordPath(clientRequestId)).catch(() => undefined);
782
+ return updated;
783
+ });
784
+ }
562
785
  async loadByRequestId(clientRequestId) {
563
786
  validateClientRequestId(clientRequestId);
564
787
  const persisted = await this.#readRecord(clientRequestId);
@@ -592,7 +815,7 @@ export class OperationJournal {
592
815
  // the journal receives, so the keyed handle is derived over that value.
593
816
  const createdAt = this.#now().toISOString();
594
817
  const created = {
595
- version: 3,
818
+ version: 5,
596
819
  clientRequestId,
597
820
  requestIdentityHmac: await this.#hmac(REQUEST_IDENTITY_DOMAIN, requestHash),
598
821
  sourceLocatorHmac: null,
@@ -603,6 +826,9 @@ export class OperationJournal {
603
826
  detail: "text",
604
827
  groundingSchemaVersion: "none",
605
828
  bundleProtocolVersion: "none",
829
+ resultDeliveryEffective: "auto",
830
+ directProfile: null,
831
+ billing: null,
606
832
  operationId: null,
607
833
  operationToken: null,
608
834
  state: "CREATED",
@@ -698,7 +924,7 @@ export class OperationJournal {
698
924
  }
699
925
  async #persistedRecord(record) {
700
926
  return {
701
- version: 3,
927
+ version: 5,
702
928
  clientRequestId: record.clientRequestId,
703
929
  // A legacy record persisted before reconstruction (for example a
704
930
  // source-free recovery failure) gets the deterministic keyed handle of
@@ -711,6 +937,14 @@ export class OperationJournal {
711
937
  detail: record.detail,
712
938
  groundingSchemaVersion: record.groundingSchemaVersion,
713
939
  bundleProtocolVersion: record.bundleProtocolVersion,
940
+ result_delivery_effective: record.resultDeliveryEffective,
941
+ direct_profile: record.directProfile,
942
+ billing: record.billing === null
943
+ ? null
944
+ : {
945
+ credits_charged: record.billing.creditsCharged,
946
+ credits_remaining: record.billing.creditsRemaining,
947
+ },
714
948
  operationId: record.operationId,
715
949
  operationToken: record.operationToken === null
716
950
  ? null
@@ -736,7 +970,7 @@ export class OperationJournal {
736
970
  }
737
971
  async #persistedFromInMemory(record) {
738
972
  return {
739
- version: 3,
973
+ version: 5,
740
974
  clientRequestId: record.clientRequestId,
741
975
  requestIdentityHmac: record.requestIdentityHmac === null
742
976
  ? await this.#hmac(REQUEST_IDENTITY_DOMAIN, "")
@@ -746,6 +980,14 @@ export class OperationJournal {
746
980
  detail: record.detail,
747
981
  groundingSchemaVersion: record.groundingSchemaVersion,
748
982
  bundleProtocolVersion: record.bundleProtocolVersion,
983
+ result_delivery_effective: record.resultDeliveryEffective,
984
+ direct_profile: record.directProfile,
985
+ billing: record.billing === null
986
+ ? null
987
+ : {
988
+ credits_charged: record.billing.creditsCharged,
989
+ credits_remaining: record.billing.creditsRemaining,
990
+ },
749
991
  operationId: record.operationId,
750
992
  operationToken: record.operationToken,
751
993
  state: record.state,
@@ -784,6 +1026,9 @@ export class OperationJournal {
784
1026
  detail: migrated.detail,
785
1027
  groundingSchemaVersion: migrated.groundingSchemaVersion,
786
1028
  bundleProtocolVersion: migrated.bundleProtocolVersion,
1029
+ resultDeliveryEffective: migrated.resultDeliveryEffective,
1030
+ directProfile: migrated.directProfile,
1031
+ billing: migrated.billing,
787
1032
  operationId: migrated.operationId,
788
1033
  operationToken,
789
1034
  uploadUrl: migrated.uploadUrl,
@@ -819,7 +1064,7 @@ export class OperationJournal {
819
1064
  return migrated;
820
1065
  }
821
1066
  async #requireMatchingRequest(clientRequestId, canonicalIdentityJson, sourceLocator, persisted) {
822
- if (persisted.version === 3) {
1067
+ if (persisted.version === 3 || persisted.version === 4 || persisted.version === 5) {
823
1068
  const expected = await this.#hmacExisting(REQUEST_IDENTITY_DOMAIN, canonicalIdentityJson);
824
1069
  if (!hmacEqual(persisted.requestIdentityHmac, expected)) {
825
1070
  throw bridgeError("IDEMPOTENCY_COLLISION", "This local operation request identifier is already bound to different metadata.", false);
@@ -833,7 +1078,7 @@ export class OperationJournal {
833
1078
  throw bridgeError("IDEMPOTENCY_COLLISION", "This local operation request identifier is already bound to different metadata.", false);
834
1079
  }
835
1080
  async #requireMatchingCubeRequest(clientRequestId, requestHash, persisted) {
836
- if (persisted.version === 3) {
1081
+ if (persisted.version === 3 || persisted.version === 4 || persisted.version === 5) {
837
1082
  const expected = await this.#hmacExisting(REQUEST_IDENTITY_DOMAIN, requestHash);
838
1083
  if (!hmacEqual(persisted.requestIdentityHmac, expected)) {
839
1084
  throw bridgeError("IDEMPOTENCY_COLLISION", "This local operation request identifier is already bound to different metadata.", false);
@@ -3,7 +3,7 @@ import type { CubeGrantClient } from "./cube-client.js";
3
3
  import type { IiisClient, ResultRetentionSink } from "./iiis-client.js";
4
4
  import type { JournalPatch, JournalRecord, JournalState, OperationJournal } from "./operation-journal.js";
5
5
  import { type OpenAllowedFileOptions, type OpenedAllowedFile } from "./path-security.js";
6
- import { type RepresentationIntent } from "./protocol.js";
6
+ import { type RepresentationIntent, type ResultDelivery } from "./protocol.js";
7
7
  import type { RemoteOmniClient } from "./remote-client.js";
8
8
  import { type ParseResult } from "./result-contract.js";
9
9
  export interface SubmitOperationInput {
@@ -12,6 +12,7 @@ export interface SubmitOperationInput {
12
12
  readonly clientRequestId: string;
13
13
  readonly signal?: AbortSignal;
14
14
  readonly context?: unknown;
15
+ readonly resultDelivery?: ResultDelivery;
15
16
  readonly representation?: RepresentationIntent;
16
17
  }
17
18
  export interface OperationDriverUpdate {
@@ -38,8 +39,9 @@ export interface OperationManagerDriver {
38
39
  result?(record: JournalRecord, signal: AbortSignal): Promise<ParseResult | void>;
39
40
  }
40
41
  export interface OperationManagerOptions {
41
- readonly journal: Pick<OperationJournal, "beginIntent" | "migrateLegacyRecord" | "requestIdentityHmac" | "sourceLocatorHmac" | "transition" | "loadByRequestId" | "loadByOperationId" | "loadLatestByRequestIdentityHmac" | "loadLatestBySourceLocatorHmac" | "listRecoverable">;
42
+ readonly journal: Pick<OperationJournal, "beginIntent" | "bindSettlementFacts" | "migrateLegacyRecord" | "requestIdentityHmac" | "sourceLocatorHmac" | "transition" | "loadByRequestId" | "loadByOperationId" | "loadLatestByRequestIdentityHmac" | "loadLatestBySourceLocatorHmac" | "listRecoverable" | "strengthenResultDelivery">;
42
43
  readonly driver: OperationManagerDriver;
44
+ readonly presentResult?: (record: JournalRecord, canonicalResult: ParseResult) => Promise<ParseResult>;
43
45
  readonly now?: () => number;
44
46
  readonly sleep?: (milliseconds: number) => Promise<void>;
45
47
  }
@@ -61,6 +63,8 @@ export interface LocalRetention extends ResultRetentionSink {
61
63
  }
62
64
  export interface LocalArtifactStore {
63
65
  createRetention(): LocalRetention;
66
+ preflightWrite?(): Promise<void>;
67
+ mintReadCursor?(resultId: string, byteOffset: number): Promise<string>;
64
68
  read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
65
69
  readBundleDescriptor?(resultId: string): Promise<BundleLocalResult | null>;
66
70
  }