Skip to content

Add source-backed AI Hub workspace surface - #301

Merged
seonghobae merged 3 commits into
masterfrom
feature/ai-hub-source-surface-20260529
May 29, 2026
Merged

Add source-backed AI Hub workspace surface#301
seonghobae merged 3 commits into
masterfrom
feature/ai-hub-source-surface-20260529

Conversation

@seonghobae

@seonghobae seonghobae commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add signed GET /api/ai-hub/surface backed by prompt templates, organization LLM provider metadata, and durable provider audit events.
  • Replace static AI Hub fixture cards with API-wired Prompt Studio, Workflow, AI Agent, Evaluation, and Run History tabs.
  • Add backend, frontend, and Playwright coverage for signed bearer headers, no public identity headers, responsive scroll, and mobile hamburger sanity.

Verification

  • PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q from backend/ → 510 passed, 13 skipped
  • PYTHONDONTWRITEBYTECODE=1 python3 -m bandit -r backend/ -x backend/tests/ -q → passed
  • npm run lint → passed
  • npm run typecheck → passed
  • npm test → 103 passed
  • env -u FORCE_COLOR -u NO_COLOR PLAYWRIGHT_PORT=18130 npm run test:e2e -- --project=desktop tests/e2e/ai-hub-source-surface.spec.ts tests/e2e/mobile-hamburger.spec.ts → 4 passed
  • git diff --check → passed

Browser screenshots inspected

  • frontend/test-results/ai-hub-source-surface-rend-39496--Hub-with-scroll-at-desktop-desktop/ai-hub-desktop-scrolled.png
  • frontend/test-results/ai-hub-source-surface-rend-32190-I-Hub-with-scroll-at-tablet-desktop/ai-hub-tablet-scrolled.png
  • frontend/test-results/ai-hub-source-surface-rend-73ede-I-Hub-with-scroll-at-mobile-desktop/ai-hub-mobile-scrolled.png
  • frontend/test-results/ai-hub-source-surface-rend-73ede-I-Hub-with-scroll-at-mobile-desktop/ai-hub-mobile-hamburger.png
  • frontend/test-results/mobile-hamburger-Mobile-Wo-024c1-gles-and-displays-correctly-desktop/mobile-hamburger-open-scrolled.png

Summary by CodeRabbit

  • New Features

    • AI Hub now renders a source-backed surface from a signed backend API, showing real prompts, workflows, agents, evaluation metrics, and run history with opaque stable card keys; manual refresh, loading, error/retry states included.
    • Role-based visibility: provider/agent metadata redacted for non-admins; secrets not exposed.
  • Tests

    • Expanded backend/unit/integration and Postgres smoke tests for signed evidence, auth rules, redaction, and rejection of unsupported session headers.
    • Added Playwright e2e coverage across viewports.
  • Documentation

    • Added spec/plan for the source-backed AI Hub surface.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 955eedf9-2ce2-4f19-97c0-c0a7452f1ef1

📥 Commits

Reviewing files that changed from the base of the PR and between c36fde8 and 76acea4.

📒 Files selected for processing (1)
  • backend/tests/test_ai_hub_api.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/test_ai_hub_api.py

📝 Walkthrough

Walkthrough

This PR introduces a signed, source-backed GET /api/ai-hub/surface endpoint and replaces the AI Hub UI with a data-driven, refreshable frontend that fetches summary, prompt/workflow/agent cards, evaluation metrics, and run events derived from PromptTemplate, LLMProvider, and SecurityAuditEvent rows. Tests cover signed-session auth, header exclusion, role-based visibility, and a Postgres smoke path.

Changes

AI Hub Source-Backed Surface

Layer / File(s) Summary
Policy and response contracts
AGENTS.md, backend/api/ai_hub.py
Adds governance text requiring signed backend evidence and defines the ai-hub router plus Pydantic response models (summary, prompt/workflow/agent cards, evaluation metrics, run events).
Backend mapping and scoring
backend/api/ai_hub.py
Helpers generate opaque stable keys, map DB entities into cards/events (prompt-derived workflows, provider/agent state), and compute readiness/evaluation metrics from aggregated counts.
DB listings and /surface endpoint
backend/api/ai_hub.py
Async listing helpers query user-owned prompts, org providers (admin-only), and scoped llm_provider audit events with actor filtering; endpoint composes cards, run events, summaries, and returns AiHubSurfaceResponse.
App wiring and test scaffolding
backend/main.py, backend/tests/test_ai_hub_api.py
Registers ai_hub router in the FastAPI app; test scaffolding includes HMAC-signed session token helpers, in-memory mock DB session, deterministic factories, and a helper to perform requests with an overridden HMAC secret and mocked DB; includes Postgres smoke wiring.
Backend auth and Postgres tests
backend/tests/test_ai_hub_api.py
Positive test validates signed-session response fields and secret redaction; role-based test verifies member-scoped provider hiding; negative tests assert 401 for missing/unsupported signed sessions; Postgres smoke test inserts real rows and validates signed-scope response.
Frontend AI Hub component rewrite
frontend/src/components/AIHubLayout.tsx
Transforms static mock UI into a data-driven component: defines response types and helpers, fetches GET /api/ai-hub/surface with bearer token, manages loading/error/ready states and refresh, and renders tabbed panels for prompts, workflows, agents, evaluation, and runs.
Frontend unit tests and helpers
frontend/src/app/ai-hub/page.test.tsx
Adds icon mocks, forbidden identity header constants, click helper, fetch mocking asserting Authorization header and absence of public identity headers, loading/error state and retry assertions, and improved cleanup.
E2E tests and mock surface
frontend/tests/e2e/ai-hub-source-surface.spec.ts, frontend/tests/e2e/helpers.ts
Playwright suite covers desktop/tablet/mobile viewports, asserts the fetch uses the signed bearer token and omits public identity headers, checks core UI visibility/scrollability, captures screenshots, and supplies mock aiHubSurface + GET handler.
Implementation plan
docs/plans/2026-05-29-ai-hub-source-backed-surface.md
Documents the verified gap and implemented slice: signed surface API, evidence-backed UI assembly, opaque keys, header behavior, verification checklist, and roadmap items for tenant/workspace scoping and evaluation/run history persistence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Seongho-Bae/naruon#293: Introduces SecurityAuditEvent persistence and LLM provider audit logging, which this surface aggregates for run-event evidence.
  • Seongho-Bae/naruon#164: Adds the PromptTemplate model and prompt CRUD surface used by the AI Hub surface endpoint.

Poem

I’m a rabbit with a signed-session key,
I hop through prompts and audit trees,
Cards that show the honest trail, not lore,
Refresh, retry, and headers kept off shore,
🐇✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a source-backed AI Hub workspace surface endpoint to replace static fixtures with API-wired UI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ai-hub-source-surface-20260529

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/tests/test_ai_hub_api.py (1)

185-233: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add a real PostgreSQL smoke test for /api/ai-hub/surface.

This slice is DB-affecting but currently covered only with MockSession. Please add one Postgres-backed bootstrap/smoke test before merge evidence is treated as complete.

