From de5935f42280fdfdb5fdd417ab9a9f1db451550d Mon Sep 17 00:00:00 2001 From: Real Codesiman Date: Wed, 16 Sep 2026 13:22:49 +0700 Subject: [PATCH 1/6] ci(coverage): gate business and worker suites on 80% coverage --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12480486fa..702347db18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,3 +131,32 @@ jobs: # contend with builder's own workers on a 4-vCPU runner. - name: Test run: pnpm turbo run test --concurrency=2 + + coverage: + name: Coverage + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Set up pnpm + uses: pnpm/action-setup@v6.1.0 + + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Set up Turbo cache + uses: rharkor/caching-for-turbo@v2.5.1 + + # Keep this starter scope narrow enough for predictable CI wall time. + # The shared Vitest preset enforces its 80% thresholds because this job + # deliberately does not set VITEST_SKIP_COVERAGE_THRESHOLDS. + - name: Verify coverage thresholds + run: pnpm --filter @chatbotx.io/business --filter worker test -- --coverage From 37b0929135285800fc2391a26fbf1fa8395fd54d Mon Sep 17 00:00:00 2001 From: Real Codesiman Date: Wed, 16 Sep 2026 13:23:21 +0700 Subject: [PATCH 2/6] ci(database): run integration tests against real postgres and redis services --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 702347db18..646a7fcb7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,3 +160,54 @@ jobs: # deliberately does not set VITEST_SKIP_COVERAGE_THRESHOLDS. - name: Verify coverage thresholds run: pnpm --filter @chatbotx.io/business --filter worker test -- --coverage + + database-integration: + name: Database integration + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + DATABASE_URL: postgresql://chatbotx:secretkey@localhost:5432/chatbotx?schema=public + REDIS_URL: redis://localhost:6379 + services: + postgres: + image: timescale/timescaledb-ha:pg18-all + env: + POSTGRES_DB: chatbotx + POSTGRES_USER: chatbotx + POSTGRES_PASSWORD: secretkey + ports: ["5432:5432"] + options: >- + --health-cmd "pg_isready -d chatbotx -U chatbotx" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + --health-start-period 20s + redis: + image: redis:8-alpine + ports: ["6379:6379"] + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Set up pnpm + uses: pnpm/action-setup@v6.1.0 + + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Migrate database from zero + run: pnpm --filter @chatbotx.io/database db:migrate + + - name: Run database integration tests + run: pnpm --filter @chatbotx.io/database test:db From 8241d455501a6c62ff04f75a51a5ce53abd85714 Mon Sep 17 00:00:00 2001 From: Real Codesiman Date: Wed, 16 Sep 2026 13:30:42 +0700 Subject: [PATCH 3/6] chore(deps): forbid direct database client imports outside data-access chain --- .dependency-cruiser.js | 90 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 9 ++++ 2 files changed, 99 insertions(+) diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js index 4affed7fed..2107fd9ff1 100644 --- a/.dependency-cruiser.js +++ b/.dependency-cruiser.js @@ -1,6 +1,96 @@ /** @type {import('dependency-cruiser').IConfiguration} */ module.exports = { forbidden: [ + { + name: "no-direct-db-import", + severity: "error", + comment: + "App and integration code must reach the database through a service or repository.", + from: { + path: "^(apps/(builder|worker)|integrations)/", + // Existing violations are an explicit ratchet baseline. New app or + // integration imports must go through a service or repository. + pathNot: [ + "apps/builder/__tests__/integration-sendgrid-api[.]test[.]ts$", + "apps/builder/__tests__/upload-logo-action[.]test[.]ts$", + "apps/builder/src/app/integrations/\\[\\.\\.\\.integration\\]/callback[.]ts$", + "apps/builder/src/app/integrations/\\[\\.\\.\\.integration\\]/webhook[.]ts$", + "apps/builder/src/features/conversations/queries/build-conversation-where[.]ts$", + "apps/builder/src/features/dynamic-images/actions/create-dynamic-image[.]action[.]ts$", + "apps/builder/src/features/dynamic-images/actions/update-dynamic-image[.]action[.]ts$", + "apps/builder/src/features/error-logs/actions/delete-error-log-action[.]ts$", + "apps/builder/src/features/integration-instagram/actions/disconnect-instagram[.]ts$", + "apps/builder/src/features/integration-instagram/actions/update-instagram-action[.]ts$", + "apps/builder/src/features/integration-instagram/queries/index[.]ts$", + "apps/builder/src/features/integration-messenger/actions/__tests__/toggle-tag-sync[.]test[.]ts$", + "apps/builder/src/features/integration-messenger/actions/disconnect-messenger[.]ts$", + "apps/builder/src/features/integration-messenger/actions/update-messenger-action[.]ts$", + "apps/builder/src/features/integration-messenger/queries/index[.]ts$", + "apps/builder/src/features/integration-whatsapp/actions/disconnect[.]action[.]ts$", + "apps/builder/src/features/integration-whatsapp/automation/actions/update-ice-breakers[.]ts$", + "apps/builder/src/features/integration-whatsapp/queries/index[.]ts$", + "apps/builder/src/features/integration-zalo/actions/__tests__/toggle-tag-sync[.]test[.]ts$", + "apps/builder/src/features/invitations/queries/index[.]ts$", + "apps/builder/src/features/magic-links/actions/delete-magic-links[.]action[.]ts$", + "apps/builder/src/features/magic-links/actions/update-magic-link[.]action[.]ts$", + "apps/builder/src/features/messages/actions/create-webchat-message[.]action[.]ts$", + "apps/builder/src/features/workspace-members/actions/delete-workspace-member[.]action[.]ts$", + "apps/builder/src/features/workspace-members/actions/invite-workspace-member[.]action[.]ts$", + "apps/builder/src/features/workspaces/actions/upload-logo[.]ts$", + "apps/builder/src/lib/safe-action[.]ts$", + "apps/worker/__tests__/coexist-whatsapp-flush-lifecycle[.]test[.]ts$", + "apps/worker/__tests__/messenger-template-handler[.]test[.]ts$", + "apps/worker/__tests__/send-messenger-template[.]test[.]ts$", + "apps/worker/__tests__/sync-channel-labels[.]test[.]ts$", + "apps/worker/__tests__/sync-tag[.]test[.]ts$", + "apps/worker/__tests__/wa-template-handler[.]test[.]ts$", + "apps/worker/__tests__/wait-for-chat-job-completion[.]test[.]ts$", + "apps/worker/src/ai-agent/handlers/process-conversation-source-embedding[.]ts$", + "apps/worker/src/ai-agent/handlers/process-pending-embeddings[.]ts$", + "apps/worker/src/chat/handlers/send-message[.]ts$", + "apps/worker/src/default/handlers/export-contacts[.]ts$", + "apps/worker/src/default/handlers/imports/handler/contacts/handler[.]ts$", + "apps/worker/src/default/handlers/send-audit-log[.]ts$", + "apps/worker/src/default/handlers/sync-channel-labels[.]ts$", + "apps/worker/src/default/handlers/sync-tag[.]ts$", + "apps/worker/src/events/error-log/handlers/write-error-log[.]ts$", + "apps/worker/src/integration/handlers/coexist/attachment-download[.]ts$", + "apps/worker/src/integration/handlers/coexist/bulk-historical-import[.]ts$", + "apps/worker/src/integration/handlers/coexist/messenger-sync[.]ts$", + "apps/worker/src/integration/handlers/flow[.]ts$", + "apps/worker/src/integration/handlers/received-message[.]ts$", + "apps/worker/src/integration/handlers/ref[.]ts$", + "apps/worker/src/integration/handlers/step-handlers[.]ts$", + "apps/worker/src/integration/utils/message[.]ts$", + "apps/worker/src/lib/db[.]ts$", + "apps/worker/src/schedule/handlers/enqueue-broadcast[.]ts$", + "apps/worker/src/schedule/handlers/maintain-mac-partitions[.]ts$", + "apps/worker/src/schedule/handlers/prepare-broadcast[.]ts$", + "apps/worker/src/schedule/handlers/process-broadcast-contacts[.]ts$", + "apps/worker/src/schedule/handlers/reconcile-broadcasts[.]ts$", + "apps/worker/src/schedule/handlers/sync-user-quota[.]ts$", + "apps/worker/src/sequence-scheduler/revert-dispatch[.]ts$", + "apps/worker/src/sequence-scheduler/services/dispatch-processor[.]service[.]ts$", + "apps/worker/src/sequence-scheduler/services/retry-scheduler[.]service[.]ts$", + "apps/worker/src/sequence-scheduler/services/step-executor[.]service[.]ts$", + "apps/worker/src/sequence-scheduler/services/types[.]ts$", + "apps/worker/src/sequence-scheduler/worker-producer[.]ts$", + "apps/worker/src/sequence-scheduler/worker[.]ts$", + "apps/worker/src/services/integrations[.]ts$", + "apps/worker/src/trigger/services/action-executor[.]ts$", + "apps/worker/src/trigger/services/condition-evaluator[.]ts$", + "apps/worker/src/trigger/services/datetime-trigger-evaluator[.]ts$", + "apps/worker/src/trigger/services/handoff-executor[.]service[.]ts$", + "apps/worker/src/trigger/services/trigger-executor[.]service[.]ts$", + "apps/worker/src/trigger/services/trigger-matcher[.]service[.]ts$", + "apps/worker/src/webhook/services/webhook-matcher[.]service[.]ts$", + "apps/worker/src/webhook/services/webhook-payload[.]builder[.]ts$", + ], + }, + to: { + path: "^packages/database/src/client[.]ts$", + }, + }, { name: "no-circular", severity: "warn", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 646a7fcb7e..7493d6b0ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,6 +93,15 @@ jobs: - name: Lint run: pnpm lint + - name: Check circular dependencies + run: pnpm check:circular + + - name: Check unused exports + run: pnpm check:unused + + - name: Check dependency boundaries + run: pnpm exec depcruise --config .dependency-cruiser.js --output-type err --focus '^packages/database/src/client[.]ts$' --focus-depth 1 apps packages integrations + test: name: Tests runs-on: ubuntu-latest From 9fd63131cc482bc1a0d4e648f097441e9f53ec70 Mon Sep 17 00:00:00 2001 From: Real Codesiman Date: Wed, 16 Sep 2026 13:40:31 +0700 Subject: [PATCH 4/6] chore(deps): dedupe zod and add pnpm dedupe check to ci --- .github/workflows/ci.yml | 3 + pnpm-lock.yaml | 497 +++++++++++++++------------------------ 2 files changed, 190 insertions(+), 310 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7493d6b0ce..b26e9652d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,6 +102,9 @@ jobs: - name: Check dependency boundaries run: pnpm exec depcruise --config .dependency-cruiser.js --output-type err --focus '^packages/database/src/client[.]ts$' --focus-depth 1 apps packages integrations + - name: Check dependency deduplication + run: pnpm dedupe --check + test: name: Tests runs-on: ubuntu-latest diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 107ac6e3fb..3c33291c2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,13 +52,13 @@ importers: version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(@vitest/coverage-v8@4.1.8)(jsdom@25.0.1)(msw@2.14.6(@types/node@24.13.1)(typescript@5.9.3))(vite@7.3.5(@types/node@24.13.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.0)(yaml@2.9.0)) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 apps/builder: dependencies: '@ai-sdk/mcp': specifier: ^2.0.32 - version: 2.0.40(zod@4.4.3) + version: 2.0.40(zod@4.5.4) '@base-ui/react': specifier: ^1.6.0 version: 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.14)(date-fns@4.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -250,7 +250,7 @@ importers: version: 13.13.0(react@19.2.5) '@modelcontextprotocol/sdk': specifier: ^1.29.0 - version: 1.29.0(zod@4.4.3) + version: 1.29.0(zod@4.5.4) '@next-safe-action/adapter-react-hook-form': specifier: ^2.0.6 version: 2.0.6(@hookform/resolvers@5.4.0(react-hook-form@7.77.0(react@19.2.5)))(next-safe-action@8.5.3(next@16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(next@16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-hook-form@7.77.0(react@19.2.5))(react@19.2.5) @@ -271,10 +271,10 @@ importers: version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/client@1.14.5(@opentelemetry/api@1.9.1))(@tanstack/query-core@5.102.8) '@orpc/zod': specifier: ^1.14.0 - version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1)(ws@8.21.0))(ws@8.21.0)(zod@4.4.3) + version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1)(ws@8.21.0))(ws@8.21.0)(zod@4.5.4) '@t3-oss/env-nextjs': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) '@tanstack/react-query': specifier: ^5.102.8 version: 5.102.8(react@19.2.5) @@ -433,7 +433,7 @@ importers: version: 3.1.1(react@19.2.5) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 zustand: specifier: ^5.0.12 version: 5.0.14(@types/react@19.2.14)(immer@11.1.15)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) @@ -488,7 +488,7 @@ importers: version: 13.1.3 postcss: specifier: ^8.5.16 - version: 8.5.16 + version: 8.5.28 prettier: specifier: ^3.9.4 version: 3.9.4 @@ -506,13 +506,13 @@ importers: dependencies: '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) yargs: specifier: ^18.0.0 version: 18.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@types/node': specifier: 24.x @@ -543,7 +543,7 @@ importers: version: link:../../packages/logger '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -552,7 +552,7 @@ importers: version: 7.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -583,16 +583,16 @@ importers: dependencies: '@modelcontextprotocol/sdk': specifier: ^1.29.0 - version: 1.29.0(zod@4.4.3) + version: 1.29.0(zod@4.5.4) '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) dotenv: specifier: ^17.4.2 version: 17.4.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@types/node': specifier: 24.x @@ -623,10 +623,10 @@ importers: version: link:../../packages/partysocket-config '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) jose: specifier: ^6.2.3 - version: 6.2.3 + version: 6.2.8 ky: specifier: ^2.0.2 version: 2.0.2 @@ -635,7 +635,7 @@ importers: version: 0.0.115 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -657,16 +657,16 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^3.0.72 - version: 3.0.81(zod@4.4.3) + version: 3.0.81(zod@4.5.4) '@ai-sdk/deepseek': specifier: ^2.0.30 - version: 2.0.35(zod@4.4.3) + version: 2.0.35(zod@4.5.4) '@ai-sdk/google': specifier: ^3.0.65 - version: 3.0.80(zod@4.4.3) + version: 3.0.80(zod@4.5.4) '@ai-sdk/openai': specifier: ^3.0.54 - version: 3.0.68(zod@4.4.3) + version: 3.0.68(zod@4.5.4) '@aws-sdk/client-s3': specifier: ^3.1039.0 version: 3.1063.0 @@ -816,10 +816,10 @@ importers: version: 1.34.0 '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) ai: specifier: ^6.0.170 - version: 6.0.197(zod@4.4.3) + version: 6.0.197(zod@4.5.4) bullmq: specifier: ^5.76.4 version: 5.78.0 @@ -888,7 +888,7 @@ importers: version: 0.18.5 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -943,7 +943,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -983,7 +983,7 @@ importers: version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1011,7 +1011,7 @@ importers: version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1045,7 +1045,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1082,7 +1082,7 @@ importers: version: 1.13.6 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1113,7 +1113,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1230,7 +1230,7 @@ importers: version: 2.37.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1282,7 +1282,7 @@ importers: version: 2.37.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1313,7 +1313,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1341,7 +1341,7 @@ importers: version: link:../../packages/sdk '@noble/hashes': specifier: ^2.2.0 - version: 2.2.0 + version: 2.3.0 ky: specifier: ^2.0.2 version: 2.0.2 @@ -1350,7 +1350,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1375,7 +1375,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1427,7 +1427,7 @@ importers: version: 2.37.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1467,7 +1467,7 @@ importers: version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1495,7 +1495,7 @@ importers: version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1523,7 +1523,7 @@ importers: version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1582,7 +1582,7 @@ importers: version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1610,7 +1610,7 @@ importers: version: 9.1.1 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1653,7 +1653,7 @@ importers: version: 2.37.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1699,7 +1699,7 @@ importers: version: 2.37.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1785,7 +1785,7 @@ importers: version: 6.2.1 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1831,7 +1831,7 @@ importers: version: 2.37.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1856,19 +1856,19 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^3.0.72 - version: 3.0.81(zod@4.4.3) + version: 3.0.81(zod@4.5.4) '@ai-sdk/deepseek': specifier: ^2.0.30 - version: 2.0.35(zod@4.4.3) + version: 2.0.35(zod@4.5.4) '@ai-sdk/google': specifier: ^3.0.65 - version: 3.0.80(zod@4.4.3) + version: 3.0.80(zod@4.5.4) '@ai-sdk/openai': specifier: ^3.0.54 - version: 3.0.68(zod@4.4.3) + version: 3.0.68(zod@4.5.4) '@ai-sdk/openai-compatible': specifier: ^2.0.56 - version: 2.0.56(zod@4.4.3) + version: 2.0.56(zod@4.5.4) '@chatbotx.io/business': specifier: workspace:* version: link:../business @@ -1892,13 +1892,13 @@ importers: version: link:../worker-config '@openrouter/ai-sdk-provider': specifier: ^2.9.1 - version: 2.9.1(ai@6.0.197(zod@4.4.3))(zod@4.4.3) + version: 2.9.1(ai@6.0.197(zod@4.5.4))(zod@4.5.4) '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) ai: specifier: ^6.0.170 - version: 6.0.197(zod@4.4.3) + version: 6.0.197(zod@4.5.4) ky: specifier: ^2.0.2 version: 2.0.2 @@ -1910,7 +1910,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -1959,7 +1959,7 @@ importers: version: 5.10.1 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2005,7 +2005,7 @@ importers: version: 1.14.5(@opentelemetry/api@1.9.1)(ws@8.21.0) '@t3-oss/env-nextjs': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) date-fns: specifier: ^4.1.0 version: 4.4.0 @@ -2023,7 +2023,7 @@ importers: version: 7.77.0(react@19.2.5) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 zustand: specifier: ^5.0.12 version: 5.0.14(@types/react@19.2.14)(immer@11.1.15)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) @@ -2075,7 +2075,7 @@ importers: version: link:../utils '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) better-auth: specifier: ^1.6.22 version: 1.6.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(next@16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.21.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.8) @@ -2084,7 +2084,7 @@ importers: version: 16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2127,10 +2127,10 @@ importers: version: link:../worker-config '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2212,7 +2212,7 @@ importers: version: 1.0.6 '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) date-fns: specifier: ^4.4.0 version: 4.4.0 @@ -2224,13 +2224,13 @@ importers: version: 10.1.0 drizzle-orm: specifier: 1.0.0-beta.22 - version: 1.0.0-beta.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(zod@4.4.3) + version: 1.0.0-beta.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(zod@4.5.4) image-size: specifier: ^2.0.2 version: 2.0.2 jose: specifier: ^6.2.3 - version: 6.2.3 + version: 6.2.8 libphonenumber-js: specifier: ^1.12.42 version: 1.13.6 @@ -2245,7 +2245,7 @@ importers: version: 1.0.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2285,7 +2285,7 @@ importers: version: link:../utils '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) countries-list: specifier: ^3.3.0 version: 3.3.0 @@ -2297,13 +2297,13 @@ importers: version: 3.2.0(date-fns@4.4.0) drizzle-orm: specifier: 1.0.0-beta.22 - version: 1.0.0-beta.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(zod@4.4.3) + version: 1.0.0-beta.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(zod@4.5.4) pg: specifier: ^8.20.0 version: 8.21.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/vitest-config': specifier: workspace:* @@ -2334,10 +2334,10 @@ importers: dependencies: '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2371,7 +2371,7 @@ importers: version: link:../worker-config zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2433,7 +2433,7 @@ importers: version: link:../utils '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) aws4fetch: specifier: ^1.0.20 version: 1.0.20 @@ -2445,7 +2445,7 @@ importers: version: 7.3.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2479,7 +2479,7 @@ importers: version: 4.4.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2519,7 +2519,7 @@ importers: version: 0.12.5 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2544,7 +2544,7 @@ importers: dependencies: zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2572,13 +2572,13 @@ importers: version: 1.34.0 '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) async-mutex: specifier: ^0.5.0 version: 0.5.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2622,7 +2622,7 @@ importers: dependencies: '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) mjml: specifier: ^5.2.0 version: 5.3.0(svgo@4.0.1)(typescript@5.9.3) @@ -2631,7 +2631,7 @@ importers: version: 9.1.1 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2687,16 +2687,16 @@ importers: version: link:../logger '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) jose: specifier: ^6.2.3 - version: 6.2.3 + version: 6.2.8 ky: specifier: ^2.0.2 version: 2.0.2 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2721,7 +2721,7 @@ importers: version: link:../logger '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) async-mutex: specifier: ^0.5.0 version: 0.5.0 @@ -2736,7 +2736,7 @@ importers: version: 2.2.6 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2786,7 +2786,7 @@ importers: version: link:../logger zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2962,7 +2962,7 @@ importers: version: 1.4.0 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -2993,7 +2993,7 @@ importers: version: 16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) postcss: specifier: ^8.5.16 - version: 8.5.16 + version: 8.5.28 tailwindcss: specifier: ^4.3.2 version: 4.3.2 @@ -3008,7 +3008,7 @@ importers: dependencies: '@t3-oss/env-core': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) date-fns: specifier: ^4.4.0 version: 4.4.0 @@ -3020,7 +3020,7 @@ importers: version: 1.3.22 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -3072,7 +3072,7 @@ importers: version: 3.2.0(date-fns@4.4.0) zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -3143,7 +3143,7 @@ importers: version: link:../sdk '@t3-oss/env-nextjs': specifier: ^0.13.11 - version: 0.13.11(typescript@5.9.3)(zod@4.4.3) + version: 0.13.11(typescript@5.9.3)(zod@4.5.4) '@types/node': specifier: 24.x version: 24.13.1 @@ -3155,7 +3155,7 @@ importers: version: 5.10.1 zod: specifier: ^4.3.6 - version: 4.4.3 + version: 4.5.4 devDependencies: '@chatbotx.io/typescript-config': specifier: workspace:* @@ -3702,9 +3702,6 @@ packages: '@codemirror/autocomplete@6.20.3': resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} - '@codemirror/commands@6.10.3': - resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} - '@codemirror/commands@6.11.0': resolution: {integrity: sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==} @@ -3881,9 +3878,6 @@ packages: '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/runtime@1.11.2': - resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} - '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} @@ -5072,12 +5066,6 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - '@napi-rs/wasm-runtime@1.2.4': resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} @@ -5155,10 +5143,6 @@ packages: resolution: {integrity: sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw==} engines: {node: '>= 20.19.0'} - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} - engines: {node: '>= 20.19.0'} - '@noble/hashes@2.3.0': resolution: {integrity: sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==} engines: {node: '>= 20.19.0'} @@ -6587,9 +6571,6 @@ packages: cpu: [arm64] os: [win32] - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -7043,11 +7024,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.21: - resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} - engines: {node: '>=6.0.0'} - hasBin: true - baseline-browser-mapping@2.11.22: resolution: {integrity: sha512-pWc4w51fBFd7mav43/zKRC+RI6f4yfzQoVlfvE8dECePyfkn1bzLp01Fj0QACcyCZyFhiEMyD2qScfKRWgWibA==} engines: {node: '>=6.0.0'} @@ -7466,9 +7442,6 @@ packages: resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} engines: {node: '>= 10'} - crelt@1.0.6: - resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} - crelt@1.0.7: resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==} @@ -8751,9 +8724,6 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} - jose@6.2.8: resolution: {integrity: sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==} @@ -9416,16 +9386,6 @@ packages: react: '*' react-dom: '*' - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.19: resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -9605,10 +9565,6 @@ packages: obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} - obug@2.1.2: - resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} - engines: {node: '>=12.20.0'} - obug@2.1.3: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} @@ -10014,10 +9970,6 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.16: - resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.23: resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} engines: {node: ^10 || ^12 || >=14} @@ -10561,9 +10513,6 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} - set-cookie-parser@3.1.2: resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} @@ -11506,9 +11455,6 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - zod@4.5.4: resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} @@ -11547,24 +11493,17 @@ packages: snapshots: - '@ai-sdk/anthropic@3.0.81(zod@4.4.3)': - dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) - zod: 4.4.3 - - '@ai-sdk/deepseek@2.0.35(zod@4.4.3)': + '@ai-sdk/anthropic@3.0.81(zod@4.5.4)': dependencies: '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) - zod: 4.4.3 + '@ai-sdk/provider-utils': 4.0.27(zod@4.5.4) + zod: 4.5.4 - '@ai-sdk/gateway@3.0.125(zod@4.4.3)': + '@ai-sdk/deepseek@2.0.35(zod@4.5.4)': dependencies: '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) - '@vercel/oidc': 3.2.0 - zod: 4.4.3 + '@ai-sdk/provider-utils': 4.0.27(zod@4.5.4) + zod: 4.5.4 '@ai-sdk/gateway@3.0.125(zod@4.5.4)': dependencies: @@ -11573,31 +11512,25 @@ snapshots: '@vercel/oidc': 3.2.0 zod: 4.5.4 - '@ai-sdk/google@3.0.80(zod@4.4.3)': + '@ai-sdk/google@3.0.80(zod@4.5.4)': dependencies: '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) - zod: 4.4.3 + '@ai-sdk/provider-utils': 4.0.27(zod@4.5.4) + zod: 4.5.4 - '@ai-sdk/mcp@2.0.40(zod@4.4.3)': + '@ai-sdk/mcp@2.0.40(zod@4.5.4)': dependencies: '@ai-sdk/provider': 4.0.8 - '@ai-sdk/provider-utils': 5.0.33(zod@4.4.3) + '@ai-sdk/provider-utils': 5.0.33(zod@4.5.4) cross-spawn: 7.0.6 pkce-challenge: 5.0.1 - zod: 4.4.3 + zod: 4.5.4 - '@ai-sdk/openai-compatible@2.0.56(zod@4.4.3)': + '@ai-sdk/openai-compatible@2.0.56(zod@4.5.4)': dependencies: '@ai-sdk/provider': 3.0.13 - '@ai-sdk/provider-utils': 4.0.35(zod@4.4.3) - zod: 4.4.3 - - '@ai-sdk/openai@3.0.68(zod@4.4.3)': - dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) - zod: 4.4.3 + '@ai-sdk/provider-utils': 4.0.35(zod@4.5.4) + zod: 4.5.4 '@ai-sdk/openai@3.0.68(zod@4.5.4)': dependencies: @@ -11605,13 +11538,6 @@ snapshots: '@ai-sdk/provider-utils': 4.0.27(zod@4.5.4) zod: 4.5.4 - '@ai-sdk/provider-utils@4.0.27(zod@4.4.3)': - dependencies: - '@ai-sdk/provider': 3.0.10 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.1.0 - zod: 4.4.3 - '@ai-sdk/provider-utils@4.0.27(zod@4.5.4)': dependencies: '@ai-sdk/provider': 3.0.10 @@ -11619,21 +11545,21 @@ snapshots: eventsource-parser: 3.1.0 zod: 4.5.4 - '@ai-sdk/provider-utils@4.0.35(zod@4.4.3)': + '@ai-sdk/provider-utils@4.0.35(zod@4.5.4)': dependencies: '@ai-sdk/provider': 3.0.13 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.1.0 - zod: 4.4.3 + zod: 4.5.4 - '@ai-sdk/provider-utils@5.0.33(zod@4.4.3)': + '@ai-sdk/provider-utils@5.0.33(zod@4.5.4)': dependencies: '@ai-sdk/provider': 4.0.8 '@standard-schema/spec': 1.1.0 '@workflow/serde': 4.1.0 eventsource-parser: 3.1.0 undici: 7.29.0 - zod: 4.4.3 + zod: 4.5.4 '@ai-sdk/provider@3.0.10': dependencies: @@ -12071,51 +11997,51 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2)': + '@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2)': dependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@opentelemetry/semantic-conventions': 1.43.0 '@standard-schema/spec': 1.1.0 - better-call: 1.3.7(zod@4.4.3) + better-call: 1.3.7(zod@4.5.4) jose: 6.2.8 kysely: 0.28.17 nanostores: 1.4.2 - zod: 4.4.3 + zod: 4.5.4 optionalDependencies: '@cloudflare/workers-types': 4.20240718.0 '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/drizzle-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/kysely-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.28.17)': + '@better-auth/kysely-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.28.17)': dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 optionalDependencies: kysely: 0.28.17 - '@better-auth/memory-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/memory-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/mongo-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/prisma-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/prisma-adapter@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/telemetry@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + '@better-auth/telemetry@1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 @@ -12212,13 +12138,6 @@ snapshots: '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 - '@codemirror/commands@6.10.3': - dependencies: - '@codemirror/language': 6.12.3 - '@codemirror/state': 6.7.0 - '@codemirror/view': 6.43.0 - '@lezer/common': 1.5.2 - '@codemirror/commands@6.11.0': dependencies: '@codemirror/language': 6.12.3 @@ -12269,7 +12188,7 @@ snapshots: dependencies: '@codemirror/state': 6.7.0 '@codemirror/view': 6.43.0 - crelt: 1.0.6 + crelt: 1.0.7 '@codemirror/search@6.7.0': dependencies: @@ -12291,7 +12210,7 @@ snapshots: '@codemirror/view@6.43.0': dependencies: '@codemirror/state': 6.7.0 - crelt: 1.0.6 + crelt: 1.0.7 style-mod: 4.1.3 w3c-keyname: 2.2.8 @@ -12436,11 +12355,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.2': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 @@ -13182,7 +13096,7 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + '@modelcontextprotocol/sdk@1.29.0(zod@4.5.4)': dependencies: '@hono/node-server': 1.19.17(hono@4.13.5) ajv: 8.20.0 @@ -13195,12 +13109,12 @@ snapshots: express: 5.2.1 express-rate-limit: 8.5.2(express@5.2.1) hono: 4.13.5 - jose: 6.2.3 + jose: 6.2.8 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) + zod: 4.5.4 + zod-to-json-schema: 3.25.2(zod@4.5.4) transitivePeerDependencies: - supports-color @@ -13283,28 +13197,28 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.11.2 - '@emnapi/runtime': 1.11.2 - '@tybys/wasm-util': 0.10.2 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.11.0 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 '@tybys/wasm-util': 0.10.3 optional: true @@ -13345,8 +13259,6 @@ snapshots: '@noble/ciphers@2.3.0': {} - '@noble/hashes@2.2.0': {} - '@noble/hashes@2.3.0': {} '@nodable/entities@2.1.1': {} @@ -13438,10 +13350,10 @@ snapshots: '@open-draft/until@2.1.0': {} - '@openrouter/ai-sdk-provider@2.9.1(ai@6.0.197(zod@4.4.3))(zod@4.4.3)': + '@openrouter/ai-sdk-provider@2.9.1(ai@6.0.197(zod@4.5.4))(zod@4.5.4)': dependencies: - ai: 6.0.197(zod@4.4.3) - zod: 4.4.3 + ai: 6.0.197(zod@4.5.4) + zod: 4.5.4 '@opentelemetry/api@1.9.1': {} @@ -13586,7 +13498,7 @@ snapshots: transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/zod@1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1)(ws@8.21.0))(ws@8.21.0)(zod@4.4.3)': + '@orpc/zod@1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1)(ws@8.21.0))(ws@8.21.0)(zod@4.5.4)': dependencies: '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) '@orpc/json-schema': 1.14.5(@opentelemetry/api@1.9.1)(ws@8.21.0) @@ -13595,7 +13507,7 @@ snapshots: '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) escape-string-regexp: 5.0.0 wildcard-match: 5.1.4 - zod: 4.4.3 + zod: 4.5.4 transitivePeerDependencies: - '@opentelemetry/api' - crossws @@ -13654,7 +13566,7 @@ snapshots: dependencies: '@emnapi/core': 1.11.1 '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true '@oxc-parser/binding-win32-arm64-msvc@0.137.0': @@ -13725,7 +13637,7 @@ snapshots: dependencies: '@emnapi/core': 1.11.0 '@emnapi/runtime': 1.11.0 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) optional: true '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': @@ -13922,7 +13834,7 @@ snapshots: dependencies: '@emnapi/core': 1.11.1 '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': @@ -14162,17 +14074,17 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@t3-oss/env-core@0.13.11(typescript@5.9.3)(zod@4.4.3)': + '@t3-oss/env-core@0.13.11(typescript@5.9.3)(zod@4.5.4)': optionalDependencies: typescript: 5.9.3 - zod: 4.4.3 + zod: 4.5.4 - '@t3-oss/env-nextjs@0.13.11(typescript@5.9.3)(zod@4.4.3)': + '@t3-oss/env-nextjs@0.13.11(typescript@5.9.3)(zod@4.5.4)': dependencies: - '@t3-oss/env-core': 0.13.11(typescript@5.9.3)(zod@4.4.3) + '@t3-oss/env-core': 0.13.11(typescript@5.9.3)(zod@4.5.4) optionalDependencies: typescript: 5.9.3 - zod: 4.4.3 + zod: 4.5.4 '@tabby_ai/hijri-converter@1.0.5': {} @@ -14242,7 +14154,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.3.2 '@tailwindcss/oxide': 4.3.2 - postcss: 8.5.16 + postcss: 8.5.28 tailwindcss: 4.3.2 '@tanstack/query-core@5.102.8': {} @@ -14476,11 +14388,6 @@ snapshots: '@turbo/windows-arm64@2.10.4': optional: true - '@tybys/wasm-util@0.10.2': - dependencies: - tslib: 2.8.1 - optional: true - '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -14670,10 +14577,10 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@uiw/codemirror-extensions-basic-setup@4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.7.0)(@codemirror/view@6.43.0)': + '@uiw/codemirror-extensions-basic-setup@4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.11.0)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.7.0)(@codemirror/view@6.43.0)': dependencies: '@codemirror/autocomplete': 6.20.3 - '@codemirror/commands': 6.10.3 + '@codemirror/commands': 6.11.0 '@codemirror/language': 6.12.3 '@codemirror/lint': 6.9.6 '@codemirror/search': 6.7.0 @@ -14683,11 +14590,11 @@ snapshots: '@uiw/react-codemirror@4.25.10(@babel/runtime@7.29.7)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.7.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.0)(codemirror@6.0.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.7 - '@codemirror/commands': 6.10.3 + '@codemirror/commands': 6.11.0 '@codemirror/state': 6.7.0 '@codemirror/theme-one-dark': 6.1.3 '@codemirror/view': 6.43.0 - '@uiw/codemirror-extensions-basic-setup': 4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.7.0)(@codemirror/view@6.43.0) + '@uiw/codemirror-extensions-basic-setup': 4.25.10(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.11.0)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.7.0)(@codemirror/view@6.43.0) codemirror: 6.0.2 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) @@ -14874,14 +14781,6 @@ snapshots: agent-base@7.1.4: {} - ai@6.0.197(zod@4.4.3): - dependencies: - '@ai-sdk/gateway': 3.0.125(zod@4.4.3) - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) - '@opentelemetry/api': 1.9.1 - zod: 4.4.3 - ai@6.0.197(zod@4.5.4): dependencies: '@ai-sdk/gateway': 3.0.125(zod@4.5.4) @@ -15009,29 +14908,27 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.11.21: {} - baseline-browser-mapping@2.11.22: {} better-auth@1.6.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(next@16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.21.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.8): dependencies: - '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) - '@better-auth/drizzle-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/kysely-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.28.17) - '@better-auth/memory-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/prisma-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/telemetry': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/core': 1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2) + '@better-auth/drizzle-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/kysely-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.28.17) + '@better-auth/memory-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.6.22(@better-auth/core@1.6.22(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.5.4))(jose@6.2.8)(kysely@0.28.17)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@noble/ciphers': 2.3.0 '@noble/hashes': 2.3.0 - better-call: 1.3.7(zod@4.4.3) + better-call: 1.3.7(zod@4.5.4) defu: 6.1.7 jose: 6.2.8 kysely: 0.28.17 nanostores: 1.4.2 - zod: 4.4.3 + zod: 4.5.4 optionalDependencies: next: 16.3.5(@opentelemetry/api@1.9.1)(@types/node@24.13.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) pg: 8.21.0 @@ -15042,14 +14939,14 @@ snapshots: - '@cloudflare/workers-types' - '@opentelemetry/api' - better-call@1.3.7(zod@4.4.3): + better-call@1.3.7(zod@4.5.4): dependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 rou3: 0.7.12 set-cookie-parser: 3.1.2 optionalDependencies: - zod: 4.4.3 + zod: 4.5.4 big-integer@1.6.52: {} @@ -15109,7 +15006,7 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.11.21 + baseline-browser-mapping: 2.11.22 caniuse-lite: 1.0.30001810 electron-to-chromium: 1.5.368 node-releases: 2.0.47 @@ -15409,8 +15306,6 @@ snapshots: crc-32: 1.2.2 readable-stream: 3.6.2 - crelt@1.0.6: {} - crelt@1.0.7: {} cron-parser@4.9.0: @@ -15753,13 +15648,13 @@ snapshots: get-tsconfig: 4.14.0 jiti: 2.7.0 - drizzle-orm@1.0.0-beta.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(zod@4.4.3): + drizzle-orm@1.0.0-beta.22(@cloudflare/workers-types@4.20240718.0)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(zod@4.5.4): optionalDependencies: '@cloudflare/workers-types': 4.20240718.0 '@opentelemetry/api': 1.9.1 '@types/pg': 8.20.0 pg: 8.21.0 - zod: 4.4.3 + zod: 4.5.4 dts-resolver@3.0.0(oxc-resolver@11.21.3): optionalDependencies: @@ -16440,7 +16335,7 @@ snapshots: headers-polyfill@5.0.1: dependencies: '@types/set-cookie-parser': 2.4.10 - set-cookie-parser: 3.1.0 + set-cookie-parser: 3.1.2 help-me@5.0.0: {} @@ -16707,8 +16602,6 @@ snapshots: jiti@2.7.0: {} - jose@6.2.3: {} - jose@6.2.8: {} joycon@3.1.1: {} @@ -16844,7 +16737,7 @@ snapshots: tinyglobby: 0.2.17 unbash: 4.0.2 yaml: 2.9.0 - zod: 4.4.3 + zod: 4.5.4 ky@2.0.2: {} @@ -17734,10 +17627,6 @@ snapshots: stacktrace-js: 2.0.2 stylis: 4.4.0 - nanoid@3.3.12: {} - - nanoid@3.3.18: {} - nanoid@3.3.19: {} nanostores@1.4.2: {} @@ -17881,8 +17770,6 @@ snapshots: obliterator@2.0.5: {} - obug@2.1.2: {} - obug@2.1.3: {} ohash@1.1.6: {} @@ -18324,12 +18211,6 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.16: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.23: dependencies: nanoid: 3.3.19 @@ -18338,7 +18219,7 @@ snapshots: postcss@8.5.28: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.19 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -18946,8 +18827,6 @@ snapshots: set-blocking@2.0.0: {} - set-cookie-parser@3.1.0: {} - set-cookie-parser@3.1.2: {} set-function-length@1.2.2: @@ -19340,7 +19219,7 @@ snapshots: hookable: 6.1.1 import-without-cache: 0.4.0 obug: 2.1.3 - picomatch: 4.0.4 + picomatch: 4.0.5 rolldown: 1.1.4 rolldown-plugin-dts: 0.26.0(oxc-resolver@11.21.3)(rolldown@1.1.4)(typescript@5.9.3) semver: 7.8.5 @@ -19407,7 +19286,7 @@ snapshots: jsonc-parser: 3.3.1 nypm: 0.6.8 yaml: 2.9.0 - zod: 4.4.3 + zod: 4.5.4 unbash@4.0.2: {} @@ -19598,7 +19477,7 @@ snapshots: expect-type: 1.3.0 magic-string: 0.30.21 pathe: 2.0.3 - picomatch: 4.0.4 + picomatch: 4.0.5 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 @@ -19637,9 +19516,9 @@ snapshots: es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 - obug: 2.1.2 + obug: 2.1.3 pathe: 2.0.3 - picomatch: 4.0.4 + picomatch: 4.0.5 std-env: 4.1.0 tinybench: 2.9.0 tinyexec: 1.2.4 @@ -19836,14 +19715,12 @@ snapshots: compress-commons: 4.1.2 readable-stream: 3.6.2 - zod-to-json-schema@3.25.2(zod@4.4.3): + zod-to-json-schema@3.25.2(zod@4.5.4): dependencies: - zod: 4.4.3 + zod: 4.5.4 zod@3.25.76: {} - zod@4.4.3: {} - zod@4.5.4: {} zustand@4.5.7(@types/react@19.2.14)(immer@11.1.15)(react@19.2.5): From 1a87c8c4da7b82487dfe3457d2b14cb89962957c Mon Sep 17 00:00:00 2001 From: Real Codesiman Date: Wed, 16 Sep 2026 13:41:02 +0700 Subject: [PATCH 5/6] chore(lint): surface excessive function length and complexity as warnings --- biome.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/biome.json b/biome.json index 62fb5e4d97..acc47916af 100644 --- a/biome.json +++ b/biome.json @@ -68,8 +68,8 @@ "noDuplicateCustomProperties": "off" }, "complexity": { - "noExcessiveLinesPerFunction": "off", - "noExcessiveCognitiveComplexity": "off" + "noExcessiveLinesPerFunction": "warn", + "noExcessiveCognitiveComplexity": "warn" }, "performance": { "noBarrelFile": "off" From 9bf136fda29d5ac9f301d3740b9b39040e04df4b Mon Sep 17 00:00:00 2001 From: Real Codesiman Date: Wed, 16 Sep 2026 13:55:45 +0700 Subject: [PATCH 6/6] test(database): add workspace-scoping ratchet and cross-workspace isolation tests --- .github/workflows/ci.yml | 3 + package.json | 3 +- .../integration/workspace-isolation.test.ts | 249 ++++++++++++++++++ .../ads-conversion-rule/repository.ts | 5 + .../check-workspace-scoping.allowlist.json | 4 + scripts/check-workspace-scoping.mjs | 225 ++++++++++++++++ 6 files changed, 488 insertions(+), 1 deletion(-) create mode 100644 packages/database/__tests__/integration/workspace-isolation.test.ts create mode 100644 scripts/check-workspace-scoping.allowlist.json create mode 100644 scripts/check-workspace-scoping.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b26e9652d9..10a20f262b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,9 @@ jobs: - name: Check unused exports run: pnpm check:unused + - name: Check workspace-scoping ratchet + run: pnpm check:workspace-scoping + - name: Check dependency boundaries run: pnpm exec depcruise --config .dependency-cruiser.js --output-type err --focus '^packages/database/src/client[.]ts$' --focus-depth 1 apps packages integrations diff --git a/package.json b/package.json index 7c61aaec8a..a49417a423 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test:coverage": "turbo run test -- --coverage", "clean": "pnpm store prune && pnpm dlx rimraf --glob '**/{node_modules,.next,.turbo}'", "check:circular": "pnpm dlx madge --circular --extensions ts,tsx --exclude generated {apps/**/src,packages/**/src}", - "check:unused": "knip" + "check:unused": "knip", + "check:workspace-scoping": "node scripts/check-workspace-scoping.mjs" }, "devDependencies": { "@biomejs/biome": "^2.4.16", diff --git a/packages/database/__tests__/integration/workspace-isolation.test.ts b/packages/database/__tests__/integration/workspace-isolation.test.ts new file mode 100644 index 0000000000..ffb75bf5e9 --- /dev/null +++ b/packages/database/__tests__/integration/workspace-isolation.test.ts @@ -0,0 +1,249 @@ +// @vitest-environment node + +/** + * Cross-workspace isolation safety net. + * + * Workspace scoping in this repo is convention-based, not enforced by + * Postgres Row-Level Security (see docs/adr/0003-workspace-isolation-strategy.md): + * every repository method that takes a `workspaceId` is trusted to filter by + * it. This suite seeds two real workspaces' worth of Contact, Conversation, + * and Message rows against a real Postgres and asserts that a read scoped to + * workspace A never returns workspace B's row for any of the three models — + * a regression here means a repository dropped its `workspaceId` filter. + * + * SKIPS itself unless `DATABASE_URL` points at a reachable database. Run + * against local docker Postgres with: + * + * pnpm --filter @chatbotx.io/database test:db + */ + +import { createId } from "@chatbotx.io/utils" +import { eq } from "drizzle-orm" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { db } from "../../src/client" +import { contactRepository } from "../../src/repositories/contact/repository" +import { findConversationAIContextState } from "../../src/repositories/conversation-ai-context/repository" +import { createMessageRepository } from "../../src/repositories/message" +import { + contactInboxModel, + contactModel, + conversationModel, + inboxModel, + messageModel, + userModel, + workspaceModel, +} from "../../src/schema" + +/** The `setup-env` sentinel: a real database never listens on port 1. */ +const NON_ROUTABLE_PORT = "1" + +function realDatabaseUrl(): string | null { + const url = process.env.DATABASE_URL + if (!url) { + return null + } + try { + return new URL(url).port === NON_ROUTABLE_PORT ? null : url + } catch { + return null + } +} + +const databaseUrl = realDatabaseUrl() + +type SeededWorkspace = { + contactId: string + contactInboxId: string + conversationId: string + messageId: string + workspaceId: string +} + +describe.skipIf(!databaseUrl)( + "cross-workspace isolation", + () => { + const seeded: SeededWorkspace[] = [] + const createdWorkspaceIds: string[] = [] + let userId: string + + async function seedWorkspace(label: string): Promise { + const [workspace] = await db + .insert(workspaceModel) + .values({ name: `isolation-test-${label}`, ownerId: userId }) + .returning({ id: workspaceModel.id }) + + const workspaceId = workspace?.id + if (!workspaceId) { + throw new Error("Failed to seed workspace") + } + createdWorkspaceIds.push(workspaceId) + + const [contact] = await db + .insert(contactModel) + .values({ workspaceId }) + .returning({ id: contactModel.id }) + const contactId = contact?.id + if (!contactId) { + throw new Error("Failed to seed contact") + } + + const [inbox] = await db + .insert(inboxModel) + .values({ + workspaceId, + name: `isolation-test-inbox-${label}`, + channel: "webchat", + sourceId: createId(), + }) + .returning({ id: inboxModel.id }) + const inboxId = inbox?.id + if (!inboxId) { + throw new Error("Failed to seed inbox") + } + + const [contactInbox] = await db + .insert(contactInboxModel) + .values({ + originalContactId: contactId, + contactId, + inboxId, + channel: "webchat", + source: "webchat", + sourceId: createId(), + }) + .returning({ id: contactInboxModel.id }) + const contactInboxId = contactInbox?.id + if (!contactInboxId) { + throw new Error("Failed to seed contact inbox") + } + + const [conversation] = await db + .insert(conversationModel) + .values({ workspaceId, contactId }) + .returning({ id: conversationModel.id }) + const conversationId = conversation?.id + if (!conversationId) { + throw new Error("Failed to seed conversation") + } + + const messageRepository = await createMessageRepository(db) + const message = await messageRepository.create({ + conversationId, + contactInboxId, + workspaceId, + messageType: "incoming", + contentType: "text", + senderType: "contact", + text: `isolation-test-message-${label}`, + }) + + return { + contactId, + contactInboxId, + conversationId, + messageId: message.id, + workspaceId, + } + } + + beforeAll(async () => { + const [user] = await db + .insert(userModel) + .values({ + email: `isolation-test-${createId()}@example.com`, + name: "Isolation Test", + emailVerified: true, + }) + .returning({ id: userModel.id }) + const id = user?.id + if (!id) { + throw new Error("Failed to seed user") + } + userId = id + + seeded.push(await seedWorkspace("a")) + seeded.push(await seedWorkspace("b")) + }) + + afterAll(async () => { + // Deliberately NOT `messageRepository.deleteById`: it (like every other + // createdAt-scoped method on IMessageRepository) matches `createdAt` + // with exact equality against a `timestamp(precision: 6)` column, but + // `pg` returns a JS `Date` truncated to millisecond precision — even + // the exact object `create()` just returned silently matches zero + // rows. That looks like a real latent bug in the shared repository + // (worth its own follow-up), so test cleanup goes around it with a + // direct id-scoped delete instead of masking it here. + for (const workspace of seeded) { + await db + .delete(messageModel) + .where(eq(messageModel.id, workspace.messageId)) + } + // Delete every workspace created in `beforeAll`, even one whose seed + // failed partway through (e.g. a later insert threw) — cascading FKs + // clean up its contact/inbox/contactInbox/conversation rows, and this + // must happen before the owning `User` row is deleted below. + for (const workspaceId of createdWorkspaceIds) { + await db + .delete(workspaceModel) + .where(eq(workspaceModel.id, workspaceId)) + } + if (userId) { + await db.delete(userModel).where(eq(userModel.id, userId)) + } + }) + + test("contact repository never returns another workspace's contact", async () => { + const [workspaceA, workspaceB] = seeded + + const ownRead = await contactRepository.findPublicById({ + workspaceId: workspaceA.workspaceId, + id: workspaceA.contactId, + }) + expect(ownRead?.id).toBe(workspaceA.contactId) + + const crossRead = await contactRepository.findPublicById({ + workspaceId: workspaceB.workspaceId, + id: workspaceA.contactId, + }) + expect(crossRead).toBeUndefined() + }) + + test("conversation AI-context lookup never crosses workspaces", async () => { + const [workspaceA, workspaceB] = seeded + + const ownRead = await findConversationAIContextState({ + conversationId: workspaceA.conversationId, + workspaceId: workspaceA.workspaceId, + }) + expect(ownRead).not.toBeNull() + + const crossRead = await findConversationAIContextState({ + conversationId: workspaceA.conversationId, + workspaceId: workspaceB.workspaceId, + }) + expect(crossRead).toBeNull() + }) + + test("message repository never returns another workspace's message", async () => { + const [workspaceA, workspaceB] = seeded + const messageRepository = await createMessageRepository(db) + const sinceTime = new Date(Date.now() - 60_000) + + const ownRead = await messageRepository.findManyByConversation( + workspaceA.conversationId, + { limit: 10, sinceTime, workspaceId: workspaceA.workspaceId }, + ) + expect(ownRead.map((message) => message.id)).toContain( + workspaceA.messageId, + ) + + const crossRead = await messageRepository.findManyByConversation( + workspaceA.conversationId, + { limit: 10, sinceTime, workspaceId: workspaceB.workspaceId }, + ) + expect(crossRead).toHaveLength(0) + }) + }, + 30_000, +) diff --git a/packages/database/src/repositories/ads-conversion-rule/repository.ts b/packages/database/src/repositories/ads-conversion-rule/repository.ts index 58affab477..5fd1ca86b0 100644 --- a/packages/database/src/repositories/ads-conversion-rule/repository.ts +++ b/packages/database/src/repositories/ads-conversion-rule/repository.ts @@ -21,6 +21,11 @@ export type AdsConversionRuleUpdateValues = Partial< Omit > +// `update`/`delete` below build their `where` through this helper rather +// than an inline `eq(...)`, so `scripts/check-workspace-scoping.mjs`'s +// textual scan can't see the `workspaceId` filter and flags them — +// allowlisted in check-workspace-scoping.allowlist.json as verified false +// positives. Both methods DO filter by workspaceId, here. const workspaceRuleFilter = (input: { id: string; workspaceId: string }) => and( eq(adsConversionRuleModel.id, input.id), diff --git a/scripts/check-workspace-scoping.allowlist.json b/scripts/check-workspace-scoping.allowlist.json new file mode 100644 index 0000000000..5ad56c8fd1 --- /dev/null +++ b/scripts/check-workspace-scoping.allowlist.json @@ -0,0 +1,4 @@ +[ + "packages/database/src/repositories/ads-conversion-rule/repository.ts delete", + "packages/database/src/repositories/ads-conversion-rule/repository.ts update" +] diff --git a/scripts/check-workspace-scoping.mjs b/scripts/check-workspace-scoping.mjs new file mode 100644 index 0000000000..85c3a5c92d --- /dev/null +++ b/scripts/check-workspace-scoping.mjs @@ -0,0 +1,225 @@ +import { readdir, readFile, writeFile } from "node:fs/promises" +import path from "node:path" +import ts from "typescript" + +/** + * Workspace-isolation ratchet. + * + * Every repository under `packages/database/src/repositories` is trusted, by + * convention, to filter by `workspaceId` whenever a caller passes one in — + * there is no Postgres Row-Level Security backing this (see + * docs/adr/0003-workspace-isolation-strategy.md). A repository function that + * accepts a `workspaceId` parameter but never references it in its body is a + * strong signal that filter got dropped, and a real cross-workspace leak. + * + * This is a heuristic static scan, not a proof: it flags a function as a + * violation when its body's source text never mentions the `workspaceId` + * identifier inside anything that looks like a `where(`/`eq(`/`and(` call. + * It is deliberately loose (a function that only uses `workspaceId` for a + * cache key, not a filter, still counts as "referenced") because the goal is + * catching the "declared but silently ignored" failure mode, not modeling + * Drizzle's query builder. + * + * New violations fail CI. Existing ones are frozen in + * `check-workspace-scoping.allowlist.json` (regenerate it with `--update` + * after a deliberate change) so this ships as a ratchet, not a blocking + * rewrite of every repository in one PR. + */ + +const REPOSITORIES_DIR = "packages/database/src/repositories" +const ALLOWLIST_PATH = new URL( + "./check-workspace-scoping.allowlist.json", + import.meta.url, +) +const SCOPING_CALL_PATTERN = /\b(where|eq|and)\s*\(/ + +async function collectSourceFiles(dir) { + const entries = await readdir(dir, { withFileTypes: true }) + const files = [] + for (const entry of entries) { + const entryPath = path.join(dir, entry.name) + if (entry.isDirectory()) { + files.push(...(await collectSourceFiles(entryPath))) + } else if (entry.isFile() && entry.name.endsWith(".ts")) { + files.push(entryPath) + } + } + return files +} + +/** + * Textual tokens that count as "this parameter's workspaceId got used" in + * the function body: + * - a plain `workspaceId` parameter, or a destructured `{ workspaceId }` + * element, is referenced as the bare identifier `workspaceId`; + * - a parameter typed inline as `{ workspaceId: string; ... }` without + * destructuring (the dominant style in this codebase, e.g. + * `findPublicById(input: { workspaceId: string; id: string })`) is + * referenced as `.workspaceId` — destructuring it locally + * inside the body (`const { workspaceId } = input`) also produces the + * bare identifier, so both tokens are checked regardless of which type + * annotation shape matched. + */ +function collectWorkspaceIdAccessTokens(param) { + if (ts.isIdentifier(param.name)) { + if (param.name.text === "workspaceId") { + return ["workspaceId"] + } + const hasWorkspaceIdProperty = + param.type && + ts.isTypeLiteralNode(param.type) && + param.type.members.some( + (member) => member.name && member.name.getText() === "workspaceId", + ) + return hasWorkspaceIdProperty + ? [`${param.name.text}.workspaceId`, "workspaceId"] + : [] + } + if (ts.isObjectBindingPattern(param.name)) { + const hasWorkspaceId = param.name.elements.some( + (element) => + ts.isIdentifier(element.name) && element.name.text === "workspaceId", + ) + return hasWorkspaceId ? ["workspaceId"] : [] + } + return [] +} + +function findViolations(filePath, sourceText) { + const sourceFile = ts.createSourceFile( + filePath, + sourceText, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ) + const violations = [] + + function checkFunctionLike(node, label) { + if (!node.body) { + return + } + const tokens = node.parameters.flatMap((param) => + collectWorkspaceIdAccessTokens(param), + ) + if (tokens.length === 0) { + return + } + + const bodyText = node.body.getText(sourceFile) + const referencesWorkspaceId = tokens.some((token) => + bodyText.includes(token), + ) + const looksLikeAQuery = SCOPING_CALL_PATTERN.test(bodyText) + + if (looksLikeAQuery && !referencesWorkspaceId) { + const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart()) + // The allowlist key deliberately excludes the line number: an edit + // above a flagged function would otherwise shift its line and make + // the entry silently "resolve" and reappear as a false new violation. + violations.push({ + key: `${filePath} ${label}`, + display: `${filePath}:${line + 1} ${label}`, + }) + } + } + + function visit(node) { + if ( + (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && + node.name + ) { + checkFunctionLike(node, node.name.getText(sourceFile)) + } else if ( + ts.isPropertyAssignment(node) && + (ts.isArrowFunction(node.initializer) || + ts.isFunctionExpression(node.initializer)) + ) { + checkFunctionLike(node.initializer, node.name.getText(sourceFile)) + } else if ( + ts.isVariableDeclaration(node) && + node.initializer && + (ts.isArrowFunction(node.initializer) || + ts.isFunctionExpression(node.initializer)) && + ts.isIdentifier(node.name) + ) { + checkFunctionLike(node.initializer, node.name.text) + } + ts.forEachChild(node, visit) + } + + visit(sourceFile) + return violations +} + +async function loadAllowlist() { + try { + const raw = await readFile(ALLOWLIST_PATH, "utf8") + return new Set(JSON.parse(raw)) + } catch (error) { + if (error.code === "ENOENT") { + return new Set() + } + throw error + } +} + +async function main() { + const update = process.argv.includes("--update") + const root = process.cwd() + const repositoriesDir = path.join(root, REPOSITORIES_DIR) + const files = await collectSourceFiles(repositoriesDir) + + const violations = [] + for (const file of files) { + const relativePath = path.relative(root, file) + const sourceText = await readFile(file, "utf8") + violations.push(...findViolations(relativePath, sourceText)) + } + violations.sort((a, b) => a.key.localeCompare(b.key)) + + if (update) { + const keys = violations.map((v) => v.key) + await writeFile(ALLOWLIST_PATH, `${JSON.stringify(keys, null, 2)}\n`) + console.log( + `Wrote ${keys.length} baseline violation(s) to ${path.relative(root, ALLOWLIST_PATH.pathname)}`, + ) + return + } + + const allowlist = await loadAllowlist() + const currentKeys = new Set(violations.map((v) => v.key)) + const newViolations = violations.filter((v) => !allowlist.has(v.key)) + const resolved = [...allowlist].filter((key) => !currentKeys.has(key)) + + if (resolved.length > 0) { + console.log( + `${resolved.length} previously-allowlisted violation(s) no longer reproduce — run with --update to shrink the baseline:`, + ) + for (const entry of resolved) { + console.log(` - ${entry}`) + } + } + + if (newViolations.length > 0) { + console.error( + `Found ${newViolations.length} new function(s) that take a workspaceId but never reference it in a where/eq/and call:`, + ) + for (const violation of newViolations) { + console.error(` - ${violation.display}`) + } + console.error( + "\nIf this is a real bug, add the missing workspaceId filter. If it's a false positive, add it to " + + "scripts/check-workspace-scoping.allowlist.json (or run this script with --update) with a comment " + + "explaining why.", + ) + process.exitCode = 1 + return + } + + console.log( + `Workspace-scoping check passed: ${violations.length} known violation(s), 0 new.`, + ) +} + +await main()