Skip to content

Misc fixes - #14

Open
rolandgeider wants to merge 7 commits into
masterfrom
misc-fixes
Open

Misc fixes#14
rolandgeider wants to merge 7 commits into
masterfrom
misc-fixes

Conversation

@rolandgeider

Copy link
Copy Markdown
Member

Follow-ups from a full read-through of src/wger_mcp, looking for duplication, overengineering and YAGNI. No structural changes — the layering holds. Two user-visible fixes, one hardening fix, and four refactors that remove repeated definitions rather than lines.

Breaking (response shape)

Both are documented under Unreleased in the changelog:

  • add_exercise_with_sets returns the ids flat (slot_id, slot_entry_id, …) instead of one-key sub-dicts. The flat keys match the parameter names the follow-up tools take, verbatim.
  • lookup_food_by_barcode / lookup_foods_by_barcodes no longer return wger_ingredient_payload. It repeated macros_per_100g under a second set of keys, shaped for a create_ingredient call that cannot exist: wger's REST /ingredient/ is read-only (the generated client has no ingredient_create and no IngredientRequest), and the tool was removed with the move to multi-user auth. Every lookup was paying the caller's context for it. docs/HANDOFF.md kept the payload as the seed of that tool should the endpoint ever appear; the note now says why it went instead.

Fixes

  • Comma-separated lists work in an env file. ALLOWED_HOSTS=a,b in .env aborted startup with a parse error, because only the process environment was rewritten to the JSON form pydantic-settings parses — the dotenv file is a source of its own. NoDecode plus a before-validator now handles both sources identically, and load_settings() no longer mutates os.environ as a side effect. The JSON spelling keeps working.
  • The JWKS is no longer refetched on every rejected token. Any JoseError counted as "the key may have rotated", so an unauthenticated caller could drive one request to the IdP per malformed token. Only InvalidKeyIdError forces a refetch now, capped at one per minute and measured from the last forced refetch — measuring from the last fetch of any kind would answer a rotation with 401s until the window ran out. JwksCache also takes a lock, so concurrent misses share one fetch.
  • lookup_food_by_barcode gains the one-shot 429 retry the batch variant always had, as a consequence of the two sharing a fetch path.

Refactors

  • opt_int / opt_uuid / opt_decimal replace opt(as_int(x, "x") if x is not None else None) at 38 call sites across six modules.
  • day_bounds / day_range_filters replace three copies of the inclusive date-range translation. The exclusive upper bound is the kind of detail a fourth copy gets wrong, and getting it wrong drops the final day.
  • One METRICS table drives analytics' buckets, projection and deltas, which spelled the same mapping out three times. weekly_summary and exercise_history now accumulate through _accumulate instead of open-coding reps * weight a second and third time.
  • The single and batch barcode lookups share one fetch path.

Verification

264 tests pass, ruff check clean. New tests cover the JWKS cache (6 cases, including that a rotation is still picked up immediately), the OFF lookup (4), day_bounds (4) and env-file list parsing (2).

The two refactors that could silently change output were checked by dumping the real results before and after: every analytics tool's response over the same logs is byte-identical, metric subsets included, and so are the date filters all three affected tools send.

🤖 Generated with Claude Code

rolandgeider and others added 7 commits August 26, 2026 22:04
_verify treated any JoseError as "the signing key may have rotated" and
forced a fresh key-set fetch. Anyone can post a malformed or foreign-signed
token, so an unauthenticated caller could drive one request to the IdP per
attempt — the TTL cache never applied on the one path strangers reach.

joserfc raises InvalidKeyIdError for an unknown key id and something else
for a bad signature or a malformed token, and only the first can be fixed by
fetching keys, so only it forces a refetch now. A kid is attacker-chosen
though, so forced refetches are capped at one per minute, measured from the
last forced one — measuring from the last fetch of any kind would let an
ordinary refresh start the window and answer a rotation moments later with
401s. JwksCache also takes a lock, so concurrent misses share one fetch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The one-key sub-dicts ({"slot": {"id": ...}}) were a vestige of the full
objects trimmed in #13; every caller had to map ["slot_entry"]["id"] onto
the slot_entry_id parameter the follow-up tools take. The flat keys match
those parameter names, and the delete tools' responses, verbatim. A new
test pins the success shape, which nothing asserted on before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant