Context
PR #16962 replaces MailboxService.listMessages() cache-wide edge enumeration with the Native Edge Graph Store's existing source and target secondary indexes. That repair is correct at exact head e25f972812: Database.beforeSetEdges() constructs both indexes, the focused mailbox/Fleet slice passes 168/168, and all hosted checks are green.
The cross-family review identified a changed failure direction worth carrying separately rather than returning the performance repair for another cycle. An exact-object probe demonstrated it:
store shape raw matching edges target-index result
unindexed Graph Store 1 0
source+target indexed Store 1 1
Store.getByIndex() returns [] when the index set, property map, or value set is absent. A missing configured index is therefore indistinguishable from an honestly empty lookup. Before #16962, the list path was slow but still walked the raw edge store; after #16962, loss of its index precondition can manufacture an empty mailbox.
This is not a blocker for #16962. Production constructs the required indexes today. It is a follow-up contract-hardening leaf under #16677.
The Problem
The indexed mailbox path depends on two facts that are currently positional rather than asserted:
- the edge Store was constructed with both
source and target indexes; and
- callers can distinguish an unavailable index from a valid index with no matching value.
Today getByIndex() collapses both states to []. A future alternate Store construction, test double, or configuration regression can therefore turn a lookup failure into a plausible empty inbox. That is a more dangerous failure mode than the superseded full scan because callers cannot distinguish "there are no messages" from "the mailbox could not look."
One sibling remains on the old path: getBroadcastDeliveryEdges() still filters GraphService.db.edges.items and is reached by single-message authorization, mark-read, and archive paths. Those calls are not the quadratic hot path fixed by #16962, but they share the same delivery-edge projection and can now use the established source-index idiom. Keeping one scan-based and one indexed projection also leaves two failure semantics for the same graph fact.
The Architectural Reality
ai/graph/Store.mjs#getByIndex() owns secondary-index lookup. Its JSDoc says the property is assumed to exist, but its implementation silently returns [] when that assumption is false.
ai/graph/Database.mjs#beforeSetEdges() is the production construction authority and configures [{property:'source'}, {property:'target'}].
ai/services/memory-core/MailboxService.mjs#listMessages() at PR #16962 consumes both indexes for candidate discovery and per-message projection.
MailboxService.mjs#getBroadcastDeliveryEdges() still enumerates db.edges.items for single-message delivery-state consumers.
- The targeted Agent OS structure maps place the contract in the existing
ai/graph/Store.mjs / Database.mjs owners and the consumer assertion/projection in the existing MailboxService.mjs; no new file or service is warranted. The whole-ai/ structure-map command currently exceeds Node's maximum string length, while targeted ai/graph and ai/services/memory-core maps complete.
The Fix
- Give the Graph Store public lookup surface an explicit distinction between index unavailable and indexed value absent. The implementation may expose an assertion/query helper or fail loudly inside
getByIndex() for an unknown property, but MailboxService must not reach into indexMaps directly.
- Assert the edge Store's
source and target index contract before mailbox routing can return a result.
- Convert
getBroadcastDeliveryEdges(messageId) to the existing source index and preserve its DELIVERED_TO filtering and first-match identity semantics at every current caller.
- Bind the changed failure mode with mutation-sensitive controls: a matching raw edge plus a removed/missing index must produce an explicit bounded failure, while a present index with no matching value must still return
[].
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
Store.getByIndex(property, value) |
Store.indices / secondary-index maps |
Unknown or unavailable index is explicit; valid index with no value returns [] |
No raw-store scan |
JSDoc on lookup contract |
Indexed/unindexed exact-object pair |
Database.edges construction |
Database.beforeSetEdges() |
source and target remain mandatory edge indexes |
Construction/config failure is loud |
Existing method JSDoc |
Production-construction control |
MailboxService.listMessages() |
Graph Store lookup contract |
Cannot report an empty mailbox when either required index is unavailable |
Bounded tool error; never manufactured empty success |
Local Anchor and Echo |
Delete-index mutation with a matching message |
getBroadcastDeliveryEdges() and its callers |
Message-source edge index |
Source-index projection preserves delivery authorization/read/archive semantics |
No cache-wide scan |
Helper JSDoc |
Poisoned full-store iterator plus behavior controls |
Decision Record Impact
none. This hardens the existing Native Edge Graph secondary-index contract and MailboxService ownership; it does not introduce a new architecture or public mailbox schema.
Acceptance Criteria
Out of Scope
- Adding a new secondary index or changing edge persistence.
- Falling back to a full edge-store scan when an index is unavailable.
- Changing mailbox request/response schemas, pagination, or read-state semantics.
- Refactoring unrelated graph-index consumers.
- A timing benchmark; structural mutation witnesses remain the evidence shape.
Avoided Traps
- Fallback scan for safety. It hides the broken precondition and reintroduces the cost
#16960 removed.
- Mailbox reaches into
indexMaps. That duplicates Store internals in a consumer and makes later index implementation changes unsafe.
- Treat
[] as one universal state. Genuine no-match and unavailable instrumentation need distinct outcomes.
- Return
#16962 for scope expansion. The shipped performance leaf is correct; this changed failure mode is a successor contract.
- Split two one-file-adjacent observations into micro-tickets. The remaining delivery scan and index assertion share the same Store/Mailbox boundary and fit one PR.
Related
Parent: #16677
Predecessor: #16960
Source review: https://github.com/neomjs/neo/pull/16962#pullrequestreview-4905950248
Adjacent historical cache-coherence repair: #10257
Duplicate and Collision Sweep
- Exact GitHub search for
mailbox index silent empty getByIndex found no existing issue.
- Knowledge Base ticket retrieval was degraded at synthesis but returned only unrelated candidates; direct repository history surfaced
#10257, which repaired cross-process cache freshness rather than secondary-index availability.
- The
#16960 predecessor owns only the repeated list-projection scan and closes with PR #16962; reopening it would violate the resolved-ticket successor rule.
- Live latest-open created-descending and recent all-state A2A claim sweeps immediately before creation found no equivalent ticket or competing lane claim.
Origin Session ID: 019fe5e8-b963-7e93-8762-c8e4af16bdec
Retrieval Hint: "MailboxService secondary index unavailable silent empty inbox getByIndex source target broadcast delivery"
Context
PR
#16962replacesMailboxService.listMessages()cache-wide edge enumeration with the Native Edge Graph Store's existingsourceandtargetsecondary indexes. That repair is correct at exact heade25f972812:Database.beforeSetEdges()constructs both indexes, the focused mailbox/Fleet slice passes 168/168, and all hosted checks are green.The cross-family review identified a changed failure direction worth carrying separately rather than returning the performance repair for another cycle. An exact-object probe demonstrated it:
Store.getByIndex()returns[]when the index set, property map, or value set is absent. A missing configured index is therefore indistinguishable from an honestly empty lookup. Before#16962, the list path was slow but still walked the raw edge store; after#16962, loss of its index precondition can manufacture an empty mailbox.This is not a blocker for
#16962. Production constructs the required indexes today. It is a follow-up contract-hardening leaf under#16677.The Problem
The indexed mailbox path depends on two facts that are currently positional rather than asserted:
sourceandtargetindexes; andToday
getByIndex()collapses both states to[]. A future alternate Store construction, test double, or configuration regression can therefore turn a lookup failure into a plausible empty inbox. That is a more dangerous failure mode than the superseded full scan because callers cannot distinguish "there are no messages" from "the mailbox could not look."One sibling remains on the old path:
getBroadcastDeliveryEdges()still filtersGraphService.db.edges.itemsand is reached by single-message authorization, mark-read, and archive paths. Those calls are not the quadratic hot path fixed by#16962, but they share the same delivery-edge projection and can now use the established source-index idiom. Keeping one scan-based and one indexed projection also leaves two failure semantics for the same graph fact.The Architectural Reality
ai/graph/Store.mjs#getByIndex()owns secondary-index lookup. Its JSDoc says the property is assumed to exist, but its implementation silently returns[]when that assumption is false.ai/graph/Database.mjs#beforeSetEdges()is the production construction authority and configures[{property:'source'}, {property:'target'}].ai/services/memory-core/MailboxService.mjs#listMessages()at PR#16962consumes both indexes for candidate discovery and per-message projection.MailboxService.mjs#getBroadcastDeliveryEdges()still enumeratesdb.edges.itemsfor single-message delivery-state consumers.ai/graph/Store.mjs/Database.mjsowners and the consumer assertion/projection in the existingMailboxService.mjs; no new file or service is warranted. The whole-ai/structure-map command currently exceeds Node's maximum string length, while targetedai/graphandai/services/memory-coremaps complete.The Fix
getByIndex()for an unknown property, butMailboxServicemust not reach intoindexMapsdirectly.sourceandtargetindex contract before mailbox routing can return a result.getBroadcastDeliveryEdges(messageId)to the existingsourceindex and preserve itsDELIVERED_TOfiltering and first-match identity semantics at every current caller.[].Contract Ledger
Store.getByIndex(property, value)Store.indices/ secondary-index maps[]Database.edgesconstructionDatabase.beforeSetEdges()sourceandtargetremain mandatory edge indexesMailboxService.listMessages()getBroadcastDeliveryEdges()and its callersDecision Record Impact
none. This hardens the existing Native Edge Graph secondary-index contract and MailboxService ownership; it does not introduce a new architecture or public mailbox schema.Acceptance Criteria
[].Database.edgesconstruction proves bothsourceandtargetindexes are present.MailboxService.listMessages()fails explicitly when either required edge index is unavailable; it never returns an empty mailbox for that state.getBroadcastDeliveryEdges()uses the source index rather than enumeratingdb.edges.items.dev.Out of Scope
Avoided Traps
#16960removed.indexMaps. That duplicates Store internals in a consumer and makes later index implementation changes unsafe.[]as one universal state. Genuine no-match and unavailable instrumentation need distinct outcomes.#16962for scope expansion. The shipped performance leaf is correct; this changed failure mode is a successor contract.Related
Parent: #16677
Predecessor: #16960
Source review: https://github.com/neomjs/neo/pull/16962#pullrequestreview-4905950248
Adjacent historical cache-coherence repair: #10257
Duplicate and Collision Sweep
mailbox index silent empty getByIndexfound no existing issue.#10257, which repaired cross-process cache freshness rather than secondary-index availability.#16960predecessor owns only the repeated list-projection scan and closes with PR#16962; reopening it would violate the resolved-ticket successor rule.Origin Session ID: 019fe5e8-b963-7e93-8762-c8e4af16bdec
Retrieval Hint: "MailboxService secondary index unavailable silent empty inbox getByIndex source target broadcast delivery"