From 6ce00e29eee05bb7c50f585b3a56e291d6740dcb Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:43:32 +0000 Subject: [PATCH 001/208] chore: add schema creation script and update Directus workflow rules Add scripts/create_schema.py for programmatic Directus collection creation via REST API. Update CLAUDE.md Directus rules to document the new workflow: script -> verify -> sync pull -> commit. --- echo/CLAUDE.md | 117 +++++ echo/scripts/create_schema.py | 889 ++++++++++++++++++++++++++++++++++ 2 files changed, 1006 insertions(+) create mode 100644 echo/CLAUDE.md create mode 100644 echo/scripts/create_schema.py diff --git a/echo/CLAUDE.md b/echo/CLAUDE.md new file mode 100644 index 00000000..69932763 --- /dev/null +++ b/echo/CLAUDE.md @@ -0,0 +1,117 @@ +# CLAUDE.md + +Rules and conventions for working on the ECHO codebase. Follow these precisely. + +## Directus Rules (Critical) + +**Never hand-write Directus sync/snapshot JSON files.** To create or modify Directus collections: + +1. Write a Python script (e.g., `scripts/create_schema.py`) that uses the Directus REST API (`POST /collections`, `POST /fields`, `POST /relations`) with the admin token +2. Make scripts idempotent (check `collection_exists()` / `field_exists()` before creating) +3. Run the script step-by-step to verify each change +4. After all changes, pull the schema: `cd directus && bash sync.sh -u http://directus:8055 -e admin@dembrane.com -p admin pull` +5. Commit the sync output (the JSON files in `directus/sync/snapshot/`) + +See `scripts/create_schema.py` for the established pattern (Session 2 workspaces schema). + +### Python DirectusClient + +- `create_item` / `update_item` return `{"data": {...}}` — **MUST** unwrap with `["data"]` +- `get_items` / `get_item` return data directly (no wrapper) +- `get_items` requires `{"query": {filter, fields, sort, ...}}` wrapper +- `search()` silently returns `{"error": "..."}` on failure — always validate return is a list before iterating + +```python +# CORRECT +new = client.create_item("collection", {...})["data"] +items = client.get_items("collection", {"query": {"filter": {...}}}) +if not isinstance(items, list): + items = [] + +# WRONG — missing ["data"] unwrap +new = client.create_item("collection", {...}) +# WRONG — missing "query" wrapper +items = client.get_items("collection", {"filter": {...}}) +``` + +### TypeScript Directus SDK + +- Auto-unwraps everything — no `["data"]` needed +- If there's a type error with `.count`, add it to `typesDirectus.d.ts` and use `count("")` in fields + +See `memory/directus-rules.md` for comprehensive patterns. + +## Brand & UI Copy + +Follow `brand/STYLE_GUIDE.md` for all user-facing text: + +- **Never say "AI"** — use "language model" or just describe the action ("Generating your report..." not "Generating report with AI...") +- **Never say "successfully"** — just state what happened ("Saved" not "Successfully saved") +- **"dembrane" always lowercase**, even at sentence start +- **Never use bold for emphasis** — use Royal Blue (#4169e1) or italics +- Say "participants/hosts" not "users" +- Dutch translations: use informal "je/jij" form, keep English terms when they sound better (Dashboard, Upload, Chat) + +## UI Rules + +- **Never stack multiple Alert components** — show either the error alert or the info alert, not both +- **Don't use `@mantine/charts`** — use better charting libraries +- **Loading spinners**: always use `alwaysDembrane` prop on `DembraneLoadingSpinner` for whitelabel safety; never `animate-spin` on custom logos +- **Show emails only on hover** — don't display them by default in lists +- **Conversations come from QR codes or uploads** — never add "new conversation" buttons in the UI +- **Prefer text buttons over icon-only buttons** for important actions (e.g., "Go full screen" should be a text button) + +## Architecture Preferences + +- **BFF pattern**: move frontend Directus SDK calls to backend `/bff/` routes. Frontend should call aggregated API endpoints, not make multiple Directus queries +- **URL-driven state**: use URL search params (not React state) so state is shareable and persistent +- **SSE for progress**: use Server-Sent Events + Redis pub/sub for real-time progress (report generation, health streams) +- **No asyncio in Dramatiq actors**: use gevent pools + dramatiq groups instead. Report generation is fully synchronous +- **gevent.pool.Pool only in `network` queue** (uses `dramatiq-gevent`). CPU queue runs standard dramatiq +- **Use `gevent.sleep()` not `time.sleep()`** in network-queue actors + +## LLM Model Groups + +- `MULTI_MODAL_PRO` (Gemini 2.5 Pro) — chat, report generation, transcript correction. **Do not downgrade chat to Flash.** +- `MULTI_MODAL_FAST` (Gemini 2.5 Flash) — suggestions, verification, stateless endpoints, lightweight tasks +- `TEXT_FAST` (Azure GPT-4.1) — being deprecated, migrating to Gemini +- Report prompt templates are written IN the target language (not just instructing the LLM to write in that language) + +## Translations + +```bash +cd frontend +pnpm messages:extract # Extract new strings to .po files +# Edit .po files in src/locales/ (en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT) +pnpm messages:compile # Compile for production +``` + +Use `` component or `` t` `` template literal from Lingui. + +## Branching Strategy & Deployment + +See [docs/branching_and_releases.md](docs/branching_and_releases.md) for the full guide. + +Quick reference: +- **Feature flow**: branch off `main` → (optional) merge to `testing` for testing → PR to `main` → auto-deploys to Echo Next +- **Releases**: tagged from `main` every ~2 weeks → auto-deploys to production +- **Hotfixes**: branch off release tag → fix → new release → cherry-pick into main +- Always check for Directus data migrations before deploying (see `docs/database_migrations.md`) + +## Transcription + +- AssemblyAI `universal-3-pro` supports: en, es, pt, fr, de, it +- Dutch ("nl") requires `universal-2` fallback — `universal-3-pro` does NOT support it +- Production uses webhook mode (`ASSEMBLYAI_WEBHOOK_URL`), polling is only a fallback + +## Dramatiq Tasks + +- Restart workers after changing task signatures (positional args are serialized) +- `SkipRetryOnUnrecoverableError` middleware skips retries for TypeError, SyntaxError, AttributeError, ImportError, NotImplementedError +- When invoking async code from Dramatiq actors, use `run_async_in_new_loop` from `dembrane.async_helpers` + +## Project Management + +- Linear for issue tracking — tickets are `ECHO-xxx` +- Two-week cycles/sprints +- GitOps repo: `dembrane/echo-gitops` (separate repo) diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py new file mode 100644 index 00000000..61b6e30c --- /dev/null +++ b/echo/scripts/create_schema.py @@ -0,0 +1,889 @@ +""" +Session 2: Create workspace schema collections via Directus API. + +Usage: + python scripts/create_schema.py --step 1 # app_user only (test) + python scripts/create_schema.py --step 2 # org + org_membership + python scripts/create_schema.py --step 3 # workspace + workspace_membership + python scripts/create_schema.py --step 4 # workspace_invite + project_membership + python scripts/create_schema.py --step 5 # usage_event + python scripts/create_schema.py --step 6 # add fields to project + python scripts/create_schema.py --step 7 # add deleted_at to existing collections + python scripts/create_schema.py --step 8 # remove legacy chat collection + python scripts/create_schema.py --step all # everything + +Requires DIRECTUS_TOKEN and DIRECTUS_BASE_URL env vars (reads from directus/.env). +""" + +import argparse +import json +import os +import sys + +import requests + +# --------------------------------------------------------------------------- +# Config +# --------------------------------------------------------------------------- + +DIRECTUS_URL = os.environ.get("DIRECTUS_BASE_URL", "http://directus:8055") +DIRECTUS_TOKEN = os.environ.get("DIRECTUS_TOKEN", "") + +if not DIRECTUS_TOKEN: + # Try reading from directus/.env + env_path = os.path.join(os.path.dirname(__file__), "..", "directus", ".env") + if os.path.exists(env_path): + with open(env_path) as f: + for line in f: + line = line.strip() + if line.startswith("DIRECTUS_TOKEN="): + DIRECTUS_TOKEN = line.split("=", 1)[1].strip().strip('"').strip("'") + +HEADERS = { + "Authorization": f"Bearer {DIRECTUS_TOKEN}", + "Content-Type": "application/json", +} + + +def api(method, path, data=None): + """Make a Directus API call. Returns response JSON or raises on error.""" + url = f"{DIRECTUS_URL}{path}" + resp = requests.request(method, url, headers=HEADERS, json=data, timeout=30) + if resp.status_code >= 400: + print(f" ERROR {resp.status_code}: {resp.text[:500]}") + return None + if resp.status_code == 204: + return {} + return resp.json() + + +def collection_exists(name): + """Check if a collection already exists.""" + resp = requests.get( + f"{DIRECTUS_URL}/collections/{name}", headers=HEADERS, timeout=10 + ) + return resp.status_code == 200 + + +def field_exists(collection, field): + """Check if a field already exists on a collection.""" + resp = requests.get( + f"{DIRECTUS_URL}/fields/{collection}/{field}", headers=HEADERS, timeout=10 + ) + return resp.status_code == 200 + + +def create_collection(name, fields, meta=None): + """Create a collection with fields. Skips if already exists.""" + if collection_exists(name): + print(f" SKIP {name} (already exists)") + return True + + payload = { + "collection": name, + "meta": meta or {}, + "schema": {}, + "fields": fields, + } + print(f" Creating collection: {name}") + result = api("POST", "/collections", payload) + if result: + print(f" OK {name} created") + return True + return False + + +def add_field(collection, field_name, field_def): + """Add a field to an existing collection. Skips if already exists.""" + if field_exists(collection, field_name): + print(f" SKIP {collection}.{field_name} (already exists)") + return True + + payload = {"field": field_name, **field_def} + print(f" Adding field: {collection}.{field_name}") + result = api("POST", f"/fields/{collection}", payload) + if result: + print(f" OK {collection}.{field_name} added") + return True + return False + + +def create_relation(collection, field, related_collection, meta=None, schema=None): + """Create a M2O relation.""" + payload = { + "collection": collection, + "field": field, + "related_collection": related_collection, + } + if meta: + payload["meta"] = meta + if schema: + payload["schema"] = schema + + print(f" Creating relation: {collection}.{field} -> {related_collection}") + result = api("POST", "/relations", payload) + if result: + print(f" OK relation created") + return True + return False + + +# --------------------------------------------------------------------------- +# Field definitions (reusable) +# --------------------------------------------------------------------------- + +def pk_uuid(): + return { + "field": "id", + "type": "uuid", + "schema": {"is_primary_key": True, "has_auto_increment": False}, + "meta": {"special": ["uuid"], "interface": "input", "readonly": True, "hidden": True}, + } + + +def timestamp_created(): + return { + "type": "timestamp", + "schema": {"is_nullable": True, "default_value": "CURRENT_TIMESTAMP"}, + "meta": {"special": ["date-created"], "interface": "datetime", "readonly": True, + "width": "half"}, + } + + +def timestamp_updated(): + return { + "type": "timestamp", + "schema": {"is_nullable": True, "default_value": "CURRENT_TIMESTAMP"}, + "meta": {"special": ["date-updated"], "interface": "datetime", "readonly": True, + "width": "half"}, + } + + +def deleted_at_field(): + return { + "type": "timestamp", + "schema": {"is_nullable": True, "default_value": None}, + "meta": {"interface": "datetime", "width": "half", "note": "Soft delete timestamp"}, + } + + +# --------------------------------------------------------------------------- +# Step 1: app_user +# --------------------------------------------------------------------------- + +def step_1_app_user(): + print("\n=== Step 1: app_user ===") + + fields = [ + pk_uuid(), + { + "field": "directus_user_id", + "type": "uuid", + "schema": {"is_nullable": True, "is_unique": True}, + "meta": {"interface": "input", "note": "Maps to directus_users.id"}, + }, + { + "field": "email", + "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}, + }, + { + "field": "display_name", + "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}, + }, + { + "field": "created_at", + **timestamp_created(), + }, + { + "field": "updated_at", + **timestamp_updated(), + }, + ] + + meta = { + "accountability": "all", + "display_template": "{{display_name}}", + } + + ok = create_collection("app_user", fields, meta) + if not ok: + return False + + # Note: directus_user_id is a logical FK to directus_users but we do NOT + # create a Directus relation for it. This is intentional — app_user is our + # indirection layer and we don't want Directus managing this relationship. + + return True + + +# --------------------------------------------------------------------------- +# Step 2: org + org_membership +# --------------------------------------------------------------------------- + +def step_2_org(): + print("\n=== Step 2: org + org_membership ===") + + # --- org --- + org_fields = [ + pk_uuid(), + { + "field": "name", + "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "logo_url", + "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}, + }, + { + "field": "created_by", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "FK to app_user.id"}, + }, + { + "field": "deleted_at", + **deleted_at_field(), + }, + { + "field": "created_at", + **timestamp_created(), + }, + { + "field": "updated_at", + **timestamp_updated(), + }, + ] + + ok = create_collection("org", org_fields, { + "accountability": "all", + "display_template": "{{name}}", + }) + if not ok: + return False + + # Relation: org.created_by -> app_user + create_relation("org", "created_by", "app_user", schema={"on_delete": "SET NULL"}) + + # --- org_membership --- + om_fields = [ + pk_uuid(), + { + "field": "org_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "user_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "role", + "type": "string", + "schema": {"is_nullable": False}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Owner", "value": "owner"}, + {"text": "Admin", "value": "admin"}, + {"text": "Member", "value": "member"}, + ]}, + "required": True, + }, + }, + { + "field": "custom_policies", + "type": "json", + "schema": {"is_nullable": True, "default_value": "[]"}, + "meta": {"interface": "input-code", "options": {"language": "json"}, + "note": "Extra policies beyond role preset. Usually empty."}, + }, + { + "field": "deleted_at", + **deleted_at_field(), + }, + { + "field": "created_at", + **timestamp_created(), + }, + { + "field": "updated_at", + **timestamp_updated(), + }, + ] + + ok = create_collection("org_membership", om_fields, { + "accountability": "all", + }) + if not ok: + return False + + # Relations + create_relation("org_membership", "org_id", "org", schema={"on_delete": "CASCADE"}) + create_relation("org_membership", "user_id", "app_user", schema={"on_delete": "CASCADE"}) + + return True + + +# --------------------------------------------------------------------------- +# Step 3: workspace + workspace_membership +# --------------------------------------------------------------------------- + +def step_3_workspace(): + print("\n=== Step 3: workspace + workspace_membership ===") + + # --- workspace --- + ws_fields = [ + pk_uuid(), + { + "field": "org_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "name", + "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "description", + "type": "text", + "schema": {"is_nullable": True}, + "meta": {"interface": "input-multiline"}, + }, + { + "field": "logo_url", + "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Override org logo"}, + }, + { + "field": "tier", + "type": "string", + "schema": {"is_nullable": False, "default_value": "pioneer"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Pilot", "value": "pilot"}, + {"text": "Pioneer", "value": "pioneer"}, + {"text": "Innovator", "value": "innovator"}, + {"text": "Changemaker", "value": "changemaker"}, + {"text": "Guardian", "value": "guardian"}, + ]}, + }, + }, + { + "field": "billed_to_workspace_id", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Partner billing. NULL = org pays."}, + }, + { + "field": "is_default", + "type": "boolean", + "schema": {"is_nullable": False, "default_value": False}, + "meta": {"interface": "boolean"}, + }, + { + "field": "legal_basis", + "type": "string", + "schema": {"is_nullable": True}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Consent", "value": "consent"}, + {"text": "Client-managed", "value": "client-managed"}, + {"text": "Dembrane Events", "value": "dembrane-events"}, + ]}, + }, + }, + { + "field": "privacy_policy_url", + "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}, + }, + { + "field": "settings", + "type": "json", + "schema": {"is_nullable": True, "default_value": "{}"}, + "meta": {"interface": "input-code", "options": {"language": "json"}, + "note": "Feature flags, limits"}, + }, + { + "field": "deleted_at", + **deleted_at_field(), + }, + { + "field": "created_by", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "FK to app_user.id"}, + }, + { + "field": "created_at", + **timestamp_created(), + }, + { + "field": "updated_at", + **timestamp_updated(), + }, + ] + + ok = create_collection("workspace", ws_fields, { + "accountability": "all", + "display_template": "{{name}}", + }) + if not ok: + return False + + # Relations + create_relation("workspace", "org_id", "org", schema={"on_delete": "CASCADE"}) + create_relation("workspace", "created_by", "app_user", schema={"on_delete": "SET NULL"}) + create_relation("workspace", "billed_to_workspace_id", "workspace", + schema={"on_delete": "SET NULL"}) + + # --- workspace_membership --- + wm_fields = [ + pk_uuid(), + { + "field": "workspace_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "user_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "role", + "type": "string", + "schema": {"is_nullable": False}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Owner", "value": "owner"}, + {"text": "Admin", "value": "admin"}, + {"text": "Member", "value": "member"}, + {"text": "Viewer", "value": "viewer"}, + ]}, + "required": True, + }, + }, + { + "field": "custom_policies", + "type": "json", + "schema": {"is_nullable": True, "default_value": "[]"}, + "meta": {"interface": "input-code", "options": {"language": "json"}, + "note": "Extra policies beyond role preset. Usually empty."}, + }, + { + "field": "source", + "type": "string", + "schema": {"is_nullable": False, "default_value": "direct"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Direct", "value": "direct"}, + {"text": "Inherited", "value": "inherited"}, + ]}, + "note": "direct = explicitly invited. inherited = auto-added from org role.", + }, + }, + { + "field": "is_external", + "type": "boolean", + "schema": {"is_nullable": False, "default_value": False}, + "meta": {"interface": "boolean", + "note": "True if user's primary org != workspace's org"}, + }, + { + "field": "deleted_at", + **deleted_at_field(), + }, + { + "field": "created_at", + **timestamp_created(), + }, + { + "field": "updated_at", + **timestamp_updated(), + }, + ] + + ok = create_collection("workspace_membership", wm_fields, { + "accountability": "all", + }) + if not ok: + return False + + create_relation("workspace_membership", "workspace_id", "workspace", + schema={"on_delete": "CASCADE"}) + create_relation("workspace_membership", "user_id", "app_user", + schema={"on_delete": "CASCADE"}) + + return True + + +# --------------------------------------------------------------------------- +# Step 4: workspace_invite + project_membership +# --------------------------------------------------------------------------- + +def step_4_invite_and_project_membership(): + print("\n=== Step 4: workspace_invite + project_membership ===") + + # --- workspace_invite --- + wi_fields = [ + pk_uuid(), + { + "field": "workspace_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "email", + "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "role", + "type": "string", + "schema": {"is_nullable": False}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Admin", "value": "admin"}, + {"text": "Member", "value": "member"}, + {"text": "Viewer", "value": "viewer"}, + ]}, + "required": True, + "note": "Role to assign on acceptance", + }, + }, + { + "field": "invited_by", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "FK to app_user.id"}, + }, + { + "field": "token", + "type": "string", + "schema": {"is_nullable": False, "is_unique": True}, + "meta": {"interface": "input", "note": "secrets.token_urlsafe(32)"}, + }, + { + "field": "expires_at", + "type": "timestamp", + "schema": {"is_nullable": False}, + "meta": {"interface": "datetime", "note": "7 days from creation"}, + }, + { + "field": "accepted_at", + "type": "timestamp", + "schema": {"is_nullable": True}, + "meta": {"interface": "datetime"}, + }, + { + "field": "created_at", + **timestamp_created(), + }, + ] + + ok = create_collection("workspace_invite", wi_fields, { + "accountability": "all", + }) + if not ok: + return False + + create_relation("workspace_invite", "workspace_id", "workspace", + schema={"on_delete": "CASCADE"}) + create_relation("workspace_invite", "invited_by", "app_user", + schema={"on_delete": "SET NULL"}) + + # --- project_membership --- + pm_fields = [ + pk_uuid(), + { + "field": "project_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "user_id", + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "role", + "type": "string", + "schema": {"is_nullable": False, "default_value": "editor"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Editor", "value": "editor"}, + {"text": "Viewer", "value": "viewer"}, + ]}, + }, + }, + { + "field": "custom_policies", + "type": "json", + "schema": {"is_nullable": True, "default_value": "[]"}, + "meta": {"interface": "input-code", "options": {"language": "json"}, + "note": "Extra policies beyond role preset. Usually empty."}, + }, + { + "field": "granted_by", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "FK to app_user.id"}, + }, + { + "field": "created_at", + **timestamp_created(), + }, + ] + + ok = create_collection("project_membership", pm_fields, { + "accountability": "all", + }) + if not ok: + return False + + create_relation("project_membership", "project_id", "project", + schema={"on_delete": "CASCADE"}) + create_relation("project_membership", "user_id", "app_user", + schema={"on_delete": "CASCADE"}) + create_relation("project_membership", "granted_by", "app_user", + schema={"on_delete": "SET NULL"}) + + return True + + +# --------------------------------------------------------------------------- +# Step 5: usage_event +# --------------------------------------------------------------------------- + +def step_5_usage_event(): + print("\n=== Step 5: usage_event ===") + + fields = [ + pk_uuid(), + { + "field": "trace_id", + "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Request correlation ID"}, + }, + { + "field": "org_id", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Reference only, no FK constraint"}, + }, + { + "field": "workspace_id", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Reference only, no FK constraint"}, + }, + { + "field": "project_id", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Reference only, no FK constraint"}, + }, + { + "field": "user_id", + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "Reference only, no FK constraint"}, + }, + { + "field": "event_type", + "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}, + }, + { + "field": "event_data", + "type": "json", + "schema": {"is_nullable": True, "default_value": "{}"}, + "meta": {"interface": "input-code", "options": {"language": "json"}, + "note": "Always include \"v\": 1 for schema versioning"}, + }, + { + "field": "created_at", + **timestamp_created(), + }, + ] + + ok = create_collection("usage_event", fields, { + "accountability": "all", + "note": "Append-only. Never updated. Never deleted.", + }) + + # No FK relations — these are reference-only UUID fields + return ok + + +# --------------------------------------------------------------------------- +# Step 6: Add fields to project +# --------------------------------------------------------------------------- + +def step_6_project_fields(): + print("\n=== Step 6: Add fields to project ===") + + add_field("project", "workspace_id", { + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "FK to workspace. NULL during migration."}, + }) + + # Create the relation for workspace_id + if not field_exists("project", "workspace_id"): + pass # field creation failed, skip relation + else: + create_relation("project", "workspace_id", "workspace", + schema={"on_delete": "SET NULL"}) + + add_field("project", "visibility", { + "type": "string", + "schema": {"is_nullable": False, "default_value": "workspace"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Workspace", "value": "workspace"}, + {"text": "Private", "value": "private"}, + ]}, + "note": "workspace = visible to all workspace members. private = explicit sharing.", + }, + }) + + add_field("project", "deleted_at", { + **deleted_at_field(), + }) + + return True + + +# --------------------------------------------------------------------------- +# Step 7: Add deleted_at to existing collections +# --------------------------------------------------------------------------- + +def step_7_deleted_at(): + print("\n=== Step 7: Add deleted_at to existing collections ===") + + for collection in ["conversation", "project_chat", "project_report"]: + add_field(collection, "deleted_at", { + **deleted_at_field(), + }) + + return True + + +# --------------------------------------------------------------------------- +# Step 8: Remove legacy chat collection +# --------------------------------------------------------------------------- + +def step_8_remove_chat(): + print("\n=== Step 8: Remove legacy chat collection ===") + + if not collection_exists("chat"): + print(" SKIP chat (already removed)") + return True + + # Verify it's empty first + resp = api("GET", "/items/chat?limit=0&meta=total_count") + if resp and resp.get("meta", {}).get("total_count", 0) > 0: + count = resp["meta"]["total_count"] + print(f" ABORT: chat collection has {count} rows! Not safe to remove.") + return False + + print(" Confirmed: chat collection is empty") + print(" Deleting chat collection...") + result = api("DELETE", "/collections/chat") + if result is not None: + print(" OK chat collection removed") + return True + return False + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +STEPS = { + "1": ("app_user", step_1_app_user), + "2": ("org + org_membership", step_2_org), + "3": ("workspace + workspace_membership", step_3_workspace), + "4": ("workspace_invite + project_membership", step_4_invite_and_project_membership), + "5": ("usage_event", step_5_usage_event), + "6": ("project fields (workspace_id, visibility, deleted_at)", step_6_project_fields), + "7": ("deleted_at on conversation, project_chat, project_report", step_7_deleted_at), + "8": ("remove legacy chat", step_8_remove_chat), +} + + +def main(): + parser = argparse.ArgumentParser(description="Create workspace schema in Directus") + parser.add_argument("--step", required=True, + help="Step number (1-8) or 'all'") + args = parser.parse_args() + + # Verify connection + print(f"Directus URL: {DIRECTUS_URL}") + health = api("GET", "/server/health") + if not health: + print("ERROR: Cannot connect to Directus") + sys.exit(1) + print("Directus is healthy\n") + + if args.step == "all": + steps_to_run = list(STEPS.keys()) + else: + steps_to_run = [args.step] + + for step_num in steps_to_run: + if step_num not in STEPS: + print(f"ERROR: Unknown step {step_num}. Valid: 1-8 or 'all'") + sys.exit(1) + + name, fn = STEPS[step_num] + print(f"{'='*60}") + print(f"Step {step_num}: {name}") + print(f"{'='*60}") + + ok = fn() + if not ok: + print(f"\nStep {step_num} FAILED. Stopping.") + sys.exit(1) + + print(f"Step {step_num} complete.\n") + + print("\nAll requested steps complete.") + print("Next: run 'cd directus && bash sync.sh pull' to capture schema changes.") + + +if __name__ == "__main__": + main() From 6f34b48180e6891a3ddf0cd8aaffb73209fe2208 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:43:40 +0000 Subject: [PATCH 002/208] chore: create app_user collection Indirection layer between domain tables and Directus auth. All new workspace/org tables FK to app_user.id instead of directus_users.id. Fields: id (uuid PK), directus_user_id (unique), email, display_name, created_at, updated_at. --- .../sync/snapshot/collections/app_user.json | 28 +++++++++++ .../snapshot/fields/app_user/created_at.json | 46 +++++++++++++++++++ .../fields/app_user/directus_user_id.json | 44 ++++++++++++++++++ .../fields/app_user/display_name.json | 44 ++++++++++++++++++ .../sync/snapshot/fields/app_user/email.json | 44 ++++++++++++++++++ .../sync/snapshot/fields/app_user/id.json | 46 +++++++++++++++++++ .../snapshot/fields/app_user/updated_at.json | 46 +++++++++++++++++++ 7 files changed, 298 insertions(+) create mode 100644 echo/directus/sync/snapshot/collections/app_user.json create mode 100644 echo/directus/sync/snapshot/fields/app_user/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/app_user/directus_user_id.json create mode 100644 echo/directus/sync/snapshot/fields/app_user/display_name.json create mode 100644 echo/directus/sync/snapshot/fields/app_user/email.json create mode 100644 echo/directus/sync/snapshot/fields/app_user/id.json create mode 100644 echo/directus/sync/snapshot/fields/app_user/updated_at.json diff --git a/echo/directus/sync/snapshot/collections/app_user.json b/echo/directus/sync/snapshot/collections/app_user.json new file mode 100644 index 00000000..1f7c3bfa --- /dev/null +++ b/echo/directus/sync/snapshot/collections/app_user.json @@ -0,0 +1,28 @@ +{ + "collection": "app_user", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "app_user", + "color": null, + "display_template": "{{display_name}}", + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "app_user" + } +} diff --git a/echo/directus/sync/snapshot/fields/app_user/created_at.json b/echo/directus/sync/snapshot/fields/app_user/created_at.json new file mode 100644 index 00000000..2259bf37 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/app_user/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "app_user", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "app_user", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 5, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "app_user", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/app_user/directus_user_id.json b/echo/directus/sync/snapshot/fields/app_user/directus_user_id.json new file mode 100644 index 00000000..45c3ec9c --- /dev/null +++ b/echo/directus/sync/snapshot/fields/app_user/directus_user_id.json @@ -0,0 +1,44 @@ +{ + "collection": "app_user", + "field": "directus_user_id", + "type": "uuid", + "meta": { + "collection": "app_user", + "conditions": null, + "display": null, + "display_options": null, + "field": "directus_user_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Maps to directus_users.id", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "directus_user_id", + "table": "app_user", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": true, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/app_user/display_name.json b/echo/directus/sync/snapshot/fields/app_user/display_name.json new file mode 100644 index 00000000..dedaa4a8 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/app_user/display_name.json @@ -0,0 +1,44 @@ +{ + "collection": "app_user", + "field": "display_name", + "type": "string", + "meta": { + "collection": "app_user", + "conditions": null, + "display": null, + "display_options": null, + "field": "display_name", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "display_name", + "table": "app_user", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/app_user/email.json b/echo/directus/sync/snapshot/fields/app_user/email.json new file mode 100644 index 00000000..7d710faa --- /dev/null +++ b/echo/directus/sync/snapshot/fields/app_user/email.json @@ -0,0 +1,44 @@ +{ + "collection": "app_user", + "field": "email", + "type": "string", + "meta": { + "collection": "app_user", + "conditions": null, + "display": null, + "display_options": null, + "field": "email", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "email", + "table": "app_user", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/app_user/id.json b/echo/directus/sync/snapshot/fields/app_user/id.json new file mode 100644 index 00000000..6e6e6a41 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/app_user/id.json @@ -0,0 +1,46 @@ +{ + "collection": "app_user", + "field": "id", + "type": "uuid", + "meta": { + "collection": "app_user", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "app_user", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/app_user/updated_at.json b/echo/directus/sync/snapshot/fields/app_user/updated_at.json new file mode 100644 index 00000000..78573e83 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/app_user/updated_at.json @@ -0,0 +1,46 @@ +{ + "collection": "app_user", + "field": "updated_at", + "type": "timestamp", + "meta": { + "collection": "app_user", + "conditions": null, + "display": null, + "display_options": null, + "field": "updated_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 6, + "special": [ + "date-updated" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "updated_at", + "table": "app_user", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} From 31ab178acd80ffc0626ea4305bf571176fe3355a Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:43:47 +0000 Subject: [PATCH 003/208] chore: create org and org_membership collections org: id, name, logo_url, created_by (FK app_user), deleted_at, timestamps. org_membership: id, org_id (FK org), user_id (FK app_user), role (owner/admin/member), custom_policies (json), deleted_at, timestamps. Policies use computed-at-runtime pattern: role maps to preset policy array in Python, custom_policies stores extras beyond preset. --- .../sync/snapshot/collections/org.json | 28 +++++++++ .../snapshot/collections/org_membership.json | 28 +++++++++ .../sync/snapshot/fields/org/created_at.json | 46 +++++++++++++++ .../sync/snapshot/fields/org/created_by.json | 44 ++++++++++++++ .../sync/snapshot/fields/org/deleted_at.json | 44 ++++++++++++++ .../directus/sync/snapshot/fields/org/id.json | 46 +++++++++++++++ .../sync/snapshot/fields/org/logo_url.json | 44 ++++++++++++++ .../sync/snapshot/fields/org/name.json | 44 ++++++++++++++ .../sync/snapshot/fields/org/updated_at.json | 46 +++++++++++++++ .../fields/org_membership/created_at.json | 46 +++++++++++++++ .../org_membership/custom_policies.json | 46 +++++++++++++++ .../fields/org_membership/deleted_at.json | 44 ++++++++++++++ .../snapshot/fields/org_membership/id.json | 46 +++++++++++++++ .../fields/org_membership/org_id.json | 44 ++++++++++++++ .../snapshot/fields/org_membership/role.json | 59 +++++++++++++++++++ .../fields/org_membership/updated_at.json | 46 +++++++++++++++ .../fields/org_membership/user_id.json | 44 ++++++++++++++ .../snapshot/relations/org/created_by.json | 25 ++++++++ .../relations/org_membership/org_id.json | 25 ++++++++ .../relations/org_membership/user_id.json | 25 ++++++++ 20 files changed, 820 insertions(+) create mode 100644 echo/directus/sync/snapshot/collections/org.json create mode 100644 echo/directus/sync/snapshot/collections/org_membership.json create mode 100644 echo/directus/sync/snapshot/fields/org/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/org/created_by.json create mode 100644 echo/directus/sync/snapshot/fields/org/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/org/id.json create mode 100644 echo/directus/sync/snapshot/fields/org/logo_url.json create mode 100644 echo/directus/sync/snapshot/fields/org/name.json create mode 100644 echo/directus/sync/snapshot/fields/org/updated_at.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/custom_policies.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/id.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/org_id.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/role.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/updated_at.json create mode 100644 echo/directus/sync/snapshot/fields/org_membership/user_id.json create mode 100644 echo/directus/sync/snapshot/relations/org/created_by.json create mode 100644 echo/directus/sync/snapshot/relations/org_membership/org_id.json create mode 100644 echo/directus/sync/snapshot/relations/org_membership/user_id.json diff --git a/echo/directus/sync/snapshot/collections/org.json b/echo/directus/sync/snapshot/collections/org.json new file mode 100644 index 00000000..32b1c6f8 --- /dev/null +++ b/echo/directus/sync/snapshot/collections/org.json @@ -0,0 +1,28 @@ +{ + "collection": "org", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "org", + "color": null, + "display_template": "{{name}}", + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "org" + } +} diff --git a/echo/directus/sync/snapshot/collections/org_membership.json b/echo/directus/sync/snapshot/collections/org_membership.json new file mode 100644 index 00000000..797db440 --- /dev/null +++ b/echo/directus/sync/snapshot/collections/org_membership.json @@ -0,0 +1,28 @@ +{ + "collection": "org_membership", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "org_membership", + "color": null, + "display_template": null, + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "org_membership" + } +} diff --git a/echo/directus/sync/snapshot/fields/org/created_at.json b/echo/directus/sync/snapshot/fields/org/created_at.json new file mode 100644 index 00000000..4616c4fa --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "org", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 6, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "org", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org/created_by.json b/echo/directus/sync/snapshot/fields/org/created_by.json new file mode 100644 index 00000000..d94b9574 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/created_by.json @@ -0,0 +1,44 @@ +{ + "collection": "org", + "field": "created_by", + "type": "uuid", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_by", + "group": null, + "hidden": false, + "interface": "input", + "note": "FK to app_user.id", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "created_by", + "table": "org", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/org/deleted_at.json b/echo/directus/sync/snapshot/fields/org/deleted_at.json new file mode 100644 index 00000000..eb50ef2c --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "org", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "org", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org/id.json b/echo/directus/sync/snapshot/fields/org/id.json new file mode 100644 index 00000000..26fd8707 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/id.json @@ -0,0 +1,46 @@ +{ + "collection": "org", + "field": "id", + "type": "uuid", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "org", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org/logo_url.json b/echo/directus/sync/snapshot/fields/org/logo_url.json new file mode 100644 index 00000000..6fd1f873 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/logo_url.json @@ -0,0 +1,44 @@ +{ + "collection": "org", + "field": "logo_url", + "type": "string", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "logo_url", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "logo_url", + "table": "org", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org/name.json b/echo/directus/sync/snapshot/fields/org/name.json new file mode 100644 index 00000000..abb3ee9d --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/name.json @@ -0,0 +1,44 @@ +{ + "collection": "org", + "field": "name", + "type": "string", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "name", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "name", + "table": "org", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org/updated_at.json b/echo/directus/sync/snapshot/fields/org/updated_at.json new file mode 100644 index 00000000..b84b5ae0 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org/updated_at.json @@ -0,0 +1,46 @@ +{ + "collection": "org", + "field": "updated_at", + "type": "timestamp", + "meta": { + "collection": "org", + "conditions": null, + "display": null, + "display_options": null, + "field": "updated_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 7, + "special": [ + "date-updated" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "updated_at", + "table": "org", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/created_at.json b/echo/directus/sync/snapshot/fields/org_membership/created_at.json new file mode 100644 index 00000000..14a9d7fb --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "org_membership", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 7, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "org_membership", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/custom_policies.json b/echo/directus/sync/snapshot/fields/org_membership/custom_policies.json new file mode 100644 index 00000000..1a1c765f --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/custom_policies.json @@ -0,0 +1,46 @@ +{ + "collection": "org_membership", + "field": "custom_policies", + "type": "json", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "custom_policies", + "group": null, + "hidden": false, + "interface": "input-code", + "note": "Extra policies beyond role preset. Usually empty.", + "options": { + "language": "json" + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "custom_policies", + "table": "org_membership", + "data_type": "json", + "default_value": [], + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/deleted_at.json b/echo/directus/sync/snapshot/fields/org_membership/deleted_at.json new file mode 100644 index 00000000..e7782b1a --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "org_membership", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "org_membership", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/id.json b/echo/directus/sync/snapshot/fields/org_membership/id.json new file mode 100644 index 00000000..f9b3234f --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/id.json @@ -0,0 +1,46 @@ +{ + "collection": "org_membership", + "field": "id", + "type": "uuid", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "org_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/org_id.json b/echo/directus/sync/snapshot/fields/org_membership/org_id.json new file mode 100644 index 00000000..93ce57e7 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/org_id.json @@ -0,0 +1,44 @@ +{ + "collection": "org_membership", + "field": "org_id", + "type": "uuid", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "org_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "org_id", + "table": "org_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "org", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/role.json b/echo/directus/sync/snapshot/fields/org_membership/role.json new file mode 100644 index 00000000..76b87f1f --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/role.json @@ -0,0 +1,59 @@ +{ + "collection": "org_membership", + "field": "role", + "type": "string", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "role", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": null, + "options": { + "choices": [ + { + "text": "Owner", + "value": "owner" + }, + { + "text": "Admin", + "value": "admin" + }, + { + "text": "Member", + "value": "member" + } + ] + }, + "readonly": false, + "required": true, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "role", + "table": "org_membership", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/updated_at.json b/echo/directus/sync/snapshot/fields/org_membership/updated_at.json new file mode 100644 index 00000000..9955a3f1 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/updated_at.json @@ -0,0 +1,46 @@ +{ + "collection": "org_membership", + "field": "updated_at", + "type": "timestamp", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "updated_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 8, + "special": [ + "date-updated" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "updated_at", + "table": "org_membership", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/org_membership/user_id.json b/echo/directus/sync/snapshot/fields/org_membership/user_id.json new file mode 100644 index 00000000..9bc7379f --- /dev/null +++ b/echo/directus/sync/snapshot/fields/org_membership/user_id.json @@ -0,0 +1,44 @@ +{ + "collection": "org_membership", + "field": "user_id", + "type": "uuid", + "meta": { + "collection": "org_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "user_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "user_id", + "table": "org_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/relations/org/created_by.json b/echo/directus/sync/snapshot/relations/org/created_by.json new file mode 100644 index 00000000..ab056433 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/org/created_by.json @@ -0,0 +1,25 @@ +{ + "collection": "org", + "field": "created_by", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "org", + "many_field": "created_by", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "org", + "column": "created_by", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "org_created_by_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/org_membership/org_id.json b/echo/directus/sync/snapshot/relations/org_membership/org_id.json new file mode 100644 index 00000000..f7c40a48 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/org_membership/org_id.json @@ -0,0 +1,25 @@ +{ + "collection": "org_membership", + "field": "org_id", + "related_collection": "org", + "meta": { + "junction_field": null, + "many_collection": "org_membership", + "many_field": "org_id", + "one_allowed_collections": null, + "one_collection": "org", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "org_membership", + "column": "org_id", + "foreign_key_table": "org", + "foreign_key_column": "id", + "constraint_name": "org_membership_org_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/directus/sync/snapshot/relations/org_membership/user_id.json b/echo/directus/sync/snapshot/relations/org_membership/user_id.json new file mode 100644 index 00000000..e9bcb655 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/org_membership/user_id.json @@ -0,0 +1,25 @@ +{ + "collection": "org_membership", + "field": "user_id", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "org_membership", + "many_field": "user_id", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "org_membership", + "column": "user_id", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "org_membership_user_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} From 9492e656ed07e5a65f973214a00092f439f7e5b8 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:43:55 +0000 Subject: [PATCH 004/208] chore: create workspace and workspace_membership collections workspace: id, org_id (FK org CASCADE), name, description, logo_url, tier (pioneer default), billed_to_workspace_id (self-ref FK SET NULL), is_default, legal_basis, privacy_policy_url, settings (json), deleted_at, created_by (FK app_user), timestamps. workspace_membership: id, workspace_id (FK workspace CASCADE), user_id (FK app_user CASCADE), role (owner/admin/member/viewer), custom_policies (json), source (direct/inherited), is_external, deleted_at, timestamps. --- .../sync/snapshot/collections/workspace.json | 28 ++++++++ .../collections/workspace_membership.json | 28 ++++++++ .../workspace/billed_to_workspace_id.json | 44 ++++++++++++ .../snapshot/fields/workspace/created_at.json | 46 +++++++++++++ .../snapshot/fields/workspace/created_by.json | 44 ++++++++++++ .../snapshot/fields/workspace/deleted_at.json | 44 ++++++++++++ .../fields/workspace/description.json | 44 ++++++++++++ .../sync/snapshot/fields/workspace/id.json | 46 +++++++++++++ .../snapshot/fields/workspace/is_default.json | 44 ++++++++++++ .../fields/workspace/legal_basis.json | 59 ++++++++++++++++ .../snapshot/fields/workspace/logo_url.json | 44 ++++++++++++ .../sync/snapshot/fields/workspace/name.json | 44 ++++++++++++ .../snapshot/fields/workspace/org_id.json | 44 ++++++++++++ .../fields/workspace/privacy_policy_url.json | 44 ++++++++++++ .../snapshot/fields/workspace/settings.json | 46 +++++++++++++ .../sync/snapshot/fields/workspace/tier.json | 67 +++++++++++++++++++ .../snapshot/fields/workspace/updated_at.json | 46 +++++++++++++ .../workspace_membership/created_at.json | 46 +++++++++++++ .../workspace_membership/custom_policies.json | 46 +++++++++++++ .../workspace_membership/deleted_at.json | 44 ++++++++++++ .../fields/workspace_membership/id.json | 46 +++++++++++++ .../workspace_membership/is_external.json | 44 ++++++++++++ .../fields/workspace_membership/role.json | 63 +++++++++++++++++ .../fields/workspace_membership/source.json | 55 +++++++++++++++ .../workspace_membership/updated_at.json | 46 +++++++++++++ .../fields/workspace_membership/user_id.json | 44 ++++++++++++ .../workspace_membership/workspace_id.json | 44 ++++++++++++ .../workspace/billed_to_workspace_id.json | 25 +++++++ .../relations/workspace/created_by.json | 25 +++++++ .../snapshot/relations/workspace/org_id.json | 25 +++++++ .../workspace_membership/user_id.json | 25 +++++++ .../workspace_membership/workspace_id.json | 25 +++++++ 32 files changed, 1365 insertions(+) create mode 100644 echo/directus/sync/snapshot/collections/workspace.json create mode 100644 echo/directus/sync/snapshot/collections/workspace_membership.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/billed_to_workspace_id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/created_by.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/description.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/is_default.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/legal_basis.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/logo_url.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/name.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/org_id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/privacy_policy_url.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/settings.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/tier.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/updated_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/custom_policies.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/is_external.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/role.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/source.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/updated_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/user_id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_membership/workspace_id.json create mode 100644 echo/directus/sync/snapshot/relations/workspace/billed_to_workspace_id.json create mode 100644 echo/directus/sync/snapshot/relations/workspace/created_by.json create mode 100644 echo/directus/sync/snapshot/relations/workspace/org_id.json create mode 100644 echo/directus/sync/snapshot/relations/workspace_membership/user_id.json create mode 100644 echo/directus/sync/snapshot/relations/workspace_membership/workspace_id.json diff --git a/echo/directus/sync/snapshot/collections/workspace.json b/echo/directus/sync/snapshot/collections/workspace.json new file mode 100644 index 00000000..1d5036fa --- /dev/null +++ b/echo/directus/sync/snapshot/collections/workspace.json @@ -0,0 +1,28 @@ +{ + "collection": "workspace", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "workspace", + "color": null, + "display_template": "{{name}}", + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "workspace" + } +} diff --git a/echo/directus/sync/snapshot/collections/workspace_membership.json b/echo/directus/sync/snapshot/collections/workspace_membership.json new file mode 100644 index 00000000..207d9cee --- /dev/null +++ b/echo/directus/sync/snapshot/collections/workspace_membership.json @@ -0,0 +1,28 @@ +{ + "collection": "workspace_membership", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "workspace_membership", + "color": null, + "display_template": null, + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "workspace_membership" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/billed_to_workspace_id.json b/echo/directus/sync/snapshot/fields/workspace/billed_to_workspace_id.json new file mode 100644 index 00000000..a85d4eaa --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/billed_to_workspace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "billed_to_workspace_id", + "type": "uuid", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "billed_to_workspace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Partner billing. NULL = org pays.", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 7, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "billed_to_workspace_id", + "table": "workspace", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "workspace", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/created_at.json b/echo/directus/sync/snapshot/fields/workspace/created_at.json new file mode 100644 index 00000000..4fc7bcb8 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 14, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "workspace", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/created_by.json b/echo/directus/sync/snapshot/fields/workspace/created_by.json new file mode 100644 index 00000000..35620d61 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/created_by.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "created_by", + "type": "uuid", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_by", + "group": null, + "hidden": false, + "interface": "input", + "note": "FK to app_user.id", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 13, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "created_by", + "table": "workspace", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/deleted_at.json b/echo/directus/sync/snapshot/fields/workspace/deleted_at.json new file mode 100644 index 00000000..a4dfd082 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 12, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "workspace", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/description.json b/echo/directus/sync/snapshot/fields/workspace/description.json new file mode 100644 index 00000000..717e1caf --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/description.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "description", + "type": "text", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "description", + "group": null, + "hidden": false, + "interface": "input-multiline", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "description", + "table": "workspace", + "data_type": "text", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/id.json b/echo/directus/sync/snapshot/fields/workspace/id.json new file mode 100644 index 00000000..3b73d882 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/id.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace", + "field": "id", + "type": "uuid", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "workspace", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/is_default.json b/echo/directus/sync/snapshot/fields/workspace/is_default.json new file mode 100644 index 00000000..8375b862 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/is_default.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "is_default", + "type": "boolean", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "is_default", + "group": null, + "hidden": false, + "interface": "boolean", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 8, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "is_default", + "table": "workspace", + "data_type": "boolean", + "default_value": false, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/legal_basis.json b/echo/directus/sync/snapshot/fields/workspace/legal_basis.json new file mode 100644 index 00000000..6d12b87f --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/legal_basis.json @@ -0,0 +1,59 @@ +{ + "collection": "workspace", + "field": "legal_basis", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "legal_basis", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": null, + "options": { + "choices": [ + { + "text": "Consent", + "value": "consent" + }, + { + "text": "Client-managed", + "value": "client-managed" + }, + { + "text": "Dembrane Events", + "value": "dembrane-events" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 9, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "legal_basis", + "table": "workspace", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/logo_url.json b/echo/directus/sync/snapshot/fields/workspace/logo_url.json new file mode 100644 index 00000000..1f514d63 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/logo_url.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "logo_url", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "logo_url", + "group": null, + "hidden": false, + "interface": "input", + "note": "Override org logo", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "logo_url", + "table": "workspace", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/name.json b/echo/directus/sync/snapshot/fields/workspace/name.json new file mode 100644 index 00000000..bbb3adb8 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/name.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "name", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "name", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "name", + "table": "workspace", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/org_id.json b/echo/directus/sync/snapshot/fields/workspace/org_id.json new file mode 100644 index 00000000..1015762e --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/org_id.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "org_id", + "type": "uuid", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "org_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "org_id", + "table": "workspace", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "org", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/privacy_policy_url.json b/echo/directus/sync/snapshot/fields/workspace/privacy_policy_url.json new file mode 100644 index 00000000..f7f98005 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/privacy_policy_url.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "privacy_policy_url", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "privacy_policy_url", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 10, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "privacy_policy_url", + "table": "workspace", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/settings.json b/echo/directus/sync/snapshot/fields/workspace/settings.json new file mode 100644 index 00000000..ad8c39f4 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/settings.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace", + "field": "settings", + "type": "json", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "settings", + "group": null, + "hidden": false, + "interface": "input-code", + "note": "Feature flags, limits", + "options": { + "language": "json" + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 11, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "settings", + "table": "workspace", + "data_type": "json", + "default_value": {}, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/tier.json b/echo/directus/sync/snapshot/fields/workspace/tier.json new file mode 100644 index 00000000..6716ac33 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/tier.json @@ -0,0 +1,67 @@ +{ + "collection": "workspace", + "field": "tier", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "tier", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": null, + "options": { + "choices": [ + { + "text": "Pilot", + "value": "pilot" + }, + { + "text": "Pioneer", + "value": "pioneer" + }, + { + "text": "Innovator", + "value": "innovator" + }, + { + "text": "Changemaker", + "value": "changemaker" + }, + { + "text": "Guardian", + "value": "guardian" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "tier", + "table": "workspace", + "data_type": "character varying", + "default_value": "pioneer", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/updated_at.json b/echo/directus/sync/snapshot/fields/workspace/updated_at.json new file mode 100644 index 00000000..21c022f8 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/updated_at.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace", + "field": "updated_at", + "type": "timestamp", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "updated_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 15, + "special": [ + "date-updated" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "updated_at", + "table": "workspace", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/created_at.json b/echo/directus/sync/snapshot/fields/workspace_membership/created_at.json new file mode 100644 index 00000000..7d1b3fe4 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace_membership", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 9, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "workspace_membership", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/custom_policies.json b/echo/directus/sync/snapshot/fields/workspace_membership/custom_policies.json new file mode 100644 index 00000000..e9cdc07e --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/custom_policies.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace_membership", + "field": "custom_policies", + "type": "json", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "custom_policies", + "group": null, + "hidden": false, + "interface": "input-code", + "note": "Extra policies beyond role preset. Usually empty.", + "options": { + "language": "json" + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "custom_policies", + "table": "workspace_membership", + "data_type": "json", + "default_value": [], + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/deleted_at.json b/echo/directus/sync/snapshot/fields/workspace_membership/deleted_at.json new file mode 100644 index 00000000..6be752a2 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_membership", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 8, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "workspace_membership", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/id.json b/echo/directus/sync/snapshot/fields/workspace_membership/id.json new file mode 100644 index 00000000..de75b309 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/id.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace_membership", + "field": "id", + "type": "uuid", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "workspace_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/is_external.json b/echo/directus/sync/snapshot/fields/workspace_membership/is_external.json new file mode 100644 index 00000000..f119264e --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/is_external.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_membership", + "field": "is_external", + "type": "boolean", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "is_external", + "group": null, + "hidden": false, + "interface": "boolean", + "note": "True if user's primary org != workspace's org", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 7, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "is_external", + "table": "workspace_membership", + "data_type": "boolean", + "default_value": false, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/role.json b/echo/directus/sync/snapshot/fields/workspace_membership/role.json new file mode 100644 index 00000000..63df3d2b --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/role.json @@ -0,0 +1,63 @@ +{ + "collection": "workspace_membership", + "field": "role", + "type": "string", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "role", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": null, + "options": { + "choices": [ + { + "text": "Owner", + "value": "owner" + }, + { + "text": "Admin", + "value": "admin" + }, + { + "text": "Member", + "value": "member" + }, + { + "text": "Viewer", + "value": "viewer" + } + ] + }, + "readonly": false, + "required": true, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "role", + "table": "workspace_membership", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/source.json b/echo/directus/sync/snapshot/fields/workspace_membership/source.json new file mode 100644 index 00000000..8f11a3dd --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/source.json @@ -0,0 +1,55 @@ +{ + "collection": "workspace_membership", + "field": "source", + "type": "string", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "source", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": "direct = explicitly invited. inherited = auto-added from org role.", + "options": { + "choices": [ + { + "text": "Direct", + "value": "direct" + }, + { + "text": "Inherited", + "value": "inherited" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "source", + "table": "workspace_membership", + "data_type": "character varying", + "default_value": "direct", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/updated_at.json b/echo/directus/sync/snapshot/fields/workspace_membership/updated_at.json new file mode 100644 index 00000000..c8c719cd --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/updated_at.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace_membership", + "field": "updated_at", + "type": "timestamp", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "updated_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 10, + "special": [ + "date-updated" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "updated_at", + "table": "workspace_membership", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/user_id.json b/echo/directus/sync/snapshot/fields/workspace_membership/user_id.json new file mode 100644 index 00000000..4715d732 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/user_id.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_membership", + "field": "user_id", + "type": "uuid", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "user_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "user_id", + "table": "workspace_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_membership/workspace_id.json b/echo/directus/sync/snapshot/fields/workspace_membership/workspace_id.json new file mode 100644 index 00000000..3ed803ea --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_membership/workspace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_membership", + "field": "workspace_id", + "type": "uuid", + "meta": { + "collection": "workspace_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "workspace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "workspace_id", + "table": "workspace_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "workspace", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace/billed_to_workspace_id.json b/echo/directus/sync/snapshot/relations/workspace/billed_to_workspace_id.json new file mode 100644 index 00000000..eb0e4437 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace/billed_to_workspace_id.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace", + "field": "billed_to_workspace_id", + "related_collection": "workspace", + "meta": { + "junction_field": null, + "many_collection": "workspace", + "many_field": "billed_to_workspace_id", + "one_allowed_collections": null, + "one_collection": "workspace", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace", + "column": "billed_to_workspace_id", + "foreign_key_table": "workspace", + "foreign_key_column": "id", + "constraint_name": "workspace_billed_to_workspace_id_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace/created_by.json b/echo/directus/sync/snapshot/relations/workspace/created_by.json new file mode 100644 index 00000000..e41f103a --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace/created_by.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace", + "field": "created_by", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "workspace", + "many_field": "created_by", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace", + "column": "created_by", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "workspace_created_by_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace/org_id.json b/echo/directus/sync/snapshot/relations/workspace/org_id.json new file mode 100644 index 00000000..056d9f7a --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace/org_id.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace", + "field": "org_id", + "related_collection": "org", + "meta": { + "junction_field": null, + "many_collection": "workspace", + "many_field": "org_id", + "one_allowed_collections": null, + "one_collection": "org", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace", + "column": "org_id", + "foreign_key_table": "org", + "foreign_key_column": "id", + "constraint_name": "workspace_org_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace_membership/user_id.json b/echo/directus/sync/snapshot/relations/workspace_membership/user_id.json new file mode 100644 index 00000000..6752030b --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace_membership/user_id.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace_membership", + "field": "user_id", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "workspace_membership", + "many_field": "user_id", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace_membership", + "column": "user_id", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "workspace_membership_user_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace_membership/workspace_id.json b/echo/directus/sync/snapshot/relations/workspace_membership/workspace_id.json new file mode 100644 index 00000000..cff13374 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace_membership/workspace_id.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace_membership", + "field": "workspace_id", + "related_collection": "workspace", + "meta": { + "junction_field": null, + "many_collection": "workspace_membership", + "many_field": "workspace_id", + "one_allowed_collections": null, + "one_collection": "workspace", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace_membership", + "column": "workspace_id", + "foreign_key_table": "workspace", + "foreign_key_column": "id", + "constraint_name": "workspace_membership_workspace_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} From 686a81aff0d792b9499075e5bfc02978a5c671c2 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:44:01 +0000 Subject: [PATCH 005/208] chore: create workspace_invite and project_membership collections workspace_invite: id, workspace_id (FK workspace CASCADE), email, role, invited_by (FK app_user), token (unique), expires_at, accepted_at, created_at. project_membership: id, project_id (FK project CASCADE), user_id (FK app_user CASCADE), role (editor/viewer), custom_policies (json), granted_by (FK app_user SET NULL), created_at. --- .../collections/project_membership.json | 28 +++++++++ .../collections/workspace_invite.json | 28 +++++++++ .../fields/project_membership/created_at.json | 46 +++++++++++++++ .../project_membership/custom_policies.json | 46 +++++++++++++++ .../fields/project_membership/granted_by.json | 44 ++++++++++++++ .../fields/project_membership/id.json | 46 +++++++++++++++ .../fields/project_membership/project_id.json | 44 ++++++++++++++ .../fields/project_membership/role.json | 55 +++++++++++++++++ .../fields/project_membership/user_id.json | 44 ++++++++++++++ .../fields/workspace_invite/accepted_at.json | 44 ++++++++++++++ .../fields/workspace_invite/created_at.json | 46 +++++++++++++++ .../fields/workspace_invite/email.json | 44 ++++++++++++++ .../fields/workspace_invite/expires_at.json | 44 ++++++++++++++ .../snapshot/fields/workspace_invite/id.json | 46 +++++++++++++++ .../fields/workspace_invite/invited_by.json | 44 ++++++++++++++ .../fields/workspace_invite/role.json | 59 +++++++++++++++++++ .../fields/workspace_invite/token.json | 44 ++++++++++++++ .../fields/workspace_invite/workspace_id.json | 44 ++++++++++++++ .../project_membership/granted_by.json | 25 ++++++++ .../project_membership/project_id.json | 25 ++++++++ .../relations/project_membership/user_id.json | 25 ++++++++ .../workspace_invite/invited_by.json | 25 ++++++++ .../workspace_invite/workspace_id.json | 25 ++++++++ 23 files changed, 921 insertions(+) create mode 100644 echo/directus/sync/snapshot/collections/project_membership.json create mode 100644 echo/directus/sync/snapshot/collections/workspace_invite.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/custom_policies.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/granted_by.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/id.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/project_id.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/role.json create mode 100644 echo/directus/sync/snapshot/fields/project_membership/user_id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/accepted_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/email.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/expires_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/id.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/invited_by.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/role.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/token.json create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/workspace_id.json create mode 100644 echo/directus/sync/snapshot/relations/project_membership/granted_by.json create mode 100644 echo/directus/sync/snapshot/relations/project_membership/project_id.json create mode 100644 echo/directus/sync/snapshot/relations/project_membership/user_id.json create mode 100644 echo/directus/sync/snapshot/relations/workspace_invite/invited_by.json create mode 100644 echo/directus/sync/snapshot/relations/workspace_invite/workspace_id.json diff --git a/echo/directus/sync/snapshot/collections/project_membership.json b/echo/directus/sync/snapshot/collections/project_membership.json new file mode 100644 index 00000000..e30db96b --- /dev/null +++ b/echo/directus/sync/snapshot/collections/project_membership.json @@ -0,0 +1,28 @@ +{ + "collection": "project_membership", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "project_membership", + "color": null, + "display_template": null, + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "project_membership" + } +} diff --git a/echo/directus/sync/snapshot/collections/workspace_invite.json b/echo/directus/sync/snapshot/collections/workspace_invite.json new file mode 100644 index 00000000..2bb2199f --- /dev/null +++ b/echo/directus/sync/snapshot/collections/workspace_invite.json @@ -0,0 +1,28 @@ +{ + "collection": "workspace_invite", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "workspace_invite", + "color": null, + "display_template": null, + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": null, + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "workspace_invite" + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/created_at.json b/echo/directus/sync/snapshot/fields/project_membership/created_at.json new file mode 100644 index 00000000..34c3f587 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "project_membership", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 7, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "project_membership", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/custom_policies.json b/echo/directus/sync/snapshot/fields/project_membership/custom_policies.json new file mode 100644 index 00000000..3ff4fd98 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/custom_policies.json @@ -0,0 +1,46 @@ +{ + "collection": "project_membership", + "field": "custom_policies", + "type": "json", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "custom_policies", + "group": null, + "hidden": false, + "interface": "input-code", + "note": "Extra policies beyond role preset. Usually empty.", + "options": { + "language": "json" + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "custom_policies", + "table": "project_membership", + "data_type": "json", + "default_value": [], + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/granted_by.json b/echo/directus/sync/snapshot/fields/project_membership/granted_by.json new file mode 100644 index 00000000..94196be3 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/granted_by.json @@ -0,0 +1,44 @@ +{ + "collection": "project_membership", + "field": "granted_by", + "type": "uuid", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "granted_by", + "group": null, + "hidden": false, + "interface": "input", + "note": "FK to app_user.id", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "granted_by", + "table": "project_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/id.json b/echo/directus/sync/snapshot/fields/project_membership/id.json new file mode 100644 index 00000000..69123f60 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/id.json @@ -0,0 +1,46 @@ +{ + "collection": "project_membership", + "field": "id", + "type": "uuid", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "project_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/project_id.json b/echo/directus/sync/snapshot/fields/project_membership/project_id.json new file mode 100644 index 00000000..6a2ec33e --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/project_id.json @@ -0,0 +1,44 @@ +{ + "collection": "project_membership", + "field": "project_id", + "type": "uuid", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "project_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "project_id", + "table": "project_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "project", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/role.json b/echo/directus/sync/snapshot/fields/project_membership/role.json new file mode 100644 index 00000000..41a54d3c --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/role.json @@ -0,0 +1,55 @@ +{ + "collection": "project_membership", + "field": "role", + "type": "string", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "role", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": null, + "options": { + "choices": [ + { + "text": "Editor", + "value": "editor" + }, + { + "text": "Viewer", + "value": "viewer" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "role", + "table": "project_membership", + "data_type": "character varying", + "default_value": "editor", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project_membership/user_id.json b/echo/directus/sync/snapshot/fields/project_membership/user_id.json new file mode 100644 index 00000000..79b51330 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_membership/user_id.json @@ -0,0 +1,44 @@ +{ + "collection": "project_membership", + "field": "user_id", + "type": "uuid", + "meta": { + "collection": "project_membership", + "conditions": null, + "display": null, + "display_options": null, + "field": "user_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "user_id", + "table": "project_membership", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/accepted_at.json b/echo/directus/sync/snapshot/fields/workspace_invite/accepted_at.json new file mode 100644 index 00000000..46f2f2dc --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/accepted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "accepted_at", + "type": "timestamp", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "accepted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 8, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "accepted_at", + "table": "workspace_invite", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/created_at.json b/echo/directus/sync/snapshot/fields/workspace_invite/created_at.json new file mode 100644 index 00000000..3dd9b5ba --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace_invite", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 9, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "workspace_invite", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/email.json b/echo/directus/sync/snapshot/fields/workspace_invite/email.json new file mode 100644 index 00000000..aaf89c69 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/email.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "email", + "type": "string", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "email", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "email", + "table": "workspace_invite", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/expires_at.json b/echo/directus/sync/snapshot/fields/workspace_invite/expires_at.json new file mode 100644 index 00000000..2b6f1ef3 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/expires_at.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "expires_at", + "type": "timestamp", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "expires_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "7 days from creation", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 7, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "expires_at", + "table": "workspace_invite", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/id.json b/echo/directus/sync/snapshot/fields/workspace_invite/id.json new file mode 100644 index 00000000..4b6803a0 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/id.json @@ -0,0 +1,46 @@ +{ + "collection": "workspace_invite", + "field": "id", + "type": "uuid", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "workspace_invite", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/invited_by.json b/echo/directus/sync/snapshot/fields/workspace_invite/invited_by.json new file mode 100644 index 00000000..439e4370 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/invited_by.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "invited_by", + "type": "uuid", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "invited_by", + "group": null, + "hidden": false, + "interface": "input", + "note": "FK to app_user.id", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "invited_by", + "table": "workspace_invite", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/role.json b/echo/directus/sync/snapshot/fields/workspace_invite/role.json new file mode 100644 index 00000000..7a9c1483 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/role.json @@ -0,0 +1,59 @@ +{ + "collection": "workspace_invite", + "field": "role", + "type": "string", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "role", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": "Role to assign on acceptance", + "options": { + "choices": [ + { + "text": "Admin", + "value": "admin" + }, + { + "text": "Member", + "value": "member" + }, + { + "text": "Viewer", + "value": "viewer" + } + ] + }, + "readonly": false, + "required": true, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "role", + "table": "workspace_invite", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/token.json b/echo/directus/sync/snapshot/fields/workspace_invite/token.json new file mode 100644 index 00000000..cd4e8a79 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/token.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "token", + "type": "string", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "token", + "group": null, + "hidden": false, + "interface": "input", + "note": "secrets.token_urlsafe(32)", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "token", + "table": "workspace_invite", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/workspace_id.json b/echo/directus/sync/snapshot/fields/workspace_invite/workspace_id.json new file mode 100644 index 00000000..823468e8 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/workspace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "workspace_id", + "type": "uuid", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "workspace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "workspace_id", + "table": "workspace_invite", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "workspace", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/relations/project_membership/granted_by.json b/echo/directus/sync/snapshot/relations/project_membership/granted_by.json new file mode 100644 index 00000000..275321e7 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/project_membership/granted_by.json @@ -0,0 +1,25 @@ +{ + "collection": "project_membership", + "field": "granted_by", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "project_membership", + "many_field": "granted_by", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "project_membership", + "column": "granted_by", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "project_membership_granted_by_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/project_membership/project_id.json b/echo/directus/sync/snapshot/relations/project_membership/project_id.json new file mode 100644 index 00000000..201a80cd --- /dev/null +++ b/echo/directus/sync/snapshot/relations/project_membership/project_id.json @@ -0,0 +1,25 @@ +{ + "collection": "project_membership", + "field": "project_id", + "related_collection": "project", + "meta": { + "junction_field": null, + "many_collection": "project_membership", + "many_field": "project_id", + "one_allowed_collections": null, + "one_collection": "project", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "project_membership", + "column": "project_id", + "foreign_key_table": "project", + "foreign_key_column": "id", + "constraint_name": "project_membership_project_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/directus/sync/snapshot/relations/project_membership/user_id.json b/echo/directus/sync/snapshot/relations/project_membership/user_id.json new file mode 100644 index 00000000..2b299248 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/project_membership/user_id.json @@ -0,0 +1,25 @@ +{ + "collection": "project_membership", + "field": "user_id", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "project_membership", + "many_field": "user_id", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "project_membership", + "column": "user_id", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "project_membership_user_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace_invite/invited_by.json b/echo/directus/sync/snapshot/relations/workspace_invite/invited_by.json new file mode 100644 index 00000000..43520a7e --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace_invite/invited_by.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace_invite", + "field": "invited_by", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "workspace_invite", + "many_field": "invited_by", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace_invite", + "column": "invited_by", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "workspace_invite_invited_by_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/workspace_invite/workspace_id.json b/echo/directus/sync/snapshot/relations/workspace_invite/workspace_id.json new file mode 100644 index 00000000..7bef5231 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/workspace_invite/workspace_id.json @@ -0,0 +1,25 @@ +{ + "collection": "workspace_invite", + "field": "workspace_id", + "related_collection": "workspace", + "meta": { + "junction_field": null, + "many_collection": "workspace_invite", + "many_field": "workspace_id", + "one_allowed_collections": null, + "one_collection": "workspace", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "workspace_invite", + "column": "workspace_id", + "foreign_key_table": "workspace", + "foreign_key_column": "id", + "constraint_name": "workspace_invite_workspace_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} From 7c6a0712038d51fb7d97f86046a1c59edd034a4e Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:44:07 +0000 Subject: [PATCH 006/208] chore: create usage_event collection Append-only audit/billing log. Never updated, never deleted. No FK constraints on reference fields (org_id, workspace_id, project_id, user_id) to prevent CASCADE side effects. Fields: id, trace_id, org_id, workspace_id, project_id, user_id, event_type, event_data (json, always include "v": 1), created_at. --- .../snapshot/collections/usage_event.json | 28 +++++++++++ .../fields/usage_event/created_at.json | 46 +++++++++++++++++++ .../fields/usage_event/event_data.json | 46 +++++++++++++++++++ .../fields/usage_event/event_type.json | 44 ++++++++++++++++++ .../sync/snapshot/fields/usage_event/id.json | 46 +++++++++++++++++++ .../snapshot/fields/usage_event/org_id.json | 44 ++++++++++++++++++ .../fields/usage_event/project_id.json | 44 ++++++++++++++++++ .../snapshot/fields/usage_event/trace_id.json | 44 ++++++++++++++++++ .../snapshot/fields/usage_event/user_id.json | 44 ++++++++++++++++++ .../fields/usage_event/workspace_id.json | 44 ++++++++++++++++++ 10 files changed, 430 insertions(+) create mode 100644 echo/directus/sync/snapshot/collections/usage_event.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/created_at.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/event_data.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/event_type.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/id.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/org_id.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/project_id.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/trace_id.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/user_id.json create mode 100644 echo/directus/sync/snapshot/fields/usage_event/workspace_id.json diff --git a/echo/directus/sync/snapshot/collections/usage_event.json b/echo/directus/sync/snapshot/collections/usage_event.json new file mode 100644 index 00000000..4877432e --- /dev/null +++ b/echo/directus/sync/snapshot/collections/usage_event.json @@ -0,0 +1,28 @@ +{ + "collection": "usage_event", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "usage_event", + "color": null, + "display_template": null, + "group": null, + "hidden": false, + "icon": null, + "item_duplication_fields": null, + "note": "Append-only. Never updated. Never deleted.", + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": null, + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "usage_event" + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/created_at.json b/echo/directus/sync/snapshot/fields/usage_event/created_at.json new file mode 100644 index 00000000..bb6e66e6 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/created_at.json @@ -0,0 +1,46 @@ +{ + "collection": "usage_event", + "field": "created_at", + "type": "timestamp", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "created_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 9, + "special": [ + "date-created" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "created_at", + "table": "usage_event", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/event_data.json b/echo/directus/sync/snapshot/fields/usage_event/event_data.json new file mode 100644 index 00000000..2c1075e1 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/event_data.json @@ -0,0 +1,46 @@ +{ + "collection": "usage_event", + "field": "event_data", + "type": "json", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "event_data", + "group": null, + "hidden": false, + "interface": "input-code", + "note": "Always include \"v\": 1 for schema versioning", + "options": { + "language": "json" + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 8, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "event_data", + "table": "usage_event", + "data_type": "json", + "default_value": {}, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/event_type.json b/echo/directus/sync/snapshot/fields/usage_event/event_type.json new file mode 100644 index 00000000..26b69257 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/event_type.json @@ -0,0 +1,44 @@ +{ + "collection": "usage_event", + "field": "event_type", + "type": "string", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "event_type", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": true, + "searchable": true, + "sort": 7, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "event_type", + "table": "usage_event", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/id.json b/echo/directus/sync/snapshot/fields/usage_event/id.json new file mode 100644 index 00000000..1bfc4ab2 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/id.json @@ -0,0 +1,46 @@ +{ + "collection": "usage_event", + "field": "id", + "type": "uuid", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "input", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": [ + "uuid" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "usage_event", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/org_id.json b/echo/directus/sync/snapshot/fields/usage_event/org_id.json new file mode 100644 index 00000000..7958ef29 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/org_id.json @@ -0,0 +1,44 @@ +{ + "collection": "usage_event", + "field": "org_id", + "type": "uuid", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "org_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Reference only, no FK constraint", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "org_id", + "table": "usage_event", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/project_id.json b/echo/directus/sync/snapshot/fields/usage_event/project_id.json new file mode 100644 index 00000000..6470733f --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/project_id.json @@ -0,0 +1,44 @@ +{ + "collection": "usage_event", + "field": "project_id", + "type": "uuid", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "project_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Reference only, no FK constraint", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "project_id", + "table": "usage_event", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/trace_id.json b/echo/directus/sync/snapshot/fields/usage_event/trace_id.json new file mode 100644 index 00000000..55eb8fdb --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/trace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "usage_event", + "field": "trace_id", + "type": "string", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "trace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Request correlation ID", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "trace_id", + "table": "usage_event", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/user_id.json b/echo/directus/sync/snapshot/fields/usage_event/user_id.json new file mode 100644 index 00000000..627067f6 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/user_id.json @@ -0,0 +1,44 @@ +{ + "collection": "usage_event", + "field": "user_id", + "type": "uuid", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "user_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Reference only, no FK constraint", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "user_id", + "table": "usage_event", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/usage_event/workspace_id.json b/echo/directus/sync/snapshot/fields/usage_event/workspace_id.json new file mode 100644 index 00000000..ea0554cf --- /dev/null +++ b/echo/directus/sync/snapshot/fields/usage_event/workspace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "usage_event", + "field": "workspace_id", + "type": "uuid", + "meta": { + "collection": "usage_event", + "conditions": null, + "display": null, + "display_options": null, + "field": "workspace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "Reference only, no FK constraint", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "workspace_id", + "table": "usage_event", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} From 705e1a956e30ee53db29ff49a08fbe44bcf5c0c2 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:44:14 +0000 Subject: [PATCH 007/208] chore: add workspace_id, visibility, and deleted_at to project workspace_id: uuid, nullable, FK to workspace (SET NULL). NULL only during migration window. visibility: string, default 'workspace'. workspace/private. deleted_at: timestamp, nullable. Soft delete support. --- .../snapshot/fields/project/deleted_at.json | 44 +++++++++++++++ .../snapshot/fields/project/visibility.json | 55 +++++++++++++++++++ .../snapshot/fields/project/workspace_id.json | 44 +++++++++++++++ .../relations/project/workspace_id.json | 25 +++++++++ 4 files changed, 168 insertions(+) create mode 100644 echo/directus/sync/snapshot/fields/project/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/project/visibility.json create mode 100644 echo/directus/sync/snapshot/fields/project/workspace_id.json create mode 100644 echo/directus/sync/snapshot/relations/project/workspace_id.json diff --git a/echo/directus/sync/snapshot/fields/project/deleted_at.json b/echo/directus/sync/snapshot/fields/project/deleted_at.json new file mode 100644 index 00000000..e5be34ee --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "project", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "project", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 39, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "project", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project/visibility.json b/echo/directus/sync/snapshot/fields/project/visibility.json new file mode 100644 index 00000000..4bf8f457 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project/visibility.json @@ -0,0 +1,55 @@ +{ + "collection": "project", + "field": "visibility", + "type": "string", + "meta": { + "collection": "project", + "conditions": null, + "display": null, + "display_options": null, + "field": "visibility", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": "workspace = visible to all workspace members. private = explicit sharing.", + "options": { + "choices": [ + { + "text": "Workspace", + "value": "workspace" + }, + { + "text": "Private", + "value": "private" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 38, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "visibility", + "table": "project", + "data_type": "character varying", + "default_value": "workspace", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project/workspace_id.json b/echo/directus/sync/snapshot/fields/project/workspace_id.json new file mode 100644 index 00000000..d53f17df --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project/workspace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "project", + "field": "workspace_id", + "type": "uuid", + "meta": { + "collection": "project", + "conditions": null, + "display": null, + "display_options": null, + "field": "workspace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": "FK to workspace. NULL during migration.", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 37, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "workspace_id", + "table": "project", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "workspace", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/relations/project/workspace_id.json b/echo/directus/sync/snapshot/relations/project/workspace_id.json new file mode 100644 index 00000000..033ff9ab --- /dev/null +++ b/echo/directus/sync/snapshot/relations/project/workspace_id.json @@ -0,0 +1,25 @@ +{ + "collection": "project", + "field": "workspace_id", + "related_collection": "workspace", + "meta": { + "junction_field": null, + "many_collection": "project", + "many_field": "workspace_id", + "one_allowed_collections": null, + "one_collection": "workspace", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "project", + "column": "workspace_id", + "foreign_key_table": "workspace", + "foreign_key_column": "id", + "constraint_name": "project_workspace_id_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} From 1128430d830ab076c3cc4d07ab217f44924bc497 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:44:21 +0000 Subject: [PATCH 008/208] chore: add deleted_at to conversation, project_chat, project_report Soft delete support for existing collections. All nullable timestamps, default null. No existing behavior changes until Session 3 converts delete operations to use these fields. --- .../fields/conversation/deleted_at.json | 44 +++++++++++++++++++ .../fields/project_chat/deleted_at.json | 44 +++++++++++++++++++ .../fields/project_report/deleted_at.json | 44 +++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 echo/directus/sync/snapshot/fields/conversation/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/project_chat/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/project_report/deleted_at.json diff --git a/echo/directus/sync/snapshot/fields/conversation/deleted_at.json b/echo/directus/sync/snapshot/fields/conversation/deleted_at.json new file mode 100644 index 00000000..c30ddb8c --- /dev/null +++ b/echo/directus/sync/snapshot/fields/conversation/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "conversation", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "conversation", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 28, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "conversation", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project_chat/deleted_at.json b/echo/directus/sync/snapshot/fields/project_chat/deleted_at.json new file mode 100644 index 00000000..1af4cdd4 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_chat/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "project_chat", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "project_chat", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 12, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "project_chat", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/project_report/deleted_at.json b/echo/directus/sync/snapshot/fields/project_report/deleted_at.json new file mode 100644 index 00000000..a716bd42 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_report/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "project_report", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "project_report", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 16, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "project_report", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} From b424d978623f2d0c40130ed76fef58bbfa9782ae Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:44:27 +0000 Subject: [PATCH 009/208] chore: remove legacy chat collection The chat collection (not project_chat) was confirmed unused: zero code references, zero rows, no permissions, no flows. Removed from Directus and schema sync files. project_chat remains as the active chat system. --- .../sync/snapshot/collections/chat.json | 28 ----------- .../snapshot/fields/chat/date_created.json | 48 ------------------- .../snapshot/fields/chat/date_updated.json | 48 ------------------- .../sync/snapshot/fields/chat/id.json | 46 ------------------ .../sync/snapshot/fields/chat/title.json | 44 ----------------- .../snapshot/fields/chat/user_created.json | 48 ------------------- .../snapshot/fields/chat/user_updated.json | 48 ------------------- .../snapshot/relations/chat/user_created.json | 25 ---------- .../snapshot/relations/chat/user_updated.json | 25 ---------- 9 files changed, 360 deletions(-) delete mode 100644 echo/directus/sync/snapshot/collections/chat.json delete mode 100644 echo/directus/sync/snapshot/fields/chat/date_created.json delete mode 100644 echo/directus/sync/snapshot/fields/chat/date_updated.json delete mode 100644 echo/directus/sync/snapshot/fields/chat/id.json delete mode 100644 echo/directus/sync/snapshot/fields/chat/title.json delete mode 100644 echo/directus/sync/snapshot/fields/chat/user_created.json delete mode 100644 echo/directus/sync/snapshot/fields/chat/user_updated.json delete mode 100644 echo/directus/sync/snapshot/relations/chat/user_created.json delete mode 100644 echo/directus/sync/snapshot/relations/chat/user_updated.json diff --git a/echo/directus/sync/snapshot/collections/chat.json b/echo/directus/sync/snapshot/collections/chat.json deleted file mode 100644 index b03c799c..00000000 --- a/echo/directus/sync/snapshot/collections/chat.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "collection": "chat", - "meta": { - "accountability": "all", - "archive_app_filter": true, - "archive_field": null, - "archive_value": null, - "collapse": "open", - "collection": "chat", - "color": null, - "display_template": null, - "group": null, - "hidden": false, - "icon": null, - "item_duplication_fields": null, - "note": null, - "preview_url": null, - "singleton": false, - "sort": 15, - "sort_field": null, - "translations": null, - "unarchive_value": null, - "versioning": false - }, - "schema": { - "name": "chat" - } -} diff --git a/echo/directus/sync/snapshot/fields/chat/date_created.json b/echo/directus/sync/snapshot/fields/chat/date_created.json deleted file mode 100644 index 102ea56d..00000000 --- a/echo/directus/sync/snapshot/fields/chat/date_created.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "collection": "chat", - "field": "date_created", - "type": "timestamp", - "meta": { - "collection": "chat", - "conditions": null, - "display": "datetime", - "display_options": { - "relative": true - }, - "field": "date_created", - "group": null, - "hidden": true, - "interface": "datetime", - "note": null, - "options": null, - "readonly": true, - "required": false, - "searchable": true, - "sort": 3, - "special": [ - "date-created" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "half" - }, - "schema": { - "name": "date_created", - "table": "chat", - "data_type": "timestamp with time zone", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/chat/date_updated.json b/echo/directus/sync/snapshot/fields/chat/date_updated.json deleted file mode 100644 index 6a9a0ef8..00000000 --- a/echo/directus/sync/snapshot/fields/chat/date_updated.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "collection": "chat", - "field": "date_updated", - "type": "timestamp", - "meta": { - "collection": "chat", - "conditions": null, - "display": "datetime", - "display_options": { - "relative": true - }, - "field": "date_updated", - "group": null, - "hidden": true, - "interface": "datetime", - "note": null, - "options": null, - "readonly": true, - "required": false, - "searchable": true, - "sort": 5, - "special": [ - "date-updated" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "half" - }, - "schema": { - "name": "date_updated", - "table": "chat", - "data_type": "timestamp with time zone", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/chat/id.json b/echo/directus/sync/snapshot/fields/chat/id.json deleted file mode 100644 index 72e1f4dc..00000000 --- a/echo/directus/sync/snapshot/fields/chat/id.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "collection": "chat", - "field": "id", - "type": "uuid", - "meta": { - "collection": "chat", - "conditions": null, - "display": null, - "display_options": null, - "field": "id", - "group": null, - "hidden": true, - "interface": "input", - "note": null, - "options": null, - "readonly": true, - "required": false, - "searchable": true, - "sort": 1, - "special": [ - "uuid" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "id", - "table": "chat", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": false, - "is_unique": true, - "is_indexed": false, - "is_primary_key": true, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/chat/title.json b/echo/directus/sync/snapshot/fields/chat/title.json deleted file mode 100644 index 6bc177bb..00000000 --- a/echo/directus/sync/snapshot/fields/chat/title.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "chat", - "field": "title", - "type": "text", - "meta": { - "collection": "chat", - "conditions": null, - "display": null, - "display_options": null, - "field": "title", - "group": null, - "hidden": false, - "interface": "input", - "note": null, - "options": null, - "readonly": false, - "required": false, - "searchable": true, - "sort": 6, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "title", - "table": "chat", - "data_type": "text", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/chat/user_created.json b/echo/directus/sync/snapshot/fields/chat/user_created.json deleted file mode 100644 index 81d255d6..00000000 --- a/echo/directus/sync/snapshot/fields/chat/user_created.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "collection": "chat", - "field": "user_created", - "type": "uuid", - "meta": { - "collection": "chat", - "conditions": null, - "display": "user", - "display_options": null, - "field": "user_created", - "group": null, - "hidden": true, - "interface": "select-dropdown-m2o", - "note": null, - "options": { - "template": "{{avatar}} {{first_name}} {{last_name}}" - }, - "readonly": true, - "required": false, - "searchable": true, - "sort": 2, - "special": [ - "user-created" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "half" - }, - "schema": { - "name": "user_created", - "table": "chat", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": "directus_users", - "foreign_key_column": "id" - } -} diff --git a/echo/directus/sync/snapshot/fields/chat/user_updated.json b/echo/directus/sync/snapshot/fields/chat/user_updated.json deleted file mode 100644 index 0962304f..00000000 --- a/echo/directus/sync/snapshot/fields/chat/user_updated.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "collection": "chat", - "field": "user_updated", - "type": "uuid", - "meta": { - "collection": "chat", - "conditions": null, - "display": "user", - "display_options": null, - "field": "user_updated", - "group": null, - "hidden": true, - "interface": "select-dropdown-m2o", - "note": null, - "options": { - "template": "{{avatar}} {{first_name}} {{last_name}}" - }, - "readonly": true, - "required": false, - "searchable": true, - "sort": 4, - "special": [ - "user-updated" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "half" - }, - "schema": { - "name": "user_updated", - "table": "chat", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": "directus_users", - "foreign_key_column": "id" - } -} diff --git a/echo/directus/sync/snapshot/relations/chat/user_created.json b/echo/directus/sync/snapshot/relations/chat/user_created.json deleted file mode 100644 index ce3d75cb..00000000 --- a/echo/directus/sync/snapshot/relations/chat/user_created.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "collection": "chat", - "field": "user_created", - "related_collection": "directus_users", - "meta": { - "junction_field": null, - "many_collection": "chat", - "many_field": "user_created", - "one_allowed_collections": null, - "one_collection": "directus_users", - "one_collection_field": null, - "one_deselect_action": "nullify", - "one_field": null, - "sort_field": null - }, - "schema": { - "table": "chat", - "column": "user_created", - "foreign_key_table": "directus_users", - "foreign_key_column": "id", - "constraint_name": "chat_user_created_foreign", - "on_update": "NO ACTION", - "on_delete": "NO ACTION" - } -} diff --git a/echo/directus/sync/snapshot/relations/chat/user_updated.json b/echo/directus/sync/snapshot/relations/chat/user_updated.json deleted file mode 100644 index 2460da26..00000000 --- a/echo/directus/sync/snapshot/relations/chat/user_updated.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "collection": "chat", - "field": "user_updated", - "related_collection": "directus_users", - "meta": { - "junction_field": null, - "many_collection": "chat", - "many_field": "user_updated", - "one_allowed_collections": null, - "one_collection": "directus_users", - "one_collection_field": null, - "one_deselect_action": "nullify", - "one_field": null, - "sort_field": null - }, - "schema": { - "table": "chat", - "column": "user_updated", - "foreign_key_table": "directus_users", - "foreign_key_column": "id", - "constraint_name": "chat_user_updated_foreign", - "on_update": "NO ACTION", - "on_delete": "NO ACTION" - } -} From 46200ed99f8fa2e0b5da01dee48e541114ad9b8a Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 13:44:33 +0000 Subject: [PATCH 010/208] docs: add workspaces planning docs and codebase exploration report Session 1 exploration report covering: full Directus schema map, Python API routes, frontend routes, delete operation inventory, auth flow, Directus client patterns, CASCADE analysis, email capability assessment, and PRD reconciliation. --- echo/docs/workspaces/architecture-review.md | 434 +++++++ .../workspaces/codebase-exploration-report.md | 1090 +++++++++++++++++ echo/docs/workspaces/execution-plan-final.md | 593 +++++++++ echo/docs/workspaces/failure-analysis.md | 391 ++++++ echo/docs/workspaces/gate-check-protocol.md | 285 +++++ echo/docs/workspaces/reference.md | 379 ++++++ .../workspaces/workspaces-prd-v3-final.md | 861 +++++++++++++ 7 files changed, 4033 insertions(+) create mode 100644 echo/docs/workspaces/architecture-review.md create mode 100644 echo/docs/workspaces/codebase-exploration-report.md create mode 100644 echo/docs/workspaces/execution-plan-final.md create mode 100644 echo/docs/workspaces/failure-analysis.md create mode 100644 echo/docs/workspaces/gate-check-protocol.md create mode 100644 echo/docs/workspaces/reference.md create mode 100644 echo/docs/workspaces/workspaces-prd-v3-final.md diff --git a/echo/docs/workspaces/architecture-review.md b/echo/docs/workspaces/architecture-review.md new file mode 100644 index 00000000..c18538ca --- /dev/null +++ b/echo/docs/workspaces/architecture-review.md @@ -0,0 +1,434 @@ +# Architecture Review: Workspaces PRD +## What makes a senior eng at Google/Apple have a heart attack + +--- + +## 1. CRITICAL: Mutable slugs in URLs + +**The problem:** Workspace slugs are used in URLs (`/:locale/:workspaceSlug/projects/...`) AND are editable in settings. This is a classic footgun. + +User bookmarks `app.dembrane.com/en/dietz-consulting/projects/abc`. Admin renames workspace slug to `dietz-nl`. Every bookmark, shared link, browser history entry, saved integration URL, and CI/CD webhook breaks instantly. The PRD says "no redirect" — that's a data loss event for users. + +**The fix:** Pick one: + +- **Option A (recommended):** Slugs are immutable after creation. Want a different URL? Too bad. This is what GitHub does with repo URLs (renames auto-redirect forever). +- **Option B:** Slugs are editable BUT old slugs redirect to new slug for 90 days. Requires a `workspace_slug_history` table. This is what Slack does with workspace URLs. +- **Option C:** Don't use slugs in URLs at all. Use short IDs (`/w/abc123/projects/...`). Slugs are display-only. This is what Notion does. + +**Recommendation:** Option A. Simplest. If someone really wants a different slug, they can create a new workspace and move projects. + +--- + +## 2. CRITICAL: No tenant isolation strategy + +**The problem:** All orgs and workspaces live in the same tables with no row-level security. A single missing `WHERE workspace_id = :ws_id` in any query leaks data across tenants. This is a compliance-ending bug for EU public sector clients under ISO 27001. + +**The fix:** + +```python +# WRONG — every endpoint does its own filtering +@router.get("/api/v1/workspaces/{ws_id}/projects") +async def list_projects(ws_id: str, user: User): + projects = await db.fetch_all( + "SELECT * FROM project WHERE workspace_id = :ws_id", {"ws_id": ws_id} + ) + # Oops, forgot to check if user can access this workspace + +# RIGHT — middleware sets tenant context, all queries are scoped +class WorkspaceContext: + """Dependency injection that validates access AND sets query scope.""" + + async def __call__(self, ws_id: str, user: User = Depends(get_current_user)): + access = await get_workspace_access(ws_id, user.id) + if not access: + raise HTTPException(403) + return WorkspaceScopedSession(ws_id=ws_id, user=user, role=access.role) + +workspace_ctx = WorkspaceContext() + +@router.get("/api/v1/workspaces/{ws_id}/projects") +async def list_projects(ctx: WorkspaceScopedSession = Depends(workspace_ctx)): + # ctx.query() automatically adds WHERE workspace_id = ctx.ws_id + projects = await ctx.query(project).fetch_all() +``` + +Also consider PostgreSQL Row-Level Security (RLS) as a defense-in-depth layer: + +```sql +ALTER TABLE project ENABLE ROW LEVEL SECURITY; + +CREATE POLICY project_workspace_isolation ON project + USING (workspace_id = current_setting('app.current_workspace_id')::uuid); +``` + +Set `app.current_workspace_id` at the start of each request. Even if application code has a bug, the DB won't return wrong-tenant rows. + +--- + +## 3. CRITICAL: Permission check is a multi-query waterfall on every request + +**The problem:** The `get_user_project_access` function does up to 4 sequential DB queries on every single API call: + +1. Check legacy ownership → query `project` +2. Check org membership → query `org_membership` +3. Check workspace membership → query `workspace_membership` +4. Check project_user → query `project_user` + +At 2-5ms per query, that's 8-20ms of permission overhead before you even start the actual work. Under load, this cascades. + +**The fix:** Single query with JOINs + cache. + +```sql +-- Single query: "what access does user X have to project Y?" +SELECT + p.id AS project_id, + p.workspace_id, + p.visibility, + p.directus_user_id, + w.org_id, + om.role AS org_role, + wm.role AS workspace_role, + pu.role AS project_user_role +FROM project p +LEFT JOIN workspace w ON w.id = p.workspace_id +LEFT JOIN org_membership om ON om.org_id = w.org_id AND om.user_id = :user_id +LEFT JOIN workspace_membership wm ON wm.workspace_id = p.workspace_id AND wm.user_id = :user_id +LEFT JOIN project_user pu ON pu.project_id = p.id AND pu.user_id = :user_id +WHERE p.id = :project_id; +``` + +One query, one round trip. Resolve access in application code from the joined result. + +For the workspace list (selector page), similar approach: + +```sql +-- All workspaces accessible to user X, with counts +SELECT + w.*, + COALESCE(om.role, NULL) AS org_role, + COALESCE(wm.role, NULL) AS ws_role, + (SELECT COUNT(*) FROM project WHERE workspace_id = w.id) AS project_count, + (SELECT COUNT(*) FROM workspace_membership WHERE workspace_id = w.id) AS member_count +FROM workspace w +LEFT JOIN org_membership om ON om.org_id = w.org_id AND om.user_id = :user_id +LEFT JOIN workspace_membership wm ON wm.workspace_id = w.id AND wm.user_id = :user_id +WHERE om.role IN ('owner', 'admin') OR wm.id IS NOT NULL +ORDER BY w.updated_at DESC; +``` + +**Optional cache layer:** For the workspace selector (called on every page load), cache the result per user for 30-60 seconds. Invalidate on membership changes. Even a simple in-memory dict with TTL is fine at your scale. No Redis needed. + +--- + +## 4. HIGH: Cascading deletes with no soft delete + +**The problem:** `ON DELETE CASCADE` from org → workspace → project means: +- Deleting an org nukes every workspace, every project, every conversation, every transcript +- No recovery. No audit trail. No "oops" button. +- An angry org admin (or a compromised account) can destroy a partner's entire client portfolio in one click. + +ISO 27001 auditors will flag this. + +**The fix:** + +```sql +-- Soft delete on all tenant-scoped tables +ALTER TABLE org ADD COLUMN deleted_at timestamptz; +ALTER TABLE workspace ADD COLUMN deleted_at timestamptz; +ALTER TABLE project ADD COLUMN ... ; -- if not already present + +-- All queries filter: WHERE deleted_at IS NULL +-- Cascade becomes: set deleted_at on children +-- Actual purge: scheduled job after 30 days, with email notification +``` + +Application-level delete flow: +1. User clicks delete → sets `deleted_at = now()` +2. Data disappears from all queries immediately +3. Emit `workspace.deleted` usage event +4. 30-day grace period — support can restore by setting `deleted_at = NULL` +5. Scheduled job hard-deletes after 30 days +6. Email notification to org owner when workspace is soft-deleted + +Remove all `ON DELETE CASCADE`. Replace with application-level soft delete propagation. + +--- + +## 5. HIGH: No idempotency on mutations + +**The problem:** User clicks "Create workspace" → network timeout → user clicks again → two workspaces created. Same with invites, project creation, etc. Mobile users with flaky connections will hit this constantly. + +**The fix:** Idempotency key pattern. + +```python +@router.post("/api/v1/workspaces") +async def create_workspace( + body: CreateWorkspaceRequest, + idempotency_key: str = Header(alias="Idempotency-Key"), + user: User = Depends(get_current_user), +): + # Check if we've seen this key before + existing = await db.fetch_one( + "SELECT response_body FROM idempotency_cache WHERE key = :key AND user_id = :uid", + {"key": idempotency_key, "uid": user.id} + ) + if existing: + return JSONResponse(content=json.loads(existing.response_body), status_code=201) + + # Create workspace... + result = await _create_workspace(body, user) + + # Cache the response (TTL: 24h) + await db.execute( + idempotency_cache.insert().values( + key=idempotency_key, user_id=user.id, + response_body=json.dumps(result), expires_at=now() + timedelta(hours=24) + ) + ) + return result +``` + +Frontend generates `Idempotency-Key: {uuid}` on form submission, reuses same key on retry. + +At minimum, do this for: workspace creation, invite sending, project creation. These are the most visible duplicate-creation bugs. + +--- + +## 6. HIGH: No pagination on any list endpoint + +**The problem:** `GET /workspaces/:ws_id/projects` returns all projects as a flat array. A workspace with 200 projects sends 200 rows on every page load. Usage events will be thousands of rows per month. + +**The fix:** Cursor-based pagination on all list endpoints. + +```python +@router.get("/api/v1/workspaces/{ws_id}/projects") +async def list_projects( + ctx: WorkspaceScopedSession = Depends(workspace_ctx), + cursor: str | None = Query(None), # Opaque cursor (base64 encoded created_at + id) + limit: int = Query(25, ge=1, le=100), +): + # Decode cursor, query with WHERE (created_at, id) < (cursor_ts, cursor_id) + # Return: { "items": [...], "next_cursor": "...", "has_more": bool } +``` + +Offset-based pagination is fine too at your scale, but cursor-based is better for real-time lists where items get added/removed. + +At minimum: projects, members, usage events, conversations. + +--- + +## 7. HIGH: usage_event table will eat your database + +**The problem:** Append-only, never deleted. `chat.query` alone could be 100+ events per workspace per day. At 50 workspaces × 100 events × 365 days = 1.8M rows/year. With jsonb `event_data`, that's non-trivial storage and index bloat. + +**The fix:** Plan for it now, implement when needed. + +```sql +-- Partition by month from day 1 (cheap to add, expensive to add later) +CREATE TABLE usage_event ( + id uuid NOT NULL, + trace_id varchar(100) NOT NULL, + org_id uuid, + workspace_id uuid, + -- ... + created_at timestamptz NOT NULL DEFAULT now() +) PARTITION BY RANGE (created_at); + +-- Create partitions (automate with pg_partman or a monthly cron) +CREATE TABLE usage_event_2026_04 PARTITION OF usage_event + FOR VALUES FROM ('2026-04-01') TO ('2026-05-01'); +CREATE TABLE usage_event_2026_05 PARTITION OF usage_event + FOR VALUES FROM ('2026-05-01') TO ('2026-06-01'); +``` + +Benefits: queries scoped to a billing period only scan one partition. Old partitions can be archived to cold storage. Indexes stay small per partition. + +**Do this in the initial migration.** Adding partitioning to an existing table requires a full rewrite. + +--- + +## 8. MEDIUM: FK coupling to directus_users + +**The problem:** Every new table has `FK → directus_users.id`. When you eventually migrate to Better Auth (or any other auth system), you'll need to: +1. Create new user table +2. Map old Directus IDs to new IDs +3. Update EVERY foreign key in EVERY table + +This is a multi-day, high-risk migration. + +**The fix:** Indirection layer. + +```sql +-- Create a thin user reference table that YOU control +CREATE TABLE app_user ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + directus_user_id uuid UNIQUE, -- current auth provider + -- future: better_auth_user_id uuid UNIQUE, + email varchar(255) NOT NULL, + display_name varchar(255), + created_at timestamptz NOT NULL DEFAULT now() +); + +-- All new tables FK to app_user.id, NOT directus_users.id +ALTER TABLE org_membership ADD COLUMN user_id uuid REFERENCES app_user(id); +ALTER TABLE workspace_membership ADD COLUMN user_id uuid REFERENCES app_user(id); +``` + +When you migrate auth, you update the `app_user` table once (add `better_auth_user_id`, drop `directus_user_id`). Zero changes to org/workspace/project tables. + +**Trade-off:** One more JOIN on user lookups. Worth it for migration safety. + +--- + +## 9. MEDIUM: No RBAC middleware — permission checks will be forgotten + +**The problem:** Every endpoint does its own permission check. New endpoints will inevitably forget. One intern adding a quick admin endpoint without the permission check = data breach. + +**The fix:** Declarative policy enforcement via decorators/dependencies. + +```python +# Define policies as a dependency +def require_policy(*policies: str): + """FastAPI dependency that checks workspace-level policies.""" + async def check(ctx: WorkspaceScopedSession = Depends(workspace_ctx)): + for policy in policies: + if not ctx.has_policy(policy): + raise HTTPException(403, f"Missing policy: {policy}") + return ctx + return Depends(check) + +# Usage — impossible to forget +@router.post("/api/v1/workspaces/{ws_id}/members") +async def invite_member( + body: InviteMemberRequest, + ctx = require_policy("member:invite"), # Enforced by the framework +): + ... + +@router.delete("/api/v1/workspaces/{ws_id}") +async def delete_workspace( + ctx = require_policy("*"), # Only owner +): + ... +``` + +Also: write a test that introspects all registered routes and verifies every workspace-scoped endpoint has a policy dependency. Fail CI if any endpoint is missing one. + +--- + +## 10. MEDIUM: No rate limiting on invite and creation endpoints + +**The problem:** Invite spam. A compromised or malicious account can send thousands of invite emails. Workspace creation spam fills the org with garbage. + +**The fix:** + +```python +from fastapi_limiter import RateLimiter + +@router.post("/api/v1/workspaces/{ws_id}/members", + dependencies=[Depends(RateLimiter(times=20, minutes=60))] # 20 invites/hour +) +async def invite_member(...): + ... + +@router.post("/api/v1/workspaces", + dependencies=[Depends(RateLimiter(times=5, minutes=60))] # 5 workspaces/hour +) +async def create_workspace(...): + ... +``` + +For MVP, even a simple in-memory counter per user is fine. Don't ship invite endpoints without this. + +--- + +## 11. MEDIUM: No event versioning on usage_event + +**The problem:** `event_data` is schemaless jsonb. When you change the shape (add a field, rename a field, remove a field), old events have the old shape and new events have the new shape. Billing aggregation queries that span months will break silently. + +**The fix:** Version every event schema. + +```json +{ + "v": 1, + "duration_seconds": 3600, + "conversation_id": "abc" +} +``` + +Aggregation code checks `v` and handles each version: + +```python +def get_audio_hours(event_data: dict) -> float: + v = event_data.get("v", 1) + if v == 1: + return event_data["duration_seconds"] / 3600 + elif v == 2: + return event_data["duration_ms"] / 3_600_000 # hypothetical future change +``` + +Cheap to add, painful to add later. + +--- + +## 12. MEDIUM: Global slug uniqueness is too restrictive + +**The problem:** Workspace slugs are globally unique. Two different orgs can't both have a workspace called "default". At scale, good slugs get consumed. Users get `default-47`. + +**The fix:** Slugs should be unique per org, not globally. The URL already has locale context — add org context too: + +``` +/:locale/:orgSlug/:workspaceSlug/projects +``` + +Or, if you don't want org in the URL (cleaner), make the uniqueness constraint: + +```sql +-- Instead of: UNIQUE (slug) +-- Use: UNIQUE (org_id, slug) +``` + +And use the workspace `id` (or a short hash) in the URL for routing, with slug as display-only. This is the Notion/Linear pattern. + +**If you keep global uniqueness:** At least namespace default workspaces: `{org_slug}-default` instead of just `default`. + +--- + +## 13. LOW: No request tracing / correlation + +**The problem:** User reports "I clicked invite and nothing happened." How do you debug? You have usage_events but no way to correlate a specific HTTP request to the events it generated, the queries it ran, and the response it returned. + +**The fix:** Generate a request ID on every API call, propagate through all logging and events. + +```python +@app.middleware("http") +async def add_request_id(request: Request, call_next): + request_id = request.headers.get("X-Request-ID", str(uuid4())) + # Set on context for all downstream logging + contextvars.request_id.set(request_id) + response = await call_next(request) + response.headers["X-Request-ID"] = request_id + return response +``` + +Use this `request_id` as the `trace_id` in usage_events. Now you can trace: request → permission check → DB queries → usage events → response, all with one ID. + +--- + +## Summary: Priority Order + +| # | Issue | Severity | Effort | Do Now? | +|---|-------|----------|--------|---------| +| 1 | Mutable slugs in URLs | Critical | Low | **Yes** — decide before shipping | +| 2 | No tenant isolation | Critical | Medium | **Yes** — build the middleware pattern from day 1 | +| 3 | Permission query waterfall | High | Medium | **Yes** — write the single JOIN query | +| 4 | No soft delete | High | Low | **Yes** — add `deleted_at` columns in initial migration | +| 5 | No idempotency | High | Medium | Yes for create endpoints | +| 6 | No pagination | High | Low | Yes on all list endpoints | +| 7 | usage_event partitioning | High | Low | **Yes** — partition from day 1 (can't add later easily) | +| 8 | FK coupling to directus_users | Medium | Medium | Recommended — `app_user` indirection table | +| 9 | No RBAC middleware | Medium | Medium | Yes — saves you from future security bugs | +| 10 | No rate limiting on invites | Medium | Low | Yes — trivial to add | +| 11 | No event versioning | Medium | Trivial | Yes — just add `"v": 1` | +| 12 | Global slug uniqueness | Medium | Low | Decide before shipping | +| 13 | No request tracing | Low | Low | Nice to have | diff --git a/echo/docs/workspaces/codebase-exploration-report.md b/echo/docs/workspaces/codebase-exploration-report.md new file mode 100644 index 00000000..fa481058 --- /dev/null +++ b/echo/docs/workspaces/codebase-exploration-report.md @@ -0,0 +1,1090 @@ +# Codebase Exploration Report +## Session 1: EXPLORE — Dembrane ECHO Platform +> **Date:** 2026-04-07 +> **Branch:** workspaces (based on main) +> **Status:** READ-ONLY exploration, no code changes +> **Directus:** v11.13.4 on PostgreSQL + +--- + +## 1. DIRECTUS SCHEMA MAP + +### 1.1 directus_users (Directus system collection) + +**Default Directus fields** (not in sync files, managed by Directus core): +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | Directus-managed | +| `first_name` | string, nullable | | +| `last_name` | string, nullable | | +| `email` | string, unique | | +| `password` | hash | Never exposed via API | +| `location` | string, nullable | | +| `title` | string, nullable | | +| `description` | text, nullable | | +| `tags` | json, nullable | | +| `avatar` | uuid, FK → directus_files | | +| `language` | string, nullable | | +| `tfa_secret` | string, nullable | 2FA secret (exposed as boolean `tfa_enabled` in /me) | +| `status` | string | active/invited/suspended/archived | +| `role` | uuid, FK → directus_roles | | +| `token` | string, nullable | Static API token | +| `last_access` | timestamp, nullable | | +| `last_page` | string, nullable | | +| `provider` | string | default/ldap/oauth2 | +| `external_identifier` | string, nullable | | +| `auth_data` | json, nullable | | +| `email_notifications` | boolean | | +| `appearance` | string, nullable | | +| `theme_dark` | string, nullable | | +| `theme_light` | string, nullable | | +| `theme_light_overrides` | json, nullable | | +| `theme_dark_overrides` | json, nullable | | + +**Custom fields** (from sync files): +| Field | Type | Nullable | Default | Notes | +|-------|------|----------|---------|-------| +| `disable_create_project` | boolean | yes | false | Locks user to single project | +| `hide_ai_suggestions` | boolean | yes | false | Hides chat suggestions | +| `legal_basis` | string | yes | "client-managed" | consent/client-managed/dembrane-events | +| `privacy_policy_url` | string | yes | null | Per-user privacy policy URL | +| `whitelabel_logo` | uuid, FK → directus_files | yes | null | on_delete: SET NULL | +| `quick_access_preferences` | json | yes | [] | Ordered template preferences | +| `projects` | alias (O2M) | — | — | O2M → project via project.directus_user_id | + +**Roles:** +| Role | Parent | admin_access | app_access | +|------|--------|-------------|------------| +| Administrator | — | true | true | +| Basic User | — | false | true | +| Enterprise User | Basic User | false | true | +| Read-Only | — | false | false | + +--- + +### 1.2 project + +| Field | Type | Nullable | Default | Notes | +|-------|------|----------|---------|-------| +| `id` | uuid, PK | no | — | | +| `name` | string | yes | null | | +| `context` | text | yes | null | Project description/context for LLM | +| `language` | string | yes | null | Project language code | +| `directus_user_id` | uuid, FK → directus_users | yes | null | Owner. on_delete: SET NULL | +| `is_conversation_allowed` | boolean | no | — | Controls participant portal access | +| `pin_order` | integer | yes | null | 1-3 for pinned projects | +| `created_at` | timestamp | yes | CURRENT_TIMESTAMP | | +| `updated_at` | timestamp | yes | CURRENT_TIMESTAMP | | +| `anonymize_transcripts` | boolean | yes | false | | +| `conversation_title_prompt` | text | yes | null | | +| `conversation_ask_for_participant_name_label` | string | yes | null | | +| `default_conversation_ask_for_participant_email` | boolean | yes | false | | +| `default_conversation_ask_for_participant_name` | boolean | yes | true | | +| `default_conversation_description` | text | yes | null | | +| `default_conversation_finish_text` | text | yes | null | | +| `default_conversation_title` | string | yes | null | | +| `default_conversation_transcript_prompt` | text | yes | null | | +| `default_conversation_tutorial_slug` | string | yes | "none" | | +| `enable_ai_title_and_tags` | boolean | yes | false | | +| `get_reply_mode` | string | yes | "summarize" | | +| `get_reply_prompt` | text | yes | null | | +| `image_generation_model` | string | yes | "PLACEHOLDER" | | +| `is_enhanced_audio_processing_enabled` | boolean | yes | false | | +| `is_get_reply_enabled` | boolean | yes | false | | +| `is_project_notification_subscription_allowed` | boolean | yes | false | | +| `is_verify_enabled` | boolean | yes | false | | +| `is_verify_on_finish_enabled` | boolean | yes | false | | +| `selected_verification_key_list` | text | yes | null | Comma-separated keys | +| **Alias fields (O2M):** | | | | | +| `conversations` | alias | — | — | O2M → conversation | +| `tags` | alias | — | — | O2M → project_tag | +| `project_chats` | alias | — | — | O2M → project_chat | +| `project_reports` | alias | — | — | O2M → project_report | +| `project_analysis_runs` | alias | — | — | O2M → project_analysis_run | +| `custom_verification_topics` | alias | — | — | O2M → verification_topic | +| `processing_status` | alias | — | — | O2M → processing_status | + +**Key relations from project:** +- `project.directus_user_id` → `directus_users` (SET NULL) +- `conversation.project_id` → `project` (CASCADE) +- `project_tag.project_id` → `project` (CASCADE) +- `project_chat.project_id` → `project` (CASCADE) +- `project_agentic_run.project_id` → `project` (CASCADE) +- `project_analysis_run.project_id` → `project` (CASCADE) +- `project_report.project_id` → `project` (SET NULL) +- `project_webhook.project_id` → `project` (SET NULL) +- `verification_topic.project_id` → `project` (SET NULL) +- `processing_status.project_id` → `project` (SET NULL) + +--- + +### 1.3 conversation + +| Field | Type | Nullable | Default | Notes | +|-------|------|----------|---------|-------| +| `id` | uuid, PK | no | — | | +| `project_id` | uuid, FK → project | no | — | on_delete: CASCADE | +| `duration` | float | yes | null | Duration in seconds | +| `title` | text | yes | null | AI-generated | +| `summary` | text | yes | null | AI-generated | +| `participant_name` | string | yes | null | | +| `participant_email` | string | yes | null | | +| `participant_user_agent` | string | yes | null | | +| `source` | string | yes | null | | +| `is_finished` | boolean | yes | false | | +| `is_all_chunks_transcribed` | boolean | yes | null | | +| `is_audio_processing_finished` | boolean | yes | false | | +| `is_anonymized` | boolean | yes | false | | +| `merged_audio_path` | text | yes | null | S3 path | +| `merged_transcript` | text | yes | null | | +| `created_at` | timestamp | yes | CURRENT_TIMESTAMP | | +| `updated_at` | timestamp | yes | CURRENT_TIMESTAMP | | +| **Alias fields:** | | | | | +| `chunks` | alias | — | — | O2M → conversation_chunk | +| `conversation_artifacts` | alias | — | — | O2M → conversation_artifact | +| `conversation_segments` | alias | — | — | O2M → conversation_segment | +| `tags` | alias | — | — | M2M → project_tag via conversation_project_tag | +| `project_chats` | alias | — | — | M2M → project_chat via project_chat_conversation | +| `project_chat_messages` | alias | — | — | O2M via junction | +| `replies` | alias | — | — | O2M → conversation_reply | +| `linked_conversations` | alias | — | — | O2M → conversation_link (source) | +| `linking_conversations` | alias | — | — | O2M → conversation_link (target) | + +**NOTE:** The field is named `duration` (not `duration_seconds` as PRD assumes). It stores seconds as a float. + +--- + +### 1.4 conversation_chunk + +| Field | Type | Nullable | Default | +|-------|------|----------|---------| +| `id` | uuid, PK | no | — | +| `conversation_id` | uuid, FK → conversation | no | — (CASCADE) | +| `path` | string | yes | null | S3 audio path | +| `transcript` | text | yes | null | Corrected transcript | +| `raw_transcript` | text | yes | null | Original from ASR | +| `source` | string | yes | null | | +| `timestamp` | timestamp | no | — | | +| `created_at` | timestamp | yes | CURRENT_TIMESTAMP | +| `updated_at` | timestamp | yes | CURRENT_TIMESTAMP | +| `desired_language` | string | yes | null | +| `detected_language` | string | yes | null | +| `detected_language_confidence` | float | yes | null | +| `diarization` | json | yes | null | +| `error` | text | yes | null | +| `hallucination_reason` | text | yes | null | +| `hallucination_score` | float | yes | null | +| `noise_ratio` | float | yes | null | +| `silence_ratio` | float | yes | null | +| `cross_talk_instances` | integer | yes | null | +| `runpod_job_status_link` | text | yes | null | +| `runpod_request_count` | integer | yes | null | +| `translation_error` | string | yes | null | + +--- + +### 1.5 project_chat + +| Field | Type | Nullable | Default | +|-------|------|----------|---------| +| `id` | uuid, PK | no | — | +| `project_id` | uuid, FK → project | yes | null (CASCADE) | +| `name` | string | yes | null | +| `chat_mode` | string | yes | null | overview/deep_dive/agentic | +| `auto_select` | boolean | yes | null | +| `user_created` | uuid, FK → directus_users | yes | null | +| `user_updated` | uuid, FK → directus_users | yes | null | +| `date_created` | timestamp | yes | null | +| `date_updated` | timestamp | yes | null | + +--- + +### 1.6 project_chat_message + +| Field | Type | Nullable | Default | +|-------|------|----------|---------| +| `id` | uuid, PK | no | — | +| `project_chat_id` | uuid, FK → project_chat | yes | null (CASCADE) | +| `message_from` | string | yes | null | user/assistant/dembrane | +| `text` | text | yes | null | +| `template_key` | string | yes | null | +| `tokens_count` | integer | yes | null | +| `date_created` | timestamp | yes | null | +| `date_updated` | timestamp | yes | null | + +--- + +### 1.7 project_report + +| Field | Type | Nullable | Default | +|-------|------|----------|---------| +| `id` | bigInteger, PK | no | — | +| `project_id` | uuid, FK → project | yes | null (SET NULL) | +| `content` | text | yes | null | +| `language` | string | yes | null | +| `status` | string | no | — | draft/generating/published/archived/scheduled/cancelled/error | +| `show_portal_link` | boolean | yes | null | +| `scheduled_at` | timestamp | yes | null | +| `error_code` | string | yes | null | +| `error_message` | text | yes | null | +| `user_instructions` | text | yes | null | +| `date_created` | timestamp | yes | null | +| `date_updated` | timestamp | yes | null | + +--- + +### 1.8 Other Collections (summary) + +| Collection | PK Type | Key Fields | Key Relations | +|-----------|---------|------------|---------------| +| `conversation_artifact` | uuid | content, key, topic_label, approved_at | conversation_id → conversation (CASCADE) | +| `conversation_segment` | integer | transcript, contextual_transcript, config_id, counter | conversation_id → conversation (CASCADE) | +| `conversation_link` | bigInteger | link_type, source_conversation_id, target_conversation_id | → conversation (SET NULL) | +| `conversation_reply` | uuid | content_text, type | reply → conversation (SET NULL) | +| `conversation_project_tag` | integer | — | conversation_id → conversation (CASCADE), project_tag_id → project_tag (CASCADE) | +| `conversation_segment_conversation_chunk` | integer | — | junction: conversation_segment (CASCADE) ↔ conversation_chunk (CASCADE) | +| `project_chat_conversation` | integer | — | junction: project_chat (CASCADE) ↔ conversation (CASCADE) | +| `project_tag` | uuid | text, sort | project_id → project (CASCADE) | +| `project_webhook` | uuid | name, url, events, secret, status | project_id → project (SET NULL) | +| `project_agentic_run` | uuid | status, agent_thread_id, directus_user_id | project_id → project (CASCADE), project_chat_id → project_chat (SET NULL) | +| `project_agentic_run_event` | bigInteger | event_type, payload (json), seq | project_agentic_run_id → project_agentic_run (CASCADE) | +| `project_analysis_run` | uuid | — | project_id → project (CASCADE) | +| `project_report_metric` | bigInteger | type, ip | project_report_id → project_report (SET NULL) | +| `project_report_notification_participants` | uuid | email, email_opt_in, email_opt_out_token | conversation_id → conversation (SET NULL) | +| `prompt_template` | uuid | title, content, description, icon, is_public, is_anonymous, language, tags, sort | user_created → directus_users | +| `verification_topic` | uuid (key-based) | All translated fields via junction | project_id → project (SET NULL) | +| `verification_topic_translations` | — | title, message per language | verification_topic_key → verification_topic (SET NULL) | +| `view` | uuid | name, summary, description, language, user_input | project_analysis_run_id → project_analysis_run (SET NULL) | +| `aspect` | uuid | name, short_summary, long_summary, image_url | view_id → view (SET NULL) | +| `aspect_segment` | uuid | description, verbatim_transcript, relevant_index | aspect → aspect (CASCADE), segment → conversation_segment (SET NULL) | +| `processing_status` | bigInteger | event, message, duration_ms, timestamp | multiple nullable FKs to project, conversation, etc. (all SET NULL) | +| `insight` | uuid | title, summary | project_analysis_run_id → project_analysis_run (SET NULL) | +| `chat` | uuid | title | user_created/user_updated → directus_users (appears unused/legacy) | +| `project_chat_message_metadata` | uuid | conversation, message_metadata, ratio, reference_text, type (reference/citation) | In TypeScript types but NOT in sync snapshot (newer collection) | +| `prompt_template_preference` | uuid | template_type (static/user), static_template_id, prompt_template_id, sort | In TypeScript types but NOT in sync snapshot | +| `prompt_template_rating` | uuid | prompt_template_id, rating, chat_message_id | In TypeScript types but NOT in sync snapshot | +| `announcement` | uuid | level, expires_at | system announcements with translations | +| `announcement_activity` | uuid | read, user_id | tracks which users read announcements | +| `languages` | string (code), PK | name, direction | Reference table | + +--- + +### 1.9 CASCADE Dependency Tree + +``` +project (DELETE CASCADE triggers) + ├── conversation (CASCADE) + │ ├── conversation_chunk (CASCADE) + │ │ └── conversation_segment_conversation_chunk (CASCADE) + │ ├── conversation_segment (CASCADE) + │ │ └── conversation_segment_conversation_chunk (CASCADE) + │ ├── conversation_artifact (CASCADE) + │ ├── conversation_project_tag (CASCADE) + │ └── project_chat_conversation (CASCADE) + ├── project_tag (CASCADE) + │ └── conversation_project_tag (CASCADE) + ├── project_chat (CASCADE) + │ ├── project_chat_message (CASCADE) + │ └── project_chat_conversation (CASCADE) + ├── project_agentic_run (CASCADE) + │ └── project_agentic_run_event (CASCADE) + └── project_analysis_run (CASCADE) + +project (SET NULL on delete — data preserved, FK nullified) + ├── project_report.project_id + ├── project_webhook.project_id + ├── verification_topic.project_id + └── processing_status.project_id +``` + +--- + +## 2. PYTHON API ROUTE MAP + +### Architecture + +- **Entry point:** `server/dembrane/main.py` +- **Router aggregation:** `server/dembrane/api/api.py` — mounts all sub-routers under `/api` +- **Auth:** `DependencyDirectusSession` decodes JWT from Directus session cookie using shared `DIRECTUS_SECRET` +- **Admin client:** Module-level `directus` instance with static admin token (`DIRECTUS_TOKEN` env var) +- **User-scoped client:** Per-request `DirectusClient` created from user's JWT, available as `session.client` +- **No global response envelope** — each endpoint returns its own shape + +### Route Table + +#### /api (root) +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/health` | None | Health check | — | No | + +#### /api/projects +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/projects/home` | Session | BFF: paginated projects list with pins, search, owner | R: project, directus_users | No | +| PATCH | `/api/projects/{id}/pin` | Session | Pin/unpin project (1-3 or null) | RW: project | No | +| POST | `/api/projects` | Session | Create project | W: project | No | +| GET | `/api/projects/{id}/transcripts` | Session | Download all transcripts as ZIP | R: project, conversation, conversation_chunk | No | +| POST | `/api/projects/{id}/create-library` | Session | Enqueue library generation task | R: project | No | +| POST | `/api/projects/{id}/create-view` | Session | Enqueue view creation task | R: project, project_analysis_run | No | +| POST | `/api/projects/{id}/create-report` | Session | Create report (immediate or scheduled) | RW: project_report | No | +| GET | `/api/projects/{id}/reports` | Session | List project reports | R: project_report | No | +| GET | `/api/projects/{id}/reports/latest` | Session | Get latest report | R: project_report | No | +| PATCH | `/api/projects/{id}/reports/{rid}` | Session | Update report | RW: project_report | No | +| DELETE | `/api/projects/{id}/reports/{rid}` | Session | **Hard delete** report | D: project_report | **YES** | +| POST | `/api/projects/{id}/reports/{rid}/cancel-schedule` | Session | Cancel scheduled report | W: project_report | No | +| GET | `/api/projects/{id}/reports/{rid}/detail` | Session | Report full content | R: project_report | No | +| GET | `/api/projects/{id}/reports/{rid}/views` | Session | Report view counts | R: project_report_metric | No | +| GET | `/api/projects/{id}/reports/{rid}/needs-update` | Session | Check for newer conversations | R: project_report, conversation | No | +| GET | `/api/projects/{id}/participants/count` | Session | Email-opted-in participant count | R: project_report_notification_participants | No | +| GET | `/api/projects/{id}/reports/{rid}/progress` | Session | SSE: report generation progress | R: project_report | No (SSE) | +| POST | `/api/projects/{id}/clone` | Session | Clone project with tags | RW: project | No | + +#### /api/projects/{id}/webhooks +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/projects/{id}/webhooks` | Session | List webhooks | R: project_webhook | No | +| GET | `/api/projects/{id}/webhooks/copyable` | Session | Copyable webhooks from other projects | R: project_webhook | No | +| POST | `/api/projects/{id}/webhooks` | Session | Create webhook | W: project_webhook | No | +| PATCH | `/api/projects/{id}/webhooks/{wid}` | Session | Update webhook | RW: project_webhook | No | +| DELETE | `/api/projects/{id}/webhooks/{wid}` | Session | **Hard delete** webhook | D: project_webhook | **YES** | +| POST | `/api/projects/{id}/webhooks/{wid}/test` | Session | Test webhook delivery | R: project_webhook | No | + +#### /api/conversations +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/conversations/health/stream` | None | SSE: conversation health monitoring | R: conversation | No (SSE) | +| GET | `/api/conversations/{id}/counts` | Session | Chunk counts | R: conversation_chunk | No | +| GET | `/api/conversations/{id}/content` | Session | Audio content (merge + redirect) | R: conversation_chunk; W: conversation | No | +| GET | `/api/conversations/{id}/chunks/{cid}/content` | Session | Single chunk audio (S3 redirect) | R: conversation_chunk | No | +| GET | `/api/conversations/{id}/transcript` | Session | Full transcript text | R: conversation_chunk | No | +| GET | `/api/conversations/{id}/emails` | Session | Participant emails | R: project_report_notification_participants | No | +| GET | `/api/conversations/{id}/token-count` | Session | LLM token count (cached) | R: conversation_chunk | No | +| POST | `/api/conversations/{id}/get-reply` | None | LLM reply (SSE stream) | R: conversation, conversation_chunk | No | +| POST | `/api/conversations/{id}/summarize` | Session | Generate summary | RW: conversation | No | +| POST | `/api/conversations/{id}/generate-title` | Session | Generate title | RW: conversation | No | +| POST | `/api/conversations/{id}/retranscribe` | Session | Clone + re-transcribe | RW: conversation, conversation_chunk, conversation_link | On error only | +| DELETE | `/api/conversations/{id}` | Session | **Hard delete** conversation + S3 files | D: conversation (service) | **YES** | + +#### /api/chats +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/chats/{id}/context` | Session | Chat context (conversations, token usage) | R: project_chat, project_chat_message, conversation | No | +| POST | `/api/chats/{id}/add-context` | Session | Add conversations to chat | RW: project_chat junction | No | +| POST | `/api/chats/{id}/delete-context` | Session | Remove conversation from chat | D: project_chat_conversation junction | **YES** (junction) | +| POST | `/api/chats/{id}/lock-conversations` | Session | Lock conversations into chat | W: project_chat_message | No | +| GET | `/api/chats/{id}/suggestions` | Session | LLM question suggestions | R: project_chat, project | No | +| POST | `/api/chats/{id}/initialize-mode` | Session | Set chat mode | RW: project_chat | No | +| POST | `/api/chats/{id}` | Session | Send message (SSE stream) | RW: project_chat_message | On error: deletes user msg | + +#### /api/participant (PUBLIC — no auth) +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/participant/projects/{id}` | None | Public project info | R: project, directus_users | No | +| POST | `/api/participant/projects/{id}/conversations/initiate` | None | Start conversation | W: conversation | No | +| GET | `/api/participant/projects/{id}/conversations/{cid}` | None | Conversation info | R: conversation | No | +| GET | `/api/participant/projects/{id}/conversations/{cid}/chunks` | None | List chunks | R: conversation_chunk | No | +| DELETE | `/api/participant/.../chunks/{chid}` | None | **Hard delete** chunk | D: conversation_chunk | **YES** | +| POST | `/api/participant/conversations/{cid}/upload-text` | None | Upload text chunk | W: conversation_chunk | No | +| POST | `/api/participant/conversations/{cid}/upload-chunk` | None | Upload audio chunk | W: conversation_chunk | No | +| POST | `/api/participant/conversations/{cid}/check-s3` | None | S3 connectivity test | R: conversation, project | No | +| POST | `/api/participant/conversations/{cid}/get-upload-url` | None | Presigned S3 URL | R: conversation | No | +| POST | `/api/participant/conversations/{cid}/confirm-upload` | None | Confirm S3 upload | RW: conversation_chunk | No | +| POST | `/api/participant/conversations/{cid}/finish` | None | Signal finished | — (dispatches task) | No | +| GET | `/api/participant/{id}/report/latest` | None | Latest published report | R: project_report | No | +| GET | `/api/participant/{id}/report/{rid}/detail` | None | Published report content | R: project_report | No | +| GET | `/api/participant/{id}/report/views` | None | Report view counts | R: project_report_metric | No | +| POST | `/api/participant/{id}/report/metric` | None | Record view metric | W: project_report_metric | No | +| POST | `/api/participant/report/subscribe` | None | Email subscription | RWD: project_report_notification_participants | **YES** (re-create) | +| POST | `/api/participant/{id}/report/unsubscribe` | None | Unsubscribe | W: project_report_notification_participants | No | +| GET | `/api/participant/report/unsubscribe/eligibility` | None | Check unsubscribe token | R: project_report_notification_participants | No | + +#### /api/agentic +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| POST | `/api/agentic/runs` | Session | Create agentic run | W: project_agentic_run, project_agentic_run_event | No | +| POST | `/api/agentic/runs/{id}/messages` | Session | Add follow-up message | RW: project_agentic_run_event, project_chat_message | No | +| GET | `/api/agentic/projects/{id}/conversations` | Session | List conversations for agent | R: conversation, conversation_chunk | No | +| POST | `/api/agentic/runs/{id}/stream` | Session | SSE: process + stream events | RW: project_agentic_run | No (SSE) | +| POST | `/api/agentic/runs/{id}/stop` | Session | Cancel active run | W: project_agentic_run | No | +| GET | `/api/agentic/runs/{id}` | Session | Run details | R: project_agentic_run | No | +| GET | `/api/agentic/runs/{id}/events` | Session | Run events (SSE or JSON) | R: project_agentic_run_event | No | + +#### /api/verify +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/verify/topics/{pid}` | None | Get verification topics | R: verification_topic, project | No | +| PUT | `/api/verify/topics/{pid}` | None | Update selected topics | RW: project | No | +| POST | `/api/verify/topics/{pid}/custom` | Session | Create custom topic | W: verification_topic, project | No | +| PATCH | `/api/verify/topics/{pid}/custom/{key}` | Session | Update custom topic | RW: verification_topic | No | +| DELETE | `/api/verify/topics/{pid}/custom/{key}` | Session | **Hard delete** topic | D: verification_topic; W: project | **YES** | +| GET | `/api/verify/artifacts/{cid}` | None | List approved artifacts | R: conversation_artifact | No | +| GET | `/api/verify/artifact/{aid}` | None | Single artifact | R: conversation_artifact | No | +| POST | `/api/verify/generate` | None | Generate artifact (LLM) | RW: conversation_artifact | No | +| PUT | `/api/verify/artifact/{aid}` | None | Update/revise artifact | RW: conversation_artifact | No | + +#### /api/user-settings +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/user-settings/me` | Session | Current user profile | R: directus_users (admin client) | No | +| PATCH | `/api/user-settings/password` | Session | Change password | Proxies to Directus auth | No | +| POST | `/api/user-settings/tfa/generate` | Session | Generate 2FA secret | Proxies to Directus | No | +| POST | `/api/user-settings/tfa/enable` | Session | Enable 2FA | Proxies to Directus | No | +| POST | `/api/user-settings/tfa/disable` | Session | Disable 2FA | Proxies to Directus | No | +| POST | `/api/user-settings/whitelabel-logo` | Session | Upload logo | W: directus_files, directus_users | No | +| DELETE | `/api/user-settings/whitelabel-logo` | Session | **Hard delete** logo file | D: directus_files; W: directus_users | **YES** (file) | +| PATCH | `/api/user-settings/name` | Session | Update display name | W: directus_users | No | +| POST | `/api/user-settings/avatar` | Session | Upload avatar | W: directus_files, directus_users | No | +| DELETE | `/api/user-settings/avatar` | Session | **Hard delete** avatar file | D: directus_files; W: directus_users | **YES** (file) | +| PATCH | `/api/user-settings/legal-basis` | Session | Set legal basis | RW: directus_users | No | + +#### /api/templates +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/templates/prompt-templates` | Session | List user templates | R: prompt_template | No | +| POST | `/api/templates/prompt-templates` | Session | Create template | W: prompt_template | No | +| PATCH | `/api/templates/prompt-templates/{id}` | Session | Update template | RW: prompt_template | No | +| DELETE | `/api/templates/prompt-templates/{id}` | Session | **Hard delete** template | D: prompt_template | **YES** | +| GET | `/api/templates/quick-access` | Session | Quick access preferences | R: directus_users | No | +| PUT | `/api/templates/quick-access` | Session | Save quick access prefs | W: directus_users | No | +| PATCH | `/api/templates/ai-suggestions` | Session | Toggle AI suggestions | W: directus_users | No | + +#### /api/home +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/home/search` | Session | Global search | R: project, conversation, conversation_chunk, project_chat | No | + +#### /api/stats +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| GET | `/api/stats/` | None | Aggregate platform stats (cached) | R: directus_users, project, conversation | No | + +#### /api/webhooks +| Method | Path | Auth | Description | Collections | Deletes? | +|--------|------|------|-------------|-------------|----------| +| POST | `/api/webhooks/assemblyai` | Webhook secret | AssemblyAI transcription callback | R: Redis; dispatches task | No | + +--- + +## 3. FRONTEND ROUTE MAP + +### Host Dashboard (mainRouter) + +All paths prefixed with `/:language?/` + +| Path | Component | Data Source | Delete Ops | +|------|-----------|-------------|------------| +| `/login` | LoginRoute | Directus SDK (auth) | — | +| `/register` | RegisterRoute | Directus SDK | — | +| `/check-your-email` | CheckYourEmailRoute | Static | — | +| `/password-reset` | PasswordResetRoute | Directus SDK | — | +| `/request-password-reset` | RequestPasswordResetRoute | Directus SDK | — | +| `/verify-email` | VerifyEmailRoute | Directus SDK | — | +| `/projects` | ProjectsHomeRoute | **Python API** (`/projects/home` BFF) | — | +| `/projects/:id` | Redirect → portal-editor | — | — | +| `/projects/:id/portal-editor` | ProjectPortalSettingsRoute | Directus SDK + Python API | Delete tags (Directus direct), delete verification topics (Python) | +| `/projects/:id/overview` | ProjectSettingsRoute | Directus SDK + Python API | **Delete project** (Directus SDK direct!), delete webhooks (Python) | +| `/projects/:id/chats/new` | NewChatRoute | Directus SDK + Python API | — | +| `/projects/:id/chats/:chatId` | ProjectChatRoute | Python API + Directus SDK | **Delete chat** (Directus SDK direct!) | +| `/projects/:id/conversation/:cid/overview` | ProjectConversationOverviewRoute | Directus SDK + Python API | **Delete conversation** (Python API) | +| `/projects/:id/conversation/:cid/transcript` | ProjectConversationTranscript | Directus SDK | — | +| `/projects/:id/library` | ProjectLibraryRoute | Directus SDK + Python API | — | +| `/projects/:id/library/views/:vid` | ProjectLibraryView | Directus SDK | — | +| `/projects/:id/library/views/:vid/aspects/:aid` | ProjectLibraryAspect | Directus SDK | — | +| `/projects/:id/report` | ProjectReportRoute | Python API | **Delete report** (Python API) | +| `/projects/:id/host-guide` | HostGuidePage | Directus SDK + SSE | — | +| `/settings` | UserSettingsRoute | Python API + Directus SDK | — | + +### Participant Portal (participantRouter) + +All paths prefixed with `/:language?/:projectId/` + +| Path | Component | Data Source | Delete Ops | +|------|-----------|-------------|------------| +| `/start` | ParticipantStartRoute | Python API | — | +| `/conversation/:cid` | ParticipantConversationAudioRoute | Python API | Delete chunk (Python API) | +| `/conversation/:cid/text` | ParticipantConversationTextRoute | Python API | — | +| `/conversation/:cid/refine` | RefineSelection | Python API | — | +| `/conversation/:cid/verify` | VerifySelection | Python API | — | +| `/conversation/:cid/verify/approve` | VerifyArtefact | Python API | — | +| `/conversation/:cid/finish` | ParticipantPostConversation | Python API | — | +| `/report` | ParticipantReport | Python API | — | +| `/unsubscribe` | ProjectUnsubscribe | Python API | — | + +--- + +## 4. DELETE OPERATION INVENTORY + +### 4a. Frontend → Directus Direct DELETE Calls + +| # | File | Hook/Function | Collection | Trigger | Metadata Lost | +|---|------|--------------|------------|---------|---------------| +| D1 | `components/project/hooks/index.ts:104` | `useDeleteProjectByIdMutation` | **project** | User clicks delete in ProjectDangerZone | **CATASTROPHIC**: ALL conversations (duration, transcripts, audio), ALL chunks, segments, artifacts, tags, chats, chat messages, analysis runs, agentic runs. Biggest data loss event possible. | +| D2 | `components/chat/hooks/index.ts:68` | `useDeleteChatMutation` | **project_chat** | User deletes chat from sidebar menu | All chat messages (text, tokens_count), conversation junction records | +| D3 | `components/project/hooks/index.ts:188` | `useDeleteTagByIdMutation` | **project_tag** | User deletes a project tag | All conversation-tag associations (CASCADE) | +| D4 | `components/conversation/hooks/index.ts:187` | `useUpdateConversationTagsMutation` | **conversation_project_tag** | User removes tags from conversation | Junction records only (low impact) | + +### 4b. Frontend → Python API Delete Endpoints + +| # | Frontend File | Python Endpoint | Collection | Trigger | Metadata Lost | +|---|------|--------|------------|---------|---------------| +| D5 | `lib/api.ts:1614` | `DELETE /api/conversations/{id}` | **conversation** | User clicks delete in ConversationDangerZone | duration, summary, title, transcript, participant info; S3 audio files also deleted | +| D6 | `lib/api.ts:1341` | `DELETE /api/projects/{id}/reports/{rid}` | **project_report** | User deletes report | Report content, language, status; metrics SET NULL | +| D7 | `lib/api.ts:1737` | `DELETE /api/projects/{id}/webhooks/{wid}` | **project_webhook** | User deletes webhook | Webhook URL, secret, events config | +| D8 | `lib/api.ts:1811` | `DELETE /api/templates/prompt-templates/{id}` | **prompt_template** | User deletes template | Template title, content, description | +| D9 | `components/settings/WhitelabelLogoCard.tsx:70` | `DELETE /api/user-settings/whitelabel-logo` | **directus_files** | User removes logo | File only (no billing impact) | +| D10 | `components/settings/AccountSettingsCard.tsx:87` | `DELETE /api/user-settings/avatar` | **directus_files** | User removes avatar | File only (no billing impact) | +| D11 | `lib/api.ts:965` | `POST /api/chats/{id}/delete-context` | **project_chat_conversation** | User removes conversation from chat | Junction record only | +| D12 | `lib/api.ts:58` | `DELETE /api/participant/.../chunks/{id}` | **conversation_chunk** | Participant deletes their chunk | Chunk transcript, S3 audio path (orphaned!) | +| D13 | (verify settings page) | `DELETE /api/verify/topics/{pid}/custom/{key}` | **verification_topic** | User deletes custom topic | Topic translations SET NULL | + +### 4c. Python API → Directus DELETE Calls (System-Initiated) + +| # | File | Function | Collection | Trigger | Metadata Lost | +|---|------|----------|------------|---------|---------------| +| D14 | `audio_utils.py:740` | split_audio_chunk | **conversation_chunk** | Audio chunk too large, auto-split | Original replaced by sub-chunks (data preserved) | +| D15 | `api/conversation.py:878` | retranscribe (error path) | **conversation** | Retranscription fails | Partial new conversation (cleanup) | +| D16 | `api/chat.py:1138,1160` + `service/chat.py:296` | ChatService.delete_message | **project_chat_message** | LLM stream error | User's chat message text | +| D17 | `api/participant.py:808` | report subscribe | **project_report_notification_participants** | Participant re-subscribes | Re-created immediately | + +### 4d. Directus Hooks/Flows That Delete Data + +| Flow | Status | Trigger | What It Does | +|------|--------|---------|-------------| +| Send Email | active | operation (manual trigger) | Sends email, no deletes | +| Send Email Base | active | manual on project | Sends email, no deletes | +| Send Report Emails | active | event on project_report update | Sends notification emails, no deletes | +| Validate create project | **inactive** (draft) | filter on project create | Would validate, no deletes | + +**No Directus flows or hooks perform delete operations.** + +### 4e. Automated Cleanup + +| # | Location | What | Trigger | +|---|----------|------|---------| +| D18 | `audio_utils.py:287` | S3 original audio after format conversion | Audio processing pipeline | +| D19 | `audio_utils.py:751` | S3 files on split failure (cleanup) | Error during chunk splitting | +| D20 | `api/project.py:369` | Local temp files after ZIP download | BackgroundTask after response | +| D21 | `coordination.py:441` | Redis coordination keys | After conversation finalization | + +--- + +## 5. AUTH FLOW + +### End-to-End Login + +``` +Frontend Login.tsx + → directus.login({ email, password }, { otp }) # Directus SDK + → Directus POST /auth/login (session mode) + → Directus sets HTTP-only cookie: directus_session_token + → Frontend stores nothing (browser cookie jar handles it) + → Redirect to /projects (or ?next= param) +``` + +### Session Validation (Frontend) + +``` +Protected.tsx → useAuthenticated() + → directus.refresh() # Directus SDK + → Directus POST /auth/refresh (using session cookie) + → If fails → logout + redirect to /login + → staleTime: 60s (re-validates every 60s) +``` + +### Get Current User (Python) + +**File:** `server/dembrane/api/dependency_auth.py` + +```python +async def require_directus_session(request: Request) -> DirectusSession: + # 1. Extract JWT from cookie or Authorization header + directus_cookie = request.cookies.get(DIRECTUS_SESSION_COOKIE_NAME) + auth_header = request.headers.get("Authorization") + + # 2. Decode JWT locally (NOT forwarded to Directus) + decoded = jwt.decode(token=to_decode, key=DIRECTUS_SECRET, algorithms=["HS256"]) + user_id = decoded.get("id") + is_admin = decoded.get("admin_access", False) + + # 3. Create per-request Directus client scoped to user's token + client = create_directus_client(token=to_decode) + + return DirectusSession(str(user_id), bool(is_admin), access_token=to_decode, client=client) +``` + +**Returns:** `DirectusSession` with `user_id` (string), `is_admin` (bool), `access_token`, `client` (user-scoped DirectusClient) + +### User Profile Fields (from /me endpoint) + +```python +USER_PROFILE_FIELDS = [ + "id", "first_name", "email", "avatar", "disable_create_project", + "tfa_secret", "whitelabel_logo", "legal_basis", "privacy_policy_url", + "hide_ai_suggestions", +] +# tfa_secret is replaced with boolean tfa_enabled before returning +``` + +--- + +## 6. DIRECTUS CLIENT PATTERN + +### Python DirectusClient + +**File:** `server/dembrane/directus.py` (975 lines) + +**Library:** `requests` (synchronous) + +**Two instances:** +1. **Admin client** (module-level): `directus = create_directus_client(token=settings.directus.token)` +2. **User-scoped** (per-request): `create_directus_client(token=user_jwt)` — returned as `session.client` + +**CRUD patterns:** + +```python +# CREATE — returns {"data": {...}} — MUST unwrap with ["data"] +new = directus.create_item("collection", {"field": "value"})["data"] + +# READ (list) — returns data directly (list), requires "query" wrapper +items = directus.get_items("collection", {"query": {"filter": {...}, "fields": [...]}}) +# Internally calls: self.search(f"/items/{collection}", query=query) +# Uses HTTP SEARCH method + +# READ (single) — returns data directly +item = directus.get_item("collection", item_id) + +# UPDATE — returns {"data": {...}} +updated = directus.update_item("collection", item_id, {"field": "value"})["data"] + +# DELETE — no return value +directus.delete_item("collection", item_id) + +# GET USERS — special method for directus_users +users = directus.get_users({"query": {"filter": {...}, "fields": [...]}}) +``` + +**Filter syntax (Directus operators):** + +```python +{"filter": {"field": {"_eq": value}}} +{"filter": {"field": {"_null": True}}} +{"filter": {"field": {"_in": [val1, val2]}}} +{"filter": {"_and": [{"field1": {"_eq": v1}}, {"field2": {"_eq": v2}}]}} +``` + +**Error handling:** + +```python +# Custom exceptions in directus.py: +DirectusGenericException # Base +DirectusAuthError # Auth failures +DirectusServerError # Connection errors +DirectusBadRequest # 4xx responses + +# Retry logic: up to 3 retries with exponential backoff +# Auto re-auth on 401/403 if email/password available +``` + +**Environment variables:** + +| Var | Required | Default | Purpose | +|-----|----------|---------|---------| +| `DIRECTUS_BASE_URL` | No | `http://directus:8055` | Internal Directus URL | +| `DIRECTUS_SECRET` | Yes | — | Shared JWT secret (HS256) | +| `DIRECTUS_TOKEN` | Yes | — | Admin static token | +| `DIRECTUS_SESSION_COOKIE_NAME` | No | `directus_session_token` | Cookie name | + +--- + +## 7. EXISTING PATTERNS + +### Multi-User / Sharing / Team Concepts + +**None exist.** Projects are owned by a single user via `project.directus_user_id`. All Directus permissions for "Basic User" filter by `directus_user_id = $CURRENT_USER`. No `shared_with`, `collaborators`, `team_id`, or `workspace` fields exist anywhere. + +### Soft Delete / Archive Patterns + +**None exist.** No `deleted_at` field on any collection. No archive functionality configured. The only "status" field with archive-like values is `project_report.status` which includes `"archived"`, but this is a workflow status, not a soft delete. + +### Schema Sync + +**Tool:** `directus-sync` (npm package) +**Config:** `directus/directus-sync.config.js` — dumpPath: `./sync`, specs disabled +**Wrapper:** `directus/sync.sh` — supports `push` (source → Directus), `pull` (Directus → source), `diff` +**Output:** +- `sync/collections/` — permissions.json, roles.json, policies.json, flows.json, operations.json, settings.json +- `sync/snapshot/collections/` — one JSON per collection +- `sync/snapshot/fields//` — one JSON per field +- `sync/snapshot/relations//` — one JSON per relation + +**No CI/CD validates schema.** Sync is manual. + +### Email + +**Handled entirely by Directus Flows** using Liquid templates. Templates in `directus/templates/`: +- `email-base.liquid` — base layout +- `password-reset.liquid`, `user-invite.liquid`, `user-registration.liquid` — auth +- `report-notification-en.liquid`, `report-notification-nl.liquid` — report notifications + +**Python does NOT send emails directly.** No SendGrid in the Python backend. This is important for the workspace invite flow — it will need to either use Directus email or add SendGrid to Python. + +### Background Tasks (Dramatiq) + +- **Broker:** Redis with lz4 compression +- **Queues:** `network` (gevent, most tasks), `cpu` (standard, compute-heavy) +- **17 actors** in `server/dembrane/tasks.py` +- **4 periodic jobs** via APScheduler in `scheduler.py` +- **Middleware:** Results, GroupCallbacks, Workflow, SkipRetryOnUnrecoverableError + +--- + +## 8. PROJECT STRUCTURE + +### Python API + +``` +server/dembrane/ + api/ # Routes — add new router files here + api.py # Router aggregator — register new routers here + dependency_auth.py # Auth dependencies + exceptions.py # HTTP exception constants + rate_limit.py # Redis rate limiters + project.py, conversation.py, chat.py, ... # Route files + service/ # Business logic — add new service classes here + __init__.py # Factory functions (build_*_service) + project.py, conversation.py, chat.py, ... + directus.py # DirectusClient wrapper + settings.py # All env var config (Pydantic BaseSettings) + tasks.py # Dramatiq actors + utils.py # General utilities + async_helpers.py # Thread pool + async helpers +``` + +### Frontend + +``` +frontend/src/ + Router.tsx # Route definitions — add new routes here + routes/ # Page components — add new pages here + auth/, project/, participant/, settings/ + components/ # Feature-organized — add new components in domain folders + /hooks/ # React Query hooks per domain + lib/ + api.ts # Centralized API calls — add new API functions here + directus.ts # Directus SDK setup + typesDirectus.d.ts # Directus schema types — add new collection types here + types.d.ts # Frontend-only types + locales/ # i18n translations +``` + +--- + +## 9. PRD RECONCILIATION + +### 9a. APP_USER GAP ANALYSIS + +The PRD defines `app_user` with: `id`, `directus_user_id`, `email`, `display_name`, `created_at`, `updated_at`. + +**Fields on directus_users that are actively used by the app but NOT in PRD's app_user:** + +| Field | Used Where | Recommendation | +|-------|-----------|----------------| +| `first_name` | /me endpoint, migration display_name construction, participant project view | **Denormalize** into app_user as `display_name` (concat first+last at migration time) | +| `last_name` | Same as first_name | Folded into `display_name` | +| `avatar` | /me endpoint, settings page | **Fetch from directus_users at runtime** — it's a file reference that changes | +| `disable_create_project` | /me endpoint, project creation gating | **Denormalize** — this is a domain concept | +| `whitelabel_logo` | /me endpoint, branding, participant portal | **Move to workspace** settings (per PRD's workspace.settings or workspace.logo_url) | +| `legal_basis` | /me endpoint, settings, participant consent | **Move to workspace** (PRD already has workspace.legal_basis) | +| `privacy_policy_url` | /me endpoint, settings, participant consent | **Move to workspace** (PRD already has workspace.privacy_policy_url) | +| `tfa_secret` | /me endpoint (as tfa_enabled boolean) | **Never denormalize** — sensitive, stays in directus_users | +| `quick_access_preferences` | Template quick access | **Stays in directus_users** — per-user UI preference | +| `hide_ai_suggestions` | Template suggestions toggle | **Stays in directus_users** — per-user UI preference | +| `email` | /me endpoint, various lookups | **Denormalize** (PRD already includes this) | +| `role` (Directus role ID) | Admin checks via JWT `admin_access` claim | **Do NOT denormalize** — resolved from JWT | + +**Summary:** app_user should add `disable_create_project` beyond what the PRD specifies. The whitelabel/legal_basis/privacy_policy fields should move from directus_users to workspace-level settings (which the PRD already plans). `avatar` stays as a runtime fetch from directus_users. + +### 9b. COLLECTION NAME CHECK + +| PRD Says | Actual Codebase | Match? | Notes | +|----------|----------------|--------|-------| +| `conversation` | `conversation` | YES | | +| `project` | `project` | YES | | +| `chat` | `project_chat` | **NO** | PRD says "chat (or whatever the chat/message collection is called)". Actual name is `project_chat` with messages in `project_chat_message` | +| `report` | `project_report` | **NO** | PRD says "report". Actual name is `project_report` | +| `conversation.duration_seconds` | `conversation.duration` | **NO** | PRD references `duration_seconds`. Actual field is `duration` (float, stores seconds) | +| — | `chat` collection | — | There's a **separate legacy `chat` collection** (uuid id, title, user_created/updated). Appears unused. Not the same as `project_chat`. | + +### 9c. API PATTERN CHECK + +| PRD Pattern | Actual Pattern | Match? | +|-------------|---------------|--------| +| `await directus.get(f"/items/project/{id}")` | `directus.get_item("project", id)` | **NO** — Python uses wrapper methods, not raw path construction | +| `await directus.patch(f"/items/conversation/{id}", {...})` | `directus.update_item("conversation", id, {...})` | **NO** — uses `update_item` not raw `patch` | +| `await directus.get("/items/workspace_membership", {"filter": ...})` | `directus.get_items("workspace_membership", {"query": {"filter": ...}})` | **NO** — requires `{"query": {...}}` wrapper | +| `await directus.post("/items/app_user", {...})` | `directus.create_item("app_user", {...})["data"]` | **NO** — uses `create_item` and must unwrap `["data"]` | +| `async def` (PRD shows async functions) | DirectusClient is **synchronous** (uses `requests` library) | **NO** — no `await` needed | +| `directus.search()` returns `{"error": ...}` on failure | Actual: returns dict, must check `isinstance(result, list)` | Correct in CLAUDE.md but PRD examples don't show this | + +**Critical:** The PRD's example code uses raw HTTP-style calls (`directus.get("/items/...")`, `directus.patch(...)`) but the actual codebase uses typed wrapper methods (`get_item`, `get_items`, `create_item`, `update_item`, `delete_item`). All PRD code examples need translation. + +### 9d. PERMISSION MODEL CHECK + +| PRD Assumption | Reality | Conflict? | +|----------------|---------|-----------| +| All deletes through Python API | Project delete currently goes Frontend → Directus SDK directly | **YES** — must reroute before soft delete | +| Chat delete through Python | Chat delete goes Frontend → Directus SDK directly | **YES** — must reroute | +| Directus roles have DELETE permissions | Basic User policy has DELETE on: conversation, project, project_chat, project_tag, view, aspect, conversation_artifact, etc. | Compatible — these can remain for backward compat during migration | +| Admin check via Directus | Auth uses JWT claim `admin_access` from Directus | Compatible | +| workspace_membership checks | No membership tables exist yet | N/A — new | + +### 9e. CONFLICT CHECK + +| Conflict | Details | Severity | +|----------|---------|----------| +| Legacy `chat` collection | A separate `chat` collection exists (not `project_chat`). It has `id`, `title`, `user_created`, `user_updated`. Appears unused but may confuse schema. | LOW — verify it's truly unused, then ignore | +| `project.directus_user_id` stays after workspace migration | PRD says to keep for backward compat. The field has `on_delete: SET NULL` to directus_users. No conflict, but need to decide when to stop using it. | LOW | +| `project_report.project_id` is SET NULL (not CASCADE) | Unlike most relations, deleting a project does NOT delete its reports. Reports survive project deletion. This is actually good for billing — but means soft delete of projects won't automatically "hide" their reports. | MEDIUM — reports need their own `deleted_at` filter, or need to join through project | +| `project_webhook.project_id` is SET NULL | Same as reports — webhooks survive project deletion | LOW | +| `verification_topic.project_id` is SET NULL | Topics survive project deletion | LOW | +| No `duration_seconds` field | PRD references `duration_seconds` but field is `duration` (float) | LOW — just use correct field name | +| `project_report.id` is bigInteger | Not UUID like other collections. PRD assumes UUID for all new collections. | LOW — only matters if we FK to it | + +### 9f. RISK REGISTER + +| # | Risk | Likelihood | Impact | Mitigation | +|---|------|-----------|--------|------------| +| 1 | **Project delete bypasses Python entirely** — goes Frontend → Directus SDK. Soft delete conversion requires rerouting this through Python first. If missed, projects can still be hard-deleted. | HIGH (it's the current behavior) | CRITICAL (destroys all data) | Session 3 must create a Python endpoint for project delete AND update the frontend hook to call it. Remove Directus DELETE permission for Basic User on project collection. | +| 2 | **Chat delete also bypasses Python** — same issue as project. Frontend calls `directus.request(deleteItem("project_chat", id))` directly. | HIGH | HIGH (destroys chat history) | Same mitigation as #1 — new Python endpoint, update frontend, restrict Directus permissions. | +| 3 | **CASCADE deletes are destructive** — deleting a project cascades to 10+ tables. Even with soft delete on project, if any code path still does a hard DELETE, all cascaded data is gone forever. Must ensure NO hard delete paths remain after conversion. | MEDIUM | CRITICAL | Audit all code paths in Session 3. Consider removing CASCADE constraints and replacing with application-level soft cascade. | +| 4 | **Email sending is in Directus, not Python** — the PRD assumes workspace invites will use SendGrid from Python. But the current codebase has NO email capability in Python. All email goes through Directus Flows with Liquid templates. | HIGH | MEDIUM (blocks invite flow) | Either: (a) add SendGrid to Python for invite emails, or (b) create a Directus Flow triggered via Directus API from Python, or (c) use Directus's built-in mail API from Python. | +| 5 | **Synchronous DirectusClient** — the DirectusClient uses `requests` (blocking). In FastAPI async endpoints, this blocks the event loop. The PRD shows `async def` + `await` patterns but the client is synchronous. At scale with workspace permission checks on every request, this could become a bottleneck. | MEDIUM | MEDIUM (performance) | For now, wrap in `run_in_thread_pool()` (already used elsewhere in the codebase). Long-term, consider an async client. | + +--- + +## 10. SUMMARY OF CRITICAL FINDINGS + +### Must Address Before Session 2 (Schema) + +1. **Collection name corrections for PRD:** `project_chat` (not "chat"), `project_report` (not "report"), `conversation.duration` (not `duration_seconds`) +2. **app_user needs `disable_create_project`** field added beyond PRD spec +3. **whitelabel_logo, legal_basis, privacy_policy_url** should move from directus_users to workspace — PRD already handles this via `workspace.settings`, `workspace.legal_basis`, `workspace.privacy_policy_url` + +### Must Address Before Session 3 (Soft Delete) + +1. **Route project delete through Python** — currently Frontend → Directus direct +2. **Route chat delete through Python** — currently Frontend → Directus direct +3. **Route tag delete through Python** — currently Frontend → Directus direct +4. **Email capability in Python** — needed for workspace invites (or use Directus mail API) + +### Must Address Before Session 4 (Core API) + +1. **Translate all PRD code examples** from raw HTTP patterns to actual DirectusClient wrapper methods +2. **All DirectusClient calls are synchronous** — use `run_in_thread_pool()` for async endpoints +3. **`get_items` requires `{"query": {...}}` wrapper** — PRD examples miss this +4. **`create_item` returns `{"data": {...}}`** — must unwrap with `["data"]` + +--- + +## 10a. CASCADE & Hard Delete Analysis + +### PostgreSQL Triggers + +**None exist.** Zero custom PostgreSQL triggers in this codebase. The `.devcontainer/docker-compose.yml` mounts `./init.sql:/docker-entrypoint-initdb.d/init.sql` but that path is an empty directory. No Directus extensions install triggers either (`directus/extensions/` is empty). + +### PostgreSQL Constraints Beyond Directus + +**None exist.** All database schema is managed exclusively through Directus's snapshot system. While `alembic` appears in `pyproject.toml` as a dependency, no actual Alembic migration directory or `alembic.ini` exists. No `.sql` files with `ALTER TABLE...ADD CONSTRAINT` or `CREATE INDEX` exist anywhere. + +All FK constraints and their `on_delete` behaviors are defined solely in `directus/sync/snapshot/relations/`. + +### Remaining Hard DELETE Paths After Soft Delete Conversion + +When we convert to soft delete (PATCH `deleted_at = now()`), the CASCADE constraints won't fire — they only trigger on actual SQL `DELETE`. However, these paths could still perform hard DELETEs: + +#### Frontend → Directus SDK Direct (MUST reroute before removing permissions) + +| # | File | Line | What | Collection | +|---|------|------|------|------------| +| 1 | `frontend/src/components/project/hooks/index.ts` | 105 | `useDeleteProjectByIdMutation` → `deleteItem("project", projectId)` | **project** | +| 2 | `frontend/src/components/chat/hooks/index.ts` | 72 | `useDeleteChatMutation` → `deleteItem("project_chat", payload.chatId)` | **project_chat** | +| 3 | `frontend/src/components/project/hooks/index.ts` | 193 | `useDeleteTagByIdMutation` → `deleteItem("project_tag", tagId)` | **project_tag** | + +These go through the user's Directus session token and rely on Directus DELETE permissions. + +#### Python Backend (use admin token — will still work after permission removal) + +| # | File | Line | What | Collection | +|---|------|------|------|------------| +| 4 | `server/dembrane/service/project.py` | 135 | `ProjectService.delete()` — dead code, not called from any route | project | +| 5 | `server/dembrane/service/conversation.py` | 372 | `ConversationService.delete()` — called from DELETE endpoint | conversation | +| 6 | `server/dembrane/api/project.py` | 850-852 | Report delete endpoint — uses admin client | project_report | +| 7 | `server/dembrane/service/chat.py` | 299 | `ChatService.delete_message()` — error cleanup | project_chat_message | +| 8 | `server/dembrane/audio_utils.py` | 740 | Chunk split — replaces original with sub-chunks | conversation_chunk | +| 9 | `server/dembrane/api/conversation.py` | 878 | Retranscription error cleanup | conversation | + +#### Directus Admin Panel + +**Yes, Administrator role users can hard-delete anything via the Directus UI.** The Administrator policy has `admin_access: true` granting full CRUD. This cannot be prevented without Directus hooks — but admin users are trusted internal staff, so this is acceptable. + +### Current DELETE Permissions (Basic User Policy) + +All under policy `37a60e48-dd00-4867-af07-1fb22ac89078` ("Basic User Policy"), which applies to both "Basic User" and "Enterprise User" roles: + +| Collection | Permission Filter | Scope | +|------------|------------------|-------| +| `project` | `directus_user_id._eq: $CURRENT_USER` | Owner only | +| `conversation` | `project_id.directus_user_id._eq: $CURRENT_USER` | Project owner only | +| `project_chat` | `project_id.directus_user_id._eq: $CURRENT_USER` | Project owner only | +| `project_report` | **No DELETE permission exists** | Deletion uses admin token from backend | +| `conversation_artifact` | via project owner chain | Project owner only | +| `conversation_chunk` | via project owner chain | Project owner only | +| `project_tag` | via project owner chain | Project owner only | +| `project_chat_message` | via project owner chain | Project owner only | +| `project_chat_conversation` | unrestricted (null filter) | Any user | +| `project_analysis_run` | via project owner chain | Project owner only | +| `project_webhook` | via project owner chain | Project owner only | +| `view` | via project owner chain | Project owner only | +| `verification_topic` | unrestricted (null filter) | Any user | + +### Recommendation: Remove DELETE Permissions + +**Yes — remove DELETE permissions on `project`, `conversation`, `project_chat`, and `project_tag` from the Basic User Policy after soft delete conversion.** + +**Migration order (must follow this sequence):** + +1. Create Python BFF soft-delete endpoints for project, chat, tag +2. Update frontend hooks to call BFF endpoints instead of Directus SDK +3. Remove DELETE permission on `project` from Basic User Policy +4. Remove DELETE permission on `project_chat` from Basic User Policy +5. Remove DELETE permission on `conversation` from Basic User Policy (already routed through Python, but defense-in-depth) +6. **Keep** DELETE on junction tables (`project_chat_conversation`, `conversation_project_tag`) — used for legitimate detach operations, not data destruction + +**Benefits:** +- Eliminates all user-initiated hard-delete paths on core data +- Makes the CASCADE chain (`project` → 10+ tables) impossible to trigger from user operations +- Forces all deletes through controlled backend endpoints with soft-delete + usage event emission + +**Risks:** +- If any frontend code still calls `deleteItem` via user session after permission removal, it gets a 403. Must complete step 2 before step 3. +- Backend service methods use the admin client and are unaffected by permission changes. + +--- + +## 10b. Legacy `chat` Collection Status + +### Verification Results + +The legacy `chat` collection (distinct from `project_chat`) has been thoroughly investigated: + +| Check | Result | +|-------|--------| +| Seed data | No references in `seed.py` or any fixture files | +| Python SDK calls | Zero: no `get_items("chat"`, `create_item("chat"`, etc. | +| Frontend SDK calls | Zero: no `readItems("chat"`, `createItem("chat"`, etc. | +| TypeScript types | No `Chat` interface in `typesDirectus.d.ts` (only `ProjectChat`) | +| Directus permissions | No DELETE/CREATE/UPDATE permissions for `chat` collection | +| Directus flows | No flows reference `chat` collection | +| Relations | Only standard audit fields (`user_created`, `user_updated` → `directus_users`). No other collection has FK to `chat`. | +| Tests | Zero references | + +The only grep hits for `"chat"` in code are: +- `APIRouter(tags=["chat"])` — OpenAPI tag for the `project_chat` router (not the collection) +- `"chat": {` in `service/__init__.py` — exception dict key for ChatService (operates on `project_chat`) + +### Conclusion + +**Legacy `chat` collection confirmed unused. To be removed in Session 2 schema work.** + +Files to remove from sync snapshot: +- `directus/sync/snapshot/collections/chat.json` +- `directus/sync/snapshot/fields/chat/*.json` (6 files) +- `directus/sync/snapshot/relations/chat/*.json` (2 files) + +The `chat` table should also be dropped from the database in each environment after confirming zero rows via Directus admin UI. + +--- + +## 10c. Email Capability for Workspace Invites + +### Current Email Configuration + +**Directus uses SendGrid via SMTP relay** (not the SendGrid API directly). + +From `directus/.env`: +``` +EMAIL_TRANSPORT="smtp" +EMAIL_FROM="do-not-reply@dembrane.com" +EMAIL_SMTP_HOST="smtp.sendgrid.net" +EMAIL_SMTP_PORT=587 +EMAIL_SMTP_USER="apikey" +EMAIL_SMTP_PASSWORD="SG.****" +``` + +The SendGrid API key is used as an SMTP password (standard SendGrid SMTP auth pattern). + +### Existing Email Templates + +| Template | Purpose | +|----------|---------| +| `email-base.liquid` | Base layout (Dembrane logo, styling) | +| `user-invite.liquid` | Directus user invitation ("Join {{ projectName }}") | +| `user-registration.liquid` | Email verification after registration | +| `password-reset.liquid` | Password reset | +| `report-notification-en.liquid` | English report notification | +| `report-notification-nl.liquid` | Dutch report notification | + +### Can We Trigger Directus Flows from Python? + +The "Send Email" flow has `trigger: "operation"` — it can only be invoked from within another Directus Flow, NOT via `POST /flows/trigger/:flow_id` (which requires `trigger: "manual"` or `trigger: "webhook"`). + +The "Send Email Base" flow has `trigger: "manual"` but is hardcoded to send to `sameer@dembrane.com` — a test/debug flow. + +**No webhook-triggered email flow exists currently.** + +### Python Email Dependencies + +**Zero.** No `sendgrid`, `python-sendgrid`, `emails`, `aiosmtplib`, or any email package in `pyproject.toml`. No `SENDGRID_API_KEY` or `SMTP_*` env vars in server settings. + +### Directus `/utils/mail` Endpoint + +**Not used anywhere in the codebase currently**, but the `DirectusClient` has generic `.post()` that could call it. This endpoint requires admin-level token (which the server already has). + +### Recommendation: Use Directus `POST /utils/mail/send` + +**Option 1 (Recommended): Directus `/utils/mail/send` from Python** + +```python +# Using the existing admin DirectusClient +directus.post("/utils/mail/send", json={ + "to": "invitee@example.com", + "subject": "You've been invited to collaborate", + "template": { + "name": "workspace-invite", + "data": { + "url": "https://app.dembrane.com/...", + "workspaceName": "Client Project", + "inviterName": "Sameer", + } + } +}) +``` + +**What's needed:** +- Create a new `workspace-invite.liquid` template in `directus/templates/` (extend `email-base.liquid`) +- No new Python dependencies +- No new environment variables +- Reuses existing SendGrid SMTP relay +- Works with the existing admin `DirectusClient` singleton + +**Other options considered but NOT recommended:** + +| Option | Why Not | +|--------|---------| +| Add `sendgrid` Python SDK | New dependency, duplicate transport config, need new env vars | +| Create webhook-triggered Directus Flow | More complex, constrained payload format, harder to maintain | +| Use `smtplib` directly | Reinventing the wheel, need SMTP config in Python | + +--- + +*End of Codebase Exploration Report — Session 1* diff --git a/echo/docs/workspaces/execution-plan-final.md b/echo/docs/workspaces/execution-plan-final.md new file mode 100644 index 00000000..728f2c10 --- /dev/null +++ b/echo/docs/workspaces/execution-plan-final.md @@ -0,0 +1,593 @@ +# Execution Plan: Workspaces Implementation +## Refined for Solo Developer + Claude Code + +--- + +## Engineering Principles + +### What we follow + +**Strangler Fig Pattern** — Don't rewrite. Wrap. The workspace layer grows around existing functionality. At no point does the existing system stop working. + +**Expand and Contract** — First, add new fields and tables (expand). Then, migrate data. Then, update code to use new paths. Finally, deprecate old paths (contract). Never do these in the same commit. + +**Make the Change Easy, Then Make the Easy Change** (Kent Beck) — If a change is hard, first refactor to make it easy (that's a separate commit), THEN make the change. + +**One Commit, One Concern** — Each commit does exactly one thing. "Add deleted_at field to conversation" is one commit. "Convert conversation delete to soft delete" is another commit. Never both. + +**Reversibility** — Every commit should be revertible without data loss. Schema additions (new fields, new tables) are safe. Schema modifications (changing types, renaming) are risky. Data mutations (migration) need a backup. + +**The Campsite Rule** — Leave the code better than you found it, but don't refactor unrelated things in a workspace commit. Stay focused. + +### What we DON'T do + +**No Big Bang.** We don't build everything and then flip a switch. Each commit is deployable. + +**No Premature Abstraction.** Don't build a "generic soft delete framework." Just convert each delete one at a time. The pattern will emerge. + +**No Future-Proofing Beyond One Step.** `app_user` is one step of future-proofing (we know auth migration is coming). Don't add fields "just in case." Add them when needed. + +**No Gold Plating.** The usage dashboard doesn't need charts in the first pass. A JSON response from the API is enough. The UI comes later. + +**No Mixing Concerns.** A session about schema doesn't touch Python routes. A session about API doesn't touch frontend. Separation is discipline. + +--- + +## Session Plan + +Four sessions. Each produces commits on the `workspaces` branch. + +``` +Session 1: EXPLORE (no code changes) + ↓ +Session 2: SCHEMA (Directus collections + soft delete fields) + ↓ +Session 3: SOFT DELETE CONVERSION (reroute deletes through Python) + ↓ +Session 4: CORE API (workspace/org endpoints + migration script) +``` + +Frontend is a separate effort AFTER backend is solid. Not covered here. + +--- + +## Session 1: EXPLORE + +**Goal:** Understand the codebase. Reconcile PRD with reality. Produce updated PRD. +**Output:** `codebase-exploration-report.md` + `workspaces-prd-v4.md` (reconciled) +**Commits:** None. Read-only session. +**Duration estimate:** 1 session + +### What the agent does + +1. Map every Directus collection (especially `directus_users` — ALL custom fields) +2. Map every Python API route (method, path, what it does, auth pattern) +3. Map every frontend route (for awareness, not modification) +4. Inventory every delete operation in the codebase +5. Map the auth flow (cookie → Directus → Python) +6. Map the Directus client/wrapper used by Python +7. Check: what fields does `directus_users` have that `app_user` is missing? +8. Check: what Directus roles exist and what permissions are configured? +9. Check: does the project collection already have any sharing/team fields? +10. Check: is there an existing `deleted_at` or archive pattern anywhere? + +Then reconcile with PRD and produce updated version. + +### Prompt for Claude Code + +``` +Read the entire codebase. Do not make any changes. + +I'm planning a major feature: adding Organizations and Workspaces to this platform. +Before I start, I need to understand exactly what exists. + +Produce a report (`codebase-exploration-report.md`) covering: + +1. DIRECTUS SCHEMA + For every collection in the Directus schema (check the schema sync files + or query the Directus API): + - Collection name, all fields with types + - Relations to other collections + - Pay SPECIAL attention to directus_users — list EVERY field including custom ones + - Pay SPECIAL attention to project — list EVERY field + +2. PYTHON API ROUTES + For every route in the FastAPI app: + - Method + path + what it does (1 line) + - How it authenticates (admin token vs user cookie) + - What Directus collections it reads/writes + - Does it delete anything? If so, what collection and how? + +3. DELETE INVENTORY + Every place data is deleted, anywhere in the codebase: + - File path + line number (or function name) + - What collection + - Frontend→Directus direct, Frontend→Python, or Python→Directus? + - What metadata would be lost if this row is deleted? + (e.g., conversation delete loses duration_seconds) + +4. AUTH FLOW + - How does login work end-to-end? + - What does the Python API's "get current user" function look like? + - What user fields does it return? + - How is the Directus client configured in Python? (wrapper class? raw fetch?) + +5. DIRECTUS CLIENT PATTERN + - How does Python call Directus? Show the actual code pattern. + - Is there a wrapper/helper class? + - How are filters constructed? + - How is error handling done? + +6. EXISTING PATTERNS + - Any existing multi-user, sharing, or team concepts? + - Any existing soft delete or archive patterns? + - How is the Directus schema synced (show the config/setup)? + +After producing the report, read the attached PRD (workspaces-prd-v3-final.md) and +produce a reconciliation section at the end of the report noting: + - Fields missing from app_user that directus_users has + - Any collection names that don't match PRD assumptions + - Any API patterns that don't match PRD assumptions + - Any conflicts or surprises +``` + +**Attach:** `workspaces-prd-v3-final.md` + +### Success criteria +- [ ] Can answer: "What exact fields does directus_users have?" +- [ ] Can answer: "What are all the delete operations and what metadata do they lose?" +- [ ] Can answer: "What's the exact pattern for calling Directus from Python?" +- [ ] PRD discrepancies identified with specific corrections + +--- + +## Session 2: SCHEMA + +**Goal:** Create all new Directus collections. Add soft delete fields to existing collections. +**Output:** Series of commits, each adding one collection or modifying one existing collection. +**Duration estimate:** 1 session + +### Commit sequence + +``` +commit 1: "chore: create app_user collection" + - Create app_user in Directus with ALL fields from reconciliation + - Include directus_user_id as unique FK + - Sync schema via directus-extension-sync + +commit 2: "chore: create org and org_membership collections" + - org: id, name, slug, logo_url, created_by→app_user, deleted_at, timestamps + - org_membership: id, org_id→org, user_id→app_user, role, deleted_at, timestamps + - Sync schema + +commit 3: "chore: create workspace and workspace_membership collections" + - workspace: id, org_id→org, name, slug, tier, billed_to→workspace, + is_default, legal_basis, privacy_policy_url, logo_url, settings, + deleted_at, created_by→app_user, timestamps + - workspace_membership: id, workspace_id→workspace, user_id→app_user, + role, source, is_external, deleted_at, timestamps + - Sync schema + +commit 4: "chore: create workspace_invite and project_user collections" + - workspace_invite: id, workspace_id→workspace, email, role, + invited_by→app_user, token, expires_at, accepted_at, timestamps + - project_user: id, project_id→project, user_id→app_user, role, + granted_by→app_user, timestamps + - Sync schema + +commit 5: "chore: create usage_event collection" + - id, trace_id, org_id, workspace_id, project_id, user_id, + event_type, event_data (json), created_at + - No deleted_at (append-only, never deleted) + - Sync schema + +commit 6: "chore: add workspace_id and visibility fields to project" + - workspace_id: M2O → workspace, nullable + - visibility: string, default 'workspace' + - Sync schema + +commit 7: "chore: add deleted_at to existing collections" + - Add deleted_at (timestamp, nullable) to: + conversation, project, chat (whatever it's called), report + - Sync schema +``` + +### Prompt for Claude Code + +``` +We're on branch `workspaces`. Make the following changes as SEPARATE COMMITS. +Each commit should ONLY contain schema changes synced via directus-extension-sync. +Do NOT modify any Python code or frontend code in this session. + +Use the Directus admin API or whatever method this project uses to create collections +(check the codebase exploration report for the exact pattern). + +After each collection is created, sync the schema using the project's +directus-extension-sync setup. + +[Include commit sequence from above] + +Use the codebase exploration report to: +- Match the exact Directus field types used in this project +- Match the exact relation configuration pattern +- Match the schema sync workflow +- Include ALL app_user fields identified in the reconciliation + (not just the ones in the original PRD) + +IMPORTANT: This session is schema-only. No Python code. No frontend code. +No migration logic. Just Directus collections and schema sync. +``` + +**Attach:** `codebase-exploration-report.md` + `workspaces-prd-v4.md` + +### Success criteria +- [ ] All 7 commits on branch +- [ ] Schema synced after each commit +- [ ] Can CRUD each new collection via Directus API +- [ ] project.workspace_id exists and is nullable +- [ ] deleted_at exists on all required collections + +--- + +## Session 3: SOFT DELETE CONVERSION + +**Goal:** Convert all delete operations to soft deletes. Route everything through Python. +**Output:** Series of commits, one per delete operation converted. +**Duration estimate:** 1 session + +### Commit sequence (order determined by exploration report) + +``` +commit 1: "feat: add emit_usage_event utility" + - Create utility function in Python API + - Fire-and-forget, never fails parent operation + - Posts to usage_event collection via Directus API + - Always includes "v": 1 in event_data + +commit 2: "refactor: convert conversation delete to soft delete" + - Create/update Python endpoint: DELETE /api/v1/conversations/:id + - Soft delete: PATCH deleted_at via Directus API + - Emit usage event: conversation.deleted with duration_seconds snapshot + - Update frontend to call Python API (not Directus direct) + - Purge audio file if applicable + +commit 3: "refactor: convert project delete to soft delete" + - Same pattern + - Emit: project.deleted with conversation_count, total_audio_hours + +commit 4: "refactor: convert chat delete to soft delete" + - Same pattern + - Emit: chat.deleted + +commit 5: "refactor: convert report delete to soft delete" + - Same pattern + - Emit: report.deleted + +commit N: (one commit per remaining delete operation found in exploration) + +commit N+1: "refactor: add deleted_at filter to all read queries" + - Audit every Directus read call in Python API + - Add filter: { "deleted_at": { "_null": true } } + - Audit every Directus read call in frontend (if any) + - Add same filter + +commit N+2: "test: verify soft deletes work end-to-end" + - Test each delete: item disappears from reads, usage event emitted + - Document in commit message what was tested +``` + +### Prompt for Claude Code + +``` +We're on branch `workspaces`, continuing from the schema commits. + +TASK: Convert all delete operations in the codebase to soft deletes. + +STEP 1: Create the emit_usage_event utility (see PRD for implementation). + Put it wherever utility functions live in this codebase. + +STEP 2: For each delete operation listed in the codebase exploration report, + create a SEPARATE COMMIT that: + a) Creates or updates a Python API endpoint for the delete + b) Changes it from hard DELETE to PATCH { deleted_at: now() } + c) Emits a usage_event with metadata snapshot BEFORE soft deleting + (so we capture duration_seconds, counts, etc.) + d) Updates the frontend to call the Python endpoint + (if it was previously calling Directus directly) + e) Handles any binary file cleanup (audio files can be hard-deleted) + +STEP 3: Create ONE commit that adds { "deleted_at": { "_null": true } } filter + to ALL existing read queries across the codebase. + +METADATA TO SNAPSHOT (per collection): +- conversation: duration_seconds, audio_hours (duration/3600), project_id +- project: conversation_count, total_audio_hours (sum of conversations) +- chat: message_count, project_id +- report: project_id + +PATTERN: + async def delete_conversation(conversation_id, current_user): + conv = await directus.get(f"/items/conversation/{conversation_id}") + project = await directus.get(f"/items/project/{conv['project_id']}") + + await emit_usage_event( + "conversation.deleted", + {"v": 1, "duration_seconds": conv.get("duration_seconds", 0), ...}, + workspace_id=project.get("workspace_id"), + user_id=current_user.id, + ) + + await directus.patch(f"/items/conversation/{conversation_id}", { + "deleted_at": datetime.utcnow().isoformat() + }) + +Follow the EXACT Directus client pattern from this codebase. +Each commit = one delete operation converted. Atomic and reviewable. +``` + +**Attach:** `codebase-exploration-report.md` + `workspaces-prd-v4.md` + +### Success criteria +- [ ] Every delete operation is soft +- [ ] Every soft delete emits a usage event with billing metadata +- [ ] All read queries filter deleted_at IS NULL +- [ ] No frontend→Directus direct deletes remain +- [ ] Deleted items don't appear in any UI + +--- + +## Session 4: CORE API + MIGRATION + +**Goal:** Implement workspace/org API endpoints and the migration script. +**Output:** Series of commits building up the API surface. +**Duration estimate:** 1-2 sessions (this is the biggest one) + +### Commit sequence + +``` +FOUNDATION: +commit 1: "feat: add get_workspace_context middleware" + - FastAPI dependency that validates workspace access + - Returns WorkspaceContext with workspace_id, user, role + - 403 if no access + +commit 2: "feat: add permission resolution helpers" + - get_user_project_access() + - get_user_accessible_workspaces() + - Uses Directus API calls, returns resolved access objects + +WORKSPACE CRUD: +commit 3: "feat: GET /api/v1/workspaces — list accessible workspaces" + - Used by workspace selector + - Returns workspaces with role, source, counts, is_external + +commit 4: "feat: POST /api/v1/workspaces — create workspace" + - Creates workspace in user's org + - Auto-adds creator as owner + - Auto-adds org admins as inherited members + - Emits workspace.created + workspace.member_added events + +commit 5: "feat: GET/PATCH/DELETE workspace endpoints" + - Detail, update, soft delete + - Delete blocked if workspace has projects + +MEMBERSHIP: +commit 6: "feat: workspace membership CRUD" + - List (includes inherited org admins in separate section) + - Invite by email (existing user → immediate, new user → invite) + - Change role + - Remove (soft delete) + - Emits member_added / member_removed events + +commit 7: "feat: workspace invite flow" + - Create invite with secure token + - Send email via SendGrid + - Accept endpoint (validates token, creates membership) + - Post-registration hook: check for pending invites + +ORG: +commit 8: "feat: org CRUD + membership endpoints" + - List user's orgs + - Update org (name, logo) + - Org member management + - Role changes propagate inherited workspace memberships + +USAGE: +commit 9: "feat: usage summary endpoint" + - GET /api/v1/workspaces/:id/usage + - Aggregates from usage_event table + - Per-project breakdown + +commit 10: "feat: org billing rollup endpoint" + - GET /api/v1/orgs/:id/billing + - Sums across all org workspaces + +MIGRATION: +commit 11: "feat: migration script — create orgs and workspaces for existing users" + - Creates app_user for each directus_user + - Creates org + workspace + memberships + - Moves projects into default workspace + - Dry-run mode, idempotent, per-user error handling + - Progress logging + +commit 12: "feat: post-registration hook — auto-create org + workspace" + - New users get org + workspace on signup + - Called from Directus hook or Python endpoint after user creation + +PROJECT SHARING: +commit 13: "feat: project sharing endpoints (private projects)" + - project_user CRUD + - Tier-gated: innovator+ + +ADMIN: +commit 14: "feat: admin usage endpoint + tier management" + - Cross-org usage for manual invoicing + - Set workspace tier manually +``` + +### Prompt for Claude Code + +``` +We're on branch `workspaces`, continuing from soft delete commits. + +TASK: Implement the workspace and org API endpoints. One commit per endpoint group. + +CRITICAL RULES: +1. Follow the EXACT Directus client pattern from this codebase + (use the same wrapper/helper class for all Directus API calls) +2. Follow the EXACT auth pattern from this codebase + (match how existing endpoints get the current user) +3. Every workspace-scoped endpoint MUST use get_workspace_context dependency +4. Every mutation MUST emit a usage_event +5. Every read MUST filter deleted_at IS NULL +6. Match the existing code style (naming conventions, file organization, etc.) + +API RESPONSE SHAPES: See PRD for exact response JSON structures. + +PERMISSION MODEL: +- Org owner/admin → admin access to all org workspaces (via inherited membership rows) +- Workspace owner/admin/member/viewer → see workspace role policies in PRD +- Private projects → only creator + workspace admin + project_user entries + +MIGRATION SCRIPT: +- Must have --dry-run flag +- Must be idempotent (safe to re-run) +- Must handle per-user errors without stopping +- Must log progress +- Must create app_user with ALL fields from directus_users +- Test on local devcontainer before production + +Start with commits 1-2 (middleware + permission helpers) as they're used by everything else. +``` + +**Attach:** `codebase-exploration-report.md` + `workspaces-prd-v4.md` + +### Success criteria +- [ ] All endpoints return correct response shapes +- [ ] Permission checks work for all role combinations +- [ ] Invite flow works end-to-end (create → email → accept) +- [ ] Migration script works in dry-run mode +- [ ] Migration script works on local devcontainer +- [ ] Usage events emitted for all mutations +- [ ] Org inheritance creates/removes workspace memberships correctly + +--- + +## Anti-Patterns to Watch For + +### During Claude Code sessions + +| Anti-pattern | What it looks like | What to do instead | +|---|---|---| +| **Boiling the ocean** | Agent tries to do everything in one commit | Stop it. One commit, one concern. | +| **Inventing patterns** | Agent creates a new utility/framework not in the codebase | Ask: "How does the existing code do this?" | +| **Ignoring existing code** | Agent writes a fresh Directus client wrapper | Use the existing one. | +| **Optimistic testing** | "I've implemented the endpoints" but didn't test | Ask: "Call the endpoint. Show me the response." | +| **Silent failures** | emit_usage_event throws but nobody notices | Check: is there error logging? | +| **Schema drift** | Agent creates fields via API but doesn't sync schema | Every schema change → directus-extension-sync | +| **God commits** | One commit with 20 files changed | Break it up. | + +### During your review + +| Red flag | What it means | +|---|---| +| Agent modified files outside the feature scope | Scope creep. Revert. | +| Agent introduced a new dependency (pip/npm package) | Question it. Is it necessary? | +| Agent used raw SQL instead of Directus API | Wrong pattern. Rewrite. | +| Commit message is vague ("update files") | Rewrite the commit message. | +| No deleted_at filter on a new read query | Bug. Fix before merging. | +| Usage event has no "v" field in event_data | Fix. This will save you later. | + +--- + +## Pre-Flight Checklist (Before Session 1) + +- [ ] Create `workspaces` branch from main +- [ ] Verify local devcontainer is running and healthy +- [ ] Verify Directus admin access works locally +- [ ] Verify Python API starts and serves requests locally +- [ ] Verify directus-extension-sync works (pull current schema) +- [ ] Take a snapshot/backup of the local DB (for safe migration testing) +- [ ] Have the PRD v3 final ready to attach to Claude Code +- [ ] Have this execution plan ready to reference + +--- + +## Post-Session Verification (After Each Session) + +### After Session 2 (Schema) +```bash +# Verify collections exist +curl http://localhost:8055/items/app_user # Should return empty array +curl http://localhost:8055/items/org # Should return empty array +curl http://localhost:8055/items/workspace # Should return empty array +curl http://localhost:8055/items/usage_event # Should return empty array + +# Verify project has new fields +curl http://localhost:8055/items/project?limit=1&fields=id,workspace_id,visibility + +# Verify deleted_at on existing collections +curl http://localhost:8055/items/conversation?limit=1&fields=id,deleted_at +``` + +### After Session 3 (Soft Delete) +```bash +# Create a test conversation, then delete it +# Verify: conversation has deleted_at set (not hard deleted) +# Verify: usage_event created with duration_seconds +# Verify: conversation doesn't appear in normal queries +``` + +### After Session 4 (API) +```bash +# Run migration in dry-run +python migration.py --dry-run + +# Run migration for real (on local DB) +python migration.py + +# Verify: every user has app_user + org + workspace +curl http://localhost:8000/api/v1/workspaces \ + -H "Cookie: " +# Should return workspaces + +# Create a new workspace +curl -X POST http://localhost:8000/api/v1/workspaces \ + -H "Cookie: " \ + -H "Content-Type: application/json" \ + -d '{"name": "Test Client"}' +# Should return workspace with inherited memberships +``` + +--- + +## The Moment of Truth: Session 1 Prompt + +When you're ready, open Claude Code with the repo loaded and paste this: + +``` +I'm implementing a Workspaces & Organizations feature for this platform. + +FIRST: Read the attached PRD (workspaces-prd-v3-final.md) to understand what we're building. + +THEN: Explore this codebase thoroughly and produce a report (save as +docs/codebase-exploration-report.md) that maps: + +1. Every Directus collection with ALL fields (especially directus_users and project) +2. Every Python API route with auth pattern and Directus calls +3. Every delete operation (file, collection, method, what metadata is lost) +4. The exact auth flow and Directus client pattern +5. The schema sync setup (directus-extension-sync config) + +FINALLY: Compare the codebase against the PRD and note: +- What fields does directus_users have that PRD's app_user is missing? +- What collection names or patterns in the PRD don't match reality? +- Any existing concepts that overlap with workspaces (sharing, teams, etc.)? +- What's the exact code pattern for calling Directus from Python? + +Save the reconciliation notes at the end of the report. + +DO NOT make any code changes. This is a read-only exploration session. +``` + +Attach: `workspaces-prd-v3-final.md` diff --git a/echo/docs/workspaces/failure-analysis.md b/echo/docs/workspaces/failure-analysis.md new file mode 100644 index 00000000..9fb4ea36 --- /dev/null +++ b/echo/docs/workspaces/failure-analysis.md @@ -0,0 +1,391 @@ +# Workspaces PRD — Multi-Perspective Failure Analysis +## "Red Team" Review from 8 Specialist Perspectives + +--- + +## Agent 1: Security Engineer + +### How this fails + +**Privilege escalation via org inheritance.** The "org owner/admin gets admin on all workspaces" rule is computed at query time. If someone compromises an org owner account, they instantly have admin access to every client workspace. That's not one customer breached — it's ALL their clients breached simultaneously. For a consultancy with 20 client workspaces, one phished password = 20 data breaches. + +**Mitigation:** Add an explicit "require re-authentication for workspace switching" option that high-security clients can enable. At minimum, log workspace switches as security events with IP/device fingerprint. Consider making org-inherited access opt-out per workspace (client workspace admin can say "I don't want org admins auto-inheriting into my workspace"). + +**Invite token brute force.** Workspace invite tokens are the keys to the kingdom. If they're UUIDs (36 chars, 122 bits of entropy), they're fine. If they're short tokens for "nice URLs," they're brute-forceable. + +**Mitigation:** Use `secrets.token_urlsafe(32)` (256 bits). Rate-limit invite acceptance endpoint. Expire aggressively (7 days, single-use). + +**Cross-tenant data leakage via usage_event.** The usage_event table contains org_id, workspace_id, project_id, user_id. If the admin usage endpoint (`/api/v1/admin/usage`) has an authz bug, it exposes the entire activity graph of all customers. An attacker learns: which orgs exist, how many projects they have, when they're active, who's in which workspace. + +**Mitigation:** The admin endpoints should require a separate authentication mechanism (not just "is Directus admin"). Consider a separate admin API key or require 2FA verification for admin endpoints. + +**Exported usage data as intelligence.** Even legitimate access to usage data reveals competitive intelligence. If a partner can see their client's exact chat query counts, they can infer how dependent the client is on Dembrane (leverage in handoff negotiations). + +**Mitigation:** Decide what usage data partners should vs. shouldn't see about client workspaces they manage. The current design shows everything. + +### Recommendations +- [ ] Invite tokens: `secrets.token_urlsafe(32)`, single-use, 7-day expiry +- [ ] Log all workspace switches as security events +- [ ] Rate-limit invite acceptance: 5 attempts per token per hour +- [ ] Admin endpoints: separate auth mechanism or 2FA gate +- [ ] Consider per-workspace "disable org inheritance" flag for high-security clients +- [ ] Sanitize usage data visible to partners (aggregate, not per-user) + +--- + +## Agent 2: Data Engineer / DBA + +### How this fails + +**The soft delete gap (CTO's insight).** When a conversation is deleted, its audio duration metadata disappears. But billing needs that data. "You used 25 hours this month" becomes "you used 18 hours" because someone deleted 7 hours of conversations mid-month. The customer disputes the invoice. You have no proof. + +This extends beyond conversations. If a project is deleted, all its conversations vanish from usage calculations. If a workspace member is removed, you lose the "they were a billable seat for 15 days" data. + +**The fix: Universal soft delete with metadata preservation.** + +Every entity that affects billing needs a soft delete that preserves billing-relevant metadata: + +``` +conversation.deleted_at → keep: duration_seconds, audio_hours, created_at +project.deleted_at → keep: workspace_id, conversation_count_at_deletion +workspace_membership.deleted_at → keep: user_id, role, created_at (for seat-days calc) +workspace.deleted_at → keep: tier, org_id, member_count_at_deletion +``` + +Implementation pattern — every "delete" route must: +1. Route through Python API (not Directus direct) +2. Set `deleted_at = now()` instead of `DELETE` +3. Emit a usage_event with metadata snapshot +4. All SELECT queries add `WHERE deleted_at IS NULL` + +**Subagent task for implementation:** Audit every existing DELETE endpoint (Directus + Python), reroute through Python, convert to soft delete, update all queries. + +**The query performance cliff.** Permission resolution joins 4 tables. The workspace selector joins workspace + org_membership + workspace_membership + counts. These are fine at 10 workspaces but degrade at 100+ (which a large partner will have). + +**The fix:** +- Materialized view for "user accessible workspaces" refreshed on membership changes +- Or: denormalized `workspace_summary` table updated via triggers/events +- Index strategy: composite indexes on the JOIN paths + +```sql +-- Critical indexes for permission resolution +CREATE INDEX idx_org_membership_user_role ON org_membership(user_id, role); +CREATE INDEX idx_ws_membership_user ON workspace_membership(user_id) INCLUDE (workspace_id, role); +CREATE INDEX idx_project_workspace_visibility ON project(workspace_id, visibility) WHERE deleted_at IS NULL; +CREATE INDEX idx_project_user_project ON project_user(project_id, user_id); +``` + +**Migration atomicity.** The migration script creates org + workspace + membership + moves projects for EVERY existing user in one go. If it fails halfway, you have orphaned data. + +**The fix:** +- Wrap per-user migration in its own transaction (not one giant transaction) +- Add idempotency check (`IF EXISTS org_membership for this user, SKIP`) +- Run in batches of 50 with progress logging +- Dry-run mode that reports what it WOULD do + +### Recommendations +- [ ] Soft delete on: conversation, project, workspace, workspace_membership, org_membership +- [ ] Emit metadata snapshot in usage_event on every soft delete +- [ ] All deletes routed through Python API (no direct Directus deletes) +- [ ] Add `WHERE deleted_at IS NULL` to all existing queries (tracked as implementation subtask) +- [ ] Composite indexes for permission resolution +- [ ] Migration: per-user transactions, idempotent, batched, dry-run mode + +--- + +## Agent 3: Frontend Engineer + +### How this fails + +**Workspace context is ambient state.** Once a user selects a workspace, every API call needs `workspace_id`. Where does this live? If it's in the URL (slug), the frontend has to slug→ID resolve on every route transition. If it's in React context/store, a page refresh loses it. If it's in localStorage, it can desync with the URL. + +**The fix:** Single source of truth: +- URL slug is the authority: `/:locale/:workspaceSlug/...` +- On route mount, resolve slug→workspace (cache aggressively) +- Store resolved workspace in React context (but derive from URL, don't sync bidirectionally) +- If slug resolution fails (404), redirect to workspace selector +- localStorage stores `lastWorkspaceSlug` for the post-login redirect ONLY + +```typescript +// WorkspaceProvider pattern +function WorkspaceProvider({ children }: PropsWithChildren) { + const { workspaceSlug } = useParams(); + const { data: workspace, error } = useQuery( + ['workspace', workspaceSlug], + () => api.getWorkspaceBySlug(workspaceSlug), + { staleTime: 60_000 } // Cache for 60s + ); + + if (error?.status === 404) return ; + if (!workspace) return ; + + return ( + + {children} + + ); +} +``` + +**Stale workspace list.** User is on workspace selector. Another admin adds them to a new workspace. They don't see it until they refresh. Worse: they get removed from a workspace, navigate to it from a stale selector, and get a 403. + +**The fix:** +- Poll workspace list every 30s on the selector page +- On 403 from any workspace API call, invalidate workspace cache and redirect to selector with a toast: "You no longer have access to this workspace" + +**Deep linking breaks.** User shares `app.dembrane.com/en/dietz/projects/abc123` with a colleague. Colleague clicks it. They're not logged in. After login, the post-login router sends them to the workspace selector instead of the deep link. + +**The fix:** Store the intended URL pre-login, restore after authentication: + +```typescript +// Before redirect to login +sessionStorage.setItem('redirectAfterLogin', window.location.pathname); + +// After login +const redirect = sessionStorage.getItem('redirectAfterLogin'); +if (redirect) { + sessionStorage.removeItem('redirectAfterLogin'); + navigate(redirect); +} else { + navigate(postLoginRoute); +} +``` + +**Mobile: workspace selector on small screens.** The card view works on tablet but is cramped on phone. The list view is fine on phone but wastes space on desktop. + +**The fix:** Card view only on tablet+, always list view on mobile. Don't try to make cards responsive — just switch the component. + +### Recommendations +- [ ] WorkspaceProvider derives from URL, caches in context, never syncs bidirectionally +- [ ] Slug→ID resolution with aggressive caching (60s staleTime) +- [ ] 403 handling: invalidate cache, redirect to selector, toast +- [ ] Deep link preservation through login flow (sessionStorage) +- [ ] Mobile: list view only, card view tablet+ +- [ ] Polling on selector page (30s interval) + +--- + +## Agent 4: Product Manager (Customer Success) + +### How this fails + +**The "Default workspace" is confusing.** Every user gets a workspace called "Default" in an org called "{Name}'s Organization." For solo facilitators who never need multi-user features, this is mystery meat. "Why am I in an organization? I'm a freelancer." "What's a workspace? I just want my projects." + +**The fix:** +- For single-workspace users: HIDE all workspace/org language. Don't show "Default" anywhere. The experience should be identical to today — just "Projects." +- Only surface workspace concepts when the second workspace is created or when they're invited to someone else's workspace. +- The org name should be editable during onboarding (not auto-generated as "Sameer's Organization"). +- Consider: don't auto-name it at all. When the user first needs to see the org name (e.g., inviting someone), prompt them to name it. + +**Partner can't explain it to clients.** This is the imagination problem from the strategy doc, now in the product itself. A partner creates a workspace for Client 1 and invites C1X. C1X gets an email: "You've been invited to a workspace on Dembrane." C1X has never heard of Dembrane. They don't know what a workspace is. They bounce. + +**The fix:** +- Invite email must be customizable by the partner (at minimum: partner logo, partner name, custom message) +- The invite landing page should show: who invited you, what org, and a one-sentence explanation +- Consider: "You've been invited by [Partner Name] to collaborate on [Workspace Name]" +- Partner should be able to preview the invite email before sending + +**Tier confusion during workspace creation.** Partner creates a new workspace for a client. What tier is it? The mockdown shows a tier selection step, but the pricing page isn't in-app. The partner has to remember "Pioneer is €200, Innovator is €500..." + +**The fix:** +- Show tier comparison inline during workspace creation (not just a dropdown) +- Default to the partner's own workspace tier (reasonable guess) +- Show estimated monthly cost based on selection +- "Contact us" for Guardian tier (don't make it self-serve) + +**No onboarding for the workspace concept.** Existing users who've been using Dembrane for months suddenly see a workspace selector after the migration. "What changed? Did I lose my data?" + +**The fix:** +- First login after migration: show a one-time explainer modal +- "We've upgraded your account! Your projects are right where you left them, now inside your workspace. Here's what's new: [collaborate with team members, manage client projects, ...]" +- Include a "Learn more" link to documentation +- Make it dismissible but not skippable on first appearance + +### Recommendations +- [ ] Hide workspace/org language for single-workspace users +- [ ] Customizable invite emails (partner logo, custom message) +- [ ] Tier comparison card during workspace creation +- [ ] Post-migration onboarding modal for existing users +- [ ] Lazy naming: don't force org name on signup, prompt when needed + +--- + +## Agent 5: DevOps / SRE + +### How this fails + +**Migration script runs against production DB with no rollback.** The migration creates orgs and workspaces for every user and updates every project. If it corrupts data, there's no undo. + +**The fix:** +- Take a full DB snapshot before migration +- Run migration on a clone first, verify +- Migration script has a `--dry-run` flag that logs what it would do +- Migration is idempotent (can be re-run safely) +- Each user migration is its own transaction +- Progress reporting: "Migrated 450/2000 users, 3200/8500 projects" + +**No health checks for the new workspace layer.** The Python API now has critical new functionality. If the workspace endpoints go down, users can't log in (post-login router calls `/api/v1/workspaces`). But there's no specific health check for workspace functionality. + +**The fix:** +- `/api/v1/health` checks: DB connectivity, workspace table exists, can query workspace_membership +- Post-login router: if workspace endpoint fails, fall back to legacy project list (graceful degradation) +- Alert on workspace endpoint error rate > 1% + +**Alembic migration + Directus in same DB = collision risk.** Alembic manages workspace tables. Directus manages its own tables. If Directus runs a migration that affects shared resources (indexes, sequences, roles), it could conflict with Alembic's state. + +**The fix:** +- Use separate schemas: `public` for Directus, `app` for workspace tables +- Or: prefix all workspace tables with `app_` to avoid any naming collision +- Document: "Never modify app_* tables through Directus admin panel" + +### Recommendations +- [ ] DB snapshot before migration, clone-test first +- [ ] Migration: dry-run, idempotent, per-user transactions, progress logging +- [ ] Health check endpoint covering workspace layer +- [ ] Graceful degradation if workspace API is down +- [ ] Schema separation or table prefixing to avoid Directus collision + +--- + +## Agent 6: Business / Pricing Strategist + +### How this fails + +**Seat counting double-charges partners.** PX (org owner) is a billable seat in WS A, WS B, and WS C. If each workspace is on Pioneer (3 seats), PX consumes one seat in each. The partner is paying for PX three times. At €25/extra seat, that's €75/month for one person existing in multiple workspaces. + +This is the correct business model if each workspace is a separate client engagement (they're getting value in each). But it FEELS wrong to the partner. "I'm paying for myself three times?" + +**The fix (product, not pricing):** +- Be explicit about this in the UI: "Members who belong to multiple workspaces count as a seat in each" +- Consider: first N workspaces include the org admin as a "free seat" (avoids the psychological sting) +- Or: partner-tier pricing that includes "unlimited org admin seats across your workspaces" +- At minimum: show a clear breakdown so there are no surprises on the invoice + +**Audio hours are consumed even on failed transcriptions.** User uploads 2 hours of audio, transcription partially fails (bad audio quality, language mismatch). They re-upload. Now they've "used" 4 hours. With Pioneer at 25h/month and €5/extra hour, that's a €10 charge for a system failure. + +**The fix:** +- Only count successfully transcribed audio toward usage +- Failed/partial transcriptions should be flagged in usage events: `{ "status": "failed", "billable": false }` +- Show "billable hours" vs "total hours" in usage dashboard +- Allow admin to mark specific events as non-billable (for support resolution) + +**No trial/free tier means self-service friction.** The cheapest option is Pilot at €349 (one-time) or Pioneer at €200/month. There's no way for someone to try Dembrane without committing €200+. The strategy doc says "you need to experience it to imagine what it can do" — but the pricing prevents experiencing. + +**Consideration:** This is a deliberate filter for quality clients. But the workspace architecture should support a `free` or `trial` tier if you ever decide to add one. The `tier` field is a string so this is fine architecturally. Just note it. + +### Recommendations +- [ ] Explicit UI explanation of per-workspace seat counting for multi-workspace users +- [ ] Only count successfully processed audio as billable hours +- [ ] `billable: bool` field on relevant usage events +- [ ] Support marking events as non-billable (admin/support tool) +- [ ] Ensure tier field supports future `free`/`trial` values + +--- + +## Agent 7: Legal / Compliance + +### How this fails + +**Data residency is workspace-level, not just platform-level.** Dembrane is EU-hosted (good). But different clients may have different data residency requirements. A Dutch municipality may require data to stay in NL. A German client may require DE. Currently, all workspaces share the same infrastructure. + +**For now:** This is fine — all EU-hosted. But the workspace model should have a `region` field for future multi-region support. Don't build it, just don't design it out. + +**Soft delete retention periods.** GDPR gives users the right to erasure. Soft delete with 30-day retention is fine for business data, but if a user requests account deletion, all their personal data must be purged within 30 days (not just soft-deleted). + +**The fix:** +- Soft delete for billing/audit purposes: preserve metadata, strip PII +- User deletion (GDPR): hard delete PII, keep anonymized usage events +- Two different paths: "delete conversation" (soft, keep metadata) vs "delete my account" (hard, GDPR) + +```python +# Conversation soft delete (billing-safe) +async def soft_delete_conversation(conversation_id: str): + # Snapshot billing metadata before soft delete + await emit_usage_event("conversation.deleted", { + "duration_seconds": conversation.duration_seconds, + "audio_hours": conversation.audio_hours, + "project_id": str(conversation.project_id), + }) + await db.execute( + "UPDATE conversation SET deleted_at = now() WHERE id = :id", + {"id": conversation_id} + ) + # Audio file can be purged immediately (not needed for billing) + await delete_audio_file(conversation.audio_path) + +# GDPR account deletion (privacy-safe) +async def gdpr_delete_user(user_id: str): + # Anonymize usage events (keep for billing, strip PII) + await db.execute( + "UPDATE usage_event SET user_id = NULL, " + "event_data = event_data - 'participant_name' - 'email' " + "WHERE user_id = :uid", + {"uid": user_id} + ) + # Hard delete PII-containing records + await db.execute("DELETE FROM workspace_invite WHERE email IN (SELECT email FROM directus_users WHERE id = :uid)", {"uid": user_id}) + # Remove memberships + await db.execute("DELETE FROM workspace_membership WHERE user_id = :uid", {"uid": user_id}) + await db.execute("DELETE FROM org_membership WHERE user_id = :uid", {"uid": user_id}) + # Anonymize user record (don't delete — preserves FK integrity) + await db.execute( + "UPDATE directus_users SET first_name = 'Deleted', last_name = 'User', " + "email = 'deleted-' || id || '@deleted.dembrane.com' WHERE id = :uid", + {"uid": user_id} + ) +``` + +**Data processing agreements (DPA) scope changes.** Today, Dembrane processes data for one customer at a time. With workspaces, partner consultancies process data on behalf of their clients, through Dembrane. This is a sub-processor chain: Client → Partner → Dembrane. The DPA needs to reflect this. + +**For now:** Flag for legal review. Not a technical blocker, but the workspace feature changes the data processing relationship. + +### Recommendations +- [ ] Add `region` field to workspace (nullable, future use) +- [ ] Two deletion paths: soft delete (billing) vs GDPR erasure (privacy) +- [ ] GDPR deletion anonymizes usage events, doesn't delete them +- [ ] Audio files can be hard-deleted immediately on conversation delete +- [ ] Flag DPA update for legal team before B2B2B partner features go live + +--- + +## Agent 8: The Dev Who Has to Implement This + +### How this fails + +**Scope creep disguised as "best practices."** The PRD + architecture review + this failure analysis describe a system that would take a team of 4 engineers ~3 months. If one person is building this, half of the "best practices" need to be deferred or the feature never ships. + +**The actual critical path is:** +1. Tables + migration script (1 week) +2. Core API: workspace CRUD + membership + permission resolution (1 week) +3. Frontend: routing + selector + topbar (1 week) +4. Frontend: settings pages + usage dashboard (1 week) +5. Soft delete conversion + usage event instrumentation (1 week) +6. Testing + edge cases + polish (1 week) + +That's 6 weeks for a focused engineer. Every "nice to have" adds a week. + +**What to defer (build later, design for now):** +- `app_user` indirection table (just FK to directus_users for now, migrate later) +- Event versioning (add `"v": 1` but don't build multi-version aggregation) +- Materialized views for permission (single JOIN query is fine for now) +- Rate limiting (add after launch if abuse occurs) +- Request tracing (use existing logging) +- Customizable invite emails (plain text email first) +- Tier comparison during workspace creation (just a dropdown) + +**What must NOT be deferred:** +- Soft delete (can't retrofit without data loss) +- Tenant isolation middleware (can't retrofit without security audit) +- usage_event table partitioning (can't add to existing table) +- Immutable slugs decision (can't change once URLs are in the wild) +- Pagination on list endpoints (can't add without breaking clients) + +**The migration is the scariest part.** It touches every user and every project. If it's wrong, everything is wrong. Budget 2 full days for migration script development and testing. Run it on a DB clone at least 3 times before production. + +### Recommendations for implementation sequencing +- [ ] Week 1: Schema + migration + soft delete columns + app_user table decision +- [ ] Week 2: Core API (CRUD, permissions, invites) + tenant isolation middleware +- [ ] Week 3: Frontend routing + workspace selector + post-login router +- [ ] Week 4: Settings pages + member management + invite modal +- [ ] Week 5: Usage events + dashboard + soft delete conversion of existing endpoints +- [ ] Week 6: Testing, edge cases, migration dry-run on prod clone, deploy diff --git a/echo/docs/workspaces/gate-check-protocol.md b/echo/docs/workspaces/gate-check-protocol.md new file mode 100644 index 00000000..205eb8e5 --- /dev/null +++ b/echo/docs/workspaces/gate-check-protocol.md @@ -0,0 +1,285 @@ +# Gate Check Protocol +## "Surgical Timeout" Before Every Code Change Session + +--- + +## The Rule + +**No Claude Code session makes code changes without first running a Gate Check.** + +Session 1 (Explore) is exempt — it's read-only. +Sessions 2, 3, and 4 MUST run the Gate Check before the first commit. + +--- + +## How It Works + +Every session prompt includes a two-phase structure: + +``` +PHASE 1: GATE CHECK (mandatory, before any changes) + - Analyze what you're about to do + - Surface impacts, risks, and open questions + - Ask me questions until I say "proceed" + - Do NOT write any code until I explicitly say "proceed" + +PHASE 2: EXECUTE (only after I say "proceed") + - Make changes commit by commit + - Pause after each commit for my review +``` + +The Gate Check is NOT a formality. It's where the agent catches things like: +- "This collection has 3 Directus hooks that fire on delete — my soft delete conversion will trigger them" +- "The project.directus_user_id field has a unique constraint I didn't expect" +- "There's an existing archived_at field on conversations that conflicts with our deleted_at plan" + +--- + +## Gate Check Template + +Add this to every session prompt (Sessions 2, 3, 4): + +``` +IMPORTANT: This session has two phases. Do NOT skip Phase 1. + +=== PHASE 1: GATE CHECK === + +Before making ANY code changes, do the following: + +1. IMPACT ANALYSIS + List every file you plan to modify or create. For each: + - File path + - What changes + - What could break + +2. BLAST RADIUS + Answer these questions: + - What existing functionality could this break? + - What Directus hooks/flows/triggers will be affected? + - Are there any scheduled jobs or background processes that touch these collections? + - Will any existing API responses change shape? + - Will any existing frontend behavior change? + +3. IRREVERSIBILITY CHECK + For each change, classify it: + - SAFE: Additive change, easily reverted (new collection, new field, new endpoint) + - CAUTION: Modifies existing behavior (changed query filters, rerouted deletes) + - DANGER: Data mutation or destructive change (migration, schema modification) + + List all CAUTION and DANGER items explicitly. + +4. OPEN QUESTIONS + List anything you're unsure about. Examples: + - "I see a Directus flow called 'cleanup-orphans' — will it conflict with soft delete?" + - "The project collection has a field called 'status' — should deleted_at interact with it?" + - "There are 3 different patterns for calling Directus in this codebase — which should I use?" + +5. DEPENDENCIES + What must be true before these changes work? + - Are there collections from a previous session that must exist? + - Are there environment variables needed? + - Does the local DB need specific seed data? + +6. ASK ME + Based on the above, ask me specific questions. Keep asking until you have + no remaining uncertainties. Format as numbered questions I can answer quickly. + + Examples of GOOD questions: + - "The conversation collection has an 'audio_status' field with values + 'processing'/'complete'/'error'. Should soft-deleted conversations preserve + this field, or should we set it to a new 'deleted' status?" + - "I found 2 places where conversations are deleted: user-initiated delete + in the UI, and an automated cleanup that deletes conversations with + status='error' after 24h. Should the automated cleanup also become a + soft delete?" + - "The Directus permissions for the 'facilitator' role include DELETE on + conversations. After soft delete conversion, should I remove this + Directus permission (since deletes now go through Python)?" + + Examples of BAD questions (too vague): + - "Is this approach okay?" + - "Should I proceed?" + - "Any concerns?" + +DO NOT proceed to Phase 2 until I explicitly say "proceed" or "go ahead" or similar. + +=== PHASE 2: EXECUTE === + +After I confirm, make changes commit by commit. +After each commit, give me a one-line summary of what changed. +Pause after every 3 commits and ask: "Should I continue?" +``` + +--- + +## Session-Specific Gate Checks + +### Session 2: Schema — Gate Check Focus Areas + +The agent should specifically investigate and ask about: + +``` +SCHEMA-SPECIFIC CHECKS: + +a) For each existing collection getting a new field (deleted_at, workspace_id, visibility): + - Are there any Directus hooks/triggers that fire on UPDATE for this collection? + - Are there any computed/alias fields that might conflict? + - Are there any Directus permissions that restrict field creation? + - What's the current field count? Any approaching Directus limits? + +b) For the app_user collection: + - List EVERY field from directus_users that the Python API or frontend + currently reads or writes + - For each: should it be denormalized into app_user, or fetched from + directus_users at runtime? + - Are there any fields with sensitive data (passwords, tokens) that + should NOT be in app_user? + +c) For relations: + - How does this project configure Directus M2O/O2M relations? + - Are there any naming conventions for relation fields? + - Will the new FK fields conflict with existing ones? + +d) For schema sync: + - Show me the current directus-extension-sync config + - After I create these collections, what's the exact command to sync? + - Is there a CI/CD step that validates schema? + +ASK ME about anything that isn't clear from the codebase. +``` + +### Session 3: Soft Delete — Gate Check Focus Areas + +``` +SOFT-DELETE-SPECIFIC CHECKS: + +a) For each delete operation you found: + - Is this delete user-initiated, system-initiated, or both? + - If system-initiated (cron, background job, Directus flow): + should it ALSO become soft delete, or is hard delete correct? + - What's the current error handling if the delete fails? + +b) For Directus permissions: + - Which Directus roles currently have DELETE permission on affected collections? + - After converting to soft delete (PATCH instead of DELETE), do we need to + ADD UPDATE permission and REMOVE DELETE permission? + - Or do we leave Directus permissions as-is since Python uses admin token? + +c) For read query updates: + - How many read queries are there for each affected collection? + - Are any of these queries in Directus flows/hooks (not Python/frontend)? + - Are there any aggregate queries (COUNT, SUM) that would be affected? + - Are there any Directus dashboard/insights panels that show these collections? + +d) For the emit_usage_event utility: + - Where should this file live? (Show me the project's utility/helper structure) + - What's the existing logging pattern? (So error logging matches) + - Is there an existing request context/trace ID system to use for trace_id? + +e) For frontend changes: + - List each frontend file that calls Directus DELETE directly + - What's the existing error handling pattern in the frontend for API calls? + - Are there any optimistic UI updates that assume immediate deletion? + +ASK ME about each delete operation's intended behavior after conversion. +``` + +### Session 4: Core API + Migration — Gate Check Focus Areas + +``` +API-SPECIFIC CHECKS: + +a) For the API structure: + - Where do new route files go? Show the existing file/folder structure. + - How are routes registered? (FastAPI router includes, prefix patterns) + - What's the existing response format? (Envelope pattern? Raw data? Error format?) + - What's the existing input validation pattern? (Pydantic? Manual?) + +b) For auth: + - Show me the EXACT code of the current get_current_user (or equivalent) + - What does it return? (Directus user object? Custom user object? Just user ID?) + - How does it handle expired sessions? + - For admin endpoints: how do existing admin-only endpoints check admin status? + +c) For the migration script: + - What's the exact count of users in the local DB? In production? + - How long does a typical Directus API call take locally? (To estimate migration time) + - Is there a maintenance mode or can we run migration while the app is live? + - What's the rollback plan if migration goes wrong? + (Restore from backup? Reverse script?) + +d) For email (invites): + - Show me the existing SendGrid integration code + - What email templates exist? + - What's the FROM address used? + - Is there a template system or are emails constructed in code? + +e) For the workspace selector: + - The API endpoint GET /workspaces needs to be FAST (called on every login) + - How many Directus API calls will it need to assemble the response? + - Can we batch the queries? + +ASK ME about architectural decisions before implementing. +``` + +--- + +## What If the Gate Check Reveals a Problem? + +Three possible outcomes from a Gate Check: + +### 1. Minor clarification needed +Agent asks a question → you answer → agent proceeds. +Example: "Should deleted_at use ISO format or Unix timestamp?" → "ISO, match existing patterns" → proceed. + +### 2. PRD needs updating +Agent discovers something that contradicts the PRD. +Example: "The project collection already has a 'shared_with' JSON field that stores user IDs. This overlaps with the project_user collection in the PRD." + +**Action:** Update the PRD section before proceeding. The agent should propose the update, you approve it, agent writes it, THEN proceeds with code. + +### 3. Scope change needed +Agent discovers something that makes the planned changes significantly harder or riskier. +Example: "There are 47 read queries across the codebase that touch the conversation collection. Adding deleted_at filter to all of them in one commit is risky." + +**Action:** Replan. Break the session into smaller pieces. Or defer the risky part. + +--- + +## The Full Session Prompt Pattern + +Here's the complete prompt structure for any code-changing session: + +``` +You are working on the `workspaces` branch of the Dembrane ECHO platform. + +CONTEXT: +[Attach PRD + codebase exploration report + this session's specific task description] + +=== PHASE 1: GATE CHECK === +[Session-specific gate check from above] + +Do NOT write any code until I say "proceed." + +=== PHASE 2: EXECUTE === +[Session-specific commit sequence from execution plan] + +After each commit: +- Tell me: what changed, what files, what to verify +- Pause every 3 commits and ask if I want to continue + +If at any point you discover something unexpected: +- STOP +- Tell me what you found +- Ask how to handle it +- Wait for my response before continuing +``` + +--- + +## Summary + +The Gate Check turns Claude Code from "autonomous agent that might go off the rails" into "surgical assistant that explains every cut before making it." It costs 5-10 minutes per session but prevents the scenario where you review 15 commits and realize commit #3 made a wrong assumption that invalidated commits 4-15. + +> *Measure twice, cut once.* +> *Or in our case: Gate Check once, commit many.* diff --git a/echo/docs/workspaces/reference.md b/echo/docs/workspaces/reference.md new file mode 100644 index 00000000..591e62eb --- /dev/null +++ b/echo/docs/workspaces/reference.md @@ -0,0 +1,379 @@ +# Dembrane Platform - App Overview & Screenshot Reference + +A complete snapshot of the current Dembrane platform for designer reference. +36 labelled screenshots covering both the host dashboard and the participant portal. + +--- + +## App Feature Tree + +``` +Dembrane Platform +│ +├── AUTH +│ ├── Login (email/password, optional 2FA PIN) +│ ├── Register (first name, last name, email, password) +│ ├── Verify Email +│ ├── Password Reset (request + reset) +│ └── Language picker (EN, NL, DE, FR, IT, ES, UA) +│ +├── HOME (after login) +│ ├── Pinned Projects (max 3, quick access cards) +│ ├── Projects List (infinite scroll, search, owner filter for admins) +│ ├── Create Project button +│ └── Header +│ ├── Logo / Home link +│ ├── Announcements bar +│ └── User Menu +│ ├── Settings +│ ├── Documentation +│ ├── Feedback portal +│ ├── Report an issue +│ ├── Slack community +│ ├── Language picker +│ └── Logout +│ +├── PROJECT (sidebar + main content layout) +│ │ +│ ├── Sidebar (resizable, collapsible on mobile) +│ │ ├── Home breadcrumb +│ │ ├── Project name (links to portal editor) +│ │ ├── Ask button (opens new chat) +│ │ ├── Library link +│ │ ├── Report button +│ │ ├── Chats accordion (list of past chats with title, date, menu) +│ │ ├── Conversations accordion +│ │ │ ├── Search conversations +│ │ │ ├── Upload button +│ │ │ ├── Options/Filters (Sort, Tags, Verified, Reset) +│ │ │ └── Conversation list (name, duration, date, tags, verified badge) +│ │ └── "Powered by Dembrane" footer +│ │ +│ ├── Project Overview +│ │ ├── QR code for participant portal link +│ │ ├── "Open for Participation?" toggle +│ │ ├── Ongoing conversations count +│ │ ├── Open guide / Copy link / Download QR buttons +│ │ │ +│ │ ├── [Tab] Portal Editor +│ │ │ ├── Conversation flow settings (ask for name, email, tags) +│ │ │ ├── AI title & tag generation settings +│ │ │ ├── Verification settings & topics +│ │ │ ├── GetReply mode settings +│ │ │ ├── Tutorial slug selection +│ │ │ ├── Finish text customization +│ │ │ └── Transcript anonymization toggle +│ │ │ +│ │ └── [Tab] Project Settings +│ │ ├── Name & context +│ │ ├── Upload section (add recordings) +│ │ ├── Export (download all transcripts) +│ │ ├── Host Guide link +│ │ ├── Webhooks (advanced, add/manage webhooks) +│ │ └── Actions (clone project, delete project) +│ │ +│ ├── Conversation Detail +│ │ ├── [Tab] Overview +│ │ │ ├── Summary (AI-generated, copy/regenerate buttons) +│ │ │ ├── Outcomes (approved artifacts from verify flow) +│ │ │ │ └── Expandable accordion per artifact (title, approval date, full content) +│ │ │ ├── Edit: name (portal-entered), title (AI-generated + Generate button), tags +│ │ │ ├── Move to another project (BETA) +│ │ │ ├── Download audio +│ │ │ └── Delete conversation +│ │ │ +│ │ └── [Tab] Transcript +│ │ ├── Full transcript with timestamps & speaker labels +│ │ ├── Copy transcript +│ │ └── Download transcript +│ │ +│ ├── Ask / Chat +│ │ ├── New Chat - Mode Selection +│ │ │ ├── Agentic (BETA) - multi-step analysis with tool execution +│ │ │ ├── Specific Details - select conversations, find exact quotes +│ │ │ └── Overview (BETA) - themes & patterns across all conversations +│ │ │ +│ │ └── Chat Interface +│ │ ├── Chat title + action buttons (copy, menu) +│ │ ├── System welcome message +│ │ ├── Context indicator (which conversations are loaded) +│ │ ├── User messages + AI responses (markdown, headings, bold, quotes) +│ │ ├── Conversation checkboxes in sidebar (select context for chat) +│ │ ├── Quick template buttons (Summarize, Compare & Contrast, Meeting Notes) +│ │ ├── Text input with "/" for template picker +│ │ ├── Message streaming with citations +│ │ ├── Save responses as templates +│ │ ├── Copy chat to markdown +│ │ └── Scroll to bottom button +│ │ +│ ├── Library (access-gated) +│ │ ├── Create library (requires conversations) +│ │ ├── Views list (auto-generated analysis) +│ │ │ └── View Detail +│ │ │ ├── View summary (markdown) +│ │ │ └── Aspect cards with insights +│ │ │ └── Aspect Detail (deep-dive analysis) +│ │ └── Request Access button (if not enabled) +│ │ +│ ├── Report +│ │ ├── Report list (multiple reports per project, language-specific) +│ │ ├── Update / generate report +│ │ ├── Published toggle +│ │ ├── Include portal link toggle +│ │ ├── Edit mode toggle +│ │ ├── Copy link / share +│ │ ├── Full report content (AI-generated Q&A/interview format) +│ │ ├── "Share your voice" CTA (links to participant portal) +│ │ ├── "X reading now" live indicator +│ │ └── Analytics (views count, timeline chart with milestones) +│ │ +│ └── Host Guide (protected, separate full-page view) +│ ├── Drag-and-drop section reordering +│ ├── Live conversation tracking via QR code +│ ├── Add/remove sections +│ ├── Fullscreen & print modes +│ └── Real-time participant tracking +│ +├── USER SETTINGS +│ ├── Account (profile info, email, delete account) +│ ├── Password management +│ ├── Two-factor authentication +│ ├── Appearance (font, font size) +│ ├── Whitelabel (custom logo upload) +│ ├── Legal basis selection +│ └── Audit logs viewer +│ +└── PARTICIPANT PORTAL (separate app/router, public-facing via QR/link) + │ + ├── Start / Onboarding (multi-slide carousel) + │ ├── Slide 1: Consent & Privacy + │ │ ├── Data controller info + │ │ ├── How recordings are processed + │ │ ├── Storage & deletion policy (EU servers, 30 day retention) + │ │ └── Consent checkbox (required to proceed) + │ │ + │ ├── Slide 2: Microphone Check + │ │ ├── Microphone device selector + │ │ ├── Live audio level meter + │ │ └── Skip button + │ │ + │ └── Slide 3: Ready to Begin + │ ├── Session name input (required) + │ ├── Tags selector (multi-select) + │ └── "Next" button → initiates conversation + │ + ├── Audio Conversation + │ ├── Welcome message + pattern image + │ ├── "Record" button (large, central) + │ ├── Text mode toggle button (switch to text input) + │ ├── Settings button (top-right) + │ ├── Wake lock (screen stays on while recording) + │ ├── S3 connectivity check (connection issue dialog if blocked) + │ └── After 60+ seconds: + │ ├── Refine options (explore / verify) + │ └── Finish (skip to end) + │ + ├── Text Conversation (alternative to audio) + │ ├── Text area input ("Type your response here") + │ ├── Submit button + │ └── Microphone toggle (switch back to audio) + │ + ├── Refine / Verify Flow + │ ├── Refine Selection + │ │ ├── "Make your contribution concrete" (verify option) + │ │ └── "Get immediate reply" (explore option, if enabled) + │ │ + │ ├── Verify Topic Selection + │ │ └── Topic cards: Actions, Agreements, Disagreements, Gems, Moments, Truths, Custom + │ │ + │ └── Verify Artifact Editor + │ ├── AI-generated artifact (markdown preview) + │ ├── Revise button (re-generate with verbal feedback, 30s cooldown) + │ ├── Edit button (manual markdown editor) + │ ├── Read Aloud button (audio playback) + │ └── Approve button → saves artifact, returns to recording + │ + ├── Finish Page + │ ├── Thank you / completion message (customizable per project) + │ ├── "Record another conversation" button + │ └── Email notification signup (optional) + │ ├── Email input + add button + │ ├── Email list with remove + │ ├── Privacy disclaimer + │ └── Submit confirmation + │ + ├── Public Report + │ ├── Published report content (same as host report, read-only) + │ ├── "X reading now" live indicator + │ ├── "Contribute" portal link (if enabled) + │ └── View tracking (anonymous) + │ + └── Unsubscribe + ├── Token-based verification + ├── "Unsubscribe from Notifications" button + └── Success/error messaging +``` + +--- + +## Screenshots Index + +### AUTH FLOW + +| File | Screen | Key Elements | +|------|--------|-------------| +| `01-login-page.png` | Login page (with credentials filled) | Email/password form, Login button, language picker, "Register as new user", "Forgot password?" link, Privacy Statements footer | + +--- + +### HOME / PROJECTS LIST + +| File | Screen | Key Elements | +|------|--------|-------------| +| `02-home-projects-list.png` | Home - Projects list (viewport) | "Home" heading, "Create" button, search bar, project cards with name/language/conversations/date/owner/pin | +| `02-home-projects-list-full.png` | Home - Projects list (full scroll) | Complete list of all projects | +| `03-home-user-menu-open.png` | Header user menu dropdown | Settings, Documentation, Feedback portal, Report an issue, Slack community, language picker, Logout | + +--- + +### PROJECT OVERVIEW & SETTINGS + +| File | Screen | Key Elements | +|------|--------|-------------| +| `04-project-overview.png` | Project overview (viewport) | Sidebar (Ask/Library/Report/Chats/Conversations), QR code, participation toggle, Project Settings tab (name, context, upload, export, webhooks, clone/delete) | +| `04-project-overview-full.png` | Project overview (full scroll) | All settings sections visible | +| `05-portal-editor.png` | Portal Editor tab (viewport) | Participant onboarding, conversation flow, verification, finish text settings | +| `05-portal-editor-full.png` | Portal Editor tab (full scroll) | All portal configuration options | + +--- + +### CONVERSATION DETAIL + +| File | Screen | Key Elements | +|------|--------|-------------| +| `06-conversation-detail.png` | Conversation overview tab (viewport) | Name + duration header, Overview/Transcript tabs, AI summary, Outcomes section, edit fields | +| `06-conversation-detail-full.png` | Conversation overview tab (full scroll) | Includes move-to-project, download audio, delete actions | +| `07-conversation-transcript.png` | Conversation transcript tab | Full transcript with timestamps and speaker labels | +| `21-conversation-with-artifacts.png` | Conversation with verified artifacts (collapsed) | 2 approved outcomes: "Breakthrough moments" and "What we think should happen" with approval dates | +| `22-conversation-artifacts-expanded.png` | Verified artifact expanded (viewport) | Full artifact content visible in accordion - rich markdown with headings, bold, structured argument | +| `22-conversation-artifacts-expanded-full.png` | Verified artifact expanded (full scroll) | Complete artifact text + edit conversation section below | + +--- + +### SIDEBAR DEEP FEATURES + +| File | Screen | Key Elements | +|------|--------|-------------| +| `18-sidebar-chats-expanded.png` | Sidebar with Chats accordion expanded | Chat list with titles, dates, per-chat action menus; Conversations accordion below with conversation checkboxes (for chat context selection) | +| `23-sidebar-conversation-filters.png` | Sidebar conversation filter options | Sort button, Tags filter, Verified filter, Reset to default - filter bar below search | + +--- + +### ASK / CHAT + +| File | Screen | Key Elements | +|------|--------|-------------| +| `11-ask-new-chat.png` | New chat mode selection | "What would you like to explore?" + 3 mode cards: Agentic (BETA), Specific Details, Overview (BETA) with example prompts | +| `19-chat-interface.png` | Active chat interface (viewport) | Chat title, system welcome, context indicator, user message, AI response (markdown with headings/sections), quick templates (Summarize, Compare & Contrast, Meeting Notes), text input | +| `19-chat-interface-full.png` | Active chat interface (full scroll) | Complete chat thread showing full AI analysis response with citations, section headings, follow-up Q&A | + +--- + +### LIBRARY + +| File | Screen | Key Elements | +|------|--------|-------------| +| `08-library.png` | Library page | "Request Access" button, "Create Library" disabled, "Your Views" with "Recurring Themes" template, access-gated alert | + +--- + +### REPORT + +| File | Screen | Key Elements | +|------|--------|-------------| +| `09-report.png` | Report page (viewport) | Report selector (3 reports), Published/portal link/edit mode toggles, AI-generated Q&A report content, "1 reading now" indicator | +| `09-report-full.png` | Report page (full scroll) | Complete report text + Analytics section (timeline chart, views count, milestones) | + +--- + +### HOST GUIDE + +| File | Screen | Key Elements | +|------|--------|-------------| +| `20-host-guide.png` | Host guide (viewport) | Full-page session management view with QR code, live participant tracking | +| `20-host-guide-full.png` | Host guide (full scroll) | Complete host guide with all sections | + +--- + +### USER SETTINGS + +| File | Screen | Key Elements | +|------|--------|-------------| +| `10-settings.png` | User settings (viewport) | Account-level settings | +| `10-settings-full.png` | User settings (full scroll) | Account info, password, 2FA, appearance (font/size), whitelabel logo, legal basis, audit logs | + +--- + +### PARTICIPANT PORTAL + +| File | Screen | Key Elements | +|------|--------|-------------| +| `12-participant-start.png` | Consent & privacy slide | Data controller info, storage/deletion policy, consent checkbox (unchecked), "I understand" button disabled | +| `12-participant-start-consent-checked.png` | Consent slide (checked) | Same as above with checkbox checked, "I understand" button now enabled | +| `13-participant-tutorial.png` | Microphone check slide | Microphone device selector, live audio level meter, "Skip" button, requesting mic access alert | +| `14-participant-ready.png` | "Ready to Begin?" slide | Session name input (required), tags multi-select, "Next" button | +| `15-participant-conversation-audio.png` | Audio conversation screen | Welcome heading + pattern image, "Record" button, text mode toggle, connection issue dialog (S3 check) | +| `15-participant-conversation-audio-clean.png` | Audio conversation screen (clean) | Same as above with connection dialog dismissed | +| `16-participant-conversation-text.png` | Text conversation screen | Text area ("Type your response here"), Submit button, microphone toggle to switch back to audio | +| `17-participant-report.png` | Participant-facing report (viewport) | Public report view - same content as host report, read-only, "Contribute" link | +| `17-participant-report-full.png` | Participant-facing report (full scroll) | Complete public report content | + +--- + +## User Flow Diagrams + +### Host Flow (authenticated) +``` +Login + └─> Home (Projects List) + ├─> Create Project + ├─> Settings (user account) + └─> Select Project + ├─> Project Overview + │ ├─> Portal Editor tab (configure participant experience) + │ └─> Project Settings tab (name, upload, export, webhooks, delete) + ├─> Conversation Detail + │ ├─> Overview (summary, artifacts, edit, move, delete) + │ └─> Transcript (view, copy, download) + ├─> Ask / Chat + │ ├─> New Chat (pick mode: Agentic / Details / Overview) + │ └─> Chat Interface (query conversations, get AI analysis) + ├─> Library (create views, explore aspects) + ├─> Report (generate, publish, share, analytics) + └─> Host Guide (live session management with QR) +``` + +### Participant Flow (public, via QR code or link) +``` +Scan QR / Open Link + └─> Start / Onboarding + ├─> Consent & Privacy (checkbox required) + ├─> Microphone Check (skippable) + └─> Ready to Begin (name + tags) + └─> Conversation + ├─> Audio Mode (record button, wake lock) + │ └─> After 60s+: + │ ├─> Refine → Verify Flow + │ │ ├─> Pick topic (actions/agreements/gems/etc.) + │ │ ├─> AI generates artifact + │ │ └─> Revise / Edit / Approve + │ └─> Finish + └─> Text Mode (type + submit) + └─> Finish + ├─> Thank you message + ├─> "Record another" button + └─> Email signup (optional) + +Public Report (separate URL, read-only) +Unsubscribe (email opt-out via token link) +``` diff --git a/echo/docs/workspaces/workspaces-prd-v3-final.md b/echo/docs/workspaces/workspaces-prd-v3-final.md new file mode 100644 index 00000000..2641200b --- /dev/null +++ b/echo/docs/workspaces/workspaces-prd-v3-final.md @@ -0,0 +1,861 @@ +# Workspaces & Organizations — Final PRD +## Dembrane ECHO Platform + +> **Status:** Ready for implementation +> **Date:** April 2026 +> **Version:** 3 (final) + +--- + +## Guiding Principles + +> *"The Tao that can be told is not the eternal Tao."* — Lao Tzu +> +> The best infrastructure is invisible. A solo facilitator should never know they're "in a workspace." A partner managing 20 client engagements should feel the system anticipates their needs. **Wu Wei** — effortless action — is the design target. + +> *"योगः कर्मसु कौशलम्"* (Yogah karmasu kaushalam) — "Yoga is skill in action." — Bhagavad Gita 2.50 +> +> Every architectural decision should make the *next* decision easier, not harder. Skill is not in building more — it's in building the thing that makes everything else simpler. + +> *"水善利万物而不争"* — "Water benefits all things and does not compete." — Tao Te Ching, Ch. 8 +> +> The platform serves facilitators who serve communities. We are water — shaping ourselves to the container (workspace) while carrying what matters (conversations, insights, outcomes) downstream. + +### Architectural Dharma — Each Layer Has Its Duty + +| Layer | Dharma (duty) | Fails when... | +|-------|--------------|---------------| +| **Org** | Be the legal/billing boundary. Protect the business entity. | ...it leaks into the user's daily experience | +| **Workspace** | Be the collaboration boundary. Contain the work. | ...it becomes a prison (can't share, can't leave) | +| **Project** | Be where meaning is made. Hold conversations, insights, reports. | ...it's burdened with access control it doesn't need | +| **User** | Move freely between contexts. Carry identity, not permissions. | ...their access is confusing or surprising | + +> *"In the Arthashastra, Kautilya teaches that the strength of an alliance is not in its rigidity but in its flexibility — the ability to change relationships without changing structure."* +> +> This is why `billed_to_workspace_id` is a pointer, not a hierarchy. Relationships change. Structure should not need to. + +--- + +## TL;DR + +Introduce **Orgs** and **Workspaces** above projects. On signup, every user gets an Org + Default Workspace. Solo users never see these concepts. Multi-user and B2B2B features are additive. All data access through Directus HTTP API. All new tables as Directus collections. + +--- + +## The Real World + +``` +Partner 1 (consultancy) Org 1 + ├── manages Client 1 projects ├── Workspace A (Default) — own projects + └── manages Client 2 projects ├── Workspace B — Client 1's projects + └── Workspace C — Client 2's projects +``` + +**Two paths to collaboration:** + +| | Path A: Partner-led | Path B: Client-led | +|---|---|---| +| Who creates workspace | Partner | Client | +| Where it lives | Partner's org | Client's org | +| Who pays | Partner | Client | +| Partner users appear as | Direct members | External members | +| Handoff needed? | Yes (future) | No | + +> *Sun Tzu: "The supreme art of war is to subdue the enemy without fighting."* +> We don't compete with partners — we make them more powerful. The B2B2B model wins by making the partner successful with their clients. + +--- + +## Technical Stack (As-Is) + +| Layer | Technology | Access Pattern | +|-------|-----------|----------------| +| Frontend | React + Vite SPA | Calls Python API + some direct Directus calls (migrating to Python) | +| API | Python FastAPI | Calls Directus HTTP API (admin token + user cookie forwarding) | +| Database | PostgreSQL (via Directus) | No direct SQL from Python. All through Directus REST API. | +| Schema | Directus admin UI + directus-extension-sync | Push/pull schema changes | +| Auth | Directus (cookie-based) | Frontend gets cookie, forwards to Python, Python validates with Directus | +| Email | SendGrid | Transactional emails configured | + +**All new tables are Directus collections.** Created via Directus admin UI, synced via directus-extension-sync, accessed via Directus REST API from Python. + +--- + +## Data Model + +### New Collections + +#### `app_user` + +**Why:** Indirection layer between our domain tables and Directus auth. When we eventually migrate off Directus auth, we update this table once instead of every FK in every table. + +> *Kautilya: "A wise king does not build his palace on borrowed land."* +> Our domain model should not be structurally dependent on a third-party auth system's internal IDs. + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | Our canonical user ID. All other tables FK to this. | +| `directus_user_id` | uuid, UNIQUE | Maps to `directus_users.id`. Current auth provider. | +| `email` | string | Denormalized for quick lookup without hitting Directus | +| `display_name` | string | | +| `created_at` | timestamp | | +| `updated_at` | timestamp | | + +**On user creation (Directus hook or Python post-registration):** Create corresponding `app_user` row. +**On user lookup:** Resolve `directus_user_id` → `app_user.id` once, use `app_user.id` everywhere. + +#### `org` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | | +| `name` | string | Default: "{user.display_name}'s Organization" | +| `slug` | string, UNIQUE | Display-only (NOT used in URLs) | +| `logo_url` | string, nullable | Default branding for workspaces | +| `created_by` | uuid, FK → `app_user.id` | | +| `deleted_at` | timestamp, nullable | Soft delete | +| `created_at` | timestamp | | +| `updated_at` | timestamp | | + +#### `org_membership` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | | +| `org_id` | uuid, FK → `org.id` | | +| `user_id` | uuid, FK → `app_user.id` | | +| `role` | string | `owner` / `admin` / `member` | +| `deleted_at` | timestamp, nullable | Soft delete (preserves seat-days for billing) | +| `created_at` | timestamp | | + +UNIQUE: `(org_id, user_id)` WHERE `deleted_at IS NULL` + +#### `workspace` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | **Used in URLs** as short ID | +| `org_id` | uuid, FK → `org.id` | Owning org | +| `name` | string | | +| `slug` | string | **Display-only.** Not in URLs. Unique per org (not globally). | +| `description` | string, nullable | | +| `logo_url` | string, nullable | Override org logo | +| `tier` | string, default `'pioneer'` | `pilot`/`pioneer`/`innovator`/`changemaker`/`guardian` | +| `billed_to_workspace_id` | uuid, nullable, FK → `workspace.id` | Partner billing stub. NULL = org pays. | +| `is_default` | boolean, default `false` | Auto-created workspace | +| `legal_basis` | string, nullable | `consent`/`client-managed`/`dembrane-events` | +| `privacy_policy_url` | string, nullable | | +| `settings` | json, default `{}` | Feature flags, limits | +| `deleted_at` | timestamp, nullable | Soft delete | +| `created_by` | uuid, FK → `app_user.id` | | +| `created_at` | timestamp | | +| `updated_at` | timestamp | | + +**URL pattern:** `/:locale/w/:workspaceId/projects` — uses `workspace.id` (short UUID), NOT slug. + +**Slug uniqueness:** UNIQUE per `(org_id, slug)` — two orgs can both have a "default" workspace. + +#### `workspace_membership` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | | +| `workspace_id` | uuid, FK → `workspace.id` | | +| `user_id` | uuid, FK → `app_user.id` | | +| `role` | string | `owner`/`admin`/`member`/`viewer` | +| `source` | string, default `'direct'` | `direct` = explicitly invited. `inherited` = auto-added from org role. | +| `is_external` | boolean, default `false` | User's primary org ≠ workspace's org | +| `deleted_at` | timestamp, nullable | Soft delete (preserves seat-days) | +| `created_at` | timestamp | | + +UNIQUE: `(workspace_id, user_id)` WHERE `deleted_at IS NULL` + +**Org inheritance behavior:** +- When workspace is created: org `owner` and `admin` members get auto-added as workspace_membership rows with `source='inherited'`, `role='admin'` +- When org member is promoted to admin/owner: auto-add inherited memberships to all org workspaces +- When org member is demoted from admin/owner: remove their `source='inherited'` memberships (but not `source='direct'`) +- Workspace admin can remove inherited members (just soft-delete the row) +- Removed inherited members are NOT re-added automatically + +**Workspace role policies:** + +| Role | Policies | +|------|----------| +| `viewer` | Read-only access to workspace-visible projects | +| `member` | `project:create`, `project:update` | +| `admin` | All member + `project:delete`, `project:share`, `member:invite`, `member:manage`, `settings:manage` | +| `owner` | `*` (everything including ownership transfer) | + +#### `project_user` + +For sharing private projects with specific users. **Tier-gated: innovator+.** + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | | +| `project_id` | uuid, FK → `project.id` | | +| `user_id` | uuid, FK → `app_user.id` | | +| `role` | string, default `'editor'` | `editor`/`viewer` | +| `granted_by` | uuid, FK → `app_user.id` | | +| `created_at` | timestamp | | + +#### `workspace_invite` + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | | +| `workspace_id` | uuid, FK → `workspace.id` | | +| `email` | string | | +| `role` | string | Role to assign on acceptance | +| `invited_by` | uuid, FK → `app_user.id` | | +| `token` | string, UNIQUE | `secrets.token_urlsafe(32)` — 256 bits | +| `expires_at` | timestamp | 7 days from creation | +| `accepted_at` | timestamp, nullable | | +| `created_at` | timestamp | | + +#### `usage_event` + +Append-only. **Never updated. Never deleted.** Source of truth for billing. + +| Field | Type | Notes | +|-------|------|-------| +| `id` | uuid, PK | | +| `trace_id` | string | Correlation ID (use request ID when available) | +| `org_id` | uuid, nullable | | +| `workspace_id` | uuid, nullable | | +| `project_id` | uuid, nullable | | +| `user_id` | uuid, nullable | | +| `event_type` | string | | +| `event_data` | json | **Always include `"v": 1`** for schema versioning | +| `created_at` | timestamp | | + +> *I Ching, Hexagram 1: "The Creative works sublime success."* +> The usage event log is the memory of the system. It never forgets, never argues, never lies. All billing disputes are settled by reading the log. + +**Event types:** + +| Type | Data (v1) | Emitted when | +|------|-----------|-------------| +| `org.created` | `{ "v": 1, "name": str }` | Signup | +| `workspace.created` | `{ "v": 1, "tier": str, "is_default": bool }` | Workspace creation | +| `workspace.member_added` | `{ "v": 1, "member_user_id": str, "role": str, "source": str, "is_external": bool }` | Member joins | +| `workspace.member_removed` | `{ "v": 1, "member_user_id": str, "role": str, "seat_days": int }` | Member removed (include days active for billing) | +| `project.created` | `{ "v": 1, "visibility": str }` | Project creation | +| `project.deleted` | `{ "v": 1, "conversation_count": int, "total_audio_hours": float }` | Project soft-deleted (snapshot billing metadata) | +| `conversation.deleted` | `{ "v": 1, "duration_seconds": int, "audio_hours": float }` | Conversation soft-deleted (preserve duration!) | +| `audio.uploaded` | `{ "v": 1, "duration_seconds": int, "conversation_id": str }` | Audio upload | +| `audio.processed` | `{ "v": 1, "duration_seconds": int, "conversation_id": str, "billable": bool }` | Transcription complete. `billable: false` for failures. | +| `chat.query` | `{ "v": 1, "mode": str }` | Chat message | +| `report.generated` | `{ "v": 1, "report_id": str }` | Report created | +| `report.deleted` | `{ "v": 1 }` | Report soft-deleted | + +### Modified Collections + +#### `project` (existing) + +Add fields: + +| Field | Type | Notes | +|-------|------|-------| +| `workspace_id` | uuid, nullable, FK → `workspace.id` | NULL only during migration window | +| `visibility` | string, default `'workspace'` | `workspace` / `private` | +| `deleted_at` | timestamp, nullable | Soft delete | + +#### `conversation` (existing) + +Add field: + +| Field | Type | Notes | +|-------|------|-------| +| `deleted_at` | timestamp, nullable | Soft delete. **Critical for billing** — preserves duration metadata. | + +#### Other collections needing `deleted_at` + +- `chat` (or whatever the chat/message collection is called) +- `report` + +--- + +## Soft Delete Strategy + +> *Ahimsa (अहिंसा) — non-harm. First, do no harm to user data. Second, do no harm to billing accuracy.* + +### The Rule + +**Every delete operation in the entire application must:** +1. Route through the Python API (no frontend→Directus direct deletes) +2. Set `deleted_at = now()` instead of hard deleting +3. Emit a `usage_event` with a metadata snapshot of billing-relevant fields +4. The actual data (audio files, etc.) can be purged, but metadata stays + +### Implementation Pattern + +```python +# In Python API — generic soft delete handler +async def soft_delete( + collection: str, + item_id: str, + metadata_snapshot: dict, + event_type: str, + workspace_id: str | None = None, + org_id: str | None = None, + user_id: str | None = None, +): + """Soft delete any item. Emit usage event with billing metadata.""" + + # 1. Set deleted_at via Directus API + await directus.patch(f"/items/{collection}/{item_id}", { + "deleted_at": datetime.utcnow().isoformat() + }) + + # 2. Emit usage event with metadata snapshot + await emit_usage_event( + event_type=event_type, + event_data={"v": 1, **metadata_snapshot}, + workspace_id=workspace_id, + org_id=org_id, + user_id=user_id, + ) + +# Example: deleting a conversation +async def delete_conversation(conversation_id: str, user: AppUser): + conv = await directus.get(f"/items/conversation/{conversation_id}") + project = await directus.get(f"/items/project/{conv['project_id']}") + + await soft_delete( + collection="conversation", + item_id=conversation_id, + metadata_snapshot={ + "duration_seconds": conv["duration_seconds"], + "audio_hours": conv["duration_seconds"] / 3600 if conv["duration_seconds"] else 0, + "project_id": conv["project_id"], + }, + event_type="conversation.deleted", + workspace_id=project["workspace_id"], + user_id=user.id, + ) + + # 3. Optionally purge audio file (not needed for billing) + if conv.get("audio_path"): + await storage.delete(conv["audio_path"]) +``` + +### GDPR Erasure (Separate Path) + +When a user requests account deletion (GDPR Article 17): +- Anonymize usage_events: set `user_id = NULL`, strip PII from `event_data` +- Remove memberships (soft delete with metadata snapshot) +- Anonymize the `app_user` record (don't hard delete — preserves FK integrity) +- Hard delete PII: invite records with their email, etc. + +### Subtask: Soft Delete Audit & Conversion + +**This is a prerequisite implementation task.** Before workspace features go live: + +1. **Audit:** Scan the entire codebase for all delete operations + - Frontend → Directus direct DELETE calls + - Frontend → Python API delete endpoints + - Python API → Directus DELETE calls + - Directus flows/hooks that delete items +2. **Add `deleted_at`** to: conversation, project, chat, report (workspace/org/membership tables are new and have it from day 1) +3. **Reroute** all frontend→Directus direct deletes through Python API endpoints +4. **Convert** each delete to soft delete + usage event emission +5. **Update all read queries** to filter `deleted_at IS NULL` (Directus filter: `{ "deleted_at": { "_null": true } }`) +6. **Test:** Verify deleted items don't appear in UI, usage events are emitted, billing metadata is preserved + +--- + +## Permission Resolution + +> *Confucius: "Let the ruler be a ruler, the minister a minister, the father a father, and the son a son."* +> Each role has clear responsibilities. Ambiguity in access control is a security vulnerability. + +```python +async def get_user_project_access(app_user_id: str, project_id: str) -> Access | None: + """Single resolution path. Check in order of specificity.""" + + # Fetch project (with deleted_at filter) + project = await directus.get(f"/items/project/{project_id}", { + "filter": {"deleted_at": {"_null": True}} + }) + if not project: + return None + + # 1. Legacy ownership (backward compat — phase out over time) + if project.get("directus_user_id"): + app_user = await get_app_user(app_user_id) + if app_user and app_user["directus_user_id"] == project["directus_user_id"]: + return Access(role="owner", source="legacy") + + if not project.get("workspace_id"): + return None + + # 2. Workspace membership (includes inherited org admins as explicit rows) + membership = await directus.get("/items/workspace_membership", { + "filter": { + "workspace_id": {"_eq": project["workspace_id"]}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "limit": 1 + }) + + if membership and len(membership) > 0: + ws_role = membership[0]["role"] + + if project["visibility"] == "workspace": + return Access(role=ws_role, source="workspace") + + if project["visibility"] == "private": + if ws_role in ("admin", "owner"): + return Access(role=ws_role, source="workspace") + + # 3. Direct project share (private projects only) + if project["visibility"] == "private": + project_user = await directus.get("/items/project_user", { + "filter": { + "project_id": {"_eq": project_id}, + "user_id": {"_eq": app_user_id}, + }, + "limit": 1 + }) + if project_user and len(project_user) > 0: + return Access(role=project_user[0]["role"], source="project_share") + + return None +``` + +### Tenant Isolation Middleware + +**Every workspace-scoped endpoint** must use this dependency: + +```python +async def get_workspace_context( + workspace_id: str, # From URL path parameter + current_user: AppUser = Depends(get_current_user), +) -> WorkspaceContext: + """Validates user has access to this workspace. Returns scoped context.""" + + membership = await directus.get("/items/workspace_membership", { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "user_id": {"_eq": current_user.id}, + "deleted_at": {"_null": True}, + }, + "limit": 1 + }) + + if not membership or len(membership) == 0: + raise HTTPException(status_code=403, detail="No access to this workspace") + + return WorkspaceContext( + workspace_id=workspace_id, + user=current_user, + role=membership[0]["role"], + source=membership[0]["source"], + ) + +# Usage — structurally impossible to forget the access check +@router.get("/api/v1/workspaces/{workspace_id}/projects") +async def list_projects(ctx: WorkspaceContext = Depends(get_workspace_context)): + return await directus.get("/items/project", { + "filter": { + "workspace_id": {"_eq": ctx.workspace_id}, + "deleted_at": {"_null": True}, + } + }) +``` + +--- + +## URL Structure + +> *"Use short IDs in URLs, slugs are display-only."* + +``` +/:locale/login +/:locale/register +/:locale/select-workspace +/:locale/w/:workspaceId/projects # Dashboard +/:locale/w/:workspaceId/projects/new # Create project +/:locale/w/:workspaceId/projects/:projectId # Project view +/:locale/w/:workspaceId/projects/:projectId/chats/:id # Chat +/:locale/w/:workspaceId/projects/:projectId/reports/:id # Report +/:locale/w/:workspaceId/settings # Workspace settings +/:locale/org/:orgId/settings # Org settings (also by ID) +/:locale/settings # User settings +``` + +`workspaceId` and `orgId` are UUIDs (or shortened UUIDs if you prefer — e.g., first 8 chars). Slugs appear in the UI (page titles, breadcrumbs, workspace cards) but never in URLs. + +--- + +## Frontend Architecture + +### Progressive Solo Experience + +> *Wu Wei: the best infrastructure is invisible.* + +``` +IF user has exactly 1 workspace AND is not org admin/owner: + → Auto-redirect to workspace dashboard + → Topbar shows ONLY logo + user avatar (no workspace name, no "change workspace") + → Settings gear goes to workspace settings (no "org" language) + → Subtle prompt: "Invite your team →" in sidebar footer + → The word "workspace" never appears + +IF user has 2+ workspaces OR is org admin/owner: + → Full workspace experience (selector, topbar with workspace name, etc.) +``` + +### Workspace Selector (Full Page) + +Route: `/:locale/select-workspace` + +Three variants based on context — see `ui-flows-mockdown.md` for complete specs: +- **Card View**: 2-3 workspaces, not org admin +- **List View**: >3 workspaces, searchable with Internal/External tabs +- **Partner View**: Org admin/owner, shows org context + management links + +### Post-Login Router + +```typescript +// After successful authentication +const workspaces = await api.getAccessibleWorkspaces(); +const lastWorkspace = localStorage.getItem('lastWorkspaceId'); +const isOrgAdmin = workspaces.some(w => !w.is_external && ['owner', 'admin'].includes(w.org_role)); + +// Check for deep link (saved before login redirect) +const deepLink = sessionStorage.getItem('redirectAfterLogin'); +if (deepLink) { + sessionStorage.removeItem('redirectAfterLogin'); + navigate(deepLink); + return; +} + +if (workspaces.length === 0) { + navigate('/error/no-workspace'); // Should never happen +} else if (workspaces.length === 1 && !isOrgAdmin) { + navigate(`/w/${workspaces[0].id}/projects`); +} else { + navigate('/select-workspace'); +} +``` + +### Stale State Handling + +- On 403 from any workspace API call → invalidate workspace cache, redirect to selector, show toast +- Workspace list on selector page → poll every 30 seconds +- WorkspaceContext provider → staleTime 60s, refetch on window focus + +--- + +## API Endpoints + +All under `/api/v1/`. Auth via Directus cookie forwarding. + +### Org +``` +GET /orgs # User's orgs +GET /orgs/:id # Org detail +PATCH /orgs/:id # Update (name, logo) +GET /orgs/:id/members # Org members +POST /orgs/:id/members # Add member +PATCH /orgs/:id/members/:uid # Change role +DELETE /orgs/:id/members/:uid # Remove (soft delete) +GET /orgs/:id/billing # Usage rollup across workspaces +``` + +### Workspace +``` +GET /workspaces # All accessible (for selector) +POST /workspaces # Create (in user's org) +GET /workspaces/:id # Detail +PATCH /workspaces/:id # Update +DELETE /workspaces/:id # Soft delete (must be empty) +GET /workspaces/:id/members # Members + inherited + pending invites +POST /workspaces/:id/members # Invite (by email) +PATCH /workspaces/:id/members/:uid # Change role +DELETE /workspaces/:id/members/:uid # Remove (soft delete) +GET /workspaces/:id/projects # Workspace projects +POST /workspaces/:id/projects # Create project +GET /workspaces/:id/usage # Usage summary +``` + +### Project +``` +GET /projects/:id/users # Project share list +POST /projects/:id/users # Share (private projects, innovator+) +DELETE /projects/:id/users/:uid # Revoke share +DELETE /projects/:id # Soft delete project +``` + +### Conversations, Chats, Reports (existing, modified) +``` +DELETE /conversations/:id # Soft delete (via Python, not Directus direct) +DELETE /chats/:id # Soft delete +DELETE /reports/:id # Soft delete +``` + +### Admin (Dembrane internal) +``` +GET /admin/usage # Cross-org usage for manual invoicing +PATCH /admin/workspaces/:id/tier # Set workspace tier manually +``` + +--- + +## Migration Strategy + +### Existing User Migration + +> *I Ching, Hexagram 18 — "Work on What Has Been Spoiled": Correct the situation carefully. Acting too hastily brings misfortune.* + +**Pre-migration:** +1. Full database backup +2. Run migration on a DB clone first +3. Dry-run mode: log what would be created without writing + +**Migration script (runs via Python API against Directus HTTP API):** + +```python +async def migrate_existing_users(dry_run: bool = True): + """Idempotent. Safe to re-run. Per-user error handling.""" + + users = await directus.get("/users", {"limit": -1, "fields": ["id", "first_name", "last_name", "email"]}) + + for i, user in enumerate(users): + try: + # Idempotency: skip if app_user already exists + existing = await directus.get("/items/app_user", { + "filter": {"directus_user_id": {"_eq": user["id"]}}, + "limit": 1 + }) + if existing and len(existing) > 0: + logger.info(f"[{i+1}/{len(users)}] SKIP {user['email']} — already migrated") + continue + + if dry_run: + logger.info(f"[{i+1}/{len(users)}] WOULD migrate {user['email']}") + continue + + # Create app_user + app_user_id = str(uuid4()) + await directus.post("/items/app_user", { + "id": app_user_id, + "directus_user_id": user["id"], + "email": user.get("email", ""), + "display_name": f"{user.get('first_name', '')} {user.get('last_name', '')}".strip(), + }) + + # Create org + org_name = f"{user.get('first_name', 'My')}'s Organization" + org_id = str(uuid4()) + org_slug = slugify(org_name) + await directus.post("/items/org", { + "id": org_id, "name": org_name, "slug": org_slug, "created_by": app_user_id, + }) + await directus.post("/items/org_membership", { + "id": str(uuid4()), "org_id": org_id, "user_id": app_user_id, "role": "owner", + }) + + # Create default workspace + ws_id = str(uuid4()) + await directus.post("/items/workspace", { + "id": ws_id, "org_id": org_id, "name": "Default", + "slug": "default", "is_default": True, "tier": "pioneer", + "created_by": app_user_id, + }) + await directus.post("/items/workspace_membership", { + "id": str(uuid4()), "workspace_id": ws_id, "user_id": app_user_id, + "role": "owner", "source": "inherited", + }) + + # Move user's projects into default workspace + projects = await directus.get("/items/project", { + "filter": {"directus_user_id": {"_eq": user["id"]}}, + "fields": ["id"], + "limit": -1, + }) + for proj in projects: + await directus.patch(f"/items/project/{proj['id']}", { + "workspace_id": ws_id, + }) + + # Emit usage events + await emit_usage_event("org.created", {"v": 1, "name": org_name}, + org_id=org_id, user_id=app_user_id) + await emit_usage_event("workspace.created", + {"v": 1, "tier": "pioneer", "is_default": True}, + org_id=org_id, workspace_id=ws_id, user_id=app_user_id) + + logger.info(f"[{i+1}/{len(users)}] OK {user['email']} — org:{org_id} ws:{ws_id} projects:{len(projects)}") + + except Exception as e: + logger.error(f"[{i+1}/{len(users)}] FAIL {user['email']} — {e}") + # Continue to next user — don't fail the whole migration + continue +``` + +### New User Signup (Post-Deploy) + +1. User registers via Directus +2. Python post-registration endpoint (called by Directus hook or frontend): + - Create `app_user` + - Create org + org_membership(owner) + - Create default workspace + workspace_membership(owner, source='inherited') + - Emit usage events +3. Post-login router auto-redirects to workspace dashboard + +--- + +## Implementation Sequence + +> *Kautilya: "The work which has been begun should be completed."* +> Each phase ships a working increment. No phase depends on a later phase. + +### Phase 0: Soft Delete Conversion (Prerequisite) + +**This must happen BEFORE workspace features.** It's a standalone task. + +1. Add `deleted_at` field to: `conversation`, `project`, `chat`, `report` +2. Audit all delete operations in the codebase (frontend + Python) +3. Create Python API delete endpoints for each collection +4. Reroute all frontend delete calls through Python API +5. Convert each to soft delete + usage event emission +6. Update all Directus read queries to filter `{ "deleted_at": { "_null": true } }` +7. Test: deleted items invisible, usage events emitted, metadata preserved + +**Deliverable:** Every delete in the system goes through Python, is soft, and emits billing metadata. + +### Phase 1: Schema + Data Model + +1. Create Directus collections: `app_user`, `org`, `org_membership`, `workspace`, `workspace_membership`, `project_user`, `workspace_invite`, `usage_event` +2. Add fields to existing collections: `project.workspace_id`, `project.visibility`, plus `deleted_at` on workspace/org/membership tables +3. Sync schema via directus-extension-sync +4. Write `emit_usage_event` utility function +5. Write migration script (dry-run tested) + +**Deliverable:** All tables exist. Migration script ready to run. + +### Phase 2: Migration + Core API + +1. Run migration on production (after clone testing) +2. Implement tenant isolation middleware (`get_workspace_context`) +3. Implement permission resolution (`get_user_project_access`) +4. Workspace CRUD endpoints +5. Org CRUD endpoints +6. Membership management endpoints +7. Invite flow (create invite → send email via SendGrid → accept on registration) +8. Instrument existing code paths to emit usage events + +**Deliverable:** Full API working. Testable via Postman/curl. + +### Phase 3: Frontend — Routing + Selector + +1. Add workspace-scoped routing (`/:locale/w/:workspaceId/*`) +2. Post-login router logic (with deep link preservation) +3. Workspace selector page (card/list/partner variants) +4. Topbar changes (workspace name, change workspace button) +5. Progressive solo experience (hide workspace language for single-workspace users) +6. Legacy URL handling (redirect old URLs to workspace-scoped) +7. WorkspaceContext provider with stale state handling + +**Deliverable:** Users can log in, see workspace selector, navigate to workspace. + +### Phase 4: Frontend — Settings + Management + +1. Workspace settings page (General, Members, Branding, Legal, Billing tabs) +2. Org settings page (General, Members, Billing tabs) +3. Member invite modal +4. Usage dashboard (per-workspace + org-level billing rollup) +5. New workspace creation flow (3-step wizard) +6. User settings updates (workspace list, org list) + +**Deliverable:** Full workspace and org management in UI. + +### Phase 5: Frontend — Project Changes + +1. Project visibility (workspace/private) — private gated to innovator+ +2. Private project sharing UI (project_user management) +3. Create project within workspace context (auto-set workspace_id) +4. Empty states and tier-gating upgrade prompts +5. Post-migration onboarding modal for existing users + +**Deliverable:** Complete feature. Ready for production. + +--- + +## Tier Feature Matrix (UI Enforcement) + +| Feature | Pioneer | Innovator | Changemaker | Guardian | +|---------|:-------:|:---------:|:-----------:|:--------:| +| Projects + conversations | ✓ | ✓ | ✓ | ✓ | +| Chats + reports | ✓ | ✓ | ✓ | ✓ | +| Data export | — | ✓ | ✓ | ✓ | +| Private project sharing | — | ✓ | ✓ | ✓ | +| Whitelabel branding | — | — | ✓ | ✓ | +| API/integration access | — | — | ✓ | ✓ | + +**Enforcement:** Python API checks `workspace.tier` before allowing tier-gated operations. Frontend hides/disables UI elements with upgrade prompts. + +--- + +## Edge Cases + +| Scenario | Handling | +|---|---| +| Solo user, 1 workspace | Progressive: no workspace language shown. "Invite your team →" prompt. | +| Delete workspace with projects | Blocked. "Delete or move all projects first." | +| Last owner leaves workspace | Blocked. "Transfer ownership first." | +| Invite to unregistered email | Create workspace_invite, send email. On signup, auto-add. Expire 7 days. | +| User removed from workspace | Soft delete membership. Loses access to all workspace projects. project_user entries also soft-deleted. | +| Inherited member removed | Soft delete the `source='inherited'` row. NOT re-added automatically. | +| External user | `is_external=true` on membership. Tagged "External" in UI. Counts as seat. | +| 403 on workspace API call | Frontend: invalidate cache, redirect to selector, toast "Access changed." | +| Audio transcription fails | Usage event: `{ "billable": false }`. Doesn't count toward hours. | +| User requests GDPR deletion | Anonymize usage events + app_user. Hard delete PII. Preserve billing metadata. | +| Deep link through login | sessionStorage preserves URL, restore after auth. | + +--- + +## What's NOT In This Phase + +- Automated billing / Stripe +- Better Auth migration +- Workspace handoff (transfer between orgs) +- Kickback/commission tracking +- Google OAuth +- Project transfer between workspaces +- API key management +- Real-time / WebSocket updates +- Network effects features + +--- + +## Companion Documents + +| Document | Contains | +|----------|---------| +| `ui-flows-mockdown.md` | Complete screen-by-screen specs (Flows 1-24) | +| `b2b2b-strategy.md` | Partner model, Path A/B, billing, onboarding | +| `architecture-review.md` | Detailed security/performance/compliance recommendations | +| `failure-analysis.md` | 8-perspective red team analysis | + +--- + +## Final Wisdom + +> *Thiruvalluvar (திருவள்ளுவர்), Kural 391:* +> *"செய்க பொருளைச் செறுநர் செருக்கறுக்கும்"* +> *"Acquire wealth — it cuts the arrogance of foes."* +> +> Build the billing infrastructure now, even though billing is manual. The usage event log is your wealth. It cuts disputes, proves value, and funds everything else. + +> *Sun Tzu: "Every battle is won before it is ever fought."* +> +> The migration script, the soft delete conversion, the tenant isolation middleware — these are boring battles fought before the exciting feature work begins. Win them thoroughly. + +> *Lao Tzu: "A journey of a thousand miles begins with a single step."* +> +> Phase 0 (soft delete conversion) is the single step. It's not glamorous. It doesn't ship a visible feature. But without it, every phase that follows is built on sand. From 1d332d83b6eb6dff48c817e92cc6efeeef768564 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 13 Apr 2026 15:52:54 +0000 Subject: [PATCH 011/208] refactor: swap DirectusClient from requests to httpx Replace the requests library with httpx in the DirectusClient. httpx is a modern HTTP client with the same sync API but better async support for future migration. Key changes: - requests.request() -> httpx.request() - requests.exceptions.ConnectionError -> httpx.ConnectError - requests.exceptions.HTTPError -> httpx.HTTPStatusError - Explicit 30s timeout (httpx defaults to 5s, requests had none) - Removed urllib3 warning suppression (not needed with httpx) All callers catch DirectusServerError/DirectusBadRequest (our custom exceptions), not library-specific exceptions, so this change is fully contained within directus.py. --- echo/server/dembrane/directus.py | 61 ++++++++++++++++---------------- echo/server/pyproject.toml | 1 + 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/echo/server/dembrane/directus.py b/echo/server/dembrane/directus.py index 316eb9fa..8f2b3346 100644 --- a/echo/server/dembrane/directus.py +++ b/echo/server/dembrane/directus.py @@ -8,9 +8,7 @@ from dataclasses import dataclass from urllib.parse import urljoin -import urllib3 -import requests -from urllib3.exceptions import InsecureRequestWarning +import httpx from dembrane.settings import get_settings @@ -56,7 +54,7 @@ class DirectusBadRequest(DirectusGenericException): """Exception raised for bad requests to Directus API (e.g., assertion errors).""" -def is_recoverable_error(response: requests.Response) -> bool: +def is_recoverable_error(response: httpx.Response) -> bool: """ Check if the response status code indicates a recoverable error. @@ -77,7 +75,7 @@ def make_request_with_retry( max_retries: int = 3, retry_delay: float = 1.0, **kwargs: Any, -) -> requests.Response: +) -> httpx.Response: """ Make an HTTP request with retry logic for recoverable errors. @@ -87,14 +85,15 @@ def make_request_with_retry( url: URL to make the request to max_retries: Maximum number of retry attempts retry_delay: Initial delay between retries in seconds - **kwargs: Additional arguments to pass to requests + **kwargs: Additional arguments to pass to httpx Returns: - requests.Response: The response from the server + httpx.Response: The response from the server Raises: - requests.exceptions.RequestException: If the request fails after all retries + httpx.HTTPError: If the request fails after all retries """ + kwargs.setdefault("timeout", 30.0) retries = 0 while retries < max_retries: try: @@ -105,7 +104,7 @@ def make_request_with_retry( if client.email and client.password: client.login(client.email, client.password) - response = requests.request(method, url, **kwargs) + response = httpx.request(method, url, **kwargs) if is_recoverable_error(response): retries += 1 @@ -127,7 +126,7 @@ def make_request_with_retry( return response - except requests.exceptions.RequestException as exc: + except httpx.HTTPError as exc: if getattr(exc, "response", None) is not None: if exc.response is not None and not is_recoverable_error(exc.response): raise @@ -140,7 +139,7 @@ def make_request_with_retry( time.sleep(wait_time) continue - return requests.request(method, url, **kwargs) + return httpx.request(method, url, **kwargs) class DirectusClientProtocol(Protocol): @@ -181,9 +180,6 @@ def __init__( verify (bool): Whether to verify SSL certificates (default: False). """ self.verify = verify - if not self.verify: - urllib3.disable_warnings(category=InsecureRequestWarning) - self.url = url self.static_token: Optional[str] = None self.temporary_token: Optional[str] = None @@ -219,10 +215,11 @@ def login(self, email: Optional[str] = None, password: Optional[str] = None) -> self.email = email self.password = password - response = requests.post( + response = httpx.post( f"{self.url}/auth/login", json={"email": email, "password": password}, verify=self.verify, + timeout=30.0, ) auth_data = self._get_validated_auth_data(response) self.static_token = None @@ -241,16 +238,17 @@ def logout(self, refresh_token: Optional[str] = None) -> None: try: if refresh_token is None: refresh_token = self.refresh_token - response = requests.post( + response = httpx.post( f"{self.url}/auth/logout", headers={"Authorization": f"Bearer {self.get_token()}"}, json={"refresh_token": refresh_token}, verify=self.verify, + timeout=30.0, ) response.raise_for_status() self.temporary_token = None self.refresh_token = None - except requests.exceptions.HTTPError as exc: + except httpx.HTTPStatusError as exc: raise DirectusAuthError(f"Failed to logout from Directus API: {exc}") from exc def refresh(self, refresh_token: Optional[str] = None) -> dict: @@ -262,10 +260,11 @@ def refresh(self, refresh_token: Optional[str] = None) -> dict: """ if refresh_token is None: refresh_token = self.refresh_token - response = requests.post( + response = httpx.post( f"{self.url}/auth/refresh", json={"refresh_token": refresh_token, "mode": "json"}, verify=self.verify, + timeout=30.0, ) auth_data = self._get_validated_auth_data(response) self.temporary_token = auth_data["access_token"] @@ -330,7 +329,7 @@ def get(self, path: str, output_type: str = "json", **kwargs: Any) -> Any: if output_type == "csv": return data.text return data_json["data"] - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -355,7 +354,7 @@ def post(self, path: str, **kwargs: Any) -> Dict[str, Any]: raise AssertionError(response.text) return response.json() - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -382,7 +381,7 @@ def search(self, path: str, query: Optional[Dict[str, Any]] = None, **kwargs: An except Exception: # noqa: BLE001 - want best-effort fallback logger.exception("Failed to parse SEARCH response JSON") return {"error": "No data found for this request"} - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -405,7 +404,7 @@ def delete(self, path: str, **kwargs: Any) -> None: ) if response.status_code != 204: raise AssertionError(response.text) - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -431,7 +430,7 @@ def patch(self, path: str, **kwargs: Any) -> Dict[str, Any]: raise AssertionError(response.text) return response.json() - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -492,7 +491,7 @@ def retrieve_file(self, file_id: str, **kwargs: Any) -> Union[str, bytes]: if response.status_code != 200: raise AssertionError(response.text) return response.content - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -517,7 +516,7 @@ def download_file(self, file_id: str, file_path: str) -> None: raise AssertionError(response.text) with open(file_path, "wb") as file: file.write(response.content) - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -555,7 +554,7 @@ def download_photo( raise AssertionError(response.text) with open(file_path, "wb") as file: file.write(response.content) - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -632,7 +631,7 @@ def upload_file(self, file_path: str, data: Dict[str, Any] | None = None) -> Dic result = patched["data"] return result - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -842,7 +841,7 @@ def search_query( words = [query] return {"query": {"search": words}} - def _validate_auth_response(self, response: requests.Response) -> requests.Response: + def _validate_auth_response(self, response: httpx.Response) -> httpx.Response: """ Validate the authentication response from the Directus API. """ @@ -872,11 +871,11 @@ def _validate_auth_response(self, response: requests.Response) -> requests.Respo KeyError, ValueError, json.JSONDecodeError, - requests.exceptions.HTTPError, + httpx.HTTPStatusError, ) as exc: raise DirectusAuthError(f"Invalid response format from API: {exc}") from exc - def _get_validated_auth_data(self, response: requests.Response) -> dict: + def _get_validated_auth_data(self, response: httpx.Response) -> dict: """ Get the validated authentication data from the Directus API. """ @@ -953,7 +952,7 @@ def directus_client_context( yield active_client except DirectusGenericException: raise - except requests.exceptions.ConnectionError as exc: + except httpx.ConnectError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc diff --git a/echo/server/pyproject.toml b/echo/server/pyproject.toml index 0e0f294b..6c06fe5e 100644 --- a/echo/server/pyproject.toml +++ b/echo/server/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ # Config "python-dotenv==1.0.*", # Other + "httpx>=0.28", "backoff==2.2.*", "aiofiles==23.2.*", "sentry-sdk==2.2.1", From 0466d6f016401d3dd6dfe3e62edc1a0212f5d3bc Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 09:41:55 +0000 Subject: [PATCH 012/208] feat: add AsyncDirectusClient for non-blocking FastAPI endpoints New async Directus client using httpx.AsyncClient with connection pooling. Same API surface and return value semantics as the sync DirectusClient. Raises the same custom exceptions. Used by new workspace/org endpoints (Session 4). Existing sync code and Dramatiq workers continue using the sync client unchanged. Includes: retry logic with exponential backoff, lazy client init, send_mail() for workspace invite emails via Directus /utils/mail. --- echo/server/dembrane/directus_async.py | 299 +++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 echo/server/dembrane/directus_async.py diff --git a/echo/server/dembrane/directus_async.py b/echo/server/dembrane/directus_async.py new file mode 100644 index 00000000..feec00cf --- /dev/null +++ b/echo/server/dembrane/directus_async.py @@ -0,0 +1,299 @@ +""" +Async Directus client for FastAPI endpoints. + +Uses httpx.AsyncClient for non-blocking I/O. Same API surface and return +value semantics as the sync DirectusClient in directus.py. + +Usage: + from dembrane.directus_async import async_directus, create_async_directus_client + + # Module-level singleton (admin token) + items = await async_directus.get_items("collection", {"query": {"filter": {...}}}) + + # Per-request client (user token) + client = create_async_directus_client(token=user_jwt) + items = await client.get_items("collection", {"query": {"filter": {...}}}) + +Return value contract (matches sync client): + - create_item() returns {"data": {...}} — caller MUST unwrap with ["data"] + - get_items() returns list directly + - get_item() returns dict directly + - update_item() returns {"data": {...}} — caller MUST unwrap with ["data"] + - delete_item() returns None +""" + +from __future__ import annotations + +import asyncio +import json +import logging +from typing import Any +from urllib.parse import urljoin + +import httpx + +from dembrane.directus import ( + DirectusAuthError, + DirectusBadRequest, + DirectusGenericException, + DirectusServerError, +) +from dembrane.settings import get_settings + +logger = logging.getLogger(__name__) + +RECOVERABLE_STATUS_CODES = {401, 403, 408, 429, 500, 502, 503, 504} + +DEFAULT_TIMEOUT = 30.0 +DEFAULT_MAX_RETRIES = 3 +DEFAULT_RETRY_DELAY = 1.0 + + +class AsyncDirectusClient: + """Async Directus HTTP client using httpx.AsyncClient.""" + + def __init__( + self, + url: str, + token: str | None = None, + verify: bool = False, + ): + self.url = url.rstrip("/") + self.token = token or "" + self._client: httpx.AsyncClient | None = None + self._verify = verify + + def _get_client(self) -> httpx.AsyncClient: + """Lazy-init the httpx.AsyncClient. Reuses connection pool.""" + if self._client is None or self._client.is_closed: + self._client = httpx.AsyncClient( + base_url=self.url, + headers={"Authorization": f"Bearer {self.token}"}, + verify=self._verify, + timeout=DEFAULT_TIMEOUT, + ) + return self._client + + async def close(self) -> None: + """Close the underlying httpx client. Call on shutdown.""" + if self._client and not self._client.is_closed: + await self._client.aclose() + self._client = None + + # ------------------------------------------------------------------ + # Low-level request with retry + # ------------------------------------------------------------------ + + async def _request( + self, + method: str, + path: str, + *, + max_retries: int = DEFAULT_MAX_RETRIES, + retry_delay: float = DEFAULT_RETRY_DELAY, + **kwargs: Any, + ) -> httpx.Response: + """Make an HTTP request with retry logic for recoverable errors.""" + client = self._get_client() + retries = 0 + + while retries < max_retries: + try: + response = await client.request(method, path, **kwargs) + + if response.status_code in RECOVERABLE_STATUS_CODES: + retries += 1 + if retries == max_retries: + response.raise_for_status() + + wait_time = retry_delay * (2 ** (retries - 1)) + await asyncio.sleep(wait_time) + continue + + return response + + except httpx.HTTPError: + retries += 1 + if retries == max_retries: + raise + + wait_time = retry_delay * (2 ** (retries - 1)) + await asyncio.sleep(wait_time) + continue + + return await client.request(method, path, **kwargs) + + # ------------------------------------------------------------------ + # HTTP verbs (match sync client return semantics) + # ------------------------------------------------------------------ + + async def get(self, path: str, **kwargs: Any) -> Any: + """GET request. Returns response.json()["data"].""" + try: + response = await self._request("GET", path, **kwargs) + try: + data = response.json() + except json.JSONDecodeError: + return response.text + + if "errors" in data: + raise AssertionError(data["errors"]) + + return data["data"] + except httpx.ConnectError as exc: + raise DirectusServerError(exc) from exc + except AssertionError as exc: + raise DirectusBadRequest(exc) from exc + + async def post(self, path: str, **kwargs: Any) -> dict[str, Any]: + """POST request. Returns full response.json() (includes {"data": ...}).""" + try: + response = await self._request("POST", path, **kwargs) + if response.status_code not in (200, 201): + raise AssertionError(response.text) + return response.json() + except httpx.ConnectError as exc: + raise DirectusServerError(exc) from exc + except AssertionError as exc: + raise DirectusBadRequest(exc) from exc + + async def search(self, path: str, query: dict[str, Any] | None = None, **kwargs: Any) -> Any: + """SEARCH request. Returns response.json()["data"] (list).""" + try: + response = await self._request("SEARCH", path, json=query, **kwargs) + try: + return response.json()["data"] + except Exception: + logger.exception("Failed to parse SEARCH response JSON") + return {"error": "No data found for this request"} + except httpx.ConnectError as exc: + raise DirectusServerError(exc) from exc + except AssertionError as exc: + raise DirectusBadRequest(exc) from exc + + async def patch(self, path: str, **kwargs: Any) -> dict[str, Any]: + """PATCH request. Returns full response.json() (includes {"data": ...}).""" + try: + response = await self._request("PATCH", path, **kwargs) + if response.status_code not in (200, 204): + raise AssertionError(response.text) + return response.json() + except httpx.ConnectError as exc: + raise DirectusServerError(exc) from exc + except AssertionError as exc: + raise DirectusBadRequest(exc) from exc + + async def delete(self, path: str, **kwargs: Any) -> None: + """DELETE request. Returns None.""" + try: + response = await self._request("DELETE", path, **kwargs) + if response.status_code != 204: + raise AssertionError(response.text) + except httpx.ConnectError as exc: + raise DirectusServerError(exc) from exc + except AssertionError as exc: + raise DirectusBadRequest(exc) from exc + + # ------------------------------------------------------------------ + # Collection CRUD (match sync client method signatures exactly) + # ------------------------------------------------------------------ + + async def get_items( + self, collection: str, params: dict[str, Any] | None = None, **kwargs: Any + ) -> Any: + """Get items from a collection. Returns list directly. + + Usage: items = await client.get_items("coll", {"query": {"filter": {...}}}) + """ + return await self.search(f"/items/{collection}", query=params, **kwargs) + + async def get_item(self, collection: str, item_id: str, **kwargs: Any) -> Any: + """Get a single item. Returns dict directly.""" + return await self.get(f"/items/{collection}/{item_id}", **kwargs) + + async def create_item( + self, collection: str, data: dict[str, Any] | list[dict[str, Any]], **kwargs: Any + ) -> dict[str, Any]: + """Create item(s). Returns {"data": {...}} — caller MUST unwrap with ["data"].""" + return await self.post(f"/items/{collection}", json=data, **kwargs) + + async def update_item( + self, collection: str, item_id: str, data: dict[str, Any], **kwargs: Any + ) -> dict[str, Any]: + """Update an item. Returns {"data": {...}} — caller MUST unwrap with ["data"].""" + return await self.patch(f"/items/{collection}/{item_id}", json=data, **kwargs) + + async def delete_item(self, collection: str, item_id: str, **kwargs: Any) -> None: + """Delete an item. Returns None.""" + await self.delete(f"/items/{collection}/{item_id}", **kwargs) + + # ------------------------------------------------------------------ + # User operations + # ------------------------------------------------------------------ + + async def get_users(self, query: dict[str, Any] | None = None, **kwargs: Any) -> Any: + """Get users. Returns list directly.""" + return await self.search("/users", query=query, **kwargs) + + async def get_user(self, user_id: str, **kwargs: Any) -> Any: + """Get a single user. Returns dict directly.""" + return await self.get(f"/users/{user_id}", **kwargs) + + # ------------------------------------------------------------------ + # File operations + # ------------------------------------------------------------------ + + async def delete_file(self, file_id: str, **kwargs: Any) -> None: + """Delete a file.""" + await self.delete(f"/files/{file_id}", **kwargs) + + # ------------------------------------------------------------------ + # Mail (for workspace invites) + # ------------------------------------------------------------------ + + async def send_mail( + self, + to: str | list[str], + subject: str, + template_name: str, + template_data: dict[str, Any], + ) -> dict[str, Any]: + """Send email via Directus /utils/mail/send endpoint. + + Requires admin token. Uses Directus's configured email transport + (SendGrid SMTP) and Liquid templates from directus/templates/. + """ + return await self.post("/utils/mail/send", json={ + "to": to if isinstance(to, list) else [to], + "subject": subject, + "template": { + "name": template_name, + "data": template_data, + }, + }) + + +# --------------------------------------------------------------------------- +# Factory + singleton +# --------------------------------------------------------------------------- + +_settings = get_settings() +_DEFAULT_VERIFY = bool(getattr(_settings.directus, "verify_ssl", False)) + + +def create_async_directus_client( + *, + token: str | None = None, + verify: bool | None = None, +) -> AsyncDirectusClient: + """Factory for AsyncDirectusClient instances.""" + return AsyncDirectusClient( + url=_settings.directus.base_url, + token=token, + verify=verify if verify is not None else _DEFAULT_VERIFY, + ) + + +# Module-level admin client singleton. +# Used by workspace/org endpoints that need full access. +async_directus = create_async_directus_client(token=_settings.directus.token) From 35281fed8847e079410ade2ba2bea924c194166e Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 11:12:30 +0000 Subject: [PATCH 013/208] chore: remove usage_event collection Billing data lives in domain tables (conversation.duration, workspace_membership). Analytics tracked via PostHog. usage_event table is unnecessary complexity for now. The schema remains available in git history if needed later. --- echo/directus/sync/collections/policies.json | 7 ++- .../snapshot/collections/usage_event.json | 28 ----------- .../fields/usage_event/created_at.json | 46 ------------------- .../fields/usage_event/event_data.json | 46 ------------------- .../fields/usage_event/event_type.json | 44 ------------------ .../sync/snapshot/fields/usage_event/id.json | 46 ------------------- .../snapshot/fields/usage_event/org_id.json | 44 ------------------ .../fields/usage_event/project_id.json | 44 ------------------ .../snapshot/fields/usage_event/trace_id.json | 44 ------------------ .../snapshot/fields/usage_event/user_id.json | 44 ------------------ .../fields/usage_event/workspace_id.json | 44 ------------------ 11 files changed, 3 insertions(+), 434 deletions(-) delete mode 100644 echo/directus/sync/snapshot/collections/usage_event.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/created_at.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/event_data.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/event_type.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/id.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/org_id.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/project_id.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/trace_id.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/user_id.json delete mode 100644 echo/directus/sync/snapshot/fields/usage_event/workspace_id.json diff --git a/echo/directus/sync/collections/policies.json b/echo/directus/sync/collections/policies.json index 1dff0924..278746a9 100644 --- a/echo/directus/sync/collections/policies.json +++ b/echo/directus/sync/collections/policies.json @@ -10,11 +10,8 @@ "roles": [ { "role": "_sync_default_admin_role", + "user": null, "sort": 1 - }, - { - "role": null, - "sort": null } ], "_syncId": "_sync_default_admin_policy" @@ -41,6 +38,7 @@ "roles": [ { "role": "feebe863-90b1-41d1-a7ef-9694ddee3844", + "user": null, "sort": 1 } ], @@ -79,6 +77,7 @@ "roles": [ { "role": null, + "user": null, "sort": 1 } ], diff --git a/echo/directus/sync/snapshot/collections/usage_event.json b/echo/directus/sync/snapshot/collections/usage_event.json deleted file mode 100644 index 4877432e..00000000 --- a/echo/directus/sync/snapshot/collections/usage_event.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "collection": "usage_event", - "meta": { - "accountability": "all", - "archive_app_filter": true, - "archive_field": null, - "archive_value": null, - "collapse": "open", - "collection": "usage_event", - "color": null, - "display_template": null, - "group": null, - "hidden": false, - "icon": null, - "item_duplication_fields": null, - "note": "Append-only. Never updated. Never deleted.", - "preview_url": null, - "singleton": false, - "sort": null, - "sort_field": null, - "translations": null, - "unarchive_value": null, - "versioning": false - }, - "schema": { - "name": "usage_event" - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/created_at.json b/echo/directus/sync/snapshot/fields/usage_event/created_at.json deleted file mode 100644 index bb6e66e6..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/created_at.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "collection": "usage_event", - "field": "created_at", - "type": "timestamp", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "created_at", - "group": null, - "hidden": false, - "interface": "datetime", - "note": null, - "options": null, - "readonly": true, - "required": false, - "searchable": true, - "sort": 9, - "special": [ - "date-created" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "half" - }, - "schema": { - "name": "created_at", - "table": "usage_event", - "data_type": "timestamp with time zone", - "default_value": "CURRENT_TIMESTAMP", - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/event_data.json b/echo/directus/sync/snapshot/fields/usage_event/event_data.json deleted file mode 100644 index 2c1075e1..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/event_data.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "collection": "usage_event", - "field": "event_data", - "type": "json", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "event_data", - "group": null, - "hidden": false, - "interface": "input-code", - "note": "Always include \"v\": 1 for schema versioning", - "options": { - "language": "json" - }, - "readonly": false, - "required": false, - "searchable": true, - "sort": 8, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "event_data", - "table": "usage_event", - "data_type": "json", - "default_value": {}, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/event_type.json b/echo/directus/sync/snapshot/fields/usage_event/event_type.json deleted file mode 100644 index 26b69257..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/event_type.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "usage_event", - "field": "event_type", - "type": "string", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "event_type", - "group": null, - "hidden": false, - "interface": "input", - "note": null, - "options": null, - "readonly": false, - "required": true, - "searchable": true, - "sort": 7, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "event_type", - "table": "usage_event", - "data_type": "character varying", - "default_value": null, - "max_length": 255, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": false, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/id.json b/echo/directus/sync/snapshot/fields/usage_event/id.json deleted file mode 100644 index 1bfc4ab2..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/id.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "collection": "usage_event", - "field": "id", - "type": "uuid", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "id", - "group": null, - "hidden": true, - "interface": "input", - "note": null, - "options": null, - "readonly": true, - "required": false, - "searchable": true, - "sort": 1, - "special": [ - "uuid" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "id", - "table": "usage_event", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": false, - "is_unique": true, - "is_indexed": false, - "is_primary_key": true, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/org_id.json b/echo/directus/sync/snapshot/fields/usage_event/org_id.json deleted file mode 100644 index 7958ef29..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/org_id.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "usage_event", - "field": "org_id", - "type": "uuid", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "org_id", - "group": null, - "hidden": false, - "interface": "input", - "note": "Reference only, no FK constraint", - "options": null, - "readonly": false, - "required": false, - "searchable": true, - "sort": 3, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "org_id", - "table": "usage_event", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/project_id.json b/echo/directus/sync/snapshot/fields/usage_event/project_id.json deleted file mode 100644 index 6470733f..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/project_id.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "usage_event", - "field": "project_id", - "type": "uuid", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "project_id", - "group": null, - "hidden": false, - "interface": "input", - "note": "Reference only, no FK constraint", - "options": null, - "readonly": false, - "required": false, - "searchable": true, - "sort": 5, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "project_id", - "table": "usage_event", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/trace_id.json b/echo/directus/sync/snapshot/fields/usage_event/trace_id.json deleted file mode 100644 index 55eb8fdb..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/trace_id.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "usage_event", - "field": "trace_id", - "type": "string", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "trace_id", - "group": null, - "hidden": false, - "interface": "input", - "note": "Request correlation ID", - "options": null, - "readonly": false, - "required": false, - "searchable": true, - "sort": 2, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "trace_id", - "table": "usage_event", - "data_type": "character varying", - "default_value": null, - "max_length": 255, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/user_id.json b/echo/directus/sync/snapshot/fields/usage_event/user_id.json deleted file mode 100644 index 627067f6..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/user_id.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "usage_event", - "field": "user_id", - "type": "uuid", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "user_id", - "group": null, - "hidden": false, - "interface": "input", - "note": "Reference only, no FK constraint", - "options": null, - "readonly": false, - "required": false, - "searchable": true, - "sort": 6, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "user_id", - "table": "usage_event", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} diff --git a/echo/directus/sync/snapshot/fields/usage_event/workspace_id.json b/echo/directus/sync/snapshot/fields/usage_event/workspace_id.json deleted file mode 100644 index ea0554cf..00000000 --- a/echo/directus/sync/snapshot/fields/usage_event/workspace_id.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "collection": "usage_event", - "field": "workspace_id", - "type": "uuid", - "meta": { - "collection": "usage_event", - "conditions": null, - "display": null, - "display_options": null, - "field": "workspace_id", - "group": null, - "hidden": false, - "interface": "input", - "note": "Reference only, no FK constraint", - "options": null, - "readonly": false, - "required": false, - "searchable": true, - "sort": 4, - "special": null, - "translations": null, - "validation": null, - "validation_message": null, - "width": "full" - }, - "schema": { - "name": "workspace_id", - "table": "usage_event", - "data_type": "uuid", - "default_value": null, - "max_length": null, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null - } -} From 948890caabc86fd4d5ba323f0f8db5402fd94233 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:02:52 +0000 Subject: [PATCH 014/208] chore: add deleted_at to project_webhook Missed in Session 2 schema work. Needed for soft delete conversion of webhook deletion in Session 3. --- .../fields/project_webhook/deleted_at.json | 44 +++++++++++++++++++ echo/scripts/create_schema.py | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 echo/directus/sync/snapshot/fields/project_webhook/deleted_at.json diff --git a/echo/directus/sync/snapshot/fields/project_webhook/deleted_at.json b/echo/directus/sync/snapshot/fields/project_webhook/deleted_at.json new file mode 100644 index 00000000..99af9dd2 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project_webhook/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "project_webhook", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "project_webhook", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 12, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "project_webhook", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py index 61b6e30c..9f9f58fe 100644 --- a/echo/scripts/create_schema.py +++ b/echo/scripts/create_schema.py @@ -6,7 +6,7 @@ python scripts/create_schema.py --step 2 # org + org_membership python scripts/create_schema.py --step 3 # workspace + workspace_membership python scripts/create_schema.py --step 4 # workspace_invite + project_membership - python scripts/create_schema.py --step 5 # usage_event + python scripts/create_schema.py --step 5 # (removed — usage_event dropped) python scripts/create_schema.py --step 6 # add fields to project python scripts/create_schema.py --step 7 # add deleted_at to existing collections python scripts/create_schema.py --step 8 # remove legacy chat collection From bee6bfc952b5e89ebbf59f66150074c729945dd1 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:05:00 +0000 Subject: [PATCH 015/208] refactor: convert project delete to soft delete via BFF - New DELETE /api/projects/:id endpoint that PATCHes deleted_at instead of hard-deleting. Uses admin client, verifies ownership. - Updated frontend useDeleteProjectByIdMutation to call BFF endpoint instead of Directus SDK deleteItem("project"). - Added onError toast to the mutation (was missing before). - S3 audio files are preserved (no cascade deletion). --- .../src/components/project/hooks/index.ts | 9 ++++--- echo/frontend/src/lib/api.ts | 11 ++++++++ echo/server/dembrane/api/project.py | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/echo/frontend/src/components/project/hooks/index.ts b/echo/frontend/src/components/project/hooks/index.ts index c27742dc..5f7e76d9 100644 --- a/echo/frontend/src/components/project/hooks/index.ts +++ b/echo/frontend/src/components/project/hooks/index.ts @@ -22,6 +22,7 @@ import { cloneProjectById, createCustomVerificationTopic, deleteCustomVerificationTopic, + deleteProjectById, getLatestProjectAnalysisRunByProjectId, getVerificationTopics, type UpdateCustomTopicPayload, @@ -101,14 +102,16 @@ export const useTogglePinMutation = () => { export const useDeleteProjectByIdMutation = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (projectId: string) => - directus.request(deleteItem("project", projectId)), + mutationFn: (projectId: string) => deleteProjectById(projectId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"], }); queryClient.resetQueries(); - toast.success("Project deleted successfully"); + toast.success(t`Project deleted`); + }, + onError: (error: Error) => { + toast.error(error.message || t`Failed to delete project`); }, }); }; diff --git a/echo/frontend/src/lib/api.ts b/echo/frontend/src/lib/api.ts index e0e312b6..268f58b6 100644 --- a/echo/frontend/src/lib/api.ts +++ b/echo/frontend/src/lib/api.ts @@ -1611,6 +1611,17 @@ export const submitNotificationParticipant = async ( } }; +export const deleteProjectById = async (projectId: string) => { + try { + const response = await api.delete(`/projects/${projectId}`); + return response; + } catch (error: any) { + const message = + error?.response?.data?.detail || "Failed to delete project"; + throw new Error(message); + } +}; + export const deleteConversationById = async (conversationId: string) => { try { const response = await api.delete(`/conversations/${conversationId}`); diff --git a/echo/server/dembrane/api/project.py b/echo/server/dembrane/api/project.py index eff32588..d481c5af 100644 --- a/echo/server/dembrane/api/project.py +++ b/echo/server/dembrane/api/project.py @@ -288,6 +288,32 @@ async def create_project( return project +@ProjectRouter.delete("/{project_id}") +async def delete_project( + project_id: str, + auth: DependencyDirectusSession, +) -> dict: + """Soft-delete a project by setting deleted_at. + + The project and all its data (conversations, chats, etc.) are preserved + in the database. Read queries filter deleted_at IS NULL so the project + disappears from all views. S3 audio files are kept for the grace period. + """ + await _verify_project_access(auth, project_id) + + from dembrane.directus import directus + + await run_in_thread_pool( + directus.update_item, + "project", + project_id, + {"deleted_at": datetime.utcnow().isoformat()}, + ) + + logger.info(f"Soft-deleted project {project_id} by user {auth.user_id}") + return {"status": "success"} + + def _parse_iso_datetime(value: Any) -> datetime: if isinstance(value, datetime): return value From 6d23381c222734e368108f340dea5d2cf08ac3f9 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:05:51 +0000 Subject: [PATCH 016/208] refactor: convert conversation delete to soft delete - ConversationService.delete now PATCHes deleted_at instead of hard-deleting the conversation and its S3 audio files. - Audio files preserved for grace period recovery. - Conversation data stays in DB, excluded by deleted_at IS NULL filter. - The endpoint already goes through Python API, no frontend changes needed. --- echo/server/dembrane/api/conversation.py | 10 +++------- echo/server/dembrane/service/conversation.py | 21 +++++++++++--------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/echo/server/dembrane/api/conversation.py b/echo/server/dembrane/api/conversation.py index 7797a86e..95aba5cb 100644 --- a/echo/server/dembrane/api/conversation.py +++ b/echo/server/dembrane/api/conversation.py @@ -905,14 +905,10 @@ async def delete_conversation( auth: DependencyDirectusSession, ) -> dict: """ - Delete a conversation and its associated documents from RAG, Postgres, and Directus. + Soft-delete a conversation by setting deleted_at. - Args: - conversation_id: ID of the conversation to delete - auth: Authentication session to verify ownership - - Returns: - Dictionary with status info from Directus deletion + S3 audio files are preserved. The conversation data remains in the + database but is excluded from read queries via deleted_at IS NULL. """ await raise_if_conversation_not_found_or_not_authorized(conversation_id, auth) try: diff --git a/echo/server/dembrane/service/conversation.py b/echo/server/dembrane/service/conversation.py index 4a8ba4b3..c429badd 100644 --- a/echo/server/dembrane/service/conversation.py +++ b/echo/server/dembrane/service/conversation.py @@ -360,16 +360,19 @@ def delete( self, conversation_id: str, ) -> None: + """Soft-delete a conversation by setting deleted_at. + + S3 audio files are preserved for the grace period. + The conversation and all its data remain in the database + but are excluded from read queries via deleted_at IS NULL filter. + """ with self._client_context() as client: - conversation = self.get_by_id_or_raise(conversation_id, with_chunks=True) - for chunk in conversation["chunks"]: - try: - if chunk["path"]: - self.file_service.delete(chunk["path"]) - except Exception as e: - logger.exception(f"Error deleting chunk {chunk['id']} file: {e}") - - client.delete_item("conversation", conversation_id) + self.get_by_id_or_raise(conversation_id) + client.update_item( + "conversation", + conversation_id, + {"deleted_at": datetime.utcnow().isoformat()}, + ) def get_chunk_by_id_or_raise( self, From 43e2d1652ae09216faa53063cbffc0c5229324ab Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:07:08 +0000 Subject: [PATCH 017/208] refactor: convert project_chat delete to soft delete via BFF - New DELETE /api/chats/:id endpoint that PATCHes deleted_at instead of hard-deleting. Verifies chat ownership. - Updated frontend useDeleteChatMutation to call BFF endpoint instead of Directus SDK deleteItem("project_chat"). - Added onError toast and lingui translation to the mutation. --- .../src/components/chat/hooks/index.ts | 9 +++++++-- echo/frontend/src/lib/api.ts | 11 +++++++++++ echo/server/dembrane/api/chat.py | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/echo/frontend/src/components/chat/hooks/index.ts b/echo/frontend/src/components/chat/hooks/index.ts index 824cb87a..6b3844e2 100644 --- a/echo/frontend/src/components/chat/hooks/index.ts +++ b/echo/frontend/src/components/chat/hooks/index.ts @@ -7,6 +7,7 @@ import { readItems, updateItem, } from "@directus/sdk"; +import { t } from "@lingui/core/macro"; import { useMutation, useQuery, @@ -17,6 +18,7 @@ import { import { toast } from "@/components/common/Toaster"; import { type ChatMode, + deleteChatById, getChatHistory, getChatSuggestions, getProjectChatContext, @@ -69,7 +71,7 @@ export const useDeleteChatMutation = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: { chatId: string; projectId: string }) => - directus.request(deleteItem("project_chat", payload.chatId)), + deleteChatById(payload.chatId), onSuccess: (_, vars) => { queryClient.invalidateQueries({ queryKey: ["projects", vars.projectId, "chats"], @@ -77,7 +79,10 @@ export const useDeleteChatMutation = () => { queryClient.invalidateQueries({ queryKey: ["chats", vars.chatId], }); - toast.success("Chat deleted successfully"); + toast.success(t`Chat deleted`); + }, + onError: (error: Error) => { + toast.error(error.message || t`Failed to delete chat`); }, }); }; diff --git a/echo/frontend/src/lib/api.ts b/echo/frontend/src/lib/api.ts index 268f58b6..d7492be2 100644 --- a/echo/frontend/src/lib/api.ts +++ b/echo/frontend/src/lib/api.ts @@ -1611,6 +1611,17 @@ export const submitNotificationParticipant = async ( } }; +export const deleteChatById = async (chatId: string) => { + try { + const response = await api.delete(`/chats/${chatId}`); + return response; + } catch (error: any) { + const message = + error?.response?.data?.detail || "Failed to delete chat"; + throw new Error(message); + } +}; + export const deleteProjectById = async (projectId: string) => { try { const response = await api.delete(`/projects/${projectId}`); diff --git a/echo/server/dembrane/api/chat.py b/echo/server/dembrane/api/chat.py index a121ca1b..44920adf 100644 --- a/echo/server/dembrane/api/chat.py +++ b/echo/server/dembrane/api/chat.py @@ -161,6 +161,24 @@ async def raise_if_chat_not_found_or_not_authorized( return chat +@ChatRouter.delete("/{chat_id}") +async def delete_chat(chat_id: str, auth: DependencyDirectusSession) -> dict: + """Soft-delete a chat by setting deleted_at.""" + await raise_if_chat_not_found_or_not_authorized(chat_id, auth) + + from datetime import datetime + from dembrane.directus import directus + + await run_in_thread_pool( + directus.update_item, + "project_chat", + chat_id, + {"deleted_at": datetime.utcnow().isoformat()}, + ) + + return {"status": "success"} + + @ChatRouter.get("/{chat_id}/context", response_model=ChatContextSchema) async def get_chat_context(chat_id: str, auth: DependencyDirectusSession) -> ChatContextSchema: chat = await raise_if_chat_not_found_or_not_authorized( From 2b5e4e252d07a6d898766fa90c5a05953c954602 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:07:35 +0000 Subject: [PATCH 018/208] refactor: convert project_report delete to soft delete - DELETE /api/projects/:id/reports/:rid now PATCHes deleted_at instead of hard-deleting the report. - Report content preserved in DB. No frontend changes needed (endpoint already called via Python API). --- echo/server/dembrane/api/project.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/echo/server/dembrane/api/project.py b/echo/server/dembrane/api/project.py index d481c5af..9f8bf555 100644 --- a/echo/server/dembrane/api/project.py +++ b/echo/server/dembrane/api/project.py @@ -889,7 +889,7 @@ async def delete_report( report_id: int, auth: DependencyDirectusSession, ) -> dict: - """Delete a report permanently.""" + """Soft-delete a report by setting deleted_at.""" await _verify_project_access(auth, project_id) from dembrane.directus import directus @@ -903,9 +903,10 @@ async def delete_report( raise HTTPException(status_code=404, detail="Report not found") await run_in_thread_pool( - directus.delete_item, + directus.update_item, "project_report", str(report_id), + {"deleted_at": datetime.utcnow().isoformat()}, ) return {"deleted": True} From 54afb0bec4ee3cbe225d3e7a431f11e36f48d9ff Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:08:02 +0000 Subject: [PATCH 019/208] refactor: convert project_webhook delete to soft delete - DELETE /api/projects/:id/webhooks/:wid now PATCHes deleted_at instead of hard-deleting. No frontend changes needed. --- echo/server/dembrane/api/project_webhook.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/echo/server/dembrane/api/project_webhook.py b/echo/server/dembrane/api/project_webhook.py index 1b30b4c2..18b858bf 100644 --- a/echo/server/dembrane/api/project_webhook.py +++ b/echo/server/dembrane/api/project_webhook.py @@ -387,7 +387,7 @@ async def delete_webhook( webhook_id: str, auth: DependencyDirectusSession, ) -> None: - """Delete a webhook.""" + """Soft-delete a webhook by setting deleted_at.""" await _check_project_access(project_id, auth) from dembrane.directus import directus_client_context @@ -409,7 +409,11 @@ async def delete_webhook( if not existing: raise HTTPException(status_code=404, detail="Webhook not found") - client.delete_item("project_webhook", webhook_id) + client.update_item( + "project_webhook", + webhook_id, + {"deleted_at": datetime.utcnow().isoformat()}, + ) except HTTPException: raise From 25cb4e81e95e8d36a588b148f12bf8db2926c3ce Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:11:51 +0000 Subject: [PATCH 020/208] refactor: reroute tag deletes through Python API - New DELETE /api/projects/:id/tags/:tid endpoint (hard delete via admin client) - New POST /api/projects/:id/conversations/:cid/tags/delete endpoint for batch conversation-tag junction deletion (hard delete via admin client) - Updated frontend useDeleteTagByIdMutation to call BFF (now takes projectId) - Updated ProjectTagPill to pass projectId prop - Updated useUpdateConversationTagsMutation to call BFF instead of Directus SDK deleteItems - Removed unused deleteItems import from conversation hooks --- .../components/conversation/hooks/index.ts | 14 +++---- .../components/project/ProjectTagsInput.tsx | 8 +++- .../src/components/project/hooks/index.ts | 10 +++-- echo/frontend/src/lib/api.ts | 18 +++++++++ echo/server/dembrane/api/project.py | 38 +++++++++++++++++++ 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/echo/frontend/src/components/conversation/hooks/index.ts b/echo/frontend/src/components/conversation/hooks/index.ts index 76f9946c..c7fe7de8 100644 --- a/echo/frontend/src/components/conversation/hooks/index.ts +++ b/echo/frontend/src/components/conversation/hooks/index.ts @@ -1,7 +1,6 @@ import { aggregate, createItems, - deleteItems, type Query, type QueryFields, readItem, @@ -26,6 +25,7 @@ import { deleteChatContext, deleteConversationById, getConversationChunkContentLink, + deleteConversationTags, getConversationContentLink, getConversationEmails, getConversationTranscriptString, @@ -179,15 +179,13 @@ export const useUpdateConversationTagsMutation = () => { ), ); - // slightly esoteric, but basically we only want to delete if there are any tags to delete - // otherwise, directus doesn't accept an empty array + // Delete removed tags via BFF endpoint const deletePromise = needToDelete.length > 0 - ? directus.request( - deleteItems( - "conversation_project_tag", - needToDelete.map((tag) => tag.id), - ), + ? deleteConversationTags( + projectId, + conversationId, + needToDelete.map((tag) => tag.id), ) : Promise.resolve(); diff --git a/echo/frontend/src/components/project/ProjectTagsInput.tsx b/echo/frontend/src/components/project/ProjectTagsInput.tsx index 734ec78f..6aecceee 100644 --- a/echo/frontend/src/components/project/ProjectTagsInput.tsx +++ b/echo/frontend/src/components/project/ProjectTagsInput.tsx @@ -42,7 +42,10 @@ import { useUpdateProjectTagByIdMutation, } from "./hooks"; -export const ProjectTagPill = ({ tag }: { tag: ProjectTag }) => { +export const ProjectTagPill = ({ + tag, + projectId, +}: { tag: ProjectTag; projectId: string }) => { const deleteTagMutation = useDeleteTagByIdMutation(); const [confirmOpened, { open: openConfirm, close: closeConfirm }] = useDisclosure(false); @@ -115,7 +118,7 @@ export const ProjectTagPill = ({ tag }: { tag: ProjectTag }) => { confirmLabel={Delete} confirmColor="red" onConfirm={() => { - deleteTagMutation.mutate(tag.id); + deleteTagMutation.mutate({ tagId: tag.id, projectId }); closeConfirm(); }} /> @@ -298,6 +301,7 @@ export const ProjectTagsInput = (props: { project: Project }) => { ))} diff --git a/echo/frontend/src/components/project/hooks/index.ts b/echo/frontend/src/components/project/hooks/index.ts index 5f7e76d9..517a69e7 100644 --- a/echo/frontend/src/components/project/hooks/index.ts +++ b/echo/frontend/src/components/project/hooks/index.ts @@ -23,6 +23,7 @@ import { createCustomVerificationTopic, deleteCustomVerificationTopic, deleteProjectById, + deleteTagById, getLatestProjectAnalysisRunByProjectId, getVerificationTopics, type UpdateCustomTopicPayload, @@ -192,13 +193,16 @@ export const useDeleteTagByIdMutation = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (tagId: string) => - directus.request(deleteItem("project_tag", tagId)), + mutationFn: (payload: { tagId: string; projectId: string }) => + deleteTagById(payload.projectId, payload.tagId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"], }); - toast.success("Tag deleted successfully"); + toast.success(t`Tag deleted`); + }, + onError: (error: Error) => { + toast.error(error.message || t`Failed to delete tag`); }, }); }; diff --git a/echo/frontend/src/lib/api.ts b/echo/frontend/src/lib/api.ts index d7492be2..ba9018f0 100644 --- a/echo/frontend/src/lib/api.ts +++ b/echo/frontend/src/lib/api.ts @@ -1611,6 +1611,24 @@ export const submitNotificationParticipant = async ( } }; +export const deleteTagById = async ( + projectId: string, + tagId: string, +) => { + return api.delete(`/projects/${projectId}/tags/${tagId}`); +}; + +export const deleteConversationTags = async ( + projectId: string, + conversationId: string, + tagIds: number[], +) => { + return api.post( + `/projects/${projectId}/conversations/${conversationId}/tags/delete`, + { tag_ids: tagIds }, + ); +}; + export const deleteChatById = async (chatId: string) => { try { const response = await api.delete(`/chats/${chatId}`); diff --git a/echo/server/dembrane/api/project.py b/echo/server/dembrane/api/project.py index 9f8bf555..43aa3d0d 100644 --- a/echo/server/dembrane/api/project.py +++ b/echo/server/dembrane/api/project.py @@ -314,6 +314,44 @@ async def delete_project( return {"status": "success"} +@ProjectRouter.delete("/{project_id}/tags/{tag_id}") +async def delete_tag( + project_id: str, + tag_id: str, + auth: DependencyDirectusSession, +) -> dict: + """Delete a project tag (hard delete — no billing relevance).""" + await _verify_project_access(auth, project_id) + + from dembrane.directus import directus + + await run_in_thread_pool(directus.delete_item, "project_tag", tag_id) + return {"status": "success"} + + +class DeleteConversationTagsRequest(BaseModel): + tag_ids: List[int] + + +@ProjectRouter.post("/{project_id}/conversations/{conversation_id}/tags/delete") +async def delete_conversation_tags( + project_id: str, + conversation_id: str, + body: DeleteConversationTagsRequest, + auth: DependencyDirectusSession, +) -> dict: + """Delete conversation-tag junction records (hard delete).""" + await _verify_project_access(auth, project_id) + + from dembrane.directus import directus + + for tag_id in body.tag_ids: + await run_in_thread_pool( + directus.delete_item, "conversation_project_tag", str(tag_id) + ) + return {"status": "success", "deleted": len(body.tag_ids)} + + def _parse_iso_datetime(value: Any) -> datetime: if isinstance(value, datetime): return value From bb02811814d5ceb9a4ffe76b3501fc205ecca4a0 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:32:01 +0000 Subject: [PATCH 021/208] refactor: add deleted_at IS NULL filters to all read queries Ensures soft-deleted items are excluded from all list/search queries across 5 collections: project, conversation, project_chat, project_report, project_webhook. Python backend (29 queries): - service/conversation.py: _list_conversations, list_by_project_with_filters, get_recent_titles_for_project - service/chat.py: get_recent_user_queries - service/webhook.py: get_webhooks_for_project - api/project.py: pinned projects, paginated list, count aggregate, draft check, list reports, latest report, other published, conversation freshness - api/search.py: projects, conversations, chats - api/stats.py: projects, conversations - api/agentic.py: conversation list - api/project_webhook.py: list webhooks, copyable webhooks - conversation_utils.py: 3 scheduler queries (unfinished, needing flag, unsummarized) - report_generation.py: fetch conversations for report - reply_utils.py: adjacent conversations - summary_utils.py: conversations with summaries, all conversations for overview - tasks.py: scheduled reports check Frontend (8 queries): - conversation hooks: useConversationsByProjectId, useInfiniteConversationsByProjectId - chat hooks: useProjectChats, useInfiniteProjectChats - project hooks: useInfiniteProjects - report hooks: timeline reports, timeline conversations - api.ts: getProjectConversationCounts --- .../src/components/chat/hooks/index.ts | 6 +++++ .../components/conversation/hooks/index.ts | 6 +++++ .../src/components/project/hooks/index.ts | 4 ++++ .../src/components/report/hooks/index.ts | 2 ++ echo/frontend/src/lib/api.ts | 3 +++ echo/server/dembrane/api/agentic.py | 5 +++- echo/server/dembrane/api/project.py | 20 ++++++++++++---- echo/server/dembrane/api/project_webhook.py | 6 ++++- echo/server/dembrane/api/search.py | 24 ++++++++++++------- echo/server/dembrane/api/stats.py | 6 ++++- echo/server/dembrane/conversation_utils.py | 3 +++ echo/server/dembrane/reply_utils.py | 1 + echo/server/dembrane/report_generation.py | 5 +++- echo/server/dembrane/service/chat.py | 5 +++- echo/server/dembrane/service/conversation.py | 5 ++++ echo/server/dembrane/service/webhook.py | 1 + echo/server/dembrane/summary_utils.py | 6 ++++- echo/server/dembrane/tasks.py | 1 + 18 files changed, 89 insertions(+), 20 deletions(-) diff --git a/echo/frontend/src/components/chat/hooks/index.ts b/echo/frontend/src/components/chat/hooks/index.ts index 6b3844e2..956b8b53 100644 --- a/echo/frontend/src/components/chat/hooks/index.ts +++ b/echo/frontend/src/components/chat/hooks/index.ts @@ -182,6 +182,9 @@ export const useProjectChats = ( project_id: { _eq: projectId, }, + deleted_at: { + _null: true, + }, }, sort: "-date_created", ...query, @@ -219,6 +222,9 @@ export const useInfiniteProjectChats = ( project_id: { _eq: projectId, }, + deleted_at: { + _null: true, + }, ...(query?.filter && query.filter), }, limit: initialLimit, diff --git a/echo/frontend/src/components/conversation/hooks/index.ts b/echo/frontend/src/components/conversation/hooks/index.ts index c7fe7de8..c79a75e1 100644 --- a/echo/frontend/src/components/conversation/hooks/index.ts +++ b/echo/frontend/src/components/conversation/hooks/index.ts @@ -752,6 +752,9 @@ export const useConversationsByProjectId = ( project_id: { _eq: projectId, }, + deleted_at: { + _null: true, + }, ...(filterBySource && { source: { _in: filterBySource, @@ -971,6 +974,9 @@ export const useInfiniteConversationsByProjectId = ( project_id: { _eq: projectId, }, + deleted_at: { + _null: true, + }, ...(filterBySource && { source: { _in: filterBySource, diff --git a/echo/frontend/src/components/project/hooks/index.ts b/echo/frontend/src/components/project/hooks/index.ts index 517a69e7..57dc81cc 100644 --- a/echo/frontend/src/components/project/hooks/index.ts +++ b/echo/frontend/src/components/project/hooks/index.ts @@ -319,6 +319,10 @@ export const useInfiniteProjects = ({ const response = await directus.request( readItems("project", { ...query, + filter: { + ...((query as any)?.filter ?? {}), + deleted_at: { _null: true }, + }, limit: initialLimit, offset: pageParam * initialLimit, }), diff --git a/echo/frontend/src/components/report/hooks/index.ts b/echo/frontend/src/components/report/hooks/index.ts index 51442d5c..d097861a 100644 --- a/echo/frontend/src/components/report/hooks/index.ts +++ b/echo/frontend/src/components/report/hooks/index.ts @@ -212,6 +212,7 @@ export const useProjectReportTimelineData = (projectReportId: string) => { fields: ["id", "date_created"], filter: { project_id: { _eq: projectReport.project_id }, + deleted_at: { _null: true }, }, limit: 1000, sort: "date_created", @@ -229,6 +230,7 @@ export const useProjectReportTimelineData = (projectReportId: string) => { fields: ["id", "created_at"], filter: { project_id: { _eq: projectReport.project_id }, + deleted_at: { _null: true }, }, limit: 1000, }), diff --git a/echo/frontend/src/lib/api.ts b/echo/frontend/src/lib/api.ts index ba9018f0..b814f7cf 100644 --- a/echo/frontend/src/lib/api.ts +++ b/echo/frontend/src/lib/api.ts @@ -843,6 +843,9 @@ export const getProjectConversationCounts = async (projectId: string) => { project_id: { _eq: projectId, }, + deleted_at: { + _null: true, + }, }, }), ); diff --git a/echo/server/dembrane/api/agentic.py b/echo/server/dembrane/api/agentic.py index f3900a23..df5d7e65 100644 --- a/echo/server/dembrane/api/agentic.py +++ b/echo/server/dembrane/api/agentic.py @@ -319,7 +319,10 @@ def _list_project_conversations_for_agent( "conversations": conversations, } - conversation_filter: dict[str, Any] = {"project_id": {"_eq": project_id}} + conversation_filter: dict[str, Any] = { + "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, + } if normalized_conversation_id: conversation_filter["id"] = {"_eq": normalized_conversation_id} diff --git a/echo/server/dembrane/api/project.py b/echo/server/dembrane/api/project.py index 43aa3d0d..e87d2cb4 100644 --- a/echo/server/dembrane/api/project.py +++ b/echo/server/dembrane/api/project.py @@ -108,7 +108,7 @@ async def get_projects_home( # Fetch pinned projects (always, regardless of search) # Admins see only their own pins; non-admins see all (Directus permissions handle scoping) - pin_filter: dict[str, Any] = {"pin_order": {"_nnull": True}} + pin_filter: dict[str, Any] = {"pin_order": {"_nnull": True}, "deleted_at": {"_null": True}} if auth.is_admin: pin_filter["directus_user_id"] = {"_eq": auth.user_id} @@ -150,16 +150,19 @@ async def get_projects_home( } # Build query for paginated project list + base_filter: dict[str, Any] = {"deleted_at": {"_null": True}} + if owner_filter: + base_filter.update(owner_filter) + query: dict = { "fields": fields, "sort": ["-updated_at"], "limit": limit + 1, "offset": offset, + "filter": base_filter, } if text_search: query["search"] = text_search - if owner_filter: - query["filter"] = owner_filter projects_raw = await run_in_thread_pool( client.get_items, @@ -179,7 +182,7 @@ async def get_projects_home( count_result = await run_in_thread_pool( client.get_items, "project", - {"query": {"aggregate": {"count": ["id"]}}}, + {"query": {"aggregate": {"count": ["id"]}, "filter": {"deleted_at": {"_null": True}}}}, ) if isinstance(count_result, list) and len(count_result) > 0: total_count = int(count_result[0].get("count", {}).get("id", 0)) @@ -668,6 +671,7 @@ async def create_report( "filter": { "project_id": {"_eq": project_id}, "status": {"_eq": "draft"}, + "deleted_at": {"_null": True}, }, "limit": 1, } @@ -768,6 +772,7 @@ async def list_project_reports( "filter": { "project_id": {"_eq": project_id}, "status": {"_in": ["archived", "published", "scheduled", "draft"]}, + "deleted_at": {"_null": True}, }, "fields": [ "id", @@ -814,6 +819,7 @@ async def get_latest_report( "query": { "filter": { "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, }, "fields": [ "id", @@ -897,6 +903,7 @@ async def update_report( "project_id": {"_eq": project_id}, "status": {"_eq": "published"}, "id": {"_neq": report_id}, + "deleted_at": {"_null": True}, }, "fields": ["id"], "limit": -1, @@ -1083,7 +1090,10 @@ async def check_report_needs_update( "conversation", { "query": { - "filter": {"project_id": {"_eq": report.get("project_id")}}, + "filter": { + "project_id": {"_eq": report.get("project_id")}, + "deleted_at": {"_null": True}, + }, "fields": ["id", "created_at"], "sort": ["-created_at"], "limit": 1, diff --git a/echo/server/dembrane/api/project_webhook.py b/echo/server/dembrane/api/project_webhook.py index 18b858bf..f45e15f1 100644 --- a/echo/server/dembrane/api/project_webhook.py +++ b/echo/server/dembrane/api/project_webhook.py @@ -121,7 +121,10 @@ async def list_webhooks( "project_webhook", { "query": { - "filter": {"project_id": {"_eq": project_id}}, + "filter": { + "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, + }, "fields": [ "id", "name", @@ -183,6 +186,7 @@ async def list_copyable_webhooks( "filter": { "project_id": {"_neq": project_id}, "status": {"_eq": "published"}, + "deleted_at": {"_null": True}, }, "fields": [ "id", diff --git a/echo/server/dembrane/api/search.py b/echo/server/dembrane/api/search.py index 7f586b1d..d965acd2 100644 --- a/echo/server/dembrane/api/search.py +++ b/echo/server/dembrane/api/search.py @@ -190,7 +190,7 @@ def _search_projects(client: DirectusClient, term: str, limit: int) -> List[Sear "project", { "query": { - "filter": _search_like_filter("name", term), + "filter": {**_search_like_filter("name", term), "deleted_at": {"_null": True}}, "fields": ["id", "name", "updated_at", "count(conversations)"], "sort": ["-updated_at"], "limit": limit, @@ -214,11 +214,14 @@ def _search_conversations( { "query": { "filter": { - "_or": [ - _search_like_filter("participant_name", term), - _search_like_filter("participant_email", term), - _search_like_filter("summary", term), - _search_like_filter("id", term), + "_and": [ + {"deleted_at": {"_null": True}}, + {"_or": [ + _search_like_filter("participant_name", term), + _search_like_filter("participant_email", term), + _search_like_filter("summary", term), + _search_like_filter("id", term), + ]}, ] }, "fields": [ @@ -336,9 +339,12 @@ def _search_chats(client: DirectusClient, term: str, limit: int) -> List[SearchC { "query": { "filter": { - "_or": [ - _search_like_filter("name", term), - _search_like_filter("id", term), + "_and": [ + {"deleted_at": {"_null": True}}, + {"_or": [ + _search_like_filter("name", term), + _search_like_filter("id", term), + ]}, ] }, "fields": [ diff --git a/echo/server/dembrane/api/stats.py b/echo/server/dembrane/api/stats.py index 47f6bd08..f26c1878 100644 --- a/echo/server/dembrane/api/stats.py +++ b/echo/server/dembrane/api/stats.py @@ -72,6 +72,7 @@ def _fetch_projects(admin_user_ids: List[str]) -> List[dict[str, Any]]: filter_conditions.append( {"directus_user_id": {"_nin": admin_user_ids}}, ) + filter_conditions.append({"deleted_at": {"_null": True}}) return directus.get_items( "project", @@ -98,7 +99,10 @@ def _fetch_conversation_stats(project_ids: List[str]) -> tuple[int, float]: "conversation", { "query": { - "filter": {"project_id": {"_in": project_ids}}, + "filter": { + "project_id": {"_in": project_ids}, + "deleted_at": {"_null": True}, + }, "fields": ["duration"], "limit": -1, } diff --git a/echo/server/dembrane/conversation_utils.py b/echo/server/dembrane/conversation_utils.py index 8b0c40a9..fb5efa59 100644 --- a/echo/server/dembrane/conversation_utils.py +++ b/echo/server/dembrane/conversation_utils.py @@ -33,6 +33,7 @@ def collect_unfinished_conversations(limit: int = 100) -> List[str]: "filter": { # Must be unfinished "is_finished": False, + "deleted_at": {"_null": True}, # Must not have a chunk in the last 5 minutes :) "chunks": { "_none": { @@ -95,6 +96,7 @@ def collect_conversations_needing_transcribed_flag(limit: int = 50) -> List[str] "filter": { "is_finished": True, "is_all_chunks_transcribed": False, + "deleted_at": {"_null": True}, "created_at": { "_lte": (get_utc_timestamp() - timedelta(minutes=5)).isoformat() }, @@ -148,6 +150,7 @@ def collect_unsummarized_conversations(limit: int = 50) -> List[str]: "filter": { "is_all_chunks_transcribed": True, "summary": {"_null": True}, + "deleted_at": {"_null": True}, "created_at": { "_lte": (get_utc_timestamp() - timedelta(minutes=5)).isoformat() }, diff --git a/echo/server/dembrane/reply_utils.py b/echo/server/dembrane/reply_utils.py index 1bc35a94..f73ac290 100644 --- a/echo/server/dembrane/reply_utils.py +++ b/echo/server/dembrane/reply_utils.py @@ -241,6 +241,7 @@ async def generate_reply_for_conversation( "filter": { "id": {"_neq": current_conversation.id}, "project_id": {"_eq": conversation["project_id"]["id"]}, + "deleted_at": {"_null": True}, }, "fields": adjacent_fields, "deep": { diff --git a/echo/server/dembrane/report_generation.py b/echo/server/dembrane/report_generation.py index 9e822dc5..fcd13d68 100644 --- a/echo/server/dembrane/report_generation.py +++ b/echo/server/dembrane/report_generation.py @@ -51,7 +51,10 @@ def _fetch_conversations_sync(project_id: str, fields: list) -> list: "conversation", { "query": { - "filter": {"project_id": {"_eq": project_id}}, + "filter": { + "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, + }, "fields": fields, "sort": "-updated_at", }, diff --git a/echo/server/dembrane/service/chat.py b/echo/server/dembrane/service/chat.py index b2572f3f..113a1064 100644 --- a/echo/server/dembrane/service/chat.py +++ b/echo/server/dembrane/service/chat.py @@ -378,7 +378,10 @@ def list_recent_user_queries( "project_chat", { "query": { - "filter": {"project_id": {"_eq": project_id}}, + "filter": { + "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, + }, "fields": ["id"], "sort": "-date_created", "limit": 10, diff --git a/echo/server/dembrane/service/conversation.py b/echo/server/dembrane/service/conversation.py index c429badd..311b8360 100644 --- a/echo/server/dembrane/service/conversation.py +++ b/echo/server/dembrane/service/conversation.py @@ -177,6 +177,7 @@ def list_by_project_with_filters( # Build filter query filter_query: dict[str, Any] = { "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, } if tag_ids and len(tag_ids) > 0: @@ -692,6 +693,9 @@ def _list_conversations( ) deep["chunks"] = {"_sort": "timestamp"} + # Ensure soft-deleted conversations are excluded from list queries + filter_query["deleted_at"] = {"_null": True} + try: with self._client_context() as client: conversations: Optional[List[dict]] = client.get_items( @@ -760,6 +764,7 @@ def get_recent_titles_for_project(self, project_id: str, limit: int = 10) -> Lis "filter": { "project_id": {"_eq": project_id}, "title": {"_nnull": True}, + "deleted_at": {"_null": True}, }, "fields": ["title"], "sort": "-created_at", diff --git a/echo/server/dembrane/service/webhook.py b/echo/server/dembrane/service/webhook.py index 3528a1fc..b75d3cc7 100644 --- a/echo/server/dembrane/service/webhook.py +++ b/echo/server/dembrane/service/webhook.py @@ -74,6 +74,7 @@ def get_webhooks_for_project( "filter": { "project_id": {"_eq": project_id}, "status": {"_eq": "published"}, + "deleted_at": {"_null": True}, }, "fields": ["id", "name", "url", "secret", "events", "project_id"], } diff --git a/echo/server/dembrane/summary_utils.py b/echo/server/dembrane/summary_utils.py index 4b5c2276..6399f438 100644 --- a/echo/server/dembrane/summary_utils.py +++ b/echo/server/dembrane/summary_utils.py @@ -198,6 +198,7 @@ async def get_conversations_with_summaries( {"project_id": {"_eq": project_id}}, {"summary": {"_nnull": True}}, {"summary": {"_nempty": True}}, + {"deleted_at": {"_null": True}}, ] }, "fields": [ @@ -249,7 +250,10 @@ async def get_all_conversations_for_overview( "conversation", { "query": { - "filter": {"project_id": {"_eq": project_id}}, + "filter": { + "project_id": {"_eq": project_id}, + "deleted_at": {"_null": True}, + }, "fields": [ "id", "participant_name", diff --git a/echo/server/dembrane/tasks.py b/echo/server/dembrane/tasks.py index 14e39da9..a0696a17 100644 --- a/echo/server/dembrane/tasks.py +++ b/echo/server/dembrane/tasks.py @@ -1477,6 +1477,7 @@ def task_check_scheduled_reports() -> None: "filter": { "status": {"_eq": "scheduled"}, "scheduled_at": {"_lte": now}, + "deleted_at": {"_null": True}, }, "fields": ["id", "project_id", "language", "user_instructions", "scheduled_at"], "limit": 50, From 24b94f9fc53b505c00a1857d5c50dad9a58d940b Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:33:17 +0000 Subject: [PATCH 022/208] chore: remove DELETE permissions from Basic User policy Removes Directus DELETE permissions on 9 collections from the Basic User policy. All deletes now go through Python API endpoints which use the admin token for soft-delete (PATCH deleted_at). Collections affected: project, conversation, project_chat, project_chat_message, project_tag, conversation_project_tag, project_chat_conversation, conversation_chunk, conversation_artifact. This makes it physically impossible for non-admin users to hard-delete data via the Directus SDK or admin panel. All deletion must go through the controlled BFF endpoints. --- .../sync/collections/permissions.json | 210 ------------------ 1 file changed, 210 deletions(-) diff --git a/echo/directus/sync/collections/permissions.json b/echo/directus/sync/collections/permissions.json index 73946008..ab5e900f 100644 --- a/echo/directus/sync/collections/permissions.json +++ b/echo/directus/sync/collections/permissions.json @@ -415,30 +415,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "c974e57f-270b-469c-9265-5263a30ebf49" }, - { - "collection": "conversation_artifact", - "action": "delete", - "permissions": { - "_and": [ - { - "conversation_id": { - "project_id": { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "98f115f3-a7ac-40b4-8c2f-b4a5aa517c58" - }, { "collection": "conversation_artifact", "action": "read", @@ -511,30 +487,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "3539ba7c-1070-4e91-9e29-d9ac9b1eeb3e" }, - { - "collection": "conversation_chunk", - "action": "delete", - "permissions": { - "_and": [ - { - "conversation_id": { - "project_id": { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "b61ba7ee-1075-42ed-aaf4-a9e979ceeb36" - }, { "collection": "conversation_chunk", "action": "read", @@ -640,30 +592,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "562a8ec3-2134-41f8-bfaf-2d298d28b228" }, - { - "collection": "conversation_project_tag", - "action": "delete", - "permissions": { - "_and": [ - { - "conversation_id": { - "project_id": { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "8ed5e576-d46a-4a76-8935-0f710e4e6c25" - }, { "collection": "conversation_project_tag", "action": "read", @@ -807,28 +735,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "743b9372-711a-47cd-8d93-d3783bc0fae8" }, - { - "collection": "conversation", - "action": "delete", - "permissions": { - "_and": [ - { - "project_id": { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "5331eb25-dc93-4529-9c31-46444f2652e9" - }, { "collection": "conversation", "action": "read", @@ -1174,32 +1080,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "1099fee7-4468-4039-9e46-2321ef1d4147" }, - { - "collection": "project_chat_conversation", - "action": "delete", - "permissions": { - "_and": [ - { - "project_chat_id": { - "project_id": { - "directus_user_id": { - "id": { - "_eq": "$CURRENT_USER" - } - } - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "8d2becb9-aa42-4c8b-9e25-39b8d99d20fc" - }, { "collection": "project_chat_conversation", "action": "read", @@ -1374,32 +1254,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "d0508f5d-8c4a-4b61-883c-eba2ea207d17" }, - { - "collection": "project_chat_message", - "action": "delete", - "permissions": { - "_and": [ - { - "project_chat_id": { - "project_id": { - "directus_user_id": { - "id": { - "_eq": "$CURRENT_USER" - } - } - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "a429339c-08bf-47b0-8fb5-1076d1ab42d0" - }, { "collection": "project_chat_message", "action": "read", @@ -1474,28 +1328,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "7902af81-50fe-4e50-bacc-c78409088eec" }, - { - "collection": "project_chat", - "action": "delete", - "permissions": { - "_and": [ - { - "project_id": { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "fb7c57f6-129f-4803-8f02-5deb6818bf46" - }, { "collection": "project_chat", "action": "read", @@ -1745,28 +1577,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "bf938907-c824-41a6-aec1-de49350351b8" }, - { - "collection": "project_tag", - "action": "delete", - "permissions": { - "_and": [ - { - "project_id": { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "a52b787f-707e-407e-b343-558656405d04" - }, { "collection": "project_tag", "action": "read", @@ -1919,26 +1729,6 @@ "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", "_syncId": "822be74a-a55d-4745-8bb5-f0030bd62f41" }, - { - "collection": "project", - "action": "delete", - "permissions": { - "_and": [ - { - "directus_user_id": { - "_eq": "$CURRENT_USER" - } - } - ] - }, - "validation": null, - "presets": null, - "fields": [ - "*" - ], - "policy": "37a60e48-dd00-4867-af07-1fb22ac89078", - "_syncId": "96f78d3f-ae30-4268-beb1-ea8d7ea277f9" - }, { "collection": "project", "action": "read", From 35a08140839dbff48dfb466412a9787c033b6ad9 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 12:36:49 +0000 Subject: [PATCH 023/208] feat: add PostHog analytics integration to frontend Adds PostHog SDK for frontend analytics and event tracking. Instruments key user flows: auth, project creation, chat, report generation, and file uploads. --- echo/frontend/package.json | 2 + echo/frontend/pnpm-lock.yaml | 304 ++++++++++++++++++ echo/frontend/posthog-setup-report.md | 33 ++ .../src/components/auth/hooks/index.ts | 11 +- .../dropzone/UploadConversationDropzone.tsx | 9 +- .../components/report/CreateReportForm.tsx | 16 +- echo/frontend/src/main.tsx | 11 +- echo/frontend/src/routes/auth/Login.tsx | 10 + echo/frontend/src/routes/auth/Register.tsx | 9 + .../src/routes/project/ProjectsHome.tsx | 4 + .../routes/project/chat/ProjectChatRoute.tsx | 76 ++--- echo/server/uv.lock | 2 + 12 files changed, 440 insertions(+), 47 deletions(-) create mode 100644 echo/frontend/posthog-setup-report.md diff --git a/echo/frontend/package.json b/echo/frontend/package.json index ef384bfe..0bf0701b 100644 --- a/echo/frontend/package.json +++ b/echo/frontend/package.json @@ -40,6 +40,7 @@ "@mantine/notifications": "^7.17.8", "@mdxeditor/editor": "^3.40.0", "@phosphor-icons/react": "^2.1.10", + "@posthog/react": "^1.9.0", "@react-pdf/renderer": "^4.3.0", "@sentry/react": "^8.55.0", "@tabler/icons-react": "^3.34.1", @@ -69,6 +70,7 @@ "motion": "^11.18.2", "next-themes": "^0.4.6", "plausible-tracker": "^0.3.9", + "posthog-js": "^1.367.0", "re-resizable": "^6.11.2", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/echo/frontend/pnpm-lock.yaml b/echo/frontend/pnpm-lock.yaml index d8aeb4d5..9d707266 100644 --- a/echo/frontend/pnpm-lock.yaml +++ b/echo/frontend/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@posthog/react': + specifier: ^1.9.0 + version: 1.9.0(@types/react@19.0.12)(posthog-js@1.367.0)(react@19.0.0) '@react-pdf/renderer': specifier: ^4.3.0 version: 4.3.0(react@19.0.0) @@ -164,6 +167,9 @@ importers: plausible-tracker: specifier: ^0.3.9 version: 0.3.9 + posthog-js: + specifier: ^1.367.0 + version: 1.367.0 re-resizable: specifier: ^6.11.2 version: 6.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -1597,10 +1603,78 @@ packages: '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.6.1': + resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-http@0.208.0': + resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.208.0': + resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.208.0': + resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.6.1': + resolution: {integrity: sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.208.0': + resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + '@phosphor-icons/react@2.1.10': resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==} engines: {node: '>=10'} @@ -1612,6 +1686,22 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@posthog/core@1.25.2': + resolution: {integrity: sha512-h2FO7ut/BbfwpAXWpwdDHTzQgUo9ibDFEs6ZO+3cI3KPWQt5XwczK1OLAuPprcjm8T/jl0SH8jSFo5XdU4RbTg==} + + '@posthog/react@1.9.0': + resolution: {integrity: sha512-lVdTsWT5+PtHBu44gSQ7QohbLjAYqHkFAIGAQ+HV8Eh9yj+OcnQ7mXCmyhaMlTBD3z7D0H1eWMp4vQaFnsIyWQ==} + peerDependencies: + '@types/react': '>=16.8.0' + posthog-js: '>=1.257.2' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@posthog/types@1.367.0': + resolution: {integrity: sha512-FUcTEAeKhuHKyCcTQPx/sTN3s8S+PusPsiP8T/LrG/T7pDkwMfNZG0/P630JX6fT6qiW0moVvVSsaXgZDJF7wg==} + '@preact/signals-core@1.14.0': resolution: {integrity: sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==} @@ -1620,6 +1710,36 @@ packages: peerDependencies: preact: 10.x + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/colors@3.0.0': resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} @@ -2442,6 +2562,9 @@ packages: '@types/showdown@2.0.6': resolution: {integrity: sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2774,6 +2897,9 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -3015,6 +3141,9 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dompurify@3.3.3: + resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} @@ -3174,6 +3303,9 @@ packages: picomatch: optional: true + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -3576,6 +3708,9 @@ packages: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -4116,6 +4251,9 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + posthog-js@1.367.0: + resolution: {integrity: sha512-jWNwB8XjlVUC9PbGaIlmsyohUDMBrwf7cvLuOY3lIOmWVO3L6VxTE3GZShjxpFKQtmWcPxFbf1hcbct1YCb6xg==} + preact@10.29.0: resolution: {integrity: sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==} @@ -4142,6 +4280,10 @@ packages: property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -4161,6 +4303,9 @@ packages: qrcode-generator@1.4.4: resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==} + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4890,6 +5035,9 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-vitals@5.2.0: + resolution: {integrity: sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -6495,8 +6643,82 @@ snapshots: '@open-draft/deferred-promise@2.2.0': {} + '@opentelemetry/api-logs@0.208.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/semantic-conventions@1.40.0': {} + '@phosphor-icons/react@2.1.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: react: 19.0.0 @@ -6505,6 +6727,17 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@posthog/core@1.25.2': {} + + '@posthog/react@1.9.0(@types/react@19.0.12)(posthog-js@1.367.0)(react@19.0.0)': + dependencies: + posthog-js: 1.367.0 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.12 + + '@posthog/types@1.367.0': {} + '@preact/signals-core@1.14.0': {} '@preact/signals@1.3.4(preact@10.29.0)': @@ -6512,6 +6745,29 @@ snapshots: '@preact/signals-core': 1.14.0 preact: 10.29.0 + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@radix-ui/colors@3.0.0': {} '@radix-ui/number@1.1.1': {} @@ -7314,6 +7570,9 @@ snapshots: '@types/showdown@2.0.6': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -7643,6 +7902,8 @@ snapshots: cookie@1.0.2: {} + core-js@3.49.0: {} + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -7893,6 +8154,10 @@ snapshots: '@babel/runtime': 7.27.0 csstype: 3.1.3 + dompurify@3.3.3: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dotenv@16.4.7: {} downshift@7.6.2(react@19.0.0): @@ -8123,6 +8388,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.4.8: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -8519,6 +8786,8 @@ snapshots: chalk: 5.4.1 is-unicode-supported: 1.3.0 + long@5.3.2: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -9317,6 +9586,22 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + posthog-js@1.367.0: + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@posthog/core': 1.25.2 + '@posthog/types': 1.367.0 + core-js: 3.49.0 + dompurify: 3.3.3 + fflate: 0.4.8 + preact: 10.29.0 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.2.0 + preact@10.29.0: {} prebuild-install@7.1.3: @@ -9356,6 +9641,21 @@ snapshots: property-information@7.0.0: {} + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.13.14 + long: 5.3.2 + proxy-from-env@2.1.0: {} pseudolocale@2.1.0: @@ -9372,6 +9672,8 @@ snapshots: qrcode-generator@1.4.4: {} + query-selector-shadow-dom@1.0.1: {} + queue-microtask@1.2.3: {} queue@6.0.2: @@ -10189,6 +10491,8 @@ snapshots: dependencies: defaults: 1.0.4 + web-vitals@5.2.0: {} + webidl-conversions@4.0.2: {} webpack-virtual-modules@0.6.2: diff --git a/echo/frontend/posthog-setup-report.md b/echo/frontend/posthog-setup-report.md new file mode 100644 index 00000000..f104fd6b --- /dev/null +++ b/echo/frontend/posthog-setup-report.md @@ -0,0 +1,33 @@ + +# PostHog post-wizard report + +The wizard has completed a deep integration of PostHog analytics into the Echo/Dembrane frontend. PostHog (`posthog-js` + `@posthog/react`) was installed, initialized in `src/main.tsx`, and the app wrapped with `PostHogProvider`. User identification via `posthog.identify()` is called on login and registration. Session reset via `posthog.reset()` is called on logout. Nine business-critical events are now tracked across six files. + +| Event | Description | File | +|---|---|---| +| `user_logged_in` | Host successfully logs in; calls `posthog.identify(email)` | `src/routes/auth/Login.tsx` | +| `user_login_failed` | Host login attempt fails (wrong password, invalid OTP, etc.) | `src/routes/auth/Login.tsx` | +| `user_registered` | Host submits the registration form; calls `posthog.identify(email)` | `src/routes/auth/Register.tsx` | +| `user_logged_out` | Host logs out; calls `posthog.reset()` | `src/components/auth/hooks/index.ts` | +| `project_created` | Host creates a new project from the projects home page | `src/routes/project/ProjectsHome.tsx` | +| `chat_mode_selected` | Host selects a chat mode (overview, deep_dive, agentic) | `src/routes/project/chat/ProjectChatRoute.tsx` | +| `chat_message_sent` | Host sends a message in the chat interface | `src/routes/project/chat/ProjectChatRoute.tsx` | +| `report_generated` | Host triggers report generation (immediate or scheduled) | `src/components/report/CreateReportForm.tsx` | +| `conversation_upload_started` | Host begins uploading conversation audio files | `src/components/dropzone/UploadConversationDropzone.tsx` | + +## Next steps + +We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented: + +- **Dashboard**: [Analytics basics](https://eu.posthog.com/project/160282/dashboard/625219) +- **Insight**: [User Login & Registration Funnel](https://eu.posthog.com/project/160282/insights/sfG1jkEN) — conversion from registration to first login +- **Insight**: [Daily Active Users (Logins)](https://eu.posthog.com/project/160282/insights/58s1MgzV) — daily unique users logging in +- **Insight**: [Project & Report Creation Trend](https://eu.posthog.com/project/160282/insights/h9dm8arV) — weekly project creation and report generation activity +- **Insight**: [Chat Engagement Funnel](https://eu.posthog.com/project/160282/insights/jMr58R6h) — funnel from chat mode selection to first message sent +- **Insight**: [Conversation Upload Trend](https://eu.posthog.com/project/160282/insights/ofstTlUa) — weekly conversation audio uploads + +### Agent skill + +We've left an agent skill folder in your project. You can use this context for further agent development when using Claude Code. This will help ensure the model provides the most up-to-date approaches for integrating PostHog. + + diff --git a/echo/frontend/src/components/auth/hooks/index.ts b/echo/frontend/src/components/auth/hooks/index.ts index bf6a2b4c..45e54b5a 100644 --- a/echo/frontend/src/components/auth/hooks/index.ts +++ b/echo/frontend/src/components/auth/hooks/index.ts @@ -4,6 +4,7 @@ import { registerUser, registerUserVerify, } from "@directus/sdk"; +import { usePostHog } from "@posthog/react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; import { useLocation, useSearchParams } from "react-router"; @@ -22,10 +23,9 @@ export const useCurrentUser = ({ enabled, queryFn: async () => { try { - const response = await fetch( - `${API_BASE_URL}/user-settings/me`, - { credentials: "include" }, - ); + const response = await fetch(`${API_BASE_URL}/user-settings/me`, { + credentials: "include", + }); if (!response.ok) return null; return response.json(); } catch (_error) { @@ -181,6 +181,7 @@ export const useLoginMutation = () => { export const useLogoutMutation = () => { const queryClient = useQueryClient(); const navigate = useI18nNavigate(); + const posthog = usePostHog(); return useMutation({ mutationFn: async ({ @@ -219,6 +220,8 @@ export const useLogoutMutation = () => { queryClient.invalidateQueries({ queryKey: ["auth", "session"] }); }, onSuccess: (_data, { next, reason, doRedirect }) => { + posthog?.capture("user_logged_out"); + posthog?.reset(); if (doRedirect) { navigate( "/login" + diff --git a/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx b/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx index 262b12b2..2f108930 100644 --- a/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx +++ b/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx @@ -15,6 +15,7 @@ import { Tooltip, } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; +import { usePostHog } from "@posthog/react"; import { IconAlertCircle, IconArrowRight, @@ -253,6 +254,7 @@ export const UploadConversationDropzone = ( ) => { // Modal state const [opened, { open, close }] = useDisclosure(false); + const posthog = usePostHog(); // File selection and upload tracking state const [selectedFiles, setSelectedFiles] = useState([]); @@ -477,6 +479,11 @@ export const UploadConversationDropzone = ( const handleUpload = useCallback(() => { if (selectedFiles.length === 0) return; + posthog?.capture("conversation_upload_started", { + file_count: selectedFiles.length, + project_id: props.projectId, + }); + // Start the upload process using our uploader hook uploader.uploadFiles({ chunks: selectedFiles, @@ -487,7 +494,7 @@ export const UploadConversationDropzone = ( tagIdList: [], timestamps: selectedFiles.map(() => new Date()), }); - }, [selectedFiles, props.projectId, uploader]); + }, [selectedFiles, props.projectId, uploader, posthog]); if (projectQuery.isLoading) { return ; diff --git a/echo/frontend/src/components/report/CreateReportForm.tsx b/echo/frontend/src/components/report/CreateReportForm.tsx index 8a4c1a0f..6943aa14 100644 --- a/echo/frontend/src/components/report/CreateReportForm.tsx +++ b/echo/frontend/src/components/report/CreateReportForm.tsx @@ -14,10 +14,7 @@ import { Text, Tooltip, } from "@mantine/core"; -import { - isDateFarEnough, - ScheduleDateTimePicker, -} from "./ScheduleDateTimePicker"; +import { usePostHog } from "@posthog/react"; import { IconArrowLeft, IconClock, @@ -37,6 +34,10 @@ import { languageOptionsByIso639_1 } from "../language/LanguagePicker"; import { ConversationStatusTable } from "./ConversationStatusTable"; import { useCreateProjectReportMutation } from "./hooks"; import { ReportFocusSelector } from "./ReportFocusSelector"; +import { + isDateFarEnough, + ScheduleDateTimePicker, +} from "./ScheduleDateTimePicker"; function getLanguageLabel(iso: string): string { return languageOptionsByIso639_1.find((o) => o.value === iso)?.label ?? iso; @@ -78,6 +79,7 @@ export const CreateReportForm = ({ onSuccess }: { onSuccess: () => void }) => { const [detailModalOpened, setDetailModalOpened] = useState(false); const [showSchedule, setShowSchedule] = useState(false); const [scheduledDate, setScheduledDate] = useState(null); + const posthog = usePostHog(); const hasConversations = conversationCounts && conversationCounts.total > 0; const hasFinishedConversations = @@ -88,6 +90,12 @@ export const CreateReportForm = ({ onSuccess }: { onSuccess: () => void }) => { error instanceof AxiosError && error.response?.status === 409; const handleCreate = (schedule?: boolean) => { + posthog?.capture("report_generated", { + has_user_instructions: !!userInstructions, + language, + project_id: projectId, + scheduled: !!schedule, + }); mutate( { language, diff --git a/echo/frontend/src/main.tsx b/echo/frontend/src/main.tsx index 678eb99f..430df0a2 100644 --- a/echo/frontend/src/main.tsx +++ b/echo/frontend/src/main.tsx @@ -3,13 +3,20 @@ import ReactDOM from "react-dom/client"; import { App } from "./App"; import "./index.css"; +import { PostHogProvider } from "@posthog/react"; import * as Sentry from "@sentry/react"; +import posthog from "posthog-js"; import { BUILD_VERSION, DISABLE_SENTRY, USE_PARTICIPANT_ROUTER, } from "./config"; +posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN, { + api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + defaults: "2026-01-30", +}); + const sentryCommonOpts: Partial = { integrations: [ Sentry.browserTracingIntegration(), @@ -64,6 +71,8 @@ if (root === null) { ReactDOM.createRoot(root).render( - + + + , ); diff --git a/echo/frontend/src/routes/auth/Login.tsx b/echo/frontend/src/routes/auth/Login.tsx index c51a62d9..36904ae5 100644 --- a/echo/frontend/src/routes/auth/Login.tsx +++ b/echo/frontend/src/routes/auth/Login.tsx @@ -15,6 +15,7 @@ import { Title, } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; +import { usePostHog } from "@posthog/react"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { useSearchParams } from "react-router"; @@ -79,6 +80,7 @@ export const LoginRoute = () => { const [formParent] = useAutoAnimate(); const pinInputRef = useRef(null); const loginMutation = useLoginMutation(); + const posthog = usePostHog(); const submitLogin = async (data: { email: string; @@ -103,6 +105,9 @@ export const LoginRoute = () => { password: data.password, }); + posthog?.identify(data.email); + posthog?.capture("user_logged_in", { email: data.email }); + const isNewUser = searchParams.get("new") === "true"; const next = searchParams.get("next"); const transitionPromise = runTransition({ @@ -135,6 +140,11 @@ export const LoginRoute = () => { ? firstError.message : undefined; + posthog?.capture("user_login_failed", { + email: data.email, + error_code: code, + }); + if (code === "INVALID_OTP") { setOtpRequired(true); if (trimmedOtp && trimmedOtp.length > 0) { diff --git a/echo/frontend/src/routes/auth/Register.tsx b/echo/frontend/src/routes/auth/Register.tsx index 0fa3a356..52b63877 100644 --- a/echo/frontend/src/routes/auth/Register.tsx +++ b/echo/frontend/src/routes/auth/Register.tsx @@ -12,6 +12,7 @@ import { Title, } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; +import { usePostHog } from "@posthog/react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useRegisterMutation } from "@/components/auth/hooks"; @@ -32,6 +33,7 @@ export const RegisterRoute = () => { const [error, setError] = useState(""); const registerMutation = useRegisterMutation(); + const posthog = usePostHog(); const onSubmit = handleSubmit(async (data) => { if (data.password !== data.confirmPassword) { @@ -39,6 +41,13 @@ export const RegisterRoute = () => { return; } + posthog?.identify(data.email); + posthog?.capture("user_registered", { + email: data.email, + first_name: data.first_name, + last_name: data.last_name, + }); + registerMutation.mutate([ data.email, data.password, diff --git a/echo/frontend/src/routes/project/ProjectsHome.tsx b/echo/frontend/src/routes/project/ProjectsHome.tsx index 96fe6514..1906033e 100644 --- a/echo/frontend/src/routes/project/ProjectsHome.tsx +++ b/echo/frontend/src/routes/project/ProjectsHome.tsx @@ -16,6 +16,7 @@ import { Title, } from "@mantine/core"; import { useDebouncedValue, useDocumentTitle } from "@mantine/hooks"; +import { usePostHog } from "@posthog/react"; import { IconInfoCircle, IconSearch, IconX } from "@tabler/icons-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useInView } from "react-intersection-observer"; @@ -85,6 +86,7 @@ export const ProjectsHomeRoute = () => { const createProjectMutation = useCreateProjectMutation(); const updateProjectMutation = useUpdateProjectByIdMutation(); const user = useCurrentUser(); + const posthog = usePostHog(); const { language } = useLanguage(); @@ -103,6 +105,8 @@ export const ProjectsHomeRoute = () => { image_generation_model: "MODEST", }, }); + + posthog?.capture("project_created", { project_id: project.id }); navigate(`/projects/${project.id}/overview`); }; diff --git a/echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx b/echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx index f8bbc473..5cada80b 100644 --- a/echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx +++ b/echo/frontend/src/routes/project/chat/ProjectChatRoute.tsx @@ -14,6 +14,7 @@ import { Title, } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; +import { usePostHog } from "@posthog/react"; import { ErrorBoundary } from "@sentry/react"; import { IconAlertCircle, @@ -24,6 +25,7 @@ import { import { useQueryClient } from "@tanstack/react-query"; import { useEffect, useMemo, useRef, useState } from "react"; import { useParams } from "react-router"; +import { useCurrentUser } from "@/components/auth/hooks"; import { AgenticChatPanel } from "@/components/chat/AgenticChatPanel"; import { ChatAccordionItemMenu, @@ -50,7 +52,18 @@ import { useChat as useProjectChat, useProjectChatContext, } from "@/components/chat/hooks"; +import { + useCreateUserTemplate, + useDeleteUserTemplate, + useQuickAccessPreferences, + useSaveQuickAccessPreferences, + useToggleAiSuggestions, + useUpdateUserTemplate, + useUserTemplates, +} from "@/components/chat/hooks/useUserTemplates"; import SourcesSearch from "@/components/chat/SourcesSearch"; +import type { QuickAccessItem } from "@/components/chat/templateKey"; +import { Templates } from "@/components/chat/templates"; import { CopyRichTextIconButton } from "@/components/common/CopyRichTextIconButton"; import { Logo } from "@/components/common/Logo"; import { ScrollToBottomButton } from "@/components/common/ScrollToBottom"; @@ -62,18 +75,6 @@ import { ENABLE_AGENTIC_CHAT, ENABLE_CHAT_AUTO_SELECT, } from "@/config"; -import { useCurrentUser } from "@/components/auth/hooks"; -import type { QuickAccessItem } from "@/components/chat/templateKey"; -import { - useCreateUserTemplate, - useDeleteUserTemplate, - useQuickAccessPreferences, - useSaveQuickAccessPreferences, - useToggleAiSuggestions, - useUpdateUserTemplate, - useUserTemplates, -} from "@/components/chat/hooks/useUserTemplates"; -import { Templates } from "@/components/chat/templates"; import { useElementOnScreen } from "@/hooks/useElementOnScreen"; import { useLanguage } from "@/hooks/useLanguage"; import { useLoadNotification } from "@/hooks/useLoadNotification"; @@ -82,6 +83,7 @@ import { testId } from "@/lib/testUtils"; const useDembraneChat = ({ chatId }: { chatId: string }) => { const chatHistoryQuery = useChatHistory(chatId); const chatContextQuery = useProjectChatContext(chatId); + const posthog = usePostHog(); const [templateKey, setTemplateKey] = useState(null); const [showProgress, setShowProgress] = useState(false); @@ -230,6 +232,11 @@ const useDembraneChat = ({ chatId }: { chatId: string }) => { // Submit the chat handleSubmit(); + posthog?.capture("chat_message_sent", { + chat_id: chatId, + template_key: templateKey, + }); + // Scroll to bottom when user submits a message setTimeout(() => { lastMessageRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -311,12 +318,15 @@ export const ProjectChatRoute = () => { useDocumentTitle(t`Chat | Dembrane`); const { chatId, projectId } = useParams(); + const posthog = usePostHog(); const queryClient = useQueryClient(); const chatQuery = useProjectChat(chatId ?? ""); const chatContextQuery = useProjectChatContext(chatId ?? ""); const [referenceIds, setReferenceIds] = useState([]); const [templatesModalOpen, setTemplatesModalOpen] = useState(false); - const [saveAsTemplateContent, setSaveAsTemplateContent] = useState(null); + const [saveAsTemplateContent, setSaveAsTemplateContent] = useState< + string | null + >(null); const handleSaveAsTemplate = (content: string) => { setSaveAsTemplateContent(content); @@ -350,16 +360,15 @@ export const ProjectChatRoute = () => { const saveQuickAccessMutation = useSaveQuickAccessPreferences(); const toggleAiSuggestionsMutation = useToggleAiSuggestions(); - const hideAiSuggestions = - currentUserQuery.data?.hide_ai_suggestions ?? false; + const hideAiSuggestions = currentUserQuery.data?.hide_ai_suggestions ?? false; // Resolve quick access items — default to first 3 built-in templates const quickAccessItems: QuickAccessItem[] = useMemo(() => { if (!quickAccessQuery.data || quickAccessQuery.data.length === 0) return Templates.slice(0, 3).map((t) => ({ - type: "static" as const, id: t.id, title: t.title, + type: "static" as const, })); return quickAccessQuery.data .map((pref) => { @@ -367,19 +376,17 @@ export const ProjectChatRoute = () => { const found = Templates.find((t) => t.id === pref.id); if (found) return { - type: "static" as const, id: found.id, title: found.title, + type: "static" as const, }; } else if (pref.type === "user") { - const found = userTemplatesQuery.data?.find( - (t) => t.id === pref.id, - ); + const found = userTemplatesQuery.data?.find((t) => t.id === pref.id); if (found) return { - type: "user" as const, id: found.id, title: found.title, + type: "user" as const, }; } return null; @@ -390,8 +397,8 @@ export const ProjectChatRoute = () => { const handleSaveQuickAccess = (items: QuickAccessItem[]) => { saveQuickAccessMutation.mutate( items.map((item) => ({ - type: item.type, id: item.id, + type: item.type, })), ); }; @@ -566,6 +573,11 @@ export const ProjectChatRoute = () => { chatId={chatId ?? ""} projectId={projectId ?? ""} onModeSelected={async (mode) => { + posthog?.capture("chat_mode_selected", { + chat_id: chatId, + mode, + project_id: projectId, + }); // Only prefetch suggestions for overview mode // Deep dive mode will fetch suggestions when conversations are added if (chatId && mode === "overview") { @@ -772,9 +784,7 @@ export const ProjectChatRoute = () => { onTemplateSelect={handleTemplateSelect} selectedTemplateKey={templateKey} suggestions={ - hideAiSuggestions - ? [] - : suggestionsQuery.data?.suggestions + hideAiSuggestions ? [] : suggestionsQuery.data?.suggestions } chatMode={chatMode} userTemplates={userTemplatesQuery.data ?? []} @@ -787,20 +797,12 @@ export const ProjectChatRoute = () => { onDeleteUserTemplate={(id) => deleteUserTemplateMutation.mutateAsync(id) } - isCreatingTemplate={ - createUserTemplateMutation.isPending - } - isUpdatingTemplate={ - updateUserTemplateMutation.isPending - } - isDeletingTemplate={ - deleteUserTemplateMutation.isPending - } + isCreatingTemplate={createUserTemplateMutation.isPending} + isUpdatingTemplate={updateUserTemplateMutation.isPending} + isDeletingTemplate={deleteUserTemplateMutation.isPending} quickAccessItems={quickAccessItems} onSaveQuickAccess={handleSaveQuickAccess} - isSavingQuickAccess={ - saveQuickAccessMutation.isPending - } + isSavingQuickAccess={saveQuickAccessMutation.isPending} hideAiSuggestions={hideAiSuggestions} onToggleAiSuggestions={(hide) => toggleAiSuggestionsMutation.mutate(hide) diff --git a/echo/server/uv.lock b/echo/server/uv.lock index 03d4c07a..e4dbbce6 100644 --- a/echo/server/uv.lock +++ b/echo/server/uv.lock @@ -461,6 +461,7 @@ dependencies = [ { name = "gevent" }, { name = "google-cloud-aiplatform" }, { name = "gunicorn" }, + { name = "httpx" }, { name = "isort" }, { name = "jinja2" }, { name = "langchain" }, @@ -520,6 +521,7 @@ requires-dist = [ { name = "gevent", specifier = ">=25.4.2" }, { name = "google-cloud-aiplatform", specifier = "==1.120.*" }, { name = "gunicorn", specifier = "==21.2.*" }, + { name = "httpx", specifier = ">=0.28" }, { name = "isort", specifier = "==5.13.*" }, { name = "jinja2", specifier = "==3.1.*" }, { name = "langchain", specifier = "==0.1.*" }, From f6d982e7564b928796702de32e41768ffb3b758c Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 13:17:16 +0000 Subject: [PATCH 024/208] chore: scaffold v2 API structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New /api/v2/ router with typed Pydantic response models, workspace context middleware, policy-based access control, and app_user resolution helpers. Structure: api/v2/__init__.py — v2 router aggregator api/v2/schemas.py — typed response/request models api/v2/middleware.py — get_workspace_context dependency api/v2/me.py — GET /v2/me (placeholder) api/v2/onboarding.py — POST /v2/onboarding/complete (placeholder) policies.py — role presets + has_policy() (workspace/org/project) app_user.py — resolve/create app_user, directus user profile lookup --- echo/server/dembrane/api/v2/__init__.py | 18 ++++ echo/server/dembrane/api/v2/me.py | 16 ++++ echo/server/dembrane/api/v2/middleware.py | 101 ++++++++++++++++++++++ echo/server/dembrane/api/v2/onboarding.py | 20 +++++ echo/server/dembrane/api/v2/schemas.py | 80 +++++++++++++++++ echo/server/dembrane/app_user.py | 80 +++++++++++++++++ echo/server/dembrane/main.py | 5 ++ echo/server/dembrane/policies.py | 78 +++++++++++++++++ 8 files changed, 398 insertions(+) create mode 100644 echo/server/dembrane/api/v2/__init__.py create mode 100644 echo/server/dembrane/api/v2/me.py create mode 100644 echo/server/dembrane/api/v2/middleware.py create mode 100644 echo/server/dembrane/api/v2/onboarding.py create mode 100644 echo/server/dembrane/api/v2/schemas.py create mode 100644 echo/server/dembrane/app_user.py create mode 100644 echo/server/dembrane/policies.py diff --git a/echo/server/dembrane/api/v2/__init__.py b/echo/server/dembrane/api/v2/__init__.py new file mode 100644 index 00000000..6a555ed6 --- /dev/null +++ b/echo/server/dembrane/api/v2/__init__.py @@ -0,0 +1,18 @@ +""" +V2 API — workspace-aware endpoints. + +All v2 endpoints use app_user.id (not directus_users.id), check workspace +permissions via policies, and return typed Pydantic response models. + +Mounted at /api/v2/ in main.py. +""" + +from fastapi import APIRouter + +from dembrane.api.v2.me import router as me_router +from dembrane.api.v2.onboarding import router as onboarding_router + +v2_router = APIRouter() + +v2_router.include_router(me_router, prefix="/me", tags=["v2:me"]) +v2_router.include_router(onboarding_router, prefix="/onboarding", tags=["v2:onboarding"]) diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py new file mode 100644 index 00000000..7ddbd5f9 --- /dev/null +++ b/echo/server/dembrane/api/v2/me.py @@ -0,0 +1,16 @@ +"""GET /v2/me — lightweight user profile with onboarding status.""" + +from logging import getLogger + +from fastapi import APIRouter + +from dembrane.api.dependency_auth import DependencyDirectusSession + +router = APIRouter() +logger = getLogger("api.v2.me") + + +@router.get("") +async def get_me(auth: DependencyDirectusSession) -> dict: + """Placeholder — implemented in commit 3.""" + return {"directus_user_id": auth.user_id, "onboarding_completed": False} diff --git a/echo/server/dembrane/api/v2/middleware.py b/echo/server/dembrane/api/v2/middleware.py new file mode 100644 index 00000000..d3ebdb63 --- /dev/null +++ b/echo/server/dembrane/api/v2/middleware.py @@ -0,0 +1,101 @@ +"""V2 API middleware: workspace context and permission enforcement.""" + +from __future__ import annotations + +from logging import getLogger +from typing import Optional + +from fastapi import Depends, HTTPException + +from dembrane.policies import has_policy +from dembrane.app_user import resolve_app_user +from dembrane.api.dependency_auth import DependencyDirectusSession +from dembrane.directus_async import async_directus + +logger = getLogger("api.v2.middleware") + + +class WorkspaceContext: + """Resolved workspace context for a request. + + Injected via FastAPI Depends. Validates that the authenticated user + has access to the workspace and provides their role/policies. + """ + + def __init__( + self, + workspace_id: str, + workspace: dict, + app_user_id: str, + role: str, + custom_policies: list[str], + source: str, + is_external: bool, + ): + self.workspace_id = workspace_id + self.workspace = workspace + self.app_user_id = app_user_id + self.role = role + self.custom_policies = custom_policies + self.source = source + self.is_external = is_external + + def has_policy(self, required: str) -> bool: + return has_policy(self.role, self.custom_policies, required) + + def require_policy(self, required: str) -> None: + if not self.has_policy(required): + raise HTTPException(status_code=403, detail=f"Missing policy: {required}") + + +async def get_workspace_context( + workspace_id: str, + auth: DependencyDirectusSession = Depends(), +) -> WorkspaceContext: + """FastAPI dependency that validates workspace access. + + Usage: + @router.get("/workspaces/{workspace_id}/projects") + async def list_projects(ctx: WorkspaceContext = Depends(get_workspace_context)): + ctx.require_policy("project:read") + ... + """ + app_user = await resolve_app_user(auth.user_id) + if not app_user: + raise HTTPException(status_code=403, detail="User not onboarded") + + app_user_id = app_user["id"] + + memberships = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "limit": 1, + } + }, + ) + + if not isinstance(memberships, list) or len(memberships) == 0: + raise HTTPException(status_code=403, detail="No access to this workspace") + + membership = memberships[0] + + # Fetch workspace details + workspace = await async_directus.get_item("workspace", workspace_id) + if not workspace or workspace.get("deleted_at"): + raise HTTPException(status_code=404, detail="Workspace not found") + + return WorkspaceContext( + workspace_id=workspace_id, + workspace=workspace, + app_user_id=app_user_id, + role=membership["role"], + custom_policies=membership.get("custom_policies") or [], + source=membership.get("source", "direct"), + is_external=membership.get("is_external", False), + ) diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py new file mode 100644 index 00000000..988f2814 --- /dev/null +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -0,0 +1,20 @@ +"""POST /v2/onboarding/complete — one-time user onboarding.""" + +from logging import getLogger + +from fastapi import APIRouter + +from dembrane.api.dependency_auth import DependencyDirectusSession +from dembrane.api.v2.schemas import OnboardingCompleteRequest, OnboardingCompleteResponse + +router = APIRouter() +logger = getLogger("api.v2.onboarding") + + +@router.post("/complete", response_model=OnboardingCompleteResponse) +async def complete_onboarding( + body: OnboardingCompleteRequest, + auth: DependencyDirectusSession, +) -> OnboardingCompleteResponse: + """Placeholder — implemented in commit 4.""" + raise NotImplementedError("Onboarding endpoint not yet implemented") diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py new file mode 100644 index 00000000..51cf32c5 --- /dev/null +++ b/echo/server/dembrane/api/v2/schemas.py @@ -0,0 +1,80 @@ +"""Typed response models for v2 API endpoints.""" + +from __future__ import annotations + +from typing import Optional + +from pydantic import BaseModel + + +# ── /v2/me ── + + +class MeResponse(BaseModel): + """Lightweight user profile. Called on every page load — must be fast.""" + + id: Optional[str] = None # app_user.id (null if not onboarded) + directus_user_id: str + email: str + display_name: str + avatar: Optional[str] = None + onboarding_completed: bool + + +# ── /v2/onboarding ── + + +class OnboardingCompleteRequest(BaseModel): + org_name: str + + +class OnboardingCompleteResponse(BaseModel): + app_user_id: str + org_id: str + workspace_id: str + + +# ── /v2/workspaces ── + + +class WorkspaceSummary(BaseModel): + id: str + name: str + org_id: str + org_name: str + role: str + is_default: bool + tier: str + project_count: int + member_count: int + is_external: bool + + +class WorkspaceListResponse(BaseModel): + workspaces: list[WorkspaceSummary] + + +# ── /v2/workspaces/:id/invite ── + + +class WorkspaceInviteRequest(BaseModel): + email: str + role: str = "member" + + +class WorkspaceInviteResponse(BaseModel): + status: str # "invited" | "added" (existing user → immediate membership) + email: str + user_existed: bool + + +# ── /v2/projects/:id/move ── + + +class MoveProjectRequest(BaseModel): + target_workspace_id: str + + +class MoveProjectResponse(BaseModel): + project_id: str + workspace_id: str diff --git a/echo/server/dembrane/app_user.py b/echo/server/dembrane/app_user.py new file mode 100644 index 00000000..53135346 --- /dev/null +++ b/echo/server/dembrane/app_user.py @@ -0,0 +1,80 @@ +""" +App user resolution and creation helpers. + +Maps directus_users.id → app_user.id. Used by all v2 endpoints. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import Optional + +from dembrane.utils import generate_uuid +from dembrane.directus_async import async_directus + +logger = getLogger("dembrane.app_user") + + +async def resolve_app_user(directus_user_id: str) -> Optional[dict]: + """Look up app_user by directus_user_id. Returns None if not onboarded.""" + items = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"directus_user_id": {"_eq": directus_user_id}}, + "limit": 1, + } + }, + ) + if isinstance(items, list) and len(items) > 0: + return items[0] + return None + + +async def get_app_user_or_raise(directus_user_id: str) -> dict: + """Look up app_user or raise. For endpoints that require onboarding.""" + from fastapi import HTTPException + + app_user = await resolve_app_user(directus_user_id) + if not app_user: + raise HTTPException(status_code=403, detail="User not onboarded") + return app_user + + +async def create_app_user( + directus_user_id: str, + email: str, + display_name: str, +) -> dict: + """Create a new app_user. Returns the created record (unwrapped).""" + app_user_id = generate_uuid() + result = await async_directus.create_item( + "app_user", + { + "id": app_user_id, + "directus_user_id": directus_user_id, + "email": email, + "display_name": display_name, + }, + ) + return result["data"] + + +async def get_directus_user_profile(directus_user_id: str) -> Optional[dict]: + """Fetch display name and email from directus_users.""" + users = await async_directus.get_users( + { + "query": { + "filter": {"id": {"_eq": directus_user_id}}, + "fields": ["id", "first_name", "last_name", "email", "avatar"], + "limit": 1, + } + }, + ) + if isinstance(users, list) and len(users) > 0: + user = users[0] + first = user.get("first_name") or "" + last = user.get("last_name") or "" + user["display_name"] = f"{first} {last}".strip() or user.get("email", "") + return user + return None diff --git a/echo/server/dembrane/main.py b/echo/server/dembrane/main.py index e39a0e97..606f252e 100644 --- a/echo/server/dembrane/main.py +++ b/echo/server/dembrane/main.py @@ -92,6 +92,11 @@ async def add_process_time_header( logger.info("mounting api on /api") app.include_router(api, prefix="/api") +from dembrane.api.v2 import v2_router + +logger.info("mounting v2 api on /api/v2") +app.include_router(v2_router, prefix="/api/v2") + class SPAStaticFiles(StaticFiles): async def get_response(self, path: str, scope: Scope) -> Response: diff --git a/echo/server/dembrane/policies.py b/echo/server/dembrane/policies.py new file mode 100644 index 00000000..52192e98 --- /dev/null +++ b/echo/server/dembrane/policies.py @@ -0,0 +1,78 @@ +""" +Policy-based access control for workspaces and orgs. + +Uses the AWS IAM-inspired pattern: role is a display label, policies are +the enforcement source of truth. Presets are hardcoded here — the DB stores +only custom_policies (extras beyond the preset). + +Effective policies = preset[role] + custom_policies +Enforcement code always calls has_policy(), never checks role directly. +""" + +from __future__ import annotations + + +# ── Workspace role presets ── + +WORKSPACE_ROLE_PRESETS: dict[str, list[str]] = { + "viewer": [], + "member": [ + "project:create", + "project:update", + ], + "admin": [ + "project:create", + "project:update", + "project:delete", + "project:share", + "member:invite", + "member:manage", + "settings:manage", + ], + "owner": ["*"], +} + +# ── Org role presets ── + +ORG_ROLE_PRESETS: dict[str, list[str]] = { + "member": [], + "admin": [ + "org:manage_users", + "org:manage_billing", + "org:view_all_workspaces", + ], + "owner": ["*"], +} + +# ── Project role presets ── + +PROJECT_ROLE_PRESETS: dict[str, list[str]] = { + "viewer": [], + "editor": [ + "project:update", + "conversation:read", + "chat:use", + ], +} + + +def get_effective_policies( + role: str, + custom_policies: list[str] | None = None, + presets: dict[str, list[str]] = WORKSPACE_ROLE_PRESETS, +) -> list[str]: + """Compute effective policies: preset for role + any custom additions.""" + base = presets.get(role, []) + extras = custom_policies or [] + return base + extras + + +def has_policy( + role: str, + custom_policies: list[str] | None, + required: str, + presets: dict[str, list[str]] = WORKSPACE_ROLE_PRESETS, +) -> bool: + """Check if a role + custom_policies grants a required policy.""" + effective = get_effective_policies(role, custom_policies, presets) + return "*" in effective or required in effective From 2ba4193ab4a9c8125457060d0f2ab1377ba73e96 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 13:18:05 +0000 Subject: [PATCH 025/208] feat: implement GET /v2/me endpoint Returns lightweight user profile with onboarding status. Checks if app_user record exists (onboarding_completed). Falls back to directus_users profile for display info. Typed response via MeResponse Pydantic model. --- echo/server/dembrane/api/v2/me.py | 45 ++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 7ddbd5f9..95715121 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -4,13 +4,50 @@ from fastapi import APIRouter +from dembrane.app_user import resolve_app_user, get_directus_user_profile +from dembrane.api.v2.schemas import MeResponse from dembrane.api.dependency_auth import DependencyDirectusSession router = APIRouter() logger = getLogger("api.v2.me") -@router.get("") -async def get_me(auth: DependencyDirectusSession) -> dict: - """Placeholder — implemented in commit 3.""" - return {"directus_user_id": auth.user_id, "onboarding_completed": False} +@router.get("", response_model=MeResponse) +async def get_me(auth: DependencyDirectusSession) -> MeResponse: + """Lightweight user profile with onboarding status. + + Called on every page load — must be fast. Returns app_user info + if onboarded, or just directus_user info if not yet onboarded. + """ + # Check if user has completed onboarding (has app_user record) + app_user = await resolve_app_user(auth.user_id) + + # Fetch display info from Directus (needed regardless of onboarding state) + directus_profile = await get_directus_user_profile(auth.user_id) + + if not directus_profile: + logger.warning(f"Directus user not found for id {auth.user_id}") + return MeResponse( + directus_user_id=auth.user_id, + email="", + display_name="", + onboarding_completed=False, + ) + + if app_user: + return MeResponse( + id=app_user["id"], + directus_user_id=auth.user_id, + email=app_user.get("email") or directus_profile.get("email", ""), + display_name=app_user.get("display_name") or directus_profile.get("display_name", ""), + avatar=directus_profile.get("avatar"), + onboarding_completed=True, + ) + + return MeResponse( + directus_user_id=auth.user_id, + email=directus_profile.get("email", ""), + display_name=directus_profile.get("display_name", ""), + avatar=directus_profile.get("avatar"), + onboarding_completed=False, + ) From 80efb64a9443ba4fd52604d7e3758aa7439e257b Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 13:19:09 +0000 Subject: [PATCH 026/208] feat: implement POST /v2/onboarding/complete Creates app_user + org + default workspace + moves user's projects. Fully idempotent: each step checks if already done before creating. Flow: 1. Get or create app_user (linked to directus_user) 2. Get or create org (user becomes owner) 3. Get or create default workspace in org 4. Move all user's projects (where workspace_id is null) into workspace --- echo/server/dembrane/api/v2/onboarding.py | 153 +++++++++++++++++++++- 1 file changed, 148 insertions(+), 5 deletions(-) diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py index 988f2814..10b52a9c 100644 --- a/echo/server/dembrane/api/v2/onboarding.py +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -1,11 +1,18 @@ -"""POST /v2/onboarding/complete — one-time user onboarding.""" +"""POST /v2/onboarding/complete — one-time user onboarding. + +Creates app_user + org + default workspace + moves all user's projects. +Idempotent: if already onboarded, returns existing IDs without creating anything. +""" from logging import getLogger -from fastapi import APIRouter +from fastapi import APIRouter, HTTPException -from dembrane.api.dependency_auth import DependencyDirectusSession +from dembrane.utils import generate_uuid +from dembrane.app_user import resolve_app_user, create_app_user, get_directus_user_profile +from dembrane.directus_async import async_directus from dembrane.api.v2.schemas import OnboardingCompleteRequest, OnboardingCompleteResponse +from dembrane.api.dependency_auth import DependencyDirectusSession router = APIRouter() logger = getLogger("api.v2.onboarding") @@ -16,5 +23,141 @@ async def complete_onboarding( body: OnboardingCompleteRequest, auth: DependencyDirectusSession, ) -> OnboardingCompleteResponse: - """Placeholder — implemented in commit 4.""" - raise NotImplementedError("Onboarding endpoint not yet implemented") + """Complete user onboarding. Creates org + workspace + moves projects. + + Idempotent: safe to call multiple times. If already onboarded, returns + existing IDs. If partially completed (e.g., app_user exists but no org), + picks up where it left off. + """ + directus_user_id = auth.user_id + org_name = body.org_name.strip() + + if not org_name: + raise HTTPException(status_code=400, detail="Organization name is required") + + # ── Step 1: Get or create app_user ── + + app_user = await resolve_app_user(directus_user_id) + + if not app_user: + profile = await get_directus_user_profile(directus_user_id) + if not profile: + raise HTTPException(status_code=404, detail="Directus user not found") + + app_user = await create_app_user( + directus_user_id=directus_user_id, + email=profile.get("email", ""), + display_name=profile.get("display_name", ""), + ) + logger.info(f"Created app_user {app_user['id']} for directus user {directus_user_id}") + + app_user_id = app_user["id"] + + # ── Step 2: Get or create org ── + + existing_orgs = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "user_id": {"_eq": app_user_id}, + "role": {"_eq": "owner"}, + "deleted_at": {"_null": True}, + }, + "fields": ["org_id"], + "limit": 1, + } + }, + ) + + if isinstance(existing_orgs, list) and len(existing_orgs) > 0: + org_id = existing_orgs[0]["org_id"] + logger.info(f"User {app_user_id} already owns org {org_id}, skipping org creation") + else: + org_id = generate_uuid() + await async_directus.create_item("org", { + "id": org_id, + "name": org_name, + "created_by": app_user_id, + }) + await async_directus.create_item("org_membership", { + "id": generate_uuid(), + "org_id": org_id, + "user_id": app_user_id, + "role": "owner", + }) + logger.info(f"Created org {org_id} '{org_name}' for user {app_user_id}") + + # ── Step 3: Get or create default workspace ── + + existing_workspaces = await async_directus.get_items( + "workspace", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "is_default": {"_eq": True}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + + if isinstance(existing_workspaces, list) and len(existing_workspaces) > 0: + workspace_id = existing_workspaces[0]["id"] + logger.info(f"Default workspace {workspace_id} already exists for org {org_id}") + else: + workspace_id = generate_uuid() + await async_directus.create_item("workspace", { + "id": workspace_id, + "org_id": org_id, + "name": "Default", + "is_default": True, + "tier": "pioneer", + "created_by": app_user_id, + }) + await async_directus.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": workspace_id, + "user_id": app_user_id, + "role": "owner", + "source": "inherited", + }) + logger.info(f"Created default workspace {workspace_id} for org {org_id}") + + # ── Step 4: Move user's projects into the workspace ── + + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "directus_user_id": {"_eq": directus_user_id}, + "workspace_id": {"_null": True}, + }, + "fields": ["id"], + "limit": -1, + } + }, + ) + + moved_count = 0 + if isinstance(projects, list): + for project in projects: + await async_directus.update_item( + "project", + project["id"], + {"workspace_id": workspace_id}, + ) + moved_count += 1 + + if moved_count > 0: + logger.info(f"Moved {moved_count} projects into workspace {workspace_id}") + + return OnboardingCompleteResponse( + app_user_id=app_user_id, + org_id=org_id, + workspace_id=workspace_id, + ) From b085fa676b0e33e115d004841e6572bd2a996a4d Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 15:55:10 +0000 Subject: [PATCH 027/208] feat: v2 API endpoints, invite flow, email, and pre-seed tooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V2 endpoints (all under /api/v2/): - GET /me: user profile + orgs + onboarding status + pending invites - POST /onboarding/complete: smart onboarding with invite auto-accept - GET /workspaces: list accessible workspaces with avatar previews - POST /workspaces/:id/invite: invite by email (internal/external) - POST /projects/:id/move: move between workspaces Onboarding decision tree: - Internal invite → join org + workspace, skip personal org - External invite → own org + workspace as external - No invites → personal org + default workspace Invite flow: - Existing user → immediate membership + notification email - New user → workspace_invite record + invite email - Expired invites enforced (7-day), self-invite prevented Infrastructure: - SendGrid email module (sync + async, Jinja2 templates) - Workspace invite email template (brand-compliant) - Pre-seed script (YAML config, handles existing + new users) - Reverted httpx migration on sync DirectusClient (production safety) - Fixed DirectusClient.search() error handling (returns [] not error dict) --- .../include_org_membership.json | 44 +++ echo/scripts/preseed/example.yaml | 29 ++ echo/scripts/preseed_workspace.py | 361 ++++++++++++++++++ echo/server/dembrane/api/v2/__init__.py | 6 + echo/server/dembrane/api/v2/invites.py | 232 +++++++++++ echo/server/dembrane/api/v2/me.py | 90 ++++- echo/server/dembrane/api/v2/middleware.py | 2 +- echo/server/dembrane/api/v2/onboarding.py | 296 +++++++++----- echo/server/dembrane/api/v2/projects.py | 98 +++++ echo/server/dembrane/api/v2/schemas.py | 23 +- echo/server/dembrane/api/v2/workspaces.py | 202 ++++++++++ echo/server/dembrane/directus.py | 69 ++-- echo/server/dembrane/email.py | 195 ++++++++++ echo/server/dembrane/settings.py | 23 ++ .../email_templates/workspace_invite.html | 64 ++++ echo/server/pyproject.toml | 3 +- echo/server/uv.lock | 63 ++- 17 files changed, 1660 insertions(+), 140 deletions(-) create mode 100644 echo/directus/sync/snapshot/fields/workspace_invite/include_org_membership.json create mode 100644 echo/scripts/preseed/example.yaml create mode 100644 echo/scripts/preseed_workspace.py create mode 100644 echo/server/dembrane/api/v2/invites.py create mode 100644 echo/server/dembrane/api/v2/projects.py create mode 100644 echo/server/dembrane/api/v2/workspaces.py create mode 100644 echo/server/dembrane/email.py create mode 100644 echo/server/email_templates/workspace_invite.html diff --git a/echo/directus/sync/snapshot/fields/workspace_invite/include_org_membership.json b/echo/directus/sync/snapshot/fields/workspace_invite/include_org_membership.json new file mode 100644 index 00000000..74953456 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace_invite/include_org_membership.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace_invite", + "field": "include_org_membership", + "type": "boolean", + "meta": { + "collection": "workspace_invite", + "conditions": null, + "display": null, + "display_options": null, + "field": "include_org_membership", + "group": null, + "hidden": false, + "interface": "boolean", + "note": "True = add to org as member. False = external workspace access only.", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 10, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "include_org_membership", + "table": "workspace_invite", + "data_type": "boolean", + "default_value": false, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/scripts/preseed/example.yaml b/echo/scripts/preseed/example.yaml new file mode 100644 index 00000000..1eb1a7c3 --- /dev/null +++ b/echo/scripts/preseed/example.yaml @@ -0,0 +1,29 @@ +# Example pre-seed configuration for a partner org. +# +# Usage: +# python scripts/preseed_workspace.py --config scripts/preseed/example.yaml --dry-run +# python scripts/preseed_workspace.py --config scripts/preseed/example.yaml +# +# - owner: gets org ownership + workspace ownership +# - admins: get org admin + inherited workspace admin +# - members: get org member + direct workspace member +# +# Users who don't exist in Directus will be created with an invite email. + +org: + name: "Example Consultancy" + owner: owner@example.com + admins: + - admin1@example.com + - admin2@example.com + members: + - member1@example.com + +workspaces: + - name: "Client Alpha" + tier: pioneer + include_projects: true # move owner+admin+member projects here + + - name: "Client Beta" + tier: pioneer + include_projects: false # empty workspace, projects added manually diff --git a/echo/scripts/preseed_workspace.py b/echo/scripts/preseed_workspace.py new file mode 100644 index 00000000..213fd650 --- /dev/null +++ b/echo/scripts/preseed_workspace.py @@ -0,0 +1,361 @@ +""" +Pre-seed workspaces for existing clients. + +Creates orgs, workspaces, and invites users. Handles both existing +Directus users (immediate setup) and new users (creates Directus +account with invite email). + +Usage: + python scripts/preseed_workspace.py --config preseed/example.yaml + python scripts/preseed_workspace.py --config preseed/example.yaml --dry-run + +YAML config format: + + org: + name: "Dietz Consulting" + owner: petra@dietz.nl + admins: + - jan@dietz.nl + - lisa@dietz.nl + + workspaces: + - name: "Client Alpha" + tier: pioneer + include_projects: true # move owner+admin projects here + + - name: "Client Beta" + tier: pioneer + include_projects: false # empty workspace +""" + +import argparse +import sys +from logging import getLogger, basicConfig, INFO +from pathlib import Path + +import yaml + +# Add server to path so we can import dembrane modules +sys.path.insert(0, str(Path(__file__).parent.parent / "server")) + +logger = getLogger("preseed") + + +def load_config(path: str) -> dict: + with open(path) as f: + return yaml.safe_load(f) + + +async def find_directus_user_by_email(client, email: str) -> dict | None: + """Find a Directus user by email. Returns None if not found.""" + users = client.get_users( + {"query": {"filter": {"email": {"_eq": email}}, "fields": ["id", "email", "first_name", "last_name"], "limit": 1}} + ) + if isinstance(users, list) and len(users) > 0: + return users[0] + return None + + +async def find_or_create_directus_user(client, email: str, role_id: str, dry_run: bool) -> dict | None: + """Find existing Directus user or create one with invite.""" + existing = await find_directus_user_by_email(client, email) + if existing: + logger.info(f" Found existing Directus user: {email} (id: {existing['id']})") + return existing + + if dry_run: + logger.info(f" WOULD create Directus user + send invite: {email}") + return None + + logger.info(f" Creating Directus user + sending invite: {email}") + result = client.post("/users", json={ + "email": email, + "role": role_id, + "status": "invited", + }) + user = result.get("data", result) + logger.info(f" Created Directus user: {email} (id: {user.get('id')})") + return user + + +async def find_or_create_app_user(client, directus_user_id: str, email: str, display_name: str, dry_run: bool) -> dict | None: + """Find existing app_user or create one.""" + items = client.get_items("app_user", {"query": {"filter": {"directus_user_id": {"_eq": directus_user_id}}, "limit": 1}}) + if isinstance(items, list) and len(items) > 0: + logger.info(f" Found existing app_user for {email}") + return items[0] + + if dry_run: + logger.info(f" WOULD create app_user for {email}") + return None + + from dembrane.utils import generate_uuid + app_user_id = generate_uuid() + result = client.create_item("app_user", { + "id": app_user_id, + "directus_user_id": directus_user_id, + "email": email, + "display_name": display_name, + }) + app_user = result["data"] + logger.info(f" Created app_user: {app_user['id']} for {email}") + return app_user + + +async def run_preseed(config: dict, dry_run: bool = True): + from dembrane.directus import create_directus_client + from dembrane.utils import generate_uuid + from dembrane.settings import get_settings + + settings = get_settings() + client = create_directus_client(token=settings.directus.token) + + org_config = config["org"] + workspace_configs = config.get("workspaces", []) + + # Get the Basic User role ID for new user creation + basic_user_role_id = None + try: + import requests + resp = requests.get( + f"{settings.directus.base_url}/roles?fields=id,name", + headers={"Authorization": f"Bearer {settings.directus.token}"}, + timeout=10, + ) + for role in resp.json().get("data", []): + if role["name"] == "Basic User": + basic_user_role_id = role["id"] + break + except Exception as e: + logger.error(f"Failed to fetch roles: {e}") + return + + if not basic_user_role_id: + logger.error("Could not find 'Basic User' role") + return + + logger.info(f"{'DRY RUN — ' if dry_run else ''}Pre-seeding org: {org_config['name']}") + + # ── Collect all emails ── + all_emails = { + org_config["owner"]: "owner", + } + for email in org_config.get("admins", []): + all_emails[email] = "admin" + for email in org_config.get("members", []): + if email not in all_emails: + all_emails[email] = "member" + + # ── Ensure all users exist in Directus + app_user ── + app_users: dict[str, dict] = {} # email -> app_user record + + for email, role in all_emails.items(): + logger.info(f"\nProcessing user: {email} (org role: {role})") + + directus_user = await find_or_create_directus_user(client, email, basic_user_role_id, dry_run) + if not directus_user: + if dry_run: + app_users[email] = {"id": f"", "email": email} + continue + + first = directus_user.get("first_name") or "" + last = directus_user.get("last_name") or "" + display_name = f"{first} {last}".strip() or email + + app_user = await find_or_create_app_user( + client, directus_user["id"], email, display_name, dry_run + ) + if app_user: + app_users[email] = app_user + elif dry_run: + app_users[email] = {"id": f"", "email": email} + + owner_email = org_config["owner"] + owner_app_user = app_users.get(owner_email) + if not owner_app_user: + logger.error(f"Owner {owner_email} could not be resolved. Aborting.") + return + + # ── Create org ── + logger.info(f"\nCreating org: {org_config['name']}") + + existing_orgs = client.get_items("org_membership", { + "query": {"filter": {"user_id": {"_eq": owner_app_user["id"]}, "role": {"_eq": "owner"}, "deleted_at": {"_null": True}}, "fields": ["org_id"], "limit": 1} + }) + + if isinstance(existing_orgs, list) and len(existing_orgs) > 0: + org_id = existing_orgs[0]["org_id"] + logger.info(f" Org already exists: {org_id}") + elif dry_run: + org_id = "" + logger.info(f" WOULD create org: {org_config['name']}") + else: + org_id = generate_uuid() + client.create_item("org", { + "id": org_id, + "name": org_config["name"], + "created_by": owner_app_user["id"], + }) + client.create_item("org_membership", { + "id": generate_uuid(), + "org_id": org_id, + "user_id": owner_app_user["id"], + "role": "owner", + }) + logger.info(f" Created org: {org_id}") + + # Add org admins/members + for email, role in all_emails.items(): + if email == owner_email: + continue + app_user = app_users.get(email) + if not app_user or app_user["id"].startswith(" 0: + logger.info(f" {email} already org member, skipping") + continue + + if dry_run: + logger.info(f" WOULD add {email} as org {role}") + else: + client.create_item("org_membership", { + "id": generate_uuid(), + "org_id": org_id, + "user_id": app_user["id"], + "role": role, + }) + logger.info(f" Added {email} as org {role}") + + # ── Create workspaces ── + for ws_config in workspace_configs: + ws_name = ws_config["name"] + ws_tier = ws_config.get("tier", "pioneer") + include_projects = ws_config.get("include_projects", False) + + logger.info(f"\nCreating workspace: {ws_name} (tier: {ws_tier})") + + # Check if workspace already exists + existing_ws = client.get_items("workspace", { + "query": {"filter": {"org_id": {"_eq": org_id}, "name": {"_eq": ws_name}, "deleted_at": {"_null": True}}, "limit": 1} + }) + + if isinstance(existing_ws, list) and len(existing_ws) > 0: + ws_id = existing_ws[0]["id"] + logger.info(f" Workspace already exists: {ws_id}") + elif dry_run: + ws_id = f"" + logger.info(f" WOULD create workspace: {ws_name}") + else: + ws_id = generate_uuid() + client.create_item("workspace", { + "id": ws_id, + "org_id": org_id, + "name": ws_name, + "tier": ws_tier, + "is_default": False, + "created_by": owner_app_user["id"], + }) + logger.info(f" Created workspace: {ws_id}") + + # Add workspace memberships for org admins/owner (inherited) + for email, org_role in all_emails.items(): + if org_role not in ("owner", "admin"): + continue + app_user = app_users.get(email) + if not app_user or app_user["id"].startswith(" 0: + continue + + ws_role = "owner" if email == owner_email else "admin" + if dry_run: + logger.info(f" WOULD add {email} as workspace {ws_role} (inherited)") + else: + client.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": ws_id, + "user_id": app_user["id"], + "role": ws_role, + "source": "inherited", + }) + logger.info(f" Added {email} as workspace {ws_role} (inherited)") + + # Add workspace members (direct) + for email, org_role in all_emails.items(): + if org_role != "member": + continue + app_user = app_users.get(email) + if not app_user or app_user["id"].startswith(" 0: + continue + + if dry_run: + logger.info(f" WOULD add {email} as workspace member (direct)") + else: + client.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": ws_id, + "user_id": app_user["id"], + "role": "member", + "source": "direct", + }) + logger.info(f" Added {email} as workspace member (direct)") + + # Move projects if requested + if include_projects and not dry_run: + for email in all_emails: + app_user = app_users.get(email) + if not app_user or app_user["id"].startswith(" WorkspaceInviteResponse: + """Invite a user to a workspace by email. + + If the user already has a Directus account + app_user: + → Create workspace_membership immediately (status: "added") + + If the user doesn't exist: + → Create workspace_invite with token + → Send invite email via SendGrid + → When they register + onboard, the invite is auto-accepted (status: "invited") + """ + ctx.require_policy("member:invite") + + email = body.email.strip().lower() + role = body.role + + if role not in ("admin", "member", "viewer"): + raise HTTPException(status_code=400, detail="Role must be admin, member, or viewer") + + # Prevent self-invite + inviter_app_user = await async_directus.get_items( + "app_user", + {"query": {"filter": {"id": {"_eq": ctx.app_user_id}}, "fields": ["email"], "limit": 1}}, + ) + if isinstance(inviter_app_user, list) and len(inviter_app_user) > 0: + if inviter_app_user[0].get("email", "").lower() == email: + raise HTTPException(status_code=400, detail="Cannot invite yourself") + + # Check if already a member + existing_membership = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["user_id"], + "limit": -1, + } + }, + ) + + # Try to find the user + users = await async_directus.get_users( + { + "query": { + "filter": {"email": {"_eq": email}}, + "fields": ["id", "email", "first_name", "last_name"], + "limit": 1, + } + }, + ) + + user_existed = isinstance(users, list) and len(users) > 0 + + if user_existed: + directus_user = users[0] + + # Check if they have an app_user + app_user = await resolve_app_user(directus_user["id"]) + + if app_user: + # Check if already a member + if isinstance(existing_membership, list): + for m in existing_membership: + if m.get("user_id") == app_user["id"]: + raise HTTPException( + status_code=409, + detail="User is already a member of this workspace", + ) + + # Determine external status based on invite intent + is_external = not body.is_org_member + ws_org_id = ctx.workspace.get("org_id") + + # If marked as org member, add them to the org too + if body.is_org_member and ws_org_id: + existing_org_mem = await async_directus.get_items( + "org_membership", + {"query": {"filter": { + "org_id": {"_eq": ws_org_id}, + "user_id": {"_eq": app_user["id"]}, + "deleted_at": {"_null": True}, + }, "limit": 1}}, + ) + if not (isinstance(existing_org_mem, list) and len(existing_org_mem) > 0): + await async_directus.create_item("org_membership", { + "id": generate_uuid(), + "org_id": ws_org_id, + "user_id": app_user["id"], + "role": "member", + }) + logger.info(f"Added {email} to org {ws_org_id} as member") + + await async_directus.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": workspace_id, + "user_id": app_user["id"], + "role": role, + "source": "direct", + "is_external": is_external, + }) + + logger.info( + f"Added {email} to workspace {workspace_id} as {role} " + f"(external: {is_external}) by {ctx.app_user_id}" + ) + + # Send a notification email + inviter_name = "Your team" + inviter_app_user = await resolve_app_user( + # ctx has app_user_id but we need display name from directus + ctx.app_user_id + ) + if inviter_app_user: + inviter_name = inviter_app_user.get("display_name", "Your team") + + await send_email( + to=email, + subject=f"You've been added to {ctx.workspace.get('name', 'a workspace')}", + template="workspace_invite", + template_data={ + "inviter_name": inviter_name, + "workspace_name": ctx.workspace.get("name", "a workspace"), + "invite_url": f"{settings.urls.admin_base_url}/projects", + }, + ) + + return WorkspaceInviteResponse( + status="added", + email=email, + user_existed=True, + ) + + # User doesn't exist or doesn't have app_user — create an invite + token = secrets.token_urlsafe(32) + expires_at = (datetime.utcnow() + timedelta(days=7)).isoformat() + + # Check for existing pending invite + existing_invites = await async_directus.get_items( + "workspace_invite", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "email": {"_eq": email}, + "accepted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + + if isinstance(existing_invites, list) and len(existing_invites) > 0: + raise HTTPException(status_code=409, detail="An invite is already pending for this email") + + # Get inviter name + inviter_name = "Your team" + inviter_app_user_data = await async_directus.get_items( + "app_user", + {"query": {"filter": {"id": {"_eq": ctx.app_user_id}}, "fields": ["display_name"], "limit": 1}}, + ) + if isinstance(inviter_app_user_data, list) and len(inviter_app_user_data) > 0: + inviter_name = inviter_app_user_data[0].get("display_name", "Your team") + + await async_directus.create_item("workspace_invite", { + "id": generate_uuid(), + "workspace_id": workspace_id, + "email": email, + "role": role, + "invited_by": ctx.app_user_id, + "token": token, + "expires_at": expires_at, + "include_org_membership": body.is_org_member, + }) + + # Send invite email + invite_url = f"{settings.urls.admin_base_url}/register?next=/onboarding" + + await send_email( + to=email, + subject=f"{inviter_name} invited you to collaborate on dembrane", + template="workspace_invite", + template_data={ + "inviter_name": inviter_name, + "workspace_name": ctx.workspace.get("name", "a workspace"), + "invite_url": invite_url, + }, + ) + + logger.info( + f"Invited {email} to workspace {workspace_id} as {role} by {ctx.app_user_id} " + f"(user_existed: {user_existed})" + ) + + return WorkspaceInviteResponse( + status="invited", + email=email, + user_existed=user_existed, + ) diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 95715121..047d1422 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -1,11 +1,13 @@ """GET /v2/me — lightweight user profile with onboarding status.""" from logging import getLogger +from datetime import datetime from fastapi import APIRouter from dembrane.app_user import resolve_app_user, get_directus_user_profile -from dembrane.api.v2.schemas import MeResponse +from dembrane.directus_async import async_directus +from dembrane.api.v2.schemas import MeResponse, OrgSummary from dembrane.api.dependency_auth import DependencyDirectusSession router = APIRouter() @@ -14,15 +16,10 @@ @router.get("", response_model=MeResponse) async def get_me(auth: DependencyDirectusSession) -> MeResponse: - """Lightweight user profile with onboarding status. + """Lightweight user profile with onboarding status, org memberships, + and pending invite check.""" - Called on every page load — must be fast. Returns app_user info - if onboarded, or just directus_user info if not yet onboarded. - """ - # Check if user has completed onboarding (has app_user record) app_user = await resolve_app_user(auth.user_id) - - # Fetch display info from Directus (needed regardless of onboarding state) directus_profile = await get_directus_user_profile(auth.user_id) if not directus_profile: @@ -34,20 +31,81 @@ async def get_me(auth: DependencyDirectusSession) -> MeResponse: onboarding_completed=False, ) - if app_user: + email = directus_profile.get("email", "") + + # Check for pending workspace invites (by email, regardless of onboarding) + has_pending_invites = False + if email: + pending = await async_directus.get_items( + "workspace_invite", + { + "query": { + "filter": { + "email": {"_eq": email}, + "accepted_at": {"_null": True}, + "expires_at": {"_gt": datetime.utcnow().isoformat()}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + has_pending_invites = isinstance(pending, list) and len(pending) > 0 + + if not app_user: return MeResponse( - id=app_user["id"], directus_user_id=auth.user_id, - email=app_user.get("email") or directus_profile.get("email", ""), - display_name=app_user.get("display_name") or directus_profile.get("display_name", ""), + email=email, + display_name=directus_profile.get("display_name", ""), avatar=directus_profile.get("avatar"), - onboarding_completed=True, + onboarding_completed=False, + has_pending_invites=has_pending_invites, + ) + + # Fetch org memberships + orgs: list[OrgSummary] = [] + org_memberships = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "user_id": {"_eq": app_user["id"]}, + "deleted_at": {"_null": True}, + }, + "fields": ["org_id", "role"], + "limit": -1, + } + }, + ) + if isinstance(org_memberships, list) and len(org_memberships) > 0: + org_ids = [m["org_id"] for m in org_memberships if m.get("org_id")] + org_data = await async_directus.get_items( + "org", + { + "query": { + "filter": {"id": {"_in": org_ids}, "deleted_at": {"_null": True}}, + "fields": ["id", "name"], + "limit": -1, + } + }, ) + org_map = {o["id"]: o for o in (org_data or []) if isinstance(o, dict)} + for m in org_memberships: + org = org_map.get(m["org_id"]) + if org: + orgs.append(OrgSummary( + id=org["id"], + name=org.get("name", ""), + role=m["role"], + )) return MeResponse( + id=app_user["id"], directus_user_id=auth.user_id, - email=directus_profile.get("email", ""), - display_name=directus_profile.get("display_name", ""), + email=app_user.get("email") or email, + display_name=app_user.get("display_name") or directus_profile.get("display_name", ""), avatar=directus_profile.get("avatar"), - onboarding_completed=False, + onboarding_completed=True, + orgs=orgs, + has_pending_invites=has_pending_invites, ) diff --git a/echo/server/dembrane/api/v2/middleware.py b/echo/server/dembrane/api/v2/middleware.py index d3ebdb63..c3fd7cc3 100644 --- a/echo/server/dembrane/api/v2/middleware.py +++ b/echo/server/dembrane/api/v2/middleware.py @@ -50,7 +50,7 @@ def require_policy(self, required: str) -> None: async def get_workspace_context( workspace_id: str, - auth: DependencyDirectusSession = Depends(), + auth: DependencyDirectusSession, ) -> WorkspaceContext: """FastAPI dependency that validates workspace access. diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py index 10b52a9c..891ef80b 100644 --- a/echo/server/dembrane/api/v2/onboarding.py +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -1,10 +1,23 @@ """POST /v2/onboarding/complete — one-time user onboarding. -Creates app_user + org + default workspace + moves all user's projects. -Idempotent: if already onboarded, returns existing IDs without creating anything. +Creates app_user, auto-accepts pending workspace invites, and conditionally +creates a personal org + default workspace based on invite context. + +Decision tree: + 1. Create app_user (always) + 2. Auto-accept pending workspace invites (if any) + - include_org_membership=true → also add to that org (skip personal org) + - include_org_membership=false → external access only + 3. Has own projects OR no internal invites? + → Create personal org + default workspace + move projects + 4. Only has internal invites (no projects)? + → Skip personal org (they belong to the inviter's org) """ +from __future__ import annotations + from logging import getLogger +from datetime import datetime from fastapi import APIRouter, HTTPException @@ -23,12 +36,7 @@ async def complete_onboarding( body: OnboardingCompleteRequest, auth: DependencyDirectusSession, ) -> OnboardingCompleteResponse: - """Complete user onboarding. Creates org + workspace + moves projects. - - Idempotent: safe to call multiple times. If already onboarded, returns - existing IDs. If partially completed (e.g., app_user exists but no org), - picks up where it left off. - """ + """Complete user onboarding. Idempotent — safe to call multiple times.""" directus_user_id = auth.user_id org_name = body.org_name.strip() @@ -52,84 +60,101 @@ async def complete_onboarding( logger.info(f"Created app_user {app_user['id']} for directus user {directus_user_id}") app_user_id = app_user["id"] + app_user_email = app_user.get("email", "") - # ── Step 2: Get or create org ── + # ── Step 2: Auto-accept pending workspace invites ── - existing_orgs = await async_directus.get_items( - "org_membership", + now = datetime.utcnow().isoformat() + joined_an_org = False + first_workspace_id = None + + pending_invites = await async_directus.get_items( + "workspace_invite", { "query": { "filter": { - "user_id": {"_eq": app_user_id}, - "role": {"_eq": "owner"}, - "deleted_at": {"_null": True}, + "email": {"_eq": app_user_email}, + "accepted_at": {"_null": True}, + "expires_at": {"_gt": now}, }, - "fields": ["org_id"], - "limit": 1, + "fields": [ + "id", "workspace_id", "role", + "include_org_membership", "expires_at", + ], + "limit": -1, } }, ) - if isinstance(existing_orgs, list) and len(existing_orgs) > 0: - org_id = existing_orgs[0]["org_id"] - logger.info(f"User {app_user_id} already owns org {org_id}, skipping org creation") - else: - org_id = generate_uuid() - await async_directus.create_item("org", { - "id": org_id, - "name": org_name, - "created_by": app_user_id, - }) - await async_directus.create_item("org_membership", { - "id": generate_uuid(), - "org_id": org_id, - "user_id": app_user_id, - "role": "owner", - }) - logger.info(f"Created org {org_id} '{org_name}' for user {app_user_id}") - - # ── Step 3: Get or create default workspace ── - - existing_workspaces = await async_directus.get_items( - "workspace", - { - "query": { - "filter": { - "org_id": {"_eq": org_id}, - "is_default": {"_eq": True}, + if isinstance(pending_invites, list): + for invite in pending_invites: + ws_id = invite.get("workspace_id") + if not ws_id: + continue + + # Check workspace exists and get its org + ws = await async_directus.get_item("workspace", ws_id) + if not ws or ws.get("deleted_at"): + continue + + is_org_invite = invite.get("include_org_membership", False) + is_external = not is_org_invite + + # If org member invite, add to that org + if is_org_invite and ws.get("org_id"): + org_id = ws["org_id"] + existing_org_mem = await async_directus.get_items( + "org_membership", + {"query": {"filter": { + "org_id": {"_eq": org_id}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, "limit": 1}}, + ) + if not (isinstance(existing_org_mem, list) and len(existing_org_mem) > 0): + await async_directus.create_item("org_membership", { + "id": generate_uuid(), + "org_id": org_id, + "user_id": app_user_id, + "role": "member", + }) + logger.info(f"Auto-added {app_user_email} to org {org_id} via invite") + joined_an_org = True + + # Create workspace membership + existing_ws_mem = await async_directus.get_items( + "workspace_membership", + {"query": {"filter": { + "workspace_id": {"_eq": ws_id}, + "user_id": {"_eq": app_user_id}, "deleted_at": {"_null": True}, - }, - "fields": ["id"], - "limit": 1, - } - }, - ) + }, "limit": 1}}, + ) + if not (isinstance(existing_ws_mem, list) and len(existing_ws_mem) > 0): + await async_directus.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": ws_id, + "user_id": app_user_id, + "role": invite.get("role", "member"), + "source": "direct", + "is_external": is_external, + }) + logger.info( + f"Auto-accepted invite: {app_user_email} → workspace {ws_id} " + f"(role: {invite.get('role')}, external: {is_external})" + ) + + if not first_workspace_id: + first_workspace_id = ws_id - if isinstance(existing_workspaces, list) and len(existing_workspaces) > 0: - workspace_id = existing_workspaces[0]["id"] - logger.info(f"Default workspace {workspace_id} already exists for org {org_id}") - else: - workspace_id = generate_uuid() - await async_directus.create_item("workspace", { - "id": workspace_id, - "org_id": org_id, - "name": "Default", - "is_default": True, - "tier": "pioneer", - "created_by": app_user_id, - }) - await async_directus.create_item("workspace_membership", { - "id": generate_uuid(), - "workspace_id": workspace_id, - "user_id": app_user_id, - "role": "owner", - "source": "inherited", - }) - logger.info(f"Created default workspace {workspace_id} for org {org_id}") - - # ── Step 4: Move user's projects into the workspace ── - - projects = await async_directus.get_items( + # Mark invite as accepted + await async_directus.update_item("workspace_invite", invite["id"], { + "accepted_at": now, + }) + + # ── Step 3: Check if user has their own projects ── + + user_projects = await async_directus.get_items( "project", { "query": { @@ -138,26 +163,125 @@ async def complete_onboarding( "workspace_id": {"_null": True}, }, "fields": ["id"], - "limit": -1, + "limit": 1, } }, ) + has_own_projects = isinstance(user_projects, list) and len(user_projects) > 0 + + # ── Step 4: Decide whether to create personal org + workspace ── + # + # Create personal org if: + # - User has their own projects (need somewhere to put them) + # - User has NO internal org invites (they need their own org) + # Skip personal org if: + # - User joined an org via invite AND has no projects of their own - moved_count = 0 - if isinstance(projects, list): - for project in projects: - await async_directus.update_item( + org_id = None + workspace_id = first_workspace_id # default to first invited workspace + + needs_personal_org = has_own_projects or not joined_an_org + + if needs_personal_org: + # Check if already has a personal org + existing_orgs = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "user_id": {"_eq": app_user_id}, + "role": {"_eq": "owner"}, + "deleted_at": {"_null": True}, + }, + "fields": ["org_id"], + "limit": 1, + } + }, + ) + + if isinstance(existing_orgs, list) and len(existing_orgs) > 0: + org_id = existing_orgs[0]["org_id"] + else: + org_id = generate_uuid() + await async_directus.create_item("org", { + "id": org_id, + "name": org_name, + "created_by": app_user_id, + }) + await async_directus.create_item("org_membership", { + "id": generate_uuid(), + "org_id": org_id, + "user_id": app_user_id, + "role": "owner", + }) + logger.info(f"Created personal org {org_id} '{org_name}' for {app_user_email}") + + # Create default workspace in personal org + existing_ws = await async_directus.get_items( + "workspace", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "is_default": {"_eq": True}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + + if isinstance(existing_ws, list) and len(existing_ws) > 0: + personal_ws_id = existing_ws[0]["id"] + else: + personal_ws_id = generate_uuid() + await async_directus.create_item("workspace", { + "id": personal_ws_id, + "org_id": org_id, + "name": "Default", + "is_default": True, + "tier": "pioneer", + "created_by": app_user_id, + }) + await async_directus.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": personal_ws_id, + "user_id": app_user_id, + "role": "owner", + "source": "inherited", + }) + logger.info(f"Created default workspace {personal_ws_id} for org {org_id}") + + # Move user's orphaned projects into the personal workspace + if has_own_projects: + all_projects = await async_directus.get_items( "project", - project["id"], - {"workspace_id": workspace_id}, + { + "query": { + "filter": { + "directus_user_id": {"_eq": directus_user_id}, + "workspace_id": {"_null": True}, + }, + "fields": ["id"], + "limit": -1, + } + }, ) - moved_count += 1 + moved = 0 + if isinstance(all_projects, list): + for p in all_projects: + await async_directus.update_item("project", p["id"], { + "workspace_id": personal_ws_id, + }) + moved += 1 + if moved > 0: + logger.info(f"Moved {moved} projects into workspace {personal_ws_id}") - if moved_count > 0: - logger.info(f"Moved {moved_count} projects into workspace {workspace_id}") + workspace_id = personal_ws_id return OnboardingCompleteResponse( app_user_id=app_user_id, - org_id=org_id, - workspace_id=workspace_id, + org_id=org_id or "", + workspace_id=workspace_id or "", ) diff --git a/echo/server/dembrane/api/v2/projects.py b/echo/server/dembrane/api/v2/projects.py new file mode 100644 index 00000000..7a765788 --- /dev/null +++ b/echo/server/dembrane/api/v2/projects.py @@ -0,0 +1,98 @@ +"""V2 project endpoints — workspace-aware operations.""" + +from logging import getLogger + +from fastapi import APIRouter, HTTPException + +from dembrane.app_user import get_app_user_or_raise +from dembrane.directus_async import async_directus +from dembrane.api.v2.schemas import MoveProjectRequest, MoveProjectResponse +from dembrane.api.dependency_auth import DependencyDirectusSession + +router = APIRouter() +logger = getLogger("api.v2.projects") + + +@router.post("/{project_id}/move", response_model=MoveProjectResponse) +async def move_project( + project_id: str, + body: MoveProjectRequest, + auth: DependencyDirectusSession, +) -> MoveProjectResponse: + """Move a project to a different workspace. + + Requires admin/owner on BOTH source and target workspace. + """ + app_user = await get_app_user_or_raise(auth.user_id) + app_user_id = app_user["id"] + target_workspace_id = body.target_workspace_id + + # Fetch the project + project = await async_directus.get_item("project", project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + if project.get("deleted_at"): + raise HTTPException(status_code=404, detail="Project not found") + + source_workspace_id = project.get("workspace_id") + + # Check access to source workspace (if project is in one) + if source_workspace_id: + source_membership = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": source_workspace_id}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "limit": 1, + } + }, + ) + if not isinstance(source_membership, list) or len(source_membership) == 0: + raise HTTPException(status_code=403, detail="No access to source workspace") + source_role = source_membership[0].get("role", "") + if source_role not in ("admin", "owner"): + raise HTTPException(status_code=403, detail="Must be admin or owner of source workspace") + + # Check access to target workspace + target_membership = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": target_workspace_id}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "limit": 1, + } + }, + ) + if not isinstance(target_membership, list) or len(target_membership) == 0: + raise HTTPException(status_code=403, detail="No access to target workspace") + target_role = target_membership[0].get("role", "") + if target_role not in ("admin", "owner"): + raise HTTPException(status_code=403, detail="Must be admin or owner of target workspace") + + # Verify target workspace exists and is not deleted + target_workspace = await async_directus.get_item("workspace", target_workspace_id) + if not target_workspace or target_workspace.get("deleted_at"): + raise HTTPException(status_code=404, detail="Target workspace not found") + + # Move the project + await async_directus.update_item("project", project_id, { + "workspace_id": target_workspace_id, + }) + + logger.info( + f"Moved project {project_id} from workspace {source_workspace_id} " + f"to {target_workspace_id} by user {app_user_id}" + ) + + return MoveProjectResponse( + project_id=project_id, + workspace_id=target_workspace_id, + ) diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py index 51cf32c5..0d44af77 100644 --- a/echo/server/dembrane/api/v2/schemas.py +++ b/echo/server/dembrane/api/v2/schemas.py @@ -4,12 +4,20 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, EmailStr # ── /v2/me ── +class OrgSummary(BaseModel): + """Org membership info for the current user.""" + + id: str + name: str + role: str # owner / admin / member + + class MeResponse(BaseModel): """Lightweight user profile. Called on every page load — must be fast.""" @@ -19,6 +27,8 @@ class MeResponse(BaseModel): display_name: str avatar: Optional[str] = None onboarding_completed: bool + orgs: list[OrgSummary] = [] + has_pending_invites: bool = False # ── /v2/onboarding ── @@ -37,6 +47,13 @@ class OnboardingCompleteResponse(BaseModel): # ── /v2/workspaces ── +class MemberPreview(BaseModel): + """Minimal member info for avatar bubbles.""" + + display_name: str + avatar: Optional[str] = None + + class WorkspaceSummary(BaseModel): id: str name: str @@ -48,6 +65,7 @@ class WorkspaceSummary(BaseModel): project_count: int member_count: int is_external: bool + members_preview: list[MemberPreview] = [] class WorkspaceListResponse(BaseModel): @@ -58,8 +76,9 @@ class WorkspaceListResponse(BaseModel): class WorkspaceInviteRequest(BaseModel): - email: str + email: EmailStr role: str = "member" + is_org_member: bool = False # True = add to org, False = external class WorkspaceInviteResponse(BaseModel): diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py new file mode 100644 index 00000000..a71f03f3 --- /dev/null +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -0,0 +1,202 @@ +"""V2 workspace endpoints — list accessible workspaces.""" + +from logging import getLogger + +from fastapi import APIRouter + +from dembrane.app_user import resolve_app_user +from dembrane.directus_async import async_directus +from dembrane.api.v2.schemas import MemberPreview, WorkspaceSummary, WorkspaceListResponse +from dembrane.api.dependency_auth import DependencyDirectusSession + +router = APIRouter() +logger = getLogger("api.v2.workspaces") + + +@router.get("", response_model=WorkspaceListResponse) +async def list_workspaces( + auth: DependencyDirectusSession, +) -> WorkspaceListResponse: + """List all workspaces accessible to the current user. + + Used by the workspace selector. Returns workspace details with + role, project count, and member count. + """ + app_user = await resolve_app_user(auth.user_id) + if not app_user: + return WorkspaceListResponse(workspaces=[]) + + app_user_id = app_user["id"] + + # Get all active workspace memberships for this user + memberships = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "fields": [ + "workspace_id", + "role", + "source", + "is_external", + ], + "limit": -1, + } + }, + ) + + if not isinstance(memberships, list) or len(memberships) == 0: + return WorkspaceListResponse(workspaces=[]) + + # Fetch workspace details for all memberships + workspace_ids = [m["workspace_id"] for m in memberships if m.get("workspace_id")] + + workspaces = await async_directus.get_items( + "workspace", + { + "query": { + "filter": { + "id": {"_in": workspace_ids}, + "deleted_at": {"_null": True}, + }, + "fields": [ + "id", + "name", + "org_id", + "is_default", + "tier", + ], + "limit": -1, + } + }, + ) + + if not isinstance(workspaces, list): + workspaces = [] + + # Build a map of workspace_id -> workspace + ws_map = {ws["id"]: ws for ws in workspaces} + + # Fetch org names + org_ids = list({ws.get("org_id") for ws in workspaces if ws.get("org_id")}) + org_map: dict[str, str] = {} + if org_ids: + orgs = await async_directus.get_items( + "org", + { + "query": { + "filter": {"id": {"_in": org_ids}}, + "fields": ["id", "name"], + "limit": -1, + } + }, + ) + if isinstance(orgs, list): + org_map = {o["id"]: o.get("name", "") for o in orgs} + + # Count projects and members per workspace + results: list[WorkspaceSummary] = [] + + for membership in memberships: + ws_id = membership.get("workspace_id") + ws = ws_map.get(ws_id) + if not ws: + continue + + # Count projects in this workspace + project_count_result = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_eq": ws_id}, + "deleted_at": {"_null": True}, + }, + "aggregate": {"count": ["id"]}, + } + }, + ) + project_count = 0 + if isinstance(project_count_result, list) and len(project_count_result) > 0: + project_count = int(project_count_result[0].get("count", {}).get("id", 0)) + + # Count members in this workspace + member_count_result = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": ws_id}, + "deleted_at": {"_null": True}, + }, + "aggregate": {"count": ["id"]}, + } + }, + ) + member_count = 0 + if isinstance(member_count_result, list) and len(member_count_result) > 0: + member_count = int(member_count_result[0].get("count", {}).get("id", 0)) + + # Fetch first 4 members for avatar preview bubbles + members_preview: list[MemberPreview] = [] + preview_memberships = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": ws_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["user_id"], + "limit": 4, + } + }, + ) + if isinstance(preview_memberships, list) and len(preview_memberships) > 0: + preview_user_ids = [m["user_id"] for m in preview_memberships if m.get("user_id")] + if preview_user_ids: + preview_users = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"id": {"_in": preview_user_ids}}, + "fields": ["id", "display_name", "directus_user_id"], + "limit": 4, + } + }, + ) + if isinstance(preview_users, list): + # Fetch avatars from directus_users + du_ids = [u["directus_user_id"] for u in preview_users if u.get("directus_user_id")] + avatar_map: dict[str, str | None] = {} + if du_ids: + du_profiles = await async_directus.get_users( + {"query": {"filter": {"id": {"_in": du_ids}}, "fields": ["id", "avatar"], "limit": 4}} + ) + if isinstance(du_profiles, list): + avatar_map = {u["id"]: u.get("avatar") for u in du_profiles} + + for u in preview_users: + members_preview.append(MemberPreview( + display_name=u.get("display_name", ""), + avatar=avatar_map.get(u.get("directus_user_id", "")) + )) + + results.append(WorkspaceSummary( + id=ws["id"], + name=ws.get("name", ""), + org_id=ws.get("org_id", ""), + org_name=org_map.get(ws.get("org_id", ""), ""), + role=membership.get("role", ""), + is_default=ws.get("is_default", False), + tier=ws.get("tier", "pioneer"), + project_count=project_count, + member_count=member_count, + is_external=membership.get("is_external", False), + members_preview=members_preview, + )) + + return WorkspaceListResponse(workspaces=results) diff --git a/echo/server/dembrane/directus.py b/echo/server/dembrane/directus.py index 8f2b3346..0b510ac8 100644 --- a/echo/server/dembrane/directus.py +++ b/echo/server/dembrane/directus.py @@ -8,7 +8,9 @@ from dataclasses import dataclass from urllib.parse import urljoin -import httpx +import urllib3 +import requests +from urllib3.exceptions import InsecureRequestWarning from dembrane.settings import get_settings @@ -54,7 +56,7 @@ class DirectusBadRequest(DirectusGenericException): """Exception raised for bad requests to Directus API (e.g., assertion errors).""" -def is_recoverable_error(response: httpx.Response) -> bool: +def is_recoverable_error(response: requests.Response) -> bool: """ Check if the response status code indicates a recoverable error. @@ -75,7 +77,7 @@ def make_request_with_retry( max_retries: int = 3, retry_delay: float = 1.0, **kwargs: Any, -) -> httpx.Response: +) -> requests.Response: """ Make an HTTP request with retry logic for recoverable errors. @@ -85,15 +87,14 @@ def make_request_with_retry( url: URL to make the request to max_retries: Maximum number of retry attempts retry_delay: Initial delay between retries in seconds - **kwargs: Additional arguments to pass to httpx + **kwargs: Additional arguments to pass to requests Returns: - httpx.Response: The response from the server + requests.Response: The response from the server Raises: - httpx.HTTPError: If the request fails after all retries + requests.exceptions.RequestException: If the request fails after all retries """ - kwargs.setdefault("timeout", 30.0) retries = 0 while retries < max_retries: try: @@ -104,7 +105,7 @@ def make_request_with_retry( if client.email and client.password: client.login(client.email, client.password) - response = httpx.request(method, url, **kwargs) + response = requests.request(method, url, **kwargs) if is_recoverable_error(response): retries += 1 @@ -126,7 +127,7 @@ def make_request_with_retry( return response - except httpx.HTTPError as exc: + except requests.exceptions.RequestException as exc: if getattr(exc, "response", None) is not None: if exc.response is not None and not is_recoverable_error(exc.response): raise @@ -139,7 +140,7 @@ def make_request_with_retry( time.sleep(wait_time) continue - return httpx.request(method, url, **kwargs) + return requests.request(method, url, **kwargs) class DirectusClientProtocol(Protocol): @@ -180,6 +181,9 @@ def __init__( verify (bool): Whether to verify SSL certificates (default: False). """ self.verify = verify + if not self.verify: + urllib3.disable_warnings(category=InsecureRequestWarning) + self.url = url self.static_token: Optional[str] = None self.temporary_token: Optional[str] = None @@ -215,11 +219,10 @@ def login(self, email: Optional[str] = None, password: Optional[str] = None) -> self.email = email self.password = password - response = httpx.post( + response = requests.post( f"{self.url}/auth/login", json={"email": email, "password": password}, verify=self.verify, - timeout=30.0, ) auth_data = self._get_validated_auth_data(response) self.static_token = None @@ -238,17 +241,16 @@ def logout(self, refresh_token: Optional[str] = None) -> None: try: if refresh_token is None: refresh_token = self.refresh_token - response = httpx.post( + response = requests.post( f"{self.url}/auth/logout", headers={"Authorization": f"Bearer {self.get_token()}"}, json={"refresh_token": refresh_token}, verify=self.verify, - timeout=30.0, ) response.raise_for_status() self.temporary_token = None self.refresh_token = None - except httpx.HTTPStatusError as exc: + except requests.exceptions.HTTPError as exc: raise DirectusAuthError(f"Failed to logout from Directus API: {exc}") from exc def refresh(self, refresh_token: Optional[str] = None) -> dict: @@ -260,11 +262,10 @@ def refresh(self, refresh_token: Optional[str] = None) -> dict: """ if refresh_token is None: refresh_token = self.refresh_token - response = httpx.post( + response = requests.post( f"{self.url}/auth/refresh", json={"refresh_token": refresh_token, "mode": "json"}, verify=self.verify, - timeout=30.0, ) auth_data = self._get_validated_auth_data(response) self.temporary_token = auth_data["access_token"] @@ -329,7 +330,7 @@ def get(self, path: str, output_type: str = "json", **kwargs: Any) -> Any: if output_type == "csv": return data.text return data_json["data"] - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -354,7 +355,7 @@ def post(self, path: str, **kwargs: Any) -> Dict[str, Any]: raise AssertionError(response.text) return response.json() - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -377,11 +378,15 @@ def search(self, path: str, query: Optional[Dict[str, Any]] = None, **kwargs: An ) try: - return response.json()["data"] + resp_json = response.json() + if "errors" in resp_json: + logger.warning("Directus SEARCH returned errors: %s", resp_json["errors"]) + return [] + return resp_json["data"] except Exception: # noqa: BLE001 - want best-effort fallback logger.exception("Failed to parse SEARCH response JSON") - return {"error": "No data found for this request"} - except httpx.ConnectError as exc: + return [] + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -404,7 +409,7 @@ def delete(self, path: str, **kwargs: Any) -> None: ) if response.status_code != 204: raise AssertionError(response.text) - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -430,7 +435,7 @@ def patch(self, path: str, **kwargs: Any) -> Dict[str, Any]: raise AssertionError(response.text) return response.json() - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -491,7 +496,7 @@ def retrieve_file(self, file_id: str, **kwargs: Any) -> Union[str, bytes]: if response.status_code != 200: raise AssertionError(response.text) return response.content - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -516,7 +521,7 @@ def download_file(self, file_id: str, file_path: str) -> None: raise AssertionError(response.text) with open(file_path, "wb") as file: file.write(response.content) - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -554,7 +559,7 @@ def download_photo( raise AssertionError(response.text) with open(file_path, "wb") as file: file.write(response.content) - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -631,7 +636,7 @@ def upload_file(self, file_path: str, data: Dict[str, Any] | None = None) -> Dic result = patched["data"] return result - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc @@ -841,7 +846,7 @@ def search_query( words = [query] return {"query": {"search": words}} - def _validate_auth_response(self, response: httpx.Response) -> httpx.Response: + def _validate_auth_response(self, response: requests.Response) -> requests.Response: """ Validate the authentication response from the Directus API. """ @@ -871,11 +876,11 @@ def _validate_auth_response(self, response: httpx.Response) -> httpx.Response: KeyError, ValueError, json.JSONDecodeError, - httpx.HTTPStatusError, + requests.exceptions.HTTPError, ) as exc: raise DirectusAuthError(f"Invalid response format from API: {exc}") from exc - def _get_validated_auth_data(self, response: httpx.Response) -> dict: + def _get_validated_auth_data(self, response: requests.Response) -> dict: """ Get the validated authentication data from the Directus API. """ @@ -952,7 +957,7 @@ def directus_client_context( yield active_client except DirectusGenericException: raise - except httpx.ConnectError as exc: + except requests.exceptions.ConnectionError as exc: raise DirectusServerError(exc) from exc except AssertionError as exc: raise DirectusBadRequest(exc) from exc diff --git a/echo/server/dembrane/email.py b/echo/server/dembrane/email.py new file mode 100644 index 00000000..62c881f2 --- /dev/null +++ b/echo/server/dembrane/email.py @@ -0,0 +1,195 @@ +""" +Reusable email sending via SendGrid. + +Works both sync (Dramatiq tasks) and async (FastAPI endpoints). +Uses the SendGrid API (not SMTP) for reliability. + +Usage: + + # Async (in FastAPI endpoints) + from dembrane.email import send_email + await send_email( + to="user@example.com", + subject="Welcome", + html="

Hello

", + ) + + # Sync (in Dramatiq tasks) + from dembrane.email import send_email_sync + send_email_sync( + to="user@example.com", + subject="Welcome", + html="

Hello

", + ) + + # Templated email + from dembrane.email import send_email + await send_email( + to="user@example.com", + subject="You're invited", + template="workspace_invite", + template_data={"workspace_name": "Acme", "inviter_name": "Sam", "invite_url": "..."}, + ) +""" + +from __future__ import annotations + +from logging import getLogger +from pathlib import Path +from typing import Optional + +import jinja2 +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, Email, To, Content + +from dembrane.settings import get_settings + +logger = getLogger("dembrane.email") + +# Template directory +TEMPLATE_DIR = Path(__file__).parent.parent / "email_templates" + +# Jinja2 environment for email templates +_jinja_env: Optional[jinja2.Environment] = None + + +def _get_jinja_env() -> jinja2.Environment: + global _jinja_env + if _jinja_env is None: + _jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(str(TEMPLATE_DIR)), + autoescape=True, + ) + return _jinja_env + + +def _render_template(template_name: str, data: dict) -> str: + """Render an email template. Falls back to empty string if template missing.""" + try: + env = _get_jinja_env() + template = env.get_template(f"{template_name}.html") + return template.render(**data) + except jinja2.TemplateNotFound: + logger.error(f"Email template not found: {template_name}") + raise + except Exception: + logger.exception(f"Failed to render email template: {template_name}") + raise + + +def _build_message( + to: str | list[str], + subject: str, + html: str | None = None, + plain_text: str | None = None, + template: str | None = None, + template_data: dict | None = None, + from_email: str | None = None, + from_name: str | None = None, +) -> Mail: + """Build a SendGrid Mail object.""" + settings = get_settings() + + sender = Email( + email=from_email or settings.email.from_email, + name=from_name or settings.email.from_name, + ) + + recipients = [To(addr) for addr in (to if isinstance(to, list) else [to])] + + # Resolve content + if template: + html = _render_template(template, template_data or {}) + + if not html and not plain_text: + raise ValueError("Either html, plain_text, or template must be provided") + + content = Content("text/html", html) if html else Content("text/plain", plain_text or "") + + message = Mail() + message.from_email = sender + message.subject = subject + message.to = recipients + message.content = content + + return message + + +def send_email_sync( + to: str | list[str], + subject: str, + html: str | None = None, + plain_text: str | None = None, + template: str | None = None, + template_data: dict | None = None, + from_email: str | None = None, + from_name: str | None = None, +) -> bool: + """Send email synchronously. For use in Dramatiq tasks. + + Returns True on success, False on failure (never raises). + """ + settings = get_settings() + api_key = settings.email.sendgrid_api_key + + if not api_key: + logger.warning("SendGrid API key not configured, skipping email send") + return False + + try: + message = _build_message( + to=to, + subject=subject, + html=html, + plain_text=plain_text, + template=template, + template_data=template_data, + from_email=from_email, + from_name=from_name, + ) + + sg = SendGridAPIClient(api_key) + response = sg.send(message) + + if response.status_code >= 400: + logger.error( + f"SendGrid error {response.status_code}: {response.body}" + ) + return False + + logger.info(f"Email sent to {to}: {subject} (status: {response.status_code})") + return True + + except Exception: + logger.exception(f"Failed to send email to {to}: {subject}") + return False + + +async def send_email( + to: str | list[str], + subject: str, + html: str | None = None, + plain_text: str | None = None, + template: str | None = None, + template_data: dict | None = None, + from_email: str | None = None, + from_name: str | None = None, +) -> bool: + """Send email asynchronously. For use in FastAPI endpoints. + + Runs the sync SendGrid call in a thread pool to avoid blocking. + Returns True on success, False on failure (never raises). + """ + from dembrane.async_helpers import run_in_thread_pool + + return await run_in_thread_pool( + send_email_sync, + to=to, + subject=subject, + html=html, + plain_text=plain_text, + template=template, + template_data=template_data, + from_email=from_email, + from_name=from_name, + ) diff --git a/echo/server/dembrane/settings.py b/echo/server/dembrane/settings.py index e87dc223..c3d6ad1e 100644 --- a/echo/server/dembrane/settings.py +++ b/echo/server/dembrane/settings.py @@ -313,6 +313,28 @@ class DirectusSettings(BaseSettings): ) +class EmailSettings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env", extra="ignore", case_sensitive=False) + + sendgrid_api_key: str = Field( + default="", + alias="SENDGRID_API_KEY", + validation_alias=AliasChoices( + "SENDGRID_API_KEY", "EMAIL__SENDGRID_API_KEY", "EMAIL_SMTP_PASSWORD" + ), + ) + from_email: str = Field( + default="do-not-reply@dembrane.com", + alias="EMAIL_FROM", + validation_alias=AliasChoices("EMAIL_FROM", "EMAIL__FROM"), + ) + from_name: str = Field( + default="dembrane", + alias="EMAIL_FROM_NAME", + validation_alias=AliasChoices("EMAIL_FROM_NAME", "EMAIL__FROM_NAME"), + ) + + class DatabaseSettings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore", case_sensitive=False) @@ -559,6 +581,7 @@ def __init__(self) -> None: self.urls = URLSettings() self.feature_flags = FeatureFlagSettings() self.directus = DirectusSettings() + self.email = EmailSettings() self.database = DatabaseSettings() self.cache = CacheSettings() self.storage = StorageSettings() diff --git a/echo/server/email_templates/workspace_invite.html b/echo/server/email_templates/workspace_invite.html new file mode 100644 index 00000000..9696c22f --- /dev/null +++ b/echo/server/email_templates/workspace_invite.html @@ -0,0 +1,64 @@ + + + + + + + +
+ {{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. +
+ + + + +
+ + + + + + + + + + + + + + + +
+ dembrane +
+

+ You've been invited to collaborate +

+

+ {{ inviter_name }} invited you to join + {{ workspace_name }} on dembrane. +

+ + + + + + +
+ + Accept invitation + +
+ +

+ This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it. +

+
+

+ dembrane - conversations that matter +

+
+
+ + diff --git a/echo/server/pyproject.toml b/echo/server/pyproject.toml index 6c06fe5e..65df2318 100644 --- a/echo/server/pyproject.toml +++ b/echo/server/pyproject.toml @@ -26,7 +26,6 @@ dependencies = [ # Config "python-dotenv==1.0.*", # Other - "httpx>=0.28", "backoff==2.2.*", "aiofiles==23.2.*", "sentry-sdk==2.2.1", @@ -79,6 +78,8 @@ dependencies = [ "gevent>=25.4.2", "pylance>=0.30.0", "google-cloud-aiplatform==1.120.*", + "sendgrid>=6.12.5", + "email-validator>=2.3.0", ] [tool.setuptools] diff --git a/echo/server/uv.lock b/echo/server/uv.lock index e4dbbce6..b57fe799 100644 --- a/echo/server/uv.lock +++ b/echo/server/uv.lock @@ -455,13 +455,13 @@ dependencies = [ { name = "configparser" }, { name = "dramatiq", extra = ["redis", "watch"] }, { name = "dramatiq-workflow" }, + { name = "email-validator" }, { name = "fastapi" }, { name = "ffmpeg-python" }, { name = "flower" }, { name = "gevent" }, { name = "google-cloud-aiplatform" }, { name = "gunicorn" }, - { name = "httpx" }, { name = "isort" }, { name = "jinja2" }, { name = "langchain" }, @@ -492,6 +492,7 @@ dependencies = [ { name = "pyyaml" }, { name = "ruff" }, { name = "scikit-learn" }, + { name = "sendgrid" }, { name = "sentry-dramatiq" }, { name = "sentry-sdk" }, { name = "setuptools" }, @@ -515,13 +516,13 @@ requires-dist = [ { name = "configparser", specifier = "==7.2.0" }, { name = "dramatiq", extras = ["redis", "watch"], specifier = "==1.17.*" }, { name = "dramatiq-workflow", specifier = "==0.2.*" }, + { name = "email-validator", specifier = ">=2.3.0" }, { name = "fastapi", specifier = "==0.109.*" }, { name = "ffmpeg-python", specifier = ">=0.2.0" }, { name = "flower", specifier = ">=2.0.1" }, { name = "gevent", specifier = ">=25.4.2" }, { name = "google-cloud-aiplatform", specifier = "==1.120.*" }, { name = "gunicorn", specifier = "==21.2.*" }, - { name = "httpx", specifier = ">=0.28" }, { name = "isort", specifier = "==5.13.*" }, { name = "jinja2", specifier = "==3.1.*" }, { name = "langchain", specifier = "==0.1.*" }, @@ -553,6 +554,7 @@ requires-dist = [ { name = "pyyaml", specifier = "==6.0.2" }, { name = "ruff", specifier = ">=0.11.12" }, { name = "scikit-learn", specifier = "==1.4.*" }, + { name = "sendgrid", specifier = ">=6.12.5" }, { name = "sentry-dramatiq", specifier = "==0.3.*" }, { name = "sentry-sdk", specifier = "==2.2.1" }, { name = "setuptools", specifier = "==75.8.0" }, @@ -574,6 +576,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + [[package]] name = "docstring-parser" version = "0.17.0" @@ -628,6 +639,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, ] +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + [[package]] name = "execnet" version = "2.1.1" @@ -1972,6 +1996,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, ] +[[package]] +name = "python-http-client" +version = "3.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/fa/284e52a8c6dcbe25671f02d217bf2f85660db940088faf18ae7a05e97313/python_http_client-3.3.7.tar.gz", hash = "sha256:bf841ee45262747e00dec7ee9971dfb8c7d83083f5713596488d67739170cea0", size = 9377, upload-time = "2022-03-09T20:23:56.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/31/9b360138f4e4035ee9dac4fe1132b6437bd05751aaf1db2a2d83dc45db5f/python_http_client-3.3.7-py3-none-any.whl", hash = "sha256:ad371d2bbedc6ea15c26179c6222a78bc9308d272435ddf1d5c84f068f249a36", size = 8352, upload-time = "2022-03-09T20:23:54.862Z" }, +] + [[package]] name = "python-jose" version = "3.5.0" @@ -2227,6 +2260,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" }, ] +[[package]] +name = "sendgrid" +version = "6.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "python-http-client" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/fa/f718b2b953f99c1f0085811598ac7e31ccbd4229a81ec2a5290be868187a/sendgrid-6.12.5.tar.gz", hash = "sha256:ea9aae30cd55c332e266bccd11185159482edfc07c149b6cd15cf08869fabdb7", size = 50310, upload-time = "2025-09-19T06:23:09.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/55/b3c3880a77082e8f7374954e0074aafafaa9bc78bdf9c8f5a92c2e7afc6a/sendgrid-6.12.5-py3-none-any.whl", hash = "sha256:96f92cc91634bf552fdb766b904bbb53968018da7ae41fdac4d1090dc0311ca8", size = 102173, upload-time = "2025-09-19T06:23:07.93Z" }, +] + [[package]] name = "sentry-dramatiq" version = "0.3.3" @@ -2716,6 +2763,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + [[package]] name = "yarl" version = "1.22.0" From 66db587219415c048f8c38f920307773fc94ffb3 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 15:55:26 +0000 Subject: [PATCH 028/208] feat: onboarding page, login redirect, and header menu integration 3-step onboarding flow: - Loading screen ("Setting things up for you") - Org name step (brand illustration hero + "Team name" form) - Invite step (email fields + send invites) Login redirect: - After login, checks /v2/me for onboarding status - Fault-tolerant: errors never block login - 300ms delay ensures session cookie is available - "Welcome to dembrane" for new users, "Welcome back" for returning Header menu: - "Set up workspace" item (blue, sparkles icon) shown when not onboarded - Uses shared useV2Me hook with 60s staleTime Design: - Parchment background with soft radial gradient blur orbs - Brand illustration as hero image (520px width) - Staggered fade-in animations - "Use default" escape hatch for quick onboarding - Brand-compliant: no bold, Royal Blue primary, left-aligned --- .../illustrations/onboarding-banner.png | Bin 0 -> 1477500 bytes echo/frontend/src/Router.tsx | 13 + .../frontend/src/components/layout/Header.tsx | 13 + echo/frontend/src/hooks/useV2Me.ts | 30 ++ echo/frontend/src/routes/auth/Login.tsx | 30 +- .../src/routes/onboarding/OnboardingRoute.tsx | 451 ++++++++++++++++++ 6 files changed, 534 insertions(+), 3 deletions(-) create mode 100644 echo/frontend/public/illustrations/onboarding-banner.png create mode 100644 echo/frontend/src/hooks/useV2Me.ts create mode 100644 echo/frontend/src/routes/onboarding/OnboardingRoute.tsx diff --git a/echo/frontend/public/illustrations/onboarding-banner.png b/echo/frontend/public/illustrations/onboarding-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8ddc1310a4c03d00ab4d8a21befbe75ef9e19b GIT binary patch literal 1477500 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

    ^w-QESq!gK}3(fC({Y&uuw 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

    E3TEGuf4Lwt;{e>)LyU&6ZM#+FkC!^I7X+wa+*_ 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

    BM;Macpth3z;0h=&; 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

    +Cz_!ts~;CM{%g& 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%0DK(?GG$$dht5uGnL$rn9_cOXt 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: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

    ^*B&Tn6SyAF`WIAQmjyUF%rK%K0D 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 =6es19e@W4Ep`_aA`Lug7F(3 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 NAh`4g?64nN;Y_S}i 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�Ew_K;+yD9P@89^2 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 z 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


    L5W?C_dhbK=N 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

    Q~z7e}+Gvdg>|d&O7ha4jw!xu;MiuLo0+w zmmrc8cizJK>h-oy{qj?`qeqXjpJu}9ZU&TWX2U|PwI|r481;qOg_56^-~QtE7q_ph zT=0;Z0GJrO(FB5pOSpL} zEK3bcA7r9CAS|)x&jX_yB!rO0@bL0MMOfdCUqFbu51wBh5o;!dG+?ryG$^?!7`2#t z6@wiX?Ty0WS}dGr33PrjkumAp(%W#Z$CMN1i}dvL?I_F@C#?e>_{&vZeb+->P*8-R z;Qa8NDG%J})I#qWPx&sIXIffpJ#E3uum(%RIt^XbD&CpJ=u7FS?%0IonyKN%t+M;u zk1H@UALsf6RxN|Dc=<>Bq?~W`y;ZmGqM?OZP?G{Dqd?HzDUR>~P1oxoe5~R)8fn>~ z1d{X);woPO0LOfQOt7wlu##i;nFc);kr+I+D4+;GN#sc{FP^F!U}^F%#GW(Vm}As_ zi{l~kL?nnZ73?`sPKGBH&MAR|Pr#GO0RTwaB?$0~24##4S`_3!3PCS}Az*D!y?*9L z&Ns2J1R%@}1H@x^XE8R2w0cR8Io?J87s>-GQH0@i!*hH;3U3KeLI@rElZ1}G0`SXj zRZdknW2IsI#C%KOj1>w6RtljD<{Rw->zo$UpLC&>$&C8;2I_m$|xVih80>9aLE&ozlQYKI;#=Cj>yTF;07whXlWIB^hm0geqf0d zT{oA{^#@X&C*Yr}>o^&5zyHAt@?&Jsiz*5VnmKopIP(a!f&v-#^bG#mR)J>}Eq}#K z0wC43v-u}*tV0?e+osk2{7*1$(S}mM^z^I}z3kRYw9*?F>h#nyQofmUN+rFjP`pzM zrJ-!A^uW?`?|~+)xq|2uw2Oe0pq!L}`LFnf1ycI{CGdLYkZvfc?f=(jG|4MeDRWY|oW__=@iyty~$AyQ-o2&3FSVK%NAY z!Z0oyU^3>#!5(@miXX+_sdC__8OEf534R%qJm>SK;$w1%Qgcl8`OMnP>%vk%hwrzr zUs^f9x5(N#H5m;(=@NA0YJt!O0C8>Lw%IhVBj_kfZ)h((UMmA^qzhYeGxv8K1>6qj zZrvGwGN$XtFt^Xfescj7wWq=Zk%gd+P5?S`7Qwq_36z%s5lC)p2GN02GKVo9$z?i& z=@_rl>V7-~h4df{4`Ch8KyQu-oF_uq17i(OP(pt`R4c@}5zZj*WC~**xT#3y%=*T{ zG3l#qQ}V^eXSPeAzC#%cpywt&37}4&&Ewi_ciCUf$S*$n?7h#9xKSk-#^dpPDi8?# z5wAgGUjgWGIPJiEeuntBKA(>u5hL7{Jf6?P+O^ghlfxbuNOUDSyDeI#MR$9=5$Wy@ zEz4gPUAJzXvC%x0GTn97KrdQeUY@(Nw<`_(-HLt$R*+E;_9?~~6#0*}9h2jojR33w zY7s#n#6#R4$Kdq3E7B?50A2KVd-M^v+b#Jug;JDXtlmG~*(4pEZ4&Ya@K{1@c00iF z1+pBTO>SC#vrL;hO(wx>Ojdf9*p(5FBr&+%=4yJhZq57XXcp|t2^4YK;c)~!A9(kX zUVpFJ_lzX3Po{ou4=giV_S9Q@S(3!$xY$_AS3`zhz9{g@HL79d?|uk956D2j~s0fhinF3 zh{O1nARFQ}&6NpR&TIhi=zY_c%nLFhoYM<2y~tST?L>JZ24DvNEZ`y1+Xs1M9JRo^ z%EE32C>;$5pH|T+(1~i5#B={njv@XRuUR0AfAk6L;mMpHTknI+Yez=9bZoAHTXib!GAkrF zh%u02eyf{<{)F4Xgkpvx1bTjJ7GWE;GZT<|c*AvxUzur^sD zQGcHON7E`SE-$o9^-$R6Iw0gqrm_Th6SfA(6NQAMCZ9 zc5klG1*fa%kxGvCxqK4ym3 z&2#G?SQ?gfoBUchI-dHDl-xM2|3Q|CCx2A`UuAcUl*&bYt)amhcH5>vC`(C>z$<~I zRpy~D!HDN5YbDl~pqJw>ye+$`ACcaDjj8SpA)Y;8&vZZGD$cp5YijL5WyAZTCXiWp zlLCV&&;S8r(LT)9>*Nz zz`T@H&W21c5$?rYcB0bvD9Mj~nfo))Akwm3f`^`wgr9oNMxX)!z2v0N`KY+7u$;yG zref*=MF1)>b*cCL`~RQ-C@i0vDOB2c7=mb8#iTbh8!TiJ3`D?-oiTeVgy^*L3&0D7 z$R`aL`S2`*1wd6-1;9)Il{)h0O5fgx4Z308#6uob>n3FGf%dx_;1^c(TZmKnE&~7| znx`yBtNcG#>NCG`gU)^MXn{>V+Pw>L9fbjPS*6hfttzKY$rlH}A;$h%B+?a-j!lO( z07mqz`Q@a+=n0(Sop>lJP0#Pt_I~SeEz(IR5k~0Td|2z*eN64ir~UP{0KXWRxS|fEJd>`+0tE7dHWSow)PSj6C$2LZ+KXi45Cgrurv4#-Hb}DVjXD=!RmJ~hD^O0Z%ET{(p z0UdeXJPe8_>8Navu@*E&lB%;uV^QUyr7sAhv>1GuG+*VB|F|XZ3G~P-J6@3wUHKuQ zcNK*3q(4ybv-pbumWH+lp_dv4dUv5!Xq(!e8yOMYvig zvhfk^^?k2v2O17&UA`_=wiGJ^gc|-n2Z&t6+Yvv3NF|sz@j>r#>C`^K2Qr`(T zRbE6ApV~#-Pe8*}ofqTC^a!qD+8ijl5y>mz-jnI^$cc#8%`g@P2!2uh8wS(m?y)C? zvC=W`oe~9x@-X>o^(nn?KKu@)z`8vVtbS>jgz-?SmVRH0_zxQ3$J90RXj0&8Q=mP) zpe@7S@~5~px(B??g)K?qc@YzZHFbTB3Im{@EoknX_$*s{I7dRL;`m)+P416y3r^%ENFOASTWRMGFoox5=1biBmi)oQ)k zvhLP(w&0PVyQ`}!GaO9h_k{yXU?9i~!xs>Nq3CLlz}Ce+0ez3>@n{T$Ww8*FC8K8| z2L7d7T&;rHUs2pxFeZT}7-wv*!*%*4!5SUlEv9F|vjnHaAy+usc2pjD;}I!5SSTeK zMN*nwBKaBl3b@WAfiM}a3670bXH_fbTR7V;a1E;CUL4iNaIPVKe~#BidWp z_hge0Bn1yVVR!(7tQ#M*YB$Q|>^U>QN4-?>9xagn8AEb%02oL4(K|touu40@{OI9Z z8ND<5B$9x|SgQ|Sh4yF`=gSF;bvs~>?eH~xarJ|7&|)Gz5{mgfEDE!n1h+rX;MIcMqdan;}6k3OOb>VNAvqXO{yXtdBFf(D30u!AA!95Mv>XA6OG*}N} zazAA;AByR!CWAVV!6fNr(u9_pq{;*kDBlmy_Kl^JpjhZym&6rUP zMi=y#+M?3ArB)(Azu4Sd-zF1JzHI@cFlX5mXwc5`bRFd?uGi1u$w;K|c2JkNxTwKmWxSabrR*w0nDd)04#>e9*0S>v;n>Oi&>) z+my=OONI~z5+VeGU0^K=J#PeTi+X}LM-JeK2}}!}k~P$kQyO#?*=e_Gw^}ylZCsF( zo^ySDOZ|d}HaxUC8jhyFzUOsUEDqipdSFLS2R`wEuE=8c_Uhy&w|p-Y3T;pinSVy3fJ4%XW>`9+tD}1CGKofD%38NcGzc7C z@VLd&*PhtXatxN`o2;JfzIfPwM6C8+Nh|1*_5)Px8Lj_}mfz`Ihi0#Rj@aui6CW-A zMIkBBbK`-Q(s;O&+Vk)e9y=nT9thRqO_oUCF)h91ZEH^1&d8_N?3e0laQ82!py@GR z9yrq>B_yO(__W!#iL2~NiFF-TLJouMv>HDNAQkFmQLdM43V!!s=JP!&7DJ?^Do<-s zX*1UXa4;DF6ig#HSIV{p3m^~~Mk0b=+_~bJ@c{^6+N5vKZ(;E1fbuv)5RD;PQ^}70 z2j!q}#XIkl$O~4*AO!2&@C{lsOUG3BX-Q9iPWt+2b@^O`QY30rd?0oG(n~KzUmAb- z_S!5x!>!3L|K!(#5zNw~4wvIkbLf*W&nJn4an1U{#(PUod2Sz?)&xC+5jso6awV)3 zsy&$(YamZ?4um^4wYE92)abDZi*)QbEPKBBm;${}k%7VLCg{cUh`li9KTE>y{ax$E zv7kFK1M`YWfr(9l^7I-@i}OKvCYcuxb}q&sPVfu09$=xbMq)bvfN6MB(&v9tJkxG8 zgz9H7HWAQagGVoVYf*Z-&@@t3ATib(K#uWE^U6>lED)y6{2;z>kN9@{T%xVJAX^Pc z-|k-na9N{-^#s;1UFBisY_tT5M=OT@T@Na-r%qw6+R9)q^yDgUES{MkGN#BObmBe| zR#IV2(|_=B@$Y_6;Asulkf2(BVh*Fw8FaD0Qlz730p{VW#96!)^J|$>7*LcU;AKn? zFJn|v37s)nalWN@oq~DNyZQU@IIpo3wI7w1?ng~M~}3=LoHJKi`Nu5M)kR&Nx_0aVotcJ>pooO1lSo5#hFBqaCz{vO#|zqh|yGs|Ym$FKSLkUD+nst+meEqm&q zC79fsbG_^f_~Z-seL*SKaeN{mhS!I$rKxzgXzC)FH)Wn=rV(c{dZ2e3ME!8nVg37$ zeP4TZ=c~&53xGr#ElVS9EUXha3>oZG39=p7##bey`nK@k$XAllAB>n3Ygws#w5?_+}tCxwLWQ9d#^mnvOOqA zf@^VYopvkwef8-iX})7pz@&glfpbRz7+S()Nx-Pzk(2I7B(y!4RO$7lDiKR)Gzi!F z>V9z-WlO=;v!TEYF9r@y&AP+_VF@+E+d>1tu=q_`9z6^zg8(O+xksuI-@$sHo*BV^ zQh)+JMv!`_wO0bix+HB_#CQ4=Uv4DwIzRe)p|a^L25^f|olZRvR# zn@guwwmkdL$lhKCzZ8$h@3Y(Od$F$iKCYJ|a_yAb&oE2IR7N~Y_f%I`pYtgQ`MX+tExA~dmPKQM*;pLTjztp{@%DHTik<<)w3T~D z06_czf^>pe(PgtZy0k8TebgU1YL9ph+p_gmcdom;GZS79wxMpR+Oj%#;cRHt1}Ztm zuO**dV(-`-Py6YDhti|I-dw*wR1}NGrowy6G5}Rp;#DtFwpg$)umG$;fC_;n0K=fz z$Pk{6ELIrffIOOBv{?rlM_{LB002M$Nkl-2R>x+h&7=_Qio=K5Ye2fz!B@b%MdFfCb}i#!-x4(@ND zU(D?XcxmJxsR$Yj31~+G9IWu-K~Mv|I9t<;z-uaiH;!G(LrVh2&h%g>L~{TPK^8-! zWmZofx+#D)sxP1glOCHR?fb-u`xZCGBLX^-w59z(!@JC7$R3>v9#3fa%qSoF4$DXI z3lN0xSL4>Scs6)>KsW*T(yKuILyA4YPZAPvb&DgmADwd(>U1e&PKD^~4m}#s=;1^I zu!OQR8x6_<#5M9Z@jTR$;|6($p&n(RjzA}RqoyG`dhJnd44F82vFs!CTJ0=Z27^cN zRzqWT3P^RTA5gN-p|6qWOYn;hfDOxm0G^ViL$g?Sfk*p%!jbhS$<3^nBU7MIv+h(g zIu8VV=VTELMmp^tdlyCy@D40+!=+QuY6g=6XOjX1z2Jk_9!f;~k6nDa5oePW6DuwK zdHoA0(HEf`F$rrUOwRpl?1@!Svozr0pM1T11`OMVJ%cOuwfs2y@iqU^@7Tqkh zNdc1rBT)brj1nm&NHQyC{=XY!=3m_aP9)s?!fMUp@82#{Ker0rR1SezV3qt8vn0K& z0BZ-XAIOg#U*NMgk5d-?>&Ilqm){G^tu;;kwaXI2K*BXE{*(^BY( z2NhEstUcs3az5D5f>{AQb13PA+j=F`0xK@CUS)GrdFDi(Lh*a&%l?0R7CC^6+#7^7 zkmxn|c{<9m`GNMpZqNsuFe8BRs5o#`MEKUXzNO!J=bfLxonc(L)YBLKc=+&PTG|{Q zbZ3UMVfe)}IfwPBr1}Y{PC3SqP(9>q0-bycW!Z>Ga1gsoD(C>bP#I2z{)_l<7*glM zj=a7faWUe63!-5Uo$6_MW_fl<3#}?ED0^Rb3rObVTlJy%(QZEg_V0+qC#TKdK>|1Lf}HsjuQSr=+n3F4-!818a;o^ zmxy*H5_xPK37yU(pdTwZG<@`<9|bW^E!=TqroM8vOYZz#msZzC!Q+Xv8g)1w)e4{v zA2Qlrd?vRw+*^L=)Jk*_J-uX!J+1RtS+KFA6#Yz%EsmTtvAZc`LpK;=M2sUix;})k zq0S!J^Dj?8+4-0f*s^bH4x8WNvj49=&H2-uti-N88}D{+4d<`}<3VRbUn{FZp&B_)vL0G`z2WasxeO*1vAPBa40Qox~IGc@p`f4PREp)^i)L!?!oDuNgeoZQlZ5G;dah7 z|EB2XKk%4p57YK@vt%D&)xx1I11X^o46>o(%Pn&6uK-+M-yUf8L=g;BCg)rfnVJnZK_ zl?W!VSBG}L2-ma^Al{E?hBK!C+l+e)*bh&RPB`RxbCrja(@vh-Rt*3pu73z%#91I5 zkjLM6TzF*Q%10lzW#XEWN@G8G^auJkKJpEH;gp4A6~V}5aIYKd>2cnpBHQm@{k=4H zG{P{pQjFoSBNc@evTDJqA-Q>>90zZ!QkYdJ$2*SWBN+HJ_?yngH5p7@nKIJV3;2RC!OMe*wxEXEZqP{I;`PYb_ zL99dMMkfc6d?()LH{72VevcLS9C>Xk5xK{qi-e~TzldmtlTo1G`wQu9bOqB3%*lvm z+y}^o;}`mDmo+3M(Wm4_XwuQRLO)ZQ04!O@KV~o~U{b)Oz{I5h2mh$u>h6fzBm2PI zKa_}Rl~CHZ$00Z4`q>6B3YQr!V+pM)fHUYV3TFO|$W zlhj5iHK&Zf+TcooPilv!M&;SX#^nG~fd|J>V>ftI^^!HGTpaN9NHBsCP$*;SGEUWH z{uz8R`RG|ZFm-yIbiv?X+T?tAe(8{4Q#X{V)vynTp$RS87lgy@gyp!;X1n|DyDfL! zb=PR?qW+RaqtW#^S>$%RuZ%<@AI5ra5+Yq*k{D5}3A+(%5w~PyWUTM(>}=?NIr*bn z@UOah$Yos78HZdvdncAjlO{Ra!|j!^xaG1$Bmx(k`fT)(Nnl}TLu3plq*5^C@|#T) zMV-Q^1B~y7gYf9njU{7;#4JY>cKgmiUu3&GkbO8<=;&CrYE^js`t_unDmxMwPaQN_ zxzZ{%HFjt;yBjt%WQ7ves%T%|v}hn&1&~Sw7P1v6V>#00+LJqdv>80RK(?(ByZ-Qf_I-6fE#i5o_(Yy`dVAAv96A#sn; z>jzRI>{lLP=+%Y5_izMqZh&M6h^GO&w%#_$PRlmD#?W9zih4YzezSNNZQy>6TQUTK zUBHK;48+(*46hX0mR?sNW1*oidS|h~b24Qw(bk>H%L@T7dIa8IVj&t1lem)kK-Yl0 zE(KUXyWw5}yt^2@LfiA_!RjY^nd;Xp1^bU3`V0JIagMSXH~AA_DHtUq_yx=@+|X@$ zJPxIVkP~itjKOYrkth6lLs(psngO=jsYhd5K(`g>@IaCztfZ3;5Rp1kFXTe@H-2|) zPk5q6XYjOw?>rNc?FaIseJFc#mR<(X%OwE4%*3}|gv(gw8{^iaf@*qir3*_c01 zj{@!GGyId<8{V{N+EUCz1xSTF2n-$^K!#aGIlU~*x8B_p;0O@Hu<(j45&6ln`)AO= ziy0;W1@ss68!`5N21UWZu6T|d+0F27rT{&?h#bap{L9+=w9a=kDa^W?6fh|;J_>M` zX$F%5CI#L>6v&uaqIK_SfQ1Z02=6J&P1cSKJMPy(Supygw0-UJB_( z87P`^t;|6bLH&W?MFC;*aUq||rQnh;#5*|Ez&Y0YV&TpROo|tY26;*sRp6fN#7Zkm=dTkH!$=G;0Yi5Gtx+SAj?~WIzEm&szryrcBZFOM@pcg zqGHuZ*H3osk||Do{WpsAy~iW+wTHVchniy&igM1zSeByc=!x(Na;|9w`Vhvw@74cj z*&jxK{2g?sVVffw_Ds=ZIu~kcpzqz=C>?Lrh`TIH9J%D}GeF!5ON7CuUh(a3lExR_ zmeyDHLSPTx52&ZvtbJC8?SHLq%L5tt_8mtz9EAlkGnf>3M^gYMWqaxqo{E36S^_U6 z;=zwVGI|97FfjMXu?OD_EJ6CVKOnZODaxw}=S1V)iF6^*Ov%uaLT}}91zu7T6ujb^ zn&0`F8jYa^0^m#Ej$bMvtaH*8oL7@+pM1Ecjxoo3=cLOd(+;DNTYn5-%4IOwuTo+*vzGKMmR^SiR76dz6B6_S@(pZ4hZTIJQ+fx> zyL75_W8X|LWNHnfLs-xAEuVS7GXI`i^}OZNPTo03${=~I{mmM!>n$kEg(!0vJb}WrM0WIKfgH@bL8r|R~uHF`>z^5Zol?+rRW9_igf$C`|C06u_33db3s;tJn*Fl zbfl{%ZL$n`X0#K&Yq!}kFxur~t3RgSa``Pf!9?2+Z`YpL{EYV6?$@NbyV>wS11FYD z2lY~6SepE{IKDDYN96elgDS3_O5pqaPT6Q5MO=Y+5b^I2k0Y94EDETR&lnS{*=)lD zzb6$2ZV@0u{@z3b${0My!4*grotFlW@Pi*Yz26bM#%KnU0wx7a3S8_I(ES#R4-PF3 zL2k7xVYMv79ykZv-OS z^0K-B!62Bo!g$I7`KWh>gI`WLq4B*yD!W_yTBhl`?OKT? z-h=wj15Ln()Vanp0@jaeX<>8=UblmW>Fh@cWoS!?diBLPU7$rR3&fh(;&y3oH8wUL z0%)ikbpq?;r2K1*fS1C~PG@h!&g@t)RvL=NrvQ?aj|l?X>F6Y)eTkm0#lm|~y6 zEjt3rMlUWl^eFG_91G45#-QFxaaj{jyp$PqkNh;G~pG-U;dhK3m<9pDcEC<6;&ihPZJCu0C>QL z=QV6!s4!1xE#+_Qe+0jTqcB8?M7fsd9)|lI-fQh?l}-CLiNo%YNqLjhx2T}| z?<-b#0Jg!)hZVf3NZVe;10qO*9$*-m4@ip!XJ1u6@DuF|PcRsGh9;jnV?(QL&0vDE z@zIupWkNb!uIn@)7$2#ysnjO&F(Em&K&&U$jCtb#3c3t2lMO>a3297Vk%c_H{!VrQ zs)M$R2ioDq`yh_kW25~99+t?*CBG8J2-P@1FF=mM1uC*%@B<K?am0dwgS+{?pEUwsYze{nB8p5sC0NFiJG2jK9T!^_%<`HP zFez|hD1hCw8B7Y86nH05z+II2Lig?lD3I#(ZXt*6`=gZKb{Q;SP_Mw)pA&pCHUQ<+ zD8h8X?i=$ZGWEt2XwjV3co^VTTYQIFq~w|tcxo3HR5 zcygm}JbB@n5=2^sRkQ-5jBF3=eXyzqL-Oli|A!RlWt_=fxW^hBn^<%*Q9Zdxo9d2bwHpiQzT&gdayg;TFPgPc-l(i{Z zEP`bzGT)gd8SJ$!wetGD*JWSbKB;M}QRy%b)zfjt-|ao^McsYfkJ#<@zk%hl2Zv<( zZaS%2lVNMxxhxiLzbPILuf}&Y4UAEuA`pzviVSxdXp_BKW!gsHPw?5zo_Yd5qx>*3z& zfD|q7lB17zfEq(u0@rdv5;KF?+IJo3aBcP11nq`NQFPXshtf^uF^O{PkWd4)s zAEV`@-UbObZo=;d3zT&sP>SA+hpH?t+g#hE>wh1qd4WoT6E}o{g)m7q9dveDoLOSc ztWb)uRJct*(OFrv=P-zRTikg@{Yjf40b@J?FZ|q;bY#fQ~49ejY8cH?!GyY|pwmV=p33 zu`CzG<9Yd-r8AbM1tN~5Fd0@T+ok5GuSoB{lLGGw3dAIEv^kzN`31Y~u8AjNt0mNXBfNes zLhZ4x$KC{%0{wd*7CYu_0$`GZzCj!)Bc$d>T8*TTHP(Fup!){pDJI_6jB8N7$K06+ zb*byA;h2CJdTQy}^dFLjc~yB2gV0rZYN9|jB~%@)88BCHJ>e)^EE$*lJ-&0h^7x`W zmT@Y9=dwh&7s*5i(i|{Mk@a~pO@_Pp5uyj-W=BTX%?tfXL=g=QSBFlXsH^FTQ)pf$avamy1p z@MseJf^`npV0OD@?zjFzFIqiUr`6Y?L8pX!=%opCX4LK!3yA8J+_+GDP-f%&5$ypD zNGAkL#?rN|R!Y#eh_~cvKW_X@i^uv$ovqaZrz8GUp8rV9cGl6(_NT&b~2DV#5%HwG&?U`jWl_g~=h7I<9N{h)4yzqdQ zpP4ToUio3&2_WGKVMyvH+&!%e4Q&nbPrv#nIofhmqDjyo71=JIeYxCx+07%SNnj4j zsGg^%n6@6wIoK~Mw#~3Nm=+b@mP}is-?8=%Sw3^Qp5_`t{$X#7eVUb)C5+4qUT5?L zQx@p$z3p0aXS4ia;~!+pfh`&p??RD~@&tqD@TBpb^A|#jitGoxki3WwGj)egA*ffA z_l+yVpGaOc4$w{#Od?RcGQQK_;P^5k!SDMJ&2Tyt=pPXW8mC&di85bfH0`@-L0@0VjoRv3`2-@0vt^ONsxMA{s#WXp_CX; zWF%sVMPT&80+eL|o_(c3kNxpifB4l~s6rd!=xdECaY|o$hGYNM{O*JmvWR$Az`c^Ssp6@EZo!EGr&8^OBc_i9B~(COB#5kX%)F>*DMvCxW~Fs z>>clo0OaBqe^a~k)-)+^M?JfbOQ5a|d{uxYut#P+nTsI>21^5d+TyT-w-33R3!ZV& zztgJ~`^DFOmk&IKC+4X7n-wpaOvmG$1xs(7>xA&xu}>$pf;c z1N@R+3gawqNvx{|eH7`zGl(+4!p2w2U(g5qHH*WGci}9E-atkTgNer-kRdyxAsloP zyoLJ&zCcEUlYzzcsOe3}8?Yb|v`Sb5^Cp6Qmu_PRvLxl3j)H}XU#h_i^o0xw@bY54 z^%7~{2YxK|Tlq}_>wtcr2mz0XHetVq*KY-cqGupJmk8q?`U!cQ8jRl!=~LX*qOT<) z1g%lb$~4mEKX1q@`;!1*b!thN zPoGl?ydYO$3SNS*8w*k~^_I^mu_tiihLiwSuVg2d(syX@(Kb%t(wapxDPU4y0#d-7 zKqg?Lm_;%va3&OJczXLGi{1JJ_JJScRsC_2+ zo&9%dg~mk%@xE{tKrh*Nc09nBQaraCQGs5VzJYsD zNBQbkzM_Bg?r-*Uz|JnMQu}jqb5p-rg3}LuK2({?35J3~Dxp@@)TuM`_$!d$VZ^T? zeh4uaky>%&v~oV=k!?;X5IJIXhjWI_nj6-o2$4fMgT>nIyXt7R-g_|tR8=AzKwHLsQns_XF zSkiNj$?EU;W!+kI5KVU$%$*wTZTL7qFDt;oH9#*Yqb+-ixFF3H1HMTqukd#as7Q*-J0^UiA3p9eig(hD4|YLJgRx z*EK6#-rFGb(5(23!30-I!?X&8Kwt}9pc4S2zUmVygAaq>#~BBva~2X}KcmMOBWQX& z2I)*S8xJPR&UzT6Q96)zb8~Z6NeS_wXBM1xa;ZcXR5&1C3BrTPuLPhv3IzufYF*Ep z|MAqB<#chsT~(eL?XX4a@EtdY+k6EOzFKH?uye~11#+QqR{>ujP^D$gNEa>ICHprQ zc>Hg(9ub@NNKA9TEe$5<<>KxUX6a9z00dB^a`~l2w zSmWscdTv&r5)TFC0NyD}fyjt+bgb*JL|b>ldgnd>Sz8p)h4Z=|gLgCnS`pPesm6E? zUtC~u?u1Aj?}wYVVBYUmU{V4X?YT1m-iM_?3S2Sws=Y(Mzkk$yoYD zjQ?&4)&Cjk5oB{rf;CU8wTBCyz^q7*031WcXS~b0jT)eoo(Q%}uzrIC;JHQ3jjHT; z2bK_>rI(4TVkIm)LT;E^dvZ(xU?+40-Zu9C0ZLLk3?M14vp%l!tHe`vBLJH7l|@Ly zn;Ozfy+3MU&hib_7I5u>b(m*5ni_2t*#C%HZ&5+Pk>-J=*9-t)xU4q8xN zt7pIy4nVf25Dwk-GDjdq=$%g8~vZG)BW38vEQSQL+A7#4beg2!4RL3DFGWrvUdF zd4nIPHxVjbok1A*B-KEP?gPcKP&8yHkPS+>JilCKSIpLfp`f;{W*fT4K>w&|s*wj? zd_eQKJhJwxwF5T>y?-`;9&0-$55D}MJhka5!&^!+@ukz2%Blscq&SzhHBJmR>_rIR z`OEA7QYIHpmM7nQQg$5KL9mGdo4?|1*1mPH;b0wpPXd1-4@!Pn{;)qC{0|4(7`Ek4XeN#mU~b5jh-HX# z5PyaE9HJRa3QP^6U-N=v-*8K^1D5f!Xdd4{V+t;OY>k)Akf~}4`>0u-43xQW2QTf_)(g2#t zlIdT#4&GSG6(C1RW5$vD={&-Eavzd0y+~}XOR(>@$&m+M1^5ME`!M+Qx;6t`h1IcW zc<-KuhNF;&^sZfd?$?#7F4f@rR%3?2zjG9vuxdU9X0UDnzwtxpJ=2hnLIP-31Hi<6 zns;h}0(fA$13v=55%izn@veSPDKS4hrO@*yLX)yuf1-z%J-1XeSGNP(U#q zO6+Ja0;JLabn(t8mW){?k~zOj(kk=dX~-)!0H-vpo!O_kHK4JeXfM3hHg#fOSSRpS zs`{WGZ5-_b$O8BIZ7`6>YH9oN~yhJre2j zD?=0Q8xKj-v)h%do&B2o64YPxw75(ws*)?FFPD<6Vsz&u@CBr3>}-?+^#_zOx~9%1 z>GAjU?*W>-nx&5vMIm*T?_7tGwu3T=+

    Tm24^Zny0|brfP`4BU`reW%IFXPyTxTyE<;TPXBStldBgcfwH=Tm=p^kkH9geGBr0grIzR22o4!>zz2zOXTl9_SX>D*FM%y{O z{_v51Bd)pckEwz@Il(*N;EgbyyY4bxbAAPju>UQ9cyNFF%k+`7Z=lV5f!kdTV)a&n z@!DI5+f997$@Z%C-tIGqW5Cs;b0VQVjOK2~r5sR&gGJFKF?FV&vKbqP=h$yNt1hd1 zHMtSlOMcLgzp=<>dC>KUU31@#PRK^+Vp;ks;`uH;Gi9!=s4r+z`(GttvNW|I{+HC` zl=6gx1k=e|KIc33ekjkzhNj`F9lh%ZCco6wO|S3wMC+JEK0*40e4;YkrJVY3i^sS) z8dz`AnsN(GMI5Q`7YA>@b3zOOY|+2b8kTl9LW?p_hKUOQnCo1SYY$xZx;pRY29tRW z>e6~Ke_~opkRClqXO&*q0%gz%-wl~YHEP5?Je>|$pbRNxJ%X+RYnMgPc#Y-hflxSh zSa)nGP|%R?2YnczI5LD={|xALf3H8=7@1q7+Hn896lPSO?Rl5rY~df7t&zyekO086 z@#_>64qk?0=4b59Btxz_2QYouD!P67>&ojYBLr8S_5 z7s-SlO3hI(h=h<>T<@ zpZmjzoC618=svW&0D>g25( zR`hCjL$|FwF=NU>V7yT>2_$`OM&|bcow7|wK=1nH2GORPYM5pe(`QUKfCrD>d*B+m z<<~!6HJbguwhL<>bnM%G^2Tti{rxYUQt0m8KWbB_A8i)T6RYaFFhPF{k&=zaNh4_xbsLnl-1YC7A|p&0b?G4 zg@S}A*V?i@-{{Ot{pGX=GzLaT*&mYD5~Vl76&akBM5VlbwV%?iki#;dl;8Z-d*Ev| zKjuO(#Po20>n@y)gY2AF8HtW?M=Dgd(K-Qf;;RMiZc!5;TAf;|-_5YJ+c>`A5)nGy zuJ+%1&316oT|$%qz!ZpV@b8eOX_0{@UhY65`Jixf*{oYaf68$TN2Z^+A>%=56be#w zDK%7sD&?Ctue~}VQqmdp+wGT-{u9waCPVfhe@?)(;7F)`;ec2p!G)1(Ng|zoI3E}i zphT@M^9$sn8j3XM&w+D+-8uXvg%RLdGgGg%rDv1ry24M5qB+{}Ew5tpuEEXXsq(p= z{_o!ryF7vYRI4oGo2HSyb8$XvFY%Y49`~Ukb6!N+8qZ+6f7|w`_s|OqDVmBapk-D9 z+EsSkLBS=))_9{W50w~Bi)W@4#tG8VUSF}1Pd%vOZ?H25b3s@ZNi)(Ps~hx&@HGj+ zbaJByCjzQY%JZ;hMm`ji33HKH+PdgYG})EJ4vkc>uC$YNXGeGdng!oVo<@FQ--YU| z!wP_63ZgmGJh?Riut+KO`qio%)iGbPyChO!li^8wcDP@w75&?P2nKk0dK$q?T(U0f ztyA1NkoNl0=8D4K-mBG{$GT!I(v3Qb%spRfcrT^$ z1>;IlMUfzPqR@)b&m||HP4A%Gd)gXH24(9640aIN)ro5=Yxt=DG5{1+l|Q};8KGiX zUKC^0AbV+)Di?SJsnoLt?fDA0eei3JjI8`J?U&z)(v?lirX&uvN!B7yQe0VGHNEMY za6S+6NXSa_@ZLg147>hT)&JA&Yu8t07Tv(-Bd#rV2HPPF{I`mRD)lB@yU#KzH#t4| zES-x#3nXPUF{p6BZf){v!!G!h?XFK!SH{zrUsS|{ozJ2!;n;~(EA1OCCvdB-J&Ijw zhdrPFse4<{2`R8g)at|@b66#rW+K0w0@|JYsHme#H)Hj8FbKB`7oHLE{b%2pJjXr1 zB<-wu>g-+9>h?zbTBhzSl_FM0lv@?$NX`+@Q6oRY*N4tPE?}|WA8c?c^(AdQFcxDW zdsGq6U>z^fh?UtVh26Q>biahZIk%DI2*%+AZO@Zy%SF{U_z9ehki7zg7R-GeL9!r( z=Eav#3V+%N5LPCkf7-y|S%kaVsayh5icqJ)^0TJ&5h6a8aWa05rPZ{6+yf&b3%6dE z`mi_jRGiv{>Nd)BX#DQm&|l##qfKY+;f)#hxD-x`bQ^JVFBbqNfO)F~yP<+`f*_~| zUZ0p3~WqU7j3f{JzTksyU!0mx~6H&s~d3z4fY@Q&lXR*0UTw` zf4Krh{(@cF+miy^ap981O$ckw!Rb<(JXkh;2?uDk}=+%{sGXe3;n$)}C3Rci!!9QQ46l2d@ zfNY&?nM>vOTq1D63<%sBWV`3V?dR@%R25koKC&wQV>_26t zVL%+FV?9i!=Thk>rWn#Y$}#dOe{c+_A9R1lR_NBK*`l%MjUv@nL>MK#i!v^5EHfK; zzedW%f9Vzc*!B@gB4lY*?YbYU9w}fEEKhq^ukoBCRl%- zZfP#;_jWk2-P1C^>YD#I#I^UT+5lHBMmc34F?H;EU~E6HDb(~W`|f(N`GX#~tIi`P zMT~+@4$aaDQH>aNkGRUe${d8YWn$m{BC!5`&Q{(*NGEp7Q`oBJX8DdHk`bf_TQI5B zuDABBDy+*}I6Ga7qO8R{oN7O$d=0@;km0&&Gu z!hg)+?|RXo_uh1MACnamn&ch(o9SAZ3nrMq(x3pV<8W23_f`jeX$2p=5N4=c1{mFE zGm-w}a5o_5a5{+4e>xGk1`NrQQ7!OcR_i~=rF|Rqb(dvzRuV>@KcAjbW+Hhfx?OUm z7sGKY_jCO9BRh}Cv*7a{Z5f>=5XGC;rHen9ttgCA4Sb;$wqtam_)KOJn3>^Re?jw9#+sD4ZJdKFlXw-%^w5d(q#|0Z5lG z6Y(xIS$__T#aefO5ME#Ds+dzs7KNCqZG9vmv`RtP8obDUA}V<>5gGbg+@~A4U5D@Q z&t{7Z19kozXTM?@!Q``aklx|_V%cKNxPbONY4j)8wO8&&o{{VXA)W5FZ9q(PpkQo) z(;C*`c&V&Ehp4~6ZAn)I+YW;aKm1TF3dOTJt8NPhm&?#MU%z(6tUF> zl<0LySdQn3uB1_JuUqSnqd?`V?!LnYcYmq8dJJ_8lqv>~OQQMdLpWJwGTCO!`n%k0 z3(!lS!=)V%5ya?pX&L`(mx7woxp>edYvQPvoKedV0RadlxpY4I#VQ27KI9jwtUyNv zP$0@pMw!(q_&C91gmHgActf)WRn@9gv#1Uy{E@fYayx@q>P`*mZ~U4v?>59B6iYPn zWx&You}OID14)3vhL~`K%{*7niN6x12zkfsx7l(4kLGY%CSZ@1L`xH+gTFXT0Cq6d zJv-WmAiK5BsE`bLv@U?G69R5N z4Z)*DAZ_A%Vg6`0pw&WF8O8f3u<=GB>477FJVJ1Ep+J( z?k{|RmIw3{1?1Fr`>pnf%DhU^Jzu+=8~lUh>`lqo&T*t!&m1&6omav&pdbn=N{R=e znc|=TQSf(D40mHFLpfuDkJ$^>*1qM0Ds+I=Ibdr5ahHwOpim*VF{S{vu0xZkC~o~s z(#P)S>e{MMS@zD_+FHs_jXn;b2f{zO)D=Cw1mRtoQf!i9iM02hlZ(%g!$}E{cKbR! zrUZ^7MPTTA^-J>YpVW2EywFdosx!G+>rWC?=FKQ~=~UtWQ!Qzvo{+k_8L2 z&L|+TY;F%+3_T1@4v@azYx= z;Gs2=1aUI&+k`46D$(8#Zj~Yx+Os1l}T6m>Ej!NkCdU51k_gLPZ>Q9ut$^{#NgJ zE{F?ae49FUlYx5zKARlRhBeMy-f?tLQRJIyeBLT_?&qA`t5`uyDB+c*XeJKppE65K z#JDYw-Z@*^jU}vayuC(L9Q+urg#)4_Uzh~?C&q7k^S_Dr3BZnwkd+6s+|1hsX^6Ix-nmi2a=Qx z%mD^*(yRq%ZIU0j>TAC(6D5r02luJ}UReEmMx9qN9_72c(US4j$1!9{%ERvg^Z>W( zd;=JL_Y&D(e)GtUZ8(?tdZZc?%$Bo#qvlwSk0%=t@giP`J{IO1U+?Dtl|xQ8w0u-= zukVx~lYgbUzj`^T^}>0-Z`^#Zs&98e!EcGUBGya@#QxrT64AbQkKH_$8Ndfa7A(Zt zi)g_l`4#RcITVFBAvD`lhB@1(cCKk*G%rE|;i#9FZ?M$wbPmo`KapI~fu2X#F;Ia8 zRE51u5n{ME5uu7o1m693^)O7+{UGaMw`%i`_#hm|#^L+VNVuq`x2<>5;n84*{dT|S zs*cXXGfTmJ8Taofn$@rc6|Gu~NrbNuFq7-p~f*+0?4F>hEsR{Pg7vdqs*Y zBbz)Qdj4SqI>=Kv=||&@&viI+pkH(KwPPYgMmC6&d~Lu`cNV(ZsKG>4=t)6KAodwI%w@L9|06eDP zpUZkxp-}l;_dE4=4!za=4Y8O)bXkrrBz%8?yfc}aRbZRutnxd$<}Sp1I0G1a!g2Jv z>G!NxAbjFy>WfOv`C^p)6LQQ|jY1JmaunTbu3_LXFTmhV1R-byK{=znNAxu1P&o)| zryc;i?KHyaZ>#6uQ@h`HT~{`l-^I8UYGUfA^mkFLRs`XecG%nvsY7-k1dZgGfuc?X z_V_k`c2gowzEfXDM!m&5^zL%*g0&?bEJh`8f#JGw$HH)DY@wyC@f+RhI&WRkiY1=Xo>v6JLWcI6d!M8@oqm9nX zGZC`uD>fqR2j@Nf2%@BRD+cWJ=!?^7Z;#&cGQqHP8@@D_4BS|ILRJD3bh_>)DrII% z224~GAjwX0mDBUl+js&~$A>}i+hElG+9ve?^~lddy%Oc|Oqsaw$u}BgH6*;W*t8gN zvVjAr-}}LeENI#VSYyV!7o@PW1w2{=T4AT_Wi^shvZufHa2<2TA6m3J-HsdI3jTR1 zc1^NceYKfv!aJ!Lhs9nuzM1BUzw(=3TA*ExY?emEILM5n(+TVYc7CNZ$xg#MlJ{Tz z6d{qax8II9fX;(a0X~}?)gj{k!h4arr3SCzx)Nu)(9R(y0ox;iJeQfj!(>ztMSN!k zuy23YJPSs8n-_}vTAZfFm(*fLUKK}R)|bB5GS@bJk-I(+xGN!fOLSYz80I;+Aj8t; znonPv!7Sl=PJDwtwwm|#Ydc3ShJN)|uoSWgEd8wXRqiimWy`(W19JsouHwe&K_F1f z5Nb+%j2aiI04+m1LcO$!Iz0^6F}B13GJ}${)F%lphH-E&DtP;ZuG_H}f!fkMwsaVt z9=5CBAd0k%vkeVdCaIkgXKJ<589vSF(4ir3Xo~5hqWGywbYK9Z?NwZ;n7*~LVi3am+?G;_sXxbz5zdWd6UHm5??sb& zO;V5*@yodG-woaFA~{V=vcQGz?dDG z5PdaEQv2+b1l)^W)d&MwHNU!xw)KiN^>iYDP=844^2v;*AK!84+U^rfh z0I``d0bd!PTAV5w@UhvgNeY6=Tgvc1@}W+xPK8-wP&p1M>W+>jjuU(cbqQOiaA#*G zQ8cxSupcMLoZ%JEm{uq1tmd2)DhF1=I}3w6{0%?~cVZD|VV1Uu9sL9=>Z)CH*y&Yt ze$Y2gfPT0&NM*{1y!kN%P54CO!7e?{_1JO7$J{@AZMIE&JR;*7?0rLFjx>996|>!EED-~?!V!= zA`1UJM=t)6-?je(koeyaq!U6KIF(Ib%=}&|7V-}l6VPyM%#gDh*sMTT5$BZoYZ($6 zkL(w}lN(7|hY|n)E*jXVe9!7jXm&==M(R+O%jO}^5Ies+y*y1|rG&j8loRqU-4!(Q z#Hsl0iRHrEI~4=n^_9E86pP%h^OufiGaRyRTj&Hz^J-FsgGT=LfcJ#qMSe&cT%G<3RVe1a*^S} z&ze7n85QytSy~J)JjmqECDnZo9B|l{Z;H?e*ccZ3qe;qo{Fa2#07KfZ!;_f|t*MjJ z@XPdX5zxlL?_;Z^Z?*1(0!dtKl*XVIBJOUc>ASMYgu#;Dk?4~gjc;0fo8{go20p@@ zi+PrbU-z+L*xQc%_h%PFLdjkG|BQ){bvK|}%l-%i?NSDET$M#>J zh|A_^zrS^J7&h@`ncWf4kp0UJv#+X`4^5fULGc3N#|aEQ3ufzK@X@ zIdIUu-ymjG%eGOU%#1P)Ee^)sc-C1SHXXZ(D*5TFZM`J*R~{2OYIl+Mjikf9(|CM~ zPf%DtF1|P0tlNDyg1^&EWZkRuX%UtW(E_AW7h+&0j5guT&HQ_67&R2+A=REgBQ*nr zJ)~$DXTR;M&SVo=C&7?S-QVaQbhf|zaG~C2+rKiL3&mEU8{gxrgvQ4+3#6stktQhqs_ z|90ILbSV#0?R7mrh~>kN*S5_)0eoV?`Rdh%KiZi@*n@95;I=#3jra44n^Ceeuo$b( zU)HZtTgN~u3g}Dh5JW7Ne?ViB1-T!)*9j85p1ST?`v*9{!MO4J{#cyXms1NOs zh5nc;gsTk>@gfv(a~Cu00P~{N@Typ;F@efUwjYdO(Ned6!PTiB>S|tLe*s(YYT+0? zE%i#rgCVbKuPA2CRldV-^@ea*he{WgdZ!=iJr-vze^n}Xsz)(67o{MK6VSV;wY}#R zu#=2cpq!YDK|6Ajr{zVxNl?upiy44|&YU~O>vj>1feVLWV75eZtbho(#mAy)<}? z(nNIH%_!8l5i6*Raw8WT=QnPn`Qqu9+tjL4h@>+dHrKC^>5sLDKz%tRNg{ghV5n#~ zb>VL03E;O48s-HxT;-XvJ+6-2!AeZuULUgL2}4z#PI)taJf^ARMpsU#-RPKv_%MxM z1TE*TAni7WjKV#h6WMRkBl6JL2)z>IMA%>J1 zQr?TIy0D-bd+1Vl`U~dHD^_8hD05%@Zu8nBRzZw@FM=I^^kKA4c|53^cwUAO9biuH z6&Fz@@8kktLb4SB7KWA5l-dG5Rt(A_*F?e~fb_-tb6csXqw$65Y=kE}$*=1{xw1PI zm)?DP^`8gFe3r?i3>QVD|l#J z6t_U*zl8XOcy-h`2iaN4F?N<@fQrtyclrD(_^0N)Hg-|izuz#Srcn;u1>d=qKRGu| zgtf8{r!RXLf61rmv|ZI!PaBFIzHl$l=6+D>8zCpg8OG>LKPwi=F6zu?OXJq+`!W zIh^Qtw7w-D2aCS`L3smJ9h+aICX{&baFU;aEU1H5DKFp%!X>MvL3Sj!==??2&AO>yf?Ltv@Uvy9!L>m~&n9b*Yq9K2?#Hj+*G=M-PPZMO zjA-6H3+`l>O3`i|R0X=x50X&f+E^^RExjNR;raSex{Jez{<4c}EWez!92VLaH_}ii zG1y!}gy8ZH+rBHU*j-jfJN0eREL zS0^V-5JC)CM^`dv?CrUmXEuVi)3zj+{s3Qyqo8ZH%*+7Bf^UHaWiJ9`l#JhA*{@qI z1w#fsE8smY)scrB@GPQx9ZQ|ELXqAXn@P+>XwaIUCT-1vyI|LjFy7_k{vilL9E zMdv{sm*j=~r&?1NoECN0yt@JZLIe&-;74@R4ei{ZgdT`YsOVIJ+9xtg<&7^h;_umP zU=#ti8>s)2_W1vmkN=evghaY&$NtKAwX8q78re_HbHeEM(RRbDLCmA;k^8^zN;@C$ zHuQ3=Ej@*8=JhdbK70^0*i$Y+~dj$3Ef^l%s{1SLQS!jb&#XG*-?_7PA ziQUPhTk$)m>gY!YG@;}rd8aR8(wN9NFU9zVrT3m)khs<859!A*LL+HLu5_X`GWW?Y zW*@%{)L&i(6(uo1KZ)6Iq)Ev5u3F4yXi^!rBFj&*L5=l=*>FLMG!eMj-@DfI);k!V zNuE&92*#i8Cw7z+HP3jSZ9$k#CM30F%mMl+Z~w6A-gc#^`&Dwt^&?jy2A$XbRb3y@ zz=m0`tvk1`KV4{czqFG${53PB#k>`V;aTS9F>wD(3VAh-XK^nA3X!mvAoz>E6p!BF z8dM*#QTl~Iwoe(eC0mocoSLxv{qs&w;E>b>^wY_r&hv&4e8`j`)mq4VMXtTPdF?;_ z%GSCYWltPYKHy^FT7T*D+P|}_9*aHyh&QzWQ*dMPU3Baywfa1r$w3Sto``QgPVjst z%(FF+9)o9bMzc~k;m26!_BAT1;t%YPHsdt~(GXk^_BnWbrG#D1EKF~DCa&voeTzG$ zRYcJsSg(ZOAO#(3eXEDITlpSJT3HPEFJ`oUC7rp~Li_(6Qj)o|UMOx<8SO-Y;!g`v z+^O_b_Ath86r}Hg2S%!39iSVjmp?u+Lb``5#ziU6-`*pd)~p(;O1prp%6HBldbQht zLhbU4z4H%lSm@a{Xh+9`9J}UDrrC(qTpoBlibLQ-Ae>(WFA|_Hk1rR z=gtsj7-#PFf)!%q`Dz4&eY~xg?hQ+uaOa)Tx+_f+py` zn?Fx#dLZdpk~gEGeKYB*X`5xuF{h360`}wW)*&te5{Jt?W-4%@VWyK4Jm1aiC{!?T5kAm2%1CE%YS^6zCC187B z((AD`wDHaCMK!onR-7Cmq=W+~r}HeW<*jbtc3Hd;N;4Xs=O@dWNJJ|_rd;j!j5me& z5RN~BPmdg+XRee+_Xnq14JUp!aRxbZ*xGw=4&XS}Rp)cP6d60Rl4X;uma&Oy)^O>r z_-ANfegFaBvY*cYJ4S3k>WSZ5JCz25y>6XhlYhl7(icnj{FEjF|4$5AF0?jqxaOGq z0MW{Pst`>3ze6xM3D-GfZGuQhrc0&Y-IMZp3h+O$z-vl@Rx6N~uidls_N>kO0=wPX zGw?fZA_d!LR=i*usL3amPDSIT>qFl_54^fS9HgMgi2gz<7;uhxHMwg?b zD`>#9YI^9UyDIBYgm6sXChu#;gxQMv!|7Wxe93ze)Y=x){ulHkd{Z z6+`_ltSVCS_)XrKmde^`^q)%fo(i&r1{a~!4LG}REyG?g|Je30Kgf^Vh$ew+_+YBi#(;v&ikQzm5Eb zsV193d857@q&(ahoj{ICo%BFq?-llBf4Sm1g^K%?y~J?{c;!crPzD6k6;KDp4h&5U#*rE$L5jvX>%&F~ zwXHDtR|dxu1DxVS$Or?i$i}=QC)CiLm)IP!#fLqUqI?76B$S4{rwc*<6K^#XR3j@_ zU?<^lI9M(OJJP8%7nzpOuzY6iGMd`SsYCY*tvz0fNyiclyv5o7iohZiw-qwdhCv!+ zG?QZIG|Stn`uQ>a4MXGnQcg^UoAak!PC z#U!2R_3DqK5VFaH=~D3S(YnFl;Nl@Rs~#9hlX0)>B29}%0z!}N@=S#!KUt^E+yS~? z#BO^|7#zP{EGlLEj*Mt_(fm;!1jmW8F}viXxG|JktRdVAt-Izg@qXuWpwaDni8#vt zEiF_uWVf)LIU(T3q;y5!A*Bx4td)=&U3)kFf&lW6d>#@bRkz1E>c#uj;Hs~nW60p* zx_I~E2X6Jb-mq;wX`)+jdhoT#!&~f{+lXBVnHd!b3k9A^$UW=ykK-n?x4dX8`KEq@ zp0MYcG;(fap5Na+WjnyZ`P8(8^9Aa8a`Qu1|_VN4f1%z1aH4 zY@5i);2Yt7XwG}yTF0w7oGNdUHkF@o*XvzlH$g6b$A}lI&n( zp~ETBm*_mTNr6K_K8FPl=f%2cBgmh&LQW5wGOZlQ0t&c7QPQtg1pUaAwr+pM*hSR}s@f*7!gu4uUBh z7-a9DdwFHr%tYhB`DBDh&EQ;?eel~Lf#k3KU{DqyhnOaMNk(~q4@@q`Dlk&2AvZpZ zfStH&?yZe+u*pEcXc8~B((OH7Pi)_p>Z#T-f04%vaW#LIa{u60xi+#o zYpx0@$j?_&1LsjdlP&s#I&IJ^ui-}-fFeYkyMkLRei3klCyvs_U?H?LoBJ5eF9sY& z#kTuPHW4e0+`yxg;f$+ayjF#&9n_(dSJOp5^}Snj#$o|{#ha`!VW_s%paK9TSi*jJ zq)xCLh%KUXCV4x9?!468RN|hhHxXrdMT+b=oI9lkc3w_y9GXAexdNvUFEDR=Hv_pqZjyT!3;=W)G! z$5a1>Tt7mJ832aZ#47#cgog~^?irhS`x4UxupzBLCOg~P9A+8Xm^ViFxGokuK)8~` z(hgIJ!=GxiUWl^25C{||4l=`4z`>qF>>8RaWOL%VOU+vN-M|ndd|!z- z=q~!HWsDd8l)Yc}{DM+x|K`hgNVqtX^uj#L#0+PZ6?h153X!*TaOF3JX3wB9{>R%X z)aZ%twTciNTiE&(=*zbQtKRU>u439UiCyQm{GrfHxz+74eh~)d6H{M!K=O$sBUyls z%>x$6zgXa38mB2P0k}OYWKXg`mtj&T6tm4wI$1`}LCUKN{Sc_a32| zsfoLZ`&~BYv)!&Ysji+r__CH4_NiLUo%DY0H>gK$+wcBiN88j~@RICT_xFqpnAQq@!NxQ|?g5!!48XnOd zqs~)M6R^}E$M}d{DZ7ToVPRTR&M^<1Z)EbLz7dT*6s}1@mlWIXh{5$~80OP3B_L}I zB9W4vme1v}@Qjj1FA~z%xB6%Ja51B-)qQ6(n2u+xEyhE`KweUp6C_X_&CYj%ViLlG zAJ#p)2||%MM(G8oAgCIP=wBvD{1t zo%Mk_4AXxO;622QMaxwbdn3y#-hSrWk{?M#WE= za!<>mse87-0Bp0S>zXaT?p_9 z{?$^bhvO7L5-;fQX!u;D&R}dVX!jVCThZML-jm!4GTIUj^ipKVBiNQJq#}hGq&ieZ zk2l-<9{%tsj7&b&9z#mP75(6&8mFpDj&(%obO{V7az5_Pflg zJiczw+3KN5zBtAIOOF(rqoH7u&9YG%l`(JhiS5tKbMByL#$m=dLY59ybA>c=o2IIG zt){Mgz-!j}WfImUor$Z4qQ>=k*RSB*R0bTqJX6nE8s*fd`h!TEnZoiexu(-q3SfIj z`-2{ICXKuKCuoQu2vy*BeZ?Tc>|w?FifvwktU$a5eGIuZ)Nt1zYo%D$(SE2$z^EeX z=jlV9Md3=xgpO8#161_6{5Jzd>4m)Hat0Q*)>G4+IS60Ij5OGIbtb%NLH-!ja%ZN^ znjvy6IR{Kdd10{IGxu6%;@&p#78#MsfUIyr{}@6)g0>)ZhOO~B-jLg0qPA9#0gwE9!AziT*whqA;iI`WkOVW{c>@eA@Iqpa-u9DVqeQnl6Bo}j z9K#bn5=bldzj7_qCZ@)H5zdjx_9qiVn#81tpl=l0m%7?E+y;EbXSx>id%Oc6c2;#!4Q81Ar4sc=7IP`i!fp?-;sU{kbCa6>I*+e`}JMq>S>vMJEchN_J#+`Md1PVT8D?E z6Px8@Ck?fihhX#1qtY-TPS^tN!yr|I6>f{%I)1uT*g6WvueUh@BG50V-kdw71Ij>uxW~zD9%W*U)8b7;+BUbvy<==GYBU=7+zF)r=h_A}^XnW&^RGc|_Ty+iq+g3CzVR|qZ|IdQWL`P^K zl7)lzo{e`4sa1hF!r3MA_rp>C5+Vcl5rf;J)T4boE<_r^{sHzaTYiKo-bCujBNr6Z z#|jkQef-9=%+E%RkkrQmkLFSeaUjz78`nY@j(Z!I^DIxDXsFf}SHp#36l^Sd)x8kyt=`Qo&HyuHCaxZ$d_R-sPqKb zCC{+*-aaRj4#zxK{WiT_}fJ&DHOkBp-*n)15YZ5;_9!D`y6YPl<;NXbOAp z;lA+T*24%dEW7uY^bbvaeF`!xPvuIxGK~l;lpi^QE~`G`8AvJjpA>vTR&S@K1KArK zk=z_+0fI%R^3Vr97TQnGLdo34Y`C+0JJ;9LDMo);|LHvN+4DuJVcsyCV0K%`VB~g1i0>_eI|K_VYWwfe z-U5uj!7noq{A6+Zp**r~8vFRvgy7rdCxEL$08gTzlVPm0I6jK-L$gQGRj2VWZB=kW z8SYgi@64`IMYT#@btc1SiU;Bra`$TioaeS&$cZ}ve8uZ3x0NTVpx`itT`pp0M4tI2IG+?4p1}f0k1$WxpeBqYn ztp^G16e(-3wbwc(XaVmy*=8AOS>lczP5{wdr6|jLQ+7=hULA1mGn%eAok(JE^u+m7 zHP3R1W}3kY_denlQlc;BDzUyFYD>OX{hC%(M6$hB9s^NSuWB54`n2VN>yxCfv%IO( z_M@n(Q)~q+ox3*VL1boQZ*I#98t8<15GjADn1oXT~{>o=oefe-gc@@GbQVjgVN^dR@Svklpr)+t*+?gZ! z?eabqwv{45E;E3_bEkjQqr*6T;kgTzeO=o)_Brv0xCwaWb9@W|L=c0bfn>ZGnd;ph zFjM%sReZ3n-73)0x!c(bqCreYe}8=0v01qX_ytOX$76@!Ag7mBvc#_P(9QsHkboEz z0p?>c57q!5m11sm%FCcf(}m6_X!>xb8&zZJ$!3lhcF&3}-?^Hs-nQJXRucL8fx}$i zgmugxIcKlo3=mVScYn@9#^>P0U6W+dr%iCs0~f?6W|5xF@%aS*We~;s7+s_N%=xZ+ zorQH00PV2HyKF$23yV$5TZjmiGkhp?4<-nHybK2T>82m`|hvHMi8<%g0_vUb|8K_8&1(Kr9x>H6sjZu?hM%v(fLV zQcTJ_TB)#2jLGD&)dL0uqq;SVdzIFoM>gO6eHw}g|H8T}%e3_At$Tg{-Ix0ZD}>JZ z#< zG;lPD5qHSU#sBl$X6n@DlP66={u=r94I7HA2tQC>$aSGOuN#|f^YZoBHzDOW6@6-@ zfa@0a(VC09>zH_#myF-doAQfxp}D4Bo33iZFv%a-+nXVWH`UI!G3K#dX5lJ1F^kVc z2Z2%szV((HrIi|IaSR@ITHA!TD|TUZd+~t)=6UA4NRYfbh2;+N=q>>K(p1Y|;Ya(H z)PC+kEjCf&BT~<|upY#-e2RW=fQTZrU?(JH9zK(v%S!6z^1~or&8W&Bb`(DS^Vaf1 zUX+`kX3I)r91yxZ&NKQw8Bq_9b19r~wv{TwnK0!qiT%hdp=BN*4umnam0`8yN#bRlfSk>n*?Jb&BzK`9J@C5+f+K45=|;1Ml)6s8Zd~QQwi>}Q+vsdi}&rw793zODsL!cP-DT_%z@-Y;?zr=m3Q1 zkXlIct^H=BMA4B_6XT$8J4Dm`=q&GdgXe?9&G{`}N!uULPc??V27mYP-kq2Hl~N{j zi}=sU8xhK!v%L#m!Zp{`2^G_`448NhfzN}4n{h3P2cwR=mfraZIFG3g=YI0PKSLX# zFonldSJmnNa-_5~M29fa(}=KeAl8=KlBN zG=IuAn~w18{O`#JoE>fxB?Y9{zv8}skDdT~LjcOCz6U009mcCXB%E5LZlHD^AQhq_ z0#HW6611VNe>J!R)ERRfu5wO#79{l^JZ50tiKcr?SFlv@+s&2sxHggVu;a@fpVv z0O9U*>Ia@{fSm_bA$=^%#T~^NW37KEK_mdS(4*?e3yXnx+D-eDv5j5@`L$sCT>R?g zTkOkKz>rZrE1rQ-n+o*alFem^t(RABuE6j^0qvS!I{<%X*rabzyd)99scA zNh^@jqy9J?J-LEgF&jlI!A7q-A%7|88ReP&IL8C4|67`G@IPgJ^<&C?+;TIL5tHRC znhuYMba?D@_1Qkrct104DVfQ?ZnmDhM&w2k4i^QfPu(k1TNn9gtLQY1a5uW{Z}bKr zP3cE(;g5PAcJ4hFHj3&y)gO}1;t|`phbMP3=?J}INPVa4)Qy`TNzZEzt)W3jB;Dfa ztW-aocnG1-e|Xy`aF!Mhtk2fkuQzt4f9A%UFmw(C&g~I#`S(nq)tSU?1FEy3Kg^IY zP6<0-1fi?|?F{C2)b>y!e#<}RB3X~I{$3J#fsO2gE%ZbpxCKZDob304d1us7KNl^g0QP9TY1i0_W7#jQGJ7jT-MSC+;tZiEkWr&2j^DSW}Sy z8*CI@jhQ%NfV1sQ9Esfg?YbxcTsK^Z7148~Cz9yOnK9r2?w$}4HN-3G<154pZTy3A z0loc3wyuh-%`wfB!|t7%0G`-%rsZ;4OYVQH4Lyk-*{Pio!MOvO;Bi+kBGo}-5&YjL z>;Ea!e8E({4v*2F*!WyHOmQtSN_5)1eLp55FDia4zk@d~XGt7jw?GRuFI4F%hZtVm zVwi$Q_4KhF$Lxcr#4RpQJs%+c!ac5Z2r}1h#t)$^=mBak1qlK{J@jev7P1G0e+%xA zFUuaSI7^`wEQi$HF=)gEgRGGaXr;5gr1`+SjCpRJ_|jgE#|9FS2T0N1@~qhK{Mod| z6Y{&f^&}AzkD@M5v)53AcBGT$%P(2N7W1?)#K3z9Y&5(GU1l=$fw{H9+kOGPtlSo`z2>qGxQIgQ!yc|t2#!TzA3v(kXb^CRABG{Pz3 zrav`1E+dM7rS=65jSRt&k~PZD2}(&uep+w?ote1nVE5;#WD{Hng#YRjx6WVRXbcTZ zNp?)92_-yn8Me!J@yk!vl{6os&~^6t zCUN6a_CwJO{(6@5j0o6T!%NK<@Il286Xh24ck)vs`7LxS7A$A^!p){Ec{Ag{2=e@Q z0;B;qR{=98Ig$*6<|sVIc(&u|DuCX}=}zuenT(GT_H?7Ng5DZK0U~}^8Ho&qAqyC_ zSFfq#vF7m3nTG#Ul$S!ytN|zN6`(YbAujBxvHS5|$4k?>?YcvaB51&X7{krWX0<-S zurhVzu`(_SzWDUP>|}@aR!+w$2jX=tpL+Hm_f|YcD8G98z&GzYy^P_%8akKfw=c4W z-Ev(2zKQf$+*i-~%jo-e35+-a8X;%(;>? zEcveG!`=6c#inCdnEl910h6gAZ`6smbPOCyxTAHPvKEj1q#+@;StswL8&fIvMp_hm zqKsGU?#)5TCq}|mPod*A>u<$MuIdC8!xwO#%yC5TY9Med(ee(!Hw0=en|Au?UpHrg z4V0oxa0CDlpa%n@9gu;zui}9lqA+y=^}E+pYw&z|7bDx?dP{cmN*yz;maHS8>x3u; zD&8`VUKfCp3+&Pzm+BN0jc*CNbNBvIM-jagE$5Snc`3?*vk2RdU^wkl#`0MOw((qa#w^y^YJy67&845}N)XW^?Y}ZY9zD9@WE;QFlQ-Ajj19sMf*PtDp^%(S% z>X%y@u*tKfOa(Wk!|p)Eh2P9o{emKe2_iCV{;(veY^f@yCKxsLy|NLjX88ilnr^Bu zRe@NzkTZ%*t0hU|`MELgQws%-DJBCCe}GJWDh!9Gj6uu;9 zijn&e4!DjQ<=be-Hx3jHBPO&WZT53M2Hs63w)kM}rMdwu@aCIH&xltav}6TEI3n0p zV_9g7f({7=zpUC^&muTk?7AelqNZW#Uf_otvCUqhFF zn-v;>i-$x~5=xSu5`!ffJM0&BBUG^<{-d}Jw>xcJGtw2`ss-Is2y}Y`5}f9pTUm<%_%a5GzndCUwVDHb^nW~7W-HTY6W04VaQ^!A_BNZv1Y6}hwOhCB=B zk##`J2cIPPEX!$PaC>tKCIQY6I2HrOK1o%TIz367KPp0|+qqWeU#QXlhZ_GIgJ+e- z2xvIjh{rj`&H-oj$AI*veQf`qMP}U7Y1Y5RWc8D9kFG5Xw%+WL?|-#?j8Im(+clyh ze`COy40pQ3=)W8+6`T|jCb&M^HRpstuc2P#KM}5bu*dO2tHhYx(Md`0e1G{BAupd; zimcDX-}P2M%Fg&m@I2wg`BV*{`S&J2=zWgr@L9C7Y2UxbtFeW+#pm zCVlfmw*thx#|rt++m11e{_6(T;11}~zCNe>VK{u6jxqw_iQ;lA_}JD2DwUPO`%TgZ zw|o&~U3YpqMQfff<1H;=66>7eWr6AAxqjl>7ww|W%k!Ptfwz61Nt+e8iv11~mE*8^ zZ{OmJDZ>1{m*hj4WZL5=bSi`VOj>?_Q_L{CSbV_GRT zqYeqebLi#vm=vJz(cKIA=$ZzzwYN@&{(Kbm^je?$1v}}m+?X<@!zBE zdC3nrZ5@5>dVQGN=u4tR37_*~IN&zaS z1*o8jgM!aEoWux*yd8A<@D?|`UOQet-zR;wyb>sFwQoiR9~L?;W7s>rFTVN`U6)^b zq8r5IDdx}1R5eu<($h;raPRdgz zz2a^@i;ai{mHUE(eIJ>mFSPK~>Gi0h)7Cc}$DuRb8h^PB&}gfJ z8~+z_@ZWfT&kq6$62~)pmGO6?tSRAGWOT7fzUw6c@SG8#dRV>U5rt)b;F&i|ucE<| z63l!Dz@{C6IoxxH))&1D95S8_%VNRq3a(xr3i(5d48Z1CxA*>sknom(5|4K78!F_$ zuOB7(ZFjnb9>2YkMcirn*9NpAi_a!@_}}==A)@0Raz@I33GH^_wsV8NKRx7K?powY zJbC>k0#_5_v}mE0MOOakqsxr6V;f<7o1i7gAVW$LZg{>S?>kiYYmIQFTTNV?;F}HP z=_;bJ*}k4Bxi2YGgEt<2rwP5zmviX~su+M}?@Q~*&f6=qI%&n6rBD@9#KLY;00mNE z)L;oqqsKZheq))65_O8wv0;*3{uWRIU{G;%W}FCTQa{7p<*Pz{5<{`dk@uXARJn$7 zh?WDB20;)0{9>`Fn0jFBHd$r%h2&RpQ6soDi z$@PwNbxb$l0HzyJ#d%lr@(#ltuXr3OtJ*E%o*NV1b0#A#z&l=hwqFCzcmS?ZlJRyy zQ=JI2N;+^brMc>f^y21NsO86&4~N0iSpeW<0@r8DKf%I-Yov0d$3{S(%&pkjx)3le476Bh4U1=fA0b`Lou zOhTG|e5!m#EF~+bt@R!XRCQ=@MglG4fxlt1`$_zSExw0_$g!o6unR9`4^ZhyPVhmh z6U5pEc0mpZ3Y|l$g1r$oucWAYF^TTLr z}0p}g`_2EIQT&`b`ev^e^6q_a*ffOGg8r0$Cu0M!r$Q_Tl3457OfZbbr^4l;b> zDRSx}FNKXKx9zzF1N_ZF@l29k{NpKxf*^X!E}sq?!JW_T7(ghd_lm?Vzm&2|2SoCu z`4%c_kPPs{2axWSB;S7?QwS zddH>VGF44j*z1=Dt>o9PX0GXflC4~ zU%;oOp=&51bX~pHvcw&AYxoD!>Y!)7BWywd*VxK%|6Hr%Xm zxy4b{P*sDy1v<*%{j=8#GSG%w$>v`Bo-YCk(JAqYkMJF7&&|z&NyG!)S(bG6&AdZ$mqS8Zf{nt_d?QMj$W2@(JF-MT4EiYhwtNB>I2dvy zf!D*xmti7>iZ~E&5a=lgsyoTm6R5fiXXV-dW`zGhKOvRlOYSlc4H2K|lD^9cQ;a+U z5L6o=*jcDyaU>dYSFxO{FE^=W5vvTAV@;#(Va>)0Wfu#yOX{NmsOWo+u@E2jZ9&}F z58GQ(s}i4UDgmO0nW3H_XKb&okJAA0Orfa#bj9q`I7o-$zvtuvCar)|N(&YG|A}D# ze~aG#s2;H(C;1?mH1{ubs0%cXsK7POG(*f^*H1a-;$N%6~JRx&4KQVg@MS*|Aqky zyLqjSKe-e}R+bH&QJ+xelG_J&2xJi!UogF6U%V_;1zobjLh=3xwnT0eM**vD?cw4H zFTpX6@HwR8eOeG6M9f1OY+rJ}y$QQ`9qPDzE&S<}=_)%S=&M-yDEboK znL|Nvax7`+Q(mBz619R&T^2>0z(HCQxc=&8RbTyNk;xXI~~iE97x zA?VMX?RjWPLCY)Kd3C`&4Buv;l7rQG>hJSJtO4=czwbrFl0BB{f4xsCf@X~d1Nozxp`^XBweV%%`P=qIfcE$+G|H1uk|B<`jIEUF^>_!; zhK;7l8@1TU8MW7fvd#;UY#9&>*@X-z6sVItolT2#uOS?uvbL%s4c@;^rw=J=3V9JW zYpo6NcB4ZkU$Sxt`f3fJ>f);zc~sMfE)8m^#MNGyx@<()gj(9MFep{Eeha+EC#ASh z*-Gq=(88z*h8iJXjff4FL&T8?TQwrky3K$9OE4b(X^w+vG#QRjj2itXk?seU!vLd7 z0z)txIp!uW;wD<^Ar85SWuSguP`E%S41a}m&Nk-!$d%(%xG2O53!5Sv*(e2b3-+k@ zxP~d*g!KF#!#fKG*GS|hV$HnMT{boALk|fL-f-gYbNaa8eA4anlBk*7Nwvxf6NaxJ z1kV(WivC#u{+Iwj{s3VC;Wm!EQU*+vr35iEYfaSWjaU#_!Ao}w2!nqp7Bd0id6T4z z6I~I2SIk=_T08u#Purbq6a|dKrR)X=NYM~u(x}nmd<`*ah=aD1*u4|u+509xYFp;O z_88Qh)5$B<&#Wn>%y2T6?6j5tWRYC!O^_F<={I=KMswQ!Jn)vx@3&Kd?i`Bx)!e($ zTqIx?C^e^ycLdAasDdGG8p&p~10BQ(X$Nu|H$xNLe7JBRXib*lvb$@0Z z7qcLJO-DF->NNRJZXY2CO7XfMJEG7yX~wAGK?|u|V4gE6v(u+4tD)6I(|{|wWQTCG z0=}eMs;0Tts%MUTYEGN+B-`VAc?R2bj(jr5{6p@~Vs{bZ#i$~1PN5g*D9HRAdirc_ zfZ>y!^d-S5!xRl7m>kp4jYf(-PH}9XPiFI9mUBGnx z+ABCrSWb4z+aw!SxUtdE|B0^^Znj^TuPPs1lmU)(+=(Kr?LOItseU!y|6J=&p?dDoEKI)Hp)NO z$$OU5jeZMaqjQSW_Res7AOkpSm{RAsas`(Gtl0s=5c4AjogFUh8rEWYH5o25fF zGH~?}1H)hpVr+F=#Ca6QUhuDgvo>7%2K>RA zFV#2}d=Jl%UDq!OLnTKf88`9`yCV}*y!?PV7TzFxUSdu;)41(VMsA_Avg%ywZ_N4} z0+2rUhT~l%!T=au2Dcc;ReY#C^@CEKXyZ`Z%DPJRI@mWOlps;Iy{|}iV#px@IH16~ z>fgmxVSLaSJR?ifwBJ{4A${^a;7Iv%oEv2yp8qQG*T-&U!hkeq0(gX@m;Rh6CZ6rt z$vPE>i{qYVcUtV3kN7)QPIL-E_<;R>_mQng1@{7(0jLC4h%gI7-sVoq7UUkAZkKC- zq(}do1)#*M?k}TMpQ$pVbE0>uf${sl6t$s{qD#W#3>lXTVvl^dy4~%Hvlg77|BDfh zzSV1bp=j`>D&vNg(t%&*^e&z|uDEY)e?=j@CRWaNP<9 z?c|n#Zp9TP(e4*TFQm9sJ!_h)Cb)Ae2E7{&>CMSUp8t(a>p_giy9X+a)9q+6=^OsZ zuPZ-lIS3AwrhIS?5cpaQF2RlT=wnGL{`Q?k-Z)h&QK}#t8_Za33wsh$6spDK>a|fG z*Z1(HKWB05eb1df3Z^m*eovs)v@uo8JDP;;oB%R&l%wq5`j*T-QPUkXqPhPLrMpp zAEEiZ*nsxn?%gVdBv=u0>(yMclV#6wPI1=)8rsI7?-o)I;-W=zP6nw4#ZuHyxAA)! z1QNcUf6tzK$+w(2uQ^?0&&B;yD6_r!t76#d2r~ojy~W^Eo<)`Gv)VZ*^^5J5U&o{= zK7Ua*|Fmdq8RvKHAsA^cP!A|0F$_i_t7|_qLOhheEcb%kaVXxCtdEmsJiHR8iaHNuccFP%8RFqA?HoS)ALl&4}_) zq>?5kdcj}qS6pD;t3pWjM@RYTPRQjoENKgu$U)0(+aTUH#em%1(7Q>$ z^XO+w0f88a-)_BYZl}G=QrevcWaPp1y@4Z2zSd|&!m^FdOj)?PG5 zz3~A1yg}VroY#wx+*50n(eEvDc*_)@;`FMG$LRaAp-ey31woPq3*>thaoi$$^IH*c zPkEf);)(gEf8)rI_ld{{AM7k`esu+fc`qqG>^(|6B`a1qcw0wkw~{M@4Z);BCEqQt zwy&3?4L%(RzOSQn)+WV~Wa{K)y_4mPi-EW=$0P6^Ppx!oTnCC|wt>x~Kl}ylLV#lX z!FPvNC#-026EPX*>W$cLjjl64wzPz0^c(tQbWT`av?#H+4Xc$~`7cl}%2w!M)pZxS z)Ki|jKU`VnipCCOSA3xU{ORKkba+1v^LX6GY}0NS96@61$?jE6J%I!Gy#Vy(e$o$s z7i*x($tX0+eg&*ZW@QQXqN0}_Z@cV!_jcy(v&@P{^>6^E>3Q(d)}B76rgK(~mf(*W zQIAFql4!4eb7sEjf~21(crHnIG0^)hPF=v{JQ573kWAnh%2v;?!Jj@;iNJtI0lE^{ zC{miRUHaqxmZ^9!<-}hW94|dU^g%=_!J*Rkv}~eSCUM@xO%dN~AxsDGe>8=RWQ|8u z&2C2ip9buyDhYmnnpE8E9t(n?H~toDvtNmbP^!0UG8+~iAMXx`*aJ z4wZmn+O~EbzK_b|La{W^^5ll#rr_U*E^EWH6Ksbo) zW!QGS1|L#Gk$eU`x-?iQ8$3Z#^2`*!gLlh1X)y9pmgb&q0?y(NVjoW4DhckPGZqdi zDrqi#r?+Mpv){pCs*;(oF z@J^$9gXyTx)NeK0{5w)FHTDV+%kxbO^vdI~O$N(hQs5fgpj7M1mU7%a$nl|2YIj{j z(F9}4|K5jav{(4&zQSLVN@B6AQzz)lS2!UGU}fL6HsM{omXrp=)-;vmf)&SMA~0kw zl91F%fgnijK7A4l-*Jb&qdqpG!|}uADgd;1N9dfq0Y_RZfUQX(00)c+{>ZG`-5Ym& zPImp9Xl<@6Yz`9)_>;VYjSeV40t2Mam$-*1s_a{h8leIGxVCvNxrrGjANUdUQ-}!G z4M%BsT;gB10AOj&nl=qx5I~z{bZo?O>k7IX{f4VT>){U^t>+jn8!V)aGAMDF3)gnB`64PfZ-atSclPja6boE}99t1QnZ7V-Vh4(Mz zzo>YR8{8RKbh0EsMl$%coQ=6q%wN>f5(w2_D2(V^B>Vu$7>erPR;YsWeeqvdUx(P8 z=i^G6?c{G85%knLl3ODHt5N9Uh|WPJfDOv|-jOV(Dix-xu>fG&041Rf7;6`DQ%=(d_h#5Q}2K9)Q~a^P4xxL z+8v!;@QR`+sxwzn5EHoH+jx~gzaP-KOv?%4jRxm)NG;WIqsfTK3TXc!5v?-5sE5Bz?Jhexjtax{Qp$yl5hw!3rhd6FZC(~5 zv84bJ^XNl!$#Ub*S+rsx+OQt@MRauZSJFO6n{f?P zcaS?pk$d?Mw+G6Ig|fI1VePvMIhVQa1>4q zYOZd0k&TxfY)LEK*%gP3rbD)iVqb~HY;x_7dKx3u?!5oCarvJw&Qy%wpk2OHjlZLf zG>#9nC^i?uYb$G?O$;mX2i8zs&#^`^1>34P^^2~f3;s{_Jnet+x%lS=S>_#qx*2IK zX2jxkVAt769hZ2#FCCU4S+T!uJ}mRcU$4^pla-*gBHBw^?cUSKAZZ9(%?ATyJlt}i z;6Stotc&M9+)A-pH&uoOAm%u(vT!yp=hq3zOTD?-=d<|Y94i|3>_SEm`3U1!ms2dy z%qPyZW!XrmBk6*$rz|ZS&5;J|bE~r&_=+vvXRhojC%R&~O@qb0A5Q(61hP~CJK~QG z*Ymc?ELikX7}aU5i`QRhn*R4oh8j@xXYy-LOokheb^l0IQY4+RM|Nfhey-n~7I0Ln zYcgwBwolDIh1~nUOev2cf82pvUs|W09Gs9|DpKg18y5naNg2OOmE695>q4I#GIEd%6H~nw zY(8sJC@P`7$@x>{=EDKw`-su1pWGX@a$GQ3h4_De zCQL?f!EJM{l4NhJy8p#ao83>lx zIz_1ELOql4f07Q~>iSa9tF@W^78$$RN-PM6Jm6L~2oWbbzlA>$&qUmWyhmn6n_W&&XzN@A`0B6~PgDYU$T`L)Gh1+i5L7E`U<@57 zMNy77>Yd_SO}-(ndRh~DO?+JrVBOx171z7a^YZv%^6U9}TX6O2Q5-9Lj>u#|S}ngP zw$}*NttA10MP&Am!`NQZ2>g2>wvX@oyrK95QGg3JV zq|uacXmdyf65$z|H$DC`qU(BR9!bCz8!5b37NT=2X_^Hig=iEGtu2y@v@A;??A+k5 z+yfrD`4_tzJ4Lt)4}H63+Y;~tk{Ma1HV@(U6pzfA;&rB@odwJk+54GDR0;?M9}2qNoKDVlWke|7zI@$QiFj( zr@rQdA!YTMx1^F=rn)-|aZ1Pgky|BCv9@%9s$^vFP}nNJPm<&Fy!eCeJ>$ToB`$TF zGTg>ODYxNoOBwQer@EKB#`VRyBHifA#R4^OCfq>94YU6lJwj=|Mvv-0s4cr_ih;;G zre4rYDyl4AXSl7Kd^I!xI0@F~B1uYJ&~7B`6L1@A(a}xEKC=H}(B~N|!dmCIoSi;A zKLDp#)E`OY$a9G?0YSpc$+E|hXQXtoI0}p>WBl0iY24E(n6iU>fHI99CD+1$wBOvw zDFKBLDg9SJPf+q+KLMlz+cF^kIY@EG;Y`gu&Z6ZMOU{NigB~DLu&6@q4R#_-2C@A} zC4#?9B4(kfBzF{RuC$j*MBHM1?DFyMU1)n1KWe4}=sBkPt!e zWUDjaI%WJQPC0A}BIZ3N1ArxjTvi0MVMsFMJr=7O{$| z(Qv**{xp_2wa9n!RRkh?nmYKq08%^SD?HXC{OgE+c~8TE!N!0_uC5Bx-NESi?{&6P|6Ebc0>iO0C&&iA*Q#3*maJeTb|W4YMsAxG0cNrj0TMOf zfcy~t*zor~E*>&HsFpQzxi3Z`v&N+DmD5h!9tpRmSaI=2C|@Cut;BN{P-x3HQF)KY zXApK5+BJ6G{5e**5`aCqMtMTu>ivKLGQ^Ovgc{}B9W|)HMR5kMFcMIC9#jkxq(in? zQEzZA(#EZq9Wf{Qe@ht`3hDr$P~%|x41EM&qnLVH0oFYSC zvr&eblr$x))QRN03>hhSQem(X`bD9V<9Ja9mMh<1jf)nMWCYPosFHJ6qjsALOZ}r$ z|M$4@-&$mgK+VW5`h&M3PNQBj;5GypU-N(t6C{*7WYTg+A_0yZHU!Q)eRd<&;XM+> z8o=nx(|r}gh+-_50ROY#th0Q7y6FSLPeXk8N|X0GeaXB>`bX#h)UyhGCM+Zj)`%{< zGVD~E^5?ciL^mLMd+FJ4l3~DX@;r4k-rpPVtT7(@&Bw3)zEtWgoUSIs$e{zowtw!C55w`Sx=KPWv)u5VsFlS46Y zuy)MG{I=E$c^V37{ zho!y7c}vTJoZj;fUCi&*XeI|e&X^tio}}HDN&nT>T^)9^zo?MU!}i4S%ALE3H}kg2 zBd?wBLJNe}_0ESc{QD!-g0zyJlA{Hu> zKSn*rKN5(Mamaj%5rB#n=FrXrU)j?+Ye(l0ns3w+Q%q;Iz#WX7tdNrsZqdc9qXT<~ zn3{Srr$JE1IlVBIlbJ&9fpw5Ya8g8m$~nZvh~UK$v$UL4^PtwH>q*qhZ9fkhdKZ-Q zuvKR2_I~L=ID4iQINyF%f=DSbsnzU$RDuj^I`g`oY}3e4@mXFwmg?~E-(mk&Mhhph z%F!k*P`K1rzDb6={3(wsaAj*QP}vvnd9~+u?6psy;5TrH!4pAJc$~^zIGXukaYyLo zGunI-Is1N-iim;uW_$Icp3HOxO3 zm9`hB3wCYGLq4Rs?xkh=A0F|cw8RUDENugWkk*++*=mP2Y?8o~PjGhtco(xo7Jq>^ z&L~YWs}a3xBi;g=1D%{H6^t=!x%cn;P;30gTl2GR_7L9=TGVF~>CY8T6{Eg`^9J^mh=u?w23!}n z{Ipw~lQ$kTZ6@lKzgEAdCx?6sl*bs&mQ>WTT^KQAexbs%bCE89l(%9`-lc(@qjFle` z)8DLmf1-uC94H$NO9Fjz0cJF{&pyaLVfnMhZ&Xj~a{p~@qeQ_Z%8N7ZKVI*%1p!CL z@HiY>Gde@kQYEXj!nR1{8p`t2VEJ$k*whhgNB68rn@|o#Y+=^kDjxM9ggI z7W>s+vHSyOa)N&!+DMB}Cq?SvY$@TuPn#1$)0a$%fWsp!knq_EM-=A*L&ZDoSQvX1 z!M$W|jOhN6eN87JQV1|kM~qrE33f{!6G0ZW1JX9hvCGiwAv-SR@Imgf6ADK@tRE62 zHTbJV?IwmZ!T;R^(>x3W4?6_Vv`Q(Wc9&lr6oY}J534>X#^#vo0Qt>qAIkp5>V`l0AZBiac!BkF~U8mcM=gGSrw z+)w}%Fqw8du;y-EWWP;sHNx-Kb#zNI=Gh_NEMJU(z0AIhwvPdA{*qn@Zilc$EuDkP zOz`ZY=a-$yoQZuw;-V)?;=9-Yk7iR@qC2Mlg?TA}?`zw7ms1t08U{S?m__VXT8iOo za4R+qF>|P6pw}VOfPCuQzO!MAh}j>#9mBi|0mTJ$gX+IfFIvQ8(4@cegFt!CYl1Ei z$&^+h@m6?zcm<7xnqQj1!p;VRYhVR+&ub@19?iX5nOCBc&y?y>6j_pG4&q+KR}Z9s zY^+Ug@ z0)F$_=CtX+45fsx1^e-Q{9|-d3?p0k?004tal6R`7#K}=Yj4obpr=O(W~@_cY|wFy z8YUybhUi%@(um-4lP52K19ODDoN~Y;=4`A@wdW%P(mwMLh)cfWq56~%qkacE_rBX> z1QJLTq+rQr-uf^Q{ODy+i`3Z})TU6W8WQ8M>g~^2WwkI8sd>zg+Wj-pFp`#WYe#al zLOt<8WbOAg$VlM`r4bsrKo>ZvcLl${n$kW8#r(dA91LvA^MfbZQfNYO8GN2{x8D zhBGwIXWRQkFecTnHHH4~WaNKV5QmVE-79({Ujsq_EA7@Jcf`GHJ>1KgFPPu zq!zjl%j}lf_+hcDT2Lw(IRoqc6Rr8xTb!`pvbEP3W7{Vl??Vu_QFvq=luU!LuMVA% zVrOF~M* z1TfxIad*jQt$aiYyv_E(&5Myt@Go|W6X|{a#+aZLLv9=q_PHX)_{HUNFeoP55KAPWlfqqi;>vVrMG9}IR@Q=W#~2&RCU zmTqoKAtW$O5f3oPScz1njG>>ym6~klGzJ;KV6lxZCFNENLT*C8e5glRIo;yG?ovc0 zS|6^n9+zqW5^>tG5sCZ|;CM&n!-h0AOq~mXYfiivP!5>xun=u5cl%9wA`0wq zX26MSrZd`*gwBI%(yi}QzN?*KFg)I>l;b#8Kj&``{d={yQwty+$Y0tmm9{pFd!Jn)l~g7xsQ_NC_YL z@8#hXsar!W8D?gI4;HWo;5UwukqJKuCoyp?$gIW)$H|%@M&%{Aj=SI5#DgZZ!8+F} z56C7(jnPAl>5imOkqU;c@qR3T%lv%MyEahFr)hU)M*)p+hyOUUAi+ALPk^S551-GO zedSsyB_Hgb$;%@>5C~G(4;{y3iq0Q=l@mfxWDaBL0}WPLbkl|DE_>mkmniZwpD>2R4}uyed>LPb<>fjclJt<1NGxIK*+2Jnnmis10dpt$NIuI#jCs0 z-xBX7EH=JoH{uaeU|uMS$^wT3w@$yhTTzE2?$BDvOi4*uz`0=xC4Hn$$JU3@q>uXt zYmScpz_9}>%JMbRBjuA4ZNZFBG(JgqgQ547dhT%Y62wfSC7`RQ0&oo}QE!wkfSe;* zdgPV}2i@_QeekdX9_>h^Nqw_*RVslru)mc=O|pZs(&QdeQg$XGSb};YL>>*ab2;BF z)4*g!Hy|%KykxeS7z&Rn8K{cDXLN?3I-+fMuU7lW;WZo!TSyDO7p8N5LmU>Q|1Ezi z2V+`|>~=(}4RJKMIY&hU_PZyOWJWDf{Z2j$!D%?JXnmM{a!lU?g?bFJELO#xX989V z6Cm>~n`a9zEBhbd5RBOb?TcM3jyF$LS*F~2U;^k4xyJ$0K7nE5ck?5Wz5KyZF&vi3 zR-y~=ay>7Ta>+H`k-{YTdyO|@^4yN)B=ZcPk)+4d((n!66oxFyIf{ybOK!N}?gdZ^ zc9p%A*0TkoqzQZ{piAW~FANapKE^vzL8E}a63`fl4Ksvj&1|-F0b8dQo!`uo74^j26DlEQ;To%+7K|c$Vw}xtNlDjg( z?nc8Sj~{5~IgEyGv-LV8iva{E4}k&T%9A$+Gjxmce_1mF5a2|>NJ^oV{q6)bH(7`N zfo6HX{41qIJX4|%h+P^2cER$KP$iCNJTXz^lg1RXZq3V7H@k(SBE z>SxbX0hj9@`@5Cf&y$WwzM(VI8he=GQc21rUy=~sns^eu2Vhm?-h-^ab5~)}dECHk z+Zo$(5$`?Jul|I%2)1R1>a0L%15{co8bISJMvUYGv>4t!JLq-!JgaO2~W+zV`fouJ_xc-HB8gaeoR7ACZesI7%&F!BUHd?x-!9!6GOE_Wo$m?p7{y)n&JFwJIS*bAoKJO66_k;nXs5)-3pu) zgx$``sNL?FPs)LQUX_OG@DvPVz(bHzPrnM2YN8c2z!AZ$WFd~v&-xMj3Vd#F$V~@F z9I&GfThQEY__quu-?`b&ci#cA{RhYAL~_~b^Ai`PoH6{i$PlALP4{67hIHvs5#b3O z1OC|BCSfJF!)SCueV*>kZNJFBq^r2W`8QV=%;P8es>uNgI(+KF4S@}0XFI`aMe%@X z)Mm>$EYe(zOJC8q@&0o5x~y3}It|ir=3)ByD*va`o9=Uz;Y$pxinsk)2M1n6M7TFtfpXg&R_;uMB&4{xsw8aiZP*82I^6Xd_|p1*5^) zEXbJw0%XWvezQ9eKf_Hhc7SLLoca-_bT9)R?Ve*H3G|`hK!gMqsW}Sz8bdM&o5=fg zHlyZ2-2W`WLY`6ic|2~vE04>onK&ruqsWyURyUo6!@v_&Wl|tHUg$9 znHjU>5!TE>&i8_k+sNT`XG~m4x`vqFj^m9b6KM8UfgWhvQ2{-$2Lf2jP$F}z-RV4k z2A`yrh{y+BjB;XcW72bZl*0IsjfkJZ3Js9u^lpfuQU%O!15{7yY_)8_9l)t5!#+xX zc_~_E_WPh8Tlc@j>j2;j(obI zE<1`I_407m-QbFkN?qFBRO2Ylqril1!pGPAkGG?;H?Z=7VVupyje)MT9Rc4Dux*3w>+oi6=17O2sp!gTw< z;dF+?$+Z3O1L9yTzOq(|UzAOR&N$Nxi85R5AP|IN3@G{8SsmfIM(1j_!qS4m=3zfB z2Fv}QM9(RV8IqZ+Q}x`}U1Jr7+|tCc{kSA-tl7MiL2%qir|)w&0(4%hvH`5AGA}zu z5`l_gJ-jd~o(it(TP`fC|tu-K^5<#;Y(mb*181|>fGR@CIvyO@&_0YB!lD5hbdq4*z zs?wABxjVh7yu0wFXoB$E70XO?HS=<4;%31Gk4VRM@sUg)NURekCx$3J?T~?sRDUek z)L3k=b0b*%F|kBq*;3^&uqR7t>AmP42xl{fEQC_)9gC@Z{^;jXPE2Sf_=0v*QS;-F z7aAkG^0X7->kMIhp)XB(GZjCeEe1e1B+AI(tK4E?H8?Y&Z-zWDj9gaRKyeC#b22oM`i1ru)Wb@LdN0Wjbo;^7Iq2J}pkMkuIHg9b zkPwrpHy{}g0m%y_C-;Deu*DdMR;fB+~Ln>cf$3@RVlt5f1iigCO48IlB8 z3ChNaNm=2WuyZ8fTJ|UC!JG42vxXrWz`-FT^=Qr^C1k+W!oeU)x%0*kc-5(78?HuV z&~A8MB$d}n5K4!8`?QGSf zQ@oY(UbAH#bONV>yJ_WOk0JB(#KFLl; z3u#4G{)w|d$=wlR1%4qxVRe)2$9rCd-@P;B!Po$q2c6cH>YtBtsKY;kg-KG$zB0|f zS~b5LQ5gmXpqqcNXDi_s{DNwFmOojz2rJga-S?wxCwFnKUa`3BKoG{U`>I7v!DdN! z$uIEyUCq~n{_(PGBgGQG;HqUbfAo)l(C#DsHm63cwDaxZXvyVAz0Xc%ZhU4czUTS0 zb?tMvnP@#4@3N;V->YE-Pi%MiV%75*7roI)t>+je2d`S$?{dm-MBps6FLv#BlFPn6 zP3-;K=^_S0O#1Dn;}3-%s(T+Xo)6R(w*FZEAV>=8nb%w;ULM~t#U{WV!N>DLwhqfuQG+;UBX0l$ zd?0=vG@%tR77bZe6tK6SN~jitTU=4H!MFlD9K8R=@_0(S(-qpY$?nwiweNNDHuG8? z>HU?FvB3Yn$@Qa5BTxrF{Ua57YpVv^+R5;)EUakO4-D)NyveI)6%kXTJ43uMB~bac z2%Jy!Twg3i*}~bpE=iu@5MO-kd{9X7RK@CMQ7@zwT*OQMHZLX0AUTA$bPa(g@Dy>= z&It^~7SDb$_|s05I=Y&ae>@*uVEjLG*MZAhjuv8t>XAb>Dp$w-MAa@d&{bJmaRuam$c+lN}hWQ2ZC@gminAAl6M z&o4b;FCQ?6Ozfq8mOj!VpSN1yq! z^+A_9(9tsyPCY~%?EU;lr>=lvTxYXOO}p>;s_A)t10T{@ac0mq$cDdB!Fz9C>c1uE z>BVJgY$x*ZS>F>>+<3HKub=GHP-JA7t}B!4?EE4Xf82R;y-9QmAW^4ri{10F;WBj} zYi$1fO%dn@iM{Jid>I@4z|JQe4aFD+$HYloNm~)`I<_!Uz#?2GAW>fTR9EL!0<;h| z`~W^|oI6Dg?W+V3n~5o-tO}ZOW?H5>`3Wq2t^K4$3mC-vY{G&7&8Be{yw>tiKdINHuuppWfd$G9^Ma$sN;Oy({%ZE>eH^NI=^UIhx6X2d492Q^eYE zjBjy}iHEV1Ed&@kU{QZQr?WrVs`dm}dm%$hGFP)E3^wy@g-i_)+0lFjPb_X#q)b9V zT@iD7P5cSR-sSI_*=Re~Q8(fqU7Ohi8lnuPe$RiwuMlRaj&@vKQflid5M#=^aZ}h^mlIh4Y?CW{Cmck{YNStD&nGIwCZw0f#!L^ z2P)x5GnKJrQ5{~Xs^UE}|3a>-6UNG`p9TY3h1rRoTf1Ep0I z6HE$CN?m=ry7V*oGmGkQyqd|GAI<+M5 zr*sqFK(8h0L?E6yfUF7bDRWUnqXVX1#r?=ozQ1Er^svs#byewmL3^{x_l~b`!>*pY zku(N&c6NeExMhT3JCtTFd}YD`#8*x*KFgG~6z`-}RVm1I345e|M%$xnzR%^ZmS3UE z-dj*=$K89~PlE(ctn0pepA5v(;>Jd+9%YT9j+Cj79O5?o91)tIh2KO3E*$}iVO{9v z*wAPlUM?-(Mq`_U-ni_;oCu}DEt7|!l-UMTUBs|EyuT8DCy!&5dWO{^*4 zYy8G(p=g_WAMI~RcYaz$E+slioZ8QV-0!+yrFlE==W)qSC+xS)*u$Js?5+Y5*Rx(C zk-QV@nFQ z9n615az#^mQBLJG3XSI+x(W=!|J^SCKfl3O$NUo%Fif0t!|yEP|R6WXIh+_a6%uaU}}-@I|<) zndQwt%hd($M{oTwE~i|3p(MSUDNpH0#8R{Yp{Q67W@KMLZBhq3CfMyu@0xf1M%^Vd zfsXnQsv)9JC#eiN%7@mD#H36PlO^_DsMsVjr~_j-a!bO&se}!)!?f|VXRO=(_oj9d z;stWm)n9TDBy31NOVG3TB~u^j4~yqNde4d{ska~Y8fn61&Gf8rRo}ykVuh*Mtj4o{ ztkaP+KSR{kJN+peGZA4C57kA4AzjRGiIQxjq{gFA0v70hOctkeyqjOOJL3s3RtB0( zV%sc*91(4%)^mNL$O^V|_e+8J&rw1(?*2A&9xrM~%fFTPsn7bM{M@oUjYnB~2HXPl1WBT-W^D3wso2~niOhScB`6jVVAl^`53fcoclAi_G)-a%5CBEzcz^pum~0u~ zzvMnMpRJpfI2R`gO4!*7)E@(gvW((pg_zF9C@++ zpQ$_?+*Qh!tz-%bsZ%><%;GDis1b9VW3+GUc<4APQ`rPDjM~3~oh_CDP(Wk&ELH$b zJCV&B)vxOk3f>v=%T2o4Gz|?a#+jPD^C?aksrq|HGrVRtL1G>;{U6ZA9|}Moj8Dq4&bR6pA04tvxU`$xN_qGuaQCe3dhpMz7@zr zf%j+#h4Llc*PZS0e%V&wUAtU-Mdr3VXMuqA-7CBqmow(zsQC-^jY&gN{-7oD0mM^k z&WWl{IHMZuuQIpiiC=SL`^Wpt4n|14jlJ3skI#hsV~}||=H)U$#(614RA*-ySA=C0 z!g*(ej>Ou`up+5!F}ZFl*k{PCO*AZ_5B^z)F_w8$f?!E;?hjgSHa0ILt%r@`&7~znvPhNwZii$fcmxxG-Z* z>;frMoW(&&{0qEmimI`(#u22nHPX2D8`v=XU2ux#2gEzts!>k)EIaaZwT?fs*q|Gc zhr0*h(X?@^XRi6wu@phvs5Oi}!8_6-C?{DVWAoWl)3%)NOKd3sZv!|AZP2T#Jaqq_ zS4*dLVAPuog)!GVXLB~EkHL676ym%~3~sQyPU;SGk&2`7;K$BaT2x}ZZJ9#96J>qMw@ zf#uv!HIVmaBs0Yd06p&JbE_kJq2EF-MRAmwYJ88bltl)yx5cwju0-JgRGt9(!qaV002u0d zQxmdNQZW4qLd<;vUV+C|0h?9ihX|`YA_zhWc6FJb-l744&#QxW*PpI!k~m?-nE-LDGF~ z`UDsRxOaiNCbFz6*TNgYFpL8N>|l6aiZH`wL#TgA(h}HsAkh0wvOnFe2RyG(85d8w4&D|-8@-aRz=LpyNoe^^nlKV#7ChmWl-v5O+Ub6PdSog=y$=#SZK!^83m>}hg%l{18Ib->IHg{6i z)qZfO?ItLYQ9Mlmo02CRi8s{dKtn}Eb<;=kq1<&nNZ=Zvr>@7u#B_74PC-f`_sIe!16oY}Q_LzHQE_-! zHE3Ro{0f*a;_+VYQ?1!H z{>TUzYQ(-`mo^o)|29!@u_xY@I;Ik*+pi9T`w4CTj(h7B(-mF`hJFUj3qjCohX~Em zCl+{%C#JxwwSEAZNmOCwOZv^MpBorL1&#@tZXp`8CWH7fMqw@G* z5MXdl%p(_~8@?04d8>Oyxt9kfPG?8>fd)_D?KSV804eUEno~oe-U}c)dhrS|8RM`M z;g75suaOc{Vm*Tpf-`#4Q|y}_oouPd@$wzg^eDqdEy0ylcC&AEkGLtjn!m_bTFU3l z5L(aCAwVfxVvuL3a9NDIZTE0S`YgBTlQqWHtc|@)>oo2=SQPjn8I(m2>QNw@8>01h zZ$Vq=~`9gZDlMFa4%?i>4YK4r|HB>Mke^tvm;dsWbzfMc1* zOB>f9__JewG|TZLg@ILRC^t@am!y%Q8~VBWjDdlwp-fo{bL!s$*fXL=wt~Qfx^*Y) zhH!7@FqpU)KOs$eH-Q&UnB%I2kkWh~^5yCsHj@9Cc%%p(et)*15ws056LZ)K=ez(O zT!aX*8EctjJtdymOG)PSv%hdl*Jls3Y1eeGh^w0ypLZ|sAh42Li=Z9b`s3hGTo+XfmDA7) z|6W|xry~zL!b;8g5qFEzObYmw(vGtl-=xf#%Ag0Qm$4DQMKyNhcXb!y`)wt9hPcwK8c*^7h8H0B)ZT0`d(au*rMm*gY3Tml?=ef^F%bXg{pzH*AwP{sf+ zs^cW7^@W@n`aJp@wKv!;Zr6MxiX-4jnOASP1F@d)^+g=bLF3?%f(~+<2D!iD(BH&B zyk9dj`}nfF&M8LoRI39;dMKZG(D}~q3Wfj60{Hpk2HRyVH8+W68E<*VaLJw(s%J(bM+Rx_0;IYYB9z-kv;iYM``X*EM!gHdbv;nlm(#f%*YdVAqv44ScrQ z^MYMkxJriw`h-2WtAT+G2Mb@!0j4r0EdR6g z5J3^83HZ`rqBw}67W0OQO&-W4GvxrS@f$hSvd!WFBL^+W!W>4aclMbfLKhuOD;Vl= zI8qXmcSsGo5=9E??`Hh(oO4xZRDx@Mup>Ux_ydBBjSMnkbW%y6aH-~*q!kTF z4B7r$Dl>CBFihBuhkv)uwd=Lc=Dj-(t!1HF(%rPrdcHNZnVVM^YTUo*>AZY@Yw;sO zc|NSOdD~svbOy&RTsa{~cPwx0&WkkUh&8~e8&+nk<@Xhx1%!!h%oVB|>WU86WscuqAQaWe<{K*RaA^qt{yB)+U{Y*0 zN^|*A=(EQ}lJF(*Fnz5#uWd4`A}kdqU59|hK+L1eb>{<)g3RaNG!j!Xi+Vd*7__O& z%QPduVZASoZ%%(k(|#b`L)7P^ld<&_IE2jszXhAHQn_!J2lh1Pk|W+7lAcW(==RyZ z7W%LHi>O3cC}4ftmJiZlt6{5iAq-7>QeI@EaL24{M<`0m!^b5DO!A(>je@kQQN%Wk z!}BPmv8-)bRviK%o5OD}CTLv}3gSAR@SQbv(^x8c+=htRxDEY@ir9nfr&UI5 zmRBlnMyN|sO%CV_d`B_P3>uR5TDD$~u`VAtpCGn$^TC#8J4ht7e)vO$q-XUC_QlUd z4?)l#XGl+j^D|Uj=RZvWn=-kfjFY?Z3iJCNnM^TK)aI;~v8kMwoJ&i=*a^z3Yu`Vk$%5&|;b~M3Bcxaq25jioqv=aq zFgtxlpIh3T@o?f(PgOLzG+Au=@4T+Gnx5WaVKF3&;#Mh;;n`~h{|WwkBXrL+bO|~` zoDnK~FsT%1IHy}~b}v}?@_uQ9$Aq7*c+Wtneyi=JI)2IYZ}FBkAvxL&gD#R*!OBiyu7{;M7a3$)duuRNk0QoB&< zG?*@cp)UC+!Gmot-;NkGJJis@9J=*JQJDuxGCHSTK`oJbv63a+-7zQ*htWp12&sSP zM!`*#BmJbOeOLOSKTXk!85v_aNgk?i=31l+b%5ZxoHi}Zi1l0)`KOpXePSup@AR4Y zi{IN1B>c1*R1At|OFd_FoT$jT=4P$$5c|V_GUvu^8R3f4jn>_R*06!)eY_#lv)sbu zm+Mgt6IxTdjR<_pUhg#QV1=c^emYsTkC*XoeSp(6& z2|OPsh7BkEK{&(}WUi3g+@_|uq0H}asQi8<{W5DLQ%(;VPpC^Is5a!=^WX=ibL8{h z3_f~GR8&Q)l3D<9op(%(Z`4UDbH>=W)PsqTa;6yzUt#|WBoxDeTq$Ac1O?&?P#bcz;S7OQEn*KvV5d*FGP}b^M+XD?oTENbJ zE8Hh|x@&!u{GcF(Wj`5Ffj0&y%a4Ueny-m969lJuE%as)VNp64>n9$*$ucNJ1Suze z`#E}$Ja(U^!*bbtbFP{hXnu@p$_IBxH%XWJCNH7rNK+0Qr3;}uJ3T{s-f^ConivL< z!u@OB85OIo-D!Vvqnf0a@-aKJs|>>8BO8{UjGui|rJG<#sbCE7y|O!kog%qISeX3{ zLpH3xjxdc3CgxH$WO5_1#u1vI=i91pKPP}Mif3LETK4qqC^tqO{hn-c!+^4-7Kwxa z^)sR4X;{|Q%{I@@ojFvbTs=SlQ778n}ja0 zbsN4si+Bi36Yy;v+?Sz;FcGH_hwN%bL&H~L14xM1gBm+ZHvqs1t{v`JxuZ&l+5#|X z1823#f^mpqeVEe`j{JZLgbx1&aEUkOAIR@U+kPSDQ6|T&jZ0>WC=@qia0wb(O63+3 z=IRT$D0R1aYSGefwr;X#ZP1i|PSS~-1CU!x)#Jnh2!4kBq*sdzBbo`+gljkp;rKrM zx_)dS^~Y)2eE3jukH3RNE0H5HnxjA!*doUY!*PPJz^dY7UZaJ^cdII4@(a)MUO1BDp-1a2Rq;Tr{Brx3Vyn)=qfKcaj2TQc~E$~Lih>E$`foBXew zO_nGj_qJ`Mk?asFR3q^1vYq>JK_!CrTyc)}I@Hn~>y!A0G-SCxk|w!-m+k@2LCWZ2 za`0vhUwZSxDTW~IurPfLn$*nXB)Wl19VV&HTf`>`1?bpDv1Sj6-}W7d&suM*{pteu z$emnXP4|-{$~kVHQd1ky%KuoMc(DWz z{pB1_W#ycP9mF)@@W*n6anq6g7@i!nlT~4u%h$v&2K4oDT)$tflOp=aBkv4(CPM#m zYoOUze-}WF0W_2f19}K`>6gW_rBGe>nv9fKMCYha4yIHOV@1gpyv6c;k!UeYg4l-> zRG{M=p`I-L7R)m|fZ^N6M1~zIP~ehWP&#=f-Y0ND6RSJmUb_wS5X%;<%$X%YOpw1m zkh{}0$x!=$v}=M%Sfuj;w=D5E&F668O~WCp)uqKEtdvGWcfhz&o{+J0!$<_|V@nwg ziT*La@*I7wsPY&3g9Jg^*>{~ERikZ*9NsgG^J(uGvfLg{C!6=A?TqIn%u@e)-Ru|4 z%-!Y}-%S^}#|H2F7Aq+JyRK}+V@4b&xX^mlv6kaP8{r<>GB2fCijTE^qtq>4v>5jV zI5}kp*I=Vf*}>r6S$aXJJ8L$-gF^U`$z@kmxk_@c3At0S<|25%q*!xKW?xli%Q3Ng zA^m189IA$0Rn(gzm>$R9Iqk-XKZ7yJFd{`Kp*%W&`0u}wf#>*fF8WikWq=9tuU-2l ze8}BQlJ1xfA0$QCibPJ z(nck@4TMpX99SbVkL$f~RRE_4co6Y@KE_(+bSy)=3;XprJJ{I8HAj}0M33azze+9s zg2qc-Y9RE-4?ZAaOqc8iCC9M9#a(cHErmU9U{=0;-eh@+VlO)Om(Ww&6!AboLy?GGjeTI>MMjE+(7S#C?sk`# ze-HvdXToaW^JymZ`;%A$ORpf!#800ZWx_d{Q*HHE`DEN{#}!#U6f$-Fqs}KWuQE*B zfeHx;4!FBk6~~upeVWwXB_HR+fwN*DW?3r=fhLBU`eb8$lZoh^$O=_de0;%SOE3%x znoqO#f3O$bcXT4t2)UA&`0$p^hIyqT3`;WP@nKyP^?d^A$M50ypJ6Evgyv08En#py zXKUA>2ThUy-k)}Gt-<-;)nx;{gXdB}!(44uTY^2iARCj8w}M8r*~(K^*NpSTf5oqg z-uFQr9z?yLE{~HjP~ucG(v(Q2Mr{zt9jc>f3MLZ**JefHX9^jhlwvV3#TgX|2RPUJ zKH52yoEebiOFlV(lATpaye+bnRCo2%mO`1NUGA`7^Yhj zXy7bZ@3FtQKv}cW9`M)jlI}`s7l85DS)C<{cvn%T^or!D=ud5Rb+&Pn}aA8kv?FHpr;lIEdD^pjLb7H;SHR(zVRH4*31 z-cJ$jxXg@HhrNucdy+V^1?YX|_Q=ediuFr=)Y!{|`7hQk#bnMo*M8j(%%tnM^1A*fOr7FWqlsE3p#~4ei(qn z{!Q}0fvlEO6iN-RyjcIo>4+L_!1c_5NPY4hyW@`TS3RNNCcoZcldlKBoW`a+SkzNz z&Meal_S)-roU7gN)?t|C>?&xn9rLi`PX6ILh^9QwWGX#wtR@OdFZ$XSc0t-Cd{D!2 zwwK){60hC^LMJuePshu+-Z-<#mv_)id$S{6f}}aTNTX$#Q^WWS#3KI=uESLK80YU` zjdLTJ8xj47VE zn1FMSDQ&PBb+alls9dkV$X zgE8%qrMqE}UhSfKsWg)s)DKh#wJ#8c>bpJ*Z6IeEO@r`j9p*Z`xF>@`5;$M`VKZ6$ zq{d}bMo1oNI#C9g^S&@S)+D1!_u1u1>Pl{>01)j3LQuf7Hu2N{u3U4&8`Ud>PaKQ0;o$@TOscZ9e_mcBlWggL8BDPl=-0HhK%^cA4BnPGllljQyhd zCiYZs>0Yds#Q--C)3gA$3}-6PBJFOc3s0_)uq|v^Funo2l@hz#n>MC?_DkCkqiQJf z9V+-F7DOR9IV!yMPh0rI*!=Z()vk11)6*ykw6~H7#@vTcGy>Mq@TACk)F_4G$!J_z z^+FhE`K`h%FZF21iw8i)AHZ^ju5>FyAaM1sFIh6!B>sGj5(&8uar$=|_(ll^n)caM zK#|m21f(;jA8&Yy5ajW6Qv3@gSw-AKc8J3hYMK39F8CW7+<7#V7ZwloTq{i4v`SbQ zOTqjo1Wq^Qp)dH;z{NyDy#`6JlcUJnyb|3$)arcGy?3JHeHt1X2zMfV#N9I5cYCsL zwre*W-DVB)#_?*$(_^x!&3$L<^3C-H`H!}JtQC*x|04@4#f)$v8Y0KNFiHU@VW0&^ z%=2mB1wHOhvn7eo5rhsymh_S}U;uD_Y}3Qd+4gIl1y7xN!F~8YY(TWfp0;9dID1Q7 z#!$j)CZT^%$Zxgzo=THM!f+ zYhOeN!n9Qw-a>5YAk^wf|HX&|eCO7=`rXwT9%L@16x&~d@N!f1U%^C!=DGS%C#GE9MsxVuaK*Fg{GP44Jy`(cwoOCo5wc4UM>1vBzsMF;WF8kXC? zEjzwy%~eQ4hiTNpV{bRz8l@Qc`3FvaQ$0;M7?7b^bPUHiz8GgYXefr;K0Y+qT(H#f zdp$TLb02T-{(m{}+4nhyBu0yRWfn;hB%cuD!ntZw{9bG9$ENh3TAUVk+_`kg_F*7C3{{zDC`I!3W7}vUCMg!~MIdI2(Y^jIhsXc*Ey=!6#FKhs3@D zwB|rB-#-Pjnp{z4*t-#hIV#E-q4JwLJa)Yi`TVgCmu0(eZroO1GIootT9#0j@YX-v zjFJ?cHgfh@jI*cW+(Fs3amZP#gib(u5ZMK5~ z6V)jbN=jV9yk=o*9|F080nu3Nbj-_beLT2*Cy)U!34h53(0Ax>ocFmQ8d6ktl=VkYZV-`cooT2Q$) zmAzVw$8YiUV#iY6TP+*1{_YyVz{jE{oA}u92g97&SYP4b8%Q4FlmxzQ~7pc>&0o9lGYB;s8ckjQox8e<6u%4=2b@+(yNx zJOz$ZVhpIgi>6IYX-Ar;ZzhJWmwZUy=DFFb*MkkW{9%S1>5fAZFp`JY`oVJ$;p zxPbvvS#zO+Cj@?IHg1ezfcQK3r^TI12_hk7l4Vvjp&(5`j4rwzIO#8_yxxv$LgeW1 zMF67HqwC}FtDR4-P5H8uA=}geE%DU8n~bez<+SF~+I+>5+KSgoVMDpbY?eb%CfWpb zT_|rjyHnd*t>?X2i}%nv{1N*CBCnwyn}84_WGhg>T~$xuW~aiACz~ko)qo$;Y#Q^b zqPAA?mloVrfl)!QDJ4Acz<|E9Lq%*|lqcFi==@v^x@Rqf`CAV z^ZZUZ^DEp67GAPk7sB%vBZa}eL`G(2VE~L4#M}tX+1_$cF7<*$z?9MfImg!UCc+n?lOSK=@`pKNpA@n z690S7sgR6tTIki31~T1=-SDXaP(D9M9CR|t|c^u2(+Zc?R4PFNm3GgC4e(SP<>W&t>OFM*B*09 zW%Zz+bVsK1683IMg6tvFp5gU-!7#tn{|dInjapd8L0Ze@#Wo(;Cxr-)ktm~yU2|-M z3XZzBo5igz_N@%aR^?Ww9>qM@%1;WOOF@Og+SS(Y{>VWM2js_NJe@g*A~Nu~Y*}Z^ zbu_-`4U2kvwYMmn{-5t&wzien$l=a`Eb26$c&RMe{sV81gx372Ffce!vw6X=1>Jfz z7jkvh*as}*4EM6bOj}5_d>;51Am}6sbZ9m;!KUaOey58RH_XG+9eCi(Y~xgL&8}5- zR`VbMiFk*bw8bjP?*b5U2_w+)(3Lllgh`8;1QrZPhU#uaeM3BNO2V=z?+g;F`5MAj04uMo zO~ZT(1ra`SqX3=vJ;D)+&-;7N`H0#LNTr)NM2!%$4#Q9TU|`H%p~S3qJLK;>g~o@) z7>Dw2sZiH1>_J}kXCJ7%r06~|NreOhqNFGFmMD_*vKXAW#Yy<1)#sc$>$QADg_rc^ z%!qDlzN1q3zqi0>3`a>ZEmw#tIc>qfGvWl8j;OAn<@BAY^c-*BAl|bK4qRz^QH*qN zXQm4fhmy2r@9b~;vi!7~=`Bwll0WeC(Pcy%VB`Ps)(Q&*&DtV#P_|gegzH2yq$d zdED8eS1vzT6;*+b9gm#uD@(3o8$9(v2?tg{bnU1V`K``2hal|Qb=k%{<`Vr1bbbF- zb1HZe)6{NVpQpkQH*vb~oYYX7vzpmTD5u5Q$BG5Tb$8%M9km7pRz8ky)TS%_m2NGiX zmzQ?o-*!bXk!F9z$eC|PP$MCWp#lvXd3xu8IK=T?FHK#lGZE&aaWBw8!T`nuVc)6T z6B6&$p}U))n_ETy&xy;$O}G2iI|2P1pp`I{|7$@PR`oEbs89IKm4DsobU}MxXOs7s z_zW9hJf{;U&tkUI(&9xJWTG`V^eDv4|LrU}{ByCdj1if~k9`Ulg7U8!=Aw$Nmc55d z-p0LIo>%yA7Qz04w^K@w?`5_yY4}DJlTp=F1t#A2AY@-(iLP`+9}k}*`VB>}T+sVz zdEB@Z3(5mA3agXi2mM0h|E&#DCj)DQ&uw!?ea04Cd%Sul zm!5{0Gd_9b<-~>4#oFPxxWj#;jB)gBYxZpafKf=ZiLl|)?0VGOwQ&5jL+C(OtMhuqXrS!~`LZ-(!kZI#5?KK&;9uqgVGN-e($fKvzfA2g4P4O9#4PgLvvDgStrvyxYt_72~8eKhftuO5)C+u#|1yK26V#% zqtJnU?*=Fuj9gym;-Z)dm!l-G!>$DGBm7kz+IFj6S)9pS5yn=avY%;2H<3UHO-#+w zP!V@_$;8n3+fWNmdgdj03{DLkqq8bqOYu@yZdmJA@;G|Ji577(#Yh*R08s zV8&uC=XczCpeq~}PQBprS||@8`%1KDyUI%FEUd*)w@GAzTm7-j(r8)#&vrUvC2{Y- zhv97&7~?zL>D8Fs2gH!YAhnsCSU1|&*ohysdP=Z-JOP-P{Eq_gby|s?Mwh{tYsXkL zHoeT%fB|ph9?2EBY`tD211YMFd#+X{PuZXzA-wPPsPnzd&8*`%_9rwR!E3g}Lr%=} z#-=fi0k8jutheB51M0SRli*HqDDLj=?(Xhdw73)8-L<&8JE6E+TeN6#iWK*c_ndp~ z9p62_AR}WWd#^p`Gw1S=8;)73vISt8zP-aV!WaBqUgYfKbIQszN;PZ4skPG<^nlA>^d+|PoJ z*-Z4Sl~KDMikjf+FgvnUbNQ`qwKWo9@I;Lg2*osUwFx$%t;Gx0b*)eRdB+Ix;kzH1 zd5ycUr>gk3KBSH&!ckNV(BaTlM;NYFAsP}(TjA)oc1i$3;l!;UAO%d3il5B`BRb}QqM$%J)Tn9v ztY9z}Oi4~11SA^g=Jn+tQxU2K6Asn{q9qL87+6Tn-OwNzj%&J$(sj=`{8bGR0Ht?Q zxra~YE($iw+H(%aDW?y*pmaHWSn(oAZkLeN6XP&;yz$OQNnxQ@z#|Ds$E_==ZG}}{M9#y!o0H1Uy<7=*6tTD8e=!&SWeo(K>#nVr- zcx7qJ?{Z{MU+FQH@j8b9JrE-4KH>T%F%x_XIF5F=lL#)8!moLsv$q6!*f`6Fx)fWg zWM@ak*cZGfcaX(cKM@v8pNP@$j;LGYwYvvh`Jd=z)W+Qk{yoZ;cG@C#I!&B7a z2`OI2K(xLe~=XwK9n|c@HMJ}(*)GmApO+|q?nm^fo>`Wk*j5y z=D_6@HfCtklvh@-9!IB9Nn;;ho3hJJY*CD$pb!?8=Ly`f9`i^7)?v;ZNf z3?|tShZ@A1(&|q6)o^tvx&*m>!wWtx;ai`8UCMRvBQwsQa@@IL;H9WkV0dr9*t(V0 zBmHX7mw_{(E*;cwLX00aCkD&|q}7966|l<2r?M@+;G*;y7oSDXS$a&yidB%U){{&f z4{}rQBG@Y$()+-nXJe=dsc(Rw)t~#EgM=GVG$k5!czd76|g;}ME|_f0g`%w@xyDz zVFk{B?p}9v`dg+Wo$kDe-j?V|kH|6$lEd}PJ*8})15ZK?2AbL91WK#FW}2mfX&_l8 zF)BKQwqWW#M7iNA3JTgsWn6>Ci8pi!wuU9PZ^)nR$Y>A`;-k=Xg<&iH8 z|ERs>^;vpSkBYRkET#Uv=K#t}KhbgV(7paamX|NH3H;gq_R5PM=-`qX#`tbhCyfHOlz0c{MH3{}08} z+v~I5(#QL(W>(~S@opF+N;I`(jI3phQewQ!%GoG@N*2JConXj`Sey0Z#R1EDbnFV7 z;{`7(rL_)#$vaw98*!iqGPr;#ECTjCt{N{^`1Z>t1rf+Q!2r}`c1%xLv}#$90voO` zgolWazV4av_TGug$oEmg1I4*m{VC`DJ}V^DW3bwiv8Kt)95n!gkn-)Q0OdT#7u842NYT#@vZQJ3z6CQdns}l9!grL zo-t{EKOdwGcr^cK+SX}@HeG0gsrfSBzPvyh3#|RZ?_i130_&RjaK5_C26Dm{W$plQ zt`!vn%gJn{mlYu6M^!y2+~ZRu4kn31$AXixkn(FyfJF=jj7v^HP)s)vY?bo}0nBxq zQ-FWQMh)#m*ISv+;nVeb?9~=OVLYh6_S=_T>`U`X#!r3@dgEAqtMl7wEh|Pz@wEzu z(@8ai9mNuofVTs9r!$JSauj{yv@t$-z#DNK60wR#So8pGh}bx zN8#cbi{}f3-58GxpgvD);hNEKWzKSgW&Y-$a^ShU*Y@>r*rzk>tfMF%`)Rx18ZU#* zde<8{!G#;_nITx@e^M1EwY>opna{M_hJzs~4U=FtL5m>)b<8F;Q(YcPIK zmu5x6JoG%Z|1~A@@+KndW7g#gvUua7;`(fGV zwZ%Ans4)LEuOcx9&$ebLm%ki2@UfLHcerkQ0aTUHy`3VPAzsF8t6vF+iaMKh;w7n2 z!Ekz9tI?uZVW<11bQRgSgTr5=5R4MyG;_IkuQVe)8#`RhK^3o_hu z(NG=6DV#3lP|jE(NFq{y1p8lfkVk|5SmdbXrD`llWt+&%=}=k~2tfLx!0LP@>gXFS zVXsJ0oIkYq{-p%@+*zA6A?5Qjl&%KUfjWQ-aL$S;rlu^CMYF60)+$pHsxci;P8j`3 z$nuM)SXVjDo;he{XFIYm2n2>#{tDX{V--o7ca;%1v98PH6r#?$p2w*yz3|{S$vN$> z*fy#m#&UsSqE!U_d2+-+uW-d@G0Q9Yclq`5CEGzKjt7}@0L%>}syxvrivbJWiw%TT zXc+U=N)T4`(?P6v2mfT{IUm`g++!RGbK{LE!l6H z>EPE8Nal#L_m*g zMj4<3FRGLRpRvCkKe1=I^jy&^FAXX*=aQz-ekA|8C-N5QnV2tfJ11+XDoy37B^Y1y znYS=xaz*gr&CIivj24AEjfj=&SNO$Gc>STfd`4D;5I9&s2Hhs|HZhn*Tc006)Z}~| zYP{98qBO!FJ&-dWPl6(YK6KXO^Y_L4uaR(Y=D9o_;|nF6g|qG7r}wj`1zCQ$l~U#L zP!<@gF2OVC6T{+&X-<+e>VN4)E36&D6Gg(AH&EYx!$Sa|GLTe09X+Ck4lL4zw zo#YezOFjKJX13e(nO@SGUrMN09XYY!eE5x?FyAV>eCBng8#9i@$|ph$<|54QVVtij zaO}>CE=iN0S!pW(wi;QJaR~yD0HiI8;0Y}VXEQv%(0Jo|{>~`QGc3>U@VMtCW7088mshL^NXTv*%RC)6uvj$gTnD&ix6loxdN1`dg* z`D(fgbdKGMNbmI{DAy!3VT#C@a4-+n2*YfDw?0B;tKI7uAVq+sYo(E+A1T5s4NP13 z=Aep4u;7JrBJ9S=P;5N~LQfSxkD*KYkcwHRX2eQ3m82bF5t0u~N_lrOZ0j?V1Rk z58J=@@zbrO@9P7#meIJ*!uv44vV)NhB>gx&mwULW~u*}X85_}(kNa!?m!|T$f9dv`-3yMR-<0< zq9TG#6a&^fK>-4;ND#QLTy&MSEIu7GtTWp4_vel_O<6SH88>)a_WQH){z#RjkxN!G z(&8g7h(Cd8QG%z<-bVw{6@18v@Szd<_p`*C{<`ekV!4JSs=xO3T)6sHI0yro$HgHO zQ!DFJSWiTJAz`*XvQ1~K0{L>+X2W_}%3saYs#B8=gNA#zKAGvO-KAW>uG)7_r zd~7yShi(kpNLJ#~3A+A)w<5`k)aWRu=wj4xxu}!64vON& z(5+Hd9z{kEHv+;;>&s5d6N`LY$$IJtatsvQ#^VIA;rE$}&WvR8|;+w{yL;{1I(dES3Q zS!O^-&Y$wEU#AnEgJw|1n3ePo#2#?}5`p{p`sY+-vVZCMGW+w-#4X0Dz~zJ<$~m}! zr1Z?ALB53>OX2nkG-0MO{ltA?lvCo*C!UO{SW;aydh7pVI9yU^h&nfv=A(YTX~(0~ zph1vv4ykH#d7jDT9sCYSkD~@O4Fgs2;oQb}vl#3$DMR(qpKI8h2cF}fi-s1 z`TBrzwyam)hRJngQ8---EQ(UT4jzu|C4xD4oA;| zY8ejH(&dC`YrmNhoN6UqD5u$aWJFb>gPpK|XR4kd;Bis$UpXUkfPU}%-5S~?(E$7v z+kci=08Ci!?Pt08VMzwEo>|U`jclKGyIIwLw`uzdZ@={M`4)-*%Fr#PgWDW2TmH+R zz!8WIdQ?R-S{Rj5m(VY6xMUB|V3s@+`bI2>0vn*@hRe?Cu)sQj^Y>qtZkBkqcEnBa z^X2ac)_N9rY}o<r$_4JEyW@5$2^z zPILnKFtYYwDd5B&sE@+$n6&QydPdXiAwGzcq8ze9-na zLUJmmy4;#N2U^#yz0Q|!{(J$9g#b8sQz#tui!VA6{bjgu)(%%Z=eR#H4i!2>153$G zp^J@}*r=aWk-)4`;lZM=Ir{V5h04iP`b0L3*=Ib#zkuAbutF3`)Q1pIZ?v;jpat@5l799`wb4~*Z9TH2mZOjWQPpGIE{jQdfC_<25ypf zKszK7^ok%6NP+3)65>MTUrC~WU?o`vP$%YvaF{R=Q1MdH(4f7RkO%Ybo zowTxu2*L#Jv%QSK&~E^T`?N46^XE*f+`;cc?Naku6&y0vP5pm<+W(_GW=Mi?F0JH- zoqpfQCj+pjA9a@H!~*hv1ifg>eV5}!w84~CQ%AI+%^h18!$&EjJ{}%hfBNJDH&7L& z8DmYK_X$V?tHhYr1>NJ~J5s5ID)~ycrp1D)+Bp|ES{4hH+Ewl4tOLQN@5fWCIOQ3m zY$$>?uW($Icl-FTer4Jbp)#^tW}m;18k>WZk>dPVeF`BzW!brwv6gNYdTArE3-7^% zZn*BCfERNsV0c)vt`{sEoQ=utG*X2gE-tPDDY|K3mrND(YVdyYMt)}V%(!+J2~s*l zyP1+z3*@dn$yHb?Ru|{SORRs6$^#$z?TH0!vW-$3geo*b4PeXLt00bGvy%)l5UBzX zxRZy2oKGy~UU}94{OQ%Qs+#UglCbDeLb?bIt&r~<7UyN^I;{Hs+7k(`U!q(k$MmoV ziNV;D61Qcn(DOyOiWGca#er_(nN@b0=ec(z+zPCOXy2raZwg^5M-9RRJ?Tn7*zXL& zbR{KGK|>dnw(k~WLFJonH)l>@ZDMhuCoD-GiPzv-J3vveCb}FmN|~>Dr`~RUgF@>q zpQNp?M6f9-H~|cf{274G~Q-OE9v|+{ilA z>cbU7QJp0Q$c^hZXCYvJ>*MH|lVd47%-&=FWyyQrBIqtoL3=okOVV1l4H>PRa#t~K1L*m^gm(I}%T!`4& zXp2il0O^fN8D17&QS5*_lK3Q?3o)ZWIGB4B`?GqK{8mJE!sKWdKDxyI$6|KJc4~z< zCuHT+zr?72De>p#AM=)EOa~^>36uu4nglCu23+iWErqKeQ|tg9$c67wZCh3}IP zf7T`~fU-fgg(E;4ue#HSsX91m(?MGOIz8;l@$}CS^V`mLcIYJEOmD_xZIx~bxjM%d zb|!Tjtn@M4Rz^Vlb7@yk_RnemQZom^@Nf^-o+UxsI;Q{EGY6*tZgbBBF1J+W1{a=Z zkbR420bjNaNfB{Uo63a&Bx{3jEA^T^d5Vyfw8xz+* zy5zSXB*av1mcOthCU$@np)&BAV}-)^dM{{tOUfBAezjgb@Zv;Bg{6ZGBuI058ez2&~X} zj-$}6I^WC3OnYq|0wAW*BapR4w80P~Z9xN`5ymtor{+8N^d~l}nIetZ2ig7Et&?%i zJ$esN{hr%FXdmZVn;P35GRe>bFLEK~!JyejESRJo0hWG6y#ioCuS&6rIkt(&3zOT1 zxQ(YE_O4XnmHu4zF4ni#_dp13la{VC$%V2!KAPg8j==~EU#=>TC=c|cON|LZgo$dh zsCS;lX6^%MxI!2{f?kL>;vYZ)rfMNB=-9vy;sm`ta6UWkKUo%gn|hI3h)uKN^U^qw zR1~r8n^5v-?48&AN93AkSy3<};Qp&wi z_y=*yG!!s3%XE!s(eonRx_+e_CI7W_GKhYH%Pm3S9_(tFWes9rs)c?q2A;tG1phq! zvf{jMHboh`UWnqk1Y*Juigj9cPEvyXt(3hl5~_36j! zFfYM^JF&x*y@y}1w0XO9GMcC`n(nN|HExSX+5gzL{|$JU%273f>TDJ0Sxyf!ppg;g zkMpJ7Y!|(2MjUFT1$-NY{&ygrk9)X0a9-(2jccQNY3X}S`m zygXSNQ;Td??}rUcHC+~$Y>y))Y9K$JlI9@x{iQ^sKB&r6WykICz6Co!f?=)qGb42X z2H;_&=fo3x1Od$_8Vyd0r#+4Xvr-H{+L1lD*# zdraWYN|b@MUad*57Xi_Sx7cbQD1k%d$Pv)85TVi!;$ePA zVR>H?gbYRPE=1>*ctsDjrcyrcV_v;!*sbc*B7C$b#nD(sT5Y@#C7Q>K!omHl?!m;j z5_XCzTkA#w>96>TJR5{%(f)10otLYFqId-pqxQ|Sc#kOZPyaLH{8X#+@S2_6_6#$y z{W!SQHjl6h*65ZkYPfuDGaBb){7Vc+fV2H_gRVtzJuVS$g8a9} zagbWjmn;BrT9~q2oyca%?S-SdZ&-9w{8x@ynB!sTNErURq&~Nq9Ij^H+b?~Eg#aKs z;cmoq=RkF}JkZ3Oc}y63m{nEG$EU0R>)xnb84aNPqiD?7dk1EA?+07kIk6i>l#IS$ zs!L-ovXTqHZ#vd}D;LW5& z43+KoWY>RO<;K&7hs0c`*n_N=qE!raQy_!sYuGW^whO^KBEAJraiD#t$RDJMIs@nQ zt`||onBL}@UDW;E2Hm)A8KM`M=2fhw)dT`UR#v)Ws^ksZXMM&dYbd@Yg z*je*fK8ZuG>*s-&mohDYTW1{PfGUAvC$`A7F7*%Uf0%+`ReqQsIIv$e=X z?puf{Q<7HkqO(qW5Y>hGx%W`O%`lR%H%wrPl`x>L*66!*7v)bq!OCEkC#4Eqq4MLe zS+8PY972X#9NYZsCru0FSegMa^okAaT?Z+vm?&au&4_^pJUH(2b<;FT^L@7e@3;Ze zT{AG~z1sZi{&Sj9!glgtz)T990MVgy0y+&jVRBJ1FN9GYDhXl5*1RYfmL_PiSfl6p}o{pgTc1B+6WyqqI9r5 ztp$))8jZX&rh~3Q2hBdRqZkp@Z<7j>R?7#c`v*UHWTh$<*=SN8N5Y%WQ$2< zVCh4a<<={vc9xZcWnQ3ugyP*t;yniEfF@|l=Xp5kpy%YZg^~WUB*W!*#wC=m4>h79 z3bd{Y0dMjbrTNKm#J`s3>Fp8=JGNi;thJ>E^pQ3;2k*`Jky#Lb$ps8%RS6HXy2017=ruYFoox_j=cq*)X z$*0O4sR9w+%90DiHWyZ63YpN|6!CnI@K-jRw;Ry`I<6|_FN@Q(>(@FStW9s#24`!> zk44H1YXnYaL_IH@YKJxl>Y3eNSS@+hc^mb+@A2M>ZC~IIeiXF?*yKux4UI2_93Los{ z$yAp3Rs_2_?L%tGLnRj9F5jX-F6I{=R1U%yJcV&NRr>#41R0leaLL!4|6IJkU7SvD zAvFbF_-)mQTu*;Em{p1eH8|wX2f&d$*nOt}UIJ}-e)@=-nC+fcLE|}bq4M-OkSxT# zmc6BU{qM0m#%wzS4gNN6ZtUdbJFNX8m#*fYisa=8EY*>Y^A54SUc}#swPEsRLf;}K zoPh^vnw<=OO}};GXpgY7{WIk)S=B(D#e6%dS!&G{--%ccfMH+Ht!U3xih3D8>r1H+ zV?pAqoo;FlLjUk@(yO8c3q@6rF5NtIvOWo6+HL-QQwImceBDO0SA?wWG8)nhcK91c zh|(2^LkAEDjV8QG{&RX!5NyBzgL)}pa{%=Gh?G;2&63U7Pk3(1kwy zJ-n8gXTv4b@S_vl*TbU?)c?nrXWbEG-FYcomBvO~jJdjPKhr)R519I%ZC@TnJIqh< ziKS+kzo3eNHj@v0W$~P^d;T3`^6YR31grn}1&c=-z(fW(c?N9o56p>LS%U0HUdf`vkD7j%y&EdAA2 zZ;I~cAb`|n;LLhicjwx}1I=cI&vD#ocQgN^T>eX$-{t~WBx^LjF7@djaBDR*Z&)tCobjYC^UfMieQLQ0$Nu3JE+&M483KMB-*@?w$W+~GPU^%J-CPh zi}U%P&hphJu9EH7!bQ|t=JaBs+r{@2{E-*Xs2skqr?PZ{hpeem_T>6Frj__zJqX&5 zc1i=eAbJPB02qw42k^4gwa~PM$_Qw}fY5sKfMozAY*o$RXeu`8b}?uw^6@A;0w4AM zFL~4VoJQZB-~e1jDCvl%mP3B00S9=0mQc0vkFFm#(M^*bc+?o>-~1a^Y&H|u>0lyu zHDP%Slfu*)AXxt-jf$k$qY80rV!v2%LHJ~!!wE+#yOMHHbQ7yJYpv<2PqOmgu zmMfFv&Eeb51bPzqqf|7>Wg~ix(yL3h>n%gGAF3Vn?`^qOY*U+A1oco@kQ-M#Kbmz2 zY+M~%5ZO3`yZ9MLoWz%gTN^qm(RC~MLBGj*^}B8?5Jh>RiVVe>7)8gY{72`V@Y`4x zL68#8tzM>v^GF7L$8PqzHBv^(WyE0V#hF=;(8ozFBc_5Y>MK2797y8ZP}Tia3g(QC%s4VkJYv|kIBWuNyT3S%p}*oJ zFzx66i>&FKxjWs=B4|OEaet=0%OUB}4eZD*xl3 zEVu2wUx83PGAX9K8*gITAyL=PpoqXf$srPx%0f+FS}ABzfIcr_gtnR4(hSd*qFVMO zDu)}MHZH9Y!`(^Bi9kXdu?9Z-7fmIEvo3896H=fF*=ixI^zJ9KFmvkmapMl1M4DYY z3A-Ni0?f^%GPJe6Cf=iLk+O>2}&qA zgaj5{Sxj7_adECHVC19PpBkl@mWH20b}?r=SoufSOpXMG&dngf@dj08(&NFQaJKJxEpxJ~gHKZZ8uA zrgpR)G@LyZ5^milgGj!#`*T}Fe^-{_=BKbJI8Md>Bl~s|Vu1ZxwS5Vvjbz+&EiijE zOu^z|>Hv1ZDA}XBKB*>6t94JDV5OebcDE&Y+;l*k@1*xp{Yn+`##Q}mOdSL8py2~k zlHX<;K!VVYA!iHU6!RVAncl%vJFBwL9FlTh32RuGW={Z<*Ye_q75oPYF`jmZA$HjN zDLSiA#8;ZoO|Z03vy#V^JLq#3Ly>cKJEP}(A5W*>u5$2(MakIQ%VeA0JkkjGLY=*> zivPsPu6+B{HH=EOd=B&fH6;cp(zs2D(}3*qlx=VxKzpq>y)NR_!T}{HSFt*?aY!Cf zJ475aaQ3y;ZMqF3>Or5nveR=zWkZak(y0B8HFS4%N1Gt_l0+pX}_Geq$C- zFa>md(rWlNfl~!O3W3)@NK>#{*1-MNKt(-Q?t>+N6_Eo!t;tIwi?tX_ua=`-o&UcC zl}VwV^POkT&3Lul>&bkL)~Z}z38~P{%UM{T3zUoW$a_J?#Ipd&V9pPe!;+D|d{XF9 zATJNb`E^?XZPKDaxSa6dyz>C{aid4L0&H#>QWRJ z&4=LYuFx9Bc3H|-gAyk&hdh5t-^Nw&mF=*xo436BKBjgPzc6WlFHL=liTo+I#5PPPgn{5#tqnkDkBe)&n}GDUu%p7FH_NkU^Sgu?!MlG2 zG4Ac&Vn6QIh0XrlOR_U=CT_oe00-ozx@Pm8 zyGHgSCOUD=VZ))#t9kA+S-%Y|YNST9NCC``245`yNJdghQvjZCs{R(AE}JuF!3}IG zDnTBV1!K|fcdmy4Z?ZJ3ZU4SQ|1cJs8S>%;J50t5{cxs*dqBwWN_f(@( z3u!G)elSh!aAVUZntryzG`DdmD@z6Zadvw$8#92roZ{Z=8;zJBkX&Jdz1r;9dA$|4 zfGd+gm+o5-I};gz8fc|4rDC;LJ-_4Z63z|#RV%VzCur*CO$;YL)?gofOIt#6r^mz= zwe2_m$REQUH59$ANN=Q1qyZCho-IcHx0U9eQ2ZPG@22`lccwP^8{KqC#|PUq;)_M6 z47#Sa=Kq_`*Tv8^L|`l`H~UKRqdS)>I6O^s4m0vw!b1>$s`+~I!4XI%7DNHpa)46ehJ1geQFDt3 zHH1kqJi$fKHda`Zpzwj-K)PG9=4xLeZ+FmY@~@jYQD7iiMu@uu7HTb;9O0*2|64D* z3NxEe#i$IIuuo86BJ1?+yqtv4b3x?+;Z6x#)*p}ZW{ptBj_p3vC(>?$2=~7V$Gy^f zrGiZ`LMyNhqGOcHG;7NXutzMzC^)(WLp6@)>k7Qlw{ZXl?6Vzqw4qgT#~ThY;w^(_zqy&YWl|4ZNJ4`5v6*zQ90a}nN(lQd5V3L|w|}ku#}(s~2gh@u zIO{e@eOn)QzWvr8BlJz;RR6U&6=GN|GuAdp{K`W8AZuK8oD3^Pr|h1-WB>vV9H$1 zj+15*@`pYYXBXj6NbzrfLxAl-5IKrguwFAaQEX>xQStPCarrT+bL}F1N924wWxO^} zq4p8sV4K=b5n_!+(PVf+uUg}ZU1FJ{vkAHk$#@o=teT0Z&36%ZgL*JPj#wUe*<(5K z8h9o0wWISlV{s$TqJ~aV;`{>ET*8PJ`UGd0gx731isR@NgEV zIqo;GJL9d%nQXde$LCj)0Fbn2_>XVL;uYEE_@Px(0|5rC&GEjw#dRYsCgJ6ikr~?( zE6idVTEz-K5{YrnH&ES`I1By8?X4%*4uqH!#nvD|WdCyX}#Tpv8dt~T%OgTD%J zcLd13@8CAR%82|Femo+)ZdUG~boq*~GB0P{ZIZx^eH308TSn+>-1`H`1-75vg_%m4 zoZY)ptGj?d1mHDh^+&T}Z47PUlY1WCQua9)b#U%Kqy^V5SCkQAi}qk zl$|{ycGqUiX*Q@7JaKfgX{d42VhGAfG+Tt(b=W@!Y6r&6gXJ9d*=jDFMBP7&|*diu#ZeDjnBT0XF(_U8ASZ}B?jv{ z_TUVYDa3H%0mMD;qR>ED@`)8gyaBS#Bu-8{b|BX3(}3A5W;LptBATX&yhq+#Tp40n zu+FGakJnf?YhEKDp+1L-5{o&_Kk|_E&luN3RB2(i7R`kTH%i7 zIkj+5(p+bji}i%WhhcI_?pj~6xy0yN;FMrxY@Oj)-Uaf21)6|?$bYQXr$R)X^Mca0 zU&JYwArOdbL2gc>T^$`zhpsYbmS|~l;5j)jzzgcu(&?sS>1;3VIUvPyh(;#5w zS-|CN4z>=EQWY>ck4rCu1(5)g->L5mt*ItwRyQ)&Td1f~PE;Zt=2jgNJdSYf9O@%b zmeZwO#MzrmDH>;Br?g1IN3St3ef4jiJ%jihCb$~Iabgl6QJocVTDUxN%kqoC-OQS_ z8j&{t`0qC0ia}?uh@;Qq0JxSLO4f#w*#* z;lB!1&fB**`3S8H@0>YC4oy2?JM!uESMk`HT`glr_pp+((f-YrR?(=R+6&M)#{Wzt zCdu@KW{DAF?Is|-1AV92Y602E?FmE+e8Ey^C+U2>Ti5W~_&R2Z^yM5ov(d=dSU4X_ z=5BM`S19AcVl|pd2kFh}<&8WeXyl(B<5INSiT*mw{7{0AQ?zN zo)0ZwX(klTDyN8|I{1xDxjfIej?<8PC-Ws0kX z?BpyAL_Pc+EZR%@$wr%WU;Z)}dc2+gckw|5V9@UpjyUbh_#fK!fHC;*dcJFMn=D4M zIC7N&(fL*Wv1KEr5>(BHf`?4akETg3$S@Xij!;rno+T%3{2Jr^oES?H6wiLJ)<<=e zD6R_;KR|6B9JR_;2EXDHI!@S*+@{>?&^RS(C@f}7eiDL;76KWPc!R!8aY zHDKCJEoTG3W$fb*Zx(MfOC1GM#qZD8um4^bwr$6HOP)CYNls)BB$QexiPKJA72pO8WU2%aho-!CfA;H6D({kd0&+>KH^s{ zvP=9fV$D5hXTRRg$AWn166@^S$*XTqj-iaQCYi52@a>lxT#Ac}+Em}E_mPPZpVIPA zJx_FAue2^xSnG%)sX;Co>UGo92)PoMQPdD+2rJbaqn zZ?AqKKC*K)<-=O152Xqo$p8RYOdXO~ zK~xv7Rsz8~s8jQw>o8zq5rHr{P=o!DFY5jhq5sh}gSGv&q+<4m&%MlQ%`%H9>N@Yh z?!rE@tBvsLWGf?DN7(Px7-ZvAXlJBitl^jXAAVcY=*kPgc3Tix`A66|j6v2wxg=tP zF_gejr=)Kr(Em7-4E*)U#4&BB>>1xZ{tS^faepy^t)1@DQ^|FPJv0fmR0Q6j3BKh z(8Z-swysGE09|TOR6?2{63#+}CbvHNxe}9QjAT05Hn#1f&L;Y2hC?JH=Amj=bTeyd zK@aY_HCqd)z;)v#TBiB&%=BeE^lb|cpa=sJb4V!~a%2augZ_bRUlDQ3^LH0B{GJ~JO?fAh)CwmCP9yOo|PVZRL(ga24DIGo@K zMz~J+0%@2u`kKjh%+AsBYsvsb^lV&IHIiJ7&~O%i$&m9w{(ByK1S;5An`y0v4{ zO2quKz$;)EqyTbAB@~2;NcL@_VEs)M#5ncLRGYPEXWKDuEACfiel1og0+yYx0y`-z z>y*g()-dHOm$vPs<^3bfcs~COVei81zNxUXG9Teswob6N`V&aK(IG&8{6zh=9JR!p zr?7!mI@~k33f6av`9{MOS;tqpgouMV_s~R8a7+q1oG;|(F0!Vq;38U6sc9l0H~FA; z=pjYTdCojr_519#=P#@Op^X1iYw}+hW-LpvVa49aK#zxv0*FBqG=#kzzhd${LaeyA zDK>J|u8kRkqOJ6?(9(NnZ^HI)v5!&x z?yg+2(=+OzAAUXJ52F2Trq+yf7I|{yTldiB`?Me{Y5^cm5UQ4{+%W2*6aM8X`BG-m z#s{*Y`t(*vX|XJk{8x$QJBDKbvD$Am`bm46D{1WG{4?r1(=t#K>n=*w{HIqqP+`gj zYnJM#f&^4+Fj^e^5pFJS@|^dkx&jiSuk`U1SFeJ)3phTcPO_<|@aztNw^9^E^D00w zO(U&8oZbry&ghG>eOIE`?RAY9-D@5kZn@mheV$_9e1H!uA({KLxAh=4UPbU+|3`SK zQ1ZSm0QY3lEy)5IHjaAF!qljdZ_)cv$c0=aYFq)qfyRNEMId)6hhZfrh9==S;k&l( zPGqA>=D7yLy+pJ8e*#hvklZ$uCxWmZY4W&q?o5Ej)AkN5XX$MteR@f}2W1JHcli4p25O+t zLyv-ldA%P!r0I>XpF58K4tNtO5{-VQBBj7j7{nz-uqnajrU4si^C4z~F~+azIyR|; zo|l6fmeX`jsoDY34jf9k%qsDlO%{=X*E2j*n|*#Q4zzLSg08JZsBZf?Eh}gf*WoJ4 z-uBr6heVSFyx%BRV-Gw34_EKt9`_%8`%WgdZQHhO+iB3ob|y|5HnwdwW@Fn<8e5H% zoA2-3bDwkW-|(Ji_Wta()@%Jb2A=`MIxA#lX57u!`d$2ZLFC@ZbD7EjYo|#;pD(9L zR~Rc-FC|kJdbZg6qPydsU0qE}W0mzL8bMfbbWb$ko_z)$kn60mts59$;v(C#mNpSv zfj>}vf+V|sYVZ{k+Y2!me>|uDwn7ptw76gRDAnMwVDnT^OfogCQ`z!+?>J!h2#{LR ztWE@B+ubve^JQL{eD8kiIvODIa^6n-X*VP=<@I6g_f&iT1HVS{Afx`D!ggI+X(Kud zACP@r_6OhK^ZECjk)t_>Rp%&#IK(sR>FkB>$I2^NbDu&k6ry-*fegq$3S%U9!M9{} zq4M@@x#@0DcKEg4JV?^^TL|;OXJ@&8JKHx{j;lttTQH;boo?9%ynx1!Y(y< zBWsza#6bvEeePHqk{L_8bil&v7*O>xC3Ykps2}R4gj82TD=vihek8n=|BDpPI&fRE zNr7Nu05h?+yFOAh50m*#5R6|0R<^^2$>F#gtu-^-6y~LCAk|kj_c0=L;*Td9)4PI7 z3^mK9V*o=kx~)eq$$wUC?b8K;*E^$zHb7u+)}0tz3~)S7%rCSX!#W;=UV#L=4LYOe zvDapx{WvvSX}bl4+?Zb?lmI$d5xIvb36}PSwV5W`9F46;;4P2ipR};$w}!rh?7kQ8 zobsLcJaeR|v%yK>0O@$#xrTXvW>ed+WKj&8!PT+-u8wj?2A>H|jrM7i4~Cfm0kCZe z*=wT@c3phrZbLv=Ux~dUQuN7{RioekNOiJVDgLgudyai=b$vP*4D>iE`8B;}<39l2 zq}Id6Mi7R9?FZkgaTwtBy$7H60u2$^wAf@R5Ss})sT0-?k*p?HHQX1H7)dG3kSrSc zOILfnT1&}~gZv>tR3q$l6&i5OOQY8hc6G!#npJ|MzsmTuobRA$SG2;tI3d_F&r93( ziwx7~ivF--f`YCL>cR2_CN^qZ+2NfMWj(U);=asK92%T7DDSf=g+Ev8%PVD-nzNJK zIM0eI%Dx*FYApGE=${u|W|Xt=3F$SgUE}hUMhxsq)Rd0d(dxrmQbbz}Vb?u{8rbF= z;6BRB4lHJlxpdkqx2b0^3Z*DXd&sh>)|`z3@bu~CR}xmQ`Cqk;tpAt=XN&D)1aG-w zyv%DUw!HIYyPt>6*6G_Gd#da4543^F3-Rw^xcWqXppz7>0`Upzf#>{H-yumE?8xf+ zgN3?06U~&Z@_H?{S))_Qjv>^AaD?&i|9Wn4)ePI&2arA2=BXsU=HcjbT*DtE>Z{J7 zjUoRiys`&s0z>436mDKPx~8$j1m2R3j6;zAO9PeUrrhY>j5pj(dt`0G#xoufoqX5EXQ0$ z`qRTA1QIi)XGQ%K#PiUC!1bP_&2HT_;pn{y-5i}lV!>{JX@VcdCFdkyFQyaUV_yy0?r{Gil+vO`(!j1gC=t2Bk<9mIG zEa#6RWRY5kwI_ty6xg%%_~Rk(&2p2;qoKX%#X-sUWgu$JudzHxo}y596DuW#^@MKi zE%1}THsAsRM9@QiQ{UlgsE>$VpACrDSw2HM6C#N{++_JqrYDp%4|l&u3o5{y(wrhm z3Md})TDc6JMVa~@-2hIX{36E5hf6*tq{M*{*GRYgO#rrb!q6Qv&m~t(qQeYwsm|-J z&)y|UV%>;MgaFXXQXm+}I9pN*0PsO%+n_=1l0>(F;q14o^m5=pZuPo{1II*WE3*9G z@wb_`I30!?_oa^X37GeT8U2?y;#(_g!0XCoWFn`Ec>Q*|mX+7CF4@~RU-1PAx1ljq z(Q?LMwT(8d7cNhJHKzuC6wwkar!B1xGI#&C?4&@XghQO~Z5!-4M%&BA33hV|5^V<_ z{^rV#)y^Z{O-l^6#3r(cOK~F}})fRt&;S$D#JIVnFT<13PJ8PZEDMLBi>&a%1`Ujsg`5P$*G5w`|=CMo09}^Q( zO&hwD1^|QsvZ{|y8%Yivabhqx+)cVD4!V_kwFtln>T(Q)An9lTifYn@X~QMMgkW|GAB)h z0~hc`hKJ61H1|Rsr%5@8_g4K1yy$SHXm{1gFcc8m@RBhKGF}@l6gjjahf`5sQq#k( z8jc{StE&}ejab5arf01`_mH?;EbiCB^>IseMDJ`^H-4Oh2T*+xz!u(es_YNnzH?~e z_OiEHyJ)W-Jl}=Di_%q&X`MV^KCq$Tswz1v6Z`5J34Js`@s1upmBsH4KtQK|6Z#xg z!C366lSz!4qM5&H+ZEh%ReAW8IB|P)uBVk5))^C*osIDEi}3hW+28n#IB*{hC((KA z6|FfS>27_kiNaA*e#ht?I;U(!7-9O)_Hb!Ey0O2KF{=x48!$8|e0Js04`zZHFU?q} z5t!RYciWH%aE(_2k?lPNtyqZ(%I<*7CPprFQw-aVv`Gl}KXsVXc#wa~4(~%3ot5{a zodBXZa~8D?Y4T$g8o0{O)z<3xD(x2>f`~$nC5r+018O+aTKPe%4Dt`^CndB1=GSYM3zV(#4j%Uu<>keHya$YGpP zxzs>?VYZIJO-eOb(QMjmDPa{>00*H< z;|}$rsW;g%9>%#wwZ0`sxbuxgM>9hd$)Hh6c+rppLk+xyl7fPaMjCJ&6uOw>#OuMm z0H9r8UgEpv*&+K8qF!u3iB6m^CycWsRMy1nRD_>Dy#Q2@GD)wLL|{yrI%#9{WFNqd zV4G<`@lcz?4RZ|p8ltGC3hC|)ik8o)2nVkty!~KgmAt|4-MyTq3RM0*to(wbg=_d@ zo%VNIsePv~YARBNN zMee|PUj^BEao&U7NV4RePYG(q;?m~_inWct06}ULLUb=G>=klZwxVE+sJh645QQ)& zM6YRr?T3TGbN65pG~Oop*`X@aQ$NmiVJF`W&gM`RO9W>9_32n4Od3&XA-rv%I2QKz zd4dPcdg@VNtI5xyoBY_U3+jZ;L?{Q4hq~a^^a@noSeT;Aog00>vTy&_=={SQ^0WdpDUii}K%iS&J zLKPozf73OctD!7Yk~=Wd>M7WcBifDXEDUu^=K1$fZ|f#GyYLCNR=n=hwMv8#4dv)MUZ=*Ozvz z-L%EAHq?PEM~4aqa|l`2))^E5V!@hU#T)bd413(qd<+CT)PJg$Ejqv*wEw!bthLg+ z_kPh5n7V?hCwooEf*=)0%;VqLXN^i=UrFG;+O&>`dLZ&o%-&6{+ z<0;Yqe*2%J`qn)fC^+*6=SEkHe3gwfo&7p64;-QHU;^x?6UleRE7ATGdI)X- z01+Wz*d)ds@f8P?yJ$^_ErZtaD>h>iC;A#bXnr4~SJNKSNRt#eoxnH`=kuAw2%_Rp zL;nj5Zo2^_k>#3dupmiOg~Pw%-a+8>3vq^*`A@8Qsu?+*Emmlv!GI*8mE71VbF73u+ua80Blef{ z9SmE3EZ4XV#}O-YadBOKPI+{*!>!u>VfD1JG{#~d1gY8gqYbJB9@sNXn|Tqys~pN_ ztiWUq-3HXZ5n|PMGV zQuZ$eW~Y_xEU-8;FVcSb(U>#J&kDU8Ei4k1z&;XZ|3tC8K&X^BG}`WN(fZE#^w4vb zOR;cW{R(yX38)45iaN%!Lu@vh!KTxsg1ZP4iwM*J zCpN*HNv%Uf@ETfG+>_9U@mn+7xAb*sF8A`HTM%jTV2{FKS8k=ie8F5ef7Yo?HDP<7 z&2mW|_}nfY>HJA-;(H^_LCsU# zj<9x$l<4H4#G7g9rje4=EarDasvT(TQjR4D8QKz5SD)f%hnVjSh93X~N3J+!oSttG0&1 za|^mi>#O6)#P2CTlk4+>12f}9R1yM15W19#>)a8^lV|93Yx}<=h+oIML5ldZQKx#B z>iz#lK_)cCVpEawP37h_IBOb_G_wArc0B4= z@YI^mwz|Ryg^nepYdXdfl~x9~C9m?Koh^7%#xkyQG9=!!AHr?MHP6G_$-NnDoe$Cb zD`fIe_C{<=OXL=L=`t$THq9kd!$?>CLj#wQ)g9Da`JgcL;TY~M&sAezMa7u=hiqcT zly9QA4d4B3=YCQA44nnKpm$wc$+gPh>MlBU7wXbpIIWF5MF@Nj-Hk;S7hrVD$luXf zdDqZ;2yb07=RAk{9`x0Y(2&wYA=xBe ztP{I(AQH*9n{`mKIcNWOuua!Se$O}EESzVu=#x43o&a1G5BZpU@<2MRBsJFMM-3?B z)T}It1m+}fH^k${YymD+^3O)TloE8s$PV9d9x=`cdx*mC4B(&RWtv@XEwb0A!tsA) zqC${;v8UrkPF;&8d06M4S6e>1_FzJqEZ&^0%0UEC6G_Hh+5Ux9Em`QH&xFo=;(+m zp&#lm;3K?+`6#i#ML5Nf-m&kEhG&5W!upE)wc~>^TX2z{MTOaQ)WQ^LK@0*=`Wt3& zpulS;5le8AHUR?w3(-0nVBan>mTQrL75e2^cVv{@A)n75GQ(N{-`Cl1K^9HuiJY&D~)Gk-FKhJwn#1Ug@QY>Zh z-Kh9jy8NV-O6%$AcN=SKom(?4P0i7zYW;&xt1CgketSjd&0R^HH*+)Pcmw0Q6LX}j z8Cv)Ntq4gN$4We813dh>G>pYF__+PVAExV@da#BLhWt{I-h2od@<~!y+4B{r+6KnT zM<&JG$Iti6Om4gfGuf0ZkBdsLyTfk%sjjOOm#M2PLVovwGqlrl%`rt)z-$ub{u6Vd zq4ms&qsew;m)Dye;YE7m8{z)Q_8etb43tAi+YL;+%{-N$U^bHe5H?Io+tXeYE-wso z=gVp3gSL@Z@fUpdg86*CB~FgI5*3z$6}m@8Rdp{zy()N<%t=ES*(Z? zyLD&4;up#Yb-`G4|7CpT7iSAQ#gJy%zEz8hVJpu%EnWjm2*w+xf1FI52H`+V$D2xq zVs|9>X(aE5(B1uI>9^$@!<+57gt@;dQ!+%7t@9T%4~x+_C+p$(i% z@Hp!jHPXa2PGHe0?5Z)n$I8E#q3P-Tg9Q6C{4PR6A{a70biLSCFE#(- z291K$sumFzq$L5Q{aio-{3lSk&-AdyTdRGRc7R89h8%mYLBgl9v9K_Y9UvVojcfE&N?qgQl z)TNvMZ@FdyV>HkswbR3Z1frWJjvDa3ZgA~fzGnYMC;>&FoOoz8PLLHt2GDHPUgI^5k2am%x9 zIOFh~5)|@u5XE=oAomOfpagx8^T(}mbR*OmSv-!uhddaPGTsNM)MN}2XP6Ei_8>FMF^IOuN;L(dR*NrjN&x^ zbpYH1-adtz<9zQ1bk!kNaa?7%X~RzNg9nhrnTkp4k$V6O2xNE6S*SuHqeDZ6Uky*M ztM+T{QjH|MS5?8RoVY!wknm_s-=Ui;jgjW>LSL^NyuLMbdh&{%&0UZKRvw+=nvd{YOw$U?EDth^qTbHRgEB`%+J^uA5 zCHCv=7iX32NMVGqlrQEi9D6QMwR{Ejdh|tV)b32?a#1P7kcVJB8`7I63lZJ}K)CK~ z>#oi8hV6pZY&t7Z`E)5>87QEV32&b$Xo^!}Gjq>UHN48tnc1>gA|Ew>VRI*`-rgT- z(*9cu2O;D3wr+y0D34c<_hAY3hTUyAwO2wi`cF@%s^Go968;!U-x zIG!IF&8T`O?zvMg#F;xSE-heSCKskyy8ok?E1-iMZEzLBKY(J;1K0>mw@4h#k~dgF zqizSI2cRZ!O*Pj;g2y>k%lp1Cbi{=Hrr{^~#3xEX&;IY7l<9eTB=m*%Mhj9z%mRc0y$3Soez?= zPkkOQV}eAntlaB-p@g#2(`j>GH;(R^eRtu1y}i9{Dk>_bB8)sNfUf%e6)nh+Gb4ln zz-wmVSM294^X)`V_~j9FV;?cNMlk>Mj~asdjHAuC_}`1%OXJj+uOYU&Es`mWdQ@m> zf3(bems3Hac zJe%US<{BxW&5%KB_k&J&0$nCq5abQ8b!S-Dd%GJ8PKZ{{0u8cyPK()gw+;e zsC^@lq&rpS7Ab=3{o;JU5WJk=d{9k~cdTY4i2(zpbxyM~-|zS}hEw#`b#iqMSe9wy zNM*1Cr!@lgW15y2c4%8JZ#2gC2 z*!|cVsYp5<^Hez;2OfPeqIzFd+_Rv&PleEemVP3OK-WFddp3|44@9y=uNAI?hbFwz zWc+639>MNIyJiH?i_Lprwas=S3arZ}S&*^%#(b!Uml zdKb(8RUx)&m5aFHvF#`Aez~z49L+`Mj82GOEfzLTB>^;Vo!)!Sl9Qf{K=)d`UeSeg z2g4-n4sL~Tq9i-RL58rfD7F~I{81CHZ1_p5+v1r$vp+g}TBtT}KyE0cvmWckWn=v# z{;L`f(sP%G^f)*rd6`+S7QiC#!c1>e0$>=;`Rj=ri9P%ycKl=hwYa<=z;yeTlAF$) z>$Pn~F&gYYnT@ z%->Zn+tOQW^nhHIIf>+rYwJ&UQ#hjK@xgn91sg);<7rRR^q2G@_Sg9 zv{T3d?BT?&oUe8bT70n*(MAhzL-+=T})b9ZCJD z(6!xj4f}yeWMO-acIn1Xd6iJSS8Efj<{H=Z2-TYy+CY1+CvMjTRR>9=m&H*LdqfZ~ zjTRwFB41Xh?M<&=1iyvLzMMoNY@2OHQ+8ZGa7x8Aavv8Lte1Z0VvjFOzb|^QDmyx>R*TUfv|B*)Dp&%rwtRV=q ziwg+Gw6Iiql9H$8avN(U)x-zALprx#Hm#Ks^Z1FxE)par@KTB|C4PBU=HmC2yMoQF z&CK|OxLKl$echHoSssf)nhl=|{*b<+LK71NVkRb|(kQ~wGBz7$5ys*hfjn4HhzYv8 z1S){{7JdrFUgV!UpNCkZb`{`{y_Em*#Q)4(UwaBTj_tufk=K>&m;_V;WYZ6W>WObW z$xSD(65|h{#hGX>@ALi|q9lorJ1MP~z#Hk`;rdDyHMdA7WG81}Z9y}WVw>QY-o~!o zJJwtehEcd{Pb$kzY+m;8TpBPJ?>+3!{T zSC!Z+K?5i@lc~xD`9wrLKc$Bo{hI6PB7iQ9wBW*>#yMvxyQr9e(K`{^f*Eq${-IP$ zqFno7MH4J~0xF(zEDY~ z?NhYg#RapQvG2SYn&tN(PQ$2<0MCT!M65EgW`oAm{fRTZ%8-9`H^qOo@cG*F*-_up z!i_)3hhn-TpSZA~@f;rT1pR(y{MlY1ZDm!8G|iHFFIZkP;<_rlW3;Euruu?IORx@W zjE+83`)dgLptOhJDeB8U{MV@M^XBy+hySvE1>cEGaTci#Tlbqp9Ct=tli_>;R*JM%t-!lXcbvh zr4ojQ09upPNsRkKLBYAK3I%Z=vxXVjz#!~;K!)U(;(+LAB?_q-AwR$vTbXFr03_xH z;>}Q|MZ2;w`l#$6EQ(;tZ-+SVS!CO@~j zYLwUAL}pjLhQ~K5n-6cgHJsP(8gn#P2d<@Ze7i zWs`hnFZs7PFGN*XA!K^PrZASf&w~i}QVH3c#_8j4c6`D|6Y1nXma0&pF=oX`#fhfw zv?k4cdbxF2zW8BGGp!_GnpqUUSee+%Lbo};j1@HUYl71U?~_3(Ww{KJv_BIJyXaDs zP3!g@lZ|d?OTRnZFW~(CHRjq0JMM`1@DSugSr|!hWd_PgXQBoQq%f0r2YCX2nWUc} zozHAW#6)z)f*T(P7SYtofN2XgKCr_LIG63kXW8>GGG7L$8VOt%XmDxCp~U;>2`fQ3 zk>vUZa(M7>-a52=X8M}kL0j7asPy}Ui>6R zU%o>!!11BB3Hd_)ZLqzWhJ^@@0}@G6;G&!p5Y)R^TEjJbkAMag$ufaHD4pmT`hC>v%6AvVekjj4_HIJbQTair9+558KP>~MQ21)QU;rfqR# z`pmWgXzB@@+6?6F`GoDv={^FnNXNJf73A2R$N+h-jm8^0rLp9h5X!#xaBTIAa{s&5hT3OBixb)ZdcjwHWCC()Jm@E0{&rScp2!LIZR_=#;hWxN zw0ymy6O@`3d}HYMYNVf9D&h6WLV?Yw!FYIp40Nclrm`Nyvujw`-+%7p>Hf;HAXtQ^ zi-Eb)9`fLKX80WDt+T)}4iM0tWAr@pw2G9@hM%^$WxkobaE*^NdGy0T1Z{(f>=aByzSxOKjlNWVWlBy&#KILrVc?xp?zlVLZ3nZvt}YeD=9HQL*8 zrxmq(^~7HV#w;YGpxWV?_xoG6W7(p8L9RBuo)&T=AQ}St;Z!#Lf3C|d?w&7`mAH6t zvq>ND{cFjO|54k;mD4zVl=yYnWp4iF1dFVVf%jr{&bz2=9=BNF~ zVBJ9Ls}urBWh#Sp5>E$Jftbr7g@UN{GA8CO#K30SyKu%j~gktY3r?|q$8((9aB)LbmuVV%r~ z;Y|!k)USUGu1m}mc3BU_g)maIMPe2qaTyT2vb(PP4GBu+l$#lPuh{jXJhb~3egD@$ zV%ng*Qut@*l6;v{ZN{+Sb0uw=y4O0TwAZPccW4!IS0{}lJj3|hIt+jnVo2hh5XF-d zTtKy)p-U1hA`)s1Fd_mm1Ia|9ku!uB=!=$&vFF{vgV>9ov5Pvn@)4F{iowi^LNV6H z!IiuAg;Oe;h6aS0kmzJ7GNbye2{eHdeKR;QQyyalkoY3i$CXgorMG3|qs!yIa;^uy zpnJ~!K7yX}*^7e{5c3}Fn$KhAJ%V5QYFGMmQA#n>iY#`Xc#Uw!@#2yQtibUT`d1(; z4onmyO!mYW@jCT4^K-XkRNL}}0hk5E;Kj!w&8U?|8(?+Kwg01gQr7&Gs9*LkM`q+>0Wr6}DL9bvsqZO{ARcdQlWxE9 zxJ*ZrOqCJh8>y$C=#Z4Vdxzoe?QIAV-?SS`AX$~Xre<;lQu(6(W<^q_s}5s{YD_&u zGgFXxA@&p8$`7q&b1(ZpHYz*m#+`JC(*)60yRLl2O-&jN4GkD+)F1s6h7{D)@B+O& z+n)O|8&)}9R6gs?cHNsD9*;l{36;pvr~{s0tz=*98_I!N^6u!B;eozX8@MX;$M*$2 zQ|xjN5@CAdkAGu5izf#M!bH!=F-CHF`N*tBSLp=8aU;fg-^eB!)RymB9B;?6Dt4G@Y|yEfZ$3gEf{F2^VZto2u<3v!AT zNGQ5`RRWHk8w1_Ty_a1;KNd#?OG8S1$A+r9N@Q)jN<`QVzKco9G=zC&jXYcWtazs( z`Iw*3N(^V0enbU;iFancc8UHlb{54?k14gmpyW<@|&<9_@CO1TQ zgaBjjd1h$Ps>8*!N#j5eAl{DOk=!!sSNd;sOWo(-;>T8vfpM( z5uBNI$YA#IBj(hsdjd*$_{ST=F#PLIWpKnRiB6TY{XjRqjkZ_w=BMs6l0olJ-2h3+Xq{C~pby ztH)AR_D587p(n^DEuDc&FA@m;D_RmtZ@OTxVt@O& zvFdZ_!las>ccSTwkyu)BQ7aF)+()E0szPB{KD0V--#dQHX!*g7tQkjnsMsXR2^2xU zTiTiK!XZ(N05_!pS9hjyBcEeOxJQNlu=VdbZy4v0_z z;W6FXD{|sc6?`ozA%pDB`EWF!JzT@t+ppZNEV&@4AWg<@!JrCazZlR?g@GWesh;ee zuoP4(U?NdWT`K>j?5z2$4Qd%Cc^6t<1pg^`xSv;sqhwDJ(zu@+6ry zZBu{#!zkyKBvu+tGuU!~*%y)MENrB5%yIAk@&Y(Bxk$-W35N*P1pOX^S9lDBUcACf zY9_>mpP8Da>eLkvt=CSv51NTHf;ZWOnXd2YXO|%0{b#8|%lNggq)EWp&fCR0On0%I zUcI4!P?yClIFQdo&KG(kIh&bwNJOs~wjj0T)mD85lRLz~gkhAx{@|1k0qk)~*65I7 zls8?NLWud`30yN-I3C6gPl5sEMvwINo>~fQ@onb;i(C^wA^8#jJ{RqGSM~Oa^ z4gS48h=Rq;3Szw`6ThbFif1(r63QbDg4P5Ns~p&Q)u@ot@2?&%Hb3gx6K(|0FrUE{ z=lWNq07|UExGM0VCsD|=EIsWnqP$8ZVcdo6AR!PS=_?5a^axOv|M*~ndcrj(Ea+lH z6C;Z3`_%?>5v<}{1$LH5bPh#WuN_AVv)ZZRIR>Fan%<84-ai-MZH18%Vt3&)T}Es| zs)ri{(?3?%4_`NllKKEmfB3ISK?Ig}g6c7!*0P^)DmPPSmC7thUcJvV8+Y&%O zb0`EgHF3zw2Bzkd$`h(Y+C1rLXcYBa`CTPm`9F48>VpwP$t~Y_pM(_Xy|BUk8ph+u zvdn+tUixeGiwO@-oVG?ih*bomv4A>z-J!>@bUC#gfyIhL1>u#a^euPy4dQ#a z@SEuh1QKPEytlpB8|9ub=?8=pANV*mDU3H*xVT#_o`+_Prt@_FqqX~lp@!Cex*x-s zF*v>Q6_dxdYs=dvX8^Sqt`~SfY=E$li^0{so7nPnngfA*xJ9w$NfaD*`XHY0KyHNb z03G3{&@E&jX=LE`ieqEaz;?+F-rzBn@X$n#Bt1D|{WH|#s#c+;Wx_rHID zc4O`AobtmxSIR{s-%pXB|C7Yl#>0(>jxQji;%>L}*}G5m%NgM&nBaO%7Q(N`P#3Mf zt5J4SHdeA)Y3f5h!J3%ydTXufAnRlF!XI4o>_D^P&55hlz1KVT>1g7-wwb_p`KHyT z)o)Dt2Ic3>Tp|2^EwXb6T@d;x;plNz@O8V`o03heiju=Tru3Lc%R;RGguVvPc(h(} z)X&9TG2@v1b^hz`v)3N&MA9W(T_%_>>2b)=GmI@PG|S0nHp?EB1scb zr|t}Gk~_-(KB@z@FFDGCJPeEza}QT9E-l3gx;S|ccUBpK`6{nH>7$gQ$E$TIq3)B9 z*7kym5ku;dRi*^%XCB(70~N8B?_9y2cV=n)~lm@p6yRMVRz!}5*bM( z5f=m&q)J?;98Ad^z(G}} zH*eMS089=?&Khud*DDg%08|I@>fQ|t&b^&F={^v1kN@GX>E%swzUiArnArDyt${#N z0aulxL9>ZIOB7xsrEGCXm`L71&k7CN*0GVQ3F#?RSNgm!_K#aJxSqg^ed88kZd&8j z(BZzeG&S9wAqWYkDZ+I+Hiot=OoP47sLFq4S?7E@`gM7}QZL?dnDL{@5eJzXNoPNQ zdY7DK?(PXuFT~HS_)i4wInE7l9j8`mO-iYUFd`yxW|K$D_-K2g8pklOJ6fit@fnrF z;nX^z#t4pfH*Q^8FpU-|tp=_U{+rFLn%Bkg4=K~XKUSMOcE{P(4PI==j8X~t%kmKp zOGH>lhqyBkL^@Md*etUYNlRF`{}P04!`#l8xEVt-cyoDTITm5k51rb6KeSj(Hu6J= z1Ba3X?HAn^V*dAW?0Kfzdnh-_1RpT@3Pyw5g`Y7M#lkx0?3Sy>0I9md&Fhi^cI}+( z`I8x&8idb#juZs3aoXs`VDk} z!2j3Fwu=hePp+`ATmYi0m@>F-y<|mD4e)lZZ=1((q?B`n7buo6J(;;SV5>8#zlwY= zgA;WkI2vN<;}&%L85>xkEu!T46U-#h$b`T~GVe$Ya*M+7gjdSzFBvAx3+a0}{4w@v zs)Tpufc8z6>(wVOLrsndzN+%V;GrDw_<&AU*9H0(dI)n%r?Yr@4_5@`I#a6&R!$oF zTA}SWWuN}5xmT{X4=+9HNH#0*f}ns*NBzVr{DOQk2IPh~CfSd=9-l_}BDCia zI-DC^CbSmYIiJ$IE|2=!cV5*EJ*SQRgNa65)y)_eQNqpdceUF4F_Ya#IFsOy`7>)= zHXvjnw7TM}+YMAdBdN1A%x3}XtTNip(VW7rPQ1q>M-GPvX{^9a9EKc0#GjQEW< zm(StHnUeTxx`Q$C$-H{>hkV%nwS>~9m*2eL>C*m&=JLS{D^_bY?@;&$Eja7`+CFLH ze$IFz9R_B+!q@Cf@)kPkDNJYgB+pn}-vuLOFYZ1x{qe?r!5`-*9jtBG;EHzk$@sSi zoArjMTd(iSDicOvZg=SW0I595!B2OcwQ_*;DK`1AZVseS>+L!}_v`+U5xJ~ty= zbTaTv(xaK;L4sVRJT9zYW1W5nD8ekL$;g8h=r_8FojvV?0H!FL|8OeA3rbL^g&ot072~tG9geSoOK${=s*bNJO znsckt^0Jn7wu(gD!rtU9=UM&+RR{zdcINd^`pGlD zGYILdINvJ@fI5-U_F;-4z%lPTbd7-T#m<4hFruD%7vfZc3A?Jc3B7@im~7SLwNmGJ zbx-=>JWGj>PCc{>reoh{Zt)AlF4VhO^eA)Td7+8+cf85wJP=8GjafQB`HtGsWM8T; zca$`?p30=j})8Fbl>K$hPB>gKp8xsT^oba_5RE#u(y)^)~=)HMF~O zfK5iXHYX%1^zYtxZyEx`W-;V_(wxn{v@{ONJku2Bs&%drWQ^R*q~d(c?R5{{H!&(Nhuyj6I_C@P||?mhBvmh#Ob6^wGJ`_MjN#Qr@egAix`;z=v_J+UN&_c z=V}UjMlkRSse2Y;+&#Wxd`McfroadIw1B)eeRsce>bZuqB-_?2auuD1eSxPXzE9MR zBpH2{jkAXcBatxnYORZ|S8V5J;!jvPa{>Ce{nDj9;m~|Q7(~RfVcp0y*5g$d`JQl_Z!blXNc))f^~I6Lqi}kOEWk$l8P6c zV%|c&@k*q_<3k7geJaaBq)G4+PNM5BVrW#pI#O$g%^=lt&-!O=`?$Z4|J7Z?bHQX2 zmsx0e9>6HAy_wJEtRyyurc!;@WI?3}Z{;;nQ|Q)>y5rl|PQ!B@N)#lZG`;8%aC}*0 z_vk==w*~i3+lyW1Kw21TrFWGZiPrq_K=jJZqIt5l4g*U5^UjT#F$$C2M!E!6Rftxn zxFz4uxmo9q;qX3@v@=*~W%%E_v6N$^c_c1>`@w|RLg=GCvh2+mA`(^4NVb-*jGN+u zEKtX@`@6rxB&s!vDOgM{eMKNWK3mjekoKp3o9KF5WA%yWAzv9wUThY*%`D@J9+K!e zDWoSXJ&Sa3du?a#mq11DEaitj)_Z4wY+I)zb43w*k5*58L<%HH=hNO?CPzt9oq-3z z2IBH{mLHqyG!{$p5+~VsXF8<>WuX8&ta(sg3E{X6*@oL=I$vI#ynJ11_=AW;4!00D4CT))e2F@S-2B?;LBY!E{0PD05VkYM+;i_7DIWXc>0>IWHej$-o@?=_i}I7(j?zwwOADaa6JW2@xlg{kQFK>JMnx%yW4CtOG z0K@{BvNyP&bmOzv z-4~U)l}9Phw1RzheIj+X3Y&`AZE`tHeNZF$2Ozj^iDC-7>%*x0dkx!}Mbl_nou}Q> zY^!i{&k%e6??2XFNDV;`Nvsyu8t!G@#~c*5;maR+o$XZdF>PFA-5_hC_92z{u}S%bJgAP5gJ@X-D9(adwGagFhQbyha$Wyy7R_2L<6;qYv0fA z%Za=VPCgMX(<%kynoO%O4BO@4nkwR)_4(%n)mz_FIvyfY;*q8pN&!~|J+JTrmd387 zfyLwXO3D`SevAt{O3J97P#{{}k)6zBNOvq}#hIQSF1KLb6+ks<d(v_m}*5{j>rSN1KQ;7YBs) zj0)-c#|229xf?^k`^EXD^Ks*Jx!boJi5%e_=|YYr-GN2Xnizj9`R)=$ILr<2TK-SB z~-W2f7W>G*M5Mbb+NXK zYtb}0ulp|F)1B%;q@$_VcN9r^XtNqnF0WeO>5ub(_e1N*TAyZ`{|i}taYnotN3J!Q zybE7#0|YlbWD zBiXbELbM6P!^#>lr06^@CPMlJ%rWDLg>mhr;|`En84+GN5Je{gPE`3+D?hNxl*OYE zy?tTURyQgV@*jnm7P`H~buH8^NS{lIFHqcCVk3p$dHmR&Nk6oB!Fyw6kyCb5Bu>n1 zZ9dH&m+hmT#OP(RUxbMh`Zm23v_8fdYeM15G1U9Fwylrs>=Ji3FOYxM_Dt|s=)5zF zFe}n{wWwq~fv?8Jih^f@14Wr3d{WN-fC)!GErePPJyBGgML~G*2756P)mAJ=t5m^w z5*oUxQLp#>3KpS++3)*e0=cO@qudM(ub~lBZRM#IVs%YUX#8eG;f?M_`&10InIbDA z)ne3H236U6%5tllD^|&by-q^uz@xqricyf8AJ5H)+k@Az2OdJggCm{DACmAQH7!mX z5G;!MJxV%_o7HI&xh3@WoDiibWwT||5`m_JJ0^u7o>*Atpa5Mf8p%h3SHXm)Rm%N< zZ=jA~rGmOij$S9TXV}X?wdD{rSB9D!Z@th1gVVV$to)xQIcBg^Mc2eF?CRcc>5CmC zLQnEU*CPNGZ;gn6A1&+CKFj2T^uu2e4>R#3tshM&@ z1jF8*WCz%t#T%0gBsUO21M;Qv1XX5-I;!W`984uyo9XBst{P00X;pef0=~iUsGd8C z(}ogh8&ggm_$}xLD8byQo|4T!Y|91%c_Ep4HbQ>D^x(}HW1j#`@Pc|8=&&JqLPQ7x zEM7pYice9SF-d-jU%kVq0_Y+yzhjv)gl(Z5s8!n4-6`{v2_G}y7t$5ZRJIWO zU^|x~Y>cGWzBl0i2#E#kf&Ce5hsjAUyoC0HnxxqBmTc^iLH(2mMAEw!w!m}AlpLd+ z%usb+z3kAos)0$!?)uz?3$M8L2qqC7aRilE$K$qGuEf`wSvaM$%83XSD579dKmXrVyX5UM2;Xi?P!=zJP)G>u$ z){aFc{qkzV8a+5R_aRH9^QM|Q`1UR@`QBgzp(_^(nzZJNR`(MCc#-%LX>x^%ik zv5ZpteHTwkYC-8bwaC_=DqqfyJ`tN3Ldz=Ciu@6xU1yVx>n^ zC1j?+eS8i4u-}1m(Z540O}<60As|P;390cT+92o(CE~z(x$pMl>+zsz@j2Df8KY`w zHEh5B9ruXLcQ!K%R`3n^Zm`Lmc%nJz`N&v z`ttg$wtZdse!)o)k{St`GflKjK!t<*DF=}?iKr(xob!{rT=B^4U!Zij^Qd>m)zKKK z$vJ@$^|;$nb-Jfu!TLL5N? z!pvOa47rpu*LM!NfJR%SA?~aCA$s7EK&Y5vVly&tS)*d7RnteHAL3Q`#QM-;j;S5Y zAnF(DrtMIISJWV-nkHN*Ac*xmodKV7*yVBx#0rL~r7!7tc23?ESp9q3T_kC&pM&0I zy&hX~adL7zi?@mo+ZB^R^TZTmG8ts~y(YWIX)J&j-u!*%-m&hXfS^=>tdX(trnd`w zZ`4J56p|AA&k`)Nc&%)3w%KXB)3Ox~+gWm5s|Ao8=y-J}3iRmU@#Op2^=x_g)%+w^ z&HOK%xeKTBi!ZR1jsoGcnY&mg!&UJ55dUN&+l?HB=oTv|jNK&nfvbg|fjgvxY?9<> z?kerO)|luix!|Eu^<-V7qv@8g8&y$+ey;Z1ZfsMymgn@$J};o9gg(&NuML8VJo<~j zIno?Li8WrUL#}|ko}gv=V6McxyXW5CldqcPUef_#%~0BmdI!EC8C?YS1{~88)p4xL zj>X`oI-jT{d7{uC?gFhjzWTbf_!tqizOq@EOK3#PrsV4!8oj6 z6W{K>d5hvql0wSFE!--c3e?Y0a=vO-v}YTPt6+no%o~ZpG!GynI(Ex)%J?6y>;D3U zmZ*>nTVo6-4CQ6)DZt;WzCDqTgghyQ&V<#;H=piWT0YnBS1*nabp`(oUX}AeQcZ0UT4W?-Ep!c9dyiB5~Djp1e{x+tg<0q7~XOy;%C8 z64WQVo^|&9%G~3!82KM+?=sQ_OaJ&aXKoBz6E(QMUk0B`Se9-Go`|&S!wqXPa+j9n z@1ptU#d7H!hg=4Z0k_#ct3d5Q1>*oCrNvB*<0vh{;Qi_vo?()78pIIhnHNZm91Dv% zLa!fI<<8IT1t6%cF%@!?I+~D5_gblJC-C)T5P=L;&UuApWSe|YDSE8Ue z7Z|OEHe>;SzeAB**hWvM>Hqe7mgkM9LKZ7(yCBrys(d<9bbKDKi zcHdZR$A*MJEE&M&Mav~rekVgygxiaSb1T#XSM{d3lhq7+t+vl70cP03#gY}C zL&Zd+;4leX4szlX40OoSs6EaJ;-kY{hB#O=wd;YN{lV3|3~>HcFs{0|Ndw_jAie@4 zCX&vL-CIkrC8kEw5UeT>v`GLZKn4(%br!&nN=*~Um`$eB!Ol?>?UJMP(}Cb8(KjOJ_TM<61^ssuA*L2_Ppl*?~efW&vGPMO{r;OKT6 zo|tE(7svHhMq(ENk%41J*onABtka08EYqZa>ovNRL9H)r51hk-{YK1AI&%OThM)Wu zikXJ!;U65j^nTCUN#zdC8_pZ8)San6jGSW^yel*=-RtYj_eF~7&OnNiY3_KSKTq@e z@AQvpapdnp#2D!ql$=-aHU-zg{-Cz*x^6i54@6IltEX_@HXAy2JPwQ( z08iPgMDdnl;-U=~Yy^aJcYe9a@9FWOoKdH@d5+V`)eDr*7 z1f}r%t#TuiQTF6~T&ujf;&IwGfI}R^L91B@c`=%k}C9w0Sk8H6Gz@ZTVuX$%lkDg+Y|Z2aWs%~D{@@u=dJvC z-}1E17a6*zVtjvI4&Lt5(M`U5eChv0g76$3X#DWNJvBUI|0X+Pb0iz&_owyJfLfB< zzF!xNh;bX$+!CVW6U7rEjj!-}pik{N{cBwQQsr%750W>oHcuz+030U9>jNh_KVv%f z0@Gf!t)<>X0ti9}`dTY6L6~_UP7$iHDY*KS@g=v`tHpAGlcZ9>xU%B#fL~OKsUw(8lBxym<^?}W1xC?<%Z%o-C91h6K>(~cXUqE$KbN~sDvo@ z#>mdVBi^R8`E?EHrtH_~H*NCxnR<@g68Z_$3mYGVk`9Y_7dLeHz-3wSHenig&Jers z_@bf+LVFtUTd=PC|J+)#M9Ri>B<1Wny5G;E#2)dVgD$A6RcrHJcPu#hBhY>5J~B2L z-|Orq=QsP*GAVf2<}}j~a0b-naE;xwI)HyTen_O(;_9^~fp8!Q;A-6oqqqGp=k?pg zWu(b&wZZ7~Wl7K5yOWLP&!!#R#7{b6gmog8zCC*s_o%h-C-;8BKUzGSG&SCy^O?idUpil(xWlrhc`?jnP1c&Svpo;7=^!3zu=(?5t;ixfxGcbhBW z|Cf(sMHCGFiH-kdq--Cd7?85G|A6t>=V5=fTXAyjnXMM_&=4GKe6N&|Dm@)e6gHaG zPV|L2-z)M0U_Dp`JNESTsNg?8;8H!`^{&k=NDx=X<~0XRkf-4Xuc3tq)uJ&wAIU~q zoohSXm&Dbsr|C~3zWW&&YDXL(e!4(U7JZ$a{VU!Nx$ze2yc#l0WFGs#_rqteflTF% z_8sZ&6F#k1(E$*Ylk20fkYC_$og|+ZgRqZyS^NM)y@$*M^hZH-vp0OsS!3Ff%=kCq ziT0`afm5D4$nh*(|8aS|OIanQ1CsN2;MF05b4l)<2?f!q(G98G5EqU<6ZRbgy7fQp z!SDJ^m#=&wW}s~YxY<^@;lti6BJ(8VaL z<}qNjCqGT>xIABkdfcXDCS%N%>(p*w{b^-$W)q+>Y8^JjR$9#S1)c}v%Xn@st}mtf zUs--uY$%-tC14Q#jH$^n_8`fmsZj4Ny$w=8(Sb}u5*b-mshV3Wt?}yy+GRZH6mZhg zL94s3)cYk}-(sAjjqq*%pg~lBUUI#m9Vls}2491NVrpqcpRC&qRM9KQ@q=5*cpS$0 zZILLZiZME<_1~pvOQ1l+(cf$;dJ)t&Y*BOe8`2MVjCHEmv5Q-TU{K#au;z~#*<(g{ z|5sX}1%65V7sYxCy`u4ev-%K3u5p(Z2xYu=dlGK{T*BS`J1_8(3E*Swn@hz(XK^ch z;60iC+s}XimlANv5PbE6HNg>!CYQ!He~H;FF!G_?W$KB*y=tG27Y((hPoyi25KgBk zpyj!gs1FaGIan3$o@OgBhiMJb0=x+x>$ulo0j`@F{=HLO95fC=WJ%^hSzp&(Xf$q?jWmNh5gQbKT;>Rj%%t=i94{! z3xj=PxQ$&dCLdtD=+Lkb%$fXVF1)>j;57@p6XT2NA{g%$vlYpx5V7kP>qj>8X#-?mV~0z@SreF^=QY-ue!#s? z;$LWpVAIKFO5zA)@tTmJJl$N~BL8@cSXUcFb1V9MW*0t0CH{ zEAzeX3<^5m&inD%yTp0WfIT|s<}u-N#`kFtnNx2_>Rv;HXpdQwd?OSFaoFUhANlLc zbP!VAivBnB4D`L!dKVSm2=yIs;VME?NLxck@LIR@9OKor_FC!*T+9k0*r8r+>|W<~4@MHs4o`yV zr<{O|lEF^yk0KB_FME117bn0htrli74cPF2@%m*!^|!i7Gk~!3>PVeda6vJy14fG0 z;X=KbYrH6?J1msQ6ySWb)U=DBxFz3wrv?6$=Y~r~X+pv@P&n!epvVXML<3Gh1gOIEw-Gm(VNSMZYWug;(!FCEPrd$3j#c{B*aa*sQ6*mqa7ugYu6=UCXNgi z;s-nSe7PoNe@K@Lblv`HJqie?m`kTss-EMqk9n&n&B=mTo-^)wdwW(>H3DOe7H%vn z8|C)>cx&-WQIu6wOtklXUF9R6%5&Xtr|#^r&dkiL!OENCABRDpkVF_wTp||=|Ksid z2EQ|fQt9xaB68X4rCYyS+SI4V;e}%F!)n=LdV@7GM$tX1{a&^l2Zol9O6oSAwdVI1 z-Km9*4kKi#Y6Gi+UUPKL1?H`nUDTid&gmNrLmo4Do7dwr&nzAAWuDcm9PslHsJy97|Y|=ewJ@sV9{&)jn_j?HSEQk z9&46PROps$j=Czg*M6JO30Y(#VaQvr-M$>t*pHi@_11EU;vRZHu09I@GbFY`Mw@XK zZLBYtht%GOQ*1NQedwj(e-X3S7&U&(>|Nm%aFbYYJoSQ`2$K2SlHS`4ZA(S2N%Fq3 zt*O+V} z^W*X3d*Q@y344Z38V}yrs5Hd)S6&SJo4$s#TI2B+*2J-+mrZ;jG&Y| zB4%O9-rVf07?q7FEyp2iSB+uszRP~Kn5Y#lF@E>&QQM|S<*26%E;tlIB$BfEy0E9p>a_6NE(lwQ00CONxUh&Z zMEv*IsXUM~K3=ZDZ=+xaud_;B1e$xUMxr_;8ur-ooN}>-#-E54s%#>Y2}!4btk0(x z%zZ^2j#35Mk`<@)q2LYHYT;A#t4G9tkh`$>?H7%5dk@GU4-|-j<-_4+_J(t(Y3ntNuK-{|wguOJrz#z}SQsp2-P= zMhc2D6#g=ugjsD+!V&cH#sv(1N1n$u2LRMVQRu@O|BAJefZW0Sgv9`aXy!eeJN}`V z8O0m`#v{tHEH>>7-Wt4bZn@hN6pb=q0xUgwiYc#}t3_h4;0>C4lI!TM?bl&Dy-><- zKmBS{E%Y{>zGhb7bD0(m_1aH`?&Oa*9?=2B^oV#{J}r&<16{UPH8bXoO3NL*axKXn z=JcSHZy1EjQkbtqcrSEKS*;{6=Yr2Ic|URvI3e3|ABn*Q*cHrVT<4jAS4jD|wl!2T z%4{NCTZ=4HX@9R}C3CFpq5Zh`hoL<_#<6$(;7lPJ`=>-&}WD=e$jfTWzi2OB}OyX}S38E%p(C zD<>ysy{;vY#Y&_%0bP6)-|(M8DFP-7{Uq6Bzgd;Z2dOMCde$Au&IXOnDgA*M3kMxd zwo{mr0P@PcCTS)geC|VP7ng&Vw>YKzcE4KPW{@=bjqjfdk-S9;G&C>LaaGSq1sbe-Z{4-Bh8f$6Mq4zN}kq~xrl39Q)FBhg^s?v z_ip5agsW2j?`WqB$>3@`;Zo#OkMG+;yjtGlJX4Q?>k-h|;$FP0;HUrcK;MFa(bmLm z#9P=M)HBScCw>+#k?F*{6B(KkTk@4Gy(nBq0)Y7~bkz@Y?4hwMX5Tx}^M~tOdn-)D ziqR}5INSIVyVk>mPHLU1)iZjRZyceX0+B;$DUW82KlyBJF294zi1pnoz@g$u-!3$g ze~sQ%kMe|q#^jsKtBq(zDDSxB?3oX>YeY$QM}w%7#Q;zMTh%O)WeFgqu};-?s`wX+ zs_lMTP@G(V=GGG!ni3Vbz;ha!0G*8F7O!BWMIk%DgZv9>z}eiDZy+<3tSW+He?-## z&v&q^!ZOJK13{=iG80WQ>1S%jctE51F1T0vs8kdXZZ49W0U~IwH2L|)zw{Vvoj62W z%Nh|qCiu3*6h>qN-})#4aH5E7|A|Og7Kfw4*@!E-J^_+nkhmY}@MIDqEe@j2&T_VS=#jE9_ptO(~M`RRDAmF58xbazcpLiZC`XsN67B}g;S=#Y3Soy9g?a80de zx3{~$=C7jrGiR`{=HunIwCH?!iZ_3Ojv>0=ZiPSVo2;gY@b`vDOnOB)h>t!;RAGyv z-*B$^{~(iYL|W^Nf`TzESs|P3qj~DFG$HeQGWGgvA^ND0ffU>aoQlvXeujCOEEy76 zxlrKZY2YEjQiN>zGkqyX7+a810c7iqVbVA@LcmL7YEXWE+IHZQJ{}rbH~QNNi-3nT znDey7PumOP%82HJ%e$S6Nu&Iy(>+1x(=FLdsRD^dblhH!bF#xT4R| z7jg5uMH-uMre>?$(q^7ouKM_BE;pHPW1l0~%chM1jHG~Oh9mF&sNS-W{v_d_iXTY~xvW|%%F_XB{=--xF zmdU2DAXzE=t;GfBsfGbsHh*@N`xa8;?VBBhht4&Y)v9>SG9@OaPsW@5oPJcOagh^! z50;4IaIL*C?fTH^elPJyCdIy0wu|srwb(r~af}|#B~6w(ReCT(k};EQ@#rg@q&1!Y``tPG4O!AnC!%U3aQVARS& zs-!;$MlE|{?sxmrCIXXGgo1=O|7MoVPcgqquCU80{4mHCgk%wmTOZl^g}bgm|IOJ| z7y2x>_TEaf!xrYDN6EvME9)QVpak?l~*PNoe{9!mvqf$ z&S_qEZ$vN0XFH1Sc;nM)e*h$Vs_De+Un2mis~iI4dnD*YEAfF0##QHGxN&{-a&yYJ zUG2g9qPV^U&=p0nv!jC^4jXM!hEfBp1#n;R_Wbla6)W5BL=vf400^`uW>V-nf(Yo; zN2GTjND18vcKpZalxJ9bi`?Uj(Jy&)3;|T~-8Z68dF88s%j)$$+9?Iy9UNfti@{uL zeA}8BQ}@E{SnNQ=GQu{U$|yRpfE!9VjDV5DiI{^ZGQLw3OUz+|AT@AY@dUeBDTj!I zcu#S15fhW7N6bM(1fWY>(sp#{)5?Z#L~_-5llBD|nA)!w0d= z$>lKIckm#Gh0(8%E8rYKaM0-+sG?hT9ywP+w`elW0|(#N=3qHGH_38t{`N{s+PK4WJhXfFI? zIEZ=6ELb2S(yWHxzB}%CO%@Pk+FD z;3gGcDBt(2E&8|*$TFzHF~rg7DYlKs7xi%=!lI%&qjGKhTkpZs0BOl%&n^@dVgmb$ zc7V+TOwkKDg15H1{Hm*jxkp9iOB&mM?wlHndgm`7sTb+?*}Z=$wts3Upe%%}Q>)A4 z`Oy~hi4yTOVBWzDJJu(q`_6fZH|KYqTzwVPCuxCw%mL%?F^lpCWjcN*ICzbx-YTUc zl&;aS+Kj6&M4)DRhv*Z z3)4mYKI@1Q%<~2)*wcTBqsF5u<%hO{R~Qkjs=VJoZK-u7BD2ZZ zf4P~#alA7OUKb0TwA`6ezhdogk%U)D>d@5`I!LNk(qUg|66VAROi6&{mcCgu=U&Ew zvklzuo@GCNe#}D=TXyvHKS;L1Dzu8ef>6rLseq2B`(_pfT!BV;engb0XA;OPia`kc!+(?7PoT3A*4W7`=% zXwSQ(m{1oQ~gToW~C?WA*kz^kZ^KvX*j1dqrR+ zP%-0f?KXU?tJMq{c(?4giK2mT6<*)1?Q6Q957M(_D1b%+??@+q?X3mE$C$hIeV_M} zU9x1mA3HC$8z(6~s|hrv1P40~@b=nc{nN5c(Zr2f|HL6wG7lC?GMPGE!(nloTJ0di`O z5~;ltQ1k@jL4E5LK*$Tcp0!i;WEmlbeKx-TtMCKEze~f>dXaJ?8`k~GHG*M)U6mdC zg^R2+9Xp8We(iEIn57#8XgxPA6?Nt_3X79+q3VvjL+zL9Pz*k@}BhXd!N!LDB8?9*O9y<7;F0$m#-#=ARR z1uH5ti;^%z_)X3f1c3xdUboy=B{%wSfMN!Ke{A<&{nF@({Tdv+G}4|morj_{dua$} z!Wn0w;{dx+f?&3{3LTM~Wb4Vs{dKn7AbkypgB$;xKn~W1?}WS20_ANE-t~@d>n$?S zrQaY$mmZ=ECs*63V^3=+^(NFL^+f7MucTT9e7~$}!YID+(>;B>^Jga>^!SgruT`?E zsAw;Atz@u@lX}OBaQ5}rB}#3q&)<(DtlNfAHpDB_Nj;cAeSU1aYGM7fP%AebOh`2V({w%1EXG<2nrSZ zo5}#`N|>)!3LabxtK{@wTx-Q2x54QX)#{IbV-3}G$WE~;TMRv z_TFC~zb(=!o+Ni~K++DwDhOmeb(mAk1;kC~k@@+`4ciuFx@4y`uqaQzM8kbm4cvry zGA(1Bfb&E(>q1N{8!wE;w#s;neNsG(L&h~+@i9OZENy-Nt?;}nNZOZ_^zm->kyl~C zd#fGhxiN4QFrsVUvla||R@z6>XVmxmDJF7ls&OHT*&JD#rbYH2?{CQf$uaUTWd|m~ z2;<>ZC?NI75(a1{0Qk9uQo<1gY6WGnN&i8tg-Y+LXwRdqbi{0WK0Aw z9$Wn>dQ+`e)qXqoMC9T${Uq@wtRcec01<}OS~ zk^Ifh)Vp9$cRbeR8k0bulX=?8sw$)f@v*D!L-W9nj~U{TMtdJbIpp_O8@hj>4=a`- ze?0gU_+Aws{+9`1Sr`BgdqGjr=vu22rLgd32WP$z?D+Ftv2dk8x7R+s(F`3O9f!wI zzx#kSMQ6dEH*hkH#6-yT`ch$Jvv8End^QtUoD>Knuj>Qm6XMQ;e9P36!q(5*HRy+> z?evHY_UA7K?60(9TxMD&f*63HfI+cZzs9k``O=8&$1HY;up7=X+T4R6=fA`my+rWJ z-5b13<{ADOs@z+ohJ44 zpnS7+ZaZr-F>wvrYx@Ir5HX-qaI{=O4XmTD=JR_U2ojdQV}fVy7;F(XM`-UmO?H+x z5I`QxZ=y)F{~gYaBL^)0jx&r6aZ7&FodT|vNO1nYXl2~tVaY?g<93j0ArIyFE z-NqL3Ax+K`3rPGcW-uMKJL^=2>Wp};vJuGiuTt_pbH=)cu3}YL`~y72Q{yWcU07Z` zc4zaqK>eHVB;q^w82Mb7W7+t;f^NNAWQX_u>b~{8Dyd4S$mJ4JHT~(nKH9K7ZQlYZe?Az3hyPz7 z3VV_ggqbpv=vt^w@RMwRF-j#1J*`8haKU@SY}Kk!V1BZDN()O=UQGmg<4lQO%|6T1 z=(FjqW%yVuMOx??e2}P9$}i8SZZ}U@OTX)U5;suKOXu(_j9ZXZjSz?>8`xodTE1sz zd4RXL%y7l3+O;ES3OydY3`ed4VDmr*^j*bO@q*gPeGQhbsZYR^xujhsZYd~_25C10 zhN5GZAbcG8`t*4I{dL^dq$)ZKGE?MOhSdc`{P1HY$TsIGmfpt16`>*23jBT~2W}j@ zMdwpD#aXO)$p&WK0SDNNe-%1#*9h30S(8SjLG`b*P~6k` z4J$hI*dnIEIv3vm`9CSj|3BNZrLShJ0ruHik)hf21(~G$U(W5h3YkVf#grxI>{VQ) zl_l%XUS5gE_I+Qdby^*^yoB4lmXP*DChF}+x2r!kwv2YnrKjO5aVp=ZkY!e=t5p@+ zogi6qLvgVyI6Kd+vi&HFzUE>tePcG*CvN^pUnGxxx^bVtLY7}Uu~~X)*DLx{Wp$%Y zQ=|G0FWs$?ws(>&pQ=8gHNyJq(S1`yWGqoK90t>}eP7)LhEkN~vpOc)L8lw-1#qaLKGPqre1ZI$#RZOnbr) z$P`bT9tb%+#hrpQ&5>pq1FDne6ofJ%2e&yGQDMQhL;}(2b zfQeUwa*1#skP+oQ4-Ck<)P0kpx_!WJ8x?6u0`bGohDv#qdK`wC1z&yW)}e{TQc=gB z;Z2@Dy+`X6vXcW%%Gn2ro%cTB6C|gR4VBW#tzBEP5bOiZXaA?pZ`=@01df)rklwCq zu33_-r!pn*zybH{2TGSJ%%A6xSXHtO$Zvo}&te`YX3z(w6P8avK`ryMv%V@XE(vUw zw-|u>WvU~mqdiD3itdL`#$y7cFx)Oq0!lpQz!@Yd_MzkD+ZY&9hlV?S?jw5Pse-o% zWCYi28MNgLv0lCtW<_#@d^dzg2h0_G$2tH*j3nZw3~la^ie|R`Y5iy_xUBPTmg^$t zYwZ@$M-9hfjUgt^PBRuph`CIJvFi}f30P{BFs7b@!r|Fg(t!(-lv)sOfgW?;Nv%gU zoX(->Z7cI(cOrZ%1+k-Ogrp&pSF_9b{Q^TK=f%hF2}Hy!M$4X2QO~EqN_>*v*q1Lb z`@}1CyGndIcd(afQ{s?0*#ROBg3~NS?k`@>xV?N}GuSX3zX&gUrbo@07!hf%y2{?$ z|0;;6Y|%K7la-DA>V(3*@mdtFclwBwLWm;dr$R(TO#ANTCujE&g0$jv7+41vb`8Ox zD8=HA@b`}WlGXQCsB|?@Na*SL9PJ`FPPf;seLDpI?yEZ0Y)~2E()aV#g$~Di8{TNT ztgH+wz$hu_Z^X2M$_B?3YpzH<=KLC4gi?RVej|R>LNXRJA$H`^dwx@+)^Fm?O2LPJ zW_XzW|JqI5`3YvhjvT&%r9uo;nacL{`5!xG4zV55{qjb1RZg*Mdhx^X1k3^24}HgakGcwG8;zfw-8q_i|m8ugSy4D}VdZoM`dk}qz~ z95VD)C`&I(T=@BbI&Aj#a~3q5txyz>LM(MASs~(u5(}L!>P5R&AMj;cj{~kYt{O@8 zX?d&o@~|_^Ft&7Irtq?n%wF~KUEjD}!4A+vO66i1L3d??sF9BJ*js>zZk?;wS+TSR zrQyCyj?X$vq$^d7$$c+ecB?#-=gs8sUuZeS0%N(B{7lbC4;QxSV)ktt*n#~=&NO$A z>6rdd7RitKyTujDcMojdCB^R%?@hmb%k3F8zIsh^GqogRhLK9Hsbuf#lkj!DVyO1F!AC10Sg z#;fRYp{b8YteT?l7o?}L4VN;+K4_H(pezcw*#$dCbcHVPHlp;?`#u_|_*Hf7FgWP8 zrR!eu520n^rA$j{@S4N0w$?Dm_l@+p(l$0}X|&Mgk#-tuED>p|a8A{3$(H}T?Ke$d zCMweDHkF$V!cNA~z=}wI4_^j2hQm5o*OUfCBRyeQ2swr&o|Tioy{O-CQDVCJR~7!v zNvw8ZeX%-VCz(=GzJR4%MPP-fqe)%;5w(nGJNqcNh^JY{fhw+Z|4Zt~UwN)N-B<`= zSBHB-{EN5^&voJh4B6dV&^B6d|M;9u?kW&`Im*{_b#3OWFD37};&jRrK+LBtDqwsGNObLah>wkksUc>9q zDbGy=CF9mr+VJ)hU*R4f^UWhw&qV?{Kmg^E{h%H16P5sTH#b(xo_;S5?1PsGHJs+a zVtW8Dk314@HkHs_QWurCj6H!M$Vtmlin4vL7tJN0H4gew30|^!E+&{%BGcA`lRLC` zqk+qru7c6Vd}CV}p<9Fq4{?^3@&pS$K{>R&%p~O)#%-Gs=Nr9A&5zn`0~n%^Ba^ki zAJNX-G-kTni;g&IbVKiGwZ)hw#4*+8P*D|X1&f*kvq4>VrA#3JFU5K1+6Ul5~%X<1S5)!I7Jla{rUE%;?vg`K2eis z7&jD2qwBu0VPs?K18Aj~(R$SZ?DXU9xbSS2T^XtpB{~t|a)z37PVO=q)-k+oM5d9~ z{LiV(@uf;ctv@C(biYEq{Tmu4YL)Xoq2&_cU-n^FsRkifmVIV-UIHre+h&DsU(}Nq zD9d#mF9*2>{C1+j8`D-`K_n-nPqb-{PG!8(zOH zqv?#x{RkOZwi+AEVmf(7l^F$T?eCa<${)H6?xmV#6hrC2!?Hb5GnfIB{IG_CgduT4 zgQ;|UHo8%n!0jwP8s61fa75%mE;>O-)JA1))}0u^A5}cJ%t;?NLxr~s)v~6$T(X8w zurzuzscI(ta4R#{=|~fTt3F6g$(uC$?E4o`ff4oAykT4VTX*qLL;E7T-*(DOAwWU^vxGP&yE~%AtOSrat{z zck&QnD0e% z-1LNjlR=?zs6nOxu?%hy zy&R7D1cunuWv|LcLDt=Wt+U^V@!6QUp1`{b#_3}2DAfxNkj+Q_MqcBb_5e+P z9TrH`S7#MQNyFwl;)Yernk5s>K_!}1Mt;_^lRLPb{8OV`w_ifm$cmsq7QBne%8V=t z62EX#z)lAR-rn9?Jm2iAsj8A*w&My=!%3M1h3k){u{ZKBq6{^uKx2sGPj|?sN z(yYnz;YFEk7%>rG2ucb%n+*hfkip^o5M~?%#dRTCLFrR6Hq|rCL)4om2L097c5f;_ zS9)r)u3xRZOUEoz`%a=RA-Z{9-n;|GcPkm!83#g7kb`~imWXxhX6x{TJtm~_?Ys64 zT7QP7gK}#N-9r0*S{0<>-4nk>68*gn!K;2()$VxSNxwgiFaDY@COE%gztJ5v#+AZ$ zo!1rb!rLFLMoT`70T1!wkRxn8xbBNE+8yd2ixkFf>@I4Q@_D$MLYRh!TJ8y)XUFp> z7RX}urDA(Tchd-ZQ{A)K8;Ug&C%Uul!DdfIYf+)Rf<7nWQx`-XD|SgQ-)BF0TPW5H zbFZWIzm*0WGL%0-7g57R$I$;1pL~o+lvw1x4iHhlOadm$AA_Zu`2!P30&|C$dW}x^{ zoA<#6X0oD!uz!*Zi7(+o*WVG^U@2Raw>WM0BW%HE^QW5Y1Ll`&7eF#fP>OYBEG*dw z09_XUI@9-zJuML0fY^%d8i8Hq2NjQIAZEfLp8DvowrkP>hY*aLmjUG8sm4R9R1%B1 zt&10jA>(^8qUqBBZn?qumExuo`+F3n!d`Mdr1GJ zCVrlW2s-lza9`YTyEdC2Shlsj88EsnnwKdAAP&yX%#nI`31OgWF~pzvM@cuTgs_rF zJ|g7Pb0E3>CARCC4eig*L0wboKl$jbH8b_311~w-A2S*<$c25C!Zbt&GGa7 z@dreo3bCKUF@dsJ2NBu)aC2<;-#5M+gJEkKOFm%=2MQ9{?}$k;F0LGTQ}g+;3LSnr z{U=xf)V+e7FpKrDU5rQLR}F-z^$AK>{fZOTA?A*xfzCDF-4N+bvN`V1I)*l2WM`7T zg!RFt;*q=1dys@=E`7fPNNXAa5KbSb{?Hc}5#C!m3PnV06~-VjBcg9fn6gSC0fo_9$>^u}gNAc|bnJ*|5BlQOr0XG5pJ9=yI z?I`gYldcLNg(OPBUoYn04b0(!U{i!3>}3csLTIoM$=IjNo`f@Faq81>NdJ_x24eFm z?N_r1HmoQ7R%7;Bnc<7%!NC?s&ofukEE73Ug5Q7t?ji^U*c3DtN{2-X5wqOX|6Xvc zU<)RhKk^=apF7fkjkUN5JoRBy)Y7P1`W&xQLj9IzhZYdW95v}7R(eXsfUz)#lT zJRi(h+T~qLWl4DJE?EmDE&rG>42hLe1WDe~txOi=k5Gkn`@(;UEK`5c z=I_+L`nhNCdb32KX%dQg8N90+f_xF9Xxp+mw)D)K{!mwTb#+yLm9b3WX?WG!t%sd(vK2qxXzDi?z5- z&drVLWIDj5js;i}*N0mo6F`I({EcTfY@Y2^Yqgx=yxAM1UbwZC?A^Xne2&YWripYW zV@IdlI{m)DsG4)a5MH`e&>LI9eCXY~j25hxj9QR##G@#8HsgYGlCb;P(_$y8D_bvI zl2G#T!d+m0=o{F$#VMQNl-yFhQIS!{+thI3i{B1Mgk5F32LUb2F7(UyA*@zqj*>kwk2BKl{q_uxegeDUWSd^7v{ zL_qFe3u~r}8Ma0X9{PN;FjeVd%unDd+zyV@q40N(!=-ut~=RZy)!+ zE`xZjGd}CZi19Aq3mvRw|Mm&Xm^p=%BNIS^3E-p*U?l4gFkzU!!WzWjS)2wfwJiZy zTm4<|PIdg*yrdV&#CEkH>tty||&qiXSFhA5Mi0&eD}Yg>0*-a&{Og)DO_tm_^kYj}UMeJ;*=qF+U=MBgO1}`K}j9 zLa{1#?Z=_aBIaBVMcn^K)jRlA8h>Bkr_;%{Z8uG}CfiM%++@2>HQBap*G#r;Os>hc zpT0lb_x=12pVxKmz4lt~h3rRCAS^}0S&x)#4Ydp$XO3^Km1=D!!F$Ye^DCHtGmQv_ z?dM%e#(lGYF0EyOG>yE69MuS9_&}7`)zNtWS;a2cMUZO*h`OX$rv0@QsbklnPIEH$ z6kBOeOTjmiCl*qemnU-bY&2)_*b3qkPpHC8)k9)IZySr z#$fuO_`&SIv90=y;(HnRJi)bDnX0Dn@oe7t>+>o9`D9u;4{H+py*eNIYiXQw}bmu`xN;Z{oIkP!H<$kvbbBE0lHg{MR(7hSQ!Wf z92mDiA+CjwOB2E=)p?KO3VH`MzenL1UwL=(2a=w)E5?758JB5yE_q!U&;q^i-r4?{ zk^J!yq+<=xqW-^H?|DU{m#bWnNO8w7YJ|}A*?dN-`D$9fc5%0H6AVQOmhlmCdl&Me zyoRo-HW^adN?J7ImEqAZL+>9tcKas?;7t*1FZTp2l=M(UDh6)G@Y*99L_J?W%U!h+ zK$Y6P{CpTOW)Y%Zm{=Ix=&@1_=`)6P#`7(9pnP!XQ4i4l#gBsr83^pL?!U*|gEsm6 zp|esM{@3Tv-pCi&B1shUeb5hYs0t0l z0xHG<3)f0SSIH`|P;!V-Rv`88PSmtl9goP+$bS>}1bPnJ8WVcG@`}irn?ZTK%Fe#Z z6lb0bxXd>DcGdp~>F`}QYI0{n_9|kc`q9BR;eq==D8(R+z;*u9x_g}s)&LuP+dSE+ z5XBfGa@fIhK{Vj~080Aei_fqXl%U74DX1{gsB=(sFfuy8U^>?+5A-T<_G-h@8LiCM zX5;ayr))*9d&ISTa>x{&`e)miF#k)9Zk##sGdxFEL$_r3)#cvP^M=HGtH*%)s+yDHH-ujaLND`>9&OR&*mmPVGX& z7OD+r}P=gH0hc`(J`xt3tOJ0u{a`%$&t_52Y|Jj52K=SZ! zZd)X@0Qz}3aM`qN7C}{#5CWqS{UcOoyCJuWu90Y=|mi7YUDWR|9`myw6viO=&UpIBYHp(75ec~l`h zd)j4I;LHp=l`Dl=`1Oy5+vsy+m;?2B4UUROL@_PW;R(e59QNqLV18dQS}4(OvaqCV zD*fj9Gq9k#%Om&y{k8w+v3K*3`G3t@;GwY{ulz;l-$3V|jPu~$kdov>IlcJ6j?2sT z$Kvl>ln?`y+m#oy*su(AC1Ex!{E?ti1)*W$Muw1a2hU5!Me{izc*1Rx<@XilDGQFu@&UzoKSmO)Euj`nJk?UsOlII(4B=ve|6(dj`vY{^KNdr z0S2+^FZ&aWcFCk=rmnKOb@W$WoX?X~xm->y-BEAva~9Ch!VaL&lz*<7e`v095yeA& zyo*SWPX$m)>MN@Hnn0`u-ir+gO_|SD;**OOwa@D;1gpQN&Q@?gC+C@;uD5Y@OLdr2 zpgjikXW&0yDyn;+gO7QOuL7Whqcg;01!j;$tGtv~zKw=OSKqRF=n8n)q_ZM!oulNF$Uv?dJB85$-Qkh3^d^D6B zVAeJ?;I)Br{9eQuZbx@J^!j<3U^t5((E+^#_q9?`*w5k?<#1e>Q&G{S`W%#grd@ZgPh^XHWRgb2&%>X)Nnn zazJ%<4k|*jI;Id`DGq{?H3qa)F1Qi>Z`4Tk(h;el#RUuUq~r&bJ3p<#t?9XPm+Vrv; z*;^C=rH3N>xseXYn#EXj?atNNfT7aNtw<3ppqsXdM zp{3X<)7(;)@KoE7=SP|}vN;(mvdSQ`8iux9vsYrP$z;|ZE3OC~DL+MfFC;C$-g{tC zpXA}qj7S~M4Qr}M?))ttZ@zbg4U?rY|J$6gabCIPBjo)m80V?Zw5f81p{b%p%1^zb zZZPoG$;oNI^W}W*4^{4-x3sCLspFh{Xeh#BihKqqUGA@PzHFB@0+=sD{;HZjDVXhE zdEqAf$uIcfUqZ*64m<`aaw+NdXv@A zNdD_IC2+lJRRb5G&gxn1NGjTKlqkN{4HNr;_oSwx>z%2^dz91FTTXIk*Q|SMj-?!uE}ft}ZT07@{f?IVi-eH5f+O@%?q@hMUO{%Rsgn z-Oy=RYO1yVF;=fV#*d@e#Xa`3E*d+_^e?@Y_v@1G!nZeDZ{Bl*l69WPHKh^6bAfw| z8mnO&XLH=FxGSX?CL8-0EOXR1Qe_af&G{m5{M6;Harq2tBSg}IZ zD#W_V$@zL)!j*MBsunq(Bgs*^b(iHZ$WAYSpTvXgn%X45kUn<8Jmo+Onfh1UCpI!`UmlE%(sIgNCFSy^J{%{1YZeVJgd5AMQ9@rH43K`OeF}*>eeX zBiatgC5W+dgK-;A5an_wWdx>Pzdd;m|K1suQ66UrzoF!0s0Ft7a^RU_Aq4~p42Gg- z_~Iu85{>IDqYtkD6SSPTlX8P!999Rmpx+k-uUfWwJC|3r0@p|LgDx*^ zQfpVW>{ca{2w%xSn3*NZ5t(U*Eck{X{LI@ZW2_-v%umYGM(k}Om(*V#&qauAI5SKXN0Z~*(bE$7rB^*yx8|!4-Va}d;xDS7Z2^vX8<&ln z0iWPf@n-84^t02`L>8YT*;Q3Vfug53-)e(DuH13GaZCE(S8BE;2sg3|an89v4F0j) zx5*aM&2vUk*gbAQ5so_*6Y^DdBGP0fb0{#>XJXz}1ZKVJHW5UFyOq*IKoQt@ej$2E za2QOmp~lFveNK;67*(m;7-4B>A9hj=7Ys0jfXbC7bB`Q=fM#V)ZG#`?QvB|kC+B)2u@Eb<&tgn+1Ebz9+y>$vr98o!_Pe+bQRm%Hlg^f|JIp3c>)Gny zf8mjJ8`V+^*nn*Eo+>u8rZEPXkmIOAa?)Tm{PktdUaU(oig}Go2vIWkND;>JQ4loh14PW^A8tWve~D zduPD)U^{##chjkb_}Wi21{!Tgfd|zXQW7o#GH${VGjvkVH6i?$WC7|62nrkT5N+yG zk3kXpR_qXE1RmYuFS&nfRJZ`;j!n$MKqhrpZH%DXRJ}z-c$X#qN$qaBNe^Yz{uos8lMi7RY|}w6F|l0H>>0E)jaqOYMu?VGY2GQ2 z?ZwZT6jkr~PLmyo`?7sHa2MORojdp2`#K?Keu`!q!7k>G<|~{(i@od8@$t!JsgVuh zgY4iOfUxrrpaqMn@~_^~0wwWo1Y-%#foE_DiJzg9MPIaZjYfc=997&TORZ`}(A|e{ z%e4)QVdC}a#)bDSQ<090OoX}hkX&LqY4iQQ<-3fNt3MaCym&x9~o6 zfqNr44*5-oI#5U(0C(Sh?4HebS-a~s&-u<*(=!goh$pfPcbhxmoS*fuGrw_r>&(Q* z$45s)Q&L-9ZHw>gW@cu#ip{K>YJb+WG}-lfJ$A6mb*Imp1qj{xsN`u-JHNQV0W-oP z&fcG|RCMXRH!sctseja@SZBj$qSX+u(W$(?xw1EL$-m#6_$5y)WXz4Ojom?3!4xvq z$R#H;VWlht73_x8+bJ3BJoGzGCb1W$*U{rO@l&|0YuBmz>*KEyL`DpVJ5O*d9sZ{$ zS@#g;n(V`yyB!W7jo&TBo0?1hIN59kVQ;zIhF9^05ArzQj0^)LR?^}!?Ce@0#`$04 z?SBiMu`v50NdD8OXfaGZMoq2rhn0H)WGFWiJs0NMTB+pUp2cQ6+7u@I->N#aB6KM8 zXvL_R=n~lL3L#OP?1zG1{3EC)N8)7~@?w6uVD@YqvhLE9CN4u5^6%Ld!o{r=s%(9t?>(*V3|Y+nLt&PPCh%o&1tcrCqzu=N|IkZ?2-L z8=bekmwyZs(y8^uP!IgexDw-OIS`^sc$|E61)(|A6Y9Y9$M8O3?^E)`?+r%SC8jiY zQQEBA%&yMSdsRG)HjAy9$h>2wDYHVpZ_%(EXL17{64Em1RBuXT6g;D2Aut@}5Kgvd zqX;z0vLpm4C4SsRRQk>)aA8XA7mTUp7C8C;0P)UN8GrDPs70Inslcadb6{ zb}YysWhp3^Cp+hx67PN+aH+PIu-=jN(~3UknU%~U)j zZ;kT)7O>?a6uncR4)6?uH8C?N&B z!v-;}AwV=)!~BiMq6QV)Bb=6Nuy*q~;gnSgI5lhObXQh6FZiHCyrAB#n9#T65h-__ z4X2d=rOVER&}#KUhb-1Ec*PKx$gTpYy*x3nD31@#-u{#WP!ALYv}95l&NalyA= zmipk)Bek`7Ka}mu`7tLH2J*Zxj}0Tm%p%zQP}P2asF7KWKZ4xCq2B^#3zqrC^Ac4v z^2!&PCVZC#t?lxLGEzGoKjHmrzb67jIMu?(b5j}ROs&|WMwgPlRIx#P zKUo%GX?o72athjozM>YE8AWLCuuWpj~0K<2lCY!YQ>T&*;($Qpx%(gxf&DyekSOtneuZm5eN<#orRP zuGN*PU83*5&ts02=;iw2F59-9ADOt%qavsIe&pe7#k2^aIkdl4P1e?Q5TUt-ms%tC zqH(Nl(rbuM`>R~GcUY>F48ahxb!z3hlO&0qmtNrR;*q2Ofv-VjNGvz;yi)JTrTuef zkGd4#fVGuDdu>mPFvxKm=B&~pY?;A&wjN)9I9DDy2h(Byx6X(;pwt`5Zh$b{a!z!X zkt~DnX20L#;jQ%`$`0>kyz4&?CX)rdR?5`emloZdq#M>C)L%bRnvI&39ke=ILYJJ< zZ`lG)GseEp|ImQIq4^om*BR9Z9jS>c7GQ{VMf}e9oX>^j6sn99spFvult z1ZmOB@x(Sj+xwmlb9C8iNb(Y`!2bLrsyEbCp|Mf{>pDPSsoyS?HZkt~Q6cZK>CBZx{nl%B|f4X`EeR2Tqa z+%-#o_;=-62z9fN1YeI8d{5cwT>VO?yi6F07*tIndte%p$(F=rI#)1m0t?Y#clkxO&DNSf`akbo*s+Wiz! z8qK(p$IaVy7x0(?P3M2c{w*AnXqk z*v*ezItUiOzIsDQiPTh9CJKE#P2oep!SnZI_*V*GF{#`MoeZe1)6aF;6OrQ`NQ?d$ zl(ze9mdc}h7wFu#Lj=uqZpW&+a@$w>qk|yp0>Lj6Gm z?sfFV@L+60*YG{k`DQ&57mV#0_6S^cTopuj?R!WP$w2)QN><9LJbiSSc*5)R2jIO@ekaf|+vQdK_k#mrKFYboPO$5M z-AW-mg3&5!X-BT4_0fvZR=4L{-D>@9OF;|8sbZiS15X$$SR-ytXE-q^#5Tg+pt_BU zw=n1jy}1>zdSW5Nb=iYqu#z}n(i1V$6ceLZiNo`&lS zbIr_=K+PqxBho^wH3j|-mMcKwYAdGviTdDes8ga?muAZ!V6UKX9I}=*PEwQEO^x)m>sSUmSk&cG=HxB zG+pblsV}vTOEgOf0KPX1R9# zye#OThlbJ=NK(6WRDIK>Tg+u)K)+@daZZtQMm1>?wV82(ctk(uCUpg)lkIx=}clUh}j2f{D6vD`6otk^Wp>$FpPjiP_4 zE>r-p&WHy?d>+;OlB4)-O(Xs5vY4wGTkprQtB@4=_rD^MOkXGqSvh0EE1J1zG@C}^} zuy&&wt+ZPwx7)Zwpe5iaO5O1IxKOt#lhX(vh9`Bn|6dkBp36{cBK&9p21Qq^lgdC` zTxd`^WV9@EQED?OuUahu8X+$l6g+ews-V~QRxiipbpOe8?)3e$NK#gspE4pV6-ldZ zeNA>$sgnIH@92R0;nUEEwX*V0ECQ4T+uw9{@-KrfEpNDSw7!n7bJ5R5Zwu?X?3;Pas^WR1y)7dM`tRon;gB*&jem3FDWEqZKVRAMkDb92!q;}HAyU? z)tW5s*nr)v(!DEp_$&YLqnW|)Yzl-SU6Ic`qWL!ysj8koJ=ujNNX6Yu$!$CVm=E-9 zr5gTkHL*cobIm!z`S1CirplxNQ-Slxh0!#Z1G^8y zlcQN|1=fRCrt)GiIlEVCJH}OTqiy@MI?!MiexmRPy7h_ml%3t@I&3!);;!dtsp)JD zC|}3ii$-!mfJ#(Iu0=DsOmsbV_GDDWgUTCEmWsmB-%r@+0p5~XW7%A13YwLOfDNq7 z8(qBXQ_FzBKK_+PCuvVGlM-c%a$UGXq<^Fl8`x35;ZN?e;7fq2#v$Xv@VLSw+7 z4z2qTRgVYvGWFpIOs|J))HK!#B*&utq}G&d{&HLgRB zn9iVF9LWt8=}Z@x=Ul5RA_a`yg`7wY30=f#E$nXH&XZbqhHRXRypmddzz6^$@n@Z( zoUHmDC#(HRJc80@((qE>Fq|uFZCkAL2@#4b&-Z_r1g{u>Lv0Uhv}+KDB7-IE&2wE^ zmf4PkA_J!%B%gH>l~*Lh4B>-sAyDG9@^Hi135U$VLafx}M$D1Z;Z;z|*b7d*PR{@I zDwo|vZM+6PDN}4lPW%*)L8mMlU=uY=NnGLuC&ms9yo#q+g=E-ClRqYvVPI1Qs?&yL zLfo$%6xj+eCHG4x!_9q6^;_F~RJ-wP4isR}Qp{C981c{2RKo25mEl^YL{`+7B&*uN z16RvD;u_8m1G|{w@hnC_&-4jNNjC9gP_R}t&0%HG^^e@MPlT7=z3&6dY~WoMF>&U! zBa-9I1lNhd%j(kPWFakc>D=FdG;zVtz&-{z*Jh(#yIt_2mP06ODLs3QNxI3GsTY?!tKGlIloeBiI{;&8P`?px(P; zpu7`f7k$cPJWm&HkSrm3Y=8E0Jz$gKGcyg-bE)j_cn<*8-7R&!H+{B)+`@Z*kUDa* z@uDwWT|)6ue@+}|!f0JJ#=b)5$cclk0J*-($%}{EvFr}7TOY%kl*inPOlASJ*K7D$(2ZU-#BXY8 z#u4$@W))H&P2pHXQ!ZzMNLo~yC5=46 zuI~ht`v*oss}R5;?M&BYyaa%)v?>I8FKq$_M*!zDG6xx{9z`|^q1n3hE;UM%M$cci z<$Zsj8idK!pF{)$9qz0}z^bzMR;rbbzT;V@8NUrSzb~DuiooQH zK+jJJ^>)=Lfr=;KzgRh6D6yV9KpYMK*UpzQr2(ocU3BV-%9u|+A z1G=d-_h`M-|GfA`H-UXGw?zJ2biOd!b=+YunaZGn@AGD1mkb5|k=jWVQ#|_&PmyOF zWyT46NTD-Z#r%41S!Rl6C;x5;uLIkKH0}8dFyQWn9Dp1pT!D!zDx~~8T&opfeTUK#~-6i4MZZ^DbZwf(2)bS{$~nBzcLKg zS4Qt2Tn1A*hEAf)6^g~bzNP#t$PFHyP_W#3G$TlXd>GW7LW-w4+}tZHbDDN%kxmSE zASw2KN};Dde-cST5xz!L|JW2AO8nWOc=tm%P!CdH5+DbaM*@uv{d~$fZ<%5g9pnBz z($nFu*jnf<7d@v(gjM|SA2+!Qt8kpxN_;@kx{2-V(lz7~+)-!>`3n5e+eE@#!N~w_zja5>+(mm~&sPAEZc9RlJpmxw>wfZ;5P^KaUc*v?RKA zH?xJXLf0=*^yd1U>Slf?8ntt}Aiaa-)BSeAqyuk3Z(lj3Qi@EFJfpEXRF15GX4;{Pfd!xw*IXkfXYU@7eLF&K}Z4WPL`00qPIGAlOaL)}Q~4awlhh zzR}^e6aa;AcqxB*%aCw`)RR%XnXjU~uO6(FpE&7v1BX=1)fMvYU&Am(Kr~6G-F5RU>3j&-MXRj3=v%t*> zZ_FO`i`|)iTE<%U-?BJrQrjLre**}h{~qoV_oeu;!az9}2R~PjN|RyntJ}!vj}-20 z9Nj*HHe!j3?=E`HC|+#&H`LUIr$8N400StVP(V^C4otvndLv1al#ilM|FzRGkTSXV z5~+l~zGQZLJT0M|n%@c){+4jlFPanYv{6o@@^QlXkZMsO~OzH0CyJdw_$ZvRnn)$n0t5li%S8;dR|}1 zUS4>}UdTDgD3YvgY>e&=nl`6X8YM5r2te$K#A%~D8snASeq7wgP%pHHZ(@FCjjZO} zQ{7eLOpWU5Jq^wkqL%Tyh6ZOj11jhy>%v>|W61AS(q|}|2w0l~CCB>R&aNgJOj}1S zpoB^EGnu?A$Xkdl>n}K!sDKTHCiVXe9?Yn47H~f*IQE9b12N=WRF%gOUXVE`rgdw8 zY6;uMg@5+MrG$qF$*0X~licaMFC&}43^dlf#~Cx}%FflmX{gSusHkkcgtq*Q zFek|LU}q(URHYP5bn zTWwN?7HdDq_G&cG@zLmO7@F%N$zKG(mXkN0O)WJkK^*>$pXuSlWej0l_)*&_{+0zFifZu}-cXE3t74>e zJ4j*JUyuO8x%;PlF;2i2SYKE-b>OW^S*l=ZPNPv55=4AvIFWZ6aESKbPnQD=dTS;z z3>~&-?*Z>g|4_lFqLu|bvaP?3x>0IDXtC7&}n#D9XDpygp4H(Z655sz}e{EJ-nB!Zo z2lU|DkaN(_te`g~hMEwg`eV%E9m6PB|5wQ3|L4vaR0iI1{`cllZlgx23S(-t+UaCC#8sIn(}{2gjduJ)S5G>>?%bzSklSuAA@A{B`H%}5HiS9cT(C?>Jn z22HvDMq&e$!=Lltf3vCf`+RGi{gL?BAt+we&$O-4t{Q-9Rue9`S5;sf=b&f1wpke~ z@AjsvVXIAb)DSU<=nKJz5P>JB!DK%=`7piPBVg_0>G{kS!*p3QV|XZ-hyraR#vlg8 zrTW@-1tqwvAc^4&Cb@Tcw z78UJVsAHm7#SrloPeA7Qf6m8=* z9S2#2%@hXRN;O@J7ngEqS?KXf)x+rJG%B@BY(qFEbTIK z^EW(Pw)t$Lgfr#(K{AxQ>{GWOGBa+%?MYzlzxsvPPpMNO+zuB3ae#w3Mj(&wXWi@K zX5&%f^ZHe3vUc!)yqzV?5IWyBuR{l(9A-pQkRO}tyTmusSl>Qlp!PM(4gc~~MK&v+ zXH>fuQ>Vm*rp_k}K7wyAUN8Vi1crscSfCHuJMI9TaRD?dv=(piM&a`}uR&+1)x6zU zdc$!X-;gZufeawL;!%&@h`eCsh^*$<+Jo4=9Z>vMf8k&)-f?01tU{|`Rcy9b5xfo( z!G-B&A&j`xZtxuUOe-#y)i6F_o-$S+1_akWPe&? zLTA04$w~9i!zN}g$C055w^kcI9ZMsLF?w_wZR(1QW64JboeDyayEq&H28W~r;#+cl zrWMv54@=*VXi}_HyPP}DCl$71j%~u5G>Cck3i8pY92ufFb}riG2@9-TKAoQnHH!?A zRF!9qr7^ig1(UibvsWN1?15XyB|SH3ed0In$&db*bPlUz{=0eAnJJ%hr`19EjK=J2 zTdX9v!}TxaPA9Ez*Q?=vN!kWo(%~C^b-!=6Z-WCPONXC{^%bP%ClSRl=phv4&ew0MO}YPZN{r%y@M0XQOsVy zt!!`IRmb}FTwKI8BDDUbNY8sGy{6tymUH%x3wi0|--owq_S$vkjEvDf8GYk_oH1(M zC1f_F%$*pvBf|)sq#o~22L&KU2tYx{`}2v3f6uf~CEm?XHQaZ=6Wd-6*@fu;HFC5= zQpKhwU_$VP8bA(s&65mXgY=J5dt;IMRTqE~NrVpmKlUPZI3;2Q&Y;Q0WnZjT3m*&% zfCAIiyQ9)cjZg#hmjqca+iE<$D>&!XtilX$fDVjdG?;Ryvu|y*J*;XS814Di{;7Q0 zWE5@+_yM?vQE>k-K#`DO+DHz=7I^Oz&K9XtINQS%2u~?lZ~-%5B=#$wF1ewTr4Q0p zWV!+ts3#3d0&LR4o3m9)YmG=ha7+FJ<+i}kI4n52{weFMQ0EH09eMNUT1__3~}UJlH)ZX*R6Dgdsc_As4HK?;$d=Wt?DAA7>o+ zGZ4>3r#Oq={cgN-f+Bb(nE|W&KP#l^9lz2FbvG)sNx%nRb5vc;Eb( z^Zj_)w9?ARgQo#?*LqqG3rW6{U0=j@Maj8Bf6d~6s|=SBsI^kH<0ohGF>3nLn!9NQ z0vnFu4}M(jt`|(O{6)}UXH}Q~!bt7uEl5NNYxN`` z>hm_@R|R)lSHMpF*72v?8*y*~bk+?ZLZ12q21AulN6qF9t%I>2qAOoSN;s>__!<2Z zt4rmD5GQ7RBa;%oaM6Yaw{r$$moiXC#g-$8$X14EUFI^m=SsEO!mtivpL6Fz5yF;6 zG@Hmm8!LI1X_aN%``4Uj_@}!842R zg%^jRe8|~nq1QbP{jYNQKk?CO*=|s0=858Pk+(&i zNYxlAd@<8)A{o!1L%NaQ`_xVD){DxETAUrI*1GjYSG2;})v zXgOiK;(bvdBSI_`34b>^k;G9#we6!H8|-5T9XV(4x}x` z*?zG#v7IvUBo5j{>*}V6sip5jCwSlZ;U)3(O(%aQppFDKAB!IfEkaG4)|Xhlcjcyd zJi+p5&cu!58(DpwcfoC*z!&mazHv?t`naq`kwZwlFotYS5>R%8Vr==>Ws*f39U7F^ za|jQgixbBa)0R=Y&S!$O%&#ll)V{NyK+{?FHtHqDc8Q&C9NH(Uk*vnsY}xZPa=YO= zqSYhai*^Awz2$S}YWNr42ayr2-nc4Oh;cz8pjL?X#mvJSf65EdDH|GKR-~Z+v zz-&MLH|M8=>_E}f6P>bn5ne6tG~LXwog?+PsNgyzKsRBx`&t>|GJWgL)wvi_kl{C| zwTSTh(3c;4(zKJw;|wKz+LglS5!EU2CU$z$<#2TXheR$VcTaW2wm4s)_R@XM-Xw zMPXq(6bmD|b$EKJb{c`2CUD!|p>u!hVM%IjP;AFIQC~L%fKpAyJw%v(Nb7r2)pDHk zG;V$fsE%21@V3xCSTzqIbNo!GlnA|*z2D9$jv2=Y6ccf~D}B2niiO=B<=mj z(efYU?GeBxx2oPj4>!!v|JN_jKxZ%PpSpg6LUK#N>$PD>p{<*%*_O*bf#ITI891mR z3ZH>+tmn`Bjtj)$w}KnIZYEx$cvwfC&HZ7{5wCp2n)V$TpXbISUNFn*aaUzB*M_^8 zl?_xVJUGJt9xs8gt73STSbXE&Yrn2~Xm25_M&?uC_cnpjGBjgKxGlV0o{_X+jxByT z{b#POW(AEEM$;VD+bjBXtl3qL0dS%ZGyNoD!sDfVhG$7wVI%Gj3u1m+EmnpF#D#1z zy8;~ORKgyKKcgh?1RetQ|7UC4BBF-y`U%V=?9jL^hT-f(x1YU@fNu*rS`xuT`_L8pQ%odWD0lbOLk%|Dx0(McL$30?^zEvuun-;;`Ik^JuYy)eH{w3%p?Iw77$TH7~m zoIah(DI87B_}V!wQ$U+7o~_?h(!il!HrHd%bdPRpY~#3Nc5$v)nCCxdocGl?*z;6+ zTY}f3sk>h<&V6+PLKFs{+P3;um#?2tsjZ9BzKpcF*TEc~HXg~KP5&lzXZjba>9jVU zY8XS3MZecwl9Pu`zpneX%{K+HONjamHYXp~c3yj;7wNqiGCQ$%V7-W97>ZfCJ)C%v z5z^pcnIt(L9kCAOGCqKz;PR_S93M%!9F(@C_0y)l5dcDPe9ZARYDItMUK(R7g*HR2jNjivD8Q@&f1|_{VwLVz7&##7v|? zxpg3`0yRJ*f&7hW((r_Mo(RIpCWQf-z)H0Er(~mKEEe^AGm29PTVzys%QSQp(*+me zS|c;WvU+|QwtNPm3wOhSCUEx(jlSgn&2#DYL_ z8l{=0(pwiZ>IzSZJb+@>SG74-54wXAm#CLn@6K?Hb3i-7z1zj?ausUtJ_X_Ni8MxB0aV&D8b3iwbt9+aqPCNU!K?d zy8N^{UzXhq1gR??8TEYs2o8{q}4G`XcBgPA03>;o&k$ zJLyY9xPz|w-Io0@3*HI?5#Bq^Lhctjp0vPQHb1o z*bB;ed$-Mfm+b#Pw%#!~(l>h7?bx>MWMUf=+nU&z*y@gxiEZ1)#I}uzZ6_1W$?sq1 z>{EN!{@P!=>Z$dvd)?QSo;7yP+CXx8;70p~WXkM1(6^Pn=LZoA_dnNqjzTgrbgU2lBX{rSMN)ZWmP-OIIwn`;Ky#OO9qy~A`9pNQ;+IYX;1_Xg-&5Eo}(p0hJ3 z*kz9VpS#X*q$CrPfMB7Dn2$#0x9Yud#FjHaQw6|W7(W+rO4qh-bunqi&#Pzz%Ina- ze1&i*O25VS@9-}(8Jd67v{-FrxHlg?v_2p&%W$507MrLJ``rDPIM9fg4oBg^PqK7g z-2Wp0L4R!u#8MKzIBn?f({<8c z7p>LIx4Zosj0d1~I^3%@)zk_Bx}9GrIB_*vLe-d`W=ZClXBEHc=|<_}5-!kngdEx# zb31&smBaFnD`pt4RxNxcjgQBXw}i8i1DcPtRM)hvCDt`t^TrnE%kO$yNoHMKoR!{k zR<$s3aO4yfV~|tG+D{68G5#kNzF4gnM}Q6zCjmd^V~B4LHx&ZH`YeGJ1}v zOifNw=LaoDcF2_|FShT|#O1&kWlCUX9@oXcw zQQ&SY(+^ebNh`z^mGf#hI%WhJ*axDIC7B_`F77RU@h*po1t zGOpuk^U)&{niueNQP={}nvEz<9PnP_@IDrHN~hPKa_+L|X?#Sme8)qZ(YD~=!mr32 zV5{@r#?%_?HJiW=AT6AL{2Y^15>dtx09*tfcFjXb#V%`_m|2^$BRnfMM22}16+ zXAKc9<4o)$0vNY+Uh+ymAnRd$bNWd-Ly-26{P6HlwEc&!DhlRbJ8aw7t#XZUPDkEV zgQJ0?jMA`vnKE`PK5bM{Xg~jC2rEN_x)ayy8mKa;J|@7^o?o1CC>Sggm)XzuP{|Ip zI~bnf>}>jJ#5#IZ%%JC>2DO<%l-^$>wLoe8RFz>!2i94Ds2qJ4O;{yR1IW@0*>ECS+$}SF91_S@1xPB$h8t zln5U@C4|T?XfB$DzSTyY@X8x_rz6oY#19$_h)d<|s>%kmPMB@$H}Z|P*Nb!ILs|k# z5Od@u6qf?@hRUIdAi@U~o*0LZs+~Z(V}3RP&LI0-AIC6I5}vXM96f)3c2>>`8|7h; z$Y}U*J z+qgCX0=f4?m~~^vf1h(?q1l8v%O7T{d!)vDb*Rh2(8huTiB2z2_8r-$2~*P5VYux> zK3lmDvXh_lhT6RM4ff|aG`G2s>s$p4lWdy9+R^9XJ%q z(oUGex4L`-C!g0X*A6+7Th-NJvDdKqyoj;VHL zdz9U!bI^yfmnNJRbxuT^a4g&zEEkn;V-O?xB)smy;{DCjZVp;|%rflP#*Qjr3QtuB`j-X^$_b|1eC%iy}8Ez-_}HUe9$a6P2aMnOGD z{eeT3w4xGHgxBvM#q$Jn?praR$8FQlT_f}RB%q7%U$fw?o8kk*nR1#AEmb>JWDPMg z>DfttRR2arMx}f9$s)oFw?e}arL5AWnKr_)4tXEJtyv9pwkujw|G>}8aZ!4duhj(G zFx|^MEseN%%x$-p{oKH*iiEA{A{A|VxzQ27g@jRHc9den=y1L9p_!c2hNDOGi;Y3Z z9Y0#+4f^+11*q09#-@o485M6*?b*9=GXFG=20PDvMVTs(WFwzSfFwP!zrzUc$NLr% zugX{@A`Br86W5=FG@@{7v9;}RQknm^gBvgM{UbBgCjO?G1rn=p-X>n84!kg#YO3eU z;P}g^6XYlTSc&1PcVfz-3H)Q;^A*x0zSsgx0Hcm^W061{2@7dh+GG5;86de z?_o*eS|Hvnn>1d2(_B3l8Fiu<9WWabf8jb=PM%|NtNCTWC?HEXj2Sj-L9o~L)^E9qhELUNYpFdXW}uZz^{?b3L9KrdRHvXqv-L2pp@WB|EPX06Hh2HvmWv= zEFs@r7H*${jROLaZ;R3t`p_fG`gN~Asja<2E~@IS+5x>M;hKbJk2I2gqPD99i?BKQ zjX%n8{ISJrrtV@4{y9ghC@Yh4Z~HcO8})$?m7w)pg|tmgOeB7ByalPKzVo*B-1H-? zZ#eWYnK4GxuJBOpzW(lM);nw?T)Y;~vE0OC5;BY>kyT zj%`n5+uVMS$4!uYr>6c7}YGp~kdTgsZ=>m|iIcUiWYA87vNrz*Fa zpK=ubcxz%q@@5Vj37 ztwAg_?D4`;{>L%pyShi)s)?qN+OGrtLk|H;-_p;3G?(SSbx`D*J5iNhPAGs$T0aZU zr(Msh1p-HKXxCGzbO-Ppf;zc6VMwhU7{(uWkUMo-AmM7jmr`P9BKL2MXr5h zaaa}m z1_^*MJVncHU19htn(=M7nOHkG;6{5~YU8V+$JgJa!!S6OZQGzhGXpah5%#hpB>L8+OsKQNsn7kjb zYy(}Y87NMfQknEG{~+V%%oWofxlG|%%e_ktI#kO1AE=! zFTReiz2q$~FAqNg`~$%RQzXj}&k<1E|DxUrj&kt*W4@y|12>uE z_(TUzYFRt`{Jm*mvyAzsr1?sO-NnC%a~cDP2YeI}Wy4FF$@^k8hU=YT6Se=sRO7u2 z0lg}m1mtZ%Sbc{9DsYNFna+x@qcx3axE_iCOC24$B)Tq7_WL&ByV!%CB>a913qf|O zJq^72#2g-EY%G$S4FgzB;5sNxp!Ad}QT(MuO09$hY9)ci zw6X@Ory;}MWfKmJ#}?th_3vvAONCb#7O=d7|5lAK%P z--^~hgNO8%Q5#5lbfCd-`vYk2 zYM6%a3B`}TW#|_UGX!ZdM7-IlI?EOv0@OThv8oS$lA0r7VK-Yh%0}r|pZH#iHop+& z9EH9GC#SR4eS38rILVyr*RER9>fT!6Vz_o2`L1`eJ**ky^-xCH8?N)E5cUPj4bBgK ze`W3w0(jF+FeD0pEaaN0(q~L+8yu?q;B`lJ&9*+ZW(yeih4S4s;A0oPooRVhl*=%i zmPU6N;gECqpqjIKu>q=m8))f8g)~?&uBErDa?DXkb8n1HFqQkpk(FwkhcA6LMeN%p zLqU8invNLK5IGD9feFGzQwn+|1_kQEKp;}gBqoP&9OO~<@jy^Y4 z%5y6*x26NlmE(fkqNc#cSv*u&$5_ z4PdJfG!k+Mxj@V~n*&@9C;iDl>M?d2nYvp=36spA?g_jejRrF+`Ht_KYBf+SWKJgb zZYOVJ)L2$)aI+IOw^}{01G|pGuz{3BYxhGxyah#=S7<3p!Su~~6!r_#73IKjSL{xU zjb?Qy`%?ZuFCY*92>6hM1MLpQL&QN1+e=N0v2X5cC5h>rl2F=D=^C9 zUiDXWwWKAGw17$y&VffL#{bu0S z6M9tw`e~K%+p*(JK@g571{i(0-9K!Y z__pw$IdnlSSdnEw8(EO!R8Iyu?_~#iX?92d)@u+hTv;>hRKE3X)l%p_{Xxm^OwE1P zKFKjlyP=UlAc8nu$n>{-HkXEjy}icHugFOFL%2`_sS~J@N(3L=8>SeP&gI2r%JYro ziD5y45J?^*5z(!%kKl%PwlSHhcswfhB7s_c^uFz&_32xksPXN~KbLoRDXGb=IJ)Cp z;K_$4eW$~jKa+|u@sV*!vqU+Oj7U3stctGhXM}4(!Xd^5mio*CdJ!g@5ifTDDqM|J zI7eW4EO{DI2JFT)_nul8CWRr_D@3>4o}zReJL!7*k~+c~WTZ!d%SIVPR^O*t zUW@m9ETQB%1$!o>u(VGC#@Tte&;jgf?9H5{c@&0@CodYC`v$ZIt}ZuuTf@`H(NhN) z(g3(){;(Om1%C>sDRA0UJ2^!Z?wKaYNet3;u9VH8Aqp@NA*9*Q71C2+Mvp1X$LtAm zNkpaU^4H`C@?o><5`8lxb+8(q`xTrLdXAkYX9HceGUTGafneatXK{!8ECH>IFp4=Tn^$W=l01v|jf&9QJF)^$B}-1Ep*^pDO{L4F@Er zW$izQ%hQIuZUlP|Y_QSIY&vQCxrW5I6Mi>NCeVpSv5+f(?thK4ZVAi579<0zR|NCb zGyq=e`?&qsKeb08W9s%Y{rW`&D$)f(`#R-zd2Bd7>08Z(~0(YUK99e!#-N@z@%jvd~d^b28#|^|c z2Yao<-60|Tv5Iq4n^g>81*Iqg;{bV7z0fmZ9q;oYe#QVG8o0&0MQmbHdB{z8|7jS< zgU#YyKZlf*X;t`a-IPh%lo z(C7KhMz7V&)*PB^U#)&6E2@QAk|@C`2041KVSyd!4=Gmf-??6j+zG=MhF9-E^(8Rr zr5|ZvtEjb5mp6Io?qy%rW#W1x16l~p27dNf)$~3cHBm4%a2=T(2P49CqRP8d)}ny@ zgU|ck%zcs#as4gP2!M2N=8)&qR1{1}H_4a;2Zdp#+DD0(iZBB_YvMQIv?={?xkQ4u7#H8#E_pxGUyeskc%NR1XavK2jC;qm{?fmUC98Pv z@k8D~Zi!W2gF*exX@gMM({mv=HS?4%lruHeVqte_ebN7;X=}#3#s4ErgBka=LY(f| zIK;8{Xu9myqNr)~Db*L0VD9c?o6JNQVJ5nq83^ZTL%C{QNaNa4%2bSti_3_ydnsE4 z@d3%YS$(jO;$1PUtQpx2k>h%87H*#%Ja@a-+Bcvk%8ru=P7NgwpN7doXQbL&1e5{A z=L5-_aML%Z^cB8-#*G7Sx(Us6SksT0+F8<{R^asNQtSRusSk>moJ9yyO;fR?c!!yE zL5RXd?PXIYjidZG&#UVC;k$O9%Pg5VbPIkWL*-{tZZ?%whFgL4EL1ue%Q@qadKO@p zQV`2@QNAK_&7-w4mi929JlEpz@g0BV@|q5s$xQ~tK6BRi^;zf{jxpE? z!!Wz6pua4bK>gyPI+VYc^Pfc8D(bh`%Tz(B=h31B85OiQI&&*(;yCa;&JgI+h*Q z9(=^xS9_CMUj_8xTIDSWyYgaM>c=o>Yp%}RZyiX~e91m={t>!0*R`{zC-^5~eDn9n zVJ3@fBv(A0S{cT7vxxuw<5?t_X)WVdJikvP^N0h>-Ns_DGseke;AeNz8|dllAgn<9 z4?1wl=wxNn0d^Q7%mH90hZ<4rxSETeGi<%7__c$igFSx_!=Gt051!7Ho!{?2v zFRTyfljS1ry&E%xaApp}0^aIV)y4po-_ODjwbsQZAd!_6)xf9GWaSAkbA|fKJ-EZi zkY37`WT-s4Dam6rJO0U10mpSoetxiu`o4VxeAJaSKW5n6|BnSQ;tjE}8PAIpcYli^b0&k@CYoLW`uqzjwPOiTWh;tDB8*3-J=Up z#jJX3u@gj$lWSD9DG0=1Osyzz@dF^gWplQGd0(i&-R@S(QVF`uxeT|Nm-WPncHz@IH@L2~&F9H44IpB)7%pi0hI7TB zu(r(`$}$ub@vKGRZllYa1iaCoqJsEtpsjy~_25!=A`?(PgqCp<V)g*a2yFpoWP*BBLF=J*@^>?{cx;=mCFi*H5s}ed@xs|J>Ap0lOHu z@vujBs|sguo%kF^c=l0bo_DFs0~*DC*OiWSIsZ0eq}4Lem=^fAK({cKJ6G4;;Ik7# zCHw<#UQNm9rr?&>lS}11YJWlz<=aycqIhc zI2yizM%%IJjtert@J@&g<1(lCj%G(31*`%WT%@2J<84T`nWRWGs7j7n_BD{LIaVDH zae_oa3?O>@LlpQERzMU6POoYixpHEQ5Yg6b6d&Rf&O!g*FHWI!?q}tcp0~6b9j?A0 z1vu>7`v)U4urmQVLRia!Z!+3Z&NV<7sAvpq17pZ6;}Vl_BF_@<^53hOEV}TYG2b7| zghoFka5U+w^DL`}er}t(ZLGA*-fy)?oW*b*al238bKaI;C?3w_zluk@&`kSvFw8E^ zdsn$RAwJBj6*LWUq-*o2SR_rySyL^TsoqjTO^-@XDia&nX?nhh2B`Y$A}*B`YPZLCOb%%S{d_u^0t?^ zt7hm`0RR5@{ANo^D7_r0J9kmu7yu)JDol%N{16O2$1x}y=5JApjf6bDC>KN8I@e~SEeGRZ2Ev2ONO`11xu&5z5s^Ea}YsB#m2DP+HhO7M;a-N}F}F8vT{ zuxQ#oF>x`@&EhSf%U4<-&~2s3eRS0o_d%pWiY;!ivcU3EbJ`Umf(YV9r|N-VR`tTB z*~YtPY8(kU$s19LYL5IDzGO5?&>5BaGR$=2Y_BjLw43L(QyesNDmzlWs~HmB?Y#I& z#Uw)GJqfpXtxW9kKC(6B{k4}_s%RH)bk)76LFuK1-W#n~x?AX+pJz+Zh(vt{_W^?$ zI9=xQ8EuxcLq^nlLe^WITfK~HF>Vcm1=#wIODFg8>Nl_BkEpcmV@&dHwteVPGF z1TPHz!q%8Ca)ju_A;g2HlVk|A9(H{gn*C{Sm4#&QeiEuZlQ6W z{B)lio3#EKUBgB3_4uovKhu(uy^1gDBMjn0opbGkdtl~M6J)@0|DDmJ3k#*Ee;9+x zyI{DH{3EnB$5aTp$}tRyugDPY5dB$~kEA6_J{FStDp_Iz7zZaGWU_lp*{{;SrIj7$ zsH^E0ffbwTn>L99D1H`~mSw)IX)PtUqknxDDXSUR z&iK9CiEsX4Xb)+DjI-wWB@5$?G{(*pwSJCRGYM^KFmH)7Lln&yVVW90(v)-x1ba=q z%4z> z2fFa!msY|Nh>aLWX}+WlWa9+Tg`3K&ON4Mn5n_Fc$kb57LQpA8jqUUVcR^^|sm5d9Ncyu*@R9a8M3LYyYWncqz`v=b z?lD45gF#Xd`$_S6e9C}u7E2`IW?4cUk!2>;3=Ghzw*lXiPAiPY(YJNz_n;o}qwcx1 z_LX-|Q3_$xtvJx6TFcHP-~ZW;iycB>EMnZK+PUvfQ<9dC>FnJq3~|6K$&7IW5#pZC=T`AKD~^Kp zPJUCCzqGP~fXGa%3nX7n8a7FTLjm2;Mry1 zPGfpFPT?URh6=M@ezddojD|G(tGC1Ue7;zYomw#~e8S>H985Xs!=^tNF6xy==oU_= z^=G!g$HPK{{RY<|Sm>q55FeMggWAJUzwLpC^4isIDshUGftQ3%X^u9FN{xyuU2LdNG_i#)N z>0RK8lA3zFKLo+elWP8|>)GIPwceBkal3Kb%+R9eh;lB5=d7RpV^`l%QVWFH3d9tN znty}s3RC_Yv!(tmtN#3gbGm%CX}hWZ*BMY4qBx?5IDpu20aPCyG((g1` zqI8B5aN)3S$%UwQWg$roB}oD$wkK=bpxx1`A2KMm< z9cjx)unVeO(7}!0|AID!gq$pE=_dov6dqeZpoA2O95UB3FujQQ`2eRlQ@Vlksv6sA zA87DSOCUH|r$i2^9P?Y6#ZlRn#M}@9W{HyjR)#TC;JC*!{hZHj83KDJ`~!4a^{_I6~Gjy7HM z<-J83*WZD2B858qhm{w4bJ6nq`RP3lU?BQ>_RnR` z>A(~(>PCfq`t90n`ZO9G@5G=mm7XYnO(4fs?l6le{Gnx5CBfNyjY0U*7b?vsEELg} zN?RmN#VyBZ@~rVvcF=o_#fbBp9q(q3W60ZDR&um((#JCCrSsuvaw?MF+Wx9U{RU9r|7V9VUPT$3@qc2!1i=u` z+Su4|TqJ_zV?ssYr$RvhX`yIO2J-4_KZsk6!FpnoZ0F?fC6`~2m`n{mK7DjpN@*Ul zu`3(drD7qf16wx_FQ=c4mb(2X7-Zh$h~-lP@@17VZxBwuukY=czElGk3GsPZh)8Y@`>cpX zYU&AW@9=mh`Hb!x5+TL?2E@85zZeoFLBLzArkqr47?ls^u5xLXh(DEiD zo$~^%*a@-J5Bv(7r@JC>FF$NoOnlb~S5!2mitKfyq>(StZ6Tw7DnR4%g0ant;hr>v z+%O*IAN(o=u@cy%ja1#h+B}|n)1rNVBVd@T120LyggX$}571n*O?M@&YjS8TgSJls`mb1Q|Ceqf*$ne}&yitA$=70sm z&$=fR*3gSMXI^0pbEbRkcbK0czm989<5@a|=|EW(2>P)M=H~oWt^D`L5sgMplOHd} zEG3D9hHp8lMlEf$m67}0IrLPt!D?WJOd&2#pz%iax@M}Qe((i58K5&Klfd8eEtnni zL`X-(z68NO)~z6BdVqYLZ!Be!VKw7)kZts4)O&Q4@fd8%A=UsvD&w^JkR!K0LHY0j z+C#Ax^kD?ZwZ5Hi=m%~>3dIZS#lU+SWxftwBKUC012-B-YL5I;GuqzErC;^8v$mMnksC=!}B!eOn{;{!nn{XcwP1^ zb`}m_t6*@uzo`0+ym|U+R-O(~8V&1=I7_y&JJkFnj5eoAS6J;ivxF%7B*Yl4ndBz~ zbdo5@p@NGxoyX1br6$U2=q4=zF5PXrJ>vXrA5(@U08JU3KZ))qLmzb%BGWZVd=N_a zHw`7DuGvkf`G*=-u}g!s&p(`d($CVTmvUzFD_T4J&f`%e+oVKq=VsUc{snS4@Ih8i z0pQ&{lU1>kOUfpRN!p}}#FCL_*46R{h3K?Osz2NAG{tL~Vf^28)z{!rRFEKS<>}2( zPr!(R@-LqEx4BxTcg-cPy5D|&)O1`G+(mYNG3XmCXNbKQXHV8ZdEi(3h|GkLu`1XGf6_bn`2)mG?SSvi_iDJ)p99}yVOvW|D465;pXv#koIZna}o`Tk8Jr zD={tr2WHT-QIH2 zkSK_Qt&)R_HgBTdw0p3rNu22E27_fd5yntKu6}*GPqPo2Gz5E4x5rc|8hpUTK3Aw# zoef7JZt+*-ju$1;8x_E4haxmH{6v=4-`WkG+!(hM#2v_Fgy26a_ z>HpArqoy+al|p)f1kM5Jz!+Mp@V)Rvu=&`f_ptZHP9`IbVHzo7{EK$VMh28w7jc== zEwn-d;Sdgtf`%+qfH;*Hkux`+VgnYppd1f78O6E#x>7S8?=4wm98y~}q)!1mfnP$eaVjxfhL z7C&dX+oI8NFNVX&cCFEu&fC2f2+W!nbZKjjSf{~a5V#kV<_!jJdx@emwG1DjhFPik zL{YyCyndtGKTR*oWBvy7ZS3-&RVl;El5PfR8fD8nkeiE~D>CAGL9>+zeXRO43x$X) zgMG@%e~!CzY<-Vze-3pxf7#0tGE*4fSn8J4jzI9<$hC zlk$X`B1JT=1te5EmH2m`%U~Z0-u_(sYgt0_7<9iMGTSum%#6OSXk#dFnHLo%bU7A)noYHS|Lf6`+m)vwF>~XQ2|8-V1>7a z9ujeuQ3VV$V80t2X9o|+#8+W#b(FWtUK)r3(@kRbOA}GSn8eWo@S|+xrt5nL>1mYj z5`$OF+0I*u2R0IrcPg}tU4#Mt0-O}dJOx_|{w0kLckjZJNHn}TjRxqEmx_bZTQIh7 z#8DwnWL*|<`la`{81df0iH0NFbI@Ybz{h;h=)&OSSp9=~I6=Rjh$>GA{WNzSsKW4D z1p;`gLjRP7bV%Iva+F6HL_kKNj!!c}vXX$aOpg2vgDy!E9?aepekCG2{uO#WDyZYj zaEP=pW@jvUq5AEUzS@&fkJ`Cf!-g5-E|X_tPZ@wYh;-O5FTNU?dXd#PJ4gNRYKo4+tbE&GkW; z+nw56Tc3L7r)927=r=OuaSH<)y?XZJHYv~Awn36deM>GzUSrYvKKGZ`e_h;=T7D0vr!%Hj{mmf!JK??dHy}6ZDKbN72yEYT>wf#+ zdROJcNO!yB+u+}6)HgnQ4^9(S@^(DI-IKY(l)ymn%a)Cw3nacbeIWd(6}&MPLRHT% zu0*rPmX}m96iI?U4D^vxA|HQ{Fl6^jzL}*fGh49|Z{Bw~1Ud7$gYS3iEh{3J3S8=T zaRVdBa@L}!tEQkFHc;p(See06^CDceDc){Q=5>64!Q+gPGrpFiS#41Uwhyj%MC}9& z1Ut*@V2NP#(U6)S#CoP!a_57926hWKBwo$@#e4z;CrknL+?x=qrvF;i(iB~!UHYeL_S<`!gv#Wr2d@*#X&LE1p+eKsC5;X^od zsYXs3lqdbh{CSk1ZAP%%x1fZ_4nSvT@IEw92j7^VbB|eslVk zgd*S$+>bI+03Le7FT579Ud0qs2vbgW+V~EJL42K2JZS2EC=k8$-LCvkR1@_RUVCUG zi)8-sA40CYvEA;veKtj2WW&Eru1xQuuo`9hv8|5EIb`Nod0iGH1<> z($_Venmt|dmlPm#uivv;^u)^B3okx}hgEGd{prEk35!{S*fYsZJnC;YKQRauL(|gw zG^YPVdo~{tBlwsDT;`NFJZCfN4O0Z8NkXKYsaYE?Hkz~L_8m@&;VBeQQ;~;h-$_HM zf};&N+Am&5ds3^rk~nZ_g)3@IDw+25*B! zq3cyGj0lVfB5lJQ8*32s5dD?4nq^F4fyRCDj%g#7XQ-^EN+Dc9((Dc$$5k@0Bt#4! zj95K|8{#*7QrNeS3pOa5eE)F~S$g>q*r9ra6ua76Tx8ZHS+!S z)NsP~x~Jp}H6Sx5rTZb7`IMw*ci$<<$~Q7zFqg|dqnDt5`OhCBVF}c>X25N6yx&{- zp?~(LG_D4iUp5TBzw>6$~8#3@dar&%k#Tmk+;3T!$7_rBQ>4CwaqWh%XX zP95#EP&GnSCY_(37mGFAuvgrm(Do5u&`AMGAs+tt11b}N5%L5ga=3lv6JORuCv{az zOc{8SyFp)1>q%58ivn#T<*cDk8ooXiUAH}ToIgQ3=l#4F&J8Zcema#C)FSo2d{Ry7zw1#*;~4tCj{CT8)a#VcQd2 z74@+Pzm)XfL*h{>d~NXD(+nTneaQs|($kdHv0^%KI=rc2o|2Wrl(dDxonHuu&0Q-; z(A*D;Lf=_BsWddi{h_dhGTu?ga@EFrn!gI+<$8^_(an3w_)3;V4DDTo6PFIQO^|i- zmRgBsrB}h#AtGUh?)NTbk{!d-rWXv^CQ6bxY zKjLowI;LL$?-gLdGs8i`*A~|HR@qYNiJ(A|P}ThMw=IuRr>B3rwGlC_QA$wef!g+k z#*`gr6tV?d=Y{EPLu7$n$a}~|gob!#kil&NA}(G6LITTS^59=PUzI&CD7b%}Z%d-| zu`CQ!*3-(lD(goG)ke)Kw}fpUNQLqPEv(il^0RbIKx8y}>z=rqL5L{5Ni2OhqMnA5 zd{ijvUUj@T;3u8Wn-f!4pFr5~_A3J?RXR_Z7bRsRScR2w@ai}ixG{B0-QIxGU z*fN*WAcxdq0bRA}(f7qN*M^*a@~jmQ5|qJrQ~`upp4Cni;QTYCqa8*RA~*ziVc@|` z#gm1gsV{dFLIgVU)GTK}E9XgFaANhf>}vykxjz3oZieXX5l48V3q$C8k~HdlR?U>C zU&CxX3%`l-@I#b%VvSDASxXTPQ${UpZ>p*%KSRIrmFTsd822jT?l^XBBQ9-~Wx=H3 z{J{Ub`TyINp)ljW2t%DaR;P_s9?Z6k|AB}_#C~}A_sj721XWGI>i1f-pwBa<;kCEz z&tK}oUEjaGp3>8OzL?nQzyy@^_{Q`|-pKX2xg>{dAhm9xR1z4_^|rwk2%9^XBA|AK=7&+(_ubpV>s2#$v9AFuQ z8^WK5GEag$Wa=_6KL~IwQSUu-80z{L&bF%Kq>XYA9^<{!}mR$WGW)vlW2s43>IX%-i+F(FW$I;4!^~x6H4`n zS5x884}m2$`XcRjY&beeBpuG%T3nN#o{-oL2d`8sQXpoldqsy*Rq($TZ8MS}<}5)x z9h2pp5%zzG@ncZvBJ4zxquu}BuK#QNSIp-c{`J!N2wAdgDzm}1j>HA!SzXU{jT3$B zI|zOQ6pkhp&^cdcKg%jYBPO%{Nrx23&CaM6m_ZF5_<7pUp>Oob+~2{KE3;hqw5Cy6 z6$h`6j@%Rg0m;lYE@S)URKyC`qOFu`83adsFaivq$3@l$FBgFut4A*#vvWD)=TmH9 zJz4<%5(>ajdOCk$6g*DM&6k8$)$CaQ=GWjmENsmUml1OwupyYJ8mX)F?vFJ<_G?Ls z^^MVLxuV1>t);Mv>hGt3ov^?SZ0hKSi?D`|i~i^!fjK!uGspYJS0b4jgkN>N#$LiO z`->fgSnL9oXPB-%ivD*@Uq7l^jp(TDOa|D^nLvIiPLdiIW}cwBCyrrQDz|`ju=wM>L8fbDZn2>NgZX} zgIw(lhOxx_&SXUzthV5PZ3788sGcY|yEvhNf4f0q9v)7=<=@A#68I{$-F+>iU5U%Y z1^2LHCIQQiA4o*1NJ_cPU-F(n+v8D4PbN1DDw$oRd#Yy&b`ksww_9AOCu4vSE~BD$ zTGN`VE@lf&JBJ>5WG~+!l$K(`m9&h=t!K6^ZWHnykLL|(m-}FGXPpv~@Q z_44Hi4TssFN-0+;+d%q3JPg_Ti|3t!nk8XG&~=ZxX+k^pC?$HuD-H=`lKK7DJhQ#O z4A<2bt;`kWZJS_1;#Z4%!h-&M5#x?VTE`$=b$ccmN6oWANfkUsfS8tx%#kqq&q3?#63s5}C)faw za`7YcNHEv{8-6!`paIKTxwTzy`2}I>y6*>W5uoR{Z($l62UYE^+_tw2hi_tue8*OZJiyEYvYGSRVa1UTpnmNqXN)i%{LsPcGilB z;^c6uhfgVEk4_{>jY?>zjtGng;wEZw?4Eu!|F+wkmP4E2(XNv6f~&LAZ8e;?<^YNi z2O26mRS0z@WSyz4!o-uHwxSf;#oHHtz0PVr`rV`%P;ZQZ$!ypu5C;pSTNOvdx_W?b z4b(iCy`%7jz5+^&V+m1E<^bsYzgTVNJY~dMC1hE5(U4uVN*rGWvO)?QvR^~%P%{GA zdk;hQk}xx8fsc0<`qrXza6wj)}xvm+`|$oPkP%~PO|GwA7<~h?*H6k z-Ck~%%ZQ6_3&=IN>NH9^m~#Iq1b!P3<>}rh{tEo@ecblDhE+h)HCZmjn{FRJ*z%3Q zH|Kk;2IJ@!)mrF#uoWJJG?8NmNVjohmcKmCSDooS>^u^ zSMU5E*8~0Q&P;5hv28SVLata%cyE%nM-N`o>Zcn+3q(oVazlJY;}XfUPqruqU8UlyLZWQ8I+J z=N=@CdFv|Cc(&!*TcQPYpyBmV%*x^WhSIP@uw#S@#>B0Oa7$O=#oM%xYQwzIss(j^ zOJO<`zv}ipNhOY@Mn%HD#2db7Ky5IDX-*))xn!98t)~YFpSGew0W#9CM~SgA-;t@R zxrG8R>9wdal@c8j{v6j}MvRZ}aKk=B^uq*7`VVGq!w`Q}XcPZJrqr(Yhk0;=W{pOu zV47m!Q9#yF6>p}9U|3+nEl^9Z)vFEwBUbs-L6n{{v8@$rH zB0j7y9F442y^mYIv9DX5)#E>in&UYT6~dU9UzqjD<%h=%XZ+6#cMtiYlFGp;co*DZ z8%Zz7+8B35I&1|I1bD*b|H8}OUJry8z;}|@*Vo2JGlkDlTH0g(RTgbr(E@Uw#Nr)JLFI|RRJHAJb`{48KERb#Q;wtm&==(~gJgsOK(A1>UF zJJ|Qiy8EC5n!m7+5TdfWMr?9=S)aLUXE7mt&)**v1PLn1HT(e~%|q3(yYBj`*c)?_ z-+GJ@zs-kW3y{PKs!wjC$O%L4_+9-lvBh7FTVXGje&{S7(0)oY%!>ENhwByaDA8WU zsEXF|I5tR%FDFF8*7&6qpJs1RK{Q=W4ust$)#>i-D`}VCtkk369gLbQOTS~IwR=|v zWM*M%b3ZL^*U8D%e;`3z0i+0P3B`;z6i|LU1D=^W2r z8aOH{V*@|lg7WYlj7pmi1>FF`*>%6^-aYLRm!Qa zSN6F{-!Mz(vPG_$?4J(W+#?SSwANbex&%HLETR?|WOLbO>G;0gHJb#_=K8JdVM`1{ z*U@mPXLH-zP|I=gkPAnV{|HhDW6^v+7YI>)dn4DJ5ht0Y+*`4 z->GUh{F-Bi%(cwxMeP_`a!l=e+uzIk%YuVq+=J+c*Oj*3vR$_`vUj_?f>wDx>nsk* zhU-p|wQ-QfR^N2E9Bc(DGuAXHzjq`44lIsWkiz#F8nMRwXt?Gi{(Nf6FgIl^4FZ}4 zgJ66;%TUsfzQbooeinaJMCzA?-pn9%6bY>cuo)ZxYX>MHJ=mL`Ry_@P(#~l_$kK|Y zX;D8L0jF6o#7F$mS@=*;oLUd=`&lXyg zr!lm9*AY7`)jw%ox8&VIjESbt0_@q-_Ksfn1f>FhGX>ln<+9as$S%96(YkBDX=82E zj{cSde5gETM&gMv(yoh|~{tlu>upJAmLUxil&d@e`gL_z-O2JHejn zW9FDp^At;& z6~ep;mb?}O+jpL4i`B+&x70$FzQ;?&8XoiG+|hY6D3vtoy3UEwCHDrdYtG59pB)sk zvy+n@_~IxKVz+<%UkqAT;G<-J8>&$=TSiotpP~)Zv2q}=np}i{F!fJpRz&4bJ#GS8 zK)kiiw|GMi(g|mp_Ye$L+~3(&L$))($^4rep`o8xtfoMnJODHN7$`Q35KS*Lu^5X8 zE*ikBrE7!-`u>&>l`KMFI}_wTvuVvjqWVrAUGZCDro~%z-V*w(A)` zki?JdP|g%e21f%m?7SK6aX)d`IF@dru!)_7kb@7>QTPeftEg%SQ5%*&Zk52k9KVy+ zNv&T*JPN#Fh(tp(#v*}HJ zI^?3(lq+yOl^3xoC|B{0%t$~%Cw-Oomt>Ed6%RC`cz^&|7rhy;8_}a1!r32MB+1kp z0CDrX@QbqmxOUk!gx8>yVqqTpSgH)ov`(z|2SbgP7`pkjtOp|F2#5 zSCsd6%RMS#uFw>Y2O0eWIZ0k2v21v{ESqF&Qw@LSVh_91II(|!I6`djD0o#uQN=Jv z1;v>k+)_wiQR9qE5DOJPVtD%A#npv!J_rdh_WY=}YCmfNv$z_uN9G0c#CBNKaie48 zicfc4VSo(L*=CGq*658#)!$39ZbVsXUQ8!PUiH`fXAmyeES({YU}O(OoT$UVAH?+2X)=$8QMsE^NMd)h&cqq~BVdfJ5|n-GBEah*hbw z#c-+@${Dc4a>YD%e@muF-I=TxJ3#hO^A~d}(a}y>%5g-P`6!%*T11NbvRzDy%TK;m zD?8pB$V>&G`O}sC8YHYUiM;XG3u6+fb<4YEsLM4BlxehC;BD~!M&!PS_Ou_kk+2`V zpH;j?`t7{^_(#jZye9=axua8^n5T><@{h&z=51m5E2GykZVl3h*GU00XxQ`13xzXM z%{yOpX+ODfAP5Q#(Qc>`({Iou=A^urv)fUuHPVKy7M$1KzGsq{5iauGx`vk{Nz}b% zR!x&jzxsJI9?kcK^AWcD^`|htpZO`19-VakIi|J@+~M*^N>yA;nb2cw4@q$$wfa?v z;=SPj#l`ZCTSD%7vo`M^egJf}`X@7(Qz2BkJ;d6?*gzF>WibZhC4}aSpE6&G+G%*c z=XNHO#3JzQw_pkVA15Ng3KiE*nG|bE*=Yc48JQ!N>&wP(&kz8^Fv7NztR|6WAZWNc z6}SeFY9}SU5Q3Z&F^FuCknCc$ z#6sj1u*AEduapxErGVlbaR_Iwxu6EU7m@6bsHK-F;flprWiQ?)cbZsnfg7 zYN<|~&+D;9cuPhzmdckROS0G1(RNI@nm)4Ot2N}cI_}>MlF6WO5007NSqV%H|1y$N zsHEYfw|7_Zaa6MYCV86eOJ7DXZz}(W_qhEkCOdaL=LJRfu=dIO{wHSrPe1Y_pL+0R zr%v$c2yn~$6zHwo{&v4IGXsL)=C|?kUYnbCGF2%=*NUKld52|wu{1teBl3KVSzZGwa`4C{k9 z%T4~u;a8w}c67YvT<}GIG|H$AH_XlYS!!CZl0gNF8j!!)5k9Rqq4ps1=3=?YHzIyU zs<JcWJ$5cH@wcPsZh){+ny$rA8bV6dOxhk z)u!LeCZ~lOgqtV{{CzfLF6v`|yUd=v4DBYupqLw8?le9T`6lOaO5jltoC`5pceKT0o*FrUjaOHuj-kxD8jiwCFAuz|z$jrkm6 z;_BMLDjpKgWs}RTlO+--5d}1|J;5+g84Ct- z7a>gd8n|ItpvcP5w<9W+ERL!OoL~uT1<}YtJQ*blv%yrVX~TN0f30|z>De&@TA7xw z?JQwjt3sP}(QyUgb;u=S)Uh-FU-R+*eb0u18R~F35fYbX!a>U-K@-a%=*hZTu5V$r zepU*nCJYB2hr^#;Boc6Uc&|}{`eU<|yT`_Jxv6zE*GQWqGl!90aFyAy zxGB-UF_l|&RcOqDS*cYL9f%d6r#xcw!cuucSFW|{c;mHNnJjf55LR;clnefTDLWnh z#CGSo@HhHGfN1Yib?_U!nLVqX^wIhB^kyXD_( z7A~qmbNeBOkk}H9(p=nowQ%*q*)RFh;xm$b39|Osa-x1I>cP%WPO|KBX2j)bm1D@D zp+Bfj7*NKJ;6sCVSNZtD;78eP_zutN$E*V1^0D-?_wjoE5z4ix3Qsz?V;uXjp!`8; zYvn6;{<7@tFgzxk_w*1qB;~P1u;f^x7c@1qnzuS6)VM{P+m!Tx(q4^e>lSl1cN9gZ zfwzgRXA0)a)1Bi{UKhI_#Iy`Wan@xcnGI6@B&F9pmyeFkDBt4_H(?FInzCXeyFo?{J zY2}_Jq3%Jd<}Sy{>dh9`%z8Vu9sVvT#HNU=b3EnbW{YXYY&1rcQES{VQf9*n72fpF z04E`0Yk&2B*5gX>39ILIf7-vYqr>?^?ywg`Aws57gtwWMF4h;*8&E=pljGGuQsDNw zS_L}U_-X*L^NirAJt1!Eg!yM}pxq2h(LJ(~Z-ztBuNZztyGI9@Y0H!@&JKz-KZ%^~ zr96;UxL`94uKJcjl6n=*3Hh@*;bOnFJ_y3~urvH~nT-_$EoZ7>w&>w%3BO}zVK4*= zpZKHcG0eEb%t-PVpsd-~i7WNY;h6IphAj1U#$kZDSa`hy9P|s*9*g{IIstdo z<;X!bf73v>@wtd?Hu#>>6%RJyG4UX6|04?5%>$4{a{rYZT9mL6`c!-@$5A771Ku>{*2NejbswRF~j6Y(#-p*{-KkZcazaKle1$CbE`xnq) z5LU0y$Mr+Fkrkaa{FoPE7-x&+7pmK$J*Izho517h-`b)>v;p0~GTeV#6$aU2|M9}Be&1CVHd*<2Z!^_^9>&6@` zUfw}|v+&fYPru?opk;gxl%PAZ|0e*T#hzfgu9dUO_A7k z?Ajj!E}Vd`(3aQJmPG`Y(kln^rtn zF82&|w?!9__{WgHT4@sp|H(u`;c0MCp4@YXsIK5&mu>gO4)fi2)ECZzZ*PL`?>dpw zSunUA5s!S7Q;+_SG3}*(3_X8jrp;4AC76Xx?;%XO%fS9|NEnZzlUutRGx1!dXXRQb zSyYSCpACRC{${c;uzh+GXMBSKuBvbG>Rd#W#W`diUdr_=h!1mW9D{Bxtf$y3`n;o6 zmAMv_U$SEC>MAA@_?OmApAVn%>xRMqzU=>hC(JCgb`qGl_34Z3dErkr(*9_9-E?oP zC$h8W&r}SuJ*Er~;bc$h;|U3HLEzD>j05RS*#(i)QNz#a$%!DYy8MUKzMY{#^w{Gt zQr(dwO0M736tF(~eDw?F4zVO#hhudH)06rHKdoKRn4F|io?P33mS)p&mx`bYVEHb- znBvB_EBEVCzNLOe_~Hd|fE$(f9l67<{7HfJ0;@QR#ycKy+37nb(c||nluTcon7qHr zsOAFqTKy?CMEquuH7BawcOnp&^q5@3-1qS*4-4daOOu~cf`%Het$)J<@m*dGSBElw zN`^W_+@3a7zl-P5CNYp61bwO*GTIe$EikD88FT-EPBqv$d_ zQcZl(Yt0)fpe}xBPu@`z@#xtuD=O2^&FFwSmA3cESiCtHN9H2dK!bDAhb}ZnXVks8 z+7IwSfOZvqnu~)KH}Rb3;M~t<-m&ju^y@UX&;5Z>rKncQ+w`W_a#7@Y9{u2p0-nUA zF=p<`w{TO^qzU%!N{y_e1l>ySF~NnY?;|=^%&bzMH)f2*kv$<|5UBKG&YQvI~gbmkF^bt(&62 zShSERuPB*I1euTbhpr8Pd>V@HC&c~cBq0&(*?ere@93lKCmi~zByztVrrz5MAwK_= zjH+RAhGB7aqS=3dmg{dfBW&007}`Wu1nx2s`8QvwEh$c%+Z2aIuT~?Uwk1ac_F5a- z`wfdS%x>BXF*cSu)fS{aT|^bYa|o2Mu(S-)1EX?gVXUH|1RQ;lj~;c*$sW{O<}P!R zXfc#bq_)LfFiN#2aOBNY($iyF(DSVHImmQ9hJsS?QgZZWnAYUKOF2~gIs&ieXmKhyYo_KQ9}LXRFa~Tz{CWwmh@h6~YxSP#e8G(`i9A%L zIt@VsG^!IgU^gM%@zg&k zAsXo+YAzj-TnyqLpphg90Dt|9(@F>u-x#)mT81EDH3W7U!k4uD>as*m%jT>vEH%+1 zfukrdRQ?MR=4OY9NTXW6Gas9Nj;D3P<#%nEO^u_yp&n0;8G8wpW6TP_7qU_0Wf;Jtz)%QU|3QTxLky>bZ$9(i!F+5)f*pzT?|?i}oEJkj+}|GS**69K%DCNs>oc+$=?GjjCD&< z)!xxBkIwMYl5;ss|J6gee>waXv56RaV~cCRb(ayBU>V}KD0?g1Q)Y$-OIu)!t^612 zPtEf--LH3u=O77NK{H>3Na(ns$vb&Sl2qY2UVizD@{CDXGGwFQxw;_KF%kP>&hVxn ze%it5e}*)p`wFH9bw8yq*6H4Nbqy*J!ey9nOEmZnUn4wHdOmG9|DT!(R@&sx_k;7| znZz4oIDh)?&$*=UqRqFmFVW-&{wG>mynA1`XfenN`k^$yvRe88S(8&S#e#ye(;4R; z)wQE!vgfiOpWWkvwMRkDgZ&rmVnRJS=G%oPhxQT+;De0B)NK=X5MmRv$l9pFrQ-Zp zq?((wP0TCb&wsUdX4B(VjDP;2rc)YE_GmT2#+(X)!> zL-h@4#R+&zxey1Ek$dt9=zM3!9O;zEb?aD7V zy1OS|vGt#ug{1r!R;#hyRsL9KQ?W>+zTw#TdVuqx-08HI3=Q3tlW;>lfurT0_o+@( zD+CV$IxtPJX@3fIB*)rMoKBtoszK=M z$%omiw)~*;^*EESJNa96DE>-my(k~)TOj?p6N6`ir}!jg2lpc5XEKz;Z3A=(YN+~8 z*kw#Ns~t>uSkK4fIEz`hQ5@YTIQTp##3Mu*VLB0Hj}a)* zM?|Ung^}~>nR^{_$vv_8b97LG7U?nb`85}N)h1D=$yhYy@xjZlnH78~n z5U!`XD_oQH;A+!!m)D&c`$LiP zPvy;(0fFjGDC-|F5{YuVEZTBFQ8NLE%}-CFjz7P#P{O38=4X(4^)EPLv6r)u!mdo2 z(BaOVv2+x9%3*-QcWA&pp1Ez8{5V>}doi9yBeU8}ihQNCc=J~kM3#B^6i-@>gA92T z%ZAfX=TI$$EgKHs83a*vLL!W)90TjPuT8MMZnsD4mBp>&oE=&_L+C@*SNX|#4?ET5 z2*(Pn_8vRWZ_hm&m5olPrT)n{0Ee25{j7&yfQU&IhI0KjZSlMB zhS?SOUyikV*mb%}#7;9(H#wMa8)t#M&F7Lr&(pG=_owc=o*OUgckxu;N9?!>>?0DI zi^pbJ<$>oyC7UttIXwY(htrb8Shv4-M3|jPAhF&*qG9`&V?p0zlu6GF`%6Qa+7ViV z(W2HlI`1lNr5zmVkS~};W1$6nQVfh~z);=He0OO*W4lhO~`jDP+ zIxnHtpy}@q=uiRQPR=hBN#j+8Tx^h!3#3ua&1qrc-w=~U2|I3gp zA7XtFZ~KUu?(FkhnH93%H}r6W)gwg2eY8kaxL-koz-1Tpo>93Z3i-bW>&MNNWE&1sLHTGhalJ7xl>yLR34txH_gKI@BflQrX!R2}(?cvst)HWaf-X&gc=|Wh$6)sXa|X+;rWs$r?Pp0Xso* zNV%#R(NZL4^=IowWnTFfVGr_lPgD%WaC}-5B4(qFQlTC$Yq?20isWf!>ib`LFds#U z&*tAWVE3qDZD6*o7;$cf|Fz-1SPSh-2>T0Jw`MC^8yMCy{d%Ua5xw;r2(JdWg|TrF z9VMh*nM>x4zZr>`V9Fgi4I>G*Hczjx!l~E_bqBIXiG9V}hPFY3C-wXZrc1 z^?RGjNxo{XP?2_{yqSS_qs?!WPe-)(vtz6BdQhCk>BCX`g4f{xT`BGU(Au3-XqvVa z?Z14UM}y#-8T_IA?Qnl$)P_L5P?#uX8SAob73e8P=4pc)`j7`p(e7Wj-A$HCcV~%v z_Y>MnwifG2Q4(zTexpXp#wb^V&2sqn6rVI0`xM11SS`>`=Ob?a_#l6OHqc`^*c*+hA}WP&+lU{mNjm8 z+HY~l@3$S}uYm3M%C+jgXhW)@#y&;cQYU5^VvARQRgE^MxlIGFSIsnt%%1|`c^jY` zQLZ9I)2PpiMwUmid-IgwAYr|qQE{0^R^TgS;LZ_nCy#ZZxuL4s_D&vrbw_SSVD4T; z2dh8bP1CzCu~!g4?C3AjU#_>iI%qJ$ss5&ME&T0zf7jmwpY;%3BKWiT`o3ITH-=Xa z$1Ta`ajLgO_6UJH3|V6D_~u97CRdp!t-K_(__XCqXw0`=noaf}Q%4*}6CJA(uH1J_ zWTq2JObPBO^0@{c$aX7l8J7Y*?#Si;enc{X`HO1$00)__7p;+f8ZT99d=)yvWfePuI?m>Y+)tM{ari-W@H!)|22ck*8$3w zT*}B^fQ^;6I8QGCVr^fF!Ji>omaAAa&oHz|sx zY(}DLKDrLOYsHz^q7@axGkNo+bw!-p>Fn?{j5(i z3Ip-rP#!YEF!eTblJg=#YsE|-lttwNAZ--!vfpoUQi>V!+?&lz#9B(dyR7= zVlvoy{WfvX(AQpexP7xmrx?NV7A0Wxc*J<0SPE0KT5xw7HprYd_5{#4r~sxhvd1Zh zo>4qp#&?oVPlb@}WTkMvKyn^zd;@P8Kfrh-B?US%XH%s{jF2PQs++y|yoJve1?yK*TKap4$WzYep>4zYVx0#)n~nr}$+&aXBI{Y|1*)NT6E zFQHJ2lVvDTo5{LuF2kQPX(3IY&!0YK|2Z}V=O-m2gWlHhVT(4IZjeF)FyTq%b7jiM zlSoTXcMkZy?tVbH)aBc?*)+dCPXyHi0Em$wzJzaF;AC$UKN9Q)-d>^i*&j6v69uEW z5fLBlQPR`PGIiW4z0GUeCKk%($~q&9X=|rX;ZFym2+~aQ-^dY9AtcPc5)u;n3SZ$$ znmp#;A2GQ-`;5`~;0L>1PBnya^Ll85Rj^kW{^1q^o^@f%ogGI$_O zW&H}c{~8%0cWh3-RD|vEEV9d~=;_5#9fTFLK&-3RT=>gPYzO4A`XMh0iiL5(Cq4Aw z%a$PML6S*t+9U7C0sN49u*@B03a`$*;cwA@E!;8vy*SoegHw>>#njf_{tM`Oa9YQQ z==fyQ?f=}yy<=_gLZwmIpfNI~a`9LQGxYP*l;-5e-FvBSOkgptq% zXAd%zFCJWuv;2DZBf~}b{RVHoau!@rg?LQ@9o|QZs3|_EPT4RJA~M%^l}o=V~=3utTzujud-yqUUN@~yRJ9}vT~d?vB|R;+ue_(2k)iz@($bYgIO5erHBa=T!g zte_0#q=tu;SvweZZ9dI)T3WcdqYoLwS# za$}R^dFRO8lNGh#BmkpX)cUbAKF7Jqeg>RJx8rlhzaeYM@_%lMLSB8DHsGG0I>RD zn&%4vTo*?!)>&WCPsrMr|1bEcTca3&L-F%Jlj|mfBFWTICXA)TV6d=5RSMI@uW!P% z(dzwY+9Xx)!;ZGAweqYrQEhj&Dt^EY7{rIGINnBFPbiDfZ1--LFCV>1J})Lpc6U~# zK0hNkI_u?GZHBxJa|_04de(YIy}W1-rKw)FX_f5Oba+W|E#C8*AI2*P~i+&Ie^y5zlF#lS`17= zBhU`WdUu;+ufEF$tnT8a>t@iEfV{UlgWQO>(fZlppPrt?&&JO_*3YfG(8dU({NRHz z<-`BeK()wr8IMrLrDo7Jl*#Ph$)@YaN$UB0b0G?Dw3O0-wIadYKi^nqwEgZOI}=yJ z@1av$qguRx=HZpwNJV~m(tH-aVmtzQ*30M7D_ew``FIq2>!BI=tWG`m=49}~K$qMV$7a2z*6z?CY6$$4+WW|`aJW&|Zyk93bpH;X ze0*XxM{7m=tqdoHhq0le1`gR9`rUFNwWeoCH54MP%E=1~cSE2y1z@$AG0{`*IB4zk z6uYOF_yr3__$_za;fKP# zC}OFcdjT2?MKw4=S`8y7V>xVm5jHU^ZBTx~oD}@Ot*FpVJi+q)EJ0bridfQ9XiCac@SAXGLM1Ae>% z2D78&5(^G|73tE`3itD&SxvEpH&?@cZzPev575r>w$Ig7`ycsx1R%b>s&Np9I1;O~-t#*VJgN6nqvd{a81V-kUXZa}oS@xY5+RV? z3NT^=7>wu2RPg|)%ZyE{c^mdaguKEUr^Qkh!S&+)x3^B3wT3~_X6;H*yejR6_=J2e zh#u{U%RejDK8QUxfEtp{h2HzBckAhl+J9EtSQnwKjJg}TdzIu6XoX39>tbVnS=9-{ zOZ(kgp_2@Q{Dr%JDTu+P{FHM9KCDxeKU_b1-uP~0a?Qjuw2UT=>AYC%kFU1ujb&<2 z@NDovGBh?cm~nD)ilWfUiGdN_(cCJJ-HD!%hm%S4e@S{ckZuMaKrW7F@F;ifC28ME z$A2cQ{OlQe{@Z%3{dkl8QL*-!cZ3IIsrA2;%=X&uLlN?VT<~0v{LT$Kk!+UitWzd* zSwMBg^B7C0X&Df@z9ns0g-G5>CE5fv~Ft!r+JA z6tG#VFMrKe*GE9Qu&NkU&a_89ZM=&Fo1)@t32{-|Z>k20&}#Kp4MF78Dj-<)Wzb&P zml{PhtUdw8CTVd@p%Wq#(RlN>AZKUEBliIpGcdN+myg@kwt+8SP~7sSz7(jB^{va{ znmD};F!g3_#_iU54X5ykE&~<(b!1?D%r)yJ-HReaaMBv2FS&n1?hCZ6z~DCv_)}x+ ze=k?*s*}JITBI7zzv<7+%p2kO!vvGA$w6o;1my*^9s_tgfRO*C4QTvHaSgs@?VW(g z_aDv&M$!!jfY_de)01;hO(`&$V4ngV8~iN2l}^^FQnITrifeTk2tGaaW=aVkm z{*OrCyqMRV`#kE(U4i_7O4S1hn_|AE*#&nc4HTodeB)nnsL3SpGStKF^%L>4R9RF5 zO9L|dE)9Zks`+}DMjLsj%&5P()ZhDTp-0f%ktdmEAUF6k-Y0j(f2RgD1G&^hsI8^v(xtGm~h<`nM6HjyDh&ze#-kH^3$St8M-O_ zAgtv@dY_v`r*0_PIed0N%v&cI7drSf<2>`HSaKJ=$+pm8h)NKRzQpt=5s zlLv>;p}dVSjbH;?5CedOhCy2^Q9T(9(caEc5!iH5so~pwnb`%k2F33I(0~oi8zLUj z(&dTA+6;R8mcKx}$|b5Ea##fM*m(bK>?5ciuwAL}e)K(<;hs6doIj0bT>M zh6b$Io!1Tb4e0pW5yUxc5iag8A_Js3Tv6VcEEo)c-(=kdK8g-%`}&`FEFLA!TdUSR zaec&P#xN*H-Yni4*M)RIK>VWC7|2W{Gtt+eA@8aIyuh6+z=j=Jh+C;5dm-n(Jo3GZ zS?Fm-xRiK@ShXLB({>tT>lF7&Nq4nXLYR6pD9199cX8{x@IO-NEE!Eg?Nj~&1;O#0 zw+Uos>I)`0^ZYQWowbP>9Evs}tc$Nd@X!*FcY5ZFkPCJek?{Ar%3!8rkZ?{D9#H@|#xv`hFcTjqgu5($? zat<+}8loOmpLhxH88rB^7@UkupU7AJIlXw@{cip+Wm?5l*iy*Wg7{n(^3>9=W}=WO zxr8FC&Xo9))he&2|I~_(-9OLzIfa_Yvj3yQ|6rKPWuH}0459H&5q1NCeCZ&Nc2l5; zx)9+PyXYnY1SF|3Y;9v;<)4bIFG^F=#Y-01e9Wlu6aB&}fS`F}J2K_mJ4hzBRm^nY zTjt2j#O?3knvk5>6x)6|mnlgo7k`i`DmJ1veZq@XYe$iDE=vXCkwV7Y)>@F96-W)U z{!Tc&UcsYqfOep{IFarf+nH`lJZ04*2a72+u1uYx!JNnfXWA4L3YQ&gFk9E>eb01`Xh!&1YCi{{a#{QH0~#MH z=bt|jW~J&_d-1+7(wjwrF~ei2n`;N9|MDEbv_T}bTnBw*Le;Tu8V03Ekp>E&re(kD zC2Q~%SkfSjiw_%2iu-jt|1(9*U$mYt&{43xQ-t=o+uxdHrT*ie)NmI5tl6EI!0JnD zD#qtW!Une_bK4VQri|ry_UXJ?bFR-4cGQTat{(WlDLB-Jx#3TS>-;y`4jZNjyvlRNiUZMdwM| z(|&p%78cfJFHR9lOPdhx_kBTQjO_)~1yVeGtPFymkH9RWgEqukz_iM)L5r4{7=*xt z*{AKs|Gu%>I|`gZw1GUMvBj<6x9)nF7}T9oYczChnqeOd=g_0j>y6!v5(?Sb(AX&L zsoe&efASJs{3-qKNk>#P0Mu3UEYrj2K>-^~+#PYoN4Q|gP(rU_2F~BL26+3Gt9+^p1*6=65~Q`%weMsCMm**>L*FYwPSsNxIeHE zpiUFDS-WwcYVL|s#U279M-XBiNh6@{)LuaID zDGf&mywmy2RsmMN{D0aTysX;@*Z?zhoaA&mY04$^7;>*xaG(K0JWYpVF(s{C==d2X z;R{T_+L6dih_mmt6fRk4prT)vb{sj`*9SRB4dd}ImEBD1bhxBsi_pRm9=OA@G!vNW z9(fc=KgFJw?5C7bqS~O+z|G!I*h??3GDoXxBuBF$`3!DHF6kceA!^Q5d^CI~>j&Zo zJ#S})$d#cY33ycVJGybaRSHVf*#16umnmY^LWino0<`GxQ9%dE?m0t0RMOsRm~*RI z-USn&0-qw?`Bl=!1|ak$_%T=^eqW=q-)f>@+=^cK`#$y&-+n@JGukP-8i-S)a(>@y z_R(NAybX2oKpi?92V|*2&Yh8C#I$fs$CdY~m74qSF|08TNCfa9eq1+UI9|31%$)!~ zum%oSBd4^iBmx1C_`>KTbj$g!ty?~to9XX|`7UbJ@2cnpk~-r>$F;TPzAF5lr ze=zRMgzpI%eAUO9G#i~0n+-^UEv@+g_Hunx%0zIJbf9W(QD*KRe%m^mncQ1JVwFRm zCmmEM57tD@BX32*d>Vl}3JzVWy!%|Aju%KhZt}%)%Y$s+&S390HeBHHZ4p9`?rOdm z*jXj;yNfG8VMByhPbBYu+wl5p|{uDV_7A;z4!eYRjzh9 z@4{k1(Hs@PYg3u#e0Y8xw1Bo)K&VJT2<`hJ2lzdM2oU8q6&xPtd-JxAtpqP^A0bg&m;j)Q!16 zpTr58aL@O7@6Eo1iG6%$8OilfL{*9bskzP+=b3Q_c@Go>o{r?fAYaemz|$xv20Q0&|l; z>Fa-#GYC4ey2RA-ztMQtaO&~f)?QbSG=A% zF76dQQ7_Ey8GWZ;@Uvr~)lm=)J#UTWad1=WIOm5xtf0{&K%(}KcTa_Utv86U#f2to zW(k&Lt#f1{`VwADJ5nDpo(fC#h`Yxlesvi0O;d|?tY;7Svsb@G$ICOh&-?zT#(Sx% zoH|?Oi?ZMR>1NN;(r-k;C=mjc6ggMAK?!hBR4)yi0t6wtO?8~YjoOuZwSo!d_ja{y z$t(=-ETl6b8a83p)qNRJd$&Y0c7G1Y1UhKX24r4=DKBhL#V5OQAxYnTT0O zgaN%|>ff)1&j?TO9iC$H*(-QxD}#|#JGFMIUjdrl+din?V7MA^Ltj)3#MDp{#w*z% z$*N?QLY0i3#*|}I=uYFmgSN-X4sU@z$pBUiO&63Vs{7s)rLh_SYZTB&m=hjng4_xl ze&AKlquy>iOaV1%<(V4H2(W4#vykZh5cUn{f@)OY02~Op42*niQ?X(*hYVqpfuf1= zqty^lS{8gY*DT@6aBYtHo8=+lQPZWWphOk4Fd5B>e{NXLr%k|L)3e2h@xl=>1^#fq zT)LdjD`wk0(+AX7$|@cmkj!HOXW(asa0+Ba4S2i7!dl6DD%;rO6An~z%Jz1<)+j8y z9vbHBTu_#YbAlu3bO|4$K6A$mmuMy0hi{4v7I2I}lb-h!C zfQ7Aj+wc6CVGBvIiOh? zz0wY_MCe7k1X08C0^M|4@22>ETjcqvPw`#xw;ukoiEGxyFbVB10e&|CpcXHEgZ8#Q zp{oy=v4P(#@;30z^9}3C6nt#+Gjp1#ZK%Ch1*MVS#dTV2ak~)?I-$3_YkBLZGBY!8 z`-wj5G4<^kLmv*L*YRV8&w-GQBH;_3-`rE@HvjleQ?1Bp|4T7Qw2MKOb#(thh%1dP_ z(q}e_OTR6CH_JxKhqu~NCgZ59!Mqfy$4>lP=t?azZ0qw%6PurISf!!8q#->xM-6^2ZlB3txL-Q&%qDZWrfzCmg{WxX+eK3O}N;liaNv$Qm5vRoljh z41=s$v;r#|mM-`K;t}>Y$}ioq-KBej=W0QA{>#W9;IG<;98n05O|P2zD=nYnTA5;+ zPW=y|mngiWDpDC}BMGh%w9SUvv3u9A_*16|ybg6lc}9`Mjlm6+#w}^7Q2(xtm(t5A zINU=9strMjpC&`HLz-Qb>QrUMVV~5{);{IQ563EvgBxK3f5L;iX4(U>oF;2IZr_dq zn2<+=cQ zywwb+1dx|ifBUKcLafFe9`ES0Hj>d8j|lplR+OE#dt5D!kB{5M#>TSa^nT90$vF!7 z5$yIyNPK@LW!@q$TNPlK+jcsatxw2TD2x}CJ)n{%lWYl*b?SroL+8WagBSteTu#3I zS%k&|kWJBV-Q6pw4DXxfn_ay+^7^jc*!vcPq;EjowC@1-X2g%OE$m56lF)w%T>rNn zc|lb+eaUdEJ}QIcF>mPCpa!N7?q&l#-T>K7$p9?N-nV$AGeRR`a3Q4IgE>NS*;?OH zmaxYn+ATX2G%n?I?uj~3#+39{zV-BnQdKSLjVUJGp4Ld@e-bgyH;=F*=JEjvtHqIX zB09ShH*s~kXgRhM_uV+K_qP!y`xWtq=q zUdhz|r72q~mWn@OK(re;7DYGv9q*$u71X$OfvyIvJ_+jbfO-sw-mCJD z$hn;rJ)qn1Z0WF@|I{ORMFw(I5GUAu>{ta00~_OL>7vr-wMNeYmzrhX_0?p~A!v4p z2_g_sLpd^PBA=GG_3V<#W)|1Lb1uCi(e#(0U*)W&I+=+7HJETyC&T+?=%BI5xGaVd z&yHIub`o5t?`7_299LyJK|`aD9kOp_Zkm&`_dRfuCi9%@^Hf}`)vW})Gr`OMv-<8J z?9KsrEBFoRrU!|883ND4Hsvk1Lm}7m49<*a+*U1{O}@4AtFMr0q^|;D5XmTGRoaD5 zAOt+~UHW=f&%#kBb$fWu&x$#_q%TCJpTm&eA6YUiu`-Sqlc$#r>~2|)S!LZzrN9<0 zKN|0HB1bU>e0u=-X(6iBiyYB`xx~%W@#ex?g_lbJ348UdSJO^CEeuh-t#_29CsaM# zkPt0q!3o|`>>nKN4d*xCLoALf*lzoy~GnE<8a2g2za*xsjq@(&{RKz7=-TKO80n^m}A%oy|h;q>i!bb)yh5rJX_ zMP?HoPMC9n{=Pa0Lb8*SL1^Nbnsk_~>XoAVNDl*2hCFr212(#5b?Ou-BPh*&K_U?~ zu1eWnOixY>`bL1imCwx}_MbeLDW0F-sN`kacEa$^?>`M`d$S%I1vd=}%fWOGq+P&gFj7p|ICI0#0EuG2^LRu*st+U(<-SJe zkF=aztStzeuOIN?7VC$+X?4H5YYbITU8y%CsN$m5bj;gg!T|CWJ!g+O8F zb~%^jCiZjzWLBQF&p*_F0>cf31h`%j=`19jXvH#v_tSC}A?xDC|(U`Eehqdb1u zuIJ_76wI0+b$Z_JV2Z&Ab?~Jap+-jKU1_eEuX$zzf_Z?RqO%RZ)oJrSG_4K7p-l1=htbfd|$-fSuTjjBc@?*-4 zu?pd?BwVzKZ5md{En$VNuiDeldP~sKwJUWCtSw}*78_pK_!idL2)L|1Em7;fav7r; zUvV`#oMzBzTC9S&@Vo>zY-)_a#n1bcdx;&lgCIldWhiec1^ILB^rslU* zC^w}|16bgW%NqjplPH2QgPzAG)KLlga#Q!c-+GgUsRjA@N+*o@a=)^cK}E-;qn-RU zf`~9>e8|!LeNs{e>8R5v$n)$j-LhYNBx$5xfN_8!1xhoen5IcOlg2pyT|f2uo*ci} z6u%q4tvs=2K`?64(@-aHiiW}JN>uWM?O7Znn- z4{HSg^pEXA065;jf}4(rZn;sg6S)vVTjCDY3-0$I`DQ8}eNjBZ+hs?I8Zgfa9fVB! zLrYH|IFG(uIALFF#S#qyZ!f8dU|r9Yzs_Z1;`~U2T@uu|4a%m(UdXVXkp%_P2wsqz zS#&PdkaJ5#gl_1_##wVx2(^&=DJTZPEDDr&7Rin2unMg1KFf=; zx~y{mZOWcn=AT#T{|*agkS__(>$})?!m(vO+Ov9#3;L2N1I?#t#=rJj5oQv~COqs2 zP-FwErn}nC51tG@_bPL$rZhDgq%cy@1jhn|NA2nYGmSbjH#P+y5mU}``BzOg64L+28H&|fmkWu6D9ng z9bFP&?Iv>F`qQ%C?U{u?!z96^9?cy3+a5al5MD5Im3-`S*O7$ zrM7I#?L_8olDqsu>!UEL5|<5=`dgLAbLIdVaoAJgr&HVmc-K9k2d>4c<2pn?!4?s2 zGyl>r9eSm4DJBK_qCHv0yhf|GQg~GunvAj|8-+51v3MAh4+18f24Dyi%JM0s9ThE ztx{MDmD00wO+Lk3`vNqF87uWH@#E&F^Rr8{4AG3Z7RLg&ts9+2O+|?w)T?#}{@fwm zTRI)7Ne@(tEI+d#ca%g#}h$hph73?AZ;^^VJG|GAT6sSqe zC8N$keqSvyo7DGYWe6;Qfo+eiUA9nL{%Ec2brPyDCOQy3HVmo=ElPTNOPvvmu$#TVk!M(juXn)Vt@8Xg zfg^F4xw^8F`3edE!+hPu2dH+wo>pNtnNNP_a^4@AhxCW@O*+IY49OcV(KMDtUC9EgNZeG)ZC64Sp$#-&c!s>Ra>NivPQ*N$Q?ydj*exF6Ao)oss~D3ib?D)YRQ2$J^4+>f=L|%N8qs@BexoP^7NNGe>S_oVZV)kKp})T+_r6#F$m zxuDv&_zh8>u0aI>Hp&m>SQ>?w%J?6JlV_UgB!kALX9s3%SOWxUd43NpQ(@Uj+;-f2 z$oAsm$sC>OfwZ*hBepE-pt4iI$&-fzvVB)zgxpgvO!~Ol+muJww?HbvS}E0@uHn3_ z+&3MZ&~}{04%ucoFYi(5$)uG%^Ks0(x`a8(97>R>75aJ4u;swqfJG;2mbZgIc}bnh z2k&w0QX2yF=z6E$UWzg?-CxgM22w$K^6h&RbRo?rY!$$dM0ebLg71r<^0sMj$Ujl_ zhanX`c(@QOY>^4nn$(<@S5`y?XEO&9<9T)}_28wJ#~Z8Z6}qtoGLPNKNT-4Kn3kE3 z*Pk0HXCM*Fx+kXWr$~a%hKg9>Pr`;#pb%zfB;f+jF;TSyy`Cqsb^?)z%$0hCGVskH z$Ur=v&<=vwpmpHnCw}cTlc;aY(nIivl=YrZ2px92ds0YHdN21eJz^57|4DuME~x4} z_fO9^ckJoJzd>3Fy|Fw%Ju-9dIB>cM@#hf?eB|I{f!?U1o+CUW z>^R>FEn0N%5k`<2sYKkFp`c+`B(`smHj83NCbPwne{cLd498zUPDrJ*=?M9yY)nPwX< zl?#p$`tDieO{7PVl^nS7x_W|IFffBtN_7BSd=wOAcT^KL}aTIzPw6_ z|5_>zUN?NH2W|v19r3n3=+ujx_VjI7zf@OaZF}hZVP4Fs676_C^0Ub&XD#8X!ST#L zvS^Drw%O0oyi^&x5})9+xP5TVOz?hz8dWfR((8Y_b()l$c1$Mxw%{&lF^UO^eX`6Z zn3|z(N)JW{s!&dl4k6XNK-Cf1K_Uz^$QzvlFTCs1K-Dv|A{#wnhp9~5lA(wbvhf)K%` zt?M>86vtAi&`+vcKxzgdMi0O&3od7f-{rjY5532ytESet+MedqvKM>Rum}#DoDI~_ zN*Ouqbpcq~o?+CD;O`&1J12vBp32StGHxC%)%(ZJAoBrl%It<+T)@ir$q$Bib56T3 zwP9(t(ABq=+7*&DB3~Wn!}#=St$qq4Zj52{N2Ysm8f%VT!qwCjw7l|a)il+N-cchD z*GraHpXHoPa;PJPg{H%pw3tk?=2s8SvkM*T;*+UB^JURLYjzUzmZ|JaObXMZqlps}vQm=31qn_2-+eP>fYd4g zv{KajwRXWw6GBG`BpBAp=lk9CAF+mRoDz8v^8V5~f9X&l7U(%$LpAvE4w*=t5eUC- zVG!*pNIFqj`H30Gau+wTKf$dj>253}A4<5csE5LfjaF09AVui)jCRX^ztbNcj>V{J znFk6qF>r9G9Yci~h7SCw9e=Z?ly2y=hyoYPQ{c2;rv{14GqCyEAa4!;P})3;f$Xa{Cf((Vr`xW#;SEb5C;Z8Ia-?_Ub|m=N5T#_x*q?DiDNcV_s#ZmOko{ zcY*e;j2li!45`C63C|7IVnsf(kHOpybA}+QqPDjFqixc+(^VvxyO%F}7|P>O-S^h| zN7uc>C*55G#ax|eT08xJde{$m$TeR>j@`N4xPIN(tl^A~`8cpMqT0acX;|K#Pr(~? z&d9nJwIp%TmGn)9cGjrTZAp0+4(@gN(F{)A+vyzAt~T|PW$~*O#V&^%$+;*3KObkNlv%u|=cm7X*d_ zw>W!7sPm@ie)fh#k6_c8;A8szC3-7pDC2p`Ajl?8<^<|r-h_Xoi~8#YQS!mVaaL{S43V9$*<8KOy`2zL^oZqNfBz*`GN|3jX_ zqU6XHinn)h{1BR%gK#sSB%OkKFqI8#JAH;(F#{sgN>rAJr>_?*IGR9z3+}ITaXh4E zMc*}NzxTInOgL;~G=I{)YAVkzsH+14bulh%RK#=wcf zfaUbD94fCmr7^i}2=$PpMopMR=_JMTzwPjR6b8c%j4=b(w>6|q_upa1^;mmJ;%@}J zw-PT#19T?v=&~-~(xr8thM?gBh@=PIYiZl>G`L_+h_w5pCq`3lb+AWI9r$Y@AqtT5 zY@csjSdT`(cmZc_jvP^TxKCTyeMs-pH)?+>J+N(`gLFu%aK!xr)tA@ctHXfj9e;!*cpOD~9$j}4?(x7VcUemQb5U0F2~9hZmFf^xot z;u>eb-YTS*oY6mABkE)@_}Ho&d)+uwTja6gab649q&%ADG0^|qHfw#V0Nr0&TV$Aj zI#6nS-b;IZ!c32^Te-CNG9@@2m>!HYE6wef#Oo(bre>AYbjqc5wEwu&bFTBwjd|&t z5cN6?$W2TfB!fZG?IS^m|&A=+6`=T+g*9(7iL}-6d-GWdc?7;#krUcOiVnL>q z3%WwOB#%C;l~|8S`-1W}Qb(`Dspy=Y-G6UOgB$qJgfqa4P=M zjXYDOz#zDFM7z}ISK?o=3^S-v4=rbH|LsG4N8c`j;V(zugu>ZNG#tjdA)m3X|KCLc z#};Gc*Dtb~tRZPl8I>)1cSHX+>f_FrK zzBz>JUs$mSguTN^pr^pEl&0HLDX{yj+84Yk>>w4~2|?3xX+bVjlB>nWo@#-lovTN`D&jY1)?ILBGdEvOE1`6I>sLFk9v`w^9&$7c zDq?MF?|+OMuuGL$XwX(%yq+~mZnW!I=R2mIRav>wMo-qW)?n!hG&P9r6hi7_;CR7A zGr|oH4~H9-EFwcW^D!j#(ZwiA1*aKRVC=c_o^Yrf{XRdpB~c8_HKwl@RoH!eh?dr5 zU|<-_k`WUNK74{mh=oPLDgco*80xJTDw%8VI(uS(F(-8K88*7X3ncd9>p{;j0~Zkx zo$ZBX=~ZY?RRGa<%K-NuxZ4?j663PcrfnmFOuSo& znoqrUMgl(M+S|B#kh4sT*u|cH*y;0(koE9gQ!c-3xD3JVNWhE2x8AT;PAbK|QkNFE6UIv76Z<@Wi?c zI_$fW-|Tl1s5KHHv?@e$KR$0EtGi%yx;)?sMl=2!t8B_GZIS8#Ng)c_s>TVh_^i2Xq5|kU3p<1nC+R3Gg1_!0p}5hV-OO z<+v$IK2)_i#vfGKqkW%jqG}sN<}o>x+D~jW5*JY2$l@z;lM9_^3hpow!}Kfz@)X`& zW~)lkFeyU6uwx@d6It~{m_yIPY`XRdUdKt*ePda+%QuQDo~BN9KfmK`S&{)Xi(B;7 z*Yc5l{yP><_NeXL=!TB!Bcz5$xqr3zKGK26;(mE9;#K=n1g1utKp~U%W7{gl%2=OJ zUyFQKPD-|wz&ZH~cy5VN7(Ko7OO}8`Z7RzxcOr*dV>W44QL}Ehkmn|g6iAGU3}X4E zA`|s6UNFqgFBY5nVIpBFZ)h_3SR?hws7NHcdh-qLh4T6PV!MbR^_APBx~yE=&ZKvaiiYN*-kg{XE5_6OK^f4F}r z1iU79nGDBul_$(Cft+|*7U|EwwH_Tk!x(!JNE5FsaENbj!1uN#C7ZygcgHfxZ~Cz% z@jV+`Lg;Z^`w82J(Vu%t@SiZAH)(vD!1zTnvyljGEt%t4TbWJWjc#H2&Qg>Fy!pA> zwtHQ7_cj~Js(_~fj;E>H{+w@syl0`pPp_5h*V#q_7w#~46S@w@n58glqRc9;RKYHpL zx8?I)M%0H{Kx|fDHjCJlNTD{>+v#Evd0tX3S7PGhB-Ovn9u^eQ-35BTG%iEN&#k2h zR6_W~ESRJK(-BNl$rmUcGn^VKfqS_5?$@wDBe)&6tnla=Jcm^KHJqf_@sCmzX)jUq?=qos7>Rm z?GzJ7!Ydj0XA&Nt+EkXuVDNd6eh@52q_`vTGsvK}?TFUvM1pBFMCn_bT0FB3L)@s9H$k;N=dRrrXQge^n?nLws9s zC+Kg;>UKI58t>2Wtyf`hrk~X>!c;pemm2KFzc$(%OM!t)E9QUygxi}lt1HgFC4zlj zO#R~LJ+63ZA-vZO)b0hRyC2_nqq1N2mCE#Q;&!2;D{Vmkl_|7Y5BCxL_jKgtvPx7v|5D0^fhf~DK`Wx)u%(fF;j88dmTk?uM;(oc$H&p{YNF6qx z#F6qmn5RWNxHY;FgAMcG$9qkLT|HJe456VQ1`;ujk8!T^o*Z#P`!+eykeCc0wvh-6 zp+y}n!V;dyggKJqh@wF0yu23kZZinyv0>VhmjHHOGX5+H;1x4fZAZ7vp}{Xs++1Cf zAEDHVZKP&Sq1j#(O@>ee3|Jye;=rfx5>L=#SS;ea7dr+3hJ2=;a4aB@@QO|Pw}=(b z7F)MF#OlrZ%I_AIS1&qEs8v=f-+;=%yS9q$KDXds1|;ch58_@mb@nklT`m%#wqJfV z&DE3yyIA=Ps9L9F42Y(+g2pk7HmNjD4 zocCTXLmIrUy#ymnx~JJXCl3-*oI+VoEHog=0{=mK2Fg|Wzo@`;^jLnba>6xxvzs~b za=KU}xWW8%^~7^Rh5ZKAe++0B4uGL!0O}joXaJNGK$lj#p%~oP^;VaX=4REhGAQp5 z5mvW-o?d3iaR7Ny534F6D;TS+2Pq6K8QQ*r^k0mdufij4M9=MLKB61^0n)UgPq4bm9k$Zk znVaQ5gwZ{0l?d>ft8-U^H9{YobBz_o$RU71rUUnOL3fgxuXM_OX^~ zn*NHF7c;9|ynu%0z&}5!N+_~z(}NVe`ezdAH*5Ls>-JqHZI!?0@Tg0nAHmWI%3$b1 zHLkn6bRm`AuZQm$75@5f%3MXtKaP9D-S!hA^Jv#gw#=INn=|I?%v(Qr&bz>Sfr&ERV(c7CO8%O~9KI`g=^X0m$B>IQP*JZy?> z_lFy0N1gQ%KYu{FLyCgIJwwdyoFp7hng0ji>_qZ0^VqO=tG2343~Pag>T@`=RmsNz z|Cu4-N*CnUffYs)-a}W{xf?W$#rIf=ynd$VpelD^@Y+#pPk2b=`Q~e4`?8EZ(>0ji zZ)%zhqm`6nm~PbCZz}$RNHfZ)^l+e^xlP!T#SZk4yLsjxvyoHrN#;d=?q~l50ByL3 zz)iGywIOd+pn|lzZ8|{=Lm7~onSz-{^DBOghys*8OUg{68HF1~N7)8Efmp<<%L-?) z4k{&cV50D(2JjMvDBt6C0@|tbd+Q^*T#n>je2jVR%x@dFdH#5mS#PDnY+cfidONJU>A^z9h^&3iXbBp@#|I24>Lly!S^lJVW)B>m&E}9s~Xgz&UnR{HaA|CQ2@0kr7Y^Fs~ z5Cs*S_l7NOQ=nb%O2#GM>R^p7JLJKz=*|iPP(=-sqFl&rrl?om)@e19;3W@S=qT(a z9Gi`gsW19U;|EGgM~)rnlQ6jiiy!xzvwwevPfJf(?bRz$5xEQLwu1RvgeM7z@RQZ$ zF|A!0R6-KK_h7)Mwu86`&ks`u#(_fY-<_#r1>voxY50aeL>^$CfIR2K#|PP&M$D}34Z%Gi1!r;-Z*ehbWtJV}=Sqj^f zmr{SSgtOu=RJ`B`4DF~Rs#C4&@hM9)r(@EHQ!}V2S*;O&N zZ}ruGVgw#gu_}wz_yD?AiiPoe5A7U73C%u(z^ZWu43m6Xx`?OdjnIX5ZKZHr8XAMl z(r|D7^q;|1pMixGtso)HqiIq_HH$nkyK$eg&ht47?e9OWA3zhhqP%3eI?S2VG{F?N zbX!!P{pSzDrveemS~d>6sbOm}BED1G*~5O$N@D{50Z~4v4yB~st>2Yq z35$}ap`l62%&h2eQ>-unl}n8INPxu8+^j4*NM=-={vL@|^TWg_4(V1(Z^B9S4Yuh? zi~0WH8mJ^2g!t^u0RPyRFng{~vlfjY9dp93~C&HK-$JSJnfab=%x74)vr9YQfRtkCXUf^r1DJ97~}I2_!3Ya^D{4yxyj45+)Q+v)8_A!0{Vu-LTAUIsdf_i7@k;k0!@( z6hKiF?3j`>k6D-rGLTw<_cTn~gh(AO_<^J)YY1hIvKMdH$^;`;z(?5Po@ykJz|t4Z zoy-N_=@O|R1mseTYBh+^<0GgkWe^khqaE+N%Y91Iv6m8#ab#MSSVo>skI(<|l|nhA zS;<@p__6T?VbcNkH!bvjxp0kwk3k0n&rgmdqoOTxhqMU^A~8#e6GjF%1TmdT8akC= zD;?||(q0d}E9`19gM?tu4fkDgy5ymdjr9KJs(x`WfWN5X&`tEDYUts%9_9M%Hn}wJ zweH4*)gp)3=p7wS7P$@h&tzr(@?u!*b^%1{#c+RrE1&oD=*V#%i7feM_V~QweET;U zQ%*BL)B?_%1W7f7N~yMDjc=`eRewpjY;~GUVB_Cf^sT*EJ}QuPqG)oBl6^#DLzRIY z{IAgy^nXQV`RR#J*WfDr+TxhtkjZNF6{8jCFkUeTLp{fCRAKUZ#oHOeUX|bRgjvo{ zNRM&?2%kdqe#BA|GJCtLnMY)2dmUybg3t-U(8RBmHw7`*zxL&&6CPjD3O%Nv+jdOj zU_22L!1^u;_}l*h(2zP>;msqFh7!23cg!BtE>T;EpZfJPQ!;%VPCH= z{PBtmBm;TQEtv#i2=Vj2^)lqz*hlx(zIhjFWUN}nJOvBjl{!WRR>+e8&nSDw)9=!27TKS&3BA_) zsG##?X=%qp>7M8B&&tn8_m&o4*!lI!!vD)~AsWGJ_N0@%-&R+m5WuSn-J}UDUk3YV z&*KIJ5eTk)Zf8}3m8N5VjtKh2$hJB(m`>xzGBB)n(0;(CRu2>5EyjQjmLMKdhlm1= ziM;?e7E3-md&pywt-%S0i`j7AW>P68A10vub7QjcEb^)N{0`xt{}?Gu0C5Br37weu zhI5PWZR~I?yh_4$yGT z>RY#0_kZ_m=chf7@!N;-68NDy99-pZDhFrSetK9;y7WRm=N7k1T%VOaxE&5b`n4GW zq3`?ALW#45%3{?US(8DRsmYmS@xE_pJXdc=Glex{!Nl=y?MtI-LaoxdZ!<1Qsp5o! zu7kaX=EPzdR-WY7ho5y@OxW4(z1t9-jvXv3HT!-n$_xOufP4XB84dc>FQ136{nU7a zliQf!c3MTJSgbbKA)kP~x2TG#4cg3oTRte%t=GFhrjt_~w>?~h37*WI&_C!+)GhPl z^XI|@@Ivw_TN1wjAzmVfG*L*dCvW>#Q-lUsr`2!vk70N&6LC z5e#UXb4Cmo%aD$=J*IJV{95l6cLPm(W*uk58K8_{>*+)mC8PH1C_YljIliuf#)Qq3 z+j&rSQjAw5Ri{uNL^I%kH~*TUB7^rvfyu~!1W6x`w96b||8ju)*Ts(3{t}-6fl+lrqd=K0lLKE1Gf#D0$HrhR8WMrbZyO1f!4RwRinkPYn2j8jO_wTNP&%c}t^r5Q1L-%>(=5ysa&=5^S^}gSJQLAy@UR(Tn;Ev^*R2> zj>!H~k*0D6z5%yT59tH{3lSsBE7~wZ)RTCu6BJhehyc4Uq6C!lQYz%)cY&~6P1p?D z7${6Xn*XhzwX-zU$3aMf#aWoP&4?;|+m(pWh3$we7Y36_qfZ1So^Q$D1reMG^>3)J zMRu=qU*xVOTfy~YDjY)mz`9K!0@#u=orQMuPy_4LqL{fstIhxC5c;1;`8TPc8yAM{ z{-2h_g<%RI{8_?LXlK1V3M#We8MC9tPL(zE-x6(tg;(7T#Xp$GAwcx$O-iV2CvuTU zF7r|QVqV?=4uJaZt%{MNef9IHA%FP>6@iXTs3HCc1ynA1FLtPz7f=U&Dp=Iby^=kk z5nBVXr?ml-FrJ>6g|7CU$@s10~sEOFWCX4eB z$d@2C;b=+mr*~LW)G4gc;;c>Dlk+%)irDsBjCq-HG`rGVUz4uWYHrW-4WH@9YY_u# z4p@}jMYQ#wE&ukTGNBKeC%88sI4S1re29+P)U=c?Sk!llh<;)=P=u0kr?qZGdQ|zg zE$S7%!mc9plefe2^<8;Rxd_0t&WAcLs=zolE@ctzL@o^NBkdh}QVI2ncssaM^3xH~ zVs7Y@FyH0Fqx)rptLv%rs#S4oa9L;?Kp_2>Qa(y1 zY4%=wn+_*_QF-ULp)UjBo`--EQ-TZK%=D$aj~;>4lcNl?ohvEEcXyGbrA z%WG;|Qu4AN&MGdUL&(|4 zWvjzgYCO>rE08m&K*;#K%3t|^{6Xo8cvYclAwOlHzU)P9O+gxi+q9Sgv{a}qdTc~?k?81J^vc3R?)F}##HqUz$FgV$Cwa6L?ej;;l3SthwE63Az|V;8+1ke+EDfM^AlFk9a9XR z939R*>M~Tfsp&fq+p{h3DFwB&%oWs4w<82DGkja;?QIRjDema3Sl~>(&@~IJ$Y&qw zRdaAzIvDpgDFAX#si-bL+AOG1&v&m^MW%`p3#yG8*sH${Cmq$Wxx?$~wM1|>@c;We z08rk}u+aGcI+_0>T|XY_*T^{QP_p=G^S}EDsAz?E=g{1+KDJ{^zARz&{!-;{CVreI ziv)pPe4shMJmVPWbp%()YVc^uO~xdv$wuRez6VC3O+Ej}ybg{&*g)`K_${*K`Q)N} z&qJ+JMn&>7#(sv_9%~D?vkP@#uBAbm#-z7WLwn=ShVkd`0KteVf89z>hv$yJ@l`^f zX^pfYe|#6-t4zoK(rj!eT5{<#n*?UF9%Aqubjv3yc+4_0%FbiOr-pYw)MvA2zG9R9 zm#*f2nM&4^)oy$I{8;j5!k&P}3GLn9_^x-KsbK^c*I9KUWZWBEigxeOQeF&IXF^!RDYjokO@zKW$fhGFrC^WdoX~TP8bC70@9?Z}`Yh*M9y=c>1(f1oGJjQe;pGe%y>G?+ zmhC%^9?;`Pu=!ril7x_wzt(<+bh3VrGNHA`VqgLm??8+W8&%h$WW9NulE%?a?sd-0 za5Zt0)Sj=aKsz1;wQHY-RmP7IS?1bfJaJpJu#M#AWAIPLBPy7;C@t$1Cy>5ChZ1X^ z!qF7!ks))*lS3LJ`+>la8)G5^BK~>yq$j`klpSOFm-YB68M-c)gt77Lzm7_;Ye}HMewz*xc0X%?IN;u zbbV=-r>)ZS{98YRs{-;>&?>uo1e8Q|gQBb=t!}IE0x(bR-KEuBQaj#n#=nzwmdpk< zoFbf`C;=e5xpYm0Sgk6Lqda<_lG|)oSecCJF0Xoh5PPc>)%ErD@%fP`rgH_>cx)Ew zb+oiHi%Ufd#N4(%t*!L7!oiE%ZX$;_jla%*=*Exuze?V7Z7J||+zwo#m*Is zJmvjW2jawYNh>O&PgNWYPJ!2u)yot@-9Okr=TNnMgGwtSV@?U&p&ekwVG^$vey1q> zpNr)GBTeO(^whq-O1ihrpM5$~;S7)f#P1cu4G?C;;7q+Z%RQXiLSYcC)^y1()~rd2 zf|Uq@xc}O-eA4Ad5)oq`$ez7+&UQGLJ)fQ2!yjfgTuFYate8X8rKZeKjsm4 znH`M`Jd1$b_CrHM7z!jF+8+IO;j!IW7DJPQk0W5arCkJ+gd{1rk-w+14KriponBSd z@nOIk?r>s$3k#-LPfsLz;uipavR3*h6MyFSK4N8MpUG|V8Y8DUrw4Q+T?)}%a)bIU z1cAC&Bu6yb=r&&5AKeAx2rbZwdfd0gPAy@AfY|wlv6QN*GV2(8dl}_&-ozpF7@qq^ z^mhx2SzOsyls`t=oyk${wf4Jf4l@{;RevWAAKnwOxu2IF8li;C)lv=(<0B?5EiGXV zvwNSit=*x?m!~r`K7LpWwF4VSYiBL>jG@nNQyiTyje#tmh&^DH;St{crFHRp_ZvAA z-E=vRBnHreCP<&UYPHRyD(S?oZnGB1cpoKJ4<7?Z8(#}Vq|Ph{?{ga?i8<__27QTLzF)@(vS^{hmsoVwcPbv$kYRD8Qny zRIN1^?0`Ne-E|ZE@c%INPJxw0>$dKU?NrQ)&5CVRY}>YL#-b(T<9HmNz}sH0Swe%?z%~mO&$#$>=R&!~}Z}x{`7TsMr<3 zrXZH1i0w;`&1+BX7OLEZnVpmF--`4Y`~Kbmj@wLW55*hHJsT{Ds17`0CL;N*n2S9M z3E?3K17-xpXZK@9M@5boV`Bl&^m_5e7FwBUKaYbNsddl}CnjH87Ri%ot5Ldtj%eqx zBi86rLDyCYUj;Dl7!A2i#>GAxi;O@t+XuIHF%G^>h9(BZGTMz6NRjyrAF1o0KmfDq zNt?d5FayF3YCe*!2(IDKx{R~f$7cwY9{qqM0JMKhFNXN&>wzcT0`6V*jGO4RYEitE z0J<6Yr}buVZzlhQry)9${Mx$sB0j4Y_1hB!DX?>d?*@{k?p6M0=>LCC(U8fY8=9bC zTG2^uqJC3s5Sm&64Q~3w9|eW-xQ3OlvN*(Zf1Ij}bF`y%^uKg<4Phi0B@`vAjVI_6 zPSqBv4_y|cQb);{-R1`U+a>J54#kwjI)SV%38i}=`#QBnC17)aXE3Y z>SGOXQxbm`>^ztZL?M+4Ia89pWN_D{yDhnVy)Eh>89muSB!IT@v|;#3@}zBv_;U+( zdiJQjnFatuoY{FgQ*%=ihB9gYd~I%-sLwD8M?pW0MU0ue*WSPm_q*{!Ez8!$; z;^44J63TQK%DIYtOI|NQAJ_LQuekKdl3~c8SIc%N(q_7Xr5dJV#4=TY^zo2veZQ^f zO7u`V9I5)#qT)J#F8}@Jat`|X^>NWflXCjB>0EQO&ScW`u#n!@0tuMcvf4cLMBe(# zPeyFw;w0|0)M(@spK)xV>&Avvxa6M-c|4Ft8Ma3cb_|iNyM34$xxI&aU4`n5mg3A6 ze&lV+=(r^c2%rtk5ay4j43qPv9Q(=03zT zF<`X93x+!ehZlJXQL#A76O;pa#DvYB0DjK10F2=c=pZ)Ftr`@Ee~JA(G&OsbxVe08 zex}~^BWh9HC~?#%-Jrn_)uoJfvEHay=%~Zk)Dx3hEGr=Oajdgdmt&La!AUA6pN{i_ zYV^F&NvjrFpDIJwQG4ai8;pwIRW87iSaiwPnuYS?LLb)eND5cZ4i~f7QU>!}FwY(}#UHifqT>_BLJ*D= z13{mu7(mA%SaUs}dCW`jpgUAmbK&OVVii%LQ=J&b|C3gxAhbl5TqqiX%AE3 z2qSny4lhc?Ai%%|{F$1=Pem05`z#1h?D^n`84?!#1gBJOy{3K10I!vA4I9-t z4{R4JBL6Jz?Zp0fX0FTGLIP!|SxxG`&D*~aIvx@68&x0a=W7sX`}TMKa81YG9pYyd zGtF)e|C#s4KbD%gkR!yQq5wEVbv(KWI+d_7*imDVOXv@O4b0<&T& z_+z=AyJi%sK-WF?t{)W9*w4mr0$#difBd4{9(r7T&RUw}a%TxZl+i-u5?P8f^W>qb)_~-V&xIn{v+)}yJ>OvidA?X_veD8rT{m1Z3c0l;I$mMd-zcHe zZCB5B-wgvhC`yW=ZbQ!Ic7x#d4;!X3Er>ZaT9eQpTg|e903P|k_5Ns_61k`wRDN2x zx;$G?n{AUue)?c!J^Gs2;CIx$d$B@gVPQdQoiy(5_&gnvF|z3UNKb2e67xH0f#NI$ z@|ym3kiEhD9=&5i^!nNDx?y*2fiLEGkyQVoh{_~Q8SfC82(u^eL#5 zgwoBZeh?cabcvw=z-)j5V48R{hd6cy9PDVTx-~9X@!6InPUB=~~JY zs*BAhkZ2Ynzl(vAj-*83$He&P$aSMOimrb3-i^6VKi3??o*ajpmhgdSS^YWz&lP~? zW&Qm}%FQChVi0m0+&pms{J#ztEN}E$AVa`amijL)5Gv%91joFZysXHc>TVy4mEUH= z`)v%EZl27p!1)`F76I~PFjyhcl$Nfj(jCV`Jf-S|Ye{|z`TuvPAAAK?69Zq}BCTw{r zO2Ou~8P$KyETF}s29C1d>-P%4iPzp&&|;U#`Q?C{ar#PZEnTKe zeZAj}M!=-qfVaHA9Pp1tm*Y#NLCIBI-{Jbwv+Fo2x$%zU1{{R4&7z_Yg?|b#+(-U3 zfBo5Ny=mfj+OIT_1%M5J0yC5+-&-EUJClf9_d4q+|a62nUw1O!M_T;mz&PqZ*L=3%TLKgrqemC2?1LMxJ zTz0xye?Ej-`Jo(NvI(g9HK3nw2epUo7}hx$)HBmUQVxOrnMWzCqp_|QKCBr8u1Uvu zh@!CQxt!k(kk17(eP{Kdhoqs*!0}i1GGrSp->H3sG_nB8MG+cvzkl4VdBy5&_GAse zMKuy|#>}R@d_R#&}o`M*#RiT%6UlzXFo{Lyz(&ROqIwCtdZA3L9 z^0v;D=Yjd#`|J6;Sn0>LOb)|Y^y2GUY}KprBq3;?4N85-kJ_@CoLZ7O%@U1ezyaiR z1ST{t3Co~SM@Y|VRC9QN)3wqw#4u5NvU}#i0hC*m(QltE>&ib0ejHn}Bn`jU-kt|2 zCSgjc^b}7*S1KD}V;24p%aXF|JY}=ctScB*+en9oXlSUV$PLOH$IOe@1(17|P5b>h z{i6m#=^t_M8BpF;HXmp_lVZS=@4JoUf-`TLmaZs9gynA*p3M_ts6dVWxIi*q+}gS# zgX>cm6QBXGb)gv4`0CxhG~QTATWVpk6kLyb1~DZ2xg-bPaxS_aKI~=cnThyMJ{(6U zMtwNlj|ZpwW7T3$Zq6(+y+=Ca$F8=6<<|r+hi=j3jfZjXHH_nAq!YLRRvzf$Kb^A{ z{v~Q0YStRUO{f!>8Tj&)RDJ=0y^TBSq&ciPhbp`)ZPiM3!B<7%Xl(hsQj~DFsB3y< ze$V@3(#*4KMCHu*_;~ObK}7dx$&t}fM*)S&=z?JLfMz0mA{@HKH{@IFQ;-}-5vXli zZ6&3-e;#*-rqh;~=w#dgqSN(8s}!j0)b{7-`4$@WZ+h948MiiZ5WG0Ur{9 zd3wq;oWG%oeq?B(hU$e22VpHtrX{)<)iI{lcm(x35>k@_0f$klwyZ zv^<*SKNK6MS_<{4B7OlZycrIt8&_|oou?^Cm15WXY>gw zx)K4?ZLGS=?1tgdzlXf)-ke-(`(W}0FzK?tR%e&!AEDm6AW}&Qt&ZIr`X`(0|tt>Z`67E+WKnO4HOf#NK0h-R93lHP2?^J z4mJ)pT&}2zWK@ijZfpB)siur`iTz)bJuNJ)6fG896rnA;QI2?D=GXyqT#*RVU*+4z3ixTmvTEu5l!^-=R{+MT!Zcn6N1_wR z0-9>)v}C-=-9xTF$4LZ#Zj+RIz5ix}Z|_AG_!-JtA0Q-%(ax6C{POzvl*deWSwYJ4 zQ;57G1jd+Hem;Ptz4;lSP3#UaWH0W5KJ4$XfrLZ}Xg>z;BoYI)32hP6hj0Hk4JShz zljt%(O@DBKua9cR^=;(*^VkVqTV1Z{mE~o38S9b#T~L!CJB=-+HL5TTJpI>v9Nia1(+@C!9>h!s6ZIzs3vpKv! zH{N2o_#4vr#A#O3xYUfXpXablF)dz@l2PiNl%)6rx4W8L0*a`rpXZ)T1kQ_nJE91f z=?&4#l488Fe&dnX-wR%*Of)&1VGXF@84nLWN=bPjW9)X$Y)6C{)0P;ke0pR&+cTKQ zqemng7W##RFE&x1{w)dl-%EW!6ex;KV2>(Y1s?`PFbqU4Z9HGWGb(iV7r2F@a*O#> z|H!@;=4i`RP7&6nFi1ygqMx~xBPJdKB!@0&?e%I)llCe97BTkRkpEO3ti<} z9~)@aJiT)0F~Qacud^6-xVO{y8l0ae%LZ_&9rfjk)o*()qdIP`mQ6e!a|w#dK#2Uw zZg^vn8YWZwf!4^q(BE-Y+|8pjgkAaQ%oy3V^0~-pKkzc1O6hJptb0uA8yzd&?-}@2 zN+_%H1TrwJshp2Kr#jzRUV4i78Bdv}9g7hWLh%IHF#CH|E1TnIJPL{`^JT~*Q;8zJ zX%X$LsSH**#o63|UbfgrsP7Wl^reU7MID8y`a_ zZ;oH#yjK5p$gCT5av>#02)Jh`TCUOq@j6n^Eh@y#JoY@R*>ZBM5}~XlyyTo&7OGar ze>gjMN(g@&S@l2&R1O(3hUDQ_p({dJb1l}lB7m%F6XGWa07vP>2# z-CSiCh_~OtNijlFUNZhKuEec-zB!c8c!8^0{}*nbG(5jK)JQEP8(z2bmJrB@_aE%tz5k*=k&fng#Lq*geVOd1Ioy#vQq~ zY>Y8mW86eX&E>W_hsNAbsEFVi07)*3u-0QJC%19yC=^?n0v7_L*Cy<{Jsjku*sOlH zn{}a>>AyhvD?l>yr#LO6>*cMpuSSyhW$h`TL6AesuK>=-?yvGv>a0?NL^4WR36)!| zx&%BU5h2nEkri-Yx0r=dS0!`Ej)y2zt#rGt z9rO~brsZI({C5@+>5Xf$AdHVc5xxV^yp5`jPpqu0d~U#>KR*3U{5PRx1P%@k?h6pf z1LuN<|KL%AHdOA^L9B%q$J`W(64GdIW8)=s>OfKteP0qPfLU@T90Mbr>pj``q8fBnP+YlVm8PsnFS9xzVs{% z%BMMk2Dndi7j%J))*rT)#NbLr`mykW2w?c@F>VQYsMu92=$dg1F!O&_#z4m4R96}U zOr#30-?8I8S@)4-iy2D{dCO?;N`^4UGq!IA4nh9}S4u*9`oehnT&s>yY1<_(BzQi4 zbZE0Xf9s_`QVk_D6lm#87Dn&j!a#t^c!NuU@X*XsK{ZhBpv{53@(*)W-2@~OM*Qc6%{u)Jy7H_A?jAhCi4ppK=l zFdm4w&J4$MbZ*y?ojX&m2;g5<4D!^&5WPaW%d9|5k*_6p{Il-)hDpryGxx7QW&J1~ zIajCR2pEGC`)3p=*{q) zf-`v@j1SVtdXj*PMxz@lVfc>x?O>e&&qK`60&XQOWSqnr$0&#WWfY@};Z?T-iwygJ z>2gNO>s2ffrdhcJd?N@90qlx0JHX_Kf>XJU*K{CQ z!Md|a6zD)<3HAdG8~`?`!Td`LX`LWj8D|`#1!V~<{B=WsLF5zS(#MS^V5-{52Dx6+ z-AE0P%qU0;-u8Ul3L$_AssWgX@z{}!+Qyqt6MIm`sot7pB+MU;5f=6I&|Lhl3*f?N zy_9VS>k5SqQ9$0T9`scH(LxRzrhClXT|uy)gGeWJ5YP_aBp$cr(gup}Z~@eH>*YA~ zh5`v}91v!XzjzGZ?O+Dms!uuvWzfufqRge6dB*3)-qai1bLoV4AT>q602Dt-SRc%D zqj$q0S&F3N)N~{iBt`s}=~G8P_6OAl*8BhGgO7+`;)y7Wrt88N-SS}sb!{D`n{9dI z=i-PIDU?=mhzgg8YP@=E)pv_=j&3!rtV&m#0&__Uo~kdH584{bPO+28(qBTFr>n${ z3-3dtGIy)S{TyZpWgHx-t$SIP-CZM-Jv%)n5q)#m5HXThcg6`h8#_Dd_dgLID)^yd1`dh^n{@`Ey&C zs)T>eaJ{4DU9>2*-<1)g5YxM%&=E%5q$}`wyzA)oeO1N%PiApqMBTAO4Vk1;7qLSc z+O@ZOO?2N0G+Alz^g>)LLz$Bus@sjlY|F0OF2fh}V!kVp^d2$dudCqhe*~bnGQyBP z96<<+Ue}xXm{6KYhQR3#y5)uoqcS+0(wrVmy9!+lh-eO2+J=aai^AIGGGwIkI(a|Q zKVF8_^}$P+3>5T_5I9YSaW`nRjF*vl9O!!A7%Ph#BnI5u;GB$%!Qv%j0Z`GkXP2d8 zu?#3`q{+^TQPVb~UIdMl+?`e3-h~Xl7xGKuD*8*mvO3UYS4Z8tdLW;gqE4O=bh*t& zttv5P$s(zyDq+4%G@jFpwuywy!aA8iFO_;S5?np_ygQmO_a}G@Wi@F!Bn9wXzG8k~ z6%ixVG=~S#Rf%8wO5P2qO6n`ZWkf(N9SRF}HbF>;{w=MXtFOJx$$RtXq7!Mps7^RU z`0*p-^Lel$Y#^Q+}&)1 zTte!lx+qt*%tHxR;m&Dr(Js?SQEEIajhz&<^NY}$Z|nLxu!SHFkh;V8LJ;PP7n2%Y zDx@ox{h6fH`IBX6lC@M{mt(pK-?p&SH@EQcI|q~}gq2Snk{c784C&!^(RRD@4+&yZ zFGnZ>OmFF0vdK?|0>6ZI#^CfLkccqon3xoXd~tzfQPz;xkGIzBM5LjqGMoeijlb8G`~8^BTcd%5038lRDdHs--7cL@!e6ui_+ogaP2 z9m%C;3=X8JN7@pv_Cg z2cuhTkcO8_?irrNWEwn9n^ksXwttlo<^lNwpz!48CE2pGRM+Pov&nZ%2a5OGSrD_Y_cM*z_P zaHXdcSJGZQtoLxtY=d@{2UJ){Mzw(>MW2qhmJwG3U2TAQni4F8W+<+HXw7cX)HEdJ zjaaeTyLsE4BAL}D2UWX%v6OTTEx5(KroNT}CTIZbHIylldg!*Ndm-!T93d+c9APfq z3%VP|F>jj)HWO(~(KDJSDopS#(^v*@pgt~bkJ-wSZCX^0^*)fxI5S7E9Y^6teMY;k zua42}QhV(?N?D#W+jxX|asOBL>+IjRB=YieV&e*Qo9~~q_ep4eem*H^=8lE}$u(}b z{W0H~&@~P!qXuFx-5XEJDby~4l)zuIvCFBoeGfVM8D2QX-Kp9_hS+) z^**AAcZ9fX1{iO z#by?sXx0LRs;rHV@~jsRr67+D3OQy0&>*=+El>!|!cahhdsCqRX}^v$GJp}gb`4yk zDitscgtX@@f>d02iwvPA8CgS?18LwHVQx@JnrvU-I$a2f;T|4LmFbc)59v^fLbc87QGzS?D+B8?z0ob2xw~~cs!-KR- zUa?-E=K~lHBLFX`c)dem-&N_Z?luM=0Mr?6z=`{I9VFx%v|}AbOgEdHM|TrSL!WcY zkozy!U+a*4$KAM)*(a^ZwA0eIL_p1(Ztr?X))fYiF^(gyj zXHrgXc4N(MM6&{XuZc7p2$OmF_Hjh-eO_WK8dVJb?^NCqecM0bfB2PSFj(UpPvyzY zCw3lmYqwb zAOa(zFU*(XW+Uae*^2&9(B39}_+J#96u(b#QOpS&Qb1Gt>sC7tl%$aO*0yO zq@|g{*Os9~5nE{!&=TY-SEXw%Vw?J;X5>Ea(N=Uj@FVz%M`Xv6nLLc<8@oC`)fzkA z6UPqXm9|--ZVP-sjRla~$Fy>~AT)8?-zhTqQYe2&mY~``j-LAdAnxiYt_(P6w(-EIlUZbbPz|#^Rr~j`=6pbGYHAo=l$00c0JU= zs&q9z&SNYSt)->qe(O_?lE{u%I;^xi{>R@CWv;-rp?{hgu2a*Xo6lQKFllsk*ZP7wO1!pPQp@#8-on8Rs{VK%N~(QDN6o zW7+85+~w!crcDu}R_r>MR%7B-n`h8{o9WW`3-4pp%w2TzMyX3{~-avy*b=K&d{5|rHel=F5hv6Wq&@1 zYsmNB9|aLjDw|7+2{Lkw{daIaN}I>-7uUTa>zu;l(U-(ZpB>fIUjz4uFi2ye8kT}i z*Y<2)yLL@S1P`Vy(Cs1@x2(e|^J$;P2|xJ+HID=swl_e(l?nX!7LT0%EISHnuCs>4 z5=X~#N_{)1oL-N?XdcXr-%Sr#mA{d9D<%(x%cY9*L)W!PZ; zt>#{Z|Hh#iCJ_;aJ090nJ~*x2AX` z_)AAB0yXgy5dqW`>qMpa``O3n-;d{B{ZKmbCc7i#v$gKdfwvOj);h9VY7;`l_JFd? zlzmCXU;%TV3?n}Rr;~*u$~K9Taj;;Pdj-w{_*QXiF{m|mD*eKM~ZnlReDmgQOLh7}WX?Hx3BE#nfz)Q|!MupSOfDdGtAisu4eqj3A^)_jN zk}b8dg?gS>h)1kph_V_R&{_dxvsDUeEEfdX7;VGWHWX2{Lo{@^aGM`fwH2YLCQSB1CkW2@%ae#hZm zptB&4hgUZoYYAyl3qHNLx3p6P-8NESZT1qdWv?fMFtW>hN7M`~6n#rY#X)w)oPR0! z)?z^vC(0eB>%&AALbYZTrK^2%7H_?-0_ZgHQcyLP0 z2g1RJDR%??wvQZgXx8TMb&G3vW>A703W-`@+|HcPDDFC2wJ^J5T`*@*V3@q^Z#ml2 zhk)vBC;zJob3k_O1E4H+LD`7Vo1up>QP9zi`7P12p?D(56L}tjYh&uI$pmFv+WkLIN_Z~dYzfz;jz0Pm=1^d~6 zP-)W^>^Ue~=N!Ckn*d~a05F!y0FlJ0#r6!kY6&(RO5n;03_tdRvZI|(Xn^J<{u+Sb z-l8xyg4Dwe)`)6wYE)%Cab}bLBT5)Cf#8leDUh;nNJ#Hd*-2zmIUR$(O(UaJ|H%GU zr(8k#-`$Wyh)SQ14L=Xx?pIq36@WaSzVZBzewA~&Vt+SyjQ_?BKM$Ji#9FrzH(4Gf zS|tnuJ|*Q1G4?H z<{ZVh!>J%AhQz8Qt)ht~^8YB{-q>x`jC$G$HiX1P&#L&npF>9dX+@-mRZ_ARxCE@?%foAIm^g zkeQ~g$m=lg>EJNZq$4U22kYgV2`8BPsb!uWZX4y)ZnI@&`jc-+XTwUDkW`C)MJd2c z2-AfO-sETXMzqzb&ac-|G-v)u6e!%)hGIIeY0Nke8q{DAXAOq~lGVzguN?GDb{+PP z%R*VIUX^#@7|K~wf>UB2L-pqD?V50Hg-f`%xqM%QxO_23!>40>8} z;BM}aPf&_L95{aeJNaFR%!H-@ft5K;&wDd#?_gW!QSPO6eB{EglUI`eQ$tb^roi{W zpN3?^zYeG%K~^2Z?o-*(_2{GV*XXzXMAI+2Yn;9AUzq#mY`^q-4d?(6sYKadMA=@) zKX;PA1_b%O*DJzTea?H+RJVIGjdB^D5PL`CfXV2c#-MEs9E`m8J|6KR4u37wjW8ja zX;BSPX0u*ApPq3DPhHJy&>qI$H4x#(&p*KzA^GLq74|5S*}NwqyYy?8pbbs_uTe@k z5%l$ZCFs+P@WYxt6|TiOZe2fP>!hkuO&9s&fll#jK-Pl3LNQy9ga2W_SYlKmC|*62 zWFq6S+rLfRBQ(#DzN7E?kqvBr1CmTl#;>gh;{kDlu@7>5BQ1&Y`}7e`!vX`jCf`-h zdmVbB{8lFZql~V@81;3F0_4^vqYqGu`;G}5grwB<@KMetK{AToqHwv`lUX6m_Gg9g zEz?mb7EmC@0?`LP7XjXtEv7HuB$B&@9#I?J@wj77B`;*VC-jJ-1$TZB8mKCZ6ph)VyJeqV|@`YL5Y>XBHGHV3&bYqINBE@OP_9;R(OZw^a9pwp1t zC1D(096n_j7|_IJ|AUKC$uaDSMJ9|V$~X)OX6wgKSNgR^(XFhJ+dytha>*txYhxf8YWZ=}=r|0U zG4;Ib9@m||L!;k{96#@gw9t@yTJT;QY&IB^9lww_lu-kaaR3mw$$wlXQU!IXwaCZ= zW*euE#LZLJ?H2{VU=P<(K?w+lkyqHm zHzJt&AL-BDC`HB>-z=&As?%*!XQ~>eH+j ze73k}Mji#GxMdy3tn^II%PF29@jah#3`=kvk^7%Kah&1bKbC{ex`df(b<_tL+vf#7 z&nWPduzuLOS-%hw8Y{k}UYrwzSK-dOEKW|d6@Y9RlH8ZLK!=gSC@v=4tY`*?7$6dW z@i<@i;$C*G9LJKEs=Nr701o_d&_A8sRgh?E0z$GwTtfc;=_&{Ke=^g;M9E3>`2ucK z+vAeF{L72p5S0&XJzgmH$*ye?N*AW=BA< z)5T7-vD4UtB?h_v=I(kRdikxT-C=4!AqVKL?|o1jlIKjB;kyL6aI{~oRq=GQMQ~R3 zIf%mv1VahwMSyRb_X?x5R}%Shr)<4?ju~Us*oj;p*LVOsLvba7 z)p$kmK9-r*-Hn&(23t=-?}KX~PN5$+xq+mZg~1~Xnxi>EO$i4XK*v;1)KmvV!c_F<*k?MS;TEld4^q;A|*0L8c;@22X5GV zLtXlh9_)V@W+@X-A6ix`ss*p3?P9v0dv@=Va2sa{yER$JEOo0XV)EjlmCsjRQ7>^J zYe*IoyN_*+eOA8}Evnsb=L$XU2nv3PS=uMw>h!lEyWx}s4_uX*53GcSrz)FLk1))@ zvA`zfFY4P7zooaITg=JM$z^#`Cv`2$Q7QB>A(sf=&?wh63+bseQNQoJB0WU4v7Ig; zEOb_RHLYf7X6)w5h*h+2KPRfJ&;G#G9Ju| zGpDEVDt~x}flA+#HIc2xU;q3VkWhsD@<07dRZW+G5*ljI04ZbV$QD*S?s z%vTfMeRnT&h4K5N`oH0%?}4W*y81tR3yw=_Hy4Q|3o$4woU1zbK1cN_H^pk6!g9tU8_3bfReOL}NJn05c--p)JZ5-TzE`ii{+ zRF(}C^&n5r1npwSA@+IGZGY^5koO==u@X?8`BUU80Qq1uyO7QIWT0?SsKjOlkhYnZ zyW3R|OBEz4Sfmnn$Tk)MjK>K=N2qj_=_~$-2W4J`Cl^z|E*jHSB%|IU+>(nn5;+BhTKnB4 z_vNfZ(Oli&n&35v?$2H?amj@QzHn1!ZP#y%%Xl=As)U%D0g}lYz&(;SN zrsTy#G$ArFp$pa(Jbvg;jtwXYkJ*UBWc3#ZAfkZkqr!h5S z>=1k)JZ&Ai?UU&EpmI|bMoV?I4@nzHFqd9n2Yl{%goOnX|5orQ5Bw||+{fl*A!Rd< znSj}bTSQ{z&N`;%a&X*Ryq9mXT}mK5&gCga+X)sq3;s>kD4obtOn~6YhFV}Vr#BZd zt#>l*K_$j;u-8l5&nnWH!Hm2^E_s|a;pfbXCo;tZFV!AM*G0o)glK(U{&|?JHlNFg zSh+u0 zI@~K?QJlp@VT@eKg|)N7_kkqE`=XUxB8Z*<{@SUDZl>wlJk$DA@lpW^seUViExt#x?i;JM$s$B_6kcQ`gcp-mfx9d`R}H6_m5z}on0 zq)Kjs8qSn97Ogl(zf@9Q+yD8ktU9PxC8|R<~AtlJgQTjnDHRDH(;Ri5#dad^qbcV4Z8$ zti+VY;g1&FdSu@$YS2>cV*&UmqB>oH$wDBwGs{6j>|d%_nLndd0tuLW?t&65kh9kF zp$fH4`G{U$P)7X+4kI>d>*OUL_pArL8?o-@LJWKna$2--rVPll4)TzZ5o;-%^lg@I zWdr`h`TcLr-Vezp5m(ylger@LWQlNYD|$gmKO19>1BOuKZY-|Fn9=rP1BPLJL_uKt z_W;aN*{+SOHJ>zrM=GOlW1H-D$qgQW)t)twpjr3sw{lpY90tsu>FU~;`ri#;S9SKa z4@xklqHmoHTh#xvb6zUt7LDRBx>Q~^b~)iuiC(|@w~E_Awd3}7a@|~bBPHLt;@%PH zr&`$1(>t}hi)EK?V%E8wTu`gO;0vPX`iE!+@*!VCut*&1JU`hc2S3^Mfx7GG=?V-o_52Rd*sz+s5?$d(tz}wnqohPo{~8h1^bXW?Njz9U$q^m$2m5 zW5*r$bcmuH$o0Z zbcw1WWQc|9+b=b$HK1#I4)@^VUAKL|pwV2TSKkHyvI^F(kXzjNCjcTxkt_OH?0NBV$2sFP1< zrUF3xZF1pWYy`#H3kX?3hBrFihH%M5>U$1>j2N?B|A8dOYMauR6sU#2{gMQQUDOin zufd>XYM6Z{GZQ`4<&gBmq=?b81QJ5nL-Ab$JgXEzGg-v*w~(ylr0At6J|me1djxy^ zG}h^u>V`@R2I=X}Kmkc6QI=7?mxod>%x!?h4k5W*gF2@bjK?j@YOBB~x!@FnYnGWg zr)JUw&Dj8&)>C<&u?>5!Ht5zFPk{0>=ymRqHFduf=&~;2fDyEF3on8P_b@_rJcVM; znWRvU>?F9^;*w9MTmH2Gv$h>ewLH@EGxi)Zbmka{xX~TpM0C-%NWOVGc0cq9e=@NdJHK;ck@QAhbHmiswJnBbOuGjDdIJ)m^`;X;NB@o9BJBA=5I5o!$D@q!ld) z+B*xS@qnb&=f*p&#lMqa?@=KMcLP%Tn{K(jE2qB`z7MVEdbf^JKd#W(^Jyp~Z}c+7 zoo}*^4?MAfIw4R8rg`fUX|P0q=?G>#_dp*%b1^?%J!lcfqr%XxCTwOLYMV0xMn#Vu z`O;F`n3%1%Y9PaTPG6Ba-6b!~%`cHjDw$36HnD9ymzUdvu;UT|ZS8eo_I3L{;pR=x zGv1c)0oWpV+^mq@7H+UsZVSUTv^G2Can z9K=!|5c|SpSIybs z#A$OKpgS!roBUxUVA{Ex;!pWbc&{;%FKUH2j7gQhUW3U9Mfk3bk%#CPi{cKrIBAUF+Ls${Eng3?`k&KLnq z6KzS_+~;=P+zma9$zTQC+G-0Y#9?pqLp>}eBJw_6eQ9ziaN?x&kv;_g8ujxdCV++d z(63PeUmO9cre~QObOIz2&p7}+9SV1Q)12408cr{9l79^i!J6=l1KXK-Y@A#c5|+w^3eaiiUzYoR7xIQ zU+Pu3eEbGcU#-A*`pPoU%N^Fg@80N%pNV{{MpiTAG4<}K8MX!^6pT=8HT!4$?qcSL zcKpHonh=T3(}IdxwEqv{_NMvM8XY0ngRZ@EKj+tvY9$M80f*@ijVfXKvA9zBCafpH zo1vZQ=Gs#wQL zmv6dVkcM}iFmY*Vm%@=Pib6pYMT@Y646fh^JBcX9IR)!lfBpJ(d)M`@{rS90sPdsc zg-yn?pCkPVgM^43zYX$C>Z$>_ExbF*VsQI&X4`rhvqZJDt;}IiX;~W2ow7;g#_!bp zt&ksC8Oo$n%kgRacF#*_3Z*O08~VV3Y!i6g>&1-rG4Rt8R=x?8pH(Cwr@JUW1YXl{ z@@W*O$Y6SAa+)o2GJD(`hspW3XY$Hobi@P@%qKnl_?zk!!@abA(Grwdy>uGzna<)8 z?Y$RiH{3C`RWXP(guKy!mgxLGGbG)P#)@SfL%W|erbd?nrRT}Wby&xU48PXP4t75~ z8>jE%x}VszNQHU}VgLdvSQjAxKXuH)IH@40eo-&9r3egffI*}GVfN`DrO|XU7qb1K z-!b3uS47cli?TW&ZHDeR0#WF+vr5VCaMI1F))ox|qNQ5WqhrI&iNh@z^hDibZ7oAo z!$ad6ubftbJvK1oKp&3$&|lrFGXxGyEj#hjLE3>f+KU4Pb8T4`5sCjq%Y2;QXzXJD zB2YBYWGn3q&LJ!pC7s}Cx!J5S zql-%goSv`mBfownLBe9-LW^DyeJ04!A=vNjrX5}N*c%i>aeAZefn zh_MOB)@T4}P~_-UMOPtxIP@j=r_a@J=`5i(MFHbsw||>zA(T<7r6{GP`im(pxyFTr zg(Q?F0RI!8AW??-t0AzI12+jqPD&7Ojq@Qx9`xKhaUhYhJB>i0-WihwqJ6{?BtSYJ z@e6>k9D^A%L4?j7my=oD>nmGPsH3BFG!Ex|bwK{T+I!!)?l0%3W_roS*}wm@ zaoZg)17;rR1#^Hh??3GcRbv2-RISsC(+>IFG7v`uw^P-iHCQ>FSuT~yp&V{Y@rq$m zidC5CDx$125a5owHq}-LT_uewbrX>8DRD>%sA5YS^x5c2+g&o^k96+o8>Gg!k~Si~ z+XXz4JoQ8CI272MLK5l+@`DuOLLlTH+gdcCV-nsuvEK50;TB=5du3d;4Vb&7K>k5; z-vF(s79k=gMpRhuQ_zIiE7eYocqF!f$LISalbo#VDWh0V4Cf&M1jpm5!KxAmmRbk* z?mt$xPQAJfIW_kb15eBx~(NM1Z@nf)%slrbbXnM(zh`{bAB?C|bAvR%;{_>%3 zNki>qj==%)+(~CcFMzdhuMB{`Gv#hvJb!Xws!(;7qhon z_pvL$Hu2_9e%iaKf*$ISab!uHI7M1i1b;$K7-z37htP%*!(LAMRnMM&N*Ub&cj>e{ zGl!xmwHvwpdKe!IAXVt?Ztq0TD?D@~`&U3U!0wu`zgfi;wVeWzOeP!z8?y^V*nyF+ z7d40Pu8ax*=VG+qoU09=pXu>z)QQ{Jc>cH`0MQLt{n_nvzA++ zWB*=dhMrqkUr+rds8kRm{rTpAIOBu_Lw#?2EErvjt-udALaG&HlH7{lyCh}qZSMeP zzWV544NdN|xH!h%>oNL(?+j|OHTlaZwZxk@Lg~!g+cwQ}{;v=DKV8{c#VR}`BY19A zyRNe0en0FY%?d12`>$)xHKqzoJ!3qs7n@$u!DVtYu{0@6z7L4Oh{Ll; z;FFXM(m~T!Uh;Svmnw3%8(3xyCdZCne4d9bSUie)@Rz>+D*WA#bgI8mA)&79)!^0e z*zbpN#d6dby`4=-9v*@R|8-2b7#`k=CT-ooe~-x%<*@^d$sLd357xr3xJ4-7J*_%LBY=gR`D=A|^BEYXKu1o&bv))mVm6>DgdvZy9 z`6i~2?io?VWWa<1yz;kR>5$GobY~i)Pm=U|D#P5zIwv*_^zQQ6Zl>g*QTKftiZ#=` zXPl>RO83L$yM+4Ax`e|7x4eH=wf-6Roha;t+xbjn@)%Ej*(RA|R@#SYY6e9rggfwap8~~xy&VV`R7GX_AF!`T0 zBO)Gr)*k9>hI3hWD(xh8S!#o=UbpOMkL!FwxML?m$aDJN2vki4eA zL#q2*ht?0f`SlU=BP9}7+K~Jrrz}EiDN>{!x@(h(ID1tdf4Iyd7u7@^HRLF6eXyYO z;W?Zip$A;rX2@-ZoU!7TVRUHv36Awo{0h1uPMA-#u_2az_jrGV2_F{l*}uL2_0=+m z$~~yGO-QNsY7BMZD>@nHNMJ37+|ZGfUfJ_r&yEhUHD4rSGwsQ^vixOpPWljEK4fib zBzQXZipqA?8Z?SYp(gwxPYe+-rpHOS;0cSY)SToZ-4G zoqC);oIcVU?(reD61W+p5I>Ggw2T}}9adq<%bg<$&k$v33*&;La$H*iHqrswI#_B$ zM)n+7D1&*8ydPxsHpc(;_&9u25V`m(2kr_!$vZD8e}iU&8yH98P-m2Zb8xD6pIgp_ zwwiw6>!mrUF$To*su_G8!%UzM@wvw!+;Klqd1s3J(0t7xojY-O(189|k^+IDUoh*; zD9)x=bX<=~@f$6~mPzfprQ^UCy}*&wib6^Vgy*FxTj9I6r1JjKkE!(*3J7@Ee@KJr z;&vkw`!g}LaP6Ul>kYF1QPe*VN<2vVlUVCWv7Lxv$)$VWBL%jXGOncm5VkcDtJ-M9 zAiiGTo{A@%UKA-C@=M)XV1KnQL2rT5i#E3(rnQ%#-XL0f>aIfNm(H+(dXcFT&7^8$t7Qd9?IN$sSi5p_jsEdmWtIvz`Y78Ntnib|w2;nM=Ql2yCP8gx0+bir- z=NuZTmkDEc(sdr|dWY~&FrE}T`2dEt>{+yU)ICiB)MMzHZP8L;9zXw2O{f#))7>#@KFD8dB}#W zh2fxt5{w84rR-_lts45>RS(k#QLNnRIvy)Jc@ z9C0M;oZ0lv!!tFUE7Sd{nx)YTw$Y)-DVmsk{TCWam|dJOSHyQ#Qh9~`BRiG44CWc` zty>C2;bahZg8F-JG}bNpqB^E9ozybZWvJ#wMhYR zC3@(R$AI-uER24<93Z`k}{q zm?Cq`fxrxc0THUw=g6Gwky)-ZNIv*BsI!*Dsyj{P%PCC&v#(xQw+Ec|du!iVR+9d7 z=~F=+PR6xRv_+ir{$C zY8HC+>PP4%ix;$F9FEv%Zcxof&#N@G%Z)sy$l8ciTW@q|2k_y2#}BFh{TBNUo^QqO z1|RW72guO$!_G$vUCBXrniUmP52&mEH64W!Lj7~A34s_ z6P}-!tDpEZNcjA-N*YRymO{%8Oik-!VR^Y$m+cxk-tW?_yP0l7YsXVUCv*VV6U|Ls zCdf_Zy^_O`G$0G1O}U}`c~0@;(E2CvdrjAoXX{16sXyR&DYTat{52EA{1I@N&8zK*Ol5ba#o6K_1ZoPY;SIIr{VtXyzUmEtc(=t7qUa-fxZ$O6UaOd|1J*3 zUp<1yQe~vrqNVNnQ}>z=4*YHAYT9m|+c_4`a|=fY5usa9 z_tAiw8IeJxqTjaA5#8E0u>@&H>*r0+Z!yc}kdAWB&a43k+OlcGOgAZ~#0Ebzl~qu@R$#NNm0`3If~ zORLvHN!LkF!wbJ|!RI|5aJxoQWZ8mS5Y?y&i&O@BSLr=N{_;det|4IAte84F6H=hK zi(!sfz_0wlMXZjU`MzG}ztPg}ynNvxw#sFiKdUYbx-ncHqzms^S(_(UWWQnw^q({O zW>-gByd4fpMA1^M5SVv!{zL^8 z)ta^tcn^|w#<=tEh`5L|8!R^2tWtr_z)KEyzxb*u`ggC*SN+*&u8koN2pqPS8+CuF zsR?0}t9SXfr3+*fB7Z7D|6RIw5kE#|<`R|i9mP1;T!0`4upEk5%Xnmij<6&#kQm>723{JdJ> z&1oR7h0rXzF39four_!3e&vM#kAN^(YF>0$QdA;m->Pd#y-M_5@T6y?QFEwDtMoxq z!7WePYi7G+acYVrR^TD+p^*4pN^G-ipukVxf3-^Te z9kcN*;0_(7wE8L|0LQG3sv{5bnPslf2xpOziO$} zoW-|!`#glzj#4Pfbih2t`711(KX!nK^a$3xFC-?0ZDs%OXsd&FDf8PRV}WMCrVnnF zN*+q#*~1I+V@I9HSAPRoyqP3hsFkQxR0|w)H;R_*3Ar#giE=8sO}vY~x)rV{YQsSN zr6SlZXr@>Tk|}7$iAOj-`%njAh)$-AH-zplf6W5jSq6-dzkmY<5k+t<>LCmP zw8!S&q%gXfNUgG%8n+m!{kG}pfrgx^;qTbxqH;l$sj}?5VwMNyHVmLk+5dn%N%Ffz zLcRnQD>&+<%ecBvp2VizAS;1#iPj#mu7mx zw(sY)S}nG*i8lA&E+plU+_-W(6;rd)foK|0`e6mb-YF8K=ie(g6NErFF~>=c$045E zNtSPmhL)lmae(fEDmUMzeYR!o->M6?-z(RAHRsi(&Gd@d|A=GKm{cf;*ufaW1}j;) zZ3*^u0bg*^g={*#Pl0yBt%EsquCNgb8J-CQd>2~J7QK=ug17q2wwQVWyO^zSd!zdk zpKhglrUwKBcg71)y+718s9PV~y!KOS*T{%%-lRT$zX;v*;VTYJoMg*z+obgK&NA=> zY1;-2NmZ*29UY-v}Of-ZFMwr=%b85{RpT3xK!JGwf;G z)AhDHyLxkEsv5V`_^vS^fWC#pDEw^$4nnC%jD9BcC6(qX68*{wSxd0FdP>hzuNJU@ zT+gZD_)i8v{uaQ2>`yAth8?JdN6azCV4*&7prC9B1l6;d?YxnF?%`qcdS|lXxos5v z8^j+>&88K=z>(GTPBX*s?R|?lPf?fhp4O5_xOdTa{OU$t9#|i zdvKC?U*X1S0m|}Ip4p^^uvc#~@O^ybr}#~UHe$)TJ)HskL;rSc~ znsYTv3wUGoFvrU>+@prRr1UuU&=HXch_f@pyIF@ERRbkDI&DE@Fjkj4H32S$=E)NQ z3RX^i)a=32fJ)ma`(2;>;IA+W8&;apIu!;6-KBo@=aHyM&nqFu(3*lp>Fbm)=&L$k zd^qtu2#+{nzPXD2H1$S$@Tk7SGkYZ^_biy!ebOLA3M%l^D%MXQAE-0m4PA1#!;;z!^TG zhDXsvQ5zR>juM^<5FN}Yd&rRor17&nj@mHro`$cmQqe~y-G(pL>))ge;*Qjm-n_zv zB==U#ih}BzbNzY$8YxQA($VdgWZKGzQkXPjUU%6aKQ86OaO0w#6&aZ0?I<@k=Q!_3 zS8jMb5^g?S?;`YYwHjh7ZnhW|8k@F4mzC`0A-boKEEE}jf=EA_LJOawTC#J5cYpwk z8(m$TJx|Ucgj2YsN0>nW4v+6YMcRKm%ijuZ5XZWWBi~Nm$r(dt6lH@9MH@DO%Y~hvxVD7P7FQ@jU7GQK;t* zvhyYvZNJ2A`0=6z4*GqgSn3G#ma8V*LTI6c>phB}@Ks}F@DywsG&hPu>?04#H;%!U zX$p1jHtG2a0XDE%XD72g#W>GyOWp=0(SY1_z*YnM7sZ>;2-AXU&&nT+#yviuNA5cT zKz4#+-VU`$5=6(kI~9((AXK7`cz|$%aZHDRYreriyMrMR^j_1fIn&qI04=} z1OJx=!18x9Mc*&fbTR|rbJ_`Gv6MFz$7>dB?RBQd^U@@{WxNid-}yW0hs8ZW92J=w z7~8c?U+!yX(s+aK_ZWex=bF_rvn}YV)Qq-+1zS6A=)}Ws`FlXF#>4B~^>)p0xkmf< zfRNw7n?cn^gC`Myz&WuxSOlf(tq%D6g|PKKvw75GFuPPO;7n;zQCcoOsR;86+s`P{ z&-@{j7qiAA9SvnFuagIARA>=8HQQyCdU02^bEOFSdK*qKD$Jy=n`=7S9Pa0Dtq*}# z`)fRhBPK!~a-H6*sr$3zxe|7Tf3(1jJN>pF_X{wXSc5gfZo~6`$#+s|w^|mz-FEoN z>~D-vGY>+x*G>KFr3$*X5%|qrSF8aW`M(Gm164LyAb-TUi!S|fnbd&V@i;C_-J6d! zNG6=;Q_EhKkbni@bvE@W=kMoldS!hr{Fyyfu|prKXuo3>Cu-Z?LGCI1gHUE3{lWxy zeSO+Ygol<(~ zM6!3TiLX|x{`=$*aiP$i(EAq?zKSp<)kNE6j*Cx4T3R%`do337IwHYuPAECG2J37o zLqv?w8am9!>jJL!TS%{cPtlO7GGC*?2_>qZ*V%DIO%Ci|)nN0mJc(q|8l}#ppJ&>( za{-0k@Wo|1*S3BbTggZ}H2Net(+GX(E|>u0@{>%Py&x5nTZz^-TKwB@MNT*qV1NT$ zI1XDJVgPezMC?_z!PlOWb%6QYZn}Sf&L54+UENG}Hrr(euZ=g|NLl%~y&2KrG(Fn% zTNw%)nVgGZ`OwEN8Oo9TElX+VwnFA}Rk!uawv7F|j2u>+lP)V6YN{TWOt}h=h)1MY zfbwn*<879ggI7FuJ8_5-g@HAiIqbNDhM(^9G>K=bRCme(BE>NQg`$=&e5x4i=2TJC zzZcoFSlgnwuJ(j2GMuX{Cu1i>vHO2;vJ)}4x;xHQMtF3Vu4*s=XqoK+mvwm2Xjksf zFZ(xrnTi&BLHWS>08JB*y$)WO8+*j;XQ2{GVz*(Stm%(qIkP9gTcs@?pOk_WHgyCX zJsDljZ>LUBqKqW=+N)0DdLN^bY2|~LcPZP)aUJLGIM?QV3_)AEYAZm;ER4M4IFXk~ zrUd5%1!v%w)iCe$1Y|m!ux$mWSa$sSYzHH6(#?wy!)o_pQOlK6fz-|Ml3&T^sZp}U ze{yvJB9dV1@kCv+b`LhN%}}w>d3=0lCw}5M%;gMe+n-AHfj?K{Xvds-%sH!o=;FPF$>tPZ7qUM9?JE zXM+VZf^7#rs?j}3bc66cjEwFuWZauMTPblUk=AWQFiy|qoTV5|8)xMl?T=swl|~Dr zHmxwv>#Wf6SyU`tqv#$i$B)_Tl+n!I+`K7Cx!O_+ywOLJ-hiR-iQKWPaQTJ z11SqB(70swAi=8CaD9vrJ`~T8nFNa;Nh`C(6xZHqpufKuY%L+cyF~}P8sniIA9MxT2zBuk-a{_5yf2f$WdtN-tJAFyp^Z)~CsrEtDnpT}CFMba*ou>fRo^ z4U9<)P>}@(P&EDP1&fGFdr29V=%RQLkOi79j@0hR;!Di?>hbv4QRbC8Z@#L9Gemw> z>x!cGXQCG{=$WVP>@KefU34S@?Nf}XA>wMcw`A4>Lu>Y=t9lnN zT9Fg_I}L#z?Wuz1Vn53Qu%1$6-@S;hp!* zKH}QN#YI^cF%T5EO=m;zwkE==sU}{-40o7%Pze|HUzlH=VrlOc$HyS2eV<3W!{$%b zX1#tyL!{}T+gZRrCE ze|^th_)yx)??N>Cr5DLx%KpgMI{k$=lrW(Abg7ikD4>SAvNx$7W_w|Wty`bD8xB%x zdH;r|`2kk>-H3X1ho50AQwBKw3O$Xy!KH>;!pel@F+VCZyAT#ApJ{qKdj4Y!S~-CUM?-Vr!tO+8L5A_9bT!HC|G39o>fq~=&z>U#^-cKK&DpDC7 zP%I)~)WA3kIgM!qy^fgTpX$x&;w)At+z^$J0EN0koWe#Gth5MM&pWl2Nf{X&DzDqn zHiL=jDsT6#tT#8>o+{D`UWr;TL9*dApWW>ep46jd8P^XF{!Z{!Mmas=`sSwlPXI}U zbQQ`}GuEs_euW0L*SF`U6(!0GSSZkeCJmFBaZm9Ht zB~hPp7oCcY!JHoY$5e5>r_^vin zIj>th=LU42pn}%lN|-<0bN4>?AyEY$^LwXiGBGdjbM%mA=$`UWilx(r5FV>%N!B*s z7GU4YlqH8*iU*Gh9VMhePTCYR^s}hv^`79f?v?w9lZCnObzL~K64w{<8E>PDE^KI| z=gKzoPq^1d_R!&SZorKNI47t!NYLj#}El5M{ zPnZcKno5?Pi4~XuvJ4wtz`tObBsi3c#DUtMi;55KGOyd99(!8%AP2+Ba}Z_c7PCnD zrp4P|H=!r;3Ts4>^#?z|qlGmA1oMFgAa(m23y#V5NB~Z`z+jyMiS=nLk9LM&O{dxY zcL{&pMa&9zDBnsB-9B>p5p2x$tl^^SNCv_X`Oi==E|Uj%dSal$gvx-ch0S{3+eqj2 z*!F#-Ior=Xv`f+VU~A-=^6upO8?yTgvD$ULyOP$Cu(7GOZv`UURo1l0rz?VG{04&sdx7<_T@*g$6csf9wJYDs@~XDiFQ7|?p#|JP8Tt$AQM=<~*xEjFl> z=m*WwI#ej}e&*&|N%X2#DWm&i*;M&((L8IP73%ZbJ$qi41W$*j9u?>H z34*g4wR%cZ2<|U*bf=aM>n+5Nd;(M8%X;MBCVcnbR}Z9nU(-1exPhU&_9B_ct=iBG z7G-IRDK~^ce@XdG^{0*C8jFh)p@-|p z{O581H<|E%dn6*FUP zv{%rIGi>%%j|x<74EBb}MRP?E-%YY{p0Z-j$jC^d1#PsduvUCy6<&u>k+(?$0~4vx+nN^!M6v++%TwHKcSZ}^5iZl!jlu@A>>7olWsdf^n17&3`~YO@jwBh5$@p-_gV+r zDc=pCEl8AgP6gL(LE+R4cRR)3*}QFaz&~&{^84-H+(Gkj$AT`YgmWMO+POn#(A^tIe&>`fCG(KVNUTVtsMy|+8V`XC;UvfkO+PN&Qp;@f=nAxEyLw1?_7p37n9xG*yB6UIYQ<&xi^m=~!lG#I} zy>vDByVOmD&O)oU)rCT*&S_@L{jX*~^W*nmncO$SZ5~kebIFzP$3@UN=Wo3?YODUB zYF7=Pf7ypRYGx(OrQ4H32I*ad1sRgK!Z6WYM!7ld(5K;1=AhS39y>%n&q@B)$uJCwQV(esk`uWp8fj43O=Q;aO zEwvEe9?D!3aRm!*WH)y`AK1!-+yHk#)|bIOk=}qf!tPXVKM$qxQ;){AR;4bcAh&;ACAX+sdGID<>l4$|77AoU2SEdXD z>>_4NNU1*$U%#}y_V!uVv>{z=R4nDj;7h`h#)Iy?Aq z@2l5=4`LD-nx)o6=yA_jPlQ@tRgPPX4t7VDV0vb35aolKYp!@#4x<3K3btR_?QZVs zYOC(x3^GNL6N)bE9*S4Uh8VJq0tjH#LxG_@AP7QoFA@iN#!dBAg#I_o{omi^-`+{L zuu|UdEmM*c!(>Zk!y%RK#6-T6MeX!qA{8@wW+cjiDtvMR9zT`EyBw z`9_mYwWi1U0cu=TEmKvCq8K=s<>@Y==f^JWiSJLtH!h=ob7xi`r2a6j5mc}%TTdG; zR)ueqpC7Sn&#`=FpcgUEd)n;&oMq|A7zA&ofRi-x}rr$jJR$jgl=t!mNvb{X(rY(V4&cNi^qnzt(he8G-{OO3vY zI_Y0Qtt12ITyoB&LHjQ4WO*U})br-lOniLbKdh$xhOylA!DfW}5lyO?mT9(^0tXsP2b4kvjGJnm>1sFU3_fhuJ;&_ES!&GMwIGE=NzR*K|YZsc-iaWneB z-%^txiijBqd0Adw8_&VFAf@i3&Zh-koWA?(u|mjNn`x@(<3>m~Cyb)KAo=8#P^o5^ z(g>d;Lm368>Qkg!epKNn^SuvL6?&TpSQOd`hw`+>F_+Jc|I0Gr$vN%hdpc+1XoEe=Ym-@J1yq^I(;B=sdZ% zCj?4H^M=N%4}4%3kb)FoeWXsM)B&ykowMS4wdr0xnX7B|bV-npdbEP)0Z}&ZM@76X(VtEZ(^4FQnI@d z^U#uV*3>!bR^Yjx&Ob3$3Qj_SKWw4QxIQ)#SOs=w3}kkX`D#337KrT8r+fOio?xXo zi{H6a>`{qVk4xmyl_wtXqOp8)m8o;Rs4!)@-VzG|5H%eTM*#CTanC&#CL>ZJ&85N) zbquFIz?TP5e}lOalT{3Uth0r_i3H^${Uyku`6^w1?hP6XblRJKF{wL695zVrAt*ek z9}Y?%gjr$Dop^Lpm^{n^Qa_8`1V>j#@3sF)P3fZO(tg2@mPk8(F6M65>N0$Qy4g?c zs`}c7^*YZ}+2dl(4$O`Vs&rkiL3-)OhK1%8TC*b!YHrUS27Cp|eV%Hv?D+*Nqj&^J zxJ{s9m%9zdUFOzEkNznCn#D(Bu#k)UT=bB9u2=eJ;=eS;t)T({zXL^x_qMeFu7a4e z{m8DAv-C<83*c|5|6zCAuj^=L*dT2$T^%3=rdc>!2tQJd$p-pQ1Std{ann3|cqz5s z*_Fnk5tWecFuW%k=H*qlDsfZRDUBzgsV$H0Fxo1kr%%1t)-%`uCQ{p0Ma^p6)%M>& z=*Egd5qZ%9$@kyAtXyRJL^QVaO>Sg4QA4fq_5NYm?@(jQEO%m_-@j*8fp)H6FF!x* z&KJJh&9B)u%cU`kOz<2MysvJ4G)>$W`NIFmUgl)IJ-FV+Cie2PCQg6FeWy)Z?Pd7- zAjoFtgzt48zJUTA)Zluq^L(P&^>a4`Xl%XZZW=Ctvs@}J0sS_Mh#0y_CNKb=;aLlA+(IghF6{;M$ zuTK;QAhgyG3)?s>6EO2faOJlDa?J-zfAMZObx6Ot6R zj>x|VPxI-dGrGb`+{~xJ8z}v8Si0-mIZGMfQ2R!-W^~LaTX|3UB7X>Up1A#9*|edg zuji}tyO}3|RirpDu>Y4KHT6FWx!>U9Je8HxuS)Id(2NlXqcMkJL?!B@0M;ioaB zPAP$}wqKszr`QmUB~(Iny{`ZKivtg-+6huV3i#!Mcc;t><6KzU6j4!=G>I_ znoijtikUWLR4(^r+a45K#ea&`i99aQoF-gEHc)Eo)uz7s zZLYcH=9mtr2+wz##9sI~9ZqEPxE)Mnu^sXqnveQXg;k=E*Had?&YBKC%kEuNUZ%9F z9^jGL2I`ev^u6-%so09gA+c5?HI-|4ux?MEa?Rj%M8i8PYp7FU{lS{qvr+-z z8^pmoN{}e^szLHv?P(_@!^AF75lSU0;=X}(fiI=?@0su8yBxCZu$yO(3s2G#`j#x& zp0$^|Enovou>p8!?l-NuJ_;Ug&lgCW$(QRd9gr4BCp?;}!$W}gysj&OkCRq|bhpu} z`rmsKl^03khl~jYfn-4kXXFk(-0ka(Y8qrimS^@u!Osf381{bRE+J|Q8Y82b`YD>6 zzycr)ntN~Fh6r%dJ_xD1ragbjg@=E7E!MXa$QFB4!XNkJ)Uv`ziM_y++wu8zz8k?cG(L+UPU%IThaoGaA>;WMd%))(5WH!BFyhf>ug6LT7D#hcO2cB zLX+Tl*&!an9~Fegx4QSCSt*0JHz}-`g3BHml zq%1vI&9ixXeUN_mFy%`hv`qJ9C(>NJIY$x3%;q3j1mI;=XxsR+ob#t6apBwPA5>Q$ zvgJ3&l-D_C^YSaY{V1}?Z^JG`h0G=Ja;fAzE8*z?^A>S3A#&UcsDQpEERW<85<$FduGuD(^tBCJM$(4q&2BED-$$W3Q*`Z_+(l;c`I#U^(=trGC4<;<<8qD!+ADEc)TcWSkC80Bl~ zDEo)32KN5t1awNjR52BR;fn10lVFO^-V<8%0N+Q$2@~E)g#pj2OzZYveCH9`7d>XY zjpqR;-LDumO!%h2Z}Wjl+}}{z-5Gd`SbwDsJ7|P1=`hcxx(E*cwwwF(COWJ3L)MO% zq6T-rsxJ+yt^wvG^5+p7h$8S*fO22@;XehJV=I5EkNgTkl zyK%^)7(k?NqP2MZy@a z*;~-&_F$a$V+%%Tij|FRAC%3WzHGk~^m)44{>o5_fwP?dM6mF?u=^z=DAr?qlW_l1 z&XW}>VUo|Owg7=OV{l^4WdE0{|N77I42~60s;#VNR;k@tIh$$YGlA6};uEb;h@41P z=5j(6-_w|C=|(!fPGOlT*yzZ92|+E?h$Qx$BTgEqiC)(2Q)n@ZhD*83#EaM&e5}W_ zMvse=5{lCQ61@$1#)Nyh$xKn}el48?;y@F~!{4`y(7j z=W_(er^w6~r1Jac3YmPV)va8mr-8G*rwD4erdL?A6y__;<*#{iG;(vc{AK>k{i$*g z9k0m%Tt~W0NW)e-t#u2aGi-Q4r;=(vLZ-p#vi_7$vDGNMjLpJ{;_;$yv)tk>j?v`d9CUjrgpnwn|N$uu@%bW@*^v0b)d=C|otTs^DPnH*Gvw*3S67fy>b z)BQd$VydA4#Q0u<4j%`!}u}V9bkeBXP9TE1lnQ zko@M)q_@04SF-o73M^`;em=Ur?XScsC^AAykC}d~0_l#kfth9+OgB<`Ab}d=aej)s;;&^^FD z;#y};bEY1lNgxKTbv8Kns5q2@y^5^lo;g`QV5rL+eqiEY$~qX;%jJT{igI~NdLX@w z;`_x5bzL7q0!GdhAuj=#Y(BI8rr7Q&^lP~U`23SuZGGZzxdd9^tt zGa0}zxYZhnwh5)qnQNpNvdE$cHdDn@7HKy*GT|Gt97m*=(acZUDKJ?XeG4f}kuI7= zl}@;YCJ@6k1=dGKJcVBOsi*`mLaPu97P!qVOhFy$&m4m?Vb@4o2E;5E-F0Ff8^$X{^`P?p8Ai-Ond6muO4!{*kda z0l*y;ljqmCmn4A7WH{?k*UwL?vt_wGAlnQi`lwKcz zKo3mCxy{6m>?`sxz`~^S{AdpfO$}1{l9x;lJINLh!8;RlIa35fClXV1NY{J#AZM7# zC|tCNgViw8@E7DA#kUu69AJPWiB-MWCpS6X$}Ia?cQu`Faps4mHfcB{+EGmP@7@gc z8DT)ZbqJb0VM^Z?4Ntx0zg&s*Vr_12*+{u>6y-;mFA7#pD}8e`C^kFwNIV#R`+a!I zbIdbh0STa0(N|@};=t0;bIQJ97b8mbE*h5ny11`)2n>sdMm-`-@+Sf~u-}x{`aESI zG7&H?X8A?S5p(Fd@lt;~>YWlLD74D~(*Ed@zv%tiRoGlJU}d1K`NhYhca)tbVeRqh z&sW-G(=|W4`9_S%UwTmoh++~_f)*P4a&RY<#=$FwE7Eo%bU8HR%`2Awb;ez(m6Vj? zh!CH{`pM$at_;(}E*U1y)IfvTWR_y^eL14(wx{^5V~s|HXA=q-iprc%FwMdEVBS6S zMAuNo9+S$$nY`#5hK7oYUu9bvfI~ae$bd(Jy8sn|Ct&$UWDLeN5!&!G5fDaE$n1 zr_NYU2(OVkpkJ#>3zBy0#%oSzFvmeY%JtaU{Ni`(^@x z_f-IsT`SO`pUf!g5Z4`n_p0A5PCw3QEhJPw>8ygw6EioUjGu9KN)EJ>6AdFCMw(gH zEc51N@C}QUiHNY*%@WqTe(8MnIk34C#uOZcdMzd>G?Wid_^FYPDANtO%U( zNYGz<)Ec=%e+GzFZ_h+!Q&sr?9~#0a%H1Hc8LCmWv7!^aT+Ug<#p*3X3#XDWj zF0-=4x&xZ?MY+YwE7-ylg_C!^X|5jGyw$@sq#gXYP##NBib2WP0H z_H87;GU1Mrq1+ih77q-22E0 z$LB(_z0x(;Kwq!3XnE5cOqfKqJPJJ^FdduAcQ`uIp1rE7Z${;8h0sdf3XUCDlT~2? z&n1Ok(5*g;>zMzG9_tD!i_?%Hwet1&?!!=lN2KX=mUx51qQvuhWLM&+-g}pfQAndH z&y^tg=F_CzP>Rp@z0}7y71kLNe-noK$-C)`*uD5Jelo?b+eXitXP5{f$JPGkVLi=t z%0$pmQ_kR_3MPtATR0P$RdZExSI0Y-c9&C!HBX)RpI)HQ^AKl7_pd$Y9I?2Fjh|BO za&?l9L>rQm%w3)jnzS)^{4yRR+RhVX>CE1NV?=!43F>@H)aaI|MxuC0`FGiRWro+? z@dCchtIs=bEKXv0`aM5q3#E>D_O8Se%Djav+vl%0>4v|aW}KK#i$!7@mVp!p*N;bq zVYx~6BQs7&J>ID3as#1HtK^&N(@x6AP0y}iZ&D~^+G_gbWl+l@=vaL4{QEg*Ouu|Y{F3(@q9UxwQBNuNN_CvcnQ?to5F6V9AdKtX6qOvWz z4ysnM{zv&zfcGX|%|=#~n7bgh9)n0T1q*E~?cO2w?_1~MLM=nwihRU<_2^3fGK=t42!0Q3##8U~Oe?~~lAbohpfs^2 zx~h}V0fKYaA%~KsriT(#)BVtY>XSrbV8K)T*qb92p*FSAo6M;skHb|BJ8g7k_>SDH zhR%V)^*0TK!JmY7Bpbij+%HexWAqP%>2a*ND|* z4AG}A4=o~<^-`PLg;ZcHkLzcH=jl(Rw^E5%R%TYiAIfhZ^0#&~sbhL%SD2OjwIGh0 zMG~PJmxipoPqe}%Gdp+BYX-QyBXGL~AeT>A!CUmCe#`68G68NfRQej^yRYZEns1|{ z|0Cy2pg}WNiB*Jdh&GOfyz~es+eLZus`S@Fvy(C62F8XeA5VH8@_O5&Yg|23V(c8j zG?@EOzLJMR1qjjeeb4uD-MUj%;R|Qf)ey}-MG9kc162c+uJ)X2u0e_$1-IlM$tM3j zV*=y?gquv#=6>#)^emxP>8ZkQ*dG?lUYsfKOn@^JzX`X`vMqd@7-N^l&#C42q^=E0 z(=zS}XFsn_B3UTTF}eceOmz_tsqwn3OMi_fG9cI@zs^l~EEA&Nrn>rm6+G)2;Cq6u z7)wwT$u)Re zcQ8*&OS_jJMl^E-ckma1DJl2kuC(5AwhahlU6Zb!PW^wVdI#>x0yWz5#I|kQwox%E zc2coz+pO5OZQHEaw$-`!zSpDsH=KRO*x%Y~&XwwaENgbKC4a~Ealx4O?3C8mKKqX9 zcHQ-S(0*RsfXB03SAc$Tf6K>mgEw!nbB**J(h5}fEQ~1}{u>g97iL@<6_{?~3sb|X)GHY1g^&s%$36H{sf`gh1DjnuRfok3FMk#pHjQyGo zd>%pQe$F0zmN$oKCaGQGObkEqM5NTLvPP-=Q0M;xbB!Ef07S!I%XC|}UG z*zbJQpN2U8L^iM_7>okay2R@n+mA&lmq|#-dK(lX3JG10PZ>y$TFr}cM9n(652obF zJgYqtdDSVINXB%$*<_|S5!yuMhOg5h!;QvrkcAQl*V%Uug{{nOSLEgm(UUT@ro9lg z&@=R7;oqOr`qYj{(k|U;0<~{{X(3z2ue~=tx38Buf|-b5Lz#*4iQ?QU79h8=ykxup zgQPF;H4icw7Ir@uye+b*iET}KcPw{@t3t=aY58rcob*R3d!g1ED_`Osa}cNHLXCd6 z5j5A)9{$&wyYXRl1>9krq9zLABisG0Yx%TawrPaLjH?W!baoVc^5kmc*vh?*{-?zQ=lq-tpM;2 zx3ssq39)oB@NkF9*3AxdmITp{j=Yc`Ve8ocbpq8xrtnhlc_%(3{7a563KLEbzEKkD z>S zXLBB$z`~Qm^M~3_uGfP!eSMRpqXN|mS@Kk|I$<**O9DN7u5!1+Z}va8vhi1PuDvfJ zG{v@#r=~)S`oD!q7|iZM$1a1^QU9`9AxEp7v|+U|le&gY@>ph1V)BWUv|xx3(7nc_ zI4+d-@oa3*;cMNdJ)IS+qxB5o9<)I+w%z@sdGzgm%6Ysc+ehIrEipMSfQXg*V3#i~ z5#7oL#|0DT~z8&Qy8B zKS}K*LlR>8c^cck>PkHqPPCy+FP$O{Uy{l(F!5?eFgI#x`uJwDFkgx@##YIMBb+UV ze?Fwnp)bMD>OfdGZHBsvtj|+-hUjf*7CmeeOt}r&uu}Ssa`=~~m;CJ3Sh&hetu7|KyK8}+isFb$9ZMhI^DMT@GKjS$FITZ=HD#Et-ZS@I4W_(aCr-D?!HwuK32c(nC>-)KkSb*Bhit4hcjyFb{z zJT%wpbe|D)N3cHk)%>KVp-Gfs=vr*H-R@4j^)%JnCd=I%s2xXcz+L_uP zmZS{N(?+igrW3vNpb-Q&lV?C*+4-9micJa~JC5WgQtWytxo-f4w_)riR(yPT5sxTu zH0j){^{kfszT~K{5L)ld2U|1zk*Ppy<|Fl{D&|?s%xH)LyyD4}D!mAp|M+z>u zFh5|Ml-ecJoDcF>bHM-q%*plq`9PEUzYQm3uv(CEwAJoB38F&4gs_kAs`IhrZl{zt zJY(KUYM**0-3Go7c!V|4+S8|5(T%X1cOF?0&@>0qYJ#pd;ZROCHN;n|IMl=^nzPdF zd?@o%jLhs^cf3Z^CC>$_P^+qG*QDUYFC^a=Hb33<=N2ndT)`2~)NPsQhR3Kk<#z9@ z=VMcoLSt=6lO$wHo=hBAaI=1HOa5vMK`Jt$UKTVHi*$>FqGvrN`=iyr4%bc3;f@F87w9-l`nnJho2^Ei4#e}Flc7Kmpys56XgYN%uT{w2DZ>e_*9hI zT(+7Vt{@V${8evHc%LMl|5%iz|@&h(1_mlsNivZ2nCr#ZN)U5jl_L~hzCwf&gmoh=D z+i^{TN#f()W#Yd&SG^VL{O{Sq_BTf6ZT~NJWF1@>4<5dLfGhGaGNq}bshL`vgAy~Y zxr;n4?n3?+;y40GEOMU*lzTq_*2vo&pqc;ybC`?p!(7yH-mN7F&qWfEo z-Z}tG<~Ql!+p=e?+y}(@pl7ahXK0Ki5M{O)AOLCw1QVq%AlZ>W(nt|D?K|tVEP>4= z47TUiN(w8}w9xg+Hs5s820eUlYGFEgo?QLzvy#po)A4ef{G7@+pSD>ItU+n)Fk^*( zdhQ6277J}-9Wb@@x!uMdyCXlZ+eJ&gg!Gc%`P6Jo8Y*a7Jmere(d6%PBp!^GH)fD8 z0C*IMQA<_eEbQ#y{Tkl~4unk~`%Z@=^-at9TLWOYK`+ajVs4D}F@#yAupucfHdZcL2E%=UDB)fTN)%F|$g;2z!vvHT~GWl1Hq_{=MKl@WW zX4gG!EFR-Zxoh^D>;(su&@~Du)62wR?T#Vu`jSR1V+3+?3oS)uq7xA8?uD^l3~3iN zlB=^mZYQI;!?>M8b|9}6W_$5fxf@}YNIg-IGad;Ms|N42E@mZy>O?#g znmRgJcfj|b*S>lgT#m&bH=zQH81&k!b2s}{ri}MajbZ1Mn?I>Z=~+Tj?pfNy2KS{9 zwAEU@*j4L3JWed$txLrBA%NK>FlX@gIBdhSRx^thyB7oRDtIcEz#aOE2y3$=(ZtD? z-Gj@;IJ(zonTzoYm_+JJ^CU?{M0XjMgy|i~YfSOZkf$@qvMdg#;W?wA%(G(tTSi$A z>T#r$rihlY6eTbf7tG`Io9@IP8~I~jSv+2U2htlP3&ilJg&8R5z`k~&u^P_6x!E0W zO0n~A$PqWv07~^<@>c-9tB3^i|ws_?1(x-|W zJ3=j-;yLZb(ozw9j%hKQQ&|El-R8fr$p5E9Hd5dR=Jo34)NsWeNEOFbW*pUWy6O}- z7YYOdNjBnrudF~C8oth74CBwBu`fevHVn!2b%kH%d_lO?^vK1)hpfM?t;a%x!#2!lhABYN~NF&jRppFx0Eu(Z8+xbZj zDK#4OHMTxF%>@7Gzv6&?3tfTgxXI0MjabA|*}?mMKYaU?^A<(XVpADKj6l+z)mN_) zClrjtN)uY~Mve1;cGc>rz!t7#6k*UM*Id7FMu(21JzFUBAw@Jbp7zcjKj`m^$B+ zRPD7V17l)1J=)0A^@0OBqxLVW6|9KJ0Dgpqdti9NfPPJHM^@)Yl#aviPm5h&Es}`P0R#>?pbejNr zdU-M|!|7-^y0L}6(`}l5TMueI zHjmzw(rVEk64OQM#D6c73C1#T5SsRVM$k#0`(^Sd$`#J3=MMEx1)@(w8q49PAXh$^ zBlQsIYIyDOog^XxsOtc|RJC}RaQ%kNNrg^7CS>ds%N4@uGET4}YF8olP8-v3lBp^~ zX#;Hh0254*X-r{fAoEoXvPUg=nSm4$UW>eSa%{R@mfHBp`ZWg$Y(WOI=me`>@#el$ElGu5OfPJ`5O^arFdgk#$^f%TZ;H zvdww7>dkv)s{{q~9!exXi#F1Wd%sa}0eL7PbkoHyqqFr(Orya&dKQG*T zPB#}=Rm3sy<8DpQ;^D_HIVp2^6wE>M7jOuOBH(F+9TolTP*L+@bUDrDAMLbRX~zVp zX)lsqbLe9u4%|r)ES6QoYFBTb_el9V;Jra%I^tcqvIV&Hj@&QJN#Bon2J2KwTpy|9 zMWLK-q_^7Wr{CleSv&8-)!lnzjwIo$J|EXt!4@RwKoaB(6bZh#nfxaLf z2({buFBR+@0(|l)GFd3n&LJOgJDsZYq!6N=fDkNcITmdam0Q-7q^tj40tr;`F!Q#* z|Nh&pLoR0W`r{MIb^dF*_!@gVy21y4YdWZfL`CpN9TNPl)bE^nC$!n;(*q7)fVE|# zYU(dbC}G;F`*D_;>FE^7`DCvv)9hAg`HSF7gZ;jE+t0>54~!cT!5!w_w+QVe#pQmk znI4lNjR^G7b1s5KM3)h^gX2+j=miY^DE0)IOC#3s>&_6!R{y|=YafY5_bN+BSY);y zZexQzICzXzKoTlE6aT^OW-*fpWc##WK<${7ujnJ%;iLE|YNX!U@V+bZvJq22B@s8!wm~G0p_@=T3#`Tx2AZn|4>t%ifJn2qjW-CJJ`sQ8qqYpi_4 z*e|z8udO^K{tzWUG6hQE+;a$tc*a~P&dFL{k3QAn&T9F*%J-lKm{42LsHnv*6m*U? zdg>)0L!kKrR4l4ifHe6Vj|=BI3|ihK zkN{v4=K(Tl2B(#l1;mtB^J)Oxw4|Hu$a?t}wh-`EFcltc=ccs!Msa)T4!@Fv&DG}+P=OHC<^WM_azssf z<#86v$f!krHTZsI43W9i!UlF+mCA_Min;ranetp>5YofMj(ob=Kn zRHbJwAIAz{fn|gSLR_eT^U1>PTSo=Rjq>%0N7_;;wDK5R&PTL{qH>4ZJ9%&Q4BR~N z9(0%>g}fAXp+kDt?mXI6a?jY$N_X4XMO(hgaoe(R*#?X3G@TB$4{7h;HvknbCDPjccOa8)AJqE{3AXF%BjH@({=fXyS-}~ z(pwPleLY5TmZ_E~nU0S6cRDRAIJ2mzjFl&Wg#zsRbzbzXgs&6Bfvb*h2N}cky83j6 zIP7{<^vGadW8obSNP7JzLz_(d;VUVUD+ywRqwbxku*f1>LOGWFt)) zTLEmYxv5Z_Dkx8_5jyZic*op5WpQT??QHNDIL z{@J0~Iclav_P)`xf}R79U(3DWLX-2qs76j4NULzpm;DK7L}*k_=C=!_gY^0+@EG__ zFaZ8#g=g&ZrM>?=kA_6m-{fCleNrk$^|IlO!``MI9G7lap+U~Xm3?Mm=E6QQ7R()h zonsyQm>KmZI0TjvB@4-a%2*4jO*}eycWDuV=H`aCJd_9p*n2OjmswNBv9LX|QPPdo zg_yWIo!qZ=AkS(0=;&w+Y}L3DzAqLG<3*~!5J+r@uk>=@7BVl1y9Z6;s=+S#-<7mR zp8j5>+r`?S3iX=hqp1ut85xqd`c$l=Bkj|xO@BX)GAf&6~DfB}VBh%l1?Wc*V2!h+`=AIGQy z$x)fvjC@HbB4Kc>qR06G$qmYwn~b!%7Ge=LVwQA{y~bSbX=go);)V^1zdhwXQnl8c1zK_b}uxwoo9u$IT1{)N!fqZrY?UNj~Z^g8KJ4Cx5PEv!Ush)KmYk(_PwGnyiGL%_+ zvEXZVm!9>KA#&Ul*e}-nK(B}}vlSp=$8L;AlaF<&T(x~dCj~6KTNnH-^*Q!Fy-ejn zQ(`6KMw6J7jmJUtNoT9q-!wHkEUP+OKKV4-+>0(MR!ewWNY(v4ST3|aR@g~FOx>)X zUe@X_mQ5UnK4=cAoNc8XduNaDT7kU$4>y&=<#6FK8T`R^MEQA{S8)sH<2)V)^H2^g zb6(TSWo*Q&{}Zu@afu~lWz((wT?7AorGInzUEc2U0&(rOK4^YN@clqg|1ctxoQgM( z5kdC=$qMw0Culsw^EdZ&Fm-MhL`z5iFWqxF#i0Iar?!P&kGccHt60V4a_pBV5I|4K zVJMpmmH;&rxDOE$h4Z0!bpb-=Eh)nu&tckF>}gspXCH^Y|KzW~+Jp6YI(knurlb^f zK~=;62i>|hQQkO$0a#9m|L+iM7K=8uvvVBH!zQ2qboXZwr~qOoi|ao)^^MzF4}|F@RgiwRZ+ zFm}tGO{q~vl9}qljZv9*$WxH38`i(Hs>_-zsRX14W)lS2KuV*`(4rjL2Upw*NttJP zM%g3SD#I)%Rz=EFGVTq8aCROyLes{fvvj=kteg>5`MM$WY0cG%2f`Y2M-Rs-_l5AX zVSsz-YdPI8dnsz_C};7$Up1@?Y`uprJWb&6oT~OdX7Jj_Iw@cHI3+c$@18KxZiEme6bh5929y{WMVhj zYPl{$4Itv=Ti0YN2v&e@89i*OjeYJ&DDkLyH%j{&Y{FO>DwP8ZRSW*gn`Nr)|2B8E zYSsPTXSgZa!*zz|zSU=?ZLXzK^>JF#GYh{C-!^y6wz#s<33#0N-#c^( z2N)|C2M1s46b~7CObXy5K{z*(M?mLNrU)pvW@GZucqC}2&_Lc^YCX(rXkgKnO8M{D zfZqzzcD&};100$JyA$b*U+-hjQGi-neWqi!y=fP|~ZKZ#V+Iz)(tGegVpZhw(YWac;(<-=|aandb({VKmJn(S$3N{|5 zV3X#I)nf8Otiuq*xvwWj=_KGYf(9KdY^Y_D)+C^~h^U6g%@er|n|qJD!7GPc{qcr; z<#2tL^v&jZ`Dt`1STL^;mo1QK)~ z5B>)Q>DjV~_?RhEo?f;&=T>nY;_>Ge4MVf}!n%|+> zKyJT!fua0_K}hxKmyOxp_=JeS{DOjRObf!8IxTSk$_jn`OkhlSpZ=f3aGyYlP+voF zrja{EhC5W`M{u-{OuzV;vw$%@8?K5VqN16ju# zUphd0NGJ)GCl3f6iV7Z7{*6GTdMAThN7t3^Vv5u(tKj{)X$Fl4u=Biepq9mPgdGnE zVNaLAj>9qGzheIKAygqHdX!a zu=HPu2L343`tLTJoRa>edJQGz`@&iH?_A0}7@cGcU0x{5w6B2y`^^+xB|?$vY@(m< zJu!xrPrPsCuzK7Q%pRCH*vuHlJ1aWAeUH9igohnB{q=(c zcQkx>oEqqELGk|y{x#}sg1wH5uSe&>WXQ+Z-k--wzTd+>9;bS!?V_M6i3Cac$aHMFUrgJ~Zuyu^Sp6ta1#f)Zl24lig&;bt zIya6?56AQGG0I*fZ;;k>>a<*N{|SK+uQ|J=Dv{?qz@tSbosgF0hv@#2>E}rC67hLC zB!Cv8w^ApaXJ&^~BH9W4yf^j_Fab_L24eNG|MA?Ke4{5j-S=l4R%cu>|Cz2&OP=GU zkl{cCK69T6usopI=q8Tke=mJ&rclk`3#5|{D@~|&9x+#1<7Eq&0;x?!dt(4&WVgDB z+nK}*wB_a15Sf)Bl$}&3^AD_%8IQXkWOFlJIhXI_0x*1u)JWAD@_~-rn1-0Og6EJ_ ze@MMF3z;t91Hhr(^Y(dm0`TvNIb<)UOOIqVN_g&`B^{MzM$SBh~b|Vf4op4oz=UyThignvjEsB>4J= zTsj8~x8^g;qNgABRt%b+-sdWJ@NZLGOu$9cu(A*Q}qm}P4tlcA)|F-&1^onHU zt9n5A{#kkny4cCavogH~`+Xr!V6(*cDs%v@`Hghs<>hdry1;xODcbak9`HCB`Wq)$_Qg^+@C{GI+}Pbf$TH_Q7zs3_*=BdMVJeskWdJsBZnx&NO5cw3BW* z-6LMo7fKJ>fRo)hklilWfSc!vbJvpuq|JPZ%w;*KA-#3;RsH;*oH_2XV@1YVxytDP zEToqR^IYM1k$rhwY`bkVB`bxlg}zg@$7}hJJ6eD=;8vyJiZs|jxsMQD; zvhgtJ725ohwRELVAZbw_c;zYI(I&9)*xM_ZLxQZ;vh7KXy5#qN{{H_3Arq(=QKmDy zuIzPh4F{y$KOMN4cP>pt{f(mth^e?vUus=!EE<4Lgt~`VNY$9m3lVsa&plZCA001+)H@rZNrCYJ*hXKXN$Yt^Z4Cbvc)H|Sin9MG6$ zsf8<}c@+;?tUJqfM_J*fsT+OiYz|6AY?vKgSX$y!q%KIWo5Nr4<@6+yS>jj*wB4e*h;s>VCX_KF7Cob>im2dtyXcxC_KB}0Jh2}@L*opwuE++o3!;iDPhpd zr;qi2n4bVJ7}nU40y^zh1>cjr(1*V)$(3J;cbKJeq5dWa#_JRuQJ0M7CisapuIF4^ ze=GHRbUSuKa2nIo(X;C>MH04H&PRTY+mdu|vFizZ;DNC15{p)i?JpHXrPKw_daK$3 zb6;qPkeRH40ghCq!-j2yXX)VkVH~FDp@2U2ByplX%WhX)fo%znU*F5SyWX`Yb>I;) z^^LM!axY&1_dHYH9i;@`XyC<=C%lfp{19H>e>nJDKt2PujoHFqYQd~rD2^!Q1cVGhsK&9PNOH4skz5O1em+*9A%#e7 zXa@Ed$+ad zVD0WP}9Z(BpDf zB*V$ezR_C=nWmEBQ|RQWV|VLc>^TG637-}WX)}zmWA5uRGTqbnDZJG$TtaN7k^@ro z^CNq$m`$ZaJin`YDu1**SyN_)->gtZc>lXpXE#x6{TMcXDOPkYqyvv+>*Wwcib|wM zPo{IrDn<65JGB}zJ3c)rCMxP>GC<^bVN@b{7(e=@dxPxJgndW;VI<(>%Id#!O{sy1uEt9cIM@@45$fSqbG1@pL{-t%Se@QH z>^Qs^c<=mpYtx(H5V-%P!RuOQY#Nwktb}Oo5@@D`Na-$D{Nm>P<-cB($?}oFTCzJ{z=)V%$Fpa9k7n?|$*C$lU^ zIh#X_NGVE2gf`35_Ij7Y@IfI7o(QXe+x`PYQc5#!4N;AsJ*Ns~L)iK#Wb58H;#L<& z6{<3hm{$yDzr`~B)NN9gsmNEncwbIr#GV7oKYm}c$UUc;xt zZfW(pl+s1^e|O@34C(r{qQURsJHy~krw*8+V7OTZfT~hrQy(l6x)3_ zZU3O9%y7ShU5X%VntyC`#Djd>pP=e&{4O4K#^~Y?*|B=Eq@2R>KT#v5v*wpB3S((f z1*ymoq6CnR48F~1x;?cespI{k8jF@?cye~4V#4G-@A7VP-3;_{Rv7To#0)_41$DP@ z155s41rbvR$dVtE*YDUSnxI4|PEw~jW$817HvLli@@!bZH9CXn38MV($Hq_YM0mVK zWL6T?Il8EAeO|~9k)+BjtUl2XRh`=>ebR1i*R63F=C?m;C1bR~+V(fys2t}cp;Zz# z)(6<+ze37w*-<#Dl=o12pIh-oN|D1C>AC%CUSn!SIR<#;r;NGQ8)dbYnf*xu|Jzyn zk5X*s0CjW>D;b;bq62$-A^!I$a$iN8!#t^@Qk}6J=41{91EG&<)!}Yd_7=@T$ZJeN**s0#u7pQ2I0QL4B2WtM8h&CRN}5FJ%9b)Zd6K&X68A4?lJ0aBB{C zazxQs@upIa;yE5!V!>VX*A9w z{4vIM7Q67UI~Lvu%>&ku)dw{3S>xg^>Xu(T5f>X936ZyAr99s%0p5kP+(B+q37AG);ZyiU$o}99Lu^E1d=gc^$T^ zn~J6!g1py=O(EgUjg_9DT@SQa@Lbfa#7q<-3_B3hTL|R07zie;c-|uByguz*$fvej zcV7gcLh=Dk+zIm^I0Kiz_#pRe^%~~;a6iy}IzQ%io1@~(ZrX%0Q6AFHEdueA6<`#! z)asR+6ef~L!|xdjKy(5W`1nRz-`4!x{2`aEbwU<+jt%}6Zw@v5dgHe4PKRMPY*~pn zVa3H!Z&zGfz(z=_kImnRFsP0#2#O45CjnBe5TX=%|2gUoaHqM+uuBK|;|>>N@SpDn z_47mv{msCnn)GdwUU5|Ejz~7AoU=x9%%wtlKwNa687c*nc!Ioi18(lnTcXkm8-#y! z5%fvDvyBD!i=oZzV#3-v?cuUu%-FF)N$B?SQKa~%Z-M&fz!k(`@ugU?VmhFZ$uYs8 zogg2=9PsZD;|$$o?8_b2B7YWV&A~sTz$%G!lsJett%`2(qg3uYFXvh88TP%(EX8up? zg9X;E2eQLT61RjK2|R{6d-;Sl2Us4{PB*$uVXMPr*5cvhbVM8dcN;mz@pQ*!1u6BF zX|H7W#rb3NNG(@d(_0xkVPHt=zbDV_)|rz!I~>p1vu`X;Mu_a5mW^Gf)PH(IK-h&F zJQOc!?dhOTG$*qa?3F?7kJjmNS9JNX6BDi*RJroEOHaqA&^n`F@jyMe>ySltgWps5 zaUS}Hj>-*-<4Y7OO)w8WBSClp8YJI3zZN&;v)(s<7NZTFwgx*mg7*qOQ-71|D( zeS3a|puwY1&Qc;UCWgo(bD(a?o?Y=Zs%N_$Lj6Z_TJ+Pj&WE|C*a-%n|7&DAj==v6 z(ze%06J@ZT6ADBnoWhF_8AOGpl7IHo^#^VH=hEgbRpfr&m5`E<0_grv9gd2DAvK|| z6~doYGUxz5RgzUM0j)r?0aPD6KdUKYc|1dFO9iWj^B5Y7$I$8<2(HQe2L&)RK0G)a zmG8bmdA;cIZoa(SyjA4r0pewLpdFcEgPuktV!J;8+5v3JL&659#8aU*2n!VX(Z@_0 z0NQ0!cH*Ctb2+T%qZ8P7x$F91WcLP~gfHi^kom?h&?S2@t+l^PS#96-On%91&M--y zD|YX%TCFnp*Dvi&W|Y2#;pm!Vt`*eY!`G~{WA^&*BxR7L1rfJeJ1?Sdkk+rC2~x~% zOaJ7<1XR6LL|ppOYalq;7CC9lB2&F^>G*Cj8lBD;@~At5C5YI zQF=QwB6K3d_!|6iDkNo`W7!zEk3K52p#GbkK1s_l>Ko#u(o7Bfs<$j&uBS6K5X61^ za2gI(_fV=d7CBerz6C`N06nMYecO>$42vpX0kidYIUg6X_}(+-yNq~s%#{&0$bETM zdWr-Vhs!HN&UQN@GHV%>6d7^O?Mq=}it<7LZo^>4q61U5=|3BQ-e~L)467{@dyb}h zi7M9SbAih~&}}LRLZye&Ui?385BRzfj>L`{G735U&fTsy9hu^CzBTc(b)4_6N+6Gv)22$F zp_&B5pa{O}b>mbC!`>F-l}(OX|1#UJivF~0WWV1#d{-b3@)V&wJk1#paU1q$;ekzz z_p_tdQ$Vy1E~{)OT8Z|PDAHVxSE^g$TyWfv<+E$k6LtB<`kj#ilz;bLOn?|O zDjenhl|_Dnq!k4z2nzUZzmt#mfozy0{G8B0nhr1EI1a4Z-0r`9?k1L1|F0p4Gagv( zyLmnHH>DRmuhoHw_Hk`sPVP!dRGrw_DjA5?4iD}rz7$S6gL%PSk%1)_?NA~_WZ1Pr zwA-_~%y3#Cpj+X&#)Qw=unF7+8Ve_5fl97%fz^2UlLdI;6fE?Z9g~A|Y`$RzN}pEy zb>;V+-8J)<<@xyT*-x^{D*3!fJ! z8$(mM;XcyuGr3AWO=%oVvm_QP(ag}W<_Wqm)^sMM_;;#8sZbzR6@RR=^YbRXk3Ed_ z8lOiREFPE2k7wU+SF7nvuG=5epV)5<4?p0C8{yuGUmTl-Y^-)zUWSrBuC3$#wa~W` zVHHRaYJN(*v^2RjhHEv$0_|*qWWqDljQmEdH4GV`cm1Ooe9mz{mF;2vMIe(Ty0ZfH zSVA9y0FD!cCEX;rh|hnq+|eZ!>%>I4Ry+I$2g4(_S zsTm2d8w(LeTt$kJGzsD1nY}vrbiB*>m48E)7!UCxm&>8}vg+XG8G8Q4DVOLJ$&Z;; zfYbpm5W4w;gbT{$PNd7^?4++V29AcpqvF3)1@8^Gvzz2T0dc`-2U%vhqP;jh9;lrm zH0hiL0twNSj%go0l_~*Z26GVcg<_LVa(F(&`^|C@B5_vm8^Pg2WD4?xir2LkKYS83 z!xct9gB3quYD;b0Y;waINS8u9moY$>zR#YL*q;(qv^MeiT7NV#nwva_CD@uV=I_~` z52jwiF&XOnOul4fj%r5ClNQh#xE`qZIfLrRlWIZ$8R$GC$2JTJ5UX~x1F{hvacRFNI#XY6s%U9D3?_b~F} z@AtHV$?CYcK5Rb2D0$06Liib$qvn8igba@`twi!>V)BhA(ZQ2+5 zPoE^R6F<Ho@>!*G`M(D|~Vyl|#wSK^W zeDEavF`0l8pKrp@rg6;4p|NzWWD``khXPzQdm}CtB$J3orCr^+&5w_d!?$l}ZAbp%m2Su-BrzX7 zr`q(*HJr<@7`|F-c>lnxEaSL-p@GCterOP&VE?VCM3|PMscH5Z4E)l(%Dc8l&o2QS zswO1Vit4a%xrfytc~J`>Q%GQ`I;NU-Ypxr(i z@KTP5zH%Kpb%R<>bS;4MJOg;Vk`@*)Gab>sf)FQrn-NY6XGV-hbJ(zBYw?{_{)zB! zW#p!(Cj-+BnpY_;))2##*nw}Vr@aUYKB}Z5^SFF}XES4`y`5XHg@{m>l>bpa@(pn% z0M{_FyRL{C|LMXO9M;7#HwX|$b{3gZ2}S{#>I~(Y{G=(oJ#q;E@{{_PnMBf-S$H6q zlYQpHn;5<8tAugb40c-Dyr(I%8O@w*h$dIJ-_j$k!EP#G3{?o)BR~%vH;TiuAQhmP zosYsQ_Ch&?Qc*AOpj9C=qGq$CfDnif%K?f-DeHz{pKHrFHvnS-g3B>0XbyGo8sQLe zQl{Kx*Vt#%nS~! zKZ?==d9({3=8L9=R&3XhV;Yj zHQIHJLT*m_Fm|F~l9Zz2z5r6&tM7WyaT5b;9uorNR{1v-T$ds12Xw5EYkCOUk{j;k z$@(u&TP(NE9gYXDRWq@vDIc(xz9WlbjlCTBz-6TyG zRk%Ocev;vZ2BzW5LgGgp-zqMn9-NWO&3rf-VG3ui$Rt5R7=|ubz^dboT~EKZ+ye%) z-Q0LErbUQ%yq$<3kOuJ2&WnH=f*%a=^jIJ>ol~bq0NXF*>gT=&n02tYM0|fFfQK_s zt3(|OWsCy>upcCNJ_asMXDT*;a5O`Rz=O`@i}DYOjh}<_9XIOnO~hFg_=6RowDxNy zWS~|A^tH@N17ogH8dVQy?h(mX`M~b1Hei6cbElSJM}YWs;N0Zh<%$A+P;jk}U@yKP zn0FQdk%FihbGkPc9(XilQy7rqaPYSH9`f#!YA zm#Z93i&EEryu%9?6uC7Oh&_V{{R?3|!lc*yfC0$T8IDM}>D(P6P-`QOKFpAQo@ZdH zj0O|G*CVrH1_56;c0P4ZM{$|~%Xn~1oL~tAyk1;9Po>>6Yvcf=A11Pn6Y@Y2MNLl) zQ}rZSu-|V%ITvDD=jEYx4V0x9GvsbP(6-*gw@WFf+XwQPz^${-rzqx2hO2=%ECk=i zF=&UYbPUfq-*x5LM2xNNgt#Lqg?s4S!5z3SDhwSLbmtV3?+6pCK`bQfiU50|NhnR* zk<oCc?_(Z7mr;2W7%{rc63?Zn<)SJY8i3O7lAoE?90;{Cb5!{Hm< zARe^QlCdb9y4oCgfX>{>Pn<9e!ubdi2sRV>;Q$DM>C(7=HYl7Tz;=}jS`k7yxa7{5 zX)CZ}$EKPRBh?3&rLZ*KI|{ugmf8aj|29Eb>~QS7jCNRi%gg@v$>xm$ti8KpTVTUr zX*^%BMuE_-ak^}oR9~eOHBmDmkgcYUnv9ZIVUtn||2O@@*C+Y!bh4aTR~j`BXDo*$ z*)e|4(nVXY%Am63S`W{QM>-|1Q>T+z1ldn`8(PkVc~aLWN)x_tV6g3YZu50-l{?d@ zKGku74vsWG4BH|5GTjC(YTY-!Nlmw(X>2yJL69w$ZUVwr$(C zZQHhOyQ5Rj`<-u`^RLFJs$aG1UVF{8<~8N%#`cT-xw>aDF~kj7y$-gc`d=4Hye)Kp z_QGQ4fOi5}l840*9AjN8+q4P8(k#Eg!B8IK&2*qBT`!zlbTDPLsKC3KkZbQ-mV654 zqdcGQXU69Y#n3nkd?W&Dgn*o}@Z>N!UWqTGJ#DvezrSDSFRBv`mgdMBf#cE-B{M{? zG=Iw3C46Rev3{ms|9jK{<{#6V=uM1|uInwxKTwi=62vLeI#Q`$=YGZ{_uh?KMxqA` z$`|=sa5fUH=8QxW7Tv7Z$O&}=@4&q1tWq3jTQDYGSd zBaZ>IkoslViD`2h_62^yT0^Q$$T}$QDyzSO^mU1Cm$bQ%SKBRrL}6%=9alMXvgS`$ z&XM%Uh(T>a3ocVT$-QH}RjmVaPO_+CdB6WW@4+v2`6X~C7e$)vnh=91XrV(w^v(9z`iF*un^fnySLgi6!S z`(CAQUOVlcW_V}QO0F>@Mtl~^A)pw7IR}4^ZA3MM_JEz&oIMf-FNRu7bg zw~hk+%@rD-j#|O8tKt$P8q#%1c-+y@U7M_eux=3j74u3%I z#%r<8nl7HHMKDYl6hj}Ucx>2W!m>?tZ5))HnC8`K3_tFyS+Xxp2b%5ap*3U_cSmAj zFWE!y&M9;VI7?<@6W>C={YUTOcY$y5*&pV{-KRYW6bQ6F3T5f)MUn()69gSHF9{zf zZ@^2I^#Ey=_y3Mpno4%uxZ@W7HcVJ!?G(%XhX>=KF6Ja@Omr&88Y>}de}Osj%miPQg)@_=Nz5i$i}z9uQ|@Y(!6`lXk+Ch9j-(uh zv@J9*iq&6==g1(pP?i9PgSQ+$J*oT~=>t3mH%VLxaIaWd1G>5pV4C%X8X1Ij4m~T` z%1qo>P66R~mqdVdlXuWh`a%W<=$Ylz8^lxeEV(}xiQxx~>Cc%}2DT=HtrWmB_SzC5 zFt^$(Mv*?p61>U;c;D{lKjWsw=mrBiL31I!zzJp7LZpYo-s4b+03uT$c)Ypo*oJ<{ znSLQc+DCDJv7^D4Sck@ z@v#UU3ym2ejKE9wkT60hc5&j(lbaycu-I^L7rp7W>i`_vb5njIt>Oi!M^aun{r)H( zl(7%h2N<+3*4jzabrP(gw=5Ib%PzF9!ULOeO`#0(%}l@z-^-Z(Q${5W{L*SVn;qdD&-{D;D+I<^-54`;LdgtiM)T2r5&9}=cfLNIVx$2FzG#G4 zJ|ohQYk;SQJ@KjZZ=z_Ah1}I-Lx4xw=|*&>LTU!cqrZWqzmC=4Urv+#p73k9&ra62 zo`a>n3~apP-u;MaWH{#R8n&o%Q&8d*a!Cf`7o;*rts#VYj+DG27@?SW)QB6zV`F9U z?c6lHPL?I}YNsOd$7;w}xrf5a>Fw}|Kby)K=Kt5Z2+GO#Vq3oI!tg`i`+l`|%XIvz zxC|51F#dmG-_m&iwf+B*Jaf^|Q#&vJJ$l7db&|<1<=U4ZOeIXh4DCX|L$KhvmJ%#Eqq?wD~Xc<7H*_2SK40~U(tlbWMlWc&l>@lys9bmAS@jL#|J~U`g(mMyo=%9=k zNdAFy0EHLirxDrDl86Pm`+;NFaRMSyBuFj^I&xvWGNYFm;G8V<)ofew?=NXd=Z@EE?YqKy{$McE{ps z#aN54`MaCoPIWLUbmfOC@$&{#o>4!$+K$gt%SG4I3FJU+U=rCW>FEBt+sb&j6Rpuk zpY{AYN`<*f9{zmdCW@f|s10DA zKz3)HMUuAnTghOHOHh5`Ax?+*{IDMFbESbR?r~D#o!^O89wRjXq=m0aoR^d#dA4o} zwR(+oiYF&taLAo%8dHCuUY8nO2~ep7!UZYBM34j<%kc^s21A3k=BSWIe~ zX*A;j*iDNYC4#6m;r`eEw7=1#2%-<2Yak%suV59ge?gT7yI9_V9*5L_f|^m~Nf9)Q zK4>`p3-s@N0m@c|*aeUTp$@J4XCBGoxs&6{oOC(F9u`UbY@*}90cWx4ds9 zxM|WavnSHH^)AZPZuHZj3$SbJOb|af7f5g1JL|<)Yh`*Hk(<|-wGWC4k3}L@Jv@uD z9P4?_zvqKEVL}MfN)`A43 zrhhYAiO;8#aTvqc?#-EbC_X87pkWAQvB|3PKPG5eU0yF(O{e_XcO}`>wv$h%^|!Zm znxES8wxB_65N<4mM6XrUCm{}&)`!&n!(E!-$F9unZTO-={ehQl5NkW%%MUQlldn=6 z3a$##!j1M8hU=*AbdiR(wz~LrXH0)10O=hKNy!$>cyGr^9o-E2`WojRW!S} zAoygtVf6s4UqBGL#f7sv6(7qzTGOFXe72I?0C%MLNnq z{*W>u=8xp0*R^gvoFk)Um%`Iy(XH;;b^q+Pyt$CXLj#XaF(uTTcMB$IG?;qs5K#%s%ur31ZL)CalZl{y&Wm_{7eeV;zosJXVcxX1LCg>JMoyOZ^qt&Ln z6^FdUUdE&@^x~5;RLAqqMHGR^fhlj-KfGaBslZ(_OSXAtmwD~0O z7BM`OmevtJgw@U*>JaW!n2;;h_QO&|F0DcN>nWv5dDyU*3G-S|4e9@yQR~0jd zbq#{WHs5e=f2Pxy4Z!V!P^s}U)e3gcBQgrJ!3*NfvPKe|{KR^c2gxKMI^a{U8drRe zE!CsL7HZdXOU+v$-r2*2A*@&d%-RKJfFaH<62OVgE2#cuE5_;@{}hjXrt__$$=lbW z*tRui)9l#7DO)?E5wCbG##o%_3t+RDGbfy|vJl{kU-M)pY;$oy*dG9T^ALh`Gab>1 zIb!QIzl)0d)Y7LyK(yr(zv+Js@9nyYsShxlNJ^IZJ^FMq_dJQWy#MyEAs8 z|2Tjj9m9O(R_sQ(7Ar^Ct(~R(FaXMKJcY#PCxt8zcb%?f!*4_}zVDo_7eMSm>HLVE zT=tYkMnFFe6q!bICfG3K0T~pNX>^0#AyX>RE5OjuZj&f7%8v&RgfbqfqydTP=P1{h zeXJGv#JC2nxx;o=*5>z3MRrA z(fFV38C#J1REE4~Ue7UDy02ZbPW7W%Eb0JH3_@LDw^A$I^H3}qN}ISD11HgB((>`l z(LHcIDTm^4FkOwzq7$mO%KLrcTDU&?*dyF~&;y10sK}5v-oqx&!In-+lt?|?UY6!c zNxMEn*-|vovcx)(TB;`|mZdgixy6aRBzdl8%we0`FD2(hWLYIrrHkBte(}%e8u6G*rDARGFSxaWd{lH7baH?n- zPtWk7t;#?wzjp`IDVEKm(fhhj8M?BU@(}B2Y;2OWu$%61*|s0KHmW!<;RQbovhUbZ zqb(PMbN{H#blG2C6)%&*0sS*l57k51FGavjYQ-TP zjQ`zE_nr%~`!X1yK?PK1W2_QCxA5Aa5H2Z}WLN%qvCjY;Y)QusWOH0jA$-cHa6Uh_ z;T0aBk11*PoHFQf%6GQ@9{y^%LR)mAdo&A9Ous?$D}-iTi4+4Zi1|<7;cxln@1$OH zVNQHbgxyrye!nSe&5`G;bk?_|+O0PvYT8xu42~Q+*0LWFZzF}hrAovUY+>C4BkH72 zP5r=QmCAWzfW8TY8yCYGR@p`tW%kAd02CDcAu}CBvX;gRl9{RRO~^n_w4rk=$IysR z!(E3-y_6$OQ2FY|Qh}K5QFg;<0&eD_pdDNbNj%pW zsk3>p^hO4mP|J}KJZd%MoP_Q?xSoIaRdMO6W#=w0Hn&(v-`rtQ%Sz&c9&<#>IpxSr zPDBOa8sX6Z&pjg~B_2HpU1K|Z@N@yZLmFZa(}1#3zH(hTVhL1;qO&iMdT@{eaXHQI z@Jp%*b0iOf232TpRBJ2$&w&RIyW8#!Hnwk`pu$AOe+e;`?{D^ijMnUUk6qbMUpnMpsk}gb2K>c&>vpP(ZRC)%DKk-g7c{N>ngCoB+l*!glwvKU3g_+lJHst2x^AvnZnY z=B9LbvhYK~e#F>ibSkG?qnU7(CWiMrR8)UrkAh0T*$ENacaIRC&>Di!n%&>*`hDMP z98S)i<%$yjxBgYf2Ge6VUwD4>BQ%Iylyy}7<6V)aiipZbi=V?Y#bA}#_&}$PVlVK~ z27GE!K**B8qF+-xIzv99_ArzH$n^Yd7_|8$#=cWtXF%UWgA@|_m!RQNFeP&r z`~s;oaeRdGqw`Ca#pbY52ErtC*IU%Uau=a*7yzA(0{{qL%DPX47oXh|E$!VJTfI;( zL=eVJ4(wf9T9J(Xs(X5;>J|o=M?px68b7fuqL}rNU!K4uENT$P*xlo*E z`OS=CS!($=Tn`yKIT&nVzJRJJ>#+aJ0s!p1rt=iS1><%+bI<^!p#-_JN_l#@aC!UH z71TJ)E{3z|;3h`@Aa^M~ydHyKHz0S_bLlr!y11Tma8VR4IWa}?lvl7mB zKylsvIL%aZeSO`%aWmHJMIW_x$2;_ubAOvKY~&-Rsg@++1|}px1^L@?m#@$ll1`uo zGq}HlMHvhkzAG%!d-nru8xy{Ljg?Z0xJ&zvM zu;V|t0&w7UJxM3X<^$%gu_$=;Fs1xq2qyV~jW=J%ar|;o2rZetio#qAJ|L9~CX7ua zh$JX9g=uQia43x+ekDfw8!?_66)r0tUpgosS{pg&zA8_i+9XNR_N!#~Gd(nVOf+zg zj1LA_yTyc{hdB|kPjFp7FL}sm4S1+>elvBJk=D^lSy8_YcZ{8=S8|sd#W@KcKFk_& zM{M4Bg_0eGkM577YfZz~aa;xByFvNRhNv_Bn4*`c{@c*|gi41u(f0aga+4W-#lbgl z)}OL6t8sqJOrX_$yzpP?h0g-@yy2`8M1Z@K{51aLx>8!7&NJ07W~0{s-_iDSg%w;F zg8$z~gFu0WQZPh&01}ZF#p=)KZE*@#>V^DBbJvo7xclMmcU7E*R7xm8vOBzl$INEI zKX7eeE^u&0+?MQ&5zO1rJwaKSqT9*u`#ozPw&nn;zZ(Ixg{Sdv`u+0}C!PajbBr#2Uh1XLVlh6f%Uh1POSWHOVgpD5OJs{?k9KUL$p>M!pSykIeTOHq@z zVtS}Ay1yt}(QkT1Q)ir^Lbe$2IK0UcC}co^_7kzd@rzLKG%|cMMFoXCdaY(@WR-#z z>y6?a;0?DCSVBT^ygI=#GzwM!+|{PQOC8^y&&S@x-@o%~EpLhX{b)}c+{ zbS7UIdL(aSFPYqwA+AQ}ja`F{KzB?+bw5?VVQkNKG%_6L{su@^?{?XGG5VZ-x?j@s zR$0E*-Y7vxfTi%m@%xg*hb))Fp*)0We4$P8`Mt{}=KZl4Gjvcua?oucdIsONw~zM^ z0s@DJ{s70_V@<0;t@}OXro3*bq86L3>`edm33Bq0-sDmjqyq}m%>$q(tL2%V#fXXf zTb7VAARA5CUlT7);IXoj&U)(ni4RKhE^MD{A+NsG z)DFy#g|H-q#8UGjN}SIhfT~8DkAlGU@>%xF91vmUFxb3a(vo_q8~+-k!||v{^PZlxuG0B*gFgN>4IivewL$p`+ zP@IW6qvMGn47ms$vb{z^-3=)+ZA^Y4IH z{@rwfcR}cufZBMh`$eI-5LhFC;Uz+U9SvYiS)_t%(66kfJ1?CcL;< zJPN1oza$lXkRm#hZK;0sC-gXB3Nm3`U|vbHHQ@J<2+9+3uDw!$*m6$_PJ(w=eH9~? z>9xsNy1G!_3axUrU&XVyfkx?_CfM-lwG@#pkCW0=x?`)H;7#UOC_n9>?~5ph^0H3; zq~L5*aA9ZiIJEM6GNkeMQ$>FGr2qrFiJDUF(dFlvEbd9Ac*+JUn)It-IWcg|6B z<#~t^HLlkM%GkMZv9A74g5>EfR4=-Q4M%mS?V0Ca3jZ59^(akfk5l1;kohKX3tDBv z$mhzNbNWZwoKUKV;wCjqhgCBfSD6y>S%Ny_?Yu7uOBsS|PY>SbiB+U0-Sk~(T^;*$ z>VW(XEGfbFvztF8_p$0^lJR#tyn8yy=UxwA<>$@#_Y#mSUG%&?>>L;L1P>lo`Klk( zZ1F&&q24C1`x=AgPJunmSa3AwOn`6|thDZHerEj4`rIIvNUXFD7t$4;`~3Km|{t_4M$%h*^!WJNaNb42AW*Sf0KXgj!p`Zm#{~4O29m6tl%+7`HWD&*3q|mXJeYPt+w*^KhQC5nVm&W-TdH zSuIFJ+T;N!#Hk7}n~DPiaC|eb7FtDKE?JE>Jaj;IeSa*m;50b-(tsnMAhRT5*k{E0 zbAdpCt_~Gcu^5qudpsv)A$4zrrEq&I91HOOHN;K=c5)_jdT-1a`rIOl41&JKm;O12rV?>0F0xJ{?gy&&22 z@Ldmzd0{L#yXq2W7$k!Q4snix<&{VX;#d+8q*PQ=V(GHwl=5|Dr|b4L*V56Wj`C#|zo3Gv5`zh&WNW{Ik~C_i;hTeHQ7Z zHT8v$$r)^h1e3Gk)Ks0&$K5u%0W38|HJqzL5fnj-c|>p-yv__;SI_K&UjD^ykakj; z4W>pZhJo)(g>Vj)3vYMhcN>161h_Odh#r$`f7pY&|Abd@lKA?ay;}y+Z3_Pur4-Nnyo*L7&{2yR$>O zzrP%SG0_(*-PcOCI(Lfh*=lYpfA>T%1{p)-=@os3+*NoGI$Ty)Igo(hN5y7qCdH`s z`PV3*nZl^jP{zzba99B3X4NQB0oJ`gAbKaJz2BpGC_Tnb6?xf;nx$bo|Kp4zG<|VO zNOjRJRCF|G&Y@J%Kb)z78CZ1nOVM{dN(^MmB~Z5e7zq#FLqu z68-5^|4bVrNrP^5y!q``L>Dk6y>pN17^JH=UL&C7{INniE(HsMA64NZwbVc)K6EnO ze;w=^T89QLE>SjmrL?!M#xW5A*F$@mLZ+@T&f^nImU1F)uMyq4$DQd9i!s`&pvC#2 z@ExT?fU}}pW4`S!xZX?{{~6Oo8}i+)%iQSUt6N*=)T*QCby{0kQbfuqLQUy@sZ@v3 z$z;9G=5{xP#pU|g-p;L2Zxm*Hwceyjj4TiTn+dg}?Id_LFyfdN1uIg&333XT$E@l# zZa<_q@U>lF7wXQjh|1w9O>Ls3=DnFN6}L>&W)@aU!kH%@O)|J-ieKid`O&8Ss25YC z#WL&T{7p|nNi1P9gHz7ienBoU7oo|CsPX5)C573qq5_YvH^MgkZHW5In+bI5^Yn|t zi5>DR-E-*hL>a1t!^FccKcVxj)8dIOpH?krwHs9|OgTL|y?=`-{~#tE1(*@1nVwgd z8w=UY9~!CC?W<4k9nZ?l%kYB&sIc5|7{BF zwX)QEr-cC=#{LV_8q{b-p5(@5gNmUcfp6JvV)8*s15LF!OCq=ax*DgITj^ebCx%Te zNDS801{iTK41q#eXo*5}zcaCfTs*=b@HFCXYXRqFOX`fXe6A~z`H#{hOgEeCA6QqS ztA<1pZI9;)d&9&*7!=;z#q{is!j4k;NxL+|ni#s#B#D3D{$jHgn#5j6irL17603dz zk%xKT03q~3d$_FFuhD>$A2XOtN`)fHjGT0f8;*Y+{y_zf8`PDGR?thV=~tkS)}w;^ zn*-FsHcOZlI*`6`aA$Dba`@;q_pfTxGQL<~o$$Izt5M zK`w)zld9M}za`V^CI+nIYlad}k=s=m_ez6Pw}iU2~Do!o{gtB>l_1DL?|h~|U+b}nWL<4YO391p zF$pp?%?DZXV*~TIKyim#;n*01!NFjgli%8x>SxP0c3y(k`E9RlR`(~8L=+kInPM^o zV*(>kSW$?6b;6=7Z!K!#hFl{>-R+$yfHZHBu zb+b<C|&adTDBZNOmi)vd#|wJwOaQ*bVtd$?;wdbNn{ zsc`LN%M2IXChl}ZW6O(NiG$L=Z5?K3r&1%E;SI!2(()(NH?pKBhVRDZkGv`Ba%&39 zg&F&su-Na|ah!>b=sXE~rsE*Ix9fO2FLfIPaibrswqdnkmxrrv{SxJ<=>;`(A= zmlREK#PN~2P5NBR@Z*P~d>zEQIS}c6|7P^lDh>h=0U{wPez!u}(l5JD2xI?;$zCWR6j@ zd~T$So-=;7#G(D%1Ae&^U58t*$xz=bz>vx{kPrdnE!L;cD($&S%4md9H;_S{qhNLC zGVWhXt0~&r_o2tRI0b6~glcLjsK{{p5TXgIw-$+bEoUWgQ3(SmMIeJC(4+ch*h1b@ z|H;K-VHn+%wO3KQs*oHPpApAHsEsIB7x1~izc*ScSFxzoaX%|sr>*G}21FJS75}Kh ztrF&K$gvWAoX3;esXh8)d+AF)PUaCIfo8j{VQW$Lc7C5ZEN|$wJiaHSizkI-p^L2s zvgyNN!w>rx7!rNBUvdhU7HEv!jN{|u?eG0`^M6Imng9XG^{&rSmlVn6IzIyG zysXM3i|8I$bZ&g_2y2L!Hn-_&s(mnSK(_IioV$HKU-opcJV^~h-Ithk8g(N=2t#_b7)eam*8GY3+MXb&0)I7+B z#7MhJtWo?UkCK05t{^((rjqBI8G{Y)5a#Yz8L*-K?|#Eq^UeQG;@$Y8$Q}M%-UJP? zd#B{L#0qqwBkM1Opj4z@AL@y^rSkrPM0`(VC*8X7TS<+P|6bZ3dWB`X8M+#6S(1lu z?&oujBW&J0tk}7Kv!=av-ALEG-yunPaVo1Jk`tDn{ZFhB%>hMc!Ucq%@?`jO+jSHB zf`XP4`Zra-M+5@AG{4IbMHcyjpV5$Z2n>zN*)W7f7V6&Wv*Ld7KXa zp|l?oUpQfW=*6$;JhrWlPOO^0!+(!j{lEiq>pR!+%HJ~DND*PJ!gx#0@$@ppKix8p zdKK}xTL{K!={b?iR8xqmN|(HthJbRgdLn}6CL2ETUq6hFuGnw(KE*r^v!=%L_Lilz z#KW_DC{1D3>Wg@elzT8_J!~l)TE8cX`HloaQc?rPXdaXu;SrN_)%@T9L7KBR^_^r( z#0~t4fuC^W^lr6mndpe%^k#gU6~q;%uc60_J1FM;x{(~i?LQx(mZz9nXX`3tD?AJ< zg1t&*kEr1CNl7*0DUbUf&UyKaX?S6{?y8B51+NxQ&p%sx%9P5#Iz=sAJt%ph`Xc1P za|gJOvURAHdhX2UPhW#u_s0EoYW=m{ugXNKp*jL=7WI>cT}P~wqPv0{xf9y}Cl=R> z=DB3>gfg3@+Yy5{#Lwx-P_%o2?Ja;NE~q*xP1?y{|MJTs_;v07#fRU+98uqSw40@S z$G9S|Kowd0%n(>`k|9(jCg73qab>-`O`UP>t3q!D=sTv~+4}gzXpL+pWQ zC7{>HKgE`2c-n{_KRw3MW`nNU1)nSVDm${DVy(%NS0X_F=-1!y!3?_(kRNH}M(2-+ z#1WuYww)HbHEJH`WhITkGOJlpS(ymj2nqqYJ^h@Ncz?g$8?fJUTG9B=y8V`M9g42(jL4i1gYuceCzV1a_2+%8XgG04G05+jczEMdPXS0(J;-tOYtS znTA+~U@B$m08^=UGwK27xJ0in$F;W!wIcH= z!pjMECI_U^ZGmp`h3L(_q(@~oE0ri&HwffwVbck ziy#8}%(m3AmT%a8O@D_O3lk6Y^rt;rQ z+j-ZaTMiB1>>abJ7mb{Eh2o}6oW)ZgDY>i12l>OMa-wq#A*j?of8MG;U3Sbkw_vyA zNg@rH7_=Q#n+Y(%&}!IC?v(uW`=F7t_+p*mStz>(KLhT9S0y$GM@mK_(VKl2fM=xPs8%v2Y+qQL^M7PFOif2)coOENMN?*T)}QHbxEq{ z9mvejF_?=hB-wz|zRc|DZb%P610b~{om6Mjw(KhVLjxwq8LJc48r5cr=I+`{<^v%^ z!CRS8xy3gx2NTv&2bAs1O*7?*FJj>HLf&K@PEm`e6X{BSiRC5@9r)Asx^Uh7VycgP ze|FGSgoqL}2&?%*wr4xma<9AZe@Hexd9Xl)^C0c#Y`PAc9HZfYwbwC#PVU%ss(?_2 z1{sVGmf%EKUZUGnl8;^6!ntz{pO4ekhWvUS7O*>ne5}lfBXlz2Ta`=4aB7;DY1a!)M^rWcabL%WwI zr zoKJb`CWQ^im;5UM--o3;^O0FDo=>GvQ)1DihTHHB-j}b1uG%t|h3iI_Qdg74tst`!-2piHMB8YF*uEWMF&|)&E?R zF1b2Gvi|^cDm|kCxOQB@JV>SOwHya!8x8(2|Biy<7sg}M#zlO*#YM926w6-1+Y-PY z*!Fi&mIgx#{&(~LKTT_|dY&)4Yb^f|oHz0XBLWMbDMeC9 z9l?P+e4GcS?^4o921?ZrRayYMMDGH5*86#zsiJR+s;kQ#zLnzrKrEKl$mo~Ez*iLH z!(}22rm+M!Kpw|Iip*LQq?h6LJRauaZmf10ktyLAu=?B0{pzpKOpwM)e*@2uc;@f% zKImr7Ji!<<3A<1N->kxT?XgTf25Z>U1}$8*o8)JLrC4LI$}2dHxxcu6wiZIA0~VTL zCJj@FfmSv==@#3Ma!PwZS9xd#j z*G#p5o|dET_v;0lj;rQ_r79N|^neyRj=YPHL@ zh9S`a;cWM#I$R=pd@aD@W<;9@2e0a(;a{R3Nc*oGhpSSD$tk?_pq`njgjbKRQRe>J zodNvV17HFepU~TMbM;X_&*=z!~V1G|9Pe;|dgg#(lV z;*eK`eIw`zJeM0;jue8nbzS8g3Ul89ibJo(Be+K(6kXCdXn}cDMlk$Xu<-AGN&_Ba zUq>FWZy@3O{p3PIQ_ zZ4EP7%_bx1MDxN_9ydx40_1W&-{ul>G8}@8UGqrqC(QLq!8+UOZoY;%o$0O+>Fvqf z<~LR;z+?hj+aCyh7W!v5$>RdcAEe7H7Bk3?@fpdq0{`w~;Frhi>eV>!quFEB5yVcx zpBnn^@zGNbr=o1ig?E>AKllYK^*Du#5Yy=cY1Uo7uCyT5G}ba~x;~#J)k8weU1UfP zs-8YWY)z61&(vs-v-^w)*mxdHeo*_m1a9jl9?`azyj**TFne34CFqawfziR;Qd3j; zN?Fm;!}ocyPrl~X?ApOkszUPZD(t4{FF2dK#oQBVgB3IxJ{;`R@jzV!DQkyzSDrij zf9cm&+>j!o+?Y4c9?|u;=f1*AXx(DC`UBbHsyZIJ%GX^Owot0zBvBIcB6)Da1{Yy$Xn;FwX567DwZvHTKyy__-gErGS*wg=X)d$6_I3TSD#D6v;^Px5dKdMf02ZXnM@+j60@897M5wzQS#C8JKz57OzD>0I8XuEUXs^y6^(kQQVBtm_sW4-=*@K8#wf4NTxL6u}>15zaosA_nbmkauN& z1MIUPh+K4777T%2om3J8o156iv!FjSKu7CEAd5=gAzgOm0?%2T@Ow0FQLW1RKg=?b z!g*C?4cdQnNwxKuiBf&!5BVvgC#9zW#fCT4!(U7mepm&1hY@-5%UF;77mte_H*z-? zOw#>1b189Nat0sM2IxLN$ocS++5mN&nq|r^<(_vRjN@snGj!7S6hkuibLattP`FKV zA^DCGU&$@}-cpE*1fid@&g4?Ry@&L3Q`9vly|#{=a&TVe#_9sMOzWgz1kM-&Oug3K z*pB8NrhY##_B*1WK2LOL?IZ!$sx3MYZ&2NRNqngJZ(s66eV5ZKf;=9(*gcZ^VEy%F z8+(6tH5?IWJii#PSK#ReE%pvt5*!e+D> zUQEIH3Z=7&aURA8RZ`0Y!M7&HNRK)7L9elZSl1NA>=L7hARX zRUmhR-q{!y)QNC68`|*q$K^(_N1=9$#92&I`X{7708BXhqq2~zLEL3?#w~SA;)Ac^ zM(>7CAX#VTqhU>Dq&zUWS<(_C-mDJZL2TVUO1Rq_FaT+UQ>3o-TEw*S-Y%5N6R3c; zy=q&WpKVFYxerD1-Jf7(XSprt_g)?h7HArGb7L2L=sW~*?;krq7=9g^Xd}Sj8-iT8 zu$)qtE9m~YI6ptE(EBWZuCnox>NqRTcqIC|4RN=id3~i6x^?BfKTMFb*m)i1Xq=vw zZCVMNE$LbIiHvX|uD-lEmax0h*AkbiyB@|0h9ygwJvMSrg?38^cvoU>tr(oPA&shY zXDjaqhH~M4h---kaoU>*J~ye2)tWWRxP6)lfNRVl!W?7=vDBjFZgNYZJ0uKpuEcwd)WDJ$lD>90W}-7{6KG)CUVV! zulz5a!w)>qIn#sE&YX(j44?n>T+VFt-ePgyR4z;D*@-){4vpmhNgQ&Zp9~ zY?J~)Drk=CRZXUNp$mrbLGN4-q!9XW>CQ84uG^l9KH!pagY8p_H>oM_gHv5!)1%l6 zA3apIC(%0%SYez*gaSDi-#vESIH{q#7j?H|9=bNKm_~pxANTX(M6{T0yqi=_z+YZ$ zOhdZaDNi=4;kK=R<$SDX-&^nB*Si0fGHiPAd$FLpvOZ4z-kXR%U&SQr)UGJskY%Bh z!ysyzF(}c(g&2TI3uDu95d!aN%@x(1_1BxMGqM3ki#f#BL?)((Z*z^oOlE5_e9!6| zXmY%Bu*HVvl~uzqRe$hXYJvG#9-iKM3~ATEB!l~kn(uKySL*81!90|_e|XZu>~b#M zWO0$d+#ulR*9B3h>_ipzZg82pQ)DL{($>FNh3UsR2VSWVTJ`d~cJ7fbt=>;nGq)6M*F`nrzf%{prmd(IBem1m8 z2HtqP=EZqVUv-pBe2MN~COtzmokEaf10yPcXxYsz)ckXwPk7I<$mtJ5Y(eTu%NMD* z`~7a_R^bUg#|9b53J7=^(?06>_FKK7Y<3L|M9H*wV^?=Kg^wmYCM99HHt&X6OQHM5 ziasj>7!X?lqrDbp&Ez3JZU%JIy&n=;4gsLOuFc=SJ=sf%g777G=o3*9gR6t?H>@ri z7(cP;zD#3jccA_^zhW|iyuNN!enaZ2OWD!tB zl77ewn={N@<$m{B8X@&BB~Zb~KRN737ya!Z=;jW8cu>z5KNG9NppwD%{D$Vsp*%zU ze61Q+H-$VTsneI6hfL1OTiQyTnl_}kjdzH;NTAgU`qZBOZptzk6i}ppO1SWTERNH# zrN_ZC&G}+MV?vO`LD-(7dxFv0W=An6M;Ma);pfjg!?r)Ht3!jlfhI$I2c2(g$yVTb z+|;Z@(O1A8W(PK$iyJP}3;4B@8QO7BVtv@a6d!otMgn@H~Ohq@z#}h#@l^sHkgZoRnPjP{0?SpIld}G^)4Lzpn zXP=s$N0Cm^84f_rX^AY4N^y#G3IxSu+04u23pKs0)fG0}u!sXF_h;tBSX_+f|GI}lrA6ko~i zc;Nq-DE@0ZfqUe{my-c|eq4gpvV-18kvwMKLeD8K(`*u8w|%lTk()gTwVBwu#&H~i zL=xo6je$?D5ZO$E_ua*^ckIG(Y$QkUHL4?&?8!*P1w-Z`mFUC1f+4Y35p3AFg(XR~ z0(QbbnihkK>EN+LYq|+oX1UF`w8>?Nu0c+Z0Km|0UzBH9$)BSZbu(>P0VMv1^T0e& z$s7&3GuCkOCKkQ;@p=Ong7ImKrYXYx9)7&f1Ce2als|MEi>>wMfS$roQaD2WaR)MM z3%|l2KRX*l(m4oYt3~8{p>%EuSD#|D171MiwW^fiLL%t7h^yUewJGYwAMGib8zER#!8)z3zoH{b%o3-)wU@IuZD8^Iq04 z9jZx-n|g%?;uEZLo<~Vz%&e`g^`jmEgD-!{^e#P+eb(QH$6h=fM*q-m6j)}lV=L;c zCK_g7G|oDRmKKQiZ;Mi0pZwuJ_t6r-eatt7w^WSgv^N!!{ePHx2ll$cu3dX&#b#sM zwr!)aZM2QCVmpl+v$375SdHCiMNQJ!Xz=BE_I}^}9rs_DbB=qCabD+WQCLd#paP0Y zZrOMTtijje^RUYsz|_*$Bkb|d)|2$~V0^6e4Of4Wx!{B8O%g-S#y^3%-){H6o4bIz zK-l9$H>zGVzn~t0Ek|yA8E$JTKH|_&KER}rptleMR)U4lSWx$HmlR4q`qu%%XUnkFP&_&LXlqMrKw zR9~}kA4lNw)}eHEvA5~-$DT~GX_gp!1XcXhpMV4G4>8zY$6m>=iSO7jmt1{89gDra z1UWo|9{shZy|kZ91(h_Jt{bRGgkBU^S2IfHncYM5PaKCvu%VXI|6Hth)N!sS`w|9G zMmY24yg;`8;hX1@GFs$KCqTphba?oDwvgJi5KCuuusVsPn5!DOU=d#5k2R_%8Pxy1 zf<0jzQ0Dl!Z0Kqny=Uvk1A_MPs&k~ezbOjGGe(;s$hXYr5cXaO&sw5P#z`4kA z|9ZZ2URhc!)9G)f>@GOK2yr_z?|3@oizQRcCVsbCW->=`39=mL+kZx`6BC?8zM;!U zcghaX`L|!L_O5NRewpq6mOC>U@a|8xrMe*$96I&;OZ3;br$gJq&dpSBugx_qH$FMs zM=S=_uDd^MreB{*Gnm=v8GWHi?qJ=98X*owj(+du6}e@?k@OksBKSU=-+5-R3kqBY6Y4Yl48N16XBR0s_DZcJW;A(eKJ|r4(fn`ebhAPdCmPI#3(baN z`Rl`gVwUAwmvH>!LZ7n_eS!k<9@qd5i2j{3G=tvIW%)PE8Ch~w5n@I*iyY<#cv_5} z>Y4j9?XR!IJhk38Src@fueXFS zLr4;;J;?nB9Y)i2vTPf=nuZp3k$fGlqSPey3<)MU7|X}LeBJ~Gr&C);u?VnPEmhy; zjgUkRVV@(Ev!N(VA49GAll?6;znHHM8~6X68IN8W_e-o;5~}v$h~|&6oo6>uB@|5Z zGfu9Scsrm0$8?pt+nIoWzUD*y!eQ48bpR8^Df$i#zoC^LMf4ldYVN1$BZ8k{mvXyX zD~|CS;T|g&0UbqS#YvunCmO-+=r&52AQM$aHehj#o}(>Dv{Ka&)3#L4|D+MPEwU} zPV7&&qZZ@{L2wqH&DR3<0?#z%CRp?{Op(n-#__+n=5U}*_Tt~>@4eMak0nGuY@G0= zJ+YlGvSX@~=?k{Gq^|`j%Akje#G3vRph9q+4VQTB zADX2ii`g#Xw_?P8-z0B5HW3|+#JZ%>*fJ+Sr5-97VBMW1V8~X)@1xdFi~XCNX+GQv zQbmpk^?%SKB$|zSWT!lo4iuXDKS726W^hJuVPj%9m#533)z@?3$)z|L(W%xyyQ|2j zBI5lDTS3>ufETB7hssw6p&MO3+o_cUS=!PbD}14MUG1<0hkhO zo*{vD_)1)0x{W=69Y^))v(m2spe{qdD*mt?i0-WC8`A7tvu_5s>ndH|YrQ6V;tI?< zJB4*Cx0^f_w2Sw>!jI47Yshp8f9NKl0)ur)jUvJ73IyK-wMc4Ytb^i3CLEFKLwoB> zJE$#HE2-5cQh#+EB;f@y-(8$1wK^{WkfDAR+-vND4KUzHZ)fbxofXGuv1`l^i7pen z^yldo$9n_zmM$^*gdSvfb@;au^8#dQMs&3RTFwB$7q?C zPi9GJmWmAh4;={lfXgCVvxI*5%r$f3T|XA@#mn;)!~#W%UdvKIXBtzTbnC6AxAXYu zZ{RDmS)6YIa!7v}!-RBFI)pW(4ZI+?ep!J8@imKEI6?lG*=QWvLe!IAKsEYFf-Mc3 z-VC+N!@H8NZ@#d{t%dE6=I6D*kZ%`(Iu4#=I!=# zj7Z*6cKDx$Mi2e0W%1TY-qXjtRN*616V~2W;Do2F(FumpYMwzu^Q-eg_lJsGe4UWt zu6c@p?0_Wn?++F(IW@hH=}K<@Y85^>Z)hPV4Cb@HbnfTzswmcp=-j^^ljzWqr8%~~ z9Qg=1Kh=ClrFZ_hS1fCMlAsWu7!r2>RKf&7K0c{&Y!dRnwqG@76)$U;B7aQySp^7b zC@(Lyoz5HPukz{$=xp)|X}HcoS+>m&_}q8ic@h%X_dXx^SEXzp?_Y-~WyZ3T@fSmO zoaSH+io;)`a-v>ahY4u%9F#3Wh@8V?s^LO@Kmov83ZPrpx#l;c7Oe`VYq_q=>u^nD zxlJOlXEyufQ*2$*;=cCNY0=C?%b}6qSD285KwZC#=6>L>Vm1-RzQ+@`lFmwlgS)>G zx~P!JxdZ==yrR-AyMoiAiVB2{@<8ZqI!cN}-P!A#8`rHi+nK~kI#qu-vZ9&LlXNt= zj^*dK(27u21ua&sWd|^^kjXbe`^myXT=>l5=@-}-OhUU=%7mBj6v201cGFm#Q0_&2 z_<~7ucGZ5E@^Tzbqfk*qFo|#th73Xd$1>ORYqbGrWG`TQkYNtIoKELmoa0l0eC}grT6Za8c+D-pp#j>}MD;U^_AfJxncp9PcpnTFt$K%9l3|Coi#2b^_L2 z&BE9zr47ylvqY`;pFJnUu{lr{YpMtxYzi^{D3gh^O!@hVYW{0PC}uwM*?jp{qzMAs z`U;sD)iPGblI+gG@{5s#462E1Ibhva3V7$6%g*jb(ClynjGVMvIhj$=FGWWDh^`+G zF29`MV3h6u4Cj2H%U^gs6Lp>m>Z?6_-CM`M05m=zZD0@Lhw*!8!pV=doc;| z#ijUJmO83oqVCf1w3|~HP-tLgr;4_SkD|O0LlFt5=vi_?%7$YELFFR+PY9*M%;Wa( zh&bMb_cxy)4}P;YFcf!2uiAC3Psa3lRnvKo6trH?cMsRyoV0PMPgtpU|HA3>ZL{jS zm?)4MZ@N!C#ki+ibXZI#kyrM8#8l+ZmupvtY{z}S#K4*BiB4TRda7s1D|vEAYSav6 zD|vSR|I3-g2_UsI2ZxyH<-~vYp<&EdHG_j<$hw)Jw)8~=5Z>Mo*vNR^5gkC2*Q>TEXXSm5OU;oZ#M*EV6TVLe3@cfGD%%I>-7 z4DjZW!3z&CziRVTTwj)&Z})BEu3cksHbjeV>y!z(d@F*lkAQWRK2t#O9+ywqZ!`iJ zPf(vMj;C-`>21L3TPV4Qyd!o1tiZDJ%J1lP5>&a_%!WdvT}qsXx=~&HO7oDNvita{ z`Nu6)?{TQ0VQ5rH5+?)#RBOlTOMcKiuJ+rfUx5b(E zrrlM;JfzgD5)|ZT4~qxAW+AnYI?iuwFh6Vu_1!~4(H)v>XVU*8Jhq%hR{mg7yKm=} zet&F)rSiK!4Zb035475YL$8OSGL_gD?MKIz^WT+Pq_Snqe*iFuZImbbS zG-U&ro>{LDcpNr8sC~;zKSo@(S$dYvB{a#e$r?Cv;yfu=2W+15jNKV_HCeogJaXth zH|*z=Nu$~T#WLtiPRvmwE2KJBqi;K~>iP@)$RLW_bD&SzFsiE$!xPex#Y`R=!CxzY zSsY@_z_a4y2^SSQhfPWy;RV168}F;qVJa-)P|4h}vkHDQkghN6j(iVc9LDdV77HJVtA{IJDN@pJtOkKlP zCw=_GjZ9$RES=lRn~(IMifaCPbPuGpOU?Pjp$h~;Laz4M_%>y1hPGoIqFPRnfDOw& zrOft&HGD1Hqu?CGc>BGjZmjgsl+o1h(ee6WDJU`6M72^_=Iv1EovM1ro zOSU--gZKy^{%a>z?!YQ9KEzz_xN+6kVkR>z#GULM;tqXyH3-dn51ErG{>1jDA23o) zBOn)S3PfE`A};vEo)WNhGQ_ciElapBQRbV8w8ir8l66gETADqO!O!aGgGT*2(4_ni z?r5(l_ZQa%EIX37JiGS4G93bxH+UcQdWnc8zujSrnI8;iCqYakWMby*kI-8JJNLh3 z8qIbH^@SpEaYEaEcYlCn(hLRqxugw`xme4mv5w|QAjjiqW>xk1?e=fxbGaTCzlT_1u5?4Y@B^}`jnWm*mz%S!to)I#d)Fz)2VHM;zV#PCZP;=6_k0(c+_P6yzFIAb6YzWO zGMY%A#FsxMU`=;ums{+5mwH?3g|P_H_rBT{=kdqo#Oq1aQZ5$Kf?{!jwN+bUDe=^2 z3hfz`o~4?`i=fX0wE11G8VEu74Srwd16-MN=iFoznv2kKsB-g@$r#6(2=wTk)fMCG zkG{wSSh2=D;tYFvK?M^90(s>)zqV2JrkyJenTklvRJL(6Ih6@N{h<2>Qn<}|yP=(o zTo+5@MQ0we$U7pbP-}gKcy_$*tRDpDkS4L2fX~GOgAe@tNjv_$ca1c+8CR7HB2mJqkR#1?b!z*@I-t6`2*^Lzp5U(>a3)(b#2m_^~HYmU1L?W#|Xj~BHKN#YRM*5 zDP=i-gewPW%7yVuT_{KEPnT4r(8VZo&a3qjKEc8M3SBSPli$YN?mlGQW2tmog{7r3 zE*%HB&ciXEvPbu#TTkTKV63f7IT>(;y<{!)vySvEC0xRWJb`8g~Us=7Q(Qa->|AfPK+D*KZrbt3HBnD_z-rs_?fBr-*L_}05GOlu;?o|!apliQ5Jc?cr zpLcCx{4t^5w z-l`m?;OjsS8$o5S!RcXHnwjyytGE}r8?~cqc<#gR2dk6Unel#Gv9O$LPrWD)rL}M6 z5iaSAk?PYKA@H|TjZc&hs5n=he#iqptutr{(UaWG8X zN{6`uAC@2M8VyThr?$W|CYatuh$JQ@U*w+P?C-x5-=$J&56g?Czz!@vNy|fSS zZ-E~i^)>zx&4-pOeY?X*Q>!q}v$<2rOiScphcS^s0S)fN1VhL_J#R{1-3B5@>V6buG1+z6Ync#aA*U9{Yr~QVc#*tZ#;eQmYdbLswi~XAKHWjmNd|BT}D4r@7{Q&8L#CLvB zl;2Z04^7Ga>1B9DZYY~R=IK8s&dlapU0+11DO5iBZJk=4&^Bk0+S%zuAN)Jv{ps89 zs1kv@XxrgQ#ySLV``+-$LeyCkVpRZ&SXt13c6*go#`S)>B9y9tBQM6Pw!fEP?naLl zR_{3Qw<^Veg*pimfq_B`^FlJi=Hnwk99cDWnypsoP{gQ$VjgO(q}9M6LgDZ6w0)5l z9wD^-?1iF5{_KuUk~Wc@)%sjsKO5L_*_HnzN3iJ;w`#DnB0$af^FpLwP5i}K`kxj* z9x)WHu*%Hg7MT|V!U(>Q%SHL{_P3BO7{}KtJcWZ5)-dpLsbPN?Kc>k^=Su4=lVkM+ zOfR4tyHx4amr{*fUKc4exR=!Zz95%zXwH0y*pR9NT%4j~g@sp{sb2Vl?@*W-4gS=A zUckLs7S;FSVznfF8h5STuNcQh=7G4|cQH7mZ)oYMqKd-@HHA-P2__8x)D=PAgqjWuF1pBqIRI(rUjxwy!Z~V)?U|a?c`&8Kd zV3cIK;k=Q|?wE4&{;tzUj2IeV)6_hBL>{G5i-o4|%PuU348I_ijnlc#5^kU{ZJ75n zLU7j7AMJM@iAr50AB6|Y3tQB-6tiqx#!t)DN)W>kLVMkag{^J5@YN9Z?^HF>%8pGJ z{bsx5jSrp2v(I6_mLpORYMhCkgF{N!MX#aXhbM*`V7dxe?zn&}b^T;+td&mrhL(KG zF$l()Wl`1rM!%{oQ@3UnAx@yooL5{rQyIc{~%8(yUKQV@V9C_!@jTQUhbXrT&q-`-pe`cVnc z1-_B1-%A2^gdJZ|57ZTFREiJUApV)ga5QMpJH~_?DNicU%^7)An@7!4ll+mywOaw~ z#u%)~muu=G9GEUq4d%m#xrc+3;8dp$L?H~KIeq;#;+@ZGt|V}jPFF|{r-7gDTL2jh z#}%FDrmes{1wQNN68sfm2Y%>G_DB?vY)2Lv2t*-JmCotZr)y$@OO%3{-x7NFU_v<gF9RaghI;S?*1s~!8Fbpp>U1TOhuPP-gF-L(Dro_GMcA(LPG6ssXb z9~+-6F0R3i|2ofs+Ba^-6l0P|+_sY$&0n~3*YK#d-E5zFF+$|JxLjv?0a!hdcZT5R z)t0(S5rd^KlN<5hUIkMB{Q9lma-+$qK+qdFyfO?GjD~w{!@q?CsJq<_$D0%~+|m%w zIMZ1_$dkLExG`o}T`ffTS0xlk^QFAMK<@qdYv@YmDcaYwi58}V1ZywQw$-fwGv;Jm zBLULw?U*hZ=7$-SKxjd!dJ2#kwdq0@Rhb$Y_8!eY`>H2OCo*D~aKh_*%RlE6#GNekURselyO&MTwxJSpOhYTb`;vvK*1XX^;%EXTMGF z)n5xzgntH?f;)U$>tiSWzxHR@AM}jYlKnH;?z!`1c_Q^GHlqDI6FYL}#y~OWxk?}L zyXd~aChv#71P8DtQp-Ms|W!OhiozOFU~~iVB0)I=dw{1V?nmm zZ63PVZ|E=GKhA$C^auv{UR4ffu*oAf`!t)CtA)zp3%oqs8LX#EVcEgD@gRf)dkbC? zzd?U$E9wV2rdXTmF@L183vt>~6u+oe44bUCIz&G_N}epA?^a}V+A(4Im82LR2Gc6L zml!4z+)AZ2^E8x_WNg&CrNKrmhQ)k)OB-NFkHs}wYy%nr2?x0I?#Om8hCEKi>|b=w zNjhAcEmV|haivtW;{Rht@qbQJ8>J&T74E&wJNCWTo$=c{!b4?N9?K*@oDnjuW3Ega zH4QTI`*UWyZ#F1#1z!+`{UA}shd=MVb~9Nrxk9u3`r>6S&sX*@$t?!95OQ<%mUuwa z_Y`4$(>sNQ0Hj8a*TxwNq$-Ed4EbTI^fO19{TBoNZ$Zkzp%-mmo!e^2FUaW7gOd@*#ZZHXOrGC%lbvB?7XDj{acxh|W9x^^-r>U`tMHn1E z7T;a^qMVH;HPNm~)G=TB8t?v6`lKrw7u?gWV8BNL-oh~<4KZggrXiTv#g=+gM?z_{ ziYR58O8(wBaq)K*`8^v(As;0exV9}r8NVtFYDd6C+Wvbm4CU%ZEq%$>@B?q<-(?A< zYwr^+)uk(LJUBzY$!0u-M(K7Y@V@DJe?0a3sj4+MFo<3&@I!Oa5=j=Qw_L4X+|tql zMFslyJ_}~`H?KH&GaeenP>E)XVo$?Bpj$C0sX_XpPt~pTJ@`n~7qxISN&JSj0b5+c zb(8K_+E5H&K$0B@hR4MNvz&QemI2V9H-eBi2)pp zFfbgUrKX>LFH*xwA>Nug>Ap%#zEU1*KNCk*nFii46J5KsOkhmIJx zH*-r6!9+bGU)|Q*1@WK3rzk0cn9v>SGD|`#wZF%LwyRwvD9%J1l!5A^1*yT)hiLSVEgJV@*TN;T7O!?j^H0 z#bL2&8I?+9Sc{P`G`id(B`5|sm;Sk{Oq}lWbbv$3EjAuP0Wfy5mw6O{+GD{!DZznN z)!36Gz^A4S+Z#Fxl{Ze<1s`6~S~wXHALIAku0lgwCEfi5Hw53Xi>ndy{S;1Uk%s;L zX+kb1?N{c}x*aIlG#3jB&x^zN%YNc-AHq*J+i}u?lg!t-W>;aN(sH3r3v;kq?!6ev z0ZNn3dfGc=nVbB4A&60#VxEC{l563U{-v3AbI|{$QRL zxfy4uM4j<(hdKUB+9UdH+w7ub-fs85Zx6pOlG~2e|7Gf-Y;EQPAsdc>}l+mYP1Q;low)jKizVZVa)?a9u;Mg=LH0dLwaJ_diihf z*IiAczPQlaWA4cZ`<#k*<6iAi(Wu_OK9@Z!nUiow5Bw*edYvVcPmo#=DZOB=eLZ;M zzSVs*^cH4(L=&Z`6vGfX;YR9rlpeYNTg^N(=?BLKh+yvIVPY^*j9KZuSzB0?Js9JU zN;Vz(#tX=Z|6S73Z&deoC=;mjns7@O7&tRMjhi#YI~zu}AS|evN9EI3Q9-}CvQ#;h z#n-p9r;^iH2;+3+1pf&9b-4J{7KJ?lo`B`v8nv`VO0ZHIG1;F=g_Tsh z$p$F7=6t+>95BL(iHZpigrHOL55cA4qiFkWF^_jVt|*d&dkc|W{cK8sy_a5NT2#G* zWb{z-qY>{Bu=Ru9<&Oe;IEmH%XGnHZaQ=VkT(JCjFY{xr5_7C(=P5Dq-w3mYFj&1k zMd=bYvQoIu2p5Naoc5reJdD=9kf%X~W=j&Xzbl3I-Xo=vT0hoj|DW3m0I7W=E3mJw z2bFf|suX4CV1$u)c_Hnt11e7-d$Kv(2ELvZckx9Nor2uEIvQtV2#G(BE)$)iqVZ4} z`(a3JKx;YD!G$|4zh58Yt7=AznzML3VWMr#uf`7@P$3{4GBcjh|2c`pH0`|-y|lxt zeLjWN4bR#ru6d#G>!D(ff!(-ZsZqA*D%C%-AT`G|y(K!LgtN35BZk)(drki-%0kMF zKlOyjp}m!IXEA-r%a~n!fb$R(k;6p=DubE(Gak*mz6+w0`=f|aJ#Ng~M%TqC_gmO6 z+d(202HeQ>vI?IK(m(7td!@a%#L9;3Z+Q}>!T0=gyG2h`UlnSp&ts`UJhW0u@WQ>P zYxpYbH)C`RIC1D#2cxE9h%*Z*^H#FgAKp7Hz9UG69SlZK>kiMAccD6V`4DEv{M1|L z>vd$tdx7w&(U)bO#tIM2<-4CszVi?GN2_y76jiO&Deu1bea&8N-N?3IwFxa$w5`V& zywR@uFh(gsr}*@iqW?RE-HQXJ|H9p3D90zE7>x*(FW9#&8*vO0mS| zlJp1l->_dhtiJ=9aNm)O2nI6>JP%#kgVsQ{81`&KnImdZ5)SGEHC+ZI}3?&Mi_iy$bl6jVN zLY8XxdoRYs4dZw~;wgK#JwF%K&BCPzX~SX~6BLFxIk3kWo??AIV0JIuKemy4q6XMH z^_T~B1WY8x^$sjtL8FOn#JmJ1;wF*l6Wh1L^`K6SSzrfz9XM3qoyIP}n`pvWg(>(1 z%MGQ5xN=oS-${Q#e#u;H<#fv~RR#^FyA>}6GhD6~c7GG6g@X=G>i-t9Ieh_*I=4d# zU^WNp+quPn2w*+>zg_cJ081t$0~S>?AMCYZt*hop39erPA=<&XPA-eEc_wCVF&+={ zlEkA5;yFh@MB_Mqij$YngxVm+EG@^pRp3P~F&zg}I?}fzSn1=Vh1-PKeJX(cUYx=) zXGSjm8UO0s!u=4ip$VKhyZkd?i6q>Kaa7dZ2{_&52D5T>I5>6JL*&UWxiO&}u@aP+ z#CcCg?acDAA4@=*w{Rt2%do1Ez*mw2Z2fEwr_xHg9y))E8pP3lYM@pSnTivNnQw$Y z@(oFZ_TrdjaZwfElRaj6j?1p73`nI)D26~?V(ki5?sU>(Q#~}gZC^S$Ut&=h>27Pz z2d`w2V#B^=?>uUNkC4;x0-jdkPo>`etQI5_=cQ`IA>x7cp!X%7;Onknuc=-sh$(K< zpc&(3P-J@RIrZWdj1RO>mnPusENCf6`aNnMH%kNeC`uG0qNU6GIWv_to*WfjQ!PXM zCw+~|eg9bH@|h3q?~Ns*smIP%EhO=Y+PCM$ZNp^j3>E+wV7|HM&NU^IJ+$qt#uSqr z@4O-ReO(-fTeDC%aQks#OJslnRA>H}X&G|-K7)xj@38gX%^Pt!G++tyb1Kh2T~O!WcMX)h_nifFcH65O>^*S3eWPb59oiae|^+}|jc`Kq;>mkNnSF8_^8)bkyN7Q+3&?o4Y zBCN)uQRxAmZ%vk1>r_4dgyDxiYp1=at>^hV7X$#I-5GYb_rMWOr_WifM2-uncJ-_B1jX)&u0d!J0DcX@c?S3WROzokLe&vm8 zB*=?)9wb+s`2=|==sP3$wEOEl%=^BVk%UYHX@*m4SifHVySs7_d){f*Co}B~26Omp zAf-+|<#DO~yl=r%<~1)FFM}JFI+zHpWx(@I^fJ05vowTji{=-y|Mrr9Khr2F!cbE` zvW~xcp7mn)kdJjWJY&0+#aj+MXhz$+u;?6$|I$1t8#hf&zWxu15qmJ3Wlzqi(kln| zJN3I!o3`oidVN|_um(pG zuTme;Gs;k(^Kok}WkRJs5CTiuvI(+p-L$+sBv9!LD38R0|2DmB(z=B&yr&1~JDpR7KOrMikT zIFpWrG$g9C@x6ghVoqx|nS%VE5;b*9Y4Wq*8K3~Nbl$X>g&oEEChL#cFK@%!jz>!I z2ZjFUR;&5VP7>$?eG*3T|L>N{&cL3|06BGXU~PbF(FYjdSiByUtbDRH$u5~vKK#n@@EbZ>*k<6X|^T7}fvUU^vXZ$c3tre#b5*=B$J$SbDDBfJ2d= z$}r3T%^oGrs1G;|yxu+xuIhle&NeT_z2~0!&MsL>6>W#INWAj)CV)4?R7Uk0Hoy}K zV!b6jo{4r*zt>n`g#4p3%%ZYK`mEFGtVi~~QQ+H^uuVqCksl0Phj`srF|efm@nupf z8bG;qaj;2M$bO5M{qohjUXwX2X&D`$UxpcX*sSkNI-afq0XI*?%vk|z9+ofmj2Zm` zNv^~&*`!|(cbTO<|0dKHFRvQe+!Sq6V8fd^;8ge=S) zQcrJOfw(KtbhEHKus~ctGs#wkJQ~*0jlN0YAOPQ}c#a2U1Y&63O+yb$jtBoZ=N}0n zu*R7NIT+~Dyi2QG>AR6BLXh|EX~o%i&0xPk*L^V=@tcT^n}E*8J;>dE)x4LjM*$Eb zfr}<09&ixgI)%MWot?ALK&*^C`#fb;q6&BVo|+_$pZ(j-jX*;9RfyrPP$UH~&bS7j znaQHN9%WaaO7^ralx?;1HBte$umEaOP%nSHEhclx?-{#(h>o1DbFG`1==l`a)Fpx5 z?5}>VJqd!ZII-yf5;Sz-jbK|f{W2~~VXMWD2)qdF4X1E7BpW&cKyrxzvmz-^7%-+P zCeh(?#+h`hJrzzkz+Z4?9O1-gqTI|3&`DdzdMSFw_*dJ>2MRYc)(Pk;8JC0L2CxvCla*b2WVw$XQ8a- zxA`g9>##k*y?h2PhV&=re<_+VL4Lk^Xo0h@3oQfv_khVYLD)TL*}!aIK%uy&=NLk3 z&croF9{LJ)g>V#6ZExCfdtPvza-!*;v z+dT-5GMw9E4F{ZS8qePIG8!*Lf>GO0ea%gr@_%>!mY4M%sj$y|e)hdN@49e#e7y)F ztEjDo8vNr9yb5= zrGg0+n-h3rA zhicC=DRu``$Z^k-{@W_=x3&#I{F4Op&|3|IkcI3T4tSLwPNTI?qW`I=quY&Ac+uuQr2)H;f9S!0@g(S-cw}* zO;}+V@?Mr6s7qFORHuGXJCGH`&q*5=L4XyYWzUpAaFtYf6uDl05m_o3J;Bi|N>By) zbz`eSUt0forH9F~5sr!85QM_xrTYE-f`cMJO3UnszW$eRhi=*N~>-sQm zOIIQ!%VXWSc^m7oRhYX#t`!u#D#O=PW)|L(p+(3vcA^Hd%xg% zXDU|~87bBd!uCq$Frks1WRQ=xw3JMKvQ!d=IJ$(WA9-FU_hrU!t_3lSX+po2MAaH zV6M)gQh^F-FXW10aWs8=4b>a!SN;u{jT$*^w<%#}OW+ay*tBvXKK#c8O2u3jf?D2S zYyeqnIIkhmY_s{$6nt00*m7@w(zOV>QF1{;Y*u5P(E?&MOA@t!(dEAumJi7mEJ*yRqJ158LJ|!6et+-z(Jn=$A~RQi&6cCJq_e)DNI4vb z(T00W7N)HuI~Z$RA`is73X}!{zVf>ur49ecQ{cg0xi_n?i|9zw1TYv~>RAk}Gw$43 zmtTkNgi(qf$k{dTO}mq071(0?dVDA)whVWZq$urW|? z!FM|XmTD498GFuj;=^UZKhPJnFzuU^Jq_Ar>sS!Qk>MP2o2299m3~ysFkP{_RKQ1F z?|mCijNEMtzM4R!AWTSyrD~62FAB+CBro-d95jSRr}J?$b0g%vM0RI0Jb(?=yk#s# znWb=r-N3gTBn{TL!4M_~n596~plU}Qw-)UP@UsB)>S5zUv=#;t;FXo{_~-42E;9CfscI7j1w<+^ z>LsDJ_;a})e(=`~3G^h+QsQnGsu8!l$P%iqB`jv|IKofBw$C#cN?h^}@Z|ls82zUp zWdjFQp;Si%?U3w{Zi9hz@9T8cBtibPSo~&0ND-ejswFxShZPelr4+wv$~o@X)JAI?-3bdg&OV?A5Y=s-hePQ@Dx?>y_!3}x?1J`CL64|KG^ zB!XzU0;uWFgVP-OesUymaljYi`lSR|i^luV7EKdF)+*k*6zCRsuwzI#7yIk{>oF%q zP)IKK8TCixQ{_+k^!JmQK9kn(uBnGx0je&YN3+C+-G)UdY-m16kkCig@SU?ZSaD|ajuVXQt`(VUE}EqRKTc2#(DGo_e@XJL7uf^ZYA}-vj7!Y zv2upX!cuUNjJxu*In_LAt3|N#d_PKXzKR<2L<}L_)*eZ5Kjx_a1xSY{#B^7}PfV)t zJ7TQ!#M&y8Mn4ji9>`@Aj+o;5l-HqFG7lb(XMu>%kq#r zc09o_SEr6UK(YVZhIxtB@o!!C_v$CxajpwtODBT}SeyPqWnJOPMoIBP3^~cKQ>8ZlK!FNP&7<}v-Wc_nGZND z{Bb=6-)lcqzudRm%v2o>i%qjR8nD?A-uP;LJ@6YZuCMwfw!wWy_Iuqtu;?>9%X*A( zxtIoc}ran>hT3#O1c@W$st4t5}2Ps&F~ep)Hj=EMrNzu&HTnc#-R{ygn&- zeE3Vl#ONZTe~wvx;pld%)g-EnH5ai+Ad)(y*%^xauqsq6qsmf5@Tin$yr_>+)k~<0 zV*v~S(!g~r#Zq$gGrunyias*LCMH1mlkXWBzxi5~SU_xNe&%Kf;sm`H0Jkxvx|F6E z>Y!s~2H_j&@oYW`A8(4IEt8>Z*BQqu(&&C&GiG+uXvJoRuvb*Dzz#k!#%z0%lyC=3 zh$`=JHg}A%bZIvWAoEU2^C> zy;-h7n*wS6VXZLw2}jZ-{kO(~yZdrox&#BV7lqT!Cac_uYZiGZX!LDarxlU`xE2m=|NX zS7nAJy4>Y3bJU)Tj zL4WZAtO%B^P;1|vnOO_S3k}wru4Zg0^K$-Q?=>I||yk7Xw-z;X!aUREXHF#26z)r<_aAWRlxfu_OS?e=%Y33pNLPsl*@q z`nvY{oi!S-h<5YbnIC*4Bk!~f7%0J76GRxOfo@S+Qe-)(9BWE~Tm&y%yjh{7@klro zv5m$|{U=2`?8pVQtsDo{Qthlnv=%6$9g$q{dq#sc7Qe++nW+z!f*BL2du__yOgGY@d(|uxrEly{a%n1c^6q{`628U%chM)i4zN@0tA$Bz&CzPWqft zCZ341>UNl){X^`!8^&$Sc037{*lFnJ_eRJZaeFPVRsTa>^3RM``I6rFU)*aC3Ub+d~Q#?NyzO@8+yhP5m{l`P?X ziycj#F6cp;6%PbR5;`qX4`xvKWceuSao7S6s!$M>YjCd{#VnmD-bIt-$_xCrc% zI~?1!#9yqfFuIA~4wMRfdz{hv_i=@$ z+avzb7ANI1JGA)&5Bp-J7y15N(dz#d1ou69te>&R7q~m(xsmf`3kG5q}eQ+ zCVFVl4MY0-4?OiU%H`xe-FFY6?|q-NKGTGR6-r-B3rTR|ZNf#=Y~tktPON@B)2RM5 zj&WBOyJQU(u4b}EHbdyLaHi;);iski8!6U|lX;(C=C{pfXlb)wAk6|B#%rz+2f{&0 z!>OqLXYrsHJ^a8?#&51Mm1TlphI-F|Gaa67ZrlPjD2)%oWOV#Wl$gMizY|Ehht519 zA&;VNw&U+z=!++Xg+~pS>_aDI8J^z7eQDjku5h?b1q%S}h#<`=VIX>ffnW^u5l!(maG*l=FHWCoYq7#PXYgbo%%ptGGXxOX-K^j^^Mc1*3Y}$vg2>`Sxo{8~6h4BU!ru`B zJI+?x2+#vwDFnRwJ3a@AKAkU^aeMDs>&1mwxXzfaRN+(&^D%Rp>}zj575`@twragis( zcYF(&H+f78bgGL2-+G`A^e>rHku`I^6a0Tvy=7EeQQNhf;O-Q6DDF_)-Q6h!cXuhp zrMN?jySo&MB*k5eySuv{p7;6Wd}EK1{~1|3Yppr2Iqwj>flq-;mBy^e!}~n4*J-YS z!FiQ2`zUxF4j0*0R zEkaE+sLNL)e@$BD-{Gl|%K5?Ze{U22{gjyo&uC749B_C!VC(QbVn=M_xF!!MU>0IA z40U*-zx%TUc-XJSznmd-$=7l(&d?54Ewd&tkL0NSy5hyrY*~2>&ANV3ydZ}2O;`Sk z)EBBMh_;-JJmvtpMuAe3W^?&^ev2`MbESJSRFV&2d8Bjv&Hv>Zx@-?xSffh?g5KY+GLyjG@QykKCZZ zVbKyARydJm`rkr#;9AjGy8|D-_P)N#S<|iaeOo)!>Hd8tg(%aLl$=Pl?KZHye3Q@A zt2=_Mhzls)nRtI_T)DmfWA69psU;Dha&s|l5V^g)q|>Q#*V&;$>ir1hGg~9L%AJ2N zHSD@^edg{fJYj}i=}DLD%l`h_*FAi0{L<~Xvu~)=kp`% zBZ!%lrZdr>Xh%@fYJT7l27@)9{1=laYD+ax>3@V%@DyF$i~eZM8XW%5j&Tag*oak7OZyJ_x6NSP!3$+82X< zXv1R+4H7s6y>3!P>Vg)7{JOU~OoJ|i%wLmQ<2-jN4eLnx+}G+S)ZedAf8{VtaPH+$ zoSz1$tS!92#ekN{9o8HXn+8$ezlB^z)V_$Ze043CnkYbHu=flf$8t~Wx~{rzQ}dAi z#wCsIhFbW9>ML1*x%jntS@%OJ6KuWX)9ZHYoFcRrLfM(tu#l^o?WF7MV>_J%+kFps zo#?-lSl*3J|E?zNaaM}6s*x)vwEw&_HK}p3yWX}^-)-3q1)&zkTiFXO7iHZMS`Fp> z>2+JKHNXFVU+QU9(m}6Y)9cM1GeOT#P!ghGh`IcDR#XySH3A5mm1WuP7layb|7+<4 zZP#C7?{7m`w1L?5U+BiADTqQ)n4IgxIOLIVCi9L*MKX#B+nd^GodH}XBM%8qGBnu8 z(;2`VsXvQTEXh<5l963^QjUy-~yE{DT7Hm{_T)cBPZ;Wudwhux{Y zM^icSx)oSi)tY3k=R(wP|5}#&yI^<7Ys)ePT417hJkyHK-PX4ORun@xYVUbn1@^X3 zkC9JW?J11aT5z&Oj0O2F-U7S)4i4|BrxA{AqdXX5$jmdQ9=Hn0Xl*SM4pVwgD4a@=kjAX) zO%CM^prQXL)~zhD;MLC_Z@P7%YM&Pq4Kdl-&~NFbG&&K6>{!|cqVN)5z-{%90QxbQ z@}WFCx!&Mf8PW*Qfs)g363)aICZ}VV5yu$M>;jk_{WpnuP|6!Ks^#RUzUVHzg{t%s zH08P>Ho6XMF*vZgrHd%dA+$Jlu5BGAF8k7`xwUdlH5%{JG1F%e>@irdbj;^pDMlO8h`pAlTg2IU~ z4>uWyNWn3nzNt7EzIs&1bC8RWulFSmD;Ak~JWMC=i*t}zCiOBZri0)W!ruTzJ*b^p zv_NANFG3s&x||4F{u_i~BhA&t{$8eycMKR$YOH7%Yggi6`9mfy*#G|n7XlSQFN-Gq zng2s)_!wwpeL`bD-X;4wPQJq1dRCQobNRU76LU6ZPb@f~XWXWX#`0a~Lu?I_BL`&L zCtg|2DI97ksCDmH-su`71sEK3hP~(6M}JpjM3F8dh+|MizNV7sw%G>SEb0T(&wzAQ zT!1pto5|~AN4TXOnlMQcSF**+wr+eGk*&0mG zOmmjqp!c1#9D2NqNnMDNlM5q|Bu%JC1qKZhnwM#6Y9e&B_U8B=mFxGWRa8{C8;A{gR8o2f2iI+;m%eTP zQr-7X{`NS=>@;k!9-tunZLQQ|aX)?NW@yG?zoxTZRW)43I6yPVDC55SeWrWlzWd_a zWr^SJakui-zEq8&uZ0K$U$7By$TUAg@L@QlLRBG+>CkCYR!JI-dWcG|98=&@Ath$ zlx8PuVpM6g(-G_zy*~ALjOk9~4$gIZtMZhAOaGbBcp{2mlGO+{lnf;|5k!1FBpURi zpKV#I2HFg-zuG#R@e*}ucE@Xag#@s?gbOj!P`iY zdM-Z1m;16yi7d`A=D!+h7!)53rjDQ?ZlNeM`&~apdZE=PAe{>cN>~}eNpM>wZSpNO zK}ifG0Ytl^79CIJL|s%xH)(>8k;0$xPZnmVCc3IG1vi!9G-f9gv#g7&oXyhbACcf! zeF#SQE;t+dH5iwIl4q=1gIQYCKBW^)D|%c%`S_uPIyF0+^z^Eg(-z=Ah`}Mi0J?hQ zY_nWZnl}EakiYyFmF*7x-Fs_(3z3Y7vO5IbnM%M!%O}CAyK2(A&yz;kx2LgGBL6bu zJTA-eMp~{Mt{_3^MJ0$Ea?Ru|#)bO{zs}nhdHvoNuUOMxqA&D9d#=wr{&F__h%=+G z-4`PMj!0SVP?Jg$;fWyhyK1qef8pcg^KK#jNzEXvn6Vv{L)TDba^JBFyj}2fw94O`pTIVqCK@`_<2(7c;KMCh=-L{$F|I9y zrFEz_&W^3nDgQoQ3rcHbMz~3N81>=3NG|&oXtqAza=m8MygU7<7I9!I03>)6Oibx_ zCP^Pm(IFnF(qPpCjo7i*OJW{%{~N}Xr4w$L1vOA#{I+`fcR;)*&|TwwPT9udE#d3* z2=Yh*yIDtyp>KoYZ`;J391a5chH;8ho=$C31jmBn1Z2Ta7CzKYQ#d9D)5qfwnRP7( zi*SgBdZph8I#q-laoV=0vE!QJd)5k`c|~)v%OmOX*ak+52acs$9LIWkB$M!c@I2+( z4{y^gp8K$G1h`3L&>|?(Z0{DRlr!IfY7v{o0Qh9fs8eB3==L*v3k*|FXid;zURL;3 zF>g#}+w_204is4On^%tkC~gLZ4Fg}amKCj)EL;(|K%E5n(JDTE8}3ca>fqqY83fo8@PdDEnT|2@BWO8qN0!3(PzHRoeotiI)36D+`4($S8ZL_hw^1UL)(o+ z(6bf#^`pZnD4U`*2H2wz^F8mJ!kNR&%YoK$8_0^%PLNZ#$?>Z&Uo<3i%fV8brK+z@ zF;M5Bj)01d#?V^s*T@ff8UmS;+3J?NYTeiwVXQ^j=nO1J_DOMnxo>eB)dqmD43qKg z!0`nh>33s4CLM4*udJ=K{^YDVCdgYZhAqXIUodlPFfcvcN>$F+CnF7+%z6{Dp+T1a8|>dPn!Q{ zDgM{51mMUz;&HM6XU}>P1*({g$9Z$X?bb!Amszj*tNs|zSdQS4xua?d-I*9qgB9u0 zUVC|9t+Rn;gpIjl_7Y~H!OMl2-xd}G?m3b{6ZD~>uujQ}{%j>l5%m#b;&KSHyZB-e z)*J@HYllS-g*fo~uS;8O74?-Csfr+aUgGr>Og~Z= zino8^x42;&#E2V2h7~0VlcPU-ZZ7PfKEbx} zbEyj&40Jk1g6$z9=E=vcp+p=p+lcHSxK9)!B{Uc~y1nxRCzib$?S{DERBnKB-`Z zvh8{9&+Zd)jgEtUK%Swty?%aKQg#}%g0e10`yzV+eT`A8eqK}2(alNYM1`5iTOBMx zYJtAU+W7<{3?ar16ibci509xxiDiQmvUukAKVH{q%#axo*AgBadoK)s%!S)3$%=Kn z_1^ekF`pJ*WIWM}ANg^C2&jR4@&+4z!R!^}*7z*n4S@O!H>bx1Sp;KX&RrA{sA0KYyBT9tf28<_)BK14J``D8v$mnaSkjico)WcpZ6<=w`c3s$)U$3A8D*6hzvItlFD?sO#1!C&(~e z3$H-hn;SV51}@1-y91btn<*Q3v=eD`;6G?*sG=R z`Ir{aFRo_pq1%1X`hvkmokPT|=kaMQMYjw63ATvZ( zRo7TL*wEAkpzx2h;=Z7uvVz}>P37d|S&`_4k8&HX_|E9v-igGIyE#er#%XPJf;4;k3b?gM9cB=C=D{a@?3-dyMdoS1l2%F?4!JH)bAMq!b!w9m~?nEk=$e{y4yzhbU%Si zGI-CPJ-Wy-GWh2Ry=tWFS;BrVjcf`8;qaub4KzpG;yJP*F(Va;uH-x<%*wm4VVruz zX9g=J^JZ-?{%fwk14+a@==|LIHW&Kr2-T=QHHtecIf6F&!g==C%mQ|jQOqqEeV7ZH_6Cq8f4*gdpH6|e;{}k)IJdx3o6muJ?S#}#+RNY-x1s7J zC&;XO=R_TY8Hlq;Oflq3u0tcybSdMvVcp*lh?4bX} zq5Ur!%~=f~0Q>-#KK3GcppK18F>x7VPl+Q7Pfasp>j+}&*OmEsnHx8DfWc?O7TF)H zdgQnhj+$U>UlYCiV)&gFnC^8nd)JA0{pqbAP9OHn7oy`pPsBq^fd>{fna4?@Xlb|(=_4({O zw&UPtGaSoAuK$fogE-w36l-6?lD+|E__=zA6IoK*l~t`tUN4IIejWKe)f*3`qECD$ zMmTMVd7e62XV-x<3J&;0F@O9jJ$^<$IYJt8q$G1Z8dX$sHk$46AdRr{7E{#2t zWBeZ~=>}r|<9NFl$L3g>$JC0{yUtgGS06gkSOtcm3xWXnRMb@0neXe=D%An8J&DRQw6!wA zpAR1fmgwra-<@!ARqxJ$niUS5th5%0(WQExJs^Ko>wBh%Q%-Glv6|!iRrkM@`{idO zkj2nPCz0Q>85VyNT#nSIo~2EhKGc$8=r!rVS?SW94V%)c_mTvtC0{_v2V(%AV2)iC zo4)wb09lQt;!Rk$>K)>#s?7zSk=G zFF;0PqemvF|0SX5-x>E-oO}$t;Q{})Ln@$1a|0XAWM$*;hIhAi7rJO96vRT@`d=Uy zMHck$&u9s;P_?lHo5Z$A6jk4^+J>J$RZp9+^=|BB1cl8kZtvw>yE-f)6~o+b9^1F~ zQ>8!|R>sN{9rwn0{OacA$r19xVpj7qB+B1Cr~9M>MO%j>BtqlTFx9$ll^D4+CIf{L3VtRZtEzQgUU z$GaZ84Yt<|W*^n!>i<-WMVbG_{(}RGRFR3Xp4cL;sXNJetY{e4j_Y4JoXrnnF9koq zZNWz>)%{WTJl1N3o5=K2CbZtpCdwuH=6+jvX!AVN^3>5K@G%g*M5X}K%62VZR`$#J zDpR-rRl4*Cj=&^jQoif5?4MD(7BKw#e$g+sRm9uiAM|6=Ud7E|{1RV0m2k`hsVPmt z{~~&*f}!T3=QLwdw840_nKWp~zNbT}8_tuC7*|D? z{P0J1*XZ>aHpp_yumRE^Z%r!?hqg^fBM z4^?ZVe&y3V&p6tDXS9^V4}a44)P{_VliGND@$CHA#bR=564EIUm}Ern?AU<<8#^DN z3<>$z!f%8pgv2$I>u8;boQT^l$fYp2tO*dD!tH&`1LofO3HLY<*)iGqs3pt*8E=$y z?6l$pd!Zgw|KR`zWb%fLXTWO=(Ig$+(-jbA8OlHF;FZ3Q1GMXx`E%@(`&qA-xGjCh}R z@XM+X=YRNqSm{zhwwnFX3=q+(h8x~56E4`%=7dfRYs*Pza$qygd;wt^;&zy|F}8A~ zCg_~x;D{uLw6+fBzmmy+(6j&j+=v5RuigIx#vD+=5*q8RpA3yH5X4LFyPC~39R`N} zH0VuM(!1FdNEZGqAWj-m0{r6XcK!m>(U<}ehfUF{4_dx{##O(awrtzTws7vuiPpdC zm?&^B>nsq{{+K~o^p?F^VPwAG8ED`grnmu9KFfX?l9t)=X!Bz;7Zz{#V`GySX5zJ& z$R_W)T->^}+OWWWCkOB2=-1-#Bu$~8*W zXXGqG%<#Yp@@sP9w3!iT=_9H2F3@Ndfe0|4e zuC|`=cH0|kl4}iLaru1ahHOz(a}5f3@ODJcf6ym?k0?YO7>}>=Euwn1o9NI^B~u*D zN2KfdsLBY!%y;3aHeb5r%&KEps&fbtY}_NzoS6G{oKGpof1Od}fgX=`Y+*1nRHB&f z+JIhf-u<+@x}CQAcnU6{Q6L;aC(e)sNL1O7WqFV#UF3VEG?O|F9bU*bs1oZXLmoo< zhyo^70||TdM<~W$>NZjEtT*W#`iqA{6i|kbU^;#SY%kNY;CAg=P2-Xre*__UEsn>C zBOrkOvHSh_?N;KMUuydiCOph?S|od(n>k^TV^o0YX3NO8yqxGTTnW2QgiC*`IQ-WM zd<-d=s=yV3Uj!C=_$Zdr&iv@Jwpae2<9DG32(O5a&tuE^40al}WD140n;!O~@0FX1 zL^lJi(pE<0G*X2cj#cL~Q!>;fzEkaARni3B>GrJ)xaPmzi2MWb*vp5HdP$$W`(oE7Px{bKT?O+B$t1oPg zlLUmYKt_~y9qv(8oTHZwT53F{g`VFK&U~$72*T$o$=37gFt5du*`4x)cC17{7lym| ze()*omXLYAh z?qbBuNqA1+g61)J^&Z|ke<|~!KCAvjrQyl!^(TBAd_U~P+;b31mO>!T_Z}SmTtenNcVNotiU9&^sa@na> zp>%(->bx%eb%U%?z@QDf;4O>}lvtS++&)aDJ(*b5cFZ6JBv{uX3zD*L74@AY=UDDN z9O8O7Zb8L+VTLc^dqqVQtCU$1GiVchnnplkJLmWsKl-A~z-Jr4^=JyyWU0{$-8_XM z^0vMJKp5s+kO^;Si5eZ_UN{Dlxu%+d=gGwi;IhssU_MYrr_`fOp<7I*Wz9xrULkA; z!v)9a*~k31KJPBzCKI=zXK=uZ&c+T z9ajbZ6q%fo+N^6pTewxF0z^&yyfD-EN6{~*0k~SYY)f;cSVI;X#Jk{QqROsBIM~1fRfz0!6Jv* zmdk?`pnDGPi9U$0$Akz>B;%z78UJfs91kqVV&4zskyyeAf6avD_R z$qt9EEX_iAo^KxqmpGb5|Ii=xkEFn^oqV>!44%xn`Jlo8uk09%A|4UsG;s5&!)vp%I5(m6$pGHwDOo*7Yx@~&uS z2P-1Y!cawcg3{0P{q9TO7|I=C*lwD}+#$2*2R5RB?dRyy-~YeQqrj$7*E@+x*H(n8EL@t|3H6{c8$ziGzW=V&)jIX(Pv*J0f(mqZ(Vslh$d@BWfJ$nHHQ zjrVQeTHJfxAL*`HgF~T7yRjWR<@DA2E{5hxuM%NFu)x%8(Lc2GKkDK@28?w{!=MKy z%FNbP&#K>76bP0-)H|RRp`e)Dd68fNY6x(_SKaY%wJW!+e~P+_jpP*+EM`iSk}?fH2$R%l9#@k%- ztNN*b5mr4bF3!&L{pnh2%UkDt#W??SmM$+&4ge5?X~&n>ff~v<$51KM^g%15(`>zD znm&L>VRVzkgMa_rm`uA z<*-s6D2(s)HDg_VyTm7rsqxDY2bG5fCx1nW0^cWk(cQtt$a!+lszt%aL&#q@t3M6< zN*k1*Sr~>IwV=@-UU4)#K#UGERTn1=@!wra1xk}VVM+SK@)5`l`xww63A?H^<=R@K zF~iOb8?m3t8l4SF=A$V}5?7Lorp!M7d#Uw;f^FRhrp7DQ0Yx6xvTybR3Lp9&2uJoslwGW$k$+%vRMpum}VO%RHeA5X_bS7ObYl5~Xaa^O8CRag; z$0tXS^w8&7pPkM!SI?C+>4{5}&92(2+8}l$=5?q@L~|es%SjNQ7gPII1&-VV>geqA zZ?Fi3P6N<#Fhjj85J!gc5HvG{co9r10xiAX(2F`jFlAX~1?wa5vYFC$sW1KZ9UO1UWP0|?20SJCkC$z*~p?HL*VOJ9+_;m{)t6 z5D$kAb@7)A>_syf7MPAN%75t@*bRKz_rN}EL`@Z(-5HMlx09Pv_{w;R=P>fV!iu@D zRS%(J36wEB3>cJyNc4MnkiGV0?_#5ySFjKxj=TIlj%CD7yPwI6Q3j)ku?q)UB*ck1 z@t*L9kkqMs0Fh=#yv_S&1PiS;`0Sd97v3=Knah)Pcy#tL89$~Nxl7iYB6gE#S@_!@mfnMMR#D{nz@&rRN zV1aEP@zx9W0;gqc>^H0hm`B7QC0k*5)RQlyWq)sYVXvg)dEgnmvg7_=q0PEflvRCT z%82`Gz<=ToqT^Xf4oU`SaHFbBsB^YOp1WTNyoN`+6e{3Lb)nN3AfMg3JW`2$pM-Yx z1v8yfV)g^<=WHD4_EY4NILXey6QS_wi`ac4^mv~tL8F8hpX_N|G?`R(6c_(gb6^}p zPtxeMj8mNyc&{{E=;WrSs~D8vk7eI$ZpJss2~Ibs%+J#2=HJ1oiNr?2W2{T~V_trf z)K5HB;P*c1!2|)d%bX#iu+gG)*?|eyuhX>!JvgNC-O85 zZ-q1qGTOqF@io4hpT-QSTfe&e^1nJ!k@tmLdBF-vlf8xLke;Z*@ZT4?S%|amL`Z}J zVVQ`hefi#B7$j7mnD~>u7zCNPa15>nbmKBhH#=Q_gJQ)afA`BosVY z4)Ji#3x3zqD?hjbOuIGo$$l1>p6D4-*x*k>kF@fGuSos$gNG@%E&Md9w;+2mPT+PG zurhKDu~hD0l8~lM!h%mZb|7Hi%=Mify`V;a$aIwJp{xc0_u?TDuXxV%#m7zJBJ`*y zz*mzVO_C5={c`phJefHZhMv;W5joJJtytVQHGVvQ#ty3LxzjLZyuHRf)1Uhs_adY( z;R>7=Q~71)nsT%-yCHG+Jfw+L$sH4R*C|X>k|VU0`!ed^=6aawy4S%;L&Yrd_Vi>N z`{M~I%FNM(9HAt?SPUVOemsPwMjeaSRGsRG5+EQgVGsg!a-&{{KPV%thOvsK&r;XS zDlUrD{MEY?H?gD83uB&QiRfDvu@wS$3)l3dJjPAv&mxyk<5AA%CsY~G^QXE4Wi{oN z{baZ6a3P7g9%NhBkuW<4|IHh)RpA7BB{`Y)w_{T2AtH%`5W`?ewwLNYIW}3W$?@RP zAtaw%v8&#YUBESt{$B95Z_?W59XVEXlXl%oe85r+z6MBd2rr9)MCY;)lZ4>Iv5+{h zH%`X;rd{?H2j-_#RU2-=jFb@EoE7&E8wG5Ry3KKsv(QBZXPE|84P#ixf5Oqlt$`&{k2@|A(JZkbHg0b)u?2mnc700(rg; zp0I=jR1McjC5OMQlT;vLkQ$O;Dm*l;p3Oy4ay1ItQ@<4>bO;*2?BQ$!PBX?s)aXzH z5CC+J%rQnuS2$W*0$wO+K)skg0$qQGFYzidQaUeTN9~$)&*gPkF8+#uFqbKR)c_T# zk+V4JL}PlA)e+AHpr~uG2ZAZBSI8#=r4P4)`0=$ErP?lcso_UAD_})DE>aFLdp3vu z@Ign$aOD0k5YIS%p;+%8t*4+Nuk|0Yv+1#7%;BBgQ*n}wQpVsLKenyCi;$%?S z99;**Xw=7$?>?$5_hbbc4=Y#hNZ(`wYo6Zb{|Hb25up6(=)kG1|0ARhhz31*dDXgE zJ)vh}{TiA-nmdsPe2Jq-#C)NH&@O7oo6nKp4edEed`enyo?30aZFBQu=F9y1s8AYm z2n?5*wY>8MMIGLBzg6yxWg+oH+T))h{2ruKvR`--f_!kYI288$pSK%!1?avf^#uzt zh9csEglAp^gBX@EjYS1Rf1@V~C^8IyN^5X%57Kc341);Zi|g3xV5JJo_(aS578m4W zFuD@=_~~mI(--v`A>{ZY&1W*JU~CQw(KfHg&a3A2Z%RPrq;mh0mw3p978)$e0Ft-d z=S0DzqZh2C#1il6fTxk%x3kjwm!`uMY~{B4ims#2x(!0u0l}=7yKv z^%zCIpX^^1S9Ve7@||-g!HLOxSTw&l?5-#I41Q#{;Yslt@R5|f%HACoyQK6Ql&(vL zv`URVUe=82C@B+4yPAy<+iG5Snn6Yk$897YC!XTIac$r?&qbPaMdT)KB1WCg`tv81 z@8fDZI-H0#RV0?bStz50rf6ObqwYSaGjL0%(| z66+qPn>AmQxkwK7A#(MyMSi!s_XjN702IwKU)>$wKNfUX**a4I>v)heQbXIEt0DM^ zEO^$s2d%#3>+J63)LRHCtzZ~(Z>-M`Kxgm5Pl_opqPj6V{H-6J3!OGgVRlOUONb1x zhE_$UgD`QQ0dRtq^)zAC-mtGi$So*?klhQi{_gcCjfoLW=&iW-C#Pe zspQ?Z8lmuvIi#@^_M0>@5DQ9f(16hXs5|@$a?|ErA0Yt!XhAMxE9rrF0K-7df)&%# zcK#e`hoSZU_kX2Vf=mhlqO9wpd#x0EX2etz>LEsO72GM(6$OV;vR6EL9xJ~Gy>`sZQ=Ri&B*eIef7 z8QHTGb$l?F{e_$g)o*FGZ?z7`6@x>I(ZJ7VIg$U?zVjlG^DT>yV{=%8#!VXYVSpIR zKFV|;M23H}B03L8`s1H7jAQOoxXvKCHvj%7b2Q3`^m#uhgzUP^oUbO&HTx=cLr_q*8(Yz zX*4rqj8OJ2n*?)08*ylUy~Kvacz7%0))zhu6sy1(WH27>uI~jtnhqBoityJF@ykLZ z-R{8S27Kz)9==L6{%?wMBa!^U5Vp?=kx`g2r>1>W02$M9fGP|ri0yY!D$mN8M@La! zew8!lo<^W6ePt*MIz$;wg@5V0;2v4o^v3-HqOOD59n@kA)U`VoIbjm$^KcD3V^IwO z|MkXEek-oze)5yz{{u*ENGJxWQAxgIv?kU(Aru92u0Bgw32`M$RerZC4K-NmO-_w- zz(_Ta`5xHglo>G6*6_N|N(aYk*K{9WX?_%I1*ORWy}V3yO^$o8Gn>hAnfqxZsJmJ% zX3Y6(<;i#J<{kf9oO9GzMIp4+G(O`$P;1`82}e4u6|Qw?pr$?D@MAZu=+5zVAsB{1 zHfP#e2@(2)Jvjle4t<6V8rKJ4k~;bY;6Ih043!at$kn#3<26WT^dhl;+q-yb+%9zF z`G*9<5(UPs(2uaC7dq{IZT(W1GXj$4ecj%CO!)nxlgzyP?#Zrginl6tldO}2-F~rE z%CU1_yY2qaZ+C#_UH8lB5_Mf@<(U%W&(C-{zMGTfV}ZGM;1P6$5%0N|MsrL0@a^qQ zf&3rLO3beB3ip*ydF#?-Psb8ILJ0v)jZ9i3@pteZt>b*pRYR$}&oQ#pSg&K9AH#a? zpG8JLXskOuOPI84lT$Qy^=6><0SCwf4f9g~3Qq?VaBw>e{glFhDi(lp~fY+P7_p^>KVEsli4YS+$VDDuVw6XNr^C-|7X0tQ5fcp z`a5-J8*%+X8kYS+>qUbp-3-+3hMv5LDZ6iHWG#$*+~u7u;)%Ds5A>1uigOx~@;h32 zC!Aw9-JjY2=xKTJIFWKQ%FbQDT;g>TwPUtzyzT8zY!I&Uarq}6!T^qr>Kmjq3EbPT zXfqE*d;t*Y@-_Dk$y0&H+vJ~65gLI%p=L14V-_$sNIcTJd9ARjUgJ1y+AKutRX4u% zX=5nN(m7pfGFpk*$h^W`^~Ut3mM-c3arnlb_yxE0z$Ao4R%W|`Lk+bLbOg0>2pb9U zvcJ`4;nh2)n(w&IG-rW!-&s4vF8wavLb6g4$TDPpi0F9?sdk0@MF!RRy338I^-&@(7d;cVz*yFaz3~;Ao%%73xalf^lOP(z#?o-$_h6o6_eMng~Sk z1sL43jSuds=jSu7xT8l(fUK#UGUnap_vo(F{*asf=X|_yI!J+L zCI6GOVpjt^dk^$>c%igjf42VGBd?mw<*>jiWig#PSxz=R+S%D# z@N|H01NUxn@oGxQfnAM&s% zGs+>r7Ru?ctDQf78YVGj5l>O2BlbIc+@}(GfS`9Qw>Y>-|23%(js*q&?ZMVU`2!p9 z944}0NJ4FKaHTglav#=nLKwKs|eUa@8|cmbSXe1pk1Zz zq9=of#%Jl~>}QlNhn21;uz}`9xu!g-UI`?t*Oo517#OMn&8jf+B{OLDW0w^{5$1{>-d>BLVzTy}gyq!k;2@jp6Z z(qG8dv8!JCsL^Vfrk>C@n$7v8!+>CG(TuH73t_J_#i%)ZU>Y1a-I_5)5>OYU`Y$`g zpC3j*LgQF$4Av~I)jK~FtqZ77E0Bmgpv{O(o*P}%zPtY*Cba=s&m+hF@B`ySfeaO_ zVkax!g&{02xBHb1d&W=XhGiG`!E*e8phf_V|YI}ulBDPbF$l@~eI3ncY@N_b$A(|D1 zdwvVhv?>CNPfy&&c-`|(g3o7-SO^eTyMkzw?>k_=$3tVOQ1$qgcwPWDm-?ZxH_~o- zPt7s1_bZ2_wUh^eMg~LmG$esGp$OU>%3B)eO@usx3z)Fc_*KHxC#r?35D*CfkReNvhuEi!`h+w&3Q;qE zG&eDqszU*SMc?2_;ZqaMkW|f~G{tLIhPX*|Dv?qO0(p0QmNq`Y17wk!g4g}SbF}E} z-1A4_0*Tp_0Rb(q9ocZCTb%wAs6J5I(Lq-5{Sk;DXrIeOBJDDQ{sc7eu|o0hhR>)X^la40i+Se02^xIl z6P$?tb&6MwFLZugyxWUdsHV*C<`rPV!3m#sb5izAw}`X<9UIx+IMnc>!0-M`fQSc-tgX?w zoA7zI&AgM1rX+r!t*0uF6ClyKiTZyO;g3`+CVd{kuncu{R~h}J0PG6iVQuguF<6>4 zR&lPK#|!GAA@4q2dhu%?!skXVg1Yi*m<)@F5JrnVJeELDUA&&cp;B;ajKX-8I{oX- zVZ$*`t!bgPY+1XM=jwAs*46gJ*4gAi>eRF1*@-&eZ5NTn?Z0kK6!SD#Bz79rULOuO z?V;jo0frV29K&1`I#@wc6-L2GcLXebXqm}KvPohYHPZ~A%Hzu2Co&^e#xK>X8<;eM zhvxRW%B3?<7X|JByT16$te=i6gsBo&zVMu3fi$fH4k^kksRHS%HsAKg&t=*0xg6jA zj$I8>#ml0^bl_KwCr@SmN@V28`rb2H{uJo&H0)O^EA=f5=c8TKLLqP2P0&_)W2_ls zH@_OcS{(^&A31ydgZvocen698BqTIBb8D1DwAhLeXoC0}kh`5LeCZ-$I&lB`H=0#X zZiJNnTmEcCGr7sN( z(uWCW-^+x6=r}mTdzR+mju%mu&9Rqq!_=X4Zq?1fBfJa{%CJ(3K7VNvW{ysrJY+XPW z@bGJkmJ?v4h?+=(SSH(OoFjY&^egg-jnU9qkEsnWS-`9Yy(rUa`oTtM`<^a7IjM75 z-zYsK)HSoCO27*R0KnaNL8}s?KT-&JXm%Q=oe*{6@I4Ue|D)<112T=ew&85sUD>v6 zyUChrCR{8HH5Tyl2R0jP$F_jG*ekx(kwDRC|Y+vgQ1$_+9G0Ku3S3Gv^!^Y8Gu_FkyO;y{G>ywpK+M2r8b z?=0aEs37TIAwm{usKI8TVF*lIaIE2GE0OdYzS#D(p}M&bgw4$d5tKp`{yEEUGt+|h zLi)jfkK>SAFhg1i*?zi+*Jwe*#~VLg`wS25;Q!Qo%C`1`iRajn3=wn4>oHVWPE7KQ>j?fcfF zsD#8mhT6_f9tJW&LFgcATUl@PVhkTisQegi)B`w;Gh7e+f6)nYgR;vLt9SrahIj3! zEfPrDO0=YugN3nDu%#$c4_*9z?Cnw`xHrj^VF9QYCuIH1`kSlKs;m?2_34D!%9sQ; z+51+|SjWd*z0sO80fzgfQd>~9T$q=Yu z9o(_;1}IF!gfJ@tdB0l~!m39jsKFq=EY(mF{*RK02nokQXTR8=Q8qhra@2TkLzyhC zqO;Is_9>^|^iZ5GHj;A!*4IrVPN5M?5JPHc_JJ~5frPrt>@>Q6`=Ca_FIfgE>b`@G zrhq)xGUBr$1WuD2DR1Z=b3L3QMx=HWwAFXiKz+tD;{PLS|DP{?nmm6d{nLEkUvLlM zU{hQD;ELrNL4?o@O|nBy_&=Y%IvSL&V=_WVkrh_|8poEt#Oq!G^}fANWaj1WFh>ui zAM>?R(_0Jy@5DOQICDU^8Dh=xhs>Gy$Px7F(HLH@P?E2M0stZ6bwT@5DXfWFyxXpke~r}qiCnR+xs z^j$i!ZwdH=Xa;!@&&M7;vpPc0w$iU9VL1x*w@GBsg3tjl9{Q;AnfSf|RvAyvc9*Yz z&r&&FZ*3M}@x2-#T<#~AhvUUs+x5pwE2ioqvRO7 zrBVJiyYt&#uGAM^BKx-YoHi%h@$6!j2w?rTF*WoL^*6EiVUyPp3OFQV4#|(gq^b}_ zjg;TDfApGv`}~qwtBMtS^`s;>GddCb#q&DJqK4_6nxn0r2T6)Krw7*rcB6nGjCQ2t zZ-OwXr_EgOjQ7q98PkNYQqfBd;hy%e(tmezAEZj9??cxs*ZLQ#XQxFra~X7RSAaY~ zE(~HcFUC1pflhz*Rhd!A1v-b?(0)el<|=ORCvC&pF8~7^mXQ>phgzr&0NgU*gU0D# zE~+^a^SA$Tx`1f$yX7V)jCQ4iLB1cOdK?uQaq<~;(C&1Z+nZBP#86gk#04H{kfPMn zo4A_Mq%y$VEEzW@#T&=Bq)_#gAhEzj*Z9z`=khUlf6@UOJ;m074&G)igz$G%xM>u8 za!>FrwijLuWccoJYa`wj1oJSu{RyLtu`sX}hb%XT} z#~9)(wDS)kehj}9KY8Iv1x?%tjom$XZ2oKf+((`&BmTleu|fO3p=6Q6&WhCW_9!An z?J`YSQ)LMJDEpi1YwF_Oq^34$(wm>eCaDcO&t=FPskes2;Kck{P})yVxBpCiCgtGs z)bHeA=^IC)uQqh+q3h*oBzQ{HdPbO>WRFKJrr)E)O;tQ@{z6lr1^1IYAu7uTeC9M* zM!N4oYB8*Ib2UX{TK{bh_Cnh})ACSV!ZY&XogLD6gWl0+DQX`mUmEIz-DmX!MY2l)>sFrrNizf#ULjya;AI3r zn{L%tS6)Yk7j_L9%OWy+o6UQXyt5@CupqRNng!@n6(*GsF_gv$WyHA+>(GI=Lhym^ zH-qv}c6D;k!xY31{hfe*!i-Q0Q%h)tOf_@i-`vy>h3_J_H ztSq%`dv>+8mGBd}7EQ&3u2GJV^}*jRI)h2dx7yp$y~8KfZe!Vvq1`G8YJIyHs75(w zwxwNIdjoj-7#^BLm1PO~g{fUeG62+mQkY;mCzOIAVbF0L{LMEo6Y|dc)x8uKD^1!p zS0ZE4&#=5Is_Gl(+@!>ilDif|clp-(5mNqsQY=B)g4%2lX@dyDz0e|%!Qifb0Bre# zovUMzkJsucfYOF>`dW)nxJR1-u-GDih)Qgh#h_Sbhz|pS*B=tc^T~;^D*u z%34IW$Q(ZDLI}k49RN}GH-bpUO2hdtQh>9}su7;zwc^0(d-jB31Ha~@s{$98vxmXg ze^dwG3`rfO-_GXjb`j8-N+G3H&M{hFxn0j7QTy%F!84Oj`VXQYq z4`@dk?l7K@wP&8LNk^WpUruAuMqJHwBlZVfNoi!`P3a0Y)MYdx3Zx?>$cGpM0KyLj zuGqTf7Dr#x+1Wz{^?ral_zgKES6A2Zu5AXVYo231D$r}6`jSm75@iHt{-r2E7TlS1 zA?OgId(`aTLKtPt?0wpW&4k0;9uZl=_YO`(ql_0kg)_S=24fTsWC_vu2dVD&1}r&+ zKt(dtE?!i4#)4cs>t34tdhd$PO!1v6n2j*D6w}6Ivyy@1Ii}Y+eaLA&rbL5_eTy!Y zk}Q;87p%I5e5^)TNo=)A{LGXcIN<`MZauoCgB@@4cCm46gXiy{&Uyc@&>(@ zlPMK-6!l$F$P5tsTd@MUf}nxiwWUyaZ~h}l1E4Wzyf5w*Ft{HT-yXZprx$Wlu?Fud zynlhRtDcsXfFAp;HvTf~7*%`BKX}&f^|$Rl1!!cj=De2*=6=;27m%`daUXFo(h%fe zxIuohbtQ$=jzM*BV_N!9KkeqtAwLv`H-LS5B^xz$)=qcD$(k)U0UhjaDy=2a0e=~0dpP}H=1IFpbatzt z;|&ogJM7s}jguzGN#nFox`ykkYnLcrY55`97@AwIj!(fCN3X7-u!b(QJaA?Y|E4{oj{<4g5RgT+W?;evgf5P4(nIhAzq^5HA_)OIqkg z^o>AN5^3{bV^@eX<<3>4r_ex_s_aWu@0J3tNqqT}O}F~H-DBpVH^^PeMy*c-!bg@4c|W>b$xUhb|W~Yy7*@s1m3iqT-*Q=J~jZ z6ndUvd?klqm3&dB(>=I}_4aRDc-XVT8dCc5)Ro<4Zc4fvm4^AaXc6K&wdZ~kHu7eW z@`QP+Pn?s;Cghu(rj9DIoTd>rEG*<*ry~c*EM_TEk96QcfVhqbLgWVZ~i&r)ai0=K9u70*pv9z zTXpER?kkmV8HOcqD9okj8i?ukvk5s^(Q4t3LR0PUTolDufzOi>!Owat$=2*>)UQq` zd?H41B~D2oKR(*=@^qvA^XbTN6@uk77+ah%+}-`X)~3(GyL;nxuJB^sk$vlR_GZVn z$7}hvfd2a^*_SI#K|zweb0BxnV0aB}#8Rhh&E61mzk#17oIi^+Q3QHUFwhm$#h_%Y^k##6|wXO$&g_LGKrYUNCHsRP6E&%C0-9tO>-Ie0i^tXbjJ^KMX{*h8}O@56X%bu}7Yg>JAdS34hSwtH>A zVxuyAyWl~4z(t-g%6rNG8z7Nbnx=Mk8OR_&cl(8e~Z-!6(`q(>o# zQlH;X3RQk1Jvffcm>C9>NFSIjhcyXjC!y!+4i8aIf%+ROo_sHivd;{gWKxnGTMQf& z2F$iw%hzcae>VrH+nstgi~8B|!{yk9M9HYIMq)dP^LmhPp~6j2m-71t8?nP!FA`o+ ztUqXqWZ3NcklVrdn67Tud_= z6g7daCp4Kr>>|3^ZS_Y~)^T>7(7!Yt8{9C`d3c2MI>sw=IDh>rx1Z8uW3Dv2fJTo} z3OB;g4MN;koBGU2$*fvG{82uYaCFy8!+n%h?(yp=Idu6mAT2wEqspzE zIyKt4m3yEhs)nv-St)goy&wT;?gjbVRw*# zfGK}{Fv%895Yroeb2OrzR?v{ED=8DBrY2;^3BF*iUq#?VE8-5ccxJ=29ndCi6g7P! z$O&1ginS`)o0rO*Vi0YEy0xZ#Fv`vdqpha{vjE}X#ENE^6p#={wIr%6oPO_L9-G>X zgaQf#@E=Sv46V)P7(~PGS$M!?d6JQaL!(M}9zvq58>h$Vg*Jk>Q^OE>(km)L&|^Bq zHf}KB`}_$*X-o1QlRrCzVU)syxDB4L8LX1#+Pu!eQZZ;pUQ&I|_Y`;kg{4j2Z@&~8 zMiqt-kV<&=_cBOtk*=Vr6}4~Z+u}9#RN^4_hy3R_>74@fD&$H#yoj?%5iVwsH72Uw zN@9E1MnBhX@@n1vVqfh{-}VUT9So_-=1(|N#gu)o>(WR-eyq3HhCg~uTCcsaIgTnzX z=}&$jfv`LIWTZT|47*+xpX`GX|3w8QRTef6!3`VD10Q-=)NCLmy?0XKmv=<7EC?MY z^_kv3Xe#DR?AtkBiAQFhNkwT(?bS5+y#P39ARJ#TTd%Q+lxI@Nl*Hr&&UVH&jXO5k z@94SZ05?{!aXf4Ap#A=OL(tIz3evlYo9ii->FL(3X&;Zh1)tEh++OjMr1TAlu1Z*W z`jK!j+&QXzDo9CpObhE5L^6g%4H7 zwqi^4>?&^Cj$(i2hvF$p6{x)Gx$Y@>f3hnQdrs|mMQyrGARXO>e(Ax?o%^;(9YBvGbEdS7Ek+U2ON$a!v#6*% zXj`?cHhB1=j34OVi2fx^<5Gxye6V>+g{&C=T+XQ2-}W(2VB=xL@dhu5%d_!aTps}= z24{%mfK8-!aBvSKkOQu{-ULR%ybhA}cn5tx2K!3nHbB90!a34Te{rRR2?Qwd9}Gx&uH zt{la3`i~|?JoP`@x9Mdn3lYn2+1*&$TD82qgB+46 zVn0ly{N}qzV z;sy?F;8bL7$(QPl!J9&39|ukLcsWSQ54r$ zY84+y3W)eQOH2FzI*mdXHr6ZBC!Po_S@Nvna1*iK9?ohD*@0bK)Xs=wxg!pLv2u{i zlMT{?k1?fDsrh`XO9m*}^B-VO>W<}K`o5h|+g%>3&28iu{_VXQQeO&O%KM;V#wU5u zi&6@OMi+Kp^ba2<$dKA=LKp!`M4^n+eA%5~30SPt-sgDrxS@4f{sgbA%vj;tNCt%{ z;M{QbP5w?i^&=+L7{oCldzzA2h0x4#54^b+w=Ch-8e*#}A=hT`#{mzeT&a_$%(?H? zj#S)5uL(bec&d++d_WY`l9AZkF-5Z0W;T&CqJvB~uSPQ_MM|_uLWGkW90h{>^Z+3; z{BAF^=)hX$u0_1lL;pbTl1_hFa1htNh9#Tos7bGf;}rE+jLu ztY11K9P3tO$Ao1v3N+4|)HfVEef$kJ*)1HFZulL0P}?1Oudp#ItL8sQTa%}qIp zj1MJK4Xk4Mkr45k4<0W=U=Sm3GG^`}J0t$~M0l+-)5j~yE(4c)C9Iw%$bAa3D-rmC zF$hQ=V+=dn8-FH?P!fbZO7fd!k9NdLO7vAkC4D=H<6B1F*Nbfx+OK3@g?t-xhN#9e zY5{*|Q$rP$`&4EYIY3<%nrK5;7oX1U;1&+9JW9ZdQn_(QZ+7c~<`L9P9+^f5x5eLa6JaU5@o<^FL>XJ6D7rxL|k?n`>A*Mi^Y|OP$pjc}x998yhtJ74%^rn)19IB(O>?&`}6RE&&Nh6CyYV zOXU~=yB9g8VwjhS5mU)(SrcDT+IVp@AO8r>>l}9>ANL5!AFH|>HM>hUJp70h|I@tt z_q1v|+rKC4W{oL)-tZzGgLIx8#ms3ZxBC7~kHi)t?vF>!OJ?BHYez( z(gjp83t@}GJl<^E3ZBuzu#4inclz4ORJU;39f#O#TdkIDByVE+Dpv1rDAA75spxhv z#P(=JYb>s<${rRU?oW8|K40W~rCUBu zijdHnbxGCd@|iOL8T%u!equBw;>fr85br|lFMI+9hj@El$!Abp9r`CC(|yz6x2;vT zpof$fdmZsC(`iGDy*H*&BaPCN$Lh;i!d>yok;%RXNvx@!;2h;Gk1g!fHa!y^-Nw_f zTrhJ+ z9TV)$XejIFFvGH;gNcre3dS49RHAOxBt6}kPJ^Y-p!%fruhS;hTBerYe#JfmnOL|4_)+!`@IOt10s10P{qnt+ z-~x-*n%O+1yY*)rL;JN$=;311S-1`g$jwxaA0)g2d@#%XuzUQY|1C3wV13VP`8OM( zKh8MJMQ1E1!eU?);?GjWlTuhzazq>sCvy>5M@d!B;b4mR^GCxuu1R9R4$E6rqteqr zV)ff7Zxs369F8>OI=~|K5@+Bgl1F2RbU`oY;R^PlZ?h4PekQ;~L4#G^#GzR~>Gj?u z9nH`8c>1y&iOt58#KcM&FuvgOXNL7TNWNgS$E6kiXi?WwgzH^zsO{LO^>x*xO!#O?e&*~lVqIw9rKAWGL4J*RUhbZJ(@}$ za3^OrUL_r+&Mynzaon5T%Nz!8GAT>hg=%P4{=4Ok7-QNVesZu*kcLGu!@~u2YUDvT zT~0N6_!7Ke8Wcn))i>ei!yfRpb<Pr7R z8ZL$9H9SUuv%vIvn8veygl)GiuZ;;}P5iF}l3rE-N(oVk6(ExJR1hN; zw$Ifn)2w)^bZ+x zz!eeX0+HHms>KmhWv>k_kVP~^2i2fj!$uWGZyTrc(HH?SM&ahb^L;TSKXCya7)So< z@!+Atwj$P|5DFU!q3GvZPumBHRmTeLJ?VzLHByxHkme}~zMKV6!lkwWY!i2QqunUU z*!S>+ncpo)d8nU^%~sk(s_&w50Y^!}bAJg5vf{EHR2as^C_Us_l%rQ#53@ zll&~`A@G0bQWV!pJ~)xRZ`_U)xukcPxGevB{`d}ZYoLEI5OjS|p9KHcn`1o|a z;t3Ywbc#{te*ePOS9|1PwiSP}^ItanKZFoMV-Er>SHg=`%m^7bxHpFG(*BE5{m*dn zU1mT~PPF`_x&MPz-Anc!I`CJw5%`K^T!p!Xl<%gyS)QhDBg>umM||FQJ3#668V_6+ z^`9pQ(rs&T(tTx4l{h$66}z$2c2y8>%YH94@*#6uQGZ?uZFf(DAW5W77e??g7Pd!s zK+FXdcB>0LLFqra`Gy#dU{g)Ub^t<`KF{Qu9>s+6r#DN*bAk{yrknnafw?5jDFV8} z^q&}mo05{a@gufz%56%!6RWFd6uu%Q-F{sSC;W9nrqTJR4$+FOCv}r8jo6F8qU^47 z438G|Bhz6bjD+9-s*$~4JPgkRgPa#8x}SLM)j zM(usIJEKAlr67nf{sRXrVQ9%JMeiOL#qdQPX1GcdhNOoXCSoY$sdQ0*%JP`c@NJqA z=X!gD2*|VPG$3&BDE7i?@I%7;nPOejO`kXAntoMrbM=+b z=l!w*^4EZpY4)W8N|Wa}MrbWsfQX@xKRD(DQEA$S33-OFJrCjxugYIcOR>=!LX?>& z1CPy1o}TaE)%L=wrUyI}O(6BnM?EdOS0fQkK0l_A*U7cp$I!8m?*hdHc8_0_4^-eJ zfB`ZC7d{@7s%oxlM7`yU_`%4TY{=W;?p$}b5}WZh>3J8Ii!Q_mHfZ{WG8i?u3*Oi? zzbe@sW#7zKXS+&dYU@~uKUSBs=K_ZdNIShj1YEZ{3r{70blMrF{;VnS-I{rZtnmA}M}~v+7Y9Mi_0W1>;2KVGi>f2(MT4WLGTB_0$b_lX|&z?KvDn@iMT)JwH~M{nyIC{2d#l z>Qxa4W9JNq&X|_YN9{IShMk~`Qj%dj6eao%vZ{;aa)^-qKwiR*L%1w_513Ag z<8PLf*8>e&1>^EWvquqJjLG9*=!0kQI~OFT1n1XPJ~hk{PdvoZwKBVzbhiYQ z)MIEU5aER-x}cg`mo3Wb$2JrW4m@T13BwItQWa6m)gB{U?<%06-R~fwBW{fGCux7A zojK~k_fOvsh3qJO&t}}M{a+QFd0rAAgrIsNSdT><$}~5{@6G4haK6?;|Su7@av*wQ!cn6(6dRoPIY1_D5KH zFIwQE3wMh5HAxdU3d@6KC_o2S56d9JUw%C1iQtnJ#c+lAfL!i_QIscrrvMs%_Hd_> zP$>VVekI4nqX!H zJ4~(du6&AK{*C6Kk2B3F*pZ9neTzkJ+u$I=SC>WUwIpR?$A?rKYEECRZj~d1Go`Ef zO5^V24Js>6U9gjLecn?NAZM;}`}02Ec<{|fxS^>i^134$&j`Yllz@kdvU~EYAF;nN z_VCcBR|5Mn5;VG^wjgflYgcgXDc-k^UwSuu%;gd{m*Jr94>PAn393XoMh4cevmB^^ z*`Ex$F($j$znu=<4ew^MpK7+yeFdG{UvF^j_+@-Iw3@Gx0iM8w$dO?t7R1PBc_sR7 zkg-$seHu7-aCOZD_7?){hJgFMFq5Y9mm2l(w*v2S@A^P4YYR6j?>DO4lHnLh5zpOy zpO&vMG?lB5TCJ}MN~wDPDCJ}3+u)gSacgkb&lJ=#;Dc7IPY08Eay@rTnqb2dSs_(U zmR&r6&&JNKbn050cXhqCGke2_npVoH1VmtWV%@NW;o258f+Ysf_}shopmQX$p;(}J z!F1=efvPNwcRQVCz_Q=5{R(BLM+{SALcW)=R5=iBN{@9Z8H7Vlh7~>Ji5f$`vH7>7 zrHs2}{rdjZ@q>r0%iviSrr;l-R1Yd(L~p~CG|4gtQ|`!aY65og;pyp)0BFB1%MA?f z(F9xARuB)IIrF&ngmX1v1{o+qCURl`Z~u3|^oslbkTnJHrk={W4QRn96` zz)QozI2JQI<|A+D`u*=A%$CdA?Ru7dN@Lu>+{saGlHTm!NSw|lbUgeug~`Ni9}*8u z@2?y-#4`^{R+@c89+-`_`H`MTTnM9Huhpq(pAi8p#KJ54%}alwV`neI=Uh=4X7^m4 z|HzMA^52cPJ9?ce0nI?h{|Wu>GXgWnwiznJ6v{FcP$y7Pk8&?FNG3Cl(qFri^v{ zFnizotQQ}C3LDOS!pmlOb-&nutj->1Cd6!Td-qGrF*(bS<<9`<73B43|C<{;OCy`V zVIpt}F>SbWLKscp=0zm75TV!(M!a`F?@tMBw2H@vp1v5w++6wXK)~oH@`pscY!{RF z!)50gWqNOvv-$z6;4l5a)`)P02swHd*@6ckZ=(VTe~{YM`%>$-rwVlBi#s&|^!^K4 zccc;UX0a98eYUn%=U7=BIPb(}39e#+y0(j9-4zi>alJ`#fPGa%rHQ{k&ef1_>WZLJ zLb=DqfIg*wx{E|Yi;4^f`;lox9UUJ}s1kg>M*U*I)@;KkssC=pQhRL7k*;h?06G%= zc9(+fchf(oKTho{LvhGhgED8peFl#njDdZIb_;wN{!l8p5C&1u6+d)1V!bUEnu$<* z1gI+YUOKfFZWe01J!~so$v`xq`!m4)<@r~Xe@cb=_>;4wn#bDDE=ZT;tBl00C{W#( z#hDlVQE;aoYrkzluA&ykai3JguRaWZzUEj`v zgCYoQ5{CqR&cJyQ6P-#u_Yit#@_DSzPy)9FtB}k9qP9UVZs;}CCZMlQ5GExA$VOfqv&m6dNsleaH`6W9Y7DO%s18G ze}h0cz7Q)XivL~DCBdk7-I#1>5V)rl+}7TDmO8m{1fQ>y#AS!np02g12pWMgnZ~NR znq2d}y6K5Ne2AP0sD8PA-i1{h*?>}@aY-7!-L#66M&lNq4OQ^dp zIR5tB^}Bh(3tlc+=vft^>vo~?A@t2k``F7hkU!6Di+%Drg#GRL3Bs>ESay&Rtlnv! zgOb*WiYVna7l3+Jd0r$a?rMCkrg!u(Xk}oq(txc?DW7%=fy+{j5xMIXAa!=Fq8D?` z!+)g!8Cm)-Z(mrTe@mW_2I%q2qpGqaau0998^N>XO~2g?bxoy&a*+c640t|gH-}y; zC?X&7ap=gGrQ22^NbM*c}~dv4!TIpGHOfC<9myMnT#nXNgc^Bgtu$q;hu%M zqp}Q&2G5;N#EDbjM;vb$Qi6;qz|~|hNFKY#NV=A$jihOZ>1C;|GgDf*jgzV80TRC{S5cElK?WAT2|Q=|e@41+ z5u;kktG&Rau#1s{h6GG?)PfkXsZd!J`95DPo`>lGme&!)Vvvk|api9|FypGIxgL-!-699tn z3VzZkB;jB?JbbPt%{6Nh5?d0GYt{~cfRco(A0d5T=;r9L${wqCuH!(3&?lXO}atQn&cp@6u=dRsOzZrKsbP*svK2LK8U!EmB+o9QD z3j2r7?TWUTi*-m@3|T}Y6ytRxpa>I_w^4la$baN#$kqnK3F!HVeQkf`SN1@V_yBOW z@}>NeN8RDHk{Ed}X^_O_cEDeNRydhbf8Ztd1n^}vya|2s?g)>ihwdoLC!sLQTM~ux z6Y!GLe>qVzyJgI>g`+F$R(VBox`-(a87$vlq9L{k79!^n@cSmSC%H?K#r6I%6F&WQzFn6dy2*ky+G0tJoOL7*aaI1SOJF3YT4hZ2d^nY_!JoWRiEvZZGdB zM^AZA=wS-}BBsugB9my5v#XT> zc9ugd7LsNQKPm`}WzC(zw%wt%@{r~KS@EKQ0+`3Ji5g-kz_4*atHA$Fz~#V)m>3p_ z_hW{-6{IZ3%=RlOg#tHqPz(+T^pe&apQfLh27_WOKn9%4-TdV8+J=j6RJX2 z=MEn-B^q*f{%ikwWpyFuk7j*R9&3&q+n+*xZDH#Z&vC~wkmGx&-a;?=!xf`aR}swX zs;+`#d|X)F`O+d6@qPSXFnxT)P-yiKbj?CImsh%cmnE2VI;SYQXiDKtzn>~{Z1Qbb zPz(nve7?s1ImhnjOfv9og}ozP78>%uN2=qyTQ8VCjCX6DUKX!&a^m~JStp>hOiFkT!@v=NFYx=<3g;cdElB3Bg zDaFR;A_r?i6+sgN?b`t1;C52*i5B3id?qML3~;a-4Q8M`kZeTg&8>> zhq#{ivzc*dzs>Bd)6VJr#>Z$R~nY^ z5F!Z>^oA|L2`vEXwC}x3(?=K3YUC6u7G2IN70GGAd2Lpw5-}Igi=>c*gY%c9PD8`>yl+5)(QrD@wdkAV(Bo zd60|PRstMO8R8!r#cY-{LW97bXC7c=@9x`jUyqZp1{?+Ps0?yaBfH7$mJS)r$#GaLRG;J;s zXfb29*Y6|}S;t|7UyI2`M4?tfVk8Jzh?CeI3%q?n!u}-iH*&0TVnck7%puNfHS;bR z=DXEXJ6X5p+TeV58s<41Bf?D2itD}bVuMc^N+!q$XVR4QQy&0VWP;%=S>dX$g=s*T z!1Cz9J#L7qgw3sPRpOxbU}+COyG4@mQi*&w-yr@O*w2`9ExAg2^NMcWV!ThypQtJs z)DUgB=Q?yv8Qfj8QPRomgG}^H<&YaBKiNzAr^gE}^yftr!X_;`dVzje89S&r&;gJU zEBKYcWk6%o$&)EgImh!aO8JViA7sGP;kYqq?lw)inlaeT7en-_k^dk+-V(yA*+h^Wa@dY!O{>uE?6cTOdUxe6%zTc%j~^kQeewC{H}!6b%) z(i{?@-X*gaS?Fu|ZQRQ5-bSl=4|a1m&vKyS@jcaRpoDqJt`W>VsR0s=1j>b#%nvfP z?ap@1#f!NWYUY2Qh@Ez;598xX2Vz8x(GHtKtADMupn0}dKoC@P2)iA>(af*&Z1Lca z9E+?I!{HO1uwkNVq49FcFN?UdmomOO6SLJ3{Ow=#t+-3&2<=35A z%dU?-+j7v|@Ax`*fBB?GOyYyrU=*f&UdLc#b&8i63xF^+9oxYKAlNq+>wosy_fs_T zQi`G=RFQFF2`OKUFlB>NCrR4HCCbrXMy8*jD7AX!YW!ls*yPIGhk&<*AriiweDeAo ziYmw5c)vXknIDNpz;N@AhfJ2cA?tQAeqGThdv8ImWU~&YVn27bVoNf)lwCtssh=T; zOeb$eK(OLIK<@?W`7%NRLE$6-3v~#tC=YodSD&Ao4~^5gb6sv*RznsdT@hhS^`Aa_ zE?(9By;+y_LV}c}5%#*rpMxs0viGvSs>=q?56l#{gDh7N%A?^$bq=#fQ<=#fSM65) z!FXjITI(I_$M!$cK2$RdW$`slqVQ{N;oE-cn-bPb=_?ZJf2lb3)2%S;Ykc?&3zfCt zAlwrH&qN&XmJ!cBMk(l++88g}&1w~L5}w4?YBm#W%5B$_9NjQrn2($iGB=NKO}8wf z7S16I8@X&Y+coBzjQNmLpL>|{%%U)_>a|KU;ytNIWn8+$7+#YqD&XC-I@SNfuEmBh zKW?H4)p<^j0ROlj{L7Drr<^CD)FV=pP<%k2=C-@)XCfi)4LU~Qct#;EQjY1gDz1b$ zgy)*jXy5%v;B>cgRbeaco$iGGm>o1^lmsn-kY7>{Av>m#)bXJ4vX-KWg}v2qKo~;W zdyi|tOh00dKiHtPuQp_b&AC6K8)A@;RWzH~)#7mAgb#}as?)1{sPECz!J=Mu2e6Zn3sBgG|*fx<>K?CCM#DG20 zC9NkJ9E|7-`XRa;E|UJ42VjK$TXi5JF7hUrzuR870hR+8RoXyTO;~StsY4D8nIMVf zi~oS8*eK0LKWZjtnj%t%j;KiB4+RE(i*$(1ooVurm0{>MguQkqGR zk`v6uee-}}syOTQXHth5?;%6B;;Wp6@>M_Fo&3HunC6da9?dm*tAo;U!v;{$s71XN zc~{juzN2LDbqLe!W7eji<%{Sw-Ue&x=*dEHqYKoV_W)0-zZE6>h474K%TZKzPo7X|PSzWL!b z%J_>(fvV9}!0#}T*ee!vmA_$obOj~CMjRlSlS~v_jl2@nND2(Hu;QNK9G6%{sqX6}sO7=$cx!G5MTwqGw}d5o8^#ri>Sdu~ zcggVu-Y6~|INrz?SeX1waRGORfp-``PYDA*r<1d;LkC}r!gA#^I7%rZ(gq3fcUZZ+-b$6YYU zwQ{d5`^Yv0Ylb$VsSq&6X!K#BJM*W0R4~XMC=~+pD-6<1o$mYZ96dl~6vO#zzR!0( zxvKMho0lX;y_V^rH}u21lmoTHORd?-^;+_`8qxS^M&8%!+J*1G@bd}x6%|%_D;4~m zw=GqeP-VafKEwIHU1E;%7?XS=x^ag2%Q9-O?RB|1`y>bvGb5+*D96I_BZXWkcnkU> zZ;f|V4-+s1FZ+LIJ-}o*C-~ES-#Z7A-_pWieFxZ_AA6jvrPfIA*F|0LPxFR?cd7k= z@Yq2Z^!s~VXFr!jEaz58Ka&VG&UVgp3S zkk@gVD}hS(yC|xG`*(S9Y$m#4ECej8?EGCZ0x?o;^5OHa(5Hf-6ZL1!LVai>4YI;4 zMTSeKILP)O4EFS)s0nI7n(>I-_?a3{<_2|iL(f}hNOSCYkHC-Ygd1ZtaOhY#SzppM zOC`ZF!U9K)YPhS;o6;})TNmm`&z$BRbyN&>D>ho7Vf~)9@MPR$I^C`2c2`?-(wi9$ z!Hfg2um7${r~j^GIwN}#-)8oYlmxmiHu^7BK_?72ud8U^`9%{%0Km923jtiCLY@ED z0gnmo?V4j+8+i*=heGjmb{Jvof@uY9n$oZEL(t4;YfW2yZ@Q0yymaKUKTYV3)~Sbh zS`1y|8y-L^4mS>D#^q4uEQJ;FTZ)M@m#PLUL5rH+F2UfOHW(WsMx$FLyu(<`&O5>5 z0Kd(x7j%=;1B#EaL0mqEpb9oBxr0?9Ib)A3@B-&2r>a0bKW<6DBJ-HAc)<`Ax-!pM zR0x$ATYv$1V^=Y97% z`~3I$G{4VzUC(^(F~)D)ND$XNQGxbW*=WOBn;6Te+g+Xvq~nOa{;=eczt@TrZ3=(c z(SG`Fy5h%-!^$Rpd^77Lb-)yk(wB}b(L$K$y_bC+8K|z|#h7Vn(Ze>JvquWD1YY!n zA>%VSQR)%@2)t{+F!92ocnfogXZg6MC<%Cn_l!2!^HO4ZjsVSoBzj14LrP6 z$d7Cpkia$BiA;q6s0gN)?WWRTV=SFWu3#8QEj&o+y>GAkD(|8=bP+#)b7COk3IBtI z3O5tkokPzJe4nu_r#G>_#BM=*n|Vs88A{1^I$b?Nr z4SQoqNEW#ilN}?r?MC!7cF_ooWTIMakUe{O*(;&geW!@cB_n1_Iy+Z|=Z-i$gN6-m z6a+n$Vrz}R!(mLhIZG=YBdj`1p3WVtqN>b>G25mO zMFZkW8>Prud>44E0t~L!vYppf^CRbn`ZJe@uxBl*QbQqEqwVEY^|ngK!w@U;1Wew2 zBUz7mN5$d5F4Ga}+fQMuDfzvc-+`P_1KhDyjDY7tqao=p6*lKYQcn_B%hlO;i%I|w zo@~~+#MJVowzLMd7mf`HyB=7Oo9~zRHOuqIz#8Z2L%EHPjO4-!Jp$LCn_W;sUV5RD zxJmK6K8mx8mryz+9@p$p-E?$^339LHPDa%xwWKqFonAYV**RHVS*rah7SD4V*5?-Q zm{LrNZOG{@b2aVuYAv+DWH_$Y#>U15vnisV^`;XwIFU<#_1;&jv3q+)hYFJ2_HU0q z6xI|F2HWhVU1j;d6;o}xDu0ptH;oN;*ck+ogU9@BHZ@dIKA-cO#dQ~suTj-H-Pp2e zs^6J3D4S+gC8mt1I%N;-Q~8}M03j0j4IYmdu_s1{8bS&i3SXZig&eNm9e<0{syMF= zKbJdWqVW4&7WN*F`i!UpmQfl9j4gez&$s7;4f?#Uqbt&3qRECy41e4m+Ri^2&yIFh z>`Ys-<*>eiZf#_Ww8LUQhddNd48lymA@El+a7H|FG`QcdLd_}_?Q0)UodUodGX$fN z!*f-CF=%rPA?`Nqmy+Ri^mW2!LTi0G!JKNdE&h|aj#4}UdR%3C51ACPadnm$sW4X- zN0U|3N_$Xy-0e=e)UZ68uu^a`U7)grG0A#vJ8Kpd`DZ}rQib1FWKQXmOW6{+t5j4Y zIwEMQ8LqIJ{vw$q(c#iAwu@%zEYsg1Nv+)iSWaOKLZY@C{$wjpDyx zoAHGXDP?hp$R@X?0<^TiAuauW4P~Ef05A$5Aa?-CR`RpodEQvOscKp>g({67u}|Bj z(`g_akTEQyL>F$pV3`gG^x&Etbs|M1cVX?p?%Tm*@}_meOM=goRXO6F)jdSoXaqCJ zZ|*GKs7Q0}2!5d{{o>yIi7M#@55E(|Hx*5La56dn$HE;{ zJUyZ&;%eb~L3pyJ6FJP`WkvLTl7OyEZscE}s`nfMtu9-(C!0QQ*nL3gg_Rt7{g2|b zQs+3oXT$YUU}wR8Bx>yomS;*szJod`GZvsnQDe5S<) zOi!06^KK6LE(Yi|4oSM4kvVB}gp*`2km9ur}efb1!w){w8MsXz7h zLAh;_&+};a^dZp)!2mm`ArLJZ!>4X$l8lL8G`jN%m!YcRt|h5yrhwmvX#Lanac-t9 ze@C11&TnWnN#;=RB~X3C&4he5JQA`w^1D^u_TTVoaD~>BAY}Y`eP(V0a}xZ`U+3oU zaLIuJh?{mR$n1`TvNDOk4&MyDAtHB+qvj^_v8lFpm%P?)`nbQn6M60lbsgLDsya$V zDA^RJXFV-oc*JY7zAc{!r{N!1xnZQMh(`55xCXoD*DCQbBm9*3KA{V_0B?5M4BDAhp7q# z)6jbmhWDJV=!PZh$xrfkd=8Y)rr~FwRuc47+Y@MIK|KX&&1sQ+5DSaw?rp(JK%I2kfHAL>(U*7V#?mMmFK>Z*Y>vn8)udQ|0#SC0BdGP+Z0;Bf* zAL?$W78-TVS6!98u^hu+&c8rQO7!{Z4dK;jt72q2F2x=U+)FE+hQq|YL`4&OihXy- z#7U@(6Gv#xP))tH!)t%+py5TOXO|P8zFmSKx#jfU?^u?1)jgT70E00CL=#8OI}f|w zn#Kl(1<`MI-5Y^&(GGD9%+ZA3qVWk+nlK{ zBCd+w$&sXeI)&N}8iK=BP^S677GoV(veb7C-dvma-}T}y+D%8~QN7|qVU#2)^b!1NcQvPT`Uus_b6f9q)y?1f)h{IUxP@ic?X=kgt#A?lPn2bu|!v+`4lc z7J^NLCxt`FL7V>mmnLBmf1oY#P%ZVHSE;7rg6E&GbN43l`wBP$k!>D`TpoohuL>Kw zA7z6^w4H(mB@ehS1kV=O13B5S{EB*=4wc4#p~KL{1hsFK z{8M9saWC?7^??7=(Gy{PLj|n<q4F~&xOpyWp# zRi#OeMA>=u2mTg)K@09sY5!|S722umAdYzpQtu$RbNo?N5iT6l%*(mmlQVxQ2HF8w z$X#B8@1HkLb|#YmiwSCTTZ0_|kO=r&VP^+ZNQ}Rmq@PuXSjvR~C-x-FToqMCV*Q{J~^+K{AF{kW(SQ*S6fG0>)++SUn zLI$}$lB3Hghrx_g2O z93|Vf$LyjD$YN+EjoVJ6X-V1y=VW~(Vf z4DA+j6xU1Fy8+4L=2h=>Uf2R+*HUF6#M$tHf836-K2h1W{Rhno0rnjLgh2Db2s|%j zWaO(Wl8!)1k7%#Of%lG2y2tJ)tG7<;ZpT-w=4keB+8Gf!mLF3|pClj@Qcl`7=ZVUE zOFm`}D{Je*;4JUqQK~jOdzKF0=C?fAC1L${}b>Ji;4}H}! z)@%4%!hf}g06+*|__uY_DPknar*~7*UP|S#!LYO-!aYVEM$U)v-y-min!(k+5Hf-g z$`k3m;lQcOUBaO)Xlq!vB0UmJ9h$LRME7MVz41}LHy11h@ZWS&sCBa~_>KgGFr&4t zm`L_50@TU>p#tzA&KNQ#(C1Qt63^DjcR$u@XQAUcaiNV5RWWDOsb}XCNBQ~u><()F z>cZbOj6dS_{png=nl5eVvPV+OhXlddO;MAYi;@VGqFZ)b*4Bdgb48(~g}sm(qj9H5 z!vi=4#pmf1C;L*nYm+TC7xw_T zbAbo3W}CA{OfUklGJDKP%HB4@RP`TWmxWtba7>G(L=^86tY>H zomkC`BEXmqoaQ}#`t(0CpS`r}b7ael670f`t$t;Cp#9Jz?+#FKx0duje5qX!^2SIa zQemhvawTxb749&>R9LaLQwgpfeHHdl@i7cliIF~URf9LPLmsyaFb#lOzCu%;FuOtD zN~mubt_+HY(8Q7gEHs^OufYsdS(+n*;W75~8=yR>ZF5AB0@NDk5 zfD9+Cgb~Ycjf9a!k*b-SfvNo0Vh{g!Zv^u-oYc-(|B~PMUvd$!vEof521opA*&YSIpT2KUg0*yhdHHB;WTX@pK1iB{#!&X;Pk8g~0~?bd zTQSq(Av>_fXlJAhx?DqoT@b!TT!i~|SDss4r0w`7y~OJ0%25bY%K6()s-B{bN9Ivq zNWa)C>ZMu6`Ka{Jn=B-Hv+nD+Nwuc`0K)`Y#HL0c_0WfaTeq`i7Wd1}MM)_A#JT&c>kw0nx4rG; zVEo5^?9pmT!z2iw6;4J3(`>jag1oPpUas^VJh7Kh51cQ+o=t6YT*tt}pG2>EWlyvR zjyu+$%3-r8V&3Xod|6LZ1AEYo59tExFzMS2H@>S!x$qx82GrSI=&`h2m6=}g&z3w_ zHzz!BZOW{NCWs`F1p=*mwkBg$ zT^-xw_{;t_vic1iHeT-Fg_q{Fh{ouo+)dDc=g&bdz?O72uV-lT#%rHJJH{1YIjW_c zCRZ1GQ9b(gapovCZ?C2=m%$Pw>J>pjj1rD|9GKpUD`~_plU3lHlLfe+CX~Qv4v&QR zE<~q*VffzNjU`A@P#-i?87Y~hN(B3!WLdjsOoX~>g!lRa!g%1$0oGIi5c^z|E;pNG z3L!>NTPiQcs3GRt46XFm*wy{U!-}3CI>pf zwK8RB52oZ6Gihg{Q(s2`j6e}UU)z7efYPv6Ba5tyxzun69mbBb%NIy8%v3RZu)VfD zK2Xa#m#Np{K0!9wnLl?RuU$L&Iti4}U$~$c6`8H*;iw?@PcZ%6LCNB=( z_rqH=528!wr~IEnmHr#xtDfL`NL$c8j#U5GH3Rie9Bj(Kv%u2hKuA^hXG29^O64bJ z_ZnD$o`kNpfN+q9$OS5fS1rD*9FEcV4m!~7=Z^a*1B2M}-1)#35_py%-(yCet69qT zY8|Ml*_u^+yUC;53~vg=wQXX=;EIaJMrY-grp%ws1kkO8fc^q(z=F7BiK{@|h4w_- z7~TlOF{;0?Z(zM*K}ddCxL9``uy0Md&1PhC{?lM8>29w0-$$FtsegO)dDMN7$2Q8J zqejT;8NH4B-aBWthCG8#&PecsBbNa`nIb)M@hYZ#lW+Tt)(PN5QBJ!T<$*zU0dO9t=Ra55IUjMZdHTsK zmgYapOHWD0@inQor`<_0tr5{r{pp8(q{5*!AWXCHIIKPO3!3`!!?UwzqQ`RM*)Ou8 zq+pwZNMzA)N#z9QTP%b!HV6y0M$~4v#=}M!!Z5zIkq+Y?eDQ<7;jMVwEXd~)B*5gB zL;QzP30e7MWH~JQjsiw(Z4VL&x|G@l1M3bFo;4N?KB4ioqy)9t3EvM}`pjM7y02CLBDvg%BAgzt zOZr-`BtaTGod7e7%uEwPeD;u6r$-L{u?dmw( z-;koOn}~8(-c&;cUj}nxdqp&M7A%gw=sLG~IjhVT$H3L)g?_>KN$6Ddr1N9pB$a|q zD8L`t=Vq9~8%D^wchQz`-luA1W25TDQ?RkAsp<1be9hxcGJ1&$J+yJTUq#rE)Tzc^ z4!_w3b0dOIhSuUe>^>hOo^(p+{avB7u^p?9u|?C_!{U{1)hr*l!owY11yiJ~DH%My ztT*s36mouO?lQ&6#pRfepYfC-S#w?dpGex5M_~omuYc}mZ?6mT^8lq9`8lYczKa|Dnd~CqsYlc#^5i zaqGkjF1{&&Sz}{Q+)JibXY5keNg(sf_&a5Ry-m$DjzT>4#pI%dN0zZi-GQrXZ&xV5+7FQHTN+U4n#{2k#KvnHI@Sv?1oiuS@pmn7*hBkSeCJ zGVDmkZB<~t`2*g=x4-lpi$r-3%?gUUj;|$;=8zVhHLFmAwDGQ6z$n5^8XGF4f+5Y? z7<(*wHgb2uypAISwCD6T1I)=Wf(DBtvmeEfS<-9vqq|aaUnD>gKg1DkroFNpw-$7% ze}YX%lhd4Rj|369eAH81NDUV{Ni(5O!*Wq?U@al|Qw@nB%#B}Zss%OBo7;&2if1Y5 zdTOyn$Ua;iMG}=Xu%VAR2M3q@3IfVD_p6=*ku=iDFl5)B5%Q3rPk+Fe5;k7PR7dhd zoS0;@V|7@w0rwv1U3f8s(U0T9v4ODiRv_%_;$Q0SXNb&4GlPi1NO*vglq-p5T-O)! z{y|Uk5!u#^S&&@5a!VcGILW{A%QD;?zA^C*#MYY(t+Z(hF{8Cp2`i222T75~5~+H4 zL^Yhgp`{QC%rff%WoDUnJVe=-IbWvt;CPRaU)!_~pD;wF$}o7vKJ}26^@2StcJ@QP zZjh%EDaWCmB#*q&kg|442BDYaOvhKQSo|xXA)nUJjkhbI>2SI(G269kBi_5ucX`(| zgrx>#l4iI%#CG)>jPI#oPVw|Y9xDz9FK7b;3t!0yEkCreXCbM0T{mBcx9)xrjvA7GO zVwdgUBD{6d^;`|EH&R7FJ9yR`aH~8&3butGEv6T&!J7hfDy(irp88kY8wQ8jL!{&? zJnwg_2*cD5_S?Dt5>FdDc=}u|DpW2*O$)swv26H-HU7?E=3oX5MUEu9>W+jxk{_sn zr?;h*G!;Ia74FmGzW(Aqb7v*5!u#l|xNM0I) zZiPO`uy6#T%i=J@mkx<0*qHuKRy_C+1t{96d=Pp80Ix@zc1+j_C`WTR?mljvApKEN z{d)>j_j&%i3fS@DaX+R^8pQDgrWQcNz|QpX9WA37+@)zItPWX{@Sjk@Fqa;Z_M}i! z{XO(wpoLTN&v8Qsf7!{XC0EvDFlpf8qRdZJK8UFtb-23TSU!KMw9?}N$o`jUZswpH zC{}2$XH!h2k2<`B<`Vxk=yBM|oDPL&8&-QTJA6~ppP2nPCG8-IoZ=D?tHfa zXq+F%9QG8+3Q2GoToAmaaCmv#yjP4zgD}d0BoPOZ8ceD7d|!>=^e?T{c3u4-_zhP* zats+mwr$_PLE*o8jerGBHtBzT-U#9`}S5z-7!xIqI@em@#B&|fcJawc{<_ZyvW0kkI;M4EheORG+nekoU;f2aphoY#)Z7-pE1Y-a?4VFXLoyZ(-{LNO953kPwGSaBbMHB z5!f?|J>d!&jvfh*_0#r`ega7do~BArESvixwAu&OShn+?D^CL5Vg$l{hn50id&`^A zkez-WqU>B;dU#%?zyT+qR`qi@zROczhsXn zcJF#4kA-UYsOAjbw;l=3hFA+IDAVL|!9i=$!0Cdo6w!vItL+;cCfVL`2dWq+hwTG@ z+pCvLv}@_mIZL=>Sa;X%Ra7KdmXnQ;w%?q*K0c|jpCOnb<~q0cTg&ma>wK%@VOg}` zOUdktn}J+SX=!Md+X3GGhR5ZPBnGXZ6XmQ5LCsDa@@xXi7vB!h)jvzG?PblJ zLMx%VO!>wP^vCD9dmrAmSA(S6u|scZ9TIMdI;)rnz0ZTCfxfkf!f54JP9)}=X&#d4 z@Oo~;xy)sRnZ>H1q-LWb6w0QGUQTVpvFliG{0mPv+5dhWW(b)5q;qH2n`YlVh(IK4 zpkkG|4pr%8zvC6=BAg7~825qb)(%cy@y#~}EQUX|$Pmg~zJSvr*IPmUa)f9TmR`mE zi9mkGysAJh(FDiXWmN^DI=|kuJ#{Cq!1|ms)-*QBZ_{$B`?67o4w9+S=Vf<0`jM?1 z%(iKew}Jk5yDK%>8O%nem5N{8M~PBK>a?p_aoV(P4q2i1PAAJ6=QT^_oKNBG`s+p> zL^U?vxWrdS0#4`bYExM~uUGCdhpE!bl^N{L>@*7#3lBgmA9;PwU_p1aWBN9yi$Yah zRlzMuP8$!KCCAb2xnMTdO-NP`B0X9J+M}?6jtJtomPy%K)reKg^{WM(*Ky&&OR zFX}fLTu};e)6qr<%`Fy5Fj}~^_+oM%4GQZV(Qi+)c(<^l_@>AZ_3LePTd1Y-W{6O| z-?-4pa#fHDa2D~74Gyr)4pc$TWIHmNf^K1sf2zU;R@^}tZqY8m+v0)&NaiATGw%8G zjs;o&HuzDL0U5=P1vp2`4yMV|4fo*|FNEbKPV0s@d9ndT4VrTq#u7s);JWiE8mP>v zbYVxivT@(5oJg6IFI55Sq(hdn62UM=uz4MGs-itP=3uNpX1BA$Rr&TUOhiH8}R z*?b-5A@1_y!GXSTJ;fLi#2uOM*pXQg%Of^3lO_qe|f zx6iqlFj|bBETPy>%2>52Wio_#J%SQZ9MWevWfjxO2M9eWm z!x0}U|MOMpNYT9ofaJ)eZ{bT7&{h7JC4mpFKFW()CqxlaYQZ5&&4IRmE!+=EidxguQoBRBQ>-z8hrHt~|smws6^P}9V^~QORyx6p?Ri+MU zlF?CLL$(={{{CKQEG0v26zpEAH42_IqR=jsOBJu{s4M)iA+MUVGM>)H?)Cah{GqNK zL1CW>M1%V4R>)E<8&%M?^5*{J3V?gob+3^8SUAJ8|5ot3^rhlOCWMb#85yY=Ce0H;ud}hoFXI}j}=FORgWqN2dxAHwl^X8BVMY9 z1^xwgGZ`aG6RJ0SpAVo%8%7%vnLId=fi!L7>6kw}mr=*#w2lywe^|Z*`RhLN{;t1V zA%x}6`R>h|o5#(~4X&0)&Q{&c16)(IA>F_imA&-^XnsKWk2Nn=Y7K3)bVr@rTUqgV z6+KcS_AD9gTK+#W{9(XX6Hu_2YTKJd*drq)q{J=d^YWZthkf#uVN(5$j{#IKYxbs1 zzTET@x1(7cen+(Hs(~vVUtnv!L}=C=W~E!Eb|9ji+3OG9f5FDX&WhZnU8D}ljKvy# z^saySLh@2I=y55g6*oi)2TAr+zv&uFIhz=8LJYG_F$H?|On!Du=@E~nk`-n0Pfg_& z)V#_Q>0olIg|Ry+Z z5mMQ=IknCoL_s5*N~$rI>%Oomj$XNRrIqy3)NW0w(skfiXj~}l;JIZ(St2T8fNh$d zO_j0GeePiKI0J2jER9(vR}~I%3P^*^&24 z`7WP?QT9&2C~X#LN{zghn`qG14w?Qv9*(;_4a9hLKcs@*Tkt5AF+W7BP2U-uOPp9~ z5SS10KS?6|RY{S&FGC4Lq0T_=0$+5T%DLm`*YYAcM|TU# zeQysBq|rypj+AsTQMg2ZDPUV!uogg|GizKUCM3TIj!Ie6-xuxGL|T_8{RkH3TRKri z@fY$H#(9Qz;{{NnAPJEq*r$ksSuN!j7@D!s<5C^M2gT-Nq5}#l;m2EXR>+f?@`iSS z1$fKjxL=tYZe*2)2QuI$JK2p+=)Nrdd)5(I5jOO5J@hS)U116^ApFzkVdHDV?JhsY z8(aNFSr_VEAj~%|@EuAT@B*o@Vh}P%k%yH{GjAWYNh<2dkNQM3Ty|R&W{JJjN+uJV4*dAZ=Y1_-~Ys;BbT_nL~QWGVO z`;!5&0H3$bPcq7yH2w(wrdS3}sn8jC5zB*j2fhDaA`%;l86#HYKe;>xs4mABF38V? z$6{FE<2$Xw518nU91#8Yw7ID4N6{PRi8zvSt_e#aj$7EoDXR4!;y%#HReDCVh{0Kp zjLKG45odN2f{8b56XJeRLa)x!X^P92_*l46I_E_f&67$kMj@o@R?EhuYq3YY&{7x} zDM$XyMQ)j1?(D7IM}L0^7$JLAs|nPS+-V7U6d6U=1!GBr%ui(j7Ro~p1<|7uvS_Y0 zLyp@4$*3>yB0}j8dAmzdyX)iR10GIHAvo#kwngXV&2y>vW{Q7QNm^{$&lrEIHJlm0 z_Ec0=*Eui0hqR;VG}Cc7U9?^}NF?YpOA)NDiW4Pxy*{j8Bxyy}?g$2J$s2bR!Y&F5 zXYr$k>m3M|9Pz=z9CyxLhMIX@|7BA4Ccz%A!@AE|>At&=Vc#M~+Xy=2_-}Y^vBvCT$1|%#<01RJd1uzgxG2#HK7e}Vf?3EvS}`O=sQ)m08a=k

    Xij}iAU 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 import("./routes/project/HostGuidePage"), "HostGuidePage", ); +const OnboardingRoute = createLazyNamedRoute( + () => import("./routes/onboarding/OnboardingRoute"), + "OnboardingRoute", +); export const mainRouter = createBrowserRouter([ { @@ -163,6 +167,15 @@ export const mainRouter = createBrowserRouter([ ), path: "verify-email", }, + { + // Onboarding - one-time setup after first login + element: ( + + + + ), + path: "onboarding", + }, { // Host Guide - standalone page, protected but no header/layout element: ( diff --git a/echo/frontend/src/components/layout/Header.tsx b/echo/frontend/src/components/layout/Header.tsx index 2518cea2..6c44c5c3 100644 --- a/echo/frontend/src/components/layout/Header.tsx +++ b/echo/frontend/src/components/layout/Header.tsx @@ -17,6 +17,7 @@ import { IconMessageCircle, IconNotes, IconSettings, + IconSparkles, IconUsers, } from "@tabler/icons-react"; import { useEffect, useState } from "react"; @@ -26,6 +27,7 @@ import { useCurrentUser, useLogoutMutation, } from "@/components/auth/hooks"; +import { useV2Me } from "@/hooks/useV2Me"; import { I18nLink } from "@/components/common/i18nLink"; import { COMMUNITY_SLACK_URL, @@ -79,6 +81,8 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => { const logoutMutation = useLogoutMutation(); const { data: user } = useCurrentUser({ enabled: isAuthenticated }); + const { data: meV2 } = useV2Me({ enabled: isAuthenticated }); + const needsOnboarding = meV2?.onboarding_completed === false; const navigate = useI18nNavigate(); const { runTransition } = useTransitionCurtain(); const { setLogoUrl } = useWhitelabelLogo(); @@ -199,6 +203,15 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => { {/* Primary */} + {needsOnboarding && ( + } + onClick={() => navigate("/onboarding")} + color="blue" + > + Set up workspace + + )} } onClick={handleSettingsClick} diff --git a/echo/frontend/src/hooks/useV2Me.ts b/echo/frontend/src/hooks/useV2Me.ts new file mode 100644 index 00000000..fd34dcc4 --- /dev/null +++ b/echo/frontend/src/hooks/useV2Me.ts @@ -0,0 +1,30 @@ +import { useQuery } from "@tanstack/react-query"; +import { API_BASE_URL } from "@/config"; + +export interface V2MeData { + id: string | null; + directus_user_id: string; + email: string; + display_name: string; + avatar: string | null; + onboarding_completed: boolean; + orgs: Array<{ id: string; name: string; role: string }>; + has_pending_invites: boolean; +} + +async function fetchV2Me(): Promise { + const res = await fetch(`${API_BASE_URL}/v2/me`, { + credentials: "include", + }); + if (!res.ok) return null; + return res.json(); +} + +export const useV2Me = ({ enabled = true }: { enabled?: boolean } = {}) => + useQuery({ + queryKey: ["v2", "me"], + queryFn: fetchV2Me, + enabled, + staleTime: 60_000, + retry: false, + }); diff --git a/echo/frontend/src/routes/auth/Login.tsx b/echo/frontend/src/routes/auth/Login.tsx index 36904ae5..89ce4076 100644 --- a/echo/frontend/src/routes/auth/Login.tsx +++ b/echo/frontend/src/routes/auth/Login.tsx @@ -24,6 +24,7 @@ import { I18nLink } from "@/components/common/i18nLink"; import { toast } from "@/components/common/Toaster"; import { useTransitionCurtain } from "@/components/layout/TransitionCurtainProvider"; import { useCreateProjectMutation } from "@/components/project/hooks"; +import { API_BASE_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { testId } from "@/lib/testUtils"; @@ -110,21 +111,44 @@ export const LoginRoute = () => { const isNewUser = searchParams.get("new") === "true"; const next = searchParams.get("next"); + + // Start transition immediately — user sees smooth curtain right away const transitionPromise = runTransition({ - message: isNewUser ? t`Setting up your first project` : t`Welcome back`, + message: isNewUser ? t`Welcome to dembrane` : t`Welcome back`, }); + // Check onboarding in parallel with the transition animation. + // Small delay ensures the session cookie from login is available. + let needsOnboarding = false; + try { + await new Promise((r) => setTimeout(r, 300)); + const meResponse = await fetch(`${API_BASE_URL}/v2/me`, { + credentials: "include", + }); + if (meResponse.ok) { + const meData = await meResponse.json(); + needsOnboarding = meData.onboarding_completed === false; + } + } catch { + // Swallow — never block login for onboarding check + } + + await transitionPromise; + + if (needsOnboarding) { + navigate("/onboarding"); + return; + } + if (isNewUser) { toast(t`Setting up your first project`); const project = await createProjectMutation.mutateAsync({ name: t`New Project`, }); - await transitionPromise; navigate(`/projects/${project.id}`); return; } - await transitionPromise; if (!!next && next !== "/login") { navigate(next); } else { diff --git a/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx b/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx new file mode 100644 index 00000000..eea5b6e4 --- /dev/null +++ b/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx @@ -0,0 +1,451 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + ActionIcon, + Box, + Button, + Group, + Loader, + Stack, + Text, + TextInput, + Title, +} from "@mantine/core"; +import { useDocumentTitle } from "@mantine/hooks"; +import { IconPlus, IconX } from "@tabler/icons-react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; +import { useCurrentUser } from "@/components/auth/hooks"; +import { toast } from "@/components/common/Toaster"; +import { API_BASE_URL } from "@/config"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { useV2Me } from "@/hooks/useV2Me"; + +async function completeOnboarding(orgName: string) { + const response = await fetch(`${API_BASE_URL}/v2/onboarding/complete`, { + body: JSON.stringify({ org_name: orgName }), + credentials: "include", + headers: { "Content-Type": "application/json" }, + method: "POST", + }); + if (!response.ok) { + const data = await response.json().catch(() => ({})); + throw new Error(data.detail || "Something went wrong"); + } + return response.json(); +} + +async function sendInvite(workspaceId: string, email: string) { + const response = await fetch( + `${API_BASE_URL}/v2/workspaces/${workspaceId}/invite`, + { + body: JSON.stringify({ email, is_org_member: true, role: "member" }), + credentials: "include", + headers: { "Content-Type": "application/json" }, + method: "POST", + }, + ); + if (!response.ok) { + const data = await response.json().catch(() => ({})); + throw new Error(data.detail || "Failed to send invite"); + } + return response.json(); +} + +export const OnboardingRoute = () => { + const navigate = useI18nNavigate(); + const queryClient = useQueryClient(); + const user = useCurrentUser(); + const { data: meV2, isLoading: meLoading } = useV2Me(); + + const displayName = (user.data as Record)?.first_name || ""; + const defaultOrgName = displayName ? `${displayName}'s Team` : ""; + + const [orgName, setOrgName] = useState(defaultOrgName); + const [inviteEmails, setInviteEmails] = useState([""]); + const [step, setStep] = useState<"loading" | "org" | "invite">("loading"); + const [workspaceId, setWorkspaceId] = useState(null); + const [sendingInvites, setSendingInvites] = useState(false); + const [ready, setReady] = useState(false); + + useDocumentTitle(t`Set up your workspace | dembrane`); + + useEffect(() => { + if (meLoading) return; + + if (meV2?.onboarding_completed === true) { + navigate("/projects"); + return; + } + + const timer = setTimeout(() => { + setStep("org"); + requestAnimationFrame(() => setReady(true)); + }, 1200); + + return () => clearTimeout(timer); + }, [meV2, meLoading, navigate]); + + const onboardingMutation = useMutation({ + mutationFn: () => completeOnboarding(orgName.trim() || defaultOrgName), + onError: (error: Error) => { + toast.error(error.message || t`Something went wrong`); + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["v2", "me"] }); + setWorkspaceId(data.workspace_id); + setReady(false); + setTimeout(() => { + setStep("invite"); + requestAnimationFrame(() => setReady(true)); + }, 150); + }, + }); + + const handleSendInvites = async () => { + if (!workspaceId) return; + + const validEmails = inviteEmails.filter((e) => e.trim() && e.includes("@")); + + if (validEmails.length === 0) { + navigate("/projects"); + return; + } + + setSendingInvites(true); + let sent = 0; + for (const email of validEmails) { + try { + await sendInvite(workspaceId, email.trim()); + sent++; + } catch (err) { + const message = err instanceof Error ? err.message : "Failed"; + toast.error(`${email}: ${message}`); + } + } + setSendingInvites(false); + + if (sent > 0) { + toast.success(sent === 1 ? t`Invite sent` : t`${sent} invites sent`); + } + navigate("/projects"); + }; + + const addEmailField = () => setInviteEmails([...inviteEmails, ""]); + + const removeEmailField = (index: number) => + setInviteEmails(inviteEmails.filter((_, i) => i !== index)); + + const updateEmail = (index: number, value: string) => { + const updated = [...inviteEmails]; + updated[index] = value; + setInviteEmails(updated); + }; + + // ── Loading ── + if (step === "loading") { + return ( +

    + ); + } + + // ── Invite step ── + if (step === "invite") { + return ( +
    + +
    + + + + <Trans>Invite your team</Trans> + + + + Colleagues you invite can explore conversations, share + insights, and build reports with you. + + + + + + {inviteEmails.map((email, index) => ( + + updateEmail(index, e.currentTarget.value)} + /> + {inviteEmails.length > 1 && ( + removeEmailField(index)} + > + + + )} + + ))} + + + + + + + + + + +
    +
    + ); + } + + // ── Org name step ── + return ( +
    + + + {/* Top: illustration as atmospheric hero */} +
    + +
    + + {/* Bottom: form, centered */} +
    +
    + + + + {displayName ? ( + <Trans> + {displayName}, your projects just got a new home + </Trans> + ) : ( + <Trans>Your projects just got a new home</Trans> + )} + + + + Everything is right where you left it. Now, with teams and + workspaces, you can also invite your colleagues, share reports + across projects, and organize work into dedicated spaces. + + + + +
    { + e.preventDefault(); + onboardingMutation.mutate(); + }} + > + + setOrgName(e.currentTarget.value)} + /> + + + + + + + + + You'll find all your projects waiting for you. + + + +
    +
    +
    +
    +
    + ); +}; + +function GradientBlurs() { + return ( + <> +
    +
    +
    + + ); +} From 208c0d2add8fcafc877a26668a95e3f8035d1974 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 16:06:18 +0000 Subject: [PATCH 029/208] feat: workspace usage stats, team rollups, and create workspace endpoint GET /v2/workspaces enhanced: - Per-workspace: audio_hours, conversation_count - Team rollups: total projects, unique members, total audio hours, total conversations, workspace count across all team workspaces POST /v2/workspaces: - Creates workspace in user's team - Auto-adds creator as owner + team admins as inherited members - Requires team admin/owner role --- echo/server/dembrane/api/v2/schemas.py | 38 +++ echo/server/dembrane/api/v2/workspaces.py | 359 +++++++++++++++------- 2 files changed, 288 insertions(+), 109 deletions(-) diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py index 0d44af77..79e28c7e 100644 --- a/echo/server/dembrane/api/v2/schemas.py +++ b/echo/server/dembrane/api/v2/schemas.py @@ -54,6 +54,13 @@ class MemberPreview(BaseModel): avatar: Optional[str] = None +class WorkspaceUsage(BaseModel): + """Usage stats for a workspace.""" + + audio_hours: float = 0.0 + conversation_count: int = 0 + + class WorkspaceSummary(BaseModel): id: str name: str @@ -66,10 +73,41 @@ class WorkspaceSummary(BaseModel): member_count: int is_external: bool members_preview: list[MemberPreview] = [] + usage: WorkspaceUsage = WorkspaceUsage() + + +class TeamRollup(BaseModel): + """Aggregated stats across all workspaces in a team.""" + + id: str + name: str + role: str + total_projects: int = 0 + total_members: int = 0 # unique across workspaces + total_audio_hours: float = 0.0 + total_conversations: int = 0 + workspace_count: int = 0 class WorkspaceListResponse(BaseModel): workspaces: list[WorkspaceSummary] + teams: list[TeamRollup] = [] + + +# ── /v2/workspaces CRUD ── + + +class CreateWorkspaceRequest(BaseModel): + name: str + tier: str = "pioneer" + org_id: Optional[str] = None # defaults to user's primary org + + +class CreateWorkspaceResponse(BaseModel): + id: str + name: str + org_id: str + tier: str # ── /v2/workspaces/:id/invite ── diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index a71f03f3..482c17f1 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -1,34 +1,140 @@ -"""V2 workspace endpoints — list accessible workspaces.""" +"""V2 workspace endpoints — list, create, manage workspaces.""" from logging import getLogger +from typing import Optional -from fastapi import APIRouter +from fastapi import APIRouter, HTTPException -from dembrane.app_user import resolve_app_user +from dembrane.utils import generate_uuid +from dembrane.app_user import resolve_app_user, get_app_user_or_raise from dembrane.directus_async import async_directus -from dembrane.api.v2.schemas import MemberPreview, WorkspaceSummary, WorkspaceListResponse +from dembrane.api.v2.schemas import ( + CreateWorkspaceRequest, + CreateWorkspaceResponse, + MemberPreview, + TeamRollup, + WorkspaceListResponse, + WorkspaceSummary, + WorkspaceUsage, +) from dembrane.api.dependency_auth import DependencyDirectusSession router = APIRouter() logger = getLogger("api.v2.workspaces") +async def _get_workspace_usage(ws_id: str) -> WorkspaceUsage: + """Get audio hours + conversation count for a workspace.""" + # Get all projects in this workspace + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_eq": ws_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": -1, + } + }, + ) + if not isinstance(projects, list) or len(projects) == 0: + return WorkspaceUsage() + + project_ids = [p["id"] for p in projects] + + # Get all conversations across those projects + conversations = await async_directus.get_items( + "conversation", + { + "query": { + "filter": { + "project_id": {"_in": project_ids}, + "deleted_at": {"_null": True}, + }, + "fields": ["duration"], + "limit": -1, + } + }, + ) + if not isinstance(conversations, list): + return WorkspaceUsage() + + total_seconds = sum(c.get("duration") or 0 for c in conversations) + + return WorkspaceUsage( + audio_hours=round(total_seconds / 3600, 1), + conversation_count=len(conversations), + ) + + +async def _get_member_previews(ws_id: str) -> list[MemberPreview]: + """Get first 4 member avatars for a workspace.""" + memberships = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": ws_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["user_id"], + "limit": 4, + } + }, + ) + if not isinstance(memberships, list) or len(memberships) == 0: + return [] + + user_ids = [m["user_id"] for m in memberships if m.get("user_id")] + if not user_ids: + return [] + + users = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"id": {"_in": user_ids}}, + "fields": ["id", "display_name", "directus_user_id"], + "limit": 4, + } + }, + ) + if not isinstance(users, list): + return [] + + # Fetch avatars from directus_users + du_ids = [u["directus_user_id"] for u in users if u.get("directus_user_id")] + avatar_map: dict[str, Optional[str]] = {} + if du_ids: + profiles = await async_directus.get_users( + {"query": {"filter": {"id": {"_in": du_ids}}, "fields": ["id", "avatar"], "limit": 4}} + ) + if isinstance(profiles, list): + avatar_map = {u["id"]: u.get("avatar") for u in profiles} + + return [ + MemberPreview( + display_name=u.get("display_name", ""), + avatar=avatar_map.get(u.get("directus_user_id", "")), + ) + for u in users + ] + + @router.get("", response_model=WorkspaceListResponse) async def list_workspaces( auth: DependencyDirectusSession, ) -> WorkspaceListResponse: - """List all workspaces accessible to the current user. - - Used by the workspace selector. Returns workspace details with - role, project count, and member count. - """ + """List all accessible workspaces with usage stats and team rollups.""" app_user = await resolve_app_user(auth.user_id) if not app_user: - return WorkspaceListResponse(workspaces=[]) + return WorkspaceListResponse(workspaces=[], teams=[]) app_user_id = app_user["id"] - # Get all active workspace memberships for this user + # Get all active workspace memberships memberships = await async_directus.get_items( "workspace_membership", { @@ -37,23 +143,18 @@ async def list_workspaces( "user_id": {"_eq": app_user_id}, "deleted_at": {"_null": True}, }, - "fields": [ - "workspace_id", - "role", - "source", - "is_external", - ], + "fields": ["workspace_id", "role", "source", "is_external"], "limit": -1, } }, ) if not isinstance(memberships, list) or len(memberships) == 0: - return WorkspaceListResponse(workspaces=[]) + return WorkspaceListResponse(workspaces=[], teams=[]) - # Fetch workspace details for all memberships workspace_ids = [m["workspace_id"] for m in memberships if m.get("workspace_id")] + # Fetch workspace details workspaces = await async_directus.get_items( "workspace", { @@ -62,22 +163,14 @@ async def list_workspaces( "id": {"_in": workspace_ids}, "deleted_at": {"_null": True}, }, - "fields": [ - "id", - "name", - "org_id", - "is_default", - "tier", - ], + "fields": ["id", "name", "org_id", "is_default", "tier"], "limit": -1, } }, ) - if not isinstance(workspaces, list): workspaces = [] - # Build a map of workspace_id -> workspace ws_map = {ws["id"]: ws for ws in workspaces} # Fetch org names @@ -86,104 +179,39 @@ async def list_workspaces( if org_ids: orgs = await async_directus.get_items( "org", - { - "query": { - "filter": {"id": {"_in": org_ids}}, - "fields": ["id", "name"], - "limit": -1, - } - }, + {"query": {"filter": {"id": {"_in": org_ids}}, "fields": ["id", "name"], "limit": -1}}, ) if isinstance(orgs, list): org_map = {o["id"]: o.get("name", "") for o in orgs} - # Count projects and members per workspace + # Build workspace summaries with usage results: list[WorkspaceSummary] = [] - for membership in memberships: ws_id = membership.get("workspace_id") ws = ws_map.get(ws_id) if not ws: continue - # Count projects in this workspace - project_count_result = await async_directus.get_items( + # Project count + proj_count_result = await async_directus.get_items( "project", - { - "query": { - "filter": { - "workspace_id": {"_eq": ws_id}, - "deleted_at": {"_null": True}, - }, - "aggregate": {"count": ["id"]}, - } - }, + {"query": {"filter": {"workspace_id": {"_eq": ws_id}, "deleted_at": {"_null": True}}, "aggregate": {"count": ["id"]}}}, ) project_count = 0 - if isinstance(project_count_result, list) and len(project_count_result) > 0: - project_count = int(project_count_result[0].get("count", {}).get("id", 0)) + if isinstance(proj_count_result, list) and len(proj_count_result) > 0: + project_count = int(proj_count_result[0].get("count", {}).get("id", 0)) - # Count members in this workspace - member_count_result = await async_directus.get_items( + # Member count + mem_count_result = await async_directus.get_items( "workspace_membership", - { - "query": { - "filter": { - "workspace_id": {"_eq": ws_id}, - "deleted_at": {"_null": True}, - }, - "aggregate": {"count": ["id"]}, - } - }, + {"query": {"filter": {"workspace_id": {"_eq": ws_id}, "deleted_at": {"_null": True}}, "aggregate": {"count": ["id"]}}}, ) member_count = 0 - if isinstance(member_count_result, list) and len(member_count_result) > 0: - member_count = int(member_count_result[0].get("count", {}).get("id", 0)) + if isinstance(mem_count_result, list) and len(mem_count_result) > 0: + member_count = int(mem_count_result[0].get("count", {}).get("id", 0)) - # Fetch first 4 members for avatar preview bubbles - members_preview: list[MemberPreview] = [] - preview_memberships = await async_directus.get_items( - "workspace_membership", - { - "query": { - "filter": { - "workspace_id": {"_eq": ws_id}, - "deleted_at": {"_null": True}, - }, - "fields": ["user_id"], - "limit": 4, - } - }, - ) - if isinstance(preview_memberships, list) and len(preview_memberships) > 0: - preview_user_ids = [m["user_id"] for m in preview_memberships if m.get("user_id")] - if preview_user_ids: - preview_users = await async_directus.get_items( - "app_user", - { - "query": { - "filter": {"id": {"_in": preview_user_ids}}, - "fields": ["id", "display_name", "directus_user_id"], - "limit": 4, - } - }, - ) - if isinstance(preview_users, list): - # Fetch avatars from directus_users - du_ids = [u["directus_user_id"] for u in preview_users if u.get("directus_user_id")] - avatar_map: dict[str, str | None] = {} - if du_ids: - du_profiles = await async_directus.get_users( - {"query": {"filter": {"id": {"_in": du_ids}}, "fields": ["id", "avatar"], "limit": 4}} - ) - if isinstance(du_profiles, list): - avatar_map = {u["id"]: u.get("avatar") for u in du_profiles} - - for u in preview_users: - members_preview.append(MemberPreview( - display_name=u.get("display_name", ""), - avatar=avatar_map.get(u.get("directus_user_id", "")) - )) + usage = await _get_workspace_usage(ws_id) + previews = await _get_member_previews(ws_id) results.append(WorkspaceSummary( id=ws["id"], @@ -196,7 +224,120 @@ async def list_workspaces( project_count=project_count, member_count=member_count, is_external=membership.get("is_external", False), - members_preview=members_preview, + members_preview=previews, + usage=usage, )) - return WorkspaceListResponse(workspaces=results) + # Build team rollups + teams: list[TeamRollup] = [] + org_membership_data = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": {"user_id": {"_eq": app_user_id}, "deleted_at": {"_null": True}}, + "fields": ["org_id", "role"], + "limit": -1, + } + }, + ) + if isinstance(org_membership_data, list): + for om in org_membership_data: + oid = om.get("org_id") + if not oid: + continue + team_workspaces = [w for w in results if w.org_id == oid] + # Unique members across all workspaces in this team + all_member_ids: set[str] = set() + for tw in team_workspaces: + mems = await async_directus.get_items( + "workspace_membership", + {"query": {"filter": {"workspace_id": {"_eq": tw.id}, "deleted_at": {"_null": True}}, "fields": ["user_id"], "limit": -1}}, + ) + if isinstance(mems, list): + all_member_ids.update(m["user_id"] for m in mems if m.get("user_id")) + + teams.append(TeamRollup( + id=oid, + name=org_map.get(oid, ""), + role=om.get("role", ""), + total_projects=sum(w.project_count for w in team_workspaces), + total_members=len(all_member_ids), + total_audio_hours=round(sum(w.usage.audio_hours for w in team_workspaces), 1), + total_conversations=sum(w.usage.conversation_count for w in team_workspaces), + workspace_count=len(team_workspaces), + )) + + return WorkspaceListResponse(workspaces=results, teams=teams) + + +@router.post("", response_model=CreateWorkspaceResponse) +async def create_workspace( + body: CreateWorkspaceRequest, + auth: DependencyDirectusSession, +) -> CreateWorkspaceResponse: + """Create a new workspace in the user's team.""" + app_user = await get_app_user_or_raise(auth.user_id) + app_user_id = app_user["id"] + + # Determine which org to create in + org_id = body.org_id + if not org_id: + # Use user's primary org (where they're owner) + orgs = await async_directus.get_items( + "org_membership", + {"query": {"filter": {"user_id": {"_eq": app_user_id}, "role": {"_in": ["owner", "admin"]}, "deleted_at": {"_null": True}}, "fields": ["org_id"], "limit": 1}}, + ) + if not isinstance(orgs, list) or len(orgs) == 0: + raise HTTPException(status_code=403, detail="No team found. Complete onboarding first.") + org_id = orgs[0]["org_id"] + + # Verify user has admin/owner on this org + org_access = await async_directus.get_items( + "org_membership", + {"query": {"filter": {"org_id": {"_eq": org_id}, "user_id": {"_eq": app_user_id}, "role": {"_in": ["owner", "admin"]}, "deleted_at": {"_null": True}}, "limit": 1}}, + ) + if not isinstance(org_access, list) or len(org_access) == 0: + raise HTTPException(status_code=403, detail="Must be team admin or owner to create workspaces") + + ws_id = generate_uuid() + await async_directus.create_item("workspace", { + "id": ws_id, + "org_id": org_id, + "name": body.name.strip(), + "tier": body.tier, + "is_default": False, + "created_by": app_user_id, + }) + + # Add creator as owner + await async_directus.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": ws_id, + "user_id": app_user_id, + "role": "owner", + "source": "direct", + }) + + # Add all org admins/owners as inherited members + org_admins = await async_directus.get_items( + "org_membership", + {"query": {"filter": {"org_id": {"_eq": org_id}, "role": {"_in": ["owner", "admin"]}, "user_id": {"_neq": app_user_id}, "deleted_at": {"_null": True}}, "fields": ["user_id"], "limit": -1}}, + ) + if isinstance(org_admins, list): + for admin in org_admins: + await async_directus.create_item("workspace_membership", { + "id": generate_uuid(), + "workspace_id": ws_id, + "user_id": admin["user_id"], + "role": "admin", + "source": "inherited", + }) + + logger.info(f"Created workspace {ws_id} '{body.name}' in org {org_id} by {app_user_id}") + + return CreateWorkspaceResponse( + id=ws_id, + name=body.name.strip(), + org_id=org_id, + tier=body.tier, + ) From 91fa1fe03c7406ccab4e3afd69f711afa75b3777 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 16:11:41 +0000 Subject: [PATCH 030/208] feat: workspace selector, create workspace, topbar, and security fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend: - Workspace selector page (/workspaces): cards grouped by team with usage stats (audio hours, conversations, projects), avatar bubbles, tier badge, search (when >3 workspaces), external section - Create workspace page (/workspaces/new): name + tier selector - WorkspaceContext provider: localStorage-persisted current workspace - Header: shows workspace name next to logo (clickable → /workspaces) - Post-login router: multi-workspace or team admin → /workspaces, solo user → /projects Security fixes from code review: - Project move: verify ownership for orphaned projects (IDOR fix) - Tier validation: reject invalid tier values on workspace creation - Name length limits on workspace + onboarding requests (1-100 chars) --- echo/frontend/src/App.tsx | 14 +- echo/frontend/src/Router.tsx | 26 ++ .../frontend/src/components/layout/Header.tsx | 10 + echo/frontend/src/hooks/useWorkspace.ts | 52 +++ echo/frontend/src/routes/auth/Login.tsx | 30 +- .../workspaces/CreateWorkspaceRoute.tsx | 127 +++++++ .../workspaces/WorkspaceSelectorRoute.tsx | 318 ++++++++++++++++++ echo/server/dembrane/api/v2/projects.py | 5 + echo/server/dembrane/api/v2/schemas.py | 16 +- 9 files changed, 589 insertions(+), 9 deletions(-) create mode 100644 echo/frontend/src/hooks/useWorkspace.ts create mode 100644 echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx create mode 100644 echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx diff --git a/echo/frontend/src/App.tsx b/echo/frontend/src/App.tsx index 0fb5e7ea..a170b09e 100644 --- a/echo/frontend/src/App.tsx +++ b/echo/frontend/src/App.tsx @@ -13,6 +13,10 @@ import { I18nProvider } from "./components/layout/I18nProvider"; import { USE_PARTICIPANT_ROUTER } from "./config"; import { AppPreferencesProvider } from "./hooks/useAppPreferences"; import { WhitelabelLogoProvider } from "./hooks/useWhitelabelLogo"; +import { + WorkspaceContext, + useWorkspaceProvider, +} from "./hooks/useWorkspace"; import { analytics } from "./lib/analytics"; import { mainRouter, participantRouter } from "./Router"; import { theme } from "./theme"; @@ -79,6 +83,8 @@ export const App = () => { }; }, []); + const workspaceValue = useWorkspaceProvider(); + return ( {/* */} @@ -86,9 +92,11 @@ export const App = () => { - - - + + + + + diff --git a/echo/frontend/src/Router.tsx b/echo/frontend/src/Router.tsx index c3c1c90f..b657b77a 100644 --- a/echo/frontend/src/Router.tsx +++ b/echo/frontend/src/Router.tsx @@ -111,6 +111,14 @@ const OnboardingRoute = createLazyNamedRoute( () => import("./routes/onboarding/OnboardingRoute"), "OnboardingRoute", ); +const WorkspaceSelectorRoute = createLazyNamedRoute( + () => import("./routes/workspaces/WorkspaceSelectorRoute"), + "WorkspaceSelectorRoute", +); +const CreateWorkspaceRoute = createLazyNamedRoute( + () => import("./routes/workspaces/CreateWorkspaceRoute"), + "CreateWorkspaceRoute", +); export const mainRouter = createBrowserRouter([ { @@ -176,6 +184,24 @@ export const mainRouter = createBrowserRouter([ ), path: "onboarding", }, + { + // Workspace selector + element: ( + + + + ), + path: "workspaces", + }, + { + // Create workspace + element: ( + + + + ), + path: "workspaces/new", + }, { // Host Guide - standalone page, protected but no header/layout element: ( diff --git a/echo/frontend/src/components/layout/Header.tsx b/echo/frontend/src/components/layout/Header.tsx index 6c44c5c3..357eb058 100644 --- a/echo/frontend/src/components/layout/Header.tsx +++ b/echo/frontend/src/components/layout/Header.tsx @@ -28,6 +28,7 @@ import { useLogoutMutation, } from "@/components/auth/hooks"; import { useV2Me } from "@/hooks/useV2Me"; +import { useWorkspace } from "@/hooks/useWorkspace"; import { I18nLink } from "@/components/common/i18nLink"; import { COMMUNITY_SLACK_URL, @@ -83,6 +84,7 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => { const { data: user } = useCurrentUser({ enabled: isAuthenticated }); const { data: meV2 } = useV2Me({ enabled: isAuthenticated }); const needsOnboarding = meV2?.onboarding_completed === false; + const { workspaceName } = useWorkspace(); const navigate = useI18nNavigate(); const { runTransition } = useTransitionCurtain(); const { setLogoUrl } = useWhitelabelLogo(); @@ -152,6 +154,14 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => { + {workspaceName && isAuthenticated && ( + navigate("/workspaces")}> + / + + {workspaceName} + + + )} {!loading && isAuthenticated && user ? ( diff --git a/echo/frontend/src/hooks/useWorkspace.ts b/echo/frontend/src/hooks/useWorkspace.ts new file mode 100644 index 00000000..40877c22 --- /dev/null +++ b/echo/frontend/src/hooks/useWorkspace.ts @@ -0,0 +1,52 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from "react"; +import { API_BASE_URL } from "@/config"; + +export interface WorkspaceContextValue { + workspaceId: string | null; + workspaceName: string | null; + setWorkspace: (id: string, name: string) => void; + clearWorkspace: () => void; +} + +export const WorkspaceContext = createContext({ + workspaceId: null, + workspaceName: null, + setWorkspace: () => {}, + clearWorkspace: () => {}, +}); + +export const useWorkspace = () => useContext(WorkspaceContext); + +const STORAGE_KEY = "dembrane_workspace_id"; +const STORAGE_NAME_KEY = "dembrane_workspace_name"; + +export function useWorkspaceProvider(): WorkspaceContextValue { + const [workspaceId, setId] = useState( + () => localStorage.getItem(STORAGE_KEY), + ); + const [workspaceName, setName] = useState( + () => localStorage.getItem(STORAGE_NAME_KEY), + ); + + const setWorkspace = useCallback((id: string, name: string) => { + localStorage.setItem(STORAGE_KEY, id); + localStorage.setItem(STORAGE_NAME_KEY, name); + setId(id); + setName(name); + }, []); + + const clearWorkspace = useCallback(() => { + localStorage.removeItem(STORAGE_KEY); + localStorage.removeItem(STORAGE_NAME_KEY); + setId(null); + setName(null); + }, []); + + return { workspaceId, workspaceName, setWorkspace, clearWorkspace }; +} diff --git a/echo/frontend/src/routes/auth/Login.tsx b/echo/frontend/src/routes/auth/Login.tsx index 89ce4076..b6f117e5 100644 --- a/echo/frontend/src/routes/auth/Login.tsx +++ b/echo/frontend/src/routes/auth/Login.tsx @@ -117,9 +117,11 @@ export const LoginRoute = () => { message: isNewUser ? t`Welcome to dembrane` : t`Welcome back`, }); - // Check onboarding in parallel with the transition animation. + // Check onboarding + workspace count in parallel with transition. // Small delay ensures the session cookie from login is available. let needsOnboarding = false; + let workspaceCount = 0; + let isTeamAdmin = false; try { await new Promise((r) => setTimeout(r, 300)); const meResponse = await fetch(`${API_BASE_URL}/v2/me`, { @@ -128,6 +130,20 @@ export const LoginRoute = () => { if (meResponse.ok) { const meData = await meResponse.json(); needsOnboarding = meData.onboarding_completed === false; + isTeamAdmin = (meData.orgs ?? []).some( + (o: { role: string }) => o.role === "owner" || o.role === "admin", + ); + } + + // If onboarded, check workspace count for routing + if (!needsOnboarding) { + const wsResponse = await fetch(`${API_BASE_URL}/v2/workspaces`, { + credentials: "include", + }); + if (wsResponse.ok) { + const wsData = await wsResponse.json(); + workspaceCount = wsData.workspaces?.length ?? 0; + } } } catch { // Swallow — never block login for onboarding check @@ -140,6 +156,12 @@ export const LoginRoute = () => { return; } + // Deep link takes priority + if (!!next && next !== "/login") { + navigate(next); + return; + } + if (isNewUser) { toast(t`Setting up your first project`); const project = await createProjectMutation.mutateAsync({ @@ -149,8 +171,10 @@ export const LoginRoute = () => { return; } - if (!!next && next !== "/login") { - navigate(next); + // Multi-workspace or team admin → workspace selector + // Solo user → straight to projects + if (workspaceCount > 1 || isTeamAdmin) { + navigate("/workspaces"); } else { navigate("/projects"); } diff --git a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx new file mode 100644 index 00000000..263e4271 --- /dev/null +++ b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx @@ -0,0 +1,127 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + Button, + Container, + Group, + Select, + Stack, + Text, + TextInput, + Title, +} from "@mantine/core"; +import { useDocumentTitle } from "@mantine/hooks"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { toast } from "@/components/common/Toaster"; +import { API_BASE_URL } from "@/config"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { useWorkspace } from "@/hooks/useWorkspace"; + +async function createWorkspace(name: string, tier: string) { + const res = await fetch(`${API_BASE_URL}/v2/workspaces`, { + body: JSON.stringify({ name, tier }), + credentials: "include", + headers: { "Content-Type": "application/json" }, + method: "POST", + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.detail || "Failed to create workspace"); + } + return res.json(); +} + +export const CreateWorkspaceRoute = () => { + const navigate = useI18nNavigate(); + const queryClient = useQueryClient(); + const { setWorkspace } = useWorkspace(); + const [name, setName] = useState(""); + const [tier, setTier] = useState("pioneer"); + + useDocumentTitle(t`New workspace | dembrane`); + + const mutation = useMutation({ + mutationFn: () => createWorkspace(name.trim(), tier), + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["v2", "workspaces"] }); + setWorkspace(data.id, data.name); + toast.success(t`Workspace created`); + navigate("/projects"); + }, + onError: (error: Error) => { + toast.error(error.message); + }, + }); + + return ( +
    + + + + + <Trans>New workspace</Trans> + + + + Workspaces hold projects for a specific client or purpose. + Team admins automatically get access. + + + + +
    { + e.preventDefault(); + if (!name.trim()) return; + mutation.mutate(); + }} + > + + setName(e.currentTarget.value)} + /> + + v && setTier(v)} diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx index efca3c04..258ce3d9 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx @@ -147,7 +147,7 @@ function WorkspaceCard({ members={workspace.members_preview} count={workspace.member_count} /> - + {workspace.role} diff --git a/echo/server/dembrane/api/v2/invites.py b/echo/server/dembrane/api/v2/invites.py index 9165e858..1c946564 100644 --- a/echo/server/dembrane/api/v2/invites.py +++ b/echo/server/dembrane/api/v2/invites.py @@ -7,6 +7,7 @@ from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException +from dembrane.api.rate_limit import create_user_rate_limiter from dembrane.utils import generate_uuid from dembrane.email import send_email @@ -21,6 +22,7 @@ logger = getLogger("api.v2.invites") settings = get_settings() +_invite_rate_limiter = create_user_rate_limiter(name="workspace_invite", capacity=20, window_seconds=3600) @router.post("/{workspace_id}/invite", response_model=WorkspaceInviteResponse) @@ -41,6 +43,8 @@ async def invite_to_workspace( """ ctx.require_policy("member:invite") + await _invite_rate_limiter.check(ctx.app_user_id) + email = body.email.strip().lower() role = body.role diff --git a/echo/server/dembrane/main.py b/echo/server/dembrane/main.py index 606f252e..41340bb8 100644 --- a/echo/server/dembrane/main.py +++ b/echo/server/dembrane/main.py @@ -50,6 +50,11 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]: yield # shutdown logger.info("shutting down server") + try: + from dembrane.directus_async import async_directus + await async_directus.close() + except Exception: + logger.exception("Failed to close async Directus client") docs_url = None From dd93052ba5de1c37a120db0550b2eb9cc1bd931e Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 18:02:04 +0000 Subject: [PATCH 038/208] feat: monthly usage breakdown on workspace cards and team rollups _get_workspace_usage now returns both all-time and current-month stats: - audio_hours_this_month, conversations_this_month per workspace - total_audio_hours_this_month, total_conversations_this_month per team Uses in-Python date filtering on already-fetched conversations (no extra Directus query). Month boundary is first day of current UTC month. Pattern learned from the existing usage-tracker Streamlit tool. --- echo/server/dembrane/api/v2/schemas.py | 8 +++++++- echo/server/dembrane/api/v2/workspaces.py | 23 ++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py index 4cbc3cd7..cc826345 100644 --- a/echo/server/dembrane/api/v2/schemas.py +++ b/echo/server/dembrane/api/v2/schemas.py @@ -55,10 +55,13 @@ class MemberPreview(BaseModel): class WorkspaceUsage(BaseModel): - """Usage stats for a workspace.""" + """Usage stats for a workspace (all-time + current month).""" audio_hours: float = 0.0 conversation_count: int = 0 + # Current calendar month + audio_hours_this_month: float = 0.0 + conversations_this_month: int = 0 class WorkspaceSummary(BaseModel): @@ -87,6 +90,9 @@ class TeamRollup(BaseModel): total_audio_hours: float = 0.0 total_conversations: int = 0 workspace_count: int = 0 + # Current calendar month + total_audio_hours_this_month: float = 0.0 + total_conversations_this_month: int = 0 class WorkspaceListResponse(BaseModel): diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 482c17f1..8f72237e 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -1,5 +1,6 @@ """V2 workspace endpoints — list, create, manage workspaces.""" +from datetime import datetime, timezone from logging import getLogger from typing import Optional @@ -24,7 +25,7 @@ async def _get_workspace_usage(ws_id: str) -> WorkspaceUsage: - """Get audio hours + conversation count for a workspace.""" + """Get audio hours + conversation count for a workspace (all-time and current month).""" # Get all projects in this workspace projects = await async_directus.get_items( "project", @@ -44,7 +45,7 @@ async def _get_workspace_usage(ws_id: str) -> WorkspaceUsage: project_ids = [p["id"] for p in projects] - # Get all conversations across those projects + # Get all conversations across those projects (include created_at for monthly filtering) conversations = await async_directus.get_items( "conversation", { @@ -53,7 +54,7 @@ async def _get_workspace_usage(ws_id: str) -> WorkspaceUsage: "project_id": {"_in": project_ids}, "deleted_at": {"_null": True}, }, - "fields": ["duration"], + "fields": ["duration", "created_at"], "limit": -1, } }, @@ -61,11 +62,25 @@ async def _get_workspace_usage(ws_id: str) -> WorkspaceUsage: if not isinstance(conversations, list): return WorkspaceUsage() + # All-time totals total_seconds = sum(c.get("duration") or 0 for c in conversations) + # Current month totals + now = datetime.now(timezone.utc) + month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0).isoformat() + monthly_seconds = 0 + monthly_count = 0 + for c in conversations: + created_at = c.get("created_at") + if created_at and created_at >= month_start: + monthly_seconds += c.get("duration") or 0 + monthly_count += 1 + return WorkspaceUsage( audio_hours=round(total_seconds / 3600, 1), conversation_count=len(conversations), + audio_hours_this_month=round(monthly_seconds / 3600, 1), + conversations_this_month=monthly_count, ) @@ -265,6 +280,8 @@ async def list_workspaces( total_audio_hours=round(sum(w.usage.audio_hours for w in team_workspaces), 1), total_conversations=sum(w.usage.conversation_count for w in team_workspaces), workspace_count=len(team_workspaces), + total_audio_hours_this_month=round(sum(w.usage.audio_hours_this_month for w in team_workspaces), 1), + total_conversations_this_month=sum(w.usage.conversations_this_month for w in team_workspaces), )) return WorkspaceListResponse(workspaces=results, teams=teams) From 1c49fb89ab3f02515b3eeda45edfa83136d655b5 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 18:10:43 +0000 Subject: [PATCH 039/208] fix: security + email fixes from final audit Security: - Membership delete verifies it belongs to the target workspace (IDOR fix) - Prevents removing the last owner of a workspace - Prevents removing already-deleted memberships Email: - New workspace_added.html template for existing users (not "Accept invitation") - Says "You've been added" + "Open workspace" (not "Accept invitation" + "expires in 7 days") - Existing user invite links to /workspaces (not generic /projects) - Inviter name correctly fetched via get_item (was using wrong ID type) --- echo/server/dembrane/api/v2/invites.py | 20 +++--- .../dembrane/api/v2/workspace_settings.py | 24 ++++++- .../email_templates/workspace_added.html | 62 +++++++++++++++++++ 3 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 echo/server/email_templates/workspace_added.html diff --git a/echo/server/dembrane/api/v2/invites.py b/echo/server/dembrane/api/v2/invites.py index 1c946564..d0470ff2 100644 --- a/echo/server/dembrane/api/v2/invites.py +++ b/echo/server/dembrane/api/v2/invites.py @@ -4,7 +4,7 @@ import secrets from logging import getLogger -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, HTTPException from dembrane.api.rate_limit import create_user_rate_limiter @@ -143,21 +143,21 @@ async def invite_to_workspace( # Send a notification email inviter_name = "Your team" - inviter_app_user = await resolve_app_user( - # ctx has app_user_id but we need display name from directus - ctx.app_user_id - ) - if inviter_app_user: - inviter_name = inviter_app_user.get("display_name", "Your team") + try: + inviter_data = await async_directus.get_item("app_user", ctx.app_user_id) + if inviter_data: + inviter_name = inviter_data.get("display_name") or "Your team" + except Exception: + pass await send_email( to=email, subject=f"You've been added to {ctx.workspace.get('name', 'a workspace')}", - template="workspace_invite", + template="workspace_added", template_data={ "inviter_name": inviter_name, "workspace_name": ctx.workspace.get("name", "a workspace"), - "invite_url": f"{settings.urls.admin_base_url}/projects", + "invite_url": f"{settings.urls.admin_base_url}/workspaces", }, ) @@ -169,7 +169,7 @@ async def invite_to_workspace( # User doesn't exist or doesn't have app_user — create an invite token = secrets.token_urlsafe(32) - expires_at = (datetime.utcnow() + timedelta(days=7)).isoformat() + expires_at = (datetime.now(timezone.utc) + timedelta(days=7)).isoformat() # Check for existing pending invite existing_invites = await async_directus.get_items( diff --git a/echo/server/dembrane/api/v2/workspace_settings.py b/echo/server/dembrane/api/v2/workspace_settings.py index 2c3c3cbe..f9a47acd 100644 --- a/echo/server/dembrane/api/v2/workspace_settings.py +++ b/echo/server/dembrane/api/v2/workspace_settings.py @@ -166,10 +166,30 @@ async def remove_workspace_member( """Soft-delete a workspace membership. Requires member:manage.""" ctx.require_policy("member:manage") - from datetime import datetime + # Verify the membership belongs to THIS workspace + membership = await async_directus.get_item("workspace_membership", membership_id) + if not membership or membership.get("workspace_id") != ctx.workspace_id: + raise HTTPException(status_code=404, detail="Membership not found in this workspace") + if membership.get("deleted_at"): + raise HTTPException(status_code=404, detail="Membership already removed") + + # Prevent removing the last owner + if membership.get("role") == "owner": + owners = await async_directus.get_items( + "workspace_membership", + {"query": {"filter": { + "workspace_id": {"_eq": ctx.workspace_id}, + "role": {"_eq": "owner"}, + "deleted_at": {"_null": True}, + }, "fields": ["id"], "limit": 2}}, + ) + if isinstance(owners, list) and len(owners) <= 1: + raise HTTPException(status_code=400, detail="Cannot remove the last owner. Transfer ownership first.") + + from datetime import datetime, timezone await async_directus.update_item( "workspace_membership", membership_id, - {"deleted_at": datetime.utcnow().isoformat()}, + {"deleted_at": datetime.now(timezone.utc).isoformat()}, ) return {"status": "success"} diff --git a/echo/server/email_templates/workspace_added.html b/echo/server/email_templates/workspace_added.html new file mode 100644 index 00000000..91585998 --- /dev/null +++ b/echo/server/email_templates/workspace_added.html @@ -0,0 +1,62 @@ + + + + + + + +
    + {{ inviter_name }} added you to {{ workspace_name }} on dembrane. +
    + + + + +
    + + + + + + + + + + + + + + + +
    + + dembrane +
    +

    + You've been added to a workspace +

    +

    + {{ inviter_name }} added you to + {{ workspace_name }} on dembrane. + You can start collaborating right away. +

    + + + + + + +
    + + Open workspace + +
    +
    +

    + dembrane - conversations that matter +

    +
    +
    + + From 7c3db7613e26b2672edb1711807c6ad07f0fc0d6 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 18:11:48 +0000 Subject: [PATCH 040/208] perf: parallelize workspace list queries + fix pre-seed script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workspace list performance: - All per-workspace queries (project count, member count, usage, avatar previews) now run in parallel via asyncio.gather - Team rollup member queries also parallelized - Reduces ~7N sequential queries to ~7 concurrent batches Pre-seed script: - Converted from async to fully sync (matches sync DirectusClient) - Removed asyncio.run() wrapper datetime.utcnow() → datetime.now(timezone.utc): - Fixed in invites.py, me.py, onboarding.py (deprecated in Python 3.12) --- echo/scripts/preseed_workspace.py | 17 +++-- echo/server/dembrane/api/v2/me.py | 4 +- echo/server/dembrane/api/v2/onboarding.py | 4 +- echo/server/dembrane/api/v2/workspaces.py | 85 ++++++++++++++++------- 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/echo/scripts/preseed_workspace.py b/echo/scripts/preseed_workspace.py index 213fd650..0e3d830c 100644 --- a/echo/scripts/preseed_workspace.py +++ b/echo/scripts/preseed_workspace.py @@ -46,7 +46,7 @@ def load_config(path: str) -> dict: return yaml.safe_load(f) -async def find_directus_user_by_email(client, email: str) -> dict | None: +def find_directus_user_by_email(client, email: str) -> dict | None: """Find a Directus user by email. Returns None if not found.""" users = client.get_users( {"query": {"filter": {"email": {"_eq": email}}, "fields": ["id", "email", "first_name", "last_name"], "limit": 1}} @@ -56,9 +56,9 @@ async def find_directus_user_by_email(client, email: str) -> dict | None: return None -async def find_or_create_directus_user(client, email: str, role_id: str, dry_run: bool) -> dict | None: +def find_or_create_directus_user(client, email: str, role_id: str, dry_run: bool) -> dict | None: """Find existing Directus user or create one with invite.""" - existing = await find_directus_user_by_email(client, email) + existing = find_directus_user_by_email(client, email) if existing: logger.info(f" Found existing Directus user: {email} (id: {existing['id']})") return existing @@ -78,7 +78,7 @@ async def find_or_create_directus_user(client, email: str, role_id: str, dry_run return user -async def find_or_create_app_user(client, directus_user_id: str, email: str, display_name: str, dry_run: bool) -> dict | None: +def find_or_create_app_user(client, directus_user_id: str, email: str, display_name: str, dry_run: bool) -> dict | None: """Find existing app_user or create one.""" items = client.get_items("app_user", {"query": {"filter": {"directus_user_id": {"_eq": directus_user_id}}, "limit": 1}}) if isinstance(items, list) and len(items) > 0: @@ -102,7 +102,7 @@ async def find_or_create_app_user(client, directus_user_id: str, email: str, dis return app_user -async def run_preseed(config: dict, dry_run: bool = True): +def run_preseed(config: dict, dry_run: bool = True): from dembrane.directus import create_directus_client from dembrane.utils import generate_uuid from dembrane.settings import get_settings @@ -152,7 +152,7 @@ async def run_preseed(config: dict, dry_run: bool = True): for email, role in all_emails.items(): logger.info(f"\nProcessing user: {email} (org role: {role})") - directus_user = await find_or_create_directus_user(client, email, basic_user_role_id, dry_run) + directus_user = find_or_create_directus_user(client, email, basic_user_role_id, dry_run) if not directus_user: if dry_run: app_users[email] = {"id": f"", "email": email} @@ -162,7 +162,7 @@ async def run_preseed(config: dict, dry_run: bool = True): last = directus_user.get("last_name") or "" display_name = f"{first} {last}".strip() or email - app_user = await find_or_create_app_user( + app_user = find_or_create_app_user( client, directus_user["id"], email, display_name, dry_run ) if app_user: @@ -353,8 +353,7 @@ def main(): config = load_config(args.config) - import asyncio - asyncio.run(run_preseed(config, dry_run=args.dry_run)) + run_preseed(config, dry_run=args.dry_run) if __name__ == "__main__": diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 047d1422..46711549 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -1,7 +1,7 @@ """GET /v2/me — lightweight user profile with onboarding status.""" from logging import getLogger -from datetime import datetime +from datetime import datetime, timezone from fastapi import APIRouter @@ -43,7 +43,7 @@ async def get_me(auth: DependencyDirectusSession) -> MeResponse: "filter": { "email": {"_eq": email}, "accepted_at": {"_null": True}, - "expires_at": {"_gt": datetime.utcnow().isoformat()}, + "expires_at": {"_gt": datetime.now(timezone.utc).isoformat()}, }, "fields": ["id"], "limit": 1, diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py index 8a734679..6760eaa8 100644 --- a/echo/server/dembrane/api/v2/onboarding.py +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -17,7 +17,7 @@ from __future__ import annotations from logging import getLogger -from datetime import datetime +from datetime import datetime, timezone from fastapi import APIRouter, HTTPException @@ -70,7 +70,7 @@ async def complete_onboarding( # ── Step 2: Auto-accept pending workspace invites ── - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() joined_an_org = False first_workspace_id = None diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 8f72237e..3f7780fb 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -1,5 +1,6 @@ """V2 workspace endpoints — list, create, manage workspaces.""" +import asyncio from datetime import datetime, timezone from logging import getLogger from typing import Optional @@ -199,35 +200,43 @@ async def list_workspaces( if isinstance(orgs, list): org_map = {o["id"]: o.get("name", "") for o in orgs} - # Build workspace summaries with usage - results: list[WorkspaceSummary] = [] - for membership in memberships: - ws_id = membership.get("workspace_id") - ws = ws_map.get(ws_id) - if not ws: - continue - - # Project count - proj_count_result = await async_directus.get_items( + # Build workspace summaries with usage — parallelize per-workspace queries + # Filter to valid memberships first + valid_memberships = [(m, ws_map[m["workspace_id"]]) for m in memberships if ws_map.get(m.get("workspace_id"))] + + async def _get_workspace_aggregates(ws_id: str) -> tuple[int, int, WorkspaceUsage, list[MemberPreview]]: + """Fetch project count, member count, usage, and member previews in parallel.""" + proj_task = async_directus.get_items( "project", {"query": {"filter": {"workspace_id": {"_eq": ws_id}, "deleted_at": {"_null": True}}, "aggregate": {"count": ["id"]}}}, ) - project_count = 0 - if isinstance(proj_count_result, list) and len(proj_count_result) > 0: - project_count = int(proj_count_result[0].get("count", {}).get("id", 0)) - - # Member count - mem_count_result = await async_directus.get_items( + mem_task = async_directus.get_items( "workspace_membership", {"query": {"filter": {"workspace_id": {"_eq": ws_id}, "deleted_at": {"_null": True}}, "aggregate": {"count": ["id"]}}}, ) + usage_task = _get_workspace_usage(ws_id) + previews_task = _get_member_previews(ws_id) + + proj_count_result, mem_count_result, usage, previews = await asyncio.gather( + proj_task, mem_task, usage_task, previews_task + ) + + project_count = 0 + if isinstance(proj_count_result, list) and len(proj_count_result) > 0: + project_count = int(proj_count_result[0].get("count", {}).get("id", 0)) member_count = 0 if isinstance(mem_count_result, list) and len(mem_count_result) > 0: member_count = int(mem_count_result[0].get("count", {}).get("id", 0)) - usage = await _get_workspace_usage(ws_id) - previews = await _get_member_previews(ws_id) + return project_count, member_count, usage, previews + # Run all workspace aggregate queries in parallel across all workspaces + all_aggregates = await asyncio.gather( + *[_get_workspace_aggregates(ws["id"]) for _, ws in valid_memberships] + ) + + results: list[WorkspaceSummary] = [] + for (membership, ws), (project_count, member_count, usage, previews) in zip(valid_memberships, all_aggregates): results.append(WorkspaceSummary( id=ws["id"], name=ws.get("name", ""), @@ -256,20 +265,44 @@ async def list_workspaces( }, ) if isinstance(org_membership_data, list): + # Build org-to-workspaces map and collect all workspace IDs for member queries + org_team_workspaces: dict[str, list[WorkspaceSummary]] = {} + all_team_ws_ids: list[str] = [] + valid_org_memberships = [] for om in org_membership_data: oid = om.get("org_id") if not oid: continue - team_workspaces = [w for w in results if w.org_id == oid] - # Unique members across all workspaces in this team - all_member_ids: set[str] = set() - for tw in team_workspaces: - mems = await async_directus.get_items( + team_ws = [w for w in results if w.org_id == oid] + org_team_workspaces[oid] = team_ws + all_team_ws_ids.extend(tw.id for tw in team_ws) + valid_org_memberships.append(om) + + # Fetch all workspace memberships for team rollups in parallel + all_team_mems = await asyncio.gather( + *[ + async_directus.get_items( "workspace_membership", - {"query": {"filter": {"workspace_id": {"_eq": tw.id}, "deleted_at": {"_null": True}}, "fields": ["user_id"], "limit": -1}}, + {"query": {"filter": {"workspace_id": {"_eq": ws_id}, "deleted_at": {"_null": True}}, "fields": ["user_id"], "limit": -1}}, ) - if isinstance(mems, list): - all_member_ids.update(m["user_id"] for m in mems if m.get("user_id")) + for ws_id in all_team_ws_ids + ] + ) if all_team_ws_ids else [] + + # Build ws_id -> member user_ids map + ws_member_map: dict[str, set[str]] = {} + for ws_id, mems in zip(all_team_ws_ids, all_team_mems): + member_ids: set[str] = set() + if isinstance(mems, list): + member_ids = {m["user_id"] for m in mems if m.get("user_id")} + ws_member_map[ws_id] = member_ids + + for om in valid_org_memberships: + oid = om["org_id"] + team_workspaces = org_team_workspaces[oid] + all_member_ids: set[str] = set() + for tw in team_workspaces: + all_member_ids.update(ws_member_map.get(tw.id, set())) teams.append(TeamRollup( id=oid, From b54a044b679e7986598708a886cbfb061f5f8166 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 16 Apr 2026 18:12:02 +0000 Subject: [PATCH 041/208] fix: brand consistency across ALL email templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 7 email templates now follow the brand guide: - "dembrane" lowercase everywhere (was "Dembrane" in 15+ places) - CTA buttons use Royal Blue #4169e1 (was #171717 and #007bff) - No bold text: font-weight 600/700 → 400, tags removed - Consistent footer: "dembrane - conversations that matter" - Warmer tone: removed corporate language ("contact your administrators", "We believe in you!", "Please click") - Dutch report notification: confirmed informal "je" form (no "uw") - workspace_invite.html: logo URL commented for deployment awareness --- echo/directus/templates/email-base.liquid | 8 ++--- echo/directus/templates/password-reset.liquid | 15 ++++---- .../templates/report-notification-en.liquid | 36 ++++++++++--------- .../templates/report-notification-nl.liquid | 34 ++++++++++-------- echo/directus/templates/user-invite.liquid | 10 +++--- .../templates/user-registration.liquid | 6 ++-- .../email_templates/workspace_invite.html | 7 ++-- 7 files changed, 62 insertions(+), 54 deletions(-) diff --git a/echo/directus/templates/email-base.liquid b/echo/directus/templates/email-base.liquid index a50bd694..51a012be 100644 --- a/echo/directus/templates/email-base.liquid +++ b/echo/directus/templates/email-base.liquid @@ -64,7 +64,7 @@ h1 { font-size: 28px; color: #171717; - font-weight: 700; + font-weight: 400; line-height: 32px; margin: 0; padding-bottom: 20px; @@ -252,7 +252,7 @@ - The Dembrane Team
    Administratordembrane - conversations that matter

    diff --git a/echo/directus/templates/password-reset.liquid b/echo/directus/templates/password-reset.liquid index 22884bd4..a3fbcd47 100644 --- a/echo/directus/templates/password-reset.liquid +++ b/echo/directus/templates/password-reset.liquid @@ -1,12 +1,11 @@ {% layout "email-base" %} {% block content %} -

    Reset your Dembrane password

    +

    Reset your dembrane password

    - We have received a request to reset the password for your - Dembrane account. If you did not make this change, please - contact one of your administrators. Otherwise, to complete the process, click - the following link to confirm your email address and enter your new password. + We received a request to reset the password for your + dembrane account. If you did not make this request, you can safely + ignore this email. Otherwise, click the button below to set a new password.

    @@ -37,6 +36,6 @@ -

    Important: This link will expire in 24 hours.

    +

    This link will expire in 24 hours.

    {% endblock %} \ No newline at end of file diff --git a/echo/directus/templates/report-notification-en.liquid b/echo/directus/templates/report-notification-en.liquid index e32c6139..e5c6fda2 100644 --- a/echo/directus/templates/report-notification-en.liquid +++ b/echo/directus/templates/report-notification-en.liquid @@ -3,12 +3,12 @@ - Dembrane Report Notification - + dembrane Report Notification + + + + +
    + + +
    +
    +
    v2 · 2026-04-20
    +

    Chosen directions, with your notes applied.

    +

    + Each section below starts with a change note spelling out what moved from v1. Ask 1 combines 1B and 1C as + a view switcher. Ask 3 adds a persistent "Shared with" strip on the project overview. Ask 4 shows the + tier + role gate. Ask 5 surfaces per-workspace manage. Onboarding gets a real fix. +

    +
    + Tweaks are preserved from v1 — flip tier=pilot to see gating (ask 4), role=staff for the set-tier + modal, density for scale. +
    +
    + + +
    +
    ask 01

    Team admin · list ⇄ matrix

    +
    + Same page, two views. 1B (list) is the default — friendly, readable, search-forward. 1C (matrix) is a + one-click alternate for admins who want the full access grid. +
    +
    + v2: combined 1B + 1C as a view switcher on the same URL. Shared filters/search. + Row actions & invite flow identical across views. External members get the pill + their own row group. +
    + +
    +

    1.Team admin page

    +

    /org/:orgId/members — owners & admins. Toggle view with the switcher top-right.

    +
    +
    +
    human collective › team · members
    +
    🔍 name, email+ Invite to team
    +
    + +
    +
    +
    Everyone with access · 12
    +
    9 on the team · 3 external · 3 workspaces
    +
    +
    + + +
    +
    + + +
    +
    + AllTeamExternal + sort: recent · role ▾ +
    + +
    Team · inherited to all workspaces
    +
    +
    + owner + DefaultA'damUtr + +
    +
    +
    +
    ownerDefA'damUtr
    +
    adminDefA'damUtr
    +
    adminDefA'damUtr
    +
    +
    +
    owner+3
    +
    admin+3
    +
    admin+2
    +
    member+1
    +
    member+1
    +
    …44 more team members
    +
    + +
    External guest
    +
    externalviewerA'dam
    +
    externaleditorUtr
    +
    ⋯ row menu: change role · view workspaces · remove.
    +
    + + +
    +
    group: teamclick a cell to change access
    +
    +
     
    +
    Default
    +
    A'dam
    +
    Utrecht
    + +
    owner
    +
    admin
    +
    admin
    +
    admin
    + +
    admin
    +
    admin
    +
    admin
    +
    admin
    + +
    admin
    +
    admin
    +
    — removed
    +
    admin
    + +
    ext
    +
    +
    viewer
    +
    + +
    ext
    +
    +
    +
    editor
    +
    +
    blue row = inherited. "— removed" reflects the sticky-remove rule. empty cell on external rows = not invited.
    +
    +
    +
    + +
    + mobile +
    List view collapses to stacked cards. Matrix view is hidden ≤ 768px with a "switch to list on mobile" toast.
    +
    +
    + + +
    +
    ask 02

    Tier management · 2B

    +
    Feature matrix in the Billing tab. Current tier column highlighted. Staff see the extra set-tier control inline.
    +
    v2: went with 2B per Notion doc + PRD. Staff controls live inline (no separate modal in the first pass — confirm dialog still fires on change).
    + +
    +
    +

    2.Compare + current tier

    +

    /w/:id/settings?tab=billing — visible to everyone in the workspace.

    +
    +
    A'dam · settings › billing
    +
    +
    +
    Current tier
    Renews · contact to change
    +
    innovator
    +
    +
    +
    Compare
    +
    +
    +
    pioneer
    +
    innovator
    you
    +
    changemaker
    +
    guardian
    + +
    Projects + convs
    +
    + +
    Chats + reports
    +
    + +
    Data export
    +
    + +
    Private sharing
    +
    + +
    Whitelabel
    +
    + +
    API access
    +
    +
    +
    + Pricing discussed over email. + Request upgrade +
    +
    +
    + +
    +

    2s.Staff · set tier inline

    +

    Shown only when role = dembrane staff. Same tab — no hidden route.

    +
    +
    A'dam · settings › billing
    staff
    +
    +
    dembrane staff controls
    +
    + Set tier + innovator ▾ +
    +
    + Reason (internal) + +
    +
    confirm dialog fires on change: "Set A'dam to changemaker?"
    +
    +
    All other content (compare, request upgrade) stays visible — no separate staff route.
    +
    +
    +
    + +
    + mobile +
    Matrix becomes horizontal scroll with sticky feature column on ≤ 640px.
    +
    +
    + + +
    +
    ask 03

    Private sharing · 3B modal + overview strip

    +
    The modal from 3B, plus a quiet "Shared with" strip on the project overview so it's always visible without opening the modal.
    +
    v2: added a persistent "Shared with" strip to the project overview page. The modal stays for editing; the strip is always-on presence. Gated at innovator+.
    + +
    + +
    +

    3.1Project overview · persistent strip

    +

    Lives under the project header, above content. Click → opens the modal (3.2).

    +
    +
    project · Stakeholder interviews
    Share
    +
    Stakeholder interviews
    +
    Last activity · 2 days ago
    + + +
    +
    +
    + Private + Shared with + + + 2 more +
    + Manage → +
    +
    + +
    +
    project body · conversations · reports…
    +
    +
    +
    +
    +
    when public: strip reads "Visible to everyone in A'dam · Make private".
    +
    +
    + + +
    +

    3.2"Who can see this?" modal

    +

    Opens from the strip or from project settings. Verb labels, not nouns.

    +
    +
    Who can see this project?
    ×
    +
    Stakeholder interviews · Private
    +
    you · can edit
    +
    can edit ▾
    +
    can read ▾
    +
    +
    + + can read ▾ +
    +
    Only people already in A'dam — no cross-workspace.
    +
    +
    +
    Everyone else in A'damno access
    +
    Link sharing offDone
    +
    +
    +
    + +
    mobile
    Strip collapses to a single line with the pill + "Manage". Modal becomes a full sheet.
    +
    + + +
    +
    ask 04

    Upgrade prompts · 4B (feature card) + 4C (modal)

    +
    4B gates whole feature surfaces. 4C is the modal that opens from any gate. Both check tier and role — only team admins + owners see "Request upgrade" as primary; members get a softer "Ask an admin" CTA.
    +
    v2: added the tier + role gate. Non-admins see "Ask an admin" instead of "Request upgrade". Try flipping role=member + tier=pilot in Tweaks.
    + +
    +
    +

    4.1Feature card · hatched overlay (4B)

    +

    For whole feature surfaces: Whitelabel, API, Data export. CTA changes with role.

    +
    +
    settings · branding
    +
    +
    +
    Whitelabel branding
    +
    Replace the dembrane logo with your own across the workspace.
    +
    +
    +
    +
    +
    +
    + changemaker tier +
    Bring your own brand once you're on changemaker.
    + Request upgrade + + +
    only owners + admins see this CTA
    +
    +
    +
    +
    when role=member: the primary button swaps to "Ask an admin" (ghost style) — same modal opens.
    +
    + +
    +

    4.2Upgrade modal (4C)

    +

    Opens from any gate. One feature, one benefit, one tier, one CTA.

    +
    +
    Private project sharing
    ×
    +
    Keep sensitive projects visible to a hand-picked few.
    +
    +
    Your tierpioneer
    +
    Neededinnovator
    +
    +
    We'll email you about pricing and next steps.
    + +
    + Not now + Request upgrade +
    +
    copy swaps on role: admin → "Request upgrade" · member → "Notify your admin".
    +
    +
    +
    + +
    mobile
    4B overlay stays the same; card becomes the tap target. 4C is a full sheet.
    +
    + + +
    +
    ask 05

    Selector + settings polish · 5B with per-workspace manage

    +
    5B's team header on top, workspaces as a list — but with a per-row "manage" affordance so admins can jump straight into workspace settings without opening it first.
    +
    v2: added a hover "⚙ manage" affordance on each workspace row, plus the team-level "manage team" as before. Externals stay in their own quieter section. Settings tabs view (5C) kept for reference.
    + +
    +
    +

    5.1Workspace selector

    +

    /select-workspace — team hero on top, workspaces below, externals quieter.

    +
    +
    choose a workspace
    🔍
    + +
    +
    +
    +
    Human Collective
    +
    3 workspaces · 12 people · pioneer tier (aggregated)
    +
    +
    + + +9 + Manage team +
    +
    +
    + +
    + 🔍 namesort: recent +
    + +
    + + Default + pioneer · 4 projects + ⚙ manage +
    +
    + + A'dam + innovator · 12 projects + ⚙ manage +
    +
    + + Utrecht + pioneer · 6 projects + ⚙ manage +
    + +
    External
    +
    + + Pilot 2026 + guest of GreenLabs + viewer +
    +
    ⚙ manage shows on row hover / keyboard focus — keeps selector calm.
    +
    +
    + +
    +

    5.2Workspace settings · tabs (reference)

    +

    Already exists on the workspaces branch — just confirming labels + the inherited-pill.

    +
    +
    A'dam · settings
    +
    + General + Members + Branding + Legal + Billing +
    +
    + Workspace-scoped. Team admins are inherited. + + Invite +
    +
    admin · inherited
    +
    editor
    +
    external · viewer
    +
    × on inherited row = sticky remove (matches the data-model rule).
    +
    +
    +
    + +
    mobile
    Team hero collapses to a single-row strip; ⚙ manage on each workspace moves to a row kebab menu.
    +
    + + +
    +
    fix

    Onboarding — new users shouldn't see the migration screen

    +
    + The current Welcome back, John screen is a migration prompt — it assumes you already have projects from before workspaces existed. + A freshly-registered account has nothing to migrate, so this copy is confusing and the "Use default" / "Team name" step is jarring. +
    +
    v2: split into two explicit paths. New user → ask team name during registration, then land in their empty default workspace. Existing user (no team yet) → current screen, re-copied as a migration.
    + +
    +
    +

    ANew user · team name at signup

    +

    One extra field on the signup form. No separate onboarding route.

    +
    +
    sign up
    +
    Create your account
    +
    Takes 30 seconds.
    +
    Email
    +
    Password
    +
    Your name
    +
    +
    Team name · optional
    +
    +
    If you're joining teammates later, skip this — they'll invite you in.
    +
    +
    By signing up…Create account
    +
    on submit: auto-create team (fallback "John's team"), default workspace, land in /w/:id/projects empty state.
    +
    +
    + +
    +

    BExisting user · migration only

    +

    The current screen, re-copied to be honest about what it is. Only shown when user has pre-workspaces projects.

    +
    +
    welcome back
    +
    Welcome back, john
    +
    We've added teams so you can organise projects and share them with colleagues. Everything you had before is still here — we just need a name for your team.
    +
    +
    Team name
    +
    +
    Change this anytime in settings.
    +
    +
    Use defaultGet started
    +
    gate: only shown if user.createdAt < workspaces-launch AND has no team yet. everyone else goes straight in.
    +
    +
    +
    + +
    + the rule: the current screen is the migration screen. New accounts should never see it — they should land in an empty default workspace after signup, with team name already captured. Detection is trivial: if the user has no team membership AND their account predates the launch timestamp, show B; otherwise skip. +
    + +
    mobile
    Both screens are identical on mobile — single column, CTAs stack full-width.
    +
    + +
    +
    next
    +

    + When you're happy with these, I'll push the team admin page (ask 1) to mid-fi using Mantine — that's the heaviest surface. Rest can follow in parallel. +

    +
    +
    +
    + + + + + + diff --git a/echo/docs/workspaces/inheritance-rules.md b/echo/docs/workspaces/inheritance-rules.md new file mode 100644 index 00000000..f8436a9f --- /dev/null +++ b/echo/docs/workspaces/inheritance-rules.md @@ -0,0 +1,247 @@ +# Workspace inheritance — codified rules (derived model) + +**Status:** canonical. Any code that answers "does user U have access to workspace W?" or "who is on workspace W?" must go through the resolvers defined here. **Inherited admin access is not stored — it is derived at query time.** + +**Supersedes:** the trigger-based fan-out model briefly codified in an earlier draft of this file on 2026-04-20. That model is gone. There are no `workspace_membership` rows with `source='inherited'`. If you see one, it's legacy from a pre-derivation-migration state and must be archived. + +--- + +## Core principle + +> **Rule-of-system:** every **open** workspace in a team includes every current + future team owner/admin as an inherited admin. Private workspaces never include anyone automatically. Inheritance is a **function of current state**, not a stored fact. If you change the state (promote, demote, flip privacy, remove from team), access recomputes on the next read — no triggers, no fan-out, no drift. + +Flags that drive the derivation: +- **`org_membership.role`** — only `owner` and `admin` contribute to inheritance. `member` does not. +- **`workspace.settings.inherit_team_admins`** — boolean, default `true`. `false` = private workspace; no inheritance. +- **`workspace.settings.inherit_team_members`** — boolean, default `false`. `true` = team members (org role `member`) also inherit. Exposed via the creation wizard's second checkbox (when the user picks "Open"). +- **`workspace.settings.sticky_removed`** — JSON array of `{user_id, removed_at, removed_by}`. If a user is in this list, they are never re-granted inherited access via any derivation path. + +--- + +## Access resolution — one function, priority order + +``` +user_can_access(workspace_id, user_id) → role | None +``` + +Resolution order: + +1. **Direct workspace membership.** `workspace_membership` row exists for `(ws, user)` with `deleted_at IS NULL`. Return its role. No further checks. +2. **Project-level share** (only relevant when caller is resolving access to a specific project inside a private project). Delegates to `get_user_project_access()` — see PRD §"Permission Resolution". +3. **Inherited admin** (derived): + - Workspace has `settings.inherit_team_admins == true`, **and** + - User has an active `org_membership` in the workspace's org with role `owner` or `admin`, **and** + - User is **not** in `workspace.settings.sticky_removed`. + - ⇒ return `'admin'`. +4. **Inherited member** (derived, only if workspace has `settings.inherit_team_members == true`): + - Workspace has `settings.inherit_team_admins == true` (admins-only open workspaces don't fan to members), **and** + - User has an active `org_membership` with role `member`, **and** + - User is **not** in `workspace.settings.sticky_removed`. + - ⇒ return `'member'`. +5. **Otherwise** — `None` (no access). + +Direct always wins. If a user is `source='direct'` with role `member`, and they're also a team admin, they see the workspace as a member (direct row beats derivation). This is intentional: direct is an explicit, audited state; derivation is ambient. + +--- + +## Member list resolution + +``` +get_effective_members(workspace_id) → list[EffectiveMember] +``` + +Returns the union of: +- every direct `workspace_membership` (source recorded as `'direct'`) +- every derived team admin (source `'inherited'`, role `'admin'`) — computed as in step 3 above +- every derived team member (source `'inherited'`, role `'member'`) — if `inherit_team_members=true`, computed as in step 4 + +Deduplication: if a user has a direct row, they appear only once with their direct role — the derivation is suppressed in the output. Users in `sticky_removed` never appear as inherited. + +This function is what the Teams admin page, workspace members tab, and seat-count queries all call. + +--- + +## Storage model + +**Stored:** +- `org_membership` — (org_id, user_id, role) — the source of truth for team membership and role +- `workspace_membership` — only `source='direct'` rows. These represent explicit invites. +- `workspace.settings.inherit_team_admins` — boolean flag +- `workspace.settings.inherit_team_members` — boolean flag +- `workspace.settings.sticky_removed` — JSON array of tombstones + +**Not stored (derived):** +- Inherited admin access +- Inherited member access +- Effective role for users not in `workspace_membership` + +--- + +## Transitions — what happens when… + +Because access is derived, most transitions are no-ops for the inheritance layer. The state change on the stored table is enough; the next access check reflects it. + +| Event | What to store | What to call | +|---|---|---| +| Workspace created (via `POST /v2/workspaces`) | Insert one `workspace_membership` for the creator with `source='direct', role='owner'`. Set `settings.inherit_team_admins` + `inherit_team_members` per wizard choice. | `inheritance.on_workspace_created()` (small helper; no fan-out) | +| Team admin invites a new person to the team | Insert `org_membership` with the chosen role. | Nothing in inheritance — derivation picks them up on next read | +| Team member promoted to admin/owner | Update `org_membership.role`. | Nothing — derivation sees the new role immediately | +| Team admin demoted to member | Update `org_membership.role`. | Nothing — derivation stops including them on open workspaces where they were inherited. Direct rows survive (they're explicit). | +| Team member removed from the team | Soft-delete `org_membership`. | Call `inheritance.on_team_member_removed()` which soft-deletes their `source='direct'` workspace_membership rows in this org. Inherited access stops automatically. | +| External user added as an explicit workspace member | Insert `workspace_membership` with `source='direct', is_external=True`. No `org_membership`. | — | +| External user is later added to the team | Insert `org_membership`. `is_external=True` on any existing workspace_membership rows becomes stale — reconcile to `False` on those rows. Derivation now applies to any workspace they're a team admin for. | `inheritance.on_external_became_internal()` | +| Internal user later loses team membership (becomes external) | Soft-delete `org_membership`. Direct rows on workspaces survive (explicit state). Any rows in this org should set `is_external=True` so UI renders correctly. | `inheritance.on_internal_became_external()` | +| Workspace privacy flipped open → private | Update `settings.inherit_team_admins = false`. | Nothing — derivation stops granting inherited access on next read | +| Workspace privacy flipped private → open | Update `settings.inherit_team_admins = true`. | Nothing — derivation grants inherited access on next read, minus anyone in `sticky_removed` | +| Admin "removes" an inherited member from a workspace | Add `{user_id, removed_at, removed_by}` to `workspace.settings.sticky_removed`. | `inheritance.sticky_remove(workspace_id, user_id)` | +| Workspace soft-deleted | Soft-delete `workspace`. All direct memberships hidden by `deleted_at IS NULL` filters. Derivation returns None for a deleted workspace. | — | + +The only two triggers left are **(a) workspace creation** (set the flags + insert creator row) and **(b) sticky-remove on explicit kick**. Plus the external-↔-internal reconciliation helpers, which are one-liners. + +--- + +## Sticky removal — representation + +`workspace.settings.sticky_removed` is a JSON array of tombstones: + +```json +{ + "sticky_removed": [ + { "user_id": "uuid", "removed_at": "2026-04-21T10:30:00Z", "removed_by": "uuid" } + ] +} +``` + +Checks: +- `is_sticky_removed(workspace, user_id)` = `user_id in [t['user_id'] for t in workspace.settings.sticky_removed or []]` +- `sticky_remove(workspace_id, user_id, by_user_id)` appends a tombstone (idempotent — skip if already present) + +Reverse query ("which workspaces has user X been sticky-removed from?") isn't needed for the release. If it becomes load-bearing, promote to a dedicated table. + +--- + +## Module shape — `dembrane/inheritance.py` + +```python +"""Workspace inheritance resolvers. See docs/workspaces/inheritance-rules.md. + +Inheritance is derived at query time, not stored. All access decisions and +member-list queries must route through this module. +""" + +from __future__ import annotations + + +# ── Helpers ─────────────────────────────────────────────────────────── + +def workspace_inherits_team_admins(workspace: dict) -> bool: + return (workspace.get("settings") or {}).get("inherit_team_admins", True) + + +def workspace_inherits_team_members(workspace: dict) -> bool: + return (workspace.get("settings") or {}).get("inherit_team_members", False) + + +def is_sticky_removed(workspace: dict, user_id: str) -> bool: + tombstones = (workspace.get("settings") or {}).get("sticky_removed") or [] + return any(t.get("user_id") == user_id for t in tombstones) + + +# ── Resolvers (read-side) ───────────────────────────────────────────── + +async def user_can_access(workspace_id: str, user_id: str) -> str | None: + """Return the effective role for this user on this workspace, or None. + Implements steps 1–5 in the access-resolution order.""" + ... + + +async def get_effective_members(workspace_id: str) -> list[dict]: + """Return [{user_id, role, source: 'direct'|'inherited', is_external, ...}]. + Derivation runs here; direct rows deduplicate derived.""" + ... + + +# ── State transitions (write-side) ──────────────────────────────────── + +async def on_workspace_created( + workspace_id: str, + creator_app_user_id: str, + inherit_team_admins: bool, + inherit_team_members: bool, +) -> None: + """Set settings flags + insert creator as source='direct', role='owner'.""" + ... + + +async def on_team_member_removed(org_id: str, user_id: str) -> None: + """User left or was removed from the team. Soft-delete all their + source='direct' rows in this org. (Derived access stops automatically.)""" + ... + + +async def on_external_became_internal(org_id: str, user_id: str) -> None: + """User just got an org_membership. Reconcile is_external=False on + any existing workspace_membership rows in this org.""" + ... + + +async def on_internal_became_external(org_id: str, user_id: str) -> None: + """User lost org_membership. Set is_external=True on any surviving + workspace_membership rows in this org so UI labels correctly.""" + ... + + +async def sticky_remove( + workspace_id: str, user_id: str, by_user_id: str +) -> None: + """Append a tombstone to workspace.settings.sticky_removed (idempotent).""" + ... +``` + +No fan-out. No triggers. The resolvers replace every path that used to read `workspace_membership.source='inherited'` rows. + +--- + +## Migration — from stored to derived + +One-time, idempotent, dry-run-supported: + +1. For every `workspace_membership` row with `source='inherited'` and `deleted_at IS NULL`: verify the derived model would grant the same access. Log divergences. Archive the row (soft-delete with a note) — it's no longer the source of truth. +2. For every `workspace_membership` row with `source='inherited'` and `deleted_at IS NOT NULL`: convert to a `sticky_removed` tombstone on the workspace's settings. Then archive the row. +3. After this script runs once, no code should ever insert `source='inherited'` again. Delete the `source='inherited'` branches from `POST /v2/workspaces` and any other handler (there shouldn't be many — the existing fan-out loop in `workspaces.py` is the main one). + +Run in dry-run mode first. Expect `source='inherited'` rows only on workspaces that were created via the current fan-out path (very few today). + +--- + +## Call sites + +| Where | Pattern | Owns | +|---|---|---| +| `middleware.get_workspace_context` | `await user_can_access(workspace_id, session.user_id)` → 403 if None | Every scoped endpoint | +| `GET /v2/workspaces/:id/settings` (members list) | `get_effective_members(workspace_id)` | Members tab | +| `GET /v2/orgs/:id/members` (matrix view) | For each user × workspace, compute `user_can_access` | Teams admin page | +| `GET /v2/workspaces` (selector) | `get_effective_members(ws).filter(user_id == me)` for each workspace | Workspace selector counts | +| `POST /v2/workspaces` | `on_workspace_created(...)` — no fan-out loop | Creation | +| `DELETE /v2/workspaces/:id/members/:uid` | If the removed user was inherited only: `sticky_remove(...)`. If direct: soft-delete the row. | Explicit kicks | +| `DELETE /v2/orgs/:id/members/:uid` | Soft-delete `org_membership` + `on_team_member_removed(...)` | Org membership management | +| External ↔ internal transitions | `on_external_became_internal` / `on_internal_became_external` | Invite accept with `include_org_membership=true`, and org removal | + +--- + +## Invariants (tests) + +1. **Single source of truth.** `user_can_access` is the only function that returns a role. No endpoint inspects `workspace_membership.source` directly to decide access. +2. **Direct wins.** If a user has both a direct row and a derived path, `user_can_access` returns the direct role and `get_effective_members` lists them once with `source='direct'`. +3. **Sticky forever (this release).** Once a user is tombstoned on a workspace, no state change (promotion, workspace re-opening, team re-join) re-grants them inherited access. They can only return via an explicit direct invite, which appends a direct row and is independent of the tombstone. +4. **Private means private.** When `inherit_team_admins=false`, `user_can_access` returns a role only via steps 1 or 2 (direct or project-share). Step 3/4 short-circuits. +5. **No orphan inherited rows.** After migration, `SELECT count(*) FROM workspace_membership WHERE source='inherited' AND deleted_at IS NULL` = 0 forever. +6. **External flag consistency.** A user with an active `org_membership` in this org never has `is_external=true` on any `workspace_membership` row in the same org. Reconciled by the two helpers above. + +--- + +## Open items (workshop) + +- **Sticky-forever vs expiring.** Currently sticky never clears. Do we want a "restore inherited access" path for the case where a past kick is no longer appropriate? Probably yes, but post-release. +- **Tier-per-team vs tier-per-workspace.** Separate decision (tracked in release-checklist.md §"Questions for the team"). Doesn't interact with this derivation model — it just moves where the tier flag lives. +- **Trigger #7 question from the old spec ("on open → private, do we kick existing inherited members?").** Resolved: there are no inherited members to kick in the derived model. Flipping `inherit_team_admins=false` makes them stop being members on the next read. Elegant. diff --git a/echo/docs/workspaces/release-checklist.md b/echo/docs/workspaces/release-checklist.md new file mode 100644 index 00000000..5d3ef17c --- /dev/null +++ b/echo/docs/workspaces/release-checklist.md @@ -0,0 +1,322 @@ +# Workspaces Release — Master Checklist + +> **Target release:** end of the week of 2026-04-20 +> **Status doc:** this file. Tick items as they land. +> **Companion docs:** `workspaces-prd-v3-final.md`, `execution-plan-final.md`, `designer-return.html`, `designer-brief.md`, `designer-brief-v2.md`, `inheritance-rules.md` + +## Progress gauges + +``` +Schema [██████████] 100% +Soft delete [██████████] 100% +Core API [███████░░░] 65% +Frontend route [██████████] 95% +Settings UI [█████░░░░░] 50% +Project polish [██░░░░░░░░] 20% +Emails [████░░░░░░] 40% +Internal tools [░░░░░░░░░░] 0% +──────────────────────────────────── +Feature total [██████░░░░] ~65% +``` + +--- + +## Session 1 — EXPLORE `[██████████] 100%` +- [x] Codebase exploration report +- [x] PRD v3 final + reconciliation +- [x] Execution plan, architecture review, failure analysis + +## Session 2 — SCHEMA `[██████████] 100%` +- [x] `app_user` collection (6f34b48) +- [x] `org` + `org_membership` (31ab178) +- [x] `workspace` + `workspace_membership` (9492e65) +- [x] `workspace_invite` + `project_membership` (686a81a) — `project_membership` replaces PRD's `project_user` +- [x] `project.workspace_id` / `visibility` / `deleted_at` (705e1a9) +- [x] `deleted_at` on conversation / project_chat / project_report (1128430, 948890c) +- [~] `usage_event` — intentionally removed (35281fe); revisit post-release + +## Session 3 — SOFT DELETE `[██████████] 100%` +- [x] `AsyncDirectusClient` for FastAPI (0466d6f) +- [x] Conversation soft delete (6d23381) +- [x] Project soft delete (bee6bfc) +- [x] Chat soft delete (43e2d16) +- [x] Report soft delete (2b5e4e2) +- [x] Webhook soft delete (54afb0b) +- [x] Tag soft delete (25cb4e8) +- [x] `deleted_at IS NULL` on all reads (bb02811) +- [x] DELETE permissions removed from Basic User (24b94f9) + +## Session 4 — CORE API + ONBOARDING `[███████░░░] 65%` + +**Built** +- [x] `get_workspace_context` middleware + policy system (1925651) +- [x] `GET/POST /v2/workspaces` +- [x] Workspace settings CRUD (get, update, remove member, change role, resend/cancel invite) +- [x] `POST /v2/workspaces/:id/invite` (HMAC token, rate-limited) +- [x] Workspace-scoped projects: list/create/move (0d3ba76, 1637c85) +- [x] `/v2/me` (get/update/pending invites/accept/decline/accept-by-hash) +- [x] `/v2/onboarding/complete` (idempotent, auto-accepts pending invites) +- [x] Inherited org admins auto-added on workspace create +- [x] Tier policies enforced server-side + +**Decision: skip migration script.** Auto-onboarding covers the new-user case. Existing-user migration lives in the onboarding flow itself (see "Onboarding split" below). + +**Missing** +- [ ] Org API: `GET /v2/orgs`, `GET /v2/orgs/:id/members`, `PATCH/DELETE /v2/orgs/:id/members/:uid` +- [ ] Org admin promotion → inherited workspace memberships (verify + wire) +- [ ] `PATCH /v2/workspaces/:id/tier` (staff-only; see Internal tools below) +- [ ] `DELETE /v2/workspaces/:id` (soft delete; blocked if projects exist) +- [ ] `POST/DELETE /v2/projects/:id/members` (private project sharing, innovator+) +- [ ] Staff guard: `is_staff` check on directus_users; `/v2/me` returns `is_staff` flag +- [ ] `POST /v2/workspaces/:id/suspend` + `unsuspend` (staff-only, blocks all access) + +## Phase 3 — Frontend routing + selector `[██████████] 95%` +- [x] `/:locale/w/:workspaceId/...` routing (bb5c8f3) +- [x] Workspace selector + last-used memory (cac5561) +- [x] Post-login router + onboarding redirect +- [x] Topbar workspace switcher +- [x] 403 stale-state handling (5aa191b) +- [ ] Progressive solo experience (hide "workspace" language for 1-ws users) +- [ ] Selector polish per designer Ask 5: team hero card on top, per-row `⚙ manage` on hover, externals in quieter section + +## Phase 4 — Frontend settings + management `[█████░░░░░] 50%` + +**Built** +- [x] Workspace settings page (756aecc) — one-page layout +- [x] Member role change, remove, resend/cancel invite (b4a77d9) +- [x] Create workspace (single-step form) +- [x] User settings + `PATCH /v2/me` (eae799d) + +**Designer-locked directions (from `designer-return.html`)** +- [ ] **Ask 1 — Teams admin page** `/org/:orgId/members`: list ⇄ matrix view switcher on same URL. Team section (inherited to all workspaces) + External section separate. Row menu: change role / view workspaces / remove. Matrix hidden ≤768px with "switch to list" toast. +- [ ] **Ask 2 — Tier management** on workspace settings `?tab=billing`: feature-matrix comparison, current tier highlighted, "Request upgrade" CTA (email-based). **Staff-only inline block** with Set tier dropdown + internal reason field + confirm dialog. No separate `/admin` route. +- [ ] **Ask 4 — Upgrade prompts**: 4B hatched overlay for feature surfaces (Whitelabel, API, Data export); 4C modal per feature. **Role-aware**: admin/owner see "Request upgrade" primary; member sees "Ask an admin" ghost — same modal opens. +- [ ] **Ask 5 — Selector polish**: team hero card (name, 3 workspaces, 12 people, tier agg, "Manage team" CTA), workspaces list with hover `⚙ manage`, external section with "guest of X" pill. +- [ ] Settings tab split (General / Members / Branding / Legal / Billing) — currently one page +- [ ] Delete workspace UI +- [ ] Migration onboarding modal ("your projects moved to [workspace]") + +## Phase 5 — Project changes + polish `[██░░░░░░░░] 20%` + +**Designer-locked directions** +- [ ] **Ask 3.1 — "Shared with" strip** on project overview page (under header, above content). Shows Private pill + avatars + "+N more" + "Manage →". When public: "Visible to everyone in [workspace] · Make private". +- [ ] **Ask 3.2 — "Who can see this project?" modal**: user list with `can edit` / `can read` dropdowns. Only users already in workspace (no cross-workspace). "Everyone else in [workspace]" = no access. Link sharing off. Verb labels, not nouns. Gated innovator+. +- [ ] Visibility toggle (workspace ↔ private) +- [ ] Tier-gated empty states with upgrade CTAs + +## Onboarding split (designer fix) `[░░░░░░░░░░] 0%` + +Current `OnboardingRoute` is a **migration prompt** that new users shouldn't see. Split into two paths: + +- [ ] **New user path**: add optional "Team name" field to signup form. On submit → auto-create team (fallback `"{firstName}'s team"`), default workspace, land in `/w/:id/projects` empty state. No separate onboarding route. +- [ ] **Existing user path**: keep current screen, re-copy as migration. Show only if `user.createdAt < workspaces-launch-ts` AND no team membership yet. + +--- + +## Internal tools — "how do we block access?" `[░░░░░░░░░░] 0%` + +Three levers: + +1. **Tier downgrade** — staff sets `workspace.tier` via inline billing-tab control (designer Ask 2s). Policy layer blocks innovator+/changemaker+/guardian features automatically. Best for "stop letting them use feature X". +2. **Workspace suspend** — new `workspace.suspended_at` field. Middleware checks it in `get_workspace_context` → returns 403 with friendly "This workspace is paused — contact your admin". Best for "block all access" (non-payment, abuse, GDPR). +3. **Membership removal** — existing soft-delete path. Blocks a specific user. Best for individual offboarding. + +Deliverables: +- [ ] `workspace.suspended_at` field + middleware 403 + frontend friendly page +- [ ] `is_staff` boolean on `directus_users` (or role check); surfaced in `/v2/me` +- [ ] Staff-only inline block on workspace billing tab: + - [ ] Set tier (dropdown + reason field + confirm dialog) + - [ ] Suspend / Unsuspend (toggle + reason) + - [ ] Internal notes field (free text, staff-only visible) +- [ ] Staff-only inline block on org admin page: + - [ ] View all members across all org workspaces (matrix already handles this) + - [ ] Export CSV of members +- [ ] Audit log of staff actions (lightweight `staff_action` collection — append-only, `{actor, action, target, reason, created_at}`) + +--- + +## Emails `[████░░░░░░] 40%` + +Exists: `workspace_invite.html`, `workspace_added.html` — basic, brand-updated. + +**Polish** +- [ ] Shared layout partial (header/footer de-duplicated; consider MJML) +- [ ] Proper logo header, typography scale, Royal Blue accent +- [ ] Preview text (``) for inbox preview lines +- [ ] Footer: help link, company address, unsubscribe (where applicable) +- [ ] Plain-text fallback for both templates (currently HTML-only → spam risk) +- [ ] Inviter name + avatar/initial; workspace name prominent +- [ ] Test rendering: Gmail / Outlook / Apple Mail / dark mode + +**New templates (this release)** +- [ ] Welcome email after first signup (post-onboarding) +- [ ] Role changed notification ("You are now admin of X") +- [ ] Removed from workspace notification (GDPR-friendly wording) + +**Deferred to post-release** +- [ ] Invite reminder (24h before expiry — cron) +- [ ] Org admin promoted notification +- [ ] Workspace suspended/unsuspended notifications (tie to internal tools) + +--- + +## Decisions locked + +- **Inheritance semantics (2026-04-20):** **rule-of-system (option a)** — every *open* workspace in a team always includes every current + future team owner/admin as an inherited admin. Private workspaces (`workspace.settings.inherit_team_admins = false`) never auto-include. No time dimension. ⚠️ **Must be ratified by the team before release.** +- **Derived inheritance (2026-04-20):** inherited admin/member access is **computed at query time, not stored.** No `workspace_membership` rows with `source='inherited'`. One resolver (`user_can_access`) is the single source of truth. Sticky removal lives as JSON tombstones in `workspace.settings.sticky_removed`. Full spec + migration plan in `inheritance-rules.md`. Replaces the earlier trigger/fan-out codification (now gone from the doc). +- **Access buckets in the creation wizard (2026-04-20):** two booleans stored in `workspace.settings` — `inherit_team_admins` (default true, shown as a checked-disabled row) + `inherit_team_members` (default false, shown as an optional checkbox). Dropped "external" as a bucket — externals never inherit (their presence on a workspace is always an explicit `source='direct'` row). Show step 2 of the wizard even for solo teams; dry-run reads "0 team admins will inherit." +- **Billing for private (2026-04-20):** **innovator+ tier at both workspace and project level** (designer's lean, accepted). Matches the existing gate on private-project sharing — one mental model: privacy is an innovator-tier capability. Solo users (team of 1) still see the option; it just has no behavioral effect. +- **30-day soft-delete SLA (2026-04-20):** accept the principle — rows with `deleted_at` older than 30 days are eligible for hard-delete. **Purger + Trash/Restore UI deferred to post-release.** Soft-delete alone is sufficient for ship. +- **Tier lives on the workspace (2026-04-20):** `workspace.tier` stays. Partner-client handoff model needs per-workspace flexibility — a partner might run three client workspaces on different tiers (one client on Guardian for whitelabel, another on Pioneer during pilot). Moving tier to the team would collapse that flexibility. Selector "mixed tier" display stays a real state and gets handled in UI. Ask 2 "Tier management" stays on the workspace billing tab per designer's v5 assumption. +- **Workspace-level configuration (2026-04-20):** whitelabel (logo + branding), trash / retention settings, and (future) custom prompts are **workspace-scoped**, not org-scoped. Storage: `workspace.logo_url` (already present) + `workspace.settings` JSON (already present) carry these. Org-level defaults may bubble down later (inheritance pattern for configuration, not access) — but the per-workspace override is always authoritative. +- **"Request upgrade" CTA (2026-04-20):** **real endpoint.** `POST /v2/workspaces/:id/upgrade-request` sends a styled email via SendGrid + returns a toast confirmation. Target inbox is configurable via env var `UPGRADE_REQUEST_INBOX` (default `sameer@dembrane.com` for now). Swap to a shared inbox at workshop. **Admin-role only.** +- **Member-role upgrade prompt (2026-04-20):** **no CTA.** Gate copy reads "This feature requires [tier]. Ask one of your team admins to upgrade." No button, no mailto, no admin-name list. Members can find admins via the members tab. The real friction is the gate, not a missing button; adding a CTA would be hollow flow. +- **Access-blocking levers:** tier downgrade + soft-delete workspace + membership removal. No `suspended_at` field this release. +- **`is_staff`:** derived from `auth.is_admin` (Directus Administrator role). No new schema. +- **Private workspace / private project flags:** stored in existing JSON/enum columns (`workspace.settings.inherit_team_admins`, `project.visibility`). No new columns. +- **Migration script:** skipped. Auto-onboarding + onboarding-split covers it. +- **Creation wizards (2026-04-20):** **full multi-step flow (option 2).** Dedicated routes (`/w/new`, `/w/:id/projects/new` — verify naming during S9), progress indicator, step-back, reviewable summary before create, cancel at each step. Applies to workspace creation and project creation. Designer to deliver Ask 6 wires for both. +- **Delete workspace (2026-04-20):** **option A — must be empty.** `DELETE /v2/workspaces/:id` returns 409 if any non-deleted projects exist. UI error message links to the team-page project surface (below) so admins can bulk-clean without walking into every workspace. +- **Team page — project management surface (2026-04-20):** Ask 1 expands to cover project-level actions across all team workspaces. Exact UI is open (see designer-brief-v2 Ask 1 follow-up). Goal: from the team admin page, an owner/admin can see every project in every workspace in the team and soft-delete any of them, so winding down a workspace is one page, not twenty. +- **Tier downgrade behavior (2026-04-20):** **Option A — freeze**, with **one hybrid exception: whitelabel is cleared on downgrade**. Existing premium artifacts (private shares, exports, API tokens) remain usable after downgrade but can't be added to; new attempts are gated. Whitelabel branding reverts to dembrane logo on downgrade — the confirmation dialog explicitly lists "your custom logo will be removed" before staff/admin proceeds. Generalizable rule: freeze by default, explicit-revert only where leaving state visible would brand-misrepresent the tier. +- **Tier gate auto-wiring in `has_policy()` (2026-04-20):** **included in S6.** `has_policy()` will look up `TIER_REQUIRED_FOR_POLICY[policy]` and deny if the workspace's current tier falls below. Removes the per-endpoint `ctx.require_tier()` requirement and closes the silent-gap risk flagged in `policies.py:21`. + +## Tier + role gating matrix + +Two independent axes. To use a feature you need the minimum tier **AND** the minimum role (within the workspace or org). Source: `server/dembrane/policies.py` + PRD + `docs/workspaces/reference.md` feature tree. + +### Tier gates (workspace.tier) + +| Feature | Min tier | Policy | Downgrade behavior | +|---|---|---|---| +| Projects + conversations + chat + reports (core) | pilot | — | — | +| Data export (transcripts / CSV / report download) | innovator | `workspace:export` | Freeze — existing files keep working; can't trigger new export | +| Private project sharing (add people to a private project) | innovator | `project:share` | Freeze — existing shares stay; no new shares can be added | +| Private project creation (`visibility='private'`) | innovator | `project:set_private` (new) | Freeze — stays private; can't newly mark private | +| Private workspace (opt out of team inheritance) | innovator | `workspace:set_private` (new) | Freeze — stays private; can't newly mark private | +| Whitelabel branding (custom logo) | changemaker | `workspace:whitelabel` | **Revert** — custom logo cleared, dembrane logo restored; downgrade dialog must explicitly warn | +| API / integration access | changemaker | `workspace:api_access` | Freeze — existing tokens keep working; no new tokens; rotation blocked | +| Webhooks | pilot *(current)* | none | Keep — free for all tiers today. Team Q: bump to changemaker with API? | +| Library / analysis views | pilot *(current, invite-gated)* | none | Team Q: should this be tier-gated at innovator+? | +| Agentic chat mode | pilot *(current BETA)* | none | Team Q: post-BETA, gate at innovator+? | + +### Role gates (workspace_membership.role) + +| Policy | viewer | member | admin | owner | +|---|:-:|:-:|:-:|:-:| +| project:read, conversation:read, report:view | ✓ | ✓ | ✓ | ✓ | +| project:create, project:update | — | ✓ | ✓ | ✓ | +| conversation:delete, chat:use, report:generate | — | ✓ | ✓ | ✓ | +| project:delete, project:share, project:move | — | — | ✓ | ✓ | +| report:delete | — | — | ✓ | ✓ | +| member:invite, member:manage | — | — | ✓ | ✓ | +| settings:manage, workspace:view_usage | — | — | ✓ | ✓ | +| workspace:export (+ innovator) | — | — | ✓ | ✓ | +| workspace:whitelabel (+ changemaker) | — | — | ✓ | ✓ | +| workspace:api_access (+ changemaker) | — | — | ✓ | ✓ | +| workspace:delete *(needs policy, owner-only)* | — | — | — | ✓ | +| upgrade-request submission | — | — | ✓ | ✓ | + +### Org-role gates (org_membership.role) + +| Policy | member | admin | owner | +|---|:-:|:-:|:-:| +| org:view | ✓ | ✓ | ✓ | +| org:manage_users, org:manage_settings, org:manage_billing | — | ✓ | ✓ | +| org:create_workspace, org:view_all_workspaces, org:view_usage | — | ✓ | ✓ | +| `*` (everything, transfer ownership) | — | — | ✓ | + +### Staff gates (`auth.is_admin`, i.e. Directus Administrator) + +| Action | Non-staff | Staff | +|---|:-:|:-:| +| View any workspace in Directus admin | — | ✓ | +| `PATCH /v2/admin/workspaces/:id/tier` | — | ✓ | +| Future: workspace audit log, suspend, force-transfer | — | ✓ | + +### Enforcement pattern + +- **Role** checks via `ctx.require_policy("...")` (middleware does the work; see `server/dembrane/api/v2/middleware.py`). +- **Tier** checks via `ctx.require_tier("innovator")` at the endpoint (explicit, auditable). +- **Staff** checks via `auth.is_admin` on the session object (JWT claim `admin_access`). +- **Gaps to close in S6:** wire `TIER_REQUIRED_FOR_POLICY` into `has_policy()` so tier gates fire automatically, not per-endpoint. Currently each tier-gated endpoint must call `ctx.require_tier()` by hand (`policies.py` has a TODO comment on this). + +## Questions for the team (workshop) + +> Sameer is running a workshop with the team to ratify these before release. Each item needs a call, not more discussion. + +### Access & inheritance +- [ ] **Inheritance rule-of-system (a) vs time-based (b).** Locked at (a). Confirm partners expect this when adding a new team admin retroactively. Sensitive past workspaces must be flipped to private *before* the admin joins — is that workflow acceptable, or do we need a smarter default? +- [ ] **Sticky removal across team re-join.** If person A is removed from workspace W (inherited) and later leaves the team and rejoins as admin, do they get auto-added to W again? PRD says no ("sticky"). Keep sticky forever, or expire it (e.g. 90 days)? +- [ ] **Last-admin protection.** What happens if the last owner of a team tries to leave or is removed? Block, or auto-promote someone? Who? +- [ ] **Cross-team membership.** Can a single user be an admin of two teams at once? Technically yes. Product: do we want the UI to encourage this or nudge against it? + +### Tier +- [ ] **Tier gating matrix.** Confirm the matrix above matches the product strategy. Specific open gates: + - **Webhooks** — currently free. Promote to changemaker alongside API access? + - **Library / analysis views** — currently invite-gated. Move to tier-gated innovator+, or keep invite-gated? + - **Agentic chat mode** — post-BETA, does it stay free or gate at innovator+? +- [ ] **Downgrade behavior list.** Freeze is default; whitelabel reverts on downgrade. Any other feature that should revert rather than freeze? Candidates: portal editor "custom finish text", transcript anonymization (probably keep as compliance). +- [ ] **Quota/seat gates.** Tiers currently gate *features*. Do we also want to gate *quantities* (projects per workspace, audio hours per month, members per workspace)? Not in scope this release unless essential. +- [ ] **New workspace starting tier.** Always `pioneer`, or does it inherit the team's highest-active tier? +- [ ] **Upgrade request inbox.** `sameer@dembrane.com` for now. When do we switch to a shared inbox, and what's the address? +- [ ] **Staff definition.** Is "Directus Administrator role" the right population for set-tier + audit? Any non-Administrators we want to include, or any Administrators we want to exclude? + +### Billing & seats +- [ ] **Billable seat count.** Does a seat = direct members only, or direct + inherited? Affects how partners think about cost of adding team admins. + +### Copy & UI +- [ ] **External vs Guest.** Designer uses "guest of [team]", PRD uses "External". Pick one for all UI. +- [ ] **Role names.** owner / admin / member / viewer — keep or soften (e.g. "editor" instead of "member")? +- [ ] **Tier names.** pilot / pioneer / innovator / changemaker / guardian — friendly enough for partners, or rename for clarity? + +## Sessions ahead + +Each session = one focused Claude Code conversation that lands a coherent batch of commits. We extend the original 4-session pattern (EXPLORE → SCHEMA → SOFT DELETE → CORE API). Sessions 1–4 completed on workspaces branch prior to 2026-04-20. + +``` +✓ S1 EXPLORE — codebase + PRD reconciliation +✓ S2 SCHEMA — org/workspace/membership/invite collections +✓ S3 SOFT DELETE — project/conversation/chat/report/webhook/tag + read filters +✓ S4 CORE API v1 — workspace CRUD, invites, /v2/me, onboarding-complete +───────────────────────────────────────────────────────────────────────── +○ S5 ORGS + STAFF — /v2/orgs endpoints; is_staff in /v2/me (derived from auth.is_admin); answer access-inheritance semantics +○ S6 ACCESS RULES — workspace.settings.inherit_team_admins respected in: create-workspace, team-invite, org-admin promotion; tier PATCH (staff-only); upgrade-request endpoint; delete-workspace endpoint +○ S7 TEAMS ADMIN PAGE — Ask 1 list ⇄ matrix ⇄ projects (3-view switcher); row menu; Invite-to-team; project delete across team +○ S8 TIER MANAGEMENT UI — Ask 2 compare matrix + Ask 2s inline staff controls on billing tab +○ S9 CREATION WIZARDS — workspace + project creation wizards with dry-run preview; privacy toggle +○ S10 PRIVATE PROJECT SHARING — Ask 3 "Shared with" strip + "Who can see" modal + visibility toggle +○ S11 UPGRADE PROMPTS — Ask 4 4B hatched overlay + 4C modal, role-aware copy +○ S12 SELECTOR + SETTINGS POLISH — Ask 5 team hero + per-row manage + settings tab split +○ S13 ONBOARDING SPLIT — new-user team-name-at-signup vs migration screen +○ S14 EMAILS — shared layout partial, plain-text fallbacks, welcome/role-changed/removed templates +○ S15 BUG BASH + RELEASE — smoke tests across roles (owner/admin/member/external/staff); deploy testing → main +``` + +Dependencies: S7 depends on S5; S8 depends on S6 tier PATCH; S9 depends on S6 access-rule wiring; S10 depends on S9's visibility plumbing. + +--- + +## Release blockers (must ship) + +1. Teams / Org admin page + org API (Ask 1) +2. Tier set/change via inline staff controls (Ask 2s) — **replaces "internal tools" migration story** +3. Workspace suspend/unsuspend (access-blocking primitive) +4. Delete workspace endpoint + UI +5. Onboarding split (new user vs migration) +6. Email polish + plain-text fallback + +## Deferred (post-release OK) + +- Private project sharing UI (Ask 3) — strongly prefer this week if time allows; designer is ready +- Workspace usage detail page +- Org billing rollup page +- Invite reminder cron +- `usage_event` reinstatement +- Staff action audit log (can start as simple log lines, formalize later) + +--- + +## Changelog for this doc + +- **2026-04-20**: initial master checklist; incorporated designer v2 directions; dropped migration script in favor of auto-onboarding + onboarding split; added internal tools / access-blocking track. From 41412620874848007f073a363fe50a5682e24415 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 15:45:44 +0000 Subject: [PATCH 074/208] feat: tier mgmt, upgrade-request, delete workspace, downgrade effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five endpoints land together because they share the tier-gate logic: PATCH /v2/workspaces/:id/tier — staff-only, reason required GET /v2/workspaces/:id/tier/preview- — W5 confirmation dialog data downgrade?to_tier=X POST /v2/workspaces/:id/upgrade-request — admin-role, emails configurable inbox (defaults sameer@dembrane.com, override via UPGRADE_REQUEST_INBOX) DELETE /v2/workspaces/:id — owner-only, blocked if projects exist PATCH /v2/workspaces/:id/settings — now also takes privacy flags (inherit_team_admins / _members); set_private is innovator+ gated New module dembrane/tier_downgrade.py declares DOWNGRADE_EFFECTS per policy ("revert" vs "freeze" — D19). preview_downgrade() powers the confirmation dialog; apply_downgrade_effects() runs revert-class mutations inside the tier change. Startup check raises if any policy in TIER_REQUIRED_FOR_POLICY is missing a downgrade behavior declaration — prevents silent drift when a new tier gate is added. The "Request upgrade" CTA is admin-role only (D9). Member-role path deliberately has no CTA per the Q3 call. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dembrane/api/v2/workspace_settings.py | 37 ++- echo/server/dembrane/api/v2/workspaces.py | 228 +++++++++++++++++- echo/server/dembrane/settings.py | 10 + echo/server/dembrane/tier_downgrade.py | 135 +++++++++++ 4 files changed, 406 insertions(+), 4 deletions(-) create mode 100644 echo/server/dembrane/tier_downgrade.py diff --git a/echo/server/dembrane/api/v2/workspace_settings.py b/echo/server/dembrane/api/v2/workspace_settings.py index a8a3e9e4..8ff1030c 100644 --- a/echo/server/dembrane/api/v2/workspace_settings.py +++ b/echo/server/dembrane/api/v2/workspace_settings.py @@ -190,6 +190,13 @@ async def get_workspace_settings( class UpdateWorkspaceRequest(BaseModel): name: Optional[str] = None description: Optional[str] = None + logo_url: Optional[str] = None + # Privacy flags — wizard step 2 equivalents. Flipping true→false makes + # the workspace private; derivation stops on next access check. + # Flipping false→true re-enables team-admin derivation (subject to the + # per-user sticky_removed tombstones). + inherit_team_admins: Optional[bool] = None + inherit_team_members: Optional[bool] = None @router.patch("/{workspace_id}/settings") @@ -197,14 +204,40 @@ async def update_workspace_settings( body: UpdateWorkspaceRequest, ctx: WorkspaceContext = Depends(get_workspace_context), ) -> dict: - """Update workspace name/description. Requires settings:manage.""" + """Update workspace name/description/logo/privacy flags. + + Requires settings:manage. Making a workspace private is tier-gated at + innovator+ via has_policy's workspace:set_private rule. + """ ctx.require_policy("settings:manage") - payload = {} + payload: dict = {} if body.name is not None: payload["name"] = body.name.strip() if body.description is not None: payload["description"] = body.description.strip() + if body.logo_url is not None: + payload["logo_url"] = body.logo_url + + # Privacy flags write into workspace.settings (existing JSON column). + if body.inherit_team_admins is not None or body.inherit_team_members is not None: + # Setting inherit_team_admins=false makes the workspace private — gated. + going_private = ( + body.inherit_team_admins is False + and ctx.workspace.get("settings", {}) .get("inherit_team_admins", True) is not False + ) + if going_private: + ctx.require_policy("workspace:set_private") + + current_settings = ctx.workspace.get("settings") or {} + if not isinstance(current_settings, dict): + current_settings = {} + merged = dict(current_settings) + if body.inherit_team_admins is not None: + merged["inherit_team_admins"] = bool(body.inherit_team_admins) + if body.inherit_team_members is not None: + merged["inherit_team_members"] = bool(body.inherit_team_members) + payload["settings"] = merged if not payload: raise HTTPException(status_code=400, detail="Nothing to update") diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index b9ae8102..307ad3dc 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -3,13 +3,19 @@ import asyncio from datetime import datetime, timezone from logging import getLogger -from typing import Optional +from typing import Literal, Optional -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel, Field from dembrane.utils import generate_uuid from dembrane.app_user import resolve_app_user, get_app_user_or_raise from dembrane.directus_async import async_directus +from dembrane.email import send_email +from dembrane.policies import TIER_ORDER +from dembrane.settings import get_settings +from dembrane.tier_downgrade import preview_downgrade, apply_downgrade_effects +from dembrane.api.v2.middleware import WorkspaceContext, get_workspace_context from dembrane.api.v2.schemas import ( CreateWorkspaceRequest, CreateWorkspaceResponse, @@ -21,6 +27,8 @@ ) from dembrane.api.dependency_auth import DependencyDirectusSession +settings = get_settings() + router = APIRouter() logger = getLogger("api.v2.workspaces") @@ -382,3 +390,219 @@ async def create_workspace( org_id=org_id, tier="pioneer", # Matches what we actually stored ) + + +# ── DELETE workspace ──────────────────────────────────────────────────── + + +@router.delete("/{workspace_id}") +async def delete_workspace( + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> dict: + """Soft-delete a workspace. Owner-only. Blocked if workspace has any + non-deleted project — decision A from the design log: partners wind + projects down via the team admin page's Projects view (Ask 1 D14). + """ + if ctx.role != "owner": + raise HTTPException( + status_code=403, detail="Only the workspace owner can delete it" + ) + + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_eq": ctx.workspace_id}, + "deleted_at": {"_null": True}, + }, + "aggregate": {"count": "id"}, + } + }, + ) + project_count = 0 + if isinstance(projects, list) and projects: + project_count = int(projects[0].get("count", {}).get("id", 0) or 0) + + if project_count > 0: + raise HTTPException( + status_code=409, + detail=( + f"This workspace has {project_count} project(s). " + "Delete or move them first — you can do this from the team's Projects view." + ), + ) + + now_iso = datetime.now(timezone.utc).isoformat() + await async_directus.update_item( + "workspace", ctx.workspace_id, {"deleted_at": now_iso} + ) + logger.info( + f"Deleted workspace {ctx.workspace_id} by {ctx.app_user_id} " + f"(role={ctx.role})" + ) + return {"status": "deleted"} + + +# ── Tier management (staff-only per D1 / Ask 2s) ──────────────────────── + + +class SetTierRequest(BaseModel): + tier: Literal["pilot", "pioneer", "innovator", "changemaker", "guardian"] + reason: str = Field( + min_length=1, + max_length=500, + description="Internal note for the staff audit trail (D17).", + ) + + +class SetTierResponse(BaseModel): + workspace_id: str + previous_tier: str + new_tier: str + direction: Literal["upgrade", "downgrade", "no-change"] + effects_applied: list[dict] = [] + + +@router.patch("/{workspace_id}/tier", response_model=SetTierResponse) +async def set_workspace_tier( + workspace_id: str, + body: SetTierRequest, + auth: DependencyDirectusSession, +) -> SetTierResponse: + """Staff-only tier change. Applies DOWNGRADE_EFFECTS when going down. + + Direction + reason are logged for the (future) staff audit surface. + The reason field is required (D17) so every change has a paper trail. + """ + if not auth.is_admin: + raise HTTPException(status_code=403, detail="Staff-only action") + + workspace = await async_directus.get_item("workspace", workspace_id) + if not workspace or workspace.get("deleted_at"): + raise HTTPException(status_code=404, detail="Workspace not found") + + from_tier = workspace.get("tier", "pioneer") + to_tier = body.tier + + try: + direction: Literal["upgrade", "downgrade", "no-change"] + from_idx = TIER_ORDER.index(from_tier) + to_idx = TIER_ORDER.index(to_tier) + except ValueError: + raise HTTPException(status_code=500, detail="Unknown tier value") + + if from_idx == to_idx: + direction = "no-change" + elif to_idx > from_idx: + direction = "upgrade" + else: + direction = "downgrade" + + effects: list[dict] = [] + if direction == "downgrade": + # Order matters: compute effects on the pre-change state, apply + # revert mutations, then update the tier. If we updated tier first, + # has_policy would already be denying policies we need to read. + effects = await apply_downgrade_effects(workspace_id, from_tier, to_tier) + + await async_directus.update_item("workspace", workspace_id, {"tier": to_tier}) + + logger.info( + f"STAFF tier change: workspace {workspace_id} {from_tier} → {to_tier} " + f"(direction={direction}, by={auth.user_id}, reason={body.reason!r}, " + f"effects={[e['policy'] for e in effects]})" + ) + return SetTierResponse( + workspace_id=workspace_id, + previous_tier=from_tier, + new_tier=to_tier, + direction=direction, + effects_applied=effects, + ) + + +@router.get("/{workspace_id}/tier/preview-downgrade") +async def preview_workspace_downgrade( + to_tier: Literal["pilot", "pioneer", "innovator", "changemaker", "guardian"], + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> dict: + """What would a downgrade to `to_tier` do? Read-only — powers the W5 + confirmation dialog copy. Anyone with settings:manage can preview. + """ + ctx.require_policy("settings:manage") + current = ctx.workspace.get("tier", "pioneer") + return { + "from_tier": current, + "to_tier": to_tier, + "effects": await preview_downgrade(ctx.workspace_id, current, to_tier), + } + + +# ── Upgrade request (Ask 2 + 4C "Request upgrade" CTA, admin-role only) ─ + + +class UpgradeRequestBody(BaseModel): + target_tier: Optional[ + Literal["pioneer", "innovator", "changemaker", "guardian"] + ] = None + message: Optional[str] = Field(default=None, max_length=1000) + + +@router.post("/{workspace_id}/upgrade-request") +async def request_upgrade( + body: UpgradeRequestBody, + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> dict: + """Admin clicks "Request upgrade" in the tier compare view. Sends an + email to settings.email.upgrade_request_inbox with context. Configurable + via UPGRADE_REQUEST_INBOX env var (defaults to sameer@dembrane.com during + this release). + + Member role doesn't see this CTA (Q3 / D9 — member-role path shows copy + only, no button). Enforced here by require_policy(member:invite) which + only admin/owner have — keeps the endpoint from being abused. + """ + ctx.require_policy("member:invite") + + requester = await async_directus.get_item("app_user", ctx.app_user_id) + requester_name = (requester or {}).get("display_name") or "" + requester_email = (requester or {}).get("email") or "" + + workspace_name = ctx.workspace.get("name", "") + current_tier = ctx.workspace.get("tier", "pioneer") + org = await async_directus.get_item("org", ctx.workspace.get("org_id")) + org_name = (org or {}).get("name", "") + + target = body.target_tier or "(not specified)" + body_html = ( + f"

    Upgrade request

    " + f"

    Team: {org_name}
    " + f"Workspace: {workspace_name} ({ctx.workspace_id})
    " + f"Current tier: {current_tier}
    " + f"Requested tier: {target}

    " + f"

    Requested by: {requester_name} <{requester_email}>

    " + ) + if body.message: + body_html += f"

    Message:
    {body.message}

    " + + sent = await send_email( + to=settings.email.upgrade_request_inbox, + subject=f"Upgrade request: {org_name} / {workspace_name} ({current_tier} → {target})", + html=body_html, + ) + if not sent: + # Don't silently drop — mirrors the pattern from 9021900. + logger.error( + f"Upgrade request email failed for workspace {ctx.workspace_id}" + ) + raise HTTPException( + status_code=502, + detail="Couldn't send the request. Please try again or email us directly.", + ) + + logger.info( + f"Upgrade request: workspace {ctx.workspace_id} {current_tier} → {target} " + f"by {ctx.app_user_id}" + ) + return {"status": "sent"} diff --git a/echo/server/dembrane/settings.py b/echo/server/dembrane/settings.py index c3d6ad1e..f3773c2e 100644 --- a/echo/server/dembrane/settings.py +++ b/echo/server/dembrane/settings.py @@ -333,6 +333,16 @@ class EmailSettings(BaseSettings): alias="EMAIL_FROM_NAME", validation_alias=AliasChoices("EMAIL_FROM_NAME", "EMAIL__FROM_NAME"), ) + # Where "Request upgrade" CTAs route (Ask 2 + 4C in the designer spec). + # Defaults to sameer@dembrane.com during the workspaces release; switch + # to a shared billing inbox once the team decides one. + upgrade_request_inbox: str = Field( + default="sameer@dembrane.com", + alias="UPGRADE_REQUEST_INBOX", + validation_alias=AliasChoices( + "UPGRADE_REQUEST_INBOX", "EMAIL__UPGRADE_REQUEST_INBOX" + ), + ) class DatabaseSettings(BaseSettings): diff --git a/echo/server/dembrane/tier_downgrade.py b/echo/server/dembrane/tier_downgrade.py new file mode 100644 index 00000000..30ad3fab --- /dev/null +++ b/echo/server/dembrane/tier_downgrade.py @@ -0,0 +1,135 @@ +"""Tier-downgrade effect list. + +Per-policy behavior when a workspace drops below a tier that previously +granted the policy. Two effects, following D19 in the design log: + + "revert" (↺) — feature state is cleared on downgrade. Confirmation + dialog must list this explicitly before the staff + action proceeds. + "freeze" (❄) — existing use stays; new use blocked by has_policy() + on the new tier. No data loss. + +Adding a new tier-gated policy: declare it here AND in +policies.TIER_REQUIRED_FOR_POLICY. The startup check below raises if the +two sets disagree — prevents "someone forgot to declare the downgrade +behavior" drift. + +See docs/workspaces/release-checklist.md §"Tier + role gating matrix" for +the canonical mapping. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import Literal + +from dembrane.directus_async import async_directus +from dembrane.policies import TIER_REQUIRED_FOR_POLICY, meets_tier + +logger = getLogger("dembrane.tier_downgrade") + +Effect = Literal["revert", "freeze"] + +DOWNGRADE_EFFECTS: dict[str, Effect] = { + "workspace:whitelabel": "revert", # ↺ clear custom logo + "workspace:api_access": "freeze", # ❄ existing tokens stay, no new/rotate + "workspace:export": "freeze", # ❄ existing exports intact + "project:share": "freeze", # ❄ existing shares stay + "workspace:set_private": "freeze", # ❄ stays private + "project:set_private": "freeze", # ❄ stays private +} + + +def _startup_check() -> None: + """Every tier-gated policy must declare its downgrade behavior here.""" + missing = set(TIER_REQUIRED_FOR_POLICY) - set(DOWNGRADE_EFFECTS) + if missing: + raise RuntimeError( + "TIER_REQUIRED_FOR_POLICY declares policies with no downgrade " + f"effect in DOWNGRADE_EFFECTS: {sorted(missing)}. " + "Add them to dembrane/tier_downgrade.py." + ) + + +_startup_check() + + +# Human-readable copy for the confirmation dialog. Keep terse; the dialog +# should list these as bullet points under the "This will" heading. +_HUMAN: dict[str, str] = { + "workspace:whitelabel": "Remove your custom logo (revert to dembrane logo)", + "workspace:api_access": "Freeze API access (existing tokens keep working; no new tokens)", + "workspace:export": "Freeze data export (existing files stay; new exports blocked)", + "project:share": "Freeze private project sharing (existing shares stay; no new shares)", + "workspace:set_private": "Freeze ability to make new private workspaces", + "project:set_private": "Freeze ability to make new private projects", +} + + +async def preview_downgrade( + workspace_id: str, from_tier: str, to_tier: str +) -> list[dict]: + """What will happen if we downgrade this workspace? Pure read. + + Returns a list of {policy, effect, human} entries for every policy + whose tier gate newly kicks in at to_tier. Empty list if to_tier + ≥ from_tier (no effects to preview). + + UI renders as "This will:" bulleted list in the confirmation dialog. + """ + if meets_tier(to_tier, from_tier): + return [] + + affected: list[dict] = [] + for policy, required_tier in TIER_REQUIRED_FOR_POLICY.items(): + # Policy was available at from_tier but not at to_tier + if meets_tier(from_tier, required_tier) and not meets_tier( + to_tier, required_tier + ): + affected.append( + { + "policy": policy, + "effect": DOWNGRADE_EFFECTS[policy], + "human": _HUMAN.get(policy, policy), + } + ) + return affected + + +async def apply_downgrade_effects( + workspace_id: str, from_tier: str, to_tier: str +) -> list[dict]: + """Execute the revert effects; return the preview list for logging. + + Called inside the PATCH tier transaction in workspaces.py. + + - revert effects mutate workspace state (e.g. clear logo_url). + - freeze effects require no action — has_policy() will start denying + the policy automatically once workspace.tier is updated. + """ + effects = await preview_downgrade(workspace_id, from_tier, to_tier) + if not effects: + return [] + + workspace = await async_directus.get_item("workspace", workspace_id) + if not workspace: + return effects + + patches: dict = {} + + for e in effects: + if e["effect"] != "revert": + continue + if e["policy"] == "workspace:whitelabel": + # Clear the custom workspace logo. Org-level fallback still applies. + patches["logo_url"] = None + # Any future "revert" policy adds its clear-on-downgrade here. + + if patches: + await async_directus.update_item("workspace", workspace_id, patches) + logger.info( + f"Applied revert effects on workspace {workspace_id} " + f"({from_tier} → {to_tier}): {list(patches)}" + ) + + return effects From e26d725b7cbc8560ba827aeafc6b87b95cde794e Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 15:48:18 +0000 Subject: [PATCH 075/208] refactor: shared email layout + auto plain-text fallbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted the brand header/footer into _layout.html. workspace_invite.html and workspace_added.html now extend it via {% extends %} — de-duplicates the markup and keeps future templates consistent. Plain-text versions (.txt) live alongside each HTML template. send_email picks them up automatically when a template name is passed, and wires both parts into a multipart/alternative payload (plain first, html second per SendGrid guidance). Improves deliverability and lets mail clients that prefer text render readable content. New plain-text fallbacks: workspace_invite.txt, workspace_added.txt. Role-changed / removed / welcome templates intentionally not added yet — will land with the endpoints that need them, not pre-built. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/email.py | 34 +++++++- echo/server/email_templates/_layout.html | 78 ++++++++++++++++++ .../email_templates/workspace_added.html | 68 +++------------- .../email_templates/workspace_added.txt | 7 ++ .../email_templates/workspace_invite.html | 79 ++++--------------- .../email_templates/workspace_invite.txt | 9 +++ 6 files changed, 153 insertions(+), 122 deletions(-) create mode 100644 echo/server/email_templates/_layout.html create mode 100644 echo/server/email_templates/workspace_added.txt create mode 100644 echo/server/email_templates/workspace_invite.txt diff --git a/echo/server/dembrane/email.py b/echo/server/dembrane/email.py index 62c881f2..9e655e76 100644 --- a/echo/server/dembrane/email.py +++ b/echo/server/dembrane/email.py @@ -77,6 +77,24 @@ def _render_template(template_name: str, data: dict) -> str: raise +def _render_plain_text_template(template_name: str, data: dict) -> Optional[str]: + """Render a plain-text fallback for a named template. + + Conventions: a template "foo" can have an adjacent "foo.txt" with the + same variables. Missing .txt is fine — we just skip the multipart + alternative in that case. + """ + try: + env = _get_jinja_env() + template = env.get_template(f"{template_name}.txt") + return template.render(**data) + except jinja2.TemplateNotFound: + return None + except Exception: + logger.exception(f"Failed to render plain-text template: {template_name}") + return None + + def _build_message( to: str | list[str], subject: str, @@ -100,17 +118,27 @@ def _build_message( # Resolve content if template: html = _render_template(template, template_data or {}) + # Auto-pick up a plain-text fallback when one exists alongside the + # HTML template — better deliverability (reduces spam score) and + # works in mail clients that prefer text. + if plain_text is None: + plain_text = _render_plain_text_template(template, template_data or {}) if not html and not plain_text: raise ValueError("Either html, plain_text, or template must be provided") - content = Content("text/html", html) if html else Content("text/plain", plain_text or "") - message = Mail() message.from_email = sender message.subject = subject message.to = recipients - message.content = content + + # SendGrid expects multipart/alternative as text/plain first, then html. + # Use add_content() (not direct .content assignment) so multiple parts + # are accepted. + if plain_text: + message.add_content(Content("text/plain", plain_text)) + if html: + message.add_content(Content("text/html", html)) return message diff --git a/echo/server/email_templates/_layout.html b/echo/server/email_templates/_layout.html new file mode 100644 index 00000000..58e6c160 --- /dev/null +++ b/echo/server/email_templates/_layout.html @@ -0,0 +1,78 @@ +{# + Shared base layout for dembrane transactional emails. + + Blocks to override in children: + preview — small text shown as inbox preview line (keep <90 chars) + heading — top H2 inside the card + body — main message content + cta — primary button (leave empty to hide) + footer — trailing small text under the body (leave empty to hide) + + Brand rules (brand/STYLE_GUIDE.md): lowercase "dembrane" always, Royal Blue + (#4169e1) for CTAs, no "successfully" / "AI" copy, warm but not gushing. +#} + + + + + + {% block title %}dembrane{% endblock %} + + +
    + {% block preview %}{% endblock %} +
    + + + + +
    + + + + + + + + + + + + + + + +
    + dembrane +
    +

    + {% block heading %}{% endblock %} +

    +
    + {% block body %}{% endblock %} +
    + + {% block cta %}{% endblock %} + + {% block footer %}{% endblock %} +
    +

    + dembrane · conversations that matter +

    +
    +
    + + +{# Helper macro: primary CTA button. Import and call from child templates. #} +{% macro cta_button(label, url) -%} + + + + +
    + + {{ label }} + +
    +{%- endmacro %} diff --git a/echo/server/email_templates/workspace_added.html b/echo/server/email_templates/workspace_added.html index 5e8f0c5f..10f56100 100644 --- a/echo/server/email_templates/workspace_added.html +++ b/echo/server/email_templates/workspace_added.html @@ -1,56 +1,12 @@ - - - - - - - -
    - {{ inviter_name }} added you to {{ workspace_name }} on dembrane. -
    - - - - -
    - - - - - - - - - - - - -
    - dembrane -
    -

    - You've been added to a workspace -

    -

    - {{ inviter_name }} added you to {{ workspace_name }} on dembrane. - You can start collaborating right away. -

    - - - - - -
    - - Open workspace - -
    -
    -

    - dembrane · conversations that matter -

    -
    -
    - - +{% extends "_layout.html" %} +{% from "_layout.html" import cta_button %} +{% block title %}You've been added to {{ workspace_name }}{% endblock %} +{% block preview %}{{ inviter_name }} added you to {{ workspace_name }} on dembrane.{% endblock %} +{% block heading %}You've been added to a workspace{% endblock %} +{% block body %} +

    + {{ inviter_name }} added you to {{ workspace_name }} on dembrane. + You can start collaborating right away. +

    +{% endblock %} +{% block cta %}{{ cta_button("Open workspace", invite_url) }}{% endblock %} diff --git a/echo/server/email_templates/workspace_added.txt b/echo/server/email_templates/workspace_added.txt new file mode 100644 index 00000000..26483a11 --- /dev/null +++ b/echo/server/email_templates/workspace_added.txt @@ -0,0 +1,7 @@ +{{ inviter_name }} added you to {{ workspace_name }} on dembrane. You can start collaborating right away. + +Open the workspace: +{{ invite_url }} + +— +dembrane · conversations that matter diff --git a/echo/server/email_templates/workspace_invite.html b/echo/server/email_templates/workspace_invite.html index 0273235a..37b5dac0 100644 --- a/echo/server/email_templates/workspace_invite.html +++ b/echo/server/email_templates/workspace_invite.html @@ -1,63 +1,16 @@ - - - - - - - -
    - {{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. -
    - - - - -
    - - - - - - - - - - - - - - - -
    - dembrane -
    -

    - You've been invited to collaborate -

    -

    - {{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. -

    - - - - - - -
    - - Accept invitation - -
    - -

    - This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it. -

    -
    -

    - dembrane · conversations that matter -

    -
    -
    - - +{% extends "_layout.html" %} +{% from "_layout.html" import cta_button %} +{% block title %}You're invited to collaborate on dembrane{% endblock %} +{% block preview %}{{ inviter_name }} invited you to join {{ workspace_name }} on dembrane.{% endblock %} +{% block heading %}You've been invited to collaborate{% endblock %} +{% block body %} +

    + {{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. +

    +{% endblock %} +{% block cta %}{{ cta_button("Accept invitation", invite_url) }}{% endblock %} +{% block footer %} +

    + This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it. +

    +{% endblock %} diff --git a/echo/server/email_templates/workspace_invite.txt b/echo/server/email_templates/workspace_invite.txt new file mode 100644 index 00000000..7b54e7e4 --- /dev/null +++ b/echo/server/email_templates/workspace_invite.txt @@ -0,0 +1,9 @@ +{{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. + +Accept the invitation: +{{ invite_url }} + +This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it. + +— +dembrane · conversations that matter From c51a7e04509fe2ffb79cfe0b58810e5dd8f7e1a4 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 15:50:25 +0000 Subject: [PATCH 076/208] =?UTF-8?q?feat:=20private=20project=20sharing=20A?= =?UTF-8?q?PI=20=E2=80=94=20/v2/projects/:id/members=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend for the designer's Ask 3 (persistent "Shared with" strip + "Who can see this project?" modal). Four endpoints: GET /v2/projects/:id/members — list current shares POST /v2/projects/:id/members — add a share (innovator+ gated) PATCH /v2/projects/:id/members/:uid — change role DELETE /v2/projects/:id/members/:uid — revoke (hard delete) Constraints from Ask 3: 1. Project must be visibility='private' 2. Invitee must already be a workspace member (direct or derived). No cross-workspace sharing. 3. Caller needs project:share policy — admin/owner role AND innovator+ tier. Pushed through has_policy(workspace_tier=...) so the tier gate rides along automatically. Role hierarchy uses existing PROJECT_ROLE_PRESETS (viewer / editor). Default role on add = viewer (D16: safest default). Hard-delete on revoke matches the designer's modal — no undo, re-grant is an explicit POST. project_membership has no deleted_at column by design; if we need tombstones later we can promote to soft-delete. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/__init__.py | 6 +- .../server/dembrane/api/v2/project_sharing.py | 377 ++++++++++++++++++ 2 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 echo/server/dembrane/api/v2/project_sharing.py diff --git a/echo/server/dembrane/api/v2/__init__.py b/echo/server/dembrane/api/v2/__init__.py index 1429f8cf..9b2c1d9b 100644 --- a/echo/server/dembrane/api/v2/__init__.py +++ b/echo/server/dembrane/api/v2/__init__.py @@ -14,6 +14,7 @@ from dembrane.api.v2.onboarding import router as onboarding_router from dembrane.api.v2.invites import router as invites_router from dembrane.api.v2.projects import router as projects_router +from dembrane.api.v2.project_sharing import router as project_sharing_router from dembrane.api.v2.workspaces import router as workspaces_router from dembrane.api.v2.workspace_projects import router as workspace_projects_router from dembrane.api.v2.workspace_settings import router as workspace_settings_router @@ -32,5 +33,8 @@ v2_router.include_router(workspace_projects_router, prefix="/workspaces", tags=["v2:workspace-projects"]) v2_router.include_router(workspace_settings_router, prefix="/workspaces", tags=["v2:workspace-settings"]) -# Project-level: /projects/{id}/move +# Project-level: /projects/{id}/move + /projects/{id}/members (private sharing) v2_router.include_router(projects_router, prefix="/projects", tags=["v2:projects"]) +v2_router.include_router( + project_sharing_router, prefix="/projects", tags=["v2:project-sharing"] +) diff --git a/echo/server/dembrane/api/v2/project_sharing.py b/echo/server/dembrane/api/v2/project_sharing.py new file mode 100644 index 00000000..0a2cc6e8 --- /dev/null +++ b/echo/server/dembrane/api/v2/project_sharing.py @@ -0,0 +1,377 @@ +"""V2 private project sharing — project_membership CRUD. + +Implements the backend for the designer's Ask 3 (persistent "Shared with" +strip + "Who can see this project?" modal). Tier-gated at innovator+ via +has_policy's project:share rule. + +Private project = project.visibility == 'private'. When visibility is +'workspace', project is visible to every workspace member — project_membership +is irrelevant. + +Role hierarchy on a project share (PROJECT_ROLE_PRESETS): + viewer → read-only + editor → can edit + run chats/reports + export data + +Endpoints: + GET /v2/projects/:id/members — list current shares + POST /v2/projects/:id/members — add a share (innovator+) + PATCH /v2/projects/:id/members/:uid — change role + DELETE /v2/projects/:id/members/:uid — revoke share (hard delete) +""" + +from __future__ import annotations + +from logging import getLogger +from typing import Literal, Optional + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel, EmailStr + +from dembrane.utils import generate_uuid +from dembrane.app_user import get_app_user_or_raise +from dembrane.directus_async import async_directus +from dembrane.inheritance import user_can_access +from dembrane.policies import ( + WORKSPACE_ROLE_PRESETS, + TIER_REQUIRED_FOR_POLICY, + has_policy, + meets_tier, +) +from dembrane.api.dependency_auth import DependencyDirectusSession + +router = APIRouter() +logger = getLogger("api.v2.project_sharing") + +_VALID_PROJECT_ROLES = {"viewer", "editor"} + + +# ── Response shapes ───────────────────────────────────────────────────── + + +class ProjectShareResponse(BaseModel): + user_id: str + email: str + display_name: str + avatar: Optional[str] = None + role: Literal["viewer", "editor"] + granted_by: Optional[str] = None + created_at: Optional[str] = None + + +class AddShareRequest(BaseModel): + email: EmailStr + role: Literal["viewer", "editor"] = "viewer" # D16: safest default + + +class ChangeShareRoleRequest(BaseModel): + role: Literal["viewer", "editor"] + + +# ── Helpers ───────────────────────────────────────────────────────────── + + +async def _get_project(project_id: str) -> dict: + project = await async_directus.get_item("project", project_id) + if not project or project.get("deleted_at"): + raise HTTPException(status_code=404, detail="Project not found") + return project + + +async def _require_share_admin( + project: dict, acting_app_user_id: str +) -> dict: + """Verify the caller has project:share on this project's workspace. + + project:share is admin-level and tier-gated at innovator+. Workspace + must also resolve access (derived or direct) for this user. + """ + workspace_id = project.get("workspace_id") + if not workspace_id: + raise HTTPException( + status_code=400, + detail="Project is not attached to a workspace", + ) + + resolved = await user_can_access(workspace_id, acting_app_user_id) + if resolved is None: + raise HTTPException(status_code=403, detail="No access to this project") + role, _ = resolved + + workspace = await async_directus.get_item("workspace", workspace_id) + if not workspace: + raise HTTPException(status_code=404, detail="Workspace not found") + + tier = workspace.get("tier", "pioneer") + if not has_policy(role, [], "project:share", workspace_tier=tier): + required_tier = TIER_REQUIRED_FOR_POLICY.get("project:share", "innovator") + if not meets_tier(tier, required_tier): + raise HTTPException( + status_code=403, + detail=( + f"Private project sharing requires the {required_tier} " + "plan or above." + ), + ) + raise HTTPException( + status_code=403, detail="Only workspace admins can share projects" + ) + return workspace + + +async def _enrich_member(row: dict) -> Optional[ProjectShareResponse]: + """Turn a project_membership row into the response shape by joining + app_user + directus_users for display_name/email/avatar.""" + uid = row.get("user_id") + if not uid: + return None + + app_user = await async_directus.get_item("app_user", uid) + if not app_user: + return None + + du_id = app_user.get("directus_user_id") + avatar = None + if du_id: + du_rows = await async_directus.get_users( + { + "query": { + "filter": {"id": {"_eq": du_id}}, + "fields": ["avatar"], + "limit": 1, + } + } + ) + if isinstance(du_rows, list) and du_rows: + avatar = du_rows[0].get("avatar") + + return ProjectShareResponse( + user_id=uid, + email=app_user.get("email") or "", + display_name=app_user.get("display_name") or "", + avatar=avatar, + role=row.get("role", "viewer"), + granted_by=row.get("granted_by"), + created_at=row.get("created_at"), + ) + + +# ── Endpoints ─────────────────────────────────────────────────────────── + + +@router.get("/{project_id}/members", response_model=list[ProjectShareResponse]) +async def list_project_shares( + project_id: str, + auth: DependencyDirectusSession, +) -> list[ProjectShareResponse]: + """List everyone the project is explicitly shared with. + + Anyone who can access the project can see its share list — matches how + people naturally understand "who else is in this room". + """ + app_user = await get_app_user_or_raise(auth.user_id) + project = await _get_project(project_id) + + # Anyone with workspace access can read. + workspace_id = project.get("workspace_id") + if workspace_id: + if not await user_can_access(workspace_id, app_user["id"]): + raise HTTPException(status_code=403, detail="No access to this project") + + rows = await async_directus.get_items( + "project_membership", + { + "query": { + "filter": {"project_id": {"_eq": project_id}}, + "fields": ["user_id", "role", "granted_by", "created_at"], + "limit": -1, + } + }, + ) or [] + if not isinstance(rows, list): + return [] + + out: list[ProjectShareResponse] = [] + for row in rows: + enriched = await _enrich_member(row) + if enriched: + out.append(enriched) + return out + + +@router.post("/{project_id}/members", response_model=ProjectShareResponse) +async def add_project_share( + project_id: str, + body: AddShareRequest, + auth: DependencyDirectusSession, +) -> ProjectShareResponse: + """Share a private project with a specific workspace member. + + Constraints (Ask 3 modal copy: "only people already in this workspace + — no cross-workspace sharing"): + 1. Project must be visibility='private'. + 2. Invitee must already be a workspace member (direct or derived). + 3. Caller must have project:share policy (admin role) AND tier + must meet innovator+. + """ + acting_user = await get_app_user_or_raise(auth.user_id) + project = await _get_project(project_id) + + if project.get("visibility") != "private": + raise HTTPException( + status_code=400, + detail=( + "This project is visible to the whole workspace. " + "Mark it private before adding individual shares." + ), + ) + + workspace = await _require_share_admin(project, acting_user["id"]) + + # Find the invitee's app_user (via email). + email = body.email.strip().lower() + app_users = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"email": {"_eq": email}}, + "fields": ["id"], + "limit": 1, + } + }, + ) + if not isinstance(app_users, list) or not app_users: + raise HTTPException( + status_code=404, + detail="That email isn't on this workspace. Invite them to the workspace first.", + ) + invitee_id = app_users[0]["id"] + + # Invitee must have workspace access. No cross-workspace sharing. + if not await user_can_access(workspace["id"], invitee_id): + raise HTTPException( + status_code=400, + detail="That person isn't in this workspace. Invite them first.", + ) + + # Upsert — if a share row exists, update role; else insert fresh. + existing = await async_directus.get_items( + "project_membership", + { + "query": { + "filter": { + "project_id": {"_eq": project_id}, + "user_id": {"_eq": invitee_id}, + }, + "fields": ["id", "role"], + "limit": 1, + } + }, + ) + if isinstance(existing, list) and existing: + await async_directus.update_item( + "project_membership", existing[0]["id"], {"role": body.role} + ) + logger.info( + f"Updated project {project_id} share for {invitee_id} to {body.role}" + ) + else: + await async_directus.create_item( + "project_membership", + { + "id": generate_uuid(), + "project_id": project_id, + "user_id": invitee_id, + "role": body.role, + "granted_by": acting_user["id"], + }, + ) + logger.info( + f"Added project {project_id} share: {invitee_id} as {body.role} " + f"by {acting_user['id']}" + ) + + enriched = await _enrich_member( + { + "user_id": invitee_id, + "role": body.role, + "granted_by": acting_user["id"], + } + ) + # _enrich_member only returns None when the app_user row vanishes — since + # we just verified it via email lookup, enriched is non-None here. + assert enriched is not None + return enriched + + +@router.patch("/{project_id}/members/{user_id}") +async def change_project_share_role( + project_id: str, + user_id: str, + body: ChangeShareRoleRequest, + auth: DependencyDirectusSession, +) -> dict: + acting_user = await get_app_user_or_raise(auth.user_id) + project = await _get_project(project_id) + await _require_share_admin(project, acting_user["id"]) + + rows = await async_directus.get_items( + "project_membership", + { + "query": { + "filter": { + "project_id": {"_eq": project_id}, + "user_id": {"_eq": user_id}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + if not isinstance(rows, list) or not rows: + raise HTTPException(status_code=404, detail="Share not found") + + await async_directus.update_item( + "project_membership", rows[0]["id"], {"role": body.role} + ) + logger.info( + f"Project {project_id} share role changed: {user_id} → {body.role} " + f"by {acting_user['id']}" + ) + return {"status": "updated", "role": body.role} + + +@router.delete("/{project_id}/members/{user_id}") +async def revoke_project_share( + project_id: str, + user_id: str, + auth: DependencyDirectusSession, +) -> dict: + """Revoke a project share. Hard-delete — the project_membership row + doesn't have a deleted_at field, and the designer's share modal has + no "undo remove" affordance. Re-grant is explicit via POST. + """ + acting_user = await get_app_user_or_raise(auth.user_id) + project = await _get_project(project_id) + await _require_share_admin(project, acting_user["id"]) + + rows = await async_directus.get_items( + "project_membership", + { + "query": { + "filter": { + "project_id": {"_eq": project_id}, + "user_id": {"_eq": user_id}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + if not isinstance(rows, list) or not rows: + raise HTTPException(status_code=404, detail="Share not found") + + await async_directus.delete_item("project_membership", rows[0]["id"]) + logger.info( + f"Revoked project {project_id} share for {user_id} by {acting_user['id']}" + ) + return {"status": "revoked"} From e66c4835d5047086a97d195d45d5181d53bc28e7 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 15:52:13 +0000 Subject: [PATCH 077/208] =?UTF-8?q?feat:=20onboarding=20split=20=E2=80=94?= =?UTF-8?q?=20differentiate=20new=20vs=20legacy=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: /v2/me now returns has_legacy_projects — true if the user owns projects with workspace_id IS NULL (created before workspaces existed). Computed via a one-row probe on /project, cheap enough on every call. Frontend: OnboardingRoute reads has_legacy_projects and splits copy three ways: - has_pending_invites → "Welcome, {name}. Your team is waiting." - has_legacy_projects → "Welcome back, {name}. We've added teams so you can organize projects…" (migration copy) - neither → "Welcome, {name}. Give your team a name." (fresh-setup copy, previously missing) Copy matches the designer's onboarding-fix section in designer-return.html. Legacy-only hint ("your projects are waiting") suppressed for new users so they don't see a claim that doesn't apply. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/frontend/src/hooks/useV2Me.ts | 7 ++++ .../src/routes/onboarding/OnboardingRoute.tsx | 35 +++++++++++++------ echo/server/dembrane/api/v2/me.py | 21 +++++++++++ echo/server/dembrane/api/v2/schemas.py | 5 +++ 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/echo/frontend/src/hooks/useV2Me.ts b/echo/frontend/src/hooks/useV2Me.ts index fd34dcc4..259062c7 100644 --- a/echo/frontend/src/hooks/useV2Me.ts +++ b/echo/frontend/src/hooks/useV2Me.ts @@ -10,6 +10,13 @@ export interface V2MeData { onboarding_completed: boolean; orgs: Array<{ id: string; name: string; role: string }>; has_pending_invites: boolean; + // Gates internal-only UI (workspace tier-set, future audit controls). + // Derived from Directus Administrator role / JWT admin_access claim. + is_staff: boolean; + // True if the user has projects from before workspaces existed. Drives + // the onboarding split: new users (false) see signup-time team name; + // legacy users (true) see the migration screen copy. + has_legacy_projects: boolean; } async function fetchV2Me(): Promise { diff --git a/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx b/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx index 662d8abb..af9dd4eb 100644 --- a/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx +++ b/echo/frontend/src/routes/onboarding/OnboardingRoute.tsx @@ -62,6 +62,11 @@ export const OnboardingRoute = () => { const displayName = (user.data as Record)?.first_name || ""; const hasInvites = meV2?.has_pending_invites === true; + // The designer's onboarding split (docs/workspaces/designer-return.html): + // users with projects from before workspaces existed see the "migration" + // copy; users with no legacy projects see the fresh-setup copy. hasInvites + // takes precedence over both (they're here to join a team, not set one up). + const isLegacyUser = meV2?.has_legacy_projects === true; const defaultOrgName = displayName ? `${displayName}'s Team` : ""; const [orgName, setOrgName] = useState(defaultOrgName); @@ -352,12 +357,12 @@ export const OnboardingRoute = () => { ) : ( Welcome to dembrane ) + ) : isLegacyUser && displayName ? ( + Welcome back, {displayName} ) : displayName ? ( - - Welcome back, {displayName} - + Welcome, {displayName} ) : ( - Set up your workspace + Set up your team )} @@ -365,10 +370,16 @@ export const OnboardingRoute = () => { Your team is waiting for you. Just one quick step to get set up. + ) : isLegacyUser ? ( + + We've added teams so you can organize projects and share them + with colleagues. Everything you had before is still here — we + just need a name for your team. + ) : ( - We added workspaces so you can organize projects and share - them with colleagues. Everything you had before is still here. + Give your team a name. You can invite teammates right after, + or later from settings. )} @@ -419,11 +430,13 @@ export const OnboardingRoute = () => { - - - You'll find all your projects waiting for you. - - + {isLegacyUser && ( + + + You'll find all your projects waiting for you. + + + )} diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 634c7082..63df308d 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -61,6 +61,25 @@ async def get_me(auth: DependencyDirectusSession) -> MeResponse: ) has_pending_invites = isinstance(pending, list) and len(pending) > 0 + # Does this user have projects from before workspaces existed? Drives + # the onboarding split: new users get signup-time team name, legacy + # users get the "we've added teams" migration screen. + legacy_probe = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "directus_user_id": {"_eq": auth.user_id}, + "workspace_id": {"_null": True}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + has_legacy_projects = isinstance(legacy_probe, list) and len(legacy_probe) > 0 + if not app_user: return MeResponse( directus_user_id=auth.user_id, @@ -69,6 +88,7 @@ async def get_me(auth: DependencyDirectusSession) -> MeResponse: avatar=directus_profile.get("avatar"), onboarding_completed=False, has_pending_invites=has_pending_invites, + has_legacy_projects=has_legacy_projects, is_staff=bool(auth.is_admin), ) @@ -118,6 +138,7 @@ async def get_me(auth: DependencyDirectusSession) -> MeResponse: onboarding_completed=True, orgs=orgs, has_pending_invites=has_pending_invites, + has_legacy_projects=has_legacy_projects, is_staff=bool(auth.is_admin), ) diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py index 837a0fcc..172ab238 100644 --- a/echo/server/dembrane/api/v2/schemas.py +++ b/echo/server/dembrane/api/v2/schemas.py @@ -32,6 +32,11 @@ class MeResponse(BaseModel): # Derived from Directus JWT `admin_access` claim (i.e. Administrator role # in Directus). Gates internal-only UI like workspace tier-set + audit. is_staff: bool = False + # True if the user has projects that predate workspaces (no workspace_id). + # Drives the onboarding split: new users (false) get a signup-time + # team-name field; existing users (true) get the "Welcome back, we've + # added teams" migration screen. Independent of onboarding_completed. + has_legacy_projects: bool = False # ── /v2/onboarding ── From 00613dd2d7d165228090b95145df0a68a20e0787 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 15:53:51 +0000 Subject: [PATCH 078/208] =?UTF-8?q?docs:=20status=20report=20for=20autonom?= =?UTF-8?q?ous=20S5=E2=80=93S14=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session-by-session status, commit list, judgment calls, TODOs, and the things I want Sameer's eyes on when he's back. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/docs/workspaces/release-checklist.md | 54 +++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/echo/docs/workspaces/release-checklist.md b/echo/docs/workspaces/release-checklist.md index 5d3ef17c..1c9cf2e3 100644 --- a/echo/docs/workspaces/release-checklist.md +++ b/echo/docs/workspaces/release-checklist.md @@ -320,3 +320,57 @@ Dependencies: S7 depends on S5; S8 depends on S6 tier PATCH; S9 depends on S6 ac ## Changelog for this doc - **2026-04-20**: initial master checklist; incorporated designer v2 directions; dropped migration script in favor of auto-onboarding + onboarding split; added internal tools / access-blocking track. + +## Session status — autonomous run 2026-04-20 (~2h) + +Commits landed on `workspaces` branch (not pushed): + +| # | Hash | Commit | +|---|---|---| +| 1 | 94cf40d | feat: derived inheritance module + tier auto-wire in has_policy | +| 2 | 818c774 | feat: surface is_staff in /v2/me | +| 3 | 9736a2c | feat: /v2/orgs endpoints for team management | +| 4 | (docs) | docs: workspaces release sources of truth | +| 5 | 4141262 | feat: tier mgmt, upgrade-request, delete workspace, downgrade effects | +| 6 | e26d725 | refactor: shared email layout + auto plain-text fallbacks | +| 7 | c51a7e0 | feat: private project sharing API — /v2/projects/:id/members CRUD | +| 8 | e66c483 | feat: onboarding split — differentiate new vs legacy users | + +### What landed + +- **S5 Orgs + staff** `[██████████] 100%` — `dembrane/inheritance.py` (derived resolvers + helpers), `/v2/orgs` CRUD + members, `is_staff` in `/v2/me`, middleware delegates to `user_can_access`, workspace creation no longer fans out inherited rows. +- **S6 Access rules + tier + delete + downgrade** `[█████████░] 95%` — `has_policy()` auto-enforces tier gates via `TIER_REQUIRED_FOR_POLICY`; `PATCH /v2/workspaces/:id/tier` (staff-only, reason required); `POST /v2/workspaces/:id/upgrade-request` (admin-only, configurable inbox via `UPGRADE_REQUEST_INBOX`); `DELETE /v2/workspaces/:id` (owner-only, blocked if projects); `dembrane/tier_downgrade.py` with `DOWNGRADE_EFFECTS` map + `preview_downgrade()` / `apply_downgrade_effects()`; privacy flags (`inherit_team_admins` / `_members`) accepted on create + patch. +- **S10 Private project sharing API** `[████░░░░░░] 40%` — backend complete (`/v2/projects/:id/members` CRUD, innovator+ gated, no cross-workspace). Frontend UI (strip + modal) blocked on design wires. +- **S13 Onboarding split** `[████████░░] 80%` — backend flag `has_legacy_projects` on `/v2/me`; `OnboardingRoute` copy splits three ways (invite / legacy / new). Hasn't been visually verified in a browser — the copy changes are additive and don't touch layout. +- **S14 Emails** `[███████░░░] 70%` — shared `_layout.html` partial, brand-compliant; existing `workspace_invite` + `workspace_added` refactored to extend; plain-text `.txt` fallbacks auto-picked by `send_email`; multipart/alternative wiring correct (plain first, html second). New templates (welcome, role-changed, removed) deferred until their endpoints need them. + +### What I did NOT touch (needs design wires before I can start) + +- **S7 Teams admin page** (Ask 1 list ⇄ matrix ⇄ projects) — backend (`/v2/orgs/:id/members`) is ready, UI not. +- **S8 Tier management UI** (Ask 2 compare matrix + Ask 2s staff inline block) — backend ready. +- **S9 Creation wizards** (workspace + project, full multi-step with dry-run preview) — backend accepts the flags; UI blocked on wizard wires. +- **S10 frontend** — the strip on project overview + the share modal. +- **S11 Upgrade prompts** — backend gates fire correctly; UI component (hatched overlay + 4C modal, member-role = no CTA) blocked on wires. +- **S12 Selector polish** — designer has wires; retargeting needs attention to Ask 5's team hero + per-row manage. Not touched here. + +### Judgment calls made while you were out + +Tracked as `# Note:` comments in code at the decision site. Summary: +- Tier-gate auto-wire: added `workspace_tier` kwarg to `has_policy()` and resolve via `TIER_REQUIRED_FOR_POLICY`. Made `policies.py:24-25` include `workspace:set_private` + `project:set_private` so privacy is gated uniformly (matches designer's S1 assumption). +- Team-invite endpoint (`POST /v2/orgs/:id/members`) deliberately not built — reused the existing workspace-invite flow with `include_org_membership=true` targeting the team's default workspace. Saves a collection + keeps the invite email template single-source. +- `accessible_workspace_count` rollup in `/v2/orgs/:id/members` uses `user_can_access` per (user × workspace) pair — O(U × W) round-trips per list call. Fine at current scale; noted a TODO in-line to batch when a team grows past ~50 workspaces. +- Downgrade-effect: `revert` currently only clears `logo_url`. Any new tier-gated feature marked `"revert"` must add its clear-on-downgrade branch to `apply_downgrade_effects()`. +- Email multipart: SendGrid `.add_content()` requires plain-first, html-second order. Wired that way; `_build_message` verified via a unit-style sanity check. +- Onboarding split terminology: used "team" in user-facing copy (`Welcome, {name} · Set up your team`). "Team admin" / "admins follow team access" phrasing is consistent with Q6's accepted rename, though the global sweep across older docs hasn't happened yet. + +### Things I want your eyes on + +1. **Run the local stack and hit the new endpoints** — I didn't start the devcontainer to curl them. The code compiles and imports cleanly; behavior-testing is yours. +2. **Q6 terminology sweep** — I used "follow team access" phrasing in new code + comments. Older docs (`inheritance-rules.md`, `workspaces-prd-v3-final.md`) still say "inherit". Low-risk but inconsistent until swept. +3. **Legacy inherited rows migration** — `inheritance-rules.md` specifies a cleanup pass for any `source='inherited'` rows that might exist from older code paths. I didn't run it because nothing today writes those rows anymore (the fan-out code in `POST /v2/workspaces` is gone). If any legacy rows linger they'll just be ignored by derivation. Easy to script later if you want them archived. +4. **Frontend onboarding split** — browser-verify the three copy branches render correctly. Code compiles; visual verification is TODO. + +### Still blocked on you / workshop + +- 12 questions in "Questions for the team (workshop)" section above — all of them still open. +- Design wires for S7/S8/S9/S10-frontend/S11/S12 — the designer's v5 is the dependency. From 15c7d1acc274ca956c46823441d5e35ecc3222a7 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 19:13:15 +0000 Subject: [PATCH 079/208] fix(inheritance): onboarding writes creator as source='direct' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Derived-inheritance invariant #5 (docs/workspaces/inheritance-rules.md) forbids any new row with source='inherited'. Onboarding's personal- workspace creation was still inserting the creator with source='inherited' — now 'direct' to match the invariant and avoid accumulating legacy rows every time a new user onboards. Found in red-team audit 2026-04-20. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/onboarding.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py index 2d024c12..90e856a6 100644 --- a/echo/server/dembrane/api/v2/onboarding.py +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -253,12 +253,16 @@ async def complete_onboarding( "tier": "pioneer", "created_by": app_user_id, }) + # Creator is stored as source='direct' per the derived-model + # invariant (see docs/workspaces/inheritance-rules.md §5). Team + # admins never get materialized rows — their access is derived + # from org_membership + workspace.settings. await async_directus.create_item("workspace_membership", { "id": generate_uuid(), "workspace_id": personal_ws_id, "user_id": app_user_id, "role": "owner", - "source": "inherited", + "source": "direct", }) logger.info(f"Created default workspace {personal_ws_id} for org {org_id}") From 2f543ac26521e07d4c44f3ee31695b3e77bb17d2 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Mon, 20 Apr 2026 19:13:28 +0000 Subject: [PATCH 080/208] fix(security): upgrade-request uses Jinja template + rate limit + subject strip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Red-team + security audit caught three issues on the upgrade-request flow: C1 (stored XSS): body_html was built via f-string with unescaped workspace_name, org_name, requester_name, and message. Sending "Acme " as a workspace name would render in the billing inbox webmail preview. Fixed by rendering through the autoescaping Jinja env via new upgrade_request.html/.txt templates. H2 (spam amplifier): no rate limit on the endpoint — any admin could flood the billing inbox. Added a per-user limiter (5/hr) using the existing create_user_rate_limiter pattern (same as workspace_invite). Security 2 (header injection): workspace_name and org_name flowed into the Subject line without CR/LF stripping. Added _strip_header_unsafe so SMTP relays can't see injected headers even if a rename endpoint's strip() misses an internal newline. Template autoescape is verified with a direct render of " + + + + + + + + \ No newline at end of file diff --git a/echo/frontend/src/components/project/ProjectListItem.tsx b/echo/frontend/src/components/project/ProjectListItem.tsx index 70fbc4d1..40d5a940 100644 --- a/echo/frontend/src/components/project/ProjectListItem.tsx +++ b/echo/frontend/src/components/project/ProjectListItem.tsx @@ -166,6 +166,10 @@ export const ProjectListItem = ({ {(ownerName || ownerEmail) && ( <> {" • "} + {/* Show name by default; email only on hover via tooltip. + Matches the "don't display emails by default in lists" + rule from CLAUDE.md + brand style guide. Falls back to + email when the owner has no display_name (rare). */} - {ownerName ?? ownerEmail} + {ownerName || t`Unknown`} diff --git a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx index 58634e58..ad55b5a6 100644 --- a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx +++ b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx @@ -1,9 +1,12 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { + Alert, Button, + Center, Container, Group, + Loader, Select, Stack, Text, @@ -12,15 +15,22 @@ import { } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { useSearchParams } from "react-router"; import { toast } from "@/components/common/Toaster"; import { API_BASE_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { useV2Me } from "@/hooks/useV2Me"; import { useWorkspace } from "@/hooks/useWorkspace"; -async function createWorkspace(name: string, tier: string) { +async function createWorkspace(payload: { + name: string; + org_id?: string; + inherit_team_admins?: boolean; + inherit_team_members?: boolean; +}) { const res = await fetch(`${API_BASE_URL}/v2/workspaces`, { - body: JSON.stringify({ name, tier }), + body: JSON.stringify(payload), credentials: "include", headers: { "Content-Type": "application/json" }, method: "POST", @@ -32,20 +42,64 @@ async function createWorkspace(name: string, tier: string) { return res.json(); } +/** + * Create-workspace form. Entered from the dashed "+ Add workspace" card + * on the selector, which passes `?teamId=` so the form knows which + * team to create into. Without teamId the form falls back to the user's + * primary admin team and asks them to confirm. + * + * Pre-checks before rendering the form: + * - Not onboarded → redirect to /onboarding. Matches the user's ask: + * "don't dump 'No team found. Complete onboarding first.' as an + * error — route me there first." + * - Onboarded but admin/owner on no team → render a friendly "Ask + * your team admin" state. Members genuinely can't create workspaces. + */ export const CreateWorkspaceRoute = () => { const navigate = useI18nNavigate(); const queryClient = useQueryClient(); const { setWorkspace } = useWorkspace(); + const [searchParams] = useSearchParams(); + const teamIdFromQuery = searchParams.get("teamId") ?? null; + const { data: meV2, isLoading: meLoading } = useV2Me(); const [name, setName] = useState(""); - const [tier, setTier] = useState("pioneer"); + const [privacy, setPrivacy] = useState<"open" | "private">("open"); + const [includeMembers, setIncludeMembers] = useState(false); useDocumentTitle(t`New workspace | dembrane`); + // Teams where the caller has permission to create workspaces. + const adminTeams = useMemo( + () => + (meV2?.orgs ?? []).filter( + (o) => o.role === "owner" || o.role === "admin", + ), + [meV2], + ); + + // Redirect unonboarded users to onboarding instead of showing a 403. + useEffect(() => { + if (meLoading) return; + if (meV2 && meV2.onboarding_completed === false) { + navigate("/onboarding"); + } + }, [meLoading, meV2, navigate]); + + const targetTeamId = teamIdFromQuery || adminTeams[0]?.id || null; + const targetTeam = adminTeams.find((o) => o.id === targetTeamId) ?? null; + const mutation = useMutation({ - mutationFn: () => createWorkspace(name.trim(), tier), + mutationFn: () => + createWorkspace({ + name: name.trim(), + org_id: targetTeamId ?? undefined, + inherit_team_admins: privacy === "open", + inherit_team_members: privacy === "open" ? includeMembers : false, + }), onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["v2", "workspaces"] }); queryClient.invalidateQueries({ queryKey: ["v2", "workspaces-context"] }); + queryClient.invalidateQueries({ queryKey: ["v2", "team"] }); setWorkspace(data.id); toast.success(t`Workspace created`); navigate(`/w/${data.id}/projects`); @@ -55,73 +109,163 @@ export const CreateWorkspaceRoute = () => { }, }); - return ( + if (meLoading || meV2?.onboarding_completed === false) { + return ( +
    + +
    + ); + } + + // Onboarded, but not admin on any team. The backend would 403 — + // tell the user what's actually wrong instead of error-toasting. + if (adminTeams.length === 0) { + return ( - - - - <Trans>New workspace</Trans> - + + + <Trans>You can't create a workspace yet</Trans> + + + + Only team admins and owners can create workspaces. Ask an admin + on your team to create one, or ask them to promote you first. + + + + + + + + ); + } + + return ( + + + + + <Trans>New workspace</Trans> + + {targetTeam && ( - Workspaces hold projects for a specific client or purpose. - Team admins automatically get access. + Creating in {targetTeam.name} - + )} + + + Workspaces hold projects for a specific client or purpose. + + + -
    { - e.preventDefault(); - if (!name.trim()) return; - mutation.mutate(); - }} - > - - setName(e.currentTarget.value)} - /> + {teamIdFromQuery && !targetTeam && ( + + + You don't have permission to create workspaces in that team. + Falling back to your primary team instead. + + + )} + + { + e.preventDefault(); + if (!name.trim()) return; + mutation.mutate(); + }} + > + + setName(e.currentTarget.value)} + /> + {/* Team picker only shows when the user has multiple admin + teams and no ?teamId was supplied. Keeps the common + single-team path friction-free. */} + {!teamIdFromQuery && adminTeams.length > 1 && ( + setIncludeMembers(e.currentTarget.checked) + } + style={{ marginTop: 2 }} + /> + - Create workspace - + Also give team members access (not just admins) + - - -
    -
    + )} + + + + + + + + + ); }; diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx index 1e1d05eb..e31d24af 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx @@ -188,6 +188,58 @@ function WorkspaceCard({ ); } +/** + * Dashed placeholder card that lives at the end of each team's workspace + * grid for admins/owners. Clicking it navigates to /w/new?teamId= + * so the create form knows which team to create inside. + * + * Solves the "create workspace BUT WHERE?" ambiguity — the card sits + * physically under the team it belongs to, no confusion. + */ +function AddWorkspaceCard({ teamId }: { teamId: string }) { + const navigate = useI18nNavigate(); + return ( + { + e.currentTarget.style.borderColor = "var(--mantine-color-blue-5)"; + e.currentTarget.style.background = "rgba(65,105,225,0.03)"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.borderColor = "var(--mantine-color-gray-4)"; + e.currentTarget.style.background = "transparent"; + }} + onClick={() => navigate(`/w/new?teamId=${teamId}`)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + navigate(`/w/new?teamId=${teamId}`); + } + }} + > + + + + Add workspace + + + + ); +} + function TeamHeroCard({ team, onManage, @@ -297,19 +349,12 @@ export const WorkspaceSelectorRoute = () => { return ( - {/* Header */} - - - <Trans>Workspaces</Trans> - - - + {/* Header — create-workspace is NOT up here. We put + "Add workspace" dashed cards inside each team's grid + so the placement answers "create where?" by itself. */} + + <Trans>Workspaces</Trans> + {/* Search (show when >3 workspaces) */} {workspaces.length > 3 && ( @@ -346,9 +391,16 @@ export const WorkspaceSelectorRoute = () => { key={ws.id} workspace={ws} onSelect={() => handleSelect(ws)} - onManage={() => navigate(`/workspaces/${ws.id}/settings`)} + onManage={() => navigate(`/w/${ws.id}/settings`)} /> ))} + {/* Dashed "+ new workspace" placeholder — admin/ + owner only. Lives under the team it belongs + to so create-where is answered by placement. */} + {(group.role === "owner" || + group.role === "admin") && ( + + )} ); diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py index 90e856a6..9be46a70 100644 --- a/echo/server/dembrane/api/v2/onboarding.py +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -253,17 +253,17 @@ async def complete_onboarding( "tier": "pioneer", "created_by": app_user_id, }) - # Creator is stored as source='direct' per the derived-model - # invariant (see docs/workspaces/inheritance-rules.md §5). Team - # admins never get materialized rows — their access is derived - # from org_membership + workspace.settings. - await async_directus.create_item("workspace_membership", { - "id": generate_uuid(), - "workspace_id": personal_ws_id, - "user_id": app_user_id, - "role": "owner", - "source": "direct", - }) + # Delegate the settings seed + creator membership to the same + # helper POST /v2/workspaces uses. Keeps the two creation paths + # producing identical rows (previously drifted: onboarding left + # settings NULL and skipped the inherit_team_* flags). + from dembrane.inheritance import on_workspace_created + await on_workspace_created( + workspace_id=personal_ws_id, + creator_app_user_id=app_user_id, + inherit_team_admins=True, + inherit_team_members=False, + ) logger.info(f"Created default workspace {personal_ws_id} for org {org_id}") # Move user's orphaned projects into the personal workspace diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index cc8879d6..c75e28a1 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -352,9 +352,14 @@ async def list_org_members( """Team members list. Anyone in the team can read this (members need to see who their admins are, per Q3 decision: we don't build an "ask an admin" CTA, but members should still be able to find them). + + Email redaction: mirrors the workspace-settings pattern — only team + admins/owners see the full email. Members see display_name only. + A member always sees their own email (self-row). """ app_user = await get_app_user_or_raise(auth.user_id) - await _require_org_role(org_id, app_user["id"], minimum="member") + caller_role = await _require_org_role(org_id, app_user["id"], minimum="member") + can_manage = caller_role in ("admin", "owner") memberships = await async_directus.get_items( "org_membership", @@ -420,11 +425,13 @@ async def list_org_members( uid = m["user_id"] app_row = app_user_map.get(uid) or {} du_id = app_row.get("directus_user_id") or "" + is_self = uid == app_user["id"] + show_email = can_manage or is_self out.append( OrgMemberResponse( user_id=uid, app_user_id=uid, - email=app_row.get("email") or "", + email=(app_row.get("email") or "") if show_email else "", display_name=app_row.get("display_name") or "", avatar=avatar_map.get(du_id) if du_id else None, role=m.get("role", "member"), @@ -495,7 +502,8 @@ async def list_team_workspaces( joined directly. Anyone in the team can read this. """ app_user = await get_app_user_or_raise(auth.user_id) - await _require_org_role(org_id, app_user["id"], minimum="member") + caller_role = await _require_org_role(org_id, app_user["id"], minimum="member") + caller_is_manager = caller_role in ("admin", "owner") # Pull settings.inherit_team_admins explicitly (sub-field projection) # so we don't need to send the whole JSON (also avoids accidentally @@ -572,10 +580,16 @@ async def list_team_workspaces( if wid: member_counts[wid] = cnt + # Hide private workspaces from non-admin team members — the whole + # point of a private workspace is that team admins can't see it, + # advertising its name + tier in a team-scoped list contradicts that. + # Admins/owners still see the full roster (they're the audience). out: list[OrgWorkspaceSummary] = [] for ws in workspaces: settings = ws.get("settings") if isinstance(ws.get("settings"), dict) else {} is_private = (settings or {}).get("inherit_team_admins") is False + if is_private and not caller_is_manager: + continue out.append( OrgWorkspaceSummary( id=ws["id"], diff --git a/echo/server/dembrane/api/v2/projects.py b/echo/server/dembrane/api/v2/projects.py index 4bbf8c73..ffe9fa18 100644 --- a/echo/server/dembrane/api/v2/projects.py +++ b/echo/server/dembrane/api/v2/projects.py @@ -99,34 +99,32 @@ async def move_project( if project.get("directus_user_id") != auth.user_id: raise HTTPException(status_code=403, detail="Not the owner of this project") - # Check source workspace access + # Source + target access must both resolve as admin/owner. Using + # user_can_access honors derived inheritance — a team owner who has + # no direct workspace row still legitimately administers the + # workspace. The previous raw workspace_membership lookup 403'd them + # incorrectly. (Audit round 2026-04-21, HIGH.) if source_workspace_id: - source_membership = await async_directus.get_items( - "workspace_membership", - {"query": {"filter": { - "workspace_id": {"_eq": source_workspace_id}, - "user_id": {"_eq": app_user_id}, - "deleted_at": {"_null": True}, - }, "limit": 1}}, + src = await user_can_access(source_workspace_id, app_user_id) + if src is None: + raise HTTPException( + status_code=403, detail="No access to source workspace" + ) + if src[0] not in ("admin", "owner"): + raise HTTPException( + status_code=403, + detail="Must be admin or owner of source workspace", + ) + + tgt = await user_can_access(target_workspace_id, app_user_id) + if tgt is None: + raise HTTPException( + status_code=403, detail="No access to target workspace" + ) + if tgt[0] not in ("admin", "owner"): + raise HTTPException( + status_code=403, detail="Must be admin or owner of target workspace" ) - if not isinstance(source_membership, list) or len(source_membership) == 0: - raise HTTPException(status_code=403, detail="No access to source workspace") - if source_membership[0].get("role", "") not in ("admin", "owner"): - raise HTTPException(status_code=403, detail="Must be admin or owner of source workspace") - - # Check target workspace access - target_membership = await async_directus.get_items( - "workspace_membership", - {"query": {"filter": { - "workspace_id": {"_eq": target_workspace_id}, - "user_id": {"_eq": app_user_id}, - "deleted_at": {"_null": True}, - }, "limit": 1}}, - ) - if not isinstance(target_membership, list) or len(target_membership) == 0: - raise HTTPException(status_code=403, detail="No access to target workspace") - if target_membership[0].get("role", "") not in ("admin", "owner"): - raise HTTPException(status_code=403, detail="Must be admin or owner of target workspace") target_workspace = await async_directus.get_item("workspace", target_workspace_id) if not target_workspace or target_workspace.get("deleted_at"): diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index c1f1e5da..0521e129 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -113,24 +113,20 @@ async def _get_workspace_usage(ws_id: str) -> WorkspaceUsage: async def _get_member_previews(ws_id: str) -> list[MemberPreview]: - """Get first 4 member avatars for a workspace.""" - memberships = await async_directus.get_items( - "workspace_membership", - { - "query": { - "filter": { - "workspace_id": {"_eq": ws_id}, - "deleted_at": {"_null": True}, - }, - "fields": ["user_id"], - "limit": 4, - } - }, - ) - if not isinstance(memberships, list) or len(memberships) == 0: - return [] + """Get first 4 member avatars for a workspace. + + Uses get_effective_members so derived team admins are represented. + Raw workspace_membership reads would show only direct rows and lie + about who's on open workspaces. (Audit round 2026-04-21, MEDIUM.) + """ + from dembrane.inheritance import get_effective_members - user_ids = [m["user_id"] for m in memberships if m.get("user_id")] + members = await get_effective_members(ws_id) + if not members: + return [] + # Direct rows bubble to the top of get_effective_members.sort() so + # previews are deterministic and prioritise explicit membership. + user_ids = [m["user_id"] for m in members[:4] if m.get("user_id")] if not user_ids: return [] From b71a2d72e7a949c3dcc6d843d34ac185138f5fd8 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Tue, 21 Apr 2026 11:30:47 +0000 Subject: [PATCH 094/208] feat(inbox): notifications table + service + emit backfill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New in-app notifications system. Mirrors the announcement trio (`notification` + `notification_translations` + `notification_activity`) adding targeting fields for per-user push events: `audience_user_id`, `actor_user_id`, `event_code`, `action` enum (NAVIGATE_WS / NAVIGATE_PROJECT / NAVIGATE_REPORT / NAVIGATE_CHAT / NAVIGATE_INVITE / NAVIGATE_TEAM_SETTINGS / NAVIGATE_WORKSPACE_SETTINGS / NONE), and nullable FKs for ref_org_id / workspace_id / project_id / chat_id / report_id / conversation_id / invite_id. Schema - scripts/create_schema.py step 9 — creates the three collections, all FKs, translations + activity O2M aliases. Idempotent. Dry-run gate: --step 9 runs only this. Service - dembrane/notifications.py — one `emit(...)` function that creates the notification row + an English translation + the pre-filled activity row (audience is known, so unread count stays a cheap aggregate). Never raises; swallows failures at logger.warning so a broken notification can't fail the parent action. - `emit_to_audience(audience_user_ids, ...)` fans the same payload out, auto-skipping the actor so "you accepted your own invite" doesn't loop back. - Audience helpers: audience_workspace_admins, audience_workspace_ members, audience_team_admins, audience_team. Route through inheritance.get_effective_members so derived team admins are reached without fan-out storage. Channel note (see module docstring): storage is channel-agnostic on purpose. Email digests and Slack bridges will read from this table rather than own their own pipelines. Don't add per-channel booleans to the notification row — preferences live in the delivery layer. BFF endpoints: /v2/me/notifications - GET list (most recent first, expired rows filtered) - GET /unread-count (cheap aggregate for the inbox badge) - POST /:id/read (verifies audience before flipping) - POST /read-all (caps at 500 rows) Emit backfill — wired into the high-traffic action paths: - invites.invite_to_workspace: WORKSPACE_ADDED on direct add - workspace_settings.change_member_role: WORKSPACE_ROLE_CHANGED - workspace_settings.remove_workspace_member: WORKSPACE_REMOVED - project_sharing.add_project_share: PROJECT_SHARE_ADDED - orgs.change_member_role: TEAM_ROLE_CHANGED - orgs.remove_team_member: TEAM_REMOVED Remaining emit sites (invite accepted, visibility changed, tier changed, report ready, etc.) land in a follow-up pass — the explorer catalogued all 27. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/scripts/create_schema.py | 182 ++++++++ echo/server/dembrane/api/v2/__init__.py | 4 + echo/server/dembrane/api/v2/invites.py | 18 + echo/server/dembrane/api/v2/notifications.py | 388 ++++++++++++++++++ echo/server/dembrane/api/v2/orgs.py | 36 ++ .../server/dembrane/api/v2/project_sharing.py | 20 + .../dembrane/api/v2/workspace_settings.py | 28 +- echo/server/dembrane/notifications.py | 229 +++++++++++ 8 files changed, 904 insertions(+), 1 deletion(-) create mode 100644 echo/server/dembrane/api/v2/notifications.py create mode 100644 echo/server/dembrane/notifications.py diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py index 9f9f58fe..8c20063a 100644 --- a/echo/scripts/create_schema.py +++ b/echo/scripts/create_schema.py @@ -829,6 +829,187 @@ def step_8_remove_chat(): return False +# --------------------------------------------------------------------------- +# Step 9: Notifications (inbox) — mirrors the announcement trio +# --------------------------------------------------------------------------- + +def step_9_notifications(): + """Per-user notifications. Follows the announcement pattern + (parent + translations + activity) and adds targeting fields + (audience_user_id, action enum, ref_* nullable FKs). + + Note on channels: this is the canonical in-app store. A future + delivery layer can read from it to ship email or Slack; we don't + split the storage per channel. Inbox UI, email digests, and Slack + webhooks all flow through the same rows. (See the sibling comment + in dembrane/notifications.py service module.) + """ + print("\n=== Step 9: notification + notification_translations + notification_activity ===") + + notification_fields = [ + pk_uuid(), + { + "field": "audience_user_id", "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True, + "note": "FK to app_user — the recipient."}, + }, + { + "field": "actor_user_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", + "note": "FK to app_user — who triggered the event."}, + }, + { + "field": "event_code", "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True, + "note": "Machine enum. INVITE_CREATED, ROLE_CHANGED, SHARE_ADDED, REPORT_READY, etc."}, + }, + { + "field": "action", "type": "string", + "schema": {"is_nullable": False, "default_value": "NONE"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "None", "value": "NONE"}, + {"text": "Navigate to workspace", "value": "NAVIGATE_WS"}, + {"text": "Navigate to project", "value": "NAVIGATE_PROJECT"}, + {"text": "Navigate to report", "value": "NAVIGATE_REPORT"}, + {"text": "Navigate to chat", "value": "NAVIGATE_CHAT"}, + {"text": "Navigate to invite", "value": "NAVIGATE_INVITE"}, + {"text": "Navigate to team settings", "value": "NAVIGATE_TEAM_SETTINGS"}, + {"text": "Navigate to workspace settings", "value": "NAVIGATE_WORKSPACE_SETTINGS"}, + ]}, + "note": "Codified nav target. UI resolves the URL from ref_* fields.", + }, + }, + {"field": "ref_org_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + {"field": "ref_workspace_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + {"field": "ref_project_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + {"field": "ref_chat_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + {"field": "ref_report_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + {"field": "ref_conversation_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + {"field": "ref_invite_id", "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input"}}, + { + "field": "level", "type": "string", + "schema": {"is_nullable": False, "default_value": "info"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Info", "value": "info"}, + {"text": "Urgent", "value": "urgent"}, + ]}, + }, + }, + {"field": "expires_at", "type": "timestamp", + "schema": {"is_nullable": True}, + "meta": {"interface": "datetime", + "note": "Hide from inbox after this timestamp."}}, + {"field": "translations", "type": "alias", + "meta": {"interface": "translations", "special": ["translations"]}}, + {"field": "activity", "type": "alias", + "meta": {"interface": "list-o2m", "special": ["o2m"]}}, + {"field": "created_at", **timestamp_created()}, + {"field": "updated_at", **timestamp_updated()}, + ] + if not create_collection("notification", notification_fields, { + "accountability": "all", + "display_template": "{{event_code}} → {{audience_user_id}}", + }): + return False + + create_relation("notification", "audience_user_id", "app_user", + schema={"on_delete": "CASCADE"}) + create_relation("notification", "actor_user_id", "app_user", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_org_id", "org", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_workspace_id", "workspace", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_project_id", "project", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_chat_id", "project_chat", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_report_id", "project_report", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_conversation_id", "conversation", + schema={"on_delete": "SET NULL"}) + create_relation("notification", "ref_invite_id", "workspace_invite", + schema={"on_delete": "SET NULL"}) + + # notification_translations + nt_fields = [ + pk_uuid(), + {"field": "notification_id", "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}}, + {"field": "languages_code", "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}}, + {"field": "title", "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}}, + {"field": "message", "type": "text", + "schema": {"is_nullable": True}, + "meta": {"interface": "input-multiline", + "note": "Markdown allowed."}}, + ] + if not create_collection("notification_translations", nt_fields, { + "accountability": "all", + }): + return False + create_relation("notification_translations", "notification_id", "notification", + meta={"one_field": "translations"}, + schema={"on_delete": "CASCADE"}) + create_relation("notification_translations", "languages_code", "languages", + schema={"on_delete": "NO ACTION"}) + + # notification_activity — per-user read state. Mirrors announcement_activity + # exactly so the inbox drawer can render both with one component. Rows + # are pre-created on emit (one per notification — audience is known) so + # unread counts are a simple aggregate. + na_fields = [ + pk_uuid(), + {"field": "notification_id", "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True}}, + {"field": "user_id", "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "special": ["user-created"], + "required": True, + "note": "FK to directus_users (matches announcement_activity)."}}, + {"field": "read", "type": "boolean", + "schema": {"is_nullable": False, "default_value": False}, + "meta": {"interface": "boolean"}}, + {"field": "created_at", **timestamp_created()}, + {"field": "updated_at", **timestamp_updated()}, + ] + if not create_collection("notification_activity", na_fields, { + "accountability": "all", + }): + return False + create_relation("notification_activity", "notification_id", "notification", + meta={"one_field": "activity"}, + schema={"on_delete": "CASCADE"}) + + return True + + # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- @@ -842,6 +1023,7 @@ def step_8_remove_chat(): "6": ("project fields (workspace_id, visibility, deleted_at)", step_6_project_fields), "7": ("deleted_at on conversation, project_chat, project_report", step_7_deleted_at), "8": ("remove legacy chat", step_8_remove_chat), + "9": ("notifications trio (inbox)", step_9_notifications), } diff --git a/echo/server/dembrane/api/v2/__init__.py b/echo/server/dembrane/api/v2/__init__.py index 9b2c1d9b..865fc8ca 100644 --- a/echo/server/dembrane/api/v2/__init__.py +++ b/echo/server/dembrane/api/v2/__init__.py @@ -10,6 +10,7 @@ from fastapi import APIRouter from dembrane.api.v2.me import router as me_router +from dembrane.api.v2.notifications import router as notifications_router from dembrane.api.v2.orgs import router as orgs_router from dembrane.api.v2.onboarding import router as onboarding_router from dembrane.api.v2.invites import router as invites_router @@ -22,6 +23,9 @@ v2_router = APIRouter() v2_router.include_router(me_router, prefix="/me", tags=["v2:me"]) +v2_router.include_router( + notifications_router, prefix="/me/notifications", tags=["v2:notifications"] +) v2_router.include_router(onboarding_router, prefix="/onboarding", tags=["v2:onboarding"]) # Team (org) management — user-facing word is "team", internal is "org" (see D1). diff --git a/echo/server/dembrane/api/v2/invites.py b/echo/server/dembrane/api/v2/invites.py index d934b1c4..c5dd94f6 100644 --- a/echo/server/dembrane/api/v2/invites.py +++ b/echo/server/dembrane/api/v2/invites.py @@ -174,6 +174,24 @@ async def invite_to_workspace( f"(external: {is_external}) by {ctx.app_user_id}" ) + # Notify the invitee in-app so they see the new workspace + # on their next page load without having to wait for the + # email to land. + from dembrane.notifications import emit + await emit( + audience_user_id=app_user["id"], + actor_user_id=ctx.app_user_id, + event_code="WORKSPACE_ADDED", + title=f"You're in {ctx.workspace.get('name', 'a workspace')}", + message=( + f"You were added to **{ctx.workspace.get('name', '')}** " + f"as {role}." + ), + action="NAVIGATE_WS", + ref_workspace_id=workspace_id, + ref_org_id=ws_org_id, + ) + # Send a notification email inviter_name = "Your team" try: diff --git a/echo/server/dembrane/api/v2/notifications.py b/echo/server/dembrane/api/v2/notifications.py new file mode 100644 index 00000000..6820ceaa --- /dev/null +++ b/echo/server/dembrane/api/v2/notifications.py @@ -0,0 +1,388 @@ +"""GET /v2/me/notifications + POST /v2/me/notifications/:id/read. + +Inbox BFF. Wraps the `notification` collection so the frontend drawer +can render both announcements and personal notifications with one +component — same shape, same mark-read semantics. + +Lives under /v2/me because every row is user-scoped. The underlying +notification table supports cross-team / system notifications; the BFF +restricts reads to the caller's own rows (audience_user_id = me). +""" + +from __future__ import annotations + +from datetime import datetime, timezone +from logging import getLogger +from typing import Optional + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +from dembrane.app_user import get_app_user_or_raise +from dembrane.directus_async import async_directus +from dembrane.api.dependency_auth import DependencyDirectusSession + +router = APIRouter() +logger = getLogger("api.v2.notifications") + + +class NotificationTranslation(BaseModel): + languages_code: str + title: str + message: Optional[str] = None + + +class NotificationRefs(BaseModel): + org_id: Optional[str] = None + workspace_id: Optional[str] = None + project_id: Optional[str] = None + chat_id: Optional[str] = None + report_id: Optional[str] = None + conversation_id: Optional[str] = None + invite_id: Optional[str] = None + + +class NotificationRow(BaseModel): + id: str + event_code: str + action: str # enum from NotificationAction + level: str # info | urgent + created_at: Optional[str] = None + expires_at: Optional[str] = None + read: bool = False + actor_user_id: Optional[str] = None + actor_name: Optional[str] = None + actor_avatar: Optional[str] = None + refs: NotificationRefs = NotificationRefs() + translation: Optional[NotificationTranslation] = None + + +@router.get("", response_model=list[NotificationRow]) +async def list_notifications( + auth: DependencyDirectusSession, + unread_only: bool = False, + limit: int = 50, +) -> list[NotificationRow]: + """User's own notifications, most recent first. + + Expired rows (`expires_at < now`) are filtered server-side so the + client doesn't have to handle stale items. + """ + app_user = await get_app_user_or_raise(auth.user_id) + now_iso = datetime.now(timezone.utc).isoformat() + + notif_filter: dict = { + "audience_user_id": {"_eq": app_user["id"]}, + "_or": [ + {"expires_at": {"_null": True}}, + {"expires_at": {"_gt": now_iso}}, + ], + } + + rows = await async_directus.get_items( + "notification", + { + "query": { + "filter": notif_filter, + "fields": [ + "id", + "event_code", + "action", + "level", + "created_at", + "expires_at", + "actor_user_id", + "ref_org_id", + "ref_workspace_id", + "ref_project_id", + "ref_chat_id", + "ref_report_id", + "ref_conversation_id", + "ref_invite_id", + ], + "sort": ["-created_at"], + "limit": max(1, min(limit, 200)), + } + }, + ) or [] + if not isinstance(rows, list) or not rows: + return [] + + notif_ids = [r["id"] for r in rows if r.get("id")] + + # Read state (activity rows) keyed by notification_id. + activity_rows = await async_directus.get_items( + "notification_activity", + { + "query": { + "filter": { + "notification_id": {"_in": notif_ids}, + "user_id": {"_eq": auth.user_id}, + }, + "fields": ["notification_id", "read"], + "limit": -1, + } + }, + ) or [] + read_map: dict[str, bool] = {} + if isinstance(activity_rows, list): + for ar in activity_rows: + nid = ar.get("notification_id") + if nid: + read_map[nid] = bool(ar.get("read", False)) + + if unread_only: + rows = [r for r in rows if not read_map.get(r["id"], False)] + if not rows: + return [] + notif_ids = [r["id"] for r in rows] + + # Translations — pick the user's language with en-US fallback. + # Batch once; caller UX doesn't need every locale. + accept_lang = "en-US" # TODO: thread Accept-Language through session + translation_rows = await async_directus.get_items( + "notification_translations", + { + "query": { + "filter": {"notification_id": {"_in": notif_ids}}, + "fields": ["notification_id", "languages_code", "title", "message"], + "limit": -1, + } + }, + ) or [] + tl_by_notif: dict[str, dict] = {} + if isinstance(translation_rows, list): + for tr in translation_rows: + nid = tr.get("notification_id") + if not nid: + continue + # Prefer user's language; fall back to en-US; any other as + # final backstop. + existing = tl_by_notif.get(nid) + lang = tr.get("languages_code") + if ( + existing is None + or lang == accept_lang + or (lang == "en-US" and existing.get("languages_code") != accept_lang) + ): + tl_by_notif[nid] = tr + + # Actor name + avatar in one batch. + actor_ids = list({r.get("actor_user_id") for r in rows if r.get("actor_user_id")}) + actor_map: dict[str, dict] = {} + if actor_ids: + app_users = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"id": {"_in": actor_ids}}, + "fields": ["id", "display_name", "directus_user_id"], + "limit": -1, + } + }, + ) or [] + if isinstance(app_users, list): + du_ids = [u["directus_user_id"] for u in app_users if u.get("directus_user_id")] + avatar_map: dict[str, Optional[str]] = {} + if du_ids: + profiles = await async_directus.get_users( + { + "query": { + "filter": {"id": {"_in": du_ids}}, + "fields": ["id", "avatar"], + "limit": -1, + } + } + ) + if isinstance(profiles, list): + avatar_map = {p["id"]: p.get("avatar") for p in profiles} + for u in app_users: + actor_map[u["id"]] = { + "display_name": u.get("display_name") or "", + "avatar": avatar_map.get(u.get("directus_user_id") or "") or None, + } + + out: list[NotificationRow] = [] + for r in rows: + tl = tl_by_notif.get(r["id"]) + actor = actor_map.get(r.get("actor_user_id") or "") or {} + out.append( + NotificationRow( + id=r["id"], + event_code=r.get("event_code", ""), + action=r.get("action", "NONE"), + level=r.get("level", "info"), + created_at=r.get("created_at"), + expires_at=r.get("expires_at"), + read=read_map.get(r["id"], False), + actor_user_id=r.get("actor_user_id"), + actor_name=actor.get("display_name"), + actor_avatar=actor.get("avatar"), + refs=NotificationRefs( + org_id=r.get("ref_org_id"), + workspace_id=r.get("ref_workspace_id"), + project_id=r.get("ref_project_id"), + chat_id=r.get("ref_chat_id"), + report_id=r.get("ref_report_id"), + conversation_id=r.get("ref_conversation_id"), + invite_id=r.get("ref_invite_id"), + ), + translation=( + NotificationTranslation( + languages_code=tl.get("languages_code", ""), + title=tl.get("title", ""), + message=tl.get("message"), + ) + if tl + else None + ), + ) + ) + return out + + +@router.get("/unread-count") +async def unread_count(auth: DependencyDirectusSession) -> dict: + """Cheap count for the inbox badge. + + Runs a single aggregate — don't call list_notifications just to + get `len(unread)`. + """ + app_user = await get_app_user_or_raise(auth.user_id) + now_iso = datetime.now(timezone.utc).isoformat() + + # Notifications this user owns that haven't expired yet. + notif_rows = await async_directus.get_items( + "notification", + { + "query": { + "filter": { + "audience_user_id": {"_eq": app_user["id"]}, + "_or": [ + {"expires_at": {"_null": True}}, + {"expires_at": {"_gt": now_iso}}, + ], + }, + "fields": ["id"], + "limit": -1, + } + }, + ) or [] + if not isinstance(notif_rows, list) or not notif_rows: + return {"unread": 0} + notif_ids = [r["id"] for r in notif_rows] + + # Activity rows for the caller's read/unread state on those. + read_rows = await async_directus.get_items( + "notification_activity", + { + "query": { + "filter": { + "notification_id": {"_in": notif_ids}, + "user_id": {"_eq": auth.user_id}, + "read": {"_eq": True}, + }, + "fields": ["id"], + "limit": -1, + } + }, + ) or [] + read_count = len(read_rows) if isinstance(read_rows, list) else 0 + return {"unread": max(0, len(notif_ids) - read_count)} + + +@router.post("/{notification_id}/read") +async def mark_read( + notification_id: str, + auth: DependencyDirectusSession, +) -> dict: + """Flip the caller's activity row for this notification to read=true. + + Verifies audience — a user can only mark their own rows read, even + if they guess another user's notification_id. + """ + app_user = await get_app_user_or_raise(auth.user_id) + + notif = await async_directus.get_item("notification", notification_id) + if not notif or notif.get("audience_user_id") != app_user["id"]: + raise HTTPException(status_code=404, detail="Notification not found") + + rows = await async_directus.get_items( + "notification_activity", + { + "query": { + "filter": { + "notification_id": {"_eq": notification_id}, + "user_id": {"_eq": auth.user_id}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + if isinstance(rows, list) and rows: + await async_directus.update_item( + "notification_activity", rows[0]["id"], {"read": True} + ) + else: + # Emit normally pre-creates activity; defend in case a row is missing. + from dembrane.utils import generate_uuid + await async_directus.create_item( + "notification_activity", + { + "id": generate_uuid(), + "notification_id": notification_id, + "user_id": auth.user_id, + "read": True, + }, + ) + return {"status": "read"} + + +@router.post("/read-all") +async def mark_all_read(auth: DependencyDirectusSession) -> dict: + """Flip every unread activity row for this user to read=true. + + Cap at the 500 most recent to keep the mutation bounded. + """ + from dembrane.utils import generate_uuid # noqa: F401 (future use) + + app_user = await get_app_user_or_raise(auth.user_id) + + notif_rows = await async_directus.get_items( + "notification", + { + "query": { + "filter": {"audience_user_id": {"_eq": app_user["id"]}}, + "fields": ["id"], + "sort": ["-created_at"], + "limit": 500, + } + }, + ) or [] + if not isinstance(notif_rows, list) or not notif_rows: + return {"status": "noop", "marked": 0} + notif_ids = [r["id"] for r in notif_rows] + + activity_rows = await async_directus.get_items( + "notification_activity", + { + "query": { + "filter": { + "notification_id": {"_in": notif_ids}, + "user_id": {"_eq": auth.user_id}, + "read": {"_eq": False}, + }, + "fields": ["id"], + "limit": -1, + } + }, + ) or [] + marked = 0 + if isinstance(activity_rows, list): + for row in activity_rows: + await async_directus.update_item( + "notification_activity", row["id"], {"read": True} + ) + marked += 1 + return {"status": "read", "marked": marked} diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index c75e28a1..e9478073 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -733,6 +733,22 @@ async def change_member_role( ) # Note: derived model means no membership fan-out needed — next access # check on any workspace re-derives from the new role. + + # Notify the affected user (unless they changed their own role). + if user_id != app_user["id"]: + team_row = await async_directus.get_item("org", org_id) + team_name = (team_row or {}).get("name") or "your team" + from dembrane.notifications import emit + await emit( + audience_user_id=user_id, + actor_user_id=app_user["id"], + event_code="TEAM_ROLE_CHANGED", + title=f"Your role in {team_name} changed", + message=f"You're now a **{body.role}** in {team_name}.", + action="NAVIGATE_TEAM_SETTINGS", + ref_org_id=org_id, + ) + logger.info( f"Team {org_id} role change: user {user_id} " f"{target_role} → {body.role} by {app_user['id']}" @@ -810,6 +826,26 @@ async def remove_team_member( ) affected = await on_team_member_removed(org_id, user_id) + + # Notify the removed user — they'll see workspaces drop from their + # selector; this gives them the honest explanation. + if user_id != app_user["id"]: + team_row = await async_directus.get_item("org", org_id) + team_name = (team_row or {}).get("name") or "the team" + from dembrane.notifications import emit + await emit( + audience_user_id=user_id, + actor_user_id=app_user["id"], + event_code="TEAM_REMOVED", + title=f"You were removed from {team_name}", + message=( + "Workspace access that depended on your team role has ended. " + "Reach out to a team admin if this was unexpected." + ), + action="NONE", + ref_org_id=org_id, + ) + logger.info( f"Removed user {user_id} from team {org_id} by {app_user['id']} — " f"soft-deleted direct memberships on {len(affected)} workspace(s)" diff --git a/echo/server/dembrane/api/v2/project_sharing.py b/echo/server/dembrane/api/v2/project_sharing.py index d1f67895..f568ed0c 100644 --- a/echo/server/dembrane/api/v2/project_sharing.py +++ b/echo/server/dembrane/api/v2/project_sharing.py @@ -311,6 +311,26 @@ async def add_project_share( f"by {acting_user['id']}" ) + # Notify the invitee — they now have access to a project they + # didn't before. Skip when they're re-granting themselves (shouldn't + # happen given the admin-only guard above, defense-in-depth). + if invitee_id != acting_user["id"]: + project_name = project.get("name") or "a project" + from dembrane.notifications import emit + await emit( + audience_user_id=invitee_id, + actor_user_id=acting_user["id"], + event_code="PROJECT_SHARE_ADDED", + title=f"{project_name} was shared with you", + message=( + f"You can **{body.role}** this project in " + f"{workspace.get('name', 'its workspace')}." + ), + action="NAVIGATE_PROJECT", + ref_project_id=project_id, + ref_workspace_id=workspace["id"], + ) + enriched = await _enrich_member( { "user_id": invitee_id, diff --git a/echo/server/dembrane/api/v2/workspace_settings.py b/echo/server/dembrane/api/v2/workspace_settings.py index 6f8504e7..50c6c97c 100644 --- a/echo/server/dembrane/api/v2/workspace_settings.py +++ b/echo/server/dembrane/api/v2/workspace_settings.py @@ -349,12 +349,24 @@ async def remove_workspace_member( {"deleted_at": datetime.now(timezone.utc).isoformat()}, ) + removed_user_id = membership.get("user_id") + if removed_user_id and removed_user_id != ctx.app_user_id: + from dembrane.notifications import emit + await emit( + audience_user_id=removed_user_id, + actor_user_id=ctx.app_user_id, + event_code="WORKSPACE_REMOVED", + title=f"You were removed from {ctx.workspace.get('name', 'a workspace')}", + message="Reach out to the workspace admin if this was unexpected.", + action="NONE", + ref_workspace_id=ctx.workspace_id, + ) + # Sticky-remove: if this user would otherwise re-derive admin/member # access via their org role (rule-of-system inheritance), tombstone # them so team-role changes don't silently re-grant access. Only # applies when the removed user has an active org_membership. from dembrane.inheritance import sticky_remove - removed_user_id = membership.get("user_id") if removed_user_id and ctx.workspace.get("org_id"): # Check if they'd re-derive via org role — only tombstone if yes. org_rows = await async_directus.get_items( @@ -440,6 +452,20 @@ async def change_member_role( membership_id, {"role": body.role}, ) + + # Notify the affected user (unless they're the one making the change). + if membership.get("user_id") and membership["user_id"] != ctx.app_user_id: + from dembrane.notifications import emit + await emit( + audience_user_id=membership["user_id"], + actor_user_id=ctx.app_user_id, + event_code="WORKSPACE_ROLE_CHANGED", + title=f"Your role changed in {ctx.workspace.get('name', 'a workspace')}", + message=f"You're now a **{body.role}** here.", + action="NAVIGATE_WS", + ref_workspace_id=ctx.workspace_id, + ) + return {"status": "success"} diff --git a/echo/server/dembrane/notifications.py b/echo/server/dembrane/notifications.py new file mode 100644 index 00000000..2c2e4fb9 --- /dev/null +++ b/echo/server/dembrane/notifications.py @@ -0,0 +1,229 @@ +"""In-app notification service. + +One canonical store (`notification` + `notification_translations` + +`notification_activity`) serves the inbox UI today. The module exposes a +single `emit` function that action code paths call — everything else +(list, mark-read, unread-count) runs through `/v2/me/notifications` BFF +endpoints. + +### Channels + +Notifications are stored here. Future delivery layers (email digest, +Slack webhook) read from this store rather than having their own +pipeline: + + emit(...) → notification row + activity row + └── inbox UI reads via /v2/me/notifications + └── (future) digest worker groups by user + sends SendGrid + └── (future) Slack bridge fans out urgent/mentioned rows + +This keeps the emission sites dumb — they describe *what happened*, not +*where it goes*. Per-user channel preferences live in the delivery +layer, not here. Don't add per-channel booleans to the notification row. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import Literal, Optional + +from dembrane.utils import generate_uuid +from dembrane.directus_async import async_directus + +logger = getLogger("dembrane.notifications") + + +NotificationAction = Literal[ + "NONE", + "NAVIGATE_WS", + "NAVIGATE_PROJECT", + "NAVIGATE_REPORT", + "NAVIGATE_CHAT", + "NAVIGATE_INVITE", + "NAVIGATE_TEAM_SETTINGS", + "NAVIGATE_WORKSPACE_SETTINGS", +] + +NotificationLevel = Literal["info", "urgent"] + + +async def emit( + *, + audience_user_id: str, + event_code: str, + title: str, + message: str, + action: NotificationAction = "NONE", + level: NotificationLevel = "info", + actor_user_id: Optional[str] = None, + ref_org_id: Optional[str] = None, + ref_workspace_id: Optional[str] = None, + ref_project_id: Optional[str] = None, + ref_chat_id: Optional[str] = None, + ref_report_id: Optional[str] = None, + ref_conversation_id: Optional[str] = None, + ref_invite_id: Optional[str] = None, + language: str = "en-US", + expires_at: Optional[str] = None, +) -> Optional[str]: + """Create a single notification row + its English translation + + one pre-filled activity row. Returns the notification id, or None + on failure (never raises — notifications are a best-effort side + effect, they must not fail the parent action). + + Don't call this with a list of users — call once per user. The + activity row is created eagerly so unread counts can be a cheap + aggregate. + + ### Translations + + For the first pass we only write one translation row (caller's + language or en-US). The frontend drawer falls back to en-US when + the user's locale has no row. When we ship server-side message + templating (grouped into dembrane/notification_templates.py), we + can fan out every language in one call. + """ + try: + notification_id = generate_uuid() + + # Resolve audience_user_id (app_user.id) → directus_user_id so + # activity rows match the announcement_activity convention + # (user_id on activity = directus_users.id). + directus_user_id: Optional[str] = None + audience_row = await async_directus.get_item("app_user", audience_user_id) + if audience_row: + directus_user_id = audience_row.get("directus_user_id") + + await async_directus.create_item( + "notification", + { + "id": notification_id, + "audience_user_id": audience_user_id, + "actor_user_id": actor_user_id, + "event_code": event_code, + "action": action, + "level": level, + "ref_org_id": ref_org_id, + "ref_workspace_id": ref_workspace_id, + "ref_project_id": ref_project_id, + "ref_chat_id": ref_chat_id, + "ref_report_id": ref_report_id, + "ref_conversation_id": ref_conversation_id, + "ref_invite_id": ref_invite_id, + "expires_at": expires_at, + }, + ) + + await async_directus.create_item( + "notification_translations", + { + "id": generate_uuid(), + "notification_id": notification_id, + "languages_code": language, + "title": title, + "message": message, + }, + ) + + if directus_user_id: + await async_directus.create_item( + "notification_activity", + { + "id": generate_uuid(), + "notification_id": notification_id, + "user_id": directus_user_id, + "read": False, + }, + ) + + return notification_id + except Exception as exc: # noqa: BLE001 — notifications must never raise + logger.warning( + "emit notification failed (event=%s audience=%s): %s", + event_code, audience_user_id, exc, + ) + return None + + +# ── Audience derivation helpers ───────────────────────────────────────── + +async def audience_workspace_admins(workspace_id: str) -> list[str]: + """Return app_user.id for every admin/owner on a workspace (direct + + derived team admins). Use for "someone joined your workspace"- + shaped notifications. + """ + from dembrane.inheritance import get_effective_members + + members = await get_effective_members(workspace_id) + return [ + m["user_id"] + for m in members + if m.get("user_id") and m.get("role") in ("admin", "owner") + ] + + +async def audience_workspace_members(workspace_id: str) -> list[str]: + """Every effective member on the workspace, any role.""" + from dembrane.inheritance import get_effective_members + + members = await get_effective_members(workspace_id) + return [m["user_id"] for m in members if m.get("user_id")] + + +async def audience_team_admins(org_id: str) -> list[str]: + rows = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "role": {"_in": ["admin", "owner"]}, + "deleted_at": {"_null": True}, + }, + "fields": ["user_id"], + "limit": -1, + } + }, + ) or [] + if not isinstance(rows, list): + return [] + return [r["user_id"] for r in rows if r.get("user_id")] + + +async def audience_team(org_id: str) -> list[str]: + rows = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["user_id"], + "limit": -1, + } + }, + ) or [] + if not isinstance(rows, list): + return [] + return [r["user_id"] for r in rows if r.get("user_id")] + + +async def emit_to_audience( + audience_user_ids: list[str], + **emit_kwargs, +) -> list[str]: + """Fan out the same notification to every user in `audience_user_ids`. + + Skips the actor to avoid "you accepted your own invite" self-notifs + when `actor_user_id` is in the audience list. + """ + actor = emit_kwargs.get("actor_user_id") + created: list[str] = [] + for uid in audience_user_ids: + if actor and uid == actor: + continue + nid = await emit(audience_user_id=uid, **emit_kwargs) + if nid: + created.append(nid) + return created From 1b2ed00894d8522fb97c4780f371950183e5bf05 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Tue, 21 Apr 2026 11:52:47 +0000 Subject: [PATCH 095/208] feat(inbox): notifications drawer + more emit coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend - NotificationsDrawer — ribbon-mounted bell icon + right-side drawer that lists the user's personal notifications. Unread indicator + mark-all-read + per-row click resolves the codified action into a concrete URL (resolveNotificationHref). Follows the announcement drawer's visual pattern; lives next to AnnouncementIcon in Header until the consolidated inbox mockup is implementable. - useNotifications hook family (useNotifications, useUnreadNotificationCount, useMarkNotificationRead, useMarkAllNotificationsRead) with light 60s polling so the badge stays honest without adding a websocket. Backend emit coverage (follow-up to the initial 6-site backfill) - me.accept_my_invite: INVITE_ACCEPTED → inviter. - me.decline_my_invite: INVITE_DECLINED → inviter. - projects.set_project_visibility: PROJECT_NOW_PRIVATE / PROJECT_NOW_WORKSPACE → workspace members (fan-out via emit_to_audience, actor skipped). - project_sharing.change_project_share_role: PROJECT_SHARE_ROLE_CHANGED → affected user. - project_sharing.revoke_project_share: PROJECT_SHARE_REVOKED. - workspaces.set_workspace_tier: TIER_UPGRADED / TIER_DOWNGRADED to workspace admins/owners. Includes the downgrade effect list in the body so admins see exactly what changed ("Remove your custom logo, freeze data export, ..."). Still on the follow-up list (explorer catalogued 27 sites): invite resend, team member added via direct path, invite cancelled, upgrade-request fan-out to other admins, report-ready, conversation- finished. Mechanical work; queued for the next pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../frontend/src/components/layout/Header.tsx | 5 + .../notifications/NotificationsDrawer.tsx | 239 ++++++++++++++++++ echo/frontend/src/hooks/useNotifications.ts | 164 ++++++++++++ echo/server/dembrane/api/v2/me.py | 32 +++ .../server/dembrane/api/v2/project_sharing.py | 29 +++ echo/server/dembrane/api/v2/projects.py | 36 +++ echo/server/dembrane/api/v2/workspaces.py | 31 +++ 7 files changed, 536 insertions(+) create mode 100644 echo/frontend/src/components/notifications/NotificationsDrawer.tsx create mode 100644 echo/frontend/src/hooks/useNotifications.ts diff --git a/echo/frontend/src/components/layout/Header.tsx b/echo/frontend/src/components/layout/Header.tsx index 86955bb0..dd7c635a 100644 --- a/echo/frontend/src/components/layout/Header.tsx +++ b/echo/frontend/src/components/layout/Header.tsx @@ -43,6 +43,7 @@ import { AnalyticsEvents as events } from "@/lib/analyticsEvents"; import { testId } from "@/lib/testUtils"; import { AnnouncementIcon } from "../announcement/AnnouncementIcon"; import { Announcements } from "../announcement/Announcements"; +import { NotificationsDrawer } from "../notifications/NotificationsDrawer"; import { TopAnnouncementBar } from "../announcement/TopAnnouncementBar"; import { FeedbackPortalModal } from "../common/FeedbackPortalModal"; import { Logo } from "../common/Logo"; @@ -179,6 +180,10 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => { )} + {/* Personal notifications — sibling of the announcement + icon. Collapses into one consolidated "Inbox" icon + when the designer's inbox pattern lands. */} + { + const [opened, { open, close }] = useDisclosure(false); + const navigate = useI18nNavigate(); + const { data: notifications = [], isLoading } = useNotifications(); + const { data: unreadCount = 0 } = useUnreadNotificationCount(); + const markRead = useMarkNotificationRead(); + const markAllRead = useMarkAllNotificationsRead(); + + const handleClick = (row: NotificationRow) => { + // Flip read state optimistically via the mutation, then resolve the + // action into a URL. Static copy ("NONE") notifications stay put. + if (!row.read) { + markRead.mutate(row.id); + } + const href = resolveNotificationHref(row); + if (href) { + navigate(href); + close(); + } + }; + + return ( + <> + 0 ? unreadCount : undefined} + disabled={unreadCount === 0} + withBorder + > + + + + + + + + Notifications + + {unreadCount > 0 && ( + + )} + + } + > + {isLoading ? ( +
    + +
    + ) : notifications.length === 0 ? ( +
    + + + + You're all caught up. + + +
    + ) : ( + + {notifications.map((row) => ( + handleClick(row)} + /> + ))} + + )} +
    + + ); +}; + +function NotificationItem({ + row, + onClick, +}: { + row: NotificationRow; + onClick: () => void; +}) { + const title = row.translation?.title ?? row.event_code; + const message = row.translation?.message ?? ""; + const hasAction = resolveNotificationHref(row) !== null; + const createdLabel = row.created_at + ? formatRelative(new Date(row.created_at), new Date()) + : ""; + + return ( + + + {/* Actor avatar when available; otherwise level-coloured dot + so the row still has a visual anchor. */} + {row.actor_user_id ? ( + + {(row.actor_name || "?").slice(0, 2).toUpperCase()} + + ) : ( + + + + )} + + + + + {title} + + {!row.read && ( + + )} + {row.level === "urgent" && ( + + Urgent + + )} + + {message && ( + + {message} + + )} + {createdLabel && ( + + {createdLabel} + + )} + + + + ); +} diff --git a/echo/frontend/src/hooks/useNotifications.ts b/echo/frontend/src/hooks/useNotifications.ts new file mode 100644 index 00000000..5f9ed7bf --- /dev/null +++ b/echo/frontend/src/hooks/useNotifications.ts @@ -0,0 +1,164 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { API_BASE_URL } from "@/config"; + +/** + * Hooks for the in-app notification inbox. + * + * Backend lives at /v2/me/notifications. Row shape mirrors the response + * from `server/dembrane/api/v2/notifications.py:NotificationRow`. + * + * Announcements share the drawer UI pattern but have their own hooks in + * `@/components/announcement/hooks`. When the consolidated inbox design + * lands, this file + that one can collapse into a single store. + */ + +export interface NotificationRefs { + org_id: string | null; + workspace_id: string | null; + project_id: string | null; + chat_id: string | null; + report_id: string | null; + conversation_id: string | null; + invite_id: string | null; +} + +export interface NotificationTranslation { + languages_code: string; + title: string; + message: string | null; +} + +export type NotificationAction = + | "NONE" + | "NAVIGATE_WS" + | "NAVIGATE_PROJECT" + | "NAVIGATE_REPORT" + | "NAVIGATE_CHAT" + | "NAVIGATE_INVITE" + | "NAVIGATE_TEAM_SETTINGS" + | "NAVIGATE_WORKSPACE_SETTINGS"; + +export interface NotificationRow { + id: string; + event_code: string; + action: NotificationAction; + level: "info" | "urgent"; + created_at: string | null; + expires_at: string | null; + read: boolean; + actor_user_id: string | null; + actor_name: string | null; + actor_avatar: string | null; + refs: NotificationRefs; + translation: NotificationTranslation | null; +} + +async function fetchNotifications(): Promise { + const res = await fetch(`${API_BASE_URL}/v2/me/notifications`, { + credentials: "include", + }); + if (!res.ok) return []; + return res.json(); +} + +async function fetchUnreadCount(): Promise { + const res = await fetch( + `${API_BASE_URL}/v2/me/notifications/unread-count`, + { credentials: "include" }, + ); + if (!res.ok) return 0; + const body = await res.json().catch(() => ({})); + return typeof body.unread === "number" ? body.unread : 0; +} + +export const useNotifications = () => + useQuery({ + queryKey: ["v2", "notifications"], + queryFn: fetchNotifications, + staleTime: 30_000, + // Light polling so the badge stays honest without a websocket. + refetchInterval: 60_000, + }); + +export const useUnreadNotificationCount = () => + useQuery({ + queryKey: ["v2", "notifications", "unread-count"], + queryFn: fetchUnreadCount, + staleTime: 30_000, + refetchInterval: 60_000, + }); + +export const useMarkNotificationRead = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (notificationId: string) => { + const res = await fetch( + `${API_BASE_URL}/v2/me/notifications/${notificationId}/read`, + { credentials: "include", method: "POST" }, + ); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.detail || "Couldn't mark as read"); + } + return res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["v2", "notifications"] }); + }, + }); +}; + +export const useMarkAllNotificationsRead = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async () => { + const res = await fetch( + `${API_BASE_URL}/v2/me/notifications/read-all`, + { credentials: "include", method: "POST" }, + ); + if (!res.ok) throw new Error("Couldn't mark all as read"); + return res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["v2", "notifications"] }); + }, + }); +}; + +/** + * Translate a notification's codified action into a concrete URL. + * + * Returns null if the action is NONE or the required refs are missing. + * Centralises the URL mapping so callers (click handlers) don't each + * reinvent it. Keep aligned with `NotificationAction` enum on the + * server side. + */ +export function resolveNotificationHref( + row: Pick, +): string | null { + const { action, refs } = row; + switch (action) { + case "NAVIGATE_WS": + return refs.workspace_id ? `/w/${refs.workspace_id}/projects` : null; + case "NAVIGATE_PROJECT": + return refs.project_id + ? `/projects/${refs.project_id}/overview` + : null; + case "NAVIGATE_REPORT": + return refs.project_id && refs.report_id + ? `/projects/${refs.project_id}/reports/${refs.report_id}` + : null; + case "NAVIGATE_CHAT": + return refs.project_id && refs.chat_id + ? `/projects/${refs.project_id}/chats/${refs.chat_id}` + : null; + case "NAVIGATE_INVITE": + return "/invites"; + case "NAVIGATE_TEAM_SETTINGS": + return refs.org_id ? `/t/${refs.org_id}/settings` : null; + case "NAVIGATE_WORKSPACE_SETTINGS": + return refs.workspace_id ? `/w/${refs.workspace_id}/settings` : null; + default: + return null; + } +} diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 0884fbe3..4614d00e 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -338,6 +338,22 @@ async def accept_my_invite(invite_id: str, auth: DependencyDirectusSession) -> d "accepted_at": now_iso, }) + # Notify the inviter that their invite was accepted. + inviter_id = invite.get("invited_by") + if inviter_id and inviter_id != app_user_id: + from dembrane.notifications import emit + accepter_name = app_user.get("display_name") or app_user.get("email") or "Someone" + await emit( + audience_user_id=inviter_id, + actor_user_id=app_user_id, + event_code="INVITE_ACCEPTED", + title=f"{accepter_name} joined {ws.get('name', 'your workspace')}", + message="They accepted your invite and can now collaborate.", + action="NAVIGATE_WORKSPACE_SETTINGS", + ref_workspace_id=invite["workspace_id"], + ref_org_id=ws.get("org_id"), + ) + return {"status": "success", "workspace_id": invite["workspace_id"]} @@ -355,6 +371,22 @@ async def decline_my_invite(invite_id: str, auth: DependencyDirectusSession) -> if invite.get("accepted_at"): raise HTTPException(status_code=400, detail="Invite already accepted") + # Notify the inviter before deletion (we still have the row). + inviter_id = invite.get("invited_by") + if inviter_id: + ws_for_notif = await async_directus.get_item("workspace", invite.get("workspace_id")) + ws_name = (ws_for_notif or {}).get("name") or "a workspace" + from dembrane.notifications import emit + await emit( + audience_user_id=inviter_id, + actor_user_id=app_user["id"], + event_code="INVITE_DECLINED", + title=f"{email} declined your invite", + message=f"They chose not to join **{ws_name}**.", + action="NAVIGATE_WORKSPACE_SETTINGS", + ref_workspace_id=invite.get("workspace_id"), + ) + await async_directus.delete_item("workspace_invite", invite_id) return {"status": "success"} diff --git a/echo/server/dembrane/api/v2/project_sharing.py b/echo/server/dembrane/api/v2/project_sharing.py index f568ed0c..32e68f73 100644 --- a/echo/server/dembrane/api/v2/project_sharing.py +++ b/echo/server/dembrane/api/v2/project_sharing.py @@ -378,6 +378,20 @@ async def change_project_share_role( f"Project {project_id} share role changed: {user_id} → {body.role} " f"by {acting_user['id']}" ) + + if user_id != acting_user["id"]: + project_name = project.get("name") or "a project" + from dembrane.notifications import emit + await emit( + audience_user_id=user_id, + actor_user_id=acting_user["id"], + event_code="PROJECT_SHARE_ROLE_CHANGED", + title=f"Your access to {project_name} changed", + message=f"You're now a **{body.role}** on this project.", + action="NAVIGATE_PROJECT", + ref_project_id=project_id, + ) + return {"status": "updated", "role": body.role} @@ -415,4 +429,19 @@ async def revoke_project_share( logger.info( f"Revoked project {project_id} share for {user_id} by {acting_user['id']}" ) + + if user_id != acting_user["id"]: + project_name = project.get("name") or "a project" + from dembrane.notifications import emit + await emit( + audience_user_id=user_id, + actor_user_id=acting_user["id"], + event_code="PROJECT_SHARE_REVOKED", + title=f"Your access to {project_name} was revoked", + message="Ask the project owner if you still need access.", + action="NONE", + ref_project_id=project_id, + ref_workspace_id=project.get("workspace_id"), + ) + return {"status": "revoked"} diff --git a/echo/server/dembrane/api/v2/projects.py b/echo/server/dembrane/api/v2/projects.py index ffe9fa18..b3c1ce3c 100644 --- a/echo/server/dembrane/api/v2/projects.py +++ b/echo/server/dembrane/api/v2/projects.py @@ -254,4 +254,40 @@ async def set_project_visibility( f"Project {project_id} visibility: {current} → {body.visibility} " f"by {app_user['id']}" ) + + # Notify workspace members so they understand why a project they + # could see yesterday is suddenly gone (or why a new one appeared). + # Skip the actor so they don't see "you changed the visibility". + from dembrane.notifications import emit_to_audience, audience_workspace_members + project_name = project.get("name") or "A project" + audience = await audience_workspace_members(workspace_id) + if body.visibility == "private": + await emit_to_audience( + audience, + actor_user_id=app_user["id"], + event_code="PROJECT_NOW_PRIVATE", + title=f"{project_name} is now private", + message=( + f"It's no longer visible to the whole workspace. " + "Only the people explicitly shared can see it." + ), + action="NONE", + ref_workspace_id=workspace_id, + ref_project_id=project_id, + ) + else: + await emit_to_audience( + audience, + actor_user_id=app_user["id"], + event_code="PROJECT_NOW_WORKSPACE", + title=f"{project_name} is now shared with the workspace", + message=( + f"Everyone in {workspace.get('name', 'this workspace')} " + "can see it." + ), + action="NAVIGATE_PROJECT", + ref_workspace_id=workspace_id, + ref_project_id=project_id, + ) + return {"status": "updated", "visibility": body.visibility} diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 0521e129..a6d53626 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -528,6 +528,37 @@ async def set_workspace_tier( f"(direction={direction}, by={auth.user_id}, reason={body.reason!r}, " f"effects={[e['policy'] for e in effects]})" ) + + # Notify workspace admins/owners so they know about the tier change. + # Staff changes bypass the usual admin flow — without this notification + # admins would see feature gates flip (or logo disappear) with no + # explanation. Skip the (staff) actor since they already know. + if direction != "no-change": + from dembrane.notifications import emit_to_audience, audience_workspace_admins + ws_name = workspace.get("name", "your workspace") + if direction == "upgrade": + title = f"{ws_name} upgraded to {to_tier}" + message = f"You now have {to_tier}-tier features unlocked." + else: + title = f"{ws_name} moved to {to_tier}" + effect_list = ", ".join(e.get("human", "") for e in effects if e.get("human")) + message = ( + f"Some features are now limited: {effect_list}." + if effect_list + else "Some features are now limited." + ) + audience = await audience_workspace_admins(workspace_id) + await emit_to_audience( + audience, + event_code=( + "TIER_UPGRADED" if direction == "upgrade" else "TIER_DOWNGRADED" + ), + title=title, + message=message, + action="NAVIGATE_WS", + ref_workspace_id=workspace_id, + ) + return SetTierResponse( workspace_id=workspace_id, previous_tier=from_tier, From 00b32188c8956fd1776371b7502b6fd872426206 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Tue, 21 Apr 2026 12:04:59 +0000 Subject: [PATCH 096/208] feat(inbox): more emit sites + dead-code sweep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Notification coverage expansion - Invite cancelled by admin → notify the pending invitee when they have an app_user account. New-email invites stay email-only. - Upgrade request sent → fan out to co-admins so two admins don't both hit the billing inbox with the same ask. - Workspace created → notify other team admins so they don't need to refresh the selector to discover the new workspace. - Report generated → report creator (REPORT_READY). Kept audience tight: fanning to every workspace member on every report would spam inboxes. Wider broadcast can land via project-visibility-aware audience derivation later if needed. - Report generation failed → report creator only, level=urgent (REPORT_FAILED). New sync bridge: notifications.emit_sync() wraps emit() via the existing run_async_in_new_loop helper for Dramatiq actors that can't await. Same swallow-on-fail semantics as emit — notifications must never fail the parent task. Dead-code sweep (from 2026-04-21 audit) - workspace_settings.py: duplicate `description: Optional[str]` field on WorkspaceDetailResponse collapsed to one. - projects.py: unused `from dembrane.api.v2.middleware import WorkspaceContext` removed inside set_project_visibility. - middleware.py: stale "once that wiring is in place" comment on WorkspaceContext.has_policy — tier wiring is live. - docs/workspaces/inheritance-rules.md: `workspace_inherits_*` → `workspace_follows_*` to match the module's actual names. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/docs/workspaces/inheritance-rules.md | 4 +- echo/server/dembrane/api/v2/middleware.py | 6 +-- echo/server/dembrane/api/v2/projects.py | 2 - .../dembrane/api/v2/workspace_settings.py | 33 +++++++++++- echo/server/dembrane/api/v2/workspaces.py | 41 ++++++++++++++ echo/server/dembrane/notifications.py | 24 +++++++++ echo/server/dembrane/tasks.py | 54 ++++++++++++++++++- 7 files changed, 155 insertions(+), 9 deletions(-) diff --git a/echo/docs/workspaces/inheritance-rules.md b/echo/docs/workspaces/inheritance-rules.md index f8436a9f..df47475e 100644 --- a/echo/docs/workspaces/inheritance-rules.md +++ b/echo/docs/workspaces/inheritance-rules.md @@ -134,11 +134,11 @@ from __future__ import annotations # ── Helpers ─────────────────────────────────────────────────────────── -def workspace_inherits_team_admins(workspace: dict) -> bool: +def workspace_follows_team_admins(workspace: dict) -> bool: return (workspace.get("settings") or {}).get("inherit_team_admins", True) -def workspace_inherits_team_members(workspace: dict) -> bool: +def workspace_follows_team_members(workspace: dict) -> bool: return (workspace.get("settings") or {}).get("inherit_team_members", False) diff --git a/echo/server/dembrane/api/v2/middleware.py b/echo/server/dembrane/api/v2/middleware.py index 819c194e..b54087de 100644 --- a/echo/server/dembrane/api/v2/middleware.py +++ b/echo/server/dembrane/api/v2/middleware.py @@ -41,9 +41,9 @@ def __init__( self.is_external = is_external def has_policy(self, required: str) -> bool: - # Note: tier auto-wiring lives in policies.has_policy (S6). Workspace - # tier is forwarded through so tier gates resolve without per-endpoint - # require_tier() calls once that wiring is in place. + # Tier auto-wiring is in policies.has_policy — workspace_tier is + # forwarded so tier gates fire without per-endpoint require_tier + # calls. return has_policy( self.role, self.custom_policies, diff --git a/echo/server/dembrane/api/v2/projects.py b/echo/server/dembrane/api/v2/projects.py index b3c1ce3c..0736e1f8 100644 --- a/echo/server/dembrane/api/v2/projects.py +++ b/echo/server/dembrane/api/v2/projects.py @@ -173,8 +173,6 @@ async def set_project_visibility( Existing project_membership rows are preserved across a flip — admin curates via the share modal afterwards. """ - from dembrane.api.v2.middleware import WorkspaceContext - app_user = await get_app_user_or_raise(auth.user_id) project = await async_directus.get_item("project", project_id) diff --git a/echo/server/dembrane/api/v2/workspace_settings.py b/echo/server/dembrane/api/v2/workspace_settings.py index 50c6c97c..4075dd90 100644 --- a/echo/server/dembrane/api/v2/workspace_settings.py +++ b/echo/server/dembrane/api/v2/workspace_settings.py @@ -54,9 +54,9 @@ class WorkspaceDetailResponse(BaseModel): my_role: str = "" my_policies: list[str] = [] # Privacy + settings context for the settings page controls. + # `description` lives above (shared with legacy consumers). inherit_team_admins: bool = True inherit_team_members: bool = False - description: Optional[str] = None logo_url: Optional[str] = None @@ -549,6 +549,37 @@ async def cancel_workspace_invite( if invite.get("accepted_at"): raise HTTPException(status_code=400, detail="Invite has already been accepted") + # Notify the invitee if they already have an app_user account. New- + # email invites have no in-app target — email would be the only + # channel and we deliberately don't chase those. + from dembrane.app_user import resolve_app_user + invitee_directus = await async_directus.get_users( + { + "query": { + "filter": {"email": {"_eq": (invite.get("email") or "").lower()}}, + "fields": ["id"], + "limit": 1, + } + } + ) + if isinstance(invitee_directus, list) and invitee_directus: + invitee_app_user = await resolve_app_user(invitee_directus[0]["id"]) + if invitee_app_user: + from dembrane.notifications import emit + await emit( + audience_user_id=invitee_app_user["id"], + actor_user_id=ctx.app_user_id, + event_code="INVITE_CANCELLED", + title=( + f"An invite to {ctx.workspace.get('name', 'a workspace')} " + "was cancelled" + ), + message="The admin withdrew your pending invite.", + action="NONE", + ref_workspace_id=ctx.workspace_id, + ref_invite_id=invite_id, + ) + # Hard delete — there's no reason to keep canceled invites around await async_directus.delete_item("workspace_invite", invite_id) return {"status": "success"} diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index a6d53626..0f602a25 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -399,6 +399,28 @@ async def create_workspace( f"(admins_follow={body.inherit_team_admins}, members_follow={body.inherit_team_members})" ) + # Tell the team's other admins/owners that a new workspace exists. + # Open workspaces grant them access via derivation; they'd otherwise + # find out by refreshing the selector. + from dembrane.notifications import emit_to_audience, audience_team_admins + creator_row = await async_directus.get_item("app_user", app_user_id) + creator_name = (creator_row or {}).get("display_name") or "A team admin" + team_admin_ids = await audience_team_admins(org_id) + await emit_to_audience( + team_admin_ids, + actor_user_id=app_user_id, + event_code="WORKSPACE_CREATED", + title=f"{creator_name} created {body.name.strip()}", + message=( + "The new workspace is open to the team — you have access." + if body.inherit_team_admins + else "The new workspace is private — only explicitly invited people have access." + ), + action="NAVIGATE_WS", + ref_workspace_id=ws_id, + ref_org_id=org_id, + ) + return CreateWorkspaceResponse( id=ws_id, name=body.name.strip(), @@ -669,4 +691,23 @@ async def request_upgrade( f"Upgrade request: workspace {ctx.workspace_id} {current_tier} → {target} " f"by {ctx.app_user_id}" ) + + # Tell co-admins that a request is out so two of them don't both + # email the billing inbox with the same ask. Skips the requester. + from dembrane.notifications import emit_to_audience, audience_workspace_admins + audience = await audience_workspace_admins(ctx.workspace_id) + await emit_to_audience( + audience, + actor_user_id=ctx.app_user_id, + event_code="UPGRADE_REQUEST_SENT", + title=f"{requester_name or 'A team admin'} requested an upgrade", + message=( + f"**{workspace_name}** · {current_tier} → {target}. " + "We'll follow up over email." + ), + action="NAVIGATE_WS", + ref_workspace_id=ctx.workspace_id, + ref_org_id=ctx.workspace.get("org_id"), + ) + return {"status": "sent"} diff --git a/echo/server/dembrane/notifications.py b/echo/server/dembrane/notifications.py index 2c2e4fb9..3dc39c8f 100644 --- a/echo/server/dembrane/notifications.py +++ b/echo/server/dembrane/notifications.py @@ -227,3 +227,27 @@ async def emit_to_audience( if nid: created.append(nid) return created + + +# ── Sync bridge for Dramatiq actors ────────────────────────────────────── + + +def emit_sync(**emit_kwargs) -> Optional[str]: + """Blocking variant for Dramatiq actors that can't await. + + Dramatiq workers run sync code; async frameworks can't be called + directly. CLAUDE.md's rule is to use `run_async_in_new_loop` rather + than nesting event loops. Catch-all on exceptions mirrors `emit` — + notifications are best-effort. + """ + try: + from dembrane.async_helpers import run_async_in_new_loop + return run_async_in_new_loop(emit(**emit_kwargs)) + except Exception as exc: # noqa: BLE001 + logger.warning( + "emit_sync failed (event=%s audience=%s): %s", + emit_kwargs.get("event_code"), + emit_kwargs.get("audience_user_id"), + exc, + ) + return None diff --git a/echo/server/dembrane/tasks.py b/echo/server/dembrane/tasks.py index a0696a17..f4fb0044 100644 --- a/echo/server/dembrane/tasks.py +++ b/echo/server/dembrane/tasks.py @@ -1340,6 +1340,36 @@ def progress_callback(event_type: str, message: str, detail: Optional[dict] = No publish_report_progress(report_id, "completed", "Report ready") logger.info(f"Report {report_id} generated for project {project_id}") + # Notify the report's creator. Keep the audience tight — + # fanning to every workspace member on every report would + # spam inboxes. If we want a wider broadcast later, derive + # audience via project visibility + project_membership. + try: + from dembrane.notifications import emit_sync + from dembrane.app_user import resolve_app_user + with directus_client_context() as client: + report_row = client.get_item("project_report", report_id_str) + project_row = client.get_item("project", project_id) if project_id else None + report_data = (report_row or {}).get("data") or report_row or {} + creator_directus_id = report_data.get("user_created") + if creator_directus_id: + creator = run_async_in_new_loop( + resolve_app_user(creator_directus_id) + ) + if creator: + project_name = (project_row or {}).get("name") or "your project" + emit_sync( + audience_user_id=creator["id"], + event_code="REPORT_READY", + title="Your report is ready", + message=f"**{project_name}** — open to review.", + action="NAVIGATE_REPORT", + ref_project_id=project_id, + ref_report_id=report_id_str, + ) + except Exception as e: + logger.warning(f"Failed to emit REPORT_READY notification: {e}") + # Dispatch report.generated webhook try: from dembrane.service.webhook import dispatch_webhooks_for_report_event @@ -1360,7 +1390,29 @@ def progress_callback(event_type: str, message: str, detail: Optional[dict] = No except Exception as update_err: logger.error(f"Failed to update report status to error: {update_err}") publish_report_progress(report_id, "failed", str(e)) - # Non-retriable + # Non-retriable — tell the creator so they don't keep waiting. + try: + from dembrane.notifications import emit_sync + from dembrane.app_user import resolve_app_user + with directus_client_context() as client: + report_row = client.get_item("project_report", report_id_str) + report_data = (report_row or {}).get("data") or report_row or {} + creator_directus_id = report_data.get("user_created") + if creator_directus_id: + creator = run_async_in_new_loop(resolve_app_user(creator_directus_id)) + if creator: + emit_sync( + audience_user_id=creator["id"], + event_code="REPORT_FAILED", + title="Report generation ran into a problem", + message="Open the report to retry or check details.", + action="NAVIGATE_REPORT", + level="urgent", + ref_project_id=project_id, + ref_report_id=report_id_str, + ) + except Exception as notif_err: + logger.warning(f"Failed to emit REPORT_FAILED: {notif_err}") return except Exception as e: From cfa758e14fe553c419623c9ee6494fad5b3519ef Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Tue, 21 Apr 2026 12:18:33 +0000 Subject: [PATCH 097/208] feat(inbox): close INVITE_ACCEPTED + TEAM_MEMBER_ADDED on remaining paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three emission sites that the roster flagged as pending. - Onboarding auto-accept (onboarding.py): pending invites resolved at first login emit INVITE_ACCEPTED → inviter. Previously only the /invites page and hash-link paths fired it. - accept_invite_by_hash (me.py): same INVITE_ACCEPTED notification — consistency across all three accept paths so the inviter's inbox reads the same regardless of how the invitee took the invite. - TEAM_MEMBER_ADDED → team admins on all three org-joining paths: workspace_invite direct add (invites.py), accept_my_invite (me.py), onboarding auto-accept. Each guards via `newly_joined_team` so accepting a second workspace invite inside a team you already belong to doesn't refire the "Sarah joined the team" notice. Coverage now: 21 live emissions + 2 piggybacks (downgrade-effect details rolled into TIER_DOWNGRADED, sticky-remove piggybacks on WORKSPACE_REMOVED). Remaining three are explicit product calls: INVITE_RESENT (email already covers — would feel like spam), TEAM_RENAMED (low signal, selector reflects it), CONVERSATION_FINISHED (volume too high for per-row inbox — needs digest treatment). Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/invites.py | 30 ++++++++- echo/server/dembrane/api/v2/me.py | 74 ++++++++++++++++++++++- echo/server/dembrane/api/v2/onboarding.py | 51 ++++++++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/echo/server/dembrane/api/v2/invites.py b/echo/server/dembrane/api/v2/invites.py index c5dd94f6..b8cbee55 100644 --- a/echo/server/dembrane/api/v2/invites.py +++ b/echo/server/dembrane/api/v2/invites.py @@ -141,7 +141,10 @@ async def invite_to_workspace( is_external = not body.is_org_member ws_org_id = ctx.workspace.get("org_id") - # If marked as org member, add them to the org too + # If marked as org member, add them to the org too. Track + # whether we freshly added them so the TEAM_MEMBER_ADDED + # notification only fires for genuinely new team joiners. + newly_joined_team = False if body.is_org_member and ws_org_id: existing_org_mem = await async_directus.get_items( "org_membership", @@ -159,6 +162,7 @@ async def invite_to_workspace( "role": "member", }) logger.info(f"Added {email} to org {ws_org_id} as member") + newly_joined_team = True await async_directus.create_item("workspace_membership", { "id": generate_uuid(), @@ -192,6 +196,30 @@ async def invite_to_workspace( ref_org_id=ws_org_id, ) + # TEAM_MEMBER_ADDED to team admins when the invitee is new + # to the team. Kept out of the workspace-only path (they're + # still a guest there, no team-roster change to announce). + if newly_joined_team and ws_org_id: + from dembrane.notifications import ( + audience_team_admins, + emit_to_audience, + ) + team_admin_ids = await audience_team_admins(ws_org_id) + team_row = await async_directus.get_item("org", ws_org_id) + team_name = (team_row or {}).get("name") or "the team" + new_member_name = ( + app_user.get("display_name") or email or "A new member" + ) + await emit_to_audience( + team_admin_ids, + actor_user_id=ctx.app_user_id, + event_code="TEAM_MEMBER_ADDED", + title=f"{new_member_name} joined {team_name}", + message="They're now a team member.", + action="NAVIGATE_TEAM_SETTINGS", + ref_org_id=ws_org_id, + ) + # Send a notification email inviter_name = "Your team" try: diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 4614d00e..12f9e78d 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -296,7 +296,11 @@ async def accept_my_invite(invite_id: str, auth: DependencyDirectusSession) -> d if not ws or ws.get("deleted_at"): raise HTTPException(status_code=404, detail="Workspace no longer exists") - # Add org membership if requested + # Add org membership if requested. Track whether we freshly created + # the row so the TEAM_MEMBER_ADDED notification only fires once per + # new team joiner (not every time an existing team member accepts + # a workspace invite). + newly_joined_team = False if invite.get("include_org_membership") and ws.get("org_id"): existing_org_mem = await async_directus.get_items( "org_membership", @@ -313,6 +317,7 @@ async def accept_my_invite(invite_id: str, auth: DependencyDirectusSession) -> d "user_id": app_user_id, "role": "member", }) + newly_joined_team = True # Create workspace membership (if not already) existing_ws_mem = await async_directus.get_items( @@ -354,6 +359,29 @@ async def accept_my_invite(invite_id: str, auth: DependencyDirectusSession) -> d ref_org_id=ws.get("org_id"), ) + # TEAM_MEMBER_ADDED — fires once, when the invitee is new to the + # team. Announces the new teammate to team admins. + if newly_joined_team and ws.get("org_id"): + from dembrane.notifications import ( + audience_team_admins, + emit_to_audience, + ) + team_admin_ids = await audience_team_admins(ws["org_id"]) + team_row = await async_directus.get_item("org", ws["org_id"]) + team_name = (team_row or {}).get("name") or "the team" + new_member_name = ( + app_user.get("display_name") or app_user.get("email") or "A new member" + ) + await emit_to_audience( + team_admin_ids, + actor_user_id=inviter_id, + event_code="TEAM_MEMBER_ADDED", + title=f"{new_member_name} joined {team_name}", + message="They're now a team member.", + action="NAVIGATE_TEAM_SETTINGS", + ref_org_id=ws["org_id"], + ) + return {"status": "success", "workspace_id": invite["workspace_id"]} @@ -548,6 +576,50 @@ async def accept_invite_by_hash( "accepted_at": now_iso, }) + # INVITE_ACCEPTED → the person who sent the invite. Mirrors the + # notification in accept_my_invite; same event regardless of + # whether the invitee came via the inbox page or the email link. + inviter_id = target_invite.get("invited_by") + if inviter_id and inviter_id != app_user_id: + from dembrane.notifications import emit + accepter_name = app_user.get("display_name") or my_email or "Someone" + await emit( + audience_user_id=inviter_id, + actor_user_id=app_user_id, + event_code="INVITE_ACCEPTED", + title=f"{accepter_name} joined {ws.get('name', 'your workspace')}", + message="They accepted your invite and can now collaborate.", + action="NAVIGATE_WORKSPACE_SETTINGS", + ref_workspace_id=target_invite["workspace_id"], + ref_org_id=ws.get("org_id"), + ) + + # TEAM_MEMBER_ADDED → team admins, but only when the invite granted + # org membership AND the invitee wasn't already on the team (i.e. + # we just created the org_membership row a few lines above). + if ( + target_invite.get("include_org_membership") + and ws.get("org_id") + and not (isinstance(existing_org_mem, list) and len(existing_org_mem) > 0) + ): + from dembrane.notifications import ( + audience_team_admins, + emit_to_audience, + ) + team_admin_ids = await audience_team_admins(ws["org_id"]) + team_row = await async_directus.get_item("org", ws["org_id"]) + team_name = (team_row or {}).get("name") or "the team" + new_member_name = app_user.get("display_name") or my_email or "A new member" + await emit_to_audience( + team_admin_ids, + actor_user_id=inviter_id, + event_code="TEAM_MEMBER_ADDED", + title=f"{new_member_name} joined {team_name}", + message="They're now a team member.", + action="NAVIGATE_TEAM_SETTINGS", + ref_org_id=ws["org_id"], + ) + return { "status": "success", "workspace_id": target_invite["workspace_id"], diff --git a/echo/server/dembrane/api/v2/onboarding.py b/echo/server/dembrane/api/v2/onboarding.py index 9be46a70..2b146199 100644 --- a/echo/server/dembrane/api/v2/onboarding.py +++ b/echo/server/dembrane/api/v2/onboarding.py @@ -161,6 +161,57 @@ async def complete_onboarding( "accepted_at": now, }) + # Notify the inviter (INVITE_ACCEPTED #3). Mirrors the + # notification fired by me.accept_my_invite — same event + # so the inviter sees a consistent inbox row regardless of + # how the invitee accepted. + inviter_id = invite.get("invited_by") + if inviter_id and inviter_id != app_user_id: + from dembrane.notifications import emit + display = ( + (await get_directus_user_profile(directus_user_id) or {}) + .get("display_name") or app_user_email or "Someone" + ) + await emit( + audience_user_id=inviter_id, + actor_user_id=app_user_id, + event_code="INVITE_ACCEPTED", + title=f"{display} joined {ws.get('name', 'your workspace')}", + message="They accepted your invite and can now collaborate.", + action="NAVIGATE_WORKSPACE_SETTINGS", + ref_workspace_id=ws_id, + ref_org_id=ws.get("org_id"), + ) + + # Notify team admins when a new person joins the team + # (TEAM_MEMBER_ADDED #16). Only fires when the invite + # included an org_membership grant AND the user didn't + # already belong to the team. + if is_org_invite and ws.get("org_id") and not ( + isinstance(existing_org_mem, list) and len(existing_org_mem) > 0 + ): + from dembrane.notifications import ( + audience_team_admins, + emit_to_audience, + ) + team_admins = await audience_team_admins(ws["org_id"]) + # Skip the actor (inviter) — they already know. + team_row = await async_directus.get_item("org", ws["org_id"]) + team_name = (team_row or {}).get("name") or "the team" + new_member_name = ( + (await get_directus_user_profile(directus_user_id) or {}) + .get("display_name") or app_user_email or "A new member" + ) + await emit_to_audience( + team_admins, + actor_user_id=inviter_id, + event_code="TEAM_MEMBER_ADDED", + title=f"{new_member_name} joined {team_name}", + message="They're now a team member.", + action="NAVIGATE_TEAM_SETTINGS", + ref_org_id=ws["org_id"], + ) + # ── Step 3: Check if user has their own projects ── user_projects = await async_directus.get_items( From bc3310c63765f1615d5bd62da8d9fb664dc7fa38 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 09:44:52 +0000 Subject: [PATCH 098/208] =?UTF-8?q?chore(workspaces):=20in-flight=20polish?= =?UTF-8?q?=20=E2=80=94=20notifications=20unification,=20email=20templates?= =?UTF-8?q?,=20auth=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batches in-flight work that had accumulated on the workspaces branch before the workspaces-validate session started. No new features; mostly cleanup + copy polish + dead-code removal. Notifications - Consolidate the notifications service (server/dembrane/notifications.py) and API surface (api/v2/notifications.py) around the unified inbox model. - Swap the old NotificationsDrawer for the new inbox/Inbox.tsx surface; Header wires to the new drawer. - Remove the announcement subsystem (6 files) — subsumed by the inbox. - tasks.py trims a stale notification call site. Email templates - Rework the shared _layout.html header/footer/typography. - Polish the four product templates (workspace_invite, workspace_added, upgrade_request) — brand pass, plain-text fallbacks tightened. - Collapse Directus transactional templates (email-base.liquid, password reset, invite, registration, report-notification en+nl) onto a smaller shared shell. Auth copy - Register / CheckYourEmail / auth hooks — copy pass per brand guide. Scripts - create_schema.py: in-flight tweaks to the workspaces schema pass. - preseed_workspace.py: align with the current schema shape. Adds frontend/src/lib/avatar.ts (avatar URL helper used by the selector and team routes). Excludes docs/workspaces/ and docs/workspaces-validate/ per this session's decision to keep doc churn out of code commits. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/directus/templates/email-base.liquid | 420 +++--------------- echo/directus/templates/password-reset.liquid | 50 +-- .../templates/report-notification-en.liquid | 98 ++-- .../templates/report-notification-nl.liquid | 100 ++--- echo/directus/templates/user-invite.liquid | 49 +- .../templates/user-registration.liquid | 50 +-- .../announcement/AnnouncementDrawerHeader.tsx | 60 --- .../announcement/AnnouncementErrorState.tsx | 40 -- .../announcement/AnnouncementIcon.tsx | 82 ---- .../announcement/AnnouncementSkeleton.tsx | 67 --- .../components/announcement/Announcements.tsx | 323 -------------- .../components/announcement/WhatsNewItem.tsx | 67 --- .../src/components/auth/hooks/index.ts | 38 +- echo/frontend/src/components/inbox/Inbox.tsx | 420 ++++++++++++++++++ .../frontend/src/components/layout/Header.tsx | 20 +- .../notifications/NotificationsDrawer.tsx | 239 ---------- .../components/project/ProjectListItem.tsx | 3 +- .../project/ProjectSharingModal.tsx | 3 +- .../project/ProjectSharingStrip.tsx | 3 +- echo/frontend/src/hooks/useNotifications.ts | 23 +- echo/frontend/src/lib/avatar.ts | 18 + .../src/routes/auth/CheckYourEmail.tsx | 15 +- echo/frontend/src/routes/auth/Register.tsx | 85 +++- echo/frontend/src/routes/team/TeamRoute.tsx | 3 +- echo/scripts/create_schema.py | 136 +++--- echo/scripts/preseed_workspace.py | 18 +- echo/server/dembrane/api/v2/notifications.py | 263 +++-------- echo/server/dembrane/notifications.py | 170 ++++--- echo/server/dembrane/tasks.py | 1 - echo/server/email_templates/_layout.html | 88 ++-- .../email_templates/upgrade_request.html | 22 +- .../email_templates/upgrade_request.txt | 3 +- .../email_templates/workspace_added.html | 7 +- .../email_templates/workspace_added.txt | 3 +- .../email_templates/workspace_invite.html | 15 +- .../email_templates/workspace_invite.txt | 7 +- 36 files changed, 1088 insertions(+), 1921 deletions(-) delete mode 100644 echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx delete mode 100644 echo/frontend/src/components/announcement/AnnouncementErrorState.tsx delete mode 100644 echo/frontend/src/components/announcement/AnnouncementIcon.tsx delete mode 100644 echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx delete mode 100644 echo/frontend/src/components/announcement/Announcements.tsx delete mode 100644 echo/frontend/src/components/announcement/WhatsNewItem.tsx create mode 100644 echo/frontend/src/components/inbox/Inbox.tsx delete mode 100644 echo/frontend/src/components/notifications/NotificationsDrawer.tsx create mode 100644 echo/frontend/src/lib/avatar.ts diff --git a/echo/directus/templates/email-base.liquid b/echo/directus/templates/email-base.liquid index fd63a6df..9919c0d6 100644 --- a/echo/directus/templates/email-base.liquid +++ b/echo/directus/templates/email-base.liquid @@ -1,370 +1,88 @@ +{% comment %} + Shared layout for Directus-sent emails (registration verify, password + reset, Directus user invite). Matches the designer's "Verify Email C" + direction: letter-style card on parchment, DM Sans Light, royal-blue + pill CTA, crowd banner pinned to the card bottom. Children provide + content via {% block content %}. +{% endcomment %} - {{ projectName }} Email Service - + {{ projectName }} + + + + - - - - - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ -
    + + + + +
    + + + + + + + + + + - - + + + + +
    + dembrane +
    + {% block content %}{{ html }}{% endblock %} - - - - - - -
    - - - - - - - - + -

    All the best,

    -
    - - - - - - -
    - -
    -
    -
    - {% block content %}{{ html }}{% endblock %} -
    +

    — the dembrane team

    +
    - - - - -
    - - -

    - The dembrane team
    dembrane - conversations that matter -

    -
    -
    -
    - -
    + +
    + +
    - \ No newline at end of file + diff --git a/echo/directus/templates/password-reset.liquid b/echo/directus/templates/password-reset.liquid index 4c5e2191..9e0f63a0 100644 --- a/echo/directus/templates/password-reset.liquid +++ b/echo/directus/templates/password-reset.liquid @@ -1,40 +1,24 @@ {% layout "email-base" %} {% block content %} -

    Reset your dembrane password

    +

    Reset your password.

    -

    - We got a request to reset the password for your dembrane account. - Click the button below to set a new one. If you didn't request this, you can ignore this email. +

    + We got a request to set a new password for your dembrane account. The link expires in 24 hours.

    - - - Reset password - - + + +
    + Reset password +
    -

    This link will expire in 24 hours.

    +

    + Or paste this into your browser:
    + {{url}} +

    + +

    + Didn't request this? Ignore this email — your password stays as it is. +

    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/echo/directus/templates/report-notification-en.liquid b/echo/directus/templates/report-notification-en.liquid index f2da0eac..c4ef684b 100644 --- a/echo/directus/templates/report-notification-en.liquid +++ b/echo/directus/templates/report-notification-en.liquid @@ -3,76 +3,70 @@ - dembrane Report Notification - + + dembrane · report ready - - + +
    -
    - + diff --git a/echo/directus/templates/report-notification-nl.liquid b/echo/directus/templates/report-notification-nl.liquid index 3e119c16..29e98fec 100644 --- a/echo/directus/templates/report-notification-nl.liquid +++ b/echo/directus/templates/report-notification-nl.liquid @@ -1,78 +1,72 @@ - + - dembrane Report Notification - + + dembrane · je rapport is klaar - -
    + - + + + + + +
    - - - - - -
    - dembrane logo - -

    dembrane

    -
    - -

    - -

    +

    + dembrane +
    +

    {% if conversation_name and conversation_name != "" %} - A report has been created for the conversation "{{ conversation_name }}" that you contributed to. + Your report on "{{ conversation_name }}" is ready. {% else %} - A report has been created that you contributed to. + Your report is ready. {% endif %} -

    + -

    - Take a look here: View Report +

    + Take a look — we've pulled together what came out of the conversation you contributed to.

    -

    Thanks for being part of this.

    -

    The dembrane team

    - -

    - This is an automated message, so replies aren't monitored. - Questions? Reach us at - info@dembrane.com. -

    + + +
    + View report +
    -
    +

    — the dembrane team

    -

    - If you no longer wish to receive these notifications, click - Unsubscribe +

    + This is an automated message, so replies aren't monitored. Questions? + Reach us at info@dembrane.com.

    -

    - dembrane - conversations that matter +

    + Unsubscribe from these notifications.

    + +
    + +
    -
    - + diff --git a/echo/directus/templates/user-invite.liquid b/echo/directus/templates/user-invite.liquid index fbe976fc..26d2d7d7 100644 --- a/echo/directus/templates/user-invite.liquid +++ b/echo/directus/templates/user-invite.liquid @@ -1,37 +1,24 @@ {% layout "email-base" %} {% block content %} -

    You've been invited to dembrane

    +

    You're invited to dembrane.

    -

    - Click the button below to accept the invitation and get started. +

    + Click below to accept the invitation and get started with {{ projectName }}.

    - - - Join {{ projectName }} - - +
    + - + + + + + +
    - - - - - -
    - dembrane logo - -

    dembrane

    -
    - -

    - -

    +

    + dembrane +
    +

    {% if conversation_name and conversation_name != "" %} - Er is een rapport aangemaakt voor het gesprek "{{ conversation_name }}" waar je aan hebt bijgedragen. + Je rapport over "{{ conversation_name }}" is klaar. {% else %} - Er is een rapport aangemaakt waar je aan hebt bijgedragen. + Je rapport is klaar. {% endif %} -

    + -

    - Bekijk het hier: Rapport bekijken +

    + Bekijk wat er uit het gesprek is gekomen waar je aan hebt bijgedragen.

    -

    Bedankt voor je bijdrage.

    -

    Het dembrane team

    - -

    - Beantwoord deze e-mail niet. Dit is een automatisch bericht en antwoorden worden niet gelezen. - Heb je vragen? Neem contact met ons op via - info@dembrane.com. -

    + + +
    + Rapport bekijken +
    -
    +

    — het dembrane team

    -

    - Wil je deze meldingen niet meer ontvangen? Klik op - Afmelden +

    + Dit is een automatisch bericht, reacties worden niet gelezen. Vragen? + Mail ons op info@dembrane.com.

    -

    - dembrane - conversations that matter +

    + Afmelden voor deze meldingen.

    + +
    + +
    + Join {{ projectName }} +
    -{% endblock %} \ No newline at end of file +

    + Or paste this into your browser:
    + {{url}} +

    + +

    + Didn't expect this? Ignore this email — nothing will happen. +

    + +{% endblock %} diff --git a/echo/directus/templates/user-registration.liquid b/echo/directus/templates/user-registration.liquid index bf1cf4d6..bdd7e627 100644 --- a/echo/directus/templates/user-registration.liquid +++ b/echo/directus/templates/user-registration.liquid @@ -1,38 +1,24 @@ -{% layout 'email-base' %} +{% layout "email-base" %} {% block content %} -{% block content %} +

    One tap and you're in.

    -

    Verify your email address

    +

    + Confirm your email to finish setting up your dembrane account. The link expires in 24 hours. +

    + + + +
    + Verify email +
    -

    - Thanks for joining dembrane. Click the button below to verify your email and finish setting up your account. - If you didn't sign up, you can ignore this email. +

    + Or paste this into your browser:
    + {{url}}

    - - - Verify email - - +

    + Didn't sign up? Ignore this email — nothing will happen. +

    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx b/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx deleted file mode 100644 index 0faa6d0d..00000000 --- a/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { t } from "@lingui/core/macro"; -import { Trans } from "@lingui/react/macro"; -import { ActionIcon, Button, Group, Stack, Text } from "@mantine/core"; -import { X } from "@phosphor-icons/react"; -import { testId } from "@/lib/testUtils"; -import { useUnreadAnnouncements } from "./hooks"; - -export const AnnouncementDrawerHeader = ({ - onClose, - onMarkAllAsRead, - isPending, -}: { - onClose: () => void; - onMarkAllAsRead: () => void; - isPending: boolean; -}) => { - const { data: unreadCount } = useUnreadAnnouncements(); - const hasUnreadAnnouncements = unreadCount && unreadCount > 0; - - return ( - - - - Announcements - - - - - - - {hasUnreadAnnouncements && ( - - {unreadCount}{" "} - {unreadCount === 1 - ? t`unread announcement` - : t`unread announcements`} - - )} - {hasUnreadAnnouncements && ( - - )} - - - ); -}; diff --git a/echo/frontend/src/components/announcement/AnnouncementErrorState.tsx b/echo/frontend/src/components/announcement/AnnouncementErrorState.tsx deleted file mode 100644 index 592d8192..00000000 --- a/echo/frontend/src/components/announcement/AnnouncementErrorState.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Trans } from "@lingui/react/macro"; -import { Alert, Box, Button, Stack, Text } from "@mantine/core"; -import { IconAlertCircle, IconRefresh } from "@tabler/icons-react"; - -interface AnnouncementErrorStateProps { - onRetry: () => void; - isLoading?: boolean; -} - -export const AnnouncementErrorState = ({ - onRetry, - isLoading = false, -}: AnnouncementErrorStateProps) => { - return ( - - } - color="red" - variant="light" - title={Error loading announcements} - > - - - Failed to get announcements - - - - - - ); -}; diff --git a/echo/frontend/src/components/announcement/AnnouncementIcon.tsx b/echo/frontend/src/components/announcement/AnnouncementIcon.tsx deleted file mode 100644 index 409166b2..00000000 --- a/echo/frontend/src/components/announcement/AnnouncementIcon.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { ActionIcon, Box, Group, Indicator, Loader, Text } from "@mantine/core"; -import { FlagBannerIcon } from "@phosphor-icons/react"; -import { useAnnouncementDrawer } from "@/components/announcement/hooks"; -import { getTranslatedContent } from "@/components/announcement/hooks/useProcessedAnnouncements"; -import { useLanguage } from "@/hooks/useLanguage"; -import { testId } from "@/lib/testUtils"; -import { useLatestAnnouncement, useUnreadAnnouncements } from "./hooks"; - -export const AnnouncementIcon = () => { - const { open } = useAnnouncementDrawer(); - const { language } = useLanguage(); - const { data: latestAnnouncement, isLoading: isLoadingLatest } = - useLatestAnnouncement(); - const { data: unreadCount, isLoading: isLoadingUnread } = - useUnreadAnnouncements(); - - const title = latestAnnouncement - ? getTranslatedContent(latestAnnouncement as Announcement, language).title - : ""; - - const isUnread = latestAnnouncement - ? !latestAnnouncement.activity?.some( - (activity: AnnouncementActivity) => activity.read === true, - ) - : false; - - const showPreview = - isUnread && title && latestAnnouncement?.level === "info"; - - const isLoading = isLoadingLatest || isLoadingUnread; - - return ( - - - - {unreadCount || 0} - - } - size={20} - disabled={(unreadCount || 0) === 0} - withBorder - > - - {isLoading ? ( - - ) : ( - - )} - - - - - {showPreview && ( - - - {title} - - - )} - - ); -}; diff --git a/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx b/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx deleted file mode 100644 index 3e06a361..00000000 --- a/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Box, Group, Stack, ThemeIcon } from "@mantine/core"; -import { IconInfoCircle } from "@tabler/icons-react"; - -export const AnnouncementSkeleton = () => ( - - {[1, 2, 3, 4, 5, 6].map((i) => ( - - - - - {/* Use a generic icon skeleton */} - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -); diff --git a/echo/frontend/src/components/announcement/Announcements.tsx b/echo/frontend/src/components/announcement/Announcements.tsx deleted file mode 100644 index 4725a4f0..00000000 --- a/echo/frontend/src/components/announcement/Announcements.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import { Trans } from "@lingui/react/macro"; -import { - Box, - Center, - Collapse, - Divider, - Group, - Loader, - ScrollArea, - Stack, - Text, - ThemeIcon, - UnstyledButton, -} from "@mantine/core"; -import { - CaretDown, - CaretUp, - CheckCircle, - Sparkle, -} from "@phosphor-icons/react"; -import { useEffect, useMemo, useState } from "react"; -import { useInView } from "react-intersection-observer"; -import { useAnnouncementDrawer } from "@/components/announcement/hooks"; -import { - useProcessedAnnouncements, - useWhatsNewProcessed, -} from "@/components/announcement/hooks/useProcessedAnnouncements"; -import { useLanguage } from "@/hooks/useLanguage"; -import { analytics } from "@/lib/analytics"; -import { AnalyticsEvents as events } from "@/lib/analyticsEvents"; -import { testId } from "@/lib/testUtils"; -import { Drawer } from "../common/Drawer"; -import { AnnouncementDrawerHeader } from "./AnnouncementDrawerHeader"; -import { AnnouncementErrorState } from "./AnnouncementErrorState"; -import { AnnouncementItem } from "./AnnouncementItem"; -import { AnnouncementSkeleton } from "./AnnouncementSkeleton"; -import { WhatsNewItem } from "./WhatsNewItem"; -import { - useInfiniteAnnouncements, - useMarkAllAsReadMutation, - useMarkAsReadMutation, - useMarkAsUnreadMutation, - useWhatsNewAnnouncements, -} from "./hooks"; - -export const Announcements = () => { - const { isOpen, close } = useAnnouncementDrawer(); - const { language } = useLanguage(); - const markAsReadMutation = useMarkAsReadMutation(); - const markAsUnreadMutation = useMarkAsUnreadMutation(); - const markAllAsReadMutation = useMarkAllAsReadMutation(); - const [openedOnce, setOpenedOnce] = useState(false); - const [readExpanded, setReadExpanded] = useState(false); - const [whatsNewExpanded, setWhatsNewExpanded] = useState(false); - - const { ref: loadMoreRef, inView } = useInView(); - - // Track when drawer is opened for the first time - useEffect(() => { - if (isOpen && !openedOnce) { - setOpenedOnce(true); - try { - analytics.trackEvent(events.ANNOUNCEMENT_CREATED); - } catch (error) { - console.warn("Analytics tracking failed:", error); - } - } - }, [isOpen, openedOnce]); - - const { - data: announcementsData, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isLoading, - isError, - refetch, - } = useInfiniteAnnouncements({ - enabled: openedOnce, - options: { - initialLimit: 10, - }, - }); - - const { data: whatsNewData } = useWhatsNewAnnouncements({ - enabled: openedOnce, - }); - - // Flatten all announcements from all pages - const allAnnouncements = - announcementsData?.pages.flatMap( - (page) => (page as { announcements: Announcement[] }).announcements, - ) ?? []; - - // Process announcements with translations and read status - const processedAnnouncements = useProcessedAnnouncements( - allAnnouncements, - language, - ); - - // Split into unread and read - const unreadAnnouncements = useMemo( - () => processedAnnouncements.filter((a) => !a.read), - [processedAnnouncements], - ); - const readAnnouncements = useMemo( - () => processedAnnouncements.filter((a) => a.read), - [processedAnnouncements], - ); - - // Auto-expand read section when there are no unread items - // biome-ignore lint/correctness/useExhaustiveDependencies: only react to unread/read count changes - useEffect(() => { - if (unreadAnnouncements.length === 0 && readAnnouncements.length > 0) { - setReadExpanded(true); - } - }, [unreadAnnouncements.length, readAnnouncements.length]); - - // Process "What's new" announcements, excluding those already in the main list - const whatsNewRaw = useWhatsNewProcessed(whatsNewData ?? [], language); - const whatsNewAnnouncements = useMemo(() => { - const mainIds = new Set(processedAnnouncements.map((a) => a.id)); - return whatsNewRaw.filter((a) => !mainIds.has(a.id)); - }, [whatsNewRaw, processedAnnouncements]); - - // Load more announcements when user scrolls to bottom - useEffect(() => { - if (inView && hasNextPage && !isFetchingNextPage) { - fetchNextPage(); - } - }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); - - const handleMarkAsRead = async (id: string) => { - markAsReadMutation.mutate({ announcementId: id }); - }; - - const handleMarkAsUnread = async (id: string, activityIds: string[]) => { - markAsUnreadMutation.mutate({ announcementId: id, activityIds }); - }; - - const handleMarkAllAsRead = async () => { - markAllAsReadMutation.mutate(); - }; - - const handleRetry = () => { - refetch(); - }; - - return ( - - } - classNames={{ - body: "p-0", - content: "border-0", - header: "border-b", - title: "px-3 w-full", - }} - withCloseButton={false} - styles={{ - content: { - maxWidth: "95%", - }, - }} - {...testId("announcement-drawer")} - > - - - - {isError ? ( - - ) : isLoading ? ( - - ) : processedAnnouncements.length === 0 && - whatsNewAnnouncements.length === 0 ? ( - - - No announcements available - - - ) : ( - <> - {/* Unread announcements */} - {unreadAnnouncements.map((announcement, index) => ( - - ))} - - {isFetchingNextPage && ( -
    - -
    - )} - - {/* Earlier (read) section */} - {readAnnouncements.length > 0 && ( - <> - - setReadExpanded(!readExpanded) - } - > - - - - Earlier - - {readExpanded ? ( - - ) : ( - - )} - - - } - labelPosition="left" - /> - - - - {readAnnouncements.map( - (announcement, index) => ( - - ), - )} - - - - )} - - {/* Release notes */} - {whatsNewAnnouncements.length > 0 && ( - <> - - setWhatsNewExpanded(!whatsNewExpanded) - } - > - - - - Release notes - - {whatsNewExpanded ? ( - - ) : ( - - )} - - - } - labelPosition="left" - /> - - - - {whatsNewAnnouncements.map((announcement) => ( - - ))} - - - - )} - - {/* Infinite scroll sentinel */} -
    - - )} - - - - - ); -}; diff --git a/echo/frontend/src/components/announcement/WhatsNewItem.tsx b/echo/frontend/src/components/announcement/WhatsNewItem.tsx deleted file mode 100644 index a6ecf3c8..00000000 --- a/echo/frontend/src/components/announcement/WhatsNewItem.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { - Box, - Collapse, - Group, - Stack, - Text, - ThemeIcon, - UnstyledButton, -} from "@mantine/core"; -import { CaretDown, CaretRight, Sparkle } from "@phosphor-icons/react"; -import { useState } from "react"; -import { Markdown } from "@/components/common/Markdown"; -import { testId } from "@/lib/testUtils"; -import type { ProcessedAnnouncement } from "./hooks/useProcessedAnnouncements"; -import { useFormatDate } from "./utils/dateUtils"; - -interface WhatsNewItemProps { - announcement: ProcessedAnnouncement; -} - -export const WhatsNewItem = ({ announcement }: WhatsNewItemProps) => { - const [expanded, setExpanded] = useState(false); - const formatDate = useFormatDate(); - - return ( - - setExpanded(!expanded)} - w="100%" - > - - {expanded ? ( - - ) : ( - - )} - - - - - {announcement.title} - - - {formatDate(announcement.created_at)} - - - - - - - - - - - ); -}; diff --git a/echo/frontend/src/components/auth/hooks/index.ts b/echo/frontend/src/components/auth/hooks/index.ts index f17ad578..01ef84eb 100644 --- a/echo/frontend/src/components/auth/hooks/index.ts +++ b/echo/frontend/src/components/auth/hooks/index.ts @@ -119,33 +119,37 @@ export const useVerifyMutation = (doRedirect = true) => { }; export const useRegisterMutation = () => { - const navigate = useI18nNavigate(); return useMutation({ mutationFn: async (payload: Parameters) => { try { - const response = await directus.request(registerUser(...payload)); - return response; + return await directus.request(registerUser(...payload)); } catch (e) { + // Map the raw Directus error to a user-facing message, then + // re-throw so react-query marks the mutation as failed and + // onError / the inline Alert both fire. Previously only the + // "no permission" case re-threw; every other failure fell + // through as undefined and looked like a success, which + // bounced users to the "Check your email" step even when + // registration actually failed (e.g. validation errors). + let mapped: Error = new Error("Registration failed"); try { throwWithMessage(e); } catch (inner) { - if (inner instanceof Error) { - if (inner.message === "You don't have permission to access this.") { - throw new Error( - "Oops! It seems your email is not eligible for registration at this time. Please consider joining our waitlist for future updates!", - ); - } - } + if (inner instanceof Error) mapped = inner; + } + if (mapped.message === "You don't have permission to access this.") { + throw new Error( + "Oops! It seems your email is not eligible for registration at this time. Please consider joining our waitlist for future updates!", + ); } + throw mapped; } }, - onError: (e) => { - toast.error(e.message); - }, - onSuccess: () => { - toast.success("Please check your email to verify your account."); - navigate("/check-your-email"); - }, + // Success handling lives inline on the Register page — the + // stepper advances to step 2 ("Check your email"). No toast + + // no redirect, since the inline state already shows the user + // exactly what's next. Failures surface via the inline Alert + // that reads from `registerMutation.error`. }); }; diff --git a/echo/frontend/src/components/inbox/Inbox.tsx b/echo/frontend/src/components/inbox/Inbox.tsx new file mode 100644 index 00000000..cd55e14d --- /dev/null +++ b/echo/frontend/src/components/inbox/Inbox.tsx @@ -0,0 +1,420 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + ActionIcon, + Avatar, + Badge, + Box, + Button, + Center, + Drawer, + Group, + Indicator, + Loader, + ScrollArea, + Stack, + Tabs, + Text, + UnstyledButton, +} from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { IconBell, IconCheck } from "@tabler/icons-react"; +import { formatRelative } from "date-fns"; +import { useMemo, useState } from "react"; +import { useInView } from "react-intersection-observer"; +import { + useInfiniteAnnouncements, + useMarkAllAsReadMutation as useAnnouncementsMarkAllAsReadMutation, + useMarkAsReadMutation as useAnnouncementMarkAsReadMutation, + useMarkAsUnreadMutation as useAnnouncementMarkAsUnreadMutation, + useUnreadAnnouncements, +} from "@/components/announcement/hooks"; +import { useProcessedAnnouncements } from "@/components/announcement/hooks/useProcessedAnnouncements"; +import { AnnouncementItem } from "@/components/announcement/AnnouncementItem"; +import { + resolveNotificationHref, + useMarkAllNotificationsRead, + useMarkNotificationRead, + useNotifications, + useUnreadNotificationCount, + type NotificationRow, +} from "@/hooks/useNotifications"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { useLanguage } from "@/hooks/useLanguage"; +import { avatarUrl } from "@/lib/avatar"; + +/** + * Unified Inbox drawer — single entry point in the header. + * + * Replaces the separate announcement icon + notifications icon pair with + * one bell that opens a two-tab drawer: + * + * For you Personal notifications — events that target this user + * specifically (workspace added, role changed, report + * ready, destructive events). Rendered with severity + * styling per the designer spec in `docs/workspaces/ + * inbox.html`. + * + * Announcements Admin broadcasts — existing `announcement` collection. + * Never action-required, never destructive by design. + * + * The unread badge sums both streams so users only track one number. + * "Mark all read" applies to the active tab so users don't nuke + * announcements when they meant to clear notifications (or vice versa). + */ +export const Inbox = () => { + const [opened, { open, close }] = useDisclosure(false); + const [activeTab, setActiveTab] = useState<"for-you" | "announcements">( + "for-you", + ); + const navigate = useI18nNavigate(); + const { language } = useLanguage(); + + const { data: notifications = [], isLoading: loadingNotifs } = + useNotifications(); + const { data: unreadNotifs = 0 } = useUnreadNotificationCount(); + const markNotifRead = useMarkNotificationRead(); + const markAllNotifsRead = useMarkAllNotificationsRead(); + + const { ref: loadMoreRef, inView } = useInView(); + const { + data: announcementsData, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading: loadingAnnouncements, + } = useInfiniteAnnouncements({ + enabled: opened, + options: { initialLimit: 10 }, + }); + const { data: unreadAnnouncements = 0 } = useUnreadAnnouncements(); + const markAnnouncementRead = useAnnouncementMarkAsReadMutation(); + const markAnnouncementUnread = useAnnouncementMarkAsUnreadMutation(); + const markAllAnnouncementsRead = useAnnouncementsMarkAllAsReadMutation(); + + const allAnnouncements = + announcementsData?.pages.flatMap( + (page) => (page as { announcements: Announcement[] }).announcements, + ) ?? []; + const processedAnnouncements = useProcessedAnnouncements( + allAnnouncements, + language, + ); + const unreadAnnouncementRows = useMemo( + () => processedAnnouncements.filter((a) => !a.read), + [processedAnnouncements], + ); + const readAnnouncementRows = useMemo( + () => processedAnnouncements.filter((a) => a.read), + [processedAnnouncements], + ); + + // Infinite-scroll sentinel + if (inView && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + + const totalUnread = unreadNotifs + unreadAnnouncements; + + const handleNotificationClick = (row: NotificationRow) => { + if (!row.read) markNotifRead.mutate(row.id); + const href = resolveNotificationHref(row); + if (href) { + navigate(href); + close(); + } + }; + + const handleMarkAllReadForActiveTab = () => { + if (activeTab === "for-you") { + markAllNotifsRead.mutate(); + } else { + markAllAnnouncementsRead.mutate(); + } + }; + + const markAllPending = + activeTab === "for-you" + ? markAllNotifsRead.isPending + : markAllAnnouncementsRead.isPending; + + return ( + <> + 0 ? totalUnread : undefined} + disabled={totalUnread === 0} + withBorder + > + + + + + + + + Inbox + + + + } + > + + setActiveTab((value as "for-you" | "announcements") ?? "for-you") + } + variant="default" + keepMounted={false} + > + + 0 ? ( + + {unreadNotifs} + + ) : null + } + > + For you + + 0 ? ( + + {unreadAnnouncements} + + ) : null + } + > + Announcements + + + + + + {loadingNotifs ? ( +
    + +
    + ) : notifications.length === 0 ? ( +
    + + + + You're all caught up. + + +
    + ) : ( + + {notifications.map((row) => ( + handleNotificationClick(row)} + /> + ))} + + )} +
    +
    + + + + {loadingAnnouncements ? ( +
    + +
    + ) : processedAnnouncements.length === 0 ? ( +
    + + Nothing from dembrane right now. + +
    + ) : ( + + {unreadAnnouncementRows.map((a, index) => ( + + markAnnouncementRead.mutate({ announcementId: id }) + } + onMarkAsUnread={(id, activityIds) => + markAnnouncementUnread.mutate({ + announcementId: id, + activityIds, + }) + } + index={index} + /> + ))} + {readAnnouncementRows.map((a, index) => ( + + markAnnouncementRead.mutate({ announcementId: id }) + } + onMarkAsUnread={(id, activityIds) => + markAnnouncementUnread.mutate({ + announcementId: id, + activityIds, + }) + } + index={index} + /> + ))} + {isFetchingNextPage && ( +
    + +
    + )} +
    + + )} + + + + + + ); +}; + +function NotificationRowItem({ + row, + onClick, +}: { + row: NotificationRow; + onClick: () => void; +}) { + const hasAction = resolveNotificationHref(row) !== null; + const createdLabel = row.created_at + ? formatRelative(new Date(row.created_at), new Date()) + : ""; + const isDestructive = row.severity === "destructive"; + const isActionRequired = row.severity === "action_required"; + const unreadBg = isDestructive + ? "rgba(192,57,43,0.045)" + : isActionRequired + ? "rgba(65,105,225,0.04)" + : "rgba(65,105,225,0.03)"; + + return ( + + + {row.actor_user_id ? ( + + {(row.actor_name || "?").slice(0, 2).toUpperCase()} + + ) : ( + + + + )} + + + + + {row.title} + + {!row.read && ( + + )} + {isActionRequired && ( + + Action needed + + )} + + {row.scope && ( + + {row.scope} + + )} + {row.message && ( + + {row.message} + + )} + {createdLabel && ( + + {createdLabel} + + )} + + + + ); +} diff --git a/echo/frontend/src/components/layout/Header.tsx b/echo/frontend/src/components/layout/Header.tsx index dd7c635a..7580d314 100644 --- a/echo/frontend/src/components/layout/Header.tsx +++ b/echo/frontend/src/components/layout/Header.tsx @@ -41,10 +41,8 @@ import { useWhitelabelLogo } from "@/hooks/useWhitelabelLogo"; import { analytics } from "@/lib/analytics"; import { AnalyticsEvents as events } from "@/lib/analyticsEvents"; import { testId } from "@/lib/testUtils"; -import { AnnouncementIcon } from "../announcement/AnnouncementIcon"; -import { Announcements } from "../announcement/Announcements"; -import { NotificationsDrawer } from "../notifications/NotificationsDrawer"; import { TopAnnouncementBar } from "../announcement/TopAnnouncementBar"; +import { Inbox } from "../inbox/Inbox"; import { FeedbackPortalModal } from "../common/FeedbackPortalModal"; import { Logo } from "../common/Logo"; import { UserAvatar } from "../common/UserAvatar"; @@ -174,16 +172,12 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => { {!loading && isAuthenticated && user ? ( - {ENABLE_ANNOUNCEMENTS && ( - <> - - - - )} - {/* Personal notifications — sibling of the announcement - icon. Collapses into one consolidated "Inbox" icon - when the designer's inbox pattern lands. */} - + {/* Unified Inbox — one bell, two tabs (For you + + Announcements). Replaces the prior split icons. + ENABLE_ANNOUNCEMENTS still controls whether the + broadcast channel is live, but the bell itself + stays so personal notifications remain reachable. */} + { - const [opened, { open, close }] = useDisclosure(false); - const navigate = useI18nNavigate(); - const { data: notifications = [], isLoading } = useNotifications(); - const { data: unreadCount = 0 } = useUnreadNotificationCount(); - const markRead = useMarkNotificationRead(); - const markAllRead = useMarkAllNotificationsRead(); - - const handleClick = (row: NotificationRow) => { - // Flip read state optimistically via the mutation, then resolve the - // action into a URL. Static copy ("NONE") notifications stay put. - if (!row.read) { - markRead.mutate(row.id); - } - const href = resolveNotificationHref(row); - if (href) { - navigate(href); - close(); - } - }; - - return ( - <> - 0 ? unreadCount : undefined} - disabled={unreadCount === 0} - withBorder - > - - - - - - - - Notifications - - {unreadCount > 0 && ( - - )} - - } - > - {isLoading ? ( -
    - -
    - ) : notifications.length === 0 ? ( -
    - - - - You're all caught up. - - -
    - ) : ( - - {notifications.map((row) => ( - handleClick(row)} - /> - ))} - - )} -
    - - ); -}; - -function NotificationItem({ - row, - onClick, -}: { - row: NotificationRow; - onClick: () => void; -}) { - const title = row.translation?.title ?? row.event_code; - const message = row.translation?.message ?? ""; - const hasAction = resolveNotificationHref(row) !== null; - const createdLabel = row.created_at - ? formatRelative(new Date(row.created_at), new Date()) - : ""; - - return ( - - - {/* Actor avatar when available; otherwise level-coloured dot - so the row still has a visual anchor. */} - {row.actor_user_id ? ( - - {(row.actor_name || "?").slice(0, 2).toUpperCase()} - - ) : ( - - - - )} - - - - - {title} - - {!row.read && ( - - )} - {row.level === "urgent" && ( - - Urgent - - )} - - {message && ( - - {message} - - )} - {createdLabel && ( - - {createdLabel} - - )} - - - - ); -} diff --git a/echo/frontend/src/components/project/ProjectListItem.tsx b/echo/frontend/src/components/project/ProjectListItem.tsx index 40d5a940..9f2ce714 100644 --- a/echo/frontend/src/components/project/ProjectListItem.tsx +++ b/echo/frontend/src/components/project/ProjectListItem.tsx @@ -5,6 +5,7 @@ import { IconLock, IconPin, IconPinFilled } from "@tabler/icons-react"; import { formatRelative } from "date-fns"; import type { PropsWithChildren } from "react"; import { Icons } from "@/icons"; +import { avatarUrl } from "@/lib/avatar"; import { testId } from "@/lib/testUtils"; import { I18nLink } from "../common/i18nLink"; @@ -64,7 +65,7 @@ function AccessBubbles({ project }: { project: Project }) { key={`${p.display_name}-${i}`} size="sm" radius="xl" - src={p.avatar ?? undefined} + src={avatarUrl(p.avatar, 48)} aria-label={p.display_name || t`Unknown`} > {(p.display_name || "?").slice(0, 2).toUpperCase()} diff --git a/echo/frontend/src/components/project/ProjectSharingModal.tsx b/echo/frontend/src/components/project/ProjectSharingModal.tsx index 40cddf7c..fe9eb434 100644 --- a/echo/frontend/src/components/project/ProjectSharingModal.tsx +++ b/echo/frontend/src/components/project/ProjectSharingModal.tsx @@ -22,6 +22,7 @@ import { useRevokeProjectShare, useSetProjectVisibility, } from "@/hooks/useProjectSharing"; +import { avatarUrl } from "@/lib/avatar"; interface ProjectSharingModalProps { projectId: string; @@ -169,7 +170,7 @@ export function ProjectSharingModal({ )} {shares?.map((share) => ( - + {(share.display_name || share.email || "?") .slice(0, 2) .toUpperCase()} diff --git a/echo/frontend/src/components/project/ProjectSharingStrip.tsx b/echo/frontend/src/components/project/ProjectSharingStrip.tsx index deac831b..7171b1ff 100644 --- a/echo/frontend/src/components/project/ProjectSharingStrip.tsx +++ b/echo/frontend/src/components/project/ProjectSharingStrip.tsx @@ -10,6 +10,7 @@ import { import { IconLock, IconUsers } from "@tabler/icons-react"; import { useState } from "react"; import { useProjectShares } from "@/hooks/useProjectSharing"; +import { avatarUrl } from "@/lib/avatar"; import { ProjectSharingModal } from "./ProjectSharingModal"; interface ProjectSharingStripProps { @@ -79,7 +80,7 @@ export function ProjectSharingStrip({ key={s.user_id} size="sm" radius="xl" - src={s.avatar ?? undefined} + src={avatarUrl(s.avatar, 48)} > {(s.display_name || s.email) .slice(0, 2) diff --git a/echo/frontend/src/hooks/useNotifications.ts b/echo/frontend/src/hooks/useNotifications.ts index 5f9ed7bf..57fabf08 100644 --- a/echo/frontend/src/hooks/useNotifications.ts +++ b/echo/frontend/src/hooks/useNotifications.ts @@ -7,9 +7,11 @@ import { API_BASE_URL } from "@/config"; * Backend lives at /v2/me/notifications. Row shape mirrors the response * from `server/dembrane/api/v2/notifications.py:NotificationRow`. * - * Announcements share the drawer UI pattern but have their own hooks in - * `@/components/announcement/hooks`. When the consolidated inbox design - * lands, this file + that one can collapse into a single store. + * Rendered by `@/components/inbox/Inbox`, which also pulls the + * announcement collection through `@/components/announcement/hooks` + * for the sibling "Announcements" tab. Each store stays separate — + * they're different collections with different lifecycles — but the + * UI presents them as one inbox. */ export interface NotificationRefs { @@ -22,12 +24,6 @@ export interface NotificationRefs { invite_id: string | null; } -export interface NotificationTranslation { - languages_code: string; - title: string; - message: string | null; -} - export type NotificationAction = | "NONE" | "NAVIGATE_WS" @@ -38,11 +34,17 @@ export type NotificationAction = | "NAVIGATE_TEAM_SETTINGS" | "NAVIGATE_WORKSPACE_SETTINGS"; +export type NotificationSeverity = "info" | "action_required" | "destructive"; + export interface NotificationRow { id: string; event_code: string; + severity: NotificationSeverity; action: NotificationAction; - level: "info" | "urgent"; + title: string; + message: string | null; + scope: string | null; + params: Record | null; created_at: string | null; expires_at: string | null; read: boolean; @@ -50,7 +52,6 @@ export interface NotificationRow { actor_name: string | null; actor_avatar: string | null; refs: NotificationRefs; - translation: NotificationTranslation | null; } async function fetchNotifications(): Promise { diff --git a/echo/frontend/src/lib/avatar.ts b/echo/frontend/src/lib/avatar.ts new file mode 100644 index 00000000..6f2d65a1 --- /dev/null +++ b/echo/frontend/src/lib/avatar.ts @@ -0,0 +1,18 @@ +import { DIRECTUS_PUBLIC_URL } from "@/config"; + +/** + * Resolve a Directus file id → asset URL for . + * + * The backend hands us the raw `directus_users.avatar` column, which is + * a file UUID, not a URL. Passing the UUID directly to silently + * 404s and Mantine falls back to initials — which looks like "avatars + * aren't showing" rather than "we forgot to build the URL." This helper + * exists so no one has to remember the prefix. + */ +export function avatarUrl( + fileId: string | null | undefined, + size = 64, +): string | undefined { + if (!fileId) return undefined; + return `${DIRECTUS_PUBLIC_URL}/assets/${fileId}?width=${size}&height=${size}&fit=cover`; +} diff --git a/echo/frontend/src/routes/auth/CheckYourEmail.tsx b/echo/frontend/src/routes/auth/CheckYourEmail.tsx index 05736974..88347e16 100644 --- a/echo/frontend/src/routes/auth/CheckYourEmail.tsx +++ b/echo/frontend/src/routes/auth/CheckYourEmail.tsx @@ -4,18 +4,17 @@ import { useDocumentTitle } from "@mantine/hooks"; import { testId } from "@/lib/testUtils"; export const CheckYourEmailRoute = () => { - useDocumentTitle("Check your Email | dembrane"); + useDocumentTitle("Check your email | dembrane"); return ( - - - + <Container size="sm" py="xl"> + <Stack gap="sm"> + <Title order={2} fw={400} {...testId("auth-check-email-title")}> <Trans>Check your email</Trans> - + - We have sent you an email with next steps. If you don't see it, - check your spam folder. If you still don't see it, please contact - evelien@dembrane.com + We sent you a verification link. Click the link to finish setting + up your account. diff --git a/echo/frontend/src/routes/auth/Register.tsx b/echo/frontend/src/routes/auth/Register.tsx index 3bd7f819..5fb206fe 100644 --- a/echo/frontend/src/routes/auth/Register.tsx +++ b/echo/frontend/src/routes/auth/Register.tsx @@ -24,7 +24,7 @@ import { testId } from "@/lib/testUtils"; export const RegisterRoute = () => { useDocumentTitle(t`Register | dembrane`); - const { register, handleSubmit, trigger, getValues } = useForm<{ + const { register, handleSubmit, trigger, getValues, watch } = useForm<{ email: string; password: string; confirmPassword: string; @@ -34,6 +34,7 @@ export const RegisterRoute = () => { const [step, setStep] = useState(0); const [error, setError] = useState(""); + const [submittedEmail, setSubmittedEmail] = useState(null); const registerMutation = useRegisterMutation(); const posthog = usePostHog(); @@ -67,18 +68,29 @@ export const RegisterRoute = () => { first_name: data.first_name, }); - registerMutation.mutate([ - data.email, - data.password, + // Directus rejects empty string on last_name ("INVALID_PAYLOAD: + // last_name is not allowed to be empty"), so we only include the + // field when the user actually typed something. + const extras: Record = { + first_name: data.first_name, + verification_url: `${ADMIN_BASE_URL}/verify-email`, + }; + const lastName = data.last_name?.trim(); + if (lastName) extras.last_name = lastName; + + registerMutation.mutate( + [data.email, data.password, extras], { - first_name: data.first_name, - // Directus allows empty last_name - last_name: data.last_name || "", - verification_url: `${ADMIN_BASE_URL}/verify-email`, + onSuccess: () => { + setSubmittedEmail(data.email); + setStep(2); + }, }, - ]); + ); }); + const emailWatch = watch("email"); + return ( @@ -112,7 +124,7 @@ export const RegisterRoute = () => { label={t`First name`} {...register("first_name", { required: true })} {...testId("auth-register-first-name-input")} - placeholder={t`Alex`} + placeholder={t`John`} /> { description={t`Optional`} {...register("last_name")} {...testId("auth-register-last-name-input")} - placeholder={t`Chen`} + placeholder={t`Doe`} /> - + {step !== 2 && ( + <> + + + + + + + )} ); diff --git a/echo/frontend/src/routes/team/TeamRoute.tsx b/echo/frontend/src/routes/team/TeamRoute.tsx index b868b089..23934d36 100644 --- a/echo/frontend/src/routes/team/TeamRoute.tsx +++ b/echo/frontend/src/routes/team/TeamRoute.tsx @@ -33,6 +33,7 @@ import { useMemo, useState } from "react"; import { useParams } from "react-router"; import { API_BASE_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { avatarUrl } from "@/lib/avatar"; /** * Team admin page — single-page matrix view. @@ -348,7 +349,7 @@ export const TeamRoute = () => { > diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py index 8c20063a..3e1a6948 100644 --- a/echo/scripts/create_schema.py +++ b/echo/scripts/create_schema.py @@ -830,21 +830,30 @@ def step_8_remove_chat(): # --------------------------------------------------------------------------- -# Step 9: Notifications (inbox) — mirrors the announcement trio +# Step 9: Notifications (inbox) — flat per-recipient rows # --------------------------------------------------------------------------- def step_9_notifications(): - """Per-user notifications. Follows the announcement pattern - (parent + translations + activity) and adds targeting fields - (audience_user_id, action enum, ref_* nullable FKs). - - Note on channels: this is the canonical in-app store. A future - delivery layer can read from it to ship email or Slack; we don't - split the storage per channel. Inbox UI, email digests, and Slack - webhooks all flow through the same rows. (See the sibling comment - in dembrane/notifications.py service module.) + """Per-user notifications — one row per (event, recipient). + + The announcement pattern (parent + translations + activity) was + rejected here because fan-out is almost always 1–3 people and pre- + rendering N locales per row wastes more than we'd save on string + dedupe. Client-side Lingui catalogs render text from `event_code + + params` when that migration lands; for now title/message are plain + English strings written at emit time. + + Severity is server-derived from the event_code — client renders + styling from severity, never from event_code. + + Scope is the denormalized breadcrumb ("Org › Workspace › Project") + computed once at emit time; a later rename correctly preserves the + historical breadcrumb instead of mutating past notifications. + + Note on channels: in-app only for now. A future email digest or + Slack bridge reads the same rows rather than having its own store. """ - print("\n=== Step 9: notification + notification_translations + notification_activity ===") + print("\n=== Step 9: notification (flat per-recipient) ===") notification_fields = [ pk_uuid(), @@ -864,7 +873,20 @@ def step_9_notifications(): "field": "event_code", "type": "string", "schema": {"is_nullable": False}, "meta": {"interface": "input", "required": True, - "note": "Machine enum. INVITE_CREATED, ROLE_CHANGED, SHARE_ADDED, REPORT_READY, etc."}, + "note": "Machine enum. WORKSPACE_ADDED, INVITE_ACCEPTED, REPORT_READY, etc."}, + }, + { + "field": "severity", "type": "string", + "schema": {"is_nullable": False, "default_value": "info"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Info", "value": "info"}, + {"text": "Action required", "value": "action_required"}, + {"text": "Destructive", "value": "destructive"}, + ]}, + "note": "Server-derived from event_code. Controls row styling.", + }, }, { "field": "action", "type": "string", @@ -884,6 +906,22 @@ def step_9_notifications(): "note": "Codified nav target. UI resolves the URL from ref_* fields.", }, }, + {"field": "title", "type": "string", + "schema": {"is_nullable": False}, + "meta": {"interface": "input", "required": True, + "note": "Server-rendered headline. Plain text."}}, + {"field": "message", "type": "text", + "schema": {"is_nullable": True}, + "meta": {"interface": "input-multiline", + "note": "Optional body. Markdown allowed."}}, + {"field": "scope", "type": "string", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", + "note": "Breadcrumb: 'Org › Workspace › Project'. Frozen at emit time."}}, + {"field": "params", "type": "json", + "schema": {"is_nullable": True}, + "meta": {"interface": "input-code", "options": {"language": "JSON"}, + "note": "Event-specific params for future client-rendered i18n."}}, {"field": "ref_org_id", "type": "uuid", "schema": {"is_nullable": True}, "meta": {"interface": "input"}}, @@ -905,25 +943,14 @@ def step_9_notifications(): {"field": "ref_invite_id", "type": "uuid", "schema": {"is_nullable": True}, "meta": {"interface": "input"}}, - { - "field": "level", "type": "string", - "schema": {"is_nullable": False, "default_value": "info"}, - "meta": { - "interface": "select-dropdown", - "options": {"choices": [ - {"text": "Info", "value": "info"}, - {"text": "Urgent", "value": "urgent"}, - ]}, - }, - }, + {"field": "read_at", "type": "timestamp", + "schema": {"is_nullable": True}, + "meta": {"interface": "datetime", + "note": "When the recipient marked this read. Null = unread."}}, {"field": "expires_at", "type": "timestamp", "schema": {"is_nullable": True}, "meta": {"interface": "datetime", "note": "Hide from inbox after this timestamp."}}, - {"field": "translations", "type": "alias", - "meta": {"interface": "translations", "special": ["translations"]}}, - {"field": "activity", "type": "alias", - "meta": {"interface": "list-o2m", "special": ["o2m"]}}, {"field": "created_at", **timestamp_created()}, {"field": "updated_at", **timestamp_updated()}, ] @@ -952,61 +979,6 @@ def step_9_notifications(): create_relation("notification", "ref_invite_id", "workspace_invite", schema={"on_delete": "SET NULL"}) - # notification_translations - nt_fields = [ - pk_uuid(), - {"field": "notification_id", "type": "uuid", - "schema": {"is_nullable": False}, - "meta": {"interface": "input", "required": True}}, - {"field": "languages_code", "type": "string", - "schema": {"is_nullable": False}, - "meta": {"interface": "input", "required": True}}, - {"field": "title", "type": "string", - "schema": {"is_nullable": False}, - "meta": {"interface": "input", "required": True}}, - {"field": "message", "type": "text", - "schema": {"is_nullable": True}, - "meta": {"interface": "input-multiline", - "note": "Markdown allowed."}}, - ] - if not create_collection("notification_translations", nt_fields, { - "accountability": "all", - }): - return False - create_relation("notification_translations", "notification_id", "notification", - meta={"one_field": "translations"}, - schema={"on_delete": "CASCADE"}) - create_relation("notification_translations", "languages_code", "languages", - schema={"on_delete": "NO ACTION"}) - - # notification_activity — per-user read state. Mirrors announcement_activity - # exactly so the inbox drawer can render both with one component. Rows - # are pre-created on emit (one per notification — audience is known) so - # unread counts are a simple aggregate. - na_fields = [ - pk_uuid(), - {"field": "notification_id", "type": "uuid", - "schema": {"is_nullable": False}, - "meta": {"interface": "input", "required": True}}, - {"field": "user_id", "type": "uuid", - "schema": {"is_nullable": False}, - "meta": {"interface": "input", "special": ["user-created"], - "required": True, - "note": "FK to directus_users (matches announcement_activity)."}}, - {"field": "read", "type": "boolean", - "schema": {"is_nullable": False, "default_value": False}, - "meta": {"interface": "boolean"}}, - {"field": "created_at", **timestamp_created()}, - {"field": "updated_at", **timestamp_updated()}, - ] - if not create_collection("notification_activity", na_fields, { - "accountability": "all", - }): - return False - create_relation("notification_activity", "notification_id", "notification", - meta={"one_field": "activity"}, - schema={"on_delete": "CASCADE"}) - return True diff --git a/echo/scripts/preseed_workspace.py b/echo/scripts/preseed_workspace.py index 0e3d830c..ee5a8dd5 100644 --- a/echo/scripts/preseed_workspace.py +++ b/echo/scripts/preseed_workspace.py @@ -12,16 +12,24 @@ YAML config format: org: - name: "Dietz Consulting" - owner: petra@dietz.nl + name: "Fiets Consulting" admins: - - jan@dietz.nl - - lisa@dietz.nl + - foo@fiets.nl + member: + - bar@fiets.nl + - bin@fiets.nl workspaces: - name: "Client Alpha" tier: pioneer - include_projects: true # move owner+admin projects here + include_projects: true # if true, then move admin+member projects here + admins: + - foo@fiets.nl + members: + - bin@fiets.nl + external_members: + - baz@leets.nl + - name: "Client Beta" tier: pioneer diff --git a/echo/server/dembrane/api/v2/notifications.py b/echo/server/dembrane/api/v2/notifications.py index 6820ceaa..c5a9c7b9 100644 --- a/echo/server/dembrane/api/v2/notifications.py +++ b/echo/server/dembrane/api/v2/notifications.py @@ -1,19 +1,14 @@ -"""GET /v2/me/notifications + POST /v2/me/notifications/:id/read. +"""GET /v2/me/notifications + mark-read endpoints. -Inbox BFF. Wraps the `notification` collection so the frontend drawer -can render both announcements and personal notifications with one -component — same shape, same mark-read semantics. - -Lives under /v2/me because every row is user-scoped. The underlying -notification table supports cross-team / system notifications; the BFF -restricts reads to the caller's own rows (audience_user_id = me). +Inbox BFF. Flat `notification` rows keyed to the caller — no parent +or activity sidecars to join. Read state lives inline as `read_at`. """ from __future__ import annotations from datetime import datetime, timezone from logging import getLogger -from typing import Optional +from typing import Any, Optional from fastapi import APIRouter, HTTPException from pydantic import BaseModel @@ -26,12 +21,6 @@ logger = getLogger("api.v2.notifications") -class NotificationTranslation(BaseModel): - languages_code: str - title: str - message: Optional[str] = None - - class NotificationRefs(BaseModel): org_id: Optional[str] = None workspace_id: Optional[str] = None @@ -45,8 +34,12 @@ class NotificationRefs(BaseModel): class NotificationRow(BaseModel): id: str event_code: str - action: str # enum from NotificationAction - level: str # info | urgent + severity: str # info | action_required | destructive + action: str # NotificationAction enum + title: str + message: Optional[str] = None + scope: Optional[str] = None + params: Optional[dict[str, Any]] = None created_at: Optional[str] = None expires_at: Optional[str] = None read: bool = False @@ -54,7 +47,16 @@ class NotificationRow(BaseModel): actor_name: Optional[str] = None actor_avatar: Optional[str] = None refs: NotificationRefs = NotificationRefs() - translation: Optional[NotificationTranslation] = None + + +def _row_filter(app_user_id: str, now_iso: str) -> dict: + return { + "audience_user_id": {"_eq": app_user_id}, + "_or": [ + {"expires_at": {"_null": True}}, + {"expires_at": {"_gt": now_iso}}, + ], + } @router.get("", response_model=list[NotificationRow]) @@ -71,13 +73,9 @@ async def list_notifications( app_user = await get_app_user_or_raise(auth.user_id) now_iso = datetime.now(timezone.utc).isoformat() - notif_filter: dict = { - "audience_user_id": {"_eq": app_user["id"]}, - "_or": [ - {"expires_at": {"_null": True}}, - {"expires_at": {"_gt": now_iso}}, - ], - } + notif_filter = _row_filter(app_user["id"], now_iso) + if unread_only: + notif_filter["read_at"] = {"_null": True} rows = await async_directus.get_items( "notification", @@ -85,20 +83,13 @@ async def list_notifications( "query": { "filter": notif_filter, "fields": [ - "id", - "event_code", - "action", - "level", - "created_at", - "expires_at", + "id", "event_code", "severity", "action", + "title", "message", "scope", "params", + "created_at", "expires_at", "read_at", "actor_user_id", - "ref_org_id", - "ref_workspace_id", - "ref_project_id", - "ref_chat_id", - "ref_report_id", - "ref_conversation_id", - "ref_invite_id", + "ref_org_id", "ref_workspace_id", "ref_project_id", + "ref_chat_id", "ref_report_id", + "ref_conversation_id", "ref_invite_id", ], "sort": ["-created_at"], "limit": max(1, min(limit, 200)), @@ -108,65 +99,6 @@ async def list_notifications( if not isinstance(rows, list) or not rows: return [] - notif_ids = [r["id"] for r in rows if r.get("id")] - - # Read state (activity rows) keyed by notification_id. - activity_rows = await async_directus.get_items( - "notification_activity", - { - "query": { - "filter": { - "notification_id": {"_in": notif_ids}, - "user_id": {"_eq": auth.user_id}, - }, - "fields": ["notification_id", "read"], - "limit": -1, - } - }, - ) or [] - read_map: dict[str, bool] = {} - if isinstance(activity_rows, list): - for ar in activity_rows: - nid = ar.get("notification_id") - if nid: - read_map[nid] = bool(ar.get("read", False)) - - if unread_only: - rows = [r for r in rows if not read_map.get(r["id"], False)] - if not rows: - return [] - notif_ids = [r["id"] for r in rows] - - # Translations — pick the user's language with en-US fallback. - # Batch once; caller UX doesn't need every locale. - accept_lang = "en-US" # TODO: thread Accept-Language through session - translation_rows = await async_directus.get_items( - "notification_translations", - { - "query": { - "filter": {"notification_id": {"_in": notif_ids}}, - "fields": ["notification_id", "languages_code", "title", "message"], - "limit": -1, - } - }, - ) or [] - tl_by_notif: dict[str, dict] = {} - if isinstance(translation_rows, list): - for tr in translation_rows: - nid = tr.get("notification_id") - if not nid: - continue - # Prefer user's language; fall back to en-US; any other as - # final backstop. - existing = tl_by_notif.get(nid) - lang = tr.get("languages_code") - if ( - existing is None - or lang == accept_lang - or (lang == "en-US" and existing.get("languages_code") != accept_lang) - ): - tl_by_notif[nid] = tr - # Actor name + avatar in one batch. actor_ids = list({r.get("actor_user_id") for r in rows if r.get("actor_user_id")}) actor_map: dict[str, dict] = {} @@ -204,17 +136,20 @@ async def list_notifications( out: list[NotificationRow] = [] for r in rows: - tl = tl_by_notif.get(r["id"]) actor = actor_map.get(r.get("actor_user_id") or "") or {} out.append( NotificationRow( id=r["id"], event_code=r.get("event_code", ""), + severity=r.get("severity", "info"), action=r.get("action", "NONE"), - level=r.get("level", "info"), + title=r.get("title", ""), + message=r.get("message"), + scope=r.get("scope"), + params=r.get("params"), created_at=r.get("created_at"), expires_at=r.get("expires_at"), - read=read_map.get(r["id"], False), + read=bool(r.get("read_at")), actor_user_id=r.get("actor_user_id"), actor_name=actor.get("display_name"), actor_avatar=actor.get("avatar"), @@ -227,15 +162,6 @@ async def list_notifications( conversation_id=r.get("ref_conversation_id"), invite_id=r.get("ref_invite_id"), ), - translation=( - NotificationTranslation( - languages_code=tl.get("languages_code", ""), - title=tl.get("title", ""), - message=tl.get("message"), - ) - if tl - else None - ), ) ) return out @@ -243,52 +169,25 @@ async def list_notifications( @router.get("/unread-count") async def unread_count(auth: DependencyDirectusSession) -> dict: - """Cheap count for the inbox badge. - - Runs a single aggregate — don't call list_notifications just to - get `len(unread)`. - """ + """Cheap count for the inbox badge.""" app_user = await get_app_user_or_raise(auth.user_id) now_iso = datetime.now(timezone.utc).isoformat() - # Notifications this user owns that haven't expired yet. - notif_rows = await async_directus.get_items( - "notification", - { - "query": { - "filter": { - "audience_user_id": {"_eq": app_user["id"]}, - "_or": [ - {"expires_at": {"_null": True}}, - {"expires_at": {"_gt": now_iso}}, - ], - }, - "fields": ["id"], - "limit": -1, - } - }, - ) or [] - if not isinstance(notif_rows, list) or not notif_rows: - return {"unread": 0} - notif_ids = [r["id"] for r in notif_rows] + notif_filter = _row_filter(app_user["id"], now_iso) + notif_filter["read_at"] = {"_null": True} - # Activity rows for the caller's read/unread state on those. - read_rows = await async_directus.get_items( - "notification_activity", + rows = await async_directus.get_items( + "notification", { "query": { - "filter": { - "notification_id": {"_in": notif_ids}, - "user_id": {"_eq": auth.user_id}, - "read": {"_eq": True}, - }, + "filter": notif_filter, "fields": ["id"], "limit": -1, } }, ) or [] - read_count = len(read_rows) if isinstance(read_rows, list) else 0 - return {"unread": max(0, len(notif_ids) - read_count)} + count = len(rows) if isinstance(rows, list) else 0 + return {"unread": count} @router.post("/{notification_id}/read") @@ -296,7 +195,7 @@ async def mark_read( notification_id: str, auth: DependencyDirectusSession, ) -> dict: - """Flip the caller's activity row for this notification to read=true. + """Stamp `read_at` on the caller's notification row. Verifies audience — a user can only mark their own rows read, even if they guess another user's notification_id. @@ -307,82 +206,46 @@ async def mark_read( if not notif or notif.get("audience_user_id") != app_user["id"]: raise HTTPException(status_code=404, detail="Notification not found") - rows = await async_directus.get_items( - "notification_activity", - { - "query": { - "filter": { - "notification_id": {"_eq": notification_id}, - "user_id": {"_eq": auth.user_id}, - }, - "fields": ["id"], - "limit": 1, - } - }, + if notif.get("read_at"): + return {"status": "read"} # already read — idempotent no-op + + await async_directus.update_item( + "notification", + notification_id, + {"read_at": datetime.now(timezone.utc).isoformat()}, ) - if isinstance(rows, list) and rows: - await async_directus.update_item( - "notification_activity", rows[0]["id"], {"read": True} - ) - else: - # Emit normally pre-creates activity; defend in case a row is missing. - from dembrane.utils import generate_uuid - await async_directus.create_item( - "notification_activity", - { - "id": generate_uuid(), - "notification_id": notification_id, - "user_id": auth.user_id, - "read": True, - }, - ) return {"status": "read"} @router.post("/read-all") async def mark_all_read(auth: DependencyDirectusSession) -> dict: - """Flip every unread activity row for this user to read=true. + """Stamp `read_at` on every unread notification for this user. - Cap at the 500 most recent to keep the mutation bounded. + Capped at 500 to keep the mutation bounded. Anything older is + effectively read anyway — the drawer doesn't page back that far. """ - from dembrane.utils import generate_uuid # noqa: F401 (future use) - app_user = await get_app_user_or_raise(auth.user_id) + now_iso = datetime.now(timezone.utc).isoformat() - notif_rows = await async_directus.get_items( + rows = await async_directus.get_items( "notification", - { - "query": { - "filter": {"audience_user_id": {"_eq": app_user["id"]}}, - "fields": ["id"], - "sort": ["-created_at"], - "limit": 500, - } - }, - ) or [] - if not isinstance(notif_rows, list) or not notif_rows: - return {"status": "noop", "marked": 0} - notif_ids = [r["id"] for r in notif_rows] - - activity_rows = await async_directus.get_items( - "notification_activity", { "query": { "filter": { - "notification_id": {"_in": notif_ids}, - "user_id": {"_eq": auth.user_id}, - "read": {"_eq": False}, + "audience_user_id": {"_eq": app_user["id"]}, + "read_at": {"_null": True}, }, "fields": ["id"], - "limit": -1, + "sort": ["-created_at"], + "limit": 500, } }, ) or [] marked = 0 - if isinstance(activity_rows, list): - for row in activity_rows: + if isinstance(rows, list): + for row in rows: await async_directus.update_item( - "notification_activity", row["id"], {"read": True} + "notification", row["id"], {"read_at": now_iso} ) marked += 1 return {"status": "read", "marked": marked} diff --git a/echo/server/dembrane/notifications.py b/echo/server/dembrane/notifications.py index 3dc39c8f..e72cae3e 100644 --- a/echo/server/dembrane/notifications.py +++ b/echo/server/dembrane/notifications.py @@ -1,31 +1,29 @@ """In-app notification service. -One canonical store (`notification` + `notification_translations` + -`notification_activity`) serves the inbox UI today. The module exposes a -single `emit` function that action code paths call — everything else -(list, mark-read, unread-count) runs through `/v2/me/notifications` BFF -endpoints. +One flat `notification` table, one row per `(event, recipient)`. The +announcement-pattern split (parent + translations + activity) was +rejected here because fan-out in this product is almost always 1–3 +people — shared-parent dedup buys less than the JOIN cost on every +inbox read. ### Channels -Notifications are stored here. Future delivery layers (email digest, -Slack webhook) read from this store rather than having their own -pipeline: +Storage is channel-agnostic. Future delivery layers read the same +rows rather than having their own pipeline: - emit(...) → notification row + activity row + emit(...) → notification row (one per recipient) └── inbox UI reads via /v2/me/notifications └── (future) digest worker groups by user + sends SendGrid └── (future) Slack bridge fans out urgent/mentioned rows -This keeps the emission sites dumb — they describe *what happened*, not -*where it goes*. Per-user channel preferences live in the delivery -layer, not here. Don't add per-channel booleans to the notification row. +Emission sites describe *what happened*, not *where it goes*. Per- +user channel preferences live in the delivery layer, not here. """ from __future__ import annotations from logging import getLogger -from typing import Literal, Optional +from typing import Any, Literal, Optional from dembrane.utils import generate_uuid from dembrane.directus_async import async_directus @@ -44,7 +42,61 @@ "NAVIGATE_WORKSPACE_SETTINGS", ] -NotificationLevel = Literal["info", "urgent"] +NotificationSeverity = Literal["info", "action_required", "destructive"] + + +# Event → severity map. Controls client-side row styling (background +# tint, button style, unread-dot color). Anything not listed defaults +# to "info". Keep in sync with the designer's spec in inbox.html. +_SEVERITY_BY_EVENT: dict[str, NotificationSeverity] = { + # Access revoked / data lost / feature downgraded + "WORKSPACE_REMOVED": "destructive", + "TEAM_REMOVED": "destructive", + "PROJECT_NOW_PRIVATE": "destructive", + "PROJECT_SHARE_REVOKED": "destructive", + "TIER_DOWNGRADED": "destructive", + "INVITE_CANCELLED": "destructive", + "REPORT_FAILED": "destructive", + # Requires user action. None wired today — MEMBERSHIP_REQUEST etc. + # will slot in here when we ship those flows. +} + + +def severity_for(event_code: str) -> NotificationSeverity: + return _SEVERITY_BY_EVENT.get(event_code, "info") + + +async def _compute_scope( + *, + ref_org_id: Optional[str], + ref_workspace_id: Optional[str], + ref_project_id: Optional[str], +) -> Optional[str]: + """Build the 'Org › Workspace › Project' breadcrumb from refs. + + Frozen at emit time so a later rename preserves the historical + breadcrumb on existing rows. Missing names are skipped (we don't + invent placeholders). Returns None when there's nothing to show. + """ + parts: list[str] = [] + try: + if ref_org_id: + org = await async_directus.get_item("org", ref_org_id) + if org and org.get("name"): + parts.append(org["name"]) + if ref_workspace_id: + ws = await async_directus.get_item("workspace", ref_workspace_id) + if ws and ws.get("name"): + parts.append(ws["name"]) + if ref_project_id: + proj = await async_directus.get_item("project", ref_project_id) + if proj and proj.get("name"): + parts.append(proj["name"]) + except Exception as exc: # noqa: BLE001 — scope is best-effort + logger.debug("scope computation failed: %s", exc) + if not parts: + return None + return " \u203a ".join(parts) async def emit( @@ -52,9 +104,9 @@ async def emit( audience_user_id: str, event_code: str, title: str, - message: str, + message: Optional[str] = None, action: NotificationAction = "NONE", - level: NotificationLevel = "info", + severity: Optional[NotificationSeverity] = None, actor_user_id: Optional[str] = None, ref_org_id: Optional[str] = None, ref_workspace_id: Optional[str] = None, @@ -63,37 +115,42 @@ async def emit( ref_report_id: Optional[str] = None, ref_conversation_id: Optional[str] = None, ref_invite_id: Optional[str] = None, - language: str = "en-US", + params: Optional[dict[str, Any]] = None, + scope: Optional[str] = None, expires_at: Optional[str] = None, + # Deprecated alias — old callers pass `level=`, we translate it on + # the way through until they migrate. Harmless. + level: Optional[str] = None, ) -> Optional[str]: - """Create a single notification row + its English translation + - one pre-filled activity row. Returns the notification id, or None - on failure (never raises — notifications are a best-effort side - effect, they must not fail the parent action). - - Don't call this with a list of users — call once per user. The - activity row is created eagerly so unread counts can be a cheap - aggregate. - - ### Translations - - For the first pass we only write one translation row (caller's - language or en-US). The frontend drawer falls back to en-US when - the user's locale has no row. When we ship server-side message - templating (grouped into dembrane/notification_templates.py), we - can fan out every language in one call. + """Create one notification row for the recipient. Returns the + notification id, or None on failure (never raises — notifications + are a best-effort side effect, they must not fail the parent + action). + + Don't call with a list of users — use `emit_to_audience`. + + `severity` defaults from `severity_for(event_code)` when omitted. + `scope` is computed from refs when omitted. + `params` is forward-compat metadata for client-rendered i18n. """ try: - notification_id = generate_uuid() - - # Resolve audience_user_id (app_user.id) → directus_user_id so - # activity rows match the announcement_activity convention - # (user_id on activity = directus_users.id). - directus_user_id: Optional[str] = None - audience_row = await async_directus.get_item("app_user", audience_user_id) - if audience_row: - directus_user_id = audience_row.get("directus_user_id") + resolved_severity: NotificationSeverity = ( + severity or severity_for(event_code) + ) + # Silently accept `level=` from legacy callers. + if level and not severity: + # Old "urgent" maps onto action_required in the new spec. + resolved_severity = "action_required" if level == "urgent" else "info" + + resolved_scope = scope + if resolved_scope is None: + resolved_scope = await _compute_scope( + ref_org_id=ref_org_id, + ref_workspace_id=ref_workspace_id, + ref_project_id=ref_project_id, + ) + notification_id = generate_uuid() await async_directus.create_item( "notification", { @@ -101,8 +158,12 @@ async def emit( "audience_user_id": audience_user_id, "actor_user_id": actor_user_id, "event_code": event_code, + "severity": resolved_severity, "action": action, - "level": level, + "title": title, + "message": message, + "scope": resolved_scope, + "params": params, "ref_org_id": ref_org_id, "ref_workspace_id": ref_workspace_id, "ref_project_id": ref_project_id, @@ -113,29 +174,6 @@ async def emit( "expires_at": expires_at, }, ) - - await async_directus.create_item( - "notification_translations", - { - "id": generate_uuid(), - "notification_id": notification_id, - "languages_code": language, - "title": title, - "message": message, - }, - ) - - if directus_user_id: - await async_directus.create_item( - "notification_activity", - { - "id": generate_uuid(), - "notification_id": notification_id, - "user_id": directus_user_id, - "read": False, - }, - ) - return notification_id except Exception as exc: # noqa: BLE001 — notifications must never raise logger.warning( diff --git a/echo/server/dembrane/tasks.py b/echo/server/dembrane/tasks.py index f4fb0044..7d557f3a 100644 --- a/echo/server/dembrane/tasks.py +++ b/echo/server/dembrane/tasks.py @@ -1407,7 +1407,6 @@ def progress_callback(event_type: str, message: str, detail: Optional[dict] = No title="Report generation ran into a problem", message="Open the report to retry or check details.", action="NAVIGATE_REPORT", - level="urgent", ref_project_id=project_id, ref_report_id=report_id_str, ) diff --git a/echo/server/email_templates/_layout.html b/echo/server/email_templates/_layout.html index 897200a2..ad1ae0f9 100644 --- a/echo/server/email_templates/_layout.html +++ b/echo/server/email_templates/_layout.html @@ -1,60 +1,83 @@ {# Shared base layout for dembrane transactional emails. - Blocks to override in children: - preview — small text shown as inbox preview line (keep <90 chars) - heading — top H2 inside the card - body — main message content + Visual spec: designer's "Verify Email C" direction — letter-style card + on parchment, DM Sans Light, royal-blue pill CTA, crowd banner anchors + the card bottom. Keep child overrides minimal; the frame is the brand. + + Blocks to override: + preview — inbox preview line (<90 chars) + heading — H1 inside the card + body — main message copy cta — primary button (leave empty to hide) - footer — trailing small text under the body (leave empty to hide) + fallback — "or paste this URL" block (leave empty to hide) + disclaim — "didn't expect this email" disclaimer (leave empty to hide) + footer — extra trailing text (leave empty to hide) - Brand rules (brand/STYLE_GUIDE.md): lowercase "dembrane" always, Royal Blue - (#4169e1) for CTAs, no "successfully" / "AI" copy, warm but not gushing. + Brand rules (brand/STYLE_GUIDE.md): lowercase "dembrane" always, Royal + Blue (#4169e1) for accent, no "successfully" / "AI" copy, no bold on + body text — use Royal Blue or italics for emphasis. #} + {% block title %}dembrane{% endblock %} + - -
    + +
    {% block preview %}{% endblock %}
    - +
    - - +
    + - - + - - + -
    - dembrane + + dembrane
    -

    +

    +

    {% block heading %}{% endblock %} -

    -
    + + +
    {% block body %}{% endblock %}
    {% block cta %}{% endblock %} + {% block fallback %}{% endblock %} + + {% block disclaim %}{% endblock %} + +

    + — the dembrane team +

    + {% block footer %}{% endblock %}
    -

    - dembrane · conversations that matter -

    +
    +
    @@ -63,16 +86,27 @@

    +
    + style="display:inline-block; background-color:#4169E1; color:#FFFFFF; text-decoration:none; padding:14px 30px; border-radius:9999px; font-size:15px; font-weight:400; font-family:inherit; line-height:1;"> {{ label }}
    {%- endmacro %} +{# "Or paste this into your browser" fallback block. Show for any email + that contains a magic link so recipients whose mail client mangles + the button still have a way through. #} +{% macro fallback_url(url) -%} +

    + Or paste this into your browser:
    + {{ url }} +

    +{%- endmacro %} diff --git a/echo/server/email_templates/upgrade_request.html b/echo/server/email_templates/upgrade_request.html index 646cad6b..3334f2cf 100644 --- a/echo/server/email_templates/upgrade_request.html +++ b/echo/server/email_templates/upgrade_request.html @@ -1,24 +1,18 @@ {% extends "_layout.html" %} {% block title %}Upgrade request: {{ org_name }} / {{ workspace_name }}{% endblock %} {% block preview %}{{ requester_name }} asked to upgrade {{ workspace_name }} from {{ current_tier }} to {{ target_tier }}.{% endblock %} -{% block heading %}Upgrade request{% endblock %} +{% block heading %}Upgrade request.{% endblock %} {% block body %} -

    - Team: {{ org_name }}
    - Workspace: {{ workspace_name }}
    - Workspace ID: {{ workspace_id }} +

    + {{ requester_name }}{% if requester_email %} ({{ requester_email }}){% endif %} wants to move {{ workspace_name }} from {{ current_tier }} to {{ target_tier }}.

    -

    - Current tier: {{ current_tier }}
    - Requested tier: {{ target_tier }} -

    -

    - Requested by: {{ requester_name }}{% if requester_email %} ({{ requester_email }}){% endif %} +

    + Team: {{ org_name }} · Workspace ID: {{ workspace_id }}

    {% if message %} -
    -
    Message
    -
    {{ message }}
    +
    +
    Message
    +
    {{ message }}
    {% endif %} {% endblock %} diff --git a/echo/server/email_templates/upgrade_request.txt b/echo/server/email_templates/upgrade_request.txt index c934693e..648ed2f3 100644 --- a/echo/server/email_templates/upgrade_request.txt +++ b/echo/server/email_templates/upgrade_request.txt @@ -10,5 +10,4 @@ Requested by: {{ requester_name }}{% if requester_email %} <{{ requester_emai {% if message %}Message: {{ message }} -{% endif %}— -dembrane · conversations that matter +{% endif %}— the dembrane team diff --git a/echo/server/email_templates/workspace_added.html b/echo/server/email_templates/workspace_added.html index 10f56100..98a36173 100644 --- a/echo/server/email_templates/workspace_added.html +++ b/echo/server/email_templates/workspace_added.html @@ -2,11 +2,10 @@ {% from "_layout.html" import cta_button %} {% block title %}You've been added to {{ workspace_name }}{% endblock %} {% block preview %}{{ inviter_name }} added you to {{ workspace_name }} on dembrane.{% endblock %} -{% block heading %}You've been added to a workspace{% endblock %} +{% block heading %}You're in.{% endblock %} {% block body %} -

    - {{ inviter_name }} added you to {{ workspace_name }} on dembrane. - You can start collaborating right away. +

    + {{ inviter_name }} added you to {{ workspace_name }} on dembrane. You can start collaborating right away.

    {% endblock %} {% block cta %}{{ cta_button("Open workspace", invite_url) }}{% endblock %} diff --git a/echo/server/email_templates/workspace_added.txt b/echo/server/email_templates/workspace_added.txt index 26483a11..fb23a773 100644 --- a/echo/server/email_templates/workspace_added.txt +++ b/echo/server/email_templates/workspace_added.txt @@ -3,5 +3,4 @@ Open the workspace: {{ invite_url }} -— -dembrane · conversations that matter +— the dembrane team diff --git a/echo/server/email_templates/workspace_invite.html b/echo/server/email_templates/workspace_invite.html index 37b5dac0..3da810cf 100644 --- a/echo/server/email_templates/workspace_invite.html +++ b/echo/server/email_templates/workspace_invite.html @@ -1,16 +1,17 @@ {% extends "_layout.html" %} -{% from "_layout.html" import cta_button %} +{% from "_layout.html" import cta_button, fallback_url %} {% block title %}You're invited to collaborate on dembrane{% endblock %} {% block preview %}{{ inviter_name }} invited you to join {{ workspace_name }} on dembrane.{% endblock %} -{% block heading %}You've been invited to collaborate{% endblock %} +{% block heading %}You've been invited to collaborate.{% endblock %} {% block body %} -

    - {{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. +

    + {{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. The invite expires in 7 days.

    {% endblock %} {% block cta %}{{ cta_button("Accept invitation", invite_url) }}{% endblock %} -{% block footer %} -

    - This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it. +{% block fallback %}{{ fallback_url(invite_url) }}{% endblock %} +{% block disclaim %} +

    + Didn't expect this? You can ignore this email — nothing will happen.

    {% endblock %} diff --git a/echo/server/email_templates/workspace_invite.txt b/echo/server/email_templates/workspace_invite.txt index 7b54e7e4..cebad59c 100644 --- a/echo/server/email_templates/workspace_invite.txt +++ b/echo/server/email_templates/workspace_invite.txt @@ -1,9 +1,8 @@ -{{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. +{{ inviter_name }} invited you to join {{ workspace_name }} on dembrane. The invite expires in 7 days. Accept the invitation: {{ invite_url }} -This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it. +Didn't expect this? Ignore this email — nothing will happen. -— -dembrane · conversations that matter +— the dembrane team From ec672572ce67463f01f43cfcea223b538cf483e8 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 09:51:37 +0000 Subject: [PATCH 099/208] chore(workspaces): default upgrade-inbox to upgrades@dembrane.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matrix v1.1 §11 locks the shared inbox address. Switches the code default in settings.py + the .env.sample default + the endpoint docstring. Prod env var still overrides. Honeypot easter-egg in me.py (recruiting signal on URL-tampering) is left alone — unrelated to the upgrade-request flow. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/.env.sample | 6 ++---- echo/server/dembrane/api/v2/workspaces.py | 10 +++++----- echo/server/dembrane/settings.py | 6 ++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/echo/server/.env.sample b/echo/server/.env.sample index c1bf6b71..29fb206b 100644 --- a/echo/server/.env.sample +++ b/echo/server/.env.sample @@ -94,7 +94,5 @@ EMBEDDING_API_VERSION= SENDGRID_API_KEY= EMAIL_FROM=do-not-reply@dembrane.com EMAIL_FROM_NAME=dembrane -# Where "Request upgrade" CTAs route. During the workspaces release this -# defaults to sameer@dembrane.com in code — override to a shared billing -# inbox in production. -UPGRADE_REQUEST_INBOX=sameer@dembrane.com +# Where "Request upgrade" CTAs route. Shared inbox per matrix v1.1 §11. +UPGRADE_REQUEST_INBOX=upgrades@dembrane.com diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 0f602a25..360f83fa 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -624,12 +624,12 @@ async def request_upgrade( ) -> dict: """Admin clicks "Request upgrade" in the tier compare view. Sends an email to settings.email.upgrade_request_inbox with context. Configurable - via UPGRADE_REQUEST_INBOX env var (defaults to sameer@dembrane.com during - this release). + via UPGRADE_REQUEST_INBOX env var (defaults to upgrades@dembrane.com per + matrix v1.1 §11). - Member role doesn't see this CTA (Q3 / D9 — member-role path shows copy - only, no button). Enforced here by require_policy(member:invite) which - only admin/owner have — keeps the endpoint from being abused. + Member role doesn't see this CTA (matrix §11 — member-role path shows + copy only, no button). Enforced here by require_policy(member:invite) + which only admin/owner have — keeps the endpoint from being abused. Rate-limited per-user (5/hr) to avoid flooding the billing inbox when the UI misfires or a bored admin leans on the button. diff --git a/echo/server/dembrane/settings.py b/echo/server/dembrane/settings.py index f3773c2e..6b3efa72 100644 --- a/echo/server/dembrane/settings.py +++ b/echo/server/dembrane/settings.py @@ -333,11 +333,9 @@ class EmailSettings(BaseSettings): alias="EMAIL_FROM_NAME", validation_alias=AliasChoices("EMAIL_FROM_NAME", "EMAIL__FROM_NAME"), ) - # Where "Request upgrade" CTAs route (Ask 2 + 4C in the designer spec). - # Defaults to sameer@dembrane.com during the workspaces release; switch - # to a shared billing inbox once the team decides one. + # Where "Request upgrade" CTAs route. Shared inbox per matrix v1.1 §11. upgrade_request_inbox: str = Field( - default="sameer@dembrane.com", + default="upgrades@dembrane.com", alias="UPGRADE_REQUEST_INBOX", validation_alias=AliasChoices( "UPGRADE_REQUEST_INBOX", "EMAIL__UPGRADE_REQUEST_INBOX" From b9c0b48595d891cd3debb1da35b3ad04571665f8 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 09:55:51 +0000 Subject: [PATCH 100/208] S0: backfill script for direct-membership walkback (dry-run default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prerequisite for matrix v1.1 §5 + §6 (derivation retirement). Writes an explicit source='direct' workspace_membership row for every user who currently reaches a workspace only through the derivation walker in inheritance.user_can_access / get_effective_members. Re-runs dedupe via existing direct rows — idempotent. Honors today's settings flags (inherit_team_admins / inherit_team_members / sticky_removed) so the backfill produces the same effective access set as the current live resolver. STOP CONDITION: --dry-run is the default. --apply is a separate flag, guarded by a per-host lockfile. Sameer signs off on the row count before any prod run. Dev dry-run today: 2 proposals across 2 orgs (both sharing a default workspace with an org member who has no direct row). Prod number TBD at cutover. Flow spec lives at docs/workspaces-validate/flows/derivation-walkback.md (not committed in this commit per D10 — doc folders are their own commits). Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/scripts/backfill_direct_memberships.py | 363 ++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 echo/scripts/backfill_direct_memberships.py diff --git a/echo/scripts/backfill_direct_memberships.py b/echo/scripts/backfill_direct_memberships.py new file mode 100644 index 00000000..c6c93f38 --- /dev/null +++ b/echo/scripts/backfill_direct_memberships.py @@ -0,0 +1,363 @@ +"""Backfill explicit `source='direct'` workspace_membership rows for every +user who currently reaches a workspace only through derivation. + +Context: matrix v1.1 §5 + §6 retires the derivation model. After this script +runs + the resolver simplifies, access is stored-direct-only. This script +materialises every currently-derived user into a direct row so no one loses +access at cutover. + +Two passes after the inserts land: + 1. Simplify `inheritance.user_can_access` to a direct-row lookup. + 2. Purge `workspace.settings.{inherit_team_admins, inherit_team_members, + sticky_removed}` — the resolver no longer reads them. + +Both follow in a separate commit. This script only writes rows. + +STOP CONDITION per brief: dry-run default prints the proposed row count. +Do NOT run with --apply until Sameer has signed off on the count. + +Usage: + python scripts/backfill_direct_memberships.py # dry-run + python scripts/backfill_direct_memberships.py --dry-run # explicit + python scripts/backfill_direct_memberships.py --apply # mutate + python scripts/backfill_direct_memberships.py --csv out.csv + +Environment: DIRECTUS_BASE_URL, DIRECTUS_TOKEN (falls back to +directus/.env DIRECTUS_TOKEN line). +""" + +from __future__ import annotations + +import argparse +import csv +import json +import os +import sys +import uuid +from contextlib import contextmanager +from datetime import datetime, timezone +from pathlib import Path +from urllib.parse import urlencode + +import requests + +# Separate lockfile from migrate_inherited_to_derived — the two scripts +# target different concerns and could theoretically run sequentially +# in a cutover script; don't let one lock out the other. +_LOCK_PATH = Path("/tmp/dembrane_backfill_direct_memberships.lock") + + +@contextmanager +def _exclusive_lock(): + if _LOCK_PATH.exists(): + raise RuntimeError( + f"Another backfill is already running (lock: {_LOCK_PATH}). " + "If this is stale, remove it manually after confirming no " + "other process is running." + ) + _LOCK_PATH.write_text( + f"pid={os.getpid()} started={datetime.now(timezone.utc).isoformat()}" + ) + try: + yield + finally: + try: + _LOCK_PATH.unlink() + except FileNotFoundError: + pass + + +DIRECTUS_URL = os.environ.get("DIRECTUS_BASE_URL", "http://directus:8055") +DIRECTUS_TOKEN = os.environ.get("DIRECTUS_TOKEN", "") + +if not DIRECTUS_TOKEN: + env_path = Path(__file__).parent.parent / "directus" / ".env" + if env_path.exists(): + for line in env_path.read_text().splitlines(): + if line.startswith("DIRECTUS_TOKEN="): + DIRECTUS_TOKEN = line.split("=", 1)[1].strip().strip('"').strip("'") + +HEADERS = { + "Authorization": f"Bearer {DIRECTUS_TOKEN}", + "Content-Type": "application/json", +} + + +def _req(method: str, path: str, json_body: dict | None = None) -> dict | list | None: + url = f"{DIRECTUS_URL}{path}" + resp = requests.request(method, url, headers=HEADERS, json=json_body, timeout=60) + if resp.status_code >= 400: + raise RuntimeError(f"{method} {path} → {resp.status_code}: {resp.text[:500]}") + if resp.status_code == 204 or not resp.content: + return None + return resp.json() + + +def fetch_all(collection: str, filter_: dict, fields: list[str]) -> list[dict]: + params = { + "limit": -1, + "filter": json.dumps(filter_), + "fields": ",".join(fields), + } + resp = requests.get( + f"{DIRECTUS_URL}/items/{collection}", + headers=HEADERS, + params=params, + timeout=60, + ) + if resp.status_code >= 400: + raise RuntimeError( + f"fetch_all {collection} failed {resp.status_code}: {resp.text[:500]}" + ) + return resp.json().get("data", []) or [] + + +def _settings_of(ws: dict) -> dict: + raw = ws.get("settings") + return raw if isinstance(raw, dict) else {} + + +def _follows_admins(ws: dict) -> bool: + return _settings_of(ws).get("inherit_team_admins", True) + + +def _follows_members(ws: dict) -> bool: + return _settings_of(ws).get("inherit_team_members", False) + + +def _sticky_user_ids(ws: dict) -> set[str]: + raw = _settings_of(ws).get("sticky_removed") or [] + if not isinstance(raw, list): + return set() + return {t.get("user_id") for t in raw if isinstance(t, dict) and t.get("user_id")} + + +def derive_access_for_org( + workspaces: list[dict], + org_memberships: list[dict], +) -> list[dict]: + """Given all workspaces in one org + that org's memberships, return a + list of (workspace_id, user_id, role) triples that the current + derivation model grants. Mirrors inheritance.user_can_access / + get_effective_members for org+workspace pairs, without a direct row. + + Does not itself check for existing direct rows — caller does that. + """ + out: list[dict] = [] + + for ws in workspaces: + if ws.get("deleted_at"): + continue + ws_id = ws["id"] + sticky = _sticky_user_ids(ws) + follows_admins = _follows_admins(ws) + follows_members = _follows_members(ws) + + for om in org_memberships: + if om.get("deleted_at"): + continue + uid = om.get("user_id") + if not uid: + continue + if uid in sticky: + continue + + role = om.get("role") + + # Team owners always derive admin (team-owner carve-out in + # inheritance.user_can_access). + if role == "owner": + out.append({"workspace_id": ws_id, "user_id": uid, "role": "admin"}) + continue + + # Team admins derive admin on open workspaces only. + if role == "admin" and follows_admins: + out.append({"workspace_id": ws_id, "user_id": uid, "role": "admin"}) + continue + + # Team members derive member only when the workspace opts in. + if role == "member" and follows_admins and follows_members: + out.append({"workspace_id": ws_id, "user_id": uid, "role": "member"}) + + return out + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--apply", action="store_true", + help="Actually insert direct rows. Default is dry-run.") + parser.add_argument("--dry-run", action="store_true", + help="Explicit dry-run flag (default).") + parser.add_argument("--csv", type=str, default=None, + help="Write proposed rows to CSV at this path.") + args = parser.parse_args() + if args.apply and args.dry_run: + print("ERROR: --apply and --dry-run are mutually exclusive") + return 2 + dry_run = not args.apply + + if not DIRECTUS_TOKEN: + print("ERROR: DIRECTUS_TOKEN not set (env or directus/.env)") + return 2 + + print(f"Mode: {'DRY-RUN' if dry_run else 'APPLY'}") + print(f"Directus: {DIRECTUS_URL}") + + health = _req("GET", "/server/health") + if not health: + print("ERROR: Directus /server/health returned empty") + return 2 + print(f"Health: {health.get('status', '?')}") + + script_start_iso = datetime.now(timezone.utc).isoformat() + print(f"Started: {script_start_iso}") + # Idempotency: re-runs dedupe via existing direct rows; no time cutoff + # needed since a row created mid-run will be picked up on the next run + # (which is still a no-op if it's already direct). + + # 1. Fetch all active workspaces. + workspaces = fetch_all( + "workspace", + {"deleted_at": {"_null": True}}, + ["id", "name", "org_id", "settings", "deleted_at"], + ) + print(f"\nActive workspaces in scope: {len(workspaces)}") + + by_org: dict[str, list[dict]] = {} + for ws in workspaces: + oid = ws.get("org_id") + if not oid: + continue + by_org.setdefault(oid, []).append(ws) + + # 2. Fetch all active org_memberships (chunk by org for clarity). + all_om = fetch_all( + "org_membership", + {"deleted_at": {"_null": True}}, + ["id", "org_id", "user_id", "role", "deleted_at"], + ) + om_by_org: dict[str, list[dict]] = {} + for om in all_om: + om_by_org.setdefault(om.get("org_id") or "", []).append(om) + print(f"Active org_memberships: {len(all_om)} across {len(om_by_org)} orgs") + + # 3. Fetch all current direct rows — so we can dedupe proposals. + direct_rows = fetch_all( + "workspace_membership", + { + "source": {"_eq": "direct"}, + "deleted_at": {"_null": True}, + }, + ["workspace_id", "user_id", "role"], + ) + direct_key = {(r["workspace_id"], r["user_id"]) for r in direct_rows} + print(f"Existing direct rows: {len(direct_rows)}") + + # 4. Compute derivations per org + propose rows that have no direct. + proposals: list[dict] = [] + per_org_summary: list[tuple[str, int, int]] = [] # (org_id, ws_count, propose_count) + per_ws_summary: list[tuple[str, str, int]] = [] # (ws_id, ws_name, propose_count) + + for org_id, org_workspaces in by_org.items(): + om_list = om_by_org.get(org_id, []) + derived = derive_access_for_org(org_workspaces, om_list) + + org_propose = 0 + per_ws_counts: dict[str, int] = {} + for d in derived: + key = (d["workspace_id"], d["user_id"]) + if key in direct_key: + continue # direct wins, no-op + proposals.append({ + "workspace_id": d["workspace_id"], + "user_id": d["user_id"], + "role": d["role"], + "org_id": org_id, + }) + org_propose += 1 + per_ws_counts[d["workspace_id"]] = per_ws_counts.get(d["workspace_id"], 0) + 1 + + per_org_summary.append((org_id, len(org_workspaces), org_propose)) + for ws in org_workspaces: + if ws["id"] in per_ws_counts: + per_ws_summary.append((ws["id"], ws.get("name") or "(no name)", + per_ws_counts[ws["id"]])) + + # 5. Summary. + print(f"\n=== Proposal summary ===") + print(f"Proposed INSERT rows: {len(proposals)}") + print(f"Affected orgs: {sum(1 for _, _, n in per_org_summary if n > 0)}") + print(f"Affected workspaces: {len(per_ws_summary)}") + + if per_org_summary: + print("\nPer-org:") + for oid, ws_count, n in sorted(per_org_summary, key=lambda x: -x[2])[:20]: + print(f" org={oid[:8]} workspaces={ws_count:3d} proposed={n}") + + if per_ws_summary: + print("\nTop workspaces by proposed rows:") + for ws_id, ws_name, n in sorted(per_ws_summary, key=lambda x: -x[2])[:15]: + print(f" ws={ws_id[:8]} name={ws_name[:40]:40s} proposed={n}") + + if args.csv: + with open(args.csv, "w", newline="") as f: + w = csv.DictWriter( + f, fieldnames=["org_id", "workspace_id", "user_id", "role"] + ) + w.writeheader() + w.writerows(proposals) + print(f"\nCSV written: {args.csv} ({len(proposals)} rows)") + + if dry_run: + print("\nDry-run — no changes written.") + print("Paste the proposal summary into 04-QUESTIONS-FOR-SAMEER.md") + print("and wait for Sameer's sign-off before running with --apply.") + return 0 + + if not proposals: + print("\nNothing to apply.") + return 0 + + # Lock only for the mutating portion. + try: + lock_ctx = _exclusive_lock() + lock_ctx.__enter__() + except RuntimeError as exc: + print(f"ERROR: {exc}") + return 2 + + errors = 0 + written = 0 + try: + for p in proposals: + try: + _req( + "POST", + "/items/workspace_membership", + { + "id": str(uuid.uuid4()), + "workspace_id": p["workspace_id"], + "user_id": p["user_id"], + "role": p["role"], + "source": "direct", + "is_external": False, + }, + ) + written += 1 + except Exception as e: + errors += 1 + print( + f" FAIL ws={p['workspace_id'][:8]} " + f"user={p['user_id'][:8]}: {e}" + ) + print(f"\nWrote {written}/{len(proposals)} direct rows. Errors: {errors}") + finally: + lock_ctx.__exit__(None, None, None) + + if errors: + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 28ff73291031bf7117f12179f4c332a9219bb23e Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:03:42 +0000 Subject: [PATCH 101/208] =?UTF-8?q?S6:=20roles=20=E2=80=94=20retire=20view?= =?UTF-8?q?er,=20add=20billing=20preset,=20wire=20upgrade:request=20policy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matrix v1.1 §4 + §11 role model. Workspace roles: member / billing / admin / owner. - billing preset: view_usage, view_invoices, update_payment, upgrade:request. No project capabilities. No invite. Matches matrix §4. - admin preset gains view_invoices, update_payment, workspace:webhooks, and the new upgrade:request policy. - viewer preset removed (D11). Legacy rows normalised to member on context build (middleware) + at has_policy read. No migration; a warning logs every time a legacy viewer row is observed so ops can clean at leisure. Org roles: add team-level billing. Same shape — org:view_all_workspaces + view_usage + view_invoices + update_payment. Cannot invite, cannot create workspaces, cannot change team settings. Policies: - New: workspace:view_invoices, workspace:update_payment, upgrade:request, workspace:webhooks (changemaker-gated), org:view_invoices, org:update_payment. - STAFF_POLICIES set introduced for future narrowing of auth.is_admin (matrix §11 "staff:can_set_tier"). Not wired yet — placeholder literal so future grep finds it. - workspace:webhooks now auto-gated at changemaker via TIER_REQUIRED_FOR_POLICY (D12). Endpoints: - POST /v2/workspaces/:id/upgrade-request now gates on upgrade:request (was member:invite — hacky proxy that blocked billing from requesting). - PATCH /v2/workspaces/:id/members/:id accepts member | billing | admin | owner. Hierarchy rewritten: member(1) < billing(2) < admin(3) < owner(4). - POST /v2/workspaces/:id/invite same. Guest hard-rule extended to block promotion into billing. Frontend: - WorkspaceSettingsRoute role dropdown + invite radio drop viewer, add billing. Copy matches matrix §4. Schema (workspace.visibility enum, workspace_membership.role enum add for billing) lands in the next commit alongside the create_schema.py pass. Current code accepts 'billing' in role literals; Directus field is free text today so the write path works end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspaces/WorkspaceSettingsRoute.tsx | 23 ++--- echo/server/dembrane/api/v2/invites.py | 28 +++--- echo/server/dembrane/api/v2/me.py | 2 +- echo/server/dembrane/api/v2/middleware.py | 8 +- .../dembrane/api/v2/workspace_settings.py | 17 ++-- echo/server/dembrane/api/v2/workspaces.py | 14 +-- echo/server/dembrane/policies.py | 90 +++++++++++++++++-- 7 files changed, 135 insertions(+), 47 deletions(-) diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx index d591c6ba..e0d97130 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx @@ -445,12 +445,15 @@ export const WorkspaceSettingsRoute = () => { // backend blocks non-owner promotion to owner // anyway, but hiding the option prevents the // misleading "why did this fail?" click path. - // Externals can't be admin or owner (hard rule). + // Guests (is_external=true) can't be admin, + // owner, or billing (hard rule). data={[ - { label: t`Viewer`, value: "viewer" }, { label: t`Member`, value: "member" }, ...(!member.is_external - ? [{ label: t`Admin`, value: "admin" }] + ? [ + { label: t`Billing`, value: "billing" }, + { label: t`Admin`, value: "admin" }, + ] : []), ...(myRole === "owner" && !member.is_external ? [{ label: t`Owner`, value: "owner" }] @@ -689,23 +692,23 @@ export const WorkspaceSettingsRoute = () => { > - {t`Viewer`} + {t`Member`} - Can view projects and conversations. Cannot edit anything. + Can create projects, run conversations, and generate reports. } /> - {t`Member`} + {t`Billing`} - Can create projects, run conversations, and generate reports. + Sees usage, invoices, and payment. Doesn't touch projects. } @@ -716,7 +719,7 @@ export const WorkspaceSettingsRoute = () => { {t`Admin`} - Everything a member can do, plus invite others and manage workspace settings. + Everything a member can do, plus invite others and manage the workspace. } diff --git a/echo/server/dembrane/api/v2/invites.py b/echo/server/dembrane/api/v2/invites.py index b8cbee55..34d37280 100644 --- a/echo/server/dembrane/api/v2/invites.py +++ b/echo/server/dembrane/api/v2/invites.py @@ -60,27 +60,31 @@ async def invite_to_workspace( email = body.email.strip().lower() role = body.role - if role not in ("admin", "member", "viewer"): - raise HTTPException(status_code=400, detail="Role must be admin, member, or viewer") + if role not in ("admin", "member", "billing"): + raise HTTPException( + status_code=400, detail="Role must be admin, member, or billing" + ) - # Prevent role escalation — can only grant roles at or below your own level - ROLE_HIERARCHY = {"viewer": 0, "member": 1, "admin": 2, "owner": 3} + # Prevent role escalation — can only grant roles at or below your own level. + # Billing sits between member and admin: it's more than a member (financial + # visibility) but less than an admin (no project or content control). + ROLE_HIERARCHY = {"member": 1, "billing": 2, "admin": 3, "owner": 4} inviter_level = ROLE_HIERARCHY.get(ctx.role, 0) requested_level = ROLE_HIERARCHY.get(role, 0) if requested_level > inviter_level: raise HTTPException(status_code=403, detail="Cannot grant a role higher than your own") - # Hard rule: externals (non-team members on a workspace) can only ever be - # member or viewer. Owner/admin roles imply management responsibility, - # which doesn't make sense for a guest of another team. If the caller - # isn't inviting this person as a team member (is_org_member=false), - # clamp to member max. - if not body.is_org_member and role in ("admin", "owner"): + # Hard rule: externals (guests — non-team members on a workspace) can + # only ever be member. Admin/owner/billing roles imply management or + # financial responsibility that doesn't make sense for a guest of + # another team. If the caller isn't inviting this person as a team + # member (is_org_member=false), clamp to member. + if not body.is_org_member and role in ("admin", "owner", "billing"): raise HTTPException( status_code=400, detail=( - "External members can't be admins or owners. " - "Invite them as a team member first, or choose member/viewer." + "Guests can't be admins, owners, or billing. " + "Invite them as a team member first, or choose member." ), ) diff --git a/echo/server/dembrane/api/v2/me.py b/echo/server/dembrane/api/v2/me.py index 12f9e78d..2b5fccac 100644 --- a/echo/server/dembrane/api/v2/me.py +++ b/echo/server/dembrane/api/v2/me.py @@ -19,7 +19,7 @@ _accept_rate_limiter = create_user_rate_limiter(name="invite_accept", capacity=30, window_seconds=3600) -_ROLE_LEVEL = {"viewer": 0, "member": 1, "admin": 2, "owner": 3} +_ROLE_LEVEL = {"member": 1, "billing": 2, "admin": 3, "owner": 4} @router.get("", response_model=MeResponse) diff --git a/echo/server/dembrane/api/v2/middleware.py b/echo/server/dembrane/api/v2/middleware.py index b54087de..6e12daba 100644 --- a/echo/server/dembrane/api/v2/middleware.py +++ b/echo/server/dembrane/api/v2/middleware.py @@ -120,11 +120,17 @@ async def list_projects(ctx: WorkspaceContext = Depends(get_workspace_context)): custom_policies = rows[0].get("custom_policies") or [] is_external = bool(rows[0].get("is_external", False)) + # Normalize legacy role names at context build so every downstream + # check — has_policy, role-hierarchy compares, UI serialisation — sees + # the current role set. D11: viewer → member. + from dembrane.policies import _normalize_legacy_role + normalized_role = _normalize_legacy_role(role) or role + return WorkspaceContext( workspace_id=workspace_id, workspace=workspace, app_user_id=app_user_id, - role=role, + role=normalized_role, custom_policies=custom_policies, source=source, is_external=is_external, diff --git a/echo/server/dembrane/api/v2/workspace_settings.py b/echo/server/dembrane/api/v2/workspace_settings.py index 4075dd90..cb8a47cd 100644 --- a/echo/server/dembrane/api/v2/workspace_settings.py +++ b/echo/server/dembrane/api/v2/workspace_settings.py @@ -390,7 +390,7 @@ async def remove_workspace_member( # ── Change member role ── -ROLE_HIERARCHY = {"viewer": 0, "member": 1, "admin": 2, "owner": 3} +ROLE_HIERARCHY = {"member": 1, "billing": 2, "admin": 3, "owner": 4} class ChangeRoleRequest(BaseModel): @@ -406,7 +406,7 @@ async def change_member_role( """Change a member's role. Requires member:manage.""" ctx.require_policy("member:manage") - if body.role not in ("viewer", "member", "admin", "owner"): + if body.role not in ("member", "billing", "admin", "owner"): raise HTTPException(status_code=400, detail="Invalid role") # Prevent escalation — can only set roles at or below your own level @@ -421,15 +421,16 @@ async def change_member_role( if membership.get("deleted_at"): raise HTTPException(status_code=404, detail="Membership already removed") - # Hard rule: an external member can never be admin or owner. Externals - # are guests — promoting them into management roles mixes access layers - # that should stay separate. If you want them as admin, add them to the - # team first, then promote. - if membership.get("is_external") and body.role in ("admin", "owner"): + # Hard rule: a guest (is_external=true) can never be admin, owner, or + # billing. Guests live inside one workspace without team-level presence; + # promoting them into management or financial roles mixes access layers + # that should stay separate. To promote, add them to the team first, then + # change role. + if membership.get("is_external") and body.role in ("admin", "owner", "billing"): raise HTTPException( status_code=400, detail=( - "External members can't be admins or owners. Add them to the " + "Guests can't be admins, owners, or billing. Add them to the " "team first (via invite with is_org_member=true), then promote." ), ) diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 360f83fa..65e9f69c 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -622,19 +622,19 @@ async def request_upgrade( body: UpgradeRequestBody, ctx: WorkspaceContext = Depends(get_workspace_context), ) -> dict: - """Admin clicks "Request upgrade" in the tier compare view. Sends an - email to settings.email.upgrade_request_inbox with context. Configurable - via UPGRADE_REQUEST_INBOX env var (defaults to upgrades@dembrane.com per - matrix v1.1 §11). + """Admin or billing clicks "Request upgrade" in the tier compare view. + Sends an email to settings.email.upgrade_request_inbox with context. + Configurable via UPGRADE_REQUEST_INBOX env var (defaults to + upgrades@dembrane.com per matrix v1.1 §11). Member role doesn't see this CTA (matrix §11 — member-role path shows - copy only, no button). Enforced here by require_policy(member:invite) - which only admin/owner have — keeps the endpoint from being abused. + copy only, no button). Enforced here by require_policy(upgrade:request) + which admin and billing have — members do not. Rate-limited per-user (5/hr) to avoid flooding the billing inbox when the UI misfires or a bored admin leans on the button. """ - ctx.require_policy("member:invite") + ctx.require_policy("upgrade:request") await _upgrade_request_rate_limiter.check(ctx.app_user_id) diff --git a/echo/server/dembrane/policies.py b/echo/server/dembrane/policies.py index 3b04e3f8..f4cd9bd2 100644 --- a/echo/server/dembrane/policies.py +++ b/echo/server/dembrane/policies.py @@ -11,24 +11,43 @@ from __future__ import annotations +from logging import getLogger + +logger = getLogger("dembrane.policies") + # ── Tier ordering (lowest to highest) ── TIER_ORDER: list[str] = ["pilot", "pioneer", "innovator", "changemaker", "guardian"] # Policies that require a minimum workspace tier. Enforced automatically by -# has_policy() when the caller passes workspace_tier. See release-checklist.md -# §"Tier + role gating matrix" for the canonical list. +# has_policy() when the caller passes workspace_tier. Matrix v1.1 §2 lists +# the canonical gates. TIER_REQUIRED_FOR_POLICY: dict[str, str] = { "workspace:export": "innovator", "project:share": "innovator", "workspace:whitelabel": "changemaker", "workspace:api_access": "changemaker", + "workspace:webhooks": "changemaker", "workspace:set_private": "innovator", "project:set_private": "innovator", } +# ── Staff policies (narrower than auth.is_admin) ── +# +# Matrix v1.1 §11 introduces a finer grain than "any Directus administrator." +# Wiring in progress — endpoints that read auth.is_admin today will migrate +# to require(staff_policy=...) once we have a storage mechanism (claim list +# on app_user or JWT). Until then, literals live here for reference and +# future-you can grep. +STAFF_POLICIES: set[str] = { + "staff:can_set_tier", # PATCH /v2/workspaces/:id/tier + "staff:can_set_visibility", # Force workspace visibility, bypass tier check + "staff:can_transfer", # Workspace transfer (partner handoff flips) +} + + # ── Org role presets ── ORG_ROLE_PRESETS: dict[str, list[str]] = { @@ -44,18 +63,34 @@ "org:view_all_workspaces", "org:view_usage", ], + # Billing role at the team level — matrix v1.1 §5. Sees every workspace + # for usage + invoicing but cannot invite, create workspaces, or change + # team settings. + "billing": [ + "org:view", + "org:view_all_workspaces", + "org:view_usage", + "org:view_invoices", + "org:update_payment", + ], "owner": ["*"], } # ── Workspace role presets ── +# +# Matrix v1.1 §4 collapses to four roles: Admin / Billing / Member / Guest. +# - Admin (code: admin + owner) — full workspace control + billing. +# - Billing — financial surface only; no project capabilities. +# - Member — content author. +# - Guest (code: is_external=true on a direct row + 'member' role preset with +# one edge: guests cannot delete conversations). Enforced at the endpoint +# layer; preset is shared with member for simplicity. +# +# Retired: 'viewer' (matrix has no viewer role; D11). If any stray rows +# surface with role='viewer', _normalize_legacy_role treats them as 'member'. WORKSPACE_ROLE_PRESETS: dict[str, list[str]] = { - "viewer": [ - "project:read", - "conversation:read", - "report:view", - ], "member": [ "project:read", "project:create", @@ -84,16 +119,28 @@ "member:manage", "settings:manage", "workspace:view_usage", + "workspace:view_invoices", + "workspace:update_payment", "workspace:export", "workspace:set_private", "workspace:whitelabel", "workspace:api_access", + "workspace:webhooks", + "upgrade:request", + ], + "billing": [ + # Matrix v1.1 §4: financial visibility only. No project or content + # access. Cannot invite, cannot create projects. CAN request upgrade. + "workspace:view_usage", + "workspace:view_invoices", + "workspace:update_payment", + "upgrade:request", ], "owner": ["*"], } -# ── Project role presets (for project_user sharing, innovator+ tier) ── +# ── Project role presets (for private project sharing, innovator+ tier) ── PROJECT_ROLE_PRESETS: dict[str, list[str]] = { "viewer": [ @@ -114,12 +161,38 @@ } +# ── Role mapping (defensive) ── + +_LEGACY_ROLE_MAP = { + "viewer": "member", # D11: viewer retired, no migration; map at read. +} + + +def _normalize_legacy_role(role: str | None) -> str | None: + """Map legacy role names to current equivalents. Logs when a legacy + row is seen so ops can spot lingering data. + """ + if role is None: + return None + mapped = _LEGACY_ROLE_MAP.get(role) + if mapped is not None: + logger.warning( + "legacy_role_observed role=%r mapped_to=%r — no migration planned, " + "please convert this row at next touch", + role, + mapped, + ) + return mapped + return role + + def get_effective_policies( role: str, custom_policies: list[str] | None = None, presets: dict[str, list[str]] = WORKSPACE_ROLE_PRESETS, ) -> list[str]: """Compute effective policies: preset for role + any custom additions.""" + role = _normalize_legacy_role(role) or role base = presets.get(role, []) extras = custom_policies or [] return base + extras @@ -139,6 +212,7 @@ def has_policy( need to call require_policy() and the tier check rides along. Callers in test contexts can omit workspace_tier to bypass the tier gate. """ + role = _normalize_legacy_role(role) or role effective = get_effective_policies(role, custom_policies, presets) role_allows = "*" in effective or required in effective if not role_allows: From a815f15510274252507b27c6a80d98ad963294d5 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:05:54 +0000 Subject: [PATCH 102/208] S10a: add workspace.visibility enum (open_to_team | private) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matrix v1.1 §6 first slice. Adds the column + backfills from the existing settings.inherit_team_admins flag. Idempotent — rerunning skips the field add and only backfills NULL rows. Still pending for the full §6 walkback (blocked on backfill_direct_ memberships --apply running in prod per Q7 deferred): - Simplify inheritance.user_can_access to direct-only. - Drop settings.inherit_team_admins / inherit_team_members flags. - Purge settings.sticky_removed tombstones. Dev confirmed: field add + idempotent re-run both clean. sync.sh pulled. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../snapshot/fields/workspace/visibility.json | 55 +++++++++++++ echo/scripts/create_schema.py | 77 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 echo/directus/sync/snapshot/fields/workspace/visibility.json diff --git a/echo/directus/sync/snapshot/fields/workspace/visibility.json b/echo/directus/sync/snapshot/fields/workspace/visibility.json new file mode 100644 index 00000000..2272a478 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/visibility.json @@ -0,0 +1,55 @@ +{ + "collection": "workspace", + "field": "visibility", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "visibility", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": "Matrix v1.1 §6. open_to_team = discoverable by team admins (join) and members (request access). private = visible only to team admins in discovery. Innovator+ tier to create.", + "options": { + "choices": [ + { + "text": "Open to team", + "value": "open_to_team" + }, + { + "text": "Private", + "value": "private" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 16, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "visibility", + "table": "workspace", + "data_type": "character varying", + "default_value": "open_to_team", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py index 3e1a6948..a26c41d3 100644 --- a/echo/scripts/create_schema.py +++ b/echo/scripts/create_schema.py @@ -986,6 +986,82 @@ def step_9_notifications(): # Main # --------------------------------------------------------------------------- +def step_10_workspace_visibility(): + """Add workspace.visibility enum (open_to_team | private). + + Matrix v1.1 §6 replaces the two-boolean inherit_team_admins / + inherit_team_members model with a single visibility enum. This step: + + 1. Adds the column (nullable for transition). + 2. Backfills existing rows from settings.inherit_team_admins: + inherit_team_admins == True → 'open_to_team' (default) + inherit_team_admins == False → 'private' + + What it does NOT do (those happen after the backfill_direct_memberships + script runs --apply in prod): + - Drop settings.inherit_team_admins / inherit_team_members flags. + - Drop settings.sticky_removed tombstones. + - Simplify inheritance.user_can_access to direct-only. + + Idempotent — rerunning only backfills rows still NULL. + """ + print("\n=== Step 10: workspace.visibility enum ===") + + add_field("workspace", "visibility", { + "type": "string", + "schema": {"is_nullable": True, "default_value": "open_to_team"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Open to team", "value": "open_to_team"}, + {"text": "Private", "value": "private"}, + ]}, + "note": ( + "Matrix v1.1 §6. open_to_team = discoverable by team admins " + "(join) and members (request access). private = visible only " + "to team admins in discovery. Innovator+ tier to create." + ), + }, + }) + + # Backfill from existing settings flags for any workspace still NULL. + # Batched paging handled by fetch-all behavior. + resp = api( + "GET", + "/items/workspace" + "?fields=id,settings,visibility,deleted_at" + "&filter[visibility][_null]=true" + "&filter[deleted_at][_null]=true" + "&limit=-1", + ) + if not resp: + print(" WARN: could not fetch workspaces to backfill") + return True + rows = resp.get("data") or [] + print(f" Backfilling {len(rows)} workspaces with NULL visibility") + + fixed = 0 + failed = 0 + for row in rows: + settings = row.get("settings") or {} + if not isinstance(settings, dict): + settings = {} + follows_admins = settings.get("inherit_team_admins", True) + visibility = "open_to_team" if follows_admins else "private" + result = api( + "PATCH", + f"/items/workspace/{row['id']}", + {"visibility": visibility}, + ) + if result is not None: + fixed += 1 + else: + failed += 1 + print(f" Backfilled {fixed}/{len(rows)} (errors: {failed})") + + return True + + STEPS = { "1": ("app_user", step_1_app_user), "2": ("org + org_membership", step_2_org), @@ -996,6 +1072,7 @@ def step_9_notifications(): "7": ("deleted_at on conversation, project_chat, project_report", step_7_deleted_at), "8": ("remove legacy chat", step_8_remove_chat), "9": ("notifications trio (inbox)", step_9_notifications), + "10": ("workspace.visibility enum + backfill", step_10_workspace_visibility), } From e734319c7de0d79712517f7c1334f171677de9b2 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:13:40 +0000 Subject: [PATCH 103/208] S13: GET /v2/workspaces/:id/usage + tier_capacity matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matrix v1.1 §8 workspace-level rollup. New: /workspaces/echo/server/dembrane/tier_capacity.py — canonical per-tier capacity records (taglines, price, included seats/hours, overage rates, hard-block flag, guest cap, duration). Single source of truth; every surface that displays tier info reads from here. Helpers: compute_hour_overage_eur, compute_seat_overage_eur, next_tier, get_capacity. Endpoint: GET /v2/workspaces/:id/usage. - Gated on workspace:view_usage. Guests (is_external=true) rejected even though they carry the member preset — matrix §4 View-usage is member+ only. - Audio hours derived at read time from SUM(conversation.duration) where deleted_at IS NULL AND created_at within current calendar month. No usage_event table (D9). - Per-project breakdown of hours + conversation count. - Seat count: direct members with role in (member, billing, admin, owner) and is_external=false (matrix §7). - Guest count: direct members with is_external=true. Guest w/ elevated role logs a warning (shouldn't happen; blocked at invite + change-role). - Admin + billing also receive overage_forecast_eur, seat_overage_eur, next_tier recommendation. Members get raw numbers only. - Pilot hard-block flag surfaced for UI consumption; enforcement lands with the host-side gate in the next commit. Preset fix (audit): member preset was missing workspace:view_usage — matrix §4 grants it. Added. Guest exclusion handled in the endpoint since we don't fork the preset for is_external=true. Legacy-row safety (audit): workspaces with NULL tier fall through to the unknown-tier unlimited path instead of defaulting to Pilot (which would silently hard-block). Webhooks gate (D12 follow-through): workspace:webhooks added to TIER_REQUIRED_FOR_POLICY in a prior commit; this commit adds the matching DOWNGRADE_EFFECTS entry (freeze — existing webhooks keep firing; no new configs) and the _HUMAN copy row. Audit ledger: F1 (member preset missing view_usage) fixed in same commit; F2 (Pilot fallback) fixed; F3 (elevated-role guest logging) fixed; F4 (billing-as-seat) false-positive, kept per matrix §7. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/workspaces.py | 261 ++++++++++++++++++++++ echo/server/dembrane/policies.py | 1 + echo/server/dembrane/tier_capacity.py | 150 +++++++++++++ echo/server/dembrane/tier_downgrade.py | 2 + 4 files changed, 414 insertions(+) create mode 100644 echo/server/dembrane/tier_capacity.py diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 65e9f69c..36909a12 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -711,3 +711,264 @@ async def request_upgrade( ) return {"status": "sent"} + + +# ──────────────────────────────────────────────────────────────────── +# Usage rollup (matrix §8) +# ──────────────────────────────────────────────────────────────────── + + +def _calendar_month_bounds(now: datetime) -> tuple[str, str]: + """Return (iso_start, iso_end_of_next_month) for the calendar month + containing `now`. Month-end is exclusive (so < next_start is the + inclusive-of-this-month check).""" + month_start = now.replace( + day=1, hour=0, minute=0, second=0, microsecond=0 + ) + if now.month == 12: + next_start = month_start.replace(year=now.year + 1, month=1) + else: + next_start = month_start.replace(month=now.month + 1) + return month_start.isoformat(), next_start.isoformat() + + +class ProjectUsageItem(BaseModel): + id: str + name: str + audio_hours: float + conversation_count: int + + +class NextTierRecommendation(BaseModel): + tier: str + tagline: str + price_eur_monthly: Optional[int] + price_note: str + included_hours: Optional[int] + included_seats: Optional[int] + + +class WorkspaceUsageResponse(BaseModel): + # Everyone with workspace:view_usage sees these. + cycle_start: str + cycle_end_exclusive: str + tier: str + tier_tagline: str + audio_hours: float + audio_hours_included: Optional[int] # None = unlimited + seat_count: int + seat_count_included: Optional[int] + guest_count: int + guest_cap: Optional[int] + project_count: int + projects: list[ProjectUsageItem] + pilot_hard_block_active: bool # informational for members too + + # Admin + billing only — None for members. + overage_forecast_eur: Optional[float] = None + seat_overage_eur: Optional[float] = None + next_tier: Optional[NextTierRecommendation] = None + + +@router.get( + "/{workspace_id}/usage", + response_model=WorkspaceUsageResponse, +) +async def get_workspace_usage( + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> WorkspaceUsageResponse: + """Workspace usage rollup for the current calendar month. + + Members see raw numbers. Admin + billing additionally see overage + forecast and tier recommendation (matrix §8). + + Implementation: hours derive from `conversation.duration` SUM where + `deleted_at IS NULL AND created_at >= month_start AND created_at < + next_month_start`. No separate `usage_event` table (D9). + """ + from dembrane.tier_capacity import ( + compute_hour_overage_eur, + compute_seat_overage_eur, + get_capacity, + next_tier as tier_next, + ) + + ctx.require_policy("workspace:view_usage") + + # Guest exclusion. Matrix §4 "View usage & overage" row grants Admin / + # Billing / Member but not Guest. Our preset system gives guests the + # member preset (guest = is_external=true on a direct row), so we gate + # here explicitly rather than forking the preset. + if ctx.is_external: + raise HTTPException(status_code=403, detail="Guests don't see workspace usage") + + now = datetime.now(timezone.utc) + cycle_start, cycle_end_exclusive = _calendar_month_bounds(now) + + # Projects in workspace (also used for per-project breakdown). + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_eq": ctx.workspace_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id", "name"], + "limit": -1, + } + }, + ) + if not isinstance(projects, list): + projects = [] + + project_ids = [p["id"] for p in projects if p.get("id")] + + # Conversations in this workspace, this cycle. + if project_ids: + conversations = await async_directus.get_items( + "conversation", + { + "query": { + "filter": { + "project_id": {"_in": project_ids}, + "deleted_at": {"_null": True}, + "created_at": { + "_gte": cycle_start, + "_lt": cycle_end_exclusive, + }, + }, + "fields": ["project_id", "duration"], + "limit": -1, + } + }, + ) + else: + conversations = [] + if not isinstance(conversations, list): + conversations = [] + + # Per-project and total aggregates. + per_project_seconds: dict[str, int] = {} + per_project_count: dict[str, int] = {} + total_seconds = 0 + for c in conversations: + pid = c.get("project_id") + if not pid: + continue + sec = int(c.get("duration") or 0) + total_seconds += sec + per_project_seconds[pid] = per_project_seconds.get(pid, 0) + sec + per_project_count[pid] = per_project_count.get(pid, 0) + 1 + + per_project_items = [ + ProjectUsageItem( + id=p["id"], + name=p.get("name", ""), + audio_hours=round(per_project_seconds.get(p["id"], 0) / 3600, 2), + conversation_count=per_project_count.get(p["id"], 0), + ) + for p in projects + ] + + audio_hours = round(total_seconds / 3600, 2) + + # Seat + guest count. Members + admin + billing count as seats; guest + # (is_external=true) is its own bucket and is not billed (matrix §7). + members = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": ctx.workspace_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["role", "is_external"], + "limit": -1, + } + }, + ) + if not isinstance(members, list): + members = [] + + seat_count = 0 + guest_count = 0 + for m in members: + role = m.get("role") + if m.get("is_external"): + # Guest bucket. A guest with an elevated role (admin/billing/ + # owner) shouldn't exist — blocked at invite + change-role — but + # log if we ever see one so ops can spot it. + if role in ("admin", "billing", "owner"): + logger.warning( + "external_with_elevated_role workspace=%s role=%s", + ctx.workspace_id, role, + ) + guest_count += 1 + continue + if role in ("owner", "admin", "member", "billing"): + seat_count += 1 + + # Tier capacity lookup. Legacy rows with NULL tier fall through to the + # unknown-tier path below (unlimited / no block) rather than silently + # adopting Pilot defaults — the Pilot hard-block is only fair when + # the workspace was explicitly set to Pilot. + tier = ctx.workspace.get("tier") or "" + cap = get_capacity(tier) + if cap is None: + # Unknown tier — treat as unlimited / no block. Safer default + # than crashing on a legacy row. + tagline = "" + included_hours: Optional[int] = None + included_seats: Optional[int] = None + guest_cap: Optional[int] = None + hard_block = False + else: + tagline = cap.tagline + included_hours = cap.included_hours + included_seats = cap.included_seats + guest_cap = cap.guest_cap + hard_block = ( + cap.hard_block_on_hours + and cap.included_hours is not None + and audio_hours >= cap.included_hours + ) + + base = WorkspaceUsageResponse( + cycle_start=cycle_start, + cycle_end_exclusive=cycle_end_exclusive, + tier=tier, + tier_tagline=tagline, + audio_hours=audio_hours, + audio_hours_included=included_hours, + seat_count=seat_count, + seat_count_included=included_seats, + guest_count=guest_count, + guest_cap=guest_cap, + project_count=len(projects), + projects=per_project_items, + pilot_hard_block_active=hard_block, + ) + + # Admin + billing: add financial surface. + if ctx.has_policy("workspace:view_invoices"): + overage_forecast = compute_hour_overage_eur(tier, audio_hours) + seat_overage = compute_seat_overage_eur(tier, seat_count) + recommended = tier_next(tier) + next_rec: Optional[NextTierRecommendation] = None + if recommended: + rcap = get_capacity(recommended) + if rcap: + next_rec = NextTierRecommendation( + tier=recommended, + tagline=rcap.tagline, + price_eur_monthly=rcap.price_eur_monthly, + price_note=rcap.price_note, + included_hours=rcap.included_hours, + included_seats=rcap.included_seats, + ) + base.overage_forecast_eur = overage_forecast + base.seat_overage_eur = seat_overage + base.next_tier = next_rec + + return base diff --git a/echo/server/dembrane/policies.py b/echo/server/dembrane/policies.py index f4cd9bd2..eefa29ae 100644 --- a/echo/server/dembrane/policies.py +++ b/echo/server/dembrane/policies.py @@ -100,6 +100,7 @@ "chat:use", "report:view", "report:generate", + "workspace:view_usage", # matrix §4: members see usage (raw, no €). ], "admin": [ "project:read", diff --git a/echo/server/dembrane/tier_capacity.py b/echo/server/dembrane/tier_capacity.py new file mode 100644 index 00000000..bad9797d --- /dev/null +++ b/echo/server/dembrane/tier_capacity.py @@ -0,0 +1,150 @@ +"""Per-tier capacity + pricing — the canonical matrix. + +Single source of truth for matrix v1.1 §1 (tier × capacity). Every surface +that shows a tier's limits, taglines, or pricing should read from here — +no duplication in i18n strings, UI components, or email templates. + +Hard truths encoded here: + - Pilot hard-blocks host-side operations at the hour cap. + - Pioneer+ tiers bill overage; no hard block. + - Guardian is unlimited, subject to commercial agreement. + +When pricing changes, edit here. Downstream code reads these records and +never hard-codes the numbers. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + + +@dataclass(frozen=True) +class TierCapacity: + tier: str + tagline: str + price_eur_monthly: Optional[int] # None for one-off Pilot, None for Guardian + price_note: str # "one-time" / "per month" / "negotiated" + included_seats: Optional[int] # None = unlimited + seat_overage_eur: Optional[int] # None = not billed + included_hours: Optional[int] # None = unlimited + hour_overage_eur: Optional[int] # None = hard block (pilot) or unlimited (guardian) + hard_block_on_hours: bool # Pilot only + guest_cap: Optional[int] # None = unlimited + training_included: str # human-readable + duration: str # "1 month" / "ongoing" / etc + + +# Ordered lowest → highest. Matches TIER_ORDER in policies.py. +TIER_CAPACITIES: dict[str, TierCapacity] = { + "pilot": TierCapacity( + tier="pilot", + tagline="one month to try it.", + price_eur_monthly=None, + price_note="€349 one-time", + included_seats=2, + seat_overage_eur=None, + included_hours=10, + hour_overage_eur=None, + hard_block_on_hours=True, + guest_cap=2, + training_included="2 people", + duration="1 month", + ), + "pioneer": TierCapacity( + tier="pioneer", + tagline="for your first real engagements.", + price_eur_monthly=200, + price_note="per month", + included_seats=3, + seat_overage_eur=25, + included_hours=25, + hour_overage_eur=5, + hard_block_on_hours=False, + guest_cap=5, + training_included="—", + duration="ongoing", + ), + "innovator": TierCapacity( + tier="innovator", + tagline="privacy and data portability.", + price_eur_monthly=500, + price_note="per month", + included_seats=10, + seat_overage_eur=30, + included_hours=50, + hour_overage_eur=4, + hard_block_on_hours=False, + guest_cap=20, + training_included="—", + duration="ongoing", + ), + "changemaker": TierCapacity( + tier="changemaker", + tagline="your brand, your integrations.", + price_eur_monthly=1500, + price_note="per month", + included_seats=20, + seat_overage_eur=60, + included_hours=100, + hour_overage_eur=3, + hard_block_on_hours=False, + guest_cap=50, + training_included="—", + duration="ongoing", + ), + "guardian": TierCapacity( + tier="guardian", + tagline="enterprise scale.", + price_eur_monthly=5000, + price_note="per month", + included_seats=None, + seat_overage_eur=None, + included_hours=None, + hour_overage_eur=None, + hard_block_on_hours=False, + guest_cap=None, + training_included="negotiable", + duration="ongoing", + ), +} + + +def get_capacity(tier: str) -> Optional[TierCapacity]: + """Look up a tier's capacity. Returns None if the tier name is unknown + (e.g., a legacy row). Callers should treat None as "unlimited / no + block" rather than crashing.""" + return TIER_CAPACITIES.get(tier) + + +def next_tier(tier: str) -> Optional[str]: + """Return the next tier up, or None if `tier` is the top (guardian) or + unknown.""" + order = list(TIER_CAPACITIES.keys()) + try: + idx = order.index(tier) + except ValueError: + return None + if idx + 1 >= len(order): + return None + return order[idx + 1] + + +def compute_hour_overage_eur(tier: str, hours_used: float) -> float: + """Monthly overage cost in € for a given tier + hours used. Zero when + under the included cap, for Pilot (hard block — no overage bill), and + for Guardian (unlimited).""" + cap = get_capacity(tier) + if cap is None or cap.included_hours is None or cap.hour_overage_eur is None: + return 0.0 + over = max(0.0, hours_used - cap.included_hours) + return round(over * cap.hour_overage_eur, 2) + + +def compute_seat_overage_eur(tier: str, seats_used: int) -> float: + """Monthly overage cost in € for exceeding the included seat count.""" + cap = get_capacity(tier) + if cap is None or cap.included_seats is None or cap.seat_overage_eur is None: + return 0.0 + over = max(0, seats_used - cap.included_seats) + return round(over * cap.seat_overage_eur, 2) diff --git a/echo/server/dembrane/tier_downgrade.py b/echo/server/dembrane/tier_downgrade.py index 30ad3fab..4d89ea9e 100644 --- a/echo/server/dembrane/tier_downgrade.py +++ b/echo/server/dembrane/tier_downgrade.py @@ -33,6 +33,7 @@ DOWNGRADE_EFFECTS: dict[str, Effect] = { "workspace:whitelabel": "revert", # ↺ clear custom logo "workspace:api_access": "freeze", # ❄ existing tokens stay, no new/rotate + "workspace:webhooks": "freeze", # ❄ existing webhooks fire, no new configs "workspace:export": "freeze", # ❄ existing exports intact "project:share": "freeze", # ❄ existing shares stay "workspace:set_private": "freeze", # ❄ stays private @@ -59,6 +60,7 @@ def _startup_check() -> None: _HUMAN: dict[str, str] = { "workspace:whitelabel": "Remove your custom logo (revert to dembrane logo)", "workspace:api_access": "Freeze API access (existing tokens keep working; no new tokens)", + "workspace:webhooks": "Freeze webhooks (existing webhooks keep firing; no new configs)", "workspace:export": "Freeze data export (existing files stay; new exports blocked)", "project:share": "Freeze private project sharing (existing shares stay; no new shares)", "workspace:set_private": "Freeze ability to make new private workspaces", From 21e666fa41a030f29072909284e4ce544710036f Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:15:48 +0000 Subject: [PATCH 104/208] =?UTF-8?q?S14:=20Pilot=20hard-block=20on=20host-s?= =?UTF-8?q?ide=20endpoints=20(matrix=20=C2=A78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New dep require_no_pilot_block(ctx) in api/v2/middleware.py: - Short-circuits for non-Pilot tiers + legacy NULL tier (safe default). - Sums conversation.duration for the current calendar month across non-deleted projects + conversations of the workspace. - Raises 402 if hours >= 10 with the matrix-§8 verbatim copy: "Pilot limit reached. Host-side tools are paused. Recording keeps working — your participants are unaffected. Upgrade to continue." New helper tier_capacity.is_hard_blocked(tier, hours_used) — the decision logic, unit-testable without DB. Wired to POST /v2/workspaces/:id/projects (host-side entry point — new project creation). Dep runs after ctx.require_policy("project:create") so unauthorized callers get 403, not 402. Participant portal untouched. Recording + upload + transcription (in api/participant.py and the v1 conversation path) are not gated here. Still to wire in follow-up commits (host-side v1 endpoints): - chat / agentic analysis (api/chat.py, api/agentic.py) - transcript view (api/conversation.py) - report generate / update (api/project.py + tasks.py) - data export Those endpoints live in v1 and don't take a WorkspaceContext today — a workspace_id-only variant of require_no_pilot_block lands with those. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/middleware.py | 102 ++++++++++++++++++ .../dembrane/api/v2/workspace_projects.py | 14 ++- echo/server/dembrane/tier_capacity.py | 23 ++++ 3 files changed, 137 insertions(+), 2 deletions(-) diff --git a/echo/server/dembrane/api/v2/middleware.py b/echo/server/dembrane/api/v2/middleware.py index 6e12daba..e074f188 100644 --- a/echo/server/dembrane/api/v2/middleware.py +++ b/echo/server/dembrane/api/v2/middleware.py @@ -135,3 +135,105 @@ async def list_projects(ctx: WorkspaceContext = Depends(get_workspace_context)): source=source, is_external=is_external, ) + + +# ──────────────────────────────────────────────────────────────────── +# Pilot hard-block — host-side operations only (matrix §8) +# ──────────────────────────────────────────────────────────────────── + + +async def _current_cycle_hours(workspace_id: str) -> float: + """Sum of conversation.duration across this workspace's projects for + the current calendar month (UTC). Respects soft-delete on both + project and conversation.""" + from datetime import datetime, timezone + + now = datetime.now(timezone.utc) + month_start = now.replace( + day=1, hour=0, minute=0, second=0, microsecond=0 + ) + if now.month == 12: + next_start = month_start.replace(year=now.year + 1, month=1) + else: + next_start = month_start.replace(month=now.month + 1) + + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": -1, + } + }, + ) + if not isinstance(projects, list) or not projects: + return 0.0 + + ids = [p["id"] for p in projects if p.get("id")] + if not ids: + return 0.0 + + conversations = await async_directus.get_items( + "conversation", + { + "query": { + "filter": { + "project_id": {"_in": ids}, + "deleted_at": {"_null": True}, + "created_at": { + "_gte": month_start.isoformat(), + "_lt": next_start.isoformat(), + }, + }, + "fields": ["duration"], + "limit": -1, + } + }, + ) + if not isinstance(conversations, list): + return 0.0 + + total = sum(int(c.get("duration") or 0) for c in conversations) + return total / 3600.0 + + +async def require_no_pilot_block( + ctx: WorkspaceContext, +) -> None: + """Raise 402 if this workspace is a Pilot workspace at or past its + 10-hour cap. Matrix §8: **host-side** operations are blocked; the + participant portal (recording, upload, transcription) is never gated + on this. + + Use this dep in addition to `get_workspace_context` on host-side + endpoints: project creation, chat / agentic analysis, transcript + view, report generate/update, data export. + + Copy choice (response body) includes the participant-reassurance line + verbatim per matrix — the UI's level-3 modal (screens/status-banner) + lifts this text for the hard-block screen. + """ + from dembrane.tier_capacity import is_hard_blocked + + tier = ctx.workspace.get("tier") + if not tier: + return # legacy NULL tier — treat as unlimited (not Pilot) + + # Only Pilot hard-blocks. Short-circuit before reaching the DB. + if tier != "pilot": + return + + hours = await _current_cycle_hours(ctx.workspace_id) + if is_hard_blocked(tier, hours): + raise HTTPException( + status_code=402, + detail=( + "Pilot limit reached. Host-side tools are paused. " + "Recording keeps working — your participants are unaffected. " + "Upgrade to continue." + ), + ) diff --git a/echo/server/dembrane/api/v2/workspace_projects.py b/echo/server/dembrane/api/v2/workspace_projects.py index ff51a5d6..94843b20 100644 --- a/echo/server/dembrane/api/v2/workspace_projects.py +++ b/echo/server/dembrane/api/v2/workspace_projects.py @@ -8,7 +8,11 @@ from dembrane.utils import generate_uuid from dembrane.directus_async import async_directus -from dembrane.api.v2.middleware import WorkspaceContext, get_workspace_context +from dembrane.api.v2.middleware import ( + WorkspaceContext, + get_workspace_context, + require_no_pilot_block, +) from dembrane.api.dependency_auth import DependencyDirectusSession router = APIRouter() @@ -406,8 +410,14 @@ async def create_workspace_project( auth: DependencyDirectusSession, ctx: WorkspaceContext = Depends(get_workspace_context), ) -> V2CreateProjectResponse: - """Create a project in a workspace. Requires project:create policy.""" + """Create a project in a workspace. Requires project:create policy. + + Host-side operation — gated by the Pilot hard-block (matrix §8). A + Pilot workspace at the 10h cap cannot create new projects until the + admin upgrades; the participant portal continues to operate regardless. + """ ctx.require_policy("project:create") + await require_no_pilot_block(ctx) project_id = generate_uuid() result = await async_directus.create_item("project", { diff --git a/echo/server/dembrane/tier_capacity.py b/echo/server/dembrane/tier_capacity.py index bad9797d..0c67e116 100644 --- a/echo/server/dembrane/tier_capacity.py +++ b/echo/server/dembrane/tier_capacity.py @@ -148,3 +148,26 @@ def compute_seat_overage_eur(tier: str, seats_used: int) -> float: return 0.0 over = max(0, seats_used - cap.included_seats) return round(over * cap.seat_overage_eur, 2) + + +def is_hard_blocked(tier: str, hours_used: float) -> bool: + """Is this workspace hitting a hard block right now? + + Matrix §8: Pilot is the only hard-block tier. Pioneer+ bill overage + and keep going; Guardian is unlimited. The hard block applies to + **host-side operations only** — participant recording + upload + + transcription are always allowed. + + Returns False when: + - Tier is unknown (legacy NULL row) — treat as unlimited / safe. + - Tier is not Pilot — no hard-block tier. + - Tier is Pilot but hours_used < included_hours. + """ + cap = get_capacity(tier) + if cap is None: + return False + if not cap.hard_block_on_hours: + return False + if cap.included_hours is None: + return False + return hours_used >= cap.included_hours From b49c30d2215d6ff4f07b3d9327ef2bd4c0be7006 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:23:01 +0000 Subject: [PATCH 105/208] =?UTF-8?q?S3a:=20post-downgrade=20tracking=20+=20?= =?UTF-8?q?email=20to=20admin+billing=20(matrix=20=C2=A73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schema (step 11): - workspace.downgraded_at (timestamp, nullable) - workspace.downgraded_from_tier (string, nullable) Set on downgrade, cleared on next upgrade. Powers the 7-day post-downgrade banner the frontend will render. Audience helper: - notifications.audience_workspace_admins_and_billing(workspace_id) Returns effective admin / owner / billing members. Matrix §3 (downgrade email) + §11 (upgrade-request co-admin notify) both use this audience. set_workspace_tier endpoint: - On downgrade: stamp downgraded_at + downgraded_from_tier. - On upgrade: clear both (the upgrade makes the old downgrade irrelevant). - In-app notification audience broadened from admins-only to admins+billing per matrix §3. - New synchronous email send via send_email + tier_downgraded template. Reads app_user.email for the audience; skips silently on missing addrs. Latency is acceptable — the action is staff-gated so volume is low. Email template (server/email_templates/tier_downgraded.{html,txt}): - Groups effects into Frozen (existing state stays) and Reverted buckets. - Uses the shared _layout.html with Royal Blue accent on workspace name. - Plain-text twin auto-picks per the existing multipart wiring. - Copy: factual, no "successfully/please", Everyman/Explorer tone. WorkspaceSummary serialisation: exposes downgraded_at + downgraded_from_tier so the home page + workspace route can render the 7-day banner. Not in this commit: the 7-day banner UI component + dismiss-with-auto- return behavior. Frontend wiring is a follow-up now that the fields are live. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fields/workspace/downgraded_at.json | 44 ++++++ .../workspace/downgraded_from_tier.json | 44 ++++++ echo/scripts/create_schema.py | 45 ++++++ echo/server/dembrane/api/v2/schemas.py | 6 + echo/server/dembrane/api/v2/workspaces.py | 129 ++++++++++++++++-- echo/server/dembrane/notifications.py | 16 +++ .../email_templates/tier_downgraded.html | 33 +++++ .../email_templates/tier_downgraded.txt | 16 +++ 8 files changed, 325 insertions(+), 8 deletions(-) create mode 100644 echo/directus/sync/snapshot/fields/workspace/downgraded_at.json create mode 100644 echo/directus/sync/snapshot/fields/workspace/downgraded_from_tier.json create mode 100644 echo/server/email_templates/tier_downgraded.html create mode 100644 echo/server/email_templates/tier_downgraded.txt diff --git a/echo/directus/sync/snapshot/fields/workspace/downgraded_at.json b/echo/directus/sync/snapshot/fields/workspace/downgraded_at.json new file mode 100644 index 00000000..c70de23a --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/downgraded_at.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "downgraded_at", + "type": "timestamp", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "downgraded_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Set when a staff tier change lowered this workspace's tier. Frontend renders the post-downgrade banner for 7 days from this timestamp (matrix v1.1 §3).", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 17, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "downgraded_at", + "table": "workspace", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/workspace/downgraded_from_tier.json b/echo/directus/sync/snapshot/fields/workspace/downgraded_from_tier.json new file mode 100644 index 00000000..7901ac05 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/workspace/downgraded_from_tier.json @@ -0,0 +1,44 @@ +{ + "collection": "workspace", + "field": "downgraded_from_tier", + "type": "string", + "meta": { + "collection": "workspace", + "conditions": null, + "display": null, + "display_options": null, + "field": "downgraded_from_tier", + "group": null, + "hidden": false, + "interface": "input", + "note": "The tier the workspace was on BEFORE the downgrade. Used so the banner can say 'downgraded from X to Y' without guessing. Cleared on next upgrade.", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 18, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "downgraded_from_tier", + "table": "workspace", + "data_type": "character varying", + "default_value": null, + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py index a26c41d3..8816c47e 100644 --- a/echo/scripts/create_schema.py +++ b/echo/scripts/create_schema.py @@ -1062,6 +1062,50 @@ def step_10_workspace_visibility(): return True +def step_11_downgrade_tracking(): + """Add workspace.downgraded_at + downgraded_from_tier for the 7-day + post-downgrade banner (matrix v1.1 §3). + + Rules: + - Set both on tier downgrade; clear both on tier upgrade. + - Frontend renders the banner until + downgraded_at + 7 days < now() + OR until the admin dismisses it (dismissal state lives in a + per-user settings key, not on the workspace). + + Idempotent. + """ + print("\n=== Step 11: workspace downgrade tracking ===") + + add_field("workspace", "downgraded_at", { + "type": "timestamp", + "schema": {"is_nullable": True}, + "meta": { + "interface": "datetime", + "note": ( + "Set when a staff tier change lowered this workspace's tier. " + "Frontend renders the post-downgrade banner for 7 days from " + "this timestamp (matrix v1.1 §3)." + ), + }, + }) + + add_field("workspace", "downgraded_from_tier", { + "type": "string", + "schema": {"is_nullable": True}, + "meta": { + "interface": "input", + "note": ( + "The tier the workspace was on BEFORE the downgrade. Used " + "so the banner can say 'downgraded from X to Y' without " + "guessing. Cleared on next upgrade." + ), + }, + }) + + return True + + STEPS = { "1": ("app_user", step_1_app_user), "2": ("org + org_membership", step_2_org), @@ -1073,6 +1117,7 @@ def step_10_workspace_visibility(): "8": ("remove legacy chat", step_8_remove_chat), "9": ("notifications trio (inbox)", step_9_notifications), "10": ("workspace.visibility enum + backfill", step_10_workspace_visibility), + "11": ("workspace downgrade tracking", step_11_downgrade_tracking), } diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py index 172ab238..7b681dac 100644 --- a/echo/server/dembrane/api/v2/schemas.py +++ b/echo/server/dembrane/api/v2/schemas.py @@ -85,6 +85,12 @@ class WorkspaceSummary(BaseModel): is_external: bool members_preview: list[MemberPreview] = [] usage: WorkspaceUsage = WorkspaceUsage() + # Post-downgrade banner state (matrix v1.1 §3). Set on downgrade, + # cleared on next upgrade. Frontend renders the banner for 7 days + # past downgraded_at; auto-returns on dismiss if the admin attempts + # a frozen feature. + downgraded_at: Optional[str] = None + downgraded_from_tier: Optional[str] = None class TeamRollup(BaseModel): diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 36909a12..66d40e9e 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -37,6 +37,83 @@ ) +async def _send_downgrade_emails( + *, + audience_app_user_ids: list[str], + ws_name: str, + workspace_id: str, + from_tier: str, + to_tier: str, + effects: list[dict], + downgraded_at_iso: str, +) -> None: + """Send the matrix §3 post-downgrade email to a list of app_user ids. + + Groups effects into freeze + revert buckets for the template and + resolves email addresses via app_user.email. Silent on missing + addresses; SendGrid misconfig is logged but not raised. + """ + if not audience_app_user_ids: + return + + rows = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"id": {"_in": audience_app_user_ids}}, + "fields": ["email"], + "limit": -1, + } + }, + ) + if not isinstance(rows, list): + return + emails = sorted({ + (r.get("email") or "").strip() + for r in rows + if r.get("email") + }) + if not emails: + return + + freeze_items = [ + e["human"] for e in effects if e.get("effect") == "freeze" and e.get("human") + ] + revert_items = [ + e["human"] for e in effects if e.get("effect") == "revert" and e.get("human") + ] + + # Human-readable stamp. Matches the banner in the UI — "on {date}." + try: + when = datetime.fromisoformat(downgraded_at_iso.replace("Z", "+00:00")) + downgraded_at_human = when.strftime("%d %B %Y") + except Exception: + downgraded_at_human = "today" + + # Base URL for the workspace — matches how invite / added emails link in. + base = (settings.urls.admin_base_url or "").rstrip("/") + workspace_url = f"{base}/w/{workspace_id}" if base else f"/w/{workspace_id}" + + subject = _strip_header_unsafe( + f"{ws_name} moved to {to_tier}" + ) + + await send_email( + to=emails, + subject=subject, + template="tier_downgraded", + template_data={ + "workspace_name": ws_name, + "from_tier": from_tier, + "to_tier": to_tier, + "downgraded_at_human": downgraded_at_human, + "freeze_items": freeze_items, + "revert_items": revert_items, + "workspace_url": workspace_url, + }, + ) + + def _strip_header_unsafe(value: str) -> str: """Remove CR/LF from strings that will appear inside an email Subject. @@ -202,7 +279,10 @@ async def list_workspaces( "id": {"_in": workspace_ids}, "deleted_at": {"_null": True}, }, - "fields": ["id", "name", "org_id", "is_default", "tier"], + "fields": [ + "id", "name", "org_id", "is_default", "tier", + "downgraded_at", "downgraded_from_tier", + ], "limit": -1, } }, @@ -273,6 +353,8 @@ async def _get_workspace_aggregates(ws_id: str) -> tuple[int, int, WorkspaceUsag is_external=membership.get("is_external", False), members_preview=previews, usage=usage, + downgraded_at=ws.get("downgraded_at"), + downgraded_from_tier=ws.get("downgraded_from_tier"), )) # Build team rollups @@ -543,7 +625,20 @@ async def set_workspace_tier( # has_policy would already be denying policies we need to read. effects = await apply_downgrade_effects(workspace_id, from_tier, to_tier) - await async_directus.update_item("workspace", workspace_id, {"tier": to_tier}) + # Tier change + downgrade-banner state. On downgrade we stamp + # downgraded_at + downgraded_from_tier so the frontend renders the + # 7-day banner (matrix v1.1 §3). On upgrade we clear those so the + # banner goes away immediately — an upgrade makes the old downgrade + # irrelevant. No-change: touch nothing. + now_iso = datetime.now(timezone.utc).isoformat() + ws_update: dict = {"tier": to_tier} + if direction == "downgrade": + ws_update["downgraded_at"] = now_iso + ws_update["downgraded_from_tier"] = from_tier + elif direction == "upgrade": + ws_update["downgraded_at"] = None + ws_update["downgraded_from_tier"] = None + await async_directus.update_item("workspace", workspace_id, ws_update) logger.info( f"STAFF tier change: workspace {workspace_id} {from_tier} → {to_tier} " @@ -551,12 +646,15 @@ async def set_workspace_tier( f"effects={[e['policy'] for e in effects]})" ) - # Notify workspace admins/owners so they know about the tier change. - # Staff changes bypass the usual admin flow — without this notification - # admins would see feature gates flip (or logo disappear) with no - # explanation. Skip the (staff) actor since they already know. + # Notify workspace admins/owners + billing so they know about the tier + # change. Staff changes bypass the usual admin flow — without this + # notification admins would see feature gates flip (or logo disappear) + # with no explanation. Matrix v1.1 §3 audience = admin + billing. if direction != "no-change": - from dembrane.notifications import emit_to_audience, audience_workspace_admins + from dembrane.notifications import ( + emit_to_audience, + audience_workspace_admins_and_billing, + ) ws_name = workspace.get("name", "your workspace") if direction == "upgrade": title = f"{ws_name} upgraded to {to_tier}" @@ -569,7 +667,7 @@ async def set_workspace_tier( if effect_list else "Some features are now limited." ) - audience = await audience_workspace_admins(workspace_id) + audience = await audience_workspace_admins_and_billing(workspace_id) await emit_to_audience( audience, event_code=( @@ -581,6 +679,21 @@ async def set_workspace_tier( ref_workspace_id=workspace_id, ) + # Matrix v1.1 §3 requires a post-downgrade email within 1 minute to + # every admin + billing user. We send synchronously inside the + # PATCH — volume is low (staff-only action) and the latency is + # acceptable. Failure to email does not block the tier change. + if direction == "downgrade" and audience: + await _send_downgrade_emails( + audience_app_user_ids=audience, + ws_name=ws_name, + workspace_id=workspace_id, + from_tier=from_tier, + to_tier=to_tier, + effects=effects, + downgraded_at_iso=now_iso, + ) + return SetTierResponse( workspace_id=workspace_id, previous_tier=from_tier, diff --git a/echo/server/dembrane/notifications.py b/echo/server/dembrane/notifications.py index e72cae3e..45bbc16a 100644 --- a/echo/server/dembrane/notifications.py +++ b/echo/server/dembrane/notifications.py @@ -208,6 +208,22 @@ async def audience_workspace_members(workspace_id: str) -> list[str]: return [m["user_id"] for m in members if m.get("user_id")] +async def audience_workspace_admins_and_billing(workspace_id: str) -> list[str]: + """Admin/owner + billing roles on the workspace. + + Matrix v1.1 §3 downgrade audience: every admin + billing-role user on + the workspace. Matrix v1.1 §11 upgrade-request co-admin notify audience. + """ + from dembrane.inheritance import get_effective_members + + members = await get_effective_members(workspace_id) + return [ + m["user_id"] + for m in members + if m.get("user_id") and m.get("role") in ("admin", "owner", "billing") + ] + + async def audience_team_admins(org_id: str) -> list[str]: rows = await async_directus.get_items( "org_membership", diff --git a/echo/server/email_templates/tier_downgraded.html b/echo/server/email_templates/tier_downgraded.html new file mode 100644 index 00000000..13fb72e2 --- /dev/null +++ b/echo/server/email_templates/tier_downgraded.html @@ -0,0 +1,33 @@ +{% extends "_layout.html" %} +{% from "_layout.html" import cta_button %} +{% block title %}{{ workspace_name }} moved to {{ to_tier }}{% endblock %} +{% block preview %}{{ workspace_name }} is now on {{ to_tier }}. Some features are limited.{% endblock %} +{% block heading %}{{ workspace_name }} is on {{ to_tier }}.{% endblock %} +{% block body %} +

    + As of {{ downgraded_at_human }}, {{ workspace_name }} moved from {{ from_tier }} to {{ to_tier }}. +

    + +{% if freeze_items %} +

    + These features are frozen — existing state stays; no new use until upgrade: +

    +
      + {% for item in freeze_items %}
    • {{ item }}
    • {% endfor %} +
    +{% endif %} + +{% if revert_items %} +

    + These features were reverted: +

    +
      + {% for item in revert_items %}
    • {{ item }}
    • {% endfor %} +
    +{% endif %} + +

    + Everything else keeps working as it did. Open the workspace to review your options or request a different tier. +

    +{% endblock %} +{% block cta %}{{ cta_button("Open workspace", workspace_url) }}{% endblock %} diff --git a/echo/server/email_templates/tier_downgraded.txt b/echo/server/email_templates/tier_downgraded.txt new file mode 100644 index 00000000..19962674 --- /dev/null +++ b/echo/server/email_templates/tier_downgraded.txt @@ -0,0 +1,16 @@ +{{ workspace_name }} moved from {{ from_tier }} to {{ to_tier }} on {{ downgraded_at_human }}. + +{% if freeze_items %}Frozen — existing state stays; no new use until upgrade: +{% for item in freeze_items %}- {{ item }} +{% endfor %} +{% endif %} +{% if revert_items %}Reverted: +{% for item in revert_items %}- {{ item }} +{% endfor %} +{% endif %} +Everything else keeps working as it did. + +Open the workspace: +{{ workspace_url }} + +— the dembrane team From c8fae01895c832a8e1e66c3df036a2b7a8da9078 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:27:10 +0000 Subject: [PATCH 106/208] =?UTF-8?q?S6:=20Slack-style=20discovery=20+=20joi?= =?UTF-8?q?n=20+=20access-request=20endpoints=20(matrix=20=C2=A76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schema (step 12): access_request collection. id, workspace_id, user_id, status (pending|approved|rejected), requested_at, actioned_at, actioned_by, deleted_at. Relations: workspace_id CASCADE, user_id CASCADE, actioned_by SET NULL. New module: server/dembrane/api/v2/access_requests.py. Endpoints: - POST /v2/workspaces/:id/join — team admin/owner self-join, immediate direct Admin row. Idempotent (returns 'already_member' if a row exists). 403 for team members. Rate-limited 30/hr. - POST /v2/workspaces/:id/access-requests — team member request to join an open_to_team workspace. Private workspace → 404 (intentional; don't confirm existence to members). Admin calling this → 400 ("join directly, no approval needed"). Pending-duplicate idempotent. - GET /v2/workspaces/:id/access-requests — admin list of pending, with user display + email. - POST /v2/workspaces/:id/access-requests/:req_id/approve — writes direct Member row, marks request approved, notifies requester with MEMBERSHIP_REQUEST_APPROVED. - POST /v2/workspaces/:id/access-requests/:req_id/reject — marks rejected. **Silent per matrix §6** — no notification to requester. - GET /v2/orgs/:id/discoverable-workspaces — org-scoped discovery list. Admins see all (open + private) with action='join'. Members see open only with action='request-access' / 'pending' / 'member'. Notifications: - MEMBERSHIP_REQUESTED: action_required severity. Audience = workspace admins + team admins. - MEMBERSHIP_REQUEST_APPROVED: info. Sent to requester only. - MEMBERSHIP_REQUEST_REJECTED: deliberately NOT in the severity map. Matrix §6 locks silent rejection. - WORKSPACE_JOINED: quiet self-notification on /join for activity feed; not broadcast (team admin joining their team's workspace is a mundane action). Safety: - Approve/reject endpoints use WorkspaceContext + member:manage policy. Today team admins have derived access; post-walkback they'll need to /join first (cheap; one POST). Cross-workspace req IDs 404 on mismatch (existence-hiding). - Request-access existence-hiding for private workspaces. - Rate limits on join + request-access (30/hr each). Additive to current derivation model — no resolver changes in this commit. When the walkback runs --apply + the resolver simplifies, the direct rows these endpoints write become the sole source of truth; no backport needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../snapshot/collections/access_request.json | 28 + .../fields/access_request/actioned_at.json | 44 ++ .../fields/access_request/actioned_by.json | 44 ++ .../fields/access_request/deleted_at.json | 44 ++ .../snapshot/fields/access_request/id.json | 44 ++ .../fields/access_request/requested_at.json | 44 ++ .../fields/access_request/status.json | 59 ++ .../fields/access_request/user_id.json | 44 ++ .../fields/access_request/workspace_id.json | 44 ++ .../relations/access_request/actioned_by.json | 25 + .../relations/access_request/user_id.json | 25 + .../access_request/workspace_id.json | 25 + echo/scripts/create_schema.py | 98 +++ echo/server/dembrane/api/v2/__init__.py | 10 + .../server/dembrane/api/v2/access_requests.py | 644 ++++++++++++++++++ echo/server/dembrane/notifications.py | 6 +- 16 files changed, 1226 insertions(+), 2 deletions(-) create mode 100644 echo/directus/sync/snapshot/collections/access_request.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/actioned_at.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/actioned_by.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/deleted_at.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/id.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/requested_at.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/status.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/user_id.json create mode 100644 echo/directus/sync/snapshot/fields/access_request/workspace_id.json create mode 100644 echo/directus/sync/snapshot/relations/access_request/actioned_by.json create mode 100644 echo/directus/sync/snapshot/relations/access_request/user_id.json create mode 100644 echo/directus/sync/snapshot/relations/access_request/workspace_id.json create mode 100644 echo/server/dembrane/api/v2/access_requests.py diff --git a/echo/directus/sync/snapshot/collections/access_request.json b/echo/directus/sync/snapshot/collections/access_request.json new file mode 100644 index 00000000..5004a2ab --- /dev/null +++ b/echo/directus/sync/snapshot/collections/access_request.json @@ -0,0 +1,28 @@ +{ + "collection": "access_request", + "meta": { + "accountability": "all", + "archive_app_filter": true, + "archive_field": null, + "archive_value": null, + "collapse": "open", + "collection": "access_request", + "color": null, + "display_template": "{{user_id}} → {{workspace_id}} ({{status}})", + "group": null, + "hidden": false, + "icon": "meeting_room", + "item_duplication_fields": null, + "note": "Pending join requests from team members on open-to-team workspaces. Matrix v1.1 §6 Slack-style discovery.", + "preview_url": null, + "singleton": false, + "sort": null, + "sort_field": "requested_at", + "translations": null, + "unarchive_value": null, + "versioning": false + }, + "schema": { + "name": "access_request" + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/actioned_at.json b/echo/directus/sync/snapshot/fields/access_request/actioned_at.json new file mode 100644 index 00000000..0a917d49 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/actioned_at.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "actioned_at", + "type": "timestamp", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "actioned_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 6, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "actioned_at", + "table": "access_request", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/actioned_by.json b/echo/directus/sync/snapshot/fields/access_request/actioned_by.json new file mode 100644 index 00000000..63943e8e --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/actioned_by.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "actioned_by", + "type": "uuid", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "actioned_by", + "group": null, + "hidden": false, + "interface": "input", + "note": "app_user.id of the approver/rejecter", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 7, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "actioned_by", + "table": "access_request", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/deleted_at.json b/echo/directus/sync/snapshot/fields/access_request/deleted_at.json new file mode 100644 index 00000000..78eccd75 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/deleted_at.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "deleted_at", + "type": "timestamp", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "deleted_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": "Soft delete timestamp", + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 8, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "deleted_at", + "table": "access_request", + "data_type": "timestamp with time zone", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/id.json b/echo/directus/sync/snapshot/fields/access_request/id.json new file mode 100644 index 00000000..cc5a6da6 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/id.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "id", + "type": "integer", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "id", + "group": null, + "hidden": true, + "interface": "numeric", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 1, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "id", + "table": "access_request", + "data_type": "integer", + "default_value": "nextval('access_request_id_seq'::regclass)", + "max_length": null, + "numeric_precision": 32, + "numeric_scale": 0, + "is_nullable": false, + "is_unique": true, + "is_indexed": false, + "is_primary_key": true, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": true, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/requested_at.json b/echo/directus/sync/snapshot/fields/access_request/requested_at.json new file mode 100644 index 00000000..29c84399 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/requested_at.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "requested_at", + "type": "timestamp", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "requested_at", + "group": null, + "hidden": false, + "interface": "datetime", + "note": null, + "options": null, + "readonly": true, + "required": false, + "searchable": true, + "sort": 5, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "requested_at", + "table": "access_request", + "data_type": "timestamp with time zone", + "default_value": "CURRENT_TIMESTAMP", + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/status.json b/echo/directus/sync/snapshot/fields/access_request/status.json new file mode 100644 index 00000000..cad3d1e0 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/status.json @@ -0,0 +1,59 @@ +{ + "collection": "access_request", + "field": "status", + "type": "string", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "status", + "group": null, + "hidden": false, + "interface": "select-dropdown", + "note": null, + "options": { + "choices": [ + { + "text": "Pending", + "value": "pending" + }, + { + "text": "Approved", + "value": "approved" + }, + { + "text": "Rejected", + "value": "rejected" + } + ] + }, + "readonly": false, + "required": false, + "searchable": true, + "sort": 4, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "status", + "table": "access_request", + "data_type": "character varying", + "default_value": "pending", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/user_id.json b/echo/directus/sync/snapshot/fields/access_request/user_id.json new file mode 100644 index 00000000..88118094 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/user_id.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "user_id", + "type": "uuid", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "user_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 3, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "user_id", + "table": "access_request", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "app_user", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/fields/access_request/workspace_id.json b/echo/directus/sync/snapshot/fields/access_request/workspace_id.json new file mode 100644 index 00000000..49abfc4e --- /dev/null +++ b/echo/directus/sync/snapshot/fields/access_request/workspace_id.json @@ -0,0 +1,44 @@ +{ + "collection": "access_request", + "field": "workspace_id", + "type": "uuid", + "meta": { + "collection": "access_request", + "conditions": null, + "display": null, + "display_options": null, + "field": "workspace_id", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "searchable": true, + "sort": 2, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + }, + "schema": { + "name": "workspace_id", + "table": "access_request", + "data_type": "uuid", + "default_value": null, + "max_length": null, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": false, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": "workspace", + "foreign_key_column": "id" + } +} diff --git a/echo/directus/sync/snapshot/relations/access_request/actioned_by.json b/echo/directus/sync/snapshot/relations/access_request/actioned_by.json new file mode 100644 index 00000000..a683d350 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/access_request/actioned_by.json @@ -0,0 +1,25 @@ +{ + "collection": "access_request", + "field": "actioned_by", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "access_request", + "many_field": "actioned_by", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "access_request", + "column": "actioned_by", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "access_request_actioned_by_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/access_request/user_id.json b/echo/directus/sync/snapshot/relations/access_request/user_id.json new file mode 100644 index 00000000..bcb1519f --- /dev/null +++ b/echo/directus/sync/snapshot/relations/access_request/user_id.json @@ -0,0 +1,25 @@ +{ + "collection": "access_request", + "field": "user_id", + "related_collection": "app_user", + "meta": { + "junction_field": null, + "many_collection": "access_request", + "many_field": "user_id", + "one_allowed_collections": null, + "one_collection": "app_user", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "access_request", + "column": "user_id", + "foreign_key_table": "app_user", + "foreign_key_column": "id", + "constraint_name": "access_request_user_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/directus/sync/snapshot/relations/access_request/workspace_id.json b/echo/directus/sync/snapshot/relations/access_request/workspace_id.json new file mode 100644 index 00000000..ea208952 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/access_request/workspace_id.json @@ -0,0 +1,25 @@ +{ + "collection": "access_request", + "field": "workspace_id", + "related_collection": "workspace", + "meta": { + "junction_field": null, + "many_collection": "access_request", + "many_field": "workspace_id", + "one_allowed_collections": null, + "one_collection": "workspace", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": null, + "sort_field": null + }, + "schema": { + "table": "access_request", + "column": "workspace_id", + "foreign_key_table": "workspace", + "foreign_key_column": "id", + "constraint_name": "access_request_workspace_id_foreign", + "on_update": "NO ACTION", + "on_delete": "CASCADE" + } +} diff --git a/echo/scripts/create_schema.py b/echo/scripts/create_schema.py index 8816c47e..5a2eedd1 100644 --- a/echo/scripts/create_schema.py +++ b/echo/scripts/create_schema.py @@ -1106,6 +1106,103 @@ def step_11_downgrade_tracking(): return True +def step_12_access_requests(): + """Create the access_request collection for Slack-style discovery + (matrix v1.1 §6). + + Flow: a team member clicks "Request access" on an open workspace → + writes a pending row here → audience (workspace admins + team admins) + is notified → admin approves (writes a workspace_membership direct + row + marks request approved) or rejects (marks request rejected; + no notification to requester per matrix §6 "silent rejection"). + + Fields are deliberately lean: the workshop question about adding a + user-provided "reason" text is deferred — add post-release if abuse + patterns demand it. + + Idempotent. + """ + print("\n=== Step 12: access_request collection ===") + + if not collection_exists("access_request"): + print(" Creating access_request collection...") + api("POST", "/collections", { + "collection": "access_request", + "meta": { + "icon": "meeting_room", + "note": ( + "Pending join requests from team members on open-to-team " + "workspaces. Matrix v1.1 §6 Slack-style discovery." + ), + "display_template": "{{user_id}} → {{workspace_id}} ({{status}})", + "sort_field": "requested_at", + }, + "schema": {}, + }) + print(" OK access_request collection created") + + add_field("access_request", "id", { + "type": "uuid", + "schema": {"is_primary_key": True, "has_auto_increment": False, "is_nullable": False}, + "meta": {"hidden": True, "readonly": True, "interface": "input", "special": ["uuid"]}, + }) + + add_field("access_request", "workspace_id", { + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input"}, + }) + create_relation("access_request", "workspace_id", "workspace", + schema={"on_delete": "CASCADE"}) + + add_field("access_request", "user_id", { + "type": "uuid", + "schema": {"is_nullable": False}, + "meta": {"interface": "input"}, + }) + create_relation("access_request", "user_id", "app_user", + schema={"on_delete": "CASCADE"}) + + add_field("access_request", "status", { + "type": "string", + "schema": {"is_nullable": False, "default_value": "pending"}, + "meta": { + "interface": "select-dropdown", + "options": {"choices": [ + {"text": "Pending", "value": "pending"}, + {"text": "Approved", "value": "approved"}, + {"text": "Rejected", "value": "rejected"}, + ]}, + }, + }) + + add_field("access_request", "requested_at", { + "type": "timestamp", + "schema": {"is_nullable": False, "default_value": "now()"}, + "meta": {"interface": "datetime", "readonly": True}, + }) + + add_field("access_request", "actioned_at", { + "type": "timestamp", + "schema": {"is_nullable": True}, + "meta": {"interface": "datetime"}, + }) + + add_field("access_request", "actioned_by", { + "type": "uuid", + "schema": {"is_nullable": True}, + "meta": {"interface": "input", "note": "app_user.id of the approver/rejecter"}, + }) + create_relation("access_request", "actioned_by", "app_user", + schema={"on_delete": "SET NULL"}) + + add_field("access_request", "deleted_at", { + **deleted_at_field(), + }) + + return True + + STEPS = { "1": ("app_user", step_1_app_user), "2": ("org + org_membership", step_2_org), @@ -1118,6 +1215,7 @@ def step_11_downgrade_tracking(): "9": ("notifications trio (inbox)", step_9_notifications), "10": ("workspace.visibility enum + backfill", step_10_workspace_visibility), "11": ("workspace downgrade tracking", step_11_downgrade_tracking), + "12": ("access_request collection", step_12_access_requests), } diff --git a/echo/server/dembrane/api/v2/__init__.py b/echo/server/dembrane/api/v2/__init__.py index 865fc8ca..bccf04a9 100644 --- a/echo/server/dembrane/api/v2/__init__.py +++ b/echo/server/dembrane/api/v2/__init__.py @@ -19,6 +19,10 @@ from dembrane.api.v2.workspaces import router as workspaces_router from dembrane.api.v2.workspace_projects import router as workspace_projects_router from dembrane.api.v2.workspace_settings import router as workspace_settings_router +from dembrane.api.v2.access_requests import ( + router as access_requests_router, + discover_router as access_requests_discover_router, +) v2_router = APIRouter() @@ -30,12 +34,18 @@ # Team (org) management — user-facing word is "team", internal is "org" (see D1). v2_router.include_router(orgs_router, prefix="/orgs", tags=["v2:orgs"]) +v2_router.include_router( + access_requests_discover_router, prefix="/orgs", tags=["v2:discover"] +) # Workspace-scoped: /workspaces, /workspaces/{id}/invite, /workspaces/{id}/projects v2_router.include_router(workspaces_router, prefix="/workspaces", tags=["v2:workspaces"]) v2_router.include_router(invites_router, prefix="/workspaces", tags=["v2:invites"]) v2_router.include_router(workspace_projects_router, prefix="/workspaces", tags=["v2:workspace-projects"]) v2_router.include_router(workspace_settings_router, prefix="/workspaces", tags=["v2:workspace-settings"]) +v2_router.include_router( + access_requests_router, prefix="/workspaces", tags=["v2:access-requests"] +) # Project-level: /projects/{id}/move + /projects/{id}/members (private sharing) v2_router.include_router(projects_router, prefix="/projects", tags=["v2:projects"]) diff --git a/echo/server/dembrane/api/v2/access_requests.py b/echo/server/dembrane/api/v2/access_requests.py new file mode 100644 index 00000000..033593be --- /dev/null +++ b/echo/server/dembrane/api/v2/access_requests.py @@ -0,0 +1,644 @@ +"""Slack-style discovery endpoints (matrix v1.1 §6). + +Two paths into a workspace the user doesn't currently belong to: + + 1. Team admin → "Join" → immediate `source='direct', role='admin'` row. + No approval. Workspace visibility (open or private) doesn't matter; + team admins can join either. + + 2. Team member → "Request access" → pending access_request row → + workspace admin OR team admin approves → `source='direct', + role='member'` row. Rejection is silent (matrix §6 — no notification + to the requester). + +Both endpoints are workspace-scoped at the URL but use a thinner guard +than the normal WorkspaceContext: the caller does NOT have workspace +access yet. We authenticate their org membership instead. +""" + +from __future__ import annotations + +from datetime import datetime, timezone +from logging import getLogger +from typing import Literal, Optional + +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel, Field + +from dembrane.app_user import get_app_user_or_raise +from dembrane.directus_async import async_directus +from dembrane.api.rate_limit import create_user_rate_limiter +from dembrane.api.dependency_auth import DependencyDirectusSession +from dembrane.api.v2.middleware import WorkspaceContext, get_workspace_context +from dembrane.utils import generate_uuid + +router = APIRouter() +logger = getLogger("api.v2.access_requests") + +# Modest rate limit on both join + request-access. Prevents a team admin +# bot from flooding workspace_membership; prevents a curious team member +# from spamming requests across every workspace in a large team. +_join_rate_limiter = create_user_rate_limiter( + name="workspace_join", capacity=30, window_seconds=3600 +) +_request_access_rate_limiter = create_user_rate_limiter( + name="workspace_request_access", capacity=30, window_seconds=3600 +) + + +# ── Helpers ──────────────────────────────────────────────────────────── + + +async def _org_role(org_id: str, user_id: str) -> Optional[str]: + rows = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "user_id": {"_eq": user_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["role"], + "limit": 1, + } + }, + ) + if isinstance(rows, list) and rows: + return rows[0].get("role") + return None + + +async def _has_direct_row(workspace_id: str, user_id: str) -> bool: + rows = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "user_id": {"_eq": user_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + return isinstance(rows, list) and bool(rows) + + +async def _pending_request( + workspace_id: str, user_id: str +) -> Optional[dict]: + rows = await async_directus.get_items( + "access_request", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "user_id": {"_eq": user_id}, + "status": {"_eq": "pending"}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": 1, + } + }, + ) + if isinstance(rows, list) and rows: + return rows[0] + return None + + +async def _load_workspace_or_404(workspace_id: str) -> dict: + ws = await async_directus.get_item("workspace", workspace_id) + if not ws or ws.get("deleted_at"): + raise HTTPException(status_code=404, detail="Workspace not found") + return ws + + +# ── Join (team admin, immediate) ─────────────────────────────────────── + + +class JoinResponse(BaseModel): + status: Literal["joined", "already_member"] + workspace_id: str + role: str + + +@router.post("/{workspace_id}/join", response_model=JoinResponse) +async def join_workspace( + workspace_id: str, + auth: DependencyDirectusSession, +) -> JoinResponse: + """Team admin (or owner) self-joins a workspace in their team. + + Matrix v1.1 §6: team admins can discover and join any workspace in + the team, open or private. The action is explicit and reversible. + Writes a direct Admin row. + + 403 if the caller is not a team admin/owner on the workspace's org. + 404 if the workspace doesn't exist or is soft-deleted. + 200 + status='already_member' if a direct row already exists — + idempotent, not an error. + """ + app_user = await get_app_user_or_raise(auth.user_id) + app_user_id = app_user["id"] + + await _join_rate_limiter.check(app_user_id) + + workspace = await _load_workspace_or_404(workspace_id) + org_id = workspace.get("org_id") + if not org_id: + raise HTTPException(status_code=500, detail="Workspace has no org") + + role = await _org_role(org_id, app_user_id) + if role not in ("admin", "owner"): + raise HTTPException(status_code=403, detail="Team admins only") + + # Check existing direct row separately from the insert so the "already + # joined" response is precise rather than a unique-constraint 500. + if await _has_direct_row(workspace_id, app_user_id): + return JoinResponse( + status="already_member", + workspace_id=workspace_id, + role="admin", + ) + + await async_directus.create_item( + "workspace_membership", + { + "id": generate_uuid(), + "workspace_id": workspace_id, + "user_id": app_user_id, + "role": "admin", + "source": "direct", + "is_external": False, + }, + ) + + logger.info( + "workspace_join workspace=%s user=%s (team role=%s)", + workspace_id, app_user_id, role, + ) + + # Quiet self-notification — "You joined {ws}" — for the activity feed. + # No broadcast to co-admins: team admin joining their own team's + # workspace isn't news (matrix §6 treats it as a mundane action). + from dembrane.notifications import emit + await emit( + audience_user_id=app_user_id, + actor_user_id=app_user_id, + event_code="WORKSPACE_JOINED", + title=f"You joined {workspace.get('name') or 'a workspace'}", + message="You're in as an admin.", + action="NAVIGATE_WS", + ref_workspace_id=workspace_id, + ref_org_id=org_id, + ) + + return JoinResponse( + status="joined", + workspace_id=workspace_id, + role="admin", + ) + + +# ── Request access (team member, goes pending) ───────────────────────── + + +class RequestAccessResponse(BaseModel): + status: Literal["submitted", "already_pending", "already_member"] + request_id: Optional[str] = None + + +@router.post( + "/{workspace_id}/access-requests", + response_model=RequestAccessResponse, +) +async def request_workspace_access( + workspace_id: str, + auth: DependencyDirectusSession, +) -> RequestAccessResponse: + """Team member requests to join an open-to-team workspace. + + Matrix v1.1 §6: + - Allowed only when workspace.visibility = 'open_to_team'. + - Allowed only for team members (org role 'member'). Admins/owners + use /join directly (they don't need approval). + - If the caller is already a direct member → 200 already_member. + - If a pending request already exists → 200 already_pending. + - Notifies workspace admins + team admins (audience_action_required). + """ + app_user = await get_app_user_or_raise(auth.user_id) + app_user_id = app_user["id"] + + await _request_access_rate_limiter.check(app_user_id) + + workspace = await _load_workspace_or_404(workspace_id) + org_id = workspace.get("org_id") + if not org_id: + raise HTTPException(status_code=500, detail="Workspace has no org") + + # Visibility gate. Private workspaces are invisible to team members in + # discovery; no request path exists for them. + if workspace.get("visibility") == "private": + raise HTTPException( + status_code=404, + detail="Workspace not found", # intentional — don't confirm existence + ) + + org_role = await _org_role(org_id, app_user_id) + if org_role is None: + raise HTTPException( + status_code=403, detail="Not a member of this team" + ) + if org_role in ("admin", "owner"): + raise HTTPException( + status_code=400, + detail="Team admins can join directly — no approval needed", + ) + + if await _has_direct_row(workspace_id, app_user_id): + return RequestAccessResponse(status="already_member") + + existing = await _pending_request(workspace_id, app_user_id) + if existing: + return RequestAccessResponse( + status="already_pending", request_id=existing["id"] + ) + + req_id = generate_uuid() + await async_directus.create_item( + "access_request", + { + "id": req_id, + "workspace_id": workspace_id, + "user_id": app_user_id, + "status": "pending", + }, + ) + + # Audience: workspace admins + team admins. Either can approve. + from dembrane.notifications import ( + emit_to_audience, + audience_workspace_admins, + audience_team_admins, + ) + ws_admins = await audience_workspace_admins(workspace_id) + team_admins = await audience_team_admins(org_id) + audience = sorted(set(ws_admins) | set(team_admins)) + requester_name = ( + app_user.get("display_name") or app_user.get("email") or "Someone" + ) + ws_name = workspace.get("name") or "a workspace" + await emit_to_audience( + audience, + actor_user_id=app_user_id, + event_code="MEMBERSHIP_REQUESTED", + title=f"{requester_name} wants to join {ws_name}", + message=( + f"{requester_name} requested access. Approve from the " + f"workspace members tab." + ), + action="NAVIGATE_WORKSPACE_SETTINGS", + ref_workspace_id=workspace_id, + ref_org_id=org_id, + ) + + logger.info( + "access_request_submitted workspace=%s user=%s id=%s", + workspace_id, app_user_id, req_id, + ) + + return RequestAccessResponse(status="submitted", request_id=req_id) + + +# ── Admin: list + approve + reject ───────────────────────────────────── + + +class AccessRequestRow(BaseModel): + id: str + workspace_id: str + user_id: str + user_display_name: Optional[str] = None + user_email: Optional[str] = None + status: str + requested_at: str + + +class ListAccessRequestsResponse(BaseModel): + requests: list[AccessRequestRow] + + +@router.get( + "/{workspace_id}/access-requests", + response_model=ListAccessRequestsResponse, +) +async def list_access_requests( + workspace_id: str, + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> ListAccessRequestsResponse: + """Pending access requests on this workspace. Workspace admin or team + admin gate via member:manage. Team admins already have member:manage + via the derivation today + (post-walkback) via their direct row.""" + ctx.require_policy("member:manage") + + rows = await async_directus.get_items( + "access_request", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "status": {"_eq": "pending"}, + "deleted_at": {"_null": True}, + }, + "fields": ["id", "workspace_id", "user_id", "status", "requested_at"], + "sort": ["-requested_at"], + "limit": -1, + } + }, + ) + if not isinstance(rows, list): + rows = [] + + # Resolve user display info for the list view. + uids = sorted({r["user_id"] for r in rows if r.get("user_id")}) + users: dict[str, dict] = {} + if uids: + urows = await async_directus.get_items( + "app_user", + { + "query": { + "filter": {"id": {"_in": uids}}, + "fields": ["id", "display_name", "email"], + "limit": -1, + } + }, + ) + if isinstance(urows, list): + users = {u["id"]: u for u in urows} + + out: list[AccessRequestRow] = [] + for r in rows: + u = users.get(r.get("user_id") or "", {}) + out.append( + AccessRequestRow( + id=r["id"], + workspace_id=r["workspace_id"], + user_id=r["user_id"], + user_display_name=u.get("display_name"), + user_email=u.get("email"), + status=r.get("status", "pending"), + requested_at=r.get("requested_at") or "", + ) + ) + return ListAccessRequestsResponse(requests=out) + + +class ActionRequestResponse(BaseModel): + status: Literal["approved", "rejected"] + + +async def _load_pending_or_404(workspace_id: str, req_id: str) -> dict: + req = await async_directus.get_item("access_request", req_id) + if not req or req.get("deleted_at"): + raise HTTPException(status_code=404, detail="Request not found") + if req.get("workspace_id") != workspace_id: + # URL says one workspace, record says another — treat as 404 so + # we don't confirm existence of cross-workspace requests. + raise HTTPException(status_code=404, detail="Request not found") + if req.get("status") != "pending": + raise HTTPException(status_code=409, detail="Request already actioned") + return req + + +@router.post( + "/{workspace_id}/access-requests/{req_id}/approve", + response_model=ActionRequestResponse, +) +async def approve_access_request( + workspace_id: str, + req_id: str, + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> ActionRequestResponse: + """Approve: write a direct Member row + mark request approved + notify + the requester.""" + ctx.require_policy("member:manage") + + req = await _load_pending_or_404(workspace_id, req_id) + requester_id = req["user_id"] + + # If a direct row has appeared since the request was filed (e.g. admin + # manually invited them), just close the request approved without + # duplicating the membership. + if not await _has_direct_row(workspace_id, requester_id): + await async_directus.create_item( + "workspace_membership", + { + "id": generate_uuid(), + "workspace_id": workspace_id, + "user_id": requester_id, + "role": "member", + "source": "direct", + "is_external": False, + }, + ) + + await async_directus.update_item( + "access_request", + req_id, + { + "status": "approved", + "actioned_at": datetime.now(timezone.utc).isoformat(), + "actioned_by": ctx.app_user_id, + }, + ) + + from dembrane.notifications import emit + ws_name = ctx.workspace.get("name") or "a workspace" + await emit( + audience_user_id=requester_id, + actor_user_id=ctx.app_user_id, + event_code="MEMBERSHIP_REQUEST_APPROVED", + title=f"You're in {ws_name}", + message=f"Your request to join {ws_name} was approved.", + action="NAVIGATE_WS", + ref_workspace_id=workspace_id, + ref_org_id=ctx.workspace.get("org_id"), + ) + + logger.info( + "access_request_approved workspace=%s req=%s requester=%s by=%s", + workspace_id, req_id, requester_id, ctx.app_user_id, + ) + return ActionRequestResponse(status="approved") + + +@router.post( + "/{workspace_id}/access-requests/{req_id}/reject", + response_model=ActionRequestResponse, +) +async def reject_access_request( + workspace_id: str, + req_id: str, + ctx: WorkspaceContext = Depends(get_workspace_context), +) -> ActionRequestResponse: + """Reject a pending request. **Silent per matrix §6** — the requester + receives no notification. They learn of it only by noticing nothing + happened.""" + ctx.require_policy("member:manage") + + req = await _load_pending_or_404(workspace_id, req_id) + + await async_directus.update_item( + "access_request", + req_id, + { + "status": "rejected", + "actioned_at": datetime.now(timezone.utc).isoformat(), + "actioned_by": ctx.app_user_id, + }, + ) + + logger.info( + "access_request_rejected workspace=%s req=%s by=%s", + workspace_id, req_id, ctx.app_user_id, + ) + return ActionRequestResponse(status="rejected") + + +# ── Discovery (team member sees open workspaces; admin sees all) ─────── + + +class DiscoverableWorkspace(BaseModel): + id: str + name: str + visibility: str + action: Literal["join", "request-access", "pending", "member"] + pending_request_id: Optional[str] = None + + +class DiscoverResponse(BaseModel): + workspaces: list[DiscoverableWorkspace] + + +discover_router = APIRouter() + + +@discover_router.get( + "/{org_id}/discoverable-workspaces", + response_model=DiscoverResponse, +) +async def list_discoverable_workspaces( + org_id: str, + auth: DependencyDirectusSession, +) -> DiscoverResponse: + """Workspaces in this team that the caller could join or request. + + - Team admin / owner: sees every workspace (open + private). Action + is 'join' (unless already a direct member). + - Team member: sees only open_to_team workspaces. Action is + 'request-access' (unless already a member, or a pending request + exists). + - Not a team member: 403. + """ + app_user = await get_app_user_or_raise(auth.user_id) + app_user_id = app_user["id"] + + org_role = await _org_role(org_id, app_user_id) + if org_role is None: + raise HTTPException(status_code=403, detail="Not a member of this team") + + can_see_private = org_role in ("admin", "owner") + + filters: dict = { + "org_id": {"_eq": org_id}, + "deleted_at": {"_null": True}, + } + if not can_see_private: + filters["visibility"] = {"_eq": "open_to_team"} + + workspaces = await async_directus.get_items( + "workspace", + { + "query": { + "filter": filters, + "fields": ["id", "name", "visibility"], + "sort": ["name"], + "limit": -1, + } + }, + ) + if not isinstance(workspaces, list): + workspaces = [] + + # Caller's existing direct rows in this org's workspaces. + ws_ids = [w["id"] for w in workspaces if w.get("id")] + existing_direct: set[str] = set() + if ws_ids: + mems = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_in": ws_ids}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["workspace_id"], + "limit": -1, + } + }, + ) + if isinstance(mems, list): + existing_direct = {m["workspace_id"] for m in mems} + + # Caller's pending access requests (relevant for members). + pending_map: dict[str, str] = {} + if not can_see_private and ws_ids: + reqs = await async_directus.get_items( + "access_request", + { + "query": { + "filter": { + "workspace_id": {"_in": ws_ids}, + "user_id": {"_eq": app_user_id}, + "status": {"_eq": "pending"}, + "deleted_at": {"_null": True}, + }, + "fields": ["id", "workspace_id"], + "limit": -1, + } + }, + ) + if isinstance(reqs, list): + pending_map = {r["workspace_id"]: r["id"] for r in reqs} + + out: list[DiscoverableWorkspace] = [] + for w in workspaces: + wid = w["id"] + visibility = w.get("visibility") or "open_to_team" + if wid in existing_direct: + action: Literal["join", "request-access", "pending", "member"] = "member" + pending_id = None + elif can_see_private: + action = "join" + pending_id = None + elif wid in pending_map: + action = "pending" + pending_id = pending_map[wid] + else: + action = "request-access" + pending_id = None + out.append( + DiscoverableWorkspace( + id=wid, + name=w.get("name") or "", + visibility=visibility, + action=action, + pending_request_id=pending_id, + ) + ) + + return DiscoverResponse(workspaces=out) diff --git a/echo/server/dembrane/notifications.py b/echo/server/dembrane/notifications.py index 45bbc16a..22acdf21 100644 --- a/echo/server/dembrane/notifications.py +++ b/echo/server/dembrane/notifications.py @@ -57,8 +57,10 @@ "TIER_DOWNGRADED": "destructive", "INVITE_CANCELLED": "destructive", "REPORT_FAILED": "destructive", - # Requires user action. None wired today — MEMBERSHIP_REQUEST etc. - # will slot in here when we ship those flows. + # Requires user action. + "MEMBERSHIP_REQUESTED": "action_required", + # NB: MEMBERSHIP_REQUEST_REJECTED is deliberately absent — matrix §6 + # specifies silent rejection, and emit() is never called for that code. } From 385bf8fdcfed8195aff7938c051a2da06531c1a3 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:28:36 +0000 Subject: [PATCH 107/208] =?UTF-8?q?S14b:=20Pilot=20hard-block=20on=20v1=20?= =?UTF-8?q?chat=20+=20agentic=20endpoints=20(matrix=20=C2=A78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends §8 coverage beyond v2 project creation. Same 402 + copy. New helpers in api/v2/middleware.py: - check_no_pilot_block_for(workspace_id, tier=None) — workspace_id-only variant for v1 routes that don't build a WorkspaceContext. - check_no_pilot_block_for_project(project_id) — resolves workspace via project.workspace_id + delegates. Safe no-op for legacy pre-workspace projects (workspace_id is NULL). Wired: - POST /api/chat/{chat_id} (post_chat) — blocks new chat turn. - POST /api/agentic/runs (create_run) — blocks new agentic analysis. - POST /api/agentic/runs/{id}/messages (append_message) — blocks continuing an agentic run. Still pending §8 coverage in a follow-up (out of scope for this commit): - Report generation trigger (api/project.py report actions + tasks.py). - Data export endpoints. - Transcript view reads (lower urgency — read-only). Participant portal (api/participant.py, recording, upload, transcription) is deliberately untouched. Matrix §8 is explicit: "Recording keeps working — your participants are unaffected." Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/agentic.py | 10 +++++++ echo/server/dembrane/api/chat.py | 4 +++ echo/server/dembrane/api/v2/middleware.py | 36 +++++++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/echo/server/dembrane/api/agentic.py b/echo/server/dembrane/api/agentic.py index df5d7e65..1d6bbb71 100644 --- a/echo/server/dembrane/api/agentic.py +++ b/echo/server/dembrane/api/agentic.py @@ -511,6 +511,10 @@ async def create_run( _assert_project_authorized(project, auth) + # Matrix §8: agentic analysis is a host-side operation → Pilot hard-block. + from dembrane.api.v2.middleware import check_no_pilot_block_for_project + await check_no_pilot_block_for_project(body.project_id) + run = await run_in_thread_pool( agentic_run_service.create_run, project_id=body.project_id, @@ -553,6 +557,12 @@ async def append_message( if run.get("status") in {"queued", "running"}: raise HTTPException(status_code=409, detail="Run already in progress") + # Matrix §8: continuing an agentic analysis is a host-side operation. + project_id = run.get("project_id") + if project_id: + from dembrane.api.v2.middleware import check_no_pilot_block_for_project + await check_no_pilot_block_for_project(str(project_id)) + await run_in_thread_pool( agentic_run_service.append_event, run_id, diff --git a/echo/server/dembrane/api/chat.py b/echo/server/dembrane/api/chat.py index 44920adf..73e11917 100644 --- a/echo/server/dembrane/api/chat.py +++ b/echo/server/dembrane/api/chat.py @@ -938,6 +938,10 @@ async def post_chat( if not project_id: raise HTTPException(status_code=500, detail="Chat is missing a project reference") + # Matrix §8: chat is a host-side operation → Pilot hard-block. + from dembrane.api.v2.middleware import check_no_pilot_block_for_project + await check_no_pilot_block_for_project(str(project_id)) + user_message_content = body.messages[-1].content user_message_id = generate_uuid() diff --git a/echo/server/dembrane/api/v2/middleware.py b/echo/server/dembrane/api/v2/middleware.py index e074f188..6d623a54 100644 --- a/echo/server/dembrane/api/v2/middleware.py +++ b/echo/server/dembrane/api/v2/middleware.py @@ -217,17 +217,35 @@ async def require_no_pilot_block( verbatim per matrix — the UI's level-3 modal (screens/status-banner) lifts this text for the hard-block screen. """ + await check_no_pilot_block_for( + ctx.workspace_id, tier=ctx.workspace.get("tier") + ) + + +async def check_no_pilot_block_for( + workspace_id: str, tier: str | None = None +) -> None: + """workspace_id-only variant of require_no_pilot_block, for v1 routes + that don't build a WorkspaceContext. Accepts an optional tier arg to + skip the workspace read when the caller already has it. + + Same 402 + copy as require_no_pilot_block. See that function's docstring. + """ from dembrane.tier_capacity import is_hard_blocked - tier = ctx.workspace.get("tier") + if tier is None: + ws = await async_directus.get_item("workspace", workspace_id) + if not ws or ws.get("deleted_at"): + return # workspace gone → caller will 404 on its own path + tier = ws.get("tier") + if not tier: return # legacy NULL tier — treat as unlimited (not Pilot) - # Only Pilot hard-blocks. Short-circuit before reaching the DB. if tier != "pilot": return - hours = await _current_cycle_hours(ctx.workspace_id) + hours = await _current_cycle_hours(workspace_id) if is_hard_blocked(tier, hours): raise HTTPException( status_code=402, @@ -237,3 +255,15 @@ async def require_no_pilot_block( "Upgrade to continue." ), ) + + +async def check_no_pilot_block_for_project(project_id: str) -> None: + """Look up the project's workspace and gate on the Pilot block. Safe + no-op when the project has no workspace (legacy pre-workspace data).""" + project = await async_directus.get_item("project", project_id) + if not project or project.get("deleted_at"): + return + workspace_id = project.get("workspace_id") + if not workspace_id: + return # legacy pre-workspace project — never gated + await check_no_pilot_block_for(workspace_id) From 1feb9634c7bc029f67e43b3bf86bddcd3a12ebb8 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:29:40 +0000 Subject: [PATCH 108/208] =?UTF-8?q?matrix=20=C2=A76:=20set=20workspace.vis?= =?UTF-8?q?ibility=20on=20create=20+=20honesty=20disclosure=20on=20Private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - POST /v2/workspaces now writes workspace.visibility alongside the existing settings.inherit_team_admins/inherit_team_members JSON flags. visibility derives from inherit_team_admins: true → 'open_to_team', false → 'private'. The enum becomes the sole source of truth once the derivation walkback runs in prod and the settings flags are purged. Frontend: - CreateWorkspaceRoute shows the matrix §6 honesty disclosure when the creator picks Private: "Team admins can still discover and join this workspace." Shown immediately below the Access select, dimmed, Trans- wrapped for locale coverage. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/routes/workspaces/CreateWorkspaceRoute.tsx | 11 +++++++++++ echo/server/dembrane/api/v2/workspaces.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx index ad55b5a6..76e74922 100644 --- a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx +++ b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx @@ -227,6 +227,17 @@ export const CreateWorkspaceRoute = () => { size="sm" /> + {/* Matrix §6 honesty disclosure. Private protects from team + members, not team admins. Surface it so the creator isn't + surprised later. */} + {privacy === "private" && ( + + + Team admins can still discover and join this workspace. + + + )} + {privacy === "open" && ( Date: Thu, 23 Apr 2026 10:41:32 +0000 Subject: [PATCH 109/208] =?UTF-8?q?matrix=20=C2=A76:=20guard=20+=20legacy-?= =?UTF-8?q?write=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Approve/reject guard widened (answer to question 1): - list/approve/reject access-requests now accept team admins without requiring a workspace_membership row. Avoids the post-walkback footgun where an admin sees MEMBERSHIP_REQUESTED, clicks Approve, and hits 403 because they haven't /joined the workspace yet. - New helper _require_can_action_requests: workspace direct-with- member:manage OR team admin/owner on the workspace's org. Legacy settings writes dropped (answer to question 3): - POST /v2/workspaces no longer writes settings.inherit_team_admins or inherit_team_members. Visibility lives on workspace.visibility enum (matrix §6 is the source of truth for new rows). - on_workspace_created simplified: just inserts the creator direct row. No settings payload. - CreateWorkspaceRequest still accepts inherit_team_members for backward compat but ignores it on write — team members no longer auto-inherit. - inheritance.workspace_follows_team_admins prefers workspace.visibility when present; falls back to the legacy flag for pre-enum rows. Safe across envs until prod backfill + settings purge land. Frontend copy: - CreateWorkspaceRoute removes the "Also give team members access" checkbox (retired flag). Replaces with honesty line: "Team members can find this workspace and request access." Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspaces/CreateWorkspaceRoute.tsx | 27 +--- .../server/dembrane/api/v2/access_requests.py | 126 +++++++++++++++--- echo/server/dembrane/api/v2/schemas.py | 10 +- echo/server/dembrane/api/v2/workspaces.py | 26 ++-- echo/server/dembrane/inheritance.py | 42 +++--- 5 files changed, 155 insertions(+), 76 deletions(-) diff --git a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx index 76e74922..603dfc05 100644 --- a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx +++ b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx @@ -27,7 +27,6 @@ async function createWorkspace(payload: { name: string; org_id?: string; inherit_team_admins?: boolean; - inherit_team_members?: boolean; }) { const res = await fetch(`${API_BASE_URL}/v2/workspaces`, { body: JSON.stringify(payload), @@ -64,7 +63,6 @@ export const CreateWorkspaceRoute = () => { const { data: meV2, isLoading: meLoading } = useV2Me(); const [name, setName] = useState(""); const [privacy, setPrivacy] = useState<"open" | "private">("open"); - const [includeMembers, setIncludeMembers] = useState(false); useDocumentTitle(t`New workspace | dembrane`); @@ -94,7 +92,6 @@ export const CreateWorkspaceRoute = () => { name: name.trim(), org_id: targetTeamId ?? undefined, inherit_team_admins: privacy === "open", - inherit_team_members: privacy === "open" ? includeMembers : false, }), onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["v2", "workspaces"] }); @@ -239,25 +236,11 @@ export const CreateWorkspaceRoute = () => { )} {privacy === "open" && ( - - - setIncludeMembers(e.currentTarget.checked) - } - style={{ marginTop: 2 }} - /> - - Also give team members access (not just admins) - - + + + Team members can find this workspace and request access. + + )} diff --git a/echo/server/dembrane/api/v2/access_requests.py b/echo/server/dembrane/api/v2/access_requests.py index 033593be..ea35c9a2 100644 --- a/echo/server/dembrane/api/v2/access_requests.py +++ b/echo/server/dembrane/api/v2/access_requests.py @@ -29,7 +29,6 @@ from dembrane.directus_async import async_directus from dembrane.api.rate_limit import create_user_rate_limiter from dembrane.api.dependency_auth import DependencyDirectusSession -from dembrane.api.v2.middleware import WorkspaceContext, get_workspace_context from dembrane.utils import generate_uuid router = APIRouter() @@ -117,6 +116,76 @@ async def _load_workspace_or_404(workspace_id: str) -> dict: return ws +async def _require_can_action_requests( + workspace: dict, app_user_id: str +) -> None: + """Guard for approve/reject endpoints. + + Matrix v1.1 §6: either a workspace admin OR a team admin/owner can + action a pending request. We accept either — otherwise, post-walkback, + a team admin receiving MEMBERSHIP_REQUESTED would click Approve and + hit 403 because they don't have a direct workspace row yet. + + Checks (short-circuit on first pass): + 1. Direct workspace_membership with role in (admin, owner) or any + role whose preset grants `member:manage`. + 2. Team admin/owner on the workspace's org. + + Raises 403 otherwise. + """ + from dembrane.policies import has_policy + + workspace_id = workspace["id"] + org_id = workspace.get("org_id") + + # Pass 1: direct workspace membership with member:manage. + direct_rows = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_eq": workspace_id}, + "user_id": {"_eq": app_user_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["role", "custom_policies"], + "limit": 1, + } + }, + ) + if isinstance(direct_rows, list) and direct_rows: + row = direct_rows[0] + if has_policy( + row.get("role") or "", + row.get("custom_policies") or [], + "member:manage", + workspace_tier=workspace.get("tier"), + ): + return + + # Pass 2: team admin or owner in the workspace's org. + if org_id: + team_rows = await async_directus.get_items( + "org_membership", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "user_id": {"_eq": app_user_id}, + "role": {"_in": ["admin", "owner"]}, + "deleted_at": {"_null": True}, + }, + "fields": ["role"], + "limit": 1, + } + }, + ) + if isinstance(team_rows, list) and team_rows: + return + + raise HTTPException(status_code=403, detail="Access denied") + + # ── Join (team admin, immediate) ─────────────────────────────────────── @@ -337,12 +406,18 @@ class ListAccessRequestsResponse(BaseModel): ) async def list_access_requests( workspace_id: str, - ctx: WorkspaceContext = Depends(get_workspace_context), + auth: DependencyDirectusSession, ) -> ListAccessRequestsResponse: - """Pending access requests on this workspace. Workspace admin or team - admin gate via member:manage. Team admins already have member:manage - via the derivation today + (post-walkback) via their direct row.""" - ctx.require_policy("member:manage") + """Pending access requests on this workspace. + + Guard: workspace admin (via direct membership's `member:manage`) OR + team admin/owner on the workspace's org. The latter is what makes + the UX work post-walkback — team admins can approve without first + having to /join the workspace. + """ + app_user = await get_app_user_or_raise(auth.user_id) + workspace = await _load_workspace_or_404(workspace_id) + await _require_can_action_requests(workspace, app_user["id"]) rows = await async_directus.get_items( "access_request", @@ -420,11 +495,18 @@ async def _load_pending_or_404(workspace_id: str, req_id: str) -> dict: async def approve_access_request( workspace_id: str, req_id: str, - ctx: WorkspaceContext = Depends(get_workspace_context), + auth: DependencyDirectusSession, ) -> ActionRequestResponse: """Approve: write a direct Member row + mark request approved + notify - the requester.""" - ctx.require_policy("member:manage") + the requester. + + Guard: workspace admin OR team admin/owner (see + `_require_can_action_requests`). + """ + app_user = await get_app_user_or_raise(auth.user_id) + actor_id = app_user["id"] + workspace = await _load_workspace_or_404(workspace_id) + await _require_can_action_requests(workspace, actor_id) req = await _load_pending_or_404(workspace_id, req_id) requester_id = req["user_id"] @@ -451,26 +533,26 @@ async def approve_access_request( { "status": "approved", "actioned_at": datetime.now(timezone.utc).isoformat(), - "actioned_by": ctx.app_user_id, + "actioned_by": actor_id, }, ) from dembrane.notifications import emit - ws_name = ctx.workspace.get("name") or "a workspace" + ws_name = workspace.get("name") or "a workspace" await emit( audience_user_id=requester_id, - actor_user_id=ctx.app_user_id, + actor_user_id=actor_id, event_code="MEMBERSHIP_REQUEST_APPROVED", title=f"You're in {ws_name}", message=f"Your request to join {ws_name} was approved.", action="NAVIGATE_WS", ref_workspace_id=workspace_id, - ref_org_id=ctx.workspace.get("org_id"), + ref_org_id=workspace.get("org_id"), ) logger.info( "access_request_approved workspace=%s req=%s requester=%s by=%s", - workspace_id, req_id, requester_id, ctx.app_user_id, + workspace_id, req_id, requester_id, actor_id, ) return ActionRequestResponse(status="approved") @@ -482,12 +564,18 @@ async def approve_access_request( async def reject_access_request( workspace_id: str, req_id: str, - ctx: WorkspaceContext = Depends(get_workspace_context), + auth: DependencyDirectusSession, ) -> ActionRequestResponse: """Reject a pending request. **Silent per matrix §6** — the requester receives no notification. They learn of it only by noticing nothing - happened.""" - ctx.require_policy("member:manage") + happened. + + Guard: workspace admin OR team admin/owner. + """ + app_user = await get_app_user_or_raise(auth.user_id) + actor_id = app_user["id"] + workspace = await _load_workspace_or_404(workspace_id) + await _require_can_action_requests(workspace, actor_id) req = await _load_pending_or_404(workspace_id, req_id) @@ -497,13 +585,13 @@ async def reject_access_request( { "status": "rejected", "actioned_at": datetime.now(timezone.utc).isoformat(), - "actioned_by": ctx.app_user_id, + "actioned_by": actor_id, }, ) logger.info( "access_request_rejected workspace=%s req=%s by=%s", - workspace_id, req_id, ctx.app_user_id, + workspace_id, req_id, actor_id, ) return ActionRequestResponse(status="rejected") diff --git a/echo/server/dembrane/api/v2/schemas.py b/echo/server/dembrane/api/v2/schemas.py index 7b681dac..680e1b0e 100644 --- a/echo/server/dembrane/api/v2/schemas.py +++ b/echo/server/dembrane/api/v2/schemas.py @@ -120,10 +120,14 @@ class WorkspaceListResponse(BaseModel): class CreateWorkspaceRequest(BaseModel): name: str = Field(min_length=1, max_length=100) org_id: Optional[str] = None # defaults to user's primary org - # Wizard step 2 access choice: which org roles derive access to this - # workspace. Admins always follow team access (default true). Members - # are opt-in (default false). Both false = private workspace. + # Visibility choice on the create form. Maps to workspace.visibility + # (matrix v1.1 §6). True → 'open_to_team', False → 'private'. + # Private requires innovator+ tier — solo users can still pick it but + # the set_private policy enforces at mutation time. inherit_team_admins: bool = True + # Accepted for backward compatibility; ignored on write. Team members + # no longer auto-inherit access (matrix §6 retires derivation). They + # use Request access. inherit_team_members: bool = False # tier is always "pioneer" on creation — upgrades happen via billing/admin diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 1a004bba..3f136087 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -455,9 +455,9 @@ async def create_workspace( raise HTTPException(status_code=403, detail="Must be team admin or owner to create workspaces") # Tier is always "pioneer" on creation — plan changes happen via admin/billing - # Matrix v1.1 §6 visibility: derive from inherit_team_admins for now - # (the two booleans are the existing API surface; the enum becomes the - # source of truth once the derivation walkback runs in prod). + # Matrix v1.1 §6 visibility: stored on workspace.visibility. The enum + # is the sole source of truth on new workspaces; legacy settings flags + # are no longer written (resolver still reads them for pre-enum rows). visibility = "open_to_team" if body.inherit_team_admins else "private" ws_id = generate_uuid() await async_directus.create_item("workspace", { @@ -470,25 +470,23 @@ async def create_workspace( "created_by": app_user_id, }) - # Seed settings + creator membership via the inheritance module. No - # fan-out of source='inherited' rows — derived model computes team - # admin/member access at query time from org_membership + these flags. + # Insert the creator as source='direct', role='owner'. No settings + # flags (matrix v1.1 §6 — derivation is retired for new rows). from dembrane.inheritance import on_workspace_created await on_workspace_created( workspace_id=ws_id, creator_app_user_id=app_user_id, - inherit_team_admins=body.inherit_team_admins, - inherit_team_members=body.inherit_team_members, ) logger.info( f"Created workspace {ws_id} '{body.name}' in org {org_id} by {app_user_id} " - f"(admins_follow={body.inherit_team_admins}, members_follow={body.inherit_team_members})" + f"(visibility={visibility})" ) # Tell the team's other admins/owners that a new workspace exists. - # Open workspaces grant them access via derivation; they'd otherwise - # find out by refreshing the selector. + # Open workspaces are discoverable via the discovery endpoint so they + # can explicitly join; private workspaces are still discoverable to + # team admins per matrix §6. from dembrane.notifications import emit_to_audience, audience_team_admins creator_row = await async_directus.get_item("app_user", app_user_id) creator_name = (creator_row or {}).get("display_name") or "A team admin" @@ -499,9 +497,9 @@ async def create_workspace( event_code="WORKSPACE_CREATED", title=f"{creator_name} created {body.name.strip()}", message=( - "The new workspace is open to the team — you have access." - if body.inherit_team_admins - else "The new workspace is private — only explicitly invited people have access." + "The new workspace is open to the team — discover it from your team page." + if visibility == "open_to_team" + else "The new workspace is private — only explicitly invited people and team admins have access." ), action="NAVIGATE_WS", ref_workspace_id=ws_id, diff --git a/echo/server/dembrane/inheritance.py b/echo/server/dembrane/inheritance.py index be72735f..cc8005f9 100644 --- a/echo/server/dembrane/inheritance.py +++ b/echo/server/dembrane/inheritance.py @@ -43,15 +43,32 @@ def _settings(workspace: dict) -> dict: def workspace_follows_team_admins(workspace: dict) -> bool: """True if team admins follow this workspace's access (inherit admin role). - Default True — opt-out is the "private workspace" choice in the wizard. + Resolution order (matrix v1.1 §6 transition): + 1. workspace.visibility column, when present: 'open_to_team' → True, + 'private' → False. + 2. Legacy settings.inherit_team_admins flag on pre-enum rows. + 3. Default True (open) — matches matrix §9 default. """ + visibility = workspace.get("visibility") + if visibility == "open_to_team": + return True + if visibility == "private": + return False + # Legacy fallback until the walkback purges settings flags. return _settings(workspace).get("inherit_team_admins", True) def workspace_follows_team_members(workspace: dict) -> bool: """True if team members (org role 'member') also follow team access. - Default False — explicit opt-in via wizard step 2 checkbox. + Matrix v1.1 §6 retires team-member derivation — new workspaces DO NOT + fan members into derived access. They go through "Request access" + (access_request flow) and get a direct Member row on approval. + + This helper keeps the legacy read so the resolver remains correct for + workspaces created before the matrix-§6 model landed (the flag persists + in their settings JSON until the walkback purge). After prod backfill + + settings purge, this helper always returns False. """ return _settings(workspace).get("inherit_team_members", False) @@ -298,25 +315,14 @@ async def get_effective_members(workspace_id: str) -> list[dict]: async def on_workspace_created( workspace_id: str, creator_app_user_id: str, - inherit_team_admins: bool, - inherit_team_members: bool, ) -> None: - """After POST /v2/workspaces creates the workspace row: - - set settings flags, - - insert the creator as source='direct', role='owner'. + """After POST /v2/workspaces creates the workspace row, insert the + creator as source='direct', role='owner'. - No fan-out of inherited rows — derivation handles that at read time. + No settings-flag writes anymore (matrix v1.1 §6). Visibility lives on + workspace.visibility (enum). The inherit_team_members concept retired + — team members request access explicitly via the access_request flow. """ - settings_payload = { - "inherit_team_admins": bool(inherit_team_admins), - "inherit_team_members": bool(inherit_team_members), - "sticky_removed": [], - } - await async_directus.update_item( - "workspace", - workspace_id, - {"settings": settings_payload}, - ) await async_directus.create_item( "workspace_membership", { From c59b681dec58b2f4bf49c3f8ac719b72f21cc751 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:43:12 +0000 Subject: [PATCH 110/208] =?UTF-8?q?matrix=20=C2=A73:=20move=20downgrade=20?= =?UTF-8?q?email=20to=20Dramatiq=20network=20queue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before: send_email was awaited synchronously inside PATCH /v2/workspaces/:id/tier. After: task_send_downgrade_email.send(...) enqueues; the network-queue worker does the SendGrid call + email-address lookup out-of-band. Matrix §3 SLA ("within 1 minute") holds comfortably under normal queue latency. Staff tier-change returns faster; a SendGrid hiccup no longer stretches a staff action. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/workspaces.py | 101 +++------------------- echo/server/dembrane/tasks.py | 92 ++++++++++++++++++++ 2 files changed, 105 insertions(+), 88 deletions(-) diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 3f136087..bee8b182 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -37,83 +37,6 @@ ) -async def _send_downgrade_emails( - *, - audience_app_user_ids: list[str], - ws_name: str, - workspace_id: str, - from_tier: str, - to_tier: str, - effects: list[dict], - downgraded_at_iso: str, -) -> None: - """Send the matrix §3 post-downgrade email to a list of app_user ids. - - Groups effects into freeze + revert buckets for the template and - resolves email addresses via app_user.email. Silent on missing - addresses; SendGrid misconfig is logged but not raised. - """ - if not audience_app_user_ids: - return - - rows = await async_directus.get_items( - "app_user", - { - "query": { - "filter": {"id": {"_in": audience_app_user_ids}}, - "fields": ["email"], - "limit": -1, - } - }, - ) - if not isinstance(rows, list): - return - emails = sorted({ - (r.get("email") or "").strip() - for r in rows - if r.get("email") - }) - if not emails: - return - - freeze_items = [ - e["human"] for e in effects if e.get("effect") == "freeze" and e.get("human") - ] - revert_items = [ - e["human"] for e in effects if e.get("effect") == "revert" and e.get("human") - ] - - # Human-readable stamp. Matches the banner in the UI — "on {date}." - try: - when = datetime.fromisoformat(downgraded_at_iso.replace("Z", "+00:00")) - downgraded_at_human = when.strftime("%d %B %Y") - except Exception: - downgraded_at_human = "today" - - # Base URL for the workspace — matches how invite / added emails link in. - base = (settings.urls.admin_base_url or "").rstrip("/") - workspace_url = f"{base}/w/{workspace_id}" if base else f"/w/{workspace_id}" - - subject = _strip_header_unsafe( - f"{ws_name} moved to {to_tier}" - ) - - await send_email( - to=emails, - subject=subject, - template="tier_downgraded", - template_data={ - "workspace_name": ws_name, - "from_tier": from_tier, - "to_tier": to_tier, - "downgraded_at_human": downgraded_at_human, - "freeze_items": freeze_items, - "revert_items": revert_items, - "workspace_url": workspace_url, - }, - ) - - def _strip_header_unsafe(value: str) -> str: """Remove CR/LF from strings that will appear inside an email Subject. @@ -683,18 +606,20 @@ async def set_workspace_tier( ) # Matrix v1.1 §3 requires a post-downgrade email within 1 minute to - # every admin + billing user. We send synchronously inside the - # PATCH — volume is low (staff-only action) and the latency is - # acceptable. Failure to email does not block the tier change. + # every admin + billing user. Queue via Dramatiq network actor + # (task_send_downgrade_email) so the PATCH returns immediately; + # the worker picks it up on the next cycle. "Within 1 minute" + # holds comfortably under normal queue latency. if direction == "downgrade" and audience: - await _send_downgrade_emails( - audience_app_user_ids=audience, - ws_name=ws_name, - workspace_id=workspace_id, - from_tier=from_tier, - to_tier=to_tier, - effects=effects, - downgraded_at_iso=now_iso, + from dembrane.tasks import task_send_downgrade_email + task_send_downgrade_email.send( + audience, + ws_name, + workspace_id, + from_tier, + to_tier, + effects, + now_iso, ) return SetTierResponse( diff --git a/echo/server/dembrane/tasks.py b/echo/server/dembrane/tasks.py index 7d557f3a..dab39fe8 100644 --- a/echo/server/dembrane/tasks.py +++ b/echo/server/dembrane/tasks.py @@ -1565,3 +1565,95 @@ def task_check_scheduled_reports() -> None: except Exception as e: logger.error(f"Error checking scheduled reports: {e}") raise + + +@dramatiq.actor(queue_name="network", priority=30) +def task_send_downgrade_email( + audience_app_user_ids: list[str], + ws_name: str, + workspace_id: str, + from_tier: str, + to_tier: str, + effects: list[dict], + downgraded_at_iso: str, +) -> None: + """Send the matrix §3 post-downgrade email to a list of app_user ids. + + Runs in the network queue (SendGrid is network-bound). Silent on + missing addresses; SendGrid misconfig is logged but never raises. + """ + from datetime import datetime + + from dembrane.email import send_email_sync + from dembrane.settings import get_settings as _get_settings + + logger = getLogger("dembrane.tasks.task_send_downgrade_email") + + if not audience_app_user_ids: + return + + settings = _get_settings() + + with directus_client_context() as client: + rows = client.get_items( + "app_user", + { + "query": { + "filter": {"id": {"_in": audience_app_user_ids}}, + "fields": ["email"], + "limit": -1, + } + }, + ) or [] + + emails = sorted({ + (r.get("email") or "").strip() + for r in rows + if isinstance(r, dict) and r.get("email") + }) + if not emails: + logger.info( + "downgrade_email_skipped workspace=%s — no recipient addresses", + workspace_id, + ) + return + + freeze_items = [ + e["human"] for e in effects + if isinstance(e, dict) and e.get("effect") == "freeze" and e.get("human") + ] + revert_items = [ + e["human"] for e in effects + if isinstance(e, dict) and e.get("effect") == "revert" and e.get("human") + ] + + try: + when = datetime.fromisoformat(downgraded_at_iso.replace("Z", "+00:00")) + downgraded_at_human = when.strftime("%d %B %Y") + except Exception: + downgraded_at_human = "today" + + base = (settings.urls.admin_base_url or "").rstrip("/") + workspace_url = f"{base}/w/{workspace_id}" if base else f"/w/{workspace_id}" + + subject = f"{ws_name} moved to {to_tier}".replace("\r", " ").replace("\n", " ") + + ok = send_email_sync( + to=emails, + subject=subject, + template="tier_downgraded", + template_data={ + "workspace_name": ws_name, + "from_tier": from_tier, + "to_tier": to_tier, + "downgraded_at_human": downgraded_at_human, + "freeze_items": freeze_items, + "revert_items": revert_items, + "workspace_url": workspace_url, + }, + ) + + logger.info( + "downgrade_email workspace=%s recipients=%d sent=%s", + workspace_id, len(emails), ok, + ) From f90ab7b3e8fc263d11ba123e045c64cd1d9066e9 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:51:48 +0000 Subject: [PATCH 111/208] =?UTF-8?q?frontend:=20Pilot-block=20modal=20+=207?= =?UTF-8?q?-day=20downgrade=20banner=20(matrix=20=C2=A73=20+=20=C2=A78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes the §3 and §8 backend state user-visible without more endpoint gates (per the user's "UI should make it clear" direction). New pilot-block signal bus (lib/pilotBlock.ts): - Copy-substring detection on 402 "Pilot limit reached" pattern — both sides are under our control, locked by matrix §8. - Global QueryClient MutationCache + QueryCache onError handlers fire the signal; no per-caller plumbing needed. - usePilotBlockSignal() hook for the listening component. PilotBlockModal (level-3 canonical per screens/status-banner.md): - Matrix §8 verbatim copy including the participant-reassurance line. - Two actions: "Go to usage" and "Request upgrade". No plain dismiss — a hard block shouldn't be clearable just by clicking away. DowngradeBanner (matrix §3 7-day banner): - Reads workspace.downgraded_at + downgraded_from_tier from useWorkspace. - Renders for 7 days past the stamp; dismissable per-session via sessionStorage keyed on workspace + timestamp. - Golden Pollen background, graphite text, "Learn more" anchors to the billing tab. - Auto-return-on-frozen-feature-attempt per matrix §3 is still TBD — will wire when FeatureGate dispatches a signal from its deny path. Both mount in WorkspaceLayout. WorkspaceSummary interface extended with downgraded_at + downgraded_from_tier (backend already returns them). Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/frontend/src/App.tsx | 20 +++- .../src/components/layout/WorkspaceLayout.tsx | 14 ++- .../components/workspace/DowngradeBanner.tsx | 99 +++++++++++++++++++ .../components/workspace/PilotBlockModal.tsx | 78 +++++++++++++++ echo/frontend/src/hooks/useWorkspace.ts | 3 + echo/frontend/src/lib/pilotBlock.ts | 71 +++++++++++++ 6 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 echo/frontend/src/components/workspace/DowngradeBanner.tsx create mode 100644 echo/frontend/src/components/workspace/PilotBlockModal.tsx create mode 100644 echo/frontend/src/lib/pilotBlock.ts diff --git a/echo/frontend/src/App.tsx b/echo/frontend/src/App.tsx index 1c63133e..2707e912 100644 --- a/echo/frontend/src/App.tsx +++ b/echo/frontend/src/App.tsx @@ -7,7 +7,8 @@ import { MantineProvider } from "@mantine/core"; import "@mantine/core/styles.css"; import { DatesProvider } from "@mantine/dates"; import { ModalsProvider } from "@mantine/modals"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { detectAndEmitPilotBlock } from "./lib/pilotBlock"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { useEffect } from "react"; import { RouterProvider } from "react-router/dom"; @@ -33,7 +34,22 @@ import { analytics } from "./lib/analytics"; import { mainRouter, participantRouter } from "./Router"; import { theme } from "./theme"; -const queryClient = new QueryClient(); +// Pilot hard-block (matrix §8): intercept 402 + copy-locked body from +// host-side mutations and fan out a level-3 modal. Detection is +// copy-substring since we control both the backend body and the frontend +// match — see lib/pilotBlock.ts. +const queryClient = new QueryClient({ + mutationCache: new MutationCache({ + onError: (error) => { + detectAndEmitPilotBlock(error); + }, + }), + queryCache: new QueryCache({ + onError: (error) => { + detectAndEmitPilotBlock(error); + }, + }), +}); const router = USE_PARTICIPANT_ROUTER ? participantRouter : mainRouter; diff --git a/echo/frontend/src/components/layout/WorkspaceLayout.tsx b/echo/frontend/src/components/layout/WorkspaceLayout.tsx index 973392ab..0a0f4adc 100644 --- a/echo/frontend/src/components/layout/WorkspaceLayout.tsx +++ b/echo/frontend/src/components/layout/WorkspaceLayout.tsx @@ -1,11 +1,15 @@ import { useEffect } from "react"; import { Outlet, useParams } from "react-router"; +import { DowngradeBanner } from "@/components/workspace/DowngradeBanner"; +import { PilotBlockModal } from "@/components/workspace/PilotBlockModal"; import { useWorkspace } from "@/hooks/useWorkspace"; /** * Layout wrapper for /w/:workspaceId/* routes. * Reads workspaceId from URL and syncs it to workspace context. - * Renders children via . + * Mounts the 7-day downgrade banner (matrix §3) and the Pilot-block + * modal (matrix §8) so they're available on every workspace-scoped route. + * Renders the route via . */ export const WorkspaceLayout = () => { const { workspaceId: urlWorkspaceId } = useParams<{ workspaceId: string }>(); @@ -18,5 +22,11 @@ export const WorkspaceLayout = () => { } }, [urlWorkspaceId, contextWorkspaceId, setWorkspace]); - return ; + return ( + <> + + + + + ); }; diff --git a/echo/frontend/src/components/workspace/DowngradeBanner.tsx b/echo/frontend/src/components/workspace/DowngradeBanner.tsx new file mode 100644 index 00000000..5c614e43 --- /dev/null +++ b/echo/frontend/src/components/workspace/DowngradeBanner.tsx @@ -0,0 +1,99 @@ +import { Trans } from "@lingui/react/macro"; +import { ActionIcon, Anchor, Group, Paper, Text } from "@mantine/core"; +import { IconX } from "@tabler/icons-react"; +import { useEffect, useState } from "react"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { useWorkspace } from "@/hooks/useWorkspace"; + +/** + * 7-day post-downgrade banner (matrix v1.1 §3). + * + * Reads `workspace.downgraded_at` + `downgraded_from_tier` from the + * workspace summary. Renders for 7 days past the stamp. + * + * Dismissable per-session. Matrix spec says it auto-returns on frozen- + * feature-attempt — that's a follow-up (FeatureGate would need to fire a + * signal here). For this first pass, dismissal is simple session-local. + */ +const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; +const DISMISS_KEY_PREFIX = "dembrane_downgrade_banner_dismissed:"; + +export const DowngradeBanner = () => { + const { workspace } = useWorkspace(); + const navigate = useI18nNavigate(); + const downgradedAt = workspace?.downgraded_at; + const tier = workspace?.tier; + const workspaceId = workspace?.id; + + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + if (!workspaceId || !downgradedAt) return; + const stored = sessionStorage.getItem( + `${DISMISS_KEY_PREFIX}${workspaceId}:${downgradedAt}`, + ); + setDismissed(stored === "1"); + }, [workspaceId, downgradedAt]); + + if (!workspace || !downgradedAt || !tier || !workspaceId) return null; + + const stamp = Date.parse(downgradedAt); + if (Number.isNaN(stamp)) return null; + + const now = Date.now(); + if (now - stamp > SEVEN_DAYS_MS) return null; + if (dismissed) return null; + + const sinceDate = new Date(stamp).toLocaleDateString(undefined, { + day: "numeric", + month: "long", + year: "numeric", + }); + + const handleDismiss = () => { + sessionStorage.setItem( + `${DISMISS_KEY_PREFIX}${workspaceId}:${downgradedAt}`, + "1", + ); + setDismissed(true); + }; + + return ( + + + + + This workspace was downgraded to {tier} on {sinceDate}. Some + features are limited. + {" "} + + navigate(`/w/${workspaceId}/settings?tab=billing`) + } + > + Learn more + + + + + + + + ); +}; diff --git a/echo/frontend/src/components/workspace/PilotBlockModal.tsx b/echo/frontend/src/components/workspace/PilotBlockModal.tsx new file mode 100644 index 00000000..63dbfc8d --- /dev/null +++ b/echo/frontend/src/components/workspace/PilotBlockModal.tsx @@ -0,0 +1,78 @@ +import { Trans } from "@lingui/react/macro"; +import { Button, Group, Modal, Stack, Text, Title } from "@mantine/core"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { useWorkspace } from "@/hooks/useWorkspace"; +import { usePilotBlockSignal } from "@/lib/pilotBlock"; + +/** + * Level-3 status modal for the Pilot hard-block (matrix §8). + * + * Fired via emitPilotBlock() when a mutation 402s with the copy-locked + * body. Copy lifts verbatim from matrix §8 — the participant-reassurance + * line is non-negotiable. + * + * Escape hatches: "Go to usage" (opens the billing tab) and "Request + * upgrade" (jumps straight to the tier compare view). Never "Dismiss" + * alone — a hard block shouldn't be clearable just by clicking away. + */ +export const PilotBlockModal = () => { + const { detail, clear } = usePilotBlockSignal(); + const { workspaceId } = useWorkspace(); + const navigate = useI18nNavigate(); + + const open = detail !== null; + const targetId = detail?.workspaceId || workspaceId; + + return ( + + + + <Trans>Pilot limit reached</Trans> + + + + You've used all 10 hours of the pilot. Host-side tools + (chat, reports, analysis, exports) are paused. + + + + + Recording keeps working — your participants are unaffected. + + + + + + + + + ); +}; diff --git a/echo/frontend/src/hooks/useWorkspace.ts b/echo/frontend/src/hooks/useWorkspace.ts index d0028fbb..54da875b 100644 --- a/echo/frontend/src/hooks/useWorkspace.ts +++ b/echo/frontend/src/hooks/useWorkspace.ts @@ -15,6 +15,9 @@ interface WorkspaceSummary { project_count: number; member_count: number; is_external: boolean; + // Matrix v1.1 §3 downgrade banner fields. + downgraded_at?: string | null; + downgraded_from_tier?: string | null; } export interface WorkspaceContextValue { diff --git a/echo/frontend/src/lib/pilotBlock.ts b/echo/frontend/src/lib/pilotBlock.ts new file mode 100644 index 00000000..8f35cd1d --- /dev/null +++ b/echo/frontend/src/lib/pilotBlock.ts @@ -0,0 +1,71 @@ +/** + * Pilot hard-block signal bus. + * + * Matrix §8 ships a 402 with a copy-locked body when a Pilot workspace + * tries a host-side operation past its 10h cap. We detect that exact + * response server-side and surface a level-3 modal via this bus. + * + * Why a custom event instead of a context? Host-side operations fan out + * across many mutation sites (chat, agentic, report, project create, + * export). A context would require every caller to thread a trigger fn. + * A single window event is trigger-from-anywhere, listen-anywhere. + */ + +import { useEffect, useState } from "react"; + +export interface PilotBlockDetail { + message: string; + workspaceId?: string | null; +} + +const EVENT_NAME = "dembrane:pilot-block"; + +// Shape of the 402 body the backend emits. Stable per matrix §8. +const MATRIX_COPY_MARKER = "Pilot limit reached"; + +export function emitPilotBlock(detail: PilotBlockDetail) { + window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail })); +} + +export function onPilotBlock(handler: (detail: PilotBlockDetail) => void) { + const listener = (e: Event) => { + const ce = e as CustomEvent; + handler(ce.detail); + }; + window.addEventListener(EVENT_NAME, listener); + return () => window.removeEventListener(EVENT_NAME, listener); +} + +/** + * Inspect an Error thrown by our fetch wrappers. If the message matches + * the matrix §8 Pilot-block copy, fire the event. + * + * Wire this in the React Query default error handlers so we catch blocks + * at the mutation level without threading code through every caller. + */ +export function detectAndEmitPilotBlock( + error: unknown, + context?: { workspaceId?: string | null }, +): boolean { + if (!(error instanceof Error)) return false; + if (!error.message.includes(MATRIX_COPY_MARKER)) return false; + emitPilotBlock({ + message: error.message, + workspaceId: context?.workspaceId ?? null, + }); + return true; +} + +/** + * Subscribe to Pilot-block events from a React component. Returns the + * latest detail (or null if none) and a clear() function. Used by the + * PilotBlockModal. + */ +export function usePilotBlockSignal() { + const [detail, setDetail] = useState(null); + useEffect(() => onPilotBlock(setDetail), []); + return { + detail, + clear: () => setDetail(null), + }; +} From f1b7e30977450bc48424dc18395a09921ea230f8 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:54:36 +0000 Subject: [PATCH 112/208] =?UTF-8?q?frontend=20=C2=A76:=20discovery=20secti?= =?UTF-8?q?on=20on=20home=20+=20access-requests=20list=20on=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the Slack-style discovery loop in the UI — endpoints shipped in c8fae01 now have their consumer surfaces. DiscoverableWorkspaces (home page): - Mounted inside each team group, below the workspace cards + dashed add-card. Hides itself when there's nothing joinable (no clutter for small teams). - Pulls /v2/orgs/:id/discoverable-workspaces → renders rows with per-row action: Join (admin) / Request access (member) / Pending (member with live request) / Member (already direct — filtered out client-side to keep the section pure-discovery). - Private workspaces render a lock icon + "Private" label for admins; members don't see them at all (server-filtered). AccessRequestsList (workspace settings): - Between Pending invites and Your access. Hides itself when zero pending. Approve + Decline action icons per row. - Approve invalidates workspaces, settings, and the list itself. - Decline is silent to the requester per matrix §6. Copy follows brand: factual, no "successfully", verbs for actions, emails on hover only, one CTA per row. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspace/AccessRequestsList.tsx | 165 +++++++++++++++++ .../workspace/DiscoverableWorkspaces.tsx | 166 ++++++++++++++++++ .../workspaces/WorkspaceSelectorRoute.tsx | 6 + .../workspaces/WorkspaceSettingsRoute.tsx | 7 + 4 files changed, 344 insertions(+) create mode 100644 echo/frontend/src/components/workspace/AccessRequestsList.tsx create mode 100644 echo/frontend/src/components/workspace/DiscoverableWorkspaces.tsx diff --git a/echo/frontend/src/components/workspace/AccessRequestsList.tsx b/echo/frontend/src/components/workspace/AccessRequestsList.tsx new file mode 100644 index 00000000..0ca5b17d --- /dev/null +++ b/echo/frontend/src/components/workspace/AccessRequestsList.tsx @@ -0,0 +1,165 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + ActionIcon, + Badge, + Divider, + Group, + Paper, + Stack, + Text, + Title, + Tooltip, +} from "@mantine/core"; +import { IconCheck, IconX } from "@tabler/icons-react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { toast } from "@/components/common/Toaster"; +import { API_BASE_URL } from "@/config"; + +interface AccessRequestRow { + id: string; + workspace_id: string; + user_id: string; + user_display_name: string | null; + user_email: string | null; + status: string; + requested_at: string; +} + +async function fetchRequests(workspaceId: string): Promise { + const res = await fetch( + `${API_BASE_URL}/v2/workspaces/${workspaceId}/access-requests`, + { credentials: "include" }, + ); + if (!res.ok) return []; + const data = await res.json(); + return data.requests ?? []; +} + +async function postAction( + workspaceId: string, + reqId: string, + action: "approve" | "reject", +) { + const res = await fetch( + `${API_BASE_URL}/v2/workspaces/${workspaceId}/access-requests/${reqId}/${action}`, + { method: "POST", credentials: "include" }, + ); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.detail || `Couldn't ${action}`); + } + return res.json(); +} + +/** + * Pending access-requests list on the workspace settings page (matrix §6). + * + * Shows team-member requests-to-join that need admin approval. Hides + * itself when there are no pending rows — no empty-state clutter next + * to the members table. Approve writes a direct Member row; Reject is + * silent to the requester. + */ +export const AccessRequestsList = ({ workspaceId }: { workspaceId: string }) => { + const queryClient = useQueryClient(); + + const { data } = useQuery({ + queryKey: ["v2", "access-requests", workspaceId], + queryFn: () => fetchRequests(workspaceId), + staleTime: 15_000, + }); + + const rows = data ?? []; + + const invalidateAll = () => { + queryClient.invalidateQueries({ queryKey: ["v2", "access-requests", workspaceId] }); + queryClient.invalidateQueries({ queryKey: ["v2", "workspace-settings", workspaceId] }); + queryClient.invalidateQueries({ queryKey: ["v2", "workspaces"] }); + }; + + const approveMutation = useMutation({ + mutationFn: (reqId: string) => postAction(workspaceId, reqId, "approve"), + onSuccess: () => { + toast.success(t`Request approved`); + invalidateAll(); + }, + onError: (e: Error) => toast.error(e.message), + }); + + const rejectMutation = useMutation({ + mutationFn: (reqId: string) => postAction(workspaceId, reqId, "reject"), + onSuccess: () => { + toast.success(t`Request declined`); + invalidateAll(); + }, + onError: (e: Error) => toast.error(e.message), + }); + + if (rows.length === 0) return null; + + return ( + <> + + + + <Trans>Access requests</Trans> + + + {rows.map((r) => ( + + + + + {r.user_display_name || r.user_email || t`Team member`} + + {r.user_email && r.user_display_name && ( + + + · + + + )} + + Pending + + + + + approveMutation.mutate(r.id)} + aria-label={t`Approve`} + > + + + + + rejectMutation.mutate(r.id)} + aria-label={t`Decline`} + > + + + + + + + ))} + + + + ); +}; diff --git a/echo/frontend/src/components/workspace/DiscoverableWorkspaces.tsx b/echo/frontend/src/components/workspace/DiscoverableWorkspaces.tsx new file mode 100644 index 00000000..f48670eb --- /dev/null +++ b/echo/frontend/src/components/workspace/DiscoverableWorkspaces.tsx @@ -0,0 +1,166 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { Button, Group, Paper, Stack, Text } from "@mantine/core"; +import { IconLock, IconPlus } from "@tabler/icons-react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { toast } from "@/components/common/Toaster"; +import { API_BASE_URL } from "@/config"; + +interface DiscoverableWorkspace { + id: string; + name: string; + visibility: string; + action: "join" | "request-access" | "pending" | "member"; + pending_request_id: string | null; +} + +async function fetchDiscoverable(orgId: string): Promise { + const res = await fetch( + `${API_BASE_URL}/v2/orgs/${orgId}/discoverable-workspaces`, + { credentials: "include" }, + ); + if (!res.ok) return []; + const data = await res.json(); + return data.workspaces ?? []; +} + +async function postJoin(workspaceId: string) { + const res = await fetch(`${API_BASE_URL}/v2/workspaces/${workspaceId}/join`, { + method: "POST", + credentials: "include", + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.detail || "Couldn't join"); + } + return res.json(); +} + +async function postRequestAccess(workspaceId: string) { + const res = await fetch( + `${API_BASE_URL}/v2/workspaces/${workspaceId}/access-requests`, + { method: "POST", credentials: "include" }, + ); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.detail || "Couldn't send request"); + } + return res.json(); +} + +/** + * Matrix §6 Slack-style discovery surface for the home page. + * + * Shows workspaces in the team that the user has NO direct membership + * to (filtered server-side). Team admins see all with Join; team + * members see open workspaces with Request access (or Pending when a + * request is already out). + */ +export const DiscoverableWorkspaces = ({ orgId }: { orgId: string }) => { + const queryClient = useQueryClient(); + + const { data, isLoading } = useQuery({ + queryKey: ["v2", "discoverable-workspaces", orgId], + queryFn: () => fetchDiscoverable(orgId), + staleTime: 30_000, + }); + + const joinable = (data ?? []).filter( + (w) => w.action === "join" || w.action === "request-access" || w.action === "pending", + ); + + const joinMutation = useMutation({ + mutationFn: postJoin, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["v2", "workspaces"] }); + queryClient.invalidateQueries({ + queryKey: ["v2", "discoverable-workspaces", orgId], + }); + toast.success(t`Joined`); + }, + onError: (error: Error) => toast.error(error.message), + }); + + const requestMutation = useMutation({ + mutationFn: postRequestAccess, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["v2", "discoverable-workspaces", orgId], + }); + toast.success(t`Request sent`); + }, + onError: (error: Error) => toast.error(error.message), + }); + + if (isLoading || joinable.length === 0) return null; + + return ( + + + Discoverable in this team + + + {joinable.map((ws) => ( + + + + {ws.visibility === "private" && ( + + )} + + {ws.name} + + {ws.visibility === "private" && ( + + Private + + )} + + {ws.action === "join" && ( + + )} + {ws.action === "request-access" && ( + + )} + {ws.action === "pending" && ( + + Request sent + + )} + + + ))} + + + ); +}; diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx index e31d24af..b630d104 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx @@ -20,6 +20,7 @@ import { useDocumentTitle } from "@mantine/hooks"; import { IconPlus, IconSettings } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; +import { DiscoverableWorkspaces } from "@/components/workspace/DiscoverableWorkspaces"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { useWorkspace } from "@/hooks/useWorkspace"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; @@ -402,6 +403,11 @@ export const WorkspaceSelectorRoute = () => { )} + + {/* Matrix §6: Slack-style discovery. Renders only + when there's something joinable/requestable and + hides itself otherwise. */} +
    ); })} diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx index e0d97130..c2426199 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx @@ -27,6 +27,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useParams } from "react-router"; import { toast } from "@/components/common/Toaster"; +import { AccessRequestsList } from "@/components/workspace/AccessRequestsList"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { useV2Me } from "@/hooks/useV2Me"; @@ -629,6 +630,12 @@ export const WorkspaceSettingsRoute = () => { )} + {/* Matrix §6 access requests from team members. Hides itself + when nothing is pending. */} + {canManage && workspaceId && ( + + )} + {/* Your access */} From 58d462fb0a7eda4e8116e622a594083a3dd403a4 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 10:56:21 +0000 Subject: [PATCH 113/208] =?UTF-8?q?frontend=20=C2=A78:=20workspace=20usage?= =?UTF-8?q?=20card=20on=20settings=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumes /v2/workspaces/:id/usage (shipped in e734319). First consumer surface for the usage endpoint and tier_capacity module. UsageCard: - Hours with progress bar, color-coded (blue normal, yellow approaching, red at-limit); shows used / included when tier has a cap. - Seats with progress bar. - Guests and projects counts; guest_cap when present. - Admin / billing: adds overage forecast + seat overage + next-tier recommendation. Fields are null for members (server-gated via workspace:view_invoices). The card renders the financial block only when those fields are present, so members never see an empty section. - Badges: "At limit" (red) when Pilot hard-block is active; "Approaching limit" (yellow) at ≥80% hours usage. - Tier tagline rendered alongside name per matrix §1. Mounted between the "General" block and "Members" on the workspace settings page. Eventually moves into a dedicated Billing tab when the settings tab-split lands. Copy respects brand: factual, verb-first, no "successfully / please", no bold, DM Sans via theme, Royal Blue for primary emphasis. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/workspace/UsageCard.tsx | 249 ++++++++++++++++++ .../workspaces/WorkspaceSettingsRoute.tsx | 8 + 2 files changed, 257 insertions(+) create mode 100644 echo/frontend/src/components/workspace/UsageCard.tsx diff --git a/echo/frontend/src/components/workspace/UsageCard.tsx b/echo/frontend/src/components/workspace/UsageCard.tsx new file mode 100644 index 00000000..822c70ff --- /dev/null +++ b/echo/frontend/src/components/workspace/UsageCard.tsx @@ -0,0 +1,249 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + Anchor, + Badge, + Divider, + Group, + Paper, + Progress, + Stack, + Text, + Title, +} from "@mantine/core"; +import { useQuery } from "@tanstack/react-query"; +import { API_BASE_URL } from "@/config"; + +interface ProjectUsage { + id: string; + name: string; + audio_hours: number; + conversation_count: number; +} + +interface UsageResponse { + cycle_start: string; + cycle_end_exclusive: string; + tier: string; + tier_tagline: string; + audio_hours: number; + audio_hours_included: number | null; + seat_count: number; + seat_count_included: number | null; + guest_count: number; + guest_cap: number | null; + project_count: number; + projects: ProjectUsage[]; + pilot_hard_block_active: boolean; + overage_forecast_eur?: number | null; + seat_overage_eur?: number | null; + next_tier?: { + tier: string; + tagline: string; + price_eur_monthly: number | null; + price_note: string; + included_hours: number | null; + included_seats: number | null; + } | null; +} + +async function fetchUsage(workspaceId: string): Promise { + const res = await fetch( + `${API_BASE_URL}/v2/workspaces/${workspaceId}/usage`, + { credentials: "include" }, + ); + if (!res.ok) return null; + return res.json(); +} + +function formatCycleMonth(iso: string): string { + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return ""; + return d.toLocaleDateString(undefined, { month: "long", year: "numeric" }); +} + +function formatEur(value: number | null | undefined): string { + if (value == null) return "—"; + if (value === 0) return "€0"; + return `€${Math.round(value)}`; +} + +/** + * Workspace usage card (matrix v1.1 §8). + * + * Role-aware rendering: + * - Member: hours / seats / guests / projects, raw numbers. + * - Admin + Billing: adds overage forecast and next-tier recommendation. + * + * Source fields come pre-differentiated from the backend (next_tier + + * overage_forecast_eur are null for members — `workspace:view_invoices` + * gates them server-side). + */ +export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { + const { data, isLoading } = useQuery({ + queryKey: ["v2", "workspace-usage", workspaceId], + queryFn: () => fetchUsage(workspaceId), + staleTime: 60_000, + }); + + if (isLoading || !data) return null; + + const hoursPct = + data.audio_hours_included && data.audio_hours_included > 0 + ? Math.min(100, (data.audio_hours / data.audio_hours_included) * 100) + : null; + + const seatsPct = + data.seat_count_included && data.seat_count_included > 0 + ? Math.min(100, (data.seat_count / data.seat_count_included) * 100) + : null; + + const pilotExhausted = data.pilot_hard_block_active; + const approachingCap = hoursPct !== null && hoursPct >= 80 && hoursPct < 100; + + return ( + + + + + + <Trans>Usage · {formatCycleMonth(data.cycle_start)}</Trans> + + {data.tier_tagline && ( + + {data.tier} + {" — "} + {data.tier_tagline} + + )} + + {pilotExhausted && ( + + At limit + + )} + {!pilotExhausted && approachingCap && ( + + Approaching limit + + )} + + + {/* Audio hours */} + + + + Audio hours + + + {data.audio_hours.toFixed(1)} + {data.audio_hours_included != null && ( + + {" / "} + {data.audio_hours_included} + + )} + + + {hoursPct !== null && ( + + )} + + + {/* Seats */} + + + + Seats + + + {data.seat_count} + {data.seat_count_included != null && ( + + {" / "} + {data.seat_count_included} + + )} + + + {seatsPct !== null && ( + + )} + + + {/* Guests + projects (compact row) */} + + + Guests + + + {data.guest_count} + {data.guest_cap != null && ( + + {" / "} + {data.guest_cap} + + )} + + + + + Projects + + {data.project_count} + + + {/* Admin / billing: financial forecast */} + {(data.overage_forecast_eur != null || data.next_tier) && ( + <> + + + {data.overage_forecast_eur != null && ( + + + Overage forecast this month + + {formatEur(data.overage_forecast_eur)} + + )} + {data.seat_overage_eur != null && data.seat_overage_eur > 0 && ( + + + Extra-seat cost + + {formatEur(data.seat_overage_eur)} + + )} + {data.next_tier && ( + + + Next tier: {data.next_tier.tier} — {data.next_tier.tagline} + + {data.next_tier.price_eur_monthly != null && ( + <> + {" "} + + + {formatEur(data.next_tier.price_eur_monthly)} + /mo · see what's included + + + + )} + + )} + + + )} + + + ); +}; diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx index c2426199..2b50dda7 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx @@ -28,6 +28,7 @@ import { useState } from "react"; import { useParams } from "react-router"; import { toast } from "@/components/common/Toaster"; import { AccessRequestsList } from "@/components/workspace/AccessRequestsList"; +import { UsageCard } from "@/components/workspace/UsageCard"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { useV2Me } from "@/hooks/useV2Me"; @@ -357,6 +358,13 @@ export const WorkspaceSettingsRoute = () => { + {/* Usage + financial rollup (matrix §8). Role-aware rendering + lives inside the card — members see raw; admin/billing see + overage forecast + next-tier recommendation. */} + {workspaceId && } + + + {/* Members */} From 5de8891a6dc2cc1cbc67bc50b0ef39f4d30e781e Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 11:28:36 +0000 Subject: [PATCH 114/208] server-cache the usage endpoint (30m TTL + ?refresh) + drop broken tab anchors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matrix §8 endpoint now caches server-side. Redis, 30-min TTL, keyed by workspace_id. Full admin-view payload stored; member responses derive by zeroing the financial fields. Cache busts on tier change. Frontend: - UsageCard gets a refresh button that calls the endpoint with ?refresh=true and writes the fresh payload into the React Query cache. - UsageCard, PilotBlockModal, DowngradeBanner drop the ?tab=billing + #request-upgrade anchors that pointed nowhere. All three now route to /w/:id/settings, which is the current one-page home for usage + billing until the tab split lands. New module: server/dembrane/cache_utils.py — thin JSON wrappers over the async Redis client plus the usage namespace helpers. Errors on read/write are swallowed (cache-miss semantics); the endpoint never fails because Redis hiccuped. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/workspace/DowngradeBanner.tsx | 4 +- .../components/workspace/PilotBlockModal.tsx | 16 +--- .../src/components/workspace/UsageCard.tsx | 91 ++++++++++++------ echo/server/dembrane/api/v2/workspaces.py | 94 ++++++++++++++----- echo/server/dembrane/cache_utils.py | 77 +++++++++++++++ 5 files changed, 213 insertions(+), 69 deletions(-) create mode 100644 echo/server/dembrane/cache_utils.py diff --git a/echo/frontend/src/components/workspace/DowngradeBanner.tsx b/echo/frontend/src/components/workspace/DowngradeBanner.tsx index 5c614e43..2e1f6e85 100644 --- a/echo/frontend/src/components/workspace/DowngradeBanner.tsx +++ b/echo/frontend/src/components/workspace/DowngradeBanner.tsx @@ -77,9 +77,7 @@ export const DowngradeBanner = () => { component="button" type="button" size="sm" - onClick={() => - navigate(`/w/${workspaceId}/settings?tab=billing`) - } + onClick={() => navigate(`/w/${workspaceId}/settings`)} > Learn more diff --git a/echo/frontend/src/components/workspace/PilotBlockModal.tsx b/echo/frontend/src/components/workspace/PilotBlockModal.tsx index 63dbfc8d..18546824 100644 --- a/echo/frontend/src/components/workspace/PilotBlockModal.tsx +++ b/echo/frontend/src/components/workspace/PilotBlockModal.tsx @@ -50,26 +50,14 @@ export const PilotBlockModal = () => { - diff --git a/echo/frontend/src/components/workspace/UsageCard.tsx b/echo/frontend/src/components/workspace/UsageCard.tsx index 822c70ff..83880991 100644 --- a/echo/frontend/src/components/workspace/UsageCard.tsx +++ b/echo/frontend/src/components/workspace/UsageCard.tsx @@ -1,7 +1,7 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { - Anchor, + ActionIcon, Badge, Divider, Group, @@ -10,8 +10,11 @@ import { Stack, Text, Title, + Tooltip, } from "@mantine/core"; -import { useQuery } from "@tanstack/react-query"; +import { IconRefresh } from "@tabler/icons-react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; import { API_BASE_URL } from "@/config"; interface ProjectUsage { @@ -47,11 +50,12 @@ interface UsageResponse { } | null; } -async function fetchUsage(workspaceId: string): Promise { - const res = await fetch( - `${API_BASE_URL}/v2/workspaces/${workspaceId}/usage`, - { credentials: "include" }, - ); +async function fetchUsage( + workspaceId: string, + refresh = false, +): Promise { + const url = `${API_BASE_URL}/v2/workspaces/${workspaceId}/usage${refresh ? "?refresh=true" : ""}`; + const res = await fetch(url, { credentials: "include" }); if (!res.ok) return null; return res.json(); } @@ -80,12 +84,33 @@ function formatEur(value: number | null | undefined): string { * gates them server-side). */ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { + const queryClient = useQueryClient(); + const [refreshing, setRefreshing] = useState(false); + const { data, isLoading } = useQuery({ queryKey: ["v2", "workspace-usage", workspaceId], queryFn: () => fetchUsage(workspaceId), staleTime: 60_000, }); + const handleRefresh = async () => { + // Manual force-recompute. Bypasses the server's 30-min cache via + // ?refresh=true, then writes the fresh payload into the React + // Query cache so the card updates without a second fetch. + setRefreshing(true); + try { + const fresh = await fetchUsage(workspaceId, true); + if (fresh) { + queryClient.setQueryData( + ["v2", "workspace-usage", workspaceId], + fresh, + ); + } + } finally { + setRefreshing(false); + } + }; + if (isLoading || !data) return null; const hoursPct = @@ -104,8 +129,8 @@ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { return ( - - + + <Trans>Usage · {formatCycleMonth(data.cycle_start)}</Trans> @@ -117,16 +142,30 @@ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { )} - {pilotExhausted && ( - - At limit - - )} - {!pilotExhausted && approachingCap && ( - - Approaching limit - - )} + + {pilotExhausted && ( + + At limit + + )} + {!pilotExhausted && approachingCap && ( + + Approaching limit + + )} + + + + + + {/* Audio hours */} @@ -225,17 +264,9 @@ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { {data.next_tier.price_eur_monthly != null && ( <> - {" "} - - - {formatEur(data.next_tier.price_eur_monthly)} - /mo · see what's included - - + {" · "} + {formatEur(data.next_tier.price_eur_monthly)} + /mo )} diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index bee8b182..f8346c64 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -566,6 +566,13 @@ async def set_workspace_tier( ws_update["downgraded_from_tier"] = None await async_directus.update_item("workspace", workspace_id, ws_update) + # Bust the cached usage rollup so the UI reflects the new tier's + # caps + rates on the next read. Without this, overage_forecast_eur + + # next_tier would lag by up to USAGE_TTL_SECONDS. + if direction != "no-change": + from dembrane.cache_utils import invalidate_workspace_usage + await invalidate_workspace_usage(workspace_id) + logger.info( f"STAFF tier change: workspace {workspace_id} {from_tier} → {to_tier} " f"(direction={direction}, by={auth.user_id}, reason={body.reason!r}, " @@ -817,6 +824,7 @@ class WorkspaceUsageResponse(BaseModel): ) async def get_workspace_usage( ctx: WorkspaceContext = Depends(get_workspace_context), + refresh: bool = False, ) -> WorkspaceUsageResponse: """Workspace usage rollup for the current calendar month. @@ -826,7 +834,19 @@ async def get_workspace_usage( Implementation: hours derive from `conversation.duration` SUM where `deleted_at IS NULL AND created_at >= month_start AND created_at < next_month_start`. No separate `usage_event` table (D9). + + Caching: 30-minute Redis cache of the full (admin-view) response, + keyed by workspace. Member responses are derived from the cached + admin response by zeroing the financial fields. Cache is busted on + tier change (see set_workspace_tier). Pass `?refresh=true` to + force a recompute + cache overwrite. """ + from dembrane.cache_utils import ( + USAGE_TTL_SECONDS, + cache_get_json, + cache_set_json, + usage_cache_key, + ) from dembrane.tier_capacity import ( compute_hour_overage_eur, compute_seat_overage_eur, @@ -843,6 +863,24 @@ async def get_workspace_usage( if ctx.is_external: raise HTTPException(status_code=403, detail="Guests don't see workspace usage") + # Role-class decides whether financial fields are returned. We always + # compute + cache the full-admin response; member response nulls the + # financial fields at serialisation time. + sees_financials = ctx.has_policy("workspace:view_invoices") + + cache_key = usage_cache_key(ctx.workspace_id) + if not refresh: + cached = await cache_get_json(cache_key) + if isinstance(cached, dict): + if not sees_financials: + cached = { + **cached, + "overage_forecast_eur": None, + "seat_overage_eur": None, + "next_tier": None, + } + return WorkspaceUsageResponse(**cached) + now = datetime.now(timezone.utc) cycle_start, cycle_end_exclusive = _calendar_month_bounds(now) @@ -975,7 +1013,25 @@ async def get_workspace_usage( and audio_hours >= cap.included_hours ) - base = WorkspaceUsageResponse( + # Always compute the full admin-view payload so the cache holds one + # variant; members get a filtered copy at serialisation time. + overage_forecast = compute_hour_overage_eur(tier, audio_hours) + seat_overage = compute_seat_overage_eur(tier, seat_count) + recommended = tier_next(tier) + next_rec: Optional[NextTierRecommendation] = None + if recommended: + rcap = get_capacity(recommended) + if rcap: + next_rec = NextTierRecommendation( + tier=recommended, + tagline=rcap.tagline, + price_eur_monthly=rcap.price_eur_monthly, + price_note=rcap.price_note, + included_hours=rcap.included_hours, + included_seats=rcap.included_seats, + ) + + full = WorkspaceUsageResponse( cycle_start=cycle_start, cycle_end_exclusive=cycle_end_exclusive, tier=tier, @@ -989,27 +1045,21 @@ async def get_workspace_usage( project_count=len(projects), projects=per_project_items, pilot_hard_block_active=hard_block, + overage_forecast_eur=overage_forecast, + seat_overage_eur=seat_overage, + next_tier=next_rec, ) - # Admin + billing: add financial surface. - if ctx.has_policy("workspace:view_invoices"): - overage_forecast = compute_hour_overage_eur(tier, audio_hours) - seat_overage = compute_seat_overage_eur(tier, seat_count) - recommended = tier_next(tier) - next_rec: Optional[NextTierRecommendation] = None - if recommended: - rcap = get_capacity(recommended) - if rcap: - next_rec = NextTierRecommendation( - tier=recommended, - tagline=rcap.tagline, - price_eur_monthly=rcap.price_eur_monthly, - price_note=rcap.price_note, - included_hours=rcap.included_hours, - included_seats=rcap.included_seats, - ) - base.overage_forecast_eur = overage_forecast - base.seat_overage_eur = seat_overage - base.next_tier = next_rec + # Cache the full payload (admin view). Best-effort — failure to cache + # never breaks the read. + await cache_set_json(cache_key, full.model_dump(), USAGE_TTL_SECONDS) + + if not sees_financials: + return WorkspaceUsageResponse(**{ + **full.model_dump(), + "overage_forecast_eur": None, + "seat_overage_eur": None, + "next_tier": None, + }) - return base + return full diff --git a/echo/server/dembrane/cache_utils.py b/echo/server/dembrane/cache_utils.py new file mode 100644 index 00000000..6cf660ae --- /dev/null +++ b/echo/server/dembrane/cache_utils.py @@ -0,0 +1,77 @@ +"""Lightweight JSON cache helpers around the async Redis client. + +Used for: +- Workspace usage rollups (30-min TTL). Re-computing hours means summing + conversation.duration across a workspace's projects, which is O(N) in + conversations — cacheable for minutes without stale-data concerns + because "current calendar month hours" is the whole point. + +Caller conventions: +- Cache keys include a namespace prefix ("usage:", "capacity:", ...) so + flushes are targeted. +- Reads return None on miss or on any error (never raise) — the caller + treats Redis-down as a cache miss, never as an endpoint failure. +- Writes are fire-and-forget best-effort. Redis-down means the next + request recomputes. +""" + +from __future__ import annotations + +import json +from logging import getLogger +from typing import Any, Optional + +from dembrane.redis_async import get_redis_client + +logger = getLogger("dembrane.cache_utils") + + +async def cache_get_json(key: str) -> Optional[Any]: + """Return the JSON-decoded value at `key`, or None on miss/error.""" + try: + client = await get_redis_client() + raw = await client.get(key) + if raw is None: + return None + if isinstance(raw, bytes): + raw = raw.decode("utf-8") + return json.loads(raw) + except Exception as exc: + logger.debug("cache_get_json miss/error key=%s err=%s", key, exc) + return None + + +async def cache_set_json(key: str, value: Any, ttl_seconds: int) -> None: + """Write JSON-encoded `value` at `key` with TTL. Best-effort.""" + try: + client = await get_redis_client() + payload = json.dumps(value, default=str) + await client.setex(key, ttl_seconds, payload) + except Exception as exc: + logger.debug("cache_set_json error key=%s err=%s", key, exc) + + +async def cache_delete(key: str) -> None: + """Delete a single cache key. Best-effort.""" + try: + client = await get_redis_client() + await client.delete(key) + except Exception as exc: + logger.debug("cache_delete error key=%s err=%s", key, exc) + + +# ── Namespaced helpers ───────────────────────────────────────────────── + + +USAGE_TTL_SECONDS = 30 * 60 # 30 minutes + + +def usage_cache_key(workspace_id: str) -> str: + return f"usage:{workspace_id}" + + +async def invalidate_workspace_usage(workspace_id: str) -> None: + """Bust the cached usage rollup for a workspace. Call on tier changes + (upgrade/downgrade) so the next /usage read reflects new caps + rates + immediately instead of waiting for TTL expiry.""" + await cache_delete(usage_cache_key(workspace_id)) From 35b17054339c57e12bb2bd26c86eb417cce734d1 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 11:29:55 +0000 Subject: [PATCH 115/208] =?UTF-8?q?matrix=20=C2=A73:=20auto-return=20Downg?= =?UTF-8?q?radeBanner=20on=20frozen-feature-attempt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FeatureGate fires emitFrozenFeatureAttempt() on gate-modal open. DowngradeBanner listens and clears its sessionStorage dismissal key so the banner re-surfaces on the next render. No-op for workspaces without an active downgrade — the banner's early-return skips subscription in that case. Matches matrix §3 wording: "dismissible but auto-returns if the admin attempts a frozen feature." New: lib/frozenFeatureAttempt.ts — same window-CustomEvent pattern as lib/pilotBlock.ts. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/workspace/DowngradeBanner.tsx | 15 +++++++++++++ .../src/components/workspace/FeatureGate.tsx | 14 ++++++++++-- echo/frontend/src/lib/frozenFeatureAttempt.ts | 22 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 echo/frontend/src/lib/frozenFeatureAttempt.ts diff --git a/echo/frontend/src/components/workspace/DowngradeBanner.tsx b/echo/frontend/src/components/workspace/DowngradeBanner.tsx index 2e1f6e85..82cfe107 100644 --- a/echo/frontend/src/components/workspace/DowngradeBanner.tsx +++ b/echo/frontend/src/components/workspace/DowngradeBanner.tsx @@ -4,6 +4,7 @@ import { IconX } from "@tabler/icons-react"; import { useEffect, useState } from "react"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { useWorkspace } from "@/hooks/useWorkspace"; +import { onFrozenFeatureAttempt } from "@/lib/frozenFeatureAttempt"; /** * 7-day post-downgrade banner (matrix v1.1 §3). @@ -35,6 +36,20 @@ export const DowngradeBanner = () => { setDismissed(stored === "1"); }, [workspaceId, downgradedAt]); + // Matrix §3: auto-return on frozen-feature-attempt. When FeatureGate + // opens its tier modal, clear the dismissal so this banner comes back. + // No-op for workspaces without an active downgrade — the outer early- + // returns guard that path. + useEffect(() => { + if (!workspaceId || !downgradedAt) return; + return onFrozenFeatureAttempt(() => { + sessionStorage.removeItem( + `${DISMISS_KEY_PREFIX}${workspaceId}:${downgradedAt}`, + ); + setDismissed(false); + }); + }, [workspaceId, downgradedAt]); + if (!workspace || !downgradedAt || !tier || !workspaceId) return null; const stamp = Date.parse(downgradedAt); diff --git a/echo/frontend/src/components/workspace/FeatureGate.tsx b/echo/frontend/src/components/workspace/FeatureGate.tsx index 4d39070e..9b2bad3c 100644 --- a/echo/frontend/src/components/workspace/FeatureGate.tsx +++ b/echo/frontend/src/components/workspace/FeatureGate.tsx @@ -13,6 +13,7 @@ import { IconLock } from "@tabler/icons-react"; import { useState, type ReactNode } from "react"; import { toast } from "@/components/common/Toaster"; import { API_BASE_URL } from "@/config"; +import { emitFrozenFeatureAttempt } from "@/lib/frozenFeatureAttempt"; /** * Tier-gating UI primitives for the ECHO platform. @@ -101,11 +102,20 @@ export function FeatureGate({ // gated subtree (round-2 audit, Security M1). The only safe boundary // is "don't mount the feature at all when the tier doesn't meet" — // render just the gate placeholder card. + // Matrix §3: attempting a frozen feature re-shows the post-downgrade + // banner if it was dismissed. We fire on every gate-open; DowngradeBanner + // only reacts when the current workspace has an active downgrade, so this + // is a cheap no-op on never-downgraded workspaces. + const openModal = () => { + emitFrozenFeatureAttempt(); + setModalOpen(true); + }; + return ( <> setModalOpen(true)} + onClick={openModal} style={{ cursor: "pointer", minHeight: 160, @@ -123,7 +133,7 @@ export function FeatureGate({ onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); - setModalOpen(true); + openModal(); } }} > diff --git a/echo/frontend/src/lib/frozenFeatureAttempt.ts b/echo/frontend/src/lib/frozenFeatureAttempt.ts new file mode 100644 index 00000000..9f01d7ee --- /dev/null +++ b/echo/frontend/src/lib/frozenFeatureAttempt.ts @@ -0,0 +1,22 @@ +/** + * Frozen-feature-attempt signal. + * + * Matrix v1.1 §3: the 7-day post-downgrade banner is dismissable but + * "auto-returns if the admin attempts a frozen feature." This bus + * carries that signal — FeatureGate fires when its tier-gate modal + * opens; DowngradeBanner listens and clears its session dismissal. + * + * Same pattern as lib/pilotBlock.ts: window CustomEvent so any caller + * can fire without threading a context. + */ + +const EVENT_NAME = "dembrane:frozen-feature-attempt"; + +export function emitFrozenFeatureAttempt() { + window.dispatchEvent(new Event(EVENT_NAME)); +} + +export function onFrozenFeatureAttempt(handler: () => void) { + window.addEventListener(EVENT_NAME, handler); + return () => window.removeEventListener(EVENT_NAME, handler); +} From 893be3fc426275b2721a71f8785615eea5018652 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 11:57:23 +0000 Subject: [PATCH 116/208] =?UTF-8?q?HCD=20audit=20=E2=80=94=20tier-1=20fixe?= =?UTF-8?q?s=20(pattern=20+=20copy=20hygiene)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four subagents, one per role pair, audited workspace settings / team settings / team admin page / home selector (see 07-HCD-AUDIT.md). This commit lands the universal pattern + copy fixes that surfaced. A. Shared TierBadge component (components/workspace/TierBadge.tsx) + lib/tiers.ts with canonical tagline map. Used on: - WorkspaceSelectorRoute cards (tooltip tagline, dense layout) - WorkspaceSettingsRoute header (inline tagline, detail page) - TeamRoute workspace drawer rows (tooltip tagline) Matrix §1: every tier name must pair with a tagline. Previously only UsageCard complied. B. "Your access" drops raw policy badges. A member looking at `member:manage`, `settings:manage` as chips reads engineer-speak. The role badge alone does the "remind me what I can do" job. C. Privacy & defaults hidden for non-admins. Members / billing / guests previously saw disabled fields → read as broken. Now show nothing for the inapplicable role. D. TeamSettingsRoute alert: "Only team admins and owners can change team settings. You're a {role}." → "Only team admins can change team settings." Matrix §5 role model is Admin / Billing / Member — no Owner at team level. E. TeamRoute footer: "Access shown is derived from team role. Workspace- direct invites are managed in each workspace's settings." → "Team admins can join any workspace to get a direct admin seat. Workspace invites live in each workspace's settings." Matrix §5 retired derivation language; don't lie to users about the model. F. TeamRoute drops the team-level "Guests" filter + team header guest count. Matrix §5 has no team-level Guest role — guests exist only at the workspace level. 07-HCD-AUDIT.md captures the full per-role concern list + converged plan. Tier-2 fixes (Request upgrade, Leave workspace, Guest chip) land in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/workspace/TierBadge.tsx | 59 +++++++++++++++++++ echo/frontend/src/lib/tiers.ts | 46 +++++++++++++++ echo/frontend/src/routes/team/TeamRoute.tsx | 33 ++++------- .../src/routes/team/TeamSettingsRoute.tsx | 3 +- .../workspaces/WorkspaceSelectorRoute.tsx | 7 ++- .../workspaces/WorkspaceSettingsRoute.tsx | 47 +++++++-------- 6 files changed, 142 insertions(+), 53 deletions(-) create mode 100644 echo/frontend/src/components/workspace/TierBadge.tsx create mode 100644 echo/frontend/src/lib/tiers.ts diff --git a/echo/frontend/src/components/workspace/TierBadge.tsx b/echo/frontend/src/components/workspace/TierBadge.tsx new file mode 100644 index 00000000..cd30d2d9 --- /dev/null +++ b/echo/frontend/src/components/workspace/TierBadge.tsx @@ -0,0 +1,59 @@ +import { Badge, Group, Text, Tooltip } from "@mantine/core"; +import type { MantineSize } from "@mantine/core"; +import { taglineFor } from "@/lib/tiers"; + +interface TierBadgeProps { + tier: string; + size?: MantineSize; + /** When true, render the tagline inline next to the badge. Use on + * surfaces with enough width. Defaults to false (tagline via tooltip). */ + showTagline?: boolean; +} + +/** + * Tier badge with pairing tagline (matrix v1.1 §1). + * + * Two render modes: + * - Inline (`showTagline=true`): badge + "— tagline" text after it. Use + * on detail pages and selector cards where space allows. + * - Tooltip (default): badge alone; the tagline is in a tooltip. Use on + * compact rows (matrix cells, header chips) where a second line of + * copy would be visual clutter. + * + * Either way, the tagline is never absent — matrix requires pairing. + */ +export const TierBadge = ({ + tier, + size = "sm", + showTagline = false, +}: TierBadgeProps) => { + const tagline = taglineFor(tier); + + const badge = ( + + {tier} + + ); + + if (showTagline && tagline) { + return ( + + {badge} + + — {tagline} + + + ); + } + + if (tagline) { + return {badge}; + } + + return badge; +}; diff --git a/echo/frontend/src/lib/tiers.ts b/echo/frontend/src/lib/tiers.ts new file mode 100644 index 00000000..9df5f0ca --- /dev/null +++ b/echo/frontend/src/lib/tiers.ts @@ -0,0 +1,46 @@ +/** + * Tier taglines (matrix v1.1 §1). + * + * Every surface that shows a tier name must pair it with a tagline. + * Kept in sync with server/dembrane/tier_capacity.py — if copy changes, + * update both sides. The two-source pattern is intentional: the backend + * uses taglines in email templates; the frontend uses them in UI; they + * don't need a round-trip. + */ + +export type Tier = + | "pilot" + | "pioneer" + | "innovator" + | "changemaker" + | "guardian"; + +export const TIER_ORDER: Tier[] = [ + "pilot", + "pioneer", + "innovator", + "changemaker", + "guardian", +]; + +export const TIER_TAGLINE: Record = { + pilot: "one month to try it.", + pioneer: "for your first real engagements.", + innovator: "privacy and data portability.", + changemaker: "your brand, your integrations.", + guardian: "enterprise scale.", +}; + +export function isTier(value: string | null | undefined): value is Tier { + return ( + value === "pilot" || + value === "pioneer" || + value === "innovator" || + value === "changemaker" || + value === "guardian" + ); +} + +export function taglineFor(tier: string | null | undefined): string { + return isTier(tier) ? TIER_TAGLINE[tier] : ""; +} diff --git a/echo/frontend/src/routes/team/TeamRoute.tsx b/echo/frontend/src/routes/team/TeamRoute.tsx index 23934d36..495a9b3f 100644 --- a/echo/frontend/src/routes/team/TeamRoute.tsx +++ b/echo/frontend/src/routes/team/TeamRoute.tsx @@ -31,6 +31,7 @@ import { import { useQuery } from "@tanstack/react-query"; import { useMemo, useState } from "react"; import { useParams } from "react-router"; +import { TierBadge } from "@/components/workspace/TierBadge"; import { API_BASE_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { avatarUrl } from "@/lib/avatar"; @@ -100,7 +101,7 @@ async function fetchTeamWorkspaces(teamId: string): Promise { return res.json(); } -type RoleFilter = "all" | "admins" | "members" | "guests"; +type RoleFilter = "all" | "admins" | "members"; export const TeamRoute = () => { const { teamId } = useParams(); @@ -133,19 +134,12 @@ export const TeamRoute = () => { const filteredMembers = useMemo(() => { const q = search.trim().toLowerCase(); return members.filter((m) => { - // Role filter: team role doesn't have "guest" directly, but we - // approximate via backend's convention (members where role maps - // to a guest-adjacent state). For now filter owners+admins into - // "admins", "member" into "members", rest treated as guests. + // Matrix §5: team-level roles are Admin / Billing / Member. + // No team-level Guest — guests exist only at the workspace + // level. Filter collapses owner → admin for display. if (roleFilter === "admins" && !(m.role === "owner" || m.role === "admin")) return false; if (roleFilter === "members" && m.role !== "member") return false; - if ( - roleFilter === "guests" && - m.role !== "external" && - m.role !== "guest" - ) - return false; if (!q) return true; return ( (m.display_name || "").toLowerCase().includes(q) || @@ -197,12 +191,10 @@ export const TeamRoute = () => { {team.workspace_count === 1 ? t`workspace` : t`workspaces`} ·{" "} {team.member_count}{" "} {team.member_count === 1 ? t`person` : t`people`} - {team.external_count > 0 ? ( - <> - {" "}· {team.external_count} {t`guests`} - - ) : null} + {/* Matrix §5: team-level role set is Admin / Billing / + Member — no team-level Guest. Guest count intentionally + dropped from the header summary (HCD audit). */} @@ -257,7 +249,6 @@ export const TeamRoute = () => { { value: "all", label: t`All` }, { value: "admins", label: t`Admins` }, { value: "members", label: t`Members` }, - { value: "guests", label: t`Guests` }, ]} /> @@ -448,8 +439,8 @@ export const TeamRoute = () => { - Access shown is derived from team role. Workspace-direct invites - are managed in each workspace's settings. + Team admins can join any workspace to get a direct admin seat. + Workspace invites live in each workspace's settings. @@ -499,9 +490,7 @@ export const TeamRoute = () => { - - {ws.tier} - + {isAdmin && ( { {!canEdit && ( - Only team admins and owners can change team settings. You're a{" "} - {team.role}. + Only team admins can change team settings. )} diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx index b630d104..85c1f1fb 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSelectorRoute.tsx @@ -21,6 +21,7 @@ import { IconPlus, IconSettings } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; import { DiscoverableWorkspaces } from "@/components/workspace/DiscoverableWorkspaces"; +import { TierBadge } from "@/components/workspace/TierBadge"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { useWorkspace } from "@/hooks/useWorkspace"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; @@ -136,9 +137,9 @@ function WorkspaceCard({ )} - - {workspace.tier} - + + {/* Tagline via tooltip — cards are dense, inline tagline + would crowd the single-line workspace name. */} diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx index 2b50dda7..b26fadd6 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx @@ -28,6 +28,7 @@ import { useState } from "react"; import { useParams } from "react-router"; import { toast } from "@/components/common/Toaster"; import { AccessRequestsList } from "@/components/workspace/AccessRequestsList"; +import { TierBadge } from "@/components/workspace/TierBadge"; import { UsageCard } from "@/components/workspace/UsageCard"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; @@ -324,10 +325,8 @@ export const WorkspaceSettingsRoute = () => { {settings.name} )} - - - {settings.tier} - + + {settings.org_name} @@ -345,18 +344,20 @@ export const WorkspaceSettingsRoute = () => { - {/* Privacy + defaults — admin-only. Kept compact: name + logo - + a single toggle for team-admin inheritance. Member - inheritance is an advanced knob hidden unless the caller - is an owner (exposing three sub-toggles to every admin - clutters the surface). */} - - - + {/* Privacy + defaults — admin-only. Hidden entirely for non- + admin roles (HCD audit 2026-04-23): disabled fields read + as "broken" to members / billing / guests who can't edit + them. Show nothing rather than disabled state. */} + {canEditSettings && ( + <> + + + + )} {/* Usage + financial rollup (matrix §8). Role-aware rendering lives inside the card — members see raw; admin/billing see @@ -644,7 +645,10 @@ export const WorkspaceSettingsRoute = () => { )} - {/* Your access */} + {/* Your access — role only. Raw policy strings removed after + HCD audit (2026-04-23): "member:manage" reads as engineer- + speak to a user. The role badge is sufficient for the + "remind me what I can do" job. */} @@ -655,15 +659,6 @@ export const WorkspaceSettingsRoute = () => { {settings.my_role} </Badge> </Group> - {settings.my_policies && settings.my_policies.length > 0 && ( - <Group gap={6}> - {settings.my_policies.map((policy) => ( - <Badge key={policy} size="xs" variant="outline" color="gray"> - {policy} - </Badge> - ))} - </Group> - )} </Stack> </Stack> </Container> From 7139a307b70546895a877820ec09f8a936e94346 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti <sameer@dembrane.com> Date: Thu, 23 Apr 2026 12:01:10 +0000 Subject: [PATCH 117/208] =?UTF-8?q?HCD=20audit=20=E2=80=94=20tier-2=20fixe?= =?UTF-8?q?s=20(action=20affordances)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit G. Request-upgrade button on UsageCard. Admin / owner / billing see it next to the "Next tier" line. Opens the existing UpgradeModal (shared with FeatureGate's 4C path), which POSTs to /v2/workspaces/:id/upgrade-request. Member + guest never see the button — matrix §11 "Ask a team admin". H. Leave workspace. Self-leave was impossible before this commit — the remove-member endpoint required member:manage. Backend now allows self-leave for any role (policy bypassed on is_self_leave). Last-admin protection still applies: last admin gets a clear "Promote someone else before leaving" error. Frontend: "Your access" section shows a "Leave workspace" button for every role; click opens a confirm modal spelling out what stays and what goes, then routes to /w on success. I. Guest chip in workspace header + UsageCard hidden for guests. Matrix §4 puts "View usage & overage" as ✗ for Guest. We previously showed the UsageCard to guests, including "Guests: 3 / 10" (they are one of those guests). Header now shows "Guest of {org_name}" badge for is_external=true members, replacing the tier badge (guests don't care about tier — it's not theirs). UsageCard and its divider are hidden. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- .../src/components/workspace/UsageCard.tsx | 62 ++++++++++--- .../workspaces/WorkspaceSettingsRoute.tsx | 93 ++++++++++++++++--- .../dembrane/api/v2/workspace_settings.py | 37 +++++++- 3 files changed, 167 insertions(+), 25 deletions(-) diff --git a/echo/frontend/src/components/workspace/UsageCard.tsx b/echo/frontend/src/components/workspace/UsageCard.tsx index 83880991..443bf595 100644 --- a/echo/frontend/src/components/workspace/UsageCard.tsx +++ b/echo/frontend/src/components/workspace/UsageCard.tsx @@ -3,6 +3,7 @@ import { Trans } from "@lingui/react/macro"; import { ActionIcon, Badge, + Button, Divider, Group, Paper, @@ -15,7 +16,10 @@ import { import { IconRefresh } from "@tabler/icons-react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; +import { UpgradeModal, type Tier } from "@/components/workspace/FeatureGate"; import { API_BASE_URL } from "@/config"; +import { useWorkspace } from "@/hooks/useWorkspace"; +import { isTier } from "@/lib/tiers"; interface ProjectUsage { id: string; @@ -85,7 +89,9 @@ function formatEur(value: number | null | undefined): string { */ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { const queryClient = useQueryClient(); + const { workspace } = useWorkspace(); const [refreshing, setRefreshing] = useState(false); + const [upgradeOpen, setUpgradeOpen] = useState(false); const { data, isLoading } = useQuery({ queryKey: ["v2", "workspace-usage", workspaceId], @@ -113,6 +119,12 @@ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { if (isLoading || !data) return null; + // Matrix §11: admin + billing + owner can request upgrades. Role comes + // from the workspace context (the selector response includes role). + const role = workspace?.role; + const canRequestUpgrade = + role === "admin" || role === "owner" || role === "billing"; + const hoursPct = data.audio_hours_included && data.audio_hours_included > 0 ? Math.min(100, (data.audio_hours / data.audio_hours_included) * 100) @@ -126,8 +138,25 @@ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { const pilotExhausted = data.pilot_hard_block_active; const approachingCap = hoursPct !== null && hoursPct >= 80 && hoursPct < 100; + const nextTier = data.next_tier; + const currentTierName = isTier(data.tier) ? (data.tier as Tier) : "pioneer"; + const nextTierName = + nextTier && isTier(nextTier.tier) ? (nextTier.tier as Tier) : null; + return ( <Paper p="lg" withBorder radius="sm"> + {nextTierName && ( + <UpgradeModal + opened={upgradeOpen} + onClose={() => setUpgradeOpen(false)} + currentTier={currentTierName} + requiredTier={nextTierName} + featureName={t`Upgrade to ${nextTier?.tier ?? ""}`} + benefit={nextTier?.tagline ?? ""} + canRequestUpgrade={canRequestUpgrade} + workspaceId={workspaceId} + /> + )} <Stack gap={16}> <Group justify="space-between" align="flex-start" wrap="nowrap"> <Stack gap={2} style={{ minWidth: 0 }}> @@ -258,18 +287,29 @@ export const UsageCard = ({ workspaceId }: { workspaceId: string }) => { </Group> )} {data.next_tier && ( - <Text size="xs" c="dimmed"> - <Trans> - Next tier: {data.next_tier.tier} — {data.next_tier.tagline} - </Trans> - {data.next_tier.price_eur_monthly != null && ( - <> - {" · "} - {formatEur(data.next_tier.price_eur_monthly)} - /mo - </> + <Group justify="space-between" align="center" wrap="nowrap"> + <Text size="xs" c="dimmed"> + <Trans> + Next tier: {data.next_tier.tier} — {data.next_tier.tagline} + </Trans> + {data.next_tier.price_eur_monthly != null && ( + <> + {" · "} + {formatEur(data.next_tier.price_eur_monthly)} + /mo + </> + )} + </Text> + {canRequestUpgrade && ( + <Button + size="compact-xs" + variant="light" + onClick={() => setUpgradeOpen(true)} + > + <Trans>Request upgrade</Trans> + </Button> )} - </Text> + </Group> )} </Stack> </> diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx index b26fadd6..6e6bbef2 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx @@ -33,6 +33,7 @@ import { UsageCard } from "@/components/workspace/UsageCard"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { useV2Me } from "@/hooks/useV2Me"; +import { useWorkspace } from "@/hooks/useWorkspace"; interface WorkspaceMember { id: string; @@ -171,6 +172,12 @@ export const WorkspaceSettingsRoute = () => { const navigate = useI18nNavigate(); const queryClient = useQueryClient(); const { data: meV2 } = useV2Me(); + const { workspace: myWorkspaceSummary } = useWorkspace(); + // Guest = is_external on my direct row. Matrix §4: guest permissions + // mirror member inside their assigned workspace, but they don't see + // usage / privacy / pending invites (matrix §4 "View usage & overage" + // row is ✗ for Guest). + const iAmGuest = myWorkspaceSummary?.is_external === true; const [inviteEmail, setInviteEmail] = useState(""); const [inviteRole, setInviteRole] = useState("member"); const [editingName, setEditingName] = useState<string | null>(null); @@ -272,6 +279,22 @@ export const WorkspaceSettingsRoute = () => { onError: (err: Error) => toast.error(err.message), }); + // Self-leave. Same endpoint as removeMember but we surface different + // success copy + route the user out. + const leaveMutation = useMutation({ + mutationFn: (membershipId: string) => { + if (!workspaceId) throw new Error("No workspace"); + return removeMember(workspaceId, membershipId); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["v2", "workspaces"] }); + queryClient.invalidateQueries({ queryKey: ["v2", "workspace-settings"] }); + toast.success(t`You left the workspace`); + navigate("/w"); + }, + onError: (err: Error) => toast.error(err.message), + }); + if (isLoading || !settings) { return ( <Container size="sm" py="xl"> @@ -326,10 +349,18 @@ export const WorkspaceSettingsRoute = () => { )} - - - {settings.org_name} - + {iAmGuest ? ( + + Guest of {settings.org_name} + + ) : ( + + )} + {!iAmGuest && ( + + {settings.org_name} + + )} + ); + })()} diff --git a/echo/server/dembrane/api/v2/workspace_settings.py b/echo/server/dembrane/api/v2/workspace_settings.py index cb8a47cd..d2193802 100644 --- a/echo/server/dembrane/api/v2/workspace_settings.py +++ b/echo/server/dembrane/api/v2/workspace_settings.py @@ -322,15 +322,27 @@ async def remove_workspace_member( membership_id: str, ctx: WorkspaceContext = Depends(get_workspace_context), ) -> dict: - """Soft-delete a workspace membership. Requires member:manage.""" - ctx.require_policy("member:manage") + """Soft-delete a workspace membership. + + Two callers: + - Admin removing someone else (member:manage). + - User removing themselves (self-leave; no policy required — it's + always valid for a user to leave unless they're the last admin). + Last-admin protection applies to both paths. + """ membership = await async_directus.get_item("workspace_membership", membership_id) if not membership or membership.get("workspace_id") != ctx.workspace_id: raise HTTPException(status_code=404, detail="Membership not found in this workspace") if membership.get("deleted_at"): raise HTTPException(status_code=404, detail="Membership already removed") + # Authorization: self-leave always allowed (for members + guests — HCD + # audit 2026-04-23). Removing someone else requires member:manage. + is_self_leave = membership.get("user_id") == ctx.app_user_id + if not is_self_leave: + ctx.require_policy("member:manage") + if membership.get("role") == "owner": owners = await async_directus.get_items( "workspace_membership", @@ -343,6 +355,27 @@ async def remove_workspace_member( if isinstance(owners, list) and len(owners) <= 1: raise HTTPException(status_code=400, detail="Cannot remove the last owner. Transfer ownership first.") + # Last-admin protection (matrix §4: last admin cannot be removed). + # Applies to both self-leave and admin-removes-admin. + if membership.get("role") == "admin": + admins = await async_directus.get_items( + "workspace_membership", + {"query": {"filter": { + "workspace_id": {"_eq": ctx.workspace_id}, + "role": {"_in": ["admin", "owner"]}, + "deleted_at": {"_null": True}, + }, "fields": ["id"], "limit": 2}}, + ) + if isinstance(admins, list) and len(admins) <= 1: + raise HTTPException( + status_code=400, + detail=( + "You're the only admin. Promote someone else before leaving." + if is_self_leave + else "Can't remove the last admin. Promote someone else first." + ), + ) + await async_directus.update_item( "workspace_membership", membership_id, From e4387e90527963b86a35f3881403ea788a0dec79 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 12:09:49 +0000 Subject: [PATCH 118/208] =?UTF-8?q?matrix=20=C2=A78=20team-scope:=20GET=20?= =?UTF-8?q?/v2/orgs/:id/usage=20+=20TeamUsageRollup=20strip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Biggest HCD concern for team admin + team billing: no cross-workspace financial / usage summary anywhere. Matrix §8 mandates a team-level rollup. This commit ships both the backend endpoint and the UI strip. Backend — GET /v2/orgs/:id/usage: - Member+ role required (via _require_org_role). - Aggregates across every active workspace in the org: total_audio_hours (SUM conversation.duration this cycle) total_seat_count (members + admin + billing + owner, !is_external) total_guest_count (is_external=true) total_project_count workspaces_at_cap (Pilot tier + at/over cap) workspaces_approaching_cap (capped tier, >=80%) - Admin / billing additionally receive total_overage_forecast_eur. - Cached 30 min via org_usage:{org_id}. Invalidated on any tier PATCH in the team (set_workspace_tier busts both scopes). - ?refresh=true bypasses + overwrites. Frontend — TeamUsageRollup (mounted at top of TeamRoute): - Hours / seats / guests / projects + overage forecast (admin/billing). - Badges: "N at limit" (red) / "N approaching limit" (yellow) when any workspace is in trouble — gives team admins a single-glance answer to "where should I look next?" - Refresh button mirrors per-workspace UsageCard pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/workspace/TeamUsageRollup.tsx | 176 ++++++++++++++ echo/frontend/src/routes/team/TeamRoute.tsx | 7 + echo/server/dembrane/api/v2/orgs.py | 216 ++++++++++++++++++ echo/server/dembrane/api/v2/workspaces.py | 12 +- echo/server/dembrane/cache_utils.py | 11 + 5 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 echo/frontend/src/components/workspace/TeamUsageRollup.tsx diff --git a/echo/frontend/src/components/workspace/TeamUsageRollup.tsx b/echo/frontend/src/components/workspace/TeamUsageRollup.tsx new file mode 100644 index 00000000..8d8845eb --- /dev/null +++ b/echo/frontend/src/components/workspace/TeamUsageRollup.tsx @@ -0,0 +1,176 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + ActionIcon, + Badge, + Group, + Paper, + Stack, + Text, + Tooltip, +} from "@mantine/core"; +import { IconRefresh } from "@tabler/icons-react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { API_BASE_URL } from "@/config"; + +interface OrgUsage { + cycle_start: string; + cycle_end_exclusive: string; + workspace_count: number; + total_audio_hours: number; + total_seat_count: number; + total_guest_count: number; + total_project_count: number; + workspaces_at_cap: number; + workspaces_approaching_cap: number; + total_overage_forecast_eur: number | null; +} + +async function fetchOrgUsage( + orgId: string, + refresh = false, +): Promise { + const url = `${API_BASE_URL}/v2/orgs/${orgId}/usage${refresh ? "?refresh=true" : ""}`; + const res = await fetch(url, { credentials: "include" }); + if (!res.ok) return null; + return res.json(); +} + +function formatCycleMonth(iso: string): string { + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return ""; + return d.toLocaleDateString(undefined, { month: "long", year: "numeric" }); +} + +function formatEur(value: number | null | undefined): string { + if (value == null) return "—"; + if (value === 0) return "€0"; + return `€${Math.round(value)}`; +} + +/** + * Team-level usage rollup strip (matrix §8 team scope). + * + * Rendered at the top of TeamRoute for team admins + billing + members. + * Members see raw numbers. Admin/billing additionally see aggregate € + * forecast across all workspaces they bill for (server-gated). + * + * Refresh button mirrors the per-workspace UsageCard pattern. + */ +export const TeamUsageRollup = ({ orgId }: { orgId: string }) => { + const queryClient = useQueryClient(); + const [refreshing, setRefreshing] = useState(false); + + const { data, isLoading } = useQuery({ + queryKey: ["v2", "org-usage", orgId], + queryFn: () => fetchOrgUsage(orgId), + staleTime: 60_000, + }); + + const handleRefresh = async () => { + setRefreshing(true); + try { + const fresh = await fetchOrgUsage(orgId, true); + if (fresh) { + queryClient.setQueryData(["v2", "org-usage", orgId], fresh); + } + } finally { + setRefreshing(false); + } + }; + + if (isLoading || !data) return null; + + const anyWarning = + data.workspaces_at_cap > 0 || data.workspaces_approaching_cap > 0; + + return ( + + + + + Team usage · {formatCycleMonth(data.cycle_start)} + + + + + + + + + + + + {data.total_audio_hours.toFixed(1)} + + {" "}{t`hours`} + + + + across {data.workspace_count} workspaces + + + + + {data.total_seat_count} + + seats + + + + + {data.total_guest_count} + + guests + + + + + {data.total_project_count} + + projects + + + + {data.total_overage_forecast_eur != null && ( + + + {formatEur(data.total_overage_forecast_eur)} + + + overage forecast + + + )} + + + {anyWarning && ( + + {data.workspaces_at_cap > 0 && ( + + + {data.workspaces_at_cap} at limit + + + )} + {data.workspaces_approaching_cap > 0 && ( + + + {data.workspaces_approaching_cap} approaching limit + + + )} + + )} + + + ); +}; diff --git a/echo/frontend/src/routes/team/TeamRoute.tsx b/echo/frontend/src/routes/team/TeamRoute.tsx index 495a9b3f..d2746bd8 100644 --- a/echo/frontend/src/routes/team/TeamRoute.tsx +++ b/echo/frontend/src/routes/team/TeamRoute.tsx @@ -31,6 +31,7 @@ import { import { useQuery } from "@tanstack/react-query"; import { useMemo, useState } from "react"; import { useParams } from "react-router"; +import { TeamUsageRollup } from "@/components/workspace/TeamUsageRollup"; import { TierBadge } from "@/components/workspace/TierBadge"; import { API_BASE_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; @@ -230,6 +231,12 @@ export const TeamRoute = () => { + {/* Matrix §8 team-scope usage rollup — hours, seats, guests, + projects aggregated across all team workspaces + count of + workspaces at/approaching cap. Admin + billing see + aggregate € forecast. Members see raw numbers only. */} + {teamId && } + {/* Toolbar — search + role filter + count */} diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index e9478073..5f7792b3 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -854,3 +854,219 @@ async def remove_team_member( "status": "removed", "workspace_memberships_deleted": len(affected), } + + +# ──────────────────────────────────────────────────────────────────── +# Team-level usage rollup (matrix §8 team-scope) +# ──────────────────────────────────────────────────────────────────── + + +class OrgUsageResponse(BaseModel): + cycle_start: str + cycle_end_exclusive: str + workspace_count: int + total_audio_hours: float + total_seat_count: int + total_guest_count: int + total_project_count: int + workspaces_at_cap: int # Pilot + over cap (hard block active) + workspaces_approaching_cap: int # tier with cap, usage >= 80% + # Admin / billing only (null for plain members): + total_overage_forecast_eur: Optional[float] = None + + +@router.get("/{org_id}/usage", response_model=OrgUsageResponse) +async def get_org_usage( + org_id: str, + auth: DependencyDirectusSession, + refresh: bool = False, +) -> OrgUsageResponse: + """Team-wide usage rollup across all active workspaces in the org. + + Matrix §8: "Team level — rollup across all workspaces in the team. + Shows which workspaces are over, which tier each is on, aggregate + spend." This endpoint returns the aggregate; the per-workspace list + is already available via `/orgs/:id/workspaces`. + + Cached 30 min, keyed by org_id. Pass `?refresh=true` to bypass. + Cache busts are NOT wired for conversation create/delete (would mean + hooking every conversation write site) — the 30-min TTL is the + explicit freshness contract. + + Access: + - Any team member can read raw numbers. + - Admin / billing (org:view_invoices) additionally receive + total_overage_forecast_eur. + """ + from datetime import datetime, timezone + + from dembrane.cache_utils import ( + USAGE_TTL_SECONDS, + cache_get_json, + cache_set_json, + ) + from dembrane.tier_capacity import ( + compute_hour_overage_eur, + get_capacity, + ) + + app_user = await get_app_user_or_raise(auth.user_id) + caller_role = await _require_org_role(org_id, app_user["id"], minimum="member") + sees_financials = caller_role in ("admin", "owner", "billing") + + cache_key = f"org_usage:{org_id}" + if not refresh: + cached = await cache_get_json(cache_key) + if isinstance(cached, dict): + if not sees_financials: + cached = {**cached, "total_overage_forecast_eur": None} + return OrgUsageResponse(**cached) + + # Cycle bounds. + now = datetime.now(timezone.utc) + month_start = now.replace( + day=1, hour=0, minute=0, second=0, microsecond=0 + ) + if now.month == 12: + next_start = month_start.replace(year=now.year + 1, month=1) + else: + next_start = month_start.replace(month=now.month + 1) + cycle_start = month_start.isoformat() + cycle_end_exclusive = next_start.isoformat() + + # All active workspaces in this org. + workspaces = await async_directus.get_items( + "workspace", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id", "tier"], + "limit": -1, + } + }, + ) or [] + if not isinstance(workspaces, list): + workspaces = [] + + ws_ids = [w["id"] for w in workspaces if w.get("id")] + + # Projects + conversations (this cycle) across all workspaces — batch. + project_count = 0 + per_ws_hours: dict[str, float] = {w["id"]: 0.0 for w in workspaces if w.get("id")} + if ws_ids: + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_in": ws_ids}, + "deleted_at": {"_null": True}, + }, + "fields": ["id", "workspace_id"], + "limit": -1, + } + }, + ) or [] + if isinstance(projects, list): + project_count = len(projects) + ws_by_project = { + p["id"]: p["workspace_id"] + for p in projects + if p.get("id") and p.get("workspace_id") + } + pids = list(ws_by_project.keys()) + if pids: + conversations = await async_directus.get_items( + "conversation", + { + "query": { + "filter": { + "project_id": {"_in": pids}, + "deleted_at": {"_null": True}, + "created_at": { + "_gte": cycle_start, + "_lt": cycle_end_exclusive, + }, + }, + "fields": ["project_id", "duration"], + "limit": -1, + } + }, + ) or [] + if isinstance(conversations, list): + for c in conversations: + pid = c.get("project_id") + ws_id = ws_by_project.get(pid) if pid else None + if not ws_id: + continue + per_ws_hours[ws_id] = ( + per_ws_hours.get(ws_id, 0.0) + + (int(c.get("duration") or 0) / 3600.0) + ) + + # Memberships — one read, classify by is_external. + total_seat_count = 0 + total_guest_count = 0 + if ws_ids: + memberships = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_in": ws_ids}, + "deleted_at": {"_null": True}, + }, + "fields": ["role", "is_external"], + "limit": -1, + } + }, + ) or [] + if isinstance(memberships, list): + for m in memberships: + if m.get("is_external"): + total_guest_count += 1 + elif m.get("role") in ("admin", "owner", "member", "billing"): + total_seat_count += 1 + + # Per-tier aggregation for cap counts + forecast. + total_hours = 0.0 + at_cap = 0 + approaching = 0 + forecast = 0.0 + for w in workspaces: + wid = w.get("id") + tier = w.get("tier") or "" + hours = per_ws_hours.get(wid, 0.0) if wid else 0.0 + total_hours += hours + cap = get_capacity(tier) + if cap is None or cap.included_hours is None: + continue + pct = (hours / cap.included_hours) if cap.included_hours else 0.0 + if cap.hard_block_on_hours and hours >= cap.included_hours: + at_cap += 1 + elif pct >= 0.8: + approaching += 1 + forecast += compute_hour_overage_eur(tier, hours) + + payload = { + "cycle_start": cycle_start, + "cycle_end_exclusive": cycle_end_exclusive, + "workspace_count": len(workspaces), + "total_audio_hours": round(total_hours, 2), + "total_seat_count": total_seat_count, + "total_guest_count": total_guest_count, + "total_project_count": project_count, + "workspaces_at_cap": at_cap, + "workspaces_approaching_cap": approaching, + "total_overage_forecast_eur": round(forecast, 2), + } + + await cache_set_json(cache_key, payload, USAGE_TTL_SECONDS) + + if not sees_financials: + payload = {**payload, "total_overage_forecast_eur": None} + + return OrgUsageResponse(**payload) diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index f8346c64..b02e07a3 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -567,11 +567,17 @@ async def set_workspace_tier( await async_directus.update_item("workspace", workspace_id, ws_update) # Bust the cached usage rollup so the UI reflects the new tier's - # caps + rates on the next read. Without this, overage_forecast_eur + - # next_tier would lag by up to USAGE_TTL_SECONDS. + # caps + rates on the next read. Both the workspace-scope cache and + # the org-scope aggregate depend on tier info, so bust both. if direction != "no-change": - from dembrane.cache_utils import invalidate_workspace_usage + from dembrane.cache_utils import ( + invalidate_workspace_usage, + invalidate_org_usage, + ) await invalidate_workspace_usage(workspace_id) + ws_org_id = workspace.get("org_id") + if ws_org_id: + await invalidate_org_usage(ws_org_id) logger.info( f"STAFF tier change: workspace {workspace_id} {from_tier} → {to_tier} " diff --git a/echo/server/dembrane/cache_utils.py b/echo/server/dembrane/cache_utils.py index 6cf660ae..259ee344 100644 --- a/echo/server/dembrane/cache_utils.py +++ b/echo/server/dembrane/cache_utils.py @@ -70,8 +70,19 @@ def usage_cache_key(workspace_id: str) -> str: return f"usage:{workspace_id}" +def org_usage_cache_key(org_id: str) -> str: + return f"org_usage:{org_id}" + + async def invalidate_workspace_usage(workspace_id: str) -> None: """Bust the cached usage rollup for a workspace. Call on tier changes (upgrade/downgrade) so the next /usage read reflects new caps + rates immediately instead of waiting for TTL expiry.""" await cache_delete(usage_cache_key(workspace_id)) + + +async def invalidate_org_usage(org_id: str) -> None: + """Bust the cached team-wide rollup. Call alongside + invalidate_workspace_usage on tier changes since the aggregate + depends on per-workspace caps.""" + await cache_delete(org_usage_cache_key(org_id)) From c2c3fb1fa74f289fcc907e15ae8365009fda06bd Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 12:12:29 +0000 Subject: [PATCH 119/208] =?UTF-8?q?matrix=20=C2=A71:=20full=20tier=20capac?= =?UTF-8?q?ity=20matrix=20in-product=20(UpgradeModal)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matrix §1 contract: "the tier capacity matrix must be visible inside the product at minimum on the workspace billing tab and in the upgrade-request modal. Customers should never have to leave the app to understand what each tier gets them." Backend — GET /v2/workspaces/tier-capacities: - Returns the canonical list of TierCapacity records from dembrane/tier_capacity.py. Static per deploy; clients cache aggressively (1 hour staleTime in the frontend hook). - Public read. Pricing is already published; serving it here keeps every surface in the product on the same source of truth. Frontend — TierCapacityMatrix component: - Renders the matrix §1 table: price, duration, seats, seat overage, hours, hour overage, guests, training. - `fromTier` clips the table to tiers strictly above the caller's current — in the upgrade modal, the user sees only tiers they'd move to, not ones they've already passed. - `highlightTier` draws a Royal Blue top border + light fill on a specific column (the minimum tier the gate needs). - `compact` mode drops duration / seat-overage / training rows for modal widths. Wired into UpgradeModal (FeatureGate.tsx): replaces the prior pair of tier cards (current + required) with the full comparative matrix. Users now see every tier above their current in one read, with the target tier highlighted — matches the matrix-§11 "compare modal" design without a second page. Next surface: the billing tab when settings tab-split lands — same component, compact=false. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/workspace/FeatureGate.tsx | 40 +--- .../workspace/TierCapacityMatrix.tsx | 214 ++++++++++++++++++ echo/server/dembrane/api/v2/workspaces.py | 46 ++++ 3 files changed, 270 insertions(+), 30 deletions(-) create mode 100644 echo/frontend/src/components/workspace/TierCapacityMatrix.tsx diff --git a/echo/frontend/src/components/workspace/FeatureGate.tsx b/echo/frontend/src/components/workspace/FeatureGate.tsx index 9b2bad3c..9e045cac 100644 --- a/echo/frontend/src/components/workspace/FeatureGate.tsx +++ b/echo/frontend/src/components/workspace/FeatureGate.tsx @@ -12,6 +12,7 @@ import { import { IconLock } from "@tabler/icons-react"; import { useState, type ReactNode } from "react"; import { toast } from "@/components/common/Toaster"; +import { TierCapacityMatrix } from "@/components/workspace/TierCapacityMatrix"; import { API_BASE_URL } from "@/config"; import { emitFrozenFeatureAttempt } from "@/lib/frozenFeatureAttempt"; @@ -249,36 +250,15 @@ export function UpgradeModal({ {benefit} - - - - Your workspace is on - - - {TIER_LABEL[currentTier]} - - - - - This feature needs - - - {TIER_LABEL[requiredTier]} - - - + {/* Matrix §1 requires the full capacity matrix visible in- + product on the upgrade-request modal. fromTier clips the + table to tiers strictly above the current; highlightTier + calls out the minimum tier the gate needs. */} + {canRequestUpgrade ? ( <> diff --git a/echo/frontend/src/components/workspace/TierCapacityMatrix.tsx b/echo/frontend/src/components/workspace/TierCapacityMatrix.tsx new file mode 100644 index 00000000..bbb97fb0 --- /dev/null +++ b/echo/frontend/src/components/workspace/TierCapacityMatrix.tsx @@ -0,0 +1,214 @@ +import { Trans } from "@lingui/react/macro"; +import { Box, Paper, Stack, Table, Text } from "@mantine/core"; +import { useQuery } from "@tanstack/react-query"; +import { API_BASE_URL } from "@/config"; + +interface TierCapacity { + tier: string; + tagline: string; + price_eur_monthly: number | null; + price_note: string; + duration: string; + included_seats: number | null; + seat_overage_eur: number | null; + included_hours: number | null; + hour_overage_eur: number | null; + hard_block_on_hours: boolean; + guest_cap: number | null; + training_included: string; +} + +async function fetchTierCapacities(): Promise { + const res = await fetch(`${API_BASE_URL}/v2/workspaces/tier-capacities`, { + credentials: "include", + }); + if (!res.ok) return []; + return res.json(); +} + +function fmtEur(value: number | null): string { + if (value == null) return "—"; + return `€${value}`; +} + +function fmtPrice(cap: TierCapacity): string { + if (cap.price_eur_monthly == null) { + return cap.price_note; // e.g. "€349 one-time" + } + return `€${cap.price_eur_monthly}/mo`; +} + +function fmtSeats(cap: TierCapacity): string { + if (cap.included_seats == null) return "∞"; + return String(cap.included_seats); +} + +function fmtSeatOverage(cap: TierCapacity): string { + if (cap.seat_overage_eur == null) return "—"; + return `+${fmtEur(cap.seat_overage_eur)}/seat`; +} + +function fmtHours(cap: TierCapacity): string { + if (cap.included_hours == null) return "∞"; + return String(cap.included_hours); +} + +function fmtHourOverage(cap: TierCapacity): string { + if (cap.hard_block_on_hours) return "hard block"; + if (cap.hour_overage_eur == null) return "—"; + return `${fmtEur(cap.hour_overage_eur)}/h`; +} + +function fmtGuests(cap: TierCapacity): string { + if (cap.guest_cap == null) return "∞"; + return String(cap.guest_cap); +} + +interface Props { + /** Optional: highlight one tier column (e.g. the workspace's current + * tier). */ + highlightTier?: string; + /** Optional: cap the rendered tiers to those at or above this one. + * Useful in the upgrade modal where tiers below the current aren't + * relevant. */ + fromTier?: string; + /** Compact mode drops the long rows (overage, training) to fit narrow + * modals. Default false. */ + compact?: boolean; +} + +const TIER_ORDER = [ + "pilot", + "pioneer", + "innovator", + "changemaker", + "guardian", +]; + +/** + * Matrix v1.1 §1 — tier × capacity table rendered in-product. + * + * Contract: "the tier capacity matrix must be visible inside the product + * at minimum on the workspace billing tab and in the upgrade-request + * modal. Customers should never have to leave the app to understand what + * each tier gets them." + * + * Two render surfaces today: UpgradeModal (compact=true, fromTier set to + * the caller's current) and — when the settings tab split lands — the + * billing tab (compact=false, full history). + */ +export const TierCapacityMatrix = ({ + highlightTier, + fromTier, + compact = false, +}: Props) => { + const { data, isLoading } = useQuery({ + queryKey: ["v2", "tier-capacities"], + queryFn: fetchTierCapacities, + // Static per deploy — cache aggressively. + staleTime: 60 * 60 * 1000, + }); + + if (isLoading || !data || data.length === 0) return null; + + const fromIdx = fromTier ? TIER_ORDER.indexOf(fromTier) : -1; + const tiers = data.filter((cap) => { + if (fromIdx < 0) return true; + const idx = TIER_ORDER.indexOf(cap.tier); + return idx >= 0 && idx > fromIdx; // strictly above current + }); + if (tiers.length === 0) return null; + + const rows = compact + ? ([ + { label: "Price", render: fmtPrice }, + { label: "Seats", render: fmtSeats }, + { label: "Hours", render: fmtHours }, + { label: "Hour overage", render: fmtHourOverage }, + { label: "Guests", render: fmtGuests }, + ] as const) + : ([ + { label: "Price", render: fmtPrice }, + { label: "Duration", render: (c: TierCapacity) => c.duration }, + { label: "Seats", render: fmtSeats }, + { label: "Seat overage", render: fmtSeatOverage }, + { label: "Hours", render: fmtHours }, + { label: "Hour overage", render: fmtHourOverage }, + { label: "Guests", render: fmtGuests }, + { + label: "Training", + render: (c: TierCapacity) => c.training_included, + }, + ] as const); + + return ( + + + + + + {tiers.map((cap) => { + const isHighlight = cap.tier === highlightTier; + return ( + + + + {cap.tier} + + + — {cap.tagline} + + + + ); + })} + + + + {rows.map((row) => ( + + + + {row.label} + + + {tiers.map((cap) => ( + + {row.render(cap)} + + ))} + + ))} + +
    + {compact && ( + + + ∞ = unlimited subject to plan + + + )} +
    + ); +}; diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index b02e07a3..5bc7f7f0 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -824,6 +824,52 @@ class WorkspaceUsageResponse(BaseModel): next_tier: Optional[NextTierRecommendation] = None +class TierCapacityItem(BaseModel): + tier: str + tagline: str + price_eur_monthly: Optional[int] + price_note: str + duration: str + included_seats: Optional[int] + seat_overage_eur: Optional[int] + included_hours: Optional[int] + hour_overage_eur: Optional[int] + hard_block_on_hours: bool + guest_cap: Optional[int] + training_included: str + + +@router.get("/tier-capacities", response_model=list[TierCapacityItem]) +async def list_tier_capacities() -> list[TierCapacityItem]: + """The canonical tier × capacity matrix (matrix §1). + + Static per deployment — clients can cache indefinitely. Authentication + is not required: this data is public pricing info + lives on the + product's own pricing page anyway. Served here so every in-product + surface (upgrade modal, billing tab, pricing comparison) reads from + a single source. + """ + from dembrane.tier_capacity import TIER_CAPACITIES + + return [ + TierCapacityItem( + tier=cap.tier, + tagline=cap.tagline, + price_eur_monthly=cap.price_eur_monthly, + price_note=cap.price_note, + duration=cap.duration, + included_seats=cap.included_seats, + seat_overage_eur=cap.seat_overage_eur, + included_hours=cap.included_hours, + hour_overage_eur=cap.hour_overage_eur, + hard_block_on_hours=cap.hard_block_on_hours, + guest_cap=cap.guest_cap, + training_included=cap.training_included, + ) + for cap in TIER_CAPACITIES.values() + ] + + @router.get( "/{workspace_id}/usage", response_model=WorkspaceUsageResponse, From 963fb0b4b018efc2c2bcc4ff99ccad7867a95dc5 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 12:16:27 +0000 Subject: [PATCH 120/208] HCD tier-3: admin chips + Private tier-gate + no dead-clicks on /t/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes three HCD concerns that pair well as small copy/gate fixes. 1. Admin chips on the member upgrade-modal path (HCD concern #13). FeatureGate's member view previously told users "find your admins in the team members list" — abstract, friction. New TeamAdminChips sub- component fetches /v2/orgs/:id/members, filters to admin+owner, and renders 1-3 avatars + names inline under the modal. Silent fallback to the generic message if the fetch fails. 2. Private radio tier-gate (HCD concern #11 / matrix §2). - WorkspaceSettingsRoute: Private option disabled when current tier is below innovator AND the workspace isn't already private. If the workspace IS already private (matrix §3 freeze: existing stays private after downgrade), the radio stays enabled so admins can toggle back to Open without blockers. - CreateWorkspaceRoute: Private Select option permanently disabled (new workspaces start at Pioneer) with inline copy explaining why and pointing at the upgrade path. Previously a Pioneer admin could click Private and hit a 403 with no hint as to why. 3. Workspace-column dead-clicks on /t/ matrix (HCD concern #9). Non-admin callers previously had column headers as clickable anchors that 403'd on click (matrix §6: team members don't auto-have access). Admins keep the anchor. Members see plain dimmed text. Their path to discover + request access lives on the home selector via DiscoverableWorkspaces (already shipped in f1b7e30). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/workspace/FeatureGate.tsx | 99 +++++++++++++++++-- echo/frontend/src/routes/team/TeamRoute.tsx | 43 +++++--- .../workspaces/CreateWorkspaceRoute.tsx | 10 +- .../workspaces/WorkspaceSettingsRoute.tsx | 65 ++++++++---- 4 files changed, 178 insertions(+), 39 deletions(-) diff --git a/echo/frontend/src/components/workspace/FeatureGate.tsx b/echo/frontend/src/components/workspace/FeatureGate.tsx index 9e045cac..bf7403d4 100644 --- a/echo/frontend/src/components/workspace/FeatureGate.tsx +++ b/echo/frontend/src/components/workspace/FeatureGate.tsx @@ -1,20 +1,26 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { + Avatar, Badge, Box, Button, + Group, Modal, Stack, Text, Textarea, + Tooltip, } from "@mantine/core"; import { IconLock } from "@tabler/icons-react"; +import { useQuery } from "@tanstack/react-query"; import { useState, type ReactNode } from "react"; import { toast } from "@/components/common/Toaster"; import { TierCapacityMatrix } from "@/components/workspace/TierCapacityMatrix"; import { API_BASE_URL } from "@/config"; +import { useWorkspace } from "@/hooks/useWorkspace"; import { emitFrozenFeatureAttempt } from "@/lib/frozenFeatureAttempt"; +import { avatarUrl } from "@/lib/avatar"; /** * Tier-gating UI primitives for the ECHO platform. @@ -188,6 +194,92 @@ interface UpgradeModalProps { * there's no button — Q3 decision (D9). Keeping the message honest: * there's nothing we can do for them, only their admin can. */ +interface TeamAdminRow { + user_id: string; + display_name: string | null; + avatar: string | null; + role: string; +} + +/** + * Renders the matrix §11 member-path copy with actual admin faces + + * names so "ask a team admin" is concrete, not abstract. + * + * Silent fallback to the generic message if the org lookup fails — no + * broken state. + */ +function TeamAdminChips() { + const { workspace } = useWorkspace(); + const orgId = workspace?.org_id; + + const { data } = useQuery({ + queryKey: ["v2", "team-admins", orgId], + queryFn: async (): Promise => { + if (!orgId) return []; + const res = await fetch(`${API_BASE_URL}/v2/orgs/${orgId}/members`, { + credentials: "include", + }); + if (!res.ok) return []; + const rows = (await res.json()) as TeamAdminRow[]; + return Array.isArray(rows) + ? rows.filter((r) => r.role === "admin" || r.role === "owner") + : []; + }, + enabled: Boolean(orgId), + staleTime: 5 * 60 * 1000, + }); + + const admins = data ?? []; + if (admins.length === 0) { + // Fallback — generic message when we can't resolve the admin list. + return ( + + + A team admin can request this upgrade. Ask someone with the admin + role. + + + ); + } + + const firstThree = admins.slice(0, 3); + const names = firstThree + .map((a) => a.display_name || t`a team admin`) + .join(", "); + const more = + admins.length > firstThree.length + ? ` +${admins.length - firstThree.length}` + : ""; + + return ( + + + Ask a team admin to request this upgrade. + + + + {firstThree.map((a) => ( + + + + ))} + + + {names} + {more} + + + + ); +} + + export function UpgradeModal({ opened, onClose, @@ -279,12 +371,7 @@ export function UpgradeModal({ ) : ( - - - A team admin can request this upgrade. You can find your admins - in the team members list. - - + )} {/* Role-aware footer: admin gets primary, member gets close-only (D9) */} diff --git a/echo/frontend/src/routes/team/TeamRoute.tsx b/echo/frontend/src/routes/team/TeamRoute.tsx index d2746bd8..e54e08aa 100644 --- a/echo/frontend/src/routes/team/TeamRoute.tsx +++ b/echo/frontend/src/routes/team/TeamRoute.tsx @@ -309,21 +309,38 @@ export const TeamRoute = () => { /> )} - { - e.preventDefault(); - navigate(`/w/${ws.id}/projects`); - }} - style={{ cursor: "pointer" }} - > - + {isAdmin ? ( + { + e.preventDefault(); + navigate(`/w/${ws.id}/projects`); + }} + style={{ cursor: "pointer" }} + > + + {ws.name} + + + ) : ( + /* Matrix §6: team members don't auto-have access + to every team workspace. Linking non-admins to + /w/:id/projects would 403 for most. Plain text + instead; the discovery + Request-access path + lives on the home selector. */ + {ws.name} - + )}
    diff --git a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx index 603dfc05..8077f740 100644 --- a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx +++ b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx @@ -206,9 +206,14 @@ export const CreateWorkspaceRoute = () => { /> )} + {/* Matrix §2: Private workspaces are innovator+. New + workspaces always start on pioneer, so Private is + gated on fresh creates. Disabled option + inline + copy tells the user why, rather than letting them + click it and hit a cryptic 403. */} Date: Thu, 23 Apr 2026 12:22:30 +0000 Subject: [PATCH 121/208] =?UTF-8?q?workspace=20settings=20=E2=80=94=20Over?= =?UTF-8?q?view=20/=20Billing=20tab=20split?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundational HCD fix: 900+ line settings page now has tab structure so role-scoped landing works. Tabs (visible for non-guests): - Overview: Privacy & defaults (admin-only) + Members + Pending invites + Access requests + Your access + Leave workspace. - Billing: UsageCard + full TierCapacityMatrix (non-compact, with current tier highlighted). Admin + billing additionally see a note pointing at upgrades@dembrane.com for invoices/payment until those surfaces land (matrix defers self-serve billing to v2). URL-driven state via ?tab= so links are shareable + reload-stable. Role-based default: - Workspace Billing with no management rights → Billing tab. - Admin / Member → Overview. Guest bypasses tabs entirely — single minimal view with their role badge + Leave workspace button. Matrix §4: guests shouldn't see usage, privacy, invites, or access requests, so hiding tabs is cleaner than showing empty tabs. Downstream updates (now that the tab exists): - PilotBlockModal "Go to workspace settings" → "Go to billing" (routes to ?tab=billing). - DowngradeBanner "Learn more" routes to ?tab=billing. Matrix §1 fully honored now — full capacity matrix visible on both the upgrade modal (compact, fromTier) and the billing tab (non-compact, highlightTier). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/workspace/DowngradeBanner.tsx | 2 +- .../components/workspace/PilotBlockModal.tsx | 4 +- .../workspaces/WorkspaceSettingsRoute.tsx | 163 +++++++++++++++--- 3 files changed, 143 insertions(+), 26 deletions(-) diff --git a/echo/frontend/src/components/workspace/DowngradeBanner.tsx b/echo/frontend/src/components/workspace/DowngradeBanner.tsx index 82cfe107..2155c801 100644 --- a/echo/frontend/src/components/workspace/DowngradeBanner.tsx +++ b/echo/frontend/src/components/workspace/DowngradeBanner.tsx @@ -92,7 +92,7 @@ export const DowngradeBanner = () => { component="button" type="button" size="sm" - onClick={() => navigate(`/w/${workspaceId}/settings`)} + onClick={() => navigate(`/w/${workspaceId}/settings?tab=billing`)} > Learn more diff --git a/echo/frontend/src/components/workspace/PilotBlockModal.tsx b/echo/frontend/src/components/workspace/PilotBlockModal.tsx index 18546824..d2edc5c4 100644 --- a/echo/frontend/src/components/workspace/PilotBlockModal.tsx +++ b/echo/frontend/src/components/workspace/PilotBlockModal.tsx @@ -53,11 +53,11 @@ export const PilotBlockModal = () => { onClick={() => { clear(); if (targetId) { - navigate(`/w/${targetId}/settings`); + navigate(`/w/${targetId}/settings?tab=billing`); } }} > - Go to workspace settings + Go to billing
    diff --git a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx index 201354b0..9c20caf6 100644 --- a/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx +++ b/echo/frontend/src/routes/workspaces/WorkspaceSettingsRoute.tsx @@ -15,6 +15,7 @@ import { Radio, Select, Stack, + Tabs, Text, TextInput, Title, @@ -25,10 +26,11 @@ import { useDisclosure, useDocumentTitle } from "@mantine/hooks"; import { IconPlus, IconRefresh, IconTrash, IconX } from "@tabler/icons-react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; -import { useParams } from "react-router"; +import { useParams, useSearchParams } from "react-router"; import { toast } from "@/components/common/Toaster"; import { AccessRequestsList } from "@/components/workspace/AccessRequestsList"; import { TierBadge } from "@/components/workspace/TierBadge"; +import { TierCapacityMatrix } from "@/components/workspace/TierCapacityMatrix"; import { UsageCard } from "@/components/workspace/UsageCard"; import { API_BASE_URL, DIRECTUS_PUBLIC_URL } from "@/config"; import { useI18nNavigate } from "@/hooks/useI18nNavigate"; @@ -309,6 +311,31 @@ export const WorkspaceSettingsRoute = () => { const myRole = settings.my_role; const myAppUserId = meV2?.id ?? null; const canEditSettings = settings.my_policies?.includes("settings:manage") ?? false; + const seesFinancials = + settings.my_policies?.includes("workspace:view_invoices") ?? false; + + // Tab state — URL-driven for shareable + reload-stable links. Role- + // based default lands each role on the tab most useful to them: + // - Billing role → billing (finance view is their whole job here). + // - Member → overview (members list is their main read). + // - Admin → overview (same; admin manages members here too). + // - Guest → no tabs at all; bypass below. + const [searchParams, setSearchParams] = useSearchParams(); + const tabParam = searchParams.get("tab"); + const defaultTab = + myRole === "billing" && !canManage ? "billing" : "overview"; + const allowedTabs = ["overview", "billing"] as const; + type TabValue = (typeof allowedTabs)[number]; + const activeTab: TabValue = + tabParam && (allowedTabs as readonly string[]).includes(tabParam) + ? (tabParam as TabValue) + : (defaultTab as TabValue); + const setActiveTab = (value: string | null) => { + if (!value) return; + const next = new URLSearchParams(searchParams); + next.set("tab", value); + setSearchParams(next, { replace: true }); + }; return ( <> @@ -375,30 +402,67 @@ export const WorkspaceSettingsRoute = () => { - {/* Privacy + defaults — admin-only. Hidden entirely for non- - admin roles (HCD audit 2026-04-23): disabled fields read - as "broken" to members / billing / guests who can't edit - them. Show nothing rather than disabled state. */} - {canEditSettings && ( - <> - - - - )} - - {/* Usage + financial rollup (matrix §8). Role-aware rendering - lives inside the card — members see raw; admin/billing see - overage forecast + next-tier recommendation. Guests are - excluded entirely (matrix §4 "View usage & overage" ✗). */} - {workspaceId && !iAmGuest && } + {/* Guests bypass the tab structure — they have one workspace + and nothing to navigate. Tabs come next for everyone else. */} + {!iAmGuest && ( + + + + Overview + + + Billing + + - {!iAmGuest && } + + + {workspaceId && } + {/* Matrix §1 full capacity matrix on the billing tab. + Non-compact: price / duration / seats / overage / + hours / guests / training. Highlights the current + tier so admins can see what they have vs what's + next. */} + + {seesFinancials && ( + + + Invoices and payment method will land here in a + future release. Request an upgrade above or email{" "} + + upgrades@dembrane.com + {" "} + for now. + + + )} + + - {/* Members */} + + + {/* Privacy + defaults — admin-only. Hidden entirely + for non-admin roles (HCD audit 2026-04-23): + disabled fields read as "broken" to members + + billing. */} + {canEditSettings && ( + + )} + {canEditSettings && } @@ -729,6 +793,59 @@ export const WorkspaceSettingsRoute = () => { })()} </Group> </Stack> + </Stack> + </Tabs.Panel> + </Tabs> + )} + + {/* Guest view — minimal, no tabs. They can see their own + access block + leave affordance, nothing else. */} + {iAmGuest && ( + <Stack gap={12}> + <Title order={5} fw={400}> + <Trans>Your access</Trans> + + + + Guest + + {(() => { + const myMembership = settings.members.find( + (m) => m.user_id === myAppUserId, + ); + if (!myMembership) return null; + return ( + + ); + })()} + + + )} From bdd16dd477bcdc4c1828fee0717f59dea48687b0 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 12:56:58 +0000 Subject: [PATCH 122/208] fix: seat count double-counted when a user has duplicate memberships MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while debugging admin@dembrane.com's usage. Pre-walkback data can carry BOTH a source='direct' and a legacy source='inherited' row for the same (workspace, user). The seat counter looped through all rows and counted each — two rows for one human = two seats. Matrix §7 is explicit: "one seat per person per workspace". We need to dedupe by (workspace_id, user_id) before counting. Fix: - /v2/workspaces/:id/usage — dedupe members by user_id before seat counting. Prefer source='direct' over 'inherited' (matches inheritance.get_effective_members's dedup rule); tie-break on seat-worthy role. - /v2/orgs/:id/usage — same dedupe by (workspace_id, user_id) across the team aggregate. Example from dev: - Workspace 1fd78488 had f5f6d608 with both (admin/direct) and (owner/inherited) → seat_count = 2. After dedup: 1. Legacy source='inherited' rows have now been archived via scripts/migrate_inherited_to_derived.py --apply (dev). Invariant #5 restored. The dedup fix stays as a defense-in-depth against any future data drift. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/server/dembrane/api/v2/orgs.py | 32 ++++++++++++++++++-- echo/server/dembrane/api/v2/workspaces.py | 37 ++++++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index 5f7792b3..4d1aae3b 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -1007,7 +1007,10 @@ async def get_org_usage( + (int(c.get("duration") or 0) / 3600.0) ) - # Memberships — one read, classify by is_external. + # Memberships — dedupe by (workspace_id, user_id). Pre-walkback data + # can carry both a direct and an inherited row for the same pair + # (matrix §7: "one seat per person per workspace"). Row-count alone + # would double-bill the same human. total_seat_count = 0 total_guest_count = 0 if ws_ids: @@ -1019,16 +1022,39 @@ async def get_org_usage( "workspace_id": {"_in": ws_ids}, "deleted_at": {"_null": True}, }, - "fields": ["role", "is_external"], + "fields": ["workspace_id", "user_id", "role", "source", "is_external"], "limit": -1, } }, ) or [] if isinstance(memberships, list): + # Dedupe: prefer direct over inherited, then seat-worthy over not. + by_pair: dict[tuple[str, str], dict] = {} + seat_roles = {"owner", "admin", "member", "billing"} for m in memberships: + wid = m.get("workspace_id") + uid = m.get("user_id") + if not wid or not uid: + continue + key = (wid, uid) + existing = by_pair.get(key) + if existing is None: + by_pair[key] = m + continue + existing_direct = existing.get("source") == "direct" + this_direct = m.get("source") == "direct" + if this_direct and not existing_direct: + by_pair[key] = m + elif this_direct == existing_direct: + if ( + m.get("role") in seat_roles + and existing.get("role") not in seat_roles + ): + by_pair[key] = m + for m in by_pair.values(): if m.get("is_external"): total_guest_count += 1 - elif m.get("role") in ("admin", "owner", "member", "billing"): + elif m.get("role") in seat_roles: total_seat_count += 1 # Per-tier aggregation for cap counts + forecast. diff --git a/echo/server/dembrane/api/v2/workspaces.py b/echo/server/dembrane/api/v2/workspaces.py index 5bc7f7f0..78c651d3 100644 --- a/echo/server/dembrane/api/v2/workspaces.py +++ b/echo/server/dembrane/api/v2/workspaces.py @@ -1022,9 +1022,44 @@ async def get_workspace_usage( if not isinstance(members, list): members = [] + # Seat + guest count — deduplicated by user_id. Matrix §7 says "one + # seat per person per workspace", so we count distinct users, not + # distinct rows. Pre-walkback data can carry both a source='direct' + # row and a legacy source='inherited' row for the same (workspace, + # user) pair, and a naive row-count would double-bill the same + # human. + # + # Direct rows take priority over inherited (matches inheritance.py + # get_effective_members: a user's direct role supersedes their + # derived one). is_external is read from the winning row. + by_user: dict[str, dict] = {} + for m in members: + uid = m.get("user_id") + if not uid: + continue + # Skip rows with no role or with a retired role value that + # wouldn't count either way. + role = m.get("role") + existing = by_user.get(uid) + if existing is None: + by_user[uid] = m + continue + # Prefer direct over inherited. If both are direct (shouldn't + # happen, but guard), keep the one with a seat-worthy role over + # a non-seat one. + existing_direct = existing.get("source") == "direct" + this_direct = m.get("source") == "direct" + if this_direct and not existing_direct: + by_user[uid] = m + elif this_direct and existing_direct: + # Double-direct: prefer the seat-worthy role. + seat_roles = {"owner", "admin", "member", "billing"} + if role in seat_roles and existing.get("role") not in seat_roles: + by_user[uid] = m + seat_count = 0 guest_count = 0 - for m in members: + for m in by_user.values(): role = m.get("role") if m.get("is_external"): # Guest bucket. A guest with an elevated role (admin/billing/ From 5ec8cd4a9e67e4630d37449ced6a4cfcaec46b8d Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 13:11:50 +0000 Subject: [PATCH 123/208] fix: team matrix hid direct workspace memberships for non-admin members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While debugging the "seat count shows 5 but I only see 3 cells of admin" report: the 5-seat count was actually correct. The matrix view was wrong. Reality on dev: Sameer's team has 3 people, one of whom is the team owner (Sameer) with access to all 3 workspaces (=3 seats). The other two team members had DIRECT workspace memberships the matrix wasn't showing — Sameer Pashikanti as owner on Default, john doe as member on claude (= 2 more seats, total 5). Root cause: /v2/orgs/:id/members only returned each user's team role plus a rolled-up workspace count. The matrix cell computation inferred access purely from derivation (team owner → admin everywhere, team admin → admin on open workspaces). Direct workspace_membership rows for non-admin team members were invisible. Fix: - Add direct_workspace_roles: dict[workspace_id, role] to OrgMemberResponse. One batched DB read per team-page load — keyed by (workspace_id, user_id) with direct > inherited dedup, same as the seat counter. - TeamRoute matrix cells: prefer the direct role when present, fall back to derivation. Color-code by role (admin/owner = blue, billing = yellow, member = gray). Tooltip distinguishes direct vs inherited origin. Post-walkback derivation retires; the backend fallback branch can go away and direct rows become the sole source. Co-Authored-By: Claude Opus 4.7 (1M context) --- echo/frontend/src/routes/team/TeamRoute.tsx | 50 ++++++++++--- echo/server/dembrane/api/v2/orgs.py | 81 +++++++++++++++++++++ 2 files changed, 119 insertions(+), 12 deletions(-) diff --git a/echo/frontend/src/routes/team/TeamRoute.tsx b/echo/frontend/src/routes/team/TeamRoute.tsx index e54e08aa..80a949a5 100644 --- a/echo/frontend/src/routes/team/TeamRoute.tsx +++ b/echo/frontend/src/routes/team/TeamRoute.tsx @@ -66,6 +66,8 @@ interface TeamMember { role: string; accessible_workspace_count: number; is_pending: boolean; + // workspace_id → role for direct memberships (not derived). + direct_workspace_roles?: Record; } interface TeamWorkspace { @@ -403,27 +405,51 @@ export const TeamRoute = () => { {workspaces.map((ws) => { - // Derived access: team owner always has access - // everywhere (carve-out). Team admin has access to - // non-private workspaces. Precise per-cell role - // (direct vs inherited) would require an extra - // fetch — for this first-pass matrix we show - // coarse access only. + // Direct membership takes priority. Backend returns + // direct_workspace_roles = {workspace_id: role} + // dedup'd against legacy inherited rows. + const directRole = + m.direct_workspace_roles?.[ws.id]; + // Derivation fallback: team owner has admin + // everywhere; team admin has admin on non-private + // workspaces. Post-walkback this derivation + // retires — direct rows will be the sole source. const derivedAdmin = - m.role === "owner" || - (m.role === "admin" && !ws.is_private); + !directRole && + (m.role === "owner" || + (m.role === "admin" && !ws.is_private)); + const displayRole = directRole + ? directRole + : derivedAdmin + ? "admin" + : null; return ( - {derivedAdmin ? ( + {displayRole ? ( - - admin + + {displayRole} ) : ws.is_private && m.role === "admin" ? ( diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index 4d1aae3b..ae03ab05 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -82,6 +82,11 @@ class OrgMemberResponse(BaseModel): # but we give the team-admin page a rolled-up count for the list view. accessible_workspace_count: int = 0 is_pending: bool = False # placeholder — will cover pending org invites later + # Direct workspace memberships: workspace_id → role. Powers the team + # admin matrix page so non-admin team members with a direct invite + # on a specific workspace aren't hidden. Includes is_external=true + # rows (guests) — frontend decides how to display. + direct_workspace_roles: dict[str, str] = {} class OrgDetailResponse(BaseModel): @@ -420,6 +425,13 @@ async def list_org_members( # with inherit_team_members=true plus any direct memberships. workspace_counts = await _rollup_workspace_access(org_id, user_ids) + # Direct workspace roles per team user — keyed {user_id: {workspace_id: role}}. + # Powers the matrix page so direct-invited members on specific + # workspaces show correctly (they were hidden before when the + # matrix relied on derivation alone). Dedup'd by (workspace_id, + # user_id) with direct-over-inherited priority. + direct_roles = await _direct_workspace_roles_by_user(org_id, user_ids) + out: list[OrgMemberResponse] = [] for m in memberships: uid = m["user_id"] @@ -436,11 +448,80 @@ async def list_org_members( avatar=avatar_map.get(du_id) if du_id else None, role=m.get("role", "member"), accessible_workspace_count=workspace_counts.get(uid, 0), + direct_workspace_roles=direct_roles.get(uid, {}), ) ) return out +async def _direct_workspace_roles_by_user( + org_id: str, user_ids: list[str] +) -> dict[str, dict[str, str]]: + """Return {user_id: {workspace_id: role}} for direct memberships this + team's users hold on any workspace in this team. + + One DB call for the whole team page. Dedup'd (workspace_id, user_id) + since pre-walkback data can carry inherited+direct rows for the + same pair (matrix §7 one seat per person per workspace — see + get_workspace_usage for the same dedup logic). + """ + if not user_ids: + return {} + + ws_rows = await async_directus.get_items( + "workspace", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id"], + "limit": -1, + } + }, + ) or [] + ws_ids = [w["id"] for w in (ws_rows if isinstance(ws_rows, list) else []) if w.get("id")] + if not ws_ids: + return {} + + mem_rows = await async_directus.get_items( + "workspace_membership", + { + "query": { + "filter": { + "workspace_id": {"_in": ws_ids}, + "user_id": {"_in": user_ids}, + "deleted_at": {"_null": True}, + }, + "fields": ["workspace_id", "user_id", "role", "source"], + "limit": -1, + } + }, + ) or [] + if not isinstance(mem_rows, list): + return {} + + # Dedup (workspace_id, user_id): direct > inherited. + by_pair: dict[tuple[str, str], dict] = {} + for m in mem_rows: + wid = m.get("workspace_id") + uid = m.get("user_id") + if not wid or not uid: + continue + key = (wid, uid) + existing = by_pair.get(key) + if existing is None or ( + m.get("source") == "direct" and existing.get("source") != "direct" + ): + by_pair[key] = m + + out: dict[str, dict[str, str]] = {} + for (wid, uid), row in by_pair.items(): + out.setdefault(uid, {})[wid] = row.get("role", "member") + return out + + async def _rollup_workspace_access( org_id: str, user_ids: list[str] ) -> dict[str, int]: From 1ff2d5a5c987c638de7c8f930d78de759e936885 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 13:21:24 +0000 Subject: [PATCH 124/208] team usage: per-workspace breakdown with at/approaching-cap rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Team admins + billing need "which workspaces are hot?" — the aggregate tells them there's 5 seats and €0 overage but not where to look. Backend — /v2/orgs/:id/usage extended: - OrgUsageResponse.workspaces: list[OrgUsageWorkspaceRow] with id/name/tier/audio_hours/hours_included/hours_pct/at_cap/ approaching_cap/overage_forecast_eur. - Rows sorted: at-cap first, then approaching, then by hours desc. Team admins reading top-to-bottom hit the hot workspaces in the first few rows. - Members get workspaces[] with overage_forecast_eur stripped (matrix §8: members see raw hours, no €). - Cached with the aggregate in the same 30-min key. Frontend — TeamUsageRollup: - Collapsible "Per-workspace breakdown" section below the aggregate. Default collapsed so the rollup stays a glance-sized summary. - Table: workspace (with red ! or yellow · prefix when at/approaching), tier, hours with progress bar, overage (admin/billing only). - Click-through: clicking a workspace row goes to that workspace's billing tab. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/workspace/TeamUsageRollup.tsx | 144 +++++++++++++++++- echo/server/dembrane/api/v2/orgs.py | 82 ++++++++-- 2 files changed, 213 insertions(+), 13 deletions(-) diff --git a/echo/frontend/src/components/workspace/TeamUsageRollup.tsx b/echo/frontend/src/components/workspace/TeamUsageRollup.tsx index 8d8845eb..887e6a55 100644 --- a/echo/frontend/src/components/workspace/TeamUsageRollup.tsx +++ b/echo/frontend/src/components/workspace/TeamUsageRollup.tsx @@ -5,15 +5,31 @@ import { Badge, Group, Paper, + Progress, Stack, + Table, Text, Tooltip, + UnstyledButton, } from "@mantine/core"; -import { IconRefresh } from "@tabler/icons-react"; +import { IconChevronDown, IconChevronRight, IconRefresh } from "@tabler/icons-react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { API_BASE_URL } from "@/config"; +interface OrgUsageWorkspaceRow { + id: string; + name: string; + tier: string; + audio_hours: number; + hours_included: number | null; + hours_pct: number | null; + at_cap: boolean; + approaching_cap: boolean; + overage_forecast_eur: number | null; +} + interface OrgUsage { cycle_start: string; cycle_end_exclusive: string; @@ -24,6 +40,7 @@ interface OrgUsage { total_project_count: number; workspaces_at_cap: number; workspaces_approaching_cap: number; + workspaces: OrgUsageWorkspaceRow[]; total_overage_forecast_eur: number | null; } @@ -60,7 +77,9 @@ function formatEur(value: number | null | undefined): string { */ export const TeamUsageRollup = ({ orgId }: { orgId: string }) => { const queryClient = useQueryClient(); + const navigate = useI18nNavigate(); const [refreshing, setRefreshing] = useState(false); + const [expanded, setExpanded] = useState(false); const { data, isLoading } = useQuery({ queryKey: ["v2", "org-usage", orgId], @@ -170,6 +189,129 @@ export const TeamUsageRollup = ({ orgId }: { orgId: string }) => { )} )} + + {data.workspaces.length > 0 && ( + + setExpanded((v) => !v)} + style={{ display: "inline-flex", alignItems: "center", gap: 4 }} + > + {expanded ? ( + + ) : ( + + )} + + Per-workspace breakdown + + + {expanded && ( + + + + + + Workspace + + + + + Tier + + + + + Hours + + + {data.total_overage_forecast_eur != null && ( + + + Overage + + + )} + + + + {data.workspaces.map((ws) => ( + + navigate(`/w/${ws.id}/settings?tab=billing`) + } + > + + + {ws.at_cap && ( + + + ! + + + )} + {!ws.at_cap && ws.approaching_cap && ( + + + · + + + )} + + {ws.name} + + + + + + {ws.tier} + + + + + + {ws.audio_hours.toFixed(1)} + {ws.hours_included != null && ( + + {" / "} + {ws.hours_included} + + )} + + {ws.hours_pct != null && ( + + )} + + + {data.total_overage_forecast_eur != null && ( + + + {formatEur(ws.overage_forecast_eur)} + + + )} + + ))} + +
    + )} +
    + )} ); diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index ae03ab05..83eccae9 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -942,6 +942,19 @@ async def remove_team_member( # ──────────────────────────────────────────────────────────────────── +class OrgUsageWorkspaceRow(BaseModel): + id: str + name: str + tier: str + audio_hours: float + hours_included: Optional[int] # None = unlimited tier + hours_pct: Optional[float] # 0..1 progress; None when unlimited + at_cap: bool # Pilot + at/over cap + approaching_cap: bool # >=80% on capped tiers + # Admin / billing only: + overage_forecast_eur: Optional[float] = None + + class OrgUsageResponse(BaseModel): cycle_start: str cycle_end_exclusive: str @@ -952,6 +965,7 @@ class OrgUsageResponse(BaseModel): total_project_count: int workspaces_at_cap: int # Pilot + over cap (hard block active) workspaces_approaching_cap: int # tier with cap, usage >= 80% + workspaces: list[OrgUsageWorkspaceRow] = [] # Admin / billing only (null for plain members): total_overage_forecast_eur: Optional[float] = None @@ -1024,7 +1038,7 @@ async def get_org_usage( "org_id": {"_eq": org_id}, "deleted_at": {"_null": True}, }, - "fields": ["id", "tier"], + "fields": ["id", "name", "tier"], "limit": -1, } }, @@ -1138,25 +1152,59 @@ async def get_org_usage( elif m.get("role") in seat_roles: total_seat_count += 1 - # Per-tier aggregation for cap counts + forecast. + # Per-workspace rows + aggregation. We build the rows even when the + # caller doesn't see financials — the €-field is stripped below. total_hours = 0.0 at_cap = 0 approaching = 0 forecast = 0.0 + ws_rows: list[dict] = [] for w in workspaces: - wid = w.get("id") + wid = w.get("id") or "" + name = w.get("name") or "" tier = w.get("tier") or "" hours = per_ws_hours.get(wid, 0.0) if wid else 0.0 total_hours += hours + cap = get_capacity(tier) - if cap is None or cap.included_hours is None: - continue - pct = (hours / cap.included_hours) if cap.included_hours else 0.0 - if cap.hard_block_on_hours and hours >= cap.included_hours: - at_cap += 1 - elif pct >= 0.8: - approaching += 1 - forecast += compute_hour_overage_eur(tier, hours) + hours_included: Optional[int] = cap.included_hours if cap else None + hours_pct: Optional[float] = None + ws_at_cap = False + ws_approaching = False + ws_forecast_eur = 0.0 + + if cap and cap.included_hours is not None: + pct = hours / cap.included_hours if cap.included_hours else 0.0 + hours_pct = round(pct, 3) + if cap.hard_block_on_hours and hours >= cap.included_hours: + at_cap += 1 + ws_at_cap = True + elif pct >= 0.8: + approaching += 1 + ws_approaching = True + ws_forecast_eur = compute_hour_overage_eur(tier, hours) + forecast += ws_forecast_eur + + ws_rows.append({ + "id": wid, + "name": name, + "tier": tier, + "audio_hours": round(hours, 2), + "hours_included": hours_included, + "hours_pct": hours_pct, + "at_cap": ws_at_cap, + "approaching_cap": ws_approaching, + "overage_forecast_eur": round(ws_forecast_eur, 2), + }) + + # Sort: at-cap first, then approaching, then by hours desc — Team admins + # reading top-to-bottom hit the hot workspaces immediately. + ws_rows.sort( + key=lambda r: ( + 0 if r["at_cap"] else 1 if r["approaching_cap"] else 2, + -r["audio_hours"], + ) + ) payload = { "cycle_start": cycle_start, @@ -1168,12 +1216,22 @@ async def get_org_usage( "total_project_count": project_count, "workspaces_at_cap": at_cap, "workspaces_approaching_cap": approaching, + "workspaces": ws_rows, "total_overage_forecast_eur": round(forecast, 2), } await cache_set_json(cache_key, payload, USAGE_TTL_SECONDS) if not sees_financials: - payload = {**payload, "total_overage_forecast_eur": None} + # Strip both the aggregate and each per-workspace € figure for + # plain members — matrix §8 says members see raw hours only. + stripped_rows = [ + {**r, "overage_forecast_eur": None} for r in ws_rows + ] + payload = { + **payload, + "total_overage_forecast_eur": None, + "workspaces": stripped_rows, + } return OrgUsageResponse(**payload) From 868ffba67c87abb5873e5e62812294cc5128cf86 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 13:24:42 +0000 Subject: [PATCH 125/208] =?UTF-8?q?S7:=20team=20admin=20Projects=20view=20?= =?UTF-8?q?on=20/t/:orgId=20(matrix=20=C2=A74=20wind-down=20workflow)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admins can now scan + delete projects across every team workspace from one surface. Matrix §4 locks delete-workspace behind "must be empty", so walking into 20 workspaces to clear them was the workaround before. Backend: - GET /v2/orgs/:id/projects (team admin/owner only). Returns [{id, name, workspace_id, workspace_name, visibility, conversation_count, created_at}] across every workspace in the team. Batched via a single conversation-count groupBy aggregate. Frontend: - New SegmentedControl view switcher on TeamRoute (admin-only): People (existing matrix) / Projects (new). Defaults to People. - TeamProjectsTable component: search + workspace filter, table with project name (Private badge when visibility=private), workspace, conversation count, created date, trash action. - Per-row delete uses the existing v1 DELETE /projects/:id endpoint (soft-delete). Confirm modal spells out conversation-count impact. - On success: invalidates both the projects query and the team usage rollup (project_count drops). Member + team-member callers get 403 on the endpoint — matrix §5 says members have no cross-workspace project reach, and the view toggle hides the tab from them client-side too. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspace/TeamProjectsTable.tsx | 269 ++++++++++++++++++ echo/frontend/src/routes/team/TeamRoute.tsx | 97 ++++--- echo/server/dembrane/api/v2/orgs.py | 121 ++++++++ 3 files changed, 454 insertions(+), 33 deletions(-) create mode 100644 echo/frontend/src/components/workspace/TeamProjectsTable.tsx diff --git a/echo/frontend/src/components/workspace/TeamProjectsTable.tsx b/echo/frontend/src/components/workspace/TeamProjectsTable.tsx new file mode 100644 index 00000000..971ac813 --- /dev/null +++ b/echo/frontend/src/components/workspace/TeamProjectsTable.tsx @@ -0,0 +1,269 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { + ActionIcon, + Badge, + Group, + Paper, + Select, + Stack, + Table, + Text, + TextInput, + Tooltip, +} from "@mantine/core"; +import { modals } from "@mantine/modals"; +import { IconSearch, IconTrash } from "@tabler/icons-react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useMemo, useState } from "react"; +import { toast } from "@/components/common/Toaster"; +import { API_BASE_URL } from "@/config"; + +interface OrgProject { + id: string; + name: string; + workspace_id: string; + workspace_name: string; + visibility: string; + conversation_count: number; + created_at: string | null; +} + +async function fetchOrgProjects(orgId: string): Promise { + const res = await fetch(`${API_BASE_URL}/v2/orgs/${orgId}/projects`, { + credentials: "include", + }); + if (!res.ok) return []; + return res.json(); +} + +async function deleteProject(projectId: string) { + // v1 endpoint; soft-deletes via deleted_at. + const res = await fetch(`${API_BASE_URL}/projects/${projectId}`, { + method: "DELETE", + credentials: "include", + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error( + typeof data.detail === "string" ? data.detail : "Couldn't delete", + ); + } + return res.json().catch(() => ({})); +} + +function formatDate(iso: string | null): string { + if (!iso) return ""; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return ""; + return d.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + }); +} + +/** + * Team-wide projects view (matrix §4 delete-workspace workflow). + * + * Team admins can scan every project across every team workspace and + * soft-delete from one surface. Prereq for winding down a workspace: + * delete-workspace is blocked while any project exists, and admins + * don't want to walk into 20 workspaces one by one. + * + * Filter + search are client-side — volume is small enough to render + * everything. + */ +export const TeamProjectsTable = ({ orgId }: { orgId: string }) => { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(""); + const [workspaceFilter, setWorkspaceFilter] = useState(null); + + const { data: projects = [], isLoading } = useQuery({ + queryKey: ["v2", "org", orgId, "projects"], + queryFn: () => fetchOrgProjects(orgId), + staleTime: 30_000, + }); + + const workspaceOptions = useMemo(() => { + const seen = new Map(); + for (const p of projects) { + if (!seen.has(p.workspace_id)) { + seen.set(p.workspace_id, p.workspace_name); + } + } + return Array.from(seen.entries()).map(([value, label]) => ({ + value, + label, + })); + }, [projects]); + + const filtered = useMemo(() => { + const q = search.trim().toLowerCase(); + return projects.filter((p) => { + if (workspaceFilter && p.workspace_id !== workspaceFilter) return false; + if (!q) return true; + return ( + p.name.toLowerCase().includes(q) || + p.workspace_name.toLowerCase().includes(q) + ); + }); + }, [projects, search, workspaceFilter]); + + const deleteMutation = useMutation({ + mutationFn: deleteProject, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["v2", "org", orgId, "projects"], + }); + queryClient.invalidateQueries({ + queryKey: ["v2", "org-usage", orgId], + }); + toast.success(t`Project deleted`); + }, + onError: (e: Error) => toast.error(e.message), + }); + + const handleDelete = (p: OrgProject) => { + modals.openConfirmModal({ + title: t`Delete ${p.name}?`, + children: ( + + + + This soft-deletes the project in {p.workspace_name}. + Conversations are preserved in the database but hidden from + all views. + + + {p.conversation_count > 0 && ( + + + {p.conversation_count} conversations will be hidden along + with it. + + + )} + + ), + labels: { confirm: t`Delete`, cancel: t`Cancel` }, + confirmProps: { color: "red" }, + onConfirm: () => deleteMutation.mutate(p.id), + }); + }; + + if (isLoading) return null; + + return ( + + + + + Projects across team · {projects.length} + + + } + placeholder={t`Search project or workspace`} + size="xs" + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + style={{ minWidth: 220 }} + /> + + + + + + Project + + + + + Workspace + + + + + Conversations + + + + + Created + + + + + + + {filtered.map((p) => ( + + + + + {p.name || t`Untitled`} + + {p.visibility === "private" && ( + + Private + + )} + + + + + {p.workspace_name} + + + + {p.conversation_count} + + + + {formatDate(p.created_at)} + + + + + handleDelete(p)} + aria-label={t`Delete project`} + > + + + + + + ))} + +
    + )} +
    +
    + ); +}; diff --git a/echo/frontend/src/routes/team/TeamRoute.tsx b/echo/frontend/src/routes/team/TeamRoute.tsx index 80a949a5..5115f89c 100644 --- a/echo/frontend/src/routes/team/TeamRoute.tsx +++ b/echo/frontend/src/routes/team/TeamRoute.tsx @@ -31,6 +31,7 @@ import { import { useQuery } from "@tanstack/react-query"; import { useMemo, useState } from "react"; import { useParams } from "react-router"; +import { TeamProjectsTable } from "@/components/workspace/TeamProjectsTable"; import { TeamUsageRollup } from "@/components/workspace/TeamUsageRollup"; import { TierBadge } from "@/components/workspace/TierBadge"; import { API_BASE_URL } from "@/config"; @@ -112,6 +113,7 @@ export const TeamRoute = () => { const [search, setSearch] = useState(""); const [roleFilter, setRoleFilter] = useState("all"); const [workspacesDrawer, setWorkspacesDrawer] = useState(false); + const [view, setView] = useState<"matrix" | "projects">("matrix"); useDocumentTitle(t`Team | dembrane`); @@ -239,38 +241,64 @@ export const TeamRoute = () => { aggregate € forecast. Members see raw numbers only. */} {teamId && } - {/* Toolbar — search + role filter + count */} - - - } - placeholder={t`Search name or email`} - size="sm" - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - style={{ flex: 1, maxWidth: 320 }} - /> - setRoleFilter(v as RoleFilter)} - data={[ - { value: "all", label: t`All` }, - { value: "admins", label: t`Admins` }, - { value: "members", label: t`Members` }, - ]} - /> + {/* View switcher — admin-only Projects tab gives access to + the matrix §4 delete-workspace workflow (wind down + projects across workspaces from one surface). */} + {isAdmin && ( + setView(v as "matrix" | "projects")} + data={[ + { value: "matrix", label: t`People` }, + { value: "projects", label: t`Projects` }, + ]} + style={{ alignSelf: "flex-start" }} + /> + )} + + {/* Toolbar — only shown on People view. */} + {view === "matrix" && ( + + + } + placeholder={t`Search name or email`} + size="sm" + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + style={{ flex: 1, maxWidth: 320 }} + /> + setRoleFilter(v as RoleFilter)} + data={[ + { value: "all", label: t`All` }, + { value: "admins", label: t`Admins` }, + { value: "members", label: t`Members` }, + ]} + /> + + + + Showing {filteredMembers.length} of {members.length} + + - - - Showing {filteredMembers.length} of {members.length} - - - + )} + + {/* Projects view — admin-only. Matrix §4 delete-workspace + workflow: wind down projects across all team workspaces + from one surface. */} + {view === "projects" && isAdmin && teamId && ( + + )} {/* Matrix — the main content. Sticky first column (name+email) so it stays visible on horizontal scroll. Mantine Table with stickyHeader + custom sticky-left on the first cell. */} + {view === "matrix" && ( {
    + )} - - - Team admins can join any workspace to get a direct admin seat. - Workspace invites live in each workspace's settings. - - + {view === "matrix" && ( + + + Team admins can join any workspace to get a direct admin seat. + Workspace invites live in each workspace's settings. + + + )} {/* Manage workspaces drawer — list of team workspaces with diff --git a/echo/server/dembrane/api/v2/orgs.py b/echo/server/dembrane/api/v2/orgs.py index 83eccae9..9a585a46 100644 --- a/echo/server/dembrane/api/v2/orgs.py +++ b/echo/server/dembrane/api/v2/orgs.py @@ -1235,3 +1235,124 @@ async def get_org_usage( } return OrgUsageResponse(**payload) + + +# ──────────────────────────────────────────────────────────────────── +# Team-wide projects listing (for the team admin projects view) +# ──────────────────────────────────────────────────────────────────── + + +class OrgProjectRow(BaseModel): + id: str + name: str + workspace_id: str + workspace_name: str + visibility: str + conversation_count: int = 0 + created_at: Optional[str] = None + + +@router.get("/{org_id}/projects", response_model=list[OrgProjectRow]) +async def list_team_projects( + org_id: str, + auth: DependencyDirectusSession, +) -> list[OrgProjectRow]: + """Every project across every workspace in the team. + + Matrix §4: delete-workspace requires empty. Without a cross-team + projects view, team admins have to walk into each workspace to clear + it before winding down. This endpoint powers the "Projects" view on + the team page where admins can scan + soft-delete at scale. + + Team admin/owner only — members have no cross-workspace project + reach per matrix §5. + """ + app_user = await get_app_user_or_raise(auth.user_id) + await _require_org_role(org_id, app_user["id"], minimum="admin") + + # Workspaces in the team. + workspaces = await async_directus.get_items( + "workspace", + { + "query": { + "filter": { + "org_id": {"_eq": org_id}, + "deleted_at": {"_null": True}, + }, + "fields": ["id", "name"], + "limit": -1, + } + }, + ) or [] + if not isinstance(workspaces, list) or not workspaces: + return [] + + ws_by_id: dict[str, str] = { + w["id"]: w.get("name") or "" for w in workspaces if w.get("id") + } + ws_ids = list(ws_by_id.keys()) + + projects = await async_directus.get_items( + "project", + { + "query": { + "filter": { + "workspace_id": {"_in": ws_ids}, + "deleted_at": {"_null": True}, + }, + "fields": [ + "id", + "name", + "workspace_id", + "visibility", + "created_at", + ], + "sort": ["-created_at"], + "limit": -1, + } + }, + ) or [] + if not isinstance(projects, list): + return [] + + # Batch conversation-count per project via group-by aggregate. + pid_list = [p["id"] for p in projects if p.get("id")] + conv_counts: dict[str, int] = {} + if pid_list: + agg = await async_directus.get_items( + "conversation", + { + "query": { + "aggregate": {"count": "id"}, + "groupBy": ["project_id"], + "filter": { + "project_id": {"_in": pid_list}, + "deleted_at": {"_null": True}, + }, + } + }, + ) or [] + if isinstance(agg, list): + for row in agg: + pid = row.get("project_id") + cnt = int((row.get("count") or {}).get("id", 0) or 0) + if pid: + conv_counts[pid] = cnt + + out: list[OrgProjectRow] = [] + for p in projects: + wid = p.get("workspace_id") + if not wid or wid not in ws_by_id: + continue + out.append( + OrgProjectRow( + id=p["id"], + name=p.get("name") or "", + workspace_id=wid, + workspace_name=ws_by_id[wid], + visibility=p.get("visibility") or "workspace", + conversation_count=conv_counts.get(p["id"], 0), + created_at=p.get("created_at"), + ) + ) + return out From da70a103b68d3f6f19eaa3e79a53483b80628b80 Mon Sep 17 00:00:00 2001 From: Sameer Pashikanti Date: Thu, 23 Apr 2026 13:29:23 +0000 Subject: [PATCH 126/208] S9: workspace creation wizard (3-step) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converts the single-form route into Name → Access → Review with a Stepper progress indicator. Back + cancel on every step; cancel confirms when the form has content so a misclick doesn't nuke a half-typed name. Multi-role design (per the brief's reminder): - Creator is always a team admin/owner (route-gated). The wizard doesn't ask *their* role; it spells out what OTHER roles will experience in the new workspace so the creator isn't surprised. - Review step has a "What each role will experience" list covering you / other team admins / team members / guests. Matches matrix §6 Slack-style model (admins discover + join; members discover + request access; guests only via explicit invite). - Private workspace copy includes the matrix §6 honesty disclosure: "Team admins can still discover and join this workspace. Private protects from team members, not team admins." Private radio stays disabled (matrix §2 innovator+ gate). Copy tells the creator why and what to do — start open, upgrade, flip later. Pre-checks unchanged: unonboarded redirects to /onboarding; no-admin- team users get the friendly "ask a team admin" state. Backend unchanged — POST /v2/workspaces already takes the minimal payload (name + org_id + inherit_team_admins) this wizard writes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspaces/CreateWorkspaceRoute.tsx | 393 +++++++++++++----- 1 file changed, 282 insertions(+), 111 deletions(-) diff --git a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx index 8077f740..dd40cac5 100644 --- a/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx +++ b/echo/frontend/src/routes/workspaces/CreateWorkspaceRoute.tsx @@ -6,14 +6,19 @@ import { Center, Container, Group, + List, Loader, + Paper, + Radio, Select, Stack, + Stepper, Text, TextInput, Title, } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; +import { modals } from "@mantine/modals"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useEffect, useMemo, useState } from "react"; import { useSearchParams } from "react-router"; @@ -41,18 +46,30 @@ async function createWorkspace(payload: { return res.json(); } +type Privacy = "open" | "private"; + /** - * Create-workspace form. Entered from the dashed "+ Add workspace" card - * on the selector, which passes `?teamId=` so the form knows which - * team to create into. Without teamId the form falls back to the user's - * primary admin team and asks them to confirm. + * Workspace creation wizard — matrix §6 Slack-style model. + * + * Three steps with back + cancel on each. The review step spells out + * what EVERY role (team admin, team member, guest) will experience in + * the new workspace so the creator isn't surprised later. + * + * Multi-role lens: + * - Creator is always a team admin/owner (route-gated). Wizard + * copy centers on what OTHER roles experience once created. + * - Team admins: auto-discover; can Join directly. + * - Team members: auto-discover open; can Request access. Don't see + * private workspaces in discovery. + * - Guests: no team-scope presence. Only exist once explicitly + * invited; wizard doesn't cover that path (invite after create). + * + * Entry: `/w/new?teamId=`. Without teamId, falls back to the + * caller's primary admin team (with a quiet notice). * - * Pre-checks before rendering the form: - * - Not onboarded → redirect to /onboarding. Matches the user's ask: - * "don't dump 'No team found. Complete onboarding first.' as an - * error — route me there first." - * - Onboarded but admin/owner on no team → render a friendly "Ask - * your team admin" state. Members genuinely can't create workspaces. + * Pre-checks mirror the old one-step flow: + * - Not onboarded → redirect to /onboarding. + * - No admin team → friendly "Ask your team admin" state. */ export const CreateWorkspaceRoute = () => { const navigate = useI18nNavigate(); @@ -61,12 +78,13 @@ export const CreateWorkspaceRoute = () => { const [searchParams] = useSearchParams(); const teamIdFromQuery = searchParams.get("teamId") ?? null; const { data: meV2, isLoading: meLoading } = useV2Me(); + + const [step, setStep] = useState(0); const [name, setName] = useState(""); - const [privacy, setPrivacy] = useState<"open" | "private">("open"); + const [privacy, setPrivacy] = useState("open"); useDocumentTitle(t`New workspace | dembrane`); - // Teams where the caller has permission to create workspaces. const adminTeams = useMemo( () => (meV2?.orgs ?? []).filter( @@ -75,7 +93,6 @@ export const CreateWorkspaceRoute = () => { [meV2], ); - // Redirect unonboarded users to onboarding instead of showing a 403. useEffect(() => { if (meLoading) return; if (meV2 && meV2.onboarding_completed === false) { @@ -106,6 +123,26 @@ export const CreateWorkspaceRoute = () => { }, }); + const handleCancel = () => { + // Confirm only if the user has typed a name — empty forms should + // cancel silently. Consistent with most wizards users have seen. + if (name.trim()) { + modals.openConfirmModal({ + title: t`Discard this workspace?`, + children: ( + + Your draft won't be saved. + + ), + labels: { confirm: t`Discard`, cancel: t`Keep editing` }, + confirmProps: { color: "red" }, + onConfirm: () => navigate("/w"), + }); + } else { + navigate("/w"); + } + }; + if (meLoading || meV2?.onboarding_completed === false) { return (
    @@ -114,8 +151,6 @@ export const CreateWorkspaceRoute = () => { ); } - // Onboarded, but not admin on any team. The backend would 403 — - // tell the user what's actually wrong instead of error-toasting. if (adminTeams.length === 0) { return ( @@ -139,10 +174,13 @@ export const CreateWorkspaceRoute = () => { ); } + const canAdvanceFromName = name.trim().length > 0; + const canCreate = canAdvanceFromName && Boolean(targetTeamId); + return ( - - - + + + <Trans>New workspace</Trans> @@ -153,11 +191,6 @@ export const CreateWorkspaceRoute = () => { )} - - - Workspaces hold projects for a specific client or purpose. - - {teamIdFromQuery && !targetTeam && ( @@ -169,102 +202,240 @@ export const CreateWorkspaceRoute = () => { )} -
    { - e.preventDefault(); - if (!name.trim()) return; - mutation.mutate(); + { + // Let the user jump backwards only. Jumping forward past + // an incomplete step is what the Next button is for. + if (i <= step) setStep(i); }} + size="sm" + iconSize={28} > - - setName(e.currentTarget.value)} - /> - - {/* Team picker only shows when the user has multiple admin - teams and no ?teamId was supplied. Keeps the common - single-team path friction-free. */} - {!teamIdFromQuery && adminTeams.length > 1 && ( - v && setPrivacy(v as "open" | "private")} - size="sm" - /> + {!teamIdFromQuery && adminTeams.length > 1 && ( + setInheritMembers(e.currentTarget.checked)} - style={{ marginTop: 3 }} - id="inherit-members" - /> - - - Team members also get access - - - - By default only admins inherit. Check this to open the workspace - to everyone on the team. - - - - - )} + {/* inherit_team_members toggle removed per matrix §6 — team members + now go through the explicit Request-access flow; no automatic + derivation. Backend still accepts the flag for legacy rows + but we don't expose it. */} {canEdit && (

    c=u3%TT)~1a2zfFCe&7c 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