VM batch 4: profile, presence, roster and blocking on all three servers #26

Merged
asxpi merged 142 commits from vm/batch-4 into main 2026-11-10 16:37:17 +00:00
Member

Batch 4 (profile, presence, roster, blocking) of the GUI VM runs: anotherim-js 0.8.0-rc9 (142ee094, libtacky v1.2.3) against Gajim, Dino, Kaidan and anotherim-web on ejabberd, Prosody and anotherim-server.

Coverage: tools/interop.py gaps --feature <spec> --server <server> prints 0 gaps for RFC 6121, XEP-0054, 0084, 0085, 0153, 0172, 0191, 0313 (r), 0319, 0377, 0392 on all three servers. Dino and Kaidan lack nick, idle and spam reporting (clients.toml, bf6874c); XEP-0377 is na on all three (no server advertises urn:xmpp:reporting:1).

Runs: 94 recorded, 75 pass, 13 fail, 6 na: ejabberd 33 (27/4/2), prosody 30 (24/4/2), anotherim-server 31 (24/5/2).

Found:

  • #1 C12 reproduced on all three (Dino, Kaidan): after a restart aim's first presence carries the stored photo hash before its own vCard result (XEP-0153 §4.2; upstream's documented deviation). The Gajim and web 0153 runs predate the restart check (16c265d).
  • aim writes its profile only as XEP-0292 vCard4, no vcard-temp: Kaidan (vcard-temp only) reads an empty card on ejabberd and anotherim-server; Prosody maps vCard4 to vcard-temp and hides it.
  • anotherim-js UI: after aim clears its nick (empty <nick/>), web's Contact info keeps "Aim Kiwi" (all three servers).
  • libtacky: after an unblock, web never refetches aim's OMEMO device list that failed while blocked; its next message fails (prosody 143456).
  • Minor, libtacky: aim keeps a contact's avatar after the contact deletes its metadata node, the <delete/> received (prosody 150252).
  • #9 "Gajim keeps the old nick after an empty <nick/>": not reproduced on any server.
  • Peer/server notes: Dino removes its avatar by deleting the node and ignores an empty <metadata/>; Kaidan never puts its photo hash in presence; Prosody writes an empty metadata's item id as a photo hash and keeps a vCard-derived avatar.

XEP-0313 r: a real session death on each server (link down past the resume limit: 300 s ejabberd/anotherim-server, 600 s Prosody), resume refused, MAM catch-up with original timestamps.

Harness: profile.py with one PART per spec, aim's contact/block actions, Gajim's Add Contact, idle monitor (libXss) and width fix, Dino and Kaidan approve/avatar/profile actions, tools/xep0392.py for name colours, xep0313.py PART=reset, AGENTS.md per server.

🤖 Generated with Claude Code

Batch 4 (profile, presence, roster, blocking) of the GUI VM runs: anotherim-js 0.8.0-rc9 (`142ee094`, libtacky v1.2.3) against Gajim, Dino, Kaidan and anotherim-web on ejabberd, Prosody and anotherim-server. **Coverage:** `tools/interop.py gaps --feature <spec> --server <server>` prints 0 gaps for RFC 6121, XEP-0054, 0084, 0085, 0153, 0172, 0191, 0313 (`r`), 0319, 0377, 0392 on all three servers. Dino and Kaidan lack nick, idle and spam reporting (`clients.toml`, bf6874c); XEP-0377 is `na` on all three (no server advertises `urn:xmpp:reporting:1`). **Runs:** 94 recorded, 75 pass, 13 fail, 6 na: ejabberd 33 (27/4/2), prosody 30 (24/4/2), anotherim-server 31 (24/5/2). **Found:** - #1 **C12** reproduced on all three (Dino, Kaidan): after a restart aim's first presence carries the stored photo hash before its own vCard result (XEP-0153 §4.2; upstream's documented deviation). The Gajim and web 0153 runs predate the restart check (16c265d). - aim writes its profile only as XEP-0292 vCard4, no vcard-temp: Kaidan (vcard-temp only) reads an empty card on ejabberd and anotherim-server; Prosody maps vCard4 to vcard-temp and hides it. - anotherim-js UI: after aim clears its nick (empty `<nick/>`), web's Contact info keeps "Aim Kiwi" (all three servers). - libtacky: after an unblock, web never refetches aim's OMEMO device list that failed while blocked; its next message fails (prosody 143456). - Minor, libtacky: aim keeps a contact's avatar after the contact deletes its metadata node, the `<delete/>` received (prosody 150252). - #9 "Gajim keeps the old nick after an empty `<nick/>`": not reproduced on any server. - Peer/server notes: Dino removes its avatar by deleting the node and ignores an empty `<metadata/>`; Kaidan never puts its photo hash in presence; Prosody writes an empty metadata's item id as a photo hash and keeps a vCard-derived avatar. **XEP-0313 `r`:** a real session death on each server (link down past the resume limit: 300 s ejabberd/anotherim-server, 600 s Prosody), resume refused, MAM catch-up with original timestamps. **Harness:** `profile.py` with one PART per spec, aim's contact/block actions, Gajim's Add Contact, idle monitor (libXss) and width fix, Dino and Kaidan approve/avatar/profile actions, `tools/xep0392.py` for name colours, `xep0313.py PART=reset`, AGENTS.md per server. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
asxpi added 142 commits 2026-11-10 16:23:17 +00:00
Batch 4 records one spec per run, while profile.py ran every profile,
roster and blocking step in one go. PART now picks the steps of one
spec (6121, 0084, 0153, 0172, 0054, 0085, 0319, 0191, 0377, 0392)
between the sign-in and a restore of only what that part changed.

New steps: idle both ways (aim reports idle at once while minimized,
Gajim after its 5 min auto-away, web minimized as aim), spam (aim drops
the peer, the stranger writes, aim's Block with "Report for spamming",
then re-added) and colors (shots and the XEP-0392 reference angles).
The roster step now checks that the subscription is mutual again.

Assisted-by: Claude:opus-5-5
Their DOAP files at the versions in the VMs (dino.doap at v0.5.1,
Kaidan's misc/doap.xml at v0.16.0) list neither XEP-0172, XEP-0319 nor
XEP-0377, and Kaidan's no XEP-0084 (it does vCard avatars), so those
cells need no run.

Assisted-by: Claude:opus-5-5
After a nick the contact's chat row is renamed, and the search's
Messages results below it also show the JID: _row_menu_aim right-clicked
such a result, which opens no menu (Remove not found). It now takes the
row under the "Chats" heading. OCR missed the Block dialog's Cancel once
on the dimmed chat: the red Block button is found by colour then.

PART=0172 also clears aim's nick (an empty <nick/>, §4.3) and shoots the
peer's contact info, for #9's "Gajim keeps the old nick". Avatars are
gradient (aim) and bars (peer) so the shots tell them apart.

Assisted-by: Claude:opus-5-5
Gajim's subscription-accept sends only <subscribed/> for a contact still
in its roster (subscription none after aim's remove), so Gajim never
asked back and the roster ended to/from (prosody run 110336, parked).
With the peer removing aim as well, its approve goes through Gajim's Add
Contact dialog, which subscribes: both requests and both approvals.

Assisted-by: Claude:opus-5-5
aim's Add contact field shows only a grey placeholder that OCR does
not read, so add_contact never typed the address: it now clicks 64 px
under the dialog's title. Gajim's Add Contact keeps Next greyed for a
contact still in its roster (subscription none after aim removed it),
so its add_contact falls back to Contact Details › Settings › Request.
profile.py's roster step uses that when the peer still approves without
asking back, so the subscription ends mutual (checked in a REPL on
anotherim-server: subscribed both ways).

Assisted-by: Claude:opus-5-5
PART=0377 leaves aim without the peer in its roster (it was removed
so the peer writes as a stranger); restore called roster(), whose
remove would find no contact to remove. The re-add half is now
readd(), which the 0377 restore calls after the peer drops aim too.

Assisted-by: Claude:opus-5-5
Return moved neither page of Gajim's Add Contact window (prosody REPL:
the address page stayed with Next enabled), so the dialog that
subscription-accept opens for a contact not in the roster never sent
Gajim's own subscribe, and add_contact for a new contact did nothing.
Both now click Next and then Add Contact at (-59,-41) of the window;
with them the roster went mutual (both) on prosody.

Assisted-by: Claude:opus-5-5
On prosody OCR did not read the "Add contact" title of a plainly visible
dialog (run 112934, parked), so add_contact raised after aim and Gajim
had both dropped each other. The field is now also found from the
"<domain> address" line under it, else at its place (640,414).

Assisted-by: Claude:opus-5-5
Gajim approves aim's request with only <subscribed/> even after it
dropped aim: the server then adds aim with subscription from and
Gajim never subscribes. profile.py's roster step now checks the
peer's own log for its subscribe (aim's lags) and otherwise has it
ask: Gajim's Contact Details › Settings › Request. That window is
not always at 0,0 (seen at 5,200), so its rows are searched on the
whole left side. Explore run on anotherim-server: mutual again.

xep0313.py PART=reset is batch 4's MAM resumption case: net_reset()
aborts aim's TCP, its link stays down past the resume max while the
peer sends, and aim must fetch both from MAM in a new session.

Assisted-by: Claude:opus-5-5
On ejabberd Gajim showed one of its rooms after the server bounced
the line aim blocked, and "after unblock" was typed into that room's
composer (no message to aim, the step failed). Open aim's chat again
first.

Assisted-by: Claude:opus-5-5
On ejabberd the check ran while aim's XML log still lagged: the
subscribed/subscribe exchange and the push with subscription both
were there 30 s later, yet the check failed. It now waits up to 90 s
for that push first.

Assisted-by: Claude:opus-5-5
Away and do-not-disturb with status texts go both ways and show in
both clients. aim removes vm-gajim, Gajim drops aim too, aim re-adds
it, Gajim approves and asks back, aim accepts from its request bar:
both roster pushes end subscription='both'. The run's "roster is
mutual" FAIL line read aim's lagging XML log too early.

Assisted-by: Claude:opus-5-5
Presence both ways: aim Away with a status text shown in Gajim, Gajim
Do not disturb with its text shown in aim. Roster: aim removed Gajim,
Gajim removed aim, aim re-added and asked, Gajim approved and asked
back, aim approved; the roster push ends in subscription both. The
step FAIL "mutual again" was a race with aim's lagging XML log, fixed
in the harness since.

Assisted-by: Claude:opus-5-5
Presence each way (aim Away with a status text, Gajim Busy with one):
both arrive and show. aim removed vm-gajim, Gajim removed aim, aim
re-added it; Gajim accepted and asked back from its Add Contact dialog,
aim accepted from its request bar, and the item ends subscription both.
The steps.log FAIL on "mutual again" is the harness reading aim's
lagging XML log 10 s after the fact (fixed in profile.py since).

Assisted-by: Claude:opus-5-5
aim set a gradient avatar and published data and metadata; Gajim got
the event and shows it in vm-aim's Information. Gajim set colour bars;
aim got the metadata event, fetched the data node and shows the bars
in the chat row and contact info. Both avatars removed afterwards.

Assisted-by: Claude:opus-5-5
aim's picture goes out as data then metadata (SHA-1 ItemID) and Gajim
shows it; Gajim's goes the other way, aim fetches its data item and
shows it in the list, header and contact info. Restored to no avatar
(an empty metadata). Gajim had aim's picture cached from an earlier
harness run (same bytes), so it fetched nothing this time.

Assisted-by: Claude:opus-5-5
aim blocks vm-gajim from its chat header's Block… dialog: <block/>,
the server's push, the blocklist holds it. Gajim's line meanwhile
bounces with service-unavailable (§3.5) and never reaches aim; after
aim's <unblock/> the next line arrives.

Assisted-by: Claude:opus-5-5
aim's presence carries its vcard-temp❌update photo hash once its
avatar is set; on Gajim's new hash aim fetched vm-gajim's vCard and
got the PHOTO, and shows the picture. Gajim shows aim's picture from
PEP. C12 is not exercised (no avatar at sign-in; the own vCard get
went before the first presence).

Assisted-by: Claude:opus-5-5
Both presences carry the SHA-1 of the new photo; aim fetches Gajim's
vCard on the hash and shows the picture, Gajim shows aim's. C12 is not
exercised (aim signed in without an avatar). Prosody puts the UUID item
id of an empty avatar <metadata/> into room presences as a photo hash
after the restore, which Gajim logs as invalid (server side).

Assisted-by: Claude:opus-5-5
Both publish a nick and show the other's (Gajim: "vm-aim (Aim Kiwi)",
aim's contact info: Nickname "Gajim Kiwi"). aim then cleared its name
with an empty <nick/>; Gajim logged "nickname removed" and shows plain
vm-aim, so #9's "Gajim keeps the old nick" is not reproduced on
ejabberd.

Assisted-by: Claude:opus-5-5
Each side's PEP nick reaches the other and names the contact (aim's
list, header and details; Gajim's Information title). aim's cleared
name goes out as an empty <nick/> and Gajim drops the nick at once
("Nickname ... None", plain vm-aim): #9's "Gajim keeps the old nick"
is not reproduced on prosody.

Assisted-by: Claude:opus-5-5
anotherim-server's disco#info names no urn:xmpp:reporting:1, so aim's
Block… dialog has no "Report … for spamming" box for the stranger
Gajim (it arrived as one after aim removed it). The roster went back
to both.

Assisted-by: Claude:opus-5-5
aim and Gajim each set a full name and show the other's in contact
info. Both GUIs write the profile as XEP-0292 vCard4 over PEP; aim's
vcard-temp get of vm-gajim is answered empty by ejabberd (Gajim 2.6
writes only vCard4), so vcard-temp is exercised on the read side
only. Pass on the user-visible exchange.

Assisted-by: Claude:opus-5-5
Each side's full name shows in the other's contact info. aim reads
Gajim's vcard-temp (Prosody maps it from Gajim's vCard4); aim's own
Edit profile writes XEP-0292 vCard4 only, which Gajim reads directly,
so a vcard-temp-only peer would depend on the server's mapping.

Assisted-by: Claude:opus-5-5
aim's "Aim Kiwi" and Gajim's "Gajim Kiwi" travel as PEP nick events
and show in the other's contact details. aim then clears its name
with an empty <nick/>, and Gajim drops "(Aim Kiwi)": #9's "Gajim keeps
the old nick" did not reproduce on anotherim-server.

Assisted-by: Claude:opus-5-5
Gajim shows a contact's typing only in the open chat: in the prosody
XEP-0085 run (120722, parked) its shot showed the chat list with no
chat selected while its log had aim's <composing/>. The receiver now
opens the sender's chat before the sender types.

Assisted-by: Claude:opus-5-5
Full names set in aim's and Gajim's profile editors show in the
other's contact info. Both write XEP-0292 vCard4; aim's vcard-temp
read of Gajim gets an empty card (no vCard4 mirroring here), as on
ejabberd.

Assisted-by: Claude:opus-5-5
Gajim keeps its chat list's width across starts. On ejabberd it had
grown to ~1170 px (the "+" at x 1173), leaving an 80 px chat pane: the
Start Chat result did not show, the composer click hit a room row and
an XEP-0085 run typed into that room's draft instead of aim's chat.
open_chat now drags the pane handle (~30 px right of the "+") back to
x 700 when the list is wider than 900 px, trying a few offsets until
the "+" moves. Tried in a REPL on ejabberd: 1150 -> 674, then aim got
Gajim's <composing/>.

Assisted-by: Claude:opus-5-5
On ejabberd Gajim's <composing/> reached aim (it is in client.log) but
only after wait_typing's 30 s: aim's XML log lags the wire. The shot
taken after the wait would also come after the sender's <paused/>. The
receiver is now shot 2 s after the typing, and the stanza waited for
up to 90 s.

Assisted-by: Claude:opus-5-5
aim publishes a gradient avatar (data, then metadata) from its profile
and Gajim shows it; Gajim's bars avatar reaches aim as a metadata
event, aim fetches the data and draws it on the chat row and header.

Assisted-by: Claude:opus-5-5
Typing without sending: aim sends composing, paused and active, which
Gajim shows under the open chat; Gajim's composing and paused show as
"typing" in aim's header and list row.

Assisted-by: Claude:opus-5-5
aim's unsent text sends <composing/> then <paused/>; Gajim shows "vm-aim
is composing a message". Gajim's typing reaches aim, which shows
"typing" in the header and chat row. Two earlier tries were harness
fails (Gajim's chat list too wide, a 30 s wait lost to aim's log lag)
and are not recorded.

Assisted-by: Claude:opus-5-5
Gajim's first "Send: COMPOSING" came from the draft a parked try left
when it opened aim's chat; the typing step's is peer-gajim.log:5926.

Assisted-by: Claude:opus-5-5
aim's presence carries its new photo hash, and on Gajim's new hash aim
fetches vm-gajim's vcard-temp and draws the PHOTO. Gajim shows aim's
picture from PEP. C12 is not exercised (aim had no avatar at sign-in).

Assisted-by: Claude:opus-5-5
Typing without sending sends <composing/> both ways; Gajim shows "vm-aim
is composing a message…", aim "… typing" in the header and chat row.

Assisted-by: Claude:opus-5-5
Gajim 2.6 logged "IDLE False" at start and never went auto-away, so it
sent no XEP-0319 <idle/> however long the X session idled: its only
usable monitor on X11 is XScreenSaverInfo through ctypes, and NixOS'
ctypes.util.find_library("Xss") finds nothing without ld and the
library dirs in LD_LIBRARY_PATH. The guest now has binutils, and
interop-client.sh starts Gajim with libXScrnSaver's and libX11's lib
dirs (INTEROP_GAJIM_LIBS) in LD_LIBRARY_PATH; checked on the host:
find_library then answers libXss.so.1 and libX11.so.6.

profile.py's "aim got an <idle/> from the peer" matched aim's own
reflected presence, which carries <idle/> once aim itself has been
left alone 5 min during the peer's wait (ejabberd, Gajim): it now
requires the peer's JID.

Assisted-by: Claude:opus-5-5
aim blocked vm-gajim from the chat menu (block set and push, "Blocked"
on the row); Gajim's line sent meanwhile never reached aim. After the
unblock (set and push) Gajim's next line arrived. Nothing left on the
block list.

Assisted-by: Claude:opus-5-5
profile.py's restore clears Gajim's Full Name with edit_profile({...: ""}),
which looked for the row once, right after the Edit page opened: on
prosody it found none, deleted nothing and saved "gajim Mango" again,
so vm-gajim kept that name (cleared by hand in a REPL since). Clearing
now waits up to 4 s for the row before pressing its delete.

