@xyo-network/event-kit-statement-graph 0.0.1 → 0.1.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.
@@ -1,5 +1,712 @@
1
- // src/grantReadModel.ts
1
+ // src/checkpointViewer.ts
2
+ function eventKey(record) {
3
+ return `${record.txHash}:${record.eventHash}`;
4
+ }
5
+ function recordKey(record) {
6
+ return `${eventKey(record)}:${record.hashIndex}`;
7
+ }
8
+ function compareApplied(left, right) {
9
+ return left.block - right.block || left.blockEventOrdinal.tx - right.blockEventOrdinal.tx || left.blockEventOrdinal.event - right.blockEventOrdinal.event || left.hashIndex - right.hashIndex || recordKey(left).localeCompare(recordKey(right));
10
+ }
11
+ var LegacyHexAddress = /^[A-Fa-f0-9]{40}$/;
12
+ function normalizeAddress(value) {
13
+ return LegacyHexAddress.test(value) ? value.toLowerCase() : value;
14
+ }
15
+ function claimKey(source, subject, objectHash) {
16
+ return `${normalizeAddress(source)}|${normalizeAddress(subject)}|${objectHash}`;
17
+ }
18
+ function matchesClaim(record, query) {
19
+ return (query?.objectHash === void 0 || record.objectHash === query.objectHash) && (query?.subject === void 0 || normalizeAddress(record.subject) === normalizeAddress(query.subject)) && (query?.declaredObjectSchemaPrefix === void 0 || record.declaredObjectSchema.startsWith(query.declaredObjectSchemaPrefix));
20
+ }
21
+ function sortClaims(records) {
22
+ return records.toSorted((left, right) => claimKey(left.source, left.subject, left.objectHash).localeCompare(claimKey(right.source, right.subject, right.objectHash)));
23
+ }
24
+ function buildClaims(applied) {
25
+ const claims = /* @__PURE__ */ new Map();
26
+ for (const record of applied.toSorted(compareApplied)) {
27
+ const key = claimKey(record.source, record.subject, record.objectHash);
28
+ const prior = claims.get(key);
29
+ if (prior === void 0) {
30
+ claims.set(key, {
31
+ declaredObjectSchema: record.objectSchema,
32
+ firstBlock: record.block,
33
+ firstEventHash: record.eventHash,
34
+ lastBlock: record.block,
35
+ objectHash: record.objectHash,
36
+ source: record.source,
37
+ subject: record.subject
38
+ });
39
+ } else if (record.block > prior.lastBlock) {
40
+ claims.set(key, { ...prior, lastBlock: record.block });
41
+ }
42
+ }
43
+ return sortClaims([...claims.values()]);
44
+ }
45
+ function withoutSchema(body) {
46
+ return Object.fromEntries(Object.entries(body).filter(([key]) => key !== "schema"));
47
+ }
48
+ function createCheckpointStatementGraphViewer(checkpoint) {
49
+ const applied = checkpoint.applied.toSorted(compareApplied);
50
+ const claims = buildClaims(applied);
51
+ const bodies = new Map(checkpoint.bodies.map((entry) => [entry.hash, entry.body]));
52
+ const declaredSchemas = /* @__PURE__ */ new Map();
53
+ for (const record of applied) {
54
+ if (!declaredSchemas.has(record.objectHash)) declaredSchemas.set(record.objectHash, record.objectSchema);
55
+ }
56
+ const unresolved = checkpoint.blocked?.unresolvedEvents ?? [];
57
+ return Object.freeze({
58
+ async claims(source, query) {
59
+ const normalized = normalizeAddress(source);
60
+ return claims.filter((record) => normalizeAddress(record.source) === normalized && matchesClaim(record, query));
61
+ },
62
+ async claimsByObject(objectHash) {
63
+ return claims.filter((record) => record.objectHash === objectHash);
64
+ },
65
+ async claimsBySubject(subject, query) {
66
+ const normalized = normalizeAddress(subject);
67
+ return claims.filter((record) => normalizeAddress(record.subject) === normalized && matchesClaim(record, query));
68
+ },
69
+ async event(txHash, eventHash) {
70
+ return applied.filter((record) => record.txHash === txHash && record.eventHash === eventHash);
71
+ },
72
+ async events(source, query) {
73
+ const normalized = normalizeAddress(source);
74
+ return applied.filter((record) => normalizeAddress(record.source) === normalized && (query?.fromBlock === void 0 || record.block >= query.fromBlock) && (query?.toBlock === void 0 || record.block <= query.toBlock) && (query?.objectSchemaPrefix === void 0 || record.objectSchema.startsWith(query.objectSchemaPrefix)) && (query?.subject === void 0 || normalizeAddress(record.subject) === normalizeAddress(query.subject)));
75
+ },
76
+ async head() {
77
+ if (checkpoint.indexedThrough === null) throw new Error("Event Kit replay has not indexed its floor block");
78
+ return checkpoint.indexedThrough;
79
+ },
80
+ async object(objectHash) {
81
+ const body = bodies.get(objectHash);
82
+ const declaredSchema = declaredSchemas.get(objectHash);
83
+ if (body === void 0 || declaredSchema === void 0) return void 0;
84
+ return {
85
+ assurance: "resolved",
86
+ declaredSchema,
87
+ objectHash,
88
+ params: withoutSchema(body)
89
+ };
90
+ },
91
+ async source(source) {
92
+ const normalized = normalizeAddress(source);
93
+ const records = applied.filter((record) => normalizeAddress(record.source) === normalized);
94
+ const blocks = records.map((record) => record.block);
95
+ return {
96
+ claims: new Set(records.map((record) => claimKey(record.source, record.subject, record.objectHash))).size,
97
+ firstBlock: blocks.length === 0 ? null : Math.min(...blocks),
98
+ lastBlock: blocks.length === 0 ? null : Math.max(...blocks),
99
+ source: normalized
100
+ };
101
+ },
102
+ async unresolved(txHash, eventHash) {
103
+ return unresolved.find((record) => record.txHash === txHash && record.eventHash === eventHash);
104
+ }
105
+ });
106
+ }
107
+
108
+ // src/eventKitSchemaPolicy.ts
109
+ import {
110
+ EncryptedWakeSubscriptionSchema,
111
+ PublisherEncryptionKeyRevocationSchema,
112
+ PublisherEncryptionKeySchema,
113
+ PublisherServiceRevocationSchema,
114
+ PublisherServiceSchema,
115
+ WakeGrantObjectSchema,
116
+ WakeGrantRevocationSchema,
117
+ WakeSubscriptionAcceptanceRevocationSchema,
118
+ WakeSubscriptionAcceptanceSchema,
119
+ WakeSubscriptionRevocationSchema
120
+ } from "@xyo-network/event-kit-schemas";
121
+ var EventKitClaimedObjectSchemas = Object.freeze([
122
+ PublisherServiceSchema,
123
+ PublisherEncryptionKeySchema,
124
+ PublisherServiceRevocationSchema,
125
+ PublisherEncryptionKeyRevocationSchema,
126
+ EncryptedWakeSubscriptionSchema,
127
+ WakeGrantObjectSchema,
128
+ WakeSubscriptionRevocationSchema,
129
+ WakeGrantRevocationSchema,
130
+ WakeSubscriptionAcceptanceSchema,
131
+ WakeSubscriptionAcceptanceRevocationSchema
132
+ ]);
133
+ var EventKitClaimedObjectSchemaSet = new Set(EventKitClaimedObjectSchemas);
134
+
135
+ // src/finalizedReplayController.ts
136
+ import { HashZod as HashZod2 } from "@ariestools/sdk";
137
+ import {
138
+ ChainIdZod as ChainIdZod2,
139
+ normalizeAddress as normalizeAddress3,
140
+ NormalizedAddressZod as NormalizedAddressZod2,
141
+ WakeSubscriptionAcceptanceSchema as WakeSubscriptionAcceptanceSchema2,
142
+ WakeSubscriptionAcceptanceZod
143
+ } from "@xyo-network/event-kit-schemas";
144
+ import { STATEMENT_FOLD_VERSION as STATEMENT_FOLD_VERSION2 } from "@xyo-network/xl1-statement-graph-schemas";
145
+ import {
146
+ resolveVerifiedBodies,
147
+ scanFinalizedStatements
148
+ } from "@xyo-network/xl1-statement-graph-sdk";
149
+
150
+ // src/replayCheckpoint.ts
2
151
  import { HashZod } from "@ariestools/sdk";
152
+ import { ChainIdZod, NormalizedAddressZod } from "@xyo-network/event-kit-schemas";
153
+ import { PayloadBuilder } from "@xyo-network/sdk";
154
+ import {
155
+ AppliedEventRecordZod,
156
+ BlockNumberZod,
157
+ STATEMENT_FOLD_VERSION,
158
+ UnresolvedEventReferenceZod
159
+ } from "@xyo-network/xl1-statement-graph-schemas";
160
+ import {
161
+ assembleCheckpointRecords,
162
+ StatementScanCheckpointZod
163
+ } from "@xyo-network/xl1-statement-graph-sdk";
164
+ import { z } from "zod";
165
+ var EVENT_KIT_REPLAY_CHECKPOINT_VERSION = 2;
166
+ var ResolvedReplayBodyZod = z.strictObject({
167
+ body: z.record(z.string(), z.unknown()),
168
+ hash: HashZod
169
+ });
170
+ var ReplayCorruptBodyZod = z.strictObject({
171
+ claimedHash: z.string().optional(),
172
+ computedHash: z.string()
173
+ });
174
+ var ReplayBlockedStateZod = z.strictObject({
175
+ atBlock: BlockNumberZod,
176
+ corrupt: z.array(ReplayCorruptBodyZod),
177
+ objectHashes: z.array(HashZod),
178
+ unresolvedEvents: z.array(UnresolvedEventReferenceZod)
179
+ });
180
+ var EventKitReplayCheckpointZod = z.strictObject({
181
+ applied: z.array(AppliedEventRecordZod),
182
+ blocked: ReplayBlockedStateZod.optional(),
183
+ bodies: z.array(ResolvedReplayBodyZod),
184
+ chainId: ChainIdZod,
185
+ finalizedHead: BlockNumberZod,
186
+ floorBlock: BlockNumberZod,
187
+ foldVersion: z.literal(STATEMENT_FOLD_VERSION),
188
+ indexedThrough: z.union([BlockNumberZod, z.null()]),
189
+ network: z.string().min(1),
190
+ objectSchemas: z.tuple(EventKitClaimedObjectSchemas.map((schema) => z.literal(schema))),
191
+ publisher: NormalizedAddressZod,
192
+ scan: z.union([StatementScanCheckpointZod, z.null()]),
193
+ sourceId: z.string().min(1),
194
+ version: z.literal(EVENT_KIT_REPLAY_CHECKPOINT_VERSION)
195
+ });
196
+ function sameSchemas(actual) {
197
+ return actual.length === EventKitClaimedObjectSchemas.length && actual.every((schema, index) => schema === EventKitClaimedObjectSchemas[index]);
198
+ }
199
+ function appliedKey(record) {
200
+ return `${record.txHash}:${record.eventHash}:${record.hashIndex}`;
201
+ }
202
+ function compareApplied2(left, right) {
203
+ return left.block - right.block || left.blockEventOrdinal.tx - right.blockEventOrdinal.tx || left.blockEventOrdinal.event - right.blockEventOrdinal.event || left.hashIndex - right.hashIndex || appliedKey(left).localeCompare(appliedKey(right));
204
+ }
205
+ var LegacyHexAddress2 = /^[A-Fa-f0-9]{40}$/;
206
+ function normalizeAddress2(value) {
207
+ return LegacyHexAddress2.test(value) ? value.toLowerCase() : value;
208
+ }
209
+ function claimKey2(source, subject, objectHash) {
210
+ return `${normalizeAddress2(source)}|${normalizeAddress2(subject)}|${objectHash}`;
211
+ }
212
+ function eventKey2(record) {
213
+ return `${record.txHash}:${record.eventHash}`;
214
+ }
215
+ function sortClaimRecords(records) {
216
+ return records.toSorted((left, right) => claimKey2(left.source, left.subject, left.objectHash).localeCompare(claimKey2(right.source, right.subject, right.objectHash)));
217
+ }
218
+ function expectedClaimRecords(applied) {
219
+ const records = /* @__PURE__ */ new Map();
220
+ for (const event of applied.toSorted(compareApplied2)) {
221
+ const key = claimKey2(event.source, event.subject, event.objectHash);
222
+ const prior = records.get(key);
223
+ if (prior === void 0) {
224
+ records.set(key, {
225
+ declaredObjectSchema: event.objectSchema,
226
+ firstBlock: event.block,
227
+ firstEventHash: event.eventHash,
228
+ lastBlock: event.block,
229
+ objectHash: event.objectHash,
230
+ source: event.source,
231
+ subject: event.subject
232
+ });
233
+ } else if (event.block > prior.lastBlock) {
234
+ records.set(key, { ...prior, lastBlock: event.block });
235
+ }
236
+ }
237
+ return sortClaimRecords([...records.values()]);
238
+ }
239
+ function sameClaimRecords(left, right) {
240
+ return left.length === right.length && left.every((record, index) => {
241
+ const candidate = right[index];
242
+ if (candidate === void 0) return false;
243
+ return record.declaredObjectSchema === candidate.declaredObjectSchema && record.firstBlock === candidate.firstBlock && record.firstEventHash === candidate.firstEventHash && record.lastBlock === candidate.lastBlock && record.objectHash === candidate.objectHash && normalizeAddress2(record.source) === normalizeAddress2(candidate.source) && normalizeAddress2(record.subject) === normalizeAddress2(candidate.subject);
244
+ });
245
+ }
246
+ function progressIntegrityError(checkpoint) {
247
+ const { indexedThrough } = checkpoint;
248
+ if (indexedThrough !== null && indexedThrough > checkpoint.finalizedHead) {
249
+ return `indexedThrough ${indexedThrough} exceeds finalizedHead ${checkpoint.finalizedHead}`;
250
+ }
251
+ if (indexedThrough !== null && indexedThrough < checkpoint.floorBlock) {
252
+ return `indexedThrough ${indexedThrough} precedes floorBlock ${checkpoint.floorBlock}`;
253
+ }
254
+ if (indexedThrough === null && checkpoint.scan !== null) {
255
+ return "scan checkpoint exists before floorBlock has been indexed";
256
+ }
257
+ if (indexedThrough !== null && checkpoint.scan === null) {
258
+ return "scan checkpoint is missing for indexed state";
259
+ }
260
+ if (indexedThrough === null && checkpoint.applied.length > 0) {
261
+ return "applied events exist before floorBlock has been indexed";
262
+ }
263
+ if (indexedThrough === null && checkpoint.bodies.length > 0) {
264
+ return "resolved bodies exist before floorBlock has been indexed";
265
+ }
266
+ return void 0;
267
+ }
268
+ function bodySetIntegrityError(checkpoint) {
269
+ const hashes = /* @__PURE__ */ new Set();
270
+ for (const entry of checkpoint.bodies) {
271
+ if (hashes.has(entry.hash)) return `duplicate persisted body ${entry.hash}`;
272
+ if (typeof entry.body.schema !== "string" || entry.body.schema.length === 0) {
273
+ return `persisted body ${entry.hash} has no schema`;
274
+ }
275
+ hashes.add(entry.hash);
276
+ }
277
+ return void 0;
278
+ }
279
+ function appliedIntegrityError(checkpoint) {
280
+ const { indexedThrough } = checkpoint;
281
+ const bodyByHash = new Map(checkpoint.bodies.map((entry) => [entry.hash, entry]));
282
+ const appliedIdentities = /* @__PURE__ */ new Set();
283
+ const eventObjects = /* @__PURE__ */ new Set();
284
+ const eventLocations = /* @__PURE__ */ new Map();
285
+ let prior;
286
+ for (const event of checkpoint.applied) {
287
+ const key = appliedKey(event);
288
+ if (normalizeAddress2(event.subject) !== normalizeAddress2(checkpoint.publisher)) {
289
+ return `applied event ${key} subject ${event.subject} != publisher ${checkpoint.publisher}`;
290
+ }
291
+ if (appliedIdentities.has(key)) return `duplicate applied-event identity ${key}`;
292
+ appliedIdentities.add(key);
293
+ const objectKey = `${event.txHash}:${event.eventHash}:${event.objectHash}`;
294
+ if (eventObjects.has(objectKey)) return `duplicate applied-event object ${objectKey}`;
295
+ eventObjects.add(objectKey);
296
+ if (prior !== void 0 && compareApplied2(prior, event) > 0) return "applied events are not in canonical order";
297
+ prior = event;
298
+ if (indexedThrough === null || event.block < checkpoint.floorBlock || event.block > indexedThrough) {
299
+ return `applied event ${key} lies outside the indexed block range`;
300
+ }
301
+ const eventKey3 = `${event.txHash}:${event.eventHash}`;
302
+ const location = [
303
+ event.block,
304
+ event.blockEventOrdinal.tx,
305
+ event.blockEventOrdinal.event,
306
+ normalizeAddress2(event.source),
307
+ normalizeAddress2(event.subject)
308
+ ].join(":");
309
+ const priorLocation = eventLocations.get(eventKey3);
310
+ if (priorLocation !== void 0 && priorLocation !== location) {
311
+ return `applied event ${eventKey3} has inconsistent provenance`;
312
+ }
313
+ eventLocations.set(eventKey3, location);
314
+ const body = bodyByHash.get(event.objectHash)?.body;
315
+ if (body === void 0) return `applied object body ${event.objectHash} is missing`;
316
+ if (body.schema !== event.objectSchema) {
317
+ return `applied object ${event.objectHash} schema ${String(body.schema)} != ${event.objectSchema}`;
318
+ }
319
+ }
320
+ return void 0;
321
+ }
322
+ function scanRecordIntegrityError(records, checkpoint, indexedThrough) {
323
+ const indexedSchemas = new Set(checkpoint.objectSchemas);
324
+ for (const record of records) {
325
+ const key = claimKey2(record.source, record.subject, record.objectHash);
326
+ if (record.firstBlock < checkpoint.floorBlock || record.lastBlock > indexedThrough) {
327
+ return `scan claim record ${key} lies outside the indexed block range`;
328
+ }
329
+ if (record.lastBlock < record.firstBlock) return `scan claim record ${key} has a regressing clock`;
330
+ if (!indexedSchemas.has(record.declaredObjectSchema)) {
331
+ return `scan claim record ${key} has an unindexed declared schema`;
332
+ }
333
+ }
334
+ return void 0;
335
+ }
336
+ function scanIntegrityError(checkpoint) {
337
+ const { indexedThrough, scan } = checkpoint;
338
+ if (scan === null || indexedThrough === null) return void 0;
339
+ const contract = scan.contract;
340
+ if (contract.throughBlock !== indexedThrough) {
341
+ return `scan throughBlock ${contract.throughBlock} != indexedThrough ${indexedThrough}`;
342
+ }
343
+ if (contract.floorBlock !== checkpoint.floorBlock) return "scan floorBlock differs from checkpoint";
344
+ if (contract.foldVersion !== checkpoint.foldVersion) return "scan foldVersion differs from checkpoint";
345
+ if (contract.network !== checkpoint.network) return "scan network differs from checkpoint";
346
+ if (contract.eventBodyCorpus.sourceId !== checkpoint.sourceId) return "scan body source differs from checkpoint";
347
+ if (contract.eventBodyCorpus.unresolvedDigest !== void 0) {
348
+ return "scan body corpus unresolved digest is invalid for finalized replay";
349
+ }
350
+ if (JSON.stringify(contract.indexedSchemaPolicy) !== JSON.stringify({ foldedObjectSchemas: checkpoint.objectSchemas })) {
351
+ return "scan indexed schema policy differs from checkpoint";
352
+ }
353
+ if (scan.unresolved.length > 0) return "committed scan checkpoint contains unresolved events";
354
+ if (scan.recordShards.length > 0) return "Event Kit scan checkpoint is not self-contained";
355
+ const assembled = assembleCheckpointRecords(scan, []);
356
+ if (assembled.status !== "valid") return `scan claim records do not assemble: ${assembled.detail}`;
357
+ const recordIntegrityError = scanRecordIntegrityError(assembled.records, checkpoint, indexedThrough);
358
+ if (recordIntegrityError !== void 0) return recordIntegrityError;
359
+ const actualRecords = sortClaimRecords(
360
+ assembled.records.filter((record) => normalizeAddress2(record.subject) === normalizeAddress2(checkpoint.publisher))
361
+ );
362
+ if (!sameClaimRecords(actualRecords, expectedClaimRecords(checkpoint.applied))) {
363
+ return "scan claim records differ from applied-event history";
364
+ }
365
+ if (scan.watermark !== void 0 && scan.watermark.block > indexedThrough) {
366
+ return `scan watermark block ${scan.watermark.block} exceeds indexedThrough ${indexedThrough}`;
367
+ }
368
+ return watermarkIntegrityError(checkpoint);
369
+ }
370
+ function watermarkIntegrityError(checkpoint) {
371
+ const scan = checkpoint.scan;
372
+ if (scan === null) return void 0;
373
+ const { watermark, watermarkGroupEventKeys } = scan;
374
+ if (watermark === void 0) {
375
+ if (checkpoint.applied.length > 0) return "scan watermark is missing for applied-event history";
376
+ return watermarkGroupEventKeys.length === 0 ? void 0 : "scan has group event keys without a watermark";
377
+ }
378
+ if (watermark.block < checkpoint.floorBlock) {
379
+ return `scan watermark block ${watermark.block} precedes floorBlock ${checkpoint.floorBlock}`;
380
+ }
381
+ const aboveWatermark = checkpoint.applied.find((event) => event.block > watermark.block || event.block === watermark.block && event.blockEventOrdinal.tx > watermark.ordinal.tx || event.block === watermark.block && event.blockEventOrdinal.tx === watermark.ordinal.tx && event.blockEventOrdinal.event > watermark.ordinal.event);
382
+ if (aboveWatermark !== void 0) return "applied event lies above the scan watermark";
383
+ const actualKeys = [...new Set(watermarkGroupEventKeys)].toSorted();
384
+ if (actualKeys.length !== watermarkGroupEventKeys.length) {
385
+ return "scan watermark group event keys contain duplicates";
386
+ }
387
+ if (actualKeys.some((key) => !/^[0-9a-f]{64}:[0-9a-f]{64}$/u.test(key))) {
388
+ return "scan watermark group contains a malformed event key";
389
+ }
390
+ const group = checkpoint.applied.filter((event) => event.block === watermark.block && event.blockEventOrdinal.tx === watermark.ordinal.tx);
391
+ const expectedKeys = [...new Set(group.map(eventKey2))].toSorted();
392
+ if (expectedKeys.some((key) => !actualKeys.includes(key))) {
393
+ return "scan watermark group event keys omit publisher applied-event history";
394
+ }
395
+ return void 0;
396
+ }
397
+ function blockedIntegrityError(checkpoint) {
398
+ const { blocked, indexedThrough } = checkpoint;
399
+ if (blocked === void 0) return void 0;
400
+ const expectedBlock = indexedThrough === null ? checkpoint.floorBlock : indexedThrough + 1;
401
+ if (blocked.atBlock !== expectedBlock) {
402
+ return `blocked atBlock ${blocked.atBlock} != next unindexed block ${expectedBlock}`;
403
+ }
404
+ if (blocked.atBlock > checkpoint.finalizedHead) {
405
+ return `blocked atBlock ${blocked.atBlock} exceeds finalizedHead ${checkpoint.finalizedHead}`;
406
+ }
407
+ if (blocked.unresolvedEvents.some((event) => event.block !== blocked.atBlock)) {
408
+ return "blocked unresolved event does not belong to blocked block";
409
+ }
410
+ return void 0;
411
+ }
412
+ function checkpointIntegrityError(checkpoint) {
413
+ return progressIntegrityError(checkpoint) ?? bodySetIntegrityError(checkpoint) ?? appliedIntegrityError(checkpoint) ?? scanIntegrityError(checkpoint) ?? blockedIntegrityError(checkpoint);
414
+ }
415
+ async function persistedBodyIntegrityError(checkpoint) {
416
+ for (const entry of checkpoint.bodies) {
417
+ let computedHash;
418
+ try {
419
+ computedHash = await PayloadBuilder.dataHash(entry.body);
420
+ } catch {
421
+ return `persisted body ${entry.hash} cannot be hashed`;
422
+ }
423
+ if (computedHash !== entry.hash) {
424
+ return `persisted body ${entry.hash} hashes to ${computedHash}`;
425
+ }
426
+ }
427
+ return void 0;
428
+ }
429
+ async function loadEventKitReplayCheckpoint(value, identity) {
430
+ if (value === void 0 || value === null) return { status: "missing" };
431
+ const parsed = EventKitReplayCheckpointZod.safeParse(value);
432
+ if (!parsed.success) {
433
+ return { detail: "recognized Event Kit replay checkpoint has invalid content", status: "corrupt" };
434
+ }
435
+ const checkpoint = parsed.data;
436
+ if (checkpoint.chainId !== identity.chainId) {
437
+ return { detail: `chainId ${checkpoint.chainId} != ${identity.chainId}`, status: "identity-mismatch" };
438
+ }
439
+ if (checkpoint.floorBlock !== identity.floorBlock) {
440
+ return { detail: `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`, status: "identity-mismatch" };
441
+ }
442
+ if (checkpoint.network !== identity.network) {
443
+ return { detail: `network ${checkpoint.network} != ${identity.network}`, status: "identity-mismatch" };
444
+ }
445
+ if (checkpoint.sourceId !== identity.sourceId) {
446
+ return { detail: `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`, status: "identity-mismatch" };
447
+ }
448
+ if (checkpoint.publisher !== identity.publisher) {
449
+ return { detail: `publisher ${checkpoint.publisher} != ${identity.publisher}`, status: "identity-mismatch" };
450
+ }
451
+ if (!sameSchemas(checkpoint.objectSchemas)) {
452
+ return { detail: "Event Kit indexed schema policy differs", status: "identity-mismatch" };
453
+ }
454
+ const bodyIntegrityError = await persistedBodyIntegrityError(checkpoint);
455
+ if (bodyIntegrityError !== void 0) return { detail: bodyIntegrityError, status: "corrupt" };
456
+ const integrityError = checkpointIntegrityError(checkpoint);
457
+ if (integrityError !== void 0) return { detail: integrityError, status: "corrupt" };
458
+ return { checkpoint, status: "valid" };
459
+ }
460
+
461
+ // src/finalizedReplayController.ts
462
+ function requireHead(value) {
463
+ const head = Number(value);
464
+ if (!Number.isSafeInteger(head) || head < 0) {
465
+ throw new TypeError(`finalized head must be a nonnegative safe integer, received ${String(value)}`);
466
+ }
467
+ return head;
468
+ }
469
+ function requireFloor(value) {
470
+ if (!Number.isSafeInteger(value) || value < 0) {
471
+ throw new TypeError(`floorBlock must be a nonnegative safe integer, received ${String(value)}`);
472
+ }
473
+ return value;
474
+ }
475
+ function replayContract(options, throughBlock) {
476
+ return {
477
+ eventBodyCorpus: { sourceId: options.sourceId },
478
+ floorBlock: options.floorBlock,
479
+ foldVersion: STATEMENT_FOLD_VERSION2,
480
+ indexedSchemaPolicy: { foldedObjectSchemas: [...EventKitClaimedObjectSchemas] },
481
+ network: options.network,
482
+ throughBlock
483
+ };
484
+ }
485
+ function initialCheckpoint(options) {
486
+ return EventKitReplayCheckpointZod.parse({
487
+ applied: [],
488
+ bodies: [],
489
+ chainId: options.chainId,
490
+ finalizedHead: 0,
491
+ floorBlock: options.floorBlock,
492
+ foldVersion: STATEMENT_FOLD_VERSION2,
493
+ indexedThrough: null,
494
+ network: options.network,
495
+ objectSchemas: [...EventKitClaimedObjectSchemas],
496
+ publisher: options.publisher,
497
+ scan: null,
498
+ sourceId: options.sourceId,
499
+ version: EVENT_KIT_REPLAY_CHECKPOINT_VERSION
500
+ });
501
+ }
502
+ function corruptKey(record) {
503
+ return `${record.claimedHash ?? ""}:${record.computedHash}`;
504
+ }
505
+ function uniqueCorrupt(records) {
506
+ return [...new Map(records.map((record) => [corruptKey(record), record])).values()];
507
+ }
508
+ function uniqueStrings(values) {
509
+ return [...new Set(values)];
510
+ }
511
+ function appliedKey2(record) {
512
+ return `${record.txHash}:${record.eventHash}:${record.hashIndex}`;
513
+ }
514
+ function mergeApplied(existing, additions) {
515
+ const merged = new Map(existing.map((record) => [appliedKey2(record), record]));
516
+ for (const record of additions) {
517
+ const key = appliedKey2(record);
518
+ const prior = merged.get(key);
519
+ if (prior !== void 0 && JSON.stringify(prior) !== JSON.stringify(record)) {
520
+ throw new Error(`applied-event identity collision at ${key}`);
521
+ }
522
+ merged.set(key, record);
523
+ }
524
+ return [...merged.values()].toSorted((left, right) => left.block - right.block || left.blockEventOrdinal.tx - right.blockEventOrdinal.tx || left.blockEventOrdinal.event - right.blockEventOrdinal.event || left.hashIndex - right.hashIndex);
525
+ }
526
+ function mergeBodies(existing, additions) {
527
+ const merged = new Map(existing.map((entry) => [entry.hash, entry.body]));
528
+ for (const [hash, body] of additions) {
529
+ const prior = merged.get(hash);
530
+ if (prior !== void 0 && JSON.stringify(prior) !== JSON.stringify(body)) {
531
+ throw new Error(`content-addressed body collision for ${hash}`);
532
+ }
533
+ merged.set(hash, body);
534
+ }
535
+ return [...merged].map(([hash, body]) => ({ body, hash: HashZod2.parse(hash) })).toSorted((left, right) => left.hash.localeCompare(right.hash));
536
+ }
537
+ async function stageBlock(options, prior, block) {
538
+ const result = await scanFinalizedStatements(
539
+ options.chain,
540
+ options.bodies,
541
+ replayContract(options, block),
542
+ {
543
+ batchSize: 1,
544
+ ...prior.scan === null ? {} : { resumeFrom: prior.scan },
545
+ strict: { rejectAmbiguousSchemaDeclarations: true },
546
+ through: block
547
+ }
548
+ );
549
+ const applied = result.fold.applied.filter(
550
+ (record) => normalizeAddress3(record.subject) === options.publisher
551
+ );
552
+ const appliedObjectHashes = uniqueStrings(applied.map((record) => record.objectHash));
553
+ const objects = await resolveVerifiedBodies(options.bodies, appliedObjectHashes);
554
+ const missingObjects = appliedObjectHashes.filter((hash) => !objects.bodies.has(hash));
555
+ const schemaMismatchedObjects = uniqueStrings(applied.flatMap((record) => {
556
+ const body = objects.bodies.get(record.objectHash);
557
+ return body !== void 0 && body.schema !== record.objectSchema ? [record.objectHash] : [];
558
+ }));
559
+ const acceptanceAttachments = [];
560
+ for (const record of applied) {
561
+ if (normalizeAddress3(record.source) !== options.publisher || record.objectSchema !== WakeSubscriptionAcceptanceSchema2) continue;
562
+ const acceptance = WakeSubscriptionAcceptanceZod.safeParse(objects.bodies.get(record.objectHash));
563
+ if (acceptance.success) acceptanceAttachments.push(acceptance.data.challenge, acceptance.data.response);
564
+ }
565
+ const attachmentHashes = uniqueStrings(acceptanceAttachments);
566
+ const attachments = await resolveVerifiedBodies(options.bodies, attachmentHashes);
567
+ const missingAttachments = attachmentHashes.filter((hash) => !attachments.bodies.has(hash));
568
+ const corrupt = uniqueCorrupt([
569
+ ...objects.corrupt,
570
+ ...attachments.corrupt
571
+ ]);
572
+ const objectHashes = uniqueStrings([
573
+ ...missingObjects,
574
+ ...schemaMismatchedObjects,
575
+ ...missingAttachments
576
+ ]).map((hash) => HashZod2.parse(hash));
577
+ const isBlocked = result.unresolved.length > 0 || objectHashes.length > 0 || corrupt.length > 0;
578
+ return {
579
+ applied,
580
+ bodies: new Map([...objects.bodies, ...attachments.bodies]),
581
+ scan: result.checkpoint,
582
+ ...isBlocked ? {
583
+ blocked: {
584
+ atBlock: block,
585
+ corrupt,
586
+ objectHashes,
587
+ unresolvedEvents: result.unresolved
588
+ }
589
+ } : {}
590
+ };
591
+ }
592
+ var DefaultFinalizedEventKitReplayController = class {
593
+ #checkpoint;
594
+ #options;
595
+ #syncTail = Promise.resolve();
596
+ constructor(options, checkpoint) {
597
+ this.#options = options;
598
+ this.#checkpoint = checkpoint;
599
+ }
600
+ body(hash) {
601
+ return this.#checkpoint.bodies.find((entry) => entry.hash === hash)?.body;
602
+ }
603
+ snapshot() {
604
+ return this.#checkpoint;
605
+ }
606
+ status() {
607
+ return {
608
+ ...this.#checkpoint.blocked === void 0 ? {} : { blocked: this.#checkpoint.blocked },
609
+ finalizedHead: this.#checkpoint.finalizedHead,
610
+ indexedThrough: this.#checkpoint.indexedThrough
611
+ };
612
+ }
613
+ sync() {
614
+ const result = this.#syncTail.then(async () => await this.#syncOnce());
615
+ this.#syncTail = result.then(
616
+ () => true,
617
+ () => false
618
+ );
619
+ return result;
620
+ }
621
+ viewer() {
622
+ return createCheckpointStatementGraphViewer(this.#checkpoint);
623
+ }
624
+ async #syncOnce() {
625
+ const finalizedHead = requireHead(await this.#options.chain.finalization.headNumber());
626
+ if (finalizedHead < this.#checkpoint.finalizedHead) {
627
+ throw new RangeError(
628
+ `finalized head regressed from ${this.#checkpoint.finalizedHead} to ${finalizedHead}; refusing to roll back`
629
+ );
630
+ }
631
+ const indexedThrough = this.#checkpoint.indexedThrough;
632
+ if (indexedThrough !== null && finalizedHead < indexedThrough) {
633
+ throw new RangeError(`finalized head ${finalizedHead} is behind indexedThrough ${indexedThrough}`);
634
+ }
635
+ let next = indexedThrough === null ? this.#options.floorBlock : indexedThrough + 1;
636
+ let changed = finalizedHead !== this.#checkpoint.finalizedHead;
637
+ while (next <= finalizedHead) {
638
+ const staged = await stageBlock(this.#options, this.#checkpoint, next);
639
+ if (staged.blocked !== void 0) {
640
+ const blocked = EventKitReplayCheckpointZod.parse({
641
+ ...this.#checkpoint,
642
+ blocked: staged.blocked,
643
+ finalizedHead
644
+ });
645
+ await this.#options.checkpointStore.save(blocked);
646
+ this.#checkpoint = blocked;
647
+ return this.status();
648
+ }
649
+ const committed = EventKitReplayCheckpointZod.parse({
650
+ ...this.#checkpoint,
651
+ applied: mergeApplied(this.#checkpoint.applied, staged.applied),
652
+ bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
653
+ blocked: void 0,
654
+ finalizedHead,
655
+ indexedThrough: next,
656
+ scan: staged.scan
657
+ });
658
+ await this.#options.checkpointStore.save(committed);
659
+ this.#checkpoint = committed;
660
+ changed = false;
661
+ next += 1;
662
+ }
663
+ if (changed || this.#checkpoint.blocked !== void 0) {
664
+ const observed = EventKitReplayCheckpointZod.parse({
665
+ ...this.#checkpoint,
666
+ blocked: void 0,
667
+ finalizedHead
668
+ });
669
+ await this.#options.checkpointStore.save(observed);
670
+ this.#checkpoint = observed;
671
+ }
672
+ return this.status();
673
+ }
674
+ };
675
+ async function createFinalizedEventKitReplayController(rawOptions) {
676
+ const options = {
677
+ ...rawOptions,
678
+ chainId: ChainIdZod2.parse(rawOptions.chainId),
679
+ floorBlock: requireFloor(rawOptions.floorBlock),
680
+ network: rawOptions.network.trim(),
681
+ publisher: NormalizedAddressZod2.parse(rawOptions.publisher),
682
+ sourceId: rawOptions.sourceId.trim()
683
+ };
684
+ if (options.network.length === 0) throw new TypeError("network must not be empty");
685
+ if (options.sourceId.length === 0) throw new TypeError("sourceId must not be empty");
686
+ const loaded = await loadEventKitReplayCheckpoint(await options.checkpointStore.load(), options);
687
+ if (loaded.status === "corrupt" || loaded.status === "identity-mismatch") {
688
+ throw new Error(`cannot load Event Kit replay checkpoint: ${loaded.detail}`);
689
+ }
690
+ const checkpoint = loaded.status === "valid" ? loaded.checkpoint : initialCheckpoint(options);
691
+ return new DefaultFinalizedEventKitReplayController(options, checkpoint);
692
+ }
693
+ function createMemoryEventKitReplayCheckpointStore(initial) {
694
+ let value = initial;
695
+ const saved = [];
696
+ return {
697
+ async load() {
698
+ return value;
699
+ },
700
+ async save(checkpoint) {
701
+ value = checkpoint;
702
+ saved.push(checkpoint);
703
+ },
704
+ saved
705
+ };
706
+ }
707
+
708
+ // src/grantReadModel.ts
709
+ import { HashZod as HashZod3 } from "@ariestools/sdk";
3
710
  import { XyoAddressZod } from "@xyo-network/sdk-protocol/address";
4
711
  function createStatementGraphGrantReadModel(options) {
5
712
  const { viewer } = options;
@@ -12,7 +719,7 @@ function createStatementGraphGrantReadModel(options) {
12
719
  return Object.freeze(records.map(toGrantClaim));
13
720
  },
14
721
  async object(objectHash) {
15
- const record = await viewer.object(HashZod.parse(objectHash));
722
+ const record = await viewer.object(HashZod3.parse(objectHash));
16
723
  return record === void 0 ? void 0 : toGrantObject(record);
17
724
  }
18
725
  });
@@ -21,7 +728,8 @@ function toClaimQuery(query) {
21
728
  if (query === void 0) return void 0;
22
729
  return {
23
730
  ...query.declaredObjectSchemaPrefix === void 0 ? {} : { declaredObjectSchemaPrefix: query.declaredObjectSchemaPrefix },
24
- ...query.objectHash === void 0 ? {} : { objectHash: HashZod.parse(query.objectHash) }
731
+ ...query.objectHash === void 0 ? {} : { objectHash: HashZod3.parse(query.objectHash) },
732
+ ...query.subject === void 0 ? {} : { subject: XyoAddressZod.parse(query.subject) }
25
733
  };
26
734
  }
27
735
  function toGrantClaim(record) {
@@ -31,7 +739,8 @@ function toGrantClaim(record) {
31
739
  firstEventHash: record.firstEventHash,
32
740
  lastBlock: record.lastBlock,
33
741
  objectHash: record.objectHash,
34
- source: record.source
742
+ source: record.source,
743
+ subject: record.subject
35
744
  });
36
745
  }
