Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ MAX_REQUEST_BODY_BYTES=1048576
REDIS_HOST=
REDIS_PORT=6379

# Mira partner API (optional — leave unset to disable the bridge)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Correct the Mira key disablement wording

This comment says leaving THO_API_KEY_MIRA unset disables the Mira bridge, but main.require_partner_api_key() accepts the primary THO_API_KEY and any THO_API_KEY_* for all protected /api/v1/* routes, including /api/v1/mira/* as wired in main.py. In environments that still have a primary or another partner key configured, operators could believe Mira is disabled while the endpoints remain reachable; please describe this as only omitting the Mira-specific credential or add a real Mira route gate.

Useful? React with 👍 / 👎.

# Used by /api/v1/mira/* endpoints. Any env var matching THO_API_KEY_* is
# accepted by require_partner_api_key(); store the actual value in Secret Manager.
THO_API_KEY_MIRA=

# Mira Telegram bot integration (optional — leave unset to disable)
# TELEGRAM_BOT_TOKEN powers the THO bot. KIMI_RELAY_CHAT_ID is the legacy
# fallback; MIRA_GROUP_ID is the dedicated group for the Mira AI agent.
Expand All @@ -82,8 +87,11 @@ MIRA_GROUP_ID=
# GITHUB_WEBHOOK_SECRET validates inbound X-Hub-Signature-256 headers on
# /api/github/mira/webhook. PARTNER_WEBHOOK_URL_MIRA is where THO sends
# HMAC-signed partner events for the Mira bridge API.
# PARTNER_WEBHOOK_SIGNING_KEY is the shared HMAC secret for outbound webhooks
# (used by tools/partner_webhooks.py and shared across all partner webhooks).
GITHUB_WEBHOOK_SECRET=
PARTNER_WEBHOOK_URL_MIRA=
PARTNER_WEBHOOK_SIGNING_KEY=

# DNS/MX cutover verification (optional — defaults to texashomeoutlet.com targets)
CUTOVER_DOMAIN=texashomeoutlet.com
Expand Down
171 changes: 171 additions & 0 deletions docs/integration/gcp-mira-pivot-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# GCP-Native Mira Integration — Design Spec

**Status:** Scoped, not implemented
**Supersedes:** `docs/integration/notion-bridge.md` (Notion source path)
**Related:** PR #188 in-app Ops Copilot, `mira_routes.py`, `tools/notion_client.py`

## 1. Goal

Move the Mira bridge (`/api/v1/mira/*`) off the Notion "Delivery Tracker / CS survey" source and onto a GCP-native operational data layer that the in-app **Ops Copilot** can read and write. The existing response schema stays unchanged and PII-redacted; only the `source` field changes from `"notion"` to `"gcp"`.

## 2. Why pivot

- Notion requires manual workspace sharing, brittle column-name mapping, and a third-party token outside GCP IAM.
- PR #188 already added an in-app Ops Copilot that can query the same operational data; keeping that data in Firestore/Cloud SQL lets the copilot mutate it natively.
- Firestore is already the canonical store for customers, deals, inventory, and leads. Installations and feedback should live there too.

## 3. Current state

| Endpoint | Today | Source switch logic |
|----------|-------|---------------------|
| `/api/v1/mira/installations/*` | Notion Delivery Tracker if configured, else Firestore `service_requests` | `notion_client.is_installations_configured()` |
| `/api/v1/mira/feedback/*` | Notion CS survey DB if configured, else Firestore `feedback` | `notion_client.is_feedback_configured()` |
| `/api/v1/mira/leads/*` | Firestore `leads` | direct |
| `/api/v1/mira/appointments/*` | Firestore `appointments` | direct |
| `/api/v1/mira/notify` | Telegram | env-gated |

The Notion path is **additive/env-gated**; removing it does not break current behavior when the env vars are unset.

## 4. Proposed architecture

```
┌─────────────────┐ ┌──────────────────────────────────────┐
│ GitHub / Ops │────▶│ Cloud Run project-go-forward │
│ Copilot UI │ │ (existing Mira routes) │
└─────────────────┘ └──────────────┬───────────────────────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
Firestore Cloud Pub/Sub Cloud SQL
(canonical) (event ingestion) (optional mirror
service_requests • mira-installations for complex queries)
feedback • mira-feedback
activities • github-events
```

### 4.1 Data store — Firestore as canonical

Reuse the existing Firestore collections and enrich their schema to match what Notion currently provides.

**`service_requests` (installations)**
```
status: str # e.g. NEW, SCHEDULED, COMPLETED, WARRANTY
issue_type: str # e.g. DELIVERY, SETUP, WARRANTY_REPAIR
is_warranty_claim: bool
warranty_status: str | None
assigned_contractor: str | None
deal_id: str | None
created_at: datetime
updated_at: datetime
source: "gcp" | "import" | "notion"
```

**`feedback` (CS surveys)**
```
rating: int | None # 1-5
sentiment: str | None # positive | neutral | negative
source: str | None # e.g. POST_INSTALL_EMAIL, PHONE_FOLLOWUP
deal_id: str | None
created_at: datetime
updated_at: datetime
```

These shapes are already what `mira_routes.py` expects when it falls back to Firestore.

### 4.2 Source module refactor

Create `tools/gcp_mira_source.py` with the same public interface as `tools/notion_client.py`:

```python
is_installations_configured() -> bool # always True on GCP
is_feedback_configured() -> bool # always True on GCP
fetch_installations(limit=1000) -> list[dict]
fetch_feedback(limit=1000) -> list[dict]
```

Change `mira_routes.py` to import `gcp_mira_source` as the preferred source and keep `notion_client` as an opt-in legacy fallback (or remove it in phase 2). The default `source` becomes `"gcp"`.

### 4.3 Event ingestion — Cloud Pub/Sub

For external systems (GitHub, DocuSeal e-sign, Resend events, partner webhooks), add an async Pub/Sub topic so Mira/Ops Copilot gets notified without blocking the HTTP request:

| Topic | Purpose | Producer |
|-------|---------|----------|
| `mira-github-events` | PR/issue/deploy status | `github_mira_trigger.py` (optional async) |
| `mira-installations` | New/updated service request | Ops Copilot, staff UI, scheduled job |
| `mira-feedback` | New CS survey | Resend webhook, Ops Copilot |

Add a `POST /api/v1/mira/pubsub` endpoint (authenticated via Pub/Sub OIDC token) that updates Firestore. This keeps the public API read-only and resilient.

### 4.4 Optional Cloud SQL mirror

If Firestore aggregation becomes expensive for the monitoring check, mirror `service_requests` and `feedback` to a small Cloud SQL (PostgreSQL) instance via a nightly Cloud Scheduler job or CDC. The Mira endpoints would still read Firestore by default; Cloud SQL would be used only for analytics/historical dashboards.

### 4.5 Ops Copilot integration

PR #188 introduced the in-app Ops Copilot. Extend it with:
- `tools/ops_copilot.py` functions `create_service_request`, `update_service_request`, `record_feedback`.
- Firestore rules/audit logging identical to existing CRM writes.
- Admin UI at `/admin/ops` for staff to edit installations/feedback without Notion.

## 5. Env vars

No new secrets are required for the Firestore path (it uses the existing GCP service account). If Pub/Sub is added:

```bash
# Optional — enable async event ingestion
MIRA_PUBSUB_TOPIC_PROJECT=tho-ai-agent
MIRA_PUBSUB_INSTALLATIONS_TOPIC=mira-installations
MIRA_PUBSUB_FEEDBACK_TOPIC=mira-feedback
MIRA_PUBSUB_SUBSCRIPTION=mira-events-pull
```

If Cloud SQL is used later:

```bash
MIRA_CLOUD_SQL_INSTANCE=tho-ai-agent:us-central1:tho-ops
MIRA_CLOUD_SQL_DATABASE=mira_ops
# Password via Secret Manager, mounted as env var
MIRA_CLOUD_SQL_PASSWORD_SECRET=mira-cloud-sql-password:latest
```

## 6. Migration plan

1. **Phase 1 — Stop preferring Notion (safe, no deploy risk):**
- Add `tools/gcp_mira_source.py`.
- Modify `mira_routes.py` to prefer Firestore/`gcp_mira_source` and report `"source": "gcp"`.
- Keep Notion env-gated but lower its precedence to fallback.
- Tests: extend `tests/test_mira_routes.py` to assert `source == "gcp"` when Notion is unset.

2. **Phase 2 — Seed existing data:**
- Export current Notion Delivery Tracker / CS survey rows to JSON.
- Run a one-time idempotent import script `tools/migrate_notion_to_firestore.py`.
- Verify counts match via `/api/v1/mira/installations/summary` and `/api/v1/mira/feedback/summary`.

3. **Phase 3 — Add async ingestion (optional):**
- Create Pub/Sub topics and a push subscription to `POST /api/v1/mira/pubsub`.
- Update `github_mira_trigger.py` to optionally publish instead of sending Telegram directly.

4. **Phase 4 — Deprecate Notion:**
- Remove `tools/notion_client.py` and Notion env vars from `.env.example`.
- Delete `docs/integration/notion-bridge.md` or mark it archived.

## 7. Safety / reversibility

- Default `INVENTORY_SOURCE=legacy`-style flag is not needed here because Notion is already opt-in; unsetting `NOTION_TOKEN` instantly reverts to Firestore.
- Every write path is audited to the `activities` collection.
- PII remains redacted on Mira endpoints; only `deal_id`, status, and operational fields are exposed.
- No external sends: Pub/Sub is internal GCP; Telegram remains disabled when `MIRA_GROUP_ID` is unset.

## 8. Testing checklist

- [ ] `tests/test_mira_routes.py` passes with `source == "gcp"`.
- [ ] `scripts/live_monitoring_check.py` still reports all Mira endpoints healthy.
- [ ] Migration script dry-run shows correct row counts before writing.
- [ ] Ops Copilot smoke test creates a service request and feedback record.

## 9. Decision needed from Ari

1. Confirm we should **deprecate Notion** as the installations/feedback source.
2. Decide whether to invest in **Pub/Sub async ingestion** now or keep synchronous webhooks.
3. Decide whether a **Cloud SQL mirror** is justified for monitoring/dashboard queries.
3 changes: 2 additions & 1 deletion docs/integration/notion-bridge.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Notion → Mira Bridge

**Workstream:** `notion-bridge`
**Status:** Implemented / ready for env wiring
**Status:** Implemented / ready for env wiring
**⚠️ Future:** This Notion-sourced path is planned for deprecation in favor of a GCP-native operational data layer. See [`gcp-mira-pivot-spec.md`](./gcp-mira-pivot-spec.md).

## What it does

Expand Down
Loading