Skip to content

Ai analytics#1502

Open
aadesh18 wants to merge 113 commits into
devfrom
ai-analytics
Open

Ai analytics#1502
aadesh18 wants to merge 113 commits into
devfrom
ai-analytics

Conversation

@aadesh18
Copy link
Copy Markdown
Collaborator

@aadesh18 aadesh18 commented May 27, 2026

Summary

This PR ships AI analytics on top of the existing internal MCP Review tool: a new Unified AI Endpoint Analytics tab over the AI proxy/query path, plus the supporting backend telemetry (ai-proxy-handlers, ai-query-handlers, SpacetimeDB log sync, OpenRouter usage attribution, MCP-call / Q&A reducers). It also reworks several MCP Review components and tightens the in-product AI dashboard builder (vibe-coding chat + sandbox host) with tests around the new handlers and SpacetimeDB client.

Base: dev → Head: ai-analytics · 104 files changed · +7,874 / -1,312

What this PR actually adds vs. what was already there

The MCP Review tool itself (Call Logs / Knowledge Base / Analytics tabs) shipped in #1321 ("LLM MCP Flow") — that's the dev baseline shown in the before-shots. This PR layers on top of that:

Pre-existing on dev (from #1321) New in this PR
MCP Review tool app + MCP Review Tool shell Tabs renamed: Call Logs → MCP Review, Analytics → Unified AI Endpoint Analytics
Analytics.tsx — MCP-only QA-score / flag-types dashboard Usage.tsx + UsageDetail.tsx — full Unified AI Endpoint Analytics surface (new file)
CallLogList.tsx, CallLogDetail.tsx, ConversationReplay.tsx, KnowledgeBase.tsx, AddManualQa.tsx All five reworked (auth, schema, polish)
mcp_call_log SpacetimeDB table (public: true) mcp_call_log reshaped (sharding, indices, optional fields) + brand-new ai_query_log table + reducers (log_mcp_call_reducer, update_mcp_qa_review_reducer)
New backend: ai-proxy-handlers.ts, ai-query-handlers.ts, ai-proxy-logger.ts, mcp-call-logger.ts (+ tests), QA pipeline (qa-reviewer, reviewer-auth, verified-qa), models/prompts/tools
Dashboard: rewrites of dashboards/[id]/page-client.tsx, vibe-coding/*, assistant-ui/thread.tsx (Grok model swap from #1476 + sandbox host fixes)

Screenshots are captured against a live local dev env (NEXT_PUBLIC_STACK_PORT_PREFIX=82) with SpacetimeDB bindings published per branch — dev's mcp_call_log-only schema for the before-pass, then this PR's schema (mcp_call_log + ai_query_log) for the after-pass — so the table-renders below are real, not stubs.

MCP Review home (was: "Call Logs")

Tab rename + new empty-state messaging. Same data on both sides (zero logged MCP calls in this seed), but the dev shell is just a filter + table, whereas this PR adds the KPI strip, calls-over-time chart, QA score distribution, top flag types, response-time card, and tool-usage card on top — all visible even with zero calls.

Before (dev) After (this PR)
internal-home-before internal-home-after

Unified AI Endpoint Analytics — populated

The headline new surface — Usage.tsx + UsageDetail.tsx did not exist on dev. The before-pass shows dev's old "Analytics" tab, which was scoped to MCP QA review stats. The after-pass shows this PR's tab populated with 11 live AI calls fired through the dashboard's vibe-coding chat during this session.

Before (dev: MCP "Analytics" tab) After (Unified AI Endpoint Analytics, 11 live calls)
internal-analytics-before internal-analytics-after

What's wired on the after side:

  • Filters: range (24h / 7d / 30d / all), mode (all / stream / generate), auth (all / authed / anon), status (all / ok / error), free-text search, system-prompt chip row (command-center-ask-ai, create-dashboard, docs-ask-ai, email-assistant family, email-wysiwyg-editor, rewrite-template-source, run-query, wysiwyg-edit), and a model chip (x-ai/grok-build-0.1).
  • KPI strip: total calls (11), errors, input/output tokens, cache hit %, total cost, cache savings, avg + p95 duration (6,019 / 6,036ms). All live aggregates from ai_query_log via SpacetimeDB subscriptions.
  • Breakdowns: calls over time, token volume (input + output), cached vs fresh, cache hit % by system prompt, by-system-prompt + by-model rollups, tool usage (update-dashboard, patch-dashboard, sql-query), latency histogram.
  • Paginated calls table CALLS (11) with sortable columns: time, system prompt, model, mode, in/out tokens, cache read/write, $ cost, duration, status.

Knowledge Base — populated

Q&A management surface. Backed by mcp_call_log.qa_* and the new update_mcp_qa_review_reducer. Below: seeded with 2 published entries + 1 draft via "+ Add Q&A". The structural diff is the schema rework (sharding, indexes, optional fields), not so much new chrome here.

Before (dev) After (this PR, 2 published + 1 draft)
internal-kb-before internal-kb-after

Each row carries status (Draft / Published), source (manual), timestamp, author, and per-row Publish / Unpublish / Edit / Delete actions. The "+ Add Q&A" modal:

internal-kb-add-modal

Dashboard view — vibe-coding chat + sandbox host

apps/dashboard/.../dashboards/[dashboardId]/page-client.tsx, the vibe-coding/ chat pieces, and the dashboard sandbox host all change here. The chat now drives a live IDE-style preview while a layout is being assembled, with chat history + Generate / Stop controls.

Empty state — dark Empty state — light
dashboard-after-dark dashboard-after-light
Active chat — multiple user turns Mid-generation: sandbox host streaming dashboard.tsx
dashboard-chat-active dashboard-ai-generating
dev baseline (empty state on dev looks similar — the meaningful diff is the sandbox host wiring + the Grok model swap from #1476)

dashboard-before-dark

Notes for reviewers

  • All 11 calls in the analytics shot show error. That's a model-side issue (Grok build-0.1 default in dev-forwarding mode rejects this PR's tool-call schema), not a logging bug — the proxy still attributes, times, and records every call, which is exactly the property the new dashboard is meant to expose. With a working tool-call response you'd see non-zero Output Tokens + Total Cost.
  • Capture method: before-pass on origin/dev with pnpm --filter @stackframe/internal-tool spacetime:publish:local so dev's mcp_call_log schema is live (otherwise the page hangs on "Connecting to SpacetimeDB…"); then back to ai-analytics, re-publish (this DESTROYS the dev database — --delete-data=on-conflict), and re-fire calls. The dev-side data shown is purely the chrome / empty state, since dev's schema is what gets wiped each cycle.
  • MCP Review home still shows 0 calls because dashboard chats hit the AI query/proxy path, not the MCP server. Populating that surface would need a real MCP client; the visible diff is the new KPI scaffold around the (currently empty) call list.
  • /questions route exists but trips a separate Postgres probe on this local DB seed — not regressed, just not seeded. Not screenshotted.

Test plan

  • pnpm typecheck clean
  • pnpm lint clean
  • Backend test suite passes (new tests around ai-proxy-handlers, ai-query-handlers, spacetimedb-client, spacetimedb-bindings-sync, tools/index, ai-proxy-logger)
  • Internal-tool: MCP Review tab renders KPI scaffold + empty call list (no "Connecting to SpacetimeDB…" stall) on a seeded local env
  • Internal-tool: Unified AI Endpoint Analytics renders KPIs + charts under realistic call volume; filter changes (range, mode, auth, status, system-prompt) refetch correctly
  • Internal-tool: Knowledge Base "+ Add Q&A" → publishes a draft → reviewer mark/unmark/retry flow
  • Dashboard: vibe-coding chat → Generate → sandbox host renders + persists; delete flow works

Summary by cubic

Adds Unified AI Endpoint Analytics to the internal MCP Review tool and logs all ai-proxy and ai-query traffic to SpacetimeDB for live KPIs, breakdowns, and call-level detail. Also upgrades the dashboard builder chat with patch-based edits, streaming, and stability fixes.

  • New Features

    • New Unified AI Endpoint Analytics tab (Usage.tsx, UsageDetail.tsx) with filters, KPIs, charts, and a paginated calls table sourced from ai_query_log.
    • Full telemetry pipeline: ai-proxy-handlers, ai-query-handlers, and loggers for proxy, query, and MCP tool calls; OpenRouter usage and cost attribution.
    • SpacetimeDB updates: private mcp_call_log reshaped (shard, indices), new ai_query_log, reviewer reducers, and public views (my_visible_*, published_qa).
    • Reviewer workflow: auth gate via isAiChatReviewer, reviewer enroll endpoint, retry/unmark/update QA routes, and Knowledge Base CRUD.
    • Dashboard builder: context chips, patchDashboard alongside updateDashboard, streaming generation, improved sandbox host, and Grok model wiring.
    • Tests: unit tests for handlers/loggers/bindings sync and e2e coverage for SpacetimeDB RLS, projections, reducer auth, and internal endpoints.
  • Migration

    • Env changes: rename STACK_SPACETIMEDB_URISTACK_SPACETIMEDB_URL; add STACK_SPACETIMEDB_SERVICE_TOKEN. Provision a local token via POST /v1/identity and set it.
    • Re-publish the SpacetimeDB module to apply the new schema; internal-tool pre-dev script now auto-publishes and helps provision the service token.
    • Grant reviewer access by setting client_read_only_metadata.isAiChatReviewer = true for approved users (seed sets it for the default user).

Written for commit df8df02. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dashboard patch editing for precise, surgical code changes
    • Added AI query analytics dashboard with usage metrics, trends, and cost tracking
    • Enhanced error handling with chip-based error UI in dashboard editor
    • Improved public Q&A visibility with dedicated published questions view
  • Bug Fixes

    • Image attachment validation now returns structured error codes with specific limits

Review Change Stack

mantrakp04 and others added 30 commits March 23, 2026 10:48
- Added new internal API endpoint for documentation tools, allowing actions such as listing available docs, searching, and fetching specific documentation by ID.
- Updated environment configuration to support optional internal secret for enhanced security.
- Refactored existing search functionality to utilize the new docs tools API instead of the previous MCP server.
- Improved error handling and response parsing for documentation-related requests.
- Expanded documentation to clarify the relationship between the new tools and existing API functionalities.

This update streamlines the documentation access process and enhances the overall developer experience.
- Introduced error capturing for failed HTTP requests in the docs tools API, improving debugging capabilities.
- Updated the API response for unsupported methods to include an 'Allow' header, clarifying the expected request type.

These changes enhance the robustness of the documentation tools integration and improve developer experience.
- Updated the key name in the capabilities section of the API documentation to follow a consistent naming convention, improving clarity and maintainability.
The .gitmodules was updated in d22593d to point at
apps/backend/src/private/implementation, but the gitlink entry (mode
160000) was never added to the tree. This caused
`git clone --recurse-submodules` to silently skip the private submodule.

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

- Added `STACK_DOCS_INTERNAL_BASE_URL` to backend `.env` and `.env.development` files for AI tool bundle configuration.
- Removed references to `STACK_INTERNAL_DOCS_TOOLS_SECRET` from backend and docs environment files and validation logic from the docs tools API route.
- Introduced a new `.env` file for the docs app with essential configuration variables.
aadesh18 and others added 19 commits May 11, 2026 13:20
…minal callbacks

- Added a new test file for `handleStreamMode` that verifies logging behavior for various terminal callback scenarios.
- Introduced a guard in `handleStreamMode` to ensure that logging occurs at most once per request lifecycle, addressing the issue of double-logging when both `onError` and `onAbort` are triggered.
- Tests include cases for single callbacks and multiple callbacks firing in rapid succession, ensuring correct logging behavior under all conditions.
…rift test

The server schema added `qa_entries.requestId` (4th column) and
`add_manual_qa.requestId` (6th arg) on May 11 but the auto-generated client
bindings were last regenerated on May 5 and never picked up the new field.
Because BSATN is positional, every column after position 3 in
`my_visible_qa_entries` would deserialize one slot off (question bytes read as
answer, etc.) once the new schema is published — corrupting the Knowledge Base
view in the internal-tool.

Add `requestId` to `my_visible_qa_entries_table.ts`, `types.ts:QaEntries`, and
`add_manual_qa_reducer.ts` in the same positions the codegen would produce,
plus a structural test that parses both the server schema and the generated
bindings and asserts field/arg orders match. The test fails on the prior
state with the exact missing-`requestId` diff and passes after the patch,
catching any future drift in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Introduced unit tests for `observeAndLog` in `ai-proxy-handlers.test.ts` to ensure correct behavior when logging functions throw errors.
- Added tests for `callSql` in `spacetimedb-client.test.ts` to verify 401 enrollment retry logic and error handling.
- Created tests for `buildProxyLogRow` in `ai-proxy-logger.test.ts` to validate tool-name extraction from parsed logs.
- Implemented validation tests for tool names in `index.test.ts` to ensure consistency with defined tool names.

These additions enhance test coverage and reliability of the AI-related functionalities.
…r and update envOrDevDefault calls for consistency
Copilot AI review requested due to automatic review settings May 27, 2026 20:16
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-auth-internal-tool Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-auth-mcp Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-auth-skills Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-backend Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-dashboard Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-demo Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-docs Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-preview-backend Ready Ready Preview, Comment May 27, 2026 8:24pm
stack-preview-dashboard Ready Ready Preview, Comment May 27, 2026 8:24pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

Moves AI query/proxy to shared handlers with structured errors and robust logging, introduces SpacetimeDB HTTP client and expanded schema/reducers, reworks MCP review endpoints and auth, adds dashboard chip/patch tooling, updates internal tool with enrollment/usage views, and adds extensive tests.

Changes

Unified AI, SpacetimeDB, and Dashboard updates

Layer / File(s) Summary
AI, SpacetimeDB, dashboard, and tests end-to-end Refactors AI endpoints/proxy, adds logging modules and OpenRouter usage parsing, replaces direct bindings with SpacetimeDB HTTP client plus expanded schema/reducers, reworks internal-tool enrollment/usage UIs, adds dashboard chip/patch flows, and comprehensive unit/E2E tests.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • hexclave/stack-auth#1339 — Prior refactor of AI query route and AI proxy routing aligns with shared handler delegation here.
  • hexclave/stack-auth#1456 — Client-side image handling complements new structured image-attachment errors added in this PR.

Suggested reviewers

  • N2D4
  • mantrakp04
  • nams1570

Poem

A rabbit taps the keys with cheer,
Logs and streams grow crystal-clear.
Chips hop in, patches stitch,
Spacetime hums without a glitch.
Dashboards bloom, reviews enroll—
Thump-thump! We’ve shipped a whole. 🐇✨

✨ 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 ai-analytics

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds AI analytics and SpacetimeDB-backed telemetry for AI proxy/query usage, while reworking MCP Review authorization, Q&A workflows, and dashboard vibe-coding tools.

Changes:

  • Adds AI query/proxy logging, OpenRouter usage attribution, SpacetimeDB reducer/table bindings, and analytics UI.
  • Tightens MCP Review auth and Q&A CRUD flows with new reviewer enrollment and visibility tests.
  • Updates dashboard AI generation with cached prompt context, patch-based edits, and improved assistant composer/tool UI.

Reviewed changes

Copilot reviewed 104 out of 105 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/stack-shared/src/known-errors.tsx Adds structured image attachment validation errors.
packages/stack-shared/src/ai/image-limits.ts Returns typed image validation failure metadata.
apps/mcp/src/mcp-handler.ts Avoids emitting empty conversation IDs in MCP responses.
apps/internal-tool/src/types.ts Exports new SpacetimeDB row types.
apps/internal-tool/src/stack.ts Uses shared env fallback helper.
apps/internal-tool/src/lib/env.ts Adds strict prod/dev env helper.
apps/internal-tool/src/lib/mcp-review-api.ts Adds reviewer enrollment and expanded MCP review API methods.
apps/internal-tool/src/components/UsageDetail.tsx Adds AI usage detail drawer.
apps/internal-tool/src/components/ConversationReplay.tsx Exports reusable replay UI pieces.
apps/internal-tool/src/components/CallLogList.tsx Adds QA review-failed state and pagination.
apps/internal-tool/src/components/AddManualQa.tsx Adds request IDs for manual Q&A creation.
apps/internal-tool/src/app/questions/page.tsx Reads public Q&A from the published projection.
apps/internal-tool/scripts/pre-dev.mjs Publishes SpacetimeDB and provisions backend service token.
apps/internal-tool/.env Removes unused server secret placeholder.
apps/internal-tool/src/module_bindings/* Updates/generated SpacetimeDB table and reducer bindings.
apps/e2e/tests/spacetimedb/*.test.ts Adds SpacetimeDB auth, RLS, projection, and Q&A invariant tests.
apps/e2e/tests/backend/endpoints/api/v1/ai-query.test.ts Updates image error expectations.
apps/e2e/tests/backend/endpoints/api/latest/internal/*.test.ts Adds internal MCP review/enrollment endpoint auth tests.
apps/e2e/tests/backend/backend-helpers.ts Adds AI reviewer test helpers.
apps/dashboard/src/lib/ai-dashboard/shared-prompt.ts Reworks dashboard prompt context and caching.
apps/dashboard/src/components/vibe-coding/dashboard-tool-components.tsx Adds patch-dashboard tool UI and restore snapshots.
apps/dashboard/src/components/vibe-coding/assistant-chat.tsx Passes composer top content through.
apps/dashboard/src/components/commands/ask-ai.tsx Enables off-white light mode for AI preview thread.
apps/dashboard/src/components/assistant-ui/thread.tsx Adds composer top content and light-mode styling support.
apps/backend/src/lib/ai/types.ts Adds shared AI logging/types.
apps/backend/src/lib/ai/tools/sql-query.ts Adds unauthenticated SQL-query stub behavior.
apps/backend/src/lib/ai/tools/index.ts Adds patch-dashboard tool support and tool-name validation.
apps/backend/src/lib/ai/tools/index.test.ts Tests tool-name validation.
apps/backend/src/lib/ai/tools/create-dashboard.ts Adds patch-dashboard tool definition.
apps/backend/src/lib/ai/spacetimedb-client.ts Adds HTTP SpacetimeDB reducer/SQL client.
apps/backend/src/lib/ai/spacetimedb-client.test.ts Tests SpacetimeDB enrollment retry.
apps/backend/src/lib/ai/spacetimedb-bindings* Removes old backend WebSocket generated bindings.
apps/backend/src/lib/ai/spacetimedb-bindings-sync.test.ts Adds schema/binding drift tests.
apps/backend/src/lib/ai/qa/verified-qa.ts Reads verified Q&A via SpacetimeDB SQL.
apps/backend/src/lib/ai/qa/reviewer-auth.ts Centralizes MCP reviewer authorization.
apps/backend/src/lib/ai/qa/qa-reviewer.ts Reworks QA reviewer to HTTP reducers and stronger parsing.
apps/backend/src/lib/ai/openrouter-usage.ts Adds OpenRouter usage/cost extraction and refinement.
apps/backend/src/lib/ai/models.ts Exposes OpenRouter proxy base URL helper.
apps/backend/src/lib/ai/mcp-logger.ts Removes old WebSocket MCP logger.
apps/backend/src/lib/ai/loggers/*.ts Adds AI query/proxy/MCP call logging.
apps/backend/src/lib/ai/loggers/*.test.ts Tests proxy log row extraction/delivery.
apps/backend/src/lib/ai/ai-query-handlers.ts Splits stream/generate AI query handling and logging.
apps/backend/src/lib/ai/ai-query-handlers.test.ts Tests stream terminal callback de-duplication.
apps/backend/src/lib/ai/ai-proxy-handlers.ts Adds proxy body sanitization and logging observation.
apps/backend/src/lib/ai/ai-proxy-handlers.test.ts Tests proxy logging failure isolation.
apps/backend/src/app/api/latest/internal/spacetimedb-enroll-reviewer/route.ts Adds reviewer enrollment endpoint.
apps/backend/src/app/api/latest/internal/mcp-review/*/route.ts Reworks MCP review routes to strict reviewer auth and HTTP reducers.
apps/backend/src/app/api/latest/integrations/ai-proxy/[[...path]]/route.ts Wires proxy request logging.
apps/backend/src/app/api/latest/ai/query/[mode]/route.ts Wires unified query logging, image errors, and prompt caching.
apps/backend/prisma/seed.ts Marks seeded internal default user as AI reviewer.
apps/backend/.env.development Switches SpacetimeDB config to HTTP URL and service token.
apps/backend/.env Documents new SpacetimeDB HTTP/service-token env vars.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +35
const portPrefix = process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81";
const spacetimeHttpUrl = `http://127.0.0.1:${portPrefix}39`;
Comment on lines +13 to +18
export default __t.row({
identity: __t.identity().primaryKey(),
addedAt: __t.timestamp().name("added_at"),
stackUserId: __t.string().name("stack_user_id"),
displayName: __t.string().name("display_name"),
});
Comment on lines +122 to +129
__reducerSchema("remove_operator", RemoveOperatorReducer),
__reducerSchema("set_qa_published", SetQaPublishedReducer),
__reducerSchema("unmark_human_reviewed", UnmarkHumanReviewedReducer),
__reducerSchema("update_ai_query_cost", UpdateAiQueryCostReducer),
__reducerSchema("update_mcp_qa_review", UpdateMcpQaReviewReducer),
__reducerSchema("update_qa_entry", UpdateQaEntryReducer),
__reducerSchema("update_qa_entry_with_publish", UpdateQaEntryWithPublishReducer),
__reducerSchema("upsert_qa_from_call", UpsertQaFromCallReducer),
}

function truncateLargeToolResult(toolName: string, output: unknown): unknown {
const serialized = JSON.stringify(output);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 105 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/backend/.env.development">

<violation number="1" location="apps/backend/.env.development:127">
P2: Wrong port-prefix variable in comment. Should be `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX` to match the actual `STACK_SPACETIMEDB_URL` above and all other env vars in this file.</violation>
</file>

<file name="apps/backend/src/lib/ai/loggers/ai-query-logger.ts">

<violation number="1" location="apps/backend/src/lib/ai/loggers/ai-query-logger.ts:29">
P2: Guard the `JSON.stringify` result before reading `.length`; `undefined` outputs currently throw here and force `stepsJson` to fall back to `_serializationFailed`, dropping detailed tool-call logs.</violation>
</file>

<file name="apps/internal-tool/scripts/pre-dev.mjs">

<violation number="1" location="apps/internal-tool/scripts/pre-dev.mjs:34">
P2: Use the Hexclave-prefixed port override in this provisioning path as well; otherwise local setups that only set `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX` will probe the wrong SpacetimeDB port and fail service-token provisioning.</violation>
</file>

Partial review: This PR has more than 50 files, so cubic reviewed the highest-priority files first. During the trial, paid plans get a higher file limit.
You can try an ultrareview to bypass the file limit, comment @cubic-dev-ai ultrareview. Learn more.

Fix all with cubic | Re-trigger cubic

STACK_SPACETIMEDB_DB_NAME=stack-auth-llm
STACK_MCP_LOG_TOKEN=change-me

# To provision locally: `curl -X POST http://127.0.0.1:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39/v1/identity`
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 27, 2026

Choose a reason for hiding this comment

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

P2: Wrong port-prefix variable in comment. Should be NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX to match the actual STACK_SPACETIMEDB_URL above and all other env vars in this file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/.env.development, line 127:

<comment>Wrong port-prefix variable in comment. Should be `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX` to match the actual `STACK_SPACETIMEDB_URL` above and all other env vars in this file.</comment>

<file context>
@@ -120,10 +120,14 @@ STACK_QSTASH_CURRENT_SIGNING_KEY=sig_7kYjw48mhY7kAjqNGcy6cr29RJ6r
 STACK_SPACETIMEDB_DB_NAME=stack-auth-llm
 STACK_MCP_LOG_TOKEN=change-me
 
+# To provision locally: `curl -X POST http://127.0.0.1:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39/v1/identity`
+# then copy the `token` field from the response.
+STACK_SPACETIMEDB_SERVICE_TOKEN=
</file context>
Suggested change
# To provision locally: `curl -X POST http://127.0.0.1:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39/v1/identity`
# To provision locally: `curl -X POST http://127.0.0.1:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}39/v1/identity`
Fix with Cubic

}

