Keep deployment identity out of upstream

Site copy now resolves through CONTENT_DIR, alongside PARLIAMENT_CONFIG and
PROMPTS_DIR. All three let a deployment keep its own wording, branding and contact
details outside the repository, so they never appear in a diff against upstream —
which is what keeps a production fork mergeable.

parliament.yaml's site.title was "rixdagen.se". That is the deployment's identity,
not Sweden's data, so upstream now ships a neutral default. The Swedish user guide
keeps its content but loses the personal contact details and site-specific wording;
a deployment ships its own guide by pointing CONTENT_DIR at it.

Fixes /api/guide, which still read ../user-guide.md after that file moved to
content/sv/ and had been returning 404.

Deliberately kept: the author metadata in pyproject.toml and the copyright line in
LICENSE, which are attribution rather than configuration, and the README's note that
this runs in production as rixdagen.se, which is provenance worth stating.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
Lasse Edfast 1 week ago
parent ac181022e5
commit dbbc264a0a
  1. 5
      .env.example
  2. 13
      README.md
  3. 15
      backend/app.py
  4. 4
      bootstrap.py
  5. 10
      content/sv/user-guide.md
  6. 4
      docs/eval-harness.md
  7. 7
      parliament.py
  8. 11
      parliament.yaml

@ -60,10 +60,11 @@ AUTH_PRELOGIN_SECRET=
# download to reuse it rather than fetching again.
PLENUM_DATA_DIR=
# Use a parliament.yaml / prompt tree outside the repository, so a deployment's
# own values never show up in a diff against upstream.
# Point these outside the repository and a deployment's own values never show up
# in a diff against upstream — config, prompts and site copy all overridable.
# PARLIAMENT_CONFIG=/etc/plenum/parliament.yaml
# PROMPTS_DIR=/etc/plenum/prompts
# CONTENT_DIR=/etc/plenum/content
# Re-read prompt files on every call. Development only.
# PROMPTS_RELOAD=1

@ -101,11 +101,20 @@ deploy/examples/ systemd units and an nginx site, with placeholders
All settings are environment variables, documented in `.env.example`. Two paths let a
deployment keep its own values outside the repository entirely:
- `PARLIAMENT_CONFIG` — path to a `parliament.yaml` elsewhere on disk
- `PROMPTS_DIR` — path to a prompt tree elsewhere on disk
- `PARLIAMENT_CONFIG` — a `parliament.yaml` elsewhere on disk
- `PROMPTS_DIR` — a prompt tree elsewhere on disk
- `CONTENT_DIR` — site copy (explainer, guide) elsewhere on disk
Set `PROMPTS_RELOAD=1` in development to re-read prompt files on every call.
### Running a fork
If you maintain a deployment as a fork, keep it differing from upstream only in
files upstream does not have. Everything branded or private is either an env-var
pointer (the three above), an untracked file (`.env`, `providers.yaml`), or an
addition under `deploy/prod/`. `make check-fork-divergence` fails the build if
anything else drifts, which turns a merge conflict into a caught mistake.
## Security
The `database_query` tool executes model-authored SQL. Give it a database role with

@ -110,12 +110,15 @@ def _reap_abandoned_jobs() -> None:
@app.get("/api/guide", response_class=PlainTextResponse)
def get_guide() -> str:
"""Serve user-guide.md as plain text. Single source of truth for all guide links."""
guide_path = os.path.join(os.path.dirname(__file__), "..", "user-guide.md")
if not os.path.exists(guide_path):
raise HTTPException(status_code=404, detail="Guide not found")
with open(guide_path, encoding="utf-8") as f:
return f.read()
"""Serve the user guide as plain text — the single source for all guide links.
Resolved through CONTENT_DIR, so a deployment can ship its own guide without
editing the repository.
"""
guide = PARLIAMENT.read_content("guide_file")
if not guide:
raise HTTPException(status_code=404, detail="No guide configured")
return guide
@app.get("/api/meta")

@ -5,8 +5,8 @@ the repository root is not on ``sys.path`` and relative paths would resolve agai
whatever directory the caller happened to be in. Importing this module fixes both,
deriving the root from this file's own location.
The predecessor hardcoded ``/home/lasse/riksdagen`` in 22 files, which is why the
project could only ever run on one machine.
The predecessor hardcoded one absolute path in 22 files, which is why it could
only ever run on a single machine.
"""
from __future__ import annotations

@ -4,11 +4,11 @@ Den här guiden förklarar hur Riksdagen-appen är byggd, vilka val som gjorts,
---
## Om rixdagen.se
## Om den här sajten
Sajten är utvecklad av [Lasse Edfast](https://lasseedfast.se), hittills utan ekonomiskt stöd. Den är gratis att använda och ingen information sparas utöver de sju dagarna som en chatt sparas i.
Sajten drivs utan ekonomiskt stöd. Den är gratis att använda och ingen information sparas utöver de sju dagarna som en chatt sparas i.
Vill du vara med och utveckla, eller bara har frågor, kontakta gärna Lasse via [e-post](mailto:lasse@edfast.se).
Vill du vara med och utveckla, eller bara har frågor, hör gärna av dig.
---
@ -207,11 +207,11 @@ En API-nyckel är ett lösenord som identifierar dig mot en AI-leverantörs serv
- **OpenRouter** – [openrouter.ai](https://openrouter.ai) (aggregator med tillgång till hundratals modeller)
- **OpenAI** – [platform.openai.com](https://platform.openai.com) (samma modeller som driver ChatGPT)
Användning av externa modeller kostar pengar och debiteras direkt från ditt konto hos leverantören – inte via rixdagen.se.
Användning av externa modeller kostar pengar och debiteras direkt från ditt konto hos leverantören – inte via den här sajten.
### Hur nyckeln lagras
Din nyckel sparas **enbart lokalt i din webbläsare** (`localStorage`). Den skickas till rixdagen.se:s server bara för att vidarebefordras till leverantören när ett anrop görs – den loggas inte, sparas inte i databasen och syns aldrig i någon annan användares session.
Din nyckel sparas **enbart lokalt i din webbläsare** (`localStorage`). Den skickas till sajtens server bara för att vidarebefordras till leverantören när ett anrop görs – den loggas inte, sparas inte i databasen och syns aldrig i någon annan användares session.
Konkret:
- Nyckeln ligger kvar i webbläsaren tills du rensar den (knappen ✕ i inställningspanelen) eller tömmer webbläsarens lagringsdata.

@ -97,12 +97,12 @@ via sigmoid so 0.5 = neutral, >0.7 = likely grounded, <0.3 = likely hallucinated
`NULL` means the scorer endpoint was unreachable when the judgment was recorded.
Start the scorer (first run downloads the model to `/home/lasse/models`):
Start the scorer (first run downloads the model to `$HOME/models`):
```bash
nohup vllm serve BAAI/bge-reranker-v2-m3 \
--port 8001 \
--download-dir /home/lasse/models \
--download-dir $HOME/models \
--gpu-memory-utilization 0.2 \
--max-model-len 8192 \
--trust-remote-code \

@ -24,6 +24,11 @@ import yaml
_ROOT = Path(__file__).resolve().parent
# Site copy — the explainer, the limit warning, the user guide. Overridable so a
# deployment's own wording lives outside the repository, the same way PROMPTS_DIR
# works for prompts.
CONTENT_DIR = Path(os.environ.get("CONTENT_DIR") or _ROOT / "content")
# A Postgres text-search configuration name. It is interpolated into SQL rather
# than passed as a parameter (identifiers cannot be bound), so it is validated
# on load and never trusted from arbitrary input.
@ -129,7 +134,7 @@ class Parliament:
rel = self.site.get(key)
if not rel:
return ""
path = _ROOT / rel
path = CONTENT_DIR / rel
return path.read_text(encoding="utf-8") if path.exists() else ""
# -- serialisation ------------------------------------------------------

@ -174,13 +174,18 @@ embeddings:
# Served to the frontend via GET /api/meta.
site:
title: rixdagen.se
# Deployment identity, not country data. Upstream ships neutral values; a
# deployment overrides them in its own PARLIAMENT_CONFIG file so its branding
# never appears in a diff against upstream.
title: plenum
tagline: "Sök i riksdagens anföranden och motioner"
# Markdown, so a deployment can rewrite its own copy without touching source.
explainer_file: content/sv/explainer.md
limit_warning_file: content/sv/limit_warning.md
# Paths are relative to CONTENT_DIR (default: ./content).
explainer_file: sv/explainer.md
limit_warning_file: sv/limit_warning.md
# Upstream ships these empty. A deployment fills them in via its own
# PARLIAMENT_CONFIG file, which keeps personal contact details out of the repo.
guide_file: sv/user-guide.md
contact:
email: null
url: null

Loading…
Cancel
Save