Per-order terminal reorg linkage

This commit is contained in:
2026-10-20 19:08:19 -04:00
parent e1fbf5e5af
commit 22da9764eb
9 changed files with 98 additions and 24 deletions
+6 -4
View File
@@ -11,7 +11,8 @@ const pageSchema = z.object({ streamId: z.string().uuid(), previousAnchor: hex,
events: z.array(eventSchema).max(64), cursor: z.number().int().nonnegative().safe(),
head: z.number().int().nonnegative().safe(), caughtUp: z.boolean() }).strict();
export type CanonicalInboxPage = z.infer<typeof pageSchema>;
export type CanonicalInboxResult = { streamId: string; cursor: number; head: number; height: number; tip: string; replayed: boolean };
export type CanonicalInboxResult = { streamId: string; cursor: number; head: number; height: number; tip: string;
replayed: boolean; appendedEvents: z.infer<typeof eventSchema>[] };
export class CanonicalInboxError extends Error {
readonly code: string;
@@ -96,7 +97,7 @@ export async function ingestCanonicalPage(client: PoolClient, raw: unknown): Pro
const savedAnchor = await anchorAt(client, page.cursor);
if (page.cursor > cursor || savedAnchor !== page.anchor || page.previousAnchor !== page.anchor) throw new CanonicalInboxError('empty-cursor-mismatch');
return { streamId: page.streamId, cursor, head: Math.max(page.head, cursor), height: stack.length,
tip: stack.at(-1) ?? GENESIS_HASH, replayed: true };
tip: stack.at(-1) ?? GENESIS_HASH, replayed: true, appendedEvents: [] };
}
const firstSequence = page.events[0].sequence;
if (firstSequence > cursor + 1) throw new CanonicalInboxError('cursor-gap');
@@ -104,6 +105,7 @@ export async function ingestCanonicalPage(client: PoolClient, raw: unknown): Pro
if (!predecessor || page.previousAnchor !== predecessor) throw new CanonicalInboxError('cursor-anchor-mismatch');
let priorAnchor = predecessor;
let appended = false;
const appendedEvents: z.infer<typeof eventSchema>[] = [];
for (const e of page.events) {
if (e.eventHash !== eventDigest(page.streamId, e, priorAnchor)) throw new CanonicalInboxError('event-hash-invalid');
if (e.sequence <= cursor) {
@@ -113,13 +115,13 @@ export async function ingestCanonicalPage(client: PoolClient, raw: unknown): Pro
if (e.sequence !== cursor + 1 || priorAnchor !== anchor) throw new CanonicalInboxError('cursor-anchor-mismatch');
applyTip(stack, e);
await client.query('INSERT INTO wk_custody.custody_meta(key,value) VALUES($1,$2)', [eventKey(e.sequence), JSON.stringify(e)]);
cursor = e.sequence; anchor = e.eventHash; appended = true;
cursor = e.sequence; anchor = e.eventHash; appended = true; appendedEvents.push(e);
}
priorAnchor = e.eventHash;
}
if (page.cursor !== page.events.at(-1)!.sequence || page.anchor !== priorAnchor) throw new CanonicalInboxError('cursor-anchor-mismatch');
return { streamId: page.streamId, cursor, head: Math.max(page.head, cursor), height: stack.length,
tip: stack.at(-1) ?? GENESIS_HASH, replayed: !appended };
tip: stack.at(-1) ?? GENESIS_HASH, replayed: !appended, appendedEvents };
}
/** Full append-only inbox verification. Expected to run under the caller's coherent audit snapshot. */