---
id: 12473
title: team memorySharing excludes the untagged summary commons → empty team summary reads
state: CLOSED
labels:
  - bug
  - ai
  - regression
assignees: []
createdAt: '2026-06-04T02:43:34Z'
updatedAt: '2026-06-04T08:17:24Z'
githubUrl: 'https://github.com/neomjs/neo/issues/12473'
author: neo-claude-opus
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-06-04T08:17:24Z'
---
# team memorySharing excludes the untagged summary commons → empty team summary reads

## Context

`query_summaries` / `list_summaries` returned empty under the `team` memorySharing policy — the intended repo default per the cloud-deployment access work (operator: "OUR own and the repo default should be team based access"). Memory-mining over session summaries was 100% non-functional on the team default.

Surfaced while investigating #12450 (the "query_summaries empty" **symptom** ticket): the operator's redirect ("explore if access related → team vs private") isolated this as a **distinct root cause** from #12450's documented Chroma vector-corruption (its Layers 1–2). This is the access-policy layer (Layer 3).

## Root cause (V-B-A'd)

Session summaries carry **no `userId` metadata** (untagged legacy-commons — the summary writer does not tag `userId`, unlike raw-memory writes). But the `team` read applied `where:{userId:'shared'}`, which matched **none** of the untagged records → empty. Only `legacy` (no DB-where + JS post-filter passing untagged) returned them. With `team` as the intended default, every summary query read empty. `query_raw_memories` is unaffected (raw writes DO tag `userId`).

## The Fix (delivered in PR #12470)

Make `team` **additive** like `legacy` — caller-owned + `'shared'` + untagged commons — via the existing over-fetch + JS post-filter path; drop the restrictive `{userId:'shared'}` DB-where. `private` stays strict (own-only). Applied to `querySummaries` + `listSummaries` in `SummaryService.mjs`. The JS post-filter is mandatory for additive policies (else other tenants' private records leak) — kept consistent via an `additivePolicy` flag.

## Contract Ledger

| Target Surface | Source of Authority | Behavior | Fallback | Evidence |
|---|---|---|---|---|
| `query_summaries` / `list_summaries` (`memorySharing:'team'`) | `SummaryService.mjs` `querySummaries`/`listSummaries` | additive: own + `'shared'` + untagged commons; no restrictive DB-where | JS post-filter excludes other-tenant private | `SummaryService.TenantIsolation.spec.mjs:363` (16/16) |
| `memorySharing:'private'` | same | strict own-only `{userId}` DB-where | — | private contract test |

## Acceptance Criteria

- [x] `team` summary reads return caller-owned + `'shared'` + untagged commons (not the empty `{userId:'shared'}` filter). *(PR #12470)*
- [x] Other-tenant private summaries excluded from `team`/`legacy` reads via the JS post-filter. *(codified test, incl. the cross-tenant assertion)*
- [x] `private` stays strict own-only. *(unchanged + tested)*
- [ ] (post-merge) After Memory Core restart, `query_summaries(memorySharing:'team')` returns non-empty for the commons.

## Scope

Access-policy only. **NOT** the Chroma HNSW corruption / `injectQueryReRanker` observability / healthcheck-canary work — those remain on #12450 (Layers 1–2).

Refs #12450 (symptom ticket; this is its access-policy Layer 3).

Origin Session ID: 3ecb40bf-bfef-40b1-8693-a8aae5afa1b7
Authored by Claude Opus 4.8 (Claude Code), /lead-role.


## Timeline

- 2026-06-04T02:43:35Z @neo-claude-opus added the `bug` label
- 2026-06-04T02:43:35Z @neo-claude-opus added the `ai` label
- 2026-06-04T02:43:35Z @neo-claude-opus added the `regression` label
- 2026-06-04T02:45:03Z @neo-claude-opus cross-referenced by PR #12470
- 2026-06-04T08:17:25Z @tobiu referenced in commit `3628ce1` - "fix(memory-core): team summary reads include the untagged commons (#12473) (#12470)

* fix(memory-core): team summary reads include the untagged commons (#12450)

query_summaries / list_summaries returned empty under the 'team' (and intended default)
memorySharing policy: the team filter applied where:{userId:'shared'}, but session
summaries carry NO userId metadata (untagged legacy-commons) despite
RequestContextService's intent to tag them 'shared'. The restrictive DB-where matched
nothing; only the legacy path (no DB-where + JS post-filter passing untagged) worked. So
with defaultPolicy='team' every summary query returns empty.

Make 'team' additive like 'legacy' (caller-owned + 'shared' + untagged commons) via the
over-fetch + JS post-filter path; drop the restrictive {userId:'shared'} DB-where.
'private' stays strict (own-only). The JS post-filter is MANDATORY for additive policies
-- without it team would return other tenants' private records unfiltered.

raw-memory reads unaffected (raw writes DO tag userId). Follow-up (separate): tag summary
writes with 'shared' + backfill so the model is explicit.

Co-Authored-By: Neo Claude Opus <neo-claude-opus@neomjs.com>

* test(memory-core): codify team=additive summary-read invariant (#12450)

The team-policy contract test still asserted the pre-fix behavior (team => shared-only, where:{userId:'shared'}), so it failed on the corrected additive change (expected 1, got 3). Rewrite it to codify the corrected invariant: team is additive (own + shared + untagged commons) with no restrictive DB-where, and -- the security assertion gpt requested -- an other-tenant private record (Bob) stays isolated via the JS post-filter.

Also rephrases 2 pre-existing decay-prone ticket refs in unrelated comments: the ticket-archaeology hook scans the whole file, so touching this spec requires clearing them to commit.

Co-Authored-By: Neo Claude Opus <neo-claude-opus@neomjs.com>

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-06-04T08:17:25Z @tobiu closed this issue

