diff --git a/packages/cultnet-ts/src/idunn-runtime-authority.ts b/packages/cultnet-ts/src/idunn-runtime-authority.ts index 2956cc2f..af294c3f 100644 --- a/packages/cultnet-ts/src/idunn-runtime-authority.ts +++ b/packages/cultnet-ts/src/idunn-runtime-authority.ts @@ -13,7 +13,7 @@ import { encodeRuntimePresenceHealth, type RuntimePresenceHealth, } from "./runtime-presence-health"; -import { encodeCultNetMessageForWire, parseCultNetMessage } from "./contracts"; +import { encodeCultNetMessageForWire, parseCultNetMessage, type CultNetRawDocumentRecord } from "./contracts"; import { CultNetRudpSession, decodeRudpPacket, encodeRudpPacket } from "./rudp"; import type { CultNetRudpPacket } from "./rudp"; import dgram from "node:dgram"; @@ -79,6 +79,7 @@ export type IdunnRuntimePresencePublisher = { readonly runtimeId: string; readonly runtimeInstanceId: string; readonly requiresWriteLease: boolean; + latestPresenceDocument(): CultNetRawDocumentRecord | null; publish(state: RuntimePresenceHealth["state"], detail: string): Promise; waitForWriteLease(options?: { pollIntervalMs?: number; signal?: AbortSignal }): Promise; assertWriteLease(): Promise; @@ -219,6 +220,7 @@ export function createIdunnRuntimePresencePublisher( const connectionId = 0x0d1d0002; let sequence = 0; let latestLeaseSha256: string | null = null; + let latestPresenceDocument: CultNetRawDocumentRecord | null = null; const recentWarmingProofs: string[] = []; const assertWriteLease = async () => { @@ -243,20 +245,22 @@ export function createIdunnRuntimePresencePublisher( publisherSequence: sequence, observedAtUnixMillis: Date.now(), }); + const document: CultNetRawDocumentRecord = { + schemaId: GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA, + recordKey: expected.target, + storedAt: new Date().toISOString(), + payloadEncoding: "messagepack", + payload: new Uint8Array(signed.payload), + sourceRuntimeId: expected.runtimeId, + sourceRole: "runtime-presence-health-publisher", + tags: ["cultnet.transport.rudp.v0", "runtime-presence"], + }; await publishDocument(endpoint, connectionId, { schemaVersion: "cultnet.document_put_raw.v0", messageId: `runtime-presence:${expected.target}:${options.authority.activation.runtimeInstanceId}:${sequence}`, - document: { - schemaId: GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA, - recordKey: expected.target, - storedAt: new Date().toISOString(), - payloadEncoding: "messagepack", - payload: signed.payload, - sourceRuntimeId: expected.runtimeId, - sourceRole: "runtime-presence-health-publisher", - tags: ["cultnet.transport.rudp.v0", "runtime-presence"], - }, + document, }); + latestPresenceDocument = document; if (state === "warming") { recentWarmingProofs.push(signed.canonicalSha256); if (recentWarmingProofs.length > MAX_RECENT_WARMING_PROOFS) recentWarmingProofs.shift(); @@ -268,6 +272,14 @@ export function createIdunnRuntimePresencePublisher( runtimeId: expected.runtimeId, runtimeInstanceId: options.authority.activation.runtimeInstanceId, requiresWriteLease: expected.writeLeaseRequired, + latestPresenceDocument() { + if (!latestPresenceDocument) return null; + return { + ...latestPresenceDocument, + payload: new Uint8Array(latestPresenceDocument.payload), + ...(latestPresenceDocument.tags ? { tags: [...latestPresenceDocument.tags] } : {}), + }; + }, publish, async waitForWriteLease({ pollIntervalMs = 5000, signal } = {}) { if (!expected.writeLeaseRequired) return ""; diff --git a/packages/cultnet-ts/test/idunn-runtime-authority.test.ts b/packages/cultnet-ts/test/idunn-runtime-authority.test.ts index a27499cf..6dcdc956 100644 --- a/packages/cultnet-ts/test/idunn-runtime-authority.test.ts +++ b/packages/cultnet-ts/test/idunn-runtime-authority.test.ts @@ -142,6 +142,7 @@ test("opens Idunn identity and publishes valid CultNet data while surfacing pigg let receivedMessage: unknown; let responseAck: number | undefined; let peerFailure: Error | undefined; + let rejectOdinAdmission = true; peer.on("message", (wire, remote) => { try { const packet = decodeRudpPacket(wire); @@ -156,14 +157,18 @@ test("opens Idunn identity and publishes valid CultNet data while surfacing pigg const frame = result.delivered.find((candidate) => candidate.channelId === "schema"); if (!frame) return; receivedMessage = decode(frame.payload); - const errorPayload = encode({ + const responsePayload = encode(rejectOdinAdmission ? { schemaVersion: "cultnet.error.v0", error: "test admission denied", routingHint: null, code: null, details: null, + } : { + schemaVersion: "cultnet.snapshot_response.v0", + messageId: "ack", + documents: [], }); - const [response] = serverSession.sendMany("schema", errorPayload, { + const [response] = serverSession.sendMany("schema", responsePayload, { reliable: true, ordered: true, nowMs: Date.now(), @@ -186,6 +191,18 @@ test("opens Idunn identity and publishes valid CultNet data while surfacing pigg assert.ok(Buffer.from(encode(receivedMessage)).byteLength > 0); assert.equal(responseAck, 2, "the response data packet must acknowledge the publisher's reliable schema packet"); assert.equal(peerFailure, undefined, peerFailure?.message); + assert.equal(publisher.latestPresenceDocument(), null, "a rejected publication must not become the snapshot source"); + + rejectOdinAdmission = false; + await publisher.publish("warming", "accepted presence"); + const publishedDocument = (receivedMessage as { document: { payload: Uint8Array; schemaId: string; recordKey: string } }).document; + const snapshotDocument = publisher.latestPresenceDocument(); + assert.ok(snapshotDocument); + assert.equal(snapshotDocument.schemaId, publishedDocument.schemaId); + assert.equal(snapshotDocument.recordKey, publishedDocument.recordKey); + assert.deepEqual(Buffer.from(snapshotDocument.payload), Buffer.from(publishedDocument.payload)); + snapshotDocument.payload[0] = snapshotDocument.payload[0]! ^ 0xff; + assert.deepEqual(Buffer.from(publisher.latestPresenceDocument()?.payload ?? []), Buffer.from(publishedDocument.payload), "snapshot readers cannot mutate publisher state"); descriptorProviderCredential = Buffer.from(encode([ ...(decode(providerCredential) as unknown[][]),