Skip to content

feat(integrations): AI API Sandbox - Foundation (Tasks 1-16) - #628

Closed
2witstudios wants to merge 8 commits into
masterfrom
claude/ai-api-sandbox-5d2ob
Closed

2witstudios wants to merge 8 commits into
masterfrom
claude/ai-api-sandbox-5d2ob

Conversation

@2witstudios

@2witstudios 2witstudios commented Feb 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements the foundation layer for the AI API Sandbox epic, enabling AI agents to safely call external APIs with zero-trust security.

Epic: AI API Sandbox
Project: AI API Sandbox (Project #4)
Milestone: AI Sandbox: Foundation (completed)

Tasks Completed (16/39)

Epic 1: Core Types (#595) ✅

  • Task 1: Core type definitions

Epic 2: Pure Functions (#596) ✅

  • Task 2: Auth method application
  • Task 3: Tool validation
  • Task 4: Request building
  • Task 5: Output transformation
  • Task 6: Rate limit calculation
  • Task 7: Visibility checks

Epic 3: Database & Encryption (#597) ✅

  • Task 8: Database schema migration
  • Task 9: Encryption utilities

Epic 4: Data Access Layer (#598) ✅

  • Task 10: Connection repository
  • Task 11: Grant repository
  • Task 12: Audit log repository

Epic 5: Execution Engine (#599) ✅

  • Task 13: Rate limiter integration
  • Task 14: HTTP executor
  • Task 15: Integration executor saga
  • Task 16: Zero-trust validator saga

Key Features

  • Zero-trust validation chain: Every tool call validates user → drive → integration → tool chain
  • Encrypted credentials: AES-256-GCM encryption at rest
  • Hybrid scoping: User integrations (follow you everywhere) and drive integrations (team-scoped)
  • Rate limiting: Per connection/grant/tool rate limiting
  • Audit logging: Every external API call logged

Files Added

packages/lib/src/integrations/
├── types.ts                    # All type definitions
├── auth/apply-auth.ts          # Pure auth functions
├── validation/
│   ├── is-tool-allowed.ts      # Tool permission validation
│   └── visibility.ts           # Visibility checks
├── execution/
│   ├── build-request.ts        # Request building
│   ├── transform-output.ts     # Output transformation
│   └── http-executor.ts        # HTTP execution
├── rate-limit/
│   ├── calculate-limit.ts      # Rate limit calculation
│   └── integration-rate-limiter.ts
├── credentials/encrypt-credentials.ts
├── repositories/
│   ├── connection-repository.ts
│   ├── grant-repository.ts
│   └── audit-repository.ts
└── saga/execute-tool.ts        # Execution orchestration

packages/db/src/schema/integrations.ts  # Database schema

Test Plan

  • All pure function unit tests passing
  • Repository integration tests passing
  • Saga orchestration tests passing
  • Manual verification of database migration

Next Steps

Remaining tasks (17-39) tracked in:


🤖 Generated with Claude Code

Closes #595, #596, #597, #598, #599

Summary by CodeRabbit

  • New Features
    • Added integration management framework supporting secure connection management, tool execution, and access control.
    • Implemented credential encryption for secure integration authentication.
    • Added comprehensive audit logging for integration tool usage.
    • Introduced rate limiting and visibility controls for integrations.
    • Added support for multiple authentication methods (OAuth2, API key, bearer token, basic auth, custom headers).

claude and others added 8 commits February 1, 2026 03:23
Comprehensive TDD plan for zero-trust external API integration system.

- 39 tasks covering pure functions, IO layer, API routes, UI
- Hybrid model: user integrations + drive integrations
- Generic design: OAuth, API key, Bearer, custom auth methods
- OpenAPI import, custom tool builder, MCP server support
- Full test strategy: unit, integration, saga, E2E

https://claude.ai/code/session_01RaETdMRb8DzD8CWUftU3Rj
TDD implementation of AI API Sandbox pure function layer:

- Task 1: Core type definitions (AuthMethod, ToolExecution, etc.)
- Task 2: applyAuth() - builds auth headers from credentials
- Task 3: isToolAllowed() - validates tool permissions
- Task 4: buildHttpRequest() - builds HTTP requests from templates
- Task 5: transformOutput() - transforms API responses
- Task 6: calculateEffectiveRateLimit() - finds most restrictive limit
- Task 7: isUserIntegrationVisibleInDrive() - visibility checks

All 105 tests passing across 7 test files.

https://claude.ai/code/session_01RaETdMRb8DzD8CWUftU3Rj
…8-16)

Implements the remaining core integration features:

Tasks 8: Database schema for integrations
- integrationProviders, integrationConnections, integrationToolGrants
- globalAssistantConfig, integrationAuditLog tables
- Full relations and type exports

Task 9: Credential encryption utilities
- encryptCredentials/decryptCredentials wrappers
- Uses existing AES-256-GCM encryption

Tasks 10-12: Repository layer
- connectionRepository: CRUD for connections with provider eager loading
- grantRepository: CRUD for tool grants with agent/connection relations
- auditRepository: Logging and querying audit entries

Task 13: Rate limiter integration
- Integration-specific rate limiting using distributed rate limiter
- Connection, agent, and tool level rate limit keys

Task 14: HTTP executor
- Request execution with retry logic (exponential backoff)
- Timeout handling, 429 Retry-After support
- Proper error categorization (client/server/network/timeout)

Tasks 15-16: Execution saga
- Full tool execution pipeline orchestration
- Validation → rate limiting → auth → execute → transform → audit
- Dependency injection for testability

177 tests passing across 14 test files.

https://claude.ai/code/session_01RaETdMRb8DzD8CWUftU3Rj
- Add missing fields to ToolCallRequest (grant) and ToolCallResult
  (errorType, retryAfter)
- Add rateLimit to IntegrationProviderConfig
- Fix vi.fn mock typing to use function signature syntax
- Add userId/driveId to all test request objects
- Fix object spread order to avoid duplicate property warnings

All 177 tests pass, typecheck clean.

https://claude.ai/code/session_01RaETdMRb8DzD8CWUftU3Rj
- Fix URL composition dropping base path when pathTemplate starts with /
- Guard credential decryption for 'none' auth method and null credentials
- Pass tool-level rate limits to calculateEffectiveRateLimit
- Fix wildcard field extraction ($.array[*].field) to map over elements
- Add check constraint enforcing exclusive userId/driveId scope

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change wildcard rate limit key to :provider for proper aggregation
- Fix http-executor tests to import real function instead of inline impl
- Fix execute-tool tests to use vi.mock for proper dependency injection
- Skip auth headers when credentials missing (avoid invalid Bearer headers)
- Add try/catch for JSON.parse in build-request transform
- Fix retry count to only increment on actual retries
- Add windowMs > 0 guards to prevent division by zero in rate limits
- Use Headers.forEach for cross-platform TypeScript compatibility
- Update documentation paths to reflect actual package structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolve migration conflicts:
- Master's 0059_spicy_zemo.sql (channel_messages fileId) stays as 0059
- Our 0059_smart_fantastic_four.sql (integrations schema) renamed to 0060

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Feb 13, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR establishes a complete integration system foundation by introducing PostgreSQL schema with 5 tables and 3 ENUMs, comprehensive TypeScript types covering authentication/execution/providers, pure utility functions for auth/validation/request-building/transformation, repository patterns for database operations, saga-based tool execution orchestration with zero-trust validation, and extensive test coverage across all modules.

Changes

Cohort / File(s) Summary
Database Schema
packages/db/drizzle/0060_smart_fantastic_four.sql, packages/db/drizzle/meta/_journal.json, packages/db/src/schema/integrations.ts, packages/db/src/schema.ts
Adds PostgreSQL ENUM types (integration_connection_status, integration_provider_type, integration_visibility) and 5 tables (integration_providers, integration_connections, integration_tool_grants, global_assistant_config, integration_audit_log) with foreign keys, indexes, and constraints; exports schema module.
Type System
packages/lib/src/integrations/types.ts, packages/lib/src/integrations/types.test.ts
Defines 40+ types/interfaces for auth methods (OAuth2, API key, bearer, basic, custom header, none), execution models (HTTP, GraphQL, function, chain), tool definitions, provider config, connections, grants, audit logging, and rate limiting; comprehensive test validation of type structures.
Authentication & Credentials
packages/lib/src/integrations/auth/apply-auth.ts, packages/lib/src/integrations/auth/apply-auth.test.ts, packages/lib/src/integrations/credentials/encrypt-credentials.ts, packages/lib/src/integrations/credentials/encrypt-credentials.test.ts
Pure functions for constructing auth headers/query params from credentials and applying multiple auth schemes; credential encryption/decryption utilities using existing encryption module.
HTTP Execution
packages/lib/src/integrations/execution/build-request.ts, packages/lib/src/integrations/execution/build-request.test.ts, packages/lib/src/integrations/execution/http-executor.ts, packages/lib/src/integrations/execution/http-executor.test.ts, packages/lib/src/integrations/execution/transform-output.ts, packages/lib/src/integrations/execution/transform-output.test.ts
HTTP request construction with path interpolation and parameter resolution; HTTP executor with retry logic, exponential backoff, timeout handling, and error categorization; output transformation with JSONPath extraction, field mapping, and string truncation.
Rate Limiting
packages/lib/src/integrations/rate-limit/calculate-limit.ts, packages/lib/src/integrations/rate-limit/calculate-limit.test.ts, packages/lib/src/integrations/rate-limit/integration-rate-limiter.ts, packages/lib/src/integrations/rate-limit/integration-rate-limiter.test.ts
Hierarchical rate limit calculation across provider/connection/grant/tool levels; multi-scope rate limiting wrapper using distributed limiter with per-minute windows and per-tool key construction.
Validation
packages/lib/src/integrations/validation/is-tool-allowed.ts, packages/lib/src/integrations/validation/is-tool-allowed.test.ts, packages/lib/src/integrations/validation/visibility.ts, packages/lib/src/integrations/validation/visibility.test.ts
Tool allowance validation respecting grant allowlists, denylists, read-only mode, and dangerous category restrictions; visibility logic for scoping integrations to drive roles (private/owned_drives/all_drives).
Repository Layer
packages/lib/src/integrations/repositories/connection-repository.ts, packages/lib/src/integrations/repositories/connection-repository.test.ts, packages/lib/src/integrations/repositories/audit-repository.ts, packages/lib/src/integrations/repositories/audit-repository.test.ts, packages/lib/src/integrations/repositories/grant-repository.ts, packages/lib/src/integrations/repositories/grant-repository.test.ts
CRUD and query operations for integration connections (create, fetch, find by user/drive, update status/credentials, list); audit log operations (insert, retrieve by drive/connection/date/success/agent/tool, count by error type); tool grant operations (create, fetch, find by agent/connection, list, delete).
Execution Saga & Orchestration
packages/lib/src/integrations/saga/execute-tool.ts, packages/lib/src/integrations/saga/execute-tool.test.ts
Comprehensive tool execution saga orchestrating connection loading, tool validation, rate limit enforcement, credential decryption, auth application, HTTP execution, output transformation, and audit logging with dependency injection for testing.
Module Exports & Documentation
packages/lib/src/integrations/index.ts, plan.md, tasks/ai-api-sandbox.md
Central index re-exporting all integration APIs (auth, validation, execution, rate limiting, repositories, saga); development plan outlining AI API Sandbox epic with status, goals, and next steps; detailed task breakdown spanning 39 items with schemas, examples, and test strategy.

Sequence Diagram

sequenceDiagram
    participant Client as Tool Caller
    participant Saga as Execute Tool Saga
    participant ConnRepo as Connection Repo
    participant Validation as Validation Layer
    participant RateLimit as Rate Limiter
    participant CredMgr as Credential Manager
    participant AuthMgr as Auth Manager
    participant HttpExec as HTTP Executor
    participant AuditRepo as Audit Logger

    Client->>Saga: executeToolSaga(request)
    
    Saga->>ConnRepo: loadConnection(connectionId)
    ConnRepo-->>Saga: ConnectionWithProvider
    
    Saga->>Validation: isToolAllowed(toolName, config)
    alt Tool Denied
        Saga->>AuditRepo: logAudit(TOOL_NOT_ALLOWED)
        Saga-->>Client: ToolCallResult(error)
    else Tool Allowed
        Saga->>RateLimit: checkIntegrationRateLimit(config)
        alt Rate Limited
            Saga->>AuditRepo: logAudit(RATE_LIMITED)
            Saga-->>Client: ToolCallResult(error, retryAfter)
        else Allowed
            Saga->>CredMgr: decryptCredentials(encrypted)
            Saga->>AuthMgr: applyAuth(credentials, authMethod)
            AuthMgr-->>Saga: headers, queryParams
            
            Saga->>HttpExec: executeHttpRequest(request)
            alt HTTP Success
                HttpExec-->>Saga: response
                Saga->>Validation: transformOutput(response)
                Saga->>AuditRepo: logAudit(success=true)
                Saga-->>Client: ToolCallResult(data)
            else HTTP Error
                HttpExec-->>Saga: error
                Saga->>AuditRepo: logAudit(success=false, error)
                Saga-->>Client: ToolCallResult(error)
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hops with joy at SQL rows and TypeScript types,
Auth methods bloom, HTTP requests take flight,
Rate limits keep order, sagas conduct the night,
Pure functions gleam without side effect's bites,
Zero-trust gardens flourish—integrations in sight! 🌿✨

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (460 files):

⚔️ .env.example (content)
⚔️ .github/workflows/security.yml (content)
⚔️ .gitignore (content)
⚔️ apps/desktop/package.json (content)
⚔️ apps/desktop/src/main/auth-storage.ts (content)
⚔️ apps/desktop/src/main/index.ts (content)
⚔️ apps/desktop/src/main/mcp-manager.ts (content)
⚔️ apps/desktop/src/main/ws-client.ts (content)
⚔️ apps/desktop/src/preload/index.ts (content)
⚔️ apps/ios/capacitor.config.ts (content)
⚔️ apps/ios/ios/App/App.xcodeproj/project.pbxproj (content)
⚔️ apps/ios/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png (content)
⚔️ apps/marketing/package.json (content)
⚔️ apps/processor/Dockerfile (content)
⚔️ apps/processor/package.json (content)
⚔️ apps/processor/src/api/ingest.ts (content)
⚔️ apps/processor/src/api/optimize.ts (content)
⚔️ apps/processor/src/api/serve.ts (content)
⚔️ apps/processor/src/api/upload.ts (content)
⚔️ apps/processor/src/cache/content-store.ts (content)
⚔️ apps/processor/src/db.ts (content)
⚔️ apps/processor/src/middleware/__tests__/auth.test.ts (content)
⚔️ apps/processor/src/middleware/auth.ts (content)
⚔️ apps/processor/src/middleware/rate-limit.ts (content)
⚔️ apps/processor/src/server.ts (content)
⚔️ apps/processor/src/services/__tests__/user-validator.test.ts (content)
⚔️ apps/processor/src/services/file-links.ts (content)
⚔️ apps/processor/src/services/rbac.ts (content)
⚔️ apps/processor/src/types/index.ts (content)
⚔️ apps/processor/src/utils/security.ts (content)
⚔️ apps/processor/src/workers/image-processor.ts (content)
⚔️ apps/processor/src/workers/ocr-processor.ts (content)
⚔️ apps/processor/src/workers/queue-manager.ts (content)
⚔️ apps/processor/src/workers/text-extractor.ts (content)
⚔️ apps/processor/tests/auth-middleware.test.ts (content)
⚔️ apps/processor/tsconfig.json (content)
⚔️ apps/processor/vitest.config.ts (content)
⚔️ apps/realtime/src/__tests__/per-event-auth.test.ts (content)
⚔️ apps/realtime/src/index.ts (content)
⚔️ apps/realtime/src/per-event-auth.ts (content)
⚔️ apps/realtime/src/validation.ts (content)
⚔️ apps/web/.gitignore (content)
⚔️ apps/web/Dockerfile (content)
⚔️ apps/web/middleware.ts (content)
⚔️ apps/web/next.config.ts (content)
⚔️ apps/web/package.json (content)
⚔️ apps/web/public/sw.js (content)
⚔️ apps/web/src/app/admin/layout.tsx (content)
⚔️ apps/web/src/app/api/account/password/route.ts (content)
⚔️ apps/web/src/app/api/account/route.ts (content)
⚔️ apps/web/src/app/api/activities/[activityId]/rollback-to-point/route.ts (content)
⚔️ apps/web/src/app/api/activities/[activityId]/rollback/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/activities/[activityId]/rollback/route.ts (content)
⚔️ apps/web/src/app/api/activities/[activityId]/route.ts (content)
⚔️ apps/web/src/app/api/activities/actors/route.ts (content)
⚔️ apps/web/src/app/api/activities/export/route.ts (content)
⚔️ apps/web/src/app/api/activities/route.ts (content)
⚔️ apps/web/src/app/api/activity/summary/route.ts (content)
⚔️ apps/web/src/app/api/admin/audit-logs/__tests__/search-security.test.ts (content)
⚔️ apps/web/src/app/api/admin/audit-logs/export/route.ts (content)
⚔️ apps/web/src/app/api/admin/audit-logs/integrity/route.ts (content)
⚔️ apps/web/src/app/api/admin/audit-logs/route.ts (content)
⚔️ apps/web/src/app/api/admin/contact/route.ts (content)
⚔️ apps/web/src/app/api/admin/global-prompt/route.ts (content)
⚔️ apps/web/src/app/api/admin/schema/route.ts (content)
⚔️ apps/web/src/app/api/admin/users/[userId]/gift-subscription/route.ts (content)
⚔️ apps/web/src/app/api/admin/users/route.ts (content)
⚔️ apps/web/src/app/api/ai/chat/messages/[messageId]/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/chat/messages/[messageId]/route.ts (content)
⚔️ apps/web/src/app/api/ai/chat/messages/[messageId]/undo/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/chat/messages/[messageId]/undo/route.ts (content)
⚔️ apps/web/src/app/api/ai/chat/messages/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/chat/messages/route.ts (content)
⚔️ apps/web/src/app/api/ai/chat/route.ts (content)
⚔️ apps/web/src/app/api/ai/global/[id]/messages/route.ts (content)
⚔️ apps/web/src/app/api/ai/global/route.ts (content)
⚔️ apps/web/src/app/api/ai/lmstudio/models/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/lmstudio/models/route.ts (content)
⚔️ apps/web/src/app/api/ai/ollama/models/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/ollama/models/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/config/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/config/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/messages/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/conversations/[conversationId]/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/conversations/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/[agentId]/conversations/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/consult/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/create/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/create/route.ts (content)
⚔️ apps/web/src/app/api/ai/page-agents/multi-drive/route.ts (content)
⚔️ apps/web/src/app/api/ai/settings/route.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/csrf.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/device-refresh.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/google-callback-redirect.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/login-redirect.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/login.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/logout.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/mcp-tokens.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/me.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/mobile-login.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/mobile-oauth-google-exchange.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/mobile-refresh.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/mobile-signup.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/signup-redirect.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/signup.test.ts (content)
⚔️ apps/web/src/app/api/auth/__tests__/verify-email.test.ts (content)
⚔️ apps/web/src/app/api/auth/desktop/exchange/route.ts (content)
⚔️ apps/web/src/app/api/auth/device/refresh/route.ts (content)
⚔️ apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts (content)
⚔️ apps/web/src/app/api/auth/google/__tests__/one-tap.test.ts (content)
⚔️ apps/web/src/app/api/auth/google/__tests__/open-redirect-protection.test.ts (content)
⚔️ apps/web/src/app/api/auth/google/callback/route.ts (content)
⚔️ apps/web/src/app/api/auth/google/native/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/auth/google/native/route.ts (content)
⚔️ apps/web/src/app/api/auth/google/one-tap/route.ts (content)
⚔️ apps/web/src/app/api/auth/mcp-tokens/route.ts (content)
⚔️ apps/web/src/app/api/auth/me/route.ts (content)
⚔️ apps/web/src/app/api/auth/mobile/login/route.ts (content)
⚔️ apps/web/src/app/api/auth/mobile/oauth/google/exchange/route.ts (content)
⚔️ apps/web/src/app/api/auth/mobile/refresh/route.ts (content)
⚔️ apps/web/src/app/api/auth/mobile/signup/route.ts (content)
⚔️ apps/web/src/app/api/auth/signup/route.ts (content)
⚔️ apps/web/src/app/api/auth/ws-token/route.ts (content)
⚔️ apps/web/src/app/api/channels/[pageId]/messages/route.ts (content)
⚔️ apps/web/src/app/api/channels/[pageId]/upload/route.ts (content)
⚔️ apps/web/src/app/api/cron/cleanup-tokens/route.ts (content)
⚔️ apps/web/src/app/api/debug/chat-messages/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/agents/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/members/[userId]/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/members/invite/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/pages/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/restore/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/search/glob/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/search/glob/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/search/regex/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/search/regex/route.ts (content)
⚔️ apps/web/src/app/api/drives/[driveId]/trash/route.ts (content)
⚔️ apps/web/src/app/api/drives/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/drives/route.ts (content)
⚔️ apps/web/src/app/api/feedback/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/files/[id]/download/route.ts (content)
⚔️ apps/web/src/app/api/files/[id]/view/route.ts (content)
⚔️ apps/web/src/app/api/mcp-ws/__tests__/route.security.test.ts (content)
⚔️ apps/web/src/app/api/mcp-ws/route.ts (content)
⚔️ apps/web/src/app/api/mcp/documents/__tests__/route.security.test.ts (content)
⚔️ apps/web/src/app/api/mcp/documents/route.ts (content)
⚔️ apps/web/src/app/api/mcp/drives/route.ts (content)
⚔️ apps/web/src/app/api/mentions/search/route.ts (content)
⚔️ apps/web/src/app/api/messages/[conversationId]/route.ts (content)
⚔️ apps/web/src/app/api/messages/conversations/route.ts (content)
⚔️ apps/web/src/app/api/messages/threads/route.ts (content)
⚔️ apps/web/src/app/api/monitoring/[metric]/route.ts (content)
⚔️ apps/web/src/app/api/notifications/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/agent-config/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/breadcrumbs/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/export/csv/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/export/csv/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/export/docx/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/export/docx/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/export/xlsx/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/export/xlsx/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/history/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/history/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/permissions/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/permissions/check/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/reprocess/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/restore/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/tasks/[taskId]/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/tasks/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/tasks/route.ts (content)
⚔️ apps/web/src/app/api/pages/[pageId]/versions/compare/route.ts (content)
⚔️ apps/web/src/app/api/pages/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/reorder/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/pages/reorder/route.ts (content)
⚔️ apps/web/src/app/api/pages/route.ts (content)
⚔️ apps/web/src/app/api/search/multi-drive/route.ts (content)
⚔️ apps/web/src/app/api/search/route.ts (content)
⚔️ apps/web/src/app/api/settings/hotkey-preferences/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/stripe/invoices/route.ts (content)
⚔️ apps/web/src/app/api/tasks/__tests__/route.test.ts (content)
⚔️ apps/web/src/app/api/tasks/route.ts (content)
⚔️ apps/web/src/app/api/track/route.ts (content)
⚔️ apps/web/src/app/api/trash/[pageId]/route.ts (content)
⚔️ apps/web/src/app/api/trash/drives/[driveId]/route.ts (content)
⚔️ apps/web/src/app/api/upload/route.ts (content)
⚔️ apps/web/src/app/api/user/recents/route.ts (content)
⚔️ apps/web/src/app/api/users/search/route.ts (content)
⚔️ apps/web/src/app/dashboard/[driveId]/[pageId]/page.tsx (content)
⚔️ apps/web/src/app/dashboard/[driveId]/activity/page.tsx (content)
⚔️ apps/web/src/app/dashboard/[driveId]/members/page.tsx (content)
⚔️ apps/web/src/app/dashboard/[driveId]/settings/page.tsx (content)
⚔️ apps/web/src/app/dashboard/[driveId]/tasks/page.tsx (content)
⚔️ apps/web/src/app/dashboard/[driveId]/trash/page.tsx (content)
⚔️ apps/web/src/app/dashboard/connections/page.tsx (content)
⚔️ apps/web/src/app/dashboard/layout.tsx (content)
⚔️ apps/web/src/app/dashboard/trash/page.tsx (content)
⚔️ apps/web/src/app/globals.css (content)
⚔️ apps/web/src/app/layout.tsx (content)
⚔️ apps/web/src/app/notifications/page.tsx (content)
⚔️ apps/web/src/app/settings/account/page.tsx (content)
⚔️ apps/web/src/app/settings/page.tsx (content)
⚔️ apps/web/src/components/activity/ActivityDashboard.tsx (content)
⚔️ apps/web/src/components/activity/ActivityFilterBar.tsx (content)
⚔️ apps/web/src/components/activity/ActivityGroupItem.tsx (content)
⚔️ apps/web/src/components/activity/ActivityItem.tsx (content)
⚔️ apps/web/src/components/activity/ActorFilter.tsx (content)
⚔️ apps/web/src/components/activity/DateRangeFilter.tsx (content)
⚔️ apps/web/src/components/admin/UsersTable.tsx (content)
⚔️ apps/web/src/components/ai/chat/input/ChatInput.tsx (content)
⚔️ apps/web/src/components/ai/chat/input/ChatTextarea.tsx (content)
⚔️ apps/web/src/components/ai/shared/AiUsageMonitor.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/CompactMessageRenderer.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/MessageRenderer.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/message-types.ts (content)
⚔️ apps/web/src/components/ai/shared/chat/tool-calls/CompactToolCallRenderer.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/tool-calls/RichContentRenderer.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/tool-calls/RichDiffRenderer.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/tool-calls/ToolCallRenderer.tsx (content)
⚔️ apps/web/src/components/ai/shared/chat/tool-calls/index.ts (content)
⚔️ apps/web/src/components/ai/shared/chat/useGroupedParts.ts (content)
⚔️ apps/web/src/components/ai/ui/confirmation.tsx (content)
⚔️ apps/web/src/components/ai/ui/tool.tsx (content)
⚔️ apps/web/src/components/auth/GoogleOneTap.tsx (content)
⚔️ apps/web/src/components/billing/UsageCounter.tsx (content)
⚔️ apps/web/src/components/common/PageTypeIcon.tsx (content)
⚔️ apps/web/src/components/dialogs/DeleteDriveDialog.tsx (content)
⚔️ apps/web/src/components/editors/MonacoEditor.tsx (content)
⚔️ apps/web/src/components/editors/RichEditor.tsx (content)
⚔️ apps/web/src/components/editors/Toolbar.tsx (content)
⚔️ apps/web/src/components/layout/Layout.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/CreatePageDialog.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/DashboardSidebar.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/DriveFooter.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/FavoritesSection.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/MemoizedSidebar.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/Pulse.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/RecentsSection.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/index.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/page-tree/PageTree.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/page-tree/PageTreeItem.tsx (content)
⚔️ apps/web/src/components/layout/left-sidebar/workspace-selector.tsx (content)
⚔️ apps/web/src/components/layout/main-header/index.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/CenterPanel.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/content-header/EditorToggles.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/content-header/ExportDropdown.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/content-header/index.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/ai-page/AiChatView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/channel/ChannelInput.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/channel/ChannelInputFooter.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/channel/ChannelView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/dashboard/GlobalAssistantView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/document/DocumentView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/file/viewers/CodeViewer.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/file/viewers/PDFViewer.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/settings/mcp/MCPSettingsView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/sheet/SheetView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/task-list/TaskKanbanView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/task-list/TaskListView.tsx (content)
⚔️ apps/web/src/components/layout/middle-content/page-views/task-list/task-list-types.ts (content)
⚔️ apps/web/src/components/layout/navbar/DriveSwitcher.tsx (content)
⚔️ apps/web/src/components/layout/right-sidebar/ai-assistant/SidebarChatTab.tsx (content)
⚔️ apps/web/src/components/layout/right-sidebar/ai-assistant/SidebarHistoryTab.tsx (content)
⚔️ apps/web/src/components/layout/right-sidebar/index.tsx (content)
⚔️ apps/web/src/components/layout/tabs/TabBar.tsx (content)
⚔️ apps/web/src/components/layout/tabs/TabItem.tsx (content)
⚔️ apps/web/src/components/mentions/SuggestionPopup.tsx (content)
⚔️ apps/web/src/components/messages/ChatInput.tsx (content)
⚔️ apps/web/src/components/messages/MessagePartRenderer.tsx (content)
⚔️ apps/web/src/components/notifications/NotificationBell.tsx (content)
⚔️ apps/web/src/components/notifications/NotificationDropdown.tsx (content)
⚔️ apps/web/src/components/search/GlobalSearch.tsx (content)
⚔️ apps/web/src/components/search/InlineSearch.tsx (content)
⚔️ apps/web/src/components/shared/RecentsDropdown.tsx (content)
⚔️ apps/web/src/components/shared/UserDropdown.tsx (content)
⚔️ apps/web/src/components/tasks/TasksDashboard.tsx (content)
⚔️ apps/web/src/components/tasks/types.ts (content)
⚔️ apps/web/src/components/ui/avatar.tsx (content)
⚔️ apps/web/src/components/ui/floating-input/InputFooter.tsx (content)
⚔️ apps/web/src/components/ui/floating-input/InputPositioner.tsx (content)
⚔️ apps/web/src/components/ui/pull-to-refresh.tsx (content)
⚔️ apps/web/src/components/version-history/VersionHistoryPanel.tsx (content)
⚔️ apps/web/src/contexts/GlobalChatContext.tsx (content)
⚔️ apps/web/src/hooks/__tests__/useAuth.test.ts (content)
⚔️ apps/web/src/hooks/__tests__/useBreadcrumbs.test.ts (content)
⚔️ apps/web/src/hooks/__tests__/useDashboardContext.test.ts (content)
⚔️ apps/web/src/hooks/__tests__/useDocument.test.ts (content)
⚔️ apps/web/src/hooks/__tests__/useFavorites.test.ts (content)
⚔️ apps/web/src/hooks/__tests__/usePageTree.test.ts (content)
⚔️ apps/web/src/hooks/__tests__/usePermissions.test.ts (content)
⚔️ apps/web/src/hooks/page-agents/usePageAgentSidebarState.ts (content)
⚔️ apps/web/src/hooks/useAiUsage.ts (content)
⚔️ apps/web/src/hooks/useAuth.ts (content)
⚔️ apps/web/src/hooks/useBreadcrumbs.ts (content)
⚔️ apps/web/src/hooks/useCapacitor.ts (content)
⚔️ apps/web/src/hooks/useDashboardContext.ts (content)
⚔️ apps/web/src/hooks/useDocument.ts (content)
⚔️ apps/web/src/hooks/useFavorites.ts (content)
⚔️ apps/web/src/hooks/useGlobalDriveSocket.ts (content)
⚔️ apps/web/src/hooks/useHotkeyPreferences.ts (content)
⚔️ apps/web/src/hooks/useIOSKeyboardInit.ts (content)
⚔️ apps/web/src/hooks/useMobile.ts (content)
⚔️ apps/web/src/hooks/usePageTree.ts (content)
⚔️ apps/web/src/hooks/usePageTreeSocket.ts (content)
⚔️ apps/web/src/hooks/usePermissions.ts (content)
⚔️ apps/web/src/hooks/usePushNotifications.ts (content)
⚔️ apps/web/src/hooks/useSpeechRecognition.ts (content)
⚔️ apps/web/src/hooks/useSuggestion.ts (content)
⚔️ apps/web/src/hooks/useTabSync.ts (content)
⚔️ apps/web/src/lib/ai/core/__tests__/ai-tools.test.ts (content)
⚔️ apps/web/src/lib/ai/core/__tests__/provider-factory.test.ts (content)
⚔️ apps/web/src/lib/ai/core/ai-providers-config.ts (content)
⚔️ apps/web/src/lib/ai/core/ai-tools.ts (content)
⚔️ apps/web/src/lib/ai/core/complete-request-builder.ts (content)
⚔️ apps/web/src/lib/ai/core/index.ts (content)
⚔️ apps/web/src/lib/ai/core/message-utils.ts (content)
⚔️ apps/web/src/lib/ai/core/model-capabilities.ts (content)
⚔️ apps/web/src/lib/ai/core/provider-factory.ts (content)
⚔️ apps/web/src/lib/ai/core/schema-introspection.ts (content)
⚔️ apps/web/src/lib/ai/core/system-prompt.ts (content)
⚔️ apps/web/src/lib/ai/core/timestamp-utils.ts (content)
⚔️ apps/web/src/lib/ai/core/tool-filtering.ts (content)
⚔️ apps/web/src/lib/ai/core/types.ts (content)
⚔️ apps/web/src/lib/ai/shared/error-messages.ts (content)
⚔️ apps/web/src/lib/ai/shared/hooks/index.ts (content)
⚔️ apps/web/src/lib/ai/shared/hooks/useMessageActions.ts (content)
⚔️ apps/web/src/lib/ai/tools/__tests__/agent-communication-tools.test.ts (content)
⚔️ apps/web/src/lib/ai/tools/__tests__/page-read-tools.test.ts (content)
⚔️ apps/web/src/lib/ai/tools/__tests__/page-write-tools.test.ts (content)
⚔️ apps/web/src/lib/ai/tools/activity-tools.ts (content)
⚔️ apps/web/src/lib/ai/tools/agent-communication-tools.ts (content)
⚔️ apps/web/src/lib/ai/tools/drive-tools.ts (content)
⚔️ apps/web/src/lib/ai/tools/page-read-tools.ts (content)
⚔️ apps/web/src/lib/ai/tools/page-write-tools.ts (content)
⚔️ apps/web/src/lib/ai/tools/search-tools.ts (content)
⚔️ apps/web/src/lib/ai/tools/task-management-tools.ts (content)
⚔️ apps/web/src/lib/auth/__tests__/admin-role-version.test.ts (content)
⚔️ apps/web/src/lib/auth/__tests__/auth-middleware.test.ts (content)
⚔️ apps/web/src/lib/auth/__tests__/auth.test.ts (content)
⚔️ apps/web/src/lib/auth/admin-role.ts (content)
⚔️ apps/web/src/lib/auth/auth-fetch.ts (content)
⚔️ apps/web/src/lib/auth/auth.ts (content)
⚔️ apps/web/src/lib/auth/index.ts (content)
⚔️ apps/web/src/lib/canvas/sample-dashboard.html (content)
⚔️ apps/web/src/lib/editor/tiptap-mention-config.tsx (content)
⚔️ apps/web/src/lib/monitoring/monitoring-queries.ts (content)
⚔️ apps/web/src/lib/onboarding/__tests__/getting-started-drive.test.ts (content)
⚔️ apps/web/src/lib/onboarding/drive-setup.ts (content)
⚔️ apps/web/src/lib/onboarding/faq/about-agent-system-prompt.ts (content)
⚔️ apps/web/src/lib/onboarding/faq/content-page-types.ts (content)
⚔️ apps/web/src/lib/onboarding/faq/example-agent-prompts.ts (content)
⚔️ apps/web/src/lib/onboarding/faq/knowledge-base.ts (content)
⚔️ apps/web/src/lib/onboarding/faq/seed-template.ts (content)
⚔️ apps/web/src/lib/repositories/chat-message-repository.ts (content)
⚔️ apps/web/src/lib/repositories/conversation-repository.ts (content)
⚔️ apps/web/src/lib/repositories/global-conversation-repository.ts (content)
⚔️ apps/web/src/lib/repositories/index.ts (content)
⚔️ apps/web/src/lib/utils/index.ts (content)
⚔️ apps/web/src/lib/websocket/__tests__/socket-utils.test.ts (content)
⚔️ apps/web/src/lib/websocket/__tests__/ws-connections.test.ts (content)
⚔️ apps/web/src/lib/websocket/index.ts (content)
⚔️ apps/web/src/lib/websocket/socket-utils.ts (content)
⚔️ apps/web/src/lib/websocket/ws-connections.ts (content)
⚔️ apps/web/src/middleware/__tests__/security-headers.test.ts (content)
⚔️ apps/web/src/middleware/monitoring.ts (content)
⚔️ apps/web/src/middleware/security-headers.ts (content)
⚔️ apps/web/src/services/api/__tests__/ai-undo-service.test.ts (content)
⚔️ apps/web/src/services/api/__tests__/rollback-service.test.ts (content)
⚔️ apps/web/src/services/api/ai-undo-service.ts (content)
⚔️ apps/web/src/services/api/page-reorder-service.ts (content)
⚔️ apps/web/src/services/api/page-service.ts (content)
⚔️ apps/web/src/services/api/permission-management-service.ts (content)
⚔️ apps/web/src/services/api/rollback-service.ts (content)
⚔️ apps/web/src/stores/__tests__/useAuthStore.test.ts (content)
⚔️ apps/web/src/stores/useAuthStore.ts (content)
⚔️ apps/web/src/stores/useDocumentManagerStore.ts (content)
⚔️ apps/web/src/stores/useLayoutStore.ts (content)
⚔️ apps/web/src/styles/tiptap.css (content)
⚔️ apps/web/src/types/google-identity.d.ts (content)
⚔️ apps/web/tsconfig.json (content)
⚔️ docker-compose.yml (content)
⚔️ docs/1.0-overview/changelog.md (content)
⚔️ docs/2.0-architecture/2.2-backend/database.md (content)
⚔️ docs/2.0-architecture/2.2-backend/permissions.md (content)
⚔️ docs/2.0-architecture/2.2-backend/processor-service.md (content)
⚔️ docs/2.0-architecture/2.5-integrations/monaco-editor.md (content)
⚔️ docs/2.0-architecture/2.5-integrations/tiptap.md (content)
⚔️ docs/2.0-architecture/2.6-features/editor-architecture.md (content)
⚔️ docs/2.0-architecture/2.6-features/file-upload.md (content)
⚔️ docs/features/local-mcp-servers.md (content)
⚔️ docs/security/zero-trust-architecture.md (content)
⚔️ package.json (content)
⚔️ packages/db/drizzle/meta/0060_snapshot.json (content)
⚔️ packages/db/drizzle/meta/_journal.json (content)
⚔️ packages/db/src/index.ts (content)
⚔️ packages/db/src/schema.ts (content)
⚔️ packages/db/src/schema/ai.ts (content)
⚔️ packages/db/src/schema/auth.ts (content)
⚔️ packages/db/src/schema/chat.ts (content)
⚔️ packages/db/src/schema/core.ts (content)
⚔️ packages/db/src/schema/dashboard.ts (content)
⚔️ packages/db/src/schema/members.ts (content)
⚔️ packages/db/src/schema/monitoring.ts (content)
⚔️ packages/db/src/schema/sessions.ts (content)
⚔️ packages/db/src/schema/storage.ts (content)
⚔️ packages/db/src/schema/tasks.ts (content)
⚔️ packages/db/src/test/factories.ts (content)
⚔️ packages/lib/package.json (content)
⚔️ packages/lib/src/__tests__/page-type-validators.test.ts (content)
⚔️ packages/lib/src/__tests__/sheet.test.ts (content)
⚔️ packages/lib/src/__tests__/token-lookup.test.ts (content)
⚔️ packages/lib/src/auth/constants.ts (content)
⚔️ packages/lib/src/auth/session-service.ts (content)
⚔️ packages/lib/src/client-safe.ts (content)
⚔️ packages/lib/src/client.ts (content)
⚔️ packages/lib/src/config/__tests__/env-validation.test.ts (content)
⚔️ packages/lib/src/config/env-validation.ts (content)
⚔️ packages/lib/src/content/index.ts (content)
⚔️ packages/lib/src/content/page-type-validators.ts (content)
⚔️ packages/lib/src/content/page-types.config.ts (content)
⚔️ packages/lib/src/file-processing/file-processor.ts (content)
⚔️ packages/lib/src/index.ts (content)
⚔️ packages/lib/src/integrations/saga/execute-tool.test.ts (content)
⚔️ packages/lib/src/integrations/saga/execute-tool.ts (content)
⚔️ packages/lib/src/logging/index.ts (content)
⚔️ packages/lib/src/logging/logger-browser.ts (content)
⚔️ packages/lib/src/logging/logger-config.ts (content)
⚔️ packages/lib/src/logging/logger-database.ts (content)
⚔️ packages/lib/src/logging/logger.ts (content)
⚔️ packages/lib/src/monitoring/activity-logger.ts (content)
⚔️ packages/lib/src/monitoring/activity-tracker.ts (content)
⚔️ packages/lib/src/permissions/index.ts (content)
⚔️ packages/lib/src/permissions/permissions-cached.ts (content)
⚔️ packages/lib/src/repositories/page-repository.ts (content)
⚔️ packages/lib/src/security/__tests__/url-validator.test.ts (content)
⚔️ packages/lib/src/security/index.ts (content)
⚔️ packages/lib/src/security/url-validator.ts (content)
⚔️ packages/lib/src/services/__tests__/rate-limit-cache.test.ts (content)
⚔️ packages/lib/src/services/agent-awareness-cache.ts (content)
⚔️ packages/lib/src/services/drive-member-service.ts (content)
⚔️ packages/lib/src/services/drive-search-service.ts (content)
⚔️ packages/lib/src/services/drive-service.ts (content)
⚔️ packages/lib/src/services/page-tree-cache.ts (content)
⚔️ packages/lib/src/services/permission-cache.ts (content)
⚔️ packages/lib/src/services/rate-limit-cache.ts (content)
⚔️ packages/lib/src/services/shared-redis.ts (content)
⚔️ packages/lib/src/services/storage-limits.ts (content)
⚔️ packages/lib/src/sheets/sheet.ts (content)
⚔️ packages/lib/src/types.ts (content)
⚔️ packages/lib/src/utils/enums.ts (content)
⚔️ packages/lib/src/utils/file-security.ts (content)
⚔️ packages/lib/tsconfig.build.json (content)
⚔️ packages/lib/tsconfig.json (content)
⚔️ pnpm-lock.yaml (content)
⚔️ types/pdf-parse.d.ts (content)

These conflicts must be resolved before merging into master.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: implementing the foundation for the AI API Sandbox epic (Tasks 1-16) with specific scope indicators.
Linked Issues check ✅ Passed The PR successfully implements all requirements from linked issue #595: comprehensive type definitions for auth methods, tool execution types, provider types, HTTP execution configs, and tool definitions are all defined and exported.
Out of Scope Changes check ✅ Passed All changes directly support the foundation layer objectives: database schema, pure functions, repositories, executors, and audit logging for the integration system are all in scope per the epic requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/ai-api-sandbox-5d2ob
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch claude/ai-api-sandbox-5d2ob
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 14

🤖 Fix all issues with AI agents
In `@packages/db/drizzle/0060_smart_fantastic_four.sql`:
- Around line 108-130: The migration currently adds FK constraints
integration_audit_log_drive_id_drives_id_fk and
integration_audit_log_connection_id_integration_connections_id_fk with ON DELETE
CASCADE which allows deletion of drives/connections to remove audit rows; change
these constraints to use ON DELETE SET NULL (or ON DELETE RESTRICT if you prefer
to block deletes) and make integration_audit_log.drive_id and
integration_audit_log.connection_id nullable in the Drizzle schema
(packages/db/src/schema/integrations.ts), then regenerate the migrations (pnpm
db:generate) so the SQL migration replaces CASCADE with SET NULL and includes
the column nullability change.
- Around line 19-28: The migration uses bare timestamp columns; update all
timestamp columns in packages/db/src/schema/integrations.ts to use timestamptz
by changing their Drizzle definitions to timestamp(..., { withTimezone: true,
mode: 'date' }) for each created_at/updated_at (and any other timestamp) column
referenced in the schemas for global_assistant_config, integration_audit_log,
integration_connections, integration_providers, and integration_tool_grants,
then regenerate the migration by running pnpm db:generate so the SQL reflects
timestamp with time zone.

In `@packages/db/drizzle/meta/_journal.json`:
- Around line 424-431: The journal shows migration "0060_smart_fantastic_four"
(idx 60) has a "when" timestamp earlier than "0059_spicy_zemo" (idx 59);
regenerate or re-create the 0060 migration so it gets a new, monotonic timestamp
(or update the migration generation step) to ensure "when" for
"0060_smart_fantastic_four" is later than "0059_spicy_zemo" and avoid confusion
when tooling inspects timestamps.

In `@packages/db/src/schema/integrations.ts`:
- Around line 302-325: The relation block integrationConnectionsRelations
defines two relations to users (the "user" relation and "connectedByUser") but
only connectedByUser sets relationName; add a relationName to the "user"
relation as well (e.g., relationName: 'user') inside the one(users, {...}) call
for the user field so both multi-user relations have explicit relationName
values matching the pattern used elsewhere (like connectedByUser).

In `@packages/lib/src/integrations/auth/apply-auth.ts`:
- Around line 52-61: The Basic auth branch (case 'basic_auth') currently uses
btoa on `${username}:${password}` which throws for non-ASCII characters; change
it to UTF-8 encode the credential string (use TextEncoder to get bytes) and then
Base64-encode those bytes in a cross-platform way before setting
headers['Authorization'] = `Basic ${encoded}`; update the logic around
usernameField, passwordField, credentials and encoded so username/password
remain checked for undefined as before and use the byte-based approach to
produce a correct RFC 7617-compliant encoded value.

In `@packages/lib/src/integrations/execution/build-request.ts`:
- Around line 14-22: The interpolatePath function currently replaces missing
path params with empty strings which creates invalid URLs (e.g.,
"/user//repos"); update interpolatePath to detect placeholders with no
corresponding key in the input and fail loudly: either throw a descriptive Error
(including the missing placeholder name(s)) or at minimum log a clear warning
before returning; modify the replace callback in interpolatePath to check for
key presence (use Object.prototype.hasOwnProperty or key in input) and aggregate
missing keys so you can include them in the thrown error or warning message,
referencing the interpolatePath function and its template placeholder handling.
- Around line 170-175: The code incorrectly casts
resolveBody(config.bodyTemplate, input) to Record<string,unknown> even though
HttpExecutionConfig.bodyTemplate can be a string; ensure you detect the
resolvedBody's runtime type before calling encodeBody: if resolvedBody is a
string, pass it through to body (or call encodeBody with 'json' only if
appropriate), and if it's an object (Record<string,unknown>) then call
encodeBody(resolvedBody, config.bodyEncoding ?? 'json'); update the logic around
resolveBody and encodeBody (variables: resolveBody, encodeBody,
config.bodyTemplate, config.bodyEncoding, body) to avoid treating string bodies
as objects so form encoding won't iterate characters mistakenly.

In `@packages/lib/src/integrations/execution/http-executor.ts`:
- Around line 148-170: The Retry-After handling in the 429 branch (where
response.status === 429) uses parseInt on response.headers.get('Retry-After')
which fails for HTTP-date values; update the logic in that block (around the
retryAfter, delayMs, retryDelayMs, attempt, sleep usage) to: if retryAfter is
numeric use it as seconds, else try to parse it as a Date (Date.parse) and
compute seconds = (dateMillis - Date.now())/1000, clamp to a minimum of 0, and
if parsing still fails or results in non-positive delay fall back to the
exponential backoff (retryDelayMs * 2^attempt); then await sleep(delayMs) as
before and continue. Ensure lastError/lastErrorType/retryCount behavior remains
unchanged.

In `@packages/lib/src/integrations/execution/transform-output.ts`:
- Around line 135-138: The current truncation conditional skips a maxLength of 0
because it uses a falsy check; update the check around the call to
truncateStrings so it runs when transform.maxLength is explicitly provided
(e.g., use a nullish or undefined check like "transform.maxLength != null" or
"typeof transform.maxLength !== 'undefined'") to ensure truncateStrings(result,
transform.maxLength) is invoked when maxLength is 0; keep the rest of the logic
unchanged and reference the existing symbols transform.maxLength,
truncateStrings, and result.

In `@packages/lib/src/integrations/rate-limit/integration-rate-limiter.test.ts`:
- Around line 30-50: Replace the inline test implementations by importing the
real exported functions buildRateLimitKey, checkIntegrationRateLimit, and
resetIntegrationRateLimit from the integration-rate-limiter module and use those
in assertions; keep mocking only the external rate limiter calls (the underlying
check/reset dependency currently represented in tests as
mockCheckRateLimit/mockResetRateLimit) so the tests verify the module's actual
key-formatting and behavior while stubbing the external store/adapter.

In `@packages/lib/src/integrations/repositories/audit-repository.test.ts`:
- Around line 47-105: Tests are exercising inline mock implementations
(logAuditEntry, getAuditLogsByDrive, getAuditLogsByConnection,
getAuditLogsByDateRange, getAuditLogsBySuccess) instead of the real functions
from audit-repository and also use a simplified object-based API that diverges
from Drizzle; replace the inline implementations by importing the real exported
functions from audit-repository and either (A) provide a mocked db that mirrors
Drizzle’s chained API (eq(), and(), desc(), findMany() shape) so the real
functions are exercised, or (B) convert the tests to integration tests against a
test database and remove the inline stubs — ensure the tests call the imported
functions and the mock or test DB implements the same query helpers the
repository expects.

In `@packages/lib/src/integrations/repositories/audit-repository.ts`:
- Around line 193-211: countAuditLogsByErrorType currently loads all matching
rows then groups/counts in JS; change it to perform aggregation in the DB using
Drizzle's count/groupBy to avoid pulling large result sets. Replace the
database.query.integrationAuditLog.findMany call and the subsequent Map loop
with a single aggregate query that selects COUNT(*) (or Drizzle's count helper)
grouped by errorType (use database.query.integrationAuditLog with sql/count and
groupBy on integrationAuditLog.errorType or equivalent column reference), then
map the returned grouped rows to the { errorType, count } shape before returning
from countAuditLogsByErrorType.

In `@packages/lib/src/integrations/saga/execute-tool.ts`:
- Around line 248-263: The catch block in execute-tool.ts currently calls
deps.logAudit and if that call throws the exception escapes instead of returning
a ToolCallResult; wrap the deps.logAudit(...) call in its own try/catch so any
errors from logAudit are swallowed/handled (e.g., log to deps.logger or console)
and ensure the outer catch always returns a ToolCallResult with success: false,
error and errorType: 'internal' (references: the catch block in
executeTool/execute-tool.ts, deps.logAudit, and the ToolCallResult return
object).

In `@plan.md`:
- Around line 7-21: Update the plan header to reflect current progress by
changing the "Status" value from "PLANNED" to an appropriate current state
(e.g., "IN PROGRESS") and revise the "Next Steps" section so the numbered tasks
reflect work beyond Task 16 (e.g., replace "Task 1–3" and the "Task 1: Core Type
Definitions" through "Task 3: Pure Tool Validation Functions" entries with a new
sequence starting at Task 17 or otherwise summarizing remaining work). Ensure
you edit the "Status" line and the "Next Steps"/task list in plan.md so they
accurately represent that tasks 1–16 are implemented and list the upcoming tasks
(Task 17+).
🧹 Nitpick comments (17)
tasks/ai-api-sandbox.md (1)

1-4: Minor doc nits from static analysis.

Line 4: "drive scoped" → "drive-scoped" (hyphenated compound adjective). Also, the fenced code blocks at lines 51 and 1104 should specify a language (e.g., text or plaintext) per markdownlint MD040.

packages/db/src/schema/integrations.ts (2)

88-91: slugIdx is redundant — the .unique() on slug already creates an index.

PostgreSQL automatically creates a unique index to enforce the UNIQUE constraint on line 60. The explicit slugIdx on line 89 is a duplicate.

Suggested fix
   (table) => ({
-    slugIdx: index('integration_providers_slug_idx').on(table.slug),
     driveIdx: index('integration_providers_drive_id_idx').on(table.driveId),
   })

245-284: Consider a retention/growth strategy for the audit log table.

integrationAuditLog will grow with every external API call. Over time, this can impact query performance and storage costs. Consider planning for:

  • A TTL-based cleanup job or time-based partitioning
  • An updatedAt column if entries are ever amended
  • Archival to cold storage for old entries

The composite index driveCreatedAtIdx is well-chosen for the most common query pattern (recent logs per drive).

packages/lib/src/integrations/rate-limit/calculate-limit.ts (1)

21-48: Zero or negative rate limits are silently accepted from connection/grant levels.

connection.requestsPerMinute and grant.requestsPerMinute are pushed into candidates without validation. A value of 0 or a negative number would pass through Math.min and become the effective limit, potentially blocking all requests or producing nonsensical results. Similarly, Math.floor(perMinute) can yield 0 for slow windows.

Consider clamping or filtering out non-positive candidates, or at minimum documenting that 0 means "blocked."

Proposed guard
   // Return most restrictive (minimum) or default
-  return candidates.length > 0 ? Math.min(...candidates) : DEFAULT_RATE_LIMIT;
+  const positive = candidates.filter((c) => c > 0);
+  return positive.length > 0 ? Math.min(...positive) : DEFAULT_RATE_LIMIT;
packages/lib/src/integrations/auth/apply-auth.ts (1)

26-95: Consider adding an exhaustive check for the switch.

If a new AuthMethod type variant is added in the future, the compiler won't flag this switch as incomplete. A default with a never assertion catches this at compile time.

Proposed addition
     case 'none':
       // No authentication needed
       break;
+
+    default: {
+      const _exhaustive: never = authMethod;
+      throw new Error(`Unhandled auth method: ${JSON.stringify(_exhaustive)}`);
+    }
   }
packages/lib/src/integrations/repositories/connection-repository.test.ts (1)

60-144: Tests re-implement repository logic instead of testing actual code.

These inline function implementations (lines 63–144) duplicate the repository API surface but don't import from connection-repository.ts. The tests verify the behavior of these local copies against their own mocks — so a bug in the real repository would go undetected.

Consider importing the real functions and mocking @pagespace/db at the module level instead, similar to how execute-tool.test.ts mocks its dependencies. This would give you actual coverage of the repository code.

packages/lib/src/integrations/execution/http-executor.ts (1)

81-91: startTime is set once — durationMs in responses reflects cumulative time across retries, not per-request latency.

This is likely intentional for the outer result, but callers (like the saga's audit log) should be aware that response.durationMs includes all retry wait times. If per-attempt timing is ever needed, you'd need a separate timer inside the loop.

packages/lib/src/integrations/rate-limit/integration-rate-limiter.ts (1)

56-61: requestsPerMinute: 0 is a dummy value to satisfy the type — consider using Pick or Omit for buildRateLimitKey.

resetIntegrationRateLimit already narrows its parameter with Pick<..., 'connectionId' | 'agentId' | 'toolName'>, but then spreads in requestsPerMinute: 0 to call buildRateLimitKey which requires the full config. Consider having buildRateLimitKey accept only the key-relevant fields:

Suggested refactor
-export const buildRateLimitKey = (config: IntegrationRateLimitConfig): string => {
+export const buildRateLimitKey = (config: Pick<IntegrationRateLimitConfig, 'connectionId' | 'agentId' | 'toolName'>): string => {
   return `integration:${config.connectionId}:${config.agentId}:${config.toolName}`;
 };

Then resetIntegrationRateLimit can call buildRateLimitKey(config) directly without the dummy field.

packages/lib/src/integrations/execution/build-request.ts (1)

121-124: Multipart encoding falls back to JSON — document or throw for unsupported encoding.

The 'multipart' case silently returns JSON, which could surprise callers. Either throw a not-implemented error or add an explicit comment in the type/docs that multipart is not yet supported.

packages/lib/src/integrations/saga/execute-tool.ts (1)

31-44: Local ConnectionWithProvider duplicates types from the repository layer.

This interface mirrors what's defined in connection-repository.ts but could drift. Consider importing or re-exporting a shared type from types.ts to keep them in sync.

packages/lib/src/integrations/repositories/connection-repository.ts (2)

22-22: ConnectionStatus is duplicated — also defined in types.ts.

This local type definition at line 22 mirrors the one in packages/lib/src/integrations/types.ts (line 156). Import it from there to avoid drift.

Suggested change
+import type { ConnectionStatus } from '../types';
+
-type ConnectionStatus = 'active' | 'expired' | 'error' | 'pending' | 'revoked';

206-222: updateConnectionCredentials implicitly resets status to 'active' — consider documenting this side effect.

This is reasonable for credential refresh flows, but callers might not expect that updating credentials also changes the connection status. A brief JSDoc note would help.

packages/lib/src/integrations/repositories/audit-repository.ts (2)

28-38: Consider handling an empty returning() result.

If the insert somehow returns no rows (e.g., a trigger-based rejection), destructuring [logged] yields undefined, and the function silently returns it despite the Promise<IntegrationAuditLogEntry> return type. A guard would make the failure explicit.

Proposed guard
   const [logged] = await database
     .insert(integrationAuditLog)
     .values(entry)
     .returning();
 
+  if (!logged) {
+    throw new Error('Failed to insert audit log entry');
+  }
+
   return logged;

20-23: No upper-bound on limit — callers can request unbounded result sets.

All query functions accept an arbitrary limit via QueryOptions. A very large value could cause memory pressure and slow queries. Consider capping it.

Example
 interface QueryOptions {
   limit?: number;
   offset?: number;
 }
+
+const MAX_QUERY_LIMIT = 1000;
+const clampLimit = (limit: number): number =>
+  Math.min(Math.max(1, limit), MAX_QUERY_LIMIT);

Then use clampLimit(limit) in each function.

packages/lib/src/integrations/types.ts (3)

276-279: ZeroTrustValidationResult uses unknown for connection and grant — loses type safety.

These fields are typed as unknown, which forces consumers to cast or narrow at every use site. Consider using the actual connection/grant types (or a lightweight subset interface) to preserve the zero-trust validation contract.

 export interface ZeroTrustValidationResult extends ValidationResult {
-  connection?: unknown;
-  grant?: unknown;
+  connection?: { id: string; status: string; providerId: string };
+  grant?: ToolGrant & { id: string };
 }

Adjust the shapes to match what the validation saga actually attaches.


148-151: Inline rate limit shape duplicates RateLimitConfig.

IntegrationProviderConfig.rateLimit repeats the same { requests: number; windowMs: number } shape already defined as RateLimitConfig on lines 104-107. Reuse the existing type.

♻️ Suggested fix
 export interface IntegrationProviderConfig {
   ...
-  rateLimit?: { requests: number; windowMs: number };
+  rateLimit?: RateLimitConfig;
 }

115-125: ToolDefinition.inputSchema could be more precisely typed.

Record<string, unknown> is very loose for what is conceptually a JSON Schema object. A minimal structural type (e.g., requiring type and properties fields) would catch misconfigurations earlier and improve developer ergonomics, while still being flexible.

Comment on lines +19 to +28
CREATE TABLE IF NOT EXISTS "global_assistant_config" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"enabled_user_integrations" jsonb,
"drive_overrides" jsonb,
"inherit_drive_integrations" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "global_assistant_config_user_id_unique" UNIQUE("user_id")
);

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files packages/db/src/schema/ | head -20

Repository: 2witstudios/PageSpace

Length of output: 790


🏁 Script executed:

# Check the structure of packages/db/src/schema
find packages/db/src/schema -type f -name "*.ts" 2>/dev/null | head -20

Repository: 2witstudios/PageSpace

Length of output: 795


🏁 Script executed:

# Also check if the main schema.ts exists and has the table definitions
cat packages/db/src/schema.ts 2>/dev/null || echo "Not found at root"

Repository: 2witstudios/PageSpace

Length of output: 2529


🏁 Script executed:

# Search for global_assistant_config table definition in schema
rg -n "global_assistant_config" packages/db/src/ --type=ts -A 5 -B 2

Repository: 2witstudios/PageSpace

Length of output: 739


🏁 Script executed:

# Get the full globalAssistantConfig table definition
sed -n '217,235p' packages/db/src/schema/integrations.ts

Repository: 2witstudios/PageSpace

Length of output: 840


🏁 Script executed:

# Check the migration file to understand what tables are being created/modified
wc -l packages/db/drizzle/0060_smart_fantastic_four.sql

Repository: 2witstudios/PageSpace

Length of output: 118


🏁 Script executed:

# Get the full migration file to see all tables
cat packages/db/drizzle/0060_smart_fantastic_four.sql

Repository: 2witstudios/PageSpace

Length of output: 9028


🏁 Script executed:

# Check all timestamp definitions in integrations.ts to verify the scope
rg -n "timestamp\(" packages/db/src/schema/integrations.ts

Repository: 2witstudios/PageSpace

Length of output: 872


🏁 Script executed:

# Check how timestamps are used in other schema files to see if there's a consistent pattern
rg -n "timestamp\(" packages/db/src/schema/ -A 1 | grep -E "(timestamp|withTimezone)" | head -30

Repository: 2witstudios/PageSpace

Length of output: 3531


🏁 Script executed:

# Check if any other schema files use withTimezone for timestamptz
rg -n "withTimezone" packages/db/src/schema/ --type=ts

Repository: 2witstudios/PageSpace

Length of output: 47


Change timestamps to include timezone information in the schema source.

All timestamps in this migration use bare timestamp without timezone context. PostgreSQL's timestamp without time zone can cause subtle bugs when server or client locale changes. Use timestamp with time zone (timestamptz) instead.

Update the timestamp definitions in packages/db/src/schema/integrations.ts to use timestamp(..., { withTimezone: true, mode: 'date' }) for all timestamp columns, then run pnpm db:generate to regenerate the migration.

This affects the following tables in the migration: global_assistant_config, integration_audit_log, integration_connections, integration_providers, and integration_tool_grants.

🤖 Prompt for AI Agents
In `@packages/db/drizzle/0060_smart_fantastic_four.sql` around lines 19 - 28, The
migration uses bare timestamp columns; update all timestamp columns in
packages/db/src/schema/integrations.ts to use timestamptz by changing their
Drizzle definitions to timestamp(..., { withTimezone: true, mode: 'date' }) for
each created_at/updated_at (and any other timestamp) column referenced in the
schemas for global_assistant_config, integration_audit_log,
integration_connections, integration_providers, and integration_tool_grants,
then regenerate the migration by running pnpm db:generate so the SQL reflects
timestamp with time zone.

Comment on lines +108 to +130
DO $$ BEGIN
ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_drive_id_drives_id_fk" FOREIGN KEY ("drive_id") REFERENCES "public"."drives"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_agent_id_pages_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."pages"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_connection_id_integration_connections_id_fk" FOREIGN KEY ("connection_id") REFERENCES "public"."integration_connections"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

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 | 🟠 Major

Audit logs deleted on cascade — defeats audit purpose.

ON DELETE CASCADE on the drive_id and connection_id FKs means deleting a drive or connection silently destroys the associated audit trail. Audit logs should survive the deletion of the entities they reference.

Consider changing these to ON DELETE SET NULL (and making drive_id/connection_id nullable on the audit table) or ON DELETE RESTRICT to preserve the audit history.

Suggested changes for the FK constraints
- ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_drive_id_drives_id_fk" FOREIGN KEY ("drive_id") REFERENCES "public"."drives"("id") ON DELETE cascade ON UPDATE no action;
+ ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_drive_id_drives_id_fk" FOREIGN KEY ("drive_id") REFERENCES "public"."drives"("id") ON DELETE set null ON UPDATE no action;
- ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_connection_id_integration_connections_id_fk" FOREIGN KEY ("connection_id") REFERENCES "public"."integration_connections"("id") ON DELETE cascade ON UPDATE no action;
+ ALTER TABLE "integration_audit_log" ADD CONSTRAINT "integration_audit_log_connection_id_integration_connections_id_fk" FOREIGN KEY ("connection_id") REFERENCES "public"."integration_connections"("id") ON DELETE set null ON UPDATE no action;

You'll also need to make drive_id and connection_id nullable in the integration_audit_log table definition (lines 32, 35) to support SET NULL. This change should originate in the Drizzle schema at packages/db/src/schema/integrations.ts and then be re-generated. Based on learnings, database schema changes must be made in packages/db/src/schema/ and then pnpm db:generate must be run to create migrations.

🤖 Prompt for AI Agents
In `@packages/db/drizzle/0060_smart_fantastic_four.sql` around lines 108 - 130,
The migration currently adds FK constraints
integration_audit_log_drive_id_drives_id_fk and
integration_audit_log_connection_id_integration_connections_id_fk with ON DELETE
CASCADE which allows deletion of drives/connections to remove audit rows; change
these constraints to use ON DELETE SET NULL (or ON DELETE RESTRICT if you prefer
to block deletes) and make integration_audit_log.drive_id and
integration_audit_log.connection_id nullable in the Drizzle schema
(packages/db/src/schema/integrations.ts), then regenerate the migrations (pnpm
db:generate) so the SQL migration replaces CASCADE with SET NULL and includes
the column nullability change.

Comment on lines +424 to 431
},
{
"idx": 60,
"version": "7",
"when": 1770002659951,
"tag": "0060_smart_fantastic_four",
"breakpoints": true
}

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 | 🟡 Minor

Migration 60 has an earlier timestamp than migration 59.

Migration 0060_smart_fantastic_four has "when": 1770002659951 which is earlier than migration 0059_spicy_zemo at "when": 1770003359293. While Drizzle applies by index order, this out-of-order timestamp suggests the migration was generated before the previous one was committed, which could cause confusion during debugging or if tooling ever sorts by timestamp. Consider regenerating the migration to get a fresh timestamp.

🤖 Prompt for AI Agents
In `@packages/db/drizzle/meta/_journal.json` around lines 424 - 431, The journal
shows migration "0060_smart_fantastic_four" (idx 60) has a "when" timestamp
earlier than "0059_spicy_zemo" (idx 59); regenerate or re-create the 0060
migration so it gets a new, monotonic timestamp (or update the migration
generation step) to ensure "when" for "0060_smart_fantastic_four" is later than
"0059_spicy_zemo" and avoid confusion when tooling inspects timestamps.

Comment on lines +302 to +325
export const integrationConnectionsRelations = relations(
integrationConnections,
({ one, many }) => ({
provider: one(integrationProviders, {
fields: [integrationConnections.providerId],
references: [integrationProviders.id],
}),
user: one(users, {
fields: [integrationConnections.userId],
references: [users.id],
}),
drive: one(drives, {
fields: [integrationConnections.driveId],
references: [drives.id],
}),
connectedByUser: one(users, {
fields: [integrationConnections.connectedBy],
references: [users.id],
relationName: 'connectedBy',
}),
toolGrants: many(integrationToolGrants),
auditLogs: many(integrationAuditLog),
})
);

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's look at the integrations.ts file to confirm the current state
cat -n packages/db/src/schema/integrations.ts | sed -n '302,325p'

Repository: 2witstudios/PageSpace

Length of output: 960


🏁 Script executed:

#!/bin/bash
# Check the users schema file for any reverse relations to integrationConnections
cat packages/db/src/schema/users.ts

Repository: 2witstudios/PageSpace

Length of output: 128


🏁 Script executed:

#!/bin/bash
# Search for other instances of multiple relations to the same table in schema files
find packages/db/src/schema -name "*.ts" -type f -exec grep -l "relations" {} \;

Repository: 2witstudios/PageSpace

Length of output: 899


🏁 Script executed:

#!/bin/bash
# Find where the users table is defined
rg -n "export const users" packages/db/src/schema/ -A 5

Repository: 2witstudios/PageSpace

Length of output: 993


🏁 Script executed:

#!/bin/bash
# Search for usersRelations to see if reverse relations are defined
rg -n "usersRelations\|relations(users" packages/db/src/schema/ -A 15

Repository: 2witstudios/PageSpace

Length of output: 172


🏁 Script executed:

#!/bin/bash
# Look for patterns with multiple one() relations to the same table in other schema files
rg -n "one\(" packages/db/src/schema/ | grep -A 5 -B 5 "one(" | head -100

Repository: 2witstudios/PageSpace

Length of output: 6387


🏁 Script executed:

#!/bin/bash
# Check members.ts for multiple user relations and relationName usage
sed -n '115,180p' packages/db/src/schema/members.ts

Repository: 2witstudios/PageSpace

Length of output: 1708


🏁 Script executed:

#!/bin/bash
# Check storage.ts for creator and linker relations
sed -n '30,70p' packages/db/src/schema/storage.ts

Repository: 2witstudios/PageSpace

Length of output: 1150


🏁 Script executed:

#!/bin/bash
# Check social.ts for its multiple user relations (user1, user2, requester, blocker, etc.)
sed -n '80,140p' packages/db/src/schema/social.ts

Repository: 2witstudios/PageSpace

Length of output: 1446


🏁 Script executed:

#!/bin/bash
# Check if there are any known issues with the current schema by looking for errors in build or migrations
fd -e "ts" -e "json" packages/db | xargs grep -l "relationName" | head -20

Repository: 2witstudios/PageSpace

Length of output: 414


🏁 Script executed:

#!/bin/bash
# Look for any drizzle configuration or validation that might enforce relationName
cat packages/db/drizzle.config.ts 2>/dev/null || cat packages/db/src/index.ts | head -50

Repository: 2witstudios/PageSpace

Length of output: 447


🏁 Script executed:

#!/bin/bash
# Check the actual integrations.ts file more carefully around the relations
cat -n packages/db/src/schema/integrations.ts | sed -n '285,330p'

Repository: 2witstudios/PageSpace

Length of output: 1848


For consistency with other multi-user relations, add relationName to the user relation as well.

integrationConnectionsRelations defines two relations to users: user (line 309) and connectedByUser (line 317). Only connectedByUser has a relationName. Following the pattern in social.ts (connections, dmConversations) where multiple relations to the same table all include relationName, apply it here for clarity:

Suggested fix
     user: one(users, {
       fields: [integrationConnections.userId],
       references: [users.id],
+      relationName: 'connectionUser',
     }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const integrationConnectionsRelations = relations(
integrationConnections,
({ one, many }) => ({
provider: one(integrationProviders, {
fields: [integrationConnections.providerId],
references: [integrationProviders.id],
}),
user: one(users, {
fields: [integrationConnections.userId],
references: [users.id],
}),
drive: one(drives, {
fields: [integrationConnections.driveId],
references: [drives.id],
}),
connectedByUser: one(users, {
fields: [integrationConnections.connectedBy],
references: [users.id],
relationName: 'connectedBy',
}),
toolGrants: many(integrationToolGrants),
auditLogs: many(integrationAuditLog),
})
);
export const integrationConnectionsRelations = relations(
integrationConnections,
({ one, many }) => ({
provider: one(integrationProviders, {
fields: [integrationConnections.providerId],
references: [integrationProviders.id],
}),
user: one(users, {
fields: [integrationConnections.userId],
references: [users.id],
relationName: 'connectionUser',
}),
drive: one(drives, {
fields: [integrationConnections.driveId],
references: [drives.id],
}),
connectedByUser: one(users, {
fields: [integrationConnections.connectedBy],
references: [users.id],
relationName: 'connectedBy',
}),
toolGrants: many(integrationToolGrants),
auditLogs: many(integrationAuditLog),
})
);
🤖 Prompt for AI Agents
In `@packages/db/src/schema/integrations.ts` around lines 302 - 325, The relation
block integrationConnectionsRelations defines two relations to users (the "user"
relation and "connectedByUser") but only connectedByUser sets relationName; add
a relationName to the "user" relation as well (e.g., relationName: 'user')
inside the one(users, {...}) call for the user field so both multi-user
relations have explicit relationName values matching the pattern used elsewhere
(like connectedByUser).

Comment on lines +52 to +61
case 'basic_auth': {
const { usernameField, passwordField } = authMethod.config;
const username = credentials[usernameField];
const password = credentials[passwordField];
if (username !== undefined && password !== undefined) {
const encoded = btoa(`${username}:${password}`);
headers['Authorization'] = `Basic ${encoded}`;
}
break;
}

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 | 🟡 Minor

btoa will throw on non-ASCII credentials.

btoa only accepts Latin1 characters. If a username or password contains non-ASCII characters (e.g., accented letters, CJK), this will throw a DOMException. Per RFC 7617, Basic auth credentials should be UTF-8 encoded before Base64 encoding.

Proposed fix using TextEncoder
-      const encoded = btoa(`${username}:${password}`);
+      const encoded = Buffer.from(`${username}:${password}`, 'utf-8').toString('base64');

Or for a cross-platform approach (browser + Node):

const bytes = new TextEncoder().encode(`${username}:${password}`);
const encoded = btoa(String.fromCharCode(...bytes));
🤖 Prompt for AI Agents
In `@packages/lib/src/integrations/auth/apply-auth.ts` around lines 52 - 61, The
Basic auth branch (case 'basic_auth') currently uses btoa on
`${username}:${password}` which throws for non-ASCII characters; change it to
UTF-8 encode the credential string (use TextEncoder to get bytes) and then
Base64-encode those bytes in a cross-platform way before setting
headers['Authorization'] = `Basic ${encoded}`; update the logic around
usernameField, passwordField, credentials and encoded so username/password
remain checked for undefined as before and use the byte-based approach to
produce a correct RFC 7617-compliant encoded value.

Comment on lines +30 to +50
// Inline implementation for testing
const buildRateLimitKey = (config: IntegrationRateLimitConfig): string => {
return `integration:${config.connectionId}:${config.agentId}:${config.toolName}`;
};

const checkIntegrationRateLimit = async (
config: IntegrationRateLimitConfig
): Promise<RateLimitResult> => {
const key = buildRateLimitKey(config);
return mockCheckRateLimit(key, {
maxAttempts: config.requestsPerMinute,
windowMs: 60 * 1000,
});
};

const resetIntegrationRateLimit = async (
config: Pick<IntegrationRateLimitConfig, 'connectionId' | 'agentId' | 'toolName'>
): Promise<void> => {
const key = buildRateLimitKey({ ...config, requestsPerMinute: 0 });
await mockResetRateLimit(key);
};

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 | 🟠 Major

Same concern: inline re-implementations instead of testing actual exports.

buildRateLimitKey, checkIntegrationRateLimit, and resetIntegrationRateLimit are re-implemented locally rather than imported from ./integration-rate-limiter. The production module's buildRateLimitKey logic or any future changes to key format would not be caught by these tests.

Import the actual functions and mock only the external rate limiter dependency they consume.

#!/bin/bash
# Verify that the test file does not import from the actual module
rg -n 'from.*integration-rate-limiter' packages/lib/src/integrations/rate-limit/integration-rate-limiter.test.ts

# Check actual exports
rg -n 'export const' packages/lib/src/integrations/rate-limit/integration-rate-limiter.ts
🤖 Prompt for AI Agents
In `@packages/lib/src/integrations/rate-limit/integration-rate-limiter.test.ts`
around lines 30 - 50, Replace the inline test implementations by importing the
real exported functions buildRateLimitKey, checkIntegrationRateLimit, and
resetIntegrationRateLimit from the integration-rate-limiter module and use those
in assertions; keep mocking only the external rate limiter calls (the underlying
check/reset dependency currently represented in tests as
mockCheckRateLimit/mockResetRateLimit) so the tests verify the module's actual
key-formatting and behavior while stubbing the external store/adapter.

Comment on lines +47 to +105
// Inline repository implementations for testing
const logAuditEntry = async (
db: MockDb,
entry: Omit<MockAuditEntry, 'id' | 'createdAt'>
): Promise<MockAuditEntry> => {
const result = await db.insert().values(entry).returning();
return result[0];
};

const getAuditLogsByDrive = async (
db: MockDb,
driveId: string,
options: { limit?: number; offset?: number } = {}
): Promise<MockAuditEntry[]> => {
return db.query.integrationAuditLog.findMany({
where: { driveId },
limit: options.limit,
offset: options.offset,
orderBy: { createdAt: 'desc' },
}) ?? [];
};

const getAuditLogsByConnection = async (
db: MockDb,
connectionId: string,
options: { limit?: number; offset?: number } = {}
): Promise<MockAuditEntry[]> => {
return db.query.integrationAuditLog.findMany({
where: { connectionId },
limit: options.limit,
offset: options.offset,
orderBy: { createdAt: 'desc' },
}) ?? [];
};

const getAuditLogsByDateRange = async (
db: MockDb,
driveId: string,
startDate: Date,
endDate: Date
): Promise<MockAuditEntry[]> => {
return db.query.integrationAuditLog.findMany({
where: { driveId, createdAt: { gte: startDate, lte: endDate } },
orderBy: { createdAt: 'desc' },
}) ?? [];
};

const getAuditLogsBySuccess = async (
db: MockDb,
driveId: string,
success: boolean,
options: { limit?: number } = {}
): Promise<MockAuditEntry[]> => {
return db.query.integrationAuditLog.findMany({
where: { driveId, success },
limit: options.limit,
orderBy: { createdAt: 'desc' },
}) ?? [];
};

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 | 🟠 Major

Tests exercise inline mock implementations, not the actual repository functions.

The test file re-implements logAuditEntry, getAuditLogsByDrive, etc. inline instead of importing them from ./audit-repository. The inline versions also diverge from the real Drizzle API — e.g., passing plain { where: { driveId } } objects instead of using eq(), and(), desc() helpers. These tests will pass regardless of whether the actual repository code is correct.

Consider either:

  1. Importing and testing the actual functions with a mocked db that matches Drizzle's chained API, or
  2. Treating these as integration tests against a real (test) database.
#!/bin/bash
# Verify that audit-repository.test.ts does not import from the actual repository module
rg -n 'from.*audit-repository' packages/lib/src/integrations/repositories/audit-repository.test.ts

# Check the actual repository module's exports for comparison
rg -n 'export const' packages/lib/src/integrations/repositories/audit-repository.ts
🤖 Prompt for AI Agents
In `@packages/lib/src/integrations/repositories/audit-repository.test.ts` around
lines 47 - 105, Tests are exercising inline mock implementations (logAuditEntry,
getAuditLogsByDrive, getAuditLogsByConnection, getAuditLogsByDateRange,
getAuditLogsBySuccess) instead of the real functions from audit-repository and
also use a simplified object-based API that diverges from Drizzle; replace the
inline implementations by importing the real exported functions from
audit-repository and either (A) provide a mocked db that mirrors Drizzle’s
chained API (eq(), and(), desc(), findMany() shape) so the real functions are
exercised, or (B) convert the tests to integration tests against a test database
and remove the inline stubs — ensure the tests call the imported functions and
the mock or test DB implements the same query helpers the repository expects.

Comment on lines +193 to +211
const logs = await database.query.integrationAuditLog.findMany({
where: whereClause,
columns: {
errorType: true,
},
});

// Group by errorType
const counts = new Map<string, number>();
for (const log of logs) {
if (log.errorType) {
counts.set(log.errorType, (counts.get(log.errorType) ?? 0) + 1);
}
}

return Array.from(counts.entries()).map(([errorType, count]) => ({
errorType,
count,
}));

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 | 🟠 Major

countAuditLogsByErrorType fetches all rows into memory to count — use SQL aggregation instead.

This function loads every matching audit log row (with no limit) just to count them in JS. For a high-traffic integration, this could pull millions of rows into Node memory. Use Drizzle's sql/count with groupBy to push the aggregation to the database.

♻️ Suggested approach using Drizzle SQL aggregation
+import { sql, count } from 'drizzle-orm';

 export const countAuditLogsByErrorType = async (
   database: typeof defaultDb,
   driveId: string,
   startDate?: Date,
   endDate?: Date
 ): Promise<Array<{ errorType: string; count: number }>> => {
-  let whereClause = eq(integrationAuditLog.driveId, driveId);
+  const conditions = [
+    eq(integrationAuditLog.driveId, driveId),
+    integrationAuditLog.errorType.isNotNull(),
+  ];
 
   if (startDate && endDate) {
-    whereClause = and(
-      whereClause,
-      gte(integrationAuditLog.createdAt, startDate),
-      lte(integrationAuditLog.createdAt, endDate)
-    )!;
+    conditions.push(gte(integrationAuditLog.createdAt, startDate));
+    conditions.push(lte(integrationAuditLog.createdAt, endDate));
   }
 
-  const logs = await database.query.integrationAuditLog.findMany({
-    where: whereClause,
-    columns: {
-      errorType: true,
-    },
-  });
-
-  // Group by errorType
-  const counts = new Map<string, number>();
-  for (const log of logs) {
-    if (log.errorType) {
-      counts.set(log.errorType, (counts.get(log.errorType) ?? 0) + 1);
-    }
-  }
-
-  return Array.from(counts.entries()).map(([errorType, count]) => ({
-    errorType,
-    count,
-  }));
+  const rows = await database
+    .select({
+      errorType: integrationAuditLog.errorType,
+      count: count(),
+    })
+    .from(integrationAuditLog)
+    .where(and(...conditions))
+    .groupBy(integrationAuditLog.errorType);
+
+  return rows.map((r) => ({
+    errorType: r.errorType!,
+    count: Number(r.count),
+  }));
 };
🤖 Prompt for AI Agents
In `@packages/lib/src/integrations/repositories/audit-repository.ts` around lines
193 - 211, countAuditLogsByErrorType currently loads all matching rows then
groups/counts in JS; change it to perform aggregation in the DB using Drizzle's
count/groupBy to avoid pulling large result sets. Replace the
database.query.integrationAuditLog.findMany call and the subsequent Map loop
with a single aggregate query that selects COUNT(*) (or Drizzle's count helper)
grouped by errorType (use database.query.integrationAuditLog with sql/count and
groupBy on integrationAuditLog.errorType or equivalent column reference), then
map the returned grouped rows to the { errorType, count } shape before returning
from countAuditLogsByErrorType.

Comment on lines +248 to +263
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';

await deps.logAudit({
success: false,
errorType: 'INTERNAL_ERROR',
errorMessage,
durationMs: Date.now() - startTime,
});

return {
success: false,
error: errorMessage,
errorType: 'internal',
};
}

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 | 🟠 Major

If logAudit throws inside the catch block, the saga throws instead of returning a ToolCallResult.

The global error handler at line 248 catches any error and calls deps.logAudit(...). If that audit call itself fails (e.g., DB connection lost), the exception propagates unhandled, breaking the contract of always returning a ToolCallResult.

Proposed fix
   } catch (error) {
     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
 
-    await deps.logAudit({
-      success: false,
-      errorType: 'INTERNAL_ERROR',
-      errorMessage,
-      durationMs: Date.now() - startTime,
-    });
+    try {
+      await deps.logAudit({
+        success: false,
+        errorType: 'INTERNAL_ERROR',
+        errorMessage,
+        durationMs: Date.now() - startTime,
+      });
+    } catch {
+      // Audit logging failure should not mask the original error
+    }
 
     return {
       success: false,
       error: errorMessage,
       errorType: 'internal',
     };
   }
🤖 Prompt for AI Agents
In `@packages/lib/src/integrations/saga/execute-tool.ts` around lines 248 - 263,
The catch block in execute-tool.ts currently calls deps.logAudit and if that
call throws the exception escapes instead of returning a ToolCallResult; wrap
the deps.logAudit(...) call in its own try/catch so any errors from logAudit are
swallowed/handled (e.g., log to deps.logger or console) and ensure the outer
catch always returns a ToolCallResult with success: false, error and errorType:
'internal' (references: the catch block in executeTool/execute-tool.ts,
deps.logAudit, and the ToolCallResult return object).

Comment thread plan.md
Comment on lines +7 to +21
**Status**: 📋 PLANNED
**Epic**: [tasks/ai-api-sandbox.md](tasks/ai-api-sandbox.md)
**Goal**: Enable AI agents to safely call external APIs with zero-trust security

A generic integration sandbox allowing:
- User integrations (personal, follow you everywhere)
- Drive integrations (team-scoped isolation)
- Multiple auth methods (OAuth, API key, Bearer, etc.)
- Built-in adapters, OpenAPI import, custom tools, MCP servers
- Zero-trust validation chain on every tool call

**Next Steps**:
1. Task 1: Core Type Definitions
2. Task 2: Pure Auth Functions
3. Task 3: Pure Tool Validation Functions

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 | 🟡 Minor

Plan status and next steps are stale.

The status is still "PLANNED" and next steps list tasks 1–3, but this PR implements tasks 1–16. Update the status (e.g., "IN PROGRESS") and the next steps to reflect the current state (e.g., Task 17+).

🤖 Prompt for AI Agents
In `@plan.md` around lines 7 - 21, Update the plan header to reflect current
progress by changing the "Status" value from "PLANNED" to an appropriate current
state (e.g., "IN PROGRESS") and revise the "Next Steps" section so the numbered
tasks reflect work beyond Task 16 (e.g., replace "Task 1–3" and the "Task 1:
Core Type Definitions" through "Task 3: Pure Tool Validation Functions" entries
with a new sequence starting at Task 17 or otherwise summarizing remaining
work). Ensure you edit the "Status" line and the "Next Steps"/task list in
plan.md so they accurately represent that tasks 1–16 are implemented and list
the upcoming tasks (Task 17+).

@2witstudios
2witstudios deleted the claude/ai-api-sandbox-5d2ob branch March 11, 2026 03:12
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.

[Epic] AI Sandbox: Core Types

2 participants