Vollständiger Statusreport aller geplanten Erweiterungen und Änderungen — destilliert aus Architektur-Blöcken, Plan-Dateien und verifiziertem Git-Stand.
Das Projekt ctx (PostgreSQL 18 + pgvector + TimescaleDB Knowledge-Store, Go-Monolith aus ctx CLI und ctxd Daemon, llama.cpp-Inferenz, MPL-2.0, Autor GottZ) steht an einem klaren Wendepunkt: das Web-Frontend ist mit dem 41-Wellen-Plan G01–G41 vollstaendig gebaut (letzte Release v3.15.0 vom 2026-06-14, die ausserplanmaessige Graph-Landkarte F5-W6; der 41-Plan selbst geschlossen mit v3.14.0 vom 2026-06-13), und das aktive Grossvorhaben ist die Multi-Tenant-Ausarbeitung im Gold-Standard nach Tenant-Modell C (Hybrid, 3-Ebenen Tenant/Scope/Block), entschieden durch User-Praezisierung E1 am 2026-06-15. Von 44 geplanten Wellen (T01–T43, T40 in T40a/b gespalten, 7 Achsen / Phasen A–G, 11 Ziel-Migrationen 058–068, ~7805 LOC inkl. Tests) sind 20 Wellen committet und Migrationen 058–064 (7 Stueck) im Branch; der Branch feat/multi-tenant (HEAD ddbfd7f) ist jedoch lokal und NICHT deployed — die echte Live-DB steht bei max(migration)=57.
Auf den Multi-Tenant-Kern setzen zwei weitere Stufen auf: ein bereits anforderungsseitig ausgearbeiteter Enterprise-Aufsatz (Stand 2026-06-16, 5 Dimensionen — Reseller, Konzern-Hierarchie, ephemere Test-Tenants, separate Models, Lastverteilung — ~140 Anforderungen, Phasen 0–5, noch kein Bau-Plan), und danach die Workflow-Engine / das Issue-System (Vision aus 2026-06-01, 4 Schema-First-Wellen W1–W4, noch nicht gebaut). Am 2026-06-17 hat der User sechs MT-Grundsatzentscheidungen getroffen, die die naechste Bau-Phase praegen: eine destruktive Welle 0 (Scope-Konsolidierung work+shared→private, mit Backup, VOR den MT-Wellen 058+, in dieser Session nicht ausgefuehrt), gefolgt von additiv/pausierbaren Wellen (parent_tenant_id, embed_model-Provenance/Attribution T35b, metadata.ephemeral). Daneben laeuft ein operativer Backlog (Resilienz-Reste Welle G, zwei Code-Fixes, Retention/Kuration). Die Inferenz laeuft durchgaengig auf llama.cpp; Ollama ist komplett raus.
Acht User-Entscheidungen blockieren jeweils Wellen/Migrationen (E1 = Modell C ist entschieden): E2 Rollen-Wertebereich (2 vs. 3 Stufen — Anmerkung: 059/T02 bereits committet mit owner|admin|member); E3 shared-Schreibrecht (blockiert 05-Write-Gate T25/T31); E5 shared-_global-Provider-Fallback + tenant-owned OAuth (blockiert 03-W5 T31 + 04-W2 T34, plus T26); E7 HNSW-Cap/Hop-Budget/Fairness (datengetrieben, Bench T16). E4 (shared-Zuordnung der 21 kanonischen Cross-Tenant-Bloecke) ist durch die Welle-0-Entscheidung 2026-06-17 (Scope-Konsolidierung work+shared→private) gegenstandslos geworden. UB-1..UB-6 sind die offenen block-level-Freigabe-Sub-Entscheidungen der Achse 07/Phase G.
Die Scope-Konsolidierung work+shared→private ist eine destruktive DB-weite Operation ueber alle scope-Spalten (blocks/dream_links/blobs/sources/chat_sessions/settings) + Work-API-Key + Overview-Rebuild, geplant als erste Welle der naechsten Bau-Phase VOR den MT-Wellen 058+, mit Backup — in dieser Session noch nicht ausgefuehrt. Parallel ist die User-Bestaetigung der scope-granularen Quota-Lesart ausstehend (Budget pro Scope, 063-Kommentar 'per tenant'→'per scope', Struktur bleibt, Tenant-Gesamtbudget = Summe der Scope-Quotas) — diese Revision vom 2026-06-17 betrifft direkt Befund 5 (063:59 falscher Kommentar) und die fruehere Enterprise-Doktrin LB-A1 (tenant_id-keyed gilt nicht mehr unveraendert).
OD-2: Erzeugung des cosine-Aequivalenz-Proofs (heute nur nackter bool aus Client-Payload validate.go:195) — Empfehlung B (Test-Harness, cosine ≥ 0.9999) mit zwei zwingenden Haertungen (Flag server-admin-only, Gate lokalitaets-blind auf Scope!=_global statt nur LocalityExternal); C (Verbot/separate Partition) als fail-closed-Default solange B nicht gebaut. LB-OD4: aller Last-/Health-/Quota-State ist prozess-lokal (ausser ClaimTurn) — kurzfristig N==1 explizit dokumentieren; bei N>1 sind on_exceed=block (echtes Geld/Egress) und Cooldown-Verduennung bei geteilten _global-Backends die verifizierten Ausnahmen, die einen SHARED Counter-Store (geparkt) erzwingen wuerden.
Noch nicht entschieden und Voraussetzung fuer die Workflow-Engine: deklarativ-reich (empfohlen als Fundament) — Type = Config mit Verhaltens-Feldern (retrieval_policy, guard_dedup, dream_linkable, structural_parent, is_meta-Aequivalent), kein server-seitiger User-Code, kein Sandboxing; vs. programmierbar (Hooks/Plugins/Expressions mit eingeklinkter User-Logik) — maximale Macht, aber Sandboxing/Sicherheit/Lebenszyklus als eigenes Subsystem (dokumentiertes W6d-Risiko), nur als Erweiterungspunkt wenn ein realer Workflow das deklarative Vokabular nachweislich sprengt.
Als naechster Release-Meilenstein soll RC-1 (Tenant-Fundament + Read-Isolation = Phase A+C+D) als Sicherheits-Kern-Release vorbereitet werden; die Versionsnummer ist offen und vom User zu entscheiden. Vorgelagert bleibt der Reihenfolge-Zwang innerhalb der Phasen (Achse 05: T18→T19→T22→T25 als harte Vorbedingung fuer T31+T37; Phase G erst nach 02-V3-Bench).
Dieselbe Bewegung — Mechanismus vs. Policy trennen, ein Enum zu generischer Konfiguration aufweiten — zieht sich durch alle Stufen: v2.0.0 generalisierte scope von Enum zu VARCHAR(50) fuer Multi-Tenant; die MT-Migration 058 (scope_generalize) setzt das Altbestand-weit fort (CHECK-Drop + VARCHAR(20)→50 auf dream_links/blobs/sources/write_log); und die geplante Workflow-Engine fuehrt denselben Schritt eine Ebene tiefer durch — das hardcoded 4-Klassen-block_role-Enum (system-meta/audit-trail/reference/knowledge) wird zu dynamischer Policy-Config, sodass User eigene Workflows auf ctx bauen. Modell C haelt scope explizit als Daten-Diskriminator und legt das Tenant-Register schmal darueber.
Quer durch MT-Kern und Enterprise-Aufsatz dokumentiert die Quelle fail-closed als erklaerte oberste Prioritaet: RequireScopes-Kontrakt (leeres scope-Array → ErrNoScopes, unknown → [] → rrf lehnt ab, T04/T07 committet), COALESCE-Untergrenze in ctx_auth (T15), MCP-private-Default-Fix (K-SEC2, T07), Suspend-Status-Gate. Der Enterprise-Review benennt explizit sechs verifizierte fail-OPEN-Stellen (Chain filtert Backend.Scope nicht; CreateApiKey laesst tenant_id/tenant_role weg → DEFAULT default-Tenant; quota.scope ohne _-CHECK; descendant-scope-Set ohne RequireScopes u.a.), die als INV-1 geschlossen werden muessen. Die User-Entscheidung 2026-06-17, die Backend-Sicht auf Whitelist (own ∪ granted, fail-closed) statt additiv zu stellen, schliesst nach Quelle die einzige verbleibende fail-open-Luecke und macht drei Grant-Ebenen (scope/block/backend) konsistent.
Skalierungs-Entscheidungen werden konsequent gemessen statt geblind gesetzt: E7 (HNSW-Filtered-Search-Cap, Graph-Hop-Budget, Background-Fairness) wird per Bench-Welle T16/02-V3 zuerst gemessen, dann max_scan_tuples-Cap; die Phase-G-Skalen-Bench T42 (OR-Kosten @1M) ist harte Vorbedingung fuer die teure RRF-OR-Welle T40b; OD-1 (geteilte vs. per-Tenant-Embedding-Generation) ist als CoW-Embedding entschieden mit Bau-Trigger 'erster inkompatibler Tenant ODER gemessener HNSW-recall-Druck'; der externe Vektor-Store (Qdrant/Milvus) wird verworfen, weil pgvector @1M gebencht ist und der Kern-Wert Hybrid-Fusion in EINER Transaktion bleibt.
Tragende Erkenntnis des Enterprise-Aufsatzes, von vier der fuenf Dimensionen unabhaengig getragen und gegen HEAD verifiziert: ein Reseller verwaltet seinen Sub-Tree, sieht aber NULL Block-Inhalte; eine Konzern-Mutter vererbt Verwaltung transitiv abwaerts, aber Daten-Sicht entsteht NUR ueber eine explizite context_tenant_grants-Kante. read_scopes wird ausschliesslich aus tenant_id + Grants gebaut — die Management-Hierarchie spielt keine Rolle. Der Grant-Write ist damit der einzige Pfad, der read_scopes weitet, und das hoechstwertige Eskalations-Ziel; diese Trennung soll test-gepinnt erhalten bleiben.
Jede der 20 committeten MT-Wellen folgt laut Quelle demselben Muster (TDD rot→gruen, 3-Linsen-Verify, Nachverifikation, pre-commit 0 issues). Migrations-Nummern werden linear neu vergeben (K-M1), weil vier Achsen 058 beanspruchten und der Runner ON CONFLICT DO NOTHING still ueberspringt; Gates pruefen Artefakte/Spalten, nie Nummern. Harte Reihenfolge-Zwaenge ziehen sich durch: 05-A8 (T25) MUSS vor 03-W5 (T31) und 04-W5 (T37), sonst fail-OPEN; die teure RRF-OR-Welle T40b erst NACH gruener Skalen-Bench T42; Phase G erst nach Phase C/02-V3-Bench.
Das aktive Grossvorhaben: Multi-Tenant-Ausarbeitung im Gold-Standard, Tenant-Modell C (Hybrid, 3-Ebenen Tenant/Scope/Block), durch User-Praezisierung 2026-06-15 entschieden (E1). Umfang 44 Wellen (T01-T43, T40 gespalten in T40a/b) ueber 7 Achsen / Phasen A-G, 11 Migrationen 058-068, ~7805 LOC inkl. Tests. Bau-Status gegen Git verifiziert: 20 Wellen committet (jede TDD rot->gruen + 3-Linsen-Verify + Nachverifikation + pre-commit 0 issues), Migrationen 058-064 committet. Branch feat/multi-tenant ist lokal, NICHT gepusht/deployed; echte Live-DB max(migration)=57.
01-T1, Phase A. Altbestand-Generalisierung: CHECK-Drop chk_blob_scope/chk_source_scope + VARCHAR(20)->50 auf dream_links/blobs/sources/write_log. Modellunabhaengige Vorbedingung fuer ALLES. Committet 180d894.
01-T2, Phase A. context_tenants (UUID-PK, slug, status-CHECK) + context_tenant_scopes (scope PK, tenant_id FK) + context_api_keys.tenant_id/tenant_role (CHECK = E2-Satz) + default-Tenant-Backfill (feste UUID, 3 scope-Zeilen, 7 Keys, idempotent). owner|admin|member RBAC-Bootstrap. Committet 0a18292.
02-V1, Phase C. Cross-tenant Read-Kanal (grantee_tenant FK->tenants, granted_scope FK->tenant_scopes, ON DELETE CASCADE, uq grantee+scope, idx). Additive Tabelle, kein Konsument (no-consumer). Committet 2b08ec3.
01-T3 + 02-V2 in EINER Migration (K-A1). ctx_auth DROP+CREATE: tenant identity (+tenant_id/tenant_role via Join) + Suspend-Status-Gate + positional read_scopes-Body (own ∪ grants, _-gefiltert, COALESCE-Untergrenze). AuthResult.TenantID/TenantRole + auth.go:48 NAMED-SELECT additiv. Committet 3bdaf30.
01-T4, Phase A. Per-tenant scope foundation: TenantScopes(tenantID) Lookup + RequireScopes (fail-closed read contract: unknown -> [] -> rrf lehnt ab, leeres scope-Array -> ErrNoScopes). Committet bd6897c.
01-T5 (Teil), Phase A. tenant-create/list/get/update Actions, admin-gated, eigene reservedSlug-Validierung. Committet 0bc2133.
01-T5 (Teil), Phase A. FK-geordneter batched DELETE (dream_links->blocks->blobs->scopes->tenant, NICHT metadata-only). Committet 9033ef5.
01-T5 (Teil), Phase A. Per-Turn owner-tenant Status-Recheck (Suspend-Snapshot-Entwertung). Setzt die in E6 offene Entscheidung um. Committet 2c2d1d9.
01-T7, Phase A. RequireScopes-Wiring in SearchBlocks + Manage-Reads + Blob/Overview-Reads; MCP-private-Default -> fail-closed (L7, K-SEC2 — 05-A10 hierher fusioniert). Committet e68c391.
06-C1, Phase B. Store v2: base-Pointer + leerer Tenant-Cache (sync.Map) + SnapshotForRequest/SnapshotForTenant (faellt heute IMMER auf base zurueck), Snapshot() unveraendert, Replace wiped Cache + baseGen-Stempel, single-flight. Committet 7e45d42.
02-V3 (Teil), Phase C. Vier Cross-Tenant-Leak-Pfade als end-to-end Graph-Isolations-Probes verankert (negativ-geprobt, keine Produkt-Code-Aenderung). Committet 9f846ed.
02-V4, Phase C. Admin-gated manage-actions tenant-grant-create/list/delete + store CRUD (Freund-Tenant-Workflow). Read wirkt sofort (naechster Auth resolved neu). Committet 563abae.
05-A3, Phase D. auth.Role + reine Funktionen (fail-closed-Lattice, Leer-Guard tenant=='' -> false VOR is_admin-Kurzschluss). Admin-Tier-Foundation. Committet f93082e.
05-A4, Phase D. whoami additiv um tenant_id + per-tenant role (admin-Feld bleibt back-compat). Committet 19110c6.
05-A6, Phase D. ListApiKeys(tenantFilter, activeOnly=true) WHERE tenant_id (schliesst L1-Enumeration). activeOnly=true ist benannter Verhaltens-Change fuer Operator-Tooling. Committet 8482fd4.
05-A7, Phase D. DeleteApiKey(id, tenant, isServerAdmin) mit WHERE id=$ AND tenant=ANY($), atomar in Store-Schicht (TOCTOU-frei), 404-no-oracle. Schliesst L2-DoS. Committet ffcdce3.
03-W1, Phase E. Migration 064 (idx_settings_scope_key/idx_secrets_scope_name, scope-leading) + store.LoadSettingOverridesMulti (zwei-Scope-Pass tenant+_global, tenant gewinnt, empty-scopes fail-closed). Kein Konsument. Committet 3f182e8.
04-W1 (Teil), Phase F. Migration 062: context_backends.scope VARCHAR(50) NOT NULL DEFAULT '_global' (K-S1) + UNIQUE-Swap name->(scope,name) + idx_backends_scope + Audit-Trigger. Backend.Scope geladen/gescannt (read path). ctxd verhaltensgleich (alle 5 Backends _global). Committet ca7c0f5.
04-W1 (Teil), Phase F. Migration 063: context_tenant_quota + trg_tenant_quota_notify (K-N1) + llmlog/access_log-Indizes. Quota leer = fail-open = heute. Committet 70d944c.
04-W3 (Teil), Phase F. Telemetrie-Attribution: api_key_id am query-synthesis Pfad (synthesize.go — der teuerste, heute unattribuierte LLM-Pfad). Committet ddbfd7f.
01-T6, Phase A. CreateApiKey +tenantID-Param (Key erbt tenant_id des Erzeugers) + {shared}-Default tenant-bewusst (R-LEAK5, api_keys.go:67) + whoami liefert owning-tenant-Identitaet. Deps T03/T05, E4 fix. Negativ: neuer Key fuer Tenant B -> read_scopes ohne default-shared-Bloecke. ~90 LOC. (Plan-ID T06; Briefing nennt T06 nicht als committet.)
06-C2, Phase B. TenantOverlay-Typ + settings-Implementierung (zweiter Overlay-Pass: _global-Basis + tenant-Rows, Praezedenz tenant>global, voller config.Build mit zwei Override-Slices). Deps T08, 03-MT3-W3 (loadTenantOverrideRows-Refactor). ~120 LOC.
06-C3, Phase B. Store.overlay per Boot injiziert (main.go); SnapshotForTenant baut/cached echte Tenant-Generationen; Build-Fehler -> base (fail-safe). Deps T09. ~40 LOC.
06-C4, Phase B. NOTIFY-Payload-scope + Listener-Dispatch (InvalidateTenant bei tenant-scope, Replace+wipe bei _global); No-Notify-Loop-Invariante gewahrt. Deps T10, 03-MT3-W6 (Migration 065 + Trigger-Payload live). ~60 LOC.
06-C5, Phase B. ConfigStore-Interface um SnapshotForRequest; ~12 TENANT-Request-Call-Sites umstellen, tenantScope INTERN aus AuthResultFromContext (kein Scope-Param, fail-closed by construction). Deps T10, 01-T3. ~90 LOC.
06-C6, Phase B. Scheduler/audit/classify loopen ueber Tenant-Liste, SnapshotForTenant pro Iteration (round-robin, E7-Fairness offen). Vorbedingung K-SNAP1: tenant-loser Pfad SnapshotForTenant(_global) muss existieren. Deps T10, 01, E7. ~110 LOC.
02-V3 (Rest), Phase C. Vier Leak-Pfade als Mutationstests + scope-Selektivitaets-Bench (R-SCALE1/2/3, E7): 1M-x-N-Korpus mit scope-Selektivitaet als Achse, EXPLAIN ANALYZE small-tenant-Query. Deps T15. ~260 LOC. (T16a end-to-end-Probes bereits committet.)
05-A1, Phase D. Konsum-Welle: tenant_role/tenant_id + ctx_auth 6->8 sind Achse-01-Artefakte (060); verifiziert nur den Konsum-Kontrakt. Deps T03. ~0 LOC.
05-A2, Phase D. AuthResult + Authenticate Scan um TenantID/TenantRole (reiner Daten-Transport; Felder gehoeren Achse 01, Scan-Anschluss hier verifiziert). Deps T18. ~15 LOC.
05-A5, Phase D. non-server-admin home_scope MUSS == eigener Tenant (schliesst L3-Escalation). Negativ: non-server-admin mint fremder Tenant -> 403. Deps T20. ~40 LOC.
05-A8, Phase D. actionRequiresAdmin -> actionTier + requireTenantAdmin(targetTenant): tenant-admin darf key/mcp/backend/audit, NICHT gaming/dream/Status/llmlog/events (server-admin). Verallgemeinert K-SEC1 Write-Gate. HARTE Vorbedingung fuer 03-W5 (T31) + 04-W5 (T37), sonst fail-OPEN (R-LEAK8). Deps T22/T23/T24. ~50 LOC.
05-A9 (optional, E5), Phase D. Migration 066: context_oauth_clients.tenant_id + Backfill aus created_by.home_scope (Modell-C-UUID) + _orphan-Sentinel (server-admin-only) + MCP-Client tenant-owned (schliesst L6). Nur falls E5=ja. Deps T25. ~50 LOC.
03-W2, Phase E. tenancy-Tag (tenant-overridable|global-only, untagged Leaf -> Boot-Panic) + admitOverride-Gate. gaming.active + 4 embed-Keys (embed.host/protocol, dream_embed.host/protocol) = global-only. R-SCALE6-Gate: Spy auf embedcache.Flush. Cross-achsen-frei, sofort startbar. ~230 LOC.
03-W3, Phase E. loadTenantOverrideRows(tenant) + Merge im Build-Pfad (tenant>global>env>default). Bootstrap bleibt _global-only. Liefert den Refactor, den 06-C2 (T09) konsumiert. Deps T28, 01-T3. ~260 LOC.
03-W4, Phase E. cfg.SnapshotForRequest/SnapshotForTenant liefert tenant-aufgeloeste Config gegen 06-Halterung. EIN Snapshot pro Operation pro Tenant (kein torn read). Vorbedingung K-SNAP1: tenant-loser Pfad fuer Scheduler. Deps T29, 06-C3. ~280 LOC.
03-W5, Phase E. 12 Handler-Stellen tenant-scoped + tenant-Secret-Resolver + Zwei-Schreib-Welten (IsOperator schreibt _global, tenant-admin nur TenantOf(ar), R-LEAK8) + resolveGlobalFallback gegated auf tenant.allow_shared_secrets (default false, E5-D2) + referencedBy folgt resolveScopes-Kette. Reihenfolge-Zwang: 05-A8 (T25) VOR W5, sonst fail-OPEN. Deps T29, 05-A8, E5. ~380 LOC.
03-W6, Phase E. Migration 065: notify_settings_write CREATE OR REPLACE, scope im NOTIFY-Payload (K-N1) + scope-getragene Cache-Invalidierung. Optimierung; ohne W6 invalidiert jeder Write alle Tenants. 063-Quota-Trigger erbt scope-Payload automatisch nach 065. Deps T30, T27, 06-C4 (soft). ~220 LOC.
04-W2, Phase F. visibleTo als ERSTE Filter-Case (_global OR caller-Tenant, _-Praefix-Guard K-SEC3) + Caller-Tenant durch 5 Request-Pfad-Call-Sites (synthesize.go:398, query.go:432/661/905, engine.go:569). Egress-Isolation (R-LEAK7). Negativ: fremdes tenant-private prio-1000-Backend NICHT in Chain(...tenantA). Deps T33, 01-T3. ~350 LOC.
04-W3 (Rest), Phase F. Entry.APIKeyID an allen Caller-getragenen WIRE-Calls: chain.go:307-328 (translate/temporal), chain.go:198-217 (embed). Dream bleibt NULL (Background). Optional scope-Denormalisierung (R-ATTR1, E8). Deps T33. ~200 LOC. (query-synthesis-Teil T35a bereits committet.)
04-W4, Phase F. TenantQuota-Typ + QuotaAccountant (lock-frei atomic.Pointer+CAS, TTL) + QuotaGate vor Locality=='external' (Kosten-Gate) + daily_calls vor jedem Versuch + pool.default_tenant_quota + manage-Action tenant-quota-set/get + CLI ctx quota. Lokal IMMER erreichbar. Deps T34+T35, 02/03-Settings-Overlay. ~450 LOC.
04-W5, Phase F. backend-create erzwingt scope=ar.HomeScope (tenant-admin)/erlaubt _global (server-admin); update/delete 404 fuer fremde/_global; backend-list=Status(tenant); GET /api/llmlog + 24h-Rollup per-Tenant-Filter + sum(cost_usd). SSE-Re-Auth (events.go:469-471) + Pull-Filter MUESSEN zusammen deployen (K-T1). Deps T34+T35, 05-A8. ~400 LOC.
04-W6, Phase F. Dream/Digest pro Tenant (DISTINCT home_scope aktiver Keys) mit tenant-sichtbarem Chain (router.go:44/56/75) + per-Tenant-ScopeSensitivityFloor. Kein tenant-privates Backend im Background fremd-genutzt. Deps T34, 01, 02/06. ~400 LOC.
07-W1, Phase G (NEU 2026-06-15). Migration 067: context_block_grants (block_id FK CASCADE + grantee_tenant FK + granted_by + uq + idx; KEINE permission-Spalte), backfill-frei/idempotent, kein Konsument. T07-Count 28->29. Deps T02. ~150 LOC.
07-W2, Phase G. VisibilityPredicate zentral +Grant-Set-Param (Pflicht-Klammer) + GetBlock/ResolveBlockID/SearchBlocks/Browse/GraphExpand-inline + 3 MCP-Handler. Orakel-Fix (Log hinter GetBlock, Full-UUID nicht ungated). KEIN ctx_rrf. Deps T39, 01-T4, 01-T7. ~290 LOC.
07-W3, Phase G. Grant-Block = Blatt (sichtbar, nicht traversierbar) in ALLEN 3 Seed-Quellen (frontier / Hop-0-Focus / GraphExpand-Seed). G4 dreigleisig negativ-geprobt. Deps T40a (+T40b fuer RRF-Arm). ~260 LOC.
07-W4, Phase G. OR-Kosten @ 1M (scope x Grant x Array-Groesse), EXPLAIN ANALYZE BitmapOr/Seq-Scan-Kipp, Strategie-A-Schwelle + U-E7-Cap. Reiner Bench, kein Produkt-Code-Delta. HART abhaengig von 02-V3. Deps 02-V3 (HART), T40a. ~390 LOC.
07-W5, Phase G. ctx_rrf DROP+CREATE (Migration 068) +p_granted_block_ids UUID[] DEFAULT NULL sechsfach, innere Klammer ERZEUGT; rrf.Search +$13. Bestand byte-identisch bei leerem Set. Reihenfolge: erst NACH gruener T42 (sonst W8-Verstoss, R-SCALE8). Deps T40a, gruene T42. ~270 LOC.
07-W6, Phase G. block-grant-create/list/revoke admin-gated + Eigentums-Pruefung (block.scope ∈ TenantScopes, Unaufloesbarkeit->DENY) + cross-tenant-opt-in (default AUS) + grant-fester Egress-Floor (annotateSensitivities-Signaturaenderung, max(owner,grantee,GRANT_FLOOR_DEFAULT,block.sens)). Deps T40a (+T40b RRF-Arm); per-Tenant-Admin spaeter 05-A8. ~440 LOC.
Enterprise-Aufsatz auf die abgeschlossene Multi-Tenant-Planung (Modell C, 44 Wellen T01-T43, Migrationen 058-068): Reseller/Mandanten-Verwaltung, Konzern-Hierarchie (Mutter/Tochter), ephemere Test-Tenants, separate Models (Embedding+Inference) und Lastverteilung. Das Anforderungs-Dokument (Stand 2026-06-16, ~140 Anforderungen ueber 5 Dimensionen) ist die Anforderungs-Wahrheit fuer die naechste Planungsstufe — es legt fest WAS verlangt wird und WO es den Code beruehrt, NICHT die Bau-Wellen-Reihenfolge. Alle IST-Stellen gegen HEAD feat/multi-tenant (ddbfd7f) re-verifiziert. Tragende Erkenntnis (von vier der fuenf Dimensionen unabhaengig getragen): Verwaltungs-Autoritaet ist orthogonal zur Daten-Sicht — der context_tenant_grants-Write ist der einzige Pfad, der read_scopes weitet.
Ein Tenant-Key (Reseller) provisioniert + verwaltet Tenants INNERHALB seines eigenen Sub-Trees (Keys/Quotas/Grants), sieht aber NIE deren Daten. Mehrstufig (Reseller-of-Reseller). 23 Anforderungen R-RES-01..R-RES-23. Braucht das fehlende Hierarchie-Primitiv parent_tenant_id, einen sub-tree-bewussten Admin-Praedikat-Satz, hierarchische Quota mit Usage-Rollup und einen resumablen Sub-Tree-Prune. Heute der EINZIGE Akteur mit Tenant-Management ist server-admin (is_admin); kein Reseller/per-tenant-admin erreicht irgendeine Tenant-Management-Action (actionRequiresAdmin context_manage.go:206-207). Modell C bleibt: scope ist Daten-Diskriminator, der Baum lebt auf context_tenants.
context_tenants erhaelt parent_tenant_id UUID NULL REFERENCES context_tenants(id) ON DELETE RESTRICT + btree-Index + PRAEVENTIVER acyclicity-Trigger. default-Tenant = Wurzel (parent NULL). Repo-weiter grep fuer parent_tenant|ancestor|reseller|RECURSIVE|closure|subtree ueber internal/+migrations/ = NULL Treffer (context_tenants flach 059:55-65). Empfehlung D-RES-01: self-FK + recursive CTE pro Query + Constraint-Trigger; Closure-Table premature bei diesem Row-Count. Tenant struct/tenantCols/scanTenant (tenant.go:103-130) erweitern.
Tenant-Management-Authz vom binaeren server-admin-Gate auf Praedikat-SATZ: (a) IsTenantAdminOf self bleibt; (b) NEU CanAdministerDescendant = STRIKTER Descendant + NICHT self; (c) server-admin global. IsTenantAdminOf (role.go:47-61) ist heute STRIKT ar.TenantID==tenant, kann descendant nicht ausdruecken. Vermischtes OR self oeffnet Self-Promotion. EXPLIZIT: default/root-owner ist NIE impliziter Reseller-ueber-alle (braucht weiterhin is_admin). BEHAVIORLOS bis 05/T25 das Gate von is_admin auf IsTenantAdminOf flippt.
Tenant-Deletion durch Reseller auf Sub-Tree beschraenkt; Tenant mit Descendants verweigern ODER als EXPLIZITE geordnete RESUMABLE Kaskade leaf->root. PruneTenant (tenant.go:277-313) ist autocommit-batched (kein Tx-Wrapper) -> passt bei 1M+ NICHT in eine Tx. Zuerst status=offboarding auf GANZEN Sub-Tree (friert create ein), dann leaf-zuerst prunen (Crash-Resume = no-op). default-Wurzel-Delete bleibt verweigert. VORBEDINGUNG: context_dream_links scope-Index CONCURRENTLY out-of-band VOR Auslieferung.
Bei >1 ctx-Prozess driften prozess-lokale Counter (pool.go:259 atomic.Pointer); ancestor charge-up zaehlt N-fach NICHT (fail-OPEN auf Geld) -> SHARED Counter-Store (PG atomic UPSERT) ODER single-process-Constraint. Plus Invalidierungs-Kanal bei parent-Aenderung (sonst staler Cache = ge-reparenteter Tenant beim alten Reseller). Background-Kost GEBLOCKT auf T38/06-C6 (Dream/Digest global = dominanter nicht-attributierter Block).
Derselbe Baum (parent_tenant_id), andere Geschaeftslogik: transitive Verwaltungs-Vererbung abwaerts (Owner verwaltet alle Nachkommen), Chinese Walls zwischen Schwester-Toechtern, zentrales inhaltsfreies Compliance-Audit, Suspend-Kaskade auf den Sub-Baum. KH-F1..KH-L1 (functional/non-functional/security/data-isolation/lifecycle). Drei harte Vorbedingungen: (a) ungecappter HNSW-Post-Filter ist der @1M-mal-N-Killer; (b) transitive Verwaltung behaviorlos bis 05/T25; (c) Background-Suspend-Stop existiert nicht ohne 06-C6/04-W6 (Scheduler tenant-blind scheduler.go:700/724). Reseller = strikter Descendant + NICHT self; Konzern = transitive Verwaltung abwaerts.
(1) ctx_auth-Auth-Zeit-Gate (060:146-147) von Single-PK-Lookup auf bounded recursive ancestor-Walk — kein Vorfahre != active, NULL/unaufloesbar -> __UNAUTHORIZED__ (fail-closed-Arm erhalten). (2) Per-Turn-Engine-Cut: ownerActive (engine.go:271) -> TenantStatusForScope (scope-keyed) muss sess.Scope->tenant->ancestor-walken (NEUE store-Fn), Turn abgelehnt (kein Sentinel). (3) Background-Stop: scheduler.go:700/724 muss suspendierte UND ancestor-suspendierte ueberspringen — existiert NICHT, HARTE Abhaengigkeit auf 06-C6/04-W6 (BLOCKER).
Gleiches parent_tenant_id -> WEDER gegenseitige Sicht NOCH Verwaltung. Vertikal-only. Mutationstest BEIDE Richtungen: (a) lateral (B1 sieht B2-scopes NICHT, B1-Admin verwaltet B2 NICHT); (b) aufwaerts (A verwaltet B1+B2, B1 verwaltet B2 false, B1 verwaltet A false). role.go:60 ancestor-Check false fuer Geschwister UND aufwaerts.
ancestor-Status-Walk auf den ctx_auth-Hot-Path, der bereits WRITE-bound ist (last_used_at write-on-read 060:111-122, R-SCALE7 offen). Daher denormalisiertes effective_status/root_tenant_id (Trigger-gepflegt) als PRIMAER-DESIGN — ein indexed Lookup statt recursive CTE pro Request. recursive CTE mit variabler Tiefe ist plpgsql-generic-plan-Verlierer (060:90 cached generic plan nach ~5 Calls). ancestor_tenant_ids + Status-Gate in EINE Resolution fusionieren (kein N+1). Tiefen-Cap KONSTANTE (Vorschlag 16).
handleTenantUpdate lehnt default-Tenant als Re-Parent-SUBJEKT ab (darf nie Kind werden) UND verhindert, dass ein anderer Tenant ihn via transitive Verwaltung uebernimmt. Wuerde default Kind, koennte dessen Owner via KH-F3 den Bestands-Korpus (private/work/shared) verwalten/suspendieren. KH-S10 ergaenzt: voll-transitive Owner-Vererbung macht den default-Owner zum De-facto-zweiten-Server-Admin OHNE is_admin-Bit, sobald andere Tenants unter default haengen — default kinderlos halten ODER transitive Verwaltung an der Wurzel begrenzen.
Korpus-Test-Tenants als first-class Use-Case: billiger Create (Modell C), korrektheits-sicherer Teardown, additive Test-Markierung, Ausschluss aus ALLEN Background-Konsumenten (Overview/Louvain, Dream, Daily-Synthesis), Quota-Befreiung, Auto-Cleanup-Janitor, strikte Isolation gegen Prod, Embedding-/Vektorraum-Hygiene. F-ETT/NF-ETT/S-ETT/DI-ETT. ZENTRALE W3-Korrektur: der Background-Ausschluss-Filter gehoert an context_blocks.scope (loadNodes cluster.go:164 + aggSQL), NICHT an context_dream_links.scope (057:20-21 + visibility.go:20-21 verbieten l.scope als Diskriminator HART; loadEdges hat keine scope-Spalte).
Test-Tenant als markierter context_tenants-Eintrag, additiv ueber metadata.ephemeral=true (059:63 JSONB), NICHT ueber neuen status-Wert. Begruendung (S-ETT-03): ctx_auth (060:32-36 v_status IS NULL OR <> active -> __UNAUTHORIZED__) wuerde status=ephemeral jeden Test-Request fail-closed abweisen (401, Test unbenutzbar). ephemeral orthogonal zum Lifecycle, ohne Auth-Tabellen-Lock. Plus redundante slug-Konvention (test- Praefix, NICHT _) als zweite Erkennungs-Achse (Defense-in-Depth).
defaultTenantID nach internal/store exportieren (store.DefaultTenantID) und entweder PruneTenant um Default-Reject ODER separate store.PruneEphemeralTenant(id) die VOR dem Prune (a) id<>Default UND (b) metadata.ephemeral==true prueft. Handler-Pfad UND Janitor erben dieselbe Garde by-construction (doppelt fail-closed). Heute ist defaultTenantID UNEXPORTIERTE handler-lokale Konstante (tenant_manage.go:146); Janitor (internal/events) kann die handler-const NICHT importieren. Janitor-WHERE doppelt fail-closed; Cast-Fehler schliesst Zeile AUS.
fetchDailyDreamLinks (synthesize_report.go:223-244) liest ALLE context_dream_links GLOBAL ohne scope-Param; GenerateDailyReport schreibt via UpsertBlock in cfg.Scheduler.HomeScope (Default private) -> Test-Aktivitaet im Klartext in einem PROD-retrieval-sichtbaren Synthese-Block. EINZIGER echter Test->Prod-DATEN-Leak (dream-links sind by-construction same-scope dream.go:672, computeClustering skippt dangling). F-ETT-05/06 sind am HEAD nahezu NO-OP (cfg.Scheduler.ReadScopes nur default-Tenant), Nutzen GEBLOCKT auf per-Tenant-Iteration 06-C6.
Auto-Cleanup-Janitor erbt NICHT das gutartige Profil von rebuildOverviewOnce/EvictBodies. MUSS: (1) eigenen pg_try_advisory_xact_lock (Muster overviewLockKey cluster.go:34/268), damit bei horizontaler Skalierung nur EINE Instanz prunt; (2) activeQueries-Yield (scheduler.go:545/592/714), damit 1M-Teardown nicht in Prod-Last faellt; (3) PRO TICK nur EINEN Tenant (oder Batch-Budget). Janitor friert ablaufenden Test-Tenant VOR dem Prune atomar ein (UpdateTenant status=offboarding, F-ETT-08b).
Per-Tenant Embedding-Provider mit Vektorraum-Isolation, embed-Provenance, scope-selektiver Rebuild-Job (inkl. zwei Embed-Rollen + drei Write-Pfade); per-Tenant Inference-Whitelist via NOCH-NICHT-EXISTENTES context_backend_grants. SM-F/SM-N/SM-S/SM-DI/SM-O/SM-Q. Die teuerste und gefaehrlichste Dimension, weil alle Tenants HEUTE EINEN HNSW-Vektorraum teilen (001:250) und gemischte Generationen darin NACHWEISLICH (cosine 0.997, embedcache.go:174) verboten sind. Dimension hart auf 1024 (vector(1024), embed.go:30 TargetDims=1024).
Per-Tenant Inference ueber Whitelist visible(tenant)=own (scope=tenant) UNION granted UNION _global. DREI Voraussetzungen, Fundament FEHLT: (a) context_backend_grants EXISTIERT NICHT (Migrationen enden 064; context_tenant_grants 061 ist BLOCK-READ, KEINE Backend-Sicht) — komplettes Subsystem (Tabelle+CRUD+NOTIFY+Handler) bauen; (b) Chain (pool.go:258-301) MUSS Scope-Filter bekommen, Backend.Scope (backend.go:132) heute NICHT ausgewertet (fail-OPEN: jeder Tenant bekommt VOLLE _global-Chain), Signatur-Bruch an ALLEN 9 Call-Sites; (c) visible()-Join im Pool-Snapshot vor atomic swap. Scope-Filter MUSS deployt sein BEVOR der erste Nicht-_global-Backend existiert (SM-S0).
embed-Role-Routing HART entkoppelt vom Inference-Whitelist (SM-F4) + BEIDE Embed-Rollen (RoleEmbed UND RoleDreamEmbed, backend.go:49): role in embedRoles bekommt einen Sonderzweig, der NUR scope=_global aufloest. embed darf NIE tenant-scoped Backend ziehen — sonst gewinnt nach SM-F4-Nachruestung ein tenant-scoped embed-Backend mit hoeherer Priority die scope-blinde Sortierung (pool.go:289-299) und uebernimmt query-embed eines ANDEREN Tenants. Plus: embed-Chains NIE mehr als EINE Generation (Pool-Invariant-Check beim Reload, embed-Failover auf anderes Modell VERBOTEN).
Bei Modellwechsel ALLE Bloecke des Scopes stale markieren + neu einbetten, scope-gefiltert. Stale-Marker MUSS bestehende Infra nutzen — embed_status (001:43, 002:14) mit partiellem Index idx_embed_pending (001:206) statt embedding=NULL (verliert alten Vektor + scope-weites Sichtbarkeitsloch = Denial-Vektor). ALLE DREI Pick-Queries (scheduler.go:762, query.go:898, Rebuild) auf embed_status umstellen (heute liest KEINE den Index = toter Index). Idempotent, crash-resume, suspend-resume (Tenant-SUSPEND waehrend Rebuild MUSS pausieren, nicht abbrechen), ratenbegrenzt. Edit-vs-Rebuild-Race deterministisch.
context_embed_cache global (kein scope, PK (text_hash, model)), text_preview (200 chars Klartext, embedcache.go:27/75-78/144-145). Bei geteiltem Modell liegen ALLE Cache-Eintraege ALLER Tenants im selben model-Bucket — ein DB-Leser/break-glass/SQL-Injection-Fund sieht 200-Zeichen-Fragmente JEDES Texts JEDES Tenants. Plus: PruneTenant loescht den scope-losen Cache NICHT -> text_preview ueberlebt Tenant-Delete (DSGVO-Loeschpflicht verletzt). Fix: text_preview im MT-Betrieb GANZ entfernen (loest SM-S2+SM-S5 gemeinsam); text_hash genuegt zur Identifikation.
Bei echter per-Tenant-Generation braeuchte ein Tenant eine SEPARATE Index-Partition + ZWEITE ctx_rrf-Funktion. Jede Kopie der scope=ANY-Postfilter-Logik (ctx_rrf an mehreren CTE-Stellen 018:71/91/118/139) ist eine NEUE scope-Leak-Stelle. visibility.go self-dokumentiert sich als designated single switch point. Aufloesung (VERBINDLICH): die scope=ANY-Sicht-Filter-Logik MUSS in EINE wiederverwendbare SQL-Funktion/CTE extrahiert werden, die JEDE ctx_rrf-Variante teilt, BEVOR eine zweite Partition-Variante entsteht — STARKES Argument fuer OD-1=A. read_scopes-Build bleibt FLACH (Gleichheit scope=ANY, kein Praefix/Rekursion in ctx_auth 060).
Backend-Pool, Per-Tenant-Fairness, Quota als Lastschutz, Health/Failover, Background-Fairness, horizontale Skalierung. LB-A1/LB-F/LB-N/LB-S/LB-D/LB-L/LB-OBS. Zentrale Korrektur (von allen drei Linsen als critical markiert): scope == Last-Einheit ist die Wurzel-Fehlannahme — der default-Tenant hat DREI scopes (059:83-87), die Last-/Quota-/Fairness-Einheit ist ar.TenantID, NICHT die scope-Partition. Pool.Chain() hat genau 9 produktive Call-Sites; 6 von 9 umgehen ChainCall.Do -> Enforcement MUSS in Pool.Chain() selbst (LB-F5).
JEDE Achse schluesselt auf die ueber api_key->tenant_id aufgeloeste TENANT-Identitaet (ar.TenantID), NICHT die scope-Partition. (a) Quota 063 von uq_tenant_quota_scope UNIQUE(scope) auf UNIQUE(tenant_id) (063:42-43/59), Rollup ueber alle scopes UND Keys; (b) chat.go:112 inflight map auf tenant_id; (c) Background-Round-Robin iteriert context_tenants, nicht scopes. ar.TenantID verfuegbar (auth.go:24, populiert aus 060). HINWEIS: durch User-Entscheidung 2026-06-17 ist die Quota-Granularitaet revidiert (siehe offene Frage).
Chain MUSS Scope-Whitelist anwenden BEVOR Last verteilt wird: visible(tenant)=eigene UNION granted (context_backend_grants ~069) UNION _global-Pool. Tenant ohne Grant/Backend sieht nur _global; _global-loser grant-loser Tenant -> LEERE Chain (fail-closed). Scope-Filter als ZWEITER reason-loser Switch-Case NACH HasRole + VOR Enabled/Trust (sonst leaken fremde Backend-Namen in die admin-Surface). context_backend_grants (~069, LB-F1b) als eigenes Schema mit fail-closed-FK (ON DELETE CASCADE, uq(grantee_tenant, granted_backend)); _global einziger System-Sonderfall (additiv per Default, NICHT ueber Grant).
Background-Pfad MUSS (a) fair ueber AKTIVE Tenants iterieren (status=active, suspended/offboarding HART uebersprungen) statt global aeltesten Block, und (b) FOR-UPDATE-SKIP-LOCKED race-frei ueber DreamParallelism>1. KEIN geteilter Cursor — pro Worker-Pick aktiven Tenant gewichtet-zufaellig ziehen (Backlog als Gewicht, Round-Robin-Floor). LB-F3b (logisch PRIOR): activeQueries (scheduler.go:104) DARF NICHT EIN globaler Counter bleiben, der den GESAMTEN Background suspendiert, sobald IRGENDEIN Tenant querying ist. Yield-Entscheidung pro bearbeitetem Tenant-Scope. Einhaengen in T38/06-C6.
Quota-Rollup-Abfrage MUSS api_key_id auf tenant_id aufloesen und ALLE jemals zum Tenant gehoerenden Keys summieren — auch DEAKTIVIERTE, deren Calls noch im Fenster liegen. NEUE Store-Fn KeysForTenantRollup(tenantID) []uuid.UUID (NICHT activeOnly); ListApiKeys(activeOnly=true) UNGEEIGNET (Tenant unterlaeuft Budget per Key-Rotation). context_llm_log.api_key_id ist FK-LOS (054:23) — nach Key-HARD-Delete zeigen Zeilen auf geloeschte Keys.
Innerhalb der gegateten+trust-gefilterten+nicht-cooled Menge MUSS ein Lastverteiler streuen statt deterministisch das namens-kleinste (pool.go:289-300 cooldown ASC, priority DESC, name ASC). Neuer Sort: (cooled ASC, priority DESC, in_flight ASC, name ASC) — In-Flight rankt NUR im (cooled, priority)-Plateau. In-Flight als eigener Lebenszyklus (LB-N5): neues inFlight int im prozess-lokalen healthState (NICHT im by-value-Snapshot), Inc/Dec an der Wire-Grenze (chain.go:94, embedcache.go:136), Dec per defer auch im Panik-/Cancel-Pfad. Round-Robin als einfacherer erster Schritt (LB-OD2).
Chat-Semaphore (chat.go:112/228 acquire(sess.Scope)) ist HEUTE per-SCOPE und damit KEINE Per-Tenant-Drossel — MUSS auf ar.TenantID umgeschluesselt werden, sonst multipliziert ein Multi-Scope-Tenant die ConcurrentTurns-Decke ueber seine N Scopes/Sessions. config.go ConcurrentTurns kommentiert als per-tenant, Implementierung per-scope. LB-D4: ein neuer Tenant (insb. Test/Staging) DARF NICHT per Default auf das _global-Prod-Backend last-verteilt werden — explizites opt-out am Tenant, sodass die Whitelist nicht jedem grant-/backend-losen Tenant automatisch die geteilte Prod-Ressource gibt.
Acht Szenarien (X1-X7 plus Reseller-Skeleton-Key), in denen mehrere Dimensionen an EINER Code-Stelle kollidieren, jedes gegen HEAD verifiziert mit verbindlicher Aufloesung. X1: Reseller-Kunde mit LAN-Ollama-embed + Test-Sub-Tenant + Last-Isolation (vier Seams an einer Stelle). X2: Suspend-Kaskade trifft geliehene-Scope-Session + Background-Kosten (drei Lecks). X3: Quota trifft Multi-Scope-Tenant + Embed-Last + Rebuild. X4: Sub-Tree-Prune mit ephemeren Toechtern + 2M dream_links + Rebuild. X5: Test-Cross-Tenant-Grant + Synthese-Leak. X6: Reseller-Key-Kompromiss als Skeleton-Key + Topologie-DoS. X7: Horizontale Skalierung trifft prozess-lokalen State.
INV-1 fail-closed durchgaengig (6 verifizierte fail-OPEN-Stellen, siehe Risiken). INV-2 _-Praefix = system, gehoert keinem Tenant (hart an DREI Stellen; _global wird NIEMALS Tenant/Parent/Root, Wurzel ist realer default-Tenant). INV-3 Single Switch Point (scope=ANY in EINE wiederverwendbare CTE, read_scopes bleibt FLACH). INV-4 Modell C = scope-Diskriminator, KEIN tenant_id auf Daten-Tabellen (parent_tenant_id + Quota-Umschluesselung betreffen nur MANAGEMENT-Tabellen). INV-5 Pausierbarkeit (drei Wellen NICHT additiv: CreateApiKey DROP DEFAULT, Chain-Signatur-Wechsel an 9 Sites, context_blocks-Partitionierung).
Naht-Marker als reiner Kommentar an die genannte file:line, damit der naechste Implementierer die Enterprise-Anforderung am Beruehrungspunkt sieht. // TENANT-DECISION(id) = HEUTE relevante Entscheidung/Luecke; // TENANT-FUTURE(id) = bewusst spaetere Welle. 16 Marker u.a. tenant.go:103 (parent-Kante), role.go:60 (CanAdministerDescendant), api_keys.go:36 (CreateApiKey), 060:146 (Status-Gate), pool.go:264 (Chain switch), validate.go:194 (embed-Gate), chain.go:198 (LogEmbedWire), 063:59 (uq_tenant_quota_scope falscher Kommentar).
Am 2026-06-17 hat der User sechs Multi-Tenant-Grundsatzentscheidungen getroffen: Scope-Konsolidierung work+shared→private (macht E4 gegenstandslos), scope-granulare Quota statt tenant_id-Umbau, OD-1 als CoW-Embedding, Cross-Vektorraum-Grant per Re-Embed statt Abruf-only, Wurzel = default-Tenant und Backend-Sicht als Whitelist (fail-closed, ändert T34). Das Embedding-Storage-Layout ist live als TOAST-EXTERNAL gemessen; die separate Tabelle context_block_embeddings ist identisch mit der OD-1-Form. Die nächste Bau-Phase startet mit der destruktiven Welle 0 (mit Backup), gefolgt von additiv/pausierbaren Wellen (parent_tenant_id, embed_model-Provenance, embed-Attribution T35b, metadata.ephemeral). Inferenz läuft durchgängig auf llama.cpp, Ollama ist komplett raus.
pre-MT, DB-weit über alle scope-Spalten (blocks/dream_links/blobs/sources/chat_sessions/settings) + Work-Key + Overview-Rebuild. Erste Welle der nächsten Bau-Phase, VOR den MT-Wellen 058+. Destruktive DB-weite Operation, mit Backup.
Danach additiv/pausierbar: ADD COLUMN NULL + idx + acyclicity-Trigger, ohne Konsument. Hierarchie-Fundament.
embed_model-Provenance schreiben über 3 Pfade (blocks.go:248, scheduler.go:803, query.go:927) + einmaliger Backfill → Voraussetzung für selektiven Rebuild. Teil von T35b. Billige Versicherung Phase 1, inkl. ctx_rrf-Lesepfad kapseln.
api_key_id durch EmbedChain/LogEmbedWire = Rest von T35b.
metadata.ephemeral als Test-Tenant-Marker + gezielte tenantCols-Projektion.
T34 als Whitelist: Chain wird own∪granted statt _global∪own; context_backend_grants (künftig ~069), Tabelle + CRUD + NOTIFY + Handler ist neu, fail-closed. T34 risikoarm baubar, keine weitere Entscheidung nötig.
Multi-Vektorraum (Generationen/per-Tenant-Modell/Blue-Green-Rebuild) → separate Tabelle context_block_embeddings(block_id FK, generation, embedding, PK(block_id,generation)) ist die richtige FORM, IDENTISCH mit OD-1. Bau-Trigger: erster inkompatibler Tenant ODER gemessener HNSW-recall-Druck bei wachsendem N — datengetrieben. Externer Store (Qdrant/Milvus): NEIN — Kern-Wert = Hybrid-Fusion in EINER Transaktion (RRF+FTS+Graph+temporal+scope, alles SQL); pgvector @1M ist gebencht.
T28 / T34 / T35b sind risikoarm baubar; keine Entscheidung nötig (T34 als Whitelist).
User-Idee, validiert: Idle-Voll-Swap (Chat-Server raus, Embed auf GPU, bei Query zurück). Page-Cache-Backoff (mmap, herbert 128 GB RAM / ~76 frei) macht Reload RAM→VRAM (~1–2 s) statt Disk. Offen: Warm-Restart-Messung (braucht Chat-Server-Bounce, Idle-Fenster).
Inferenz-Engine llama.cpp DURCHGÄNGIG, Ollama KOMPLETT RAUS (war Stack bis ~April 2026). Alle Backends sprechen OpenAI-Wire-Protokoll, Routing via context_backends. CHAT/SYNTHESE/DREAM/DIGEST/CLASSIFY/TRANSLATE: qwen3.6:27b (think:false) — Backend 'herbert-chat' (10.13.37.11:8089) @96k-context. RERANK: bge-reranker-v2-m3 — 'herbert-rerank' (10.13.37.11:8082), dediziert. EMBEDDING: qwen3-embedding-8b — 'llama-embed' (Docker, CPU am ctx-Host, getrennt von GPU), 1024d Matryoshka von nativ 4096d. Fallbacks: 'openrouter' (extern, prio 50), 'llama-cpu' (Docker CPU, prio 10). Hardware: herbert = Windows-Box, EINE Quadro RTX 6000 (24 GB), fährt 2× llama-server (Chat 27b @96k + Rerank), ~21 GB belegt; ctx-Host (Docker) hat KEINE GPU → Embedding CPU-only; KEIN GPU-Swap Embed↔Chat. CPU-Embedding = Durchsatz-Engpass bei Bulk-Rebuild, nicht Live-Query-Latenz.
Vision/Anker aus Session 2026-06-01 (NOCH NICHT GEBAUT, Reihenfolge: nach Web-Frontend, nach Multi-Tenant). Kern-Richtungsentscheidung: das hardcoded 4-Klassen-block_role-Enum (system-meta / audit-trail / reference / knowledge) wird zu dynamischer Policy-Config, sodass User eigene Workflows auf ctx bauen koennen. Erster Workflow darauf: Issues als isolierter Tenant-Korpus, der die ctx-Maschine (Guard, RRF, Dream, Scope) gratis erbt, plus GitHub-Forge-Sync. Roadmap in 4 Schema-First-Wellen (W1-W4) plus ein offener Themen-Backlog (Webhooks, Project-Boards-Sync, Multi-User, Auth-Redesign, Admin-Oberflaeche, autonome Agenten).
Schema-First, 1 Welle. Dynamische Type-Registry plus mitgelieferte Default-Type-Configs (Migration vom hardcoded block_role-Enum) und das Fundament der CLI-Familie ctx project (detect/init/show). Projekt-Identitaet, die Klone/Umzuege ueberlebt: github:owner/repo | git-root:<root-commit-sha> | manual:slug.
Schema-First, 1 Welle. Issue = context_block in repo-spezifischem Scope-Tenant; erbt Guard (Duplikat-Erkennung via HNSW-Similarity), RRF (related-issues, semantisch), Dream (autonomes Cross-Linking) und Scope-Modell (home_scope, v2.0.0) als Repo-Isolation. Repo-Agent = MCP-Key mit home_scope=repo-tenant, sieht nur sein Repo. Forge-Sync GitHub zuerst (Forge-Abstraktion; GitLab/BitBucket/self-hosted als Erweiterungspunkt), externer Spiegel via 3-Wege-Content-Hash (NICHT Timestamp — W16): github-ahead (pull) / ctx-ahead (push) / beide=Konflikt (anzeigen, kein Auto-Merge). CLI: ctx project issues (list/create/sync/status). Comments = eigene embedded Bloecke, Parent via Metadata-Pointer; RRF aggregiert Comment-Treffer auf ihr Issue. Neue strukturelle Link-Klasse (deterministisch aus Forge, conf 1.0): comment->issue belongs-to; erweiterbar issue->PR/milestone/sub-issue; strukturelle Links NICHT in den Dream-Graph mischen.
Schema-First, 1 Welle. Drift-Behandlung und Resilienz: bei GitHub-Ausfall in ctx weiterarbeiten (lokaler Fallback).
Schema-First, 1 Welle. ctx kanban: interaktive TUI bei TTY, strukturierte Ausgabe bei Pipe.
Offener Themen-Backlog (User-Vision 2026-06-01, zu dokumentieren sobald Basis steht). Webhooks eingehend und ausgeloest. Im Kern-Redesign als Beispiel fuer den programmierbaren Pfad (event-driven Webhooks) genannt.
Offener Themen-Backlog (User-Vision 2026-06-01). GitHub Project Boards <-> kanban-Sync via Projects-v2 GraphQL (spaet).
Offener Themen-Backlog (User-Vision 2026-06-01). Multi-User: ein Freund bekommt einen eigenen ctx-Tenant.
Offener Themen-Backlog (User-Vision 2026-06-01). Auth-Architektur Kern-Redesign: funktioniert fuer Einzelperson, braucht mehr.
Offener Themen-Backlog (User-Vision 2026-06-01). Admin-Oberflaeche fuer User/Tenants/Access-Keys/API-Token.
Offener Themen-Backlog (User-Vision 2026-06-01). Autonome Agenten arbeiten eigene Issues ab. Uebergeordnetes Ziel: sobald die Basis steht, dokumentiert der User (und Agenten) den Rest als Issues IM System selbst; dann entfaellt .project/todos.md ganz.
Der 41-Wellen-Masterplan G01–G41 für das Web-Frontend ist komplett gebaut. Die Release-Kette reicht von F1 (G09–G14/G15/G20) bis G39 Graph-Bench + G32 Pool-/Vault-Editor (v3.14.0, 2026-06-13), die den 41-Plan schließt. Zusätzlich wurde die außerhalb des Plans benannte F5-W6 Graph-Landkarte (W1/W2/W3) entworfen und mit v3.15.0 (2026-06-14) released. Offen bleiben W4 1M-Bench (optional), F6-C6 Write-Confirmation, Ingest-Generalisierung sowie F5-W7 Map-Mode (verfrüht).
Erster Release-Block des 41-Wellen-Plans, umfasst die Wellen G09–G14 sowie G15 und G20.
Wellen G16–G19, released als v3.3.0, markiert als RC-1.
Wellen G21–G24, released als v3.4.0.
Wellen G25–G31 plus G40 und G41, released über die Versionsspanne v3.5.0 bis v3.10.0, markiert als RC-2.
llmlog-Retention released als v3.9.0 (Teil der F3-Versionsspanne).
Dashboard-Block: G33 released als v3.11.0, G34 released als v3.12.0.
Chat-Kette mit Wellen G35–G38, released als v3.13.0, markiert als RC-3.
G39 Graph-Bench und G32 Pool-/Vault-Editor released als v3.14.0 (2026-06-13). Dieser Release schließt den 41-Wellen-Plan.
Außerhalb des 41-Plans benannt. Design fertig 2026-06-14 (Doc design/07-graph-overview.md, kanonisch ctx projects 019ec337, via 21-Agent-Workflow wf_4970eccf). Scope-Count-Leak gelöst über scope-partitionierte Vor-Aggregate (Migration 057 = 3 normale Tabellen graph_cluster_member/_node/_edge, keine MView; Request summiert nur scope=ANY(readScopes)); gonum-Louvain-Daemon-Job (raw_confidence-Gewicht, min-UUID-cluster_id, advisory-lock); cluster_id nie roh raus, per-Request-Ordinal. Verworfen: A3 k-Anon (fatal), A1 globale-MView, A4 per-sig (K-Explosion). W1 gebaut 2026-06-14 (Migration 057 + internal/overview/cluster.go + config.GraphOverviewConfig + scheduler.runOverviewRebuild, Determinismus 250x verifiziert). W2 gebaut 2026-06-14 (store/overview.go GraphOverview + handler/overview.go HandleOverview; enabled=false→404; per-Request-Ordinal; Integration scope-Negativ-Proben). Live deployed + enabled 2026-06-13/14: clusters=70 nodes=669 edge_rows=58 modularity=0.857 elapsed=45ms. W3 Client code-complete 2026-06-14 (lib/graph/api.ts fetchOverview + overview-map.ts buildOverviewGraph + OverviewMap.svelte + GraphPage Landkarte; svelte-check 0/0, vitest 141). Released v3.15.0 2026-06-14.
Offener Rest der Graph-Landkarte-Wellen: 1M-Bench, als optional gekennzeichnet.
Außerhalb des 41-Plans benannt, keine fixe Reihenfolge. ctx_store/update als Tools mit Confirm-Card + payload_hash, zuzüglich Migration.
Außerhalb des 41-Plans benanntes offenes Frontend-Item, keine fixe Reihenfolge.
Map-Mode für >50k Knoten; Korpus aktuell 691 — als verfrüht eingestuft.
Außerhalb des 41-Plans benannt. grep TODO(multi-tenant) war 9 Marker, jetzt 0 im Code — durch feat/multi-tenant-Arbeit ersetzt/abgelöst.
Sammlung offener operativer Arbeit aus MEMORY.md ("Offene Arbeit" Rest + "Operative Quirks"): Resilienz-Reste der Welle G (Watchdog, Aktivitäts-Agent als Logon-Task), zwei offene Code-Fixes (StoreEmbedding ohne pgx.Tx, ClassifyConfidence-Rekalibrierung), Daten-Lebenszyklus-Themen (context_access_log-Retention geparkt, Korpus-Kuration) sowie ein Sammelposten Niedrig-Prio-Punkte. Der n8n-Service bleibt bewusst erhalten und ist nicht zu entfernen.
Welle G Rest (llama.cpp-Resilienz): Gaming-Toggle erledigt (G30/v3.8.0). OFFEN: Watchdog. Infra-Referenz ctx 019eaf18.
Welle G Rest (llama.cpp-Resilienz): OFFEN neben dem Watchdog der Aktivitäts-Agent als Logon-Task. Infra-Referenz ctx 019eaf18.
1 Code-Fix (W49, ungefixt): store.StoreEmbedding ohne pgx.Tx. Der dream.chatJSON-DreamProtocol-Fix war a137ee0, done.
ClassifyConfidence-Rekalibrierung unter Blend-Scores — N01-05 + T01-Klasse (Blend hebt No-Hit knapp über ScoreThreshold → low_confidence statt none); eigene Welle, falls Confidence je als Gate dient.
context_access_log-Retention (geparkt — Review nach allen Phasen, User-Entscheidung 2026-06-13): Tabelle wächst unbegrenzt (87k rows / 18 MB, db.md:105), keine Hypertable/keine Retention. Weniger dringlich als llmlog (v3.9.0 via Body-NULLing abgedeckt). Kandidat bei 1M-Scale: Hypertable + zeitbasierte Retention analog llmlog.EvictBodies / embed_cache-Janitor.
Korpus-Kuration — veraltete Config-Blöcke (CPU-Antwort zitierte alten "aktuelles Modell"-Block).
Sammelposten Niedrig-Priorität: Session-10-Todos P2; ctx dream --dry-run; FTS OR-Matching (M018); PromoteToCanonical Unit-Tests; Shared-Scope-Migration Warnings (→ /ctx-init); go test -short im pre-commit; OLLAMA_EMBED_DIMS-Echos in test/eval/state.sh (display-only tot — Altlast aus Ollama-Ära); TestResolveProject-Mini-Fix; test.sh-exit-0-Kosmetik.
Bewusst KEINE Altlast: n8n-Service läuft bewusst weiter (override.yml, SMS-Webhook); NICHT als Altlast entfernen.