232 Universe Space Database Duplication Research
Documentation for Sessions
S232: Universe/Space Database Duplication — Root Cause Analysis & Fixes
Date: 2026-03-18
Session: 232
Status: Implemented
Problem Summary
Multiple issues observed in MongoDB:
- Duplicate space databases:
space_chrome-space-1andspace_chrome-space-1_79fe44376823for the same logical space - Duplicate universes in gograb-api: Multiple documents in
gograb-api.universeswith samespaceId: "chrome-space-1" - Double-prefixed collections:
universe_universe_1772912869184_wly1ktx_matchs,universe_universe_1772912869184_wly1ktx_players
Root Causes
1. Space Database Resolver — Slug vs ObjectId (PRIMARY)
Location: backend-api/src/app/spaces/space-database/services/space-database-resolver.service.ts
Cause: resolveSpaceDatabaseName(spaceId) used findById(spaceId) only. When spaceId was a slug (e.g. "chrome-space-1") instead of MongoDB ObjectId:
findById("chrome-space-1")fails (invalid ObjectId or no match)- Resolver fell back to
space_${spaceId}→space_chrome-space-1 - Meanwhile, correct path with ObjectId produced
space_chrome-space-1_79fe44376823
Result: Same space accessed via slug vs ObjectId → two different databases.
Callers passing slug:
- API routes:
/api/spaces/:spaceId/universes/...— frontend may use slug in URL spaces.service.updateUniverse— accepts slug, resolves to ObjectId internally but passes slug downstream in some paths- Workflow/deployment:
tenantId/spaceIdfrom workflow URLs
2. Entity Schema Service — Hardcoded Legacy Format
Location: backend-api/src/app/data/entity-schema.service.ts
Cause: Used space_${spaceId} directly, bypassing the resolver entirely. When spaceId was a slug, created/accessed space_chrome-space-1.
Fix: Inject SpaceDatabaseService and use resolveSpaceDatabaseName(spaceId).
3. gograb-api.universes Legacy Merge
Location: backend-api/src/app/spaces/space-database/services/space-database-universe.service.ts
Cause: listUniversesInSpace merges universes from:
- Space database
universescollection gograb-api.universeswith{ spaceId }
When spaceId was slug, gograb-api query returned legacy documents with spaceId: "chrome-space-1". E2E tests and legacy flows may have written to gograb-api.universes with slug as spaceId.
Note: Resolver fix ensures we now resolve slug → ObjectId before any DB access. The gograb-api.universes merge remains for backward compatibility but should receive consistent spaceId.
4. universe_universe_ Double Prefix (Fixed S232)
Cause: Some code path passes universeId that already includes universe_ prefix, and another layer prepends universe_ again. Example: universeId = "universe_1772912869184_wly1ktx" → universe_${universeId} → universe_universe_....
Fix: (1) collection-name.util.ts now strips all leading universe_ prefixes before adding one, ensuring exactly one prefix. (2) space-module-database.service.ts uses buildCollectionName(universeId, 'modules') instead of manual universe_${universeId}_modules. (3) Verification script checks for manual universe_${id}_ construction.
Implemented Fixes
Fix 1: Space Database Resolver — Slug Resolution
File: space-database-resolver.service.ts
- When
findById(spaceId)fails andspaceIdlooks like a slug (contains-, not 24-char hex), tryfindOne({ spaceSlug: spaceId }) - Use resolved space's ObjectId and
databaseFormat/spaceSlugfor canonical db name - Cache both slug and ObjectId → same db name
Fix 2: Entity Schema Service — Use Resolver
File: entity-schema.service.ts
- Inject
SpaceDatabaseService - Replace
space_${spaceId}withawait this.spaceDatabaseService.resolveSpaceDatabaseName(spaceId)inregisterSchemaandgetSchema
Migration Script (S232)
Run to consolidate orphan DBs and fix double-prefix collections:
yarn migrate:space-db:sot --dry-run # Preview
yarn migrate:space-db:sot # Execute
Script: tools/scripts/database/migrate-space-db-to-sot.ts
- Merges
space_{slug}→space_{slug}_{shortId}(SSoT) - Renames
universe_universe_*→universe_*in target - Drops orphan database after merge
Additional Fixes (S232)
- space-modules.service: Removed deprecated
getCollection()that usedspace_${spaceId}directly. - space-database-collections.service:
createSpaceDatabasenow usesresolveSpaceDatabaseName(spaceId)whenspaceSlugis not provided.
Cleanup Recommendations (Manual)
Orphan databases: Useyarn migrate:space-db:sot(see above).gograb-api.universes: Audit documents with
spaceId: "chrome-space-1". UpdatespaceIdto canonical ObjectId or migrate to space DB. Consider deprecating the gograb-api.universes merge.Double-prefixed collections: Migration script renamesuniverse_universe_*→universe_*.
API Audit Summary
| API | Uses Resolver | Bypasses | Notes |
|---|---|---|---|
| backend-api | data.controller, space-database-*, space-modules (getCollectionAsync), deployment.service | All space DB access now via resolver | |
| backend-ai-api | N/A (no direct space DB) | spaceAIDB uses own naming | Uses spaceId for analytics; ensure ObjectId |
| backend-vitto-api | Calls backend-api HTTP | N/A | Passes spaceId from session; ensure ObjectId from platform |
Verification Scripts (SSoT Enforcement)
Run before merge to ensure no new bypasses:
yarn verify:db:naming
Script: tools/scripts/database/verify-space-database-naming.ts
Checks:
- No direct
space_${spaceId}oruseDb('space_${...}')in backend-api (except resolver/collection-name.util) - Entity schema uses
SpaceDatabaseService.resolveSpaceDatabaseName - E2E
DatabaseHelper.getSpaceModuleusesgetSpaceDb()(canonical resolution) - backend-ai-api
getSpaceDatabaseName/getSpaceAIDatabaseNamedocument CONTRACT (canonical spaceId)
Collection naming: All MongoDB universe collections MUST use buildCollectionName(universeId, collection) from collection-name.util.ts. Manual universe_${...}_ construction risks double-prefix (universe_universe_*).
References
space-database/README.md— Architecturedocs/english/sessions/S200/200.10-architecture-how-should-gitlab-repos-be-organized...— Slug vs ID confusion (GitLab)vitto-engine.service.ts:923— Previous bug: "preferred slug over _id, causing chrome-space-1 as universeId"