37
746
  function toGrantObject(record) {
@@ -42,7 +751,782 @@ function toGrantObject(record) {
42
751
  params: record.params === null ? null : Object.freeze({ ...record.params })
43
752
  });
44
753
  }
754
+
755
+ // src/handshakeReadModel.ts
756
+ import {
757
+ EncryptedWakeSubscriptionSchema as EncryptedWakeSubscriptionSchema2,
758
+ EncryptedWakeSubscriptionZod,
759
+ EndpointChallengeResponseZod,
760
+ EndpointChallengeZod,
761
+ FINALIZED_HEAD_EVENT_KIND as FINALIZED_HEAD_EVENT_KIND2,
762
+ HandshakeIdZod,
763
+ NormalizedAddressZod as NormalizedAddressZod4,
764
+ PublisherServiceSchema as PublisherServiceSchema3,
765
+ WakeGrantObjectSchema as WakeGrantObjectSchema2,
766
+ WakeGrantObjectZod,
767
+ WakeGrantRevocationSchema as WakeGrantRevocationSchema2,
768
+ WakeGrantRevocationZod,
769
+ WakeSubscriptionAcceptanceRevocationSchema as WakeSubscriptionAcceptanceRevocationSchema2,
770
+ WakeSubscriptionAcceptanceRevocationZod,
771
+ WakeSubscriptionAcceptanceSchema as WakeSubscriptionAcceptanceSchema3,
772
+ WakeSubscriptionAcceptanceZod as WakeSubscriptionAcceptanceZod2,
773
+ WakeSubscriptionRevocationSchema as WakeSubscriptionRevocationSchema2,
774
+ WakeSubscriptionRevocationZod
775
+ } from "@xyo-network/event-kit-schemas";
776
+ import { XyoAddressZod as XyoAddressZod3 } from "@xyo-network/sdk-protocol/address";
777
+
778
+ // src/handshakeCatalog.ts
779
+ import {
780
+ resolvePreferredEncryptionKey,
781
+ selectPublisherService
782
+ } from "@xyo-network/event-kit-protocol";
783
+ import {
784
+ ChainIdZod as ChainIdZod3,
785
+ FINALIZED_HEAD_EVENT_KIND,
786
+ NormalizedAddressZod as NormalizedAddressZod3,
787
+ PublisherEncryptionKeyRevocationSchema as PublisherEncryptionKeyRevocationSchema2,
788
+ PublisherEncryptionKeyRevocationZod,
789
+ PublisherEncryptionKeySchema as PublisherEncryptionKeySchema2,
790
+ PublisherEncryptionKeyZod,
791
+ PublisherServiceRevocationSchema as PublisherServiceRevocationSchema2,
792
+ PublisherServiceRevocationZod,
793
+ PublisherServiceSchema as PublisherServiceSchema2,
794
+ PublisherServiceZod
795
+ } from "@xyo-network/event-kit-schemas";
796
+ import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
797
+ import { XyoAddressZod as XyoAddressZod2 } from "@xyo-network/sdk-protocol/address";
798
+ function handshakeEvidence(record) {
799
+ return {
800
+ block: record.block,
801
+ eventHash: record.eventHash,
802
+ hashIndex: record.hashIndex,
803
+ objectHash: record.objectHash,
804
+ ordinal: record.blockEventOrdinal,
805
+ source: record.source,
806
+ txHash: record.txHash
807
+ };
808
+ }
809
+ function handshakeEventEvidence(record) {
810
+ const item = handshakeEvidence(record);
811
+ return {
812
+ block: item.block,
813
+ eventHash: item.eventHash,
814
+ ordinal: item.ordinal,
815
+ source: item.source,
816
+ txHash: item.txHash
817
+ };
818
+ }
819
+ function handshakeEventKey(record) {
820
+ return `${record.txHash}:${record.eventHash}`;
821
+ }
822
+ function earliestByHash(records) {
823
+ const result = /* @__PURE__ */ new Map();
824
+ for (const record of records) {
825
+ const prior = result.get(record.objectHash);
826
+ if (prior === void 0 || record.block < prior.block || record.block === prior.block && record.blockEventOrdinal.tx < prior.blockEventOrdinal.tx || record.block === prior.block && record.blockEventOrdinal.tx === prior.blockEventOrdinal.tx && record.blockEventOrdinal.event < prior.blockEventOrdinal.event) {
827
+ result.set(record.objectHash, record);
828
+ }
829
+ }
830
+ return [...result.values()];
831
+ }
832
+ async function verifiedHandshakeObjectBody(viewer, record) {
833
+ const object = await viewer.object(record.objectHash);
834
+ if (object?.assurance !== "resolved" || object.declaredSchema !== record.objectSchema || object.params === null) return void 0;
835
+ const body = { schema: object.declaredSchema, ...object.params };
836
+ return await PayloadBuilder2.dataHash(body) === record.objectHash ? body : void 0;
837
+ }
838
+ async function verifiedHandshakeAttachmentBody(resolver, hash) {
839
+ const body = resolver?.(hash);
840
+ if (body === void 0) return void 0;
841
+ return await PayloadBuilder2.dataHash(body) === hash ? { ...body } : void 0;
842
+ }
843
+ function firstHandshakeRevocation(revocations, target) {
844
+ const blocks = revocations.filter((item) => item.target === target).map((item) => item.block);
845
+ return blocks.length === 0 ? void 0 : Math.min(...blocks);
846
+ }
847
+ async function parsedHandshakeObjects(viewer, records, parse) {
848
+ const objects = [];
849
+ for (const record of earliestByHash(records)) {
850
+ const body = await verifiedHandshakeObjectBody(viewer, record);
851
+ const parsed = parse(body);
852
+ if (!parsed.success) continue;
853
+ objects.push({
854
+ body: parsed.data,
855
+ evidence: handshakeEvidence(record),
856
+ objectHash: record.objectHash
857
+ });
858
+ }
859
+ return objects;
860
+ }
861
+ async function handshakeRevocations(viewer, records, parse, field) {
862
+ const result = [];
863
+ for (const record of earliestByHash(records)) {
864
+ const parsed = parse(await verifiedHandshakeObjectBody(viewer, record));
865
+ if (parsed.success && typeof parsed.data[field] === "string") {
866
+ result.push({ block: record.block, target: parsed.data[field] });
867
+ }
868
+ }
869
+ return result;
870
+ }
871
+ async function buildHandshakeCatalogs(viewer, publisher) {
872
+ const events = await viewer.events(XyoAddressZod2.parse(publisher));
873
+ const services = await parsedHandshakeObjects(
874
+ viewer,
875
+ events.filter((record) => record.objectSchema === PublisherServiceSchema2),
876
+ (value) => PublisherServiceZod.safeParse(value)
877
+ );
878
+ const keys = await parsedHandshakeObjects(
879
+ viewer,
880
+ events.filter((record) => record.objectSchema === PublisherEncryptionKeySchema2),
881
+ (value) => PublisherEncryptionKeyZod.safeParse(value)
882
+ );
883
+ const serviceRevocations = await handshakeRevocations(
884
+ viewer,
885
+ events.filter((record) => record.objectSchema === PublisherServiceRevocationSchema2),
886
+ (value) => PublisherServiceRevocationZod.safeParse(value),
887
+ "service"
888
+ );
889
+ const keyRevocations = await handshakeRevocations(
890
+ viewer,
891
+ events.filter((record) => record.objectSchema === PublisherEncryptionKeyRevocationSchema2),
892
+ (value) => PublisherEncryptionKeyRevocationZod.safeParse(value),
893
+ "key"
894
+ );
895
+ const serviceByHash = new Map(services.map((service) => {
896
+ const revokedAtBlock = firstHandshakeRevocation(serviceRevocations, service.objectHash);
897
+ return [service.objectHash, {
898
+ ...service,
899
+ ...revokedAtBlock === void 0 ? {} : { revokedAtBlock }
900
+ }];
901
+ }));
902
+ const keyByHash = new Map(keys.map((key) => {
903
+ const revokedAtBlock = firstHandshakeRevocation(keyRevocations, key.objectHash);
904
+ return [key.objectHash, {
905
+ ...key,
906
+ ...revokedAtBlock === void 0 ? {} : { revokedAtBlock }
907
+ }];
908
+ }));
909
+ return {
910
+ keyByHash,
911
+ keyLifecycles: [...keyByHash.values()].map((key) => ({
912
+ claimedAtBlock: key.evidence.block,
913
+ expiresAtBlock: key.body.expiresAtBlock,
914
+ keyId: key.body.keyId,
915
+ objectHash: key.objectHash,
916
+ revokedAtBlock: key.revokedAtBlock,
917
+ source: key.evidence.source,
918
+ validFromBlock: key.body.validFromBlock
919
+ })),
920
+ serviceByHash,
921
+ serviceLifecycles: [...serviceByHash.values()].map((service) => ({
922
+ claimedAtBlock: service.evidence.block,
923
+ encryptionKeys: service.body.encryptionKeys,
924
+ eventKinds: service.body.events.map((event) => event.kind),
925
+ expiresAtBlock: service.body.expiresAtBlock,
926
+ objectHash: service.objectHash,
927
+ preferredEncryptionKey: service.body.preferredEncryptionKey,
928
+ revision: service.body.revision,
929
+ revokedAtBlock: service.revokedAtBlock,
930
+ serviceId: service.body.serviceId,
931
+ source: service.evidence.source,
932
+ supersedes: service.body.supersedes,
933
+ validFromBlock: service.body.validFromBlock
934
+ }))
935
+ };
936
+ }
937
+ async function viewHead(viewer, finalizedHead) {
938
+ if (!Number.isSafeInteger(finalizedHead) || finalizedHead < 0) {
939
+ throw new TypeError("finalizedHead must be a nonnegative safe integer");
940
+ }
941
+ let indexedThrough;
942
+ try {
943
+ indexedThrough = await viewer.head();
944
+ } catch {
945
+ return {
946
+ finalizedHead,
947
+ indexedThrough: null,
948
+ ok: false,
949
+ reason: "view-unavailable"
950
+ };
951
+ }
952
+ if (indexedThrough < finalizedHead) return {
953
+ finalizedHead,
954
+ indexedThrough,
955
+ ok: false,
956
+ reason: "stale-view"
957
+ };
958
+ if (indexedThrough > finalizedHead) return {
959
+ finalizedHead,
960
+ indexedThrough,
961
+ ok: false,
962
+ reason: "ahead-of-finality"
963
+ };
964
+ return { indexedThrough };
965
+ }
966
+ function exactHandshakeServiceAt(catalogs, service, publisher, finalizedHead) {
967
+ const selection = selectPublisherService({
968
+ eventKind: FINALIZED_HEAD_EVENT_KIND,
969
+ finalizedHead,
970
+ publisherSource: publisher,
971
+ serviceId: service.body.serviceId,
972
+ services: catalogs.serviceLifecycles
973
+ });
974
+ return selection.ok && selection.service.objectHash === service.objectHash;
975
+ }
976
+ function exactHandshakeKeyAt(catalogs, service, keyHash, finalizedHead) {
977
+ const serviceLifecycle = catalogs.serviceLifecycles.find((item) => item.objectHash === service.objectHash);
978
+ if (serviceLifecycle === void 0) return false;
979
+ const resolution = resolvePreferredEncryptionKey({
980
+ finalizedHead,
981
+ keys: catalogs.keyLifecycles,
982
+ service: serviceLifecycle
983
+ });
984
+ return resolution.ok && resolution.key.objectHash === keyHash;
985
+ }
986
+ function hasPendingPublisherService(catalogs, query) {
987
+ return catalogs.serviceLifecycles.some((service) => service.source === query.publisher && service.claimedAtBlock <= query.finalizedHead && service.validFromBlock > query.finalizedHead && (service.revokedAtBlock === void 0 || service.revokedAtBlock > query.finalizedHead) && (query.serviceId === void 0 || service.serviceId === query.serviceId) && service.eventKinds.includes(query.eventKind ?? FINALIZED_HEAD_EVENT_KIND));
988
+ }
989
+ async function discoverHandshakeService(options, rawQuery) {
990
+ const query = {
991
+ ...rawQuery,
992
+ chainId: ChainIdZod3.parse(rawQuery.chainId),
993
+ eventKind: rawQuery.eventKind ?? FINALIZED_HEAD_EVENT_KIND,
994
+ publisher: NormalizedAddressZod3.parse(rawQuery.publisher)
995
+ };
996
+ const head = await viewHead(options.viewer, query.finalizedHead);
997
+ if ("ok" in head) return { query, result: head };
998
+ const catalogs = await buildHandshakeCatalogs(options.viewer, query.publisher);
999
+ const serviceSelection = selectPublisherService({
1000
+ eventKind: query.eventKind ?? FINALIZED_HEAD_EVENT_KIND,
1001
+ finalizedHead: query.finalizedHead,
1002
+ publisherSource: query.publisher,
1003
+ ...query.serviceId === void 0 ? {} : { serviceId: query.serviceId },
1004
+ services: catalogs.serviceLifecycles
1005
+ });
1006
+ if (!serviceSelection.ok) {
1007
+ const reason = serviceSelection.reason === "service-not-found" && hasPendingPublisherService(catalogs, query) ? "service-pending" : serviceSelection.reason;
1008
+ return {
1009
+ catalogs,
1010
+ query,
1011
+ result: {
1012
+ finalizedHead: query.finalizedHead,
1013
+ indexedThrough: head.indexedThrough,
1014
+ ok: false,
1015
+ reason
1016
+ }
1017
+ };
1018
+ }
1019
+ const service = catalogs.serviceByHash.get(serviceSelection.service.objectHash);
1020
+ if (service === void 0) throw new Error("selected service body is unavailable");
1021
+ const keySelection = resolvePreferredEncryptionKey({
1022
+ finalizedHead: query.finalizedHead,
1023
+ keys: catalogs.keyLifecycles,
1024
+ service: serviceSelection.service
1025
+ });
1026
+ if (!keySelection.ok) {
1027
+ return {
1028
+ catalogs,
1029
+ query,
1030
+ result: {
1031
+ finalizedHead: query.finalizedHead,
1032
+ indexedThrough: head.indexedThrough,
1033
+ ok: false,
1034
+ reason: keySelection.reason
1035
+ }
1036
+ };
1037
+ }
1038
+ const key = catalogs.keyByHash.get(keySelection.key.objectHash);
1039
+ if (key === void 0) throw new Error("selected encryption-key body is unavailable");
1040
+ return {
1041
+ catalogs,
1042
+ query,
1043
+ result: {
1044
+ finalizedHead: query.finalizedHead,
1045
+ indexedThrough: head.indexedThrough,
1046
+ key,
1047
+ ok: true,
1048
+ service
1049
+ }
1050
+ };
1051
+ }
1052
+
1053
+ // src/publisherSubjectViewer.ts
1054
+ var LegacyHexAddress3 = /^[A-Fa-f0-9]{40}$/;
1055
+ function normalizeAddress4(value) {
1056
+ return LegacyHexAddress3.test(value) ? value.toLowerCase() : value;
1057
+ }
1058
+ function isPublisherSubject(subject, publisher) {
1059
+ return normalizeAddress4(subject) === normalizeAddress4(publisher);
1060
+ }
1061
+ function publisherSubjectViewer(viewer, publisher) {
1062
+ const claimQuery = (query) => ({ ...query, subject: publisher });
1063
+ const eventQuery = (query) => ({ ...query, subject: publisher });
1064
+ const filterClaims = async (records) => (await records).filter((record) => isPublisherSubject(record.subject, publisher));
1065
+ const filterEvents = async (records) => (await records).filter((record) => isPublisherSubject(record.subject, publisher));
1066
+ return Object.freeze({
1067
+ async claims(source, query) {
1068
+ return await filterClaims(viewer.claims(source, claimQuery(query)));
1069
+ },
1070
+ async claimsByObject(objectHash) {
1071
+ return await filterClaims(viewer.claimsBySubject(publisher, claimQuery({ objectHash })));
1072
+ },
1073
+ async claimsBySubject(subject, query) {
1074
+ if (!isPublisherSubject(subject, publisher)) return [];
1075
+ return await filterClaims(viewer.claimsBySubject(publisher, claimQuery(query)));
1076
+ },
1077
+ async event(txHash, eventHash) {
1078
+ return await filterEvents(viewer.event(txHash, eventHash));
1079
+ },
1080
+ async events(source, query) {
1081
+ return await filterEvents(viewer.events(source, eventQuery(query)));
1082
+ },
1083
+ async head() {
1084
+ return await viewer.head();
1085
+ },
1086
+ async object(objectHash) {
1087
+ return await viewer.object(objectHash);
1088
+ },
1089
+ async source(source) {
1090
+ return await viewer.source(source);
1091
+ },
1092
+ async unresolved(txHash, eventHash) {
1093
+ return await viewer.unresolved(txHash, eventHash);
1094
+ }
1095
+ });
1096
+ }
1097
+
1098
+ // src/subscriptionLifecycleView.ts
1099
+ import { selectSubscriptionActivation } from "@xyo-network/event-kit-protocol";
1100
+ function inactiveState(tuples, acceptances, finalizedHead) {
1101
+ const subscriptionHashes = new Set(tuples.map((tuple) => tuple.subscription.objectHash));
1102
+ if (tuples.some((tuple) => (tuple.grantRevokedAtBlock ?? Number.POSITIVE_INFINITY) <= finalizedHead) || tuples.some((tuple) => (tuple.subscription.revokedAtBlock ?? Number.POSITIVE_INFINITY) <= finalizedHead) || acceptances.some((item) => subscriptionHashes.has(item.lifecycle.subscription) && (item.lifecycle.revokedAtBlock ?? Number.POSITIVE_INFINITY) <= finalizedHead)) {
1103
+ return "revoked";
1104
+ }
1105
+ if (tuples.every((tuple) => tuple.subscription.body.expiresAtBlock <= finalizedHead)) return "expired";
1106
+ return "inactive";
1107
+ }
1108
+ function pendingState(tuples, acceptances, finalizedHead) {
1109
+ const latest = tuples.reduce((selected, candidate) => selected === void 0 || candidate.subscriptionLifecycle.revision > selected.subscriptionLifecycle.revision ? candidate : selected, void 0);
1110
+ if (latest === void 0) return "inactive";
1111
+ const matchingAcceptances = acceptances.filter((item) => item.lifecycle.subscription === latest.subscription.objectHash);
1112
+ if ((latest.grantRevokedAtBlock ?? Number.POSITIVE_INFINITY) <= finalizedHead || (latest.subscription.revokedAtBlock ?? Number.POSITIVE_INFINITY) <= finalizedHead || matchingAcceptances.some((item) => (item.lifecycle.revokedAtBlock ?? Number.POSITIVE_INFINITY) <= finalizedHead)) {
1113
+ return "revoked";
1114
+ }
1115
+ return latest.subscription.body.expiresAtBlock <= finalizedHead ? "expired" : "pending";
1116
+ }
1117
+ function buildPublisherSubscriptionViews(tuples, acceptances, publisher, finalizedHead) {
1118
+ const grouped = Map.groupBy(tuples, (tuple) => `${tuple.subscriber}:${tuple.subscription.body.subscriptionId}`);
1119
+ const result = [];
1120
+ for (const group of grouped.values()) {
1121
+ const first = group[0];
1122
+ const groupAcceptances = acceptances.filter((item) => item.subscriber === first.subscriber);
1123
+ const activation = selectSubscriptionActivation({
1124
+ acceptances: groupAcceptances.map((item) => item.lifecycle),
1125
+ finalizedHead,
1126
+ publisherSource: publisher,
1127
+ subscriberSource: first.subscriber,
1128
+ subscriptionId: first.subscription.body.subscriptionId,
1129
+ subscriptions: group.map((item) => item.subscriptionLifecycle)
1130
+ });
1131
+ if (activation.ok) {
1132
+ const selected = group.find((item) => item.subscription.objectHash === activation.subscription.objectHash);
1133
+ const acceptance = groupAcceptances.find((item) => item.lifecycle.objectHash === activation.acceptance.objectHash);
1134
+ if (selected?.grantRevokedAtBlock !== void 0 && selected.grantRevokedAtBlock <= finalizedHead) {
1135
+ result.push({
1136
+ reason: "grant-revoked",
1137
+ state: "revoked",
1138
+ subscriber: first.subscriber,
1139
+ subscriptionId: first.subscription.body.subscriptionId,
1140
+ tuples: group
1141
+ });
1142
+ } else {
1143
+ result.push({
1144
+ ...acceptance === void 0 ? {} : { acceptance: acceptance.claimed },
1145
+ activeFromBlock: activation.activeFromBlock,
1146
+ pendingReplacement: activation.pendingReplacement,
1147
+ selectedSubscription: activation.subscription.objectHash,
1148
+ state: "active",
1149
+ subscriber: first.subscriber,
1150
+ subscriptionId: first.subscription.body.subscriptionId,
1151
+ tuples: group
1152
+ });
1153
+ }
1154
+ } else {
1155
+ result.push({
1156
+ reason: activation.reason,
1157
+ state: activation.reason === "subscription-pending" ? pendingState(group, groupAcceptances, finalizedHead) : activation.reason === "invalid-revision-chain" || activation.reason === "ambiguous-acceptance" ? "rejected" : inactiveState(group, groupAcceptances, finalizedHead),
1158
+ subscriber: first.subscriber,
1159
+ subscriptionId: first.subscription.body.subscriptionId,
1160
+ tuples: group
1161
+ });
1162
+ }
1163
+ }
1164
+ return result;
1165
+ }
1166
+ function publicSubscriptionTuple(tuple) {
1167
+ return {
1168
+ grant: tuple.grant,
1169
+ service: tuple.service,
1170
+ subscriber: tuple.subscriber,
1171
+ subscription: tuple.subscription,
1172
+ tupleEvent: tuple.tupleEvent
1173
+ };
1174
+ }
1175
+ function isAcceptanceEligibleAtHead(tuple, catalogs, serviceHash, key, finalizedHead, publisher) {
1176
+ return tuple.service.objectHash === serviceHash && tuple.subscription.body.encryptionKey === key.objectHash && tuple.subscription.body.keyId === key.body.keyId && exactHandshakeServiceAt(catalogs, tuple.service, publisher, finalizedHead) && exactHandshakeKeyAt(catalogs, tuple.service, key.objectHash, finalizedHead) && (tuple.grantRevokedAtBlock === void 0 || tuple.grantRevokedAtBlock > finalizedHead) && (tuple.subscription.revokedAtBlock === void 0 || tuple.subscription.revokedAtBlock > finalizedHead) && finalizedHead < Number.MAX_SAFE_INTEGER && tuple.subscription.body.expiresAtBlock - finalizedHead > 2;
1177
+ }
1178
+ function selectPublisherAcceptanceCandidates(input) {
1179
+ const acceptedSubscriptions = new Set(input.acceptances.map((item) => item.lifecycle.subscription));
1180
+ const candidates = [];
1181
+ for (const subscription of input.subscriptions) {
1182
+ if (subscription.state !== "pending" && !(subscription.state === "active" && subscription.pendingReplacement === true)) continue;
1183
+ const highestRevision = Math.max(...subscription.tuples.map((tuple2) => tuple2.subscription.body.revision));
1184
+ const latest = input.tuples.filter((tuple2) => tuple2.subscriber === subscription.subscriber && tuple2.subscription.body.subscriptionId === subscription.subscriptionId && tuple2.subscription.body.revision === highestRevision);
1185
+ if (latest.length !== 1) continue;
1186
+ const tuple = latest[0];
1187
+ if (acceptedSubscriptions.has(tuple.subscription.objectHash) || !isAcceptanceEligibleAtHead(
1188
+ tuple,
1189
+ input.catalogs,
1190
+ input.service.objectHash,
1191
+ input.key,
1192
+ input.finalizedHead,
1193
+ input.publisher
1194
+ )) continue;
1195
+ candidates.push({
1196
+ key: input.key,
1197
+ subscriber: tuple.subscriber,
1198
+ subscriptionId: tuple.subscription.body.subscriptionId,
1199
+ tuple: publicSubscriptionTuple(tuple)
1200
+ });
1201
+ }
1202
+ return candidates;
1203
+ }
1204
+
1205
+ // src/handshakeReadModel.ts
1206
+ function publisherSubjectOptions(options, publisher) {
1207
+ return {
1208
+ ...options,
1209
+ viewer: publisherSubjectViewer(options.viewer, XyoAddressZod3.parse(publisher))
1210
+ };
1211
+ }
1212
+ function hasExactGrantBinding(grant, publisher) {
1213
+ return grant.publisher === publisher && grant.permissions.length === 1 && grant.permissions[0] === "enqueue";
1214
+ }
1215
+ function hasExactSubscriptionBinding(subscription, chainId, publisher, serviceHash, grantHash) {
1216
+ return subscription.publisher === publisher && subscription.chainId === chainId && subscription.service === serviceHash && subscription.grant === grantHash && subscription.eventKind === FINALIZED_HEAD_EVENT_KIND2;
1217
+ }
1218
+ function hasExactKeyBinding(subscription, service, catalogs, tupleBlock) {
1219
+ const key = catalogs.keyByHash.get(subscription.encryptionKey);
1220
+ return subscription.encryptionKey === service.body.preferredEncryptionKey && key?.body.keyId === subscription.keyId && service.body.encryptionKeys.includes(key.objectHash) && exactHandshakeKeyAt(catalogs, service, key.objectHash, tupleBlock);
1221
+ }
1222
+ function expiryIsInsideServicePolicy(subscription, service, tupleBlock) {
1223
+ return subscription.expiresAtBlock > tupleBlock && subscription.expiresAtBlock <= tupleBlock + service.limits.maxSubscriptionLifetimeBlocks;
1224
+ }
1225
+ async function tupleRevocations(viewer, records) {
1226
+ return {
1227
+ grants: await handshakeRevocations(
1228
+ viewer,
1229
+ records.filter((record) => record.objectSchema === WakeGrantRevocationSchema2),
1230
+ (value) => WakeGrantRevocationZod.safeParse(value),
1231
+ "grant"
1232
+ ),
1233
+ subscriptions: await handshakeRevocations(
1234
+ viewer,
1235
+ records.filter((record) => record.objectSchema === WakeSubscriptionRevocationSchema2),
1236
+ (value) => WakeSubscriptionRevocationZod.safeParse(value),
1237
+ "subscription"
1238
+ )
1239
+ };
1240
+ }
1241
+ async function validateTuple(viewer, records, subscriber, service, catalogs, chainId, publisher, sourceEvents) {
1242
+ const serviceEntries = records.filter((record) => record.objectSchema === PublisherServiceSchema3);
1243
+ const grantEntries = records.filter((record) => record.objectSchema === WakeGrantObjectSchema2);
1244
+ const subscriptionEntries = records.filter((record) => record.objectSchema === EncryptedWakeSubscriptionSchema2);
1245
+ const baseEvidence = handshakeEventEvidence(records[0]);
1246
+ const reject = (reason) => ({
1247
+ rejection: {
1248
+ evidence: baseEvidence,
1249
+ reason,
1250
+ subscriber
1251
+ }
1252
+ });
1253
+ if (serviceEntries.length !== 1 || serviceEntries[0]?.objectHash !== service.objectHash || grantEntries.length !== 1 || subscriptionEntries.length !== 1) {
1254
+ return reject("ambiguous-fan");
1255
+ }
1256
+ const grantRecord = grantEntries[0];
1257
+ const subscriptionRecord = subscriptionEntries[0];
1258
+ const grantParsed = WakeGrantObjectZod.safeParse(await verifiedHandshakeObjectBody(viewer, grantRecord));
1259
+ if (!grantParsed.success) return reject("malformed-grant");
1260
+ const subscriptionParsed = EncryptedWakeSubscriptionZod.safeParse(
1261
+ await verifiedHandshakeObjectBody(viewer, subscriptionRecord)
1262
+ );
1263
+ if (!subscriptionParsed.success) return reject("malformed-subscription");
1264
+ const grant = grantParsed.data;
1265
+ const subscription = subscriptionParsed.data;
1266
+ const tupleBlock = subscriptionRecord.block;
1267
+ if (!hasExactGrantBinding(grant, publisher)) return reject("grant-binding-invalid");
1268
+ if (!hasExactSubscriptionBinding(subscription, chainId, publisher, service.objectHash, grantRecord.objectHash)) {
1269
+ return reject("subscription-binding-invalid");
1270
+ }
1271
+ const claimedKey = catalogs.keyByHash.get(subscription.encryptionKey);
1272
+ if (service.evidence.block >= tupleBlock) return reject("service-inactive-at-claim");
1273
+ if (claimedKey === void 0 || claimedKey.evidence.block >= tupleBlock) return reject("key-binding-invalid");
1274
+ if (!exactHandshakeServiceAt(catalogs, service, publisher, tupleBlock)) return reject("service-inactive-at-claim");
1275
+ if (!hasExactKeyBinding(subscription, service, catalogs, tupleBlock)) return reject("key-binding-invalid");
1276
+ if (!expiryIsInsideServicePolicy(subscription, service.body, tupleBlock)) return reject("expiry-out-of-policy");
1277
+ const revoked = await tupleRevocations(viewer, sourceEvents);
1278
+ const subscriptionRevokedAtBlock = firstHandshakeRevocation(revoked.subscriptions, subscriptionRecord.objectHash);
1279
+ const grantRevokedAtBlock = firstHandshakeRevocation(revoked.grants, grantRecord.objectHash);
1280
+ return {
1281
+ tuple: {
1282
+ grant: {
1283
+ body: grant,
1284
+ evidence: handshakeEvidence(grantRecord),
1285
+ objectHash: grantRecord.objectHash
1286
+ },
1287
+ grantRevokedAtBlock,
1288
+ service,
1289
+ subscriber,
1290
+ subscription: {
1291
+ body: subscription,
1292
+ evidence: handshakeEvidence(subscriptionRecord),
1293
+ objectHash: subscriptionRecord.objectHash,
1294
+ ...subscriptionRevokedAtBlock === void 0 ? {} : { revokedAtBlock: subscriptionRevokedAtBlock }
1295
+ },
1296
+ subscriptionLifecycle: {
1297
+ claimedAtBlock: tupleBlock,
1298
+ expiresAtBlock: subscription.expiresAtBlock,
1299
+ objectHash: subscriptionRecord.objectHash,
1300
+ revision: subscription.revision,
1301
+ revokedAtBlock: subscriptionRevokedAtBlock,
1302
+ source: subscriber,
1303
+ subscriptionId: subscription.subscriptionId,
1304
+ supersedes: subscription.supersedes
1305
+ },
1306
+ tupleEvent: baseEvidence
1307
+ }
1308
+ };
1309
+ }
1310
+ async function discoverTuples(viewer, services, catalogs, query, candidateSources) {
1311
+ const serviceByHash = new Map(services.map((service) => [service.objectHash, service]));
1312
+ const serviceHashes = new Set(serviceByHash.keys());
1313
+ const sources = candidateSources === void 0 ? [...new Set((await viewer.claimsBySubject(XyoAddressZod3.parse(query.publisher))).filter((claim) => serviceHashes.has(claim.objectHash)).map((claim) => claim.source))].filter((source) => !isPublisherSubject(source, query.publisher)) : [...new Set(candidateSources)].filter((source) => !isPublisherSubject(source, query.publisher));
1314
+ const tuples = /* @__PURE__ */ new Map();
1315
+ const rejections = [];
1316
+ for (const source of sources) {
1317
+ const sourceAddress = XyoAddressZod3.parse(source);
1318
+ const events = await viewer.events(sourceAddress, { subject: XyoAddressZod3.parse(query.publisher) });
1319
+ const groups = Map.groupBy(events, handshakeEventKey);
1320
+ for (const records of groups.values()) {
1321
+ const referencedServices = records.flatMap((record) => {
1322
+ const service = serviceByHash.get(record.objectHash);
1323
+ return service === void 0 ? [] : [service];
1324
+ });
1325
+ for (const service of referencedServices) {
1326
+ const result = await validateTuple(
1327
+ viewer,
1328
+ records,
1329
+ source,
1330
+ service,
1331
+ catalogs,
1332
+ query.chainId,
1333
+ query.publisher,
1334
+ events
1335
+ );
1336
+ if (result.rejection !== void 0) rejections.push(result.rejection);
1337
+ if (result.tuple !== void 0) {
1338
+ const tupleKey = `${result.tuple.subscriber}:${result.tuple.subscription.objectHash}`;
1339
+ const prior = tuples.get(tupleKey);
1340
+ if (prior === void 0 || result.tuple.subscription.evidence.block < prior.subscription.evidence.block) {
1341
+ tuples.set(tupleKey, result.tuple);
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+ }
1347
+ return { rejections, tuples: [...tuples.values()] };
1348
+ }
1349
+ function challengeMatchesTuple(challenge, response, acceptance, tuple, chainId, publisher) {
1350
+ return challenge.chainId === chainId && challenge.publisher === publisher && challenge.subscriber === tuple.subscriber && challenge.service === tuple.service.objectHash && challenge.encryptionKey === tuple.subscription.body.encryptionKey && challenge.keyId === tuple.subscription.body.keyId && challenge.subscription === tuple.subscription.objectHash && challenge.grant === tuple.grant.objectHash && challenge.eventKind === tuple.subscription.body.eventKind && response.challenge === acceptance.challenge;
1351
+ }
1352
+ async function validatedAcceptances(options, catalogs, tuples, chainId, publisher) {
1353
+ const publisherAddress = XyoAddressZod3.parse(publisher);
1354
+ const events = await options.viewer.events(publisherAddress, { subject: publisherAddress });
1355
+ const acceptances = await parsedHandshakeObjects(
1356
+ options.viewer,
1357
+ events.filter((record) => record.objectSchema === WakeSubscriptionAcceptanceSchema3),
1358
+ (value) => WakeSubscriptionAcceptanceZod2.safeParse(value)
1359
+ );
1360
+ const acceptanceRevocations = await handshakeRevocations(
1361
+ options.viewer,
1362
+ events.filter((record) => record.objectSchema === WakeSubscriptionAcceptanceRevocationSchema2),
1363
+ (value) => WakeSubscriptionAcceptanceRevocationZod.safeParse(value),
1364
+ "acceptance"
1365
+ );
1366
+ const result = [];
1367
+ for (const claimed of acceptances) {
1368
+ const candidates = tuples.filter((candidate) => claimed.body.service === candidate.service.objectHash && claimed.body.encryptionKey === candidate.subscription.body.encryptionKey && claimed.body.subscription === candidate.subscription.objectHash && claimed.body.grant === candidate.grant.objectHash);
1369
+ if (candidates.length === 0) continue;
1370
+ const challengeBody = await verifiedHandshakeAttachmentBody(options.body, claimed.body.challenge);
1371
+ const responseBody = await verifiedHandshakeAttachmentBody(options.body, claimed.body.response);
1372
+ const challenge = EndpointChallengeZod.safeParse(challengeBody);
1373
+ const response = EndpointChallengeResponseZod.safeParse(responseBody);
1374
+ if (!challenge.success || !response.success) continue;
1375
+ const eligible = candidates.filter((tuple2) => claimed.evidence.block > tuple2.subscription.evidence.block && exactHandshakeServiceAt(catalogs, tuple2.service, publisher, claimed.evidence.block) && exactHandshakeKeyAt(catalogs, tuple2.service, claimed.body.encryptionKey, claimed.evidence.block) && claimed.evidence.block + 1 < tuple2.subscription.body.expiresAtBlock && (tuple2.grantRevokedAtBlock === void 0 || tuple2.grantRevokedAtBlock > claimed.evidence.block) && (tuple2.subscription.revokedAtBlock === void 0 || tuple2.subscription.revokedAtBlock > claimed.evidence.block) && challengeMatchesTuple(challenge.data, response.data, claimed.body, tuple2, chainId, publisher));
1376
+ if (eligible.length !== 1) continue;
1377
+ const tuple = eligible[0];
1378
+ const revokedAtBlock = firstHandshakeRevocation(acceptanceRevocations, claimed.objectHash);
1379
+ const withRevocation = {
1380
+ ...claimed,
1381
+ ...revokedAtBlock === void 0 ? {} : { revokedAtBlock }
1382
+ };
1383
+ result.push({
1384
+ claimed: withRevocation,
1385
+ lifecycle: {
1386
+ acceptanceBlock: claimed.evidence.block,
1387
+ objectHash: claimed.objectHash,
1388
+ revokedAtBlock,
1389
+ source: publisher,
1390
+ subscription: tuple.subscription.objectHash
1391
+ },
1392
+ subscriber: tuple.subscriber
1393
+ });
1394
+ }
1395
+ return result;
1396
+ }
1397
+ async function handshakeLifecycle(options, rawQuery, candidateSources, subscriptionId) {
1398
+ const scopedOptions = publisherSubjectOptions(options, rawQuery.publisher);
1399
+ const discovery = await discoverHandshakeService(scopedOptions, rawQuery);
1400
+ if (discovery.catalogs === void 0) return { discovery };
1401
+ const serviceRevisions = [...discovery.catalogs.serviceByHash.values()].filter((service) => service.evidence.source === discovery.query.publisher && (discovery.query.serviceId === void 0 || service.body.serviceId === discovery.query.serviceId) && service.body.events.some((event) => event.kind === discovery.query.eventKind) && service.evidence.block <= discovery.query.finalizedHead);
1402
+ const found = await discoverTuples(
1403
+ scopedOptions.viewer,
1404
+ serviceRevisions,
1405
+ discovery.catalogs,
1406
+ { chainId: discovery.query.chainId, publisher: discovery.query.publisher },
1407
+ candidateSources
1408
+ );
1409
+ const tuples = subscriptionId === void 0 ? found.tuples : found.tuples.filter((tuple) => tuple.subscription.body.subscriptionId === subscriptionId);
1410
+ const acceptances = await validatedAcceptances(
1411
+ scopedOptions,
1412
+ discovery.catalogs,
1413
+ tuples,
1414
+ discovery.query.chainId,
1415
+ discovery.query.publisher
1416
+ );
1417
+ return {
1418
+ acceptances,
1419
+ discovery,
1420
+ rejections: found.rejections,
1421
+ subscriptions: buildPublisherSubscriptionViews(
1422
+ tuples,
1423
+ acceptances,
1424
+ discovery.query.publisher,
1425
+ discovery.query.finalizedHead
1426
+ ),
1427
+ tuples
1428
+ };
1429
+ }
1430
+ function handshakeView(projection) {
1431
+ if (!("subscriptions" in projection)) {
1432
+ if (projection.discovery.result.ok) {
1433
+ throw new Error("successful service discovery produced no catalogs");
1434
+ }
1435
+ return projection.discovery.result;
1436
+ }
1437
+ const {
1438
+ discovery,
1439
+ rejections,
1440
+ subscriptions
1441
+ } = projection;
1442
+ const lifecycle = { rejections, subscriptions };
1443
+ if (discovery.result.ok) {
1444
+ return {
1445
+ ...discovery.result,
1446
+ ...lifecycle,
1447
+ hasCurrentAdvertisement: true,
1448
+ serviceDiscovery: discovery.result
1449
+ };
1450
+ }
1451
+ if (discovery.result.indexedThrough === null) {
1452
+ throw new Error("catalog-backed service discovery must have an indexed head");
1453
+ }
1454
+ return {
1455
+ finalizedHead: discovery.result.finalizedHead,
1456
+ hasCurrentAdvertisement: false,
1457
+ indexedThrough: discovery.result.indexedThrough,
1458
+ ok: true,
1459
+ serviceDiscovery: discovery.result,
1460
+ ...lifecycle
1461
+ };
1462
+ }
1463
+ function createEventKitHandshakeReadModel(options) {
1464
+ return Object.freeze({
1465
+ async acceptanceCandidates(rawQuery) {
1466
+ const subscriber = rawQuery.subscriber === void 0 ? void 0 : NormalizedAddressZod4.parse(rawQuery.subscriber);
1467
+ const subscriptionId = rawQuery.subscriptionId === void 0 ? void 0 : HandshakeIdZod.parse(rawQuery.subscriptionId);
1468
+ const projection = await handshakeLifecycle(
1469
+ options,
1470
+ rawQuery,
1471
+ subscriber === void 0 ? void 0 : [subscriber],
1472
+ subscriptionId
1473
+ );
1474
+ if (!("subscriptions" in projection)) {
1475
+ if (projection.discovery.result.ok) {
1476
+ throw new Error("successful service discovery produced no catalogs");
1477
+ }
1478
+ return projection.discovery.result;
1479
+ }
1480
+ if (!projection.discovery.result.ok) return projection.discovery.result;
1481
+ const {
1482
+ catalogs,
1483
+ query,
1484
+ result
1485
+ } = projection.discovery;
1486
+ if (catalogs === void 0) throw new Error("catalog-backed candidate discovery requires catalogs");
1487
+ return {
1488
+ ...result,
1489
+ candidates: selectPublisherAcceptanceCandidates({
1490
+ acceptances: projection.acceptances,
1491
+ catalogs,
1492
+ finalizedHead: query.finalizedHead,
1493
+ key: result.key,
1494
+ publisher: query.publisher,
1495
+ service: result.service,
1496
+ subscriptions: projection.subscriptions,
1497
+ tuples: projection.tuples
1498
+ }),
1499
+ rejections: projection.rejections,
1500
+ subscriptions: projection.subscriptions
1501
+ };
1502
+ },
1503
+ async discoverPublisherService(query) {
1504
+ return (await discoverHandshakeService(publisherSubjectOptions(options, query.publisher), query)).result;
1505
+ },
1506
+ async publisher(query) {
1507
+ return handshakeView(await handshakeLifecycle(options, query));
1508
+ },
1509
+ async subscriber(rawQuery) {
1510
+ const subscriber = NormalizedAddressZod4.parse(rawQuery.subscriber);
1511
+ const subscriptionId = rawQuery.subscriptionId === void 0 ? void 0 : HandshakeIdZod.parse(rawQuery.subscriptionId);
1512
+ const view = handshakeView(await handshakeLifecycle(options, rawQuery, [subscriber], subscriptionId));
1513
+ return view.ok ? { ...view, subscriber } : view;
1514
+ }
1515
+ });
1516
+ }
45
1517
  export {
46
- createStatementGraphGrantReadModel
1518
+ EVENT_KIT_REPLAY_CHECKPOINT_VERSION,
1519
+ EventKitClaimedObjectSchemaSet,
1520
+ EventKitClaimedObjectSchemas,
1521
+ EventKitReplayCheckpointZod,
1522
+ ReplayBlockedStateZod,
1523
+ ReplayCorruptBodyZod,
1524
+ ResolvedReplayBodyZod,
1525
+ createCheckpointStatementGraphViewer,
1526
+ createEventKitHandshakeReadModel,
1527
+ createFinalizedEventKitReplayController,
1528
+ createMemoryEventKitReplayCheckpointStore,
1529
+ createStatementGraphGrantReadModel,
1530
+ loadEventKitReplayCheckpoint
47
1531
  };
48
1532
  //# sourceMappingURL=index.mjs.map