# Multi-Org Support Plan

## Overview

Add an `org` field to the entire pipeline so sessions are scoped to organizations. Set once at install time, stored locally, sent with every push, persisted in Postgres, filterable in the dashboard.

---

## Install Flow

```bash
# Org is required during install
curl -fsSL https://quickcall.dev/trace/install.sh | sh -s -- --org pratilipi

# Re-running updates the org (idempotent)
curl -fsSL https://quickcall.dev/trace/install.sh | sh -s -- --org neworg
```

The installer writes `org` to `~/.quickcall-trace/config.json`:
```json
{
  "org": "pratilipi"
}
```

If `--org` is not provided, the installer exits with an error.

---

## Changes

### 1. Install script (`scripts/install.sh`)

- Parse `--org <name>` from arguments
- Fail if `--org` not provided: `"Error: --org is required. Usage: install.sh --org <org-name>"`
- Write `org` to `~/.quickcall-trace/config.json` (merge with existing config, don't overwrite)
- Pass `org` as `QC_TRACE_ORG` env var in the service definition (launchd plist / systemd unit)

### 2. Daemon config (`qc_trace/daemon/config.py`)

- Add `org: str | None` field to `DaemonConfig`
- Read from `QC_TRACE_ORG` env var, fall back to `config.json`
- Precedence: env var > config.json > None

### 3. Schema (`qc_trace/schemas/unified.py`)

- Add `org: str | None = None` to `SessionContext` dataclass

### 4. Daemon main loop (`qc_trace/daemon/main.py`)

- Read `self.config.org` on startup
- Pass `org` to `collect_file()` calls

### 5. Collector (`qc_trace/daemon/collector.py`)

- Add `org` parameter to `collect_file()` and `_build_session_context()`
- Set `ctx.org = org`

### 6. Database schema (`qc_trace/db/schema.sql`)

Add `org` column to `sessions` table:
```sql
ALTER TABLE sessions ADD COLUMN org TEXT;
CREATE INDEX idx_sessions_org ON sessions (org);
```

### 7. Migration (`qc_trace/db/migrations.py`)

Add v5 migration:
```python
V5_ADD_ORG = """
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.columns
        WHERE table_name = 'sessions' AND column_name = 'org'
    ) THEN
        ALTER TABLE sessions ADD COLUMN org TEXT;
        CREATE INDEX idx_sessions_org ON sessions (org);
    END IF;
END $$;
"""
```

### 8. Writer (`qc_trace/db/writer.py`)

- Add `org` to `SESSION_UPSERT_SQL` (INSERT and ON CONFLICT UPDATE)
- Read `org` from `session_context` when upserting

### 9. Reader (`qc_trace/db/reader.py`)

- Add `org` filter to session list query (`WHERE org = %s`)
- Add `org` filter to stats query
- Add `org` filter to feed query

### 10. Server handlers (`qc_trace/server/handlers.py`)

- Accept `?org=` query parameter on `/api/sessions`, `/api/stats`, `/api/feed`
- Pass through to reader

### 11. CLI status (`qc_trace/cli/traced.py`)

- Read `org` from config.json
- Display in `quickcall status` output:
  ```
  QuickCall Trace v0.2.0
  Org: pratilipi
  Daemon: running (PID 12345) · uptime 3d 4h
  ...
  ```

### 12. Dashboard

- Add org filter dropdown to session list and overview pages
- Read available orgs from a new `/api/orgs` endpoint (or from `/api/stats` response)

---

## Production Database Migration

The migration must run on the live database. Two options:

**Option A: Automatic (recommended)**
The server already runs migrations on startup (`migrations.py`). Adding a v5 migration means deploying the new server version will auto-apply it. The `ALTER TABLE ADD COLUMN` is safe on a live table — Postgres doesn't lock the table for `ADD COLUMN` with no default.

**Option B: Manual (if you want to be cautious)**
```sql
-- Run on prod before deploying new server
ALTER TABLE sessions ADD COLUMN org TEXT;
CREATE INDEX CONCURRENTLY idx_sessions_org ON sessions (org);
```
`CREATE INDEX CONCURRENTLY` avoids locking the table during index creation. Use this if the sessions table is large.

**Backfill:** Existing sessions will have `org = NULL`. You can backfill if needed:
```sql
UPDATE sessions SET org = 'pratilipi' WHERE org IS NULL;
```

---

## Execution Order

1. Add v5 migration + update schema.sql
2. Update `SessionContext` in unified.py
3. Update `DaemonConfig` to read org
4. Update collector to pass org through
5. Update daemon main.py to pass org to collectors
6. Update writer.py to persist org
7. Update reader.py + handlers.py to filter by org
8. Update install.sh to accept --org
9. Update CLI status to show org
10. Deploy new server (auto-runs migration)
11. Re-install daemon on user machines with --org flag
12. Dashboard: add org filter (can be done separately)

---

## Verification

```bash
# Install with org
bash scripts/install.sh --org pratilipi

# Verify config
cat ~/.quickcall-trace/config.json
# → {"org": "pratilipi"}

# Verify daemon sends org
quickcall status
# → Org: pratilipi

# Verify in database
psql -c "SELECT DISTINCT org FROM sessions;"
# → pratilipi

# Verify API filtering
curl "http://localhost:19777/api/sessions?org=pratilipi"
```
