File size: 24,484 Bytes
799e1e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# Corvus β€” Platform Blueprint v2.0

*Corvus Β· Platform Blueprint v2.0 Β· August 2026 Β· Confidential*
*Solo-track-owned document β€” AI Engineer reference, shared with SDE co-founder*

**This version supersedes v1.0 (June 2026).** Where they disagree, this document
reflects what was actually built and decided since β€” not what was originally planned.
Section 0 explains what changed and why, so nothing here reads as a silent reversal.

---

## 0. What changed since v1.0, and why

v1.0 was written pre-code, as a complete company blueprint. Since then Corvus went
through a real build (`PLAN.md`, Wave 1), a code review (`docs/code-review.md`, 28
verified findings), and a scoping pass specifically on the AI-reasoning side
(Β§7 below). The result is a narrower, more honest v2. Nothing in the pivot changes
the core thesis β€” it changes the plan for reaching it.

| v1.0 said | v2.0 says | Why |
|---|---|---|
| Salesforce + HubSpot + CSV connectors, bespoke MCP servers you write | **MCP-as-connectors**: reuse existing community MCP servers (Postgres-MCP first) | Writing and maintaining bespoke connectors was the single biggest scope item in v1.0. Existing MCP servers exist for most of this; Corvus's job is to be a good MCP *host*, not a connector author. |
| Railway / Fly.io β†’ AWS at scale | **Self-host on Hetzner, staged in waves** | Chosen deliberately over "managed-first." Cheaper, and data-sovereignty positioning (Β§4) is stronger when Corvus's own infra is self-hosted, not merely BYOK at the model layer. |
| 6-layer stack incl. ClickHouse, S3, Vault, ~12 services from day one | **4 containers at MVP** (Postgres+pgvector, Redis, app, Caddy); ClickHouse/observability/MinIO are Wave 3 | Standing up 12 services before a product exists was identified as the exact trap the original blueprint warned against in its own risk section. Staged waves fix that. |
| Subagent orchestration, hooks system, episodic/semantic memory store | **Deferred, not cancelled.** Wave 1 ships one bounded reasoning loop (`ask.ts`, `MAX_TURNS = 8`) | Anthropic's own finding (Β§7.1) and Corvus's own build experience agree: the loop itself should stay simple. Complexity earns its place once the semantic layer under it is solid β€” not before. |
| CORVUS.md as a literal config file the harness parses at runtime | **CORVUS.md as this document** β€” a human/AI-readable blueprint, not a runtime artifact | The functional equivalent β€” the tenant's actual business definitions β€” now lives in the **Metric Registry** and **Entity Catalog** (Β§7.3–7.4), which is queryable and versioned, not a markdown file the harness re-parses per request. |
| "Cursor for business data," CFO/audit/compliance scope included | **Narrower**: sales/ops-facing BI, compliance explicitly cut | Kept from v1.0 β€” this one didn't change. Compliance-grade reporting has different liability and audit requirements than an ops manager asking about win rate; conflating them was correctly identified as scope creep. |
| One data source, live-call only | **Same for MVP** β€” canonical-table sync (a local warehouse) is explicitly Wave 2, gated on the Insight Feed needing it | Confirmed still correct: live-call MCP is fine for ad-hoc queries; it breaks for scheduled background scans and heavy aggregation, so sync is deferred until something actually needs it. |

The one-paragraph version: **v1.0 was the vision, correctly ambitious for a company
document. v2.0 is what two engineers can actually build and defend, in an order that
proves the trust story (Β§1) before anything else.**

---

## 1. What Corvus is

Corvus is a **multi-tenant business-intelligence harness**: a thin web app in front of
a harness core that takes a plain-English question from a business user β€” a sales VP,
an ops manager, not a developer β€” figures out what data it needs, fetches **verified**
numbers from the company's connected systems, and returns an answer with **citations
and a confidence level**.

**Tagline:** *Your data. Your model. Total clarity.*

