# Randomayzer — Phase G-3 VK Client Adversarial Review **Reviewer:** Grok (xAI) **Date:** 2026-08-17 **Commit reviewed:** `7acf4d2d4ed131f999936186377e85663c19316a` **Scope:** Phase 2.1 + 2.1.1 VkClient, retry, cancellation, pagination, rate limit, token security, error mapping, method capability claims. **Constraints:** No production Core / Randomizer / AuditProof / Prisma / OAuth implementation changes. Docs + optional tests only. --- ## 1. Executive Verdicts | Area | Verdict | |------|---------| | **Cancellation / Timeout** | **PASS WITH WARNINGS** | | **Retry** | **PASS** | | **Pagination** | **PASS WITH WARNINGS** | | **Token Security** | **PASS WITH WARNINGS** | | **Error Mapping** | **PASS** | | **VK Contract Accuracy** | **PASS WITH WARNINGS** | | **OAuth Readiness** | **YES** (with non-blocking risks) | **Overall:** VkClient is solid enough to proceed to Phase 2.2 OAuth. Blocking issues are absent; remaining risks are documented and manageable. --- ## 2. Cancellation vs Timeout ### Implementation summary ```ts let timedOut = false; let callerCancelled = false; // timeout → timedOut=true; controller.abort() // caller signal → callerCancelled=true; controller.abort() // catch order: // 1. if (callerCancelled || signal?.aborted) → VkCancelledError // 2. if (timedOut) → VkTimeoutError // 3. AbortError fallback with same priority // finally: clearTimeout + removeEventListener ``` | Scenario | Expected | Observed design | |----------|----------|-----------------| | signal already aborted before call | VkCancelledError, 0 retries | Yes (early check) | | abort before rate limiter | VkCancelledError | Yes (pre-acquire check) | | abort while waiting rate limiter | VkCancelledError after slot granted | **Partial** — acquire() has no AbortSignal; abort is only observed after acquire resolves | | abort during fetch | VkCancelledError, no retry | Yes | | abort during retry backoff | VkCancelledError, stop retries | Yes (backoff Promise rejects on abort) | | timeout during fetch | VkTimeoutError, retryable | Yes | | timeout after several retries | final VkTimeoutError | Yes | | timeout + caller abort nearly simultaneous | **VkCancelledError** (caller wins) | Deterministic: callerCancelled checked first | **Classification is deterministic and documented** in `docs/VK_CLIENT.md`. **Warning:** Rate-limiter queue wait is not abortable. Long queue under load delays cancellation observation until the slot is granted. Recommendation (proposal only): pass AbortSignal into `IVkRateLimiter.acquire(signal?)`. **Listener / timer hygiene:** `finally` always clears timeout and removes the abort listener (`{ once: true }` + explicit remove). No obvious MaxListeners accumulation on the happy path. Stress of 10k–100k calls should be safe if finally runs (normal Promise path). --- ## 3. Retry Policy Matrix ### HTTP status → typed error → retryable | HTTP | Mapped class | Retryable | Notes | |------|--------------|-----------|-------| | 400 | VkValidationError | No | | | 401 | VkAuthError | No | | | 403 | VkPermissionError | No | | | 404 | VkNotFoundError | No | | | 408 | VkNetworkError (fallback) | Yes* | Treated as network | | 429 | VkRateLimitError | Yes | | | 500–504 | VkTemporaryError | Yes | | | other 4xx | VkValidationError | No | | | other | VkNetworkError | Yes | | \*408 is not specially cased; falls through to Network (retryable). Acceptable. ### VK API error_code → typed error → retryable | Code | Mapped class | Retryable | Notes | |------|--------------|-----------|-------| | 1 | VkTemporaryError | Yes | Unknown error | | 5 | VkAuthError | No | Auth | | 6 | VkRateLimitError | Yes | Too many requests / s | | 7 | VkPermissionError | No | | | 9 | VkRateLimitError | Yes | Flood control | | 10 | VkTemporaryError | Yes | Internal server | | 15 | VkPrivateResourceError | No | Access denied | | 28 | VkAuthError | No | | | 29 | VkRateLimitError | Yes | Rate limit | | 30 | VkPrivateResourceError | No | Private profile | | 36 | VkTimeoutError | Yes | Method execution timeout on VK side | | 100 | VkValidationError | No | Invalid params | | 104 | VkNotFoundError | No | | | 113 | VkValidationError | No | Invalid user id | | 203 | VkPrivateResourceError | No | | | 210 | VkNotFoundError | No | Wall access / not found | | 260 | VkPermissionError | No | | | default | VkValidationError | No | Safe default | **Critical check:** VK error **code 500 is not treated as HTTP 500**. There is no case 500 in `mapVkApiError`; HTTP 500 is handled only in `mapHttpStatusError`. Correct separation. **Backoff:** exponential with full jitter, default maxRetries=3, initial 300 ms, maxDelay 4000 ms. Cancellation aborts backoff. Good. **Retry storm / thundering herd:** Full jitter reduces sync; global rate limiter serializes outbound calls. 100 parallel clients hitting VK 429 will queue behind the limiter + backoff — acceptable, not a thundering herd of raw HTTP. --- ## 4. VK Rate Limiter - Default: 10 RPS, sequential FIFO queue, minInterval ≈ 100 ms. - `acquire()` has **no AbortSignal** → cancellation while queued is delayed (see §2). - One large likes import (many pages) occupies the single global limiter and can **starve** concurrent short calls (e.g. wall.getById for another giveaway) for the duration of the import. - Memory: queue of resolve callbacks; 1000 concurrent is fine; 10k+ starts to matter. - Fairness: pure FIFO, no priority lanes. **Proposal (non-blocking):** optional separate limiters per token type / priority, or AbortSignal on acquire. --- ## 5. Pagination (`fetchPaginatedVk`) | Case | Behavior | Grade | |------|----------|-------| | 0 items | break, return [] | OK | | 1 page | OK | OK | | exact page boundary | continues until short page / total | OK | | 2+ pages | accumulates | OK | | totalCount changes mid-flight | uses latest recordedTotalCount for truncation check | OK | | duplicated IDs across pages | accumulated as-is; provider Map dedups later | OK at client, OK at provider | | API repeats same page forever | stopped by **maxPages** (default 10000) | **WARN** — no fingerprint / no-progress detection | | items.length < pageSize while total larger | treated as last page (break) | OK | | maxPages reached + truncation | throws `VkPaginationLimitError` if `throwOnTruncation` (default true) | OK — **partial set is not returned as complete** | | caller cancel | VkCancelledError between pages | OK | | network / rate limit mid-page | bubbles; no silent partial complete | OK | **Stuck pagination:** only maxPages protects against a broken VK that always returns the same non-empty page. **Proposal:** optional loop detection (fingerprint of first/last id + offset progress). Non-blocking for Phase 2.2. --- ## 6. Participant Deduplication & Subscription Batching **Provider:** `participantsMap` keyed by `platformUserId`. Like then comment merge actions → one Participant. Correct. **groups.isMember batching:** chunkSize = **500**, sequential calls. Sizes 1 / 499 / 500 / 501 / 1000 / 1001 → all users covered, no duplicate checks of the same id in one batch. Partial failure of one chunk fails the whole `checkSubscription` (no per-chunk continue) — acceptable for correctness, could be improved later with partial results. **Duplicate likes pages / name change between pages:** Map overwrites with later profile data; still one entry. Deactivated users appear with whatever fields VK returns; not specially filtered here (filter engine may later). --- ## 7. Token Security | Vector | Protection | Result | |--------|------------|--------| | access_token in URL | Sent in **POST form body** only | OK | | VK error `request_params` | `sanitizeRequestParams` → `[REDACTED]` for token keys | OK | | Error.message / method | Uses method name, not full URL+token | OK | | Network Error wrapping `err.message` | Could theoretically contain URL if fetch implementation leaks it; current code uses generic message | Low risk | | redactToken() helper | Present for logs | OK | | Stack traces | Do not embed token | OK | | Fake token `SUPER_SECRET_RANDOMAYZER_TOKEN_123456` in error paths | Sanitized in request_params; not present in constructed messages | Expected pass | **Warning:** Ensure no debug/logging middleware serializes the raw `URLSearchParams` body into error metadata. Current VkClient does not. **Form body token:** Correct choice; never lands in query string. --- ## 8. VK Method Capability Claims vs Official Sources | Claim | Project says | Official (dev.vk.com / schema practice) | Verdict | |-------|--------------|------------------------------------------|---------| | wall.getById token types | service, user, group, open | Supported with those tokens | **VERIFIED** | | likes.getList max count | Max 100 with extended=1; 1000 IDs only | Official: max **1000** (friends_only off); extended returns profiles | **PARTIALLY VERIFIED** (project is more conservative) | | groups.isMember max user_ids | **500** | Common community/SDK limit; official page does not always spell 500 explicitly | **PARTIALLY VERIFIED** (widely used & safe) | | wall.getReposts limitations | capabilities.reposts = false; privacy | Method exists for service/user; practical privacy limits on third-party posts | **PARTIALLY VERIFIED** (pragmatic & correct for product) | | groups.getMembers managers for adminDetection | requires admin rights; capability false | Correct | **VERIFIED** | | Service token usable for listed methods | Yes | Yes for wall/likes/comments/isMember | **VERIFIED** | | Service token lifetime | (not overclaimed in client) | Long-lived app token | OK | No claim was found **WRONG**. Conservative count limits are safer than optimistic ones. --- ## 9. Auth / OAuth Readiness - `VkAuthContext` already supports `SERVICE | USER | COMMUNITY` with `communityId` for group tokens. - Factories: `createServiceAuth`, `createUserAuth`, `createCommunityAuth`. - `validateAuthContext` enforces non-empty token and communityId for COMMUNITY. - VkClient is token-agnostic; no hardcoded OAuth endpoints or legacy assumptions that block Phase 2.2. - No OAuth implementation present (as required). **Verdict: YES — safe to start Phase 2.2 OAuth.** Non-blocking risks: - Rate limiter is global (one import can delay OAuth-related calls). - acquire() not cancellable. - No token refresh / lifecycle hooks yet (expected for 2.2). --- ## 10. Error Surface (public safety) Typed errors expose: `category`, `errorCode`, `method`, sanitized `details`. They do **not** expose: access_token, full request URL with secrets, raw request_params with tokens. Owner/post ids may appear in messages when the application constructs them (e.g. “Post X not found”) — acceptable and useful. Safe public mapping path exists via existing `handleApiError` style (HTTP layer already maps AppErrors). --- ## 11. Performance Notes - Pagination accumulates in memory (full list). For 100k likes this is the dominant cost (same as G-1 baseline). - Retry overhead: up to 3 backoffs with jitter; small vs network. - Rate limiter serializes to ~10 RPS → ~10k likes pages ≈ 1000 s theoretical floor (plus VK latency). Real large imports need background jobs (already noted in prior phases). - Mock throughput of single call path is high; bottleneck is limiter + network. --- ## 12. CRITICAL / HIGH Findings **CRITICAL:** none that block Phase 2.2. **HIGH:** 1. Rate-limiter `acquire()` ignores AbortSignal → delayed cancellation under queue load. 2. Single global limiter → large import starves other VK traffic. 3. No pagination loop-detection beyond maxPages (stuck identical pages). **MEDIUM:** - likes.getList pageSize=100 is conservative vs official max 1000 (performance only). - isMember partial chunk failure fails entire check (no partial map). - NetworkError may surface underlying fetch message (low token risk). --- ## 13. Tests Executed / Scale - Code review of: `vk-client.ts`, `vk-retry.ts`, `vk-errors.ts`, `vk-rate-limit.ts`, `vk-auth.ts`, `vk-provider.ts`, docs. - Existing suites present: `vk-client-integration.test.ts`, `vk-correctness-gate.test.ts`, `vk-errors.test.ts`. - Synthetic reasoning for cancellation races, retry matrix, token redaction, 500 concurrent queue behaviour. - Official VK docs cross-check for capability claims (dev.vk.com). - Stress scale for listener/timer: reasoned from `finally` + `{ once: true }`; recommend 10k mock-call run in CI. Optional artifact: `tests/vk-client-grok-stress.test.ts` can be added later without touching production sources. --- ## 14. Final Answer ### Безопасно ли начинать Phase 2.2 OAuth? **YES.** **Blocking issues:** none. **Remaining non-blocking risks:** 1. Make rate-limiter acquire abortable and/or add priority / per-token limiters before heavy production traffic. 2. Consider pagination progress fingerprint for pathological VK responses. 3. Confirm likes.getList `count` strategy (100 vs up to 1000) under real load. 4. After OAuth lands, ensure user/group tokens do not share the same global limiter bucket with long service-token imports without isolation. VkClient cancellation/timeout separation, retry classification, token redaction, and error mapping are production-grade for the next phase.