diff --git a/CHANGELOG.md b/CHANGELOG.md index 48135a1..a8d1c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,99 @@ for 1.0.0 have something to be assembled from. ## Unreleased +## 1.0.0 + +The first release. Every version before it shipped as a running deployment +rather than as a release; this is what those add up to, and the point at which +it is worth somebody else installing. + +**What it is.** A self-hosted web interface for OpenAI-compatible endpoints. +Server-rendered, no build step, no CDN, one SQLite file. Point it at whatever +you run — llama.cpp, LM Studio, vLLM, Ollama, OpenRouter, OpenAI — and it works +the same. + +### What arrived since 0.8.1 + +- **Things that happen because time passed.** Say "every Monday at nine" and a + model sets it up itself, against the same recurrence rule the manual form + uses. A run can file a **report** you read later, send you a message, or work + in a chat of its own. +- **News that finds you.** A dot in the sidebar, a count in the tab title while + you are looking elsewhere, and **web push** so a schedule firing at seven in + the morning reaches a browser that is shut. Opt-in per device, and the one + thing here that contacts an outside service — `services/push.py` says so + plainly and says what it costs. +- **Helpers.** A reply can hand a self-contained piece of work to another model + that runs on its own and reports back, several at once. A helper cannot ask + questions, cannot send helpers of its own, changes nothing unless asked, and + on a machine runs only a fixed list of read-only commands. +- **Drawing.** Point it at a ComfyUI and a model can make images, against + workflow templates and defaults you set — size, steps, sampler, scheduler, + checkpoint. It reviews its own result and can try again. +- **Semantic search.** Pick an embedding model and library search fuses keyword + and meaning, so *"how do I get paid"* finds a document that says *"invoicing"*. + Choosing none is not a degraded mode: it is byte-for-byte the keyword search + that was always there, with nothing written and no requests made. +- **Quotas and sharing.** 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*. Documents, notes, skills and reports can be + handed to a group or a person, read-only, with a *Shared with me* filter + everywhere. And a screen that answers **"what can this account actually do?"** + by naming where each permission came from. +- **Make it yours.** Name, tagline, logo, favicon and launcher icons; the + Middle-earth wording is editable data; custom themes defined as a set of + colours rather than a stylesheet. +- **Install it and update it.** A Dockerfile, a Proxmox container script, and an + `/admin/updates` page showing what is running, what is available and what + changed between. The button that applies an update is opt-in and cannot do the + work itself — it writes a file that a systemd unit picks up, because a web + application that can restart its own service is one whose worst day is much + worse. + +### The part worth reading + +Five audit passes went into this release rather than one, and they found things +that had shipped looking correct. These are the entries somebody stops working +around a bug because of: + +- **Every model was told the time in a zone with no name** — on any account that + had not chosen one, which is every account by default. +- **A helper could write files and run programs on a remote machine, + unattended, in a mode that promises to change nothing.** `find` was on the + read-only command list, and `find -fprintf` writes a file. +- **Two ways to get root out of the update helper**, one of which needed no + compromise at all: root ran a script the unprivileged service account owns, + and an update fetches that script as that account. +- **Deleting a chat left every file it held on disk** — attachments, generated + images, all of it, with nothing that would ever look at them again. +- **Folder nesting was fully built, documented in the README, and reachable by + nothing.** So was moving a chat into a folder. +- **The terminal silently stopped accepting input after a reconnect**, while + output kept arriving so the panel looked healthy. +- **On the Messages screen, half the keyboard shortcuts did nothing**, because + two scripts were loaded twice and each toggle ran twice. +- **The prompt preview could not show two thirds of what it previews.** +- **Hints and timestamps failed the contrast minimum in both themes.** + +### Where the edges are + +Stated because they are the things worth knowing before you rely on it: + +- **Nothing executes on the machine LLeMbas runs on.** Agent chats run their + commands over SSH on a host you choose, and the security of an agent chat is + the security of that host. There is no sandbox here and that is deliberate — + `PLAN.md` records the one that was designed and dropped, and why. +- **One worker.** The generation registry, the terminal sessions and the + schedule ticker are all in-process. Two workers means two tickers and every + schedule firing twice. +- **A restart abandons replies in flight**, keeping whatever each had. +- **Schema changes are additive.** New tables and columns apply themselves at + startup; renames and drops are manual. The upgrade path is tested from an + 0.8.1-shaped database with rows in it. +- **Sharing grants reading only.** + +2283 tests on Python 3.11, 3.12 and 3.14. + ## 0.9.13 **The testing pass.** 2140 tests became 2283, and writing them found four bugs diff --git a/PLAN.md b/PLAN.md index 02ea53f..3b7b465 100644 --- a/PLAN.md +++ b/PLAN.md @@ -4,14 +4,16 @@ Where the project is, what is deliberately not built yet, and the decisions that would be expensive to revisit. Kept current as work lands; the detail of *how* things work lives in [`CLAUDE.md`](CLAUDE.md). -**Status:** usable daily, and closing on 1.0.0. Streaming chat, attachments, -reasoning, tool calling with web search, custom HTTP tools and MCP servers, -agent chats that work on a machine over SSH, a knowledge library, notes, memory -and skills, speech in and out, image generation over ComfyUI, users and groups, -model administration, installable as an app, reports, messages, and scheduled -work that runs on its own. 2088 tests, `ruff` clean. +**Status:** released. **1.0.0.** Streaming chat, attachments, reasoning, tool +calling with web search, custom HTTP tools and MCP servers, agent chats that +work on a machine over SSH, helpers a reply can delegate to, a knowledge library +with keyword and semantic search, notes, memory and skills, speech in and out, +image generation over ComfyUI, users, groups, quotas and sharing, model +administration, branding, installable as an app, reports, messages, scheduled +work that runs on its own, web push, and updating from the web interface. +2283 tests on Python 3.11, 3.12 and 3.14; `ruff` clean. -What remains before the first stable release is written out below, in phases, +How it got there is written out below, in phases, under [The road to 1.0.0](#the-road-to-100). --- @@ -566,28 +568,47 @@ seen working. - [x] `/healthz`, which opens the database rather than only proving the socket is listening, and says nothing about what is here -### Phase 8 — audit and finalization (`0.9.8` … `0.9.9`) -- [ ] Security review over the whole accumulated diff -- [ ] A sweep for the failure this codebase keeps cataloguing: a control that - looks like it works — a verb against a route that does not serve it, a - trigger bound where the event does not go -- [ ] Every harness fragment read as a model would read it -- [ ] Focus, contrast and narrow widths across the admin screens -- [ ] Documentation, a fresh install, and an upgrade from an 0.8.x database +### Phase 8 — the audit, in five passes (`0.9.9` … `0.9.13`) + +Five passes rather than one, each ending in a deploy. What each found is in +`CHANGELOG.md`; the shape of it is worth keeping here. + +- [x] **The main logic and the harness** (`0.9.9`). Every model was being told + the time in a zone with no name; the prompt preview could not show two + thirds of what it previews; Plan mode was told to use a tool Plan mode + withdraws; reading one knowledge document could fill the whole window +- [x] **Functional bugs and unreachable features** (`0.9.10`). The four control + sweeps came back **clean** — 68 htmx verbs against 179 routes, zero + mismatches. What they found instead was one level up: folder nesting fully + built, documented in the README, and reachable by nothing; deleting a chat + leaving every file it held on disk +- [x] **Security** (`0.9.11`, `0.9.12`). Six findings. A helper could write files + and run programs unattended in a mode that promises to change nothing; an + SSH connection could be pointed at `0.0.0.0` and reach this host; **two + root escalations in the update helper**, one of which meant control of the + branch was control of root +- [x] **Testing** (`0.9.13`). 2140 tests to 2283, and four bugs that reading had + not found — three of them from driving the JavaScript under a DOM stub +- [x] Contrast, measured rather than eyeballed: `--ink-faint` failed the 4.5:1 + minimum in **both** themes +- [x] Documentation, and `docs/notes/release-checklist.md` for the half a + machine cannot test ### Phase 9 — 1.0.0 -- [ ] A commit that changes the version, `CHANGELOG.md`, this file and the +- [x] A commit that changes the version, `CHANGELOG.md`, this file and the README, and nothing else -- [ ] A **signed annotated tag** whose message is the 1.0.0 changelog entry. +- [x] A **signed annotated tag** whose message is the 1.0.0 changelog entry. Not decoration: `/admin/updates` reads release notes out of the tag object, so the tag message is what an administrator sees on that page -- [ ] The deployment moves to the `stable` channel, which has something to +- [x] The deployment moves to the `stable` channel, which has something to follow for the first time --- ## After 1.0.0 +Features: + - **OCR** for scanned PDFs - **Conversation branching** — `Message.parent_id` exists unused; needs a UI for choosing between versions, which is why rewind truncates for now @@ -597,6 +618,37 @@ seen working. - **Writable shares**, which need history and a merge story before they need a column +Carried out of the 1.0.0 audit, deliberately. Each is real; each would change +what something *does* rather than fix what it claims to do, which is why none of +them landed in an audit: + +- **A read-only helper is still told about tools it does not have.** + `resolve_tools` filters per tool and `harness._families` gates per family, so + a family survives on its readers while its writers are gone — and seven + fragments name fifteen withdrawn write tools. The principled fix is the split + `tool.skills` / `tool.skills_write` already demonstrates, applied to `notes`, + `report`, `schedule` and `agent_edits`. That is a prompt restructure. The cost + today is bounded: `{{tool_names}}` is authoritative and the model has it, so a + helper wastes at most one round finding out. +- **`tool.background` promises a notification that can be switched off.** It has + no `requires` for `agents.background_notify`, while the runner branches on + exactly that flag. One fragment, two behaviours. Same shape as the split above. +- **`ask_user` has no harness fragment**, alone among the families. All of its + guidance lives in its schema description, which is the one thing an + administrator cannot edit. +- **`Connection.extra_headers_json` is read on every request and written by no + form**, so its documented use — OpenRouter's `HTTP-Referer` — is unreachable. + Nothing advertises it, so nothing is currently untrue. +- **Four columns are written and never read**: `Chat.compacted_at`, + `User.last_login_at`, `Schedule.last_fire_at`, `Schedule.compiled_at`. Each is + bookkeeping somebody may want to surface; none is load-bearing. +- **Dependency floor.** `pyproject.toml` pins no upper bounds and + `deploy/update.sh` runs `pip install -e` on every update, so a breaking + upstream release arrives on a button press. pip's `only-if-needed` default + limits the blast radius, which is why this is a note rather than an emergency. +- **`deploy/lxc-install.sh` has never been executed.** There is no Proxmox host + here. It is reviewed and syntax-checked; that is not the same claim. + --- ## Known limits diff --git a/README.md b/README.md index f18e5b5..d4c8104 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@

+ Version 1.0.0 Python 3.11+ License GPL-3.0 No Node required diff --git a/src/lembas/__init__.py b/src/lembas/__init__.py index d9d720c..b334b13 100644 --- a/src/lembas/__init__.py +++ b/src/lembas/__init__.py @@ -1,3 +1,3 @@ """LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints.""" -__version__ = "0.9.13" +__version__ = "1.0.0"