**Positioning:** "Cursor for business data" β€” a serious professional tool a business
user can trust in front of leadership, in the register of Linear, Vercel, Stripe, or
Hex. Not a consumer AI chatbot.

**The one non-negotiable rule**, unchanged since v1.0 and the thing everything else in
this document exists to protect:

> **The AI model never computes numbers.** It calls a tool; the tool β€” deterministic,
> tested code β€” returns a verified number; the model only explains it.

A wrong number costs Corvus the customer instantly. Every architectural decision below
is in service of never presenting an unverified number as if it were verified.

---

## 2. The one loop that matters

> User connects **one** data source β†’ asks a question in plain English β†’
> gets a **correct answer with a citation to the source rows** + a confidence level.

Everything else in this document is addition. This loop already works end-to-end in
the current build (`askCorvus()` in `src/harness/ask.ts`). Ship it to design partners,
let their real questions decide what's next.

```mermaid
sequenceDiagram
    participant U as User (browser)
    participant R as /api/ask route (SDE)
    participant A as askCorvus() β€” ask.ts (AI)
    participant G as callModel() β€” gateway (SEAM)
    participant T as Tools β€” registry.ts (AI)
    participant S as Semantic Layer (AI, NEW β€” Β§7)
    participant C as MCP connectors (SDE)

    U->>R: "Which products are about to run out of stock?"
    R->>A: askCorvus(tenantId, question)
    A->>A: assemble tools = built-in + discover_metrics + MCP-discovered
    A->>G: callModel({tenantId, messages, tools})
    G-->>A: "I need to call discover_metrics, then calculate_kpi"
    A->>S: discover_metrics(query) β†’ governed metric match
    A->>T: runTool(tenantId, call)
    T->>C: (if raw query) callMcpTool β†’ real DB query
    C-->>T: real rows
    T-->>A: ToolResult { value, citation }
    A->>G: feed tool results back, call model again
    G-->>A: (repeat until no more tool calls)
    A-->>R: { answer, citations, confidence }
    R-->>U: rendered answer + "show me the data"
```

---

## 3. The stack, as built

```
Layer 1  Experience      Next.js web app (query box + answer + "show me the data")
Layer 2  API + Auth      Next.js API routes Β· Better Auth (multi-tenant) Β· JWT
Layer 3  Harness Core    Model Gateway Β· MCP Host Β· Tool Registry Β· Semantic Layer (NEW)
Layer 4  Connectors      Existing MCP servers (Postgres-MCP first; platform MCPs as needed)
Layer 5  Data            PostgreSQL + pgvector Β· Redis  (canonical tables added when sync is needed)
Layer 6  Infra           Hetzner VPS Β· Docker Β· Caddy (auto-TLS)
```

