T13: fit.today + fit.deadlines aggregates #12

Merged
dzenda merged 74 commits from t13-aggregates into t12-marast 2026-11-14 17:50:16 +01:00
Owner

T13: Aggregates (fit.today, fit.deadlines)

Stacked on #11 (t12-marast) → … → #1. Merge those first.

What

  • fit.today({ date? }): that day's classes and exams (Sirius), slimmed down to type, course, course name, time, room, teacher names and moved. Plus dueSoon: Marast quizzes closing that day or within the 2 days after. date defaults to today in Europe/Prague.
  • fit.deadlines({ from?, to? }): a merged, time-sorted list of exams and assessments (Sirius, the terms the user is registered for) and Marast quizzes (by closing time). Defaults to today + 14 days, with a maximum of 120 days. Items have kind, title, course, at, a Prague date, source, room and opensAt.
  • Partial failure is never total failure: each source runs independently. A failing source becomes a warnings entry built from the safe toErrorPayload codes (reauth_required, upstream_error, …), de-duplicated per source and code. Upstream bodies never appear.
  • Completeness is never faked: a capped Sirius result adds a truncated warning. Quizzes that already closed inside the range are included, because we fetch all quizzes and filter by date.
  • Aggregates call the provider handlers directly, so there's no extra HTTP round trip. They're listed right after fit.status, so search and describe show them early, as SPEC §6 intends for one-call answers.

Verified

  • bun run lint && bun run typecheck && bun run test: 171 tests, 11 of them for the aggregates. They cover today's classes plus due-soon, a failing source returning a safe warning, an explicit date, the merge and sort of exams, assessments and quizzes, range filtering with de-duplicated warnings, range validation, closed quizzes in past ranges and earlier today, truncation warnings, and Czech and English search ("co mám dnes", "what's on tomorrow", "upcoming deadlines").
  • bun run dev + curl /api/functions lists 11 functions.

Astra review (gpt-6-astra)

Fixed (both):

  1. (P2) Quizzes that closed within a requested past range, or earlier today, were silently omitted, because only open and future quizzes were fetched → now fetches all and filters by date. Tests added.
  2. (P2) Sirius truncated was dropped, so capped results looked complete → truncated warning. Test added.

🤖 Generated with Claude Code

## T13: Aggregates (`fit.today`, `fit.deadlines`) **Stacked on #11 (`t12-marast`) → … → #1.** Merge those first. ### What - **`fit.today({ date? })`:** that day's classes and exams (Sirius), slimmed down to type, course, course name, time, room, teacher names and `moved`. Plus `dueSoon`: Marast quizzes closing that day or within the 2 days after. `date` defaults to today in Europe/Prague. - **`fit.deadlines({ from?, to? })`:** a merged, time-sorted list of **exams and assessments** (Sirius, the terms the user is registered for) and **Marast quizzes** (by closing time). Defaults to today + 14 days, with a maximum of 120 days. Items have `kind`, `title`, `course`, `at`, a Prague `date`, `source`, `room` and `opensAt`. - **Partial failure is never total failure:** each source runs independently. A failing source becomes a `warnings` entry built from the safe `toErrorPayload` codes (`reauth_required`, `upstream_error`, …), de-duplicated per source and code. Upstream bodies never appear. - **Completeness is never faked:** a capped Sirius result adds a `truncated` warning. Quizzes that already closed inside the range are included, because we fetch all quizzes and filter by date. - Aggregates call the provider handlers directly, so there's no extra HTTP round trip. They're listed right after `fit.status`, so `search` and `describe` show them early, as SPEC §6 intends for one-call answers. ### Verified - `bun run lint && bun run typecheck && bun run test`: 171 tests, 11 of them for the aggregates. They cover today's classes plus due-soon, a failing source returning a safe warning, an explicit date, the merge and sort of exams, assessments and quizzes, range filtering with de-duplicated warnings, range validation, closed quizzes in past ranges and earlier today, truncation warnings, and Czech and English search ("co mám dnes", "what's on tomorrow", "upcoming deadlines"). - `bun run dev` + `curl /api/functions` lists 11 functions. ### Astra review (gpt-6-astra) **Fixed (both):** 1. *(P2)* Quizzes that closed within a requested past range, or earlier today, were silently omitted, because only open and future quizzes were fetched → now fetches all and filters by date. Tests added. 2. *(P2)* Sirius `truncated` was dropped, so capped results looked complete → `truncated` warning. Test added. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fixtures:record: reads the encrypted token from local D1, GET-only, never refreshes,
  scrubs PII into git-ignored test/fixtures/live/, prints shapes only
- Marast: FIT token goes in Api-Key (Bearer is 401); only_open is 401 → filter locally
- Usermap: fields may be null (preferredEmail) → nullish schemas
- Grades: precise error for missing OAuth app resource; docs on scopes vs. resources

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also fixes a production bug found while smoke-testing: createFnContext stored the global
fetch on the context object, so ctx.fetch(...) threw "Illegal invocation" in workerd. That
broke every registry call in the Worker (including all MCP tool calls); Node tests could
not see it. Regression test emulates workerd's this-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ALLOWED_USERS enforced for MCP too (shared policy); excluded users' grants revoked on use
- telemetry: streaming 512-byte cap before parsing
- POST /oauth/register rate-limited per IP and instance-wide (Workers rate-limit bindings)
- FIT API bodies capped at 2 MB (streaming); Grades tree walk iterative and bounded
- readBounded moved to src/lib/bounded.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logos: FIT service marks vendored in public/services (see README there); assistant marks
from @lobehub/icons (MIT), simple-icons (CC0) and devicon (MIT).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- service_settings table (migration 0002); MCP calls to a switched-off service fail with
  service_disabled, aggregates skip it with a warning, fit.status lists it
- setServiceEnabledFn: same-origin + session only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fit.sirius.courseEvents: every parallel of a course (room, teachers, capacity/occupied),
  shared paging helper with fit.sirius.events; respects the Sirius MCP switch
- getCourseWeek server fn for the calendar overlay; upcoming deadlines (60 days) beside the week

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- layoutLanes/pragueMinutes in src/lib/calendar.ts (tested)
- shared EventDetails popover; agenda list stays on mobile
- agentSetupPrompt in src/mcp/setup.ts (tested)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Referrer-Policy: same-origin (no-referrer made browsers send Origin: null on our form POSTs,
  so every management form was rejected with 403); shared isSameOriginRequest accepts
  Origin: null only with Sec-Fetch-Site: same-origin
- No COOP on OAuth/consent/login routes (popup-based web MCP clients keep window.opener)
- fit.sirius.icalUrl is websiteOnly: excluded from the MCP registry (URL embeds a credential)
- docs: telemetry wording (operator-enabled, consent advice), CSRF/COOP/credential notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #25
Reviewed-on: #24
Reviewed-on: #23
Reviewed-on: #22
Reviewed-on: #21
Reviewed-on: #20
Reviewed-on: #19
Reviewed-on: #18
Reviewed-on: #17
Reviewed-on: #16
Reviewed-on: #15
Reviewed-on: #14
Reviewed-on: #13
dzenda merged commit b1583746e9 into t12-marast 2026-11-14 17:50:16 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
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
dzenda/os!12
No description provided.