Skip to content

feat: reference check runner, docs and example DGD for validation gates - #11

Closed
sejori wants to merge 1 commit into
validation-gates-v2/5-loopbackfrom
validation-gates-v2/6-runner-docs
Closed

sejori wants to merge 1 commit into
validation-gates-v2/5-loopbackfrom
validation-gates-v2/6-runner-docs

Conversation

@sejori

@sejori sejori commented Oct 12, 2026 •

Copy link
Copy Markdown

Part 6 of 6 of the worker validation gates stack (DEP ai-dynamo/dynamo#15897). Based on part 5.

Summary

  • dynamo.validation.completion: a built-in check that one chat completion through the loopback endpoint returns content.
  • dynamo.validation.runner: a stateless check runner for url gates, run as a sidecar. POST /validate/<name> runs that check with the gate's environment and answers 200 on exit 0, else 500 with the end of stderr. Loopback only, standard library only.
  • A docs page and an example DGD: an exec gate plus a sidecar url gate, with max-unavailable: 0.
  • Codeowners: components/src/dynamo/validation/ goes to the runtime area.

Validation

  • E2E (tests/fault_tolerance/test_worker_validation_gates.py, CPU mocker workers, real frontend, etcd and NATS), 3 passed on this stack: a failing gate holds the worker (/health 503 with its reason, /live 200, no client requests reach it) while the old worker serves; passing gates release it, including a completion check through the loopback endpoint while the card is held; a restarted worker is checked again.
  • Runner unit test: 200 on pass, 500 with the stderr tail on fail, 404 for an unknown check.
  • pre-commit clean; codeowners strict build at 100%.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

"""Serve ``checks`` on loopback only: the runner runs commands."""

class Handler(BaseHTTPRequestHandler):
def do_POST(self) -> None:

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/src/dynamo/validation/__init__.py">

<violation number="1" location="components/src/dynamo/validation/__init__.py:7">
P3: The completion check also passes with empty `content` when `reasoning_content` or `tool_calls` is present, so this description overstates what the gate guarantees. Describe the requirement as a non-empty message, or make the check require `content`.</violation>
</file>

<file name="docs/fern/pages/kubernetes/fault-tolerance/validation-gates.md">

<violation number="1" location="docs/fern/pages/kubernetes/fault-tolerance/validation-gates.md:49">
P2: `card` mode cannot guarantee this for every router: the scope section below lists `worker_id`-pinned standalone-router traffic as uncovered. Replace “under any router” with a discovery-routing qualifier and retain that bypass warning.</violation>
</file>

<file name="components/src/dynamo/validation/runner.py">

<violation number="1" location="components/src/dynamo/validation/runner.py:42">
P2: `run_gate` drops the URL request at its deadline, but this blocking `subprocess.run` does not observe the disconnect, so a hung check and its descendants continue after the worker records failure. Run checks in a cancellable handler and terminate their process group on cancellation.

(Based on your team's feedback about process-group cancellation.)</violation>
</file>

<file name="components/src/dynamo/validation/completion.py">

<violation number="1" location="components/src/dynamo/validation/completion.py:35">
P2: `urlopen` uses urllib's default proxy and redirect handlers, so this check can leave the worker's loopback API; a proxy without a `NO_PROXY` exception can fail the gate, while a redirect can let another endpoint satisfy it. Use an opener with proxies and redirects disabled.

(Based on your team's feedback about keeping validation clients loopback-confined.)</violation>
</file>

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

View guided diff | Turn on auto-fix | Re-trigger cubic

`DYN_WORKER_VALIDATION_HOLD` sets what a held worker keeps back:

- `card` (default): the model card isn't written to discovery until release, so no frontend routes to
the worker under any router. `/health` reports 503.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: card mode cannot guarantee this for every router: the scope section below lists worker_id-pinned standalone-router traffic as uncovered. Replace “under any router” with a discovery-routing qualifier and retain that bypass warning.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/fern/pages/kubernetes/fault-tolerance/validation-gates.md, line 49:

<comment>`card` mode cannot guarantee this for every router: the scope section below lists `worker_id`-pinned standalone-router traffic as uncovered. Replace “under any router” with a discovery-routing qualifier and retain that bypass warning.</comment>

<file context>
@@ -0,0 +1,81 @@
+`DYN_WORKER_VALIDATION_HOLD` sets what a held worker keeps back:
+
+- `card` (default): the model card isn't written to discovery until release, so no frontend routes to
+  the worker under any router. `/health` reports 503.
+- `health`: the card is written at once and only `/health` is held. Use it when the deployment keeps
+  traffic off the worker by other means.
</file context>

except ValueError:
body = {}
env = {key: str(body[key]) for key in GATE_ENV_KEYS if key in body}
result = subprocess.run(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: run_gate drops the URL request at its deadline, but this blocking subprocess.run does not observe the disconnect, so a hung check and its descendants continue after the worker records failure. Run checks in a cancellable handler and terminate their process group on cancellation.

(Based on your team's feedback about process-group cancellation.)

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At components/src/dynamo/validation/runner.py, line 42:

<comment>`run_gate` drops the URL request at its deadline, but this blocking `subprocess.run` does not observe the disconnect, so a hung check and its descendants continue after the worker records failure. Run checks in a cancellable handler and terminate their process group on cancellation.

(Based on your team's feedback about process-group cancellation.) </comment>

<file context>
@@ -0,0 +1,80 @@
+            except ValueError:
+                body = {}
+            env = {key: str(body[key]) for key in GATE_ENV_KEYS if key in body}
+            result = subprocess.run(
+                argv,
+                env={**os.environ, **env},
</file context>

headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(request, timeout=120) as response:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: urlopen uses urllib's default proxy and redirect handlers, so this check can leave the worker's loopback API; a proxy without a NO_PROXY exception can fail the gate, while a redirect can let another endpoint satisfy it. Use an opener with proxies and redirects disabled.

(Based on your team's feedback about keeping validation clients loopback-confined.)

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At components/src/dynamo/validation/completion.py, line 35:

<comment>`urlopen` uses urllib's default proxy and redirect handlers, so this check can leave the worker's loopback API; a proxy without a `NO_PROXY` exception can fail the gate, while a redirect can let another endpoint satisfy it. Use an opener with proxies and redirects disabled.

(Based on your team's feedback about keeping validation clients loopback-confined.) </comment>

<file context>
@@ -0,0 +1,55 @@
+        headers={"Content-Type": "application/json"},
+    )
+    try:
+        with urllib.request.urlopen(request, timeout=120) as response:
+            message = json.loads(response.read())["choices"][0]["message"]
+    except urllib.error.HTTPError as error:
</file context>

"""Reference check and check runner for worker validation gates.

* ``python -m dynamo.validation.completion``: one chat completion through the
worker's loopback OpenAI endpoint must return content.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The completion check also passes with empty content when reasoning_content or tool_calls is present, so this description overstates what the gate guarantees. Describe the requirement as a non-empty message, or make the check require content.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At components/src/dynamo/validation/__init__.py, line 7:

<comment>The completion check also passes with empty `content` when `reasoning_content` or `tool_calls` is present, so this description overstates what the gate guarantees. Describe the requirement as a non-empty message, or make the check require `content`.</comment>

<file context>
@@ -0,0 +1,10 @@
+"""Reference check and check runner for worker validation gates.
+
+* ``python -m dynamo.validation.completion``: one chat completion through the
+  worker's loopback OpenAI endpoint must return content.
+* ``python -m dynamo.validation.runner``: a check runner for ``url`` gates, run
+  as a sidecar with its own image.
</file context>

@sejori
sejori force-pushed the validation-gates-v2/5-loopback branch from a89f76b to 1bd0479 Compare October 12, 2026 16:48
@sejori
sejori force-pushed the validation-gates-v2/6-runner-docs branch from e75480e to 4e004f6 Compare October 12, 2026 16:48
@sejori
sejori had a problem deploying to external_collaborator October 12, 2026 16:48 — with GitHub Actions Failure
@sejori
sejori force-pushed the validation-gates-v2/5-loopback branch from 1bd0479 to 377ff0c Compare October 12, 2026 17:05
@sejori
sejori force-pushed the validation-gates-v2/6-runner-docs branch from 4e004f6 to 261b972 Compare October 12, 2026 17:05
@sejori
sejori had a problem deploying to external_collaborator October 12, 2026 17:06 — with GitHub Actions Failure
@sejori
sejori force-pushed the validation-gates-v2/5-loopback branch from 377ff0c to 407708d Compare October 12, 2026 17:07
@sejori
sejori force-pushed the validation-gates-v2/6-runner-docs branch from 261b972 to fc13532 Compare October 12, 2026 17:07
@sejori
sejori had a problem deploying to external_collaborator October 12, 2026 17:07 — with GitHub Actions Failure
- dynamo.validation.completion: a built-in check that one chat
  completion through the worker's loopback endpoint returns content.
- dynamo.validation.runner: a stateless check runner for url gates, run
  as a sidecar with its own image. POST /validate/<name> runs that
  check with the gate's environment and answers 200 on exit 0, else 500
  with the end of its stderr. Loopback only.
- A docs page and an example DGD (an exec gate and a sidecar url gate
  with max-unavailable 0).
- An E2E test with CPU mocker workers, a real frontend, etcd and NATS:
  a failing gate holds the worker while the old one serves, passing
  gates release it, and a restarted worker is checked again.
- Assign components/src/dynamo/validation to the runtime area.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Seb Ringrose <seb@doubleword.ai>
@sejori
sejori force-pushed the validation-gates-v2/6-runner-docs branch from fc13532 to b8ea685 Compare October 13, 2026 10:36
@sejori
sejori had a problem deploying to external_collaborator October 13, 2026 10:36 — with GitHub Actions Failure
@sejori

sejori commented Oct 15, 2026

Copy link
Copy Markdown
Author

Superseded by ai-dynamo#16524 (prep) and ai-dynamo#16525 (gates), which carry this series rebased on current upstream main, including the per-gate phase that replaces DYN_WORKER_VALIDATION_HOLD (see DEP ai-dynamo#15897). Closing so there's one copy to maintain.

@sejori sejori closed this Oct 15, 2026

This branch had an error being deployed

1 failed deployment
external_collaborator — b8ea685a Deployed Oct 13, 2026 by sejori via ok-to-test #33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend::vllm documentation Improvements or additions to documentation feat

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants