114 Commits
Author SHA1 Message Date
John SmithandClaude b661542ec8 fix(gateway)+benchmark: surprise proxy wire gap; live cross-stack e2e (loop 3, iteration 35)
Milestone verification caught the exact defect class it exists for: the
client's GM Surprised toggle dials /api/v1/engine/combat/surprise but
the gateway never registered the proxy — the button 404'd in production
while the vitest mock (which stubs fetch) passed green. Adds
EngineSurpriseAdjudicationRequest + engine_combat_surprise forwarding
{entity_id, surprised} + the caller's real identity; the engine's own
RBAC still enforces GM-seat and first-round window. Regression guards:
combat-surprise in anonymous-401 cases + TestCombatProxy (missing-token
401, path+coerced-UUID payload+real-actor forwarding, extra-field 422,
unreachable-engine 502).

New engine-live module test_live_surprise_delay_timeline.py drives the
real gateway against the real engine (skips when engine down, matching
repo convention): surprise grant through the proxy, hidden-NPC surprise
not leaked to a player feed, ENTITY_SURPRISED delay refusal, surprise
release after first turns, delayed-slot skip, spotlight report +
SPOTLIGHT_SPOOFED_SEAT denial, timeline surfacing SURPRISE_GRANTED/
RELEASED + DELAY_TAKEN with no hidden name in the player feed.

Benchmark run: all gates PASS — playtest MCR 100%, HCI 1.0, AFPR 0.0%,
auditor recall 14/14; cargo 518; pytest 1350/34 standalone (+3 guards),
1366/18 engine-up; client 768/58 + relay 66.

Co-Authored-By: Claude <[email protected]>
2026-08-26 17:24:30 -04:00
John SmithandClaude 4c1196b17d feat(gateway): session spotlight aggregation over self-reports (loop 3, iteration 33)
POST /sessions/{id}/spotlight/report accepts participant self-reports of
local speaking (0..600000ms, member seat only, per-IP tight spotlight
bucket); GET /sessions/{id}/spotlight returns per-roster-seat rolling
scores decayed at half-life 3min with is_quiet = score < mean - sigma
and an observed_at staleness stamp; DELETE clears scores (GM/admin).

Honesty contract: survey confirmed the CRDT speech ledger is real and
remote-aware at the client layer, but python has no read-path into the
Y.Doc (Node relay), so the self-report channel is the honest seam this
iteration can implement; every route states that nonzero values
originate from spoofable client self-reports (insight tooling for the
DM, not an authentication boundary). DirectorAgent remains unwired by
design — aggregation is built so a future iteration can feed it. The
legacy VoiceSpotlightTracker hardcoded demo roster is left untouched.

Gates: pytest 1347 passed / 33 skipped (baseline 1302, +45).

Co-Authored-By: Claude <[email protected]>
2026-08-26 16:59:11 -04:00
John SmithandClaude a52bcd4dfc fix(gateway): audit-sweep remediations F1/F2/F5 (loop 3, iteration 28)
F1 (HIGH): per-session chat message cap (CHAT_MESSAGE_CAP = 10_000) auto-
trims the head on overflow in both MemoryStore and PostgresStore; next
id computed before trim so trimmed ids never reappear. New DELETE
/api/v1/sessions/{id}/chat/{message_id} reuses the timeline gate
(404 SESSION_NOT_FOUND / 403 outsider) plus author-or-staff check
(403 CHAT_DELETE_FORBIDDEN; 404 CHAT_MESSAGE_NOT_FOUND).

F2 (MED): timeline projection now also collapses actor_id to None and
filters referenced_entity_ids to drop hidden ids when the hidden-entity
collapse branch fires — visible ids still survive. Regression test
asserts no hidden UUID appears anywhere in a non-GM projected entry.

F5 (LOW): system-channel write gate tightened to {gm, admin} only,
mirroring the gm-channel posture; runtime gate only, schema stays
forward-compatible.

Gates: pytest 1302 passed / 33 skipped (baseline 1288, +14).

Co-Authored-By: Claude <[email protected]>
2026-08-26 12:09:25 -04:00
John SmithandClaude fd0f36ba5c feat(gateway): session event timeline merging narrative + engine ledger (loop 3, iteration 26)
GET /api/v1/sessions/{id}/timeline returns the chronological merge of
narrative chat and engine ledger events (engine sequence_ids namespaced
e_<n>, narrative n_<n>), sorted by (created_at_ms, kind, sequence_id),
paginated by an opaque cursor with default 100 / cap 500. Hidden-entity
projection lifted from the existing _entity_is_visible convention;
private channels (gm/whisper) collapse to "Something happens" for
players and drop entirely for spectators. ALSO had to add the missing
narrative chat log + POST /chat route — survey found NarrativeChat
consumed chat from React local state only with no server anchor.
Summary numbers (ATTACK_RESOLVED damage, HP, etc.) are stripped from
spectator view to avoid leaking tactical information.

Gates: pytest 1288 passed / 33 skipped (baseline 1248, +40).

Co-Authored-By: Claude <[email protected]>
2026-08-26 11:38:24 -04:00
John SmithandClaude d95fd6787b feat(gateway): safety boundaries summary endpoint (loop 3, iteration 24)
GET /api/v1/sessions/{id}/safety/boundaries/summary returns aggregate
counts only ({you.{lines,veils}, others.{lines,veils}, redacted.lines})
so client UIs can render declaration-count badges without pulling the
full topic list. Reuses iter20's _boundary_gate for auth/HMAC/404/403.
The response body carries no topic strings, entry ids, actor ids, or
timestamps — enforced by a canary-string sweep test that fetches the
full listing first to harvest strings, then asserts the summary body
contains none of them. Empty registry returns all zeros (no 404).

Gates: pytest 1248 passed / 33 skipped (baseline 1232, +16).

Co-Authored-By: Claude <[email protected]>
2026-08-26 10:38:15 -04:00
John SmithandClaude 1e7028801b fix(gateway): audit-sweep remediations F1/F7/F8 (loop 3, iteration 22)
F1: /api/v1/engine/delay and /api/v1/engine/delay/resume proxies added
mirroring the dodge/dash/disengage template (EngineEntityActionRequest +
_maneuver_proxy) — the client's existing Delay/Resume UI now actually
reaches the engine; engine verdicts surface verbatim.

F7: encounter-balance roster capped at min=1/max=64; new
_require_staff_role sub-dependency hoists role verification ahead of
body parsing so a non-staff caller with an oversized/malformed body
gets 403 without the gateway allocating/validating the array; route
moved into the tight media bucket (10/min) with rationale documented.

F8: ambience coalescing rewritten with asyncio.shield + an _adopt done-
callback — leader cancellation propagates only to the leader; the
upstream generation continues to populate the cache for later callers,
and late joiners keep coalescing onto the adopted task (exactly 1
upstream call). Normal failure paths still propagate via the shared
future so coalesced-failure semantics are preserved; cap violations
(413) also wake waiters.

Gates: pytest 1232 passed / 33 skipped (baseline 1217, +15).

Co-Authored-By: Claude <[email protected]>
2026-08-26 10:01:25 -04:00
John SmithandClaude effcf0006f feat(gateway): Lines &amp; Veils per-session safety registry (loop 3, iteration 20)
GOALS.md pillar 11 safety toolkit beyond the X-Card: per-session boundary
registry under /api/v1/sessions/{id}/safety/{boundaries,lines,veils}.
Any participant may declare under their own actor id; GM/admin manage
all; deletes are author-or-staff. Load-bearing redaction rule: veils are
shared openly, but a non-staff viewer sees OTHER participants' line
topics as [redacted] (metadata intact) while their own stay verbatim.
Topics trimmed/capped at 120 chars with machine-readable 422 codes;
in-process storage matching the established session-scoped-state
convention — documented trade-off: restart drops declarations rather
than persisting the most privacy-sensitive strings.

Gates: pytest 1217 passed / 33 skipped (+29 vs same-environment control).

