Say 0.16.21 where the docs still said 0.16.20
The README badge still read 0.16.20, in both the label and the shield it links to. It is the first version number a reader sees and it was the one place the prose update missed, because it is HTML rather than Markdown. Two entries had gone further than stale and were wrong. FEATURES said occurrence ids are not stable across a write, and KNOWN-ISSUES carried that as a live hazard with the five-week series that proved it. 0.16.21 fixed exactly that: an occurrence is identified by its recurrence id now, and holding an id across a write keeps it on its own date. Both entries say so, keep the old behaviour and the evidence for it because the client still supports 0.16 as a whole, and record what replaced it. The defence in the client stays either way, and the reason is written down: re-resolving by recurrenceId costs one lookup, a date can still leave a series, and 0.16.20 is still a server someone may be running. The KNOWN-ISSUES header now says the live instance runs 0.16.21 and, unlike the upgrades before it, that this one was re-run rather than read against the diff — with what was exercised by hand.
This commit is contained in:
+16
-9
@@ -12,8 +12,10 @@ questions:
|
|||||||
| [KNOWN-ISSUES.md](KNOWN-ISSUES.md) | What was verified live, and where Stalwart departs from a spec |
|
| [KNOWN-ISSUES.md](KNOWN-ISSUES.md) | What was verified live, and where Stalwart departs from a spec |
|
||||||
| [docs.ihasmail.org](https://docs.ihasmail.org) | How to install, configure and drive each of these |
|
| [docs.ihasmail.org](https://docs.ihasmail.org) | How to install, configure and drive each of these |
|
||||||
|
|
||||||
Written against the tree at Stalwart **0.16.20**, which is the version the live
|
Written against the tree at Stalwart **0.16.21**, which is the version the live
|
||||||
instance runs and the one every behaviour below was checked against. ihasmail
|
instance runs. Behaviours carrying an older version below were checked against
|
||||||
|
that one and have not changed since; where 0.16.21 changed something, the entry
|
||||||
|
says so and names both. ihasmail
|
||||||
requires 0.16 or newer and refuses older servers at sign-in, by name.
|
requires 0.16 or newer and refuses older servers at sign-in, by name.
|
||||||
|
|
||||||
## The shape of it
|
## The shape of it
|
||||||
@@ -670,13 +672,18 @@ work:
|
|||||||
success; the rest are applied. ihasmail checks the patch before sending it, so
|
success; the rest are applied. ihasmail checks the patch before sending it, so
|
||||||
a rejected property is an error you can see and an inherited one is reported
|
a rejected property is an error you can see and an inherited one is reported
|
||||||
as something it could not do for one date, rather than claimed as saved.
|
as something it could not do for one date, rather than claimed as saved.
|
||||||
- **Occurrence ids are not stable across a write.** Stalwart's synthetic ids
|
- **Occurrence ids became stable in 0.16.21, and were not before it.** Through
|
||||||
encode a position in the expanded series, and writing an override renumbers
|
0.16.20 Stalwart's synthetic ids encoded a *position* in the expanded series,
|
||||||
them — confirmed live on 0.16.20: after one override, the same five ids
|
so writing one override renumbered the rest and the same five ids addressed a
|
||||||
addressed a different five dates. So an occurrence is re-resolved from its
|
different five dates. 0.16.21 identifies an occurrence by its recurrence id
|
||||||
`recurrenceId` (the date itself) immediately before it is touched, and a
|
instead — confirmed live on 0.16.21 (2026-09-06): a five-week series was
|
||||||
vanished date says so rather than acting on an id that now means something
|
expanded, its third occurrence retitled through its own synthetic id, and all
|
||||||
else.
|
five original ids re-read afterwards still named their own dates. ihasmail
|
||||||
|
re-resolves an occurrence from its `recurrenceId` immediately before touching
|
||||||
|
it anyway. That is no longer load-bearing on the current server, and it stays
|
||||||
|
because it costs one lookup, because a vanished date still has to say so
|
||||||
|
rather than be acted on, and because the client supports 0.16 as a whole
|
||||||
|
rather than only its newest release.
|
||||||
|
|
||||||
*This and future* is not offered: the server refuses an occurrence that belongs
|
*This and future* is not offered: the server refuses an occurrence that belongs
|
||||||
to such a change, and where it does, ihasmail says so and offers the series.
|
to such a change, and where it does, ihasmail says so and offers the series.
|
||||||
|
|||||||
+13
-9
@@ -4,15 +4,19 @@ What was checked, against which server, and when. For a failure you are hitting
|
|||||||
right now, start with [Troubleshooting](https://docs.ihasmail.org/troubleshooting/);
|
right now, start with [Troubleshooting](https://docs.ihasmail.org/troubleshooting/);
|
||||||
for what is not built yet, see [ROADMAP.md](ROADMAP.md).
|
for what is not built yet, see [ROADMAP.md](ROADMAP.md).
|
||||||
|
|
||||||
The live instance runs **0.16.20**, upgraded from 0.16.19 on 2026-08-31 with
|
The live instance runs **0.16.21**, and as of **2026-08-26 there is nothing
|
||||||
eight seconds of downtime, and as of **2026-08-26 there is nothing left
|
left pending**. Most entries below were exercised against 0.16.19 on the date
|
||||||
pending**. Every entry below was exercised against 0.16.19 on the date it
|
they name, and the dates still say so: each upgrade since was read against the
|
||||||
names, and the dates still say so: the upgrade was read against the
|
diff rather than re-run, and nothing in those diffs touches the session
|
||||||
0.16.19→0.16.20 diff rather than re-run, and nothing in it touches the session
|
|
||||||
capabilities, blob, quota, submission or registry paths these entries describe.
|
capabilities, blob, quota, submission or registry paths these entries describe.
|
||||||
The calendar entries below carrying a 2026-08-31 date are the exception: those
|
The calendar entries carrying a 2026-08-31 date were exercised against a live
|
||||||
were exercised against the live 0.16.20 directly, as are the public-key entries
|
0.16.20 directly, as were the public-key entries dated 2026-09-05.
|
||||||
dated 2026-09-05.
|
|
||||||
|
**0.16.21 was different and was re-run rather than read.** It changed four
|
||||||
|
things a client can see, one of which resolved an entry below outright. The app
|
||||||
|
was run against a real 0.16.21 with mail, calendar and contacts exercised by
|
||||||
|
hand, including editing one occurrence of a recurring series through the
|
||||||
|
interface and confirming the rest of the series stayed where it was.
|
||||||
What remains here is not a list of unknowns but of things worth knowing — where
|
What remains here is not a list of unknowns but of things worth knowing — where
|
||||||
Stalwart departs from a spec, where a setting has to be turned on for a feature
|
Stalwart departs from a spec, where a setting has to be turned on for a feature
|
||||||
to work, and what ihasmail deliberately does not do.
|
to work, and what ihasmail deliberately does not do.
|
||||||
@@ -63,7 +67,7 @@ works the same way — and dropped where 0.15 was the whole subject. Support for
|
|||||||
|
|
||||||
- **An override can move an occurrence, and then `start` and `recurrenceId` mean two different times.** The slot stays where the rule put it and only the clock time moves. **Confirmed live on 0.16.20 (2026-08-31)**: one occurrence of a weekly 09:00 series moved to 14:00 came back `start: 2027-06-14T14:00:00` with `recurrenceId` still `2027-06-14T09:00:00`. This is the right behaviour and it is the reason `recurrenceId` is the handle ihasmail holds: it is the one name for an instance that survives *both* a renumbering and a move, so a mutation can always be re-resolved from it. Worth recording because the mock got it wrong in the other direction — it overwrote an override's `start` with the slot time, so a moved occurrence did not move, and per-occurrence *time* editing looked broken against the mock and correct against the server. Found by asking a real server rather than by reading the mock, which is the only way this kind of disagreement ever surfaces.
|
- **An override can move an occurrence, and then `start` and `recurrenceId` mean two different times.** The slot stays where the rule put it and only the clock time moves. **Confirmed live on 0.16.20 (2026-08-31)**: one occurrence of a weekly 09:00 series moved to 14:00 came back `start: 2027-06-14T14:00:00` with `recurrenceId` still `2027-06-14T09:00:00`. This is the right behaviour and it is the reason `recurrenceId` is the handle ihasmail holds: it is the one name for an instance that survives *both* a renumbering and a move, so a mutation can always be re-resolved from it. Worth recording because the mock got it wrong in the other direction — it overwrote an override's `start` with the slot time, so a moved occurrence did not move, and per-occurrence *time* editing looked broken against the mock and correct against the server. Found by asking a real server rather than by reading the mock, which is the only way this kind of disagreement ever surfaces.
|
||||||
|
|
||||||
- **A synthetic id is only true until the next write, and a stale one is wrong rather than invalid.** Stalwart's expanded-occurrence ids encode a position in the series, and writing a `recurrenceOverrides` entry adds a component that renumbers it. **Confirmed live on 0.16.20 (2026-08-31)**: a five-week series came back as `e i m q u` over 03-01 … 03-29; one override written to 03-08 left the *same five ids* addressing 03-01, 03-15, 03-29, 03-08 and 03-22. Nothing was rejected and nothing reported a change — `i` simply meant a week later than it had a moment earlier. So an id cached across a write silently points at another date, and a delete meant for one occurrence removes a different one. This is the second time the same shape of problem has cost a live debugging session, and it is worth saying plainly why it is dangerous: the failure is not a `notFound` a client would notice, it is a confident answer about the wrong day. ihasmail therefore never mutates an occurrence by an id it is holding. `recurrenceId` is the stable name for a slot in a series — it is the date — so `updateEvent` and `destroyEvent` look the current id up by it immediately before they act, and refuse outright if the date is no longer in the series rather than falling back to the id in hand. The mock renumbers too, by a different permutation to the real server's but with the property that matters, since a mock that kept ids stable would agree with precisely the belief that is wrong.
|
- **A synthetic id was only true until the next write, through 0.16.20. Fixed in 0.16.21.** Stalwart's expanded-occurrence ids used to encode a position in the series, so writing a `recurrenceOverrides` entry renumbered them. **Confirmed live on 0.16.20 (2026-08-31)**: a five-week series came back as `e i m q u` over 03-01 … 03-29; one override written to 03-08 left the *same five ids* addressing 03-01, 03-15, 03-29, 03-08 and 03-22. Nothing was rejected and nothing reported a change — `i` simply meant a week later than it had a moment earlier, so an id cached across a write silently pointed at another date and a delete meant for one occurrence removed a different one. The failure was never a `notFound` a client would notice; it was a confident answer about the wrong day. **0.16.21 identifies an occurrence by its recurrence id, and confirming that was the point of re-running rather than reading the diff. Confirmed live on 0.16.21 (2026-09-06)**: the same shape of test — five weekly occurrences expanded, the third retitled through its own synthetic id, all five original ids re-read — left every id on its own date, with none renumbered and none `notFound`. A second override written through the interface behaved the same way. The defence stays regardless: ihasmail still never mutates an occurrence by an id it is holding, and `updateEvent` and `destroyEvent` still re-resolve by `recurrenceId` immediately before acting, because a date can still leave a series and because the client supports 0.16 as a whole rather than only its newest release. The mock follows the new behaviour, and the test that pinned the old renumbering now pins the stability instead — rewritten rather than deleted, so the reversal stays on the record.
|
||||||
|
|
||||||
- **A per-occurrence patch made only of inherited properties creates an override that loses the title.** The twelve properties 0.16.20 drops from a per-occurrence patch are dropped *after* it has decided to write an override, so a patch consisting only of them still writes one — and that override carries the `start` and `duration` the server fills in and nothing else. **Confirmed live on 0.16.20 (2026-08-31)**: `{"privacy": "private"}` aimed at one occurrence answered `updated`, left `privacy` untouched on the series, and left that date with no title at all. A successful response, a silently discarded change, and real data loss on a third property nobody mentioned. ihasmail narrows a per-occurrence patch before sending it and sends nothing when narrowing empties it, which was written as a point of principle — a request whose response could only be a meaningless "updated" is worse than no request — and turns out to prevent this. Worth remembering as the argument for the principle.
|
- **A per-occurrence patch made only of inherited properties creates an override that loses the title.** The twelve properties 0.16.20 drops from a per-occurrence patch are dropped *after* it has decided to write an override, so a patch consisting only of them still writes one — and that override carries the `start` and `duration` the server fills in and nothing else. **Confirmed live on 0.16.20 (2026-08-31)**: `{"privacy": "private"}` aimed at one occurrence answered `updated`, left `privacy` untouched on the series, and left that date with no title at all. A successful response, a silently discarded change, and real data loss on a third property nobody mentioned. ihasmail narrows a per-occurrence patch before sending it and sends nothing when narrowing empties it, which was written as a point of principle — a request whose response could only be a meaningless "updated" is worse than no request — and turns out to prevent this. Worth remembering as the argument for the principle.
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="LICENSE"><img alt="Licence: AGPL-3.0-or-later" src="https://img.shields.io/badge/licence-AGPL--3.0--or--later-2dd4bf?style=flat-square"></a>
|
<a href="LICENSE"><img alt="Licence: AGPL-3.0-or-later" src="https://img.shields.io/badge/licence-AGPL--3.0--or--later-2dd4bf?style=flat-square"></a>
|
||||||
<a href="https://stalw.art" target="_blank" rel="noreferrer"><img alt="Requires Stalwart 0.16 or newer; tested against 0.16.20" src="https://img.shields.io/badge/Stalwart-0.16.20-6366f1?style=flat-square"></a>
|
<a href="https://stalw.art" target="_blank" rel="noreferrer"><img alt="Requires Stalwart 0.16 or newer; tested against 0.16.21" src="https://img.shields.io/badge/Stalwart-0.16.21-6366f1?style=flat-square"></a>
|
||||||
<a href="https://docs.ihasmail.org" target="_blank" rel="noreferrer"><img alt="Documentation: docs.ihasmail.org" src="https://img.shields.io/badge/docs-docs.ihasmail.org-0ea5e9?style=flat-square"></a>
|
<a href="https://docs.ihasmail.org" target="_blank" rel="noreferrer"><img alt="Documentation: docs.ihasmail.org" src="https://img.shields.io/badge/docs-docs.ihasmail.org-0ea5e9?style=flat-square"></a>
|
||||||
<a href="https://coffeylabs.org" target="_blank" rel="noreferrer"><img alt="by Coffey Labs" src="https://img.shields.io/badge/by-Coffey%20Labs-0f766e?style=flat-square"></a>
|
<a href="https://coffeylabs.org" target="_blank" rel="noreferrer"><img alt="by Coffey Labs" src="https://img.shields.io/badge/by-Coffey%20Labs-0f766e?style=flat-square"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user