Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ROUTES: RouteSpec[] = [
{ path: "/adventures/dead-reckoning", title: /Dead Reckoning/ },
{ path: "/adventures/dead-reckoning/levels/beginner", title: /Laying the Keel/ },
{ path: "/adventures/dead-reckoning/levels/intermediate", title: /Sea Trial/ },
{ path: "/adventures/dead-reckoning/levels/expert", title: /The Chronometer/ },
{ path: "/adventures/lex-imperfecta", title: /Lex Imperfecta/ },
{ path: "/adventures/lex-imperfecta/levels/beginner", title: /The Twelve Tables/ },
{ path: "/adventures/lex-imperfecta/levels/intermediate", title: /Governing the Provinces/ },
Expand Down
17 changes: 9 additions & 8 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
<url><loc>https://offon.dev/accessibility/</loc><lastmod>2026-06-01</lastmod><changefreq>yearly</changefreq><priority>0.5</priority></url>
<url><loc>https://offon.dev/brand/</loc><lastmod>2026-06-05</lastmod><changefreq>yearly</changefreq><priority>0.4</priority></url>
<!-- GENERATED:adventures -->
<url><loc>https://offon.dev/adventures/dead-reckoning/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/dead-reckoning/levels/beginner/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/dead-reckoning/levels/intermediate/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/dead-reckoning/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/dead-reckoning/levels/beginner/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/dead-reckoning/levels/intermediate/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/dead-reckoning/levels/expert/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/lex-imperfecta/</loc><lastmod>2026-07-15</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/lex-imperfecta/levels/beginner/</loc><lastmod>2026-07-15</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://offon.dev/adventures/lex-imperfecta/levels/intermediate/</loc><lastmod>2026-07-15</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
Expand All @@ -38,13 +39,13 @@
<!-- /GENERATED:adventures -->
<url><loc>https://offon.dev/challenges/</loc><lastmod>2026-06-03</lastmod><changefreq>weekly</changefreq><priority>0.9</priority></url>
<!-- GENERATED:challenge-tags -->
<url><loc>https://offon.dev/challenges/argo-cd/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/argo-events/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/argo-cd/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/argo-events/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/argo-rollouts/</loc><lastmod>2026-05-31</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/argo-workflows/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/backstage/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/argo-workflows/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/backstage/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/flagd/</loc><lastmod>2026-07-15</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/gitea/</loc><lastmod>2026-07-14</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/gitea/</loc><lastmod>2026-07-21</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/github-actions/</loc><lastmod>2026-07-15</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/grafana/</loc><lastmod>2026-07-15</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/jaeger/</loc><lastmod>2026-07-08</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
Expand Down
1 change: 1 addition & 0 deletions react-router.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default {
"/adventures/dead-reckoning",
"/adventures/dead-reckoning/levels/beginner",
"/adventures/dead-reckoning/levels/intermediate",
"/adventures/dead-reckoning/levels/expert",
"/adventures/lex-imperfecta",
"/adventures/lex-imperfecta/levels/beginner",
"/adventures/lex-imperfecta/levels/intermediate",
Expand Down
2 changes: 1 addition & 1 deletion scripts/refresh-leaderboard.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const QUERY_ID = 5;
// Category IDs are from: GET https://community.offon.dev/categories.json
const ADVENTURE_CATEGORIES = {
// GENERATED:adventures
"dead-reckoning": { categoryId: 45, has_beginner: true, has_intermediate: true, has_expert: false, has_single: false },
"dead-reckoning": { categoryId: 45, has_beginner: true, has_intermediate: true, has_expert: true, has_single: false },
"lex-imperfecta": { categoryId: 43, has_beginner: true, has_intermediate: true, has_expert: true, has_single: false },
"blind-by-design": { categoryId: 41, has_beginner: true, has_intermediate: true, has_expert: true, has_single: false },
"the-ai-observatory": { categoryId: 37, has_beginner: true, has_intermediate: true, has_expert: true, has_single: false },
Expand Down
100 changes: 97 additions & 3 deletions src/data/adventures/dead-reckoning.generated.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CODESPACES_BASE, COMMUNITY_URL } from "@/data/constants";
import deadReckoningIntermediate from "@/assets/diagrams/dead-reckoning-intermediate.svg";
import deadReckoningExpert from "@/assets/diagrams/dead-reckoning-expert.svg";
import type { Adventure } from "./types";

export const DEAD_RECKONING: Adventure = {
Expand Down Expand Up @@ -28,9 +29,6 @@ export const DEAD_RECKONING: Adventure = {
rankingNote: "Ranking is determined by total points across all three levels. Points per level are awarded by submission order within the active week (100 for the first valid solution, 95 for the second, and so on; late submissions still earn 60).",
rankingRulesUrl: `${COMMUNITY_URL}/t/about-the-challenges-category/16`,
},
upcomingLevels: [
{ name: "Expert", difficulty: "Expert" },
],
levels: [
{
id: "beginner",
Expand Down Expand Up @@ -205,5 +203,101 @@ commissioned vessel stage by stage and reports the first place the pipeline runs
},
metaDescription: "Sea Trial: Fix the broken integration points in the delivery pipeline so that commissioning a vessel in Backstage results in a running deployment.",
},
{
id: "expert",
name: "The Chronometer",
difficulty: "Expert",
topics: ["Backstage", "Argo Workflows", "Argo CD", "OpenTelemetry", "Jaeger"],
audience: "Platform and DevOps engineers who already know how the delivery pipeline fits together and want to see how a distributed trace ties it into one story. You should be comfortable with Kubernetes, YAML, and reading a tool's logs and UI. Prior exposure to OpenTelemetry, trace context propagation, and a trace viewer like Jaeger helps, but the focus here is using a trace to reason across service boundaries, not any one tool.",
learnings: [
"How trace context crosses an asynchronous boundary with no call chain: a commission carries its <a href=\"https://www.w3.org/TR/trace-context/\" target=\"_blank\" rel=\"noopener noreferrer\" aria-describedby=\"new-tab-hint\">W3C traceparent</a> to a push-triggered pipeline, so its spans <a href=\"https://opentelemetry.io/docs/concepts/context-propagation/\" target=\"_blank\" rel=\"noopener noreferrer\" aria-describedby=\"new-tab-hint\">continue the same trace</a> instead of starting a new one",
"Why a <a href=\"https://opentelemetry.io/docs/concepts/signals/traces/\" target=\"_blank\" rel=\"noopener noreferrer\" aria-describedby=\"new-tab-hint\">distributed trace</a> reveals what per-tool logs cannot: the value that flowed through each step, so a fault surfaces where the data diverges",
"How to read that trace in <a href=\"https://www.jaegertracing.io/docs/latest/\" target=\"_blank\" rel=\"noopener noreferrer\" aria-describedby=\"new-tab-hint\">Jaeger</a> to localise a fault to a single service by following one attribute across the whole voyage",
],
codespacesUrl: `${CODESPACES_BASE}?devcontainer_path=.devcontainer%2Fdead-reckoning_expert%2Fdevcontainer.json&quickstart=1&machine=standardLinux32gb`,
discussionUrl: "",
deadline: "2026-07-28T23:59:00+01:00",
intro: [
"Repair the fleet's broken navigation log, then use the complete trace it produces to find why vessels arrive carrying the wrong cargo.",
],
backstory: [
"Every commission the office issues is supposed to leave one clean line in the navigation log: the moment a captain fills in the papers, the yards that build and deliver the vessel, and the harbor master bringing it into formation, all recorded as a single voyage you can read end to end. Read the log and you know exactly what happened to any vessel, and when.",
"Lately the log lies. It records the commission office plainly enough, then goes dark the instant a vessel leaves for the yards: the shipyard's and harbor master's work never appears on the same line. And at the far end of the voyage, captains keep signing for the wrong cargo, salt-pork where citrus was ordered, though every yard along the route reported a clean, successful run. No single tool shows anything wrong.",
"Your mission: repair the log so a commission reads as one unbroken voyage again, from the office all the way to open water, then use that complete picture to find where the cargo goes astray, and set it right.",
],
objective: [
"Every commission appears in the navigation log (Jaeger) as a single, connected trace, unbroken from the commission office (Backstage) through the shipyard (Argo Workflows) and the harbor master (Argo CD)",
"Every vessel sails carrying the provisions it was commissioned to carry",
],
architecture: [
"<p>This level adds a tracing overlay to the Sea Trial delivery pipeline: every stage now reports spans to an <strong>OpenTelemetry Collector</strong>, which forwards them to <strong>Jaeger</strong>. The commission office opens the trace and hands its context to the pipeline, so one commission should read as a single connected voyage: <code>commission &#x3C;vessel></code> -> <code>enter parameters</code> and <code>scaffold repos</code> (Backstage) -> <code>ci pipeline</code> (Argo Workflows) -> <code>rollout</code> (Argo CD).</p>",
"<p>The cargo rides that trace at three checkpoints, so you see not just <em>that</em> it is wrong but <em>where</em> it changed: <code>provisions.ordered</code> (root span, what was selected), <code>provisions.declared</code> (rollout span, what the deployment sets), and <code>provisions.reported</code> (rollout span, what the running vessel says it carries). In a healthy voyage all three agree.</p>",
"<p>Your repairs live in the vessel commissioning template under <code>backstage/templates/vessel-commissioning-template/</code> (<code>template.yaml</code> and the files under <code>content/</code>). Everything else, the <code>platform/</code> manifests and the Backstage tracing setup, is working: read it to see how the trace is carried out to sea, but you won't need to change it.</p>",
"<p>This level runs a real container build, so your Codespace uses a larger (<strong>4-core / 16 GB</strong>) machine. Give the pipeline a few minutes after commissioning; the delivery spans land in Jaeger once the rollout completes.</p>",
`<blockquote>
<p>A quick translation from the story to the tools: a <strong>vessel</strong> is a small service you deploy; its <strong>cargo</strong> (<strong>provisions</strong>) is a value it is commissioned to carry, set in its deployment and reported by the running service. The <strong>commission office</strong> is Backstage, the <strong>shipyard</strong> is Argo Workflows, the <strong>harbor master</strong> is Argo CD, and the <strong>navigation log</strong> is Jaeger. A commission's <strong>voyage</strong> is the single distributed trace that should span all of them.</p>
</blockquote>`,
],
architectureDiagram: deadReckoningExpert,
diagramAlt: "The delivery pipeline runs left to right across the top: Backstage creates the repositories, Argo Events triggers the CI build, Argo Workflows builds the image and updates the tag, Argo CD syncs the deployment, and the vessel's app is up and running. A W3C traceparent thread runs through the pipeline. Backstage, Argo Workflows, and Argo CD each report spans down to the OpenTelemetry Collector, which feeds Jaeger, where a single commission appears as one connected trace: a Commission Vessel root span over Backstage, Argo Workflows, and Argo CD spans.",
toolbox: [
{ name: "Jaeger", description: "The navigation log (port 30103). Shows each commission's trace; search by service <code>backstage</code> and operation <code>commission &#x3C;vessel></code> to open a voyage, then read its spans and their attributes.", url: "https://www.jaegertracing.io/docs/latest/" },
{ name: "Backstage", description: "The commission office and your cockpit (port 3000). Commission a vessel from Create; its page carries a Voyage log card that links to the trace.", url: "https://backstage.io/docs/features/software-templates/" },
{ name: "Argo Workflows", description: "The shipyard (port 30113). Runs the multi-step build-and-deliver workflow and emits the <code>ci pipeline</code> spans.", url: "https://argo-workflows.readthedocs.io/en/latest/" },
{ name: "Argo CD", description: "The harbor master (port 30100). Reconciles each vessel into the cluster and emits the <code>rollout</code> span.", url: "https://argo-cd.readthedocs.io/en/stable/" },
{ name: "OpenTelemetry Collector", description: "The signal station every component reports spans to; it forwards them on to Jaeger. Part of the working tracing setup, here to read, not to change.", url: "https://opentelemetry.io/docs/collector/" },
],
howToPlay: [
{ title: "Open the Commission Office", content: `<p>Start Backstage with <code>make backstage</code>. The first run compiles for ~30-60s; once it's up, the commission
office is available in your browser on port 3000. Leave it running in that terminal; restart any time with
Ctrl-C then <code>make backstage</code>.</p>
<p>The rest of the platform (Gitea, Argo Events, Argo Workflows, Argo CD, the OpenTelemetry Collector, and Jaeger)
is already running in the cluster.</p>` },
{ title: "Explore the UIs", content: `<p>Open the <strong>Ports</strong> tab and navigate to each service:</p>
<ul>
<li><strong>Port 3000:</strong> Backstage. The commission office and your cockpit. Start it with <code>make backstage</code>, then commission vessels. Signs in as a guest, no credentials needed.</li>
<li><strong>Port 30103:</strong> Jaeger. The navigation log: each commission's distributed trace, across Backstage, Argo Workflows, and Argo CD. No sign-in required.</li>
<li><strong>Port 30112:</strong> Gitea (admin / a-super-secure-password). The archives: each vessel's code and deployment repositories and the container registry.</li>
<li><strong>Port 30113:</strong> Argo Workflows. The shipyard: the delivery workflow runs, step by step, with per-step logs. No sign-in required.</li>
<li><strong>Port 30100:</strong> Argo CD (readonly / a-super-secure-password). The harbor master: each vessel's Application, sync status, and health.</li>
</ul>` },
{ title: "Sail a Commission and Read the Log", content: `<p>In Backstage, go to <strong>Create</strong> and run the <strong>Commission a Vessel</strong> template. Pick a cargo you'll remember (say
<strong>Citrus</strong>), and let the vessel sail. Delivery still works, but the records don't add up. You have two
instruments to check what really happened:</p>
<ul>
<li><strong>Greet the vessel.</strong> Once it's running, <code>make ahoi</code> reports the cargo it's actually carrying.
Is it what you ordered?</li>
<li><strong>Open the log.</strong> In <strong>Jaeger</strong> (port 30103), search service <code>backstage</code>, operation <code>commission &#x3C;vessel></code>,
and open the trace. Read it from the commission office outward: how much of the voyage actually reached the
log?</li>
</ul>
<p>The trace is your instrument for both. It carries the cargo along the voyage (<code>provisions.ordered</code> ->
<code>provisions.declared</code> -> <code>provisions.reported</code>), so once the log reads true end to end, Jaeger is where you
follow the cargo from order to arrival and find where it goes astray.</p>` },
{ title: "Repair the Log, Then the Cargo", content: `<p>Both repairs live in the vessel commissioning template:</p>
<pre tabindex="0" aria-label="Code block"><code class="language-text">backstage/templates/vessel-commissioning-template/
</code></pre>
<p>Start with the log. Read how a commission carries its trace context out to the pipeline, and how the pipeline
picks it back up off the push it reacts to; the two need to line up on the same commit. With the voyage whole,
the trace shows the cargo at each checkpoint (<code>provisions.ordered</code> -> <code>provisions.declared</code> ->
<code>provisions.reported</code>): follow it and let it tell you which stage the cargo survives and which one it doesn't.</p>
<p>Test a fix by commissioning a fresh vessel and watching it sail. If you edited <code>template.yaml</code>, reload it into
Backstage first so the next commission picks up your change:</p>
<pre tabindex="0" aria-label="Code block"><code class="language-bash">make reload-template
</code></pre>` },
],
helpfulLinks: [
{ title: "W3C Trace Context", url: "https://www.w3.org/TR/trace-context/", description: "The traceparent format that carries a trace across service boundaries" },
{ title: "OpenTelemetry: Context Propagation", url: "https://opentelemetry.io/docs/concepts/context-propagation/", description: "How spans in different processes are stitched into one trace by passing context between them" },
{ title: "OpenTelemetry: Traces", url: "https://opentelemetry.io/docs/concepts/signals/traces/", description: "What a trace and its spans are, and why they capture causal, data-carrying detail logs don't" },
{ title: "Jaeger: Documentation", url: "https://www.jaegertracing.io/docs/latest/", description: "Searching for and reading distributed traces across services" },
{ title: "Backstage: Software Templates", url: "https://backstage.io/docs/features/software-templates/", description: "How a scaffolder template runs its steps and passes values between them" },
],
verification: {
command: "make verify",
description: "Grades the most recently commissioned vessel against the two end-states: its commission is one connected trace in Jaeger, and the running vessel reports the cargo its deployment declares. Each check reports on its own, so you see which half of the log still reads false.",
},
metaDescription: "The Chronometer: Repair the fleet's broken navigation log, then use the complete trace it produces to find why vessels arrive carrying the wrong cargo.",
},
],
};
Loading
Loading