Skip to main content

ADR-144: Multi-Project Registry Architecture

Status

ACCEPTED (2026-02-03)

Context

Problem Statement

CODITECT currently manages projects in a fragmented manner across multiple systems with no unified registry:

  1. Scattered PROJECT-PLAN.md files - The coditect-rollout-master repository contains 74 git submodules, many of which have their own PROJECT-PLAN.md. There is no central index that maps all projects to their plan files, tracks, or ownership.

  2. PILOT project has tracks but no registry entry - The primary PILOT project is tracked via TRACK-A through TRACK-N files under internal/project/plans/pilot-tracks/, but there is no formal registry record connecting the PILOT project to these tracks, its metadata, or its lifecycle status.

  3. /project command uses sessions.db with E001-T001 format - The existing /project command (backed by scripts/work_items.py) stores work items in sessions.db using an epic-task format (E001-T001). This format does not align with the track-based nomenclature (A.9.1.3) defined in ADR-054, creating a dual-system confusion.

  4. /project-new creates directories without registration - The /project-new command creates project directories under ~/PROJECTS/ with symlinks and component activation, but does not register the project in any central database. There is no way to list all projects created this way.

  5. No multi-tenant project isolation - The cloud API at api.coditect.ai supports multi-tenant isolation via django-multitenant, but there is no local registry that distinguishes between internal CODITECT projects, customer projects (tenant-scoped), and contributor projects.

  6. No project switching - When working across multiple projects (e.g., switching between coditect-core framework work and a customer project), there is no mechanism to set the active project context, which affects task ID resolution, track loading, and session attribution.

Current State

ComponentStorageFormatLimitation
PILOT tracksMarkdown filesTRACK-{X}-*.mdNo registry, single project only
/project commandsessions.dbE001-T001Disconnected from tracks, regenerable tier
/project-newFilesystemDirectory + symlinksNo registration, no discovery
Cloud APIPostgreSQLdjango-multitenantNo local cache of project metadata
Submodule plansScattered .md filesVariousNo central index

Requirements

  1. A single registry that catalogs all projects regardless of origin (internal, customer, contributor)
  2. Storage in org.db (Tier 2, irreplaceable) since project metadata is organizational knowledge
  3. Mapping from projects to their track files, plan locations, and ownership
  4. Support for multi-tenant customer projects with cloud synchronization
  5. Backward compatibility with the existing E001-T001 work item format
  6. Project switching to set active context for task IDs, track loading, and session attribution
  7. Migration path for existing scattered PROJECT-PLAN.md files without requiring file moves

Decision

1. Project Registry in org.db (Tier 2)

Create a central project registry as two tables in org.db, the irreplaceable Tier 2 database (ADR-118). Project metadata represents accumulated organizational knowledge about what projects exist, who owns them, and how they are structured. This data cannot be regenerated from source files alone.

2. Database Schema

-- ============================================================
-- Table: projects
-- Purpose: Central registry of all CODITECT projects
-- Location: org.db (Tier 2 - irreplaceable)
-- ============================================================
CREATE TABLE IF NOT EXISTS projects (
project_id TEXT PRIMARY KEY,
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
description TEXT,
scope TEXT NOT NULL CHECK (scope IN ('platform', 'org', 'customer', 'project')),
tenant_id TEXT,
owner TEXT NOT NULL,
plan_location TEXT NOT NULL,
tracks_config TEXT,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed', 'archived')),
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
metadata TEXT,
cloud_synced_at TEXT,
cloud_project_uuid TEXT
);

-- ============================================================
-- Table: project_tracks
-- Purpose: Map projects to their assigned tracks
-- Location: org.db (Tier 2 - irreplaceable)
-- ============================================================
CREATE TABLE IF NOT EXISTS project_tracks (
project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
track_letter TEXT NOT NULL,
track_name TEXT NOT NULL,
track_file TEXT NOT NULL,
tier INTEGER NOT NULL CHECK (tier IN (1, 2, 3)),
progress_pct REAL DEFAULT 0.0,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed', 'deferred')),
PRIMARY KEY (project_id, track_letter)
);

