- NEW test/helpers/mock-ai-provider.ts: Mock AI HTTP server with
normal/timeout/429/500/invalid-json behavior controls
- NEW test/helpers/integration-harness.ts: spawn real API/Worker
processes, HTTP helpers, PrismaClient-based DB helpers
- NEW test/m-ai-01-09.worker-int-spec.ts: real integration test
using actual MySQL/Redis/BullMQ/HTTP (Active Recall, Feynman,
Document Import, SIGKILL recovery, provider faults, idempotency)
- NEW test/jest-worker-integration.json: Jest config with NO mocks
- DELETE test/m-ai-01-09.e2e-spec.ts: removed mock-based pseudo-e2e
- UPDATE test/run-integration-ci.sh: uses real integration config
- UPDATE package.json: test:integration:worker uses real config
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- TestingModule.compile() returns an application context directly
- Use $executeRaw tagged template instead of $executeRawUnsafe
- Add jest.setTimeout(30000) for integration tests
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- test/m-ai-01-09-integration.spec.ts: Active Recall/Feynman/Document Import
E2E tests with real MySQL/Redis/BullMQ
- test/run-integration-ci.sh: CI script (MySQL→Prisma→Build→API→Worker→Test)
- npm run test:integration:worker: one-command integration test
- Includes Active Recall chain audit (actual code paths with file:line)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Notification jobs are triggered implicitly by notifyJobComplete() during
Active Recall analysis, not by a dedicated test endpoint. Script now
checks Worker logs and DB records instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ProtectSystem=strict requires /run/systemd/unit-root paths to exist
which fails on production. Worker only needs network access (Redis/MySQL).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extract queue name constants to queue.constants.ts to resolve
the require cycle that caused 'Cannot read properties of undefined'
at startup. Both queue-definitions.ts and queue.service.ts now
import from the shared constants file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Freeze the architecture decision for 8-batch unified AI migration.
Covers current/target topology, API/Worker process boundaries,
5 Processors, Heavy Runtime disposition, phase-1 non-goals,
rollback principles, and cross-batch constraints.
Includes Appendix A: worker dependency closure audit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- New shared helper: src/common/helpers/name-resolver.ts
- Applied to: reviews, users/members, files, knowledge-bases, imports
- Learning service refactored to use shared helper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All list endpoints now return userName, kbName, materialName
resolved from User, KnowledgeBase, KnowledgeSource tables.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prevents 500 from crashing the entire batch. Logs the actual error
to server log for debugging.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS sends knowledgeBaseId in batch upload but DTO didn't have it,
causing forbidNonWhitelisted to reject with 400.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS ImportStatusResponse expects 'id' field. The mismatch caused
DecodingError.keyNotFound on the client side.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add timestamp to lock key to prevent blocking retries
- Wrap create logic in try-finally to always release lock
- Reduce lock TTL from 30min to 5min
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DocumentImport.userId is a required foreign key, but the iOS app
doesn't send it. Extract from JWT via @CurrentUser() decorator
and pass to service.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allows users to manually retry source parsing by creating a new
DocumentImport job and enqueuing it. Returns jobId for polling.
Route: POST /knowledge-bases/:kbId/sources/:id/parse
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KnowledgeSourceService.addSource() was creating Source + DocumentImport
records but never enqueuing the job for processing. Also the worker
wasn't linking created KnowledgeItems back to their source.
Changes:
- Inject QueueService + RedisService into KnowledgeSourceService
- Enqueue document-import job after creating Source + DocumentImport
- Set Redis status keys (matching DocumentImportService pattern)
- Worker: resolve sourceId from job data or DocumentImport record
- Worker: pass sourceRef when creating KnowledgeItems
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Query string params (page=1&limit=5) arrive as strings but DTOs expect
@IsInt(). enableImplicitConversion tells class-transformer to auto-cast
types based on the DTO decorators.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Move AdminLearningService + DTOs to learning-session module
- Merge 21 new endpoints into existing admin-api/learning controller
- Add analysis and ai-usage methods to unified service
- Delete admin-learning module (no longer needed)
- Revert JwtAuthGuard /api/admin bypass (was breaking isolation)
- Fix: /api/* now exclusively serves user/iOS traffic again
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
request.path includes the global 'api' prefix, so /admin/learning
routes appear as /api/admin/learning. Bypass /api/admin in addition
to existing /admin-api and /internal paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Global JwtAuthGuard was blocking /admin/learning/* requests before
AdminAuthGuard could process x-api-key. Now bypasses all /admin/*
paths (not just /admin-api/*), letting controller-level admin auth
handle those routes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add AdminApiKey model (keyHash, expiresAt nullable for permanent)
- Extend AdminAuthGuard to accept x-api-key header as fallback auth
- Seed creates test-admin@zhixi.com with permanent SUPER_ADMIN API key
- Key format: zxat_<64 hex chars>, stored as SHA-256 hash
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dashboard filter dropdown calls this to populate KB selector.
Returns knowledge bases that have reading event data with real titles.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 3-stage: validate → dry-run → deploy-migration
- validate: prisma format check, client generation verification
- dry-run: MySQL service container, db push + migrate deploy + status
- deploy-migration: manual trigger, SSH migrate deploy with pre/post status
- Triggered on schema.prisma or migrations/** changes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Record previous docker images before deploy
- Health check loop: 12 attempts × 5s = 60s total
- On failure: warning + previous images printed for manual rollback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add health checks: api (GET /health), worker, heavy-runtime, nginx
- Add resource limits (deploy.resources) for all 6 services
- Add heavy-runtime service (Rust AI Worker)
- Add INTERNAL_API_KEY + CREDENTIAL_ENCRYPTION_KEY env vars
- nginx health check via GET /health
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Complete error code enumeration: Internal API + User API + Runtime errors
- Rate limiting: user quota, platform circuit breaker, global throttle
- Changelog: v1.0.0 initial → v1.1.0 current with all changes
- Integration examples: heartbeat cancel detection, job creation idempotency
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace hardcoded 'https://api.deepseek.com/v1/models' with config lookup
- Single source of truth via ai.deepseek.baseUrl (DEEPSEEK_BASE_URL env)
- Add ConfigService dep to UserAiService constructor + test mock
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>