From 8e39ce0cbd809b9fb1666dce09905035bc493c10 Mon Sep 17 00:00:00 2001 From: Ochenstarik Date: Fri, 21 Aug 2026 22:49:37 +0700 Subject: [PATCH] fix(validation): Task 14 reject excludeDuplicateComments in API schemas and upgrade CI node-version to 22 --- .github/workflows/ci.yml | 2 +- .../done/TASK-2026-08-21-14-low-followups.md | 47 ++++++++++++++++ .../inbox/TASK-2026-08-21-14-low-followups.md | 24 ++++++++ src/core/validation/giveaway-schemas.ts | 1 - tests/auth-guard.test.ts | 1 - tests/duplicate-comments-rule.test.ts | 56 +++++++++++++++++++ tests/security.test.ts | 1 - 7 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 agents/antigravity/done/TASK-2026-08-21-14-low-followups.md create mode 100644 agents/antigravity/inbox/TASK-2026-08-21-14-low-followups.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e740371..c08914d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'npm' - name: Install dependencies diff --git a/agents/antigravity/done/TASK-2026-08-21-14-low-followups.md b/agents/antigravity/done/TASK-2026-08-21-14-low-followups.md new file mode 100644 index 0000000..b901351 --- /dev/null +++ b/agents/antigravity/done/TASK-2026-08-21-14-low-followups.md @@ -0,0 +1,47 @@ +# Task 14: Два LOW-хвоста из ревью 9061488 Report + +**Date:** 2026-08-21 +**Base Commit SHA:** `6510ea2a048aa9bbeec5af8d80e61a420f32c471` +**Status:** COMPLETED / PASS +**Assigned Agent:** Antigravity (Implementation Orchestrator) + +--- + +## 1. Executive Summary + +Устранены два LOW-хвоста, выявленные в ходе независимого ревью: + +1. **Запрет `excludeDuplicateComments` на границе API (`src/core/validation/giveaway-schemas.ts`):** + - Поле `excludeDuplicateComments` удалено из `filterRulesSchema`. + - Поскольку `filterRulesSchema` определена со `.strict()`, любая попытка внешнего API-клиента передать `excludeDuplicateComments` при создании розыгрыша (`POST /api/giveaways`), импорте участников (`POST /api/giveaways/[id]/participants`) или фиксации слепка (`POST /api/giveaways/[id]/snapshot`) немедленно отвергается с кодом `400 Bad Request` (`ValidationError: Unrecognized key(s) in object: 'excludeDuplicateComments'`). + - Поле физически не может попасть в `filterRulesSnapshot` нового розыгрыша и изменить `conditionsHash`. + - **Обратная совместимость legacy-снапшотов:** Функция `computeConditionsHash` (`src/core/randomizer/canonical.ts:46`) и верификатор `verifyDrawResult` сохраняют поддержку старых слепков, где это поле было записано до Задания 07 (`verified: true`, `conditionsIntegrity: true`). + +2. **Обновление версии Node.js в CI (`.github/workflows/ci.yml`):** + - Версия `node-version` в GitHub Actions поднята с 20 до 22 (текущая Active LTS). + - Все шаги CI (Prisma generate/migrations, Unit/Integration tests, ESLint, Next.js build) валидированы. + +--- + +## 2. Modified Files + +| File | Status | Description | +|------|--------|-------------| +| `src/core/validation/giveaway-schemas.ts` | MODIFIED | Удалено `excludeDuplicateComments` из `filterRulesSchema`. | +| `.github/workflows/ci.yml` | MODIFIED | Обновлен `node-version: 22` в CI workflow. | +| `tests/duplicate-comments-rule.test.ts` | MODIFIED | Добавлены тесты отсечения `excludeDuplicateComments` на уровне схем Zod. | +| `tests/auth-guard.test.ts` | MODIFIED | Удалено устаревшее поле из тестовых фикстур `validPostData`. | +| `tests/security.test.ts` | MODIFIED | Удалено устаревшее поле из тестовой фикстуры. | + +--- + +## 3. Verification Evidence + +```text +npx prisma generate -> EXIT 0 (Prisma Client v5.22.0) +npx tsc --noEmit -> EXIT 0 (0 ошибок типизации) +npm test -> EXIT 0 (59 тест-сьютов, 343 теста пройдены успешно) +npm run lint -> EXIT 0 (0 ошибок, 6 warnings на no-img-element) +npm run build -> EXIT 0 (Все 17 маршрутов скомпилированы успешно в Next.js 16.3.2) +npm audit --omit=dev -> EXIT 0 (0 vulnerabilities) +``` diff --git a/agents/antigravity/inbox/TASK-2026-08-21-14-low-followups.md b/agents/antigravity/inbox/TASK-2026-08-21-14-low-followups.md new file mode 100644 index 0000000..445ec17 --- /dev/null +++ b/agents/antigravity/inbox/TASK-2026-08-21-14-low-followups.md @@ -0,0 +1,24 @@ +# Task 14: Два LOW-хвоста из ревью 9061488 + +**Assigned to:** Antigravity (Implementation Orchestrator) +**Priority:** LOW +**Date:** 2026-08-21 +**Base SHA:** `6510ea2a048aa9bbeec5af8d80e61a420f32c471` + +## Проблемы +1. **`excludeDuplicateComments` на входе API (`filterRulesSchema`)**: + - `src/core/validation/giveaway-schemas.ts` по-прежнему объявляет `excludeDuplicateComments: z.boolean().optional()`. + - Если внешний API-клиент передаст этот флаг, он попадет в `filterRulesSnapshot` и в `computeConditionsHash`, изменив хеш условий для нового розыгрыша, хотя фактически на фильтрацию не влияет. + - Необходимо удалить поле из `filterRulesSchema` (или отбрасывать/валидировать), сохранив чтение legacy-снапшотов в `computeConditionsHash` и `verifyDrawResult`. +2. **`node-version: 20` в `.github/workflows/ci.yml`**: + - Обновить версию Node.js в CI до актуальной активной LTS (Node 22 / 24) с сохранением поддержки `engines.node >= 20.9.0`. + +## Scope +1. Схема `filterRulesSchema`: + - Удалить `excludeDuplicateComments` из `filterRulesSchema` (так как схемы используют `.strict()`, передача этого поля будет отвергаться с 400 ValidationError, либо при strip отбрасываться без записи в БД). + - Сохранить в `FilterRules` тип `excludeDuplicateComments?: boolean` (deprecated) для совместимости со старыми записями в БД. + - Проверить, что `computeConditionsHash` по-прежнему поддерживает legacy-снапшоты с этим полем. +2. CI Workflow `.github/workflows/ci.yml`: + - Обновить `node-version: 22` (текущая Active LTS). +3. Дополнить `tests/duplicate-comments-rule.test.ts`. +4. Verification gate & Report. diff --git a/src/core/validation/giveaway-schemas.ts b/src/core/validation/giveaway-schemas.ts index 565e484..2816da0 100644 --- a/src/core/validation/giveaway-schemas.ts +++ b/src/core/validation/giveaway-schemas.ts @@ -9,7 +9,6 @@ export const filterRulesSchema = z.object({ requireRepost: z.boolean().default(false), requireSubscription: z.boolean().default(false), excludeAdmins: z.boolean().default(false), - excludeDuplicateComments: z.boolean().optional(), excludeBlacklistedIds: z.array(z.string().max(128)).max(1000).default([]), targetGroupId: z.string().max(128).optional(), minEligibleParticipants: z.number().int().min(1).max(100000).default(1), diff --git a/tests/auth-guard.test.ts b/tests/auth-guard.test.ts index e1034bd..be896e2 100644 --- a/tests/auth-guard.test.ts +++ b/tests/auth-guard.test.ts @@ -46,7 +46,6 @@ describe('Phase 2.2.2 Giveaway Ownership Invariant & AuthZ Guard Security Suite' requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: [], - excludeDuplicateComments: true, minEligibleParticipants: 1, }, winnersCount: 1, diff --git a/tests/duplicate-comments-rule.test.ts b/tests/duplicate-comments-rule.test.ts index 4542033..3c2183c 100644 --- a/tests/duplicate-comments-rule.test.ts +++ b/tests/duplicate-comments-rule.test.ts @@ -262,4 +262,60 @@ describe('Task 07: Duplicate Comments Rule & Backward Compatibility', () => { expect(verification.conditionsIntegrity).toBe(true); expect(verification.verified).toBe(true); }); + + // ─── 5. API Schemas Reject excludeDuplicateComments on Input ────────────────── + it('API schemas strictly reject excludeDuplicateComments preventing it from reaching snapshot', async () => { + const { filterRulesSchema, fetchParticipantsSchema, createSnapshotSchema, createGiveawaySchema } = await import( + '../src/core/validation/giveaway-schemas' + ); + + // 1. filterRulesSchema is strict and rejects the key + const parseResult = filterRulesSchema.safeParse({ + requireLike: true, + excludeDuplicateComments: true, + }); + expect(parseResult.success).toBe(false); + if (!parseResult.success) { + expect(parseResult.error.issues.some(i => i.message.includes('unrecognized_keys') || (i as any).keys?.includes('excludeDuplicateComments'))).toBe(true); + } + + // 2. fetchParticipantsSchema rejects + const fetchResult = fetchParticipantsSchema.safeParse({ + filterRules: { + requireLike: true, + excludeDuplicateComments: true, + }, + }); + expect(fetchResult.success).toBe(false); + + // 3. createSnapshotSchema rejects + const snapResult = createSnapshotSchema.safeParse({ + filterRules: { + requireLike: true, + excludeDuplicateComments: true, + }, + }); + expect(snapResult.success).toBe(false); + + // 4. createGiveawaySchema rejects + const createResult = createGiveawaySchema.safeParse({ + sourceUrl: 'https://vk.com/wall-1_1', + post: { + platform: 'VK', + ownerId: '-1', + postId: '1', + sourceUrl: 'https://vk.com/wall-1_1', + title: 'Test', + text: 'Test', + likesCount: 1, + commentsCount: 0, + repostsCount: 0, + }, + filterRules: { + requireLike: true, + excludeDuplicateComments: true, + }, + }); + expect(createResult.success).toBe(false); + }); }); diff --git a/tests/security.test.ts b/tests/security.test.ts index e072d5d..fcd2ba8 100644 --- a/tests/security.test.ts +++ b/tests/security.test.ts @@ -49,7 +49,6 @@ describe('Security: VK_SERVICE_TOKEN handling', () => { requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: [], - excludeDuplicateComments: true, }, }), });