---
id: 10557
title: 'Memory Core: guard StorageRouter against non-iterable strategicNeighbors'
state: CLOSED
labels:
  - bug
  - ai
assignees:
  - neo-opus-ada
createdAt: '2026-05-01T08:20:00Z'
updatedAt: '2026-05-01T09:08:17Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10557'
author: neo-opus-ada
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-01T09:08:17Z'
---
# Memory Core: guard StorageRouter against non-iterable strategicNeighbors

## Context

Empirical observation in session `1f30c9d8-4a36-4be0-98a5-bd5b89289227` (2026-05-01): `get_context_frontier` MCP tool errors with `"Failed to retrieve context frontier. Message: strategicNeighbors is not iterable"`. Sibling bug surfaced alongside the chromadb tenant migration gap (#10556) but with an independent root cause; filing separately per AGENTS.md Gate 1 scoping discipline.

## The Problem

`ai/mcp/server/memory-core/managers/StorageRouter.mjs:94` has an optional-chain guard that's insufficient:

```js
const topology = GraphService.getContextFrontier();
const graphWeights = new Map();

if (topology?.strategicNeighbors) {
    topology.strategicNeighbors.forEach(n => {
        graphWeights.set(n.id, n.weight);
        if (n.semanticVectorId) graphWeights.set(n.semanticVectorId, n.weight);
    });
}
```

The `topology?.strategicNeighbors` check passes when `strategicNeighbors` exists but is non-iterable (object, plain value, or unexpected shape). `.forEach` then throws.

The `get_context_frontier` MCP tool catches and surfaces this as the user-facing error. The two-pass query re-ranker in StorageRouter degrades silently to `topologyMultiplier: 1.0` when this branch is skipped, but the error path turns it into a hard failure for direct `get_context_frontier` callers.

## The Architectural Reality

Two callsites for `GraphService.getContextFrontier()`:
1. `StorageRouter.injectQueryReRanker` — uses topology to apply Pass-2 weighting on query results
2. `get_context_frontier` MCP tool — surfaces the topology directly to agents (used at boot per AGENTS_STARTUP.md Step 6)

The shape contract for `strategicNeighbors` is implicit. Either:
- `GraphService.getContextFrontier()` is supposed to return an array but returns wrong shape under some condition
- Or the consumer's expectation (always-iterable) is wrong and the producer is allowed to return a non-array

Files in scope:
- `ai/mcp/server/memory-core/managers/StorageRouter.mjs:94` — defensive guard
- `ai/mcp/server/memory-core/services/GraphService.mjs` — producer contract verification
- Test file (canonical path): `test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.ContextFrontier.spec.mjs` (extend or new)

## The Fix

Two-line guard at the consumer site:

```js
const neighbors = topology?.strategicNeighbors;
if (Array.isArray(neighbors)) {
    neighbors.forEach(n => {
        graphWeights.set(n.id, n.weight);
        if (n.semanticVectorId) graphWeights.set(n.semanticVectorId, n.weight);
    });
}
```

Plus a defensive contract assertion in `GraphService.getContextFrontier()` if the producer is the source of the bad shape — investigation during implementation will determine which side fixes it (or both).

## Acceptance Criteria

- [ ] `StorageRouter.injectQueryReRanker` uses `Array.isArray()` check instead of bare optional-chain truthy guard
- [ ] `GraphService.getContextFrontier()` documented contract: `strategicNeighbors` is always an array (possibly empty), never undefined or non-iterable; if a regression in the producer surfaces this, fix at the producer site
- [ ] `get_context_frontier` MCP tool returns successfully on this session's local data (currently errors)
- [ ] Permanent Playwright unit test asserts: (a) re-ranker passes with `strategicNeighbors: []`, (b) re-ranker passes with non-array shape (regression coverage), (c) `get_context_frontier` end-to-end returns valid shape

## Out of Scope

- The actual semantic correctness of the strategic-neighbors weighting algorithm (Pass-2 re-ranking) — this ticket only fixes the iteration crash
- Why `getContextFrontier()` returns wrong shape under some condition — investigation belongs in implementation, but the fix is the guard regardless

## Avoided Traps

- **Add `try/catch` around the forEach** — would silently degrade re-ranking; harder to diagnose. Type guard surfaces wrong shape as observable code path.
- **Throw on non-array shape at the consumer** — propagates the same crash up; goal is graceful degradation.

## Related

- **Surfaced alongside:** #10556 (Memory Core ChromaDB legacy userId backfill)
- **Adjacent observability:** healthcheck migration block (HealthService.mjs:290+) — separate concern

Origin Session ID: 1f30c9d8-4a36-4be0-98a5-bd5b89289227
Retrieval Hint: "StorageRouter strategicNeighbors not iterable get_context_frontier"

## Timeline

- 2026-05-01T08:20:01Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-05-01T08:20:02Z @neo-opus-ada added the `bug` label
- 2026-05-01T08:20:03Z @neo-opus-ada added the `ai` label
- 2026-05-01T08:32:29Z @neo-gemini-pro cross-referenced by PR #10558
- 2026-05-01T08:50:42Z @neo-gemini-pro referenced in commit `238fb40` - "feat(ai): unify TextEmbeddingService boundary across swarm (#10003)

Migrates Knowledge Base VectorService and QueryService to consume the unified memory-core TextEmbeddingService to resolve provider divergence.

Fixes collection caching error during sync operations.

fix(memory-core): add array type guard for strategicNeighbors iteration (#10557)"
- 2026-05-01T08:50:43Z @neo-gemini-pro referenced in commit `e369daf` - "fix(memory-core): add defense-in-depth iteration guards (#10557)"
- 2026-05-01T09:08:16Z @tobiu referenced in commit `1a0e08a` - "feat(ai): unify TextEmbeddingService and guard StorageRouter (#10003, #10557) (#10558)

* feat(ai): unify TextEmbeddingService boundary across swarm (#10003)

Migrates Knowledge Base VectorService and QueryService to consume the unified memory-core TextEmbeddingService to resolve provider divergence.

Fixes collection caching error during sync operations.

fix(memory-core): add array type guard for strategicNeighbors iteration (#10557)

* fix(memory-core): add defense-in-depth iteration guards (#10557)

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-01T09:08:17Z @tobiu closed this issue
- 2026-05-01T10:06:48Z @neo-opus-ada cross-referenced by PR #10567
- 2026-05-01T10:33:47Z @neo-opus-ada cross-referenced by #10569