**Layer 3 is the moat**, same as v1.0 said β€” that part never changed. What changed is
*what's in it*. v1.0 imagined Context Engine + Semantic Layer + Agent Orchestrator +
Hooks + Memory as parallel systems built together. What's actually true a build cycle
later: the **Semantic Layer is the load-bearing piece**, and it should be built
deliberately, on top of a loop that's already proven simple and correct. Β§7 is the
current, concrete design for it β€” this is the piece v1.0 gestured at ("Business
Semantic Layer... build alongside first connector") but never fully specified.

### Model Gateway (BYOK)

Unchanged in principle from v1.0, real and shipped in code:

- `callModel(tenantId, messages, tools) β†’ ModelCallResult` β€” the single door every
  provider call goes through (`src/harness/gateway/`).
- **BYOK is architectural, not a feature.** A tenant's own Anthropic/OpenAI key,
  encrypted at rest (AES-256-GCM, HKDF per-version keys), decrypted only inside the
  gateway. One managed default (Claude) for tenants without their own key.
- This remains the enterprise sales unlocker v1.0 identified: BYOK removes the
  procurement blocker, honors data-processing agreements for sensitive-data
  customers, and means Corvus never becomes obsolete as models improve β€” a tenant
  upgrades their own key.
- `ollama` provider exists but is dev/test only, gated behind
  `CORVUS_DEV_PROVIDER=ollama`, never in a production code path.

### MCP Host β€” Layer 3 as MCP *client*, Layer 4 as connectors

This is the single biggest architectural pivot from v1.0, and it's a simplification,
not a compromise:

- **What it buys:** no bespoke Salesforce/HubSpot/Odoo API integration to write or
  maintain. The model calls MCP tools through a standard interface. Corvus is
  architecturally the host/harness; MCP servers are the tool layer β€” exactly the
  positioning v1.0 wanted, achieved with far less code.
- **What it doesn't solve** (still true, still designed around): MCP servers are thin
  API wrappers returning raw data, not canonical schema β€” the semantic layer (Β§7) is
  still Corvus's to build. Most are live-call, not synced β€” fine for ad-hoc queries,
  blocks aggregation-heavy analytics and the Insight Feed until Wave 2 adds a sync
  layer. Corvus inherits each server's maintenance quality and auth model β€” vet,
  pin versions, keep a fallback.
- **The shortcut that made Postgres-first correct:** the most mature MCP server in
  existence is the Postgres MCP server, and self-hosted Odoo β€” common among Arab
  SMBs, and among Corvus's own early prospects (BeExpress, Sweet&Fit) β€” runs on
  Postgres. Point a read-only Postgres MCP server at the Odoo DB and skip the Odoo
  API entirely for MVP.
- **Security posture, hard-won and non-negotiable:** the community
  `@henkey/postgres-mcp-server` advertises 18 tools including arbitrary SQL and
  mutations, and does **not** block them without extra config β€” verified directly
  against a live instance, not assumed from docs. Corvus enforces its own tool
  allowlist at the host layer (`mcp/host.ts`) rather than trusting a connector's
  internal safety switch. Only `pg_execute_query` is allowlisted; its own handler
  independently rejects anything that isn't `SELECT`/`WITH`. This same
  don't-trust-the-connector discipline applies to every future MCP server added.

### Tool Registry β€” the trust primitive, unchanged

`query_source`, `calculate_kpi`, `compare_periods` β€” three deterministic tools. The
model never does math; tools return verified numbers with citations, the model
explains them. This is the actual moat, and it hasn't moved since v1.0 first stated
the rule.

---

## 4. What makes it defensible

Carried forward from v1.0, still the correct list, now field-tested:

- **The model never computes numbers** β€” only tested, deterministic code does.
  Competitors letting a model estimate a number are one hallucination from losing a
  customer's trust.
- **Every answer is cited** β€” a user can always see the underlying rows, not just
  trust a black box. `confidence` (`high`/`medium`/`low`) is scored honestly: no
  tool calls, a tool error, or hitting the reasoning-loop turn cap all suppress
  `"high"` β€” the system tells the truth about its own uncertainty rather than
  presenting every answer with false confidence.
- **BYOK** β€” cost control and data-handling comfort at the enterprise end; see Β§3.
- **Multi-tenant from day one** β€” every table has `tenant_id`; every query is scoped.
- **Data sovereignty**, elevated since v1.0: self-hosting Corvus's own infra (not
  just BYOK at the model layer) is the strongest differentiator against
  platform-native agentic BI (Oracle, SAP, Microsoft Fabric) and semantic-layer
  incumbents (Snowflake Cortex, Databricks, Looker/Gemini) β€” none of which offer a
  cross-platform, self-hosted, BYOK harness. This was validated in a competitive
  pass after v1.0 shipped: the "connect data to AI" premise is increasingly covered
  by incumbents; sovereignty over both the model *and* the infrastructure isn't.

---

## 5. Who it's for

