Skip to content

Add server-side moderation pipeline - #3

Merged
Bonobo791 merged 30 commits into
mainfrom
phase-c-server-libs
Jul 29, 2026
Merged

Add server-side moderation pipeline#3
Bonobo791 merged 30 commits into
mainfrom
phase-c-server-libs

Conversation

@Bonobo791

@Bonobo791 Bonobo791 commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Automated PR. Verify checklist per plan:

  • crypto.ts: AES-256-GCM encrypt/decrypt for refresh tokens
  • youtube.ts: 4 exported async functions (token refresh, incremental comment fetch with 3-page/cursor stop, batched setModerationStatus, deleteComment)
  • moderation.ts: omni-moderation-latest, score = max of 6 toxicity categories
  • pipeline.ts: AUTO_REJECT = 0.85 / QUEUE = 0.35 thresholds, rules-then-AI order, batched hold/reject/ban with inline delete (intentional per doc), audit log rows, cursor advance, DRY_RUN gating
  • npm run check: 0 errors, 0 warnings

All files carry the AGPL header and tabs per AGENTS.md. Code is verbatim from the execution plan otherwise.

Do not merge if any step's Verify failed.

Greptile Summary

Adds a server-side YouTube comment moderation pipeline with durable action reconciliation.

  • Adds AES-256-GCM refresh-token encryption that now requires an explicit encryption key.
  • Adds incremental YouTube comment retrieval, moderation and deletion operations, rules-based decisions, and OpenAI toxicity scoring.
  • Adds persistent moderation-action state, verification and retry handling, dry-run isolation, pagination continuation, cron leasing, audit logging, and the corresponding database migrations and tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/lib/server/crypto.ts Implements authenticated refresh-token encryption and fails closed when the encryption key is absent.
src/lib/server/youtube.ts Adds token refresh, incremental comment retrieval, batched moderation updates, verification support, and deletion operations.
src/lib/server/pipeline.ts Coordinates rule and AI decisions with durable action staging, remote reconciliation, dry-run handling, auditing, and cursor persistence.
src/lib/server/db/schema.ts Defines continuation, cron lease, moderation-action, comment, and audit persistence needed by the pipeline.
drizzle/0000_add_channel_scan_state.sql Adds the channel pagination and scan-cursor columns expected by the runtime schema.
drizzle/0001_add_moderation_actions.sql Adds durable moderation-action storage used to reconcile local and remote state.
drizzle/0002_add_channel_cron_lease.sql Adds the channel lease state used to coordinate cron processing.
src/routes/api/cron/+server.ts Adds the cron entry point that leases channels and invokes the moderation pipeline.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Cron selects eligible channel] --> B[Refresh access token]
    B --> C[Fetch incremental comment pages]
    C --> D[Apply rules]
    D --> E[Score unresolved comments]
    E --> F{Dry run?}
    F -- Yes --> G[Write dry-run audit entries]
    F -- No --> H[Stage comments and pending actions]
    H --> I[Claim pending actions]
    I --> J[Verify dispatched actions]
    J --> K[Apply required YouTube actions]
    K --> L[Complete actions and write audit entries]
    L --> M[Persist page token and scan cursor]
Loading

Reviews (15): Last reviewed commit: "Merge remote-tracking branch 'origin/pha..." | Re-trigger Greptile

@cla-bot cla-bot Bot added the cla-signed label Jul 29, 2026
@codeant-ai

codeant-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 3fca7ea Jul 29, 2026 · 18:47 18:49
✅ Incremental review completed 2a03142 Jul 29, 2026 · 17:45 17:47
✅ Incremental review completed f32d373 Jul 29, 2026 · 16:23 16:26
✅ Reviewed your PR 9c38c33 Jul 29, 2026 · 15:21 15:23

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds server utilities for encryption, retrying HTTP, YouTube comment moderation, OpenAI scoring, rule matching, database migrations, and a channel pipeline that persists decisions and scan progress.

Changes

Auto-moderation pipeline