As per coding guidelines: "DB-affecting API slices need both mocked fast tests and a real PostgreSQL bootstrap/smoke path before PR merge evidence is considered complete."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_ai_hub_api.py` around lines 185 - 233, Add a
PostgreSQL-backed smoke test for the /api/ai-hub/surface endpoint instead of
only using MockSession: create a new test (e.g.,
test_ai_hub_surface_postgres_smoke) that uses the real get_db dependency
(override get_db only to yield a real async DB session connected to the test
Postgres instance), run any required bootstrap/seed routines to insert the
minimal prompts/providers/audit_events rows (reuse the same helper factories
used by MockSession if available), call the endpoint via TestClient and assert
the same critical response fields (status 200 and presence/absence checks done
in test_ai_hub_surface_uses_signed_source_evidence), and ensure you clear
app.dependency_overrides and teardown DB state after the test so it does not
leak to other tests.
🧹 Nitpick comments (1)
backend/api/ai_hub.py (1)

270-271: ⚡ Quick win

Make ordering deterministic for stable payload rendering.

Sorting only by timestamp can reorder cards/events nondeterministically when timestamps tie. Add a secondary stable sort key.

♻️ Proposed change
-        .order_by(desc(PromptTemplate.updated_at))
+        .order_by(desc(PromptTemplate.updated_at), desc(PromptTemplate.id))
         .limit(8)

-        .order_by(desc(LLMProvider.updated_at))
+        .order_by(desc(LLMProvider.updated_at), desc(LLMProvider.id))
         .limit(8)

-        .order_by(desc(SecurityAuditEvent.observed_at))
+        .order_by(
+            desc(SecurityAuditEvent.observed_at),
+            desc(SecurityAuditEvent.event_uid),
+        )
         .limit(8)

Also applies to: 285-286, 304-305

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/api/ai_hub.py` around lines 270 - 271, The order_by calls that
currently use only desc(PromptTemplate.updated_at) (seen in the .order_by(...)
before .limit(8)) are non-deterministic when timestamps tie; update each such
.order_by call (including the other occurrences around PromptTemplate.updated_at
at the mentioned locations) to include a secondary stable key like
PromptTemplate.id (e.g., add PromptTemplate.id.asc() as a tie-breaker) so
ordering becomes deterministic; ensure you apply the same change to every
similar query in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/api/ai_hub.py`:
- Around line 276-288: The _list_providers function returns provider metadata
for any authenticated user; add an authorization guard that checks AuthContext
for allowed roles (e.g., admin or provider-registry role) before querying
LLMProvider: inspect auth_context (fields like role, is_admin, or has_role) and
if the caller is not in the permitted roles, either return an empty list or
raise an appropriate authorization error, otherwise proceed with the existing
select(...) query and return statement; update the function to perform this role
check early and reference _list_providers, AuthContext, and LLMProvider when
making the change.
- Around line 264-269: The query currently allows PromptTemplate.is_shared to be
true without scoping to the tenant, so shared prompts can leak across tenants;
update the where clause that uses PromptTemplate.is_shared (near the
.where(or_(...)) block) to require the shared row also matches the request's
tenant/workspace (e.g. add PromptTemplate.organization_id ==
auth_context.organization_id or PromptTemplate.workspace_id ==
auth_context.workspace_id as part of the OR condition for shared rows), so only
prompts that are either owned by auth_context.user_id OR are shared within the
same tenant/workspace are returned.

In `@frontend/src/app/ai-hub/page.test.tsx`:
- Around line 147-149: The test's header omission assertion can false-pass due
to case-sensitivity and only checks one header name; update the assertion that
inspects fetchMock.mock.calls[0] (firstFetchCall) headers to normalize header
names (e.g., lower-case keys) or explicitly check for both 'X-User-Id' and
'x-user-id' and any other forbidden identity headers so the test fails if any
case-variant or additional identity header is present; locate the usage of
fetchMock, firstFetchCall and the headers access in page.test.tsx and replace
the single JSON string containment check with a case-insensitive key presence
check or explicit checks for all forbidden header names.

---

Outside diff comments:
In `@backend/tests/test_ai_hub_api.py`:
- Around line 185-233: Add a PostgreSQL-backed smoke test for the
/api/ai-hub/surface endpoint instead of only using MockSession: create a new
test (e.g., test_ai_hub_surface_postgres_smoke) that uses the real get_db
dependency (override get_db only to yield a real async DB session connected to
the test Postgres instance), run any required bootstrap/seed routines to insert
the minimal prompts/providers/audit_events rows (reuse the same helper factories
used by MockSession if available), call the endpoint via TestClient and assert
the same critical response fields (status 200 and presence/absence checks done
in test_ai_hub_surface_uses_signed_source_evidence), and ensure you clear
app.dependency_overrides and teardown DB state after the test so it does not
leak to other tests.

---

Nitpick comments:
In `@backend/api/ai_hub.py`:
- Around line 270-271: The order_by calls that currently use only
desc(PromptTemplate.updated_at) (seen in the .order_by(...) before .limit(8))
are non-deterministic when timestamps tie; update each such .order_by call
(including the other occurrences around PromptTemplate.updated_at at the
mentioned locations) to include a secondary stable key like PromptTemplate.id
(e.g., add PromptTemplate.id.asc() as a tie-breaker) so ordering becomes
deterministic; ensure you apply the same change to every similar query in this
file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 606b37b9-15b0-46d3-b544-e6851e66741c

📥 Commits

Reviewing files that changed from the base of the PR and between 39e52ea and ecca55b.

📒 Files selected for processing (9)
  • AGENTS.md
  • backend/api/ai_hub.py
  • backend/main.py
  • backend/tests/test_ai_hub_api.py
  • docs/plans/2026-05-29-ai-hub-source-backed-surface.md
  • frontend/src/app/ai-hub/page.test.tsx
  • frontend/src/components/AIHubLayout.tsx
  • frontend/tests/e2e/ai-hub-source-surface.spec.ts
  • frontend/tests/e2e/helpers.ts

Comment thread backend/api/ai_hub.py Outdated
Comment thread backend/api/ai_hub.py
Comment thread frontend/src/app/ai-hub/page.test.tsx
@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for 76acea4e8c163dc3ea5036a8990eccd3861288fa:

  • Review decision is CHANGES_REQUESTED; address requested changes before merge.
  • Current-head CodeRabbit review comment has blocking warning/failure evidence on 76acea4.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/tests/test_ai_hub_api.py (1)

168-188: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve existing dependency overrides in this helper.

app.dependency_overrides.clear() wipes unrelated overrides from outer fixtures, so this helper can make adjacent tests order-dependent. Snapshot and restore the original mapping here the same way the Postgres smoke path does.

Suggested fix
 def _request_with_signed_session(
     db_session: MockSession,
     **payload_overrides: object,
 ):
     previous_secret = settings.AUTH_SESSION_HMAC_SECRET
+    original_overrides = dict(app.dependency_overrides)

     async def scoped_db():
         yield db_session

     settings.AUTH_SESSION_HMAC_SECRET = SecretStr(TEST_SESSION_HMAC_SECRET)
     app.dependency_overrides[get_db] = scoped_db
@@
     finally:
         settings.AUTH_SESSION_HMAC_SECRET = previous_secret
         app.dependency_overrides.clear()
+        app.dependency_overrides.update(original_overrides)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_ai_hub_api.py` around lines 168 - 188, The helper
_request_with_signed_session currently clears app.dependency_overrides at the
end which removes unrelated overrides; instead snapshot the original mapping,
set app.dependency_overrides[get_db] = scoped_db for the test, and restore the
original mapping in the finally block; also preserve and restore
settings.AUTH_SESSION_HMAC_SECRET as already done — i.e., save
original_overrides = dict(app.dependency_overrides) before mutating, assign the
scoped override for get_db, and in finally restore app.dependency_overrides =
original_overrides to avoid breaking other tests.
🧹 Nitpick comments (1)
backend/tests/test_ai_hub_api.py (1)

168-180: ⚡ Quick win

Add a regression for unsupported JWT crit headers.

The new signed-session coverage never exercises the crit-header rejection boundary, and this helper currently makes that hard by hardcoding the JWT header. Please allow header overrides and add a case asserting /api/ai-hub/surface rejects a bearer token carrying unsupported crit values.

As per coding guidelines, "JWT/session verification must reject unsupported critical headers (crit) before trusting payload claims; do not rely only on library defaults for this boundary."

Also applies to: 191-253

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_ai_hub_api.py` around lines 168 - 180, The helper
_request_with_signed_session currently hardcodes JWT headers and thus cannot
exercise rejection of unsupported critical (`crit`) headers; modify
_request_with_signed_session to accept an optional headers_override argument
(pass-through to where _signed_session_token is called) so tests can inject a
JWT with a `crit` claim, ensure _signed_session_token/validation path uses that
header when building the token, and add a regression test that sends a bearer
token with an unsupported crit value to the /api/ai-hub/surface endpoint and
asserts it is rejected (unauthorized/400) to confirm JWT/session verification
rejects unsupported critical headers before trusting payload claims.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/tests/test_ai_hub_api.py`:
- Around line 258-264: The test function
test_ai_hub_surface_postgres_smoke_uses_signed_scope currently calls
create_async_engine(settings.DATABASE_URL, ...) before any guard, which will
hard-fail when DATABASE_URL is unset; modify the test to either short-circuit
with pytest.skip when settings.DATABASE_URL is falsy or move the
create_async_engine call into the try/guard block so engine is only constructed
after verifying the URL, and use pytest.skip(...) with a clear message if
settings.DATABASE_URL is missing.

---

Outside diff comments:
In `@backend/tests/test_ai_hub_api.py`:
- Around line 168-188: The helper _request_with_signed_session currently clears
app.dependency_overrides at the end which removes unrelated overrides; instead
snapshot the original mapping, set app.dependency_overrides[get_db] = scoped_db
for the test, and restore the original mapping in the finally block; also
preserve and restore settings.AUTH_SESSION_HMAC_SECRET as already done — i.e.,
save original_overrides = dict(app.dependency_overrides) before mutating, assign
the scoped override for get_db, and in finally restore app.dependency_overrides
= original_overrides to avoid breaking other tests.

---

Nitpick comments:
In `@backend/tests/test_ai_hub_api.py`:
- Around line 168-180: The helper _request_with_signed_session currently
hardcodes JWT headers and thus cannot exercise rejection of unsupported critical
(`crit`) headers; modify _request_with_signed_session to accept an optional
headers_override argument (pass-through to where _signed_session_token is
called) so tests can inject a JWT with a `crit` claim, ensure
_signed_session_token/validation path uses that header when building the token,
and add a regression test that sends a bearer token with an unsupported crit
value to the /api/ai-hub/surface endpoint and asserts it is rejected
(unauthorized/400) to confirm JWT/session verification rejects unsupported
critical headers before trusting payload claims.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8ddcc8be-ef3c-4021-9755-dc8743d35111

📥 Commits

Reviewing files that changed from the base of the PR and between ecca55b and c36fde8.

📒 Files selected for processing (4)
  • backend/api/ai_hub.py
  • backend/tests/test_ai_hub_api.py
  • docs/plans/2026-05-29-ai-hub-source-backed-surface.md
  • frontend/src/app/ai-hub/page.test.tsx
✅ Files skipped from review due to trivial changes (1)
  • docs/plans/2026-05-29-ai-hub-source-backed-surface.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/app/ai-hub/page.test.tsx
  • backend/api/ai_hub.py

Comment thread backend/tests/test_ai_hub_api.py
@seonghobae
seonghobae temporarily deployed to feature/ai-hub-source-surface-20260529 - naruon PR #301 May 29, 2026 10:35 — with Render Destroyed
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