7.7 KiB
Bounded DB-backed receive-message source design
Date: 2026-07-10
Goal
Design the smallest safe implementation slice for using copied/operator-local MsgLib.db as a future source for isphere_receive_messages.
This is a design node only. It does not add row-reading code and does not change the MCP tool surface.
Inputs
Evidence now available:
- C35 mapped
tblPersonMsg,tblMsgGroupPersonMsg,TD_SystemMessageRecord, receipt, file-record, and recent-index tables to the receive-message contract. - C36 added metadata-only
message_sources, proving candidate table availability, required/present columns, roles, and row counts without exposing message bodies, file path values, or raw rows. - Current MCP
isphere_receive_messagesis PacketReader-log backed and already supportsconversation_id,query,since,limit,include_attachment_metadata, safesource_preference, and emptycursor.
Non-goals
C37 and the next implementation slice must not:
- Add send, download, mark-read, login, hook, injection, or DB mutation behavior.
- Return raw DB rows.
- Return local
FilePathvalues from file-record tables. - Replace PacketReader as the only receive source.
- Claim production ingestion completeness.
- Print copied-DB message bodies in evidence-backed smoke output.
Proposed sidecar operation: list_messages
Add a future sidecar operation named list_messages under protocol isphere.msglib.v1.
Required args
sqlite_dll_pathdb_path
Optional args
password: default123.conversation_id: exact normalized conversation id or DB conversation key.conversation_type:direct,group,system, or empty/auto.query: bounded keyword filter over allowlisted columns only.since: ISO-8601/RFC3339 timestamp.limit: default20, clamp to1..50for the first DB row-listing slice.cursor: must be empty in the first slice.include_body: defaultfalsein sidecar verification; MCP integration may explicitly set this later after redacted/synthetic tests pass.include_attachment_metadata: defaulttrue; returns only safe attachment fields.
Returned top-level data
read_only=trueraw_rows_returned=falsefile_paths_returned=falsemessage_body_values_returned=<include_body>source_tables: unique allowlisted tables used.messages: normalized bounded message objects.next_cursor=nullin the first slice.
Normalized message fields
| Field | Required behavior |
|---|---|
message_id / id |
From table primary key: tblPersonMsg.Id, tblMsgGroupPersonMsg.Id, or TD_SystemMessageRecord.MsgGuid. |
conversation_id |
Direct: prefer ObjPersonId, fallback `SndPersonId |
conversation_type |
direct, group, or system. |
sender_id |
Direct/group: SndPersonId; System: SendPersonJid. |
sender_name |
Direct/group: SndPerson; System: SendPersonName; later can be overwritten/enriched by display-entity maps. |
receiver_id |
Direct: RecvPersonId; Group: MsgGroupId; System: null or stable system receiver. |
text / content_text |
Only when include_body=true; select MsgText first, fallback MessageBody; system messages use MsgText. |
content_type |
Derived from MsgType and safe attachment metadata; unknown when mapping is unclear. |
subject |
System: MsgTitle; direct/group: null until a DB column equivalent is proven. |
timestamp / created_at |
Direct: ActionTime; Group: MsgTime; System: MsgTime; parse into RFC3339 where possible. |
read |
Direct/group/system read-state columns mapped to boolean-like value when possible. |
receipt_id |
Null in first slice unless a safe receipt id mapping is proven; DB receipt state may remain in internal metadata only. |
attachments |
Join TD_ReceiveFileRecord by message id; include file_id, file_name, size_bytes, and null download_ref; never return FilePath. |
source |
local_readonly. |
raw_ref |
A stable non-row-dump ref such as msglib:tblPersonMsg, msglib:tblMsgGroupPersonMsg, or msglib:TD_SystemMessageRecord. |
Query and ordering policy
First DB-backed row-listing slice should:
- Read only allowlisted columns.
- Filter source tables by
conversation_typebefore querying rows when supplied. - Filter
conversation_idagainst the normalized candidate conversation key. - Filter
sinceonActionTime,MsgTime, or systemMsgTime. - Filter
queryonly over allowlisted identity/title/body columns; ifinclude_body=false, query may still use body columns internally but must not print the values. - Sort descending by timestamp, then descending by primary key/id for deterministic output.
- Clamp
limitto at most 50. - Reject non-empty
cursoruntil pagination is explicitly implemented.
Body-output policy
The first sidecar implementation may support include_body, but verification must separate two paths:
- Default/evidence-backed real copied DB smoke:
include_body=false; output summaries only, no message body values. - Synthetic/redacted fixture test path:
include_body=truemay be tested with redacted fixture values only.
Before MCP integration sets include_body=true against operator-local data, acceptance must prove:
- strict limit is enforced;
- output contains only normalized message objects, not raw rows;
- file path values are always omitted;
- audit/source fields make the DB source explicit;
- user/operator local data remains uncommitted.
Attachment policy
Attachment metadata can use TD_ReceiveFileRecord only as a safe list source:
- Join key:
ReceiveMsgGuidequals normalized message id. - Return allowed:
ReceiveMsgGuidas file/message ref,FileName,FileSize,SourceID,SourceName,FileType,SendPersonJid,SendPersonName,SendTimeonly if needed for normalized metadata. - Never return:
FilePath. download_refremains null until cache/download mapping is separately validated.
Relationship with PacketReader source
PacketReader remains the default receive source until DB-vs-log reconciliation is tested. DB-backed receive should enter in stages:
- C38: sidecar-level bounded
list_messagesoperation, with real copied DB smoke usinginclude_body=falseonly. - C39: Go wrapper/fake-sidecar tests and possibly synthetic/redacted fixture flow for
include_body=true. - C40: optional Go
ReceiveMessagesSourceimplementation behind explicit env/source configuration. - Later: decide
automerge/reconciliation policy between PacketReader and MsgLib.
The first MCP integration should not silently merge PacketReader and MsgLib rows. It should be explicit through env config or a source-mode decision recorded in docs.
C38 recommended implementation route
Implement sidecar list_messages first, not MCP integration.
C38 acceptance gate:
- Add Go fake-sidecar tests for
ListMessages(ctx, options)request shape and decoded normalized fields. - Add sidecar op
list_messageswith allowlisted table/column queries. - Default sidecar smoke still returns no DB provider checks unless env is configured.
- Evidence-backed copied DB smoke calls
list_messageswithinclude_body=falseand prints only:message_list_countmessage_list_sourcesmessage_body_values_returned=falseraw_rows_returned=falsefile_paths_returned=false
- No real copied-DB
MsgText,MessageBody,FilePath, raw rows, entity values, sends, downloads, hooks, injection, or DB writes.
Decision
Proceed to C38 with sidecar-level list_messages plus Go wrapper tests and sanitized verification. Do not wire DB-backed rows into isphere_receive_messages MCP until C38 proves the bounded sidecar behavior.