-- ============================================================
-- Indexes for common query patterns
-- ============================================================
CREATE INDEX IF NOT EXISTS idx_projects_scope ON projects(scope);
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
CREATE INDEX IF NOT EXISTS idx_projects_tenant ON projects(tenant_id);
CREATE INDEX IF NOT EXISTS idx_projects_slug ON projects(slug);
CREATE INDEX IF NOT EXISTS idx_project_tracks_project ON project_tracks(project_id);
CREATE INDEX IF NOT EXISTS idx_project_tracks_letter ON project_tracks(track_letter);

-- ============================================================
-- View: active_projects
-- Purpose: Convenience view for common filtering
-- ============================================================
CREATE VIEW IF NOT EXISTS active_projects AS
SELECT
p.project_id,
p.slug,
p.name,
p.scope,
p.owner,
p.plan_location,
p.status,
COUNT(pt.track_letter) AS track_count,
AVG(pt.progress_pct) AS avg_progress
FROM projects p
LEFT JOIN project_tracks pt ON p.project_id = pt.project_id
WHERE p.status = 'active'
GROUP BY p.project_id;

3. Schema Field Definitions

FieldTypeDescription
project_idTEXT PKUnique identifier. Convention: PILOT, ORG-<slug>, CUST-<tenant>-<slug>, PROJ-<slug>
slugTEXT UNIQUEURL-safe identifier for API routes and file paths (e.g., pilot, artifact-cleanup, acme-webapp)
nameTEXTHuman-readable display name (e.g., "CODITECT PILOT Launch")
descriptionTEXTOptional project description
scopeTEXTIsolation scope: platform (CODITECT internal), org (organization-level), customer (tenant-scoped), project (standalone)
tenant_idTEXTNULL for internal projects. UUID for customer projects, matching django-multitenant tenant_id
ownerTEXTTeam or user identifier (e.g., "core-team", "hal.casteel", "CUST-acme")
plan_locationTEXTRelative path from repository root to PROJECT-PLAN.md or equivalent (e.g., internal/project/plans/pilot-tracks/)
tracks_configTEXTJSON object defining track configuration: {"letters": ["A","B","C"], "prefix": null, "tier": 1}
statusTEXTLifecycle status: active, paused, completed, archived
metadataTEXTJSON blob for project-specific configuration (e.g., {"launch_date": "2026-03-11", "budget": "50000"})
cloud_synced_atTEXTISO 8601 timestamp of last successful cloud sync
cloud_project_uuidTEXTUUID assigned by cloud API for cross-reference

4. Scope Hierarchy

platform
scope: Internal CODITECT framework projects (e.g., PILOT)
tenant_id: NULL
tracks: Tier 1 (A-N framework tracks)
cloud_sync: Yes (to platform admin dashboard)
example: PILOT, coditect-core-v3

org
scope: Organization-level projects owned by AZ1.AI
tenant_id: NULL
tracks: Tier 2 (O-AA business tracks) or custom
cloud_sync: Yes (to org dashboard)
example: ORG-artifact-cleanup, ORG-docs-migration

customer
scope: Customer tenant projects, isolated by tenant_id
tenant_id: UUID (from django-multitenant)
tracks: Tier 2-3 (customer-defined). Tier 1 (A-N) requires explicit Platform Admin grant.
cloud_sync: Yes (to customer tenant dashboard)
example: CUST-acme-001-webapp, CUST-globex-002-api

project
scope: Standalone contributor or personal projects
tenant_id: NULL
tracks: Tier 3 (project-specific) or none
cloud_sync: Optional
example: PROJ-hal-experiment, PROJ-hackathon-q1

5. Registry Operations

5.1 CLI Tool: scripts/project_registry.py

# Register a project
python3 scripts/project_registry.py register \
--id PILOT \
--name "CODITECT PILOT Launch" \
--scope platform \
--owner core-team \
--plan-location "internal/project/plans/pilot-tracks/" \
--tracks A,B,C,D,E,F,G,H,I,J,K,L,M,N

# List projects
python3 scripts/project_registry.py list
python3 scripts/project_registry.py list --scope customer --status active

# Show project details
python3 scripts/project_registry.py show PILOT

# Update project
python3 scripts/project_registry.py update PILOT --status completed

