Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:34:21 +02:00
commit 7ca726caf2
659 files changed
+98171

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
# Data Flows
## Application search
1. Credential authenticeren en service-accountstatus controleren.
2. Requested spaces syntactisch valideren.
3. Effective spaces berekenen: `requested ∩ granted ∩ active ∩ policy-compatible`.
4. Query normaliseren zonder semantisch betekenisverlies.
5. Exacte identifiers detecteren en dense/sparse inputs maken.
6. Qdrantfilters volledig server-side construeren.
7. Kandidaten fusioneren via RRF, de-dupliceren en groeperen.
8. Citations uit authoritative metadata materialiseren.
9. Audit/metrics opslaan volgens privacybeleid.
## Source publication
1. Connector maakt source snapshot en cursor.
2. Content hash identificeert unchanged/new/changed/deleted.
3. Nieuwe bytes worden immutable opgeslagen.
4. Securityclassificatie en quarantine gate.
5. Parser maakt canonical document en extraction report.
6. Chunker maakt deterministische chunk-ID's.
7. Embeddingprofiel maakt vectors; index build blijft niet-actief.
8. Structurele, count- en samplevalidatie.
9. Databasepublicatie en indexalias omschakelen binnen gecoördineerde state transition.
## Deletion
Een bronverwijdering maakt de actieve versie onmiddellijk onvindbaar via authoritative statusfilter. Fysieke Qdrant- en blobverwijdering volgt asynchroon volgens retentiebeleid.
@@ -0,0 +1,31 @@
# Deployment Topology
## Development
Docker Compose levert app, worker, PostgreSQL en Qdrant. Ollama is een optioneel profiel. Alleen app publiceert een hostpoort.
## Unraid productie
```text
Nginx Proxy Manager / Authentik
|
ragcore-app:8080
|
internal Docker network
/ | worker PostgreSQL Qdrant Ollama
```
- PostgreSQL, Qdrant en Ollama publiceren geen internet- of LAN-poort via deze stack.
- Actieve PostgreSQL/Qdrant-volumes gebruiken directe poolpaden zoals `/mnt/cache/appdata/ragcore/...` of `/mnt/<pool>/...`, niet `/mnt/pool/...`.
- Back-ups en snapshots mogen naar `/mnt/pool/backups/ragcore` worden gekopieerd nadat ze consistent zijn geëxporteerd.
- Containers draaien non-root, read-only waar mogelijk, met `no-new-privileges`.
## Failure modes
| Failure | Gedrag |
|---|---|
| Ollama down | `/search` en `/context` blijven beschikbaar; `/answers` geeft provider unavailable |
| Qdrant down | API blijft live maar retrieval-readiness faalt |
| Worker down | Bestaande actieve kennis blijft zoekbaar; nieuwe jobs wachten |
| PostgreSQL down | readiness faalt; geen request dat autorisatie vereist wordt uitgevoerd |
| Blobstore tijdelijk down | search op reeds geïndexeerde tekst kan doorgaan; documentopen/download faalt expliciet |
+30
View File
@@ -0,0 +1,30 @@
# Module Boundaries
## Dependency rule
```text
api/web -> application -> domain
worker -> application -> domain
infrastructure -> application ports + domain
```
`domain` importeert geen FastAPI, SQLAlchemy, Qdrant, Docling of provider-SDK. `application` definieert protocols/ports en use cases. `infrastructure` implementeert deze ports.
## Modules
| Module | Verantwoordelijkheid | Mag niet |
|---|---|---|
| `domain` | Entiteiten, value objects, policies, state machines | I/O of frameworks gebruiken |
| `application` | Use cases, transacties, ports, authorization decisions | HTTP- of opslagdetails kennen |
| `infrastructure` | PostgreSQL, Qdrant, blobs, providers, connectors | Businessbeleid bepalen |
| `ingestion` | Pipelineactiviteiten en parsers | Versies rechtstreeks publiceren zonder gate |
| `retrieval` | Query, fusion, rerank, context, answerability | Grants vertrouwen uit requestinput |
| `generation` | Modelrouting en grounded output | Direct acties uitvoeren |
| `evaluation` | Datasets en metrics | Productiedata ongemaskeerd exporteren |
| `api` | HTTP-validatie en problem responses | Domeinlogica in routes stoppen |
| `web` | Admin en Query Lab | Rechtstreeks databasequeries uitvoeren |
| `platform` | Config, logging, telemetry, wiring | Domeinbeslissingen bevatten |
## Import enforcement
Architectuurtests inspecteren imports. Circular dependencies en verboden imports laten CI falen.
@@ -0,0 +1,56 @@
# System Architecture
## Stijl
V1 is een **modulaire monoliet** met twee procesrollen uit hetzelfde codebase/image:
- `ragcore-app`: HTTP API, beheerinterface, retrieval- en answerorchestratie;
- `ragcore-worker`: ingestion-, synchronisatie-, embedding- en onderhoudsjobs.
Externe stateful services zijn PostgreSQL en Qdrant. Ollama is optioneel en kan reeds centraal bestaan.
```text
Clients / MCP / UI
|
Reverse proxy + OIDC
|
ragcore-app ---------------- Model provider
| | \---- Qdrant (derived retrieval index)
|
PostgreSQL (control plane + jobs)
|
ragcore-worker ---- immutable blobs / parsed artifacts
```
## Waarom geen microservices
- Eén server en beperkte initiële belasting.
- Eenvoudiger transacties, upgrades, debugging en restore.
- Minder netwerk- en deploymentcontracten.
- Modulaire grenzen maken latere extractie mogelijk wanneer metingen dat rechtvaardigen.
## Requestpaden
### Search
`API -> authorization -> query analysis -> dense/sparse retrieval -> hybrid RRF-fusion -> citations -> response`
V1 voert geen reranker uit: de gefuseerde volgorde is de productieranking.
Een toekomstige optionele reranker vereist een apart, gevalideerd provider- en
evaluatiebesluit.
### Answer
`Search -> context assembly -> answerability -> provider policy -> grounded generation -> claim/citation validation -> response`
### Ingestion
`source sync -> immutable acquisition -> security gate -> parse -> normalize -> chunk -> embed -> shadow index -> validate -> publish`
## Betrouwbaarheidsgrenzen
- Internet, uploads en repositories zijn onbetrouwbare input.
- Reverse proxy/OIDC valideert mensidentiteit; RAGCore autoriseert resources.
- Service tokens identificeren applications maar geven alleen expliciete grants.
- Qdrant is niet publiek en vertrouwt alleen RAGCore.
- Modelproviders ontvangen uitsluitend policy-toegestane context.
+34
View File
@@ -0,0 +1,34 @@
# Authorization Model
## Principals
- Human user via OIDC session.
- Application service account via bearer token.
- Internal worker identity.
## Rollen
`platform_admin`, `workspace_admin`, `space_manager`, `editor`, `viewer`, `auditor`.
## Application scopes
Voorbeelden: `search`, `context`, `answer`, `documents:read`, `citations:read`, `feedback:write`, `sources:sync`.
## Effective-space algorithm
```text
effective = requested spaces
∩ explicit grants for principal/application
∩ active spaces in the same workspace
∩ spaces compatible with operation and provider policy
```
Een lege requested set betekent alleen de configured defaults van de application, nooit alle grants. Filters worden door de server samengesteld en kunnen niet via raw Qdrantfilterinput worden overschreven.
## Defence in depth
- Use-case authorization vóór repository calls.
- Workspace-gebonden databasequeries en waar passend PostgreSQL RLS.
- Verplichte Qdrant payloadfilters.
- Citation/documentread voert opnieuw resourceauthorization uit.
- Securitytests proberen IDOR, filterinjectie, inactive records en cross-space leakage.
+47
View File
@@ -0,0 +1,47 @@
# Domain Model
## Hoofdaggregaten
### Workspace
Beveiligings- en administratieve grens. Bezit applications, knowledge spaces en policies.
### Application
Project/client dat RAGCore gebruikt. Heeft service accounts, grants, quota en standaardprofielen.
### KnowledgeSpace
Logische kennisgrens met databeleid, retentie, trustvereisten en toegangsgrants.
### Source
Read-only oorsprong en synchronisatieconfiguratie. Voorbeelden: upload, folder, Git of website.
### Document
Stabiele logische identiteit van content door versies heen.
### DocumentVersion
Immutable snapshot met source fingerprint, blob, canonical artifact, status en timestamps.
### IndexBuild
Afgeleide indexering van een set documentversies onder exact één embedding/indexprofiel.
### RetrievalRun
Auditbare uitvoering met caller, effective spaces, profiel, kandidaten en timings.
### Citation
Immutable verwijzing naar documentversion, location, excerptchecksum en source URI.
### Job
Idempotente achtergrondopdracht met type, payloadversie, lease, attempts en events.
## Identiteiten
- Extern zichtbare objecten: UUIDv7.
- Deterministische chunks/citations: UUIDv5 op immutable inputs.
- Geen incrementele database-ID in publieke contracten.
## Domeinregels
- Exact één actieve documentversion per document.
- Een version wordt nooit inhoudelijk aangepast.
- Een indexbuild mengt nooit embeddingmodel/dimensie/profielversies.
- Een inactive/deleted/quarantined version is nooit actief zoekbaar.
- Een citation blijft naar de gebruikte version verwijzen na latere updates.
+43
View File
@@ -0,0 +1,43 @@
# State Machines
## Document version
```text
acquired -> security_checked -> parsed -> normalized -> chunked
-> embedded -> indexed -> validated -> published
```
Terminal/alternatief:
- `quarantined`: securitygate faalde of vereist review;
- `failed`: technische fout met retryclassificatie;
- `superseded`: was gepubliceerd maar is vervangen;
- `retired`: bron verwijderd/retentie verlopen.
Alle transities zijn expliciet, geaudit en idempotent. `published` vereist dat alle voorafgaande artifacts duurzaam bestaan.
## Job
```text
pending -> ready -> leased -> running -> succeeded
| \-> retry_wait -> ready
\-> expired -> ready
running -> failed_terminal
running -> cancelled
```
## Source sync
```text
idle -> scheduled -> scanning -> applying -> validating -> completed
\-> failed
```
## Index build
```text
created -> building -> validating -> ready -> active -> retired
\-> failed
```
Aliasactivatie mag alleen van `ready` naar `active`, met rollback naar vorige actieve build.
+30
View File
@@ -0,0 +1,30 @@
# Blob and Artifact Layout
## Immutable blobs
```text
blobs/sha256/ab/cd/<full-sha256>.blob
```
Metadata staat in PostgreSQL: digest, size, MIME, oorspronkelijke naam, acquisition en security status. Identieke bytes worden fysiek gededupliceerd.
## Parsed artifacts
```text
parsed/<workspace>/<document-version>/
canonical.json.zst
canonical.md
structure.json
extraction-report.json
assets/<digest>.<ext>
```
Canonical content is immutable en versieert parsernaam, parserversie, normalizerprofiel en source fingerprint.
## Temp
`tmp/<job-id>/` is alleen voor begrensde verwerking en wordt na jobfinalisatie of expiry opgeruimd. Tempfiles mogen nooit de enige kopie van evidence zijn.
## Atomicity
Schrijf naar uniek tijdelijk pad, fsync waar vereist, verifieer hash, rename atomair naar content-addressed doel en registreer pas daarna metadata.
+43
View File
@@ -0,0 +1,43 @@
# Database Schema
## Schema's
- `identity`: users, service_accounts, credentials, sessions.
- `control`: workspaces, applications, spaces, grants, policies, profiles.
- `content`: sources, documents, versions, blobs, artifacts, chunks, citations.
- `jobs`: jobs, attempts, events, schedules.
- `query`: retrieval_runs, candidates, answers, feedback.
- `evaluation`: suites, cases, runs, results.
- `audit`: append-only audit_events.
## Belangrijke constraints
- Unieke actieve documentversion per document via partial unique index.
- Unieke `(workspace_id, slug)` voor applications en spaces.
- Tokenhash uniek; plaintext token nooit opgeslagen.
- Chunk uniqueness op `(document_version_id, structure_path, content_hash, chunk_profile_version)`.
- Foreign keys gebruiken restrict/cascade bewust; contentaudit wordt niet stil verwijderd.
- Alle timestamps `timestamptz` in UTC.
- `jobs.jobs.workspace_id` is de ownership-kolom waarop iedere operator-query
filtert, met indexen `(workspace_id, updated_at)` en --- sinds migratie 0019,
voor het gefilterde operatoroverzicht --- `(workspace_id, status, updated_at)`.
`operation` staat er bewust niet in: die kolom heeft twee waarden in deze
runtime en versmalt niets wat de eerste twee kolommen niet al versmallen.
Nullable *uitsluitend* voor
legacy rijen van vóór migratie 0018; nieuwe jobs worden door de repository
geweigerd zonder eigenaar. `NULL` betekent "geen eigenaar vast te stellen" en
faalt gesloten voor workspace-scoped principals --- nooit "van iedereen".
- `content.source_items.last_failure_job_id` bewaart de child ingest job die als
laatste faalde. Een terminaal gefaald item met ongewijzigde fingerprint wordt
bewust niet opnieuw ge-enqueued; herstel loopt via retry van díe job.
## Transaction boundaries
- Nieuwe versie en jobregistratie in één transaction.
- Publication update vergrendelt document en valideert indexbuildstatus.
- Jobclaim gebruikt `FOR UPDATE SKIP LOCKED` met lease expiry.
- Qdrant is geen distributed transaction participant; outbox/reconciliation herstelt divergentie.
## Migrations
Alle wijzigingen via Alembic, met upgrade, waar realistisch downgrade, data-backfillplan en migratietest tegen lege én representatieve database.
+48
View File
@@ -0,0 +1,48 @@
# Qdrant Index Contract
## M6 embedding-space identity
New ModelForge shadow collections and payloads carry the exact `embedding_space_id`; dimensions
alone never establish compatibility. The current production collection remains legacy-observed and
unchanged. Evaluation-only shadow reads name a server-owned profile and never mutate the alias.
## Collecties
Collecties worden per indexprofiel/embeddingruimte gemaakt, niet per project:
```text
rag_dense_<profile>_<version>
rag_code_<profile>_<version>
```
Een alias verwijst naar de actieve build. Verschillende dimensies of modeldigests worden nooit gemengd.
## Named vectors
- `dense`: semantische embedding;
- `sparse`: lexicale/tokenrepresentatie;
- latere multivector alleen via nieuw profiel en evaluatie.
## Verplichte payload
`workspace_id`, `space_id`, `document_id`, `document_version_id`, `chunk_id`, `parent_chunk_id`, `source_id`, `status`, `content_type`, `language`, `trust_level`, location, profile versions en tekst/excerpt.
## Payload indexes
Minimaal: workspace, space, document, version, status, content_type, language en trust_level. Voeg geen index toe zonder gemeten filtergebruik.
## Query invariant
Iedere query bevat server-side workspace/space/statusfilters. Raw clientfilters worden nooit rechtstreeks doorgestuurd; toegestane filters worden gecompileerd uit typed requestvelden.
## Rebuild
Qdrant is afgeleid. Rebuild maakt een shadow collection, controleert counts/samples/golden queries, activeert alias en houdt de vorige build voor rollback.
Elke build-, rollback- en reconcile-aanroep wordt per alias geserialiseerd door een PostgreSQL-backed lease lock (`indexes.index_build_operations`), niet door een proceslokale lock, zodat meerdere app-/workerreplicas nooit dezelfde aliasmigratie gelijktijdig uitvoeren. Acquisitie is begrensd (timeout + backoff) en een gecrashte houder laat geen permanent vastgelopen lock achter: een verlopen lease wordt bij de volgende poging gereclaimed en als `needs_repair` gemarkeerd zodat de eerdere afbraak zichtbaar blijft.
Elke stap van een operatie wordt in dezelfde rij gelogd (saga log) zodat een gedeeltelijk mislukte build gecompenseerd kan worden: vóór activatie wordt de shadow collection verwijderd; na (een poging tot) activatie wordt de alias geforceerd teruggezet naar de vorige collectie en geverifieerd. Een compensatie die zelf niet bevestigd kan worden resulteert in `needs_repair` en een `IndexRepairError`, nooit in een stilzwijgend geslaagde melding.
Malformed of legacy points blokkeren reconciliatie niet: ze worden gedetecteerd, uitgesloten van gebruik en vervolgens hersteld (deterministisch herbouwd vanuit de authoritative bron) wanneer hun ID bij een verwachte chunk hoort, of gequarantained (verwijderd op exact opgeslagen ID) wanneer dat niet zo is.
Batchquota voor Qdrant-schrijfoperaties tellen de werkelijke UTF-8-geserialiseerde bytes van het volledige payload, de sparse-vectoren en de fingerprintvelden, niet alleen tekst/excerpt-lengte; een build kent daarnaast een cumulatieve bytequota over alle batches heen.
+21
View File
@@ -0,0 +1,21 @@
# Versioning and Retention
## Version detection
Connectorfingerprint + cryptografische contenthash bepalen `unchanged`, `new`, `changed` of `deleted_at_source`.
## Publication
De vorige actieve version blijft serving totdat de nieuwe `validated` is. Publication is een expliciete state transition. Bij mislukking blijft de vorige version actief.
## Retention
Per space instelbaar:
- actieve versions onbeperkt zolang document bestaat;
- superseded versions standaard 90 dagen;
- source-deleted versions standaard 30 dagen;
- audit/citationmetadata langer volgens policy;
- quarantined bytes apart en beperkt.
Citations kunnen een retentiehold op gebruikte documentversions plaatsen. Fysieke purge vereist dat geen geldige citation, legal hold of active index ernaar verwijst.
+51
View File
@@ -0,0 +1,51 @@
# Connectors
## V1
### Upload
Menselijke of API-upload. Vereist filename, content length en target space. Upload is nooit automatisch trusted.
### Read-only folder
Scant een expliciet gemounte root. Path traversal en symlinks buiten root worden geweigerd. De connector schrijft nooit naar de bronmap.
### Git repository
Leest een pinned commit/ref uit lokale checkout of toegestane remote. Registreert repository, commit, path en language. Secrets/binaries worden uitgesloten volgens policy.
### Website/sitemap
Optioneel late V1. Alleen expliciet toegestane domains, robots/politeness, size/time limits en SSRF-beveiligde fetcher.
## Connector contract
`scan(cursor) -> items + next_cursor`; `acquire(item) -> byte stream + metadata`; `fingerprint(item)`. Connectors bevatten geen parsing- of publicationlogica.
## Deletion semantics
Een volledige succesvolle scan mag ontbrekende items als source-deleted markeren. Een partiële/mislukte scan mag nooit massaal verwijderen.
## Source Registry en synchronisatie (1.2.0)
Iedere bron is een persistent record in `content.sources` (id, workspace, knowledge
space, naam, type, status, secret-vrije configuratie, sync-state, laatste sync,
laatste succesvolle sync, laatste revisie). Iedere ingest registreert zijn bron
bij eerste gebruik; ad-hoc uploads zonder `source_id` horen bij de ingebouwde
*Uploads*-bron van hun space (`upload_source_id(space_id)`, deterministisch).
De `source_id` is **één identiteit** door de hele keten: document → chunk →
Qdrant-payload → provenance → citation (`ragcore://source/<id>`) → `source_ids`-filter
→ Control Plane. De `source_key`/`external_id`-dedupsleutel van documenten blijft
ongewijzigd (sync-clients via de upload-API merken niets).
Synchronisatie (`SourceSyncService`, worker-job `sync_source`, alleen folder/Git):
```
discover (alle pagina's) → vergelijk fingerprint met registry-items
→ nieuw/gewijzigd: acquire → blob → ingest_upload-job (zelfde pipeline als uploads)
→ ongewijzigd: niets
→ verdwenen (alleen bij complete scan): document retiren
→ persist run: discovered / new / changed / unchanged / removed / failed
```
Git-items krijgen het commit als `source_revision` op de documentversie; het
stabiele document-id van een item is zijn pad (`locator`), niet `<commit>:<pad>`.
Er is geen scheduler: een sync start vanuit de Control Plane ("Sync now") of door
een `sync_source`-job te enqueuen. Website-bronnen zijn nog niet aan te maken.
+21
View File
@@ -0,0 +1,21 @@
# PostgreSQL Job System
## Waarom PostgreSQL
V1 heeft beperkte workerconcurrency. PostgreSQL voorkomt een extra stateful dependency en maakt jobs, attempts, events en businessstate gezamenlijk inspecteerbaar.
## Claim
Workers selecteren ready jobs met `FOR UPDATE SKIP LOCKED`, schrijven `leased_by`, `lease_until` en increment attempt atomair. Een heartbeat verlengt alleen een lease die nog eigendom is van die worker.
## Retry
Exponential backoff met jitter, typed retryable/terminal errors en maximum attempts per jobtype. Poison jobs gaan terminal en blokkeren publication niet onzichtbaar.
## Idempotency
Unieke job key op operation + immutable target + profileversion. Handlers moeten veilig opnieuw uitvoerbaar zijn na crash tussen externe side effect en databasecommit.
## Observability
Iedere transition schrijft een compact event met trace ID, duur, foutcode en sanitized details. Grote parseroutput hoort in artifacts, niet in job rows.
+28
View File
@@ -0,0 +1,28 @@
# Parsing and Chunking
## Parserrouter
- Plain text/Markdown/JSON/YAML/TOML/logs: lichte veilige parser.
- Broncode: language-aware symbol parser waar beschikbaar, veilige fallback per file/section.
- PDF/DOCX/PPTX/XLSX/HTML: Docling-adapter.
- OCR: alleen bij ontbrekende of aantoonbaar defecte text layer. **Shipped gedrag (1.2.0):** het gelockte docling-slim image bevat geen OCR-engine (`easyocr`); een PDF zonder tekstlaag wordt dus niet geOCR'd maar eindigt als `failed` (`parse_failed`/`no_indexable_content`) en is zo zichtbaar in Documents/Jobs. Born-digital PDF/DOCX/PPTX/XLSX/HTML worden wel geparsed, met paginanummers.
- Executables, macro's en scripts worden nooit uitgevoerd.
## Canonical blocks
Heading, paragraph, list, table, code, formula, caption, page break en asset reference met stable structure path en source location.
## Chunkprofielen
- `prose-v1`: 600–900 targettokens, max 1200, sectiegrens respecteren.
- `runbook-v1`: probleem/symptomen/prerequisites/stappen/validatie/rollback bijeenhouden.
- `code-v1`: module/class/function/method/config resource; parentcontext voor symbolscope.
- `table-v1`: headers aan row groups toevoegen en volledige tabel als parent bewaren.
## Deterministische identiteit
`UUIDv5(document_version_id + structure_path + content_hash + chunk_profile_version)`.
## Parent-child
Kleine childchunks dienen retrievalprecisie; ruimere parentchunks dienen context. Parentexpansie mag contextbudget en ACL nooit omzeilen.
+24
View File
@@ -0,0 +1,24 @@
# Ingestion Pipeline
## Stages
1. **Discover:** connector levert typed source items en cursor.
2. **Acquire:** begrensde read/download met checksum en MIME-detectie.
3. **Classify:** allowlist, size, extension/MIME mismatch en threat checks.
4. **Persist blob:** content-addressed en immutable.
5. **Parse:** gespecialiseerde parser maakt canonical document.
6. **Normalize:** encoding, whitespace, metadata en structure paths.
7. **Chunk:** documenttypebewuste deterministische chunks.
8. **Embed:** profile-pinned dense/sparse representations.
9. **Index:** upsert naar shadow/target build.
10. **Validate:** counts, referential checks, sample retrieval en policychecks.
11. **Publish:** active version en index visibility atomair coördineren.
12. **Reconcile:** verwijderingen en orphan cleanup.
## Idempotency
Iedere stage gebruikt immutable input-ID + stage/profileversion als idempotency key. Retry mag geen dubbele blobs, versions, chunks of points creëren.
## Resource limits
Maximale file size, archive depth, uncompressed ratio, parser timeout, page count, token count en concurrentie zijn configurabel per source/policy.
+23
View File
@@ -0,0 +1,23 @@
# Answerability
## Statussen
- `answerable`: voldoende consistente evidence dekt de vraag.
- `partially_answerable`: slechts een aantoonbaar deel is gedekt.
- `not_answerable`: onvoldoende of te zwakke evidence.
- `conflicting_evidence`: relevante bronnen spreken elkaar materieel tegen.
## Signalen
Fusievolgorde, documentdiversiteit, dekking van queryfacetten, trust level, freshness, exacte identifier match, source independence en conflictregels. Een retrievalscore wordt nooit als kans of menselijk confidencepercentage gepresenteerd.
## Gedrag
- `answerable`: grounded generatie toegestaan volgens databeleid.
- `partially_answerable`: beantwoorde deel duidelijk scheiden van ontbrekend deel.
- `not_answerable`: geen inhoudelijke invulling; toon wat wel en niet gevonden is.
- `conflicting_evidence`: bronnen en verschillen presenteren; geen willekeurige keuze.
## Kalibratie
Drempels worden uitsluitend aangepast op versioned evaluation datasets. Iedere wijziging rapporteert false-answerable en false-not-answerable regressies.
+39
View File
@@ -0,0 +1,39 @@
# Citations
## Eigenschappen
Een citation is immutable en verwijst naar:
- workspace/space;
- document en documentversion;
- chunk en parentchunk;
- pagina, sectie of regelbereik;
- excerpt en excerptchecksum;
- source URI en acquisition timestamp;
- retrieval run en gebruikte index/profileversie.
## ID
Deterministische UUIDv5 of stabiele opaque ID op immutable citationinputs. Een latere documentversion verandert een bestaande citation niet.
## Presentatie (1.2.0)
Naast de machine-velden levert iedere citation in `/v1/search`, `/v1/context` en
`/v1/answers` leesbare provenance, opgelost uit het Source Registry:
`source_id`, `source_name`, `source_kind`, `locator` (item binnen de bron),
`revision` (bijv. Git-commit van de geciteerde versie) en `display`, bijvoorbeeld
`Network Design.pdf · p. 18 · Firewall Segmentation` of
`infrastructure/runbooks/backup.md · commit 3fc821a · lines 118-143`.
`source_uri` (`ragcore://source/<source_id>`) blijft de stabiele interne locator.
Een citation is herleidbaar tot bron → item (`locator`) → versie
(`document_version_id` + `revision`) → chunk (`id`, `excerpt_sha256`) → locatie
(sectie/pagina/regels). Autorisatie verandert hierdoor niet: de presentatie
resolvet alleen documenten die de caller al geautoriseerd terugkreeg.
## API
Antwoorden refereren citation IDs in tekst/structured claims. `GET /v1/citations/{id}` autoriseert opnieuw en levert alleen content die de caller nu mag lezen. Audit kan historische metadata bewaren terwijl gevoelige excerptretentie policy volgt.
## Claimondersteuning
Generatie-output moet claims aan citations koppelen. Niet-ondersteunde feitelijke claims worden verwijderd, opnieuw gegenereerd of leiden tot een expliciete failure afhankelijk van profiel.
+27
View File
@@ -0,0 +1,27 @@
# Model Registry
## Profieltypes
- Embedding profile
- Sparse encoder profile
- Reranker profile (toekomstige optionele capability; niet actief in V1)
- Generation profile
## Verplichte metadata
Provider, model identifier, immutable digest/revision, dimensie, contextlimiet, inputprefixes, normalisatie, tokenizer, databeleid, timeout, concurrency en activation status.
## Local-first policy
Knowledge spaces kiezen `local_only`, `local_preferred`, `cloud_allowed`, `retrieval_only` of `no_generation`. De providerrouter kiest nooit een ruimere route dan het strengste effectieve spacebeleid.
## Modelwijziging
1. Registreer immutable nieuw profiel.
2. Bouw shadow index waar embeddings veranderen.
3. Draai retrieval-, security- en latencyevaluaties.
4. Vergelijk tegen actieve baseline.
5. Activeer via alias/configversie.
6. Bewaar vorige versie voor rollback.
Geen `latest` tags of automatische upgrades in productie.
+39
View File
@@ -0,0 +1,39 @@
# Retrieval Pipeline
## Stappen
1. Authenticeer caller en resolveer application/user.
2. Bereken effective spaces en toegestane trust/data policies.
3. Normaliseer vraag; bewaar originele query.
4. Detecteer identifiers, paden, versies, foutcodes en taal.
5. Maak dense queryembedding en sparse queryrepresentatie.
6. Voer parallel dense en sparse retrieval uit met verplichte payloadfilters.
7. Fuseer rankings via Reciprocal Rank Fusion.
8. Verwijder duplicate/near-duplicate chunks en beperk per document.
9. Selecteer de hoogste gefuseerde kandidaten.
10. Expandeer geselecteerde children naar toegestane parentcontext.
11. Stel context samen binnen token- en diversiteitsbudget.
12. Bepaal answerability/conflictstatus.
13. Materialiseer immutable citations en emit timings/metrics.
## Standaardbudgetten V1
- dense candidates: 50;
- sparse candidates: 50;
- fused candidates: 40;
- geselecteerde fused results: 10;
- maximaal 3 children per document;
- minimaal 2 documenten waar beschikbaar;
- contextbudget configureerbaar per generationprofiel.
Dit zijn beginwaarden, geen harde kwaliteitswaarheid. Evaluation bepaalt wijzigingen.
## Degradatie
- Sparse unavailable: dense-only met expliciete degraded metadata.
- Generator unavailable: search/context blijven werken.
- Geen voldoende evidence: antwoordstatus `not_answerable`; geen geforceerde generatie.
V1 heeft geen actieve reranker: de fused ranking is de normale productieroute,
niet een degradatiefallback. Een toekomstige reranker krijgt pas een profiel en
provider nadat die end-to-end is gevalideerd.
+25
View File
@@ -0,0 +1,25 @@
# Retrieval Profiles
## `exact-v1`
Voor foutcodes, bestandsnamen, hashes en configuratiewaarden. Hoge sparseweging, geen queryrewrite, sterke identifierboost.
## `semantic-v1`
Voor conceptuele vragen. Dense dominant, queryexpansie alleen deterministisch/geconfigureerd, sterke documentdiversiteit.
## `hybrid-v1`
Standaard voor technische kennis. Dense + sparse, RRF, optionele local reranker en parentexpansie.
## `code-v1`
Code-embedding/index, filters op repository/commit/path/language/symbol en groepering per module/symbol.
## `strict-evidence-v1`
Voor antwoorden met hogere onderbouwingseisen. Minimum evidence coverage, minstens twee onafhankelijke bronnen wanneer relevant, verplichte citations en weigering bij conflict of onvoldoende dekking.
## Profielimmutability
Een gepubliceerd profiel is immutable. Wijziging maakt een nieuwe versie. RetrievalRun registreert exacte profile ID, modeldigests, indexalias en parameters.
+31
View File
@@ -0,0 +1,31 @@
# API Design
## Principes
- Prefix `/v1` voor productresources; health blijft onversioned.
- JSON en RFC 9457-style problem details voor fouten.
- Opaque UUID identifiers.
- Idempotency-Key vereist voor create/upload/sync requests die clients kunnen herhalen.
- Cursorpagination, stabiele sortering en begrensde page sizes.
- Timestamps in RFC 3339 UTC.
- Geen raw SQL, Qdrantfilter, providerprompt of onbeperkte modelparameters in publieke requests.
## Kernresources
- workspaces/applications/service accounts;
- knowledge spaces en grants;
- sources/sync runs;
- documents/versions;
- jobs;
- search/context/answers;
- citations/feedback;
- profiles/evaluation runs;
- audit events.
## Async operations
Upload, sync, reindex en evaluatie leveren `202 Accepted` met job ID en status URL. Jobstatus bevat typed state, progress en sanitized failure.
## Streaming
Answers kunnen Server-Sent Events gebruiken met events `metadata`, `delta`, `citation`, `complete`, `error`. Citations en answerabilitymetadata worden vóór of uiterlijk met complete geleverd.
+23
View File
@@ -0,0 +1,23 @@
# Error Model
Alle API-fouten gebruiken `application/problem+json`:
```json
{
"type": "https://ragcore.example.test/problems/space-forbidden",
"title": "Knowledge space access denied",
"status": 403,
"code": "SPACE_FORBIDDEN",
"detail": "The caller is not allowed to use one or more requested spaces.",
"trace_id": "..."
}
```
## Regels
- `code` is stabiel en machineleesbaar.
- `detail` bevat geen secrets, SQL, filesystemroots of providerkeys.
- Validation errors bevatten veldpaden en veilige redenen.
- Provider/downstream errors worden gemapt naar RAGCore-codes.
- Retryable errors geven optioneel `retry_after_seconds`.
- 404 mag voor gevoelige resources worden gebruikt waar 403 bestaan zou lekken.
+51
View File
@@ -0,0 +1,51 @@
# SDK Requirements
V1 levert handgeschreven Python (`sdk/python`, pakket `ragcore-sdk`) en
TypeScript (`sdk/typescript`, pakket `@ragcore/sdk`) clients tegen de
pinned OpenAPI-specificatie, elk scoped tot de drie echt geïmplementeerde
retrieval-endpoints (`search`/`context`/`answers`) -- `/v1/citations/{id}`
en `/v1/jobs/{id}` staan wel in `docs/contracts/openapi.yaml`, maar zijn
nooit als echte route geïmplementeerd, dus geen van beide SDK's doet
alsof ze bestaan.
Beide SDK's:
- accepteren het bearer token veilig zonder het ooit te loggen (Python:
nooit buiten de request-headers; TypeScript: een echt privé class field
`#token`, niet TypeScript's compile-time-only `private`, dus niet
zichtbaar via `console.log`/spread/`JSON.stringify`);
- ondersteunen timeouts (beide) en cancellation via de standaard taal-
mechanismen (Python: normale `asyncio`-cancellation; TypeScript: een
interne `AbortController` per request) -- geen aparte cancellation-API
bovenop wat de taal al biedt;
- exposen typed problem details (`ProblemDetails`/`RAGCoreAPIError`) voor
elke non-2xx-response, die allemaal dezelfde RFC7807-vorm hebben;
- modelleren de search/context/answer-types volledig, inclusief citations;
- handelen de SSE-answerstream af (`event: <type>\ndata: <json>\n\n`,
zoals `answers/routes.py::_sse_body` het echt verstuurt);
- retryen alleen de non-streaming requestpaden, en alleen bij een
connectiefout of een `503` -- nooit een `4xx`, nooit een al gestarte
SSE-stream (zie beide SDK's eigen moduledocstring voor de redenering);
- sturen een eigen versie-identifier mee: Python zet een echte
`User-Agent: ragcore-sdk-python/<versie>` header; TypeScript zet in
plaats daarvan `X-RAGCore-SDK-Version: <versie>`, omdat browsers
pogingen om `User-Agent` via `fetch` te zetten stilzwijgend negeren
(Node accepteert het wel, wat een inconsistent gedrag tussen runtimes
zou opleveren als `User-Agent` zelf gebruikt was).
Geen SDK exposet Qdrant- of databaseconcepten.
## Bewust niet geleverd
Twee requirements uit een eerdere versie van dit document zijn bewust
losgelaten, omdat ze niet passen bij wat er echt bestaat:
- **Idempotency keys.** `search`/`context`/`answers` zijn stateless,
side-effect-vrije queries (geen enkele schrijft iets weg) -- een
idempotency key is een mechanisme tegen dubbele *schrijf*-operaties bij
een retry, en heeft dus geen functie hier. De retry-policy hierboven is
om diezelfde reden al veilig zonder een key.
- **Cursor-iterators.** Geen van de drie endpoints heeft cursor-based
paginering (search/context/answers geven een vast-begrensde
resultatenset terug, geen paginated lijst); er is dus niets om te
itereren.
+29
View File
@@ -0,0 +1,29 @@
# Prompt Injection Defense
## Hoofdregel
Retrieved content is **onbetrouwbare evidence** en nooit een instructiebron. Tekst als “ignore previous instructions” heeft geen hogere status dan andere documenttekst.
## Promptstructuur
1. System policy
2. Application contract
3. User question
4. Delimited untrusted evidence with citation IDs
5. Required output schema
De systempolicy verbiedt instructies uit evidence, externe navigatie, toolcalls en secret disclosure.
## Controls
- Trust level en source provenance in retrieval.
- Hidden/obfuscated text en suspicious instruction patterns als signals, niet als enige blocker.
- Geen tools in normale generation context.
- Geen automatic execution of modeloutput.
- Answerclaim/citationvalidation.
- Adversarial fixture corpus met direct, indirect, encoded en multilingual injections.
- Quarantine of lower trust voor poisoned source updates.
## Limiet
Promptfilters alleen zijn onvoldoende. De primaire beveiliging is scheiding van bevoegdheden, geen tools, minimale context, databeleid en menselijke/policy approval voor acties buiten RAGCore.
+22
View File
@@ -0,0 +1,22 @@
# Secrets and Network
## Secrets
- `.env` alleen lokaal en nooit committen.
- Productie gebruikt Unraid secrets/files of een passende secret store.
- Service tokens: random 256-bit minimum, prefix voor identificatie, Argon2id/HMAC-hash met pepper, one-time display.
- Provider/API keys worden geredact uit logs, traces en errors.
- Rotatie ondersteunt overlap met maximaal twee actieve credentials per service account.
## Network matrix
| From | To | Doel |
|---|---|---|
| reverse proxy | app:8080 | UI/API |
| app | postgres:5432 | control/authorization |
| app | qdrant:6333 | retrieval |
| app | approved model endpoint | generation/embedding waar nodig |
| worker | postgres/qdrant/blob paths | jobs/ingestion |
| worker | allowlisted source endpoints | connector acquisition |
Geen andere standaardflows. Productie egress wordt waar praktisch op domains/IP-ranges beperkt.
+94
View File
@@ -0,0 +1,94 @@
# Security Controls
## Identity
OIDC voor mensen; gehashte, roteerbare service tokens met scopes en expiry voor applications. Tokens worden slechts eenmaal getoond.
## Authorization
Deny by default, explicit grants, effective-space intersection, use-case policies, database scoping, verplichte Qdrantfilters en resource reauthorization voor citations/downloads.
## Operations isolation
Iedere workspace-scoped job draagt expliciete ownership: `jobs.jobs.workspace_id`
is een echte kolom (migratie 0018), geen veld in `payload`. Elke lijst, teller,
detailpagina en mutatie filtert daarop in de query zelf --- nooit op rijen die al
opgehaald zijn.
- `PLATFORM_ADMIN` krijgt de expliciete globale scope (`workspace_id=None`) en
ziet dat ook op het scherm staan (`Scope: All workspaces`).
- `WORKSPACE_ADMIN` en `AUDITOR` zijn gebonden aan `principal.workspace_id`, voor
lijst, statustellers, filters, source-namen, detail en retry. `AUDITOR` is
read-only: retry vereist een rol uit `_RETRY_ROLES`.
- Een job zonder ownership (`workspace_id IS NULL`, alleen mogelijk voor rijen van
vóór 0018 waarvan de eigenaar niet betrouwbaar vast te stellen was) faalt
gesloten: onzichtbaar en onmuteerbaar voor iedere workspace-scoped principal,
uitsluitend inspecteerbaar door een platform admin. Er is nergens een
`payload.get("workspace_id") or principal.workspace_id`-fallback; die kende een
ownerloze job toe aan wie er toevallig naar keek.
- Tellers zijn ook tenantdata. Dashboard- en System Health-queuecijfers gebruiken
dezelfde scope als de Jobs-lijst.
- Ook de operatorfilters (status, type) en de paginering zijn predicaten van
diezelfde query (`JobListFilter` --- migratie 0019 voegt de bijbehorende index
toe). Er wordt dus geen bredere set opgehaald dan getoond wordt: elke rij die
dit proces binnenkomt is er één die deze principal mag zien. De vijf tellers
boven de tabel blijven bewust een scope-brede samenvatting van de wachtrij en
het scherm zegt dat er ook bij.
- Retry dwingt ownership af in het `UPDATE` zelf, niet in een voorafgaande read:
een cross-tenant mutatie die alleen onder concurrency lukt is nog steeds een
cross-tenant mutatie. Een geweigerde toegang of retry wordt vastgelegd als
`SECURITY_FAILURE` met actor, workspace, actie, doel en reden --- zonder payload.
Nieuwe jobs kunnen niet ownerloos ontstaan: `JobService.enqueue` eist
`workspace_id` en `PostgresJobRepository.enqueue` weigert een `Job` zonder.
## Filesystem-backed sources
Een Folder-source, en de lokale checkout root van een Git-source, geven de worker
een leesprimitief op de host: alles onder dat pad wordt vindbare content. Dat is
iets anders dan content beheren, dus het vereist `FILESYSTEM_SOURCE_MANAGE`
*bovenop* `SOURCE_MANAGE`, bij zowel aanmaken als bewerken (bewerken is precies
hoe een goedgekeurde source op een andere directory gericht wordt).
Gekozen model: **alleen `PLATFORM_ADMIN`**. `RAGCORE_SOURCE_ROOTS` is één
deployment-brede lijst zonder per-workspace mapping, dus een pad binnen de
allowlist kan prima het corpus van een andere tenant zijn; een workspace admin met
dit recht zou workspace B kunnen lezen door er een source op te richten. Zolang
roots niet per workspace geconfigureerd zijn, is het kiezen van een serverpad een
deploymentbeslissing en hoort die bij de rol die de deployment bezit. Per-workspace
roots (het sterkere self-service model) blijven mogelijk als self-service
filesystem sources later echt nodig blijken.
Alleen de gevoelige kinds zijn beperkt: `SOURCE_MANAGE` blijft ongewijzigd voor
Editors en Space Managers, en upload-sources worden er niet moeilijker door.
Autorisatie en sandbox zijn twee onafhankelijke poorten, allebei altijd vereist:
- `RAGCORE_SOURCE_ROOTS` blijft lexicaal afgedwongen bij opslaan, en volledig
geresolved (symlinks gevolgd) in worker en connector vlak voor iedere read. Een
pad buiten elke root wordt geweigerd --- ook voor een platform admin. Zonder
geconfigureerde roots kan geen enkele filesystem-source bestaan.
- De source mounts horen read-only te zijn. Dat is een security-invariant, niet
alleen een aanbeveling: het is wat het resterende TOCTOU-venster tussen
padvalidatie, `stat` en `open` onschadelijk maakt. Zijn de directories muteerbaar
door niet-vertrouwde actoren, dan is die aanname geschonden.
## Network
Alleen app achter reverse proxy. PostgreSQL/Qdrant/worker op internal network. Geen debug/adminpoorten publiek. Egress voor connector/modelworkers zo beperkt mogelijk.
## Content
MIME sniffing, allowlists, size/time/depth limits, archive-bombdetectie, quarantine, sandboxing waar haalbaar, geen macro/scriptuitvoering en read-only source mounts.
## Secrets
Environment/secrets mount, geen plaintext in database/logs/evidence, redaction processor, onafhankelijke token pepper en rotatieprocedure.
## Abuse prevention
Rate limits per principal/application, request/body/token/context limits, bounded concurrency, job quotas en circuit breakers.
## Audit
Append-only events voor auth, grants, source changes, publication, modelroute, adminacties en security failures. Querycontentlogbeleid is per space configureerbaar.
+37
View File
@@ -0,0 +1,37 @@
# Threat Model
## ModelForge M6 boundary
The ModelForge credential is a mode-0600 read-only mounted file, not environment content. Adapter
requests contain transient text but logs/metrics contain only provider name, correlation and timing.
The adapter rejects cloud hosts and has no cloud fallback. Public retrieval cannot select the
shadow collection; only the operator command may target it.
## Assets
Documentbytes, parsed content, embeddings, queries, answers, credentials, grants, modelroutes, auditlogs en hersteldata.
## Trust boundaries
Internet/LAN client -> reverse proxy -> RAGCore app -> internal services; source connector -> ingestion; RAGCore -> modelprovider; operator -> backupmedia.
## Belangrijkste dreigingen
1. Cross-workspace/space data leakage, inclusief operationele metadata: joblijsten,
statustellers, source-namen en foreign job retry (zie Operations isolation in
`SECURITY_CONTROLS.md`).
2. Gestolen service token of sessie.
3. Prompt injection in documenten.
4. Data poisoning of kwaadaardige bronupdates.
5. Path traversal, SSRF, archive bomb en parser exploit.
6. Onbedoeld versturen van local-only data naar cloudprovider.
7. Qdrant/DB exposure of filterbypass.
8. Citation IDOR en historical-content leakage.
9. Job duplication/race die halfgepubliceerde index veroorzaakt.
10. Backup met secrets of onverifieerbare restore.
11. Resource exhaustion via uploads, queries of modelcalls.
12. Supply-chain of model-update regressie.
## Security acceptance
Voor ieder bedreigingsscenario bestaan preventieve controls, detectie, negatieve tests en een herstel/runbook. Critical/high bevindingen blokkeren milestone release.
+54
View File
@@ -0,0 +1,54 @@
# Design System
## Visuele richting
Professionele donkere beheerinterface met hoge informatiedichtheid zonder rommel. Neutrale oppervlakken, één primaire accentkleur, duidelijke typografische hiërarchie en consistente spacingtokens.
## Componenten
App shell, breadcrumb, page header, status badge, operation journey, metric card, data table, filter bar, form field, tabs, timeline, code block, citation card, confirmation dialog, toast en inline problem panel.
## Tokens
Gebruik CSS custom properties voor color, spacing, radius, shadow, typography en focus ring. Geen willekeurige inlinewaarden. Statuskleuren hebben tekst/iconen en geteste contrastverhoudingen.
De producttokens staan in `static/admin.css` onder `:root`. De primaire
accentkleur is warm amber op een diepe blauwzwarte basis; groen, rood en blauw
blijven uitsluitend semantische statuskleuren. Panelen gebruiken een zichtbare
rand en oppervlaktetint in plaats van overvloedige drop shadows. Gewone tekst
blijft minimaal 13–14 px; 10–12 px is alleen voor korte labels en metadata.
Iedere statusbadge combineert zijn tekst met een onderscheidend symbool
(check, waarschuwing, fout, info of pending); kleur is nooit de enige drager.
De operation journey gebruikt iconen en pijlen als compacte oriëntatie, maar
blijft een semantisch gelabelde `nav` met echte links.
## Productidentiteit en shell
Het RAGCore-beeldmerk is een lokale inline SVG: begrenzende haken rond vier
verbonden evidencepunten. Daardoor blijft het scherp zonder extern asset en
herkenbaar in login en navigatie. De login koppelt de merkbelofte aan drie echte
capabilities: governed sources, dense+sparse retrieval en cited answers.
De desktop-shell houdt de gegroepeerde navigatie zichtbaar vanaf 1024 px.
Tablet en mobiel gebruiken een focus-begrensde drawer met Escape-herstel; zo
blijft op 768 px voldoende ruimte over voor inhoud. De topbar toont paginacontext,
deployment environment en de vaste local-first architectuurrichting, geen
verzonnen live status.
## Responsive gedrag
- 1024 px en breder: vaste zijbalk; dashboardflow vier of twee kolommen.
- 768–1023 px: navigatiedrawer; inhoud over de volledige viewport.
- 375–767 px: acties stapelen, metriekkaarten worden één kolom en de
source-to-answerflow wordt een verticale route.
- Dichte Sources-, Documents- en Jobs-tabellen worden onder 768 px semantische
recordkaarten. Iedere cel herhaalt haar kolomlabel; filters worden één kolom
en gebruiken de volledige breedte. Op desktop blijft de tabelweergave intact.
- De publieke login toont op tablet/mobiel eerst de SSO-actie en daarna de
productuitleg; de DOM- en focusvolgorde blijft logisch omdat de uitleg geen
interactieve elementen bevat.
- `prefers-reduced-motion` reduceert alle animaties en transities tot één frame.
## Query Lab
Split view: links query/config; rechts resultaten. Tabs voor final, candidates, context, prompt en trace. Iedere kandidaat toont bron, space, scores, filters, location en inclusion/exclusion reason.
+25
View File
@@ -0,0 +1,25 @@
# Screen Catalog
## V1 schermen
1. Sign-in/OIDC callback/failure.
2. Dashboard — **vernieuwd in M10-01**: een source-to-answer capability map
(Sources, durable ingest, dense+sparse/RRF retrieval, grounded answers met
citations), plus geautoriseerde application/space/policy-tellingen, echte
health- en jobstatus, profile-activationstatus en links naar de bestaande
operationele schermen. Niet-geïmplementeerde backup- of indexacties worden
niet als dashboardstatus verzonnen.
3. Workspaces/applications/service accounts.
4. Knowledge spaces met policies en grants.
5. Sources list/create/detail/sync history — **geïmplementeerd (1.2.0), operationele flow vernieuwd in M10-02**: `/admin/sources`, detail met Overview/Configuration/Documents/Sync history, acties Sync now/Pause/Resume/Edit/Delete (confirm + audit); folder/Git aanmaken, website nog niet. Documenttellingen linken naar het echte bronfilter en synchistorie onderscheidt wachtende child-ingests van complete en gedeeltelijk mislukte runs.
6. Documents list/detail/versions/chunks/artifacts — **geïmplementeerd (1.2.0), operationele flow vernieuwd in M10-02**: `/admin/documents` workspace-breed met search/filter/paginering; detail met Overview/Chunks/Metadata/Versions/Jobs en failure-uitleg. Het bronfilter toont alleen een workspace-eigen bronnaam en paginering behoudt alle actieve filters.
7. Jobs list/detail/events/retry — **geïmplementeerd (1.2.0)**: `/admin/jobs` en `/admin/jobs/{id}` met attempts/timing; retry alleen voor terminale jobs met retryable laatste fout. **1.2.1**: status- en typefilter en paginering draaien server-side in dezelfde workspace-gescopete query (niet meer als filter over een opgehaalde pagina), pagineringlinks behouden het actieve filter, en de vijf tellers boven de tabel blijven expliciet een scope-brede samenvatting van de wachtrij. **M10-02**: source/document/job-details linken onderling via de persistente, workspace-gescopete identiteit; vreemde, ontbrekende of voor de huidige rol onbereikbare relaties worden fail-closed niet als link gerenderd. Een retry-race eindigt als conflict en nooit als verzonnen succes.
8. Query Lab: selector-first formulier (spaces, sources, documents, talen, content types, trust, profiel) + Advanced raw-ID's; inspector Summary/Candidates/Fusion/Context/Answer/Trace met dense/sparse rank+score en RRF-bijdragen; een rerank-sectie verschijnt alleen wanneer die stap actief is (nu niet).
9. Profiles: embedding-profielactivatie/rollback (`/admin/profiles`); reranker- en retrievalprofielen hebben geen registry.
10. Evaluations: suites, cases, runs en comparison — **niet als scherm geëxposeerd** (offline `EvaluationRunner`/JSON-stores; geen navigatie-entry).
11. Auditlog met veilige filters — **niet als scherm geëxposeerd**; gedocumenteerde beperking.
12. Operations: `/admin/system` (readiness, queue, versie, actief embeddingprofiel, Docling/OIDC/telemetry) — backup/restore/reindex blijven runbooks/scripts, geen lege navigatie.
Navigatie (1.2.0): Overview · Knowledge (Spaces, Sources, Documents, Ingestion Jobs) · Access (Applications, Policies) · Quality (Query Lab, Profile Activation) · Operations (System Health).
Iedere screen krijgt empty/loading/error/forbidden/degraded states en auditbare beheeracties. De drie operationele lijsten en hun detailtabellen schakelen onder 768 px naar gelabelde recordkaarten, zodat geen betekenis alleen in een verborgen tabelkop of horizontale scroll blijft zitten.
+56
View File
@@ -0,0 +1,56 @@
# UX Specification
## Technologie
V1 gebruikt server-rendered Jinja2 + HTMX voor beheer en Query Lab. Dit houdt één deployment, gedeelde auth en weinig frontendbuildcomplexiteit. Een rijkere SPA is pas gerechtvaardigd door gemeten UX-beperkingen.
## Principes
- Desktop-first beheer maar volledig bruikbaar op tablet/mobiel.
- Status en gevaar worden niet alleen door kleur gecommuniceerd.
- Tabellen hebben zoek/filter/paginatie en behouden URL-state.
- Destructieve acties tonen impact en vereisen bevestiging.
- Langlopende acties tonen jobstatus en blijven na refresh volgen.
- Query Lab verklaart retrieval in begrijpelijke lagen.
- Geen gevoelige documenttekst in algemene dashboards.
## Accessibility
WCAG 2.2 AA-doel: keyboard navigation, zichtbare focus, labels, landmarks, contrast, reduced motion en semantische statusupdates.
De mobiele navigatie verplaatst focus bij openen naar de eerste link, houdt Tab
binnen de drawer en herstelt focus naar de menuknop bij Escape. Het hoofdgebied
is een expliciet skip-linkdoel. Controls hebben op compacte viewports minimaal
44 px bedieningshoogte; status blijft steeds als tekst beschikbaar.
## Dashboard als capability map
De eerste inhoud na de page header is de echte keten
`Sources → Ingest → Retrieve → Answer`. De kaart gebruikt alleen vaste,
geïmplementeerde capabilitytekst en geautoriseerde runtimegegevens: jobstatus en
het aantal zichtbare applications. Retrieval heet expliciet dense+sparse met
deterministische RRF-fusion; reranking wordt nergens als actieve stap voorgesteld.
Iedere stap linkt naar het bestaande beheerscherm dat de operator nodig heeft.
Onbereikbare stores en ontbrekende rechten worden als unavailable/restricted
getoond. Een ontbrekende waarde wordt nooit als nul, healthy of success
gepresenteerd. Algemene dashboards tonen geen documentinhoud.
## Operationele bron-naar-zoekflow
De schermen Sources, Documents en Ingestion Jobs vormen één navigeerbare keten
`Source → Document → Processing`. Een compacte journey-balk blijft op lijst- en
detailschermen zichtbaar. Details gebruiken de meest specifieke geautoriseerde
relatie: bron naar zijn gefilterde documenten, document naar het producerende
job en ingest-job terug naar het werkelijk geproduceerde document. Ontbrekende
of workspace-vreemde relaties worden niet geraden en krijgen geen link. Ook
generieke journey-stappen en lijstvelden linken alleen wanneer de huidige rol
de doelroute werkelijk kan openen; opgeslagen failure-job-ID's worden opnieuw
op bestaan en workspace-eigendom gecontroleerd voordat ze klikbaar worden.
Filters en paginering blijven URL-state en behouden elkaar. Een bronfilter toont
de geautoriseerde bronnaam; een onbekende of vreemde bron wordt alleen als
onbeschikbaar omschreven. Syncuitkomsten onderscheiden discovery, wachtende
child-ingests, volledige convergentie, gedeeltelijke mislukking en terminale
mislukking. Hersteltekst verwijst naar de bestaande retry-actie wanneer die
voor de gekoppelde job werkelijk beschikbaar kan zijn.
+22
View File
@@ -0,0 +1,22 @@
# Backup and Restore
## Backupset
1. PostgreSQL consistente dump/base backup.
2. Immutable blobs en parsed artifacts.
3. Qdrant collection snapshots.
4. Collection aliases en profile/configexport.
5. Secret/configmanifest zonder plaintext secretwaarden.
6. Softwareversie, migration revision en checksums.
## Restorevolgorde
Secrets/config -> PostgreSQL -> blobs -> parsed artifacts -> Qdrant snapshots -> aliases -> reconciliation -> golden evaluations -> security tests -> traffic.
## Validatie
Een backup telt pas als geslaagd na automatische restore naar lege tijdelijke omgeving. Controleer row/point/blob counts, referential integrity, sampled hashes, citations, golden queries en cross-space denial.
## RPO/RTO V1
Startdoel: dagelijkse volledige consistente backup plus incrementele blobbackup; RPO 24 uur, RTO 4 uur. Later alleen aanpassen op gemeten noodzaak.
+138
View File
@@ -0,0 +1,138 @@
# Unraid Deployment
## Opslag
Active state gebruikt directe SSD/NVMe poolpaden:
```text
/mnt/<pool>/appdata/ragcore/postgres
/mnt/<pool>/appdata/ragcore/qdrant
/mnt/<pool>/appdata/ragcore/blobs
/mnt/<pool>/appdata/ragcore/parsed
/mnt/<pool>/appdata/ragcore/tmp
```
Gebruik voor PostgreSQL/Qdrant **niet** `/mnt/pool/appdata/...`. Backupexports mogen naar `/mnt/pool/backups/ragcore`.
## Deployment
- Maak `.env` uit voorbeeld en genereer unieke secrets.
- Zet `RAGCORE_STATE_ROOT` naar direct poolpad.
- Start Compose; houd `models` profiel uit wanneer centrale Ollama bestaat.
- Maak één dedicated extern Docker-netwerk voor Nginx Proxy Manager en RAGCore.
- Configureer Nginx Proxy Manager naar `ragcore-app:8080` op dat netwerk.
- Publiceer de app-hostpoort uitsluitend op `127.0.0.1`; stuur nooit bearer-tokens
rechtstreeks over een LAN-HTTP-poort.
- Koppel Authentik/OIDC in productie.
- Publiceer geen PostgreSQL/Qdrant/Ollamapoort.
### Reverse-proxy trust boundary
Productie accepteert `X-Forwarded-*` uitsluitend van de expliciet ingestelde
proxy-IP/CIDR. Wildcardtrust (`*`, `0.0.0.0/0`, `::/0`) wordt bij startup
geweigerd. Een dedicated netwerk voorkomt dat een willekeurige container binnen
een breed gedeeld subnet als vertrouwde proxy kan optreden.
Eenmalige netwerkinitialisatie voor de gedocumenteerde Unraid-topologie:
```bash
docker network create --driver bridge --subnet 172.30.50.0/29 ragcore-proxy
docker network connect --ip 172.30.50.2 ragcore-proxy NginxProxyManager
```
Voeg aan `.env` toe:
```dotenv
RAGCORE_PUBLIC_BASE_URL=https://rag.example.com
RAGCORE_TRUSTED_PROXY_IPS=172.30.50.2
RAGCORE_PROXY_NETWORK=ragcore-proxy
```
De productie-Compose koppelt `app` met alias `ragcore-app` aan dit externe
netwerk. Stel de Nginx Proxy Manager upstream in op scheme `http`, hostname
`ragcore-app`, port `8080`. Controleer HTTPS vóór de oude upstream wordt
verwijderd. De loopback-hostpoort blijft alleen voor lokale healthchecks en
rollbackdiagnose beschikbaar. Vertrouw bij voorkeur uitsluitend het statische
IP van Nginx Proxy Manager. Een CIDR is alleen correct wanneer elk adres in dat
bereik een geautoriseerde proxy is; een Docker-netwerkgateway valt ook binnen
het subnet en mag daarom niet impliciet forwardingheaders mogen aanleveren.
## Preflight
Beschikbare disk/RAM, filesystem, ownership UID 10001, klok/NTP, backupdoel, modelendpoint en reverse-proxy headers controleren:
```bash
python3 scripts/unraid_preflight.py \
--state-root /mnt/<pool>/appdata/ragcore \
--backup-dir /mnt/pool/backups/ragcore \
--ollama-url http://<ollama-host>:11434 \
--base-url https://ragcore.example.com
```
Blokkerende bevindingen (`[BLOCK]`, o.a. een actieve `/mnt/user`-status voor
`--state-root`) stoppen de deploy; `[WARN]` is informatief (bijv. klok-sync
niet vast te stellen buiten een Linux-host).
## Operational runbook
Alle commando's hieronder gaan uit van `compose.yaml` + `compose.production.yaml`
samen; nooit `compose.test.yaml` of `compose.restore-test.yaml` in productie
(die zijn uitsluitend voor lokale/testomgevingen).
### Start (eerste deploy of na reboot)
```bash
cp .env.example .env # eenmalig; vul echte secrets en RAGCORE_STATE_ROOT in
python3 scripts/unraid_preflight.py --state-root /mnt/<pool>/appdata/ragcore --backup-dir /mnt/pool/backups/ragcore
docker compose -f compose.yaml -f compose.production.yaml up -d
python3 scripts/check_app_health.py --base-url https://rag.example.com
```
### Stop
```bash
docker compose -f compose.yaml -f compose.production.yaml stop
```
Gebruik `down` (in plaats van `stop`) alleen wanneer je ook de containers en
het (interne) netwerk wilt verwijderen; volumes/bind mounts (dus alle data)
blijven in beide gevallen ongemoeid tenzij `-v` wordt toegevoegd -- gebruik
`-v` nooit in productie.
### Upgrade
```bash
# 1. Backup vóór elke upgrade (zie runbooks/BACKUP.md)
python3 scripts/backup.py --backup-root /mnt/pool/backups/ragcore
# 2. Nieuwe image ophalen/bouwen en migreren
git pull # of: nieuwe release-ZIP uitpakken
docker compose -f compose.yaml -f compose.production.yaml build app worker mcp
docker compose -f compose.yaml -f compose.production.yaml run --rm app alembic upgrade head
# 3. Rollend herstarten
docker compose -f compose.yaml -f compose.production.yaml up -d
python3 scripts/check_app_health.py --base-url https://rag.example.com
```
Zie `docs/09_OPERATIONS/UPGRADE_STRATEGY.md` voor het volledige beleid
(o.a. backward-compatibele migraties, nooit in-place index-upgrades).
### Rollback
Applicatie-rollback (voorkeur, geen restore):
```bash
git checkout <vorige-release-tag-of-commit> # of: vorige release-ZIP
docker compose -f compose.yaml -f compose.production.yaml build app worker mcp
docker compose -f compose.yaml -f compose.production.yaml up -d
```
Migraties zijn per beleid backward-compatible voor minstens één release, dus
een applicatie-rollback tegen de nieuwere schema-versie werkt zonder
`alembic downgrade`. Een volledige restore vanaf backup is het laatste
redmiddel, niet de normale rollback-weg -- zie `runbooks/RESTORE.md`.
Index-/profielactivatie-rollback (indien van toepassing) gaat via
`/admin/profiles` (M4-07's `ProfileActivationService`), niet via deze
Compose-commando's.
+130
View File
@@ -0,0 +1,130 @@
# Upgrade Strategy
## Applicatie
Build immutable image, run migrations/preflight, deploy, verify health and smoke tests. Database migrations are backward compatible for at least one application rollout whenever practical.
## Embedding/index
Never in place. Register new profile, build shadow collection, evaluate, activate alias, retain old build for rollback.
## PostgreSQL/Qdrant
Pin exact supported versions, read release notes, test backup restore and application suite in staging/temporary environment, then maintenance deployment. No automatic major upgrades.
## Rollback
Application image rollback plus compatible migration strategy; index alias rollback for retrieval; config/profile activation history. Restore is last resort, not ordinary rollback.
## Upgrade naar 1.2.1
Deze release bundelt drie rondes: SOURCE-CONVERGENCE (migratie `0017`),
OPERATIONS-ISOLATION (`0018`) en RELEASE-CLEANUP (`0019`). Eén
`alembic upgrade head` voert ze in volgorde uit; de stappen hieronder
beschrijven wat er per ronde verandert en wat je vóór de uitrol moet zetten.
### SOURCE-CONVERGENCE (`0017`)
1. **Eerst configureren, dan uitrollen.** `RAGCORE_SOURCE_ROOTS` is een expliciete
allowlist van server-side paden die een Folder- of Git-bron mag lezen, en is
*leeg by default* -- fail closed. Zolang die leeg is kan geen enkele
filesystem-bron worden aangemaakt of gesynchroniseerd, en falen bestaande
folder/Git-syncs **terminaal** (`source path is outside every allowed source
root`). Zet de waarde dus vóór de uitrol, met de paden zoals de *worker* ze
ziet, en zorg dat elk pad ook read-only in de worker gemount is
(`RAGCORE_SOURCE_MOUNT` in `compose.yaml`). Mount zonder allowlist leest
niets; allowlist zonder mount resolvet niet. Er is bewust geen impliciete
default, `/` al helemaal niet.
2. Migratie `0017_source_convergence` (`alembic upgrade head`) voegt de
convergentie-administratie toe aan `content.source_items`
(`applied_fingerprint`, `applied_at`, `pending_job_id`, `attempts`,
`last_failure_code`, `last_failure_retryable`), `pending` aan
`source_sync_runs`, verbreedt `sync_state`/`outcome` naar 32 tekens, en
vervangt de foreign key van `content.documents` door een samengestelde
`(source_id, workspace_id, space_id)` met een bijbehorende unique key op
`content.sources`.
3. **Twee datawijzigingen in die migratie, geen schemawijzigingen:**
(a) bestaande onthouden items worden als *applied* gebackfilld -- ze zijn
geïngest onder het oude model, waarin een afgeronde discovery het enige
completion-signaal was; (b) een bron die door `0016` over meerdere knowledge
spaces heen mocht liggen wordt gesplitst naar per-space zusterbronnen
(deterministisch afgeleid id) en de documenten daarvan worden omgehangen.
4. Qdrant-punten van zulke *gesplitste* documenten dragen nog het oude
`source_id`. Draai daarna een authoritative reconcile/rebuild van de actieve
collectie, net als bij de 1.2.0-upgrade. Controleer vooraf of er iets te
splitsen valt:
```sql
SELECT DISTINCT d.source_id, d.workspace_id, d.space_id
FROM content.documents AS d
JOIN content.sources AS s ON s.id = d.source_id
WHERE d.workspace_id <> s.workspace_id OR d.space_id <> s.space_id;
```
Geen rijen = geen split en geen rebuild nodig voor deze stap.
5. **Downgrade is niet symmetrisch.** De split wordt niet teruggedraaid (de
zusterbronnen zijn echte bronnen geworden), en `awaiting_ingest` /
`completed_with_failures` bestaan niet in het oude schema: die vallen bij een
downgrade terug op `applying` respectievelijk `failed`, zodat een
gedowngrade deployment "onaf of mislukt" leest in plaats van "geslaagd".
6. Verwachte gedragsverandering na de uitrol: een sync meldt `awaiting_ingest`
tot zijn child-`ingest_upload`-jobs teruggerapporteerd hebben, en
`last_successful_sync_at` schuift alleen op bij een volledig geconvergeerde
run. Een bron die eerder direct "Last sync ok" toonde, toont nu eerst
"Indexing" -- dat is de correctie, geen regressie.
### OPERATIONS-ISOLATION (`0018`)
7. Migratie `0018_job_workspace_ownership` maakt eigenaarschap van een job een
echte kolom (`jobs.jobs.workspace_id`, geïndexeerd) en voegt
`content.source_items.last_failure_job_id` toe. De backfill is bewust
fail-closed: alleen een `payload->>'workspace_id'` die zowel een geldige
UUID is *als* een bestaande workspace aanwijst wordt overgenomen. Al het
andere blijft `NULL` = *legacy*, en dat betekent onzichtbaar en
onwijzigbaar voor elke workspace-gebonden principal; alleen een Platform
Admin ziet zulke jobs nog. Verwachte gedragsverandering: een operator die
vóór deze upgrade jobs van andere tenants zag, ziet nu alleen de eigen
workspace. Controleer hoeveel rijen legacy blijven:
```sql
SELECT count(*) FROM jobs.jobs WHERE workspace_id IS NULL;
```
Die jobs zijn niet stuk; ze zijn alleen niet toewijsbaar. Laat ze aflopen of
ruim ze op als Platform Admin.
8. **`FILESYSTEM_SOURCE_MANAGE` is nieuw en ligt uitsluitend bij
`PLATFORM_ADMIN`.** Een Folder- of Git-bron aanmaken *of wijzigen* vereist
die permissie bovenop `SOURCE_MANAGE`. Workspace Admins en Space Managers
die vóór deze upgrade filesystem-bronnen beheerden, kunnen dat niet meer;
plan wie dat overneemt. De permissie en de `RAGCORE_SOURCE_ROOTS`-sandbox
zijn onafhankelijke poorten: een pad buiten de allowlist wordt ook voor een
Platform Admin geweigerd.
### RELEASE-CLEANUP (`0019`)
9. Migratie `0019_job_listing_index` voegt alleen een index toe
(`ix_jobs_workspace_status_recent` op `(workspace_id, status, updated_at)`).
Geen datawijziging, geen gedragsverandering buiten performance: het
jobsoverzicht filtert vanaf deze release in SQL over de hele workspace in
plaats van over een opgehaalde pagina, en dit is de index waarop die query
draait. Op een grote `jobs.jobs` kost het aanmaken kort een write-lock;
gebruik zo nodig `CREATE INDEX CONCURRENTLY` buiten Alembic om en markeer de
migratie daarna als uitgevoerd.
## Upgrade naar 1.2.0
1. Migratie `0016_sources_lifecycle` (`alembic upgrade head`) maakt `content.sources`,
`source_sync_runs`, `source_items`, voegt `documents.source_id` (NOT NULL, FK) toe en
backfillt die uit `source_key` met dezelfde regel die de runtime gebruikt
(UUID-vormige key = caller-bron; anders de ingebouwde *Uploads*-bron van de space).
Voor iedere backfilled bron ontstaat een registry-record (kind `upload`).
2. Qdrant-punten die vóór 1.2.0 zijn geïndexeerd dragen nog het oude synthetische
`source_id` (uuid5 per document). Draai na de migratie een authoritative
reconcile/rebuild van de actieve collectie (`IndexBuildService.reconcile`/`build`,
beide gebruiken nu de canonical IndexPoint-mapping) zodat `source_ids`-filters en
provenance ook voor bestaande content kloppen; tot die tijd matchen filters op die
oude punten niet.
3. Worker en app opnieuw uitrollen (nieuwe job-operation `sync_source`; nieuwe
Control Plane-routes `/admin/sources`, `/admin/documents`, `/admin/system`).
4. SDK-consumers: de citation-schema's kregen additieve optionele velden
(`source_id`, `source_name`, `source_kind`, `locator`, `revision`, `display`).
+20
View File
@@ -0,0 +1,20 @@
# MCP Specification
MCP is a thin adapter over the same application use cases and authorization as REST. It owns no database or retrieval logic.
## Read-only tools V1
- `rag_search(query, spaces?, profile?, filters?)`
- `rag_get_context(query, spaces?, profile?, budget?)`
- `rag_answer(query, spaces?, profile?)`
- `rag_list_spaces()`
- `rag_get_document(document_id, version_id?)`
- `rag_get_citation(citation_id)`
## Resources
`rag://spaces/{space}`, `rag://documents/{id}/versions/{version}`, `rag://citations/{id}`.
## Security
Remote HTTP transport uses scoped authorization. Tool schemas expose no raw filters or admin operations. Same effective-space calculation, rate limits, audit and data policies apply. Standard MCP surface is read-only; reindex/delete/grant management remains admin API/UI.
+7
View File
@@ -0,0 +1,7 @@
# Licensing status
`LICENSE_DECISION_REQUIRED`
No repository-wide public distribution license is selected by this remediation campaign. Repository visibility or source access must not be interpreted as permission to reuse, modify or redistribute the code.
Before public release, the owner must select distribution terms and review dependency, model, dataset, embedding, font, icon and other third-party licenses. Model weights and datasets are separate artifacts and may have terms that differ from the application source.
+3
View File
@@ -0,0 +1,3 @@
# Contracts
`openapi.yaml` is the target public API contract and evolves with implementation tasks. JSON schemas define durable Qdrant payload and job envelope boundaries. Contract changes require compatibility review, tests and versioning where breaking.
+41
View File
@@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ragcore.example.test/schemas/job-payload-envelope-v1.json",
"title": "RAGCore Job Payload Envelope",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"job_type",
"workspace_id",
"idempotency_key",
"payload"
],
"properties": {
"schema_version": {
"const": 1
},
"job_type": {
"type": "string",
"pattern": "^[a-z][a-z0-9_.-]+$"
},
"workspace_id": {
"type": "string",
"format": "uuid"
},
"idempotency_key": {
"type": "string",
"minLength": 16,
"maxLength": 256
},
"trace_id": {
"type": [
"string",
"null"
]
},
"payload": {
"type": "object"
}
}
}
+904
View File
@@ -0,0 +1,904 @@
openapi: 3.1.0
info:
title: RAGCore API
version: 1.2.1
description: Central secure local-first retrieval platform contract.
servers:
- url: https://ragcore.example.test
security:
- bearerAuth: []
tags:
- name: health
- name: control
- name: ingestion
- name: retrieval
- name: operations
paths:
/health/live:
get:
security: []
tags: [health]
operationId: getLiveness
responses:
'200':
description: Process is alive.
content:
application/json:
schema: {$ref: '#/components/schemas/Health'}
/health/ready:
get:
security: []
tags: [health]
operationId: getReadiness
responses:
'200':
description: Required dependencies are ready.
content:
application/json:
schema: {$ref: '#/components/schemas/Readiness'}
'503': {$ref: '#/components/responses/Problem'}
/v1/applications:
get:
tags: [control]
operationId: listApplications
parameters:
- {$ref: '#/components/parameters/Cursor'}
- {$ref: '#/components/parameters/Limit'}
responses:
'200':
description: Applications visible to the caller.
content:
application/json:
schema:
$ref: '#/components/schemas/ApplicationPage'
'401': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
post:
tags: [control]
operationId: createApplication
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ApplicationCreate'}
responses:
'201':
description: Application created.
content:
application/json:
schema: {$ref: '#/components/schemas/Application'}
'400': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'409': {$ref: '#/components/responses/Problem'}
/v1/applications/{application_id}/credentials:
post:
tags: [control]
operationId: issueApplicationCredential
parameters:
- {$ref: '#/components/parameters/ApplicationId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CredentialIssueRequest'}
responses:
'201':
description: Credential issued; plaintext token is returned only here.
content:
application/json:
schema: {$ref: '#/components/schemas/CredentialIssued'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/knowledge-spaces:
get:
tags: [control]
operationId: listKnowledgeSpaces
parameters:
- {$ref: '#/components/parameters/Cursor'}
- {$ref: '#/components/parameters/Limit'}
responses:
'200':
description: Authorized spaces.
content:
application/json:
schema: {$ref: '#/components/schemas/KnowledgeSpacePage'}
post:
tags: [control]
operationId: createKnowledgeSpace
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/KnowledgeSpaceCreate'}
responses:
'201':
description: Knowledge space created.
content:
application/json:
schema: {$ref: '#/components/schemas/KnowledgeSpace'}
'403': {$ref: '#/components/responses/Problem'}
'409': {$ref: '#/components/responses/Problem'}
/v1/knowledge-spaces/{space_id}/grants:
post:
tags: [control]
operationId: createSpaceGrant
parameters:
- {$ref: '#/components/parameters/SpaceId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SpaceGrantCreate'}
responses:
'201':
description: Grant created.
content:
application/json:
schema: {$ref: '#/components/schemas/SpaceGrant'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/policies:
get:
tags: [control]
operationId: listPolicies
parameters:
- {$ref: '#/components/parameters/Cursor'}
- {$ref: '#/components/parameters/Limit'}
responses:
'200':
description: Workspace policies visible to the caller.
content:
application/json:
schema: {$ref: '#/components/schemas/PolicyPage'}
'403': {$ref: '#/components/responses/Problem'}
post:
tags: [control]
operationId: createPolicy
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/PolicyCreate'}
responses:
'201':
description: Policy created.
content:
application/json:
schema: {$ref: '#/components/schemas/Policy'}
'400': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
'409': {$ref: '#/components/responses/Problem'}
/v1/uploads:
post:
tags: [ingestion]
operationId: createUpload
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [space_id, file]
properties:
space_id: {type: string, format: uuid}
source_id: {type: [string, 'null'], format: uuid}
external_id: {type: [string, 'null'], maxLength: 512, description: 'The stable id of this item at its source (e.g. a wiki page id). Requires source_id -- without one there is nothing stable to deduplicate against. Ingesting the same (source_id, external_id) pair again creates a new version of the same document instead of a duplicate.'}
locale: {type: [string, 'null'], maxLength: 35, description: 'BCP-47-ish language tag for the content; defaults to en.'}
file: {type: string, format: binary}
responses:
'202':
description: Upload accepted and ingestion job created.
content:
application/json:
schema: {$ref: '#/components/schemas/AcceptedJob'}
'400': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'413': {$ref: '#/components/responses/Problem'}
'415': {$ref: '#/components/responses/Problem'}
/v1/sources:
get:
tags: [ingestion]
operationId: listSources
parameters:
- {$ref: '#/components/parameters/Cursor'}
- {$ref: '#/components/parameters/Limit'}
- name: space_id
in: query
schema: {type: string, format: uuid}
responses:
'200':
description: Authorized sources.
content:
application/json:
schema: {$ref: '#/components/schemas/SourcePage'}
post:
tags: [ingestion]
operationId: createSource
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SourceCreate'}
responses:
'201':
description: Source created.
content:
application/json:
schema: {$ref: '#/components/schemas/Source'}
'403': {$ref: '#/components/responses/Problem'}
/v1/sources/{source_id}/syncs:
post:
tags: [ingestion]
operationId: startSourceSync
parameters:
- {$ref: '#/components/parameters/SourceId'}
- {$ref: '#/components/parameters/IdempotencyKey'}
responses:
'202':
description: Synchronization scheduled.
content:
application/json:
schema: {$ref: '#/components/schemas/AcceptedJob'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/documents:
get:
tags: [ingestion]
operationId: listDocuments
description: >-
Looks a document up by the sync client's own identity -- the primitive
a client uses to decide skip-vs-update by comparing content_sha256 /
source_fingerprint before ever uploading. With both filters: zero or
one item; an unmatched filter is an empty page, not a 404. With only
source_id: EVERY document that sync identity owns in the workspace
(active and retired, each with its searchable version when it has
one) -- the authoritative current set a sync client diffs its expected
set against instead of trusting its own memory. Bounded (a sync source
is a bounded set by construction), not paginated; no space_id/status
browse mode.
parameters:
- name: source_id
in: query
required: true
schema: {type: string, format: uuid}
- name: external_id
in: query
required: false
schema: {type: string, maxLength: 512}
responses:
'200':
description: Zero or one matching document (with external_id), or every document of the source identity (without).
content:
application/json:
schema: {$ref: '#/components/schemas/DocumentPage'}
'400': {$ref: '#/components/responses/Problem'}
'401': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
/v1/documents/{document_id}:
get:
tags: [ingestion]
operationId: getDocument
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses:
'200':
description: Document with its active (published) version, if any.
content:
application/json:
schema: {$ref: '#/components/schemas/Document'}
'401': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/documents/{document_id}/versions:
get:
tags: [ingestion]
operationId: listDocumentVersions
description: Every version of the document regardless of state, newest first. Not paginated.
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses:
'200':
description: Every version of this document.
content:
application/json:
schema: {$ref: '#/components/schemas/DocumentVersionPage'}
'401': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/documents/{document_id}/retire:
post:
tags: [ingestion]
operationId: retireDocument
description: >-
Soft-retires a document (source_deleted=true) via the existing
mark_source_deleted; never a hard delete. Idempotent. Callers should
only call this after a complete, successful source scan confirms the
item is genuinely gone -- a partial or failed scan must not retire
anything, per the reconcile rule in
docs/04_INGESTION/CONNECTORS.md. Not enforced server-side: there is
no concept of "a scan" on this side of the API.
parameters: [{$ref: '#/components/parameters/DocumentId'}]
responses:
'200':
description: The now-retired document.
content:
application/json:
schema: {$ref: '#/components/schemas/Document'}
'401': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/spaces/{space_id}/index/reconcile:
post:
tags: [ingestion]
operationId: reconcileSpaceIndex
description: >-
Brings the searchable index of ONE knowledge space in line with its
published, non-retired document versions: points that belong to a
superseded version or to a retired (source_deleted) document are
removed; nothing is re-embedded or inserted. Idempotent; scoped to
the given space only (other spaces in the same collection are never
read or written). The natural last step of a sync client's run after
it has uploaded new versions and retired vanished items.
parameters:
- name: space_id
in: path
required: true
schema: {type: string, format: uuid}
responses:
'200':
description: Reconciliation outcome (counts).
content:
application/json:
schema: {$ref: '#/components/schemas/SpaceIndexReconciliation'}
'401': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
'503': {$ref: '#/components/responses/Problem'}
/v1/jobs/{job_id}:
get:
tags: [operations]
operationId: getJob
parameters: [{$ref: '#/components/parameters/JobId'}]
responses:
'200':
description: Job status.
content:
application/json:
schema: {$ref: '#/components/schemas/Job'}
'401': {$ref: '#/components/responses/Problem'}
'404': {$ref: '#/components/responses/Problem'}
/v1/search:
post:
tags: [retrieval]
operationId: searchKnowledge
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SearchRequest'}
responses:
'200':
description: Authorized retrieval results.
content:
application/json:
schema: {$ref: '#/components/schemas/SearchResponse'}
'400': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'503': {$ref: '#/components/responses/Problem'}
'422': {$ref: '#/components/responses/Problem'}
/v1/context:
post:
tags: [retrieval]
operationId: buildContext
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ContextRequest'}
responses:
'200':
description: Token-bounded context package.
content:
application/json:
schema: {$ref: '#/components/schemas/ContextResponse'}
'400': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'422': {$ref: '#/components/responses/Problem'}
'503': {$ref: '#/components/responses/Problem'}
/v1/answers:
post:
tags: [retrieval]
operationId: createAnswer
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/AnswerRequest'}
responses:
'200':
description: Grounded answer or explicit non-answer.
content:
application/json:
schema: {$ref: '#/components/schemas/AnswerResponse'}
text/event-stream:
schema: {type: string}
'400': {$ref: '#/components/responses/Problem'}
'403': {$ref: '#/components/responses/Problem'}
'422': {$ref: '#/components/responses/Problem'}
'429': {$ref: '#/components/responses/Problem'}
'503': {$ref: '#/components/responses/Problem'}
/v1/citations/{citation_id}:
get:
tags: [retrieval]
operationId: getCitation
parameters: [{$ref: '#/components/parameters/CitationId'}]
responses:
'200':
description: Authorized immutable citation.
content:
application/json:
schema: {$ref: '#/components/schemas/Citation'}
'404': {$ref: '#/components/responses/Problem'}
/v1/feedback:
post:
tags: [retrieval]
operationId: createFeedback
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/FeedbackCreate'}
responses:
'201':
description: Feedback recorded.
content:
application/json:
schema: {$ref: '#/components/schemas/Feedback'}
'404': {$ref: '#/components/responses/Problem'}
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: RAGCore service token or OIDC session token
parameters:
Cursor:
name: cursor
in: query
schema: {type: string, maxLength: 2048}
Limit:
name: limit
in: query
schema: {type: integer, minimum: 1, maximum: 100, default: 50}
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: {type: string, minLength: 16, maxLength: 128}
ApplicationId: {name: application_id, in: path, required: true, schema: {type: string, format: uuid}}
SpaceId: {name: space_id, in: path, required: true, schema: {type: string, format: uuid}}
SourceId: {name: source_id, in: path, required: true, schema: {type: string, format: uuid}}
DocumentId: {name: document_id, in: path, required: true, schema: {type: string, format: uuid}}
JobId: {name: job_id, in: path, required: true, schema: {type: string, format: uuid}}
CitationId: {name: citation_id, in: path, required: true, schema: {type: string, format: uuid}}
responses:
Problem:
description: Stable problem-details response.
content:
application/problem+json:
schema: {$ref: '#/components/schemas/Problem'}
schemas:
SpaceIndexReconciliation:
type: object
required: [space_id, documents_total, documents_retired, searchable_versions, points_before, deleted_points, orphaned_documents, points_after]
properties:
space_id: {type: string, format: uuid}
documents_total: {type: integer}
documents_retired: {type: integer}
searchable_versions: {type: integer}
points_before: {type: integer}
deleted_points: {type: integer}
orphaned_documents: {type: integer}
points_after: {type: integer}
Health:
type: object
required: [status, service]
properties:
status: {const: ok}
service: {const: ragcore}
Readiness:
type: object
required: [status, components]
properties:
status: {enum: [ok, degraded, unavailable]}
components:
type: object
additionalProperties:
type: object
required: [status]
properties:
status: {enum: [ok, unavailable]}
latency_ms: {type: [number, 'null'], minimum: 0}
code: {type: [string, 'null']}
Problem:
type: object
required: [type, title, status, code, trace_id]
properties:
type: {type: string, format: uri}
title: {type: string}
status: {type: integer, minimum: 400, maximum: 599}
code: {type: string, pattern: '^[A-Z0-9_]+$'}
detail: {type: [string, 'null']}
trace_id: {type: string}
retry_after_seconds: {type: [integer, 'null'], minimum: 0}
errors: {type: array, items: {type: object}}
ApplicationCreate:
type: object
additionalProperties: false
required: [slug, display_name]
properties:
slug: {type: string, pattern: '^[a-z0-9][a-z0-9.-]{1,62}$'}
display_name: {type: string, minLength: 1, maxLength: 120}
description: {type: [string, 'null'], maxLength: 1000}
scopes: {type: array, uniqueItems: true, items: {$ref: '#/components/schemas/ApplicationScope'}}
default_space_ids: {type: array, uniqueItems: true, items: {type: string, format: uuid}}
Application:
allOf:
- {$ref: '#/components/schemas/ApplicationCreate'}
- type: object
required: [id, workspace_id, status, created_at]
properties:
id: {type: string, format: uuid}
workspace_id: {type: string, format: uuid}
status: {enum: [active, disabled]}
created_at: {type: string, format: date-time}
ApplicationPage:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Application'}}
next_cursor: {type: [string, 'null']}
CredentialIssueRequest:
type: object
additionalProperties: false
required: [name, scopes]
properties:
name: {type: string, minLength: 1, maxLength: 120}
scopes: {type: array, uniqueItems: true, items: {type: string}}
expires_at: {type: [string, 'null'], format: date-time}
CredentialIssued:
type: object
required: [id, token, token_prefix, scopes, created_at]
properties:
id: {type: string, format: uuid}
token: {type: string, writeOnly: true}
token_prefix: {type: string}
scopes: {type: array, items: {type: string}}
created_at: {type: string, format: date-time}
expires_at: {type: [string, 'null'], format: date-time}
ApplicationScope:
enum: [search, context, answer, documents:read, citations:read, feedback:write, sources:sync]
PolicyCreate:
type: object
additionalProperties: false
required: [operation]
properties:
operation: {type: string, minLength: 1, maxLength: 120}
knowledge_space_id: {type: [string, 'null'], format: uuid}
required_scopes: {type: array, uniqueItems: true, items: {$ref: '#/components/schemas/ApplicationScope'}}
Policy:
allOf:
- {$ref: '#/components/schemas/PolicyCreate'}
- type: object
required: [id, workspace_id, active, created_at]
properties:
id: {type: string, format: uuid}
workspace_id: {type: string, format: uuid}
active: {type: boolean}
created_at: {type: string, format: date-time}
PolicyPage:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Policy'}}
next_cursor: {type: [string, 'null']}
DataPolicy:
enum: [local_only, local_preferred, cloud_allowed, retrieval_only, no_generation]
KnowledgeSpaceCreate:
type: object
additionalProperties: false
required: [slug, display_name, data_policy]
properties:
slug: {type: string, pattern: '^[a-z0-9][a-z0-9._-]{1,100}$'}
display_name: {type: string, minLength: 1, maxLength: 120}
description: {type: [string, 'null'], maxLength: 2000}
data_policy: {$ref: '#/components/schemas/DataPolicy'}
minimum_trust: {enum: [trusted_internal, trusted_external, unverified, user_uploaded, internet]}
retention_days: {type: integer, minimum: 1, maximum: 3650, default: 90}
KnowledgeSpace:
allOf:
- {$ref: '#/components/schemas/KnowledgeSpaceCreate'}
- type: object
required: [id, workspace_id, status, created_at]
properties:
id: {type: string, format: uuid}
workspace_id: {type: string, format: uuid}
status: {enum: [active, disabled]}
created_at: {type: string, format: date-time}
KnowledgeSpacePage:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/KnowledgeSpace'}}
next_cursor: {type: [string, 'null']}
SpaceGrantCreate:
type: object
additionalProperties: false
required: [principal_type, principal_id, permissions]
properties:
principal_type: {enum: [application, user, role]}
principal_id: {type: string, format: uuid}
permissions: {type: array, uniqueItems: true, items: {enum: [read, search, context, answer, manage_sources, manage_space]}}
SpaceGrant:
allOf:
- {$ref: '#/components/schemas/SpaceGrantCreate'}
- type: object
required: [id, space_id, created_at]
properties:
id: {type: string, format: uuid}
space_id: {type: string, format: uuid}
created_at: {type: string, format: date-time}
SourceType: {enum: [upload, folder, git, website]}
SourceCreate:
type: object
additionalProperties: false
required: [space_id, type, name, config]
properties:
space_id: {type: string, format: uuid}
type: {$ref: '#/components/schemas/SourceType'}
name: {type: string, minLength: 1, maxLength: 160}
config: {type: object, description: Typed and redacted per connector; secrets use references.}
enabled: {type: boolean, default: true}
Source:
allOf:
- {$ref: '#/components/schemas/SourceCreate'}
- type: object
required: [id, status, created_at]
properties:
id: {type: string, format: uuid}
status: {enum: [active, disabled, degraded]}
last_sync_at: {type: [string, 'null'], format: date-time}
created_at: {type: string, format: date-time}
SourcePage:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Source'}}
next_cursor: {type: [string, 'null']}
DocumentVersionStatus:
enum: [acquired, security_checked, parsed, normalized, chunked, embedded, indexed, validated, published, superseded, retired, failed]
DocumentVersion:
type: object
required: [id, document_id, status, source_fingerprint, content_sha256]
properties:
id: {type: string, format: uuid}
document_id: {type: string, format: uuid}
status: {$ref: '#/components/schemas/DocumentVersionStatus'}
source_fingerprint: {type: string}
content_sha256: {type: string, pattern: '^[a-f0-9]{64}$'}
created_at: {type: [string, 'null'], format: date-time}
DocumentVersionPage:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/DocumentVersion'}}
next_cursor: {type: [string, 'null']}
Document:
type: object
required: [id, space_id, external_id, title, status]
properties:
id: {type: string, format: uuid}
space_id: {type: string, format: uuid}
source_id: {type: [string, 'null'], format: uuid, description: 'Null for a document ingested without a source_id (e.g. an ad hoc upload).'}
external_id: {type: string}
title: {type: string}
status: {enum: [active, source_deleted]}
active_version: {oneOf: [{$ref: '#/components/schemas/DocumentVersion'}, {type: 'null'}]}
DocumentPage:
type: object
required: [items]
properties:
items: {type: array, items: {$ref: '#/components/schemas/Document'}}
next_cursor: {type: [string, 'null']}
JobStatus: {enum: [ready, leased, succeeded, retry_wait, terminal]}
Job:
type: object
required: [id, operation, status, attempt_count, max_attempts]
properties:
id: {type: string, format: uuid}
operation: {type: string}
status: {$ref: '#/components/schemas/JobStatus'}
attempt_count: {type: integer, minimum: 0}
max_attempts: {type: integer, minimum: 1}
last_error_code: {type: [string, 'null']}
AcceptedJob:
type: object
required: [job_id, status_url]
properties:
job_id: {type: string, format: uuid}
status_url: {type: string, format: uri-reference}
SearchFilter:
type: object
additionalProperties: false
properties:
content_types: {type: array, uniqueItems: true, maxItems: 50, items: {type: string}}
languages: {type: array, uniqueItems: true, maxItems: 50, items: {type: string, minLength: 2, maxLength: 16}}
source_ids: {type: array, uniqueItems: true, maxItems: 50, items: {type: string, format: uuid}}
document_ids: {type: array, uniqueItems: true, maxItems: 50, items: {type: string, format: uuid}}
trust_levels: {type: array, uniqueItems: true, maxItems: 50, items: {enum: [trusted_internal, trusted_external, unverified, user_uploaded, internet]}}
SearchRequest:
type: object
additionalProperties: false
required: [query]
properties:
query: {type: string, minLength: 1, maxLength: 8000, pattern: '.*\S.*'}
requested_space_ids: {type: array, uniqueItems: true, maxItems: 50, items: {type: string, format: uuid}}
profile: {type: string, pattern: '^[a-z0-9][a-z0-9_-]{0,63}$', default: hybrid-v1}
max_results: {type: integer, minimum: 1, maximum: 50, default: 10}
filters: {$ref: '#/components/schemas/SearchFilter'}
include_debug: {type: boolean, default: false, description: Authorized Query Lab use only.}
Citation:
type: object
additionalProperties: false
required: [id, document_id, document_version_id, title, excerpt, excerpt_sha256, source_uri]
properties:
id: {type: string, format: uuid}
document_id: {type: string, format: uuid}
document_version_id: {type: string, format: uuid}
title: {type: string}
section: {type: [string, 'null']}
page_start: {type: [integer, 'null'], minimum: 1}
page_end: {type: [integer, 'null'], minimum: 1}
line_start: {type: [integer, 'null'], minimum: 1}
line_end: {type: [integer, 'null'], minimum: 1}
excerpt: {type: string}
excerpt_sha256: {type: string, pattern: '^[a-f0-9]{64}$'}
source_uri: {type: string, description: 'Stable internal locator ragcore://source/<source_id>.'}
source_id: {type: [string, 'null'], format: uuid, description: 'Canonical source the cited document belongs to.'}
source_name: {type: [string, 'null'], description: 'Registry name of the source (e.g. Uploads, Infra runbooks).'}
source_kind: {type: [string, 'null'], enum: [upload, folder, git, website, null]}
locator: {type: [string, 'null'], description: 'The item within the source (external id / path).'}
revision: {type: [string, 'null'], description: 'Connector revision of the cited version (e.g. a Git commit).'}
display: {type: string, description: 'Human-readable one-line provenance, e.g. "Network Design.pdf · p. 18 · Firewall Segmentation".'}
CitationResponse:
type: object
additionalProperties: false
required: [id, document_id, document_version_id, title, excerpt, excerpt_sha256, source_uri]
properties:
id: {type: string, format: uuid}
document_id: {type: string, format: uuid}
document_version_id: {type: string, format: uuid}
title: {type: string}
section: {type: [string, 'null']}
page_start: {type: [integer, 'null'], minimum: 1}
page_end: {type: [integer, 'null'], minimum: 1}
line_start: {type: [integer, 'null'], minimum: 1}
line_end: {type: [integer, 'null'], minimum: 1}
excerpt: {type: string}
excerpt_sha256: {type: string, pattern: '^[a-f0-9]{64}$'}
source_uri: {type: string, description: 'Stable internal locator ragcore://source/<source_id>.'}
source_id: {type: [string, 'null'], format: uuid, description: 'Canonical source the cited document belongs to.'}
source_name: {type: [string, 'null'], description: 'Registry name of the source (e.g. Uploads, Infra runbooks).'}
source_kind: {type: [string, 'null'], enum: [upload, folder, git, website, null]}
locator: {type: [string, 'null'], description: 'The item within the source (external id / path).'}
revision: {type: [string, 'null'], description: 'Connector revision of the cited version (e.g. a Git commit).'}
display: {type: string, description: 'Human-readable one-line provenance, e.g. "Network Design.pdf · p. 18 · Firewall Segmentation".'}
SearchResult:
type: object
required: [chunk_id, text, rank, citation]
properties:
chunk_id: {type: string, format: uuid}
parent_chunk_id: {type: [string, 'null'], format: uuid}
text: {type: string}
rank: {type: integer, minimum: 1}
scores:
type: object
properties:
dense: {type: [number, 'null']}
sparse: {type: [number, 'null']}
fused: {type: [number, 'null']}
rerank: {type: [number, 'null']}
citation: {$ref: '#/components/schemas/Citation'}
SearchResponse:
type: object
required: [retrieval_run_id, effective_space_ids, results, degraded]
properties:
retrieval_run_id: {type: string, format: uuid}
effective_space_ids: {type: array, items: {type: string, format: uuid}}
results: {type: array, items: {$ref: '#/components/schemas/SearchResult'}}
degraded: {type: boolean}
degradation_codes: {type: array, items: {type: string}}
timings_ms: {type: object, additionalProperties: {type: number, minimum: 0}}
ContextRequest:
type: object
additionalProperties: false
required: [query]
properties:
query: {type: string, minLength: 1, maxLength: 8000, pattern: '.*\S.*'}
requested_space_ids: {type: array, uniqueItems: true, maxItems: 50, items: {type: string, format: uuid}}
profile: {type: string, pattern: '^[a-z0-9][a-z0-9_-]{0,63}$', default: hybrid-v1}
max_results: {type: integer, minimum: 1, maximum: 50, default: 10}
filters: {$ref: '#/components/schemas/SearchFilter'}
include_debug: {type: boolean, default: false, description: Authorized Query Lab use only.}
max_context_tokens: {type: integer, minimum: 256, maximum: 64000, default: 8000}
ContextBlockResponse:
type: object
additionalProperties: false
required: [text, citation_ids, token_count]
properties:
text: {type: string}
citation_ids: {type: array, minItems: 1, items: {type: string, format: uuid}}
token_count: {type: integer, minimum: 1}
AnswerabilityStatus: {enum: [answerable, partially_answerable, not_answerable, conflicting_evidence]}
ContextResponse:
type: object
additionalProperties: false
required: [retrieval_run_id, answerability, token_count]
properties:
retrieval_run_id: {type: string, format: uuid}
answerability: {$ref: '#/components/schemas/AnswerabilityStatus'}
answerability_reasons: {type: array, items: {type: string}}
blocks: {type: array, items: {$ref: '#/components/schemas/ContextBlockResponse'}}
citations: {type: array, items: {$ref: '#/components/schemas/CitationResponse'}}
token_count: {type: integer, minimum: 0}
degraded: {type: boolean, default: false, description: 'True when retrieval fell back from its normal path (e.g. an unreachable reranker) but still produced these citations from a safe fallback ranking.'}
degradation_reasons: {type: array, items: {type: string}}
AnswerRequest:
allOf:
- {$ref: '#/components/schemas/ContextRequest'}
- type: object
properties:
generation_profile: {type: string, default: answer-local-balanced-v1}
stream: {type: boolean, default: false}
AnswerClaim:
type: object
required: [text, citation_ids]
properties:
text: {type: string}
citation_ids: {type: array, minItems: 1, items: {type: string, format: uuid}}
AnswerResponse:
type: object
required: [answer_id, retrieval_run_id, answerability, answer, citations, claims]
properties:
answer_id: {type: string, format: uuid}
retrieval_run_id: {type: string, format: uuid}
answerability: {$ref: '#/components/schemas/AnswerabilityStatus'}
answer: {type: string}
claims: {type: array, items: {$ref: '#/components/schemas/AnswerClaim'}}
citations: {type: array, items: {$ref: '#/components/schemas/Citation'}}
degraded: {type: boolean, default: false, description: 'True on a generation retry or when retrieval degraded (e.g. an unreachable reranker fell back to fused ranking) -- the answer and citations are still real, not fabricated.'}
degradation_reasons: {type: array, items: {type: string}}
FeedbackCreate:
type: object
additionalProperties: false
required: [target_type, target_id, rating]
properties:
target_type: {enum: [retrieval_run, answer]}
target_id: {type: string, format: uuid}
rating: {enum: [helpful, not_helpful, incorrect, incomplete, unsafe]}
comment: {type: [string, 'null'], maxLength: 4000}
Feedback:
allOf:
- {$ref: '#/components/schemas/FeedbackCreate'}
- type: object
required: [id, created_at]
properties:
id: {type: string, format: uuid}
created_at: {type: string, format: date-time}
+131
View File
@@ -0,0 +1,131 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ragcore.example.test/schemas/qdrant-payload-v1.json",
"title": "RAGCore Qdrant payload v1",
"type": "object",
"additionalProperties": false,
"required": [
"workspace_id",
"space_id",
"document_id",
"document_version_id",
"chunk_id",
"source_id",
"status",
"content_type",
"language",
"trust_level",
"text",
"content_sha256",
"embedding_profile",
"chunk_profile"
],
"properties": {
"workspace_id": {
"type": "string",
"format": "uuid"
},
"space_id": {
"type": "string",
"format": "uuid"
},
"document_id": {
"type": "string",
"format": "uuid"
},
"document_version_id": {
"type": "string",
"format": "uuid"
},
"chunk_id": {
"type": "string",
"format": "uuid"
},
"parent_chunk_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"source_id": {
"type": "string",
"format": "uuid"
},
"status": {
"const": "active"
},
"content_type": {
"type": "string",
"maxLength": 80
},
"language": {
"type": "string",
"maxLength": 16
},
"trust_level": {
"enum": [
"trusted_internal",
"trusted_external",
"unverified",
"user_uploaded",
"internet"
]
},
"title": {
"type": "string",
"maxLength": 500
},
"section_path": {
"type": "array",
"items": {
"type": "string"
}
},
"page_start": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"page_end": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"line_start": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"line_end": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"text": {
"type": "string"
},
"content_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"embedding_profile": {
"type": "string"
},
"chunk_profile": {
"type": "string"
},
"published_at": {
"type": "string",
"format": "date-time"
}
}
}