From adc06c66aa014667763facf544495c92e7f3ded2 Mon Sep 17 00:00:00 2001 From: Katharina Sick Date: Fri, 29 May 2026 11:00:17 +0200 Subject: [PATCH 1/3] move GH pages docs to offon.dev Signed-off-by: Katharina Sick --- .devcontainer/post-create.sh | 5 +- .github/ISSUE_TEMPLATE/bug.yml | 2 +- .github/ISSUE_TEMPLATE/improvement.yml | 2 +- .github/workflows/deploy-docs.yaml | 30 -- .github/workflows/idea-tracking-issue.yml | 2 +- .github/workflows/walkthrough-issues.yml | 2 +- CONTRIBUTING.md | 365 +++++++++++++++++- README.md | 2 +- adventures/01-echoes-lost-in-orbit/README.md | 2 +- .../beginner/smoke-test.sh | 2 +- .../01-echoes-lost-in-orbit/docs/index.md | 6 +- .../expert/smoke-test.sh | 2 +- .../intermediate/smoke-test.sh | 2 +- .../01-echoes-lost-in-orbit/mkdocs.yaml | 11 - adventures/02-building-cloudhaven/README.md | 2 +- .../beginner/smoke-test.sh | 2 +- .../02-building-cloudhaven/docs/beginner.md | 2 +- .../02-building-cloudhaven/docs/index.md | 6 +- .../expert/smoke-test.sh | 2 +- .../intermediate/smoke-test.sh | 2 +- adventures/02-building-cloudhaven/mkdocs.yaml | 7 - adventures/03-the-ai-observatory/README.md | 3 +- .../03-the-ai-observatory/beginner/verify.sh | 2 +- .../03-the-ai-observatory/docs/index.md | 6 +- .../03-the-ai-observatory/expert/verify.sh | 2 +- .../intermediate/verify.sh | 2 +- adventures/03-the-ai-observatory/mkdocs.yaml | 7 - adventures/04-blind-by-design/README.md | 2 +- .../04-blind-by-design/beginner/verify.sh | 2 +- adventures/04-blind-by-design/docs/expert.md | 217 ----------- .../04-blind-by-design/docs/expert.yaml | 154 ++++++++ adventures/04-blind-by-design/docs/index.md | 60 --- adventures/04-blind-by-design/docs/index.yaml | 28 ++ .../04-blind-by-design/expert/verify.sh | 2 +- .../04-blind-by-design/intermediate/verify.sh | 2 +- adventures/04-blind-by-design/mkdocs.yaml | 7 - .../planned/00-lex-imperfecta/README.md | 3 +- .../00-lex-imperfecta/beginner/verify.sh | 2 +- .../planned/00-lex-imperfecta/docs/index.md | 2 +- .../planned/00-lex-imperfecta/mkdocs.yaml | 5 - .../Blind By Design/Launch Annoucement.png | Bin 1587708 -> 0 bytes .../Blind By Design/Learning Value.png | Bin 1733540 -> 0 bytes .../Blind By Design/Reminder Graphic.png | Bin 1841645 -> 0 bytes .../Blind By Design/Share Graphic.png | Bin 1811410 -> 0 bytes docs/contributing/adventure-ideas.md | 110 ------ docs/contributing/adventures.md | 162 -------- docs/contributing/index.md | 90 ----- docs/contributing/solution-walkthroughs.md | 14 - docs/images/codespace-options.png | Bin 42462 -> 0 bytes docs/images/new-codespace.png | Bin 39791 -> 0 bytes docs/index.md | 109 ------ docs/start-a-challenge.md | 41 -- docs/verification.md | 165 -------- lib/scripts/output.sh | 2 +- mkdocs.yaml | 52 --- scripts/new-adventure.sh | 20 +- 56 files changed, 575 insertions(+), 1156 deletions(-) delete mode 100644 .github/workflows/deploy-docs.yaml delete mode 100644 adventures/01-echoes-lost-in-orbit/mkdocs.yaml delete mode 100644 adventures/02-building-cloudhaven/mkdocs.yaml delete mode 100644 adventures/03-the-ai-observatory/mkdocs.yaml delete mode 100644 adventures/04-blind-by-design/docs/expert.md create mode 100644 adventures/04-blind-by-design/docs/expert.yaml delete mode 100644 adventures/04-blind-by-design/docs/index.md create mode 100644 adventures/04-blind-by-design/docs/index.yaml delete mode 100644 adventures/04-blind-by-design/mkdocs.yaml delete mode 100644 adventures/planned/00-lex-imperfecta/mkdocs.yaml delete mode 100644 docs/Challenge Assets/Blind By Design/Launch Annoucement.png delete mode 100644 docs/Challenge Assets/Blind By Design/Learning Value.png delete mode 100644 docs/Challenge Assets/Blind By Design/Reminder Graphic.png delete mode 100644 docs/Challenge Assets/Blind By Design/Share Graphic.png delete mode 100644 docs/contributing/adventure-ideas.md delete mode 100755 docs/contributing/adventures.md delete mode 100644 docs/contributing/index.md delete mode 100644 docs/contributing/solution-walkthroughs.md delete mode 100644 docs/images/codespace-options.png delete mode 100644 docs/images/new-codespace.png delete mode 100644 docs/index.md delete mode 100644 docs/start-a-challenge.md delete mode 100644 docs/verification.md delete mode 100644 mkdocs.yaml diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 990b2d23..018ec597 100644 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -3,8 +3,5 @@ set -euo pipefail lib/shared/init.sh --version v0.17.0 # https://github.com/charmbracelet/gum/releases -echo "โ†’ Installing mkdocs-material..." -pip install --quiet mkdocs-material mkdocs-monorepo-plugin - -echo "โœ“ Done! Run 'mkdocs serve' to start the docs server." +echo "โœ“ Done!" diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 2250fdee..a25b2f56 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -33,6 +33,6 @@ body: attributes: label: Where? description: Link to the relevant page, file, or adventure level. - placeholder: "e.g. https://dynatrace-oss.github.io/open-ecosystem-challenges/01-echoes-lost-in-orbit/beginner" + placeholder: "e.g. https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/improvement.yml b/.github/ISSUE_TEMPLATE/improvement.yml index a0188b90..6ea72f18 100644 --- a/.github/ISSUE_TEMPLATE/improvement.yml +++ b/.github/ISSUE_TEMPLATE/improvement.yml @@ -15,7 +15,7 @@ body: attributes: label: Where? description: Link to the relevant page, file, or adventure level. - placeholder: "e.g. https://dynatrace-oss.github.io/open-ecosystem-challenges/01-echoes-lost-in-orbit/beginner" + placeholder: "e.g. https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner" validations: required: true - type: textarea diff --git a/.github/workflows/deploy-docs.yaml b/.github/workflows/deploy-docs.yaml deleted file mode 100644 index 2c81e638..00000000 --- a/.github/workflows/deploy-docs.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# See https://squidfunk.github.io/mkdocs-material/publishing-your-site/#with-github-actions -name: Deploy Documentation -on: - push: - branches: - - main -permissions: - contents: write -jobs: - deploy: - # Pages deploy is not necessary in forks - if: ${{ github.repository == 'dynatrace-oss/open-ecosystem-challenges' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Configure Git Credentials - run: | - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: 3.x - - name: Install Plugins - run: | - pip install mkdocs-material - pip install mkdocs-monorepo-plugin - - name: Deploy Documentation - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/.github/workflows/idea-tracking-issue.yml b/.github/workflows/idea-tracking-issue.yml index f3c604cc..7c7083f1 100644 --- a/.github/workflows/idea-tracking-issue.yml +++ b/.github/workflows/idea-tracking-issue.yml @@ -55,7 +55,7 @@ jobs: '', '## Want to build it?', '', - 'Comment on this issue to claim it, then follow the [Building Adventures](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/adventures/) guide.', + 'Comment on this issue to claim it, then follow the [Building Adventures](https://github.com/dynatrace-oss/open-ecosystem-challenges/blob/main/CONTRIBUTING.md#build-a-new-adventure) guide.', '', '**Each level should be a separate PR.** Reference this issue with `Part of #` in earlier PRs and `Closes #` in the final one.', ].join('\n'), diff --git a/.github/workflows/walkthrough-issues.yml b/.github/workflows/walkthrough-issues.yml index d18e5887..e904b110 100644 --- a/.github/workflows/walkthrough-issues.yml +++ b/.github/workflows/walkthrough-issues.yml @@ -66,7 +66,7 @@ jobs: '', '## How to contribute', '', - 'Comment on this issue to claim it, then follow the [Solution Walkthroughs](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/solution-walkthroughs/) guide.', + 'Comment on this issue to claim it, then follow the [Solution Walkthroughs](https://github.com/dynatrace-oss/open-ecosystem-challenges/blob/main/CONTRIBUTING.md#write-a-solution-walkthrough) guide.', '', 'A walkthrough can be:', "- **External content** โ€” a blog post, video, or any public resource. Link to it from the adventure's solutions page.", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1377ff29..6f5fdb83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,22 +1,361 @@ # Contributing Guide -Thank you for considering contributing to the Open Ecosystem Challenges! ๐ŸŽ‰ +Thank you for your interest in contributing to Open Ecosystem Challenges! -Whether you're here to fix a typo, suggest an adventure idea, or build an entire challenge, your contribution matters. - -๐Ÿ“– **[Read the full Contributing Guide](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/)** for -everything you need to get started. +Whether you're fixing a typo, proposing an adventure idea, or building an entire challenge, your contribution matters. ## Ways to Contribute -| Type | Description | -|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------| -| โœจ [Improvements & Bug Fixes](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/) | Improve docs, enhance challenge setup, fix bugs | -| ๐Ÿ’ก [Adventure Ideas](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/adventure-ideas/) | Propose a new adventure with a full implementation plan | -| ๐Ÿ—๏ธ [New Adventures](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/adventures/) | Build and implement a full adventure based on an approved idea | -| ๐Ÿ“– [Solution Walkthroughs](https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/solution-walkthroughs/) | Write a step-by-step guide for a completed challenge | +| Type | Description | Guide | +|-------------------------------|----------------------------------------------------------------|-----------------------------------------------------| +| โœจ Improvements & Bug Fixes | Improve docs, enhance challenge setup, fix bugs | This page | +| ๐Ÿ’ก Adventure Ideas | Propose a new adventure with a full implementation plan | [Adventure Ideas](#propose-an-adventure-idea) | +| ๐Ÿ—๏ธ New Adventures | Build and implement a full adventure based on an approved idea | [Building Adventures](#build-a-new-adventure) | +| ๐Ÿ“– Solution Walkthroughs | Write a step-by-step guide for a completed challenge | [Solution Walkthroughs](#write-a-solution-walkthrough) | + +## Code of Conduct + +This project follows the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). Be respectful and constructive. + +## Before You Start + +1. **Check existing issues.** Search [open issues](https://github.com/dynatrace-oss/open-ecosystem-challenges/issues) before creating a new one. +2. **Determine if you need an issue:** + - **Small fixes** (typos, broken links): No issue needed, just open a PR. + - **Larger improvements & bug fixes**: Open an issue first to discuss. + - **Adventure ideas**: No issue needed, submit your idea directly as a PR. + - **New adventures & solution walkthroughs**: Pick up an existing issue. +3. **Claim the issue.** If working on an existing issue, comment to let others know you're on it. + +## Pull Request Process + +1. **Fork the repository** and create your branch from `main`. +2. **Make your changes** with clear, focused commits. +3. **Test your changes.** Run verification scripts if applicable. +4. **Open a pull request** with a clear description of what you changed and why. +5. **Address feedback.** Maintainers will review and may request changes. + +Keep PRs focused. Smaller, single-purpose PRs are easier to review and merge. + +## Developer Certificate of Origin (DCO) + +This project uses the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). All commits must be signed off to certify that you have the right to submit the code and agree to the DCO terms. + +Sign off your commits by adding `-s` to your commit command: + +```bash +git commit -s -m "Your commit message" +``` + +If you've already made commits without signing off, you can amend them: + +```bash +git commit --amend -s --no-edit +git push --force-with-lease +``` + +The DCO is enforced automatically via [cncf/dco2](https://github.com/cncf/dco2). PRs without signed-off commits will be flagged. ## Getting Help -- ๐Ÿ’ญ [Community forum](https://community.open-ecosystem.com/c/challenges/builders/40) -- ๐Ÿ› [Open an issue](https://github.com/dynatrace-oss/open-ecosystem-challenges/issues) +- **Ideas & bugs?** [Open an issue](https://github.com/dynatrace-oss/open-ecosystem-challenges/issues) +- **Questions & discussions?** [Open Ecosystem Community](https://community.open-ecosystem.com/c/challenges) + +## License + +This project is licensed under the [MIT License](https://github.com/dynatrace-oss/open-ecosystem-challenges/blob/main/LICENSE). + +By contributing, you agree that your contributions will be licensed under the same MIT License. + +--- + +## Propose an Adventure Idea + +Have a concept for a new challenge? We'd love to hear it! + +Adventure ideas are proposals for new challenges. You don't need to implement anything yet. Just describe what the +adventure could look like and what learners would gain from it. + +### Before You Start + +- **Check existing ideas.** Browse [adventure idea issues](https://github.com/dynatrace-oss/open-ecosystem-challenges/issues?q=is%3Aissue+is%3Aopen+label%3A%22adventure+idea%22) + and [open PRs](https://github.com/dynatrace-oss/open-ecosystem-challenges/pulls) to make sure your idea hasn't already been submitted or is in the pipeline. +- **Focus on actions, not tools.** Frame challenges around what learners will *do* (e.g., "release safely", "observe AI + systems") rather than tools they'll use (e.g., "Argo Rollouts", "OpenTelemetry"). +- **Consider multiple levels.** Three levels (Beginner, Intermediate, Expert) are recommended but not required. Even a + single well-designed level is valuable. + +### How to Submit + +1. **Fork** the repository on GitHub +2. **Copy** `ideas/adventure-idea-template.md` and rename it to `ideas/your-adventure-name.md`: + ``` + cp ideas/adventure-idea-template.md ideas/your-adventure-name.md + ``` +3. **Fill in the template** and commit your changes +4. **[Open a pull request](https://github.com/dynatrace-oss/open-ecosystem-challenges/compare)** with the title `Adventure Idea: [emoji] Your Adventure Name` + +No issue required. Submit your idea directly as a PR. + +### From Idea to Adventure + +After you open a PR: + +1. **Review.** Maintainers review your idea for fit and feasibility. +2. **Feedback.** We may suggest adjustments or ask clarifying questions. +3. **Approval.** Once approved and merged, your idea becomes available for implementation via `make new-adventure`. +4. **Implementation.** You or another contributor picks it up, builds it, and the idea moves to `ideas/.implemented/`. + +You're welcome to implement your own idea after approval, but there's no obligation to do so. + +### Idea Folder Structure + +Ideas are organized by their status: + +``` +ideas/ +โ”œโ”€โ”€ [your-idea].md # Proposals & approved ideas (submitted via PR) +โ””โ”€โ”€ .implemented/ # Completed adventures (reference only) +``` + +### What Makes a Good Adventure Idea? + +Strong adventure ideas share these qualities: + +| Quality | Description | +|---------------------|----------------------------------------------------------------------| +| **Action-oriented** | Focuses on what learners will *do*, not just what tools they'll use | +| **Story-driven** | Has an engaging narrative that motivates the challenges | +| **Progressive** | Multiple levels that build on each other (recommended, not required) | +| **Practical** | Teaches skills applicable to real-world scenarios | +| **Self-contained** | Can run entirely in a [devcontainer](https://containers.dev/) | + +### Calibrating Difficulty + +The ๐ŸŸข Beginner / ๐ŸŸก Intermediate / ๐Ÿ”ด Expert labels set participant expectations before they start. Getting this right matters โ€” a mislabeled level is frustrating regardless of which direction it's off. + +Three levels are recommended but not required. A single well-scoped level or a two-level adventure is perfectly valid. + +#### What each level feels like + +**๐ŸŸข Beginner** โ€” Get to know the tool. Participants encounter it for the first time and learn the fundamentals: what it does, how it's configured, and what "working" looks like. The challenge is contained and approachable. + +**๐ŸŸก Intermediate** โ€” Move into systems thinking. Participants have seen the tool before; now they see how it fits into a broader, more realistic setup. The interesting part is the integration โ€” how things connect, interact, and break in non-obvious ways. + +**๐Ÿ”ด Expert** โ€” Something genuinely interesting. Not just "harder" โ€” a qualitatively different challenge that rewards deep understanding. Adventure 01 is the best example: Expert isn't just more configuration, it's a completely different observability layer that ties everything together. + +#### A quick self-check + +Ask: *Could someone who has read the docs but never used this tool in a real project solve this in under an hour?* + +- **Yes** โ†’ Beginner +- **No โ€” they'd need to understand how two systems interact** โ†’ Intermediate +- **No โ€” they'd need to understand the full architecture** โ†’ Expert + +#### One level, a few new concepts + +A common mistake is packing too much into a single level. Each level should introduce 2โ€“3 new ideas โ€” not a tour of everything the technology can do. + +Adventure 01 is a useful reference: Beginner introduces Argo CD ApplicationSets โ†’ Intermediate adds Argo Rollouts and PromQL โ†’ Expert adds OpenTelemetry Collector and distributed tracing. + +### Adventure Idea Template + +A ready-to-use template is available at [`ideas/adventure-idea-template.md`](https://github.com/dynatrace-oss/open-ecosystem-challenges/blob/main/ideas/adventure-idea-template.md). + +Copy it using the command below, fill in the placeholders, and follow the [How to Submit](#how-to-submit) steps above. + +``` +cp ideas/adventure-idea-template.md ideas/your-adventure-name.md +``` + +See [Echoes Lost in Orbit](https://github.com/dynatrace-oss/open-ecosystem-challenges/blob/main/ideas/.implemented/echoes-lost-in-orbit.md) for a complete example of a well-written idea. + +### Writing Good Objectives + +Objectives are verifiable outcomes, not tasks. Write them as the state a participant should reach, not the steps they should follow โ€” specific enough that the verification script can check them directly. + +| Task (avoid) | Outcome (aim for) | +|---|---| +| Fix the ApplicationSet | See two distinct Applications in the Argo CD dashboard | +| Add instrumentation | Send traces to the OpenTelemetry Collector at `http://localhost:30107` | + +--- + +## Build a New Adventure + +Ready to turn an approved idea into a full adventure? This guide walks you through the implementation process. + +### Before You Start + +- **Pick an approved idea.** Browse [open implementation issues](https://github.com/dynatrace-oss/open-ecosystem-challenges/issues?q=is%3Aissue+is%3Aopen+label%3A%22adventure+idea%22) to find unclaimed ideas. Once you pick one, comment on its issue to claim it. +- **Read the idea thoroughly.** Understand the story, objectives, and learning outcomes. +- **Have your own idea?** [Propose it](#propose-an-adventure-idea) โ€” ideas go through review before they're available for implementation. +- **Ready to build?** Once an idea is approved and merged into `ideas/`, it's available via `make new-adventure`. + +### What You'll Build + +An adventure consists of: + +| Component | Purpose | +|-----------|---------| +| **Challenge files** | The "broken" state participants will fix | +| **Documentation** | Story, objectives, hints, and solution walkthroughs | +| **Devcontainer** | Pre-configured environment with all required infrastructure | +| **Verification script** | Validates solutions and generates completion certificate | + +### Adventure Structure + +Use `00` as the adventure number during development. When your adventure is scheduled for release, maintainers will assign the final number and move it out of `planned/`. + +``` +adventures/planned/00-adventure-name/ +โ”œโ”€โ”€ README.md # Brief intro + link to docs +โ”œโ”€โ”€ docs/ +โ”‚ โ”œโ”€โ”€ index.md # Adventure introduction +โ”‚ โ”œโ”€โ”€ beginner.md # Level guide +โ”‚ โ”œโ”€โ”€ intermediate.md +โ”‚ โ”œโ”€โ”€ expert.md +โ”‚ โ””โ”€โ”€ solutions/ +โ”‚ โ”œโ”€โ”€ beginner.md # Solution walkthrough +โ”‚ โ”œโ”€โ”€ intermediate.md +โ”‚ โ””โ”€โ”€ expert.md +โ”œโ”€โ”€ beginner/ +โ”‚ โ”œโ”€โ”€ verify.sh # Verification script +โ”‚ โ””โ”€โ”€ [challenge files] +โ”œโ”€โ”€ intermediate/ +โ”‚ โ””โ”€โ”€ ... +โ””โ”€โ”€ expert/ + โ””โ”€โ”€ ... + +.devcontainer/00-adventure-name_01-beginner/ +โ”œโ”€โ”€ devcontainer.json +โ”œโ”€โ”€ post-create.sh # Runs once (install tools) +โ””โ”€โ”€ post-start.sh # Runs every start (start services) +``` + +### Step-by-Step + +#### 1. Scaffold the Files + +Run the scaffolding script to generate the skeleton for your adventure level: + +```bash +make new-adventure +``` + +This will prompt you to select an adventure and level, then generate: + +- `adventures/planned/00-adventure-name/` โ€” adventure base with `README.md` and `docs/index.md` +- `adventures/planned/00-adventure-name/docs/.md` โ€” level guide +- `adventures/planned/00-adventure-name//verify.sh` โ€” verification script skeleton +- `.devcontainer/00-adventure-name_NN-level/` โ€” `devcontainer.json`, `post-create.sh`, `post-start.sh` + +Search for `TODO` in the generated files to find everything that needs filling in. + +#### 2. Configure the Devcontainer + +Open the generated `.devcontainer/00-adventure-name_NN-level/` files and fill in the TODOs. + +For Kubernetes-based adventures, [Adventure 01](adventures/01-echoes-lost-in-orbit/) is a good reference for what features and setup scripts to use. + +**post-create.sh** runs once when the container is created: +- Install CLI tools using setup scripts from `lib/` โ€” every script accepts a `--version` flag to pin a specific version. Run any script with `--help` to see available flags and defaults. +- Pull container images +- Set up one-time configurations + +Example calls in `post-create.sh`: +```bash +"$REPO_ROOT/lib/kubernetes/init.sh" # use default versions +"$REPO_ROOT/lib/argocd/init.sh" --version v3.5.0 # pin a version +"$REPO_ROOT/lib/argocd/init.sh" --read-only --version v3.5.0 # combine flags +"$REPO_ROOT/lib/kubernetes/init.sh" --kubectl-version v1.35.0 --helm-version v4.1.0 # per-tool versions +``` + +**post-start.sh** runs every time the container starts: +- Start services (databases, clusters, etc.) +- Apply initial state + +**Infrastructure constraints:** + +Codespaces run on 2 cores and 8 GB RAM by default. Design your adventure within these limits โ€” avoid memory-hungry workloads running in parallel and prefer lightweight images where possible. + +Post-create should finish in under 15 minutes, but aim for well under that. + +#### 3. Build the Working Solution + +Implement the fully working version first. This is what the solved challenge looks like where everything works correctly. + +This approach helps you: +- Understand the problem space before designing the challenge +- Ensure the challenge is actually solvable +- Have a reference implementation for the solution walkthrough + +#### 4. Introduce the Challenges + +Work backwards from your working solution to create the "broken" state participants will fix. + +Good challenges are: +- **Realistic.** Introduce issues that could happen in real-world scenarios. +- **Discoverable.** Problems should be findable using standard tools and techniques. +- **Focused.** Each issue should teach something from the learning objectives. +- **Solvable.** Don't require knowledge outside what's being taught. + +Not sure if a challenge belongs at Beginner, Intermediate, or Expert? See [Calibrating Difficulty](#calibrating-difficulty) for concrete signals and time expectations. + +#### 5. Write the Documentation + +Fill in the generated `docs/.md` โ€” it already contains the story, objectives, and learning outcomes from the idea file. Add: +- Architecture overview (how the level is set up) +- UI access instructions with port numbers +- Where to start investigating +- Helpful links to external docs + +No spoilers โ€” save those for a `solutions/.md` file. + +See [Adventure 01's beginner level](adventures/01-echoes-lost-in-orbit/docs/beginner.md) for a good example. + +#### 6. Create the Verification Script + +Fill in the generated `/verify.sh`. It already has the boilerplate wired up โ€” add your checks between the `print_sub_header` and the summary block. + +A good verification script: +- Passes when the challenge is solved correctly +- Fails with helpful error messages when not solved +- Generates a certificate users can copy to claim completion + +**Check outcomes, not implementation.** Verify the state the participant should have reached โ€” a service is healthy, traces are present in Jaeger, a metric is being collected โ€” not how they got there. File content checks (`check_file_contains`) are a last resort: they break for valid alternative solutions and reward copy-pasting over understanding. If your objective says "see traces in Jaeger", your verification should check that traces exist, not that a specific import was added. + +Browse `lib/scripts/` to see the available helper functions. + +#### 7. Final Test Run + +Before submitting: + +1. **Start fresh.** Open a new Codespace to test the full experience. +2. **Solve the challenge.** Complete it as a participant would. +3. **Run verification.** Ensure `verify.sh` passes when solved and fails when not. +4. **Check all links.** Documentation should be complete and accurate. + +### Tips + +- **[Open a draft PR early.](https://github.com/dynatrace-oss/open-ecosystem-challenges/compare)** Get feedback on structure before completing everything. +- **Ship one level at a time.** Each level gets its own PR โ€” start with one, get it working, then build the next. Use `Part of #` on all but the last PR, and `Closes #` on the final one so the tracking issue closes automatically. +- **Test on slow connections.** Codespace startup time matters. +- **Write clear error messages.** Help participants understand what went wrong without giving away the solution. + +--- + +## Write a Solution Walkthrough + +Solution walkthroughs help participants who get stuck and serve as learning resources for those who want to understand the "why" behind each solution. + +> โš ๏ธ **Walkthroughs are only accepted after the challenge deadline has passed.** This protects the experience for active participants. + +### How to Contribute + +Browse [walkthrough issues](https://github.com/dynatrace-oss/open-ecosystem-challenges/issues?q=is%3Aissue+is%3Aopen+label%3A%22solution+walkthrough%22), comment to claim a level, and [submit your walkthrough as a PR](https://github.com/dynatrace-oss/open-ecosystem-challenges/compare). Walkthroughs can take any form: + +- **External content** โ€” a blog post, video, or any public resource. Just link to it from the adventure's solutions page. +- **In-repo** โ€” a markdown file in `adventures/XX-adventure-name/docs/solutions/`. + +The most useful walkthroughs don't just show what to fix โ€” they explain *why* something was broken and how you'd reason your way to the solution. diff --git a/README.md b/README.md index 3ed8409b..a57e40f7 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ headaches. ## ๐Ÿš€ Ready to Start? -[Choose your adventure](https://dynatrace-oss.github.io/open-ecosystem-challenges/) and begin learning! +[Choose your adventure](https://offon.dev/adventures/) and begin learning! diff --git a/adventures/01-echoes-lost-in-orbit/README.md b/adventures/01-echoes-lost-in-orbit/README.md index 2a68cd6d..7f1cc850 100644 --- a/adventures/01-echoes-lost-in-orbit/README.md +++ b/adventures/01-echoes-lost-in-orbit/README.md @@ -10,4 +10,4 @@ ready to go. ## ๐Ÿš€ Ready to Start? -[Choose your level](https://dynatrace-oss.github.io/open-ecosystem-challenges/01-echoes-lost-in-orbit/) and begin learning! \ No newline at end of file +[Choose your level](https://offon.dev/adventures/echoes-lost-in-orbit/) and begin learning! \ No newline at end of file diff --git a/adventures/01-echoes-lost-in-orbit/beginner/smoke-test.sh b/adventures/01-echoes-lost-in-orbit/beginner/smoke-test.sh index 20804bc4..9e7841b9 100755 --- a/adventures/01-echoes-lost-in-orbit/beginner/smoke-test.sh +++ b/adventures/01-echoes-lost-in-orbit/beginner/smoke-test.sh @@ -13,7 +13,7 @@ OBJECTIVE="By the end of this level, you should: - Make the system resilient so Argo CD automatically reverts manual changes made to the cluster - Confirm that updates happen automatically without leaving stale resources behind" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/01-echoes-lost-in-orbit/beginner" +DOCS_URL="https://offon.dev/adventures/echoes-lost-in-orbit/levels/beginner" print_header \ 'Challenge 01: Echoes Lost in Orbit' \ diff --git a/adventures/01-echoes-lost-in-orbit/docs/index.md b/adventures/01-echoes-lost-in-orbit/docs/index.md index 61bdb398..e064883d 100644 --- a/adventures/01-echoes-lost-in-orbit/docs/index.md +++ b/adventures/01-echoes-lost-in-orbit/docs/index.md @@ -40,7 +40,7 @@ independent โ€” pick your level and start wherever you feel comfortable! The Echo Server is misbehaving. Both environments seem to be down, and messages are silent. Your mission: investigate the ArgoCD configuration and restore proper multi-environment delivery. -[**Start the Beginner Challenge**](./beginner.md){ .md-button .md-button--primary } +[**Start the Beginner Challenge**](./beginner.md) ### ๐ŸŸก Intermediate: The Silent Canary @@ -51,7 +51,7 @@ After fixing the communication outage, the Intergalactic Union welcomed a new sp team attempted to deploy their language files using a progressive delivery system, but the rollout is failing. Your mission: debug the broken canary deployment and bring the Zephyrians' voices online. -[**Start the Intermediate Challenge**](./intermediate.md){ .md-button .md-button--primary } +[**Start the Intermediate Challenge**](./intermediate.md) ### ๐Ÿ”ด Expert: Echoes in the Dark @@ -62,5 +62,5 @@ After fixing the Zephyrian communications, word of your progressive release mast They want to apply progressive delivery to their mission-critical service: **HotROD** (Hyperspace Operations & Transport - Rapid Orbital Dispatch), an interstellar ride-sharing service handling dispatch requests across thousands of star systems. Every millisecond of latency matters, and any error could strand travelers between dimensions. -[**Start the Expert Challenge**](./expert.md){ .md-button .md-button--primary } +[**Start the Expert Challenge**](./expert.md) diff --git a/adventures/01-echoes-lost-in-orbit/expert/smoke-test.sh b/adventures/01-echoes-lost-in-orbit/expert/smoke-test.sh index 834191a1..339ee7a7 100755 --- a/adventures/01-echoes-lost-in-orbit/expert/smoke-test.sh +++ b/adventures/01-echoes-lost-in-orbit/expert/smoke-test.sh @@ -17,7 +17,7 @@ OBJECTIVE="By the end of this level, you should have: - Error rate thresholds (< 5%) - Latency thresholds for the 95th percentile (< 1000ms)" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/01-echoes-lost-in-orbit/expert" +DOCS_URL="https://offon.dev/adventures/echoes-lost-in-orbit/levels/expert" print_header \ 'Challenge 01: Echoes Lost in Orbit' \ diff --git a/adventures/01-echoes-lost-in-orbit/intermediate/smoke-test.sh b/adventures/01-echoes-lost-in-orbit/intermediate/smoke-test.sh index d13f3060..0678f545 100755 --- a/adventures/01-echoes-lost-in-orbit/intermediate/smoke-test.sh +++ b/adventures/01-echoes-lost-in-orbit/intermediate/smoke-test.sh @@ -12,7 +12,7 @@ OBJECTIVE="By the end of this level, you should have: - Two working PromQL queries in the AnalysisTemplate that validate application health during releases - All rollouts complete successfully" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/01-echoes-lost-in-orbit/intermediate" +DOCS_URL="https://offon.dev/adventures/echoes-lost-in-orbit/levels/intermediate" print_header \ 'Challenge 01: Echoes Lost in Orbit' \ diff --git a/adventures/01-echoes-lost-in-orbit/mkdocs.yaml b/adventures/01-echoes-lost-in-orbit/mkdocs.yaml deleted file mode 100644 index 0794e527..00000000 --- a/adventures/01-echoes-lost-in-orbit/mkdocs.yaml +++ /dev/null @@ -1,11 +0,0 @@ -site_name: '๐Ÿ›ฐ๏ธ 01: Echoes Lost in Orbit' - -nav: - - Introduction: index.md - - '๐ŸŸข Beginner': beginner.md - - '๐ŸŸก Intermediate': intermediate.md - - '๐Ÿ”ด Expert': expert.md - - 'Solutions': - - '๐ŸŸข Beginner': solutions/beginner.md - - '๐ŸŸก Intermediate': solutions/intermediate.md - - '๐Ÿ”ด Expert': solutions/expert.md diff --git a/adventures/02-building-cloudhaven/README.md b/adventures/02-building-cloudhaven/README.md index ca88d61f..b900d5ff 100644 --- a/adventures/02-building-cloudhaven/README.md +++ b/adventures/02-building-cloudhaven/README.md @@ -9,4 +9,4 @@ The entire **infrastructure is pre-provisioned in your Codespace** โ€” OpenTofu ## ๐Ÿš€ Ready to Start? -[Choose your level](https://dynatrace-oss.github.io/open-ecosystem-challenges/02-building-cloudhaven/) and begin learning! \ No newline at end of file +[Choose your level](https://offon.dev/adventures/building-cloudhaven/) and begin learning! \ No newline at end of file diff --git a/adventures/02-building-cloudhaven/beginner/smoke-test.sh b/adventures/02-building-cloudhaven/beginner/smoke-test.sh index f1108b29..f347fdd3 100755 --- a/adventures/02-building-cloudhaven/beginner/smoke-test.sh +++ b/adventures/02-building-cloudhaven/beginner/smoke-test.sh @@ -13,7 +13,7 @@ OBJECTIVE="By the end of this level, you should: - Store state remotely in a GCS backend following best practices so the Guild can collaborate - Resolve all TODOs in the code and successfully run tofu apply" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/02-building-cloudhaven/beginner" +DOCS_URL="https://offon.dev/adventures/building-cloudhaven/levels/beginner" print_header \ 'Challenge 02: Building CloudHaven' \ diff --git a/adventures/02-building-cloudhaven/docs/beginner.md b/adventures/02-building-cloudhaven/docs/beginner.md index 6ceced83..6da3835b 100644 --- a/adventures/02-building-cloudhaven/docs/beginner.md +++ b/adventures/02-building-cloudhaven/docs/beginner.md @@ -53,7 +53,7 @@ Your Codespace comes pre-configured with the following tools to help you solve t Quick start: -- Fork the [repo](https://dynatrace-oss.github.io/open-ecosystem-challenges/) +- Fork the [repo](https://github.com/dynatrace-oss/open-ecosystem-challenges) - Create a Codespace - Select "๐ŸŒ† Adventure 02 | ๐ŸŸข Beginner (The Foundation Stones)" - Wait ~2 minutes for the environment to initialize (`Cmd/Ctrl + Shift + P` โ†’ `View Creation Log` to view progress) diff --git a/adventures/02-building-cloudhaven/docs/index.md b/adventures/02-building-cloudhaven/docs/index.md index bb29e9ac..3539f079 100644 --- a/adventures/02-building-cloudhaven/docs/index.md +++ b/adventures/02-building-cloudhaven/docs/index.md @@ -45,7 +45,7 @@ some services remain half-configured or misconfigured. Your mission: Complete the OpenTofu configuration and establish proper state management. -[**Start the Beginner Challenge**](./beginner.md){ .md-button .md-button--primary } +[**Start the Beginner Challenge**](./beginner.md) ### ๐ŸŸก Intermediate: The Modular Metropolis @@ -59,7 +59,7 @@ working tests... and buggy code that doesn't match them. Your mission: Fix the bugs, complete the integration test, and deploy the infrastructure. -[**Start the Intermediate Challenge**](./intermediate.md){ .md-button .md-button--primary } +[**Start the Intermediate Challenge**](./intermediate.md) ### ๐Ÿ”ด Expert: The Guardian Protocols @@ -70,4 +70,4 @@ CloudHaven needs automated guardians: workflows that detect infrastructure drift integration tests, and security scans, and then apply safe changes. A previous engineer started the setup but left it incomplete. Your mission: bring the Guardian Protocols online. -[**Start the Expert Challenge**](./expert.md){ .md-button .md-button--primary } +[**Start the Expert Challenge**](./expert.md) diff --git a/adventures/02-building-cloudhaven/expert/smoke-test.sh b/adventures/02-building-cloudhaven/expert/smoke-test.sh index ff190690..a4eaf0e5 100755 --- a/adventures/02-building-cloudhaven/expert/smoke-test.sh +++ b/adventures/02-building-cloudhaven/expert/smoke-test.sh @@ -12,7 +12,7 @@ OBJECTIVE="By the end of this level, your workflows should: - Validate pull requests (run plan, tests, security scans, comment on PR) - Apply infrastructure automatically when PR is merged" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/02-building-cloudhaven/expert" +DOCS_URL="https://offon.dev/adventures/building-cloudhaven/levels/expert" print_header \ 'Challenge 02: Building CloudHaven' \ diff --git a/adventures/02-building-cloudhaven/intermediate/smoke-test.sh b/adventures/02-building-cloudhaven/intermediate/smoke-test.sh index bf415bca..3e507843 100755 --- a/adventures/02-building-cloudhaven/intermediate/smoke-test.sh +++ b/adventures/02-building-cloudhaven/intermediate/smoke-test.sh @@ -12,7 +12,7 @@ OBJECTIVE="By the end of this level, you should have: - A completed integration test that applies infrastructure against the mock GCP API - Three districts deployed with correctly configured infrastructure (vaults and ledgers)" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/02-building-cloudhaven/intermediate" +DOCS_URL="https://offon.dev/adventures/building-cloudhaven/levels/intermediate" print_header \ 'Challenge 02: Building CloudHaven' \ diff --git a/adventures/02-building-cloudhaven/mkdocs.yaml b/adventures/02-building-cloudhaven/mkdocs.yaml deleted file mode 100644 index 522374d7..00000000 --- a/adventures/02-building-cloudhaven/mkdocs.yaml +++ /dev/null @@ -1,7 +0,0 @@ -site_name: '๐ŸŒ†๏ธ 02: Building CloudHaven' - -nav: - - Introduction: index.md - - '๐ŸŸข Beginner': beginner.md - - '๐ŸŸก Intermediate': intermediate.md - - '๐Ÿ”ด Expert': expert.md diff --git a/adventures/03-the-ai-observatory/README.md b/adventures/03-the-ai-observatory/README.md index b609d5e1..ea8e5fd6 100644 --- a/adventures/03-the-ai-observatory/README.md +++ b/adventures/03-the-ai-observatory/README.md @@ -10,5 +10,4 @@ are ready to go. ## ๐Ÿš€ Ready to Start? -[Choose your level](https://dynatrace-oss.github.io/open-ecosystem-challenges/03-the-ai-observatory/) and begin -learning! +[Choose your level](https://offon.dev/adventures/the-ai-observatory/) and begin learning! diff --git a/adventures/03-the-ai-observatory/beginner/verify.sh b/adventures/03-the-ai-observatory/beginner/verify.sh index 87cf516e..f9b6cc7b 100755 --- a/adventures/03-the-ai-observatory/beginner/verify.sh +++ b/adventures/03-the-ai-observatory/beginner/verify.sh @@ -13,7 +13,7 @@ OBJECTIVE="By the end of this level, you should: - See and analyze traces in Jaeger to find out what causes the high bandwidth usage - Provide the correct answer in quiz.txt" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/03-the-ai-observatory/beginner" +DOCS_URL="https://offon.dev/adventures/the-ai-observatory/levels/beginner" print_header \ 'Challenge 03: The AI Observatory' \ diff --git a/adventures/03-the-ai-observatory/docs/index.md b/adventures/03-the-ai-observatory/docs/index.md index 074145e4..5a630770 100644 --- a/adventures/03-the-ai-observatory/docs/index.md +++ b/adventures/03-the-ai-observatory/docs/index.md @@ -34,7 +34,7 @@ independent โ€” pick your level and start wherever you feel comfortable! The HubSystem is running "blind". Your mission: instrument the Python application with OpenLLMetry, send traces to the collector, and use Jaeger to find out what the AI is actually doing. -[**Start the Beginner Challenge**](./beginner.md){ .md-button .md-button--primary } +[**Start the Beginner Challenge**](./beginner.md) ### ๐ŸŸก Intermediate: The Distracted Pilot @@ -46,7 +46,7 @@ You've escaped aboard the Perihelion, a research vessel piloted by a very opinio coordinates to RaviHyral should have been ready an hour ago โ€” but ART is distracted. Your mission: instrument the RAG pipeline, track what ART is actually retrieving, and fix the navigation system before you miss the jump window. -[**Start the Intermediate Challenge**](./intermediate.md){ .md-button .md-button--primary } +[**Start the Intermediate Challenge**](./intermediate.md) ### ๐Ÿ”ด Expert: The Noise Filter @@ -59,5 +59,5 @@ mess. Non-standard span names, missing token usage, and Jaeger drowning in noise instrumentation to follow GenAI semantic conventions, record errors properly, and configure tail sampling to filter out the noise. -[**Start the Expert Challenge**](./expert.md){ .md-button .md-button--primary } +[**Start the Expert Challenge**](./expert.md) diff --git a/adventures/03-the-ai-observatory/expert/verify.sh b/adventures/03-the-ai-observatory/expert/verify.sh index dc0b86a3..25a18457 100755 --- a/adventures/03-the-ai-observatory/expert/verify.sh +++ b/adventures/03-the-ai-observatory/expert/verify.sh @@ -11,7 +11,7 @@ OBJECTIVE="By the end of this level, you should: - Fix ART's chat span to follow OpenTelemetry GenAI semantic conventions โ€” including token usage - Configure tail sampling in the OpenTelemetry Collector to only keep traces that contain errors or take longer than 5 seconds" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/03-the-ai-observatory/expert" +DOCS_URL="https://offon.dev/adventures/the-ai-observatory/levels/expert" print_header \ 'Challenge 03: The AI Observatory' \ diff --git a/adventures/03-the-ai-observatory/intermediate/verify.sh b/adventures/03-the-ai-observatory/intermediate/verify.sh index 9bc4a685..f93ec9e4 100755 --- a/adventures/03-the-ai-observatory/intermediate/verify.sh +++ b/adventures/03-the-ai-observatory/intermediate/verify.sh @@ -13,7 +13,7 @@ OBJECTIVE="By the end of this level, you should: - Create a Prometheus recording rule to calculate ART's 'Distraction Level' - Restore the navigation system so ART successfully calculates the jump coordinates to RaviHyral" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/03-the-ai-observatory/intermediate" +DOCS_URL="https://offon.dev/adventures/the-ai-observatory/levels/intermediate" print_header \ 'Challenge 03: The AI Observatory' \ diff --git a/adventures/03-the-ai-observatory/mkdocs.yaml b/adventures/03-the-ai-observatory/mkdocs.yaml deleted file mode 100644 index 15009898..00000000 --- a/adventures/03-the-ai-observatory/mkdocs.yaml +++ /dev/null @@ -1,7 +0,0 @@ -site_name: '๐Ÿ”ญ 03: The AI Observatory' - -nav: - - Introduction: index.md - - '๐ŸŸข Beginner': beginner.md - - '๐ŸŸก Intermediate': intermediate.md - - '๐Ÿ”ด Expert': expert.md diff --git a/adventures/04-blind-by-design/README.md b/adventures/04-blind-by-design/README.md index 9b80e3e7..b3837fdd 100644 --- a/adventures/04-blind-by-design/README.md +++ b/adventures/04-blind-by-design/README.md @@ -9,4 +9,4 @@ The entire **infrastructure is pre-provisioned in your Codespace**. ## ๐Ÿš€ Ready to Start? -[Choose your level](https://dynatrace-oss.github.io/open-ecosystem-challenges/04-blind-by-design/) and begin learning! +[Choose your level](https://offon.dev/adventures/blind-by-design/) and begin learning! diff --git a/adventures/04-blind-by-design/beginner/verify.sh b/adventures/04-blind-by-design/beginner/verify.sh index 84aa7d22..bdbb723c 100755 --- a/adventures/04-blind-by-design/beginner/verify.sh +++ b/adventures/04-blind-by-design/beginner/verify.sh @@ -12,7 +12,7 @@ OBJECTIVE="By the end of this level, you should: - Confirm the response payload includes the OpenFeature evaluation details โ€” flag key, variant, reason, value - Edit flags.json to change the defaultVariant, save, and have the next request return the new variant without restarting the app" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/04-blind-by-design/beginner" +DOCS_URL="https://offon.dev/adventures/blind-by-design/levels/beginner" APP_URL="http://localhost:8080/" FLAGS_FILE="$SCRIPT_DIR/flags.json" diff --git a/adventures/04-blind-by-design/docs/expert.md b/adventures/04-blind-by-design/docs/expert.md deleted file mode 100644 index 3d4984a1..00000000 --- a/adventures/04-blind-by-design/docs/expert.md +++ /dev/null @@ -1,217 +0,0 @@ -# ๐Ÿ”ด Expert: Read the chart - -Spans are already flowing into Tempo from the OpenFeature `TracesHook`, but the metrics half is dead โ€” the `MeterProvider` has no exporter and the `MetricsHook` was never registered. The dashboard the operator wants to triage from is empty. The k6 loadgen is idle, waiting for a flag flip to turn it on. - -## ๐Ÿช The Backstory - -The trial just went wide. Phase 3 of the new vision amplifier โ€” `vision_amplifier_v2` โ€” was approved for the full cohort yesterday morning. The promise was straightforward: subjects emerge with sharper eyesight than they walked in with. By mid-afternoon the audit log was screaming. Subjects were stabilising 200ms slower, and roughly one in ten of them was emerging **blind** โ€” containment failure recorded as an HTTP 500. The lab director pulled up the **Feature Flag Metrics** dashboard expecting to triage visually. The dashboard was dark. Someone had wired up traces but never finished the metrics half. There is no chart to read. The lab is studying eyesight and the lab itself cannot see. - -Your job, in order: **turn on the lights**, find the bad arm of the trial, and **halt enrolment** on the amplifier โ€” all without redeploying the lab. That last constraint is the whole point of feature flags: when a rollout starts misbehaving in production, you need an operational lever that does not take twenty minutes to pull. Save the file, watch the dose drop, watch the 5xx rate fall back to baseline, watch the next batch of subjects walk out seeing. - -## ๐Ÿ† Rewards - -A **50% Linux Foundation certification voucher** for 1st place and **Credly badges** for the top 3 โ€” for players who complete all three levels by **Tuesday, 26 May 2026 at 23:59 CET**. See the [adventure overview](index.md#-rewards) for ranking rules and eligibility details. - -## ๐Ÿ’ฌ Join the discussion - -Share your solutions and questions in -the [challenge thread](https://community.open-ecosystem.com/t/read-the-chart-adventure-04-expert) -in the Open Ecosystem Community. - -## ๐Ÿ—๏ธ Architecture - -Four containers and one Spring Boot process, all on a shared Docker network. - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” OTLP/gRPC :4317 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Spring Boot โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ grafana/otel-lgtm โ”‚ -โ”‚ fun-with-flags- โ”‚ flag eval + HTTP โ”‚ - Grafana :3000 โ”‚ -โ”‚ java-spring โ”‚ โ”‚ - Prometheus :9090 โ”‚ -โ”‚ :8080 โ”‚ โ”‚ - Tempo :3200 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ฒโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ OpenFeature SDK :8013 โ”‚ scrape / pull - โ”‚ (RPC mode) โ”‚ -โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ flagd โ”‚ โ—€โ”€โ”€โ”€โ”€ poll loadgen flag โ”€โ”€โ”‚ k6 loadgen โ”‚ -โ”‚ :8013 (gRPC + HTTP โ”‚ โ”‚ HTTP GET /?userId=โ€ฆ โ”‚ -โ”‚ eval gateway)โ”‚ โ”‚ (the lab interceptor โ”‚ -โ”‚ :8014 management / โ”‚ โ”‚ sets userId as the โ”‚ -โ”‚ metrics โ”‚ โ”‚ targetingKey, which โ”‚ -โ”‚ :8015 sync stream โ”‚ โ”‚ is what fractional โ”‚ -โ”‚ :8016 OFREP โ”‚ โ”‚ rollouts bucket on) โ”‚ -โ”‚ flags.json mounted โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ๐ŸŽฏ Objective - -By the end of this level, the lab hits each of these observable outcomes: - -- **Spans for `fun-with-flags-java-spring` are visible in Tempo** with `feature_flag.context.` attributes โ€” searching `feature_flag.context.dose=underdose` lights up the requests where a tech mis-dosed, with `feature_flag.variant=clouded` on the same span. -- **`feature_flag_evaluation_requests_total` is non-zero in Prometheus** โ€” flag evaluations show up as counters, not just spans. -- **The Feature Flag Metrics dashboard renders.** Variant-distribution, error rate, latency p99 โ€” all populated from the metric counters. -- **The `vision_amplifier_v2` rollout is rolled back to 100% off** โ€” without redeploying the lab. -- **HTTP 5xx rate over the last minute drops below 1%.** The bad arm is contained. - -## ๐Ÿง  What You'll Learn - -- 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 โ€” to close the loop between *why* a - flag resolved the way it did and *what* the operator sees in Tempo -- How [`fractional`](https://flagd.dev/reference/custom-operations/fractional-operation/) - 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 - -## ๐Ÿงฐ Toolbox - -Your Codespace comes pre-configured with the following tools: - -- [`curl`](https://curl.se/): HTTP client for hitting the lab, flagd, and Prometheus -- [`./mvnw`](https://maven.apache.org/wrapper/): The Maven wrapper to build and run the Spring Boot lab -- A browser pointed at [`http://localhost:3000`](http://localhost:3000) for Grafana (admin / admin) -- [`jq`](https://jqlang.github.io/jq/): Pretty-print and filter JSON from `curl` - -flagd, the Grafana LGTM stack, and the k6 loadgen are **sibling devcontainer services** โ€” they come up automatically when the Codespace boots. There is no `docker compose up` step. Inside the workspace they are reachable as `flagd`, `lgtm`, and `loadgen`. The Grafana / Prometheus / Tempo / OTLP ports on `lgtm` are also forwarded onto the Codespace host so you can click them in the Ports tab; flagd stays on the docker-internal network only. - -## โœ… How to Play - -### 1. Start Your Challenge - -> ๐Ÿ“– **First time?** Check out the [Getting Started Guide](../../start-a-challenge) -> for detailed instructions on forking, starting a Codespace, and waiting for -> infrastructure setup. - -Quick start: - -- Fork the repo -- Create a Codespace -- Select **"Adventure 04 | ๐Ÿ”ด Expert (Read the chart)"** -- Wait ~2-3 minutes for the sibling containers (flagd, Grafana LGTM, k6 - loadgen) to come up. They are part of the devcontainer compose, so they - start automatically โ€” no `docker compose up` step. - -### 2. Start the Lab - -The sibling containers (flagd, the LGTM stack, the k6 loadgen) are already up โ€” the Spring Boot lab itself isn't. Boot it before you click into the Ports tab so the forwarded `:8080` is actually serving. Either click **Run** on `Laboratory` in the Spring Boot Dashboard panel (or press **F5** with `Laboratory.java` open), or, from the terminal: - -```bash -./mvnw spring-boot:run -``` - -Spans start flowing into Tempo on the first request โ€” the OpenTelemetry trace pipeline is already wired. The metrics half is dead (task 4a) so the Grafana dashboard panels stay empty until you fix it. - -### 3. Access the UIs - -Open the **Ports** tab in the bottom panel and click through to: - -#### Spring Boot lab (Port `8080`) - -The application under test. Open `http://localhost:8080/` to get a vision_state reading -back. Add a `userId` query parameter (e.g. `?userId=subject-42`) to give the -fractional rollout a stable bucketing key. - -#### Grafana (Port `3000`) - -The single window into the LGTM stack. Login is `admin` / `admin` (skip the -"change your password" prompt). - -- **Dashboards โ†’ Fun With Flags โ€” Feature Flag Metrics** โ€” the dashboard the - director keeps reloading. Empty for now. -- **Explore โ†’ Tempo** โ€” search by service `fun-with-flags-java-spring` - to see flag evaluations as span events nested inside HTTP request spans. - Traces work even before you wire up metrics. - -#### Prometheus (Port `9090`) - -Exposed by the LGTM container. Useful for `curl`-driven debugging: -`curl 'http://localhost:9090/api/v1/query?query=feature_flag_evaluation_requests_total'`. - -#### Tempo (Port `3200`) - -Tempo's own HTTP API. The `verify.sh` script uses -`http://localhost:3200/api/search?tags=service.name=fun-with-flags-java-spring` -to assert traces are flowing. - -#### flagd - -flagd runs on the docker-internal network only. The lab and the loadgen reach it as `flagd:8013`; you don't need to forward its ports onto the Codespace host to play this level. (`verify.sh` runs inside the workspace container so it can reach `flagd:8013` directly.) - -#### OTLP receivers (Ports `4317` / `4318`) - -The Spring Boot app exports traces (and, after you finish the wiring, metrics) -to the LGTM stack on `4317` (gRPC) and `4318` (HTTP). - -### 4. Implement the Objective - -Four sub-tasks, in order: wire the meter provider, register the matching `MetricsHook`, write your own `ContextSpanHook` to enrich spans with the flag-decision context, then turn on the loadgen so you can find and roll back the misbehaving fractional rollout. - -#### 4a. Turn on the metrics exporter - -OTel ships two parallel pipelines: **traces** (per-request spans, already flowing into Tempo) and **metrics** (aggregate counters, dead). The OpenTelemetry Java Agent attached to the lab JVM has both pipelines plumbed and pointed at the LGTM stack, but its config says `otel.metrics.exporter=none` โ€” anything the meter records goes nowhere. Flip the exporter on and the OpenFeature `MetricsHook` (next step) finds the working meter provider through `GlobalOpenTelemetry` without any further plumbing. - -`otel.properties` (next to `pom.xml`) is what the agent reads on startup. While you're there, look at the export interval โ€” the agent's default makes the next ten minutes harder than they need to be. - -#### 4b. Register `MetricsHook` on the OpenFeature API - -The OpenFeature OTel contrib library ships two hooks that turn flag evaluations into telemetry: **`TracesHook`** emits a span event on the active span (that's why flag evaluations show up nested inside HTTP request spans in Tempo); **`MetricsHook`** emits four counters per evaluation โ€” `feature_flag_evaluation_requests_total` and friends โ€” that power the dashboard panels. - -`OpenFeatureConfig.java` registers `TracesHook` but stops there. `MetricsHook` needs an `OpenTelemetry` handle to find the meter provider โ€” the agent installs one globally at JVM start, so `GlobalOpenTelemetry.get()` is the way to reach it. Even once `MetricsHook` is registered, the **Fun With Flags โ€” Feature Flag Metrics** dashboard stays empty until something drives traffic โ€” that's the next step. - -#### 4c. Author and register your own `ContextSpanHook` - -The two contrib hooks tell you *what* happened โ€” which flag, which variant, which reason. The `AuditHook` shipped with this level (carried over from Intermediate) writes the durable archive view to disk. What's missing is the **on-call's view in Tempo**: when a span shows `feature_flag.variant=clouded`, the operator can't see *why* without a separate hop into the audit log. Write a third hook that copies the merged eval context attributes onto the active OTel span as `feature_flag.context.` โ€” same data the audit log records, but visible right next to the variant in the trace UI. - -The shape is roughly: - -```text -before(hookCtx) { - span = active OTel span - for each allowlisted key in merged eval context: - span.setAttribute("feature_flag.context." + key, value) -} -``` - -The `before` callback receives a `HookContext`, and `getCtx()` returns the **merged** evaluation context (global + transaction + invocation) โ€” exactly what drove the flag's resolution. Span attributes go on the currently active span; the OpenFeature hook fires inside its scope. Register it alongside `TracesHook` / `MetricsHook` in `OpenFeatureConfig`. The verifier searches Tempo for `feature_flag.context.dose=underdose` once you're done โ€” that's the smoke signal. - -> โš ๏ธ **Allowlist, don't iterate.** Use a fixed allowlist (`List.of("species", "country", "dose")`) โ€” never iterate the whole eval context. The merged context routinely carries the OpenFeature `targetingKey`, typically a stable user id that joins to email and account data in real apps. Span attributes are retained for days in Tempo and indexed at scale; once they ship, redacting after the fact is hard. Same discipline `AuditHook` already follows for the audit log, same reason. See [OpenTelemetry's security guidance](https://opentelemetry.io/docs/security/). - -#### 4d. Turn on the loadgen, find the bad rollout, roll it back - -`fractional` is flagd's bucketing operation: given a list of `[variant, percent]` pairs, it deterministically assigns each evaluation to a variant based on a hash of the **`targetingKey`** on the eval context. Same key โ†’ same bucket โ†’ same variant. Different keys spread across the percentages. **If no targeting key is set, every evaluation hashes the same way, every request lands in the same bucket, and the percentages do nothing.** The `SpeciesInterceptor` shipped with this level reads `?userId=` and threads it through as the targetingKey โ€” the lab is already serving fractional rollouts correctly without you touching it. - -`flags.json` in the expert directory has a `loadgen_active` flag (off) and the misbehaving `vision_amplifier_v2` flag. flagd watches the file and picks up changes within a second; the k6 loadgen polls `loadgen_active` every two seconds, so flipping it turns on five virtual users hammering the lab. When the loadgen turns on, latency p99 should climb around 200ms and the 5xx rate around 10% โ€” confirmation that something is firing. The dashboard's variant-distribution panel tells you which one. Roll the offender back via the flag definition, watch the dashboard recover. - -**No deploy. No rebuild. No restart of the lab.** - -#### Helpful Documentation - -- [OpenFeature OTel contrib hooks (Java)](https://github.com/open-feature/java-sdk-contrib/tree/main/hooks/open-telemetry) โ€” where `TracesHook` and `MetricsHook` live, with constructor signatures -- [OpenTelemetry Java Agent โ€” agent configuration](https://opentelemetry.io/docs/zero-code/java/agent/configuration/) โ€” every `otel.*` key the agent honors, including exporter and batch-interval knobs -- [OpenFeature Hooks concept](https://openfeature.dev/docs/reference/concepts/hooks) โ€” the `before` / `after` / `error` / `finallyAfter` lifecycle for authoring your own hook -- [flagd `fractional` operation](https://flagd.dev/reference/custom-operations/fractional-operation/) โ€” the bucketing rule and how it reads the targetingKey -- [OpenTelemetry security guidance](https://opentelemetry.io/docs/security/) โ€” why allowlists on span attributes matter at SIEM scale - -### 5. Verify Your Solution - -Once you think you've solved the challenge, run the verification script: - -```bash -./verify.sh -``` - -**If the verification fails:** - -The script will tell you which checks failed. Fix the issues and run it again. - -**If the verification passes:** - -1. The script will check if your changes are committed and pushed. -2. Follow the on-screen instructions to commit your changes if needed. -3. Once everything is ready, the script will generate a **Certificate of Completion**. -4. **Copy this certificate** and paste it into the [challenge thread](https://community.open-ecosystem.com/c/open-ecosystem-challenges/) to claim your victory! ๐Ÿ† diff --git a/adventures/04-blind-by-design/docs/expert.yaml b/adventures/04-blind-by-design/docs/expert.yaml new file mode 100644 index 00000000..f5a051c3 --- /dev/null +++ b/adventures/04-blind-by-design/docs/expert.yaml @@ -0,0 +1,154 @@ +level: expert +emoji: "๐Ÿ”ด" +title: "Read the chart" +community_url: "https://community.open-ecosystem.com/t/read-the-chart-adventure-04-expert" + +summary: | + Spans are already flowing into Tempo from the OpenFeature `TracesHook`, but the metrics half is dead โ€” the `MeterProvider` has no exporter and the `MetricsHook` was never registered. The dashboard the operator wants to triage from is empty. The k6 loadgen is idle, waiting for a flag flip to turn it on. + +backstory: | + The trial just went wide. Phase 3 of the new vision amplifier โ€” `vision_amplifier_v2` โ€” was approved for the full cohort yesterday morning. The promise was straightforward: subjects emerge with sharper eyesight than they walked in with. By mid-afternoon the audit log was screaming. Subjects were stabilising 200ms slower, and roughly one in ten of them was emerging **blind** โ€” containment failure recorded as an HTTP 500. The lab director pulled up the **Feature Flag Metrics** dashboard expecting to triage visually. The dashboard was dark. Someone had wired up traces but never finished the metrics half. There is no chart to read. The lab is studying eyesight and the lab itself cannot see. + + Your job, in order: **turn on the lights**, find the bad arm of the trial, and **halt enrolment** on the amplifier โ€” all without redeploying the lab. That last constraint is the whole point of feature flags: when a rollout starts misbehaving in production, you need an operational lever that does not take twenty minutes to pull. Save the file, watch the dose drop, watch the 5xx rate fall back to baseline, watch the next batch of subjects walk out seeing. + +objective: + - "**Spans for `fun-with-flags-java-spring` are visible in Tempo** with `feature_flag.context.` attributes โ€” searching `feature_flag.context.dose=underdose` lights up the requests where a tech mis-dosed, with `feature_flag.variant=clouded` on the same span." + - "**`feature_flag_evaluation_requests_total` is non-zero in Prometheus** โ€” flag evaluations show up as counters, not just spans." + - "**The Feature Flag Metrics dashboard renders.** Variant-distribution, error rate, latency p99 โ€” all populated from the metric counters." + - "**The `vision_amplifier_v2` rollout is rolled back to 100% off** โ€” without redeploying the lab." + - "**HTTP 5xx rate over the last minute drops below 1%.** The bad arm is contained." + +what_you_learn: + - "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 โ€” to close the loop between *why* a flag resolved the way it did and *what* the operator sees in Tempo" + - "How [`fractional`](https://flagd.dev/reference/custom-operations/fractional-operation/) 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" + +architecture: + description: | + Four containers and one Spring Boot process, all on a shared Docker network. + diagram: | + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” OTLP/gRPC :4317 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Spring Boot โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ grafana/otel-lgtm โ”‚ + โ”‚ fun-with-flags- โ”‚ flag eval + HTTP โ”‚ - Grafana :3000 โ”‚ + โ”‚ java-spring โ”‚ โ”‚ - Prometheus :9090 โ”‚ + โ”‚ :8080 โ”‚ โ”‚ - Tempo :3200 โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ฒโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ OpenFeature SDK :8013 โ”‚ scrape / pull + โ”‚ (RPC mode) โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ flagd โ”‚ โ—€โ”€โ”€โ”€โ”€ poll loadgen flag โ”€โ”€โ”‚ k6 loadgen โ”‚ + โ”‚ :8013 (gRPC + HTTP โ”‚ โ”‚ HTTP GET /?userId=โ€ฆ โ”‚ + โ”‚ eval gateway)โ”‚ โ”‚ (the lab interceptor โ”‚ + โ”‚ :8014 management / โ”‚ โ”‚ sets userId as the โ”‚ + โ”‚ metrics โ”‚ โ”‚ targetingKey, which โ”‚ + โ”‚ :8015 sync stream โ”‚ โ”‚ is what fractional โ”‚ + โ”‚ :8016 OFREP โ”‚ โ”‚ rollouts bucket on) โ”‚ + โ”‚ flags.json mounted โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +toolbox: + - name: curl + url: "https://curl.se/" + description: "HTTP client for hitting the lab, flagd, and Prometheus" + - name: ./mvnw + url: "https://maven.apache.org/wrapper/" + description: "The Maven wrapper to build and run the Spring Boot lab" + - name: Grafana + url: "http://localhost:3000" + description: "Single window into the LGTM stack (admin / admin)" + - name: jq + url: "https://jqlang.github.io/jq/" + description: "Pretty-print and filter JSON from curl" + +services: + - name: "Spring Boot lab" + port: 8080 + description: "The application under test. Open `http://localhost:8080/` to get a vision_state reading back. Add a `userId` query parameter (e.g. `?userId=subject-42`) to give the fractional rollout a stable bucketing key." + - name: Grafana + port: 3000 + credentials: "admin / admin" + description: "Login is `admin` / `admin` (skip the \"change your password\" prompt). Go to **Dashboards โ†’ Fun With Flags โ€” Feature Flag Metrics** (empty for now) or **Explore โ†’ Tempo** to search by service `fun-with-flags-java-spring`. Traces work even before you wire up metrics." + - name: Prometheus + port: 9090 + description: "Useful for curl-driven debugging: `curl 'http://localhost:9090/api/v1/query?query=feature_flag_evaluation_requests_total'`." + - name: Tempo + port: 3200 + description: "Tempo's HTTP API โ€” `verify.sh` uses `http://localhost:3200/api/search?tags=service.name=fun-with-flags-java-spring` to assert traces are flowing." + - name: flagd + internal: true + description: "Runs on the docker-internal network only. The lab and the loadgen reach it as `flagd:8013`; no port forwarding needed. (`verify.sh` runs inside the workspace container so it can reach `flagd:8013` directly.)" + - name: "OTLP receivers" + port: "4317 (gRPC) / 4318 (HTTP)" + description: "The Spring Boot app exports traces (and, after you finish the wiring, metrics) to the LGTM stack here." + +how_to_play: + - id: start-lab + title: "Start the Lab" + content: | + The sibling containers (flagd, LGTM stack, k6 loadgen) are already up โ€” the Spring Boot lab itself isn't. Boot it before you click into the Ports tab so the forwarded `:8080` is actually serving. Either click **Run** on `Laboratory` in the Spring Boot Dashboard panel (or press **F5** with `Laboratory.java` open), or, from the terminal: + + ```bash + ./mvnw spring-boot:run + ``` + + Spans start flowing into Tempo on the first request โ€” the trace pipeline is already wired. The metrics half is dead (task 4a) so the Grafana dashboard panels stay empty until you fix it. + + - id: metrics-exporter + title: "Turn on the metrics exporter" + content: | + OTel ships two parallel pipelines: **traces** (per-request spans, already flowing into Tempo) and **metrics** (aggregate counters, dead). The OpenTelemetry Java Agent attached to the lab JVM has both pipelines plumbed and pointed at the LGTM stack, but its config says `otel.metrics.exporter=none` โ€” anything the meter records goes nowhere. Flip the exporter on and the OpenFeature `MetricsHook` (next step) finds the working meter provider through `GlobalOpenTelemetry` without any further plumbing. + + `otel.properties` (next to `pom.xml`) is what the agent reads on startup. While you're there, look at the export interval โ€” the agent's default makes the next ten minutes harder than they need to be. + + - id: metrics-hook + title: "Register MetricsHook on the OpenFeature API" + content: | + The OpenFeature OTel contrib library ships two hooks that turn flag evaluations into telemetry: **`TracesHook`** emits a span event on the active span (that's why flag evaluations show up nested inside HTTP request spans in Tempo); **`MetricsHook`** emits four counters per evaluation โ€” `feature_flag_evaluation_requests_total` and friends โ€” that power the dashboard panels. + + `OpenFeatureConfig.java` registers `TracesHook` but stops there. `MetricsHook` needs an `OpenTelemetry` handle to find the meter provider โ€” the agent installs one globally at JVM start, so `GlobalOpenTelemetry.get()` is the way to reach it. Even once `MetricsHook` is registered, the **Fun With Flags โ€” Feature Flag Metrics** dashboard stays empty until something drives traffic โ€” that's the next step. + + - id: context-span-hook + title: "Author and register your own ContextSpanHook" + content: | + The two contrib hooks tell you *what* happened โ€” which flag, which variant, which reason. The `AuditHook` shipped with this level (carried over from Intermediate) writes the durable archive view to disk. What's missing is the **on-call's view in Tempo**: when a span shows `feature_flag.variant=clouded`, the operator can't see *why* without a separate hop into the audit log. Write a third hook that copies the merged eval context attributes onto the active OTel span as `feature_flag.context.` โ€” same data the audit log records, but visible right next to the variant in the trace UI. + + The shape is roughly: + + ```text + before(hookCtx) { + span = active OTel span + for each allowlisted key in merged eval context: + span.setAttribute("feature_flag.context." + key, value) + } + ``` + + The `before` callback receives a `HookContext`, and `getCtx()` returns the **merged** evaluation context (global + transaction + invocation) โ€” exactly what drove the flag's resolution. Span attributes go on the currently active span; the OpenFeature hook fires inside its scope. Register it alongside `TracesHook` / `MetricsHook` in `OpenFeatureConfig`. The verifier searches Tempo for `feature_flag.context.dose=underdose` once you're done โ€” that's the smoke signal. + + > โš ๏ธ **Allowlist, don't iterate.** Use a fixed allowlist (`List.of("species", "country", "dose")`) โ€” never iterate the whole eval context. The merged context routinely carries the OpenFeature `targetingKey`, typically a stable user id that joins to email and account data in real apps. Span attributes are retained for days in Tempo and indexed at scale; once they ship, redacting after the fact is hard. Same discipline `AuditHook` already follows for the audit log, same reason. See [OpenTelemetry's security guidance](https://opentelemetry.io/docs/security/). + + - id: rollback + title: "Turn on the loadgen, find the bad rollout, roll it back" + content: | + `fractional` is flagd's bucketing operation: given a list of `[variant, percent]` pairs, it deterministically assigns each evaluation to a variant based on a hash of the **`targetingKey`** on the eval context. Same key โ†’ same bucket โ†’ same variant. Different keys spread across the percentages. **If no targeting key is set, every evaluation hashes the same way, every request lands in the same bucket, and the percentages do nothing.** The `SpeciesInterceptor` shipped with this level reads `?userId=` and threads it through as the targetingKey โ€” the lab is already serving fractional rollouts correctly without you touching it. + + `flags.json` in the expert directory has a `loadgen_active` flag (off) and the misbehaving `vision_amplifier_v2` flag. flagd watches the file and picks up changes within a second; the k6 loadgen polls `loadgen_active` every two seconds, so flipping it turns on five virtual users hammering the lab. When the loadgen turns on, latency p99 should climb around 200ms and the 5xx rate around 10% โ€” confirmation that something is firing. The dashboard's variant-distribution panel tells you which one. Roll the offender back via the flag definition, watch the dashboard recover. + + **No deploy. No rebuild. No restart of the lab.** + +helpful_docs: + - title: "OpenFeature OTel contrib hooks (Java)" + url: "https://github.com/open-feature/java-sdk-contrib/tree/main/hooks/open-telemetry" + description: "where TracesHook and MetricsHook live, with constructor signatures" + - title: "OpenTelemetry Java Agent โ€” agent configuration" + url: "https://opentelemetry.io/docs/zero-code/java/agent/configuration/" + description: "every otel.* key the agent honors, including exporter and batch-interval knobs" + - title: "OpenFeature Hooks concept" + url: "https://openfeature.dev/docs/reference/concepts/hooks" + description: "the before / after / error / finallyAfter lifecycle for authoring your own hook" + - title: "flagd fractional operation" + url: "https://flagd.dev/reference/custom-operations/fractional-operation/" + description: "the bucketing rule and how it reads the targetingKey" + - title: "OpenTelemetry security guidance" + url: "https://opentelemetry.io/docs/security/" + description: "why allowlists on span attributes matter at SIEM scale" diff --git a/adventures/04-blind-by-design/docs/index.md b/adventures/04-blind-by-design/docs/index.md deleted file mode 100644 index ce38960e..00000000 --- a/adventures/04-blind-by-design/docs/index.md +++ /dev/null @@ -1,60 +0,0 @@ -# ๐Ÿงช Adventure 04: Blind by Design - -Three levels of OpenFeature with **flagd** as the provider, in a Java + Spring Boot service. Wire the SDK against a flagd sidecar (Beginner), layer evaluation context to target by cohort (Intermediate), then instrument flag evaluations with OpenTelemetry and roll back a misbehaving fractional rollout (Expert) โ€” all without redeploying. - -The entire **infrastructure is pre-provisioned in your Codespace** โ€” no local setup required. - -## ๐Ÿช The Backstory - -The **Aletheia Institute** is running a multi-phase vision-enhancement trial. The **lab** is a Spring Boot service whose one job is to record the **`vision_state`** of every subject who walks through the protocol โ€” `blurry`, `sharp`, `enhanced`, or `clouded` โ€” because subjects don't all arrive with the same biology, the same dose adherence, or the same trial-jurisdiction baseline. The flag definitions that drive those readings live in `flags.json`, watched by a **flagd** sidecar; the **OpenFeature** SDK is supposed to call that sidecar on every evaluation. - -It hasn't been. For the past **eight months**, every subject through the door has been recorded as `"untreated"` โ€” the integration was never finished, and the lab director assumed the system was reading the chart. Worse, **eight weeks ago** the Institute opened its flagship Phase 3 trial: a new amplifier variant rolled out fractionally to a cohort by a targeting rule in `flags.json`. **Four adverse-event reports** have since been filed, each one a subject whose `vision_state` at discharge was worse than at enrollment. - -The monitoring is dark โ€” not by accident, but because no one ever turned the lights on. Your mission across three levels: **stand up the lab** so it reads the chart, **read the chart by cohort** so outcomes can be tracked, then **turn on the lights and roll back the Phase 3 variant** before the director signs off on the next enrollment batch. - -## ๐Ÿง  What you'll be using - -OpenFeature is a vendor-neutral standard for feature flags. The reference cloud-native implementation is **flagd** โ€” it serves flag definitions from a JSON file, locally or remotely, and the OpenFeature SDK in your application calls it on every evaluation. - -In this adventure, the lab uses OpenFeature exactly the way a real engineering team would: a Spring Boot service holds the SDK client, flagd holds the flag definitions, and the targeting rules in `flags.json` decide what reading every subject ends up with. By the end, you'll have wired the SDK in from scratch, learned to record outcomes by cohort, and rolled back a misbehaving Phase 3 trial without redeploying. - -## ๐Ÿ† Rewards - -Adventure 04 is the **first adventure with rewards**. To be eligible, you must complete **all three levels** before the deadline: **Tuesday, 26 May 2026 at 23:59 CET**. - -- ๐Ÿฅ‡ **1st place:** a 50% voucher for a Linux Foundation certification - -Additionally, the **top 3** finishers each receive a **Credly badge** to showcase the achievement. - -> โ„น๏ธ 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). See the [points & ranking rules](https://community.open-ecosystem.com/t/about-the-challenges-category/16) for the full breakdown. - -## ๐ŸŽฎ Choose Your Level - -Each level is a standalone challenge with its own Codespace, building on the story while staying technically independent. - -### ๐ŸŸข Beginner: Stand up the lab - -- **Status:** โœ… Ready to Play -- **Topics:** OpenFeature Java SDK, flagd as a sidecar (`Resolver.RPC`), Spring Boot - -Wire OpenFeature into a Spring Boot service so the lab's `vision_state` reading is resolved by a flagd sidecar against a `flags.json` instead of a hard-coded literal. - -[**Start the Beginner Challenge**](beginner.md){ .md-button .md-button--primary } - -### ๐ŸŸก Intermediate: Outcome by cohort - -- **Status:** โœ… Ready to Play -- **Topics:** OpenFeature targeting, transaction context, hooks, Spring `HandlerInterceptor` - -Add request-scoped context, a global runtime context, an invocation context at the call site, and an audit hook so the lab records the right reading per subject cohort. - -[**Start the Intermediate Challenge**](intermediate.md){ .md-button .md-button--primary } - -### ๐Ÿ”ด Expert: Phase 3 โ€” read the chart - -- **Status:** โœ… Ready to Play -- **Topics:** OpenTelemetry traces + metrics, custom hooks, Grafana LGTM, fractional rollout, OpenFeature OTel hooks - -Finish wiring OpenTelemetry through to the Grafana LGTM stack, write a `ContextSpanHook` that puts the merged eval context onto Tempo spans, find the misbehaving Phase 3 amplifier in the dashboard, and roll it back without redeploying. - -[**Start the Expert Challenge**](expert.md){ .md-button .md-button--primary } diff --git a/adventures/04-blind-by-design/docs/index.yaml b/adventures/04-blind-by-design/docs/index.yaml new file mode 100644 index 00000000..3fc4ee17 --- /dev/null +++ b/adventures/04-blind-by-design/docs/index.yaml @@ -0,0 +1,28 @@ +slug: blind-by-design +name: "Blind by Design" +emoji: "๐Ÿงช" +deadline: "2026-05-26T23:59:00+01:00" + +technologies: + - OpenFeature Java SDK + - flagd + - Spring Boot + - Grafana LGTM (Tempo + Prometheus + Loki) + - Testcontainers + +backstory: | + The **Aletheia Institute** is running a multi-phase vision-enhancement trial. The **lab** is a Spring Boot service whose one job is to record the **`vision_state`** of every subject who walks through the protocol โ€” `blurry`, `sharp`, `enhanced`, or `clouded` โ€” because subjects don't all arrive with the same biology, the same dose adherence, or the same trial-jurisdiction baseline. The flag definitions that drive those readings live in `flags.json`, watched by a **flagd** sidecar; the **OpenFeature** SDK is supposed to call that sidecar on every evaluation. + + It hasn't been. For the past **eight months**, every subject through the door has been recorded as `"untreated"` โ€” the integration was never finished, and the lab director assumed the system was reading the chart. Worse, **eight weeks ago** the Institute opened its flagship Phase 3 trial: a new amplifier variant rolled out fractionally to a cohort by a targeting rule in `flags.json`. **Four adverse-event reports** have since been filed, each one a subject whose `vision_state` at discharge was worse than at enrollment. + + The monitoring is dark โ€” not by accident, but because no one ever turned the lights on. Your mission across three levels: **stand up the lab** so it reads the chart, **read the chart by cohort** so outcomes can be tracked, then **turn on the lights and roll back the Phase 3 variant** before the director signs off on the next enrollment batch. + +overview: | + OpenFeature is a vendor-neutral standard for feature flags. The reference cloud-native implementation is **flagd** โ€” it serves flag definitions from a JSON file, locally or remotely, and the OpenFeature SDK in your application calls it on every evaluation. + + In this adventure, the lab uses OpenFeature exactly the way a real engineering team would: a Spring Boot service holds the SDK client, flagd holds the flag definitions, and the targeting rules in `flags.json` decide what reading every subject ends up with. By the end, you'll have wired the SDK in from scratch, learned to record outcomes by cohort, and rolled back a misbehaving Phase 3 trial without redeploying. + +rewards: + prizes: + - "๐Ÿฅ‡ **1st place:** 50% voucher for a Linux Foundation certification" + - "**Top 3 finishers:** Credly badge" diff --git a/adventures/04-blind-by-design/expert/verify.sh b/adventures/04-blind-by-design/expert/verify.sh index faec4d0d..e44a1d6e 100755 --- a/adventures/04-blind-by-design/expert/verify.sh +++ b/adventures/04-blind-by-design/expert/verify.sh @@ -13,7 +13,7 @@ OBJECTIVE="By the end of this level, the lab hits each of these observable outco - The 'vision_amplifier_v2' rollout is rolled back to 100% off โ€” without redeploying the lab - HTTP 5xx rate over the last minute drops below 1%" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/04-blind-by-design/expert" +DOCS_URL="https://offon.dev/adventures/blind-by-design/levels/expert" print_header \ 'Adventure 04: Blind by Design' \ diff --git a/adventures/04-blind-by-design/intermediate/verify.sh b/adventures/04-blind-by-design/intermediate/verify.sh index d4be107b..f2a1c8e7 100755 --- a/adventures/04-blind-by-design/intermediate/verify.sh +++ b/adventures/04-blind-by-design/intermediate/verify.sh @@ -14,7 +14,7 @@ OBJECTIVE="By the end of this level, the lab hits each of these observable outco - Every evaluation produces an [AUDIT] log line carrying species, country, and dose - The response is never 'untreated' (provider is wired and reaches flagd)" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/04-blind-by-design/intermediate" +DOCS_URL="https://offon.dev/adventures/blind-by-design/levels/intermediate" print_header \ 'Adventure 04: Blind by Design' \ diff --git a/adventures/04-blind-by-design/mkdocs.yaml b/adventures/04-blind-by-design/mkdocs.yaml deleted file mode 100644 index fae87634..00000000 --- a/adventures/04-blind-by-design/mkdocs.yaml +++ /dev/null @@ -1,7 +0,0 @@ -site_name: '๐Ÿงช 04: Blind by Design' - -nav: - - Introduction: index.md - - '๐ŸŸข Beginner': beginner.md - - '๐ŸŸก Intermediate': intermediate.md - - '๐Ÿ”ด Expert': expert.md diff --git a/adventures/planned/00-lex-imperfecta/README.md b/adventures/planned/00-lex-imperfecta/README.md index f27ea873..1d48f125 100644 --- a/adventures/planned/00-lex-imperfecta/README.md +++ b/adventures/planned/00-lex-imperfecta/README.md @@ -12,5 +12,4 @@ The entire **infrastructure is pre-provisioned in your Codespace** ## ๐Ÿš€ Ready to Start? -[Choose your level](https://dynatrace-oss.github.io/open-ecosystem-challenges/00-lex-imperfecta/) and begin -learning! +[Choose your level](https://offon.dev/adventures/lex-imperfecta/) and begin learning! diff --git a/adventures/planned/00-lex-imperfecta/beginner/verify.sh b/adventures/planned/00-lex-imperfecta/beginner/verify.sh index 1a8a5f61..ccc21ddc 100755 --- a/adventures/planned/00-lex-imperfecta/beginner/verify.sh +++ b/adventures/planned/00-lex-imperfecta/beginner/verify.sh @@ -12,7 +12,7 @@ OBJECTIVE="By the end of this level, you should have: - All workloads running as privileged containers blocked at admission with a clear policy violation message - Confirmed that all other workloads deploy and run successfully in the cluster" -DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/00-lex-imperfecta/beginner" +DOCS_URL="https://offon.dev/adventures/lex-imperfecta/levels/beginner" print_header \ 'Challenge 00: Lex Imperfecta' \ diff --git a/adventures/planned/00-lex-imperfecta/docs/index.md b/adventures/planned/00-lex-imperfecta/docs/index.md index eac337b9..ee14e639 100644 --- a/adventures/planned/00-lex-imperfecta/docs/index.md +++ b/adventures/planned/00-lex-imperfecta/docs/index.md @@ -25,4 +25,4 @@ independent โ€” pick your level and start wherever you feel comfortable. Fix broken Kyverno policies to restore proper admission control. -[**Start the Beginner Challenge**](./beginner.md){ .md-button .md-button--primary } +[**Start the Beginner Challenge**](./beginner.md) diff --git a/adventures/planned/00-lex-imperfecta/mkdocs.yaml b/adventures/planned/00-lex-imperfecta/mkdocs.yaml deleted file mode 100644 index 275892ff..00000000 --- a/adventures/planned/00-lex-imperfecta/mkdocs.yaml +++ /dev/null @@ -1,5 +0,0 @@ -site_name: 'โš–๏ธ 00: Lex Imperfecta' - -nav: - - Introduction: index.md - - '๐ŸŸข Beginner': beginner.md diff --git a/docs/Challenge Assets/Blind By Design/Launch Annoucement.png b/docs/Challenge Assets/Blind By Design/Launch Annoucement.png deleted file mode 100644 index b81c1c23379bf9ed7b32851680e0b397ae9934f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1587708 zcmV)qK$^daP)YT33qVU7~mc34#C!%ndVh&Tp^PdFcMSd!M2GBRF&BclKU;t-gHy zb+5}7JpVD60e~vj6U>2ag8&c+00_(s5Rv$Y@HYSq5P_MQEGty!b_7gBAl|C*|K<-7 zh{42QW)PKiH8ac+4T=DW{`hlY0$BV~A?gproU*Ra#kM1C*1d6i+=2MXVE&H@o1zCS zYGX4qQ56wb+yL|YbHLu%#B>pfivV*%Rn`Ll8v}r-`mG@ik+`^xgnbkMW>ekq1OIABiO zHdh}Y?1r}!;xIE;3PgIp%nWV@bJ!S7mHse+v2(JM1#Jj`i7wE^<`kR$Xp1Wa*>7DZpjApVF)#PPz!w1k6*GvaiB=R70W>e6p(;dF zB#~0)wDEAA4=>B{sgrU2V9#7#p1*$ag&S9IJo(u5YgZn>a`o!<8;?DH{qoi87azO4 zt_O%=Y}@IBri~ zyscN};biJMw`n$Q907v|k%&ZTDh`Wga{?1%!iETBJ=mOfJi7k}{+L0OrY!(mtscDf zR1POL2j(7i`-wW>T|`uB2@om(w1baClaQDkh); z6>M8ThYy*2K-^L1tX?^yI+{&`gcpl0ayZ!7Fh($zh$%Bv-!QO3qLZJ*;bny`Ha4-D zju63O3=od>UpG&_&i2A{ZN-J2`(##u-W>ohA%`fm56b zK(MT;%RHVzr41Hhh^|4b*ic$#j@V!ZU1UA6Ik0Vw99hPzFmH?h#qS-L%))ft3`BK3 z*w`=!ASPOZaO+LCHDX)xC_?-Y#yfH~?()HA7KsF|bx@jwAkJh}S?4kc?jr%m@ zjO5lJ>bi?m4b$e-=(1ZsBOd@Ly?SphXkzs|hg!@}iWJ~A5d*Z^k=A#hxrOBbBQ>wc zz}CqSC>76v8(9RJz4NF3#Pp(Kuc@DOn1BNb4(R`)bekwDokKE^<_Rxj6)o< zGq&zJiJg%4uGKGfFDi{&8xb+02HZqinMj9p^AKmGw9ZFEuICh&eM^;KXDuv1v}=V# zYUiRk=x9pJB0I7z47f;?Ner?q{n2D#$A`Q%09e4>DW$c8!hMhYS>1t)Lm0>^5Z!2i z9N!z;K-YbMDxKOy#5979Um#4PiRLcE9^SIq3aTQoX>?=-@k2zQ+PphxGBAy)nddg_ zsD<_d)gMt-6htZ!rIiui3dg01cxT^nS7qJ7JOMy72Ob*qEI}C4BEnhZ8=38gDR)1O zFD3Mw?7XgOwqPQb9S6kX7E39It4xDUS)FIMUo?E?Xhk3#o7ImoGCqy#l8z45_u0$8Xwrpps zonBv$@4U9Yc$Dlhys=Zlg- zfK-b(Mv+c48<@u4y4BO=4W8;X5tC4Oi%ys45I{U@opXn+MmL%M3}|$WX6bxJXdAA3 zCH)PJr<&6;09{1jHO&YH(_~>#%S4GTCW~O0*`#fH20qU5a>msLyCMuaVX=|C(Vb$r zqOvX!u_IY_Tu+C{2I-KQ(6SI&IuEumrzF;yL6h10@58icUY2IkdLA)1LC|Ez7hvFM zqNOA<~Mv-Wo?m>)e#TBwC>jI6jS0f-*s9UP!v%5C{+vRB#1F1Us5SW1% zM_{(Vrpipa%bCw1rC_?QsNO&d<1d5=*ux53`QF29uR34C>7H(NuaF~K22=l$uH-~K z2@+ry)FsU?xO%#K^*L=J<EIPdnoUDz07Aov5}jefXJrW;U9S^R zHYtp?1S+8pQVQo(mF+qBINhJpfQku05E4kj1X`=-bSng=_D_Qerp*v>buD`n&r=aj z<*~S5!Q8o~z@VH$4{nJhpA|d6Z+o}0dwXHZ9_Cu!6=Tmx*ykTF$bkWveX>GSqrzk+7E#ei}2RYsr-u!lrNr_@3k&PgD zGgzi+-qLK_%x!X7APgTT-aw+1wJ{Qh(i#(-BJ-p~J&kl=5{fICs~KV;$?m_xy@m-O z`QHbcG*JO0Cnuwf+rm?GB1Xf`;M4vLqAZ0NFiXRcY*-UWlR)MZdzdQf$G)nFIw11u z#$1>!xvj^i)`zxmpmue@^mjc?z2^;-|`-@bRc zz53RJ&%Ju@&tJX$jeB@|y$o3e6E*;GmKQa)pt2cTIhw2qsDdPz;j3zsqMY`p9fZ9( zd^sqIQih6F^(LTBglsj8lTiSiZE4`@>jD;{2Mfz21U?tVX{DMVsBpu^EE08&tVOfT zSd4%X3r6NJpFo>Mc?7`pd{v{TlO|_olFO7J3%`TdoKTZxfgwu%!eX37B_&;vQGsz< zG%KWOcpQqydgCYs=aLgng+#0QgIT{a18WzNPae|{BId=|WBrIBYC7&y78+EES~*#q zC&I)7flm>;1k}Hujmjl@U>C%_!I&-$jAP%F37faMu)4RTEd`nqi(|0KGmXHUPn-q? zb;L=?jHHDOVOwEN6_v?kDp2}eBBD+cEB&S|w(vv_2_^uOXq8}2<%L!Fv~4Uv$QYw$ zlk+~5g-X<13deeIXE{1+aU!w0$7-^->PE(F*^+K>5}RR=Nj+m;dUvR%s(!&4dO)*m zm%+gZ`20kGC~2Vie-x~}L8*%c!9pQwF6Mx+GLOUtkP>7qr`P~kmte%`y5V0!6$E*z z?p2=~vXPR-uW7R_M@$K|FqJ2Ppx^5|!c+Y(qq+|gs(-?=lm z-BSrRiRDf5nbr$oT%zrB?YdXqXq{JA0 z6N~QH>+U@O1hGE4G_cc5Jf!QZs07EqGqGI}H?G@7HOeyiaV1rsZLYP{>IigI~cd5)IPxNr>be-jmf#&v_fK;ZZND;r3J=C#jvGG5#7=y;+f)t$wdK$J~Rb*N*0Q7o+sy= zu!5g7gJX_Sz)c|rdZ-r3prcs9M9X_kplLR!>ysFZW>QUq3rvWnGS(T59F$B%U@tCE zjinx`rWmxXo()DgkA;z{wFX6-Qxct!