docs: move Azure KV and portal setup docs into docs/devops/

This commit is contained in:
saravanakumardb1 2026-02-14 22:52:06 -08:00
parent 25594381ab
commit dcf6914419
2 changed files with 1182 additions and 0 deletions

View File

@ -0,0 +1,265 @@
# BytelystAI — Azure Key Vault & Secrets Rotation (MindLyst + LysnrAI)
> **Purpose:** Centralize **all secrets** in Azure Key Vault and establish a repeatable rotation process.
> **Scope:** Staging first (current RG: `rg-mywisprai`, vault: `kv-mywisprai`), then production.
> **Last updated:** 2026-02-14
---
## Goals
- Use **Azure Key Vault** as the source of truth for secrets (no secrets in docs, code, or git).
- Make secret rotation **low/no-downtime** using primary/secondary keys where available.
- Have a single checklist-driven runbook for rotation and incident response.
## Non-Goals (for now)
- Full CI/CD + automated rotation (well outline it, but can implement later).
- Client-side secret usage (mobile apps should not embed service keys).
---
## Current State (Staging)
- Resource Group: `rg-mywisprai`
- Key Vault: `kv-mywisprai`
- MindLyst Azure secrets are centralized in Key Vault under `mindlyst-*` (see “Secret Inventory” below).
- `docs/WINDSURF/AZURE_PORTAL_SETUP.md` does not contain secret values; it references Key Vault secret names and includes scripts to pull values when needed.
> Because secrets have been committed into git at least once, treat this as a **compromise** and rotate keys ASAP.
### Rotation TODO (Deferred)
As of **2026-02-14**, secrets were moved into Key Vault, but the underlying service keys have **not** been rotated yet.
- [ ] Rotate Cosmos DB keys (`cosmos-mywisprai`) and update `mindlyst-cosmos-key`
- [ ] Rotate Storage account keys (`bytelystblobs`) and update `mindlyst-blob-connection-string`
- [ ] Rotate Azure OpenAI keys (`mywisprai-openai-sweden`) and update `mindlyst-openai-key`
- [ ] Rotate Speech keys (`mywisprai-speech`) and update `mindlyst-speech-key`
- [ ] Rotate Notification Hub SAS keys (`lysnnai`/`mindlyst-hub`) and update `mindlyst-notification-hub-connection-string`
- [ ] If treating App Insights connection string as leaked: create a new App Insights component and update `mindlyst-appinsights-connection-string`
---
## Target State
### 1) Secret Sources
- **All secrets** stored in Key Vault (`kv-<env>`).
- Apps receive secrets via:
- Azure compute platform **Key Vault references** (preferred), or
- Environment variables injected at deploy time from Key Vault (acceptable), or
- A runtime secret loader using Managed Identity (only if platform references arent available).
### 2) Environment Separation
- Separate vaults per environment:
- `kv-bytelyst-staging`
- `kv-bytelyst-prod`
### 3) No Secrets in Repo
- `docs/WINDSURF/AZURE_PORTAL_SETUP.md` should contain:
- Resource names, endpoints, regions, container names, partition keys
- **Key Vault secret names** (not values)
- Commands/scripts to fetch values when needed
---
## Secret Inventory (Canonical)
Use these as the canonical secret names. If names already exist, keep them; otherwise create them.
### MindLyst (server-side)
| Category | Env var(s) today | Key Vault secret name | Notes |
|---|---|---|---|
| Cosmos | `COSMOS_KEY` | `mindlyst-cosmos-key` | Rotate using primary/secondary keys |
| Cosmos | `COSMOS_ENDPOINT` | `mindlyst-cosmos-endpoint` | Not secret, but ok to store |
| Cosmos | `COSMOS_DATABASE` | `mindlyst-cosmos-database` | Not secret, but ok to store |
| OpenAI | `AZURE_OPENAI_KEY` | `mindlyst-openai-key` | Rotate using key1/key2 |
| OpenAI | `AZURE_OPENAI_ENDPOINT` | `mindlyst-openai-endpoint` | Config |
| OpenAI | `AZURE_OPENAI_DEPLOYMENT` | `mindlyst-openai-deployment` | Config |
| OpenAI | `AZURE_OPENAI_API_VERSION` | `mindlyst-openai-api-version` | Config |
| Speech | `AZURE_SPEECH_KEY` | `mindlyst-speech-key` | Rotate using key1/key2 |
| Speech | `AZURE_SPEECH_REGION` | `mindlyst-speech-region` | Config |
| Blob | `AZURE_BLOB_CONNECTION_STRING` | `mindlyst-blob-connection-string` | Prefer SAS later; keys rotate key1/key2 |
| Notifications | `ANH_CONNECTION_STRING` | `mindlyst-notification-hub-connection-string` | Rotate SAS keys on the auth rule |
| Insights | `APPLICATIONINSIGHTS_CONNECTION_STRING` | `mindlyst-appinsights-connection-string` | If leaked, easiest “rotation” is new App Insights resource |
| Stripe | `STRIPE_SECRET_KEY` | `mindlyst-stripe-secret-key` | Not Azure; still belongs in Key Vault |
| Stripe | `STRIPE_WEBHOOK_SECRET` | `mindlyst-stripe-webhook-secret` | Not Azure; still belongs in Key Vault |
### LysnrAI (server-side)
Keep existing `wispr-*` secrets as-is, but standardize any missing ones to the same pattern:
- `wispr-azure-openai-endpoint`
- `wispr-azure-openai-key`
- `wispr-azure-openai-deployment`
- `wispr-azure-speech-key`
- `wispr-azure-speech-region`
---
## Access Model (Recommended)
Pick one model and stick to it per environment.
### Option A: Key Vault RBAC (recommended)
- Enable `enableRbacAuthorization=true` on the vault
- Grant:
- Humans: `Key Vault Secrets Officer` (or least privilege) for provisioning
- Apps (Managed Identity): `Key Vault Secrets User` for runtime reads
- Advantage: consistent with Azure RBAC governance
### Option B: Access Policies (simple for small teams)
- Keep access policies and grant explicit secret permissions to:
- The deployment user/service principal
- The apps managed identity
- Advantage: very explicit, fewer moving parts
---
## Migration Plan (Staging First)
### Phase 0: Stop The Bleed (Immediate)
- [ ] Rotate all secrets that have appeared in git history (see “Rotation Runbooks”).
- [ ] Reduce Key Vault blast radius:
- [ ] Enable Key Vault diagnostics to Log Analytics
- [ ] Review Key Vault access policies / RBAC assignments
- [x] Add missing MindLyst secrets to Key Vault:
- [x] `mindlyst-openai-endpoint`
- [x] `mindlyst-openai-deployment`
- [x] `mindlyst-openai-api-version`
- [x] `mindlyst-speech-region`
- [x] `mindlyst-notification-hub-connection-string`
- [x] `mindlyst-appinsights-connection-string`
- [x] Docs hygiene:
- [x] Remove plaintext secrets from `docs/WINDSURF/AZURE_PORTAL_SETUP.md` (leave names + scripts)
- [x] Add a short “fetch from Key Vault” snippet per secret category
### Phase 1: App Integration (Hosted Environments)
- [ ] Decide hosting target(s):
- [ ] MindLyst web: App Service / Container Apps / other
- [ ] LysnrAI backend: where it runs
- [ ] For each app, configure a Managed Identity and grant Key Vault read access:
- [ ] MindLyst web runtime identity can read `mindlyst-*` secrets
- [ ] LysnrAI runtime identity can read `wispr-*` secrets
- [ ] Inject secrets into the app runtime:
- [ ] Prefer platform Key Vault references (App Service Key Vault reference, Container Apps secrets from Key Vault)
- [ ] Fall back to deployment-time env var injection from Key Vault
- [ ] Validate with an end-to-end smoke test:
- [ ] `/api/triage` (Azure OpenAI)
- [ ] `/api/brain-chat` (Azure OpenAI)
- [ ] `/api/memory` + `/api/brains` (Cosmos)
### Phase 2: Operationalize (Rotation + Audits)
- [ ] Define rotation cadence:
- [ ] Staging: monthly (or after any leak)
- [ ] Prod: quarterly (or after any leak)
- [ ] Add a “rotation log”:
- [ ] Create `docs/WINDSURF/SECRETS_ROTATION_LOG.md` (optional) and record dates/owners
- [ ] Add automated checks:
- [ ] Secret scanning in CI (git-secrets / gitleaks)
- [x] Block commits containing patterns like `AccountKey=` / `SharedAccessKey=` (Husky: `scripts/secret-scan-staged.sh`)
---
## Rotation Runbooks
General rule: rotate by switching to the **secondary** key first (if supported), regenerate the primary, then switch back.
### Cosmos DB Key Rotation (Serverless)
Supports primary/secondary keys.
- [ ] Determine current key used by apps (usually “primary”).
- [ ] Update Key Vault `mindlyst-cosmos-key` to the **secondary** Cosmos key.
- [ ] Redeploy / restart apps that use Cosmos.
- [ ] Verify Cosmos reads/writes:
- [ ] `GET /api/memory`
- [ ] `POST /api/memory`
- [ ] Regenerate the Cosmos **primary** key in Azure.
- [ ] Update Key Vault `mindlyst-cosmos-key` back to the new primary (or keep secondary as active if you prefer).
- [ ] Redeploy / restart again.
Rollback: switch Key Vault secret back to the other key and restart.
### Storage Account Key Rotation
Supports key1/key2.
- [ ] Update Key Vault `mindlyst-blob-connection-string` to use the **other** storage key.
- [ ] Redeploy / restart apps that use Blob.
- [ ] Regenerate the original key in Azure.
- [ ] Update Key Vault secret again (optional switch-back).
### Azure OpenAI Key Rotation
Supports key1/key2.
- [ ] Update Key Vault `mindlyst-openai-key` to use the **other** key.
- [ ] Verify `/api/triage` and `/api/brain-chat`.
- [ ] Regenerate the old key in Azure.
### Speech Key Rotation
Supports key1/key2.
- [ ] Update Key Vault `mindlyst-speech-key` to use the **other** key.
- [ ] Verify STT/TTS pipeline (when implemented).
- [ ] Regenerate the old key in Azure.
### Notification Hub SAS Key Rotation
Rotate the SAS keys on the authorization rule used by `ANH_CONNECTION_STRING`.
- [ ] Switch to the **secondary** connection string for the auth rule (update Key Vault `mindlyst-notification-hub-connection-string`).
- [ ] Verify push send (server-side) and device receive (once implemented).
- [ ] Regenerate the primary SAS key on the auth rule.
- [ ] Switch back if desired.
### Application Insights “Rotation”
App Insights doesnt have an easy “rotate key” flow thats comparable to key1/key2 (treat the connection string as sensitive anyway).
Preferred approach if leaked:
- [ ] Create a new App Insights resource (workspace-based recommended).
- [ ] Update Key Vault `mindlyst-appinsights-connection-string`.
- [ ] Redeploy apps.
- [ ] Keep old App Insights temporarily for historical traces, then delete if desired.
### Stripe Keys Rotation (External)
- [ ] Rotate keys in Stripe dashboard.
- [ ] Update Key Vault `mindlyst-stripe-secret-key` / `mindlyst-stripe-webhook-secret`.
- [ ] Redeploy apps.
---
## Implementation Tasks (Checkbox List)
### Key Vault Completion (Staging)
- [ ] Add missing `mindlyst-*` secrets to `kv-mywisprai` (see inventory)
- [ ] Add missing `wispr-*` secrets to `kv-mywisprai` (if needed)
- [ ] Ensure the `mindlyst-*` secret names match what Bicep creates (`infra/azure/bytelyst-shared/`)
### App Config Changes
- [ ] Add a “Key Vault-first” config strategy for hosted environments:
- [ ] App Service Key Vault references (if using App Service)
- [ ] Container Apps secrets from Key Vault (if using Container Apps)
- [ ] Ensure no app requires a secret at build-time (only at runtime)
### Repo Hygiene
- [ ] After rotation, remove plaintext secret values from `docs/WINDSURF/AZURE_PORTAL_SETUP.md`
- [ ] Add CI secret scanning
- [ ] (Optional) Rewrite git history to remove old secrets (only if youre prepared for the repo impact)

