# ByteLyst — Azure Portal Setup Guide (Shared Infrastructure)

> **Purpose:** Step-by-step instructions to provision and manage Azure infrastructure shared between **LysnrAI** and **MindLyst** under a single resource group.
> **Architecture:** Single RG, shared stateless services, separate databases.
> **Time estimate:** ~20 minutes (most resources already exist from LysnrAI)
> **Prerequisites:** An Azure account with an active subscription
> **Last updated:** 2026-03-21
>
> **Security note (staging):** This document does **not** include secret values. Store secrets in Azure Key Vault (`kv-mywisprai` — legacy resource name) and reference them by name (see **MindLyst Environment Variables** and **Key Vault** sections). Rotation is deferred; see `docs/devops/AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md`.
>
> **Note on Azure resource names:** Many resources below still carry `mywisprai` in their names (e.g., `cosmos-mywisprai`, `kv-mywisprai`). Azure resources **cannot be renamed** after creation. The old names are purely cosmetic and work correctly.

---

## Table of Contents

- [Architecture Overview](#architecture-overview)
- [Existing Resources (Staging / Shared)](#existing-resources-staging--shared)
- [What to Add for MindLyst](#what-to-add-for-mindlyst)
- [Step 1: Resource Group](#step-1-resource-group)
- [Step 2: Cosmos DB — Add MindLyst Database](#step-2-cosmos-db--add-mindlyst-database)
- [Step 3: Blob Storage — Add MindLyst Containers](#step-3-blob-storage--add-mindlyst-containers)
- [Step 4: Azure OpenAI — Reuse Existing](#step-4-azure-openai--reuse-existing)
- [Step 5: Speech Service — Reuse Existing](#step-5-speech-service--reuse-existing)
- [Step 6: Key Vault — Add MindLyst Secrets](#step-6-key-vault--add-mindlyst-secrets)
- [Step 7: Notification Hub — Add MindLyst Hub](#step-7-notification-hub--add-mindlyst-hub)
- [Step 8: Application Insights — Reuse or Create](#step-8-application-insights--reuse-or-create)
- [MindLyst Environment Variables](#mindlyst-environment-variables)
- [Verification Checklist](#verification-checklist)
- [Cost Estimates](#cost-estimates)
- [Resource Naming Convention](#resource-naming-convention)
- [Az CLI Script (Copy-Paste-Ready)](#az-cli-script-copy-paste-ready)
- [Bicep IaC (Replicate To Another Account)](#bicep-iac-replicate-to-another-account)

---

## Architecture Overview

Both LysnrAI and MindLyst share a **single Azure resource group** with shared stateless services and separate data stores.

```
rg-mywisprai/                             (1 resource group)
│
├── cosmos-mywisprai                       (1 Cosmos DB account — already exists)
│   ├── mywisprai                          (database: LysnrAI — legacy name, 10 containers)
│   └── mindlyst                           (database: MindLyst, 12 containers)
│
├── bytelystblobs                          (1 Storage account — already exists)
│   ├── mindlyst-voice                     ← NEW
│   ├── mindlyst-images                    ← NEW
│   └── mindlyst-exports                   ← NEW
│
├── mywisprai-openai-sweden                (1 Azure OpenAI — shared, swedencentral)
│   └── gpt-4o-mini                        (1 deployment — shared by both apps)
│
├── mywisprai-speech                        (1 Speech Service — shared, eastus)
│
├── kv-mywisprai                           (1 Key Vault — shared)
│   ├── lysnr-*                            (LysnrAI secrets)
│   ├── wispr-*                            (LysnrAI legacy secrets, pre-rebrand)
│   └── mindlyst-*                         (MindLyst secrets)
│
├── lysnnai                                (1 Notification Hub namespace)
│   ├── notificationhub                    (existing hub)
│   └── mindlyst-hub                       (MindLyst APNs + FCM) ← NEW
│
└── bytelyst-appinsights                   (1 Application Insights) ← NEW
```

**Total: 8 Azure resources** instead of 14+ with separate resource groups.

### Why Share?

| Concern                | Answer                                                                                                           |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Cost**               | Cosmos DB Serverless charges per-RU, not per-account. One account = one endpoint, one key pair, simpler rotation |
| **Management**         | 1 RG to monitor, 1 cost dashboard, 1 health check                                                                |
| **Data isolation**     | Separate databases within Cosmos = full container-level isolation. Queries can't cross databases                 |
| **Teardown safety**    | Delete the `mindlyst` database → LysnrAI data untouched                                                          |
| **Stateless services** | OpenAI & Speech are stateless — same key serves both apps, no data leakage                                       |

### What Must Stay Separate

| Resource                  | Why                                                                            |
| ------------------------- | ------------------------------------------------------------------------------ |
| **Notification Hub hubs** | Each app has its own APNs certificate and FCM server key                       |
| **Stripe accounts**       | Different products, pricing tiers, and webhook endpoints (external, not Azure) |
| **Cosmos databases**      | Each app gets its own database with its own set of containers                  |

---

## Existing Resources (Staging / Shared)

These resources are **already provisioned** and will be reused by MindLyst:

| Resource                         | Azure Name                | Region          | SKU               | Status                                                                         |
| -------------------------------- | ------------------------- | --------------- | ----------------- | ------------------------------------------------------------------------------ |
| **Resource Group**               | `rg-mywisprai`            | `eastus`        | —                 | Exists                                                                         |
| **Cosmos DB**                    | `cosmos-mywisprai`        | `westus2`       | Serverless, NoSQL | Exists — DBs: `mywisprai` (LysnrAI, 10 containers), `mindlyst` (12 containers) |
| **Blob Storage**                 | `bytelystblobs`           | `westus2`       | StorageV2, RAGRS  | Exists                                                                         |
| **Azure OpenAI**                 | `mywisprai-openai-sweden` | `swedencentral` | S0                | Exists — deployment: `gpt-4o-mini`                                             |
| **Speech Service**               | `mywisprai-speech`        | `eastus`        | F0                | Exists                                                                         |
| **Key Vault**                    | `kv-mywisprai`            | `eastus`        | Standard          | Exists                                                                         |
| **Notification Hub (namespace)** | `lysnnai`                 | `eastus`        | Free              | Exists (hubs: `notificationhub`, `mindlyst-hub`)                               |
| **Application Insights**         | `bytelyst-appinsights`    | `eastus`        | Classic           | Exists                                                                         |

### Cosmos DB: Existing LysnrAI database containers (staging)

Database: `mywisprai` (legacy name — this is the LysnrAI database)

- `api_tokens`
- `audit_log`
- `devices`
- `licenses`
- `payments`
- `settings`
- `subscriptions`
- `transcripts`
- `usage_daily`
- `users`

> Partition keys for these existing containers are not documented here. Check **Cosmos DB → Data Explorer → Container → Settings** if you need to replicate LysnrAI data layout.

### Key Vault secrets (staging)

Source of truth: Key Vault `kv-mywisprai` in `rg-mywisprai`.

MindLyst secrets (canonical):

- `mindlyst-cosmos-endpoint`
- `mindlyst-cosmos-key`
- `mindlyst-cosmos-database`
- `mindlyst-openai-endpoint`
- `mindlyst-openai-key`
- `mindlyst-openai-deployment`
- `mindlyst-openai-api-version`
- `mindlyst-speech-key`
- `mindlyst-speech-region`
- `mindlyst-blob-connection-string`
- `mindlyst-notification-hub-connection-string`
- `mindlyst-appinsights-connection-string`

LysnrAI secrets (legacy `wispr-*` prefix, pre-rebrand — superseded by `lysnr-*`):

- `wispr-azure-openai-deployment`
- `wispr-azure-openai-endpoint`
- `wispr-azure-openai-key`
- `wispr-azure-speech-key`
- `wispr-azure-speech-region`

List what’s currently in the vault:

```bash
az keyvault secret list --vault-name kv-mywisprai --query "[].name" -o tsv
```

---

## What to Add for MindLyst

Only these additions are needed — no new accounts to create:

| Action                   | Where                                  | What                                                                         |
| ------------------------ | -------------------------------------- | ---------------------------------------------------------------------------- |
| **Rename RG (optional)** | Resource Group                         | Keep `rg-mywisprai` for now. Only move resources if you want a cleaner name. |
| **Add database**         | Cosmos DB (`cosmos-mywisprai`)         | New database `mindlyst` with 12 containers                                   |
| **Add blob containers**  | Blob Storage (`bytelystblobs`)         | 3 new containers: `mindlyst-voice`, `mindlyst-images`, `mindlyst-exports`    |
| **Add secrets**          | Key Vault (`kv-mywisprai`)             | 12 secrets with `mindlyst-` prefix (keys + config)                           |
| **Add hub**              | Notification Hub namespace (`lysnnai`) | New hub `mindlyst-hub`                                                       |
| **Create App Insights**  | New resource                           | `bytelyst-appinsights` (shared telemetry)                                    |

### Current Staging State (Completed)

As of **2026-02-14**, the MindLyst staging infra is provisioned in `rg-mywisprai`:

- **Cosmos DB:** `cosmos-mywisprai` → database `mindlyst` with 12 containers
- **Blob Storage:** `bytelystblobs` → containers `mindlyst-voice`, `mindlyst-images`, `mindlyst-exports`
- **Key Vault:** `kv-mywisprai` → secrets `mindlyst-*`
- **Notification Hubs:** namespace `lysnnai` → hub `mindlyst-hub`
- **App Insights:** `bytelyst-appinsights`

---

## Step 1: Resource Group

### Option A: Create New RG + Move Resources (Optional)

> Azure doesn't support direct rename. You must **move resources** to a new RG.
>
> **Caution:** Moving resources is safe when done carefully, but it can have RBAC/policy implications in larger org setups. For staging, consider skipping this until you have a clear reason.

1. Go to [portal.azure.com](https://portal.azure.com) → **Resource groups** → search `rg-mywisprai`
2. Click on the resource group → **Overview**
3. **Select all resources** (checkbox at top of list)
4. Click **Move** → **Move to another resource group**
5. Click **Create new** → name it **`rg-bytelyst`** → same subscription
6. Click **Next** → wait for validation → click **Move**
7. Wait for move to complete (~5–10 minutes)
8. Delete the empty `rg-mywisprai` resource group

### Option B: Keep Existing Name

If you prefer not to move, keep `rg-mywisprai` and add MindLyst resources there. All instructions below assume the RG name — substitute as needed.

> **Note:** Renaming is cosmetic. All connection strings and keys remain the same regardless of RG name.

---

## Step 2: Cosmos DB — Add MindLyst Database

> Reusing the existing `cosmos-mywisprai` account. Just add a new database and containers.

### 2a. Create the MindLyst database

1. Go to **Azure Cosmos DB** → click **`cosmos-mywisprai`**
2. Click **Data Explorer** (left sidebar)
3. Click **New Database** (top toolbar)
   - **Database id:** `mindlyst`
4. Click **OK**

You should now see both `mywisprai` and `mindlyst` databases in the tree.

### 2b. Create all 12 containers

For each container below, right-click the **`mindlyst`** database (or click the `…` menu) → **New Container**:

| #   | Container Name     | Partition Key   | Purpose                                                            |
| --- | ------------------ | --------------- | ------------------------------------------------------------------ |
| 1   | `users`            | `/id`           | User accounts and profiles                                         |
| 2   | `brains`           | `/userId`       | User brain configurations (War Room, Home Base, Money Guard, etc.) |
| 3   | `brain_templates`  | `/id`           | Brain Pack gallery templates                                       |
| 4   | `memory_items`     | `/userId`       | All captured memories (text, voice, image, link, email)            |
| 5   | `actions`          | `/memoryItemId` | AI-suggested actions from triage                                   |
| 6   | `entities`         | `/memoryItemId` | Extracted entities (people, dates, places, amounts)                |
| 7   | `reflections`      | `/userId`       | Weekly reflection reports and insights                             |
| 8   | `share_cards`      | `/userId`       | Generated share cards for social sharing                           |
| 9   | `daily_briefs`     | `/userId`       | Morning and evening brief content                                  |
| 10  | `streaks`          | `/userId`       | Capture streak tracking and gamification                           |
| 11  | `notification_log` | `/userId`       | Push notification delivery log and scheduling                      |
| 12  | `brain_insights`   | `/userId`       | Cross-brain insight discoveries                                    |

**For each container:**

1. Click **New Container**
2. **Existing Database:** select `mindlyst` (do NOT create a new database)
3. Enter the **Container id** from the table
4. Enter the **Partition key** from the table (include the `/` prefix)
5. **Throughput:** leave as **Serverless** (auto — no provisioned RUs)
6. Click **OK**

Repeat for all 12 containers. Takes ~3 minutes.

### 2c. Verify

In **Data Explorer**, expand the `mindlyst` database — you should see all 12 containers listed.

### Connection details (from Key Vault)

Cosmos connection details are stored in Key Vault:

- `mindlyst-cosmos-endpoint` → `COSMOS_ENDPOINT`
- `mindlyst-cosmos-key` → `COSMOS_KEY`
- `mindlyst-cosmos-database` → `COSMOS_DATABASE` (should be `mindlyst`)

---

## Step 3: Blob Storage — Add MindLyst Containers

> Reusing the existing `bytelystblobs` storage account. Just add 3 new containers.

1. Go to **Storage accounts** → click **`bytelystblobs`**
2. Go to **Data storage** → **Containers** (left sidebar)
3. Click **+ Container** for each:

| Container Name     | Access Level | Purpose                                              |
| ------------------ | ------------ | ---------------------------------------------------- |
| `mindlyst-voice`   | **Private**  | Voice capture recordings (WAV/PCM from STT pipeline) |
| `mindlyst-images`  | **Private**  | Captured photos, screenshots, and OCR source images  |
| `mindlyst-exports` | **Private**  | User data exports (JSON), weekly reflection PDFs     |

For each:

1. Click **+ Container**
2. Enter the **Name** (must be lowercase, no underscores — use hyphens)
3. **Anonymous access level:** leave as **Private (no anonymous access)**
4. Click **Create**

### Verify

You should now see these MindLyst containers in `bytelystblobs` (plus any other app containers you already had):

| Container          | App      |
| ------------------ | -------- |
| `mindlyst-voice`   | MindLyst |
| `mindlyst-images`  | MindLyst |
| `mindlyst-exports` | MindLyst |

### Connection details (from Key Vault)

Blob connection details are stored in Key Vault:

- `mindlyst-blob-connection-string` → `AZURE_BLOB_CONNECTION_STRING`

Non-secret config:

- `AZURE_BLOB_ACCOUNT_NAME=bytelystblobs`
- Containers: `mindlyst-voice`, `mindlyst-images`, `mindlyst-exports`

---

## Step 4: Azure OpenAI — Reuse Existing

> No action needed. The existing `mywisprai-openai-sweden` resource with `gpt-4o-mini` deployment serves both apps.

### Connection details (from Key Vault)

Azure OpenAI connection details are stored in Key Vault:

- `mindlyst-openai-endpoint` → `AZURE_OPENAI_ENDPOINT`
- `mindlyst-openai-key` → `AZURE_OPENAI_KEY`
- `mindlyst-openai-deployment` → `AZURE_OPENAI_DEPLOYMENT` (staging: `gpt-4o-mini`)
- `mindlyst-openai-api-version` → `AZURE_OPENAI_API_VERSION` (default: `2024-06-01`)

MindLyst web should set:

- `OPENAI_PROVIDER=azure`

---

## Step 5: Speech Service — Reuse Existing

> No action needed. The existing `mywisprai-speech` (F0 free tier, eastus) serves both apps.

### Connection details (from Key Vault)

Speech connection details are stored in Key Vault:

- `mindlyst-speech-key` → `AZURE_SPEECH_KEY`
- `mindlyst-speech-region` → `AZURE_SPEECH_REGION`

> **Note:** F0 free tier has a 5-hour/month STT limit. If both apps start consuming real audio, upgrade to S0 ($1/audio hour).

---

## Step 6: Key Vault — MindLyst Secrets

> Staging source of truth: `kv-mywisprai` in `rg-mywisprai`.
>
> For local dev, pull values from Key Vault into `mindlyst-native/web/.env.local` (see **MindLyst Environment Variables**).

MindLyst secrets (canonical):

| Key Vault secret name                         | Env var                                 | Notes                     |
| --------------------------------------------- | --------------------------------------- | ------------------------- |
| `mindlyst-cosmos-endpoint`                    | `COSMOS_ENDPOINT`                       | Config (ok to store)      |
| `mindlyst-cosmos-key`                         | `COSMOS_KEY`                            | Secret                    |
| `mindlyst-cosmos-database`                    | `COSMOS_DATABASE`                       | Config (`mindlyst`)       |
| `mindlyst-openai-endpoint`                    | `AZURE_OPENAI_ENDPOINT`                 | Config                    |
| `mindlyst-openai-key`                         | `AZURE_OPENAI_KEY`                      | Secret                    |
| `mindlyst-openai-deployment`                  | `AZURE_OPENAI_DEPLOYMENT`               | Config                    |
| `mindlyst-openai-api-version`                 | `AZURE_OPENAI_API_VERSION`              | Config                    |
| `mindlyst-speech-key`                         | `AZURE_SPEECH_KEY`                      | Secret                    |
| `mindlyst-speech-region`                      | `AZURE_SPEECH_REGION`                   | Config                    |
| `mindlyst-blob-connection-string`             | `AZURE_BLOB_CONNECTION_STRING`          | Secret (prefer SAS later) |
| `mindlyst-notification-hub-connection-string` | `ANH_CONNECTION_STRING`                 | Secret                    |
| `mindlyst-appinsights-connection-string`      | `APPLICATIONINSIGHTS_CONNECTION_STRING` | Treat as sensitive        |

List what’s currently in the vault:

```bash
az keyvault secret list --vault-name kv-mywisprai --query "[].name" -o tsv
```

### Existing LysnrAI secrets (for reference)

| Secret Name                     | Notes                                          |
| ------------------------------- | ---------------------------------------------- |
| `wispr-azure-openai-deployment` | Legacy (pre-rebrand) — superseded by `lysnr-*` |
| `wispr-azure-openai-endpoint`   | Legacy (pre-rebrand)                           |
| `wispr-azure-openai-key`        | Legacy (pre-rebrand)                           |
| `wispr-azure-speech-key`        | Legacy (pre-rebrand)                           |
| `wispr-azure-speech-region`     | Legacy (pre-rebrand)                           |

---

## Step 7: Notification Hub — Add MindLyst Hub

> MindLyst needs its own hub (different APNs cert + FCM key) but can share the namespace.

### 7a. Check existing namespace

1. Go to **Notification Hub Namespaces** → check if `lysnnai` exists
2. If it exists, open it → skip to 7c
3. If not, continue to 7b

### 7b. Create namespace (if needed)

1. Search **"Notification Hubs"** → click **Create Notification Hub Namespace**
2. Fill in:
   - **Resource group:** `rg-mywisprai`
   - **Namespace name:** `lysnnai`
   - **Location:** `East US`
   - **Pricing tier:** **Free** (up to 1M pushes/month)
3. Click **Create**

### 7c. Create MindLyst hub

1. Inside the namespace → click **+ Notification Hub**
2. **Hub name:** `mindlyst-hub`
3. Click **Create**

### 7d. Configure APNs and FCM (later — when mobile app is ready)

1. Go to `mindlyst-hub` → **Settings** → **Apple (APNS)**
   - Upload your MindLyst APNs certificate or token key
2. Go to **Settings** → **Google (FCM v1)**
   - Enter MindLyst Firebase project credentials

### Connection details

1. Go to `mindlyst-hub` → **Settings** → **Access Policies**
2. Copy the connection string for `DefaultFullSharedAccessSignature` (Send + Manage)

Store it in Key Vault:

- `mindlyst-notification-hub-connection-string` → `ANH_CONNECTION_STRING`

Non-secret config:

- `ANH_HUB_NAME=mindlyst-hub`

---

## Step 8: Application Insights — Reuse or Create

> Optional for MVP. If you want telemetry for both apps, create one shared resource.
>
> **Staging status:** Created `bytelyst-appinsights` in `rg-mywisprai` (East US).

### Create (if not exists)

Option A: Workspace-based (recommended)

1. Search **"Application Insights"** → click **Create**
2. Fill in:
   - **Resource group:** `rg-mywisprai`
   - **Name:** `bytelyst-appinsights`
   - **Region:** `East US`
   - **Resource Mode:** **Workspace-based**
   - **Log Analytics Workspace:** create new → `bytelyst-logs`
3. Click **Review + Create** → **Create**

Option B: Classic (matches current staging)
Use the Az CLI snippet in the **Optional: Application Insights** section below.

### Get connection details

1. Go to the resource → **Overview**
2. Copy **Connection String**
3. Store it in Key Vault:
   - `mindlyst-appinsights-connection-string` → `APPLICATIONINSIGHTS_CONNECTION_STRING`

### Separating telemetry per app

In your application code, add a custom property to every trace:

```typescript
// MindLyst
telemetryClient.context.tags['ai.cloud.role'] = 'mindlyst-web';

// LysnrAI
telemetryClient.context.tags['ai.cloud.role'] = 'lysnrai-admin';
```

Then filter in the Azure Portal: **Application Insights → Logs → `where cloud_RoleName == "mindlyst-web"`**

---

## MindLyst Environment Variables

After completing all steps, create `mindlyst-native/web/.env.local`:

> **Note:** MindLyst web API routes use `x-user-id` (or `MINDLYST_USER_ID`) as the Cosmos DB partition key for containers with `/userId`.
>
> **Hosted envs:** Prefer Key Vault references / Managed Identity (see `docs/devops/AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md`). For local dev, generate `.env.local` from Key Vault.

```bash
KV_NAME="kv-mywisprai"

COSMOS_ENDPOINT="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-cosmos-endpoint --query value -o tsv)"
COSMOS_KEY="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-cosmos-key --query value -o tsv)"
COSMOS_DATABASE="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-cosmos-database --query value -o tsv)"

AZURE_OPENAI_ENDPOINT="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-endpoint --query value -o tsv)"
AZURE_OPENAI_KEY="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-key --query value -o tsv)"
AZURE_OPENAI_DEPLOYMENT="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-deployment --query value -o tsv)"
AZURE_OPENAI_API_VERSION="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-openai-api-version --query value -o tsv)"

AZURE_SPEECH_KEY="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-speech-key --query value -o tsv)"
AZURE_SPEECH_REGION="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-speech-region --query value -o tsv)"

AZURE_BLOB_CONNECTION_STRING="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-blob-connection-string --query value -o tsv)"

ANH_CONNECTION_STRING="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-notification-hub-connection-string --query value -o tsv)"
APPLICATIONINSIGHTS_CONNECTION_STRING="$(az keyvault secret show --vault-name "$KV_NAME" --name mindlyst-appinsights-connection-string --query value -o tsv)"

cat > mindlyst-native/web/.env.local <<EOF
# ── Azure Cosmos DB ─────────────────────────────────────────
COSMOS_ENDPOINT=${COSMOS_ENDPOINT}
COSMOS_KEY=${COSMOS_KEY}
COSMOS_DATABASE=${COSMOS_DATABASE}

# ── Azure OpenAI (AI Foundry) ───────────────────────────────
OPENAI_PROVIDER=azure
AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
AZURE_OPENAI_KEY=${AZURE_OPENAI_KEY}
AZURE_OPENAI_DEPLOYMENT=${AZURE_OPENAI_DEPLOYMENT}
AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}

# ── Azure Speech ────────────────────────────────────────────
AZURE_SPEECH_KEY=${AZURE_SPEECH_KEY}
AZURE_SPEECH_REGION=${AZURE_SPEECH_REGION}

# ── Azure Blob Storage ──────────────────────────────────────
AZURE_BLOB_CONNECTION_STRING=${AZURE_BLOB_CONNECTION_STRING}

# ── Push Notifications ──────────────────────────────────────
ANH_CONNECTION_STRING=${ANH_CONNECTION_STRING}
ANH_HUB_NAME=mindlyst-hub

# ── App Insights ────────────────────────────────────────────
APPLICATIONINSIGHTS_CONNECTION_STRING=${APPLICATIONINSIGHTS_CONNECTION_STRING}

# ── App Config ──────────────────────────────────────────────
NEXT_PUBLIC_APP_NAME=MindLyst
NEXT_PUBLIC_APP_VERSION=0.2.0
MINDLYST_USER_ID=staging_user

# ── Abuse Controls (Web API) ────────────────────────────────
# Basic, per-instance rate limiting + payload guards for Azure OpenAI routes.
RATE_LIMIT_ENABLED=true
LLM_RATE_LIMIT_WINDOW_MS=60000
TRIAGE_RATE_LIMIT_LIMIT=30
BRAIN_CHAT_RATE_LIMIT_LIMIT=20
TRIAGE_MAX_CONTENT_CHARS=8000
BRAIN_CHAT_MAX_MESSAGE_CHARS=2000
BRAIN_CHAT_MAX_HISTORY_MESSAGES=20
BRAIN_CHAT_MAX_HISTORY_TOTAL_CHARS=10000
EOF
```

---

## Verification Checklist

After completing all steps, verify:

| #   | Check                                          | How                                                                                                |
| --- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| 1   | **Cosmos DB: `mindlyst` database**             | Data Explorer → expand `cosmos-mywisprai` → `mindlyst` → 12 containers visible                     |
| 2   | **Cosmos DB: `mywisprai` (LysnrAI) untouched** | Data Explorer → `mywisprai` database still has its existing containers                             |
| 3   | **Blob: 3 new containers**                     | Storage → `bytelystblobs` → Containers → `mindlyst-voice`, `mindlyst-images`, `mindlyst-exports`   |
| 4   | **Blob: other containers intact (if any)**     | Same page → any pre-existing containers should still be present                                    |
| 5   | **OpenAI deployment**                          | Azure OpenAI Studio → Deployments → `gpt-4o-mini` listed                                           |
| 6   | **Speech Service keys**                        | Speech resource → Keys and Endpoint → keys visible                                                 |
| 7   | **Key Vault: MindLyst secrets**                | Key Vault → Secrets → `mindlyst-*` entries visible                                                 |
| 8   | **MindLyst web connects**                      | Run `cd mindlyst-native/web && npm run dev` → triage/brain-chat should use Azure OpenAI (not mock) |

---

## Cost Estimates

### Combined Cost (LysnrAI + MindLyst, MVP / Low Usage)

| Service                    | Pricing                        | LysnrAI | MindLyst | Combined         |
| -------------------------- | ------------------------------ | ------- | -------- | ---------------- |
| Cosmos DB (Serverless)     | $0.25/M RU + $0.25/GB          | ~$2     | ~$2      | ~$4              |
| Blob Storage (LRS)         | $0.018/GB                      | ~$0.10  | ~$0.10   | ~$0.20           |
| Azure OpenAI (GPT-4o-mini) | $0.15/M input + $0.60/M output | ~$5     | ~$5      | ~$10             |
| Speech (F0 free)           | 5h STT free                    | $0      | $0       | $0               |
| Key Vault                  | $0.03/10K ops                  | ~$0.03  | ~$0.03   | ~$0.06           |
| App Insights               | 5GB/month free                 | $0      | $0       | $0               |
| Notification Hub (Free)    | 1M pushes/month free           | $0      | $0       | $0               |
| **Total**                  |                                |         |          | **~$5–15/month** |

### Cost Savings from Sharing

- **Cosmos DB:** 1 account instead of 2 = no extra overhead (Serverless has no idle cost)
- **OpenAI:** 1 resource instead of 2 = no extra base cost (pay per token only)
- **Speech:** 1 resource instead of 2 = shared 5h free tier (may need S0 upgrade sooner)
- **Blob Storage:** 1 account instead of 2 = negligible savings but simpler key management
- **Estimated savings:** ~$3–8/month at MVP scale

---

## Resource Naming Convention

| Azure Resource      | Current Name              | Ideal Name        | Notes                                         |
| ------------------- | ------------------------- | ----------------- | --------------------------------------------- |
| Resource Group      | `rg-mywisprai`            | `rg-bytelyst`     | Move resources to new RG (Step 1)             |
| Cosmos DB Account   | `cosmos-mywisprai`        | `cosmos-bytelyst` | Can't rename — keep as-is                     |
| Blob Storage        | `bytelystblobs`           | `bytelystblobs`   | Already brand-neutral                         |
| Azure OpenAI        | `mywisprai-openai-sweden` | —                 | Can't rename — keep as-is                     |
| Speech Service      | `mywisprai-speech`        | —                 | Can't rename — keep as-is                     |
| Key Vault           | `kv-mywisprai`            | `kv-bytelyst`     | Can't rename — keep as-is                     |
| Notification Hub NS | `lysnnai`                 | `bytelyst-hub-ns` | Consider creating new NS later if desired     |
| App Insights        | `bytelyst-appinsights`    | —                 | Can recreate if you want workspace-based mode |

> **Azure resources cannot be renamed after creation.** The old names (`cosmos-mywisprai`, `mywisprai-*`, etc.) still work perfectly — they're just cosmetic. Only the RG can be "renamed" by moving resources to a new one.

---

## Az CLI Script (Copy-Paste-Ready)

> Run this from a machine with `az` CLI installed and logged in (`az login`).
> This script is **idempotent** — safe to re-run if interrupted.

```bash
#!/usr/bin/env bash
set -euo pipefail

# ─────────────────────────────────────────────────────────────
# ByteLyst — Azure Resource Setup Script
# Creates MindLyst deltas inside an existing shared resource group
# ─────────────────────────────────────────────────────────────

RG="rg-mywisprai"
COSMOS_ACCOUNT="cosmos-mywisprai"
COSMOS_DB="mindlyst"
STORAGE_ACCOUNT="bytelystblobs"

echo "=== 1/3  Cosmos DB: Ensure '$COSMOS_DB' database ==="
if az cosmosdb sql database show --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --name "$COSMOS_DB" >/dev/null 2>&1; then
  echo "  [exists] $COSMOS_DB"
else
  az cosmosdb sql database create --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --name "$COSMOS_DB" --query name -o tsv >/dev/null
  echo "  [created] $COSMOS_DB"
fi

echo ""
echo "=== 2/3  Cosmos DB: Ensure 12 MindLyst containers ==="
MINDLYST_CONTAINERS=(
  "users:/id"
  "brains:/userId"
  "brain_templates:/id"
  "memory_items:/userId"
  "actions:/memoryItemId"
  "entities:/memoryItemId"
  "reflections:/userId"
  "share_cards:/userId"
  "daily_briefs:/userId"
  "streaks:/userId"
  "notification_log:/userId"
  "brain_insights:/userId"
)

for spec in "${MINDLYST_CONTAINERS[@]}"; do
  name="${spec%%:*}"
  pk="${spec#*:}"
  if az cosmosdb sql container show --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --database-name "$COSMOS_DB" --name "$name" >/dev/null 2>&1; then
    echo "  [exists] $COSMOS_DB/$name"
  else
    az cosmosdb sql container create --account-name "$COSMOS_ACCOUNT" --resource-group "$RG" --database-name "$COSMOS_DB" --name "$name" --partition-key-path "$pk" --query name -o tsv >/dev/null
    echo "  [created] $COSMOS_DB/$name ($pk)"
  fi
done

echo ""
echo "=== 3/3  Blob Storage: Ensure MindLyst containers ==="
SA_KEY=$(az storage account keys list --resource-group "$RG" --account-name "$STORAGE_ACCOUNT" --query '[0].value' -o tsv)
for c in mindlyst-voice mindlyst-images mindlyst-exports; do
  created=$(az storage container create --name "$c" --account-name "$STORAGE_ACCOUNT" --account-key "$SA_KEY" --public-access off --query created -o tsv)
  if [ "$created" = "true" ]; then
    echo "  [created] blob/$c"
  else
    echo "  [exists] blob/$c"
  fi
done

echo ""
echo "=== Done ==="
```

### Optional: Key Vault secrets (run separately if needed)

```bash
set -euo pipefail

# Populate/refresh MindLyst secrets in Key Vault by reading current values from Azure resources.
# No rotation: this copies current keys/config into Key Vault.

KV_NAME="kv-mywisprai"
RG="rg-mywisprai"

COSMOS_ENDPOINT="$(az cosmosdb show -g "$RG" -n cosmos-mywisprai --query documentEndpoint -o tsv)"
COSMOS_KEY="$(az cosmosdb keys list -g "$RG" -n cosmos-mywisprai --query primaryMasterKey -o tsv)"
STORAGE_CONNECTION_STRING="$(az storage account show-connection-string -g "$RG" -n bytelystblobs --query connectionString -o tsv)"

OPENAI_ENDPOINT="$(az cognitiveservices account show -g "$RG" -n mywisprai-openai-sweden --query '{name:name,kind:kind,location:location,endpoint:properties.endpoint}' -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["endpoint"])')"
OPENAI_KEY="$(az cognitiveservices account keys list -g "$RG" -n mywisprai-openai-sweden --query key1 -o tsv)"

SPEECH_REGION="$(az cognitiveservices account show -g "$RG" -n mywisprai-speech --query '{name:name,location:location,kind:kind,sku:sku.name}' -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["location"])')"
SPEECH_KEY="$(az cognitiveservices account keys list -g "$RG" -n mywisprai-speech --query key1 -o tsv)"

NOTIF_PRIMARY_CONNECTION_STRING="$(az notification-hub authorization-rule list-keys -g "$RG" --namespace-name lysnnai --notification-hub-name mindlyst-hub --name DefaultFullSharedAccessSignature -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["primaryConnectionString"])')"
APPINSIGHTS_CONNECTION_STRING="$(az monitor app-insights component show --app bytelyst-appinsights -g "$RG" -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["connectionString"])')"

# Write secrets (suppress output to avoid printing values)
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-cosmos-endpoint --value "$COSMOS_ENDPOINT" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-cosmos-key --value "$COSMOS_KEY" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-cosmos-database --value mindlyst -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-blob-connection-string --value "$STORAGE_CONNECTION_STRING" -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-endpoint --value "$OPENAI_ENDPOINT" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-key --value "$OPENAI_KEY" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-deployment --value gpt-4o-mini -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-openai-api-version --value 2024-06-01 -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-speech-key --value "$SPEECH_KEY" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-speech-region --value "$SPEECH_REGION" -o none

az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-notification-hub-connection-string --value "$NOTIF_PRIMARY_CONNECTION_STRING" -o none
az keyvault secret set --vault-name "$KV_NAME" --name mindlyst-appinsights-connection-string --value "$APPINSIGHTS_CONNECTION_STRING" -o none

echo "✓ MindLyst secrets populated in $KV_NAME"
```

### Optional: Notification Hub (run when mobile app is ready)

```bash
# Create namespace (if needed) + MindLyst hub
NS="lysnnai"
RG="rg-mywisprai"

az notification-hub namespace create \
  --resource-group "$RG" \
  --name "$NS" \
  --location eastus \
  --sku Free \
  2>/dev/null && echo "✓ Namespace $NS created" \
  || echo "→ Namespace $NS already exists"

az notification-hub create \
  --resource-group "$RG" \
  --namespace-name "$NS" \
  --name mindlyst-hub \
  2>/dev/null && echo "✓ Hub mindlyst-hub created" \
  || echo "→ Hub mindlyst-hub already exists"
```

### Optional: Application Insights (shared telemetry)

```bash
RG="rg-mywisprai"
APP="bytelyst-appinsights"
LOC="eastus"

# Required once per subscription (App Insights may try to auto-register this provider)
az provider register --namespace Microsoft.OperationalInsights --wait

# Create (classic) App Insights component via ARM
az resource create \
  --resource-group "$RG" \
  --name "$APP" \
  --resource-type Microsoft.Insights/components \
  --is-full-object \
  --properties "{\"location\":\"$LOC\",\"kind\":\"web\",\"properties\":{\"Application_Type\":\"web\"}}"

# Get the connection string
APPINSIGHTS_CONNECTION_STRING="$(az resource show \
  --resource-group "$RG" \
  --name "$APP" \
  --resource-type Microsoft.Insights/components \
  --query properties.ConnectionString -o tsv)"

# Store it in Key Vault (treat as sensitive)
az keyvault secret set --vault-name kv-mywisprai --name mindlyst-appinsights-connection-string --value "$APPINSIGHTS_CONNECTION_STRING" -o none
echo "✓ Stored App Insights connection string in Key Vault: mindlyst-appinsights-connection-string"
```

---

## Bicep IaC (Replicate To Another Account)

If you want to replicate this shared infrastructure into a **different Azure subscription/account**, use the Bicep templates in:

- `infra/azure/bytelyst-shared/`

This folder also includes `infra/azure/bytelyst-shared/main.json` (compiled ARM template) which is tracked for easy diffing between runs:

```bash
az bicep build --file infra/azure/bytelyst-shared/main.bicep
```

### What the Bicep creates

- 1 Resource group (name is a parameter)
- Cosmos DB (serverless) + MindLyst `mindlyst` database + 12 containers
- (Optional) Cosmos DB empty database `mywisprai` (containers are app-specific; create separately)
- Storage account + `mindlyst-voice`, `mindlyst-images`, `mindlyst-exports` containers (private)
- Azure OpenAI (AI Foundry) account (optional: model deployment)
- Speech account
- Notification Hubs namespace + `mindlyst-hub` + auth rule
- Application Insights
- Key Vault (optional: auto-creates `mindlyst-*` secrets if `deployerObjectId` is provided)

If Key Vault secret creation is enabled, the template populates:

- `mindlyst-cosmos-endpoint`
- `mindlyst-cosmos-key`
- `mindlyst-cosmos-database`
- `mindlyst-openai-endpoint`
- `mindlyst-openai-key`
- `mindlyst-openai-deployment`
- `mindlyst-speech-key`
- `mindlyst-speech-region`
- `mindlyst-blob-connection-string`
- `mindlyst-notification-hub-connection-string`
- `mindlyst-appinsights-connection-string`

### Deploy (subscription scope)

1. `az login` and select the right subscription:

```bash
az login
az account set --subscription "<SUBSCRIPTION_ID>"
```

2. Register providers (first-time subscriptions):

```bash
az provider register --namespace Microsoft.DocumentDB --wait
az provider register --namespace Microsoft.Storage --wait
az provider register --namespace Microsoft.CognitiveServices --wait
az provider register --namespace Microsoft.KeyVault --wait
az provider register --namespace Microsoft.NotificationHubs --wait
az provider register --namespace Microsoft.Insights --wait
```

3. Get your Azure AD user objectId (used to grant Key Vault secret permissions):

```bash
az ad signed-in-user show --query id -o tsv
```

4. Edit the param file and pick **globally unique names**:

- `infra/azure/bytelyst-shared/params/staging.example.json`

5. Deploy:

```bash
az deployment sub create \
  --name bytelyst-shared-staging \
  --location eastus \
  --template-file infra/azure/bytelyst-shared/main.bicep \
  --parameters @infra/azure/bytelyst-shared/params/staging.example.json \
  --parameters deployerObjectId="<YOUR_OBJECT_ID>"
```

> **Note:** `openAiModelVersion` is region-dependent. If it’s empty, the template will create the OpenAI account but **skip** creating the `gpt-4o-mini` deployment (create it manually in Azure AI Foundry).

### Parameter mapping (current staging)

Use this mapping to understand how the current staging resource names relate to Bicep params. In a **different account**, you must choose new globally-unique names for most of these.

| Bicep parameter                | Current staging value     | Notes                                                                        |
| ------------------------------ | ------------------------- | ---------------------------------------------------------------------------- |
| `resourceGroupName`            | `rg-mywisprai`            | In a new subscription, choose a new RG name (example: `rg-bytelyst-staging`) |
| `cosmosAccountName`            | `cosmos-mywisprai`        | Globally unique; must change in another subscription                         |
| `storageAccountName`           | `bytelystblobs`           | Globally unique; must change in another subscription                         |
| `openAiAccountName`            | `mywisprai-openai-sweden` | Globally unique; must change in another subscription                         |
| `speechAccountName`            | `mywisprai-speech`        | Globally unique; must change in another subscription                         |
| `keyVaultName`                 | `kv-mywisprai`            | Globally unique; must change in another subscription                         |
| `notificationHubNamespaceName` | `lysnnai`                 | Globally unique; must change in another subscription                         |
| `mindlystHubName`              | `mindlyst-hub`            | Not globally unique (scoped to the namespace)                                |
| `appInsightsName`              | `bytelyst-appinsights`    | Must be unique within the RG                                                 |
| `createMywispraiDatabase`      | N/A                       | Creates an empty `mywisprai` database placeholder (LysnrAI, no containers)   |
| `deployerObjectId`             | N/A                       | If set, Bicep also creates Key Vault secrets (`mindlyst-*`)                  |

---

## What This Unlocks for MindLyst

Once all steps are complete, these implementation plan tasks are done:

```
Phase 0.3 (Azure Infrastructure):
[x] Reuse existing Cosmos DB account — add mindlyst database with 12 containers
[x] Reuse existing Blob Storage — add 3 mindlyst-prefixed containers
[x] Reuse existing Azure OpenAI (GPT-4o-mini) for triage + brain invocation
[x] Reuse existing Azure Speech Service for STT/TTS
[x] Add MindLyst secrets to existing Key Vault
[x] Create MindLyst notification hub
[x] Set up Application Insights for telemetry
```

**Next steps after provisioning:**

1. (Done) MindLyst web: Enable Azure OpenAI triage + brain-chat (`OPENAI_PROVIDER=azure` + `AZURE_OPENAI_*`)
2. (Done) MindLyst web: Cosmos persistence for `/api/memory` and `/api/brains` (`COSMOS_*`)
3. Wire additional MindLyst API routes to Cosmos as needed (reflections, briefs, notifications, etc.)
4. Implement voice capture STT pipeline with Azure Speech SDK
5. Implement blob upload for voice/image captures to `mindlyst-voice` / `mindlyst-images`
6. Implement push notifications via `mindlyst-hub` (once mobile app is ready)

---

## Quick Reference: Portal URLs

| Resource                | Direct Link                                               |
| ----------------------- | --------------------------------------------------------- |
| Resource Group          | `portal.azure.com → Resource groups → rg-mywisprai`       |
| Cosmos DB Data Explorer | `portal.azure.com → cosmos-mywisprai → Data Explorer`     |
| Blob Containers         | `portal.azure.com → bytelystblobs → Containers`           |
| OpenAI Studio           | `oai.azure.com` → select `mywisprai-openai-sweden`        |
| Speech Keys             | `portal.azure.com → mywisprai-speech → Keys and Endpoint` |
| Key Vault Secrets       | `portal.azure.com → kv-mywisprai → Secrets`               |

---

> **This document lives in `learning_ai_common_plat/docs/devops/`.** It is the single source of truth for Azure infrastructure setup shared by all products.
