-
released this
2026-11-08 02:42:26 +00:00 | 130 commits to develop since this releaselibtacky 1.2 · 2026-11-02 → 2026-11-08 · tag v1.2.2
The 1.2 line, released: 1.2.0 is its final, 1.2.2 its latest patch; the release candidates and betas before 1.2.0 follow below. The API is stable; hosts regenerate their bindings from
schema/tacky.schema.json. Test results for each version are in its tag message.1.2.2 — 2026-11-08
Built on 1.2.1 (
8919dc12), with the same toolchain. A patch: auto-download from contacts only, destroyed rooms that came back (interop issue 1) and room joins across a bad link.Host-visible
attachment_autofetch/attachment_autofetch_stickers:contacts(unset, the default of both),neveroranyone(new, with a warning: hosts should not offer it); a storedeveryoneis rewritten ascontacts; stickers came from everyone by default before.- Under
contactsa room's occupant is no contact; a service of the account's own server (the sticker catalogue's pubsub service) counts as one. - New
muc <Gone>: a room the library would have joined by itself answered item-not-found or gone, so it was not joined (nor created again). - A destroy clears the room's bookmark autojoin and keeps the bookmark;
destroy_roomtells<Destroyed>once and leaves the room. muc join's 60 seconds run on a live stream (offline: iq -offline-timeout; none while suspended).
Fixes
- C34: autojoin asks the room's disco#info first; a destroyed room is no longer recreated empty on every sign-in.
- A room joined while the link was down, or just before it went, is no longer given up as silent when the link comes back.
- Catch-up: room walks wait for every room peek still out, so the newest room still walks first when joins peek late.
- C3.1: a draft holding a character XML cannot carry is published without it; a refused write is logged, not a bgerror.
1.2.1 — 2026-11-08
Built on 1.2.0 (
1dae0a6c), with the same toolchain. A patch: forge issues #20-#23, modern XMPP, the fixes of interop issue 1 (AI interop runs against slixmpp) and the duplicates the 1.2.0 rigs left open.Host-visible
message forwardanswers the forwarded row's timestamp (timestamp_us), assenddoes.bookmarks item/remove: a waiting write superseded by a newer one of the same room answers "".groupcall start/invitein an OMEMO chat with no OMEMO 2 device:{TACKY GROUPCALL ENCRYPTED}(answers withheld with a<Warning>);meetingsthere:MEETINGS ENCRYPTED.MucSubscriberjid /<SubscriberChanged>user_jid "" in a nick-only room;call_invitestatejoinedonly while this device is in the call (elseelsewhereorpending).avatar publishrefuses images over 72 KiB ({TACKY AVATAR TOO_LARGE});mam queryanswerscount;roster <Subscribe> -reason; room names from disco#info.- New:
vcard set_visibility/visibility(contacts, basic, public); settinglegacy_file_metadata; the software version answered is the release.
Fixes
- Issues: #20 bounce cleared by a later receipt, #21 room names, #22 subscribe reason, #23 expired previews.
- A room mention's notification no longer fails on a stored row without an author.
- Modern XMPP: no
<priority/>0, avatar cap, profile visibility. - Security S1-S13: call invites inside the OMEMO 2 envelope, meetings refused in OMEMO chats, chat state/receipt request/markable sealed, legacy SIMS metadata switch, fake-room OMEMO downgrade, CAPTCHA images checked, spans strip, gateway roster steps, geoloc stop order, OTR ids without
expr, link ports/queries, no secrets in<Ack>or the console. - Correctness C1-C20 and minor M2-M25 of the same issue; docs brought up to date.
- Duplicates and ordering: Matrix double send, a MongooseIM re-archived 1:1 message, bookmark writes one per room at a time (a retract answered item-not-found is done), replies and backups by wire_id, store renames never split a main file from its -wal.
1.2.0 — 2026-11-07
Built on 1.2.0-rc5 (
fba31ad7), with the same toolchain. The final of 1.2.What 1.2 brings (for a host on 1.1.x)
- Catch-up reworked: peek, then walk (newest page drawn first, then the archive in order); no message lost under skew, drops or trims (fuzzer-checked); a short absence costs about what it missed; a phone's lock and unlock keep the stream.
- Read points: the call tier follows XEP-0490 from the full client's read points, so a chat read elsewhere withdraws a locked phone's notice.
- Connection: XEP-0495 Happy Eyeballs, XEP-0509 IAP, Caps 2.0, far fewer requests per connect.
- Matrix end-to-end encryption through a
urn:tacky:matrix-e2e:0gateway (tacky-olm, vodozemac 0.11.2), experimental and off unlessmatrix_e2e_enabledis set; the gateway ships in 1.3. - Polls are
urn:fluux:poll:0; XEP-0232 software information (off by default);message deletefor chosen rows of this device's history. - The stranger rule (someone this account wrote to or asked to subscribe to is no stranger) and
calls_from_strangers; the installation-wideofflinesetting is gone (account disableper account). - Also: video added to a running call, screen share on Linux, bot/channel rows and tag keys, normalised JIDs, a row's
id(stamp keys deprecated, removed in 1.3); Rust 1.99.1, floor 1.91.
Since rc5
- The three reviews of 2026-11-07: a
?in a localpart no longer passes for a domain, OMEMO keys a sender by the prepared JID, backup import judges the prepared JID; iq ids and the ack hold carried across a tier switch, timed-out requests withdrawn; and the Tcl review's Lows. - Stream: an over-cap stanza is skipped to its foot and acked instead of ending the connection (a stanza could loop every device of an account); past 16 MiB it is CLOSED oversize.
- A refusal after the call returned is an
error <MethodError>for a call with-commandand no-onerror(35 modules). - Catch-up: walks resume after a stored message, not our clock; an account or room without an archive gets no holes; ties on a millisecond stamp keep their order.
- Rooms: corrections, retractions and reactions taken by nick where the room gives no occupant ids (MongooseIM); the reserved nick asked at the join; our own affiliation change in the member list on the room's result.
- Bookmarks kept locally until the server answers a write; avatars fetched once per hash; group-call legs whose keys came late redialled.
- The stranger rule over a catch-up page as a whole; a deleted message replayed from another resource stays deleted.
- PEP, registration and interop fixes from the runs on Prosody, ejabberd, MongooseIM and Openfire; the integration rigs now cover all four.
1.2.0-rc5 — 2026-11-07
Built on 1.2.0-rc4 (
6f79790c), with the same toolchain.Catch-up
- No messages lost under clock skew, drops and trims: frontier written at the start, a room's start stored and its peek gap judged by id, capped walks bracket the chats they did not reach; a model-based fuzzer keeps it so.
- The review's fixes: a held message keeps its own hole, one top fetch per chat while no catch-up covers it, a rejected frontier replaced.
- A walk whose id the archive refuses (a moderated message) goes on from up to 20 earlier stored ids instead of the archive's start (8,780 rows for one room).
- A chat opened under the catch-up draws its stored rows at once (no "Loading" over one message after an unlock).
Connect volume
- A short absence costs about what it missed: bounded peeks from the newest stored message, no history replay for a room read here (5 min away was 1000+ stanzas and 10 MB per connect).
- A fresh session no longer replays the old session's unacked room joins; it rejoins through
muc join. - Synced lists (trust, encrypted chats, pins, tags, mutes, drafts) re-opened rather than fetched again; the OMEMO trust list compacted (200 devices' history with 10 left keeps 39).
- No
urn:xmpp:stickers:0+notify(84 KB of contacts' packs per sign-in); our devices follow our own node by an explicit subscription.
Connection
account unregistersucceeds only on the ends a removal comes with; a "Too many unacked stanzas"policy-violationkeeps our queue.
Matrix
- tacky-olm on vodozemac 0.11.2 for RUSTSEC-2026-0451 (Olm ratchet keys left in freed memory); commands and pickles unchanged.
Read points
- The call tier starts from the read points the full client knew, so a chat read elsewhere while the stream was down is told as
notify <SealedRead>at the lock.
Stores
- The capitalised-JID migration no longer fails the open; any failure is undone and retried. First open of a 60,000-row store 440 ms instead of 1,520 ms.
- A store file that cannot be renamed at the start (Windows, held open) opens under its old name; an older backup in capitals restores under the prepared JIDs.
1.2.0-rc4 — 2026-11-06
Built on 1.2.0-rc3 (
28ab9df0), with the same toolchain.Addresses
- Bare JIDs normalised on entry (RFC 7622): stored and emitted in one spelling; capitalised accounts and chats migrated once at the store's first open, duplicate chats merged; an invalid address refused at the API (issue 19).
- Hosts: events carry the normalised
-accand chat JIDs;account listis the source of truth.
Messages
Message.id: an opaque id per row, kept while the row lives; every method naming a message takes-id, every event naming a row carries it.-timestampandnewtimestampdeprecated until 1.3.- A forward of a forward keeps the original author, chat and date.
- An encrypted picture forwarded into a plain chat opens (
<Edited>once its url is on the row).
Connection
- A stanza the server ends the stream over is not replayed on every new stream; its row fails with the condition (sm
<Refused>-condition); the redial backs off across the same error. - Stream management counts and replays stanzas only.
account unregisteranswers on ejabberd; the account is disabled after.- SCRAM over WebSocket without -PLUS says gs2
n(ejabberd refused wss:// sign-ins); XEP-0440 rules 4 and 5 over SASL2. happy_eyeballsandiapreach the connection; XEP-0478 over-limit results answeredpolicy-violation.- SASL2 + Bind 2 keeps the pre-auth features (roster versioning, pre-approval); roster gets carry
ver; gateways and accepted invitations approved when their subscribe arrives.
vCard and backup
- No echo of a server-converted vCard4 item; vCard4 publishes one at a time.
- XEP-0227 export/import of vcard-temp as
<user/>'s child; vCard4 node at the profile's access.
Wire
- A forward's inner message in
jabber:client(XEP-0297); empty uploads refused before a slot request (XEP-0363); a peer cannot choose its stanza-id (XEP-0359).
Rooms
- Our other device under another nick in a room is us; a nick changed through the bookmark on another device follows here.
Presence
- The call tier says the show, status and priority the user set, not a plain available.
Catch-up
- A start on ejabberd reads the account's synced nodes once, not twice: 9 gets and 71 KB instead of 20 and 142 KB with a 47 KB trust list; the archive's peek still goes first.
Matrix
- The Matrix lists are read only when the account speaks Matrix (a gateway or Matrix keys); an account without asks nothing.
Startup
- The call tier's throwaway store is made in the cache directory, not the system's temporary one: where an Android app cannot write that, every cold start of a split store failed its first unlock.
Certificates
- A certificate pinned with
trust_certificate(or accepted withignore_certificate_errors) holds after a restart; the next process no longer refused it asUnknownIssuerevery five seconds.
1.2.0-rc3 — 2026-11-05
Built on 1.2.0-rc2 (
fe57dd98), with the same toolchain.Catch-up
- Peek, then walk: at every
<Ready>and<Resumed>the account's
archive is read newest page first (250, and up to two pages more while
a page holds only our own devices' rows), filed and told at once, then
walked forward from the anchor in order as before. On a day's backlog of
the user's mix the newest message from a peer draws in 188 ms after one
archive answer, against 3.7 s after 23 pages; the account's
<CatchupDone>stays where the walk ends. - An OMEMO message the peek cannot open out of order is stored as a
placeholder (Message.unreadablesealed) and opened in place
(<Edited>) when the walk reaches it in order; it counts and alerts
then, once. The ratchet is asked what an unwrap would cost before any
key is derived, so reading ahead never spends the keys of the messages
still to come. - Holes go below the peek's rows where the archive is unknown, so paging
back fills before the walk arrives; a complete walk takes them back. The
walk's frontier is stored with the page it follows (one commit), so a
drop, a kill or a restart mid-walk continues where it stopped. - Rooms: a joined room's newest page is read at the join and drawn under
the same rule; its walk waits in line behind the account's instead of
starting beside it, and keeps its own frontier across drops. - Quiet while syncing: markers, XEP-0490 publishes, vCard, disco, poll,
gateway, thumbnail and group-call liveness asks that an archived row
sets off wait for the walk's settle and go once. A host's
mark_displayedstill goes at once. - A row stored already is a duplicate by its archive id, told before its
payload is read. - An OMEMO heartbeat is no longer stored in the archive (1202 → 2 a day
on the measured backlog); the answer to a key exchange still is.
Calls
calls_from_strangersdrops a stranger's call before any answer;
nothing tells them the device is online (issue 15). The installation
setting (0or1) is the stranger rule for a call propose alone, so
a host can let strangers write but not ring; unset, it follows
messages_from_strangers, as every call did before. At 0 no
<ringing/>,<reject/>,<Incoming>or call record, only message
<Blocked>with reasonstranger.
Call tier / notices
- At each
<Ready>the call tier asks where the chats were read
(XEP-0490) and tells notify<SealedRead>for a chat read elsewhere
while it had no session, so a room read on the desktop withdraws the
locked phone's notice. - A correction, a retraction, a reaction or a room's history sent again
at a join is no new message for notify<Sealed>.
Certificates
- An "ignore for seven days" exception is bound to the certificate the
user was shown; another one on that host is refused and reported as
changed before any authentication starts (issue 16).
ignore_certificate_errorstakes the fingerprint of the last
<CertificateUntrusted>for the host (an error when there was none),
changed/previous_sha256cover a live exception as they cover a pin,
andCertificateTrust.ignore_sha256names the certificate. An exception
stored by an older build names none and is read as ended: the next
connect asks again. C ABI:tacky_trust_override_tgains
ignore_sha256_hex(connect'signore_sha256, tls-ignore-sha256).
ignore_certificate_errors(account and setting) takes an optional
sha256, the event's: a dialog answered after another certificate was
refused on the host is an error instead of an exception for one the
user never saw. A certificate refused again after another is reported
again, so the last event is always the one an answer binds to;
untrust_certificateand account removal forget the refusals shown.
Disappearing messages
- A message's timer travels inside the OMEMO 2 envelope, so the server
no longer sees it on a message; OMEMO 0.3 and clear chats carry it
beside the message as before (issue 17). The timer's negotiation
(set_timer) still goes in the clear: sealed, it is an empty message
to every reader before this one, and a room admin's change would be
lost there. A sealed negotiation from a peer is read, so sealing it
can follow once readers are out there.
Link previews
- No page is fetched for a send that cannot carry the preview, OMEMO 0.3
above all (issue 18): under OMEMO a send fetches its link only when its
recipients would get an OMEMO 2 envelope, and not while the chat's keys
are still to come.
Internals
- omemo: our own devices are written in the profile the peers' devices
take where they are listed under it, so a message to an OMEMO 0.3 peer
is one element, not two. - messagestore: a room's pending invite reads the account's invites, not
the chat's every row.
Tests
- The catch-up against a real archive on both rigs:
test_catchup_history.tcl (history whole, in order, once, across drops,
resumes and restarts) and test_catchup_rooms.tcl (five rooms beside a
1:1 backlog); twelve catch-up accounts on the rigs. - bench_catchup measures when the newest peer row and an open chat draw,
and what the archive holds. - Issues 15 to 18: unit cases each; on the ejabberd rig a message's
disappearing timer sealed under OMEMO 2 and its negotiation in the
clear.
Test results are in the tag message.
1.2.0-rc2 — 2026-11-04
Built on 1.2.0-rc1 (
9c847b7c), with the same toolchain.OMEMO
- A room send leaves out a member with no usable key and goes to the
others (issue 12; the rule since 1.1.19, now pinned case by case): under
blind trust a member whose keys were never verified nor refused is
encrypted to; a refused, changed or (blind trust off) undecided key is
skipped, and so is an undecided device of a member who verified another.
The send fails only when no other member is reached. OmemoRoomStatus.unreachable_sent(bool): true when the members in
unreachablewere left out of a send that went to the others, false
when they stopped it. A host can now tell "not delivered to" from
"not sent" without the event.
Chat list / messages
- A read or displayed mark on a captioned picture re-emits its chat row
(issue 14): the picture and its folded caption are one row, and the
list showed delivered until it was reloaded. No schema or wire change.
Internals
- bus: unsubscribe touches only the tag's own events.
- storage: a transient store skips the fsync.
Tests
- Issue 13 as reported: a comment then three forwards into an OMEMO room
leave in order (fixed since 1.1.19). - Issue 12's room send rule, one unit case per rule, and an integration
case with a member without OMEMO on a real server. - Faster suites: cheap Argon2 for the Tcl suites (tacky-keyring's
test-kdffeature, refused in release), OTR DSA keys from a pool, the
native media backend worked out once per suite, in-process peers do not
wait out the close. - The storage suite wants a transient store at synchronous OFF and a
file-backed one at FULL, as the fsync skip has it.
Test results are in the tag message.
1.2.0-rc1 — 2026-11-04
Built on 1.2.0-beta5 (
d2df40f2), with the same toolchain. The first release candidate of 1.2: no change since 1.2.0-beta5;1.2.0-beta5 — 2026-11-04
1.2.0-beta4 with a chat row's kind learnt for folders: People, Bots, Groups and Channels. The beta's full notes are on the 1.2.0-beta1 page; test results are in the tag message.
Chat list
ChatEntry.bot: a 1:1 chat whose contact has the identity client/bot (from their caps or a disco#info answer; any server's bot, a gateway's among them).ChatEntry.channel: a room whose disco#info carries theurn:tacky:room:1form withkindchannel.- Both are kept in the account's store across restarts; a change re-emits the row so the host re-filters, and a "people" folder no longer lists bots.
Tags
- Filter keys
botandchannel(0/1), selectors besidekind: People {kind chat, bot 0}, Bots {kind chat, bot 1}, Groups {kind muc, channel 0}, Channels {kind muc, channel 1}. Older versions ignore them.
Wire
- A room's
urn:tacky:room:1form is read (itskindonly), for joined and MUC/Sub rooms; the synced tags list carries<bot/>and<channel/>.
1.2.0-beta4 — 2026-11-04
1.2.0-beta3 with video added to a running call and screen sharing on desktop Linux. The beta's full notes are on the 1.2.0-beta1 page; test results are in the tag message.
Calls
- Video joins a running audio call:
calls set_video -on 1adds the camera on the webrtc backend (desktop, Android), the page adds it by its own offer;<Renegotiated>reports the outcome, and a refused add leaves the call on audio. - The peer's added video is accepted with our camera off;
set_video -on 1turns it on.<VideoEnded>when no video is left. - Screen share on desktop Linux:
set_video -on 1 -source screencaptures through the desktop portal and PipeWire, loaded at run time;calls statushasscreen. A screen that ends by itself mutes our video with<ScreenShare>enabled 0, and going back to a camera that is off releases the screen. set_screen_sharemarks the added content as a screen; a peer's added screen is reported as<ScreenShare>.- Media ABI 1.4: capabilities
renegotiateandscreenCapture, video device "screen".
Wire
- Jingle content-add, content-accept, content-reject and content-remove for video in a running call, on the session's verified fingerprint and sealed like the descriptions.
1.2.0-beta3 — 2026-11-03
1.2.0-beta2 with a quieter, steadier connection on phones. The beta's full notes are on the 1.2.0-beta1 page; test results are in the tag message.
Connection
- Locking and unlocking the phone keeps the XMPP stream: no fresh login, no presence flood, contacts online at once after the unlock.
- A fresh login sends its presence after the roster, carbons and catch-up only; the rest follows it. Service info is asked once per session and shared.
- Fewer repeated requests at each connect: refused OMEMO bundles (7 days), rooms without an avatar, the push enable, gateway registration, our second presence.
- A dead socket is noticed sooner, and while offline with a resumable session the redial comes at 5, 10, 20 s, then each minute.
Calls and notices
- A room read on another device withdraws the locked phone's notice of that room (XEP-0490).
Rooms and wire
- A room's member list lives in muc; a stale member-list reply from an earlier join is ignored.
- Mentions use RFC 5122 URIs on the wire (nicks with spaces or
#work); older libtacky stops matching such mentions.
1.2.0-beta2 — 2026-11-02
1.2.0-beta1 with one fix. Everything else, and the beta's notes, is on the 1.2.0-beta1 page.
Fixes
- Disappearing messages (XEP-0466): an album sent in the desktop's form arrives as one album again in a chat with a timer. Its bodiless announcement was taken for a timer change and dropped, so only the caption showed, as a text message.
1.2.0-beta1 — 2026-11-02
The first beta of the 1.2 line. New API is marked "Since 1.2.0" in
schema/tacky.schema.json; hosts regenerate their bindings. Test results are in the tag message and the README.Encryption
- Matrix end-to-end encryption (Olm/Megolm, cross-signing, SAS) through a key-less gateway: chats, rooms, files, edits, reactions, 1:1 calls (WIP).
- OTR v3 secret chats with SMP; OTR announced in caps (
urn:tacky:otr:0). - Telegram-style secret chats directly between two XMPP devices, now with files, stickers and GIFs.
- Signal E2E: design and API stubs only.
- Automatic Trust Management (XEP-0434/0450), opt-in; SCE time check (XEP-0420); aes-128-gcm shares (XEP-0448).
Communities and social
- Generic pubsub client (XEP-0060); microblogging over XEP-0472 with XEP-0470 comments and reactions; stories (XEP-0501).
- Hats, token invite links, server-side spaces, affiliation versioning, mention notifications, account invites (XEP-0317, 0488, 0503, 0463, 0452, 0401).
- Polls are ProcessOne's
urn:fluux:poll:0(votes as reactions);urn:tacky:poll:0is removed and 1.1.x poll votes are not carried over. - WebXDC apps, Payment Required, HTTP online meetings, screen-share content category, Coin (XEP-0491, 0518, 0483, 0507, 0298).
Connection
- Caps 2.0 (XEP-0390), Happy Eyeballs and auth pipelining (opt-in), JSON host-meta, every XMPP URI query.
- Offline mode is per account:
account disablewithdraws push and closes that account only; the installation-wideofflinesetting is gone. setting get_many; link previews read legacy charsets (windows-1251, koi8-r…).
Review and performance
- Four reviews (native, encryption, Tcl/interop, performance): 2 Critical, 8 High, 16 Medium fixed, mostly in Matrix E2EE and the new feeds.
- Catch-up SQL halved, chat list read 25.8 → 8.9 ms at 60k rows, backend start 331 → 302 ms, browser filesystem 2.58 → 1.84 MB gzipped.
Build
- Rust 1.99.1, MSRV 1.91, wasm on emsdk 6.0.13; HANDBOOK.md in the tree.
Known issue
- On ejabberd, an album sent in the desktop's form can arrive as its caption alone when many suites ran before it; under investigation.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads