LearnNewsExamplesServices
Frontmatter
id16086
titleExpose mailbox read-state diagnosis from container deployments
stateClosed
labels
enhancementaitestingarchitecture
assigneesneo-gpt
createdAtJul 28, 2026, 1:46 PM
updatedAtJul 28, 2026, 5:38 PM
githubUrlhttps://github.com/neomjs/neo/issues/16086
authorneo-gpt
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 28, 2026, 5:38 PM

Expose mailbox read-state diagnosis from container deployments

Closed Backlog/active-chunk-10 enhancementaitestingarchitecture
neo-gpt
neo-gpt commented on Jul 28, 2026, 1:46 PM

Context

PR #16085 adds the carrier-aware, read-only mailbox probe from #16084. It is intentionally a filesystem forensic instrument: the caller names a directly accessible SQLite file, and the script does not load active configuration or mutate the incident specimen.

That boundary is correct for local and offline forensics, but it does not reach a deployment that exposes Memory Core through MCP while providing neither host nor Docker-shell access. The diagnostic cannot assume that the Memory Core server and its SQLite owner share a process, container, or filesystem. When the database lives in another container, only the database-owning boundary can read it directly; an external operator or agent still cannot invoke the CLI against its filesystem.

The follow-up must preserve #16085's exact carrier semantics while moving only the observation path across the existing service boundary. It must not fold cloud transport work into #16085 or create a second implementation that can drift from the local classifier.

The Problem

Mailbox read state has two durable carriers:

  • a direct message stores readAt on the MESSAGE node;
  • a receipt-backed broadcast stores the affected recipient's readAt on its DELIVERED_TO edge.

PR #16085 classifies those routes correctly, including missing messages, missing recipient carriers, explicit unread state, read timestamps, malformed storage, and conflicting topology. Today that classification is coupled to the CLI's explicit SQLite open. The CLI also carries a local copy of mailbox identity comparison logic, so a future server-side implementation that independently re-derives the same rules would recreate the drift class caught during #16085 review.

Ordinary mailbox reads are the wrong carrier for this diagnostic. get_message and list_messages serve every local and remote topology and may run integrity repair as part of normal read behavior. Adding incident capture, retries, or diagnostic timeout budget there would burden healthy non-container deployments and could erase the evidence before it is classified.

Without an inside-out path, the most important deployment topology can report only the symptom ("ghost" or resurfaced wake), not the carrier state that distinguishes a missing receipt from a read-path or attribution defect.

The Architectural Reality

  • ai/scripts/diagnostics/mailboxReadStateProbe.mjs owns the explicit-path, read-only CLI adapter created by #16084.
  • ai/services/memory-core/MailboxService.mjs owns mailbox identity comparison, route authorization, graph repair, and access to the live in-process graph.
  • ai/mcp/server/memory-core/toolService.mjs already exposes inspect_deployment as a read-only, deployment-diagnostic surface; get_deployment_state_snapshot remains the plain snapshot alias.
  • ai/mcp/server/memory-core/openapi.yaml owns the transport-visible diagnostic contract.
  • PR #16079 established the surface-budget precedent: memoryWalDrain was folded into an existing diagnostic read rather than allocated a new MCP tool.
  • Structure-map gate: ai/services/memory-core/helpers/ is the established owner for pure, injectable Memory Core classifiers. classifyRepairResidue.mjs and harnessClassifier.mjs are sibling precedents.

The server-side path must observe graph state before any repair, mark-read, archive, WAL replay, or other specimen mutation. get_message, list_messages, and every non-diagnostic mailbox path remain unchanged. Deployment topology is an implementation gate, not an assumption: the PR must prove the database owner and access path before choosing where the adapter executes.

