Skip to content

eat: the guard's vLLM image by digest when deploy_guardrails is on (G01) - #275

Merged
88plug merged 2 commits into
mainfrom
claude/eas-gapfill-g01-guard
Nov 1, 2026
Merged

88plug merged 2 commits into
mainfrom
claude/eas-gapfill-g01-guard

Conversation

@88plug

@88plug 88plug commented Oct 30, 2026

Copy link
Copy Markdown
Owner

This fills the last open item of gap G01 from the agent-stack gap matrix: with deploy_guardrails=on, Qwen3Guard should be deployed by digest. #250 moved eat-up.sh to EAT release-1.1.0 and #264 added its refusals. On main, the guard still pulled vllm/vllm-openai-cpu:v0.28.0 by tag.

The gap

EAT release-1.1.0 (0c726f5) installs the guard with core/playbooks/deploy-guardrails.yml. That playbook runs helm upgrade --install vllm-guard {{ remote_helm_charts_base }}/vllm --values {{ remote_helm_charts_base }}/vllm/guardrail-values.yaml (lines 115 and 133-135), and that values file names the image by tag only (line 30). Every other image eat-up.sh deploys is pinned by digest. The shipped agentic-config.cfg keeps the guard off, so this only matters to a user who turns it on.

What changes

  • integrations/eat/brownfield.sh, stack mode, guard on: before anything deploys, it copies EAT's core/helm-charts to ~/k3d-gpu-guard-charts in the bastion.
  • The new integrations/eat/guard-values.py sets the copy's guard image.tag to the digest vllm-values.yaml already pins for the same image: v0.28.0@sha256:197354e6...df9647, the images.lock entry from eat: eat-up.sh deploys EAT release-1.1.0 (ledger F4 pre-registered) #250. The deploy stops, naming both sides, if:
    • EAT's guard names another repository,
    • EAT's guard names a tag other than the <tag> part of that pin, or
    • the vLLM values have no image or no digest.
  • The existing ansible-playbook wrapper passes the copy to deploy-guardrails.yml alone, as the extra var remote_helm_charts_base. Extra vars take precedence over the playbook's vars_files (config/vars/inference_common.yml sets /tmp/helm-charts).
  • Nothing else in that play reads the variable, and neither do the utils tasks it includes (get_optimized_cpu_topology, get_optimal_memory). The play runs on the bastion itself: brownfield_deployment.sh:212-222 puts localhost (ansible_connection: local) in kube_control_plane.
  • A marker file and a check after the deploy catch a guard playbook that bypassed the wrapper, the same way the gateway is handled. The wrapper also refuses a guard call when no copy was made.
  • EAT's checkout is not edited. images.lock is unchanged, because the guard uses the image that is already pinned.
  • Docs: integrations/eat/README.md (file table, versions table, blocker 11) and the guardrail bullet in docs/platforms.md.

Checked against 0c726f5 (read only)

helm template (Helm v4.1.1) of the vllm chart, with the playbook's values and --set-strings, renders the same manifests from EAT's charts and from the copy. The one difference is the image line:

<           image: "vllm/vllm-openai-cpu:v0.28.0"
>           image: "vllm/vllm-openai-cpu:v0.28.0@sha256:197354e6475e638eb5ccca09beb078488046a82de0e204e7429bf1a804df9647"

scripts/images-lock digest docker.io/vllm/vllm-openai-cpu:v0.28.0 re-read the same index digest on 2026-10-29.

Tests (written first)

The first commit, c77c22d, adds the cases to tests/eat_test.sh (108 passed, 10 failed). The stub EAT now calls deploy-guardrails.yml the way fresh-install.sh:419 and guardrails-controller.sh:29-35 do, and its charts carry a guardrail-values.yaml cut from 0c726f5. The cases cover:

  • with the guard on, the call gets the copy, and no other playbook does
  • the copy's tag equals vllm-values.yaml's, every other key is unchanged, and EAT's file and the rest of the chart are untouched
  • with the shipped config (guard off), no guard call is made and no copy is written
  • the deploy stops before brownfield_deployment runs on: another guard tag, a guard tag that already carries a different digest, another repository, vLLM values with no digest, and vLLM values with no image
  • a renamed guard playbook fails the deploy
  • a guard call with no copy does not run. This case was added with the implementation. It fails when that one line is removed.

