-
released this
2026-08-26 10:56:10 -05:00 | 65 commits to main since this releaselist_discussion_entriesreturned"(unknown)"as the author of every post
in a live thread, and it looked like an answer. Nineteen entries in a graded
introductions discussion, a clean200, no error, no flag — and a reader with
no way to tell "Canvas was asked and had no name" from "the lookup never
happened". The second one was true. The connector then reported the user's
required replies as 0 of 2 done when 1 was, stated as fact, because the
only attribution left was the prose: their initial post ended-Danieland was
found, their reply did not and was not.The cause is one field on one payload. A
/viewnode names its author with
a bareuser_id;user_nameand the nesteduserobject belong to
/entries, which this client stopped calling in 0.49.0 for being twice the
price. The name in a/viewresponse lives in theparticipantsroster and
nowhere else. The client readuser_name, got nothing every time, and had a
default sitting there to make it presentable.Two habits let it through, and both are worth naming.
A probe measured one payload and the design generalised to the other. C6
asked "what does an entry carry for identity" against/entriesrows —
correctly, that is whereuser_nameis — and the same session decided/view
on cost. Nothing re-asked the identity question of the payload that won.
A measurement is about the request that produced it. When a probe changes
which endpoint ships, every finding taken from the other one is unverified
again.The fixture agreed with the code because the same person wrote both. This
project's oldest failure mode, andtests/test_canvas.pyeven documented the
agreement as a feature:_entrycarrieduser_nameand the nesteduser
"on purpose", as the fields the tool must drop. They are not on a/viewnode
at all. 1399 tests were green against a payload Canvas does not send.And the default made it silent.
"(unknown)"is a plausible-looking string,
so a wrong answer built on it carries no seam. A fallback that cannot be
distinguished from a result is worse than no fallback.Fixed
-
authoris joined out of theparticipantsroster, which is where a
/viewpayload keeps names.user_nameis still preferred when a payload
carries one, so nothing regresses if this client ever reads/entriesagain.The privacy decision is unchanged. The roster is read, reduced to
{id: display_name}, and dropped — it covers people who never posted and
carries avatars and profile links, and none of that leaves the client. The
entry's ownuser_iddoes not leave either: a Canvas user id addresses a
profile on a public course host. -
An unresolved author is
author: nullwithauthor_unresolved: true,
never a string. The thread also reportsunresolved_authorswith a note
saying the authorship is UNKNOWN rather than anonymous — do not attribute
those posts, and do not count them as anybody's reply.
Added
-
is_selfon every entry, from the account's own Canvas id rather than
from the text. "Have I already replied" is the question a graded discussion
forces, and text-matching answers it wrongly the moment somebody does not
sign a post.It is absent, not
false, when the account could not be identified. A
falsethere answers the question wrongly instead of declining it, which is
this release's own defect one layer down. -
own_entry_count,own_reply_countandpeople_replied_toon the
thread.own_reply_countcounts only posts answering somebody else, and
people_replied_tocounts distinct people — because "reply to two
classmates" is not satisfied by two replies to one of them, nor by continuing
your own sub-thread, and a withdrawn post is not a completed one.Counted across the whole tree rather than the returned slice, which is
the reason the client derives them instead of leaving the caller to tally
is_selfrows. Pastmore_entriesthe user's own reply can sit at 101; a
tally of the returned rows would say zero and read as a fact.people_replied_tosays people, not classmates, on purpose —/view
names participants without their role, so the instructor is indistinguishable
from a student here. -
own_participation_unknownwith a note when the account could not be
identified: none of those counts are present, and the note forbids the
fallback that caused all of this — reading the prose for a name. -
_self_user_id— one/api/v1/users/self, cached for the process. It
runs after the thread is read and swallows every failure, including an
expired session: ordered the other way, a bad answer from it would take down
a tool that reads threads perfectly well without it.check_sessionwas the
obvious home and was declined — boot's request exists to prove the account
can see coursework, and failing a boot over a field only one tool reads
trades a working tenant for a convenience.
Changed
-
tests/test_canvas.py's/viewfixture now matches the payload Canvas
sends — nodes carryuser_idand no name, and_viewbuilds the roster
the names have to be joined out of. The old fixture is the reason this was
invisible to the suite, so the docstring on_entrysays not to put a name
back on a node. -
The thread log line carries
unresolved_authors,identityandown.
The first is the number that would have named this defect from the log alone
— 19 of 19, for a release — on a line nobody had a reason to read. Counts
only: no id and no name, as before.
Not verified live
The fix is proved against the suite and not against Canvas. The session
cookie this tenant runs on lives in a Swarm secret; there is no working local
credential, so the shape of a/viewpayload here rests on the failure report
(19 of 19 unresolved, which only the missing field explains) and on
/api/v1/users/selfbeing an unmeasured request. Read the boot log and one
thread through the deployed connector before calling this closed — this
project's standing rule that a live pass finds what a green suite cannot has
now been paid for twice on this tool.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-