• v0.55.0 ee800b3da1

    joeyr released this 2026-09-20 23:05:38 -05:00 | 56 commits to main since this release

    get_submission_feedback: the sentence half of a grade. C7, the last tool
    the Canvas build plan had left, and the only phase that was ever blocked by a
    calendar rather than by a decision — measured 2026-08-24, the endpoint worked and
    there were ZERO graded submissions across three courses, so there was nothing to
    read. Grading has started.

    get_grades returns a course total and list_assignments returns a per-assignment
    score. Neither carries a word of what the grader wrote, so "why did I lose those
    points" was the one coursework question this tenant could see the evidence of and
    not answer — the planner has been reporting has_feedback on items it could not
    open since C3.

    One request for a whole course. The bulk endpoint takes submission comments,
    their HTML twins and the rubric assessment in a single include[] set, so score
    and commentary are two calls rather than twelve. student_ids[] is omitted
    deliberately: Canvas answers the bare endpoint with the calling user's submissions
    and refuses a student who asks for anyone else's, so naming the account adds a
    parameter that can only make the request refusable.

    grouped is absent for a harder reason, and a test pins it. It silently
    disables order and order_direction — which would leave both accepted, ignored
    and invisible from the response, with the newest feedback quietly no longer landing
    on page one of a truncated read.

    A rubric assessment is unreadable on its own. Canvas returns it keyed by
    criterion id and nothing else — {"_1234": {"points": 4.5}} — while the
    descriptions live on the assignment's rubric, which is why include[]=assignment
    is sent and joined. An unjoinable row carries criterion_unresolved and a null
    name rather than the id dressed up as one. "blank" is a real Canvas rating id,
    not a null in disguise, and is resolved like any other.

    posted: false means WITHHELD, not empty. Canvas answers an unposted
    submission under a clean 200 with the score and the comments held back, so
    "nothing has been said" and "something has been said and not released" arrive in
    the same shape. Those rows are returned with a note saying so instead of being
    dropped for carrying no feedback — an ungraded submission, whose posted_at is
    also null, is not reported as withheld.

    Who wrote a comment is looked up, not inferred. A peer-reviewed assignment
    puts classmates in the same array, so "not the student's own, therefore the
    instructor's" is wrong rather than approximate. One roster request resolves it,
    made only when a comment exists to attribute; where it fails, author_is_instructor
    is ABSENT and the response says the role could not be confirmed. Same rule as
    is_self and for the same reason as 0.49.0's (unknown).

    Two more shapes that read as emptiness and are not. An audio or video comment
    arrives with no text at all and is flagged comment_is_media_only rather than
    reported as a blank remark; a quiz carries quiz_feedback_not_included, because
    per-question feedback lives in a Canvas API this server does not read — and both
    quiz engines are detected, since a New Quiz carries none of the classic signals.

    unread_only declines rather than emptying the list. Read state is reported by
    the single-assignment endpoint and not by the bulk one, so the filter is real on one
    path and refused on the other with unread_filter_not_applied saying which. It is
    measured BEFORE the filter runs: taken afterwards, a course where the user has read
    everything is indistinguishable from an endpoint that never reported read state, and
    "you are up to date" and "this filter did not run" are opposite answers.

    Narrowing to one assignment changes endpoint rather than parameters.
    assignment_ids[] on the bulk route has a standing report of returning no comments
    for an assignment the user has not submitted to, even where staff left them — the
    silent-emptiness failure this tenant keeps finding. /submissions/self is used
    instead, which is also the only route that reports read state.

    docs/canvas-plan.md is deleted. It was a queue that held only what was
    unbuilt and said to delete it when the last item was done — that was this tool.
    Its three outstanding LIVE checks moved to CLAUDE.md rather than dying with it,
    and docs/operations.md now points there for recording a session's absolute cap.

    A defect the tests caught before it shipped: a bounded read cannot report an
    absence.
    since is sent to Canvas as graded_since, so a windowed read comes
    back with a graded count of zero whether or not the course has ever been graded —
    and the empty-result note reached for "nothing has been graded yet" from a window
    that had excluded the evidence. The since case is now tested first and says the
    read was bounded. Generalised in CLAUDE.md.

    Canvas is at twelve tools and still has no write on its surface.

    Downloads