Co-Authored-By: Claude <[email protected]>
2026-08-26 09:15:09 -04:00
John SmithandClaude 6f1c03aca1 feat(gateway): curated ambience presets with bounded LRU cache (loop 3, iteration 17)
Six D&D soundscape presets (tavern murmur, dungeon drips, forest night,
battle clash, thunderstorm, campfire) generated on demand via the SFX
capability. GET /media/ambience lists presets to any authenticated seat;
POST /media/ambience/{slug} is GM/admin-only like /media/sfx. In-process
LRU (12 entries, keyed by slug+model) absorbs repeat spends; concurrent
duplicates coalesce onto one upstream call; failures and over-cap
payloads are never cached and wake all waiters.

Gates: pytest 1190 passed / 31 skipped (baseline 1151, +39).

Co-Authored-By: Claude <[email protected]>
2026-08-26 04:56:18 -04:00
John SmithandClaude 13e49cf624 feat(gateway): DMG encounter balance endpoint (loop 3, iteration 12)
POST /engine/encounter/balance: full level 1-20 XP threshold table +
official multipliers extracted from starter_adventures into a shared
model (one source of truth, pinned by agreement test); returns raw/
adjusted XP and difficulty tier; gm/admin only. 31 tests red-first;
pytest 1151 passed / 31 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-26 03:59:14 -04:00
John SmithandClaude 9dedfe4200 fix(gateway): media self-audit — cache headers, response caps, slash alias (loop 3, iteration 10)
All media responses now Cache-Control: no-store; image (10MB) and SFX
(20MB) relay caps with honest 413s; trailing-slash normalization closes
the bucket-alias path that let diffusion ride the loose default bucket.
Upload-storage/prompt-injection/cross-session-leak angles verified
non-issues and documented. 10 tests red-first; pytest 1120 passed.

Co-Authored-By: Claude <[email protected]>
2026-08-26 03:32:25 -04:00
John SmithandClaude 29ea23aad8 feat(gateway): narration TTS route + session narration log (loop 3, iteration 6)
POST /media/narrate (20/min bucket, MEDIA_TTS_VOICE env default, session
attribution gated by lobby-standing) synthesizes kokoro speech for any
authenticated seat's own text; successful narrations logged to
narrative_state.narrations with GET /media/narrations listing (standing-
gated). 29 tests red-first; pytest 1110 passed / 31 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-26 02:48:50 -04:00
John SmithandClaude 2532120ac0 refactor(gateway): generic MediaGatewayClient replaces LemonadeClient (loop 3, iteration 5)
OpenAI-compatible media client with canonical MEDIA_GATEWAY_URL/_API_KEY
env vars and per-capability model config (MEDIA_IMAGE/TTS/STT/SFX_MODEL);
LEMONADE_* kept as deprecated fallbacks with one honest INFO line.
Capability discovery via GET /v1/models label mapping, advertised at
startup. Any OpenAI-compatible media server works — Lemonade, OpenAI,
llama.cpp, or an Omni-route gateway once it wires media providers.
14 tests; pytest 1082 passed / 30 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-26 02:30:44 -04:00
John SmithandClaude d1d2b1a43d feat(gateway): authenticated Lemonade media proxy routes (loop 3, iteration 2)
POST /media/image (10/min bucket, b64 JSON), /media/speech (audio bytes
w/ 20MB cap), /media/transcribe (wav ext+magic validated, streamed 25MB
cap), /media/sfx (GM-only). Honest error mapping to Lemonade errors.
38 tests red-first; pytest 1068 passed / 30 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-26 02:14:21 -04:00
John SmithandClaude 8e9d213dd7 feat(gateway): LemonadeClient multimedia service (loop 3, iteration 1)
Async client for the self-hosted Lemonade server: generate_image
(SD-Turbo), text_to_speech (kokoro-v1), transcribe (Whisper-Large-v3-
Turbo), generate_sfx (ThinkSound-SFX), list_models. Honest error
contract (LemonadeUnavailableError/LemonadeRejectedError), per-op
timeouts, env-configurable base URL/key. 24 tests red-first;
pytest 1030 passed / 30 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-26 01:57:51 -04:00
John SmithandClaude 5fd750719d feat(rag): OpenAI-compatible remote embeddings backend (harrier via Lemonade)
New OpenAICompatibleEmbeddingBackend reaches self-hosted embedding
servers directly (REMOTE_EMBEDDINGS_BASE_URL/_API_KEY/_MODEL), taking
precedence over fastembed when set — no model download, no ONNX.
Dimension probed at init and adopted from the server. Verified live
against Lemonade serving harrier-oss-v1-0.6b: 1024-dim, semantic
separation 0.75 vs 0.70 on a spell-retrieval probe. Honest provenance
label remote-embeddings:<model>; unreachable server degrades to the
hash-fallback with its usual warning. Full suite green.

Co-Authored-By: Claude <[email protected]>
2026-08-26 01:16:41 -04:00
John SmithandClaude d3093ea0e9 test(gateway): live-LLM campaign sim checkpoint (iteration 98)
Opt-in 2x2 seeded sim run against the real endpoint through ASGI:
combat+social decisions, NPC replies, disposition interactions, director
tension — llm provenance asserted, honest fallbacks tolerated with
reasons, fabrication rejected, skip-degrade per suite contract. Live
run: 4 model combat turns + 4 model social replies in ~20s. Offline
suite 1006 passed / 30 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-26 00:50:08 -04:00
John SmithandClaude 3799a73899 fix(gateway): player-tier ledger summaries for new event types (iteration 88a)
Closes A5 F2: OPPORTUNITY_ATTACK_RESOLVED/MOVE_ENTITY/READY_*/INSPIRATION/
ITEM_TRANSFERRED get modeled fact-line summaries for players — no enemy HP,
no hidden-attacker ids; GM tier stays verbatim; spectators unchanged.
10 tests red-first; pytest 1006 passed / 29 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 23:55:28 -04:00
John SmithandClaude 7526b524c1 fix(gateway): autosave policies require verified session standing (iteration 87)
Closes A5 F1: policy PUT now derives standing from lobby membership
(fail-closed, no admin bypass on unbound sessions); the periodic loop
re-verifies fresh each cycle and auto-disables the policy + skips the
fetch when standing is lost — no more fabricated GM authority pulling
full state past revocation. 7 tests red-first; pytest 996.

Co-Authored-By: Claude <[email protected]>
2026-08-25 23:11:52 -04:00
John SmithandClaude 68a69c44c1 feat(gateway): campaign sim exercises OA + ready/release reactions (iteration 81)
Deterministic per-seed tactical layer: pending-OA disclosures queue
opponent reaction attacks (host drives ownerless attackers), 15% seeded
ready actions release only when the structured trigger observably fired.
Reaction attempts count as valid attempts, never compliance failures;
decision_seed=None preserves legacy behavior byte-identically. 20 tests
red-first; pytest 989 passed / 29 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 22:32:19 -04:00
John SmithandClaude 70533c591e fix(gateway): ready trigger field renamed, release proxy added (iteration 82)
trigger_hint guaranteed an engine 422 (deny_unknown_fields expects
trigger); field renamed and extra=forbid added locally. New
/engine/ready/release ids-only proxy via _maneuver_proxy with identity
forwarding. 9 tests red-first; pytest 969 passed / 29 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 21:52:46 -04:00
John SmithandClaude e66113ab2a feat(gateway): opt-in periodic campaign autosave (iteration 77)
GM-set per-session policy (interval 1-1440 min, persisted both stores);
30s asyncio loop saves only when interval elapsed AND ledger moved —
idle campaigns never rewritten. Rolling upsert slot, quest/faction slots
carried, per-campaign failure isolation, owner identity forwarded to
engine RBAC. Single-worker assumption disclosed. 14 tests red-first;
pytest 960 passed / 29 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 21:26:28 -04:00
John SmithandClaude 9011323d9a feat(gateway): opportunity-attack proxy closes the wire gap (iteration 78)
Ids-only EngineOpportunityAttackRequest via _maneuver_proxy with caller
identity forwarding; seed/attack-bonus smuggling structurally impossible;
added to the anonymous-access sweep matrix. 6 tests red-first + sweep
row; pytest 946.