Assisted-by: Claude:opus-5-5
tools/xep0392.py prints a text's XEP-0392 angle (and its reference
RGB) and the HSLuv hue of screenshot pixels, for judging PART=0392
runs: aim's avatar circles matched the angles to within a degree.

The "<peer> got aim's <idle/>" line of the 0319 step now needs a
presence from aim, as the other direction's check already does.

Assisted-by: Claude:opus-5-5
vm-gajim's avatar colour in aim and Gajim's own avatar share the
XEP-0392 hue of its JID (253°), and so do vm-aim's in Gajim and aim's
own (264°), measured with tools/xep0392.py on the screenshots.

Assisted-by: Claude:opus-5-5
aim adds <idle since/> while minimized and drops it when restored;
Gajim receives both. Gajim, its X idle monitor now working in the
guest, goes auto-away after 330 s with <idle since/>, and aim shows
"Last seen 6 minutes ago". An earlier try, in which Gajim had no
idle monitor, is not recorded.

Assisted-by: Claude:opus-5-5
aim minimized sends <idle since/> at once and drops it when restored;
Gajim (its X idle monitor now working in the guest) goes auto-away
after 5 min with <idle since/>, which aim shows as "Last seen 6 minutes
ago" with the away dot. Gajim keeps aim's idle time in its log and
roster tooltip only.

Assisted-by: Claude:opus-5-5
aim minimized sends <idle/> and clears it when restored; Gajim, with
its idle monitor now working in the VM, goes auto-away after 5 min
with an idle stamp, and aim shows "Last seen 6 minutes ago". Replaces
the parked run where Gajim had no idle monitor.

Assisted-by: Claude:opus-5-5
aim blocks vm-gajim from its chat menu (block + push, "Blocked" badge);
Gajim's line sent meanwhile never reaches aim. After the unblock the
next line arrives. Nothing left blocked.

Assisted-by: Claude:opus-5-5
On ejabberd tesseract read the dialog's body ("Remove vm-gajim@…")
but not its Cancel, twice in a row, and profile.py's spam step failed
before the stranger wrote. The red Remove (srgb(217,48,37), white
label) is found by colour then; tried in a REPL: (766, 449).

Assisted-by: Claude:opus-5-5
Prosody names no urn:xmpp:reporting:1, so aim's Block dialog offers no
"Report ... for spamming" for the stranger's line (vm-gajim, dropped
from the roster for the run); nothing was reported or blocked. The
restore re-added vm-gajim, subscription both.

Assisted-by: Claude:opus-5-5
client.log:317 is aim's roster remove, not the stranger's message,
which is OMEMO-encrypted at client.log:328 (its text in Gajim's log).

Assisted-by: Claude:opus-5-5
After the outage aim fetched both of the peer's lines from MAM (inside
<result><forwarded>), but wait_message only sees a top-level <message
from=peer>, so the step failed after 300 s although aim had them
(anotherim-server run 125159, PART=reset, parked). It now waits until
both ids are in aim's log, live or forwarded, as the check after it
already counted them.

Assisted-by: Claude:opus-5-5
find_text with a timeout raises when nothing matches, so the red-button
fallback of the previous commit was never reached; look twice instead.

Assisted-by: Claude:opus-5-5
Batch 4's profile.py needs both from Dino 0.5.1 (RFC 6121, XEP-0084,
XEP-0153). A subscription request is a bar atop the contact's chat
("This contact would like to add you to their contact list", Accept /
Deny); Accept also asks back (prosody REPL: roster ended both). The
avatar is set in Preferences › Accounts › the account: a pencil opens
"Select avatar" (a GTK chooser, Ctrl+L path), and with an avatar set a
trash appears beside the pencil, so the pencil's place depends on the
state: the set tries the left spot first, the removal escapes a
chooser it opened by mistake. Checked in a REPL: set, set again,
remove, remove again (no-op); each publish reached the log.

Assisted-by: Claude:opus-5-5
The resumption case: net_reset() aborts aim's TCP and its link stays
down 348 s, past the 300 s resume max. Back online the server refuses
the resume ("Previous session timed out"), aim opens a new session
and fetches Gajim's two lines from MAM, shown at their send time.

Assisted-by: Claude:opus-5-5
ejabberd's disco#info names no urn:xmpp:reporting:1, so aim's Block
dialog offers no "Report … for spamming" box. aim removed vm-gajim,
Gajim's stranger line arrived, the dialog was closed unblocked; the
roster is mutual again afterwards. Three earlier tries died on aim's
Remove dialog (OCR missed Cancel; fixed in 2534bf5/4eabf74).

Assisted-by: Claude:opus-5-5
web as the peer asked back at once on approve, but its XML log reached
the host after the 20 s wait, so the roster step took it for a peer
that does not ask back and ran web's add_contact, which found no
Contacts verb for a pending contact and failed (anotherim-server run
131731, parked). It now waits 60 s on the peer's log for a subscribe
to aim, and also accepts one already in aim's log.

Assisted-by: Claude:opus-5-5
Batch 4 needs these from Kaidan 0.16 (RFC 6121, XEP-0153, XEP-0054).
A request is a bar over the composer of the contact's chat ("would like
to receive your personal data", Refuse / Allow, white on grey); Allow
sends only <subscribed/>, so request_presence (a new action, Gajim's
existing _request_presence_gajim fits it) asks back from Contact
Details › Privacy › "Request personal data". profile.py's re-add uses
request_presence where the peer has it instead of add_contact, whose
Kaidan dialog hangs for a contact in the roster.

Kaidan keeps its picture and name in vcard-temp: Account Details' avatar
opens "Change profile image" (Open image…, a Qt dialog; Save selection;
Remove current profile image), Profile › Name edits FN with a ✓. Checked
in a prosody REPL: approve + request_presence ended both, the avatar set
twice and removed (PHOTO gone from the vCard), FN set and cleared.

Assisted-by: Claude:opus-5-5
Away and busy with status texts each way, shown on both. aim removed
vm-web, web removed aim, aim re-added it; web accepted and asked back
from its request bar, aim accepted, and the item ends both.

Assisted-by: Claude:opus-5-5
Away and do-not-disturb with status texts both ways. aim and web drop
each other, aim re-adds web, web's Accept also asks back, aim accepts:
both roster pushes end subscription='both'.

Assisted-by: Claude:opus-5-5
Presence both ways (aim Away with text shown on web, web Do not disturb
with text shown on aim). aim removed vm-web, web removed aim, aim
re-added, web approved and asked back, aim approved; the roster push
ends in subscription both. Two earlier tries were harness fails (the
ask-back wait, then the half-done roster it left) and are not
recorded.

Assisted-by: Claude:opus-5-5
aim blocks vm-web from its Block… dialog: <block/>, the server's push,
the Blocked badge. web's line bounces with service-unavailable and
never reaches aim; after <unblock/> the next one arrives.

Assisted-by: Claude:opus-5-5
anotherim-web's My profile is narrower than aim's: the picture icon
(633,155) opens Chromium's "Open File" chooser and an x beside it
removes a picture (which works). A file picked in the chooser never
published in a prosody REPL (no console error; harness or web, not
traced), and aim's set_avatar clicked web's avatar, which opens nothing,
so the XEP-0084/0153 runs with web had no web picture (two parked). web
now tries its picker, and after 30 s publishes through the app's API
(avatar.publish), logged in steps.log.

Assisted-by: Claude:opus-5-5
Dino 0.5.1 shows a pending subscription inside the chat ("This contact
would like to add you to their contact list": Accept / Deny) and, when
it gets no presence, "You do not receive status updates from this
contact yet." with "Send request" (subscription_notification.vala at
v0.5.1). approve() presses Accept, add_contact() the Send request of an
existing chat (profile.py's ask-back). Its Accept also asks back.

set_avatar(): Preferences (Ctrl+comma) > Accounts > the account; with
no picture the letter tile has one pencil at (687,319), with one a
light button pair, pencil (652,319) and trash (687,319): the pixel at
(645,319) tells which. set_avatar(None) presses the trash, which
deletes the urn:xmpp:avatar:metadata node (Dino's own tile keeps the
picture until restart).

Tried in a REPL on ejabberd: aim removed and re-added vm-dino, Dino
accepted and asked back, aim approved, the push ended in subscription
both; Dino published bars, then a gradient, then deleted the node.

Assisted-by: Claude:opus-5-5
anotherim-server names no urn:xmpp:reporting:1, so aim's Block… dialog
offers no report for the stranger vm-web. The roster went back to
both.

Assisted-by: Claude:opus-5-5
profile.py's "published an avatar" and web's API fallback matched
urn:xmpp:avatar:data in any sent stanza, so web's own fetch of aim's
data item counted as web publishing, and the fallback never ran (prosody
run 135537, parked: web had no picture, both checks PASS). Both now
want <publish node='urn:xmpp:avatar:data'> (or a vCard <PHOTO>).

Assisted-by: Claude:opus-5-5
Nicks travel both ways and name the chats. After aim's empty <nick/>
and emptied vCard4 web's chat row and header go back to vm-aim, but
its Contact info keeps "Nickname Aim Kiwi" 45 s later: an anotherim-js
UI bug on the receiving side (Gajim drops the nick).

Assisted-by: Claude:opus-5-5
aim's picture goes out as data then metadata and web fetches and shows
it; web's (published through the app's API: its Open File picker did
nothing in the VM) reaches aim, which fetches its data item and shows
it in the list, header and contact info. Both cleared again with an
empty metadata from the UI.

Assisted-by: Claude:opus-5-5
Full names set in aim's and web's profile editors show in the other's
contact info (carried as vCard4); aim's vcard-temp read of web gets an
empty card, as with Gajim.

Assisted-by: Claude:opus-5-5
Both presences carry the SHA-1 of the new photo, and each side fetches
the other's vCard on it and shows the picture (web's set through the
app's API, its picker doing nothing in the VM). C12 not exercised.

Assisted-by: Claude:opus-5-5
Kaidan 0.16 asks for a subscription inside the chat (ChatHintModel:
"... would like to receive your personal data ...", Refuse / Allow);
its notification has only "Open". approve() presses Allow. Allow does
not ask back, and its Add contact dialog hangs for a roster contact,
so add_contact() then uses Contact Details > Privacy > "Request
personal data" (ask_presence).

Its own profile is Account Details (the drawer's account name): the
avatar circle opens "Change profile image" (Open image..., a Qt file
dialog; Save selection; Remove current profile image, present only
with a picture, told by the grey button's pixel), and the Profile
card's Name row edits FN. Both are written as vcard-temp (XEP-0153 /
XEP-0054; Kaidan has no XEP-0084).

Tried in a REPL on ejabberd: aim removed and re-added vm-kaidan,
Kaidan allowed, asked back, aim approved, subscription both; Kaidan
set and removed a picture and set and cleared its full name.

Assisted-by: Claude:opus-5-5
aim's gradient and web's bars avatars travel as metadata events, each
side fetches the data node and draws the other's picture.

Assisted-by: Claude:opus-5-5
Nicks cross both ways. After aim's empty <nick/>, web (the same UI as
aim) drops the nick from its header and list but keeps "Aim Kiwi" as
the contact column's title, letter and Nickname row: the app keeps a
cleared nick in its contact details (new, cause libtacky / UI).

Assisted-by: Claude:opus-5-5
aim's gradient avatar (My profile) reaches web, which shows it in
vm-aim's contact info; web's colour bars, published through its API
because web's picker published nothing, reach aim, which shows them in
the row, header and contact info. Both removed afterwards.

Assisted-by: Claude:opus-5-5
Both presences carry the new photo hash and each side fetches the
other's vcard-temp PHOTO on it and draws the picture. C12 is not
exercised (aim had no avatar at sign-in).

Assisted-by: Claude:opus-5-5
Each full name shows in the other's contact column. Both write vCard4
from their Edit profile; each reads the other's vcard-temp, which
Prosody maps from the vCard4.

Assisted-by: Claude:opus-5-5
#1 C12: XEP-0153 §4.2 forbids advertising a photo hash before the
current vCard is read, and libtacky used to bind with a stored hash.
The batch-4 runs never had aim sign in with an avatar, so the fix was
not exercised. PART=0153 now restarts aim with its avatar still set and
checks that its first presences carry an empty <x/> until its own
vcard-temp result, then the hash.

Assisted-by: Claude:opus-5-5
Typing without sending sends <composing/> both ways; both show
"… typing" in the header and on the chat row.

Assisted-by: Claude:opus-5-5
Both advertise their vcard-temp❌update photo hash once a picture is
set and fetch the other's vCard PHOTO on the new hash; each shows the
other's picture. C12 is not exercised (no avatar at sign-in).

Assisted-by: Claude:opus-5-5
Typing without sending: composing then paused each way, shown as
"typing" in the other side's header and list row.

Assisted-by: Claude:opus-5-5
aim and web each send <idle/> when minimized and clear it when back;
web shows aim's device "was seen 1 minute ago". aim's display of an
idle stamp is the Gajim run's ("Last seen 6 minutes ago").

Assisted-by: Claude:opus-5-5
Nicks travel both ways. After aim's empty <nick/> and empty vCard4,
web's chat row and header go back to vm-aim, but its Contact info
keeps "Aim Kiwi" as title and Nickname: an anotherim-js UI bug on the
receiving side, as on anotherim-server; Gajim drops the nick.

Assisted-by: Claude:opus-5-5
The evidence named steps.log:5 for web's INFO line, which is line 6;
line 5 is aim's PASS. Both are quoted now.

Assisted-by: Claude:opus-5-5
With web as the peer the idle step restored web's window before aim's
screenshots, so aim drew web as active again ("Last seen recently",
anotherim-server run 142207) and the shot showed nothing of the idle
stamp aim had received. web is restored after aim's shots now.

Assisted-by: Claude:opus-5-5
Each side minimized sends <idle since/> at once; web shows aim's device
"was seen 1 minute ago", aim shows web as "Last seen recently" (idle
for 1.5 minutes).

Assisted-by: Claude:opus-5-5
vm-web's and vm-aim's avatar circles carry their XEP-0392 reference
colours in both clients (302.9° and 264.4°), measured on the shots.

Assisted-by: Claude:opus-5-5
Each side's full name, set in the profile editor, shows in the other's
contact info. Both write XEP-0292 vCard4; aim's vcard-temp read of
vm-web is the only vcard-temp traffic (no mirroring on ejabberd).

Assisted-by: Claude:opus-5-5
On prosody anotherim-web, blocked by aim, could not send at all: Prosody
hides aim's OMEMO device lists from a blocked contact, so web showed
"no devices on vm-aim's v2 or legacy device lists" with Retry and
send() timed out, failing the step before the unblock (run 142857,
parked). That is the block working; the step now logs it, shoots the
peer and goes on to the unblock.

Assisted-by: Claude:opus-5-5
Unsent text sends <composing/> then <paused/> both ways; each side
shows "typing" in the header and the chat row.

Assisted-by: Claude:opus-5-5
aim blocked vm-web (block set and push); web's line meanwhile was
bounced to web as service-unavailable and never reached aim. After
the unblock web's next line arrived. Nothing left blocked.

Assisted-by: Claude:opus-5-5
approve: Dino shows a subscription request as a bar in the contact's
chat ("This contact would like to add you to their contact list",
Accept / Deny); Accept approves and asks back (REPL on anotherim-server:
subscribed and subscribe, then both).

set_avatar: Preferences (Ctrl+comma, its own window) › the account row ›
the avatar's pencil, a GTK file chooser; with a picture set the pencil
moves left of a trash, which deletes the urn:xmpp:avatar:metadata node.
Its buttons take a hover and a slow press, like the details dialog's.

Assisted-by: Claude:opus-5-5
aim's block and unblock work and nothing from web reaches aim while
blocked. But web (libtacky) could not send at all: its OMEMO device-
list fetch of vm-aim got service-unavailable while blocked, and after
the unblock it never fetched again, so its next line failed with "no
devices on vm-aim's ... device lists" (new, cause libtacky).

Assisted-by: Claude:opus-5-5
All three batch-4 agents added Dino's approve and set_avatar at once
(f5deb7f, 256bdb1, 70a1b9c), so clients/dino.py held three of each and
Python used the last. The earlier two and the helpers only they used
(_account_page_dino, _pref_press_dino) go; the kept ones are the ones
every run since used. _chat_bar_dino and _add_contact_dino ("Send
request" for the ask-back) stay.

Assisted-by: Claude:opus-5-5
ejabberd's disco#info names no urn:xmpp:reporting:1, so aim's Block
dialog offers no report box. web's stranger line arrived; the roster
is mutual again afterwards and nothing is blocked.

Assisted-by: Claude:opus-5-5
Dino logs element text on its own indented line (<show>, newline,
away, newline, </show>), so "dino got aim's away presence" failed
although Dino had it (anotherim-server run 144128, peer-dino.log:6971).
The show's text may now be wrapped in whitespace.

Assisted-by: Claude:opus-5-5
vm-web's avatar colour in aim and web's own both have the XEP-0392
hue of vm-web (267.1 deg, measured 267.1); vm-aim's in web and aim's
own that of vm-aim (66.5 deg, measured 66.7).

Assisted-by: Claude:opus-5-5
aim's away presence with its status text reaches Dino (no presence
selector or display in Dino 0.5.1). aim removes and re-adds vm-dino,
Dino's request bar Accept approves and asks back, aim accepts: both
rosters end both. The away check's FAIL line was a harness false
negative on Dino's multi-line log.

Assisted-by: Claude:opus-5-5
Prosody names no urn:xmpp:reporting:1, so aim's Block dialog offers no
report for the stranger's line; nothing reported or blocked. The
restore re-added vm-web, subscription both.

Assisted-by: Claude:opus-5-5
set_avatar(None) pressed the trash through _press_dino, which activates
the window named Dino, not Preferences: nothing was deleted and Dino
kept its avatar (anotherim-server run 144950, no <delete/> in its log).
In a REPL the trash deleted the urn:xmpp:avatar:metadata node only
with Preferences active and the avatar hovered first.

Assisted-by: Claude:opus-5-5
Each side, minimized, sends <idle since/> in its presence; the other
shows the device's last-seen time in contact info. aim drops <idle/>
once restored.

Assisted-by: Claude:opus-5-5
aim's gradient avatar reaches Dino as a metadata event and Dino draws
it after fetching the data; Dino's bars avatar, set from its
Preferences, shows on aim's row, header and contact info.

Assisted-by: Claude:opus-5-5
Sampled from the shots, vm-aim's letter avatar is rgb(233,0,106) and
vm-web's rgb(186,94,0) on both sides, the hues of their reference
angles (358 and 31 deg). Same app on both sides: consistency only.

