13 KiB
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
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
VkAuthContextalready supportsSERVICE | USER | COMMUNITYwithcommunityIdfor group tokens.- Factories:
createServiceAuth,createUserAuth,createCommunityAuth. validateAuthContextenforces 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:
- Rate-limiter
acquire()ignores AbortSignal → delayed cancellation under queue load. - Single global limiter → large import starves other VK traffic.
- 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:
- Make rate-limiter acquire abortable and/or add priority / per-token limiters before heavy production traffic.
- Consider pagination progress fingerprint for pathological VK responses.
- Confirm likes.getList
countstrategy (100 vs up to 1000) under real load. - 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.