Skip to main content

ADR-159: Multi-Tenant Command Architecture

Status

PROPOSED (2026-02-06)

Context

CODITECT has 366 commands (as audited in H.12.1, 2026-02-08). The majority (76.2%) already operate at project scope, but 25 commands (6.8%) have gaps where they touch shared databases or perform git operations without proper project routing. As the platform evolves toward multi-tenant SaaS with customer projects (CUST-*), these gaps need systematic resolution.

Audit results (H.12.1):

Scope LevelCountPercentage
Global5214.2%
Machine133.6%
Session205.5%
Project27976.2%
Tenant20.5%

25 commands with scope gaps, categorized:

  • 8 DB-touching commands without --project filter (data leakage risk)
  • 12 git operation commands without project routing (code mixing risk)
  • 5 log operation commands without project scoping (noise risk)

Current state:

  • /session-log already implements --project (ADR-155) - serves as the pattern template
  • /sync-logs pushes all logs to a central repo regardless of project ownership
  • /cx extracts ALL sessions into a single unified store
  • /cxq searches across all projects with no isolation
  • /backup backs up everything globally
  • /orient and /checkpoint load global state without project context

Triggering event: Customer project CUST-avivatec-fpa has its own dedicated git repo, but /sync-logs routes to the central repo. H.12.1 audit confirmed this pattern exists across 25 commands.

Full audit data: internal/analysis/multi-tenant-command-audit/COMMAND-SCOPE-AUDIT.md

Related ADRs:

  • ADR-155 established project-scoped session logs (the first command to get project awareness)
  • ADR-156 established project-scoped context databases
  • ADR-053 defined cloud context sync (tenant isolation at cloud level)
  • ADR-118 defined four-tier database architecture (data separation)

Decision

We adopt a consistent multi-tenant command architecture with the following design:

1. Scope Hierarchy

All scope-aware commands operate within a 5-level hierarchy:

Tenant (organization: AZ1.AI, Avivatec, etc.)
└─ Team (department: engineering, product, etc.)
└─ Project (work scope: PILOT, CUST-avivatec-fpa, etc.)
└─ Machine (physical: d3d3a316-...)
└─ Session (ephemeral: uuid)

2. Standard Command Parameters

Every scope-aware command receives the same 3 optional flags:

FlagTypeDescriptionExample
--project <id>stringFilter/route by project--project PILOT
--team <id>stringFilter/route by team--team engineering
--tenant <id>stringFilter/route by tenant--tenant avivatec

Auto-detection: When no flag is provided, commands auto-detect the project from the working directory using discover_project() from scripts/core/paths.py.

Scope inheritance: --tenant implies all projects/teams within that tenant. --team implies all projects within that team.

3. Config Discovery Pattern

Commands resolve configuration via cascading lookup (most specific wins):

1. Explicit CLI flag          --project PILOT
2. Environment variable $CODITECT_PROJECT
3. Working directory detect discover_project()
4. Project config config/projects/{id}/config.json
5. Tenant config config/tenants/{id}/config.json
6. Global config config/config.json

Config file structure:

// config/projects/CUST-avivatec-fpa/config.json
{
"project_id": "CUST-avivatec-fpa",
"tenant_id": "avivatec",
"session_log_repo": "https://github.com/coditect-ai/coditect-CUST-avivatec-fpa-session-logs.git",
"backup_bucket": "gs://coditect-customer-avivatec-backups",
"context_db_isolation": true,
"data_redaction": true
}

4. Data Routing

Commands that write or sync data use a routing table to determine the destination:

// config/session-log-repos.json (example for /sync-logs)
{
"default": "https://github.com/coditect-ai/coditect-core-sessions-logs.git",
"projects": {
"PILOT": "https://github.com/coditect-ai/coditect-core-sessions-logs.git",
"CUST-avivatec-fpa": "https://github.com/coditect-ai/coditect-CUST-avivatec-fpa-session-logs.git"
}
}

Routing resolution:

  1. Check projects.{project_id} in routing config
  2. Check tenants.{tenant_id} in routing config
  3. Fall back to default

5. Data Isolation Requirements

Scope LevelIsolation Rule
TenantCUST-* data MUST NOT leak to other tenants or central stores without explicit config
ProjectProject data tagged with project_id in all stores (sessions.db, org.db, exports)
TeamOptional grouping; no hard isolation required in v1
MachineAlready isolated via machine UUID (ADR-058)
SessionAlready isolated via session UUID

