{"id":48819219,"url":"https://github.com/or-carmeli/kubequest","last_synced_at":"2026-05-15T07:14:13.327Z","repository":{"id":341851094,"uuid":"1171704483","full_name":"or-carmeli/KubeQuest","owner":"or-carmeli","description":"Interactive Kubernetes learning game - quizzes, incident simulations, daily challenges, and a global leaderboard. React 19 + Supabase + Vercel.","archived":false,"fork":false,"pushed_at":"2026-04-11T20:32:43.000Z","size":46106,"stargazers_count":3,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-11T22:16:27.102Z","etag":null,"topics":["devops","k8s","kubernetes","learning","quiz","react","supabase","vite"],"latest_commit_sha":null,"homepage":"https://www.kubequest.online","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/or-carmeli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T14:18:33.000Z","updated_at":"2026-04-11T20:31:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/or-carmeli/KubeQuest","commit_stats":null,"previous_names":["or-carmeli/k8s-interactive-game","or-carmeli/kubequest"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/or-carmeli/KubeQuest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/or-carmeli%2FKubeQuest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/or-carmeli%2FKubeQuest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/or-carmeli%2FKubeQuest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/or-carmeli%2FKubeQuest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/or-carmeli","download_url":"https://codeload.github.com/or-carmeli/KubeQuest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/or-carmeli%2FKubeQuest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31799411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T11:13:53.975Z","status":"ssl_error","status_checked_at":"2026-04-14T11:13:53.299Z","response_time":153,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","k8s","kubernetes","learning","quiz","react","supabase","vite"],"created_at":"2026-04-14T14:01:12.760Z","updated_at":"2026-05-15T07:14:13.314Z","avatar_url":"https://github.com/or-carmeli.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# KubeQuest\n\n**Kubernetes incident training for DevOps and SRE engineers.**\n\nPractice real-world Kubernetes troubleshooting through interactive quizzes, War Room incident simulations, and daily challenges. Build production-grade debugging instincts before production does it for you.\n\n[![Live](https://img.shields.io/badge/Live-kubequest.online-00D4FF?style=flat-square\u0026logo=vercel)](https://www.kubequest.online/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE)\n[![CI](https://img.shields.io/github/actions/workflow/status/or-carmeli/KubeQuest/ci.yml?branch=main\u0026style=flat-square\u0026label=CI)](https://github.com/or-carmeli/KubeQuest/actions/workflows/ci.yml)\n[![Tests](https://img.shields.io/badge/tests-742%2B%20passed-10B981?style=flat-square)](https://github.com/or-carmeli/KubeQuest/actions)\n[![Supabase](https://img.shields.io/badge/Supabase-PostgreSQL-3ECF8E?style=flat-square\u0026logo=supabase)](https://supabase.com)\n[![Status](https://img.shields.io/badge/Status-status.kubequest.online-10B981?style=flat-square)](https://status.kubequest.online)\n\n---\n\n[kubequest.online](https://www.kubequest.online/) - no registration required, works instantly in guest mode.\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"public/app-demo-gif-english.gif\" alt=\"KubeQuest Demo\" width=\"420\"\u003e\n\u003c/div\u003e\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [War Room](#war-room)\n- [PRO Subscription](#pro-subscription)\n- [Learning Path](#learning-path)\n- [Tech Stack](#tech-stack)\n- [Architecture](#architecture)\n- [Billing Architecture](#billing-architecture)\n- [Security Model](#security-model)\n- [Observability](#observability)\n- [CI/CD \u0026 Supply Chain Security](#cicd--supply-chain-security)\n- [Local Development](#local-development)\n- [Environment Variables](#environment-variables)\n- [Testing](#testing)\n- [Kubernetes Deployment](#kubernetes-deployment)\n- [Project Structure](#project-structure)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [Disclaimer](#disclaimer)\n- [License](#license)\n\n---\n\n## Features\n\n### Free Tier\n- **Topic Quizzes** - 8 topics, 3 difficulty levels each, progressively unlocked\n- **Daily Challenge** - 5 fresh questions every day\n- **Mixed Quiz** - random questions across all topics\n- **War Room** - 9 catalog incident scenarios (free)\n- **Interview Mode** - mandatory timer, hints disabled, exam pressure\n- **Leaderboard** - global ranking with tier badges\n- **Achievements** - milestone-based reward system\n- **Hebrew / English** - full bilingual support with RTL layout\n- **Guest Mode** - no account needed; sign up to sync progress\n- **Kubernetes Guide** - built-in cheatsheet for quick reference\n- **Roadmap View** - visual learning path through all topics\n\n### PRO Tier\n- **Unlimited Generated Incidents** - template-based War Room scenarios with randomized variations\n- **Architecture Scenarios** - infrastructure decision-making simulations\n- **Advanced SRE Training** - production-grade troubleshooting drills\n\n---\n\n## War Room\n\nThe War Room is KubeQuest's flagship feature: an interactive incident simulator that presents realistic Kubernetes production failures.\n\nUsers investigate system signals using simulated `kubectl` commands, discover clues stage by stage, and identify root causes of failures including misconfigured Services, missing ConfigMaps, broken probes, storage provisioning failures, DNS resolution issues, network policy blocks, OOM kills, and node disk pressure.\n\n**9 catalog incidents** are available for free. Each is scored on investigation quality, efficiency, hints used, and root cause accuracy. Solved incidents contribute to the global leaderboard.\n\n**Generated incidents** (PRO) use templates with randomized parameters - different namespaces, service names, pod hashes, and failure variations - providing unlimited practice without repeating the same scenario.\n\n---\n\n## PRO Subscription\n\nKubeQuest uses a centralized FREE vs PRO feature gating architecture with server-authoritative subscription state.\n\n### How It Works\n\n1. User clicks \"Upgrade to PRO\" in the upgrade modal\n2. Frontend calls a Supabase Edge Function (`create-checkout`)\n3. Edge Function creates a Lemon Squeezy checkout session and returns the URL\n4. User completes payment on Lemon Squeezy's hosted checkout\n5. Lemon Squeezy sends a webhook to the `lemonsqueezy-webhook` Edge Function\n6. Webhook verifies the HMAC-SHA256 signature and upserts subscription state into `user_subscriptions`\n7. Frontend reads subscription via `get_my_subscription()` RPC - returns `tier: pro` or `tier: free`\n\n### Security Model\n\n- **Server-authoritative**: PRO access is determined solely by the `user_subscriptions` table, populated by verified webhooks\n- **Fail-closed**: any error in subscription fetch defaults to FREE tier\n- **No client trust**: localStorage cannot grant PRO access (DEV-only override is dead-code-eliminated in production builds)\n- **Webhook signature verification**: HMAC-SHA256 via Web Crypto API\n- **RLS**: users can only read their own subscription row; only the service role (webhooks) can write\n\n### Billing Management\n\nPRO users can manage their subscription through Lemon Squeezy's customer portal, accessible via the \"Manage Billing\" button in the upgrade modal. Cancellation removes PRO access at the end of the billing period.\n\n---\n\n## Learning Path\n\n| # | Topic | Key Areas |\n|---|-------|-----------|\n| 1 | **Workloads \u0026 Scheduling** | Pods, Deployments, StatefulSets, DaemonSets, Jobs, taints/tolerations, HPA |\n| 2 | **Networking \u0026 Services** | Services, Ingress, CoreDNS, NetworkPolicy, kube-proxy |\n| 3 | **Cluster Operations** | kubeadm init/join/upgrade, etcd backup \u0026 restore, Static Pods, certificates |\n| 4 | **Config \u0026 Secrets** | ConfigMaps, Secrets, RBAC, ServiceAccounts, Pod Security Admission |\n| 5 | **Storage \u0026 Helm** | PV/PVC, StorageClass, dynamic provisioning, access modes, Helm |\n| 6 | **Troubleshooting \u0026 Debugging** | CrashLoopBackOff, ImagePullBackOff, Node NotReady, DNS issues, probe failures |\n| 7 | **OS \u0026 Linux Deep Dive** | Processes, memory, CPU, networking, container runtime internals |\n| 8 | **Argo \u0026 GitOps** | ArgoCD, sync policies, ApplicationSets, App of Apps |\n\n---\n\n## Tech Stack\n\n| Layer | Technology |\n|-------|-----------|\n| Frontend | [React 19](https://react.dev) + [Vite](https://vitejs.dev) |\n| Backend | [Supabase](https://supabase.com) (PostgreSQL + Auth + Edge Functions) |\n| Billing | [Lemon Squeezy](https://lemonsqueezy.com) (checkout, webhooks, customer portal) |\n| Hosting | [Vercel](https://vercel.com) (Edge Network + CDN) |\n| Error Tracking | [Sentry](https://sentry.io) (production errors + Web Vitals) |\n| Analytics | [Vercel Analytics](https://vercel.com/analytics) + custom event catalog |\n| CI/CD | GitHub Actions (build, test, scan, sign, attest) |\n| Supply Chain | Cosign + Trivy + SBOM + Provenance |\n| Security | CSP, HSTS, CORS, RLS, CodeQL, npm audit, Gitleaks, Kyverno |\n| Monitoring | Supabase Edge Functions + pg_cron (60s health checks) |\n| Testing | [Vitest](https://vitest.dev) (742+ tests) |\n| Containerization | Docker (multi-stage, nginx:alpine, ~25MB) |\n\n---\n\n## Architecture\n\n### Runtime\n\n```mermaid\nflowchart TB\n    USER([User])\n\n    subgraph Frontend[\"Frontend (Vercel)\"]\n        SPA[\"React SPA\"]\n        PWA[\"Service Worker\u003cbr/\u003eCache + Recovery\"]\n    end\n\n    subgraph Backend[\"Supabase\"]\n        AUTH[\"Authentication\"]\n        RPC[\"RPC Functions\u003cbr/\u003eScoring + Gating\"]\n        EDGE[\"Edge Functions\u003cbr/\u003eBilling + Health\"]\n    end\n\n    subgraph Billing[\"Lemon Squeezy\"]\n        CHECKOUT[\"Checkout\"]\n        WEBHOOK[\"Webhook\"]\n        PORTAL[\"Customer Portal\"]\n    end\n\n    subgraph Database[\"PostgreSQL\"]\n        DB[(\"Quiz Data\u003cbr/\u003eSubscriptions\u003cbr/\u003eProgress\")]\n    end\n\n    USER --\u003e|HTTPS| SPA\n    SPA --\u003e PWA\n    SPA --\u003e AUTH\n    SPA --\u003e RPC\n    RPC --\u003e DB\n    EDGE --\u003e DB\n    WEBHOOK --\u003e|HMAC verified| EDGE\n    EDGE --\u003e CHECKOUT\n    EDGE --\u003e PORTAL\n\n    style Frontend fill:#111827,stroke:#00D4FF,stroke-width:2px,color:#ffffff\n    style Backend fill:#111827,stroke:#A855F7,stroke-width:2px,color:#ffffff\n    style Billing fill:#111827,stroke:#10B981,stroke-width:2px,color:#ffffff\n    style Database fill:#111827,stroke:#F59E0B,stroke-width:2px,color:#ffffff\n```\n\n### Server-Authoritative Scoring\n\nThe client never sees correct answers until after submission. All scoring runs through `SECURITY DEFINER` RPC functions with rate limiting. Quiz questions use stable content-derived keys for deduplication across content reseeds.\n\n### Feature Gating\n\n```\nfeatureGating.js  →  Feature catalog (FREE/PRO per feature)\nsubscriptionTier.js  →  Server-synced tier cache\nuseProAccess.js  →  React hook: { tier, isPro, canAccess, loading }\nUpgradeModal.jsx  →  Checkout CTA / Manage Billing / Guest prompt\n```\n\nAll gating decisions flow through `hasFeatureAccess(featureKey, userTier)`. No component directly checks subscription state.\n\n---\n\n## Billing Architecture\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant App as React App\n    participant EF as Edge Function\n    participant LS as Lemon Squeezy\n    participant DB as PostgreSQL\n\n    User-\u003e\u003eApp: Click \"Upgrade to PRO\"\n    App-\u003e\u003eEF: create-checkout (JWT auth)\n    EF-\u003e\u003eLS: Create checkout session\n    LS--\u003e\u003eEF: Checkout URL\n    EF--\u003e\u003eApp: { url }\n    App-\u003e\u003eLS: Redirect to checkout\n    LS-\u003e\u003eUser: Payment form\n    User-\u003e\u003eLS: Complete payment\n    LS-\u003e\u003eEF: Webhook (HMAC-SHA256)\n    EF-\u003e\u003eDB: Upsert user_subscriptions\n    User-\u003e\u003eApp: Return (?checkout=success)\n    App-\u003e\u003eDB: get_my_subscription()\n    DB--\u003e\u003eApp: { tier: \"pro\" }\n```\n\n### Edge Functions\n\n| Function | Purpose | Auth |\n|----------|---------|------|\n| `create-checkout` | Creates Lemon Squeezy checkout URL | JWT required |\n| `create-portal` | Returns customer portal URL for billing management | JWT required |\n| `lemonsqueezy-webhook` | Processes subscription events, upserts DB | HMAC signature |\n| `health-check` | Monitors all services every 60s | Service role |\n\n### Subscription Table\n\nThe `user_subscriptions` table uses provider-agnostic column names (`provider_customer_id`, `provider_subscription_id`, `provider_price_id`) to support billing provider changes without schema migrations.\n\n---\n\n## Security Model\n\nSix layers of defense - no layer trusts the one above it:\n\n| Layer | Controls |\n|-------|----------|\n| Edge | HTTPS enforced, HSTS (1 year, preload), strict CORS |\n| Application | CSP (no inline scripts), X-Frame-Options DENY, COOP/CORP |\n| API | `SECURITY DEFINER` RPCs, rate limiting (120 checks/minute) |\n| Database | Row Level Security on all tables, server-side answer validation |\n| Billing | Webhook signature verification, fail-closed entitlement, no client trust |\n| Code | CodeQL, Trivy, Gitleaks, npm audit, Kyverno, Dependabot |\n\n---\n\n## Observability\n\n### Health Monitoring\n\nA self-monitoring loop built on Supabase:\n1. **pg_cron** triggers a health-check Edge Function every 60 seconds\n2. The function checks DB, API, Quiz Engine, Leaderboard, and Auth\n3. Results are written to status tables (append-only for history)\n4. 3 consecutive failures auto-create an incident and send an email alert via Resend\n5. The frontend polls and renders a live status page at [status.kubequest.online](https://status.kubequest.online)\n\n### Error Tracking\n\n[Sentry](https://sentry.io) captures production errors including uncaught exceptions, render crashes, and RPC failures. PII is scrubbed before transmission. Web Vitals (LCP, CLS, INP) are reported as breadcrumbs.\n\n### Synthetic Monitoring\n\nGitHub Actions workflows verify production health externally:\n- `synthetic-monitor.yml` - every 6 hours: homepage, API, response time, security headers\n- `uptime.yml` - every 30 minutes: homepage and auth endpoint\n\nDocumentation: [docs/monitoring.md](docs/monitoring.md) | [docs/observability.md](docs/observability.md)\n\n---\n\n## CI/CD \u0026 Supply Chain Security\n\n### PR Gate\n\nAll checks must pass before merge:\n\n| Check | Tool |\n|-------|------|\n| Build | Vite production build |\n| Test | Vitest (742+ tests) |\n| RPC Signatures | Validates Supabase RPC parameter contracts |\n| npm Audit | Dependency vulnerability scan |\n| Gitleaks | Secret scanning |\n| CodeQL | Static analysis |\n| Trivy | Container vulnerability scan |\n| K8s Policies | Kyverno manifest validation |\n\n### Publish Pipeline\n\nPush to `main` triggers Release Please (version bump + CHANGELOG). Version tags trigger Docker build, Trivy scan, GHCR push, SBOM + provenance attestation, and Cosign keyless signing.\n\n### Supply Chain Security\n\n- **Cosign** keyless image signing via GitHub OIDC\n- **SBOM** attached to every published image\n- **Provenance** attestation (`mode=max`)\n- **Trivy** fails on HIGH/CRITICAL CVEs\n- **Kyverno** enforces trusted registries, no-latest tags, resource limits\n- **Dependabot** weekly updates for npm, Docker, Actions\n\n---\n\n## Local Development\n\n### Prerequisites\n\n- Node.js 18+\n- A [Supabase](https://supabase.com) account (optional - guest mode works without it)\n\n### Setup\n\n```bash\ngit clone https://github.com/or-carmeli/KubeQuest.git\ncd KubeQuest\nnpm install\ncp .env.example .env   # add credentials\nnpm run dev            # http://localhost:5173\n```\n\n### Scripts\n\n```bash\nnpm run dev      # development server\nnpm run build    # production build\nnpm run preview  # preview production build locally\nnpm run test     # run tests (vitest)\n```\n\n---\n\n## Environment Variables\n\n### Frontend (Vercel / `.env`)\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `VITE_SUPABASE_URL` | Yes | Supabase project URL |\n| `VITE_SUPABASE_ANON_KEY` | Yes | Supabase anonymous key |\n| `VITE_SENTRY_DSN` | No | Sentry DSN for error tracking |\n| `VITE_SENTRY_ENVIRONMENT` | No | Sentry environment tag |\n\n### Supabase Edge Functions (secrets)\n\n| Secret | Required | Description |\n|--------|----------|-------------|\n| `LEMONSQUEEZY_API_KEY` | Yes | Lemon Squeezy API key |\n| `LEMONSQUEEZY_STORE_ID` | Yes | Lemon Squeezy store ID |\n| `LEMONSQUEEZY_VARIANT_ID` | Yes | Product variant for PRO subscription |\n| `LEMONSQUEEZY_WEBHOOK_SECRET` | Yes | Webhook signing secret (HMAC-SHA256) |\n| `SITE_URL` | Yes | Production URL for checkout redirects |\n| `RESEND_API_KEY` | No | Email alerts for health check incidents |\n\n\u003e Guest mode, quizzes, and War Room catalog work without any billing secrets. Billing secrets are only needed for PRO subscription functionality.\n\n---\n\n## Testing\n\n742+ tests covering:\n\n| Area | What's Tested |\n|------|--------------|\n| Quiz state | Persistence, resume, cross-type isolation, corrupt data recovery |\n| Scoring | Server-authoritative validation, deduplication, stable question keys |\n| Security | Auth guards, rate limit propagation, RPC parameter validation, input sanitization |\n| Feature gating | FREE/PRO access, tier resolution, fail-closed behavior |\n| Billing | Subscription status mapping, active/inactive transitions, checkout/portal stubs |\n| Analytics | Event catalog completeness, uniqueness, error resilience |\n| Incidents | Unlock progression, deterministic sort, completion invariants |\n| Architecture | Scenario unlock logic, scoring calculations |\n\n```bash\nnpm run test\n```\n\n---\n\n## Kubernetes Deployment\n\nThe `k8s/` directory contains production-ready manifests for self-hosting on any Kubernetes cluster.\n\n| Manifest | Purpose |\n|----------|---------|\n| `namespace.yaml` | Isolated namespace `kubequest` |\n| `deployment.yaml` | 2 replicas, resource limits, liveness + readiness probes |\n| `service.yaml` | ClusterIP on port 80 |\n| `ingress.yaml` | nginx Ingress with TLS via cert-manager |\n| `hpa.yaml` | HPA: 2-10 pods at 70% CPU |\n\n```bash\nkubectl apply -f k8s/\n```\n\nDocker image available at `ghcr.io/or-carmeli/kubequest`.\n\n---\n\n## Project Structure\n\n```\nsrc/\n  App.jsx                  # Main application\n  main.jsx                 # Entry point (Sentry, Web Vitals, React mount)\n  api/                     # Supabase RPCs (quiz, monitoring, analytics)\n  config/                  # Feature gating, billing, subscription tier, analytics events\n  content/                 # Quiz questions, daily challenges, incidents, scenarios\n  components/              # UI components (quiz, roadmap, stats, upgrade modal)\n    architecture/          # Architecture scenario components\n    shared/                # Reusable UI primitives\n  hooks/                   # Custom React hooks (useProAccess, useIsMobile, useTimeRange)\n  utils/                   # Helpers (storage, i18n, telemetry, vercel env detection)\n  features/\n    sandbox/               # War Room incident engine\n      data/                # 9 catalog incidents\n      engine/              # Command execution, scoring, progress, hints, generation\n      components/          # Terminal, result card, investigation replay, share\n      views/               # WarRoomView (incident list + investigation)\npublic/\n  sw.js                    # Service worker (offline cache, stale asset recovery)\n  boot.js                  # Blank-screen safety net, SW registration\nsupabase/\n  migrations/              # 83 database migrations\n  functions/               # Edge Functions (billing, health check, metrics)\n  config.toml              # Function-level JWT configuration\nk8s/                       # Kubernetes manifests\n  policies/                # Kyverno admission policies\n.github/\n  workflows/               # 10 CI/CD workflows\n  dependabot.yml           # Weekly dependency updates\ndocs/                      # Monitoring, observability, privacy, K8s policies\n```\n\n---\n\n## Troubleshooting\n\n### \"Unexpected token '\u003c'\" errors in Sentry\n\nVercel Analytics and Speed Insights scripts (`/_vercel/*.js`) are only loaded on production hostnames (`kubequest.online`). On localhost or `vite preview`, these components are not rendered, preventing HTML-as-JS parse errors.\n\n### Stale assets after deployment\n\nThe service worker detects MIME type mismatches when Vercel serves HTML for old hashed asset URLs. It purges caches and triggers a client reload automatically. A blank-screen safety net in `boot.js` shows recovery UI if React fails to render within 8 seconds.\n\n### Billing \"not configured\" errors\n\nEdge Functions require Lemon Squeezy secrets to be set via `supabase secrets set`. Without them, checkout returns a user-facing error and users stay on the FREE tier (fail-closed).\n\n### Quiz answer RPC failures\n\nThe `check_quiz_answer` and `check_daily_answer` RPCs use `stable_question_key` for content-derived identification. Client-side validation rejects null/empty keys before calling the RPC.\n\n---\n\n## Contributing\n\nContributions welcome - new questions, bug fixes, incident scenarios.\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and guidelines.\n\n---\n\n## Disclaimer\n\nKubeQuest is an independent training platform and is not affiliated with, sponsored by, or endorsed by the Linux Foundation, the CNCF, or any certification body. CKA and Kubernetes are trademarks of the Cloud Native Computing Foundation.\n\n---\n\n## License\n\n[MIT](LICENSE) - Or Carmeli\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2For-carmeli%2Fkubequest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2For-carmeli%2Fkubequest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2For-carmeli%2Fkubequest/lists"}