QDG Knowledge Base Read-only viewer QWebHub
bugs

Known Issues & Discrepancies

Version 1 · Initial known-issues page: discrepancies against the original design doc plus what's intentionally not built yet — derived from docs/PROJECT_DOCUMENTATION.md §11-12

GateKeeper — Known Issues & Discrepancies

Found by comparing the running code against entitlement-system-design.md and the seed data. None block local development, but worth knowing before building on top of this. See Architecture for the systems these refer to.

DataType is a hybrid, not a pure JSON key

The design doc states dimension codes are "just JSON keys ... rather than a database lookup table." In practice data_type got a real data_types table so the AdminUI can offer a managed multi-select when building package rules — but there's still no FK from packages.rules/customer_overrides.constraints back to it, since those remain JSON. Practical effect: deleting a DataType row doesn't check or block packages that still reference its code in their JSON rules — the package just stops offering that value in the AdminUI dropdown, it doesn't lose the underlying grant.

Cache TTL behavior differs from the design doc

The design doc described a 3–5 s L1 range and a sliding 5-minute Redis TTL (refreshed on read). The shipped implementation uses a fixed 4 s L1 TTL with no sliding refresh on hits in either tier; the Redis TTL, previously hardcoded at 5 minutes, is now configurable (Cache:TokenTtlMinutes / Cache:EntitlementTtlMinutes, default 5, 0 = no expiry — see Architecture). Freshness is still achieved via push invalidation regardless of TTL, so this mostly affects idle-customer cache-warmth, not correctness.

Seed script references customers that don't exist

gatekeeper-schema.sql's customer_subscriptions and customer_overrides INSERTs use customer ids 101, 205, 310 — the illustrative ids from the design doc's worked examples — but the same file only creates customers 401 (CWS) and 402 (WSB). Both tables have FOREIGN KEY ... REFERENCES customers(id), so running the script unmodified against a clean database raises a foreign-key violation on those INSERTs (or silently no-ops if 101/205/310 already exist from a previous run). Action needed: either remove those seed rows or repoint them at 401/402 before relying on this script as a clean-install path.

Related: package 3 (RESULTS)'s rules reference data_type: "results", but no results row is seeded into data_types — harmless (JSON isn't FK-checked), but results won't appear in the Packages page's picklist until someone adds it via Data Types.

A real credential is committed in a Development config file

Both GateKeeper.Api/appsettings.Development.json and GateKeeper.AdminUI/appsettings.Development.json contain a live-looking external MySQL host, username, and plaintext password — not a placeholder like every other config file in the repo. Action needed: rotate that password and move Development config to user-secrets or environment variables, consistent with the .env.example pattern used for other environments. Do not paste the actual value into chat, a commit message, or this KB — only the fact that it needs rotating.

HTTP API and AdminUI aren't fully symmetric

DataType CRUD, UpdateSubscriptionDatesAsync, and UpdateApiHitLimitAsync are only reachable through the Blazor AdminUI (direct in-process calls to GateKeeperAdminService) — no /admin HTTP route exists for any of them. Fine today since the AdminUI is the only consumer; worth knowing if a future integration needs to drive those operations over HTTP.

What's intentionally not built yet

  • EF Core migrations — the DbContext is mapped to match gatekeeper-schema.sql by hand; run that script directly instead of dotnet ef database update.
  • /v1/racecards, /v1/speedmaps — same shape as the meetings endpoint, not yet duplicated.
  • Customer.ApiHitLimitMonthly enforcement — the field and its AdminUI editor exist, but nothing currently enforces it against incoming request volume.
  • Unit tests — EntitlementResolver and EntitlementScope.Matches/EffectiveEntitlement.IsAllowed are pure functions with no I/O, making them the highest-value place to start.
Updated by Claude on Aug. 11, 2026, 10:20 a.m. · Task: updatewiki create project documentation