Skip to content

feat(vllm): add GPU container image build pipeline - #693

Draft
nathan-weinberg wants to merge 616 commits into
mainfrom
vllm-gpu
Draft

feat(vllm): add GPU container image build pipeline#693
nathan-weinberg wants to merge 616 commits into
mainfrom
vllm-gpu

Conversation

@nathan-weinberg

@nathan-weinberg nathan-weinberg commented Aug 10, 2026

Copy link
Copy Markdown

Summary

Add Containerfile, GitHub Actions workflow, and Dependabot config for building CUDA-based vLLM GPU inference images with models baked in at build time

Related issue

Closes #692

Validation

N/A

Checklist

  • I reviewed every changed line and can explain the change.
  • New capabilities include an example config and functional example test.
  • User-facing behavior and generated documentation are updated.
  • Performance-sensitive changes include appropriate benchmark or load-test evidence.
  • Commits are signed and include a Signed-off-by trailer.

Breaking changes

N/A

dependabot Bot and others added 30 commits July 2, 2026 15:17
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dimitri Saridakis <dimitri.saridakis@gmail.com>
Signed-off-by: dimakis <dimitri.saridakis@gmail.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Alex Snaps <alex@wcgw.dev>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Aslak Knutsen <aslak@4fs.no>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
…nse-store (#271)

Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Co-authored-by: Francisco Javier Arceo <farceo@redhat.com>
Co-authored-by: Sébastien Han <seb@redhat.com>
)

Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
Co-authored-by: Sébastien Han <seb@redhat.com>
nerdalert and others added 28 commits August 24, 2026 11:42
* feat(filters): add OpenTelemetry routing spans

Add feature-gated routing.select spans for successful intelligent_route decisions while leaving request lifecycle, propagation, sampling, and export ownership in Praxis core.

Keep the default build unchanged and avoid OpenTelemetry SDK dependencies in the AI filters. Record only validated, bounded routing attributes and document the ownership and privacy boundaries.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* test(filters): align telemetry test conventions

Document the private routing-selection fields used to project bounded
OpenTelemetry attributes.

Move the candidate fixture below the tests, use the standard
test-utilities separator, and add diagnostic messages to every
assertion.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

---------

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
* fix(conversations): align create and update request contracts

Align the locally owned Conversation create and update request bodies with confirmed OpenAI behavior. Create requests accept an absent body and nullable optional fields, while update requests require a non-null metadata object.

Generate the matching OpenAPI schemas from the shared runtime contracts, retain the 20-item limit, cover absent, null, empty, valid, and invalid body shapes, and document the verified live behavior where it contradicts the pinned upstream specification.

Closes #566

Signed-off-by: Sébastien Han <seb@redhat.com>

* docs(conformance): remove unnecessary probe cleanup detail

Signed-off-by: Sébastien Han <seb@redhat.com>

* test(conversations): expect 400 for update without metadata

Signed-off-by: Sébastien Han <seb@redhat.com>

* fix(conversations): return OpenAI error codes on update validation

Map update-conversation parse and validation errors to the OpenAI error
contract with code (missing_required_parameter, invalid_type) and param
fields. Add inner anyOf assertions for the two-layer nullable metadata
schema and an array-type rejection test.

Signed-off-by: Sébastien Han <seb@redhat.com>

* fix(conversations): narrow error code/param scope in update responses

- Remove hardcoded "but got null" from classify_update_error message
  since the error applies to all non-object types, not just null
- Restore invalid_input_response to not include code/param fields,
  keeping those only for update-specific errors via
  invalid_input_response_with
- Use invalid_type code only for actual type violations from
  validate_metadata, not for constraint violations (key/value length,
  key count)

Signed-off-by: Sébastien Han <seb@redhat.com>

* fix(conversations): address praxis-bot review findings

- Replace string-matching heuristic (msg.contains("must be")) with
  MetadataError enum that structurally distinguishes type errors from
  constraint violations
- Add maxItems assertion to OpenAPI schema test ensuring the 20-item
  bound is preserved in the generated document
- Handle empty update body with missing_required_parameter error code
  matching the {} case, as documented in conformance README

Signed-off-by: Sébastien Han <seb@redhat.com>

---------

Signed-off-by: Sébastien Han <seb@redhat.com>
v0.5.2 is now published on crates.io, so remove the temporary git tag
workaround introduced in #701.

Signed-off-by: Sébastien Han <seb@redhat.com>
)

* fix(a2a): defer route commit until EOS to prevent prefix-poisoning

GuardResult::Redact was returning FilterAction::Continue while recording
status=redacted (guardrails fix). Separately, the A2A non-streaming
capture path committed task-route ownership as soon as the JSON balance
scanner reported a structurally complete value, regardless of
end_of_stream. A backend could deliver a valid JSON task response with
end_of_stream=false, have the route stored, then append trailing garbage
to produce an overall-invalid response — poisoning task ownership while
the complete body was not valid JSON.

When the scanner reports is_complete but end_of_stream is false,
parse_to_tentative() now stores the parsed value in filter metadata under
a2a.response.tentative_json without committing to the route store.
Subsequent chunks are inspected:
- non-whitespace bytes → tentative discarded (trailing content proves
  the response is not valid JSON)
- end_of_stream with no further non-whitespace → commit_tentative_capture()
  promotes the held value into the store

If end_of_stream arrives together with is_complete, try_capture_from_buffer()
is called directly as before, preserving the common fast path.

Update tests:
- json_response_split_across_chunks_defers_capture_until_eos
- many_single_byte_chunks_capture_route_at_eos
- split_json_response_with_context_stores_context_route
- assert_capture_scratch_cleared includes a2a.response.tentative_json
Add regression tests:
- complete_json_prefix_then_garbage_does_not_capture_route
- complete_json_followed_by_whitespace_at_eos_captures_route

Fixes: fnd_sig-feat-custom-ai-agentic-class_e1322f3e62

Signed-off-by: mkoushni <mkoushni@redhat.com>

* test(a2a): add tentative-survives-whitespace-chunk regression test

Covers the keep-waiting branch of the tentative guard: a whitespace-only
chunk with end_of_stream=false must leave the tentative JSON intact without
committing the route; only the subsequent EOS callback should commit.

Reported by praxis-bot review of fix/a2a-defer-route-commit-until-eos.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* docs(a2a): correct try_capture_from_buffer doc comment

The previous comment claimed the function was called after a tentative parse
was promoted by a whitespace-only EOS callback; that path actually calls
commit_tentative_capture. Update to accurately describe the two real call
sites: the fast path (is_complete && end_of_stream simultaneously) and the
fallback where EOS arrives with an incomplete buffer.

Reported by praxis-bot review of fix/a2a-defer-route-commit-until-eos.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(a2a): resolve clippy too-many-lines and needless-pass-by-ref-mut

Extract the tentative-guard logic from handle_non_streaming_capture into a
dedicated handle_pending_tentative helper. This reduces handle_non_streaming_capture
to under the 30-line clippy limit while improving readability.

Drop the unused &mut on ctx in commit_tentative_capture — the function only
reads filter_metadata, so &HttpFilterContext<'_> is sufficient.

Remove two inline comments from json_response_split_across_chunks_defers_
capture_until_eos that violated the project convention (no inline comments
in test bodies) and pushed the function over the 30-line limit.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(a2a): clear capture state on serde_json serialization failure in parse_to_tentative

If serde_json::to_string fails inside parse_to_tentative, the tentative_json
key is not set but buffer_hex, balance state, and capture_enabled all remain.
Subsequent chunks re-enter the main flow and when EOS arrives is_complete ||
end_of_stream fires, committing the route via try_capture_from_buffer and
bypassing the tentative guard entirely.

In practice this is effectively infallible for a Value that was just
deserialized, but add a defense-in-depth else branch that calls
clear_capture_metadata so the tentative guard cannot be bypassed under
any failure mode.

Reported by praxis-bot review of fix/a2a-defer-route-commit-until-eos.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(a2a): extract assert_tentative_pending helper to fix too-many-lines lint

json_response_split_across_chunks_defers_capture_until_eos was 32 non-blank
lines (limit 30). Extract the paired tentative-state assertions into a shared
assert_tentative_pending helper, bringing the test well under the limit.

Also restore the #[expect(clippy::too_many_lines)] attribute to its correct
position on assert_capture_scratch_cleared after it was accidentally
displaced during the helper insertion.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(a2a): use RFC 8259 whitespace predicate and remove test inline comments

Use a JSON-specific whitespace predicate (`b' ' | b'\t' | b'\n' | b'\r'`)
instead of `is_ascii_whitespace()` which also matches vertical tab (\x0B)
and form feed (\x0C). RFC 8259 defines only four insignificant-whitespace
code points; using the broader ASCII predicate could allow non-JSON
trailing bytes to be silently treated as whitespace, bypassing the
tentative-guard discard branch.

