feat: every moderation action is logged and reversible (undo from the audit log) - #46
Conversation
🤖 CodeAnt AI — Review Status
|
✅ Deploy Preview for moderaty ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe 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. ChangesAudit Log Undo Flow
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
Sequence DiagramThis 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
Generated by CodeAnt AI |
🏁 CodeAnt Quality Gate ResultsCommit: ✅ Overall Status: PASSEDQuality Gate Details
|
Not up to standards ⛔🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | ✅ 32 (≤ 100 complexity) |
| Duplication |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
PR Summary by QodoAdd undo support for moderation actions via the audit log
AI Description
Diagram
High-Level Assessment
Files changed (13)
|
There was a problem hiding this comment.
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.
| if (!comment || (comment.status !== 'held' && comment.status !== 'rejected')) { | ||
| throw error(404, 'reversible comment not found in this channel'); | ||
| } |
There was a problem hiding this comment.
🛑 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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
🟡 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' |
There was a problem hiding this comment.
⚪ 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() |
There was a problem hiding this comment.
⚪ 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.
PR Code Suggestions ✨Previous suggestions up to commit
|
| Category | Suggestion | Severity | Generated at (UTC) |
| Api mismatch |
Restoration sends an unsupported ban parameter with the published moderation statusThe YouTube API only supports src/routes/(app)/channels/[id]/log/+page.server.ts [94] Why it matters? 🤔
(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 | Major | 2026-08-02 14:38
|
Latest suggestions up to commit fa01102
| Category | Suggestion | Severity | Generated at (UTC) |
| Api mismatch |
Undo accepts comments based on status without checking that their latest audit action is reversibleThe server action authorizes undo solely from the current comment status and never src/routes/(app)/channels/[id]/log/+page.server.ts [76-83] Why it matters? 🤔
(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 | Major | 2026-08-02 14:53
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
73 rules 1.
|
| import { readFileSync } from 'node:fs'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { dirname, join } from 'node:path'; | ||
| import { describe, expect, it } from 'vitest'; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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
📒 Files selected for processing (13)
src/lib/components/landing/legal/Terms.sveltesrc/lib/landing/legal.test.tssrc/lib/landing/legal.tssrc/lib/server/db/schema.tssrc/lib/server/youtube.test.tssrc/lib/server/youtube.tssrc/routes/(app)/+layout.sveltesrc/routes/(app)/channels/[id]/log/+page.server.tssrc/routes/(app)/channels/[id]/log/+page.sveltesrc/routes/(app)/channels/[id]/log/actions.test.tssrc/routes/(app)/channels/[id]/log/load.test.tssrc/routes/(app)/help/+page.sveltesrc/routes/(app)/help/help.test.ts
| <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> | ||
|
|
There was a problem hiding this comment.
🔒 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.
…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)
… 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
|
There was a problem hiding this comment.
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 winAdd full-flow restore coverage for
'held'comments.Existing tests cover restoring
'rejected'comments (prior actionsrejectandban) through the real (non-dry-run) path, but not'held'comments.holdis documented as a primary fully-reversible action alongsidereject. 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 winConcurrent resumers of a
'restoring'comment can each write a duplicaterestoreaudit 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 duplicateundosubmission also takes theresumingbranch, skips the claim, and reaches the final transaction at Line 126-136 unguarded. Each concurrent resumer independently inserts arestoreaudit row and sets the comment toapproved/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
📒 Files selected for processing (6)
src/lib/server/db/schema.tssrc/lib/server/youtube.test.tssrc/lib/server/youtube.tssrc/routes/(app)/channels/[id]/log/+page.server.tssrc/routes/(app)/channels/[id]/log/actions.test.tssrc/routes/(app)/channels/[id]/log/load.test.ts
There was a problem hiding this comment.
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.
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 winAssert that dry-run skips credential calls.
The test checks only
setModerationStatus. It would pass if the handler still calleddecryptorrefreshAccessToken. Addnot.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 winSeed an existing foreign-owned channel.
UC2is 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
UC2with a differentuserIdand 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 liftKeep 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 committedapproved.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 liftValidate the latest audit action before claiming the comment.
The loader derives undoability from the latest audit row, but the action validates only
comments.status. Thepriorquery then filters tohold,reject, andban, so it can select an older reversible row when a newerdelete,approve,queue,dry-run, orrestorerow exists.Select the latest audit row before the YouTube call. Require its action to be
hold,reject, orban, 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
📒 Files selected for processing (4)
src/lib/components/landing/legal/Terms.sveltesrc/lib/landing/legal.test.tssrc/routes/(app)/channels/[id]/log/+page.server.tssrc/routes/(app)/channels/[id]/log/actions.test.ts
| // 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); |
There was a problem hiding this comment.
🎯 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.
| // 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
…ng change PR #46 already shipped 1.3 (Terms §9.4 reversible actions); the pricing overhaul is a separate material change, so it gets its own version (1.4, effective 2 August 2026) instead of sharing 1.3.




User description
Behavior
Logging was already complete (
audit_logcovers pipeline, queue, and dry-run actions); this PR adds reversibility:hold/rejectrestore the comment at YouTube viasetModerationStatus(..., 'published');banoffers "Undo comment" (the comment is restored — YouTube has no API to lift an author ban);deleteoffers nothing (permanent).DRY_RUN=truerecords adry-runaudit row and makes no YouTube call (I8). Every undo writes arestoreaudit row naming the original action (derived server-side).approved/human.Disclosure (per review feedback):
LEGAL_VERSIONbumped to1.3, so existing users re-accept via the established/consentflow on next login.Verification
svelte-checkclean, production build green.publishedAPI params, Terms §9.4 disclosure guard, Help tab presence + copy.Notes
CodeAnt-AI Description
Add reversible moderation actions through the audit log
What Changed
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:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
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:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
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.