323 lines
13 KiB
Markdown
323 lines
13 KiB
Markdown
# MsgLib read-only sidecar contract
|
|
|
|
Date: 2026-07-10
|
|
|
|
## Purpose
|
|
|
|
`MsgLibReadSidecar` is the bounded 32-bit .NET process used by the Go MCP layer to access copied or operator-local `MsgLib.db` files through the client-compatible `System.Data.SQLite` provider.
|
|
|
|
It exists because C26 proved the database opens with the bundled 32-bit SQLite provider and password `123`, while a 64-bit probe fails at provider loading. Go should coordinate the process; the sidecar owns only the narrow Windows/.NET DB read boundary.
|
|
|
|
## Protocol
|
|
|
|
- Protocol id: `isphere.msglib.v1`
|
|
- Transport: one JSON request on stdin, one JSON response on stdout.
|
|
- Process architecture: x86.
|
|
- Database mode: read-only only.
|
|
- Raw rows: never returned.
|
|
- Message bodies: returned only when a future caller explicitly sets `list_messages.include_body=true`; current verification and MCP defaults keep it `false`.
|
|
- DB mutation: never allowed.
|
|
|
|
Request envelope:
|
|
|
|
```json
|
|
{
|
|
"protocol": "isphere.msglib.v1",
|
|
"request_id": "example-1",
|
|
"op": "self_check",
|
|
"args": {}
|
|
}
|
|
```
|
|
|
|
Response envelope:
|
|
|
|
```json
|
|
{
|
|
"protocol": "isphere.msglib.v1",
|
|
"request_id": "example-1",
|
|
"op": "self_check",
|
|
"ok": true,
|
|
"data": {},
|
|
"error": null,
|
|
"warnings": []
|
|
}
|
|
```
|
|
|
|
## Operations
|
|
|
|
### `self_check`
|
|
|
|
Returns sidecar identity, protocol, runtime, process bitness, and capability flags.
|
|
|
|
Key response fields:
|
|
|
|
- `sidecar_name`
|
|
- `sidecar_version`
|
|
- `protocol`
|
|
- `runtime`
|
|
- `process_bits`
|
|
- `requires_process_bits`
|
|
- `db_mutation_allowed=false`
|
|
- `message_body_reads_allowed=true` for explicit `list_messages.include_body` opt-in; verification keeps it disabled.
|
|
|
|
### `schema_summary`
|
|
|
|
Validates provider load and read-only DB open, then returns schema metadata only.
|
|
|
|
Required args:
|
|
|
|
- `sqlite_dll_path`: path to bundled `System.Data.SQLite.dll`.
|
|
- `db_path`: path to a copied/operator-local `MsgLib.db`.
|
|
|
|
Optional args:
|
|
|
|
- `password`: default `123`.
|
|
- `include_counts`: default `true`; returns row counts only for target tables.
|
|
- `max_tables`: default `128`, clamped to `1..256`.
|
|
- `target_tables`: optional list; defaults to known display/message/file metadata tables.
|
|
|
|
Returned data:
|
|
|
|
- `metadata_only=true`
|
|
- `read_only=true`
|
|
- `message_body_values_returned=false`
|
|
- `sqlite_version`
|
|
- `tables`: table/view names only.
|
|
- `columns`: table column names/types only.
|
|
- `target_row_counts`: safe row counts for target tables only.
|
|
|
|
### `display_sources`
|
|
|
|
Same safe provider/DB validation as `schema_summary`, but additionally maps schema availability to product-facing display source candidates.
|
|
|
|
Returned data adds:
|
|
|
|
- `display_sources[]` entries with:
|
|
- `source`
|
|
- `table`
|
|
- `available`
|
|
- `required_columns`
|
|
- `present_columns`
|
|
|
|
Initial display sources:
|
|
|
|
- `contacts_roster` from `TD_Roster`.
|
|
- `contacts_effigy` from `TD_CustomEffigy`.
|
|
- `recent_display` from `tblRecent`.
|
|
- `person_message_names` from `tblPersonMsg`.
|
|
- `group_message_names` from `tblMsgGroupPersonMsg`.
|
|
- `work_group_auth` from `TD_WorkGroupAuth`.
|
|
- `received_file_metadata` from `TD_ReceiveFileRecord`.
|
|
- `sent_file_metadata` from `TD_SendFileRecord`.
|
|
|
|
### `display_entities`
|
|
|
|
Same safe provider/DB validation as `display_sources`, but returns bounded normalized contact/group display metadata.
|
|
|
|
Required args:
|
|
|
|
- `sqlite_dll_path`
|
|
- `db_path`
|
|
- `entity_type`: `contacts` or `groups`
|
|
|
|
Optional args:
|
|
|
|
- `password`: default `123`.
|
|
- `query`: optional string matched against allowlisted jid/name columns.
|
|
- `limit`: bounded to `1..100`; default `25`.
|
|
|
|
Returned data adds:
|
|
|
|
- `metadata_only=true`
|
|
- `read_only=true`
|
|
- `message_body_values_returned=false`
|
|
- `raw_rows_returned=false`
|
|
- `entities[]` entries with:
|
|
- `entity_type`
|
|
- `source_table`
|
|
- `jid`
|
|
- `display_name`
|
|
- `confidence`
|
|
- `matched_columns`
|
|
|
|
Initial allowlisted identity sources:
|
|
|
|
- Contacts: `TD_Roster(JId,Nick)`, `TD_CustomEffigy(PersonJid,PersonName)`, `tblRecent(PersonId,PersonName)`, `tblChatLevel(PersonId,PersonName)`, selected `tblPersonMsg` id/name pairs only.
|
|
- Groups: `TD_WorkGroupAuth(GroupJID,GroupName)`, `TD_WorkGroupAuth(ChildGroupJid,GroupName)`, selected `tblMsgGroupPersonMsg` group/session id/name pairs only.
|
|
|
|
### `message_sources`
|
|
|
|
Same safe provider/DB validation as `display_sources`, but reports whether candidate message tables are ready for a future DB-backed `isphere_receive_messages` source.
|
|
|
|
Required args:
|
|
|
|
- `sqlite_dll_path`
|
|
- `db_path`
|
|
|
|
Optional args:
|
|
|
|
- `password`: default `123`.
|
|
|
|
Returned data adds:
|
|
|
|
- `metadata_only=true`
|
|
- `read_only=true`
|
|
- `message_body_values_returned=false`
|
|
- `raw_rows_returned=false`
|
|
- `file_paths_returned=false`
|
|
- `message_sources[]` entries with:
|
|
- `source`
|
|
- `table`
|
|
- `role`
|
|
- `available`
|
|
- `required_columns`
|
|
- `present_columns`
|
|
- `row_count`
|
|
|
|
Initial message source candidates:
|
|
|
|
- `direct_messages` from `tblPersonMsg`.
|
|
- `group_messages` from `tblMsgGroupPersonMsg`.
|
|
- `system_messages` from `TD_SystemMessageRecord`.
|
|
- `group_receipts` from `TD_GroupReceiptMessage`.
|
|
- `received_file_metadata` from `TD_ReceiveFileRecord`.
|
|
- `recent_conversation_index` from `tblRecent`.
|
|
|
|
### `list_messages`
|
|
|
|
Same safe provider/DB validation as `message_sources`, but returns a bounded normalized message list for future DB-backed receive-message work. C38 keeps this at sidecar/Go-wrapper level only; it is not wired into MCP receive by default.
|
|
|
|
Required args:
|
|
|
|
- `sqlite_dll_path`
|
|
- `db_path`
|
|
|
|
Optional args:
|
|
|
|
- `password`: default `123`.
|
|
- `conversation_id`: optional metadata filter.
|
|
- `conversation_type`: `auto`, `direct`, `group`, or `system`; default `auto`.
|
|
- `query`: optional metadata filter. Body columns are searched only when `include_body=true`.
|
|
- `since`: optional timestamp lower bound using the DB timestamp column representation.
|
|
- `cursor`: unsupported initially; any non-empty value is rejected.
|
|
- `limit`: bounded to `1..50`; default `20`.
|
|
- `include_body`: default `false`.
|
|
- `include_attachment_metadata`: default `false`; returns filename/size metadata only, never path values or download refs.
|
|
|
|
Returned data adds:
|
|
|
|
- `read_only=true`
|
|
- `message_body_values_returned`: equals `include_body`.
|
|
- `raw_rows_returned=false`
|
|
- `file_paths_returned=false`
|
|
- `source_tables[]`
|
|
- `messages[]` entries with normalized fields:
|
|
- `message_id` / `id`
|
|
- `conversation_id`
|
|
- `conversation_type`
|
|
- `sender_id`
|
|
- `sender_name`
|
|
- `receiver_id`
|
|
- `text` / `content_text` (empty when `include_body=false`)
|
|
- `content_type`
|
|
- `timestamp` / `created_at`
|
|
- `subject`
|
|
- `read`
|
|
- `source=local_readonly`
|
|
- `raw_ref=msglib:<table>`
|
|
- `attachments[]` with `file_id`, `file_name`, `size_bytes`, and empty `download_ref`
|
|
- `next_cursor=null`
|
|
|
|
Initial allowlisted message sources:
|
|
|
|
- Direct: `tblPersonMsg`.
|
|
- Group: `tblMsgGroupPersonMsg`.
|
|
- System: `TD_SystemMessageRecord`.
|
|
- Attachment metadata: `TD_ReceiveFileRecord`, with `FilePath`/path-like values intentionally excluded.
|
|
|
|
## Security and scope rules
|
|
|
|
- The sidecar must open DB files with read-only connection settings.
|
|
- `db_path` must point to a file named `MsgLib.db`.
|
|
- The schema/display-source operations return schema, columns, counts, and display-source availability only.
|
|
- `display_entities` may return bounded contact/group `jid` and `display_name` metadata only.
|
|
- `message_sources` may return message-source table names, required/present column names, roles, availability flags, and row counts only.
|
|
- `list_messages` may return bounded normalized message metadata. It returns `text`/`content_text` only when `include_body=true`; the standard verification path uses `include_body=false`.
|
|
- It does not return file path values, attachment contents, raw row sets, sends, downloads, hooks, injection, or DB writes.
|
|
- It does not implement send, file download, mark-read, login, hooks, injection, or DB writes.
|
|
- The Go MCP layer remains the coordinator; this process is a bounded local reader.
|
|
|
|
## Current implementation
|
|
|
|
Committed C27 implementation:
|
|
|
|
- Source: `native/MsgLibReadSidecar/`.
|
|
- Build script: `scripts/build-msglib-sidecar.ps1`.
|
|
- Verification script: `scripts/verify-msglib-sidecar.ps1`.
|
|
- Default verification builds an x86 executable and checks `self_check`.
|
|
- Evidence-backed verification with C26 paths checked provider load/read-only DB open and returned 8 display-source candidates.
|
|
|
|
## Go client wrapper
|
|
|
|
Committed C28 implementation:
|
|
|
|
- Source: `internal/msglib/`.
|
|
- `ConfigFromEnv` reads `ISPHERE_MSGLIB_SIDECAR_EXE`, `ISPHERE_MSGLIB_SQLITE_DLL`, `ISPHERE_MSGLIB_DB`, and optional `ISPHERE_MSGLIB_PASSWORD`.
|
|
- `NewClient` creates a process-backed client for protocol `isphere.msglib.v1`.
|
|
- `SelfCheck` calls `self_check` and decodes sidecar identity, protocol, bitness, and read-only/no-mutation flags.
|
|
- `DisplaySources` calls `display_sources` and decodes display-source availability metadata.
|
|
- The client uses stdin/stdout JSON, request ids, `context.Context`, per-call timeout, stderr capture, and typed `SidecarError` values.
|
|
- Unit tests use a fake sidecar process; normal tests do not require real `System.Data.SQLite.dll` or copied `MsgLib.db`.
|
|
- C28 does not register a new MCP tool and does not return message bodies, file paths, raw rows, contact row values, or group row values.
|
|
|
|
Committed C29 implementation:
|
|
|
|
- Sidecar op: `display_entities`.
|
|
- Go wrapper: `DisplayEntities(ctx, DisplayEntitiesOptions)`.
|
|
- Verification script calls `display_entities` only when copied DB env paths are supplied, and prints only sanitized `entity_count` plus `source_tables` summaries.
|
|
- Evidence-backed C29 verification against the copied DB returned contact/group entity counts and source-table names without printing entity values.
|
|
- C31 adds optional `scripts/verify-msglib-mcp-enrichment.ps1` to prove MCP-level contact/group enrichment through the real sidecar while printing only counts and `msglib:<source_table>` refs. C33 extends the same optional script to create a synthetic encrypted PacketReader fixture from internally selected MsgLib contact/group metadata and verify receive-message `sender_name` plus `conversation.display_name` enrichment; the printed result remains limited to counts, booleans, and source refs.
|
|
|
|
Committed C36 implementation:
|
|
|
|
- Sidecar op: `message_sources`.
|
|
- Go wrapper: `MessageSources(ctx)`.
|
|
- Verification script calls `message_sources` only when copied DB env paths are supplied, and prints only sanitized `message_source_count` plus message-source summaries containing source/table/role/availability/row-count metadata.
|
|
- Evidence-backed C36 verification against the copied DB returned six message-source summaries without printing message body values, file path values, raw rows, or copied DB contents.
|
|
|
|
Committed C38 implementation:
|
|
|
|
- Sidecar op: `list_messages`.
|
|
- Go wrapper: `ListMessages(ctx, ListMessagesOptions)`.
|
|
- The op reads only allowlisted direct/group/system message tables and optional received-file metadata from a copied `MsgLib.db`, clamps `limit` to `1..50`, rejects non-empty cursor, and never returns raw rows or file path values.
|
|
- `scripts/verify-msglib-sidecar.ps1` calls `list_messages` only when copied DB env paths are supplied, with `include_body=false`, and prints only `message_list_count`, `message_list_sources`, safety booleans, and omission booleans.
|
|
- Evidence-backed C38 smoke returned normalized message count/source-table evidence without printing message text, body values, file paths, download refs, raw rows, sends, downloads, hooks, injection, or DB writes.
|
|
|
|
## Verification
|
|
|
|
Default verification builds and checks `self_check` only:
|
|
|
|
```powershell
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-msglib-sidecar.ps1
|
|
```
|
|
|
|
Evidence-backed provider verification is opt-in through environment variables and should use copied DB files only. The script also verifies `display_sources` and sanitized `display_entities` summaries when these variables are present:
|
|
|
|
```powershell
|
|
$env:ISPHERE_MSGLIB_SQLITE_DLL = "<path-to-System.Data.SQLite.dll>"
|
|
$env:ISPHERE_MSGLIB_DB = "<path-to-copied-MsgLib.db>"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-msglib-sidecar.ps1
|
|
```
|
|
|
|
Optional MCP enrichment smoke, including receive-message display enrichment, is also opt-in through the same copied DB environment variables:
|
|
|
|
```powershell
|
|
$env:ISPHERE_MSGLIB_SQLITE_DLL = "<path-to-System.Data.SQLite.dll>"
|
|
$env:ISPHERE_MSGLIB_DB = "<path-to-copied-MsgLib.db>"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-msglib-mcp-enrichment.ps1
|
|
```
|
|
|
|
Expected printed evidence is sanitized: contact/group counts, `msglib:<source_table>` refs, `message_source_count`, message-source table/role/row-count summaries, `receive_display_smoke=true`, populated-field booleans, and no entity values, message bodies, file path values, or raw rows.
|
|
|
|
C38 provider verification additionally prints `message_list_count`, `message_list_sources`, `message_list_body_values_returned=false`, `message_list_raw_rows_returned=false`, `message_list_file_paths_returned=false`, `message_list_content_values_omitted=true`, and `message_list_download_refs_omitted=true`.
|