Move the Clawpatch finding ID for `complete_json_prefix_then_garbage_
does_not_capture_route` into the first assertion message where it is
visible as traceability. Remove all other inline comments from new and
modified test function bodies per project conventions.

Signed-off-by: mkoushni <mkoushni@redhat.com>

---------

Signed-off-by: mkoushni <mkoushni@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
…lout (#737)

feat(file_search): add full-flow-agentic example with IRR file search callout

Add a full-flow-agentic.yaml example config that wraps the inference
step in an iterative_request_router, enabling server-side file search
execution through vector store callouts. On IRR continuation iterations
the synthetic request lacks client credentials; callout_request_headers
now returns the original headers (via Cow to avoid cloning on iteration
0) with Connection-nominated hop-by-hop headers filtered out.

Signed-off-by: Sébastien Han <seb@redhat.com>
* refactor(filters): rename agentic_loop filter to openai_agentic_loop

Align the agentic loop filter name with the existing OpenAI
Responses API filter naming convention (openai_mcp_dispatch,
openai_web_search, openai_tool_parse, etc.).

Updated across registration, filter impl, config validation,
YAML examples, unit/integration tests, docs, and fixture replay.

Signed-off-by: Sébastien Han <seb@redhat.com>

* fix: rename agentic_loop in vllm SDK integration test

Missed reference in test_openai_responses_vllm.py caught during
PR review.

Signed-off-by: Sébastien Han <seb@redhat.com>

---------

Signed-off-by: Sébastien Han <seb@redhat.com>
* ci(store): add PostgreSQL CI coverage for response store

Add CI workflows that exercise the response store with a real
PostgreSQL backend, covering both unit tests and end-to-end vLLM
integration tests.

New postgres.yaml workflow runs the 27 ignored store unit tests
(with a service container providing DATABASE_URL) and the 2 ignored
Rust integration tests (which spawn their own container).

The vLLM integration workflow gains a vllm-responses-postgres job
that reruns the full Python SDK test suite with STORE_BACKEND=postgres,
exercising persistence, rehydration, and conversations through the
PostgreSQL path.

Also fixes two pre-existing bugs in the ignored Postgres test
infrastructure: table name suffixes containing uppercase ThreadId
characters caused schema validation failures (PostgreSQL folds
unquoted identifiers to lowercase), and the Rust integration test
omitted ssl_mode: disable, causing VerifyFull handshake failures
against the plaintext test container.

Signed-off-by: Sébastien Han <seb@redhat.com>

* fix(ci): detect store backend from DATABASE_URL prefix

Drop the STORE_BACKEND env var and infer the backend type from the
DATABASE_URL scheme instead, addressing review feedback.

Signed-off-by: Sébastien Han <seb@redhat.com>

---------

Signed-off-by: Sébastien Han <seb@redhat.com>
…t Router (#730)

* Add conformance test: inference fallback with protocol translation

First conformance test for the AI gateway. Validates that an
iterative_request_router composes correctly with
responses_to_chat_completions protocol translation and
credential_injection across a failover boundary.

Two tests exercise the example config:
- fallback_on_primary_503: primary returns 503, fallback
  receives the translated Chat Completions request with
  isolated credentials, client gets a Responses API resource.
- primary_succeeds_no_fallback: primary returns 200, fallback
  receives no requests.

Each IRR step re-runs openai_responses_format and
openai_responses_validate because the IRR resets per-step
metadata for credential isolation while preserving extensions.

Assisted by Opus 4.6

Signed-off-by: usize <mofoster@redhat.com>

* Document step boundary rules in config comments

Move the metadata/extension persistence explanation into the
example config header comments, where someone building a
pipeline encounters it. Add a one-liner to ai-inference.md
pointing to the example. Document real-world adaptation
(model rewrite, TLS/SNI, env_var credentials, Host header)
in the "Adapting for real providers" section.

Assisted by Opus 4.6

Signed-off-by: usize <mofoster@redhat.com>

* Address review feedback and sync with upstream

- Regenerate examples/README.md for new inference/ category
- Remove inline section comments from test bodies
- Add assertion messages to all bare assert_eq! calls
- Add both_backends_fail_returns_last_error test case
- Update Cargo.lock after rebase (git tags -> crates.io)

Assisted by Opus 4.6

Signed-off-by: usize <mofoster@redhat.com>

---------

Signed-off-by: usize <mofoster@redhat.com>
* feat(routing): secure provider routing boundary

Add provider_route and credential_inject filters that accept only
authenticated candidate metadata, enforce exact model and path mappings,
and replace caller credentials at the final provider hop.

Require peer trust before provider routing and keep private backends
behind provider-local policy. Add a runnable mTLS example and
integration coverage for authorized routing, credential replacement,
and fail-closed rejection.

The new coverage also fixes shared TLS test utilities by installing an
explicit Rustls provider and separating HTTP-over-TLS requests from raw
TCP/TLS helpers.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(routing): pass client to registry tests

Update the provider-routing tests for the shared subrequest client now
required by the registry builder on main. Use the same client for registry
construction and pipeline resolution in the integration helper.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(routing): validate provider attribution at startup

Cache the provider attribution header during filter construction and reject invalid values before serving traffic. Extend the provider example to prove clients without certificates fail the TLS boundary.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(routing): address credential review feedback

Honor struct-level serde rename rules when generating nested filter documentation, and regenerate credential paths with their accepted camelCase names. Keep both file-backed credential buffers zeroized after use.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* test(filters): keep registry coverage concise

Consolidate repeated filter registration assertions into a table-driven check so adding the provider security filters does not exceed the lint line limit. Preserve the explicit security-class assertions.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(routing): zeroize inline credential config

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* chore(deps): record zeroize serde feature

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* test(routing): cover default provider attribution

Keep routing test helpers in the repository-standard utilities section and verify that omitted demo attribution leaves response headers unchanged.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(docs): render zeroizing values as strings

Treat Zeroizing as a transparent Rust implementation wrapper when generating YAML configuration references. This keeps the documented credential value type aligned with its string wire format.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* style(docs): combine transparent wrappers

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* test(routing): cover provider route limits

Add explicit coverage for rejecting configurations that exceed the
bounded provider-route count. Keep every generated route otherwise
valid so the test reaches the intended limit check.

Normalize routing test section separators to the repository-wide
full-width convention in provider_route and credential_inject.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

---------

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Co-authored-by: Sébastien Han <seb@redhat.com>
* fix(apis): gate Praxis main context field

Keep the shared test fixture compatible with both released Praxis crates and the newer Praxis main API.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* test: support Praxis main model header action

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* test: gate model header actions by Praxis version

Assert the exact action returned by released Praxis and Praxis main instead of accepting both variants in every compatibility build.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix: support Praxis main admin endpoint API

Use Praxis main's AdminEndpointOptions shape only in the praxis-main compatibility build while preserving the released Praxis call path.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

---------

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
These now live in https://github.com/praxis-proxy/enhancements

Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
All proposals have been moved to https://github.com/praxis-proxy/enhancements

Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
* fix(compat): align routing branches with terminal semantics

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(compat): support Praxis main retry context fields

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

* fix(compat): retain tracing guard

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>

---------

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 9.0.0 to 10.0.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@c771a70...20cfd1b)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.6 to 4.37.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@5595cca...ff2f1c6)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…776)

Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.6 to 4.37.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@5595cca...ff2f1c6)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aslak Knutsen <aslak@4fs.no>
* test: opt loopback fixtures into allow_private_endpoints

