A changelog, kept from now rather than assembled at the end

Every version bump gets an entry in the same commit. Not afterwards: the reason
a change was made is known while it is being made and gone a week later, and a
changelog assembled from commit subjects at release time is a list of things
nobody can act on.

Backfilled 0.8.2 through 0.9.8, because those shipped as a running deployment
rather than as releases and 1.0.0's notes have to be assembled from something.

The rule that earns the file its place is the last one in CLAUDE.md: a fix to
something that *looked* like it worked gets a line, always. Those are the
entries somebody stops working around a bug because of, and they are invisible
from outside -- nobody reports a control that silently does nothing, they just
quietly stop using it. Half of what is in here is that shape: a group delete
that left its grants, a share panel that only saved if you also saved the
resource, an update script that stopped after "== fetching ==".

A release is a signed annotated tag whose message is that version's entry, and
that is not decoration -- /admin/updates reads release notes out of the tag
object, so the tag message is literally what an administrator sees on the update
page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 21:36:44 +02:00
parent 4532b95559
commit 44920b62be
2 changed files with 200 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
# Changelog
What changed, per version, for somebody using or running LLeMbas — not a
restatement of the commit log. If a change fixed something that *looked* like it
worked, that is worth a line: those are the ones nobody would otherwise know to
stop working around.
Newest first. Versions are `__version__` in `src/lembas/__init__.py`, which is
the only place a version is written.
The first tagged release is **1.0.0**. Everything below it shipped as a running
deployment rather than as a release, and is recorded here so the release notes
for 1.0.0 have something to be assembled from.
---
## Unreleased
Nothing since 0.9.8.
## 0.9.8
**Updates follow a channel, not a commit.** `stable` tracks the newest `vX.Y.Z`
tag; `edge` tracks the branch tip. A branch tip is not a release — following one
means deploying whatever was pushed five minutes ago — so stable is the default
for anybody who is not the person writing it.
- The Updates page shows a **version** rather than a commit sha: `1.0.0` at a
tag, `1.0.0-7-gd4f56d` seven commits past one, and a bare sha only before the
first release exists.
- Release notes come out of the **annotated tag itself**, so no forge API is
involved anywhere. That matters: the Gitea API this was checked against
returns a 500 from a server-side panic on exactly the releases endpoint.
- A tag with a suffix (`v1.1.0-rc1`) is deliberately not a release — git's
version sort ranks it *above* `v1.1.0`, so accepting one would step a stable
host onto a candidate.
- Fixed: `deploy/update.sh` stopped silently after `== fetching ==` on any host
with no release tags — which was every host. Fetched, not reset, not
restarted, and no error printed.
- Fixed: `install.sh` now refuses an `ssh://` repository URL up front instead of
letting the clone fail as a service user with no key.
## 0.9.7
**Packaging, and updating without a shell.**
- `/admin/updates`: what is running, what is available, and what changed between.
A button applies it — answered by an **opt-in** systemd helper, because the
service runs unprivileged and a web application that can restart its own
service is one whose worst day is much worse. Without the helper the page says
so and prints the command.
- `Dockerfile` and `docker-compose.yml`. No secret key, no data and no `.git`
baked in; loopback only; a TLS proxy expected in front, because a service
worker and a microphone both require HTTPS or localhost.
- `deploy/lxc-install.sh` creates an unprivileged Proxmox container and runs the
existing installer inside it.
- `/healthz`, which opens the database rather than only proving the socket is
listening.
## 0.9.6
**Permissions, quotas and sharing.**
- **"What can this account actually do?"** answered on screen, naming *where*
each permission came from — admin, the baseline, or a group.
- Users and groups are list-plus-detail, and membership is edited from **one**
side. It was on both, and a save from either overwrote what the other showed.
- Reading and writing split for notes, memory and skills.
- **Quotas on a group** — monthly tokens, concurrent replies, agent wall clock,
images a day, helpers a reply. Resolved by maximum across a person's groups,
with zero meaning *no limit* and winning outright.
- Fixed: **deleting a group or an account left every share naming it behind.**
`forget_principal` had existed since shares did and was called by nobody.
- Fixed: `library.share` defaulted to off, so sharing shipped documented as done
and unreachable — the panel only renders for somebody who holds it.
- The share panel is its own action with a search box. It used to be checkboxes
inside the resource's save form, listing every account on the instance, and a
tick only took effect if you also saved the resource.
- Reports are shareable, and every listing has a **Shared with me** filter.
## 0.9.5
**Extraction settings, embeddings, and hybrid search.**
- `/admin/extraction`: upload size, image edge, JPEG quality, PDF pages,
extracted characters, orphan age, extra text extensions.
- An **embedding model** can be chosen from models flagged for it. Library search
then fuses keyword and semantic ranking, so *"how do I get paid"* finds a
document that says *"invoicing"*.
- **Choosing none is not a degraded mode**: no rows written, no requests made,
and byte-for-byte the keyword search that was always there.
- Vectors carry their model and width, and a mismatch is skipped rather than
scored — comparing two embedding spaces produces a confident wrong answer.
- Indexing happens in the background as records are written, with a rebuild
button for everything that already existed.
## 0.9.4
**An instance can be somebody else's.**
- Name, tagline, logo, favicon and launcher icons derived from the logo.
- The Middle-earth wording is editable data. Leaving a box alone does not freeze
it, so a later release can still improve the default.
- **Custom themes** as a set of colours rather than a stylesheet, inheriting
whichever built-in they start from.
- Global CSS overrides, served as `/branding.css`.
## 0.9.3
**Subagents.** A reply can hand a self-contained piece of work to a helper that
runs on its own and reports back — several at once, so research fans out instead
of queueing.
- A helper cannot ask questions, cannot send helpers of its own, writes nothing
unless the call asked and the chat's mode allowed it, and on a machine runs
only a fixed list of read-only commands — in **every** mode, including Auto.
- Fixed, and it was live in scheduled runs too: an unattended chat that hit an
approval built a card nobody could see and sat on it for fifteen minutes.
## 0.9.2
**Image generation defaults an administrator can actually set** — steps, cfg,
size, sampler, scheduler, denoise, negative prompt, checkpoint, batch. There were
none: one hard-coded set from the SD1.5 era, and prose in a box as the only way
to change it.
- The samplers and schedulers ComfyUI had been reporting all along are now the
pickers; nothing had ever read them.
- The tool's own schema restates the instance's defaults, instead of telling the
model "Default 512" beside an instance that draws at 1024.
## 0.9.1
**Everything that arrives is announced, not only chat replies.** A scheduled run
that filed a report used to light a dot in a corner and say nothing.
- A count in the tab title while you are looking elsewhere.
- **Web push**, so a schedule firing at seven in the morning reaches a browser
that is shut. Opt-in per device. It is the one thing here that contacts an
outside service, and `services/push.py` says so plainly.
## 0.9.0
**A model can schedule things.** There was no tool for it — asked to "remind me
every Monday", a model wrote a note and reported that it had scheduled
something, and every screen agreed with it.
- `schedule_create`, `schedule_list`, `schedule_update`, `schedule_cancel`, over
the same rule normaliser the manual form uses.
- The reply says the resulting timing back in words, which is the only moment
anybody can check that Monday was understood as Monday.
## 0.8.3
**An SSH connection may not point at this machine unless an administrator says
so.** A profile aimed at `127.0.0.1` walked straight past "nothing runs on the
LLeMbas host" — through a real login, onto the machine holding the database and
the encryption key. Three positions: off, one named port, or anywhere.
## 0.8.2
- Fixed: **opening the canvas before a chat existed swapped the whole site into
the panel.** `hx-get=""` is not "fetch nothing" — htmx looks for the attribute,
not the value, so the empty one was a real request for the current document.
- Fixed: the Canvas and Terminal buttons appeared where they could not work.
- The bottom edge of the shell is no longer drawn, so the sidebar footer and the
composer stop meeting a line at two different heights.
- Admin pages scroll in one container; `/admin/prompts` no longer drops you at
the bottom of a shorter panel.
+31
View File
@@ -1645,6 +1645,37 @@ that the right one works, because only the second half would have passed
throughout. When adding a control that writes, check the verb against the route, throughout. When adding a control that writes, check the verb against the route,
and assert on the row rather than on the response. and assert on the row rather than on the response.
## Releasing, and the changelog
**Every version bump gets a `CHANGELOG.md` entry, in the same commit.** Not
afterwards and not at release time: the reason a change was made is known while
it is being made and is gone a week later, and a changelog assembled from commit
subjects at the end is a list of things nobody can act on.
Four rules, and the last is the one that earns the file its place:
- **Newest first, one section per version**, headed by the version alone.
`## Unreleased` sits at the top between bumps.
- **Written for somebody using or running this**, not for somebody reading the
diff. "Deleting a group left every share naming it behind" is an entry; "call
forget_principal in delete_group" is not.
- **The version is `__version__` and nothing else.** `pyproject.toml` reads it
from there, the service worker cache is keyed on it, and the footer shows it.
- **A fix to something that *looked* like it worked gets a line, always.** Those
are the entries somebody stops working around a bug because of, and they are
invisible from the outside: nobody reports a control that silently does
nothing, they just stop using it.
A release is a **signed annotated tag** whose message is that version's entry --
`git tag -s v1.0.0 -m "$(…)"`. That is not decoration: `/admin/updates` reads
release notes out of the tag object with `git for-each-ref`, so the tag message
*is* what an administrator sees on the update page. `services/updates.py` strips
the signature block, PGP and SSH both.
The first tagged release is **1.0.0**. Everything before it shipped as a running
deployment, which is why `CHANGELOG.md` covers versions that were never released
-- 1.0.0's notes are assembled from them.
## Changing the schema ## Changing the schema
There is no Alembic, but there *is* `db/migrations.py`. It compares the declared There is no Alembic, but there *is* `db/migrations.py`. It compares the declared