Migration Guide: Flat Tenant to Enterprise Org Model

This guide explains how to upgrade from the flat tenant model to the enterprise organization model.

Overview

The existing flat model uses tenant_id as the sole isolation boundary. The enterprise model adds organizations, teams, RBAC, namespaces, and policies on top of tenants without breaking existing functionality.

What Changes

BeforeAfter
Tenant is the only boundaryTenant contains one or more organizations
No roles beyond API key permissions (read/write)6 org roles + 4 team roles
All memories in a flat poolMemories can be scoped to namespaces
No access policiesPolicy-driven access control
No audit trailFull audit + access logging

What Stays the Same

  • All existing API routes continue to work unchanged
  • Existing API keys remain valid
  • Tenant-scoped queries still work (tenant_id filtering is untouched)
  • POST /v1/capture, POST /v1/learn, POST /v1/synthesize work identically when no X-Organization-ID header is sent

Migration Steps

Step 1: Run the database migrations

# From the cloud-api directory
psql $DATABASE_URL -f migrations/007_enterprise_org_model.sql
psql $DATABASE_URL -f migrations/008_memory_management.sql

These migrations:

  • Create 11 new tables
  • Add nullable organization_id and namespace_id columns to semantic_memories, events, and compiled_artifacts
  • Create 32 indexes
  • Are fully additive (no existing data is modified or deleted)

Step 2: Verify existing functionality

After running migrations, verify that your existing API calls still work:

# Should work exactly as before
curl -X POST https://api.hippocortex.dev/v1/capture \
  -H "Authorization: Bearer hx_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "type": "test", "sessionId": "s1", "payload": {} }'

Step 3: Create your first organization

curl -X POST https://api.hippocortex.dev/v1/organizations \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "My Organization" }'

Step 4: Start using enterprise features

Once the org is created, you can:

  1. Invite members
  2. Create teams
  3. Set up namespaces
  4. Configure policies
  5. Start sending X-Organization-ID header with requests to enable scoped retrieval

Step 5: (Optional) Assign existing memories to namespaces

If you want to scope existing memories into namespaces, you can update them directly in the database:

-- Assign all existing memories for a tenant to a namespace
UPDATE semantic_memories
SET namespace_id = 'ns_xxx', organization_id = 'org_xxx'
WHERE tenant_id = 'ten_xxx';

UPDATE events
SET namespace_id = 'ns_xxx', organization_id = 'org_xxx'
WHERE tenant_id = 'ten_xxx';

This is optional. Unassigned memories continue to work with standard tenant-scoped queries.

Backward Compatibility

The enterprise features are designed to be fully backward compatible:

  1. No breaking changes -- all existing routes, request formats, and response formats are unchanged
  2. Opt-in -- enterprise features only activate when X-Organization-ID is sent
  3. Nullable columns -- all new columns on existing tables are nullable
  4. Conditional routes -- enterprise routes only mount if the org repo is provided
  5. Gradual adoption -- you can use enterprise features for some requests and standard features for others

Rollback Plan

If you need to roll back:

  1. The enterprise routes are conditionally mounted, so they can be disabled by not providing org repos
  2. New tables can be dropped without affecting existing tables
  3. New columns on existing tables are nullable, so they can be ignored
-- To fully roll back (destructive):
DROP TABLE IF EXISTS lifecycle_policies CASCADE;
DROP TABLE IF EXISTS memory_lineage CASCADE;
DROP TABLE IF EXISTS memory_access_logs CASCADE;
DROP TABLE IF EXISTS audit_logs CASCADE;
DROP TABLE IF EXISTS memory_policies CASCADE;
DROP TABLE IF EXISTS memory_namespaces CASCADE;
DROP TABLE IF EXISTS agent_identities CASCADE;
DROP TABLE IF EXISTS team_memberships CASCADE;
DROP TABLE IF EXISTS org_memberships CASCADE;
DROP TABLE IF EXISTS teams CASCADE;
DROP TABLE IF EXISTS organizations CASCADE;

ALTER TABLE semantic_memories DROP COLUMN IF EXISTS organization_id;
ALTER TABLE semantic_memories DROP COLUMN IF EXISTS namespace_id;
ALTER TABLE events DROP COLUMN IF EXISTS organization_id;
ALTER TABLE events DROP COLUMN IF EXISTS namespace_id;
ALTER TABLE compiled_artifacts DROP COLUMN IF EXISTS organization_id;
ALTER TABLE compiled_artifacts DROP COLUMN IF EXISTS namespace_id;