Delibera v10.5.2 — What's New
Release type: feature release that contains two breaking behaviour changes. Published ports now bind to
127.0.0.1, andDelibera.Servergains a transitive dependency onDelibera.Redis. Both are deliberate and both are documented below. Nothing else in the public API changed — no member was removed from an existing interface, and no signature changed.The patch number is kept because the tag and merge history already declare this release as v10.5.2. If you are versioning fresh and the ports matter to you, treat the deployment section as the migration guide.
This release ships a browser front end for the debate workflow, makes Redis reachable from inside the server instead of being an unreachable package, publishes Docker images to Docker Hub, and fixes four Web UI defects — one of which made creating a debate through the UI impossible.
Table of contents#
- Breaking changes
- The Web UI
- The proxy bug that made every POST fail
- Redis, actually wired up
- Docker Hub images
- Verification
- Upgrading
Breaking changes#
Published ports bind to 127.0.0.1#
Every published port in docker-compose.yml was bound to 0.0.0.0. All of them now bind to
127.0.0.1.
This is not hardening theatre — it is the actual enforcement. The API is unauthenticated and unthrottled, and running a debate spends real LLM credits. That was already true, but publishing images makes it trivially reachable from anywhere the host can route to.
# before — reachable from the LAN
ports:
- "5200:5200"
# after — localhost only
ports:
- "127.0.0.1:5200:5200"If you need LAN or public access, put an authenticating reverse proxy in front and bind its port
explicitly. Do not simply widen the bind back to 0.0.0.0 and call it done.
A host currently reaching Delibera.Server from another machine on its LAN will stop being able to
after this upgrade. That is the intended effect.
Delibera.Server now depends on Delibera.Redis#
The ProjectReference flows into the nuspec, so consumers of Delibera.Server now also pull
Delibera.Redis and StackExchange.Redis.
This was accepted deliberately rather than hidden with PrivateAssets="all", which would remove
AddRedisDebateOrchestrator from the published package — the one thing the reference exists to
expose. Both packages ship in the same GA matrix at the same version.
The Web UI#
Delibera.WebUI is a Nuxt 4 application, shipped as a second container alongside
delibera-server. docker compose up starts it with the rest.
Scope: list debates, create one from a registered template, watch rounds arrive live, read the verdict with token stats, export Markdown, cancel a running debate.
No UI kit and no Tailwind. This becomes a published image, and every dependency is image weight and supply-chain surface for what is a handful of forms and a timeline.
Why it talks to a proxy#
The UI reaches the API only through a Nitro server-side proxy at /api/delibera/**. That is not a
convenience — two defects force it:
Delibera.Serverregisters no CORS policy, so a browser calling the API origin directly is blocked on every request, GETs included.DebateMapperbuilds the absolutestreamUrl/resultUrlfromScheme://Hostwith no forwarded-header handling, so both point at the wrong host behind a proxy or an ingress.
SSE is forwarded, not buffered#
The proxy forwards the event stream with sendStream rather than collecting it. This is asserted
against a real socket: the upstream withholds its terminal event for 3 seconds, and the test fails
if the first chunk has not arrived by then.
A buffering proxy would pass every functional test and still deliver each round 150–200 seconds late — which is exactly how long a debate runs.
Frontend hot reload is nuxt dev on the host against http://localhost:5200.
The proxy bug that made every POST fail#
readBody parses the request payload — JSON in, plain object out. fetch stringifies a
non-string body with String(value), which produces the literal "[object Object]".
The upstream therefore received invalid JSON and answered 400 Bad Request with no field detail.
Only the GET paths worked; creating a debate, cancelling one and exporting were all unreachable
through the UI.
// before — every POST sends "[object Object]"
const upstreamInit = { method, headers: buildUpstreamHeaders(...) }
// after — objects are re-encoded, strings pass through untouched,
// and empty objects become undefined so a DELETE is not asked to
// validate a body it does not have
function encodeUpstreamBody(parsed: unknown): string | undefined {
if (parsed === undefined || parsed === null) return undefined
if (typeof parsed === 'string') return parsed
if (typeof parsed === 'object' && Object.keys(parsed as object).length === 0) return undefined
return JSON.stringify(parsed)
}Three smaller defects shipped alongside it:
- A debate that finished before the timeline attached showed no rounds. Seeding ran only when the record was already inactive on mount; a debate opened right after creation arrives Running, and if it finished before the page settled the terminal event carries no rounds — the timeline stayed empty while the verdict panel was already populated. Seeding is now unconditional and dedupes by round number.
- A missing debate was polled forever. The fallback poll treated a 404 as transient. A 404 is terminal — the record is unknown or past its ~30 min eviction, and retrying hammers the API with a request that can never succeed. 5xx and unreachable still keep polling, because a debate may genuinely be running in another process after a restart.
- The stream-state badge rendered unstyled.
StatusBadge.vuescopes its styles, so the.badgeclass used on the debate page never matched — producing an unstyled element and, with it, a malformed accessibility tree. Those styles now live on the page that uses them.
Redis, actually wired up#
Delibera.Redis shipped as a standalone package and could never be switched on inside the server.
Delibera.Server.csproj referenced only Delibera.Core, so AddRedisDebateOrchestrator was never
called and the server always ran LocalDebateOrchestrator. A redis service in compose would have
connected to nothing.
Delibera.Server now references Delibera.Redis and registers the Redis orchestrator — and,
optionally, the Redis result cache — when Delibera:Redis:Enabled is true.
Off by default. The default path resolves the same in-process orchestrator as before.
{ "Delibera": { "Redis": { "Enabled": true, "ConnectionString": "localhost:6379" } } }What this buys, and what it does not#
Buys: round events published to a Redis Stream (so any API instance can stream them over SSE), shared debate state, and optional result caching.
Does not buy: distributed turn execution. DebateWorkerService.ProcessMessageAsync is an
explicit no-op placeholder, and the orchestrator still runs each debate in the process that accepted
it. Do not read this as horizontal debate scaling.
A bad connection string now fails at startup with a message naming the key, rather than silently
degrading — a deployment that quietly fell back would look distributed and not be.
abortConnect=false still covers transient ordering, so compose can start Redis and the server in
either order.
DebateWorkerService is deliberately not registered: it would spin an XREADGROUP poll loop
against a stream nobody publishes to.
Docker Hub images#
techbuzzz/delibera-server and techbuzzz/delibera-webui, published from v* tags by
.github/workflows/publish-docker.yml as linux/amd64 + linux/arm64.
deploy/docker-compose.hub.yml pulls them with no build step:
curl -O https://raw.githubusercontent.com/techbuzzz/Delibera/main/deploy/docker-compose.hub.yml
docker compose -f docker-compose.hub.yml up -dSet DELIBERA_VERSION=10.5.2 to pin a specific release.
Ollama is not re-published — it keeps its ollama profile and its official upstream image.
Verification#
Measured on this release, not carried over from the previous one:
| Suite | Result |
|---|---|
dotnet build -c Release -warnaserror |
0 errors, 0 warnings |
Delibera.Core.Tests |
493 passing |
Delibera.Server.Tests |
117 passing |
Delibera.Grpc.Tests |
10 passing |
| .NET total | 620 passing, 0 failed, 0 skipped |
nuxt typecheck |
clean |
nuxt build |
clean |
vitest run |
34 passing across 4 files |
The 611 figure published with v10.5.1 remains correct for that tag; Delibera.Server gained 9
Redis-orchestration tests in this release.
CI runs all of the above on every pull request — build, test and Web UI checks are separate required gates, not one job that can hide a failure in another.
Upgrading#
- Pull.
git pullonmain; the tag isv10.5.2. - Expect localhost-only ports. If anything reaches the server from another machine, add an authenticating reverse proxy and bind its port explicitly.
- Expect the new transitive package.
Delibera.RedisandStackExchange.Redisarrive withDelibera.Server. Nothing to configure unless you want to enable Redis, and it stays off unlessDelibera:Redis:Enabledis true. - If you use the Web UI, you are getting the POST fix — if creating a debate through the browser previously returned 400, it will work now.
Source: docs/WhatsNew-v10.5.2.md on GitHub.
