Close the gaps an audit of courses, tasks, files and grades turned up
Every area — courses, rooms, boards, topics, tasks, files, quizzes, teams,
groups, submissions, grades — was checked for data the instance has and the
tools did not show.
Grades and feedback. A teacher's /homework page is a different page from a
student's: grade and comment live in the grading form, one block per
submission, so a teacher account reported every graded submission as having
neither. parseTeacherGrading reads the form, and list_submissions can now
include the written feedback and who handed the work in.
Names. /api/v1 is partly served: courses, users and classes survive in the
deployment's ingress table, and users/{id} is the only route from an id to a
name. Submitters, file creators and course teachers resolve through it, and
degrade to "not visible to this account" where a student may not read them.
Courses, rooms and classes. get_course adds the description, teachers,
member count and weekly timetable from /api/v1/courses. list_classes is new.
get_room reports what the account may do — allowedOperations is an object of
booleans, not the list it was typed as — and applicants and invitation links
where it may manage them.
Board and topic content. Link descriptions, image alt text, drawing and
video-conference titles, the ids behind external tools and H5P content (the
only thing resembling a quiz), and what a deleted element used to be. Topic
Etherpad pads are read like board pads, and htmlToText keeps table columns
apart and drops template indentation.
Files. A scan with no text layer falls back to the preview endpoint, whose
width and outputFormat are undocumented enums, so Claude gets a picture of
the page; list_files reports counts and sizes. Teams stay documented as
unreadable at any API version; their files come later.
What the crawl missed. Tasks attached to topics (18 of 60 on the live
account), each course's own file area, and — behind INDEX_PERSONAL_FILES —
personal files and submissions with their grade comments, so search and
what_changed cover grading. A submission hit points at get_task.
The local instance's preview profile gets an ImageMagick policy that allows
the coders its 7.1.2 build needs; the image's own denies them all.
110 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
50
CLAUDE.md
50
CLAUDE.md
@@ -70,7 +70,12 @@ bin/cli.ts ──HTTP──────────┘ cli/{config,client,sync
|
||||
- **`store/`** — crawl generations, identity diffs, `german` + `pg_trgm` FTS.
|
||||
`Store.open` returns `undefined` when Postgres is down; callers degrade.
|
||||
- **`indexer/`** — crawl → persist → mirror bytes → extract text → index.
|
||||
Coalesces concurrent refreshes; enforces a minimum interval.
|
||||
Coalesces concurrent refreshes; enforces a minimum interval. The crawl walks
|
||||
topic-attached tasks too, which the course page does not list: without that
|
||||
they are unsearchable and their grades invisible. `INDEX_PERSONAL_FILES`
|
||||
additionally indexes personal files and submitted/returned work, including
|
||||
grade comments — that is what makes "what got graded this week" answerable,
|
||||
at roughly three extra requests per task.
|
||||
- **`mcp/tools/*.ts`** — tool descriptions are prompts: they are how Claude picks
|
||||
a tool, so they carry the German domain terms (Kurse, Themen, Aufgaben) and say
|
||||
when *not* to use the tool.
|
||||
@@ -146,8 +151,25 @@ These cost real time to discover; `docs/API.md` has the full list with evidence.
|
||||
so the store's digest has to include the name.
|
||||
- **Submissions: only `GET /submissions/status/task/{taskId}` exists.** No list,
|
||||
no fetch-by-id, and the payload has no submitted text, grade comment or
|
||||
graded-at — `/api/v1`, which had them, is not served here. Don't imply absent
|
||||
feedback means none was given.
|
||||
graded-at. Don't imply absent feedback means none was given.
|
||||
- **`/api/v1` is partly served, and it is production surface.** Exactly three
|
||||
legacy routes survive in the deployment's own ingress table
|
||||
(`dof_app_deploy/ansible/group_vars/all/x_ingress.yml`): **`/api/v1/courses`,
|
||||
`/api/v1/users`, `/api/v1/classes`**. Everything else under `/api/v1` is
|
||||
unrouted and 404s. They matter because v3 dropped things they still carry:
|
||||
`courses` has the description, `teacherIds`, `userIds` and `times` (the weekly
|
||||
timetable), and `users/{id}` is the **only** way to turn a user id into a name
|
||||
— submission `submitters`, file `creatorId` and course `teacherIds` are
|
||||
otherwise unreadable. Permission is per-account: a teacher may read their
|
||||
students, a student may read only themselves, so name resolution must degrade
|
||||
to "not visible to this account" rather than printing a bare id.
|
||||
- **The teacher's homework page is a different page from the student's.** Its
|
||||
tabs are `extended` and `submissions`, not `submission` and `feedback`, and
|
||||
the grade lives in the grading *form* (`name="grade"`, `name="gradeComment"`,
|
||||
one block per `submissionId`) rather than in rendered prose. The student
|
||||
parser finds nothing on it, which is why a teacher account reported every
|
||||
graded submission as "neither a percentage nor feedback was found" while the
|
||||
data was plainly there. `parseTeacherGrading` handles that side.
|
||||
- **A grade is a percentage (`Number` 0-100) or absent; there is no text grade.**
|
||||
Teachers commonly grade with `gradeComment` alone, so "graded by feedback" is
|
||||
a complete answer. `formatGradeState` in `mcp/tools/submissions.ts` owns that
|
||||
@@ -174,7 +196,27 @@ These cost real time to discover; `docs/API.md` has the full list with evidence.
|
||||
dedicated endpoints (`/boards/{id}`, `/cards`, file records) are stable.
|
||||
- Many course PDFs are **image-only scans with no text layer** (3 of 4 sampled),
|
||||
so extraction legitimately yields nothing. `extract.ts` detects this and says
|
||||
so; do not "fix" it by retrying.
|
||||
so; do not "fix" it by retrying. `download_file` then falls back to
|
||||
`GET /file/preview/...`, which renders the page as a picture Claude can read —
|
||||
the answer for a scan, though it still leaves the file unsearchable.
|
||||
- **The preview endpoint has two enums, and both 400 without saying so.**
|
||||
`width` accepts only **50, 150 or 500** — a number outside that set is a
|
||||
validation error naming the value but not the permitted set. `outputFormat`
|
||||
accepts only **`image/webp`**; omitting it is worse than wrong, because the
|
||||
preview is then rendered in the *source* format and a PDF comes back as a
|
||||
PDF. The response also labels itself `webp` rather than `image/webp`, so the
|
||||
content type has to be normalised before anything will treat it as an image.
|
||||
- **A room's `allowedOperations` is an object, not a list.** Every operation is
|
||||
present with a boolean; `false` means denied. Typing it as `string[]`
|
||||
type-checks and throws `.some is not a function` the moment anything reads it.
|
||||
- **Schulcloud has no quiz of its own.** There is no quiz module or endpoint
|
||||
upstream: interactive exercises are H5P elements, whose `contentId` is the
|
||||
only handle onto the content, or external (LTI) tools behind
|
||||
`contextExternalToolId`. Say that rather than looking for a quiz API.
|
||||
- **Teams cannot be read at any version.** v3 exposes only
|
||||
`GET /team/{teamId}/news`; upstream `main`'s teams controller is write-only
|
||||
(`POST :teamId/create-room`). `/teams` is the legacy client's HTML page, not
|
||||
an API. This one is genuinely unavailable, not merely uncovered.
|
||||
- **`exp` (30 days) is not the session lifetime.** The binding limit is a Valkey
|
||||
whitelist entry with a `JWT_TIMEOUT_SECONDS` TTL (7200s; live value at
|
||||
`GET /api/v3/config/public`) that every authenticated request re-sets.
|
||||
|
||||
Reference in New Issue
Block a user