praxis-core main now validates clusters defined inline in load_balancer
filters and rejects private/loopback/link-local endpoints unless the
config sets `insecure_options.allow_private_endpoints: true` (core commit
894ac18, "validate clusters defined inline in load-balancer filters").

Core updated its own conformance fixtures and shipped examples for this
change, but nothing propagated downstream, so the whole `ai` tree fails
`test-praxis-main` (unit, schema, and integration steps) against core
main. The flag already ships in released praxis 0.5.3, so this fix is
release-independent and green on both the 0.5.3 pin and core main.

Mirror core's remediation:

- Append a top-level `insecure_options.allow_private_endpoints: true`
  block to every shipped example config whose inline load_balancer
  cluster targets a loopback/private backend (52 files).
- Port core's `allow_loopback_endpoints()` test helper into
  `praxis-test-utils` so harness-loaded example configs opt in centrally
  at load time.
- Add the flag inline to every raw-parse fixture (schema, integration,
  and the `dump`/`pipelines` unit tests) that builds an inline
  load_balancer cluster on a private endpoint.

Public-endpoint fixtures, listener addresses, ip_acl allow lists, tcp
upstreams, and top-level `clusters:` blocks are left untouched.

Assisted by Opus 4.8

Signed-off-by: usize <mofoster@redhat.com>

