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

ConcernKnoll side (own deployment)GRAG side (app.grag.ai)
Users, roles (Partner/Analyst/Backoffice), sessions, per-route authorizationKnoll 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 PDFsKnoll 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 textvoyager 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 idsworkspaces 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 masterKG 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, groundednessgrag-client callersai-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, notificationsKnoll backend (plan 2.7)
BackupsKnoll 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_entities rows are the authoritative store (SQL leg, RLS-scoped), mirrored into a per-tenant AGE graph (kg_<tenant>, Cypher leg, vertices replayable via the backfill-age role), with pgvector 1536-dim embeddings for semantic lookup (POST /kg-service/api/v1/search/entity-match, gated on KG_EMBEDDINGS_ENABLED).
  • Entity→entity edges: AGE-only and lossy. Edges have NO relational copy (kg_crossreferences is span→document, kg_spans is span→entity). The AGE write path is best-effort by design — failures are swallowed and only counted on kg_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/intents rejects write keywords; there is no ad-hoc Cypher and no SQL passthrough over HTTP.
  • No HTTP edge-write route today. DocumentIngestRequest has no entities[] / 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 conceptGRAG resourceID conventionNotes
Firm (paying customer)tenantfirm-<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
Clientworkspaceclient-<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 workspacekb-analysis-<shortid>KB id doubles as the voyager collection name. No GRAG project layer — see below
Methodenhandbuch / firm knowledgeKB in the general workspacekb-methodologyOne-time provisioning + ingest (plan 3.3)
Questionnaire answersKnoll DB (truth) + markdown doc in analysis-KB + KG factsTriple projection, re-submit = supersede + re-upsert (plan 4.5)
Expert report / scores / recommendationsKnoll DB (versioned) + KG projection + PDF in file store (+ ingested into KB)Approval is Partner-only (plan 2.3, 5.3)
Original documentsKnoll file store (master) + GRAG KB (searchable text)keyed by grag_refs.document_id
11 success levers, service-provider catalogKG 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 calls08-gutachten-pipeline-spec.md
Users, roles, rightsKnoll DB onlyGRAG 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)

  1. User submits the client form → server action writes the clients row in Knoll Postgres (source of truth) inside a transaction.

  2. 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
    
  3. Record workspace_id in grag_refs; mark sync status.

  4. Activate the knoll-advisory KG schema for the new workspace (plan 3.4 — v1 ships without a relationships block 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"}'
    
  5. 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"]}'
    
  6. 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)

  1. Upload in the analysis UI → Knoll backend persists the binary to the file store FIRST (plan 2.4) + creates the files row (sha256, mime, checklist_item ref).

  2. 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.

  3. Start ingest (fetches docfold markdown, ensures the voyager collection dense/1536, signs the chunking → voyager_index_ingest DAG, 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"}
    
  4. Persist documentId/jobId in grag_refs, link to checklist_item + files.

  5. Progress to the UI: SSE GET $GRAG_URL/next/api/orchestrator/events/{jobId} (same headers); fallback poll GET $GRAG_URL/next/api/pipeline/status/{jobId}.

  6. Terminal failed → checklist item stays "Requested" + visible error + re-upload path (DLQ replay is operator-facing, not a product path).

  7. 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)

  1. Wizard autosaves each answer (debounced server action) to questionnaire_answers — Knoll DB is the source of truth throughout.

  2. On submit: mark submission in Knoll DB, emit "Fragebogen eingereicht" email event (plan 2.7).

  3. 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.

  4. 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"}}]}'
    
  5. 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.

  6. 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)

  1. Analyst starts the pipeline → one ai_runs row per step (state machine in the Knoll backend). Every GRAG call in a run is stamped with X-Pipeline-Id: <ai_run_id> so per-analysis cost is queryable via GET $GRAG_URL/ledger/api/v1/ledger/totals?pipeline_id=<ai_run_id> (plan 7.3; note the doubled ledger path segment — review-verified).

  2. 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 CompletionRequest has NO response_format/tools/JSON mode (plan 1.14) — all "structured output" steps use prompt-based JSON + Zod validation + bounded retries in ai_runs until the platform passes response_format through:

    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
    
  3. 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 via POST $GRAG_URL/kg-service/api/v1/segments/text:batch ({"keys": [{"document_id": "...", "ordinal": 3}]}, ≤64 keys, requires X-Workspace-ID) → findings.

  4. 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).

  5. Every step's output is QA'd: POST $GRAG_URL/groundedness/api/v1/score with {"response_text": "...", "chunks": [...], "include_nli": true, "include_per_sentence": true} (same auth headers; never include_spans — spans are English-only). Band per section recorded on the ai_runs row. Partner review gate between steps (KIU human-in-the-loop mandate; approval requires the Partner role, plan 2.3).

  6. 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_reports row in Knoll DB → KG projection of approved recommendations (§4.3 step 4–6 mechanics). Honoring knoll_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)

  1. 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.

  2. 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.

  3. Parse the SSE frames in order: subscribedtrace (per stage: history, persist_user, condense, retrieve, expand, generate, ground) → sources → optional retrieval {confidence}token {delta}×N → groundedness {score, band, ...} → optional sentencesdone {messageId, cost}; fatal errors arrive as error {message}.

  4. 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}.

  5. 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:

MechanismEffect
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-imagetenant 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 / settingskeyvault governance and the ADR 0030 settings store bite at tenant granularity
Error semanticscross-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 classWhere it livesHolderPurpose
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 browserall non-admin service routes: workspaces CRUD (below tenant level), ai-gateway, kg-service, groundedness, ledger reads
Operator admin keysingular 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 onlytenant create/patch/delete, admin-gated (security-sensitive) tenant settings, MCP token minting
PIPELINE_SERVICE_KEYinter-service secretGRAG services only — Knoll must never hold it (D5)unlocks safety-bypass headers; non-service callers presenting bypass headers are audited and ignored
MCP scoped tokensminted 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 keysai-gateway keyvault, POST /ai-gateway/api/v1/keys with monthly_budget_usd + rate_limit_rpm (plan 1.6)stored per tenant; secret write-onlyLLM-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 termMeaningTechnical resource
Kanzlei (firm)Advisory/tax firm; the paying customerGRAG tenant firm-<slug> (pilot knoll) + Knoll firms table (master data, VAT id, fee tiers)
Mandant (client)Client company of the firmGRAG workspace client-<slug>-<shortid> + Knoll clients table
Analyse (analysis)Advisory engagement for one clientGRAG KB kb-analysis-<shortid> (= voyager collection) + Knoll analyses (+ milestones, contracts, analysis_team)
MethodenhandbuchKnoll methodology handbookKB kb-methodology in the general workspace
Fragebogen (16 Kapitel)Structured client questionnaireKnoll 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 methodologyKG entity dictionary lever:<slug> (closed, seeded, tombstoned); Knoll DB master (success_levers)
Gutachten (expert report)The expert report produced per analysisKnoll expert_reports (versioned truth) + PDF in file store + ingested into KB
Hebel-Score / AmpelPer-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 leverKnoll DB row + KG entity/edges (RECOMMENDS, ADDRESSES, CONCERNS)
Dienstleister (service provider)Service provider from the catalogKnoll service_providers + service_provider_ratings (1–6, partial weights 35/35/25); KG dictionary service-provider:<slug>
Projekt (project)Implementation project from approved recommendationsKnoll projects + calculation_line_items; KG IMPLEMENTS projection
Meilenstein / Vertrag / TeamEngagement sub-entities on the analysisKnoll DB only (plan 2.1)
KIU (KI Intelligence Unit)Client-scoped steering chat, Modul 3BFF chat lane with analysis-kb_id + KIU system prompt enriched from Knoll DB (plan 5.1b)
Akquise-ErsteinschätzungLead qualification chat, Modul 1BFF chat lane over kb-methodology + deterministic lead scoring → leads (plan 5.1a)
LeadProspect in the acquisition funnelKnoll 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 agentsKnoll ai_agents registry + ai_runs stats
Wissensgraph (knowledge graph)The knowledge graph projectionkg-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