# Scan and auto-register from submodules
python3 scripts/project_registry.py scan --root ~/PROJECTS/coditect-rollout-master

# Sync to cloud
python3 scripts/project_registry.py sync --project PILOT
python3 scripts/project_registry.py sync --all

5.2 Command Enhancements

/project register - New subcommand to register an existing project:

/project register                              # Interactive: detect from current directory
/project register --id ORG-docs --plan ./PROJECT-PLAN.md --scope org

/project list - Enhanced to query registry:

/project list                                  # All active projects
/project list --scope customer # Customer projects only
/project list --owner hal.casteel # Projects by owner

/project show <id> - Enhanced with track details:

/project show PILOT
# Output:
# Project: CODITECT PILOT Launch (PILOT)
# Scope: platform | Owner: core-team | Status: active
# Plan: internal/project/plans/tracks/
# Tracks: A(100%) B(84%) C(81%) D(100%) E(100%) F(40%) ...
# Cloud: synced 2026-02-02T10:30:00Z

/project switch <id> - Set active project context:

/project switch PILOT
# Sets: $CODITECT_PROJECT=PILOT
# Loads: Track files A-N
# Updates: Statusline task section

/project-new enhancement - Auto-register on creation:

/project-new my-webapp --scope project --owner hal.casteel
# 1. Creates ~/PROJECTS/my-webapp/
# 2. Sets up symlinks
# 3. Registers as PROJ-my-webapp in org.db
# 4. Creates PROJECT-PLAN.md
# 5. Activates components

6. Project Discovery

6.1 Auto-Detection from Working Directory

When a session starts or /orient runs, the system resolves the active project by matching the current working directory against registered plan_location paths:

def discover_project(cwd: str) -> Optional[str]:
"""Resolve active project from current working directory."""
# 1. Check $CODITECT_PROJECT env var (explicit override)
if os.environ.get('CODITECT_PROJECT'):
return os.environ['CODITECT_PROJECT']

# 2. Query registry for matching plan_location
db = get_org_db_path()
conn = sqlite3.connect(db)
projects = conn.execute(
"SELECT project_id, plan_location FROM projects WHERE status = 'active'"
).fetchall()

# 3. Match cwd against plan locations (longest prefix match)
cwd_path = Path(cwd).resolve()
best_match = None
best_length = 0
for project_id, plan_loc in projects:
plan_path = Path(plan_loc).resolve()
try:
cwd_path.relative_to(plan_path.parent)
if len(str(plan_path)) > best_length:
best_match = project_id
best_length = len(str(plan_path))
except ValueError:
continue

return best_match

6.2 Agent Integration

Agents receive active project context through the $CODITECT_PROJECT environment variable:

# In agent invocation, project context is passed
/agent senior-architect "Design API for user service"
# Agent receives: CODITECT_PROJECT=PILOT
# Agent loads: PILOT track files (A-N)
# Agent uses: Task ID format A.x.y.z

7. Multi-Tenant Integration

7.1 Data Flow

┌─────────────────────────────────────┐
│ LOCAL (org.db) │
│ │
│ projects table │
│ ├─ PILOT (platform, tenant=NULL) │
│ ├─ ORG-docs (org, tenant=NULL) │
│ └─ CUST-acme-webapp (customer, │
│ tenant=abc-123-uuid) │
│ │
│ │ sync (ADR-053) │
│ ▼ │
├─────────────────────────────────────┤
│ CLOUD (api.coditect.ai) │
│ │
│ Project model (PostgreSQL) │
│ ├─ django-multitenant isolation │
│ ├─ tenant_id foreign key │
│ └─ RBAC via DRF permissions │
│ │
│ API Endpoints: │
│ POST /api/v1/projects/ │
│ GET /api/v1/projects/ │
│ GET /api/v1/projects/{uuid}/ │
│ PUT /api/v1/projects/{uuid}/ │
│ GET /api/v1/projects/{uuid}/tracks/│
└─────────────────────────────────────┘

7.2 Sync Rules

ScopeDirectionFrequencySource of Truth
platformlocal -> cloudOn changeLocal (org.db)
orglocal -> cloudOn changeLocal (org.db)
customerbidirectionalPeriodic (5 min)Cloud (PostgreSQL)
projectlocal -> cloud (optional)ManualLocal (org.db)

For customer scope projects, the cloud API is the source of truth. Local org.db acts as a cache to enable offline access. Conflict resolution uses last-write-wins with the cloud timestamp taking precedence.

7.3 Cloud API Schema (Django Model)

# In coditect-cloud-infra/backend/coditect_license/projects/models.py
from django_multitenant.models import TenantModel

class Project(TenantModel):
tenant_id = 'tenant_id'

uuid = models.UUIDField(primary_key=True, default=uuid4)
tenant = models.ForeignKey('tenants.Tenant', on_delete=models.CASCADE)
project_id = models.CharField(max_length=128, unique=True)
slug = models.SlugField(max_length=128, unique=True)
name = models.CharField(max_length=256)
description = models.TextField(blank=True, default='')
scope = models.CharField(max_length=32, choices=SCOPE_CHOICES)
owner = models.CharField(max_length=128)
plan_location = models.CharField(max_length=512)
tracks_config = models.JSONField(default=dict, blank=True)
status = models.CharField(max_length=32, choices=STATUS_CHOICES, default='active')
metadata = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
ordering = ['-updated_at']

class ProjectTrack(TenantModel):
tenant_id = 'tenant_id'

project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='tracks')
tenant = models.ForeignKey('tenants.Tenant', on_delete=models.CASCADE)
track_letter = models.CharField(max_length=8)
track_name = models.CharField(max_length=128)
track_file = models.CharField(max_length=512)
tier = models.IntegerField()
progress_pct = models.FloatField(default=0.0)
status = models.CharField(max_length=32, choices=TRACK_STATUS_CHOICES, default='active')

class Meta:
unique_together = ['project', 'track_letter']

8. Reconciliation with work_items.py

The existing scripts/work_items.py uses an E001-T001 format (epic-task) stored in sessions.db (Tier 3). This system must coexist with the project registry during a transition period.

8.1 Bridge Mapping

work_items.pyProject RegistryMapping Rule
E001project_idE001 = "PILOT" (default mapping)
E002project_idE002 = "ORG-docs" (configured in metadata)
T001Track taskSequential task within the epic/project

8.2 Transition Plan

PhaseTimelineAction
Phase 1ImmediateRegistry created, coexists with work_items.py
Phase 2+2 weeks/project command reads from registry first, falls back to work_items.py
Phase 3+4 weekswork_items.py writes to both sessions.db (legacy) and org.db (registry)
Phase 4+8 weekswork_items.py deprecated; /project reads exclusively from registry

8.3 Compatibility Layer

def resolve_epic_to_project(epic_id: str) -> Optional[str]:
"""Map legacy E001 epic ID to project registry project_id."""
# Check explicit mapping in org.db
conn = sqlite3.connect(get_org_db_path())
result = conn.execute(
"SELECT project_id FROM projects WHERE json_extract(metadata, '$.legacy_epic_id') = ?",
(epic_id,)
).fetchone()
if result:
return result[0]

# Default: E001 = PILOT
if epic_id == 'E001':
return 'PILOT'

return None

9. Migration Plan

9.1 Scan Existing Projects

A migration script scans all submodules and known project locations to discover unregistered projects:

python3 scripts/project_registry.py scan \
--root ~/PROJECTS/coditect-rollout-master \
--dry-run

The scanner:

  1. Walks all subdirectories of submodules/ looking for PROJECT-PLAN.md
  2. Extracts project name from the plan file frontmatter or first heading
  3. Generates a project_id from the directory path (e.g., submodules/dev/coditect-cli becomes ORG-coditect-cli)
  4. Generates a slug from the directory name
  5. Outputs a registration plan for review

9.2 Seed Data

The following projects are registered at initialization:

-- PILOT project (primary)
INSERT INTO projects (project_id, slug, name, scope, owner, plan_location, tracks_config, status)
VALUES (
'PILOT',
'pilot',
'CODITECT PILOT Launch',
'platform',
'core-team',
'internal/project/plans/pilot-tracks/',
'{"letters": ["A","B","C","D","E","F","G","H","I","J","K","L","M","N"], "tier": 1}',
'active'
);

-- PILOT track registrations
INSERT INTO project_tracks (project_id, track_letter, track_name, track_file, tier, progress_pct, status) VALUES
('PILOT', 'A', 'Backend API', 'internal/project/plans/pilot-tracks/TRACK-A-backend-api.md', 1, 100.0, 'completed'),
('PILOT', 'B', 'Frontend UI', 'internal/project/plans/pilot-tracks/TRACK-B-frontend-ui.md', 1, 84.0, 'active'),
('PILOT', 'C', 'DevOps Infra', 'internal/project/plans/pilot-tracks/TRACK-C-devops-infra.md', 1, 81.0, 'active'),
('PILOT', 'D', 'Security', 'internal/project/plans/pilot-tracks/TRACK-D-security.md', 1, 100.0, 'completed'),
('PILOT', 'E', 'Testing QA', 'internal/project/plans/pilot-tracks/TRACK-E-testing-qa.md', 1, 100.0, 'completed'),
('PILOT', 'F', 'Documentation', 'internal/project/plans/pilot-tracks/TRACK-F-documentation.md', 1, 40.0, 'active'),
('PILOT', 'G', 'DMS Product', 'internal/project/plans/pilot-tracks/TRACK-G-dms-product.md', 1, 0.0, 'deferred'),
('PILOT', 'H', 'Framework Autonomy', 'internal/project/plans/pilot-tracks/TRACK-H-framework-autonomy.md', 1, 56.0, 'active'),
('PILOT', 'I', 'UI Components', 'internal/project/plans/pilot-tracks/TRACK-I-ui-components.md', 1, 100.0, 'completed'),
('PILOT', 'J', 'Memory Intelligence', 'internal/project/plans/pilot-tracks/TRACK-J-memory-intelligence.md', 1, 67.0, 'active'),
('PILOT', 'K', 'Workflow Automation', 'internal/project/plans/pilot-tracks/TRACK-K-workflow-automation.md', 1, 0.0, 'deferred'),
('PILOT', 'L', 'Extended Testing', 'internal/project/plans/pilot-tracks/TRACK-L-extended-testing.md', 1, 0.0, 'deferred'),
('PILOT', 'M', 'Extended Security', 'internal/project/plans/pilot-tracks/TRACK-M-extended-security.md', 1, 0.0, 'deferred'),
('PILOT', 'N', 'GTM Launch', 'internal/project/plans/pilot-tracks/TRACK-N-gtm-launch.md', 1, 61.0, 'active');

9.3 Migration Steps

StepActionImpact
1Run ALTER TABLE or CREATE TABLE on org.dbAdds two new tables, no existing data affected
2Insert PILOT seed dataRegisters primary project
3Run scanner on submodulesDiscovers and registers existing projects
4Update /project command to read from registryNew code path, fallback to sessions.db
5Update /project-new to auto-registerEnhancement, no breaking change
6Add cloud sync endpoint for projectsNew API endpoint, no migration needed
7Deprecate E001-T001 format in work_items.pyWarning messages, dual-write

No file moves are required. The registry points to existing plan file locations. Projects remain where they are; only the index is centralized.

10. Implementation Architecture

┌─────────────────────────────────────────────────────────────────┐
│ User Interface Layer │
│ │
│ /project list /project show /project switch │
│ /project register /project-new (enhanced) │
│ │
├───────────────────────────┬─────────────────────────────────────┤
│ Command Layer │ Agent Layer │
│ │ │
│ commands/project.md │ $CODITECT_PROJECT env var │
│ commands/project-new.md │ discover_project(cwd) │
│ │ Track loading per project │
│ │ │
├───────────────────────────┴─────────────────────────────────────┤
│ Registry Service Layer │
│ │
│ scripts/project_registry.py │
│ ├── register(project_id, name, scope, ...) │
│ ├── list(scope=None, status=None, owner=None) │
│ ├── show(project_id) -> ProjectDetail │
│ ├── update(project_id, **fields) │
│ ├── switch(project_id) -> sets env + loads tracks │
│ ├── scan(root_dir) -> List[UnregisteredProject] │
│ ├── sync(project_id=None) -> cloud push/pull │
│ └── resolve_epic(epic_id) -> project_id │
│ │
├─────────────────────────────────────────────────────────────────┤
│ Storage Layer │
│ │
│ org.db (Tier 2) │ Cloud API (PostgreSQL) │
│ ├── projects │ ├── Project model │
│ ├── project_tracks │ ├── ProjectTrack model │
│ └── active_projects (view) │ └── tenant isolation │
│ │ │
│ sessions.db (Tier 3) │ Filesystem │
│ └── work_items (legacy) │ └── PROJECT-PLAN.md files │
│ │
└─────────────────────────────────────────────────────────────────┘