Results on this branch:

tests/eat_test.sh             119 passed, 0 failed
tests/platforms_doc_test.sh   195 passed, 0 failed
tests/ci_wiring_test.sh        73 passed, 0 failed
tests/bench_test.sh            71 passed, 0 failed
tests/image_pins_test.sh       43 passed, 0 failed
tests/claims_check_test.sh     83 passed, 0 failed
tests/claims_check.sh          claims_check: 27 file(s) clean
tests/pe_premise_check_test.sh 16 passed, 0 failed
tests/pe_premise_check.sh      pe_premise_check: ok
python3 tests/ledger_drift_check_test.py  OK
python3 tests/ledger_drift_check.py       42 file(s) agree with 81 ledger rows
scripts/images-lock check / pins          ok / ok
shellcheck v0.11.0 --severity=warning     brownfield.sh, tests/eat_test.sh clean
tests/docs_test.sh             63 passed, 0 failed; mkdocs build --strict exits 0 (docs-build/requirements.txt --require-hashes, in docs-build's pinned python image)

Overlap

No open PR touches brownfield.sh. #260 and #261 already conflict with main in the header comment of tests/eat_test.sh. A trial merge of #260 with this branch gives the same single conflict hunk as a merge with main, so this PR adds no new conflict.

Not verified

The guard has not run on a k3d-gpu cluster. The shipped config keeps it off, and F4 is pre-registered with it off. This PR changes nothing on that path.

…s is on (failing)

With deploy_guardrails=on, EAT release-1.1.0's deploy-guardrails.yml installs the
vllm chart with core/helm-charts/vllm/guardrail-values.yaml, which names
vllm/vllm-openai-cpu:v0.28.0 by tag only (0c726f5 lines 115 and 133-135). These
tests ask brownfield.sh to hand that playbook, and only it, a copy of EAT's charts
whose guardrail-values.yaml carries the digest vllm-values.yaml already pins for the
same image, and to stop before anything deploys when the two disagree.

The stub EAT now calls deploy-guardrails.yml the way fresh-install.sh:419 and
guardrails-controller.sh do, and its charts carry a guardrail-values.yaml cut from
0c726f5. Ten cases fail: the copy is never made, and no mismatch stops the deploy.
EAT release-1.1.0 installs its Qwen3Guard guard with deploy-guardrails.yml, which
runs helm on {{ remote_helm_charts_base }}/vllm with that chart's
guardrail-values.yaml (0c726f5 lines 115 and 133-135). That file names
vllm/vllm-openai-cpu:v0.28.0 by tag only, so with deploy_guardrails=on the guard
pulled whatever the tag pointed to, while every other image eat-up.sh deploys is
pinned by digest.

In stack mode, with the guard on, brownfield.sh now copies EAT's core/helm-charts
to ~/k3d-gpu-guard-charts before anything deploys. The new guard-values.py sets the
copy's guard image tag to the digest vllm-values.yaml already pins for the same
image. It stops the deploy if EAT's guard names another repository or tag, or if
the vLLM values carry no digest. The ansible-playbook wrapper hands that copy to
deploy-guardrails.yml alone, as the extra var remote_helm_charts_base, which takes
precedence over the playbook's vars_files. Nothing else in that play reads the
variable; the utils tasks it includes do not either. The play runs on the
bastion itself (brownfield_deployment.sh:212-222 puts localhost, ansible_connection
local, in kube_control_plane), so the copy's path is where helm runs. A marker file and a check
after the deploy catch a guard playbook that bypassed the wrapper, as for the
gateway, and the wrapper refuses a guard call that has no copy.

Checked against 0c726f5: helm template of the vllm chart with the playbook's
values renders the same manifests from EAT's charts and from the copy except for
the one image line, which now ends in
@sha256:197354e6475e638eb5ccca09beb078488046a82de0e204e7429bf1a804df9647
(re-read from Docker Hub with scripts/images-lock digest). The guard has not run on
a k3d-gpu cluster; the shipped config keeps it off and F4 runs with it off.

One more eat_test case covers the wrapper refusing a guard call with no copy; it
fails with that line removed.
@88plug
88plug merged commit 13bdc49 into main Nov 1, 2026
80 checks passed
88plug added a commit that referenced this pull request Nov 1, 2026
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