Skip to content

Kunnskapsassistenten på digdir-headless-rag - #68

Open
nlundee wants to merge 2 commits into
mainfrom
applikasjon/frontend-pa-headless-rag
Open

nlundee wants to merge 2 commits into
mainfrom
applikasjon/frontend-pa-headless-rag

Conversation

@nlundee

@nlundee nlundee commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

A port of today's Kunnskapsassistent frontend onto digdir-headless-rag.
Same product and user experience, new technology, different engine
underneath. The swap is the point: hold the frontend constant, change the
backend, and find out what the new one can do.

Live: https://ka-app.thankfulpebble-9bb35590.norwayeast.azurecontainerapps.io
Sign in with your @digdir.no address. You get a one-time code by e-mail,
no password to create.

What works

Threads, streamed answers with stage labels, sources with excerpts, copy
actions, agent selection with descriptions, and sign-in. Verified end to end
against the Kudos corpus.

apps/web Vite + Preact + Designsystemet
apps/server Hono. Holds the API key.
packages/contract The types between the two

The browser never gets the API key. The backend authenticates applications
rather than people and accepts X-User-Id unverified, so something
server-side has to be the identity. Hence a BFF (decisions/0002).

What does not work yet, and why

Both are backend-side, not in this code:

  • Answers on the deployed instance. test.rag.digdir.cloud only offers
    fact-checker and retrieve-only, and rejects the agentic RAG modes with
    mode_not_allowed. Verified with two API keys, one granting all five
    agents explicitly, so it is configuration rather than key scope.
  • Filters. The deployed backend drops the caller's filter, so the chips
    render disabled with an explanation.

Against a local backend with the proposed changes, both work. That is where
the comparison against today's Kunnskapsassistent happens.

Worth a look in review

The server measures the backend instead of assuming it. /api/capabilities
is populated at boot by a real test: it sends a filter that cannot match
anything and compares against the same query without it. Without that control
a backend that is down would look like one that supports filters. One build
therefore serves both backend versions, and filters switch on with no
redeploy.

AUTH_MODE picks entra, supabase or off. All end at the same signed
cookie, so switching is configuration only and no token reaches the browser.
Entra ID is waiting on tenant consent; the one-time code is the interim.

Follow-ups

Eleven gaps in the backend's public contract are documented outside this
repository and go upstream as PRs to digdir-headless-rag. The agent
availability above may be one of them; the admin thinks it could be an API
key bug and is investigating.

100 tests, CI runs format, typecheck, test and build.

A port of today's Kunnskapsassistent frontend, built on digdir-headless-rag
instead of the original backend. Same product and same user experience, new
technology and a different engine underneath. The swap is the point: hold the
frontend constant, change the backend, and find out what the new one can do.

  apps/web           Vite + Preact + Designsystemet
  apps/server        Hono. Holds the API key.
  packages/contract  The types between the two

The browser never gets the API key. The backend authenticates applications
rather than people and accepts X-User-Id without verifying it, so something
server-side has to be the identity. Hence a BFF and not a direct call. See
decisions/0002.

Works end to end against the Kudos corpus: threads, streamed answers with
stage labels, sources with excerpts, the filter panel and agent selection.

The server measures what the backend actually supports at boot rather than
assuming it, and exposes the result on /api/capabilities. One build therefore
works against the backend as it is today and against one carrying the proposed
changes. The filter measurement is a real test: it sends a filter that cannot
match anything and compares against the same query without it. Without that
control, a backend that is down would look like one that supports filters.

Sign-in is chosen by AUTH_MODE: entra, supabase or off. All of them end at the
same signed session cookie, so switching is configuration only, and no token
reaches the browser in any mode. Entra ID is waiting on tenant consent from an
administrator; a one-time code by e-mail is the interim.

Deployed to Azure Container Apps as one container, with the server also
serving the SPA so the session cookie stays first-party. See deploy/README.md.

Eleven gaps in the backend's public contract are documented outside this
repository and are going upstream as pull requests to digdir-headless-rag.

100 tests (node:test for the server, vitest for the web) and CI running
format, typecheck, test and build.
A returnTo path could carry control characters, quotes or a backslash,
so it could break out of the login page markup, forge a header or send
the user to another origin. It is now rejected unless it is a plain
in-origin path, and the login handoff page passes it through a data
attribute instead of interpolating it into the script.

A document url coming from the index went straight into a link, so an
indexed javascript: or data: url would run when clicked; only http and
https are kept now. Chunk ids are filtered against an allowlist before
they reach Typesense.

/api/ask accepted any conversation id, so one user could append to
another user's conversation, and the upstream call carried no user
identity at all. It now verifies ownership first and forwards
X-User-Id.

The cookie secure flag followed the redirect uri, which turned it off
for any http backend rather than only for local development. authMode
has no default any more: the empty value could infer off and ship a
deployment with no sign-in. .dockerignore matched .env only at the
root, so env files inside the apps were copied into the image.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant