import { Body, Controller, HttpCode, Inject, Param, Post, UseGuards } from "@nestjs/common"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { InternalServiceGuard } from "../../common/guards/internal-service.guard.js"; import { AiTestSeamService } from "./ai-test-seam.service.js"; import { AiUsageLimitsDto, MockAssistantDto, MockDbInvalidDto, MockFailDto, MockGenerateSeamDto, MockUnknownBlockIdDto, type AiUsageLimitsInput, type MockAssistantInput, type MockDbInvalidInput, type MockFailInput, type MockGenerateSeamInput, type MockUnknownBlockIdInput } from "./ai-test-seam.dto.js"; /** * Test-only AI seam controller (TASK-0122). * * - `POST /internal/workspaces/:workspaceId/ai/mock-fail` — * `{status?, deltas?}`: arm a deterministic mid-stream outage on the * next mock chat call FOR THAT WORKSPACE. * - `POST /internal/workspaces/:workspaceId/ai/usage-limits` — * `{aiMonthlyTokens?|null}`: set/clear the monthly token budget. * - `POST /internal/workspaces/:workspaceId/ai/mock-unknown-id` (TASK-0123) * — `{unknownId}`: arm the next mock WRITING call to emit a forged * blockId (the unknown-ID-drop validation seam). * * Gating (two independent switches, both fail-closed) — identical to * `InternalTestSeamController`: `InternalServiceGuard` (shared * `INTERNAL_SERVICE_TOKEN`, constant-time compare, user JWTs rejected) AND * `INTERNAL_TEST_SEAM=1` re-checked in the service. * * EXCLUDED from the global `api/v1` prefix (like the other `/internal/*` * routes — see main.ts): the canonical paths are the ones above. */ @Controller("internal") @UseGuards(InternalServiceGuard) export class AiTestSeamController { constructor(@Inject(AiTestSeamService) private readonly seam: AiTestSeamService) {} @Post("workspaces/:workspaceId/ai/mock-fail") @HttpCode(200) armMockOutage( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(MockFailDto)) body: MockFailInput ) { return this.seam.armMockOutage(workspaceId, body); } @Post("workspaces/:workspaceId/ai/usage-limits") @HttpCode(200) setAiUsageLimits( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(AiUsageLimitsDto)) body: AiUsageLimitsInput ) { return this.seam.setAiUsageLimits(workspaceId, body); } /** * TASK-0123 — `POST /internal/workspaces/:workspaceId/ai/mock-unknown-id` * `{unknownId}`: arm the NEXT mock WRITING call for this workspace to emit * a forged blockId (the unknown-ID-drop validation seam). */ @Post("workspaces/:workspaceId/ai/mock-unknown-id") @HttpCode(200) armMockUnknownBlockId( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(MockUnknownBlockIdDto)) body: MockUnknownBlockIdInput ) { return this.seam.armMockUnknownBlockId(workspaceId, body); } /** * TASK-0124 — `POST /internal/workspaces/:workspaceId/ai/mock-db-invalid` * `{action?}`: arm the NEXT mock DATABASE-GENERATION call to emit a * deliberately invalid tool call (the "retry-once → 422, no partial write" * seam). */ @Post("workspaces/:workspaceId/ai/mock-db-invalid") @HttpCode(200) armMockDbInvalid( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(MockDbInvalidDto)) body: MockDbInvalidInput ) { return this.seam.armMockDbInvalid(workspaceId, body); } /** * TASK-0126 — `POST /internal/workspaces/:workspaceId/ai/mock-generate` * `{failStep?, slowMs?}`: arm the NEXT mock PAGE-GENERATION call to emit a * failing plan step (the executor's rollback path) and/or fetch the plan * slowly (the service's total-timeout → `ai_timeout` path). */ /** * T-172…T-175 — `POST /internal/workspaces/:workspaceId/ai/mock-assistant` * `{hostile?, forgeId?}`: arm the mock ASSISTANT calls for this workspace to * emit a deliberately INVALID tool call (retry-once → 422) and/or a forged * candidate id (the no-fabricated-refs drop path). */ @Post("workspaces/:workspaceId/ai/mock-assistant") @HttpCode(200) armMockAssistant( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(MockAssistantDto)) body: MockAssistantInput ) { return this.seam.armMockAssistantSeam(workspaceId, body); } @Post("workspaces/:workspaceId/ai/mock-generate") @HttpCode(200) armMockGenerate( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(MockGenerateSeamDto)) body: MockGenerateSeamInput ) { return this.seam.armMockGenerateSeam(workspaceId, body); } }