Mirrored from GitHub github.com/roostorg/coop

ncmec: tighten email field role from STRING to EMAIL_ADDRESS scalar (#842) master

* ncmec: tighten `email` field role from STRING to EMAIL_ADDRESS scalar Follow-up to #840 and #841. Now that `@roostorg/coop-types@2.4.0` ships a dedicated `EMAIL_ADDRESS` scalar, switch the email field role plumbing to require it instead of any STRING-typed field. Mirrors the two-PR pattern PR #559 → PR #583 used for `IP_ADDRESS`. - Bump `@roostorg/coop-types` to ^2.4.0 in server and client - Add `EMAIL_ADDRESS` to `ScalarType`, `SignalInputType`, `FieldType` SDL enums; regenerate codegen - Add `[ScalarTypes.EMAIL_ADDRESS]` handler in `fieldTypeHandlers.ts` (string coerce + trim; null on empty; format validation deferred per Caleb's note on #840) - `FieldRoleToScalarType.email`: STRING → EMAIL_ADDRESS - Client `schemaFieldRolesFieldTypes.EMAIL`: String → EmailAddress - Migration: drop the `valid_email_field_field_type` CHECK constraint (which required STRING) and replace with one requiring EMAIL_ADDRESS - Exhaustive-switch updates for `ManualReviewJobFieldsComponent` and `RuleTestModal` - `EmailAddressArbitrary` for fast-check test fixtures, with culturally diverse example names on RFC 2606 reserved domains Migration safety: any existing email_field mapping to a STRING field will fail the new constraint. #840 merged hours before this; no adopter should have configured the role yet. Operators between #840 and this migration who did configure it will need to either change their field type to EMAIL_ADDRESS or null out the mapping before applying. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * client: add EMAIL_ADDRESS cases to three exhaustive switches CI on #842 failed `npm run lint` (= `tsc --noEmit`) because widening `ScalarType` to include `EMAIL_ADDRESS` left three client switches incomplete. The `JsonValue` errors in `itemTypeCodeSampleUtils.ts` were downstream of `generateFakeScalarFieldValue` not handling the new scalar. - `signal.ts`: comparators for `EmailAddress` mirror `String` / `Url` / `IpAddress` - `RuleInsightsSamplesTable.tsx`: stringify (mirrors `IpAddress`) - `itemTypeUtils.ts:generateFakeScalarFieldValue`: returns a fake email on RFC 2606 `example.com` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ncmec: data-migrate stale email_field mappings before tightening CHECK Addresses Tao's review on #842: the original "no adopter has had time to configure the role" assumption was wrong. Deployments between #840 and this migration may have mapped `email_field` to a STRING field; the new CHECK constraint would reject those rows and block the migration entirely. Add a DO-block data migration that runs in the same transaction before the CHECK swap. For each item_type whose `email_field` doesn't point at an EMAIL_ADDRESS-typed field in `fields`, NULL out the mapping and emit a NOTICE naming the row (id, name, org_id) so operators can reconfigure via the admin UI if needed. The underlying STRING field's data is left untouched; only the role pointer is cleared. Header comment updated to reflect the new data-migration step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * db: forward postgres NOTICE events to console.warn during migrations Addresses Tao's review on #842: the migration's `RAISE NOTICE` lines were being swallowed because Sequelize's pg dialect doesn't surface server-side messages by default. Operators running `npm run db:update` saw the migration succeed but had no signal about which adopter configurations had been cleared. Wire a Sequelize `afterConnect` hook in `pg-base.ts` that attaches a `notice` listener to every pg client the pool opens, forwarding each message to `console.warn` prefixed with the severity. Future migrations that use `RAISE NOTICE` (or `RAISE WARNING`) will also benefit, not just this one. Update the migration's header comment to direct operators to scan the `db:update` output for `[postgres NOTICE]` lines after applying, so the cleared mappings don't go unnoticed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ncmec: wire HMA hashes into outgoing report's fileDetails.originalFileHash (#844) NCMEC's CyberTipline accepts an `originalFileHash[]` element with a `hashType` attribute per entry. HMA already computes per-image hashes at item-submission time (`HMAHashBankService.hashContentFromUrl`, stored on the image object alongside `url`), but the NCMEC report builder discarded them. - Add `OriginalFileHash` type and `originalFileHash?: OriginalFileHash[]` to `FileDetails` (positioned after `industryClassification`, before `ipCaptureEvent`, in line with NCMEC XSD ordering for forensic fields). - Add `hashes?: Record<string, string>` to the internal `Media` type so the report assembly can pass them through to `#upload`. - `buildSubmitReportParamsFromDecision` now extracts hashes from the matching image in the item data (walks scalar, array, and map containers). New `extractHashesForUrl` helper. - `#upload` converts the hashes via `toOriginalFileHashes` and includes the resulting array on `fileDetails` when non-empty. - New `toOriginalFileHashes` exported helper handles trimming, empty-value filtering, and the algorithm-name uppercasing for the `hashType` attribute. Tests: - 4 new in `buildSubmitReportParamsFromDecision.test.ts` covering the scalar IMAGE case, ARRAY-of-IMAGE container traversal, URL-mismatch omission, and missing-hashes omission. - 4 new in `ncmecReporting.test.ts` for `toOriginalFileHashes`'s precedence rules (uppercasing, trimming, empty-value drops, undefined-on-empty). This is one of the P1 items from the audit in #843. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * db: also lower client_min_messages so RAISE NOTICE actually surfaces Initial fix only attached a notice listener, but verified locally that NOTICE messages from RAISE NOTICE inside PL/pgSQL DO blocks were still being filtered out. Root cause: Sequelize raises `client_min_messages` to WARNING by default, which makes postgres filter NOTICE-severity messages server-side before they reach the client. The listener was correctly attached but never fired for NOTICEs. In the same afterConnect hook, also issue `SET client_min_messages = 'notice'` so postgres sends NOTICE messages to the client. Verified locally: per-row NOTICEs from the email-field-clearing migration now appear in `db:update` output as expected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ncmec: wire webhook fileDetails.hash into outgoing report's originalFileHash (#850) Closes the follow-up gap noted in PR #844. The additional-info webhook can return a single `{ hash, hashType }` per media item; #844 wired HMA-sourced hashes through but webhook-sourced hashes were still dropped because: 1. `MediaAdditionalInfo` (the internal type that `#upload` consumes) didn't declare `fileDetails`; the validation-schema-typed `NcmecAdditionalInfoResponse` has it, but the data was erased at the boundary between webhook response and internal use. 2. `toOriginalFileHashes` took only the HMA map as input. Changes: - Add `fileDetails?: { hash; hashType }` to `MediaAdditionalInfo`. - Refactor `toOriginalFileHashes` to take both sources via an opts object (`hmaHashes`, `webhookFileDetails`). Hashes from both are combined and deduped on (`hashType` uppercase, trimmed hash value) so a webhook returning the same algorithm as HMA produces one entry in the outgoing report, not two. - Update `#upload` call site to pass both sources. - Remove dead `fileDetails: { ipCaptureEvent: [] }` from the no-webhook fallback; it never matched any consumed shape and nothing read it (the `ipCaptureEvent` lives at the top level of `MediaAdditionalInfo`, not nested). Tests: 6 new cases covering webhook-only, both sources, exact-match dedup, same-algorithm-different-hash kept-both, whitespace-only webhook hash dropped, and case-insensitive dedup on algorithm name. The toOriginalFileHashes test block is moved to its own file (`toOriginalFileHashes.test.ts`) to keep `ncmecReporting.test.ts` under the 500-line max-lines limit. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>