Assisted-by: Claude:opus-5-5
#1 C12 reproduced with libtacky v1.2.3: restarted with its avatar set,
aim sends its initial presence with the stored photo hash 0.23 s
before its own vCard's result (§4.2). The exchange itself works: aim
fetches Dino's vCard PHOTO on its hash; Dino shows aim's picture from
PEP.

Assisted-by: Claude:opus-5-5
aim's Away with text reaches Dino (Dino 0.5.1 shows no presence states
and has no selector: that leg is one-way). aim removed and re-added
vm-dino; Dino accepted from its chat bar and asked back, aim approved;
the roster push ends in subscription both.

Assisted-by: Claude:opus-5-5
aim's away presence reaches Dino (which shows no presence state and
sets none). aim removed and re-added vm-dino; Dino accepted from its
chat bar and asked back, aim accepted, the item ends both.

Assisted-by: Claude:opus-5-5
Unsent typing sends <composing/> both ways; Dino shows "vm-aim is
typing…", aim "… typing" in the header and the chat row.

Assisted-by: Claude:opus-5-5
aim's gradient avatar reaches Dino, Dino's colour bars (set from its
Preferences) reach aim; each shows the other's. Dino removes its
picture by deleting the metadata node, of which ejabberd tells aim
nothing, so aim keeps showing the bars for vm-dino afterwards.

Assisted-by: Claude:opus-5-5
No letter avatars left to compare: Dino keeps drawing aim's avatar
after aim's empty <metadata/>, and aim keeps Dino's picture since Dino
deletes its metadata node (no event reached aim). aim's own colour is
right; an exploration before the avatar runs saw equal hues.

Assisted-by: Claude:opus-5-5
aim's picture goes out as data then metadata and Dino shows it; Dino's
reaches aim, which fetches its data item and shows it. Dino removes its
picture by deleting the metadata node, and aim keeps showing the old
picture after that (outside the scenario, reported separately).

Assisted-by: Claude:opus-5-5
#1 C12 reproduced: restarted with its avatar set, aim's first presence
carries the stored vcard-temp❌update photo hash before its own
vCard's result (XEP-0153 §4.2), a deviation libtacky documents for an
Android cold start. Avatars themselves cross both ways.

Assisted-by: Claude:opus-5-5
The pictures cross both ways (Dino's through Prosody's vCard mapping).
#1 C12 reproduced: restarted with an avatar, aim's first presence
carries the kept photo hash before its own vCard result arrives
(XEP-0153 §4.2; a documented upstream deviation).

Assisted-by: Claude:opus-5-5
Unsent text sends <composing/> then <paused/> both ways; Dino shows
"vm-aim is typing", aim shows "typing" in the header and the row.

Assisted-by: Claude:opus-5-5
Typing without sending: aim's composing shows as "vm-aim is typing..."
in Dino, Dino's as "typing" in aim's header and list.

Assisted-by: Claude:opus-5-5
Dino shows pictures for vm-aim (kept after aim cleared its avatar)
and itself, so the comparison uses JIDs both draw as letter tiles:
vm-gajim and a room have the same colour in aim and Dino, at the
XEP-0392 reference hue within a degree.

Assisted-by: Claude:opus-5-5
Assisted-by: Claude:opus-5-5
aim's away presence reaches Kaidan. aim removes and re-adds
vm-kaidan; Kaidan's Allow approves, its "Request personal data" asks
back, aim accepts: both rosters end both.

Assisted-by: Claude:opus-5-5
vm-dino is rgb(216,0,193) and vm-aim rgb(233,0,106) (234 in Dino) in
both clients. This run's shots show stale pictures instead of letters
on both sides (Dino ignores aim's empty avatar metadata, aim ignores
Dino's node delete), so the letters come from this batch's earlier
prosody shots taken before either had a picture.

Assisted-by: Claude:opus-5-5
The vCard avatar exchange works both ways (each side fetches the
other's PHOTO on its hash), but #1 C12 reproduces again: restarted
with its avatar set, aim's initial presence carries the stored hash
before its own vCard's result (§4.2).

Assisted-by: Claude:opus-5-5
aim's Away with text reaches Kaidan (Kaidan 0.16 has no presence
selector: one-way). aim removed and re-added vm-kaidan; Kaidan allowed
from its chat hint and asked from its contact details, aim approved;
the roster push ends in subscription both.

Assisted-by: Claude:opus-5-5
aim's away presence reaches Kaidan. aim removed and re-added
vm-kaidan; Kaidan's Allow approved and its "Request personal data"
asked back, aim accepted, and the item ends both.

Assisted-by: Claude:opus-5-5
aim reads Kaidan's vcard-temp FN ("kaidan Mango"), but aim writes its
own full name only as an XEP-0292 vCard4 item: Kaidan, which reads
vcard-temp only, gets aim's card without a name (the server mirrors
nothing). Gajim and web passed because they read vCard4.

Assisted-by: Claude:opus-5-5
The vCard avatar exchange works both ways (aim fetches Kaidan's PHOTO
on its new hash; Kaidan shows aim's picture). #1 C12 reproduced: after
a restart with its avatar set, aim's first presence carries the stored
photo hash before its own vCard's result (XEP-0153 §4.2).

Assisted-by: Claude:opus-5-5
aim's clear <composing/> makes Kaidan show "vm-aim is typing…";
Kaidan sends its chat states inside OMEMO 2 envelopes, which aim
decrypts and shows as "… typing". The run's FAIL line was the
harness looking for a clear chat state.

Assisted-by: Claude:opus-5-5
aim's clear <composing/> is client.log:592, not :600 (a paused one is
near there); evidence and notes now point at it.

Assisted-by: Claude:opus-5-5
The pictures cross both ways (Kaidan's vCard PHOTO, which Prosody also
maps to XEP-0084; Kaidan never advertises its hash). #1 C12 reproduced:
after a restart aim's first presence carries the kept hash before its
own vCard result.

Assisted-by: Claude:opus-5-5
Kaidan's vcard-temp full name reaches aim. aim's own full name never
reaches Kaidan: anotherim-js writes the profile only as XEP-0292
vCard4, ejabberd does not mirror it into vcard-temp, and Kaidan
(vcard-temp only) reads an empty card for vm-aim.

Assisted-by: Claude:opus-5-5
vm-kaidan is drawn at 140° in aim and in Kaidan's own Notes chat,
vm-aim at 264° in Kaidan and in aim's own avatar: the same XEP-0392
hues, measured on the shots.