Customer data protection:

  • CUST-* projects enforce data_redaction: true by default
  • PII check runs before any sync/export to external stores
  • Directory permissions 0o700 for CUST-* project data

6. Command Enhancement Matrix (Updated per H.12.1 Audit)

Commands are categorized by enhancement priority based on the 366-command audit:

Phase 0 - Already Done:

CommandScope SupportADR
/session-log--projectADR-155

Phase 1 - DB Query Isolation (Tier 1, 2-3 hours):

CommandGapFix
/session-statusDB query unscopedAdd WHERE project_id = ? filter
/session-conflictsDB query unscopedAdd project filter to conflict detection
/summariesDB query unscopedScope summary queries by project
/trajectoryDB query unscopedAdd project filter to trajectory data

Phase 2 - Git/Data Routing (Tier 2, 6-8 hours):

CommandGapTask
/sync-logsSingle repo assumptionH.12.2
/db-backupBacks up all project dataH.12.4
/commitNo project-specific hooks-
/git-syncSingle remote assumption-
/session-log-codexLogs unscoped-

Phase 3 - Project Awareness (Tier 3, 6-8 hours):

CommandGapTask
/biDB query unscoped-
/component-activateDB write unscoped-
/pluginDB operations unscoped-
/search-scriptsResults unscoped-
/quality-gateGit checks unscoped-
/weekly-digestData aggregation unscoped-
/updateGit operations unscoped-
/update-planGit operations unscoped-
/project-plan-updateGit operations unscoped-

Phase 4 - Cleanup (Tier 4, opportunistic):

CommandGap
/markdown-cleanupFile operations unscoped
/pilotLog output unscoped
/aliasGit operations
/audit-trailGit operations
/component-lifecycleGit operations
/lowercase-migrationFile operations
/submodule-initGit operations

341 commands (93.2%) require no changes - already well-scoped or stateless.

7. Implementation Pattern

Each command enhancement follows this pattern:

# Standard scope resolution (add to scripts/core/scope.py)

def resolve_scope(
project: str | None = None,
team: str | None = None,
tenant: str | None = None,
) -> ScopeContext:
"""Resolve scope from flags, env vars, or auto-detection."""

# 1. Explicit flags take precedence
if project:
return ScopeContext(project=project, team=team, tenant=tenant)

# 2. Environment variable
env_project = os.environ.get("CODITECT_PROJECT")
if env_project:
return ScopeContext(project=env_project, team=team, tenant=tenant)

# 3. Auto-detect from working directory
from scripts.core.paths import discover_project
detected = discover_project()
if detected:
return ScopeContext(project=detected, team=team, tenant=tenant)

# 4. Global scope (no project context)
return ScopeContext(project=None, team=team, tenant=tenant)


def get_project_config(project_id: str) -> dict:
"""Load project-specific config with tenant fallback."""
project_config = f"config/projects/{project_id}/config.json"
if os.path.exists(project_config):
return json.load(open(project_config))

# Infer tenant from project prefix
if project_id.startswith("CUST-"):
tenant_id = project_id.split("-")[1]
tenant_config = f"config/tenants/{tenant_id}/config.json"
if os.path.exists(tenant_config):
return json.load(open(tenant_config))

return {} # Global defaults

Consequences

Positive

  • Consistent UX: All commands use the same --project/--team/--tenant flags
  • Customer data isolation: CUST-* project data stays within tenant boundaries
  • Auto-detection: Most commands work without flags via discover_project()
  • Backward compatible: No flags = current global behavior preserved
  • Incremental adoption: Commands can be enhanced one at a time following the standard pattern

Negative

  • Config proliferation: Each customer project may need its own config directory
  • Audit overhead: H.12.1 audit (366 commands) is complete; 25 gaps identified across 4 priority tiers
  • Testing complexity: Each command needs scope-aware test cases

Neutral

  • ADR-155 already established the pattern - this ADR formalizes and extends it
  • Cloud sync (ADR-053) already handles tenant isolation at the API level - this brings the same isolation to local CLI commands

Implementation Plan

PhaseTasksTimelineDependency
0H.12.1: Command scope audit (366 commands)COMPLETENone
1Create scripts/core/scope.py shared moduleNextH.12.1
2Phase 1: DB query isolation (4 commands, 2-3 hrs)After Phase 1scope.py
3H.12.2: /sync-logs project routing (7 subtasks)ParallelNone
4Phase 2: Git/data routing (5 commands, 6-8 hrs)After scope.pyscope.py
5H.12.4, H.12.5, Phase 3-4: Remaining (16 commands)After Phase 4scope.py

References