Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e
This commit is contained in:
commit
7ca726caf2
659 files changed
+98171
No files matched your search
@@ -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 |
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user