11. Environment Variable Protocol

When a project is switched or auto-detected, the following environment variable is set:

VariableExamplePurpose
CODITECT_PROJECTPILOTActive project ID for task routing

This variable is consumed by:

  • Statusline (Section 7: task) - Displays active project
  • Task ID validator (hooks/task_id_validator.py) - Validates track letters against project tracks
  • Agent dispatcher (scripts/core/agent_dispatcher.py) - Passes project context to agents
  • Session logger - Attributes session work to the correct project

Consequences

Positive

  1. Unified project discovery - All projects indexed in one place, queryable by scope, status, owner, or track
  2. Multi-tenant readiness - Customer projects are cleanly isolated by tenant_id, matching the cloud API model
  3. Project switching - Agents and commands can load the correct tracks and validate task IDs for the active project
  4. No file reorganization - Registry points to existing file locations; no moves required
  5. Irreplaceable storage - org.db (Tier 2) ensures project metadata survives regenerable data loss
  6. Cloud synchronization - Projects sync to cloud dashboard for multi-device and team visibility
  7. Backward compatible - E001-T001 work items coexist during transition via explicit mapping

Negative

  1. Schema addition to org.db - Two new tables added to the critical Tier 2 database; requires careful migration
  2. Dual system during transition - work_items.py and project registry will coexist for approximately 8 weeks
  3. Registration overhead - Existing projects must be scanned and registered (one-time migration cost)
  4. Cloud API dependency - Customer project sync depends on the cloud API being available; offline mode mitigates this

Risks and Mitigations

RiskLikelihoodMitigation
org.db corruption during migrationLowBackup before ALTER TABLE; test on copy first
Stale registry entries for moved projectsMediumPeriodic scan validates plan_location exists
Cloud sync conflicts for customer projectsMediumLast-write-wins with cloud timestamp precedence
Legacy E001 format confusion during transitionMediumClear deprecation warnings in work_items.py output

Implementation Plan

Phase 1: Foundation (Week 1)

  • Create projects and project_tracks tables in org.db schema
  • Implement scripts/project_registry.py with register, list, show, update operations
  • Add PILOT seed data migration
  • Add discover_project() function to scripts/core/paths.py

Phase 2: Command Integration (Week 2)

  • Update /project command to read from registry (with sessions.db fallback)
  • Update /project-new to auto-register created projects
  • Add /project register subcommand
  • Add /project switch subcommand with $CODITECT_PROJECT management

Phase 3: Migration (Week 3)

  • Implement submodule scanner (project_registry.py scan)
  • Run migration to register all existing projects
  • Add E001-to-project_id mapping layer in work_items.py
  • Update statusline to display active project from registry

Phase 4: Cloud Sync (Week 4)

  • Add Project and ProjectTrack Django models to cloud backend
  • Create REST API endpoints for project CRUD
  • Implement project_registry.py sync command
  • Add periodic sync for customer-scope projects

Phase 5: Deprecation (Weeks 5-8)

  • Add deprecation warnings to work_items.py E001-T001 format
  • Dual-write: work_items.py writes to both sessions.db and org.db
  • Monitor for issues during transition
  • Remove sessions.db work_items dependency after validation
ADRRelationship
ADR-053Cloud sync architecture; project registry sync follows the same patterns
ADR-054Track nomenclature standard; project registry enforces track assignment
ADR-116Track-based plan architecture; registry indexes track files
ADR-117Hierarchical plan locations; registry stores plan_location per project
ADR-118Four-tier database; registry lives in org.db (Tier 2, irreplaceable)
ADR-145Project-level RBAC; uses project registry tenant_id for access scoping
ADR-146Unified task ID strategy; project prefix resolves against registry