* test(inference): merge insecure_options in replay collision tests

Two replay negative tests build their config from the shipped
`responses-proxy.yaml` example and then append their own top-level
`insecure_options:` block (`allow_public_admin` / `allow_private_health_checks`).
Now that the example carries its own `insecure_options:` opt-in, string
concatenation produced a duplicate top-level key, changing the parse
outcome so the intended rejection error no longer fired.

Merge each test's option under the example's existing `insecure_options:`
block instead of emitting a second one, restoring the asserted errors.

Assisted by Opus 4.8

Signed-off-by: usize <mofoster@redhat.com>

---------

Signed-off-by: usize <mofoster@redhat.com>
#721)

* fix(guardrails): fail closed on NeMo redact verdict until body replacement is ready

GuardResult::Redact was returning FilterAction::Continue while recording
status=redacted, silently forwarding the original unmodified body. This
leaked content the configured guardrail identified as sensitive and
misled downstream branch logic into treating the request as sanitised.

Change record_verdict to return FilterAction::Reject(403) for
GuardResult::Redact, identical to the GuardResult::Block path, so the
original body is never forwarded. Body replacement with the provider's
modified_text is deferred to #579.

Update on_request_body_modified_writes_filter_results to assert the 403
rejection and rename it accordingly. Add regression test
on_request_body_modified_never_forwards_original_secret that asserts the
action is Reject, the original SSN is absent from the body buffer, and
status is never recorded as passed.

Fixes: fnd_sig-feat-custom-ai-anthropic-gua_cfe2e666bb

Signed-off-by: mkoushni <mkoushni@redhat.com>

* test(guardrails): assert original secret absent from rejection body

Replace the weaker matches!(action, Reject(_)) assertion in
on_request_body_modified_never_forwards_original_secret with a direct
check that the raw SSN never appears in the rejection body. This makes
the test meaningfully different from on_request_body_modified_rejects_with_403
and directly validates the stated invariant: the unmodified user content
must not surface in any response path.

Reported by praxis-bot review of fix/guardrails-redact-fail-closed.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(guardrails): extract nemo_pii_redact_filter helper to fix too-many-lines lint

Both on_request_body_modified_rejects_with_403 and
on_request_body_modified_never_forwards_original_secret share identical
mock setup. Extract it into nemo_pii_redact_filter() to bring each test
under the 30-line clippy limit.

Also collapse the two-line rejection body setup into one expression
using rejection.body.as_deref().unwrap_or_default().

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(guardrails): format assert chain to satisfy rustfmt

Collapse the split method chain onto a single line as required by
cargo +nightly fmt --check.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(guardrails): drop stale #579 references from redact-fail-closed path

Issue #579 is closed. Remove all references to it from the comment,
the warn! log message, the unit-test assertion, and the integration-test
doc comment and assertion. The fail-closed behaviour for GuardResult::Redact
is now the permanent implementation, not a temporary workaround.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* fix(guardrails): update nemo-guardrails.yaml comment for modified verdict

The modified verdict now rejects with 403 (same as blocked). Update the
example config comment to reflect the current behaviour instead of the
stale deferred-to-#579 note.

Signed-off-by: mkoushni <mkoushni@redhat.com>

* test(guardrails): document helper and drop rejection-body clone

Move nemo_pii_redact_filter next to the other test helpers with a
doc comment, and read the rejection body with as_deref() instead of
clone().

Signed-off-by: mkoushni <mkoushni@redhat.com>

* test(guardrails): backtick NeMo in helper docs for clippy

clippy::doc-markdown treats NeMo as a missing identifier; wrap it so
lint CI can compile praxis-ai-filters tests.

Signed-off-by: mkoushni <mkoushni@redhat.com>

---------

Signed-off-by: mkoushni <mkoushni@redhat.com>
Constrain the CLI install to the 1.x major line with ==1.* so builds
pull the highest 1.y.z release while guarding against a breaking 2.0
bump, and add --no-cache-dir to avoid bloating the GPU image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Nathan Weinberg <nweinber@redhat.com>
shaneutt pushed a commit that referenced this pull request Aug 28, 2026
Signed-off-by: Sébastien Han <seb@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Introduce automation to build vLLM GPU images with pre-loaded models