T8: typed fit.* API registry + fit.status #7

Merged
dzenda merged 93 commits from t8-registry into t6-better-auth 2026-11-14 17:50:45 +01:00
Owner

T8: typed fit.* API registry

Stacked on #6 (t6-better-auth) → … → #1. Merge those first.

What

  • defineFn({ path, summary, docs, keywords?, requires?, input: zod, output: zod, examples?, handler }) with full input/output type inference. defineNamespace for docs shared by all functions in a namespace.
  • createRegistry(fns, namespaces):
    • list, search(query, limit≤50), describe(paths≤50), call(path, args, ctx).
    • describe returns JSON Schemas for input and output, generated with z.toJSONSchema.
    • call validates the input, runs the handler, then validates the output. Upstream shape drift shows up as a generic internal_error, with details kept server-side.
    • Paths are validated (fit.a.bC), and duplicates are rejected.
  • Search: weighted keyword search over path, keywords, summary, namespace context and docs. It strips diacritics (zkouška = zkouska), matches prefixes from 3 characters, and ranks an exact path first. Keywords carry the Czech synonyms.
  • Errors (toErrorPayload): unknown_function, invalid_arguments, not_connected (with the connect URL; NotConnectedError from T4), reauth_required, upstream_error, temporarily_unavailable and internal_error.
    • Unknown errors never echo their text. That matters because upstream bodies can contain tokens.
    • Validation messages are built from issue codes and schema-defined values only.
  • FnContext gives handlers getToken(provider) (the T4 token store), listConnections(), fetch, now and connectUrl. Tokens are only reachable through getToken, and outputs are schema-validated. createFnContext binds a token store to one user.
  • fit.status: which providers are connected and with which scopes, never credentials. It's the first function to call when something reports not_connected.
  • GET /api/functions: a public API reference (namespaces, functions, schemas; no user data). The MCP install page (T21) can link to it.

Verified

  • bun run lint && bun run typecheck && bun run test: 111 tests, 22 of them for the registry. They cover path validation, search (including Czech with and without diacritics, namespace keywords, prefixes, exact path, gibberish and limits), describe/schemas, call input and output validation, error mapping and no-echo guarantees, and fit.status.
  • bun run dev + curl /api/functions returns the fit namespace and fit.status with input and output schemas.

Astra review (gpt-6-astra)

Fixed: (P2) formatIssues forwarded zod messages verbatim. Strict-object errors repeat unexpected keys, so {"SECRET-TOKEN":1} was echoed back. Messages are now built from issue codes and schema values only, and non-identifier path segments become *. Test added.

Astra also confirmed that defineFn typing rejects mismatched handler input and output types.

🤖 Generated with Claude Code

## T8: typed `fit.*` API registry **Stacked on #6 (`t6-better-auth`) → … → #1.** Merge those first. ### What - `defineFn({ path, summary, docs, keywords?, requires?, input: zod, output: zod, examples?, handler })` with full input/output type inference. `defineNamespace` for docs shared by all functions in a namespace. - `createRegistry(fns, namespaces)`: - `list`, `search(query, limit≤50)`, `describe(paths≤50)`, `call(path, args, ctx)`. - `describe` returns JSON Schemas for input and output, generated with `z.toJSONSchema`. - `call` validates the input, runs the handler, then validates the output. Upstream shape drift shows up as a generic `internal_error`, with details kept server-side. - Paths are validated (`fit.a.bC`), and duplicates are rejected. - **Search:** weighted keyword search over path, keywords, summary, namespace context and docs. It strips diacritics (`zkouška` = `zkouska`), matches prefixes from 3 characters, and ranks an exact path first. Keywords carry the Czech synonyms. - **Errors** (`toErrorPayload`): `unknown_function`, `invalid_arguments`, `not_connected` (with the connect URL; `NotConnectedError` from T4), `reauth_required`, `upstream_error`, `temporarily_unavailable` and `internal_error`. - Unknown errors never echo their text. That matters because upstream bodies can contain tokens. - Validation messages are built from issue codes and schema-defined values only. - `FnContext` gives handlers `getToken(provider)` (the T4 token store), `listConnections()`, `fetch`, `now` and `connectUrl`. Tokens are only reachable through `getToken`, and outputs are schema-validated. `createFnContext` binds a token store to one user. - **`fit.status`:** which providers are connected and with which scopes, never credentials. It's the first function to call when something reports `not_connected`. - **`GET /api/functions`:** a public API reference (namespaces, functions, schemas; no user data). The MCP install page (T21) can link to it. ### Verified - `bun run lint && bun run typecheck && bun run test`: 111 tests, 22 of them for the registry. They cover path validation, search (including Czech with and without diacritics, namespace keywords, prefixes, exact path, gibberish and limits), describe/schemas, call input and output validation, error mapping and no-echo guarantees, and `fit.status`. - `bun run dev` + `curl /api/functions` returns the `fit` namespace and `fit.status` with input and output schemas. ### Astra review (gpt-6-astra) **Fixed:** *(P2)* `formatIssues` forwarded zod messages verbatim. Strict-object errors repeat unexpected keys, so `{"SECRET-TOKEN":1}` was echoed back. Messages are now built from issue codes and schema values only, and non-identifier path segments become `*`. Test added. Astra also confirmed that `defineFn` typing rejects mismatched handler input and output types. 🤖 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>
Applies to Usermap calls, the FIT token/check_token requests and the login profile lookup.

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>
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
Reviewed-on: #12
Reviewed-on: #11
Reviewed-on: #10
Reviewed-on: #9
Reviewed-on: #8
dzenda merged commit 3869b1448a into t6-better-auth 2026-11-14 17:50:45 +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!7
No description provided.