feat(m7.2-B): migrate API handlers to per-tenant database pool #87

Closed
sharang wants to merge 1 commits from feat/m7.2b-handlers-tenant-scoped into feat/m7.2a-tenant-db-pool
Owner

Summary

Builds on PR M7.2-A (#86). Every HTTP handler in compliance-agent/src/api/ now takes a TenantCtx extractor and pulls a tenant-scoped Database from agent.db_pool.for_tenant(&ctx). Query bodies are unchanged — db.findings().find(doc! {...}) reads from the tenant's own physical database, so the filter doc cannot leak data across tenants because the wrong tenant's data is on a different db handle.

Targets feat/m7.2a-tenant-db-pool — stacks on top of #86 so the diff shown is only B's changes. When #86 merges, this PR will auto-retarget to main.

Foundation

  • dto::tenant_db(&agent, &tenant) -> Result<Database, StatusCode> — every migrated handler calls it at the top of the body instead of let db = &agent.db;. 500 on the rare pool failure; 4xx auth failures are already handled by the M7.1 status gate upstream.
  • api::server::inject_dev_tenant middleware — mounted only when Keycloak is NOT configured. Synthesises a TenantContext with tenant_id = $DEV_TENANT_ID (default dev) so cargo run against a bare Mongo + no KC still serves the API. Logged loudly as "DO NOT use in any environment with real customer data".
  • Test harness: TestServer mounts inject_dev_tenant so existing E2E tests reach handlers; cleanup() now drops every <db_name>_* per-tenant database, not just the legacy <db_name>.

Files migrated

File Sites Notes
chat.rs 3 RagPipeline + EmbeddingStore now wire to tenant DB's inner() so vector search is per-tenant
dast.rs 5
findings.rs 5
graph.rs 7 GraphStore inside trigger_build's spawn rewires to tenant DB
health.rs 1 stats_overview migrated; public /health stays unscoped (correct — it's in PUBLIC_ENDPOINTS)
issues.rs 1
notifications.rs 5
pentest_handlers/session.rs 12 Both wizard + legacy paths + pause/resume/stop/etc. PentestOrchestrator gets the tenant DB clone in spawn
pentest_handlers/export.rs 1 Fans out across many collections from a single tenant_db acquisition
pentest_handlers/stats.rs 1
pentest_handlers/stream.rs 1 SSE verifies session via tenant DB before subscribing
repos.rs 6
sbom.rs 5
scans.rs 1

help_chat.rs has no DB queries and was skipped.

Grep proof:

$ grep -rn "agent\.db\." compliance-agent/src/api/handlers/ | wc -l
0

Test plan

  • cargo fmt --all -- --check clean
  • cargo clippy --workspace --exclude compliance-dashboard -- -D warnings clean
  • cargo test -p compliance-core --lib — 7 pass
  • cargo test -p compliance-agent --lib — 228 pass
  • cargo test -p compliance-agent --test tenant_isolation — 5 pass (driver-level isolation still holds post-handler migration)
  • cargo test -p compliance-agent --test tenant_status_middleware — 6 pass

What's NOT yet migrated

These run as the agent's service account (no JWT-derived tenant), and need a different shape — they take a tenant_id at job-construction time. PR-C does this work.

  • scheduler.rs (6 sites)
  • pipeline/orchestrator.rs (14)
  • pentest/orchestrator.rs (13)
  • webhooks/{gitea,github,gitlab}.rs — webhooks resolve tenant from the incoming repo's owner
  • trackers/jira.rs
  • pipeline/dedup.rs, pipeline/graph_build.rs, pipeline/issue_creation.rs, pipeline/sbom/mod.rs
  • pentest/context.rs, pentest/report/html/*
  • llm/triage.rs

agent.db is still in the ComplianceAgent struct as a transitional handle for those paths. PR-D removes it once PR-C migrates them, and also adds the cross-tenant admin helpers (list-all flows).

🤖 Generated with Claude Code

## Summary Builds on **PR M7.2-A** (#86). Every HTTP handler in `compliance-agent/src/api/` now takes a `TenantCtx` extractor and pulls a tenant-scoped `Database` from `agent.db_pool.for_tenant(&ctx)`. Query bodies are unchanged — `db.findings().find(doc! {...})` reads from the tenant's own physical database, so the filter doc cannot leak data across tenants because the wrong tenant's data is on a different db handle. **Targets `feat/m7.2a-tenant-db-pool`** — stacks on top of #86 so the diff shown is only B's changes. When #86 merges, this PR will auto-retarget to `main`. ## Foundation - **`dto::tenant_db(&agent, &tenant) -> Result<Database, StatusCode>`** — every migrated handler calls it at the top of the body instead of `let db = &agent.db;`. 500 on the rare pool failure; 4xx auth failures are already handled by the M7.1 status gate upstream. - **`api::server::inject_dev_tenant` middleware** — mounted only when Keycloak is NOT configured. Synthesises a `TenantContext` with `tenant_id = $DEV_TENANT_ID` (default `dev`) so `cargo run` against a bare Mongo + no KC still serves the API. Logged loudly as *"DO NOT use in any environment with real customer data"*. - **Test harness**: `TestServer` mounts `inject_dev_tenant` so existing E2E tests reach handlers; `cleanup()` now drops every `<db_name>_*` per-tenant database, not just the legacy `<db_name>`. ## Files migrated | File | Sites | Notes | |---|---|---| | `chat.rs` | 3 | RagPipeline + EmbeddingStore now wire to tenant DB's `inner()` so vector search is per-tenant | | `dast.rs` | 5 | | | `findings.rs` | 5 | | | `graph.rs` | 7 | GraphStore inside `trigger_build`'s spawn rewires to tenant DB | | `health.rs` | 1 | `stats_overview` migrated; public `/health` stays unscoped (correct — it's in `PUBLIC_ENDPOINTS`) | | `issues.rs` | 1 | | | `notifications.rs` | 5 | | | `pentest_handlers/session.rs` | 12 | Both wizard + legacy paths + pause/resume/stop/etc. PentestOrchestrator gets the tenant DB clone in spawn | | `pentest_handlers/export.rs` | 1 | Fans out across many collections from a single `tenant_db` acquisition | | `pentest_handlers/stats.rs` | 1 | | | `pentest_handlers/stream.rs` | 1 | SSE verifies session via tenant DB before subscribing | | `repos.rs` | 6 | | | `sbom.rs` | 5 | | | `scans.rs` | 1 | | `help_chat.rs` has no DB queries and was skipped. Grep proof: ``` $ grep -rn "agent\.db\." compliance-agent/src/api/handlers/ | wc -l 0 ``` ## Test plan - [x] `cargo fmt --all -- --check` clean - [x] `cargo clippy --workspace --exclude compliance-dashboard -- -D warnings` clean - [x] `cargo test -p compliance-core --lib` — 7 pass - [x] `cargo test -p compliance-agent --lib` — 228 pass - [x] `cargo test -p compliance-agent --test tenant_isolation` — 5 pass *(driver-level isolation still holds post-handler migration)* - [x] `cargo test -p compliance-agent --test tenant_status_middleware` — 6 pass ## What's NOT yet migrated These run as the agent's service account (no JWT-derived tenant), and need a different shape — they take a `tenant_id` at job-construction time. **PR-C** does this work. - `scheduler.rs` (6 sites) - `pipeline/orchestrator.rs` (14) - `pentest/orchestrator.rs` (13) - `webhooks/{gitea,github,gitlab}.rs` — webhooks resolve tenant from the incoming repo's owner - `trackers/jira.rs` - `pipeline/dedup.rs`, `pipeline/graph_build.rs`, `pipeline/issue_creation.rs`, `pipeline/sbom/mod.rs` - `pentest/context.rs`, `pentest/report/html/*` - `llm/triage.rs` `agent.db` is still in the `ComplianceAgent` struct as a transitional handle for those paths. **PR-D** removes it once PR-C migrates them, and also adds the cross-tenant admin helpers (list-all flows). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sharang added 1 commit 2026-06-17 11:29:14 +00:00
feat(m7.2-B): migrate API handlers to per-tenant database pool
CI / Check (pull_request) Successful in 8m9s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
cdfbb62f9d
Builds on PR M7.2-A. Every HTTP handler in compliance-agent/src/api/
now takes a TenantCtx extractor and pulls a tenant-scoped Database
from agent.db_pool.for_tenant(&ctx). The query bodies are unchanged —
`db.findings().find(doc! {...})` reads from the tenant's own physical
database, so the filter doc cannot leak data across tenants because
the wrong tenant's data is literally on a different db handle.

Changes
- New `dto::tenant_db(&agent, &tenant) -> Result<Database, StatusCode>`
  helper. Every migrated handler calls it at the top of the body
  instead of `let db = &agent.db;`. 500 on the rare pool failure;
  4xx auth failures are already handled by the M7.1 status gate.
- New `api::server::inject_dev_tenant` middleware mounted only when
  Keycloak is NOT configured. Synthesizes a TenantContext with
  tenant_id = $DEV_TENANT_ID (default `dev`) so `cargo run` against
  a bare Mongo + no KC still serves the API. Logged loudly as
  "DO NOT use in any environment with real customer data".
- Test harness: TestServer mounts inject_dev_tenant so existing E2E
  tests reach handlers; cleanup() now drops every <db_name>_*
  per-tenant database, not just the legacy <db_name>.

Files migrated (handler count, all pass `cargo build`):
- chat.rs (3) — also rewires RagPipeline + EmbeddingStore to the
  tenant DB's inner() so vector search is per-tenant
- dast.rs (5)
- findings.rs (5)
- graph.rs (7) — also rewires GraphStore inside trigger_build's
  spawn to the tenant DB
- health.rs (1) — stats_overview migrated; public /health stays
  un-scoped
- issues.rs (1)
- notifications.rs (5)
- pentest_handlers/session.rs (12) — both wizard + legacy paths,
  plus pause/resume/stop/get_attack_chain/get_messages/
  get_session_findings/lookup_repo. PentestOrchestrator now gets
  the tenant DB clone in its spawn.
- pentest_handlers/export.rs (1) — fans out across sessions,
  attack_chain_nodes, dast_findings, findings, sbom_entries,
  graph_nodes from a single tenant_db acquisition
- pentest_handlers/stats.rs (1)
- pentest_handlers/stream.rs (1) — SSE handler verifies session
  via the tenant DB before subscribing
- repos.rs (6)
- sbom.rs (5)
- scans.rs (1)

help_chat.rs has no DB queries and was skipped.

Test plan
- cargo fmt --all clean
- cargo clippy --workspace --exclude compliance-dashboard
  -- -D warnings clean
- cargo test -p compliance-core --lib — 7 pass
- cargo test -p compliance-agent --lib — 228 pass
- cargo test -p compliance-agent --test tenant_isolation — 5 pass
  (driver-level isolation still holds post-handler migration)
- cargo test -p compliance-agent --test tenant_status_middleware
  — 6 pass

What's not yet migrated (PR-C / PR-D)
- scheduler.rs (6 sites), pipeline/orchestrator.rs (14),
  pentest/orchestrator.rs (13), webhooks (gitea/github/gitlab),
  trackers/jira.rs, pipeline/dedup.rs etc. — background paths
  without a JWT-derived tenant context.
- agent.db is still in the ComplianceAgent struct as a transitional
  handle for those paths. PR-D removes it once PR-C migrates the
  background paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Author
Owner

Superseded — the M7.2 stack was inadvertently included in PR #90 squash-merge (5648291) on main. The dashboard PR was branched off this PR's descendant and its full diff swept into main as one squash commit. M7.2-A through M7.2-D are all live on main and in production. Closing without merging.

Superseded — the M7.2 stack was inadvertently included in PR #90 squash-merge (`5648291`) on main. The dashboard PR was branched off this PR's descendant and its full diff swept into main as one squash commit. M7.2-A through M7.2-D are all live on main and in production. Closing without merging.
sharang closed this pull request 2026-06-18 09:32:03 +00:00
CI / Check (pull_request) Successful in 8m9s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sharang/compliance-scanner-agent#87