Skip to content

feat: every moderation action is logged and reversible (undo from the audit log) - #46

Merged
Bonobo791 merged 8 commits into
mainfrom
feat-reversible-actions
Aug 2, 2026
Merged

feat: every moderation action is logged and reversible (undo from the audit log)#46
Bonobo791 merged 8 commits into
mainfrom
feat-reversible-actions

Conversation

@Bonobo791

@Bonobo791 Bonobo791 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

User description

Behavior

Logging was already complete (audit_log covers pipeline, queue, and dry-run actions); this PR adds reversibility:

  • The audit log now shows an Undo button on the latest action per comment when it's reversible: hold/reject restore the comment at YouTube via setModerationStatus(..., 'published'); ban offers "Undo comment" (the comment is restored — YouTube has no API to lift an author ban); delete offers nothing (permanent).
  • The undo action follows the queue page's established pattern: atomic conditional claim on the comment (concurrent submissions single-winner, loser 404s), DB before the remote call (I3), claim released on YouTube failure so the restore stays retryable, loud errors only. DRY_RUN=true records a dry-run audit row and makes no YouTube call (I8). Every undo writes a restore audit row naming the original action (derived server-side).
  • A restored comment becomes approved/human.

Disclosure (per review feedback):

  • Terms §9.4 states what's logged and exactly what cannot be reversed (deletes, author bans) → LEGAL_VERSION bumped to 1.3, so existing users re-accept via the established /consent flow on next login.
  • New Help tab in the app nav with a plain-language reversibility matrix matching Terms §9.4.

Verification

  • 289 tests passing (each behavior change landed failing-test-first), svelte-check clean, production build green.
  • New coverage: undo restore/ban/404/release-on-failure/dry-run/401/404-ownership/400, load undoable flags, published API params, Terms §9.4 disclosure guard, Help tab presence + copy.

Notes

  • No schema migration (audit action and comment status are free-text enums; comment updated).
  • Author-unban is impossible via the YouTube API — disclosed in Terms + Help, not faked as functionality.

CodeAnt-AI Description

Add reversible moderation actions through the audit log

What Changed

  • Users can undo the latest hold or reject action from the audit log, restoring the comment on YouTube and marking it approved.
  • Undoing a ban restores the comment but clearly leaves the author ban in place; deleted comments remain permanent.
  • Audit entries now show undo controls only for the latest eligible action, with reliable ordering when actions share a timestamp.
  • Failed or interrupted restores remain retryable, and dry-run restores are recorded without contacting YouTube.
  • Added Help guidance and Terms disclosure explaining which moderation actions can and cannot be reversed.

Impact

✅ Restore held or rejected comments from the audit log
✅ Retry interrupted restores without losing the action
✅ Clearer limits for bans and deletions

💡 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.

@cla-bot cla-bot Bot added the cla-signed label Aug 2, 2026
@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed fa01102 Aug 02, 2026 · 14:51 14:54
✅ Reviewed your PR 14cac31 Aug 02, 2026 · 14:35 14:38

@netlify

netlify Bot commented Aug 2, 2026

Copy link
Copy Markdown

Deploy Preview for moderaty ready!

Name Link
🔨 Latest commit a621fcf
🔍 Latest deploy log https://app.netlify.com/projects/moderaty/deploys/6a6f5aa61c9eda0008ce00b9
😎 Deploy Preview https://deploy-preview-46--moderaty.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 90
Accessibility: 97
Best Practices: 100
SEO: 100
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Undo controls for reversible hold, reject, and ban actions in the audit log.
    • Added comment-only restoration when an author ban cannot be reversed.
    • Added restoration status and success or error feedback.
    • Added a Help page covering moderation workflows, audit logs, and action reversibility.
    • Added a Help link to the application navigation.
  • Documentation

    • Updated Terms of Service disclosures for audit logging and irreversible deletions or bans.
    • Updated the legal document version to 1.3.

Walkthrough

The change adds audit-log undo actions for held, rejected, and banned comments. It restores comments through YouTube when possible, records restore outcomes, adds UI controls and Help content, and updates Terms §9 and the legal version.

Changes

Audit Log Undo Flow