Unchanged from v1.0: operations- and sales-facing professionals at small/medium
businesses running a self-hosted, database-backed system (Odoo is the anchor case),
who currently wait on an analyst or don't ask the question at all because the
friction is too high. Validate with a small number of design partners before
expanding sources or customer base β€” v1.0's own instinct here ("talk to 20 business
users first... the three most painful questions they can't answer, that's your MVP
scope") held up and shaped the Wave 1 cut directly.

Global-first positioning, Arab market as the geographic wedge β€” not an Arab-only
product. Odoo depth + Arabic-language handling is the defensible niche *within* a
global addressable market, not the whole market.

---

## 6. Co-founder split

Unchanged in spirit from v1.0; updated to match what the codebase's own seam
(`src/harness/types.ts`) now encodes formally rather than just describing:

**AI Engineer (you) owns:**
- The reasoning loop (`ask.ts`) and confidence scoring
- The Tool Registry (`tools/registry.ts`) β€” specs + dispatch, deterministic bodies
- The Semantic Layer (Β§7 β€” Entity Catalog, Metric Registry, `discover_metrics`)
- Provider SDK integration inside the gateway (`gateway/providers/*.ts`)
- The eval suite (Β§7.5)

**Software Engineer (co-founder) owns:**
- Auth, multi-tenancy, the encrypted key vault, usage logging
- The MCP host's connection/transport machinery (`mcp/host.ts`) and each connector's
  launch/allowlist definition (`mcp/connectors/*.ts`)
- Database, migrations, infra (Docker, Caddy, Hetzner)
- The web app shell and every route around `askCorvus()`

**Together:** the seam itself (`types.ts` β€” `callModel`, `askCorvus`, `ToolResult`,
`Citation`) β€” v1.0's "shared schema" instinct was right; what changed is that it's
now a committed TypeScript file both tracks build against and can't change solo,
not a conceptual agreement. Same discipline applies to any future shared file
(flagged explicitly in `docs/ai-track-guide.md Β§8` β€” "Don't touch" isn't a wall,
it's "that's a conversation, not a solo edit").

---

## 7. The Semantic Layer β€” the piece v1.0 gestured at, specified for real

This is new since v1.0 and is the direct answer to two questions asked while
researching what Corvus still needs on the AI side: how Anthropic's own data team
gets self-service analytics to ~95% automated accuracy, and how WrenAI's open
"context layer" makes text-to-SQL governed instead of merely plausible.

### 7.1 The finding

Corvus already has the right foundation: the model never computes, tools do, every
answer is cited. What's missing is the layer that tells the model **what's queryable
in the first place** before it calls a tool β€” and this is not a hypothetical gap.
Anthropic's internal data-science team frames analytics accuracy as *"a context and
verification problem, not a code generation issue,"* and names three failure modes:
**concept↔entity ambiguity**, **staleness**, and **retrieval failure**. Independently,
dbt Labs' 2026 benchmark found semantic-layer grounding lifts text-to-SQL accuracy
from 90.0%β†’98.2% and 84.1%β†’100% across two frontier models, and that **schema/semantic
failures β€” not syntax β€” account for ~81% of text-to-SQL errors**. WrenAI's whole
premise (an open "context layer" β€” governed models, definitions, and memory beneath
generation) is the open-source expression of the same finding.

Corvus's current `kpi/schema.ts` allowlist (`ENTITIES`, `fields`, `numericFields`) is
**already a primitive semantic layer** β€” it's just one entity deep (`deals`) and
disconnected from how the model discovers what exists (KPI names are currently a
string baked into a tool description). This already broke once, concretely: the model
hallucinated `pipeline_coverage`, a KPI that was never real, because nothing told it
otherwise. The fix is to grow the existing primitive, not build something new beside it.

### 7.2 Shape of the change

```
BEFORE (Wave 1)                          AFTER (v2)
─────────────────                        ──────────
ask.ts                                   ask.ts
 └─ buildTools(tenantId)                  └─ buildTools(tenantId)
     └─ 6 hardcoded KPI names                 └─ Semantic Layer
        as a string in a                          β”œβ”€ Entity Catalog   (grows ENTITIES
        tool description                          β”‚   into a real, browsable model)
                                                    β”œβ”€ Metric Registry  (grows KpiDefinition
                                                    β”‚   into versioned, governed metrics)
                                                    β”œβ”€ discover_metrics tool (NEW)
                                                    └─ Business Glossary / synonyms (NEW)
```

The seam does not change. This is additive: a layer between `buildTools()` and the
model, plus one new tool that lets the model *ask what exists* instead of Corvus
guessing what to cram into a description string.

### 7.3 Entity Catalog

Extends `EntitySpec` (not a rewrite β€” the validation logic in `kpi/schema.ts` stays):
`displayName`, `description`, `grain`, `fieldDescriptions`, approved `relationships`
only, and `source: { connectorId, syncedAt }` for provenance. Hand-authored or
Claude-drafted-then-human-approved β€” **never model-inferred at query time**. Anthropic
tried auto-generating definitions with an LLM from raw tables; it *"produced
plausible-looking definitions that encoded the very ambiguities [they] were trying to
eliminate"* β€” net negative on their evals. Generate the documentation with Claude; a
human owns the definition.

### 7.4 Metric Registry

Grows `KpiDefinition` + `kpi/engine.ts` β€” same structured formula, same allowlisted,
parameterized execution, no free-form SQL, ever. Adds: mandatory `displayName`/
`description` (the only thing the model sees when discovering a metric), append-only
**versioning** (editing `win_rate` creates `win_rate@2`, doesn't mutate `win_rate@1` β€”
the direct fix for the staleness failure mode), `synonyms[]` populated from real
question logs, and `asOf` freshness carried into `Citation` β€” the smallest version of
Anthropic's "provenance footer" that fits Corvus's existing types.

### 7.5 `discover_metrics` β€” the highest-leverage single change

Replaces cramming every KPI name into `calculate_kpi`'s description. Modeled on
WrenAI's discover→select→execute flow and Anthropic's "agent is structurally required
to hit the semantic layer first":

```ts
{
  name: "discover_metrics",
  description:
    "Search the tenant's governed metric catalog by keyword or business concept " +
    "before calculating anything. ALWAYS call this before calculate_kpi if you are " +
    "not certain a metric name is exact β€” do not guess a plausible-sounding name.",
  inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
}
```

Keyword/synonym match over the Metric Registry β€” same shape as Mercer's BM25+LSH
entity retrieval, smaller corpus, no vector DB needed at Corvus's current scale.
`calculate_kpi`'s description shrinks back to static; one line added to `ask.ts`'s
system prompt: *"Always try discover_metrics or an existing KPI before calling
query_source directly."* This is what lets Corvus scale past a handful of KPIs
without the tool description growing unboundedly.

### 7.6 What this explicitly does not include yet

Matching the same "write it down so it stops nagging you" discipline `PLAN.md` uses:
no vector DB (Anthropic's own ablation found raw-corpus retrieval moved accuracy
*less than a point*, even with the right answer present and read); no adversarial-
reviewer sub-agent (+6% accuracy for +32% tokens/+72% latency β€” a real lever, wrong
priority before a design partner); no dashboard generation / WrenAI's "Deploy" beat;
no cross-connector joins; no automated correction-harvesting agent β€” a manual weekly
`QueryLog` review does this until question volume justifies automating it.

### 7.7 Validation β€” the part that doesn't exist yet

Today Corvus has one signal the reasoning loop works: a 6-question smoke test run
once. Minimum viable eval, sized correctly for a two-person team:

- **~20–30 golden Q&A pairs per tenant/domain** (diminishing returns past a few
  dozen per topic, and that ceiling drops with each model generation β€” don't
  over-invest).
- **Pinned to seeded, deterministic data**, never live β€” the exact trap Anthropic
  calls out: *"an eval written against live data goes stale the moment the
  underlying number moves."*
- **Wired into `test:integration`**, not optional β€” any PR touching `kpi/schema.ts`,
  `kpi/engine.ts`, or the semantic layer re-runs it.
- **Assert confidence tiers, not just correctness** β€” a wrong-but-confident answer
  is the actual product risk Corvus exists to prevent; the eval set should test for
  that directly, not just for the right KPI name.

### 7.8 Security posture β€” extends, never relaxes, the existing discipline

`discover_metrics` and the Entity Catalog are read-only, tenant-scoped metadata
lookups β€” same `tenant_id` scoping as every query in `kpi/engine.ts`, no new attack
surface. Governance stays **compile-time**: `validateConditions` runs before any SQL
is built, re-checked even for a stored definition β€” the Entity Catalog must preserve
that ordering, never introduce a "generate SQL, then check" path. `query_source`
remains the only raw-query tool, still routed through the same MCP allowlist
discipline as Β§3's MCP Host section. Growing the catalog makes `query_source` *safer*
to use β€” it does not add a new way to reach the database.

---

## 8. Build sequence

Where Β§0's cut narrowed *what* to build, this is *what order*, current as of this
version:

1. ~~Hetzner Wave 1, Model Gateway, MCP host, Tool Registry, reasoning loop~~ β€” **done**,
   verified via `test:integration` and `smoke:kpi-selection`.
2. **Auth / real multi-tenancy** (SDE) β€” the one blocker left before "ship to 5
   design partners."
3. **Entity Catalog schema + migration** β€” backfill the existing `deals` entity;
   zero behavior change, just structure (Β§7.3).
4. **Metric Registry additions** β€” versioning, synonyms, mandatory descriptions on
   the 6 seeded KPIs (Β§7.4).
5. **`discover_metrics` tool** β€” the actual reasoning-loop change (Β§7.5).
6. **Golden eval set**, wired into CI β€” before KPI #7 is added, not after (Β§7.7).
7. **Ship to 5 design partners.** Their questions decide the second data source,
   which KPIs get added next, and which synonyms the Metric Registry actually needs
   β€” not a roadmap guess.

**Not now** (unchanged from `PLAN.md`, still correct): microservices, ClickHouse,
self-hosted observability, MinIO, the Insight Feed, a Chrome extension, a Slack bot,
multi-platform joins, 3+ connectors, Hijri calendar, Arabic generation, a Report
Builder, a public API, Stripe/billing.

---

## 9. Risks worth tracking

Carried forward from v1.0, amended where the semantic layer changes the picture:

- **A wrong answer is an instant-churn event** β€” unchanged as the central design
  constraint. The semantic layer (Β§7) is a direct mitigation, not a new risk: it
  closes the concept↔entity ambiguity gap that's the *dominant* real-world failure
  mode for this category of system (~81% of text-to-SQL errors, per Β§7.1).
- **Semantic layer maintenance becomes its own ongoing cost** as KPI/entity count
  grows β€” this is the trade Anthropic's team also made, and their mitigation
  (colocate definition + doc changes in the same PR, enforced by review discipline)
  is adopted directly in the `corvus-semantic-layer` skill referenced in Β§7.
- **MCP server quality/maintenance** β€” still not fully in Corvus's control. Mitigate:
  vet + pin versions, keep the CSV/Postgres fallback, never trust a connector's
  internal safety switch (Β§3).
- **No local warehouse while live-call MCP is the only path** β€” blocks
  aggregation-heavy analytics and the Insight Feed. Mitigate: canonical-table sync
  is explicitly Wave 2, gated on having a concrete reason to build it.
- **Self-host ops tax** β€” every service is a potential 2am page. Mitigate: staged
  waves (Β§0), automated backups, no service added without a reason.

---

*Corvus Β· Platform Blueprint v2.0 Β· August 2026 Β· Confidential*
*Reflects the actual state of the build as of Wave 1 completion + the semantic-layer
scoping pass. Supersedes v1.0 in full; v1.0 remains available as historical record
of the original company-level vision.*