From 73a13fd16d70cc63974e5d390255be59835a8afe Mon Sep 17 00:00:00 2001 From: wh Date: Wed, 15 Apr 2026 14:25:23 +0800 Subject: [PATCH] docs: plan swagger dto annotation rollout --- ...5-label-backend-swagger-dto-annotations.md | 817 ++++++++++++++++++ 1 file changed, 817 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md diff --git a/docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md b/docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md new file mode 100644 index 0000000..7e6972f --- /dev/null +++ b/docs/superpowers/plans/2026-04-15-label-backend-swagger-dto-annotations.md @@ -0,0 +1,817 @@ +# label_backend Swagger DTO Annotation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make every public `label_backend` API parameter visible in Swagger with its name, type, requiredness, and meaning by replacing fixed `Map` request bodies with DTOs and adding OpenAPI annotations. + +**Architecture:** Keep existing URLs, HTTP methods, JSON field names, service calls, and auth behavior unchanged. Add DTOs in the flat `com.label.dto` package, annotate Controller parameters and public schema models, and extend OpenAPI reflection tests so future endpoints cannot regress to undocumented parameters. + +**Tech Stack:** Java 21, Spring Boot 3.1.5, springdoc-openapi 2.3.0, JUnit 5, AssertJ, Lombok. + +--- + +## File Structure + +### Create + +- `src/main/java/com/label/dto/CompanyCreateRequest.java` +- `src/main/java/com/label/dto/CompanyUpdateRequest.java` +- `src/main/java/com/label/dto/CompanyStatusUpdateRequest.java` +- `src/main/java/com/label/dto/UserCreateRequest.java` +- `src/main/java/com/label/dto/UserUpdateRequest.java` +- `src/main/java/com/label/dto/UserStatusUpdateRequest.java` +- `src/main/java/com/label/dto/UserRoleUpdateRequest.java` +- `src/main/java/com/label/dto/CreateTaskRequest.java` +- `src/main/java/com/label/dto/TaskReassignRequest.java` +- `src/main/java/com/label/dto/RejectRequest.java` +- `src/main/java/com/label/dto/ExportBatchCreateRequest.java` +- `src/main/java/com/label/dto/VideoProcessCreateRequest.java` +- `src/main/java/com/label/dto/VideoProcessCallbackRequest.java` +- `src/main/java/com/label/dto/SysConfigUpdateRequest.java` +- `src/main/java/com/label/dto/SysConfigItemResponse.java` +- `src/main/java/com/label/dto/DynamicJsonResponse.java` +- `src/main/java/com/label/dto/RawJsonUpdateRequest.java` +- `src/main/java/com/label/dto/FinetuneJobResponse.java` + +### Modify + +- `src/test/java/com/label/unit/OpenApiAnnotationTest.java` +- `src/main/java/com/label/common/result/Result.java` +- `src/main/java/com/label/common/result/PageResult.java` +- `src/main/java/com/label/dto/LoginRequest.java` +- `src/main/java/com/label/dto/LoginResponse.java` +- `src/main/java/com/label/dto/SourceResponse.java` +- `src/main/java/com/label/dto/TaskResponse.java` +- `src/main/java/com/label/dto/UserInfoResponse.java` +- `src/main/java/com/label/controller/AuthController.java` +- `src/main/java/com/label/controller/CompanyController.java` +- `src/main/java/com/label/controller/UserController.java` +- `src/main/java/com/label/controller/SourceController.java` +- `src/main/java/com/label/controller/TaskController.java` +- `src/main/java/com/label/controller/ExtractionController.java` +- `src/main/java/com/label/controller/QaController.java` +- `src/main/java/com/label/controller/ExportController.java` +- `src/main/java/com/label/controller/SysConfigController.java` +- `src/main/java/com/label/controller/VideoController.java` +- `src/main/java/com/label/entity/SysCompany.java` +- `src/main/java/com/label/entity/SysUser.java` +- `src/main/java/com/label/entity/SysConfig.java` +- `src/main/java/com/label/entity/TrainingDataset.java` +- `src/main/java/com/label/entity/ExportBatch.java` +- `src/main/java/com/label/entity/VideoProcessJob.java` + +--- + +### Task 1: Add Failing OpenAPI Contract Tests + +**Files:** +- Modify: `src/test/java/com/label/unit/OpenApiAnnotationTest.java` + +- [ ] **Step 1: Add DTO and parameter coverage expectations** + +Add tests that fail before implementation because new DTO classes do not exist, some endpoint parameters lack `@Parameter`, and request body parameters still use `Map`. + +```java +@Test +@DisplayName("固定结构请求体不能继续使用 Map") +void fixedRequestBodiesDoNotUseRawMap() { + for (Class controller : CONTROLLERS) { + Arrays.stream(controller.getDeclaredMethods()) + .filter(OpenApiAnnotationTest::isEndpointMethod) + .flatMap(method -> Arrays.stream(method.getParameters())) + .filter(parameter -> parameter.isAnnotationPresent(org.springframework.web.bind.annotation.RequestBody.class)) + .forEach(parameter -> assertThat(parameter.getType()) + .as(controller.getSimpleName() + " request body should use an explicit DTO") + .isNotEqualTo(java.util.Map.class)); + } +} + +@Test +@DisplayName("所有 endpoint 公开参数都声明 @Parameter 或请求体说明") +void endpointParametersHaveOpenApiDescriptions() { + for (Class controller : CONTROLLERS) { + Arrays.stream(controller.getDeclaredMethods()) + .filter(OpenApiAnnotationTest::isEndpointMethod) + .forEach(method -> Arrays.stream(method.getParameters()) + .filter(OpenApiAnnotationTest::isPublicApiParameter) + .forEach(parameter -> assertThat( + parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.Parameter.class) + || parameter.isAnnotationPresent(io.swagger.v3.oas.annotations.parameters.RequestBody.class)) + .as(controller.getSimpleName() + "." + method.getName() + + " parameter " + parameter.getName() + + " should have OpenAPI description") + .isTrue())); + } +} +``` + +Add helper: + +```java +private static boolean isPublicApiParameter(java.lang.reflect.Parameter parameter) { + return parameter.isAnnotationPresent(org.springframework.web.bind.annotation.PathVariable.class) + || parameter.isAnnotationPresent(org.springframework.web.bind.annotation.RequestParam.class) + || parameter.isAnnotationPresent(org.springframework.web.bind.annotation.RequestBody.class); +} +``` + +- [ ] **Step 2: Expand DTO and entity schema coverage** + +Update DTO coverage lists so the test will fail until new DTOs and exposed entities are annotated. + +```java +private static final List> DTOS = List.of( + LoginRequest.class, + LoginResponse.class, + UserInfoResponse.class, + TaskResponse.class, + SourceResponse.class, + CompanyCreateRequest.class, + CompanyUpdateRequest.class, + CompanyStatusUpdateRequest.class, + UserCreateRequest.class, + UserUpdateRequest.class, + UserStatusUpdateRequest.class, + UserRoleUpdateRequest.class, + CreateTaskRequest.class, + TaskReassignRequest.class, + RejectRequest.class, + ExportBatchCreateRequest.class, + VideoProcessCreateRequest.class, + VideoProcessCallbackRequest.class, + SysConfigUpdateRequest.class, + SysConfigItemResponse.class, + DynamicJsonResponse.class, + RawJsonUpdateRequest.class, + FinetuneJobResponse.class +); + +private static final List> EXPOSED_SCHEMAS = List.of( + Result.class, + PageResult.class, + SysCompany.class, + SysUser.class, + SysConfig.class, + TrainingDataset.class, + ExportBatch.class, + VideoProcessJob.class +); +``` + +Add test: + +```java +@Test +@DisplayName("公开响应模型都声明 @Schema") +void exposedModelsHaveSchema() { + assertThat(EXPOSED_SCHEMAS) + .allSatisfy(model -> + assertThat(model.getAnnotation(Schema.class)) + .as(model.getSimpleName() + " should have @Schema") + .isNotNull()); +} +``` + +- [ ] **Step 3: Run test and verify RED** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: FAIL because the new DTO classes are missing and existing Controller parameters are not fully annotated. + +### Task 2: Create Explicit Request and Response DTOs + +**Files:** +- Create all DTO files listed in File Structure + +- [ ] **Step 1: Add company request DTOs** + +Create `CompanyCreateRequest`, `CompanyUpdateRequest`, and `CompanyStatusUpdateRequest` with these fields: + +```java +@Data +@Schema(description = "创建公司请求") +public class CompanyCreateRequest { + @Schema(description = "公司名称", example = "示例公司") + private String companyName; + + @Schema(description = "公司代码,英文或数字短标识", example = "DEMO") + private String companyCode; +} +``` + +`CompanyUpdateRequest` uses the same fields with description "更新公司信息请求". + +`CompanyStatusUpdateRequest`: + +```java +@Data +@Schema(description = "更新公司状态请求") +public class CompanyStatusUpdateRequest { + @Schema(description = "公司状态,可选值:ACTIVE、DISABLED", example = "ACTIVE") + private String status; +} +``` + +- [ ] **Step 2: Add user request DTOs** + +Create: + +```java +@Data +@Schema(description = "创建用户请求") +public class UserCreateRequest { + @Schema(description = "登录用户名", example = "annotator01") + private String username; + + @Schema(description = "明文初始密码", example = "Passw0rd!") + private String password; + + @Schema(description = "真实姓名", example = "张三") + private String realName; + + @Schema(description = "用户角色,可选值:ADMIN、REVIEWER、ANNOTATOR、UPLOADER", example = "ANNOTATOR") + private String role; +} +``` + +`UserUpdateRequest` fields: + +```java +@Schema(description = "真实姓名", example = "张三") +private String realName; + +@Schema(description = "新密码;为空时不修改密码", example = "NewPassw0rd!") +private String password; +``` + +`UserStatusUpdateRequest`: + +```java +@Schema(description = "用户状态,可选值:ACTIVE、DISABLED", example = "ACTIVE") +private String status; +``` + +`UserRoleUpdateRequest`: + +```java +@Schema(description = "用户角色,可选值:ADMIN、REVIEWER、ANNOTATOR、UPLOADER", example = "REVIEWER") +private String role; +``` + +- [ ] **Step 3: Add task request DTOs** + +Create: + +```java +@Data +@Schema(description = "创建标注任务请求") +public class CreateTaskRequest { + @Schema(description = "关联资料 ID", example = "1001") + private Long sourceId; + + @Schema(description = "任务类型,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION") + private String taskType; +} +``` + +```java +@Data +@Schema(description = "管理员强制指派任务请求") +public class TaskReassignRequest { + @Schema(description = "目标用户 ID", example = "2001") + private Long userId; +} +``` + +- [ ] **Step 4: Add common moderation and dynamic JSON DTOs** + +Create: + +```java +@Data +@Schema(description = "审批驳回请求") +public class RejectRequest { + @Schema(description = "驳回原因", example = "证据不足,请补充来源片段") + private String reason; +} +``` + +```java +@Data +@Schema(description = "原始 JSON 更新请求") +public class RawJsonUpdateRequest { + @Schema(description = "完整 JSON 字符串;用于整体覆盖提取或问答标注结果", example = "{\"items\":[]}") + private String body; +} +``` + +```java +@Data +@Schema(description = "动态业务 JSON 响应") +public class DynamicJsonResponse { + @Schema(description = "动态业务数据;提取阶段通常为三元组/四元组结构,问答阶段通常为问答对结构") + private Map content; +} +``` + +- [ ] **Step 5: Add export and finetune DTOs** + +Create: + +```java +@Data +@Schema(description = "创建训练数据导出批次请求") +public class ExportBatchCreateRequest { + @Schema(description = "训练样本 ID 列表", example = "[1,2,3]") + private List sampleIds; +} +``` + +```java +@Data +@Schema(description = "微调任务响应") +public class FinetuneJobResponse { + @Schema(description = "导出批次 ID", example = "10") + private Long batchId; + + @Schema(description = "微调任务 ID", example = "glm-ft-abc123") + private String finetuneJobId; + + @Schema(description = "微调任务状态,可选值:PENDING、RUNNING、SUCCESS、FAILED", example = "RUNNING") + private String status; + + @Schema(description = "错误信息;任务失败时返回", example = "training file not found") + private String errorMessage; +} +``` + +- [ ] **Step 6: Add video and config DTOs** + +Create: + +```java +@Data +@Schema(description = "创建视频处理任务请求") +public class VideoProcessCreateRequest { + @Schema(description = "视频资料 ID", example = "1001") + private Long sourceId; + + @Schema(description = "任务类型,可选值:EXTRACT_FRAMES、VIDEO_TO_TEXT", example = "EXTRACT_FRAMES") + private String jobType; + + @Schema(description = "任务参数 JSON 字符串,例如抽帧模式、帧间隔或起止时间", example = "{\"mode\":\"interval\",\"frameInterval\":30}") + private String params; +} +``` + +```java +@Data +@Schema(description = "AI 服务视频处理回调请求") +public class VideoProcessCallbackRequest { + @Schema(description = "视频处理任务 ID", example = "123") + private Long jobId; + + @Schema(description = "处理状态,可选值:SUCCESS、FAILED", example = "SUCCESS") + private String status; + + @Schema(description = "输出文件路径;成功时返回", example = "frames/1001/0.jpg") + private String outputPath; + + @Schema(description = "错误信息;失败时返回", example = "video decode failed") + private String errorMessage; +} +``` + +```java +@Data +@Schema(description = "系统配置更新请求") +public class SysConfigUpdateRequest { + @Schema(description = "配置值", example = "glm-4-flash") + private String value; + + @Schema(description = "配置说明", example = "默认文本模型") + private String description; +} +``` + +```java +@Data +@Schema(description = "系统配置项响应") +public class SysConfigItemResponse { + @Schema(description = "配置键", example = "ai.defaultTextModel") + private String configKey; + + @Schema(description = "配置值", example = "glm-4-flash") + private String configValue; + + @Schema(description = "配置说明", example = "默认文本模型") + private String description; + + @Schema(description = "配置作用域,可选值:GLOBAL、COMPANY", example = "COMPANY") + private String scope; +} +``` + +- [ ] **Step 7: Run test and verify partial GREEN** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: FAIL remains, but missing class errors are gone. Failures should now point to missing Controller parameter annotations and raw `Map` request bodies. + +### Task 3: Annotate Common Models, Existing DTOs, and Exposed Entities + +**Files:** +- Modify common result classes, existing DTOs, and exposed entity files listed above + +- [ ] **Step 1: Add `@Schema` to `Result` and `PageResult`** + +Update `Result`: + +```java +@Data +@Schema(description = "统一接口响应包装") +public class Result { + @Schema(description = "业务状态码;成功为 SUCCESS,失败为具体错误码", example = "SUCCESS") + private String code; + + @Schema(description = "接口返回主体;不同接口类型不同") + private T data; + + @Schema(description = "响应消息;失败时说明错误原因", example = "参数不合法") + private String message; +} +``` + +Update `PageResult` similarly for `items`, `total`, `page`, `pageSize`. + +- [ ] **Step 2: Complete existing DTO field descriptions** + +Ensure every field in `LoginRequest`, `LoginResponse`, `UserInfoResponse`, `TaskResponse`, and `SourceResponse` has `@Schema(description = ..., example = ...)`. + +- [ ] **Step 3: Add class and field schemas to exposed entities** + +For each exposed entity, add class-level `@Schema(description = "...")` and field-level `@Schema`. + +Use these class descriptions: + +- `SysCompany`: "租户公司" +- `SysUser`: "系统用户" +- `SysConfig`: "系统配置" +- `TrainingDataset`: "训练样本数据" +- `ExportBatch`: "训练数据导出批次" +- `VideoProcessJob`: "视频处理任务" + +- [ ] **Step 4: Run schema coverage test** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: raw `Map` body and parameter annotation tests still fail, schema coverage should pass. + +### Task 4: Refactor Controller Request Bodies to DTOs + +**Files:** +- Modify Controller files listed in File Structure + +- [ ] **Step 1: Replace company `Map` request bodies** + +In `CompanyController`, replace: + +```java +public Result create(@RequestBody Map body) +``` + +with: + +```java +public Result create( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建公司请求体", + required = true) + @RequestBody CompanyCreateRequest body) +``` + +Call service with `body.getCompanyName()` and `body.getCompanyCode()`. + +Apply the same pattern to `update` and `updateStatus`. + +- [ ] **Step 2: Replace user `Map` request bodies** + +In `UserController`, replace create/update/status/role request bodies with `UserCreateRequest`, `UserUpdateRequest`, `UserStatusUpdateRequest`, and `UserRoleUpdateRequest`. + +Keep service calls unchanged except changing `body.get("field")` to DTO getters. + +- [ ] **Step 3: Replace task request bodies** + +In `TaskController`, use `CreateTaskRequest` and `TaskReassignRequest`. + +`createTask` service call becomes: + +```java +taskService.createTask(body.getSourceId(), body.getTaskType(), principal.getCompanyId()) +``` + +`reassign` target becomes: + +```java +Long targetUserId = body.getUserId(); +``` + +- [ ] **Step 4: Replace export batch request body** + +In `ExportController#createBatch`, use `ExportBatchCreateRequest`. + +Service call becomes: + +```java +return Result.success(exportService.createBatch(body.getSampleIds(), principal(request))); +``` + +- [ ] **Step 5: Replace config update request body** + +In `SysConfigController#updateConfig`, use `SysConfigUpdateRequest`. + +Service call uses `body.getValue()` and `body.getDescription()`. + +- [ ] **Step 6: Replace video request bodies** + +In `VideoController#createJob`, use `VideoProcessCreateRequest`. + +In `VideoController#handleCallback`, use `VideoProcessCallbackRequest`. + +Keep callback secret header logic unchanged. + +- [ ] **Step 7: Replace reject request bodies** + +In `ExtractionController#reject` and `QaController#reject`, use `RejectRequest`. + +Set: + +```java +String reason = body != null ? body.getReason() : null; +``` + +- [ ] **Step 8: Run raw Map body test** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: raw `Map` request-body test passes. Parameter description test may still fail until Task 5. + +### Task 5: Add Controller Parameter and Request Body OpenAPI Descriptions + +**Files:** +- Modify all Controller files + +- [ ] **Step 1: Add imports** + +Use: + +```java +import io.swagger.v3.oas.annotations.Parameter; +``` + +Use fully qualified `io.swagger.v3.oas.annotations.parameters.RequestBody` for Swagger request body annotations to avoid conflict with Spring `@RequestBody`. + +- [ ] **Step 2: Annotate pagination query parameters** + +For every `page` parameter: + +```java +@Parameter(description = "页码,从 1 开始", example = "1") +@RequestParam(defaultValue = "1") int page +``` + +For every `pageSize` parameter: + +```java +@Parameter(description = "每页条数", example = "20") +@RequestParam(defaultValue = "20") int pageSize +``` + +- [ ] **Step 3: Annotate status and type filters** + +Examples: + +```java +@Parameter(description = "任务状态过滤,可选值:UNCLAIMED、IN_PROGRESS、SUBMITTED、APPROVED、REJECTED", example = "SUBMITTED") +@RequestParam(required = false) String status +``` + +```java +@Parameter(description = "任务类型过滤,可选值:EXTRACTION、QA_GENERATION", example = "EXTRACTION") +@RequestParam(required = false) String taskType +``` + +- [ ] **Step 4: Annotate path variables** + +Examples: + +```java +@Parameter(description = "任务 ID", example = "1001") +@PathVariable Long id +``` + +```java +@Parameter(description = "视频处理任务 ID", example = "123") +@PathVariable Long jobId +``` + +```java +@Parameter(description = "系统配置键", example = "ai.defaultTextModel") +@PathVariable String key +``` + +- [ ] **Step 5: Annotate multipart upload parameters** + +In `SourceController#upload`: + +```java +@Parameter(description = "上传文件,支持文本、图片、视频", required = true) +@RequestParam("file") MultipartFile file +``` + +```java +@Parameter(description = "资料类型,可选值:text、image、video", example = "text", required = true) +@RequestParam("dataType") String dataType +``` + +- [ ] **Step 6: Annotate request bodies** + +Every Spring `@RequestBody` parameter should also have Swagger request-body annotation: + +```java +@io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "创建任务请求体", + required = true) +@RequestBody CreateTaskRequest body +``` + +- [ ] **Step 7: Run parameter coverage test** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: PASS for `OpenApiAnnotationTest`. + +### Task 6: Make Fixed Map Responses Swagger-Readable + +**Files:** +- Modify `ExportController.java` +- Modify `SysConfigController.java` +- Modify `ExtractionController.java` +- Modify `QaController.java` +- Modify `FinetuneService.java` only if needed to map stable fields cleanly + +- [ ] **Step 1: Convert config list response** + +Change `SysConfigController#listConfig` return type from: + +```java +Result>> +``` + +to: + +```java +Result> +``` + +Map each item from service map using keys currently returned by `SysConfigService.list`. + +- [ ] **Step 2: Wrap dynamic extraction and QA responses** + +Change `ExtractionController#getResult` and `QaController#getResult` return types from `Result>` to `Result`. + +Implementation: + +```java +DynamicJsonResponse response = new DynamicJsonResponse(); +response.setContent(extractionService.getResult(taskId, principal(request))); +return Result.success(response); +``` + +Use the same pattern for QA. + +- [ ] **Step 3: Use `RawJsonUpdateRequest` for raw JSON update bodies only if existing clients can send `{"body":"..."}`** + +Do not change `PUT /api/extraction/{taskId}` or `PUT /api/qa/{taskId}` from raw string request body unless compatibility is explicitly approved. + +Instead, annotate the raw string body with a Swagger request-body description explaining it is a complete JSON string. + +- [ ] **Step 4: Convert finetune responses only if fields are stable** + +Inspect `FinetuneService#trigger` and `FinetuneService#getStatus`. + +If returned maps have stable keys, convert Controller responses to `FinetuneJobResponse` by mapping the known keys. + +If keys are not stable, keep `Map` response but document `@Operation(description = "...")` and do not convert response type. + +- [ ] **Step 5: Run compile test** + +Run: + +```bash +mvn -DskipTests compile +``` + +Expected: PASS. + +### Task 7: Run Focused Compatibility Tests + +**Files:** +- Modify integration tests only if DTO request binding requires updating test payload helpers + +- [ ] **Step 1: Run OpenAPI annotation test** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: PASS. + +- [ ] **Step 2: Run API flows affected by DTO request body replacement** + +Run: + +```bash +mvn -Dtest=UserManagementIntegrationTest,SysConfigIntegrationTest,ExportIntegrationTest,VideoCallbackIdempotencyTest test +``` + +Expected: PASS. If Docker/Testcontainers is unavailable, record the failure reason and run `mvn -DskipTests compile` plus `OpenApiAnnotationTest`. + +- [ ] **Step 3: Run full test suite if environment supports Docker** + +Run: + +```bash +mvn test +``` + +Expected: PASS, or Docker/Testcontainers-specific skip/failure documented. + +### Task 8: Final Documentation and Review + +**Files:** +- Modify: `README.md` only if implementation reveals stricter constraints than already documented +- Modify: `docs/superpowers/specs/2026-04-15-label-backend-swagger-annotations-design.md` only if a design decision changes during implementation + +- [ ] **Step 1: Check for remaining raw request body maps** + +Run: + +```bash +rg -n "@RequestBody Map<|Map body|Map body" src/main/java/com/label/controller +``` + +Expected: no fixed-structure request-body maps remain. Dynamic non-request-body maps may remain only where intentionally documented. + +- [ ] **Step 2: Check for missing `@Parameter` on public endpoint params** + +Run: + +```bash +mvn -Dtest=OpenApiAnnotationTest test +``` + +Expected: PASS. + +- [ ] **Step 3: Review git diff** + +Run: + +```bash +git diff -- src/main/java src/test/java README.md docs/superpowers +``` + +Expected: diff only contains Swagger annotations, DTO additions, DTO request binding updates, and matching tests. + +- [ ] **Step 4: Commit implementation** + +Run: + +```bash +git add src/main/java src/test/java README.md docs/superpowers +git commit -m "docs: document backend api parameters in swagger" +``` + +Expected: commit succeeds.