This project moved to Postgres some time ago, but ~380 lines of SQL-to-AQL
translation and AQL syntax validation were still sitting in utils.py, reachable
from nothing. utils.py is now just TextChunker, which is the only part anything
imports. Also drops a stray prompt draft that had been left in backend/services/,
and the _normalize_arango_search_args name, which never touched Arango.
Worth knowing separately: the Arango server is still running and still
authenticating, so the riksdagsgruppen account is a live door into a system nothing
here uses any more. Deleting that account is simpler than rotating its password.
The env_manager account is a different one and other projects still depend on it.
.gitignore now excludes .claude/ entirely rather than just settings.local.json.
settings.json can carry credentials inside allowlisted command strings, which is
exactly how a live database password ended up committed to the predecessor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
database_query passed model-written SQL straight to a connection that commits. The
corpus reaches the model's context, and in a parliament anyone able to speak can get
text into the corpus, so that SQL has to be treated as untrusted input.
Two layers now apply. Statements must begin with SELECT or WITH, and multi-statement
payloads are rejected — that is how a write gets smuggled in behind a leading SELECT.
Then the query runs inside SET TRANSACTION READ ONLY, so PostgreSQL rejects writes
itself even if the first check is wrong. Verified both independently: five attack
shapes refused by the guard, and INSERT/DROP still refused when the guard is bypassed
entirely and the database layer is called directly.
share_insight gets the same treatment. It re-executes SQL stored in saved
conversations, which is no more trustworthy than freshly generated SQL.
Neither layer replaces running against a SELECT-only role, and SECURITY.md still says
so — it now describes what the application does rather than what it fails to do.
Tool rename completed as a clean break, no aliases, since there are three users and
six sessions to preserve: arango_search -> search_speeches (it has queried Postgres
since the Arango migration and the name was pure debt), search_motions ->
search_documents, fetch_motion -> fetch_document.
Also renamed fetch_documents -> fetch_speeches. It fetches speeches despite the name,
and sitting next to the new fetch_document it was a trap the model would fall into.
Note for anyone with an MCP client configured against the old names: riksdagen_mcp
exposes these, so the names it advertises have changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Everything that made this Sweden-only in code now lives in one configuration file:
party list and colours, chamber activity types, the Postgres text-search
dictionary, the party-code pattern used to verify attributions, identifier shapes,
source URLs, the person-photo URL template, embedding model and dimension, and the
site copy.
info.py is deleted. Its party_colors_lighten table is gone too — the tint is now
computed from the party colour, so there is no second palette to keep in sync. Its
dead select_columns and css strings go with it.
The stylesheet no longer carries per-party rules. It had --party-M through
--party-NYD plus twenty [data-party="..."] selectors, which no amount of
configuration could adapt: CSS cannot read a dict. App.tsx now publishes the
configured palette as custom properties from /api/meta, and one color-mix rule
covers every party in any country.
The author's email and Twitter handle are out of the source entirely. Explainer
copy moved to content/sv/*.md, and contact details come from `site.contact`, which
upstream ships empty for a deployment to fill in via its own PARLIAMENT_CONFIG.
Two startup assertions added, both for failures that are otherwise silent: a
text-search config mismatch returns near-zero rows with no error, and a vector
column that disagrees with embeddings.dimension fails deep inside pgvector with a
message that never mentions configuration.
Verified: five searches spanning phrase, exclusion and prefix syntax — exercising
all three tsquery builders that changed — return byte-identical payloads against
production, with differing hit counts and first hits proving the queries really
differ. Frontend builds, and tsc reports the same 9 pre-existing errors as before,
none new.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The project could only run from /home/lasse/riksdagen: 22 files hardcoded that
path, mostly as an `os.chdir(...)` + `sys.path.append(...)` prelude. Those now
derive the root from the file's own location via bootstrap.py, and bulk data
resolves against PLENUM_DATA_DIR.
requirements.txt was from March 2023 and listed Streamlit, altair and pydeck while
omitting fastapi, uvicorn, psycopg2 and pgvector. Replaced with a pyproject.toml
derived from the actual import graph, plus ruff and pytest config.
Postgres opened its connection pool in __init__, so `import backend.app` failed
outright without a reachable database — breaking test collection and any tooling
that merely imports the app. The pool is now opened on first use behind a lock.
systemd units and an nginx site move to deploy/examples/ with __PROJECT_ROOT__ and
__DOMAIN__ placeholders. Real values belong in the deployment's own deploy/prod/,
which is a path upstream never writes to, so the two cannot conflict on merge.
Removed: scripts/migrate_keys.py (an already-executed Arango migration referenced
nowhere) and a dead `import talks2db` in download_talks.py that pointed at a file
excluded from this repo.
Verified: `import backend.app` now succeeds with neither a database nor any network,
and all rewritten scripts import from a foreign working directory with their module
docstrings intact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seeded via an explicit allow-list (see /home/lasse/plenum-seed.sh) rather than by
deleting files from a copy, so nothing sensitive can survive by omission.
Excluded: WireGuard backup + client config, the plaintext DB password in admin.py,
Arango credentials in scripts/notes.md, .claude/settings.json, a 113 MB log,
providers.yaml (private endpoint), the Arango/ChromaDB-era scripts, the duplicated
claude-design-system frontend copy, and assorted screenshots and one-off planning docs.
297 tracked files / 49 MB of history -> 161 files / 2.1 MB.
Recovered 14 database migrations that the old .gitignore's `*.sql` rule had been
hiding from version control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>