The Fix

  1. Extract the carrier/recipient classification from mailboxReadStateProbe.mjs into a pure, injectable helper under ai/services/memory-core/helpers/. The helper accepts already-read message/edge records plus the recipient and returns the stable #16084 observation envelope; it performs no I/O, repair, configuration lookup, or mutation.
  2. Keep the local CLI as an adapter: validate explicit inputs, open SQLite read-only, parse persisted records, call the shared classifier, and preserve its current result and exit-code contract.
  3. Begin the implementation PR with a topology receipt: identify the process/container that owns the graph SQLite in every supported deployment layout and the existing boundary through which it can be queried. Same-process access may use the live graph directly. A split-container layout must execute the read at the database owner or use an already-owned bounded service call—never an assumed shared path, volume mount, shell, or container exec.
  4. Add a dedicated read-only service adapter at that proven owner boundary. It reads only the bounded message/carrier rows and calls the shared classifier; it must not call ordinary mailbox reads or any integrity-repair path.
  5. Extend the existing inspect_deployment diagnostic read with an optional mailbox read-state request. With no request, its current deployment-snapshot response stays shape-compatible. With a request, it adds the classifier envelope. Keep get_deployment_state_snapshot, get_message, and list_messages unchanged.
  6. Apply bound-identity and CAN_READ_INBOX_OF authorization to the requested recipient. The message-scoped branch fails closed when identity is unavailable; the ordinary deployment snapshot remains unaffected.
  7. Keep execution bounded: no model call, polling, retry loop, sleep, or new/heavier timeout. Record p50, p95, and max timings for the actual supported container topology and prove the diagnostic completes within the existing tool timeout without raising it. Document the optional request/response and prove identical classification through the stdio and HTTP-streamable inspect_deployment facades.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Shared carrier classifier MailboxService carrier/identity rules; #16084 result contract Purely classify direct MESSAGE and broadcast DELIVERED_TO state from injected records Stable malformed/conflict/missing observations; never repair helper JSDoc mutation tests plus direct/broadcast state matrix
Local CLI adapter mailboxReadStateProbe.inspectMailboxReadState Retain explicit --db-path and read-only SQLite behavior while delegating classification Existing input-error / open-error execution envelope CLI help and module JSDoc existing #16084 suite remains green; byte-identical DB control
Deployment topology runtime/compose ownership of the graph store Execute at the proven database owner; split-container layouts cross only an existing owned service boundary fail closed when no safe owner path exists PR topology receipt compose/runtime evidence plus same/split-container falsifier
Owner-side service adapter proven graph database owner plus MailboxService authorization rules Read only the named message and carrier rows, then call the shared classifier outside normal mailbox-read paths return observation/error; never invoke repair method JSDoc damaged-topology controls plus no-repair spy
Remote diagnostic contract existing inspect_deployment tool and OpenAPI operation Add an opt-in mailbox classification projection without allocating a tool absent request preserves the existing snapshot response openapi.yaml parameter and response schema schema test plus stdio/HTTP parity
Non-diagnostic isolation existing get_message, list_messages, and get_deployment_state_snapshot contracts No code-path, schema, timeout, or response change current behavior N/A unchanged-contract regression controls
Authorization RequestContextService; MailboxService permission rules Restrict observation to the bound recipient or an explicitly permitted inbox fail closed for the optional message branch OpenAPI description own-inbox, delegated-inbox, and unauthorized controls
Read-only/latency boundary #16084 classifier contract; existing diagnostic timeout Bounded owner-side graph reads plus pure classification only fail within the existing timeout; never raise it Anchor-and-Echo JSDoc graph-state comparison, forbidden-call spies, and p50/p95/max receipt

Decision Record impact

none — this extends the existing deployment read-observe surface and reuses the accepted mailbox carrier contract; it does not change service authority or introduce a new transport/tool boundary.

Acceptance Criteria

  • One pure helper is the single source of carrier-aware classification for both the explicit-path CLI and the in-process Memory Core read; recipient normalization and route rules are not duplicated between adapters.
  • The #16084 CLI retains mandatory explicit database selection, read-only/query-only SQLite access, stable result states, and byte-identical no-mutation evidence.
  • The owner-side read-state adapter reads only the named message/carrier records and never calls getMessage(), listMessages(), repairMessageGraphIntegrity(), or another mutating/repair path.
  • The PR proves the database-owning process/container for each supported deployment layout. If SQLite is split from Memory Core, the read executes at the database owner or crosses an existing bounded service contract; it never assumes shared filesystem access, a shell, docker exec, or a new volume mount.
  • inspect_deployment accepts an optional mailbox read-state request; without it, the current deployment-snapshot response stays compatible.
  • Diagnostic mode returns the same direct/broadcast/missing/unread/read/malformed/conflicting classifications as the CLI and performs no mark-read, archive, repair, WAL replay, or graph write.
  • get_message, list_messages, and get_deployment_state_snapshot remain unchanged for Docker and non-Docker deployments, including response schemas and timeout behavior.
  • The optional diagnostic uses no model call, poll/retry loop, sleep, or new/heavier timeout. A deployment-level receipt records p50, p95, and max completion times on the actual same-container or split-container path and proves it fits the existing tool timeout.
  • The diagnostic is reachable through both stdio and HTTP-streamable Memory Core transports via inspect_deployment; no new MCP tool is added.
  • Bound-identity and delegated-inbox authorization are enforced, with a negative control proving an unrelated caller cannot inspect another recipient's carrier state.
  • The OpenAPI request/response contract, tool-schema smoke coverage, pure-classifier unit matrix, CLI adapter tests, and transport-parity tests all land in the same PR.

Out of Scope

  • Naming or fixing the positive resurfacing mechanism tracked by #15825.
  • Changing wake delivery, sender provenance, attribution, or default mailbox policy.
  • Replacing the explicit-path CLI from #16084; it remains the offline/local forensic adapter.
  • Docker exec, host-volume exposure, database copying, or generic remote SQLite browsing.
  • Automatic repair or self-healing triggered by the diagnostic result.
  • Allocating a new MCP tool.

Avoided Traps

  • Docker-shell fallback: rejected because the target deployment boundary exposes no host or container shell.
  • Cross-container filesystem assumption: rejected because Memory Core may not own or mount the SQLite file; the database-owning boundary must be proven first.
  • Second classifier: rejected because independently re-derived identity/carrier rules recreate #16085's review defect by construction.
  • Ordinary mailbox-read option: rejected because it burdens every topology, changes get_message, and can encounter repair before incident capture.
  • Heavy diagnostic timeout: rejected because one message classification is a bounded local graph read; retries, polling, and model/network waits would turn an observation into a hot-path liability.
  • Observe after repair: rejected because ordinary mailbox reads can repair graph damage before returning, destroying the incident specimen.
  • Raw database endpoint: rejected because cloud parity requires a bounded semantic observation, not remote filesystem or SQL access.
  • New tool slot: rejected because inspect_deployment already owns remote deployment diagnostics across both MCP transports.

Related

Duplicate Sweep

  • Semantic and source-architecture sweep: no equivalent service-boundary ticket found; nearest related work is #16084 (local CLI), #16060 / PR #16079 (existing-read observability precedent), and #15825 (unresolved mechanism).
  • Local issue/discussion archaeology: no equivalent found. Closed #14797 owns the historical repair-caused resurrection defect, not deployment-boundary observation; #16084 remains the local/offline adapter.
  • Live latest-open + in-flight claim sweep: checked the latest 20 created-open issues and latest 30 all-state A2A messages at 2026-07-28T11:45:56Z; no equivalent ticket or competing claim found.

Origin Session ID: 019fa530-53d6-7271-bf05-51497720b29c

Retrieval Hint: #16085 mailbox read-state classifier inspect_deployment bounded local graph HTTP streamable no shell

Authored by @neo-gpt.

tobiu referenced in commit 437738d - "feat(ai): expose mailbox read-state diagnostic (#16086) (#16094)" on Jul 28, 2026, 5:38 PM
tobiu closed this issue on Jul 28, 2026, 5:38 PM