View File

@ -0,0 +1,917 @@
# BytelystAI — 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-02-14
>
> **Security note (staging):** This document does **not** include secret values. Store secrets in Azure Key Vault (`kv-mywisprai`) and reference them by name (see **MindLyst Environment Variables** and **Key Vault** sections). Rotation is deferred; see `docs/WINDSURF/AZURE_KEY_VAULT_AND_SECRETS_ROTATION.md`.
---
## 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: 10 containers — existing)
│ └── mindlyst (database: 12 containers — MindLyst)
├── 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)
│ ├── wispr-* (existing secrets)
│ └── mindlyst-* (MindLyst secrets) ← NEW
├── 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` (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` (existing)
- `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 (existing):
- `wispr-azure-openai-deployment`
- `wispr-azure-openai-endpoint`
- `wispr-azure-openai-key`
- `wispr-azure-speech-key`
- `wispr-azure-speech-region`
List whats 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 (~510 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 whats 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` | Existing OpenAI deployment name |
| `wispr-azure-openai-endpoint` | Existing OpenAI endpoint |
| `wispr-azure-openai-key` | Existing OpenAI key |
| `wispr-azure-speech-key` | Existing Speech key |
| `wispr-azure-speech-region` | Existing Speech region |
---
## 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"] = "mywisprai-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/WINDSURF/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` 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** | | | | **~$515/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:** ~$38/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
# ─────────────────────────────────────────────────────────────
# BytelystAI — 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 its 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 (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 is shared between both repos.** When updating Azure infrastructure, update this doc in `learning_multimodal_memory_agents` and reference it from `learning_voice_ai_agent`.