Skip to content

Add additionalInfo to NCMEC reports; fix XML element ordering and Node 24 multipart submission#477

Merged
juanmrad merged 8 commits into
mainfrom
fix-ncmec-reporting-unordered-reports
May 19, 2026
Merged

Add additionalInfo to NCMEC reports; fix XML element ordering and Node 24 multipart submission#477
juanmrad merged 8 commits into
mainfrom
fix-ncmec-reporting-unordered-reports

Conversation

@juanmrad
Copy link
Copy Markdown
Member

@juanmrad juanmrad commented May 15, 2026

Fixes #470

Context & Requests for Reviewers

Adds the additionalInfo free-text field to NCMEC reports per the NCMEC report-details spec, and fixes a set of bugs that surfaced while end-to-end testing against exttest.cybertip.org.

Feature: additionalInfo on the report (closes #470)

  • New optional, 3000-char free-text field, wired UI → GraphQL (SubmitNcmecReportInput.additionalInfo) → service → XML.
  • Emitted as the last child of <report> per the XSD.
  • "Escalate as High Priority" is now a checkbox; the reason textarea is only shown (and required) when the checkbox is checked. Previously two free-text boxes side-by-side confused reviewers about which signal was for which thing.

Fix: NCMEC XSD element ordering (responseCode=4100)

NCMEC rejects submissions whose elements aren't in XSD sequence order. js2xml emits children in object-literal key insertion order, so the Report type and all builders now match the XSD sequence:

added a new Opt-in, dev-only (NODE_ENV !== 'production') structured logging plus a local XML dump under server/ncmec-reports/ to make future NCMEC validation debugging tractable. Documented in server/.env.example. No-ops in production.

Screenshot 2026-05-15 at 12 26 59 PM Screenshot 2026-05-15 at 12 26 44 PM Screenshot 2026-05-15 at 12 26 36 PM

Summary by CodeRabbit

  • New Features

    • Display failed NCMEC submissions in Reports with per-row "Retry" and "Last Error" tooltip; configurable table columns persisted
    • Optional "Additional details" on NCMEC report submissions; escalation requires explicit checkbox + reason
    • Keyboard shortcuts suppressed while typing to avoid interference
  • Bug Fixes / Validation

    • Contact email trimmed, required, validated, and length-limited
    • Web page URL sanitization omits invalid/blank values from reports
  • Other

    • Opt-in NCMEC debug logging and local dump support (non-production)

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional report additionalInfo, retry orchestration and GraphQL surface, debug logging/dumps and payload builder, client keyboard-safety and dashboard retry UI, creator-synthesis for enqueue flows, ClickHouse identity query, FormData fixes, input validation, and many tests.

Changes

NCMEC Additional Info, Escalation, and Debug Enhancements

Layer / File(s) Summary
Client keyboard safety & small UI tweaks
client/src/utils/misc.ts, client/src/components/common/CoopSelect.tsx, client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/*, client/src/components/ItemAction.tsx
Adds isTypingInEditableElement and uses it to suppress global keyboard shortcuts when typing; minor dropdown/className formatting, truncation behavior, and pluralized load-failure messages.
Settings UI and contactEmail validation
client/src/webpages/settings/NCMECSettings.tsx
Contact Email UI marked required; save handler trims, enforces max length (254), validates via regex, and submits trimmed value.
GraphQL client/server types & hooks
client/src/graphql/generated.ts, server/graphql/generated.ts, server/graphql/modules/manualReviewTool.ts, server/graphql/modules/ncmec.ts
Adds additionalInfo to submit input, failed-submission types/enum, retryNcmecSubmission mutation and generated Apollo hook, and server GraphQL types/resolver wiring including Org.failedNcmecSubmissions.
Dashboard UI: failed submissions + retry
client/src/webpages/dashboard/ncmec/NcmecReportsDashboard.tsx, client/src/webpages/dashboard/components/table/Table.tsx, client/src/webpages/dashboard/Dashboard.css, client/src/webpages/dashboard/Dashboard.tsx
Adds failed-submissions display, per-row Retry action with loading state, column-visibility persistence, BlobDownloadLink, alwaysShowScrollbar prop and .scrollbar-show styles, and layout min-w-0 tweaks.
Payload builder & report construction
server/services/ncmecService/buildSubmitReportParamsFromDecision.ts, server/services/ncmecService/index.ts
Introduces buildSubmitReportParamsFromDecision and LEGACY_FALLBACK_INCIDENT_TYPE to centralize payload assembly, trim/omit conditional fields like additionalInfo, and resolve media/item types.
Reporting, validation, debug, and persistence
server/services/ncmecService/ncmecReporting.ts, server/services/ncmecService/ncmecDebug.ts, server/.env.example
Adds additionalInfo validation (trim, non-blank, max length), clampIncidentDateTimeToPast, summarizeCyberTipFailure, ncmecDebug log/dump helpers, replaces dev XML writes with debug dumps, and records submission errors. Documents NCMEC_DEBUG.
Retry orchestration and service surface
server/services/ncmecService/retryNcmecSubmission.ts, server/services/ncmecService/ncmecService.ts, server/graphql/modules/ncmec.ts
New orchestration retryNcmecSubmission (with tests), NcmecService retrySubmission wrapper, GraphQL retryNcmecSubmission resolver, and server permission checks and result mapping.
Workers and manual-review wiring
server/workers_jobs/RetryFailedNcmecDecisionsJob.ts, server/iocContainer/index.ts, server/graphql/modules/manualReviewTool.ts
Workers and manual-review flow delegate payload building to the helper; submitManualReviewDecision includes additionalInfo in the decision payload.
ManualReviewTool storage/query helpers
server/services/manualReviewToolService/modules/JobDecisioning.ts, server/services/manualReviewToolService/manualReviewToolService.ts
Adds additionalInfo to decision component type and methods to fetch org-scoped NCMEC decisions.
Failed-submission filtering and Org resolver
server/services/ncmecService/ncmecSubmissionFilters.ts, server/graphql/modules/org.ts
Adds pure helper to filter decisions to failed submissions and Org.failedNcmecSubmissions resolver that composes decisions + persisted error rows into GraphQL objects.
NCMEC error persistence & concurrency fixes
server/services/ncmecService/ncmecService.ts
Adds getSuccessfulSubmissionKeysForOrg, avoids IN () queries on empty arrays, trims IDs before persisting, and atomically increments retry_count on conflict.
Creator synthesis & ActionPublisher enqueue flows
server/rule_engine/ActionPublisher.ts, server/services/itemInvestigationService/*, server/plugins/warehouse/queries/*
Adds ClickHouse content-creator identity lookup, synthesizeUserItemFromContentTarget, adapter/export wiring, ActionPublisher synthesis fallback to create minimal USER submissions for enqueue-to-MRT/NCMEC flows, DI updates, and tests.
HMA service and FormData fixes
server/services/hmaService/index.ts
Refactors formatting and requires file+contentType when URL absent; builds FormData and sends it without explicit multipart Content-Type header.
Tests and harness updates
server/services/ncmecService/*test.ts, server/plugins/warehouse/queries/ClickhouseActionExecutionsAdapter.test.ts, server/rule_engine/ActionPublisher.test.ts
Adds/updates Jest suites covering buildInternetDetails, clamp/summarize behavior, submission filters, retry orchestration, ClickHouse identity, and ActionPublisher enqueue scenarios; test harness refactored for injectable mocks.
Minor client/tweaks
client/src/components/ItemAction.tsx, client/src/components/common/CoopSelect.tsx, client/src/webpages/dashboard/mrt/manual_review_job/v2/ManualReviewJobMagnifyImageComponent.tsx
Bulk-action failure modal text clarified; CoopSelect keyboard-safety import/use; magnify component truncation/layout adjustments.
Small server cleanup
server/services/ncmecService/ncmecEnqueueToMrt.ts
Removes a console.log success message from enqueue path.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • roostorg/coop#474: Related to NCMEC_ENV test-vs-prod submission behavior used by retry paths.
  • roostorg/coop#492: Overlaps with generated GraphQL types and retry/dashboard work.

Suggested labels

developer experience, adoption

Suggested reviewers

  • vinaysrao1
  • cassidyjames
  • dom-notion
  • julietshen

Poem

🐰 I hop through logs and trim the lines,
I hush shortcuts while you write your signs,
Extra notes tucked with gentle care,
Retries queued up — the debug light’s there.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ncmec-reporting-unordered-reports

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx (1)

720-730: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Render messages preview as JSX, not a template string.

This block uses a template literal containing JSX interpolation, which will render the text [object Object] instead of the actual elements. When React elements are interpolated into template strings, they're coerced to their string representation rather than rendered as components.

Suggested diff
-        {selectedThreadsWithMessages.length > 0
-          ? `
-        <div className="!my-4 divider" />
-        <div className="text-base font-bold">Messages</div>
-        ${selectedThreadsWithMessages.map((thread) => (
-          <div key={thread.threadId}>
-            {thread.threadId}: {thread.reportedContent.length} reported
-          </div>
-        ))}
-        `
-          : undefined}
+        {selectedThreadsWithMessages.length > 0 ? (
+          <>
+            <div className="!my-4 divider" />
+            <div className="text-base font-bold">Messages</div>
+            {selectedThreadsWithMessages.map((thread) => (
+              <div key={thread.threadId}>
+                {thread.threadId}: {thread.reportedContent.length} reported
+              </div>
+            ))}
+          </>
+        ) : undefined}
🤖 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
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`
around lines 720 - 730, The current conditional uses a template string which
coerces React elements to text; change it to render JSX directly by removing the
backticks and string interpolation and returning a React fragment or element
when selectedThreadsWithMessages.length > 0; e.g., replace the entire ternary
expression with: selectedThreadsWithMessages.length > 0 ? (<><div
className="!my-4 divider" /><div className="text-base
font-bold">Messages</div>{selectedThreadsWithMessages.map(thread => (<div
key={thread.threadId}>{thread.threadId}: {thread.reportedContent.length}
reported</div>))}</>) : undefined, ensuring you keep the key (thread.threadId)
and use thread.reportedContent.length.
🧹 Nitpick comments (3)
client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx (1)

24-24: ⚡ Quick win

Use @/utils/misc instead of deep relative import.

This import should use the project alias to match path conventions.

Suggested diff
-import { isTypingInEditableElement } from '../../../../../../utils/misc';
+import { isTypingInEditableElement } from '@/utils/misc';

As per coding guidelines, **/*.{ts,tsx}: Use absolute imports with @/ prefix over relative paths where configured in tsconfig.json.

🤖 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
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx`
at line 24, Replace the deep relative import of isTypingInEditableElement with
the project alias import to follow tsconfig absolute paths: update the import in
NCMECInspectedMedia.tsx so it imports isTypingInEditableElement from
'@/utils/misc' instead of '../../../../../../utils/misc' (keep the imported
symbol name isTypingInEditableElement unchanged).
client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx (1)

33-33: ⚡ Quick win

Use alias import for shared utility.

Replace the new deep relative import with the configured @/ alias.

Suggested diff
-import { isTypingInEditableElement } from '../../../../../../utils/misc';
+import { isTypingInEditableElement } from '@/utils/misc';

As per coding guidelines, **/*.{ts,tsx}: Use absolute imports with @/ prefix over relative paths where configured in tsconfig.json.

🤖 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
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`
at line 33, The import in NCMECReviewUser.tsx uses a deep relative path for the
shared utility isTypingInEditableElement; replace that relative import with the
configured alias import (use '@/utils/misc') so the line importing
isTypingInEditableElement uses the `@/` prefix instead of
'../../../../../../utils/misc' to follow the project absolute-import convention.
client/src/components/common/CoopSelect.tsx (1)

5-5: ⚡ Quick win

Use alias import for misc utility.

The new import uses a relative path; switch it to the configured @/ alias.

Suggested diff
-import { isTypingInEditableElement } from '../../utils/misc';
+import { isTypingInEditableElement } from '@/utils/misc';

As per coding guidelines, **/*.{ts,tsx}: Use absolute imports with @/ prefix over relative paths where configured in tsconfig.json.

🤖 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 `@client/src/components/common/CoopSelect.tsx` at line 5, Replace the relative
import for the misc utility with the project alias: update the import of
isTypingInEditableElement to use the configured "@/utils/misc" alias instead of
"../../utils/misc" so the component CoopSelect.tsx imports
isTypingInEditableElement from "@/utils/misc".
🤖 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
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`:
- Around line 302-306: The current lookup in NCMECReviewUser.tsx uses
ncmecDecisions.find to match only media.contentItem.id === decision.id and
media.contentItem.type.id === decision.typeId, which can misapply labels for
items with multiple media URLs; update the predicate in the ncmecDecisions.find
call (the decision lookup) to also compare the specific media URL(s) — e.g.,
match media.url (or the specific entry in media.contentItem.urls) against
decision.url (or decision.mediaUrl) — so decisions are only applied when id,
typeId and the exact media URL match.

In `@client/src/webpages/settings/NCMECSettings.tsx`:
- Around line 161-168: The client currently trims and format-validates the NCMEC
contact email (settings.contactEmail -> trimmedContactEmail) but lacks a
max-length check, so overly long emails can be sent to the server; add a
254-character limit check before the regex test (use trimmedContactEmail.length
<= 254), and if it exceeds 254 call toast.error with a clear message and return
from the submit/validation flow (update the validation block that contains
trimmedContactEmail and the regex test inside NCMECSettings.tsx).

In `@server/services/ncmecService/ncmecReporting.ts`:
- Around line 1228-1231: The current lexicographic comparison between
nowMinusOneSec and maxCreatedAt can be wrong for non-canonical ISO strings;
instead parse timestamps to numeric epoch ms and clamp using numeric comparison.
Convert maxCreatedAt to a Date/epoch (handle invalid dates), compute nowMs =
Date.now(), compute clampedMs = Math.min(maxCreatedAtMs, nowMs - 1000) and then
set clampedIncidentDateTime = new Date(clampedMs).toISOString(); update the
logic around nowMinusOneSec, maxCreatedAt, and clampedIncidentDateTime (and any
code that sets incidentDateTime) to use this numeric/date comparison and fall
back safely if maxCreatedAt is invalid.

---

Outside diff comments:
In
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`:
- Around line 720-730: The current conditional uses a template string which
coerces React elements to text; change it to render JSX directly by removing the
backticks and string interpolation and returning a React fragment or element
when selectedThreadsWithMessages.length > 0; e.g., replace the entire ternary
expression with: selectedThreadsWithMessages.length > 0 ? (<><div
className="!my-4 divider" /><div className="text-base
font-bold">Messages</div>{selectedThreadsWithMessages.map(thread => (<div
key={thread.threadId}>{thread.threadId}: {thread.reportedContent.length}
reported</div>))}</>) : undefined, ensuring you keep the key (thread.threadId)
and use thread.reportedContent.length.

---

Nitpick comments:
In `@client/src/components/common/CoopSelect.tsx`:
- Line 5: Replace the relative import for the misc utility with the project
alias: update the import of isTypingInEditableElement to use the configured
"@/utils/misc" alias instead of "../../utils/misc" so the component
CoopSelect.tsx imports isTypingInEditableElement from "@/utils/misc".

In
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx`:
- Line 24: Replace the deep relative import of isTypingInEditableElement with
the project alias import to follow tsconfig absolute paths: update the import in
NCMECInspectedMedia.tsx so it imports isTypingInEditableElement from
'@/utils/misc' instead of '../../../../../../utils/misc' (keep the imported
symbol name isTypingInEditableElement unchanged).

In
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`:
- Line 33: The import in NCMECReviewUser.tsx uses a deep relative path for the
shared utility isTypingInEditableElement; replace that relative import with the
configured alias import (use '@/utils/misc') so the line importing
isTypingInEditableElement uses the `@/` prefix instead of
'../../../../../../utils/misc' to follow the project absolute-import convention.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 864bb958-2bdf-4295-ac48-452398721ca6

📥 Commits

Reviewing files that changed from the base of the PR and between 61985f9 and 6de3d00.

📒 Files selected for processing (17)
  • client/src/components/common/CoopSelect.tsx
  • client/src/graphql/generated.ts
  • client/src/utils/misc.ts
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx
  • client/src/webpages/settings/NCMECSettings.tsx
  • server/.env.example
  • server/graphql/generated.ts
  • server/graphql/modules/manualReviewTool.ts
  • server/graphql/modules/ncmec.ts
  • server/iocContainer/index.ts
  • server/services/hmaService/index.ts
  • server/services/manualReviewToolService/modules/JobDecisioning.ts
  • server/services/ncmecService/ncmecDebug.ts
  • server/services/ncmecService/ncmecEnqueueToMrt.ts
  • server/services/ncmecService/ncmecReporting.test.ts
  • server/services/ncmecService/ncmecReporting.ts
💤 Files with no reviewable changes (1)
  • server/services/ncmecService/ncmecEnqueueToMrt.ts

Comment thread client/src/webpages/settings/NCMECSettings.tsx
Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 updates the NCMEC reporting flow end-to-end (UI → GraphQL → service → XML submission) to support the new additionalInfo field, fix NCMEC XML/XSD ordering and validation issues found in E2E testing, and adjust multipart submissions for Node 24/undici.

Changes:

  • Add optional additionalInfo to NCMEC reports and update MRT UI to separate “Escalate as High Priority” (checkbox + required reason) from additional notes.
  • Fix NCMEC submission correctness issues (XSD element ordering, optional/clean URL handling, incidentDateTime clamping, required reporter contact email) and improve error/debug observability.
  • Update multipart upload handling to use undici FormData without manually setting multipart Content-Type.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
server/services/ncmecService/ncmecReporting.ts Adds additionalInfo, enforces XSD ordering/validation constraints, improves submission logging/debug dumps, and switches to undici FormData types.
server/services/ncmecService/ncmecReporting.test.ts Updates/extends unit tests for buildInternetDetailsFromOrgSetting to reflect URL omission behavior.
server/services/ncmecService/ncmecEnqueueToMrt.ts Removes console logging on MRT enqueue success.
server/services/ncmecService/ncmecDebug.ts Adds opt-in, non-production NCMEC debug logging + local payload dumps.
server/services/manualReviewToolService/modules/JobDecisioning.ts Extends decision payload typing to include additionalInfo.
server/services/hmaService/index.ts Uses undici FormData and avoids manually setting multipart Content-Type for uploads; general formatting cleanup.
server/iocContainer/index.ts Plumbs additionalInfo from MRT decisions into NCMEC submission params.
server/graphql/modules/ncmec.ts Adds server-side validation for required reporter contact email and trims credentials.
server/graphql/modules/manualReviewTool.ts Extends MRT GraphQL input plumbing to include additionalInfo.
server/graphql/generated.ts Regenerates types to include SubmitNcmecReportInput.additionalInfo.
server/.env.example Documents NCMEC_DEBUG=1 opt-in debugging flag and its implications.
client/src/webpages/settings/NCMECSettings.tsx Requires/validates contact email on save and marks the field required in the UI.
client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx Adds additionalInfo textbox, changes escalation UX to checkbox + required reason, and prevents keyboard shortcuts while typing.
client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx Prevents keyboard shortcuts while typing in editable elements.
client/src/utils/misc.ts Adds isTypingInEditableElement helper for keyboard shortcut guards.
client/src/graphql/generated.ts Regenerates client types to include SubmitNcmecReportInput.additionalInfo.
client/src/components/common/CoopSelect.tsx Prevents select keyboard shortcuts from triggering while typing in editable elements.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
Comment thread server/services/hmaService/index.ts Outdated
Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
@juanmrad juanmrad requested a review from Copilot May 15, 2026 17:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
server/services/hmaService/index.ts (1)

835-841: 💤 Low value

Redundant defensive check can be simplified.

The guard at lines 809-813 already ensures that when url is absent, both file and contentType are present. Since this else branch is only reached when url is falsy, the condition !file || !contentType at line 837 should never be true.

While defensive programming is reasonable, this check is unreachable. Consider either:

  1. Removing lines 837-841 entirely, or
  2. Keeping it but changing the error message to clarify it's an "impossible" state (e.g., "Internal error: ...")
♻️ Option 1: Remove the redundant check
     } else {
-      // Guarded by the precondition above: when `url` is absent, both `file`
-      // and `contentType` are guaranteed to be set.
-      if (!file || !contentType) {
-        throw new Error(
-          'addContentToBank reached file-upload branch without file/contentType',
-        );
-      }
+      // Precondition: when `url` is absent, both `file` and `contentType` are guaranteed to be set.
       const formData = new FormData();
       formData.append(contentType, file);
🤖 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 `@server/services/hmaService/index.ts` around lines 835 - 841, The check inside
addContentToBank that throws an error when `if (!file || !contentType) { throw
new Error('addContentToBank reached file-upload branch without
file/contentType') }` is redundant because earlier preconditions guarantee
`file` and `contentType` when `url` is falsy; remove this unreachable
conditional block entirely (or if you prefer to keep it as a sanity assertion,
replace the message with an explicit impossible-state message like "Internal
error: addContentToBank reached unreachable file-upload branch").
🤖 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.

Nitpick comments:
In `@server/services/hmaService/index.ts`:
- Around line 835-841: The check inside addContentToBank that throws an error
when `if (!file || !contentType) { throw new Error('addContentToBank reached
file-upload branch without file/contentType') }` is redundant because earlier
preconditions guarantee `file` and `contentType` when `url` is falsy; remove
this unreachable conditional block entirely (or if you prefer to keep it as a
sanity assertion, replace the message with an explicit impossible-state message
like "Internal error: addContentToBank reached unreachable file-upload branch").

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ba83718-cf2a-43c4-bfa1-a860b6d47d5c

📥 Commits

Reviewing files that changed from the base of the PR and between 6de3d00 and 6672b7a.

📒 Files selected for processing (7)
  • client/src/components/common/CoopSelect.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx
  • client/src/webpages/settings/NCMECSettings.tsx
  • server/services/hmaService/index.ts
  • server/services/ncmecService/ncmecReporting.test.ts
  • server/services/ncmecService/ncmecReporting.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • client/src/components/common/CoopSelect.tsx
  • client/src/webpages/settings/NCMECSettings.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx
  • server/services/ncmecService/ncmecReporting.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
Copy link
Copy Markdown
Member

@julietshen julietshen left a comment

Choose a reason for hiding this comment

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

Excellent, thank you! Approving solely on the business logic and intended functionality - relying on copilot/coderabbit for code quality checks

…orts dashboard (#492)

* [NCMEC] Persist failed submissions and surface retry in the NCMEC Reports dashboard

* address code review comments by copilot and coderabbit

* Make NCMEC and MRT enqueue work for users with no submission record (#494)

* Make NCMEC and MRT enqueue work for users with no submission record

* make it work with content type also

* code review changes

* code review feedback
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.

Comment thread client/src/webpages/dashboard/ncmec/NcmecReportsDashboard.tsx Outdated
Comment thread server/services/ncmecService/retryNcmecSubmission.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
server/services/ncmecService/ncmecReporting.ts (1)

1285-1302: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact free-text report fields before logging reportParams.

reportParams now carries reporter-entered additionalInfo and escalation text, but this method still serializes the whole object into span attributes and failure logs. That stores sensitive report content outside the debug-only path and creates an avoidable privacy/compliance risk.

Suggested redaction
+        const logSafeReportParams = {
+          orgId: reportParams.orgId,
+          reviewerId: reportParams.reviewerId,
+          reportedUser: reportParams.reportedUser,
+          mediaCount: reportParams.media.length,
+          threadsCount: reportParams.threads.length,
+          incidentType: reportParams.incidentType,
+          hasEscalation: Boolean(reportParams.escalateToHighPriority?.trim()),
+          hasAdditionalInfo: Boolean(reportParams.additionalInfo?.trim()),
+          jobId: reportParams.jobId,
+        };
-        span.setAttribute(`ncmecReportParams`, jsonStringify(reportParams));
+        span.setAttribute(
+          `ncmecReportParams`,
+          jsonStringify(logSafeReportParams),
+        );
...
           logErrorJson({
             error: e,
-            message: jsonStringify({ reportParams, isTest }),
+            message: jsonStringify({
+              reportParams: logSafeReportParams,
+              isTest,
+            }),
           });

Also applies to: 1645-1666

🤖 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 `@server/services/ncmecService/ncmecReporting.ts` around lines 1285 - 1302, The
span and debug logs in submitReport are serializing the entire reportParams (via
jsonStringify and ncmecDebugLog) which can include reporter-entered free-text
like additionalInfo and escalation text; redact or remove those sensitive fields
before logging by cloning reportParams, deleting or replacing additionalInfo and
any escalation/free-text fields with a constant like "[REDACTED]" and then use
that sanitized object in span.setAttribute and ncmecDebugLog; update the same
pattern wherever submitReport serializes/reportParams (including the later
occurrence around the other span/log block) to ensure no free-text is stored in
span attributes or non-debug logs.
♻️ Duplicate comments (2)
server/services/ncmecService/ncmecReporting.ts (1)

1323-1333: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pick the latest createdAt by parsed time, not by string order.

_.maxBy(reportParams.media, 'createdAt') still compares timestamp strings lexicographically. If any createdAt includes offsets or non-canonical ISO formatting, you can still choose the wrong media timestamp even though the later clamp is numeric.

Suggested fix
-          const maxCreatedAt = _.maxBy(
-            reportParams.media,
-            'createdAt',
-          )?.createdAt;
+          const maxCreatedAtMs = Math.max(
+            ...reportParams.media.map(({ createdAt }) => {
+              const ms = Date.parse(createdAt);
+              if (Number.isNaN(ms)) {
+                throw new Error(
+                  `Invalid media createdAt timestamp for incidentDateTime: ${createdAt}`,
+                );
+              }
+              return ms;
+            }),
+          );
+          const maxCreatedAt = new Date(maxCreatedAtMs).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 `@server/services/ncmecService/ncmecReporting.ts` around lines 1323 - 1333, The
current max selection uses string comparison on createdAt; change the selection
to pick the latest media by parsed time (numeric timestamp) instead: use
_.maxBy(reportParams.media, m => Date.parse(m.createdAt) or new
Date(m.createdAt).getTime()) and handle invalid/NaN parse results (throw same
'No media in report' or a clearer error). Replace the existing _.maxBy(...)
usage that produces maxCreatedAt with this parsed-time comparator and keep the
subsequent clampIncidentDateTimeToPast(maxCreatedAt) call using the original
createdAt string from the chosen media (or convert consistently) so
clampIncidentDateTimeToPast receives a valid datetime.
client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx (1)

300-328: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid replaying one saved decision onto every URL for the same item.

Matching prior decisions on (id, typeId) alone means a multi-URL content item will copy the first saved category/labels onto unrelated media. That makes the read-only NCMEC view show classifications that were never actually submitted. If URL matching is unstable, only prefill when the match is unambiguous; otherwise leave that media unselected until we have a stable per-media key.

Suggested safe fallback
-          const decision = ncmecDecisions.find(
-            (decision) =>
-              media.contentItem.id === decision.id &&
-              media.contentItem.type.id === decision.typeId,
-          );
+          const decisionsForItem = ncmecDecisions.filter(
+            (decision) =>
+              media.contentItem.id === decision.id &&
+              media.contentItem.type.id === decision.typeId,
+          );
+          const decision =
+            decisionsForItem.find(
+              (decision) => decision.url === media.urlInfo.url,
+            ) ??
+            (decisionsForItem.length === 1 &&
+            allMediaItemsWithUrls.filter(
+              (it) =>
+                it.contentItem.id === media.contentItem.id &&
+                it.contentItem.type.id === media.contentItem.type.id,
+            ).length === 1
+              ? decisionsForItem[0]
+              : undefined);
🤖 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
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`
around lines 300 - 328, The current mapping in NCMECReviewUser.tsx that builds
items from allMediaItemsWithUrls applies a decision found by matching
(decision.id, decision.typeId) which can incorrectly replay one saved decision
across multiple URLs for the same content item; change the logic so you only
prefill a decision when that decision is unambiguous: compute how many media
entries share the same key (id + type) and only apply decision for a media if
the matching decision exists AND the count for that (decision.id,
decision.typeId) key is exactly 1; otherwise treat the media as no prior
decision (category 'None', labels []). Update the mapping that references
allMediaItemsWithUrls, ncmecDecisions, media.contentItem.id,
media.contentItem.type.id, and decision.id/decision.typeId to use this
uniqueness check before returning a prefilled object.
🤖 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 `@client/src/webpages/dashboard/ncmec/NcmecReportsDashboard.tsx`:
- Around line 427-433: The tableData useMemo currently swaps to
ncmecReportData.ncmecReportById whenever ncmecReportData exists and searchId is
truthy, which can show a stale previously-fetched report for a new search;
change the condition in the useMemo so you only substitute the lazy-fetched
single report when it actually matches the current searchId (e.g. verify
ncmecReportData.ncmecReportById.id or reportId equals searchId) and otherwise
fall back to ncmecReports; keep the existing fallback (ncmecReports ?? []) and
ensure the check uses the same identifier field present on ncmecReportById.
- Around line 7-8: The file uses React types such as React.ReactNode and
React.MouseEvent but only imports hooks; add an import of the React namespace to
satisfy those type references (e.g., import React from 'react') at the top of
NcmecReportsDashboard.tsx so that uses of React.ReactNode, React.MouseEvent and
any other React.* types resolve; update the same import in other affected
sections (lines ~15-24 and ~550-551) where those types are referenced.

In `@server/services/ncmecService/buildSubmitReportParamsFromDecision.ts`:
- Around line 108-122: decisionComponent.reportedMedia is currently matched
against allMediaItems using only contentItem.itemId which can pick the wrong row
when different content types share an id; update the lookup for reportedItem to
match both id and type (e.g., compare it.id to m.contentItem.itemId AND it.type
to m.contentItem.itemTypeIdentifier or equivalent), then proceed to call
getItemTypeEventuallyConsistent with the matched
reportedItem.contentItem.itemTypeIdentifier (ensure you update any error
messages referencing the lookup to reflect the combined id+type match); locate
the code around the reportedItem lookup and getItemTypeEventuallyConsistent to
implement this dual-key match.

---

Outside diff comments:
In `@server/services/ncmecService/ncmecReporting.ts`:
- Around line 1285-1302: The span and debug logs in submitReport are serializing
the entire reportParams (via jsonStringify and ncmecDebugLog) which can include
reporter-entered free-text like additionalInfo and escalation text; redact or
remove those sensitive fields before logging by cloning reportParams, deleting
or replacing additionalInfo and any escalation/free-text fields with a constant
like "[REDACTED]" and then use that sanitized object in span.setAttribute and
ncmecDebugLog; update the same pattern wherever submitReport
serializes/reportParams (including the later occurrence around the other
span/log block) to ensure no free-text is stored in span attributes or non-debug
logs.

---

Duplicate comments:
In
`@client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx`:
- Around line 300-328: The current mapping in NCMECReviewUser.tsx that builds
items from allMediaItemsWithUrls applies a decision found by matching
(decision.id, decision.typeId) which can incorrectly replay one saved decision
across multiple URLs for the same content item; change the logic so you only
prefill a decision when that decision is unambiguous: compute how many media
entries share the same key (id + type) and only apply decision for a media if
the matching decision exists AND the count for that (decision.id,
decision.typeId) key is exactly 1; otherwise treat the media as no prior
decision (category 'None', labels []). Update the mapping that references
allMediaItemsWithUrls, ncmecDecisions, media.contentItem.id,
media.contentItem.type.id, and decision.id/decision.typeId to use this
uniqueness check before returning a prefilled object.

In `@server/services/ncmecService/ncmecReporting.ts`:
- Around line 1323-1333: The current max selection uses string comparison on
createdAt; change the selection to pick the latest media by parsed time (numeric
timestamp) instead: use _.maxBy(reportParams.media, m => Date.parse(m.createdAt)
or new Date(m.createdAt).getTime()) and handle invalid/NaN parse results (throw
same 'No media in report' or a clearer error). Replace the existing _.maxBy(...)
usage that produces maxCreatedAt with this parsed-time comparator and keep the
subsequent clampIncidentDateTimeToPast(maxCreatedAt) call using the original
createdAt string from the chosen media (or convert consistently) so
clampIncidentDateTimeToPast receives a valid datetime.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e45025ae-3341-4327-863a-8e9e9bc7617d

📥 Commits

Reviewing files that changed from the base of the PR and between 8111685 and 1447cf2.

📒 Files selected for processing (32)
  • client/src/components/ItemAction.tsx
  • client/src/graphql/generated.ts
  • client/src/webpages/dashboard/Dashboard.css
  • client/src/webpages/dashboard/Dashboard.tsx
  • client/src/webpages/dashboard/components/table/Table.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ManualReviewJobMagnifyImageComponent.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECReviewUser.tsx
  • client/src/webpages/dashboard/ncmec/NcmecReportsDashboard.tsx
  • server/graphql/generated.ts
  • server/graphql/modules/ncmec.ts
  • server/graphql/modules/org.ts
  • server/iocContainer/index.ts
  • server/plugins/warehouse/queries/ClickhouseActionExecutionsAdapter.test.ts
  • server/plugins/warehouse/queries/ClickhouseActionExecutionsAdapter.ts
  • server/plugins/warehouse/queries/IActionExecutionsAdapter.ts
  • server/rule_engine/ActionPublisher.test.ts
  • server/rule_engine/ActionPublisher.ts
  • server/services/itemInvestigationService/index.ts
  • server/services/itemInvestigationService/itemInvestigationServiceAdapter.ts
  • server/services/itemInvestigationService/synthesizeUserItemFromContentTarget.ts
  • server/services/manualReviewToolService/manualReviewToolService.ts
  • server/services/manualReviewToolService/modules/JobDecisioning.ts
  • server/services/ncmecService/buildSubmitReportParamsFromDecision.ts
  • server/services/ncmecService/index.ts
  • server/services/ncmecService/ncmecReporting.ts
  • server/services/ncmecService/ncmecService.ts
  • server/services/ncmecService/ncmecSubmissionFilters.test.ts
  • server/services/ncmecService/ncmecSubmissionFilters.ts
  • server/services/ncmecService/retryNcmecSubmission.test.ts
  • server/services/ncmecService/retryNcmecSubmission.ts
  • server/workers_jobs/RetryFailedNcmecDecisionsJob.ts
✅ Files skipped from review due to trivial changes (4)
  • server/services/ncmecService/ncmecSubmissionFilters.test.ts
  • server/services/ncmecService/ncmecSubmissionFilters.ts
  • client/src/graphql/generated.ts
  • server/graphql/generated.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/src/webpages/dashboard/mrt/manual_review_job/v2/ncmec/NCMECInspectedMedia.tsx

Comment thread client/src/webpages/dashboard/ncmec/NcmecReportsDashboard.tsx Outdated
Comment thread client/src/webpages/dashboard/ncmec/NcmecReportsDashboard.tsx
Comment thread server/services/ncmecService/buildSubmitReportParamsFromDecision.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

server/workers_jobs/RetryFailedNcmecDecisionsJob.ts:130

  • submitReport() now records ncmec_reports_errors rows itself when reportParams.jobId is set (including for UNSUPPORTED_ORG and ALL_MEDIA_MISSING). This job also calls insertOrUpdateNcmecReportError for those same outcomes, which will double-increment retry_count and can cause retries to stop earlier than intended. Consider removing the extra insertOrUpdateNcmecReportError writes here (or only writing when jobId is not supplied to submitReport).

Comment thread server/graphql/modules/org.ts Outdated
Comment thread server/services/ncmecService/buildSubmitReportParamsFromDecision.ts
Comment thread server/services/ncmecService/ncmecService.ts Outdated
Comment thread server/services/ncmecService/ncmecReporting.ts Outdated
@juanmrad
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 3 comments.

Comment thread server/services/ncmecService/ncmecService.ts Outdated
Comment thread server/services/ncmecService/retryNcmecSubmission.ts
Comment thread server/services/ncmecService/ncmecReporting.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.

Comment thread client/src/webpages/dashboard/components/table/Table.tsx
Comment on lines +2127 to +2131
try {
const status = opts.status ?? 'RETRYABLE_ERROR';
// Atomic increment in `doUpdateSet` avoids the read-modify-write race
// when two retries land on the same job_id concurrently.
await this.pgQuery
@juanmrad juanmrad merged commit 6f726ca into main May 19, 2026
13 checks passed
@juanmrad juanmrad deleted the fix-ncmec-reporting-unordered-reports branch May 19, 2026 02:11
@cassidyjames cassidyjames added the NCMEC Related to NCMEC integration/flow label May 19, 2026
@cassidyjames cassidyjames changed the title [NCMEC] Add additionalInfo to reports; fix XML element ordering and Node 24 multipart submission Add additionalInfo to NCMEC reports; fix XML element ordering and Node 24 multipart submission May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NCMEC Related to NCMEC integration/flow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add freetext "additional details" for NCMEC reports

4 participants