Co-Authored-By: Claude <[email protected]>
2026-08-25 21:25:50 -04:00
John SmithandClaude ccc29f32ad feat(gateway): wizard fields persisted on lobbies, launch propagates edition (iteration 71)
rule_version/starting_level/party_size on lobby create (validated 422,
idempotent ALTER migration, exposed on all roster reads); launch passes
rule_version to engine session creation only when the lobby recorded
one — legacy payloads byte-identical. 11 tests red-first; pytest 940.

Co-Authored-By: Claude <[email protected]>
2026-08-25 20:26:41 -04:00
John SmithandClaude baa2503ace fix(gateway): bundle tokens spawn in world-feet, not grid cells (iteration 69)
EntityState.position is world feet ('world units == feet', vtt-core
distance_to_feet); _bundle_token_to_entity was passing raw grid cells,
landing every bundle token at cell/5 — inside walls on complex maps.
The deepened Sunken Crypt exposed it: all 14 spawns rejected
INGRESS_TARGET_BLOCKED and silently swallowed to tokens_spawned=0.
Convert cell centers via (cell + 0.5) * 5.0 ft.

Co-Authored-By: Claude <[email protected]>
2026-08-25 20:05:56 -04:00
John SmithandClaude b464528298 feat(gateway): auto-capture LORE_ASSERTION utterances as rumors (iteration 65)
Improvised player lore no longer evaporates at classification: triples
derived via the auditor's windowed predicate heuristic stage through
the same submit_assertion path, always SUBJECTIVE_RUMOR regardless of
role; paradoxes rejected; verdict rides an X-Rumor-Capture header so
the response contract stays byte-identical. Survey honestly declined
speculative prefetch (no streaming-STT surface exists) and confirmed
the retry loop already wired. 7 tests red-first; pytest 929.

Co-Authored-By: Claude <[email protected]>
2026-08-25 19:10:45 -04:00
John SmithandClaude 807890cf18 feat(gateway): inspiration/escape proxies close the wire gap (iteration 64)
spend_inspiration forwarded on attack/check/save; new authenticated
escape-grapple proxy via _maneuver_proxy (seed non-forwardable per
policy, engine re-checks force privilege); GM-gated inspiration
grant/revoke proxies with zero engine dials for players. 23 tests
red-first; pytest 922 passed / 28 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 18:51:58 -04:00
John SmithandClaude 024448ec18 fix(gateway): death audit reads top-level actor_id per engine wire (iteration 59)
Closes A4 F1: aggregator read identity from event payloads, which
DEATH_SAVE_RESOLVED never carries — every save was skipped and episodes
stuck in_progress forever. Fixtures rewritten to the verbatim engine
shape with a module contract comment: fixtures MUST mirror emission.
Red proof showed real-shape ledgers yielding all-in_progress before
the fix. pytest 899 passed / 28 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 17:59:48 -04:00
John SmithandClaude 87343b653a feat(gateway): death-save audit in replay export (iteration 57)
Optional include=death_audit flag on the replay export derives
trigger→save-attempts→outcome episodes per token from ledger events
(rewound events excluded, truncation scoped honestly). Instant deaths,
stabilizations, and in-progress recoveries all surfaced; empty ledgers
admit unavailability instead of inventing sections. 12 tests red-first;
pytest 897 passed / 28 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 15:49:12 -04:00
John SmithandClaude 908504e4f3 feat(gateway): session replay export, role-projected (iteration 55)
GET /sessions/{id}/replay/export?format=json|markdown: GM/admin get
verbatim events; players get summary-only projection (no raw payloads or
state hashes ride along); spectators fail closed 403. Markdown renders
round-by-round with X-card rewind markers and honest truncation at 5000
events. 25 tests red-first; pytest 885 passed / 28 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 15:31:49 -04:00
John SmithandClaude 47ca2bd3c2 feat(content): Sunken Crypt of Karas deepened to playable campaign shape (iteration 52)
8-chamber 44x30 map with per-chamber engine-WFC stitching, 7-node
encounter tree with save-DC hazards, DMG XP-threshold balance audit at
import (old opener was deadly+; now 5 fights EASY/MEDIUM, wight climax),
real compendium item ids in milestone loot, 9 tiered paradox-checked
lore seeds, full export→import round-trip verified. 22 tests red-first;
pytest 860 passed / 28 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 14:02:19 -04:00
John SmithandClaude 0d6393e690 fix(gateway): x-card survives live-engine rejection honestly (milestone gate fix)
Benchmark boot with a LIVE engine exposed EngineRejectedError (404 unknown
session) escaping the x-card handler as a hard failure — the orchestrator-
side intervention must still record. Engine rewind is best-effort: rejection
now surfaces as engine_rewind.status=ENGINE_REJECTED with the real code.
Proven against a booted engine; full suite green.