Assisted-by: Claude:opus-5-5
Kaidan loads a chat page a few seconds after opening it; contact_info
clicked the header at once and the XEP-0054 shot showed the bare chat
without its Contact Details (prosody run 153131). It now waits 5 s and
clicks again when the dialog's title is not there.

Assisted-by: Claude:opus-5-5
Assisted-by: Claude:opus-5-5
aim's <composing/> shows as "vm-aim is typing" in Kaidan. Kaidan's own
chat states travel inside its OMEMO 2 envelope; aim decrypts them and
shows "typing". The step's FAIL is the harness looking for a clear
<composing/>.

Assisted-by: Claude:opus-5-5
Kaidan draws vm-aim's letter avatar in the hue aim gives itself (66.7
deg, reference 66.5); aim draws vm-kaidan at its reference (19.8).

Assisted-by: Claude:opus-5-5
aim's clear composing shows as typing in Kaidan; Kaidan sends its chat
states inside OMEMO 2, which aim decrypts and shows as "typing" in its
header and list. The steps.log FAIL is the harness waiting for a clear
<composing/> from Kaidan.

Assisted-by: Claude:opus-5-5
Kaidan 0.16 under OMEMO 2 sends its chat states inside the envelope
(SCE), so profile.py's typing step found no clear <composing/> and
failed although aim showed "typing". The step now accepts a bodiless
OMEMO 2 envelope from the typing peer and leaves the judging to the
shot. Kaidan's missing presence selector is named in NOT_SUPPORTED.

AGENTS.md: Dino's and Kaidan's roster and profile positions, Gajim's
chat list width and idle monitor, the batch 4 results on ejabberd and
what was learned there.

Assisted-by: Claude:opus-5-5
The results of the 31 anotherim-server runs (C12 reproduced twice,
the vCard4-only profile against Kaidan, web's stale nickname in
Contact info, Dino's stale avatars) and what the harness learned:
profile.py's PART knob, how Gajim, Dino and Kaidan approve and ask
back, the lagging logs, Dino's multi-line log and Preferences window,
Gajim's idle monitor, Kaidan's encrypted chat states, xep0313's
PART=reset and tools/xep0392.py. What a PART run leaves on the
accounts goes to the H4/H5 section.

Re-run in explore mode after the last fixes: PART=0084 with Dino
(its trash deletes the avatar:metadata node now) and PART=0319 with
web (aim's shot shows web "was seen 2 minutes ago").

Assisted-by: Claude:opus-5-5
The retry of 669e257 clicked the header again 3 s after the first
click, while Kaidan's Contact Details still faded in, and so closed it
(prosody run 154253: the shot shows the chat dimming back). Now it
waits 8 s for the chat page, then up to 10 s for the dialog's title,
and clicks again only when none came.

Assisted-by: Claude:opus-5-5
PLAN's 0313 r case on ejabberd: net_reset() makes ejabberd see the
stream die, the link stays down past the 300 s resume max while Gajim
sends twice. aim's resume fails ("Previous session timed out"), and
the new session's MAM query returns both lines with their original
delay stamps; aim shows them at the times they were sent.

Assisted-by: Claude:opus-5-5
vm-aim is rgb(233,0,106) in both aim and Kaidan; vm-kaidan is
rgb(176,100,0) in aim's earlier shot and (175,100,0) as Kaidan draws
itself. In this run aim shows a picture for vm-kaidan, the XEP-0084
avatar Prosody keeps after Kaidan dropped its vCard PHOTO.

Assisted-by: Claude:opus-5-5
aim blocked vm-dino (block set and push, "Blocked" on the row); Dino's
line meanwhile was bounced as service-unavailable and never reached
aim. After the unblock Dino's next line arrived. Nothing left blocked.

Assisted-by: Claude:opus-5-5
Kaidan writes its name as vcard-temp FN, which aim reads and shows as
"kaidan Mango". aim's vCard4 name reaches Kaidan as vcard-temp through
Prosody's mapping; Kaidan's Contact Details would not draw in the VM,
so its display of it is not shown.

Assisted-by: Claude:opus-5-5
Two batch-4 agents added these to clients/kaidan.py at once, so it held
two of each and Python used the later ones (ee7f057, which every Kaidan
run since used). The earlier copies and their only helper
(_account_details_kaidan) go; _ask_presence_kaidan stays (add_contact's
fallback), beside request_presence for profile.py's re-add.

Assisted-by: Claude:opus-5-5
aim blocked vm-kaidan (block set and push, "Blocked" on the row);
Kaidan's line meanwhile was bounced as service-unavailable and never
reached aim. After the unblock Kaidan's next line arrived. Nothing
left blocked.

Assisted-by: Claude:opus-5-5
Gajim draws vm-aim at aim's own hue and aim draws vm-gajim at Gajim's
own, both at the XEP-0392 reference within a degree (Gajim at a lower
lightness).

Assisted-by: Claude:opus-5-5
Assisted-by: Claude:opus-5-5
The results of the 32 prosody runs, what was seen beside them (stale
avatars after a node delete or an empty metadata, Prosody's avatar
mapping and UUID photo hashes, Kaidan's missing presence hash), and the
harness lessons: Gajim's Add Contact buttons and late Profile rows,
aim's row menu after a nick, web's avatar picker and API fallback, a
blocked OMEMO peer that cannot send, Kaidan's and Dino's request bars,
avatar and vCard pages, XEP-0392 by pixels, and three agents adding the
same client action at once.

Assisted-by: Claude:opus-5-5
The resumption case r: aim's TCP aborted and its link down 654 s, past
Prosody's 600 s session hold, while Gajim sent two lines. Back online
the SASL2 inline resume is refused, a new session queries the archive
and gets both with their original delay stamps; aim shows each once at
its send time.

Assisted-by: Claude:opus-5-5
vms: AGENTS.md's prosody batch 4 counts, Gajim restore checked
All checks were successful
/ check (pull_request) Successful in 13m45s
8989474931
30 recorded runs on prosody, one per cell (24 pass, 4 fail, 2 na). An
explore run of PART=0054 with Gajim after 68ef53d shows its restore now
publishes the empty vCard4 (vm-gajim's Full Name cleared).

Assisted-by: Claude:opus-5-5
asxpi merged commit 17093463ec into main 2026-11-10 16:37:17 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
another-im/interop!26
No description provided.