01 — Architecture Reference: Knoll Analyzer × GRAG
Purpose. This document is the authoritative architecture reference for the Knoll Analyzer
integration with the GRAG platform (document-processing-pipelines, live at
https://app.grag.ai). It defines the target topology, which component owns which data
(source-of-truth rules), the resource mapping (Kanzlei/Mandant/Analyse → tenant/workspace/KB),
the five core end-to-end data flows, the tenancy and isolation model, and the trust/credential
model. Every claim below is grounded in the integration plan (tasks/todo.md) and the verified
platform exploration; sibling documents (02–14, referenced by filename) carry the executable
details.
Status / verified against: 2026-07-06, repo document-processing-pipelines @ db63a95,
integration plan rev. 2 (post adversarial review). Live flag state on app.grag.ai is NOT yet
verified — see plan 0.9/0.10 and 05-verification-runbook.md before relying on any flag-gated
behavior.
1. Target architecture
┌───────────────────────── Knoll Analyzer (own host — plan 0.2 / D2) ─────────────────────────┐
│ │
│ Browser (Kanzlei users: Partner / Analyst / Backoffice — Knoll roles, plan 2.3) │
│ │ session cookie (Auth.js) — the GRAG key NEVER reaches the browser (plan 2.8, 7.7) │
│ ▼ │
│ next-monorepo/apps/web — Next.js UI + Knoll backend (route handlers + server actions) │
│ ├── Knoll Postgres (packages/db, Drizzle — plan 2.1) │
│ │ SOURCE OF TRUTH for: users/auth · firms · clients · analyses │
│ │ (+ milestones / contracts / analysis_team) · questionnaire · checklists · │
│ │ expert_reports (versioned) · service_providers · projects · leads · ai_agents · │
│ │ ai_runs (pipeline state machine) · grag_refs (id mapping) · audit_log │
│ ├── File store (S3-compatible or Postgres bytea — plan 2.4) │
│ │ original uploads + generated Gutachten/Angebots-PDFs │
│ │ (GRAG keeps NO binaries — docfold output is markdown, job payloads expire) │
│ └── packages/grag-client (plan 2.5) — typed HTTP client, hard-fails on missing tenant │
└──────────────┬──────────────────────────────────────────────────────────────────────────────┘
│ HTTPS · Authorization: Bearer $GRAG_API_KEY · X-Tenant-ID: $GRAG_TENANT
│ (+ X-Workspace-ID on every kg-service call · X-Pipeline-Id per ai_run)
│ ⚠ BFF lanes (/next/api/*) present NO credential today — plan 1.8 BLOCKER
▼
┌────────────────── GRAG platform · https://app.grag.ai (single-box compose stack) ───────────┐
│ Plesk nginx (public TLS) → Traefik :8081 → path-prefix routing: │
│ external URL = $GRAG_URL/<traefik-prefix>/<internal-path> │
│ │
│ /next (BFF) POST api/chat/stream (SSE) · POST api/upload/start · │
│ POST api/ingest/start · GET api/orchestrator/events/{jobId} (SSE) · │
│ GET api/pipeline/status/{jobId} · GET api/chat/chunk/{docId}/{ordinal} │
│ /workspaces tenants · workspaces · KBs · documents (inventory, supersede/purge) · │
│ conversations + messages · per-tenant settings (ADR 0030) · quality │
│ /ai-gateway chat/completions (non-streaming, NO response_format/tools — plan 1.14) · │
│ embed · retrieval/collections/{kb}/search[/rerank] (evidence gate, │
│ ADR 0032) · BYOK keyvault (402/429/403 governance, ADR 0029) │
│ ingest lane docfold (file→markdown) → chunking → voyager-index (collection = kb_id, │
│ dense/1536, server-side embed) · orchestrator (Redis queue ONLY — no │
│ HTTP submit; BFF signs + enqueues) · KG segment substrate (KG_INGEST) │
│ /kg-service Apache AGE graph per tenant + RLS relational substrate + pgvector │
│ entity embeddings · versioned schemas · read-only Cypher intents │
│ /groundedness · /rerank-fusion answer QA (multilingual incl. German; evidence spans │
│ are English-only — never request include_spans for German text) │
│ /ledger · /lineage cost attribution · CAS provenance graph + GDPR forward-delete │
│ /agent-control · /mcp-gateway multi-turn agents (optional, plan 5.6) · scoped external │
│ tokens (operator-minted after plan 1.2 — plan 5.7) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
Conventions used in all examples in this and sibling docs:
export GRAG_URL="https://app.grag.ai"
export GRAG_API_KEY="<knoll runtime key — plan 1.1>"
export GRAG_TENANT="knoll" # dev examples use: knoll-dev
2. Component responsibilities
2.1 What lives where
| Concern | Knoll side (own deployment) | GRAG side (app.grag.ai) |
|---|---|---|
| Users, roles (Partner/Analyst/Backoffice), sessions, per-route authorization | Knoll backend + Auth.js (plan 2.3) | Nothing. GRAG has NO user model; it authorizes keys, not people (7.7) |
| Domain data (firms, clients, analyses, milestones, contracts, team, questionnaire, checklists, expert reports, service providers, projects, leads) | Knoll Postgres (plan 2.1) | — |
| Original files (Jahresabschluss, NDA, …) + generated PDFs | Knoll file store (plan 2.4) | No binaries retained. docfold returns markdown; job payloads expire (archive strips file_base64 >100 KB after 30 d); no raw-file GET exists |
| Searchable text: chunks, vectors, full segment text | — | voyager collection per KB + kg_segments (KG_INGEST lane) |
| Document inventory (status queued→indexed, supersede chain) | cached only; files row links file store ↔ grag_refs.document_id (plan 4.4) | workspaces.documents (authoritative inventory) |
| Conversations + messages (chat transcripts, sources, groundedness) | grag_refs holds conversation ids | workspaces conversations/conversation_messages |
| Relationship facts (recommendation→lever, scores, project→recommendation, …) | Knoll Postgres = source of truth (see §2.2) | KG projection (AGE edges — lossy, reconciled from Knoll DB, plan 3.6) |
| Closed taxonomies (11 success levers, service-provider catalog) | Knoll DB master | KG entity dictionary, seeded via entities/upsert; tombstoned (properties.status="inactive") — no entity delete route exists (plan 3.5) |
| AI pipeline state (step, status, tokens, cost, groundedness band, error) | ai_runs state machine (plan 2.1, 5.3) | ledger rows (per-request cost), lineage artifacts |
| LLM calls, embeddings, retrieval, rerank, groundedness | grag-client callers | ai-gateway / rerank-fusion / groundedness services |
| Chat composition (history, condense, retrieve, expand, generate, ground, persist) | not re-implemented — Knoll consumes the BFF (D3) | /next/api/chat/stream (~1.9k-line answer path) |
| Cost governance (budget, RPM) | budget display + 80 % alert (7.3) | keyvault BYOK governance (402/429/403), ledger |
| Transactional email, notifications | Knoll backend (plan 2.7) | — |
| Backups | Knoll DB + file store in Knoll's backup plan (D2) | operator concern; deploy workflow enables neither backup nor pitr — plan 1.13 blocker |
2.2 The locked Postgres+AGE+pgvector constraint — qualified
The Kanzlei's locked requirement — one engine stack of Postgres + Apache AGE + pgvector, queryable via SQL + Cypher + semantic search — is satisfied by GRAG's kg-service, with verified caveats that shape the source-of-truth rules:
- Entities: fully covered. Relational
kg_entitiesrows are the authoritative store (SQL leg, RLS-scoped), mirrored into a per-tenant AGE graph (kg_<tenant>, Cypher leg, vertices replayable via thebackfill-agerole), with pgvector 1536-dim embeddings for semantic lookup (POST /kg-service/api/v1/search/entity-match, gated onKG_EMBEDDINGS_ENABLED). - Entity→entity edges: AGE-only and lossy. Edges have NO relational copy
(
kg_crossreferencesis span→document,kg_spansis span→entity). The AGE write path is best-effort by design — failures are swallowed and only counted onkg_service_age_mirror_failures_total; a lost edge is recoverable only by re-ingesting the source document. The "SQL leg" for relationship facts does not exist inside GRAG. - Cypher: read-only, pre-registered intents only.
POST /api/v1/intentsrejects write keywords; there is no ad-hoc Cypher and no SQL passthrough over HTTP. - No HTTP edge-write route today.
DocumentIngestRequesthas noentities[]/relationships[]fields (extra="forbid"); edges ride only the internal Redis ingest envelope. Plan 1.11 adds an authenticated, fail-loud entities+relationships write route; deterministic KG writes (plan 3.6) are gated on it.
Consequence (the rule everything follows): Knoll's own Postgres stays the source of truth
for all relationship facts. The KG is a projection for graph and semantic consumption, kept
idempotently reconcilable: writes must fail loudly, a scheduled reconcile job re-upserts all
entities + edges for an analysis from Knoll DB (AGE MERGE is idempotent), and the operator
alerts on kg_service_age_mirror_failures_total (plan 3.6, 7.6). Phase-5 1-hop queries
(recommendations_for_client etc.) are answered from Knoll SQL, not the graph; Cypher intents
are registered only when a real graph consumer lands (plan 3.7/3.8). Full KG schema design:
07-kg-schema-knoll-advisory.md.
3. Resource mapping
The contract everything else hangs on (plan "Resource mapping"; id rules in
03-id-conventions.md — all GRAG ids are DNS-labels ^[a-z0-9][a-z0-9-]{0,62}$, so German
umlauts/uppercase must be slugified by Knoll):
| Knoll concept | GRAG resource | ID convention | Notes |
|---|---|---|---|
| Firm (paying customer) | tenant | firm-<slug>; pilot: single tenant knoll (dev: knoll-dev) | The only hard boundary: RLS, per-tenant AGE graph, crypto AAD, BYOK budget, settings. Created by the operator admin key (plan 1.3) — never by the Knoll runtime key. Avoid reserved ids default, canary, system |
| Client | workspace | client-<slug>-<shortid> | App-layer scope, NOT a security boundary (§5). Auto-created general workspace stays for firm-wide assets |
| Analysis (engagement) | KB in the client workspace | kb-analysis-<shortid> | KB id doubles as the voyager collection name. No GRAG project layer — see below |
| Methodenhandbuch / firm knowledge | KB in the general workspace | kb-methodology | One-time provisioning + ingest (plan 3.3) |
| Questionnaire answers | Knoll DB (truth) + markdown doc in analysis-KB + KG facts | — | Triple projection, re-submit = supersede + re-upsert (plan 4.5) |
| Expert report / scores / recommendations | Knoll DB (versioned) + KG projection + PDF in file store (+ ingested into KB) | — | Approval is Partner-only (plan 2.3, 5.3) |
| Original documents | Knoll file store (master) + GRAG KB (searchable text) | keyed by grag_refs.document_id | |
| 11 success levers, service-provider catalog | KG entity dictionary (seeded, closed taxonomy) | lever:<slug>, service-provider:<slug> | Tombstone-only lifecycle (plan 3.5) |
| Six-agent pipeline (Interview→…→Report) | Knoll ai_runs state machine over ai-gateway calls | — | 08-gutachten-pipeline-spec.md |
| Users, roles, rights | Knoll DB only | — | GRAG has no user model |
Why no project layer (plan 0.1 / D1 rationale): GRAG's hierarchy offers an optional
project between workspace and KB, but nothing in the Knoll integration consumes it — cost
attribution uses X-Pipeline-Id (plan 7.3), keyvault scope is tenant-level (plan 1.6),
kg-service scopes by workspace only, and chat/ingest key on kb_id. KB→project is nullable,
so provisioning projects would add code, idempotency handling and failure modes with zero
consumers (Simplicity First). Add projects later only with a concrete consumer (e.g.
project-scoped BYOK keys or MCP tokens).
Every provisioned GRAG id is recorded in Knoll's grag_refs table
(knoll_id ↔ tenant/workspace/kb/document/conversation id, sync status, last_error — plan 2.1).
Idempotency = deterministic client-chosen ids + treat 409 as already-provisioned. There is
no Idempotency-Key header support anywhere in the workspaces service — do not send one
(plan 3.1, review-verified).
4. End-to-end data flows (5 core journeys)
Complete request/response payloads live in 02-grag-api-cookbook.md; error handling and retry
rules in 11-resilience-and-errors.md. All examples: prod tenant $GRAG_TENANT, dev knoll-dev.
4.1 Create client (plan 3.1)
-
User submits the client form → server action writes the
clientsrow in Knoll Postgres (source of truth) inside a transaction. -
Provisioning module slugifies the name (umlauts → ASCII, 03-id-conventions.md) and creates the workspace with a deterministic client-chosen id:
curl -X POST "$GRAG_URL/workspaces/api/v1/workspaces" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "Content-Type: application/json" \ -d '{"id": "client-hartmann-a7f3", "name": "Hartmann Maschinenbau GmbH"}' # 201 created · 409 = already provisioned (treat as success) · no Idempotency-Key exists -
Record
workspace_idingrag_refs; mark sync status. -
Activate the
knoll-advisoryKG schema for the new workspace (plan 3.4 — v1 ships without arelationshipsblock so prod's enabled relation auto-extraction stays dormant, D7):curl -X POST "$GRAG_URL/kg-service/api/v1/schemas/knoll-advisory/v1/activate" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "X-Workspace-ID: client-hartmann-a7f3" \ -H "Content-Type: application/json" \ -d '{"workspace": "client-hartmann-a7f3"}' -
On first analysis for the client: create the KB (= voyager collection name):
curl -X POST "$GRAG_URL/workspaces/api/v1/workspaces/client-hartmann-a7f3/kbs" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "Content-Type: application/json" \ -d '{"id": "kb-analysis-b2c9", "name": "Analyse Hartmann 2026", "languages": ["de"]}' -
Failure handling: any step failing marks
grag_refs.sync_status+last_error; the provisioning module retries idempotently (04-provisioning-runbook.md).
4.2 Upload document (plan 4.1, 4.2 — ⚠ blocked for real data by plan 1.8)
-
Upload in the analysis UI → Knoll backend persists the binary to the file store FIRST (plan 2.4) + creates the
filesrow (sha256, mime, checklist_item ref). -
Forward to the BFF upload lane (D4 — the orchestrator has no HTTP submit; only the BFF signs and enqueues ingest DAGs):
curl -X POST "$GRAG_URL/next/api/upload/start" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -F "kb_id=kb-analysis-b2c9" \ -F "files=@jahresabschluss-2025.pdf" # → {"kb_id": "...", "jobs": [{"filename", "docfoldJobId", "documentId", ...}]}⚠ Today the BFF ignores the Authorization header (unauthenticated surface); sending it is still mandatory so nothing breaks when plan 1.8 adds the bearer check.
-
Start ingest (fetches docfold markdown, ensures the voyager collection dense/1536, signs the
chunking → voyager_index_ingestDAG, LPUSHes to Redis):curl -X POST "$GRAG_URL/next/api/ingest/start" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "Content-Type: application/json" \ -d '{"kb_id": "kb-analysis-b2c9", "document_id": "<documentId>", "docfold_job_id": "<docfoldJobId>", "workspace_id": "client-hartmann-a7f3"}' # → {"jobId": "...", "tenantId": "...", "status": "queued"} -
Persist
documentId/jobIdingrag_refs, link tochecklist_item+files. -
Progress to the UI: SSE
GET $GRAG_URL/next/api/orchestrator/events/{jobId}(same headers); fallback pollGET $GRAG_URL/next/api/pipeline/status/{jobId}. -
Terminal
failed→ checklist item stays "Requested" + visible error + re-upload path (DLQ replay is operator-facing, not a product path). -
On
indexed: checklist classification suggests the matching item of the 46-item checklist → status "Received" after human confirmation (plan 4.3; deterministic rule stays in Knoll code: "no business plan → MarketPosition ≤ 1,5"). Full lifecycle incl. supersede/delete: 10-document-pipeline.md.
4.3 Submit questionnaire (plan 4.5, 3.6, 6.2)
-
Wizard autosaves each answer (debounced server action) to
questionnaire_answers— Knoll DB is the source of truth throughout. -
On submit: mark submission in Knoll DB, emit "Fragebogen eingereicht" email event (plan 2.7).
-
Render all answers to a markdown document ("Fragebogen-Antworten <analysis>") and ingest it into the analysis-KB via the §4.2 lane (upload/start with the rendered file → ingest/start) so answers are retrievable in chat and the expert-report pipeline.
-
Deterministic KG projection (plan 3.6 — gated on plan 1.11, the fail-loud HTTP entities+relationships write route that does not exist yet): upsert entities (Client, Metrics) and edges (
SCORES {score, traffic_light, analysis_id},ADDRESSES, …) derived from the answers. Until 1.11 lands, only entity upserts are possible over HTTP:curl -X POST "$GRAG_URL/kg-service/api/v1/entities/upsert" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "X-Workspace-ID: client-hartmann-a7f3" \ -H "Content-Type: application/json" \ -d '{"entities": [{"entity_id": "client:hartmann-a7f3", "entity_type": "Client", "label": "Hartmann Maschinenbau GmbH", "properties": {"status": "active"}}]}' -
Re-submit semantics (corrected answers): re-render markdown →
POST $GRAG_URL/workspaces/api/v1/documents/{oldDocId}/supersede(body{"superseded_by": "<newDocId>"}, same auth headers) → re-run the idempotent 3.6 upserts. -
The scheduled reconcile job re-upserts all entities+edges for the analysis from Knoll DB (idempotent AGE MERGE), healing any silently lost edges (§2.2).
4.4 Expert-report pipeline (plan 5.3 — spec in 08-gutachten-pipeline-spec.md)
-
Analyst starts the pipeline → one
ai_runsrow per step (state machine in the Knoll backend). Every GRAG call in a run is stamped withX-Pipeline-Id: <ai_run_id>so per-analysis cost is queryable viaGET $GRAG_URL/ledger/api/v1/ledger/totals?pipeline_id=<ai_run_id>(plan 7.3; note the doubledledgerpath segment — review-verified). -
Step 1 Interview: structured digest of questionnaire answers (read from Knoll DB) via ai-gateway, plus contradiction detection + generated follow-up questions to the client (ag-04/ag-05 competencies). ⚠ The gateway
CompletionRequesthas NOresponse_format/tools/JSON mode (plan 1.14) — all "structured output" steps use prompt-based JSON + Zod validation + bounded retries inai_runsuntil the platform passesresponse_formatthrough:curl -X POST "$GRAG_URL/ai-gateway/api/v1/chat/completions" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "X-Pipeline-Id: run-01j8..." \ -H "Content-Type: application/json" \ -d '{"model": "openai/gpt-4o-mini", "messages": [...], "fallback": ["anthropic/claude-sonnet-4-5"]}' # non-streaming; keyvault governance errors: 402 budget, 429 RPM, 403 expired/disabled -
Step 2 Document: per checklist document —
POST $GRAG_URL/ai-gateway/api/v1/retrieval/collections/kb-analysis-b2c9/search/rerank({"query_text": "...", "top_k": 10}; German queries auto-route to the multilingual reranker) + full-text expansion viaPOST $GRAG_URL/kg-service/api/v1/segments/text:batch({"keys": [{"document_id": "...", "ordinal": 3}]}, ≤64 keys, requiresX-Workspace-ID) → findings. -
Step 3 SWOT (synthesis over 1+2) → Step 4 Scoring (LLM proposes per-lever scores + rationale → deterministic caps in Knoll code → traffic_light computed in code) → Step 5 Expert report (long-form German text per lever + recommendations).
-
Every step's output is QA'd:
POST $GRAG_URL/groundedness/api/v1/scorewith{"response_text": "...", "chunks": [...], "include_nli": true, "include_per_sentence": true}(same auth headers; neverinclude_spans— spans are English-only). Band per section recorded on theai_runsrow. Partner review gate between steps (KIU human-in-the-loop mandate; approval requires the Partner role, plan 2.3). -
Step 6 Report: assembly → PDF rendered into the Knoll file store (download/export source) → PDF ingested into the analysis-KB via §4.2 for future retrieval → versioned
expert_reportsrow in Knoll DB → KG projection of approved recommendations (§4.3 step 4–6 mechanics). Honoringknoll_ai_settings(detail_level, caps_enabled) throughout.
4.5 KB-Chat per analysis — "Frag die Akte" (plan 5.2, D3 — details in 09-chat-integration.md)
-
First turn: create the conversation FIRST. ⚠ The chat BFF does NOT create conversations — a made-up id streams fine but transcripts silently never persist (review-verified):
curl -X POST "$GRAG_URL/workspaces/api/v1/conversations" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "Content-Type: application/json" \ -d '{"id": "conv-analysis-b2c9-01", "kb_id": "kb-analysis-b2c9", "workspace_id": "client-hartmann-a7f3", "title": "Frag die Akte"}'Store the client-chosen id in
grag_refs. -
Stream the turn through the single blessed chat lane (D3 — same lane serves acquisition chat 5.1a and KIU chat 5.1b with different
kb_id/system_prompt; do not fork the answer path):curl -N -X POST "$GRAG_URL/next/api/chat/stream" \ -H "Authorization: Bearer $GRAG_API_KEY" \ -H "X-Tenant-ID: $GRAG_TENANT" \ -H "Content-Type: application/json" \ -d '{"conversation_id": "conv-analysis-b2c9-01", "kb_id": "kb-analysis-b2c9", "user_message": "Welche Risiken nennt der Jahresabschluss?", "workspace_id": "client-hartmann-a7f3"}'⚠ Same 1.8 caveat as §4.2: the header is ignored today, mandatory after the fix.
-
Parse the SSE frames in order:
subscribed→trace(per stage: history, persist_user, condense, retrieve, expand, generate, ground) →sources→ optionalretrieval {confidence}→token {delta}×N →groundedness {score, band, ...}→ optionalsentences→done {messageId, cost}; fatal errors arrive aserror {message}. -
Render: source list (document name + section hierarchy — no page numbers, the payload never populates them), groundedness band, confidence pill; full-chunk drill-down via
GET $GRAG_URL/next/api/chat/chunk/{docId}/{ordinal}. -
History windowing, retrieval, expansion, groundedness and persistence all come from the BFF for free; both turns land in the conversation created in step 1.
5. Tenancy & isolation model
What the tenant (X-Tenant-ID) actually isolates — the only hard boundary:
| Mechanism | Effect |
|---|---|
Postgres RLS (SET LOCAL app.tenant_id) | every tenant-bearing table in workspaces, kg, ledger, lineage, keyvault, mcp/agent stores |
Per-tenant AGE graph kg_<tenant> | AGE has no RLS; graph-per-tenant IS the KG isolation |
| CAS pre-image | tenant is hashed into every content-addressed artifact id |
| Per-tenant HKDF crypto keys (AES-GCM AAD) | PII vault + BYOK keyvault — cross-tenant decrypts fail |
| BYOK budget / RPM / settings | keyvault governance and the ADR 0030 settings store bite at tenant granularity |
| Error semantics | cross-tenant probes return 404, never 403 |
Flag-gated, currently OFF in the prod env example (TENANT_KEYS_ENABLED=false): Redis
t:{tenant}: key prefixes and voyager {tenant}- collection prefixes. Until the plan 1.5
cutover, vector isolation rests on per-KB collection naming discipline alone — acceptable for a
single tenant, required before any second real tenant. TODO-VERIFY: live value of
TENANT_KEYS_ENABLED (and the rest of the plan 0.9 flag list) on app.grag.ai via
host-diagnose.yml or a flag dump on the box.
What the workspace does NOT isolate: workspace/project/KB are application-layer
attribution columns — NOT in RLS predicates, NOT in the CAS pre-image, NOT in Redis keys.
Any holder of the tenant's key can read across workspaces of that tenant; client separation
inside tenant knoll is therefore Knoll-side authorization (every Knoll route checks the
user's client access before calling GRAG — plan 7.7).
Tenant-wide KG entities caveat (ADR 0025 Amendment): KG entities are tenant-scoped, not workspace-scoped — entities extracted or written for client A are visible from client B's workspaces of the same firm (per-workspace provenance is derivable via spans→segments→ documents, but the dictionary is shared). Accepted for one firm; a conflict-of-interest wall between clients would force tenant-per-client later (documented trade-off, plan 0.1).
Default-tenant hazard: allow_default_tenant=true platform-wide — a request missing
X-Tenant-ID silently reads/writes tenant default. The grag-client therefore hard-fails
when no tenant is set (plan 2.5); never rely on omission.
6. Trust & credential model
Key classes (custody and provisioning: 04-provisioning-runbook.md; gaps: 13-platform-gaps-issues.md):
| Key class | Where it lives | Holder | Purpose |
|---|---|---|---|
Knoll runtime key ($GRAG_API_KEY) | appended to the PIPELINE_API_KEY comma-list plus a named GATEWAY_CALLERS entry knoll:<token> (plan 1.1 / D5) | Knoll backend secret store; never the browser | all non-admin service routes: workspaces CRUD (below tenant level), ai-gateway, kg-service, groundedness, ledger reads |
| Operator admin key | ⚠ singular env/secret names WORKSPACES_ADMIN_API_KEY and MCP_GATEWAY_ADMIN_API_KEY — compose maps them onto the plural in-container settings; setting the plural name in .env does nothing (plan 1.2, review-verified) | platform operator only | tenant create/patch/delete, admin-gated (security-sensitive) tenant settings, MCP token minting |
PIPELINE_SERVICE_KEY | inter-service secret | GRAG services only — Knoll must never hold it (D5) | unlocks safety-bypass headers; non-service callers presenting bypass headers are audited and ignored |
| MCP scoped tokens | minted via POST /mcp-gateway/api/v1/tokens (admin-gated) | external tools of a Kanzlei (plan 5.7) | least-privilege KB access (search, list_*); scope from the token, headers ignored. After the 1.2 custody split, minting is an operator action, not a Knoll-runtime call |
| BYOK provider keys | ai-gateway keyvault, POST /ai-gateway/api/v1/keys with monthly_budget_usd + rate_limit_rpm (plan 1.6) | stored per tenant; secret write-only | LLM-provider credentials (OpenAI/Anthropic/…), NOT platform access keys; governance rejects 402/429/403 |
What the Knoll runtime key may do: create/read/update/delete workspaces, KBs, documents,
conversations within tenant knoll; call chat/completions, embed, retrieval, groundedness;
register kg-service schemas/intents and upsert entities (tenant-scoped, no admin gate today);
read ledger spend/totals for its tenant.
What it may NOT / cannot do: create or delete tenants (403 after the 1.2 split — the
post-deploy verification is exactly POST /workspaces/api/v1/tenants with the runtime key
expecting 403); write admin-gated security settings (guardrail/firewall/anonymization keys);
mint MCP tokens (after 1.2); use bypass headers; address other tenants (RLS + 404 parity).
⚠ BFF caveat (until plan 1.8 — the hard blocker): the BFF lanes Knoll depends on
(/next/api/chat/stream, /next/api/upload/start, /next/api/ingest/start,
/next/api/orchestrator/events/{id}) are publicly reachable with no authentication: there
is no middleware.ts in frontend-next, the Traefik router carries no auth middleware, the
routes inject the platform PIPELINE_API_KEY server-side and trust the inbound X-Tenant-ID.
Two consequences: (a) anyone on the internet could today chat against tenant knoll's
documents, inject documents, and burn budget — 1.8 must land before any real Mandanten data
(go/no-go list, plan 0.11); (b) BFF-lane traffic never presents Knoll's key, so
GATEWAY_CALLERS attribution and rate buckets cover only direct service calls until 1.8 adds
a bearer check in the BFF. Knoll sends Authorization: Bearer $GRAG_API_KEY on every BFF call
anyway, so the 1.8 fix is a no-op client-side. Live probes: plan 0.10a (expect success today,
401 after 1.8) and the 7.2 canary auth probe.
Knoll-side rules (plan 7.7): the GRAG key never reaches the browser or client bundles; grag-client hard-fails on missing tenant; every Knoll route enforces Knoll RBAC server-side (GRAG authorizes keys, not people); Partner-only gates (expert-report approval, fee edits) enforced in server actions.
7. Glossary — German domain terms ↔ technical resources
| German term | Meaning | Technical resource |
|---|---|---|
| Kanzlei (firm) | Advisory/tax firm; the paying customer | GRAG tenant firm-<slug> (pilot knoll) + Knoll firms table (master data, VAT id, fee tiers) |
| Mandant (client) | Client company of the firm | GRAG workspace client-<slug>-<shortid> + Knoll clients table |
| Analyse (analysis) | Advisory engagement for one client | GRAG KB kb-analysis-<shortid> (= voyager collection) + Knoll analyses (+ milestones, contracts, analysis_team) |
| Methodenhandbuch | Knoll methodology handbook | KB kb-methodology in the general workspace |
| Fragebogen (16 Kapitel) | Structured client questionnaire | Knoll questionnaire_versions / questionnaire_answers (truth); markdown projection in the analysis-KB; KG facts |
| Checkliste (46 Positionen) | Document request checklist (C.I.3) | Knoll checklist_items; classification suggestions from indexed documents (plan 4.3) |
| Erfolgshebel (11) | The 11 success levers of the Knoll methodology | KG entity dictionary lever:<slug> (closed, seeded, tombstoned); Knoll DB master (success_levers) |
| Gutachten (expert report) | The expert report produced per analysis | Knoll expert_reports (versioned truth) + PDF in file store + ingested into KB |
| Hebel-Score / Ampel | Per-lever score (1,0–5,0; 5 = best) and traffic light (Red < 2,5 / Yellow 2,5–3,5 / Green > 3,5) | Computed in Knoll code (LLM proposes, deterministic caps decide); projected as SCORES {score, traffic_light} KG edges |
| Empfehlung (recommendation) | Recommendation addressing a lever | Knoll DB row + KG entity/edges (RECOMMENDS, ADDRESSES, CONCERNS) |
| Dienstleister (service provider) | Service provider from the catalog | Knoll service_providers + service_provider_ratings (1–6, partial weights 35/35/25); KG dictionary service-provider:<slug> |
| Projekt (project) | Implementation project from approved recommendations | Knoll projects + calculation_line_items; KG IMPLEMENTS projection |
| Meilenstein / Vertrag / Team | Engagement sub-entities on the analysis | Knoll DB only (plan 2.1) |
| KIU (KI Intelligence Unit) | Client-scoped steering chat, Modul 3 | BFF chat lane with analysis-kb_id + KIU system prompt enriched from Knoll DB (plan 5.1b) |
| Akquise-Ersteinschätzung | Lead qualification chat, Modul 1 | BFF chat lane over kb-methodology + deterministic lead scoring → leads (plan 5.1a) |
| Lead | Prospect in the acquisition funnel | Knoll leads table (scoring 0.6·attractiveness + 0.4·reachability) |
| Honorar-Stufen (fee tiers) | Fee tiers (15.000 / 30.000 / individuell) | Knoll fee_tiers table; Partner-only edits |
| Freigabe (approval) | Partner sign-off (QMS human-in-the-loop) | Knoll role gate (Partner) on expert report/pipeline steps |
| KI-Agent (AI agent) | One of the 11 configured AI agents | Knoll ai_agents registry + ai_runs stats |
| Wissensgraph (knowledge graph) | The knowledge graph projection | kg-service: entities (SQL+AGE+pgvector) + edges (AGE-only); Explorer later via graph-gateway subgraph (plan 3.8) |
Sibling docs: 02-grag-api-cookbook.md (full request/response reference) · 03-id-conventions.md · 04-provisioning-runbook.md · 05-verification-runbook.md · 06-knoll-db-schema.md · 07-kg-schema-knoll-advisory.md · 08-gutachten-pipeline-spec.md · 09-chat-integration.md · 10-document-pipeline.md · 11-resilience-and-errors.md · 12-gdpr-compliance.md · 13-platform-gaps-issues.md · 14-cost-model.md