Skip to content

Externalize the card library behind an allowlisted set loader#36

Merged
shenanigansd merged 1 commit into
mainfrom
claude/confident-bardeen-RHtlG
May 31, 2026
Merged

Externalize the card library behind an allowlisted set loader#36
shenanigansd merged 1 commit into
mainfrom
claude/confident-bardeen-RHtlG

Conversation

@shenanigansd
Copy link
Copy Markdown
Member

Context

Third of three coordinated PRs externalizing Mundane's card library. Card content now loads from JSON sets in mundane-cards (validated against the mundane schema); card behaviour stays here as a fixed effect vocabulary. The engine keeps no HTTP knowledge — all fetching/validation/snapshotting lives in api/.

Engine

  • engine/cards.py: CARD_LIBRARY removed. In its place, an effect-factory registry EFFECTS (the only place effect functions live) and a loader build_card / build_pool. A card names an effect + params; the loader composes set_id:id, looks up the effect (raising UnknownEffectError), and binds params (raising InvalidEffectParamsError). Permanents use the no-op none. Old behaviour generalized: damage_composure (reads params.amount, was a hardcoded −3) and counter_task (the old Noise Complaint).
  • apply_action(state, action, cards) and Game now carry the resolved pool — kept off GameState so state still round-trips through JSON (the <function ...> regression guard test still passes). Added a flavor field and a canonical_json helper for hashing.

API

  • api/set_loader.py — given set_urls: allowlist each (parsed host + path, only the mundane-cards raw origin — not a substring test) → fetch with hardening (https-only, hard timeout, no redirects, size cap, content-type check) → validate against the vendored schema (api/card_schema/, never fetched at runtime) → build cards → reject duplicate composed ids → snapshot the resolved pool with a sha256 content hash.
  • POST /games accepts an optional {"set_urls": [...]} body, defaulting to the core set. GET /games/{id}/export now includes the snapshot, so a saved game replays self-contained.
  • Error mapping (store untouched on any failure — loading happens before the store is touched): non-allowlisted URL / schema-invalid / unknown effect / bad params / duplicate id → 422; fetch failure / timeout / oversize → 502.

Deps & docs

  • httpx promoted to a runtime dependency; jsonschema added; types-jsonschema added to dev. uv.lock regenerated (uv lock --locked verified in sync).
  • README / AGENTS / CONTRIBUTING / Sphinx docs updated; the stale game-docs/SPEC.md & CARDS.md links fixed to specs/ + rulebook/.

Verification

Against a Python 3.14 venv with litestar@main:

  • ruff format --check ✅ · ruff check ✅ · mypy --strict src/ tests/ examples/ ✅ · ty check .
  • 47/47 tests pass, including new cases: snapshot present with sha256: hash + composed ids; non-allowlisted (incl. raw.githubusercontent.com.evil.com and wrong-path) → 422; schema-invalid → 422; unknown effect → 422; bad params → 422; duplicate ids → 422; fetch failure → 502; loader failure leaves the store empty.
  • python examples/demo.py runs end to end.

The vendored api/card_schema/card-set.schema.json should be re-synced when the meta repo tags schema-v1.

https://claude.ai/code/session_015NZxntWsQego4NzHjt1Cmx


Generated by Claude Code

Replace the hardcoded CARD_LIBRARY with an effect-factory vocabulary (EFFECTS)
and a JSON-card loader (build_card/build_pool) in engine/cards.py; a card names
an effect plus params and cannot define new behaviour. Thread the resolved card
pool through apply_action(state, action, cards) and onto Game, keeping it off
GameState so state still round-trips through JSON. Add api/set_loader.py to
allowlist set URLs (parsed host + path), fetch them with hardening (https-only,
timeout, no redirects, size cap, content-type), validate against a vendored copy
of the card-set schema, build the pool, reject duplicate composed ids, and
snapshot the resolved pool with a sha256 content hash. POST /games accepts an
optional set_urls body (default: the core set); the export includes the snapshot
so a saved game replays self-contained. Map bad input to 422 and upstream fetch
failures to 502, leaving the store untouched on any failure. Update the engine
to add a flavor field, the demo and tests to build pools from a fixture set,
add httpx + jsonschema as runtime dependencies, and refresh README/AGENTS/
CONTRIBUTING/docs (fixing the stale game-docs links).

https://claude.ai/code/session_015NZxntWsQego4NzHjt1Cmx
@shenanigansd shenanigansd merged commit a6f62f7 into main May 31, 2026
12 checks passed
@shenanigansd shenanigansd deleted the claude/confident-bardeen-RHtlG branch May 31, 2026 23:08
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.

2 participants