Co-Authored-By: Claude <[email protected]>
2026-08-25 11:20:53 -04:00
John SmithandClaude 6b43e7d90c feat(gateway): persistent campaign quest + faction state across restarts (iteration 47)
Autosave snapshot carries quest-graph and faction-state slots on the
existing campaign_saves JSONB; explicit POST /campaign/restore (GM-only,
ownership-scoped) reapplies them; readonly quest reads self-heal from the
newest owned save instead of rolling new canon. Corrupt slots → honest
422 *_CORRUPT; legacy saves restore cleanly. Inventory of memory-only
singletons audited and documented durable-vs-local. 9 tests red-first;
pytest 839 passed / 27 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 11:03:17 -04:00
John SmithandClaude 7d23fbfd19 fix(gateway): authenticated session-scoped corpora, TTL cache, honest statuses (iteration 44)
Closes A3 #5/#6: anonymous/garbage-token callers resolve the default
corpus with zero engine round trips (junk-uuid amplification closed);
deterministic resolutions cached 30s/256 entries. 404 vs unreachable vs
missing-version now produce distinct provenance statuses with the real
HTTP status surfaced. 16 tests red-first; pytest 830 passed/27 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 10:37:36 -04:00
John SmithandClaude 31a0e1783a fix(gateway): cumulative zip budget + importer path containment (iteration 42)
Closes A3 #1/#2: one shared extraction budget draws down across all
entries (mid-copy cutoff), declared-size sum refused pre-extraction,
chunked streaming upload caps memory on lying Content-Length; manifest
pack paths contained via commonpath with skip-with-warning. Lying
comments corrected. 8 tests red-first incl. a lying-reader seam for
the zipfile declared-size cap; pytest 812 passed / 29 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 10:26:24 -04:00
John SmithandClaude a6af1c91e0 feat(gateway): Foundry module zip upload with hardening (iteration 40)
Replaces the multipart-transport 501: gm/admin-gated upload, zip-slip/
absolute-path/symlink rejection, double-enforced size+expansion caps
(413), manifest-root discovery delegating to the fail-loud importer.
501 reasons list updated to only still-true limitations. 10 tests
red-first; pytest 806 passed / 27 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 09:32:35 -04:00
John SmithandClaude 04f9e318fc feat(gateway): per-session rule-version corpus branching (iteration 37)
Both SRD editions load at startup when fixtures exist; compendium and
stream-grounding routes resolve the session's rule_version from the
engine snapshot and serve the matching corpus with additive provenance
(rule_version/source/reason). Version-mismatched RAG queries honestly
degrade to versioned substring scan instead of mislabeled semantic hits.
14 tests red-first; pytest 796 passed / 27 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 09:14:29 -04:00
John SmithandClaude 716c80c8dc feat(gateway): lobby ready flags + character binding + launch gating (iteration 33)
Per-member ready/selected_character_id persisted in MemoryStore and
Postgres (idempotent ALTER migration); member-scoped ready/character
routes with ownership validation; host launch refuses unready members
(409 with honest roster) unless host forces. Campaign sim readies seats
before launch. 7 new tests red-first; pytest 782 passed / 27 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 05:17:38 -04:00
John SmithandClaude 2812c8f3f9 feat(gateway): Redis-backed distributed rate-limit windows (iteration 31)
Sorted-set sliding windows keyed per bucket+identity when REDIS_URL is
set and reachable; rejected attempts still cost nothing and Retry-After
matches the memory backend value-for-value. Availability-first: any Redis
failure flips one permanent degraded flag with a single honest warning,
then delegates to the shared memory backend. Two-replica budget-sharing
test proves the quota-multiplication defect gone. 12 tests red-first;
pytest 776 passed / 26 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 04:26:07 -04:00
John SmithandClaude ccdf74271d feat(compendium): spell damage coverage 73→82/352 with upcast metadata (iteration 27)
Parser handles conjunctive one-hit components, modifier-derived
formulas (with explicit disclosure), exact flat values, and slot-scaling
rows — 45 spells gain structured upcast metadata. Distinct-trigger and
choice-dependent formulas stay honestly warned per the no-guessing
contract; coverage regression below 73 now fails CI. 17 tests red-first;
pytest 764 passed / 26 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 02:07:34 -04:00
John SmithandClaude 5551a321b7 feat(gateway): DirectorAgent wired to real campaign-sim tension signals (iteration 24)
Deterministic tension tracker: weighted components (hp swing/deaths/
disposition shifts/round pressure/quest stage) fold into exponential
smoothing over the sim's round loop; recommendations (raise_stakes,
introduce_complication, spotlight_player) derive from pure thresholds.
LLM only phrases hook text with provenance labels. Sim report gains a
director telemetry block; identical runs byte-identical. 35 tests
red-first; pytest 747 passed / 26 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 01:13:50 -04:00
John SmithandClaude dfa97ce58a fix(playtest): seeded RNG for reproducible benchmark sampling (iteration 22)
SyntheticPlaytestRunner now takes a seed (default 42) driving utterance
mix + 10% recall-probe injection via random.Random(seed) — benchmark
deltas are reproducible across runs instead of depending on global
RNG state.

Co-Authored-By: Claude <[email protected]>
2026-08-25 00:53:29 -04:00
John SmithandClaude 024c140d0a fix(routing): apostrophe-free classifier prompt after live-LLM probe (iteration 21)
Live checkpoint vs llm.heretek.one found the gateway deterministically
degrades when a raw single quote reaches it — the router prompt's
'player's' flipped 'I attack the goblin' from MECHANICAL_INVOCATION
(0.95) to IN_CHARACTER_DIALOGUE (0.72). Prompt reworded, live suite
6/6 green. Root cause is upstream template/escaping; documented at
the call site.

Co-Authored-By: Claude <[email protected]>
2026-08-25 00:32:43 -04:00
John SmithandClaude 5f47c5a8dc feat(gateway): real hybrid dense+sparse RAG via fastembed (iteration 18)
Replaces hash pseudo-embeddings with BAAI/bge-small-en-v1.5 dense +
Qdrant/bm25 sparse legs fused by reciprocal-rank fusion over Qdrant
named vectors. Opt-in via QDRANT_EMBEDDINGS=1; offline fallback keeps
hash embedder relabeled qdrant-hash-fallback with an honest warning.
Live-verified semantically on a compendium subset. 27 new offline-safe
tests; pytest 713 passed / 25 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-25 00:24:15 -04:00
John SmithandClaude 8123d6e216 refactor(gateway): delete dead fabricating modules (iteration 16)
Removed AstPdfCompendiumParser (fabricated ac=10/hp=10 defaults),
LiteLLMCircuitBreakerGateway (canned cloud-call simulation),
VttBundleBridge (weaker duplicate exporter + fabricated Foundry
defaults), ConcordiaNPCComponent (template chatter). Import graph
cleaned; live fail-loud paths (HomebrewMarkdownParser, FoundryModuleImporter,
LLMStreamingGateway, ConcordiaNPC) untouched. Suite unchanged at 690.

Co-Authored-By: Claude <[email protected]>
2026-08-24 23:05:44 -04:00
John SmithandClaude 312ab23682 feat(gateway): magnitude bounds, damage grammar, typed ingress on spawn (iteration 15)
Closes audit F10 at the gateway: ac 0..30, hp 0..1000, finite speed,
typed spell-slot dict; damage expressions validated against a strict
dice grammar (sides/dice/constant caps) since the engine rolls spawned
expressions unclamped. ingress is now a strict EngineIngressEvent model
matching the engine's SCREAMING_SNAKE_CASE wire format. 18 tests
red-first; pytest 690 passed / 21 skipped. All SRD corpus callers pass.

Co-Authored-By: Claude <[email protected]>
2026-08-24 22:49:50 -04:00
John SmithandClaude 4e8477d08b feat(gateway): admin bootstrap allowlist, fail-closed secret, lobby membership gate (iteration 12)
Signup accepts only player/spectator — staff roles come from the
VTT_ADMIN_EMAILS allowlist (rotatable without restart). Gateway aborts
startup without AUTH_SECRET/VTT_ENGINE_SECRET, mirroring engine semantics.
GET /lobbies/{id} member-or-staff only with no existence oracle; invite
codes stay visible to members. 20 tests red-first; pytest 672 passed.

Co-Authored-By: Claude <[email protected]>
2026-08-24 21:14:10 -04:00
John SmithandClaude fa18ebfff8 feat(gateway,client): auth + spend buckets on agent/dynasty/simulation/LLM routes (iteration 10)
Closes audit A1 blockers F1-F4: /agent/turn gated to GM or bound-session
participants; dynasty inject-lore and simulation tick GM-only; empirical-
benchmark GM-only with 5/min bucket; intent/orchestrator/narrative routes
authenticated in a 30/min llm bucket. Client stream/dynasty call sites send
credentials with honest sign-in gating. Compose now passes overridable
VITE_YSYNC_WS_URL to vtt-client. 33 red-first pytest tests, 6 vitest;
pytest 651 passed / 22 skipped, vitest 102.

Co-Authored-By: Claude <[email protected]>
2026-08-24 20:48:36 -04:00
John SmithandClaude b7bb40b160 feat(gateway): durability-bridge auth + header-first tokens on legacy routes (iteration 8)
persist/hydrate require auth with x-card-style participation gating
(gm/admin any session, others via bound lobby roster, unbound fail closed).
All remaining token:Query routes migrated to _require_auth (Bearer preferred);
EnginePersistRequest.owner_user_id spoofing removed — ownership from verified
token. Sweep documented remaining unauthenticated LLM-spend/agent routes for a
coordinated follow-up. 11 new tests red-first; pytest 618 passed / 22 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-24 19:26:51 -04:00
John SmithandClaude f6ee77c8fc feat(gateway): epistemic ladder + x-card/spotlight authorization (iteration 5)
Lore assertions enter at SUBJECTIVE_RUMOR; GM promotes one step per call
through paradox review, players never self-promote (403 LORE_TIER_FORBIDDEN).
X-card requires token + session participation (403 X_CARD_NOT_A_PARTICIPANT);
spotlight recording validates speaker identity (403 SPOTLIGHT_SPOOFED_SPEAKER).
29 new tests red-first; pytest 607 passed / 22 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-24 19:04:53 -04:00
John SmithandClaude c1fc3dbebb feat(gateway,client): mandatory auth on every engine proxy, client sends tokens everywhere (iteration 3)
All narrative-facing /engine/* proxies resolve callers via _require_auth and
forward verified identity (_caller_actor); optional-token service-principal
fallback removed. 28-route anonymous-401 contract test matrix. Client now
attaches ?token=/Bearer on session/attack/check/map/metrics/presence with an
honest NOT_AUTHENTICATED rejection when signed out. Full benchmark run green:
MCR 100%, HCI 1.0, AFPR 0.0%, recall 100%, trust probes 10/10.

Co-Authored-By: Claude <[email protected]>
2026-08-24 18:46:58 -04:00