Skip to content

Commit aa57d36

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/competent-engelbart-dd7518
2 parents 037820d + ed8414d commit aa57d36

28 files changed

Lines changed: 2432 additions & 310 deletions

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
--cov-report=xml:coverage.xml
4141
- name: Upload backend coverage
4242
if: always()
43-
uses: actions/upload-artifact@v6
43+
uses: actions/upload-artifact@v7.0.1
4444
with:
4545
name: backend-coverage
4646
path: coverage.xml
@@ -50,7 +50,7 @@ jobs:
5050
run: uv run python -m src.evals.regression_gate --output reports/ai-regression-${{ github.sha }}.json
5151
- name: Upload AI regression score
5252
if: always()
53-
uses: actions/upload-artifact@v6
53+
uses: actions/upload-artifact@v7.0.1
5454
with:
5555
name: ai-regression-${{ github.sha }}
5656
path: reports/ai-regression-${{ github.sha }}.json
@@ -64,7 +64,7 @@ jobs:
6464
working-directory: ui
6565
steps:
6666
- uses: actions/checkout@v5
67-
- uses: actions/setup-node@v5
67+
- uses: actions/setup-node@v7
6868
with:
6969
node-version: 24
7070
cache: npm
@@ -79,7 +79,7 @@ jobs:
7979
run: npm run test:coverage
8080
- name: Upload UI coverage
8181
if: always()
82-
uses: actions/upload-artifact@v6
82+
uses: actions/upload-artifact@v7.0.1
8383
with:
8484
name: ui-coverage
8585
path: ui/coverage/
@@ -90,7 +90,7 @@ jobs:
9090
run: npx playwright test
9191
- name: Upload Playwright failure artifacts
9292
if: failure()
93-
uses: actions/upload-artifact@v6
93+
uses: actions/upload-artifact@v7.0.1
9494
with:
9595
name: playwright-failure-artifacts
9696
path: |
@@ -115,7 +115,7 @@ jobs:
115115
output: cortex-sbom.cdx.json
116116
scanners: vuln
117117
- name: Upload container SBOM
118-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
118+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
119119
with:
120120
name: cortex-sbom-${{ github.sha }}
121121
path: cortex-sbom.cdx.json

cloudrun/service.yaml

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,12 @@ spec:
5454
value: "text-embedding-3-small"
5555
- name: EMBEDDING_DIMENSIONS
5656
value: "1536"
57-
- name: OPENAI_API_KEY
57+
- name: PROVIDER_CONFIG_JSON
5858
valueFrom:
5959
secretKeyRef:
60-
name: openai-api-key
60+
name: provider-config
6161
key: latest
6262
# --- Search ---
63-
- name: TAVILY_API_KEY
64-
valueFrom:
65-
secretKeyRef:
66-
name: tavily-api-key
67-
key: latest
6863
# --- Graph store (Neo4j Aura) ---
6964
# URI and username are not sensitive; only the password lives in Secret Manager.
7065
- name: NEO4J_URI
@@ -93,16 +88,28 @@ spec:
9388
- name: READINESS_REQUIRE_NEO4J
9489
value: "true"
9590
# --- Inngest ---
96-
- name: INNGEST_EVENT_KEY
91+
- name: INNGEST_CONFIG_JSON
9792
valueFrom:
9893
secretKeyRef:
99-
name: inngest-event-key
94+
name: inngest-config
10095
key: latest
101-
- name: INNGEST_SIGNING_KEY
96+
# --- Billing (Stripe) ---
97+
- name: BILLING_CONFIG_JSON
10298
valueFrom:
10399
secretKeyRef:
104-
name: inngest-signing-key
100+
name: billing-config
105101
key: latest
102+
# --- Redis cache ---
103+
# REDIS_URL is bundled into PROVIDER_CONFIG_JSON to stay within the
104+
# Secret Manager free active-version allowance.
105+
- name: REDIS_CACHE_TTL_DB_LIST_SECONDS
106+
value: "30"
107+
- name: STRIPE_SUCCESS_URL
108+
value: "https://cortex-ai-prod.vercel.app/billing/success"
109+
- name: STRIPE_CANCEL_URL
110+
value: "https://cortex-ai-prod.vercel.app/billing/cancel"
111+
- name: STRIPE_PORTAL_RETURN_URL
112+
value: "https://cortex-ai-prod.vercel.app"
106113
# --- CORS ---
107114
- name: CORS_ORIGINS
108115
value: '["https://cortex-ai-prod.vercel.app"]'
@@ -113,9 +120,9 @@ spec:
113120
name: internal-dispatch-secret
114121
key: latest
115122
# --- Observability ---
116-
# ponytail: observability off for the free-tier deploy; re-add
117-
# langsmith-api-key / langfuse-* secrets when tracing matters.
123+
- name: SENTRY_ENVIRONMENT
124+
value: "production"
118125
- name: LANGSMITH_TRACING
119126
value: "false"
120127
- name: LANGFUSE_ENABLED
121-
value: "false"
128+
value: "true"

docs/architecture.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ Routing policy:
6666

6767
- Greetings and other social turns normally resolve directly.
6868
- Weak or empty RAG context does not automatically trigger web search.
69+
- Missing pre-retrieved RAG context does not mean resources are absent; explicit requests about uploaded documents route to `answer_from_rag`, then retrieval runs.
70+
- Custom-agent routing receives the agent name, description, and linked/attached resource counts so short domain questions can still select `answer_from_rag` and retain citations.
6971
- Web search is selected for external, fresh, or otherwise web-dependent information.
7072
- A URL in the message or history is available context, not an automatic fetch instruction.
7173
- Direct URL fetching happens only when inspecting the resource is necessary.

docs/billing.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Cortex uses Stripe Hosted Checkout, signed webhooks, and the customer portal for
44

55
## Configuration
66

7-
Required variables:
7+
Required local-development variables:
88

99
- `STRIPE_SECRET_KEY`
1010
- `STRIPE_WEBHOOK_SECRET`
@@ -13,6 +13,11 @@ Required variables:
1313
- `STRIPE_CANCEL_URL`
1414
- `STRIPE_PORTAL_RETURN_URL`
1515

16+
In production, Cloud Run uses one `BILLING_CONFIG_JSON` Secret Manager value containing
17+
`stripe_secret_key`, `stripe_webhook_secret`, and `stripe_pro_price_id`; the three redirect
18+
URLs remain ordinary environment variables. This keeps the Stripe credentials within one
19+
active secret version.
20+
1621
Store production secret values in Google Secret Manager. See [Production configuration](env-vars-production.md).
1722

1823
## Webhook setup
@@ -46,4 +51,3 @@ flowchart LR
4651
```
4752

4853
Webhook events are the authoritative source for subscription state. The backend verifies the Stripe signature before updating `user_subscriptions`; research and chat quota guards then read the synchronized subscription and usage state.
49-

docs/deployment.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ Registered functions:
116116
- `research-run` — triggered by `research/run.requested`
117117
- `outbox-dispatcher` — scheduled every two minutes
118118

119+
After syncing the functions, enable the production failure and missing-dispatcher email alerts described in [Production monitoring](observability.md#inngest-alert-activation).
120+
121+
## Alerting activation
122+
123+
Provision or refresh the Cloud Monitoring baseline after the backend service exists:
124+
125+
```bash
126+
GCP_PROJECT=<project-id> ALERT_EMAIL=<operator-email> ./scripts/setup_alerting.sh --dry-run
127+
GCP_PROJECT=<project-id> ALERT_EMAIL=<operator-email> ./scripts/setup_alerting.sh
128+
```
129+
130+
The script configures the `/health` uptime check, 5xx burst policy, runtime/probe log alert, and email notification channel. Verify the email channel and send a test notification before treating the alarms as operational.
131+
119132
## Supabase migrations
120133

121134
The production workflow applies migrations automatically. For manual maintenance:
@@ -130,4 +143,3 @@ npx supabase db push
130143
Every pull request and push to `main` builds the production container. CI scans the repository and image with Trivy, blocking HIGH or CRITICAL findings that have an available fix as well as serious configuration and secret findings.
131144

132145
CI also generates a CycloneDX container SBOM named `cortex-sbom-<commit-sha>` and retains it for 30 days. See [Testing and quality](testing-and-quality.md) for the complete release gates.
133-

docs/env-vars-production.md

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ All secrets should be stored in **Google Secret Manager** and referenced via `va
88

