Skip to content

fix(challenge): load the role-dispatch file on first use - #22

Merged
Mathis (echobt) merged 2 commits into
mainfrom
fix/role-dispatch-lazy
Oct 20, 2026
Merged

Mathis (echobt) merged 2 commits into
mainfrom
fix/role-dispatch-lazy

Conversation

@echobt

@echobt Mathis (echobt) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Why

The Cortex challenge-supervisor starts a canary of every new image with an empty tmpfs /data and no secrets, and requires /version to answer. create_app loaded $HYPERTRAIN_ROLE_DISPATCH eagerly (app.py), so any GO image crashed at boot when the file was missing and the production update was refused ("canary did not answer /version"). e319cf6 (current prod image) never loaded role dispatch, so it passed.

What

create_app hands the store a wrapper that loads the dispatch file on first use (thread-safe, cached on success only). A missing, unreadable or invalid file raises ChallengeError(503, "ROLE_DISPATCH_UNAVAILABLE") at the point a role replay is needed; fixing the file works without a restart. Valid file: same launch behaviour, role_dispatch.load called once. Unset env: unchanged (None). store.py untouched (is None checks keep working because the wrapper is not None).

Evidence

tests/challenge/test_role_dispatch_lazy.py (3 tests): boot + /version 200 with a missing file (fails on base, passes here); 503 for missing / invalid JSON / extra field, then success after the file is fixed; valid file loaded once with identical refusal to the eager loader. test_role_dispatch_consumers_v2.py still passes (21 passed, 3 skipped: genuine-CUDA index not set).

Risk / rollback

A broken dispatch file is no longer caught at boot but at the first role replay (503). Scope pre-approved by the Principal for src/ (this change only). Revert the merge commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • The application can now start and serve version information even when role-dispatch configuration is unavailable.
    • Invalid or missing role-dispatch configuration now returns a clear service-unavailable error when role dispatch is requested.
    • Valid role-dispatch configuration is loaded on first use and remains available for subsequent requests.
    • Existing authorization safeguards for role-dispatch execution are preserved.

Update after review

  • Two extra tests kill the mutants found by review: 16 concurrent first calls load once (no-lock mutant fails), and an unexpected load error (RuntimeError) propagates instead of becoming a 503 (except Exception mutant fails).
  • Known trade-off: on the audit and referee paths the launch is evaluated after the audit lease is taken, so a broken dispatch file now costs one lease (LEASE_EXPIRED) instead of failing at boot. This is intended for the supervisor canary; a broken file is detectable with the first replay and fixed without a restart.

The supervisor canary boots every new image with an empty /data and no
secrets and requires /version to answer. create_app read
$HYPERTRAIN_ROLE_DISPATCH eagerly, so the canary crashed with
FileNotFoundError and the production update was refused.

create_app now hands the store a non-None wrapper that loads the file
on the first role replay under a lock and caches the RoleLaunch. A
missing, unreadable or invalid file raises ChallengeError(503,
"ROLE_DISPATCH_UNAVAILABLE") at that call, which the existing handler
maps to HTTP; failures are not cached, so fixing the file needs no
restart. store.py's None checks are unchanged. Errors raised by the
loaded factory pass through untouched.

Rejected: exposing the state in /health (non-goal, not trivial without
touching the store).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 56 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 623789ff-4402-4f70-8120-2d2fe6d57208
📥 Commits

Reviewing files that changed from the base of the PR and between a493bd5 and 6c9fda2.

📒 Files selected for processing: 1
  • tests/challenge/test_role_dispatch_lazy.py

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cf3abc0d-4073-4b46-ab9c-8378acdef367
📥 Commits

Reviewing files that changed from the base of the PR and between 2708b7d and a493bd5.

📒 Files selected for processing: 2
  • src/hypertrain/challenge/app.py
  • tests/challenge/test_role_dispatch_lazy.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Role dispatch now loads on first invocation instead of during app creation. Loading failures produce ChallengeError(503, "ROLE_DISPATCH_UNAVAILABLE"). Tests cover startup without a dispatch file, recovery after configuration changes, and reuse of a successfully loaded launcher.

Changes: Role-dispatch initialization

Layer / File(s) Summary
Lazy loading and validation
src/hypertrain/challenge/app.py, tests/challenge/test_role_dispatch_lazy.py
create_app passes a synchronized lazy launcher to ChallengeStore. The launcher loads role dispatch on first use and reuses it after successful loading. Tests cover missing and invalid files, recovery, scripts-path setup, and authorization behavior.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant create_app
  participant ChallengeStore
  participant _lazy_role_launch
  participant RoleDispatchFile
  create_app->>ChallengeStore: provide _lazy_role_launch
  ChallengeStore->>_lazy_role_launch: invoke role launch
  _lazy_role_launch->>RoleDispatchFile: load dispatch on first invocation
  RoleDispatchFile-->>_lazy_role_launch: dispatch implementation or loading error
  _lazy_role_launch-->>ChallengeStore: return launcher or raise 503 challenge error
Loading
Merge Risk: ⚪ Minimal · up to a493b

This change lets the challenge app start and answer /version without a dispatch file, and it returns a recoverable 503 on the first role replay if the file is missing or invalid. No actionable merge-blocking risk was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: loading the role-dispatch file on first use.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…rors

Two mutants survived the first three tests: a wrapper without the lock
(16 concurrent first calls each loaded the file) and an 'except Exception'
that would turn a RuntimeError into a 503. Add a 16-thread test asserting a
single load and a test that an unexpected load error propagates.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@echobt
Mathis (echobt) merged commit dc16319 into main Oct 20, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant