@nicia-ai/typegraph 0.67.1 → 0.68.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @nicia-ai/typegraph
2
2
 
3
+ ## 0.68.0
4
+
5
+ ### Highlights
6
+
7
+ TypeGraph 0.68 adds atomic operations to durable graph-merge branches. `operateDurableBranch()` lets a durable host commit an opaque graph mutation and immutable evidence in one host transaction, so a process can recover and deliver committed work after a crash without inventing a second coordination protocol. Canonical request digests make retries exact: the same idempotency key replays its evidence, while a changed mutation or metadata payload conflicts without another write.
8
+
9
+ The new evidence lifecycle is inspectable and bounded. Applications can read or page committed evidence, mark delivery monotonically, and ask whether any evidence remains undelivered. TypeGraph validates every host-returned outcome and preserves a typed destruction fence until downstream delivery is complete; unsupported hosts execute no mutation, and existing durable strategies remain valid without the optional capability.
10
+
11
+ ### Upgrade notes
12
+
13
+ - Existing `DurableWorkingCopyStrategy` implementations require no changes unless they opt into `operations`. To opt in, commit the host mutation and its immutable evidence in one transaction, attest the descriptor's sealed origin, enforce exact idempotency replay and digest conflicts, and serialize operations against destruction.
14
+ - Treat only `applied` and `replayed` outcomes from `operateDurableBranch()` as committed. An `unsupported` outcome guarantees that the host ran no mutation SQL; do not recreate the atomic guarantee with callbacks or a separate evidence write.
15
+ - Deliver committed evidence with `scanDurableOperations()` or `getDurableOperation()`, then call `markDurableOperationDelivered()` only after the downstream transaction commits. A first application must remain undelivered, and `destroyDurableBranch()` refuses while any evidence is undelivered.
16
+
17
+ ### Minor Changes
18
+
19
+ - [#731](https://github.com/nicia-ai/typegraph/pull/731) [`f8e800b`](https://github.com/nicia-ai/typegraph/commit/f8e800bca25f7168747624d1ab4dea525ec736c0) Thanks [@pdlug](https://github.com/pdlug)! - Add atomic durable-branch operations. A `DurableWorkingCopyStrategy` may now expose an optional `operations` capability that commits an opaque host mutation and its immutable evidence in one host transaction, keyed by idempotency. New public orchestrators `operateDurableBranch()`, `getDurableOperation()`, `scanDurableOperations()`, `markDurableOperationDelivered()`, and `durableBranchHasUndeliveredEvidence()` wrap it, with typed `DurableOperationError` subclasses for conflicts, unsupported capabilities, malformed evidence, and the undelivered-evidence destroy fence.
20
+
3
21
  ## 0.67.1
4
22
 
5
23
  ### Patch Changes
@@ -40,7 +40,7 @@ var chunkXPSPDFKO_cjs = require('../chunk-XPSPDFKO.cjs');
40
40
  require('../chunk-HAHNRASW.cjs');
41
41
  var chunkHQU2SE74_cjs = require('../chunk-HQU2SE74.cjs');
42
42
  var chunkXUO3KQFO_cjs = require('../chunk-XUO3KQFO.cjs');
43
- require('../chunk-JLSXOBLQ.cjs');
43
+ var chunkJLSXOBLQ_cjs = require('../chunk-JLSXOBLQ.cjs');
44
44
  var chunkNYWU6CBZ_cjs = require('../chunk-NYWU6CBZ.cjs');
45
45
  require('../chunk-OZ3ZU7Q2.cjs');
46
46
  var chunkBXHAPCQG_cjs = require('../chunk-BXHAPCQG.cjs');
@@ -135,7 +135,13 @@ var MERGE_ERROR_CODES = {
135
135
  candidateSource: "GRAPH_MERGE_CANDIDATE_SOURCE",
136
136
  evidence: "GRAPH_MERGE_EVIDENCE",
137
137
  candidateWriteSet: "GRAPH_MERGE_CANDIDATE_WRITE_SET",
138
- review: "GRAPH_MERGE_REVIEW"
138
+ review: "GRAPH_MERGE_REVIEW",
139
+ operation: "GRAPH_MERGE_OPERATION",
140
+ operationRequest: "GRAPH_MERGE_OPERATION_REQUEST",
141
+ operationConflict: "GRAPH_MERGE_OPERATION_CONFLICT",
142
+ operationUnsupported: "GRAPH_MERGE_OPERATION_UNSUPPORTED",
143
+ operationEvidence: "GRAPH_MERGE_OPERATION_EVIDENCE",
144
+ operationUndelivered: "GRAPH_MERGE_OPERATION_UNDELIVERED"
139
145
  };
140
146
  function toTypeGraphErrorOptions(category, options) {
141
147
  return {
@@ -342,6 +348,51 @@ var MatchEvidenceError = class extends MergeError {
342
348
  this.name = "MatchEvidenceError";
343
349
  }
344
350
  };
351
+ var DurableOperationError = class extends MergeError {
352
+ code = MERGE_ERROR_CODES.operation;
353
+ constructor(message, options = {}) {
354
+ super(message, options);
355
+ this.name = "DurableOperationError";
356
+ }
357
+ };
358
+ var DurableOperationRequestError = class extends DurableOperationError {
359
+ static errorCategory = "user";
360
+ code = MERGE_ERROR_CODES.operationRequest;
361
+ constructor(message, options = {}) {
362
+ super(message, options);
363
+ this.name = "DurableOperationRequestError";
364
+ }
365
+ };
366
+ var DurableOperationConflictError = class extends DurableOperationError {
367
+ static errorCategory = "constraint";
368
+ code = MERGE_ERROR_CODES.operationConflict;
369
+ constructor(message, options = {}) {
370
+ super(message, options);
371
+ this.name = "DurableOperationConflictError";
372
+ }
373
+ };
374
+ var DurableOperationUnsupportedError = class extends DurableOperationError {
375
+ static errorCategory = "user";
376
+ code = MERGE_ERROR_CODES.operationUnsupported;
377
+ constructor(message, options = {}) {
378
+ super(message, options);
379
+ this.name = "DurableOperationUnsupportedError";
380
+ }
381
+ };
382
+ var DurableOperationEvidenceError = class extends DurableOperationError {
383
+ code = MERGE_ERROR_CODES.operationEvidence;
384
+ constructor(message, options = {}) {
385
+ super(message, options);
386
+ this.name = "DurableOperationEvidenceError";
387
+ }
388
+ };
389
+ var DurableEvidenceUndeliveredError = class extends BranchError {
390
+ code = MERGE_ERROR_CODES.operationUndelivered;
391
+ constructor(message, options = {}) {
392
+ super(message, options);
393
+ this.name = "DurableEvidenceUndeliveredError";
394
+ }
395
+ };
345
396
  function describeCause(cause) {
346
397
  return cause instanceof Error ? cause.message : String(cause);
347
398
  }
@@ -10479,6 +10530,7 @@ async function destroyDurableBranch(descriptor, strategy) {
10479
10530
  );
10480
10531
  return chunkHQU2SE74_cjs.ok(void 0);
10481
10532
  } catch (error) {
10533
+ if (error instanceof DurableEvidenceUndeliveredError) return chunkHQU2SE74_cjs.err(error);
10482
10534
  return chunkHQU2SE74_cjs.err(
10483
10535
  new BranchError(
10484
10536
  `Failed to destroy durable working copy for branch "${descriptor.branchId}": ${describeCause(error)}`,
@@ -10733,6 +10785,458 @@ async function applyDurableMergePlan(args) {
10733
10785
  );
10734
10786
  }
10735
10787
 
10788
+ // src/graph-merge/durable-operation.ts
10789
+ var DURABLE_OPERATION_SCAN_DEFAULT_LIMIT = 100;
10790
+ var DURABLE_OPERATION_SCAN_MAX_LIMIT = 1e3;
10791
+ var OPERATION_DIGEST_BYTE_LENGTH = 16;
10792
+ async function computeDurableOperationDigest(request) {
10793
+ const canonical = canonicalValueKey({
10794
+ metadata: request.metadata,
10795
+ mutation: request.mutation
10796
+ });
10797
+ return chunkZY4GXIVN_cjs.sha256Hex(canonical, OPERATION_DIGEST_BYTE_LENGTH);
10798
+ }
10799
+ function requireDescriptorOwner(descriptor, strategy) {
10800
+ const refusal = durableDescriptorRefusal(descriptor, strategy);
10801
+ if (refusal !== void 0) {
10802
+ return {
10803
+ ok: false,
10804
+ error: new DurableOperationRequestError(
10805
+ "Durable operation descriptor validation failed.",
10806
+ { cause: refusal }
10807
+ )
10808
+ };
10809
+ }
10810
+ return { ok: true, origin: durableOriginOfDescriptor(descriptor) };
10811
+ }
10812
+ async function normalizeOperationRequest(request) {
10813
+ const rawRequest = request;
10814
+ if (typeof rawRequest !== "object" || rawRequest === null || Array.isArray(rawRequest)) {
10815
+ return chunkHQU2SE74_cjs.err(
10816
+ new DurableOperationRequestError(
10817
+ "Durable operation request must be a JSON object."
10818
+ )
10819
+ );
10820
+ }
10821
+ if (typeof request.idempotencyKey !== "string" || request.idempotencyKey.length === 0) {
10822
+ return chunkHQU2SE74_cjs.err(
10823
+ new DurableOperationRequestError(
10824
+ "Durable operation request is malformed: idempotencyKey must be a non-empty string.",
10825
+ { details: { idempotencyKey: request.idempotencyKey } }
10826
+ )
10827
+ );
10828
+ }
10829
+ try {
10830
+ chunkJLSXOBLQ_cjs.assertJsonValue(request.metadata, "metadata", "Durable operation");
10831
+ chunkJLSXOBLQ_cjs.assertJsonValue(request.mutation, "mutation", "Durable operation");
10832
+ } catch (error) {
10833
+ return chunkHQU2SE74_cjs.err(
10834
+ new DurableOperationRequestError(
10835
+ `Durable operation request is not JSON-safe: ${describeCause(error)}`,
10836
+ { cause: error, details: { idempotencyKey: request.idempotencyKey } }
10837
+ )
10838
+ );
10839
+ }
10840
+ return chunkHQU2SE74_cjs.ok({
10841
+ idempotencyKey: request.idempotencyKey,
10842
+ metadata: request.metadata,
10843
+ mutation: request.mutation,
10844
+ operationDigest: await computeDurableOperationDigest(request)
10845
+ });
10846
+ }
10847
+ async function normalizeStoredEvidence(evidence, expectedIdempotencyKey) {
10848
+ if (typeof evidence !== "object" || evidence === null || Array.isArray(evidence)) {
10849
+ return chunkHQU2SE74_cjs.err(
10850
+ new DurableOperationEvidenceError(
10851
+ "Durable operation evidence must be a JSON object.",
10852
+ { details: { idempotencyKey: expectedIdempotencyKey } }
10853
+ )
10854
+ );
10855
+ }
10856
+ const record = evidence;
10857
+ if (typeof record["idempotencyKey"] !== "string" || record["idempotencyKey"].length === 0) {
10858
+ return chunkHQU2SE74_cjs.err(
10859
+ new DurableOperationEvidenceError(
10860
+ "Durable operation evidence needs a non-empty idempotency key.",
10861
+ { details: { expectedKey: expectedIdempotencyKey } }
10862
+ )
10863
+ );
10864
+ }
10865
+ if (expectedIdempotencyKey !== void 0 && record["idempotencyKey"] !== expectedIdempotencyKey) {
10866
+ return chunkHQU2SE74_cjs.err(
10867
+ new DurableOperationEvidenceError(
10868
+ "Durable operation evidence does not carry the requested idempotency key.",
10869
+ {
10870
+ details: {
10871
+ expectedKey: expectedIdempotencyKey,
10872
+ receivedKey: record["idempotencyKey"]
10873
+ }
10874
+ }
10875
+ )
10876
+ );
10877
+ }
10878
+ if (typeof record["operationDigest"] !== "string" || record["operationDigest"].length === 0) {
10879
+ return chunkHQU2SE74_cjs.err(
10880
+ new DurableOperationEvidenceError(
10881
+ "Durable operation evidence needs a non-empty operation digest.",
10882
+ { details: { idempotencyKey: record["idempotencyKey"] } }
10883
+ )
10884
+ );
10885
+ }
10886
+ try {
10887
+ chunkJLSXOBLQ_cjs.assertJsonValue(
10888
+ record["metadata"],
10889
+ "metadata",
10890
+ "Durable operation evidence"
10891
+ );
10892
+ chunkJLSXOBLQ_cjs.assertJsonValue(
10893
+ record["mutation"],
10894
+ "mutation",
10895
+ "Durable operation evidence"
10896
+ );
10897
+ } catch (error) {
10898
+ return chunkHQU2SE74_cjs.err(
10899
+ new DurableOperationEvidenceError(
10900
+ `Durable operation evidence content is not JSON-safe: ${describeCause(error)}`,
10901
+ { cause: error, details: { idempotencyKey: record["idempotencyKey"] } }
10902
+ )
10903
+ );
10904
+ }
10905
+ const refusal = validateCoordinates(record["before"], record, "before") ?? validateCoordinates(record["after"], record, "after") ?? (typeof record["delivered"] === "boolean" ? void 0 : new DurableOperationEvidenceError(
10906
+ "Durable operation evidence is missing its delivered flag.",
10907
+ { details: { idempotencyKey: record["idempotencyKey"] } }
10908
+ ));
10909
+ if (refusal !== void 0) return chunkHQU2SE74_cjs.err(refusal);
10910
+ const normalized = evidence;
10911
+ const canonicalDigest = await computeDurableOperationDigest(normalized);
10912
+ if (normalized.operationDigest !== canonicalDigest) {
10913
+ return chunkHQU2SE74_cjs.err(
10914
+ new DurableOperationEvidenceError(
10915
+ "Durable operation evidence digest disagrees with its canonical content.",
10916
+ {
10917
+ details: {
10918
+ idempotencyKey: normalized.idempotencyKey,
10919
+ expectedDigest: canonicalDigest,
10920
+ receivedDigest: normalized.operationDigest
10921
+ }
10922
+ }
10923
+ )
10924
+ );
10925
+ }
10926
+ return chunkHQU2SE74_cjs.ok(normalized);
10927
+ }
10928
+ async function validateEvidenceForOperation(evidence, expected) {
10929
+ const normalized = await normalizeStoredEvidence(
10930
+ evidence,
10931
+ expected.idempotencyKey
10932
+ );
10933
+ if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
10934
+ if (normalized.data.operationDigest !== expected.operationDigest) {
10935
+ return chunkHQU2SE74_cjs.err(
10936
+ new DurableOperationEvidenceError(
10937
+ "Durable operation evidence digest disagrees with the canonical request digest.",
10938
+ {
10939
+ details: {
10940
+ idempotencyKey: expected.idempotencyKey,
10941
+ expectedDigest: expected.operationDigest,
10942
+ receivedDigest: normalized.data.operationDigest
10943
+ }
10944
+ }
10945
+ )
10946
+ );
10947
+ }
10948
+ if (canonicalValueKey(normalized.data.metadata) !== canonicalValueKey(expected.metadata) || canonicalValueKey(normalized.data.mutation) !== canonicalValueKey(expected.mutation)) {
10949
+ return chunkHQU2SE74_cjs.err(
10950
+ new DurableOperationEvidenceError(
10951
+ "Durable operation evidence does not echo the canonical request content.",
10952
+ { details: { idempotencyKey: expected.idempotencyKey } }
10953
+ )
10954
+ );
10955
+ }
10956
+ return normalized;
10957
+ }
10958
+ var DURABLE_OPERATION_UNSUPPORTED_DIMENSIONS = /* @__PURE__ */ new Set([
10959
+ "atomicMutation",
10960
+ "evidenceStore",
10961
+ "host"
10962
+ ]);
10963
+ function isDurableOperationUnsupportedDimension(value) {
10964
+ return typeof value === "string" && DURABLE_OPERATION_UNSUPPORTED_DIMENSIONS.has(
10965
+ value
10966
+ );
10967
+ }
10968
+ async function normalizeOperationOutcome(outcome, expected) {
10969
+ if (typeof outcome !== "object" || outcome === null || Array.isArray(outcome)) {
10970
+ return chunkHQU2SE74_cjs.err(
10971
+ new DurableOperationEvidenceError(
10972
+ "Durable operation host returned a malformed outcome envelope.",
10973
+ { details: { idempotencyKey: expected.idempotencyKey } }
10974
+ )
10975
+ );
10976
+ }
10977
+ const record = outcome;
10978
+ const outcomeKind = record["outcome"];
10979
+ if (outcomeKind === "unsupported") {
10980
+ const dimensions = record["dimensions"];
10981
+ const normalizedDimensions = Array.isArray(dimensions) ? dimensions.filter(
10982
+ (dimension) => isDurableOperationUnsupportedDimension(dimension)
10983
+ ) : [];
10984
+ const [firstDimension, ...remainingDimensions] = normalizedDimensions;
10985
+ if (!Array.isArray(dimensions) || firstDimension === void 0 || normalizedDimensions.length !== dimensions.length || new Set(normalizedDimensions).size !== normalizedDimensions.length) {
10986
+ return chunkHQU2SE74_cjs.err(
10987
+ new DurableOperationEvidenceError(
10988
+ "Durable operation host returned malformed unsupported dimensions.",
10989
+ { details: { idempotencyKey: expected.idempotencyKey } }
10990
+ )
10991
+ );
10992
+ }
10993
+ return chunkHQU2SE74_cjs.ok({
10994
+ outcome: "unsupported",
10995
+ dimensions: [firstDimension, ...remainingDimensions]
10996
+ });
10997
+ }
10998
+ if (outcomeKind !== "applied" && outcomeKind !== "replayed") {
10999
+ return chunkHQU2SE74_cjs.err(
11000
+ new DurableOperationEvidenceError(
11001
+ "Durable operation host returned an unknown outcome.",
11002
+ {
11003
+ details: {
11004
+ idempotencyKey: expected.idempotencyKey,
11005
+ outcome: outcomeKind
11006
+ }
11007
+ }
11008
+ )
11009
+ );
11010
+ }
11011
+ const evidence = await validateEvidenceForOperation(
11012
+ record["evidence"],
11013
+ expected
11014
+ );
11015
+ if (chunkHQU2SE74_cjs.isErr(evidence)) return evidence;
11016
+ if (outcomeKind === "applied" && evidence.data.delivered) {
11017
+ return chunkHQU2SE74_cjs.err(
11018
+ new DurableOperationEvidenceError(
11019
+ "Newly applied durable operation evidence must be undelivered.",
11020
+ { details: { idempotencyKey: expected.idempotencyKey } }
11021
+ )
11022
+ );
11023
+ }
11024
+ return chunkHQU2SE74_cjs.ok({ outcome: outcomeKind, evidence: evidence.data });
11025
+ }
11026
+ function validateCoordinates(coordinates, evidence, side) {
11027
+ const idempotencyKey = evidence["idempotencyKey"];
11028
+ if (typeof coordinates !== "object" || coordinates === null || Array.isArray(coordinates)) {
11029
+ return new DurableOperationEvidenceError(
11030
+ `Durable operation evidence ${side} coordinates are malformed.`,
11031
+ { details: { idempotencyKey, side } }
11032
+ );
11033
+ }
11034
+ const record = coordinates;
11035
+ if (typeof record["base"] !== "string" || record["base"].length === 0) {
11036
+ return new DurableOperationEvidenceError(
11037
+ `Durable operation evidence ${side} coordinates need a non-empty base.`,
11038
+ { details: { idempotencyKey, side } }
11039
+ );
11040
+ }
11041
+ if (record["revision"] !== void 0 && (typeof record["revision"] !== "string" || record["revision"].length === 0)) {
11042
+ return new DurableOperationEvidenceError(
11043
+ `Durable operation evidence ${side} coordinates revision must be a non-empty string.`,
11044
+ { details: { idempotencyKey, side } }
11045
+ );
11046
+ }
11047
+ return void 0;
11048
+ }
11049
+ function unsupportedError(member, strategyType) {
11050
+ return new DurableOperationUnsupportedError(
11051
+ `Durable strategy "${strategyType}" does not provide the "${member}" operation capability.`,
11052
+ {
11053
+ details: { strategyType, member },
11054
+ suggestion: "Use a strategy whose `operations` capability provides atomic mutation-plus-evidence and evidence access; TypeGraph never emulates the atomic guarantee."
11055
+ }
11056
+ );
11057
+ }
11058
+ async function operateDurableBranch(descriptor, strategy, request) {
11059
+ const owner = requireDescriptorOwner(descriptor, strategy);
11060
+ if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
11061
+ const normalized = await normalizeOperationRequest(request);
11062
+ if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
11063
+ if (strategy.operations === void 0) {
11064
+ return chunkHQU2SE74_cjs.ok({
11065
+ outcome: "unsupported",
11066
+ dimensions: ["atomicMutation"]
11067
+ });
11068
+ }
11069
+ try {
11070
+ const outcome = await strategy.operations.operate({
11071
+ descriptor: descriptor.store,
11072
+ expectedOrigin: owner.origin,
11073
+ request: normalized.data
11074
+ });
11075
+ return await normalizeOperationOutcome(outcome, normalized.data);
11076
+ } catch (error) {
11077
+ return chunkHQU2SE74_cjs.err(
11078
+ error instanceof DurableOperationError ? error : new DurableOperationError(
11079
+ `Durable operation failed: ${describeCause(error)}`,
11080
+ {
11081
+ cause: error,
11082
+ details: { idempotencyKey: request.idempotencyKey }
11083
+ }
11084
+ )
11085
+ );
11086
+ }
11087
+ }
11088
+ async function getDurableOperation(descriptor, strategy, idempotencyKey) {
11089
+ const owner = requireDescriptorOwner(descriptor, strategy);
11090
+ if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
11091
+ if (strategy.operations === void 0) {
11092
+ return chunkHQU2SE74_cjs.err(unsupportedError("get", strategy.type));
11093
+ }
11094
+ try {
11095
+ const evidence = await strategy.operations.get({
11096
+ descriptor: descriptor.store,
11097
+ expectedOrigin: owner.origin,
11098
+ idempotencyKey
11099
+ });
11100
+ if (evidence === void 0) return chunkHQU2SE74_cjs.ok(void 0);
11101
+ return await normalizeStoredEvidence(evidence, idempotencyKey);
11102
+ } catch (error) {
11103
+ return chunkHQU2SE74_cjs.err(
11104
+ error instanceof DurableOperationError ? error : new DurableOperationError(
11105
+ `Failed to read durable operation "${idempotencyKey}": ${describeCause(error)}`,
11106
+ { cause: error, details: { idempotencyKey } }
11107
+ )
11108
+ );
11109
+ }
11110
+ }
11111
+ async function scanDurableOperations(descriptor, strategy, options = {}) {
11112
+ const owner = requireDescriptorOwner(descriptor, strategy);
11113
+ if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
11114
+ const limit = options.limit ?? DURABLE_OPERATION_SCAN_DEFAULT_LIMIT;
11115
+ if (!Number.isInteger(limit) || limit < 1) {
11116
+ return chunkHQU2SE74_cjs.err(
11117
+ new DurableOperationRequestError(
11118
+ "Durable operation scan limit must be a positive integer.",
11119
+ { details: { limit } }
11120
+ )
11121
+ );
11122
+ }
11123
+ if (limit > DURABLE_OPERATION_SCAN_MAX_LIMIT) {
11124
+ return chunkHQU2SE74_cjs.err(
11125
+ new DurableOperationRequestError(
11126
+ `Durable operation scan limit ${limit} exceeds the maximum of ${DURABLE_OPERATION_SCAN_MAX_LIMIT}.`,
11127
+ {
11128
+ details: { limit, max: DURABLE_OPERATION_SCAN_MAX_LIMIT },
11129
+ suggestion: `Request at most ${DURABLE_OPERATION_SCAN_MAX_LIMIT} operations per page and page with the returned cursor.`
11130
+ }
11131
+ )
11132
+ );
11133
+ }
11134
+ if (strategy.operations === void 0) {
11135
+ return chunkHQU2SE74_cjs.err(unsupportedError("scan", strategy.type));
11136
+ }
11137
+ try {
11138
+ const rawPage = await strategy.operations.scan({
11139
+ descriptor: descriptor.store,
11140
+ expectedOrigin: owner.origin,
11141
+ after: options.after,
11142
+ limit
11143
+ });
11144
+ if (typeof rawPage !== "object" || rawPage === null || Array.isArray(rawPage)) {
11145
+ return chunkHQU2SE74_cjs.err(
11146
+ new DurableOperationEvidenceError(
11147
+ "Durable operation scan returned a malformed page.",
11148
+ { details: { limit } }
11149
+ )
11150
+ );
11151
+ }
11152
+ const page = rawPage;
11153
+ const rawOperations = page["operations"];
11154
+ const cursor = page["cursor"];
11155
+ if (!Array.isArray(rawOperations) || rawOperations.length > limit || cursor !== void 0 && (typeof cursor !== "string" || cursor.length === 0)) {
11156
+ return chunkHQU2SE74_cjs.err(
11157
+ new DurableOperationEvidenceError(
11158
+ "Durable operation scan returned a malformed page.",
11159
+ { details: { limit } }
11160
+ )
11161
+ );
11162
+ }
11163
+ const operations = [];
11164
+ for (const evidence of rawOperations) {
11165
+ const normalized = await normalizeStoredEvidence(evidence);
11166
+ if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
11167
+ operations.push(normalized.data);
11168
+ }
11169
+ return chunkHQU2SE74_cjs.ok(
11170
+ cursor === void 0 ? { operations } : { operations, cursor }
11171
+ );
11172
+ } catch (error) {
11173
+ return chunkHQU2SE74_cjs.err(
11174
+ error instanceof DurableOperationError ? error : new DurableOperationError(
11175
+ `Failed to scan durable operations: ${describeCause(error)}`,
11176
+ { cause: error, details: { limit } }
11177
+ )
11178
+ );
11179
+ }
11180
+ }
11181
+ async function markDurableOperationDelivered(descriptor, strategy, idempotencyKey) {
11182
+ const owner = requireDescriptorOwner(descriptor, strategy);
11183
+ if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
11184
+ if (strategy.operations === void 0) {
11185
+ return chunkHQU2SE74_cjs.err(unsupportedError("markDelivered", strategy.type));
11186
+ }
11187
+ try {
11188
+ const evidence = await strategy.operations.markDelivered({
11189
+ descriptor: descriptor.store,
11190
+ expectedOrigin: owner.origin,
11191
+ idempotencyKey
11192
+ });
11193
+ if (evidence === void 0) return chunkHQU2SE74_cjs.ok(void 0);
11194
+ const normalized = await normalizeStoredEvidence(evidence, idempotencyKey);
11195
+ if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
11196
+ if (!normalized.data.delivered) {
11197
+ return chunkHQU2SE74_cjs.err(
11198
+ new DurableOperationEvidenceError(
11199
+ "Durable operation delivery marking returned undelivered evidence.",
11200
+ { details: { idempotencyKey } }
11201
+ )
11202
+ );
11203
+ }
11204
+ return normalized;
11205
+ } catch (error) {
11206
+ return chunkHQU2SE74_cjs.err(
11207
+ error instanceof DurableOperationError ? error : new DurableOperationError(
11208
+ `Failed to mark durable operation "${idempotencyKey}" delivered: ${describeCause(error)}`,
11209
+ { cause: error, details: { idempotencyKey } }
11210
+ )
11211
+ );
11212
+ }
11213
+ }
11214
+ async function durableBranchHasUndeliveredEvidence(descriptor, strategy) {
11215
+ const owner = requireDescriptorOwner(descriptor, strategy);
11216
+ if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
11217
+ if (strategy.operations === void 0) {
11218
+ return chunkHQU2SE74_cjs.err(unsupportedError("hasUndelivered", strategy.type));
11219
+ }
11220
+ try {
11221
+ const hasUndelivered = await strategy.operations.hasUndelivered({
11222
+ descriptor: descriptor.store,
11223
+ expectedOrigin: owner.origin
11224
+ });
11225
+ return typeof hasUndelivered === "boolean" ? chunkHQU2SE74_cjs.ok(hasUndelivered) : chunkHQU2SE74_cjs.err(
11226
+ new DurableOperationEvidenceError(
11227
+ "Durable operation undelivered query returned a non-boolean value."
11228
+ )
11229
+ );
11230
+ } catch (error) {
11231
+ return chunkHQU2SE74_cjs.err(
11232
+ error instanceof DurableOperationError ? error : new DurableOperationError(
11233
+ `Failed to query undelivered durable evidence: ${describeCause(error)}`,
11234
+ { cause: error }
11235
+ )
11236
+ );
11237
+ }
11238
+ }
11239
+
10736
11240
  // src/graph-merge/evolution-branch.ts
10737
11241
  async function branchForEvolution(store, plan, makeBackend, options) {
10738
11242
  try {
@@ -10769,6 +11273,14 @@ exports.CandidateSourceError = CandidateSourceError;
10769
11273
  exports.CandidateWriteSetError = CandidateWriteSetError;
10770
11274
  exports.CandidateWriteSetSchema = CandidateWriteSetSchema;
10771
11275
  exports.CandidateWriteSetTargetSchema = CandidateWriteSetTargetSchema;
11276
+ exports.DURABLE_OPERATION_SCAN_DEFAULT_LIMIT = DURABLE_OPERATION_SCAN_DEFAULT_LIMIT;
11277
+ exports.DURABLE_OPERATION_SCAN_MAX_LIMIT = DURABLE_OPERATION_SCAN_MAX_LIMIT;
11278
+ exports.DurableEvidenceUndeliveredError = DurableEvidenceUndeliveredError;
11279
+ exports.DurableOperationConflictError = DurableOperationConflictError;
11280
+ exports.DurableOperationError = DurableOperationError;
11281
+ exports.DurableOperationEvidenceError = DurableOperationEvidenceError;
11282
+ exports.DurableOperationRequestError = DurableOperationRequestError;
11283
+ exports.DurableOperationUnsupportedError = DurableOperationUnsupportedError;
10772
11284
  exports.IdentityMergeConflictError = IdentityMergeConflictError;
10773
11285
  exports.InvalidMergeOptionsError = InvalidMergeOptionsError;
10774
11286
  exports.InvalidMergePlanError = InvalidMergePlanError;
@@ -10804,13 +11316,18 @@ exports.captureCandidateWriteSetTarget = captureCandidateWriteSetTarget;
10804
11316
  exports.captureCandidateWriteSetTargetForEvolution = captureCandidateWriteSetTargetForEvolution;
10805
11317
  exports.cloneWorkingCopyStrategy = cloneWorkingCopyStrategy;
10806
11318
  exports.computeBaseVersion = computeBaseVersion;
11319
+ exports.computeDurableOperationDigest = computeDurableOperationDigest;
10807
11320
  exports.destroyDurableBranch = destroyDurableBranch;
11321
+ exports.durableBranchHasUndeliveredEvidence = durableBranchHasUndeliveredEvidence;
10808
11322
  exports.forkedWorkingCopyStrategy = forkedWorkingCopyStrategy;
11323
+ exports.getDurableOperation = getDurableOperation;
10809
11324
  exports.ingestionBranch = ingestionBranch;
11325
+ exports.markDurableOperationDelivered = markDurableOperationDelivered;
10810
11326
  exports.merge = merge;
10811
11327
  exports.mergeIncremental = mergeIncremental;
10812
11328
  exports.normalizeMergeOptions = normalizeMergeOptions;
10813
11329
  exports.openProvenanceStore = openProvenanceStore;
11330
+ exports.operateDurableBranch = operateDurableBranch;
10814
11331
  exports.persistProvenanceRecords = persistProvenanceRecords;
10815
11332
  exports.planCandidateWriteSet = planCandidateWriteSet;
10816
11333
  exports.planCandidateWriteSetForEvolution = planCandidateWriteSetForEvolution;
@@ -10822,5 +11339,6 @@ exports.provenanceGraphId = provenanceGraphId;
10822
11339
  exports.readProvenance = readProvenance;
10823
11340
  exports.reopenDurableBranch = reopenDurableBranch;
10824
11341
  exports.revalidateCandidateWriteSetReview = revalidateCandidateWriteSetReview;
11342
+ exports.scanDurableOperations = scanDurableOperations;
10825
11343
  //# sourceMappingURL=index.cjs.map
10826
11344
  //# sourceMappingURL=index.cjs.map