99
| Env Var | Secret Manager | Notes |
1010
|---|---|---|
11-
| `OPENAI_API_KEY` | yes | Required when `LLM_PROVIDER=openai` |
11+
| `PROVIDER_CONFIG_JSON` | yes | Secret JSON with provider, Redis, Langfuse, LangSmith, and Sentry settings: `openai_api_key`, `tavily_api_key`, `redis_url`, `langfuse_public_key`, `langfuse_secret_key`, `langfuse_base_url`, `langsmith_api_key`, `langsmith_project`, `langsmith_endpoint`, `langsmith_redaction_mode`, `langsmith_sampling_rate`, `langsmith_tracing`, and `sentry_dsn`. Required when `LLM_PROVIDER=openai`. |
1212
| `OPENROUTER_API_KEY` | yes | Required when `LLM_PROVIDER=openrouter` |
1313
| `LLM_PROVIDER` | no | `openai` or `openrouter` |
1414
| `OPENAI_MODEL` | no | e.g. `gpt-4o-mini` |
1515
| `ROUTER_TIMEOUT_SECONDS` | no | Maximum duration of each router LLM request; production uses `10.0` |
1616
| `EMBEDDING_PROVIDER` | no | `openai` |
1717
| `EMBEDDING_MODEL` | no | `text-embedding-3-small` |
1818
| `EMBEDDING_DIMENSIONS` | no | `1536` |
19-
| `TAVILY_API_KEY` | yes | |
2019
| `ALPHA_VANTAGE_API_KEY` | yes | Required when `ASSET_PRICE_PROVIDER=alphavantage_mcp` |
2120
| `ASSET_PRICE_PROVIDER` | no | `alphavantage_mcp` |
2221
| `ALPHA_VANTAGE_MCP_URL` | no | Optional full remote MCP URL override |
@@ -32,12 +31,9 @@ All secrets should be stored in **Google Secret Manager** and referenced via `va
3231
| `READINESS_REQUIRE_SUPABASE` | no | Set to `true` in production; `/ready` returns `503` if Supabase is missing or unavailable |
3332
| `READINESS_REQUIRE_NEO4J` | no | Set to `true` in production; `/ready` returns `503` if Neo4j is missing or unavailable |
3433
| `READINESS_TIMEOUT_SECONDS` | no | Per-dependency probe timeout; defaults to `2.0` seconds |
35-
| `INNGEST_EVENT_KEY` | yes | From Inngest dashboard → Keys |
36-
| `INNGEST_SIGNING_KEY` | yes | From Inngest dashboard → Keys. **Must not be empty in prod.** |
37-
| `REDIS_URL` | yes | Upstash `rediss://` URL |
38-
| `STRIPE_SECRET_KEY` | yes | |
39-
| `STRIPE_WEBHOOK_SECRET` | yes | Update Stripe webhook URL to `https://<cloud-run-url>/api/billing/webhook` after first deploy |
40-
| `STRIPE_PRO_PRICE_ID` | no | |
34+
| `INNGEST_CONFIG_JSON` | yes | Secret JSON with `inngest_event_key` and `inngest_signing_key` from the Inngest dashboard. |
35+
| `REDIS_CACHE_TTL_DB_LIST_SECONDS` | no | Cache TTL for DB-heavy list/read-all responses; production uses `30`. `REDIS_URL` is supplied through `PROVIDER_CONFIG_JSON`. |
36+
| `BILLING_CONFIG_JSON` | yes | Secret JSON with `stripe_secret_key`, `stripe_webhook_secret`, and `stripe_pro_price_id`. This bundles Stripe credentials into one Secret Manager version. |
4137
| `STRIPE_SUCCESS_URL` | no | `https://<frontend>/billing/success` |
4238
| `STRIPE_CANCEL_URL` | no | `https://<frontend>/billing/cancel` |
4339
| `STRIPE_PORTAL_RETURN_URL` | no | `https://<frontend>` |
@@ -49,12 +45,12 @@ All secrets should be stored in **Google Secret Manager** and referenced via `va
4945
| Env Var | Secret Manager | Notes |
5046
|---|---|---|
5147
| `COHERE_API_KEY` | yes | Enables cross-encoder reranking |
52-
| `LANGFUSE_PUBLIC_KEY` | yes | |
53-
| `LANGFUSE_SECRET_KEY` | yes | |
54-
| `LANGFUSE_ENABLED` | no | Set to `true` only when both LangFuse keys are configured; otherwise feedback remains stored locally |
48+
| `LANGFUSE_ENABLED` | no | Production sets this to `true`; Langfuse credentials and base URL are supplied through `PROVIDER_CONFIG_JSON`. |
49+
| `LANGSMITH_*` | no | LangSmith tracing, API key, project, endpoint, redaction mode, and sampling rate are supplied through `PROVIDER_CONFIG_JSON`. |
5550
| `LANGFUSE_ENV` | no | `prod` |
5651
| `LANGFUSE_RELEASE` | no | Git SHA or semver, e.g. `v1.2.3` |
57-
| `SENTRY_DSN` | yes | Error tracking; unset disables it entirely |
52+
| `SENTRY_DSN` | no | Local/split-variable fallback. Production stores `sentry_dsn` inside `PROVIDER_CONFIG_JSON`; unset disables Sentry entirely. |
53+
| `SENTRY_ENVIRONMENT` | no | Sentry issue environment; production deploys set this to `production` |
5854
| `RATE_LIMIT_DEFAULT` | no | Per-IP request limit, e.g. `60/minute` (default) |
5955

6056
### Variables that must NOT be set in production
@@ -91,19 +87,16 @@ PROJECT=cortex-496709
9187
REGION=your-region
9288

9389
secrets=(
94-
openai-api-key
95-
tavily-api-key
90+
provider-config
9691
cohere-api-key
9792
neo4j-uri
9893
neo4j-username
9994
neo4j-password
10095
supabase-secret-key
10196
supabase-jwt-secret
102-
inngest-event-key
103-
inngest-signing-key
97+
inngest-config
10498
redis-url
105-
stripe-secret-key
106-
stripe-webhook-secret
99+
billing-config
107100
internal-dispatch-secret
108101
langfuse-public-key
109102
langfuse-secret-key
@@ -132,7 +125,22 @@ After the Cloud Run service is deployed:
132125
3. Set **Serve URL** to `https://<cloud-run-url>/api/inngest`
133126
4. Confirm **Event Key** and **Signing Key** match what was set in Cloud Run secrets
134127
5. Click **Sync** — all three functions should appear: `rag-ingestion`, `research-run`, `outbox-dispatcher`
135-
6. Verify `outbox-dispatcher` shows a cron trigger of `* * * * *`
128+
6. Verify `outbox-dispatcher` shows a cron trigger of `*/2 * * * *`
129+
130+
Configure email alerts for terminal failures in every registered function and for no successful `outbox-dispatcher` run within six minutes. A dispatcher run returning zero events is healthy and must count as successful.
131+
132+
## Alerting post-deploy wiring
133+
134+
After creating a Sentry Python/FastAPI project, add its DSN as the `sentry_dsn` field in the existing `provider-config` JSON secret. This keeps production at six active Secret Manager versions. Then deploy and configure a Sentry email issue alert for new or regressed production issues with a 30-minute per-issue cooldown.
135+
136+
Provision Google Cloud alerts with:
137+
138+
```bash
139+
GCP_PROJECT=<project-id> ALERT_EMAIL=<operator-email> ./scripts/setup_alerting.sh --dry-run
140+
GCP_PROJECT=<project-id> ALERT_EMAIL=<operator-email> ./scripts/setup_alerting.sh
141+
```
142+
143+
Verify the email channel and test notification delivery in Cloud Monitoring. The setup script is safe to rerun and updates resources with matching display names.
136144

137145
---
138146

0 commit comments

Comments
 (0)