#399 — refuse to boot when JWT_SECRET is still the published dev default
JWT_SECRET has a running install today that will refuse to start after upgrading, until they set it (or LQ_AI_DEV_MODE).Status: Accepted (2026-08-09) — committee-accepted at the weekly call. Date: 2026-08-02 Owner: Maintainer team (houfu)
Accepted amendment (2026-10-12): ADR 0029 R3 extends decision 3 with the post-1.0 semantics: patch = a blind upgrade; minor = read the release notes, operator action may be needed; major = an upgrade that can break a working install’s data, configuration or API contracts. The operator-centric test — does a working install need a human to touch it? — continues to govern the minor/patch line. Per ADR 0031, the patch and minor promises do not extend to third-party API clients; “API contracts” means the contracts the project’s own clients rely on. Decision 3 stands as the pre-1.0 record.
The project publishes two independent release artifacts — the container image
set (v*.*.* → release.yml) and the macOS launcher (desktop-v* →
desktop-release.yml) — with no enforced ordering between the components of
either, and no operational versioning policy tying their version strings
together.
The image pipeline is one unordered matrix. release.yml builds api,
gateway, web, and proxy as four parallel entries of a single
build-and-push job. Its needs: edges (sbom → sign) fan in only after
all four have built, so they order the supply-chain steps, not the components.
With fail-fast: false, a failed api build does not prevent web from
publishing — and tagging :latest — under the same tag.
Ordering is documented but unenforced. docs/BUILD-AND-RELEASE.md §3
prescribes cutting images first and the macOS app second, and the BYOK handoff
below follows that order by hand. What no document covers is ordering within
the image set — nothing says backend must be verified before web builds
against it — and nothing enforces either ordering in CI. The gap is
enforcement and intra-image sequencing, not a total absence of guidance.
Version drift is live on main. api/app/__init__.py is 0.6.1,
gateway/app/__init__.py is 0.5.1, desktop/package.json is 0.6.2. The
BYOK handoff records api and gateway being bumped jointly to 0.5.1; api
has moved twice since and gateway has not moved at all. No CI check catches
the divergence.
It has already broken a real install.
docs/LQVern/HANDOFF-2026-06-21-byok-recut.md records v0.5.0 images and the
desktop-v0.5.1 .dmg shipping without PR #202’s BYOK fix, requiring a manual
re-cut of both tags (v0.5.1 / desktop-v0.5.2) so that a fresh install would
work for a stranger.
No prior decision covers the unit of versioning. docs/PRD.md §7.8
commits to “Semantic versioning (semver)” in a single line with no operational
detail. No prior ADR, PRD section, roadmap item, or DE entry decides what is
versioned as a unit, or what major/minor/patch mean for this project. Nearest
canon: ADR 0023 (a cross-subsystem decision previously undecided anywhere) and
CLAUDE.md’s decide-once rule.
api, gateway, web, and proxy share one release version, bumped
together on every vX.Y.Z tag — including patch releases where only one
component substantively changed. They already build from a single tag; this
makes that fact explicit and enforced rather than incidental.
The shared version is the image tag itself. Only api and gateway
carry a project-owned version string (app/__init__.py), and those are what
the consistency check in decision 5 compares. web/package.json is
0.9.2 — inherited from the OpenWebUI fork and tracking upstream, not this
project’s releases (per ADR 0001’s pin-and-monitor posture) — and proxy
has no version string at all. Neither is retitled to match vX.Y.Z.
desktop versions independently. The launcher legitimately ships
launcher-only fixes with no backend change (e.g. desktop-v0.6.2’s
pull-order fix, 05f1cb1e). Each desktop-vX.Y.Z release records which
vX.Y.Z image set it ships against — explicit and discoverable, rather than
implied by whatever :latest resolves to at install time.
The pinning mechanism already exists: LQ_AI_IMAGE_TAG is rendered into
the launcher’s runtime .env from cfg.imageTag (desktop/src/core/env.ts).
What is missing is a truthful default. docs/BUILD-AND-RELEASE.md states the
launcher defaults to “the version we ship the app at”, but the shipped code
defaults to 'latest' (desktop/src/main/index.ts:87) — so today every
fresh install floats to whatever :latest resolves to, which is the exact
failure mode of the BYOK incident. Correcting that default, and recording the
pin in a release manifest, is implementation work under this decision.
Pre-1.0, a patch release never requires operator action. Anything that does
bumps the minor. Semver §4 says that below 1.0.0 anything may change at
any time. That gives an evaluating security or procurement team no signal at
all, so this project promises more than semver requires:
0.6.2 → 0.6.3) — safe to take without reading anything. No
new environment variable, no config change, no migration step, no client
change. Upgrade it blind.0.6.x → 0.7.0) — may require the operator to do something
before the upgrade works. Read the release notes first. New features land
here too, so a minor bump means “read”, not “brace”.1.0.0 milestone whose criteria this
ADR does not decide (see Explicitly not decided).The test is “does a working install need a human to touch it?” — not “did a function signature change?”. The former is something an operator can check; the latter is not visible to them.
The number is computed from main, not chosen in advance. A release is
numbered by what has accumulated since the last tag: if any change requiring
operator action has merged, the next release is a minor bump; otherwise it is
a patch. A breaking PR therefore does not have to wait for a 0.7.0 to exist
before it can merge — merging it is what makes the next release 0.7.0.
Milestones are planning tools and do not decide the number.
Consequence to plan around: once a change requiring operator action sits on
main, no further patch can be cut from main. If an urgent fix must ship
without also shipping that change, it is cherry-picked onto a branch taken
from the last release tag and cut from there. That path is documented in
docs/BUILD-AND-RELEASE.md and deliberately not automated — with current
maintainer capacity (see Cadence) a standing release-branch discipline
would cost more than it returns.
The image pipeline gates backend before frontend. release.yml’s matrix
splits into build-backend (api, gateway) and build-frontend (web, proxy)
with needs: build-backend, so a backend failure structurally blocks the
frontend from publishing under the same tag. Each stage gates on a real
verification step against the published images, not merely a green
docker build. The desktop-vX.Y.Z cut remains the final, separate stage,
unchanged, and is sequenced after both image stages are live and verified.
Both rules are enforced in CI, not left to the runbook. A vX.Y.Z tag
push fails if api/app/__init__.py and gateway/app/__init__.py disagree
with each other or with the tag. It also fails if the tag is a patch bump
while any PR labelled breaking-change merged since the previous release tag
— the gate for decision 3. Each has an explicit override
([allow-version-drift], [allow-breaking-in-patch] in the tagged commit
message) whose justification belongs in the release PR.
The breaking-change gate depends on the label being applied at review time; an unlabelled breaking PR is invisible to it. It is a backstop against forgetting at release time, not a substitute for judgement at review time.
Cadence commitments are restated as best-effort and capacity-dependent. PRD §7.8’s “minor release every 6–8 weeks” and “security backports for 12 months” predate the committee-governance transition (ADR 0022) and do not reflect current maintainer capacity or process latency. See Cadence below.
Taken from the milestones open at the time of writing, to show where the line falls in practice.
#399 — refuse to boot when JWT_SECRET is still the published dev default
JWT_SECRET has a running install today that will refuse to start after upgrading, until they set it (or LQ_AI_DEV_MODE).#396 — require the gateway key on /v1 inference endpoints
401 on every inference call after upgrading.#398 — sanitize skill markdown before rendering (XSS)
#442 — list knowledge bases by join table rather than the legacy column
#485, #481, #480 — dependency bumps
#415 — /lq slash command for Slack and Teams
These milestones do not currently follow this rule, and that is the point of
writing it down. v0.6.3 is labelled a patch but contains #399 and #396, both
of which require operator action; v0.7.0 is labelled a minor but contains only
additive work. Under this ADR the two breaking items belong in v0.7.0.
This is already load-bearing, not hypothetical. #396 and #399 are both
merged on main — 29 commits past v0.6.1, with nothing tagged since. The next
image tag cut from main must therefore be v0.7.0; cutting it as v0.6.2
or v0.6.3 would break this policy on the first release after it is ratified.
main. Unenforced, the components do not stay in sync.docker build is
precisely the signal that was already green during the BYOK incident; the
images built fine, they just did not contain the fix.Three factors make the current PRD §7.8 cadence unrealistic to commit to:
main, roughly
642 (~98%) are authored by one person across two identities
(kevin@tucuxi.ai, hikevin@gmail.com). The next-highest human contributor
has 6. Five distinct human identities have landed commits on main. The
project is resourced for what one person’s availability allows, not for a
committee-wide cadence.Developer ID Application: Tucuxi, Inc., team
MC8BT9Z8GD, per docs/BUILD-AND-RELEASE.md §1 — is tied to the founder’s
own developer account rather than a LegalQuants org account, and
docs/BUILD-AND-RELEASE.md marks the release checklist “Kevin only”. No one
else can cut a signed desktop-vX.Y.Z release regardless of committee
bandwidth.Proposed replacement text for PRD §7.8 (the first four bullets; the supply-chain commitments below them are unchanged):
- Semantic versioning (semver), with the versioning unit and pre-1.0 semantics defined in ADR 0025.
- Releases tagged on GitHub with full changelog.
- Targeted cadence: minor release every 8–12 weeks, patch releases as needed. Cadence is best-effort and capacity-dependent while the project operates with its current maintainer and signing-identity concentration; revisit once desktop signing moves to an org-owned Apple Developer account and/or a second maintainer holds release authority.
- Long-term-support (LTS) designation for one minor version per year, with security backports for 6 months (reduced from 12), reflecting current capacity to staff backport work; extend once the bottlenecks above are resolved.
vX.Y.Z release republishes all four images, and bumps both
project-owned version strings (api, gateway), whether or not all four
components changed. Release notes must therefore distinguish “what changed”
from “what was rebuilt”, or readers will infer substance from a version bump
that carries none.gateway
moves to match api, rather than being quietly papered over by the next
release.fail-fast
cancels sibling builds inside a stage, but it cannot un-push an image that has
already pushed, so a gateway image can still reach the registry under a tag
whose api build subsequently failed. Closing that gap needs a
build-then-push-in-a-second-pass design (build all, load, push only once every
build in the stage has succeeded), which is a larger change than this ADR
decides and is left as follow-up. Stated here so the guarantee is not read as
stronger than it is.stack-smoke.yml is the nearest existing check but is not a drop-in: it
builds the compose stack from local sources rather than pulling published
images, and its own header scopes it to “builds, migrates, boots, and holds”
— explicitly not “features work”. Cold runs take ~20–30 minutes. Wiring
the gates therefore means extending stack-smoke to run against published
image tags, and treating desktop/VERIFICATION.md Protocol 1 as the manual
backstop until an automated equivalent exists.docs/BUILD-AND-RELEASE.md and docs/PRD.md §7.8 are updated to carry the
policy and cross-reference this ADR; the release checklist gains the
version-consistency and image-pin steps.1.0 means. This ADR reserves major for a 1.0.0 milestone but
does not define what that milestone requires, and neither does any other
project doc. “GA” appears three times across the docs — two in PRD §9 (a
security-doc acceptance criterion and an MCP OAuth timing note) and one in
docs/word-addin.md (the Word add-in’s own GA) — and is never operationally
defined at the product level. The roadmap (PRD §8) is structured as
milestone-based delivery, each milestone a public release, rather than
converging on a single 1.0; docs/HONEST-STATE.md frames nothing as a 1.0
gate. Defining 1.0/GA criteria is left to a separate, future committee
decision — likely its own ADR, once someone is ready to own it. Until then
this policy governs minor and patch only.Drafted from the issue decision ledger; every factual claim re-verified against
legalquants/main at b060ae2f. Filed for committee comment by houfu
(Ang Hou Fu); accepted at the weekly call of 2026-08-09 and merged as PR #487
on 2026-08-10. v0.7.0 is the first release cut under it.
Numbering note: 0024 is taken by the jurisdiction-and-practice-area expansion
ADR, merged as PR #313 on 2026-08-09; this ADR takes 0025. It cites ADR 0022,
merged as PR #311 on 2026-07-27.