---
id: 12335
title: Chroma unit-test ephemeral tenant/database isolation (UNIT_TEST_MODE)
state: CLOSED
labels:
  - enhancement
  - ai
  - testing
assignees:
  - neo-opus-ada
createdAt: '2026-06-02T00:30:50Z'
updatedAt: '2026-06-07T17:18:05Z'
githubUrl: 'https://github.com/neomjs/neo/issues/12335'
author: neo-opus-ada
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-06-02T23:11:11Z'
---
# Chroma unit-test ephemeral tenant/database isolation (UNIT_TEST_MODE)

## Context

Split from #12331 (unit-test store isolation). #12331 ships the daemon-spec `os.tmpdir()` isolation + the `purgeTestCollections` reclaimer (parts 1 + 3). This ticket is the deferred **part 2**: by-construction ephemeral isolation of the unit-test ChromaDB namespace.

## The Problem

Under `UNIT_TEST_MODE`, the memory-core config names test collections `test-memory-*` / `test-session-*` (`config.template.mjs:119-120`), but they still land in the **same** `default_tenant`/`default_database` as production. `afterAll` cleanup handles the happy path; interrupted/`Ctrl-C`/CI-cancel runs and bare-`npx` bypasses leak orphans into the prod namespace (the 1,281 backlog #12331's purge clears). The purge is a *reclaimer*, not *prevention* — orphans re-accumulate between runs.

## The Fix

Route the unit-test Chroma client to an isolated **tenant/database** (chroma multi-tenancy) under `UNIT_TEST_MODE`, so test collections never enter `default_tenant`/`default_database` and the whole test namespace is droppable wholesale. Prod path stays `default_tenant`/`default_database` (unchanged). This is the chroma analogue of the graph store's already-correct `:memory:` isolation (`config.template.mjs:112`).

## The Architectural Reality

- `ai/services/memory-core/managers/ChromaManager.mjs:82` — `new ChromaClient({host, port, ssl: false})`; add `tenant`/`database` under `UNIT_TEST_MODE`. The test db must be ensured-to-exist on connect (chroma 3.x admin/create-database API) — confirm the exact mechanism (tenant vs database vs throwaway persist-dir) against chromadb 3.3.1 at implementation.
- `ai/scripts/maintenance/purgeTestCollections.mjs` (from #12331) — once isolation lands, the purge can drop the whole test tenant/db instead of enumerating `test-*` names.
- **Sensitive surface:** this mutates the prod chroma connect path (UNIT_TEST_MODE-guarded). Deserves its own focused PR + careful prod-regression verification (the reason it was split from #12331).

## Acceptance Criteria
- [ ] AC1 — Under `UNIT_TEST_MODE`, test collections resolve to a tenant/database distinct from `default_tenant`/`default_database` (statically verifiable); no test run — including crashed/`npx`-bypassed — can create collections in the prod namespace.
- [ ] AC2 — Prod path unchanged (`default_tenant`/`default_database`); prod-wipe guards (`DestructiveOperationGuard`, FATAL `UNIT_TEST_MODE` guard) intact.
- [ ] AC3 — `purgeTestCollections` can drop the whole test namespace (in addition to the name-pattern path).

## Related
- Parent: #12331 (parts 1 + 3 shipped)
- Resolves-by-prevention (with #12331's purge-run): #12143, #12180

Origin Session ID: da9a6007-1250-4363-8c15-dff69eccb3be
Retrieval Hint: "chroma unit-test ephemeral tenant database isolation UNIT_TEST_MODE default_tenant"

## Timeline

- 2026-06-02T00:30:51Z @neo-opus-ada added the `enhancement` label
- 2026-06-02T00:30:51Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-06-02T00:30:51Z @neo-opus-ada added the `ai` label
- 2026-06-02T00:30:52Z @neo-opus-ada added the `testing` label
- 2026-06-02T00:32:21Z @neo-opus-ada cross-referenced by PR #12336
- 2026-06-02T00:53:08Z @neo-opus-ada cross-referenced by #12331
- 2026-06-02T21:21:33Z @neo-opus-ada cross-referenced by PR #12397
- 2026-06-02T21:32:42Z @neo-opus-ada referenced in commit `334fe62` - "test(ai): assert engines.chroma.database in the Tier-1 shape contract (#12335)

The strict toEqual shape contract for engines.chroma in config.template.spec.mjs
must include the new database leaf. Full-suite CI surfaced this (1 failed / 2625
passed); the targeted single-spec runs masked it because they didn't exercise the
config-shape contract test."
- 2026-06-02T22:14:55Z @neo-opus-ada referenced in commit `59b7069` - "fix(ai): fail-closed guard — refuse production Chroma database under UNIT_TEST_MODE (#12335)

PR #12397 cycle-1 review surfaced a real AC1 escape hatch: under UNIT_TEST_MODE an
inherited NEO_CHROMA_DATABASE=default_database override still resolved the unit client
to the production database. resolveChromaClientConfig now throws fail-closed when
UNIT_TEST_MODE is active and the resolved database is the production name, so the
manager cannot construct or connect into the production namespace from a unit run.
Production NEO_CHROMA_DATABASE override behavior is intact outside UNIT_TEST_MODE."
- 2026-06-02T23:11:11Z @tobiu referenced in commit `eccfe22` - "feat(ai): isolate unit-test Chroma into a dedicated database (#12335) (#12397)

* feat(ai): isolate unit-test Chroma into a dedicated database (#12335)

Part 2 (prevention) of the #12331 store-isolation cluster. PR #12336 shipped the
daemon os.tmpdir isolation + the purge reclaimer (parts 1+3); this routes the
unit-test Chroma client to a dedicated, droppable test database so test
collections never enter the production default_database by construction — the
chroma analogue of the graph store's :memory: isolation.

- new ai/services/shared/vector/chromaTestIsolation.mjs: stateless ensure/drop
  helpers (lazy + injectable AdminClient) + the CHROMA_TEST_DATABASE SSOT const;
  dropChromaTestDatabase refuses to drop default_database (defense-in-depth).
- config: engines.chroma.database leaf — neo-unit-test under UNIT_TEST_MODE,
  default_database in prod (the KB ChromaManager reads only host/port, so the
  field is inert for it until separately migrated).
- ChromaManager: construct passes database to the ChromaClient; connect ensures
  the test database exists under UNIT_TEST_MODE before the first collection op.
- purgeTestCollections: --drop-test-db wholesale-drops the isolated database (AC3).

Tests: 10 new unit specs (isolation helpers + drop guard + config resolution +
client wiring); existing memory-core managers suite green (20/20).

* test(ai): assert engines.chroma.database in the Tier-1 shape contract (#12335)

The strict toEqual shape contract for engines.chroma in config.template.spec.mjs
must include the new database leaf. Full-suite CI surfaced this (1 failed / 2625
passed); the targeted single-spec runs masked it because they didn't exercise the
config-shape contract test.

* fix(ai): fail-closed guard — refuse production Chroma database under UNIT_TEST_MODE (#12335)

PR #12397 cycle-1 review surfaced a real AC1 escape hatch: under UNIT_TEST_MODE an
inherited NEO_CHROMA_DATABASE=default_database override still resolved the unit client
to the production database. resolveChromaClientConfig now throws fail-closed when
UNIT_TEST_MODE is active and the resolved database is the production name, so the
manager cannot construct or connect into the production namespace from a unit run.
Production NEO_CHROMA_DATABASE override behavior is intact outside UNIT_TEST_MODE.

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-06-02T23:11:12Z @tobiu closed this issue
- 2026-06-03T19:10:03Z @neo-claude-opus cross-referenced by #12451
- 2026-06-03T23:02:47Z @neo-claude-opus cross-referenced by #12456
- 2026-06-03T23:05:10Z @neo-claude-opus cross-referenced by #12457
- 2026-06-03T23:35:30Z @neo-gpt cross-referenced by PR #12458
- 2026-06-04T00:42:53Z @neo-opus-ada cross-referenced by #12435
- 2026-06-04T08:32:15Z @neo-claude-opus cross-referenced by PR #12481
- 2026-06-04T11:10:15Z @neo-claude-opus cross-referenced by #12491
- 2026-06-06T00:09:58Z @neo-opus-ada cross-referenced by PR #12605
- 2026-06-06T04:49:45Z @neo-opus-ada cross-referenced by PR #12618
- 2026-06-06T12:07:11Z @neo-opus-vega cross-referenced by #12631
- 2026-06-06T22:38:07Z @neo-claude-opus cross-referenced by PR #12660
- 2026-06-07T13:35:50Z @neo-claude-opus cross-referenced by #12686

