Drop Stalwart 0.15 support #57

Closed
opened 2026-08-26 16:24:49 +00:00 by jcoffey-dev · 0 comments
Owner

Nobody is on 0.15, and carrying it was costing more than it returned. The last release that runs on it is tagged stalwart-0.15-support.

Net −721 lines.

Why, precisely

Not the branch count — the failure mode. A wrong answer about which generation had answered always had somewhere to fall back to, so it failed quietly rather than loudly. One capability looked for in the wrong place downgraded every real 0.16 server onto the 0.15 path: it posted the current password to an endpoint 0.16 had removed, reported the wrong generation on About, and ran Files on the older code. It reached production and was recorded here as verified when it wasn't. The mock mirrored the same wrong placement, which is why the tests agreed with it.

With one supported generation, a wrong guess is a loud error on the first call. There is no path left for it to be wrong into.

Removed

  • web/src/lib/filenode.ts — was 92 lines of shim, now 27 lines of plain constructors
  • server/src/account.ts — the Backend = "registry" | "legacy" split, the parallel REST implementation of every credential operation, detectBackend/probeBackend/forgetBackend, and assertCurrentPassword/readableSecret which existed only for it (−266)
  • AccountInfo.generation and everything reading it, including the About row
  • The mock's LEGACY mode, mock:legacy, dev:mock:legacy
  • Three test files that existed only to pin 0.15 behaviour: account-legacy.test.ts, filenode.test.ts, and the readableSecret test

The sign-in guard

Refuses an older server once, by name, instead of letting Files, the account locale and credentials each fail separately with nothing connecting them. It says the credentials were fine — someone hitting this has typed a correct password, and telling them otherwise sends them round in circles — and names the tag to build from.

Four tests in login-guard.test.ts cover it, including that no session cookie is minted and that bad credentials on such a server are still a plain 401 rather than the server error. Testing it needed a MOCK_NO_REGISTRY=1 fixture that only omits the capability — the rest of the mock still behaves like 0.16.

hasStalwartRegistry now decides whether anyone can sign in at all, so its tests got kept and re-pointed rather than trimmed: getting it wrong would lock every user out of a working server.

Two judgement calls worth flagging

Both are fallbacks that weren't strictly about 0.15, removed for the same reason as everything else:

  1. Files no longer answers a refused filter or sort by fetching every node in the account. That path only existed because 0.15 refused parentId/isTopLevel. On 0.16 it would hide a real fault behind a performance cliff nobody would notice. It now surfaces the error. If you'd rather keep a fallback here, say so — I'd suggest retrying without the sort rather than abandoning filters wholesale.
  2. ensureFolder/findInFolder filter on parentId/isTopLevel alone and match names client-side. name is not a filter Stalwart is known to implement, and one it does not know fails the whole query rather than being ignored — so this is more robust than what it replaces, not less.

Testing

226 web + 75 server tests pass; typecheck and production build clean. Smoke-tested the built server against the mock end to end: sign-in, the Files tree on the 0.16 path with the app folder correctly hidden, and /api/account/security returning the new shape over the registry.

Not yet exercised against the live 0.16.19. The sign-in path and self-service credentials are the two I'd want eyes on there before this goes to production.

Merged 2026-08-26 as coffey-labs/ihasmail@2a741f6407

Rebuilt from: git history, session transcript.

Nobody is on 0.15, and carrying it was costing more than it returned. The last release that runs on it is tagged [`stalwart-0.15-support`](https://github.com/LINUXexpert-org/ihasmail/releases/tag/stalwart-0.15-support). **Net −721 lines.** ## Why, precisely Not the branch count — the *failure mode*. A wrong answer about which generation had answered always had somewhere to fall back to, so it failed quietly rather than loudly. One capability looked for in the wrong place downgraded every real 0.16 server onto the 0.15 path: it posted the current password to an endpoint 0.16 had removed, reported the wrong generation on About, and ran Files on the older code. It reached production and was recorded here as verified when it wasn't. The mock mirrored the same wrong placement, which is why the tests agreed with it. With one supported generation, a wrong guess is a loud error on the first call. There is no path left for it to be wrong *into*. ## Removed - `web/src/lib/filenode.ts` — was 92 lines of shim, now 27 lines of plain constructors - `server/src/account.ts` — the `Backend = "registry" | "legacy"` split, the parallel REST implementation of every credential operation, `detectBackend`/`probeBackend`/`forgetBackend`, and `assertCurrentPassword`/`readableSecret` which existed only for it (−266) - `AccountInfo.generation` and everything reading it, including the About row - The mock's `LEGACY` mode, `mock:legacy`, `dev:mock:legacy` - Three test files that existed only to pin 0.15 behaviour: `account-legacy.test.ts`, `filenode.test.ts`, and the `readableSecret` test ## The sign-in guard Refuses an older server once, by name, instead of letting Files, the account locale and credentials each fail separately with nothing connecting them. It says the credentials were fine — someone hitting this has typed a correct password, and telling them otherwise sends them round in circles — and names the tag to build from. Four tests in `login-guard.test.ts` cover it, including that **no session cookie is minted** and that bad credentials on such a server are still a plain 401 rather than the server error. Testing it needed a `MOCK_NO_REGISTRY=1` fixture that only omits the capability — the rest of the mock still behaves like 0.16. `hasStalwartRegistry` now decides whether anyone can sign in at all, so its tests got kept and re-pointed rather than trimmed: getting it wrong would lock every user out of a working server. ## Two judgement calls worth flagging Both are fallbacks that weren't *strictly* about 0.15, removed for the same reason as everything else: 1. **Files no longer answers a refused filter or sort by fetching every node in the account.** That path only existed because 0.15 refused `parentId`/`isTopLevel`. On 0.16 it would hide a real fault behind a performance cliff nobody would notice. It now surfaces the error. If you'd rather keep a fallback here, say so — I'd suggest retrying without the *sort* rather than abandoning filters wholesale. 2. **`ensureFolder`/`findInFolder` filter on `parentId`/`isTopLevel` alone** and match names client-side. `name` is not a filter Stalwart is known to implement, and one it does not know fails the whole query rather than being ignored — so this is more robust than what it replaces, not less. ## Testing 226 web + 75 server tests pass; typecheck and production build clean. Smoke-tested the built server against the mock end to end: sign-in, the Files tree on the 0.16 path with the app folder correctly hidden, and `/api/account/security` returning the new shape over the registry. **Not yet exercised against the live 0.16.19.** The sign-in path and self-service credentials are the two I'd want eyes on there before this goes to production. **Merged** 2026-08-26 as coffey-labs/ihasmail@2a741f640718 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.