Layer / File(s) Summary
Restoration contract and audit action
src/lib/server/youtube.ts, src/lib/server/youtube.test.ts, src/lib/server/db/schema.ts
setModerationStatus accepts published for restoration. The schema documents the restoring status and restore audit action. Tests verify the YouTube request parameters.
Audit log undo backend
src/routes/(app)/channels/[id]/log/+page.server.ts, src/routes/(app)/channels/[id]/log/*.test.ts
The loader marks only current reversible actions as undoable. The undo action validates access, claims comments atomically, restores them, handles failures and dry runs, and records audit entries.
Undo controls and Help page
src/routes/(app)/+layout.svelte, src/routes/(app)/channels/[id]/log/+page.svelte, src/routes/(app)/help/*
The audit log renders full or comment-only restoration controls. Navigation exposes Help content that describes reversible and permanent actions.
Legal disclosure and version
src/lib/components/landing/legal/Terms.svelte, src/lib/landing/legal.ts, src/lib/landing/legal.test.ts
Terms §9 documents audit logging and action permanence. The shared legal version changes to 1.3, with regression coverage.

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

Sequence Diagram(s)

sequenceDiagram
  actor Moderator
  participant AuditLogPage
  participant UndoAction
  participant Database
  participant YouTubeAPI
  Moderator->>AuditLogPage: Submit undo with commentId
  AuditLogPage->>UndoAction: Authenticate and validate request
  UndoAction->>Database: Claim reversible comment
  UndoAction->>YouTubeAPI: Set moderationStatus=published
  YouTubeAPI-->>UndoAction: Return restoration result
  UndoAction->>Database: Update comment and record restore audit entry
  UndoAction-->>AuditLogPage: Return success or failure
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title identifies moderation logging and undo functionality, which are central changes, but it overstates reversibility because deletes and author bans cannot be reversed.
Description check ✅ Passed The description clearly explains the audit-log undo behavior, irreversible actions, retry handling, disclosures, and verification coverage.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-reversible-actions

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 Aug 2, 2026
@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds undo for the latest hold, reject, or ban action shown in the audit log. The backend claims the comment, restores it on YouTube when enabled, and records the restore action.

sequenceDiagram
    participant User
    participant Audit Log
    participant Backend
    participant Database
    participant YouTube

    User->>Audit Log: Select Undo
    Audit Log->>Backend: Submit comment restore
    Backend->>Database: Claim and mark comment approved
    Backend->>YouTube: Restore comment as published
    YouTube-->>Backend: Restore confirmed
    Backend->>Database: Record restore audit action
    Backend-->>Audit Log: Show restore success
Loading

Generated by CodeAnt AI

@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: a621fcf4
Scan Time: 2026-08-02 15:01:45 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found
Duplicate Code ✅ PASSED 0.0% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating S: No bugs
IAC ✅ PASSED No IAC issues

View Full Results

@codacy-production

codacy-production Bot commented Aug 2, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🔴 Metrics 32 complexity · 3 duplication

Metric Results
Complexity 32 (≤ 100 complexity)
Duplication ⚠️ 3 (≤ 1 duplication)

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 undo support for moderation actions via the audit log

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add reversible “Undo” flows for held/rejected (and comment-only for bans) actions.
• Implement server-side undo with atomic DB claim, YouTube restore call, and audit logging.
• Disclose irreversible actions in Terms/Help and force re-consent via legal version bump.
Diagram

graph TD
U["Audit log page"] --> L["load() marks undoable"] --> DB[("SQLite DB")]
U --> A["undo action"] --> DB --> YT{{"YouTube API"}} --> DB
U --> LEG["Terms & Help disclosure"]
subgraph Legend
  direction LR
  _ui["UI/Page"] ~~~ _db[("Database")] ~~~ _ext{{"External API"}}
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Outbox/job-based undo (async restore worker)
  • ➕ Avoids holding request open on slow/failed YouTube calls
  • ➕ Natural retry/backoff and observability for restore attempts
  • ➕ Can guarantee DB/remote consistency via durable workflow
  • ➖ More infrastructure (queue/worker) and operational complexity
  • ➖ User experience becomes eventual-consistency (undo may complete later)
  • ➖ More code paths to test (enqueue, process, reconcile)
2. Explicit undo token / idempotency key per audit row
  • ➕ Makes undo strictly idempotent and easier to reason about
  • ➕ Eliminates reliance on comment status as the only concurrency gate
  • ➖ Requires schema/state expansion (token storage, consumed markers)
  • ➖ More complex UI wiring (undo specific audit entry vs latest action rule)
3. Persist undoable computation in DB (materialized latest-action view)
  • ➕ Faster load and simpler page logic for large audit histories
  • ➕ Centralizes latest-per-comment logic for reuse across endpoints
  • ➖ Adds maintenance complexity (keeping materialization correct)
  • ➖ Current 200-row limit likely makes this premature optimization

Recommendation: Keep the current approach: computing undoability at load-time and using an atomic conditional DB claim before calling YouTube is a good fit for the synchronous, user-initiated undo UX. It minimizes schema churn, prevents concurrent double-undos, and correctly keeps the restore retryable by releasing the claim on remote failure; the heavier outbox/worker alternative is only worth it if YouTube failures/latency become a frequent operational issue.

Files changed (13) +455 / -11

Enhancement (4) +121 / -8
youtube.tsAllow setModerationStatus('published') for undo restores +5/-1

Allow setModerationStatus('published') for undo restores

• Extends setModerationStatus to accept 'published' as a valid status to restore held/rejected comments. Adds documentation clarifying that deletes and author bans remain irreversible due to YouTube API limitations.

src/lib/server/youtube.ts

+layout.svelteAdd Help link to app navigation +1/-0

Add Help link to app navigation

• Adds a new /help link in the authenticated app navigation to surface reversibility guidance alongside the undo feature.

src/routes/(app)/+layout.svelte

+page.server.tsCompute undoable audit entries and implement undo action +88/-3

Compute undoable audit entries and implement undo action

• Enhances audit log load to mark only the latest reversible action per comment as undoable (full for hold/reject, comment-only for ban). Adds an undo server action that atomically claims the comment in DB, restores it on YouTube (unless DRY_RUN), releases the claim on failure, and records a restore/dry-run audit row with server-derived prior action naming.

src/routes/(app)/channels/[id]/log/+page.server.ts

+page.svelteRender Undo controls and restore status in audit log UI +27/-4

Render Undo controls and restore status in audit log UI

• Adds an Undo column with per-row forms for reversible actions and differentiates full undo vs comment-only undo for bans. Adds flash messaging for undo results and treats 'restore' as an OK badge state.

src/routes/(app)/channels/[id]/log/+page.svelte

Tests (5) +253 / -1
legal.test.tsAdd Terms disclosure guard tests for irreversibility claims +9/-0

Add Terms disclosure guard tests for irreversibility claims

• Adds a test ensuring the Terms include audit-log logging and explicitly disclose that deletes and author bans cannot be undone. Prevents shipping an undo UX without accurate legal disclosure.

src/lib/landing/legal.test.ts

youtube.test.tsTest restoring comments via moderationStatus=published +12/-0

Test restoring comments via moderationStatus=published

• Adds a unit test asserting setModerationStatus sends moderationStatus=published and banAuthor=false when restoring a comment. Ensures the undo path uses the expected YouTube API parameters.

src/lib/server/youtube.test.ts

actions.test.tsAdd comprehensive tests for undo action behavior +151/-0

Add comprehensive tests for undo action behavior

• Introduces tests covering successful restores, ban undo naming, 404 for deleted comments, claim release on YouTube failure, DRY_RUN behavior, 401 when signed out, 404 for non-owned channel, and 400 for missing commentId. Mocks YouTube and env dependencies while asserting DB state and audit log writes.

src/routes/(app)/channels/[id]/log/actions.test.ts

load.test.tsTest undoable flagging on audit log load +38/-1

Test undoable flagging on audit log load

• Adds a test ensuring only the latest action per comment is marked undoable and that restore/approve and superseded actions are not undoable. Expands test seeding to include audit_log rows.

src/routes/(app)/channels/[id]/log/load.test.ts

help.test.tsTest Help tab presence and disclosure consistency +43/-0

Test Help tab presence and disclosure consistency

• Adds tests verifying the Help link exists in app nav and that the Help page copy matches the Terms disclosure (permanence of deletes and author bans). Also asserts that the audit log is referenced for undoing holds/rejects.

src/routes/(app)/help/help.test.ts

Documentation (3) +80 / -1
Terms.svelteDisclose irreversible moderation actions in Terms §9.4 +2/-0

Disclose irreversible moderation actions in Terms §9.4

• Adds a new Terms section explicitly stating all moderation actions are logged and clarifying which actions can/cannot be reversed. Documents that deletes and author bans are permanent due to missing YouTube API support.

src/lib/components/landing/legal/Terms.svelte

schema.tsDocument new audit_log action value: restore +1/-1

Document new audit_log action value: restore

• Updates the audit_log.action comment to include the new 'restore' action value used for undo operations. No schema migration is introduced; this is a documentation-level update for the free-text enum usage.

src/lib/server/db/schema.ts

+page.svelteAdd Help page with reversibility matrix +77/-0

Add Help page with reversibility matrix

• Creates a Help page explaining moderation behavior, the audit log, and a clear matrix of which actions can be undone. Explicitly states that deletes and author bans are permanent and links back to Terms §9.4.

src/routes/(app)/help/+page.svelte

Other (1) +1 / -1
legal.tsBump LEGAL_VERSION to force re-consent +1/-1

Bump LEGAL_VERSION to force re-consent

• Updates LEGAL_VERSION from 1.2 to 1.3 to trigger the existing consent flow so existing users re-accept updated Terms.

src/lib/landing/legal.ts

@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.

The reversibility feature is well-implemented with proper authorization, atomic operations, error handling, and audit logging. The code follows established patterns (claim-before-call, release-on-failure) and includes comprehensive test coverage (289 tests). The legal disclosure updates and help documentation appropriately document the limitations of YouTube's API (author bans cannot be lifted).

Note: I flagged one concern about the status check logic, but upon further analysis the implementation appears correct - when YouTube bans a comment it sets the status to rejected, which the check on line 79 properly handles. Please verify this behavior matches YouTube's actual API response for banned comments.


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 on lines +79 to +81
if (!comment || (comment.status !== 'held' && comment.status !== 'rejected')) {
throw error(404, 'reversible comment not found in this channel');
}

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.

🛑 Logic Error: The undo handler rejects ban actions despite the UI marking them as undoable. Lines 48-52 flag ban actions as undoable='comment-only', but line 79 only accepts held or rejected status. When a user bans a comment, YouTube sets it to rejected status, but this check doesn't account for the fact that the latest action in the audit log is ban, not reject. If the comment status changed between the ban and the undo attempt, this will incorrectly throw 404 for legitimate undo requests.

@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 successfully implements an 'undo' feature for moderation actions with atomic database updates and YouTube API integration. However, Codacy results indicate the PR is not up to standards due to increased code cloning in the test suites. Two primary issues should be addressed before merging: the logic for displaying the 'Undo' button in the audit log is currently too strict, as it only checks the absolute latest log entry, potentially hiding the button if a non-state-changing action (like a dry-run) occurs. Additionally, the server-side action for undoing moderation should return a fail response rather than throwing an error to prevent full-page crashes and allow the UI to display the error message.

Test suggestions

  • Verify YouTube API call uses 'published' status and no author ban for restorations\n- [x] Verify successful undo of a rejected comment updates DB to 'approved' and inserts 'restore' log\n- [x] Verify undo of a 'ban' correctly restores the comment and names 'ban' in the audit reason\n- [x] Verify undo attempt on a deleted comment results in a 404 error and no change\n- [x] Verify that a YouTube API failure reverts the DB status to the original state (releasing the claim)\n- [x] Verify DRY_RUN mode correctly bypasses the API and records a 'dry-run' action\n- [x] Verify audit log loading logic correctly identifies only the latest action per comment as undoable\n- [x] Verify Terms of Service and Help page contain the required permanence disclosures for deletes/bans

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

.update(comments)
.set({ status: comment.status, decidedBy: comment.decidedBy })
.where(eq(comments.id, commentId));
throw e;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM RISK

Suggestion: Returning 'fail' instead of throwing an error ensures the user stays on the page and receives feedback if the YouTube API call fails.\n\nsuggestion\n\t\t\treturn fail(500, { error: e instanceof Error ? e.message : 'YouTube API failure' });\n

channelId: text('channel_id').notNull(),
commentId: text('comment_id').notNull(),
action: text('action').notNull(), // 'hold' | 'reject' | 'delete' | 'ban' | 'approve' | 'queue' | 'dry-run'
action: text('action').notNull(), // 'hold' | 'reject' | 'delete' | 'ban' | 'approve' | 'restore' | 'queue' | 'dry-run'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚪ LOW RISK

Nitpick: Ensure the schema comment includes all actions used by the application logic, including the newly added 'restore' action.

action: dryRun ? 'dry-run' : 'restore',
reason: `undo of ${prior?.action ?? 'moderation action'}`,
actor: 'user',
createdAt: new Date().toISOString()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚪ LOW RISK

Nitpick: The createdAt field has a database-level default. Omit it here to let SQLite handle the timestamp consistently using its own internal clock and the schema's defined strftime format.

@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

Previous suggestions up to commit 14cac31
CategorySuggestion                                                                                                                                    SeverityGenerated at (UTC)
Api mismatch
Restoration sends an unsupported ban parameter with the published moderation status

The YouTube API only supports banAuthor when moderationStatus is rejected; sending
banAuthor=false alongside moderationStatus=published can make the restore request
fail with a 400. The new restore path must omit that parameter for published
restores or use a request-specific API helper.

src/routes/(app)/channels/[id]/log/+page.server.ts [94]

Why it matters? 🤔
  • ❌ Normal non-dry-run Undo requests can fail at YouTube.
  • ❌ Held and rejected comments cannot be restored remotely.
  • ⚠️ The local claim is released and the audit row is not written.

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/routes/(app)/channels/[id]/log/+page.server.ts
**Line:** 94:94
**Comment:**
	*Api Mismatch: The YouTube API only supports `banAuthor` when `moderationStatus` is `rejected`; sending `banAuthor=false` alongside `moderationStatus=published` can make the restore request fail with a 400. The new restore path must omit that parameter for published restores or use a request-specific API helper.

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
Major2026-08-02 14:38

Latest suggestions up to commit fa01102
CategorySuggestion                                                                                                                                    SeverityGenerated at (UTC)
Api mismatch
Undo accepts comments based on status without checking that their latest audit action is reversible

The server action authorizes undo solely from the current comment status and never
verifies that the latest audit entry is the reversible action being undone. A
crafted POST can therefore restore any held or rejected comment whose latest audit
action is dry-run, approve, or another non-undoable action, even though the loader
does not expose an Undo button for it. Validate the latest audit row and require it
to be hold, reject, or ban before claiming the comment.

src/routes/(app)/channels/[id]/log/+page.server.ts [76-83]

Why it matters? 🤔
  • ❌ Non-undoable dry-run actions can be undone directly.
  • ⚠️ Comment status can change without matching UI authorization.
  • ⚠️ Audit rows can describe the wrong action 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/routes/(app)/channels/[id]/log/+page.server.ts
**Line:** 76:83
**Comment:**
	*Api Mismatch: The server action authorizes undo solely from the current comment status and never verifies that the latest audit entry is the reversible action being undone. A crafted POST can therefore restore any held or rejected comment whose latest audit action is `dry-run`, `approve`, or another non-undoable action, even though the loader does not expose an Undo button for it. Validate the latest audit row and require it to be `hold`, `reject`, or `ban` before claiming the comment.

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
Major2026-08-02 14:53

@qodo-code-review

qodo-code-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 73 rules

Grey Divider


Action required

1. Undo can lose audit row ✓ Resolved 🐞 Bug ☼ Reliability
Description
The undo action restores the comment on YouTube and then inserts the restore/dry-run audit row; if
that insert fails, the restore has already happened but will not be recorded. Because the comment
was already flipped to approved, subsequent undo retries will 404 and cannot recreate the missing
audit record.
Code

src/routes/(app)/channels/[id]/log/+page.server.ts[R112-115]

+		await db.insert(auditLog).values({
+			channelId: params.id,
+			commentId,
+			action: dryRun ? 'dry-run' : 'restore',
Relevance

●● Moderate

Real reliability risk, but similar “reorder remote vs DB” concerns were previously rejected; outcome
uncertain.

PR-#42

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Undo first enforces held/rejected eligibility, then updates the comment to approved before
calling YouTube, and finally inserts the audit log row without handling insert failures. If that
last insert throws, the comment remains approved (so future undo attempts fail the held/rejected
check) and the restore action is permanently missing from the audit trail.

src/routes/(app)/channels/[id]/log/+page.server.ts[74-120]
PR-#3

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

## Issue description
`actions.undo` has a failure window after a successful YouTube restore: it updates the comment to `approved` and performs the remote call, then writes the audit row. If the final `db.insert(auditLog)` fails (e.g., transient DB lock/error), the system ends up with a successful external side-effect but no corresponding audit entry, and the undo is no longer retryable because the comment is no longer `held`/`rejected`.

## Issue Context
- Undo is intended to be auditable and reversible; missing the restore audit row undermines this feature and can create compliance/operability gaps.
- The current try/catch only releases the claim on YouTube failures; it does not handle audit insert failures.

## Fix Focus Areas
- src/routes/(app)/channels/[id]/log/+page.server.ts[74-120]

### Suggested change options (pick one)
1) **Introduce a retryable intermediate state** (no migration needed since status is free-text):
  - Claim by setting `status: 'restoring'` (or similar) before the remote call.
  - After remote success, in a DB transaction: (a) insert the audit row, (b) set `status: 'approved', decidedBy: 'human'`.
  - If audit insert fails, keep `status: 'restoring'` so a subsequent retry can re-attempt audit insertion (and/or reconciliation).

2) **Outbox/pending-intent approach**:
  - Insert an audit/intention row (e.g., `restore_requested`) in the same transaction as the claim.
  - After remote success, update it to `restore` (or add a completion row) and finalize the comment status.

3) At minimum: **retry the audit insert** on transient DB failures and ensure there is a recovery path if the insert ultimately fails (so you don’t end up with “restored but unlogged and non-retryable”).

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



Remediation recommended

2. Server-only help.test.ts in routes 📘 Rule violation ⌂ Architecture
Description
The new help.test.ts and actions.test.ts files import server-only dependencies (Node.js server
APIs and $lib/server/* modules) but are placed under src/routes/ instead of the required
src/lib/server/ directory. This violates the server-only module placement rule and increases the
risk of server/client boundary confusion or accidental client-side bundling/structure drift.
Code

src/routes/(app)/help/help.test.ts[R19-22]

+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+import { describe, expect, it } from 'vitest';
Relevance

●●● Strong

Clear repo compliance rule: server-only Node/$lib/server tests shouldn’t live under src/routes;
likely enforced.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2401095 requires server-only modules—including anything importing Node
filesystem/path/url APIs or other server helpers—to live under src/lib/server/. The added
src/routes/(app)/help/help.test.ts imports node:fs, node:url, and node:path while residing
under src/routes/(app)/help/, and src/routes/(app)/channels/[id]/log/actions.test.ts imports
$lib/server/testdb and $lib/server/db/schema while residing under src/routes/(app)/...; both
locations are outside src/lib/server/, demonstrating the violation.

Rule 2401095: Place server-only modules under src/lib/server
src/routes/(app)/help/help.test.ts[19-22]
src/routes/(app)/channels/[id]/log/actions.test.ts[20-22]

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

## Issue description
Newly-added test modules that depend on server-only APIs/dependencies are currently located under `src/routes/` instead of `src/lib/server/`, which violates the compliance requirement for server-only module placement.

## Issue Context
PR Compliance ID 2401095 requires any server-only module (including files importing Node.js server APIs like `node:fs`/`node:path`/`node:url` and server helpers such as `$lib/server/*`) to be located under `src/lib/server/`. The following new route-area test files import server-only dependencies while living under `src/routes/(app)/...`, outside the required directory:
- `src/routes/(app)/help/help.test.ts` imports `node:fs`, `node:url`, and `node:path`.
- `src/routes/(app)/channels/[id]/log/actions.test.ts` imports `$lib/server/testdb` and `$lib/server/db/schema`.

## Fix Focus Areas
- src/routes/(app)/help/help.test.ts[19-27]
- src/routes/(app)/channels/[id]/log/actions.test.ts[19-40]

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


3. Unstable undoable selection ✓ Resolved 🐞 Bug ≡ Correctness
Description
The audit log load path assumes “first row per comment is latest” but orders only by createdAt, so
tied timestamps can cause the wrong entry to be treated as latest and incorrectly show/hide the Undo
button. This is plausible because audit rows are inserted with millisecond timestamps and audit_log
has an auto-increment id that is not used as a tie-breaker.
Code

src/routes/(app)/channels/[id]/log/+page.server.ts[R39-42]

+	// Newest first: the first entry seen per comment is its latest action, and
+	// only that one can be undone. 'hold'/'reject' reverse fully via YouTube;
+	// 'ban' restores the comment but the author ban is permanent (no API);
+	// everything else ('delete', 'approve', 'queue', 'dry-run', 'restore') is
Relevance

●●● Strong

Deterministic tie-break for “latest per comment” is a low-risk correctness fix; likely welcomed.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
load() orders only by createdAt and then uses a Set to treat the first encountered row per
comment as “latest”. Since audit rows are timestamped via new Date().toISOString() (millisecond
precision) and audit_log has an auto-increment id, ties can occur and should be resolved
deterministically with id ordering.

src/routes/(app)/channels/[id]/log/+page.server.ts[29-55]
src/lib/server/pipeline.ts[199-212]
src/lib/server/db/schema.ts[102-110]

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 audit log `load()` relies on the first row encountered per `commentId` being the latest action, but the SQL ordering is only `ORDER BY createdAt DESC`. When multiple audit rows share the same `createdAt` (plausible with millisecond precision), the DB may return them in an unspecified order, causing incorrect `undoable` flags.

## Issue Context
- `audit_log` rows have an auto-incrementing `id` that can act as a deterministic tie-breaker.
- The code explicitly derives “latest” from ordering and then sets `undoable` based on that derived latest row.

## Fix Focus Areas
- src/routes/(app)/channels/[id]/log/+page.server.ts[32-55]

### Suggested change
- Change the audit log query ordering to `orderBy(desc(auditLog.createdAt), desc(auditLog.id))`.
- Add/extend a test to cover two rows for the same `commentId` with identical `createdAt` values, asserting the higher `id` is treated as latest (and thus the only one considered for undoability).

ⓘ 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 on lines +19 to +22
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { describe, expect, it } from 'vitest';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Server-only help.test.ts in routes 📘 Rule violation ⌂ Architecture

The new help.test.ts and actions.test.ts files import server-only dependencies (Node.js server
APIs and $lib/server/* modules) but are placed under src/routes/ instead of the required
src/lib/server/ directory. This violates the server-only module placement rule and increases the
risk of server/client boundary confusion or accidental client-side bundling/structure drift.
Agent Prompt
## Issue description
Newly-added test modules that depend on server-only APIs/dependencies are currently located under `src/routes/` instead of `src/lib/server/`, which violates the compliance requirement for server-only module placement.

## Issue Context
PR Compliance ID 2401095 requires any server-only module (including files importing Node.js server APIs like `node:fs`/`node:path`/`node:url` and server helpers such as `$lib/server/*`) to be located under `src/lib/server/`. The following new route-area test files import server-only dependencies while living under `src/routes/(app)/...`, outside the required directory:
- `src/routes/(app)/help/help.test.ts` imports `node:fs`, `node:url`, and `node:path`.
- `src/routes/(app)/channels/[id]/log/actions.test.ts` imports `$lib/server/testdb` and `$lib/server/db/schema`.

## Fix Focus Areas
- src/routes/(app)/help/help.test.ts[19-27]
- src/routes/(app)/channels/[id]/log/actions.test.ts[19-40]

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

Comment thread src/routes/(app)/channels/[id]/log/+page.server.ts Outdated
Comment thread src/routes/(app)/channels/[id]/log/+page.server.ts Outdated

@coderabbitai coderabbitai 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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/landing/legal/Terms.svelte`:
- Around line 110-111: Update the “Moderation Action” definition in Terms.svelte
section 2 to explicitly include both automatic and manual actions, or introduce
a shared term covering both and use it consistently in section 9.4. Preserve the
existing audit-log statement while removing the ambiguity between the definition
and manual-action logging.

In `@src/lib/landing/legal.test.ts`:
- Around line 291-296: Update the test around the Terms §9 assertion to extract
only section §9.4 before matching. Within that scoped content, assert the
reversible hold/reject statement and retain assertions for the irreversible
deleted-comments and author-bans clauses, ensuring the complete disclosure is
covered.

In `@src/routes/`(app)/channels/[id]/log/+page.server.ts:
- Around line 84-103: Change the restore flow around the comments claim and
YouTube call to record an intermediate pending state or pendingAction before the
external request, rather than setting terminal approved/human immediately.
Promote the comment to approved/human only after setModerationStatus succeeds,
or immediately in dry-run mode; preserve retryability and add the existing
reconciliation mechanism for pending records left by process interruption.
- Around line 48-53: Replace the nested ternary assigned to undoable with a
small named helper or clear if/else chain that preserves the existing latest and
action conditions: return 'full' for latest hold/reject entries, 'comment-only'
for latest ban entries, and null otherwise.
- Around line 32-55: Update the loader query ordering around the rows retrieval
to add auditLog.id as a descending secondary sort key after auditLog.createdAt,
matching the ordering used by the undo action’s prior lookup. Preserve the
existing newest-first ordering and seen-based latest derivation.

In `@src/routes/`(app)/channels/[id]/log/actions.test.ts:
- Around line 24-35: Update the hoisted token mocks and their test assertions so
the channel action verifies decrypt receives the owned channel’s refreshTokenEnc
value ('enc-1') and refreshAccessToken receives the decrypted token
('decrypted-refresh-token'); ensure incorrect or undefined token arguments fail
the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 48da94e9-706f-44fa-8da9-3c4475e0151b

📥 Commits

Reviewing files that changed from the base of the PR and between 5d53113 and 14cac31.

📒 Files selected for processing (13)
  • src/lib/components/landing/legal/Terms.svelte
  • src/lib/landing/legal.test.ts
  • src/lib/landing/legal.ts
  • src/lib/server/db/schema.ts
  • src/lib/server/youtube.test.ts
  • src/lib/server/youtube.ts
  • src/routes/(app)/+layout.svelte
  • src/routes/(app)/channels/[id]/log/+page.server.ts
  • src/routes/(app)/channels/[id]/log/+page.svelte
  • src/routes/(app)/channels/[id]/log/actions.test.ts
  • src/routes/(app)/channels/[id]/log/load.test.ts
  • src/routes/(app)/help/+page.svelte
  • src/routes/(app)/help/help.test.ts

Comment on lines +110 to +111
<p><strong>9.4</strong> Every Moderation Action, automatic or manual, is recorded in the Service's audit log. Hold and reject actions can be reversed from the audit log, restoring the comment on YouTube. Deleted comments cannot be restored or reversed, and author bans cannot be lifted or reversed — YouTube provides no mechanism to undo either — so both are permanent once executed.</p>

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 & Privacy | 🟠 Major | ⚡ Quick win

Align the Moderation Action definition with manual-action logging.

Section 2 defines Moderation Action as an automated action. This section states that automatic and manual actions are recorded. Update the definition to include manual actions, or define a separate term that covers both. Otherwise, the audit-log disclosure has an ambiguous scope.

Proposed fix
-<p><strong>Moderation Action:</strong> an automated action configured by you ...
+<p><strong>Moderation Action:</strong> an automated or manual action configured or performed by you ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/landing/legal/Terms.svelte` around lines 110 - 111, Update
the “Moderation Action” definition in Terms.svelte section 2 to explicitly
include both automatic and manual actions, or introduce a shared term covering
both and use it consistently in section 9.4. Preserve the existing audit-log
statement while removing the ambiguity between the definition and manual-action
logging.

Comment thread src/lib/landing/legal.test.ts Outdated
Comment thread src/routes/(app)/channels/[id]/log/+page.server.ts
Comment thread src/routes/(app)/channels/[id]/log/+page.server.ts Outdated
Comment thread src/routes/(app)/channels/[id]/log/+page.server.ts Outdated
Comment thread src/routes/(app)/channels/[id]/log/actions.test.ts
…o, safe restore params

- Audit log load breaks createdAt ties with the auto-increment id, so the
  wrong entry can never be picked as a comment's latest action (Qodo
  #3699381183)
- Undo now claims into a 'restoring' state and commits the audit row and
  the approved status in ONE transaction after the remote call: a crash
  mid-undo parks the comment retryably instead of losing the audit record
  (Qodo #3699381186). The YouTube restore is idempotent, so resume is safe
- setModerationStatus omits banAuthor unless banning — the param is only
  valid alongside 'rejected' and defaults to false (CodeAnt suggestion)
@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Aug 2, 2026
… helper, credential-path assertions

- Terms §9.4 drops the defined-term 'Moderation Action' (§2 scopes it to
  automated actions) for the plain 'moderation action, automatic or manual'
- The §9.4 guard test now extracts the section itself and also asserts the
  reversible hold/reject statement
- undoable computation is a named helper instead of a nested ternary
  (CodeRabbit + SonarQube S3358)
- The undo test asserts the full credential path: decrypt gets the owned
  channel's refreshTokenEnc, refreshAccessToken gets the decrypted token
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/routes/(app)/channels/[id]/log/actions.test.ts (1)

78-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add full-flow restore coverage for 'held' comments.

Existing tests cover restoring 'rejected' comments (prior actions reject and ban) through the real (non-dry-run) path, but not 'held' comments. hold is documented as a primary fully-reversible action alongside reject. Add an analogous test that seeds a 'held' comment and asserts the same restore/audit behavior.

🧪 Proposed addition
test('undo restores a held comment at YouTube and records the restore', async () => {
	await seedComment('c1', 'held', 'hold');

	const res = await undo('c1');

	expect(res).toMatchObject({ success: expect.stringContaining('estored') });
	expect(mocks.setModerationStatus).toHaveBeenCalledWith(['c1'], 'published', false, 'access-token');
	expect(await commentRow('c1')).toMatchObject({ status: 'approved', decidedBy: 'human' });
	expect(await testDb().db.select().from(auditLog).all()).toContainEqual(
		expect.objectContaining({ commentId: 'c1', action: 'restore', reason: 'undo of hold', actor: 'user' })
	);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(app)/channels/[id]/log/actions.test.ts around lines 78 - 101,
Add a full-flow test alongside the existing undo tests that seeds comment c1
with status held and prior action hold, invokes undo('c1'), and verifies the
restore success response, published YouTube moderation call, approved
human-decided comment row, and restore audit entry with reason undo of hold and
actor user.
src/routes/(app)/channels/[id]/log/+page.server.ts (1)

84-136: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Concurrent resumers of a 'restoring' comment can each write a duplicate restore audit row.

The atomic claim at Line 91-95 only protects the fresh-claim path (if (!resuming)). Once a comment's status is 'restoring' — including while a first request is still mid-flight, not just after a crash — any concurrent duplicate undo submission also takes the resuming branch, skips the claim, and reaches the final transaction at Line 126-136 unguarded. Each concurrent resumer independently inserts a restore audit row and sets the comment to approved/human. This contradicts the design intent of atomic claiming preventing "duplicate concurrent undo operations," and pollutes the audit log with duplicate entries for a single logical undo.

Guard the final transaction with the same conditional-update pattern already used for the claim: update only WHERE status = 'restoring', and only insert the audit row if the update actually affected a row.

🐛 Proposed fix
 		await db.transaction(async (tx) => {
-			await tx.insert(auditLog).values({
-				channelId: params.id,
-				commentId,
-				action: dryRun ? 'dry-run' : 'restore',
-				reason: `undo of ${prior?.action ?? 'moderation action'}`,
-				actor: 'user',
-				createdAt: new Date().toISOString()
-			});
-			await tx.update(comments).set({ status: 'approved', decidedBy: 'human' }).where(eq(comments.id, commentId));
+			// Single-winner guard: only the request that actually flips
+			// 'restoring' -> 'approved' writes the audit row. A concurrent
+			// resumer of the same comment (double-click, retry) finds 0
+			// rows here and no-ops instead of duplicating the audit entry.
+			const updated = await tx
+				.update(comments)
+				.set({ status: 'approved', decidedBy: 'human' })
+				.where(and(eq(comments.id, commentId), eq(comments.status, 'restoring')))
+				.returning({ id: comments.id });
+			if (updated.length === 0) return;
+			await tx.insert(auditLog).values({
+				channelId: params.id,
+				commentId,
+				action: dryRun ? 'dry-run' : 'restore',
+				reason: `undo of ${prior?.action ?? 'moderation action'}`,
+				actor: 'user',
+				createdAt: new Date().toISOString()
+			});
 		});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(app)/channels/[id]/log/+page.server.ts around lines 84 - 136,
Guard the final transaction in the undo flow around the existing comments update
and auditLog insert so only one restoring request can finalize. Conditionally
update the comment from status 'restoring' to 'approved' and set decidedBy to
'human', inspect the affected-row result, and insert the restore audit row only
when that update succeeds; treat zero affected rows as an already-finalized
concurrent request without writing another audit entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/routes/`(app)/channels/[id]/log/+page.server.ts:
- Around line 84-136: Guard the final transaction in the undo flow around the
existing comments update and auditLog insert so only one restoring request can
finalize. Conditionally update the comment from status 'restoring' to 'approved'
and set decidedBy to 'human', inspect the affected-row result, and insert the
restore audit row only when that update succeeds; treat zero affected rows as an
already-finalized concurrent request without writing another audit entry.

In `@src/routes/`(app)/channels/[id]/log/actions.test.ts:
- Around line 78-101: Add a full-flow test alongside the existing undo tests
that seeds comment c1 with status held and prior action hold, invokes
undo('c1'), and verifies the restore success response, published YouTube
moderation call, approved human-decided comment row, and restore audit entry
with reason undo of hold and actor user.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 685413da-e3d3-42c9-ac52-fc47f8c9bd39

📥 Commits

Reviewing files that changed from the base of the PR and between 14cac31 and fa01102.

📒 Files selected for processing (6)
  • src/lib/server/db/schema.ts
  • src/lib/server/youtube.test.ts
  • src/lib/server/youtube.ts
  • src/routes/(app)/channels/[id]/log/+page.server.ts
  • src/routes/(app)/channels/[id]/log/actions.test.ts
  • src/routes/(app)/channels/[id]/log/load.test.ts

@Bonobo791
Bonobo791 merged commit fd285c4 into main Aug 2, 2026
14 of 19 checks passed
@Bonobo791
Bonobo791 deleted the feat-reversible-actions branch August 2, 2026 15:01

@coderabbitai coderabbitai 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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/routes/(app)/channels/[id]/log/actions.test.ts (2)

129-140: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that dry-run skips credential calls.

The test checks only setModerationStatus. It would pass if the handler still called decrypt or refreshAccessToken. Add not.toHaveBeenCalled() assertions for both mocks.

As per coding guidelines, tests must fail when the real logic is wrong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(app)/channels/[id]/log/actions.test.ts around lines 129 - 140,
Extend the dry-run test around undo('c1') to assert that both credential-related
mocks, decrypt and refreshAccessToken, are not called, alongside the existing
setModerationStatus assertion. Ensure the test verifies that dry-run execution
skips all YouTube and credential calls.

Source: Coding guidelines


175-180: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Seed an existing foreign-owned channel.

UC2 is not inserted before the request, so the test proves only that a missing channel returns 404. An authorization regression that permits access to an existing channel owned by another user could still pass.

Insert UC2 with a different userId and a rejected comment, then submit the undo request for that channel and assert 404, no YouTube call, and no state change.

As per coding guidelines, another user's channel must return 404 without leaking existence, and tests must fail when the real logic is wrong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(app)/channels/[id]/log/actions.test.ts around lines 175 - 180,
Update the test “undo on another user’s channel 404s without leaking existence”
to seed an existing UC2 channel owned by a different user, including its
rejected comment, before calling undo. Assert the request returns 404, the
YouTube call is not made, and the comment remains rejected, ensuring the test
detects authorization regressions rather than only missing-channel behavior.

Source: Coding guidelines

src/routes/(app)/channels/[id]/log/+page.server.ts (2)

89-98: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep resumed restore claims exclusive.

When a second request reads status === 'restoring', it skips the conditional claim and can run concurrently with the first request. Both requests can call YouTube and insert restore audit rows. If the first request then fails, Lines 108-112 can restore the old status after the second request has committed approved.

Use a durable claim owner or lease for resumed attempts. Make both the release and final commit conditional on that claim. Add a concurrent-request test.

Also applies to: 108-112, 127-137

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(app)/channels/[id]/log/+page.server.ts around lines 89 - 98, The
restore flow around the conditional update and the error-release/final-commit
paths must keep resumed attempts exclusive. Add a durable claim owner or lease
that is acquired for both new and resumed restores, and make the release and
final status/audit commit conditional on that same claim so an earlier failed
request cannot overwrite a later successful one. Add a concurrent-request test
covering two restores when the comment is already in restoring status.

82-84: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Validate the latest audit action before claiming the comment.

The loader derives undoability from the latest audit row, but the action validates only comments.status. The prior query then filters to hold, reject, and ban, so it can select an older reversible row when a newer delete, approve, queue, dry-run, or restore row exists.

Select the latest audit row before the YouTube call. Require its action to be hold, reject, or ban, and use that exact row for the restore reason.

Also applies to: 116-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/`(app)/channels/[id]/log/+page.server.ts around lines 82 - 84,
Update the claim action around the comment status validation to fetch and
validate the latest audit row before making the YouTube call. Require that row’s
action to be hold, reject, or ban, reject comments whose latest action is
delete, approve, queue, dry-run, or restore, and reuse the validated row for the
restore reason instead of selecting an older reversible audit entry via prior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/landing/legal.test.ts`:
- Around line 289-297: Add an assertion in the §9.4 test around the existing s94
checks to require disclosure of both automatic and manual moderation actions,
using the exact wording or established terminology from the terms content. Keep
the existing reversibility assertions unchanged and ensure the test fails when
either scope is omitted.

---

Outside diff comments:
In `@src/routes/`(app)/channels/[id]/log/+page.server.ts:
- Around line 89-98: The restore flow around the conditional update and the
error-release/final-commit paths must keep resumed attempts exclusive. Add a
durable claim owner or lease that is acquired for both new and resumed restores,
and make the release and final status/audit commit conditional on that same
claim so an earlier failed request cannot overwrite a later successful one. Add
a concurrent-request test covering two restores when the comment is already in
restoring status.
- Around line 82-84: Update the claim action around the comment status
validation to fetch and validate the latest audit row before making the YouTube
call. Require that row’s action to be hold, reject, or ban, reject comments
whose latest action is delete, approve, queue, dry-run, or restore, and reuse
the validated row for the restore reason instead of selecting an older
reversible audit entry via prior.

In `@src/routes/`(app)/channels/[id]/log/actions.test.ts:
- Around line 129-140: Extend the dry-run test around undo('c1') to assert that
both credential-related mocks, decrypt and refreshAccessToken, are not called,
alongside the existing setModerationStatus assertion. Ensure the test verifies
that dry-run execution skips all YouTube and credential calls.
- Around line 175-180: Update the test “undo on another user’s channel 404s
without leaking existence” to seed an existing UC2 channel owned by a different
user, including its rejected comment, before calling undo. Assert the request
returns 404, the YouTube call is not made, and the comment remains rejected,
ensuring the test detects authorization regressions rather than only
missing-channel behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 283fcaee-7f1a-48a7-974c-3aabc20f21a3

📥 Commits

Reviewing files that changed from the base of the PR and between fa01102 and a621fcf.

📒 Files selected for processing (4)
  • src/lib/components/landing/legal/Terms.svelte
  • src/lib/landing/legal.test.ts
  • src/routes/(app)/channels/[id]/log/+page.server.ts
  • src/routes/(app)/channels/[id]/log/actions.test.ts

Comment on lines +289 to +297
// The undo feature's honesty guard: §9.4 must disclose exactly which
// moderation actions cannot be reversed (YouTube offers no API for them).
it('Terms §9.4 discloses which moderation actions are reversible and which are not', () => {
const terms = readComponent('terms');
const s94 = terms.slice(terms.indexOf('<strong>9.4</strong>'), terms.indexOf('id="s10"'));
expect(s94).toMatch(/audit log/i);
expect(s94).toMatch(/hold and reject actions can be reversed/i);
expect(s94).toMatch(/deleted comments? cannot be (?:restored|reversed|undone)/i);
expect(s94).toMatch(/author bans? cannot be (?:lifted|reversed|undone)/i);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the automatic/manual scope in §9.4.

The test checks only audit log, so it can pass if the disclosure omits manual actions. Add an assertion for the complete scope, for example:

Proposed assertion
 		expect(s94).toMatch(/audit log/i);
+		expect(s94).toMatch(/every moderation action, automatic or manual, is recorded/i);

As per coding guidelines, tests must fail when the real logic is wrong.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The undo feature's honesty guard: §9.4 must disclose exactly which
// moderation actions cannot be reversed (YouTube offers no API for them).
it('Terms §9.4 discloses which moderation actions are reversible and which are not', () => {
const terms = readComponent('terms');
const s94 = terms.slice(terms.indexOf('<strong>9.4</strong>'), terms.indexOf('id="s10"'));
expect(s94).toMatch(/audit log/i);
expect(s94).toMatch(/hold and reject actions can be reversed/i);
expect(s94).toMatch(/deleted comments? cannot be (?:restored|reversed|undone)/i);
expect(s94).toMatch(/author bans? cannot be (?:lifted|reversed|undone)/i);
// The undo feature's honesty guard: §9.4 must disclose exactly which
// moderation actions cannot be reversed (YouTube offers no API for them).
it('Terms §9.4 discloses which moderation actions are reversible and which are not', () => {
const terms = readComponent('terms');
const s94 = terms.slice(terms.indexOf('<strong>9.4</strong>'), terms.indexOf('id="s10"'));
expect(s94).toMatch(/audit log/i);
expect(s94).toMatch(/every moderation action, automatic or manual, is recorded/i);
expect(s94).toMatch(/hold and reject actions can be reversed/i);
expect(s94).toMatch(/deleted comments? cannot be (?:restored|reversed|undone)/i);
expect(s94).toMatch(/author bans? cannot be (?:lifted|reversed|undone)/i);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/landing/legal.test.ts` around lines 289 - 297, Add an assertion in
the §9.4 test around the existing s94 checks to require disclosure of both
automatic and manual moderation actions, using the exact wording or established
terminology from the terms content. Keep the existing reversibility assertions
unchanged and ensure the test fails when either scope is omitted.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant