Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.noaddress. 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-Idunverified, so somethingserver-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:
test.rag.digdir.cloudonly offersfact-checkerandretrieve-only, and rejects the agentic RAG modes withmode_not_allowed. Verified with two API keys, one granting all fiveagents explicitly, so it is configuration rather than key scope.
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/capabilitiesis 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_MODEpicksentra,supabaseoroff. All end at the same signedcookie, 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 agentavailability 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.