first commit
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
# Relata
|
||||
|
||||
A self-organising semantic document database.
|
||||
|
||||
Relata accepts unstructured and semi-structured information, analyses it, discovers
|
||||
evidence-backed relationships between stored items, and lets you search associatively —
|
||||
returning an explanation of *why* each result came back.
|
||||
|
||||
The central idea: **store arbitrary information, discover evidence-backed relationships
|
||||
between items, and retrieve related knowledge with an explanation of why each result was
|
||||
returned.**
|
||||
|
||||
---
|
||||
|
||||
## What Relata does
|
||||
|
||||
- **Ingests** plain text, Markdown, JSON, PDF and DOCX — pasted, uploaded, or posted to a
|
||||
JSON API. The original is always preserved verbatim.
|
||||
- **Extracts** text while keeping headings, paragraphs, page numbers and document sections.
|
||||
- **Chunks** documents along structural boundaries rather than at arbitrary character counts.
|
||||
- **Embeds** chunks with a local model and stores the vectors in Qdrant.
|
||||
- **Extracts entities and claims** in two stages: deterministic rules first, then
|
||||
model-assisted extraction for broader concepts. Every record says which produced it.
|
||||
- **Discovers candidate relationships** between items from a bounded candidate set, and
|
||||
scores them from named, inspectable signals.
|
||||
- **Searches** with hybrid retrieval — exact matching, MongoDB text search, vector
|
||||
similarity, entity matching and relationship expansion — and explains every result.
|
||||
- **Answers questions** from stored material only, with citations, an explicit
|
||||
"Sources used" section, and an "Uncertainty and conflicts" section when sources disagree.
|
||||
- **Keeps humans in charge**: every inferred relationship is a proposal until someone
|
||||
confirms it, and rejecting one never deletes the evidence behind it.
|
||||
|
||||
## What Relata does not do
|
||||
|
||||
These are deliberate limits, not oversights.
|
||||
|
||||
- **It does not decide what is true.** A claim records that a source *said* something.
|
||||
When two sources disagree, Relata shows both and says they conflict; it never picks a winner.
|
||||
- **It does not treat similarity as a relationship.** Two documents can be near-identical in
|
||||
wording and describe entirely different events. Semantic similarity alone tops out well
|
||||
below the threshold at which a link looks well-evidenced.
|
||||
- **It does not produce calibrated probabilities.** A relationship's strength is a weighted
|
||||
sum of explicit signals under a versioned scoring model. It is a heuristic strength, and
|
||||
the interface says so wherever it appears.
|
||||
- **It does not merge entities on name similarity.** "Example Ltd" and "Example Holdings Ltd"
|
||||
stay separate. Similar names become *candidate aliases* for a person to confirm — and even a
|
||||
confirmed alias does not merge the records; it becomes one weighted signal among many.
|
||||
- **It does not ask a language model for a score.** A model may classify a relationship type
|
||||
or describe a connection; that contributes exactly one signal among many.
|
||||
- **It does not OCR.** A scanned PDF with no text layer is stored and preserved, and Relata
|
||||
tells you its contents are not searchable.
|
||||
- **It does not accept archives** (zip, tar, gz, rar, 7z) or executables.
|
||||
- **It does not fetch URLs found inside documents.** Nothing in a stored document can cause
|
||||
Relata to make an outbound request.
|
||||
- **It is not multi-tenant yet.** Every record carries a workspace ID and every query filters
|
||||
on it, so the boundary is enforced from day one — but there is one workspace in practice.
|
||||
|
||||
---
|
||||
|
||||
## Architecture overview
|
||||
|
||||
A modular monolith with in-process background workers. One binary, three data stores.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
browser ────────▶│ HTTP: server-rendered HTML + /api/v1 │
|
||||
API client ─────▶│ auth · CSRF · CSP · rate limits │
|
||||
└───────────────┬──────────────────────────┘
|
||||
│
|
||||
┌───────────────▼──────────────────────────┐
|
||||
│ Services: ingest · search · relationships│
|
||||
│ entities · analysis │
|
||||
└───────────────┬──────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
│ MongoDB │ │ Qdrant │ │ Ollama │
|
||||
│ authoritative: │ │ vectors + minimal│ │ embeddings + │
|
||||
│ items, chunks, │ │ retrieval payload│ │ analysis │
|
||||
│ entities, claims,│ │ │ │ (replaceable) │
|
||||
│ relationships, │ │ │ │ │
|
||||
│ jobs, audit │ │ │ │ │
|
||||
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
||||
▲
|
||||
│
|
||||
┌─────────┴──────────────────────────────────────────────────┐
|
||||
│ Background workers: leased job queue in MongoDB │
|
||||
│ parse → chunk → embed → entities → claims → relationships │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
`internal/domain` holds the types and port interfaces and imports only the standard library.
|
||||
Storage and AI adapters implement those ports; the service layer never sees a MongoDB, Qdrant
|
||||
or HTTP type. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.26.5 or newer** — earlier 1.26 patches carry an `html/template` XSS fix Relata needs
|
||||
- **Docker** and the Compose plugin, for MongoDB, Qdrant and Ollama
|
||||
- **~8 GB free disk** for the Ollama models
|
||||
- Linux or macOS. Windows via WSL2.
|
||||
|
||||
Ollama can run either in Compose or natively on the host — Relata only needs an HTTP endpoint.
|
||||
|
||||
---
|
||||
|
||||
## Exact local startup
|
||||
|
||||
```bash
|
||||
# 1. Fetch dependencies and create .env from the example
|
||||
make setup
|
||||
|
||||
# 2. Start MongoDB, Qdrant and Ollama
|
||||
make compose-up
|
||||
|
||||
# 3. Pull the models (a few GB, once)
|
||||
make models
|
||||
|
||||
# 4. Create the administrator account (prompts for a password, no echo)
|
||||
make createadmin
|
||||
|
||||
# 5. Run Relata
|
||||
make dev
|
||||
```
|
||||
|
||||
Then open **http://127.0.0.1:8080** and sign in.
|
||||
|
||||
To confirm everything is healthy before signing in:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8080/health/ready | jq
|
||||
```
|
||||
|
||||
`mongodb` and `qdrant` must report `ok`. The AI providers are reported but not required —
|
||||
ingestion, keyword search and browsing all work without them.
|
||||
|
||||
### Initial administrator creation
|
||||
|
||||
```bash
|
||||
relata createadmin --username admin
|
||||
```
|
||||
|
||||
You are prompted for a password twice, without echo. The minimum length is 12 characters;
|
||||
length is what matters, so a passphrase is fine. The password is hashed with Argon2id
|
||||
(64 MiB, 3 passes) and the plain text is never stored, logged or echoed.
|
||||
|
||||
For unattended setup, set `RELATA_ADMIN_PASSWORD` in the environment instead. The password
|
||||
is deliberately **not** accepted as a command-line flag, because arguments are visible in
|
||||
the process list and shell history.
|
||||
|
||||
### Signing in through an identity provider
|
||||
|
||||
Relata can sit behind OAuth 2.0 / OpenID Connect. The redirect URI to register is derived from
|
||||
`RELATA_BASE_URL` and a fixed path:
|
||||
|
||||
```
|
||||
<RELATA_BASE_URL>/auth/oauth/callback
|
||||
```
|
||||
|
||||
The path is not configurable, so what you register and what Relata serves cannot drift apart.
|
||||
|
||||
```bash
|
||||
RELATA_OAUTH_ENABLED=true
|
||||
RELATA_OAUTH_PROVIDER_NAME=Blackwall
|
||||
RELATA_OAUTH_ISSUER=https://www.blackwall.cx # or the three endpoints directly
|
||||
RELATA_OAUTH_CLIENT_ID=...
|
||||
RELATA_OAUTH_CLIENT_SECRET=...
|
||||
```
|
||||
|
||||
Each person who signs in gets a **private workspace of their own** and is admin of it. Nobody
|
||||
sees anyone else's documents — which also means relationship discovery never spans users. That
|
||||
trade-off is the point of [ADR 8](docs/decisions/0008-federated-identity-private-workspaces.md).
|
||||
|
||||
Local password login switches itself off once OAuth is configured, and back on if it is not.
|
||||
Set `RELATA_LOCAL_LOGIN_ENABLED=true` to keep it as a way back in when the provider is broken;
|
||||
`relata createadmin` works regardless. A federated account has no password and can never sign
|
||||
in locally.
|
||||
|
||||
Startup refuses a configuration with no way in at all, and refuses non-HTTPS provider endpoints
|
||||
outside development.
|
||||
|
||||
Full detail: [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md).
|
||||
|
||||
### API tokens
|
||||
|
||||
Programs authenticate with bearer tokens created in **Settings → API tokens**, carrying any of
|
||||
`ingest`, `query` and `review`:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $RELATA_TOKEN" \
|
||||
'http://127.0.0.1:8080/api/v1/search?q=delivery+schedule'
|
||||
```
|
||||
|
||||
A token is shown once — only its hash is stored — never carries more than its owner's current
|
||||
role, cannot manage tokens, and cannot reach the web interface.
|
||||
|
||||
### Ollama model setup
|
||||
|
||||
Relata uses two models, both replaceable:
|
||||
|
||||
| Purpose | Default | Why |
|
||||
|---|---|---|
|
||||
| Embeddings | `nomic-embed-text` (768 dims) | Small, fast, good retrieval quality |
|
||||
| Analysis | `llama3.1:8b` | Entity/claim extraction, relationship classification, answers |
|
||||
|
||||
```bash
|
||||
ollama pull nomic-embed-text
|
||||
ollama pull llama3.1:8b
|
||||
```
|
||||
|
||||
On a CPU-only machine, a smaller analysis model is much more comfortable:
|
||||
|
||||
```bash
|
||||
ollama pull llama3.2:3b
|
||||
# then in .env:
|
||||
RELATA_ANALYSIS_MODEL=llama3.2:3b
|
||||
```
|
||||
|
||||
To run **without** a language model entirely, set `RELATA_ANALYSIS_ENABLED=false`. Ingestion,
|
||||
chunking, embedding, deterministic entity and claim extraction, relationship scoring and
|
||||
search all continue to work; model-assisted extraction and generated answers are disabled.
|
||||
|
||||
Changing the embedding model changes the vector geometry. Relata detects a dimension mismatch
|
||||
and refuses to write mixed-width vectors. After such a change, set
|
||||
`RELATA_EMBEDDING_DIMENSIONS` to match and re-embed the workspace from **Settings →
|
||||
Re-embed workspace**.
|
||||
|
||||
---
|
||||
|
||||
## Demonstration dataset
|
||||
|
||||
```bash
|
||||
make seed # load and return
|
||||
make seed-wait # load and wait for processing to finish
|
||||
```
|
||||
|
||||
Eleven documents chosen so that the difference between *similarity* and *evidence* is visible:
|
||||
|
||||
| Document | What it demonstrates |
|
||||
|---|---|
|
||||
| Atlas delivery schedule (original) | Related to, and superseded by, the revision |
|
||||
| Atlas revised delivery schedule | Updates the original; shares entities and subject |
|
||||
| Atlas invoice reconciliation | **Contradicts** the original about the project cost |
|
||||
| Returns policy v3 / v2 | An update that **supersedes** older information |
|
||||
| Northfield fire drill / safety inspection | Superficially similar, **different events** |
|
||||
| Example Ltd sponsorship announcement | Shares an entity but **not** a subject |
|
||||
| Atlas revised delivery — forwarded copy | A **likely duplicate**, flagged not merged |
|
||||
| Office plant care rota | **Unrelated** to everything |
|
||||
| Atlas shipment record (JSON) | Structured data sharing entities and figures |
|
||||
|
||||
Demonstration searches:
|
||||
|
||||
- *What information relates to the revised delivery date?*
|
||||
- *Which sources disagree about the project cost?*
|
||||
- *Show information associated with Example Ltd.*
|
||||
- *What replaced the original policy?*
|
||||
- *Find items similar in meaning but not actually about the same event.*
|
||||
|
||||
---
|
||||
|
||||
## Example API requests
|
||||
|
||||
The API uses the same session cookie as the browser, and enforces CSRF on state-changing
|
||||
requests. Sign in first and keep the cookie jar.
|
||||
|
||||
```bash
|
||||
BASE=http://127.0.0.1:8080
|
||||
|
||||
# Sign in (the login form's own CSRF token comes from the page)
|
||||
TOKEN=$(curl -s -c cj.txt $BASE/login \
|
||||
| grep -o 'name="csrf_token" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
|
||||
|
||||
curl -s -b cj.txt -c cj.txt -X POST $BASE/login \
|
||||
--data-urlencode "username=admin" \
|
||||
--data-urlencode "password=YOUR-PASSWORD" \
|
||||
--data-urlencode "csrf_token=$TOKEN"
|
||||
|
||||
# The session's CSRF token, for subsequent writes
|
||||
CSRF=$(curl -s -b cj.txt $BASE/ \
|
||||
| grep -o 'name="csrf_token" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
|
||||
```
|
||||
|
||||
**Store a note**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt -X POST $BASE/api/v1/items \
|
||||
-H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \
|
||||
-d '{
|
||||
"title": "Delivery update",
|
||||
"content": "The revised delivery date is 14 March 2025. Example Ltd will ship 240 units.",
|
||||
"source_type": "note",
|
||||
"metadata": {"project": "atlas"},
|
||||
"created_at": "2025-02-28T09:00:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
Returns `202 Accepted` with the item and the ID of the job that will process it.
|
||||
Identical content returns `409` with `existing_item_id`; add `"allow_duplicate": true` to
|
||||
store it anyway.
|
||||
|
||||
**Upload a document**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt -X POST $BASE/api/v1/items/upload \
|
||||
-H "X-CSRF-Token: $CSRF" \
|
||||
-F 'file=@contract.pdf' \
|
||||
-F 'title=Supply contract' \
|
||||
-F 'metadata[project]=atlas'
|
||||
```
|
||||
|
||||
**Search**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt \
|
||||
"$BASE/api/v1/search?q=revised+delivery+date&limit=5" | jq '.search.results[] | {
|
||||
title: .item.title,
|
||||
query_relevance,
|
||||
semantic_similarity,
|
||||
why: .why_matched
|
||||
}'
|
||||
```
|
||||
|
||||
**Ask a question, answered only from stored material**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt -X POST $BASE/api/v1/search/answer \
|
||||
-H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \
|
||||
-d '{"question": "Which sources disagree about the project cost?"}' | jq
|
||||
```
|
||||
|
||||
**Confirm that two names refer to the same thing**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt -X POST "$BASE/api/v1/aliases/$ALIAS_ID/confirm" -H "X-CSRF-Token: $CSRF"
|
||||
```
|
||||
|
||||
Candidate aliases appear on an entity's page. Confirming one records a human judgement that
|
||||
becomes evidence during relationship discovery; the two entities remain separate records.
|
||||
|
||||
**Inspect and review a relationship**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt "$BASE/api/v1/items/$ITEM_ID/relationships" | jq '.outgoing[0].explanation'
|
||||
|
||||
curl -s -b cj.txt -X POST "$BASE/api/v1/relationships/$REL_ID/confirm" \
|
||||
-H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \
|
||||
-d '{"note": "Checked against the signed contract."}'
|
||||
```
|
||||
|
||||
**Preview a deletion before committing**
|
||||
|
||||
```bash
|
||||
curl -s -b cj.txt -X DELETE -H "X-CSRF-Token: $CSRF" \
|
||||
"$BASE/api/v1/items/$ITEM_ID?dry_run=true" | jq .plan
|
||||
```
|
||||
|
||||
The full specification is in [docs/openapi.yaml](docs/openapi.yaml).
|
||||
|
||||
---
|
||||
|
||||
## Running the tests
|
||||
|
||||
```bash
|
||||
make test # unit tests with the race detector
|
||||
make test-integration # against disposable MongoDB and Qdrant containers
|
||||
make test-security # security regression tests
|
||||
make fuzz # short run over all 15 fuzz targets: parsers, model output,
|
||||
# API decoding, filenames, entity and claim extraction, scoring
|
||||
make lint # gofmt check, go vet, staticcheck
|
||||
make security # govulncheck + security tests + secret check
|
||||
make check # everything CI runs
|
||||
```
|
||||
|
||||
Some of those tests exercise concurrency rather than behaviour, because two of the defects
|
||||
found in this codebase were only reachable under it: duplicate detection is a read followed by
|
||||
a write, and asserting a relationship used to page through a node's links. Both are now
|
||||
enforced by unique indexes and covered by `tests/race_test.go`.
|
||||
|
||||
Integration tests need Docker. They start their own containers, use a database named after
|
||||
the run, and clean up afterwards. To point them at instances you already have:
|
||||
|
||||
```bash
|
||||
RELATA_TEST_MONGO_URI=mongodb://127.0.0.1:27017 \
|
||||
RELATA_TEST_QDRANT_URL=http://127.0.0.1:6333 \
|
||||
go test -tags=integration ./tests/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resetting the local environment
|
||||
|
||||
```bash
|
||||
make backup # capture MongoDB and the uploaded originals first
|
||||
make reset # asks for confirmation, then removes all volumes and uploads
|
||||
```
|
||||
|
||||
Or piecemeal:
|
||||
|
||||
```bash
|
||||
docker compose down -v # drop MongoDB and Qdrant data
|
||||
rm -rf data/uploads/* # drop stored original files
|
||||
make createadmin # create an account again
|
||||
```
|
||||
|
||||
To clear only the vectors and rebuild them, use **Settings → Re-embed workspace**.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Every setting is an environment variable, documented in [`.env.example`](.env.example), which
|
||||
`make setup` copies to `.env`. Real environment variables take precedence over the file.
|
||||
|
||||
The settings you are most likely to change:
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `RELATA_HTTP_ADDR` | `127.0.0.1:8080` | Non-loopback requires `RELATA_ALLOW_EXTERNAL_BIND=true` |
|
||||
| `RELATA_EMBEDDING_MODEL` | `nomic-embed-text` | Changing it requires re-embedding |
|
||||
| `RELATA_ANALYSIS_MODEL` | `llama3.1:8b` | Use `llama3.2:3b` on CPU-only machines |
|
||||
| `RELATA_ANALYSIS_ENABLED` | `true` | `false` runs the deterministic pipeline only |
|
||||
| `RELATA_WORKER_COUNT` | `3` | Background workers |
|
||||
| `RELATA_AI_CONCURRENCY` | `2` | Concurrent model calls, independent of workers |
|
||||
| `RELATA_REL_STORE_SCORE` | `0.25` | Strength below which a candidate is discarded |
|
||||
| `RELATA_MAX_UPLOAD_BYTES` | `26214400` | 25 MB |
|
||||
|
||||
Configuration is validated at startup: Relata refuses to start with a clear list of every
|
||||
problem rather than failing later inside a request.
|
||||
|
||||
**`.env` must never be committed.** It is in `.gitignore`, and `make security` fails if it
|
||||
is tracked.
|
||||
|
||||
---
|
||||
|
||||
## Known limitations
|
||||
|
||||
Stated plainly, because a system that discovers relationships should be honest about its own.
|
||||
|
||||
**Relationship scoring**
|
||||
- Scores are **heuristic strengths, not calibrated probabilities**. The weights in
|
||||
`relationship-score-v1` were chosen by judgement, not fitted to labelled data. A 0.7 does
|
||||
not mean "70% likely to be a real relationship".
|
||||
- Changing weights changes every future score. Existing relationships keep the version that
|
||||
produced them; they are not recomputed automatically.
|
||||
- Human confirmations and rejections influence ranking and expansion. They do **not** retrain
|
||||
any model, and Relata never sends your decisions anywhere.
|
||||
|
||||
**Extraction quality**
|
||||
- Deterministic entity extraction is intentionally narrow: organisations with a legal suffix,
|
||||
people with an honorific, email addresses and dates. It will miss a person named without a
|
||||
title. Broader coverage comes from the model stage and is labelled as such.
|
||||
- Deterministic claim extraction only picks up sentences carrying a figure or a date, because
|
||||
those are the statements that can actually agree or conflict. Prose claims need the model.
|
||||
- Contradiction detection compares figures and dates about the same coarse subject. It will
|
||||
not catch a contradiction expressed entirely in prose.
|
||||
- Model output quality varies considerably with model size. A 3B model produces noticeably
|
||||
weaker entity and relationship classification than an 8B one, and its generated answers are
|
||||
often unusable — though the safeguards still hold: an uncited answer is downgraded to
|
||||
"not sufficient", and recorded contradictions are surfaced regardless of what the model says.
|
||||
A model that answers with the schema itself (`llama3.2:3b` returned `{"answer": "sufficient"}`)
|
||||
is rejected as `degenerate_content` rather than displayed; the request still returns the
|
||||
retrieved sources and any recorded contradictions, since neither depends on the model. That
|
||||
check catches obvious emptiness only — it cannot distinguish a weak answer from a good one,
|
||||
so a small model still produces answers not worth reading, it just no longer produces
|
||||
answers that are not answers.
|
||||
- **Answer generation on CPU is slow.** With `llama3.1:8b`, three sources take about 33
|
||||
seconds. The defaults (six passages, a 300-second model timeout) are set to complete on a
|
||||
CPU-only machine; exceeding them returns a 504 that names the remedy. A GPU changes this by
|
||||
an order of magnitude. See [docs/OPERATIONS.md](docs/OPERATIONS.md#answer-generation-times-out).
|
||||
|
||||
**Search**
|
||||
- Query relevance is a fused rank score normalised against the best result in the same
|
||||
response. It is comparable *within* one result set, not across queries.
|
||||
- MongoDB text search is English-stemmed by default. Other languages match less well.
|
||||
- Relationship expansion only follows links from the strongest matches, bounded by
|
||||
`RELATA_SEARCH_MAX_EXPANDED_ITEMS`.
|
||||
|
||||
**Scale**
|
||||
- Designed for a personal or small-team corpus — thousands to low tens of thousands of items.
|
||||
Alias proposal compares new entities against up to 200 existing ones of the same type per
|
||||
run, which is bounded but not free at large entity counts.
|
||||
- Relationship discovery indexes at most 500 **confirmed** aliases per workspace. Beyond that,
|
||||
further confirmed aliases stop contributing their signal; discovery weakens rather than
|
||||
breaking, and a warning is logged rather than the loss happening silently.
|
||||
- One binary, deliberately — no sharding, no coordination layer, no service mesh. Running
|
||||
more than one instance against the same MongoDB does work: the job queue leases rather than
|
||||
locks, so instances share the workload and an instance that dies has its jobs requeued when
|
||||
the lease expires. That was verified with two instances, but it is a property of the queue
|
||||
design rather than a supported deployment topology, and nothing else is coordinated.
|
||||
|
||||
**Formats**
|
||||
- No OCR. Scanned PDFs are preserved and flagged as having little extractable text.
|
||||
- PDF extraction handles ordinary text layouts; complex multi-column layouts and tables may
|
||||
come out in an awkward order.
|
||||
- DOCX extraction reads paragraph text and heading styles. Tables, footnotes, comments and
|
||||
tracked changes are not extracted.
|
||||
|
||||
**Authentication**
|
||||
- **No refresh tokens.** The provider's access token establishes identity once and is then
|
||||
discarded rather than stored. A revoked upstream account keeps its Relata session until that
|
||||
session expires (idle 12h, absolute 7 days by default); revoke the Relata session too if that
|
||||
matters.
|
||||
- **No group or role mapping.** Every federated user is admin of their own workspace. There is
|
||||
nothing to map a provider group onto, because there is no shared workspace to have roles in.
|
||||
- **No account linking.** A local account and a federated account with the same email address
|
||||
are two separate people with two separate workspaces. Merging them is deliberate manual work,
|
||||
not something to infer from a matching address.
|
||||
- **Token scopes are coarse** — three of them, checked per route, with no per-item restriction.
|
||||
- Creating a token through the interface puts its value in the browser's history for one page
|
||||
load; the page says so. `POST /api/v1/tokens` returns it in a response body instead.
|
||||
|
||||
**Security**
|
||||
- Prompt-injection defences (delimited untrusted blocks, an unforgeable fence, strict output
|
||||
schemas, no tools available to the model) make injection much harder and bound its effect.
|
||||
They are not a proof. Treat generated answers as summaries of sources, not as authority.
|
||||
- Relata trusts its operator. There is no protection against a malicious administrator.
|
||||
- Sessions are stored server-side and revocable, but there is no device management, no
|
||||
two-factor authentication, and no password reset flow — recreate the account instead.
|
||||
|
||||
The threat model, including what is explicitly out of scope, is in
|
||||
[docs/THREAT_MODEL.md](docs/THREAT_MODEL.md).
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Contents |
|
||||
|---|---|
|
||||
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Package layout, dependency rules, data model |
|
||||
| [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) | Trust boundaries, defences, known limitations |
|
||||
| [docs/SEARCH_AND_SCORING.md](docs/SEARCH_AND_SCORING.md) | The three scores and how each is computed |
|
||||
| [docs/INGESTION_PIPELINE.md](docs/INGESTION_PIPELINE.md) | Stage by stage, with failure behaviour |
|
||||
| [docs/AI_PROVIDER_INTERFACE.md](docs/AI_PROVIDER_INTERFACE.md) | Adding a provider without touching the pipeline |
|
||||
| [docs/BACKUP_AND_RESTORE.md](docs/BACKUP_AND_RESTORE.md) | What to back up and how to restore it |
|
||||
| [docs/OPERATIONS.md](docs/OPERATIONS.md) | Running it: metrics, logs, common problems |
|
||||
| [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md) | The plan this was built to |
|
||||
| [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md) | Sign-in, workspaces and API tokens |
|
||||
| [docs/decisions/](docs/decisions/) | Architectural decision records |
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
|
||||
Provided as-is for evaluation.
|
||||
Reference in New Issue
Block a user