Skip to content

Wave 3b: auth refactor + trust boundary + trace classification - #10

Merged
RobbieJ merged 7 commits into
red-hat-v2.1.0from
chore/upstream-wave-3b-2026-05-08
May 8, 2026
Merged

RobbieJ merged 7 commits into
red-hat-v2.1.0from
chore/upstream-wave-3b-2026-05-08

Conversation

@RobbieJ

@RobbieJ RobbieJ commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Second step of Wave 3 — auth refactor + trust boundary + trace classification + provider lifecycle hooks. Six cherry-picks from upstream/develop applied as a single coherent stack. Plus one small follow-up test fix.

The original Wave 3b scope (NVIDIA-AI-Blueprints#194/NVIDIA-AI-Blueprints#195/NVIDIA-AI-Blueprints#196) was extended to pull NVIDIA-AI-Blueprints#199/NVIDIA-AI-Blueprints#203/NVIDIA-AI-Blueprints#206 forward because NVIDIA-AI-Blueprints#194 modifies functions and imports introduced by those three commits — applying NVIDIA-AI-Blueprints#194 alone would have required substantial hand-porting and deviated from upstream verbatim. Pulling them forward keeps every commit a clean upstream cherry-pick.

Upstream Cherry-pick Title
NVIDIA-AI-Blueprints#199 bfbb467 04e50f9 fix auth trust boundary and enforce async job ownership (PRELUDE)
NVIDIA-AI-Blueprints#203 86f075b ef6d25b Add request trace classification and pseudonymous ids (PRELUDE)
NVIDIA-AI-Blueprints#206 5dda80d f5c485d Propagate AIQ request tags to NAT spans (PRELUDE)
NVIDIA-AI-Blueprints#194 0d53241 2caa876 fix: auth refactor — eliminate refresh race, increase buffer, add error semantics
NVIDIA-AI-Blueprints#195 f2685c0 50c58f8 feat: provider lifecycle hooks for composable auth extensions
NVIDIA-AI-Blueprints#196 40b0737 72fa0e6 test: close remaining auth bug fix test coverage gaps
follow-up 2035504 fix(test): adapt test_runner + test_job_access for Wave 3b changes

What this PR does

Why pull NVIDIA-AI-Blueprints#199/NVIDIA-AI-Blueprints#203/NVIDIA-AI-Blueprints#206 forward into 3b?

Wave 3b's NVIDIA-AI-Blueprints#194 directly references functions and imports introduced by those three commits:

Per feedback_stay_in_sync_with_upstream.md ("prefer NVIDIA verbatim"), pulling forward keeps every commit upstream-verbatim instead of hand-porting. Wave 3c reduces to "residual" — any docs not yet pulled, plus future cleanup of the legacy decode_jwt_payload.

Skipped / deviated from upstream

Upstream change What we did Why
frontends/aiq_api/src/aiq_api/websocket_reconnect.py __init__ adds _user_interaction_response: asyncio.Future[TextContent] | None = None Dropped this line; PR #8 migrated to _user_interaction (UserInteraction wrapper); base class initializes it. Added one comment explaining the deviation One-line dead-state cleanup; PR #8's migration replaced all reads of this attribute
deploy/compose/init-db.sql Took upstream's full version (ALL tables: job_info, job_access, job_events, summaries, plus checkpoint tables) Per "prefer NVIDIA" plus plan §9 #2 default — undo the prior RH "app creates tables on demand" deviation
deploy/helm/helm-charts-k8s/aiq/files/init-db.sql (NEW) Re-added (RH had deleted at fork time) Same reason — adopt upstream's separate-file pattern. Note: deploy/helm/deployment-k8s/values.yaml still has the inline configMap (separate chart, separate concern, not in scope here)
frontends/aiq_api/src/aiq_api/jobs/submit.py Took upstream's owner-aware refactor; preserved our PR #8 nat.front_ends.fastapi.async_jobs.job_store import path Two changes operate on different lines; merge preserves both
frontends/aiq_api/src/aiq_api/routes/jobs.py Took upstream's submit_authorized_job refactor + owner enforcement Heavy hunk replacing a manual job_args build with a single helper call
tests/aiq_agent/jobs/test_runner.py::test_submit_agent_job_passes_data_sources Updated assertion to job_args[-2] auth_token is now the last positional arg in submit_job's job_args (NVIDIA-AI-Blueprints#199 added it after data_sources). Test mechanic, not behavior
frontends/aiq_api/tests/test_job_access.py::TestAuthorizeJobAccess Added autouse monkeypatch.setenv("REQUIRE_AUTH", "true") fixture Upstream test gap — class assumes the auth-enabled path but never sets the env var. authorize_job_access returns the job without ownership check when REQUIRE_AUTH is unset, causing DID NOT RAISE HTTPException. Fixture makes the assumption explicit
frontends/aiq_api/tests/test_auth.py::TestJWTValidatorGetSigningKey::test_matches_key_by_kid (already in 3a) Kept the longer comment block explaining the long-uptime fix Better signal for future maintainers

All other upstream lines applied verbatim per feedback_stay_in_sync_with_upstream.md.

Branding / vLLM exception scan

Verification

Static

  • uv lock --check — 366 packages, lockfile self-consistent
  • ruff check . — clean
  • pytest tests/ frontends/aiq_api/tests/1014 passed, 19 skipped, 5.41s (up 39 from Wave 3a baseline of 975)
  • cd frontends/ui && npm test1167 passed, 1 skipped (UI tests including new providers.spec.tsx, websocket-client.spec.ts, authenticated-fetch.spec.ts from test: close remaining auth bug fix test coverage gaps NVIDIA-AI-Blueprints/aiq#196)
  • cd frontends/ui && npm run type-check — clean

Broader smoke (per feedback_upstream_sync_planning.md)

Sync /chat/stream against NIM (Nemotron3-Nano-30B):

6 LLM events (nvidia/nemotron-3-nano-30b-a3b)
4 Tool events (web_search_tool start/end + tavily_search start/end)
8 Function start/end pairs (workflow → intent_classifier → shallow_research_agent → web_search_tool)

Async /v1/jobs/async/submit + /stream against NIM:

TERMINAL=success
Full event hierarchy: workflow.start/end, tool.start/end, llm.start/end, artifact.update

WebSocket /websocket against NIM:

18 system_intermediate_message + 2 system_response_message events

Auth allowlist (REQUIRE_AUTH=false):

/health → 200, /docs → 200, /redoc → 200, /openapi.json → 200

Auth gate (REQUIRE_AUTH=true, no validators registered):

Error: Error loading front end worker aiq_api.plugin.AIQAPIWorker:
REQUIRE_AUTH=true but no validators have been registered. Either call
aiq_api.plugin.register_validator() before starting the server, or declare
an 'aiq_api.validators' entry point in your package.

✅ Refuses to start (correct behavior — prevents footgun deploys).

Tuple-return + ownership smokes

Tuple-return JSON body shape ({detail, error}) on 401 is covered by 5+ direct unit tests in test_auth.py::TestMiddlewareErrorCodes and test_auth.py::TestValidateErrorCodes. End-to-end requires registering a real OIDC validator; deferred to deployment-level testing.

Ownership enforcement is covered by 7 tests in test_job_access.py::TestAuthorizeJobAccess (with REQUIRE_AUTH=true autouse fixture). End-to-end requires multi-user JWT setup; deferred to deployment-level testing.

What's NOT in this PR

🤖 Generated with Claude Code

cdgamarose-nv and others added 7 commits May 8, 2026 14:31
…ueprints#199)

* fix auth trust boundary and enforce async job ownership

* fix old tests

* share request identity resolution for HTTP and WebSockets

* remove log and fix no auth flow

* make rollback async

* skip ownership check when require auth is false
…prints#203)

* add request trace classification and pseudonymous ids

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* harden trace user tagging guards

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* document trace env examples

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* extract auth trace helpers into utils

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* restrict access channel overrides

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

---------

Signed-off-by: Ajay Thorve <athorve@nvidia.com>
* fix aiq request tags on nat spans

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* docs: clarify nat span request tagging

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* fix websocket request trace tags

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

* generalize request trace access channels

Signed-off-by: Ajay Thorve <athorve@nvidia.com>

---------

Signed-off-by: Ajay Thorve <athorve@nvidia.com>
…or semantics (NVIDIA-AI-Blueprints#194)

* fix: eliminate dual token refresh race and increase refresh buffer

Two independent refresh mechanisms (SessionProvider.refetchInterval and a setInterval in useAuth) fired concurrently every ~4 minutes, causing "invalid_grant" failures with OAuth providers that use rotating refresh tokens (e.g., NVIDIA Starfleet SSO). The second concurrent refresh consumed an already-invalidated token and killed the session.

Remove the duplicate setInterval from useAuth — session refresh is now handled solely by SessionProvider's refetchInterval. Make the interval config-driven (from TOKEN_REFRESH_BUFFER_SECONDS) instead of hardcoded.

Also increase the default TOKEN_REFRESH_BUFFER_MINUTES from 5 to 15. The previous 5-minute window was insufficient for deployments running long operations (deep research with ECI runs 20-40+ minutes). Enterprise
deployments should set TOKEN_REFRESH_BUFFER_MINUTES=30 via env var.

Includes a regression test that verifies useAuth never creates its own setInterval, preventing re-introduction of the race condition.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-AI-Blueprints#195)

* feat: add provider lifecycle hooks for composable auth extensions

Add onSignIn and onSession lifecycle hooks to AuthProviderConfig,
enabling auth providers to inject custom behavior (e.g. DL group gating,
custom claims) without replacing the entire config.ts file.

This reduces the overlay surface area for internal deployments — the
Starfleet overlay can now provide hooks via its provider file instead of
replacing config.ts and session.ts wholesale. The overlay goes from
4 replaced files to 2 (types.ts + providers/index.ts).

New AuthProviderConfig fields:
- onSignIn: async hook called after OAuth callback, return value merged
  into JWT (e.g. check group membership, add custom claims)
- onSession: sync hook called on every session check, return value
  merged into session (e.g. surface hasAccess, groupName to client)
- tokenRefreshBufferSeconds: provider-level override for the refresh
  buffer (takes precedence over TOKEN_REFRESH_BUFFER_MINUTES env var)
- requiredEnvVars: additional env vars checked by validateAuthEnv()

All hooks are optional — existing providers work unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve strict TypeScript errors in lifecycle hook code

- Spread NextAuth Account/User/JWT objects before passing to hooks so
  they satisfy Record<string, unknown> (interface types lack index sigs)
- Cast session callback results through unknown for Record access in
  tests (Session | DefaultSession lacks provider-specific fields)
- Fix AdapterUser requiring email field in test mocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: match all auth error codes in frontend RUM tracking

Addresses Greptile P1: the WebSocket error handler only checked for
message === "auth_error" but the backend now sends exc.error_code
("token_expired", "token_invalid"). Also applies severity split —
expected auth events route to RUM actions, unexpected to RUM errors.

- rum.ts: add trackRumAction, trackAuthEvent with expected/unexpected
  routing via EXPECTED_AUTH_CODES set
- websocket-client: match against full set of auth error codes
- authenticated-fetch: use trackAuthEvent for severity routing
- session: use trackRumAction for expected refresh failures

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: preserve core auth fields in provider hooks

* fix: address auth provider hook review comments

* fix: guard auth provider sign-in hook

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ajay Thorve <athorve@nvidia.com>
…prints#196)

* test: add missing auth phase-5 regression coverage

Cover remaining auth test gaps by validating HTTP/WS observability paths, SessionProvider refresh wiring, and websocket auth error propagation behavior.
Two upstream test gaps caught locally:

1. tests/aiq_agent/jobs/test_runner.py — auth_token is now the last
   positional arg in submit_job's job_args list (NVIDIA-AI-Blueprints#199 added it after
   data_sources). Update test to assert job_args[-2] == data_sources
   instead of [-1].

2. frontends/aiq_api/tests/test_job_access.py::TestAuthorizeJobAccess —
   authorize_job_access only enforces ownership when REQUIRE_AUTH=true,
   but the test class never set that env var. Add an autouse
   monkeypatch.setenv fixture to the class so the cross-user-denied and
   missing-access-row tests exercise the auth-enabled branch they
   expect.

Both are upstream issues caught by our long-running smoke; not
production-code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@RobbieJ
RobbieJ merged commit e21f7e2 into red-hat-v2.1.0 May 8, 2026
@RobbieJ
RobbieJ deleted the chore/upstream-wave-3b-2026-05-08 branch May 8, 2026 13:42
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.

4 participants