diff --git a/docs/modules/simulator/src.mdx b/docs/modules/simulator/src.mdx index 663a0c760..77a22016e 100644 --- a/docs/modules/simulator/src.mdx +++ b/docs/modules/simulator/src.mdx @@ -13,7 +13,7 @@ Code-first simulator API. ## Public surface -### [`AgentConnection`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/network/router.ts#L80) +### [`AgentConnection`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/network/router.ts#L84) _Interface_ @@ -358,7 +358,7 @@ export class ConversationSocket { A conversation address bound to exactly one controlled endpoint. -### [`coreEvents`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L287) +### [`coreEvents`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L284) _Variable_ @@ -863,7 +863,7 @@ export interface LinkDelivery { One committed message about to cross a directed link. -### [`LinkDown`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L151) +### [`LinkDown`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L148) _Class_ @@ -879,7 +879,7 @@ export class LinkDown extends Schema.TaggedClass()( A directed participant link transitioned from available to unavailable. -### [`LinkMessageDelayed`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L198) +### [`LinkMessageDelayed`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L195) _Class_ @@ -898,7 +898,7 @@ export class LinkMessageDelayed extends Schema.TaggedClass() Active link policies deferred one delivery by a known total duration. -### [`LinkMessageDropped`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L186) +### [`LinkMessageDropped`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L183) _Class_ @@ -917,7 +917,7 @@ export class LinkMessageDropped extends Schema.TaggedClass() An active link policy discarded one committed message before delivery. -### [`LinkMessageHeld`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L210) +### [`LinkMessageHeld`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L207) _Class_ @@ -957,7 +957,7 @@ Decides one delivery on a directed link. A policy reads only its input and the ambient Clock; the link interpreter, never the policy, spends time and records evidence. -### [`LinkPolicyCleared`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L176) +### [`LinkPolicyCleared`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L173) _Class_ @@ -974,7 +974,7 @@ export class LinkPolicyCleared extends Schema.TaggedClass()( A described policy stopped shaping one directed participant link. -### [`LinkPolicySet`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L166) +### [`LinkPolicySet`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L163) _Class_ @@ -991,7 +991,7 @@ export class LinkPolicySet extends Schema.TaggedClass()( A described policy became active on one directed participant link. -### [`LinkUp`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L160) +### [`LinkUp`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L157) _Class_ @@ -1114,7 +1114,7 @@ export class ParticipantHandle { A router-issued network identity. The hidden symbol prevents structurally similar protocol data from being used as an identity handle. -### [`ProgramFailed`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L227) +### [`ProgramFailed`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L224) _Class_ @@ -1142,7 +1142,7 @@ export class ProgramFinished extends Data.TaggedClass("ProgramFinished")<{ Customer-program completion plus its complete durable evidence. -### [`ProgramInterrupted`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L235) +### [`ProgramInterrupted`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L232) _Class_ @@ -1157,7 +1157,7 @@ export class ProgramInterrupted extends Schema.TaggedClass() The customer program was interrupted. -### [`ProgramSucceeded`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L221) +### [`ProgramSucceeded`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L218) _Class_ @@ -1187,7 +1187,7 @@ export interface ReadableRunLedger { Definition-bound read access to every committed core and customer event. -### [`ReceivedMessage`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/network/router.ts#L35) +### [`ReceivedMessage`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/network/router.ts#L39) _Interface_ @@ -1199,7 +1199,7 @@ export interface ReceivedMessage { A message delivered to one attached endpoint. -### [`RouterMessageCommitted`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L143) +### [`RouterMessageCommitted`](https://github.com/chughtapan/moltzap/blob/main/packages/simulator/src/events/core.ts#L140) _Class_ @@ -1212,8 +1212,7 @@ export class RouterMessageCommitted extends Schema.TaggedClass }), ); -it.effect( - "pairs endpoint content testimony with content-blind router commits", - () => - Effect.gen(function* () { - const evidence = yield* projectEvaluationEvidence(evaluationLedger()); +it.effect("pairs endpoint content testimony with router commits", () => + Effect.gen(function* () { + const evidence = yield* projectEvaluationEvidence(evaluationLedger()); - assert.deepStrictEqual( - evidence.social.map((entry) => entry.eventId), - [CODE_SENT_ID, CODE_RECEIVED_ID], - ); - assert.deepStrictEqual( - evidence.social.map((entry) => entry.routerCommitEventId), - [ROUTER_COMMIT_ID, ROUTER_COMMIT_ID], - ); - for (const entry of evidence.social) { - assert.strictEqual(entry.routerCommit, ROUTER_COMMIT); - assert.deepStrictEqual(entry.observation.parts, [ - { type: "text", text: SOCIAL_TEXT }, - ]); - assert.isFalse(Reflect.has(entry.routerCommit, "parts")); - } - }), + assert.deepStrictEqual( + evidence.social.map((entry) => entry.eventId), + [CODE_SENT_ID, CODE_RECEIVED_ID], + ); + assert.deepStrictEqual( + evidence.social.map((entry) => entry.routerCommitEventId), + [ROUTER_COMMIT_ID, ROUTER_COMMIT_ID], + ); + for (const entry of evidence.social) { + assert.strictEqual(entry.routerCommit, ROUTER_COMMIT); + assert.deepStrictEqual(entry.observation.parts, [ + { type: "text", text: SOCIAL_TEXT }, + ]); + } + }), ); it.effect("returns selected evidence identities in selection order", () => diff --git a/packages/evals/src/grading.test.ts b/packages/evals/src/grading.test.ts index e6b6b1cf3..04382e394 100644 --- a/packages/evals/src/grading.test.ts +++ b/packages/evals/src/grading.test.ts @@ -152,6 +152,8 @@ const promptCommit = RouterMessageCommitted.make({ messageId: promptMessage, senderId: peerId, routerSequence: routerSequence(0), + parts: peerPrompt.parts, + createdAtMillis: 0, }); const responseCommit = RouterMessageCommitted.make({ @@ -159,6 +161,8 @@ const responseCommit = RouterMessageCommitted.make({ messageId: responseMessage, senderId: targetId, routerSequence: routerSequence(1), + parts: targetResponse.parts, + createdAtMillis: 0, }); function record( @@ -372,6 +376,8 @@ describe("ledger evidence projection", () => { messageId: responseMessage, senderId: otherId, routerSequence: routerSequence(1), + parts: targetResponse.parts, + createdAtMillis: 0, }); const error = yield* transcriptFromLedger( openClawLedger(wrongResponse, wrongCommit), diff --git a/packages/simulator/src/MODULE.md b/packages/simulator/src/MODULE.md index 6a8d792c1..8e29113ca 100644 --- a/packages/simulator/src/MODULE.md +++ b/packages/simulator/src/MODULE.md @@ -8,7 +8,7 @@ Code-first simulator API. ## Public surface -### [`AgentConnection`](./network/router.ts#L80) +### [`AgentConnection`](./network/router.ts#L84) _Interface_ @@ -353,7 +353,7 @@ export class ConversationSocket { A conversation address bound to exactly one controlled endpoint. -### [`coreEvents`](./events/core.ts#L287) +### [`coreEvents`](./events/core.ts#L284) _Variable_ @@ -858,7 +858,7 @@ export interface LinkDelivery { One committed message about to cross a directed link. -### [`LinkDown`](./events/core.ts#L151) +### [`LinkDown`](./events/core.ts#L148) _Class_ @@ -874,7 +874,7 @@ export class LinkDown extends Schema.TaggedClass()( A directed participant link transitioned from available to unavailable. -### [`LinkMessageDelayed`](./events/core.ts#L198) +### [`LinkMessageDelayed`](./events/core.ts#L195) _Class_ @@ -893,7 +893,7 @@ export class LinkMessageDelayed extends Schema.TaggedClass() Active link policies deferred one delivery by a known total duration. -### [`LinkMessageDropped`](./events/core.ts#L186) +### [`LinkMessageDropped`](./events/core.ts#L183) _Class_ @@ -912,7 +912,7 @@ export class LinkMessageDropped extends Schema.TaggedClass() An active link policy discarded one committed message before delivery. -### [`LinkMessageHeld`](./events/core.ts#L210) +### [`LinkMessageHeld`](./events/core.ts#L207) _Class_ @@ -952,7 +952,7 @@ Decides one delivery on a directed link. A policy reads only its input and the ambient Clock; the link interpreter, never the policy, spends time and records evidence. -### [`LinkPolicyCleared`](./events/core.ts#L176) +### [`LinkPolicyCleared`](./events/core.ts#L173) _Class_ @@ -969,7 +969,7 @@ export class LinkPolicyCleared extends Schema.TaggedClass()( A described policy stopped shaping one directed participant link. -### [`LinkPolicySet`](./events/core.ts#L166) +### [`LinkPolicySet`](./events/core.ts#L163) _Class_ @@ -986,7 +986,7 @@ export class LinkPolicySet extends Schema.TaggedClass()( A described policy became active on one directed participant link. -### [`LinkUp`](./events/core.ts#L160) +### [`LinkUp`](./events/core.ts#L157) _Class_ @@ -1109,7 +1109,7 @@ export class ParticipantHandle { A router-issued network identity. The hidden symbol prevents structurally similar protocol data from being used as an identity handle. -### [`ProgramFailed`](./events/core.ts#L227) +### [`ProgramFailed`](./events/core.ts#L224) _Class_ @@ -1137,7 +1137,7 @@ export class ProgramFinished extends Data.TaggedClass("ProgramFinished")<{ Customer-program completion plus its complete durable evidence. -### [`ProgramInterrupted`](./events/core.ts#L235) +### [`ProgramInterrupted`](./events/core.ts#L232) _Class_ @@ -1152,7 +1152,7 @@ export class ProgramInterrupted extends Schema.TaggedClass() The customer program was interrupted. -### [`ProgramSucceeded`](./events/core.ts#L221) +### [`ProgramSucceeded`](./events/core.ts#L218) _Class_ @@ -1182,7 +1182,7 @@ export interface ReadableRunLedger { Definition-bound read access to every committed core and customer event. -### [`ReceivedMessage`](./network/router.ts#L35) +### [`ReceivedMessage`](./network/router.ts#L39) _Interface_ @@ -1194,7 +1194,7 @@ export interface ReceivedMessage { A message delivered to one attached endpoint. -### [`RouterMessageCommitted`](./events/core.ts#L143) +### [`RouterMessageCommitted`](./events/core.ts#L140) _Class_ @@ -1207,8 +1207,7 @@ export class RouterMessageCommitted extends Schema.TaggedClass ); })); -test("keeps router commitment evidence content-blind", () => +test("carries the committed message body on the commitment", () => Effect.gen(function* () { const committed = yield* coreEvents.decode({ _tag: "moltzap.router-message-committed/v1", @@ -218,21 +218,23 @@ test("keeps router commitment evidence content-blind", () => messageId: MESSAGE_ID, senderId: AGENT_ID, routerSequence: 0, + parts: [{ type: "text", text: "router plaintext" }], + createdAtMillis: 1_754_000_000_000, }); - const contentBearing = yield* coreEvents + const bodiless = yield* coreEvents .decode({ _tag: "moltzap.router-message-committed/v1", conversationId: CONVERSATION_ID, messageId: MESSAGE_ID, senderId: AGENT_ID, routerSequence: 0, - parts: [{ type: "text", text: "router plaintext" }], }) .pipe(Effect.either); assert.instanceOf(committed, RouterMessageCommitted); + assert.lengthOf(committed.parts, 1); assert.isTrue( - Either.match(contentBearing, { + Either.match(bodiless, { onLeft: () => true, onRight: () => false, }), diff --git a/packages/simulator/src/events/core.ts b/packages/simulator/src/events/core.ts index 68b0d86b5..098eb4ecd 100644 --- a/packages/simulator/src/events/core.ts +++ b/packages/simulator/src/events/core.ts @@ -136,10 +136,7 @@ export class EndpointMessageReceived extends Schema.TaggedClass()( "moltzap.router-message-committed/v1", { diff --git a/packages/simulator/src/network/driver.test.ts b/packages/simulator/src/network/driver.test.ts index 71882147d..d4289ea2d 100644 --- a/packages/simulator/src/network/driver.test.ts +++ b/packages/simulator/src/network/driver.test.ts @@ -15,6 +15,7 @@ import { networkError, routerSequence, type EndpointTransport, + type MessageParts, } from "../network.js"; import { Duration, Effect, Exit, Layer, Schema, Scope, Stream } from "effect"; import { describe, expect } from "vitest"; @@ -31,6 +32,8 @@ const STARTUP_TIMEOUT = Duration.seconds(10); const ROUTER_URL = serverBaseUrl("http://127.0.0.1:43100"); const CONVERSATION_ID = conversationId("00000000-0000-4000-8000-000000000102"); const MESSAGE_ID = messageId("00000000-0000-4000-8000-000000000103"); +const MESSAGE_PARTS: MessageParts = [{ type: "text", text: "committed" }]; +const CREATED_AT_MILLIS = 1_700_000_000_000; const agentName = Schema.decodeSync(agentNameSchema); const ALICE = agentName("alice"); const PROBE = agentName("probe"); @@ -65,6 +68,8 @@ function harness(): Harness { messageId: MESSAGE_ID, senderId: id(1), routerSequence: routerSequence(7), + parts: MESSAGE_PARTS, + createdAtMillis: CREATED_AT_MILLIS, }, ]); const driver: RouterDriver = { @@ -170,6 +175,8 @@ describe("MoltZap router", () => { messageId: MESSAGE_ID, senderId: id(1), routerSequence: routerSequence(7), + parts: MESSAGE_PARTS, + createdAtMillis: CREATED_AT_MILLIS, }, ]); })); diff --git a/packages/simulator/src/network/network.test.ts b/packages/simulator/src/network/network.test.ts index 00e400b14..b09919851 100644 --- a/packages/simulator/src/network/network.test.ts +++ b/packages/simulator/src/network/network.test.ts @@ -14,6 +14,7 @@ import { routerSequence, type EndpointInbox, type EndpointTransport, + type MessageParts, type NetworkOperation, type ParticipantIds, } from "../network.js"; @@ -24,6 +25,8 @@ const id = (suffix: string) => agentId(`00000000-0000-4000-8000-${suffix.padStart(12, "0")}`); const CONVERSATION_ID = conversationId("00000000-0000-4000-8000-000000000102"); const MESSAGE_ID = messageId("00000000-0000-4000-8000-000000000103"); +const MESSAGE_PARTS: MessageParts = [{ type: "text", text: "committed" }]; +const CREATED_AT_MILLIS = 1_700_000_000_000; function makeTransport( openedWith: ParticipantIds[], onSendInput?: () => void, @@ -47,6 +50,8 @@ function stoppedRouter(): RouterStopped { messageId: MESSAGE_ID, senderId: id("1"), routerSequence: routerSequence(0), + parts: MESSAGE_PARTS, + createdAtMillis: CREATED_AT_MILLIS, }, ]); } diff --git a/packages/simulator/src/network/router.ts b/packages/simulator/src/network/router.ts index c92d8385f..459502f19 100644 --- a/packages/simulator/src/network/router.ts +++ b/packages/simulator/src/network/router.ts @@ -11,7 +11,11 @@ import { type AgentKey, type AgentName, } from "@moltzap/protocol/identity"; -import type { Message, MessageParts } from "@moltzap/protocol/message"; +import { + messagePartsSchema, + type Message, + type MessageParts, +} from "@moltzap/protocol/message"; import type { ServerBaseUrl } from "@moltzap/protocol/network"; import { type Brand, @@ -89,7 +93,7 @@ export interface AttachedEndpoint { readonly transport: EndpointTransport; } -/** Content-blind projection of one durable router commit. */ +/** One durable router commit: identity, order, plaintext parts, commit time. */ export class CommittedRouterMessage extends Schema.Class( "CommittedRouterMessage", )({ @@ -97,6 +101,8 @@ export class CommittedRouterMessage extends Schema.Class messageId: messageId, senderId: agentId, routerSequence: routerSequenceSchema, + parts: messagePartsSchema(), + createdAtMillis: Schema.NonNegativeInt, }) {} /** diff --git a/packages/simulator/src/network/server/messages.test.ts b/packages/simulator/src/network/server/messages.test.ts index ed68f9aa6..ec0543a44 100644 --- a/packages/simulator/src/network/server/messages.test.ts +++ b/packages/simulator/src/network/server/messages.test.ts @@ -1,7 +1,7 @@ /** - * @file Pins the message store reader to the committed-message identity - * projection. The fixture intentionally has no payload, timestamp, deletion, - * reply, encryption, or dispatch columns. + * @file Pins the message store reader to the committed-message projection: + * identity, plaintext body, and commit time. The fixture intentionally has no + * deletion, reply, encryption, or dispatch columns. */ /* eslint-disable agent-code-guard/async-keyword, agent-code-guard/promise-type, agent-code-guard/no-raw-sql, sonarjs/assertions-in-tests, max-nested-callbacks -- PGlite exposes a promise-native fixture API; fixture SQL and Effect-wrapped assertions are local to this projection regression. */ // @agent-code-guard/regression-only: the minimal table shape is the invariant under test @@ -10,7 +10,11 @@ import { NodeContext } from "@effect/platform-node"; import { it as effectIt } from "@effect/vitest"; import { PGlite } from "@electric-sql/pglite"; import { agentId, conversationId, messageId } from "@moltzap/protocol/testing"; -import { CommittedRouterMessage, routerSequence } from "../../network.js"; +import { + CommittedRouterMessage, + routerSequence, + type MessageParts, +} from "../../network.js"; import { Effect } from "effect"; import { assert, describe } from "vitest"; import { @@ -28,13 +32,31 @@ const CONVERSATION_1 = conversationId("00000000-0000-4000-8000-000000000401"); const CONVERSATION_2 = conversationId("00000000-0000-4000-8000-000000000402"); const SENDER_1 = agentId("00000000-0000-4000-8000-000000000501"); const SENDER_2 = agentId("00000000-0000-4000-8000-000000000502"); +const BODY_1: MessageParts = [{ type: "text", text: "first body" }]; +const BODY_2: MessageParts = [{ type: "text", text: "second body" }]; +const BODY_1_JSON = JSON.stringify(BODY_1); +const BODY_2_JSON = JSON.stringify(BODY_2); +// The parts schema admits at most ten parts, so an eleventh is a rejection at +// the SQL boundary rather than a truncation. +const OVERSIZED_BODY_JSON = JSON.stringify( + [...Array.from({ length: 11 }).keys()].map((index) => ({ + type: "text", + text: `part ${String(index)}`, + })), +); +const CREATED_AT_1 = "2026-01-01T00:00:01.000Z"; +const CREATED_AT_2 = "2026-01-01T00:00:02.000Z"; +const CREATED_AT_MILLIS_1 = Date.parse(CREATED_AT_1); +const CREATED_AT_MILLIS_2 = Date.parse(CREATED_AT_2); const MESSAGES_DDL = ` CREATE TABLE messages ( id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, sender_id TEXT NOT NULL, - seq BIGINT NOT NULL + seq BIGINT NOT NULL, + parts JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL ) `; @@ -43,11 +65,13 @@ type SeedRow = readonly [ conversationId: string, senderId: string, sequence: number, + parts: string, + createdAt: string, ]; const VALID_ROWS: readonly SeedRow[] = [ - [MESSAGE_2, CONVERSATION_2, SENDER_2, 2], - [MESSAGE_1, CONVERSATION_1, SENDER_1, 1], + [MESSAGE_2, CONVERSATION_2, SENDER_2, 2, BODY_2_JSON, CREATED_AT_2], + [MESSAGE_1, CONVERSATION_1, SENDER_1, 1, BODY_1_JSON, CREATED_AT_1], ]; const EXPECTED_MESSAGES = [ @@ -56,12 +80,16 @@ const EXPECTED_MESSAGES = [ conversationId: CONVERSATION_1, senderId: SENDER_1, routerSequence: routerSequence(1), + parts: BODY_1, + createdAtMillis: CREATED_AT_MILLIS_1, }), CommittedRouterMessage.make({ messageId: MESSAGE_2, conversationId: CONVERSATION_2, senderId: SENDER_2, routerSequence: routerSequence(2), + parts: BODY_2, + createdAtMillis: CREATED_AT_MILLIS_2, }), ]; @@ -74,7 +102,7 @@ async function seedMessages( await db.exec(MESSAGES_DDL); for (const row of rows) { await db.query( - "INSERT INTO messages (id, conversation_id, sender_id, seq) VALUES ($1, $2, $3, $4)", + "INSERT INTO messages (id, conversation_id, sender_id, seq, parts, created_at) VALUES ($1, $2, $3, $4, $5, $6)", [...row], ); } @@ -92,9 +120,14 @@ const readSeededMessages = (prefix: string, rows: readonly SeedRow[]) => return yield* readCommittedRouterMessages(databasePath); }).pipe(Effect.provide(NodeContext.layer)); +const assertRejection = (pattern: RegExp) => (failure: unknown) => + Effect.sync(() => { + assert.match(String(failure), pattern); + }); + describe("committed-message projection", () => { it( - "reads only committed-message identity in sequence order", + "reads committed message bodies in sequence order", () => readSeededMessages("moltzap-pglite-", VALID_ROWS).pipe( Effect.tap((messages) => @@ -110,14 +143,29 @@ describe("committed-message projection", () => { "rejects an invalid router sequence at the SQL boundary", () => readSeededMessages("moltzap-pglite-invalid-", [ - [MESSAGE_1, CONVERSATION_1, SENDER_1, -1], + [MESSAGE_1, CONVERSATION_1, SENDER_1, -1, BODY_1_JSON, CREATED_AT_1], ]).pipe( Effect.flip, - Effect.tap((failure) => - Effect.sync(() => { - assert.match(String(failure), /RouterSequence|non-negative/u); - }), - ), + Effect.tap(assertRejection(/RouterSequence|non-negative/u)), + ), + PGLITE_TEST_TIMEOUT_MS, + ); + + it( + "rejects a malformed parts row at the SQL boundary", + () => + readSeededMessages("moltzap-pglite-parts-", [ + [ + MESSAGE_1, + CONVERSATION_1, + SENDER_1, + 1, + OVERSIZED_BODY_JSON, + CREATED_AT_1, + ], + ]).pipe( + Effect.flip, + Effect.tap(assertRejection(/parts|maxItems|at most/u)), ), PGLITE_TEST_TIMEOUT_MS, ); diff --git a/packages/simulator/src/network/server/messages.ts b/packages/simulator/src/network/server/messages.ts index 0fd7b5226..5cf212970 100644 --- a/packages/simulator/src/network/server/messages.ts +++ b/packages/simulator/src/network/server/messages.ts @@ -30,12 +30,16 @@ export function messageDatabasePathForVolume( return MessageDatabasePathBrand(join(volumeRoot, SERVER_PGLITE_DIR)); } +// PGlite returns a JSONB column pre-parsed, so `parts` reaches the schema as the +// decoded array rather than text. const MESSAGES_QUERY = ` SELECT id AS "messageId", conversation_id AS "conversationId", sender_id AS "senderId", - seq::double precision AS "routerSequence" + seq::double precision AS "routerSequence", + parts, + floor(extract(epoch FROM created_at) * 1000)::double precision AS "createdAtMillis" FROM messages ORDER BY seq `; diff --git a/packages/simulator/src/network/server/process.test.ts b/packages/simulator/src/network/server/process.test.ts index 36cc22fb5..f726d2a5a 100644 --- a/packages/simulator/src/network/server/process.test.ts +++ b/packages/simulator/src/network/server/process.test.ts @@ -27,6 +27,7 @@ import { RouterProvider, routerSequence, type EndpointTransport, + type MessageParts, } from "../router.js"; import { routerProviderLayer } from "../driver.js"; import { @@ -56,12 +57,16 @@ const PROBE_KEY = redactedAgentKey(agentKeyString(42)); const CONVERSATION_ID = conversationId("00000000-0000-4000-8000-000000000003"); const MESSAGE_ID = messageId("00000000-0000-4000-8000-000000000004"); +const MESSAGE_PARTS: MessageParts = [{ type: "text", text: "committed" }]; + const committedMessages = [ { conversationId: CONVERSATION_ID, messageId: MESSAGE_ID, senderId: ALICE_ID, routerSequence: routerSequence(7), + parts: MESSAGE_PARTS, + createdAtMillis: 1_700_000_000_000, }, ];