From ab1935edd016809b8ad570ffa6b48f189f6e9bf8 Mon Sep 17 00:00:00 2001
From: Sinduri Guntupalli Register Flip ` (takes priority over `architecture_ascii`) |
+| `diagram_alt` | string | Accessible alt text for the SVG. Required when `architecture_diagram` is set. |
+| `architecture_ascii` | YAML block scalar (`\|`) | `
` block, shown when no SVG is present |
+| `architecture` | array of Markdown strings | Prose paragraphs always rendered below the diagram or ASCII block |
+
+### SVG in the challenges repo (normal path)
+
+Add the SVG to the challenges repo at:
+
+```text
+adventures/
-HookContext.getCtx() returns the merged evaluation context (global + transaction + invocation). Use a fixed allowlist of List.of("species", "country", "dose"). Never iterate the whole context: targetingKey joins to PII in real apps, and span attributes are retained for days in Tempo at scale.HookContext.getCtx() returns the merged evaluation context (global + transaction + invocation). Use a fixed allowlist of List.of("species", "country", "dose"). Never iterate the whole context: targetingKey joins to PIIPersonally Identifiable Information in real apps, and span attributes are retained for days in Tempo at scale.ContextSpanHook alongside TracesHook and MetricsHook in OpenFeatureConfig. The verifier searches Tempo for feature_flag.context.dose=underdose once you are done.flags.json has two flags: loadgen_active (off by default) and the misbehaving vision_amplifier_v2. flagd watches the file and picks up changes within about a second.loadgen_active to on. The k6 loadgen polls it every two seconds and starts five virtual users hammering the lab. Within a minute, latency p99 should climb ~200ms and the 5xx rate ~10% on the dashboard, confirming that the bad arm of vision_amplifier_v2 is active.
Wait around 5 minutes for the Codespace to provision a Kubernetes cluster, Argo CD, and the sample app. Press Cmd+Shift+P (or Ctrl+Shift+P on Windows/Linux) and search for 'View Creation Log' to track progress.
" }, @@ -106,9 +106,9 @@ export const ECHOES_LOST_IN_ORBIT: Adventure = { "All rollouts complete successfully", ], toolbox: [ - { name: "kubectl", description: "Kubernetes CLI for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" }, + { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" }, { name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" }, - { name: "k9s", description: "terminal UI for managing and inspecting your cluster", url: "https://k9scli.io/" }, + { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" }, { name: "Argo CD CLI", description: "manage Argo CD applications from the command line", url: "https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd/" }, { name: "Argo Rollouts kubectl plugin", description: "extended kubectl commands for managing rollouts", url: "https://argo-rollouts.readthedocs.io/en/stable/features/kubectl-plugin/" }, ], @@ -182,13 +182,13 @@ kubectl argo rollouts get rollout echo-server -n echo-prod --watch ], objective: [ "Automated rollout progression to HotROD version 1.76.0 driven by observability signals", - "OpenTelemetry Collector configured with an OTLP receiver for HotROD traces, a Spanmetrics connector converting traces to metrics, trace export to Jaeger, and metrics export to Prometheus", + "OpenTelemetry Collector configured with an OTLPOpenTelemetry Protocol receiver for HotROD traces, a Spanmetrics connector converting traces to metrics, trace export to Jaeger, and metrics export to Prometheus", "Canary analysis with three PromQL queries: traffic detection (at least 0.05 req/s to prevent idle canaries), error rate below 5%, and 95th-percentile latency below 1000ms", ], toolbox: [ - { name: "kubectl", description: "Kubernetes CLI for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" }, + { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" }, { name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" }, - { name: "k9s", description: "terminal UI for managing and inspecting your cluster", url: "https://k9scli.io/" }, + { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" }, { name: "Argo CD CLI", description: "manage Argo CD applications from the command line", url: "https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd/" }, { name: "Argo Rollouts kubectl plugin", description: "extended kubectl commands for managing rollouts", url: "https://argo-rollouts.readthedocs.io/en/stable/features/kubectl-plugin/" }, ], @@ -215,7 +215,7 @@ argocd app get otel --refreshIf you changed HotROD, retry the rollout:
kubectl argo rollouts retry rollout hotrod -n hotrod
-If you changed the OTel Collector config, restart it:
+If you changed the OTelOpenTelemetry Collector config, restart it:
kubectl rollout restart daemonset/collector -n otel
` },
{ title: "Watch the Rollout", content: `Watch rollout progress. The rollout should progress automatically based on analysis metrics:
diff --git a/src/data/adventures/echoes-lost-in-orbit/adventure.yaml b/src/data/adventures/echoes-lost-in-orbit/adventure.yaml index 623e144bb..11dc069bd 100644 --- a/src/data/adventures/echoes-lost-in-orbit/adventure.yaml +++ b/src/data/adventures/echoes-lost-in-orbit/adventure.yaml @@ -14,11 +14,11 @@ tags: contributor: name: Katharina Sick url: https://ksick.dev/ - about: DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native + about: DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe. backstory: - - Welcome aboard the GitOps Starliner, a multi-species engineering vessel orbiting the vibrant planet of Polaris-9. + - Welcome aboard the GitOps Starliner, a multi-species engineering vessel orbiting the vibrant planet of Polaris-9. Life in this quadrant is wonderfully diverse, from the whispering cloud-dwellers of Nebulon to the rhythmic click-speakers of Crustacea Prime. - Communication between species used to be seamless, thanks to the Echo Server, a universal translator that instantly @@ -34,7 +34,7 @@ levels: topics: - Argo CD learnings: - - Debug GitOps flows with Argo CD + - Debug GitOps flows with Argo CD - ApplicationSet templating & pitfalls - Environment isolation & namespaces - "Sync policies: automated, prune & self-heal" @@ -55,13 +55,13 @@ levels: - Confirm that updates roll out automatically without leaving stale resources behind toolbox: - name: kubectl - description: Kubernetes CLI for interacting with the cluster + description: Kubernetes CLI for interacting with the cluster url: https://kubernetes.io/docs/reference/kubectl/ - name: kubens description: fast way to switch between Kubernetes namespaces url: https://github.com/ahmetb/kubectx - name: k9s - description: terminal UI for managing and inspecting your cluster + description: terminal UI for managing and inspecting your cluster url: https://k9scli.io/ services: - name: "Argo CD" @@ -140,13 +140,13 @@ levels: - All rollouts complete successfully toolbox: - name: kubectl - description: Kubernetes CLI for interacting with the cluster + description: Kubernetes CLI for interacting with the cluster url: https://kubernetes.io/docs/reference/kubectl/ - name: kubens description: fast way to switch between Kubernetes namespaces url: https://github.com/ahmetb/kubectx - name: k9s - description: terminal UI for managing and inspecting your cluster + description: terminal UI for managing and inspecting your cluster url: https://k9scli.io/ - name: Argo CD CLI description: manage Argo CD applications from the command line @@ -280,19 +280,19 @@ levels: distributed tracing." objective: - Automated rollout progression to HotROD version 1.76.0 driven by observability signals - - OpenTelemetry Collector configured with an OTLP receiver for HotROD traces, a Spanmetrics connector converting + - OpenTelemetry Collector configured with an OTLP receiver for HotROD traces, a Spanmetrics connector converting traces to metrics, trace export to Jaeger, and metrics export to Prometheus - "Canary analysis with three PromQL queries: traffic detection (at least 0.05 req/s to prevent idle canaries), error rate below 5%, and 95th-percentile latency below 1000ms" toolbox: - name: kubectl - description: Kubernetes CLI for interacting with the cluster + description: Kubernetes CLI for interacting with the cluster url: https://kubernetes.io/docs/reference/kubectl/ - name: kubens description: fast way to switch between Kubernetes namespaces url: https://github.com/ahmetb/kubectx - name: k9s - description: terminal UI for managing and inspecting your cluster + description: terminal UI for managing and inspecting your cluster url: https://k9scli.io/ - name: Argo CD CLI description: manage Argo CD applications from the command line @@ -363,7 +363,7 @@ levels: ``` - If you changed the OTel Collector config, restart it: + If you changed the OTel Collector config, restart it: ```sh diff --git a/src/data/adventures/lex-imperfecta.generated.ts b/src/data/adventures/lex-imperfecta.generated.ts index 858d1b49a..a0c187dc7 100644 --- a/src/data/adventures/lex-imperfecta.generated.ts +++ b/src/data/adventures/lex-imperfecta.generated.ts @@ -13,7 +13,7 @@ export const LEX_IMPERFECTA: Adventure = { contributor: { name: "Katharina Sick", url: "https://ksick.dev/", - aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.", + aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.", }, backstory: [ "The Roman Republic has built a sophisticated legal system to protect its citizens — but the laws were written in haste, and the exceptions were written too generously. Policies go unenforced, the wrong citizens are exempt, and something has slipped through the gates unnoticed. As a newly appointed Praetor, your mission is to restore order before chaos takes hold.", @@ -37,9 +37,9 @@ export const LEX_IMPERFECTA: Adventure = { name: "The Twelve Tables", difficulty: "Beginner", topics: ["Kyverno", "Kubernetes"], - audience: "Platform engineers, SREsSite Reliability Engineers, and developers curious about Kubernetes security — no prior Kyverno experience needed, but familiarity with basickubectl and YAML will help.",
+ audience: "Platform engineers, SREsSite Reliability Engineers, and developers curious about Kubernetes security — no prior Kyverno experience needed, but familiarity with basic kubectl and YAML will help.",
learnings: [
- "How Kyverno ValidatingPolicy resources and CEL validation expressions work",
+ "How Kyverno ValidatingPolicy resources and CELCommon Expression Language validation expressions work",
"The difference between Audit, Deny, and Warn validation actions",
"How to use custom label keys to enforce workload identity standards",
"How Kyverno MutatingPolicy resources automatically patch incoming workloads at admission",
@@ -61,14 +61,14 @@ export const LEX_IMPERFECTA: Adventure = {
],
architecture: [
"The Twelve Tables enforced Roman law at the gates — before a citizen could act, not after the damage was done. Kyverno works the same way: it intercepts every workload request before it reaches the cluster. A misconfigured policy doesn't just fail to enforce — it fails silently, letting non-compliant workloads slip through while you assume everything is fine.
", - "Your Codespace comes with a Kubernetes cluster and Kyverno pre-installed. Three broken policies are already deployed in manifests/policies/ — two ValidatingPolicy resources and one MutatingPolicy. Edit them directly and re-apply with kubectl. The pods in manifests/pods/ are for reference only — no GitOps, no dashboards.
Your Codespace comes with a Kubernetes cluster and Kyverno pre-installed. Three broken policies are already deployed in manifests/policies/ — two ValidatingPolicy resources and one MutatingPolicy. Edit them directly and re-apply with kubectl. The pods in manifests/pods/ are for reference only — no GitOpsGit Operations, no dashboards.
When your Codespace is ready, four pods are already running — or trying to. Open a terminal and check what's going on:
@@ -91,7 +91,7 @@ kubectl get mutatingpolicy stamp-travel-permit -o yamlNavigate to ValidatingPolicy resources with :validatingpolicies and MutatingPolicy resources with :mutatingpolicies to inspect all three policies.
Review the Objective and investigate what's wrong in manifests/policies/.
All three broken policies are in manifests/policies/. Read them carefully — each has a different kind of misconfiguration.
Test Locally with the Kyverno CLI
+Test Locally with the Kyverno CLICommand Line Interface
Before applying to the cluster, you can use the kyverno CLI to test your policy changes locally against the workload manifests:
kyverno apply manifests/policies/require-labels.yaml --resource manifests/pods/missing-labels.yaml
kyverno apply manifests/policies/no-privileged-containers.yaml --resource manifests/pods/privileged.yaml
@@ -121,7 +121,7 @@ kyverno apply manifests/policies/stamp-travel-permit.yaml --resource manifests/p
name: "Governing the Provinces",
difficulty: "Intermediate",
topics: ["Kyverno", "Policy Reporter", "Kubernetes"],
- audience: "Platform engineers and SREsSite Reliability Engineers who have some familiarity with Kyverno, ideally after completing the Beginner level. You should be comfortable reading Kubernetes YAML and basic kubectl commands.",
+ audience: "Platform engineers and SREsSite Reliability Engineers who have some familiarity with Kyverno, ideally after completing the Beginner level. You should be comfortable reading Kubernetes YAML and basic kubectl commands.",
learnings: [
"How to scope policies using ValidatingPolicy (cluster-wide) and NamespacedValidatingPolicy (per-namespace), and when to use each",
"How CEL expressions in ValidatingPolicy and PolicyException express fine-grained admission conditions",
@@ -153,7 +153,7 @@ kyverno apply manifests/policies/stamp-travel-permit.yaml --resource manifests/p
toolbox: [
{ name: "kubectl", description: "Apply and inspect cluster resources, check namespace labels and policy status", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kyverno CLI", description: "Test and lint policies locally before applying to the cluster", url: "https://kyverno.io/docs/kyverno-cli/" },
- { name: "k9s", description: "Explore cluster resources and policy reports in a terminal UI", url: "https://k9scli.io/" },
+ { name: "k9s", description: "Explore cluster resources and policy reports in a terminal UIUser Interface", url: "https://k9scli.io/" },
],
howToPlay: [
{ title: "Explore the Estate", content: `When your Codespace is ready, the policy estate is already deployed, but something is wrong.
@@ -184,7 +184,7 @@ kubectl get policyreports -A
manifests/exceptions/.
Think about what each policy is supposed to cover, and compare that against what it is actually
matching. The namespace labels you saw with kubectl get ns --show-labels are a key part of the picture.
-Test locally with the Kyverno CLI before applying:
+Test locally with the Kyverno CLICommand Line Interface before applying:
kyverno apply manifests/policies/require-census.yaml --resource manifests/workloads/citizens.yaml
kyverno apply manifests/policies/aegyptus-require-scribe-role.yaml --resource manifests/workloads/aegyptus-legacy-scribe.yaml
@@ -201,7 +201,7 @@ policy reports, the OpenReports data behind Policy Reporter, as the audit of rec
helpfulLinks: [
{ title: "Kyverno ValidatingPolicy", url: "https://kyverno.io/docs/policy-types/validating-policy/", description: "Reference docs for ValidatingPolicy and NamespacedValidatingPolicy: the policy types you'll fix" },
{ title: "Kyverno PolicyException", url: "https://kyverno.io/docs/guides/exceptions/", description: "How to write and scope a PolicyException to exempt specific workloads" },
- { title: "CEL Validation Expressions", url: "https://kubernetes.io/docs/reference/using-api/cel/", description: "How CEL expressions work in Kubernetes admission, including accessing namespace context" },
+ { title: "CEL Validation Expressions", url: "https://kubernetes.io/docs/reference/using-api/cel/", description: "How CEL expressions work in Kubernetes admission, including accessing namespace context" },
{ title: "Policy Reporter", url: "https://kyverno.github.io/policy-reporter/", description: "How to use Policy Reporter to audit and visualise policy results across the cluster" },
{ title: "OpenReports Format", url: "https://openreports.io/", description: "The OpenReports standard that Kyverno uses to emit PolicyReport resources" },
],
@@ -216,7 +216,7 @@ policy reports, the OpenReports data behind Policy Reporter, as the audit of rec
name: "Quis Custodiet",
difficulty: "Expert",
topics: ["Kyverno", "Policy Reporter", "Kubernetes"],
- audience: "Security engineers and platform engineers who want to explore the boundary between admission control and runtime security. Completing the Intermediate level first is helpful but not required. You should be comfortable reading Kyverno ValidatingPolicies and CEL expressions. No prior Falco experience required.",
+ audience: "Security engineers and platform engineers who want to explore the boundary between admission control and runtime security. Completing the Intermediate level first is helpful but not required. You should be comfortable reading Kyverno ValidatingPolicies and CELCommon Expression Language expressions. No prior Falco experience required.",
learnings: [
"How Falco rules are structured: conditions, output, and kernel-level fields, and how to write a rule targeting a specific runtime behaviour",
"Why privileged: false is not enough: how Linux capabilities grant host-level access without the privileged flag",
@@ -235,13 +235,13 @@ policy reports, the OpenReports data behind Policy Reporter, as the audit of rec
"And while the Guard slept, an intruder crept in. It declared valid labels, passed the census, and presented itself as a loyal citizen of the Republic. Its papers were in order. Its power was not. Once inside, it reached straight for the census archive: the imperial rolls of every citizen, sealed records it had no right to touch. It reads them on a loop and tries to send them out of the Republic.",
],
objective: [
- "The Praetorian Guard awake: Falco fires an alert every time an unauthorized process reads the census archive, with live alerts streaming into the Falcosidekick UI",
+ "The Praetorian Guard awake: Falco fires an alert every time an unauthorized process reads the census archive, with live alerts streaming into the Falcosidekick UIUser Interface",
"The gate closed: the intruder is denied re-admission. The policy that kept privileged containers out now covers every path to unchecked host power",
"The archive sealed: the census-archive secret is inaccessible to any workload that does not bear the Archivist role",
"The empire-wide laws holding: all intermediate-level checks still green across every province",
],
architecture: [
- "The estate inherits the full intermediate topology: four province namespaces (gallia, hispania, britannia, aegyptus) and one infra namespace (castra), each labelled as before. Alongside the Kyverno stack and Policy Reporter, the cluster now runs Falco (eBPFextended Berkeley Packet Filter-based, as a DaemonSet) and Falcosidekick with its UI at port 30111. At startup, an intruder pod is already running in one of the provinces, quietly reading the census archive: imperial rolls that only workloads bearing the republic.rome/role: archivist label are permitted to access.
",
+ "The estate inherits the full intermediate topology: four province namespaces (gallia, hispania, britannia, aegyptus) and one infra namespace (castra), each labelled as before. Alongside the Kyverno stack and Policy Reporter, the cluster now runs Falco (eBPFextended Berkeley Packet Filter-based, as a DaemonSet) and Falcosidekick with its UI at port 30111. At startup, an intruder pod is already running in one of the provinces, quietly reading the census archive: imperial rolls that only workloads bearing the republic.rome/role: archivist label are permitted to access.
",
"Your working directory is the challenge root. manifests/secrets/ and manifests/workloads/ are already in place. They define the estate and the intruder, and need no changes. Everything else is yours to investigate and fix.
",
],
toolbox: [
@@ -277,8 +277,8 @@ to share expressions across validations, a pattern worth exploring.` },
{ title: "Act 3: Seal the Archive", content: `Open manifests/policies/: something is missing; the other policies show the structure, write
what's needed, then run make apply.
Going further: even with the archive sealed at admission, any workload admitted with the
-Archivist role can read the secret. Kubernetes RBACRole-Based Access Control can restrict which service accounts may
-get a secret at the APIApplication Programming Interface level, a complementary layer that admission control alone cannot
+Archivist role can read the secret. Kubernetes RBACRole-Based Access Control can restrict which service accounts may
+get a secret at the APIApplication Programming Interface level, a complementary layer that admission control alone cannot
provide.
` },
],
helpfulLinks: [
diff --git a/src/data/adventures/lex-imperfecta/adventure.yaml b/src/data/adventures/lex-imperfecta/adventure.yaml
index a50ae3c3a..4e56e3a6b 100644
--- a/src/data/adventures/lex-imperfecta/adventure.yaml
+++ b/src/data/adventures/lex-imperfecta/adventure.yaml
@@ -26,7 +26,7 @@ rewards:
contributor:
name: Katharina Sick
url: https://ksick.dev/
- about: DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native
+ about: DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native
and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities,
inline skating rinks, and quiz nights across Europe.
levels:
@@ -57,7 +57,7 @@ levels:
**`republic.rome/travel-permit: granted`** label"
- "**All other workloads** deploy and run successfully in the cluster"
what_you_learn:
- - How Kyverno [ValidatingPolicy](https://kyverno.io/docs/policy-types/validating-policy/) resources and [CEL
+ - How Kyverno [ValidatingPolicy](https://kyverno.io/docs/policy-types/validating-policy/) resources and [CEL
validation expressions](https://kubernetes.io/docs/reference/using-api/cel/) work
- The difference between [Audit, Deny, and Warn](https://kyverno.io/docs/policy-types/validating-policy/)
validation actions
@@ -72,7 +72,7 @@ levels:
through while you assume everything is fine."
- Your Codespace comes with a Kubernetes cluster and Kyverno pre-installed. Three broken policies are already
deployed in `manifests/policies/` — two `ValidatingPolicy` resources and one `MutatingPolicy`. Edit them
- directly and re-apply with `kubectl`. The pods in `manifests/pods/` are for reference only — no GitOps, no
+ directly and re-apply with `kubectl`. The pods in `manifests/pods/` are for reference only — no GitOps, no
dashboards.
toolbox:
- name: kubectl
@@ -83,7 +83,7 @@ levels:
description: Test and lint policies locally before applying
- name: k9s
url: https://k9scli.io/
- description: Explore cluster resources in a terminal UI
+ description: Explore cluster resources in a terminal UI
services: []
how_to_play:
- id: explore
@@ -151,7 +151,7 @@ levels:
misconfiguration.
- **Test Locally with the Kyverno CLI**
+ **Test Locally with the Kyverno CLI**
Before applying to the cluster, you can use the `kyverno` CLI to test your policy changes locally against the
@@ -269,7 +269,7 @@ levels:
description: Test and lint policies locally before applying to the cluster
- name: k9s
url: https://k9scli.io/
- description: Explore cluster resources and policy reports in a terminal UI
+ description: Explore cluster resources and policy reports in a terminal UI
how_to_play:
- id: explore
title: Explore the Estate
@@ -316,7 +316,7 @@ levels:
matching. The namespace labels you saw with `kubectl get ns --show-labels` are a key part of the picture.
- **Test locally with the Kyverno CLI before applying:**
+ **Test locally with the Kyverno CLI before applying:**
```bash
@@ -360,7 +360,7 @@ levels:
description: How to write and scope a PolicyException to exempt specific workloads
- title: CEL Validation Expressions
url: https://kubernetes.io/docs/reference/using-api/cel/
- description: How CEL expressions work in Kubernetes admission, including accessing namespace context
+ description: How CEL expressions work in Kubernetes admission, including accessing namespace context
- title: Policy Reporter
url: https://kyverno.github.io/policy-reporter/
description: How to use Policy Reporter to audit and visualise policy results across the cluster
@@ -384,7 +384,7 @@ levels:
unauthorized access.
audience: Security engineers and platform engineers who want to explore the boundary between admission control and
runtime security. Completing the Intermediate level first is helpful but not required. You should be comfortable
- reading Kyverno ValidatingPolicies and CEL expressions. No prior Falco experience required.
+ reading Kyverno ValidatingPolicies and CEL expressions. No prior Falco experience required.
backstory:
- "The Republic's defences have always rested on the law: block the wrong workloads at the gate, and nothing bad
gets in. But the Senate's Praetorian Guard was built for a different threat: the workload that slips through and
@@ -396,7 +396,7 @@ levels:
It reads them on a loop and tries to send them out of the Republic."
objective:
- "**The Praetorian Guard awake**: Falco fires an alert every time an unauthorized process reads the census
- archive, with live alerts streaming into the Falcosidekick UI"
+ archive, with live alerts streaming into the Falcosidekick UI"
- "**The gate closed**: the intruder is denied re-admission. The policy that kept privileged containers out now
covers every path to unchecked host power"
- "**The archive sealed**: the census-archive secret is inaccessible to any workload that does not bear the
diff --git a/src/data/adventures/summaries.ts b/src/data/adventures/summaries.ts
index dc4a9a94b..057d91584 100644
--- a/src/data/adventures/summaries.ts
+++ b/src/data/adventures/summaries.ts
@@ -11,7 +11,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
isLive: true,
icon: "Compass",
@@ -52,7 +52,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Scale",
levels: [
@@ -62,7 +62,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Beginner",
topics: ["Kyverno", "Kubernetes"],
learnings: [
- "How Kyverno ValidatingPolicy resources and CEL validation expressions work",
+ "How Kyverno ValidatingPolicy resources and CELCommon Expression Language validation expressions work",
"The difference between Audit, Deny, and Warn validation actions",
"How to use custom label keys to enforce workload identity standards",
"How Kyverno MutatingPolicy resources automatically patch incoming workloads at admission",
@@ -104,7 +104,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Simon Schrottner",
url: "https://schrottner.at/",
- aboutHtml: "CNCF Ambassador and maintainer of OpenFeature and JUnit Pioneer. Helps teams release faster and with more confidence through open standards, feature flagging, and the communities that make both possible. A familiar face at KubeCon EU, Devoxx, ContainerDays, and meetups across Europe.",
+ aboutHtml: "CNCFCloud Native Computing Foundation Ambassador and maintainer of OpenFeature and JUnit Pioneer. Helps teams release faster and with more confidence through open standards, feature flagging, and the communities that make both possible. A familiar face at KubeCon EU, Devoxx, ContainerDays, and meetups across Europe.",
},
icon: "FlaskConical",
levels: [
@@ -114,8 +114,8 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Beginner",
topics: ["OpenFeature", "flagd", "Spring Boot"],
learnings: [
- "How an OpenFeature client and provider work together: the SDK is provider-agnostic and the flagd provider plugs in via dependency only",
- "What remote provider means in practice: the SDK calls a separate flag service (flagd) over gRPC, not parsing flags.json itself",
+ "How an OpenFeature client and provider work together: the SDKSoftware Development Kit is provider-agnostic and the flagd provider plugs in via dependency only",
+ "What remote provider means in practice: the SDK calls a separate flag service (flagd) over gRPCGoogle Remote Procedure Call, not parsing flags.json itself",
"What flags.json looks like for flagd (state, variants, defaultVariant)",
"Why hot-reload of the flag file matters operationally: configuration without redeploy",
],
@@ -138,7 +138,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenFeature", "OpenTelemetry", "Grafana", "Spring Boot"],
learnings: [
"How the OpenFeature OpenTelemetry hooks (TracesHook and MetricsHook) join flag evaluations to the rest of an application's telemetry without a separate ingestion path",
- "How to author your own Hook: a tiny class that copies merged-eval-context attributes onto the active OTel span, closing the loop between why a flag resolved the way it did and what the operator sees in Tempo",
+ "How to author your own Hook: a tiny class that copies merged-eval-context attributes onto the active OTelOpenTelemetry span, closing the loop between why a flag resolved the way it did and what the operator sees in Tempo",
"How fractional rollout in flagd buckets users by targetingKey (same key, same bucket, every request) and how to read that bucketing off a dashboard",
"How a flag flip is a faster operational lever than a redeploy when a rollout is misbehaving: the difference between a one-line config change and a twenty-minute deployment",
],
@@ -154,7 +154,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Telescope",
levels: [
@@ -174,7 +174,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Intermediate",
topics: ["OpenTelemetry", "OpenLLMetry", "Jaeger", "Prometheus"],
learnings: [
- "Instrument RAG pipelines with OpenLLMetry",
+ "Instrument RAGRetrieval-Augmented Generation pipelines with OpenLLMetry",
"Create custom OpenTelemetry metrics in Python",
"Write PromQL queries & recording rules in Prometheus",
],
@@ -186,7 +186,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenTelemetry", "OpenLLMetry", "Jaeger"],
learnings: [
"OpenTelemetry GenAI semantic conventions",
- "Tail sampling in the OTel Collector",
+ "Tail sampling in the OTelOpenTelemetry Collector",
],
},
],
@@ -200,7 +200,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Building2",
levels: [
@@ -211,7 +211,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenTofu"],
learnings: [
"Infrastructure as Code with OpenTofu",
- "Remote state management with GCS backend",
+ "Remote state management with GCSGoogle Cloud Storage backend",
"Dynamic resource provisioning with for_each",
"Conditional resources with the enabled meta-argument, new in OpenTofu",
],
@@ -223,7 +223,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenTofu", "TDD"],
learnings: [
"OpenTofu module testing with tofu test",
- "Test-Driven Development (TDD) workflow",
+ "Test-Driven Development (TDDTest-Driven Development) workflow",
"Input validation with custom rules",
"Refactoring infrastructure safely with moved blocks",
],
@@ -250,7 +250,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Satellite",
levels: [
@@ -260,7 +260,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Beginner",
topics: ["Argo CD"],
learnings: [
- "Debug GitOps flows with Argo CD",
+ "Debug GitOpsGit Operations flows with Argo CD",
"ApplicationSet templating & pitfalls",
"Environment isolation & namespaces",
"Sync policies: automated, prune & self-heal",
diff --git a/src/data/adventures/the-ai-observatory.generated.ts b/src/data/adventures/the-ai-observatory.generated.ts
index e1a83b4ec..d82871c23 100644
--- a/src/data/adventures/the-ai-observatory.generated.ts
+++ b/src/data/adventures/the-ai-observatory.generated.ts
@@ -12,7 +12,7 @@ export const THE_AI_OBSERVATORY: Adventure = {
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
backstory: [
"You are stationed at Perimeter Alpha, a research outpost on the newly discovered planet HB-7742. The station is run by HubSystem, a central AI that manages everything from life support to data analysis.",
@@ -58,9 +58,9 @@ export const THE_AI_OBSERVATORY: Adventure = {
],
toolbox: [
{ name: "python", description: "programming language used for the HubSystem application" },
- { name: "kubectl", description: "Kubernetes CLI for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
+ { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" },
- { name: "k9s", description: "terminal UI for managing and inspecting your cluster", url: "https://k9scli.io/" },
+ { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
],
howToPlay: [
{ title: "Wait for Infrastructure", content: "Wait ~10 minutes for all infrastructure to initialize.
" },
@@ -68,7 +68,7 @@ export const THE_AI_OBSERVATORY: Adventure = {
- Port 30103: Jaeger. Analyze the traces sent by HubSystem.
` },
- { title: "Instrument the App", content: `The application code is in ./hubsystem.py. Add OpenTelemetry instrumentation using OpenLLMetry. The OTel
+ { title: "Instrument the App", content: `
The application code is in ./hubsystem.py. Add OpenTelemetry instrumentation using OpenLLMetry. The OTelOpenTelemetry
Collector and Jaeger are already configured correctly; you only need to instrument the app. You do not need to
interact with Kubernetes directly. The cluster is already running, so focus on the Python code.
` },
{ title: "Run and Investigate", content: `Run the application, interact with the AI to generate traces, then check Jaeger:
@@ -92,7 +92,7 @@ interact with Kubernetes directly. The cluster is already running, so focus on t
difficulty: "Intermediate",
topics: ["OpenTelemetry", "OpenLLMetry", "Jaeger", "Prometheus"],
learnings: [
- "Instrument RAG pipelines with OpenLLMetry",
+ "Instrument RAGRetrieval-Augmented Generation pipelines with OpenLLMetry",
"Create custom OpenTelemetry metrics in Python",
"Write PromQL queries & recording rules in Prometheus",
],
@@ -100,7 +100,7 @@ interact with Kubernetes directly. The cluster is already running, so focus on t
discussionUrl: `${COMMUNITY_URL}/t/instrument-debug-a-rag-pipeline-adventure-03-intermediate-is-live/936/2`,
deadline: "2026-03-08T23:59:00+01:00",
intro: [
- "ART's RAG pipeline is retrieving entertainment data instead of navigation coordinates and won't calculate your jump. Instrument the full retrieval pipeline with OpenLLMetry, build a custom OTel metric to quantify the distraction, and write a Prometheus recording rule to prove it.",
+ "ART's RAG pipeline is retrieving entertainment data instead of navigation coordinates and won't calculate your jump. Instrument the full retrieval pipeline with OpenLLMetry, build a custom OTelOpenTelemetry metric to quantify the distraction, and write a Prometheus recording rule to prove it.",
],
backstory: [
"You're a rogue SecUnit who just escaped from Preservation Station after being identified. A researcher helped you flee aboard the Perihelion, a university research vessel with a very opinionated AI.",
@@ -121,14 +121,14 @@ interact with Kubernetes directly. The cluster is already running, so focus on t
"Restore the navigation system so ART successfully calculates jump coordinates to RaviHyral",
],
architecture: [
- "The ART Pilot System runs as a local Python application outside Kubernetes, using a RAG (Retrieval-Augmented Generation) architecture. AI infrastructure (Ollama for LLM, Qdrant for vector storage) and observability tools (OpenTelemetry Collector, Jaeger, Prometheus) run inside Kubernetes.
",
+ "The ART Pilot System runs as a local Python application outside Kubernetes, using a RAG (Retrieval-Augmented Generation) architecture. AI infrastructure (Ollama for LLMLarge Language Model, Qdrant for vector storage) and observability tools (OpenTelemetry Collector, Jaeger, Prometheus) run inside Kubernetes.
",
"This setup lets you focus on observability patterns: edit Python code, run it, and see traces and metrics immediately without a build or deploy cycle.
",
],
toolbox: [
{ name: "python", description: "programming language used for the ART application" },
- { name: "kubectl", description: "Kubernetes CLI for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
+ { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" },
- { name: "k9s", description: "terminal UI for managing and inspecting your cluster", url: "https://k9scli.io/" },
+ { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
],
howToPlay: [
{ title: "Wait for Infrastructure", content: "Wait ~15 minutes for all infrastructure to initialize.
" },
@@ -166,7 +166,7 @@ make traffic
topics: ["OpenTelemetry", "OpenLLMetry", "Jaeger"],
learnings: [
"OpenTelemetry GenAI semantic conventions",
- "Tail sampling in the OTel Collector",
+ "Tail sampling in the OTelOpenTelemetry Collector",
],
codespacesUrl: `${CODESPACES_BASE}?devcontainer_path=.devcontainer%2F03-the-ai-observatory_03-expert%2Fdevcontainer.json&quickstart=1`,
discussionUrl: `${COMMUNITY_URL}/t/reduce-telemetry-noise-adventure-03-expert-is-live/999/1`,
@@ -193,13 +193,13 @@ make traffic
"Configure tail sampling in the OpenTelemetry Collector to keep only traces that contain errors or take longer than 5 seconds",
],
architecture: [
- "Same setup as the intermediate level: the ART Pilot System runs as a local Python application outside Kubernetes with a RAG architecture. AI infrastructure (Ollama, Qdrant) and observability tools (OpenTelemetry Collector, Jaeger) run inside Kubernetes.
",
+ "Same setup as the intermediate level: the ART Pilot System runs as a local Python application outside Kubernetes with a RAGRetrieval-Augmented Generation architecture. AI infrastructure (Ollama, Qdrant) and observability tools (OpenTelemetry Collector, Jaeger) run inside Kubernetes.
",
],
toolbox: [
{ name: "python", description: "programming language used for the ART application" },
- { name: "kubectl", description: "Kubernetes CLI for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
+ { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" },
- { name: "k9s", description: "terminal UI for managing and inspecting your cluster", url: "https://k9scli.io/" },
+ { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
],
howToPlay: [
{ title: "Wait for Infrastructure", content: "Wait ~15 minutes for all infrastructure to initialize.
" },
diff --git a/src/data/adventures/the-ai-observatory/adventure.yaml b/src/data/adventures/the-ai-observatory/adventure.yaml
index 5849d684f..11f18b30a 100644
--- a/src/data/adventures/the-ai-observatory/adventure.yaml
+++ b/src/data/adventures/the-ai-observatory/adventure.yaml
@@ -14,7 +14,7 @@ tags:
contributor:
name: Katharina Sick
url: https://ksick.dev/
- about: DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native
+ about: DevRel at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native
and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities,
inline skating rinks, and quiz nights across Europe.
backstory:
@@ -68,13 +68,13 @@ levels:
- name: python
description: programming language used for the HubSystem application
- name: kubectl
- description: Kubernetes CLI for interacting with the cluster
+ description: Kubernetes CLI for interacting with the cluster
url: https://kubernetes.io/docs/reference/kubectl/
- name: kubens
description: fast way to switch between Kubernetes namespaces
url: https://github.com/ahmetb/kubectx
- name: k9s
- description: terminal UI for managing and inspecting your cluster
+ description: terminal UI for managing and inspecting your cluster
url: https://k9scli.io/
services:
- name: "Jaeger"
@@ -85,7 +85,7 @@ levels:
content: Wait ~10 minutes for all infrastructure to initialize.
- title: Instrument the App
content: |-
- The application code is in `./hubsystem.py`. Add OpenTelemetry instrumentation using OpenLLMetry. The OTel
+ The application code is in `./hubsystem.py`. Add OpenTelemetry instrumentation using OpenLLMetry. The OTel
Collector and Jaeger are already configured correctly; you only need to instrument the app. You do not need to
interact with Kubernetes directly. The cluster is already running, so focus on the Python code.
- title: Run and Investigate
@@ -115,7 +115,7 @@ levels:
- Jaeger
- Prometheus
learnings:
- - Instrument RAG pipelines with OpenLLMetry
+ - Instrument RAG pipelines with OpenLLMetry
- Create custom OpenTelemetry metrics in Python
- Write PromQL queries & recording rules in Prometheus
devcontainer: 03-the-ai-observatory_02-intermediate
@@ -123,7 +123,7 @@ levels:
deadline: "2026-03-08T23:59:00+01:00"
intro:
- ART's RAG pipeline is retrieving entertainment data instead of navigation coordinates and won't calculate your
- jump. Instrument the full retrieval pipeline with OpenLLMetry, build a custom OTel metric to quantify the
+ jump. Instrument the full retrieval pipeline with OpenLLMetry, build a custom OTel metric to quantify the
distraction, and write a Prometheus recording rule to prove it.
backstory:
- You're a rogue SecUnit who just escaped from Preservation Station after being identified. A researcher helped
@@ -149,7 +149,7 @@ levels:
- Restore the navigation system so ART successfully calculates jump coordinates to RaviHyral
architecture:
- The ART Pilot System runs as a local Python application outside Kubernetes, using a RAG (Retrieval-Augmented
- Generation) architecture. AI infrastructure (Ollama for LLM, Qdrant for vector storage) and observability tools
+ Generation) architecture. AI infrastructure (Ollama for LLM, Qdrant for vector storage) and observability tools
(OpenTelemetry Collector, Jaeger, Prometheus) run inside Kubernetes.
- "This setup lets you focus on observability patterns: edit Python code, run it, and see traces and metrics
immediately without a build or deploy cycle."
@@ -157,13 +157,13 @@ levels:
- name: python
description: programming language used for the ART application
- name: kubectl
- description: Kubernetes CLI for interacting with the cluster
+ description: Kubernetes CLI for interacting with the cluster
url: https://kubernetes.io/docs/reference/kubectl/
- name: kubens
description: fast way to switch between Kubernetes namespaces
url: https://github.com/ahmetb/kubectx
- name: k9s
- description: terminal UI for managing and inspecting your cluster
+ description: terminal UI for managing and inspecting your cluster
url: https://k9scli.io/
services:
- name: "Prometheus"
@@ -221,7 +221,7 @@ levels:
- Jaeger
learnings:
- OpenTelemetry GenAI semantic conventions
- - Tail sampling in the OTel Collector
+ - Tail sampling in the OTel Collector
devcontainer: 03-the-ai-observatory_03-expert
discussion_url: /t/reduce-telemetry-noise-adventure-03-expert-is-live/999/1
deadline: "2026-03-08T23:59:00+01:00"
@@ -256,19 +256,19 @@ levels:
than 5 seconds
architecture:
- "Same setup as the intermediate level: the ART Pilot System runs as a local Python application outside
- Kubernetes with a RAG architecture. AI infrastructure (Ollama, Qdrant) and observability tools (OpenTelemetry
+ Kubernetes with a RAG architecture. AI infrastructure (Ollama, Qdrant) and observability tools (OpenTelemetry
Collector, Jaeger) run inside Kubernetes."
toolbox:
- name: python
description: programming language used for the ART application
- name: kubectl
- description: Kubernetes CLI for interacting with the cluster
+ description: Kubernetes CLI for interacting with the cluster
url: https://kubernetes.io/docs/reference/kubectl/
- name: kubens
description: fast way to switch between Kubernetes namespaces
url: https://github.com/ahmetb/kubectx
- name: k9s
- description: terminal UI for managing and inspecting your cluster
+ description: terminal UI for managing and inspecting your cluster
url: https://k9scli.io/
services:
- name: "Jaeger"
diff --git a/src/pages/ChallengeDetail.tsx b/src/pages/ChallengeDetail.tsx
index ee504aced..2db7815b0 100644
--- a/src/pages/ChallengeDetail.tsx
+++ b/src/pages/ChallengeDetail.tsx
@@ -219,7 +219,7 @@ const StructuredLayout = ({ adventure, level, rewardsBelowFold, hasSolution }: S
{
title: "Get Started",
content: [
- `Open in GitHub Codespaces. The devcontainerdevelopment container: a portable, reproducible coding environment defined by a configuration file is pre-configured and starts automatically. When you push from Codespaces, GitHub forks the repository to your account automatically.
`,
+ `Open in GitHub Codespaces. The devcontainer is pre-configured and starts automatically. When you push from Codespaces, GitHub forks the repository to your account automatically.
`,
`Prefer working locally? Clone the repo and open it in any editor that supports the Dev Containers specification (VS CodeVisual Studio Code, JetBrains, and others). The devcontainer config will be detected automatically.
`,
].join("\n"),
},
From 9eda1576adcf518fbc20e61d26177e38f2fbc7d7 Mon Sep 17 00:00:00 2001
From: Sinduri Guntupalli
Date: Tue, 21 Jul 2026 11:55:16 +0200
Subject: [PATCH 4/6] fix: address code review findings from PR
fix/sync-local-diagram-fallback
- Restore devcontainer in ChallengeDetail.tsx Get Started step; removing it was an a11y regression
- Use contributor.url ?? contributor.name as cache key in generateSummariesTs to prevent wrong aboutHtml if two contributors share a name but different URLs
- Remove redundant wrapper from TDD learning in building-cloudhaven; the full expansion was already written inline
- Regenerate building-cloudhaven.generated.ts, echoes-lost-in-orbit.generated.ts, summaries.ts, and sitemap.xml to reflect the above
Signed-off-by: Sinduri Guntupalli
---
public/sitemap.xml | 102 +++++++++---------
scripts/generate-adventures.mjs | 7 +-
.../building-cloudhaven.generated.ts | 8 +-
.../building-cloudhaven/adventure.yaml | 2 +-
.../echoes-lost-in-orbit.generated.ts | 22 ++--
src/data/adventures/summaries.ts | 30 +++---
src/pages/ChallengeDetail.tsx | 2 +-
7 files changed, 87 insertions(+), 86 deletions(-)
diff --git a/public/sitemap.xml b/public/sitemap.xml
index 48832a225..f9b4a05d5 100644
--- a/public/sitemap.xml
+++ b/public/sitemap.xml
@@ -9,59 +9,59 @@
https://offon.dev/accessibility/ 2026-06-01 yearly 0.5
https://offon.dev/brand/ 2026-06-05 yearly 0.4
- https://offon.dev/adventures/dead-reckoning/ 2026-07-14 monthly 0.8
- https://offon.dev/adventures/dead-reckoning/levels/beginner/ 2026-07-14 monthly 0.8
- https://offon.dev/adventures/dead-reckoning/levels/intermediate/ 2026-07-14 monthly 0.8
- https://offon.dev/adventures/lex-imperfecta/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/lex-imperfecta/levels/beginner/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/lex-imperfecta/levels/intermediate/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/lex-imperfecta/levels/expert/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/blind-by-design/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/blind-by-design/levels/beginner/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/blind-by-design/levels/intermediate/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/blind-by-design/levels/expert/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/the-ai-observatory/ 2026-06-30 monthly 0.8
- https://offon.dev/adventures/the-ai-observatory/levels/beginner/ 2026-06-30 monthly 0.8
- https://offon.dev/adventures/the-ai-observatory/levels/intermediate/ 2026-06-30 monthly 0.8
- https://offon.dev/adventures/the-ai-observatory/levels/expert/ 2026-06-30 monthly 0.8
- https://offon.dev/adventures/building-cloudhaven/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/building-cloudhaven/levels/beginner/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/building-cloudhaven/levels/intermediate/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/building-cloudhaven/levels/expert/ 2026-07-15 monthly 0.8
- https://offon.dev/adventures/echoes-lost-in-orbit/ 2026-05-31 monthly 0.8
- https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner/ 2026-05-31 monthly 0.8
- https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner/solution/ 2026-05-31 monthly 0.7
- https://offon.dev/adventures/echoes-lost-in-orbit/levels/intermediate/ 2026-05-31 monthly 0.8
- https://offon.dev/adventures/echoes-lost-in-orbit/levels/intermediate/solution/ 2026-05-31 monthly 0.7
- https://offon.dev/adventures/echoes-lost-in-orbit/levels/expert/ 2026-05-31 monthly 0.8
- https://offon.dev/adventures/echoes-lost-in-orbit/levels/expert/solution/ 2026-05-31 monthly 0.7
+ https://offon.dev/adventures/dead-reckoning/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/dead-reckoning/levels/beginner/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/dead-reckoning/levels/intermediate/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/lex-imperfecta/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/lex-imperfecta/levels/beginner/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/lex-imperfecta/levels/intermediate/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/lex-imperfecta/levels/expert/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/blind-by-design/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/blind-by-design/levels/beginner/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/blind-by-design/levels/intermediate/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/blind-by-design/levels/expert/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/the-ai-observatory/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/the-ai-observatory/levels/beginner/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/the-ai-observatory/levels/intermediate/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/the-ai-observatory/levels/expert/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/building-cloudhaven/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/building-cloudhaven/levels/beginner/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/building-cloudhaven/levels/intermediate/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/building-cloudhaven/levels/expert/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/echoes-lost-in-orbit/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner/solution/ 2026-07-21 monthly 0.7
+ https://offon.dev/adventures/echoes-lost-in-orbit/levels/intermediate/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/echoes-lost-in-orbit/levels/intermediate/solution/ 2026-07-21 monthly 0.7
+ https://offon.dev/adventures/echoes-lost-in-orbit/levels/expert/ 2026-07-21 monthly 0.8
+ https://offon.dev/adventures/echoes-lost-in-orbit/levels/expert/solution/ 2026-07-21 monthly 0.7
https://offon.dev/challenges/ 2026-06-03 weekly 0.9
- https://offon.dev/challenges/argo-cd/ 2026-07-14 monthly 0.7
- https://offon.dev/challenges/argo-events/ 2026-07-14 monthly 0.7
- https://offon.dev/challenges/argo-rollouts/ 2026-05-31 monthly 0.7
- https://offon.dev/challenges/argo-workflows/ 2026-07-14 monthly 0.7
- https://offon.dev/challenges/backstage/ 2026-07-14 monthly 0.7
- https://offon.dev/challenges/flagd/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/gitea/ 2026-07-14 monthly 0.7
- https://offon.dev/challenges/github-actions/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/grafana/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/jaeger/ 2026-07-08 monthly 0.7
- https://offon.dev/challenges/java/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/kubernetes/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/kyverno/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/openfeature/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/openllmetry/ 2026-06-30 monthly 0.7
- https://offon.dev/challenges/opentelemetry/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/opentofu/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/policy-reporter/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/prometheus/ 2026-06-30 monthly 0.7
- https://offon.dev/challenges/promql/ 2026-05-31 monthly 0.7
- https://offon.dev/challenges/python/ 2026-06-30 monthly 0.7
- https://offon.dev/challenges/spring-boot/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/tdd/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/terraform/ 2026-07-15 monthly 0.7
- https://offon.dev/challenges/trivy/ 2026-07-15 monthly 0.7
+ https://offon.dev/challenges/argo-cd/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/argo-events/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/argo-rollouts/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/argo-workflows/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/backstage/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/flagd/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/gitea/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/github-actions/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/grafana/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/jaeger/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/java/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/kubernetes/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/kyverno/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/openfeature/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/openllmetry/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/opentelemetry/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/opentofu/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/policy-reporter/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/prometheus/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/promql/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/python/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/spring-boot/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/tdd/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/terraform/ 2026-07-21 monthly 0.7
+ https://offon.dev/challenges/trivy/ 2026-07-21 monthly 0.7
diff --git a/scripts/generate-adventures.mjs b/scripts/generate-adventures.mjs
index 3a6ca7f50..1630cc1f9 100644
--- a/scripts/generate-adventures.mjs
+++ b/scripts/generate-adventures.mjs
@@ -906,10 +906,11 @@ async function generateSummariesTs(adventures) {
lines.push(` story: ${formatString(summaryStory)},`);
lines.push(` tags: [${data.tags.map((t) => `"${escapeDoubleQuoted(t)}"`).join(", ")}],`);
if (data.contributor) {
- if (data.contributor.about && !contributorAboutHtmlCache.has(data.contributor.name)) {
- contributorAboutHtmlCache.set(data.contributor.name, await mdToInline(data.contributor.about));
+ const cacheKey = data.contributor.url ?? data.contributor.name;
+ if (data.contributor.about && !contributorAboutHtmlCache.has(cacheKey)) {
+ contributorAboutHtmlCache.set(cacheKey, await mdToInline(data.contributor.about));
}
- const contributorAboutHtml = contributorAboutHtmlCache.get(data.contributor.name) ?? null;
+ const contributorAboutHtml = contributorAboutHtmlCache.get(cacheKey) ?? null;
lines.push(` contributor: {`);
lines.push(` name: "${escapeDoubleQuoted(data.contributor.name)}",`);
if (data.contributor.url) lines.push(` url: "${escapeDoubleQuoted(data.contributor.url)}",`);
diff --git a/src/data/adventures/building-cloudhaven.generated.ts b/src/data/adventures/building-cloudhaven.generated.ts
index 443b6a1c4..0c93a150f 100644
--- a/src/data/adventures/building-cloudhaven.generated.ts
+++ b/src/data/adventures/building-cloudhaven.generated.ts
@@ -90,7 +90,7 @@ export const BUILDING_CLOUDHAVEN: Adventure = {
topics: ["OpenTofu", "TDD"],
learnings: [
"OpenTofu module testing with tofu test",
- "Test-Driven Development (TDDTest-Driven Development) workflow",
+ "Test-Driven Development (TDD) workflow",
"Input validation with custom rules",
"Refactoring infrastructure safely with moved blocks",
],
@@ -111,7 +111,7 @@ export const BUILDING_CLOUDHAVEN: Adventure = {
"Three districts deployed with correctly configured infrastructure (vaults and ledgers)",
],
toolbox: [
- { name: "tofu", description: "OpenTofu CLICommand Line Interface for infrastructure provisioning", url: "https://opentofu.org/" },
+ { name: "tofu", description: "OpenTofu CLICommand Line Interface for infrastructure provisioning", url: "https://opentofu.org/" },
{ name: "gcp-api-mock", description: "mock GCP API running locally to simulate cloud resources without real cloud costs (Cloud Storage and Cloud SQL only)", url: "https://github.com/KatharinaSick/gcp-api-mock" },
],
howToPlay: [
@@ -174,7 +174,7 @@ make apply
discussionUrl: `${COMMUNITY_URL}/t/adventure-02-building-cloudhaven-expert-the-guardian-protocols/782/8`,
deadline: "2026-02-04T23:59:00+01:00",
intro: [
- "Three broken GitHub Actions workflows stand between CloudHaven and automated infrastructure governance. Fix drift detection that creates PRs, PRPull Request validation with Trivy security scanning and service-container integration tests, and automatic apply on merge.",
+ "Three broken GitHub Actions workflows stand between CloudHaven and automated infrastructure governance. Fix drift detection that creates PRs, PRPull Request validation with Trivy security scanning and service-container integration tests, and automatic apply on merge.",
],
backstory: [
"After the Modular Metropolis refactoring, CloudHaven flourished. But with growth came risk. One night, a rogue change slipped through unnoticed and nearly brought down the North Market's trading vaults. The Council was furious: how could this happen without anyone noticing?",
@@ -189,7 +189,7 @@ make apply
"All three workflows must have succeeded at least once",
],
toolbox: [
- { name: "tofu", description: "OpenTofu CLICommand Line Interface for infrastructure provisioning", url: "https://opentofu.org/" },
+ { name: "tofu", description: "OpenTofu CLICommand Line Interface for infrastructure provisioning", url: "https://opentofu.org/" },
{ name: "gcp-api-mock", description: "mock GCP API running locally (port set to public so GitHub Actions runners can access it)", url: "https://github.com/KatharinaSick/gcp-api-mock" },
{ name: "GitHub Actions", description: "the workflows you will fix are in .github/workflows/", url: "https://docs.github.com/en/actions" },
],
diff --git a/src/data/adventures/building-cloudhaven/adventure.yaml b/src/data/adventures/building-cloudhaven/adventure.yaml
index face9810d..90bded5bf 100644
--- a/src/data/adventures/building-cloudhaven/adventure.yaml
+++ b/src/data/adventures/building-cloudhaven/adventure.yaml
@@ -118,7 +118,7 @@ levels:
- TDD
learnings:
- OpenTofu module testing with tofu test
- - Test-Driven Development (TDD) workflow
+ - Test-Driven Development (TDD) workflow
- Input validation with custom rules
- Refactoring infrastructure safely with moved blocks
devcontainer: 02-building-cloudhaven_02-intermediate
diff --git a/src/data/adventures/echoes-lost-in-orbit.generated.ts b/src/data/adventures/echoes-lost-in-orbit.generated.ts
index c3cf9c65d..632d0f2cf 100644
--- a/src/data/adventures/echoes-lost-in-orbit.generated.ts
+++ b/src/data/adventures/echoes-lost-in-orbit.generated.ts
@@ -12,10 +12,10 @@ export const ECHOES_LOST_IN_ORBIT: Adventure = {
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
backstory: [
- "Welcome aboard the GitOpsGit Operations Starliner, a multi-species engineering vessel orbiting the vibrant planet of Polaris-9. Life in this quadrant is wonderfully diverse, from the whispering cloud-dwellers of Nebulon to the rhythmic click-speakers of Crustacea Prime.",
+ "Welcome aboard the GitOpsGit Operations Starliner, a multi-species engineering vessel orbiting the vibrant planet of Polaris-9. Life in this quadrant is wonderfully diverse, from the whispering cloud-dwellers of Nebulon to the rhythmic click-speakers of Crustacea Prime.",
"Communication between species used to be seamless, thanks to the Echo Server, a universal translator that instantly echoed your words in the listener's native format.",
"But lately, something's off. Messages are getting scrambled. Some transmissions never arrive. The Echo Server, deployed across the Staging Moonbase and the Production Outpost, is no longer syncing properly. The Argo CD dashboard shows no active deployments, and telemetry is suspiciously quiet.",
"You've been assigned to restore interstellar communication before the next critical mission.",
@@ -27,7 +27,7 @@ export const ECHOES_LOST_IN_ORBIT: Adventure = {
difficulty: "Beginner",
topics: ["Argo CD"],
learnings: [
- "Debug GitOpsGit Operations flows with Argo CD",
+ "Debug GitOpsGit Operations flows with Argo CD",
"ApplicationSet templating & pitfalls",
"Environment isolation & namespaces",
"Sync policies: automated, prune & self-heal",
@@ -49,9 +49,9 @@ export const ECHOES_LOST_IN_ORBIT: Adventure = {
"Confirm that updates roll out automatically without leaving stale resources behind",
],
toolbox: [
- { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
+ { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" },
- { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
+ { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
],
howToPlay: [
{ title: "Wait for Infrastructure", content: "Wait around 5 minutes for the Codespace to provision a Kubernetes cluster, Argo CD, and the sample app. Press Cmd+Shift+P (or Ctrl+Shift+P on Windows/Linux) and search for 'View Creation Log' to track progress.
" },
@@ -106,9 +106,9 @@ export const ECHOES_LOST_IN_ORBIT: Adventure = {
"All rollouts complete successfully",
],
toolbox: [
- { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
+ { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" },
- { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
+ { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
{ name: "Argo CD CLI", description: "manage Argo CD applications from the command line", url: "https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd/" },
{ name: "Argo Rollouts kubectl plugin", description: "extended kubectl commands for managing rollouts", url: "https://argo-rollouts.readthedocs.io/en/stable/features/kubectl-plugin/" },
],
@@ -182,13 +182,13 @@ kubectl argo rollouts get rollout echo-server -n echo-prod --watch
],
objective: [
"Automated rollout progression to HotROD version 1.76.0 driven by observability signals",
- "OpenTelemetry Collector configured with an OTLPOpenTelemetry Protocol receiver for HotROD traces, a Spanmetrics connector converting traces to metrics, trace export to Jaeger, and metrics export to Prometheus",
+ "OpenTelemetry Collector configured with an OTLPOpenTelemetry Protocol receiver for HotROD traces, a Spanmetrics connector converting traces to metrics, trace export to Jaeger, and metrics export to Prometheus",
"Canary analysis with three PromQL queries: traffic detection (at least 0.05 req/s to prevent idle canaries), error rate below 5%, and 95th-percentile latency below 1000ms",
],
toolbox: [
- { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
+ { name: "kubectl", description: "Kubernetes CLICommand Line Interface for interacting with the cluster", url: "https://kubernetes.io/docs/reference/kubectl/" },
{ name: "kubens", description: "fast way to switch between Kubernetes namespaces", url: "https://github.com/ahmetb/kubectx" },
- { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
+ { name: "k9s", description: "terminal UIUser Interface for managing and inspecting your cluster", url: "https://k9scli.io/" },
{ name: "Argo CD CLI", description: "manage Argo CD applications from the command line", url: "https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd/" },
{ name: "Argo Rollouts kubectl plugin", description: "extended kubectl commands for managing rollouts", url: "https://argo-rollouts.readthedocs.io/en/stable/features/kubectl-plugin/" },
],
@@ -215,7 +215,7 @@ argocd app get otel --refresh
If you changed HotROD, retry the rollout:
kubectl argo rollouts retry rollout hotrod -n hotrod
-If you changed the OTelOpenTelemetry Collector config, restart it:
+If you changed the OTelOpenTelemetry Collector config, restart it:
kubectl rollout restart daemonset/collector -n otel
` },
{ title: "Watch the Rollout", content: `Watch rollout progress. The rollout should progress automatically based on analysis metrics:
diff --git a/src/data/adventures/summaries.ts b/src/data/adventures/summaries.ts
index 057d91584..a55a361b8 100644
--- a/src/data/adventures/summaries.ts
+++ b/src/data/adventures/summaries.ts
@@ -11,7 +11,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
isLive: true,
icon: "Compass",
@@ -52,7 +52,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Scale",
levels: [
@@ -62,7 +62,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Beginner",
topics: ["Kyverno", "Kubernetes"],
learnings: [
- "How Kyverno ValidatingPolicy resources and CELCommon Expression Language validation expressions work",
+ "How Kyverno ValidatingPolicy resources and CELCommon Expression Language validation expressions work",
"The difference between Audit, Deny, and Warn validation actions",
"How to use custom label keys to enforce workload identity standards",
"How Kyverno MutatingPolicy resources automatically patch incoming workloads at admission",
@@ -104,7 +104,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Simon Schrottner",
url: "https://schrottner.at/",
- aboutHtml: "CNCFCloud Native Computing Foundation Ambassador and maintainer of OpenFeature and JUnit Pioneer. Helps teams release faster and with more confidence through open standards, feature flagging, and the communities that make both possible. A familiar face at KubeCon EU, Devoxx, ContainerDays, and meetups across Europe.",
+ aboutHtml: "CNCFCloud Native Computing Foundation Ambassador and maintainer of OpenFeature and JUnit Pioneer. Helps teams release faster and with more confidence through open standards, feature flagging, and the communities that make both possible. A familiar face at KubeCon EU, Devoxx, ContainerDays, and meetups across Europe.",
},
icon: "FlaskConical",
levels: [
@@ -114,8 +114,8 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Beginner",
topics: ["OpenFeature", "flagd", "Spring Boot"],
learnings: [
- "How an OpenFeature client and provider work together: the SDKSoftware Development Kit is provider-agnostic and the flagd provider plugs in via dependency only",
- "What remote provider means in practice: the SDK calls a separate flag service (flagd) over gRPCGoogle Remote Procedure Call, not parsing flags.json itself",
+ "How an OpenFeature client and provider work together: the SDKSoftware Development Kit is provider-agnostic and the flagd provider plugs in via dependency only",
+ "What remote provider means in practice: the SDK calls a separate flag service (flagd) over gRPCGoogle Remote Procedure Call, not parsing flags.json itself",
"What flags.json looks like for flagd (state, variants, defaultVariant)",
"Why hot-reload of the flag file matters operationally: configuration without redeploy",
],
@@ -138,7 +138,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenFeature", "OpenTelemetry", "Grafana", "Spring Boot"],
learnings: [
"How the OpenFeature OpenTelemetry hooks (TracesHook and MetricsHook) join flag evaluations to the rest of an application's telemetry without a separate ingestion path",
- "How to author your own Hook: a tiny class that copies merged-eval-context attributes onto the active OTelOpenTelemetry span, closing the loop between why a flag resolved the way it did and what the operator sees in Tempo",
+ "How to author your own Hook: a tiny class that copies merged-eval-context attributes onto the active OTelOpenTelemetry span, closing the loop between why a flag resolved the way it did and what the operator sees in Tempo",
"How fractional rollout in flagd buckets users by targetingKey (same key, same bucket, every request) and how to read that bucketing off a dashboard",
"How a flag flip is a faster operational lever than a redeploy when a rollout is misbehaving: the difference between a one-line config change and a twenty-minute deployment",
],
@@ -154,7 +154,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Telescope",
levels: [
@@ -174,7 +174,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Intermediate",
topics: ["OpenTelemetry", "OpenLLMetry", "Jaeger", "Prometheus"],
learnings: [
- "Instrument RAGRetrieval-Augmented Generation pipelines with OpenLLMetry",
+ "Instrument RAGRetrieval-Augmented Generation pipelines with OpenLLMetry",
"Create custom OpenTelemetry metrics in Python",
"Write PromQL queries & recording rules in Prometheus",
],
@@ -186,7 +186,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenTelemetry", "OpenLLMetry", "Jaeger"],
learnings: [
"OpenTelemetry GenAI semantic conventions",
- "Tail sampling in the OTelOpenTelemetry Collector",
+ "Tail sampling in the OTelOpenTelemetry Collector",
],
},
],
@@ -200,7 +200,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Building2",
levels: [
@@ -211,7 +211,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenTofu"],
learnings: [
"Infrastructure as Code with OpenTofu",
- "Remote state management with GCSGoogle Cloud Storage backend",
+ "Remote state management with GCSGoogle Cloud Storage backend",
"Dynamic resource provisioning with for_each",
"Conditional resources with the enabled meta-argument, new in OpenTofu",
],
@@ -223,7 +223,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
topics: ["OpenTofu", "TDD"],
learnings: [
"OpenTofu module testing with tofu test",
- "Test-Driven Development (TDDTest-Driven Development) workflow",
+ "Test-Driven Development (TDD) workflow",
"Input validation with custom rules",
"Refactoring infrastructure safely with moved blocks",
],
@@ -250,7 +250,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
contributor: {
name: "Katharina Sick",
url: "https://ksick.dev/",
- aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
+ aboutHtml: "DevRelDeveloper Relations at Dynatrace and co-organizer of Cloud Native Linz. Passionate about building user-friendly Cloud Native and Kubernetes solutions, with a background in mobile and backend development. Found in tech and sports communities, inline skating rinks, and quiz nights across Europe.",
},
icon: "Satellite",
levels: [
@@ -260,7 +260,7 @@ export const ADVENTURE_SUMMARIES: AdventureCardSummary[] = [
difficulty: "Beginner",
topics: ["Argo CD"],
learnings: [
- "Debug GitOpsGit Operations flows with Argo CD",
+ "Debug GitOpsGit Operations flows with Argo CD",
"ApplicationSet templating & pitfalls",
"Environment isolation & namespaces",
"Sync policies: automated, prune & self-heal",
diff --git a/src/pages/ChallengeDetail.tsx b/src/pages/ChallengeDetail.tsx
index 2db7815b0..ee504aced 100644
--- a/src/pages/ChallengeDetail.tsx
+++ b/src/pages/ChallengeDetail.tsx
@@ -219,7 +219,7 @@ const StructuredLayout = ({ adventure, level, rewardsBelowFold, hasSolution }: S
{
title: "Get Started",
content: [
- `Open in GitHub Codespaces. The devcontainer is pre-configured and starts automatically. When you push from Codespaces, GitHub forks the repository to your account automatically.
`,
+ `Open in GitHub Codespaces. The devcontainerdevelopment container: a portable, reproducible coding environment defined by a configuration file is pre-configured and starts automatically. When you push from Codespaces, GitHub forks the repository to your account automatically.
`,
`Prefer working locally? Clone the repo and open it in any editor that supports the Dev Containers specification (VS CodeVisual Studio Code, JetBrains, and others). The devcontainer config will be detected automatically.
`,
].join("\n"),
},
From e822b0f7d95a8658ce439e4851897f8684602c8b Mon Sep 17 00:00:00 2001
From: Sinduri Guntupalli
Date: Tue, 21 Jul 2026 11:56:07 +0200
Subject: [PATCH 5/6] chore: deduplicate tailwindcss 4.3.2 nested entries in
package-lock.json
npm deduped the stale 4.3.2 copies under @tailwindcss/node and @tailwindcss/vite;
both packages now resolve to the top-level tailwindcss@4.3.3 already declared in package.json.
Signed-off-by: Sinduri Guntupalli
---
package-lock.json | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 59fa34c0e..e621a578b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1507,13 +1507,6 @@
"tailwindcss": "4.3.3"
}
},
- "node_modules/@tailwindcss/node/node_modules/tailwindcss": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz",
- "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@tailwindcss/oxide": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
@@ -1848,13 +1841,6 @@
"vite": "^5.2.0 || ^6 || ^7 || ^8"
}
},
- "node_modules/@tailwindcss/vite/node_modules/tailwindcss": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz",
- "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
From 6de6bd4c8daa56ff5041e1801708979048004cc5 Mon Sep 17 00:00:00 2001
From: Sinduri Guntupalli
Date: Tue, 21 Jul 2026 12:19:08 +0200
Subject: [PATCH 6/6] fix(a11y): suppress CSS abbr tooltip in learnings to fix
200% zoom overflow
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Objective and learnings lists render HTML via dangerouslySetInnerHTML,
bypassing useAbbrTooltips. The CSS ::after tooltip on abbr[data-title]
(position: absolute, width: max-content, opacity: 0) contributes to
scrollWidth at narrow viewports, causing the 200% zoom overflow test to
fail when a learning item contains an abbr positioned far enough right.
Add useAbbrTooltips to the objective/learnings container so the JS portal
replaces the CSS pseudo-element on mount, matching MarkdownContent and Abbr.
Also remove the duplicate devcontainer abbr expansion from the Get Started
walkthrough step — the CTA section already expands it.
Signed-off-by: Sinduri Guntupalli
---
src/pages/ChallengeDetail.tsx | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/pages/ChallengeDetail.tsx b/src/pages/ChallengeDetail.tsx
index ee504aced..3ebc02ef5 100644
--- a/src/pages/ChallengeDetail.tsx
+++ b/src/pages/ChallengeDetail.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, type JSX } from "react";
+import { useState, useEffect, useRef, type JSX } from "react";
import { useParams, useLoaderData, Link } from "react-router";
import type { MetaFunction, LoaderFunctionArgs, LinksFunction } from "react-router";
import { Check, Clock, ExternalLink, ArrowRight } from "lucide-react";
@@ -28,6 +28,7 @@ import { stripHtml } from "@/lib/markdown";
import { isDeadlinePast, isSolutionUnlocked, resolveDiscussionUrl, escapeHtmlAttr } from "@/lib/utils";
import { InlineProse } from "@/components/InlineProse";
import { Abbr } from "@/components/Abbr";
+import { useAbbrTooltips } from "@/hooks/useAbbrTooltips";
export const links: LinksFunction = () => [
{ rel: "preload", href: `${import.meta.env.BASE_URL}fonts/jetbrains-mono-latin-400-normal.woff2`, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
@@ -113,6 +114,8 @@ const StructuredLayout = ({ adventure, level, rewardsBelowFold, hasSolution }: S
const { intro, objective, toolbox, backstory, architecture, architectureDiagram, diagramAlt, architectureAscii, howToPlay, helpfulLinks, verification } = level;
const levelUrl = `${SITE_URL}/adventures/${adventure.id}/levels/${level.id}/`;
const linkedinShareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(levelUrl)}`;
+ const objectiveLearningsRef = useRef(null);
+ useAbbrTooltips(objectiveLearningsRef, [level.learnings, level.objective]);
return (
<>
{/* Header */}
@@ -144,7 +147,7 @@ const StructuredLayout = ({ adventure, level, rewardsBelowFold, hasSolution }: S
{/* Objective + Key Learnings side-by-side */}
-
+
{objective && objective.length > 0 && (
@@ -219,7 +222,7 @@ const StructuredLayout = ({ adventure, level, rewardsBelowFold, hasSolution }: S
{
title: "Get Started",
content: [
- `
Open in GitHub Codespaces. The devcontainerdevelopment container: a portable, reproducible coding environment defined by a configuration file is pre-configured and starts automatically. When you push from Codespaces, GitHub forks the repository to your account automatically.
`,
+ `Open in GitHub Codespaces. The devcontainer is pre-configured and starts automatically. When you push from Codespaces, GitHub forks the repository to your account automatically.
`,
`Prefer working locally? Clone the repo and open it in any editor that supports the Dev Containers specification (VS CodeVisual Studio Code, JetBrains, and others). The devcontainer config will be detected automatically.
`,
].join("\n"),
},