Layer / File(s) Summary
HTTP, encryption, and test setup
package.json, vite.config.ts, src/lib/server/http.ts, src/lib/server/crypto.ts, src/lib/server/*test.ts
Adds Vitest configuration, deadline-aware retries, AES-256-GCM encryption/decryption, and unit tests.
YouTube API operations
src/lib/server/youtube.ts, src/lib/server/youtube.test.ts
Adds OAuth refresh, paginated comment fetching, batched moderation updates, deletion handling, and tests.
Moderation scoring and rules
src/lib/server/moderation.ts, src/lib/server/rules.ts, src/lib/server/{moderation,rules}.test.ts
Adds OpenAI moderation scoring, score serialization, validated rule matching, regex safety checks, and tests.
Channel run orchestration
src/lib/server/pipeline.ts, src/lib/server/pipeline.test.ts, src/lib/server/db/schema.ts, drizzle/*
Adds rule/AI decisions, dry-run handling, durable moderation actions, transactional persistence, scan cursors, migrations, and pipeline tests.
Repository and database configuration
.clabot, .codacy.yml, AGENTS.md, drizzle.config.ts, src/lib/server/db/index.ts, EXECUTION_PLAN_YouTube_Comment_Moderator.md
Updates contributor attribution, analysis exclusions, repository guidance, migration output, remote database credential validation, and the execution plan.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler
  participant Pipeline
  participant YouTube
  participant OpenAI
  participant Database
  Scheduler->>Pipeline: runChannel
  Pipeline->>YouTube: fetch comments
  YouTube-->>Pipeline: comments and scan state
  Pipeline->>OpenAI: score unruled comments
  OpenAI-->>Pipeline: moderation scores
  Pipeline->>YouTube: apply moderation actions
  Pipeline->>Database: persist decisions, audits, and scan state
Loading

Possibly related PRs

  • Bonobo791/Moderaty#1: Adds the Drizzle/libSQL scaffolding and dependencies used by this PR’s migration workflow.
  • Bonobo791/Moderaty#2: Establishes the Drizzle/Turso database layer extended here with scan-state and moderation-action schema.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding a server-side moderation pipeline.
Description check ✅ Passed The description is directly related to the changeset and details the pipeline components and checks.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-c-server-libs

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jul 29, 2026
@codeant-ai

codeant-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

User description

Automated PR. Verify checklist per plan:

  • crypto.ts: AES-256-GCM encrypt/decrypt for refresh tokens
  • youtube.ts: 4 exported async functions (token refresh, incremental comment fetch with 3-page/cursor stop, batched setModerationStatus, deleteComment)
  • moderation.ts: omni-moderation-latest, score = max of 6 toxicity categories
  • pipeline.ts: AUTO_REJECT = 0.85 / QUEUE = 0.35 thresholds, rules-then-AI order, batched hold/reject/ban with inline delete (intentional per doc), audit log rows, cursor advance, DRY_RUN gating
  • npm run check: 0 errors, 0 warnings

All files carry the AGPL header and tabs per AGENTS.md. Code is verbatim from the execution plan otherwise.

Do not merge if any step's Verify failed.


CodeAnt-AI Description

Add YouTube comment moderation with rule-based and AI-driven actions

What Changed

  • New server-side moderation flow fetches recent YouTube comments, skips previously processed comments, and tracks progress for future runs
  • Keyword, user, and regular-expression rules can hold, reject, delete, or ban comments before AI review
  • Remaining comments are scored for harassment, hate, and violence; high-risk comments are rejected, medium-risk comments are queued, and low-risk comments are approved
  • Moderation decisions are applied in batches, recorded in an audit log, and can be previewed without changing YouTube when dry-run mode is enabled
  • Refresh tokens are encrypted, and failed token, YouTube, or moderation requests return errors instead of being silently ignored

Impact

✅ Automated removal of abusive YouTube comments
✅ Fewer comments requiring manual review
✅ Auditable moderation decisions

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Issues Found

This PR introduces 4 new server libraries with several blocking defects that must be fixed before merge:

Security Vulnerabilities (2):

  • Hardcoded fallback encryption key in crypto.ts exposes refresh tokens
  • Missing environment variable validation allows undefined values in API calls

Logic Errors (2):

  • Cursor comparison in fetchNewComments skips comments at exact cursor timestamp
  • Missing database transaction in pipeline.ts causes inconsistent state on failures

Crash Risks (2):

  • Missing OPENAI_API_KEY validation causes runtime error
  • Missing GOOGLE_CLIENT_ID/SECRET validation causes runtime error

All issues have actionable fixes provided. Please address these critical defects before merging.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/moderation.ts Outdated
Comment thread src/lib/server/youtube.ts Outdated
Comment thread src/lib/server/crypto.ts
Comment on lines +22 to +24
function key(): Buffer {
return createHash('sha256').update(env.ENCRYPTION_KEY ?? 'insecure-dev-key').digest();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Replace hardcoded fallback key with environment variable validation. The fallback 'insecure-dev-key' creates a critical security risk where production deployments without ENCRYPTION_KEY will use a known, insecure key, exposing all encrypted refresh tokens.1

Suggested change
function key(): Buffer {
return createHash('sha256').update(env.ENCRYPTION_KEY ?? 'insecure-dev-key').digest();
}
function key(): Buffer {
if (!env.ENCRYPTION_KEY) {
throw new Error('ENCRYPTION_KEY environment variable is required');
}
return createHash('sha256').update(env.ENCRYPTION_KEY).digest();
}

Footnotes

  1. CWE-798: Use of Hard-coded Credentials - https://cwe.mitre.org/data/definitions/798.html

Comment thread src/lib/server/youtube.ts Outdated
@codacy-production

codacy-production Bot commented Jul 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 188 complexity · 2 duplication

Metric Results
Complexity 188
Duplication 2

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add server-side crypto, YouTube/OpenAI clients, and moderation pipeline

✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Add AES-256-GCM helpers to encrypt/decrypt stored YouTube refresh tokens.
• Implement minimal YouTube API client for comment fetch and moderation actions.
• Add OpenAI moderation scoring and a rules-first moderation pipeline with audit logging.
Diagram

graph TD
P["pipeline.runChannel"] --> DB[("DB: channels/rules/comments/audit")]
DB --> CR["crypto.decrypt"] --> YT{{"YouTube OAuth/API"}} --> DEC{"Rule match?"}
DEC -- "yes" --> ACT["Rule action + log"] --> YT
DEC -- "no" --> OAI{{"OpenAI Moderation"}} --> ACT
ACT --> DB
subgraph Legend
  direction LR
  _p["Process"] ~~~ _d[("Database")] ~~~ _e{{"External"}} ~~~ _q{"Decision"}
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use official Google API/OAuth client libraries
  • ➕ Built-in retry/backoff patterns and typed request/response helpers
  • ➕ Less hand-rolled HTTP plumbing; easier to extend to more endpoints
  • ➕ Often clearer auth flows and token refresh handling
  • ➖ Heavier dependency footprint and bundle size
  • ➖ May require more setup/abstractions than desired for a minimal server lib phase
2. Adopt envelope encryption / managed key service for refresh tokens
  • ➕ Better operational security (key rotation, auditability, separation of duties)
  • ➕ Avoids reliance on a single static env key across environments
  • ➖ More infrastructure and operational complexity
  • ➖ Harder local-dev story; increases setup burden early
3. Async moderation job queue instead of inline scoring/actions
  • ➕ Improves resilience to OpenAI/YouTube transient failures and rate limits
  • ➕ Smoother throughput control and observability for large channels
  • ➖ Requires job runner/queue infra and additional state machine complexity
  • ➖ Adds latency between comment arrival and action

Recommendation: For Phase C, the current lightweight, dependency-minimal approach is reasonable and keeps the pipeline explicit (rules-first, then AI, then batched actions). If this is moving beyond prototype scale, the two highest-leverage upgrades are (1) adding robust retry/backoff + rate-limit handling around YouTube/OpenAI calls (whether via libraries or custom), and (2) moving refresh-token encryption to a managed/key-rotatable scheme to reduce long-term security risk.

Files changed (4) +381 / -0

Enhancement (4) +381 / -0
crypto.tsAdd AES-256-GCM encrypt/decrypt helpers for token storage +42/-0

Add AES-256-GCM encrypt/decrypt helpers for token storage

• Introduces AES-256-GCM encryption and decryption utilities for sensitive strings. Derives a 32-byte key via SHA-256 from ENCRYPTION_KEY (with a dev fallback) and encodes payload as base64(iv|tag|ciphertext).

src/lib/server/crypto.ts

moderation.tsImplement OpenAI moderation scoring with max-toxicity aggregation +55/-0

Implement OpenAI moderation scoring with max-toxicity aggregation

• Adds a client for OpenAI's /v1/moderations endpoint using model omni-moderation-latest. Computes the overall moderation score as the maximum of six toxicity category scores and returns both the max score and per-category scores.

src/lib/server/moderation.ts

pipeline.tsAdd rules-first moderation pipeline with batching, audit logs, and cursoring +157/-0

Add rules-first moderation pipeline with batching, audit logs, and cursoring

• Implements runChannel() to refresh access tokens, fetch new comments with a cursor, apply keyword/user/regex rules (skipping invalid regex), and fall back to OpenAI scoring when no rule matches. Persists comment decisions, writes audit log entries, batches hold/reject/ban updates (and performs inline deletes), and advances the channel cursor; supports DRY_RUN gating for side-effectful actions.

src/lib/server/pipeline.ts

youtube.tsAdd YouTube OAuth refresh and comment moderation API helpers +127/-0

Add YouTube OAuth refresh and comment moderation API helpers

• Implements refresh token exchange via Google's OAuth token endpoint and a small fetch wrapper for YouTube Data API v3. Adds incremental commentThreads.list fetching with a 3-page limit and cursor stop, batched comments.setModerationStatus (50 ids per call), and comments.delete with 404 tolerated.

src/lib/server/youtube.ts

@codeant-ai

codeant-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

Latest suggestions up to commit 9c38c33
CategorySuggestion                                                                                                                                    Severity
Security
Missing encryption configuration silently protects refresh tokens with a known key

When ENCRYPTION_KEY is missing, all refresh tokens use the publicly known constant
fallback key. Because this key protects the stored OAuth refresh tokens, a
production configuration mistake exposes every channel credential to anyone who
obtains the database. Fail closed when the required key is absent instead of
silently using a fixed fallback.

src/lib/server/crypto.ts [23]

Why it matters? 🤔
  • ❌ Missing production secret exposes stored YouTube refresh tokens.
  • ❌ Compromised tokens enable unauthorized channel access.
  • ⚠️ Pipeline authentication depends on decrypted refresh tokens.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/crypto.ts
**Line:** 23:23
**Comment:**
	*Security: When `ENCRYPTION_KEY` is missing, all refresh tokens use the publicly known constant fallback key. Because this key protects the stored OAuth refresh tokens, a production configuration mistake exposes every channel credential to anyone who obtains the database. Fail closed when the required key is absent instead of silently using a fixed fallback.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Critical
Unbounded execution of user-supplied regular expressions enables regular-expression denial of service

User-configured regexes are executed directly against comment text. A syntactically
valid catastrophic-backtracking pattern can block Node's event loop for a long time
across incoming comments, exhausting or timing out the pipeline; catching
compilation errors does not mitigate this execution risk. Use a non-backtracking
regex engine or enforce a safe pattern/runtime limit.

src/lib/server/pipeline.ts [43]

Why it matters? 🤔
  • ❌ Malicious regexes can block the moderation event loop.
  • ❌ Channel polling can time out before processing comments.
  • ⚠️ User-configured rules affect every incoming comment.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/pipeline.ts
**Line:** 43:43
**Comment:**
	*Security: User-configured regexes are executed directly against comment text. A syntactically valid catastrophic-backtracking pattern can block Node's event loop for a long time across incoming comments, exhausting or timing out the pipeline; catching compilation errors does not mitigate this execution risk. Use a non-backtracking regex engine or enforce a safe pattern/runtime limit.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Critical
Api mismatch
Malformed successful moderation responses are treated as clean comments

A successful HTTP response with no results[0].category_scores is converted into six
zero scores and a zero overall score. runChannel interprets that result as clean and
approves the comment, so a malformed or incomplete moderation response fails open.
Validate the response shape and throw or queue the comment when scores are absent.

src/lib/server/moderation.ts [46]

Why it matters? 🤔
  • ❌ Malformed moderation responses approve comments fail-open.
  • ❌ Toxic comments can bypass AI rejection.
  • ⚠️ Pipeline persists false clean classifications.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/moderation.ts
**Line:** 46:46
**Comment:**
	*Api Mismatch: A successful HTTP response with no `results[0].category_scores` is converted into six zero scores and a zero overall score. `runChannel` interprets that result as clean and approves the comment, so a malformed or incomplete moderation response fails open. Validate the response shape and throw or queue the comment when scores are absent.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Critical
A fixed page cap combined with cursor advancement permanently skips older unseen comments

The hard three-page limit can return only the newest subset of unseen comments,
while the caller advances the cursor to the newest returned timestamp. Older
comments omitted by the page cap then fall below the new cursor and are skipped
permanently. The cursor must only advance past a fully processed range, or
pagination state must be persisted when the page limit is reached.

src/lib/server/youtube.ts [65]

Why it matters? 🤔
  • ❌ High-volume channels permanently lose older comments.
  • ❌ Omitted comments bypass rules and AI scoring.
  • ⚠️ Cursor state no longer represents fully processed history.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/youtube.ts
**Line:** 65:65
**Comment:**
	*Api Mismatch: The hard three-page limit can return only the newest subset of unseen comments, while the caller advances the cursor to the newest returned timestamp. Older comments omitted by the page cap then fall below the new cursor and are skipped permanently. The cursor must only advance past a fully processed range, or pagination state must be persisted when the page limit is reached.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Critical
Race condition
Non-atomic comment claiming allows concurrent runs to process the same comment twice

The existence check is not atomic with the later insert. Two overlapping channel
runs can both observe no existing row, classify the same comment, and issue
duplicate moderation requests; the second insert can then fail on the primary key
and abort the run. Use a per-channel lock or an atomic insert/claim operation.

src/lib/server/pipeline.ts [81-82]

Why it matters? 🤔
  • ❌ Overlapping runs duplicate moderation requests.
  • ❌ One run can abort on the comment primary key.
  • ⚠️ Concurrent processing creates inconsistent audit results.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/pipeline.ts
**Line:** 81:82
**Comment:**
	*Race Condition: The existence check is not atomic with the later insert. Two overlapping channel runs can both observe no existing row, classify the same comment, and issue duplicate moderation requests; the second insert can then fail on the primary key and abort the run. Use a per-channel lock or an atomic insert/claim operation.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Critical
Incomplete implementation
Persisting comments before external moderation succeeds can permanently lose failed moderation actions

Comments are inserted into the local database before the batched YouTube moderation
calls complete. If any setModerationStatus batch fails, the cursor is not advanced,
but a retry finds these rows as existing and skips them, permanently preventing the
failed moderation actions from being retried. Apply the external action before
marking the comment processed, or persist and retry an explicit pending-action
state.

src/lib/server/pipeline.ts [132]

Why it matters? 🤔
  • ❌ Failed YouTube actions become permanently unprocessed.
  • ❌ Held or rejected comments remain in the wrong state.
  • ⚠️ Retries skip rows already stored locally.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/pipeline.ts
**Line:** 132:132
**Comment:**
	*Incomplete Implementation: Comments are inserted into the local database before the batched YouTube moderation calls complete. If any `setModerationStatus` batch fails, the cursor is not advanced, but a retry finds these rows as existing and skips them, permanently preventing the failed moderation actions from being retried. Apply the external action before marking the comment processed, or persist and retry an explicit pending-action state.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Critical
Null pointer
Unexpected YouTube thread data aborts the entire polling run through unchecked dereferences

The code dereferences item.snippet.topLevelComment and then c.snippet without
checking the external YouTube response shape. A deleted or malformed thread can
therefore throw and abort the entire channel run before the cursor is updated,
preventing all remaining comments from being processed. Skip invalid items or handle
them as an API error without discarding progress.

src/lib/server/youtube.ts [79]

Why it matters? 🤔
  • ❌ One malformed thread aborts channel polling.
  • ❌ Remaining valid comments are not processed.
  • ⚠️ Cursor remains unchanged after the failed run.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/youtube.ts
**Line:** 79:79
**Comment:**
	*Null Pointer: The code dereferences `item.snippet.topLevelComment` and then `c.snippet` without checking the external YouTube response shape. A deleted or malformed thread can therefore throw and abort the entire channel run before the cursor is updated, preventing all remaining comments from being processed. Skip invalid items or handle them as an API error without discarding progress.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Major

@codacy-production codacy-production Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements core server-side functionality including encryption, YouTube API integration, and the moderation pipeline. However, the PR is currently not up to standards due to significant security risks, performance inefficiencies, and a complete lack of automated tests.

Two critical security vulnerabilities were identified in src/lib/server/crypto.ts regarding hardcoded fallback keys and improper GCM configuration. Furthermore, src/lib/server/pipeline.ts and src/lib/server/youtube.ts are flagged as high-risk files because they contain high complexity without any test coverage. Several performance bottlenecks exist in the pipeline, specifically N+1 database queries and a lack of batching for OpenAI API calls. All seven required test scenarios for the business logic are currently missing.

About this PR

  • No test files were included in this PR despite implementing complex business logic and security-sensitive encryption. Automated unit and integration tests are required to ensure the stability of the moderation pipeline and security protocols.

Test suggestions

  • Verify AES-256-GCM encryption and decryption round-trip functionality.
  • Verify fetchNewComments stops pagination at the 3rd page or when the cursor is reached.
  • Test moderation scoring logic correctly identifies the maximum score across the 6 toxic categories.
  • Verify rule matching logic for keyword, user ID, and regex patterns.
  • Verify pipeline thresholds correctly categorize comments into rejected, pending, or approved.
  • Ensure DRY_RUN mode prevents calls to setModerationStatus and deleteComment.
  • Verify setModerationStatus batches IDs into groups of 50 per YouTube API limitations.
  • Add unit tests for complex file: src/lib/server/pipeline.ts
  • Add unit tests for complex file: src/lib/server/youtube.ts
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify AES-256-GCM encryption and decryption round-trip functionality.
2. Verify fetchNewComments stops pagination at the 3rd page or when the cursor is reached.
3. Test moderation scoring logic correctly identifies the maximum score across the 6 toxic categories.
4. Verify rule matching logic for keyword, user ID, and regex patterns.
5. Verify pipeline thresholds correctly categorize comments into rejected, pending, or approved.
6. Ensure DRY_RUN mode prevents calls to setModerationStatus and deleteComment.
7. Verify setModerationStatus batches IDs into groups of 50 per YouTube API limitations.
8. Add unit tests for complex file: src/lib/server/pipeline.ts
9. Add unit tests for complex file: src/lib/server/youtube.ts

TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback

Comment thread src/lib/server/crypto.ts Outdated
Comment thread src/lib/server/crypto.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/youtube.ts Outdated
Comment thread src/lib/server/youtube.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/crypto.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
@qodo-code-review

qodo-code-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 19 rules

Grey Divider


Action required

1. Malformed moderation treated safe ✓ Resolved 🐞 Bug ≡ Correctness
Description
scoreComment() substitutes 0 for missing/non-numeric category scores, so malformed or partial
OpenAI moderation responses can yield score=0 and allow auto-approval downstream. This fails open
instead of failing closed.
Code

src/lib/server/moderation.ts[R46-52]

+	const cat = data.results?.[0]?.category_scores ?? {};
+	const scores: Record<string, number> = {};
+	let max = 0;
+	for (const k of TOXIC_CATEGORIES) {
+		const v = typeof cat[k] === 'number' ? cat[k] : 0;
+		scores[k] = v;
+		if (v > max) max = v;
Evidence
The implementation explicitly falls back to {} and then uses 0 for missing scores; the execution
plan specifies this must be treated as an error (missing required category scores).

src/lib/server/moderation.ts[35-54]
EXECUTION_PLAN_YouTube_Comment_Moderator.md[567-570]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`scoreComment()` currently treats missing moderation category scores as `0`, which can cause unsafe approvals when the moderation response is malformed.

### Issue Context
The execution plan requires throwing if required category scores are missing.

### Fix Focus Areas
- src/lib/server/moderation.ts[46-53]
- EXECUTION_PLAN_YouTube_Comment_Moderator.md[567-570]

### Implementation notes
- After parsing JSON, validate `data.results?.[0]?.category_scores` exists.
- Validate all six `TOXIC_CATEGORIES` keys exist and are finite numbers.
- If validation fails, throw an error (so the caller can avoid approving and avoid advancing cursor).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Dry-run mutates DB cursor ✓ Resolved 🐞 Bug ☼ Reliability
Description
When DRY_RUN=true, runChannel() still inserts comment rows and advances channels.cursor, which
can permanently skip moderation on subsequent real runs. It also logs queued items as queue
instead of dry-run, contradicting the documented dry-run behavior.
Code

src/lib/server/pipeline.ts[R121-155]

+			} else if (m.score >= QUEUE) {
+				status = 'pending';
+				decidedBy = 'ai';
+				queued++;
+				await log(channelId, c.id, 'queue', `ai score ${m.score.toFixed(2)}`, 'system');
+			} else {
+				status = 'approved';
+				decidedBy = 'ai';
+			}
+		}
+
+		await db.insert(comments).values({
+			id: c.id,
+			channelId,
+			authorChannelId: c.authorChannelId,
+			authorName: c.authorName,
+			text: c.text,
+			publishedAt: c.publishedAt,
+			status,
+			decidedBy,
+			matchedRuleId,
+			aiScore: aiScoreJson,
+			createdAt: new Date().toISOString()
+		});
+	}
+
+	if (!dryRun) {
+		if (holdIds.length) await setModerationStatus(holdIds, 'heldForReview', false, accessToken);
+		if (rejectIds.length) await setModerationStatus(rejectIds, 'rejected', false, accessToken);
+		if (banIds.length) await setModerationStatus(banIds, 'rejected', true, accessToken);
+	}
+
+	const newest = fresh.map((c) => c.publishedAt).sort().at(-1)!;
+	await db.update(channels).set({ cursor: newest }).where(eq(channels.id, channelId));
+
Evidence
The pipeline computes dryRun but unconditionally inserts into comments and unconditionally
updates channels.cursor; the execution plan explicitly says dry runs must not persist decisions
and must not advance cursor, and shows if (!dryRun) guards.

src/lib/server/pipeline.ts[66-66]
src/lib/server/pipeline.ts[121-155]
EXECUTION_PLAN_YouTube_Comment_Moderator.md[409-410]
EXECUTION_PLAN_YouTube_Comment_Moderator.md[693-730]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`runChannel()` performs DB writes (comment inserts + cursor advance) even when `dryRun` is enabled, and logs queue actions as `queue` regardless of dry-run.

### Issue Context
The repo’s reliability requirements state that dry runs should not persist decisions and must never advance the cursor.

### Fix Focus Areas
- src/lib/server/pipeline.ts[66-66]
- src/lib/server/pipeline.ts[121-155]
- EXECUTION_PLAN_YouTube_Comment_Moderator.md[409-410]
- EXECUTION_PLAN_YouTube_Comment_Moderator.md[693-730]

### Implementation notes
- Wrap `db.insert(comments)...` in `if (!dryRun)`.
- Wrap the cursor update in `if (!dryRun)`.
- Change the queue audit log call to `dryRun ? 'dry-run' : 'queue'`.
- Keep counters (`fetched/acted/queued`) working for dry runs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Persisted before YT writes ✓ Resolved 🐞 Bug ☼ Reliability
Description
For hold/reject/ban, runChannel() writes audit log rows and inserts comment decisions before
calling setModerationStatus(), so a YouTube API failure leaves the DB claiming moderation happened
while YouTube remains unchanged. Because later runs skip existing comment IDs, those missed YouTube
actions may never be retried.
Code

src/lib/server/pipeline.ts[R80-151]

+	for (const c of fresh) {
+		const existing = await db.select().from(comments).where(eq(comments.id, c.id)).get();
+		if (existing) continue;
+
+		let status = 'pending';
+		let decidedBy = 'none';
+		let matchedRuleId: number | null = null;
+		let aiScoreJson: string | null = null;
+
+		const hit = matchRule(c.text, c.authorChannelId, rs);
+		if (hit) {
+			matchedRuleId = hit.id;
+			decidedBy = 'rule';
+			const reason = `rule #${hit.id} (${hit.type}: ${hit.pattern.slice(0, 80)})`;
+			if (hit.action === 'hold') {
+				status = 'held';
+				holdIds.push(c.id);
+				await log(channelId, c.id, dryRun ? 'dry-run' : 'hold', reason, 'system');
+			} else if (hit.action === 'reject') {
+				status = 'rejected';
+				rejectIds.push(c.id);
+				await log(channelId, c.id, dryRun ? 'dry-run' : 'reject', reason, 'system');
+			} else if (hit.action === 'delete') {
+				status = 'deleted';
+				if (!dryRun) await deleteComment(c.id, accessToken);
+				await log(channelId, c.id, dryRun ? 'dry-run' : 'delete', reason, 'system');
+			} else if (hit.action === 'ban') {
+				status = 'rejected';
+				banIds.push(c.id);
+				await log(channelId, c.id, dryRun ? 'dry-run' : 'ban', reason, 'system');
+			}
+			acted++;
+		} else {
+			const m = await scoreComment(c.text);
+			aiScoreJson = JSON.stringify(m.scores);
+			if (m.score >= AUTO_REJECT) {
+				status = 'rejected';
+				decidedBy = 'ai';
+				rejectIds.push(c.id);
+				await log(channelId, c.id, dryRun ? 'dry-run' : 'reject', `ai score ${m.score.toFixed(2)}`, 'system');
+				acted++;
+			} else if (m.score >= QUEUE) {
+				status = 'pending';
+				decidedBy = 'ai';
+				queued++;
+				await log(channelId, c.id, 'queue', `ai score ${m.score.toFixed(2)}`, 'system');
+			} else {
+				status = 'approved';
+				decidedBy = 'ai';
+			}
+		}
+
+		await db.insert(comments).values({
+			id: c.id,
+			channelId,
+			authorChannelId: c.authorChannelId,
+			authorName: c.authorName,
+			text: c.text,
+			publishedAt: c.publishedAt,
+			status,
+			decidedBy,
+			matchedRuleId,
+			aiScore: aiScoreJson,
+			createdAt: new Date().toISOString()
+		});
+	}
+
+	if (!dryRun) {
+		if (holdIds.length) await setModerationStatus(holdIds, 'heldForReview', false, accessToken);
+		if (rejectIds.length) await setModerationStatus(rejectIds, 'rejected', false, accessToken);
+		if (banIds.length) await setModerationStatus(banIds, 'rejected', true, accessToken);
+	}
Evidence
The code skips already-seen comments early, inserts comment rows inside the loop, and only
afterwards performs the YouTube moderation writes. If the later YouTube call throws, the inserted
rows remain and subsequent runs will skip retrying due to the existing-comment check.

src/lib/server/pipeline.ts[80-83]
src/lib/server/pipeline.ts[94-110]
src/lib/server/pipeline.ts[132-151]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The pipeline persists decisions (audit + comment rows) before performing the required YouTube moderation writes, but also de-duplicates by skipping existing comment IDs. If `setModerationStatus()` fails, the DB blocks retries and remote state stays unmoderated.

### Issue Context
This is especially risky because YouTube write calls are batched after the loop, while comment rows are inserted inside the loop.

### Fix Focus Areas
- src/lib/server/pipeline.ts[80-83]
- src/lib/server/pipeline.ts[94-110]
- src/lib/server/pipeline.ts[132-151]

### Implementation notes
Pick one approach:
1) **Two-phase with success tracking (recommended):**
  - First, compute decisions and stage them in memory.
  - Execute YouTube writes (setModerationStatus/delete) first.
  - Only after success, insert `comments` rows and corresponding `audit_log` rows.

2) **Persist with retryable state:**
  - Insert `comments` rows with a status like `pending_youtube_write` and do NOT skip such rows on the next run.
  - After successful YouTube writes, update to final status and write a final audit entry.

Also ensure audit logs reflect actual outcome (success vs failure).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. ENCRYPTION_KEY has string fallback ✓ Resolved 📘 Rule violation ⛨ Security
Description
key() falls back to the hard-coded string insecure-dev-key when env.ENCRYPTION_KEY is unset
(or empty), creating a predictable encryption key that can make stored YouTube refresh tokens
decryptable under misconfiguration and violating the requirement to avoid hard-coded secrets in
version-controlled source. The code should fail fast instead of silently deriving an AES key from a
known constant.
Code

src/lib/server/crypto.ts[R22-24]

+function key(): Buffer {
+	return createHash('sha256').update(env.ENCRYPTION_KEY ?? 'insecure-dev-key').digest();
+}
Evidence
PR Compliance ID 2401162 prohibits hard-coded secrets in committed source, and the current key()
implementation in src/lib/server/crypto.ts explicitly derives the AES key from `env.ENCRYPTION_KEY
?? 'insecure-dev-key'`, embedding a constant fallback. This directly contradicts the repo’s
execution plan, which states ENCRYPTION_KEY must be required (throw if missing), so the presence
of a fallback both introduces an insecure, publicly known key path for refresh-token
encryption/decryption and represents a plan/verification failure if the env var is absent or blank.

Rule 2401162: Disallow hard-coded secrets in version-controlled source files
src/lib/server/crypto.ts[22-24]
EXECUTION_PLAN_YouTube_Comment_Moderator.md[370-373]
.env.example[19-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/lib/server/crypto.ts` currently derives the AES-256-GCM key from `env.ENCRYPTION_KEY ?? 'insecure-dev-key'`, embedding a secret-like fallback in source control and silently using a known constant key when `ENCRYPTION_KEY` is missing (and even an empty value can lead to a fixed/guessable key). This is security-sensitive because it can make stored YouTube refresh tokens decryptable under misconfiguration and violates the requirement to avoid hard-coded secrets in committed source.

## Issue Context
- This crypto module is used for refresh-token encryption/decryption, so any predictable fallback key is a direct confidentiality risk.
- The repo’s execution plan explicitly requires failing fast when `ENCRYPTION_KEY` is missing, so the current fallback is also a plan/verification mismatch.

## Fix Focus Areas
- src/lib/server/crypto.ts[22-24]
- EXECUTION_PLAN_YouTube_Comment_Moderator.md[370-373]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. No fetch timeout/retry ✓ Resolved 🐞 Bug ☼ Reliability
Description
OpenAI/Google/YouTube calls use raw fetch() without an AbortSignal timeout or transient-failure
retry/backoff, so a hung request can stall a channel run and transient 429/5xx/network failures will
fail immediately instead of being retried. This contradicts the repo’s documented reliability
requirements for these steps.
Code

src/lib/server/youtube.ts[R32-55]

+export async function refreshAccessToken(refreshToken: string): Promise<string> {
+	const res = await fetch('https://oauth2.googleapis.com/token', {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+		body: new URLSearchParams({
+			client_id: env.GOOGLE_CLIENT_ID!,
+			client_secret: env.GOOGLE_CLIENT_SECRET!,
+			refresh_token: refreshToken,
+			grant_type: 'refresh_token'
+		})
+	});
+	const data = await res.json();
+	if (!res.ok || !data.access_token) {
+		throw new Error(`token refresh failed: ${res.status} ${JSON.stringify(data)}`);
+	}
+	return data.access_token as string;
+}
+
+async function ytFetch(path: string, accessToken: string, init?: RequestInit): Promise<Response> {
+	const res = await fetch(`${YT}${path}`, {
+		...init,
+		headers: { Authorization: `Bearer ${accessToken}`, ...(init?.headers ?? {}) }
+	});
+	return res;
Evidence
The code paths for Google OAuth refresh, general YouTube API calls, and OpenAI moderation all call
fetch() directly with no timeout signal and no retry logic; the execution plan explicitly requires
timeout + retry for these phases.

src/lib/server/youtube.ts[32-55]
src/lib/server/moderation.ts[36-43]
EXECUTION_PLAN_YouTube_Comment_Moderator.md[398-403]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
External HTTP requests (OpenAI moderation, Google token refresh, YouTube API calls) are made without a timeout and without retry/backoff for transient errors.

### Issue Context
The execution plan specifies a shared `fetchWithTimeout` (10s) and bounded retries for network failures / 429 / 5xx.

### Fix Focus Areas
- src/lib/server/youtube.ts[32-55]
- src/lib/server/moderation.ts[36-43]
- EXECUTION_PLAN_YouTube_Comment_Moderator.md[398-403]

### Implementation notes
- Create a small helper (e.g. `src/lib/server/http.ts`) implementing:
 - `fetchWithTimeout(url, init, timeoutMs=10_000)` using `AbortController`.
 - `fetchWithRetry(...)` that retries **only** on network errors, 429, and 5xx (max 3 attempts, exponential backoff, honor `Retry-After` when present).
- Use the helper in:
 - `scoreComment()`
 - `refreshAccessToken()`
 - `ytFetch()`
- Be careful with retries for mutating YouTube endpoints: retry only when safe/idempotent and when the error indicates the request did not succeed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/lib/server/crypto.ts
Comment thread src/lib/server/moderation.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/pipeline.ts Outdated
Comment thread src/lib/server/youtube.ts Outdated
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch phase-c-server-libs (commit: 9e7eaa49e9de84bec537e6ad19809b727b9953ad)

Docstrings generation was requested by @Bonobo791.

The following files were modified:

* `src/lib/server/crypto.ts`
* `src/lib/server/http.ts`
* `src/lib/server/moderation.ts`
* `src/lib/server/pipeline.ts`
* `src/lib/server/youtube.ts`

These files were ignored:
* `src/lib/server/http.test.ts`

These file types are not supported:
* `package.json`
@cla-bot cla-bot Bot removed the cla-signed label Jul 29, 2026
Comment thread src/lib/server/db/schema.ts
Repository owner deleted a comment from cla-bot Bot Jul 29, 2026
Repository owner deleted a comment from cla-bot Bot Jul 29, 2026
Repository owner deleted a comment from cla-bot Bot Jul 29, 2026
@cla-bot

cla-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @coderabbitai[bot] on file. In order for us to review and merge your code, please contact the project maintainers to get yourself added.

Bonobo791 added a commit that referenced this pull request Jul 31, 2026
- Extract TONE_PROMPT into src/lib/server/tonePrompt.js, a dependency-free
  module imported by both tone.ts and the eval script — eliminates the
  brittle regex source extraction that could silently truncate the rubric
  (qodo #4, codacy MEDIUM).
- Replace the hand-rolled .env parser with node:process loadEnvFile: no
  backtracking regex (coderabbit), quoted/inline-comment values handled
  correctly (qodo #3), and existing env vars are never overridden.
- A missing .env is now a logged skip, not a fatal error, so the harness
  runs in env-only CI setups (qodo #2, codacy MEDIUM).
- Guard the script's main flow so tests import helpers without live API
  calls; add a fetch timeout. New tests cover each finding (failing first,
  per repo rules).
Bonobo791 added a commit that referenced this pull request Aug 2, 2026
… the sweep, contract-drop deleted_at

Audit against the sqlite-engineering skill:

- Journal append-only: 0009/0010 are applied to prod since the hotfix, so
  the renumber violated immutable history. Restored both migrations, their
  snapshots, and their journal entries; the consent-email work is now a
  NEW migration 0011.
- Unindexed sweep (field failure #3): the 10-year sweep's WHERE email IS
  NOT NULL AND created_at < cutoff scanned the whole table per cron tick.
  0011 creates partial index consents_email_retention_idx; an EXPLAIN
  QUERY PLAN test proves SEARCH, not SCAN.
- 0011 is also the CONTRACT phase for the abandoned soft delete: drops
  users.deleted_at and its index (no code path reads them in v2).
- DEPLOY.md gains the post-migrate verification step (field failure #4):
  check the actual schema, never the drizzle-kit exit code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant