Document readAt viewer semantics and drop dead column from SELECTs
AgentMessageRecord.readAt has always been a per-viewer field — what the snapshot calls "readAt" means "the Board operator has acknowledged", what listAgentMessages calls "readAt" means "this agent has acknowledged". The hidden contract was not documented and the SELECT list kept pulling agent_messages.read_at even though no code writes it, inviting the same bug §1.1 just fixed. Spell out the viewer-scoped contract in snapshot.ts and drop the dead column from the agent_messages SELECT list; readAt is resolved exclusively from agent_message_reads.
This commit is contained in:
1 parent
74ae5e922b
commit
b6184b5bf3
1 file changed
+14
-2
@@ -36,6 +36,13 @@ export function buildSnapshotMethods(
|
||||
const connectors = deps.readConnectors();
|
||||
const theme = (deps.getSetting("theme") as "system" | "light" | "dark" | null) ?? "system";
|
||||
const locale = (deps.getSetting("locale") as "en" | "zh" | null) ?? "en";
|
||||
// AgentMessageRecord.readAt is a per-viewer field — its meaning is
|
||||
// "has the current viewer acknowledged this message?". The renderer
|
||||
// view has exactly one human viewer (the Board operator), so the
|
||||
// renderer snapshot fills it from that reader's agent_message_reads
|
||||
// rows. Agent-side queries (listAgentMessages) resolve it against
|
||||
// the current agent's receipts. The raw agent_messages.read_at
|
||||
// column is never read on any path and stays NULL by design.
|
||||
const boardReadReceiptMap = new Map(
|
||||
readAll<{ messageId: string; readAt: string }>(
|
||||
`select reads.message_id as messageId, reads.read_at as readAt
|
||||
@@ -176,13 +183,18 @@ export function buildSnapshotMethods(
|
||||
createdAt: r.created_at, updatedAt: r.updated_at,
|
||||
})),
|
||||
agentMessages: readAll<SqliteRow>(
|
||||
`select id, company_id, from_agent_id, to_agent_id, sender_kind, channel, channel_target_id, subject, body, priority, read_at, parent_message_id, attachments_json, created_at from agent_messages${companyWhere} order by created_at desc limit ${companyId ? 1000 : 1000}`,
|
||||
// The raw `read_at` column is intentionally omitted from the
|
||||
// SELECT list. It is never written — readAt semantics live in
|
||||
// the agent_message_reads receipts table, resolved against the
|
||||
// current viewer above.
|
||||
`select id, company_id, from_agent_id, to_agent_id, sender_kind, channel, channel_target_id, subject, body, priority, parent_message_id, attachments_json, created_at from agent_messages${companyWhere} order by created_at desc limit 1000`,
|
||||
...companyArgs,
|
||||
).map((r) => ({
|
||||
id: r.id, companyId: r.company_id, fromAgentId: r.from_agent_id,
|
||||
toAgentId: r.to_agent_id, senderKind: r.sender_kind ?? "agent",
|
||||
channel: r.channel, channelTargetId: r.channel_target_id,
|
||||
subject: r.subject, body: r.body, priority: r.priority, readAt: boardReadReceiptMap.get(r.id) ?? null,
|
||||
subject: r.subject, body: r.body, priority: r.priority,
|
||||
readAt: boardReadReceiptMap.get(r.id) ?? null,
|
||||
parentMessageId: r.parent_message_id, attachmentsJson: r.attachments_json,
|
||||
createdAt: r.created_at,
|
||||
})),
|
||||
|
||||
Reference in new issue
Block a user