@xyo-network/event-kit-statement-graph 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,27 +30,41 @@ const replay = await createFinalizedEventKitReplayController({
30
30
 
31
31
  await replay.sync()
32
32
 
33
+ // A bounded worker still reports the real live finalized head while advancing
34
+ // its durable projection by at most the requested number of blocks.
35
+ await replay.sync({ maxBlocks: 4_096, signal })
36
+
37
+ // The durable projection exposes its exact canonical XL1 root.
38
+ const at = replay.anchor() // { block, hash } or null before the first commit
39
+
33
40
  const handshakes = createEventKitHandshakeReadModel({
34
41
  body: hash => replay.body(hash),
35
42
  viewer: replay.viewer(),
36
43
  })
37
44
  ```
38
45
 
39
- The candidate controller walks one finalized block at a time through Statement
40
- Graph with the exact Event Kit object-schema allowlist. Each block is staged
41
- before publication. An unresolved Statement Graph event, a missing or corrupt
42
- claimed object, or a missing or corrupt challenge/response attachment records
43
- an explicit blocked boundary and leaves `indexedThrough` before that block. A
44
- later `sync()` retries the same block; unavailability is never interpreted as
45
- absence or revocation.
46
-
47
- Published Statement Graph `0.5.0` admits consensus-elevated, subject-bearing
46
+ The controller bulk-stages contiguous finalized ranges through Statement Graph
47
+ with the exact Event Kit object-schema allowlist. The default full `sync()`
48
+ catches up to the independently observed live head; `sync({ maxBlocks, signal })`
49
+ lets a bounded worker retain that real `finalizedHead` while advancing
50
+ `indexedThrough` by at most `maxBlocks`. A staged range is published only after
51
+ every publisher-scoped object and required challenge/response attachment in it
52
+ is verified. An unresolved Statement Graph event, a missing or corrupt claimed
53
+ object, or a missing or corrupt attachment records the earliest affected block
54
+ as an explicit blocked boundary and leaves `indexedThrough` before that block.
55
+ A clean prefix is re-scanned and may be committed first. A later `sync()` retries
56
+ the same blocked block; unavailability is never interpreted as absence or
57
+ revocation. Cancellation retains only prefixes whose atomic checkpoint save has
58
+ already resolved.
59
+
60
+ Published Statement Graph `0.5.3` admits consensus-elevated, subject-bearing
48
61
  Claims and decodes Claim bodies from finalized block carriers while resolving
49
62
  claimed objects and acceptance attachments through the datalake. The Gate 2
50
63
  live rerun against that release remains the acceptance boundary.
51
64
 
52
- Statement Graph `0.5.0` uses fold version 4 and scan-checkpoint format 2. Event
53
- Kit's publisher-scoped outer checkpoint is also format 2. Only the exact
65
+ Statement Graph `0.5.x` uses fold version 4 and scan-checkpoint format 2. Event
66
+ Kit's publisher-scoped outer checkpoint is format 3 and binds
67
+ `indexedThrough` to the exact canonical XL1 root hash. Only the exact
54
68
  current checkpoint and ordered Event Kit schema policy are accepted. Missing,
55
69
  additional, reordered, or version-suffixed policy entries fail closed as
56
70
  invalid content; no alternative-policy reader or in-place migration exists.
@@ -61,6 +75,10 @@ invalid content; no alternative-policy reader or in-place migration exists.
61
75
  - `indexedThrough` is the highest block whose Event Kit projection was fully
62
76
  committed, or `null` before the floor block has been indexed.
63
77
 
78
+ `anchor()` returns the durable `{block, hash}` pair for `indexedThrough`. A
79
+ height-only checkpoint from the previous format is replayed from the floor; it
80
+ is never upgraded into root-bound evidence by assumption.
81
+
64
82
  The strict `EventKitReplayCheckpointZod` checkpoint binds network, chain ID,
65
83
  publisher, datalake source identity, floor block, Statement Graph fold version,
66
84
  and the ordered schema policy. It retains the complete canonical
@@ -10,20 +10,35 @@ export interface EventKitReplayCheckpointStore {
10
10
  export interface FinalizedEventKitReplayOptions extends EventKitReplayCheckpointIdentity {
11
11
  readonly bodies: BodyReader;
12
12
  readonly chain: FinalizedBlockViewer;
13
+ /** Maximum contiguous blocks staged before one atomic checkpoint save. */
14
+ readonly checkpointBatchSize?: number;
13
15
  readonly checkpointStore: EventKitReplayCheckpointStore;
16
+ /** Maximum blocks requested by each Statement Graph bulk read. */
17
+ readonly scanBatchSize?: number;
14
18
  }
15
19
  export interface FinalizedEventKitReplayStatus {
16
20
  readonly blocked?: ReplayBlockedState;
17
21
  readonly finalizedHead: number;
18
22
  readonly indexedThrough: number | null;
19
23
  }
24
+ export interface FinalizedEventKitReplayAnchor {
25
+ readonly block: number;
26
+ readonly hash: string;
27
+ }
28
+ export interface FinalizedEventKitReplaySyncOptions {
29
+ /** Advance at most this many blocks while still reporting the uncapped live finalized head. */
30
+ readonly maxBlocks?: number;
31
+ readonly signal?: AbortSignal;
32
+ }
20
33
  export interface FinalizedEventKitReplayController {
34
+ /** Exact canonical XL1 root retained with the current durable projection. */
35
+ anchor(): Readonly<FinalizedEventKitReplayAnchor> | null;
21
36
  /** Resolve a verified lifecycle or acceptance-attachment body from the durable projection. */
22
37
  body(hash: string): Readonly<Record<string, unknown>> | undefined;
23
38
  snapshot(): Readonly<EventKitReplayCheckpoint>;
24
39
  status(): FinalizedEventKitReplayStatus;
25
40
  /** Walks clean finalized blocks and stops before the first unavailable body boundary. */
26
- sync(): Promise<FinalizedEventKitReplayStatus>;
41
+ sync(options?: FinalizedEventKitReplaySyncOptions): Promise<FinalizedEventKitReplayStatus>;
27
42
  /** Snapshot viewer; request it again after `sync()` to observe the new checkpoint. */
28
43
  viewer(): StatementGraphViewer;
29
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"finalizedReplayController.d.ts","sourceRoot":"","sources":["../../src/finalizedReplayController.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAA;AAMnF,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EAErB,MAAM,sCAAsC,CAAA;AAQ7C,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,kBAAkB,EAGnB,MAAM,uBAAuB,CAAA;AAO9B,MAAM,WAAW,6BAA6B;IAC5C,yEAAyE;IACzE,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxB,oFAAoF;IACpF,IAAI,CAAC,UAAU,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,8BAA+B,SAAQ,gCAAgC;IACtF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;IACpC,QAAQ,CAAC,eAAe,EAAE,6BAA6B,CAAA;CACxD;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,iCAAiC;IAChD,8FAA8F;IAC9F,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACjE,QAAQ,IAAI,QAAQ,CAAC,wBAAwB,CAAC,CAAA;IAC9C,MAAM,IAAI,6BAA6B,CAAA;IACvC,yFAAyF;IACzF,IAAI,IAAI,OAAO,CAAC,6BAA6B,CAAC,CAAA;IAC9C,sFAAsF;IACtF,MAAM,IAAI,oBAAoB,CAAA;CAC/B;AAgRD,wBAAsB,uCAAuC,CAC3D,UAAU,EAAE,8BAA8B,GACzC,OAAO,CAAC,iCAAiC,CAAC,CAkB5C;AAED,uFAAuF;AACvF,wBAAgB,yCAAyC,CACvD,OAAO,CAAC,EAAE,OAAO,GAChB,6BAA6B,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAA;CAAE,CAazF"}
1
+ {"version":3,"file":"finalizedReplayController.d.ts","sourceRoot":"","sources":["../../src/finalizedReplayController.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAA;AAMnF,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EAErB,MAAM,sCAAsC,CAAA;AAQ7C,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,kBAAkB,EAGnB,MAAM,uBAAuB,CAAA;AAO9B,MAAM,WAAW,6BAA6B;IAC5C,yEAAyE;IACzE,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxB,oFAAoF;IACpF,IAAI,CAAC,UAAU,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,8BAA+B,SAAQ,gCAAgC;IACtF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,eAAe,EAAE,6BAA6B,CAAA;IACvD,kEAAkE;IAClE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kCAAkC;IACjD,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,MAAM,WAAW,iCAAiC;IAChD,6EAA6E;IAC7E,MAAM,IAAI,QAAQ,CAAC,6BAA6B,CAAC,GAAG,IAAI,CAAA;IACxD,8FAA8F;IAC9F,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACjE,QAAQ,IAAI,QAAQ,CAAC,wBAAwB,CAAC,CAAA;IAC9C,MAAM,IAAI,6BAA6B,CAAA;IACvC,yFAAyF;IACzF,IAAI,CAAC,OAAO,CAAC,EAAE,kCAAkC,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAAA;IAC1F,sFAAsF;IACtF,MAAM,IAAI,oBAAoB,CAAA;CAC/B;AA4ZD,wBAAsB,uCAAuC,CAC3D,UAAU,EAAE,8BAA8B,GACzC,OAAO,CAAC,iCAAiC,CAAC,CAwB5C;AAED,uFAAuF;AACvF,wBAAgB,yCAAyC,CACvD,OAAO,CAAC,EAAE,OAAO,GAChB,6BAA6B,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAA;CAAE,CAazF"}
@@ -2,7 +2,7 @@ export type { ActiveSourceWakeGrantAuthorizerOptions } from './activeSourceWakeG
2
2
  export { createActiveSourceWakeGrantAuthorizer } from './activeSourceWakeGrantAuthorizer.ts';
3
3
  export { createCheckpointStatementGraphViewer } from './checkpointViewer.ts';
4
4
  export { EventKitClaimedObjectSchemas, EventKitClaimedObjectSchemaSet, } from './eventKitSchemaPolicy.ts';
5
- export type { EventKitReplayCheckpointStore, FinalizedEventKitReplayController, FinalizedEventKitReplayOptions, FinalizedEventKitReplayStatus, } from './finalizedReplayController.ts';
5
+ export type { EventKitReplayCheckpointStore, FinalizedEventKitReplayController, FinalizedEventKitReplayOptions, FinalizedEventKitReplayStatus, FinalizedEventKitReplaySyncOptions, } from './finalizedReplayController.ts';
6
6
  export { createFinalizedEventKitReplayController, createMemoryEventKitReplayCheckpointStore, } from './finalizedReplayController.ts';
7
7
  export type { StatementGraphGrantReadModelOptions } from './grantReadModel.ts';
8
8
  export { createStatementGraphGrantReadModel } from './grantReadModel.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sCAAsC,EAAE,MAAM,sCAAsC,CAAA;AAClG,OAAO,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAA;AAC5F,OAAO,EAAE,oCAAoC,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,GAC/B,MAAM,2BAA2B,CAAA;AAClC,YAAY,EACV,6BAA6B,EAC7B,iCAAiC,EACjC,8BAA8B,EAC9B,6BAA6B,GAC9B,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,uCAAuC,EACvC,yCAAyC,GAC1C,MAAM,gCAAgC,CAAA;AACvC,YAAY,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAA;AAC9E,OAAO,EAAE,kCAAkC,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAA;AAC1E,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,iCAAiC,EACjC,0BAA0B,EAC1B,sBAAsB,EACtB,4BAA4B,EAC5B,qCAAqC,EACrC,4CAA4C,EAC5C,iCAAiC,EACjC,uBAAuB,EACvB,sBAAsB,EACtB,6BAA6B,EAC7B,wCAAwC,EACxC,2CAA2C,EAC3C,yBAAyB,EACzB,gCAAgC,EAChC,sCAAsC,EACtC,gCAAgC,EAChC,yBAAyB,EACzB,4BAA4B,EAC5B,mCAAmC,EACnC,8CAA8C,EAC9C,iDAAiD,EACjD,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,wBAAwB,EACxB,gCAAgC,EAChC,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mCAAmC,EACnC,2BAA2B,EAC3B,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sCAAsC,EAAE,MAAM,sCAAsC,CAAA;AAClG,OAAO,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAA;AAC5F,OAAO,EAAE,oCAAoC,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EACL,4BAA4B,EAC5B,8BAA8B,GAC/B,MAAM,2BAA2B,CAAA;AAClC,YAAY,EACV,6BAA6B,EAC7B,iCAAiC,EACjC,8BAA8B,EAC9B,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,uCAAuC,EACvC,yCAAyC,GAC1C,MAAM,gCAAgC,CAAA;AACvC,YAAY,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAA;AAC9E,OAAO,EAAE,kCAAkC,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAA;AAC1E,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,iCAAiC,EACjC,0BAA0B,EAC1B,sBAAsB,EACtB,4BAA4B,EAC5B,qCAAqC,EACrC,4CAA4C,EAC5C,iCAAiC,EACjC,uBAAuB,EACvB,sBAAsB,EACtB,6BAA6B,EAC7B,wCAAwC,EACxC,2CAA2C,EAC3C,yBAAyB,EACzB,gCAAgC,EAChC,sCAAsC,EACtC,gCAAgC,EAChC,yBAAyB,EACzB,4BAA4B,EAC5B,mCAAmC,EACnC,8CAA8C,EAC9C,iDAAiD,EACjD,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,8BAA8B,CAAA;AACrC,YAAY,EACV,wBAAwB,EACxB,gCAAgC,EAChC,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mCAAmC,EACnC,2BAA2B,EAC3B,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,uBAAuB,CAAA"}
@@ -225,6 +225,8 @@ import {
225
225
  WakeSubscriptionAcceptanceSchema as WakeSubscriptionAcceptanceSchema2,
226
226
  WakeSubscriptionAcceptanceZod
227
227
  } from "@xyo-network/event-kit-schemas";
228
+ import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
229
+ import { isBlockBoundWitness } from "@xyo-network/xl1-protocol";
228
230
  import { STATEMENT_FOLD_VERSION as STATEMENT_FOLD_VERSION2 } from "@xyo-network/xl1-statement-graph-schemas";
229
231
  import {
230
232
  resolveVerifiedBodies,
@@ -249,7 +251,7 @@ import {
249
251
  StatementScanCheckpointZod
250
252
  } from "@xyo-network/xl1-statement-graph-sdk";
251
253
  import { z } from "zod";
252
- var EVENT_KIT_REPLAY_CHECKPOINT_VERSION = 2;
254
+ var EVENT_KIT_REPLAY_CHECKPOINT_VERSION = 3;
253
255
  var ResolvedReplayBodyZod = z.strictObject({
254
256
  body: z.record(z.string(), z.unknown()),
255
257
  hash: HashZod2
@@ -273,6 +275,7 @@ var EventKitReplayCheckpointZod = z.strictObject({
273
275
  floorBlock: BlockNumberZod,
274
276
  foldVersion: z.literal(STATEMENT_FOLD_VERSION),
275
277
  indexedThrough: z.union([BlockNumberZod, z.null()]),
278
+ indexedThroughHash: z.union([HashZod2, z.null()]),
276
279
  network: z.string().min(1),
277
280
  objectSchemas: z.tuple(EventKitClaimedObjectSchemas.map((schema) => z.literal(schema))),
278
281
  publisher: NormalizedAddressZod2,
@@ -280,6 +283,22 @@ var EventKitReplayCheckpointZod = z.strictObject({
280
283
  sourceId: z.string().min(1),
281
284
  version: z.literal(EVENT_KIT_REPLAY_CHECKPOINT_VERSION)
282
285
  });
286
+ var LegacyEventKitReplayCheckpointIdentityZod = z.looseObject({
287
+ chainId: ChainIdZod,
288
+ floorBlock: BlockNumberZod,
289
+ network: z.string().min(1),
290
+ publisher: NormalizedAddressZod2,
291
+ sourceId: z.string().min(1),
292
+ version: z.literal(2)
293
+ });
294
+ function checkpointIdentityError(checkpoint, identity) {
295
+ if (checkpoint.chainId !== identity.chainId) return `chainId ${checkpoint.chainId} != ${identity.chainId}`;
296
+ if (checkpoint.floorBlock !== identity.floorBlock) return `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`;
297
+ if (checkpoint.network !== identity.network) return `network ${checkpoint.network} != ${identity.network}`;
298
+ if (checkpoint.sourceId !== identity.sourceId) return `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`;
299
+ if (checkpoint.publisher !== identity.publisher) return `publisher ${checkpoint.publisher} != ${identity.publisher}`;
300
+ return void 0;
301
+ }
283
302
  function appliedKey(record) {
284
303
  return `${record.txHash}:${record.eventHash}:${record.hashIndex}`;
285
304
  }
@@ -328,7 +347,7 @@ function sameClaimRecords(left, right) {
328
347
  });
329
348
  }
330
349
  function progressIntegrityError(checkpoint) {
331
- const { indexedThrough } = checkpoint;
350
+ const { indexedThrough, indexedThroughHash } = checkpoint;
332
351
  if (indexedThrough !== null && indexedThrough > checkpoint.finalizedHead) {
333
352
  return `indexedThrough ${indexedThrough} exceeds finalizedHead ${checkpoint.finalizedHead}`;
334
353
  }
@@ -341,6 +360,9 @@ function progressIntegrityError(checkpoint) {
341
360
  if (indexedThrough !== null && checkpoint.scan === null) {
342
361
  return "scan checkpoint is missing for indexed state";
343
362
  }
363
+ if (indexedThrough === null !== (indexedThroughHash === null)) {
364
+ return "indexedThrough and indexedThroughHash must be present together";
365
+ }
344
366
  if (indexedThrough === null && checkpoint.applied.length > 0) {
345
367
  return "applied events exist before floorBlock has been indexed";
346
368
  }
@@ -512,26 +534,21 @@ async function persistedBodyIntegrityError(checkpoint) {
512
534
  }
513
535
  async function loadEventKitReplayCheckpoint(value, identity) {
514
536
  if (value === void 0 || value === null) return { status: "missing" };
537
+ if (typeof value === "object" && value !== null && value.version === 2) {
538
+ const legacy = LegacyEventKitReplayCheckpointIdentityZod.safeParse(value);
539
+ if (!legacy.success) {
540
+ return { detail: "recognized Event Kit replay checkpoint v2 has invalid identity", status: "corrupt" };
541
+ }
542
+ const mismatch2 = checkpointIdentityError(legacy.data, identity);
543
+ return mismatch2 === void 0 ? { status: "missing" } : { detail: mismatch2, status: "identity-mismatch" };
544
+ }
515
545
  const parsed = EventKitReplayCheckpointZod.safeParse(value);
516
546
  if (!parsed.success) {
517
547
  return { detail: "recognized Event Kit replay checkpoint has invalid content", status: "corrupt" };
518
548
  }
519
549
  const checkpoint = parsed.data;
520
- if (checkpoint.chainId !== identity.chainId) {
521
- return { detail: `chainId ${checkpoint.chainId} != ${identity.chainId}`, status: "identity-mismatch" };
522
- }
523
- if (checkpoint.floorBlock !== identity.floorBlock) {
524
- return { detail: `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`, status: "identity-mismatch" };
525
- }
526
- if (checkpoint.network !== identity.network) {
527
- return { detail: `network ${checkpoint.network} != ${identity.network}`, status: "identity-mismatch" };
528
- }
529
- if (checkpoint.sourceId !== identity.sourceId) {
530
- return { detail: `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`, status: "identity-mismatch" };
531
- }
532
- if (checkpoint.publisher !== identity.publisher) {
533
- return { detail: `publisher ${checkpoint.publisher} != ${identity.publisher}`, status: "identity-mismatch" };
534
- }
550
+ const mismatch = checkpointIdentityError(checkpoint, identity);
551
+ if (mismatch !== void 0) return { detail: mismatch, status: "identity-mismatch" };
535
552
  const bodyIntegrityError = await persistedBodyIntegrityError(checkpoint);
536
553
  if (bodyIntegrityError !== void 0) return { detail: bodyIntegrityError, status: "corrupt" };
537
554
  const integrityError = checkpointIntegrityError(checkpoint);
@@ -540,6 +557,8 @@ async function loadEventKitReplayCheckpoint(value, identity) {
540
557
  }
541
558
 
542
559
  // src/finalizedReplayController.ts
560
+ var DEFAULT_CHECKPOINT_BATCH_SIZE = 4096;
561
+ var DEFAULT_SCAN_BATCH_SIZE = 4096;
543
562
  function requireHead(value) {
544
563
  const head = Number(value);
545
564
  if (!Number.isSafeInteger(head) || head < 0) {
@@ -553,6 +572,19 @@ function requireFloor(value) {
553
572
  }
554
573
  return value;
555
574
  }
575
+ function requirePositiveInteger(value, fallback, name) {
576
+ const selected = value ?? fallback;
577
+ if (!Number.isSafeInteger(selected) || selected < 1) {
578
+ throw new TypeError(`${name} must be a positive safe integer, received ${String(selected)}`);
579
+ }
580
+ return selected;
581
+ }
582
+ function requireMaxBlocks(value) {
583
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) {
584
+ throw new TypeError(`maxBlocks must be a nonnegative safe integer, received ${String(value)}`);
585
+ }
586
+ return value;
587
+ }
556
588
  function replayContract(options, throughBlock) {
557
589
  return {
558
590
  eventBodyCorpus: { sourceId: options.sourceId },
@@ -572,6 +604,7 @@ function initialCheckpoint(options) {
572
604
  floorBlock: options.floorBlock,
573
605
  foldVersion: STATEMENT_FOLD_VERSION2,
574
606
  indexedThrough: null,
607
+ indexedThroughHash: null,
575
608
  network: options.network,
576
609
  objectSchemas: [...EventKitClaimedObjectSchemas],
577
610
  publisher: options.publisher,
@@ -615,21 +648,23 @@ function mergeBodies(existing, additions) {
615
648
  }
616
649
  return [...merged].map(([hash, body]) => ({ body, hash: HashZod3.parse(hash) })).toSorted((left, right) => left.hash.localeCompare(right.hash));
617
650
  }
618
- async function stageBlock(options, prior, block) {
619
- const result = await scanFinalizedStatements(
620
- options.chain,
621
- options.bodies,
622
- replayContract(options, block),
623
- {
624
- batchSize: 1,
625
- ...prior.scan === null ? {} : { resumeFrom: prior.scan },
626
- strict: { rejectAmbiguousSchemaDeclarations: true },
627
- through: block
628
- }
629
- );
630
- const applied = result.fold.applied.filter(
631
- (record) => normalizeAddress3(record.subject) === options.publisher
632
- );
651
+ async function exactBlockHash(chain, block, chainId) {
652
+ const raw = await chain.block.blockByNumber(block);
653
+ if (!Array.isArray(raw) || raw.length !== 2 || !Array.isArray(raw[1])) {
654
+ throw new TypeError(`finalized block ${block} is unavailable`);
655
+ }
656
+ const header = raw[0];
657
+ if (!isBlockBoundWitness(header) || header.block !== block || header.chain !== chainId) {
658
+ throw new TypeError(`finalized block ${block} does not match the configured chain`);
659
+ }
660
+ const hash = await PayloadBuilder2.hash(PayloadBuilder2.omitStorageMeta(header));
661
+ const storageHash = header._hash;
662
+ if (typeof storageHash === "string" && storageHash !== hash) {
663
+ throw new TypeError(`finalized block ${block} has invalid storage hash metadata`);
664
+ }
665
+ return HashZod3.parse(hash);
666
+ }
667
+ async function stageAppliedBlock(options, applied) {
633
668
  const appliedObjectHashes = uniqueStrings(applied.map((record) => record.objectHash));
634
669
  const objects = await resolveVerifiedBodies(options.bodies, appliedObjectHashes);
635
670
  const missingObjects = appliedObjectHashes.filter((hash) => !objects.bodies.has(hash));
@@ -655,19 +690,72 @@ async function stageBlock(options, prior, block) {
655
690
  ...schemaMismatchedObjects,
656
691
  ...missingAttachments
657
692
  ]).map((hash) => HashZod3.parse(hash));
658
- const isBlocked = result.unresolved.length > 0 || objectHashes.length > 0 || corrupt.length > 0;
659
693
  return {
660
- applied,
661
694
  bodies: new Map([...objects.bodies, ...attachments.bodies]),
695
+ corrupt,
696
+ objectHashes
697
+ };
698
+ }
699
+ async function stageRange(options, prior, fromBlock, throughBlock, signal) {
700
+ signal?.throwIfAborted();
701
+ const beforeHash = await exactBlockHash(options.chain, throughBlock, options.chainId);
702
+ signal?.throwIfAborted();
703
+ const result = await scanFinalizedStatements(
704
+ options.chain,
705
+ options.bodies,
706
+ replayContract(options, throughBlock),
707
+ {
708
+ batchSize: requirePositiveInteger(options.scanBatchSize, DEFAULT_SCAN_BATCH_SIZE, "scanBatchSize"),
709
+ ...prior.scan === null ? {} : { resumeFrom: prior.scan },
710
+ ...signal === void 0 ? {} : { signal },
711
+ strict: { rejectAmbiguousSchemaDeclarations: true },
712
+ through: throughBlock
713
+ }
714
+ );
715
+ signal?.throwIfAborted();
716
+ const throughHash = await exactBlockHash(options.chain, throughBlock, options.chainId);
717
+ signal?.throwIfAborted();
718
+ if (beforeHash !== throughHash) {
719
+ throw new Error(`finalized block root changed while staging through ${throughBlock}`);
720
+ }
721
+ if (result.head !== throughBlock || result.checkpoint.contract.throughBlock !== throughBlock) {
722
+ throw new RangeError(
723
+ `finalized range scan stopped at ${result.head}/${result.checkpoint.contract.throughBlock}; requested ${throughBlock}`
724
+ );
725
+ }
726
+ const applied = result.fold.applied.filter((record) => record.block >= fromBlock && record.block <= throughBlock && normalizeAddress3(record.subject) === options.publisher);
727
+ const appliedByBlock = Map.groupBy(applied, (record) => record.block);
728
+ const unresolvedByBlock = Map.groupBy(
729
+ result.unresolved.filter((event) => event.block >= fromBlock && event.block <= throughBlock),
730
+ (event) => event.block
731
+ );
732
+ const relevantBlocks = [.../* @__PURE__ */ new Set([...appliedByBlock.keys(), ...unresolvedByBlock.keys()])].toSorted((left, right) => left - right);
733
+ const bodies = /* @__PURE__ */ new Map();
734
+ for (const block of relevantBlocks) {
735
+ signal?.throwIfAborted();
736
+ const staged = await stageAppliedBlock(options, appliedByBlock.get(block) ?? []);
737
+ for (const [hash, body] of staged.bodies) bodies.set(hash, body);
738
+ const unresolvedEvents = unresolvedByBlock.get(block) ?? [];
739
+ if (unresolvedEvents.length > 0 || staged.objectHashes.length > 0 || staged.corrupt.length > 0) {
740
+ return {
741
+ applied,
742
+ blocked: {
743
+ atBlock: block,
744
+ corrupt: staged.corrupt,
745
+ objectHashes: staged.objectHashes,
746
+ unresolvedEvents
747
+ },
748
+ bodies,
749
+ scan: result.checkpoint,
750
+ throughHash
751
+ };
752
+ }
753
+ }
754
+ return {
755
+ applied,
756
+ bodies,
662
757
  scan: result.checkpoint,
663
- ...isBlocked ? {
664
- blocked: {
665
- atBlock: block,
666
- corrupt,
667
- objectHashes,
668
- unresolvedEvents: result.unresolved
669
- }
670
- } : {}
758
+ throughHash
671
759
  };
672
760
  }
673
761
  var DefaultFinalizedEventKitReplayController = class {
@@ -678,6 +766,9 @@ var DefaultFinalizedEventKitReplayController = class {
678
766
  this.#options = options;
679
767
  this.#checkpoint = checkpoint;
680
768
  }
769
+ anchor() {
770
+ return this.#checkpoint.indexedThrough === null || this.#checkpoint.indexedThroughHash === null ? null : Object.freeze({ block: this.#checkpoint.indexedThrough, hash: this.#checkpoint.indexedThroughHash });
771
+ }
681
772
  body(hash) {
682
773
  return this.#checkpoint.bodies.find((entry) => entry.hash === hash)?.body;
683
774
  }
@@ -691,8 +782,8 @@ var DefaultFinalizedEventKitReplayController = class {
691
782
  indexedThrough: this.#checkpoint.indexedThrough
692
783
  };
693
784
  }
694
- sync() {
695
- const result = this.#syncTail.then(async () => await this.#syncOnce());
785
+ sync(options) {
786
+ const result = this.#syncTail.then(async () => await this.#syncOnce(options));
696
787
  this.#syncTail = result.then(
697
788
  () => true,
698
789
  () => false
@@ -702,8 +793,55 @@ var DefaultFinalizedEventKitReplayController = class {
702
793
  viewer() {
703
794
  return createCheckpointStatementGraphViewer(this.#checkpoint);
704
795
  }
705
- async #syncOnce() {
796
+ async #commitRange(fromBlock, throughBlock, finalizedHead, signal) {
797
+ const staged = await stageRange(this.#options, this.#checkpoint, fromBlock, throughBlock, signal);
798
+ signal?.throwIfAborted();
799
+ if (staged.blocked !== void 0) {
800
+ const blockedAt = staged.blocked.atBlock;
801
+ if (blockedAt > fromBlock) {
802
+ const prefixBlocked = await this.#commitRange(fromBlock, blockedAt - 1, finalizedHead, signal);
803
+ if (prefixBlocked) return true;
804
+ }
805
+ const exact = await stageRange(this.#options, this.#checkpoint, blockedAt, blockedAt, signal);
806
+ signal?.throwIfAborted();
807
+ if (exact.blocked === void 0) {
808
+ await this.#commitStaged(exact, blockedAt, finalizedHead, signal);
809
+ return blockedAt === throughBlock ? false : await this.#commitRange(blockedAt + 1, throughBlock, finalizedHead, signal);
810
+ }
811
+ const blocked = EventKitReplayCheckpointZod.parse({
812
+ ...this.#checkpoint,
813
+ blocked: exact.blocked,
814
+ finalizedHead
815
+ });
816
+ signal?.throwIfAborted();
817
+ await this.#options.checkpointStore.save(blocked);
818
+ this.#checkpoint = blocked;
819
+ return true;
820
+ }
821
+ await this.#commitStaged(staged, throughBlock, finalizedHead, signal);
822
+ return false;
823
+ }
824
+ async #commitStaged(staged, throughBlock, finalizedHead, signal) {
825
+ const committed = EventKitReplayCheckpointZod.parse({
826
+ ...this.#checkpoint,
827
+ applied: mergeApplied(this.#checkpoint.applied, staged.applied),
828
+ bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
829
+ blocked: void 0,
830
+ finalizedHead,
831
+ indexedThrough: throughBlock,
832
+ indexedThroughHash: staged.throughHash,
833
+ scan: staged.scan
834
+ });
835
+ signal?.throwIfAborted();
836
+ await this.#options.checkpointStore.save(committed);
837
+ this.#checkpoint = committed;
838
+ }
839
+ async #syncOnce(rawOptions) {
840
+ const maxBlocks = requireMaxBlocks(rawOptions?.maxBlocks);
841
+ const signal = rawOptions?.signal;
842
+ signal?.throwIfAborted();
706
843
  const finalizedHead = requireHead(await this.#options.chain.finalization.headNumber());
844
+ signal?.throwIfAborted();
707
845
  if (finalizedHead < this.#checkpoint.finalizedHead) {
708
846
  throw new RangeError(
709
847
  `finalized head regressed from ${this.#checkpoint.finalizedHead} to ${finalizedHead}; refusing to roll back`
@@ -714,39 +852,26 @@ var DefaultFinalizedEventKitReplayController = class {
714
852
  throw new RangeError(`finalized head ${finalizedHead} is behind indexedThrough ${indexedThrough}`);
715
853
  }
716
854
  let next = indexedThrough === null ? this.#options.floorBlock : indexedThrough + 1;
717
- let changed = finalizedHead !== this.#checkpoint.finalizedHead;
718
- while (next <= finalizedHead) {
719
- const staged = await stageBlock(this.#options, this.#checkpoint, next);
720
- if (staged.blocked !== void 0) {
721
- const blocked = EventKitReplayCheckpointZod.parse({
722
- ...this.#checkpoint,
723
- blocked: staged.blocked,
724
- finalizedHead
725
- });
726
- await this.#options.checkpointStore.save(blocked);
727
- this.#checkpoint = blocked;
728
- return this.status();
729
- }
730
- const committed = EventKitReplayCheckpointZod.parse({
731
- ...this.#checkpoint,
732
- applied: mergeApplied(this.#checkpoint.applied, staged.applied),
733
- bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
734
- blocked: void 0,
735
- finalizedHead,
736
- indexedThrough: next,
737
- scan: staged.scan
738
- });
739
- await this.#options.checkpointStore.save(committed);
740
- this.#checkpoint = committed;
741
- changed = false;
742
- next += 1;
855
+ const remaining = next <= finalizedHead ? finalizedHead - next + 1 : 0;
856
+ const budget = maxBlocks === void 0 ? remaining : Math.min(maxBlocks, remaining);
857
+ const target = budget === 0 ? next - 1 : next + budget - 1;
858
+ const checkpointBatchSize = requirePositiveInteger(
859
+ this.#options.checkpointBatchSize,
860
+ DEFAULT_CHECKPOINT_BATCH_SIZE,
861
+ "checkpointBatchSize"
862
+ );
863
+ while (next <= target) {
864
+ signal?.throwIfAborted();
865
+ const throughBlock = Math.min(target, next + Math.min(checkpointBatchSize, target - next + 1) - 1);
866
+ if (await this.#commitRange(next, throughBlock, finalizedHead, signal)) return this.status();
867
+ next = throughBlock + 1;
743
868
  }
744
- if (changed || this.#checkpoint.blocked !== void 0) {
869
+ if (this.#checkpoint.finalizedHead !== finalizedHead) {
745
870
  const observed = EventKitReplayCheckpointZod.parse({
746
871
  ...this.#checkpoint,
747
- blocked: void 0,
748
872
  finalizedHead
749
873
  });
874
+ signal?.throwIfAborted();
750
875
  await this.#options.checkpointStore.save(observed);
751
876
  this.#checkpoint = observed;
752
877
  }
@@ -757,9 +882,15 @@ async function createFinalizedEventKitReplayController(rawOptions) {
757
882
  const options = {
758
883
  ...rawOptions,
759
884
  chainId: ChainIdZod2.parse(rawOptions.chainId),
885
+ checkpointBatchSize: requirePositiveInteger(
886
+ rawOptions.checkpointBatchSize,
887
+ DEFAULT_CHECKPOINT_BATCH_SIZE,
888
+ "checkpointBatchSize"
889
+ ),
760
890
  floorBlock: requireFloor(rawOptions.floorBlock),
761
891
  network: rawOptions.network.trim(),
762
892
  publisher: NormalizedAddressZod3.parse(rawOptions.publisher),
893
+ scanBatchSize: requirePositiveInteger(rawOptions.scanBatchSize, DEFAULT_SCAN_BATCH_SIZE, "scanBatchSize"),
763
894
  sourceId: rawOptions.sourceId.trim()
764
895
  };
765
896
  if (options.network.length === 0) throw new TypeError("network must not be empty");
@@ -874,7 +1005,7 @@ import {
874
1005
  PublisherServiceZod,
875
1006
  SOURCE_WAKE_EVENT_KIND as SOURCE_WAKE_EVENT_KIND2
876
1007
  } from "@xyo-network/event-kit-schemas";
877
- import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
1008
+ import { PayloadBuilder as PayloadBuilder3 } from "@xyo-network/sdk";
878
1009
  import { XyoAddressZod as XyoAddressZod2 } from "@xyo-network/sdk-protocol/address";
879
1010
  function handshakeEvidence(record) {
880
1011
  return {
@@ -914,12 +1045,12 @@ async function verifiedHandshakeObjectBody(viewer, record) {
914
1045
  const object = await viewer.object(record.objectHash);
915
1046
  if (object?.assurance !== "resolved" || object.declaredSchema !== record.objectSchema || object.params === null) return void 0;
916
1047
  const body = { schema: object.declaredSchema, ...object.params };
917
- return await PayloadBuilder2.dataHash(body) === record.objectHash ? body : void 0;
1048
+ return await PayloadBuilder3.dataHash(body) === record.objectHash ? body : void 0;
918
1049
  }
919
1050
  async function verifiedHandshakeAttachmentBody(resolver, hash) {
920
1051
  const body = resolver?.(hash);
921
1052
  if (body === void 0) return void 0;
922
- return await PayloadBuilder2.dataHash(body) === hash ? { ...body } : void 0;
1053
+ return await PayloadBuilder3.dataHash(body) === hash ? { ...body } : void 0;
923
1054
  }
924
1055
  function firstHandshakeRevocation(revocations, target) {
925
1056
  const blocks = revocations.filter((item) => item.target === target).map((item) => item.block);