get_memory_graph/neighbors used require_user() (returns "" when auth
is disabled/bypassed) while every other memory route uses
get_current_user() via a local _owner() helper (returns None in that
case). MemoryManager.load() and _verify_memory_owner() both special-case
None as "no filter / bypass" but treat "" as a real owner to match
against — and add_entry() never stamps owner="" on new entries. Net
effect: in single-user/no-auth mode the graph endpoint always returned
zero nodes and the links endpoints always 404'd. Found while seeding
real data through the API to visually verify Milestone 2 — switching
to _owner() (get_current_user) matches memory_routes.py's convention
and fixes both endpoints.
New routes/memory/memory_graph_routes.py, mounted in app.py:
- GET /api/memory/graph — owner-scoped node/edge graph (require_user,
no new privilege beyond existing GET /api/memory).
- GET /api/memory/graph/{id}/neighbors — lazy single-node expansion for
graphs beyond the response limit.
- POST /api/memory/{id}/links, DELETE /api/memory/{id}/links/{target_id}
— manual relationship editing, gated by can_manage_memory like other
memory mutations, reusing the existing 404-on-owner-mismatch pattern.
Must be included before memory_router in app.py: memory_routes.py's
GET/PUT/DELETE /api/memory/{memory_id} wildcard would otherwise swallow
GET /api/memory/graph, since Starlette matches routes in registration
order, not by specificity. A dedicated TestClient-based regression test
(test_memory_graph_route_ordering.py) locks this in — the repo's usual
"call the endpoint function directly" test style can't catch this class
of bug since it looks up routes by exact path string, not by simulating
real request matching.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Slice 2c of the route-domain reorganization (#4082/#4071, per
specs/architecture-runtime-inventory.md §6.3). Moves memory_routes.py into
routes/memory/, leaving a backward-compat sys.modules shim at the old path.
Pure file reorganization, no behavior change.
The shim uses sys.modules replacement (same pattern as the merged gallery
#4903 and research #4975 slices) so that `import routes.memory_routes`,
`from routes.memory_routes import X`, `importlib.import_module(...)`, and
the `import ... as mr` + `monkeypatch.setattr(mr, ...)` pattern used by
test_memory_routes_session_owner.py / test_memory_owner_isolation.py all
operate on the same module object the application uses.
The canonical module does NOT depend on the shim — routes/memory/
memory_routes.py imports only from services/, core/, src/, and stdlib (zero
internal routes/ coupling).
Four source-introspection test sites repointed to the new canonical path:
- test_direct_upload_limits.py
- test_upload_limits_centralized.py (two dict keys)
- test_vision_owner_scope.py
Adds tests/test_memory_routes_shim.py to pin the sys.modules shim contract
(legacy and canonical paths resolve to the same module object; monkeypatch
via legacy alias reaches the canonical module).
Verified: compileall clean; full suite 4219 passed, 3 skipped.