function truncateLargeToolResult(toolName: string, output: unknown): unknown {
const serialized = JSON.stringify(output);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 27, 2026

Choose a reason for hiding this comment

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

P2: Guard the JSON.stringify result before reading .length; undefined outputs currently throw here and force stepsJson to fall back to _serializationFailed, dropping detailed tool-call logs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/lib/ai/loggers/ai-query-logger.ts, line 29:

<comment>Guard the `JSON.stringify` result before reading `.length`; `undefined` outputs currently throw here and force `stepsJson` to fall back to `_serializationFailed`, dropping detailed tool-call logs.</comment>

<file context>
@@ -0,0 +1,196 @@
+}
+
+function truncateLargeToolResult(toolName: string, output: unknown): unknown {
+  const serialized = JSON.stringify(output);
+  if (serialized.length <= MAX_TOOL_RESULT_CHARS) return output;
+  captureError(
</file context>
Fix with Cubic

await provisionServiceToken();

async function provisionServiceToken() {
const portPrefix = process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81";
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 27, 2026

Choose a reason for hiding this comment

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

P2: Use the Hexclave-prefixed port override in this provisioning path as well; otherwise local setups that only set NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX will probe the wrong SpacetimeDB port and fail service-token provisioning.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/internal-tool/scripts/pre-dev.mjs, line 34:

<comment>Use the Hexclave-prefixed port override in this provisioning path as well; otherwise local setups that only set `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX` will probe the wrong SpacetimeDB port and fail service-token provisioning.</comment>

<file context>
@@ -21,9 +24,95 @@ const publish = spawnSync("pnpm", ["spacetime:publish:local"], {
+await provisionServiceToken();
+
+async function provisionServiceToken() {
+  const portPrefix = process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81";
+  const spacetimeHttpUrl = `http://127.0.0.1:${portPrefix}39`;
+  const dbName = process.env.STACK_SPACETIMEDB_DB_NAME ?? "stack-auth-llm";
</file context>
Suggested change
const portPrefix = process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81";
const portPrefix =
process.env.NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX ??
process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ??
"81";
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (2)
apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx (1)

560-571: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Apply data-stack-no-widget consistently to all sandbox error fallbacks.

Nice improvement here, but the compile-error fallback (Dashboard failed to compile) still lacks the marker, so the overlay can still select error UI in that path.

Proposed fix
-          root.render(
-            <div className="p-6 text-red-500">
+          root.render(
+            <div className="p-6 text-red-500" data-stack-no-widget="true">
               <h2 className="text-xl font-bold mb-2">Dashboard failed to compile</h2>
               <pre className="text-sm bg-red-950/20 p-4 rounded overflow-auto whitespace-pre-wrap">
                 {message}
               </pre>
             </div>
           );

Also applies to: 663-669

🤖 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
`@apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx`
around lines 560 - 571, The compile-error fallback block that renders "Dashboard
failed to compile" is missing the data-stack-no-widget attribute; update the JSX
in dashboard-sandbox-host.tsx so the container element that displays the compile
error (the same element that contains the "Dashboard failed to compile" message
around lines ~663-669) includes data-stack-no-widget, and verify the other error
fallback(s) (e.g., the runtime error block that uses this.state.error) already
have it; ensure both error fallback containers (the compile error container and
the runtime error container rendered by the component's render method) include
data-stack-no-widget so the overlay cannot select them.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx (1)

158-160: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use performance.now() for the 2s dedupe window and replace destructive toast calls with blocking alert UI.

  • In handleDashboardRuntimeError, the elapsed-time dedupe uses Date.now(); use performance.now() instead.
  • This file surfaces blocking runtime/patch failures via toast({ variant: "destructive", ... }) (around lines 180 and 300) with no alert usage; switch those to the app’s alert pattern.
Proposed fix
-      const now = Date.now();
+      const now = performance.now();
🤖 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
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx
around lines 158 - 160, The dedupe timing in handleDashboardRuntimeError uses
Date.now() and should use performance.now() for high-resolution elapsed checks:
replace Date.now() calls and any stored lastErrorRef.current.at values with
performance.now() so the signature/time comparison uses the same high-resolution
clock. Also replace the non-blocking destructive toast calls that surface
runtime/patch failures (the toast({ variant: "destructive", ... }) usages in
handleDashboardRuntimeError and the patch-failure handler) with the app's
blocking alert UI pattern (use the project’s Alert/AlertDialog or showAlert
helper), preserving the title, description and any action buttons/links so users
see a modal/alert instead of a transient toast.
🟠 Major comments (20)
apps/backend/src/lib/ai/ai-proxy-handlers.ts-28-30 (1)

28-30: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unsupported/missing model IDs instead of silently defaulting.

Line 28 silently rewrites invalid input to a default model. This masks caller errors and violates fail-loud behavior.

Proposed fix
-  if (!parsed.model || !ALLOWED_MODEL_IDS.has(parsed.model)) {
-    parsed.model = OPENROUTER_DEFAULT_MODEL;
-  }
+  if (parsed.model == null) {
+    throw new StatusError(400, "Request body must include `model`");
+  }
+  if (typeof parsed.model !== "string" || !ALLOWED_MODEL_IDS.has(parsed.model)) {
+    throw new StatusError(400, "Unsupported `model`");
+  }

As per coding guidelines: "Never silently use fallback values without proper type checking" and "Fail early and fail loud with errors instead of silently continuing".

🤖 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 `@apps/backend/src/lib/ai/ai-proxy-handlers.ts` around lines 28 - 30, The code
currently overwrites invalid or missing parsed.model with
OPENROUTER_DEFAULT_MODEL, which hides caller errors; instead, change the
validation in ai-proxy-handlers.ts so that if parsed.model is missing or not in
ALLOWED_MODEL_IDS you reject the request (e.g., throw a BadRequestError or
return a 400 response) with a clear message listing allowed model IDs; reference
the existing symbols parsed.model, ALLOWED_MODEL_IDS and remove the fallback to
OPENROUTER_DEFAULT_MODEL so callers must supply a valid model.
apps/backend/src/lib/ai/openrouter-usage.ts-23-33 (1)

23-33: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace unknown-to-typed casts with defensive type guards.

Lines 23-33 and Line 75 rely on as casts for untrusted metadata/event shapes. Please switch to explicit narrowing helpers so malformed payloads fail predictably without bypassing types.

As per coding guidelines, "Do not use as, any, type casts, or other type system bypasses unless explicitly approved by the user."

Also applies to: 75-75

🤖 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 `@apps/backend/src/lib/ai/openrouter-usage.ts` around lines 23 - 33, The
current functions extractOpenRouterCost and extractCachedTokens use unsafe "as"
casts on untrusted meta shapes; replace those casts with explicit runtime type
guards (e.g., isProviderMetadata, isOpenRouterUsage) that validate the object
structure before accessing nested properties, and have the functions return
undefined when validation fails; update any other usage at line ~75 to employ
the same guards (don't use "as" or any) so malformed payloads fail predictably
and no type system bypasses are used.
apps/backend/src/lib/ai/ai-query-handlers.ts-139-146 (1)

139-146: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Avoid unsafe cast for tool result payload.

At Line 145, (toolResult?.output ?? null) as Json bypasses type safety. Please validate/normalize to Json explicitly and throw on non-serializable output instead of casting.

As per coding guidelines, "Do not use as, any, type casts, or other type system bypasses unless explicitly approved by the user."

🤖 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 `@apps/backend/src/lib/ai/ai-query-handlers.ts` around lines 139 - 146, Replace
the unsafe cast "(toolResult?.output ?? null) as Json" with explicit
validation/normalization: ensure in the code that builds contentBlocks (where
you push the "tool-call" object using toolCall and toolResult) you run
toolResult?.output through a serializer/validator that confirms it is valid JSON
(or null), convert non-primitive objects to safe JSON-compatible structures, and
throw a descriptive error if the output is not serializable; update the pushed
property to use the validated/normalized value (e.g., validatedToolOutput)
instead of using "as Json" so you do not bypass the type system.
apps/backend/src/lib/ai/loggers/mcp-call-logger.ts-52-54 (1)

52-54: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Avoid cast-based JSON shaping for inner tool calls.

At Lines 52-54, both casts to Json bypass validation. Prefer explicit JSON-safe normalization and throw when values are not serializable.

As per coding guidelines, "Do not use as, any, type casts, or other type system bypasses unless explicitly approved by the user."

🤖 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 `@apps/backend/src/lib/ai/loggers/mcp-call-logger.ts` around lines 52 - 54, The
current assignments for args, argsText, and result use type casts (e.g., args:
tc.input as Json and result: ... as Json) which bypass validation; replace them
with a JSON-safe normalization helper (e.g., normalizeJson(value)) that attempts
to JSON.stringify the value, throws on non-serializable inputs, and returns a
properly typed Json value; use this helper when setting args (from tc.input) and
result (from resultsByCallId.get(tc.toolCallId)?.output) and derive argsText
from the safe serialization, ensuring no uses of "as" or any casts remain in
mcp-call-logger.ts.
apps/backend/src/lib/ai/loggers/ai-query-logger.ts-20-24 (1)

20-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not coerce invalid required numeric fields to 0.

At Lines 20-24, returning 0 silently masks invalid telemetry and can corrupt analytics. This path should throw after capture (or skip write), not default to zero.

As per coding guidelines, "Fail early and fail loud with errors instead of silently continuing."

🤖 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 `@apps/backend/src/lib/ai/loggers/ai-query-logger.ts` around lines 20 - 24, The
function sanitizeRequiredNumber currently returns 0 on invalid input which
silently masks telemetry; instead, after calling captureError("ai-query-logger",
new HexclaveAssertionError(`Invalid ${name}: ${n}`)) you should re-throw that
HexclaveAssertionError (or throw a new Error) so callers fail-fast (do not
coerce to 0); update sanitizeRequiredNumber to throw after captureError and
adjust any callers of sanitizeRequiredNumber to handle the thrown error if
needed.
apps/backend/src/lib/ai/loggers/ai-proxy-logger.ts-19-23 (1)

19-23: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace cast-based tool narrowing with runtime guards.

At Line 21, t as { ... } bypasses the type system. Use explicit object/key checks before reading function.name / name.

As per coding guidelines, "Do not use as, any, type casts, or other type system bypasses unless explicitly approved by the user."

🤖 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 `@apps/backend/src/lib/ai/loggers/ai-proxy-logger.ts` around lines 19 - 23, The
map callback in ai-proxy-logger.ts currently uses a type cast ("t as { name?:
unknown, function?: { name?: unknown } }") to access function.name and name;
replace that cast with runtime guards: first verify t is an object (e.g., typeof
t === "object" && t !== null), then check if "function" in t and typeof (t as
any).function === "object" and "name" in (t as any).function and typeof ((t as
any).function.name) === "string" before returning it; similarly check "name" in
t and typeof (t as any).name === "string" before returning it. Update the map
callback (the anonymous function passed to .map) to use these explicit property
checks instead of the cast so no "as" or type-bypass is used.
apps/backend/src/lib/ai/spacetimedb-client.ts-134-139 (1)

134-139: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t silently map unnamed SQL columns to "" keys.

Line 134 currently uses e.name?.some ?? "". If a column name is missing, rows get silently corrupted via empty-key assignment. This should throw immediately.

Proposed fix
-  const cols = first.schema.elements.map(e => e.name?.some ?? "");
+  const cols = first.schema.elements.map((e, i) => {
+    const col = e.name?.some;
+    if (col == null || col.length === 0) {
+      throw new HexclaveAssertionError(`SQL query returned unnamed column at index ${i}`);
+    }
+    return col;
+  });

As per coding guidelines, "Never silently use fallback values without proper type checking" and "Fail early and fail loud with errors instead of silently continuing."

🤖 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 `@apps/backend/src/lib/ai/spacetimedb-client.ts` around lines 134 - 139,
Validate column names before building the cols array and fail fast instead of
using an empty string fallback: replace the current e.name?.some ?? "" usage by
iterating first.schema.elements to ensure each element has a defined name (e.g.,
throw a descriptive Error like "Unnamed SQL column at index X" when e.name?.some
is missing), then build cols from those validated names and proceed with
first.rows.map/obj population; this keeps the mapping logic (cols,
first.rows.map, and obj assignment) unchanged but prevents silent empty-key
assignment.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx-180-184 (1)

180-184: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use blocking alerts (not toasts) for these error paths.

Both runtime crash and patch-apply failure are blocking/high-salience errors; surfacing them as toasts is easy to miss.

As per coding guidelines: "For blocking alerts and errors in frontend components, use alerts instead of toast notifications as they are less easily missed by users."

Also applies to: 300-304

🤖 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
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx
around lines 180 - 184, Replace the non-blocking toast usage with a blocking
alert for the dashboard crash and related patch-apply failure paths: locate the
toast(...) calls in page-client.tsx (the toast invocation that sets
variant:"destructive", title:"Dashboard crashed", description:"Error added as a
chip — hit send to fix it.") and change them to use the app's blocking alert
mechanism (the same alert API used elsewhere in the app) so the error is shown
as a modal/alert instead of a toast; apply the same replacement for the similar
toast at the other occurrence referenced around lines 300-304 to ensure both
runtime crash and patch-apply failures use alerts.
apps/backend/src/lib/ai/tools/index.ts-72-75 (1)

72-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Throw on unknown tool names instead of silently continuing.

The default branch records an error but still returns a partial tools object. This should throw to stop invalid state propagation.

Proposed fix
       default: {
         const _exhaustive: never = toolName;
-        captureError("ai-tools-getTools", new HexclaveAssertionError(`Unknown tool name: ${_exhaustive as string}`));
+        const err = new HexclaveAssertionError(`Unknown tool name: ${String(_exhaustive)}`);
+        captureError("ai-tools-getTools", err);
+        throw err;
       }

As per coding guidelines: "Fail early and fail loud with errors instead of silently continuing."

🤖 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 `@apps/backend/src/lib/ai/tools/index.ts` around lines 72 - 75, The default
branch in the switch over toolName (using _exhaustive and captureError with
HexclaveAssertionError) currently only logs the error and allows execution to
continue, returning a possibly invalid partial tools object; change this to
throw the HexclaveAssertionError (or rethrow) after logging so execution stops
immediately on unknown tool names—i.e., in the default case of the switch that
references toolName/_exhaustive and captureError, throw the constructed
HexclaveAssertionError instead of just calling captureError to ensure callers
never receive a partial tools result.
apps/dashboard/src/components/vibe-coding/chat-adapters.ts-236-242 (1)

236-242: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not silently coerce malformed AI responses to empty content.

On Line 241, returning [] when json.content is not an array hides backend contract breakages and can produce misleading UI behavior. Throw an explicit error instead so failures are visible and debuggable.

As per coding guidelines: "Never silently use fallback values without proper type checking" and "Fail early and fail loud with errors instead of silently continuing".

🤖 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 `@apps/dashboard/src/components/vibe-coding/chat-adapters.ts` around lines 236
- 242, Replace the silent-coercion fallback that returns [] when parsing the AI
response; in the block that checks response and parses const json = await
response.json() as { content?: ChatContent }, instead of returning
Array.isArray(json.content) ? json.content : [], throw a descriptive Error when
json.content is missing or not an array (include the raw json/content or
response.status/response.statusText in the error message for debugging). Ensure
the thrown error surfaces the unexpected payload so callers of this parsing
logic can fail loudly and investigate backend contract breaks.
apps/dashboard/src/components/vibe-coding/dashboard-tool-components.tsx-73-78 (1)

73-78: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid empty-string fallback for snapshot keys.

On Lines 76-77, patchSnapshotKey returning "" on stringify failure can collide unrelated patches and restore incorrect source. Throw (or return a guaranteed-unique failure key) instead of silently collapsing keys.

As per coding guidelines: "Fail early and fail loud with errors instead of silently continuing".

🤖 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 `@apps/dashboard/src/components/vibe-coding/dashboard-tool-components.tsx`
around lines 73 - 78, The function patchSnapshotKey currently swallows
JSON.stringify failures and returns an empty string which can collide keys;
update patchSnapshotKey to fail loud by either re-throwing the caught error
(include a descriptive message referencing patchSnapshotKey and the edits input)
or returning a guaranteed-unique failure key (e.g. a clearly namespaced marker
plus a unique id/timestamp/error.message) so unstringifiable edits never
collapse to "". Locate patchSnapshotKey and replace the empty-string fallback in
the catch block with one of these two behaviors and ensure any thrown error
includes contextual info about the edits so calling code can surface/fail
appropriately.
apps/e2e/tests/spacetimedb/helpers.ts-21-23 (1)

21-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not default to a production-like DB name when env is missing.

On Line 22, silently defaulting dbName to "stack-auth-llm" can send test cleanup and reducers to the wrong database under misconfiguration. Throw an explicit config error when STACK_SPACETIMEDB_DB_NAME is unset.

As per coding guidelines: "Never silently use fallback values without proper type checking" and "Fail early and fail loud with errors instead of silently continuing".

🤖 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 `@apps/e2e/tests/spacetimedb/helpers.ts` around lines 21 - 23, The current
config silently defaults dbName to "stack-auth-llm"; instead ensure you fail
fast by validating process.env.STACK_SPACETIMEDB_DB_NAME and throwing a clear
error when it's missing: update the code that sets dbName (the dbName
variable/setting in apps/e2e/tests/spacetimedb/helpers.ts) to check
process.env.STACK_SPACETIMEDB_DB_NAME and throw a descriptive Error (including
the env var name) if undefined or empty, rather than using the "stack-auth-llm"
fallback; keep baseUrl/logToken handling unchanged but apply the same explicit
validation pattern if needed.
apps/e2e/tests/spacetimedb/helpers.ts-172-176 (1)

172-176: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Cleanup matching can delete unrelated operators via substring collisions.

Using JSON.stringify(row).toLowerCase().includes(identity) can match unrelated rows and remove the wrong users during cleanup. Match against explicit identity fields (exact equality) instead of full-row substring search.

🤖 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 `@apps/e2e/tests/spacetimedb/helpers.ts` around lines 172 - 176, The cleanup
currently uses
JSON.stringify(row).toLowerCase().includes(identity.toLowerCase()) which can
spuriously match substrings; change the logic in the cleanup loop that builds
out (where rowJson, identities, and stackUserIdRaw are used) to test explicit
identity fields on the row (e.g., row.email, row.username, row.id or whatever
explicit identity properties exist) with exact, case-insensitive equality
(compare identity.toLowerCase() === (row.<field> || '').toLowerCase()) instead
of a full-row substring search; iterate the known identity fields for each row
and only add stackUserIdRaw to out when one exact field matches.
apps/dashboard/src/components/vibe-coding/chat-adapters.ts-608-612 (1)

608-612: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard optional abort signal in the catch path.

On Line 609, abortSignal is optional in run(...); abortSignal.aborted can throw and mask the original failure. Use abortSignal?.aborted before returning.

🤖 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 `@apps/dashboard/src/components/vibe-coding/chat-adapters.ts` around lines 608
- 612, The catch block in run(...) checks abortSignal.aborted which can throw
when abortSignal is undefined; change the guard to use the optional chain
abortSignal?.aborted so the original error isn't masked and you still return
early on abort; update the catch in the function run (referencing the
abortSignal parameter) to use abortSignal?.aborted before returning and preserve
throwing the original or wrapped error otherwise.
apps/dashboard/src/components/vibe-coding/chat-adapters.ts-128-141 (1)

128-141: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove as assertions in parsing/conversion paths (use runtime type guards instead).

  • parsePatchEdits (128-141) uses args as ... and e as ... instead of narrowing to a record/type via explicit checks.
  • sendAiRequest (240-241) uses response.json() as ... and silently falls back to [] on shape mismatch; validate content from unknown instead.
  • uiPartsToChatContent (309-333) uses part as ... for dynamic-tool / tool-* and then relies on toolCallId/toolName without runtime presence checks (fallbacks like input ?? {} can mask invalid shapes).
  • const chipPart = { type: "text" as const, ... } (546) is also an as usage; avoid type assertions unless necessary.

As per coding guidelines: "Do not use as, any, type casts, or other type system bypasses unless explicitly approved by the user".

🤖 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 `@apps/dashboard/src/components/vibe-coding/chat-adapters.ts` around lines 128
- 141, parsePatchEdits, sendAiRequest, uiPartsToChatContent and the chipPart
creation are using TypeScript "as" casts to bypass runtime checks; replace those
casts with explicit runtime type guards and shape validations: in
parsePatchEdits avoid "args as …" / "e as …" by checking that args is a plain
object and that it has an "edits" array, then for each edit verify
oldText/newText are strings and occurrenceIndex is a number (or undefined)
before pushing; in sendAiRequest validate the result of response.json() (ensure
it is an object/array with the expected "content" shape) and only fall back to
[] after explicit checks rather than casting; in uiPartsToChatContent check each
part is an object, verify part.type === "dynamic-tool" or /^tool-/ and that
required fields like toolCallId/toolName exist and have correct types before
using them (don’t mask missing fields with input ?? {}); and replace the
chipPart "type: 'text' as const" cast by constructing the literal with runtime
checks or an explicitly typed factory function so no "as" is needed. Ensure all
fixes reference the functions parsePatchEdits, sendAiRequest,
uiPartsToChatContent and the chipPart construction and use small helper
type-guard functions to keep code readable.
apps/e2e/tests/spacetimedb/reducer-auth.test.ts-48-83 (1)

48-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

“Every mutating reducer” smoke test is currently incomplete.

Line 52 claims full mutating coverage, but the cases array misses clear_mcp_qa_review, update_ai_query_cost, delete_mcp_call_log, and delete_ai_query_log. Add them so token-gate regressions on new reducers can’t slip through.

🤖 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 `@apps/e2e/tests/spacetimedb/reducer-auth.test.ts` around lines 48 - 83, The
test's mutating-reducer coverage loop is missing four reducers; update the cases
array in reducer-auth.test.ts to include entries for clear_mcp_qa_review,
update_ai_query_cost, delete_mcp_call_log, and delete_ai_query_log so the token
gate is exercised for them; add entries with the same first element `wrong` and
sensible args consistent with nearby cases (e.g., correlation id / ids, numeric
cost or log id, and opt(null) where other log/query entries use it) so the test
invokes those reducers by name alongside add_operator, log_mcp_call,
log_ai_query, etc.
apps/internal-tool/spacetimedb/src/index.ts-305-305 (1)

305-305: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Remove as Parameters<...> casts from inserts.

These casts bypass type safety in core reducers and make schema drift easier to miss at compile time.

As per coding guidelines: "Do not use as, any, type casts, or other type system bypasses unless explicitly approved by the user."

Also applies to: 469-469, 524-524, 567-567, 678-678

🤖 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 `@apps/internal-tool/spacetimedb/src/index.ts` at line 305, The insert calls
currently use "as Parameters<typeof ctx.db.mcpCallLog.insert>[0]" to bypass type
checking (e.g., the ctx.db.mcpCallLog.insert call at the line shown and similar
casts at other locations), which hides schema mismatches; remove these type
assertions and make the object you pass match the exact parameter type expected
by ctx.db.*.insert (add/massage fields, convert types, or use helper
constructors so the payload conforms to the generated types), and rerun
TypeScript to fix any compile errors until the insert arguments are fully
type-safe without using "as" or "any".
apps/internal-tool/scripts/pre-dev.mjs-85-97 (1)

85-97: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

probeToken treats failures as valid tokens.

Line 94 and Line 96 return true for non-OK responses and network failures. That can preserve stale/invalid tokens and skip remint.

Proposed fix
 async function probeToken(spacetimeHttpUrl, dbName, token) {
   try {
     const res = await fetch(`${spacetimeHttpUrl}/v1/database/${encodeURIComponent(dbName)}/sql`, {
       method: "POST",
       headers: { "Authorization": `Bearer ${token}` },
       body: "SELECT 1",
     });
-    if (res.status === 401) return false;
-    if (res.ok) return true;
-    return true;
+    return res.ok;
   } catch {
-    return true;
+    return false;
   }
 }
🤖 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 `@apps/internal-tool/scripts/pre-dev.mjs` around lines 85 - 97, The probeToken
function currently treats non-OK responses and network errors as valid by
returning true; update probeToken so it only returns true when the fetch
response is OK (res.ok === true), return false for 401 and for any other non-OK
HTTP status, and return false on exceptions (catch block) so network failures
also mark the token invalid; locate the probeToken function and adjust the
return logic around res.ok and the catch to ensure only successful 2xx responses
yield true.
apps/internal-tool/scripts/pre-dev.mjs-65-67 (1)

65-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the caught error before reading .message.

Line 66 assumes err is an Error. If a non-Error is thrown (e.g. null), this catch block can throw and abort the script.

Proposed fix
   } catch (err) {
-    console.warn(`[internal-tool] Failed to mint service token: ${err.message}. Backend SpacetimeDB features will error until STACK_SPACETIMEDB_SERVICE_TOKEN is set manually.`);
+    const msg = err instanceof Error ? err.message : String(err);
+    console.warn(`[internal-tool] Failed to mint service token: ${msg}. Backend SpacetimeDB features will error until STACK_SPACETIMEDB_SERVICE_TOKEN is set manually.`);
     return;
   }
🤖 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 `@apps/internal-tool/scripts/pre-dev.mjs` around lines 65 - 67, The catch block
that logs the token mint failure reads err.message directly which can throw if a
non-Error (e.g. null) is thrown; update the catch handling around the
console.warn in the catch (err) block (the template string that references
err.message) to safely derive a message (e.g. use optional chaining and
fallback: err?.message ?? String(err)) or check instanceof Error and fall back
to String(err), then use that safe message in the console.warn.
apps/internal-tool/src/components/UsageDetail.tsx-27-59 (1)

27-59: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Surface JSON parse failures instead of silently defaulting to empty arrays in UsageDetail

row.messagesJson, row.stepsJson, and row.requestedToolsJson are parsed with JSON.parse(... ) as ...[] and any error is swallowed via catch { return [] }, which turns invalid/corrupt log payloads into misleading “No input messages” / “No assistant output recorded” empty states instead of surfacing the failure. Replace with runtime validation (or a schema) and render an explicit parse-error state (or fail loudly) when parsing fails.
File: apps/internal-tool/src/components/UsageDetail.tsx (around lines 37-59)

🤖 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 `@apps/internal-tool/src/components/UsageDetail.tsx` around lines 27 - 59, The
JSON parsing for row.messagesJson, row.stepsJson, and row.requestedToolsJson
inside the UsageDetail component currently swallows errors and returns empty
arrays; change each useMemo to validate and surface parse failures instead of
silently returning []: parse with JSON.parse, check the result type (e.g.,
Array.isArray and element shape for MessageIn/StepEntry/string), and on
parse/validation failure set an explicit parseError state (via useState) or
rethrow so the component can render a clear error UI (e.g., "Failed to parse
messagesJson") rather than showing "No input messages"; reference the existing
useMemo blocks for messages, steps, and requestedTools and add runtime
validation for each parsed value.
🟡 Minor comments (8)
apps/backend/.env.development-127-127 (1)

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

Inconsistent port prefix variable in provisioning comment.

The comment references ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39 but the actual URL on line 123 uses ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}39. This mismatch could confuse developers following the provisioning instructions.

Proposed fix
-# To provision locally: `curl -X POST http://127.0.0.1:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39/v1/identity`
+# To provision locally: `curl -X POST http://127.0.0.1:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}39/v1/identity`
🤖 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 `@apps/backend/.env.development` at line 127, The provisioning comment uses the
wrong port prefix variable; replace `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39`
with the same variable used elsewhere
(`${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}39`) so the comment consistently
references NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX (or alternatively update the other
occurrences to use NEXT_PUBLIC_STACK_PORT_PREFIX if that was intended), ensuring
the provisioning URL and the variable names (NEXT_PUBLIC_STACK_PORT_PREFIX,
NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX) match across the file.
apps/backend/src/lib/ai/spacetimedb-client.test.ts-60-60 (1)

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

Avoid as unknown as typeof fetch when assigning the mocked globalThis.fetch in apps/backend/src/lib/ai/spacetimedb-client.test.ts.
The test assigns globalThis.fetch = fetchMock as unknown as typeof fetch (line 60); type fetchMock as vi.fn<typeof fetch>() and assign it directly (globalThis.fetch = fetchMock) to keep type-safety without bypass casts.

🤖 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 `@apps/backend/src/lib/ai/spacetimedb-client.test.ts` at line 60, The test
currently bypasses TypeScript checks by assigning globalThis.fetch = fetchMock
as unknown as typeof fetch; instead declare fetchMock with the correct type
using vi.fn<typeof fetch>() (or equivalent typed mock) and then assign it
directly to globalThis.fetch (globalThis.fetch = fetchMock) so you keep type
safety; update the mock declaration (fetchMock) and remove the double-cast when
assigning to globalThis.fetch in spacetimedb-client.test.ts.
apps/backend/src/lib/ai/tools/index.ts-74-74 (1)

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

Remove as casts in tool-name exhaustive/default and validateToolNames

File: apps/backend/src/lib/ai/tools/index.ts
Lines: 74, 91

captureError("ai-tools-getTools", new HexclaveAssertionError(`Unknown tool name: ${_exhaustive as string}`));
Proposed fix
-        captureError("ai-tools-getTools", new HexclaveAssertionError(`Unknown tool name: ${_exhaustive as string}`));
+        captureError("ai-tools-getTools", new HexclaveAssertionError(`Unknown tool name: ${String(_exhaustive)}`));

 export function validateToolNames(toolNames: unknown): toolNames is ToolName[] {
   if (!Array.isArray(toolNames)) {
     return false;
   }
+  const toolNameSet = new Set<string>(TOOL_NAMES);
   return toolNames.every((name): name is ToolName =>
-    typeof name === "string" && (TOOL_NAMES as readonly string[]).includes(name)
+    typeof name === "string" && toolNameSet.has(name)
   );
 }
🤖 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 `@apps/backend/src/lib/ai/tools/index.ts` at line 74, Remove the explicit "as"
type assertions for tool-name handling: in the exhaustive/default branch where
you call captureError("ai-tools-getTools", new HexclaveAssertionError(`Unknown
tool name: ${_exhaustive as string}`)), replace the cast with a safe
runtime-to-string conversion such as String(_exhaustive) (i.e. `Unknown tool
name: ${String(_exhaustive)}`) and likewise remove any `as string` casts inside
validateToolNames, using String(...) or letting template interpolation coerce
values; keep the TypeScript exhaustive check semantics (use the never type
pattern) but avoid raw `as` casts in getTools/validateToolNames and ensure
errors log the converted value using the referenced functions/call sites
(captureError, HexclaveAssertionError, validateToolNames).
apps/e2e/tests/backend/endpoints/api/latest/internal/mcp-review.test.ts-26-26 (1)

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

Replace as unknown as boolean with unknown-typed fixtures in invalidBody tests

In apps/e2e/tests/backend/endpoints/api/latest/internal/mcp-review.test.ts (lines 26 and 36), publish: "yes" as unknown as boolean double-casts and bypasses the type system. Keep these negative-payload values as unknown fixtures (or widen the invalid fixture typing used by the test) instead of casting.

🤖 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 `@apps/e2e/tests/backend/endpoints/api/latest/internal/mcp-review.test.ts` at
line 26, The invalidBody test fixtures currently double-cast the string to
boolean using "yes" as unknown as boolean which defeats the type system; replace
those casts by keeping the invalid payloads typed as unknown (e.g., declare
invalidBody as unknown or annotate the fixture property as unknown: publish:
"yes" as unknown) or widen the invalid fixture type used by the test so you
don’t coerce the value — update the invalidBody variables (and any similar
fixtures) used in the mcp-review tests to use unknown-typed fixtures instead of
double-casting.
apps/internal-tool/src/components/UsageDetail.tsx-122-127 (1)

122-127: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible name for the close button.

The symbol-only button currently has no explicit accessible label.

Suggested fix
         <button
           onClick={onClose}
+          aria-label="Close usage details"
+          title="Close usage details"
           className="shrink-0 ml-2 text-gray-400 hover:text-gray-600 text-sm"
         >
           ✕
         </button>
🤖 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 `@apps/internal-tool/src/components/UsageDetail.tsx` around lines 122 - 127,
The close button in UsageDetail.tsx (the button element with onClick={onClose})
is symbol-only and needs an accessible name; add an explicit label such as
aria-label="Close" or include visually hidden text (e.g., a span with sr-only
styling containing "Close") so screen readers can announce the button purpose
while keeping the visual ✕ unchanged.
apps/internal-tool/src/lib/env.ts-15-15 (1)

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

Make the assertion message env-agnostic.

Line 15 mentions only NEXT_PUBLIC_STACK_*, but this helper is also used for SpacetimeDB env keys, which can mislead incident/debug workflows.

Suggested fix
-    throw new HexclaveAssertionError(`${name} is not configured. Set the NEXT_PUBLIC_STACK_* vars in .env.local or the hosting platform env.`);
+    throw new HexclaveAssertionError(`${name} is not configured. Set this variable in .env.local or your hosting platform environment.`);
🤖 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 `@apps/internal-tool/src/lib/env.ts` at line 15, The assertion message in the
HexclaveAssertionError is environment-specific ("NEXT_PUBLIC_STACK_*") and can
mislead when used for other keys (like SpacetimeDB); update the throw in the
helper (the new HexclaveAssertionError instantiation) to use a generic,
env-agnostic message that includes the missing variable name (e.g., "`{name} is
not configured. Set the corresponding environment variable in .env.local or your
hosting platform.`") so the error applies to any env key.
apps/internal-tool/src/hooks/useSpacetimeDB.ts-93-93 (1)

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

Avoid bigint→number conversion in row sorting.
useTableSubscription constrains rows to { id: bigint }, but the comparator uses Number(b.id - a.id) (line 93), which can lose precision for large u64-backed ids and misorder rows.

Suggested fix
-                initial.sort((a, b) => Number(b.id - a.id));
+                initial.sort((a, b) => (a.id === b.id ? 0 : a.id > b.id ? -1 : 1));
🤖 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 `@apps/internal-tool/src/hooks/useSpacetimeDB.ts` at line 93, The comparator
currently converts bigint differences to Number (initial.sort((a, b) =>
Number(b.id - a.id))), which can overflow for u64 ids; change the comparator to
a bigint-safe comparison that returns -1/0/1 using relational operators on the
id fields (e.g., compare b.id and a.id with >, <, ===) so sorting respects the {
id: bigint } constraint from useTableSubscription and avoids precision loss.
packages/stack-shared/src/known-errors.tsx-1882-1883 (1)

1882-1883: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail loudly when known-error JSON details are incomplete.

Line 1882 and Line 1896 should assert required fields instead of accepting undefined, so malformed payloads fail early.

Suggested fix
 const TooManyImageAttachments = createKnownErrorConstructor(
   KnownError,
   "TOO_MANY_IMAGE_ATTACHMENTS",
@@
-  (json) => [json.max_images] as const,
+  (json) => [
+    json?.max_images ?? throwErr("max_images not found in TooManyImageAttachments details"),
+  ] as const,
 );
@@
 const ImageAttachmentTooLarge = createKnownErrorConstructor(
   KnownError,
   "IMAGE_ATTACHMENT_TOO_LARGE",
@@
-  (json) => [json.max_bytes, json.actual_bytes] as const,
+  (json) => [
+    json?.max_bytes ?? throwErr("max_bytes not found in ImageAttachmentTooLarge details"),
+    json?.actual_bytes ?? throwErr("actual_bytes not found in ImageAttachmentTooLarge details"),
+  ] as const,
 );

As per coding guidelines: "Fail early and fail loud with errors instead of silently continuing".

Also applies to: 1896-1897

🤖 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 `@packages/stack-shared/src/known-errors.tsx` around lines 1882 - 1883, The
tuple-mapping arrow (json) => [json.max_images] as const currently allows
json.max_images to be undefined; change it to validate and fail loudly by
asserting required fields at runtime (e.g., if (json.max_images == null) throw
new Error('known-error: missing max_images'); then return [json.max_images] as
const). Apply the same pattern to the other tuple mapper(s) that currently
return fields possibly undefined (e.g., the mapper returning [json.prompt,
json.model] as const) so each required field is checked and an explicit error
thrown if absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4ac8587-93b2-44ea-a313-43918b20ef15

📥 Commits

Reviewing files that changed from the base of the PR and between c753ab2 and df8df02.

📒 Files selected for processing (105)
  • apps/backend/.env
  • apps/backend/.env.development
  • apps/backend/prisma/seed.ts
  • apps/backend/src/app/api/latest/ai/query/[mode]/route.ts
  • apps/backend/src/app/api/latest/integrations/ai-proxy/[[...path]]/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/add-manual/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/delete/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/mark-reviewed/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/retry-review/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/unmark-reviewed/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/update-correction/route.ts
  • apps/backend/src/app/api/latest/internal/mcp-review/update-qa-entry/route.ts
  • apps/backend/src/app/api/latest/internal/spacetimedb-enroll-reviewer/route.ts
  • apps/backend/src/lib/ai/ai-proxy-handlers.test.ts
  • apps/backend/src/lib/ai/ai-proxy-handlers.ts
  • apps/backend/src/lib/ai/ai-query-handlers.test.ts
  • apps/backend/src/lib/ai/ai-query-handlers.ts
  • apps/backend/src/lib/ai/loggers/ai-proxy-logger.test.ts
  • apps/backend/src/lib/ai/loggers/ai-proxy-logger.ts
  • apps/backend/src/lib/ai/loggers/ai-query-logger.ts
  • apps/backend/src/lib/ai/loggers/mcp-call-logger.ts
  • apps/backend/src/lib/ai/mcp-logger.ts
  • apps/backend/src/lib/ai/models.ts
  • apps/backend/src/lib/ai/openrouter-usage.ts
  • apps/backend/src/lib/ai/prompts.ts
  • apps/backend/src/lib/ai/qa/qa-reviewer.ts
  • apps/backend/src/lib/ai/qa/reviewer-auth.ts
  • apps/backend/src/lib/ai/qa/verified-qa.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings-sync.test.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/index.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/log_mcp_call_reducer.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/mcp_call_log_table.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/types.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/types/procedures.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/types/reducers.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/update_mcp_qa_review_reducer.ts
  • apps/backend/src/lib/ai/spacetimedb-client.test.ts
  • apps/backend/src/lib/ai/spacetimedb-client.ts
  • apps/backend/src/lib/ai/tools/create-dashboard.ts
  • apps/backend/src/lib/ai/tools/index.test.ts
  • apps/backend/src/lib/ai/tools/index.ts
  • apps/backend/src/lib/ai/tools/sql-query.ts
  • apps/backend/src/lib/ai/types.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx
  • apps/dashboard/src/components/assistant-ui/thread.tsx
  • apps/dashboard/src/components/commands/ask-ai.tsx
  • apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx
  • apps/dashboard/src/components/vibe-coding/assistant-chat.tsx
  • apps/dashboard/src/components/vibe-coding/chat-adapters.ts
  • apps/dashboard/src/components/vibe-coding/dashboard-tool-components.tsx
  • apps/dashboard/src/lib/ai-dashboard/shared-prompt.ts
  • apps/e2e/tests/backend/backend-helpers.ts
  • apps/e2e/tests/backend/endpoints/api/latest/internal/mcp-review.test.ts
  • apps/e2e/tests/backend/endpoints/api/latest/internal/spacetimedb-enroll-reviewer.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/ai-query.test.ts
  • apps/e2e/tests/spacetimedb/helpers.ts
  • apps/e2e/tests/spacetimedb/operators-rls.test.ts
  • apps/e2e/tests/spacetimedb/private-tables.test.ts
  • apps/e2e/tests/spacetimedb/published-qa-projection.test.ts
  • apps/e2e/tests/spacetimedb/published-qa-visibility.test.ts
  • apps/e2e/tests/spacetimedb/qa-entries-invariants.test.ts
  • apps/e2e/tests/spacetimedb/reducer-auth.test.ts
  • apps/internal-tool/.env
  • apps/internal-tool/scripts/pre-dev.mjs
  • apps/internal-tool/spacetimedb/src/index.ts
  • apps/internal-tool/src/app/app-client.tsx
  • apps/internal-tool/src/app/questions/page.tsx
  • apps/internal-tool/src/components/AddManualQa.tsx
  • apps/internal-tool/src/components/CallLogDetail.tsx
  • apps/internal-tool/src/components/CallLogList.tsx
  • apps/internal-tool/src/components/ConversationReplay.tsx
  • apps/internal-tool/src/components/KnowledgeBase.tsx
  • apps/internal-tool/src/components/Usage.tsx
  • apps/internal-tool/src/components/UsageDetail.tsx
  • apps/internal-tool/src/hooks/useSpacetimeDB.ts
  • apps/internal-tool/src/lib/env.ts
  • apps/internal-tool/src/lib/mcp-review-api.ts
  • apps/internal-tool/src/module_bindings/add_manual_qa_reducer.ts
  • apps/internal-tool/src/module_bindings/add_operator_reducer.ts
  • apps/internal-tool/src/module_bindings/backfill_qa_entries_reducer.ts
  • apps/internal-tool/src/module_bindings/delete_ai_query_log_reducer.ts
  • apps/internal-tool/src/module_bindings/delete_mcp_call_log_reducer.ts
  • apps/internal-tool/src/module_bindings/delete_qa_entry_reducer.ts
  • apps/internal-tool/src/module_bindings/enroll_service_reducer.ts
  • apps/internal-tool/src/module_bindings/index.ts
  • apps/internal-tool/src/module_bindings/log_ai_query_reducer.ts
  • apps/internal-tool/src/module_bindings/my_visible_ai_query_log_table.ts
  • apps/internal-tool/src/module_bindings/my_visible_mcp_call_log_table.ts
  • apps/internal-tool/src/module_bindings/my_visible_qa_entries_table.ts
  • apps/internal-tool/src/module_bindings/operators_table.ts
  • apps/internal-tool/src/module_bindings/published_qa_table.ts
  • apps/internal-tool/src/module_bindings/remove_operator_reducer.ts
  • apps/internal-tool/src/module_bindings/set_qa_published_reducer.ts
  • apps/internal-tool/src/module_bindings/types.ts
  • apps/internal-tool/src/module_bindings/types/reducers.ts
  • apps/internal-tool/src/module_bindings/unmark_human_reviewed_reducer.ts
  • apps/internal-tool/src/module_bindings/update_ai_query_cost_reducer.ts
  • apps/internal-tool/src/module_bindings/update_qa_entry_reducer.ts
  • apps/internal-tool/src/module_bindings/update_qa_entry_with_publish_reducer.ts
  • apps/internal-tool/src/module_bindings/upsert_qa_from_call_reducer.ts
  • apps/internal-tool/src/stack.ts
  • apps/internal-tool/src/types.ts
  • apps/mcp/src/mcp-handler.ts
  • packages/stack-shared/src/ai/image-limits.ts
  • packages/stack-shared/src/known-errors.tsx
💤 Files with no reviewable changes (11)
  • apps/backend/src/lib/ai/spacetimedb-bindings/mcp_call_log_table.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/types/procedures.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/types/reducers.ts
  • apps/backend/src/lib/ai/mcp-logger.ts
  • apps/internal-tool/src/module_bindings/unmark_human_reviewed_reducer.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/log_mcp_call_reducer.ts
  • apps/internal-tool/.env
  • apps/backend/src/lib/ai/spacetimedb-bindings/types.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/update_mcp_qa_review_reducer.ts
  • apps/internal-tool/src/module_bindings/delete_mcp_call_log_reducer.ts
  • apps/backend/src/lib/ai/spacetimedb-bindings/index.ts

Comment on lines +113 to +115
export const operatorsVisibility = spacetimedb.clientVisibilityFilter.sql(
'SELECT * FROM operators WHERE identity = :sender'
);
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Expired operators are still treated as authorized.

expiresAt is written in add_operator, but visibility checks only test row presence. Without an expiry check in Line 113-115 and the three view guards, expired identities keep access until some later cleanup call happens.

Proposed direction
 export const operatorsVisibility = spacetimedb.clientVisibilityFilter.sql(
-  'SELECT * FROM operators WHERE identity = :sender'
+  'SELECT * FROM operators WHERE identity = :sender AND (expires_at IS NULL OR expires_at > :now)'
 );
 (ctx) => {
-  if (ctx.db.operators.identity.find(ctx.sender) == null) return [];
+  const operator = ctx.db.operators.identity.find(ctx.sender);
+  const now = ctx.timestamp.microsSinceUnixEpoch;
+  if (operator == null) return [];
+  if (operator.expiresAt != null && operator.expiresAt.microsSinceUnixEpoch <= now) return [];
   return Array.from(ctx.db.mcpCallLog.shard.filter(0));
 }

(Apply the same guard to myVisibleAiQueryLog and myVisibleQaEntries.)

Also applies to: 120-121, 128-129, 136-137

🤖 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 `@apps/internal-tool/spacetimedb/src/index.ts` around lines 113 - 115, The
current visibility filter operatorsVisibility (and likewise myVisibleAiQueryLog
and myVisibleQaEntries) only checks row existence and ignores the expiresAt
column set by add_operator, so expired operators remain authorized; update each
spacetimedb.clientVisibilityFilter.sql query to include an expiry check (e.g.,
AND (expiresAt IS NULL OR expiresAt > CURRENT_TIMESTAMP) or equivalent DB-now
comparison) to ensure only non-expired entries match, and apply the same clause
to the three view guards that use these filters.

@aadesh18 aadesh18 requested a review from N2D4 May 27, 2026 20:34
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