AI interop runs of libtacky 1.2.0-rc2 and fix playbooks for issue #1 #2
Loading…
Reference in a new issue
No description provided.
Delete branch "ai-runs-2026-11-07"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
AI interop runs of libtacky against slixmpp on the three test servers (Prosody, ejabberd, anotherim-server), 2026-11-04 to 2026-11-08, plus the fix playbooks for every bug they found.
What is in it
runs/): AI-verified runs, all--verified-by ai --confidence low, of libtacky v1.2.0-rc2 (tackyd-json). There are also live re-checks on upstream v1.2.0 (1dae0a6c) and verify runs of local fix builds. Every committed run passesinterop check;AI-RESULTS.md,FEATURES.mdandmatrix.mdare regenerated.tools/ai/): the AI runner brief,aish,peer.py,tacky.py,common.py, and scenarios for each feature tested, includingrepro-*scripts for single bugs. Harness fixes along the way:\nonlyfixes/): one directory per bug of #1, indexed infixes/README.md, with a triage of the minor bugs.issue.mdandpr.mdfor upstream, and afix.patch.fixes/embcheck.pychecks that a tackyd binary embeds a given branch's code; all 42 fix binaries matched.Findings
#1: 71 libtacky bugs (13 security/privacy) and the live re-check results.
Before merging
20261107-113200is recordedfail, but the fault is Prosody's mod_proxy65 (libtacky passed). It may need relabelling.interop check:181318,184109,191208,191231,205307(all 2026-11-07, superseded).main.Prosody's and ejabberd's <enabled id=…/> and the previd of a resume went into the runs' server and peer logs (libtacky's console hides its own previd only): a stream's resume token, dead once the session ends, as all of these have, but not something for a public proof. Both are redacted now, also JSON-escaped, with tests; the 32 finished runs are redacted again and their run.toml hashes updated, nothing else in them changed. Found by the XEP-0198 agent. Tacky.send_raw also follows the HANDBOOK now: debugtap on answers the tap, debugtap write takes {stanza, tap}. Assisted-by: Claude:opus-5-5message send neither checks nor prepares the JID it is given. It accepts localparts RFC 7622 forbids (" & ' : < >, control characters, over 1023 bytes) and sends OMEMO iqs to them (client.log: to='q"...@prosody.asxp.io', answered jid-malformed); the message then stays pending with no Status. roster add sends the same item. AI1B@, a fullwidth address and a final dot each become a chat of their own with to= as typed, though libtacky's row says JIDs are character-checked, case-folded and width-mapped, and roster add does prepare them. Prosody prepares and delivers them itself. The first run read the wrong body field, n/a. Assisted-by: Claude:opus-5-5Every forward libtacky sends leaves the inner <message/> without xmlns='jabber:client', so it inherits urn:xmpp:forward:0 (client.log; slixmpp parses it as {urn:xmpp:forward:0}message). XEP-0297 3.2 says the forwarded stanza's namespace MUST be preserved and calls the inherited one wrong. message.tcl's ForwardNodes builds it without -ns. Everything else works: delay, inner from and id, the fallback body, the stored forward_* fields, and forwards received from slixmpp (including a nick-only author). Two earlier runs crashed in the draft scenario, n/a. Assisted-by: Claude:opus-5-5XEP-0352 on both ejabberd-based servers, the Prosody author's scenario unchanged: set_active nonzas, <inactive/> again after a resume, and a fresh inactive session; as these servers leave CSI out of the Bind 2 inline list, libtacky sends that <inactive/> as a nonza after bind. XEP-0368 on prosody: libtacky merges _xmpps-client and _xmpp-client by priority and weight, dials direct TLS with SNI the domain and ALPN xmpp-client, and falls back to STARTTLS when direct TLS is lower, refused in the handshake or absent ("."), with a fake DNS server in a network namespace for the cases real DNS cannot show. Assisted-by: Claude:opus-5-5The same libtacky bug: the inner <message/> of its forwards carries no xmlns='jabber:client' (client.log, and slixmpp parses it as {urn:xmpp:forward:0}message). Prosody re-serialises the payload but does not touch its namespace. Everything else passes as on anotherim-server. Assisted-by: Claude:opus-5-5Prosody relays an invitation with the inviter as an occupant JID (<invite from='room@service/nick'>), as XEP-0045 7.8.2 allows. libtacky's ParseInvite (message.tcl line 10754) strips every inviter to a bare JID, which for an occupant JID is the room, so decline_invite sends <decline to='room@service'>: the room bounces it to itself and Prosody discards it ("Discarding unhandled error message"), so the inviter never hears of the decline. The inviter should be kept as the room named it. ejabberd and anotherim-server name the inviter's real JID, where bare is right. Everything else passes as there. Assisted-by: Claude:opus-5-5XEP-0444 0.2.1 3.3: "When receiving this error message, the reaction-sending entity MUST revert the 'message reactions' to its previous state." slixmpp answered libtacky's {🙏, 💯} with a not-acceptable error; libtacky kept {🙏, 💯} and emitted nothing (SetReactions stores the set before sending, with no error path). The other 105 checks pass: ids (origin-id or id in 1:1, stanza-id in the room), the full set each time incl. clearing, one element with a store hint, emoji validation sending and receiving, reactions on a correction, per-sender sets (by occupant-id in the room), carbons and MAM. The first run's draft misread a clear, n/a. Assisted-by: Claude:opus-5-5`pubsub publish` and `pubsub create` return the item id or node name through Tcl's expr (pubsub.tcl lines 1158 and 1044: expr {... ? $a : $b}), which rewrites a number-like string: the service's <item id='1E20261107042352'/> came back to the host as "Inf", a server-chosen id 70262E937439 too, and 0x10 as 16. XEP-0060 ids are opaque strings the publisher needs back (7.1.2, 7.2). The other 72 checks pass on PEP and on pubsub.anotherim.asxp.io: create and configure, publish with and without id, publish-options retry, items, retract with and without notify, whitelist and affiliations, subscribe and unsubscribe, +notify, forged notifications dropped, purge, delete and errors. Two draft runs, n/a. Assisted-by: Claude:opus-5-5libtacky advertises jabber❌oob, sends a file's https url in x:oob with the body equal to it, stores and downloads a received https url (respecting the autofetch setting and strangers), never fetches plain http to another host and refuses ftp: and file:, answers jabber:iq:oob with service-unavailable, and puts no x:oob in the clear beside OMEMO. It sends no <desc/> and keeps none received (MAY). Assisted-by: Claude:opus-5-5A XEP-0158 CAPTCHA whose inline <data/> does not hash to its cid reaches the host as the challenge's media bytes (client-events.log, message cap2m20261107050619): captcha.tcl only decodes the data and never calls ::tacky::bob::check, which adhoc.tcl uses (line 312) and libtacky's XEP-0221 and XEP-0231 rows promise ("checked against its cid"); it also skips the size cap and raster-only filter. In ad-hoc forms every rule passes: cid and https renderings, types, sizes, unsupported schemes dropped, mismatching and SVG bytes dropped. Assisted-by: Claude:opus-5-512 rules of XEP-0249 1.2: jabber❌conference advertised, invitations sent with jid and the optional password, reason, continue and thread, continue read in both lexical forms, received ones as an event and a pending row, accept joining with the password, decline sending nothing, malformed and strangers' invitations dropped. INFO: three invitations to one room leave three rows; a refused accept leaves an autojoin bookmark; the HANDBOOK says the password never reaches the host, but <Invite> and the row carry it. The first run's draft, n/a. Assisted-by: Claude:opus-5-5ejabberd's debug log prints a parsed <preauth token=…/> as {<<"token">>,<<"…">>}, sometimes across a log-line prefix, which no SECRETS pattern matched: two RFC 5122 runs of 2026-11-04 committed pre-approval tokens in clear in server.log, and the XEP-0379 ejabberd runner found the same. Match that form, and re-redact those two runs' server.log (hashes updated). The tokens remain in earlier commits. Assisted-by: Claude:opus-5-5The first pattern missed a {<<"token">>,<<"…">>} pair broken across a log-line prefix and anotherim-server's Elixir formatting {"token", "…"}; six tokens were still in clear in the 2026-11-04 RFC 5122 anotherim-server log. Match both and re-redact it. Assisted-by: Claude:opus-5-5Low-confidence runner pass, with one scenario FAIL line overridden: ai3b's two devices are on both its OMEMO 0.3 and OMEMO 2 lists, and libtacky, as its features row documents, leaves OMEMO 2 devices out of a XEP-0396 key transport when the chat writes OMEMO 2. The scenario's R1 expects every 0.3 device. Strictly this departs from XEP-0396 section 3 ("MUST" encrypt to every device), the same kind of documented gap as missing BLAKE2b; the R1 check should learn the row's rule. R2-R10 pass as on prosody and ejabberd. Assisted-by: Claude:opus-5-5New low-confidence scenario with 10 rules from XEP-0492 0.2.0. libtacky stores, resolves and applies notification settings correctly, but an unscoped set_notification replaces the matching setting element and drops its <advanced/> child, against section 3 ("MUST NOT delete or alter <advanced/> settings they do not support"). Confirmed in bookmarks.tcl NotifyMerge: the element whose identity matches is skipped and a new one is built with no children. Also noted: a scoped write adds no attribute-free fallback (section 3 SHOULD), and `notify get` does not follow the setting though alerts do. The first run is an na draft. Assisted-by: Claude:opus-5-5New low-confidence scenario for XEP-0047 2.0.1 under Jingle: IBB works both ways (block-size honoured, strict base64, one iq in flight, close after the last block) and every hostile input is refused with the right error. But when the receiver answers one of libtacky's data blocks with an error, libtacky sends no <close/> and stalls 60 s (section 2.2 MUST close). Confirmed in the source: ibb.tcl Callback passes `err <text>` as two arguments while jinglefile.tcl OnIbbSent {sid n result} takes one, so the call fails with "wrong # args" and the error is lost; OnIbbOpened has the same signature. Also, as receiver, an out-of-sequence block ends the Jingle session without an IBB <close/>. The first run is na for scenario bugs. Assisted-by: Claude:opus-5-5OTR v3 works end to end and nothing OTR was archived or carbon-copied, but in one session `otr <State>` reported the secure session id as "Inf" instead of 02802368162e5437. otr.tcl:278 builds it inside `expr {... ? "" : [string tolower [binary encode hex ...]]}`, and expr turns a hex string like "02802368162e5437" into a number (2.8e5446 -> Inf); all-digit ids would lose leading zeros. The ssid is what users compare out of band, so a wrong one defeats that check. The fingerprint on the line above uses the same pattern. Random per session, so prosody's run did not hit it. Assisted-by: Claude:opus-5-5